123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561 |
- MODULE TaskScheduler; (** AUTHOR "staubesv"; PURPOSE "Simple task scheduler"; *)
- IMPORT
- Streams, Modules, Kernel, Locks, Dates, Strings, Files, Commands;
- CONST
- (* task.repeatType *)
- Unknown* = -1;
- Once* = 0; (** default *)
- EverySecond* = 1;
- EveryMinute* = 2;
- Hourly* = 3;
- Daily* = 4;
- Weekly* = 5;
- Monthly* = 6;
- Yearly* = 7;
- NameLength* = 64;
- DescriptionLength* = 256;
- CommandLength* = 256;
- ImageNameLength* = 256;
- TYPE
- TaskInfo* = RECORD
- name* : ARRAY NameLength OF CHAR;
- description* : ARRAY DescriptionLength OF CHAR;
- command* : ARRAY CommandLength OF CHAR;
- image* : ARRAY ImageNameLength OF CHAR;
- repeatType* : LONGINT;
- trigger* : Dates.DateTime;
- END;
- TYPE
- Task* = OBJECT
- VAR
- id- : LONGINT;
- timestamp- : LONGINT;
- info : TaskInfo;
- user* : ANY;
- handled : BOOLEAN;
- week, weekDay : LONGINT;
- list- : TaskList;
- next : Task;
- PROCEDURE &Init*;
- BEGIN
- id := GetId();
- info.name := ""; info.description := "";
- info.command := "";
- info.image := "";
- info.repeatType := Unknown;
- timestamp := 0;
- user := NIL;
- handled := FALSE;
- list := NIL;
- next := NIL;
- END Init;
- PROCEDURE SetInfo*(CONST info : TaskInfo);
- BEGIN {EXCLUSIVE}
- SELF.info := info;
- INC(timestamp);
- END SetInfo;
- PROCEDURE GetInfo*() : TaskInfo;
- BEGIN {EXCLUSIVE}
- RETURN info;
- END GetInfo;
- PROCEDURE ToStream(out : Streams.Writer);
- VAR string : ARRAY 128 OF CHAR;
- BEGIN {EXCLUSIVE}
- ASSERT(out # NIL);
- Strings.FormatDateTime("dd.mm.yyyy hh:nn:ss", info.trigger, string);
- out.String(string); out.String(" ");
- TypeToStream(out, info.repeatType);
- out.String(' "'); out.String(info.name); out.String('" "');
- out.String(info.description); out.String('" "');
- out.String(info.command); out.String('" "');
- out.String(info.image); out.String('"');
- out.Ln;
- END ToStream;
- PROCEDURE FromStream(in : Streams.Reader) : BOOLEAN;
- VAR string : ARRAY 2048 OF CHAR;
- BEGIN {EXCLUSIVE}
- ASSERT(in # NIL);
- in.SkipWhitespace; in.String(string); Strings.StrToDate(string, info.trigger);
- in.SkipWhitespace; in.String(string); Strings.StrToTime(string, info.trigger);
- info.repeatType := TypeFromStream(in);
- in.SkipWhitespace; in.String(info.name);
- in.SkipWhitespace; in.String(info.description);
- in.SkipWhitespace; in.String(info.command);
- in.SkipWhitespace; in.String(info.image);
- SetTriggerX(info.trigger, info.repeatType);
- RETURN TRUE;
- END FromStream;
- PROCEDURE Confirm*;
- BEGIN
- IF (list # NIL) THEN list.ConfirmTask(SELF); END;
- END Confirm;
- (* Returns time left until next time triggered in seconds or 0 if not triggered anymore *)
- PROCEDURE Left*(VAR days, hours, minutes, seconds : LONGINT);
- VAR currentTime : Dates.DateTime;
- BEGIN
- currentTime := Dates.Now();
- LeftFrom(currentTime, days, hours, minutes, seconds);
- END Left;
- PROCEDURE LeftFrom*(CONST dt : Dates.DateTime; VAR days, hours, minutes, seconds : LONGINT);
- BEGIN {EXCLUSIVE}
- IF (Dates.CompareDateTime(dt, info.trigger) = -1) THEN
- Dates.TimeDifference(dt, info.trigger, days, hours, minutes, seconds);
- ELSE
- days := 0; hours := 0; minutes := 0; seconds := 0;
- END;
- END LeftFrom;
- PROCEDURE SetTrigger*(dt : Dates.DateTime; type : LONGINT);
- BEGIN {EXCLUSIVE}
- SetTriggerX(dt, type);
- END SetTrigger;
- PROCEDURE SetTriggerX(dt : Dates.DateTime; repeatType : LONGINT);
- VAR currentTime : Dates.DateTime; ignoreYear : LONGINT;
- BEGIN
- ASSERT(Dates.ValidDateTime(dt));
- INC(timestamp);
- info.repeatType := repeatType;
- info.trigger := dt;
- IF (repeatType # Once) THEN
- currentTime := Dates.Now();
- IF (repeatType = EverySecond) THEN
- WHILE (Dates.CompareDateTime(info.trigger, currentTime) # 1) DO
- Dates.AddSeconds(info.trigger, 1);
- END;
- ELSIF (repeatType = EveryMinute) THEN
- WHILE (Dates.CompareDateTime(info.trigger, currentTime) # 1) DO
- Dates.AddMinutes(info.trigger, 1);
- END;
- ELSIF (repeatType = Hourly) THEN
- WHILE (Dates.CompareDateTime(info.trigger, currentTime) # 1) DO
- Dates.AddHours(info.trigger, 1);
- END;
- ELSIF (repeatType = Weekly) THEN
- WHILE (Dates.CompareDateTime(info.trigger, currentTime) # 1) DO
- Dates.AddDays(info.trigger, 7);
- END;
- ELSIF (repeatType = Monthly) THEN
- WHILE (Dates.CompareDateTime(info.trigger, currentTime) # 1) DO
- Dates.AddMonths(info.trigger, 1);
- END;
- ELSIF (repeatType = Yearly) THEN
- WHILE (Dates.CompareDateTime(info.trigger, currentTime) # 1) DO
- Dates.AddYears(info.trigger, 1);
- END;
- END;
- END;
- Dates.WeekDate(info.trigger, ignoreYear, week, weekDay);
- END SetTriggerX;
- PROCEDURE GetTrigger*() : Dates.DateTime;
- BEGIN {EXCLUSIVE}
- RETURN info.trigger;
- END GetTrigger;
- PROCEDURE TriggerNow*;
- VAR msg: ARRAY 256 OF CHAR; res:WORD;
- BEGIN
- IF Strings.Length(info.command)>0 THEN
- Commands.Call(info.command, {}, res, msg);
- END;
- END TriggerNow;
- PROCEDURE Check(time : Dates.DateTime; VAR left : LONGINT);
- BEGIN
- IF (left = 0) & ~handled THEN
- IF (info.repeatType = Once) THEN
- END;
- END;
- END Check;
- END Task;
- TaskArray* = POINTER TO ARRAY OF Task;
- TYPE
- SelectorProcedure* = PROCEDURE {DELEGATE} (time : Dates.DateTime; task : Task) : BOOLEAN;
- EnumeratorProcedure* = PROCEDURE {DELEGATE} (time : Dates.DateTime; task : Task);
- TaskList* = OBJECT
- VAR
- head : Task;
- nofTasks : LONGINT;
- lock : Locks.RWLock;
- PROCEDURE &Init*;
- BEGIN
- head := NIL;
- nofTasks := 0;
- NEW(lock);
- END Init;
- PROCEDURE Load*(CONST filename : ARRAY OF CHAR) : BOOLEAN;
- VAR file : Files.File; in : Files.Reader; task : Task; succeeded : BOOLEAN;
- BEGIN
- file := Files.Old(filename);
- IF (file # NIL) THEN
- succeeded := TRUE;
- Files.OpenReader(in, file, 0);
- lock.AcquireWrite;
- in.SkipWhitespace;
- WHILE succeeded & (in.Available() > 0) & (in.res = Streams.Ok) DO
- NEW(task);
- succeeded := task.FromStream(in);
- IF succeeded THEN
- Add(task);
- END;
- in.SkipWhitespace;
- END;
- succeeded := succeeded OR ~((in.res # Streams.Ok) & (in.res # Streams.EOF));
- lock.ReleaseWrite;
- ELSE
- succeeded := FALSE;
- END;
- RETURN succeeded;
- END Load;
- PROCEDURE Store*(CONST filename : ARRAY OF CHAR) : BOOLEAN;
- VAR file : Files.File; out : Files.Writer; task : Task;
- BEGIN
- file := Files.New(filename);
- IF (file # NIL) THEN
- Files.OpenWriter(out, file, 0);
- lock.AcquireRead;
- task := head;
- WHILE (task # NIL) & ~task.handled DO
- task.ToStream(out);
- task := task.next;
- END;
- out.Update;
- Files.Register(file);
- lock.ReleaseRead;
- RETURN TRUE;
- ELSE
- RETURN FALSE;
- END;
- END Store;
- PROCEDURE Reset*;
- BEGIN
- lock.AcquireWrite;
- head := NIL; nofTasks := 0;
- lock.ReleaseWrite;
- END Reset;
- PROCEDURE ConfirmTask*(task : Task);
- BEGIN
- ASSERT(task # NIL);
- lock.AcquireWrite;
- Remove(task); (* TODO: Check race condition... *)
- IF (task.info.repeatType # Once) & (task.info.repeatType # Unknown) THEN
- task.SetTrigger(task.info.trigger, task.info.repeatType);
- Add(task); (* trigger will be adapted -> add again so that the list remains sorted *)
- END;
- lock.ReleaseWrite;
- END ConfirmTask;
- PROCEDURE FindById*(id : LONGINT) : Task;
- VAR task : Task;
- BEGIN
- lock.AcquireRead;
- task := head;
- WHILE (task # NIL) & (task.id # id) DO task := task.next; END;
- lock.ReleaseRead;
- RETURN task;
- END FindById;
- PROCEDURE Select*(selector : SelectorProcedure; CONST dt : Dates.DateTime; VAR tasks : TaskArray; VAR nofSelectedTasks, nofTasks : LONGINT);
- VAR task : Task; i : LONGINT;
- BEGIN
- Clear(tasks);
- lock.AcquireRead;
- (* first count number of selected tasks *)
- nofTasks := SELF.nofTasks;
- nofSelectedTasks := 0;
- task := head;
- WHILE (task # NIL) DO
- IF selector(dt, task) THEN INC(nofSelectedTasks); END;
- task := task.next;
- END;
- IF (nofSelectedTasks > 0) THEN
- IF (tasks = NIL) OR (nofSelectedTasks > LEN(tasks)) THEN
- NEW(tasks, nofSelectedTasks);
- END;
- i := 0;
- task := head;
- WHILE (task # NIL) DO
- IF selector(dt, task) THEN
- tasks[i] := task;
- INC(i);
- END;
- task := task.next;
- END;
- END;
- lock.ReleaseRead;
- END Select;
- PROCEDURE Enumerate*(time : Dates.DateTime; proc : EnumeratorProcedure);
- VAR task : Task;
- BEGIN
- ASSERT(proc # NIL);
- ASSERT(lock.HasReadLock());
- task := head;
- WHILE (task # NIL) DO
- proc(time, task);
- task := task.next;
- END;
- END Enumerate;
- PROCEDURE Add*(task : Task);
- VAR t : Task;
- BEGIN
- ASSERT((task # NIL) & (task.list = NIL));
- task.id := GetId();
- lock.AcquireWrite;
- IF (head = NIL) OR (Dates.CompareDateTime(task.GetTrigger(), head.GetTrigger()) = -1) THEN
- task.next := head;
- head := task;
- ELSE
- t := head;
- WHILE (t # NIL) & (t.next # NIL) & (Dates.CompareDateTime(task.GetTrigger(), t.next.GetTrigger()) = 1) DO t := t.next; END;
- task.next := t.next;
- t.next := task;
- END;
- task.list := SELF;
- INC(nofTasks);
- lock.ReleaseWrite;
- END Add;
- PROCEDURE Remove*(task : Task);
- VAR t : Task;
- BEGIN
- ASSERT((task # NIL) & (task.list = SELF));
- lock.AcquireWrite;
- IF (head = task) THEN
- head := head.next;
- DEC(nofTasks);
- ELSE
- t := head;
- WHILE (t # NIL) & (t.next # task) DO t := t.next; END;
- IF (t.next # NIL) THEN
- t.next := t.next.next;
- DEC(nofTasks);
- END;
- END;
- task.list := NIL;
- lock.ReleaseWrite;
- END Remove;
- (** Returns total number of tasks in this list *)
- PROCEDURE GetNofTasks*() : LONGINT;
- BEGIN
- RETURN nofTasks;
- END GetNofTasks;
- END TaskList;
- TYPE
- Scheduler = OBJECT
- VAR
- sleepHint : LONGINT;
- alive, dead : BOOLEAN;
- timer : Kernel.Timer;
- PROCEDURE &Init;
- BEGIN
- sleepHint := 1000;
- alive := TRUE; dead := FALSE;
- NEW(timer);
- END Init;
- PROCEDURE Stop;
- BEGIN
- alive := FALSE; timer.Wakeup;
- BEGIN {EXCLUSIVE} AWAIT(dead); END;
- END Stop;
- PROCEDURE Update;
- BEGIN
- sleepHint := 500;
- timer.Wakeup;
- END Update;
- PROCEDURE CheckTask(time : Dates.DateTime; task : Task);
- VAR hint : LONGINT;
- BEGIN
- task.Check(time, hint);
- IF (hint > 0) & (hint < sleepHint) THEN sleepHint := hint; END;
- END CheckTask;
- BEGIN {ACTIVE}
- WHILE alive DO
- sleepHint := MAX(LONGINT);
- taskList.lock.AcquireRead;
- taskList.Enumerate(Dates.Now(), CheckTask);
- taskList.lock.ReleaseRead;
- IF alive THEN timer.Sleep(sleepHint); END;
- END;
- BEGIN {EXCLUSIVE} dead := TRUE; END;
- END Scheduler;
- VAR
- taskList : TaskList;
- scheduler : Scheduler;
- id : LONGINT;
- StrNoName-, StrNoDescription-, StrNoCommand-, StrNoImage-: Strings.String;
- PROCEDURE TypeToStream(out : Streams.Writer; repeatType : LONGINT);
- BEGIN
- ASSERT(out # NIL);
- CASE repeatType OF
- |Unknown: out.String("Unknown");
- |Once: out.String("Once");
- |EverySecond: out.String("EverySecond");
- |EveryMinute: out.String("EveryMinute");
- |Hourly: out.String("Hourly");
- |Daily: out.String("Daily");
- |Weekly: out.String("Weekly");
- |Monthly: out.String("Monthly");
- |Yearly: out.String("Yearly");
- ELSE
- out.String("Unknown");
- END;
- END TypeToStream;
- PROCEDURE TypeFromStream(in : Streams.Reader) : LONGINT;
- VAR repeatType : LONGINT; string : ARRAY 32 OF CHAR;
- BEGIN
- ASSERT(in # NIL);
- repeatType := Unknown;
- in.SkipWhitespace; in.String(string);
- IF (string = "Once") THEN repeatType := Once;
- ELSIF (string = "EverySecond") THEN repeatType := EverySecond;
- ELSIF (string = "EveryMinute") THEN repeatType := EveryMinute;
- ELSIF (string = "Hourly") THEN repeatType := Hourly;
- ELSIF (string = "Daily") THEN repeatType := Daily;
- ELSIF (string = "Weekly") THEN repeatType := Weekly;
- ELSIF (string = "Monthly") THEN repeatType := Monthly;
- ELSIF (string = "Yearly") THEN repeatType := Yearly;
- END;
- RETURN repeatType;
- END TypeFromStream;
- PROCEDURE GetId() : LONGINT;
- BEGIN {EXCLUSIVE}
- INC(id);
- RETURN id;
- END GetId;
- PROCEDURE GetTaskList*() : TaskList;
- VAR taskList : TaskList;
- BEGIN
- NEW(taskList);
- RETURN taskList;
- END GetTaskList;
- (** Helper functions *)
- PROCEDURE GetRepeatTypeString*(repeatType : LONGINT; VAR string : ARRAY OF CHAR);
- BEGIN
- CASE repeatType OF
- |Unknown: string := "Unknown";
- |Once: string := "Once";
- |EverySecond: string := "Each Second";
- |EveryMinute: string := "Each Minute";
- |Hourly: string := "Hourly";
- |Daily: string := "Daily";
- |Weekly: string := "Weekly";
- |Monthly: string := "Monthly";
- |Yearly: string := "Yearly";
- ELSE
- string := "Unknown";
- END;
- END GetRepeatTypeString;
- (** TaskArray helper functions *)
- (** Returns TRUE if both task arrays contain exactly the same tasks in the same order, FALSE otherwise *)
- PROCEDURE IsEqual*(tasks1, tasks2 : TaskArray) : BOOLEAN;
- VAR i : LONGINT;
- PROCEDURE SameElement(t1, t2 : Task) : BOOLEAN;
- BEGIN
- RETURN ((t1 = NIL) & (t2 = NIL)) OR ((t1 # NIL) & (t2 # NIL) & (t1.id = t2.id));
- END SameElement;
- BEGIN
- ASSERT((tasks1 # NIL) & (tasks2 # NIL));
- IF (LEN(tasks1) = LEN(tasks2)) THEN
- i := 0;
- WHILE (i < LEN(tasks1)) DO
- IF ~SameElement(tasks1[i], tasks2[i]) THEN RETURN FALSE; END;
- INC(i);
- END;
- RETURN TRUE;
- END;
- RETURN FALSE;
- END IsEqual;
- PROCEDURE Copy*(from : TaskArray; VAR to : TaskArray);
- VAR i : LONGINT;
- BEGIN
- ASSERT(from # NIL);
- IF (to = NIL) OR (LEN(to) < LEN(from)) THEN NEW(to, LEN(from)); END;
- FOR i := 0 TO LEN(from)-1 DO to[i] := from[i]; END;
- END Copy;
- PROCEDURE Clear*(tasks : TaskArray);
- VAR i : LONGINT;
- BEGIN
- ASSERT(tasks # NIL);
- FOR i := 0 TO LEN(tasks)-1 DO tasks[i] := NIL; END;
- END Clear;
- PROCEDURE InitStrings;
- BEGIN
- StrNoName := Strings.NewString("NoName");
- StrNoDescription := Strings.NewString("NoDescription");
- StrNoCommand := Strings.NewString("NoCommand");
- StrNoImage := Strings.NewString("NoImage");
- END InitStrings;
- PROCEDURE Cleanup;
- BEGIN
- scheduler.Stop;
- END Cleanup;
- BEGIN
- InitStrings;
- NEW(taskList);
- NEW(scheduler);
- Modules.InstallTermHandler(Cleanup);
- END TaskScheduler.
- System.Free WMTaskScheduler TaskScheduler ~
|