source: branches/eraser6/XmlTaskLists/Eraser.Manager/Task.cs @ 2602

Revision 2602, 14.4 KB checked in by lowjoel, 3 years ago (diff)

Make the LogSink? a base class (LogSinkBase?) and implement a normal LogSink? (API compatible with the old LogSink?) and a new LazyLogSink? class which will load a log from disk as required.
As a result of this change, Task will have two WriteXml? functions: The default will continue to generate the same code; the new overload will place references to logs if the logs are larger than a certain threshold. This is to optimize the task list for size when loading/unloading.

  • 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:
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.Text;
25using System.IO;
26using System.Runtime.Serialization;
27using System.Security.Permissions;
28using System.Xml;
29using System.Xml.Serialization;
30using System.Threading;
31
32using Eraser.Util;
33using Eraser.Util.ExtensionMethods;
34using Eraser.Plugins;
35using Eraser.Plugins.ExtensionPoints;
36
37namespace Eraser.Manager
38{
39    /// <summary>
40    /// Deals with an erase task.
41    /// </summary>
42    [Serializable]
43    public class Task : ITask
44    {
45        #region Serialization code
46        protected Task(SerializationInfo info, StreamingContext context)
47        {
48            Name = (string)info.GetValue("Name", typeof(string));
49            Executor = context.Context as Executor;
50            Targets = (ErasureTargetCollection)info.GetValue("Targets", typeof(ErasureTargetCollection));
51            Targets.Owner = this;
52            Log = (List<LogSinkBase>)info.GetValue("Log", typeof(List<LogSinkBase>));
53            Canceled = false;
54
55            Schedule schedule = (Schedule)info.GetValue("Schedule", typeof(Schedule));
56            if (schedule.GetType() == Schedule.RunManually.GetType())
57                Schedule = Schedule.RunManually;
58            else if (schedule.GetType() == Schedule.RunNow.GetType())
59                Schedule = Schedule.RunNow;
60            else if (schedule.GetType() == Schedule.RunOnRestart.GetType())
61                Schedule = Schedule.RunOnRestart;
62            else if (schedule is RecurringSchedule)
63                Schedule = schedule;
64            else
65                throw new InvalidDataException(S._("An invalid type was found when loading " +
66                    "the task schedule"));
67        }
68
69        [SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)]
70        public virtual void GetObjectData(SerializationInfo info, StreamingContext context)
71        {
72            info.AddValue("Name", Name);
73            info.AddValue("Schedule", Schedule);
74            info.AddValue("Targets", Targets);
75            info.AddValue("Log", Log);
76        }
77
78        public System.Xml.Schema.XmlSchema GetSchema()
79        {
80            return null;
81        }
82
83        public void ReadXml(XmlReader reader)
84        {
85            Canceled = false;
86            Name = reader.GetAttribute("name");
87
88            for (reader.Read() ; reader.NodeType != XmlNodeType.EndElement; reader.Read())
89            {
90                switch (reader.Name)
91                {
92                    case "Schedule":
93                        ReadSchedule(reader);
94                        break;
95
96                    case "ErasureTargetCollection":
97                        ReadTargets(reader);
98                        break;
99
100                    case "Logs":
101                        ReadLog(reader);
102                        break;
103
104                    default:
105                        System.Diagnostics.Debug.Assert(false);
106                        break;
107                }
108            }
109        }
110
111        private void ReadSchedule(XmlReader reader)
112        {
113            switch (reader.GetAttribute("type"))
114            {
115                case "Now":
116                    Schedule = Schedule.RunNow;
117                    break;
118
119                case "Restart":
120                    Schedule = Schedule.RunOnRestart;
121                    break;
122
123                case "Recurring":
124                    XmlSerializer serializer = new XmlSerializer(typeof(RecurringSchedule));
125                    reader.ReadStartElement();
126                    schedule = (RecurringSchedule)serializer.Deserialize(reader);
127                    break;
128
129                case "Manual":
130                default:
131                    Schedule = Schedule.RunManually;
132                    break;
133            }
134        }
135
136        private void ReadTargets(XmlReader reader)
137        {
138            XmlSerializer targetsSerializer = new XmlSerializer(Targets.GetType());
139            Targets = (ErasureTargetCollection)targetsSerializer.Deserialize(reader);
140            Targets.Owner = this;
141        }
142
143        private void ReadLog(XmlReader reader)
144        {
145            //We can either have a ArrayOfLogEntry or LogRef element as children.
146            Log = new List<LogSinkBase>();
147            while (reader.Read() && reader.NodeType != XmlNodeType.EndElement)
148            {
149                if (reader.Name == "LogRef")
150                {
151                    Log.Add(new LazyLogSink(reader.ReadString()));
152                }
153                else if (reader.Name == "ArrayOfLogEntry")
154                {
155                    XmlSerializer logSerializer = new XmlSerializer(typeof(LogSink));
156                    Log.Add((LogSink)logSerializer.Deserialize(reader));
157                }
158            }
159        }
160
161        /// <summary>
162        /// Writes the common part of the Task XML Element.
163        /// </summary>
164        /// <param name="writer">The XML Writer instance to write to.</param>
165        private void WriteXmlCommon(XmlWriter writer)
166        {
167            writer.WriteAttributeString("name", Name);
168
169            writer.WriteStartElement("Schedule");
170            if (schedule.GetType() == Schedule.RunManually.GetType())
171                writer.WriteAttributeString("type", "Manual");
172            else if (schedule.GetType() == Schedule.RunNow.GetType())
173                writer.WriteAttributeString("type", "Now");
174            else if (schedule.GetType() == Schedule.RunOnRestart.GetType())
175                writer.WriteAttributeString("type", "Restart");
176            else if (schedule is RecurringSchedule)
177            {
178                writer.WriteAttributeString("type", "Recurring");
179                XmlSerializer serializer = new XmlSerializer(schedule.GetType());
180                serializer.Serialize(writer, schedule);
181            }
182            writer.WriteEndElement();
183
184            XmlSerializer targetsSerializer = new XmlSerializer(Targets.GetType());
185            targetsSerializer.Serialize(writer, Targets);
186        }
187
188        public void WriteXml(XmlWriter writer)
189        {
190            WriteXmlCommon(writer);
191
192            writer.WriteStartElement("Logs");
193            foreach (LogSinkBase log in Log)
194            {
195                XmlSerializer logSerializer = new XmlSerializer(log.GetType());
196                logSerializer.Serialize(writer, log);
197            }
198            writer.WriteEndElement();
199        }
200
201        /// <summary>
202        /// Writes an XML element with the Log entries linked instead of embedded
203        /// in the task list.
204        /// </summary>
205        /// <param name="writer">The XML Writer instance to write to.</param>
206        /// <param name="logPaths">The path to a folder to store logs in.</param>
207        public void WriteSeparatedXml(XmlWriter writer, string logPaths)
208        {
209            WriteXmlCommon(writer);
210
211            writer.WriteStartElement("Logs");
212            foreach (LogSinkBase log in Log)
213            {
214                //If we have a file-backed log, retain that.
215                if (log is LazyLogSink)
216                {
217                    writer.WriteElementString("LogRef", ((LazyLogSink)log).SavePath);
218                }
219               
220                //Otherwise, decide if we want to store the log inline (if small) or
221                //link to the log file.
222                else if (log.Count < 5)
223                {
224                    //Small log, keep it inline.
225                    XmlSerializer logSerializer = new XmlSerializer(log.GetType());
226                    logSerializer.Serialize(writer, log);
227                }
228                else
229                {
230                    string savePath;
231                    do
232                    {
233                        savePath = Path.Combine(logPaths, Guid.NewGuid().ToString() + ".log");
234                    }
235                    while (File.Exists(savePath));
236
237                    log.Save(savePath);
238                    writer.WriteElementString("LogRef", savePath);
239                }
240            }
241            writer.WriteEndElement();
242        }
243        #endregion
244
245        /// <summary>
246        /// Constructor.
247        /// </summary>
248        public Task()
249        {
250            Name = string.Empty;
251            Targets = new ErasureTargetCollection(this);
252            Schedule = Schedule.RunNow;
253            Canceled = false;
254            Log = new List<LogSinkBase>();
255        }
256
257        /// <summary>
258        /// The Executor object which is managing this task.
259        /// </summary>
260        public Executor Executor
261        {
262            get
263            {
264                return executor;
265            }
266            internal set
267            {
268                if (executor != null && value != null)
269                    throw new InvalidOperationException("A task can only belong to one " +
270                        "executor at any one time");
271
272                executor = value;
273            }
274        }
275
276        /// <summary>
277        /// The name for this task. This is just an opaque value for the user to
278        /// recognize the task.
279        /// </summary>
280        public string Name { get; set; }
281
282        /// <summary>
283        /// The name of the task, used for display in UI elements.
284        /// </summary>
285        public override string ToString()
286        {
287            //Simple case, the task name was given by the user.
288            if (!string.IsNullOrEmpty(Name))
289                return Name;
290
291            string result = string.Empty;
292            if (Targets.Count == 0)
293                return result;
294            else if (Targets.Count < 5)
295            {
296                //Simpler case, small set of data.
297                foreach (IErasureTarget tgt in Targets)
298                    result += S._("{0}, ", tgt);
299
300                return result.Remove(result.Length - 2);
301            }
302            else
303            {
304                //Ok, we've quite a few entries, get the first, the mid and the end.
305                result = S._("{0}, ", Targets[0]);
306                result += S._("{0}, ", Targets[Targets.Count / 2]);
307                result += Targets[Targets.Count - 1];
308
309                return S._("{0} and {1} other targets", result, Targets.Count - 3);
310            }
311        }
312
313        /// <summary>
314        /// The set of data to erase when this task is executed.
315        /// </summary>
316        public ErasureTargetCollection Targets { get; private set; }
317
318        /// <summary>
319        /// <see cref="Targets"/>
320        /// </summary>
321        ICollection<IErasureTarget> ITask.Targets
322        {
323            get { return Targets; }
324        }
325
326        /// <summary>
327        /// The schedule for running the task.
328        /// </summary>
329        public Schedule Schedule
330        {
331            get
332            {
333                return schedule;
334            }
335            set
336            {
337                if (value.Owner != null)
338                    throw new ArgumentException("The schedule provided can only " +
339                        "belong to one task at a time");
340
341                if (schedule is RecurringSchedule)
342                    ((RecurringSchedule)schedule).Owner = null;
343                schedule = value;
344                if (schedule is RecurringSchedule)
345                    ((RecurringSchedule)schedule).Owner = this;
346                OnTaskEdited();
347            }
348        }
349
350        /// <summary>
351        /// The log entries which this task has accumulated.
352        /// </summary>
353        public List<LogSinkBase> Log { get; private set; }
354
355        /// <summary>
356        /// The progress manager object which manages the progress of this task.
357        /// </summary>
358        public SteppedProgressManager Progress
359        {
360            get
361            {
362                if (!Executing)
363                    throw new InvalidOperationException("The progress of an erasure can only " +
364                        "be queried when the task is being executed.");
365
366                return progress;
367            }
368            private set
369            {
370                progress = value;
371            }
372        }
373
374        /// <summary>
375        /// Gets the status of the task - whether it is being executed.
376        /// </summary>
377        public bool Executing { get; private set; }
378
379        /// <summary>
380        /// Gets whether this task is currently queued to run. This is true only
381        /// if the queue it is in is an explicit request, i.e will run when the
382        /// executor is idle.
383        /// </summary>
384        public bool Queued
385        {
386            get
387            {
388                if (Executor == null)
389                    throw new InvalidOperationException();
390
391                return Executor.IsTaskQueued(this);
392            }
393        }
394
395        /// <summary>
396        /// Gets whether the task has been cancelled from execution.
397        /// </summary>
398        public bool Canceled
399        {
400            get;
401            private set;
402        }
403
404        /// <summary>
405        /// Cancels the task from running, or, if the task is queued for running,
406        /// removes the task from the queue.
407        /// </summary>
408        public void Cancel()
409        {
410            Executor.UnqueueTask(this);
411            Canceled = true;
412        }
413
414        /// <summary>
415        /// Executes the task in the context of the calling thread.
416        /// </summary>
417        public void Execute()
418        {
419            OnTaskStarted();
420            Executing = true;
421            Canceled = false;
422            Progress = new SteppedProgressManager();
423
424            try
425            {
426                //Run the task
427                foreach (IErasureTarget target in Targets)
428                    try
429                    {
430                        Progress.Steps.Add(new ErasureTargetProgressManagerStep(
431                            target, Targets.Count));
432                        target.Execute();
433                    }
434                    catch (FatalException)
435                    {
436                        throw;
437                    }
438                    catch (OperationCanceledException)
439                    {
440                        throw;
441                    }
442                    catch (SharingViolationException)
443                    {
444                    }
445            }
446            catch (FatalException e)
447            {
448                Logger.Log(e.Message, LogLevel.Fatal);
449            }
450            catch (OperationCanceledException e)
451            {
452                Logger.Log(e.Message, LogLevel.Fatal);
453            }
454            catch (SharingViolationException)
455            {
456            }
457            finally
458            {
459                //If the task is a recurring task, reschedule it since we are done.
460                if (Schedule is RecurringSchedule)
461                {
462                    ((RecurringSchedule)Schedule).Reschedule(DateTime.Now);
463                }
464
465                Progress = null;
466                Executing = false;
467                OnTaskFinished();
468            }
469        }
470
471        private Executor executor;
472        private Schedule schedule;
473        private SteppedProgressManager progress;
474
475        #region Events
476        /// <summary>
477        /// The task has been edited.
478        /// </summary>
479        public EventHandler TaskEdited { get; set; }
480
481        /// <summary>
482        /// The start of the execution of a task.
483        /// </summary>
484        public EventHandler TaskStarted { get; set; }
485
486        /// <summary>
487        /// The completion of the execution of a task.
488        /// </summary>
489        public EventHandler TaskFinished { get; set; }
490
491        /// <summary>
492        /// Broadcasts the task edited event.
493        /// </summary>
494        internal void OnTaskEdited()
495        {
496            if (TaskEdited != null)
497                TaskEdited(this, EventArgs.Empty);
498        }
499
500        /// <summary>
501        /// Broadcasts the task execution start event.
502        /// </summary>
503        private void OnTaskStarted()
504        {
505            if (TaskStarted != null)
506                TaskStarted(this, EventArgs.Empty);
507        }
508
509        /// <summary>
510        /// Broadcasts the task execution completion event.
511        /// </summary>
512        private void OnTaskFinished()
513        {
514            if (TaskFinished != null)
515                TaskFinished(this, EventArgs.Empty);
516        }
517        #endregion
518    }
519
520    /// <summary>
521    /// Returns the progress of an erasure target, since that comprises the
522    /// steps of the Task Progress.
523    /// </summary>
524    public class ErasureTargetProgressManagerStep : SteppedProgressManagerStepBase
525    {
526        /// <summary>
527        /// Constructor.
528        /// </summary>
529        /// <param name="target">The erasure target represented by this object.</param>
530        /// <param name="steps">The number of targets in the task.</param>
531        public ErasureTargetProgressManagerStep(IErasureTarget target, int targets)
532            : base(1.0f / targets)
533        {
534            Target = target;
535        }
536
537        public override ProgressManagerBase Progress
538        {
539            get
540            {
541                ProgressManagerBase targetProgress = Target.Progress;
542                if (targetProgress != null)
543                    TargetProgress = targetProgress;
544
545                return TargetProgress;
546            }
547            set
548            {
549                throw new InvalidOperationException();
550            }
551        }
552
553        /// <summary>
554        /// The erasure target represented by this step.
555        /// </summary>
556        public IErasureTarget Target
557        {
558            get;
559            private set;
560        }
561
562        /// <summary>
563        /// Caches a copy of the progress object for the Target. This is so that
564        /// for as long we our object is alive we can give valid information
565        /// (as required by the SteppedProgressManager class)
566        /// </summary>
567        private ProgressManagerBase TargetProgress;
568    }
569
570    /// <summary>
571    /// A base event class for all event arguments involving a task.
572    /// </summary>
573    public class TaskEventArgs : EventArgs
574    {
575        /// <summary>
576        /// Constructor.
577        /// </summary>
578        /// <param name="task">The task being referred to by this event.</param>
579        public TaskEventArgs(Task task)
580        {
581            Task = task;
582        }
583
584        /// <summary>
585        /// The executing task.
586        /// </summary>
587        public Task Task { get; private set; }
588    }
589}
Note: See TracBrowser for help on using the repository browser.