diff --git a/src/Orchard.Web/Modules/Orchard.TaskLease/Migrations.cs b/src/Orchard.Web/Modules/Orchard.TaskLease/Migrations.cs new file mode 100644 index 000000000..4f43eb7a5 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.TaskLease/Migrations.cs @@ -0,0 +1,23 @@ +using System; +using Orchard.ContentManagement.MetaData; +using Orchard.Core.Contents.Extensions; +using Orchard.Data.Migration; + +namespace Orchard.TaskLease { + public class TaskLeaseMigrations : DataMigrationImpl { + + public int Create() { + SchemaBuilder.CreateTable("TaskLeaseRecord", + table => table + .Column("Id", column => column.PrimaryKey().Identity()) + .Column("TaskName") + .Column("MachineName") + .Column("UpdatedUtc") + .Column("ExpiredUtc") + .Column("State", c => c.Unlimited()) + ); + + return 1; + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.TaskLease/Models/TaskLeaseRecord.cs b/src/Orchard.Web/Modules/Orchard.TaskLease/Models/TaskLeaseRecord.cs new file mode 100644 index 000000000..3e28a67f3 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.TaskLease/Models/TaskLeaseRecord.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web; +using Orchard.Data.Conventions; + +namespace Orchard.TaskLease.Models +{ + public class TaskLeaseRecord + { + public virtual int Id { get; set; } + public virtual string TaskName { get; set; } + public virtual string MachineName { get; set; } + public virtual DateTime UpdatedUtc { get; set; } + public virtual DateTime ExpiredUtc { get; set; } + + [StringLengthMax] + public virtual string State { get; set; } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.TaskLease/Module.txt b/src/Orchard.Web/Modules/Orchard.TaskLease/Module.txt new file mode 100644 index 000000000..ba3350766 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.TaskLease/Module.txt @@ -0,0 +1,8 @@ +Name: Task Lease +AntiForgery: enabled +Author: The Orchard Team +Website: http://orchardtasklease.codeplex.com +Version: 1.4.2 +OrchardVersion: 1.4.2 +Description: Provides services to synchronize tasks in a farm environment +Category: Hosting \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.TaskLease/Orchard.TaskLease.csproj b/src/Orchard.Web/Modules/Orchard.TaskLease/Orchard.TaskLease.csproj new file mode 100644 index 000000000..e13fd77ff --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.TaskLease/Orchard.TaskLease.csproj @@ -0,0 +1,138 @@ + + + + + Debug + AnyCPU + 9.0.30729 + 2.0 + {3F72A4E9-7B72-4260-B010-C16EC54F9BAF} + {349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc} + Library + Properties + Orchard.TaskLease + Orchard.TaskLease + v4.0 + false + + + 4.0 + + + + false + + + true + full + false + bin\ + DEBUG;TRACE + prompt + 4 + AllRules.ruleset + + + pdbonly + true + bin\ + TRACE + prompt + 4 + AllRules.ruleset + + + + + + + 3.5 + + + + False + ..\..\..\..\lib\aspnetmvc\System.Web.Mvc.dll + + + + + + + + + + + + + + + + + + + Code + + + + + + + + + + + + + {2D1D92BB-4555-4CBE-8D0E-63563D6CE4C6} + Orchard.Framework + + + {9916839C-39FC-4CEB-A5AF-89CA7E87119F} + Orchard.Core + + + + 10.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + + + + + $(ProjectDir)\..\Manifests + + + + + + + + + + + + False + True + 45979 + / + + + False + True + http://orchard.codeplex.com + False + + + + + \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.TaskLease/Properties/AssemblyInfo.cs b/src/Orchard.Web/Modules/Orchard.TaskLease/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..7804ea84a --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.TaskLease/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Security; + +// 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("Orchard.TaskLease")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyProduct("Orchard")] +[assembly: AssemblyCopyright("Copyright © Outercurve Foundation 2009")] +[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("3320c438-e92b-4753-bfe5-944f29d13ded")] + +// 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.4.2")] +[assembly: AssemblyFileVersion("1.4.2")] + diff --git a/src/Orchard.Web/Modules/Orchard.TaskLease/Scripts/Web.config b/src/Orchard.Web/Modules/Orchard.TaskLease/Scripts/Web.config new file mode 100644 index 000000000..770adfab5 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.TaskLease/Scripts/Web.config @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Orchard.Web/Modules/Orchard.TaskLease/Services/IMachineNameProvider.cs b/src/Orchard.Web/Modules/Orchard.TaskLease/Services/IMachineNameProvider.cs new file mode 100644 index 000000000..9a7a173e3 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.TaskLease/Services/IMachineNameProvider.cs @@ -0,0 +1,16 @@ +namespace Orchard.TaskLease.Services +{ + /// + /// Describes a service which returns a name for the machine running the application. + /// + /// + /// Should be delocalized to IHostEnvironment in a leter version + /// + public interface IMachineNameProvider : IDependency + { + /// + /// Returns the current machine's name + /// + string GetMachineName(); + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.TaskLease/Services/ITaskLeaseService.cs b/src/Orchard.Web/Modules/Orchard.TaskLease/Services/ITaskLeaseService.cs new file mode 100644 index 000000000..b539bced1 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.TaskLease/Services/ITaskLeaseService.cs @@ -0,0 +1,28 @@ +using System; +using Orchard.TaskLease.Models; + +namespace Orchard.TaskLease.Services +{ + /// + /// Describes a service to save and acquire task leases. A task lease can't be acquired by two different machines, + /// for a specific amount of time. Optionnally a State can be saved along with the lease. + /// + public interface ITaskLeaseService : IDependency + { + /// + /// Acquires a lease for the specified task name, and amount of time. + /// + /// The state of the lease if it was acquired, otherwise null. + string Acquire(string taskName, DateTime expiredUtc); + + /// + /// Updates a lease for the current machine if it exists + /// + void Update(string taskName, string state); + + /// + /// Updates a lease for the current machine if it exists + /// + void Update(string taskName, string state, DateTime expiredUtc); + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.TaskLease/Services/MachineNameProvider.cs b/src/Orchard.Web/Modules/Orchard.TaskLease/Services/MachineNameProvider.cs new file mode 100644 index 000000000..11c5a18ef --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.TaskLease/Services/MachineNameProvider.cs @@ -0,0 +1,10 @@ +namespace Orchard.TaskLease.Services +{ + public class MachineNameProvider : IMachineNameProvider + { + public string GetMachineName() + { + return System.Environment.MachineName; + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.TaskLease/Services/TaskLeaseService.cs b/src/Orchard.Web/Modules/Orchard.TaskLease/Services/TaskLeaseService.cs new file mode 100644 index 000000000..23bbe658c --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.TaskLease/Services/TaskLeaseService.cs @@ -0,0 +1,101 @@ +using System; +using Orchard.Data; +using Orchard.Services; +using Orchard.TaskLease.Models; + +namespace Orchard.TaskLease.Services +{ + /// + /// Provides a database driven implementation of + /// + public class TaskLeaseService : ITaskLeaseService + { + private readonly IRepository _repository; + private readonly IClock _clock; + private readonly IMachineNameProvider _machineNameProvider; + + public TaskLeaseService( + IRepository repository, + IClock clock, + IMachineNameProvider machineNameProvider) + { + _repository = repository; + _clock = clock; + _machineNameProvider = machineNameProvider; + } + + public string Acquire(string taskName, DateTime expiredUtc) + { + var machineName = _machineNameProvider.GetMachineName(); + + // retrieve current lease for the specified task + var taskLease = _repository.Get(x => x.TaskName == taskName); + + // create a new lease if there is no current lease for this task + if (taskLease == null) + { + taskLease = new TaskLeaseRecord { + TaskName = taskName, + MachineName = machineName, + State = String.Empty, + UpdatedUtc = _clock.UtcNow, + ExpiredUtc = expiredUtc + }; + + _repository.Create(taskLease); + _repository.Flush(); + + return String.Empty; + } + + // lease can't be aquired only if for a different machine and it has not expired + if (taskLease.MachineName != machineName && taskLease.ExpiredUtc >= _clock.UtcNow) + { + return null; + } + + // otherwise update information + taskLease.MachineName = machineName; + taskLease.UpdatedUtc = _clock.UtcNow; + taskLease.ExpiredUtc = expiredUtc; + + _repository.Flush(); + + return taskLease.State; + } + + public void Update(string taskName, string state) + { + var machineName = _machineNameProvider.GetMachineName(); + + // retrieve current lease for the specified task + var taskLease = _repository.Get(x => x.TaskName == taskName && x.MachineName == machineName); + + if(taskLease == null) + { + return; + } + + taskLease.State = state; + _repository.Flush(); + } + + public void Update(string taskName, string state, DateTime expiredUtc) + { + var machineName = _machineNameProvider.GetMachineName(); + + // retrieve current lease for the specified task + var taskLease = _repository.Get(x => x.TaskName == taskName && x.MachineName == machineName); + + if (taskLease == null) + { + return; + } + + taskLease.ExpiredUtc = expiredUtc; + taskLease.State = state; + + _repository.Flush(); + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.TaskLease/Styles/Web.config b/src/Orchard.Web/Modules/Orchard.TaskLease/Styles/Web.config new file mode 100644 index 000000000..770adfab5 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.TaskLease/Styles/Web.config @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Orchard.Web/Modules/Orchard.TaskLease/Tests/Orchard.TaskLease.Tests/Orchard.TaskLease.Tests.csproj b/src/Orchard.Web/Modules/Orchard.TaskLease/Tests/Orchard.TaskLease.Tests/Orchard.TaskLease.Tests.csproj new file mode 100644 index 000000000..c99da883c --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.TaskLease/Tests/Orchard.TaskLease.Tests/Orchard.TaskLease.Tests.csproj @@ -0,0 +1,108 @@ + + + + Debug + x86 + 8.0.30703 + 2.0 + {3FD9FA96-F7C9-4E98-BF17-DC75D07B7307} + Library + Properties + Orchard.TaskLease.Tests + Orchard.TaskLease.Tests + v4.0 + + + 512 + + + x86 + true + full + false + bin\ + DEBUG;TRACE + prompt + 4 + + + x86 + pdbonly + true + bin\ + TRACE + prompt + 4 + + + + + + + + + ..\..\..\..\..\..\lib\fluentnhibernate\NHibernate.ByteCode.Castle.dll + + + ..\..\..\Contrib.RewriteRules\bin\nunit.framework.dll + + + + + False + ..\..\..\..\..\..\lib\sqlce\System.Data.SqlServerCe.dll + True + + + + + + + + + + + + + + {6CB3EB30-F725-45C0-9742-42599BA8E8D2} + Orchard.Tests.Modules + + + {ABC826D4-2FA1-4F2F-87DE-E6095F653810} + Orchard.Framework.Tests + + + {2D1D92BB-4555-4CBE-8D0E-63563D6CE4C6} + Orchard.Framework + + + {9916839C-39FC-4CEB-A5AF-89CA7E87119F} + Orchard.Core + + + {3F72A4E9-7B72-4260-B010-C16EC54F9BAF} + Orchard.TaskLease + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.TaskLease/Tests/Orchard.TaskLease.Tests/Properties/AssemblyInfo.cs b/src/Orchard.Web/Modules/Orchard.TaskLease/Tests/Orchard.TaskLease.Tests/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..482aa1f67 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.TaskLease/Tests/Orchard.TaskLease.Tests/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Orchard.TaskLease.Tests")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Microsoft")] +[assembly: AssemblyProduct("Orchard.TaskLease.Tests")] +[assembly: AssemblyCopyright("Copyright © Microsoft 2011")] +[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("0435ceba-eada-4dc4-aa03-74a68ccca515")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/src/Orchard.Web/Modules/Orchard.TaskLease/Tests/Orchard.TaskLease.Tests/Services/TaskLeaseServiceTests.cs b/src/Orchard.Web/Modules/Orchard.TaskLease/Tests/Orchard.TaskLease.Tests/Services/TaskLeaseServiceTests.cs new file mode 100644 index 000000000..dd3d2025d --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.TaskLease/Tests/Orchard.TaskLease.Tests/Services/TaskLeaseServiceTests.cs @@ -0,0 +1,97 @@ +using System; +using System.Collections.Generic; +using Autofac; +using Moq; +using NUnit.Framework; +using Orchard.TaskLease.Models; +using Orchard.TaskLease.Services; +using Orchard.Tests.Modules; + +namespace Orchard.TaskLease.Tests.Services +{ + [TestFixture] + public class TaskLeaseServiceTests : DatabaseEnabledTestsBase + { + private ITaskLeaseService _service; + private Mock _machineNameProvider; + + protected override IEnumerable DatabaseTypes + { + get + { + return new[] { + typeof (TaskLeaseRecord) + }; + } + } + + public override void Register(ContainerBuilder builder) + { + builder.RegisterType().As(); + builder.RegisterInstance((_machineNameProvider = new Mock()).Object); + + _machineNameProvider.Setup(x => x.GetMachineName()).Returns("SkyNet"); + } + + public override void Init() + { + base.Init(); + _service = _container.Resolve(); + } + + [Test] + public void AcquireShouldSucceedIfNoTask() + { + var state = _service.Acquire("Foo", _clock.UtcNow.AddDays(1)); + Assert.That(state, Is.EqualTo(String.Empty)); + } + + [Test] + public void AcquireShouldSucceedIfTaskBySameMachine() + { + var state = _service.Acquire("Foo", _clock.UtcNow.AddDays(1)); + Assert.That(state, Is.EqualTo(String.Empty)); + + state = _service.Acquire("Foo", _clock.UtcNow.AddDays(1)); + Assert.That(state, Is.EqualTo(String.Empty)); + } + + [Test] + public void AcquireShouldNotSucceedIfTaskByOtherMachine() + { + var state = _service.Acquire("Foo", _clock.UtcNow.AddDays(1)); + Assert.That(state, Is.EqualTo(String.Empty)); + + _machineNameProvider.Setup(x => x.GetMachineName()).Returns("TheMatrix"); + + state = _service.Acquire("Foo", _clock.UtcNow.AddDays(1)); + Assert.That(state, Is.EqualTo(null)); + } + + [Test] + public void ShouldUpdateTask() + { + var state = _service.Acquire("Foo", _clock.UtcNow.AddDays(1)); + Assert.That(state, Is.EqualTo(String.Empty)); + + _service.Update("Foo", "Other"); + state = _service.Acquire("Foo", _clock.UtcNow.AddDays(1)); + + Assert.That(state, Is.EqualTo("Other")); + } + + [Test] + public void AcquireShouldSucceedIfTaskByOtherMachineAndExpired() + { + var state = _service.Acquire("Foo", _clock.UtcNow.AddDays(1)); + Assert.That(state, Is.EqualTo(String.Empty)); + + _machineNameProvider.Setup(x => x.GetMachineName()).Returns("TheMatrix"); + _clock.Advance(new TimeSpan(2,0,0,0)); + + state = _service.Acquire("Foo", _clock.UtcNow.AddDays(1)); + Assert.That(state, Is.EqualTo(String.Empty)); + + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.TaskLease/Views/Web.config b/src/Orchard.Web/Modules/Orchard.TaskLease/Views/Web.config new file mode 100644 index 000000000..b7d215131 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.TaskLease/Views/Web.config @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Orchard.Web/Modules/Orchard.TaskLease/Web.config b/src/Orchard.Web/Modules/Orchard.TaskLease/Web.config new file mode 100644 index 000000000..eb8acf5f7 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.TaskLease/Web.config @@ -0,0 +1,41 @@ + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +