init commit, hangfire.raven is working
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
root = true
|
||||
|
||||
[*.cs,*.ps1]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
@@ -0,0 +1,22 @@
|
||||
# Auto detect text files and perform LF normalization
|
||||
* text=unset
|
||||
|
||||
# Custom for Visual Studio
|
||||
*.cs diff=csharp
|
||||
*.sln merge=union
|
||||
*.csproj merge=union
|
||||
*.vbproj merge=union
|
||||
*.fsproj merge=union
|
||||
*.dbproj merge=union
|
||||
|
||||
# Standard to msysgit
|
||||
*.doc diff=astextplain
|
||||
*.DOC diff=astextplain
|
||||
*.docx diff=astextplain
|
||||
*.DOCX diff=astextplain
|
||||
*.dot diff=astextplain
|
||||
*.DOT diff=astextplain
|
||||
*.pdf diff=astextplain
|
||||
*.PDF diff=astextplain
|
||||
*.rtf diff=astextplain
|
||||
*.RTF diff=astextplain
|
||||
Binary file not shown.
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="7-Zip.CommandLine" version="9.20.0" />
|
||||
<package id="coveralls.io" version="1.3.4" />
|
||||
<package id="Hangfire.Build" version="0.1.3" />
|
||||
<package id="ilmerge" version="2.14.1208" />
|
||||
<package id="OpenCover" version="4.5.3723" />
|
||||
<package id="psake" version="4.4.1" />
|
||||
<package id="xunit.runners" version="1.9.2" />
|
||||
</packages>
|
||||
@@ -0,0 +1,57 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 2012
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hangfire.Core", "src\Hangfire.Core\Hangfire.Core.csproj", "{C995EA9E-56EE-4951-8260-D94260A7F4C2}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hangfire.SqlServer", "src\Hangfire.SqlServer\Hangfire.SqlServer.csproj", "{A523C0E3-097D-4869-977F-15A717EA3E83}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hangfire.SqlServer.Msmq", "src\Hangfire.SqlServer.Msmq\Hangfire.SqlServer.Msmq.csproj", "{762BE479-0AEC-47E0-8F9C-34FA54641749}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{9FC4F973-0AA1-4E6E-AF9A-561629BE50EC}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hangfire.Core.Tests", "tests\Hangfire.Core.Tests\Hangfire.Core.Tests.csproj", "{E13C3543-39A3-475C-BB43-2E311E634843}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hangfire.SqlServer.RabbitMq", "src\Hangfire.SqlServer.RabbitMq\Hangfire.SqlServer.RabbitMq.csproj", "{4CC51F69-0311-4485-B7DE-9ECAB3A1B5E5}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hangfire.SqlServer.RabbitMq.Tests", "tests\Hangfire.SqlServer.RabbitMq.Tests\Hangfire.SqlServer.RabbitMq.Tests.csproj", "{2654D751-33D5-411D-A187-D057FED9CD25}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{C995EA9E-56EE-4951-8260-D94260A7F4C2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{C995EA9E-56EE-4951-8260-D94260A7F4C2}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{C995EA9E-56EE-4951-8260-D94260A7F4C2}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{C995EA9E-56EE-4951-8260-D94260A7F4C2}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{A523C0E3-097D-4869-977F-15A717EA3E83}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{A523C0E3-097D-4869-977F-15A717EA3E83}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{A523C0E3-097D-4869-977F-15A717EA3E83}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{A523C0E3-097D-4869-977F-15A717EA3E83}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{762BE479-0AEC-47E0-8F9C-34FA54641749}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{762BE479-0AEC-47E0-8F9C-34FA54641749}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{762BE479-0AEC-47E0-8F9C-34FA54641749}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{762BE479-0AEC-47E0-8F9C-34FA54641749}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{E13C3543-39A3-475C-BB43-2E311E634843}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{E13C3543-39A3-475C-BB43-2E311E634843}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{E13C3543-39A3-475C-BB43-2E311E634843}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{E13C3543-39A3-475C-BB43-2E311E634843}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{4CC51F69-0311-4485-B7DE-9ECAB3A1B5E5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{4CC51F69-0311-4485-B7DE-9ECAB3A1B5E5}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{4CC51F69-0311-4485-B7DE-9ECAB3A1B5E5}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{4CC51F69-0311-4485-B7DE-9ECAB3A1B5E5}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{2654D751-33D5-411D-A187-D057FED9CD25}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{2654D751-33D5-411D-A187-D057FED9CD25}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{2654D751-33D5-411D-A187-D057FED9CD25}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{2654D751-33D5-411D-A187-D057FED9CD25}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{E13C3543-39A3-475C-BB43-2E311E634843} = {9FC4F973-0AA1-4E6E-AF9A-561629BE50EC}
|
||||
{25909EE6-2A56-4EAC-9D0E-436FF2435E88} = {9FC4F973-0AA1-4E6E-AF9A-561629BE50EC}
|
||||
{2654D751-33D5-411D-A187-D057FED9CD25} = {9FC4F973-0AA1-4E6E-AF9A-561629BE50EC}
|
||||
EndGlobalSection
|
||||
GlobalSection(MonoDevelopProperties) = preSolution
|
||||
StartupItem = src\Hangfire.Core\Hangfire.Core.csproj
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@@ -0,0 +1,45 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 14
|
||||
VisualStudioVersion = 14.0.23107.0
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConsoleSample", "samples\ConsoleSample\ConsoleSample.csproj", "{C02BB718-2AE4-434C-8668-C894FF663FCE}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{119DA7FA-B94C-4B63-AEC9-428EF834E0D8}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hangfire.Raven", "src\Hangfire.SqlServer\Hangfire.Raven.csproj", "{A523C0E3-097D-4869-977F-15A717EA3E83}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{766BE831-F758-46BC-AFD3-BBEEFE0F686F}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "nuspecs", "nuspecs", "{5E687025-A525-4534-8869-CB685C7B11C4}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
nuspecs\Hangfire.Raven.nuspec = nuspecs\Hangfire.Raven.nuspec
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{F710C9CF-69F9-4373-9B74-ABB185BF985B}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
.nuget\packages.config = .nuget\packages.config
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{C02BB718-2AE4-434C-8668-C894FF663FCE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{C02BB718-2AE4-434C-8668-C894FF663FCE}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{C02BB718-2AE4-434C-8668-C894FF663FCE}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{C02BB718-2AE4-434C-8668-C894FF663FCE}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{A523C0E3-097D-4869-977F-15A717EA3E83}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{A523C0E3-097D-4869-977F-15A717EA3E83}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{A523C0E3-097D-4869-977F-15A717EA3E83}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{A523C0E3-097D-4869-977F-15A717EA3E83}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{C02BB718-2AE4-434C-8668-C894FF663FCE} = {119DA7FA-B94C-4B63-AEC9-428EF834E0D8}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@@ -0,0 +1,6 @@
|
||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<s:Boolean x:Key="/Default/CodeInspection/CodeAnnotations/NamespacesWithAnnotations/=Hangfire_002EAnnotations/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeInspection/CodeAnnotations/NamespacesWithAnnotations/=Hangfire_002ERedis_002EAnnotations/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeInspection/CodeAnnotations/NamespacesWithAnnotations/=Hangfire_002ESqlServer_002EAnnotations/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:String x:Key="/Default/FilterSettingsManager/CoverageFilterXml/@EntryValue"><data><IncludeFilters /><ExcludeFilters><Filter ModuleMask="Hangfire.Core.Tests" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Hangfire.SqlServer.Tests" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /></ExcludeFilters></data></s:String>
|
||||
<s:String x:Key="/Default/FilterSettingsManager/AttributeFilterXml/@EntryValue"><data /></s:String></wpf:ResourceDictionary>
|
||||
@@ -1,202 +0,0 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright {yyyy} {name of copyright owner}
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
@@ -0,0 +1,170 @@
|
||||
This is a work on progress! Don't use it yet.
|
||||
|
||||
|
||||
Hangfire
|
||||
=========
|
||||
|
||||
[](http://hangfire.io) [](https://www.nuget.org/packages?q=hangfire) [](https://ci.appveyor.com/project/odinserj/hangfire-525) [](https://travis-ci.org/HangfireIO/Hangfire) [](http://www.gnu.org/licenses/lgpl-3.0.html)
|
||||
|
||||
Incredibly easy way to perform **fire-and-forget**, **delayed** and **recurring jobs** inside **ASP.NET applications**. CPU and I/O intensive, long-running and short-running jobs are supported. No Windows Service / Task Scheduler required. Backed by Redis, SQL Server, SQL Azure or MSMQ.
|
||||
|
||||
Hangfire provides unified programming model to handle background tasks in a **reliable way** and run them on shared hosting, dedicated hosting or in cloud. You can start with a simple setup and grow computational power for background jobs with time for these scenarios:
|
||||
|
||||
- mass notifications/newsletter;
|
||||
- batch import from xml, csv, json;
|
||||
- creation of archives;
|
||||
- firing off web hooks;
|
||||
- deleting users;
|
||||
- building different graphs;
|
||||
- image/video processing;
|
||||
- purge temporary files;
|
||||
- recurring automated reports;
|
||||
- database maintenance;
|
||||
- *…and so on.*
|
||||
|
||||
Hangfire is a .NET Framework alternative to [Resque](https://github.com/resque/resque), [Sidekiq](http://sidekiq.org), [delayed_job](https://github.com/collectiveidea/delayed_job), [Celery](http://www.celeryproject.org).
|
||||
|
||||

|
||||
|
||||
Installation
|
||||
-------------
|
||||
|
||||
Hangfire is available as a NuGet package. So, you can install it using the NuGet Package Console window:
|
||||
|
||||
```
|
||||
PM> Install-Package Hangfire
|
||||
```
|
||||
|
||||
After install, update your existing [OWIN Startup](http://www.asp.net/aspnet/overview/owin-and-katana/owin-startup-class-detection) file with the following lines of code. If you do not have this class in your project or don't know what is it, please read the [Quick start](http://docs.hangfire.io/en/latest/quickstart.html) guide to learn about how to install Hangfire.
|
||||
|
||||
```csharp
|
||||
public void Configuration(IAppBuilder app)
|
||||
{
|
||||
GlobalConfiguration.Configuration.UseSqlServerStorage("<connection string or its name>");
|
||||
|
||||
app.UseHangfireServer();
|
||||
app.UseHangfireDashboard();
|
||||
}
|
||||
```
|
||||
|
||||
Usage
|
||||
------
|
||||
|
||||
This is incomplete list of features, to see all of them, check the [official site](http://hangfire.io) and the [documentation](http://docs.hangfire.io).
|
||||
|
||||
[**Fire-and-forget tasks**](http://docs.hangfire.io/en/latest/users-guide/background-methods/calling-methods-in-background.html)
|
||||
|
||||
Enqueued background jobs are being executed inside a dedicated worker pool threads as soon as possible, shortening your request processing time.
|
||||
|
||||
```csharp
|
||||
BackgroundJob.Enqueue(() => Console.WriteLine("Simple!"));
|
||||
```
|
||||
|
||||
[**Delayed tasks**](http://docs.hangfire.io/en/latest/users-guide/background-methods/calling-methods-with-delay.html)
|
||||
|
||||
Scheduled background jobs are being executed only after given amount of time.
|
||||
|
||||
```csharp
|
||||
BackgroundJob.Schedule(() => Console.WriteLine("Reliable!"), TimeSpan.FromDays(7));
|
||||
```
|
||||
|
||||
[**Recurring tasks**](http://docs.hangfire.io/en/latest/users-guide/background-methods/performing-recurrent-tasks.html)
|
||||
|
||||
Recurring jobs were never been simpler, just call the following method to perform any kind of recurring task using the [CRON expressions](http://en.wikipedia.org/wiki/Cron#CRON_expression).
|
||||
|
||||
```csharp
|
||||
RecurringJob.AddOrUpdate(() => Console.WriteLine("Transparent!"), Cron.Daily);
|
||||
```
|
||||
|
||||
**Continuations**
|
||||
|
||||
Continuations allow you to define complex workflows by chaining multiple background jobs together.
|
||||
|
||||
```csharp
|
||||
var id = BackgroundJob.Enqueue(() => Console.WriteLine("Hello, "));
|
||||
BackgroundJob.ContinueWith(id, () => Console.WriteLine("world!"));
|
||||
```
|
||||
|
||||
**Process them inside a web application…**
|
||||
|
||||
You can process background tasks in any OWIN compatible application frameworks, including [ASP.NET MVC](http://www.asp.net/mvc), [ASP.NET Web API](http://www.asp.net/web-api), [FubuMvc](http://fubu-project.org), [Nancy](http://nancyfx.org), etc. Forget about [AppDomain unloads, Web Garden & Web Farm issues](http://haacked.com/archive/2011/10/16/the-dangers-of-implementing-recurring-background-tasks-in-asp-net.aspx/) – Hangfire is reliable for web applications from scratch, even on shared hosting.
|
||||
|
||||
```csharp
|
||||
app.UseHangfireServer();
|
||||
```
|
||||
|
||||
**… or anywhere else**
|
||||
|
||||
In console application, Windows Service, Azure Worker Role, etc.
|
||||
|
||||
```csharp
|
||||
using (new BackgroundJobServer())
|
||||
{
|
||||
Console.WriteLine("Hangfire Server started. Press ENTER to exit...");
|
||||
Console.ReadLine();
|
||||
}
|
||||
```
|
||||
|
||||
Questions? Problems?
|
||||
---------------------
|
||||
|
||||
Open-source project are developing more smoothly, when all discussions are held in public.
|
||||
|
||||
If you have any questions, problems related to the Hangfire usage or want to discuss new features, please visit the [discussion forum](http://discuss.hangfire.io). You can sign in there using your existing Google or GitHub account, so it's very simple to start using it.
|
||||
|
||||
If you've discovered a bug, please report it to the [Hangfire GitHub Issues](https://github.com/odinserj/Hangfire/issues?state=open). Detailed reports with stack traces, actual and expected behavours are welcome.
|
||||
|
||||
Related Projects
|
||||
-----------------
|
||||
|
||||
Please see the [Extensions](http://hangfire.io/extensions.html) page on official site.
|
||||
|
||||
Building the sources
|
||||
---------------------
|
||||
|
||||
To build a solution and get assembly files, just run the following command. All build artifacts, including `*.pdb` files will be placed into the `build` folder. **Before proposing a pull request, please use this command to ensure everything is ok.** Btw, you can execute this command from Package Manager Console window.
|
||||
|
||||
```
|
||||
build
|
||||
```
|
||||
|
||||
To build NuGet packages as well as an archive file, use the `pack` command as shown below. You can find the result files in the `build` folder.
|
||||
|
||||
```
|
||||
build pack
|
||||
```
|
||||
|
||||
To see the full list of avalable commands, pass the `-docs` switch:
|
||||
|
||||
```
|
||||
build -docs
|
||||
```
|
||||
|
||||
Hangfire uses [psake](https://github.com/psake/psake) build automation tool. All psake tasks and functions defined in `psake-build.ps1` (for this project) and `psake-common.ps1` (for other Hangfire projects) files. Thanks to the psake project, they are very simple to use and modify!
|
||||
|
||||
License
|
||||
--------
|
||||
|
||||
Copyright © 2013-2014 Sergey Odinokov.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with this program. If not, see [http://www.gnu.org/licenses/](http://www.gnu.org/licenses).
|
||||
|
||||
Legal
|
||||
------
|
||||
|
||||
By submitting a Pull Request, you disavow any rights or claims to any changes submitted to the Hangfire project and assign the copyright of those changes to Sergey Odinokov.
|
||||
|
||||
If you cannot or do not want to reassign those rights (your employment contract for your employer may not allow this), you should not submit a PR. Open an issue and someone else can do the work.
|
||||
|
||||
This is a legal way of saying "If you submit a PR to us, that code becomes ours". 99.9% of the time that's what you intend anyways; we hope it doesn't scare you away from contributing.
|
||||
@@ -0,0 +1,37 @@
|
||||
Please see http://docs.hangfire.io for more information on using Hangfire. The
|
||||
`Hangfire` package is using SQL Server as a job storage and intended to run in
|
||||
any OWIN-based web application.
|
||||
|
||||
To run Hangfire in your application, update the existing OWIN Startup class or
|
||||
create the file `Startup.cs` in the root folder of your application as shown
|
||||
below. Please see also http://docs.hangfire.io/en/latest/quickstart.html.
|
||||
|
||||
!!! DASHBOARD REQUIRES AUTH CONFIGURATION !!!
|
||||
|
||||
By default, ONLY LOCAL requests are allowed to access the Dashboard. Please
|
||||
see the `Configuring Dashboard authorization` section in Hangfire documentation:
|
||||
http://docs.hangfire.io/en/latest/configuration/configuring-authorization.html
|
||||
|
||||
Sample OWIN Startup class
|
||||
--------------------------
|
||||
|
||||
using Hangfire;
|
||||
using Microsoft.Owin;
|
||||
using Owin;
|
||||
|
||||
[assembly: OwinStartup(typeof(MyWebApplication.Startup))]
|
||||
|
||||
namespace MyWebApplication
|
||||
{
|
||||
public class Startup
|
||||
{
|
||||
public void Configuration(IAppBuilder app)
|
||||
{
|
||||
GlobalConfiguration.Configuration
|
||||
.UseSqlServerStorage("<name or connection string>");
|
||||
|
||||
app.UseHangfireDashboard();
|
||||
app.UseHangfireServer();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
<?xml version="1.0"?>
|
||||
<package >
|
||||
<metadata>
|
||||
<id>Hangfire.Core</id>
|
||||
<version>0.0.0</version>
|
||||
<title>Hangfire Core Components</title>
|
||||
<authors>Sergey Odinokov</authors>
|
||||
<owners>odinserj</owners>
|
||||
<projectUrl>http://hangfire.io/</projectUrl>
|
||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||
<licenseUrl>https://raw.github.com/HangfireIO/Hangfire/master/LICENSE.md</licenseUrl>
|
||||
<description>Core components for Hangfire (background job system for ASP.NET applications).</description>
|
||||
<copyright>Copyright © 2013-2015 Sergey Odinokov</copyright>
|
||||
<tags>Hangfire OWIN Long-Running Background Fire-And-Forget Delayed Recurring Tasks Jobs Scheduler Threading Queues</tags>
|
||||
<releaseNotes>http://hangfire.io/blog/
|
||||
|
||||
1.5.0
|
||||
* Added – `IBackgroundProcess` interface that allows to implement custom background processes.
|
||||
* Added – `BackgroundProcessingServer` class for creating a server instance with completely custom background processes.
|
||||
* Added – Support for running multiple `BackgroundJobServer` instances in the same AppDomain without explicit server names.
|
||||
* Added – `JobActivatorScope` class to support custom IoC scopes/lifetimes.
|
||||
* Added – Allow to specify a custom queue for recurring jobs (by @maleet).
|
||||
* Added – Allow to use CancellationToken type as a job method parameter.
|
||||
* Added – It is now possible to query the storage in state applying filters.
|
||||
* Added – Allow to pass custom filters to `BackgroundJobServer` via options.
|
||||
* Added – Option to hide the "Back to site" link (by @dradovic).
|
||||
* Changed – Server components and background processes now use `Task` with `TaskCreationOptions.LongRunning`.
|
||||
* Changed – ServerIds are now generated without ServerName, server names are deprecated.
|
||||
* Changed – Arguments Renderer now supports TimeSpan and DateTime (by @yngndrw).
|
||||
* Changed – Minor improvements for Dashboard navigation (by @pwelter34).
|
||||
* Changed – Worker does not process a job when a transition to the Processing state failed (by @yngndrw).
|
||||
* Changed – `BackgroundJobClientException` is thrown instead of `CreateJobFailedException`.
|
||||
* Changed – WorkerId is now auto-generated and based on a GUID.
|
||||
* Changed – Use StackTraceFormatter package for marking-up stack traces (by @atifaziz).
|
||||
* Changed – Updated Dashboard CSS to Twitter Bootstrap 3.3.5.
|
||||
* Fixed – Prevent worker from fetching and processing a job on shutdown.
|
||||
* Fixed – `TaskCanceledException` thrown from a job method leads to failed job as expected.
|
||||
* Fixed – Make `DelayedJobScheduler` resilient to empty background job ids.
|
||||
* Fixed – Logical error in SqlServerDistributedLockTimeout value check (by @Tom-Kelly).
|
||||
* Fixed – `OverflowException` in Dashboard when job duration exceeds int.MaxValue (by @mavenius).
|
||||
* Fixed – "More details" button in Dashboard changes to "Less details" when collapsed (by @pascalberger).
|
||||
</releaseNotes>
|
||||
<dependencies>
|
||||
<dependency id="Owin" version="1.0" />
|
||||
<dependency id="Newtonsoft.Json" version="5.0.0" />
|
||||
</dependencies>
|
||||
</metadata>
|
||||
<files>
|
||||
<file src="Net45\Hangfire.Core.dll" target="lib\net45" />
|
||||
<file src="Net45\Hangfire.Core.xml" target="lib\net45" />
|
||||
<file src="Net45\Hangfire.Core.pdb" target="lib\net45" />
|
||||
<file src="..\src\Hangfire.Core\**\*.cs;..\src\Hangfire.Core\**\*.cshtml" target="src" />
|
||||
</files>
|
||||
</package>
|
||||
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0"?>
|
||||
<package >
|
||||
<metadata>
|
||||
<dependencies>
|
||||
<dependency id="Hangfire.Core" version="[0.0.0]" />
|
||||
</dependencies>
|
||||
</metadata>
|
||||
<files>
|
||||
<file src="Net45\Hangfire.SqlServer.dll" target="lib\net45" />
|
||||
<file src="Net45\Hangfire.SqlServer.xml" target="lib\net45" />
|
||||
<file src="Net45\Hangfire.SqlServer.pdb" target="lib\net45" />
|
||||
<file src="Tools\DefaultInstall.sql" target="tools\install.sql" />
|
||||
<file src="..\src\Hangfire.SqlServer\**\*.cs" target="src" />
|
||||
</files>
|
||||
</package>
|
||||
@@ -0,0 +1,35 @@
|
||||
<?xml version="1.0"?>
|
||||
<package >
|
||||
<metadata>
|
||||
<id>Hangfire.SqlServer.MSMQ</id>
|
||||
<version>0.0.0</version>
|
||||
<title>Hangfire MSMQ Queues for SQL Server Storage</title>
|
||||
<authors>Sergey Odinokov</authors>
|
||||
<owners>odinserj</owners>
|
||||
<projectUrl>http://hangfire.io/</projectUrl>
|
||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||
<licenseUrl>https://raw.github.com/HangfireIO/Hangfire/master/LICENSE.md</licenseUrl>
|
||||
<description>MSMQ queues support for SQL Server job storage implementation for Hangfire (background job system for ASP.NET applications).</description>
|
||||
<copyright>Copyright © 2014-2015 Sergey Odinokov</copyright>
|
||||
<tags>Hangfire SqlServer MSMQ</tags>
|
||||
<releaseNotes>http://hangfire.io/blog/
|
||||
|
||||
1.5.0
|
||||
* Added – Support for remote MSMQ queues through DTC transactions.
|
||||
|
||||
1.4.0
|
||||
* Changed – Speed up `GetCount` method with native implementation.
|
||||
* Fixed – Incorrect order of fetching when multiple queues used.
|
||||
</releaseNotes>
|
||||
<dependencies>
|
||||
<dependency id="Hangfire.Core" version="[0.0.0]" />
|
||||
<dependency id="Hangfire.SqlServer" version="1.2.2" />
|
||||
</dependencies>
|
||||
</metadata>
|
||||
<files>
|
||||
<file src="Net45\Hangfire.SqlServer.Msmq.dll" target="lib\net45" />
|
||||
<file src="Net45\Hangfire.SqlServer.Msmq.xml" target="lib\net45" />
|
||||
<file src="Net45\Hangfire.SqlServer.Msmq.pdb" target="lib\net45" />
|
||||
<file src="..\src\Hangfire.SqlServer.Msmq\**\*.cs" target="src" />
|
||||
</files>
|
||||
</package>
|
||||
@@ -0,0 +1,35 @@
|
||||
<?xml version="1.0"?>
|
||||
<package >
|
||||
<metadata>
|
||||
<id>Hangfire.SqlServer.RabbitMQ</id>
|
||||
<version>0.0.0</version>
|
||||
<title>Hangfire RabbitMQ Queues for SQL Server Storage</title>
|
||||
<authors>Denny Ferrassoli</authors>
|
||||
<owners>dennyferra</owners>
|
||||
<projectUrl>http://hangfire.io/</projectUrl>
|
||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||
<licenseUrl>https://raw.github.com/HangfireIO/Hangfire/master/LICENSE.md</licenseUrl>
|
||||
<description>RabbitMQ queues support for SQL Server job storage implementation for Hangfire (background job system for ASP.NET applications).</description>
|
||||
<copyright>Copyright © 2014 Denny Ferrassoli</copyright>
|
||||
<tags>Hangfire SqlServer RabbitMQ</tags>
|
||||
<releaseNotes>http://hangfire.io/blog/
|
||||
|
||||
1.4.2
|
||||
* Fixed – Incompatibility issue with RabbitMQ.Client >= 3.4.0 (by @justmara).
|
||||
|
||||
1.4.0
|
||||
* Added – Allow a URI to be used for `RabbitMQConnectionConfiguration` (by @dennyferra).
|
||||
</releaseNotes>
|
||||
<dependencies>
|
||||
<dependency id="Hangfire.Core" version="[0.0.0]" />
|
||||
<dependency id="Hangfire.SqlServer" version="[0.0.0]" />
|
||||
<dependency id="RabbitMQ.Client" version="3.3.0" />
|
||||
</dependencies>
|
||||
</metadata>
|
||||
<files>
|
||||
<file src="Net45\Hangfire.SqlServer.RabbitMQ.dll" target="lib\net45" />
|
||||
<file src="Net45\Hangfire.SqlServer.RabbitMQ.xml" target="lib\net45" />
|
||||
<file src="Net45\Hangfire.SqlServer.RabbitMQ.xml" target="lib\net45" />
|
||||
<file src="..\src\Hangfire.SqlServer.RabbitMQ\**\*.cs" target="src" />
|
||||
</files>
|
||||
</package>
|
||||
@@ -0,0 +1,71 @@
|
||||
<?xml version="1.0"?>
|
||||
<package >
|
||||
<metadata>
|
||||
<id>Hangfire</id>
|
||||
<version>0.0.0</version>
|
||||
<title>Hangfire</title>
|
||||
<authors>Sergey Odinokov</authors>
|
||||
<owners>odinserj</owners>
|
||||
<projectUrl>http://hangfire.io/</projectUrl>
|
||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||
<licenseUrl>https://raw.github.com/HangfireIO/Hangfire/master/LICENSE.md</licenseUrl>
|
||||
<description>
|
||||
An easy and reliable way to perform fire-and-forget, delayed and recurring long-running, short-running, CPU or I/O intensive tasks inside ASP.NET application. No Windows Service / Task Scheduler required. Even ASP.NET is not required.
|
||||
Backed by Redis, SQL Server, SQL Azure or MSMQ. This is a .NET alternative to Sidekiq, Resque and Celery.
|
||||
http://hangfire.io/
|
||||
</description>
|
||||
<copyright>Copyright © 2013-2015 Sergey Odinokov</copyright>
|
||||
<tags>Hangfire AspNet MVC OWIN SqlServer Long-Running Background Fire-And-Forget Delayed Recurring Tasks Jobs Scheduler Threading Queues</tags>
|
||||
<releaseNotes>http://hangfire.io/blog/
|
||||
|
||||
1.5.0
|
||||
|
||||
Hangfire.Core
|
||||
* Added – `IBackgroundProcess` interface that allows to implement custom background processes.
|
||||
* Added – `BackgroundProcessingServer` class for creating a server instance with completely custom background processes.
|
||||
* Added – Support for running multiple `BackgroundJobServer` instances in the same AppDomain without explicit server names.
|
||||
* Added – `JobActivatorScope` class to support custom IoC scopes/lifetimes.
|
||||
* Added – Allow to specify a custom queue for recurring jobs (by @maleet).
|
||||
* Added – Allow to use CancellationToken type as a job method parameter.
|
||||
* Added – It is now possible to query the storage in state applying filters.
|
||||
* Added – Allow to pass custom filters to `BackgroundJobServer` via options.
|
||||
* Added – Option to hide the "Back to site" link (by @dradovic).
|
||||
* Changed – Server components and background processes now use `Task` with `TaskCreationOptions.LongRunning`.
|
||||
* Changed – ServerIds are now generated without ServerName, server names are deprecated.
|
||||
* Changed – Arguments Renderer now supports TimeSpan and DateTime (by @yngndrw).
|
||||
* Changed – Minor improvements for Dashboard navigation (by @pwelter34).
|
||||
* Changed – Worker does not process a job when a transition to the Processing state failed (by @yngndrw).
|
||||
* Changed – `BackgroundJobClientException` is thrown instead of `CreateJobFailedException`.
|
||||
* Changed – WorkerId is now auto-generated and based on a GUID.
|
||||
* Changed – Use StackTraceFormatter package for marking-up stack traces (by @atifaziz).
|
||||
* Changed – Updated Dashboard CSS to Twitter Bootstrap 3.3.5.
|
||||
* Fixed – Prevent worker from fetching and processing a job on shutdown.
|
||||
* Fixed – `TaskCanceledException` thrown from a job method leads to failed job as expected.
|
||||
* Fixed – Make `DelayedJobScheduler` resilient to empty background job ids.
|
||||
* Fixed – Logical error in SqlServerDistributedLockTimeout value check (by @Tom-Kelly).
|
||||
* Fixed – `OverflowException` in Dashboard when job duration exceeds int.MaxValue (by @mavenius).
|
||||
* Fixed – "More details" button in Dashboard changes to "Less details" when collapsed (by @pascalberger).
|
||||
|
||||
Hangfire.SqlServer
|
||||
* Added – Ability to specify the schema for SQL Server job storage (by @ryanmwright).
|
||||
* Added – `TransactionTimeout` option for configuring the `TransactionScope` timeout.
|
||||
* Added – `DashboardJobListLimit` option to prevent full table scans in Dashboard.
|
||||
* Changed – `SqlServerDistributedLock` class is now public.
|
||||
* Changed – Use `rowlock` table hint in job fetch query to prevent escalation.
|
||||
* Changed – Dramatically improved page load times in Dashboard.
|
||||
* Changed – Instant re-queue for background jobs even after ungraceful shutdown.
|
||||
* Changed – `JobQueue.Queue` and `Server.Id` column length increased to 50 and 100 respectively (by @maleet).
|
||||
|
||||
Hangfire.SqlServer.Msmq
|
||||
* Added – Support for remote MSMQ queues through DTC transactions.
|
||||
</releaseNotes>
|
||||
<dependencies>
|
||||
<dependency id="Hangfire.Core" version="[0.0.0]" />
|
||||
<dependency id="Hangfire.SqlServer" version="[0.0.0]" />
|
||||
<dependency id="Microsoft.Owin.Host.SystemWeb" version="3.0.0" />
|
||||
</dependencies>
|
||||
</metadata>
|
||||
<files>
|
||||
<file src="Content\readme.txt" target="readme.txt" />
|
||||
</files>
|
||||
</package>
|
||||
@@ -0,0 +1,86 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{C02BB718-2AE4-434C-8668-C894FF663FCE}</ProjectGuid>
|
||||
<OutputType>Exe</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>ConsoleSample</RootNamespace>
|
||||
<AssemblyName>ConsoleSample</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
|
||||
<TargetFrameworkProfile />
|
||||
<NuGetPackageImportStamp>89909a41</NuGetPackageImportStamp>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<CodeAnalysisRuleSet>ManagedMinimumRules.ruleset</CodeAnalysisRuleSet>
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<StartupObject />
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Hangfire.Core, Version=1.5.1.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Hangfire.Core.1.5.1\lib\net45\Hangfire.Core.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Newtonsoft.Json, Version=4.5.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Newtonsoft.Json.5.0.1\lib\net45\Newtonsoft.Json.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Owin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=f0ebd12fd5e55cc5, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Owin.1.0\lib\net40\Owin.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="GenericServices.cs" />
|
||||
<Compile Include="Program.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Services.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="app.config" />
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Hangfire.SqlServer\Hangfire.Raven.csproj">
|
||||
<Project>{a523c0e3-097d-4869-977f-15a717ea3e83}</Project>
|
||||
<Name>Hangfire.Raven</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
</Target>
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
</Project>
|
||||
@@ -0,0 +1,12 @@
|
||||
using System;
|
||||
|
||||
namespace ConsoleSample
|
||||
{
|
||||
public class GenericServices<TType>
|
||||
{
|
||||
public void Method<TMethod>(TType arg1, TMethod arg2)
|
||||
{
|
||||
Console.WriteLine("Arg1: {0}, Arg2: {1}", arg1, arg2);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
using System;
|
||||
using Hangfire;
|
||||
using HangFire.Raven.Storage;
|
||||
|
||||
namespace ConsoleSample
|
||||
{
|
||||
public static class Program
|
||||
{
|
||||
public static int x = 0;
|
||||
|
||||
public static void Main()
|
||||
{
|
||||
// you can use Raven Storage and specify the connection string and database name
|
||||
GlobalConfiguration.Configuration
|
||||
.UseColouredConsoleLogProvider()
|
||||
.UseRavenStorage("http://localhost:9090", "hangfire2");
|
||||
|
||||
// you can use Raven Embedded Storage which runs in memory!
|
||||
//GlobalConfiguration.Configuration
|
||||
// .UseColouredConsoleLogProvider()
|
||||
// .UseEmbeddedRavenStorage();
|
||||
|
||||
//you have to create an instance of background job server at least once for background jobs to run
|
||||
var client = new BackgroundJobServer();
|
||||
|
||||
BackgroundJob.Enqueue(() => Console.WriteLine("Background Job: Hello, world!"));
|
||||
RecurringJob.AddOrUpdate(() => test(), Cron.Minutely);
|
||||
|
||||
Console.WriteLine("Press Enter to exit...");
|
||||
Console.ReadLine();
|
||||
}
|
||||
|
||||
public static void test()
|
||||
{
|
||||
Console.WriteLine($"{x++} Cron Job: Hello, world!");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("Hangfire.Hosts.Console")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("Hangfire.Hosts.Console")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2013")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("3c3451fc-663d-4e33-ab72-d92beae38a97")]
|
||||
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.0.0")]
|
||||
@@ -0,0 +1,110 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using Hangfire;
|
||||
|
||||
namespace ConsoleSample
|
||||
{
|
||||
public class Services
|
||||
{
|
||||
private static readonly Random _random = new Random();
|
||||
|
||||
public void EmptyDefault()
|
||||
{
|
||||
}
|
||||
|
||||
[Queue("critical")]
|
||||
public void EmptyCritical()
|
||||
{
|
||||
}
|
||||
|
||||
[AutomaticRetry(Attempts = 0)]
|
||||
public void Error()
|
||||
{
|
||||
Console.WriteLine("Beginning error task...");
|
||||
throw new InvalidOperationException(null, new FileLoadException());
|
||||
}
|
||||
|
||||
[Queue("critical")]
|
||||
public void Random(int number)
|
||||
{
|
||||
int time;
|
||||
lock (_random)
|
||||
{
|
||||
time = _random.Next(10);
|
||||
}
|
||||
|
||||
if (time < 5)
|
||||
{
|
||||
throw new Exception();
|
||||
}
|
||||
|
||||
Thread.Sleep(TimeSpan.FromSeconds(5 + time));
|
||||
Console.WriteLine("Finished task: " + number);
|
||||
}
|
||||
|
||||
public void Cancelable(int iterationCount, IJobCancellationToken token)
|
||||
{
|
||||
try
|
||||
{
|
||||
for (var i = 1; i <= iterationCount; i++)
|
||||
{
|
||||
Thread.Sleep(1000);
|
||||
Console.WriteLine("Performing step {0} of {1}...", i, iterationCount);
|
||||
|
||||
token.ThrowIfCancellationRequested();
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
Console.WriteLine("Cancellation requested, exiting...");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
[DisplayName("Name: {0}")]
|
||||
public void Args(string name, int authorId, DateTime createdAt)
|
||||
{
|
||||
Console.WriteLine("{0}, {1}, {2}", name, authorId, createdAt);
|
||||
}
|
||||
|
||||
public void Custom(int id, string[] values, CustomObject objects, DayOfWeek dayOfWeek)
|
||||
{
|
||||
}
|
||||
|
||||
public void FullArgs(
|
||||
bool b,
|
||||
int i,
|
||||
char c,
|
||||
DayOfWeek e,
|
||||
string s,
|
||||
TimeSpan t,
|
||||
DateTime d,
|
||||
CustomObject o,
|
||||
string[] sa,
|
||||
int[] ia,
|
||||
long[] ea,
|
||||
object[] na,
|
||||
List<string> sl)
|
||||
{
|
||||
}
|
||||
|
||||
public class CustomObject
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public CustomObject[] Children { get; set; }
|
||||
}
|
||||
|
||||
public void Write(char character)
|
||||
{
|
||||
Console.Write(character);
|
||||
}
|
||||
|
||||
public void WriteBlankLine()
|
||||
{
|
||||
Console.WriteLine();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<startup>
|
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
|
||||
</startup>
|
||||
<runtime>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-4.5.0.0" newVersion="4.5.0.0" />
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
</runtime>
|
||||
<connectionStrings>
|
||||
<add name="RavenDebug" connectionString="URL=http://localhost:9090" />
|
||||
</connectionStrings>
|
||||
</configuration>
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Hangfire.Core" version="1.5.1" targetFramework="net45" />
|
||||
<package id="Newtonsoft.Json" version="5.0.1" targetFramework="net45" />
|
||||
<package id="Owin" version="1.0" targetFramework="net45" />
|
||||
</packages>
|
||||
@@ -0,0 +1,12 @@
|
||||
using System.Web.Mvc;
|
||||
|
||||
namespace MvcSample
|
||||
{
|
||||
public class FilterConfig
|
||||
{
|
||||
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
|
||||
{
|
||||
filters.Add(new HandleErrorAttribute());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using System.Web.Mvc;
|
||||
using System.Web.Routing;
|
||||
|
||||
namespace MvcSample
|
||||
{
|
||||
public class RouteConfig
|
||||
{
|
||||
public static void RegisterRoutes(RouteCollection routes)
|
||||
{
|
||||
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
|
||||
|
||||
routes.MapRoute(
|
||||
name: "Default",
|
||||
url: "{controller}/{action}/{id}",
|
||||
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
body {
|
||||
font-size: .85em;
|
||||
font-family: "Segoe UI", Verdana, Helvetica, Sans-Serif;
|
||||
color: #232323;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
header, footer, nav, section {
|
||||
display: block;
|
||||
}
|
||||
|
||||
|
||||
/* Styles for basic forms
|
||||
-----------------------------------------------------------*/
|
||||
fieldset {
|
||||
border: 1px solid #ddd;
|
||||
padding: 0 1.4em 1.4em 1.4em;
|
||||
margin: 0 0 1.5em 0;
|
||||
}
|
||||
|
||||
legend {
|
||||
font-size: 1.2em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
textarea {
|
||||
min-height: 75px;
|
||||
}
|
||||
|
||||
.editor-label {
|
||||
margin: 1em 0 0 0;
|
||||
}
|
||||
|
||||
.editor-field {
|
||||
margin: 0.5em 0 0 0;
|
||||
}
|
||||
|
||||
|
||||
/* Styles for validation helpers
|
||||
-----------------------------------------------------------*/
|
||||
.field-validation-error {
|
||||
color: #f00;
|
||||
}
|
||||
|
||||
.field-validation-valid {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.input-validation-error {
|
||||
border: 1px solid #f00;
|
||||
background-color: #fee;
|
||||
}
|
||||
|
||||
.validation-summary-errors {
|
||||
font-weight: bold;
|
||||
color: #f00;
|
||||
}
|
||||
|
||||
.validation-summary-valid {
|
||||
display: none;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<%@ Application Codebehind="Global.asax.cs" Inherits="MvcSample.MvcApplication" Language="C#" %>
|
||||
@@ -0,0 +1,19 @@
|
||||
using System.Web.Mvc;
|
||||
using System.Web.Routing;
|
||||
|
||||
namespace MvcSample
|
||||
{
|
||||
// Note: For instructions on enabling IIS6 or IIS7 classic mode,
|
||||
// visit http://go.microsoft.com/?LinkId=9394801
|
||||
|
||||
public class MvcApplication : System.Web.HttpApplication
|
||||
{
|
||||
protected void Application_Start()
|
||||
{
|
||||
AreaRegistration.RegisterAllAreas();
|
||||
|
||||
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
|
||||
RouteConfig.RegisterRoutes(RouteTable.Routes);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProductVersion>
|
||||
</ProductVersion>
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
<ProjectGuid>{FE5EAE49-2627-42B6-8C2B-EBE4D45B4D97}</ProjectGuid>
|
||||
<ProjectTypeGuids>{349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc}</ProjectTypeGuids>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>MvcSample</RootNamespace>
|
||||
<AssemblyName>MvcSample</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
|
||||
<MvcBuildViews>false</MvcBuildViews>
|
||||
<UseIISExpress>true</UseIISExpress>
|
||||
<IISExpressSSLPort />
|
||||
<IISExpressAnonymousAuthentication />
|
||||
<IISExpressWindowsAuthentication />
|
||||
<IISExpressUseClassicPipelineMode />
|
||||
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
|
||||
<MvcProjectUpgradeChecked>true</MvcProjectUpgradeChecked>
|
||||
<FileUpgradeFlags>
|
||||
</FileUpgradeFlags>
|
||||
<UpgradeBackupLocation>C:\Users\odinserj\Documents\GitHub\Hangfire\Backup6\</UpgradeBackupLocation>
|
||||
<OldToolsVersion>4.0</OldToolsVersion>
|
||||
<UseGlobalApplicationHostFile />
|
||||
<NuGetPackageImportStamp>
|
||||
</NuGetPackageImportStamp>
|
||||
<NuGetPackageImportStamp>5fc7aa54</NuGetPackageImportStamp>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<CodeAnalysisRuleSet>ManagedMinimumRules.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="Microsoft.Owin, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Owin.Host.SystemWeb, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Microsoft.Owin.Host.SystemWeb.3.0.1\lib\net45\Microsoft.Owin.Host.SystemWeb.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Owin">
|
||||
<HintPath>..\..\packages\Owin.1.0\lib\net40\Owin.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Data.Entity" />
|
||||
<Reference Include="System.Drawing" />
|
||||
<Reference Include="System.Net.Http.Extensions">
|
||||
<HintPath>..\..\packages\Microsoft.Net.Http.2.2.13\lib\net45\System.Net.Http.Extensions.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Net.Http.Primitives">
|
||||
<HintPath>..\..\packages\Microsoft.Net.Http.2.2.13\lib\net45\System.Net.Http.Primitives.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Web.DynamicData" />
|
||||
<Reference Include="System.Web.Entity" />
|
||||
<Reference Include="System.Web.ApplicationServices" />
|
||||
<Reference Include="System.ComponentModel.DataAnnotations" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="System.Web.Helpers, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.Helpers.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System.Web.Mvc, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Microsoft.AspNet.Mvc.5.2.3\lib\net45\System.Web.Mvc.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System.Web.Razor, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Microsoft.AspNet.Razor.3.2.3\lib\net45\System.Web.Razor.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System.Web.WebPages, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System.Web.WebPages.Deployment, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.Deployment.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System.Web.WebPages.Razor, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.Razor.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Web" />
|
||||
<Reference Include="System.Web.Extensions" />
|
||||
<Reference Include="System.Web.Abstractions" />
|
||||
<Reference Include="System.Web.Routing" />
|
||||
<Reference Include="System.Xml" />
|
||||
<Reference Include="System.Configuration" />
|
||||
<Reference Include="System.Web.Services" />
|
||||
<Reference Include="System.EnterpriseServices" />
|
||||
<Reference Include="Microsoft.Web.Infrastructure, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<Private>True</Private>
|
||||
<HintPath>..\..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Net.Http">
|
||||
</Reference>
|
||||
<Reference Include="System.Net.Http.WebRequest">
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="App_Start\FilterConfig.cs" />
|
||||
<Compile Include="App_Start\RouteConfig.cs" />
|
||||
<Compile Include="Global.asax.cs">
|
||||
<DependentUpon>Global.asax</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Startup.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="Global.asax" />
|
||||
<Content Include="Content\Site.css" />
|
||||
<Content Include="Web.config" />
|
||||
<Content Include="Web.Debug.config">
|
||||
<DependentUpon>Web.config</DependentUpon>
|
||||
</Content>
|
||||
<Content Include="Web.Release.config">
|
||||
<DependentUpon>Web.config</DependentUpon>
|
||||
</Content>
|
||||
<Content Include="Views\Web.config" />
|
||||
<Content Include="Views\_ViewStart.cshtml" />
|
||||
<Content Include="Views\Shared\Error.cshtml" />
|
||||
<Content Include="Views\Shared\_Layout.cshtml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Folder Include="App_Data\" />
|
||||
<Folder Include="Controllers\" />
|
||||
<Folder Include="Models\" />
|
||||
<Folder Include="Scripts\" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Hangfire.SqlServer\Hangfire.Raven.csproj">
|
||||
<Project>{a523c0e3-097d-4869-977f-15a717ea3e83}</Project>
|
||||
<Name>Hangfire.Raven</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\ConsoleSample\ConsoleSample.csproj">
|
||||
<Project>{c02bb718-2ae4-434c-8668-c894ff663fce}</Project>
|
||||
<Name>ConsoleSample</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
|
||||
<Import Project="$(VSToolsPath)\WebApplications\Microsoft.WebApplication.targets" Condition="'$(VSToolsPath)' != ''" />
|
||||
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v10.0\WebApplications\Microsoft.WebApplication.targets" Condition="false" />
|
||||
<Target Name="MvcBuildViews" AfterTargets="AfterBuild" Condition="'$(MvcBuildViews)'=='true'">
|
||||
<AspNetCompiler VirtualPath="temp" PhysicalPath="$(WebProjectOutputDir)" />
|
||||
</Target>
|
||||
<ProjectExtensions>
|
||||
<VisualStudio>
|
||||
<FlavorProperties GUID="{349c5851-65df-11da-9384-00065b846f21}">
|
||||
<WebProjectProperties>
|
||||
<UseIIS>True</UseIIS>
|
||||
<AutoAssignPort>True</AutoAssignPort>
|
||||
<DevelopmentServerPort>0</DevelopmentServerPort>
|
||||
<DevelopmentServerVPath>/</DevelopmentServerVPath>
|
||||
<IISUrl>http://localhost:56909/</IISUrl>
|
||||
<NTLMAuthentication>False</NTLMAuthentication>
|
||||
<UseCustomServer>False</UseCustomServer>
|
||||
<CustomServerUrl>
|
||||
</CustomServerUrl>
|
||||
<SaveServerSettingsInUserFile>False</SaveServerSettingsInUserFile>
|
||||
</WebProjectProperties>
|
||||
</FlavorProperties>
|
||||
</VisualStudio>
|
||||
</ProjectExtensions>
|
||||
<Import Project="..\..\packages\Microsoft.Bcl.Build.1.0.21\build\Microsoft.Bcl.Build.targets" Condition="Exists('..\..\packages\Microsoft.Bcl.Build.1.0.21\build\Microsoft.Bcl.Build.targets')" />
|
||||
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
|
||||
<PropertyGroup>
|
||||
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
|
||||
</PropertyGroup>
|
||||
<Error Condition="!Exists('..\..\packages\Microsoft.Bcl.Build.1.0.21\build\Microsoft.Bcl.Build.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Microsoft.Bcl.Build.1.0.21\build\Microsoft.Bcl.Build.targets'))" />
|
||||
</Target>
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
</Target>
|
||||
<Target Name="AfterBuild">
|
||||
</Target> -->
|
||||
</Project>
|
||||
@@ -0,0 +1,2 @@
|
||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=App_005FStart/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
||||
@@ -0,0 +1,34 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("Hangfire.MvcSample")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("Hangfire.MvcSample")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2013")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("1579df48-9733-47a5-93b6-8b459e9912fc")]
|
||||
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
// You can specify all the values or you can default the Revision and Build Numbers
|
||||
// by using the '*' as shown below:
|
||||
[assembly: AssemblyVersion("1.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.0.0")]
|
||||
@@ -0,0 +1,26 @@
|
||||
using Hangfire;
|
||||
using Hangfire.Dashboard;
|
||||
using Hangfire.SqlServer;
|
||||
using Microsoft.Owin;
|
||||
using MvcSample;
|
||||
using Owin;
|
||||
|
||||
[assembly: OwinStartup(typeof(Startup))]
|
||||
|
||||
namespace MvcSample
|
||||
{
|
||||
public class Startup
|
||||
{
|
||||
public void Configuration(IAppBuilder app)
|
||||
{
|
||||
GlobalConfiguration.Configuration
|
||||
.UseSqlServerStorage(@"Server=.\sqlexpress;Database=Hangfire.Sample;Trusted_Connection=True;")
|
||||
.UseMsmqQueues(@".\Private$\hangfire{0}", "default", "critical")
|
||||
.UseDashboardMetric(SqlServerStorage.ActiveConnections)
|
||||
.UseDashboardMetric(SqlServerStorage.TotalConnections)
|
||||
.UseDashboardMetric(DashboardMetrics.FailedCount);
|
||||
|
||||
app.UseHangfireDashboard();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
@{
|
||||
Layout = null;
|
||||
}
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<title>Error</title>
|
||||
</head>
|
||||
<body>
|
||||
<hgroup>
|
||||
<h1>Error.</h1>
|
||||
<h2>An error occurred while processing your request.</h2>
|
||||
</hgroup>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,16 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<title>@ViewBag.Title</title>
|
||||
@Styles.Render("~/Content/css")
|
||||
@Scripts.Render("~/bundles/modernizr")
|
||||
</head>
|
||||
<body>
|
||||
@RenderBody()
|
||||
|
||||
@Scripts.Render("~/bundles/jquery")
|
||||
@RenderSection("scripts", required: false)
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,58 @@
|
||||
<?xml version="1.0"?>
|
||||
|
||||
<configuration>
|
||||
<configSections>
|
||||
<sectionGroup name="system.web.webPages.razor" type="System.Web.WebPages.Razor.Configuration.RazorWebSectionGroup, System.Web.WebPages.Razor, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35">
|
||||
<section name="host" type="System.Web.WebPages.Razor.Configuration.HostSection, System.Web.WebPages.Razor, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" />
|
||||
<section name="pages" type="System.Web.WebPages.Razor.Configuration.RazorPagesSection, System.Web.WebPages.Razor, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" />
|
||||
</sectionGroup>
|
||||
</configSections>
|
||||
|
||||
<system.web.webPages.razor>
|
||||
<host factoryType="System.Web.Mvc.MvcWebRazorHostFactory, System.Web.Mvc, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
|
||||
<pages pageBaseType="System.Web.Mvc.WebViewPage">
|
||||
<namespaces>
|
||||
<add namespace="System.Web.Mvc" />
|
||||
<add namespace="System.Web.Mvc.Ajax" />
|
||||
<add namespace="System.Web.Mvc.Html" />
|
||||
<add namespace="System.Web.Routing" />
|
||||
</namespaces>
|
||||
</pages>
|
||||
</system.web.webPages.razor>
|
||||
|
||||
<appSettings>
|
||||
<add key="webpages:Enabled" value="false" />
|
||||
</appSettings>
|
||||
|
||||
<system.web>
|
||||
<httpHandlers>
|
||||
<add path="*" verb="*" type="System.Web.HttpNotFoundHandler"/>
|
||||
</httpHandlers>
|
||||
|
||||
<!--
|
||||
Enabling request validation in view pages would cause validation to occur
|
||||
after the input has already been processed by the controller. By default
|
||||
MVC performs request validation before a controller processes the input.
|
||||
To change this behavior apply the ValidateInputAttribute to a
|
||||
controller or action.
|
||||
-->
|
||||
<pages
|
||||
validateRequest="false"
|
||||
pageParserFilterType="System.Web.Mvc.ViewTypeParserFilter, System.Web.Mvc, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"
|
||||
pageBaseType="System.Web.Mvc.ViewPage, System.Web.Mvc, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"
|
||||
userControlBaseType="System.Web.Mvc.ViewUserControl, System.Web.Mvc, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35">
|
||||
<controls>
|
||||
<add assembly="System.Web.Mvc, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" namespace="System.Web.Mvc" tagPrefix="mvc" />
|
||||
</controls>
|
||||
</pages>
|
||||
</system.web>
|
||||
|
||||
<system.webServer>
|
||||
<validation validateIntegratedModeConfiguration="false" />
|
||||
|
||||
<handlers>
|
||||
<remove name="BlockViewHandler"/>
|
||||
<add name="BlockViewHandler" path="*" verb="*" preCondition="integratedMode" type="System.Web.HttpNotFoundHandler" />
|
||||
</handlers>
|
||||
</system.webServer>
|
||||
</configuration>
|
||||
@@ -0,0 +1,3 @@
|
||||
@{
|
||||
Layout = "~/Views/Shared/_Layout.cshtml";
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?xml version="1.0"?>
|
||||
|
||||
<!-- For more information on using Web.config transformation visit http://go.microsoft.com/fwlink/?LinkId=125889 -->
|
||||
|
||||
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
|
||||
<!--
|
||||
In the example below, the "SetAttributes" transform will change the value of
|
||||
"connectionString" to use "ReleaseSQLServer" only when the "Match" locator
|
||||
finds an atrribute "name" that has a value of "MyDB".
|
||||
|
||||
<connectionStrings>
|
||||
<add name="MyDB"
|
||||
connectionString="Data Source=ReleaseSQLServer;Initial Catalog=MyReleaseDB;Integrated Security=True"
|
||||
xdt:Transform="SetAttributes" xdt:Locator="Match(name)"/>
|
||||
</connectionStrings>
|
||||
-->
|
||||
<system.web>
|
||||
<!--
|
||||
In the example below, the "Replace" transform will replace the entire
|
||||
<customErrors> section of your Web.config file.
|
||||
Note that because there is only one customErrors section under the
|
||||
<system.web> node, there is no need to use the "xdt:Locator" attribute.
|
||||
|
||||
<customErrors defaultRedirect="GenericError.htm"
|
||||
mode="RemoteOnly" xdt:Transform="Replace">
|
||||
<error statusCode="500" redirect="InternalError.htm"/>
|
||||
</customErrors>
|
||||
-->
|
||||
</system.web>
|
||||
</configuration>
|
||||
@@ -0,0 +1,31 @@
|
||||
<?xml version="1.0"?>
|
||||
|
||||
<!-- For more information on using Web.config transformation visit http://go.microsoft.com/fwlink/?LinkId=125889 -->
|
||||
|
||||
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
|
||||
<!--
|
||||
In the example below, the "SetAttributes" transform will change the value of
|
||||
"connectionString" to use "ReleaseSQLServer" only when the "Match" locator
|
||||
finds an atrribute "name" that has a value of "MyDB".
|
||||
|
||||
<connectionStrings>
|
||||
<add name="MyDB"
|
||||
connectionString="Data Source=ReleaseSQLServer;Initial Catalog=MyReleaseDB;Integrated Security=True"
|
||||
xdt:Transform="SetAttributes" xdt:Locator="Match(name)"/>
|
||||
</connectionStrings>
|
||||
-->
|
||||
<system.web>
|
||||
<compilation xdt:Transform="RemoveAttributes(debug)" />
|
||||
<!--
|
||||
In the example below, the "Replace" transform will replace the entire
|
||||
<customErrors> section of your Web.config file.
|
||||
Note that because there is only one customErrors section under the
|
||||
<system.web> node, there is no need to use the "xdt:Locator" attribute.
|
||||
|
||||
<customErrors defaultRedirect="GenericError.htm"
|
||||
mode="RemoteOnly" xdt:Transform="Replace">
|
||||
<error statusCode="500" redirect="InternalError.htm"/>
|
||||
</customErrors>
|
||||
-->
|
||||
</system.web>
|
||||
</configuration>
|
||||
@@ -0,0 +1,63 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
For more information on how to configure your ASP.NET application, please visit
|
||||
http://go.microsoft.com/fwlink/?LinkId=152368
|
||||
-->
|
||||
<configuration>
|
||||
<appSettings>
|
||||
<add key="webpages:Version" value="2.0.0.0" />
|
||||
<add key="webpages:Enabled" value="false" />
|
||||
<add key="PreserveLoginUrl" value="true" />
|
||||
<add key="ClientValidationEnabled" value="true" />
|
||||
<add key="UnobtrusiveJavaScriptEnabled" value="true" />
|
||||
</appSettings>
|
||||
<system.web>
|
||||
<httpRuntime targetFramework="4.5" />
|
||||
<compilation debug="true" targetFramework="4.5" />
|
||||
<authentication mode="Forms">
|
||||
<forms loginUrl="~/Account/Login" timeout="2880" />
|
||||
</authentication>
|
||||
<pages>
|
||||
<namespaces>
|
||||
<add namespace="System.Web.Helpers" />
|
||||
<add namespace="System.Web.Mvc" />
|
||||
<add namespace="System.Web.Mvc.Ajax" />
|
||||
<add namespace="System.Web.Mvc.Html" />
|
||||
<add namespace="System.Web.Routing" />
|
||||
<add namespace="System.Web.WebPages" />
|
||||
</namespaces>
|
||||
</pages>
|
||||
</system.web>
|
||||
<system.webServer>
|
||||
<validation validateIntegratedModeConfiguration="false" />
|
||||
|
||||
</system.webServer>
|
||||
<runtime>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="EntityFramework" publicKeyToken="b77a5c561934e089" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-5.0.0.0" newVersion="5.0.0.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="WebGrease" publicKeyToken="31bf3856ad364e35" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-1.5.2.14234" newVersion="1.5.2.14234" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Web.Helpers" publicKeyToken="31bf3856ad364e35" />
|
||||
<bindingRedirect oldVersion="1.0.0.0-3.0.0.0" newVersion="3.0.0.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Web.WebPages" publicKeyToken="31bf3856ad364e35" />
|
||||
<bindingRedirect oldVersion="1.0.0.0-3.0.0.0" newVersion="3.0.0.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Web.Mvc" publicKeyToken="31bf3856ad364e35" />
|
||||
<bindingRedirect oldVersion="1.0.0.0-5.2.3.0" newVersion="5.2.3.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Microsoft.Owin" publicKeyToken="31bf3856ad364e35" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
</runtime>
|
||||
</configuration>
|
||||
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Microsoft.AspNet.Mvc" version="5.2.3" targetFramework="net45" userInstalled="true" />
|
||||
<package id="Microsoft.AspNet.Mvc.FixedDisplayModes" version="5.0.0" targetFramework="net45" userInstalled="true" />
|
||||
<package id="Microsoft.AspNet.Razor" version="3.2.3" targetFramework="net45" userInstalled="true" />
|
||||
<package id="Microsoft.AspNet.WebPages" version="3.2.3" targetFramework="net45" userInstalled="true" />
|
||||
<package id="Microsoft.Bcl" version="1.1.10" targetFramework="net45" userInstalled="true" />
|
||||
<package id="Microsoft.Bcl.Build" version="1.0.21" targetFramework="net45" userInstalled="true" />
|
||||
<package id="Microsoft.Net.Http" version="2.2.13" targetFramework="net45" userInstalled="true" />
|
||||
<package id="Microsoft.Owin" version="3.0.1" targetFramework="net45" userInstalled="true" />
|
||||
<package id="Microsoft.Owin.Host.SystemWeb" version="3.0.1" targetFramework="net45" userInstalled="true" />
|
||||
<package id="Microsoft.Web.Infrastructure" version="1.0.0.0" targetFramework="net45" userInstalled="true" />
|
||||
<package id="Owin" version="1.0" targetFramework="net45" userInstalled="true" />
|
||||
</packages>
|
||||
@@ -0,0 +1,208 @@
|
||||
// This file is part of Hangfire.
|
||||
// Copyright � 2013-2014 Sergey Odinokov.
|
||||
//
|
||||
// Hangfire is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as
|
||||
// published by the Free Software Foundation, either version 3
|
||||
// of the License, or any later version.
|
||||
//
|
||||
// Hangfire is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with Hangfire. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Threading;
|
||||
using Hangfire.Annotations;
|
||||
using HangFire.Raven;
|
||||
using Hangfire.Raven.Entities;
|
||||
using Hangfire.Raven.Storage;
|
||||
|
||||
namespace Hangfire.Raven.DistributedLock
|
||||
{
|
||||
public class RavenDistributedLock : IDisposable
|
||||
{
|
||||
private const string LockMode = "Exclusive";
|
||||
private const string LockOwner = "Session";
|
||||
private const int CommandTimeoutAdditionSeconds = 1;
|
||||
|
||||
private static readonly IDictionary<int, string> LockErrorMessages = new Dictionary<int, string>
|
||||
{
|
||||
{ -1, "The lock request timed out" },
|
||||
{ -2, "The lock request was canceled" },
|
||||
{ -3, "The lock request was chosen as a deadlock victim" },
|
||||
{ -999, "Indicates a parameter validation or other call error" }
|
||||
};
|
||||
|
||||
private static readonly ThreadLocal<Dictionary<string, int>> AcquiredLocks = new ThreadLocal<Dictionary<string, int>>(() => new Dictionary<string, int>());
|
||||
|
||||
private static RavenStorage _storage;
|
||||
private static string _resource;
|
||||
private static RavenStorageOptions _options;
|
||||
|
||||
private static Timer _heartbeatTimer = null;
|
||||
|
||||
private bool _completed;
|
||||
|
||||
public RavenDistributedLock([NotNull] RavenStorage storage, [NotNull] string resource, TimeSpan timeout, RavenStorageOptions options)
|
||||
{
|
||||
storage.ThrowIfNull("storage");
|
||||
resource.ThrowIfNull("resource");
|
||||
|
||||
if ((timeout.TotalSeconds + CommandTimeoutAdditionSeconds) > int.MaxValue) {
|
||||
throw new ArgumentException(string.Format("The timeout specified is too large. Please supply a timeout equal to or less than {0} seconds",
|
||||
int.MaxValue - CommandTimeoutAdditionSeconds),
|
||||
"timeout");
|
||||
}
|
||||
|
||||
_storage = storage;
|
||||
_resource = resource;
|
||||
_options = options;
|
||||
|
||||
if (!AcquiredLocks.Value.ContainsKey(_resource)) {
|
||||
Acquire(_resource, timeout);
|
||||
AcquiredLocks.Value[_resource] = 1;
|
||||
} else {
|
||||
AcquiredLocks.Value[_resource]++;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_completed)
|
||||
return;
|
||||
|
||||
_completed = true;
|
||||
|
||||
if (!AcquiredLocks.Value.ContainsKey(_resource))
|
||||
return;
|
||||
|
||||
AcquiredLocks.Value[_resource]--;
|
||||
|
||||
if (AcquiredLocks.Value[_resource] != 0)
|
||||
return;
|
||||
|
||||
Release(_resource);
|
||||
AcquiredLocks.Value.Remove(_resource);
|
||||
}
|
||||
|
||||
internal static void Acquire(string resource, TimeSpan timeout)
|
||||
{
|
||||
try {
|
||||
RemoveDeadLocks();
|
||||
|
||||
// Check lock
|
||||
DateTime lockTimeoutTime = DateTime.Now.Add(timeout);
|
||||
bool isLockedBySomeoneElse;
|
||||
bool isFirstAttempt = true;
|
||||
do {
|
||||
using (var repository = new Repository()) {
|
||||
isLockedBySomeoneElse = repository.Session.Query<DistributedLocks>()
|
||||
.FirstOrDefault(t => t.Resource == resource && t.ClientId == _options.ClientId) != null;
|
||||
}
|
||||
|
||||
if (isFirstAttempt == true) {
|
||||
isFirstAttempt = false;
|
||||
} else {
|
||||
Thread.Sleep((int)timeout.TotalMilliseconds / 10);
|
||||
}
|
||||
}
|
||||
while ((isLockedBySomeoneElse == true) && (lockTimeoutTime >= DateTime.Now));
|
||||
|
||||
// Set lock
|
||||
if (isLockedBySomeoneElse == false) {
|
||||
using (var repository = new Repository()) {
|
||||
var distributedLocks = repository.Session.Query<DistributedLocks>().Where(t => t.Resource == resource).ToList();
|
||||
|
||||
if (!distributedLocks.Any()) {
|
||||
distributedLocks.Add(new DistributedLocks
|
||||
{
|
||||
Resource = resource,
|
||||
});
|
||||
}
|
||||
|
||||
foreach (var distributedLock in distributedLocks) {
|
||||
distributedLock.ClientId = _options.ClientId;
|
||||
distributedLock.LockCount = 1;
|
||||
distributedLock.Heartbeat = DateTime.UtcNow;
|
||||
|
||||
repository.Save(distributedLock);
|
||||
}
|
||||
}
|
||||
|
||||
StartHeartBeat();
|
||||
} else {
|
||||
throw new RavenDistributedLockException(string.Format("Could not place a lock on the resource '{0}': {1}.", _resource, "The lock request timed out"));
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
if (ex is RavenDistributedLockException) {
|
||||
throw;
|
||||
} else {
|
||||
throw new RavenDistributedLockException(string.Format("Could not place a lock on the resource '{0}': {1}.", _resource, "Check inner exception for details"), ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static void Release(string resource)
|
||||
{
|
||||
try {
|
||||
RemoveDeadLocks();
|
||||
|
||||
// Remove resource lock
|
||||
using (var repository = new Repository()) {
|
||||
var distributedLocks = repository.Session.Query<DistributedLocks>().Where(t => t.Resource == _resource && t.ClientId == _options.ClientId).ToList();
|
||||
|
||||
foreach (var distributedLock in distributedLocks) {
|
||||
repository.Delete(distributedLock);
|
||||
}
|
||||
}
|
||||
|
||||
if (_heartbeatTimer != null) {
|
||||
_heartbeatTimer.Dispose();
|
||||
_heartbeatTimer = null;
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
throw new RavenDistributedLockException(string.Format("Could not release a lock on the resource '{0}': {1}.", _resource, "Check inner exception for details"), ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static void StartHeartBeat()
|
||||
{
|
||||
TimeSpan timerInterval = TimeSpan.FromMilliseconds(_options.DistributedLockLifetime.TotalMilliseconds / 5);
|
||||
|
||||
_heartbeatTimer = new Timer(state =>
|
||||
{
|
||||
try {
|
||||
using (var repository = new Repository()) {
|
||||
var distributedLocks = repository.Session.Query<DistributedLocks>().Where(t => t.Resource == _resource && t.ClientId == _options.ClientId).ToList();
|
||||
|
||||
foreach (var distributedLock in distributedLocks) {
|
||||
distributedLock.Heartbeat = DateTime.UtcNow;
|
||||
repository.Save(distributedLock);
|
||||
}
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
Console.WriteLine("Unable to update heartbeat on the resource '{0}'", ex, _resource);
|
||||
}
|
||||
}, null, timerInterval, timerInterval);
|
||||
}
|
||||
|
||||
private static void RemoveDeadLocks()
|
||||
{
|
||||
using (var repository = new Repository()) {
|
||||
var heartBeat = DateTime.UtcNow.Subtract(_options.DistributedLockLifetime);
|
||||
var deadLocks = repository.Session.Query<DistributedLocks>().Where(t => t.Resource == _resource && t.Heartbeat == heartBeat).ToList();
|
||||
|
||||
foreach (var deadlock in deadLocks) {
|
||||
repository.Delete(deadlock);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
using System;
|
||||
|
||||
namespace Hangfire.Raven.DistributedLock
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents exceptions for distributed lock implementation for MongoDB
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class RavenDistributedLockException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates exception
|
||||
/// </summary>
|
||||
/// <param name="message">Exception message</param>
|
||||
public RavenDistributedLockException(string message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates exception with inner exception
|
||||
/// </summary>
|
||||
/// <param name="message">Exception message</param>
|
||||
/// <param name="innerException">Inner exception</param>
|
||||
public RavenDistributedLockException(string message, Exception innerException)
|
||||
: base(message, innerException)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
|
||||
namespace Hangfire.Raven.Entities
|
||||
{
|
||||
public class AggregatedCounter
|
||||
{
|
||||
public string Id { get; set; }
|
||||
|
||||
public string Key { get; set; }
|
||||
|
||||
public long Value { get; set; }
|
||||
|
||||
public DateTime? ExpireAt { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using Hangfire.Raven.Entities.Identity;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Hangfire.Raven.Entities
|
||||
{
|
||||
public class Counter : BaseEntity
|
||||
{
|
||||
public string Key { get; set; }
|
||||
|
||||
public int Value { get; set; }
|
||||
|
||||
public DateTime? ExpireAt { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using Hangfire.Raven.Entities.Identity;
|
||||
using System;
|
||||
|
||||
namespace Hangfire.Raven.Entities
|
||||
{
|
||||
public class DistributedLocks : BaseEntity
|
||||
{
|
||||
public string Resource { get; set; }
|
||||
|
||||
public string ClientId { get; set; }
|
||||
|
||||
public int LockCount { get; set; }
|
||||
|
||||
public DateTime Heartbeat { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace Hangfire.Raven.Entities
|
||||
{
|
||||
public class EnqueuedAndFetchedCount
|
||||
{
|
||||
public int? EnqueuedCount { get; set; }
|
||||
|
||||
public int? FetchedCount { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Hangfire.Raven.Entities.Identity
|
||||
{
|
||||
public class BaseEntity
|
||||
{
|
||||
public string Id { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
// This file is part of Hangfire.
|
||||
// Copyright © 2013-2014 Sergey Odinokov.
|
||||
//
|
||||
// Hangfire is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as
|
||||
// published by the Free Software Foundation, either version 3
|
||||
// of the License, or any later version.
|
||||
//
|
||||
// Hangfire is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with Hangfire. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
using Hangfire.Raven.Entities.Identity;
|
||||
|
||||
namespace Hangfire.Raven.Entities
|
||||
{
|
||||
internal class JobParameter : BaseEntity
|
||||
{
|
||||
public string JobId { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string Value { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using Hangfire.Raven.Entities.Identity;
|
||||
using System;
|
||||
|
||||
namespace Hangfire.Raven.Entities
|
||||
{
|
||||
public class JobQueue : BaseEntity
|
||||
{
|
||||
public string JobId { get; set; }
|
||||
public string Queue { get; set; }
|
||||
public DateTime? FetchedAt { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
// This file is part of Hangfire.
|
||||
// Copyright © 2013-2014 Sergey Odinokov.
|
||||
//
|
||||
// Hangfire is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as
|
||||
// published by the Free Software Foundation, either version 3
|
||||
// of the License, or any later version.
|
||||
//
|
||||
// Hangfire is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with Hangfire. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
using Hangfire.Annotations;
|
||||
using Hangfire.Storage;
|
||||
using HangFire.Raven;
|
||||
using Hangfire.Raven.Storage;
|
||||
|
||||
namespace Hangfire.Raven.Entities
|
||||
{
|
||||
public class RavenFetchedJob : IFetchedJob
|
||||
{
|
||||
private readonly RavenStorage _storage;
|
||||
private bool _requeued { get; set; }
|
||||
private bool _removedFromQueue { get; set; }
|
||||
private bool _disposed { get; set; }
|
||||
|
||||
public string Id { get; set; }
|
||||
public string JobId { get; set; }
|
||||
public string Queue { get; set; }
|
||||
|
||||
public RavenFetchedJob(
|
||||
[NotNull] RavenStorage storage,
|
||||
JobQueue jobQueue)
|
||||
{
|
||||
storage.ThrowIfNull("storage");
|
||||
jobQueue.ThrowIfNull("jobQueue");
|
||||
|
||||
_storage = storage;
|
||||
|
||||
JobId = jobQueue.JobId;
|
||||
Queue = jobQueue.Queue;
|
||||
Id = jobQueue.Id;
|
||||
}
|
||||
|
||||
public void RemoveFromQueue()
|
||||
{
|
||||
using (var repository = new Repository()) {
|
||||
var job = repository.Session.Load<JobQueue>(Id);
|
||||
|
||||
if (job != null) {
|
||||
repository.Delete(job);
|
||||
}
|
||||
}
|
||||
|
||||
_removedFromQueue = true;
|
||||
}
|
||||
|
||||
public void Requeue()
|
||||
{
|
||||
using (var repository = new Repository()) {
|
||||
var job = repository.Session.Load<JobQueue>(Id);
|
||||
|
||||
job.FetchedAt = null;
|
||||
|
||||
repository.Save(job);
|
||||
}
|
||||
|
||||
_requeued = true;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_removedFromQueue && !_requeued) {
|
||||
Requeue();
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
// This file is part of Hangfire.
|
||||
// Copyright © 2013-2014 Sergey Odinokov.
|
||||
//
|
||||
// Hangfire is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as
|
||||
// published by the Free Software Foundation, either version 3
|
||||
// of the License, or any later version.
|
||||
//
|
||||
// Hangfire is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with Hangfire. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
using Hangfire.Raven.Entities.Identity;
|
||||
using System;
|
||||
|
||||
namespace Hangfire.Raven.Entities
|
||||
{
|
||||
public class RavenHash : BaseEntity
|
||||
{
|
||||
public string Key { get; set; }
|
||||
public string Field { get; set; }
|
||||
public string Value { get; set; }
|
||||
public DateTime? ExpireAt { get; set; }
|
||||
|
||||
public string JobId { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
// This file is part of Hangfire.
|
||||
// Copyright © 2013-2014 Sergey Odinokov.
|
||||
//
|
||||
// Hangfire is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as
|
||||
// published by the Free Software Foundation, either version 3
|
||||
// of the License, or any later version.
|
||||
//
|
||||
// Hangfire is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with Hangfire. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
using Hangfire.Raven.Entities.Identity;
|
||||
using System;
|
||||
|
||||
namespace Hangfire.Raven.Entities
|
||||
{
|
||||
internal class RavenJob : BaseEntity
|
||||
{
|
||||
public string InvocationData { get; set; }
|
||||
public string Arguments { get; set; }
|
||||
public DateTime CreatedAt { get; set; }
|
||||
public DateTime? ExpireAt { get; set; }
|
||||
|
||||
public string StateId { get; set; }
|
||||
public string StateName { get; set; }
|
||||
public string StateReason { get; set; }
|
||||
public string StateData { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using Hangfire.Raven.Entities.Identity;
|
||||
using System;
|
||||
|
||||
namespace Hangfire.Raven.Entities
|
||||
{
|
||||
public class RavenList : BaseEntity
|
||||
{
|
||||
public string Key { get; set; }
|
||||
|
||||
public string Value { get; set; }
|
||||
|
||||
public DateTime ExpireAt { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
// This file is part of Hangfire.
|
||||
// Copyright © 2013-2014 Sergey Odinokov.
|
||||
//
|
||||
// Hangfire is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as
|
||||
// published by the Free Software Foundation, either version 3
|
||||
// of the License, or any later version.
|
||||
//
|
||||
// Hangfire is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with Hangfire. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
using Hangfire.Raven.Entities.Identity;
|
||||
using System;
|
||||
|
||||
namespace Hangfire.Raven.Entities
|
||||
{
|
||||
public class RavenServer : BaseEntity
|
||||
{
|
||||
public string Data { get; set; }
|
||||
public DateTime LastHeartbeat { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using Hangfire.Raven.Entities.Identity;
|
||||
using System;
|
||||
|
||||
namespace Hangfire.Raven.Entities
|
||||
{
|
||||
public class RavenSet : BaseEntity
|
||||
{
|
||||
public string Key { get; set; }
|
||||
|
||||
public double Score { get; set; }
|
||||
|
||||
public string Value { get; set; }
|
||||
|
||||
public DateTime? ExpireAt { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
// This file is part of Hangfire.
|
||||
// Copyright © 2013-2014 Sergey Odinokov.
|
||||
//
|
||||
// Hangfire is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as
|
||||
// published by the Free Software Foundation, either version 3
|
||||
// of the License, or any later version.
|
||||
//
|
||||
// Hangfire is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with Hangfire. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Hangfire.Raven.Entities
|
||||
{
|
||||
public class ServerData
|
||||
{
|
||||
public int WorkerCount { get; set; }
|
||||
public string[] Queues { get; set; }
|
||||
public DateTime? StartedAt { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// This file is part of Hangfire.
|
||||
// Copyright © 2013-2014 Sergey Odinokov.
|
||||
//
|
||||
// Hangfire is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as
|
||||
// published by the Free Software Foundation, either version 3
|
||||
// of the License, or any later version.
|
||||
//
|
||||
// Hangfire is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with Hangfire. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
using Hangfire.Raven.Entities.Identity;
|
||||
using System;
|
||||
|
||||
namespace Hangfire.Raven.Entities
|
||||
{
|
||||
public class State : BaseEntity
|
||||
{
|
||||
public string JobId { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string Reason { get; set; }
|
||||
public DateTime CreatedAt { get; set; }
|
||||
public string Data { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
// This file is part of Hangfire.
|
||||
// Copyright © 2013-2014 Sergey Odinokov.
|
||||
//
|
||||
// Hangfire is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as
|
||||
// published by the Free Software Foundation, either version 3
|
||||
// of the License, or any later version.
|
||||
//
|
||||
// Hangfire is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with Hangfire. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using Hangfire.Logging;
|
||||
using Hangfire.Server;
|
||||
using System.Linq;
|
||||
using HangFire.Raven;
|
||||
using Hangfire.Raven.Entities;
|
||||
|
||||
namespace Hangfire.Raven
|
||||
{
|
||||
public class ExpirationManager : IServerComponent
|
||||
{
|
||||
private static readonly ILog Logger = LogProvider.GetCurrentClassLogger();
|
||||
|
||||
private readonly TimeSpan _checkInterval;
|
||||
|
||||
public ExpirationManager()
|
||||
: this(TimeSpan.FromHours(1))
|
||||
{
|
||||
}
|
||||
|
||||
public ExpirationManager(TimeSpan checkInterval)
|
||||
{
|
||||
_checkInterval = checkInterval;
|
||||
}
|
||||
|
||||
public void Execute(CancellationToken cancellationToken)
|
||||
{
|
||||
using (var repository = new Repository()) {
|
||||
var session = repository.Session;
|
||||
|
||||
var now = DateTime.UtcNow;
|
||||
|
||||
var counters = session.Query<Counter>().Where(t => t.ExpireAt <= now).ToList();
|
||||
counters.ForEach(t => session.Delete(t));
|
||||
|
||||
var jobs = session.Query<RavenJob>().Where(t => t.ExpireAt <= now).ToList();
|
||||
jobs.ForEach(t => session.Delete(t));
|
||||
|
||||
var hashes = session.Query<RavenHash>().Where(t => t.ExpireAt <= now).ToList();
|
||||
hashes.ForEach(t => session.Delete(t));
|
||||
|
||||
var lists = session.Query<RavenList>().Where(t => t.ExpireAt <= now).ToList();
|
||||
lists.ForEach(t => session.Delete(t));
|
||||
|
||||
var sets = session.Query<RavenSet>().Where(t => t.ExpireAt <= now).ToList();
|
||||
sets.ForEach(t => session.Delete(t));
|
||||
}
|
||||
|
||||
cancellationToken.WaitHandle.WaitOne(_checkInterval);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "Raven Expiration Manager";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using Raven.Client;
|
||||
|
||||
namespace Hangfire.Raven.Extensions
|
||||
{
|
||||
public static class DatabaseExtensions
|
||||
{
|
||||
public static bool DatabaseExists(this IDocumentStore documentStore, string database)
|
||||
{
|
||||
var result = documentStore.DatabaseCommands.Head("Raven/Databases/" + database);
|
||||
|
||||
return (result != null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=DataTypes/@EntryIndexedValue">False</s:Boolean></wpf:ResourceDictionary>
|
||||
@@ -0,0 +1,150 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{A523C0E3-097D-4869-977F-15A717EA3E83}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>Hangfire.Raven</RootNamespace>
|
||||
<AssemblyName>Hangfire.Raven</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
|
||||
<TargetFrameworkProfile />
|
||||
<NuGetPackageImportStamp>5f064ca7</NuGetPackageImportStamp>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<DocumentationFile>bin\Debug\Hangfire.Raven.xml</DocumentationFile>
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
<NoWarn>1591</NoWarn>
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<DocumentationFile>bin\Release\Hangfire.Raven.xml</DocumentationFile>
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
<NoWarn>1591</NoWarn>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Dewey.Net, Version=2.7.8.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Dewey.Net.2.7.8.0\lib\net45\Dewey.Net.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Hangfire.Core">
|
||||
<HintPath>..\..\packages\Hangfire.Core.1.5.0\lib\net45\Hangfire.Core.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Hangfire.SqlServer">
|
||||
<HintPath>..\..\packages\Hangfire.SqlServer.1.5.0\lib\net45\Hangfire.SqlServer.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Owin">
|
||||
<HintPath>..\..\packages\Microsoft.Owin.3.0.0\lib\net45\Microsoft.Owin.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Owin.Host.SystemWeb">
|
||||
<HintPath>..\..\packages\Microsoft.Owin.Host.SystemWeb.3.0.0\lib\net45\Microsoft.Owin.Host.SystemWeb.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Newtonsoft.Json, Version=7.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Newtonsoft.Json.7.0.1\lib\net45\Newtonsoft.Json.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Owin">
|
||||
<HintPath>..\..\packages\Owin.1.0\lib\net40\Owin.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Raven.Abstractions">
|
||||
<HintPath>..\..\packages\RavenDB.Database.3.0.3800\lib\net45\Raven.Abstractions.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Raven.Client.Lightweight">
|
||||
<HintPath>..\..\packages\RavenDB.Client.3.0.3800\lib\net45\Raven.Client.Lightweight.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Raven.Database">
|
||||
<HintPath>..\..\packages\RavenDB.Database.3.0.3800\lib\net45\Raven.Database.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.ComponentModel.Composition" />
|
||||
<Reference Include="System.ComponentModel.DataAnnotations" />
|
||||
<Reference Include="System.Configuration" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Transactions" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="..\SharedAssemblyInfo.cs">
|
||||
<Link>Properties\SharedAssemblyInfo.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="DistributedLocks\RavenDistributedLock.cs" />
|
||||
<Compile Include="DistributedLocks\RavenDistributedLockException.cs" />
|
||||
<Compile Include="Entities\AggregatedCounter.cs" />
|
||||
<Compile Include="Entities\Counter.cs" />
|
||||
<Compile Include="Entities\DistributedLocks.cs" />
|
||||
<Compile Include="Entities\EnqueuedAndFetchedCount.cs" />
|
||||
<Compile Include="Entities\JobQueue.cs" />
|
||||
<Compile Include="Entities\RavenHash.cs" />
|
||||
<Compile Include="Entities\Identity\BaseEntity.cs" />
|
||||
<Compile Include="Entities\RavenList.cs" />
|
||||
<Compile Include="Entities\RavenSet.cs" />
|
||||
<Compile Include="ExpirationManager.cs" />
|
||||
<Compile Include="Entities\JobParameter.cs" />
|
||||
<Compile Include="Entities\RavenJob.cs" />
|
||||
<Compile Include="Entities\State.cs" />
|
||||
<Compile Include="Entities\RavenServer.cs" />
|
||||
<Compile Include="Entities\ServerData.cs" />
|
||||
<Compile Include="Extensions\DatabaseExtensions.cs" />
|
||||
<Compile Include="JobQueues\IPersistentJobQueue.cs" />
|
||||
<Compile Include="JobQueues\IPersistentJobQueueMonitoringApi.cs" />
|
||||
<Compile Include="JobQueues\IPersistentJobQueueProvider.cs" />
|
||||
<Compile Include="Listeners\TakeNewestConflictResolutionListener.cs" />
|
||||
<Compile Include="JobQueues\PersistentJobQueueProviderCollection.cs" />
|
||||
<Compile Include="JobQueues\RavenJobQueue.cs" />
|
||||
<Compile Include="Entities\RavenFetchedJob.cs" />
|
||||
<Compile Include="ObjectExtensions.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="JobQueues\RavenJobQueueProvider.cs" />
|
||||
<Compile Include="Repository.cs" />
|
||||
<Compile Include="SqlServerMonitoringApi.cs" />
|
||||
<Compile Include="JobQueues\RavenJobQueueMonitoringApi.cs" />
|
||||
<Compile Include="Storage\RavenStorage.cs" />
|
||||
<Compile Include="Storage\RavenStorageExtensions.cs" />
|
||||
<Compile Include="Storage\RavenStorageOptions.cs" />
|
||||
<Compile Include="RavenConnection.cs" />
|
||||
<Compile Include="RavenWriteOnlyTransaction.cs" />
|
||||
<Compile Include="StateHandlers\DeletedStateHandler.cs" />
|
||||
<Compile Include="StateHandlers\FailedStateHandler.cs" />
|
||||
<Compile Include="StateHandlers\ProcessingStateHandler.cs" />
|
||||
<Compile Include="StateHandlers\SucceededStateHandler.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Service Include="{508349B6-6B84-4DF5-91F0-309BEEBAD82D}" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="app.config" />
|
||||
<None Include="packages.config" />
|
||||
<None Include="Raven.Studio.Html5.zip">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
</Target>
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
</Project>
|
||||
@@ -0,0 +1,27 @@
|
||||
// This file is part of Hangfire.
|
||||
// Copyright © 2013-2014 Sergey Odinokov.
|
||||
//
|
||||
// Hangfire is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as
|
||||
// published by the Free Software Foundation, either version 3
|
||||
// of the License, or any later version.
|
||||
//
|
||||
// Hangfire is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with Hangfire. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
using System.Threading;
|
||||
using Hangfire.Storage;
|
||||
|
||||
namespace Hangfire.Raven.JobQueues
|
||||
{
|
||||
public interface IPersistentJobQueue
|
||||
{
|
||||
IFetchedJob Dequeue(string[] queues, CancellationToken cancellationToken);
|
||||
void Enqueue(string queue, string jobId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
// This file is part of Hangfire.
|
||||
// Copyright © 2013-2014 Sergey Odinokov.
|
||||
//
|
||||
// Hangfire is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as
|
||||
// published by the Free Software Foundation, either version 3
|
||||
// of the License, or any later version.
|
||||
//
|
||||
// Hangfire is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with Hangfire. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Hangfire.Raven.Entities;
|
||||
|
||||
namespace Hangfire.Raven.JobQueues
|
||||
{
|
||||
public interface IPersistentJobQueueMonitoringApi
|
||||
{
|
||||
IEnumerable<string> GetQueues();
|
||||
|
||||
IEnumerable<string> GetEnqueuedJobIds(string queue, int from, int perPage);
|
||||
|
||||
IEnumerable<string> GetFetchedJobIds(string queue, int from, int perPage);
|
||||
|
||||
EnqueuedAndFetchedCount GetEnqueuedAndFetchedCount(string queue);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
// This file is part of Hangfire.
|
||||
// Copyright © 2013-2014 Sergey Odinokov.
|
||||
//
|
||||
// Hangfire is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as
|
||||
// published by the Free Software Foundation, either version 3
|
||||
// of the License, or any later version.
|
||||
//
|
||||
// Hangfire is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with Hangfire. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace Hangfire.Raven.JobQueues
|
||||
{
|
||||
public interface IPersistentJobQueueProvider
|
||||
{
|
||||
IPersistentJobQueue GetJobQueue();
|
||||
IPersistentJobQueueMonitoringApi GetJobQueueMonitoringApi();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
// This file is part of Hangfire.
|
||||
// Copyright © 2013-2014 Sergey Odinokov.
|
||||
//
|
||||
// Hangfire is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as
|
||||
// published by the Free Software Foundation, either version 3
|
||||
// of the License, or any later version.
|
||||
//
|
||||
// Hangfire is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with Hangfire. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Hangfire.Raven.JobQueues
|
||||
{
|
||||
public class PersistentJobQueueProviderCollection : IEnumerable<IPersistentJobQueueProvider>
|
||||
{
|
||||
private readonly List<IPersistentJobQueueProvider> _providers
|
||||
= new List<IPersistentJobQueueProvider>();
|
||||
private readonly Dictionary<string, IPersistentJobQueueProvider> _providersByQueue
|
||||
= new Dictionary<string, IPersistentJobQueueProvider>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
private readonly IPersistentJobQueueProvider _defaultProvider;
|
||||
|
||||
public PersistentJobQueueProviderCollection(IPersistentJobQueueProvider defaultProvider)
|
||||
{
|
||||
defaultProvider.ThrowIfNull("defaultProvider");
|
||||
|
||||
_defaultProvider = defaultProvider;
|
||||
|
||||
_providers.Add(_defaultProvider);
|
||||
}
|
||||
|
||||
public void Add(IPersistentJobQueueProvider provider, IEnumerable<string> queues)
|
||||
{
|
||||
provider.ThrowIfNull("provider");
|
||||
queues.ThrowIfNull("queues");
|
||||
|
||||
_providers.Add(provider);
|
||||
|
||||
foreach (var queue in queues)
|
||||
{
|
||||
_providersByQueue.Add(queue, provider);
|
||||
}
|
||||
}
|
||||
|
||||
public IPersistentJobQueueProvider GetProvider(string queue)
|
||||
{
|
||||
return _providersByQueue.ContainsKey(queue)
|
||||
? _providersByQueue[queue]
|
||||
: _defaultProvider;
|
||||
}
|
||||
|
||||
public IEnumerator<IPersistentJobQueueProvider> GetEnumerator()
|
||||
{
|
||||
return _providers.GetEnumerator();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
// This file is part of Hangfire.
|
||||
// Copyright © 2013-2014 Sergey Odinokov.
|
||||
//
|
||||
// Hangfire is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as
|
||||
// published by the Free Software Foundation, either version 3
|
||||
// of the License, or any later version.
|
||||
//
|
||||
// Hangfire is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with Hangfire. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using Hangfire.Annotations;
|
||||
using Hangfire.Storage;
|
||||
using HangFire.Raven;
|
||||
using Raven.Client.Linq;
|
||||
using System.Linq;
|
||||
using Hangfire.Raven.Entities;
|
||||
using Hangfire.Raven.Storage;
|
||||
|
||||
namespace Hangfire.Raven.JobQueues
|
||||
{
|
||||
public class RavenJobQueue : IPersistentJobQueue
|
||||
{
|
||||
private readonly RavenStorage _storage;
|
||||
private readonly RavenStorageOptions _options;
|
||||
|
||||
private static object _lockMethod = new object();
|
||||
|
||||
public RavenJobQueue([NotNull] RavenStorage storage, RavenStorageOptions options)
|
||||
{
|
||||
storage.ThrowIfNull("storage");
|
||||
options.ThrowIfNull("options");
|
||||
|
||||
_storage = storage;
|
||||
_options = options;
|
||||
}
|
||||
|
||||
[NotNull]
|
||||
public IFetchedJob Dequeue(string[] queues, CancellationToken cancellationToken)
|
||||
{
|
||||
queues.ThrowIfNull("queues");
|
||||
|
||||
if (queues.Length == 0) {
|
||||
throw new ArgumentException("Queue array must be non-empty.", "queues");
|
||||
}
|
||||
|
||||
lock (_lockMethod) {
|
||||
|
||||
JobQueue fetchedJob;
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
do {
|
||||
using (var repository = new Repository()) {
|
||||
|
||||
fetchedJob = repository.Session.Query<JobQueue>().FirstOrDefault(t => t.FetchedAt == null
|
||||
&& t.Queue.In(queues));
|
||||
|
||||
if (fetchedJob == null) {
|
||||
fetchedJob = repository.Session.Query<JobQueue>().FirstOrDefault(t =>
|
||||
t.FetchedAt < DateTime.UtcNow.AddSeconds(_options.InvisibilityTimeout.Negate().TotalSeconds)
|
||||
&& t.Queue.In(queues));
|
||||
|
||||
if (fetchedJob == null) {
|
||||
cancellationToken.WaitHandle.WaitOne(_options.QueuePollInterval);
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
fetchedJob.FetchedAt = DateTime.UtcNow;
|
||||
|
||||
repository.Save(fetchedJob);
|
||||
}
|
||||
} while (fetchedJob == null);
|
||||
|
||||
return new RavenFetchedJob(_storage, fetchedJob);
|
||||
}
|
||||
}
|
||||
|
||||
public void Enqueue(string queue, string jobId)
|
||||
{
|
||||
using (var repository = new Repository()) {
|
||||
var jobQueue = new JobQueue
|
||||
{
|
||||
JobId = jobId,
|
||||
Queue = queue
|
||||
};
|
||||
|
||||
repository.Save(jobQueue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
// This file is part of Hangfire.
|
||||
// Copyright © 2013-2014 Sergey Odinokov.
|
||||
//
|
||||
// Hangfire is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as
|
||||
// published by the Free Software Foundation, either version 3
|
||||
// of the License, or any later version.
|
||||
//
|
||||
// Hangfire is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with Hangfire. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Hangfire.Raven.Entities;
|
||||
using HangFire.Raven;
|
||||
using Hangfire.Raven.JobQueues;
|
||||
|
||||
namespace Hangfire.Raven.JobQueues
|
||||
{
|
||||
internal class RavenJobQueueMonitoringApi : IPersistentJobQueueMonitoringApi
|
||||
{
|
||||
public IEnumerable<string> GetQueues()
|
||||
{
|
||||
using (var repository = new Repository()) {
|
||||
return repository.Session.Query<JobQueue>()
|
||||
.Select(x => x.Queue)
|
||||
.Distinct()
|
||||
.ToList();
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<string> GetEnqueuedJobIds(string queue, int @from, int perPage)
|
||||
{
|
||||
int start = @from + 1;
|
||||
int end = from + perPage;
|
||||
|
||||
using (var repository = new Repository()) {
|
||||
var jobs = repository.Session.Query<JobQueue>().Where(t => t.Queue == queue && t.FetchedAt == null)
|
||||
.Select((data, i) => new { Index = i + 1, Data = data })
|
||||
.Where(_ => (_.Index >= start) && (_.Index <= end))
|
||||
.Select(x => x.Data)
|
||||
.ToList();
|
||||
|
||||
var results = new List<RavenJob>();
|
||||
|
||||
foreach (var item in jobs) {
|
||||
var job = repository.Session.Query<RavenJob>().FirstOrDefault(t => t.Id == item.JobId);
|
||||
|
||||
if (job != null && repository.Session.Query<State>().FirstOrDefault(t => t.Id == job.StateId) != null) {
|
||||
results.Add(job);
|
||||
}
|
||||
}
|
||||
|
||||
return results.Select(t => t.Id).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<string> GetFetchedJobIds(string queue, int @from, int perPage)
|
||||
{
|
||||
int start = @from + 1;
|
||||
int end = from + perPage;
|
||||
|
||||
using (var repository = new Repository()) {
|
||||
var jobs = repository.Session.Query<JobQueue>().Where(t => t.Queue == queue && t.FetchedAt != null)
|
||||
.Select((data, i) => new { Index = i + 1, Data = data })
|
||||
.Where(_ => (_.Index >= start) && (_.Index <= end))
|
||||
.Select(x => x.Data);
|
||||
|
||||
var results = new List<string>();
|
||||
|
||||
foreach (var item in jobs) {
|
||||
var job = repository.Session.Query<RavenJob>().FirstOrDefault(t => t.Id == item.JobId);
|
||||
|
||||
if (job != null) {
|
||||
results.Add(job.Id);
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
}
|
||||
|
||||
public EnqueuedAndFetchedCount GetEnqueuedAndFetchedCount(string queue)
|
||||
{
|
||||
using (var repository = new Repository()) {
|
||||
int enqueuedCount = repository.Session.Query<JobQueue>().Where(t => t.Queue == queue && t.FetchedAt == null).Count();
|
||||
|
||||
int fetchedCount = repository.Session.Query<JobQueue>().Where(t => t.Queue == queue && t.FetchedAt != null).Count();
|
||||
|
||||
return new EnqueuedAndFetchedCount
|
||||
{
|
||||
EnqueuedCount = enqueuedCount,
|
||||
FetchedCount = fetchedCount
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
// This file is part of Hangfire.
|
||||
// Copyright © 2013-2014 Sergey Odinokov.
|
||||
//
|
||||
// Hangfire is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as
|
||||
// published by the Free Software Foundation, either version 3
|
||||
// of the License, or any later version.
|
||||
//
|
||||
// Hangfire is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with Hangfire. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
using System;
|
||||
using Hangfire.Annotations;
|
||||
using Hangfire.Raven.Storage;
|
||||
|
||||
namespace Hangfire.Raven.JobQueues
|
||||
{
|
||||
public class RavenJobQueueProvider : IPersistentJobQueueProvider
|
||||
{
|
||||
private readonly IPersistentJobQueue _jobQueue;
|
||||
private readonly IPersistentJobQueueMonitoringApi _monitoringApi;
|
||||
|
||||
public RavenJobQueueProvider([NotNull] RavenStorage storage, [NotNull] RavenStorageOptions options)
|
||||
{
|
||||
storage.ThrowIfNull("storage");
|
||||
options.ThrowIfNull("options");
|
||||
|
||||
_jobQueue = new RavenJobQueue(storage, options);
|
||||
//_monitoringApi = new RavenJobQueueMonitoringApi(storage);
|
||||
}
|
||||
|
||||
public IPersistentJobQueue GetJobQueue()
|
||||
{
|
||||
return _jobQueue;
|
||||
}
|
||||
|
||||
public IPersistentJobQueueMonitoringApi GetJobQueueMonitoringApi()
|
||||
{
|
||||
return _monitoringApi;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using Raven.Abstractions.Data;
|
||||
using Raven.Client.Listeners;
|
||||
using System.Linq;
|
||||
|
||||
namespace Hangfire.Raven.Listeners
|
||||
{
|
||||
public class TakeNewestConflictResolutionListener : IDocumentConflictListener
|
||||
{
|
||||
public bool TryResolveConflict(string key, JsonDocument[] conflictedDocs, out JsonDocument resolvedDocument)
|
||||
{
|
||||
var maxDate = conflictedDocs.Max(x => x.LastModified);
|
||||
resolvedDocument = conflictedDocs
|
||||
.FirstOrDefault(x => x.LastModified == maxDate);
|
||||
|
||||
if (resolvedDocument == null)
|
||||
return false;
|
||||
|
||||
resolvedDocument.Metadata.Remove("@id");
|
||||
resolvedDocument.Metadata.Remove("@etag");
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
|
||||
namespace Hangfire.Raven
|
||||
{
|
||||
public static class ObjectExtensions
|
||||
{
|
||||
public static void ThrowIfNull(this object value, string name)
|
||||
{
|
||||
if (value == null) {
|
||||
throw new ArgumentNullException(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
[assembly: AssemblyTitle("Hangfire.Raven")]
|
||||
[assembly: AssemblyDescription("Raven job storage for Hangfire")]
|
||||
[assembly: Guid("3d96bf2f-8854-4872-aee3-faf81d121a4d")]
|
||||
|
||||
[assembly: InternalsVisibleTo("Hangfire.Raven.Tests")]
|
||||
// Allow the generation of mocks for internal types
|
||||
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]
|
||||
Binary file not shown.
@@ -0,0 +1,536 @@
|
||||
// This file is part of Hangfire.
|
||||
// Copyright © 2013-2014 Sergey Odinokov.
|
||||
//
|
||||
// Hangfire is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as
|
||||
// published by the Free Software Foundation, either version 3
|
||||
// of the License, or any later version.
|
||||
//
|
||||
// Hangfire is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with Hangfire. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using Hangfire.Common;
|
||||
using Hangfire.Server;
|
||||
using Hangfire.Storage;
|
||||
using Raven.Client;
|
||||
using Hangfire.Raven.DistributedLock;
|
||||
using HangFire.Raven;
|
||||
using Hangfire.Raven.Entities;
|
||||
using Hangfire.Raven.Storage;
|
||||
|
||||
namespace Hangfire.Raven
|
||||
{
|
||||
public class RavenConnection : JobStorageConnection
|
||||
{
|
||||
private readonly RavenStorageOptions _options;
|
||||
private readonly RavenStorage _storage;
|
||||
|
||||
public RavenConnection(RavenStorage ravenStorage)
|
||||
{
|
||||
ravenStorage.ThrowIfNull("RavenStorage");
|
||||
|
||||
_storage = ravenStorage;
|
||||
_options = new RavenStorageOptions();
|
||||
}
|
||||
|
||||
public RavenConnection(RavenStorage ravenStorage, RavenStorageOptions options)
|
||||
: this(ravenStorage)
|
||||
{
|
||||
options.ThrowIfNull("options");
|
||||
|
||||
_storage = ravenStorage;
|
||||
_options = options;
|
||||
}
|
||||
|
||||
public override IWriteOnlyTransaction CreateWriteTransaction()
|
||||
{
|
||||
return new RavenWriteOnlyTransaction(_storage);
|
||||
}
|
||||
|
||||
public override IDisposable AcquireDistributedLock(string resource, TimeSpan timeout)
|
||||
{
|
||||
return new RavenDistributedLock(_storage, string.Format("HangFire:{0}", resource), timeout, _options);
|
||||
}
|
||||
|
||||
public static IDocumentStore _documentStore;
|
||||
|
||||
public override IFetchedJob FetchNextJob(string[] queues, CancellationToken cancellationToken)
|
||||
{
|
||||
queues.ThrowIfNull("queues");
|
||||
if (queues.Length == 0) {
|
||||
throw new ArgumentNullException("queues");
|
||||
}
|
||||
|
||||
var providers = queues
|
||||
.Select(queue => _storage.QueueProviders.GetProvider(queue))
|
||||
.Distinct()
|
||||
.ToArray();
|
||||
|
||||
if (providers.Length != 1) {
|
||||
throw new InvalidOperationException(string.Format(
|
||||
"Multiple provider instances registered for queues: {0}. You should choose only one type of persistent queues per server instance.",
|
||||
string.Join(", ", queues)));
|
||||
}
|
||||
|
||||
var persistentQueue = providers[0].GetJobQueue();
|
||||
return persistentQueue.Dequeue(queues, cancellationToken);
|
||||
}
|
||||
|
||||
public override string CreateExpiredJob(
|
||||
Job job,
|
||||
IDictionary<string, string> parameters,
|
||||
DateTime createdAt,
|
||||
TimeSpan expireIn)
|
||||
{
|
||||
job.ThrowIfNull("job");
|
||||
parameters.ThrowIfNull("parameters");
|
||||
|
||||
var invocationData = InvocationData.Serialize(job);
|
||||
|
||||
var ravenJob = new RavenJob
|
||||
{
|
||||
Id = Guid.NewGuid().ToString(),
|
||||
InvocationData = JobHelper.ToJson(invocationData),
|
||||
Arguments = invocationData.Arguments,
|
||||
CreatedAt = createdAt,
|
||||
ExpireAt = createdAt.Add(expireIn)
|
||||
};
|
||||
|
||||
using (var repository = new Repository()) {
|
||||
repository.Save(ravenJob);
|
||||
|
||||
if (parameters.Count > 0) {
|
||||
foreach (var parameter in parameters) {
|
||||
repository.Save(new JobParameter
|
||||
{
|
||||
JobId = ravenJob.Id,
|
||||
Name = parameter.Key,
|
||||
Value = parameter.Value
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return ravenJob.Id.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
public override JobData GetJobData(string id)
|
||||
{
|
||||
id.ThrowIfNull("id");
|
||||
|
||||
using (var repository = new Repository()) {
|
||||
var jobData = repository.Session.Load<RavenJob>(id);
|
||||
|
||||
if (jobData == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var invocationData = JobHelper.FromJson<InvocationData>(jobData.InvocationData);
|
||||
invocationData.Arguments = jobData.Arguments;
|
||||
|
||||
Job job = null;
|
||||
JobLoadException loadException = null;
|
||||
|
||||
try {
|
||||
job = invocationData.Deserialize();
|
||||
} catch (JobLoadException ex) {
|
||||
loadException = ex;
|
||||
}
|
||||
|
||||
return new JobData
|
||||
{
|
||||
Job = job,
|
||||
State = jobData.StateName,
|
||||
CreatedAt = jobData.CreatedAt,
|
||||
LoadException = loadException
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public override StateData GetStateData(string jobId)
|
||||
{
|
||||
jobId.ThrowIfNull("jobId");
|
||||
|
||||
using (var repository = new Repository()) {
|
||||
RavenJob job = repository.Session.Load<RavenJob>(jobId);
|
||||
if (job == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
State state = repository.Session.Query<State>().FirstOrDefault(t => t.Id == job.StateId);
|
||||
if (state == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new StateData
|
||||
{
|
||||
Name = state.Name,
|
||||
Reason = state.Reason,
|
||||
Data = JobHelper.FromJson<Dictionary<string, string>>(state.Data)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public override void SetJobParameter(string id, string name, string value)
|
||||
{
|
||||
id.ThrowIfNull("id");
|
||||
name.ThrowIfNull("name");
|
||||
|
||||
using (var repository = new Repository()) {
|
||||
var results = repository.Session.Query<JobParameter>().Where(t => t.JobId == id && t.Name == name).ToList();
|
||||
|
||||
results.ForEach(t => {
|
||||
t.Value = value;
|
||||
repository.Save(t);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public override string GetJobParameter(string id, string name)
|
||||
{
|
||||
id.ThrowIfNull("id");
|
||||
name.ThrowIfNull("name");
|
||||
|
||||
using (var repository = new Repository()) {
|
||||
var jobParameter = repository.Session.Query<JobParameter>().FirstOrDefault(t => t.JobId == id && t.Name == name);
|
||||
|
||||
return jobParameter != null ? jobParameter.Value : null;
|
||||
}
|
||||
}
|
||||
|
||||
public override HashSet<string> GetAllItemsFromSet(string key)
|
||||
{
|
||||
key.ThrowIfNull("key");
|
||||
|
||||
using (var repository = new Repository()) {
|
||||
var results = repository.Session.Query<RavenSet>().Where(t => t.Key == key).Select(t => t.Value).ToList();
|
||||
|
||||
return new HashSet<string>(results);
|
||||
}
|
||||
}
|
||||
|
||||
public override string GetFirstByLowestScoreFromSet(string key, double fromScore, double toScore)
|
||||
{
|
||||
key.ThrowIfNull("key");
|
||||
|
||||
if (toScore < fromScore) {
|
||||
throw new ArgumentException("The `toScore` value must be higher or equal to the `fromScore` value.");
|
||||
}
|
||||
|
||||
using (var repository = new Repository()) {
|
||||
var set = repository.Session.Query<RavenSet>()
|
||||
.Where(t => t.Key == key)
|
||||
.Where(t => t.Score >= fromScore)
|
||||
.Where(t => t.Score <= toScore)
|
||||
.OrderBy(t => t.Score)
|
||||
.FirstOrDefault();
|
||||
|
||||
|
||||
return set != null ? set.Value : null;
|
||||
}
|
||||
}
|
||||
|
||||
public override void SetRangeInHash(string key, IEnumerable<KeyValuePair<string, string>> keyValuePairs)
|
||||
{
|
||||
key.ThrowIfNull("key");
|
||||
keyValuePairs.ThrowIfNull("keyValuePairs");
|
||||
|
||||
using (var repository = new Repository()) {
|
||||
foreach (var keyValuePair in keyValuePairs) {
|
||||
var results = repository.Session.Query<RavenHash>().Where(t => t.Key == key && t.Field == keyValuePair.Key).ToList();
|
||||
|
||||
results.ForEach(t =>
|
||||
{
|
||||
t.Value = keyValuePair.Value;
|
||||
repository.Save(t);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override Dictionary<string, string> GetAllEntriesFromHash(string key)
|
||||
{
|
||||
key.ThrowIfNull("key");
|
||||
|
||||
using (var session = new Repository().Session) {
|
||||
var result = session.Query<RavenHash>().Where(t => t.Key == key)
|
||||
.ToDictionary(x => x.Field, x => x.Value);
|
||||
|
||||
return result.Count != 0 ? result : null;
|
||||
}
|
||||
}
|
||||
|
||||
public override void AnnounceServer(string serverId, ServerContext context)
|
||||
{
|
||||
serverId.ThrowIfNull("serverId");
|
||||
context.ThrowIfNull("context");
|
||||
|
||||
var data = new ServerData
|
||||
{
|
||||
WorkerCount = context.WorkerCount,
|
||||
Queues = context.Queues,
|
||||
StartedAt = DateTime.UtcNow,
|
||||
};
|
||||
|
||||
using (var repository = new Repository()) {
|
||||
var servers = repository.Session.Query<RavenServer>()
|
||||
.Where(t => t.Id == serverId)
|
||||
.ToList();
|
||||
|
||||
var queues = servers.SelectMany(t => JobHelper.FromJson<ServerData>(t.Data).Queues).ToList();
|
||||
queues.AddRange(data.Queues.Select(t => t).ToList());
|
||||
data.Queues = queues.ToArray();
|
||||
|
||||
data.WorkerCount += servers.Select(t => JobHelper.FromJson<ServerData>(t.Data).WorkerCount).Sum(t => t);
|
||||
|
||||
foreach (var item in servers) {
|
||||
repository.Delete(item);
|
||||
}
|
||||
|
||||
var server = new RavenServer
|
||||
{
|
||||
Data = JobHelper.ToJson(data),
|
||||
Id = serverId,
|
||||
LastHeartbeat = DateTime.UtcNow
|
||||
};
|
||||
|
||||
repository.Save(server);
|
||||
}
|
||||
}
|
||||
|
||||
public override void RemoveServer(string serverId)
|
||||
{
|
||||
serverId.ThrowIfNull("serverId");
|
||||
|
||||
using (var repository = new Repository()) {
|
||||
var servers = repository.Session.Query<RavenServer>()
|
||||
.Where(t => t.Id == serverId)
|
||||
.ToList();
|
||||
|
||||
foreach (var item in servers) {
|
||||
repository.Delete(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void Heartbeat(string serverId)
|
||||
{
|
||||
serverId.ThrowIfNull("serverId");
|
||||
|
||||
using (var repository = new Repository()) {
|
||||
var server = repository.Session.Load<RavenServer>(serverId);
|
||||
|
||||
if (server == null) {
|
||||
server = new RavenServer
|
||||
{
|
||||
Id = serverId
|
||||
};
|
||||
}
|
||||
|
||||
server.LastHeartbeat = DateTime.UtcNow;
|
||||
|
||||
repository.Save(server);
|
||||
}
|
||||
}
|
||||
|
||||
public override int RemoveTimedOutServers(TimeSpan timeOut)
|
||||
{
|
||||
if (timeOut.Duration() != timeOut) {
|
||||
throw new ArgumentException("The `timeOut` value must be positive.", "timeOut");
|
||||
}
|
||||
|
||||
using (var repository = new Repository()) {
|
||||
var heartBeatCutOff = DateTime.UtcNow.Add(timeOut.Negate());
|
||||
|
||||
var results = repository.Session.Query<RavenServer>()
|
||||
.Where(t => t.LastHeartbeat < heartBeatCutOff)
|
||||
.ToList();
|
||||
|
||||
foreach (var item in results) {
|
||||
repository.Delete(item);
|
||||
}
|
||||
|
||||
return results.Count;
|
||||
}
|
||||
}
|
||||
|
||||
public override long GetSetCount(string key)
|
||||
{
|
||||
key.ThrowIfNull("key");
|
||||
|
||||
using (var repository = new Repository()) {
|
||||
var count = repository.Session.Query<RavenSet>()
|
||||
.Where(t => t.Key == key)
|
||||
.Count();
|
||||
|
||||
return count;
|
||||
}
|
||||
}
|
||||
|
||||
public override List<string> GetRangeFromSet(string key, int startingFrom, int endingAt)
|
||||
{
|
||||
key.ThrowIfNull("key");
|
||||
|
||||
using (var repository = new Repository()) {
|
||||
var values = repository.Session.Query<RavenSet>()
|
||||
.Where(t => t.Key == key)
|
||||
.Skip(startingFrom - 1)
|
||||
.Take(endingAt - startingFrom + 1)
|
||||
.Select(t => t.Value)
|
||||
.ToList();
|
||||
|
||||
return values;
|
||||
}
|
||||
}
|
||||
|
||||
public override TimeSpan GetSetTtl(string key)
|
||||
{
|
||||
key.ThrowIfNull("key");
|
||||
|
||||
using (var repository = new Repository()) {
|
||||
var expireAt = repository.Session.Query<RavenSet>()
|
||||
.Where(t => t.Key == key)
|
||||
.Select(t => t.ExpireAt)
|
||||
.ToArray();
|
||||
|
||||
if (expireAt.Any() == false) {
|
||||
return TimeSpan.FromSeconds(-1);
|
||||
}
|
||||
|
||||
return expireAt.Where(t => t.HasValue).Min(t => t.Value) - DateTime.UtcNow;
|
||||
}
|
||||
}
|
||||
|
||||
public override long GetCounter(string key)
|
||||
{
|
||||
key.ThrowIfNull("key");
|
||||
|
||||
using (var repository = new Repository()) {
|
||||
var values = repository.Session.Query<AggregatedCounter>()
|
||||
.Where(t => t.Key == key)
|
||||
.Select(t => t.Value)
|
||||
.ToArray();
|
||||
|
||||
return values.Any() ? values.Sum() : 0;
|
||||
}
|
||||
}
|
||||
|
||||
public override long GetHashCount(string key)
|
||||
{
|
||||
key.ThrowIfNull("key");
|
||||
|
||||
using (var repository = new Repository()) {
|
||||
var count = repository.Session.Query<RavenHash>()
|
||||
.Where(t => t.Key == key)
|
||||
.Count();
|
||||
|
||||
return count;
|
||||
}
|
||||
}
|
||||
|
||||
public override TimeSpan GetHashTtl(string key)
|
||||
{
|
||||
key.ThrowIfNull("key");
|
||||
|
||||
using (var repository = new Repository()) {
|
||||
var hashes = repository.Session.Query<RavenHash>()
|
||||
.Where(t => t.Key == key)
|
||||
.ToList();
|
||||
|
||||
DateTime? result = hashes.Any() ? hashes.Min(x => x.ExpireAt) : null;
|
||||
|
||||
if (!result.HasValue) {
|
||||
return TimeSpan.FromSeconds(-1);
|
||||
}
|
||||
|
||||
return result.Value - DateTime.UtcNow;
|
||||
}
|
||||
}
|
||||
|
||||
public override string GetValueFromHash(string key, string name)
|
||||
{
|
||||
key.ThrowIfNull("key");
|
||||
name.ThrowIfNull("name");
|
||||
|
||||
using (var repository = new Repository()) {
|
||||
var result = repository.Session.Query<RavenHash>()
|
||||
.Where(t => t.Key == key)
|
||||
.Where(t => t.Field == name)
|
||||
.FirstOrDefault();
|
||||
|
||||
return result != null ? result.Value : null;
|
||||
}
|
||||
}
|
||||
|
||||
public override long GetListCount(string key)
|
||||
{
|
||||
key.ThrowIfNull("key");
|
||||
|
||||
using (var repository = new Repository()) {
|
||||
var count = repository.Session.Query<RavenList>()
|
||||
.Where(t => t.Key == key)
|
||||
.Count();
|
||||
|
||||
return count;
|
||||
}
|
||||
}
|
||||
|
||||
public override TimeSpan GetListTtl(string key)
|
||||
{
|
||||
key.ThrowIfNull("key");
|
||||
|
||||
using (var repository = new Repository()) {
|
||||
var results = repository.Session.Query<RavenList>()
|
||||
.Where(t => t.Key == key)
|
||||
.ToList();
|
||||
|
||||
DateTime? ttl = results.Any() ? results.Min(t => t.ExpireAt) : (DateTime?)null;
|
||||
|
||||
if (!ttl.HasValue) {
|
||||
return TimeSpan.FromSeconds(-1);
|
||||
}
|
||||
|
||||
return ttl.Value - DateTime.UtcNow;
|
||||
}
|
||||
}
|
||||
|
||||
public override List<string> GetRangeFromList(string key, int startingFrom, int endingAt)
|
||||
{
|
||||
key.ThrowIfNull("key");
|
||||
|
||||
using (var repository = new Repository()) {
|
||||
var results = repository.Session.Query<RavenList>()
|
||||
.Where(t => t.Key == key)
|
||||
.Select(t => t.Value)
|
||||
.Skip(startingFrom - 1)
|
||||
.Take(endingAt - startingFrom + 1)
|
||||
.ToList();
|
||||
|
||||
return results;
|
||||
}
|
||||
}
|
||||
|
||||
public override List<string> GetAllItemsFromList(string key)
|
||||
{
|
||||
key.ThrowIfNull("key");
|
||||
|
||||
using (var repository = new Repository()) {
|
||||
var results = repository.Session.Query<RavenList>()
|
||||
.Where(t => t.Key == key)
|
||||
.Select(t => t.Value)
|
||||
.ToList();
|
||||
|
||||
return results;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,373 @@
|
||||
// This file is part of Hangfire.
|
||||
// Copyright © 2013-2014 Sergey Odinokov.
|
||||
//
|
||||
// Hangfire is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as
|
||||
// published by the Free Software Foundation, either version 3
|
||||
// of the License, or any later version.
|
||||
//
|
||||
// Hangfire is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with Hangfire. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Hangfire.Annotations;
|
||||
using Hangfire.Common;
|
||||
using Hangfire.States;
|
||||
using Hangfire.Storage;
|
||||
using HangFire.Raven;
|
||||
using Hangfire.Raven.Entities;
|
||||
using Hangfire.Raven.Storage;
|
||||
|
||||
namespace Hangfire.Raven
|
||||
{
|
||||
internal class RavenWriteOnlyTransaction : JobStorageTransaction
|
||||
{
|
||||
private readonly Queue<Action<RavenConnection>> _commandQueue
|
||||
= new Queue<Action<RavenConnection>>();
|
||||
|
||||
private readonly SortedSet<string> _lockedResources = new SortedSet<string>();
|
||||
private readonly RavenStorage _storage;
|
||||
public RavenWriteOnlyTransaction([NotNull] RavenStorage storage)
|
||||
{
|
||||
storage.ThrowIfNull("storage");
|
||||
|
||||
_storage = storage;
|
||||
}
|
||||
|
||||
public override void Commit()
|
||||
{
|
||||
foreach (var command in _commandQueue) {
|
||||
command(new RavenConnection(_storage));
|
||||
}
|
||||
}
|
||||
|
||||
public override void ExpireJob(string jobId, TimeSpan expireIn)
|
||||
{
|
||||
using (var repository = new Repository()) {
|
||||
var result = repository.Session.Load<RavenJob>(jobId);
|
||||
|
||||
result.ExpireAt = DateTime.UtcNow.Add(expireIn);
|
||||
|
||||
repository.Save(result);
|
||||
}
|
||||
}
|
||||
|
||||
public override void PersistJob(string jobId)
|
||||
{
|
||||
using (var repository = new Repository()) {
|
||||
var result = repository.Session.Load<RavenJob>(jobId);
|
||||
|
||||
result.ExpireAt = null;
|
||||
|
||||
repository.Save(result);
|
||||
}
|
||||
}
|
||||
|
||||
public override void SetJobState(string jobId, IState state)
|
||||
{
|
||||
using (var repository = new Repository()) {
|
||||
var jobState = repository.Session.Query<State>().FirstOrDefault(t => t.JobId == jobId);
|
||||
|
||||
if (jobState == null) {
|
||||
jobState = new State
|
||||
{
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
JobId = jobId
|
||||
};
|
||||
}
|
||||
|
||||
jobState.Reason = state.Reason;
|
||||
jobState.Name = state.Name;
|
||||
jobState.Data = JobHelper.ToJson(state.SerializeData());
|
||||
|
||||
repository.Save(jobState);
|
||||
|
||||
var result = repository.Session.Load<RavenJob>(jobId);
|
||||
|
||||
result.StateId = jobState.Id;
|
||||
result.StateName = state.Name;
|
||||
result.StateData = JobHelper.ToJson(state.SerializeData());
|
||||
result.StateReason = state.Reason;
|
||||
|
||||
repository.Save(result);
|
||||
}
|
||||
}
|
||||
|
||||
public override void AddJobState(string jobId, IState state)
|
||||
{
|
||||
using (var repository = new Repository()) {
|
||||
var jobState = new State
|
||||
{
|
||||
Id = Guid.NewGuid().ToString(),
|
||||
JobId = jobId,
|
||||
Name = state.Name,
|
||||
Reason = state.Reason,
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
Data = JobHelper.ToJson(state.SerializeData())
|
||||
};
|
||||
|
||||
repository.Save(jobState);
|
||||
}
|
||||
}
|
||||
|
||||
public override void AddToQueue(string queue, string jobId)
|
||||
{
|
||||
var provider = _storage.QueueProviders.GetProvider(queue);
|
||||
var persistentQueue = provider.GetJobQueue();
|
||||
|
||||
persistentQueue.Enqueue(queue, jobId);
|
||||
}
|
||||
|
||||
public override void IncrementCounter(string key)
|
||||
{
|
||||
using (var repository = new Repository()) {
|
||||
var counter = repository.Session.Query<Counter>().FirstOrDefault(t => t.Key == key);
|
||||
|
||||
if (counter == null) {
|
||||
counter = new Counter
|
||||
{
|
||||
Id = Guid.NewGuid().ToString(),
|
||||
Key = key,
|
||||
Value = 0
|
||||
};
|
||||
}
|
||||
|
||||
counter.Value += 1;
|
||||
|
||||
repository.Save(counter);
|
||||
}
|
||||
}
|
||||
|
||||
public override void IncrementCounter(string key, TimeSpan expireIn)
|
||||
{
|
||||
using (var repository = new Repository()) {
|
||||
var counter = repository.Session.Query<Counter>().FirstOrDefault(t => t.Key == key);
|
||||
|
||||
if (counter == null) {
|
||||
counter = new Counter
|
||||
{
|
||||
Id = Guid.NewGuid().ToString(),
|
||||
ExpireAt = DateTime.UtcNow.Add(expireIn),
|
||||
Key = key,
|
||||
Value = 0
|
||||
};
|
||||
}
|
||||
|
||||
counter.Value += 1;
|
||||
|
||||
repository.Save(counter);
|
||||
}
|
||||
}
|
||||
|
||||
public override void DecrementCounter(string key)
|
||||
{
|
||||
using (var repository = new Repository()) {
|
||||
var counter = repository.Session.Query<Counter>().FirstOrDefault(t => t.Key == key);
|
||||
|
||||
if (counter == null) {
|
||||
counter = new Counter
|
||||
{
|
||||
Id = Guid.NewGuid().ToString(),
|
||||
Key = key,
|
||||
Value = 0
|
||||
};
|
||||
}
|
||||
|
||||
counter.Value -= 1;
|
||||
|
||||
repository.Save(counter);
|
||||
}
|
||||
}
|
||||
|
||||
public override void DecrementCounter(string key, TimeSpan expireIn)
|
||||
{
|
||||
using (var repository = new Repository()) {
|
||||
var counter = repository.Session.Query<Counter>().FirstOrDefault(t => t.Key == key);
|
||||
|
||||
if (counter == null) {
|
||||
counter = new Counter
|
||||
{
|
||||
Id = Guid.NewGuid().ToString(),
|
||||
ExpireAt = DateTime.UtcNow.Add(expireIn),
|
||||
Key = key,
|
||||
Value = 0
|
||||
};
|
||||
}
|
||||
|
||||
counter.Value -= 1;
|
||||
|
||||
repository.Save(counter);
|
||||
}
|
||||
}
|
||||
|
||||
public override void AddToSet(string key, string value)
|
||||
{
|
||||
AddToSet(key, value, 0.0);
|
||||
}
|
||||
|
||||
public override void AddToSet(string key, string value, double score)
|
||||
{
|
||||
using (var repository = new Repository()) {
|
||||
var set = repository.Session.Query<RavenSet>().FirstOrDefault(t => t.Key == key && t.Value == value);
|
||||
|
||||
if (set == null) {
|
||||
set = new RavenSet
|
||||
{
|
||||
Id = Guid.NewGuid().ToString(),
|
||||
Key = key,
|
||||
Value = value
|
||||
};
|
||||
}
|
||||
|
||||
set.Score = score;
|
||||
|
||||
repository.Save(set);
|
||||
}
|
||||
}
|
||||
|
||||
public override void RemoveFromSet(string key, string value)
|
||||
{
|
||||
using (var repository = new Repository()) {
|
||||
var set = repository.Session.Query<RavenSet>().FirstOrDefault(t => t.Key == key && t.Value == value);
|
||||
|
||||
if (set == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
repository.Delete(set);
|
||||
}
|
||||
}
|
||||
|
||||
public override void InsertToList(string key, string value)
|
||||
{
|
||||
using (var repository = new Repository()) {
|
||||
var list = repository.Session.Query<RavenList>().FirstOrDefault(t => t.Key == key && t.Value == value);
|
||||
|
||||
if (list == null) {
|
||||
list = new RavenList
|
||||
{
|
||||
Id = Guid.NewGuid().ToString(),
|
||||
Key = key
|
||||
};
|
||||
}
|
||||
|
||||
list.Value = value;
|
||||
|
||||
repository.Save(list);
|
||||
}
|
||||
}
|
||||
|
||||
public override void RemoveFromList(string key, string value)
|
||||
{
|
||||
using (var repository = new Repository()) {
|
||||
var list = repository.Session.Query<RavenList>().FirstOrDefault(t => t.Key == key && t.Value == value);
|
||||
|
||||
if (list == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
repository.Delete(list);
|
||||
}
|
||||
}
|
||||
|
||||
public override void TrimList(string key, int keepStartingFrom, int keepEndingAt)
|
||||
{
|
||||
using (var repository = new Repository()) {
|
||||
var list = repository.Session.Query<RavenList>().Where(t => t.Key == key).ToList();
|
||||
|
||||
if (!list.Any()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (var i = 0; i < list.Count; ++i) {
|
||||
if (i >= keepStartingFrom && i <= keepEndingAt) {
|
||||
continue;
|
||||
}
|
||||
|
||||
repository.Delete(list[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void SetRangeInHash(string key, IEnumerable<KeyValuePair<string, string>> keyValuePairs)
|
||||
{
|
||||
key.ThrowIfNull("key");
|
||||
keyValuePairs.ThrowIfNull("keyValuePairs");
|
||||
|
||||
using (var repository = new Repository()) {
|
||||
foreach (var keyValuePair in keyValuePairs) {
|
||||
var pair = keyValuePair;
|
||||
|
||||
var results = repository.Session.Query<RavenHash>().Where(t => t.Key == key && t.Field == pair.Key).ToList();
|
||||
|
||||
if (!results.Any()) {
|
||||
results.Add(new RavenHash
|
||||
{
|
||||
Key = key,
|
||||
Field = pair.Key,
|
||||
});
|
||||
}
|
||||
|
||||
foreach (var item in results) {
|
||||
item.Value = pair.Value;
|
||||
|
||||
repository.Save(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void RemoveHash(string key)
|
||||
{
|
||||
key.ThrowIfNull("key");
|
||||
|
||||
using (var repository = new Repository()) {
|
||||
var result = repository.Session.Query<RavenHash>().Where(t => t.Key == key).ToList();
|
||||
|
||||
foreach (var item in result) {
|
||||
repository.Session.Delete(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void RemoveSet(string key)
|
||||
{
|
||||
key.ThrowIfNull("key");
|
||||
|
||||
using (var repository = new Repository()) {
|
||||
var result = repository.Session.Query<RavenSet>().Where(t => t.Key == key).ToList();
|
||||
|
||||
foreach (var item in result) {
|
||||
repository.Session.Delete(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void ExpireHash(string key, TimeSpan expireIn)
|
||||
{
|
||||
key.ThrowIfNull("key");
|
||||
|
||||
using (var repository = new Repository()) {
|
||||
var result = repository.Session.Query<RavenSet>().Where(t => t.Key == key).ToList();
|
||||
|
||||
foreach (var item in result) {
|
||||
item.ExpireAt = DateTime.UtcNow.Add(expireIn);
|
||||
|
||||
repository.Save(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal void QueueCommand(Action<RavenConnection> action)
|
||||
{
|
||||
_commandQueue.Enqueue(action);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,358 @@
|
||||
using Raven.Client;
|
||||
using System;
|
||||
using Dewey.Net.Types;
|
||||
using Raven.Client.Document;
|
||||
using Raven.Client.Embedded;
|
||||
using System.Threading.Tasks;
|
||||
using System.Collections.Generic;
|
||||
using Dewey.Net.Generic;
|
||||
using Raven.Abstractions.Data;
|
||||
using System.Collections.Concurrent;
|
||||
using Raven.Client.Linq;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using Hangfire.Raven.Entities.Identity;
|
||||
using Hangfire.Raven.Extensions;
|
||||
using Hangfire.Raven.Listeners;
|
||||
|
||||
namespace HangFire.Raven
|
||||
{
|
||||
public class Repository : IDisposable
|
||||
{
|
||||
public static string ConnectionUrl { get; set; }
|
||||
public static string DefaultDatabase { get; set; }
|
||||
|
||||
private static IDocumentStore _documentStore;
|
||||
private IDocumentSession _session;
|
||||
private IAsyncDocumentSession _asyncSession;
|
||||
private string _database;
|
||||
|
||||
public static bool Embedded { get; set; }
|
||||
|
||||
public string Database => _database ?? DefaultDatabase;
|
||||
|
||||
public void Destroy()
|
||||
{
|
||||
if (!DocumentStore.DatabaseExists(Database)) {
|
||||
return;
|
||||
}
|
||||
|
||||
DocumentStore.DatabaseCommands.GlobalAdmin.DeleteDatabase(Database, hardDelete: true);
|
||||
}
|
||||
|
||||
public void Create()
|
||||
{
|
||||
if (DocumentStore.DatabaseExists(Database)) {
|
||||
return;
|
||||
}
|
||||
|
||||
DocumentStore.DatabaseCommands.GlobalAdmin.EnsureDatabaseExists(Database);
|
||||
}
|
||||
|
||||
public IDocumentStore DocumentStore
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_documentStore == null) {
|
||||
if (Embedded) {
|
||||
_documentStore = new EmbeddableDocumentStore
|
||||
{
|
||||
RunInMemory = true
|
||||
};
|
||||
|
||||
((EmbeddableDocumentStore)_documentStore).Configuration.Storage.Voron.AllowOn32Bits = true;
|
||||
} else {
|
||||
_documentStore = new DocumentStore
|
||||
{
|
||||
Url = ConnectionUrl
|
||||
};
|
||||
}
|
||||
|
||||
_documentStore.Conventions.DefaultQueryingConsistency = ConsistencyOptions.AlwaysWaitForNonStaleResultsAsOfLastWrite;
|
||||
_documentStore.Listeners.RegisterListener(new TakeNewestConflictResolutionListener());
|
||||
_documentStore.Conventions.DefaultQueryingConsistency = ConsistencyOptions.AlwaysWaitForNonStaleResultsAsOfLastWrite;
|
||||
|
||||
_documentStore.Initialize();
|
||||
}
|
||||
|
||||
return _documentStore;
|
||||
}
|
||||
}
|
||||
|
||||
public IDocumentSession Session => _session ?? (_session = DocumentStore.OpenSession(Database));
|
||||
|
||||
public IAsyncDocumentSession AsyncSession => _asyncSession ?? (_asyncSession = DocumentStore.OpenAsyncSession(Database));
|
||||
|
||||
#region constructor
|
||||
|
||||
public Repository()
|
||||
{
|
||||
_database = DefaultDatabase;
|
||||
}
|
||||
|
||||
public Repository(string database)
|
||||
{
|
||||
if (database.IsEmpty()) {
|
||||
_database = DefaultDatabase;
|
||||
} else {
|
||||
_database = database;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Bulk Insert
|
||||
|
||||
private BulkInsertOptions GetBulkInsertOptions(bool overwrite = true) => new BulkInsertOptions
|
||||
{
|
||||
BatchSize = 100,
|
||||
WriteTimeoutMilliseconds = 360000,
|
||||
OverwriteExisting = overwrite
|
||||
};
|
||||
|
||||
public void BulkInsert<T>(ConcurrentBag<T> entities, bool overwrite = true) where T : BaseEntity
|
||||
{
|
||||
if (_database.IsEmpty()) {
|
||||
_database = DefaultDatabase;
|
||||
}
|
||||
|
||||
using (var bulkInsert = DocumentStore.BulkInsert(Database, GetBulkInsertOptions(overwrite))) {
|
||||
foreach (var item in entities) {
|
||||
if (item == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (item.Id.IsEmpty()) {
|
||||
item.Id = Guid.NewGuid().ToString();
|
||||
}
|
||||
|
||||
bulkInsert.Store(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void BulkInsert<T>(List<T> entities, bool overwrite = true) where T : BaseEntity
|
||||
{
|
||||
var concurrentBag = new ConcurrentBag<T>();
|
||||
|
||||
entities.ForEach(t => concurrentBag.Add(t));
|
||||
|
||||
BulkInsert(concurrentBag, overwrite);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Find
|
||||
|
||||
public T FindById<T>(string id) where T : BaseEntity
|
||||
{
|
||||
if (id == null) {
|
||||
throw new ArgumentException("Id must be provided.", nameof(id));
|
||||
}
|
||||
|
||||
return Session.Load<T>(id);
|
||||
}
|
||||
|
||||
public async Task<T> FindByIdAsync<T>(string id) where T : BaseEntity
|
||||
{
|
||||
if (id == null) {
|
||||
throw new ArgumentException("Id must be provided.", nameof(id));
|
||||
}
|
||||
|
||||
return await AsyncSession.LoadAsync<T>(id);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Save
|
||||
|
||||
public void Save<T>(T entity) where T : BaseEntity
|
||||
{
|
||||
if (entity == null) {
|
||||
throw new ArgumentException("Entity must be provided.", nameof(entity));
|
||||
}
|
||||
|
||||
if (entity.Id.IsEmpty()) {
|
||||
entity.Id = Guid.NewGuid().ToString();
|
||||
}
|
||||
|
||||
Session.Store(entity);
|
||||
Session.SaveChanges();
|
||||
}
|
||||
|
||||
public async Task SaveAsync<T>(T entity) where T : BaseEntity
|
||||
{
|
||||
if (entity == null) {
|
||||
throw new ArgumentException("Entity must be provided.", nameof(entity));
|
||||
}
|
||||
|
||||
if (entity.Id.IsEmpty()) {
|
||||
entity.Id = Guid.NewGuid().ToString();
|
||||
}
|
||||
|
||||
await AsyncSession.StoreAsync(entity);
|
||||
await AsyncSession.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task SaveNoValidateAsync<T>(T entity) where T : BaseEntity
|
||||
{
|
||||
if (entity == null) {
|
||||
throw new ArgumentException("Entity must be provided.", nameof(entity));
|
||||
}
|
||||
|
||||
if (entity.Id.IsEmpty()) {
|
||||
entity.Id = Guid.NewGuid().ToString();
|
||||
}
|
||||
|
||||
await AsyncSession.StoreAsync(entity);
|
||||
await AsyncSession.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public void SaveAll<T>(List<T> entities) where T : BaseEntity
|
||||
{
|
||||
if (entities.IsEmpty()) {
|
||||
throw new ArgumentException("Data must be provided.", nameof(entities));
|
||||
}
|
||||
|
||||
foreach (var item in entities) {
|
||||
Save(item);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task SaveAllAsync<T>(List<T> entities) where T : BaseEntity
|
||||
{
|
||||
if (entities.IsEmpty()) {
|
||||
throw new ArgumentException("Data must be provided.", nameof(entities));
|
||||
}
|
||||
|
||||
foreach (var item in entities) {
|
||||
await SaveAsync(item);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Delete
|
||||
|
||||
public void Delete(string id)
|
||||
{
|
||||
if (id == null) {
|
||||
throw new ArgumentException("Id must be provided.", nameof(id));
|
||||
}
|
||||
|
||||
Session.Delete(id);
|
||||
|
||||
Session.SaveChanges();
|
||||
}
|
||||
|
||||
public async Task DeleteAsync(string id)
|
||||
{
|
||||
if (id == null) {
|
||||
throw new ArgumentException("Id must be provided.", nameof(id));
|
||||
}
|
||||
|
||||
AsyncSession.Delete(id);
|
||||
|
||||
await AsyncSession.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public void Delete<T>(T entity) where T : BaseEntity
|
||||
{
|
||||
if (entity == null) {
|
||||
throw new ArgumentException("Entity must be provided.", nameof(entity));
|
||||
}
|
||||
|
||||
Session.Delete(entity.Id);
|
||||
|
||||
Session.SaveChanges();
|
||||
}
|
||||
|
||||
public async Task DeleteAsync<T>(T entity) where T : BaseEntity
|
||||
{
|
||||
if (entity == null) {
|
||||
throw new ArgumentException("Entity must be provided.", nameof(entity));
|
||||
}
|
||||
|
||||
AsyncSession.Delete(entity.Id);
|
||||
|
||||
await AsyncSession.SaveChangesAsync();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Query
|
||||
|
||||
public IRavenQueryable<T> Query<T>() where T : BaseEntity
|
||||
{
|
||||
return Session.Query<T>();
|
||||
}
|
||||
|
||||
public IRavenQueryable<T> QueryAsync<T>() where T : BaseEntity
|
||||
{
|
||||
return AsyncSession.Query<T>();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Index
|
||||
|
||||
public IRavenQueryable<T> Index<T>(string indexName, bool isMapReduce = false) where T : BaseEntity
|
||||
{
|
||||
return Session.Query<T>(indexName, isMapReduce);
|
||||
}
|
||||
|
||||
public IRavenQueryable<T> IndexAsync<T>(string indexName, bool isMapReduce = false) where T : BaseEntity
|
||||
{
|
||||
return AsyncSession.Query<T>(indexName, isMapReduce);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Count
|
||||
|
||||
public int Count<T>() where T : BaseEntity
|
||||
{
|
||||
return Session.Query<T>().Count();
|
||||
}
|
||||
|
||||
public async Task<int> CountAsync<T>() where T : BaseEntity
|
||||
{
|
||||
return await AsyncSession.Query<T>().CountAsync();
|
||||
}
|
||||
|
||||
public int Count<T>(Expression<Func<T, bool>> predicate) where T : BaseEntity
|
||||
{
|
||||
return Session.Query<T>().Where(predicate).Count();
|
||||
}
|
||||
|
||||
public async Task<int> CountAsync<T>(Expression<Func<T, bool>> predicate) where T : BaseEntity
|
||||
{
|
||||
return await AsyncSession.Query<T>().Where(predicate).CountAsync();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IDisposable
|
||||
|
||||
private bool _disposed = false;
|
||||
|
||||
protected void Dispose(bool disposing)
|
||||
{
|
||||
if (!_disposed) {
|
||||
if (disposing) {
|
||||
_session = null;
|
||||
_asyncSession = null;
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,562 @@
|
||||
//// This file is part of Hangfire.
|
||||
//// Copyright © 2013-2014 Sergey Odinokov.
|
||||
////
|
||||
//// Hangfire is free software: you can redistribute it and/or modify
|
||||
//// it under the terms of the GNU Lesser General Public License as
|
||||
//// published by the Free Software Foundation, either version 3
|
||||
//// of the License, or any later version.
|
||||
////
|
||||
//// Hangfire is distributed in the hope that it will be useful,
|
||||
//// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//// GNU Lesser General Public License for more details.
|
||||
////
|
||||
//// You should have received a copy of the GNU Lesser General Public
|
||||
//// License along with Hangfire. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//using System;
|
||||
//using System.Collections.Generic;
|
||||
//using System.Data.SqlClient;
|
||||
//using System.Linq;
|
||||
//using System.Transactions;
|
||||
//using Dapper;
|
||||
//using Hangfire.Annotations;
|
||||
//using Hangfire.Common;
|
||||
//using Hangfire.SqlServer.Entities;
|
||||
//using Hangfire.States;
|
||||
//using Hangfire.Storage;
|
||||
//using Hangfire.Storage.Monitoring;
|
||||
|
||||
//namespace Hangfire.SqlServer
|
||||
//{
|
||||
// internal class SqlServerMonitoringApi : IMonitoringApi
|
||||
// {
|
||||
// private readonly RavenStorage _storage;
|
||||
// private readonly int? _jobListLimit;
|
||||
|
||||
// public SqlServerMonitoringApi([NotNull] RavenStorage storage, int? jobListLimit)
|
||||
// {
|
||||
// if (storage == null) throw new ArgumentNullException("storage");
|
||||
|
||||
// _storage = storage;
|
||||
// _jobListLimit = jobListLimit;
|
||||
// }
|
||||
|
||||
// public long ScheduledCount()
|
||||
// {
|
||||
// return UseConnection(connection =>
|
||||
// GetNumberOfJobsByStateName(connection, ScheduledState.StateName));
|
||||
// }
|
||||
|
||||
// public long EnqueuedCount(string queue)
|
||||
// {
|
||||
// var queueApi = GetQueueApi(queue);
|
||||
// var counters = queueApi.GetEnqueuedAndFetchedCount(queue);
|
||||
|
||||
// return counters.EnqueuedCount ?? 0;
|
||||
// }
|
||||
|
||||
// public long FetchedCount(string queue)
|
||||
// {
|
||||
// var queueApi = GetQueueApi(queue);
|
||||
// var counters = queueApi.GetEnqueuedAndFetchedCount(queue);
|
||||
|
||||
// return counters.FetchedCount ?? 0;
|
||||
// }
|
||||
|
||||
// public long FailedCount()
|
||||
// {
|
||||
// return UseConnection(connection =>
|
||||
// GetNumberOfJobsByStateName(connection, FailedState.StateName));
|
||||
// }
|
||||
|
||||
// public long ProcessingCount()
|
||||
// {
|
||||
// return UseConnection(connection =>
|
||||
// GetNumberOfJobsByStateName(connection, ProcessingState.StateName));
|
||||
// }
|
||||
|
||||
// public JobList<ProcessingJobDto> ProcessingJobs(int @from, int count)
|
||||
// {
|
||||
// return UseConnection(connection => GetJobs(
|
||||
// connection,
|
||||
// from, count,
|
||||
// ProcessingState.StateName,
|
||||
// (sqlJob, job, stateData) => new ProcessingJobDto
|
||||
// {
|
||||
// Job = job,
|
||||
// ServerId = stateData.ContainsKey("ServerId") ? stateData["ServerId"] : stateData["ServerName"],
|
||||
// StartedAt = JobHelper.DeserializeDateTime(stateData["StartedAt"]),
|
||||
// }));
|
||||
// }
|
||||
|
||||
// public JobList<ScheduledJobDto> ScheduledJobs(int @from, int count)
|
||||
// {
|
||||
// return UseConnection(connection => GetJobs(
|
||||
// connection,
|
||||
// from, count,
|
||||
// ScheduledState.StateName,
|
||||
// (sqlJob, job, stateData) => new ScheduledJobDto
|
||||
// {
|
||||
// Job = job,
|
||||
// EnqueueAt = JobHelper.DeserializeDateTime(stateData["EnqueueAt"]),
|
||||
// ScheduledAt = JobHelper.DeserializeDateTime(stateData["ScheduledAt"])
|
||||
// }));
|
||||
// }
|
||||
|
||||
// public IDictionary<DateTime, long> SucceededByDatesCount()
|
||||
// {
|
||||
// return UseConnection(connection =>
|
||||
// GetTimelineStats(connection, "succeeded"));
|
||||
// }
|
||||
|
||||
// public IDictionary<DateTime, long> FailedByDatesCount()
|
||||
// {
|
||||
// return UseConnection(connection =>
|
||||
// GetTimelineStats(connection, "failed"));
|
||||
// }
|
||||
|
||||
// public IList<ServerDto> Servers()
|
||||
// {
|
||||
// return UseConnection<IList<ServerDto>>(connection =>
|
||||
// {
|
||||
// var servers = connection.Query<Entities.Server>(
|
||||
// string.Format(@"select * from [{0}].Server", _storage.GetSchemaName()))
|
||||
// .ToList();
|
||||
|
||||
// var result = new List<ServerDto>();
|
||||
|
||||
// foreach (var server in servers)
|
||||
// {
|
||||
// var data = JobHelper.FromJson<ServerData>(server.Data);
|
||||
// result.Add(new ServerDto
|
||||
// {
|
||||
// Name = server.Id,
|
||||
// Heartbeat = server.LastHeartbeat,
|
||||
// Queues = data.Queues,
|
||||
// StartedAt = data.StartedAt.HasValue ? data.StartedAt.Value : DateTime.MinValue,
|
||||
// WorkersCount = data.WorkerCount
|
||||
// });
|
||||
// }
|
||||
|
||||
// return result;
|
||||
// });
|
||||
// }
|
||||
|
||||
// public JobList<FailedJobDto> FailedJobs(int @from, int count)
|
||||
// {
|
||||
// return UseConnection(connection => GetJobs(
|
||||
// connection,
|
||||
// from,
|
||||
// count,
|
||||
// FailedState.StateName,
|
||||
// (sqlJob, job, stateData) => new FailedJobDto
|
||||
// {
|
||||
// Job = job,
|
||||
// Reason = sqlJob.StateReason,
|
||||
// ExceptionDetails = stateData["ExceptionDetails"],
|
||||
// ExceptionMessage = stateData["ExceptionMessage"],
|
||||
// ExceptionType = stateData["ExceptionType"],
|
||||
// FailedAt = JobHelper.DeserializeNullableDateTime(stateData["FailedAt"])
|
||||
// }));
|
||||
// }
|
||||
|
||||
// public JobList<SucceededJobDto> SucceededJobs(int @from, int count)
|
||||
// {
|
||||
// return UseConnection(connection => GetJobs(
|
||||
// connection,
|
||||
// from,
|
||||
// count,
|
||||
// SucceededState.StateName,
|
||||
// (sqlJob, job, stateData) => new SucceededJobDto
|
||||
// {
|
||||
// Job = job,
|
||||
// Result = stateData.ContainsKey("Result") ? stateData["Result"] : null,
|
||||
// TotalDuration = stateData.ContainsKey("PerformanceDuration") && stateData.ContainsKey("Latency")
|
||||
// ? (long?)long.Parse(stateData["PerformanceDuration"]) + (long?)long.Parse(stateData["Latency"])
|
||||
// : null,
|
||||
// SucceededAt = JobHelper.DeserializeNullableDateTime(stateData["SucceededAt"])
|
||||
// }));
|
||||
// }
|
||||
|
||||
// public JobList<DeletedJobDto> DeletedJobs(int @from, int count)
|
||||
// {
|
||||
// return UseConnection(connection => GetJobs(
|
||||
// connection,
|
||||
// from,
|
||||
// count,
|
||||
// DeletedState.StateName,
|
||||
// (sqlJob, job, stateData) => new DeletedJobDto
|
||||
// {
|
||||
// Job = job,
|
||||
// DeletedAt = JobHelper.DeserializeNullableDateTime(stateData["DeletedAt"])
|
||||
// }));
|
||||
// }
|
||||
|
||||
// public IList<QueueWithTopEnqueuedJobsDto> Queues()
|
||||
// {
|
||||
// var tuples = _storage.QueueProviders
|
||||
// .Select(x => x.GetJobQueueMonitoringApi())
|
||||
// .SelectMany(x => x.GetQueues(), (monitoring, queue) => new { Monitoring = monitoring, Queue = queue })
|
||||
// .OrderBy(x => x.Queue)
|
||||
// .ToArray();
|
||||
|
||||
// var result = new List<QueueWithTopEnqueuedJobsDto>(tuples.Length);
|
||||
|
||||
// foreach (var tuple in tuples)
|
||||
// {
|
||||
// var enqueuedJobIds = tuple.Monitoring.GetEnqueuedJobIds(tuple.Queue, 0, 5);
|
||||
// var counters = tuple.Monitoring.GetEnqueuedAndFetchedCount(tuple.Queue);
|
||||
|
||||
// var firstJobs = UseConnection(connection => EnqueuedJobs(connection, enqueuedJobIds));
|
||||
|
||||
// result.Add(new QueueWithTopEnqueuedJobsDto
|
||||
// {
|
||||
// Name = tuple.Queue,
|
||||
// Length = counters.EnqueuedCount ?? 0,
|
||||
// Fetched = counters.FetchedCount,
|
||||
// FirstJobs = firstJobs
|
||||
// });
|
||||
// }
|
||||
|
||||
// return result;
|
||||
// }
|
||||
|
||||
// public JobList<EnqueuedJobDto> EnqueuedJobs(string queue, int @from, int perPage)
|
||||
// {
|
||||
// var queueApi = GetQueueApi(queue);
|
||||
// var enqueuedJobIds = queueApi.GetEnqueuedJobIds(queue, from, perPage);
|
||||
|
||||
// return UseConnection(connection => EnqueuedJobs(connection, enqueuedJobIds));
|
||||
// }
|
||||
|
||||
// public JobList<FetchedJobDto> FetchedJobs(string queue, int @from, int perPage)
|
||||
// {
|
||||
// var queueApi = GetQueueApi(queue);
|
||||
// var fetchedJobIds = queueApi.GetFetchedJobIds(queue, from, perPage);
|
||||
|
||||
// return UseConnection(connection => FetchedJobs(connection, fetchedJobIds));
|
||||
// }
|
||||
|
||||
// public IDictionary<DateTime, long> HourlySucceededJobs()
|
||||
// {
|
||||
// return UseConnection(connection =>
|
||||
// GetHourlyTimelineStats(connection, "succeeded"));
|
||||
// }
|
||||
|
||||
// public IDictionary<DateTime, long> HourlyFailedJobs()
|
||||
// {
|
||||
// return UseConnection(connection =>
|
||||
// GetHourlyTimelineStats(connection, "failed"));
|
||||
// }
|
||||
|
||||
// public JobDetailsDto JobDetails(string jobId)
|
||||
// {
|
||||
// return UseConnection(connection =>
|
||||
// {
|
||||
|
||||
// string sql = string.Format(@"
|
||||
//select * from [{0}].Job where Id = @id
|
||||
//select * from [{0}].JobParameter where JobId = @id
|
||||
//select * from [{0}].State where JobId = @id order by Id desc", _storage.GetSchemaName());
|
||||
|
||||
// using (var multi = connection.QueryMultiple(sql, new { id = jobId }))
|
||||
// {
|
||||
// var job = multi.Read<SqlJob>().SingleOrDefault();
|
||||
// if (job == null) return null;
|
||||
|
||||
// var parameters = multi.Read<JobParameter>().ToDictionary(x => x.Name, x => x.Value);
|
||||
// var history =
|
||||
// multi.Read<SqlState>()
|
||||
// .ToList()
|
||||
// .Select(x => new StateHistoryDto
|
||||
// {
|
||||
// StateName = x.Name,
|
||||
// CreatedAt = x.CreatedAt,
|
||||
// Reason = x.Reason,
|
||||
// Data = new Dictionary<string, string>(
|
||||
// JobHelper.FromJson<Dictionary<string, string>>(x.Data),
|
||||
// StringComparer.OrdinalIgnoreCase),
|
||||
// })
|
||||
// .ToList();
|
||||
|
||||
// return new JobDetailsDto
|
||||
// {
|
||||
// CreatedAt = job.CreatedAt,
|
||||
// ExpireAt = job.ExpireAt,
|
||||
// Job = DeserializeJob(job.InvocationData, job.Arguments),
|
||||
// History = history,
|
||||
// Properties = parameters
|
||||
// };
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
|
||||
// public long SucceededListCount()
|
||||
// {
|
||||
// return UseConnection(connection =>
|
||||
// GetNumberOfJobsByStateName(connection, SucceededState.StateName));
|
||||
// }
|
||||
|
||||
// public long DeletedListCount()
|
||||
// {
|
||||
// return UseConnection(connection =>
|
||||
// GetNumberOfJobsByStateName(connection, DeletedState.StateName));
|
||||
// }
|
||||
|
||||
// public StatisticsDto GetStatistics()
|
||||
// {
|
||||
// string sql = string.Format(@"
|
||||
//select count(Id) from [{0}].Job where StateName = N'Enqueued';
|
||||
//select count(Id) from [{0}].Job where StateName = N'Failed';
|
||||
//select count(Id) from [{0}].Job where StateName = N'Processing';
|
||||
//select count(Id) from [{0}].Job where StateName = N'Scheduled';
|
||||
//select count(Id) from [{0}].Server;
|
||||
//select sum(s.[Value]) from (
|
||||
// select sum([Value]) as [Value] from [{0}].Counter where [Key] = N'stats:succeeded'
|
||||
// union all
|
||||
// select [Value] from [{0}].AggregatedCounter where [Key] = N'stats:succeeded'
|
||||
//) as s;
|
||||
//select sum(s.[Value]) from (
|
||||
// select sum([Value]) as [Value] from [{0}].Counter where [Key] = N'stats:deleted'
|
||||
// union all
|
||||
// select [Value] from [{0}].AggregatedCounter where [Key] = N'stats:deleted'
|
||||
//) as s;
|
||||
//select count(*) from [{0}].[Set] where [Key] = N'recurring-jobs';
|
||||
//", _storage.GetSchemaName());
|
||||
|
||||
// var statistics = UseConnection(connection =>
|
||||
// {
|
||||
// var stats = new StatisticsDto();
|
||||
// using (var multi = connection.QueryMultiple(sql))
|
||||
// {
|
||||
// stats.Enqueued = multi.Read<int>().Single();
|
||||
// stats.Failed = multi.Read<int>().Single();
|
||||
// stats.Processing = multi.Read<int>().Single();
|
||||
// stats.Scheduled = multi.Read<int>().Single();
|
||||
|
||||
// stats.Servers = multi.Read<int>().Single();
|
||||
|
||||
// stats.Succeeded = multi.Read<long?>().SingleOrDefault() ?? 0;
|
||||
// stats.Deleted = multi.Read<long?>().SingleOrDefault() ?? 0;
|
||||
|
||||
// stats.Recurring = multi.Read<int>().Single();
|
||||
// }
|
||||
// return stats;
|
||||
// });
|
||||
|
||||
// statistics.Queues = _storage.QueueProviders
|
||||
// .SelectMany(x => x.GetJobQueueMonitoringApi().GetQueues())
|
||||
// .Count();
|
||||
|
||||
// return statistics;
|
||||
// }
|
||||
|
||||
// private Dictionary<DateTime, long> GetHourlyTimelineStats(
|
||||
// SqlConnection connection,
|
||||
// string type)
|
||||
// {
|
||||
// var endDate = DateTime.UtcNow;
|
||||
// var dates = new List<DateTime>();
|
||||
// for (var i = 0; i < 24; i++)
|
||||
// {
|
||||
// dates.Add(endDate);
|
||||
// endDate = endDate.AddHours(-1);
|
||||
// }
|
||||
|
||||
// var keyMaps = dates.ToDictionary(x => String.Format("stats:{0}:{1}", type, x.ToString("yyyy-MM-dd-HH")), x => x);
|
||||
|
||||
// return GetTimelineStats(connection, keyMaps);
|
||||
// }
|
||||
|
||||
// private Dictionary<DateTime, long> GetTimelineStats(
|
||||
// SqlConnection connection,
|
||||
// string type)
|
||||
// {
|
||||
// var endDate = DateTime.UtcNow.Date;
|
||||
// var dates = new List<DateTime>();
|
||||
// for (var i = 0; i < 7; i++)
|
||||
// {
|
||||
// dates.Add(endDate);
|
||||
// endDate = endDate.AddDays(-1);
|
||||
// }
|
||||
|
||||
// var keyMaps = dates.ToDictionary(x => String.Format("stats:{0}:{1}", type, x.ToString("yyyy-MM-dd")), x => x);
|
||||
|
||||
// return GetTimelineStats(connection, keyMaps);
|
||||
// }
|
||||
|
||||
// private Dictionary<DateTime, long> GetTimelineStats(SqlConnection connection,
|
||||
// IDictionary<string, DateTime> keyMaps)
|
||||
// {
|
||||
// string sqlQuery = string.Format(@"
|
||||
//select [Key], [Value] as [Count] from [{0}].AggregatedCounter
|
||||
//where [Key] in @keys", _storage.GetSchemaName());
|
||||
|
||||
// var valuesMap = connection.Query(
|
||||
// sqlQuery,
|
||||
// new { keys = keyMaps.Keys })
|
||||
// .ToDictionary(x => (string)x.Key, x => (long)x.Count);
|
||||
|
||||
// foreach (var key in keyMaps.Keys)
|
||||
// {
|
||||
// if (!valuesMap.ContainsKey(key)) valuesMap.Add(key, 0);
|
||||
// }
|
||||
|
||||
// var result = new Dictionary<DateTime, long>();
|
||||
// for (var i = 0; i < keyMaps.Count; i++)
|
||||
// {
|
||||
// var value = valuesMap[keyMaps.ElementAt(i).Key];
|
||||
// result.Add(keyMaps.ElementAt(i).Value, value);
|
||||
// }
|
||||
|
||||
// return result;
|
||||
// }
|
||||
|
||||
// private IPersistentJobQueueMonitoringApi GetQueueApi(string queueName)
|
||||
// {
|
||||
// var provider = _storage.QueueProviders.GetProvider(queueName);
|
||||
// var monitoringApi = provider.GetJobQueueMonitoringApi();
|
||||
|
||||
// return monitoringApi;
|
||||
// }
|
||||
|
||||
// private T UseConnection<T>(Func<SqlConnection, T> action)
|
||||
// {
|
||||
// return _storage.UseTransaction(action, IsolationLevel.ReadUncommitted);
|
||||
// }
|
||||
|
||||
// private JobList<EnqueuedJobDto> EnqueuedJobs(
|
||||
// SqlConnection connection,
|
||||
// IEnumerable<int> jobIds)
|
||||
// {
|
||||
// string enqueuedJobsSql = string.Format(@"
|
||||
//select j.*, s.Reason as StateReason, s.Data as StateData
|
||||
//from [{0}].Job j
|
||||
//left join [{0}].State s on s.Id = j.StateId
|
||||
//where j.Id in @jobIds", _storage.GetSchemaName());
|
||||
|
||||
// var jobs = connection.Query<Job>(
|
||||
// enqueuedJobsSql,
|
||||
// new { jobIds = jobIds })
|
||||
// .ToList();
|
||||
|
||||
// return DeserializeJobs(
|
||||
// jobs,
|
||||
// (sqlJob, job, stateData) => new EnqueuedJobDto
|
||||
// {
|
||||
// Job = job,
|
||||
// State = sqlJob.StateName,
|
||||
// EnqueuedAt = sqlJob.StateName == EnqueuedState.StateName
|
||||
// ? JobHelper.DeserializeNullableDateTime(stateData["EnqueuedAt"])
|
||||
// : null
|
||||
// });
|
||||
// }
|
||||
|
||||
// private long GetNumberOfJobsByStateName(SqlConnection connection, string stateName)
|
||||
// {
|
||||
// var sqlQuery = _jobListLimit.HasValue
|
||||
// ? string.Format(@"select count(j.Id) from (select top (@limit) Id from [{0}].Job where StateName = @state) as j", _storage.GetSchemaName())
|
||||
// : string.Format(@"select count(Id) from [{0}].Job where StateName = @state", _storage.GetSchemaName());
|
||||
|
||||
// var count = connection.Query<int>(
|
||||
// sqlQuery,
|
||||
// new { state = stateName, limit = _jobListLimit })
|
||||
// .Single();
|
||||
|
||||
// return count;
|
||||
// }
|
||||
|
||||
// private static Job DeserializeJob(string invocationData, string arguments)
|
||||
// {
|
||||
// var data = JobHelper.FromJson<InvocationData>(invocationData);
|
||||
// data.Arguments = arguments;
|
||||
|
||||
// try
|
||||
// {
|
||||
// return data.Deserialize();
|
||||
// }
|
||||
// catch (JobLoadException)
|
||||
// {
|
||||
// return null;
|
||||
// }
|
||||
// }
|
||||
|
||||
// private JobList<TDto> GetJobs<TDto>(
|
||||
// SqlConnection connection,
|
||||
// int from,
|
||||
// int count,
|
||||
// string stateName,
|
||||
// Func<Job, Job, Dictionary<string, string>, TDto> selector)
|
||||
// {
|
||||
// string jobsSql = string.Format(@"
|
||||
//select * from (
|
||||
// select j.*, s.Reason as StateReason, s.Data as StateData, row_number() over (order by j.Id desc) as row_num
|
||||
// from [{0}].Job j with (forceseek)
|
||||
// left join [{0}].State s on j.StateId = s.Id
|
||||
// where j.StateName = @stateName
|
||||
//) as j where j.row_num between @start and @end
|
||||
//", _storage.GetSchemaName());
|
||||
|
||||
// var jobs = connection.Query<Job>(
|
||||
// jobsSql,
|
||||
// new { stateName = stateName, start = @from + 1, end = @from + count })
|
||||
// .ToList();
|
||||
|
||||
// return DeserializeJobs(jobs, selector);
|
||||
// }
|
||||
|
||||
// private static JobList<TDto> DeserializeJobs<TDto>(
|
||||
// ICollection<Job> jobs,
|
||||
// Func<Job, Job, Dictionary<string, string>, TDto> selector)
|
||||
// {
|
||||
// var result = new List<KeyValuePair<string, TDto>>(jobs.Count);
|
||||
|
||||
// foreach (var job in jobs)
|
||||
// {
|
||||
// var deserializedData = JobHelper.FromJson<Dictionary<string, string>>(job.StateData);
|
||||
// var stateData = deserializedData != null
|
||||
// ? new Dictionary<string, string>(deserializedData, StringComparer.OrdinalIgnoreCase)
|
||||
// : null;
|
||||
|
||||
// var dto = selector(job, DeserializeJob(job.InvocationData, job.Arguments), stateData);
|
||||
|
||||
// result.Add(new KeyValuePair<string, TDto>(
|
||||
// job.Id.ToString(), dto));
|
||||
// }
|
||||
|
||||
// return new JobList<TDto>(result);
|
||||
// }
|
||||
|
||||
// private JobList<FetchedJobDto> FetchedJobs(
|
||||
// SqlConnection connection,
|
||||
// IEnumerable<int> jobIds)
|
||||
// {
|
||||
// string fetchedJobsSql = string.Format(@"
|
||||
//select j.*, s.Reason as StateReason, s.Data as StateData
|
||||
//from [{0}].Job j
|
||||
//left join [{0}].State s on s.Id = j.StateId
|
||||
//where j.Id in @jobIds", _storage.GetSchemaName());
|
||||
|
||||
// var jobs = connection.Query<Job>(
|
||||
// fetchedJobsSql,
|
||||
// new { jobIds = jobIds })
|
||||
// .ToList();
|
||||
|
||||
// var result = new List<KeyValuePair<string, FetchedJobDto>>(jobs.Count);
|
||||
|
||||
// foreach (var job in jobs)
|
||||
// {
|
||||
// result.Add(new KeyValuePair<string, FetchedJobDto>(
|
||||
// job.Id.ToString(),
|
||||
// new FetchedJobDto
|
||||
// {
|
||||
// Job = DeserializeJob(job.InvocationData, job.Arguments),
|
||||
// State = job.StateName,
|
||||
// }));
|
||||
// }
|
||||
|
||||
// return new JobList<FetchedJobDto>(result);
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
@@ -0,0 +1,24 @@
|
||||
using Hangfire.States;
|
||||
using Hangfire.Storage;
|
||||
|
||||
namespace Hangfire.Raven.StateHandlers
|
||||
{
|
||||
public class DeletedStateHandler : IStateHandler
|
||||
{
|
||||
public void Apply(ApplyStateContext context, IWriteOnlyTransaction transaction)
|
||||
{
|
||||
transaction.InsertToList("deleted", context.JobId);
|
||||
transaction.TrimList("deleted", 0, 99);
|
||||
}
|
||||
|
||||
public void Unapply(ApplyStateContext context, IWriteOnlyTransaction transaction)
|
||||
{
|
||||
transaction.RemoveFromList("deleted", context.JobId);
|
||||
}
|
||||
|
||||
public string StateName
|
||||
{
|
||||
get { return DeletedState.StateName; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using Hangfire.Common;
|
||||
using Hangfire.States;
|
||||
using Hangfire.Storage;
|
||||
using System;
|
||||
|
||||
namespace Hangfire.Raven.StateHandlers
|
||||
{
|
||||
public class FailedStateHandler : IStateHandler
|
||||
{
|
||||
public void Apply(ApplyStateContext context, IWriteOnlyTransaction transaction)
|
||||
{
|
||||
transaction.AddToSet("failed", context.JobId, JobHelper.ToTimestamp(DateTime.UtcNow));
|
||||
}
|
||||
|
||||
public void Unapply(ApplyStateContext context, IWriteOnlyTransaction transaction)
|
||||
{
|
||||
transaction.RemoveFromSet("failed", context.JobId);
|
||||
}
|
||||
|
||||
public string StateName
|
||||
{
|
||||
get { return FailedState.StateName; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using Hangfire.Common;
|
||||
using Hangfire.States;
|
||||
using Hangfire.Storage;
|
||||
using System;
|
||||
|
||||
namespace Hangfire.Raven.StateHandlers
|
||||
{
|
||||
public class ProcessingStateHandler : IStateHandler
|
||||
{
|
||||
public void Apply(ApplyStateContext context, IWriteOnlyTransaction transaction)
|
||||
{
|
||||
transaction.AddToSet("processing", context.JobId, JobHelper.ToTimestamp(DateTime.UtcNow));
|
||||
}
|
||||
|
||||
public void Unapply(ApplyStateContext context, IWriteOnlyTransaction transaction)
|
||||
{
|
||||
transaction.RemoveFromSet("processing", context.JobId);
|
||||
}
|
||||
|
||||
public string StateName
|
||||
{
|
||||
get { return ProcessingState.StateName; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using Hangfire.States;
|
||||
using Hangfire.Storage;
|
||||
|
||||
namespace Hangfire.Raven.StateHandlers
|
||||
{
|
||||
public class SucceededStateHandler : IStateHandler
|
||||
{
|
||||
public void Apply(ApplyStateContext context, IWriteOnlyTransaction transaction)
|
||||
{
|
||||
transaction.InsertToList("succeeded", context.JobId);
|
||||
transaction.TrimList("succeeded", 0, 99);
|
||||
}
|
||||
|
||||
public void Unapply(ApplyStateContext context, IWriteOnlyTransaction transaction)
|
||||
{
|
||||
transaction.RemoveFromList("succeeded", context.JobId);
|
||||
}
|
||||
|
||||
public string StateName
|
||||
{
|
||||
get { return SucceededState.StateName; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
// This file is part of Hangfire.
|
||||
// Copyright © 2013-2014 Sergey Odinokov.
|
||||
//
|
||||
// Hangfire is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as
|
||||
// published by the Free Software Foundation, either version 3
|
||||
// of the License, or any later version.
|
||||
//
|
||||
// Hangfire is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with Hangfire. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Hangfire.Logging;
|
||||
using Hangfire.Server;
|
||||
using Hangfire.Storage;
|
||||
using Raven.Client.Document;
|
||||
using Hangfire.Raven.JobQueues;
|
||||
|
||||
namespace Hangfire.Raven.Storage
|
||||
{
|
||||
public class RavenStorage : JobStorage
|
||||
{
|
||||
private readonly RavenStorageOptions _options;
|
||||
|
||||
public RavenStorage()
|
||||
: this(new RavenStorageOptions())
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes RavenStorage from the provided SqlServerStorageOptions and either the provided connection
|
||||
/// string or the connection string with provided name pulled from the application config file.
|
||||
/// </summary>
|
||||
/// <param name="connectionString"></param>
|
||||
/// <param name="options"></param>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="connectionString"/> argument is null.</exception>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="options"/> argument is null.</exception>
|
||||
/// <exception cref="ArgumentException"><paramref name="connectionString"/> argument is neither
|
||||
/// a valid Raven connection string nor the name of a connection string in the application
|
||||
/// config file.</exception>
|
||||
public RavenStorage(RavenStorageOptions options)
|
||||
{
|
||||
options.ThrowIfNull("options");
|
||||
|
||||
_options = options;
|
||||
|
||||
InitializeQueueProviders();
|
||||
}
|
||||
|
||||
public virtual PersistentJobQueueProviderCollection QueueProviders { get; private set; }
|
||||
|
||||
public override IMonitoringApi GetMonitoringApi()
|
||||
{
|
||||
//return new SqlServerMonitoringApi(this, _options.DashboardJobListLimit);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public override IStorageConnection GetConnection()
|
||||
{
|
||||
return new RavenConnection(this);
|
||||
}
|
||||
|
||||
public override IEnumerable<IServerComponent> GetComponents()
|
||||
{
|
||||
yield return new ExpirationManager(_options.JobExpirationCheckInterval);
|
||||
}
|
||||
|
||||
public override void WriteOptionsToLog(ILog logger)
|
||||
{
|
||||
logger.Info("Using the following options for Raven job storage:");
|
||||
logger.InfoFormat("Queue poll interval: {0}.", _options.QueuePollInterval);
|
||||
}
|
||||
|
||||
private void InitializeQueueProviders()
|
||||
{
|
||||
var defaultQueueProvider = new RavenJobQueueProvider(this, _options);
|
||||
QueueProviders = new PersistentJobQueueProviderCollection(defaultQueueProvider);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
// This file is part of Hangfire.
|
||||
// Copyright © 2015 Sergey Odinokov.
|
||||
//
|
||||
// Hangfire is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as
|
||||
// published by the Free Software Foundation, either version 3
|
||||
// of the License, or any later version.
|
||||
//
|
||||
// Hangfire is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with Hangfire. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
using System;
|
||||
using Hangfire;
|
||||
using Hangfire.Raven.Storage;
|
||||
using Hangfire.Raven;
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace HangFire.Raven.Storage
|
||||
{
|
||||
public static class SqlServerStorageExtensions
|
||||
{
|
||||
public static IGlobalConfiguration<RavenStorage> UseRavenStorage(this IGlobalConfiguration configuration, string connectionUrl, string database)
|
||||
{
|
||||
configuration.ThrowIfNull("configuration");
|
||||
connectionUrl.ThrowIfNull("connectionUrl");
|
||||
database.ThrowIfNull("database");
|
||||
|
||||
if (!connectionUrl.StartsWith("http")) {
|
||||
throw new ArgumentException("Connection Url must begin with http or https!");
|
||||
}
|
||||
|
||||
Repository.ConnectionUrl = connectionUrl;
|
||||
Repository.DefaultDatabase = database;
|
||||
|
||||
var storage = new RavenStorage();
|
||||
|
||||
return configuration.UseStorage(storage);
|
||||
}
|
||||
|
||||
public static IGlobalConfiguration<RavenStorage> UseRavenStorage(this IGlobalConfiguration configuration, string connectionUrl, string database, RavenStorageOptions options)
|
||||
{
|
||||
configuration.ThrowIfNull("configuration");
|
||||
connectionUrl.ThrowIfNull("connectionUrl");
|
||||
database.ThrowIfNull("database");
|
||||
options.ThrowIfNull("options");
|
||||
|
||||
if (!connectionUrl.StartsWith("http")) {
|
||||
throw new ArgumentException("Connection Url must begin with http or https!");
|
||||
}
|
||||
|
||||
Repository.ConnectionUrl = connectionUrl;
|
||||
Repository.DefaultDatabase = database;
|
||||
|
||||
var storage = new RavenStorage(options);
|
||||
|
||||
return configuration.UseStorage(storage);
|
||||
}
|
||||
|
||||
public static IGlobalConfiguration<RavenStorage> UseEmbeddedRavenStorage(this IGlobalConfiguration configuration)
|
||||
{
|
||||
configuration.ThrowIfNull("configuration");
|
||||
|
||||
Repository.Embedded = true;
|
||||
|
||||
var storage = new RavenStorage();
|
||||
|
||||
return configuration.UseStorage(storage);
|
||||
}
|
||||
|
||||
public static IGlobalConfiguration<RavenStorage> UseEmbeddedRavenStorage(this IGlobalConfiguration configuration, RavenStorageOptions options)
|
||||
{
|
||||
configuration.ThrowIfNull("configuration");
|
||||
options.ThrowIfNull("options");
|
||||
|
||||
Repository.Embedded = true;
|
||||
|
||||
var storage = new RavenStorage(options);
|
||||
|
||||
return configuration.UseStorage(storage);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
// This file is part of Hangfire.
|
||||
// Copyright © 2013-2014 Sergey Odinokov.
|
||||
//
|
||||
// Hangfire is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as
|
||||
// published by the Free Software Foundation, either version 3
|
||||
// of the License, or any later version.
|
||||
//
|
||||
// Hangfire is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with Hangfire. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Hangfire.Raven.Storage
|
||||
{
|
||||
public class RavenStorageOptions
|
||||
{
|
||||
private readonly string _clientId = null;
|
||||
|
||||
private TimeSpan _queuePollInterval;
|
||||
|
||||
public RavenStorageOptions()
|
||||
{
|
||||
QueuePollInterval = TimeSpan.FromSeconds(15);
|
||||
InvisibilityTimeout = TimeSpan.FromMinutes(30);
|
||||
JobExpirationCheckInterval = TimeSpan.FromHours(1);
|
||||
CountersAggregateInterval = TimeSpan.FromMinutes(5);
|
||||
PrepareSchemaIfNecessary = true;
|
||||
DashboardJobListLimit = 50000;
|
||||
TransactionTimeout = TimeSpan.FromMinutes(1);
|
||||
DistributedLockLifetime = TimeSpan.FromSeconds(30);
|
||||
|
||||
_clientId = Guid.NewGuid().ToString().Replace("-", String.Empty);
|
||||
}
|
||||
|
||||
public TimeSpan QueuePollInterval
|
||||
{
|
||||
get { return _queuePollInterval; }
|
||||
set
|
||||
{
|
||||
var message = string.Format(
|
||||
"The QueuePollInterval property value should be positive. Given: {0}.",
|
||||
value);
|
||||
|
||||
if (value == TimeSpan.Zero)
|
||||
{
|
||||
throw new ArgumentException(message, "value");
|
||||
}
|
||||
if (value != value.Duration())
|
||||
{
|
||||
throw new ArgumentException(message, "value");
|
||||
}
|
||||
|
||||
_queuePollInterval = value;
|
||||
}
|
||||
}
|
||||
|
||||
[Obsolete("Does not make sense anymore. Background jobs re-queued instantly even after ungraceful shutdown now. Will be removed in 2.0.0.")]
|
||||
public TimeSpan InvisibilityTimeout { get; set; }
|
||||
|
||||
public bool PrepareSchemaIfNecessary { get; set; }
|
||||
|
||||
public TimeSpan JobExpirationCheckInterval { get; set; }
|
||||
public TimeSpan CountersAggregateInterval { get; set; }
|
||||
|
||||
public int? DashboardJobListLimit { get; set; }
|
||||
public TimeSpan TransactionTimeout { get; set; }
|
||||
public TimeSpan DistributedLockLifetime { get; set; }
|
||||
|
||||
public string ClientId
|
||||
{
|
||||
get { return _clientId; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<runtime>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-7.0.0.0" newVersion="7.0.0.0" />
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
</runtime>
|
||||
</configuration>
|
||||
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Dewey.Net" version="2.7.8.0" targetFramework="net45" />
|
||||
<package id="Hangfire" version="1.5.0" targetFramework="net45" />
|
||||
<package id="Hangfire.Core" version="1.5.0" targetFramework="net45" />
|
||||
<package id="Hangfire.SqlServer" version="1.5.0" targetFramework="net45" />
|
||||
<package id="Microsoft.Owin" version="3.0.0" targetFramework="net45" />
|
||||
<package id="Microsoft.Owin.Host.SystemWeb" version="3.0.0" targetFramework="net45" />
|
||||
<package id="Newtonsoft.Json" version="7.0.1" targetFramework="net45" />
|
||||
<package id="Owin" version="1.0" targetFramework="net45" />
|
||||
<package id="RavenDB.Client" version="3.0.3800" targetFramework="net45" />
|
||||
<package id="RavenDB.Database" version="3.0.3800" targetFramework="net45" />
|
||||
<package id="RavenDB.Embedded" version="3.0.3800" targetFramework="net45" />
|
||||
</packages>
|
||||
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
[assembly: AssemblyProduct("Hangfire")]
|
||||
[assembly: AssemblyCompany("Sergey Odinokov")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2013-2015 Sergey Odinokov")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
[assembly: ComVisible(false)]
|
||||
[assembly: CLSCompliant(false)]
|
||||
|
||||
// Don't edit manually! Use `build.bat version` command instead!
|
||||
[assembly: AssemblyVersion("1.5.0")]
|
||||
@@ -0,0 +1,230 @@
|
||||
using Hangfire.Mongo.Database;
|
||||
using Hangfire.Mongo.Dto;
|
||||
using Hangfire.Mongo.MongoUtils;
|
||||
using Hangfire.Mongo.Tests.Utils;
|
||||
using MongoDB.Bson;
|
||||
using MongoDB.Bson.Serialization;
|
||||
using MongoDB.Driver;
|
||||
using MongoDB.Driver.Builders;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using Xunit;
|
||||
|
||||
namespace Hangfire.Mongo.Tests
|
||||
{
|
||||
public class ExpirationManagerFacts
|
||||
{
|
||||
private readonly MongoStorage _storage;
|
||||
|
||||
private readonly CancellationToken _token;
|
||||
|
||||
public ExpirationManagerFacts()
|
||||
{
|
||||
_storage = new MongoStorage(ConnectionUtils.GetConnectionString(), ConnectionUtils.GetDatabaseName());
|
||||
_token = new CancellationToken(true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Ctor_ThrowsAnException_WhenStorageIsNull()
|
||||
{
|
||||
Assert.Throws<ArgumentNullException>(() => new ExpirationManager(null));
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void Execute_RemovesOutdatedRecords()
|
||||
{
|
||||
using (var connection = ConnectionUtils.CreateConnection())
|
||||
{
|
||||
var entryId = CreateExpirationEntry(connection, connection.GetServerTimeUtc().AddMonths(-1));
|
||||
var manager = CreateManager();
|
||||
|
||||
manager.Execute(_token);
|
||||
|
||||
Assert.True(IsEntryExpired(connection, entryId));
|
||||
}
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void Execute_DoesNotRemoveEntries_WithNoExpirationTimeSet()
|
||||
{
|
||||
using (var connection = ConnectionUtils.CreateConnection())
|
||||
{
|
||||
var entryId = CreateExpirationEntry(connection, null);
|
||||
var manager = CreateManager();
|
||||
|
||||
manager.Execute(_token);
|
||||
|
||||
Assert.False(IsEntryExpired(connection, entryId));
|
||||
}
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void Execute_DoesNotRemoveEntries_WithFreshExpirationTime()
|
||||
{
|
||||
using (var connection = ConnectionUtils.CreateConnection())
|
||||
{
|
||||
var entryId = CreateExpirationEntry(connection, DateTime.Now.AddMonths(1));
|
||||
var manager = CreateManager();
|
||||
|
||||
manager.Execute(_token);
|
||||
|
||||
Assert.False(IsEntryExpired(connection, entryId));
|
||||
}
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void Execute_Processes_CounterTable()
|
||||
{
|
||||
using (var connection = ConnectionUtils.CreateConnection())
|
||||
{
|
||||
// Arrange
|
||||
connection.Counter.Insert(new CounterDto
|
||||
{
|
||||
Id = ObjectId.GenerateNewId(),
|
||||
Key = "key",
|
||||
Value = 1,
|
||||
ExpireAt = connection.GetServerTimeUtc().AddMonths(-1)
|
||||
});
|
||||
|
||||
var manager = CreateManager();
|
||||
|
||||
// Act
|
||||
manager.Execute(_token);
|
||||
|
||||
// Assert
|
||||
var count = connection.Counter.Count();
|
||||
Assert.Equal(0, count);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void Execute_Processes_JobTable()
|
||||
{
|
||||
using (var connection = ConnectionUtils.CreateConnection())
|
||||
{
|
||||
// Arrange
|
||||
connection.Job.Insert(new JobDto
|
||||
{
|
||||
Id = 1,
|
||||
InvocationData = "",
|
||||
Arguments = "",
|
||||
CreatedAt = connection.GetServerTimeUtc(),
|
||||
ExpireAt = connection.GetServerTimeUtc().AddMonths(-1),
|
||||
});
|
||||
|
||||
var manager = CreateManager();
|
||||
|
||||
// Act
|
||||
manager.Execute(_token);
|
||||
|
||||
// Assert
|
||||
var count = connection.Job.Count();
|
||||
Assert.Equal(0, count);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void Execute_Processes_ListTable()
|
||||
{
|
||||
using (var connection = ConnectionUtils.CreateConnection())
|
||||
{
|
||||
// Arrange
|
||||
connection.List.Insert(new ListDto
|
||||
{
|
||||
Id = ObjectId.GenerateNewId(),
|
||||
Key = "key",
|
||||
ExpireAt = connection.GetServerTimeUtc().AddMonths(-1)
|
||||
});
|
||||
|
||||
var manager = CreateManager();
|
||||
|
||||
// Act
|
||||
manager.Execute(_token);
|
||||
|
||||
// Assert
|
||||
var count = connection.List.Count();
|
||||
Assert.Equal(0, count);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void Execute_Processes_SetTable()
|
||||
{
|
||||
using (var connection = ConnectionUtils.CreateConnection())
|
||||
{
|
||||
// Arrange
|
||||
connection.Set.Insert(new SetDto
|
||||
{
|
||||
Id = ObjectId.GenerateNewId(),
|
||||
Key = "key",
|
||||
Score = 0,
|
||||
Value = "",
|
||||
ExpireAt = connection.GetServerTimeUtc().AddMonths(-1)
|
||||
});
|
||||
|
||||
var manager = CreateManager();
|
||||
|
||||
// Act
|
||||
manager.Execute(_token);
|
||||
|
||||
// Assert
|
||||
var count = connection.Set.Count();
|
||||
Assert.Equal(0, count);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void Execute_Processes_HashTable()
|
||||
{
|
||||
using (var connection = ConnectionUtils.CreateConnection())
|
||||
{
|
||||
// Arrange
|
||||
connection.Hash.Insert(new HashDto
|
||||
{
|
||||
Id = ObjectId.GenerateNewId(),
|
||||
Key = "key",
|
||||
Field = "field",
|
||||
Value = "",
|
||||
ExpireAt = connection.GetServerTimeUtc().AddMonths(-1)
|
||||
});
|
||||
|
||||
var manager = CreateManager();
|
||||
|
||||
// Act
|
||||
manager.Execute(_token);
|
||||
|
||||
// Assert
|
||||
var count = connection.Hash.Count();
|
||||
Assert.Equal(0, count);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static ObjectId CreateExpirationEntry(HangfireDbContext connection, DateTime? expireAt)
|
||||
{
|
||||
var counter = new CounterDto
|
||||
{
|
||||
Id = ObjectId.GenerateNewId(),
|
||||
Key = "key",
|
||||
Value = 1,
|
||||
ExpireAt = expireAt
|
||||
};
|
||||
connection.Counter.Insert(counter);
|
||||
|
||||
var id = counter.Id;
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
private static bool IsEntryExpired(HangfireDbContext connection, ObjectId entryId)
|
||||
{
|
||||
var count = connection.Counter.Count(Query<CounterDto>.EQ(_ => _.Id, entryId));
|
||||
return count == 0;
|
||||
}
|
||||
|
||||
private ExpirationManager CreateManager()
|
||||
{
|
||||
return new ExpirationManager(_storage);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{237F15A4-26DB-4FBD-86AF-81E87AD5762A}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>Hangfire.Mongo.Tests</RootNamespace>
|
||||
<AssemblyName>Hangfire.Mongo.Tests</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.5.1</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\..\</SolutionDir>
|
||||
<RestorePackages>true</RestorePackages>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Hangfire.Core, Version=1.3.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\..\packages\Hangfire.Core.1.3.0\lib\net45\Hangfire.Core.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="MongoDB.Bson, Version=1.9.2.235, Culture=neutral, PublicKeyToken=f686731cfb9cc103, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\..\packages\mongocsharpdriver.1.9.2\lib\net35\MongoDB.Bson.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="MongoDB.Driver, Version=1.9.2.235, Culture=neutral, PublicKeyToken=f686731cfb9cc103, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\..\packages\mongocsharpdriver.1.9.2\lib\net35\MongoDB.Driver.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Moq">
|
||||
<HintPath>..\..\packages\Moq.4.2.1409.1722\lib\net40\Moq.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Newtonsoft.Json, Version=4.5.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Newtonsoft.Json.5.0.1\lib\net45\Newtonsoft.Json.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Owin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=f0ebd12fd5e55cc5, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\..\packages\Owin.1.0\lib\net40\Owin.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Xml" />
|
||||
<Reference Include="xunit">
|
||||
<HintPath>..\..\packages\xunit.1.9.2\lib\net20\xunit.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="ExpirationManagerFacts.cs" />
|
||||
<Compile Include="MongoConnectionFacts.cs" />
|
||||
<Compile Include="MongoDistributedLockFacts.cs" />
|
||||
<Compile Include="MongoFetchedJobFacts.cs" />
|
||||
<Compile Include="MongoJobQueueFacts.cs" />
|
||||
<Compile Include="MongoStorageFacts.cs" />
|
||||
<Compile Include="MongoStorageOptionsFacts.cs" />
|
||||
<Compile Include="MongoWriteOnlyTransactionFacts.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Utils\CleanDatabaseAttribute.cs" />
|
||||
<Compile Include="Utils\ConnectionUtils.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="packages.config">
|
||||
<SubType>Designer</SubType>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Hangfire.Mongo\Hangfire.Mongo.csproj">
|
||||
<Project>{1fe9c86e-db7a-4c1b-b73a-42b993d68688}</Project>
|
||||
<Name>Hangfire.Mongo</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
</Target>
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
</Project>
|
||||
@@ -0,0 +1,869 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using Hangfire.Common;
|
||||
using Hangfire.Mongo.Database;
|
||||
using Hangfire.Mongo.Dto;
|
||||
using Hangfire.Mongo.MongoUtils;
|
||||
using Hangfire.Mongo.PersistentJobQueue;
|
||||
using Hangfire.Mongo.Tests.Utils;
|
||||
using Hangfire.Server;
|
||||
using Hangfire.Storage;
|
||||
using MongoDB.Bson;
|
||||
using MongoDB.Driver.Builders;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Hangfire.Mongo.Tests
|
||||
{
|
||||
public class MongoConnectionFacts
|
||||
{
|
||||
private readonly Mock<IPersistentJobQueue> _queue;
|
||||
private readonly Mock<IPersistentJobQueueProvider> _provider;
|
||||
private readonly PersistentJobQueueProviderCollection _providers;
|
||||
|
||||
public MongoConnectionFacts()
|
||||
{
|
||||
_queue = new Mock<IPersistentJobQueue>();
|
||||
|
||||
_provider = new Mock<IPersistentJobQueueProvider>();
|
||||
_provider.Setup(x => x.GetJobQueue(It.IsNotNull<HangfireDbContext>())).Returns(_queue.Object);
|
||||
|
||||
_providers = new PersistentJobQueueProviderCollection(_provider.Object);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Ctor_ThrowsAnException_WhenConnectionIsNull()
|
||||
{
|
||||
var exception = Assert.Throws<ArgumentNullException>(
|
||||
() => new MongoConnection(null, _providers));
|
||||
|
||||
Assert.Equal("database", exception.ParamName);
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void Ctor_ThrowsAnException_WhenProvidersCollectionIsNull()
|
||||
{
|
||||
var exception = Assert.Throws<ArgumentNullException>(
|
||||
() => new MongoConnection(ConnectionUtils.CreateConnection(), null));
|
||||
|
||||
Assert.Equal("queueProviders", exception.ParamName);
|
||||
}
|
||||
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void FetchNextJob_DelegatesItsExecution_ToTheQueue()
|
||||
{
|
||||
UseConnection((database, connection) =>
|
||||
{
|
||||
var token = new CancellationToken();
|
||||
var queues = new[] { "default" };
|
||||
|
||||
connection.FetchNextJob(queues, token);
|
||||
|
||||
_queue.Verify(x => x.Dequeue(queues, token));
|
||||
});
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void FetchNextJob_Throws_IfMultipleProvidersResolved()
|
||||
{
|
||||
UseConnection((database, connection) =>
|
||||
{
|
||||
var token = new CancellationToken();
|
||||
var anotherProvider = new Mock<IPersistentJobQueueProvider>();
|
||||
_providers.Add(anotherProvider.Object, new[] { "critical" });
|
||||
|
||||
Assert.Throws<InvalidOperationException>(
|
||||
() => connection.FetchNextJob(new[] { "critical", "default" }, token));
|
||||
});
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void CreateWriteTransaction_ReturnsNonNullInstance()
|
||||
{
|
||||
UseConnection((database, connection) =>
|
||||
{
|
||||
var transaction = connection.CreateWriteTransaction();
|
||||
Assert.NotNull(transaction);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void AcquireLock_ReturnsNonNullInstance()
|
||||
{
|
||||
UseConnection((database, connection) =>
|
||||
{
|
||||
var @lock = connection.AcquireDistributedLock("1", TimeSpan.FromSeconds(1));
|
||||
Assert.NotNull(@lock);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void CreateExpiredJob_ThrowsAnException_WhenJobIsNull()
|
||||
{
|
||||
UseConnection((database, connection) =>
|
||||
{
|
||||
var exception = Assert.Throws<ArgumentNullException>(
|
||||
() => connection.CreateExpiredJob(
|
||||
null,
|
||||
new Dictionary<string, string>(),
|
||||
database.GetServerTimeUtc(),
|
||||
TimeSpan.Zero));
|
||||
|
||||
Assert.Equal("job", exception.ParamName);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void CreateExpiredJob_ThrowsAnException_WhenParametersCollectionIsNull()
|
||||
{
|
||||
UseConnection((database, connection) =>
|
||||
{
|
||||
var exception = Assert.Throws<ArgumentNullException>(
|
||||
() => connection.CreateExpiredJob(
|
||||
Job.FromExpression(() => SampleMethod("hello")),
|
||||
null,
|
||||
database.GetServerTimeUtc(),
|
||||
TimeSpan.Zero));
|
||||
|
||||
Assert.Equal("parameters", exception.ParamName);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void CreateExpiredJob_CreatesAJobInTheStorage_AndSetsItsParameters()
|
||||
{
|
||||
UseConnection((database, connection) =>
|
||||
{
|
||||
var createdAt = new DateTime(2012, 12, 12, 0, 0, 0, 0, DateTimeKind.Utc);
|
||||
var jobId = connection.CreateExpiredJob(Job.FromExpression(() => SampleMethod("Hello")),
|
||||
new Dictionary<string, string> { { "Key1", "Value1" }, { "Key2", "Value2" } },
|
||||
createdAt,
|
||||
TimeSpan.FromDays(1));
|
||||
|
||||
Assert.NotNull(jobId);
|
||||
Assert.NotEmpty(jobId);
|
||||
|
||||
var databaseJob = database.Job.FindAll().Single();
|
||||
Assert.Equal(jobId, databaseJob.Id.ToString());
|
||||
Assert.Equal(createdAt, databaseJob.CreatedAt);
|
||||
Assert.Equal(ObjectId.Empty, databaseJob.StateId);
|
||||
Assert.Equal(null, databaseJob.StateName);
|
||||
|
||||
var invocationData = JobHelper.FromJson<InvocationData>((string)databaseJob.InvocationData);
|
||||
invocationData.Arguments = databaseJob.Arguments;
|
||||
|
||||
var job = invocationData.Deserialize();
|
||||
Assert.Equal(typeof(MongoConnectionFacts), job.Type);
|
||||
Assert.Equal("SampleMethod", job.Method.Name);
|
||||
Assert.Equal("\"Hello\"", job.Arguments[0]);
|
||||
|
||||
Assert.True(createdAt.AddDays(1).AddMinutes(-1) < databaseJob.ExpireAt);
|
||||
Assert.True(databaseJob.ExpireAt < createdAt.AddDays(1).AddMinutes(1));
|
||||
|
||||
var parameters = database.JobParameter.Find(Query<JobParameterDto>.EQ(_ => _.JobId, int.Parse(jobId)))
|
||||
.ToDictionary(x => x.Name, x => x.Value);
|
||||
|
||||
Assert.Equal("Value1", parameters["Key1"]);
|
||||
Assert.Equal("Value2", parameters["Key2"]);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void GetJobData_ThrowsAnException_WhenJobIdIsNull()
|
||||
{
|
||||
UseConnection((database, connection) => Assert.Throws<ArgumentNullException>(
|
||||
() => connection.GetJobData(null)));
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void GetJobData_ReturnsNull_WhenThereIsNoSuchJob()
|
||||
{
|
||||
UseConnection((database, connection) =>
|
||||
{
|
||||
var result = connection.GetJobData("547527b4c6b6cc26a02d021d");
|
||||
Assert.Null(result);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void GetJobData_ReturnsResult_WhenJobExists()
|
||||
{
|
||||
UseConnection((database, connection) =>
|
||||
{
|
||||
var job = Job.FromExpression(() => SampleMethod("wrong"));
|
||||
|
||||
var jobDto = new JobDto
|
||||
{
|
||||
Id = 1,
|
||||
InvocationData = JobHelper.ToJson(InvocationData.Serialize(job)),
|
||||
Arguments = "['Arguments']",
|
||||
StateName = "Succeeded",
|
||||
CreatedAt = database.GetServerTimeUtc()
|
||||
};
|
||||
database.Job.Insert(jobDto);
|
||||
|
||||
var result = connection.GetJobData(jobDto.Id.ToString());
|
||||
|
||||
Assert.NotNull(result);
|
||||
Assert.NotNull(result.Job);
|
||||
Assert.Equal("Succeeded", result.State);
|
||||
Assert.Equal("Arguments", result.Job.Arguments[0]);
|
||||
Assert.Null(result.LoadException);
|
||||
Assert.True(database.GetServerTimeUtc().AddMinutes(-1) < result.CreatedAt);
|
||||
Assert.True(result.CreatedAt < DateTime.UtcNow.AddMinutes(1));
|
||||
});
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void GetStateData_ThrowsAnException_WhenJobIdIsNull()
|
||||
{
|
||||
UseConnection(
|
||||
(database, connection) => Assert.Throws<ArgumentNullException>(
|
||||
() => connection.GetStateData(null)));
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void GetStateData_ReturnsNull_IfThereIsNoSuchState()
|
||||
{
|
||||
UseConnection((database, connection) =>
|
||||
{
|
||||
var result = connection.GetStateData("547527b4c6b6cc26a02d021d");
|
||||
Assert.Null(result);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void GetStateData_ReturnsCorrectData()
|
||||
{
|
||||
UseConnection((database, connection) =>
|
||||
{
|
||||
var data = new Dictionary<string, string>
|
||||
{
|
||||
{ "Key", "Value" }
|
||||
};
|
||||
|
||||
var jobDto = new JobDto
|
||||
{
|
||||
Id = 1,
|
||||
InvocationData = "",
|
||||
Arguments = "",
|
||||
StateName = "",
|
||||
CreatedAt = database.GetServerTimeUtc()
|
||||
};
|
||||
|
||||
database.Job.Insert(jobDto);
|
||||
var jobId = jobDto.Id;
|
||||
|
||||
database.State.Insert(new StateDto
|
||||
{
|
||||
Id = ObjectId.GenerateNewId(),
|
||||
JobId = jobId,
|
||||
Name = "old-state",
|
||||
CreatedAt = database.GetServerTimeUtc()
|
||||
});
|
||||
|
||||
var stateDto = new StateDto
|
||||
{
|
||||
Id = ObjectId.GenerateNewId(),
|
||||
JobId = jobId,
|
||||
Name = "Name",
|
||||
Reason = "Reason",
|
||||
Data = JobHelper.ToJson(data),
|
||||
CreatedAt = database.GetServerTimeUtc()
|
||||
};
|
||||
database.State.Insert(stateDto);
|
||||
|
||||
jobDto.StateId = stateDto.Id;
|
||||
database.Job.Save(jobDto);
|
||||
|
||||
var result = connection.GetStateData(jobId.ToString());
|
||||
Assert.NotNull(result);
|
||||
|
||||
Assert.Equal("Name", result.Name);
|
||||
Assert.Equal("Reason", result.Reason);
|
||||
Assert.Equal("Value", result.Data["Key"]);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void GetJobData_ReturnsJobLoadException_IfThereWasADeserializationException()
|
||||
{
|
||||
UseConnection((database, connection) =>
|
||||
{
|
||||
var jobDto = new JobDto
|
||||
{
|
||||
Id = 1,
|
||||
InvocationData = JobHelper.ToJson(new InvocationData(null, null, null, null)),
|
||||
Arguments = "['Arguments']",
|
||||
StateName = "Succeeded",
|
||||
CreatedAt = database.GetServerTimeUtc()
|
||||
};
|
||||
database.Job.Insert(jobDto);
|
||||
var jobId = jobDto.Id;
|
||||
|
||||
var result = connection.GetJobData(jobId.ToString());
|
||||
|
||||
Assert.NotNull(result.LoadException);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void SetParameter_ThrowsAnException_WhenJobIdIsNull()
|
||||
{
|
||||
UseConnection((database, connection) =>
|
||||
{
|
||||
var exception = Assert.Throws<ArgumentNullException>(
|
||||
() => connection.SetJobParameter(null, "name", "value"));
|
||||
|
||||
Assert.Equal("id", exception.ParamName);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void SetParameter_ThrowsAnException_WhenNameIsNull()
|
||||
{
|
||||
UseConnection((database, connection) =>
|
||||
{
|
||||
var exception = Assert.Throws<ArgumentNullException>(
|
||||
() => connection.SetJobParameter("547527b4c6b6cc26a02d021d", null, "value"));
|
||||
|
||||
Assert.Equal("name", exception.ParamName);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void SetParameters_CreatesNewParameter_WhenParameterWithTheGivenNameDoesNotExists()
|
||||
{
|
||||
UseConnection((database, connection) =>
|
||||
{
|
||||
var jobDto = new JobDto
|
||||
{
|
||||
Id = 1,
|
||||
InvocationData = "",
|
||||
Arguments = "",
|
||||
CreatedAt = database.GetServerTimeUtc()
|
||||
};
|
||||
database.Job.Insert(jobDto);
|
||||
string jobId = jobDto.Id.ToString();
|
||||
|
||||
connection.SetJobParameter(jobId, "Name", "Value");
|
||||
|
||||
var parameter = database.JobParameter.FindOne(Query.And(Query<JobParameterDto>.EQ(_ => _.JobId, int.Parse(jobId)),
|
||||
Query<JobParameterDto>.EQ(_ => _.Name, "Name")));
|
||||
|
||||
Assert.Equal("Value", parameter.Value);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void SetParameter_UpdatesValue_WhenParameterWithTheGivenName_AlreadyExists()
|
||||
{
|
||||
UseConnection((database, connection) =>
|
||||
{
|
||||
var jobDto = new JobDto
|
||||
{
|
||||
Id = 1,
|
||||
InvocationData = "",
|
||||
Arguments = "",
|
||||
CreatedAt = database.GetServerTimeUtc()
|
||||
};
|
||||
database.Job.Insert(jobDto);
|
||||
string jobId = jobDto.Id.ToString();
|
||||
|
||||
connection.SetJobParameter(jobId, "Name", "Value");
|
||||
connection.SetJobParameter(jobId, "Name", "AnotherValue");
|
||||
|
||||
var parameter = database.JobParameter.FindOne(Query.And(Query<JobParameterDto>.EQ(_ => _.JobId, int.Parse(jobId)),
|
||||
Query<JobParameterDto>.EQ(_ => _.Name, "Name")));
|
||||
|
||||
Assert.Equal("AnotherValue", parameter.Value);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void SetParameter_CanAcceptNulls_AsValues()
|
||||
{
|
||||
UseConnection((database, connection) =>
|
||||
{
|
||||
var jobDto = new JobDto
|
||||
{
|
||||
Id = 1,
|
||||
InvocationData = "",
|
||||
Arguments = "",
|
||||
CreatedAt = database.GetServerTimeUtc()
|
||||
};
|
||||
database.Job.Insert(jobDto);
|
||||
string jobId = jobDto.Id.ToString();
|
||||
|
||||
connection.SetJobParameter(jobId, "Name", null);
|
||||
|
||||
var parameter = database.JobParameter.FindOne(Query.And(Query<JobParameterDto>.EQ(_ => _.JobId, int.Parse(jobId)),
|
||||
Query<JobParameterDto>.EQ(_ => _.Name, "Name")));
|
||||
|
||||
Assert.Equal(null, parameter.Value);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void GetParameter_ThrowsAnException_WhenJobIdIsNull()
|
||||
{
|
||||
UseConnection((database, connection) =>
|
||||
{
|
||||
var exception = Assert.Throws<ArgumentNullException>(
|
||||
() => connection.GetJobParameter(null, "hello"));
|
||||
|
||||
Assert.Equal("id", exception.ParamName);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void GetParameter_ThrowsAnException_WhenNameIsNull()
|
||||
{
|
||||
UseConnection((database, connection) =>
|
||||
{
|
||||
var exception = Assert.Throws<ArgumentNullException>(
|
||||
() => connection.GetJobParameter("547527b4c6b6cc26a02d021d", null));
|
||||
|
||||
Assert.Equal("name", exception.ParamName);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void GetParameter_ReturnsNull_WhenParameterDoesNotExists()
|
||||
{
|
||||
UseConnection((database, connection) =>
|
||||
{
|
||||
var value = connection.GetJobParameter("1", "hello");
|
||||
Assert.Null(value);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void GetParameter_ReturnsParameterValue_WhenJobExists()
|
||||
{
|
||||
UseConnection((database, connection) =>
|
||||
{
|
||||
var jobDto = new JobDto
|
||||
{
|
||||
Id = 1,
|
||||
InvocationData = "",
|
||||
Arguments = "",
|
||||
CreatedAt = database.GetServerTimeUtc()
|
||||
};
|
||||
database.Job.Insert(jobDto);
|
||||
string jobId = jobDto.Id.ToString();
|
||||
|
||||
database.JobParameter.Insert(new JobParameterDto
|
||||
{
|
||||
Id = ObjectId.GenerateNewId(),
|
||||
JobId = int.Parse(jobId),
|
||||
Name = "name",
|
||||
Value = "value"
|
||||
});
|
||||
|
||||
var value = connection.GetJobParameter(jobId, "name");
|
||||
|
||||
Assert.Equal("value", value);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void GetFirstByLowestScoreFromSet_ThrowsAnException_WhenKeyIsNull()
|
||||
{
|
||||
UseConnection((database, connection) =>
|
||||
{
|
||||
var exception = Assert.Throws<ArgumentNullException>(
|
||||
() => connection.GetFirstByLowestScoreFromSet(null, 0, 1));
|
||||
|
||||
Assert.Equal("key", exception.ParamName);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void GetFirstByLowestScoreFromSet_ThrowsAnException_ToScoreIsLowerThanFromScore()
|
||||
{
|
||||
UseConnection((database, connection) => Assert.Throws<ArgumentException>(
|
||||
() => connection.GetFirstByLowestScoreFromSet("key", 0, -1)));
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void GetFirstByLowestScoreFromSet_ReturnsNull_WhenTheKeyDoesNotExist()
|
||||
{
|
||||
UseConnection((database, connection) =>
|
||||
{
|
||||
var result = connection.GetFirstByLowestScoreFromSet(
|
||||
"key", 0, 1);
|
||||
|
||||
Assert.Null(result);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void GetFirstByLowestScoreFromSet_ReturnsTheValueWithTheLowestScore()
|
||||
{
|
||||
UseConnection((database, connection) =>
|
||||
{
|
||||
database.Set.Insert(new SetDto
|
||||
{
|
||||
Id = ObjectId.GenerateNewId(),
|
||||
Key = "key",
|
||||
Score = 1.0,
|
||||
Value = "1.0"
|
||||
});
|
||||
database.Set.Insert(new SetDto
|
||||
{
|
||||
Id = ObjectId.GenerateNewId(),
|
||||
Key = "key",
|
||||
Score = -1.0,
|
||||
Value = "-1.0"
|
||||
});
|
||||
database.Set.Insert(new SetDto
|
||||
{
|
||||
Id = ObjectId.GenerateNewId(),
|
||||
Key = "key",
|
||||
Score = -5.0,
|
||||
Value = "-5.0"
|
||||
});
|
||||
database.Set.Insert(new SetDto
|
||||
{
|
||||
Id = ObjectId.GenerateNewId(),
|
||||
Key = "another-key",
|
||||
Score = -2.0,
|
||||
Value = "-2.0"
|
||||
});
|
||||
|
||||
var result = connection.GetFirstByLowestScoreFromSet("key", -1.0, 3.0);
|
||||
|
||||
Assert.Equal("-1.0", result);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void AnnounceServer_ThrowsAnException_WhenServerIdIsNull()
|
||||
{
|
||||
UseConnection((database, connection) =>
|
||||
{
|
||||
var exception = Assert.Throws<ArgumentNullException>(
|
||||
() => connection.AnnounceServer(null, new ServerContext()));
|
||||
|
||||
Assert.Equal("serverId", exception.ParamName);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void AnnounceServer_ThrowsAnException_WhenContextIsNull()
|
||||
{
|
||||
UseConnection((database, connection) =>
|
||||
{
|
||||
var exception = Assert.Throws<ArgumentNullException>(
|
||||
() => connection.AnnounceServer("server", null));
|
||||
|
||||
Assert.Equal("context", exception.ParamName);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void AnnounceServer_CreatesOrUpdatesARecord()
|
||||
{
|
||||
UseConnection((database, connection) =>
|
||||
{
|
||||
var context1 = new ServerContext
|
||||
{
|
||||
Queues = new[] { "critical", "default" },
|
||||
WorkerCount = 4
|
||||
};
|
||||
connection.AnnounceServer("server", context1);
|
||||
|
||||
var server = database.Server.FindAll().Single();
|
||||
Assert.Equal("server", server.Id);
|
||||
Assert.True(((string)server.Data).StartsWith(
|
||||
"{\"WorkerCount\":4,\"Queues\":[\"critical\",\"default\"],\"StartedAt\":"),
|
||||
server.Data);
|
||||
Assert.NotNull(server.LastHeartbeat);
|
||||
|
||||
var context2 = new ServerContext
|
||||
{
|
||||
Queues = new[] { "default" },
|
||||
WorkerCount = 1000
|
||||
};
|
||||
connection.AnnounceServer("server", context2);
|
||||
var sameServer = database.Server.FindAll().Single();
|
||||
Assert.Equal("server", sameServer.Id);
|
||||
Assert.Contains("1000", sameServer.Data);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void RemoveServer_ThrowsAnException_WhenServerIdIsNull()
|
||||
{
|
||||
UseConnection((database, connection) => Assert.Throws<ArgumentNullException>(
|
||||
() => connection.RemoveServer(null)));
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void RemoveServer_RemovesAServerRecord()
|
||||
{
|
||||
UseConnection((database, connection) =>
|
||||
{
|
||||
database.Server.Insert(new ServerDto
|
||||
{
|
||||
Id = "Server1",
|
||||
Data = "",
|
||||
LastHeartbeat = database.GetServerTimeUtc()
|
||||
});
|
||||
database.Server.Insert(new ServerDto
|
||||
{
|
||||
Id = "Server2",
|
||||
Data = "",
|
||||
LastHeartbeat = database.GetServerTimeUtc()
|
||||
});
|
||||
|
||||
connection.RemoveServer("Server1");
|
||||
|
||||
var server = database.Server.FindAll().Single();
|
||||
Assert.NotEqual("Server1", server.Id, StringComparer.OrdinalIgnoreCase);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void Heartbeat_ThrowsAnException_WhenServerIdIsNull()
|
||||
{
|
||||
UseConnection((database, connection) => Assert.Throws<ArgumentNullException>(
|
||||
() => connection.Heartbeat(null)));
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void Heartbeat_UpdatesLastHeartbeat_OfTheServerWithGivenId()
|
||||
{
|
||||
UseConnection((database, connection) =>
|
||||
{
|
||||
database.Server.Insert(new ServerDto
|
||||
{
|
||||
Id = "server1",
|
||||
Data = "",
|
||||
LastHeartbeat = new DateTime(2012, 12, 12, 12, 12, 12, DateTimeKind.Utc)
|
||||
});
|
||||
database.Server.Insert(new ServerDto
|
||||
{
|
||||
Id = "server2",
|
||||
Data = "",
|
||||
LastHeartbeat = new DateTime(2012, 12, 12, 12, 12, 12, DateTimeKind.Utc)
|
||||
});
|
||||
|
||||
connection.Heartbeat("server1");
|
||||
|
||||
var servers = database.Server.FindAll()
|
||||
.ToDictionary(x => x.Id, x => x.LastHeartbeat);
|
||||
|
||||
Assert.NotEqual(2012, servers["server1"].Year);
|
||||
Assert.Equal(2012, servers["server2"].Year);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void RemoveTimedOutServers_ThrowsAnException_WhenTimeOutIsNegative()
|
||||
{
|
||||
UseConnection((database, connection) => Assert.Throws<ArgumentException>(
|
||||
() => connection.RemoveTimedOutServers(TimeSpan.FromMinutes(-5))));
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void RemoveTimedOutServers_DoItsWorkPerfectly()
|
||||
{
|
||||
UseConnection((database, connection) =>
|
||||
{
|
||||
database.Server.Insert(new ServerDto
|
||||
{
|
||||
Id = "server1",
|
||||
Data = "",
|
||||
LastHeartbeat = database.GetServerTimeUtc().AddDays(-1)
|
||||
});
|
||||
database.Server.Insert(new ServerDto
|
||||
{
|
||||
Id = "server2",
|
||||
Data = "",
|
||||
LastHeartbeat = database.GetServerTimeUtc().AddHours(-12)
|
||||
});
|
||||
|
||||
connection.RemoveTimedOutServers(TimeSpan.FromHours(15));
|
||||
|
||||
var liveServer = database.Server.FindAll().Single();
|
||||
Assert.Equal("server2", liveServer.Id);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void GetAllItemsFromSet_ThrowsAnException_WhenKeyIsNull()
|
||||
{
|
||||
UseConnection((database, connection) =>
|
||||
Assert.Throws<ArgumentNullException>(() => connection.GetAllItemsFromSet(null)));
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void GetAllItemsFromSet_ReturnsEmptyCollection_WhenKeyDoesNotExist()
|
||||
{
|
||||
UseConnection((database, connection) =>
|
||||
{
|
||||
var result = connection.GetAllItemsFromSet("some-set");
|
||||
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal(0, result.Count);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void GetAllItemsFromSet_ReturnsAllItems()
|
||||
{
|
||||
UseConnection((database, connection) =>
|
||||
{
|
||||
// Arrange
|
||||
database.Set.Insert(new SetDto
|
||||
{
|
||||
Id = ObjectId.GenerateNewId(),
|
||||
Key = "some-set",
|
||||
Score = 0.0,
|
||||
Value = "1"
|
||||
});
|
||||
database.Set.Insert(new SetDto
|
||||
{
|
||||
Id = ObjectId.GenerateNewId(),
|
||||
Key = "some-set",
|
||||
Score = 0.0,
|
||||
Value = "2"
|
||||
});
|
||||
database.Set.Insert(new SetDto
|
||||
{
|
||||
Id = ObjectId.GenerateNewId(),
|
||||
Key = "another-set",
|
||||
Score = 0.0,
|
||||
Value = "3"
|
||||
});
|
||||
|
||||
// Act
|
||||
var result = connection.GetAllItemsFromSet("some-set");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(2, result.Count);
|
||||
Assert.Contains("1", result);
|
||||
Assert.Contains("2", result);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void SetRangeInHash_ThrowsAnException_WhenKeyIsNull()
|
||||
{
|
||||
UseConnection((database, connection) =>
|
||||
{
|
||||
var exception = Assert.Throws<ArgumentNullException>(
|
||||
() => connection.SetRangeInHash(null, new Dictionary<string, string>()));
|
||||
|
||||
Assert.Equal("key", exception.ParamName);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void SetRangeInHash_ThrowsAnException_WhenKeyValuePairsArgumentIsNull()
|
||||
{
|
||||
UseConnection((database, connection) =>
|
||||
{
|
||||
var exception = Assert.Throws<ArgumentNullException>(
|
||||
() => connection.SetRangeInHash("some-hash", null));
|
||||
|
||||
Assert.Equal("keyValuePairs", exception.ParamName);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void SetRangeInHash_MergesAllRecords()
|
||||
{
|
||||
UseConnection((database, connection) =>
|
||||
{
|
||||
connection.SetRangeInHash("some-hash", new Dictionary<string, string>
|
||||
{
|
||||
{ "Key1", "Value1" },
|
||||
{ "Key2", "Value2" }
|
||||
});
|
||||
|
||||
var result = database.Hash.Find(Query<HashDto>.EQ(_=>_.Key, "some-hash"))
|
||||
.ToDictionary(x => x.Field, x => x.Value);
|
||||
|
||||
Assert.Equal("Value1", result["Key1"]);
|
||||
Assert.Equal("Value2", result["Key2"]);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void GetAllEntriesFromHash_ThrowsAnException_WhenKeyIsNull()
|
||||
{
|
||||
UseConnection((database, connection) =>
|
||||
Assert.Throws<ArgumentNullException>(() => connection.GetAllEntriesFromHash(null)));
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void GetAllEntriesFromHash_ReturnsNull_IfHashDoesNotExist()
|
||||
{
|
||||
UseConnection((database, connection) =>
|
||||
{
|
||||
var result = connection.GetAllEntriesFromHash("some-hash");
|
||||
Assert.Null(result);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void GetAllEntriesFromHash_ReturnsAllKeysAndTheirValues()
|
||||
{
|
||||
UseConnection((database, connection) =>
|
||||
{
|
||||
// Arrange
|
||||
database.Hash.Insert(new HashDto
|
||||
{
|
||||
Id = ObjectId.GenerateNewId(),
|
||||
Key = "some-hash",
|
||||
Field = "Key1",
|
||||
Value = "Value1"
|
||||
});
|
||||
database.Hash.Insert(new HashDto
|
||||
{
|
||||
Id = ObjectId.GenerateNewId(),
|
||||
Key = "some-hash",
|
||||
Field = "Key2",
|
||||
Value = "Value2"
|
||||
});
|
||||
database.Hash.Insert(new HashDto
|
||||
{
|
||||
Id = ObjectId.GenerateNewId(),
|
||||
Key = "another-hash",
|
||||
Field = "Key3",
|
||||
Value = "Value3"
|
||||
});
|
||||
|
||||
// Act
|
||||
var result = connection.GetAllEntriesFromHash("some-hash");
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal(2, result.Count);
|
||||
Assert.Equal("Value1", result["Key1"]);
|
||||
Assert.Equal("Value2", result["Key2"]);
|
||||
});
|
||||
}
|
||||
|
||||
private void UseConnection(Action<HangfireDbContext, MongoConnection> action)
|
||||
{
|
||||
using (var database = ConnectionUtils.CreateConnection())
|
||||
{
|
||||
using (var connection = new MongoConnection(database, _providers))
|
||||
{
|
||||
action(database, connection);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void SampleMethod(string arg)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using Hangfire.Mongo.Database;
|
||||
using Hangfire.Mongo.Dto;
|
||||
using Hangfire.Mongo.MongoUtils;
|
||||
using Hangfire.Mongo.Tests.Utils;
|
||||
using MongoDB.Driver.Builders;
|
||||
using Xunit;
|
||||
using Hangfire.Mongo.DistributedLock;
|
||||
|
||||
namespace Hangfire.Mongo.Tests
|
||||
{
|
||||
public class MongoDistributedLockFacts
|
||||
{
|
||||
[Fact]
|
||||
public void Ctor_ThrowsAnException_WhenResourceIsNull()
|
||||
{
|
||||
UseConnection(database =>
|
||||
{
|
||||
var exception = Assert.Throws<ArgumentNullException>(
|
||||
() => new MongoDistributedLock(null, TimeSpan.FromSeconds(1), database, new MongoStorageOptions()));
|
||||
|
||||
Assert.Equal("resource", exception.ParamName);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Ctor_ThrowsAnException_WhenConnectionIsNull()
|
||||
{
|
||||
var exception = Assert.Throws<ArgumentNullException>(
|
||||
() => new MongoDistributedLock("resource1", TimeSpan.FromSeconds(1), null, new MongoStorageOptions()));
|
||||
|
||||
Assert.Equal("database", exception.ParamName);
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void Ctor_SetLock_WhenResourceIsNotLocked()
|
||||
{
|
||||
UseConnection(database =>
|
||||
{
|
||||
using (MongoDistributedLock @lock = new MongoDistributedLock("resource1", TimeSpan.FromSeconds(1), database, new MongoStorageOptions()))
|
||||
{
|
||||
var locksCount = database.DistributedLock.Count(Query<DistributedLockDto>.EQ(_ => _.Resource, "resource1"));
|
||||
Assert.Equal(1, locksCount);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void Ctor_SetReleaseLock_WhenResourceIsNotLocked()
|
||||
{
|
||||
UseConnection(database =>
|
||||
{
|
||||
long locksCount;
|
||||
using (MongoDistributedLock @lock = new MongoDistributedLock("resource1", TimeSpan.FromSeconds(1), database, new MongoStorageOptions()))
|
||||
{
|
||||
locksCount = database.DistributedLock.Count(Query<DistributedLockDto>.EQ(_ => _.Resource, "resource1"));
|
||||
Assert.Equal(1, locksCount);
|
||||
}
|
||||
|
||||
locksCount = database.DistributedLock.Count(Query<DistributedLockDto>.EQ(_ => _.Resource, "resource1"));
|
||||
Assert.Equal(0, locksCount);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void Ctor_ThrowsAnException_WhenResourceIsLocked()
|
||||
{
|
||||
UseConnection(database =>
|
||||
{
|
||||
using (MongoDistributedLock @lock1 = new MongoDistributedLock("resource1", TimeSpan.FromSeconds(1), database, new MongoStorageOptions()))
|
||||
{
|
||||
var locksCount = database.DistributedLock.Count(Query<DistributedLockDto>.EQ(_ => _.Resource, "resource1"));
|
||||
Assert.Equal(1, locksCount);
|
||||
|
||||
Assert.Throws<MongoDistributedLockException>(() => new MongoDistributedLock("resource1", TimeSpan.FromSeconds(1), database, new MongoStorageOptions()));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Ctor_ThrowsAnException_WhenOptionsIsNull()
|
||||
{
|
||||
UseConnection(database =>
|
||||
{
|
||||
var exception = Assert.Throws<ArgumentNullException>(
|
||||
() => new MongoDistributedLock("resource1", TimeSpan.FromSeconds(1), database, null));
|
||||
|
||||
Assert.Equal("options", exception.ParamName);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void Ctor_SetLockHeartbeatWorks_WhenResourceIsNotLocked()
|
||||
{
|
||||
UseConnection(database =>
|
||||
{
|
||||
using (MongoDistributedLock @lock = new MongoDistributedLock("resource1", TimeSpan.FromSeconds(1), database, new MongoStorageOptions() { DistributedLockLifetime = TimeSpan.FromSeconds(10)}))
|
||||
{
|
||||
DateTime initialHeartBeat = database.GetServerTimeUtc();
|
||||
Thread.Sleep(TimeSpan.FromSeconds(3));
|
||||
Thread.Sleep(TimeSpan.FromSeconds(60));
|
||||
|
||||
DistributedLockDto lockEntry = database.DistributedLock.FindOne(Query<DistributedLockDto>.EQ(_ => _.Resource, "resource1"));
|
||||
Assert.NotNull(lockEntry);
|
||||
Assert.True(lockEntry.Heartbeat > initialHeartBeat);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void UseConnection(Action<HangfireDbContext> action)
|
||||
{
|
||||
using (var connection = ConnectionUtils.CreateConnection())
|
||||
{
|
||||
action(connection);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
using System;
|
||||
using System.Data;
|
||||
using System.Linq;
|
||||
using Hangfire.Mongo.Database;
|
||||
using Hangfire.Mongo.Dto;
|
||||
using Hangfire.Mongo.MongoUtils;
|
||||
using Hangfire.Mongo.Tests.Utils;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Hangfire.Mongo.Tests
|
||||
{
|
||||
public class MongoFetchedJobFacts
|
||||
{
|
||||
private const string JobId = "id";
|
||||
private const string Queue = "queue";
|
||||
|
||||
|
||||
[Fact]
|
||||
public void Ctor_ThrowsAnException_WhenConnectionIsNull()
|
||||
{
|
||||
UseConnection(connection =>
|
||||
{
|
||||
var exception = Assert.Throws<ArgumentNullException>(
|
||||
() => new MongoFetchedJob(null, 1, JobId, Queue));
|
||||
|
||||
Assert.Equal("connection", exception.ParamName);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Ctor_ThrowsAnException_WhenJobIdIsNull()
|
||||
{
|
||||
UseConnection(connection =>
|
||||
{
|
||||
var exception = Assert.Throws<ArgumentNullException>(() => new MongoFetchedJob(connection, 1, null, Queue));
|
||||
|
||||
Assert.Equal("jobId", exception.ParamName);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Ctor_ThrowsAnException_WhenQueueIsNull()
|
||||
{
|
||||
UseConnection(connection =>
|
||||
{
|
||||
var exception = Assert.Throws<ArgumentNullException>(
|
||||
() => new MongoFetchedJob(connection, 1, JobId, null));
|
||||
|
||||
Assert.Equal("queue", exception.ParamName);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Ctor_CorrectlySets_AllInstanceProperties()
|
||||
{
|
||||
UseConnection(connection =>
|
||||
{
|
||||
var fetchedJob = new MongoFetchedJob(connection, 1, JobId, Queue);
|
||||
|
||||
Assert.Equal(1, fetchedJob.Id);
|
||||
Assert.Equal(JobId, fetchedJob.JobId);
|
||||
Assert.Equal(Queue, fetchedJob.Queue);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void RemoveFromQueue_ReallyDeletesTheJobFromTheQueue()
|
||||
{
|
||||
UseConnection(connection =>
|
||||
{
|
||||
// Arrange
|
||||
var id = CreateJobQueueRecord(connection, "1", "default");
|
||||
var processingJob = new MongoFetchedJob(connection, id, "1", "default");
|
||||
|
||||
// Act
|
||||
processingJob.RemoveFromQueue();
|
||||
|
||||
// Assert
|
||||
var count = connection.JobQueue.Count();
|
||||
Assert.Equal(0, count);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void RemoveFromQueue_DoesNotDelete_UnrelatedJobs()
|
||||
{
|
||||
UseConnection(connection =>
|
||||
{
|
||||
// Arrange
|
||||
CreateJobQueueRecord(connection, "1", "default");
|
||||
CreateJobQueueRecord(connection, "1", "critical");
|
||||
CreateJobQueueRecord(connection, "2", "default");
|
||||
|
||||
var fetchedJob = new MongoFetchedJob(connection, 999, "1", "default");
|
||||
|
||||
// Act
|
||||
fetchedJob.RemoveFromQueue();
|
||||
|
||||
// Assert
|
||||
var count = connection.JobQueue.Count();
|
||||
Assert.Equal(3, count);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void Requeue_SetsFetchedAtValueToNull()
|
||||
{
|
||||
UseConnection(connection =>
|
||||
{
|
||||
// Arrange
|
||||
var id = CreateJobQueueRecord(connection, "1", "default");
|
||||
var processingJob = new MongoFetchedJob(connection, id, "1", "default");
|
||||
|
||||
// Act
|
||||
processingJob.Requeue();
|
||||
|
||||
// Assert
|
||||
var record = connection.JobQueue.FindAll().Single();
|
||||
Assert.Null(record.FetchedAt);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void Dispose_SetsFetchedAtValueToNull_IfThereWereNoCallsToComplete()
|
||||
{
|
||||
UseConnection(sql =>
|
||||
{
|
||||
// Arrange
|
||||
var id = CreateJobQueueRecord(sql, "1", "default");
|
||||
var processingJob = new MongoFetchedJob(sql, id, "1", "default");
|
||||
|
||||
// Act
|
||||
processingJob.Dispose();
|
||||
|
||||
// Assert
|
||||
var record = sql.JobQueue.FindAll().Single();
|
||||
Assert.Null(record.FetchedAt);
|
||||
});
|
||||
}
|
||||
|
||||
private static int CreateJobQueueRecord(HangfireDbContext connection, string jobId, string queue)
|
||||
{
|
||||
var jobQueue = new JobQueueDto
|
||||
{
|
||||
JobId = int.Parse(jobId),
|
||||
Queue = queue,
|
||||
FetchedAt = connection.GetServerTimeUtc()
|
||||
};
|
||||
|
||||
connection.JobQueue.Insert(jobQueue);
|
||||
|
||||
return jobQueue.Id;
|
||||
}
|
||||
|
||||
private static void UseConnection(Action<HangfireDbContext> action)
|
||||
{
|
||||
using (var connection = ConnectionUtils.CreateConnection())
|
||||
{
|
||||
action(connection);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,344 @@
|
||||
using Hangfire.Mongo.Database;
|
||||
using Hangfire.Mongo.Dto;
|
||||
using Hangfire.Mongo.MongoUtils;
|
||||
using Hangfire.Mongo.PersistentJobQueue.Mongo;
|
||||
using Hangfire.Mongo.Tests.Utils;
|
||||
using MongoDB.Driver.Builders;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using Xunit;
|
||||
|
||||
namespace Hangfire.Mongo.Tests
|
||||
{
|
||||
public class MongoJobQueueFacts
|
||||
{
|
||||
private static readonly string[] DefaultQueues = { "default" };
|
||||
|
||||
[Fact]
|
||||
public void Ctor_ThrowsAnException_WhenConnectionIsNull()
|
||||
{
|
||||
var exception = Assert.Throws<ArgumentNullException>(
|
||||
() => new MongoJobQueue(null, new MongoStorageOptions()));
|
||||
|
||||
Assert.Equal("connection", exception.ParamName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Ctor_ThrowsAnException_WhenOptionsValueIsNull()
|
||||
{
|
||||
UseConnection(connection =>
|
||||
{
|
||||
var exception = Assert.Throws<ArgumentNullException>(
|
||||
() => new MongoJobQueue(connection, null));
|
||||
|
||||
Assert.Equal("options", exception.ParamName);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void Dequeue_ShouldThrowAnException_WhenQueuesCollectionIsNull()
|
||||
{
|
||||
UseConnection(connection =>
|
||||
{
|
||||
var queue = CreateJobQueue(connection);
|
||||
|
||||
var exception = Assert.Throws<ArgumentNullException>(
|
||||
() => queue.Dequeue(null, CreateTimingOutCancellationToken()));
|
||||
|
||||
Assert.Equal("queues", exception.ParamName);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void Dequeue_ShouldThrowAnException_WhenQueuesCollectionIsEmpty()
|
||||
{
|
||||
UseConnection(connection =>
|
||||
{
|
||||
var queue = CreateJobQueue(connection);
|
||||
|
||||
var exception = Assert.Throws<ArgumentException>(
|
||||
() => queue.Dequeue(new string[0], CreateTimingOutCancellationToken()));
|
||||
|
||||
Assert.Equal("queues", exception.ParamName);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Dequeue_ThrowsOperationCanceled_WhenCancellationTokenIsSetAtTheBeginning()
|
||||
{
|
||||
UseConnection(connection =>
|
||||
{
|
||||
var cts = new CancellationTokenSource();
|
||||
cts.Cancel();
|
||||
var queue = CreateJobQueue(connection);
|
||||
|
||||
Assert.Throws<OperationCanceledException>(() => queue.Dequeue(DefaultQueues, cts.Token));
|
||||
});
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void Dequeue_ShouldWaitIndefinitely_WhenThereAreNoJobs()
|
||||
{
|
||||
UseConnection(connection =>
|
||||
{
|
||||
var cts = new CancellationTokenSource(200);
|
||||
var queue = CreateJobQueue(connection);
|
||||
|
||||
Assert.Throws<OperationCanceledException>(() => queue.Dequeue(DefaultQueues, cts.Token));
|
||||
});
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void Dequeue_ShouldFetchAJob_FromTheSpecifiedQueue()
|
||||
{
|
||||
// Arrange
|
||||
UseConnection(connection =>
|
||||
{
|
||||
var jobQueue = new JobQueueDto
|
||||
{
|
||||
JobId = 1,
|
||||
Queue = "default"
|
||||
};
|
||||
connection.JobQueue.Insert(jobQueue);
|
||||
|
||||
var id = jobQueue.Id;
|
||||
var queue = CreateJobQueue(connection);
|
||||
|
||||
// Act
|
||||
MongoFetchedJob payload = (MongoFetchedJob)queue.Dequeue(DefaultQueues, CreateTimingOutCancellationToken());
|
||||
|
||||
// Assert
|
||||
Assert.Equal(id, payload.Id);
|
||||
Assert.Equal("1", payload.JobId);
|
||||
Assert.Equal("default", payload.Queue);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void Dequeue_ShouldLeaveJobInTheQueue_ButSetItsFetchedAtValue()
|
||||
{
|
||||
// Arrange
|
||||
UseConnection(connection =>
|
||||
{
|
||||
var job = new JobDto
|
||||
{
|
||||
InvocationData = "",
|
||||
Arguments = "",
|
||||
CreatedAt = connection.GetServerTimeUtc()
|
||||
};
|
||||
connection.Job.Insert(job);
|
||||
|
||||
var jobQueue = new JobQueueDto
|
||||
{
|
||||
JobId = job.Id,
|
||||
Queue = "default"
|
||||
};
|
||||
connection.JobQueue.Insert(jobQueue);
|
||||
|
||||
var queue = CreateJobQueue(connection);
|
||||
|
||||
// Act
|
||||
var payload = queue.Dequeue(DefaultQueues, CreateTimingOutCancellationToken());
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(payload);
|
||||
|
||||
var fetchedAt = connection.JobQueue.FindOne(Query<JobQueueDto>.EQ(_ => _.JobId, int.Parse(payload.JobId))).FetchedAt;
|
||||
|
||||
Assert.NotNull(fetchedAt);
|
||||
Assert.True(fetchedAt > DateTime.UtcNow.AddMinutes(-1));
|
||||
});
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void Dequeue_ShouldFetchATimedOutJobs_FromTheSpecifiedQueue()
|
||||
{
|
||||
// Arrange
|
||||
UseConnection(connection =>
|
||||
{
|
||||
var job = new JobDto
|
||||
{
|
||||
InvocationData = "",
|
||||
Arguments = "",
|
||||
CreatedAt = connection.GetServerTimeUtc()
|
||||
};
|
||||
connection.Job.Insert(job);
|
||||
|
||||
var jobQueue = new JobQueueDto
|
||||
{
|
||||
JobId = job.Id,
|
||||
Queue = "default",
|
||||
FetchedAt = connection.GetServerTimeUtc().AddDays(-1)
|
||||
};
|
||||
connection.JobQueue.Insert(jobQueue);
|
||||
|
||||
var queue = CreateJobQueue(connection);
|
||||
|
||||
// Act
|
||||
var payload = queue.Dequeue(DefaultQueues, CreateTimingOutCancellationToken());
|
||||
|
||||
// Assert
|
||||
Assert.NotEmpty(payload.JobId);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void Dequeue_ShouldSetFetchedAt_OnlyForTheFetchedJob()
|
||||
{
|
||||
// Arrange
|
||||
UseConnection(connection =>
|
||||
{
|
||||
var job1 = new JobDto
|
||||
{
|
||||
InvocationData = "",
|
||||
Arguments = "",
|
||||
CreatedAt = connection.GetServerTimeUtc()
|
||||
};
|
||||
connection.Job.Insert(job1);
|
||||
|
||||
var job2 = new JobDto
|
||||
{
|
||||
InvocationData = "",
|
||||
Arguments = "",
|
||||
CreatedAt = connection.GetServerTimeUtc()
|
||||
};
|
||||
connection.Job.Insert(job2);
|
||||
|
||||
connection.JobQueue.Insert(new JobQueueDto
|
||||
{
|
||||
JobId = job1.Id,
|
||||
Queue = "default"
|
||||
});
|
||||
|
||||
connection.JobQueue.Insert(new JobQueueDto
|
||||
{
|
||||
JobId = job2.Id,
|
||||
Queue = "default"
|
||||
});
|
||||
|
||||
var queue = CreateJobQueue(connection);
|
||||
|
||||
// Act
|
||||
var payload = queue.Dequeue(DefaultQueues, CreateTimingOutCancellationToken());
|
||||
|
||||
// Assert
|
||||
var otherJobFetchedAt = connection.JobQueue.FindOne(Query<JobQueueDto>.NE(_ => _.JobId, int.Parse(payload.JobId))).FetchedAt;
|
||||
|
||||
Assert.Null(otherJobFetchedAt);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void Dequeue_ShouldFetchJobs_OnlyFromSpecifiedQueues()
|
||||
{
|
||||
UseConnection(connection =>
|
||||
{
|
||||
var job1 = new JobDto
|
||||
{
|
||||
InvocationData = "",
|
||||
Arguments = "",
|
||||
CreatedAt = connection.GetServerTimeUtc()
|
||||
};
|
||||
connection.Job.Insert(job1);
|
||||
|
||||
connection.JobQueue.Insert(new JobQueueDto
|
||||
{
|
||||
JobId = job1.Id,
|
||||
Queue = "critical"
|
||||
});
|
||||
|
||||
|
||||
var queue = CreateJobQueue(connection);
|
||||
|
||||
Assert.Throws<OperationCanceledException>(() => queue.Dequeue(DefaultQueues, CreateTimingOutCancellationToken()));
|
||||
});
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void Dequeue_ShouldFetchJobs_FromMultipleQueues()
|
||||
{
|
||||
UseConnection(connection =>
|
||||
{
|
||||
var job1 = new JobDto
|
||||
{
|
||||
InvocationData = "",
|
||||
Arguments = "",
|
||||
CreatedAt = connection.GetServerTimeUtc()
|
||||
};
|
||||
connection.Job.Insert(job1);
|
||||
|
||||
var job2 = new JobDto
|
||||
{
|
||||
InvocationData = "",
|
||||
Arguments = "",
|
||||
CreatedAt = connection.GetServerTimeUtc()
|
||||
};
|
||||
connection.Job.Insert(job2);
|
||||
|
||||
connection.JobQueue.Insert(new JobQueueDto
|
||||
{
|
||||
JobId = job1.Id,
|
||||
Queue = "critical"
|
||||
});
|
||||
|
||||
connection.JobQueue.Insert(new JobQueueDto
|
||||
{
|
||||
JobId = job2.Id,
|
||||
Queue = "default"
|
||||
});
|
||||
|
||||
var queue = CreateJobQueue(connection);
|
||||
|
||||
var critical = (MongoFetchedJob)queue.Dequeue(
|
||||
new[] { "critical", "default" },
|
||||
CreateTimingOutCancellationToken());
|
||||
|
||||
Assert.NotNull(critical.JobId);
|
||||
Assert.Equal("critical", critical.Queue);
|
||||
|
||||
var @default = (MongoFetchedJob)queue.Dequeue(
|
||||
new[] { "critical", "default" },
|
||||
CreateTimingOutCancellationToken());
|
||||
|
||||
Assert.NotNull(@default.JobId);
|
||||
Assert.Equal("default", @default.Queue);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void Enqueue_AddsAJobToTheQueue()
|
||||
{
|
||||
UseConnection(connection =>
|
||||
{
|
||||
var queue = CreateJobQueue(connection);
|
||||
|
||||
queue.Enqueue("default", "1");
|
||||
|
||||
var record = connection.JobQueue.FindAll().Single();
|
||||
Assert.Equal("1", record.JobId.ToString());
|
||||
Assert.Equal("default", record.Queue);
|
||||
Assert.Null(record.FetchedAt);
|
||||
});
|
||||
}
|
||||
|
||||
private static CancellationToken CreateTimingOutCancellationToken()
|
||||
{
|
||||
var source = new CancellationTokenSource(TimeSpan.FromSeconds(10));
|
||||
return source.Token;
|
||||
}
|
||||
|
||||
private static MongoJobQueue CreateJobQueue(HangfireDbContext connection)
|
||||
{
|
||||
return new MongoJobQueue(connection, new MongoStorageOptions());
|
||||
}
|
||||
|
||||
private static void UseConnection(Action<HangfireDbContext> action)
|
||||
{
|
||||
using (var connection = ConnectionUtils.CreateConnection())
|
||||
{
|
||||
action(connection);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Hangfire.Mongo.Tests.Utils;
|
||||
using Hangfire.Server;
|
||||
using Hangfire.Storage;
|
||||
using Xunit;
|
||||
|
||||
namespace Hangfire.Mongo.Tests
|
||||
{
|
||||
public class MongoStorageFacts
|
||||
{
|
||||
[Fact]
|
||||
public void Ctor_ThrowsAnException_WhenConnectionStringIsNull()
|
||||
{
|
||||
var exception = Assert.Throws<ArgumentNullException>(() => new MongoStorage(null, "database"));
|
||||
|
||||
Assert.Equal("connectionString", exception.ParamName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Ctor_ThrowsAnException_WhenDatabaseNameIsNull()
|
||||
{
|
||||
var exception = Assert.Throws<ArgumentNullException>(() => new MongoStorage("localhost", null));
|
||||
|
||||
Assert.Equal("databaseName", exception.ParamName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Ctor_ThrowsAnException_WhenOptionsValueIsNull()
|
||||
{
|
||||
var exception = Assert.Throws<ArgumentNullException>(() => new MongoStorage("localhost", "database", null));
|
||||
|
||||
Assert.Equal("options", exception.ParamName);
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void GetMonitoringApi_ReturnsNonNullInstance()
|
||||
{
|
||||
MongoStorage storage = CreateStorage();
|
||||
IMonitoringApi api = storage.GetMonitoringApi();
|
||||
Assert.NotNull(api);
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void GetConnection_ReturnsNonNullInstance()
|
||||
{
|
||||
MongoStorage storage = CreateStorage();
|
||||
using (IStorageConnection connection = storage.GetConnection())
|
||||
{
|
||||
Assert.NotNull(connection);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetComponents_ReturnsAllNeededComponents()
|
||||
{
|
||||
MongoStorage storage = CreateStorage();
|
||||
|
||||
IEnumerable<IServerComponent> components = storage.GetComponents();
|
||||
|
||||
Type[] componentTypes = components.Select(x => x.GetType()).ToArray();
|
||||
Assert.Contains(typeof(ExpirationManager), componentTypes);
|
||||
}
|
||||
|
||||
private static MongoStorage CreateStorage()
|
||||
{
|
||||
return new MongoStorage(ConnectionUtils.GetConnectionString(), ConnectionUtils.GetDatabaseName());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.Remoting;
|
||||
using Xunit;
|
||||
|
||||
namespace Hangfire.Mongo.Tests
|
||||
{
|
||||
public class MongoStorageOptionsFacts
|
||||
{
|
||||
[Fact]
|
||||
public void Ctor_SetsTheDefaultOptions()
|
||||
{
|
||||
MongoStorageOptions options = new MongoStorageOptions();
|
||||
|
||||
Assert.Equal("hangfire", options.Prefix);
|
||||
Assert.True(options.InvisibilityTimeout > TimeSpan.Zero);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Ctor_SetsTheDefaultOptions_ShouldGenerateClientId()
|
||||
{
|
||||
var options = new MongoStorageOptions();
|
||||
Assert.False(String.IsNullOrWhiteSpace(options.ClientId));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Ctor_SetsTheDefaultOptions_ShouldGenerateUniqueClientId()
|
||||
{
|
||||
var options1 = new MongoStorageOptions();
|
||||
var options2 = new MongoStorageOptions();
|
||||
var options3 = new MongoStorageOptions();
|
||||
|
||||
IEnumerable<string> result = new[] {options1.ClientId, options2.ClientId, options3.ClientId}.Distinct();
|
||||
|
||||
Assert.Equal(3, result.Count());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Set_QueuePollInterval_ShouldThrowAnException_WhenGivenIntervalIsEqualToZero()
|
||||
{
|
||||
var options = new MongoStorageOptions();
|
||||
Assert.Throws<ArgumentException>(
|
||||
() => options.QueuePollInterval = TimeSpan.Zero);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Set_QueuePollInterval_ShouldThrowAnException_WhenGivenIntervalIsNegative()
|
||||
{
|
||||
var options = new MongoStorageOptions();
|
||||
Assert.Throws<ArgumentException>(
|
||||
() => options.QueuePollInterval = TimeSpan.FromSeconds(-1));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Set_QueuePollInterval_SetsTheValue()
|
||||
{
|
||||
var options = new MongoStorageOptions();
|
||||
options.QueuePollInterval = TimeSpan.FromSeconds(1);
|
||||
Assert.Equal(TimeSpan.FromSeconds(1), options.QueuePollInterval);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,735 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using Hangfire.Mongo.Database;
|
||||
using Hangfire.Mongo.Dto;
|
||||
using Hangfire.Mongo.MongoUtils;
|
||||
using Hangfire.Mongo.PersistentJobQueue;
|
||||
using Hangfire.Mongo.Tests.Utils;
|
||||
using Hangfire.States;
|
||||
using MongoDB.Bson;
|
||||
using MongoDB.Driver.Builders;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Hangfire.Mongo.Tests
|
||||
{
|
||||
public class MongoWriteOnlyTransactionFacts
|
||||
{
|
||||
private readonly PersistentJobQueueProviderCollection _queueProviders;
|
||||
|
||||
public MongoWriteOnlyTransactionFacts()
|
||||
{
|
||||
Mock<IPersistentJobQueueProvider> defaultProvider = new Mock<IPersistentJobQueueProvider>();
|
||||
defaultProvider.Setup(x => x.GetJobQueue(It.IsNotNull<HangfireDbContext>()))
|
||||
.Returns(new Mock<IPersistentJobQueue>().Object);
|
||||
|
||||
_queueProviders = new PersistentJobQueueProviderCollection(defaultProvider.Object);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Ctor_ThrowsAnException_IfConnectionIsNull()
|
||||
{
|
||||
ArgumentNullException exception = Assert.Throws<ArgumentNullException>(() => new MongoWriteOnlyTransaction(null, _queueProviders));
|
||||
|
||||
Assert.Equal("connection", exception.ParamName);
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void Ctor_ThrowsAnException_IfProvidersCollectionIsNull()
|
||||
{
|
||||
var exception = Assert.Throws<ArgumentNullException>(() => new MongoWriteOnlyTransaction(ConnectionUtils.CreateConnection(), null));
|
||||
|
||||
Assert.Equal("queueProviders", exception.ParamName);
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void ExpireJob_SetsJobExpirationData()
|
||||
{
|
||||
UseConnection(database =>
|
||||
{
|
||||
JobDto job = new JobDto
|
||||
{
|
||||
Id = 1,
|
||||
InvocationData = "",
|
||||
Arguments = "",
|
||||
CreatedAt = database.GetServerTimeUtc()
|
||||
};
|
||||
database.Job.Insert(job);
|
||||
|
||||
JobDto anotherJob = new JobDto
|
||||
{
|
||||
Id = 2,
|
||||
InvocationData = "",
|
||||
Arguments = "",
|
||||
CreatedAt = database.GetServerTimeUtc()
|
||||
};
|
||||
database.Job.Insert(anotherJob);
|
||||
|
||||
var jobId = job.Id;
|
||||
var anotherJobId = anotherJob.Id;
|
||||
|
||||
Commit(database, x => x.ExpireJob(jobId.ToString(), TimeSpan.FromDays(1)));
|
||||
|
||||
var testJob = GetTestJob(database, jobId);
|
||||
Assert.True(database.GetServerTimeUtc().AddMinutes(-1) < testJob.ExpireAt && testJob.ExpireAt <= database.GetServerTimeUtc().AddDays(1));
|
||||
|
||||
var anotherTestJob = GetTestJob(database, anotherJobId);
|
||||
Assert.Null(anotherTestJob.ExpireAt);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void PersistJob_ClearsTheJobExpirationData()
|
||||
{
|
||||
UseConnection(database =>
|
||||
{
|
||||
JobDto job = new JobDto
|
||||
{
|
||||
Id = 1,
|
||||
InvocationData = "",
|
||||
Arguments = "",
|
||||
CreatedAt = database.GetServerTimeUtc(),
|
||||
ExpireAt = database.GetServerTimeUtc()
|
||||
};
|
||||
database.Job.Insert(job);
|
||||
|
||||
JobDto anotherJob = new JobDto
|
||||
{
|
||||
Id = 2,
|
||||
InvocationData = "",
|
||||
Arguments = "",
|
||||
CreatedAt = database.GetServerTimeUtc(),
|
||||
ExpireAt = database.GetServerTimeUtc()
|
||||
};
|
||||
database.Job.Insert(anotherJob);
|
||||
|
||||
var jobId = job.Id;
|
||||
var anotherJobId = anotherJob.Id;
|
||||
|
||||
Commit(database, x => x.PersistJob(jobId.ToString()));
|
||||
|
||||
var testjob = GetTestJob(database, jobId);
|
||||
Assert.Null(testjob.ExpireAt);
|
||||
|
||||
var anotherTestJob = GetTestJob(database, anotherJobId);
|
||||
Assert.NotNull(anotherTestJob.ExpireAt);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void SetJobState_AppendsAStateAndSetItToTheJob()
|
||||
{
|
||||
UseConnection(database =>
|
||||
{
|
||||
JobDto job = new JobDto
|
||||
{
|
||||
Id = 1,
|
||||
InvocationData = "",
|
||||
Arguments = "",
|
||||
CreatedAt = database.GetServerTimeUtc()
|
||||
};
|
||||
database.Job.Insert(job);
|
||||
|
||||
JobDto anotherJob = new JobDto
|
||||
{
|
||||
Id = 2,
|
||||
InvocationData = "",
|
||||
Arguments = "",
|
||||
CreatedAt = database.GetServerTimeUtc()
|
||||
};
|
||||
database.Job.Insert(anotherJob);
|
||||
|
||||
var jobId = job.Id;
|
||||
var anotherJobId = anotherJob.Id;
|
||||
|
||||
var state = new Mock<IState>();
|
||||
state.Setup(x => x.Name).Returns("State");
|
||||
state.Setup(x => x.Reason).Returns("Reason");
|
||||
state.Setup(x => x.SerializeData())
|
||||
.Returns(new Dictionary<string, string> { { "Name", "Value" } });
|
||||
|
||||
Commit(database, x => x.SetJobState(jobId.ToString(), state.Object));
|
||||
|
||||
var testJob = GetTestJob(database, jobId);
|
||||
Assert.Equal("State", testJob.StateName);
|
||||
Assert.NotNull(testJob.StateId);
|
||||
|
||||
var anotherTestJob = GetTestJob(database, anotherJobId);
|
||||
Assert.Null(anotherTestJob.StateName);
|
||||
Assert.Equal(ObjectId.Empty, anotherTestJob.StateId);
|
||||
|
||||
StateDto jobState = database.State.FindAll().Single();
|
||||
Assert.Equal(jobId, jobState.JobId);
|
||||
Assert.Equal("State", jobState.Name);
|
||||
Assert.Equal("Reason", jobState.Reason);
|
||||
Assert.NotNull(jobState.CreatedAt);
|
||||
Assert.Equal("{\"Name\":\"Value\"}", jobState.Data);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void AddJobState_JustAddsANewRecordInATable()
|
||||
{
|
||||
UseConnection(database =>
|
||||
{
|
||||
JobDto job = new JobDto
|
||||
{
|
||||
Id = 1,
|
||||
InvocationData = "",
|
||||
Arguments = "",
|
||||
CreatedAt = database.GetServerTimeUtc()
|
||||
};
|
||||
database.Job.Insert(job);
|
||||
|
||||
var jobId = job.Id;
|
||||
|
||||
var state = new Mock<IState>();
|
||||
state.Setup(x => x.Name).Returns("State");
|
||||
state.Setup(x => x.Reason).Returns("Reason");
|
||||
state.Setup(x => x.SerializeData())
|
||||
.Returns(new Dictionary<string, string> { { "Name", "Value" } });
|
||||
|
||||
Commit(database, x => x.AddJobState(jobId.ToString(), state.Object));
|
||||
|
||||
var testJob = GetTestJob(database, jobId);
|
||||
Assert.Null(testJob.StateName);
|
||||
Assert.Equal(ObjectId.Empty, testJob.StateId);
|
||||
|
||||
StateDto jobState = database.State.FindAll().Single();
|
||||
Assert.Equal(jobId, jobState.JobId);
|
||||
Assert.Equal("State", jobState.Name);
|
||||
Assert.Equal("Reason", jobState.Reason);
|
||||
Assert.NotNull(jobState.CreatedAt);
|
||||
Assert.Equal("{\"Name\":\"Value\"}", jobState.Data);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void AddToQueue_CallsEnqueue_OnTargetPersistentQueue()
|
||||
{
|
||||
UseConnection(database =>
|
||||
{
|
||||
var correctJobQueue = new Mock<IPersistentJobQueue>();
|
||||
var correctProvider = new Mock<IPersistentJobQueueProvider>();
|
||||
correctProvider.Setup(x => x.GetJobQueue(It.IsNotNull<HangfireDbContext>()))
|
||||
.Returns(correctJobQueue.Object);
|
||||
|
||||
_queueProviders.Add(correctProvider.Object, new[] { "default" });
|
||||
|
||||
Commit(database, x => x.AddToQueue("default", "1"));
|
||||
|
||||
correctJobQueue.Verify(x => x.Enqueue("default", "1"));
|
||||
});
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void IncrementCounter_AddsRecordToCounterTable_WithPositiveValue()
|
||||
{
|
||||
UseConnection(database =>
|
||||
{
|
||||
Commit(database, x => x.IncrementCounter("my-key"));
|
||||
|
||||
CounterDto record = database.Counter.FindAll().Single();
|
||||
|
||||
Assert.Equal("my-key", record.Key);
|
||||
Assert.Equal(1, record.Value);
|
||||
Assert.Equal(null, record.ExpireAt);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void IncrementCounter_WithExpiry_AddsARecord_WithExpirationTimeSet()
|
||||
{
|
||||
UseConnection(database =>
|
||||
{
|
||||
Commit(database, x => x.IncrementCounter("my-key", TimeSpan.FromDays(1)));
|
||||
|
||||
CounterDto record = database.Counter.FindAll().Single();
|
||||
|
||||
Assert.Equal("my-key", record.Key);
|
||||
Assert.Equal(1, record.Value);
|
||||
Assert.NotNull(record.ExpireAt);
|
||||
|
||||
var expireAt = (DateTime)record.ExpireAt;
|
||||
|
||||
Assert.True(database.GetServerTimeUtc().AddHours(23) < expireAt);
|
||||
Assert.True(expireAt < database.GetServerTimeUtc().AddHours(25));
|
||||
});
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void IncrementCounter_WithExistingKey_AddsAnotherRecord()
|
||||
{
|
||||
UseConnection(database =>
|
||||
{
|
||||
Commit(database, x =>
|
||||
{
|
||||
x.IncrementCounter("my-key");
|
||||
x.IncrementCounter("my-key");
|
||||
});
|
||||
|
||||
var recordCount = database.Counter.Count();
|
||||
|
||||
Assert.Equal(2, recordCount);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void DecrementCounter_AddsRecordToCounterTable_WithNegativeValue()
|
||||
{
|
||||
UseConnection(database =>
|
||||
{
|
||||
Commit(database, x => x.DecrementCounter("my-key"));
|
||||
|
||||
CounterDto record = database.Counter.FindAll().Single();
|
||||
|
||||
Assert.Equal("my-key", record.Key);
|
||||
Assert.Equal(-1, record.Value);
|
||||
Assert.Equal(null, record.ExpireAt);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void DecrementCounter_WithExpiry_AddsARecord_WithExpirationTimeSet()
|
||||
{
|
||||
UseConnection(database =>
|
||||
{
|
||||
Commit(database, x => x.DecrementCounter("my-key", TimeSpan.FromDays(1)));
|
||||
|
||||
CounterDto record = database.Counter.FindAll().Single();
|
||||
|
||||
Assert.Equal("my-key", record.Key);
|
||||
Assert.Equal(-1, record.Value);
|
||||
Assert.NotNull(record.ExpireAt);
|
||||
|
||||
var expireAt = (DateTime)record.ExpireAt;
|
||||
|
||||
Assert.True(database.GetServerTimeUtc().AddHours(23) < expireAt);
|
||||
Assert.True(expireAt < database.GetServerTimeUtc().AddHours(25));
|
||||
});
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void DecrementCounter_WithExistingKey_AddsAnotherRecord()
|
||||
{
|
||||
UseConnection(database =>
|
||||
{
|
||||
Commit(database, x =>
|
||||
{
|
||||
x.DecrementCounter("my-key");
|
||||
x.DecrementCounter("my-key");
|
||||
});
|
||||
|
||||
var recordCount = database.Counter.Count();
|
||||
|
||||
Assert.Equal(2, recordCount);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void AddToSet_AddsARecord_IfThereIsNo_SuchKeyAndValue()
|
||||
{
|
||||
UseConnection(database =>
|
||||
{
|
||||
Commit(database, x => x.AddToSet("my-key", "my-value"));
|
||||
|
||||
var record = database.Set.FindAll().Single();
|
||||
|
||||
Assert.Equal("my-key", record.Key);
|
||||
Assert.Equal("my-value", record.Value);
|
||||
Assert.Equal(0.0, record.Score, 2);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void AddToSet_AddsARecord_WhenKeyIsExists_ButValuesAreDifferent()
|
||||
{
|
||||
UseConnection(database =>
|
||||
{
|
||||
Commit(database, x =>
|
||||
{
|
||||
x.AddToSet("my-key", "my-value");
|
||||
x.AddToSet("my-key", "another-value");
|
||||
});
|
||||
|
||||
var recordCount = database.Set.Count();
|
||||
|
||||
Assert.Equal(2, recordCount);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void AddToSet_DoesNotAddARecord_WhenBothKeyAndValueAreExist()
|
||||
{
|
||||
UseConnection(database =>
|
||||
{
|
||||
Commit(database, x =>
|
||||
{
|
||||
x.AddToSet("my-key", "my-value");
|
||||
x.AddToSet("my-key", "my-value");
|
||||
});
|
||||
|
||||
var recordCount = database.Set.Count();
|
||||
|
||||
Assert.Equal(1, recordCount);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void AddToSet_WithScore_AddsARecordWithScore_WhenBothKeyAndValueAreNotExist()
|
||||
{
|
||||
UseConnection(database =>
|
||||
{
|
||||
Commit(database, x => x.AddToSet("my-key", "my-value", 3.2));
|
||||
|
||||
var record = database.Set.FindAll().Single();
|
||||
|
||||
Assert.Equal("my-key", record.Key);
|
||||
Assert.Equal("my-value", record.Value);
|
||||
Assert.Equal(3.2, record.Score, 3);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void AddToSet_WithScore_UpdatesAScore_WhenBothKeyAndValueAreExist()
|
||||
{
|
||||
UseConnection(database =>
|
||||
{
|
||||
Commit(database, x =>
|
||||
{
|
||||
x.AddToSet("my-key", "my-value");
|
||||
x.AddToSet("my-key", "my-value", 3.2);
|
||||
});
|
||||
|
||||
var record = database.Set.FindAll().Single();
|
||||
|
||||
Assert.Equal(3.2, record.Score, 3);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void RemoveFromSet_RemovesARecord_WithGivenKeyAndValue()
|
||||
{
|
||||
UseConnection(database =>
|
||||
{
|
||||
Commit(database, x =>
|
||||
{
|
||||
x.AddToSet("my-key", "my-value");
|
||||
x.RemoveFromSet("my-key", "my-value");
|
||||
});
|
||||
|
||||
var recordCount = database.Set.Count();
|
||||
|
||||
Assert.Equal(0, recordCount);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void RemoveFromSet_DoesNotRemoveRecord_WithSameKey_AndDifferentValue()
|
||||
{
|
||||
UseConnection(database =>
|
||||
{
|
||||
Commit(database, x =>
|
||||
{
|
||||
x.AddToSet("my-key", "my-value");
|
||||
x.RemoveFromSet("my-key", "different-value");
|
||||
});
|
||||
|
||||
var recordCount = database.Set.Count();
|
||||
|
||||
Assert.Equal(1, recordCount);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void RemoveFromSet_DoesNotRemoveRecord_WithSameValue_AndDifferentKey()
|
||||
{
|
||||
UseConnection(database =>
|
||||
{
|
||||
Commit(database, x =>
|
||||
{
|
||||
x.AddToSet("my-key", "my-value");
|
||||
x.RemoveFromSet("different-key", "my-value");
|
||||
});
|
||||
|
||||
var recordCount = database.Set.Count();
|
||||
|
||||
Assert.Equal(1, recordCount);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void InsertToList_AddsARecord_WithGivenValues()
|
||||
{
|
||||
UseConnection(database =>
|
||||
{
|
||||
Commit(database, x => x.InsertToList("my-key", "my-value"));
|
||||
|
||||
var record = database.List.FindAll().Single();
|
||||
|
||||
Assert.Equal("my-key", record.Key);
|
||||
Assert.Equal("my-value", record.Value);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void InsertToList_AddsAnotherRecord_WhenBothKeyAndValueAreExist()
|
||||
{
|
||||
UseConnection(database =>
|
||||
{
|
||||
Commit(database, x =>
|
||||
{
|
||||
x.InsertToList("my-key", "my-value");
|
||||
x.InsertToList("my-key", "my-value");
|
||||
});
|
||||
|
||||
var recordCount = database.List.Count();
|
||||
|
||||
Assert.Equal(2, recordCount);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void RemoveFromList_RemovesAllRecords_WithGivenKeyAndValue()
|
||||
{
|
||||
UseConnection(database =>
|
||||
{
|
||||
Commit(database, x =>
|
||||
{
|
||||
x.InsertToList("my-key", "my-value");
|
||||
x.InsertToList("my-key", "my-value");
|
||||
x.RemoveFromList("my-key", "my-value");
|
||||
});
|
||||
|
||||
var recordCount = database.List.Count();
|
||||
|
||||
Assert.Equal(0, recordCount);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void RemoveFromList_DoesNotRemoveRecords_WithSameKey_ButDifferentValue()
|
||||
{
|
||||
UseConnection(database =>
|
||||
{
|
||||
Commit(database, x =>
|
||||
{
|
||||
x.InsertToList("my-key", "my-value");
|
||||
x.RemoveFromList("my-key", "different-value");
|
||||
});
|
||||
|
||||
var recordCount = database.List.Count();
|
||||
|
||||
Assert.Equal(1, recordCount);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void RemoveFromList_DoesNotRemoveRecords_WithSameValue_ButDifferentKey()
|
||||
{
|
||||
UseConnection(database =>
|
||||
{
|
||||
Commit(database, x =>
|
||||
{
|
||||
x.InsertToList("my-key", "my-value");
|
||||
x.RemoveFromList("different-key", "my-value");
|
||||
});
|
||||
|
||||
var recordCount = database.List.Count();
|
||||
|
||||
Assert.Equal(1, recordCount);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void TrimList_TrimsAList_ToASpecifiedRange()
|
||||
{
|
||||
UseConnection(database =>
|
||||
{
|
||||
Commit(database, x =>
|
||||
{
|
||||
x.InsertToList("my-key", "0");
|
||||
x.InsertToList("my-key", "1");
|
||||
x.InsertToList("my-key", "2");
|
||||
x.InsertToList("my-key", "3");
|
||||
x.TrimList("my-key", 1, 2);
|
||||
});
|
||||
|
||||
var records = database.List.FindAll().ToArray();
|
||||
|
||||
Assert.Equal(2, records.Length);
|
||||
Assert.Equal("1", records[0].Value);
|
||||
Assert.Equal("2", records[1].Value);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void TrimList_RemovesRecordsToEnd_IfKeepAndingAt_GreaterThanMaxElementIndex()
|
||||
{
|
||||
UseConnection(sql =>
|
||||
{
|
||||
Commit(sql, x =>
|
||||
{
|
||||
x.InsertToList("my-key", "0");
|
||||
x.InsertToList("my-key", "1");
|
||||
x.InsertToList("my-key", "2");
|
||||
x.TrimList("my-key", 1, 100);
|
||||
});
|
||||
|
||||
var recordCount = sql.List.Count();
|
||||
|
||||
Assert.Equal(2, recordCount);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void TrimList_RemovesAllRecords_WhenStartingFromValue_GreaterThanMaxElementIndex()
|
||||
{
|
||||
UseConnection(database =>
|
||||
{
|
||||
Commit(database, x =>
|
||||
{
|
||||
x.InsertToList("my-key", "0");
|
||||
x.TrimList("my-key", 1, 100);
|
||||
});
|
||||
|
||||
var recordCount = database.List.Count();
|
||||
|
||||
Assert.Equal(0, recordCount);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void TrimList_RemovesAllRecords_IfStartFromGreaterThanEndingAt()
|
||||
{
|
||||
UseConnection(database =>
|
||||
{
|
||||
Commit(database, x =>
|
||||
{
|
||||
x.InsertToList("my-key", "0");
|
||||
x.TrimList("my-key", 1, 0);
|
||||
});
|
||||
|
||||
var recordCount = database.List.Count();
|
||||
|
||||
Assert.Equal(0, recordCount);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void TrimList_RemovesRecords_OnlyOfAGivenKey()
|
||||
{
|
||||
UseConnection(database =>
|
||||
{
|
||||
Commit(database, x =>
|
||||
{
|
||||
x.InsertToList("my-key", "0");
|
||||
x.TrimList("another-key", 1, 0);
|
||||
});
|
||||
|
||||
var recordCount = database.List.Count();
|
||||
|
||||
Assert.Equal(1, recordCount);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void SetRangeInHash_ThrowsAnException_WhenKeyIsNull()
|
||||
{
|
||||
UseConnection(database =>
|
||||
{
|
||||
ArgumentNullException exception = Assert.Throws<ArgumentNullException>(
|
||||
() => Commit(database, x => x.SetRangeInHash(null, new Dictionary<string, string>())));
|
||||
|
||||
Assert.Equal("key", exception.ParamName);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void SetRangeInHash_ThrowsAnException_WhenKeyValuePairsArgumentIsNull()
|
||||
{
|
||||
UseConnection(database =>
|
||||
{
|
||||
var exception = Assert.Throws<ArgumentNullException>(
|
||||
() => Commit(database, x => x.SetRangeInHash("some-hash", null)));
|
||||
|
||||
Assert.Equal("keyValuePairs", exception.ParamName);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void SetRangeInHash_MergesAllRecords()
|
||||
{
|
||||
UseConnection(database =>
|
||||
{
|
||||
Commit(database, x => x.SetRangeInHash("some-hash", new Dictionary<string, string>
|
||||
{
|
||||
{ "Key1", "Value1" },
|
||||
{ "Key2", "Value2" }
|
||||
}));
|
||||
|
||||
var result = database.Hash.Find(Query<HashDto>.EQ(_ => _.Key, "some-hash"))
|
||||
.ToDictionary(x => x.Field, x => x.Value);
|
||||
|
||||
Assert.Equal("Value1", result["Key1"]);
|
||||
Assert.Equal("Value2", result["Key2"]);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void RemoveHash_ThrowsAnException_WhenKeyIsNull()
|
||||
{
|
||||
UseConnection(database =>
|
||||
{
|
||||
Assert.Throws<ArgumentNullException>(
|
||||
() => Commit(database, x => x.RemoveHash(null)));
|
||||
});
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void RemoveHash_RemovesAllHashRecords()
|
||||
{
|
||||
UseConnection(database =>
|
||||
{
|
||||
// Arrange
|
||||
Commit(database, x => x.SetRangeInHash("some-hash", new Dictionary<string, string>
|
||||
{
|
||||
{ "Key1", "Value1" },
|
||||
{ "Key2", "Value2" }
|
||||
}));
|
||||
|
||||
// Act
|
||||
Commit(database, x => x.RemoveHash("some-hash"));
|
||||
|
||||
// Assert
|
||||
var count = database.Hash.Count();
|
||||
Assert.Equal(0, count);
|
||||
});
|
||||
}
|
||||
|
||||
private static dynamic GetTestJob(HangfireDbContext database, int jobId)
|
||||
{
|
||||
return database.Job.FindOneById(jobId);
|
||||
}
|
||||
|
||||
private void UseConnection(Action<HangfireDbContext> action)
|
||||
{
|
||||
using (HangfireDbContext connection = ConnectionUtils.CreateConnection())
|
||||
{
|
||||
action(connection);
|
||||
}
|
||||
}
|
||||
|
||||
private void Commit(HangfireDbContext connection, Action<MongoWriteOnlyTransaction> action)
|
||||
{
|
||||
using (MongoWriteOnlyTransaction transaction = new MongoWriteOnlyTransaction(connection, _queueProviders))
|
||||
{
|
||||
action(transaction);
|
||||
transaction.Commit();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("Hangfire.Mongo.Tests")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("Hangfire.Mongo.Tests")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2014 Sergey Zwezdin")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("c9ee6131-b199-40d2-ba5d-bb51da90ba08")]
|
||||
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.0.0.0")]
|
||||
[assembly: AssemblyInformationalVersion("1.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.0.0")]
|
||||
@@ -0,0 +1,67 @@
|
||||
using System;
|
||||
using System.Data.SqlClient;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using Hangfire.Mongo.Database;
|
||||
using Hangfire.Mongo.Dto;
|
||||
using MongoDB.Driver;
|
||||
using Xunit;
|
||||
|
||||
namespace Hangfire.Mongo.Tests.Utils
|
||||
{
|
||||
public class CleanDatabaseAttribute : BeforeAfterTestAttribute
|
||||
{
|
||||
private static readonly object GlobalLock = new object();
|
||||
|
||||
private static bool _sqlObjectInstalled;
|
||||
|
||||
public CleanDatabaseAttribute()
|
||||
{
|
||||
}
|
||||
|
||||
public override void Before(MethodInfo methodUnderTest)
|
||||
{
|
||||
Monitor.Enter(GlobalLock);
|
||||
|
||||
if (_sqlObjectInstalled == false)
|
||||
{
|
||||
RecreateDatabaseAndInstallObjects();
|
||||
_sqlObjectInstalled = true;
|
||||
}
|
||||
}
|
||||
|
||||
public override void After(MethodInfo methodUnderTest)
|
||||
{
|
||||
Monitor.Exit(GlobalLock);
|
||||
}
|
||||
|
||||
private static void RecreateDatabaseAndInstallObjects()
|
||||
{
|
||||
using (HangfireDbContext context = new HangfireDbContext(ConnectionUtils.GetConnectionString(), ConnectionUtils.GetDatabaseName()))
|
||||
{
|
||||
context.Init();
|
||||
|
||||
MongoCollection[] collections =
|
||||
{
|
||||
context.DistributedLock,
|
||||
context.Counter,
|
||||
context.Hash,
|
||||
context.Job,
|
||||
context.JobParameter,
|
||||
context.JobQueue,
|
||||
context.List,
|
||||
context.Server,
|
||||
context.Set,
|
||||
context.State
|
||||
};
|
||||
|
||||
foreach (MongoCollection collection in collections)
|
||||
{
|
||||
WriteConcernResult result = collection.RemoveAll();
|
||||
if (result.Ok == false)
|
||||
throw new InvalidOperationException("Unable to cleanup database.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
using System;
|
||||
using Hangfire.Mongo.Database;
|
||||
|
||||
namespace Hangfire.Mongo.Tests.Utils
|
||||
{
|
||||
public class ConnectionUtils
|
||||
{
|
||||
private const string DatabaseVariable = "Hangfire_Mongo_DatabaseName";
|
||||
private const string ConnectionStringTemplateVariable = "Hangfire_Mongo_ConnectionStringTemplate";
|
||||
|
||||
private const string DefaultDatabaseName = @"Hangfire-Mongo-Tests";
|
||||
private const string DefaultConnectionStringTemplate = @"mongodb://localhost";
|
||||
|
||||
public static string GetDatabaseName()
|
||||
{
|
||||
return Environment.GetEnvironmentVariable(DatabaseVariable) ?? DefaultDatabaseName;
|
||||
}
|
||||
|
||||
public static string GetConnectionString()
|
||||
{
|
||||
return String.Format(GetConnectionStringTemplate(), GetDatabaseName());
|
||||
}
|
||||
|
||||
private static string GetConnectionStringTemplate()
|
||||
{
|
||||
return Environment.GetEnvironmentVariable(ConnectionStringTemplateVariable) ?? DefaultConnectionStringTemplate;
|
||||
}
|
||||
|
||||
public static HangfireDbContext CreateConnection()
|
||||
{
|
||||
HangfireDbContext connection = new HangfireDbContext(GetConnectionString(), GetDatabaseName());
|
||||
return connection;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Hangfire.Core" version="1.3.0" targetFramework="net451" />
|
||||
<package id="mongocsharpdriver" version="1.9.2" targetFramework="net451" />
|
||||
<package id="Moq" version="4.2.1409.1722" targetFramework="net451" />
|
||||
<package id="Newtonsoft.Json" version="5.0.1" targetFramework="net451" />
|
||||
<package id="Owin" version="1.0" targetFramework="net451" />
|
||||
<package id="xunit" version="1.9.2" targetFramework="net451" />
|
||||
</packages>
|
||||
@@ -0,0 +1,47 @@
|
||||
using System;
|
||||
using System.Data.SqlClient;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using Dapper;
|
||||
using Xunit;
|
||||
|
||||
namespace Hangfire.SqlServer.Tests
|
||||
{
|
||||
public class CountersAggregatorFacts
|
||||
{
|
||||
[Fact, CleanDatabase]
|
||||
public void CountersAggregatorExecutesProperly()
|
||||
{
|
||||
const string createSql = @"
|
||||
insert into HangFire.Counter ([Key], [Value], ExpireAt)
|
||||
values ('key', 1, @expireAt)";
|
||||
|
||||
using (var connection = CreateConnection())
|
||||
{
|
||||
// Arrange
|
||||
connection.Execute(createSql, new { expireAt = DateTime.UtcNow.AddHours(1) });
|
||||
|
||||
var aggregator = CreateAggregator(connection);
|
||||
var cts = new CancellationTokenSource();
|
||||
cts.Cancel();
|
||||
|
||||
// Act
|
||||
aggregator.Execute(cts.Token);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(1, connection.Query<int>(@"select count(*) from HangFire.AggregatedCounter").Single());
|
||||
}
|
||||
}
|
||||
|
||||
private static SqlConnection CreateConnection()
|
||||
{
|
||||
return ConnectionUtils.CreateConnection();
|
||||
}
|
||||
|
||||
private static CountersAggregator CreateAggregator(SqlConnection connection)
|
||||
{
|
||||
var storage = new SqlServerStorage(connection);
|
||||
return new CountersAggregator(storage, TimeSpan.Zero);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
using System;
|
||||
using System.Data;
|
||||
using System.Data.SqlClient;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using Dapper;
|
||||
using Xunit;
|
||||
|
||||
namespace Hangfire.SqlServer.Tests
|
||||
{
|
||||
public class ExpirationManagerFacts
|
||||
{
|
||||
private readonly CancellationToken _token;
|
||||
|
||||
public ExpirationManagerFacts()
|
||||
{
|
||||
var cts = new CancellationTokenSource();
|
||||
_token = cts.Token;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Ctor_ThrowsAnException_WhenStorageIsNull()
|
||||
{
|
||||
Assert.Throws<ArgumentNullException>(() => new ExpirationManager(null));
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void Execute_RemovesOutdatedRecords()
|
||||
{
|
||||
using (var connection = CreateConnection())
|
||||
{
|
||||
var entryId = CreateExpirationEntry(connection, DateTime.UtcNow.AddMonths(-1));
|
||||
var manager = CreateManager(connection);
|
||||
|
||||
manager.Execute(_token);
|
||||
|
||||
Assert.True(IsEntryExpired(connection, entryId));
|
||||
}
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void Execute_DoesNotRemoveEntries_WithNoExpirationTimeSet()
|
||||
{
|
||||
using (var connection = CreateConnection())
|
||||
{
|
||||
var entryId = CreateExpirationEntry(connection, null);
|
||||
var manager = CreateManager(connection);
|
||||
|
||||
manager.Execute(_token);
|
||||
|
||||
Assert.False(IsEntryExpired(connection, entryId));
|
||||
}
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void Execute_DoesNotRemoveEntries_WithFreshExpirationTime()
|
||||
{
|
||||
using (var connection = CreateConnection())
|
||||
{
|
||||
var entryId = CreateExpirationEntry(connection, DateTime.Now.AddMonths(1));
|
||||
var manager = CreateManager(connection);
|
||||
|
||||
manager.Execute(_token);
|
||||
|
||||
Assert.False(IsEntryExpired(connection, entryId));
|
||||
}
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void Execute_Processes_AggregatedCounterTable()
|
||||
{
|
||||
using (var connection = CreateConnection())
|
||||
{
|
||||
// Arrange
|
||||
const string createSql = @"
|
||||
insert into HangFire.AggregatedCounter ([Key], [Value], ExpireAt)
|
||||
values ('key', 1, @expireAt)";
|
||||
connection.Execute(createSql, new { expireAt = DateTime.UtcNow.AddMonths(-1) });
|
||||
|
||||
var manager = CreateManager(connection);
|
||||
|
||||
// Act
|
||||
manager.Execute(_token);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(0, connection.Query<int>(@"select count(*) from HangFire.Counter").Single());
|
||||
}
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void Execute_Processes_JobTable()
|
||||
{
|
||||
using (var connection = CreateConnection())
|
||||
{
|
||||
// Arrange
|
||||
const string createSql = @"
|
||||
insert into HangFire.Job (InvocationData, Arguments, CreatedAt, ExpireAt)
|
||||
values ('', '', getutcdate(), @expireAt)";
|
||||
connection.Execute(createSql, new { expireAt = DateTime.UtcNow.AddMonths(-1) });
|
||||
|
||||
var manager = CreateManager(connection);
|
||||
|
||||
// Act
|
||||
manager.Execute(_token);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(0, connection.Query<int>(@"select count(*) from HangFire.Job").Single());
|
||||
}
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void Execute_Processes_ListTable()
|
||||
{
|
||||
using (var connection = CreateConnection())
|
||||
{
|
||||
// Arrange
|
||||
const string createSql = @"
|
||||
insert into HangFire.List ([Key], ExpireAt)
|
||||
values ('key', @expireAt)";
|
||||
connection.Execute(createSql, new { expireAt = DateTime.UtcNow.AddMonths(-1) });
|
||||
|
||||
var manager = CreateManager(connection);
|
||||
|
||||
// Act
|
||||
manager.Execute(_token);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(0, connection.Query<int>(@"select count(*) from HangFire.List").Single());
|
||||
}
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void Execute_Processes_SetTable()
|
||||
{
|
||||
using (var connection = CreateConnection())
|
||||
{
|
||||
// Arrange
|
||||
const string createSql = @"
|
||||
insert into HangFire.[Set] ([Key], [Score], [Value], ExpireAt)
|
||||
values ('key', 0, '', @expireAt)";
|
||||
connection.Execute(createSql, new { expireAt = DateTime.UtcNow.AddMonths(-1) });
|
||||
|
||||
var manager = CreateManager(connection);
|
||||
|
||||
// Act
|
||||
manager.Execute(_token);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(0, connection.Query<int>(@"select count(*) from HangFire.[Set]").Single());
|
||||
}
|
||||
}
|
||||
|
||||
[Fact, CleanDatabase]
|
||||
public void Execute_Processes_HashTable()
|
||||
{
|
||||
using (var connection = CreateConnection())
|
||||
{
|
||||
// Arrange
|
||||
const string createSql = @"
|
||||
insert into HangFire.Hash ([Key], [Field], [Value], ExpireAt)
|
||||
values ('key', 'field', '', @expireAt)";
|
||||
connection.Execute(createSql, new { expireAt = DateTime.UtcNow.AddMonths(-1) });
|
||||
|
||||
var manager = CreateManager(connection);
|
||||
|
||||
// Act
|
||||
manager.Execute(_token);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(0, connection.Query<int>(@"select count(*) from HangFire.Hash").Single());
|
||||
}
|
||||
}
|
||||
|
||||
private static int CreateExpirationEntry(SqlConnection connection, DateTime? expireAt)
|
||||
{
|
||||
const string insertSql = @"
|
||||
insert into HangFire.AggregatedCounter ([Key], [Value], [ExpireAt])
|
||||
values ('key', 1, @expireAt)
|
||||
select scope_identity() as Id";
|
||||
|
||||
var id = connection.Query(insertSql, new { @expireAt = expireAt }).Single();
|
||||
var recordId = (int) id.Id;
|
||||
return recordId;
|
||||
}
|
||||
|
||||
private static bool IsEntryExpired(SqlConnection connection, int entryId)
|
||||
{
|
||||
var count = connection.Query<int>(
|
||||
"select count(*) from HangFire.AggregatedCounter where Id = @id", new { id = entryId }).Single();
|
||||
return count == 0;
|
||||
}
|
||||
|
||||
private SqlConnection CreateConnection()
|
||||
{
|
||||
return ConnectionUtils.CreateConnection();
|
||||
}
|
||||
|
||||
private ExpirationManager CreateManager(SqlConnection connection)
|
||||
{
|
||||
var storage = new SqlServerStorage(connection);
|
||||
return new ExpirationManager(storage, TimeSpan.Zero);
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user