source: branches/eraser6/Manager/Schedule.cs @ 494

Revision 494, 12.7 KB checked in by lowjoel, 5 years ago (diff)

-Fixed a mistake in the UnusedIdsList? population when loading a task list. The ID should be postincremented rather than preincremented
-Implemented #53

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