Update: SignalR 2.0.3 Migration; Noticeboards

Migrate all SignalR 1.x Persistent Connections to SignalR 2.x Hubs.
Abstracts ScheduledTaskStatus with core interface and adds a Mock for
optional status reporting. Noticeboards rewritten (with new theme) to be
more resilient and accurate.
This commit is contained in:
Gary Sharp
2014-06-01 23:27:07 +10:00
parent f6fae26bc7
commit 4cd57f4a90
116 changed files with 9874 additions and 6462 deletions
@@ -0,0 +1,86 @@
using Disco.Data.Repository.Monitor;
using Disco.Services.Authorization;
using Disco.Services.Web.Signalling;
using Microsoft.AspNet.SignalR;
using Microsoft.AspNet.SignalR.Hubs;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Reactive.Linq;
using Disco.Models.Repository;
using Disco.Data.Repository;
namespace Disco.Services.Devices
{
[HubName("deviceUpdates"), DiscoHubAuthorizeAll(Claims.Device.Show, Claims.Device.ShowAttachments)]
public class DeviceUpdatesHub : Hub
{
private const string UserPrefix = "Device_";
public static IHubContext HubContext { get; private set; }
private static IDisposable RepositoryBeforeSubscription;
private static IDisposable RepositoryAfterSubscription;
static DeviceUpdatesHub()
{
HubContext = GlobalHost.ConnectionManager.GetHubContext<DeviceUpdatesHub>();
// Subscribe to Repository Monitor for Changes
RepositoryBeforeSubscription = RepositoryMonitor.StreamBeforeCommit
.Where(e => e.EntityType == typeof(DeviceAttachment) && e.EventType == RepositoryMonitorEventType.Deleted)
.Subscribe(RepositoryEventBefore);
RepositoryAfterSubscription = RepositoryMonitor.StreamAfterCommit
.Where(e => e.EntityType == typeof(DeviceAttachment) && e.EventType == RepositoryMonitorEventType.Added)
.Subscribe(RepositoryAfterEvent);
}
private static string GroupName(string DeviceSerialNumber)
{
return UserPrefix + DeviceSerialNumber;
}
public override Task OnConnected()
{
var deviceSerialNumber = Context.QueryString["DeviceSerialNumber"];
if (string.IsNullOrWhiteSpace(deviceSerialNumber))
throw new ArgumentNullException("DeviceSerialNumber");
Groups.Add(Context.ConnectionId, GroupName(deviceSerialNumber));
return base.OnConnected();
}
private static void RepositoryEventBefore(RepositoryMonitorEvent e)
{
if (e.EventType == RepositoryMonitorEventType.Deleted)
{
if (e.EntityType == typeof(DeviceAttachment))
{
var repositoryAttachment = (DeviceAttachment)e.Entity;
string attachmentDeviceSerialNumber;
using (DiscoDataContext Database = new DiscoDataContext())
attachmentDeviceSerialNumber = Database.DeviceAttachments.Where(a => a.Id == repositoryAttachment.Id).Select(a => a.DeviceSerialNumber).First();
HubContext.Clients.Group(GroupName(attachmentDeviceSerialNumber)).removeAttachment(repositoryAttachment.Id);
}
}
}
private static void RepositoryAfterEvent(RepositoryMonitorEvent e)
{
if (e.EventType == RepositoryMonitorEventType.Added)
{
if (e.EntityType == typeof(DeviceAttachment))
{
var a = (DeviceAttachment)e.Entity;
HubContext.Clients.Group(GroupName(a.DeviceSerialNumber)).addAttachment(a.Id);
}
}
}
}
}
@@ -16,7 +16,7 @@ namespace Disco.Services.Devices.Exporting
public static class DeviceExport
{
public static DeviceExportResult GenerateExport(DiscoDataContext Database, IQueryable<Device> Devices, DeviceExportOptions Options, IScheduledTaskBasicStatus TaskStatus)
public static DeviceExportResult GenerateExport(DiscoDataContext Database, IQueryable<Device> Devices, DeviceExportOptions Options, IScheduledTaskStatus TaskStatus)
{
TaskStatus.UpdateStatus(15, "Building metadata and database query");
var metadata = Options.BuildMetadata();
@@ -32,15 +32,17 @@ namespace Disco.Services.Devices.Exporting
Options.AssignedUserEmailAddress)
{
TaskStatus.UpdateStatus(20, "Updating Assigned User details");
var users = Devices.Where(d => d.AssignedUserId != null).Select(d => d.AssignedUserId).Distinct().ToList();
Devices.Where(d => d.AssignedUserId != null).Select(d => d.AssignedUserId).Distinct().ToList().ForEach(userId =>
users.Select((userId, index) =>
{
TaskStatus.UpdateStatus(20 + (((double)20 / users.Count) * index), string.Format("Updating Assigned User details: {0}", userId));
try
{
UserService.GetUser(userId, Database);
return UserService.GetUser(userId, Database);
}
catch (Exception) { } // Ignore Errors
});
catch (Exception) { return null; } // Ignore Errors
}).ToList();
}
// Update Last Network Logon Date
@@ -49,8 +51,16 @@ namespace Disco.Services.Devices.Exporting
TaskStatus.UpdateStatus(40, "Updating device last network logon dates");
try
{
Interop.ActiveDirectory.ADTaskUpdateNetworkLogonDates.UpdateLastNetworkLogonDates(Database, ScheduledTaskMockStatus.Create());
TaskStatus.IgnoreCurrentProcessChanges = true;
TaskStatus.ProgressMultiplier = 20 / 100;
TaskStatus.ProgressOffset = 40;
Interop.ActiveDirectory.ADTaskUpdateNetworkLogonDates.UpdateLastNetworkLogonDates(Database, TaskStatus);
Database.SaveChanges();
TaskStatus.IgnoreCurrentProcessChanges = false;
TaskStatus.ProgressMultiplier = 1;
TaskStatus.ProgressOffset = 0;
}
catch (Exception) { } // Ignore Errors
}
@@ -103,7 +113,7 @@ namespace Disco.Services.Devices.Exporting
return GenerateExport(Database, Devices, Options, ScheduledTaskMockStatus.Create());
}
public static DeviceExportResult GenerateExport(DiscoDataContext Database, DeviceExportOptions Options, IScheduledTaskBasicStatus TaskStatus)
public static DeviceExportResult GenerateExport(DiscoDataContext Database, DeviceExportOptions Options, IScheduledTaskStatus TaskStatus)
{
switch (Options.ExportType)
{
@@ -102,7 +102,7 @@ namespace Disco.Services.Devices.Importing
Context.Header = Context.Header.Zip(HeaderTypes, (h, ht) => Tuple.Create(h.Item1, ht)).ToList();
}
public static void ParseRecords(this DeviceImportContext Context, DiscoDataContext Database, IScheduledTaskBasicStatus Status)
public static void ParseRecords(this DeviceImportContext Context, DiscoDataContext Database, IScheduledTaskStatus Status)
{
if (Context.Header == null)
throw new InvalidOperationException("The Import Context has not been initialized");
@@ -129,18 +129,13 @@ namespace Disco.Services.Devices.Importing
.Select(h => new Tuple<string, DeviceImportFieldTypes, Func<string[], string>, Type>(h.Item1, h.Item2, (f) => f[h.Item3], DeviceImport.FieldHandlers.Value[h.Item2]))
.ToList();
DateTime nextProgress = DateTime.Now;
Status.UpdateStatus(0, "Parsing Import Records", "Starting...");
Context.Records = Context.RawData.Select((d, recordIndex) =>
{
string deviceSerialNumber = Fields.DeviceSerialNumberImportField.ParseRawDeviceSerialNumber(d[Context.HeaderDeviceSerialNumberIndex]);
if (nextProgress <= DateTime.Now)
{
Status.UpdateStatus(((double)recordIndex / Context.RawData.Count) * 100, string.Format("Parsing: {0}", deviceSerialNumber));
nextProgress = DateTime.Now.AddSeconds(.5);
}
Status.UpdateStatus(((double)recordIndex / Context.RawData.Count) * 100, string.Format("Parsing: {0}", deviceSerialNumber));
Device existingDevice = null;
if (Fields.DeviceSerialNumberImportField.IsDeviceSerialNumberValid(deviceSerialNumber))
@@ -170,7 +165,7 @@ namespace Disco.Services.Devices.Importing
}).Cast<IDeviceImportRecord>().ToList();
}
public static int ApplyRecords(this DeviceImportContext Context, DiscoDataContext Database, IScheduledTaskBasicStatus Status)
public static int ApplyRecords(this DeviceImportContext Context, DiscoDataContext Database, IScheduledTaskStatus Status)
{
if (Context.Records == null)
throw new InvalidOperationException("Import Records have not been parsed");
@@ -178,18 +173,13 @@ namespace Disco.Services.Devices.Importing
if (Context.Records.Count == 0)
throw new InvalidOperationException("There are no records to import");
DateTime nextProgress = DateTime.Now;
Status.UpdateStatus(0, "Applying Import Records to Database", "Starting...");
int affectedRecords = 0;
foreach (var record in Context.Records.Cast<DeviceImportRecord>().Select((r, i) => Tuple.Create(r, i)))
{
if (nextProgress <= DateTime.Now)
{
Status.UpdateStatus(((double)record.Item2 / Context.Records.Count) * 100, string.Format("Applying: {0}", record.Item1.DeviceSerialNumber));
nextProgress = DateTime.Now.AddSeconds(.5);
}
Status.UpdateStatus(((double)record.Item2 / Context.Records.Count) * 100, string.Format("Applying: {0}", record.Item1.DeviceSerialNumber));
if (record.Item1.Apply(Database))
affectedRecords++;
+29 -15
View File
@@ -42,21 +42,21 @@
<Reference Include="LumenWorks.Framework.IO">
<HintPath>..\packages\LumenWorks.Framework.IO.3.8.0\lib\net20\LumenWorks.Framework.IO.dll</HintPath>
</Reference>
<Reference Include="Microsoft.AspNet.SignalR.Core, Version=1.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\Microsoft.AspNet.SignalR.Core.1.1.2\lib\net40\Microsoft.AspNet.SignalR.Core.dll</HintPath>
<Reference Include="Microsoft.AspNet.SignalR.Core">
<HintPath>..\packages\Microsoft.AspNet.SignalR.Core.2.0.3\lib\net45\Microsoft.AspNet.SignalR.Core.dll</HintPath>
</Reference>
<Reference Include="Microsoft.AspNet.SignalR.Owin, Version=1.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\Microsoft.AspNet.SignalR.Owin.1.1.2\lib\net45\Microsoft.AspNet.SignalR.Owin.dll</HintPath>
<Reference Include="Microsoft.Owin">
<HintPath>..\packages\Microsoft.Owin.2.0.1\lib\net45\Microsoft.Owin.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Owin.Security">
<HintPath>..\packages\Microsoft.Owin.Security.2.0.1\lib\net45\Microsoft.Owin.Security.dll</HintPath>
</Reference>
<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="Newtonsoft.Json, Version=4.5.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\Newtonsoft.Json.5.0.8\lib\net45\Newtonsoft.Json.dll</HintPath>
<Reference Include="Newtonsoft.Json">
<HintPath>..\packages\Newtonsoft.Json.5.0.1\lib\net45\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="Owin">
<HintPath>..\packages\Owin.1.0\lib\net40\Owin.dll</HintPath>
@@ -201,7 +201,9 @@
<Compile Include="Devices\Importing\Fields\DeviceSerialNumberImportField.cs" />
<Compile Include="Devices\Importing\Fields\ModelIdImportField.cs" />
<Compile Include="Devices\Importing\IDeviceImportCache.cs" />
<Compile Include="Devices\DeviceUpdatesHub.cs" />
<Compile Include="Extensions\DateTimeExtensions.cs" />
<Compile Include="Extensions\RxExtensions.cs" />
<Compile Include="Extensions\StringExtensions.cs" />
<Compile Include="Interop\ActiveDirectory\ActiveDirectory.cs" />
<Compile Include="Interop\ActiveDirectory\ActiveDirectoryContext.cs" />
@@ -227,6 +229,11 @@
<Compile Include="Jobs\JobQueues\JobQueueService.cs" />
<Compile Include="Jobs\JobQueues\JobQueueToken.cs" />
<Compile Include="Jobs\JobLists\ManagedJobList.cs" />
<Compile Include="Jobs\JobUpdatesHub.cs" />
<Compile Include="Jobs\Noticeboards\HeldDeviceItem.cs" />
<Compile Include="Jobs\Noticeboards\NoticeboardUpdatesHub.cs" />
<Compile Include="Jobs\Noticeboards\HeldDevices.cs" />
<Compile Include="Jobs\Noticeboards\HeldDevicesForUsers.cs" />
<Compile Include="Logging\LogBase.cs" />
<Compile Include="Logging\LogContext.cs" />
<Compile Include="Logging\LogReInitalizeJob.cs" />
@@ -236,9 +243,8 @@
<Compile Include="Logging\Models\LogModule.cs" />
<Compile Include="Logging\ReadLogContext.cs" />
<Compile Include="Logging\SystemLog.cs" />
<Compile Include="Logging\Targets\LogLiveContext.cs" />
<Compile Include="Logging\Targets\LogPersistContext.cs" />
<Compile Include="Logging\Targets\LogPersistContextInitializer.cs" />
<Compile Include="Logging\Persistance\LogPersistContext.cs" />
<Compile Include="Logging\Persistance\LogPersistContextInitializer.cs" />
<Compile Include="Logging\Utilities.cs" />
<Compile Include="Plugins\CommunityInterop\PluginLibraryUpdateTask.cs" />
<Compile Include="Plugins\Features\UIExtension\Results\LiteralResult.cs" />
@@ -278,9 +284,10 @@
<Compile Include="Plugins\WebPageHelper.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Searching\Search.cs" />
<Compile Include="Tasks\IScheduledTaskBasicStatus.cs" />
<Compile Include="Tasks\IScheduledTaskStatus.cs" />
<Compile Include="Tasks\ScheduledTask.cs" />
<Compile Include="Tasks\ScheduledTaskMockStatus.cs" />
<Compile Include="Tasks\ScheduledTaskNotificationsHub.cs" />
<Compile Include="Tasks\ScheduledTasks.cs" />
<Compile Include="Tasks\ScheduledTasksLog.cs" />
<Compile Include="Tasks\ScheduledTaskStatus.cs" />
@@ -290,17 +297,24 @@
<Compile Include="Users\CacheCleanTask.cs" />
<Compile Include="Users\UserExtensions.cs" />
<Compile Include="Users\UserService.cs" />
<Compile Include="Users\UserUpdatesHub.cs" />
<Compile Include="Web\AuthorizedController.cs" />
<Compile Include="Web\AuthorizedDatabaseController.cs" />
<Compile Include="Web\BaseController.cs" />
<Compile Include="Web\Bundles\Bundle.cs" />
<Compile Include="Web\Bundles\FileBundle.cs" />
<Compile Include="Web\Bundles\IBundle.cs" />
<Compile Include="Web\Bundles\BundleExtensions.cs" />
<Compile Include="Web\Bundles\BundleHandler.cs" />
<Compile Include="Web\Bundles\BundleModule.cs" />
<Compile Include="Web\Bundles\BundleTable.cs" />
<Compile Include="Web\Bundles\UrlBundle.cs" />
<Compile Include="Web\DatabaseController.cs" />
<Compile Include="Web\HandleErrorAttribute.cs" />
<Compile Include="Web\HelperExtensions.cs" />
<Compile Include="Web\Signalling\DiscoHubAuthorizeAllAttribute.cs" />
<Compile Include="Web\Signalling\DiscoHubAuthorizeAnyAttribute.cs" />
<Compile Include="Web\Signalling\DiscoHubAuthorizeAttribute.cs" />
<Compile Include="Logging\LogNotificationsHub.cs" />
<Compile Include="Web\WebViewPage.cs" />
</ItemGroup>
<ItemGroup>
@@ -328,7 +342,7 @@
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<ProjectExtensions>
<VisualStudio>
<UserProperties BuildVersion_UseGlobalSettings="False" BuildVersion_DetectChanges="False" BuildVersion_BuildAction="Both" BuildVersion_StartDate="2011/7/1" BuildVersion_BuildVersioningStyle="None.DeltaBaseYear.MonthAndDayStamp.TimeStamp" BuildVersion_UpdateAssemblyVersion="True" BuildVersion_UpdateFileVersion="True" />
<UserProperties BuildVersion_UpdateFileVersion="True" BuildVersion_UpdateAssemblyVersion="True" BuildVersion_BuildVersioningStyle="None.DeltaBaseYear.MonthAndDayStamp.TimeStamp" BuildVersion_StartDate="2011/7/1" BuildVersion_BuildAction="Both" BuildVersion_DetectChanges="False" BuildVersion_UseGlobalSettings="False" />
</VisualStudio>
</ProjectExtensions>
<PropertyGroup>
+182
View File
@@ -0,0 +1,182 @@
using System;
using System.Collections.Generic;
using System.Reactive.Concurrency;
using System.Reactive.Disposables;
using System.Reactive.Linq;
namespace Disco
{
public static class RxExtensions
{
#region RxExtensions
public static IObservable<IEnumerable<T>> DelayBuffer<T>(this IObservable<T> source, TimeSpan delay)
{
return Observable.Create<IEnumerable<T>>(o =>
{
var gate = new object();
var buffer = new List<T>();
var trigger = (IDisposable)null;
var subscription = (IDisposable)null;
var scheduler = Scheduler.Default;
Action dump = () =>
{
var bts = buffer.ToArray();
buffer = new List<T>();
if (o != null)
o.OnNext(bts);
if (trigger != null)
{
trigger.Dispose();
trigger = null;
}
};
Action dispose = () =>
{
if (subscription != null)
subscription.Dispose();
if (trigger != null)
{
trigger.Dispose();
trigger = null;
}
};
Action<Action<IObserver<IEnumerable<T>>>> onErrorOrCompleted =
onAction =>
{
lock (gate)
{
dispose();
dump();
if (o != null)
onAction(o);
}
};
Action<Exception> onError = ex =>
onErrorOrCompleted(x => x.OnError(ex));
Action onCompleted = () => onErrorOrCompleted(x => x.OnCompleted());
Action<T> onNext = t =>
{
lock (gate)
{
buffer.Add(t);
if (trigger == null)
{
trigger = scheduler.Schedule(delay, () =>
{
lock (gate)
{
dump();
}
});
}
}
};
subscription =
source
.ObserveOn(scheduler)
.Subscribe(onNext, onError, onCompleted);
return () =>
{
lock (gate)
{
o = null;
dispose();
}
};
});
}
public static IObservable<IEnumerable<T>> BufferWithInactivity<T>(this IObservable<T> source, TimeSpan inactivity)
{
return Observable.Create<IEnumerable<T>>(o =>
{
var gate = new object();
var buffer = new List<T>();
var mutable = new SerialDisposable();
var subscription = (IDisposable)null;
var scheduler = Scheduler.Default;
Action dump = () =>
{
var bts = buffer.ToArray();
buffer = new List<T>();
if (o != null)
{
o.OnNext(bts);
}
};
Action dispose = () =>
{
if (subscription != null)
{
subscription.Dispose();
}
mutable.Dispose();
};
Action<Action<IObserver<IEnumerable<T>>>> onErrorOrCompleted =
onAction =>
{
lock (gate)
{
dispose();
dump();
if (o != null)
{
onAction(o);
}
}
};
Action<Exception> onError = ex =>
onErrorOrCompleted(x => x.OnError(ex));
Action onCompleted = () => onErrorOrCompleted(x => x.OnCompleted());
Action<T> onNext = t =>
{
lock (gate)
{
buffer.Add(t);
mutable.Disposable = scheduler.Schedule(inactivity, () =>
{
lock (gate)
{
dump();
}
});
}
};
subscription =
source
.ObserveOn(scheduler)
.Subscribe(onNext, onError, onCompleted);
return () =>
{
lock (gate)
{
o = null;
dispose();
}
};
});
}
#endregion
}
}
@@ -114,7 +114,7 @@ namespace Disco.Services.Interop.ActiveDirectory
return false;
}
public static void UpdateLastNetworkLogonDates(DiscoDataContext Database, IScheduledTaskBasicStatus status)
public static void UpdateLastNetworkLogonDates(DiscoDataContext Database, IScheduledTaskStatus status)
{
var context = ActiveDirectory.Context;
const string ldapFilter = "(objectCategory=Computer)";
@@ -72,7 +72,7 @@ namespace Disco.Services.Jobs.JobQueues
return _cache.UpdateQueue(JobQueue);
}
public static void DeleteJobQueue(DiscoDataContext Database, int JobQueueId, ScheduledTaskStatus Status)
public static void DeleteJobQueue(DiscoDataContext Database, int JobQueueId, IScheduledTaskStatus Status)
{
JobQueue queue = Database.JobQueues.Find(JobQueueId);
+124
View File
@@ -0,0 +1,124 @@
using Disco.Data.Repository;
using Disco.Data.Repository.Monitor;
using Disco.Models.Repository;
using Disco.Services.Authorization;
using Disco.Services.Web.Signalling;
using Microsoft.AspNet.SignalR;
using Microsoft.AspNet.SignalR.Hubs;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reactive.Linq;
using System.Threading.Tasks;
using Disco.Services.Users;
namespace Disco.Services.Jobs
{
[HubName("jobUpdates"), DiscoHubAuthorize(Claims.Job.Show)]
public class JobUpdatesHub : Hub
{
private const string JobLogsPrefix = "JobLogs_";
private const string JobAttachmentsPrefix = "JobAttachments_";
public static IHubContext HubContext { get; private set; }
private static IDisposable RepositoryBeforeSubscription;
private static IDisposable RepositoryAfterSubscription;
static JobUpdatesHub()
{
HubContext = GlobalHost.ConnectionManager.GetHubContext<JobUpdatesHub>();
// Subscribe to Repository Monitor for Changes
RepositoryBeforeSubscription = RepositoryMonitor.StreamBeforeCommit
.Where(e => (
e.EntityType == typeof(JobLog) && e.EventType == RepositoryMonitorEventType.Deleted ||
e.EntityType == typeof(JobAttachment) && e.EventType == RepositoryMonitorEventType.Deleted
))
.Subscribe(RepositoryEventBefore);
RepositoryAfterSubscription = RepositoryMonitor.StreamAfterCommit
.Where(e => (
e.EntityType == typeof(JobLog) && e.EventType == RepositoryMonitorEventType.Added ||
e.EntityType == typeof(JobAttachment) && e.EventType == RepositoryMonitorEventType.Added
))
.Subscribe(RepositoryAfterEvent);
}
private static string LogsGroupName(int JobId)
{
return JobLogsPrefix + JobId.ToString();
}
private static string AttachmentsGroupName(int JobId)
{
return JobAttachmentsPrefix + JobId.ToString();
}
public override Task OnConnected()
{
int jobId;
string jobIdParam;
jobIdParam = Context.QueryString["JobId"];
if (string.IsNullOrWhiteSpace(jobIdParam))
throw new ArgumentNullException("JobId");
if (!int.TryParse(jobIdParam, out jobId))
throw new ArgumentException("An integer was expected", "JobId");
var userAuth = UserService.GetAuthorization(Context.User.Identity.Name);
if (userAuth.Has(Claims.Job.ShowLogs))
Groups.Add(Context.ConnectionId, LogsGroupName(jobId));
if (userAuth.Has(Claims.Job.ShowAttachments))
Groups.Add(Context.ConnectionId, AttachmentsGroupName(jobId));
return base.OnConnected();
}
private static void RepositoryEventBefore(RepositoryMonitorEvent e)
{
if (e.EventType == RepositoryMonitorEventType.Deleted)
{
if (e.EntityType == typeof(JobLog))
{
var repositoryLog = (JobLog)e.Entity;
int logJobId;
using (DiscoDataContext Database = new DiscoDataContext())
logJobId = Database.JobLogs.Where(l => l.Id == repositoryLog.Id).Select(a => a.JobId).First();
HubContext.Clients.Group(LogsGroupName(logJobId)).removeLog(repositoryLog.Id);
}
if (e.EntityType == typeof(JobAttachment))
{
var repositoryAttachment = (JobAttachment)e.Entity;
int attachmentJobId;
using (DiscoDataContext Database = new DiscoDataContext())
attachmentJobId = Database.JobAttachments.Where(a => a.Id == repositoryAttachment.Id).Select(a => a.JobId).First();
HubContext.Clients.Group(AttachmentsGroupName(attachmentJobId)).removeAttachment(repositoryAttachment.Id);
}
}
}
private static void RepositoryAfterEvent(RepositoryMonitorEvent e)
{
if (e.EventType == RepositoryMonitorEventType.Added)
{
if (e.EntityType == typeof(JobLog))
{
var a = (JobLog)e.Entity;
HubContext.Clients.Group(LogsGroupName(a.JobId)).addLog(a.Id);
}
if (e.EntityType == typeof(JobAttachment))
{
var a = (JobAttachment)e.Entity;
HubContext.Clients.Group(AttachmentsGroupName(a.JobId)).addAttachment(a.Id);
}
}
}
}
}
@@ -0,0 +1,137 @@
using Disco.Data.Configuration.Modules;
using Disco.Models.Repository;
using Disco.Models.Services.Jobs.Noticeboards;
using Disco.Services.Interop.ActiveDirectory;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Disco.Services.Jobs.Noticeboards
{
public class HeldDeviceItem : IHeldDeviceItem
{
public int JobId { get; set; }
public string DeviceSerialNumber { get; set; }
public string DeviceComputerNameFriendly
{
get
{
return DeviceComputerName == null ? null : UserExtensions.FriendlyUserId(DeviceComputerName);
}
}
public string DeviceComputerName { get; set; }
public string DeviceLocation { get; set; }
public string DeviceDescription
{
get
{
StringBuilder sb = new StringBuilder(this.DeviceComputerNameFriendly);
if (UserId != null)
sb.Append(" - ").Append(this.UserDisplayName).Append(" (").Append(this.UserIdFriendly).Append(")");
if (!string.IsNullOrWhiteSpace(this.DeviceLocation))
sb.Append(" - ").Append(this.DeviceLocation);
else if (UserId == null)
sb.Append(" - ").Append(this.DeviceSerialNumber);
return sb.ToString();
}
}
public int DeviceProfileId { get; set; }
public int? DeviceAddressId { get; set; }
public string DeviceAddressShortName
{
get
{
if (DeviceAddressId.HasValue)
{
var config = new OrganisationAddressesConfiguration(null);
var address = config.GetAddress(DeviceAddressId.Value);
if (address != null)
return address.ShortName;
}
return null;
}
}
public string UserId { get; set; }
public string UserIdFriendly
{
get
{
return UserId == null ? null : UserExtensions.FriendlyUserId(UserId);
}
}
public string UserDisplayName { get; set; }
public bool WaitingForUserAction { get; set; }
public DateTime? WaitingForUserActionSince { get; set; }
public long? WaitingForUserActionSinceUnixEpoc
{
get
{
return WaitingForUserActionSince.ToUnixEpoc();
}
}
public bool ReadyForReturn { get; set; }
public DateTime? EstimatedReturnTime { get; set; }
public long? EstimatedReturnTimeUnixEpoc
{
get
{
return EstimatedReturnTime.ToUnixEpoc();
}
}
public DateTime? ReadyForReturnSince { get; set; }
public long? ReadyForReturnSinceUnixEpoc
{
get
{
return ReadyForReturnSince.ToUnixEpoc();
}
}
public bool IsAlert
{
get
{
if (this.ReadyForReturn && (this.ReadyForReturnSince.Value < DateTime.Now.AddDays(-3)))
return true;
if (this.WaitingForUserAction && (this.WaitingForUserActionSince.Value < DateTime.Now.AddDays(-6)))
return true;
return false;
}
}
internal static IEnumerable<HeldDeviceItem> FromJobs(IQueryable<Job> jobs)
{
return jobs.Select(j =>
new HeldDeviceItem
{
JobId = j.Id,
DeviceSerialNumber = j.DeviceSerialNumber,
DeviceComputerName = j.Device.DeviceDomainId,
DeviceLocation = j.Device.Location,
DeviceProfileId = j.Device.DeviceProfileId,
DeviceAddressId = j.Device.DeviceProfile.DefaultOrganisationAddress,
UserId = j.Device.AssignedUserId,
UserDisplayName = j.Device.AssignedUser.DisplayName,
WaitingForUserAction = j.WaitingForUserAction.HasValue || ((j.JobMetaNonWarranty.AccountingChargeRequiredDate.HasValue || j.JobMetaNonWarranty.AccountingChargeAddedDate.HasValue) && !j.JobMetaNonWarranty.AccountingChargePaidDate.HasValue),
WaitingForUserActionSince = j.WaitingForUserAction.HasValue ? j.WaitingForUserAction : (j.JobMetaNonWarranty.AccountingChargeRequiredDate.HasValue ? j.JobMetaNonWarranty.AccountingChargeRequiredDate : j.JobMetaNonWarranty.AccountingChargeAddedDate),
ReadyForReturn = j.DeviceReadyForReturn.HasValue && !j.DeviceReturnedDate.HasValue,
EstimatedReturnTime = j.ExpectedClosedDate,
ReadyForReturnSince = j.DeviceReadyForReturn
});
}
}
}
@@ -0,0 +1,239 @@
using Disco.Data.Repository;
using Disco.Data.Repository.Monitor;
using Disco.Models.Repository;
using Disco.Models.Services.Jobs.Noticeboards;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
namespace Disco.Services.Jobs.Noticeboards
{
public static class HeldDevices
{
public const string Name = "HeldDevices";
private readonly static List<string> MonitorJobProperties = new List<string>() {
"DeviceSerialNumber",
"UserId",
"ExpectedClosedDate",
"ClosedDate",
"WaitingForUserAction",
"DeviceHeld",
"DeviceReadyForReturn",
"DeviceReturnedDate"
};
private readonly static List<string> MonitorJobMetaNonWarrantyProperties = new List<string>(){
"AccountingChargeRequiredDate",
"AccountingChargeAddedDate",
"AccountingChargePaidDate"
};
private readonly static List<string> MonitorDeviceProperties = new List<string>(){
"Location",
"DeviceProfileId",
"DeviceDomainId",
"AssignedUserId",
};
private readonly static List<string> MonitorDeviceProfileProperties = new List<string>(){
"DefaultOrganisationAddress"
};
private readonly static List<string> MonitorUserProperties = new List<string>(){
"DisplayName"
};
static HeldDevices()
{
// Subscribe to Repository Notifications
RepositoryMonitor.StreamAfterCommit.Where(e =>
(e.EntityType == typeof(Job) &&
(e.EventType == RepositoryMonitorEventType.Added ||
e.EventType == RepositoryMonitorEventType.Deleted ||
(e.EventType == RepositoryMonitorEventType.Modified && e.ModifiedProperties.Any(p => MonitorJobProperties.Contains(p))))
) ||
(e.EntityType == typeof(JobMetaNonWarranty) &&
(e.EventType == RepositoryMonitorEventType.Added ||
e.EventType == RepositoryMonitorEventType.Deleted ||
(e.EventType == RepositoryMonitorEventType.Modified && e.ModifiedProperties.Any(p => MonitorJobMetaNonWarrantyProperties.Contains(p))))
) ||
(e.EntityType == typeof(Device) &&
(e.EventType == RepositoryMonitorEventType.Modified && e.ModifiedProperties.Any(p => MonitorDeviceProperties.Contains(p)))
) ||
(e.EntityType == typeof(DeviceProfile) &&
(e.EventType == RepositoryMonitorEventType.Modified && e.ModifiedProperties.Any(p => MonitorDeviceProfileProperties.Contains(p)))
) ||
(e.EntityType == typeof(User) &&
(e.EventType == RepositoryMonitorEventType.Modified && e.ModifiedProperties.Any(p => MonitorUserProperties.Contains(p)))
)
)
.DelayBuffer(TimeSpan.FromMilliseconds(500))
.Subscribe(RepositoryEvent);
}
private static void RepositoryEvent(IEnumerable<RepositoryMonitorEvent> e)
{
List<string> deviceSerialNumbers = new List<string>();
List<string> userIds = new List<string>();
using (DiscoDataContext Database = new DiscoDataContext())
{
foreach (var i in e)
{
if (i.EntityType == typeof(Job))
{
if (i.EventType == RepositoryMonitorEventType.Modified &&
i.ModifiedProperties.Contains("DeviceSerialNumber"))
{
var p = i.GetPreviousPropertyValue<string>("DeviceSerialNumber");
if (p != null)
deviceSerialNumbers.Add(p);
}
var j = (Job)i.Entity;
if (j.DeviceSerialNumber != null)
deviceSerialNumbers.Add(j.DeviceSerialNumber);
}
else if (i.EntityType == typeof(JobMetaNonWarranty))
{
var jmnw = (JobMetaNonWarranty)i.Entity;
if (jmnw.Job != null)
{
if (jmnw.Job.DeviceSerialNumber != null)
deviceSerialNumbers.Add(jmnw.Job.DeviceSerialNumber);
}
else
{
var sn = Database.Jobs.Where(j => j.Id == jmnw.JobId).Select(j => j.DeviceSerialNumber).FirstOrDefault();
if (sn != null)
deviceSerialNumbers.Add(sn);
}
}
else if (i.EntityType == typeof(Device))
{
var d = (Device)i.Entity;
deviceSerialNumbers.Add(d.SerialNumber);
if (i.EventType == RepositoryMonitorEventType.Modified &&
i.ModifiedProperties.Contains("AssignedUserId"))
{
var p = i.GetPreviousPropertyValue<string>("AssignedUserId");
if (p != null)
userIds.Add(p);
}
}
else if (i.EntityType == typeof(DeviceProfile))
{
var dp = (DeviceProfile)i.Entity;
deviceSerialNumbers.AddRange(
Database.Jobs
.Where(j => !j.ClosedDate.HasValue && j.Device.DeviceProfileId == dp.Id)
.Select(j => j.DeviceSerialNumber)
);
}
else if (i.EntityType == typeof(User))
{
var u = (User)i.Entity;
deviceSerialNumbers.AddRange(
Database.Jobs
.Where(j => !j.ClosedDate.HasValue && j.Device.AssignedUserId == u.UserId)
.Select(j => j.DeviceSerialNumber)
);
}
}
deviceSerialNumbers = deviceSerialNumbers.Distinct().ToList();
// Determine Held Devices for Users
userIds.AddRange(
Database.Devices
.Where(d => d.AssignedUserId != null && deviceSerialNumbers.Contains(d.SerialNumber))
.Select(d => d.AssignedUserId)
);
userIds = userIds.Distinct().ToList();
// Notify Held Devices
HeldDevices.BroadcastUpdates(Database, deviceSerialNumbers);
// Notify Held Devices for Users
HeldDevicesForUsers.BroadcastUpdates(Database, userIds);
}
}
internal static void BroadcastUpdates(DiscoDataContext Database, List<string> DeviceSerialNumbers)
{
var jobs = Database.Jobs.Where(j => DeviceSerialNumbers.Contains(j.DeviceSerialNumber));
var items = GetHeldDevices(jobs).ToDictionary(i => i.DeviceSerialNumber, StringComparer.OrdinalIgnoreCase);
for (int skipAmount = 0; skipAmount < DeviceSerialNumbers.Count; skipAmount = skipAmount + 30)
{
var updates = DeviceSerialNumbers
.Skip(skipAmount).Take(30)
.ToDictionary(dsn => dsn,
dsn => {
IHeldDeviceItem item;
items.TryGetValue(dsn, out item);
return item;
});
NoticeboardUpdatesHub.HubContext.Clients
.Group(HeldDevices.Name)
.updateHeldDevice(updates);
}
}
private static IEnumerable<IHeldDeviceItem> GetHeldDevices(IQueryable<Job> query)
{
var jobs = query
.Where(j =>
!j.ClosedDate.HasValue &&
j.DeviceSerialNumber != null &&
((j.DeviceHeld.HasValue && !j.DeviceReturnedDate.HasValue) || j.WaitingForUserAction.HasValue)
)
.SelectHeldDeviceItems()
.GroupBy(j => j.DeviceSerialNumber);
foreach (var job in jobs.ToList())
{
if (job.Any(j => j.WaitingForUserAction))
{
var item = job.Where(j => j.WaitingForUserAction).OrderBy(j => j.WaitingForUserActionSince).First();
yield return item;
}
else
{
if (job.All(j => j.ReadyForReturn))
{
var item = job.OrderByDescending(j => j.ReadyForReturnSince).First();
yield return item;
}
else
{
var item = job.Where(j => !j.ReadyForReturn).OrderByDescending(j => j.EstimatedReturnTime).First();
yield return item;
}
}
}
}
public static IEnumerable<IHeldDeviceItem> GetHeldDevices(DiscoDataContext Database)
{
return GetHeldDevices(Database.Jobs);
}
public static IHeldDeviceItem GetHeldDevice(DiscoDataContext Database, string DeviceSerialNumber)
{
return GetHeldDevices(Database.Jobs.Where(j => j.DeviceSerialNumber == DeviceSerialNumber)).FirstOrDefault();
}
internal static IEnumerable<HeldDeviceItem> SelectHeldDeviceItems(this IQueryable<Job> jobs)
{
return HeldDeviceItem.FromJobs(jobs);
}
}
}
@@ -0,0 +1,92 @@
using Disco.Data.Repository;
using Disco.Models.Repository;
using Disco.Models.Services.Jobs.Noticeboards;
using Disco.Services.Interop.ActiveDirectory;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Disco.Services.Jobs.Noticeboards
{
public class HeldDevicesForUsers
{
public const string Name = "HeldDevicesForUsers";
// NOTE: Calculation for updates is performed by HeldDevices to avoid duplication
internal static void BroadcastUpdates(DiscoDataContext Database, List<string> UserIds)
{
var jobs = Database.Devices.Where(d => UserIds.Contains(d.AssignedUserId)).SelectMany(d => d.Jobs);
var items = GetHeldDevicesForUsers(jobs).ToDictionary(i => i.UserId, StringComparer.OrdinalIgnoreCase);
for (int skipAmount = 0; skipAmount < UserIds.Count; skipAmount = skipAmount + 30)
{
var updates = UserIds
.Skip(skipAmount).Take(30)
.ToDictionary(userId => userId,
userId =>
{
IHeldDeviceItem item;
items.TryGetValue(userId, out item);
return item;
});
NoticeboardUpdatesHub.HubContext.Clients
.Group(HeldDevicesForUsers.Name)
.updateHeldDeviceForUser(updates);
}
}
private static IEnumerable<IHeldDeviceItem> GetHeldDevicesForUsers(IQueryable<Job> query)
{
var jobs = query
.Where(j =>
!j.ClosedDate.HasValue &&
j.DeviceSerialNumber != null &&
j.Device.AssignedUserId != null &&
((j.DeviceHeld.HasValue && !j.DeviceReturnedDate.HasValue) || j.WaitingForUserAction.HasValue)
)
.SelectHeldDeviceItems()
.GroupBy(j => j.UserId);
foreach (var job in jobs.ToList())
{
if (job.Any(j => j.WaitingForUserAction))
{
var item = job.Where(j => j.WaitingForUserAction).OrderBy(j => j.WaitingForUserActionSince).First();
yield return item;
}
else
{
if (job.All(j => j.ReadyForReturn))
{
var item = job.OrderByDescending(j => j.ReadyForReturnSince).First();
yield return item;
}
else
{
var item = job.Where(j => !j.ReadyForReturn).OrderByDescending(j => j.EstimatedReturnTime).First();
yield return item;
}
}
}
}
public static IEnumerable<IHeldDeviceItem> GetHeldDevicesForUsers(DiscoDataContext Database)
{
return GetHeldDevicesForUsers(Database.Jobs);
}
public static IHeldDeviceItem GetHeldDeviceForUsers(DiscoDataContext Database, string UserId)
{
var split = UserExtensions.SplitUserId(UserId);
if (split.Item1 == null)
UserId = string.Format(@"{0}\{1}", ActiveDirectory.Context.PrimaryDomain.NetBiosName, UserId);
return GetHeldDevicesForUsers(Database.Devices.Where(d => d.AssignedUserId == UserId).SelectMany(d => d.Jobs)).FirstOrDefault();
}
}
}
@@ -0,0 +1,40 @@
using Microsoft.AspNet.SignalR;
using Microsoft.AspNet.SignalR.Hubs;
using System;
using System.Threading.Tasks;
namespace Disco.Services.Jobs.Noticeboards
{
[HubName("noticeboardUpdates")] // Public
public class NoticeboardUpdatesHub : Hub
{
public static IHubContext HubContext { get; private set; }
static NoticeboardUpdatesHub()
{
HubContext = GlobalHost.ConnectionManager.GetHubContext<NoticeboardUpdatesHub>();
}
public override Task OnConnected()
{
var noticeboardId = Context.QueryString["Noticeboard"];
if (string.IsNullOrWhiteSpace(noticeboardId))
throw new ArgumentNullException("Noticeboard");
switch (noticeboardId)
{
case HeldDevicesForUsers.Name:
Groups.Add(Context.ConnectionId, HeldDevicesForUsers.Name);
break;
case HeldDevices.Name:
Groups.Add(Context.ConnectionId, HeldDevices.Name);
break;
default:
throw new ArgumentException("Invalid Noticeboard Specified", "Noticeboard");
}
return base.OnConnected();
}
}
}
+6 -6
View File
@@ -70,7 +70,7 @@ namespace Disco.Services.Logging
}
}
private static void InitalizeDatabase(Targets.LogPersistContext LogDatabase)
private static void InitalizeDatabase(Persistance.LogPersistContext LogDatabase)
{
// Add Modules
var existingModules = LogDatabase.Modules.Include("EventTypes").ToDictionary(m => m.Id);
@@ -183,7 +183,7 @@ namespace Disco.Services.Logging
if (!File.Exists(logPath))
{
// Create Database
using (var context = new Targets.LogPersistContext(connectionString))
using (var context = new Persistance.LogPersistContext(connectionString))
{
context.Database.CreateIfNotExists();
}
@@ -191,7 +191,7 @@ namespace Disco.Services.Logging
// Add Modules/Event Types
InitalizeModules();
using (var context = new Targets.LogPersistContext(connectionString))
using (var context = new Persistance.LogPersistContext(connectionString))
{
InitalizeDatabase(context);
}
@@ -211,7 +211,7 @@ namespace Disco.Services.Logging
sqlCeCSB.DataSource = yesterdaysLogPath;
var connectionString = sqlCeCSB.ToString();
int logCount;
using (var context = new Targets.LogPersistContext(connectionString))
using (var context = new Persistance.LogPersistContext(connectionString))
{
logCount = context.Events.Where(e => !(e.ModuleId == 0 && e.EventTypeId == 100)).Count();
if (logCount == 0)
@@ -267,7 +267,7 @@ namespace Disco.Services.Logging
var eventTimestamp = DateTime.Now;
if (eventType.UseLive)
{
Targets.LogLiveContext.Broadcast(logModule, eventType, eventTimestamp, Args);
LogNotificationsHub.BroadcastLog(logModule, eventType, eventTimestamp, Args);
}
if (eventType.UsePersist)
{
@@ -276,7 +276,7 @@ namespace Disco.Services.Logging
{
args = JsonConvert.SerializeObject(Args);
}
using (var context = new Targets.LogPersistContext(PersistantStoreConnectionString))
using (var context = new Persistance.LogPersistContext(PersistantStoreConnectionString))
{
var e = new Models.LogEvent()
{
@@ -0,0 +1,67 @@
using Disco.Services.Authorization;
using Disco.Services.Logging.Models;
using Disco.Services.Web.Signalling;
using Microsoft.AspNet.SignalR;
using Microsoft.AspNet.SignalR.Hubs;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Disco.Services.Logging
{
[HubName("logNotifications"), DiscoHubAuthorize(Claims.Config.Logging.Show)]
public class LogNotificationsHub : Hub
{
private const string NotificationsModulePrefix = "Logging_";
public const string AllLoggingNotification = NotificationsModulePrefix + "_ALL";
public override Task OnConnected()
{
var logModules = Context.QueryString["LogModules"];
if (!string.IsNullOrWhiteSpace(logModules) && logModules.Length > 0)
{
foreach (var modules in ValidLogModuleGroupNames(logModules))
Groups.Add(Context.ConnectionId, modules);
}
return base.OnConnected();
}
internal static void BroadcastLog(LogBase logModule, LogEventType eventType, DateTime Timestamp, params object[] Arguments)
{
var message = LogLiveEvent.Create(logModule, eventType, Timestamp, Arguments);
var connectionManager = GlobalHost.ConnectionManager;
var context = connectionManager.GetHubContext<LogNotificationsHub>();
var targets = new List<string> { AllLoggingNotification, NotificationsModulePrefix + logModule.ModuleName };
context.Clients.Groups(targets).receiveLog(message);
}
private IEnumerable<string> ValidLogModuleGroupNames(string ModuleNames)
{
if (string.IsNullOrWhiteSpace(ModuleNames))
yield break;
var names = ModuleNames.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
foreach (var name in names)
{
// Special Case: __ALL
if (name.Equals(AllLoggingNotification, StringComparison.OrdinalIgnoreCase))
{
yield return AllLoggingNotification;
}
else
{
var module = LogContext.LogModules.Values.FirstOrDefault(m => m.ModuleName.Equals(name, StringComparison.OrdinalIgnoreCase));
if (module == null)
throw new ArgumentException(string.Format("Invalid Module Name specified: {0}", name), "ModuleNames");
yield return NotificationsModulePrefix + module.ModuleName;
}
}
}
}
}
@@ -5,7 +5,7 @@ using System.Text;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
namespace Disco.Services.Logging.Targets
namespace Disco.Services.Logging.Persistance
{
public class LogPersistContext : DbContext
{
@@ -5,7 +5,7 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Disco.Services.Logging.Targets
namespace Disco.Services.Logging.Persistance
{
public class LogPersistContextInitializer : IDatabaseInitializer<LogPersistContext>
{
+9 -10
View File
@@ -1,13 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Disco.Services.Logging.Targets;
using Disco.Data.Repository;
using System.IO;
using System.Text.RegularExpressions;
using System.Data.SqlServerCe;
using Disco.Data.Repository;
using Disco.Services.Logging.Models;
using Disco.Services.Logging.Persistance;
using System;
using System.Collections.Generic;
using System.Data.SqlServerCe;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
namespace Disco.Services.Logging
{
@@ -45,7 +44,7 @@ namespace Disco.Services.Logging
var logModules = LogContext.LogModules;
using (var context = new Targets.LogPersistContext(sqlCeCSB.ToString()))
using (var context = new LogPersistContext(sqlCeCSB.ToString()))
{
var query = this.BuildQuery(context, logFile.Item2, results.Count);
IEnumerable<LogEvent> queryResults = query; // Run the Query
@@ -1,21 +0,0 @@
using Disco.Services.Logging.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Disco.Services.Logging.Targets
{
public static class LogLiveContext
{
public delegate void LogBroadcastEvent(LogBase logModule, LogEventType eventType, DateTime Timestamp, params object[] Arguments);
public static event LogBroadcastEvent LogBroadcast;
internal static void Broadcast(LogBase logModule, LogEventType eventType, DateTime Timestamp, params object[] Arguments)
{
if (LogBroadcast != null)
LogBroadcast.Invoke(logModule, eventType, Timestamp, Arguments);
}
}
}
@@ -21,11 +21,11 @@ namespace Disco.Services.Plugins.Features.UIExtension.Results
this._resource = Resource;
this._resourceUrl = Source.PluginManifest.WebResourceUrl(Resource);
var deferredBundles = HttpContext.Current.Items[Bundle.UIExtensionCssKey] as List<string>;
var deferredBundles = HttpContext.Current.Items[BundleTable.UIExtensionCssKey] as List<string>;
if (deferredBundles == null)
{
deferredBundles = new List<string>();
HttpContext.Current.Items[Bundle.UIExtensionCssKey] = deferredBundles;
HttpContext.Current.Items[BundleTable.UIExtensionCssKey] = deferredBundles;
}
if (!deferredBundles.Contains(this._resourceUrl))
deferredBundles.Add(this._resourceUrl);
@@ -23,11 +23,11 @@ namespace Disco.Services.Plugins.Features.UIExtension.Results
if (this._placeInPageHead)
{
var deferredBundles = HttpContext.Current.Items[Bundle.UIExtensionScriptsKey] as List<string>;
var deferredBundles = HttpContext.Current.Items[BundleTable.UIExtensionScriptsKey] as List<string>;
if (deferredBundles == null)
{
deferredBundles = new List<string>();
HttpContext.Current.Items[Bundle.UIExtensionScriptsKey] = deferredBundles;
HttpContext.Current.Items[BundleTable.UIExtensionScriptsKey] = deferredBundles;
}
if (!deferredBundles.Contains(this._resourceUrl))
deferredBundles.Add(this._resourceUrl);
+9 -7
View File
@@ -97,12 +97,12 @@ namespace Disco.Services.Plugins
public static void IncludeStyleSheetResource(this HttpContextBase Context, string Resource, PluginManifest manifest)
{
var resourceUrl = manifest.WebResourceUrl(Resource);
var deferredBundles = Context.Items[Bundle.UIExtensionCssKey] as List<string>;
var deferredBundles = Context.Items[BundleTable.UIExtensionCssKey] as List<string>;
if (deferredBundles == null)
{
deferredBundles = new List<string>();
HttpContext.Current.Items[Bundle.UIExtensionCssKey] = deferredBundles;
HttpContext.Current.Items[BundleTable.UIExtensionCssKey] = deferredBundles;
}
if (!deferredBundles.Contains(resourceUrl))
deferredBundles.Add(resourceUrl);
@@ -111,11 +111,11 @@ namespace Disco.Services.Plugins
{
var resourcePath = manifest.WebResourceUrl(Resource);
var deferredBundles = Context.Items[Bundle.UIExtensionScriptsKey] as List<string>;
var deferredBundles = Context.Items[BundleTable.UIExtensionScriptsKey] as List<string>;
if (deferredBundles == null)
{
deferredBundles = new List<string>();
HttpContext.Current.Items[Bundle.UIExtensionScriptsKey] = deferredBundles;
HttpContext.Current.Items[BundleTable.UIExtensionScriptsKey] = deferredBundles;
}
if (!deferredBundles.Contains(resourcePath))
deferredBundles.Add(resourcePath);
@@ -223,7 +223,9 @@ namespace Disco.Services.Plugins
var routeValues = new RouteValueDictionary(new { PluginId = manifest.Id, PluginAction = PluginAction });
string pluginActionUrl = UrlHelper.GenerateUrl("Plugin", null, null, routeValues, RouteTable.Routes, ViewPage.ViewContext.RequestContext, false);
#pragma warning disable 618
return ViewPage.FormHelper(pluginActionUrl, method, htmlAttributes);
#pragma warning restore 618
}
[Obsolete("Inherit ViewPages from 'Disco.Services.Plugins.WebViewPage' instead.")]
public static MvcForm DiscoPluginActionBeginForm<T>(this WebViewPage<T> ViewPage, string PluginAction, FormMethod method)
@@ -302,11 +304,11 @@ namespace Disco.Services.Plugins
HtmlString pluginResourceUrlHtml = new HtmlString(pluginResourceUrl);
var deferredBundles = RequestContext.HttpContext.Items[Bundle.UIExtensionCssKey] as List<HtmlString>;
var deferredBundles = RequestContext.HttpContext.Items[BundleTable.UIExtensionCssKey] as List<HtmlString>;
if (deferredBundles == null)
{
deferredBundles = new List<HtmlString>();
HttpContext.Current.Items[Bundle.UIExtensionCssKey] = deferredBundles;
HttpContext.Current.Items[BundleTable.UIExtensionCssKey] = deferredBundles;
}
if (!deferredBundles.Contains(pluginResourceUrlHtml))
deferredBundles.Add(pluginResourceUrlHtml);
@@ -6,12 +6,22 @@ using System.Threading.Tasks;
namespace Disco.Services.Tasks
{
public interface IScheduledTaskBasicStatus
public interface IScheduledTaskStatus
{
byte Progress { get; }
string CurrentProcess { get; }
string CurrentDescription { get; }
bool IgnoreCurrentProcessChanges { get; set; }
bool IgnoreCurrentDescription { get; set; }
double ProgressMultiplier { get; set; }
byte ProgressOffset { get; set; }
string FinishedMessage { get; }
string FinishedUrl { get; }
Exception TaskException { get; }
void UpdateStatus(byte Progress);
void UpdateStatus(double Progress);
void UpdateStatus(string CurrentDescription);
@@ -19,5 +29,13 @@ namespace Disco.Services.Tasks
void UpdateStatus(double Progress, string CurrentDescription);
void UpdateStatus(byte Progress, string CurrentProcess, string CurrentDescription);
void UpdateStatus(double Progress, string CurrentProcess, string CurrentDescription);
void SetFinishedUrl(string FinishedUrl);
void SetFinishedMessage(string FinishedMessage);
void Finished();
void Finished(string FinishedMessage);
void Finished(string FinishedMessage, string FinishedUrl);
void SetTaskException(Exception TaskException);
}
}
+1 -1
View File
@@ -16,8 +16,8 @@ namespace Disco.Services.Tasks
public virtual bool CancelInitiallySupported { get { return true; } }
public virtual bool SingleInstanceTask { get { return true; } }
public virtual bool IsSilent { get { return false; } }
public virtual bool LogExceptionsOnly { get { return false; } }
public abstract string TaskName { get; }
protected abstract void ExecuteTask();
+58 -14
View File
@@ -6,19 +6,30 @@ using System.Threading.Tasks;
namespace Disco.Services.Tasks
{
public class ScheduledTaskMockStatus : IScheduledTaskBasicStatus
public class ScheduledTaskMockStatus : IScheduledTaskStatus
{
private byte progress;
private string currentProcess;
private string currentDescription;
public byte Progress { get; set; }
public string CurrentProcess { get; set; }
public string CurrentDescription { get; set; }
public byte Progress { get { return this.progress; } }
public string CurrentProcess { get { return this.currentProcess; } }
public string CurrentDescription { get { return this.currentDescription; } }
public bool IgnoreCurrentProcessChanges { get; set; }
public bool IgnoreCurrentDescription { get; set; }
public double ProgressMultiplier { get; set; }
public byte ProgressOffset { get; set; }
public string FinishedMessage { get; set; }
public string FinishedUrl { get; set; }
public Exception TaskException { get; set; }
private byte CalculateProgressValue(byte Progress)
{
return (byte)((Progress * this.ProgressMultiplier) + this.ProgressOffset);
}
public void UpdateStatus(byte Progress)
{
this.progress = Progress;
this.Progress = CalculateProgressValue(Progress);
}
public void UpdateStatus(double Progress)
{
@@ -26,12 +37,14 @@ namespace Disco.Services.Tasks
}
public void UpdateStatus(string CurrentDescription)
{
this.currentDescription = CurrentDescription;
if (!IgnoreCurrentDescription)
this.CurrentDescription = CurrentDescription;
}
public void UpdateStatus(byte Progress, string CurrentDescription)
{
this.progress = Progress;
this.currentDescription = CurrentDescription;
this.Progress = CalculateProgressValue(Progress);
if (!IgnoreCurrentDescription)
this.CurrentDescription = CurrentDescription;
}
public void UpdateStatus(double Progress, string CurrentDescription)
{
@@ -39,15 +52,46 @@ namespace Disco.Services.Tasks
}
public void UpdateStatus(byte Progress, string CurrentProcess, string CurrentDescription)
{
this.progress = Progress;
this.currentProcess = CurrentProcess;
this.currentDescription = CurrentDescription;
this.Progress = CalculateProgressValue(Progress);
if (!IgnoreCurrentProcessChanges)
this.CurrentProcess = CurrentProcess;
if (!IgnoreCurrentDescription)
this.CurrentDescription = CurrentDescription;
}
public void UpdateStatus(double Progress, string CurrentProcess, string CurrentDescription)
{
UpdateStatus((byte)Progress, CurrentProcess, CurrentDescription);
}
public void SetFinishedUrl(string FinishedUrl)
{
this.FinishedUrl = FinishedUrl;
}
public void SetFinishedMessage(string FinishedMessage)
{
this.FinishedMessage = FinishedMessage;
}
public void Finished()
{
Finished(this.FinishedMessage, this.FinishedUrl);
}
public void Finished(string FinishedMessage)
{
Finished(FinishedMessage, this.FinishedUrl);
}
public void Finished(string FinishedMessage, string FinishedUrl)
{
this.FinishedMessage = FinishedMessage;
this.FinishedUrl = FinishedUrl;
}
public void SetTaskException(Exception TaskException)
{
this.TaskException = TaskException;
}
public static ScheduledTaskMockStatus Create()
{
return new ScheduledTaskMockStatus();
@@ -0,0 +1,89 @@
using Disco.Services.Web.Signalling;
using Microsoft.AspNet.SignalR;
using Microsoft.AspNet.SignalR.Hubs;
using System;
using System.Collections.Generic;
using System.Reactive.Linq;
using System.Linq;
namespace Disco.Services.Tasks
{
using System.Reactive.Subjects;
using ChangedItem = KeyValuePair<string, object>;
[HubName("scheduledTaskNotifications"), DiscoHubAuthorize()]
public class ScheduledTaskNotificationsHub : Hub
{
private const string NotificationsPrefix = "Logging_";
private static Subject<Tuple<string, IEnumerable<ChangedItem>>> taskUpdatesStream = new Subject<Tuple<string, IEnumerable<ChangedItem>>>();
private static IDisposable taskUpdatesStreamSubscription;
internal static void Initialize()
{
if (taskUpdatesStreamSubscription == null)
{
lock (taskUpdatesStream)
{
if (taskUpdatesStreamSubscription == null)
{
taskUpdatesStreamSubscription = taskUpdatesStream
.DelayBuffer(TimeSpan.FromMilliseconds(200))
.Subscribe(BroadcastBufferedEvents);
}
}
}
}
internal static void PublishEvent(string TaskSessionId, IEnumerable<ChangedItem> ChangedItems)
{
taskUpdatesStream.OnNext(Tuple.Create(TaskSessionId, ChangedItems));
}
public override System.Threading.Tasks.Task OnConnected()
{
var taskSessionId = Context.QueryString["TaskSessionId"];
if (string.IsNullOrEmpty(taskSessionId))
throw new ArgumentNullException("TaskSessionId");
var status = ScheduledTasks.GetTaskStatus(taskSessionId);
if (status == null)
throw new ArgumentException("Invalid ScheduledTask SessionId", "TaskSessionId");
// Send Status:
var currentStatus = ScheduledTaskStatusLive.FromScheduledTaskStatus(status, null);
Clients.Caller.initializeTaskStatus(currentStatus);
// Add to Group
var groupName = NotificationsPrefix + taskSessionId;
Groups.Add(Context.ConnectionId, groupName);
return base.OnConnected();
}
private static void BroadcastBufferedEvents(IEnumerable<Tuple<string, IEnumerable<ChangedItem>>> Events)
{
var connectionManager = GlobalHost.ConnectionManager;
var context = connectionManager.GetHubContext<ScheduledTaskNotificationsHub>();
var taskStatusEvents = Events.GroupBy(e => e.Item1).Select(taskEventsGroup =>
{
Dictionary<string, object> changes = new Dictionary<string, object>();
foreach (var changeEvents in taskEventsGroup.Select(taskEvents => taskEvents.Item2))
foreach (var changeEvent in changeEvents)
changes[changeEvent.Key] = changeEvent.Value;
return Tuple.Create(taskEventsGroup.Key, changes);
});
foreach (var taskStatusEvent in taskStatusEvents)
{
var groupName = NotificationsPrefix + taskStatusEvent.Item1;
context.Clients.Group(groupName).updateTaskStatus(taskStatusEvent.Item2);
}
}
}
}
+109 -62
View File
@@ -9,7 +9,9 @@ using System.Threading.Tasks;
namespace Disco.Services.Tasks
{
public class ScheduledTaskStatus : IScheduledTaskBasicStatus
using ChangedItem = KeyValuePair<string, object>;
public class ScheduledTaskStatus : IScheduledTaskStatus
{
#region Backing Fields
@@ -52,6 +54,11 @@ namespace Disco.Services.Tasks
public string CurrentProcess { get { return this._currentProcess; } }
public string CurrentDescription { get { return this._currentDescription; } }
public bool IgnoreCurrentProcessChanges { get; set; }
public bool IgnoreCurrentDescription { get; set; }
public double ProgressMultiplier { get; set; }
public byte ProgressOffset { get; set; }
public Exception TaskException { get { return this._taskException; } }
public bool CancelSupported { get { return this._cancelSupported; } }
public bool IsCanceling { get { return this._isCanceling; } }
@@ -73,21 +80,13 @@ namespace Disco.Services.Tasks
}
}
public Task CompletionTask
{
get
{
return _tcs.Task;
}
}
public Task CompletionTask { get { return _tcs.Task; } }
#endregion
#region Events
public delegate void UpdatedBroadcastEvent(ScheduledTaskStatusLive SessionStatus);
public delegate void UpdatedEvent(ScheduledTaskStatus sender, string[] ChangedProperties);
public delegate void UpdatedEvent(ScheduledTaskStatus sender, KeyValuePair<string, object>[] ChangedProperties);
public delegate void CancelingEvent(ScheduledTaskStatus sender);
public static event UpdatedBroadcastEvent UpdatedBroadcast;
public event UpdatedEvent Updated;
public event CancelingEvent Canceling;
#endregion
@@ -112,10 +111,15 @@ namespace Disco.Services.Tasks
}
#region Progress Actions
private byte CalculateProgressValue(byte Progress)
{
return (byte)((Progress * this.ProgressMultiplier) + this.ProgressOffset);
}
public void UpdateStatus(byte Progress)
{
this._progress = Progress;
UpdateTriggered(new string[] { "Progress" });
this._progress = CalculateProgressValue(Progress);
UpdateTriggered("Progress", this._progress);
}
public void UpdateStatus(double Progress)
{
@@ -123,14 +127,27 @@ namespace Disco.Services.Tasks
}
public void UpdateStatus(string CurrentDescription)
{
this._currentDescription = CurrentDescription;
UpdateTriggered(new string[] { "CurrentDescription" });
if (!IgnoreCurrentDescription)
{
this._currentDescription = CurrentDescription;
UpdateTriggered("CurrentDescription", this._currentDescription);
}
}
public void UpdateStatus(byte Progress, string CurrentDescription)
{
this._progress = Progress;
this._currentDescription = CurrentDescription;
UpdateTriggered(new string[] { "Progress", "CurrentDescription" });
this._progress = CalculateProgressValue(Progress);
var changedProperties = new List<ChangedItem>() {
new ChangedItem("Progress", Progress)
};
if (!IgnoreCurrentDescription)
{
this._currentDescription = CurrentDescription;
changedProperties.Add(new ChangedItem("CurrentDescription", CurrentDescription));
}
UpdateTriggered(changedProperties.ToArray());
}
public void UpdateStatus(double Progress, string CurrentDescription)
{
@@ -138,10 +155,24 @@ namespace Disco.Services.Tasks
}
public void UpdateStatus(byte Progress, string CurrentProcess, string CurrentDescription)
{
this._progress = Progress;
this._currentProcess = CurrentProcess;
this._currentDescription = CurrentDescription;
UpdateTriggered(new string[] { "Progress", "CurrentProcess", "CurrentDescription" });
this._progress = CalculateProgressValue(Progress);
var changedProperties = new List<ChangedItem>() {
new ChangedItem("Progress", Progress)
};
if (!IgnoreCurrentProcessChanges)
{
this._currentProcess = CurrentProcess;
changedProperties.Add(new ChangedItem("CurrentProcess", CurrentProcess));
}
if (!IgnoreCurrentDescription)
{
this._currentDescription = CurrentDescription;
changedProperties.Add(new ChangedItem("CurrentDescription", CurrentDescription));
}
UpdateTriggered(changedProperties.ToArray());
}
public void UpdateStatus(double Progress, string CurrentProcess, string CurrentDescription)
{
@@ -157,7 +188,7 @@ namespace Disco.Services.Tasks
if (_cancelSupported)
{ // Cancelling
this._isCanceling = true;
UpdateTriggered(new string[] { "IsCancelling" });
UpdateTriggered("IsCancelling", true);
if (this.Canceling != null)
Canceling(this);
return true;
@@ -177,7 +208,7 @@ namespace Disco.Services.Tasks
if (this._cancelSupported != CancelSupported)
{
this._cancelSupported = CancelSupported;
UpdateTriggered(new string[] { "CancelSupported" });
UpdateTriggered("CancelSupported", CancelSupported);
}
}
public void SetTaskException(Exception TaskException)
@@ -185,7 +216,7 @@ namespace Disco.Services.Tasks
if (this._taskException != TaskException)
{
this._taskException = TaskException;
UpdateTriggered(new string[] { "TaskException" });
UpdateTriggered("TaskExceptionMessage", (this._taskException == null ? null : this._taskException.Message));
}
}
public void SetIsSilent(bool IsSilent)
@@ -198,7 +229,7 @@ namespace Disco.Services.Tasks
if (this._finishedUrl != FinishedUrl)
{
this._finishedUrl = FinishedUrl;
UpdateTriggered(new string[] { "FinishedUrl" });
UpdateTriggered("FinishedUrl", FinishedUrl);
}
}
public void SetFinishedMessage(string FinishedMessage)
@@ -206,7 +237,7 @@ namespace Disco.Services.Tasks
if (this._finishedMessage != FinishedMessage)
{
this._finishedMessage = FinishedMessage;
UpdateTriggered(new string[] { "FinishedMessage" });
UpdateTriggered("FinishedMessage", FinishedMessage);
}
}
public void SetNextScheduledTimestamp(DateTime? NextScheduledTimestamp)
@@ -214,59 +245,62 @@ namespace Disco.Services.Tasks
if (this._nextScheduledTimestamp != NextScheduledTimestamp)
{
this._nextScheduledTimestamp = NextScheduledTimestamp;
UpdateTriggered(new string[] { "NextScheduledTimestamp" });
UpdateTriggered("NextScheduledTimestamp", NextScheduledTimestamp);
}
}
public void Started()
{
List<string> changedProperties = new List<string>() { "IsRunning", "StartedTimestamp" };
var changedProperties = new List<ChangedItem>();
// Change StartedTimestamp
this._startedTimestamp = DateTime.Now;
changedProperties.Add(new ChangedItem("StartedTimestamp", this.StartedTimestamp));
if (this._finishedTimestamp != null)
{
this._finishedTimestamp = null;
changedProperties.Add(new ChangedItem("FinishedTimestamp", this._finishedTimestamp));
}
if (this._nextScheduledTimestamp != null)
{
this._nextScheduledTimestamp = null;
changedProperties.Add("NextScheduledTimestamp");
}
if (this._finishedTimestamp != null)
{
this._finishedTimestamp = null;
changedProperties.Add("FinishedTimestamp");
changedProperties.Add(new ChangedItem("NextScheduledTimestamp", this._nextScheduledTimestamp));
}
changedProperties.Add(new ChangedItem("IsRunning", this.IsRunning));
if (this._progress != 0)
{
this._progress = 0;
changedProperties.Add("Progress");
changedProperties.Add(new ChangedItem("Progress", this._progress));
}
if (this._currentProcess != "Starting")
{
this._currentProcess = "Starting";
changedProperties.Add("CurrentProcess");
changedProperties.Add(new ChangedItem("CurrentProcess", this._currentProcess));
}
if (this._currentDescription != "Initializing Task for Execution")
{
this._currentDescription = "Initializing Task for Execution";
changedProperties.Add("CurrentDescription");
changedProperties.Add(new ChangedItem("CurrentDescription", this._currentDescription));
}
if (this._taskException != null)
{
this._taskException = null;
changedProperties.Add("TaskException");
changedProperties.Add(new ChangedItem("TaskExceptionMessage", (this._taskException == null ? null : this._taskException.Message)));
}
if (this._cancelSupported != this._cancelInitiallySupported)
{
this._cancelSupported = this._cancelInitiallySupported;
changedProperties.Add("CancelSupported");
}
{
this._isCanceling = false;
changedProperties.Add("IsCanceling");
changedProperties.Add(new ChangedItem("CancelSupported", this._cancelSupported));
}
if (this._isCanceling)
{
this._isCanceling = false;
changedProperties.Add("IsCanceling");
changedProperties.Add(new ChangedItem("IsCanceling", this._isCanceling));
}
UpdateTriggered(changedProperties.ToArray());
}
public void Finished()
@@ -279,26 +313,29 @@ namespace Disco.Services.Tasks
}
public void Finished(string FinishedMessage, string FinishedUrl)
{
List<string> changedProperties = new List<string>() { "IsRunning", "FinishedTimestamp" };
var changedProperties = new List<ChangedItem>();
this._finishedTimestamp = DateTime.Now;
changedProperties.Add(new ChangedItem("FinishedTimestamp", this._finishedTimestamp));
changedProperties.Add(new ChangedItem("IsRunning", this.IsRunning));
if (FinishedMessage != this._finishedMessage)
{
this._finishedMessage = FinishedMessage;
changedProperties.Add("FinishedMessage");
changedProperties.Add(new ChangedItem("FinishedMessage", this._finishedMessage));
}
if (FinishedUrl != this._finishedUrl)
{
this._finishedUrl = FinishedUrl;
changedProperties.Add("FinishedUrl");
changedProperties.Add(new ChangedItem("FinishedUrl", this._finishedUrl));
}
if (this._isCanceling)
{
this._isCanceling = false;
changedProperties.Add("IsCanceling");
changedProperties.Add(new ChangedItem("IsCanceling", this._isCanceling));
}
UpdateTriggered(changedProperties.ToArray());
}
internal void Finally()
@@ -311,53 +348,58 @@ namespace Disco.Services.Tasks
this._tcs.Task.Dispose();
this._tcs = new TaskCompletionSource<ScheduledTaskStatus>();
List<string> changedProperties = new List<string>();
var changedProperties = new List<ChangedItem>();
if (this._nextScheduledTimestamp != NextScheduledTimestamp)
{
this._nextScheduledTimestamp = NextScheduledTimestamp;
changedProperties.Add("NextScheduledTimestamp");
changedProperties.Add(new ChangedItem("NextScheduledTimestamp", this._nextScheduledTimestamp));
}
if (this._startedTimestamp != null)
{
this._startedTimestamp = null;
changedProperties.Add("StartedTimestamp");
changedProperties.Add(new ChangedItem("StartedTimestamp", this._startedTimestamp));
}
if (this._finishedTimestamp != null)
{
this._finishedTimestamp = null;
changedProperties.Add("FinishedTimestamp");
changedProperties.Add(new ChangedItem("FinishedTimestamp", this._finishedTimestamp));
}
if (this._finishedMessage != null)
{
this._finishedMessage = null;
changedProperties.Add("FinishedMessage");
changedProperties.Add(new ChangedItem("FinishedMessage", this._finishedMessage));
}
if (this._finishedUrl != null)
{
this._finishedUrl = null;
changedProperties.Add("FinishedUrl");
changedProperties.Add(new ChangedItem("FinishedUrl", this._finishedUrl));
}
if (this._progress != 0)
{
this._progress = 0;
changedProperties.Add("Progress");
changedProperties.Add(new ChangedItem("Progress", this._progress));
}
this.ProgressMultiplier = 0;
this.ProgressOffset = 0;
this.IgnoreCurrentDescription = false;
this.IgnoreCurrentProcessChanges = false;
if (this._currentProcess != "Scheduled")
{
this._currentProcess = "Scheduled";
changedProperties.Add("CurrentProcess");
changedProperties.Add(new ChangedItem("CurrentProcess", this._currentProcess));
}
if (this._currentDescription != "Scheduled Task for Execution")
{
this._currentDescription = "Scheduled Task for Execution";
changedProperties.Add("CurrentDescription");
changedProperties.Add(new ChangedItem("CurrentDescription", this._currentDescription));
}
if (this._isCanceling)
{
this._isCanceling = false;
changedProperties.Add("IsCanceling");
changedProperties.Add(new ChangedItem("IsCanceling", this._isCanceling));
}
UpdateTriggered(changedProperties.ToArray());
}
@@ -373,15 +415,20 @@ namespace Disco.Services.Tasks
}
#endregion
private void UpdateTriggered(string[] ChangedProperties)
private void UpdateTriggered(string ChangedProperty, object NewValue)
{
UpdateTriggered(new ChangedItem[] { new ChangedItem(ChangedProperty, NewValue) });
}
private void UpdateTriggered(params ChangedItem[] ChangedProperties)
{
this._statusVersion++;
if (Updated != null)
Updated(this, ChangedProperties);
if (!_isSilent && UpdatedBroadcast != null)
UpdatedBroadcast.Invoke(ScheduledTaskStatusLive.FromScheduledTaskStatus(this, ChangedProperties));
if (!_isSilent)
ScheduledTaskNotificationsHub.PublishEvent(this.SessionId, ChangedProperties);
}
}
}
+4 -1
View File
@@ -28,13 +28,16 @@ namespace Disco.Services.Tasks
// Scheduled Cleanup
ScheduledTaskCleanup.Schedule(_TaskScheduler);
ScheduledTaskNotificationsHub.Initialize();
if (InitiallySchedule)
{
// Discover DiscoScheduledTask
var appDomain = AppDomain.CurrentDomain;
var scheduledTasksHostAssemblyName = typeof(ScheduledTask).Assembly.GetName().Name;
var scheduledTaskTypes = (from a in appDomain.GetAssemblies()
where !a.GlobalAssemblyCache && !a.IsDynamic
where !a.GlobalAssemblyCache && !a.IsDynamic && a.GetReferencedAssemblies().Any(ra => ra.Name == scheduledTasksHostAssemblyName)
from type in a.GetTypes()
where typeof(ScheduledTask).IsAssignableFrom(type) && !type.IsAbstract
select type);
+5 -4
View File
@@ -1,10 +1,6 @@
using Disco.Models.Repository;
using Disco.Services.Interop.ActiveDirectory;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Disco.Services
{
@@ -15,6 +11,11 @@ namespace Disco.Services
return u.Domain.Equals(ActiveDirectory.Context.PrimaryDomain.NetBiosName, StringComparison.OrdinalIgnoreCase);
}
public static string ToStringFriendly(this User u)
{
return string.Format("{0} ({1})", u.DisplayName, u.FriendlyId());
}
public static string FriendlyId(this User u)
{
return FriendlyUserId(u.UserId);
+86
View File
@@ -0,0 +1,86 @@
using Disco.Data.Repository.Monitor;
using Disco.Services.Authorization;
using Disco.Services.Web.Signalling;
using Microsoft.AspNet.SignalR;
using Microsoft.AspNet.SignalR.Hubs;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Reactive.Linq;
using Disco.Models.Repository;
using Disco.Data.Repository;
namespace Disco.Services.Users
{
[HubName("userUpdates"), DiscoHubAuthorizeAll(Claims.User.Show, Claims.User.ShowAttachments)]
public class UserUpdatesHub : Hub
{
private const string UserPrefix = "User_";
public static IHubContext HubContext { get; private set; }
private static IDisposable RepositoryBeforeSubscription;
private static IDisposable RepositoryAfterSubscription;
static UserUpdatesHub()
{
HubContext = GlobalHost.ConnectionManager.GetHubContext<UserUpdatesHub>();
// Subscribe to Repository Monitor for Changes
RepositoryBeforeSubscription = RepositoryMonitor.StreamBeforeCommit
.Where(e => e.EntityType == typeof(UserAttachment) && e.EventType == RepositoryMonitorEventType.Deleted)
.Subscribe(RepositoryEventBefore);
RepositoryAfterSubscription = RepositoryMonitor.StreamAfterCommit
.Where(e => e.EntityType == typeof(UserAttachment) && e.EventType == RepositoryMonitorEventType.Added)
.Subscribe(RepositoryAfterEvent);
}
private static string GroupName(string UserId)
{
return UserPrefix + UserId;
}
public override Task OnConnected()
{
var userId = Context.QueryString["UserId"];
if (string.IsNullOrWhiteSpace(userId))
throw new ArgumentNullException("UserId");
Groups.Add(Context.ConnectionId, GroupName(userId));
return base.OnConnected();
}
private static void RepositoryEventBefore(RepositoryMonitorEvent e)
{
if (e.EventType == RepositoryMonitorEventType.Deleted)
{
if (e.EntityType == typeof(UserAttachment))
{
var repositoryAttachment = (UserAttachment)e.Entity;
string attachmentUserId;
using (DiscoDataContext Database = new DiscoDataContext())
attachmentUserId = Database.UserAttachments.Where(a => a.Id == repositoryAttachment.Id).Select(a => a.UserId).First();
HubContext.Clients.Group(GroupName(attachmentUserId)).removeAttachment(repositoryAttachment.Id);
}
}
}
private static void RepositoryAfterEvent(RepositoryMonitorEvent e)
{
if (e.EventType == RepositoryMonitorEventType.Added)
{
if (e.EntityType == typeof(UserAttachment))
{
var a = (UserAttachment)e.Entity;
HubContext.Clients.Group(GroupName(a.UserId)).addAttachment(a.Id);
}
}
}
}
}
@@ -16,21 +16,21 @@ namespace Disco.Services.Web
// Ensure 'App-Relative' Url:
BundleUrl = BundleUrl.StartsWith("~/") ? BundleUrl : (BundleUrl.StartsWith("/") ? string.Concat("~", BundleUrl) : string.Concat("~/", BundleUrl));
var deferredBundles = htmlHelper.ViewContext.HttpContext.Items[Bundle.DeferredKey] as List<string>;
var deferredBundles = htmlHelper.ViewContext.HttpContext.Items[BundleTable.DeferredKey] as List<string>;
if (deferredBundles == null)
{
deferredBundles = new List<string>();
htmlHelper.ViewContext.HttpContext.Items[Bundle.DeferredKey] = deferredBundles;
htmlHelper.ViewContext.HttpContext.Items[BundleTable.DeferredKey] = deferredBundles;
}
if (!deferredBundles.Contains(BundleUrl))
deferredBundles.Add(BundleUrl);
}
public static HtmlString BundleRenderDeferred(this HtmlHelper htmlHelper)
{
var deferredBundles = htmlHelper.ViewContext.HttpContext.Items[Bundle.DeferredKey] as List<string>;
var deferredBundles = htmlHelper.ViewContext.HttpContext.Items[BundleTable.DeferredKey] as List<string>;
var uiExtensionScripts = htmlHelper.ViewContext.HttpContext.Items[Bundle.UIExtensionScriptsKey] as List<string>;
var uiExtensionCss = htmlHelper.ViewContext.HttpContext.Items[Bundle.UIExtensionCssKey] as List<string>;
var uiExtensionScripts = htmlHelper.ViewContext.HttpContext.Items[BundleTable.UIExtensionScriptsKey] as List<string>;
var uiExtensionCss = htmlHelper.ViewContext.HttpContext.Items[BundleTable.UIExtensionCssKey] as List<string>;
if (deferredBundles != null || uiExtensionScripts != null || uiExtensionCss != null)
{
+2 -2
View File
@@ -9,10 +9,10 @@ namespace Disco.Services.Web.Bundles
{
internal sealed class BundleHandler : IHttpHandler
{
public Bundle RequestBundle { get; private set; }
public IBundle RequestBundle { get; private set; }
public string BundleVirtualPath { get; private set; }
public BundleHandler(Bundle requestBundle, string bundleVirtualPath)
public BundleHandler(IBundle requestBundle, string bundleVirtualPath)
{
this.RequestBundle = requestBundle;
this.BundleVirtualPath = bundleVirtualPath;
+8 -4
View File
@@ -9,14 +9,18 @@ namespace Disco.Services.Web.Bundles
{
public static class BundleTable
{
private static Dictionary<string, Bundle> _bundles;
public const string DeferredKey = "Bundles.Deferred";
public const string UIExtensionScriptsKey = "Bundles.UIExtensionScripts";
public const string UIExtensionCssKey = "Bundles.UIExtensionCss";
private static Dictionary<string, IBundle> _bundles;
static BundleTable()
{
_bundles = new Dictionary<string, Bundle>();
_bundles = new Dictionary<string, IBundle>();
}
public static void Add(Bundle Bundle)
public static void Add(IBundle Bundle)
{
_bundles[Bundle.Url] = Bundle;
}
@@ -29,7 +33,7 @@ namespace Disco.Services.Web.Bundles
}
}
internal static Bundle GetBundleFor(string Url)
internal static IBundle GetBundleFor(string Url)
{
if (_bundles.ContainsKey(Url))
{
@@ -9,16 +9,13 @@ using System.Web;
namespace Disco.Services.Web.Bundles
{
public class Bundle
public class FileBundle : IBundle
{
public const string DeferredKey = "Bundles.Deferred";
public const string UIExtensionScriptsKey = "Bundles.UIExtensionScripts";
public const string UIExtensionCssKey = "Bundles.UIExtensionCss";
private DateTime? _FileLastModified { get; set; }
private string _FileHash { get; set; }
private string _VersionUrl { get; set; }
public bool RemapRequest { get { return true; } }
public string Url { get; private set; }
public string File { get; private set; }
public string FileHash
@@ -44,7 +41,7 @@ namespace Disco.Services.Web.Bundles
}
}
public Bundle(string Url, string File)
public FileBundle(string Url, string File)
{
if (string.IsNullOrWhiteSpace(Url))
throw new ArgumentNullException("Url");
@@ -117,7 +114,7 @@ namespace Disco.Services.Web.Bundles
this._FileHash = string.Empty;
}
internal void ProcessRequest(HttpContext context)
public void ProcessRequest(HttpContext context)
{
// Write Content Type
context.Response.ContentType = this.ContentType;
+23
View File
@@ -0,0 +1,23 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using System.Web;
namespace Disco.Services.Web.Bundles
{
public interface IBundle
{
bool RemapRequest { get; }
string Url { get; }
string VersionUrl { get; }
string ContentType { get; }
void ProcessRequest(HttpContext context);
}
}
+32
View File
@@ -0,0 +1,32 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Disco.Services.Web.Bundles
{
public class UrlBundle : IBundle
{
public bool RemapRequest { get { return false; } }
public string Url { get; private set; }
public string VersionUrl { get; private set; }
public string ContentType { get; private set; }
public void ProcessRequest(System.Web.HttpContext context)
{
// Not needed for Url Bundle
throw new NotImplementedException();
}
public UrlBundle(string Url, string ContentType)
{
this.Url = Url;
this.VersionUrl = Url;
this.ContentType = ContentType;
}
}
}
@@ -0,0 +1,35 @@
using Disco.Services.Users;
using Microsoft.AspNet.SignalR;
using System;
using System.Security.Principal;
namespace Disco.Services.Web.Signalling
{
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = true, AllowMultiple = true)]
public class DiscoHubAuthorizeAllAttribute : AuthorizeAttribute
{
string[] authorizedClaims;
public DiscoHubAuthorizeAllAttribute(params string[] AuthorisedClaims)
{
if (AuthorisedClaims == null || AuthorisedClaims.Length == 0)
throw new ArgumentNullException("AuthorisedClaims");
this.authorizedClaims = AuthorisedClaims;
}
protected override bool UserAuthorized(IPrincipal user)
{
if (user == null || !user.Identity.IsAuthenticated)
return false;
var username = user.Identity.Name;
var userToken = UserService.GetAuthorization(username);
if (userToken == null)
return false; // No User
return userToken.HasAll(authorizedClaims);
}
}
}
@@ -0,0 +1,35 @@
using Disco.Services.Users;
using Microsoft.AspNet.SignalR;
using System;
using System.Security.Principal;
namespace Disco.Services.Web.Signalling
{
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = true, AllowMultiple = true)]
public class DiscoHubAuthorizeAnyAttribute : AuthorizeAttribute
{
string[] authorizedClaims;
public DiscoHubAuthorizeAnyAttribute(params string[] AuthorisedClaims)
{
if (AuthorisedClaims == null || AuthorisedClaims.Length == 0)
throw new ArgumentNullException("AuthorisedClaims");
this.authorizedClaims = AuthorisedClaims;
}
protected override bool UserAuthorized(IPrincipal user)
{
if (user == null || !user.Identity.IsAuthenticated)
return false;
var username = user.Identity.Name;
var userToken = UserService.GetAuthorization(username);
if (userToken == null)
return false; // No User
return userToken.HasAny(authorizedClaims);
}
}
}
@@ -0,0 +1,37 @@
using Disco.Services.Users;
using Microsoft.AspNet.SignalR;
using System;
using System.Security.Principal;
namespace Disco.Services.Web.Signalling
{
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = true, AllowMultiple = true)]
public class DiscoHubAuthorizeAttribute : AuthorizeAttribute
{
string authorizedClaim;
public DiscoHubAuthorizeAttribute() { }
public DiscoHubAuthorizeAttribute(string AuthorisedClaim)
{
this.authorizedClaim = AuthorisedClaim;
}
protected override bool UserAuthorized(IPrincipal user)
{
if (user == null || !user.Identity.IsAuthenticated)
return false;
var username = user.Identity.Name;
var userToken = UserService.GetAuthorization(username);
if (userToken == null)
return false; // No User
if (authorizedClaim == null)
return userToken.RoleTokens.Count > 0; // Just Authenticate - no Authorization (but require at least 1 role)
else
return userToken.Has(authorizedClaim);
}
}
}
+4 -3
View File
@@ -4,12 +4,13 @@
<package id="LumenWorks.Framework.IO" version="3.8.0" targetFramework="net45" />
<package id="Microsoft.AspNet.Mvc" version="4.0.30506.0" targetFramework="net45" />
<package id="Microsoft.AspNet.Razor" version="2.0.30506.0" targetFramework="net45" />
<package id="Microsoft.AspNet.SignalR.Core" version="1.1.2" targetFramework="net45" />
<package id="Microsoft.AspNet.SignalR.Owin" version="1.1.2" targetFramework="net45" />
<package id="Microsoft.AspNet.SignalR.Core" version="2.0.3" targetFramework="net45" />
<package id="Microsoft.AspNet.WebPages" version="2.0.30506.0" targetFramework="net45" />
<package id="Microsoft.Owin" version="2.0.1" targetFramework="net45" />
<package id="Microsoft.Owin.Security" version="2.0.1" targetFramework="net45" />
<package id="Microsoft.SqlServer.Compact" version="4.0.8876.1" targetFramework="net45" />
<package id="Microsoft.Web.Infrastructure" version="1.0.0.0" targetFramework="net40" />
<package id="Newtonsoft.Json" version="5.0.8" targetFramework="net45" />
<package id="Newtonsoft.Json" version="5.0.1" targetFramework="net45" />
<package id="Owin" version="1.0" targetFramework="net45" />
<package id="RazorGenerator.Mvc" version="2.1.2" targetFramework="net45" />
<package id="Rx-Core" version="2.1.30214.0" targetFramework="net45" />