source: trunk/eraser/Eraser.Manager/Schedule.cs @ 2606

Revision 2606, 17.5 KB checked in by lowjoel, 2 years ago (diff)

Merged the XML Task Lists branch back to trunk. Implements #362: Use XML to store task lists

  • 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.Diagnostics;
27using System.Runtime.Serialization;
28using System.Xml;
29using System.Xml.Serialization;
30using System.Security.Permissions;
31using System.Globalization;
32using Eraser.Util;
33
34namespace Eraser.Manager
35{
36    /// <summary>
37    /// Base class for all schedule types.
38    /// </summary>
39    [Serializable]
40    public abstract class Schedule : ISerializable, IXmlSerializable
41    {
42        #region IXmlSerializable Members
43
44        public System.Xml.Schema.XmlSchema GetSchema()
45        {
46            return null;
47        }
48
49        public abstract void ReadXml(XmlReader reader);
50
51        public abstract void WriteXml(XmlWriter writer);
52
53        #endregion
54
55        #region Default values
56        [Serializable]
57        private class RunManuallySchedule : Schedule
58        {
59            #region Object serialization
60            public RunManuallySchedule(SerializationInfo info, StreamingContext context)
61            {
62            }
63
64            [SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)]
65            public override void GetObjectData(SerializationInfo info, StreamingContext context)
66            {
67            }
68
69            public override void ReadXml(XmlReader reader)
70            {
71                if (reader.GetAttribute("type") != "Manual")
72                    throw new InvalidDataException();
73                reader.Read();
74            }
75
76            public override void WriteXml(XmlWriter writer)
77            {
78                writer.WriteAttributeString("type", "Manual");
79            }
80            #endregion
81
82            public RunManuallySchedule()
83            {
84            }
85
86            public override string UIText
87            {
88                get { return string.Empty; }
89            }
90        }
91
92        [Serializable]
93        private class RunNowSchedule : Schedule
94        {
95            #region Object serialization
96            public RunNowSchedule(SerializationInfo info, StreamingContext context)
97            {
98            }
99
100            [SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)]
101            public override void GetObjectData(SerializationInfo info, StreamingContext context)
102            {
103            }
104
105            public override void ReadXml(XmlReader reader)
106            {
107                if (reader.GetAttribute("type") != "Now")
108                    throw new InvalidDataException();
109                reader.Read();
110            }
111
112            public override void WriteXml(XmlWriter writer)
113            {
114                writer.WriteAttributeString("type", "Now");
115            }
116            #endregion
117
118            public RunNowSchedule()
119            {
120            }
121
122            public override string UIText
123            {
124                get { return string.Empty; }
125            }
126        }
127
128        [Serializable]
129        private class RunOnRestartSchedule : Schedule
130        {
131            #region Object serialization
132            public RunOnRestartSchedule(SerializationInfo info, StreamingContext context)
133            {
134            }
135
136            [SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)]
137            public override void GetObjectData(SerializationInfo info, StreamingContext context)
138            {
139            }
140
141            public override void ReadXml(XmlReader reader)
142            {
143                if (reader.GetAttribute("type") != "Restart")
144                    throw new InvalidDataException();
145                reader.Read();
146            }
147
148            public override void WriteXml(XmlWriter writer)
149            {
150                writer.WriteAttributeString("type", "Restart");
151            }
152            #endregion
153
154            public RunOnRestartSchedule()
155            {
156            }
157
158            public override string UIText
159            {
160                get { return S._("Running on restart"); }
161            }
162        }
163        #endregion
164
165        /// <summary>
166        /// Retrieves the text that should be displayed detailing the nature of
167        /// the schedule for use in user interface elements.
168        /// </summary>
169        public abstract string UIText
170        {
171            get;
172        }
173
174        /// <summary>
175        /// The owner of this schedule item.
176        /// </summary>
177        public Task Owner
178        {
179            get;
180            internal set;
181        }
182
183        /// <summary>
184        /// Populates a SerializationInfo with the data needed to serialize the
185        /// target object.
186        /// </summary>
187        /// <param name="info">The SerializationInfo to populate with data.</param>
188        /// <param name="context">The destination for this serialization.</param>
189        [SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)]
190        public abstract void GetObjectData(SerializationInfo info, StreamingContext context);
191
192        /// <summary>
193        /// The global value for tasks which should be run manually.
194        /// </summary>
195        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes")]
196        public static readonly Schedule RunManually = new RunManuallySchedule();
197
198        /// <summary>
199        /// The global value for tasks which should be run immediately.
200        /// </summary>
201        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes")]
202        public static readonly Schedule RunNow = new RunNowSchedule();
203
204        /// <summary>
205        /// The global value for tasks which should be run when the computer is
206        /// restarted
207        /// </summary>
208        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes")]
209        public static readonly Schedule RunOnRestart = new RunOnRestartSchedule();
210    }
211
212    /// <summary>
213    /// Recurring runs schedule type.
214    /// </summary>
215    [Serializable]
216    public class RecurringSchedule : Schedule
217    {
218        #region Overridden members
219        public override string UIText
220        {
221            get
222            {
223                string result = string.Empty;
224                switch (type)
225                {
226                    case RecurringScheduleUnit.Daily:
227                        if (frequency != 1)
228                            result = S._("Once every {0} days", frequency);
229                        else
230                            result = S._("Once every day");
231                        break;
232                    case RecurringScheduleUnit.Weekdays:
233                        result = S._("Every weekday");
234                        break;
235                    case RecurringScheduleUnit.Weekly:
236                        if ((weeklySchedule & DaysOfWeek.Monday) != 0)
237                            result = S._("Every Monday, {0}");
238                        if ((weeklySchedule & DaysOfWeek.Tuesday) != 0)
239                            result += S._("Every Tuesday, {0}");
240                        if ((weeklySchedule & DaysOfWeek.Wednesday) != 0)
241                            result += S._("Every Wednesday, {0}");
242                        if ((weeklySchedule & DaysOfWeek.Thursday) != 0)
243                            result += S._("Every Thursday, {0}");
244                        if ((weeklySchedule & DaysOfWeek.Friday) != 0)
245                            result += S._("Every Friday, {0}");
246                        if ((weeklySchedule & DaysOfWeek.Saturday) != 0)
247                            result += S._("Every Saturday, {0}");
248                        if ((weeklySchedule & DaysOfWeek.Sunday) != 0)
249                            result += S._("Every Sunday, {0}");
250
251                        result = string.Format(CultureInfo.CurrentCulture, result,
252                            frequency == 1 ?
253                                S._("once every {0} week.", frequency) :
254                                S._("once every {0} weeks.", frequency));
255                        break;
256                    case RecurringScheduleUnit.Monthly:
257                        if (frequency == 1)
258                            result = S._("On day {0} of every month", monthlySchedule);
259                        else
260                            result = S._("On day {0} of every {1} months", monthlySchedule,
261                                frequency);
262                        break;
263                }
264
265                return result + S._(", at {0}", executionTime.TimeOfDay.ToString());
266            }
267        }
268        #endregion
269
270        #region Object serialization
271        protected RecurringSchedule(SerializationInfo info, StreamingContext context)
272        {
273            type = (RecurringScheduleUnit)info.GetValue("Type", typeof(RecurringScheduleUnit));
274            frequency = (int)info.GetValue("Frequency", typeof(int));
275            executionTime = (DateTime)info.GetValue("ExecutionTime", typeof(DateTime));
276            weeklySchedule = (DaysOfWeek)info.GetValue("WeeklySchedule", typeof(DaysOfWeek));
277            monthlySchedule = (int)info.GetValue("MonthlySchedule", typeof(int));
278
279            LastRun = (DateTime)info.GetDateTime("LastRun");
280            NextRunCache = (DateTime)info.GetDateTime("NextRun");
281        }
282
283        [SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)]
284        public override void GetObjectData(SerializationInfo info, StreamingContext context)
285        {
286            info.AddValue("Type", type);
287            info.AddValue("Frequency", frequency);
288            info.AddValue("ExecutionTime", executionTime);
289            info.AddValue("WeeklySchedule", weeklySchedule);
290            info.AddValue("MonthlySchedule", monthlySchedule);
291            info.AddValue("LastRun", LastRun);
292            info.AddValue("NextRun", NextRunCache);
293        }
294
295        public override void ReadXml(XmlReader reader)
296        {
297            if (!Enum.TryParse<RecurringScheduleUnit>(reader.GetAttribute("type"), out type))
298                throw new InvalidDataException();
299            if (!int.TryParse(reader.GetAttribute("frequency"), out frequency))
300                throw new InvalidDataException();
301            if (!DateTime.TryParse(reader.GetAttribute("executionTime"), out executionTime))
302                throw new InvalidDataException();
303            if (!Enum.TryParse<DaysOfWeek>(reader.GetAttribute("weeklySchedule"),
304                out weeklySchedule))
305                throw new InvalidDataException();
306            if (!int.TryParse(reader.GetAttribute("monthlySchedule"), out monthlySchedule))
307                throw new InvalidDataException();
308
309            DateTime lastRun;
310            DateTime nextRunCache;
311            if (!DateTime.TryParse(reader.GetAttribute("lastRun"), out lastRun))
312                throw new InvalidDataException();
313            if (!DateTime.TryParse(reader.GetAttribute("nextRun"), out nextRunCache))
314                throw new InvalidDataException();
315            LastRun = lastRun;
316            NextRunCache = nextRunCache;
317            reader.Read();
318        }
319
320        public override void WriteXml(XmlWriter writer)
321        {
322            writer.WriteAttributeString("type", type.ToString());
323            writer.WriteAttributeString("frequency", frequency.ToString());
324            writer.WriteAttributeString("executionTime", executionTime.ToString("O"));
325            writer.WriteAttributeString("weeklySchedule", weeklySchedule.ToString());
326            writer.WriteAttributeString("monthlySchedule", monthlySchedule.ToString());
327            writer.WriteAttributeString("lastRun", LastRun.ToString("O"));
328            writer.WriteAttributeString("nextRun", NextRunCache.ToString("O"));
329        }
330        #endregion
331
332        /// <summary>
333        /// Constructor.
334        /// </summary>
335        public RecurringSchedule()
336        {
337        }
338
339        /// <summary>
340        /// The type of schedule.
341        /// </summary>
342        public RecurringScheduleUnit ScheduleType
343        {
344            get { return type; }
345            set
346            {
347                type = value;
348                if (Owner != null)
349                    Owner.OnTaskEdited();
350            }
351        }
352
353        /// <summary>
354        /// The frequency of the event. This value is valid only with Daily,
355        /// Weekly and Monthly schedules.
356        /// </summary>
357        public int Frequency
358        {
359            get
360            {
361                if (ScheduleType != RecurringScheduleUnit.Daily && ScheduleType != RecurringScheduleUnit.Weekly &&
362                    ScheduleType != RecurringScheduleUnit.Monthly)
363                    throw new InvalidOperationException("The ScheduleUnit of the schedule " +
364                        "does not require a frequency value, this field would contain garbage.");
365
366                return frequency;
367            }
368            set
369            {
370                if (value == 0)
371                    throw new ArgumentException(S._("The frequency of the recurrence should " +
372                        "be greater than one"));
373
374                frequency = value;
375                if (Owner != null)
376                    Owner.OnTaskEdited();
377            }
378        }
379
380        /// <summary>
381        /// The time of day where the task will be executed.
382        /// </summary>
383        public DateTime ExecutionTime
384        {
385            get { return executionTime; }
386            set
387            {
388                executionTime = value;
389                if (Owner != null)
390                    Owner.OnTaskEdited();
391            }
392        }
393
394        /// <summary>
395        /// The days of the week which this task should be run. This is valid only
396        /// with Weekly schedules. This field is the DaysOfWeek enumerations
397        /// ORed together.
398        /// </summary>
399        public DaysOfWeek WeeklySchedule
400        {
401            get
402            {
403                if (ScheduleType != RecurringScheduleUnit.Weekly)
404                    throw new InvalidOperationException("The ScheduleUnit of the schedule " +
405                        "does not require the WeeklySchedule value, this field would contain garbage");
406
407                return weeklySchedule;
408            }
409            set
410            {
411                if (value == 0)
412                    throw new ArgumentException(S._("The WeeklySchedule should have at " +
413                        "least one day where the task should be run."));
414
415                weeklySchedule = value;
416                if (Owner != null)
417                    Owner.OnTaskEdited();
418            }
419        }
420
421        /// <summary>
422        /// The nth day of the month on which this task will run. This is valid
423        /// only with Monthly schedules
424        /// </summary>
425        public int MonthlySchedule
426        {
427            get
428            {
429                if (ScheduleType != RecurringScheduleUnit.Monthly)
430                    throw new InvalidOperationException("The ScheduleUnit of the schedule does " +
431                        "not require the MonthlySchedule value, this field would contain garbage");
432
433                return monthlySchedule;
434            }
435            set
436            {
437                monthlySchedule = value;
438
439                if (Owner != null)
440                    Owner.OnTaskEdited();
441            }
442        }
443
444        /// <summary>
445        /// The last time this task was executed. This value is used for computing
446        /// the next time the task should be run.
447        /// </summary>
448        public DateTime LastRun
449        {
450            get;
451            private set;
452        }
453
454        /// <summary>
455        /// Computes the next run time based on the last run time, the current
456        /// schedule, and the current time. The timestamp returned will be the next
457        /// time from now which fulfils the schedule.
458        /// </summary>
459        public DateTime NextRun
460        {
461            get
462            {
463                //Get a good starting point, either now, or the last time the task
464                //was run.
465                DateTime nextRun = LastRun;
466                if (nextRun == DateTime.MinValue)
467                    nextRun = DateTime.Now;
468                nextRun = new DateTime(nextRun.Year, nextRun.Month, nextRun.Day, executionTime.Hour,
469                    executionTime.Minute, executionTime.Second);
470
471                switch (ScheduleType)
472                {
473                    case RecurringScheduleUnit.Daily:
474                    {
475                        //First assume that it is today that we are running the schedule
476                        long daysToAdd = (DateTime.Now - nextRun).Days;
477                        nextRun = nextRun.AddDays(daysToAdd);
478
479                        //If we have passed today's run time, schedule it after the next
480                        //frequency
481                        if (nextRun < DateTime.Now)
482                            nextRun = nextRun.AddDays(frequency);
483                        break;
484                    }
485                    case RecurringScheduleUnit.Weekdays:
486                    {
487                        while (nextRun < DateTime.Now ||
488                            LastRun.DayOfWeek == DayOfWeek.Saturday ||
489                            LastRun.DayOfWeek == DayOfWeek.Sunday)
490                            nextRun = nextRun.AddDays(1);
491                        break;
492                    }
493                    case RecurringScheduleUnit.Weekly:
494                    {
495                        if (weeklySchedule == 0)
496                            break;
497
498                        //Find the next eligible day to run the task within this week.
499                        do
500                        {
501                            if (CanRunOnDay(nextRun) && nextRun >= DateTime.Now)
502                                break;
503                            nextRun = nextRun.AddDays(1);
504                        }
505                        while (nextRun.DayOfWeek < DayOfWeek.Saturday);
506
507                        while (nextRun < DateTime.Now || !CanRunOnDay(nextRun))
508                        {
509                            //Find the next eligible day to run the task
510                            nextRun = nextRun.AddDays(7 * (frequency - 1));
511                            for (int daysInWeek = 7; daysInWeek-- != 0; nextRun = nextRun.AddDays(1))
512                            {
513                                if (CanRunOnDay(nextRun) && nextRun >= DateTime.Now)
514                                    break;
515                            }
516                        }
517
518                        break;
519                    }
520                    case RecurringScheduleUnit.Monthly:
521                        if (LastRun == DateTime.MinValue)
522                        {
523                            //Since the schedule has never been used, find the next time
524                            //to run the task. If the current day is less than the
525                            //scheduled day, leave it alone. Otherwise, go to the
526                            //following month.
527                            while (monthlySchedule < nextRun.Day)
528                                nextRun = nextRun.AddDays(1);
529                        }
530                        else
531                        {
532                            //Step the number of months since the last run
533                            while (nextRun < DateTime.Now)
534                                nextRun = nextRun.AddMonths(frequency);
535                        }
536
537                        //Set the day of the month which the task is supposed to run.
538                        nextRun = nextRun.AddDays(monthlySchedule - nextRun.Day);
539                        break;
540                }
541
542                return nextRun;
543            }
544        }
545
546        /// <summary>
547        /// Gets whether the previous run was missed.
548        /// </summary>
549        public bool MissedPreviousSchedule
550        {
551            get
552            {
553                return LastRun != DateTime.MinValue && NextRun != NextRunCache;
554            }
555        }
556
557        /// <summary>
558        /// Returns true if the task can run on the given date. Applies only for
559        /// weekly tasks.
560        /// </summary>
561        /// <param name="date">The date to run on.</param>
562        /// <returns>True if the task will be run on the date.</returns>
563        private bool CanRunOnDay(DateTime date)
564        {
565            if (ScheduleType != RecurringScheduleUnit.Weekly)
566                throw new ArgumentException("The ScheduleUnit of the schedule does " +
567                    "not use the WeeklySchedule value, this field would contain garbage");
568            return ((int)weeklySchedule & (1 << (int)date.DayOfWeek)) != 0;
569        }
570
571        /// <summary>
572        /// Reschedules the task.
573        /// </summary>
574        /// <param name="lastRun">The last time the task was run.</param>
575        internal void Reschedule(DateTime lastRun)
576        {
577            LastRun = lastRun;
578            NextRunCache = NextRun;
579        }
580
581        private RecurringScheduleUnit type;
582        private int frequency;
583        private DateTime executionTime;
584        private DaysOfWeek weeklySchedule;
585        private int monthlySchedule;
586
587        /// <summary>
588        /// The next time the task is scheduled to run - this is cached from the previous
589        /// calculation of the next run time to determine if the task's schedule was missed
590        /// </summary>
591        private DateTime NextRunCache;
592    }
593
594    /// <summary>
595    /// The types of schedule
596    /// </summary>
597    public enum RecurringScheduleUnit
598    {
599        /// <summary>
600        /// Daily schedule type
601        /// </summary>
602        Daily,
603
604        /// <summary>
605        /// Weekdays-only schedule type
606        /// </summary>
607        Weekdays,
608
609        /// <summary>
610        /// Weekly schedule type
611        /// </summary>
612        Weekly,
613
614        /// <summary>
615        /// Monthly schedule type
616        /// </summary>
617        Monthly
618    }
619
620    /// <summary>
621    /// The days of the week, with values usable in a bitfield.
622    /// </summary>
623    [Flags]
624    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1714:FlagsEnumsShouldHavePluralNames")]
625    public enum DaysOfWeek
626    {
627        None = 0,
628        Sunday = 1 << DayOfWeek.Sunday,
629        Monday = 1 << DayOfWeek.Monday,
630        Tuesday = 1 << DayOfWeek.Tuesday,
631        Wednesday = 1 << DayOfWeek.Wednesday,
632        Thursday = 1 << DayOfWeek.Thursday,
633        Friday = 1 << DayOfWeek.Friday,
634        Saturday = 1 << DayOfWeek.Saturday
635    }
636}
Note: See TracBrowser for help on using the repository browser.