| 
		
	
	
	
		
	Posts: 795Threads: 136
 Joined: Feb 2009
 
	
	
		Hi Gintaras, Hi all
 i want to try my own schedule from QM.
 
 It would be minute, not second dependant.
 
 I was thinking about a function launched at QM start, examining a variable to see if something to be done next minute.
 
 something like:
 str heure
 str minutes
 DateTime x
 
 
 rep
 x.FromComputerTime
 _s=x.ToStr(10)
 h.getcurrenthour
 min.getcurrentminutes  ;;homebrew methods
 if(heure=h and min=minutes) Doaction ;;homebrew method
 wait 30
 
 Is it realistic in term of resource/memory usage/accuracy???? or should I forget this idea?
 
 NB: It's supposed to handle about 35 actions per day.
 
	
	
	
		
	Posts: 12,239Threads: 144
 Joined: Dec 2002
 
	
	
		Realistic, does not require much CPU or memory. But quite difficult to make reliable. For example, consider all cases such as QM is not running or PC time changed.
	 
	
	
	
		
	Posts: 795Threads: 136
 Joined: Feb 2009
 
	
	
		Here's the entire code 
Seems to handle both situations you mention. 
tested, seems to work flawlessly, some minor tweaks to go (recurrence is the thoughest). 
QM is invaluable and a money saver.
 outint+ MinPrec
 int Heures Minutes
 str HeuresS MinutesS h
 ARRAY(str) a
 str Schedluded=
 ;23 32 "C:\Windows\regedit.exe" ""
 ;22 33 "C:\Windows\write.exe" ""
 ;23 34 "C:\Windows\notepad.exe" ""
 out "lancé"
 rep
 ,RetourneHeureMinutes(&Heures &Minutes)
 ,HeuresS=Heures
 ,MinutesS=Minutes
 ,if(Minutes=MinPrec)
 ,,out "Minute déjà faite"
 ,,goto h
 ,foreach h Schedluded
 ,,tok(h a -1 "" 4)
 ,,if(a[0].beg(HeuresS)=0) continue
 ,,if(StrCompare(a[1] MinutesS)=0)
 ,,,run a[2] a[3]
 ,,MinPrec=Minutes
 ,,;h
 ,,wait 60
If you have corrections or comments to improve it, they are welcome!!.
	 
	
	
	
		
	Posts: 795Threads: 136
 Joined: Feb 2009
 
	
	
		No comments on that, so i consider it good and add new things to it.
 
	
	
	
		
	Posts: 12,239Threads: 144
 Joined: Dec 2002
 
	
	
		The code is distorted and not colored. Difficult to read or test.
	 
	
	
	
		
	Posts: 795Threads: 136
 Joined: Feb 2009
 
	
	
	
		
	Posts: 12,239Threads: 144
 Joined: Dec 2002
 
	
	
		My version. 
Little tested. 
Function scheduler lock schedulestr+ g_schedule=
 ;Macro1950,    2013.03.08 17:47, 5m, mac, "Macro1950", "command line"
 ;write,        2013.03.08 17:48, 0, run, "C:\Windows\write.exe"
 ;notepad,    2013.03.08 17:49, 1h, run, "C:\Windows\notepad.exe"
 int+ g_scheduleUpdate=1
 lock- schedule
 
 ;List of scheduled tasks in CSV format: name, date, action, path, arguments, flags
 ;;;name - task name. Must be unique in the list.
 ;;;date - start date. Any supported format. If in the past, anyway will run if interval is set.
 ;;;interval - repeat interval. Format like 1m, 1h, 1d, 1w, 1M. Use "" or 0 to run once.
 ;;;action - run (run program) or mac (start macro).
 ;;;path - program path or macro name or path.
 ;;;arguments (optional) - command line arguments for the program or macro.
 ;;;flags (optional): 1 disabled
 ;After updating schedule, run this function. If already running, it will catch the new schedule.
 ;After updating code, end thread and run again. Or move the loop body to other function, then will not need to end/run everytime, just compile.
 
 ;_____________________________________
 
 if(getopt(nthreads)>1) ret ;;if already running, let just update schedule
 
 type SCHEDULEDTASK
 ,str'name
 ,DateTime'tStart
 ,repeatEvery
 ,everyWhat ;;1 min, 2 hour, 3 day, 4 week, 5 month
 ,weekDays ;;flags: 1 mon, 2 tue, 4 wed, 8 thu, 16 fri, 32 sat, 64 sun
 ,action ;;1 run, 2 mac
 ,str'path str'args
 ,flags
 ARRAY(SCHEDULEDTASK) a
 SCHEDULEDTASK& t
 int i nc; lpstr k
 
 rep
 ,;first time or when schedule changed, parse schedule CSV and store in ARRAY(SCHEDULEDTASK) a
 ,if g_scheduleUpdate
 ,,lock schedule
 ,,g_scheduleUpdate=0
 ,,ICsv c._create; c.FromString(g_schedule)
 ,,lock- schedule
 ,,nc=c.ColumnCount
 ,,a.create(c.RowCount)
 ,,for i 0 a.len
 ,,,&t=a[i]
 ,,,t.name=c.Cell(i 0)
 ,,,t.tStart.FromStr(c.Cell(i 1)) ;;info: error if incorrect format
 ,,,k=c.Cell(i 2); t.repeatEvery=val(k 0 _i); if(t.repeatEvery) t.everyWhat=SelStr(0 k+_i "m" "h" "d" "w" "M"); if(!t.everyWhat) end F"invalid interval: {k}"
 ,,,k=c.Cell(i 3); t.action=SelStr(0 k "run" "mac"); if(!t.action) end F"invalid action: {k}"
 ,,,t.path=c.Cell(i 4)
 ,,,if(nc>5) t.args=c.Cell(i 5)
 ,,,if(nc>6) t.flags=val(c.Cell(i 6))
 ,,,;out _s.getstruct(t 1) ;;debug
 ,,out "Schedule updated" ;;debug
 ,
 ,;wait 1 s
 ,wait 1
 ,
 ,;get current time
 ,DateTime tNow.FromComputerTime
 ,int mNow mPrev; tNow.GetParts(0 0 0 0 mNow); if(mNow=mPrev) continue; else mPrev=mNow ;;check with precision of every 1 minute
 ,out tNow.ToStr(4) ;;debug
 ,int M d h m wd _M _d _h _m _wd
 ,tNow.GetParts(0 M d h m 0 0 0 wd)
 ,
 ,;for each scheduled task
 ,for i 0 a.len
 ,,&t=a[i]
 ,,;compare time
 ,,if(tNow<t.tStart or t.flags&1) continue
 ,,t.tStart.GetParts(0 _M _d _h _m 0 0 0 _wd)
 ,,int runNow=(_m=m and _h=h and _d=d and _M=M)
 ,,;reschedule if need
 ,,sel t.everyWhat
 ,,,case 0 t.flags|1
 ,,,case 1 rep() t.tStart.AddParts(0 0 t.repeatEvery); if(t.tStart>tNow) break ;;not the best way, but easy
 ,,,case 2 rep() t.tStart.AddParts(0 t.repeatEvery); if(t.tStart>tNow) break
 ,,,case 3 rep() t.tStart.AddParts(t.repeatEvery); if(t.tStart>tNow) break
 ,,,case 4 rep() t.tStart.AddParts(t.repeatEvery*7); if(t.tStart>tNow) break
 ,,,case 5 rep() t.tStart.AddMonths(t.repeatEvery); if(t.tStart>tNow) break
 ,,;is it late to run task now?
 ,,if !runNow
 ,,,sel t.action
 ,,,,case 1 out F"<>late: {t.name} at {t.tStart.ToStr}. <link ''{t.path} /{t.args}''>Run now</link>."
 ,,,,case 2 out F"<>late: {t.name} at {t.tStart.ToStr}. <macro ''{t.path} /{t.args}''>Run now</macro>."
 ,,,continue
 ,,;run task
 ,,sel t.action
 ,,,case 1 run t.path t.args; err out F"failed: run ''{t.path}'' ''{t.args}''"
 ,,,case 2 mac t.path t.args; err out F"failed: mac ''{t.path}'' ''{t.args}''"
 
	
	
	
		
	Posts: 795Threads: 136
 Joined: Feb 2009
 
	
	
		Great, lots of goods in there, will use as canvas and tailor to my needs.
 At first glance, one thing surprises me : mPrev is not a global variable, why?
 
	
	
	
		
	Posts: 12,239Threads: 144
 Joined: Dec 2002
 
	
	
		Can be global. Not important.The rescheduling code in my function is incorrect, may set date in the past.
 
	
	
	
		
	Posts: 12,239Threads: 144
 Joined: Dec 2002
 
	
	
		Corrected rescheduling code.
	 
	
	
	
		
	Posts: 795Threads: 136
 Joined: Feb 2009
 
	
	
		I did not tried the code yet.
 I thought mPrev should be global to keep value between calls, how can it keep last time between calls
 if not global?
 
	
	
	
		
	Posts: 12,239Threads: 144
 Joined: Dec 2002
 
	
	
		The function is not called multiple times, unless you end thread and start again.
	 
	
	
	
		
	Posts: 795Threads: 136
 Joined: Feb 2009
 
	
	
		Well i must dig into the code. 
 Q1: Is the code running endlessly (don't see wait or pause for the rep part).
 Q2: can I launch it from previous function I created associated to QM start trigger or give to it it's own trigger, as TapTap function in previous thread?
 Q3: what if I want to check interval by second or hour?
 
	
	
	
		
	Posts: 12,239Threads: 144
 Joined: Dec 2002
 
	
	
		1. Yes.2. I would use trigger 'QM events / file loaded' for this function.
 3. In GetParts mNow now receives minutes. It can receive seconds or hours. For seconds don't need that line.
 
	
	
	
		
	Posts: 795Threads: 136
 Joined: Feb 2009
 
	
	
		1. Ok, if then guess it's a continous loop, was afraid to try that because of the potentiel bootleneck itcould create in QM, and being too much ram/cpu consumer and interfere with other function. Good to know.
 
 2. I once used init2 to launch at QM start. bad deal?
 
 3. Can mNow be different for individual schedules, or the same for all? test some schedule on second basis, others minute, others hours?
 
	
	
	
		
	Posts: 12,239Threads: 144
 Joined: Dec 2002
 
	
	
		2. Good idea.
 3. Why need it? The code is fast.
 
	
	
	
		
	Posts: 795Threads: 136
 Joined: Feb 2009
 
	
	
		3. Yes, the question was stupid. Sorry.
	 
	
	
	
		
	Posts: 795Threads: 136
 Joined: Feb 2009
 
	
	
		Quote:Or move the loop body to other function, then will not need to end/run everytime, just compile. 
Interesting.
 
I suppose the "loop" is from rep line after "rep" to end of function
 case 2 mac t.path t.args; err out F"failed: mac ''{t.path}'' ''{t.args}''"
If I put the loop into something called RepeatLoop:  
Q1: Should it be macro or a function (i suppose a function) 
Q2: how to call it?
 
Rep 
,RepeatLoop 
or, mac "RepeatLoop" 
or something else?
 
Q3: How g_scheduleUpdate knows I add or remove an item in g_schedule?????
	 
	
	
	
		
	Posts: 12,239Threads: 144
 Joined: Dec 2002
 
	
	
		rep 
,RepeatLoop
 
Then you can edit function RepeatLoop while scheduler is running. Click Compile button to apply changes. On syntax error the thread ends, then you can edit the function and click Run. But always need to restart thread when you change SCHEDULEDTASK type, ie add new members. 
Some variables must exist between function calls. Yo can pass them to the function by reference, or use thread variables, like int- mPrev. Other ways exist. 
Example: 
Function scheduler lock schedulestr+ g_schedule=
 ;Macro1950,    2013.03.08 17:47, 5m, mac, "Macro1950", "command line"
 ;write,        2013.03.08 17:48, 0, run, "C:\Windows\write.exe"
 ;notepad,    2013.03.08 17:49, 1h, run, "C:\Windows\notepad.exe"
 int+ g_scheduleUpdate=1
 lock- schedule
 
 ;List of scheduled tasks in CSV format: name, date, action, path, arguments, flags
 ;;;name - task name. Must be unique in the list.
 ;;;date - start date. Any supported format. If in the past, anyway will run if interval is set.
 ;;;interval - repeat interval. Format like 1m, 1h, 1d, 1w, 1M. Use "" or 0 to run once.
 ;;;action - run (run program) or mac (start macro).
 ;;;path - program path or macro name or path.
 ;;;arguments (optional) - command line arguments for the program or macro.
 ;;;flags (optional): 1 disabled
 ;After updating schedule, run this function. If already running, it will catch the new schedule.
 ;After updating code, end thread and run again. Or move the loop body to other function, then will not need to end/run everytime, just compile.
 
 ;_____________________________________
 
 if(getopt(nthreads)>1) ret ;;if already running, let just update schedule
 
 rep
 ,scheduler_Loop
Function scheduler_Loop ;/schedulerfunction
 
 type SCHEDULEDTASK
 ,str'name
 ,DateTime'tStart
 ,repeatEvery
 ,everyWhat ;;1 min, 2 hour, 3 day, 4 week, 5 month
 ,weekDays ;;flags: 1 mon, 2 tue, 4 wed, 8 thu, 16 fri, 32 sat, 64 sun
 ,action ;;1 run, 2 mac
 ,str'path str'args
 ,flags
 ARRAY(SCHEDULEDTASK)- a
 SCHEDULEDTASK& t
 int i nc; lpstr k
 
 ;first time or when schedule changed, parse schedule CSV and store in ARRAY(SCHEDULEDTASK) a
 if g_scheduleUpdate
 ,lock schedule
 ,g_scheduleUpdate=0
 ,ICsv c._create; c.FromString(g_schedule)
 ,lock- schedule
 ,nc=c.ColumnCount
 ,a.create(c.RowCount)
 ,for i 0 a.len
 ,,&t=a[i]
 ,,t.name=c.Cell(i 0)
 ,,t.tStart.FromStr(c.Cell(i 1)) ;;info: error if incorrect format
 ,,k=c.Cell(i 2); t.repeatEvery=val(k 0 _i); if(t.repeatEvery) t.everyWhat=SelStr(0 k+_i "m" "h" "d" "w" "M"); if(!t.everyWhat) end F"invalid interval: {k}"
 ,,k=c.Cell(i 3); t.action=SelStr(0 k "run" "mac"); if(!t.action) end F"invalid action: {k}"
 ,,t.path=c.Cell(i 4)
 ,,if(nc>5) t.args=c.Cell(i 5)
 ,,if(nc>6) t.flags=val(c.Cell(i 6))
 ,,;out _s.getstruct(t 1) ;;debug
 ,out "Schedule updated" ;;debug
 
 ;wait 1 s
 wait 1
 
 ;get current time
 DateTime tNow.FromComputerTime
 int mNow; int-- mPrev; tNow.GetParts(0 0 0 0 mNow); if(mNow=mPrev) ret; else mPrev=mNow ;;check with precision of every 1 minute
 out tNow.ToStr(4) ;;debug
 int M d h m wd _M _d _h _m _wd
 tNow.GetParts(0 M d h m 0 0 0 wd)
 
 ;for each scheduled task
 for i 0 a.len
 ,&t=a[i]
 ,;compare time
 ,if(tNow<t.tStart or t.flags&1) continue
 ,t.tStart.GetParts(0 _M _d _h _m 0 0 0 _wd)
 ,int runNow=(_m=m and _h=h and _d=d and _M=M)
 ,;reschedule if need
 ,sel t.everyWhat
 ,,case 0 t.flags|1
 ,,case 1 rep() t.tStart.AddParts(0 0 t.repeatEvery); if(t.tStart>tNow) break ;;not the best way, but easy
 ,,case 2 rep() t.tStart.AddParts(0 t.repeatEvery); if(t.tStart>tNow) break
 ,,case 3 rep() t.tStart.AddParts(t.repeatEvery); if(t.tStart>tNow) break
 ,,case 4 rep() t.tStart.AddParts(t.repeatEvery*7); if(t.tStart>tNow) break
 ,,case 5 rep() t.tStart.AddMonths(t.repeatEvery); if(t.tStart>tNow) break
 ,;is it late to run task now?
 ,if !runNow
 ,,sel t.action
 ,,,case 1 out F"<>late: {t.name} at {t.tStart.ToStr}. <link ''{t.path} /{t.args}''>Run now</link>."
 ,,,case 2 out F"<>late: {t.name} at {t.tStart.ToStr}. <macro ''{t.path} /{t.args}''>Run now</macro>."
 ,,continue
 ,;run task
 ,sel t.action
 ,,case 1 run t.path t.args; err out F"failed: run ''{t.path}'' ''{t.args}''"
 ,,case 2 mac t.path t.args; err out F"failed: mac ''{t.path}'' ''{t.args}''"
When you run scheduler, it updates CSV string and sets g_scheduleUpdate=1. The loop runs every 1 s. If g_scheduleUpdate is 1 it sets it to 0 and creates new array.
	 
	
	
	
		
	Posts: 795Threads: 136
 Joined: Feb 2009
 
	
	
		Ok make sense now, I now understand that in fact compiling make scheduler restart so g_scheduleUpdate is reset to 1so next call of loop will detect the change and then reload the CSV thing. Copy that.
 
 I suppose i don't need to  declare int+ g_scheduleUpdate again in loop function because it's the same thread?
 
	
	
	
		
	Posts: 12,239Threads: 144
 Joined: Dec 2002
 
	
	
		Compiling does not make scheduler thread restart.When you run scheduler while it is already running, runs second thread of it, simultaneously. It sets scheduleUpdate=1. It detects previous thread and exits.
 
 Don't need to declare global variables everywhere, but not error.
 
	
	
	
		
	Posts: 795Threads: 136
 Joined: Feb 2009
 
	
	
		OK, that's fine for the moment.
	 |