MODULE TaskScheduler;
IMPORT
Streams, Modules, Kernel, Locks, Dates, Strings, Files, Commands;
CONST
Unknown* = -1;
Once* = 0;
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;
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:LONGINT;
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);
IF (task.info.repeatType # Once) & (task.info.repeatType # Unknown) THEN
task.SetTrigger(task.info.trigger, task.info.repeatType);
Add(task);
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;
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;
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;
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;
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.
SystemTools.Free WMTaskScheduler TaskScheduler ~