TaskScheduler.Mod 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561
  1. MODULE TaskScheduler; (** AUTHOR "staubesv"; PURPOSE "Simple task scheduler"; *)
  2. IMPORT
  3. Streams, Modules, Kernel, Locks, Dates, Strings, Files, Commands;
  4. CONST
  5. (* task.repeatType *)
  6. Unknown* = -1;
  7. Once* = 0; (** default *)
  8. EverySecond* = 1;
  9. EveryMinute* = 2;
  10. Hourly* = 3;
  11. Daily* = 4;
  12. Weekly* = 5;
  13. Monthly* = 6;
  14. Yearly* = 7;
  15. NameLength* = 64;
  16. DescriptionLength* = 256;
  17. CommandLength* = 256;
  18. ImageNameLength* = 256;
  19. TYPE
  20. TaskInfo* = RECORD
  21. name* : ARRAY NameLength OF CHAR;
  22. description* : ARRAY DescriptionLength OF CHAR;
  23. command* : ARRAY CommandLength OF CHAR;
  24. image* : ARRAY ImageNameLength OF CHAR;
  25. repeatType* : LONGINT;
  26. trigger* : Dates.DateTime;
  27. END;
  28. TYPE
  29. Task* = OBJECT
  30. VAR
  31. id- : LONGINT;
  32. timestamp- : LONGINT;
  33. info : TaskInfo;
  34. user* : ANY;
  35. handled : BOOLEAN;
  36. week, weekDay : LONGINT;
  37. list- : TaskList;
  38. next : Task;
  39. PROCEDURE &Init*;
  40. BEGIN
  41. id := GetId();
  42. info.name := ""; info.description := "";
  43. info.command := "";
  44. info.image := "";
  45. info.repeatType := Unknown;
  46. timestamp := 0;
  47. user := NIL;
  48. handled := FALSE;
  49. list := NIL;
  50. next := NIL;
  51. END Init;
  52. PROCEDURE SetInfo*(CONST info : TaskInfo);
  53. BEGIN {EXCLUSIVE}
  54. SELF.info := info;
  55. INC(timestamp);
  56. END SetInfo;
  57. PROCEDURE GetInfo*() : TaskInfo;
  58. BEGIN {EXCLUSIVE}
  59. RETURN info;
  60. END GetInfo;
  61. PROCEDURE ToStream(out : Streams.Writer);
  62. VAR string : ARRAY 128 OF CHAR;
  63. BEGIN {EXCLUSIVE}
  64. ASSERT(out # NIL);
  65. Strings.FormatDateTime("dd.mm.yyyy hh:nn:ss", info.trigger, string);
  66. out.String(string); out.String(" ");
  67. TypeToStream(out, info.repeatType);
  68. out.String(' "'); out.String(info.name); out.String('" "');
  69. out.String(info.description); out.String('" "');
  70. out.String(info.command); out.String('" "');
  71. out.String(info.image); out.String('"');
  72. out.Ln;
  73. END ToStream;
  74. PROCEDURE FromStream(in : Streams.Reader) : BOOLEAN;
  75. VAR string : ARRAY 2048 OF CHAR;
  76. BEGIN {EXCLUSIVE}
  77. ASSERT(in # NIL);
  78. in.SkipWhitespace; in.String(string); Strings.StrToDate(string, info.trigger);
  79. in.SkipWhitespace; in.String(string); Strings.StrToTime(string, info.trigger);
  80. info.repeatType := TypeFromStream(in);
  81. in.SkipWhitespace; in.String(info.name);
  82. in.SkipWhitespace; in.String(info.description);
  83. in.SkipWhitespace; in.String(info.command);
  84. in.SkipWhitespace; in.String(info.image);
  85. SetTriggerX(info.trigger, info.repeatType);
  86. RETURN TRUE;
  87. END FromStream;
  88. PROCEDURE Confirm*;
  89. BEGIN
  90. IF (list # NIL) THEN list.ConfirmTask(SELF); END;
  91. END Confirm;
  92. (* Returns time left until next time triggered in seconds or 0 if not triggered anymore *)
  93. PROCEDURE Left*(VAR days, hours, minutes, seconds : LONGINT);
  94. VAR currentTime : Dates.DateTime;
  95. BEGIN
  96. currentTime := Dates.Now();
  97. LeftFrom(currentTime, days, hours, minutes, seconds);
  98. END Left;
  99. PROCEDURE LeftFrom*(CONST dt : Dates.DateTime; VAR days, hours, minutes, seconds : LONGINT);
  100. BEGIN {EXCLUSIVE}
  101. IF (Dates.CompareDateTime(dt, info.trigger) = -1) THEN
  102. Dates.TimeDifference(dt, info.trigger, days, hours, minutes, seconds);
  103. ELSE
  104. days := 0; hours := 0; minutes := 0; seconds := 0;
  105. END;
  106. END LeftFrom;
  107. PROCEDURE SetTrigger*(dt : Dates.DateTime; type : LONGINT);
  108. BEGIN {EXCLUSIVE}
  109. SetTriggerX(dt, type);
  110. END SetTrigger;
  111. PROCEDURE SetTriggerX(dt : Dates.DateTime; repeatType : LONGINT);
  112. VAR currentTime : Dates.DateTime; ignoreYear : LONGINT;
  113. BEGIN
  114. ASSERT(Dates.ValidDateTime(dt));
  115. INC(timestamp);
  116. info.repeatType := repeatType;
  117. info.trigger := dt;
  118. IF (repeatType # Once) THEN
  119. currentTime := Dates.Now();
  120. IF (repeatType = EverySecond) THEN
  121. WHILE (Dates.CompareDateTime(info.trigger, currentTime) # 1) DO
  122. Dates.AddSeconds(info.trigger, 1);
  123. END;
  124. ELSIF (repeatType = EveryMinute) THEN
  125. WHILE (Dates.CompareDateTime(info.trigger, currentTime) # 1) DO
  126. Dates.AddMinutes(info.trigger, 1);
  127. END;
  128. ELSIF (repeatType = Hourly) THEN
  129. WHILE (Dates.CompareDateTime(info.trigger, currentTime) # 1) DO
  130. Dates.AddHours(info.trigger, 1);
  131. END;
  132. ELSIF (repeatType = Weekly) THEN
  133. WHILE (Dates.CompareDateTime(info.trigger, currentTime) # 1) DO
  134. Dates.AddDays(info.trigger, 7);
  135. END;
  136. ELSIF (repeatType = Monthly) THEN
  137. WHILE (Dates.CompareDateTime(info.trigger, currentTime) # 1) DO
  138. Dates.AddMonths(info.trigger, 1);
  139. END;
  140. ELSIF (repeatType = Yearly) THEN
  141. WHILE (Dates.CompareDateTime(info.trigger, currentTime) # 1) DO
  142. Dates.AddYears(info.trigger, 1);
  143. END;
  144. END;
  145. END;
  146. Dates.WeekDate(info.trigger, ignoreYear, week, weekDay);
  147. END SetTriggerX;
  148. PROCEDURE GetTrigger*() : Dates.DateTime;
  149. BEGIN {EXCLUSIVE}
  150. RETURN info.trigger;
  151. END GetTrigger;
  152. PROCEDURE TriggerNow*;
  153. VAR msg: ARRAY 256 OF CHAR; res:WORD;
  154. BEGIN
  155. IF Strings.Length(info.command)>0 THEN
  156. Commands.Call(info.command, {}, res, msg);
  157. END;
  158. END TriggerNow;
  159. PROCEDURE Check(time : Dates.DateTime; VAR left : LONGINT);
  160. BEGIN
  161. IF (left = 0) & ~handled THEN
  162. IF (info.repeatType = Once) THEN
  163. END;
  164. END;
  165. END Check;
  166. END Task;
  167. TaskArray* = POINTER TO ARRAY OF Task;
  168. TYPE
  169. SelectorProcedure* = PROCEDURE {DELEGATE} (time : Dates.DateTime; task : Task) : BOOLEAN;
  170. EnumeratorProcedure* = PROCEDURE {DELEGATE} (time : Dates.DateTime; task : Task);
  171. TaskList* = OBJECT
  172. VAR
  173. head : Task;
  174. nofTasks : LONGINT;
  175. lock : Locks.RWLock;
  176. PROCEDURE &Init*;
  177. BEGIN
  178. head := NIL;
  179. nofTasks := 0;
  180. NEW(lock);
  181. END Init;
  182. PROCEDURE Load*(CONST filename : ARRAY OF CHAR) : BOOLEAN;
  183. VAR file : Files.File; in : Files.Reader; task : Task; succeeded : BOOLEAN;
  184. BEGIN
  185. file := Files.Old(filename);
  186. IF (file # NIL) THEN
  187. succeeded := TRUE;
  188. Files.OpenReader(in, file, 0);
  189. lock.AcquireWrite;
  190. in.SkipWhitespace;
  191. WHILE succeeded & (in.Available() > 0) & (in.res = Streams.Ok) DO
  192. NEW(task);
  193. succeeded := task.FromStream(in);
  194. IF succeeded THEN
  195. Add(task);
  196. END;
  197. in.SkipWhitespace;
  198. END;
  199. succeeded := succeeded OR ~((in.res # Streams.Ok) & (in.res # Streams.EOF));
  200. lock.ReleaseWrite;
  201. ELSE
  202. succeeded := FALSE;
  203. END;
  204. RETURN succeeded;
  205. END Load;
  206. PROCEDURE Store*(CONST filename : ARRAY OF CHAR) : BOOLEAN;
  207. VAR file : Files.File; out : Files.Writer; task : Task;
  208. BEGIN
  209. file := Files.New(filename);
  210. IF (file # NIL) THEN
  211. Files.OpenWriter(out, file, 0);
  212. lock.AcquireRead;
  213. task := head;
  214. WHILE (task # NIL) & ~task.handled DO
  215. task.ToStream(out);
  216. task := task.next;
  217. END;
  218. out.Update;
  219. Files.Register(file);
  220. lock.ReleaseRead;
  221. RETURN TRUE;
  222. ELSE
  223. RETURN FALSE;
  224. END;
  225. END Store;
  226. PROCEDURE Reset*;
  227. BEGIN
  228. lock.AcquireWrite;
  229. head := NIL; nofTasks := 0;
  230. lock.ReleaseWrite;
  231. END Reset;
  232. PROCEDURE ConfirmTask*(task : Task);
  233. BEGIN
  234. ASSERT(task # NIL);
  235. lock.AcquireWrite;
  236. Remove(task); (* TODO: Check race condition... *)
  237. IF (task.info.repeatType # Once) & (task.info.repeatType # Unknown) THEN
  238. task.SetTrigger(task.info.trigger, task.info.repeatType);
  239. Add(task); (* trigger will be adapted -> add again so that the list remains sorted *)
  240. END;
  241. lock.ReleaseWrite;
  242. END ConfirmTask;
  243. PROCEDURE FindById*(id : LONGINT) : Task;
  244. VAR task : Task;
  245. BEGIN
  246. lock.AcquireRead;
  247. task := head;
  248. WHILE (task # NIL) & (task.id # id) DO task := task.next; END;
  249. lock.ReleaseRead;
  250. RETURN task;
  251. END FindById;
  252. PROCEDURE Select*(selector : SelectorProcedure; CONST dt : Dates.DateTime; VAR tasks : TaskArray; VAR nofSelectedTasks, nofTasks : LONGINT);
  253. VAR task : Task; i : LONGINT;
  254. BEGIN
  255. Clear(tasks);
  256. lock.AcquireRead;
  257. (* first count number of selected tasks *)
  258. nofTasks := SELF.nofTasks;
  259. nofSelectedTasks := 0;
  260. task := head;
  261. WHILE (task # NIL) DO
  262. IF selector(dt, task) THEN INC(nofSelectedTasks); END;
  263. task := task.next;
  264. END;
  265. IF (nofSelectedTasks > 0) THEN
  266. IF (tasks = NIL) OR (nofSelectedTasks > LEN(tasks)) THEN
  267. NEW(tasks, nofSelectedTasks);
  268. END;
  269. i := 0;
  270. task := head;
  271. WHILE (task # NIL) DO
  272. IF selector(dt, task) THEN
  273. tasks[i] := task;
  274. INC(i);
  275. END;
  276. task := task.next;
  277. END;
  278. END;
  279. lock.ReleaseRead;
  280. END Select;
  281. PROCEDURE Enumerate*(time : Dates.DateTime; proc : EnumeratorProcedure);
  282. VAR task : Task;
  283. BEGIN
  284. ASSERT(proc # NIL);
  285. ASSERT(lock.HasReadLock());
  286. task := head;
  287. WHILE (task # NIL) DO
  288. proc(time, task);
  289. task := task.next;
  290. END;
  291. END Enumerate;
  292. PROCEDURE Add*(task : Task);
  293. VAR t : Task;
  294. BEGIN
  295. ASSERT((task # NIL) & (task.list = NIL));
  296. task.id := GetId();
  297. lock.AcquireWrite;
  298. IF (head = NIL) OR (Dates.CompareDateTime(task.GetTrigger(), head.GetTrigger()) = -1) THEN
  299. task.next := head;
  300. head := task;
  301. ELSE
  302. t := head;
  303. WHILE (t # NIL) & (t.next # NIL) & (Dates.CompareDateTime(task.GetTrigger(), t.next.GetTrigger()) = 1) DO t := t.next; END;
  304. task.next := t.next;
  305. t.next := task;
  306. END;
  307. task.list := SELF;
  308. INC(nofTasks);
  309. lock.ReleaseWrite;
  310. END Add;
  311. PROCEDURE Remove*(task : Task);
  312. VAR t : Task;
  313. BEGIN
  314. ASSERT((task # NIL) & (task.list = SELF));
  315. lock.AcquireWrite;
  316. IF (head = task) THEN
  317. head := head.next;
  318. DEC(nofTasks);
  319. ELSE
  320. t := head;
  321. WHILE (t # NIL) & (t.next # task) DO t := t.next; END;
  322. IF (t.next # NIL) THEN
  323. t.next := t.next.next;
  324. DEC(nofTasks);
  325. END;
  326. END;
  327. task.list := NIL;
  328. lock.ReleaseWrite;
  329. END Remove;
  330. (** Returns total number of tasks in this list *)
  331. PROCEDURE GetNofTasks*() : LONGINT;
  332. BEGIN
  333. RETURN nofTasks;
  334. END GetNofTasks;
  335. END TaskList;
  336. TYPE
  337. Scheduler = OBJECT
  338. VAR
  339. sleepHint : LONGINT;
  340. alive, dead : BOOLEAN;
  341. timer : Kernel.Timer;
  342. PROCEDURE &Init;
  343. BEGIN
  344. sleepHint := 1000;
  345. alive := TRUE; dead := FALSE;
  346. NEW(timer);
  347. END Init;
  348. PROCEDURE Stop;
  349. BEGIN
  350. alive := FALSE; timer.Wakeup;
  351. BEGIN {EXCLUSIVE} AWAIT(dead); END;
  352. END Stop;
  353. PROCEDURE Update;
  354. BEGIN
  355. sleepHint := 500;
  356. timer.Wakeup;
  357. END Update;
  358. PROCEDURE CheckTask(time : Dates.DateTime; task : Task);
  359. VAR hint : LONGINT;
  360. BEGIN
  361. task.Check(time, hint);
  362. IF (hint > 0) & (hint < sleepHint) THEN sleepHint := hint; END;
  363. END CheckTask;
  364. BEGIN {ACTIVE}
  365. WHILE alive DO
  366. sleepHint := MAX(LONGINT);
  367. taskList.lock.AcquireRead;
  368. taskList.Enumerate(Dates.Now(), CheckTask);
  369. taskList.lock.ReleaseRead;
  370. IF alive THEN timer.Sleep(sleepHint); END;
  371. END;
  372. BEGIN {EXCLUSIVE} dead := TRUE; END;
  373. END Scheduler;
  374. VAR
  375. taskList : TaskList;
  376. scheduler : Scheduler;
  377. id : LONGINT;
  378. StrNoName-, StrNoDescription-, StrNoCommand-, StrNoImage-: Strings.String;
  379. PROCEDURE TypeToStream(out : Streams.Writer; repeatType : LONGINT);
  380. BEGIN
  381. ASSERT(out # NIL);
  382. CASE repeatType OF
  383. |Unknown: out.String("Unknown");
  384. |Once: out.String("Once");
  385. |EverySecond: out.String("EverySecond");
  386. |EveryMinute: out.String("EveryMinute");
  387. |Hourly: out.String("Hourly");
  388. |Daily: out.String("Daily");
  389. |Weekly: out.String("Weekly");
  390. |Monthly: out.String("Monthly");
  391. |Yearly: out.String("Yearly");
  392. ELSE
  393. out.String("Unknown");
  394. END;
  395. END TypeToStream;
  396. PROCEDURE TypeFromStream(in : Streams.Reader) : LONGINT;
  397. VAR repeatType : LONGINT; string : ARRAY 32 OF CHAR;
  398. BEGIN
  399. ASSERT(in # NIL);
  400. repeatType := Unknown;
  401. in.SkipWhitespace; in.String(string);
  402. IF (string = "Once") THEN repeatType := Once;
  403. ELSIF (string = "EverySecond") THEN repeatType := EverySecond;
  404. ELSIF (string = "EveryMinute") THEN repeatType := EveryMinute;
  405. ELSIF (string = "Hourly") THEN repeatType := Hourly;
  406. ELSIF (string = "Daily") THEN repeatType := Daily;
  407. ELSIF (string = "Weekly") THEN repeatType := Weekly;
  408. ELSIF (string = "Monthly") THEN repeatType := Monthly;
  409. ELSIF (string = "Yearly") THEN repeatType := Yearly;
  410. END;
  411. RETURN repeatType;
  412. END TypeFromStream;
  413. PROCEDURE GetId() : LONGINT;
  414. BEGIN {EXCLUSIVE}
  415. INC(id);
  416. RETURN id;
  417. END GetId;
  418. PROCEDURE GetTaskList*() : TaskList;
  419. VAR taskList : TaskList;
  420. BEGIN
  421. NEW(taskList);
  422. RETURN taskList;
  423. END GetTaskList;
  424. (** Helper functions *)
  425. PROCEDURE GetRepeatTypeString*(repeatType : LONGINT; VAR string : ARRAY OF CHAR);
  426. BEGIN
  427. CASE repeatType OF
  428. |Unknown: string := "Unknown";
  429. |Once: string := "Once";
  430. |EverySecond: string := "Each Second";
  431. |EveryMinute: string := "Each Minute";
  432. |Hourly: string := "Hourly";
  433. |Daily: string := "Daily";
  434. |Weekly: string := "Weekly";
  435. |Monthly: string := "Monthly";
  436. |Yearly: string := "Yearly";
  437. ELSE
  438. string := "Unknown";
  439. END;
  440. END GetRepeatTypeString;
  441. (** TaskArray helper functions *)
  442. (** Returns TRUE if both task arrays contain exactly the same tasks in the same order, FALSE otherwise *)
  443. PROCEDURE IsEqual*(tasks1, tasks2 : TaskArray) : BOOLEAN;
  444. VAR i : LONGINT;
  445. PROCEDURE SameElement(t1, t2 : Task) : BOOLEAN;
  446. BEGIN
  447. RETURN ((t1 = NIL) & (t2 = NIL)) OR ((t1 # NIL) & (t2 # NIL) & (t1.id = t2.id));
  448. END SameElement;
  449. BEGIN
  450. ASSERT((tasks1 # NIL) & (tasks2 # NIL));
  451. IF (LEN(tasks1) = LEN(tasks2)) THEN
  452. i := 0;
  453. WHILE (i < LEN(tasks1)) DO
  454. IF ~SameElement(tasks1[i], tasks2[i]) THEN RETURN FALSE; END;
  455. INC(i);
  456. END;
  457. RETURN TRUE;
  458. END;
  459. RETURN FALSE;
  460. END IsEqual;
  461. PROCEDURE Copy*(from : TaskArray; VAR to : TaskArray);
  462. VAR i : LONGINT;
  463. BEGIN
  464. ASSERT(from # NIL);
  465. IF (to = NIL) OR (LEN(to) < LEN(from)) THEN NEW(to, LEN(from)); END;
  466. FOR i := 0 TO LEN(from)-1 DO to[i] := from[i]; END;
  467. END Copy;
  468. PROCEDURE Clear*(tasks : TaskArray);
  469. VAR i : LONGINT;
  470. BEGIN
  471. ASSERT(tasks # NIL);
  472. FOR i := 0 TO LEN(tasks)-1 DO tasks[i] := NIL; END;
  473. END Clear;
  474. PROCEDURE InitStrings;
  475. BEGIN
  476. StrNoName := Strings.NewString("NoName");
  477. StrNoDescription := Strings.NewString("NoDescription");
  478. StrNoCommand := Strings.NewString("NoCommand");
  479. StrNoImage := Strings.NewString("NoImage");
  480. END InitStrings;
  481. PROCEDURE Cleanup;
  482. BEGIN
  483. scheduler.Stop;
  484. END Cleanup;
  485. BEGIN
  486. InitStrings;
  487. NEW(taskList);
  488. NEW(scheduler);
  489. Modules.InstallTermHandler(Cleanup);
  490. END TaskScheduler.
  491. System.Free WMTaskScheduler TaskScheduler ~