source: branches/eraser6/pluginsRewrite/Eraser/SchedulerPanel.cs @ 2488

Revision 2488, 20.6 KB checked in by lowjoel, 3 years ago (diff)

Change the progress updates to be a pull paradigm and not a push paradigm: this reduces the amount of time the CPU spends sending progress feedback, at the same time, allows future extensibility for decoupling the task executor from the front end.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
Line 
1/*
2 * $Id$
3 * Copyright 2008-2010 The Eraser Project
4 * Original Author: Joel Low <lowjoel@users.sourceforge.net>
5 * Modified By:
6 *
7 * This file is part of Eraser.
8 *
9 * Eraser is free software: you can redistribute it and/or modify it under the
10 * terms of the GNU General Public License as published by the Free Software
11 * Foundation, either version 3 of the License, or (at your option) any later
12 * version.
13 *
14 * Eraser is distributed in the hope that it will be useful, but WITHOUT ANY
15 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
16 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
17 *
18 * A copy of the GNU General Public License can be found at
19 * <http://www.gnu.org/licenses/>.
20 */
21
22using System;
23using System.Collections.Generic;
24using System.Linq;
25using System.Drawing;
26using System.Text;
27using System.Windows.Forms;
28
29using System.Globalization;
30using System.IO;
31using System.ComponentModel;
32
33using Eraser.Manager;
34using Eraser.Util;
35using Eraser.Plugins;
36using Eraser.Plugins.ExtensionPoints;
37using Microsoft.Samples;
38
39using ProgressChangedEventArgs = Eraser.Plugins.ProgressChangedEventArgs;
40
41namespace Eraser
42{
43    internal partial class SchedulerPanel : Eraser.BasePanel
44    {
45        public SchedulerPanel()
46        {
47            InitializeComponent();
48            Theming.ApplyTheme(schedulerDefaultMenu);
49            if (!IsHandleCreated)
50                CreateHandle();
51
52            //Populate the scheduler list-view with the current task list
53            ExecutorTasksCollection tasks = Program.eraserClient.Tasks;
54            foreach (Task task in tasks)
55                CreateTask(task);
56
57            //Hook the event machinery to our class. Handle the task Added and Removed
58            //events.
59            Program.eraserClient.TaskAdded += TaskAdded;
60            Program.eraserClient.TaskDeleted += TaskDeleted;
61        }
62
63        #region List-View Task Management
64        private void CreateTask(Task task)
65        {
66            //Add the item to the list view
67            ListViewItem item = scheduler.Items.Add(task.ToString());
68            item.SubItems.Add(string.Empty);
69            item.SubItems.Add(string.Empty);
70
71            //Set the tag of the item so we know which task on the list-view
72            //corresponds to the physical task object.
73            item.Tag = task;
74
75            //Add our event handlers to the task
76            task.TaskStarted += TaskStarted;
77            task.TaskFinished += TaskFinished;
78
79            //Show the fields on the list view
80            UpdateTask(item);
81
82            //If the task is set to Run Immediately, then show that status.
83            if (task.Schedule == Schedule.RunNow)
84                item.SubItems[1].Text = S._("Queued for execution");
85        }
86
87        private void UpdateTask(ListViewItem item)
88        {
89            //Get the task object
90            Task task = (Task)item.Tag;
91
92            //Set the task name
93            item.Text = task.ToString();
94
95            //Set the next run time of the task
96            if (task.Queued)
97                item.SubItems[1].Text = S._("Queued for execution");
98            else if (task.Executing)
99                TaskStarted(task, new TaskEventArgs(task));
100            else if (task.Schedule is RecurringSchedule)
101                item.SubItems[1].Text = ((task.Schedule as RecurringSchedule).NextRun.
102                    ToString("f", CultureInfo.CurrentCulture));
103            else if (task.Schedule == Schedule.RunManually || task.Schedule == Schedule.RunNow)
104                item.SubItems[1].Text = S._("Not queued");
105            else
106                item.SubItems[1].Text = task.Schedule.UIText;
107
108            //Set the group of the task.
109            CategorizeTask(task, item);
110        }
111
112        private void CategorizeTask(Task task)
113        {
114            CategorizeTask(task, GetTaskItem(task));
115        }
116
117        private void CategorizeTask(Task task, ListViewItem item)
118        {
119            if (task.Schedule == Schedule.RunNow || task.Schedule == Schedule.RunManually)
120                item.Group = scheduler.Groups["manual"];
121            else if (task.Schedule == Schedule.RunOnRestart)
122                item.Group = scheduler.Groups["restart"];
123            else
124                item.Group = scheduler.Groups["recurring"];
125        }
126        #endregion
127
128        #region Task Event handlers
129        /// <summary>
130        /// Handles the Task Added event.
131        /// </summary>
132        private void TaskAdded(object sender, TaskEventArgs e)
133        {
134            if (InvokeRequired)
135            {
136                Invoke((EventHandler<TaskEventArgs>)TaskAdded, sender, e);
137                return;
138            }
139
140            //Display a balloon notification if the parent frame has been minimised.
141            MainForm parent = (MainForm)FindForm();
142            if (parent != null && (parent.WindowState == FormWindowState.Minimized || !parent.Visible))
143            {
144                parent.ShowNotificationBalloon(S._("New task added"), S._("{0} " +
145                    "has just been added to the list of tasks.", e.Task.ToString()),
146                    ToolTipIcon.Info);
147            }
148
149            CreateTask(e.Task);
150        }
151
152        private void DeleteSelectedTasks()
153        {
154            if (MessageBox.Show(this, S._("Are you sure you want to delete the selected tasks?"),
155                    S._("Eraser"), MessageBoxButtons.YesNo, MessageBoxIcon.Question,
156                    MessageBoxDefaultButton.Button1, Localisation.IsRightToLeft(this) ?
157                        MessageBoxOptions.RtlReading | MessageBoxOptions.RightAlign : 0
158                ) != DialogResult.Yes)
159            {
160                return;
161            }
162
163            foreach (ListViewItem item in scheduler.SelectedItems)
164            {
165                Task task = (Task)item.Tag;
166                if (!task.Executing)
167                    Program.eraserClient.Tasks.Remove(task);
168            }
169        }
170
171        /// <summary>
172        /// Handles the task deleted event.
173        /// </summary>
174        private void TaskDeleted(object sender, TaskEventArgs e)
175        {
176            if (InvokeRequired)
177            {
178                Invoke((EventHandler<TaskEventArgs>)TaskDeleted, sender, e);
179                return;
180            }
181
182            foreach (ListViewItem item in scheduler.Items)
183                if (((Task)item.Tag) == e.Task)
184                {
185                    scheduler.Items.Remove(item);
186                    break;
187                }
188
189            PositionProgressBar();
190        }
191
192        /// <summary>
193        /// Handles the task start event.
194        /// </summary>
195        /// <param name="e">The task event object.</param>
196        void TaskStarted(object sender, EventArgs e)
197        {
198            if (InvokeRequired)
199            {
200                Invoke((EventHandler)TaskStarted, sender, e);
201                return;
202            }
203
204            //Get the list view item
205            Task task = (Task)sender;
206            ListViewItem item = GetTaskItem(task);
207
208            //Update the status.
209            item.SubItems[1].Text = S._("Running...");
210
211            //Show the progress bar
212            schedulerProgress.Tag = item;
213            schedulerProgress.Visible = true;
214            schedulerProgress.Value = 0;
215            PositionProgressBar();
216        }
217
218        /// <summary>
219        /// Handles the progress event by the task.
220        /// </summary>
221        private void progressTimer_Tick(object sender, EventArgs e)
222        {
223            ListViewItem item = (ListViewItem)schedulerProgress.Tag;
224            Task task = (Task)item.Tag;
225
226            //Update the progress bar
227            SteppedProgressManager progress = task.Progress;
228            schedulerProgress.Style = progress.ProgressIndeterminate ?
229                ProgressBarStyle.Marquee : ProgressBarStyle.Continuous;
230
231            if (!progress.ProgressIndeterminate)
232                schedulerProgress.Value = (int)(progress.Progress * 1000.0);
233        }
234
235        /// <summary>
236        /// Handles the task completion event.
237        /// </summary>
238        void TaskFinished(object sender, EventArgs e)
239        {
240            if (InvokeRequired)
241            {
242                Invoke((EventHandler)TaskFinished, sender, e);
243                return;
244            }
245
246            //Get the list view item
247            Task task = (Task)sender;
248            ListViewItem item = GetTaskItem(task);
249            if (item == null)
250                return;
251
252            //Hide the progress bar
253            if (schedulerProgress.Tag != null && schedulerProgress.Tag == item)
254            {
255                schedulerProgress.Tag = null;
256                schedulerProgress.Visible = false;
257            }
258
259            //Get the exit status of the task.
260            LogLevel highestLevel = task.Log.Last().Highest;
261
262            //Show a balloon to inform the user
263            MainForm parent = (MainForm)FindForm();
264            if (parent.WindowState == FormWindowState.Minimized || !parent.Visible)
265            {
266                string message = null;
267                ToolTipIcon icon = ToolTipIcon.None;
268
269                switch (highestLevel)
270                {
271                    case LogLevel.Warning:
272                        message = S._("The task {0} has completed with warnings.", task);
273                        icon = ToolTipIcon.Warning;
274                        break;
275                    case LogLevel.Error:
276                        message = S._("The task {0} has completed with errors.", task);
277                        icon = ToolTipIcon.Error;
278                        break;
279                    case LogLevel.Fatal:
280                        message = S._("The task {0} did not complete.", task);
281                        icon = ToolTipIcon.Error;
282                        break;
283                    default:
284                        message = S._("The task {0} has completed.", task);
285                        icon = ToolTipIcon.Info;
286                        break;
287                }
288
289                parent.ShowNotificationBalloon(S._("Task completed"), message,
290                    icon);
291            }
292
293            //If the user requested us to remove completed one-time tasks, do so.
294            if (EraserSettings.Get().ClearCompletedTasks &&
295                (task.Schedule == Schedule.RunNow) && highestLevel < LogLevel.Warning)
296            {
297                Program.eraserClient.Tasks.Remove(task);
298            }
299
300            //Otherwise update the UI
301            else
302            {
303                switch (highestLevel)
304                {
305                    case LogLevel.Warning:
306                        item.SubItems[2].Text = S._("Completed with warnings");
307                        break;
308                    case LogLevel.Error:
309                        item.SubItems[2].Text = S._("Completed with errors");
310                        break;
311                    case LogLevel.Fatal:
312                        item.SubItems[2].Text = S._("Not completed");
313                        break;
314                    default:
315                        item.SubItems[2].Text = S._("Completed");
316                        break;
317                }
318
319                //Recategorize the task. Do not assume the task has maintained the
320                //category since run-on-restart tasks will be changed to immediately
321                //run tasks.
322                CategorizeTask(task, item);
323
324                //Update the status of the task.
325                UpdateTask(item);
326            }
327        }
328        #endregion
329
330        #region List-View Event handlers
331        /// <summary>
332        /// Occurs when the user presses a key on the list view.
333        /// </summary>
334        /// <param name="sender">The list view which triggered the event.</param>
335        /// <param name="e">Event argument.</param>
336        private void scheduler_KeyDown(object sender, KeyEventArgs e)
337        {
338            if (e.KeyCode == Keys.Delete)
339                DeleteSelectedTasks();
340        }
341
342        /// <summary>
343        /// Occurs when the user double-clicks a scheduler item. This will result
344        /// in the log viewer being called, or the progress dialog to be displayed.
345        /// </summary>
346        /// <param name="sender">The list view which triggered the event.</param>
347        /// <param name="e">Event argument.</param>
348        private void scheduler_ItemActivate(object sender, EventArgs e)
349        {
350            if (scheduler.SelectedItems.Count == 0)
351                return;
352
353            ListViewItem item = scheduler.SelectedItems[0];
354            if (((Task)item.Tag).Executing)
355                using (ProgressForm form = new ProgressForm((Task)item.Tag))
356                    form.ShowDialog();
357            else
358                editTaskToolStripMenuItem_Click(sender, e);
359        }
360
361        /// <summary>
362        /// Occurs when the user drags a file over the scheduler
363        /// </summary>
364        private void scheduler_DragEnter(object sender, DragEventArgs e)
365        {
366            //Get the list of files.
367            bool recycleBin = false;
368            List<string> paths = new List<string>(TaskDragDropHelper.GetFiles(e, out recycleBin));
369
370            //We also need to determine if we are importing task lists.
371            bool isTaskList = !recycleBin;
372
373            for (int i = 0; i < paths.Count; ++i)
374            {
375                //Does this item exclude a task list import?
376                if (isTaskList && Path.GetExtension(paths[i]) != ".ersx")
377                    isTaskList = false;
378
379                //Just use the file name/directory name.
380                paths[i] = Path.GetFileName(paths[i]);
381            }
382
383            //Add the recycle bin if it was dropped.
384            if (recycleBin)
385                paths.Add(S._("Recycle Bin"));
386
387            string description = null;
388            if (paths.Count == 0)
389            {
390                e.Effect = DragDropEffects.None;
391                description = S._("Cannot erase the selected items");
392            }
393            else if (isTaskList)
394            {
395                e.Effect = DragDropEffects.Copy;
396                description = S._("Import tasks from {0}");
397            }
398            else
399            {
400                e.Effect = DragDropEffects.Move;
401                description = S._("Erase {0}");
402            }
403
404            TaskDragDropHelper.OnDragEnter(this, e, description, paths);
405        }
406
407        private void scheduler_DragLeave(object sender, EventArgs e)
408        {
409            DropTargetHelper.DragLeave(this);
410        }
411
412        private void scheduler_DragOver(object sender, DragEventArgs e)
413        {
414            DropTargetHelper.DragOver(new Point(e.X, e.Y), e.Effect);
415        }
416
417        /// <summary>
418        /// Occurs when the user drops a file into the scheduler.
419        /// </summary>
420        private void scheduler_DragDrop(object sender, DragEventArgs e)
421        {
422            TaskDragDropHelper.OnDrop(e);
423            if (e.Effect == DragDropEffects.None)
424                return;
425
426            //Determine our action.
427            bool recycleBin = false;
428            List<string> paths = new List<string>(TaskDragDropHelper.GetFiles(e, out recycleBin));
429            bool isTaskList = !recycleBin;
430
431            foreach (string path in paths)
432            {
433                //Does this item exclude a task list import?
434                if (isTaskList && Path.GetExtension(path) != ".ersx")
435                {
436                    isTaskList = false;
437                    break;
438                }
439            }
440
441            if (isTaskList)
442            {
443                foreach (string file in paths)
444                    using (FileStream stream = new FileStream(file, FileMode.Open,
445                        FileAccess.Read, FileShare.Read))
446                    {
447                        try
448                        {
449                            Program.eraserClient.Tasks.LoadFromStream(stream);
450                        }
451                        catch (InvalidDataException ex)
452                        {
453                            MessageBox.Show(S._("Could not import task list from {0}. The " +
454                                "error returned was: {1}", file, ex.Message), S._("Eraser"),
455                                MessageBoxButtons.OK, MessageBoxIcon.Error,
456                                MessageBoxDefaultButton.Button1,
457                                Localisation.IsRightToLeft(this) ?
458                                    MessageBoxOptions.RtlReading | MessageBoxOptions.RightAlign : 0);
459                        }
460                    }
461            }
462            else
463            {
464                //Create a task with the default settings
465                Task task = new Task();
466                foreach (IErasureTarget target in TaskDragDropHelper.GetTargets(e))
467                    task.Targets.Add(target);
468
469                //If the task has no targets, we should not go on.
470                if (task.Targets.Count == 0)
471                    return;
472
473                //Schedule the task dialog to be shown (to get to the event loop so that
474                //ComCtl32.dll v6 is used.)
475                BeginInvoke((Action<Task>)scheduler_DragDropConfirm, task);
476            }
477        }
478
479        /// <summary>
480        /// Called when a set of files are dropped into Eraser and to let the user
481        /// decide what to do with the collection.
482        /// </summary>
483        /// <param name="task">The task which requires confirmation.</param>
484        private void scheduler_DragDropConfirm(Task task)
485        {
486            //Add the task, asking the user for his intent.
487            DialogResult action = DialogResult.No;
488            if (TaskDialog.IsAvailableOnThisOS)
489            {
490                TaskDialog dialog = new TaskDialog();
491                dialog.WindowTitle = S._("Eraser");
492                dialog.MainIcon = TaskDialogIcon.Information;
493                dialog.MainInstruction = S._("You have dropped a set of files and folders into Eraser. What do you want to do with them?");
494                dialog.AllowDialogCancellation = true;
495                dialog.Buttons = new TaskDialogButton[] {
496                    new TaskDialogButton((int)DialogResult.Yes, S._("Erase the selected items\nSchedules the selected items for immediate erasure.")),
497                    new TaskDialogButton((int)DialogResult.OK, S._("Create a new Task\nA task will be created containing the selected items.")),
498                    new TaskDialogButton((int)DialogResult.No, S._("Cancel the drag-and-drop operation"))
499                };
500                dialog.RightToLeftLayout = Localisation.IsRightToLeft(this);
501                dialog.UseCommandLinks = true;
502                action = (DialogResult)dialog.Show(this);
503            }
504            else
505            {
506                action = MessageBox.Show(S._("Are you sure you wish to erase the selected "
507                    + "items?"), S._("Eraser"), MessageBoxButtons.YesNo,
508                    MessageBoxIcon.Question, MessageBoxDefaultButton.Button2,
509                    Localisation.IsRightToLeft(this) ?
510                        MessageBoxOptions.RtlReading | MessageBoxOptions.RightAlign : 0);
511            }
512
513            switch (action)
514            {
515                case DialogResult.OK:
516                    task.Schedule = Schedule.RunManually;
517                    goto case DialogResult.Yes;
518
519                case DialogResult.Yes:
520                    Program.eraserClient.Tasks.Add(task);
521                    break;
522            }
523        }
524
525        /// <summary>
526        /// Occurs when the user right-clicks the list view.
527        /// </summary>
528        /// <param name="sender">The list view which generated this event.</param>
529        /// <param name="e">Event argument.</param>
530        private void schedulerMenu_Opening(object sender, CancelEventArgs e)
531        {
532            //If nothing's selected, show the Scheduler menu which just allows users to
533            //create new tasks (like from the toolbar)
534            if (scheduler.SelectedItems.Count == 0)
535            {
536                schedulerDefaultMenu.Show(schedulerMenu.Left, schedulerMenu.Top);
537                e.Cancel = true;
538                return;
539            }
540
541            bool aTaskNotQueued = false;
542            bool aTaskExecuting = false;
543            foreach (ListViewItem item in scheduler.SelectedItems)
544            {
545                Task task = (Task)item.Tag;
546                aTaskNotQueued = aTaskNotQueued || (!task.Queued && !task.Executing);
547                aTaskExecuting = aTaskExecuting || task.Executing;
548            }
549
550            runNowToolStripMenuItem.Enabled = aTaskNotQueued;
551            cancelTaskToolStripMenuItem.Enabled = aTaskExecuting;
552
553            editTaskToolStripMenuItem.Enabled = scheduler.SelectedItems.Count == 1 &&
554                !((Task)scheduler.SelectedItems[0].Tag).Executing &&
555                !((Task)scheduler.SelectedItems[0].Tag).Queued;
556            deleteTaskToolStripMenuItem.Enabled = !aTaskExecuting;
557        }
558
559        /// <summary>
560        /// Occurs when the user selects the New Task context menu item.
561        /// </summary>
562        /// <param name="sender">The menu which generated this event.</param>
563        /// <param name="e">Event argument.</param>
564        private void newTaskToolStripMenuItem_Click(object sender, EventArgs e)
565        {
566            using (TaskPropertiesForm form = new TaskPropertiesForm())
567            {
568                if (form.ShowDialog() == DialogResult.OK)
569                {
570                    Task task = form.Task;
571                    Program.eraserClient.Tasks.Add(task);
572                }
573            }
574        }
575
576        /// <summary>
577        /// Occurs whent the user selects the Run Now context menu item.
578        /// </summary>
579        /// <param name="sender">The menu which generated this event.</param>
580        /// <param name="e">Event argument.</param>
581        private void runNowToolStripMenuItem_Click(object sender, EventArgs e)
582        {
583            foreach (ListViewItem item in scheduler.SelectedItems)
584            {
585                //Queue the task
586                Task task = (Task)item.Tag;
587                if (!task.Executing && !task.Queued)
588                {
589                    Program.eraserClient.QueueTask(task);
590
591                    //Update the UI
592                    item.SubItems[1].Text = S._("Queued for execution");
593                }
594            }
595        }
596
597        /// <summary>
598        /// Occurs whent the user selects the Cancel Task context menu item.
599        /// </summary>
600        /// <param name="sender">The menu which generated this event.</param>
601        /// <param name="e">Event argument.</param>
602        private void cancelTaskToolStripMenuItem_Click(object sender, EventArgs e)
603        {
604            foreach (ListViewItem item in scheduler.SelectedItems)
605            {
606                //Queue the task
607                Task task = (Task)item.Tag;
608                if (task.Executing || task.Queued)
609                {
610                    task.Cancel();
611
612                    //Update the UI
613                    item.SubItems[1].Text = string.Empty;
614                }
615            }
616        }
617
618        /// <summary>
619        /// Occurs when the user selects the View Task Log context menu item.
620        /// </summary>
621        /// <param name="sender">The menu item which generated this event.</param>
622        /// <param name="e">Event argument.</param>
623        private void viewTaskLogToolStripMenuItem_Click(object sender, EventArgs e)
624        {
625            if (scheduler.SelectedItems.Count != 1)
626                return;
627
628            ListViewItem item = scheduler.SelectedItems[0];
629            using (LogForm form = new LogForm((Task)item.Tag))
630                form.ShowDialog();
631        }
632
633        /// <summary>
634        /// Occurs when the user selects the Edit Task context menu item.
635        /// </summary>
636        /// <param name="sender">The menu item which generated this event.</param>
637        /// <param name="e">Event argument.</param>
638        private void editTaskToolStripMenuItem_Click(object sender, EventArgs e)
639        {
640            if (scheduler.SelectedItems.Count != 1 ||
641                ((Task)scheduler.SelectedItems[0].Tag).Executing ||
642                ((Task)scheduler.SelectedItems[0].Tag).Queued)
643            {
644                return;
645            }
646
647            //Make sure that the task is not being executed, or else. This can
648            //be done in the Client library, but there will be no effect on the
649            //currently running task.
650            ListViewItem item = scheduler.SelectedItems[0];
651            Task task = (Task)item.Tag;
652            if (task.Executing)
653                return;
654
655            //Edit the task.
656            using (TaskPropertiesForm form = new TaskPropertiesForm())
657            {
658                form.Task = task;
659                if (form.ShowDialog() == DialogResult.OK)
660                {
661                    task = form.Task;
662
663                    //Update the list view
664                    UpdateTask(item);
665                }
666            }
667        }
668
669        /// <summary>
670        /// Occurs when the user selects the Delete Task context menu item.
671        /// </summary>
672        /// <param name="sender">The menu item which generated this event.</param>
673        /// <param name="e">Event argument.</param>
674        private void deleteTaskToolStripMenuItem_Click(object sender, EventArgs e)
675        {
676            DeleteSelectedTasks();
677        }
678        #endregion
679
680        #region Item management
681        /// <summary>
682        /// Retrieves the ListViewItem for the given task.
683        /// </summary>
684        /// <param name="task">The task object whose list view entry is being sought.</param>
685        /// <returns>A ListViewItem for the given task object.</returns>
686        private ListViewItem GetTaskItem(Task task)
687        {
688            foreach (ListViewItem item in scheduler.Items)
689                if (item.Tag == task)
690                    return item;
691
692            return null;
693        }
694
695        /// <summary>
696        /// Maintains the position of the progress bar.
697        /// </summary>
698        private void PositionProgressBar()
699        {
700            if (schedulerProgress.Tag == null)
701                return;
702
703            Rectangle rect = ((ListViewItem)schedulerProgress.Tag).SubItems[2].Bounds;
704            rect.Offset(2, 2);
705            schedulerProgress.Location = rect.Location;
706            schedulerProgress.Size = rect.Size;
707        }
708
709        private void scheduler_DrawSubItem(object sender, DrawListViewSubItemEventArgs e)
710        {
711            e.DrawDefault = true;
712            if (schedulerProgress.Tag != null)
713                PositionProgressBar();
714        }
715
716        private void scheduler_DrawColumnHeader(object sender, DrawListViewColumnHeaderEventArgs e)
717        {
718            e.DrawDefault = true;
719        }
720        #endregion
721    }
722}
Note: See TracBrowser for help on using the repository browser.