source: branches/eraser6/XmlTaskLists/Eraser.Manager/DirectExecutor.cs @ 2589

Revision 2589, 12.6 KB checked in by lowjoel, 3 years ago (diff)

Save and load XML Task Lists instead for better compatibility between versions.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Rev URL
Line 
1/*
2 * $Id$
3 * Copyright 2008-2012 The Eraser Project
4 * Original Author: Joel Low <lowjoel@users.sourceforge.net>
5 * Modified By: Kasra Nassiri <cjax@users.sourceforge.net> @17/10/2008
6 * Modified By:
7 *
8 * This file is part of Eraser.
9 *
10 * Eraser is free software: you can redistribute it and/or modify it under the
11 * terms of the GNU General Public License as published by the Free Software
12 * Foundation, either version 3 of the License, or (at your option) any later
13 * version.
14 *
15 * Eraser is distributed in the hope that it will be useful, but WITHOUT ANY
16 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
17 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
18 *
19 * A copy of the GNU General Public License can be found at
20 * <http://www.gnu.org/licenses/>.
21 */
22
23using System;
24using System.Collections.Generic;
25using System.Collections.Specialized;
26using System.Text;
27using System.Threading;
28using System.IO;
29
30using Eraser.Util;
31using Eraser.Util.ExtensionMethods;
32using Eraser.Plugins;
33using Eraser.Plugins.ExtensionPoints;
34using System.Xml.Serialization;
35
36namespace Eraser.Manager
37{
38    /// <summary>
39    /// The DirectExecutor class is used by the Eraser GUI directly when the program
40    /// is run without the help of a Service.
41    /// </summary>
42    public class DirectExecutor : Executor
43    {
44        public DirectExecutor()
45        {
46            TaskAdded += OnTaskAdded;
47            TaskDeleted += OnTaskDeleted;
48            tasks = new DirectExecutorTasksCollection(this);
49            thread = new Thread(Main);
50        }
51
52        protected override void Dispose(bool disposing)
53        {
54            if (thread == null || schedulerInterrupt == null)
55                return;
56
57            if (disposing)
58            {
59                thread.Abort();
60                schedulerInterrupt.Set();
61
62                //Wait for the executor thread to exit -- we call some event functions
63                //and these events may need invocation on the main thread. So,
64                //pump messages from the main thread until the thread exits.
65                if (System.Windows.Forms.Application.MessageLoop)
66                {
67                    if (!thread.Join(new TimeSpan(0, 0, 0, 0, 100)))
68                        System.Windows.Forms.Application.DoEvents();
69                }
70
71                //If we are disposing on a secondary thread, or a thread without
72                //a message loop, just wait for the thread to exit indefinitely
73                else
74                    thread.Join();
75
76                schedulerInterrupt.Close();
77            }
78
79            thread = null;
80            schedulerInterrupt = null;
81            base.Dispose(disposing);
82        }
83
84        public override void Run()
85        {
86            thread.CurrentUICulture = Thread.CurrentThread.CurrentUICulture;
87            thread.Start();
88        }
89
90        public override void QueueTask(Task task)
91        {
92            lock (tasksLock)
93            {
94                //Queue the task to be run immediately.
95                DateTime executionTime = DateTime.Now;
96                if (!scheduledTasks.ContainsKey(executionTime))
97                    scheduledTasks.Add(executionTime, new List<Task>());
98                scheduledTasks[executionTime].Add(task);
99                schedulerInterrupt.Set();
100            }
101        }
102
103        public override void ScheduleTask(Task task)
104        {
105            RecurringSchedule schedule = task.Schedule as RecurringSchedule;
106            if (schedule == null)
107                return;
108
109            DateTime executionTime = (schedule.MissedPreviousSchedule &&
110                ManagerLibrary.Instance.Settings.ExecuteMissedTasksImmediately) ?
111                    DateTime.Now : schedule.NextRun;
112
113            lock (tasksLock)
114            {
115                if (!scheduledTasks.ContainsKey(executionTime))
116                    scheduledTasks.Add(executionTime, new List<Task>());
117                scheduledTasks[executionTime].Add(task);
118            }
119        }
120
121        public override void QueueRestartTasks()
122        {
123            lock (tasksLock)
124            {
125                foreach (Task task in Tasks)
126                    if (task.Schedule == Schedule.RunOnRestart)
127                        QueueTask(task);
128            }
129        }
130
131        public override void UnqueueTask(Task task)
132        {
133            lock (tasksLock)
134                for (int i = 0; i != scheduledTasks.Count; ++i)
135                    for (int j = 0; j < scheduledTasks.Values[i].Count; )
136                    {
137                        Task currentTask = scheduledTasks.Values[i][j];
138                        if (currentTask == task &&
139                            (!(currentTask.Schedule is RecurringSchedule) ||
140                                ((RecurringSchedule)currentTask.Schedule).NextRun != scheduledTasks.Keys[i]))
141                        {
142                            scheduledTasks.Values[i].RemoveAt(j);
143                        }
144                        else
145                        {
146                            ++j;
147                        }
148                    }
149        }
150
151        internal override bool IsTaskQueued(Task task)
152        {
153            lock (tasksLock)
154                foreach (KeyValuePair<DateTime, List<Task>> tasks in scheduledTasks)
155                    foreach (Task i in tasks.Value)
156                        if (task == i)
157                            if (task.Schedule is RecurringSchedule)
158                            {
159                                if (((RecurringSchedule)task.Schedule).NextRun != tasks.Key)
160                                    return true;
161                            }
162                            else
163                                return true;
164
165            return false;
166        }
167
168        private void OnTaskAdded(object sender, TaskEventArgs e)
169        {
170            e.Task.TaskEdited += OnTaskEdited;
171        }
172
173        private void OnTaskEdited(object sender, EventArgs e)
174        {
175            //Find all schedule entries containing the task - since the user cannot make
176            //edits to the task when it is queued (only if it is scheduled) remove
177            //all task references and add them back
178            Task task = (Task)sender;
179            lock (tasksLock)
180                for (int i = 0; i != scheduledTasks.Count; ++i)
181                    for (int j = 0; j < scheduledTasks.Values[i].Count; )
182                    {
183                        Task currentTask = scheduledTasks.Values[i][j];
184                        if (currentTask == task)
185                            scheduledTasks.Values[i].RemoveAt(j);
186                        else
187                            j++;
188                    }
189
190            //Then reschedule the task
191            if (task.Schedule is RecurringSchedule)
192                ScheduleTask(task);
193        }
194
195        private void OnTaskDeleted(object sender, TaskEventArgs e)
196        {
197            e.Task.TaskEdited -= OnTaskEdited;
198        }
199
200        public override ExecutorTasksCollection Tasks
201        {
202            get
203            {
204                return tasks;
205            }
206        }
207
208        /// <summary>
209        /// The thread entry point for this object. This object operates on a queue
210        /// and hence the thread will sequentially execute tasks.
211        /// </summary>
212        private void Main()
213        {
214            //The waiting thread will utilize a polling loop to check for new
215            //scheduled tasks. This will be checked every 30 seconds. However,
216            //when the thread is waiting for a new task, it can be interrupted.
217            while (thread.ThreadState != ThreadState.AbortRequested)
218            {
219                //Check for a new task
220                Task task = null;
221                lock (tasksLock)
222                {
223                    while (scheduledTasks.Count != 0)
224                        if (scheduledTasks.Values[0].Count == 0)
225                        {
226                            //Clean all all time slots at the start of the queue which are
227                            //empty
228                            scheduledTasks.RemoveAt(0);
229                        }
230                        else
231                        {
232                            if (scheduledTasks.Keys[0] <= DateTime.Now)
233                            {
234                                List<Task> tasks = scheduledTasks.Values[0];
235                                task = tasks[0];
236                                tasks.RemoveAt(0);
237                            }
238
239                            //Do schedule queue maintenance: clean up all empty timeslots
240                            if (task == null)
241                            {
242                                for (int i = 0; i < scheduledTasks.Count; )
243                                    if (scheduledTasks.Values[i].Count == 0)
244                                        scheduledTasks.RemoveAt(i);
245                                    else
246                                        ++i;
247                            }
248
249                            break;
250                        }
251                }
252
253                if (task != null)
254                {
255                    LogSink sessionLog = new LogSink();
256                    task.Log.Add(sessionLog);
257
258                    //Start a new log session to separate this session's events
259                    //from previous ones.
260                    try
261                    {
262                        using (new LogSession(sessionLog))
263                        {
264                            //Set the currently executing task.
265                            currentTask = task;
266
267                            //Prevent the system from sleeping.
268                            Power.ExecutionState = ExecutionState.Continuous |
269                                ExecutionState.SystemRequired;
270
271                            task.Execute();
272                        }
273                    }
274                    finally
275                    {
276                        //Allow the system to sleep again.
277                        Power.ExecutionState = ExecutionState.Continuous;
278
279                        //If the task is a recurring task, reschedule it for the next execution
280                        //time since we are done with this one.
281                        if (task.Schedule is RecurringSchedule)
282                        {
283                            ScheduleTask(task);
284                        }
285
286                        //If the task is an execute on restart task or run immediately task, it is
287                        //only run once and can now be restored to a manually run task
288                        else if (task.Schedule == Schedule.RunOnRestart ||
289                            task.Schedule == Schedule.RunNow)
290                        {
291                            task.Schedule = Schedule.RunManually;
292                        }
293
294                        //Remove the actively executing task from our instance variable
295                        currentTask = null;
296                    }
297                }
298
299                //Wait for half a minute to check for the next scheduled task.
300                schedulerInterrupt.WaitOne(30000, false);
301            }
302        }
303
304        /// <summary>
305        /// The thread object.
306        /// </summary>
307        private Thread thread;
308
309        /// <summary>
310        /// The lock preventing concurrent access for the tasks list and the
311        /// tasks queue.
312        /// </summary>
313        private object tasksLock = new object();
314
315        /// <summary>
316        /// The queue of tasks. This queue is executed when the first element's
317        /// timestamp (the key) has been past. This list assumes that all tasks
318        /// are sorted by timestamp, smallest one first.
319        /// </summary>
320        private SortedList<DateTime, List<Task>> scheduledTasks =
321            new SortedList<DateTime, List<Task>>();
322
323        /// <summary>
324        /// The task list associated with this executor instance.
325        /// </summary>
326        private DirectExecutorTasksCollection tasks;
327
328        /// <summary>
329        /// The currently executing task.
330        /// </summary>
331        Task currentTask;
332
333        /// <summary>
334        /// An automatically reset event allowing the addition of new tasks to
335        /// interrupt the thread's sleeping state waiting for the next recurring
336        /// task to be due.
337        /// </summary>
338        AutoResetEvent schedulerInterrupt = new AutoResetEvent(true);
339
340        private class DirectExecutorTasksCollection : ExecutorTasksCollection
341        {
342            /// <summary>
343            /// Constructor.
344            /// </summary>
345            /// <param name="executor">The <see cref="DirectExecutor"/> object owning
346            /// this list.</param>
347            public DirectExecutorTasksCollection(DirectExecutor executor)
348                : base(executor)
349            {
350            }
351
352            #region IList<Task> Members
353            public override int IndexOf(Task item)
354            {
355                return list.IndexOf(item);
356            }
357
358            public override void Insert(int index, Task item)
359            {
360                item.Executor = Owner;
361                lock (list)
362                    list.Insert(index, item);
363
364                //Call all the event handlers who registered to be notified of tasks
365                //being added.
366                Owner.OnTaskAdded(new TaskEventArgs(item));
367
368                //If the task is scheduled to run now, break the waiting thread and
369                //run it immediately
370                if (item.Schedule == Schedule.RunNow)
371                {
372                    Owner.QueueTask(item);
373                }
374                //If the task is scheduled, add the next execution time to the list
375                //of schduled tasks.
376                else if (item.Schedule != Schedule.RunOnRestart)
377                {
378                    Owner.ScheduleTask(item);
379                }
380            }
381
382            public override void RemoveAt(int index)
383            {
384                lock (list)
385                {
386                    Task task = list[index];
387                    task.Cancel();
388                    task.Executor = null;
389                    list.RemoveAt(index);
390
391                    //Call all event handlers registered to be notified of task deletions.
392                    Owner.OnTaskDeleted(new TaskEventArgs(task));
393                }
394            }
395
396            public override Task this[int index]
397            {
398                get
399                {
400                    lock (list)
401                        return list[index];
402                }
403                set
404                {
405                    lock (list)
406                        list[index] = value;
407                }
408            }
409            #endregion
410
411            #region ICollection<Task> Members
412            public override void Add(Task item)
413            {
414                Insert(Count, item);
415            }
416
417            public override void Clear()
418            {
419                foreach (Task task in list)
420                    Remove(task);
421            }
422
423            public override bool Contains(Task item)
424            {
425                lock (list)
426                    return list.Contains(item);
427            }
428
429            public override void CopyTo(Task[] array, int arrayIndex)
430            {
431                lock (list)
432                    list.CopyTo(array, arrayIndex);
433            }
434
435            public override int Count
436            {
437                get
438                {
439                    lock (list)
440                        return list.Count;
441                }
442            }
443
444            public override bool Remove(Task item)
445            {
446                lock (list)
447                {
448                    int index = list.IndexOf(item);
449                    if (index < 0)
450                        return false;
451
452                    RemoveAt(index);
453                }
454
455                return true;
456            }
457            #endregion
458
459            #region IEnumerable<Task> Members
460            public override IEnumerator<Task> GetEnumerator()
461            {
462                return list.GetEnumerator();
463            }
464            #endregion
465
466            public override void SaveToStream(Stream stream)
467            {
468                lock (list)
469                {
470                    XmlSerializer serializer = new XmlSerializer(list.GetType());
471                    serializer.Serialize(stream, list);
472                }
473            }
474
475            public override void LoadFromStream(Stream stream)
476            {
477                //Load the list into the dictionary
478                XmlSerializer serializer = new XmlSerializer(list.GetType());
479
480                try
481                {
482                    List<Task> deserialised = (List<Task>)serializer.Deserialize(stream);
483                    list.AddRange(deserialised);
484
485                    foreach (Task task in deserialised)
486                    {
487                        task.Executor = Owner;
488                        Owner.OnTaskAdded(new TaskEventArgs(task));
489                        if (task.Schedule == Schedule.RunNow)
490                            Owner.QueueTask(task);
491                        else if (task.Schedule is RecurringSchedule)
492                            Owner.ScheduleTask(task);
493                    }
494                }
495                catch (InvalidOperationException e)
496                {
497                    throw new InvalidDataException(e.Message, e);
498                }
499                catch (FileLoadException e)
500                {
501                    throw new InvalidDataException(e.Message, e);
502                }
503            }
504
505            /// <summary>
506            /// The data store for this object.
507            /// </summary>
508            private List<Task> list = new List<Task>();
509        }
510    }
511}
Note: See TracBrowser for help on using the repository browser.