source: branches/eraser6/6.0/Eraser/SchedulerPanel.cs @ 1719

Revision 1719, 19.1 KB checked in by lowjoel, 4 years ago (diff)

Backported changes from trunk.

r1718: Author: lowjoel: When we have problems starting Eraser from the shell extension check the return code for ERROR_ELEVATION_REQUIRED; if we get that, then we should re-run the operation as an administrator.
r1717: Author: lowjoel: If the directory we are deleting does not exist, we should just return -- there's nothing to be deleted.
r1716: Author: lowjoel: Catch IOExceptions when we try to connect to other running instances and show a error message when one occurs.
r1715: Author: lowjoel: Set that files are not meant to be indexed when it is meant for deletion before we even set the file times.
r1714: Author: lowjoel: Fixed race condition potentially created by initialising the remote executor server thread immediately upon construction since Run is not yet called.
r1713: Author: lowjoel: Since we only force the creation of the SchedulerPanel?'s handle in the constructor, InvokeRequired? should be called on the panel itself, and not on subcontrols as they are still delay-constructed. Fixes crash when Eraser is started quietly and a task is created remotely.
r1712: Author: lowjoel: ThreadAbortExceptions? should not trigger BlackBox? report creation.

  • 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.ComponentModel;
25using System.Data;
26using System.Drawing;
27using System.Text;
28using System.Windows.Forms;
29
30using Eraser.Manager;
31using Eraser.Util;
32using System.Globalization;
33using System.Runtime.InteropServices;
34using System.Diagnostics;
35using System.IO;
36using System.Runtime.Serialization;
37
38namespace Eraser
39{
40    internal partial class SchedulerPanel : Eraser.BasePanel
41    {
42        public SchedulerPanel()
43        {
44            InitializeComponent();
45            UXThemeApi.UpdateControlTheme(schedulerDefaultMenu);
46            CreateHandle();
47
48            //Populate the scheduler list-view with the current task list
49            ExecutorTasksCollection tasks = Program.eraserClient.Tasks;
50            foreach (Task task in tasks)
51                DisplayTask(task);
52
53            //Hook the event machinery to our class. Handle the task Added and Removed
54            //events.
55            Program.eraserClient.TaskAdded += TaskAdded;
56            Program.eraserClient.TaskDeleted += TaskDeleted;
57        }
58
59        private void DisplayTask(Task task)
60        {
61            //Add the item to the list view
62            ListViewItem item = scheduler.Items.Add(task.UIText);
63            item.SubItems.Add(string.Empty);
64            item.SubItems.Add(string.Empty);
65
66            //Set the tag of the item so we know which task on the LV corresponds
67            //to the physical task object.
68            item.Tag = task;
69
70            //Add our event handlers to the task
71            task.TaskStarted += task_TaskStarted;
72            task.ProgressChanged += task_ProgressChanged;
73            task.TaskFinished += task_TaskFinished;
74
75            //Show the fields on the list view
76            UpdateTask(item);
77        }
78
79        private void UpdateTask(ListViewItem item)
80        {
81            //Get the task object
82            Task task = (Task)item.Tag;
83
84            //Set the task name
85            item.Text = task.UIText;
86
87            //Set the next run time of the task
88            if (task.Queued)
89            {
90                item.SubItems[1].Text = S._("Queued for execution");
91                item.SubItems[2].Text = string.Empty;
92            }
93            else if (task.Schedule is RecurringSchedule)
94                item.SubItems[1].Text = ((task.Schedule as RecurringSchedule).NextRun.
95                    ToString("F", CultureInfo.CurrentCulture));
96            else if (task.Schedule == Schedule.RunNow || task.Schedule == Schedule.RunManually)
97                item.SubItems[1].Text = S._("Not queued");
98            else
99                item.SubItems[1].Text = task.Schedule.UIText;
100
101            //Set the group of the task.
102            CategorizeTask(task, item);
103        }
104
105        private void CategorizeTask(Task task)
106        {
107            CategorizeTask(task, GetTaskItem(task));
108        }
109
110        private void CategorizeTask(Task task, ListViewItem item)
111        {
112            if (task.Schedule == Schedule.RunNow || task.Schedule == Schedule.RunManually)
113                item.Group = scheduler.Groups["manual"];
114            else if (task.Schedule == Schedule.RunOnRestart)
115                item.Group = scheduler.Groups["restart"];
116            else
117                item.Group = scheduler.Groups["recurring"];
118        }
119
120        /// <summary>
121        /// Handles the Task Added event.
122        /// </summary>
123        private void TaskAdded(object sender, TaskEventArgs e)
124        {
125            if (InvokeRequired)
126            {
127                Invoke(new EventHandler<TaskEventArgs>(TaskAdded), sender, e);
128                return;
129            }
130
131            //Display a balloon notification if the parent frame has been minimised.
132            MainForm parent = (MainForm)FindForm();
133            if (parent != null && (parent.WindowState == FormWindowState.Minimized || !parent.Visible))
134            {
135                parent.ShowNotificationBalloon(S._("New task added"), S._("{0} " +
136                    "has just been added to the list of tasks.", e.Task.UIText),
137                    ToolTipIcon.Info);
138            }
139
140            DisplayTask(e.Task);
141        }
142
143        private void DeleteSelectedTasks()
144        {
145            if (MessageBox.Show(this, S._("Are you sure you want to delete the selected tasks?"),
146                   S._("Eraser"), MessageBoxButtons.YesNo, MessageBoxIcon.Question,
147                   MessageBoxDefaultButton.Button1,
148                   S.IsRightToLeft(this) ? MessageBoxOptions.RtlReading : 0) != DialogResult.Yes)
149            {
150                return;
151            }
152
153            foreach (ListViewItem item in scheduler.SelectedItems)
154            {
155                Task task = (Task)item.Tag;
156                if (!task.Executing)
157                    Program.eraserClient.Tasks.Remove(task);
158            }
159        }
160
161        /// <summary>
162        /// Handles the task deleted event.
163        /// </summary>
164        private void TaskDeleted(object sender, TaskEventArgs e)
165        {
166            if (InvokeRequired)
167            {
168                Invoke(new EventHandler<TaskEventArgs>(TaskDeleted), sender, e);
169                return;
170            }
171
172            foreach (ListViewItem item in scheduler.Items)
173                if (((Task)item.Tag) == e.Task)
174                {
175                    scheduler.Items.Remove(item);
176                    break;
177                }
178
179            PositionProgressBar();
180        }
181
182        /// <summary>
183        /// Handles the task start event.
184        /// </summary>
185        /// <param name="e">The task event object.</param>
186        void task_TaskStarted(object sender, TaskEventArgs e)
187        {
188            if (InvokeRequired)
189            {
190                Invoke(new EventHandler<TaskEventArgs>(task_TaskStarted), sender, e);
191                return;
192            }
193
194            //Get the list view item
195            ListViewItem item = GetTaskItem(e.Task);
196
197            //Update the status.
198            item.SubItems[1].Text = S._("Running...");
199
200            //Show the progress bar
201            schedulerProgress.Tag = item;
202            schedulerProgress.Visible = true;
203            schedulerProgress.Value = 0;
204            PositionProgressBar();
205        }
206
207        /// <summary>
208        /// Handles the progress event by the task.
209        /// </summary>
210        void task_ProgressChanged(object sender, TaskProgressEventArgs e)
211        {
212            //Make sure we handle the event in the main thread as this requires
213            //GUI calls.
214            if (InvokeRequired)
215            {
216                Invoke(new EventHandler<TaskProgressEventArgs>(task_ProgressChanged), sender, e);
217                return;
218            }
219
220            //Update the progress bar
221            schedulerProgress.Value = (int)(e.OverallProgress * 1000.0);
222        }
223
224        /// <summary>
225        /// Handles the task completion event.
226        /// </summary>
227        void task_TaskFinished(object sender, TaskEventArgs e)
228        {
229            if (InvokeRequired)
230            {
231                Invoke(new EventHandler<TaskEventArgs>(task_TaskFinished), sender, e);
232                return;
233            }
234
235            //Get the list view item
236            ListViewItem item = GetTaskItem(e.Task);
237            if (item == null)
238                return;
239
240            //Hide the progress bar
241            if (schedulerProgress.Tag != null && schedulerProgress.Tag == item)
242            {
243                schedulerProgress.Tag = null;
244                schedulerProgress.Visible = false;
245            }
246
247            //Get the exit status of the task.
248            LogLevel highestLevel = LogLevel.Information;
249            LogEntryCollection logs = e.Task.Log.LastSessionEntries;
250            foreach (LogEntry log in logs)
251                if (log.Level > highestLevel)
252                    highestLevel = log.Level;
253
254            //Show a balloon to inform the user
255            MainForm parent = (MainForm)FindForm();
256
257            //TODO: Is this still needed?
258            if (parent == null)
259                throw new InvalidOperationException();
260            if (parent.WindowState == FormWindowState.Minimized || !parent.Visible)
261            {
262                string message = null;
263                ToolTipIcon icon = ToolTipIcon.None;
264
265                switch (highestLevel)
266                {
267                    case LogLevel.Warning:
268                        message = S._("The task {0} has completed with warnings.", e.Task.UIText);
269                        icon = ToolTipIcon.Warning;
270                        break;
271                    case LogLevel.Error:
272                        message = S._("The task {0} has completed with errors.", e.Task.UIText);
273                        icon = ToolTipIcon.Error;
274                        break;
275                    case LogLevel.Fatal:
276                        message = S._("The task {0} did not complete.", e.Task.UIText);
277                        icon = ToolTipIcon.Error;
278                        break;
279                    default:
280                        message = S._("The task {0} has completed.", e.Task.UIText);
281                        icon = ToolTipIcon.Info;
282                        break;
283                }
284
285                parent.ShowNotificationBalloon(S._("Task executed"), message,
286                    icon);
287            }
288
289            //If the user requested us to remove completed one-time tasks, do so.
290            if (EraserSettings.Get().ClearCompletedTasks &&
291                (e.Task.Schedule == Schedule.RunNow) && highestLevel < LogLevel.Warning)
292            {
293                Program.eraserClient.Tasks.Remove(e.Task);
294            }
295
296            //Otherwise update the UI
297            else
298            {
299                switch (highestLevel)
300                {
301                    case LogLevel.Warning:
302                        item.SubItems[2].Text = S._("Completed with warnings");
303                        break;
304                    case LogLevel.Error:
305                        item.SubItems[2].Text = S._("Completed with errors");
306                        break;
307                    case LogLevel.Fatal:
308                        item.SubItems[2].Text = S._("Not completed");
309                        break;
310                    default:
311                        item.SubItems[2].Text = S._("Completed");
312                        break;
313                }
314
315                //Recategorize the task. Do not assume the task has maintained the
316                //category since run-on-restart tasks will be changed to immediately
317                //run tasks.
318                CategorizeTask(e.Task, item);
319
320                //Update the status of the task.
321                UpdateTask(item);
322            }
323        }
324
325        /// <summary>
326        /// Occurs when the user presses a key on the list view.
327        /// </summary>
328        /// <param name="sender">The list view which triggered the event.</param>
329        /// <param name="e">Event argument.</param>
330        private void scheduler_KeyDown(object sender, KeyEventArgs e)
331        {
332            if (e.KeyCode == Keys.Delete)
333                DeleteSelectedTasks();
334        }
335
336        /// <summary>
337        /// Occurs when the user double-clicks a scheduler item. This will result
338        /// in the log viewer being called, or the progress dialog to be displayed.
339        /// </summary>
340        /// <param name="sender">The list view which triggered the event.</param>
341        /// <param name="e">Event argument.</param>
342        private void scheduler_ItemActivate(object sender, EventArgs e)
343        {
344            if (scheduler.SelectedItems.Count == 0)
345                return;
346
347            ListViewItem item = scheduler.SelectedItems[0];
348            if (((Task)item.Tag).Executing)
349                using (ProgressForm form = new ProgressForm((Task)item.Tag))
350                    form.ShowDialog();
351            else
352                editTaskToolStripMenuItem_Click(sender, e);
353        }
354
355        /// <summary>
356        /// Occurs when the user drags a file over the scheduler
357        /// </summary>
358        private void scheduler_DragEnter(object sender, DragEventArgs e)
359        {
360            string descriptionMessage = string.Empty;
361            string descriptionInsert = string.Empty;
362            const string descrptionPlaceholder = "%1";
363            if (!e.Data.GetDataPresent(DataFormats.FileDrop))
364                e.Effect = DragDropEffects.None;
365            else
366            {
367                string[] files = (string[])e.Data.GetData(DataFormats.FileDrop, false);
368                bool isTaskList = true;
369                foreach (string file in files)
370                {
371                    if (descriptionInsert.Length < 259 &&
372                        (descriptionInsert.Length < 3 || descriptionInsert.Substring(descriptionInsert.Length - 3) != "..."))
373                    {
374                        string append = string.Format(CultureInfo.InvariantCulture, "{0}, ",
375                            Path.GetFileNameWithoutExtension(file));
376                        if (descriptionInsert.Length + append.Length > 259)
377                        {
378                            descriptionInsert += ".....";
379                        }
380                        else
381                        {
382                            descriptionInsert += append;
383                        }
384                    }
385
386                    if (Path.GetExtension(file) != ".ersx")
387                        isTaskList = false;
388                }
389                descriptionInsert = descriptionInsert.Remove(descriptionInsert.Length - 2);
390
391                if (isTaskList)
392                {
393                    e.Effect = DragDropEffects.Copy;
394                    descriptionMessage = S._("Import tasks from {0}", descrptionPlaceholder);
395                }
396                else
397                {
398                    e.Effect = DragDropEffects.Move;
399                    descriptionMessage = S._("Erase {0}", descrptionPlaceholder);
400                }
401            }
402
403            DropTargetHelper.DragEnter(this, e.Data, new Point(e.X, e.Y), e.Effect,
404                descriptionMessage, descriptionInsert);
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            DropTargetHelper.Drop(e.Data, new Point(e.X, e.Y), e.Effect);
423
424            if (!e.Data.GetDataPresent(DataFormats.FileDrop))
425                e.Effect = DragDropEffects.None;
426            else
427            {
428                string[] files = (string[])e.Data.GetData(DataFormats.FileDrop, false);
429
430                //Determine whether we are importing a task list or dragging files for
431                //erasure.
432                if (e.Effect == DragDropEffects.Copy)
433                {
434                    foreach (string file in files)
435                        using (FileStream stream = new FileStream(file, FileMode.Open,
436                            FileAccess.Read, FileShare.Read))
437                        {
438                            try
439                            {
440                                Program.eraserClient.Tasks.LoadFromStream(stream);
441                            }
442                            catch (SerializationException ex)
443                            {
444                                MessageBox.Show(S._("Could not import task list from {0}. The error " +
445                                    "returned was: {1}", file, ex.Message), S._("Eraser"),
446                                    MessageBoxButtons.OK, MessageBoxIcon.Error,
447                                    MessageBoxDefaultButton.Button1,
448                                    S.IsRightToLeft(null) ? MessageBoxOptions.RtlReading : 0);
449                            }
450                        }
451                }
452                else if (e.Effect == DragDropEffects.Move)
453                {
454                    //Create a task with the default settings
455                    Task task = new Task();
456                    foreach (string file in files)
457                    {
458                        FileSystemObjectTarget target;
459                        if (Directory.Exists(file))
460                            target = new FolderTarget();
461                        else
462                            target = new FileTarget();
463                        target.Path = file;
464
465                        task.Targets.Add(target);
466                    }
467
468                    //Confirm the erasure.
469                    if (MessageBox.Show(S._("Are you sure you wish to erase the selected files?"),
470                        S._("Eraser"), MessageBoxButtons.YesNo, MessageBoxIcon.Question,
471                        MessageBoxDefaultButton.Button2,
472                        S.IsRightToLeft(null) ? MessageBoxOptions.RtlReading : 0) == DialogResult.Yes)
473                    {
474                        //Add the task.
475                        Program.eraserClient.Tasks.Add(task);
476                    }
477                }
478            }
479        }
480
481        /// <summary>
482        /// Occurs when the user right-clicks the list view.
483        /// </summary>
484        /// <param name="sender">The list view which generated this event.</param>
485        /// <param name="e">Event argument.</param>
486        private void schedulerMenu_Opening(object sender, CancelEventArgs e)
487        {
488            //If nothing's selected, show the Scheduler menu which just allows users to
489            //create new tasks (like from the toolbar)
490            if (scheduler.SelectedItems.Count == 0)
491            {
492                schedulerDefaultMenu.Show(schedulerMenu.Left, schedulerMenu.Top);
493                e.Cancel = true;
494                return;
495            }
496
497            bool aTaskNotQueued = false;
498            bool aTaskExecuting = false;
499            foreach (ListViewItem item in scheduler.SelectedItems)
500            {
501                Task task = (Task)item.Tag;
502                aTaskNotQueued = aTaskNotQueued || (!task.Queued && !task.Executing);
503                aTaskExecuting = aTaskExecuting || task.Executing;
504            }
505
506            runNowToolStripMenuItem.Enabled = aTaskNotQueued;
507            cancelTaskToolStripMenuItem.Enabled = aTaskExecuting;
508
509            editTaskToolStripMenuItem.Enabled = scheduler.SelectedItems.Count == 1 &&
510                !((Task)scheduler.SelectedItems[0].Tag).Executing &&
511                !((Task)scheduler.SelectedItems[0].Tag).Queued;
512            deleteTaskToolStripMenuItem.Enabled = !aTaskExecuting;
513        }
514
515        /// <summary>
516        /// Occurs when the user selects the New Task context menu item.
517        /// </summary>
518        /// <param name="sender">The menu which generated this event.</param>
519        /// <param name="e">Event argument.</param>
520        private void newTaskToolStripMenuItem_Click(object sender, EventArgs e)
521        {
522            using (TaskPropertiesForm form = new TaskPropertiesForm())
523            {
524                if (form.ShowDialog() == DialogResult.OK)
525                {
526                    Task task = form.Task;
527                    Program.eraserClient.Tasks.Add(task);
528                }
529            }
530        }
531
532        /// <summary>
533        /// Occurs whent the user selects the Run Now context menu item.
534        /// </summary>
535        /// <param name="sender">The menu which generated this event.</param>
536        /// <param name="e">Event argument.</param>
537        private void runNowToolStripMenuItem_Click(object sender, EventArgs e)
538        {
539            foreach (ListViewItem item in scheduler.SelectedItems)
540            {
541                //Queue the task
542                Task task = (Task)item.Tag;
543                if (!task.Executing && !task.Queued)
544                {
545                    Program.eraserClient.QueueTask(task);
546
547                    //Update the UI
548                    item.SubItems[1].Text = S._("Queued for execution");
549                }
550            }
551        }
552
553        /// <summary>
554        /// Occurs whent the user selects the Cancel Task context menu item.
555        /// </summary>
556        /// <param name="sender">The menu which generated this event.</param>
557        /// <param name="e">Event argument.</param>
558        private void cancelTaskToolStripMenuItem_Click(object sender, EventArgs e)
559        {
560            foreach (ListViewItem item in scheduler.SelectedItems)
561            {
562                //Queue the task
563                Task task = (Task)item.Tag;
564                if (task.Executing || task.Queued)
565                {
566                    task.Cancel();
567
568                    //Update the UI
569                    item.SubItems[1].Text = string.Empty;
570                }
571            }
572        }
573
574        /// <summary>
575        /// Occurs when the user selects the View Task Log context menu item.
576        /// </summary>
577        /// <param name="sender">The menu item which generated this event.</param>
578        /// <param name="e">Event argument.</param>
579        private void viewTaskLogToolStripMenuItem_Click(object sender, EventArgs e)
580        {
581            if (scheduler.SelectedItems.Count != 1)
582                return;
583
584            ListViewItem item = scheduler.SelectedItems[0];
585            using (LogForm form = new LogForm((Task)item.Tag))
586                form.ShowDialog();
587        }
588
589        /// <summary>
590        /// Occurs when the user selects the Edit Task context menu item.
591        /// </summary>
592        /// <param name="sender">The menu item which generated this event.</param>
593        /// <param name="e">Event argument.</param>
594        private void editTaskToolStripMenuItem_Click(object sender, EventArgs e)
595        {
596            if (scheduler.SelectedItems.Count != 1 ||
597                ((Task)scheduler.SelectedItems[0].Tag).Executing ||
598                ((Task)scheduler.SelectedItems[0].Tag).Queued)
599            {
600                return;
601            }
602
603            //Make sure that the task is not being executed, or else. This can
604            //be done in the Client library, but there will be no effect on the
605            //currently running task.
606            ListViewItem item = scheduler.SelectedItems[0];
607            Task task = (Task)item.Tag;
608            if (task.Executing)
609                return;
610
611            //Edit the task.
612            using (TaskPropertiesForm form = new TaskPropertiesForm())
613            {
614                form.Task = task;
615                if (form.ShowDialog() == DialogResult.OK)
616                {
617                    task = form.Task;
618
619                    //Update the list view
620                    UpdateTask(item);
621                }
622            }
623        }
624
625        /// <summary>
626        /// Occurs when the user selects the Delete Task context menu item.
627        /// </summary>
628        /// <param name="sender">The menu item which generated this event.</param>
629        /// <param name="e">Event argument.</param>
630        private void deleteTaskToolStripMenuItem_Click(object sender, EventArgs e)
631        {
632            DeleteSelectedTasks();
633        }
634
635        #region Item management
636        /// <summary>
637        /// Retrieves the ListViewItem for the given task.
638        /// </summary>
639        /// <param name="task">The task object whose list view entry is being sought.</param>
640        /// <returns>A ListViewItem for the given task object.</returns>
641        private ListViewItem GetTaskItem(Task task)
642        {
643            foreach (ListViewItem item in scheduler.Items)
644                if (item.Tag == task)
645                    return item;
646
647            return null;
648        }
649
650        /// <summary>
651        /// Maintains the position of the progress bar.
652        /// </summary>
653        private void PositionProgressBar()
654        {
655            if (schedulerProgress.Tag == null)
656                return;
657
658            Rectangle rect = ((ListViewItem)schedulerProgress.Tag).SubItems[2].Bounds;
659            schedulerProgress.Location = rect.Location;
660            schedulerProgress.Size = rect.Size;
661        }
662
663        private void scheduler_DrawSubItem(object sender, DrawListViewSubItemEventArgs e)
664        {
665            e.DrawDefault = true;
666            if (schedulerProgress.Tag != null)
667                PositionProgressBar();
668        }
669
670        private void scheduler_DrawColumnHeader(object sender, DrawListViewColumnHeaderEventArgs e)
671        {
672            e.DrawDefault = true;
673        }
674        #endregion
675    }
676}
Note: See TracBrowser for help on using the repository browser.