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

Revision 1802, 14.8 KB checked in by lowjoel, 5 years ago (diff)

Merged the CodeReview? Branch back to trunk. (Finally!) Closes #275: Code Review.

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