[banshee] Add a smart, priority-aware, resource-contention-avoiding job scheduler.
- From: Gabriel Burt <gburt src gnome org>
- To: svn-commits-list gnome org
- Subject: [banshee] Add a smart, priority-aware, resource-contention-avoiding job scheduler.
- Date: Thu, 16 Apr 2009 20:21:08 -0400 (EDT)
commit 830bedaabcadd552ec8f74c1fac5996553caa9c1
Author: Gabriel Burt <gabriel burt gmail com>
Date: Thu Apr 16 19:15:48 2009 -0500
Add a smart, priority-aware, resource-contention-avoiding job scheduler.
Net result is BPM analysis will pause while importing, etc (BGO #577772).
Still need to convert Banshee.Kernel jobs.
2009-04-16 Gabriel Burt <gabriel burt gmail com>
* src/Core/Banshee.Core/Banshee.Base/ProductInformation.cs:
* src/Core/Banshee.Core/Banshee.Base/Resource.cs: Rename to
AssemblyResource to avoid conflicts.
* src/Core/Banshee.Services/Banshee.Collection/ImportManager.cs:
* src/Core/Banshee.Services/Banshee.Collection/RescanPipeline.cs:
* src/Core/Banshee.Services/Banshee.Database/BansheeDbFormatMigrator.cs:
* src/Core/Banshee.Services/Banshee.Library/ThreadPoolImportSource.cs:
* src/Core/Banshee.Services/Banshee.MediaEngine/TranscoderService.cs:
* src/Core/Banshee.Services/Banshee.Sources/PrimarySource.cs:
* src/Dap/Banshee.Dap.Ipod/Banshee.Dap.Ipod/DatabaseRebuilder.cs:
* src/Extensions/Banshee.AudioCd/Banshee.AudioCd/AudioCdRipper.cs:
* src/Extensions/Banshee.Bpm/Banshee.Bpm/BpmDetectJob.cs:
Set PriorityHints and Resources for Jobs.
* src/Core/Banshee.Services/Banshee.Metadata/IMetadataLookupJob.cs:
* src/Core/Banshee.Services/Banshee.Metadata/MetadataServiceJob.cs: Add
Cancel method.
* src/Core/Banshee.Services/Banshee.ServiceStack/Application.cs: Use the
new JobScheduler, checking for DataLossJobs on shutdown.
* src/Core/Banshee.Services/Banshee.ServiceStack/DbIteratorJob.cs: Rework
as subclass of SimpleAsyncJob.
* src/Core/Banshee.Services/Banshee.ServiceStack/JobScheduler.cs: Simple
new class that subclasses JobScheduler and is an IService.
* src/Core/Banshee.Services/Makefile.am:
* src/Core/Banshee.Services/Banshee.ServiceStack/UserJobManager.cs:
* src/Core/Banshee.Services/Banshee.ServiceStack/ServiceManager.cs:
* src/Core/Banshee.ThickClient/Banshee.Gui.Dialogs/ConfirmShutdownDialog.cs:
* src/Core/Banshee.ThickClient/Banshee.Gui.Widgets/TaskStatusIcon.cs:
* src/Core/Banshee.ThickClient/Banshee.Gui.Widgets/UserJobTile.cs:
* src/Core/Banshee.ThickClient/Banshee.Gui.Widgets/UserJobTileHost.cs:
* src/Extensions/Banshee.Bpm/Banshee.Bpm/BpmService.cs:
Use JobScheduler instead of UserJobManager.
* src/Core/Banshee.Services/Banshee.ServiceStack/TestUserJob.cs:
* src/Core/Banshee.Services/Banshee.ServiceStack/UserJob.cs: Inherit from
Hyena.Job. Most UserJob code now lives in Hyena.Job
* src/Extensions/Banshee.CoverArt/Banshee.CoverArt/CoverArtJob.cs: Inherit
from DbIteratorJob.
* src/Extensions/Banshee.CoverArt/Banshee.CoverArt/CoverArtService.cs:
If we were cancelled, don't try to save the last_scan time.
* src/Libraries/Hyena/Makefile.am:
* src/Libraries/Hyena/Hyena.Jobs/Job.cs:
* src/Libraries/Hyena/Hyena.Jobs/JobExtensions.cs:
* src/Libraries/Hyena/Hyena.Jobs/PriorityHints.cs:
* src/Libraries/Hyena/Hyena.Jobs/Resource.cs:
* src/Libraries/Hyena/Hyena.Jobs/Scheduler.cs:
* src/Libraries/Hyena/Hyena.Jobs/SimpleAsyncJob.cs:
* src/Libraries/Hyena/Hyena.Jobs/Tests/SchedulerTests.cs: New set of
classes to implement smart scheduling of jobs, prioritizing jobs and
avoiding resource contention.
* src/Libraries/Hyena/Hyena/Timer.cs: Add params/format ctor.
---
ChangeLog | 67 +++++
.../Banshee.Base/ProductInformation.cs | 6 +-
src/Core/Banshee.Core/Banshee.Base/Resource.cs | 2 +-
.../Banshee.Collection/ImportManager.cs | 3 +
.../Banshee.Collection/RescanPipeline.cs | 3 +
.../Banshee.Database/BansheeDbFormatMigrator.cs | 5 +-
.../Banshee.Library/ThreadPoolImportSource.cs | 6 +-
.../Banshee.MediaEngine/TranscoderService.cs | 4 +
.../Banshee.Metadata/IMetadataLookupJob.cs | 1 +
.../Banshee.Metadata/MetadataServiceJob.cs | 33 ++-
.../Banshee.ServiceStack/Application.cs | 2 +
.../Banshee.ServiceStack/DbIteratorJob.cs | 51 ++--
.../Banshee.ServiceStack/JobScheduler.cs | 41 +++
.../Banshee.ServiceStack/ServiceManager.cs | 7 +-
.../Banshee.ServiceStack/TestUserJob.cs | 3 +-
.../Banshee.ServiceStack/UserJob.cs | 151 +---------
.../Banshee.ServiceStack/UserJobManager.cs | 99 ------
.../Banshee.Sources/PrimarySource.cs | 11 +-
src/Core/Banshee.Services/Makefile.am | 2 +-
.../Banshee.Gui.Dialogs/ConfirmShutdownDialog.cs | 32 ++-
.../Banshee.Gui.Widgets/TaskStatusIcon.cs | 20 +-
.../Banshee.Gui.Widgets/UserJobTile.cs | 10 +-
.../Banshee.Gui.Widgets/UserJobTileHost.cs | 33 +-
.../Banshee.Dap.Ipod/DatabaseRebuilder.cs | 3 +
.../Banshee.AudioCd/AudioCdRipper.cs | 4 +
.../Banshee.Bpm/Banshee.Bpm/BpmDetectJob.cs | 45 +++-
.../Banshee.Bpm/Banshee.Bpm/BpmService.cs | 3 +-
.../Banshee.CoverArt/CoverArtJob.cs | 146 ++++------
.../Banshee.CoverArt/CoverArtService.cs | 5 +-
src/Libraries/Hyena/Hyena.Jobs/Job.cs | 312 ++++++++++++++++++++
src/Libraries/Hyena/Hyena.Jobs/JobExtensions.cs | 69 +++++
src/Libraries/Hyena/Hyena.Jobs/PriorityHints.cs | 41 +++
src/Libraries/Hyena/Hyena.Jobs/Resource.cs | 43 +++
src/Libraries/Hyena/Hyena.Jobs/Scheduler.cs | 230 ++++++++++++++
src/Libraries/Hyena/Hyena.Jobs/SimpleAsyncJob.cs | 79 +++++
.../Hyena/Hyena.Jobs/Tests/SchedulerTests.cs | 203 +++++++++++++
src/Libraries/Hyena/Hyena/Timer.cs | 4 +
src/Libraries/Hyena/Makefile.am | 7 +
38 files changed, 1367 insertions(+), 419 deletions(-)
diff --git a/ChangeLog b/ChangeLog
index d77b497..cebf856 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,72 @@
2009-04-16 Gabriel Burt <gabriel burt gmail com>
+ Add a smart, priority-aware, resource-contention-avoiding job scheduler
+ Net result is BPM analysis will pause while importing, etc (BGO #577772).
+ Still need to convert Banshee.Kernel jobs.
+
+ * src/Core/Banshee.Core/Banshee.Base/ProductInformation.cs:
+ * src/Core/Banshee.Core/Banshee.Base/Resource.cs: Rename to
+ AssemblyResource to avoid conflicts.
+
+ * src/Core/Banshee.Services/Banshee.Collection/ImportManager.cs:
+ * src/Core/Banshee.Services/Banshee.Collection/RescanPipeline.cs:
+ * src/Core/Banshee.Services/Banshee.Database/BansheeDbFormatMigrator.cs:
+ * src/Core/Banshee.Services/Banshee.Library/ThreadPoolImportSource.cs:
+ * src/Core/Banshee.Services/Banshee.MediaEngine/TranscoderService.cs:
+ * src/Core/Banshee.Services/Banshee.Sources/PrimarySource.cs:
+ * src/Dap/Banshee.Dap.Ipod/Banshee.Dap.Ipod/DatabaseRebuilder.cs:
+ * src/Extensions/Banshee.AudioCd/Banshee.AudioCd/AudioCdRipper.cs:
+ * src/Extensions/Banshee.Bpm/Banshee.Bpm/BpmDetectJob.cs:
+ Set PriorityHints and Resources for Jobs.
+
+ * src/Core/Banshee.Services/Banshee.Metadata/IMetadataLookupJob.cs:
+ * src/Core/Banshee.Services/Banshee.Metadata/MetadataServiceJob.cs: Add
+ Cancel method.
+
+ * src/Core/Banshee.Services/Banshee.ServiceStack/Application.cs: Use the
+ new JobScheduler, checking for DataLossJobs on shutdown.
+
+ * src/Core/Banshee.Services/Banshee.ServiceStack/DbIteratorJob.cs: Rework
+ as subclass of SimpleAsyncJob.
+
+ * src/Core/Banshee.Services/Banshee.ServiceStack/JobScheduler.cs: Simple
+ new class that subclasses JobScheduler and is an IService.
+
+ * src/Core/Banshee.Services/Makefile.am:
+ * src/Core/Banshee.Services/Banshee.ServiceStack/UserJobManager.cs:
+ * src/Core/Banshee.Services/Banshee.ServiceStack/ServiceManager.cs:
+ * src/Core/Banshee.ThickClient/Banshee.Gui.Dialogs/ConfirmShutdownDialog.cs:
+ * src/Core/Banshee.ThickClient/Banshee.Gui.Widgets/TaskStatusIcon.cs:
+ * src/Core/Banshee.ThickClient/Banshee.Gui.Widgets/UserJobTile.cs:
+ * src/Core/Banshee.ThickClient/Banshee.Gui.Widgets/UserJobTileHost.cs:
+ * src/Extensions/Banshee.Bpm/Banshee.Bpm/BpmService.cs:
+ Use JobScheduler instead of UserJobManager.
+
+ * src/Core/Banshee.Services/Banshee.ServiceStack/TestUserJob.cs:
+ * src/Core/Banshee.Services/Banshee.ServiceStack/UserJob.cs: Inherit from
+ Hyena.Job. Most UserJob code now lives in Hyena.Job
+
+ * src/Extensions/Banshee.CoverArt/Banshee.CoverArt/CoverArtJob.cs: Inherit
+ from DbIteratorJob.
+
+ * src/Extensions/Banshee.CoverArt/Banshee.CoverArt/CoverArtService.cs:
+ If we were cancelled, don't try to save the last_scan time.
+
+ * src/Libraries/Hyena/Makefile.am:
+ * src/Libraries/Hyena/Hyena.Jobs/Job.cs:
+ * src/Libraries/Hyena/Hyena.Jobs/JobExtensions.cs:
+ * src/Libraries/Hyena/Hyena.Jobs/PriorityHints.cs:
+ * src/Libraries/Hyena/Hyena.Jobs/Resource.cs:
+ * src/Libraries/Hyena/Hyena.Jobs/Scheduler.cs:
+ * src/Libraries/Hyena/Hyena.Jobs/SimpleAsyncJob.cs:
+ * src/Libraries/Hyena/Hyena.Jobs/Tests/SchedulerTests.cs: New set of
+ classes to implement smart scheduling of jobs, prioritizing jobs and
+ avoiding resource contention.
+
+ * src/Libraries/Hyena/Hyena/Timer.cs: Add params/format ctor.
+
+2009-04-16 Gabriel Burt <gabriel burt gmail com>
+
This patch adds separate library folder preferences for Music Library,
Video Library, and Podcasts (BGO #404827). They are all configurable via
the Preferences dialog. It also changes the format used to store URIs in
diff --git a/src/Core/Banshee.Core/Banshee.Base/ProductInformation.cs b/src/Core/Banshee.Core/Banshee.Base/ProductInformation.cs
index ffc0293..81445bb 100644
--- a/src/Core/Banshee.Core/Banshee.Base/ProductInformation.cs
+++ b/src/Core/Banshee.Core/Banshee.Base/ProductInformation.cs
@@ -55,7 +55,7 @@ namespace Banshee.Base
List<string> contributors_list = new List<string> ();
XmlDocument doc = new XmlDocument ();
- doc.LoadXml (Resource.GetFileContents ("contributors.xml"));
+ doc.LoadXml (AssemblyResource.GetFileContents ("contributors.xml"));
foreach (XmlNode node in doc.DocumentElement.ChildNodes) {
if (node.FirstChild == null || node.FirstChild.Value == null) {
@@ -89,7 +89,7 @@ namespace Banshee.Base
private static void LoadTranslators ()
{
XmlDocument doc = new XmlDocument ();
- doc.LoadXml (Resource.GetFileContents ("translators.xml"));
+ doc.LoadXml (AssemblyResource.GetFileContents ("translators.xml"));
foreach (XmlNode node in doc.DocumentElement.ChildNodes) {
if (node.Name != "language") {
@@ -133,7 +133,7 @@ namespace Banshee.Base
}
public static string License {
- get { return Resource.GetFileContents ("COPYING"); }
+ get { return AssemblyResource.GetFileContents ("COPYING"); }
}
}
diff --git a/src/Core/Banshee.Core/Banshee.Base/Resource.cs b/src/Core/Banshee.Core/Banshee.Base/Resource.cs
index f01c9dd..6de004a 100644
--- a/src/Core/Banshee.Core/Banshee.Base/Resource.cs
+++ b/src/Core/Banshee.Core/Banshee.Base/Resource.cs
@@ -32,7 +32,7 @@ using System.Reflection;
namespace Banshee.Base
{
- public static class Resource
+ public static class AssemblyResource
{
public static string GetFileContents (string name)
{
diff --git a/src/Core/Banshee.Services/Banshee.Collection/ImportManager.cs b/src/Core/Banshee.Services/Banshee.Collection/ImportManager.cs
index bbc767e..d759a5a 100644
--- a/src/Core/Banshee.Services/Banshee.Collection/ImportManager.cs
+++ b/src/Core/Banshee.Services/Banshee.Collection/ImportManager.cs
@@ -31,6 +31,7 @@ using System.Globalization;
using Mono.Unix;
using Hyena;
+using Hyena.Jobs;
using Hyena.Collections;
using Banshee.IO;
@@ -134,6 +135,8 @@ namespace Banshee.Collection
timer_id = Log.DebugTimerStart ();
user_job = new UserJob (Title, Catalog.GetString ("Scanning for media"));
+ user_job.SetResources (Resource.Cpu, Resource.Disk, Resource.Database);
+ user_job.PriorityHints = PriorityHints.SpeedSensitive | PriorityHints.DataLossIfStopped;
user_job.IconNames = new string [] { "system-search", "gtk-find" };
user_job.CancelMessage = CancelMessage;
user_job.CanCancel = true;
diff --git a/src/Core/Banshee.Services/Banshee.Collection/RescanPipeline.cs b/src/Core/Banshee.Services/Banshee.Collection/RescanPipeline.cs
index 66aa9c8..6cc1b87 100644
--- a/src/Core/Banshee.Services/Banshee.Collection/RescanPipeline.cs
+++ b/src/Core/Banshee.Services/Banshee.Collection/RescanPipeline.cs
@@ -31,6 +31,7 @@ using System.Data;
using Mono.Unix;
+using Hyena.Jobs;
using Hyena.Collections;
using Hyena.Data.Sqlite;
@@ -75,6 +76,8 @@ namespace Banshee.Collection
private void BuildJob ()
{
job = new BatchUserJob (Catalog.GetString ("Rescanning {0} of {1}"), "system-search", "gtk-find");
+ job.SetResources (Resource.Cpu, Resource.Disk, Resource.Database);
+ job.PriorityHints = PriorityHints.SpeedSensitive;
job.CanCancel = true;
job.CancelRequested += delegate { cancelled = true; Cancel (); };
track_sync.ProcessedItem += delegate {
diff --git a/src/Core/Banshee.Services/Banshee.Database/BansheeDbFormatMigrator.cs b/src/Core/Banshee.Services/Banshee.Database/BansheeDbFormatMigrator.cs
index f3dc17f..ad6fd25 100644
--- a/src/Core/Banshee.Services/Banshee.Database/BansheeDbFormatMigrator.cs
+++ b/src/Core/Banshee.Services/Banshee.Database/BansheeDbFormatMigrator.cs
@@ -35,6 +35,7 @@ using System.Threading;
using Mono.Unix;
using Hyena;
+using Hyena.Jobs;
using Hyena.Data.Sqlite;
using Timer=Hyena.Timer;
@@ -1078,7 +1079,7 @@ namespace Banshee.Database
private void OnServiceStarted (ServiceStartedArgs args)
{
- if (args.Service is UserJobManager) {
+ if (args.Service is JobScheduler) {
ServiceManager.ServiceStarted -= OnServiceStarted;
if (ServiceManager.SourceManager.MusicLibrary != null) {
RefreshMetadataDelayed ();
@@ -1130,6 +1131,8 @@ namespace Banshee.Database
}
UserJob job = new UserJob (Catalog.GetString ("Refreshing Metadata"));
+ job.SetResources (Resource.Cpu, Resource.Disk, Resource.Database);
+ job.PriorityHints = PriorityHints.SpeedSensitive;
job.Status = Catalog.GetString ("Scanning...");
job.IconNames = new string [] { "system-search", "gtk-find" };
job.Register ();
diff --git a/src/Core/Banshee.Services/Banshee.Library/ThreadPoolImportSource.cs b/src/Core/Banshee.Services/Banshee.Library/ThreadPoolImportSource.cs
index c7ce245..e6e6e1d 100644
--- a/src/Core/Banshee.Services/Banshee.Library/ThreadPoolImportSource.cs
+++ b/src/Core/Banshee.Services/Banshee.Library/ThreadPoolImportSource.cs
@@ -30,9 +30,11 @@ using System;
using System.IO;
using System.Threading;
-using Hyena;
using Mono.Unix;
+using Hyena;
+using Hyena.Jobs;
+
using Banshee.ServiceStack;
using Banshee.Sources;
@@ -52,6 +54,8 @@ namespace Banshee.Library
}
user_job = new UserJob (UserJobTitle, UserJobTitle, Catalog.GetString ("Importing Songs"));
+ user_job.SetResources (Resource.Cpu, Resource.Disk, Resource.Database);
+ user_job.PriorityHints = PriorityHints.SpeedSensitive | PriorityHints.DataLossIfStopped;
user_job.IconNames = IconNames;
user_job.CancelMessage = CancelMessage;
user_job.CanCancel = CanCancel;
diff --git a/src/Core/Banshee.Services/Banshee.MediaEngine/TranscoderService.cs b/src/Core/Banshee.Services/Banshee.MediaEngine/TranscoderService.cs
index 880ed54..77db232 100644
--- a/src/Core/Banshee.Services/Banshee.MediaEngine/TranscoderService.cs
+++ b/src/Core/Banshee.Services/Banshee.MediaEngine/TranscoderService.cs
@@ -33,6 +33,8 @@ using System.Collections.Generic;
using Mono.Unix;
using Mono.Addins;
+using Hyena.Jobs;
+
using Banshee.Base;
using Banshee.ServiceStack;
using Banshee.Collection;
@@ -130,6 +132,8 @@ namespace Banshee.MediaEngine
get {
if (user_job == null) {
user_job = new BatchUserJob (Catalog.GetString("Converting {0} of {1}"), Catalog.GetString("Initializing"), "encode");
+ user_job.SetResources (Resource.Cpu);
+ user_job.PriorityHints = PriorityHints.SpeedSensitive;
user_job.CancelMessage = Catalog.GetString ("Files are currently being converted to another format. Would you like to stop this?");
user_job.CanCancel = true;
user_job.DelayShow = true;
diff --git a/src/Core/Banshee.Services/Banshee.Metadata/IMetadataLookupJob.cs b/src/Core/Banshee.Services/Banshee.Metadata/IMetadataLookupJob.cs
index 5cee133..5ac2303 100644
--- a/src/Core/Banshee.Services/Banshee.Metadata/IMetadataLookupJob.cs
+++ b/src/Core/Banshee.Services/Banshee.Metadata/IMetadataLookupJob.cs
@@ -40,5 +40,6 @@ namespace Banshee.Metadata
{
IBasicTrackInfo Track { get; }
IList<StreamTag> ResultTags { get; }
+ void Cancel ();
}
}
diff --git a/src/Core/Banshee.Services/Banshee.Metadata/MetadataServiceJob.cs b/src/Core/Banshee.Services/Banshee.Metadata/MetadataServiceJob.cs
index 4096b25..97308c1 100644
--- a/src/Core/Banshee.Services/Banshee.Metadata/MetadataServiceJob.cs
+++ b/src/Core/Banshee.Services/Banshee.Metadata/MetadataServiceJob.cs
@@ -43,8 +43,10 @@ namespace Banshee.Metadata
public class MetadataServiceJob : IMetadataLookupJob
{
private MetadataService service;
+ private bool cancelled;
private IBasicTrackInfo track;
private List<StreamTag> tags = new List<StreamTag>();
+ private IMetadataLookupJob current_job;
protected bool InternetConnected {
get { return ServiceManager.Get<Network> ().Connected; }
@@ -59,17 +61,42 @@ namespace Banshee.Metadata
this.service = service;
this.track = track;
}
+
+ public virtual void Cancel ()
+ {
+ cancelled = true;
+ lock (this) {
+ if (current_job != null) {
+ current_job.Cancel ();
+ }
+ }
+ }
public virtual void Run()
{
foreach(IMetadataProvider provider in service.Providers) {
+ if (cancelled)
+ break;;
+
try {
- IMetadataLookupJob job = provider.CreateJob(track);
- job.Run();
+ lock (this) {
+ current_job = provider.CreateJob(track);
+ }
+
+ current_job.Run();
+
+ if (cancelled)
+ break;;
- foreach(StreamTag tag in job.ResultTags) {
+ foreach(StreamTag tag in current_job.ResultTags) {
AddTag(tag);
}
+
+ lock (this) {
+ current_job = null;
+ }
+ } catch (System.Threading.ThreadAbortException) {
+ throw;
} catch(Exception e) {
Hyena.Log.Exception (e);
}
diff --git a/src/Core/Banshee.Services/Banshee.ServiceStack/Application.cs b/src/Core/Banshee.Services/Banshee.ServiceStack/Application.cs
index defb002..67dfcd0 100644
--- a/src/Core/Banshee.Services/Banshee.ServiceStack/Application.cs
+++ b/src/Core/Banshee.Services/Banshee.ServiceStack/Application.cs
@@ -104,6 +104,7 @@ namespace Banshee.ServiceStack
{
shutting_down = true;
if (Banshee.Kernel.Scheduler.IsScheduled (typeof (Banshee.Kernel.IInstanceCriticalJob)) ||
+ ServiceManager.JobScheduler.HasAnyDataLossJobs ||
Banshee.Kernel.Scheduler.CurrentJob is Banshee.Kernel.IInstanceCriticalJob) {
if (shutdown_prompt_handler != null && !shutdown_prompt_handler ()) {
shutting_down = false;
@@ -198,6 +199,7 @@ namespace Banshee.ServiceStack
private static void Dispose ()
{
+ ServiceManager.JobScheduler.CancelAll (true);
ServiceManager.Shutdown ();
lock (running_clients) {
diff --git a/src/Core/Banshee.Services/Banshee.ServiceStack/DbIteratorJob.cs b/src/Core/Banshee.Services/Banshee.ServiceStack/DbIteratorJob.cs
index d0fb1e2..6ce6820 100644
--- a/src/Core/Banshee.Services/Banshee.ServiceStack/DbIteratorJob.cs
+++ b/src/Core/Banshee.Services/Banshee.ServiceStack/DbIteratorJob.cs
@@ -35,13 +35,13 @@ using Mono.Unix;
using Mono.Addins;
using Hyena;
+using Hyena.Jobs;
using Hyena.Data.Sqlite;
using Banshee.Base;
using Banshee.Collection;
using Banshee.Collection.Database;
using Banshee.Sources;
-using Banshee.Kernel;
using Banshee.Metadata;
using Banshee.MediaEngine;
using Banshee.ServiceStack;
@@ -49,10 +49,8 @@ using Banshee.Library;
namespace Banshee.ServiceStack
{
- public abstract class DbIteratorJob : UserJob
+ public abstract class DbIteratorJob : SimpleAsyncJob
{
- //private Thread thread;
-
private HyenaSqliteCommand count_command;
private HyenaSqliteCommand select_command;
private int current_count;
@@ -65,49 +63,64 @@ namespace Banshee.ServiceStack
set { select_command = value; }
}
- public DbIteratorJob (string name) : base (name)
+ public DbIteratorJob (string title)
+ {
+ Title = title;
+ CancelRequested += OnCancelled;
+ }
+
+ public void Register ()
+ {
+ ServiceManager.JobScheduler.Add (this);
+ }
+
+ private void OnCancelled (object o, EventArgs args)
{
+ OnCancelled ();
}
- public void RunAsync ()
+ protected virtual void OnCancelled ()
{
- //thread = ThreadAssist.Spawn (Run);
- ThreadAssist.Spawn (Run);
- //thread.Name = Title;
}
- public void Run ()
+ protected override void Run ()
{
- int total = ServiceManager.DbConnection.Query<int> (count_command);
- if (total > 0) {
+ if (ServiceManager.DbConnection.Query<int> (count_command) > 0) {
Init ();
- Iterate ();
+ while (!IsFinished && Iterate ()) {}
}
+
+ Cleanup ();
}
- protected void Iterate ()
+ protected bool Iterate ()
{
if (IsCancelRequested) {
- Hyena.Log.DebugFormat ("{0} cancelled", Title);
- Cleanup ();
- return;
+ return false;
}
+ YieldToScheduler ();
+
int total = current_count + ServiceManager.DbConnection.Query<int> (count_command);
try {
using (HyenaDataReader reader = new HyenaDataReader (ServiceManager.DbConnection.Query (select_command))) {
if (reader.Read ()) {
IterateCore (reader);
} else {
- Cleanup ();
+ return false;
}
}
+ } catch (System.Threading.ThreadAbortException) {
+ Cleanup ();
+ throw;
} catch (Exception e) {
Log.Exception (e);
} finally {
Progress = (double) current_count / (double) total;
current_count++;
}
+
+ return true;
}
protected virtual void Init ()
@@ -116,7 +129,7 @@ namespace Banshee.ServiceStack
protected virtual void Cleanup ()
{
- Finish ();
+ OnFinished ();
}
protected abstract void IterateCore (HyenaDataReader reader);
diff --git a/src/Core/Banshee.Services/Banshee.ServiceStack/JobScheduler.cs b/src/Core/Banshee.Services/Banshee.ServiceStack/JobScheduler.cs
new file mode 100644
index 0000000..c043c03
--- /dev/null
+++ b/src/Core/Banshee.Services/Banshee.ServiceStack/JobScheduler.cs
@@ -0,0 +1,41 @@
+//
+// JobScheduler.cs
+//
+// Author:
+// Gabriel Burt <gburt novell com>
+//
+// Copyright (C) 2009 Novell, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+
+using Hyena.Jobs;
+
+namespace Banshee.ServiceStack
+{
+ public class JobScheduler : Scheduler, IRequiredService
+ {
+ string IService.ServiceName {
+ get { return "JobScheduler"; }
+ }
+ }
+}
diff --git a/src/Core/Banshee.Services/Banshee.ServiceStack/ServiceManager.cs b/src/Core/Banshee.Services/Banshee.ServiceStack/ServiceManager.cs
index b406e8f..0dcdd24 100644
--- a/src/Core/Banshee.Services/Banshee.ServiceStack/ServiceManager.cs
+++ b/src/Core/Banshee.Services/Banshee.ServiceStack/ServiceManager.cs
@@ -33,6 +33,7 @@ using System.Collections.Generic;
using Mono.Addins;
using Hyena;
+
using Banshee.Base;
using Banshee.MediaProfiles;
using Banshee.Sources;
@@ -98,7 +99,7 @@ namespace Banshee.ServiceStack
RegisterService<PlaybackControllerService> ();
RegisterService<ImportSourceManager> ();
RegisterService<LibraryImportManager> ();
- RegisterService<UserJobManager> ();
+ RegisterService<JobScheduler> ();
RegisterService<Banshee.Hardware.HardwareManager> ();
RegisterService<Banshee.Collection.Indexer.CollectionIndexerService> ();
}
@@ -392,6 +393,10 @@ namespace Banshee.ServiceStack
public static SourceManager SourceManager {
get { return (SourceManager)Get ("SourceManager"); }
}
+
+ public static JobScheduler JobScheduler {
+ get { return (JobScheduler)Get ("JobScheduler"); }
+ }
public static PlayerEngineService PlayerEngine {
get { return (PlayerEngineService)Get ("PlayerEngine"); }
diff --git a/src/Core/Banshee.Services/Banshee.ServiceStack/TestUserJob.cs b/src/Core/Banshee.Services/Banshee.ServiceStack/TestUserJob.cs
index 263ae0d..df98fab 100644
--- a/src/Core/Banshee.Services/Banshee.ServiceStack/TestUserJob.cs
+++ b/src/Core/Banshee.Services/Banshee.ServiceStack/TestUserJob.cs
@@ -52,6 +52,7 @@ namespace Banshee.ServiceStack
public TestUserJob () : base ("UserJob Test Job", "Waiting for 7.5 seconds...")
{
+ CancelRequested += OnCancelRequested;
DelayShow = true;
Register ();
@@ -120,7 +121,7 @@ namespace Banshee.ServiceStack
});
}
- protected override void OnCancelRequested ()
+ private void OnCancelRequested (object o, EventArgs args)
{
if (initial_timeout_id > 0) {
Application.IdleTimeoutRemove (initial_timeout_id);
diff --git a/src/Core/Banshee.Services/Banshee.ServiceStack/UserJob.cs b/src/Core/Banshee.Services/Banshee.ServiceStack/UserJob.cs
index 5d83d37..0e0320e 100644
--- a/src/Core/Banshee.Services/Banshee.ServiceStack/UserJob.cs
+++ b/src/Core/Banshee.Services/Banshee.ServiceStack/UserJob.cs
@@ -29,34 +29,18 @@
using System;
using System.Threading;
+using Hyena.Jobs;
using Hyena.Data;
namespace Banshee.ServiceStack
{
- public class UserJob : IUserJob
+ public class UserJob : Job
{
- private string title;
- private string status;
- private double progress;
- private string [] icon_names;
- private string cancel_message;
- private bool can_cancel;
- private bool is_cancel_requested;
- private bool is_finished;
- private bool delay_show;
- private bool is_background;
-
- private int update_freeze_ref;
-
- public event EventHandler Finished;
- public event EventHandler Updated;
- public event EventHandler CancelRequested;
-
- public UserJob (string title, string status) : this (title, status, null)
+ public UserJob (string title) : this (title, null, null)
{
}
- public UserJob (string title) : this (title, null, null)
+ public UserJob (string title, string status) : this (title, status, null)
{
}
@@ -68,134 +52,15 @@ namespace Banshee.ServiceStack
IconNames = iconNames;
ThawUpdate (true);
}
-
+
public void Register ()
{
- if (ServiceManager.Contains<UserJobManager> ()) {
- ServiceManager.Get<UserJobManager> ().Register (this);
- }
- }
-
- public void Cancel ()
- {
- OnCancelRequested ();
+ ServiceManager.JobScheduler.Add (this);
}
-
+
public void Finish ()
{
- if (!is_finished) {
- is_finished = true;
- OnFinished ();
- }
- }
-
- protected void FreezeUpdate ()
- {
- Interlocked.Increment (ref update_freeze_ref);
- }
-
- protected void ThawUpdate (bool raiseUpdate)
- {
- Interlocked.Decrement (ref update_freeze_ref);
- if (raiseUpdate) {
- OnUpdated ();
- }
- }
-
- protected virtual void OnFinished ()
- {
- EventHandler handler = Finished;
- if (handler != null) {
- handler (this, EventArgs.Empty);
- }
- }
-
- protected virtual void OnUpdated ()
- {
- if (update_freeze_ref != 0) {
- return;
- }
-
- EventHandler handler = Updated;
- if (handler != null) {
- handler (this, EventArgs.Empty);
- }
- }
-
- protected virtual void OnCancelRequested ()
- {
- IsCancelRequested = true;
-
- EventHandler handler = CancelRequested;
- if (handler != null) {
- handler (this, EventArgs.Empty);
- }
- }
-
- public virtual string Title {
- get { return title; }
- set {
- title = value;
- OnUpdated ();
- }
- }
-
- public virtual string Status {
- get { return status; }
- set {
- status = value;
- OnUpdated ();
- }
- }
-
- public virtual double Progress {
- get { return progress; }
- set {
- progress = Math.Max (0.0, Math.Min (1.0, value));
- OnUpdated ();
- }
- }
-
- public virtual string [] IconNames {
- get { return icon_names; }
- set {
- if (value != null) {
- icon_names = value;
- OnUpdated ();
- }
- }
- }
-
- public virtual bool IsBackground {
- get { return is_background; }
- set { is_background = value; }
- }
-
- public virtual string CancelMessage {
- get { return cancel_message; }
- set { cancel_message = value; }
- }
-
- public virtual bool CanCancel {
- get { return can_cancel; }
- set {
- can_cancel = value;
- OnUpdated ();
- }
- }
-
- public virtual bool IsCancelRequested {
- get { return is_cancel_requested; }
- set { is_cancel_requested = value; }
- }
-
- public virtual bool IsFinished {
- get { return is_finished; }
- }
-
- public virtual bool DelayShow {
- get { return delay_show; }
- set { delay_show = value; }
+ OnFinished ();
}
}
}
diff --git a/src/Core/Banshee.Services/Banshee.ServiceStack/UserJobManager.cs b/src/Core/Banshee.Services/Banshee.ServiceStack/UserJobManager.cs
deleted file mode 100644
index ffafdb2..0000000
--- a/src/Core/Banshee.Services/Banshee.ServiceStack/UserJobManager.cs
+++ /dev/null
@@ -1,99 +0,0 @@
-//
-// UserJobManager.cs
-//
-// Author:
-// Aaron Bockover <abockover novell com>
-//
-// Copyright (C) 2007-2008 Novell, Inc.
-//
-// Permission is hereby granted, free of charge, to any person obtaining
-// a copy of this software and associated documentation files (the
-// "Software"), to deal in the Software without restriction, including
-// without limitation the rights to use, copy, modify, merge, publish,
-// distribute, sublicense, and/or sell copies of the Software, and to
-// permit persons to whom the Software is furnished to do so, subject to
-// the following conditions:
-//
-// The above copyright notice and this permission notice shall be
-// included in all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-//
-
-using System;
-using System.Collections.Generic;
-
-namespace Banshee.ServiceStack
-{
- public class UserJobManager : IRequiredService, IEnumerable<IUserJob>
- {
- private List<IUserJob> user_jobs = new List<IUserJob> ();
-
- public event UserJobEventHandler JobAdded;
- public event UserJobEventHandler JobRemoved;
-
- public UserJobManager ()
- {
- }
-
- public void Register (IUserJob job)
- {
- lock (this) {
- user_jobs.Add (job);
- job.Finished += OnJobFinished;
- }
-
- OnJobAdded (job);
- }
-
- private void OnJobFinished (object o, EventArgs args)
- {
- lock (this) {
- IUserJob job = (IUserJob)o;
-
- if (user_jobs.Contains (job)) {
- user_jobs.Remove (job);
- }
-
- job.Finished -= OnJobFinished;
- OnJobRemoved (job);
- }
- }
-
- protected virtual void OnJobAdded (IUserJob job)
- {
- UserJobEventHandler handler = JobAdded;
- if (handler != null) {
- handler (this, new UserJobEventArgs (job));
- }
- }
-
- protected virtual void OnJobRemoved (IUserJob job)
- {
- UserJobEventHandler handler = JobRemoved;
- if (handler != null) {
- handler (this, new UserJobEventArgs (job));
- }
- }
-
- public IEnumerator<IUserJob> GetEnumerator ()
- {
- return user_jobs.GetEnumerator ();
- }
-
- System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator ()
- {
- return GetEnumerator ();
- }
-
- string IService.ServiceName {
- get { return "UserJobManager"; }
- }
- }
-}
diff --git a/src/Core/Banshee.Services/Banshee.Sources/PrimarySource.cs b/src/Core/Banshee.Services/Banshee.Sources/PrimarySource.cs
index ce43fce..9bf98d6 100644
--- a/src/Core/Banshee.Services/Banshee.Sources/PrimarySource.cs
+++ b/src/Core/Banshee.Services/Banshee.Sources/PrimarySource.cs
@@ -31,6 +31,7 @@ using System.Collections.Generic;
using Mono.Unix;
using Hyena;
+using Hyena.Jobs;
using Hyena.Data;
using Hyena.Query;
using Hyena.Data.Sqlite;
@@ -671,10 +672,10 @@ namespace Banshee.Sources
set { delay_add_job = value; }
}
- private bool delay_delete_jbo = true;
+ private bool delay_delete_job = true;
protected bool DelayDeleteJob {
- get { return delay_delete_jbo; }
- set { delay_delete_jbo = value; }
+ get { return delay_delete_job; }
+ set { delay_delete_job = value; }
}
private BatchUserJob add_track_job;
@@ -685,6 +686,8 @@ namespace Banshee.Sources
add_track_job = new BatchUserJob (String.Format (Catalog.GetString (
"Adding {0} of {1} to {2}"), "{0}", "{1}", Name),
Properties.GetStringList ("Icon.Name"));
+ add_track_job.SetResources (Resource.Cpu, Resource.Database, Resource.Disk);
+ add_track_job.PriorityHints = PriorityHints.SpeedSensitive | PriorityHints.DataLossIfStopped;
add_track_job.DelayShow = DelayAddJob;
add_track_job.CanCancel = true;
add_track_job.Register ();
@@ -702,6 +705,8 @@ namespace Banshee.Sources
delete_track_job = new BatchUserJob (String.Format (Catalog.GetString (
"Deleting {0} of {1} From {2}"), "{0}", "{1}", Name),
Properties.GetStringList ("Icon.Name"));
+ delete_track_job.SetResources (Resource.Cpu, Resource.Database);
+ delete_track_job.PriorityHints = PriorityHints.SpeedSensitive | PriorityHints.DataLossIfStopped;
delete_track_job.DelayShow = DelayDeleteJob;
delete_track_job.Register ();
}
diff --git a/src/Core/Banshee.Services/Makefile.am b/src/Core/Banshee.Services/Makefile.am
index b1bd49e..adfbef4 100644
--- a/src/Core/Banshee.Services/Makefile.am
+++ b/src/Core/Banshee.Services/Makefile.am
@@ -168,12 +168,12 @@ SOURCES = \
Banshee.ServiceStack/IRequiredService.cs \
Banshee.ServiceStack/IService.cs \
Banshee.ServiceStack/IUserJob.cs \
+ Banshee.ServiceStack/JobScheduler.cs \
Banshee.ServiceStack/ServiceManager.cs \
Banshee.ServiceStack/ServiceStartedHandler.cs \
Banshee.ServiceStack/TestUserJob.cs \
Banshee.ServiceStack/UserJob.cs \
Banshee.ServiceStack/UserJobEventHandler.cs \
- Banshee.ServiceStack/UserJobManager.cs \
Banshee.SmartPlaylist/Migrator.cs \
Banshee.SmartPlaylist/SmartPlaylistDefinition.cs \
Banshee.SmartPlaylist/SmartPlaylistSource.cs \
diff --git a/src/Core/Banshee.ThickClient/Banshee.Gui.Dialogs/ConfirmShutdownDialog.cs b/src/Core/Banshee.ThickClient/Banshee.Gui.Dialogs/ConfirmShutdownDialog.cs
index 52213bb..3aa7f43 100644
--- a/src/Core/Banshee.ThickClient/Banshee.Gui.Dialogs/ConfirmShutdownDialog.cs
+++ b/src/Core/Banshee.ThickClient/Banshee.Gui.Dialogs/ConfirmShutdownDialog.cs
@@ -30,15 +30,19 @@ using System;
using Gtk;
using Mono.Unix;
-using Banshee.Kernel;
+using Hyena.Jobs;
+
+using Banshee.ServiceStack;
namespace Banshee.Gui.Dialogs
{
public class ConfirmShutdownDialog : ErrorListDialog
{
+ private Scheduler scheduler;
+
public ConfirmShutdownDialog() : base()
{
- ListView.Model = new ListStore(typeof(string), typeof(IJob));
+ ListView.Model = new ListStore(typeof(string), typeof(Job));
ListView.AppendColumn("Error", new CellRendererText(), "text", 0);
ListView.HeadersVisible = false;
@@ -53,14 +57,14 @@ namespace Banshee.Gui.Dialogs
AddButton(Catalog.GetString("Quit anyway"), ResponseType.Ok, false);
AddButton(Catalog.GetString("Continue running"), ResponseType.Cancel, true);
+
+ scheduler = ServiceManager.JobScheduler;
+ foreach (Job job in scheduler.Jobs) {
+ AddJob (job);
+ }
- foreach(IJob job in Scheduler.ScheduledJobs) {
- AddJob(job);
- }
-
- Scheduler.JobScheduled += AddJob;
- Scheduler.JobUnscheduled += RemoveJob;
- Scheduler.JobFinished += RemoveJob;
+ scheduler.JobAdded += AddJob;
+ scheduler.JobRemoved += RemoveJob;
}
public void AddString(string message)
@@ -68,20 +72,20 @@ namespace Banshee.Gui.Dialogs
(ListView.Model as ListStore).AppendValues(message, null);
}
- private void AddJob(IJob job)
+ private void AddJob(Job job)
{
- if(job is IInstanceCriticalJob) {
+ if (job.Has (PriorityHints.DataLossIfStopped)) {
Banshee.Base.ThreadAssist.ProxyToMain(delegate {
TreeIter iter = (ListView.Model as ListStore).Prepend();
- (ListView.Model as ListStore).SetValue(iter, 0, (job as IInstanceCriticalJob).Name);
+ (ListView.Model as ListStore).SetValue(iter, 0, job.Title);
(ListView.Model as ListStore).SetValue(iter, 1, job);
});
}
}
- private void RemoveJob(IJob job)
+ private void RemoveJob(Job job)
{
- if(!Scheduler.IsInstanceCriticalJobScheduled) {
+ if(!scheduler.HasAnyDataLossJobs) {
Dialog.Respond(Gtk.ResponseType.Ok);
return;
}
diff --git a/src/Core/Banshee.ThickClient/Banshee.Gui.Widgets/TaskStatusIcon.cs b/src/Core/Banshee.ThickClient/Banshee.Gui.Widgets/TaskStatusIcon.cs
index 4156f5a..416505e 100644
--- a/src/Core/Banshee.ThickClient/Banshee.Gui.Widgets/TaskStatusIcon.cs
+++ b/src/Core/Banshee.ThickClient/Banshee.Gui.Widgets/TaskStatusIcon.cs
@@ -35,6 +35,7 @@ using Mono.Unix;
using Gtk;
using Hyena;
+using Hyena.Jobs;
using Hyena.Widgets;
using Banshee.Base;
@@ -44,8 +45,7 @@ namespace Banshee.Gui.Widgets
{
public class TaskStatusIcon : AnimatedImage
{
- //private Dictionary<IUserJob, UserJobTile> job_tiles = new Dictionary<IUserJob, UserJobTile> ();
- private List<IUserJob> jobs = new List<IUserJob> ();
+ private List<Job> jobs = new List<Job> ();
public TaskStatusIcon ()
{
@@ -61,7 +61,7 @@ namespace Banshee.Gui.Widgets
}
// Listen for jobs
- UserJobManager job_manager = ServiceManager.Get<UserJobManager> ();
+ JobScheduler job_manager = ServiceManager.Get<JobScheduler> ();
job_manager.JobAdded += OnJobAdded;
job_manager.JobRemoved += OnJobRemoved;
@@ -73,7 +73,7 @@ namespace Banshee.Gui.Widgets
lock (jobs) {
if (jobs.Count > 0) {
StringBuilder sb = new StringBuilder ();
- foreach (IUserJob job in jobs) {
+ foreach (Job job in jobs) {
sb.AppendFormat ("\n<i>{0}</i>", job.Title);
}
@@ -131,7 +131,7 @@ namespace Banshee.Gui.Widgets
return false;
}
- private void AddJob (IUserJob job)
+ private void AddJob (Job job)
{
lock (jobs) {
if (job == null || !job.IsBackground || job.IsFinished) {
@@ -144,12 +144,12 @@ namespace Banshee.Gui.Widgets
ThreadAssist.ProxyToMain (Update);
}
- private void OnJobAdded (object o, UserJobEventArgs args)
+ private void OnJobAdded (Job job)
{
- AddJob (args.Job);
+ AddJob (job);
}
- private void RemoveJob (IUserJob job)
+ private void RemoveJob (Job job)
{
lock (jobs) {
if (jobs.Contains (job)) {
@@ -160,9 +160,9 @@ namespace Banshee.Gui.Widgets
ThreadAssist.ProxyToMain (Update);
}
- private void OnJobRemoved (object o, UserJobEventArgs args)
+ private void OnJobRemoved (Job job)
{
- RemoveJob (args.Job);
+ RemoveJob (job);
}
}
}
diff --git a/src/Core/Banshee.ThickClient/Banshee.Gui.Widgets/UserJobTile.cs b/src/Core/Banshee.ThickClient/Banshee.Gui.Widgets/UserJobTile.cs
index 24ef9d6..1a1eb42 100644
--- a/src/Core/Banshee.ThickClient/Banshee.Gui.Widgets/UserJobTile.cs
+++ b/src/Core/Banshee.ThickClient/Banshee.Gui.Widgets/UserJobTile.cs
@@ -30,15 +30,17 @@ using System;
using Mono.Unix;
using Gtk;
+using Hyena.Jobs;
+using Hyena.Gui;
+
using Banshee.Base;
using Banshee.ServiceStack;
-using Hyena.Gui;
namespace Banshee.Gui.Widgets
{
public class UserJobTile : Table
{
- private IUserJob job;
+ private Job job;
private string [] icon_names;
private string title;
@@ -56,7 +58,7 @@ namespace Banshee.Gui.Widgets
Banshee.Widgets.HigMessageDialog cancel_dialog;
- public UserJobTile (IUserJob job) : base (3, 2, false)
+ public UserJobTile (Job job) : base (3, 2, false)
{
this.job = job;
this.job.Updated += OnJobUpdated;
@@ -151,7 +153,7 @@ namespace Banshee.Gui.Widgets
if (cancel_dialog.Run () == (int)ResponseType.Yes) {
if (job.CanCancel) {
- job.Cancel ();
+ ServiceManager.JobScheduler.Cancel (job);
}
}
diff --git a/src/Core/Banshee.ThickClient/Banshee.Gui.Widgets/UserJobTileHost.cs b/src/Core/Banshee.ThickClient/Banshee.Gui.Widgets/UserJobTileHost.cs
index c118334..ffc1b05 100644
--- a/src/Core/Banshee.ThickClient/Banshee.Gui.Widgets/UserJobTileHost.cs
+++ b/src/Core/Banshee.ThickClient/Banshee.Gui.Widgets/UserJobTileHost.cs
@@ -31,6 +31,7 @@ using System.Collections.Generic;
using Gtk;
+using Hyena.Jobs;
using Hyena.Widgets;
using Hyena.Gui.Theatrics;
@@ -42,8 +43,8 @@ namespace Banshee.Gui.Widgets
public class UserJobTileHost : Alignment
{
private AnimatedVBox box;
- private Dictionary<IUserJob, UserJobTile> job_tiles = new Dictionary<IUserJob, UserJobTile> ();
- private Dictionary<IUserJob, DateTime> job_start_times = new Dictionary<IUserJob, DateTime> ();
+ private Dictionary<Job, UserJobTile> job_tiles = new Dictionary<Job, UserJobTile> ();
+ private Dictionary<Job, DateTime> job_start_times = new Dictionary<Job, DateTime> ();
public UserJobTileHost () : base (0.0f, 0.0f, 1.0f, 1.0f)
{
@@ -57,8 +58,8 @@ namespace Banshee.Gui.Widgets
Add (box);
ShowAll ();
- if (ServiceManager.Contains<UserJobManager> ()) {
- UserJobManager job_manager = ServiceManager.Get<UserJobManager> ();
+ if (ServiceManager.Contains<JobScheduler> ()) {
+ JobScheduler job_manager = ServiceManager.Get<JobScheduler> ();
job_manager.JobAdded += OnJobAdded;
job_manager.JobRemoved += OnJobRemoved;
}
@@ -72,7 +73,7 @@ namespace Banshee.Gui.Widgets
}
}
- private void AddJob (IUserJob job)
+ private void AddJob (Job job)
{
lock (this) {
if (job == null || job.IsFinished) {
@@ -90,26 +91,26 @@ namespace Banshee.Gui.Widgets
}
}
- private void OnJobAdded (object o, UserJobEventArgs args)
+ private void OnJobAdded (Job job)
{
- if (args.Job.IsBackground) {
+ if (job.IsBackground) {
return;
}
ThreadAssist.ProxyToMain (delegate {
- if (args.Job.DelayShow) {
+ if (job.DelayShow) {
// Give the Job 1 second to become more than 33% complete
Banshee.ServiceStack.Application.RunTimeout (1000, delegate {
- AddJob (args.Job);
+ AddJob (job);
return false;
});
} else {
- AddJob (args.Job);
+ AddJob (job);
}
});
}
- private void RemoveJob (IUserJob job)
+ private void RemoveJob (Job job)
{
lock (this) {
if (job_tiles.ContainsKey (job)) {
@@ -122,23 +123,23 @@ namespace Banshee.Gui.Widgets
}
}
- private void OnJobRemoved (object o, UserJobEventArgs args)
+ private void OnJobRemoved (Job job)
{
ThreadAssist.ProxyToMain (delegate {
lock (this) {
- if (job_start_times.ContainsKey (args.Job)) {
- double ms_since_added = (DateTime.Now - job_start_times[args.Job]).TotalMilliseconds;
+ if (job_start_times.ContainsKey (job)) {
+ double ms_since_added = (DateTime.Now - job_start_times[job]).TotalMilliseconds;
if (ms_since_added < 1000) {
// To avoid user jobs flasing up and out, don't let any job be visible for less than 1 second
Banshee.ServiceStack.Application.RunTimeout ((uint) (1000 - ms_since_added), delegate {
- RemoveJob (args.Job);
+ RemoveJob (job);
return false;
});
return;
}
}
- RemoveJob (args.Job);
+ RemoveJob (job);
}
});
}
diff --git a/src/Dap/Banshee.Dap.Ipod/Banshee.Dap.Ipod/DatabaseRebuilder.cs b/src/Dap/Banshee.Dap.Ipod/Banshee.Dap.Ipod/DatabaseRebuilder.cs
index 1534345..53ad203 100644
--- a/src/Dap/Banshee.Dap.Ipod/Banshee.Dap.Ipod/DatabaseRebuilder.cs
+++ b/src/Dap/Banshee.Dap.Ipod/Banshee.Dap.Ipod/DatabaseRebuilder.cs
@@ -34,6 +34,7 @@ using Mono.Unix;
using IPod;
using Hyena;
+using Hyena.Jobs;
using Banshee.Base;
using Banshee.ServiceStack;
@@ -89,6 +90,8 @@ namespace Banshee.Dap.Ipod
this.source = source;
user_job = new UserJob (Catalog.GetString ("Rebuilding Database"));
+ user_job.PriorityHints = PriorityHints.SpeedSensitive | PriorityHints.DataLossIfStopped;
+ user_job.SetResources (Resource.Disk, Resource.Cpu);
user_job.Title = Catalog.GetString ("Rebuilding Database");
user_job.Status = Catalog.GetString ("Scanning iPod...");
user_job.IconNames = source._GetIconNames ();
diff --git a/src/Extensions/Banshee.AudioCd/Banshee.AudioCd/AudioCdRipper.cs b/src/Extensions/Banshee.AudioCd/Banshee.AudioCd/AudioCdRipper.cs
index 3a2af96..370a4ab 100644
--- a/src/Extensions/Banshee.AudioCd/Banshee.AudioCd/AudioCdRipper.cs
+++ b/src/Extensions/Banshee.AudioCd/Banshee.AudioCd/AudioCdRipper.cs
@@ -32,6 +32,8 @@ using System.Collections.Generic;
using Mono.Unix;
using Mono.Addins;
+using Hyena.Jobs;
+
using Banshee.Base;
using Banshee.ServiceStack;
using Banshee.Collection;
@@ -119,6 +121,8 @@ namespace Banshee.AudioCd
user_job.CancelMessage = String.Format (Catalog.GetString (
"<i>{0}</i> is still being imported into the music library. Would you like to stop it?"
), GLib.Markup.EscapeText (source.DiscModel.Title));
+ user_job.SetResources (Resource.Cpu);
+ user_job.PriorityHints = PriorityHints.SpeedSensitive | PriorityHints.DataLossIfStopped;
user_job.CanCancel = true;
user_job.CancelRequested += OnCancelRequested;
user_job.Finished += OnFinished;
diff --git a/src/Extensions/Banshee.Bpm/Banshee.Bpm/BpmDetectJob.cs b/src/Extensions/Banshee.Bpm/Banshee.Bpm/BpmDetectJob.cs
index b65ed85..e673d3a 100644
--- a/src/Extensions/Banshee.Bpm/Banshee.Bpm/BpmDetectJob.cs
+++ b/src/Extensions/Banshee.Bpm/Banshee.Bpm/BpmDetectJob.cs
@@ -35,6 +35,7 @@ using Mono.Unix;
using Mono.Addins;
using Hyena;
+using Hyena.Jobs;
using Hyena.Data.Sqlite;
using Banshee.Base;
@@ -53,6 +54,9 @@ namespace Banshee.Bpm
private int current_track_id;
private IBpmDetector detector;
private PrimarySource music_library;
+ private ManualResetEvent result_ready_event = new ManualResetEvent (false);
+ private SafeUri result_uri;
+ private int result_bpm;
private static HyenaSqliteCommand update_query = new HyenaSqliteCommand (
"UPDATE CoreTracks SET BPM = ?, DateUpdatedStamp = ? WHERE TrackID = ?");
@@ -61,6 +65,8 @@ namespace Banshee.Bpm
{
IconNames = new string [] {"audio-x-generic"};
IsBackground = true;
+ SetResources (Resource.Cpu, Resource.Disk);
+ PriorityHints = PriorityHints.LongRunning;
music_library = ServiceManager.SourceManager.MusicLibrary;
@@ -85,33 +91,52 @@ namespace Banshee.Bpm
detector.FileFinished += OnFileFinished;
}
- protected override void Cleanup ()
+ protected override void OnCancelled ()
{
- Finish ();
+ Cleanup ();
+ result_ready_event.Set ();
+ }
+ protected override void Cleanup ()
+ {
if (detector != null) {
+ detector.FileFinished -= OnFileFinished;
detector.Dispose ();
+ detector = null;
}
+
+ base.Cleanup ();
}
protected override void IterateCore (HyenaDataReader reader)
{
SafeUri uri = new SafeUri (reader.Get<string> (0));
current_track_id = reader.Get<int> (1);
+
+ // Wait for the result to be ready
+ result_ready_event.Reset ();
detector.ProcessFile (uri);
- }
+ result_ready_event.WaitOne ();
- private void OnFileFinished (SafeUri uri, int bpm)
- {
- if (bpm > 0) {
- Log.DebugFormat ("Saving BPM of {0} for {1}", bpm, uri);
- ServiceManager.DbConnection.Execute (update_query, bpm, DateTime.Now, current_track_id);
+ if (IsCancelRequested) {
+ return;
+ }
+
+ if (result_bpm > 0) {
+ Log.DebugFormat ("Saving BPM of {0} for {1}", result_bpm, result_uri);
+ ServiceManager.DbConnection.Execute (update_query, result_bpm, DateTime.Now, current_track_id);
} else {
ServiceManager.DbConnection.Execute (update_query, -1, DateTime.Now, current_track_id);
- Log.DebugFormat ("Unable to detect BPM for {0}", uri);
+ Log.DebugFormat ("Unable to detect BPM for {0}", result_uri);
}
+ }
- Iterate ();
+ private void OnFileFinished (SafeUri uri, int bpm)
+ {
+ // This is run on the main thread b/c of GStreamer, so do as little as possible here
+ result_uri = uri;
+ result_bpm = bpm;
+ result_ready_event.Set ();
}
internal static IBpmDetector GetDetector ()
diff --git a/src/Extensions/Banshee.Bpm/Banshee.Bpm/BpmService.cs b/src/Extensions/Banshee.Bpm/Banshee.Bpm/BpmService.cs
index 1d719cf..1e0f8f3 100644
--- a/src/Extensions/Banshee.Bpm/Banshee.Bpm/BpmService.cs
+++ b/src/Extensions/Banshee.Bpm/Banshee.Bpm/BpmService.cs
@@ -121,7 +121,6 @@ namespace Banshee.Bpm
}
job.Finished += delegate { job = null; };
- job.RunAsync ();
}
private void OnTracksAdded (Source sender, TrackEventArgs args)
@@ -167,7 +166,7 @@ namespace Banshee.Bpm
Detect ();
} else {
if (job != null) {
- job.Cancel ();
+ ServiceManager.JobScheduler.Cancel (job);
}
}
}
diff --git a/src/Extensions/Banshee.CoverArt/Banshee.CoverArt/CoverArtJob.cs b/src/Extensions/Banshee.CoverArt/Banshee.CoverArt/CoverArtJob.cs
index b385b95..47171b0 100644
--- a/src/Extensions/Banshee.CoverArt/Banshee.CoverArt/CoverArtJob.cs
+++ b/src/Extensions/Banshee.CoverArt/Banshee.CoverArt/CoverArtJob.cs
@@ -36,6 +36,7 @@ using Mono.Unix;
using Gtk;
using Hyena;
+using Hyena.Jobs;
using Hyena.Data.Sqlite;
using Banshee.Base;
@@ -49,37 +50,10 @@ using Banshee.Library;
namespace Banshee.CoverArt
{
- public class CoverArtJob : UserJob, IJob
+ public class CoverArtJob : DbIteratorJob
{
- private const int BatchSize = 10;
-
private DateTime last_scan = DateTime.MinValue;
private TimeSpan retry_every = TimeSpan.FromDays (7);
-
- private static HyenaSqliteCommand count_query = new HyenaSqliteCommand (@"
- SELECT count(DISTINCT CoreTracks.AlbumID)
- FROM CoreTracks, CoreArtists, CoreAlbums
- WHERE
- CoreTracks.PrimarySourceID = ? AND
- CoreTracks.DateUpdatedStamp > ? AND
- CoreTracks.AlbumID = CoreAlbums.AlbumID AND
- CoreAlbums.ArtistID = CoreArtists.ArtistID AND
- CoreTracks.AlbumID NOT IN (
- SELECT AlbumID FROM CoverArtDownloads WHERE
- LastAttempt > ? OR Downloaded = 1)");
-
- private static HyenaSqliteCommand select_query = new HyenaSqliteCommand (@"
- SELECT DISTINCT CoreAlbums.AlbumID, CoreAlbums.Title, CoreArtists.Name, CoreTracks.Uri
- FROM CoreTracks, CoreArtists, CoreAlbums
- WHERE
- CoreTracks.PrimarySourceID = ? AND
- CoreTracks.DateUpdatedStamp > ? AND
- CoreTracks.AlbumID = CoreAlbums.AlbumID AND
- CoreAlbums.ArtistID = CoreArtists.ArtistID AND
- CoreTracks.AlbumID NOT IN (
- SELECT AlbumID FROM CoverArtDownloads WHERE
- LastAttempt > ? OR Downloaded = 1)
- GROUP BY CoreTracks.AlbumID LIMIT ?");
public CoverArtJob (DateTime lastScan) : base (Catalog.GetString ("Downloading Cover Art"))
{
@@ -91,75 +65,67 @@ namespace Banshee.CoverArt
last_scan = DateTime.Now - TimeSpan.FromDays (300);
}
+ CountCommand = new HyenaSqliteCommand (@"
+ SELECT count(DISTINCT CoreTracks.AlbumID)
+ FROM CoreTracks, CoreArtists, CoreAlbums
+ WHERE
+ CoreTracks.PrimarySourceID = ? AND
+ CoreTracks.DateUpdatedStamp > ? AND
+ CoreTracks.AlbumID = CoreAlbums.AlbumID AND
+ CoreAlbums.ArtistID = CoreArtists.ArtistID AND
+ CoreTracks.AlbumID NOT IN (
+ SELECT AlbumID FROM CoverArtDownloads WHERE
+ LastAttempt > ? OR Downloaded = 1)",
+ ServiceManager.SourceManager.MusicLibrary.DbId, last_scan, last_scan - retry_every
+ );
+
+ SelectCommand = new HyenaSqliteCommand (@"
+ SELECT DISTINCT CoreAlbums.AlbumID, CoreAlbums.Title, CoreArtists.Name, CoreTracks.Uri
+ FROM CoreTracks, CoreArtists, CoreAlbums
+ WHERE
+ CoreTracks.PrimarySourceID = ? AND
+ CoreTracks.DateUpdatedStamp > ? AND
+ CoreTracks.AlbumID = CoreAlbums.AlbumID AND
+ CoreAlbums.ArtistID = CoreArtists.ArtistID AND
+ CoreTracks.AlbumID NOT IN (
+ SELECT AlbumID FROM CoverArtDownloads WHERE
+ LastAttempt > ? OR Downloaded = 1)
+ GROUP BY CoreTracks.AlbumID LIMIT ?",
+ ServiceManager.SourceManager.MusicLibrary.DbId, last_scan, last_scan - retry_every, 1
+ );
+
+ SetResources (Resource.Database);
+ PriorityHints = PriorityHints.LongRunning;
+
IsBackground = true;
CanCancel = true;
DelayShow = true;
-
- CancelRequested += delegate {
- Finish ();
- };
}
public void Start ()
{
Register ();
- Scheduler.Schedule (this, JobPriority.Lowest);
}
- private HyenaDataReader RunQuery ()
+ protected override void IterateCore (HyenaDataReader reader)
{
- return new HyenaDataReader (ServiceManager.DbConnection.Query (select_query,
- ServiceManager.SourceManager.MusicLibrary.DbId, last_scan, last_scan - retry_every, BatchSize
- ));
- }
-
- public void Run ()
- {
- Status = Catalog.GetString ("Preparing...");
- IconNames = new string [] {Stock.Network};
-
- int current = 0;
- int total = 0;
+ DatabaseTrackInfo track = new DatabaseTrackInfo ();
- try {
- DatabaseTrackInfo track = new DatabaseTrackInfo ();
- while (true) {
- total = current + ServiceManager.DbConnection.Query<int> (count_query, ServiceManager.SourceManager.MusicLibrary.DbId, last_scan, last_scan - retry_every);
- if (total == 0 || total <= current) {
- break;
- }
-
- using (HyenaDataReader reader = RunQuery ()) {
- while (reader.Read ()) {
- if (IsCancelRequested) {
- Finish ();
- return;
- }
-
- track.AlbumTitle = reader.Get<string> (1);
- track.ArtistName = reader.Get<string> (2);
- track.PrimarySource = ServiceManager.SourceManager.MusicLibrary;
- track.Uri = new SafeUri (reader.Get<string> (3));
- track.AlbumId = reader.Get<int> (0);
- //Console.WriteLine ("have album {0}/{1} for track uri {2}", track.AlbumId, track.AlbumTitle, track.Uri);
-
- Progress = (double) current / (double) total;
- Status = String.Format (Catalog.GetString ("{0} - {1}"), track.ArtistName, track.AlbumTitle);
-
- FetchForTrack (track);
- current++;
- }
- }
- }
- } catch (Exception e) {
- Log.Exception (e);
- }
-
- Finish ();
+ track.AlbumTitle = reader.Get<string> (1);
+ track.ArtistName = reader.Get<string> (2);
+ track.PrimarySource = ServiceManager.SourceManager.MusicLibrary;
+ track.Uri = new SafeUri (reader.Get<string> (3));
+ track.AlbumId = reader.Get<int> (0);
+ //Console.WriteLine ("have album {0}/{1} for track uri {2}", track.AlbumId, track.AlbumTitle, track.Uri);
+
+ Status = String.Format (Catalog.GetString ("{0} - {1}"), track.ArtistName, track.AlbumTitle);
+
+ FetchForTrack (track);
}
private void FetchForTrack (DatabaseTrackInfo track)
{
+ bool save = true;
try {
if (String.IsNullOrEmpty (track.AlbumTitle) || track.AlbumTitle == Catalog.GetString ("Unknown Album") ||
String.IsNullOrEmpty (track.ArtistName) || track.ArtistName == Catalog.GetString ("Unknown Artist")) {
@@ -168,14 +134,24 @@ namespace Banshee.CoverArt
IMetadataLookupJob job = MetadataService.Instance.CreateJob (track);
job.Run ();
}
+ } catch (System.Threading.ThreadAbortException) {
+ save = false;
+ throw;
} catch (Exception e) {
Log.Exception (e);
} finally {
- bool have_cover_art = CoverArtSpec.CoverExists (track.ArtistName, track.AlbumTitle);
- ServiceManager.DbConnection.Execute (
- "INSERT OR REPLACE INTO CoverArtDownloads (AlbumID, Downloaded, LastAttempt) VALUES (?, ?, ?)",
- track.AlbumId, have_cover_art, DateTime.Now);
+ if (save) {
+ bool have_cover_art = CoverArtSpec.CoverExists (track.ArtistName, track.AlbumTitle);
+ ServiceManager.DbConnection.Execute (
+ "INSERT OR REPLACE INTO CoverArtDownloads (AlbumID, Downloaded, LastAttempt) VALUES (?, ?, ?)",
+ track.AlbumId, have_cover_art, DateTime.Now);
+ }
}
}
+
+ protected override void OnCancelled ()
+ {
+ AbortThread ();
+ }
}
}
diff --git a/src/Extensions/Banshee.CoverArt/Banshee.CoverArt/CoverArtService.cs b/src/Extensions/Banshee.CoverArt/Banshee.CoverArt/CoverArtService.cs
index ac063aa..1e57654 100644
--- a/src/Extensions/Banshee.CoverArt/Banshee.CoverArt/CoverArtService.cs
+++ b/src/Extensions/Banshee.CoverArt/Banshee.CoverArt/CoverArtService.cs
@@ -172,8 +172,9 @@ namespace Banshee.CoverArt
}
job = new CoverArtJob (last_scan);
job.Finished += delegate {
- DatabaseConfigurationClient.Client.Set<DateTime> ("last_cover_art_scan",
- DateTime.Now);
+ if (!job.IsCancelRequested) {
+ DatabaseConfigurationClient.Client.Set<DateTime> ("last_cover_art_scan", DateTime.Now);
+ }
job = null;
};
job.Start ();
diff --git a/src/Libraries/Hyena/Hyena.Jobs/Job.cs b/src/Libraries/Hyena/Hyena.Jobs/Job.cs
new file mode 100644
index 0000000..cc477fc
--- /dev/null
+++ b/src/Libraries/Hyena/Hyena.Jobs/Job.cs
@@ -0,0 +1,312 @@
+//
+// Job.cs
+//
+// Author:
+// Gabriel Burt <gburt novell com>
+//
+// Copyright (C) 2009 Novell, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Linq;
+using System.Threading;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+
+namespace Hyena.Jobs
+{
+ public enum JobState {
+ None,
+ Scheduled,
+ Running,
+ Paused,
+ Cancelled,
+ Completed
+ };
+
+ public class Job
+ {
+ public event EventHandler Updated;
+ public event EventHandler Finished;
+ public event EventHandler CancelRequested;
+
+ private int update_freeze_ref;
+ private JobState state = JobState.None;
+
+ private ManualResetEvent pause_event;
+ private DateTime created_at = DateTime.Now;
+ private TimeSpan run_time = TimeSpan.Zero;
+ private Object sync = new Object ();
+
+ public bool IsCancelRequested { get; private set; }
+
+#region Internal Properties
+
+ internal bool IsScheduled {
+ get { return state == JobState.Scheduled; }
+ }
+
+ internal bool IsRunning {
+ get { return state == JobState.Running; }
+ }
+
+ internal bool IsPaused {
+ get { return state == JobState.Paused; }
+ }
+
+ public bool IsFinished {
+ get {
+ lock (sync) {
+ return state == JobState.Cancelled || state == JobState.Completed;
+ }
+ }
+ }
+
+ internal DateTime CreatedAt {
+ get { return created_at; }
+ }
+
+ internal TimeSpan RunTime {
+ get { return run_time; }
+ }
+
+#endregion
+
+#region Scheduler Methods
+
+ internal void Start ()
+ {
+ Log.Debug ("Starting", Title);
+ lock (sync) {
+ if (state != JobState.Scheduled && state != JobState.Paused) {
+ Log.DebugFormat ("Job {0} in {1} state is not runnable", Title, state);
+ return;
+ }
+
+ State = JobState.Running;
+
+ if (pause_event != null) {
+ pause_event.Set ();
+ }
+
+ RunJob ();
+ }
+ }
+
+ internal void Cancel ()
+ {
+ lock (sync) {
+ if (!IsFinished) {
+ IsCancelRequested = true;
+ State = JobState.Cancelled;
+ EventHandler handler = CancelRequested;
+ if (handler != null) {
+ handler (this, EventArgs.Empty);
+ }
+ }
+ }
+ Log.Debug ("Canceled", Title);
+ }
+
+ internal void Preempt ()
+ {
+ Log.Debug ("Preemptd", Title);
+ Pause (false);
+ }
+
+ internal bool Pause ()
+ {
+ Log.Debug ("Pausing ", Title);
+ return Pause (true);
+ }
+
+ private bool Pause (bool unschedule)
+ {
+ lock (sync) {
+ if (IsFinished) {
+ Log.DebugFormat ("Job {0} in {1} state is not pausable", Title, state);
+ return false;
+ }
+
+ State = unschedule ? JobState.Paused : JobState.Scheduled;
+ if (pause_event != null) {
+ pause_event.Reset ();
+ }
+ }
+
+ return true;
+ }
+
+#endregion
+
+ private string title;
+ private string status;
+ private string [] icon_names;
+ private double progress;
+
+#region Public Properties
+
+ public string Title {
+ get { return title; }
+ set {
+ title = value;
+ OnUpdated ();
+ }
+ }
+
+ public string Status {
+ get { return status; }
+ set {
+ status = value;
+ OnUpdated ();
+ }
+ }
+
+ public double Progress {
+ get { return progress; }
+ set {
+ progress = Math.Max (0.0, Math.Min (1.0, value));
+ OnUpdated ();
+ }
+ }
+
+ public string [] IconNames {
+ get { return icon_names; }
+ set {
+ if (value != null) {
+ icon_names = value;
+ OnUpdated ();
+ }
+ }
+ }
+
+ public bool IsBackground { get; set; }
+ public bool CanCancel { get; set; }
+ public string CancelMessage { get; set; }
+ public bool DelayShow { get; set; }
+
+ public PriorityHints PriorityHints { get; set; }
+ public IEnumerable<Resource> Resources { get; protected set; }
+
+ public JobState State {
+ get { return state; }
+ internal set {
+ state = value;
+ OnUpdated ();
+ }
+ }
+
+ public void SetResources (params Resource [] resources)
+ {
+ Resources = resources;
+ }
+
+#endregion
+
+#region Constructor
+
+ public Job () : this (null, PriorityHints.None)
+ {
+ }
+
+ public Job (string title, PriorityHints hints, params Resource [] resources)
+ {
+ Title = title;
+ PriorityHints = hints;
+ Resources = resources;
+ }
+
+#endregion
+
+#region Abstract Methods
+
+ protected virtual void RunJob ()
+ {
+ }
+
+#endregion
+
+#region Protected Methods
+
+ public void Update (string title, string status, double progress)
+ {
+ Title = title;
+ Status = status;
+ Progress = progress;
+ }
+
+ protected void FreezeUpdate ()
+ {
+ System.Threading.Interlocked.Increment (ref update_freeze_ref);
+ }
+
+ protected void ThawUpdate (bool raiseUpdate)
+ {
+ System.Threading.Interlocked.Decrement (ref update_freeze_ref);
+ if (raiseUpdate) {
+ OnUpdated ();
+ }
+ }
+
+ protected void OnUpdated ()
+ {
+ if (update_freeze_ref != 0) {
+ return;
+ }
+
+ EventHandler handler = Updated;
+ if (handler != null) {
+ handler (this, EventArgs.Empty);
+ }
+ }
+
+ public void YieldToScheduler ()
+ {
+ if (IsPaused || IsScheduled) {
+ if (pause_event == null) {
+ pause_event = new ManualResetEvent (false);
+ }
+
+ pause_event.WaitOne ();
+ }
+ }
+
+ protected void OnFinished ()
+ {
+ Log.Debug ("Finished", Title);
+ pause_event = null;
+
+ if (state != JobState.Cancelled) {
+ State = JobState.Completed;
+ }
+
+ EventHandler handler = Finished;
+ if (handler != null) {
+ handler (this, EventArgs.Empty);
+ }
+ }
+
+#endregion
+
+ internal bool HasScheduler { get; set; }
+ }
+}
diff --git a/src/Libraries/Hyena/Hyena.Jobs/JobExtensions.cs b/src/Libraries/Hyena/Hyena.Jobs/JobExtensions.cs
new file mode 100644
index 0000000..0c15568
--- /dev/null
+++ b/src/Libraries/Hyena/Hyena.Jobs/JobExtensions.cs
@@ -0,0 +1,69 @@
+//
+// JobExtensions.cs
+//
+// Author:
+// Gabriel Burt <gburt novell com>
+//
+// Copyright (C) 2009 Novell, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Linq;
+using System.Collections.Generic;
+
+namespace Hyena.Jobs
+{
+ public static class JobExtensions
+ {
+ internal static IEnumerable<T> Without<T> (this IEnumerable<T> source, PriorityHints hints) where T : Job
+ {
+ return source.Where (j => !j.Has (hints));
+ }
+
+ internal static IEnumerable<T> With<T> (this IEnumerable<T> source, PriorityHints hints) where T : Job
+ {
+ return source.Where (j => j.Has (hints));
+ }
+
+ internal static IEnumerable<T> SharingResourceWith<T> (this IEnumerable<T> source, Job job) where T : Job
+ {
+ return source.Where (j => j.Resources.Intersect (job.Resources).Any ());
+ }
+
+ public static void ForEach<T> (this IEnumerable<T> source, Action<T> func)
+ {
+ foreach (T item in source)
+ func (item);
+ }
+
+ public static bool Has<T> (this T job, PriorityHints hints) where T : Job
+ {
+ return (job.PriorityHints & hints) == hints;
+ }
+
+ // Useful..
+ /*public static bool Include (this Enum source, Enum flags)
+ {
+ return ((int)source & (int)flags) == (int)flags;
+ }*/
+ }
+}
diff --git a/src/Libraries/Hyena/Hyena.Jobs/PriorityHints.cs b/src/Libraries/Hyena/Hyena.Jobs/PriorityHints.cs
new file mode 100644
index 0000000..125b3b8
--- /dev/null
+++ b/src/Libraries/Hyena/Hyena.Jobs/PriorityHints.cs
@@ -0,0 +1,41 @@
+//
+// PriorityHints.cs
+//
+// Author:
+// Gabriel Burt <gburt novell com>
+//
+// Copyright (C) 2009 Novell, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+
+namespace Hyena.Jobs
+{
+ [Flags]
+ public enum PriorityHints
+ {
+ None = 0,
+ DataLossIfStopped = 1,
+ SpeedSensitive = 2,
+ LongRunning = 4
+ }
+}
diff --git a/src/Libraries/Hyena/Hyena.Jobs/Resource.cs b/src/Libraries/Hyena/Hyena.Jobs/Resource.cs
new file mode 100644
index 0000000..bf99a97
--- /dev/null
+++ b/src/Libraries/Hyena/Hyena.Jobs/Resource.cs
@@ -0,0 +1,43 @@
+//
+// Resource.cs
+//
+// Author:
+// Gabriel Burt <gburt novell com>
+//
+// Copyright (C) 2009 Novell, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+
+namespace Hyena.Jobs
+{
+ public class Resource
+ {
+ // Convenience Resources for programs to use
+ public static readonly Resource Cpu = new Resource { Id = "cpu", Name = "CPU" };
+ public static readonly Resource Disk = new Resource { Id = "disk", Name = "Disk" };
+ public static readonly Resource Database = new Resource { Id = "db", Name = "Database" };
+
+ public string Id { get; set; }
+ public string Name { get; set; }
+ }
+}
diff --git a/src/Libraries/Hyena/Hyena.Jobs/Scheduler.cs b/src/Libraries/Hyena/Hyena.Jobs/Scheduler.cs
new file mode 100644
index 0000000..d18f713
--- /dev/null
+++ b/src/Libraries/Hyena/Hyena.Jobs/Scheduler.cs
@@ -0,0 +1,230 @@
+//
+// Scheduler.cs
+//
+// Author:
+// Gabriel Burt <gburt novell com>
+//
+// Copyright (C) 2009 Novell, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Linq;
+using System.Threading;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+
+using Hyena;
+
+namespace Hyena.Jobs
+{
+ public class Scheduler
+ {
+ private List<Job> jobs;
+
+ public event Action<Job> JobAdded;
+ public event Action<Job> JobRemoved;
+
+ public IEnumerable<Job> Jobs { get; private set; }
+
+ public int JobCount {
+ get { lock (jobs) { return jobs.Count; } }
+ }
+
+ public bool HasAnyDataLossJobs {
+ get {
+ lock (jobs) {
+ return jobs.With (PriorityHints.DataLossIfStopped).Any ();
+ }
+ }
+ }
+
+ public Scheduler ()
+ {
+ jobs = new List<Job> ();
+ Jobs = new ReadOnlyCollection<Job> (jobs);
+ }
+
+ public void Add (Job job)
+ {
+ lock (jobs) {
+ lock (job) {
+ if (jobs.Contains (job) || job.HasScheduler) {
+ throw new ArgumentException ("Job not schedulable", "job");
+ }
+
+ job.HasScheduler = true;
+ }
+
+ jobs.Add (job);
+ job.State = JobState.Scheduled;
+ job.Finished += OnJobFinished;
+
+ if (CanStart (job)) {
+ StartJob (job);
+ }
+ }
+
+ Action<Job> handler = JobAdded;
+ if (handler != null) {
+ handler (job);
+ }
+ }
+
+ public void Cancel (Job job)
+ {
+ lock (jobs) {
+ if (jobs.Contains (job)) {
+ // Cancel will call OnJobFinished which will call Schedule
+ job.Cancel ();
+ }
+ }
+ }
+
+ public void Pause (Job job)
+ {
+ lock (jobs) {
+ if (jobs.Contains (job)) {
+ if (job.Pause ()) {
+ // See if any scheduled jobs can now be started
+ Schedule ();
+ }
+ }
+ }
+ }
+
+ public void Resume (Job job)
+ {
+ lock (jobs) {
+ if (jobs.Contains (job) && CanStartJob (job, true)) {
+ StartJob (job);
+ }
+ }
+ }
+
+ public void CancelAll (bool evenDataLossJobs)
+ {
+ lock (jobs) {
+ List<Job> jobs_copy = new List<Job> (jobs);
+ foreach (var job in jobs_copy) {
+ if (evenDataLossJobs || !job.Has (PriorityHints.DataLossIfStopped)) {
+ job.Cancel ();
+ }
+ }
+ }
+ }
+
+ private void OnJobFinished (object o, EventArgs args)
+ {
+ Job job = o as Job;
+
+ lock (jobs) {
+ jobs.Remove (job);
+ }
+
+ Action<Job> handler = JobRemoved;
+ if (handler != null) {
+ handler (job);
+ }
+
+ Schedule ();
+ }
+
+ private void Schedule ()
+ {
+ lock (jobs) {
+ // First try to start any non-LongRunning jobs
+ jobs.Without (PriorityHints.LongRunning)
+ .Where (CanStart)
+ .ForEach (StartJob);
+
+ // Then start any LongRunning ones
+ jobs.With (PriorityHints.LongRunning)
+ .Where (CanStart)
+ .ForEach (StartJob);
+ }
+ }
+
+#region Job Query helpers
+
+ private bool IsRunning (Job job)
+ {
+ return job.IsRunning;
+ }
+
+ private bool CanStart (Job job)
+ {
+ return CanStartJob (job, false);
+ }
+
+ private bool CanStartJob (Job job, bool pausedJob)
+ {
+ if (!job.IsScheduled && !(pausedJob && job.IsPaused))
+ return false;
+
+ if (job.Has (PriorityHints.SpeedSensitive))
+ return true;
+
+ // Run only one non-SpeedSensitive job that uses a given Resource
+ if (job.Has (PriorityHints.LongRunning))
+ return jobs.Where (IsRunning)
+ .SharingResourceWith (job)
+ .Any () == false;
+
+ // With the exception that non-LongRunning jobs will preempt LongRunning ones
+ return jobs.Where (IsRunning)
+ .Without (PriorityHints.LongRunning)
+ .SharingResourceWith (job)
+ .Any () == false;
+ }
+
+ private void StartJob (Job job)
+ {
+ ConflictingJobs (job).ForEach (PreemptJob);
+ job.Start ();
+ }
+
+ private void PreemptJob (Job job)
+ {
+ job.Preempt ();
+ }
+
+ private IEnumerable<Job> ConflictingJobs (Job job)
+ {
+ if (job.Has (PriorityHints.SpeedSensitive)) {
+ // Preempt non-SpeedSensitive jobs that use the same Resource(s)
+ return jobs.Where (IsRunning)
+ .Without (PriorityHints.SpeedSensitive)
+ .SharingResourceWith (job);
+ } else if (!job.Has (PriorityHints.LongRunning)) {
+ // Preempt any LongRunning jobs that use the same Resource(s)
+ return jobs.Where (IsRunning)
+ .With (PriorityHints.LongRunning)
+ .SharingResourceWith (job);
+ }
+
+ return Enumerable.Empty<Job> ();
+ }
+
+#endregion
+
+ }
+}
diff --git a/src/Libraries/Hyena/Hyena.Jobs/SimpleAsyncJob.cs b/src/Libraries/Hyena/Hyena.Jobs/SimpleAsyncJob.cs
new file mode 100644
index 0000000..0b0df75
--- /dev/null
+++ b/src/Libraries/Hyena/Hyena.Jobs/SimpleAsyncJob.cs
@@ -0,0 +1,79 @@
+//
+// SimpleAsyncJob.cs
+//
+// Author:
+// Gabriel Burt <gburt novell com>
+//
+// Copyright (C) 2009 Novell, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Linq;
+using System.Threading;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+
+namespace Hyena.Jobs
+{
+ public abstract class SimpleAsyncJob : Job
+ {
+ private Thread thread;
+
+ public SimpleAsyncJob ()
+ {
+ }
+
+ public SimpleAsyncJob (string name, PriorityHints hints, params Resource [] resources)
+ : base (name, hints, resources)
+ {
+ }
+
+ protected override void RunJob ()
+ {
+ if (thread == null) {
+ thread = new Thread (InnerStart);
+ thread.Name = String.Format ("Hyena.Jobs.JobRunner ({0})", Title);
+ thread.Priority = this.Has (PriorityHints.SpeedSensitive) ? ThreadPriority.Normal : ThreadPriority.Lowest;
+ thread.Start ();
+ }
+ }
+
+ protected void AbortThread ()
+ {
+ if (thread != null) {
+ thread.Abort ();
+ }
+ }
+
+ private void InnerStart ()
+ {
+ try {
+ Run ();
+ } catch (ThreadAbortException) {
+ } catch (Exception e) {
+ Log.Exception (e);
+ }
+ }
+
+ protected abstract void Run ();
+ }
+}
diff --git a/src/Libraries/Hyena/Hyena.Jobs/Tests/SchedulerTests.cs b/src/Libraries/Hyena/Hyena.Jobs/Tests/SchedulerTests.cs
new file mode 100644
index 0000000..be04ce5
--- /dev/null
+++ b/src/Libraries/Hyena/Hyena.Jobs/Tests/SchedulerTests.cs
@@ -0,0 +1,203 @@
+//
+// SchedulerTests.cs
+//
+// Author:
+// Gabriel Burt <gburt novell com>
+//
+// Copyright (C) 2009 Novell, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+#if ENABLE_TESTS
+
+using System;
+using System.Linq;
+using System.Threading;
+
+using NUnit.Framework;
+
+using Hyena;
+
+namespace Hyena.Jobs
+{
+ [TestFixture]
+ public class SchedulerTests
+ {
+ private Scheduler scheduler;
+
+ [SetUp]
+ public void Setup ()
+ {
+ //Log.Debugging = true;
+ TestJob.job_count = 0;
+ Log.Debug ("New job scheduler test");
+ }
+
+ [TearDown]
+ public void TearDown ()
+ {
+ if (scheduler != null) {
+ // Ensure the scheduler's jobs are all finished, otherwise
+ // their job threads will be killed, throwing an exception
+ while (scheduler.JobCount > 0);
+ }
+
+ //Log.Debugging = false;
+ }
+
+ [Test]
+ public void TestSimultaneousSpeedJobs ()
+ {
+ scheduler = new Scheduler ();
+ scheduler.Add (new TestJob (200, PriorityHints.SpeedSensitive, Resource.Cpu, Resource.Disk));
+ scheduler.Add (new TestJob (200, PriorityHints.SpeedSensitive, Resource.Cpu, Resource.Disk));
+ scheduler.Add (new TestJob (200, PriorityHints.None, Resource.Cpu, Resource.Disk));
+
+ // Test that two SpeedSensitive jobs with the same Resources will run simultaneously
+ AssertJobsRunning (2);
+
+ // but that the third that isn't SpeedSensitive won't run until they are both done
+ while (scheduler.JobCount > 1);
+ Assert.AreEqual (PriorityHints.None, scheduler.Jobs.First ().PriorityHints);
+ }
+
+ [Test]
+ public void TestOneNonSpeedJobPerResource ()
+ {
+ // Test that two SpeedSensitive jobs with the same Resources will run simultaneously
+ scheduler = new Scheduler ();
+ scheduler.Add (new TestJob (200, PriorityHints.None, Resource.Cpu, Resource.Disk));
+ scheduler.Add (new TestJob (200, PriorityHints.None, Resource.Cpu, Resource.Disk));
+ AssertJobsRunning (1);
+ }
+
+ [Test]
+ public void TestSpeedJobPreemptsNonSpeedJobs ()
+ {
+ scheduler = new Scheduler ();
+ TestJob a = new TestJob (200, PriorityHints.None, Resource.Cpu);
+ TestJob b = new TestJob (200, PriorityHints.None, Resource.Disk);
+ TestJob c = new TestJob (200, PriorityHints.LongRunning, Resource.Database);
+ scheduler.Add (a);
+ scheduler.Add (b);
+ scheduler.Add (c);
+
+ // Test that three jobs got started
+ AssertJobsRunning (3);
+
+ scheduler.Add (new TestJob (200, PriorityHints.SpeedSensitive, Resource.Cpu, Resource.Disk));
+
+ // Make sure the SpeedSensitive jobs has caused the Cpu and Disk jobs to be paused
+ AssertJobsRunning (2);
+ Assert.AreEqual (true, a.IsScheduled);
+ Assert.AreEqual (true, b.IsScheduled);
+ Assert.AreEqual (true, c.IsRunning);
+ }
+
+ /*[Test]
+ public void TestManyJobs ()
+ {
+ var timer = System.Diagnostics.Stopwatch.StartNew ();
+ scheduler = new Scheduler ("TestManyJobs");
+
+ // First add some long running jobs
+ for (int i = 0; i < 100; i++) {
+ scheduler.Add (new TestJob (20, PriorityHints.LongRunning, Resource.Cpu));
+ }
+
+ // Then add some normal jobs that will prempt them
+ for (int i = 0; i < 100; i++) {
+ scheduler.Add (new TestJob (10, PriorityHints.None, Resource.Cpu));
+ }
+
+ // Then add some SpeedSensitive jobs that will prempt all of them
+ for (int i = 0; i < 100; i++) {
+ scheduler.Add (new TestJob (5, PriorityHints.SpeedSensitive, Resource.Cpu));
+ }
+
+ while (scheduler.Jobs.Count > 0);
+ Log.DebugFormat ("Took {0} to schedule and process all jobs", timer.Elapsed);
+ //scheduler.StopAll ();
+ }*/
+
+ /*[Test]
+ public void TestCannotDisposeWhileDatalossJobsScheduled ()
+ {
+ scheduler = new Scheduler ();
+ TestJob loss_job;
+ scheduler.Add (new TestJob (200, PriorityHints.SpeedSensitive, Resource.Cpu));
+ scheduler.Add (loss_job = new TestJob (200, PriorityHints.DataLossIfStopped, Resource.Cpu));
+
+ AssertJobsRunning (1);
+ Assert.AreEqual (false, scheduler.JobInfo[loss_job].IsRunning);
+
+ try {
+ //scheduler.StopAll ();
+ Assert.Fail ("Cannot stop with dataloss job scheduled");
+ } catch {
+ }
+ }
+
+ public void TestCannotDisposeWhileDatalossJobsRunning ()
+ {
+ scheduler = new Scheduler ();
+ scheduler.Add (new TestJob (200, PriorityHints.DataLossIfStopped, Resource.Cpu));
+ AssertJobsRunning (1);
+
+ try {
+ //scheduler.StopAll ();
+ Assert.Fail ("Cannot stop with dataloss job running");
+ } catch {
+ }
+ }*/
+
+ private void AssertJobsRunning (int count)
+ {
+ Assert.AreEqual (count, scheduler.Jobs.Count (j => j.IsRunning));
+ }
+
+ private class TestJob : SimpleAsyncJob
+ {
+ internal static int job_count;
+ int iteration;
+ int sleep_time;
+
+ public TestJob (int sleep_time, PriorityHints hints, params Resource [] resources)
+ : base (String.Format ("{0} ( {1}, {2})", job_count++, hints, resources.Aggregate ("", (a, b) => a += b.Id + " ")),
+ hints,
+ resources)
+ {
+ this.sleep_time = sleep_time;
+ }
+
+ protected override void Run ()
+ {
+ for (int i = 0; !IsCancelRequested && i < 2; i++) {
+ YieldToScheduler ();
+ Hyena.Log.DebugFormat ("{0} iteration {1}", Title, iteration++);
+ System.Threading.Thread.Sleep (sleep_time);
+ }
+ }
+ }
+ }
+}
+
+#endif
diff --git a/src/Libraries/Hyena/Hyena/Timer.cs b/src/Libraries/Hyena/Hyena/Timer.cs
index 72c4f48..9f4d3f3 100644
--- a/src/Libraries/Hyena/Hyena/Timer.cs
+++ b/src/Libraries/Hyena/Hyena/Timer.cs
@@ -34,6 +34,10 @@ namespace Hyena
{
private DateTime start;
private string label;
+
+ public Timer (string format, params object [] vals) : this (String.Format (format, vals))
+ {
+ }
public Timer (string label)
{
diff --git a/src/Libraries/Hyena/Makefile.am b/src/Libraries/Hyena/Makefile.am
index b670ab8..9a4699e 100644
--- a/src/Libraries/Hyena/Makefile.am
+++ b/src/Libraries/Hyena/Makefile.am
@@ -48,6 +48,13 @@ SOURCES = \
Hyena.Data/ModelSelection.cs \
Hyena.Data/PropertyStore.cs \
Hyena.Data/SortType.cs \
+ Hyena.Jobs/Job.cs \
+ Hyena.Jobs/JobExtensions.cs \
+ Hyena.Jobs/PriorityHints.cs \
+ Hyena.Jobs/Resource.cs \
+ Hyena.Jobs/Scheduler.cs \
+ Hyena.Jobs/SimpleAsyncJob.cs \
+ Hyena.Jobs/Tests/SchedulerTests.cs \
Hyena.Json/Deserializer.cs \
Hyena.Json/IJsonCollection.cs \
Hyena.Json/JsonArray.cs \
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]