github.com/angenalZZZ/gofunc@v0.0.0-20210507121333-48ff1be3917b/js/job.go (about)

     1  package js
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"strings"
     7  	"sync/atomic"
     8  	"time"
     9  
    10  	"github.com/angenalZZZ/gofunc/f"
    11  	"github.com/dop251/goja"
    12  )
    13  
    14  // JobJs use cron job worker in javascript.
    15  type JobJs struct {
    16  	/*
    17  		CRON Expression Format
    18  		----------     ----------   --------------    --------------------------
    19  		e.g. "@daily", "@hourly", "@every 1h30m",
    20  			"30 * * * *"                     every hour on the half hour
    21  			"30 3-6,20-23 * * *"             in the range 3-6am, 8-11pm
    22  			"CRON_TZ=Asia/Tokyo 30 04 * * *" at 04:30 Tokyo time every day
    23  		----------     ----------   --------------    --------------------------
    24  		Field name   | Mandatory? | Allowed values  | Allowed special characters
    25  		----------   | ---------- | --------------  | --------------------------
    26  		Minutes      | Yes        | 0-59            | * / , -
    27  		Hours        | Yes        | 0-23            | * / , -
    28  		Day of month | Yes        | 1-31            | * / , - ?
    29  		Month        | Yes        | 1-12 or JAN-DEC | * / , -
    30  		Day of week  | Yes        | 0-6 or SUN-SAT  | * / , - ?
    31  		----------   | ---------- | --------------  | --------------------------
    32  		特定字符的含义如下:
    33  		* 表示匹配该域的任意值,假如在minute域使用*, 即表示每分钟都会触发事件
    34  		/ 表示起始时间开始触发,然后每隔固定时间触发一次,例如在minute域使用5/20,则意味着在5分的时候开始触发一次,而25,45等分别触发一次
    35  		, 表示列出枚举值。例如:在minute域使用5,20,则意味着在5和20分每分钟触发一次
    36  		- 表示范围,例如在minute域使用5-20,表示从5分到20分每分钟触发一次
    37  		? 字符仅被用于“日”和“周”两个子表达式,表示不指定值,当两个子表达式其中之一被指定了值以后,为了避免冲突,需要将另一个子表达式的值设为?
    38  	*/
    39  	Spec string
    40  	// the javascript content
    41  	Script string
    42  	// the javascript filepath
    43  	File string
    44  	// the javascript file modify time
    45  	FileModTime time.Time
    46  	// the last run time
    47  	LastRunTime time.Time
    48  	// the last run id
    49  	LastRunID uint64
    50  
    51  	// the job name
    52  	Name string
    53  	// the job parent list
    54  	Parent []*JobJs
    55  	// the job parent var'name
    56  	ParentName string
    57  
    58  	// R create a javascript runtime
    59  	R func(*GoRuntimeParam) *GoRuntime
    60  	// P create a javascript runtime input parameter
    61  	P *GoRuntimeParam
    62  
    63  	// Func func runner
    64  	Func func(goja.FunctionCall) goja.Value
    65  	// Self func this object
    66  	Self goja.Value
    67  }
    68  
    69  // RunLogTimeFormat the time layout.
    70  var RunLogTimeFormat = "15:04:05.000"
    71  
    72  // Run implementation cron.Job interface.
    73  func (j *JobJs) Run() {
    74  	if j.R == nil {
    75  		return
    76  	}
    77  
    78  	r := j.R(j.P)
    79  	if r == nil {
    80  		return
    81  	}
    82  	defer func() { r.Clear() }()
    83  
    84  	id := atomic.AddUint64(&j.LastRunID, 1)
    85  	j.LastRunTime = time.Now()
    86  	s := fmt.Sprintf("%s [job] %q #%d started running. ", j.LastRunTime.Format(RunLogTimeFormat), j.Name, id)
    87  
    88  	var (
    89  		res goja.Value
    90  		err error
    91  	)
    92  
    93  	if j.Func != nil {
    94  		var jobs []*JobJs
    95  		jobs, err = NewJobs(r, j.Script, j.ParentName, j.Name)
    96  		if err == nil {
    97  			if len(jobs) == 0 {
    98  				j.RemoveFromParent()
    99  				return
   100  			}
   101  			j1 := jobs[0] // a named job is found
   102  			fmt.Println(s)
   103  			res = j1.Func(goja.FunctionCall{This: j1.Self})
   104  		}
   105  	} else if j.Script != "" {
   106  		fmt.Println(s)
   107  		res, err = r.RunString(j.Script)
   108  	} else {
   109  		return
   110  	}
   111  
   112  	ts := time.Now()
   113  	fmt.Printf("%s [job] %q #%d takes %s ", ts.Format(RunLogTimeFormat), j.Name, id, ts.Sub(j.LastRunTime))
   114  	if err != nil {
   115  		fmt.Printf("error: %v", err)
   116  	} else if res != nil {
   117  		if v := res.Export(); v != nil {
   118  			fmt.Printf("return: %+v", v)
   119  		} else {
   120  			fmt.Print("ok.")
   121  		}
   122  	} else {
   123  		fmt.Print("ok.")
   124  	}
   125  	fmt.Println()
   126  	fmt.Println()
   127  
   128  	return
   129  }
   130  
   131  // Init the javascript job if file is not empty.
   132  func (j *JobJs) Init() error {
   133  	if j.File == "" {
   134  		return nil
   135  	}
   136  	if !j.FileIsMod() {
   137  		return os.ErrNotExist
   138  	}
   139  	if err := j.FileMod(); err != nil {
   140  		return err
   141  	}
   142  	return nil
   143  }
   144  
   145  // FileIsMod check javascript file is modify.
   146  func (j *JobJs) FileIsMod() bool {
   147  	if j.File == "" {
   148  		return false
   149  	}
   150  	info, err := os.Stat(j.File)
   151  	if os.IsNotExist(err) {
   152  		return false
   153  	}
   154  	if t := info.ModTime(); t.Unix() != j.FileModTime.Unix() {
   155  		j.FileModTime = t
   156  		return true
   157  	}
   158  	return false
   159  }
   160  
   161  // FileMod read updated javascript file content.
   162  func (j *JobJs) FileMod() error {
   163  	if j.File == "" {
   164  		return os.ErrNotExist
   165  	}
   166  	script, err := f.ReadFile(j.File)
   167  	if err != nil {
   168  		return err
   169  	}
   170  	j.Script = strings.TrimSpace(string(script))
   171  	return err
   172  }
   173  
   174  // RemoveFromParent remove self from parent.
   175  func (j *JobJs) RemoveFromParent() {
   176  	p := j.Parent
   177  	if p == nil {
   178  		return
   179  	}
   180  	for i, l := 0, len(p); i < l; i++ {
   181  		if j.Name != p[i].Name {
   182  			continue
   183  		}
   184  		// reset self object
   185  		j.Script, j.Func, j.R, j.Parent = "", nil, nil, nil
   186  		// update parent underlying array
   187  		p = append(p[:i], p[i+1:]...)
   188  		// maintain the correct index
   189  		return
   190  	}
   191  }