| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215 |
- package agi
- import (
- "fmt"
- "time"
- "github.com/robertkrimen/otto"
- "imuslab.com/arozos/mod/agi/static"
- "imuslab.com/arozos/mod/info/logger"
- )
- /*
- AGI Scheduler Library
- author: tobychui
- Exposes task scheduler functionality to AGI scripts via the "scheduler" library.
- Load it with: requirelib("scheduler");
- The cron script (cron.agi by default) must live inside the webapp's own folder,
- next to init.agi — NOT in user virtual storage:
- ./web/MyApp/
- init.agi
- cron.agi ← this is what gets scheduled
- index.html
- Usage pattern (typically placed in a backend AGI called on first launch):
- requirelib("scheduler");
- var APP = "MyApp"; // must match your module folder name
- var TASK = "MyApp_DailySync";
- if (!scheduler.hasPermission()) {
- // The frontend should call ao_module_requestSchedulerPermission()
- // to ask the user to grant this permission first.
- sendResp("no_permission");
- } else if (scheduler.registered(TASK, APP)) {
- sendResp("already_registered");
- } else {
- // scriptName defaults to "cron.agi" if omitted
- var ok = scheduler.register(TASK, APP, 86400, "Daily sync", "cron.agi");
- sendResp(ok ? "registered" : "error");
- }
- // To unregister (e.g. on user opt-out):
- scheduler.unregister(TASK);
- */
- // SchedulerCallbacks holds function pointers to the scheduler's core operations.
- // This avoids a circular import between the agi and scheduler packages.
- type SchedulerCallbacks struct {
- // RegisterJob registers a new job; returns error string or ""
- RegisterJob func(creator, appName, taskName, scriptVpath, fshID, description string, interval, baseTime int64) error
- // UnregisterJob removes a job by name for the given creator (or admin)
- UnregisterJob func(creator, taskName string) error
- // JobExists checks whether a job with the given appName+creator+taskName is registered
- AppJobExists func(appName, creator, taskName string) bool
- // CanCreate checks whether the given username has cron creation permission
- CanCreate func(username string) bool
- }
- // RegisterSchedulerLib registers the AGI "scheduler" library after the scheduler is running.
- // Must be called after the Scheduler is initialized, passing callback functions
- // so that the agi package does not import the scheduler package directly.
- func (g *Gateway) RegisterSchedulerLib(callbacks *SchedulerCallbacks) {
- err := g.RegisterLib("scheduler", func(payload *static.AgiLibInjectionPayload) {
- g.injectSchedulerLibFunctions(payload, callbacks)
- })
- if err != nil {
- // Library already registered – not fatal, just warn
- logger.PrintAndLog("Agi", fmt.Sprint("[AGI] scheduler lib already registered:", err), nil)
- }
- }
- func (g *Gateway) injectSchedulerLibFunctions(payload *static.AgiLibInjectionPayload, cb *SchedulerCallbacks) {
- vm := payload.VM
- u := payload.User
- /*
- scheduler.hasPermission() => bool
- Returns true when the current user is allowed to create cron jobs.
- */
- vm.Set("_scheduler_hasPermission", func(call otto.FunctionCall) otto.Value {
- if cb == nil || cb.CanCreate == nil {
- return otto.FalseValue()
- }
- canCreate := cb.CanCreate(u.Username)
- if canCreate {
- return otto.TrueValue()
- }
- return otto.FalseValue()
- })
- /*
- scheduler.registered(taskName) => bool
- Returns true when the task with the given name is already registered for this user+app.
- */
- vm.Set("_scheduler_registered", func(call otto.FunctionCall) otto.Value {
- taskName, err := call.Argument(0).ToString()
- if err != nil || taskName == "undefined" {
- g.RaiseError(err)
- return otto.FalseValue()
- }
- appName, err := call.Argument(1).ToString()
- if err != nil || appName == "undefined" {
- appName = ""
- }
- if cb == nil || cb.AppJobExists == nil {
- return otto.FalseValue()
- }
- exists := cb.AppJobExists(appName, u.Username, taskName)
- if exists {
- return otto.TrueValue()
- }
- return otto.FalseValue()
- })
- /*
- scheduler.register(taskName, appName, intervalSecs, description, scriptName) => bool
- Registers a cron job for the calling app's own cron script.
- - taskName: unique name for the job (max 32 chars)
- - appName: the webapp's module folder name (must match the folder in ./web/)
- - intervalSecs: execution interval in seconds
- - description: optional human-readable description
- - scriptName: script filename inside the app folder, default "cron.agi"
- Returns true on success, false on error.
- */
- vm.Set("_scheduler_register", func(call otto.FunctionCall) otto.Value {
- taskName, err := call.Argument(0).ToString()
- if err != nil || taskName == "undefined" {
- g.RaiseError(err)
- return otto.FalseValue()
- }
- appName, err := call.Argument(1).ToString()
- if err != nil || appName == "undefined" || appName == "" {
- g.RaiseError(err)
- return otto.FalseValue()
- }
- intervalSecs, err := call.Argument(2).ToInteger()
- if err != nil {
- g.RaiseError(err)
- return otto.FalseValue()
- }
- description, _ := call.Argument(3).ToString()
- if description == "undefined" {
- description = ""
- }
- scriptName, _ := call.Argument(4).ToString()
- if scriptName == "undefined" || scriptName == "" {
- scriptName = "cron.agi"
- }
- if cb == nil || cb.RegisterJob == nil {
- g.RaiseError(errExitcall)
- return otto.FalseValue()
- }
- baseTime := time.Now().Unix()
- // scriptName is app-relative ("cron.agi") — RegisterJobFromAGI detects this
- // because it has no ":" separator and appName is set.
- regErr := cb.RegisterJob(u.Username, appName, taskName, scriptName, "", description, intervalSecs, baseTime)
- if regErr != nil {
- g.RaiseError(regErr)
- return otto.FalseValue()
- }
- return otto.TrueValue()
- })
- /*
- scheduler.unregister(taskName, appName) => bool
- Removes a previously registered cron job.
- Returns true on success, false on error.
- */
- vm.Set("_scheduler_unregister", func(call otto.FunctionCall) otto.Value {
- taskName, err := call.Argument(0).ToString()
- if err != nil || taskName == "undefined" {
- g.RaiseError(err)
- return otto.FalseValue()
- }
- if cb == nil || cb.UnregisterJob == nil {
- g.RaiseError(errExitcall)
- return otto.FalseValue()
- }
- unregErr := cb.UnregisterJob(u.Username, taskName)
- if unregErr != nil {
- g.RaiseError(unregErr)
- return otto.FalseValue()
- }
- return otto.TrueValue()
- })
- // Expose as a "scheduler" object in the JS vm
- vm.Run(`
- var scheduler = {
- hasPermission: function() {
- return _scheduler_hasPermission();
- },
- registered: function(taskName, appName) {
- appName = appName || "";
- return _scheduler_registered(taskName, appName);
- },
- // register(taskName, appName, intervalSecs [, description [, scriptName]])
- // scriptName defaults to "cron.agi" inside the app's own folder.
- register: function(taskName, appName, intervalSecs, description, scriptName) {
- description = description || "";
- scriptName = scriptName || "cron.agi";
- return _scheduler_register(taskName, appName, intervalSecs, description, scriptName);
- },
- unregister: function(taskName) {
- return _scheduler_unregister(taskName);
- }
- };
- `)
- }
|