Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Option Pattern #3

Merged
merged 3 commits into from
Apr 12, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 45 additions & 65 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ func main() {
interceptor.DefaultWorkerPool(), // Limit concurrent running job.
)

cronx.New(middleware)
cronx.New(cronx.WithInterceptor(middleware))
}
```

Expand All @@ -105,26 +105,6 @@ func Sleep() cronx.Interceptor {

For more example check [here](interceptor).

## Custom Configuration

```go
package main

func main() {
// Create a cron with custom config.
cronx.NewManager(cronx.Config{
Location: func() *time.Location { // Change timezone to Jakarta.
jakarta, err := time.LoadLocation("Asia/Jakarta")
if err != nil {
secondsEastOfUTC := int((7 * time.Hour).Seconds())
jakarta = time.FixedZone("WIB", secondsEastOfUTC)
}
return jakarta
}(),
})
}
```

## FAQ

### What are the available commands?
Expand Down Expand Up @@ -157,26 +137,28 @@ Go to [here](interceptor) to see the available interceptors.

### Can I use my own router without starting the built-in router?

Yes, you can. This library is very modular.
Yes, you can. This library is very modular.

Here's an example of using [gin](https://github.com/gin-gonic/gin).

```go
package main

// Since we want to create custom HTTP server.
// Do not forget to shutdown the cron gracefully manually here.
manager := cronx.NewManager(cronx.Config{})
defer manager.Stop()

// An example using gin as the router.
r := gin.Default()
r.GET("/custom-path", func (c *gin.Context) {
c.JSON(http.StatusOK, manager.GetStatusJSON())
})
func main() {
// Since we want to create custom HTTP server.
// Do not forget to shutdown the cron gracefully manually here.
manager := cronx.NewManager()
defer manager.Stop()

// An example using gin as the router.
r := gin.Default()
r.GET("/custom-path", func(c *gin.Context) {
c.JSON(http.StatusOK, manager.GetStatusJSON())
})

// Start your own server.
r.Run()
// Start your own server.
r.Run()
}
```

### Can I still get the built-in template if I use my own router?
Expand All @@ -186,21 +168,23 @@ Yes, you can.
```go
package main

// Since we want to create custom HTTP server.
// Do not forget to shutdown the cron gracefully manually here.
manager := cronx.NewManager(cronx.Config{})
defer manager.Stop()

// GetStatusTemplate will return the built-in status page template.
index, _ := page.GetStatusTemplate()

// An example using echo as the router.
e := echo.New()
index, _ := page.GetStatusTemplate()
e.GET("jobs", func (context echo.Context) error {
// Serve the template to the writer and pass the current status data.
return index.Execute(context.Response().Writer, manager.GetStatusData())
})
func main() {
// Since we want to create custom HTTP server.
// Do not forget to shutdown the cron gracefully manually here.
manager := cronx.NewManager()
defer manager.Stop()

// GetStatusTemplate will return the built-in status page template.
index, _ := page.GetStatusTemplate()

// An example using echo as the router.
e := echo.New()
index, _ := page.GetStatusTemplate()
e.GET("/jobs", func(context echo.Context) error {
// Serve the template to the writer and pass the current status data.
return index.Execute(context.Response().Writer, manager.GetStatusData())
})
}
```

### Server is located in the US, but my user is in Jakarta, can I change the cron timezone?
Expand All @@ -211,17 +195,17 @@ Yes, you can. By default, the cron timezone will follow the server location time
package main

func main() {
loc := func() *time.Location { // Change timezone to Jakarta.
jakarta, err := time.LoadLocation("Asia/Jakarta")
if err != nil {
secondsEastOfUTC := int((7 * time.Hour).Seconds())
jakarta = time.FixedZone("WIB", secondsEastOfUTC)
}
return jakarta
}()

// Create a custom config.
cronx.NewManager(cronx.Config{
Location: func() *time.Location { // Change timezone to Jakarta.
jakarta, err := time.LoadLocation("Asia/Jakarta")
if err != nil {
secondsEastOfUTC := int((7 * time.Hour).Seconds())
jakarta = time.FixedZone("WIB", secondsEastOfUTC)
}
return jakarta
}(),
})
cronx.NewManager(cronx.WithLocation(loc))
}
```

Expand All @@ -237,13 +221,9 @@ type subscription struct{}
func (subscription) Run(ctx context.Context) error {
md, ok := cronx.GetJobMetadata(ctx)
if !ok {
return errors.New("cannot get job metadata")
return errors.New("cannot job metadata")
}

log.WithLevel(zerolog.InfoLevel).
Str("job", "subscription").
Interface("metadata", md).
Msg("is running")
logx.INF(ctx, logx.KV{"job": fn.Name(), "metadata": md}, "subscription is running")
return nil
}
```
110 changes: 52 additions & 58 deletions cronx.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,58 +8,56 @@ import (
"github.com/robfig/cron/v3"
)

var defaultConfig = Config{Location: time.Local}

// Config defines the config for the manager.
type Config struct {
// Location describes the timezone current cron is running.
Location *time.Location
}

// NewManager create a command controller with a specific config.
func NewManager(config Config, interceptors ...Interceptor) *Manager {
if config.Location == nil {
config.Location = defaultConfig.Location
}

// Default configuration for the manager.
var (
// Support the v1 where the first parameter is second.
parser := cron.NewParser(
DefaultParser = cron.NewParser(
cron.SecondOptional | cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow | cron.Descriptor,
)
DefaultInterceptors = Chain()
DefaultLocation = time.Local
)

// NewManager create a command controller with a specific config.
func NewManager(opts ...Option) *Manager {
manager := &Manager{
commander: nil,
interceptor: DefaultInterceptors,
parser: DefaultParser,
location: DefaultLocation,
createdTime: time.Now().In(DefaultLocation),
unregisteredJobs: nil,
}
for _, opt := range opts {
opt(manager)
}

// Create the commander.
commander := cron.New(
cron.WithParser(parser),
cron.WithLocation(config.Location),
cron.WithParser(manager.parser),
cron.WithLocation(manager.location),
)
commander.Start()

// Create command controller.
return &Manager{
Commander: commander,
Interceptor: Chain(interceptors...),
Parser: parser,
UnregisteredJobs: nil,
Location: config.Location,
CreatedTime: time.Now().In(config.Location),
}
manager.commander = commander
manager.createdTime = time.Now().In(manager.location)
return manager
}

// Manager controls all the underlying job.
type Manager struct {
// Commander holds all the underlying cron jobs.
Commander *cron.Cron
// Interceptor holds middleware that will be executed before current job operation.
Interceptor Interceptor
// Parser is a custom parser to support v1 that contains second as first parameter.
Parser cron.Parser
// UnregisteredJobs describes the list of jobs that have been failed to be registered.
UnregisteredJobs []*Job
// Location describes the timezone current cron is running.
// commander holds all the underlying cron jobs.
commander *cron.Cron
// interceptor holds middleware that will be executed before current job operation.
interceptor Interceptor
// parser is a custom parser to support v1 that contains second as first parameter.
parser cron.Parser
// location describes the timezone current cron is running.
// By default the timezone will be the same timezone as the server.
Location *time.Location
// CreatedTime describes when the command controller created.
CreatedTime time.Time
location *time.Location
// createdTime describes when the command controller created.
createdTime time.Time
// unregisteredJobs describes the list of jobs that have been failed to be registered.
unregisteredJobs []*Job
}

// Schedule sets a job to run at specific time.
Expand Down Expand Up @@ -96,76 +94,72 @@ func (m *Manager) Schedules(spec, separator string, job JobItf) error {

func (m *Manager) schedule(spec string, job JobItf, waveNumber, totalWave int64) error {
// Check if spec is correct.
schedule, err := m.Parser.Parse(spec)
schedule, err := m.parser.Parse(spec)
if err != nil {
downJob := NewJob(m, job, waveNumber, totalWave)
downJob.Status = StatusCodeDown
downJob.Error = err.Error()
m.UnregisteredJobs = append(m.UnregisteredJobs, downJob)
m.unregisteredJobs = append(m.unregisteredJobs, downJob)
return err
}

j := NewJob(m, job, waveNumber, totalWave)
j.EntryID = m.Commander.Schedule(schedule, j)
j.EntryID = m.commander.Schedule(schedule, j)
return nil
}

// Start starts jobs from running at the next scheduled time.
func (m *Manager) Start() {
m.Commander.Start()
m.commander.Start()
}

// Stop stops active jobs from running at the next scheduled time.
func (m *Manager) Stop() {
m.Commander.Stop()
m.commander.Stop()
}

// GetEntries returns all the current registered jobs.
func (m *Manager) GetEntries() []cron.Entry {
return m.Commander.Entries()
return m.commander.Entries()
}

// GetEntry returns a snapshot of the given entry, or nil if it couldn't be found.
func (m *Manager) GetEntry(id cron.EntryID) *cron.Entry {
entry := m.Commander.Entry(id)
entry := m.commander.Entry(id)
return &entry
}

// Remove removes a specific job from running.
// Get EntryID from the list job entries manager.GetEntries().
// If job is in the middle of running, once the process is finished it will be removed.
func (m *Manager) Remove(id cron.EntryID) {
m.Commander.Remove(id)
m.commander.Remove(id)
}

// GetInfo returns command controller basic information.
func (m *Manager) GetInfo() map[string]interface{} {
if m.Location == nil {
m.Location = defaultConfig.Location
}

currentTime := time.Now().In(m.Location)
currentTime := time.Now().In(m.location)

return map[string]interface{}{
"data": map[string]interface{}{
"location": m.Location.String(),
"created_time": m.CreatedTime.String(),
"location": m.location.String(),
"created_time": m.createdTime.String(),
"current_time": currentTime.String(),
"up_time": currentTime.Sub(m.CreatedTime).String(),
"up_time": currentTime.Sub(m.createdTime).String(),
},
}
}

// GetStatusData returns all jobs status.
func (m *Manager) GetStatusData() []StatusData {
if m.Commander == nil {
if m.commander == nil {
return nil
}

entries := m.Commander.Entries()
entries := m.commander.Entries()
totalEntries := len(entries)

downs := m.UnregisteredJobs
downs := m.unregisteredJobs
totalDowns := len(downs)

totalJobs := totalEntries + totalDowns
Expand Down
Loading