source: trunk/eraser/Eraser.Manager/DirectExecutor.cs @ 2543

Revision 2543, 12.6 KB checked in by lowjoel, 2 years ago (diff)

Tasks were not being cleared from the task list upon successful completion because they were reset to Manually executed tasks before returning to the Executor. This scheduling ability should be dealt with in the Executor and not the Task. Therefore, the Task Completion event will still have the schedule of the initial configuration, and not after the schedule has been reset to Manual. This allows context menu tasks which completed successfully to be cleared automatically.

Fixes bug in https://eraser.heidi.ie/forum/viewtopic.php?f=36&p=25922#p25922.

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