aboutsummaryrefslogtreecommitdiff
path: root/modules
diff options
context:
space:
mode:
Diffstat (limited to 'modules')
-rw-r--r--modules/auth/org.go57
-rw-r--r--modules/auth/repo.go33
-rw-r--r--modules/auth/user.go58
-rw-r--r--modules/base/base.go1
-rw-r--r--modules/bin/conf.go509
-rw-r--r--modules/cron/constantdelay.go27
-rw-r--r--modules/cron/constantdelay_test.go54
-rw-r--r--modules/cron/cron.go210
-rw-r--r--modules/cron/cron_test.go255
-rw-r--r--modules/cron/doc.go129
-rw-r--r--modules/cron/manager.go24
-rw-r--r--modules/cron/parser.go231
-rw-r--r--modules/cron/parser_test.go117
-rw-r--r--modules/cron/spec.go161
-rw-r--r--modules/cron/spec_test.go173
-rw-r--r--modules/hooks/hooks.go95
-rw-r--r--modules/log/log.go15
-rw-r--r--modules/mailer/mail.go79
-rw-r--r--modules/middleware/context.go10
-rw-r--r--modules/middleware/repo.go13
-rw-r--r--modules/process/manager.go89
-rw-r--r--modules/setting/setting.go67
-rw-r--r--modules/social/social.go10
23 files changed, 1964 insertions, 453 deletions
diff --git a/modules/auth/org.go b/modules/auth/org.go
new file mode 100644
index 00000000..f87d10a7
--- /dev/null
+++ b/modules/auth/org.go
@@ -0,0 +1,57 @@
+// Copyright 2014 The Gogs Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package auth
+
+import (
+ "net/http"
+ "reflect"
+
+ "github.com/go-martini/martini"
+
+ "github.com/gogits/gogs/modules/base"
+ "github.com/gogits/gogs/modules/middleware/binding"
+)
+
+type CreateOrgForm struct {
+ OrgName string `form:"orgname" binding:"Required;AlphaDashDot;MaxSize(30)"`
+ Email string `form:"email" binding:"Required;Email;MaxSize(50)"`
+}
+
+func (f *CreateOrgForm) Name(field string) string {
+ names := map[string]string{
+ "OrgName": "Organization name",
+ "Email": "E-mail address",
+ }
+ return names[field]
+}
+
+func (f *CreateOrgForm) Validate(errs *binding.Errors, req *http.Request, ctx martini.Context) {
+ data := ctx.Get(reflect.TypeOf(base.TmplData{})).Interface().(base.TmplData)
+ validate(errs, data, f)
+}
+
+type OrgSettingForm struct {
+ DisplayName string `form:"display_name" binding:"Required;MaxSize(100)"`
+ Email string `form:"email" binding:"Required;Email;MaxSize(50)"`
+ Description string `form:"desc" binding:"MaxSize(255)"`
+ Website string `form:"site" binding:"Url;MaxSize(100)"`
+ Location string `form:"location" binding:"MaxSize(50)"`
+}
+
+func (f *OrgSettingForm) Name(field string) string {
+ names := map[string]string{
+ "DisplayName": "Display name",
+ "Email": "E-mail address",
+ "Description": "Description",
+ "Website": "Website address",
+ "Location": "Location",
+ }
+ return names[field]
+}
+
+func (f *OrgSettingForm) Validate(errors *binding.Errors, req *http.Request, context martini.Context) {
+ data := context.Get(reflect.TypeOf(base.TmplData{})).Interface().(base.TmplData)
+ validate(errors, data, f)
+}
diff --git a/modules/auth/repo.go b/modules/auth/repo.go
index 82cd0786..d3d21532 100644
--- a/modules/auth/repo.go
+++ b/modules/auth/repo.go
@@ -22,9 +22,10 @@ import (
// \/ \/|__| \/ \/
type CreateRepoForm struct {
+ Uid int64 `form:"uid" binding:"Required"`
RepoName string `form:"repo" binding:"Required;AlphaDash;MaxSize(100)"`
Private bool `form:"private"`
- Description string `form:"desc" binding:"MaxSize(100)"`
+ Description string `form:"desc" binding:"MaxSize(255)"`
Language string `form:"language"`
License string `form:"license"`
InitReadme bool `form:"initReadme"`
@@ -47,10 +48,11 @@ type MigrateRepoForm struct {
Url string `form:"url" binding:"Url"`
AuthUserName string `form:"auth_username"`
AuthPasswd string `form:"auth_password"`
+ Uid int64 `form:"uid" binding:"Required"`
RepoName string `form:"repo" binding:"Required;AlphaDash;MaxSize(100)"`
Mirror bool `form:"mirror"`
Private bool `form:"private"`
- Description string `form:"desc" binding:"MaxSize(100)"`
+ Description string `form:"desc" binding:"MaxSize(255)"`
}
func (f *MigrateRepoForm) Name(field string) string {
@@ -69,7 +71,7 @@ func (f *MigrateRepoForm) Validate(errors *binding.Errors, req *http.Request, co
type RepoSettingForm struct {
RepoName string `form:"name" binding:"Required;AlphaDash;MaxSize(100)"`
- Description string `form:"desc" binding:"MaxSize(100)"`
+ Description string `form:"desc" binding:"MaxSize(255)"`
Website string `form:"site" binding:"Url;MaxSize(100)"`
Branch string `form:"branch"`
Interval int `form:"interval"`
@@ -205,14 +207,17 @@ func (f *CreateLabelForm) Validate(errors *binding.Errors, req *http.Request, co
type NewReleaseForm struct {
TagName string `form:"tag_name" binding:"Required"`
+ Target string `form:"tag_target" binding:"Required"`
Title string `form:"title" binding:"Required"`
Content string `form:"content" binding:"Required"`
+ Draft string `form:"draft"`
Prerelease bool `form:"prerelease"`
}
func (f *NewReleaseForm) Name(field string) string {
names := map[string]string{
"TagName": "Tag name",
+ "Target": "Target",
"Title": "Release title",
"Content": "Release content",
}
@@ -223,3 +228,25 @@ func (f *NewReleaseForm) Validate(errors *binding.Errors, req *http.Request, con
data := context.Get(reflect.TypeOf(base.TmplData{})).Interface().(base.TmplData)
validate(errors, data, f)
}
+
+type EditReleaseForm struct {
+ Target string `form:"tag_target" binding:"Required"`
+ Title string `form:"title" binding:"Required"`
+ Content string `form:"content" binding:"Required"`
+ Draft string `form:"draft"`
+ Prerelease bool `form:"prerelease"`
+}
+
+func (f *EditReleaseForm) Name(field string) string {
+ names := map[string]string{
+ "Target": "Target",
+ "Title": "Release title",
+ "Content": "Release content",
+ }
+ return names[field]
+}
+
+func (f *EditReleaseForm) Validate(errors *binding.Errors, req *http.Request, context martini.Context) {
+ data := context.Get(reflect.TypeOf(base.TmplData{})).Interface().(base.TmplData)
+ validate(errors, data, f)
+}
diff --git a/modules/auth/user.go b/modules/auth/user.go
index e672a9c1..4a781acf 100644
--- a/modules/auth/user.go
+++ b/modules/auth/user.go
@@ -16,57 +16,63 @@ import (
"github.com/gogits/gogs/modules/base"
"github.com/gogits/gogs/modules/log"
"github.com/gogits/gogs/modules/middleware/binding"
+ "github.com/gogits/gogs/modules/setting"
)
// SignedInId returns the id of signed in user.
-func SignedInId(session session.SessionStore) int64 {
+func SignedInId(header http.Header, sess session.SessionStore) int64 {
if !models.HasEngine {
return 0
}
- userId := session.Get("userId")
- if userId == nil {
+ if setting.Service.EnableReverseProxyAuth {
+ webAuthUser := header.Get(setting.ReverseProxyAuthUser)
+ if len(webAuthUser) > 0 {
+ u, err := models.GetUserByName(webAuthUser)
+ if err != nil {
+ if err != models.ErrUserNotExist {
+ log.Error("auth.user.SignedInId(GetUserByName): %v", err)
+ }
+ return 0
+ }
+ return u.Id
+ }
+ }
+
+ uid := sess.Get("userId")
+ if uid == nil {
return 0
}
- if s, ok := userId.(int64); ok {
- if _, err := models.GetUserById(s); err != nil {
+ if id, ok := uid.(int64); ok {
+ if _, err := models.GetUserById(id); err != nil {
+ if err != models.ErrUserNotExist {
+ log.Error("auth.user.SignedInId(GetUserById): %v", err)
+ }
return 0
}
- return s
+ return id
}
return 0
}
-// SignedInName returns the name of signed in user.
-func SignedInName(session session.SessionStore) string {
- userName := session.Get("userName")
- if userName == nil {
- return ""
- }
- if s, ok := userName.(string); ok {
- return s
- }
- return ""
-}
-
// SignedInUser returns the user object of signed user.
-func SignedInUser(session session.SessionStore) *models.User {
- id := SignedInId(session)
- if id <= 0 {
+func SignedInUser(header http.Header, sess session.SessionStore) *models.User {
+ uid := SignedInId(header, sess)
+ if uid <= 0 {
return nil
}
- user, err := models.GetUserById(id)
+ u, err := models.GetUserById(uid)
if err != nil {
log.Error("user.SignedInUser: %v", err)
return nil
}
- return user
+ return u
}
// IsSignedIn check if any user has signed in.
-func IsSignedIn(session session.SessionStore) bool {
- return SignedInId(session) > 0
+func IsSignedIn(header http.Header, sess session.SessionStore) bool {
+ return SignedInId(header, sess) > 0
}
type FeedsForm struct {
@@ -87,7 +93,7 @@ func (f *UpdateProfileForm) Name(field string) string {
names := map[string]string{
"UserName": "Username",
"Email": "E-mail address",
- "Website": "Website",
+ "Website": "Website address",
"Location": "Location",
"Avatar": "Gravatar Email",
}
diff --git a/modules/base/base.go b/modules/base/base.go
index 145fae6f..570600c3 100644
--- a/modules/base/base.go
+++ b/modules/base/base.go
@@ -7,6 +7,7 @@ package base
type (
// Type TmplData represents data in the templates.
TmplData map[string]interface{}
+ TplName string
ApiJsonErr struct {
Message string `json:"message"`
diff --git a/modules/bin/conf.go b/modules/bin/conf.go
index f2b3fb87..fa0822d7 100644
--- a/modules/bin/conf.go
+++ b/modules/bin/conf.go
@@ -1,11 +1,11 @@
package bin
import (
- "bytes"
- "compress/gzip"
- "fmt"
- "io"
- "strings"
+ "bytes"
+ "compress/gzip"
+ "fmt"
+ "io"
+ "strings"
)
func bindata_read(data []byte, name string) ([]byte, error) {
@@ -27,227 +27,243 @@ func bindata_read(data []byte, name string) ([]byte, error) {
func conf_app_ini() ([]byte, error) {
return bindata_read([]byte{
- 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x00, 0xff, 0xb4, 0x58,
- 0xed, 0x72, 0xdb, 0xb8, 0xd5, 0xfe, 0xcf, 0xab, 0x40, 0xf4, 0xee, 0xbe,
- 0x9b, 0x74, 0x6c, 0x49, 0x76, 0x1a, 0x27, 0x6b, 0x6f, 0x66, 0x56, 0x96,
- 0x28, 0x9b, 0x8d, 0xf5, 0x11, 0x92, 0x76, 0x9a, 0x66, 0x3c, 0x1c, 0x9a,
- 0x84, 0x24, 0xd4, 0x24, 0x41, 0x13, 0x90, 0x15, 0xf5, 0x5f, 0x6f, 0xa1,
- 0xd3, 0xab, 0xe9, 0xf5, 0xf4, 0x47, 0x2f, 0xa3, 0xcf, 0x01, 0x49, 0x99,
- 0x52, 0xb4, 0xd9, 0xf4, 0x6b, 0x76, 0x27, 0x16, 0x81, 0x83, 0x83, 0x73,
- 0x9e, 0xf3, 0x8d, 0x33, 0xd6, 0xcb, 0x73, 0x96, 0x85, 0x29, 0x67, 0x7a,
- 0x11, 0x6a, 0xa6, 0x16, 0x72, 0xa5, 0x98, 0xcc, 0x18, 0x7f, 0xe4, 0xc5,
- 0x9a, 0xe5, 0xe1, 0x1c, 0x1b, 0x42, 0x27, 0xdc, 0xea, 0x4d, 0xa7, 0xc1,
- 0xb8, 0x37, 0xb2, 0xd9, 0x5b, 0x76, 0x21, 0xe7, 0xea, 0x14, 0xff, 0xb2,
- 0x0b, 0xa1, 0x99, 0xc7, 0x8b, 0x47, 0x11, 0x95, 0xfb, 0x57, 0x93, 0x8b,
- 0x09, 0xf6, 0x45, 0x3a, 0xef, 0xcc, 0x42, 0xac, 0xca, 0xac, 0x9d, 0x67,
- 0x73, 0xeb, 0x8c, 0xf5, 0x17, 0x61, 0x06, 0x4e, 0x20, 0x17, 0x33, 0xb6,
- 0x96, 0x4b, 0x56, 0x2c, 0x33, 0x96, 0xc8, 0x28, 0x4c, 0x92, 0xb5, 0xe5,
- 0x5e, 0x8f, 0x83, 0x6b, 0xcf, 0x76, 0x71, 0x72, 0x2e, 0x34, 0xa8, 0x6d,
- 0xa1, 0x17, 0xbc, 0x60, 0xad, 0x98, 0x3f, 0xb6, 0x0e, 0x58, 0x2b, 0x2f,
- 0x64, 0xdc, 0x62, 0x12, 0x0b, 0x9a, 0x2b, 0x8d, 0x95, 0x98, 0xcf, 0xc2,
- 0x65, 0x02, 0x5e, 0xaa, 0xa4, 0x31, 0x1c, 0x46, 0x93, 0x01, 0xc9, 0x86,
- 0x6f, 0xcb, 0xfa, 0x54, 0xf0, 0x5c, 0x2a, 0xa1, 0x65, 0xb1, 0xbe, 0xb5,
- 0xdc, 0xc9, 0xc4, 0xc7, 0x86, 0xe5, 0xf5, 0x5d, 0x67, 0xea, 0x07, 0xfe,
- 0xc7, 0x29, 0xd1, 0xdd, 0x85, 0x6a, 0x01, 0x42, 0x05, 0xe9, 0x79, 0x71,
- 0x6b, 0x4d, 0xdd, 0x89, 0x3f, 0xe9, 0x4f, 0xae, 0xb0, 0xb3, 0xd0, 0x3a,
- 0xb7, 0x06, 0x93, 0x51, 0xcf, 0x19, 0xe3, 0xcb, 0x08, 0xb9, 0x90, 0x4a,
- 0x1b, 0x3e, 0xc1, 0xb5, 0x4b, 0x24, 0xdf, 0x3f, 0xaf, 0xe9, 0x5f, 0xa8,
- 0xd3, 0x4e, 0xe7, 0xfb, 0xe7, 0x25, 0x39, 0x3e, 0xbe, 0x7f, 0x7e, 0xe9,
- 0xfb, 0xd3, 0x60, 0x3a, 0x71, 0xfd, 0x17, 0xaa, 0x63, 0x99, 0x8f, 0xde,
- 0x60, 0x40, 0xba, 0x59, 0x9b, 0x1d, 0x7c, 0xbc, 0xec, 0x76, 0xbb, 0x96,
- 0xe7, 0x5d, 0xd6, 0xdf, 0xc7, 0xc7, 0xd0, 0x7b, 0x20, 0x54, 0x78, 0x97,
- 0x70, 0xd6, 0x1f, 0x8c, 0x09, 0xff, 0x8c, 0x89, 0xac, 0xd6, 0x3e, 0x95,
- 0x31, 0xb7, 0x26, 0xc3, 0xe1, 0x95, 0x33, 0xb6, 0x6b, 0x55, 0x67, 0x61,
- 0xa2, 0xb8, 0x35, 0x70, 0xbc, 0xde, 0xf9, 0x95, 0x1d, 0xb8, 0x93, 0x6b,
- 0xdf, 0x76, 0xc9, 0x04, 0x9b, 0xad, 0x33, 0x76, 0xc1, 0x33, 0x5e, 0x84,
- 0x9a, 0x33, 0xa5, 0x79, 0xae, 0x4e, 0xb1, 0xf2, 0x1d, 0x8b, 0x62, 0x98,
- 0x55, 0x2f, 0x3a, 0x5a, 0x76, 0xe6, 0x30, 0x64, 0x27, 0x5a, 0x2a, 0x2d,
- 0xd3, 0x0e, 0xa9, 0xad, 0x0c, 0xc1, 0x5c, 0x1a, 0xf3, 0x7c, 0x77, 0x31,
- 0x21, 0x95, 0x3b, 0xaa, 0x88, 0x3a, 0xf9, 0xfd, 0xbc, 0x13, 0x15, 0xeb,
- 0x1c, 0x67, 0x74, 0xa2, 0x3a, 0xf3, 0x8a, 0x6d, 0x10, 0xf1, 0x42, 0xb7,
- 0x41, 0x7f, 0x18, 0x85, 0x6f, 0x75, 0xb1, 0xe4, 0xec, 0x30, 0x5e, 0x62,
- 0x43, 0xc8, 0xec, 0xed, 0x9b, 0xd7, 0x27, 0xdd, 0x45, 0x37, 0xed, 0x2a,
- 0x76, 0x48, 0xf0, 0xbd, 0x4d, 0xd7, 0xf4, 0xa7, 0xcd, 0x3f, 0x87, 0x69,
- 0x9e, 0xf0, 0x76, 0x24, 0x53, 0xab, 0x6f, 0xbb, 0x7e, 0x30, 0x74, 0xae,
- 0x48, 0x99, 0xa6, 0x14, 0x1d, 0xc3, 0x36, 0xe7, 0xa9, 0xf5, 0xce, 0xfe,
- 0xb8, 0x97, 0xe0, 0x9e, 0xaf, 0xcd, 0xfe, 0x19, 0xbb, 0xce, 0x73, 0xb8,
- 0x4a, 0x02, 0xb8, 0x12, 0x26, 0x67, 0x4c, 0x73, 0x70, 0x27, 0x85, 0xc3,
- 0x2c, 0x86, 0xd2, 0x10, 0x25, 0x62, 0x33, 0x01, 0x4c, 0x49, 0x65, 0x90,
- 0x37, 0x5c, 0x07, 0x3e, 0x66, 0x56, 0xd9, 0x0a, 0xce, 0xc6, 0x8d, 0x53,
- 0xd3, 0x32, 0xff, 0xcc, 0xa3, 0xa5, 0xe6, 0xb1, 0xe5, 0xf9, 0x3d, 0xdf,
- 0xe9, 0x07, 0xc6, 0xec, 0xd3, 0x9e, 0x7f, 0x49, 0x26, 0xb4, 0x3e, 0xc5,
- 0xa1, 0x0e, 0xe1, 0x3b, 0xfc, 0xb6, 0xe1, 0xa7, 0xe9, 0x5a, 0x3d, 0x24,
- 0xc6, 0x53, 0xa1, 0xe1, 0xbc, 0xe0, 0xaa, 0xf4, 0x56, 0x2c, 0x0a, 0xcd,
- 0x5f, 0x62, 0x43, 0xe8, 0x1f, 0x14, 0xb9, 0x7d, 0xc1, 0xa2, 0x85, 0xa4,
- 0x60, 0x19, 0x9c, 0xd7, 0x7e, 0x68, 0xce, 0x5a, 0x97, 0x13, 0x8f, 0xbc,
- 0xe0, 0xe8, 0xf8, 0x75, 0xbb, 0x8b, 0xff, 0x8e, 0x4e, 0x5f, 0xbe, 0xec,
- 0x9e, 0x58, 0x55, 0xb8, 0x91, 0x95, 0xac, 0x2a, 0x40, 0x0a, 0x29, 0xb5,
- 0x35, 0xed, 0x79, 0xde, 0x87, 0x01, 0x7b, 0x0b, 0x11, 0x86, 0x74, 0x51,
- 0xe3, 0xda, 0x2c, 0x59, 0x1f, 0x30, 0x5e, 0xc7, 0x4f, 0xe9, 0x4f, 0x24,
- 0x59, 0xc1, 0x1f, 0x96, 0xa2, 0xe0, 0xa5, 0x60, 0xf0, 0x78, 0x31, 0x5b,
- 0x1f, 0xce, 0x96, 0x49, 0xd2, 0x82, 0x13, 0x5e, 0x6d, 0x62, 0xa7, 0xa4,
- 0xaf, 0xd9, 0xd6, 0xf2, 0x1b, 0xae, 0x56, 0x05, 0x01, 0xe9, 0x6f, 0xfc,
- 0xa6, 0x1d, 0xdf, 0x01, 0x8e, 0x30, 0x4e, 0x45, 0x76, 0x6b, 0x02, 0x29,
- 0x5a, 0x16, 0x42, 0x23, 0xde, 0x9c, 0x31, 0x90, 0xbb, 0xba, 0x82, 0x27,
- 0xf6, 0xdf, 0x35, 0x5c, 0xf1, 0xd9, 0xb3, 0xfe, 0x65, 0x6f, 0x7c, 0x61,
- 0x33, 0xff, 0xd2, 0xf1, 0x98, 0x3f, 0x61, 0xef, 0x6c, 0x7b, 0xca, 0x3e,
- 0x4e, 0xae, 0x5d, 0x66, 0x74, 0x1b, 0xf4, 0xfc, 0x1e, 0xf3, 0x7a, 0x43,
- 0xfb, 0xd9, 0x33, 0xcb, 0xb3, 0xfb, 0xae, 0xed, 0x07, 0xb0, 0x3e, 0x18,
- 0x3c, 0xfb, 0xbf, 0x9f, 0x87, 0x03, 0xfb, 0x83, 0x8b, 0xff, 0xff, 0xff,
- 0x37, 0xcf, 0xc1, 0xa9, 0xb7, 0xd4, 0xf2, 0x30, 0x91, 0x73, 0x44, 0x47,
- 0xc1, 0x53, 0x9e, 0xde, 0x41, 0xd7, 0x38, 0x5c, 0x2b, 0x0b, 0xbe, 0xef,
- 0x8c, 0x03, 0xd7, 0x1e, 0xd9, 0xa3, 0x73, 0x84, 0xc2, 0xa0, 0xf7, 0xd1,
- 0xc3, 0xf9, 0xd7, 0x56, 0x7f, 0x32, 0x79, 0xe7, 0xd8, 0x26, 0xc7, 0x34,
- 0x20, 0x0d, 0xc2, 0x15, 0x57, 0x32, 0xe5, 0xf5, 0xf6, 0xe6, 0x5c, 0x93,
- 0x46, 0x64, 0x51, 0xc1, 0x63, 0x41, 0xa8, 0x94, 0xc9, 0x02, 0xd6, 0xbb,
- 0xb5, 0x7a, 0x7d, 0xdf, 0xb9, 0xb1, 0x83, 0x3e, 0x60, 0x0b, 0xae, 0xe8,
- 0xd7, 0xc8, 0x19, 0x23, 0xfa, 0xe8, 0xb6, 0xa3, 0x37, 0x5d, 0xcb, 0xb5,
- 0x3d, 0x9b, 0x7c, 0x86, 0xac, 0xf4, 0x8b, 0x44, 0x70, 0x5d, 0xf0, 0x63,
- 0x19, 0xe7, 0x31, 0xd3, 0x92, 0x21, 0x57, 0xce, 0x44, 0x91, 0x32, 0x7e,
- 0x98, 0x86, 0x22, 0x61, 0x33, 0x18, 0xa0, 0xe0, 0x73, 0xa1, 0x74, 0x19,
- 0x4e, 0xe0, 0x79, 0xe1, 0x78, 0x14, 0xe0, 0x36, 0x32, 0xcd, 0x15, 0xb8,
- 0x8e, 0x87, 0x8e, 0x3b, 0x6a, 0xe0, 0x3b, 0x90, 0x5c, 0xb1, 0x4c, 0x6a,
- 0x86, 0x9c, 0x2a, 0x57, 0xd5, 0x61, 0x5c, 0x40, 0x81, 0x60, 0xac, 0xc4,
- 0xa0, 0x89, 0x89, 0x8c, 0x28, 0x92, 0xcb, 0x4c, 0x97, 0x56, 0xdd, 0x64,
- 0x0f, 0xc3, 0xde, 0x85, 0xc7, 0x4f, 0xc6, 0x0d, 0xa6, 0x46, 0xc4, 0x14,
- 0x91, 0xc7, 0x94, 0x98, 0x9b, 0x7c, 0x04, 0x51, 0x1f, 0x05, 0x5f, 0x81,
- 0xed, 0x5a, 0x2f, 0x44, 0x36, 0x6f, 0x43, 0xb2, 0xf7, 0xd7, 0x8e, 0x6b,
- 0x07, 0x9e, 0x73, 0x31, 0x06, 0xfc, 0x37, 0x8e, 0xfd, 0xa1, 0xc1, 0xa1,
- 0x1f, 0x46, 0x88, 0xb3, 0xf0, 0x11, 0x6e, 0x03, 0x59, 0x14, 0xcb, 0x45,
- 0xa4, 0x97, 0x05, 0xb7, 0xec, 0xb1, 0xb9, 0xb7, 0xdf, 0xeb, 0x5f, 0xda,
- 0x41, 0xef, 0x06, 0xc6, 0x77, 0x1b, 0xa7, 0x46, 0x84, 0x01, 0x94, 0x11,
- 0x33, 0x11, 0x95, 0xfa, 0x57, 0xf4, 0xe3, 0x89, 0xef, 0x0c, 0x3f, 0x06,
- 0x84, 0xc1, 0x86, 0xdc, 0xfa, 0x44, 0x90, 0x51, 0x16, 0x2f, 0x89, 0x06,
- 0x0d, 0x46, 0xe7, 0xcb, 0xd9, 0xcc, 0xe4, 0x87, 0x6c, 0x8e, 0x48, 0x47,
- 0x82, 0x88, 0x50, 0x89, 0x32, 0x9e, 0x1c, 0xb0, 0x7b, 0xce, 0x73, 0x2a,
- 0x48, 0x90, 0x49, 0x98, 0x7c, 0x50, 0x55, 0xa6, 0x58, 0x66, 0x3f, 0x68,
- 0x76, 0x9f, 0x01, 0xc3, 0x15, 0x55, 0x44, 0xb3, 0xd9, 0x86, 0x4b, 0x8e,
- 0x07, 0xc1, 0xf9, 0xf5, 0x70, 0x48, 0x39, 0xd6, 0x26, 0x8c, 0x8e, 0xc8,
- 0x86, 0x63, 0xaa, 0x9c, 0x88, 0x1b, 0x24, 0x9d, 0x35, 0x0c, 0x09, 0x80,
- 0x8c, 0xf9, 0xca, 0x92, 0xe9, 0x5d, 0x9f, 0xff, 0xce, 0xee, 0xfb, 0xa6,
- 0x60, 0xd4, 0xe5, 0xf3, 0x85, 0xaa, 0xd5, 0x2b, 0x4b, 0x0f, 0x25, 0x69,
- 0x3a, 0x72, 0xca, 0x54, 0xaa, 0xf3, 0xf6, 0x9c, 0x7e, 0x53, 0x72, 0x3c,
- 0x7d, 0xf5, 0xe6, 0x35, 0xf6, 0xde, 0xbf, 0xaf, 0x36, 0x1e, 0x1e, 0xcc,
- 0xea, 0xf1, 0xab, 0x3a, 0x57, 0xd4, 0x6c, 0x66, 0x85, 0x4c, 0x61, 0xe0,
- 0x18, 0xf1, 0xaf, 0xac, 0xa1, 0x3b, 0x19, 0x3d, 0xed, 0x41, 0xf1, 0xa5,
- 0xf1, 0x31, 0x12, 0x92, 0xfc, 0x20, 0x0f, 0x95, 0x5a, 0xc9, 0x22, 0xae,
- 0xb3, 0xc9, 0x26, 0x93, 0x50, 0x66, 0x93, 0xe1, 0x52, 0x2f, 0xbe, 0xc4,
- 0xb0, 0xda, 0x68, 0xa3, 0x34, 0x2f, 0x96, 0x77, 0x5f, 0xee, 0xf7, 0xaf,
- 0x1c, 0x7b, 0xec, 0x07, 0x8e, 0xe1, 0x52, 0x7d, 0x94, 0xf1, 0x5b, 0x16,
- 0xdd, 0xc9, 0xd4, 0xb8, 0xbc, 0xc9, 0xdb, 0xa8, 0x95, 0x61, 0x2e, 0x2a,
- 0x56, 0xa4, 0x4f, 0x87, 0xe4, 0xb3, 0x7a, 0xd7, 0xfe, 0x65, 0x55, 0x59,
- 0x6b, 0xb2, 0x06, 0x89, 0x89, 0xf4, 0x8e, 0x11, 0xa2, 0x43, 0xff, 0xc8,
- 0x42, 0xfc, 0x89, 0x5b, 0xfe, 0xe4, 0x9d, 0x3d, 0xfe, 0xc6, 0x43, 0x51,
- 0x04, 0x6c, 0x02, 0x2d, 0xef, 0x79, 0x66, 0x99, 0xa2, 0xa8, 0x59, 0x94,
- 0x08, 0x8e, 0x18, 0x10, 0x71, 0x59, 0x28, 0x38, 0x62, 0x43, 0x1b, 0x28,
- 0xb1, 0x5f, 0xb3, 0x43, 0x48, 0x2a, 0x89, 0x52, 0x15, 0x53, 0x71, 0x91,
- 0x28, 0x33, 0x0a, 0xa5, 0x4e, 0xce, 0xcb, 0xe2, 0xd5, 0x41, 0x5d, 0xfe,
- 0x23, 0x8f, 0xf4, 0x06, 0x1e, 0xb3, 0xf3, 0x1f, 0xc3, 0xb3, 0x5a, 0xad,
- 0x2a, 0x56, 0x00, 0x4a, 0x99, 0x8b, 0x8c, 0x0e, 0x84, 0x93, 0xc8, 0x66,
- 0xb2, 0xcd, 0x8d, 0x7f, 0x7d, 0x33, 0x39, 0xa4, 0xa4, 0xf2, 0xb7, 0x0f,
- 0xe2, 0x2a, 0x0f, 0x6c, 0x29, 0x25, 0x4b, 0xc8, 0x8e, 0x0d, 0x97, 0xbd,
- 0x18, 0x7f, 0xf5, 0x54, 0x05, 0x71, 0x05, 0xc9, 0xc3, 0xc3, 0xbf, 0x0d,
- 0x07, 0x72, 0x98, 0x71, 0x7e, 0xf6, 0xf7, 0xbf, 0xfd, 0xe5, 0x1f, 0x7f,
- 0xfe, 0x2b, 0x25, 0xfd, 0x3d, 0x3e, 0x52, 0x84, 0xf9, 0xa2, 0x0a, 0x8c,
- 0x4a, 0x82, 0x76, 0xb7, 0xe1, 0x22, 0x67, 0x6c, 0xaf, 0x93, 0xec, 0x3d,
- 0x55, 0x4a, 0x8e, 0x13, 0x3c, 0x8b, 0xc8, 0x31, 0x56, 0x5c, 0xdc, 0xc9,
- 0x7d, 0xa8, 0xc1, 0x0f, 0xb2, 0xb6, 0xae, 0xcf, 0x47, 0x73, 0x71, 0x78,
- 0x57, 0x3b, 0xda, 0xf1, 0xaf, 0xb8, 0xe7, 0xd7, 0x8f, 0x6e, 0x39, 0x69,
- 0x85, 0xa0, 0x5e, 0x09, 0xad, 0xf7, 0x25, 0xb6, 0x7f, 0x01, 0xc6, 0x7d,
- 0x96, 0x47, 0x0c, 0x56, 0xac, 0x9f, 0x50, 0xf8, 0x15, 0xe1, 0x7f, 0xe1,
- 0xcc, 0x3e, 0xa9, 0x0d, 0x76, 0xff, 0x0b, 0x99, 0x0d, 0xe3, 0x86, 0xdd,
- 0xbe, 0x41, 0xe4, 0x2f, 0x8f, 0x6c, 0x4b, 0x1c, 0x51, 0x79, 0xda, 0xea,
- 0xe5, 0x78, 0x8a, 0xa9, 0xa1, 0x6c, 0x99, 0x90, 0xd7, 0xf1, 0x43, 0x96,
- 0xab, 0x86, 0x72, 0x67, 0xf8, 0xa8, 0x88, 0xad, 0xde, 0xa0, 0x37, 0xf5,
- 0x4d, 0x46, 0x2d, 0x57, 0xea, 0x0e, 0xaa, 0xda, 0xaf, 0xda, 0xb2, 0x8b,
- 0x3e, 0xea, 0x03, 0xf0, 0x7b, 0x0c, 0x13, 0x2a, 0x14, 0x48, 0x3a, 0x32,
- 0x8b, 0xd5, 0x16, 0xc7, 0x93, 0x2e, 0xda, 0x27, 0x70, 0xba, 0xe9, 0x91,
- 0x22, 0x27, 0xdd, 0x9a, 0x51, 0x29, 0x8b, 0xc9, 0x55, 0x4d, 0x59, 0xc0,
- 0x20, 0x43, 0x0e, 0x42, 0x7d, 0x64, 0xd4, 0x5c, 0x6f, 0xca, 0xc0, 0x19,
- 0x33, 0x07, 0x4e, 0x59, 0xeb, 0xf4, 0xa4, 0xfb, 0xf2, 0xc7, 0x16, 0x16,
- 0xea, 0x53, 0x58, 0x7b, 0xea, 0x32, 0x8f, 0x8e, 0x8e, 0x8f, 0x8e, 0x5a,
- 0x55, 0x45, 0x31, 0x0d, 0x8e, 0x52, 0x60, 0xb6, 0x1f, 0x0f, 0xca, 0x23,
- 0x4f, 0xb8, 0x94, 0xb0, 0x54, 0x8d, 0xef, 0x3e, 0x4c, 0x30, 0x21, 0xdd,
- 0x38, 0x03, 0x03, 0x8a, 0xc9, 0x40, 0x67, 0x6c, 0x5a, 0xc8, 0x47, 0x11,
- 0x83, 0xa9, 0xe9, 0x75, 0xe6, 0x4c, 0xe6, 0x24, 0xb9, 0x2a, 0x85, 0xc3,
- 0x99, 0x53, 0xd3, 0xbe, 0x2c, 0xc2, 0x47, 0x2a, 0x56, 0xeb, 0x9a, 0x6a,
- 0xcd, 0x69, 0x24, 0x24, 0x16, 0xa8, 0x84, 0xa5, 0x7c, 0x4f, 0x1d, 0x3d,
- 0x7a, 0xdd, 0xf6, 0xbc, 0x8d, 0x4e, 0x97, 0xba, 0xd2, 0x6a, 0x57, 0xb5,
- 0x9e, 0xf4, 0xaf, 0x78, 0x24, 0xe2, 0x9e, 0x97, 0x4b, 0x55, 0xd5, 0x35,
- 0x48, 0x1d, 0xb0, 0x5c, 0xca, 0xc4, 0x83, 0xfb, 0x1c, 0x6c, 0x2a, 0x63,
- 0xcd, 0xf0, 0x09, 0xa3, 0x93, 0x97, 0xaf, 0x7f, 0x3c, 0x38, 0xea, 0x76,
- 0x0f, 0x42, 0x8c, 0x13, 0x9f, 0x05, 0x37, 0x60, 0x92, 0xde, 0xa7, 0xe8,
- 0x10, 0x0f, 0xf1, 0xf7, 0x30, 0x2e, 0x04, 0x58, 0x76, 0xcc, 0x22, 0x8b,
- 0x55, 0x56, 0xdf, 0x8a, 0xde, 0x0d, 0x0d, 0x52, 0xcd, 0x91, 0x3a, 0xf7,
- 0xd3, 0xfa, 0x9a, 0x9f, 0x6b, 0x61, 0x03, 0x6d, 0x3a, 0xf4, 0x0d, 0x5a,
- 0x65, 0x63, 0x77, 0x51, 0x37, 0xda, 0xb5, 0x4a, 0xb8, 0xd3, 0xab, 0x74,
- 0x8f, 0xa4, 0xbc, 0x17, 0xdc, 0xd4, 0xf4, 0xba, 0x73, 0xad, 0x1a, 0x56,
- 0x11, 0x90, 0x9e, 0x01, 0xfa, 0x56, 0xa1, 0xe9, 0x84, 0x53, 0x36, 0x34,
- 0xa8, 0x05, 0x1b, 0xe0, 0xe0, 0x76, 0x26, 0x3a, 0x2a, 0x8f, 0x6c, 0xd8,
- 0xad, 0x8a, 0xd1, 0x92, 0x21, 0xc2, 0xf2, 0xda, 0xb5, 0x1b, 0x6d, 0x94,
- 0x9d, 0x99, 0xc1, 0x54, 0x51, 0xe5, 0x34, 0xf7, 0x6f, 0x9d, 0xa5, 0xc9,
- 0xaf, 0x6e, 0xd0, 0xa8, 0xf3, 0x2d, 0xb9, 0xe0, 0xb8, 0xd9, 0x78, 0x12,
- 0x1d, 0x01, 0xa0, 0x05, 0x5a, 0x91, 0x3a, 0x0a, 0xb6, 0x98, 0xbc, 0x39,
- 0xf9, 0x2d, 0x46, 0xe2, 0x8b, 0x7e, 0x50, 0x07, 0x40, 0xe0, 0x3b, 0x46,
- 0xad, 0x72, 0xe3, 0x89, 0x4b, 0x22, 0x66, 0xdc, 0xf0, 0xd9, 0x73, 0xdc,
- 0xb3, 0x3d, 0x0f, 0x1d, 0x2c, 0xfa, 0xed, 0xa1, 0xbd, 0x7b, 0x7e, 0x83,
- 0x41, 0x0c, 0x1f, 0x53, 0x0b, 0x36, 0x5b, 0x66, 0xd1, 0xc1, 0xc6, 0xcf,
- 0xd5, 0x22, 0x3c, 0x22, 0xef, 0xc6, 0xdf, 0xe3, 0x57, 0x27, 0x95, 0x7b,
- 0xc7, 0xaf, 0x5a, 0xcd, 0x3b, 0x88, 0x66, 0x73, 0x85, 0x33, 0x08, 0x2e,
- 0x7b, 0xde, 0xe5, 0xf0, 0x7a, 0xdc, 0xc7, 0x25, 0x66, 0xeb, 0x49, 0x46,
- 0x73, 0x01, 0x86, 0xd4, 0x2d, 0x11, 0xc9, 0x10, 0x05, 0x42, 0x18, 0xfd,
- 0x5a, 0xe9, 0x1a, 0xbb, 0xbc, 0xcc, 0xbc, 0x83, 0x30, 0xac, 0x7a, 0x64,
- 0x0a, 0x43, 0x9f, 0x86, 0xd4, 0x24, 0x8c, 0x38, 0x35, 0xde, 0xd5, 0xba,
- 0x71, 0x8d, 0xa7, 0x29, 0xaf, 0xf4, 0xe8, 0x52, 0xe2, 0x07, 0x91, 0x89,
- 0xe5, 0x4e, 0x40, 0x56, 0xfb, 0xb8, 0xcc, 0xbd, 0x71, 0xfa, 0x84, 0x48,
- 0xd5, 0x79, 0xd6, 0xbd, 0xff, 0x85, 0xbb, 0xd3, 0x7f, 0x5b, 0x9f, 0xd0,
- 0x3e, 0x95, 0x0f, 0x27, 0xd5, 0xe4, 0xdb, 0x48, 0x08, 0x55, 0x57, 0xd4,
- 0xcc, 0x08, 0x94, 0x86, 0x0c, 0x76, 0x68, 0x54, 0x4b, 0x39, 0xea, 0x29,
- 0x79, 0x47, 0x94, 0xfa, 0x6c, 0x39, 0x59, 0xc0, 0x95, 0xd2, 0x34, 0x24,
- 0xc5, 0x14, 0xcf, 0x43, 0xf3, 0x4c, 0x91, 0x82, 0x52, 0xe4, 0xf0, 0x34,
- 0x7a, 0xef, 0x50, 0x75, 0xe8, 0x54, 0xc7, 0x0e, 0x4c, 0xdc, 0xb7, 0xac,
- 0x6a, 0x5a, 0xad, 0x56, 0xff, 0x9b, 0x4d, 0xfe, 0x4e, 0x7f, 0xdf, 0x35,
- 0x7e, 0x53, 0x2b, 0xee, 0x17, 0x30, 0x03, 0xa9, 0x39, 0xe0, 0x77, 0xcb,
- 0x39, 0xfd, 0x70, 0xd0, 0x61, 0xd1, 0xdf, 0x0f, 0x61, 0x61, 0xf4, 0xb7,
- 0x8b, 0x42, 0x16, 0xf4, 0xa3, 0x8f, 0x49, 0x18, 0x83, 0xcb, 0x6e, 0x6a,
- 0x2c, 0x39, 0x58, 0x57, 0xf6, 0x8d, 0x4d, 0xe9, 0xdd, 0x7c, 0x5a, 0x75,
- 0x8a, 0xaf, 0xb1, 0x31, 0xaa, 0x97, 0xc3, 0x19, 0x99, 0xa1, 0x5d, 0xad,
- 0xdf, 0x6e, 0x8e, 0x6d, 0x4e, 0x18, 0x34, 0x76, 0xc9, 0x69, 0xb1, 0x41,
- 0x4b, 0x8f, 0x27, 0x75, 0x7e, 0xc0, 0x76, 0x39, 0xb9, 0xe3, 0x87, 0x71,
- 0x2d, 0x7a, 0xed, 0x30, 0x81, 0xad, 0x18, 0x8a, 0xa3, 0x4c, 0x61, 0x81,
- 0x98, 0xa8, 0x58, 0x21, 0x35, 0x7e, 0x3f, 0x57, 0xa8, 0xf7, 0x91, 0x01,
- 0x74, 0x26, 0x69, 0xa8, 0x84, 0xcb, 0xd6, 0x49, 0xfb, 0xc5, 0x97, 0x09,
- 0x00, 0xd3, 0x77, 0xe0, 0x4e, 0xfc, 0x9e, 0xdf, 0x88, 0xfc, 0x51, 0xf8,
- 0x19, 0xf1, 0x9a, 0x21, 0x5d, 0x2d, 0xcd, 0x98, 0x0e, 0x56, 0x0a, 0x5c,
- 0x60, 0x60, 0x92, 0x73, 0x8b, 0x87, 0x81, 0x1b, 0x80, 0x8f, 0x7a, 0xbf,
- 0x0f, 0xe8, 0x95, 0xcb, 0xab, 0x4d, 0x60, 0x8c, 0x40, 0x8c, 0x14, 0x32,
- 0x35, 0x02, 0x4d, 0xcc, 0xf4, 0xd7, 0xf8, 0x1c, 0xbf, 0x41, 0x39, 0x09,
- 0x33, 0x30, 0x64, 0x3f, 0xfd, 0x84, 0xaf, 0x03, 0x86, 0x78, 0x1e, 0x9d,
- 0x1b, 0xbe, 0x9e, 0xf3, 0x07, 0x64, 0xa8, 0x4b, 0x67, 0x68, 0x9e, 0xdc,
- 0xde, 0x98, 0x80, 0x9d, 0xa7, 0xd4, 0xef, 0x91, 0xd6, 0x31, 0x3a, 0xeb,
- 0xf5, 0x97, 0x7a, 0x0d, 0x30, 0x6b, 0x7e, 0xfc, 0x42, 0x33, 0xfb, 0x73,
- 0x2e, 0x50, 0x51, 0xcc, 0xc3, 0x03, 0x89, 0x43, 0x0c, 0x48, 0x96, 0xe7,
- 0x31, 0x4f, 0x38, 0x4d, 0xd9, 0x33, 0x1a, 0xbe, 0x53, 0x88, 0x4d, 0x14,
- 0xdb, 0x70, 0xbd, 0x36, 0xc2, 0x6c, 0x9e, 0x27, 0x1a, 0x1e, 0x90, 0xed,
- 0x33, 0x7f, 0xd6, 0xb0, 0xe7, 0x19, 0x73, 0x79, 0x55, 0xf5, 0xcb, 0x92,
- 0x4f, 0x0f, 0x05, 0xe5, 0x5b, 0x6d, 0x05, 0x48, 0x8a, 0x14, 0x14, 0xce,
- 0xf9, 0x9e, 0xe4, 0xee, 0xda, 0x28, 0x2e, 0x63, 0x0c, 0xa4, 0x01, 0x52,
- 0xce, 0xc8, 0x6b, 0xbe, 0x13, 0xfa, 0x38, 0x8f, 0x38, 0x2c, 0x36, 0xbc,
- 0x57, 0x0b, 0x9e, 0x35, 0xdb, 0x0b, 0x30, 0x49, 0x70, 0xdd, 0xd7, 0xb8,
- 0x36, 0xcb, 0x45, 0x15, 0x32, 0x3a, 0xca, 0x29, 0x1c, 0x96, 0x99, 0xf8,
- 0x5c, 0xe6, 0x85, 0x65, 0x9c, 0xef, 0xc4, 0x04, 0x91, 0x34, 0x5f, 0x5f,
- 0xf1, 0x0d, 0x06, 0x97, 0xcd, 0x6e, 0xa6, 0x7e, 0x3f, 0xdd, 0xbc, 0x4b,
- 0x99, 0x34, 0xb3, 0x83, 0x13, 0x2d, 0x6e, 0xe1, 0xf4, 0xb5, 0xc9, 0x7c,
- 0x5b, 0x84, 0x81, 0x08, 0xe7, 0x19, 0x2e, 0x14, 0x51, 0x0d, 0x5e, 0x39,
- 0x54, 0x9b, 0x34, 0xd9, 0x6a, 0x4c, 0xf1, 0x5f, 0x25, 0xdc, 0x19, 0xeb,
- 0xb7, 0xa7, 0xf4, 0x6f, 0x9f, 0xc4, 0x4b, 0x0b, 0x73, 0xea, 0x28, 0x90,
- 0xff, 0xa2, 0x30, 0x63, 0x77, 0xa4, 0x26, 0x27, 0xf8, 0xd0, 0x24, 0xf1,
- 0x2a, 0x27, 0x7e, 0x6a, 0x1d, 0xfd, 0xdc, 0x78, 0x4a, 0x6d, 0x1d, 0xb4,
- 0x8e, 0xb7, 0xbe, 0x6f, 0xc9, 0x2e, 0xb6, 0x73, 0x63, 0xbb, 0x5e, 0x13,
- 0xba, 0x4d, 0x5e, 0xde, 0x85, 0xef, 0xe9, 0x59, 0x73, 0x03, 0xe1, 0xc0,
- 0xa5, 0xe3, 0xa6, 0x59, 0x87, 0x81, 0xe9, 0xef, 0x3f, 0x03, 0x00, 0x00,
- 0xff, 0xff, 0x77, 0x1f, 0xe2, 0xa5, 0x2d, 0x18, 0x00, 0x00,
- },
+ 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x00, 0xff, 0xb4, 0x59,
+ 0xeb, 0x72, 0xdb, 0xc8, 0x95, 0xfe, 0x8f, 0xa7, 0x68, 0x73, 0x67, 0x76,
+ 0xec, 0x2d, 0x89, 0xa4, 0xe4, 0xb5, 0xec, 0x91, 0xc7, 0xb5, 0xa6, 0x48,
+ 0x50, 0xc2, 0x9a, 0x17, 0x0d, 0x00, 0xc9, 0xa3, 0xb8, 0x54, 0x28, 0x08,
+ 0x68, 0x92, 0x1d, 0x01, 0x68, 0x08, 0xdd, 0x14, 0xc5, 0xfc, 0xcb, 0x2b,
+ 0xa4, 0xf2, 0x34, 0x79, 0x9e, 0xfc, 0xc8, 0x63, 0xe4, 0x3b, 0x0d, 0x80,
+ 0x02, 0x65, 0x8e, 0xc6, 0xb9, 0x55, 0x52, 0x16, 0xd1, 0xdd, 0xe7, 0xf4,
+ 0xb9, 0x7c, 0xe7, 0xd6, 0xf3, 0x9e, 0xf5, 0xf2, 0x9c, 0x65, 0x61, 0xca,
+ 0x99, 0x5e, 0x84, 0x9a, 0xa9, 0x85, 0x5c, 0x29, 0x26, 0x33, 0xc6, 0xef,
+ 0x79, 0xb1, 0x66, 0x79, 0x38, 0xc7, 0x86, 0xd0, 0x09, 0xb7, 0x7a, 0xe7,
+ 0xe7, 0xc1, 0xa4, 0x37, 0xb6, 0xd9, 0x07, 0x76, 0x2a, 0xe7, 0xea, 0x18,
+ 0xff, 0xb2, 0x53, 0xa1, 0x99, 0xc7, 0x8b, 0x7b, 0x11, 0x95, 0xfb, 0xa3,
+ 0xe9, 0xe9, 0x14, 0xfb, 0x22, 0x9d, 0x77, 0x66, 0x21, 0x56, 0x65, 0xd6,
+ 0xce, 0xb3, 0xb9, 0xf5, 0x9e, 0xf5, 0x17, 0x61, 0x06, 0x4e, 0x38, 0x2e,
+ 0x66, 0x6c, 0x2d, 0x97, 0xac, 0x58, 0x66, 0x2c, 0x91, 0x51, 0x98, 0x24,
+ 0x6b, 0xcb, 0xbd, 0x98, 0x04, 0x17, 0x9e, 0xed, 0x82, 0x72, 0x2e, 0x34,
+ 0x4e, 0xdb, 0x42, 0x2f, 0x78, 0xc1, 0x5a, 0x31, 0xbf, 0x6f, 0xed, 0xb1,
+ 0x56, 0x5e, 0xc8, 0xb8, 0xc5, 0x24, 0x16, 0x34, 0x57, 0x1a, 0x2b, 0x31,
+ 0x9f, 0x85, 0xcb, 0x04, 0xbc, 0x54, 0x79, 0xc6, 0x70, 0x18, 0x4f, 0x07,
+ 0x24, 0x1b, 0xbe, 0x2d, 0xeb, 0x4b, 0xc1, 0x73, 0xa9, 0x84, 0x96, 0xc5,
+ 0xfa, 0xda, 0x72, 0xa7, 0x53, 0x1f, 0x1b, 0x96, 0xd7, 0x77, 0x9d, 0x73,
+ 0x3f, 0xf0, 0xaf, 0xce, 0xe9, 0xdc, 0x4d, 0xa8, 0x16, 0x38, 0xa8, 0x20,
+ 0x3d, 0x2f, 0xae, 0xad, 0x73, 0x77, 0xea, 0x4f, 0xfb, 0xd3, 0x11, 0x76,
+ 0x16, 0x5a, 0xe7, 0xd6, 0x60, 0x3a, 0xee, 0x39, 0x13, 0x7c, 0x19, 0x21,
+ 0x17, 0x52, 0x69, 0xc3, 0x27, 0xb8, 0x70, 0xe9, 0xc8, 0xf7, 0x2f, 0xeb,
+ 0xf3, 0xaf, 0xd4, 0x71, 0xa7, 0xf3, 0xfd, 0xcb, 0xf2, 0x38, 0x3e, 0xbe,
+ 0x7f, 0x79, 0xe6, 0xfb, 0xe7, 0xc1, 0xf9, 0xd4, 0xf5, 0x5f, 0xa9, 0x8e,
+ 0x65, 0x3e, 0x7a, 0x83, 0x01, 0xe9, 0x66, 0x6d, 0x76, 0xf0, 0xf1, 0xba,
+ 0xdb, 0xed, 0x5a, 0x9e, 0x77, 0x56, 0x7f, 0x1f, 0x1e, 0x42, 0xef, 0x81,
+ 0x50, 0xe1, 0x4d, 0xc2, 0x59, 0x7f, 0x30, 0x21, 0xfb, 0x67, 0x4c, 0x64,
+ 0xb5, 0xf6, 0xa9, 0x8c, 0xb9, 0x35, 0x1d, 0x0e, 0x47, 0xce, 0xc4, 0xae,
+ 0x55, 0x9d, 0x85, 0x89, 0xe2, 0xd6, 0xc0, 0xf1, 0x7a, 0x27, 0x23, 0x3b,
+ 0x70, 0xa7, 0x17, 0xbe, 0xed, 0x92, 0x0b, 0x36, 0x5b, 0xef, 0xd9, 0x29,
+ 0xcf, 0x78, 0x11, 0x6a, 0xce, 0x94, 0xe6, 0xb9, 0x3a, 0xc6, 0xca, 0x77,
+ 0x2c, 0x8a, 0xe1, 0x56, 0xbd, 0xe8, 0x68, 0xd9, 0x99, 0xc3, 0x91, 0x9d,
+ 0x68, 0xa9, 0xb4, 0x4c, 0x3b, 0xa4, 0xb6, 0x32, 0x07, 0xe6, 0xd2, 0xb8,
+ 0xe7, 0xbb, 0xd3, 0x29, 0xa9, 0xdc, 0x51, 0x45, 0xd4, 0xc9, 0x6f, 0xe7,
+ 0x9d, 0xa8, 0x58, 0xe7, 0xa0, 0xd1, 0x89, 0xea, 0xcc, 0x2b, 0xb6, 0x41,
+ 0xc4, 0x0b, 0xdd, 0xc6, 0xf9, 0xfd, 0x28, 0xfc, 0xa0, 0x8b, 0x25, 0x67,
+ 0xfb, 0xf1, 0x12, 0x1b, 0x42, 0x66, 0x1f, 0xde, 0xbd, 0x3d, 0xea, 0x2e,
+ 0xba, 0x69, 0x57, 0xb1, 0x7d, 0x32, 0xdf, 0x87, 0x74, 0x4d, 0x7f, 0xda,
+ 0xfc, 0x21, 0x4c, 0xf3, 0x84, 0xb7, 0x23, 0x99, 0x5a, 0x7d, 0xdb, 0xf5,
+ 0x83, 0xa1, 0x33, 0x22, 0x65, 0x9a, 0x52, 0x74, 0x0c, 0xdb, 0x9c, 0xa7,
+ 0xd6, 0x27, 0xfb, 0x6a, 0xe7, 0x81, 0x5b, 0xbe, 0x36, 0xfb, 0xef, 0xd9,
+ 0x45, 0x9e, 0x03, 0x2a, 0x09, 0xcc, 0x95, 0x30, 0x39, 0x63, 0x9a, 0x83,
+ 0x3b, 0x29, 0x1c, 0x66, 0x31, 0x94, 0x86, 0x28, 0x11, 0x9b, 0x09, 0xd8,
+ 0x94, 0x54, 0xc6, 0xf1, 0x06, 0x74, 0x80, 0x31, 0xb3, 0xca, 0x56, 0x00,
+ 0x1b, 0x37, 0xa0, 0xa6, 0x65, 0xfe, 0xc0, 0xa3, 0xa5, 0xe6, 0xb1, 0xe5,
+ 0xf9, 0x3d, 0xdf, 0xe9, 0x07, 0xc6, 0xed, 0xe7, 0x3d, 0xff, 0x8c, 0x5c,
+ 0x68, 0x7d, 0x89, 0x43, 0x1d, 0x02, 0x3b, 0xfc, 0xba, 0x81, 0xd3, 0x74,
+ 0xad, 0xee, 0x12, 0x83, 0x54, 0x68, 0x38, 0x2f, 0xb8, 0x2a, 0xd1, 0x8a,
+ 0x45, 0xa1, 0xf9, 0x6b, 0x6c, 0x08, 0xfd, 0x83, 0x22, 0xd8, 0x17, 0x2c,
+ 0x5a, 0x48, 0x0a, 0x96, 0xc1, 0x49, 0x8d, 0x43, 0x43, 0x6b, 0x9d, 0x4d,
+ 0x3d, 0x42, 0xc1, 0xc1, 0xe1, 0xdb, 0x76, 0x17, 0xff, 0x3b, 0x38, 0x7e,
+ 0xfd, 0xba, 0x7b, 0x64, 0x55, 0xe1, 0x46, 0x5e, 0xb2, 0xaa, 0x00, 0x29,
+ 0xa4, 0xd4, 0xd6, 0x79, 0xcf, 0xf3, 0x3e, 0x0f, 0xd8, 0x07, 0x88, 0x30,
+ 0xa4, 0x8b, 0x1a, 0xd7, 0x66, 0xc9, 0x7a, 0x8f, 0xf1, 0x3a, 0x7e, 0x4a,
+ 0x3c, 0x91, 0x64, 0x05, 0xbf, 0x5b, 0x8a, 0x82, 0x97, 0x82, 0x01, 0xf1,
+ 0x62, 0xb6, 0xde, 0x9f, 0x2d, 0x93, 0xa4, 0x05, 0x10, 0x8e, 0x36, 0xb1,
+ 0x53, 0x9e, 0xaf, 0xd9, 0xd6, 0xf2, 0x1b, 0xae, 0x56, 0x65, 0x02, 0xd2,
+ 0xdf, 0xe0, 0xa6, 0x1d, 0xdf, 0xc0, 0x1c, 0x61, 0x9c, 0x8a, 0xec, 0xda,
+ 0x04, 0x52, 0xb4, 0x2c, 0x84, 0x46, 0xbc, 0x39, 0x13, 0x58, 0x6e, 0x34,
+ 0x02, 0x12, 0xfb, 0x9f, 0x1a, 0x50, 0x7c, 0xf1, 0xa2, 0x7f, 0xd6, 0x9b,
+ 0x9c, 0xda, 0xcc, 0x3f, 0x73, 0x3c, 0xe6, 0x4f, 0xd9, 0x27, 0xdb, 0x3e,
+ 0x67, 0x57, 0xd3, 0x0b, 0x97, 0x19, 0xdd, 0x06, 0x3d, 0xbf, 0xc7, 0xbc,
+ 0xde, 0xd0, 0x7e, 0xf1, 0xc2, 0xf2, 0xec, 0xbe, 0x6b, 0xfb, 0x01, 0xbc,
+ 0x0f, 0x06, 0x2f, 0xfe, 0xeb, 0xe3, 0x70, 0x60, 0x7f, 0x76, 0xf1, 0xff,
+ 0xff, 0xfe, 0x9f, 0x97, 0xe0, 0xd4, 0x5b, 0x6a, 0xb9, 0x9f, 0xc8, 0x39,
+ 0xa2, 0xa3, 0xe0, 0x29, 0x4f, 0x6f, 0xa0, 0x6b, 0x1c, 0xae, 0x95, 0x05,
+ 0xec, 0x3b, 0x93, 0xc0, 0xb5, 0xc7, 0xf6, 0xf8, 0x04, 0xa1, 0x30, 0xe8,
+ 0x5d, 0x79, 0xa0, 0x7f, 0x6b, 0xf5, 0xa7, 0xd3, 0x4f, 0x8e, 0x6d, 0x72,
+ 0x4c, 0xc3, 0xa4, 0x41, 0xb8, 0xe2, 0x4a, 0xa6, 0xbc, 0xde, 0xde, 0xd0,
+ 0x35, 0xcf, 0x88, 0x2c, 0x2a, 0x78, 0x2c, 0x4a, 0xab, 0xb8, 0x94, 0x14,
+ 0x15, 0x50, 0x53, 0xc8, 0x87, 0x35, 0x0b, 0x97, 0xb0, 0x72, 0x06, 0x80,
+ 0x19, 0xbc, 0xb3, 0x05, 0x0f, 0x63, 0x08, 0x62, 0x52, 0x29, 0x80, 0xb8,
+ 0x54, 0xd5, 0x87, 0xe5, 0xda, 0x97, 0xb6, 0xeb, 0xd9, 0x01, 0x52, 0xc6,
+ 0x2f, 0x57, 0x41, 0xef, 0xc2, 0x3f, 0xb3, 0x27, 0x00, 0x16, 0xc0, 0x35,
+ 0xdd, 0xe4, 0xbd, 0x5f, 0xf6, 0x3f, 0xdb, 0x27, 0xb4, 0xb5, 0x4f, 0x0b,
+ 0x55, 0x5e, 0x02, 0x50, 0xae, 0xad, 0x5e, 0xdf, 0x77, 0x2e, 0xed, 0xa0,
+ 0x0f, 0x0f, 0x05, 0x23, 0xfa, 0x35, 0x76, 0x26, 0x08, 0x74, 0x52, 0xec,
+ 0xe0, 0x5d, 0x17, 0xcc, 0x3d, 0x9b, 0xe0, 0x49, 0x80, 0xf8, 0xd5, 0x43,
+ 0x88, 0x12, 0x23, 0x0d, 0xe7, 0x31, 0xd3, 0x92, 0x21, 0x2d, 0xcf, 0x44,
+ 0x91, 0x32, 0xbe, 0x9f, 0x86, 0x22, 0x61, 0x33, 0xf8, 0xba, 0xe0, 0x73,
+ 0xa1, 0x74, 0x19, 0xb9, 0xe0, 0x79, 0xea, 0x78, 0x94, 0x4b, 0x6c, 0x24,
+ 0xb5, 0x11, 0xb8, 0x4e, 0x86, 0x8e, 0x3b, 0x6e, 0xb8, 0x72, 0x20, 0xb9,
+ 0x62, 0x99, 0xd4, 0x0c, 0xe9, 0x5b, 0xae, 0x2a, 0x62, 0x5c, 0x40, 0x31,
+ 0x67, 0x00, 0xc1, 0x60, 0x34, 0x13, 0x84, 0x51, 0x24, 0x97, 0x99, 0x2e,
+ 0x01, 0xb4, 0x49, 0x54, 0x86, 0xbd, 0x6b, 0xf4, 0x6f, 0x30, 0x35, 0x22,
+ 0xa6, 0x08, 0x72, 0xa6, 0xc4, 0xdc, 0xa4, 0x3e, 0x88, 0x7a, 0x2f, 0xf8,
+ 0x0a, 0x6c, 0xd7, 0x7a, 0x21, 0xb2, 0x79, 0x1b, 0x92, 0xfd, 0x7c, 0xe1,
+ 0xb8, 0x76, 0xe0, 0x39, 0xa7, 0x13, 0x78, 0xfa, 0xd2, 0xb1, 0x3f, 0x37,
+ 0x38, 0xf4, 0xc3, 0x08, 0x21, 0x1d, 0xde, 0x03, 0xa1, 0x90, 0x45, 0xb1,
+ 0x5c, 0x44, 0x7a, 0x59, 0x70, 0xcb, 0x9e, 0x98, 0x7b, 0xfb, 0xbd, 0xfe,
+ 0x99, 0x1d, 0xf4, 0x2e, 0x81, 0x33, 0xb7, 0x41, 0x35, 0x26, 0x1b, 0x40,
+ 0x19, 0x31, 0xab, 0x3c, 0x59, 0x9f, 0x9f, 0x4c, 0x7d, 0x67, 0x78, 0x15,
+ 0x90, 0x0d, 0x9a, 0xc7, 0x25, 0x72, 0x45, 0xcc, 0x35, 0xa8, 0x8e, 0x4d,
+ 0xa9, 0xa0, 0x02, 0x80, 0xb2, 0xb5, 0x58, 0xde, 0x50, 0x4e, 0xa3, 0xd0,
+ 0x10, 0x5a, 0x95, 0x99, 0x55, 0x28, 0xb5, 0xe4, 0xaa, 0x73, 0x70, 0xf4,
+ 0xa6, 0xe6, 0xf9, 0x1c, 0x16, 0x36, 0x97, 0x58, 0x5f, 0x56, 0xfc, 0x66,
+ 0x21, 0xe5, 0x2d, 0xe5, 0x98, 0x7e, 0x01, 0x6c, 0xe9, 0x50, 0xdd, 0xc2,
+ 0x22, 0xb0, 0xf1, 0x7d, 0x98, 0x90, 0x69, 0x60, 0x63, 0xe4, 0x28, 0x65,
+ 0xf9, 0x3d, 0xef, 0x53, 0xe0, 0x4c, 0xe0, 0xac, 0xcb, 0x1e, 0x49, 0x79,
+ 0x40, 0xde, 0xe1, 0x89, 0x00, 0x4e, 0x51, 0xb6, 0x53, 0x2e, 0x97, 0x9a,
+ 0x8e, 0x23, 0x38, 0x65, 0x16, 0x2b, 0x6b, 0x60, 0x13, 0x3a, 0xdc, 0xc0,
+ 0x77, 0xc6, 0x36, 0xca, 0x05, 0x08, 0xde, 0xe0, 0x36, 0x42, 0x01, 0xd5,
+ 0xc0, 0x52, 0xc6, 0x41, 0x43, 0xd9, 0x93, 0xe5, 0x6c, 0x66, 0xb2, 0x6b,
+ 0x36, 0x47, 0x9e, 0x04, 0xaa, 0x23, 0xd4, 0xf1, 0x8c, 0x27, 0x7b, 0xec,
+ 0x96, 0xf3, 0x9c, 0xca, 0x39, 0xcc, 0x2c, 0x4c, 0x36, 0xad, 0xea, 0x7a,
+ 0x2c, 0xb3, 0x1f, 0x34, 0xbb, 0xcd, 0x00, 0x8b, 0x15, 0xf5, 0x13, 0x66,
+ 0xb3, 0x8d, 0x80, 0x9e, 0x0c, 0x82, 0x93, 0x8b, 0xe1, 0x90, 0x2a, 0x94,
+ 0x4d, 0xaa, 0x1e, 0x10, 0x2c, 0x27, 0x14, 0x2c, 0xc8, 0x3a, 0x48, 0xd9,
+ 0x6b, 0x60, 0x93, 0x14, 0x23, 0x6f, 0x94, 0x0d, 0x87, 0x77, 0x71, 0xf2,
+ 0xff, 0x76, 0xdf, 0x37, 0xe5, 0xb6, 0x6e, 0x3e, 0x5e, 0xa9, 0xda, 0x63,
+ 0x65, 0xe1, 0xa6, 0x12, 0x97, 0x1a, 0x57, 0xa8, 0x54, 0xe7, 0xed, 0x39,
+ 0xfd, 0x26, 0x37, 0x1c, 0xbf, 0x79, 0xf7, 0x16, 0x7b, 0x3f, 0xff, 0x5c,
+ 0x6d, 0xdc, 0xdd, 0x99, 0xd5, 0xc3, 0x37, 0x75, 0xa6, 0xad, 0xd9, 0xcc,
+ 0x0a, 0x99, 0x02, 0xb3, 0x31, 0xb2, 0xa7, 0xb2, 0x86, 0xee, 0x74, 0xfc,
+ 0xb8, 0x07, 0xc5, 0x37, 0x41, 0x6c, 0xa0, 0x9d, 0x87, 0x4a, 0xad, 0x64,
+ 0x11, 0xd7, 0xb9, 0x78, 0x93, 0x87, 0xa9, 0x2e, 0x48, 0x4a, 0x07, 0x5f,
+ 0xdb, 0xb0, 0xda, 0x68, 0x97, 0x08, 0xf9, 0x7a, 0xbf, 0x3f, 0x72, 0x80,
+ 0x80, 0xc0, 0x31, 0x5c, 0xaa, 0x8f, 0x32, 0xfb, 0x95, 0x2d, 0xcb, 0xf4,
+ 0xdc, 0x44, 0x71, 0x0d, 0xb4, 0x30, 0x17, 0xed, 0x06, 0xd8, 0x48, 0x3e,
+ 0x8b, 0x50, 0x54, 0xf5, 0x25, 0x3b, 0xf0, 0x68, 0xf2, 0x64, 0xc7, 0x08,
+ 0xd1, 0xa1, 0x7f, 0x64, 0x21, 0xfe, 0xc0, 0x2d, 0x7f, 0xfa, 0xc9, 0x9e,
+ 0x7c, 0x23, 0x51, 0x14, 0xc1, 0x36, 0x81, 0x96, 0xb7, 0x3c, 0xb3, 0x4c,
+ 0x4b, 0xa1, 0x59, 0x94, 0x08, 0x64, 0x3e, 0x26, 0xe2, 0xb2, 0xcc, 0x72,
+ 0x84, 0xbb, 0x36, 0xa6, 0xc4, 0x7e, 0xcd, 0x0e, 0x88, 0x53, 0x12, 0x85,
+ 0x3e, 0xa6, 0xd2, 0x2c, 0x51, 0xa4, 0x15, 0x1a, 0x05, 0x39, 0x2f, 0x4b,
+ 0x7f, 0x07, 0x29, 0xf4, 0xf7, 0x3c, 0xd2, 0x1b, 0xf3, 0x98, 0x9d, 0x7f,
+ 0xd9, 0x3c, 0xab, 0xd5, 0xaa, 0x62, 0x05, 0x43, 0x29, 0x73, 0x91, 0xd1,
+ 0x81, 0xec, 0x24, 0xb2, 0x99, 0x6c, 0x73, 0x83, 0xaf, 0x6f, 0x3e, 0x0e,
+ 0x29, 0xa9, 0x79, 0xd8, 0x65, 0xe2, 0x2a, 0xb5, 0x6d, 0x29, 0x25, 0x4b,
+ 0x93, 0x1d, 0x1a, 0x2e, 0x3b, 0x6d, 0xfc, 0x2c, 0x55, 0x65, 0xe2, 0xca,
+ 0x24, 0x77, 0x77, 0xff, 0xb4, 0x39, 0x90, 0x96, 0x0d, 0xf8, 0xd9, 0x5f,
+ 0xff, 0xf2, 0xa7, 0xbf, 0xfd, 0xf1, 0xcf, 0x54, 0x32, 0x77, 0x60, 0xa4,
+ 0x08, 0xf3, 0x45, 0x15, 0x18, 0x95, 0x04, 0xed, 0x6e, 0x03, 0x22, 0xef,
+ 0xd9, 0x4e, 0x90, 0xec, 0xa4, 0x2a, 0x25, 0x07, 0x05, 0xcf, 0x22, 0x02,
+ 0xc6, 0x8a, 0x8b, 0x1b, 0xb9, 0xcb, 0x6a, 0xc0, 0x41, 0xd6, 0xd6, 0x35,
+ 0x7d, 0x34, 0x17, 0xfb, 0x37, 0x35, 0xd0, 0x0e, 0x7f, 0x03, 0x9e, 0xcf,
+ 0x93, 0x6e, 0x81, 0xb4, 0xb2, 0xa0, 0x5e, 0x09, 0xad, 0x77, 0x25, 0xb6,
+ 0x7f, 0xc0, 0x8c, 0xbb, 0x3c, 0x8f, 0x18, 0xac, 0x58, 0x3f, 0x5a, 0xe1,
+ 0x37, 0x84, 0xff, 0x15, 0x9a, 0x5d, 0x52, 0x1b, 0xdb, 0xfd, 0x27, 0x64,
+ 0x36, 0x8c, 0x1b, 0x7e, 0xfb, 0x06, 0x91, 0xbf, 0x26, 0xd9, 0x96, 0x38,
+ 0xa2, 0x8a, 0xbb, 0xd5, 0x09, 0xf3, 0x14, 0x33, 0x57, 0xd9, 0x70, 0x22,
+ 0xaf, 0xe3, 0x87, 0x2c, 0x57, 0xcd, 0xc9, 0x27, 0xa3, 0x5b, 0x75, 0xd8,
+ 0xea, 0x0d, 0x7a, 0xe7, 0xbe, 0xc9, 0xa8, 0xe5, 0x4a, 0xdd, 0x7f, 0x56,
+ 0xfb, 0x55, 0x53, 0x7b, 0xda, 0xdf, 0xaa, 0x80, 0x55, 0x49, 0xdb, 0xe2,
+ 0x78, 0xd4, 0xb5, 0x1a, 0xb5, 0xf0, 0xa8, 0x5b, 0x33, 0x2a, 0x65, 0x31,
+ 0xb9, 0xaa, 0x29, 0x0b, 0x18, 0x64, 0xc8, 0x41, 0xa6, 0x79, 0x43, 0x07,
+ 0xbd, 0x29, 0x03, 0xef, 0x99, 0x21, 0x38, 0x66, 0xad, 0xe3, 0xa3, 0xee,
+ 0xeb, 0x1f, 0x5b, 0x58, 0xa8, 0xa9, 0xb0, 0xf6, 0xd8, 0xa3, 0x1f, 0x1c,
+ 0x1c, 0x1e, 0x1c, 0xb4, 0xaa, 0x8a, 0x62, 0x7a, 0x36, 0xa5, 0xc0, 0x6c,
+ 0xb7, 0x3d, 0x28, 0x8f, 0x3c, 0xda, 0xa5, 0x34, 0x4b, 0x35, 0x36, 0xec,
+ 0xb2, 0x09, 0x1a, 0x84, 0x4b, 0x67, 0x60, 0x8c, 0x62, 0x32, 0xd0, 0x7b,
+ 0x76, 0x5e, 0xc8, 0x7b, 0x41, 0x1d, 0xa6, 0x69, 0xdf, 0xe6, 0x4c, 0xe6,
+ 0x24, 0xb9, 0x2a, 0x85, 0x03, 0xcd, 0xb1, 0xe9, 0xc8, 0x16, 0xe1, 0x3d,
+ 0x15, 0xab, 0x75, 0x7d, 0x6a, 0xcd, 0x69, 0xa0, 0x26, 0x16, 0xa8, 0x84,
+ 0xa5, 0x7c, 0x8f, 0xf3, 0x10, 0x26, 0x85, 0xf6, 0xbc, 0x8d, 0x39, 0x81,
+ 0x7a, 0xfa, 0x6a, 0x57, 0xb5, 0x1e, 0xf5, 0xaf, 0x78, 0x24, 0xe2, 0x96,
+ 0x97, 0x4b, 0x55, 0xd5, 0x35, 0x96, 0xda, 0x63, 0xb9, 0x94, 0x89, 0x07,
+ 0xf8, 0xec, 0x6d, 0x2a, 0x63, 0xcd, 0xf0, 0xd1, 0x46, 0x47, 0xaf, 0xdf,
+ 0xfe, 0xb8, 0x77, 0xd0, 0xed, 0xee, 0x85, 0x18, 0xc6, 0x1e, 0x04, 0x37,
+ 0xc6, 0x24, 0xbd, 0x8f, 0xd1, 0x5f, 0xef, 0xe3, 0xef, 0x7e, 0x5c, 0x50,
+ 0xb7, 0xd2, 0x31, 0x8b, 0x2c, 0x56, 0x59, 0x7d, 0x2b, 0xda, 0x51, 0xf4,
+ 0x7c, 0x35, 0x47, 0x9a, 0x7b, 0x8e, 0xeb, 0x6b, 0x3e, 0xd6, 0xc2, 0x06,
+ 0xda, 0xcc, 0x37, 0x1b, 0x6b, 0x95, 0xbd, 0xea, 0x69, 0x3d, 0xa6, 0xd4,
+ 0x2a, 0xe1, 0x4e, 0xaf, 0xd2, 0x3d, 0x42, 0x5b, 0x25, 0x78, 0xd9, 0x98,
+ 0x57, 0x7d, 0x7f, 0xd5, 0xee, 0x8b, 0x80, 0xf4, 0x0c, 0xca, 0xfe, 0x0d,
+ 0x14, 0x4e, 0xd9, 0xd0, 0xa0, 0x16, 0x6c, 0x0c, 0x07, 0xd8, 0x99, 0xe8,
+ 0xa8, 0x10, 0xd9, 0xf0, 0x5b, 0x15, 0xa3, 0x25, 0x43, 0x84, 0xe5, 0x85,
+ 0x6b, 0x37, 0xda, 0x28, 0x3b, 0x33, 0x63, 0xbd, 0xa2, 0xca, 0x69, 0xee,
+ 0xdf, 0xa2, 0xa5, 0xb9, 0xb9, 0xee, 0x0f, 0xa9, 0x99, 0x2f, 0xb9, 0x80,
+ 0xdc, 0x6c, 0x3c, 0x8a, 0x8e, 0x00, 0xa0, 0x96, 0x6e, 0x13, 0x05, 0x5b,
+ 0x4c, 0xde, 0x1d, 0xfd, 0x6f, 0xb7, 0x6b, 0x9d, 0xf6, 0x37, 0xcd, 0xa0,
+ 0xe9, 0xf1, 0xc0, 0xa4, 0xdc, 0x78, 0xe4, 0x92, 0x88, 0x19, 0x37, 0x7c,
+ 0x76, 0x90, 0x7b, 0xb6, 0xe7, 0xd1, 0x50, 0x32, 0x72, 0x86, 0xf6, 0x53,
+ 0xfa, 0x8d, 0x0d, 0x62, 0x60, 0x4c, 0x2d, 0xd8, 0x6c, 0x99, 0x45, 0x7b,
+ 0x1b, 0x9c, 0xab, 0x45, 0x78, 0x40, 0xe8, 0xc6, 0xdf, 0xc3, 0x37, 0x47,
+ 0x15, 0xbc, 0xe3, 0x37, 0xad, 0xe6, 0x1d, 0x74, 0x66, 0x73, 0x85, 0x33,
+ 0x08, 0xce, 0x7a, 0xde, 0xd9, 0xf0, 0x62, 0xd2, 0xc7, 0x25, 0x66, 0xeb,
+ 0x51, 0x46, 0x73, 0x01, 0x46, 0xfc, 0x2d, 0x11, 0xc9, 0x11, 0x05, 0x42,
+ 0x18, 0xfd, 0x5a, 0x09, 0x8d, 0xa7, 0xbc, 0xcc, 0xb4, 0x88, 0x30, 0xac,
+ 0xda, 0x7e, 0x0a, 0x43, 0x9f, 0x46, 0xfc, 0x24, 0x8c, 0x38, 0xcd, 0x12,
+ 0xd5, 0xba, 0x81, 0xc6, 0xe3, 0x8c, 0x5c, 0x22, 0xba, 0x94, 0xf8, 0x4e,
+ 0x64, 0x62, 0xf9, 0x24, 0x20, 0xab, 0x7d, 0x5c, 0xe6, 0x5e, 0x3a, 0x7d,
+ 0xb2, 0x48, 0xd5, 0x79, 0xd6, 0xe3, 0xcc, 0xa9, 0xfb, 0x64, 0xa4, 0xb0,
+ 0xbe, 0xa0, 0x7d, 0x2a, 0x9f, 0x9d, 0xaa, 0x77, 0x83, 0x46, 0x42, 0xa8,
+ 0xba, 0xa2, 0x66, 0x46, 0xa0, 0x34, 0x64, 0x6c, 0x87, 0x46, 0xb5, 0x94,
+ 0xa3, 0x7e, 0x63, 0x78, 0x22, 0x4a, 0x4d, 0x5b, 0x0e, 0x4b, 0x80, 0x52,
+ 0x9a, 0x86, 0xa4, 0x98, 0xe2, 0x79, 0x68, 0x1e, 0x79, 0x52, 0x9c, 0x14,
+ 0x39, 0x90, 0x46, 0xaf, 0x45, 0xaa, 0x0e, 0x9d, 0x8a, 0x6c, 0xcf, 0xc4,
+ 0x7d, 0xcb, 0xaa, 0x66, 0xfd, 0x6a, 0xf5, 0xdf, 0xd9, 0xe4, 0x3f, 0xe9,
+ 0xef, 0xbb, 0x06, 0x37, 0xb5, 0xe2, 0x7e, 0x01, 0x37, 0x90, 0x9a, 0x03,
+ 0x7e, 0xb3, 0x9c, 0xd3, 0x0f, 0x07, 0x1d, 0x16, 0xfd, 0xfd, 0x1c, 0x16,
+ 0x46, 0x7f, 0xbb, 0x28, 0x64, 0x41, 0x3f, 0xfa, 0x85, 0xa0, 0xa9, 0xfa,
+ 0x69, 0x6a, 0x2c, 0x39, 0x58, 0x23, 0x8c, 0x50, 0x94, 0xde, 0xcd, 0xa7,
+ 0x55, 0xa7, 0xf8, 0xda, 0x36, 0x46, 0xf5, 0x72, 0xde, 0x24, 0x37, 0xb4,
+ 0xab, 0xf5, 0xeb, 0x0d, 0xd9, 0x86, 0xc2, 0x58, 0xe3, 0xe9, 0x71, 0x5a,
+ 0x6c, 0x9c, 0xa5, 0xa7, 0xa7, 0x3a, 0x3f, 0x60, 0xbb, 0x7c, 0xf7, 0xc0,
+ 0x0f, 0x03, 0x2d, 0x7a, 0x2b, 0x32, 0x81, 0xad, 0xe8, 0x29, 0x40, 0xa6,
+ 0xf0, 0x40, 0x4c, 0xa7, 0x58, 0x21, 0x35, 0x7e, 0xbf, 0x54, 0xa8, 0xf7,
+ 0x91, 0x31, 0xe8, 0x4c, 0xd2, 0x9c, 0x0c, 0xc8, 0xd6, 0x49, 0xfb, 0xd5,
+ 0xd7, 0x09, 0x60, 0x34, 0x3d, 0x0d, 0xdc, 0xa9, 0xdf, 0xf3, 0x1b, 0x91,
+ 0x3f, 0x0e, 0x1f, 0x10, 0xaf, 0x19, 0xd2, 0xd5, 0xd2, 0x3c, 0x72, 0x80,
+ 0x95, 0x02, 0x17, 0x38, 0x98, 0xe4, 0xdc, 0xe2, 0x61, 0xcc, 0x0d, 0x83,
+ 0x8f, 0x7b, 0xbf, 0x04, 0xf4, 0x46, 0xe8, 0xd5, 0x2e, 0x30, 0x4e, 0x20,
+ 0x46, 0x0a, 0x99, 0x1a, 0x81, 0x26, 0x66, 0xfa, 0x39, 0x3e, 0x87, 0xef,
+ 0x50, 0x4e, 0xc2, 0x0c, 0x0c, 0xd9, 0x4f, 0x3f, 0xe1, 0x6b, 0x8f, 0x21,
+ 0x9e, 0xc7, 0x27, 0x86, 0xaf, 0xe7, 0xfc, 0x0e, 0x19, 0xea, 0xcc, 0x19,
+ 0x9a, 0x07, 0xcb, 0x77, 0x26, 0x60, 0xe7, 0x29, 0xf5, 0x7b, 0xa4, 0x75,
+ 0x8c, 0xce, 0x7a, 0xfd, 0xb5, 0x5e, 0x03, 0x8c, 0xcf, 0x57, 0x5f, 0x69,
+ 0x66, 0x3f, 0xe4, 0x02, 0x15, 0xc5, 0x3c, 0xdb, 0x90, 0x38, 0xc4, 0x80,
+ 0x64, 0x79, 0x19, 0xf3, 0x84, 0xd3, 0xc3, 0xc1, 0x8c, 0xde, 0x13, 0x52,
+ 0x88, 0x4d, 0x27, 0xb6, 0xcd, 0xf5, 0xd6, 0x08, 0xb3, 0x79, 0xdc, 0x69,
+ 0x20, 0x20, 0xdb, 0xe5, 0xfe, 0xac, 0xe1, 0x4f, 0x7a, 0xc2, 0xa9, 0xaa,
+ 0x7e, 0x59, 0xf2, 0xe9, 0xed, 0xa3, 0x7c, 0xe9, 0xae, 0x0c, 0x92, 0x22,
+ 0x05, 0x85, 0x73, 0xbe, 0x23, 0xb9, 0xbb, 0x36, 0x8a, 0xcb, 0x04, 0x03,
+ 0x69, 0x80, 0x94, 0x33, 0xf6, 0x9a, 0xaf, 0xac, 0x3e, 0xe8, 0x11, 0x87,
+ 0xc5, 0x86, 0xf7, 0x6a, 0xc1, 0xb3, 0x66, 0x7b, 0x01, 0x26, 0x09, 0xae,
+ 0x7b, 0x8e, 0x6b, 0xb3, 0x5c, 0x54, 0x21, 0xa3, 0xa3, 0x9c, 0xc2, 0x61,
+ 0x99, 0x89, 0x87, 0x32, 0x2f, 0x2c, 0xe3, 0xfc, 0x49, 0x4c, 0xd0, 0x91,
+ 0xe6, 0xdb, 0x35, 0xbe, 0xc1, 0xe0, 0xac, 0xd9, 0xcd, 0xd4, 0xaf, 0xcf,
+ 0x9b, 0x57, 0x3d, 0x93, 0x66, 0x9e, 0xd8, 0x89, 0x16, 0xb7, 0xec, 0xf4,
+ 0xdc, 0x64, 0xbe, 0x2d, 0xc2, 0x40, 0x84, 0xf3, 0x0c, 0x17, 0x8a, 0xa8,
+ 0x36, 0x5e, 0x39, 0x54, 0x9b, 0x34, 0xd9, 0x6a, 0x4c, 0xf1, 0xcf, 0x1e,
+ 0x7c, 0x32, 0xd6, 0x6f, 0x4f, 0xe9, 0xdf, 0x3e, 0x89, 0x97, 0x1e, 0xe6,
+ 0xd4, 0x51, 0x20, 0xff, 0x45, 0x61, 0xc6, 0x6e, 0x48, 0x4d, 0x4e, 0xe6,
+ 0x43, 0x93, 0xc4, 0xab, 0x9c, 0xf8, 0xa5, 0x75, 0xf0, 0xb1, 0xf1, 0x10,
+ 0xdd, 0xda, 0x6b, 0x1d, 0x6e, 0x7d, 0x5f, 0x93, 0x5f, 0x6c, 0x7a, 0x2a,
+ 0xf1, 0x9a, 0xa6, 0xdb, 0xe4, 0xe5, 0xa7, 0xe6, 0x7b, 0x7c, 0x14, 0x6e,
+ 0x98, 0x70, 0xfb, 0x75, 0x98, 0x6d, 0x3d, 0xd4, 0x5a, 0x03, 0x97, 0xb8,
+ 0x97, 0x07, 0x4f, 0x40, 0x19, 0xd3, 0x7f, 0x73, 0x79, 0x90, 0x45, 0x5a,
+ 0x4a, 0x78, 0x6c, 0x1e, 0x7a, 0x8f, 0xe9, 0x9f, 0x8f, 0x9b, 0xff, 0x02,
+ 0x61, 0xd2, 0xcf, 0xff, 0x21, 0x3b, 0x17, 0xe8, 0x24, 0x3e, 0x2c, 0xf5,
+ 0xec, 0x9d, 0x45, 0xe0, 0x21, 0x26, 0x7f, 0x0f, 0x00, 0x00, 0xff, 0xff,
+ 0xc9, 0x2e, 0x07, 0x65, 0xc7, 0x19, 0x00, 0x00,
+ },
"conf/app.ini",
)
}
@@ -893,7 +909,7 @@ func conf_content_git_bare_zip() ([]byte, error) {
0x28, 0x3f, 0xfe, 0xba, 0x0a, 0x40, 0x72, 0xf5, 0xcf, 0xf9, 0x6a, 0x9b,
0x11, 0xa6, 0xf9, 0x31, 0xfa, 0x3f, 0x01, 0x00, 0x00, 0xff, 0xff, 0x01,
0x81, 0x55, 0x99, 0xb6, 0x26, 0x00, 0x00,
- },
+ },
"conf/content/git-bare.zip",
)
}
@@ -946,7 +962,7 @@ func conf_etc_supervisord_conf() ([]byte, error) {
0x8d, 0x88, 0x90, 0x28, 0xbf, 0x3f, 0xd4, 0xfe, 0xe7, 0x05, 0xbd, 0x28,
0xc2, 0x24, 0xff, 0x1b, 0x00, 0x00, 0xff, 0xff, 0xbc, 0x75, 0xb0, 0x31,
0xf7, 0x04, 0x00, 0x00,
- },
+ },
"conf/etc/supervisord.conf",
)
}
@@ -970,7 +986,7 @@ func conf_gitignore_android() ([]byte, error) {
0xb8, 0xa3, 0xb5, 0xe2, 0x2c, 0x81, 0x0c, 0xe2, 0x75, 0xc9, 0xf2, 0x07,
0x2f, 0x5e, 0x58, 0x0b, 0x39, 0x3d, 0xa4, 0xf9, 0x3f, 0x00, 0x00, 0xff,
0xff, 0x00, 0x96, 0x67, 0x2c, 0x0e, 0x01, 0x00, 0x00,
- },
+ },
"conf/gitignore/Android",
)
}
@@ -989,7 +1005,7 @@ func conf_gitignore_c() ([]byte, error) {
0xeb, 0x8e, 0x79, 0xeb, 0x31, 0x1d, 0x73, 0xb8, 0xa3, 0x8d, 0x6e, 0xdd,
0xea, 0xd7, 0xf5, 0x1f, 0x00, 0x00, 0xff, 0xff, 0xca, 0x54, 0xa9, 0x22,
0x8f, 0x00, 0x00, 0x00,
- },
+ },
"conf/gitignore/C",
)
}
@@ -1065,7 +1081,7 @@ func conf_gitignore_c_sharp() ([]byte, error) {
0x8b, 0x52, 0xd1, 0xf6, 0x63, 0x0e, 0x6e, 0xd8, 0x98, 0xaa, 0x6a, 0xd8,
0xb4, 0xfb, 0xc9, 0x76, 0x55, 0xfd, 0xd7, 0x1f, 0x9f, 0xfe, 0x0b, 0x00,
0x00, 0xff, 0xff, 0xfe, 0xac, 0xdb, 0x69, 0xf1, 0x05, 0x00, 0x00,
- },
+ },
"conf/gitignore/C Sharp",
)
}
@@ -1081,7 +1097,7 @@ func conf_gitignore_c_() ([]byte, error) {
0x5c, 0x92, 0x58, 0x82, 0xa6, 0x32, 0x27, 0x31, 0x13, 0x4c, 0x02, 0x89,
0x44, 0x2e, 0x40, 0x00, 0x00, 0x00, 0xff, 0xff, 0xa4, 0xe6, 0x21, 0x26,
0x7e, 0x00, 0x00, 0x00,
- },
+ },
"conf/gitignore/C++",
)
}
@@ -1106,7 +1122,7 @@ func conf_gitignore_google_go() ([]byte, error) {
0x22, 0xd5, 0x42, 0x03, 0xe7, 0x8f, 0xcc, 0x91, 0xf3, 0x76, 0xe7, 0x08,
0x5a, 0xe9, 0x27, 0x00, 0x00, 0xff, 0xff, 0x3c, 0xab, 0x59, 0x6f, 0xfb,
0x00, 0x00, 0x00,
- },
+ },
"conf/gitignore/Google Go",
)
}
@@ -1128,7 +1144,7 @@ func conf_gitignore_java() ([]byte, error) {
0xc4, 0xd9, 0x51, 0x29, 0x52, 0x96, 0x20, 0x55, 0xb3, 0x54, 0x7b, 0x4f,
0x6c, 0x82, 0x2e, 0x7d, 0x5c, 0x72, 0x5c, 0xc7, 0x47, 0x00, 0x00, 0x00,
0xff, 0xff, 0xe7, 0xd6, 0xf7, 0xa4, 0xbc, 0x00, 0x00, 0x00,
- },
+ },
"conf/gitignore/Java",
)
}
@@ -1153,7 +1169,7 @@ func conf_gitignore_objective_c() ([]byte, error) {
0xc9, 0x07, 0xae, 0xa1, 0xb9, 0x4c, 0x22, 0x3f, 0x5b, 0x4d, 0x65, 0x7b,
0x3d, 0x9f, 0x60, 0x5c, 0x71, 0xf9, 0x0d, 0x00, 0x00, 0xff, 0xff, 0xa9,
0x17, 0x4f, 0x2a, 0x18, 0x01, 0x00, 0x00,
- },
+ },
"conf/gitignore/Objective-C",
)
}
@@ -1180,7 +1196,7 @@ func conf_gitignore_python() ([]byte, error) {
0x78, 0xeb, 0xf6, 0x9c, 0x58, 0x85, 0x7f, 0x28, 0x58, 0x2b, 0xb6, 0xa6,
0x1c, 0xdd, 0x7f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x02, 0xf0, 0xe2, 0xc0,
0x3a, 0x01, 0x00, 0x00,
- },
+ },
"conf/gitignore/Python",
)
}
@@ -1200,7 +1216,7 @@ func conf_gitignore_ruby() ([]byte, error) {
0x41, 0xb1, 0xbc, 0x23, 0x4d, 0xdf, 0x7d, 0xf0, 0x88, 0x6c, 0xbf, 0x3b,
0xf1, 0xdf, 0x00, 0x00, 0x00, 0xff, 0xff, 0xb1, 0xca, 0xf7, 0x91, 0x9e,
0x00, 0x00, 0x00,
- },
+ },
"conf/gitignore/Ruby",
)
}
@@ -2189,7 +2205,7 @@ func conf_license_affero_gpl() ([]byte, error) {
0x42, 0xc2, 0x5f, 0x88, 0x57, 0x1b, 0xd8, 0x89, 0x3e, 0x15, 0x0e, 0xb8,
0x30, 0xdf, 0x60, 0x88, 0xff, 0x2f, 0x00, 0x00, 0xff, 0xff, 0x0c, 0xd2,
0xa8, 0x4c, 0xc3, 0x86, 0x00, 0x00,
- },
+ },
"conf/license/Affero GPL",
)
}
@@ -2527,7 +2543,7 @@ func conf_license_apache_v2_license() ([]byte, error) {
0x37, 0x23, 0x02, 0x0e, 0x94, 0x00, 0x65, 0xa1, 0x3f, 0x7d, 0xb3, 0x2f,
0x4c, 0x4b, 0x32, 0x5f, 0x77, 0xe7, 0xe7, 0x9a, 0xff, 0x6f, 0x00, 0x00,
0x00, 0xff, 0xff, 0xa8, 0x76, 0x8d, 0x12, 0x3b, 0x2c, 0x00, 0x00,
- },
+ },
"conf/license/Apache v2 License",
)
}
@@ -2809,7 +2825,7 @@ func conf_license_artistic_license_2_0() ([]byte, error) {
0xdd, 0x89, 0x97, 0xd6, 0xcf, 0xf7, 0x8f, 0x92, 0x0f, 0xb9, 0xfb, 0x77,
0x00, 0x00, 0x00, 0xff, 0xff, 0xaf, 0x26, 0x8b, 0xf2, 0xb7, 0x22, 0x00,
0x00,
- },
+ },
"conf/license/Artistic License 2.0",
)
}
@@ -2883,7 +2899,7 @@ func conf_license_bsd_3_clause_license() ([]byte, error) {
0x95, 0xb8, 0xec, 0x49, 0x8a, 0xac, 0xd8, 0xd0, 0x39, 0xee, 0xdb, 0xbf,
0x02, 0x00, 0x00, 0xff, 0xff, 0x84, 0xcd, 0xba, 0x22, 0xc1, 0x05, 0x00,
0x00,
- },
+ },
"conf/license/BSD (3-Clause) License",
)
}
@@ -3459,7 +3475,7 @@ func conf_license_gpl_v2() ([]byte, error) {
0x4d, 0xee, 0x25, 0x41, 0xb2, 0x70, 0x4f, 0xe6, 0xf0, 0xef, 0xb7, 0x30,
0xc7, 0xff, 0x7e, 0x8b, 0x83, 0x22, 0xe6, 0xff, 0x06, 0x00, 0x00, 0xff,
0xff, 0x82, 0x4d, 0xf9, 0x2b, 0x69, 0x46, 0x00, 0x00,
- },
+ },
"conf/license/GPL v2",
)
}
@@ -3521,7 +3537,7 @@ func conf_license_mit_license() ([]byte, error) {
0x42, 0x26, 0x78, 0x8e, 0x5c, 0x15, 0x81, 0x29, 0xe2, 0xdb, 0xf0, 0xd3,
0x9f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x49, 0x86, 0xab, 0x31, 0x29, 0x04,
0x00, 0x00,
- },
+ },
"conf/license/MIT License",
)
}
@@ -3537,7 +3553,7 @@ func conf_mysql_sql() ([]byte, error) {
0xa1, 0xe0, 0xec, 0xef, 0xe3, 0x03, 0xd2, 0x06, 0xe2, 0xc4, 0xa7, 0xa7,
0xe6, 0xa5, 0x16, 0x25, 0xe6, 0xc4, 0x27, 0x67, 0x5a, 0x73, 0x01, 0x02,
0x00, 0x00, 0xff, 0xff, 0xcd, 0xf5, 0x53, 0x80, 0x6d, 0x00, 0x00, 0x00,
- },
+ },
"conf/mysql.sql",
)
}
@@ -3557,12 +3573,11 @@ func conf_supervisor_ini() ([]byte, error) {
0xa1, 0xed, 0x82, 0x8e, 0x38, 0x6f, 0x11, 0x92, 0x13, 0x67, 0x75, 0xe7,
0xeb, 0xe5, 0xe4, 0x86, 0xef, 0xd7, 0xc1, 0x18, 0xfa, 0x04, 0x00, 0x00,
0xff, 0xff, 0x61, 0x60, 0x15, 0x6f, 0xc9, 0x00, 0x00, 0x00,
- },
+ },
"conf/supervisor.ini",
)
}
-
// Asset loads and returns the asset for the given name.
// It returns an error if the asset could not be found or
// could not be loaded.
@@ -3584,7 +3599,7 @@ func AssetNames() []string {
}
// _bindata is a table, holding each asset generator, mapped to its name.
-var _bindata = map[string] func() ([]byte, error) {
+var _bindata = map[string]func() ([]byte, error){
"conf/app.ini": conf_app_ini,
"conf/content/git-bare.zip": conf_content_git_bare_zip,
"conf/etc/supervisord.conf": conf_etc_supervisord_conf,
diff --git a/modules/cron/constantdelay.go b/modules/cron/constantdelay.go
new file mode 100644
index 00000000..cd6e7b1b
--- /dev/null
+++ b/modules/cron/constantdelay.go
@@ -0,0 +1,27 @@
+package cron
+
+import "time"
+
+// ConstantDelaySchedule represents a simple recurring duty cycle, e.g. "Every 5 minutes".
+// It does not support jobs more frequent than once a second.
+type ConstantDelaySchedule struct {
+ Delay time.Duration
+}
+
+// Every returns a crontab Schedule that activates once every duration.
+// Delays of less than a second are not supported (will round up to 1 second).
+// Any fields less than a Second are truncated.
+func Every(duration time.Duration) ConstantDelaySchedule {
+ if duration < time.Second {
+ duration = time.Second
+ }
+ return ConstantDelaySchedule{
+ Delay: duration - time.Duration(duration.Nanoseconds())%time.Second,
+ }
+}
+
+// Next returns the next time this should be run.
+// This rounds so that the next activation time will be on the second.
+func (schedule ConstantDelaySchedule) Next(t time.Time) time.Time {
+ return t.Add(schedule.Delay - time.Duration(t.Nanosecond())*time.Nanosecond)
+}
diff --git a/modules/cron/constantdelay_test.go b/modules/cron/constantdelay_test.go
new file mode 100644
index 00000000..f43a58ad
--- /dev/null
+++ b/modules/cron/constantdelay_test.go
@@ -0,0 +1,54 @@
+package cron
+
+import (
+ "testing"
+ "time"
+)
+
+func TestConstantDelayNext(t *testing.T) {
+ tests := []struct {
+ time string
+ delay time.Duration
+ expected string
+ }{
+ // Simple cases
+ {"Mon Jul 9 14:45 2012", 15*time.Minute + 50*time.Nanosecond, "Mon Jul 9 15:00 2012"},
+ {"Mon Jul 9 14:59 2012", 15 * time.Minute, "Mon Jul 9 15:14 2012"},
+ {"Mon Jul 9 14:59:59 2012", 15 * time.Minute, "Mon Jul 9 15:14:59 2012"},
+
+ // Wrap around hours
+ {"Mon Jul 9 15:45 2012", 35 * time.Minute, "Mon Jul 9 16:20 2012"},
+
+ // Wrap around days
+ {"Mon Jul 9 23:46 2012", 14 * time.Minute, "Tue Jul 10 00:00 2012"},
+ {"Mon Jul 9 23:45 2012", 35 * time.Minute, "Tue Jul 10 00:20 2012"},
+ {"Mon Jul 9 23:35:51 2012", 44*time.Minute + 24*time.Second, "Tue Jul 10 00:20:15 2012"},
+ {"Mon Jul 9 23:35:51 2012", 25*time.Hour + 44*time.Minute + 24*time.Second, "Thu Jul 11 01:20:15 2012"},
+
+ // Wrap around months
+ {"Mon Jul 9 23:35 2012", 91*24*time.Hour + 25*time.Minute, "Thu Oct 9 00:00 2012"},
+
+ // Wrap around minute, hour, day, month, and year
+ {"Mon Dec 31 23:59:45 2012", 15 * time.Second, "Tue Jan 1 00:00:00 2013"},
+
+ // Round to nearest second on the delay
+ {"Mon Jul 9 14:45 2012", 15*time.Minute + 50*time.Nanosecond, "Mon Jul 9 15:00 2012"},
+
+ // Round up to 1 second if the duration is less.
+ {"Mon Jul 9 14:45:00 2012", 15 * time.Millisecond, "Mon Jul 9 14:45:01 2012"},
+
+ // Round to nearest second when calculating the next time.
+ {"Mon Jul 9 14:45:00.005 2012", 15 * time.Minute, "Mon Jul 9 15:00 2012"},
+
+ // Round to nearest second for both.
+ {"Mon Jul 9 14:45:00.005 2012", 15*time.Minute + 50*time.Nanosecond, "Mon Jul 9 15:00 2012"},
+ }
+
+ for _, c := range tests {
+ actual := Every(c.delay).Next(getTime(c.time))
+ expected := getTime(c.expected)
+ if actual != expected {
+ t.Errorf("%s, \"%s\": (expected) %v != %v (actual)", c.time, c.delay, expected, actual)
+ }
+ }
+}
diff --git a/modules/cron/cron.go b/modules/cron/cron.go
index 27b1fc41..dbf0174b 100644
--- a/modules/cron/cron.go
+++ b/modules/cron/cron.go
@@ -1,3 +1,4 @@
+// Copyright 2012 Rob Figueiredo. All rights reserved.
// Copyright 2014 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
@@ -5,13 +6,208 @@
package cron
import (
- "github.com/robfig/cron"
-
- "github.com/gogits/gogs/models"
+ "sort"
+ "time"
)
-func NewCronContext() {
- c := cron.New()
- c.AddFunc("@every 1h", models.MirrorUpdate)
- c.Start()
+// Cron keeps track of any number of entries, invoking the associated func as
+// specified by the schedule. It may be started, stopped, and the entries may
+// be inspected while running.
+type Cron struct {
+ entries []*Entry
+ stop chan struct{}
+ add chan *Entry
+ snapshot chan []*Entry
+ running bool
+}
+
+// Job is an interface for submitted cron jobs.
+type Job interface {
+ Run()
+}
+
+// The Schedule describes a job's duty cycle.
+type Schedule interface {
+ // Return the next activation time, later than the given time.
+ // Next is invoked initially, and then each time the job is run.
+ Next(time.Time) time.Time
+}
+
+// Entry consists of a schedule and the func to execute on that schedule.
+type Entry struct {
+ Description string
+ Spec string
+
+ // The schedule on which this job should be run.
+ Schedule Schedule
+
+ // The next time the job will run. This is the zero time if Cron has not been
+ // started or this entry's schedule is unsatisfiable
+ Next time.Time
+
+ // The last time this job was run. This is the zero time if the job has never
+ // been run.
+ Prev time.Time
+
+ // The Job to run.
+ Job Job
+
+ ExecTimes int // Execute times count.
+}
+
+// byTime is a wrapper for sorting the entry array by time
+// (with zero time at the end).
+type byTime []*Entry
+
+func (s byTime) Len() int { return len(s) }
+func (s byTime) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
+func (s byTime) Less(i, j int) bool {
+ // Two zero times should return false.
+ // Otherwise, zero is "greater" than any other time.
+ // (To sort it at the end of the list.)
+ if s[i].Next.IsZero() {
+ return false
+ }
+ if s[j].Next.IsZero() {
+ return true
+ }
+ return s[i].Next.Before(s[j].Next)
+}
+
+// New returns a new Cron job runner.
+func New() *Cron {
+ return &Cron{
+ entries: nil,
+ add: make(chan *Entry),
+ stop: make(chan struct{}),
+ snapshot: make(chan []*Entry),
+ running: false,
+ }
+}
+
+// A wrapper that turns a func() into a cron.Job
+type FuncJob func()
+
+func (f FuncJob) Run() { f() }
+
+// AddFunc adds a func to the Cron to be run on the given schedule.
+func (c *Cron) AddFunc(desc, spec string, cmd func()) error {
+ return c.AddJob(desc, spec, FuncJob(cmd))
+}
+
+// AddFunc adds a Job to the Cron to be run on the given schedule.
+func (c *Cron) AddJob(desc, spec string, cmd Job) error {
+ schedule, err := Parse(spec)
+ if err != nil {
+ return err
+ }
+ c.Schedule(desc, spec, schedule, cmd)
+ return nil
+}
+
+// Schedule adds a Job to the Cron to be run on the given schedule.
+func (c *Cron) Schedule(desc, spec string, schedule Schedule, cmd Job) {
+ entry := &Entry{
+ Description: desc,
+ Spec: spec,
+ Schedule: schedule,
+ Job: cmd,
+ }
+ if !c.running {
+ c.entries = append(c.entries, entry)
+ return
+ }
+
+ c.add <- entry
+}
+
+// Entries returns a snapshot of the cron entries.
+func (c *Cron) Entries() []*Entry {
+ if c.running {
+ c.snapshot <- nil
+ x := <-c.snapshot
+ return x
+ }
+ return c.entrySnapshot()
+}
+
+// Start the cron scheduler in its own go-routine.
+func (c *Cron) Start() {
+ c.running = true
+ go c.run()
+}
+
+// Run the scheduler.. this is private just due to the need to synchronize
+// access to the 'running' state variable.
+func (c *Cron) run() {
+ // Figure out the next activation times for each entry.
+ now := time.Now().Local()
+ for _, entry := range c.entries {
+ entry.Next = entry.Schedule.Next(now)
+ }
+
+ for {
+ // Determine the next entry to run.
+ sort.Sort(byTime(c.entries))
+
+ var effective time.Time
+ if len(c.entries) == 0 || c.entries[0].Next.IsZero() {
+ // If there are no entries yet, just sleep - it still handles new entries
+ // and stop requests.
+ effective = now.AddDate(10, 0, 0)
+ } else {
+ effective = c.entries[0].Next
+ }
+
+ select {
+ case now = <-time.After(effective.Sub(now)):
+ // Run every entry whose next time was this effective time.
+ for _, e := range c.entries {
+ if e.Next != effective {
+ break
+ }
+ go e.Job.Run()
+ e.ExecTimes++
+ e.Prev = e.Next
+ e.Next = e.Schedule.Next(effective)
+ }
+ continue
+
+ case newEntry := <-c.add:
+ c.entries = append(c.entries, newEntry)
+ newEntry.Next = newEntry.Schedule.Next(now)
+
+ case <-c.snapshot:
+ c.snapshot <- c.entrySnapshot()
+
+ case <-c.stop:
+ return
+ }
+
+ // 'now' should be updated after newEntry and snapshot cases.
+ now = time.Now().Local()
+ }
+}
+
+// Stop the cron scheduler.
+func (c *Cron) Stop() {
+ c.stop <- struct{}{}
+ c.running = false
+}
+
+// entrySnapshot returns a copy of the current cron entry list.
+func (c *Cron) entrySnapshot() []*Entry {
+ entries := make([]*Entry, 0, len(c.entries))
+ for _, e := range c.entries {
+ entries = append(entries, &Entry{
+ Description: e.Description,
+ Spec: e.Spec,
+ Schedule: e.Schedule,
+ Next: e.Next,
+ Prev: e.Prev,
+ Job: e.Job,
+ ExecTimes: e.ExecTimes,
+ })
+ }
+ return entries
}
diff --git a/modules/cron/cron_test.go b/modules/cron/cron_test.go
new file mode 100644
index 00000000..417247a0
--- /dev/null
+++ b/modules/cron/cron_test.go
@@ -0,0 +1,255 @@
+package cron
+
+import (
+ "fmt"
+ "sync"
+ "testing"
+ "time"
+)
+
+// Many tests schedule a job for every second, and then wait at most a second
+// for it to run. This amount is just slightly larger than 1 second to
+// compensate for a few milliseconds of runtime.
+const ONE_SECOND = 1*time.Second + 10*time.Millisecond
+
+// Start and stop cron with no entries.
+func TestNoEntries(t *testing.T) {
+ cron := New()
+ cron.Start()
+
+ select {
+ case <-time.After(ONE_SECOND):
+ t.FailNow()
+ case <-stop(cron):
+ }
+}
+
+// Start, stop, then add an entry. Verify entry doesn't run.
+func TestStopCausesJobsToNotRun(t *testing.T) {
+ wg := &sync.WaitGroup{}
+ wg.Add(1)
+
+ cron := New()
+ cron.Start()
+ cron.Stop()
+ cron.AddFunc("", "* * * * * ?", func() { wg.Done() })
+
+ select {
+ case <-time.After(ONE_SECOND):
+ // No job ran!
+ case <-wait(wg):
+ t.FailNow()
+ }
+}
+
+// Add a job, start cron, expect it runs.
+func TestAddBeforeRunning(t *testing.T) {
+ wg := &sync.WaitGroup{}
+ wg.Add(1)
+
+ cron := New()
+ cron.AddFunc("", "* * * * * ?", func() { wg.Done() })
+ cron.Start()
+ defer cron.Stop()
+
+ // Give cron 2 seconds to run our job (which is always activated).
+ select {
+ case <-time.After(ONE_SECOND):
+ t.FailNow()
+ case <-wait(wg):
+ }
+}
+
+// Start cron, add a job, expect it runs.
+func TestAddWhileRunning(t *testing.T) {
+ wg := &sync.WaitGroup{}
+ wg.Add(1)
+
+ cron := New()
+ cron.Start()
+ defer cron.Stop()
+ cron.AddFunc("", "* * * * * ?", func() { wg.Done() })
+
+ select {
+ case <-time.After(ONE_SECOND):
+ t.FailNow()
+ case <-wait(wg):
+ }
+}
+
+// Test timing with Entries.
+func TestSnapshotEntries(t *testing.T) {
+ wg := &sync.WaitGroup{}
+ wg.Add(1)
+
+ cron := New()
+ cron.AddFunc("", "@every 2s", func() { wg.Done() })
+ cron.Start()
+ defer cron.Stop()
+
+ // Cron should fire in 2 seconds. After 1 second, call Entries.
+ select {
+ case <-time.After(ONE_SECOND):
+ cron.Entries()
+ }
+
+ // Even though Entries was called, the cron should fire at the 2 second mark.
+ select {
+ case <-time.After(ONE_SECOND):
+ t.FailNow()
+ case <-wait(wg):
+ }
+
+}
+
+// Test that the entries are correctly sorted.
+// Add a bunch of long-in-the-future entries, and an immediate entry, and ensure
+// that the immediate entry runs immediately.
+// Also: Test that multiple jobs run in the same instant.
+func TestMultipleEntries(t *testing.T) {
+ wg := &sync.WaitGroup{}
+ wg.Add(2)
+
+ cron := New()
+ cron.AddFunc("", "0 0 0 1 1 ?", func() {})
+ cron.AddFunc("", "* * * * * ?", func() { wg.Done() })
+ cron.AddFunc("", "0 0 0 31 12 ?", func() {})
+ cron.AddFunc("", "* * * * * ?", func() { wg.Done() })
+
+ cron.Start()
+ defer cron.Stop()
+
+ select {
+ case <-time.After(ONE_SECOND):
+ t.FailNow()
+ case <-wait(wg):
+ }
+}
+
+// Test running the same job twice.
+func TestRunningJobTwice(t *testing.T) {
+ wg := &sync.WaitGroup{}
+ wg.Add(2)
+
+ cron := New()
+ cron.AddFunc("", "0 0 0 1 1 ?", func() {})
+ cron.AddFunc("", "0 0 0 31 12 ?", func() {})
+ cron.AddFunc("", "* * * * * ?", func() { wg.Done() })
+
+ cron.Start()
+ defer cron.Stop()
+
+ select {
+ case <-time.After(2 * ONE_SECOND):
+ t.FailNow()
+ case <-wait(wg):
+ }
+}
+
+func TestRunningMultipleSchedules(t *testing.T) {
+ wg := &sync.WaitGroup{}
+ wg.Add(2)
+
+ cron := New()
+ cron.AddFunc("", "0 0 0 1 1 ?", func() {})
+ cron.AddFunc("", "0 0 0 31 12 ?", func() {})
+ cron.AddFunc("", "* * * * * ?", func() { wg.Done() })
+ cron.Schedule("", "", Every(time.Minute), FuncJob(func() {}))
+ cron.Schedule("", "", Every(time.Second), FuncJob(func() { wg.Done() }))
+ cron.Schedule("", "", Every(time.Hour), FuncJob(func() {}))
+
+ cron.Start()
+ defer cron.Stop()
+
+ select {
+ case <-time.After(2 * ONE_SECOND):
+ t.FailNow()
+ case <-wait(wg):
+ }
+}
+
+// Test that the cron is run in the local time zone (as opposed to UTC).
+func TestLocalTimezone(t *testing.T) {
+ wg := &sync.WaitGroup{}
+ wg.Add(1)
+
+ now := time.Now().Local()
+ spec := fmt.Sprintf("%d %d %d %d %d ?",
+ now.Second()+1, now.Minute(), now.Hour(), now.Day(), now.Month())
+
+ cron := New()
+ cron.AddFunc("", spec, func() { wg.Done() })
+ cron.Start()
+ defer cron.Stop()
+
+ select {
+ case <-time.After(ONE_SECOND):
+ t.FailNow()
+ case <-wait(wg):
+ }
+}
+
+type testJob struct {
+ wg *sync.WaitGroup
+ name string
+}
+
+func (t testJob) Run() {
+ t.wg.Done()
+}
+
+// Simple test using Runnables.
+func TestJob(t *testing.T) {
+ wg := &sync.WaitGroup{}
+ wg.Add(1)
+
+ cron := New()
+ cron.AddJob("", "0 0 0 30 Feb ?", testJob{wg, "job0"})
+ cron.AddJob("", "0 0 0 1 1 ?", testJob{wg, "job1"})
+ cron.AddJob("", "* * * * * ?", testJob{wg, "job2"})
+ cron.AddJob("", "1 0 0 1 1 ?", testJob{wg, "job3"})
+ cron.Schedule("", "", Every(5*time.Second+5*time.Nanosecond), testJob{wg, "job4"})
+ cron.Schedule("", "", Every(5*time.Minute), testJob{wg, "job5"})
+
+ cron.Start()
+ defer cron.Stop()
+
+ select {
+ case <-time.After(ONE_SECOND):
+ t.FailNow()
+ case <-wait(wg):
+ }
+
+ // Ensure the entries are in the right order.
+ expecteds := []string{"job2", "job4", "job5", "job1", "job3", "job0"}
+
+ var actuals []string
+ for _, entry := range cron.Entries() {
+ actuals = append(actuals, entry.Job.(testJob).name)
+ }
+
+ for i, expected := range expecteds {
+ if actuals[i] != expected {
+ t.Errorf("Jobs not in the right order. (expected) %s != %s (actual)", expecteds, actuals)
+ t.FailNow()
+ }
+ }
+}
+
+func wait(wg *sync.WaitGroup) chan bool {
+ ch := make(chan bool)
+ go func() {
+ wg.Wait()
+ ch <- true
+ }()
+ return ch
+}
+
+func stop(cron *Cron) chan bool {
+ ch := make(chan bool)
+ go func() {
+ cron.Stop()
+ ch <- true
+ }()
+ return ch
+}
diff --git a/modules/cron/doc.go b/modules/cron/doc.go
new file mode 100644
index 00000000..dbdf5012
--- /dev/null
+++ b/modules/cron/doc.go
@@ -0,0 +1,129 @@
+/*
+Package cron implements a cron spec parser and job runner.
+
+Usage
+
+Callers may register Funcs to be invoked on a given schedule. Cron will run
+them in their own goroutines.
+
+ c := cron.New()
+ c.AddFunc("0 30 * * * *", func() { fmt.Println("Every hour on the half hour") })
+ c.AddFunc("@hourly", func() { fmt.Println("Every hour") })
+ c.AddFunc("@every 1h30m", func() { fmt.Println("Every hour thirty") })
+ c.Start()
+ ..
+ // Funcs are invoked in their own goroutine, asynchronously.
+ ...
+ // Funcs may also be added to a running Cron
+ c.AddFunc("@daily", func() { fmt.Println("Every day") })
+ ..
+ // Inspect the cron job entries' next and previous run times.
+ inspect(c.Entries())
+ ..
+ c.Stop() // Stop the scheduler (does not stop any jobs already running).
+
+CRON Expression Format
+
+A cron expression represents a set of times, using 6 space-separated fields.
+
+ Field name | Mandatory? | Allowed values | Allowed special characters
+ ---------- | ---------- | -------------- | --------------------------
+ Seconds | Yes | 0-59 | * / , -
+ Minutes | Yes | 0-59 | * / , -
+ Hours | Yes | 0-23 | * / , -
+ Day of month | Yes | 1-31 | * / , - ?
+ Month | Yes | 1-12 or JAN-DEC | * / , -
+ Day of week | Yes | 0-6 or SUN-SAT | * / , - ?
+
+Note: Month and Day-of-week field values are case insensitive. "SUN", "Sun",
+and "sun" are equally accepted.
+
+Special Characters
+
+Asterisk ( * )
+
+The asterisk indicates that the cron expression will match for all values of the
+field; e.g., using an asterisk in the 5th field (month) would indicate every
+month.
+
+Slash ( / )
+
+Slashes are used to describe increments of ranges. For example 3-59/15 in the
+1st field (minutes) would indicate the 3rd minute of the hour and every 15
+minutes thereafter. The form "*\/..." is equivalent to the form "first-last/...",
+that is, an increment over the largest possible range of the field. The form
+"N/..." is accepted as meaning "N-MAX/...", that is, starting at N, use the
+increment until the end of that specific range. It does not wrap around.
+
+Comma ( , )
+
+Commas are used to separate items of a list. For example, using "MON,WED,FRI" in
+the 5th field (day of week) would mean Mondays, Wednesdays and Fridays.
+
+Hyphen ( - )
+
+Hyphens are used to define ranges. For example, 9-17 would indicate every
+hour between 9am and 5pm inclusive.
+
+Question mark ( ? )
+
+Question mark may be used instead of '*' for leaving either day-of-month or
+day-of-week blank.
+
+Predefined schedules
+
+You may use one of several pre-defined schedules in place of a cron expression.
+
+ Entry | Description | Equivalent To
+ ----- | ----------- | -------------
+ @yearly (or @annually) | Run once a year, midnight, Jan. 1st | 0 0 0 1 1 *
+ @monthly | Run once a month, midnight, first of month | 0 0 0 1 * *
+ @weekly | Run once a week, midnight on Sunday | 0 0 0 * * 0
+ @daily (or @midnight) | Run once a day, midnight | 0 0 0 * * *
+ @hourly | Run once an hour, beginning of hour | 0 0 * * * *
+
+Intervals
+
+You may also schedule a job to execute at fixed intervals. This is supported by
+formatting the cron spec like this:
+
+ @every <duration>
+
+where "duration" is a string accepted by time.ParseDuration
+(http://golang.org/pkg/time/#ParseDuration).
+
+For example, "@every 1h30m10s" would indicate a schedule that activates every
+1 hour, 30 minutes, 10 seconds.
+
+Note: The interval does not take the job runtime into account. For example,
+if a job takes 3 minutes to run, and it is scheduled to run every 5 minutes,
+it will have only 2 minutes of idle time between each run.
+
+Time zones
+
+All interpretation and scheduling is done in the machine's local time zone (as
+provided by the Go time package (http://www.golang.org/pkg/time).
+
+Be aware that jobs scheduled during daylight-savings leap-ahead transitions will
+not be run!
+
+Thread safety
+
+Since the Cron service runs concurrently with the calling code, some amount of
+care must be taken to ensure proper synchronization.
+
+All cron methods are designed to be correctly synchronized as long as the caller
+ensures that invocations have a clear happens-before ordering between them.
+
+Implementation
+
+Cron entries are stored in an array, sorted by their next activation time. Cron
+sleeps until the next job is due to be run.
+
+Upon waking:
+ - it runs each entry that is active on that second
+ - it calculates the next run times for the jobs that were run
+ - it re-sorts the array of entries by next activation time.
+ - it goes to sleep until the soonest job.
+*/
+package cron
diff --git a/modules/cron/manager.go b/modules/cron/manager.go
new file mode 100644
index 00000000..563426fb
--- /dev/null
+++ b/modules/cron/manager.go
@@ -0,0 +1,24 @@
+// Copyright 2014 The Gogs Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package cron
+
+import (
+ "fmt"
+
+ "github.com/gogits/gogs/models"
+ "github.com/gogits/gogs/modules/setting"
+)
+
+var c = New()
+
+func NewCronContext() {
+ c.AddFunc("Update mirrors", "@every 1h", models.MirrorUpdate)
+ c.AddFunc("Deliver hooks", fmt.Sprintf("@every %dm", setting.WebhookTaskInterval), models.DeliverHooks)
+ c.Start()
+}
+
+func ListEntries() []*Entry {
+ return c.Entries()
+}
diff --git a/modules/cron/parser.go b/modules/cron/parser.go
new file mode 100644
index 00000000..4224fa93
--- /dev/null
+++ b/modules/cron/parser.go
@@ -0,0 +1,231 @@
+package cron
+
+import (
+ "fmt"
+ "log"
+ "math"
+ "strconv"
+ "strings"
+ "time"
+)
+
+// Parse returns a new crontab schedule representing the given spec.
+// It returns a descriptive error if the spec is not valid.
+//
+// It accepts
+// - Full crontab specs, e.g. "* * * * * ?"
+// - Descriptors, e.g. "@midnight", "@every 1h30m"
+func Parse(spec string) (_ Schedule, err error) {
+ // Convert panics into errors
+ defer func() {
+ if recovered := recover(); recovered != nil {
+ err = fmt.Errorf("%v", recovered)
+ }
+ }()
+
+ if spec[0] == '@' {
+ return parseDescriptor(spec), nil
+ }
+
+ // Split on whitespace. We require 5 or 6 fields.
+ // (second) (minute) (hour) (day of month) (month) (day of week, optional)
+ fields := strings.Fields(spec)
+ if len(fields) != 5 && len(fields) != 6 {
+ log.Panicf("Expected 5 or 6 fields, found %d: %s", len(fields), spec)
+ }
+
+ // If a sixth field is not provided (DayOfWeek), then it is equivalent to star.
+ if len(fields) == 5 {
+ fields = append(fields, "*")
+ }
+
+ schedule := &SpecSchedule{
+ Second: getField(fields[0], seconds),
+ Minute: getField(fields[1], minutes),
+ Hour: getField(fields[2], hours),
+ Dom: getField(fields[3], dom),
+ Month: getField(fields[4], months),
+ Dow: getField(fields[5], dow),
+ }
+
+ return schedule, nil
+}
+
+// getField returns an Int with the bits set representing all of the times that
+// the field represents. A "field" is a comma-separated list of "ranges".
+func getField(field string, r bounds) uint64 {
+ // list = range {"," range}
+ var bits uint64
+ ranges := strings.FieldsFunc(field, func(r rune) bool { return r == ',' })
+ for _, expr := range ranges {
+ bits |= getRange(expr, r)
+ }
+ return bits
+}
+
+// getRange returns the bits indicated by the given expression:
+// number | number "-" number [ "/" number ]
+func getRange(expr string, r bounds) uint64 {
+
+ var (
+ start, end, step uint
+ rangeAndStep = strings.Split(expr, "/")
+ lowAndHigh = strings.Split(rangeAndStep[0], "-")
+ singleDigit = len(lowAndHigh) == 1
+ )
+
+ var extra_star uint64
+ if lowAndHigh[0] == "*" || lowAndHigh[0] == "?" {
+ start = r.min
+ end = r.max
+ extra_star = starBit
+ } else {
+ start = parseIntOrName(lowAndHigh[0], r.names)
+ switch len(lowAndHigh) {
+ case 1:
+ end = start
+ case 2:
+ end = parseIntOrName(lowAndHigh[1], r.names)
+ default:
+ log.Panicf("Too many hyphens: %s", expr)
+ }
+ }
+
+ switch len(rangeAndStep) {
+ case 1:
+ step = 1
+ case 2:
+ step = mustParseInt(rangeAndStep[1])
+
+ // Special handling: "N/step" means "N-max/step".
+ if singleDigit {
+ end = r.max
+ }
+ default:
+ log.Panicf("Too many slashes: %s", expr)
+ }
+
+ if start < r.min {
+ log.Panicf("Beginning of range (%d) below minimum (%d): %s", start, r.min, expr)
+ }
+ if end > r.max {
+ log.Panicf("End of range (%d) above maximum (%d): %s", end, r.max, expr)
+ }
+ if start > end {
+ log.Panicf("Beginning of range (%d) beyond end of range (%d): %s", start, end, expr)
+ }
+
+ return getBits(start, end, step) | extra_star
+}
+
+// parseIntOrName returns the (possibly-named) integer contained in expr.
+func parseIntOrName(expr string, names map[string]uint) uint {
+ if names != nil {
+ if namedInt, ok := names[strings.ToLower(expr)]; ok {
+ return namedInt
+ }
+ }
+ return mustParseInt(expr)
+}
+
+// mustParseInt parses the given expression as an int or panics.
+func mustParseInt(expr string) uint {
+ num, err := strconv.Atoi(expr)
+ if err != nil {
+ log.Panicf("Failed to parse int from %s: %s", expr, err)
+ }
+ if num < 0 {
+ log.Panicf("Negative number (%d) not allowed: %s", num, expr)
+ }
+
+ return uint(num)
+}
+
+// getBits sets all bits in the range [min, max], modulo the given step size.
+func getBits(min, max, step uint) uint64 {
+ var bits uint64
+
+ // If step is 1, use shifts.
+ if step == 1 {
+ return ^(math.MaxUint64 << (max + 1)) & (math.MaxUint64 << min)
+ }
+
+ // Else, use a simple loop.
+ for i := min; i <= max; i += step {
+ bits |= 1 << i
+ }
+ return bits
+}
+
+// all returns all bits within the given bounds. (plus the star bit)
+func all(r bounds) uint64 {
+ return getBits(r.min, r.max, 1) | starBit
+}
+
+// parseDescriptor returns a pre-defined schedule for the expression, or panics
+// if none matches.
+func parseDescriptor(spec string) Schedule {
+ switch spec {
+ case "@yearly", "@annually":
+ return &SpecSchedule{
+ Second: 1 << seconds.min,
+ Minute: 1 << minutes.min,
+ Hour: 1 << hours.min,
+ Dom: 1 << dom.min,
+ Month: 1 << months.min,
+ Dow: all(dow),
+ }
+
+ case "@monthly":
+ return &SpecSchedule{
+ Second: 1 << seconds.min,
+ Minute: 1 << minutes.min,
+ Hour: 1 << hours.min,
+ Dom: 1 << dom.min,
+ Month: all(months),
+ Dow: all(dow),
+ }
+
+ case "@weekly":
+ return &SpecSchedule{
+ Second: 1 << seconds.min,
+ Minute: 1 << minutes.min,
+ Hour: 1 << hours.min,
+ Dom: all(dom),
+ Month: all(months),
+ Dow: 1 << dow.min,
+ }
+
+ case "@daily", "@midnight":
+ return &SpecSchedule{
+ Second: 1 << seconds.min,
+ Minute: 1 << minutes.min,
+ Hour: 1 << hours.min,
+ Dom: all(dom),
+ Month: all(months),
+ Dow: all(dow),
+ }
+
+ case "@hourly":
+ return &SpecSchedule{
+ Second: 1 << seconds.min,
+ Minute: 1 << minutes.min,
+ Hour: all(hours),
+ Dom: all(dom),
+ Month: all(months),
+ Dow: all(dow),
+ }
+ }
+
+ const every = "@every "
+ if strings.HasPrefix(spec, every) {
+ duration, err := time.ParseDuration(spec[len(every):])
+ if err != nil {
+ log.Panicf("Failed to parse duration %s: %s", spec, err)
+ }
+ return Every(duration)
+ }
+
+ log.Panicf("Unrecognized descriptor: %s", spec)
+ return nil
+}
diff --git a/modules/cron/parser_test.go b/modules/cron/parser_test.go
new file mode 100644
index 00000000..9050cf78
--- /dev/null
+++ b/modules/cron/parser_test.go
@@ -0,0 +1,117 @@
+package cron
+
+import (
+ "reflect"
+ "testing"
+ "time"
+)
+
+func TestRange(t *testing.T) {
+ ranges := []struct {
+ expr string
+ min, max uint
+ expected uint64
+ }{
+ {"5", 0, 7, 1 << 5},
+ {"0", 0, 7, 1 << 0},
+ {"7", 0, 7, 1 << 7},
+
+ {"5-5", 0, 7, 1 << 5},
+ {"5-6", 0, 7, 1<<5 | 1<<6},
+ {"5-7", 0, 7, 1<<5 | 1<<6 | 1<<7},
+
+ {"5-6/2", 0, 7, 1 << 5},
+ {"5-7/2", 0, 7, 1<<5 | 1<<7},
+ {"5-7/1", 0, 7, 1<<5 | 1<<6 | 1<<7},
+
+ {"*", 1, 3, 1<<1 | 1<<2 | 1<<3 | starBit},
+ {"*/2", 1, 3, 1<<1 | 1<<3 | starBit},
+ }
+
+ for _, c := range ranges {
+ actual := getRange(c.expr, bounds{c.min, c.max, nil})
+ if actual != c.expected {
+ t.Errorf("%s => (expected) %d != %d (actual)", c.expr, c.expected, actual)
+ }
+ }
+}
+
+func TestField(t *testing.T) {
+ fields := []struct {
+ expr string
+ min, max uint
+ expected uint64
+ }{
+ {"5", 1, 7, 1 << 5},
+ {"5,6", 1, 7, 1<<5 | 1<<6},
+ {"5,6,7", 1, 7, 1<<5 | 1<<6 | 1<<7},
+ {"1,5-7/2,3", 1, 7, 1<<1 | 1<<5 | 1<<7 | 1<<3},
+ }
+
+ for _, c := range fields {
+ actual := getField(c.expr, bounds{c.min, c.max, nil})
+ if actual != c.expected {
+ t.Errorf("%s => (expected) %d != %d (actual)", c.expr, c.expected, actual)
+ }
+ }
+}
+
+func TestBits(t *testing.T) {
+ allBits := []struct {
+ r bounds
+ expected uint64
+ }{
+ {minutes, 0xfffffffffffffff}, // 0-59: 60 ones
+ {hours, 0xffffff}, // 0-23: 24 ones
+ {dom, 0xfffffffe}, // 1-31: 31 ones, 1 zero
+ {months, 0x1ffe}, // 1-12: 12 ones, 1 zero
+ {dow, 0x7f}, // 0-6: 7 ones
+ }
+
+ for _, c := range allBits {
+ actual := all(c.r) // all() adds the starBit, so compensate for that..
+ if c.expected|starBit != actual {
+ t.Errorf("%d-%d/%d => (expected) %b != %b (actual)",
+ c.r.min, c.r.max, 1, c.expected|starBit, actual)
+ }
+ }
+
+ bits := []struct {
+ min, max, step uint
+ expected uint64
+ }{
+
+ {0, 0, 1, 0x1},
+ {1, 1, 1, 0x2},
+ {1, 5, 2, 0x2a}, // 101010
+ {1, 4, 2, 0xa}, // 1010
+ }
+
+ for _, c := range bits {
+ actual := getBits(c.min, c.max, c.step)
+ if c.expected != actual {
+ t.Errorf("%d-%d/%d => (expected) %b != %b (actual)",
+ c.min, c.max, c.step, c.expected, actual)
+ }
+ }
+}
+
+func TestSpecSchedule(t *testing.T) {
+ entries := []struct {
+ expr string
+ expected Schedule
+ }{
+ {"* 5 * * * *", &SpecSchedule{all(seconds), 1 << 5, all(hours), all(dom), all(months), all(dow)}},
+ {"@every 5m", ConstantDelaySchedule{time.Duration(5) * time.Minute}},
+ }
+
+ for _, c := range entries {
+ actual, err := Parse(c.expr)
+ if err != nil {
+ t.Error(err)
+ }
+ if !reflect.DeepEqual(actual, c.expected) {
+ t.Errorf("%s => (expected) %b != %b (actual)", c.expr, c.expected, actual)
+ }
+ }
+}
diff --git a/modules/cron/spec.go b/modules/cron/spec.go
new file mode 100644
index 00000000..cb374332
--- /dev/null
+++ b/modules/cron/spec.go
@@ -0,0 +1,161 @@
+package cron
+
+import (
+ "time"
+)
+
+// SpecSchedule specifies a duty cycle (to the second granularity), based on a
+// traditional crontab specification. It is computed initially and stored as bit sets.
+type SpecSchedule struct {
+ Second, Minute, Hour, Dom, Month, Dow uint64
+}
+
+// bounds provides a range of acceptable values (plus a map of name to value).
+type bounds struct {
+ min, max uint
+ names map[string]uint
+}
+
+// The bounds for each field.
+var (
+ seconds = bounds{0, 59, nil}
+ minutes = bounds{0, 59, nil}
+ hours = bounds{0, 23, nil}
+ dom = bounds{1, 31, nil}
+ months = bounds{1, 12, map[string]uint{
+ "jan": 1,
+ "feb": 2,
+ "mar": 3,
+ "apr": 4,
+ "may": 5,
+ "jun": 6,
+ "jul": 7,
+ "aug": 8,
+ "sep": 9,
+ "oct": 10,
+ "nov": 11,
+ "dec": 12,
+ }}
+ dow = bounds{0, 6, map[string]uint{
+ "sun": 0,
+ "mon": 1,
+ "tue": 2,
+ "wed": 3,
+ "thu": 4,
+ "fri": 5,
+ "sat": 6,
+ }}
+)
+
+const (
+ // Set the top bit if a star was included in the expression.
+ starBit = 1 << 63
+)
+
+// Next returns the next time this schedule is activated, greater than the given
+// time. If no time can be found to satisfy the schedule, return the zero time.
+func (s *SpecSchedule) Next(t time.Time) time.Time {
+ // General approach:
+ // For Month, Day, Hour, Minute, Second:
+ // Check if the time value matches. If yes, continue to the next field.
+ // If the field doesn't match the schedule, then increment the field until it matches.
+ // While incrementing the field, a wrap-around brings it back to the beginning
+ // of the field list (since it is necessary to re-verify previous field
+ // values)
+
+ // Start at the earliest possible time (the upcoming second).
+ t = t.Add(1*time.Second - time.Duration(t.Nanosecond())*time.Nanosecond)
+
+ // This flag indicates whether a field has been incremented.
+ added := false
+
+ // If no time is found within five years, return zero.
+ yearLimit := t.Year() + 5
+
+WRAP:
+ if t.Year() > yearLimit {
+ return time.Time{}
+ }
+
+ // Find the first applicable month.
+ // If it's this month, then do nothing.
+ for 1<<uint(t.Month())&s.Month == 0 {
+ // If we have to add a month, reset the other parts to 0.
+ if !added {
+ added = true
+ // Otherwise, set the date at the beginning (since the current time is irrelevant).
+ t = time.Date(t.Year(), t.Month(), 1, 0, 0, 0, 0, t.Location())
+ }
+ t = t.AddDate(0, 1, 0)
+
+ // Wrapped around.
+ if t.Month() == time.January {
+ goto WRAP
+ }
+ }
+
+ // Now get a day in that month.
+ for !dayMatches(s, t) {
+ if !added {
+ added = true
+ t = time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, t.Location())
+ }
+ t = t.AddDate(0, 0, 1)
+
+ if t.Day() == 1 {
+ goto WRAP
+ }
+ }
+
+ for 1<<uint(t.Hour())&s.Hour == 0 {
+ if !added {
+ added = true
+ t = time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), 0, 0, 0, t.Location())
+ }
+ t = t.Add(1 * time.Hour)
+
+ if t.Hour() == 0 {
+ goto WRAP
+ }
+ }
+
+ for 1<<uint(t.Minute())&s.Minute == 0 {
+ if !added {
+ added = true
+ t = time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), 0, 0, t.Location())
+ }
+ t = t.Add(1 * time.Minute)
+
+ if t.Minute() == 0 {
+ goto WRAP
+ }
+ }
+
+ for 1<<uint(t.Second())&s.Second == 0 {
+ if !added {
+ added = true
+ t = time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second(), 0, t.Location())
+ }
+ t = t.Add(1 * time.Second)
+
+ if t.Second() == 0 {
+ goto WRAP
+ }
+ }
+
+ return t
+}
+
+// dayMatches returns true if the schedule's day-of-week and day-of-month
+// restrictions are satisfied by the given time.
+func dayMatches(s *SpecSchedule, t time.Time) bool {
+ var (
+ domMatch bool = 1<<uint(t.Day())&s.Dom > 0
+ dowMatch bool = 1<<uint(t.Weekday())&s.Dow > 0
+ )
+
+ if s.Dom&starBit > 0 || s.Dow&starBit > 0 {
+ return domMatch && dowMatch
+ }
+ return domMatch || dowMatch
+}
diff --git a/modules/cron/spec_test.go b/modules/cron/spec_test.go
new file mode 100644
index 00000000..855d7983
--- /dev/null
+++ b/modules/cron/spec_test.go
@@ -0,0 +1,173 @@
+package cron
+
+import (
+ "testing"
+ "time"
+)
+
+func TestActivation(t *testing.T) {
+ tests := []struct {
+ time, spec string
+ expected bool
+ }{
+ // Every fifteen minutes.
+ {"Mon Jul 9 15:00 2012", "0 0/15 * * *", true},
+ {"Mon Jul 9 15:45 2012", "0 0/15 * * *", true},
+ {"Mon Jul 9 15:40 2012", "0 0/15 * * *", false},
+
+ // Every fifteen minutes, starting at 5 minutes.
+ {"Mon Jul 9 15:05 2012", "0 5/15 * * *", true},
+ {"Mon Jul 9 15:20 2012", "0 5/15 * * *", true},
+ {"Mon Jul 9 15:50 2012", "0 5/15 * * *", true},
+
+ // Named months
+ {"Sun Jul 15 15:00 2012", "0 0/15 * * Jul", true},
+ {"Sun Jul 15 15:00 2012", "0 0/15 * * Jun", false},
+
+ // Everything set.
+ {"Sun Jul 15 08:30 2012", "0 30 08 ? Jul Sun", true},
+ {"Sun Jul 15 08:30 2012", "0 30 08 15 Jul ?", true},
+ {"Mon Jul 16 08:30 2012", "0 30 08 ? Jul Sun", false},
+ {"Mon Jul 16 08:30 2012", "0 30 08 15 Jul ?", false},
+
+ // Predefined schedules
+ {"Mon Jul 9 15:00 2012", "@hourly", true},
+ {"Mon Jul 9 15:04 2012", "@hourly", false},
+ {"Mon Jul 9 15:00 2012", "@daily", false},
+ {"Mon Jul 9 00:00 2012", "@daily", true},
+ {"Mon Jul 9 00:00 2012", "@weekly", false},
+ {"Sun Jul 8 00:00 2012", "@weekly", true},
+ {"Sun Jul 8 01:00 2012", "@weekly", false},
+ {"Sun Jul 8 00:00 2012", "@monthly", false},
+ {"Sun Jul 1 00:00 2012", "@monthly", true},
+
+ // Test interaction of DOW and DOM.
+ // If both are specified, then only one needs to match.
+ {"Sun Jul 15 00:00 2012", "0 * * 1,15 * Sun", true},
+ {"Fri Jun 15 00:00 2012", "0 * * 1,15 * Sun", true},
+ {"Wed Aug 1 00:00 2012", "0 * * 1,15 * Sun", true},
+
+ // However, if one has a star, then both need to match.
+ {"Sun Jul 15 00:00 2012", "0 * * * * Mon", false},
+ {"Sun Jul 15 00:00 2012", "0 * * */10 * Sun", false},
+ {"Mon Jul 9 00:00 2012", "0 * * 1,15 * *", false},
+ {"Sun Jul 15 00:00 2012", "0 * * 1,15 * *", true},
+ {"Sun Jul 15 00:00 2012", "0 * * */2 * Sun", true},
+ }
+
+ for _, test := range tests {
+ sched, err := Parse(test.spec)
+ if err != nil {
+ t.Error(err)
+ continue
+ }
+ actual := sched.Next(getTime(test.time).Add(-1 * time.Second))
+ expected := getTime(test.time)
+ if test.expected && expected != actual || !test.expected && expected == actual {
+ t.Errorf("Fail evaluating %s on %s: (expected) %s != %s (actual)",
+ test.spec, test.time, expected, actual)
+ }
+ }
+}
+
+func TestNext(t *testing.T) {
+ runs := []struct {
+ time, spec string
+ expected string
+ }{
+ // Simple cases
+ {"Mon Jul 9 14:45 2012", "0 0/15 * * *", "Mon Jul 9 15:00 2012"},
+ {"Mon Jul 9 14:59 2012", "0 0/15 * * *", "Mon Jul 9 15:00 2012"},
+ {"Mon Jul 9 14:59:59 2012", "0 0/15 * * *", "Mon Jul 9 15:00 2012"},
+
+ // Wrap around hours
+ {"Mon Jul 9 15:45 2012", "0 20-35/15 * * *", "Mon Jul 9 16:20 2012"},
+
+ // Wrap around days
+ {"Mon Jul 9 23:46 2012", "0 */15 * * *", "Tue Jul 10 00:00 2012"},
+ {"Mon Jul 9 23:45 2012", "0 20-35/15 * * *", "Tue Jul 10 00:20 2012"},
+ {"Mon Jul 9 23:35:51 2012", "15/35 20-35/15 * * *", "Tue Jul 10 00:20:15 2012"},
+ {"Mon Jul 9 23:35:51 2012", "15/35 20-35/15 1/2 * *", "Tue Jul 10 01:20:15 2012"},
+ {"Mon Jul 9 23:35:51 2012", "15/35 20-35/15 10-12 * *", "Tue Jul 10 10:20:15 2012"},
+
+ {"Mon Jul 9 23:35:51 2012", "15/35 20-35/15 1/2 */2 * *", "Thu Jul 11 01:20:15 2012"},
+ {"Mon Jul 9 23:35:51 2012", "15/35 20-35/15 * 9-20 * *", "Wed Jul 10 00:20:15 2012"},
+ {"Mon Jul 9 23:35:51 2012", "15/35 20-35/15 * 9-20 Jul *", "Wed Jul 10 00:20:15 2012"},
+
+ // Wrap around months
+ {"Mon Jul 9 23:35 2012", "0 0 0 9 Apr-Oct ?", "Thu Aug 9 00:00 2012"},
+ {"Mon Jul 9 23:35 2012", "0 0 0 */5 Apr,Aug,Oct Mon", "Mon Aug 6 00:00 2012"},
+ {"Mon Jul 9 23:35 2012", "0 0 0 */5 Oct Mon", "Mon Oct 1 00:00 2012"},
+
+ // Wrap around years
+ {"Mon Jul 9 23:35 2012", "0 0 0 * Feb Mon", "Mon Feb 4 00:00 2013"},
+ {"Mon Jul 9 23:35 2012", "0 0 0 * Feb Mon/2", "Fri Feb 1 00:00 2013"},
+
+ // Wrap around minute, hour, day, month, and year
+ {"Mon Dec 31 23:59:45 2012", "0 * * * * *", "Tue Jan 1 00:00:00 2013"},
+
+ // Leap year
+ {"Mon Jul 9 23:35 2012", "0 0 0 29 Feb ?", "Mon Feb 29 00:00 2016"},
+
+ // Daylight savings time EST -> EDT
+ {"2012-03-11T00:00:00-0500", "0 30 2 11 Mar ?", "2013-03-11T02:30:00-0400"},
+
+ // Daylight savings time EDT -> EST
+ {"2012-11-04T00:00:00-0400", "0 30 2 04 Nov ?", "2012-11-04T02:30:00-0500"},
+ {"2012-11-04T01:45:00-0400", "0 30 1 04 Nov ?", "2012-11-04T01:30:00-0500"},
+
+ // Unsatisfiable
+ {"Mon Jul 9 23:35 2012", "0 0 0 30 Feb ?", ""},
+ {"Mon Jul 9 23:35 2012", "0 0 0 31 Apr ?", ""},
+ }
+
+ for _, c := range runs {
+ sched, err := Parse(c.spec)
+ if err != nil {
+ t.Error(err)
+ continue
+ }
+ actual := sched.Next(getTime(c.time))
+ expected := getTime(c.expected)
+ if !actual.Equal(expected) {
+ t.Errorf("%s, \"%s\": (expected) %v != %v (actual)", c.time, c.spec, expected, actual)
+ }
+ }
+}
+
+func TestErrors(t *testing.T) {
+ invalidSpecs := []string{
+ "xyz",
+ "60 0 * * *",
+ "0 60 * * *",
+ "0 0 * * XYZ",
+ }
+ for _, spec := range invalidSpecs {
+ _, err := Parse(spec)
+ if err == nil {
+ t.Error("expected an error parsing: ", spec)
+ }
+ }
+}
+
+func getTime(value string) time.Time {
+ if value == "" {
+ return time.Time{}
+ }
+ t, err := time.Parse("Mon Jan 2 15:04 2006", value)
+ if err != nil {
+ t, err = time.Parse("Mon Jan 2 15:04:05 2006", value)
+ if err != nil {
+ t, err = time.Parse("2006-01-02T15:04:05-0700", value)
+ if err != nil {
+ panic(err)
+ }
+ // Daylight savings time tests require location
+ if ny, err := time.LoadLocation("America/New_York"); err == nil {
+ t = t.In(ny)
+ }
+ }
+ }
+
+ return t
+}
diff --git a/modules/hooks/hooks.go b/modules/hooks/hooks.go
deleted file mode 100644
index 6ae4418b..00000000
--- a/modules/hooks/hooks.go
+++ /dev/null
@@ -1,95 +0,0 @@
-// Copyright 2014 The Gogs Authors. All rights reserved.
-// Use of this source code is governed by a MIT-style
-// license that can be found in the LICENSE file.
-
-package hooks
-
-import (
- "encoding/json"
- "time"
-
- "github.com/gogits/gogs/modules/httplib"
- "github.com/gogits/gogs/modules/log"
-)
-
-// Hook task types.
-const (
- HTT_WEBHOOK = iota + 1
- HTT_SERVICE
-)
-
-type PayloadAuthor struct {
- Name string `json:"name"`
- Email string `json:"email"`
-}
-
-type PayloadCommit struct {
- Id string `json:"id"`
- Message string `json:"message"`
- Url string `json:"url"`
- Author *PayloadAuthor `json:"author"`
-}
-
-type PayloadRepo struct {
- Id int64 `json:"id"`
- Name string `json:"name"`
- Url string `json:"url"`
- Description string `json:"description"`
- Website string `json:"website"`
- Watchers int `json:"watchers"`
- Owner *PayloadAuthor `json:"author"`
- Private bool `json:"private"`
-}
-
-// Payload represents payload information of hook.
-type Payload struct {
- Secret string `json:"secret"`
- Ref string `json:"ref"`
- Commits []*PayloadCommit `json:"commits"`
- Repo *PayloadRepo `json:"repository"`
- Pusher *PayloadAuthor `json:"pusher"`
-}
-
-// HookTask represents hook task.
-type HookTask struct {
- Type int
- Url string
- *Payload
- ContentType int
- IsSsl bool
-}
-
-var (
- taskQueue = make(chan *HookTask, 1000)
-)
-
-// AddHookTask adds new hook task to task queue.
-func AddHookTask(t *HookTask) {
- taskQueue <- t
-}
-
-func init() {
- go handleQueue()
-}
-
-func handleQueue() {
- for {
- select {
- case t := <-taskQueue:
- // Only support JSON now.
- data, err := json.MarshalIndent(t.Payload, "", "\t")
- if err != nil {
- log.Error("hooks.handleQueue(json): %v", err)
- continue
- }
-
- _, err = httplib.Post(t.Url).SetTimeout(5*time.Second, 5*time.Second).
- Body(data).Response()
- if err != nil {
- log.Error("hooks.handleQueue: Fail to deliver hook: %v", err)
- continue
- }
- log.Info("Hook delivered: %s", string(data))
- }
- }
-}
diff --git a/modules/log/log.go b/modules/log/log.go
index f83ec0ad..24f0442d 100644
--- a/modules/log/log.go
+++ b/modules/log/log.go
@@ -6,13 +6,16 @@
package log
import (
+ "fmt"
"os"
+ "path"
"github.com/gogits/logs"
)
var (
- loggers []*logs.BeeLogger
+ loggers []*logs.BeeLogger
+ GitLogger *logs.BeeLogger
)
func init() {
@@ -33,7 +36,15 @@ func NewLogger(bufLen int64, mode, config string) {
loggers = append(loggers, logger)
}
logger.SetLogFuncCallDepth(3)
- logger.SetLogger(mode, config)
+ if err := logger.SetLogger(mode, config); err != nil {
+ Fatal("Fail to set logger(%s): %v", mode, err)
+ }
+}
+
+func NewGitLogger(logPath string) {
+ os.MkdirAll(path.Dir(logPath), os.ModePerm)
+ GitLogger = logs.NewLogger(0)
+ GitLogger.SetLogger("file", fmt.Sprintf(`{"level":0,"filename":"%s","rotate":false}`, logPath))
}
func Trace(format string, v ...interface{}) {
diff --git a/modules/mailer/mail.go b/modules/mailer/mail.go
index 6e34439e..62e15cd7 100644
--- a/modules/mailer/mail.go
+++ b/modules/mailer/mail.go
@@ -17,6 +17,15 @@ import (
"github.com/gogits/gogs/modules/setting"
)
+const (
+ AUTH_ACTIVE base.TplName = "mail/auth/active"
+ AUTH_REGISTER_SUCCESS base.TplName = "mail/auth/register_success"
+ AUTH_RESET_PASSWORD base.TplName = "mail/auth/reset_passwd"
+
+ NOTIFY_COLLABORATOR base.TplName = "mail/notify/collaborator"
+ NOTIFY_MENTION base.TplName = "mail/notify/mention"
+)
+
// Create New mail message use MailFrom and MailUser
func NewMailMessageFrom(To []string, from, subject, body string) Message {
msg := NewHtmlMessage(To, from, subject, body)
@@ -26,10 +35,10 @@ func NewMailMessageFrom(To []string, from, subject, body string) Message {
// Create New mail message use MailFrom and MailUser
func NewMailMessage(To []string, subject, body string) Message {
- return NewMailMessageFrom(To, setting.MailService.User, subject, body)
+ return NewMailMessageFrom(To, setting.MailService.From, subject, body)
}
-func GetMailTmplData(user *models.User) map[interface{}]interface{} {
+func GetMailTmplData(u *models.User) map[interface{}]interface{} {
data := make(map[interface{}]interface{}, 10)
data["AppName"] = setting.AppName
data["AppVer"] = setting.AppVer
@@ -37,84 +46,84 @@ func GetMailTmplData(user *models.User) map[interface{}]interface{} {
data["AppLogo"] = setting.AppLogo
data["ActiveCodeLives"] = setting.Service.ActiveCodeLives / 60
data["ResetPwdCodeLives"] = setting.Service.ResetPwdCodeLives / 60
- if user != nil {
- data["User"] = user
+ if u != nil {
+ data["User"] = u
}
return data
}
// create a time limit code for user active
-func CreateUserActiveCode(user *models.User, startInf interface{}) string {
+func CreateUserActiveCode(u *models.User, startInf interface{}) string {
minutes := setting.Service.ActiveCodeLives
- data := base.ToStr(user.Id) + user.Email + user.LowerName + user.Passwd + user.Rands
+ data := base.ToStr(u.Id) + u.Email + u.LowerName + u.Passwd + u.Rands
code := base.CreateTimeLimitCode(data, minutes, startInf)
// add tail hex username
- code += hex.EncodeToString([]byte(user.LowerName))
+ code += hex.EncodeToString([]byte(u.LowerName))
return code
}
// Send user register mail with active code
-func SendRegisterMail(r *middleware.Render, user *models.User) {
- code := CreateUserActiveCode(user, nil)
+func SendRegisterMail(r *middleware.Render, u *models.User) {
+ code := CreateUserActiveCode(u, nil)
subject := "Register success, Welcome"
- data := GetMailTmplData(user)
+ data := GetMailTmplData(u)
data["Code"] = code
- body, err := r.HTMLString("mail/auth/register_success", data)
+ body, err := r.HTMLString(string(AUTH_REGISTER_SUCCESS), data)
if err != nil {
log.Error("mail.SendRegisterMail(fail to render): %v", err)
return
}
- msg := NewMailMessage([]string{user.Email}, subject, body)
- msg.Info = fmt.Sprintf("UID: %d, send register mail", user.Id)
+ msg := NewMailMessage([]string{u.Email}, subject, body)
+ msg.Info = fmt.Sprintf("UID: %d, send register mail", u.Id)
SendAsync(&msg)
}
// Send email verify active email.
-func SendActiveMail(r *middleware.Render, user *models.User) {
- code := CreateUserActiveCode(user, nil)
+func SendActiveMail(r *middleware.Render, u *models.User) {
+ code := CreateUserActiveCode(u, nil)
subject := "Verify your e-mail address"
- data := GetMailTmplData(user)
+ data := GetMailTmplData(u)
data["Code"] = code
- body, err := r.HTMLString("mail/auth/active_email", data)
+ body, err := r.HTMLString(string(AUTH_ACTIVE), data)
if err != nil {
log.Error("mail.SendActiveMail(fail to render): %v", err)
return
}
- msg := NewMailMessage([]string{user.Email}, subject, body)
- msg.Info = fmt.Sprintf("UID: %d, send active mail", user.Id)
+ msg := NewMailMessage([]string{u.Email}, subject, body)
+ msg.Info = fmt.Sprintf("UID: %d, send active mail", u.Id)
SendAsync(&msg)
}
// Send reset password email.
-func SendResetPasswdMail(r *middleware.Render, user *models.User) {
- code := CreateUserActiveCode(user, nil)
+func SendResetPasswdMail(r *middleware.Render, u *models.User) {
+ code := CreateUserActiveCode(u, nil)
subject := "Reset your password"
- data := GetMailTmplData(user)
+ data := GetMailTmplData(u)
data["Code"] = code
- body, err := r.HTMLString("mail/auth/reset_passwd", data)
+ body, err := r.HTMLString(string(AUTH_RESET_PASSWORD), data)
if err != nil {
log.Error("mail.SendResetPasswdMail(fail to render): %v", err)
return
}
- msg := NewMailMessage([]string{user.Email}, subject, body)
- msg.Info = fmt.Sprintf("UID: %d, send reset password email", user.Id)
+ msg := NewMailMessage([]string{u.Email}, subject, body)
+ msg.Info = fmt.Sprintf("UID: %d, send reset password email", u.Id)
SendAsync(&msg)
}
// SendIssueNotifyMail sends mail notification of all watchers of repository.
-func SendIssueNotifyMail(user, owner *models.User, repo *models.Repository, issue *models.Issue) ([]string, error) {
+func SendIssueNotifyMail(u, owner *models.User, repo *models.Repository, issue *models.Issue) ([]string, error) {
ws, err := models.GetWatchers(repo.Id)
if err != nil {
return nil, errors.New("mail.NotifyWatchers(GetWatchers): " + err.Error())
@@ -123,7 +132,7 @@ func SendIssueNotifyMail(user, owner *models.User, repo *models.Repository, issu
tos := make([]string, 0, len(ws))
for i := range ws {
uid := ws[i].UserId
- if user.Id == uid {
+ if u.Id == uid {
continue
}
u, err := models.GetUserById(uid)
@@ -141,14 +150,14 @@ func SendIssueNotifyMail(user, owner *models.User, repo *models.Repository, issu
content := fmt.Sprintf("%s<br>-<br> <a href=\"%s%s/%s/issues/%d\">View it on Gogs</a>.",
base.RenderSpecialLink([]byte(issue.Content), owner.Name+"/"+repo.Name),
setting.AppUrl, owner.Name, repo.Name, issue.Index)
- msg := NewMailMessageFrom(tos, user.Email, subject, content)
+ msg := NewMailMessageFrom(tos, u.Email, subject, content)
msg.Info = fmt.Sprintf("Subject: %s, send issue notify emails", subject)
SendAsync(&msg)
return tos, nil
}
// SendIssueMentionMail sends mail notification for who are mentioned in issue.
-func SendIssueMentionMail(r *middleware.Render, user, owner *models.User,
+func SendIssueMentionMail(r *middleware.Render, u, owner *models.User,
repo *models.Repository, issue *models.Issue, tos []string) error {
if len(tos) == 0 {
@@ -161,19 +170,19 @@ func SendIssueMentionMail(r *middleware.Render, user, owner *models.User,
data["IssueLink"] = fmt.Sprintf("%s/%s/issues/%d", owner.Name, repo.Name, issue.Index)
data["Subject"] = subject
- body, err := r.HTMLString("mail/notify/mention", data)
+ body, err := r.HTMLString(string(NOTIFY_MENTION), data)
if err != nil {
return fmt.Errorf("mail.SendIssueMentionMail(fail to render): %v", err)
}
- msg := NewMailMessageFrom(tos, user.Email, subject, body)
+ msg := NewMailMessageFrom(tos, u.Email, subject, body)
msg.Info = fmt.Sprintf("Subject: %s, send issue mention emails", subject)
SendAsync(&msg)
return nil
}
// SendCollaboratorMail sends mail notification to new collaborator.
-func SendCollaboratorMail(r *middleware.Render, user, owner *models.User,
+func SendCollaboratorMail(r *middleware.Render, u, owner *models.User,
repo *models.Repository) error {
subject := fmt.Sprintf("%s added you to %s", owner.Name, repo.Name)
@@ -182,13 +191,13 @@ func SendCollaboratorMail(r *middleware.Render, user, owner *models.User,
data["RepoLink"] = path.Join(owner.Name, repo.Name)
data["Subject"] = subject
- body, err := r.HTMLString("mail/notify/collaborator", data)
+ body, err := r.HTMLString(string(NOTIFY_COLLABORATOR), data)
if err != nil {
return fmt.Errorf("mail.SendCollaboratorMail(fail to render): %v", err)
}
- msg := NewMailMessage([]string{user.Email}, subject, body)
- msg.Info = fmt.Sprintf("UID: %d, send register mail", user.Id)
+ msg := NewMailMessage([]string{u.Email}, subject, body)
+ msg.Info = fmt.Sprintf("UID: %d, send register mail", u.Id)
SendAsync(&msg)
return nil
diff --git a/modules/middleware/context.go b/modules/middleware/context.go
index 8c837d08..45f0140a 100644
--- a/modules/middleware/context.go
+++ b/modules/middleware/context.go
@@ -104,12 +104,12 @@ func (ctx *Context) HasError() bool {
}
// HTML calls render.HTML underlying but reduce one argument.
-func (ctx *Context) HTML(status int, name string, htmlOpt ...HTMLOptions) {
- ctx.Render.HTML(status, name, ctx.Data, htmlOpt...)
+func (ctx *Context) HTML(status int, name base.TplName, htmlOpt ...HTMLOptions) {
+ ctx.Render.HTML(status, string(name), ctx.Data, htmlOpt...)
}
// RenderWithErr used for page has form validation but need to prompt error to users.
-func (ctx *Context) RenderWithErr(msg, tpl string, form auth.Form) {
+func (ctx *Context) RenderWithErr(msg string, tpl base.TplName, form auth.Form) {
if form != nil {
auth.AssignForm(form, ctx.Data)
}
@@ -133,7 +133,7 @@ func (ctx *Context) Handle(status int, title string, err error) {
case 500:
ctx.Data["Title"] = "Internal Server Error"
}
- ctx.HTML(status, fmt.Sprintf("status/%d", status))
+ ctx.HTML(status, base.TplName(fmt.Sprintf("status/%d", status)))
}
func (ctx *Context) Debug(msg string, args ...interface{}) {
@@ -358,7 +358,7 @@ func InitContext() martini.Handler {
})
// Get user from session if logined.
- user := auth.SignedInUser(ctx.Session)
+ user := auth.SignedInUser(ctx.req.Header, ctx.Session)
ctx.User = user
ctx.IsSigned = user != nil
diff --git a/modules/middleware/repo.go b/modules/middleware/repo.go
index c1acc827..0c640275 100644
--- a/modules/middleware/repo.go
+++ b/modules/middleware/repo.go
@@ -21,21 +21,17 @@ import (
func RepoAssignment(redirect bool, args ...bool) martini.Handler {
return func(ctx *Context, params martini.Params) {
- log.Trace(fmt.Sprint(args))
// valid brachname
var validBranch bool
// display bare quick start if it is a bare repo
var displayBare bool
if len(args) >= 1 {
- // Note: argument has wrong value in Go1.3 martini.
- // validBranch = args[0]
- validBranch = true
+ validBranch = args[0]
}
if len(args) >= 2 {
- // displayBare = args[1]
- displayBare = true
+ displayBare = args[1]
}
var (
@@ -48,9 +44,10 @@ func RepoAssignment(redirect bool, args ...bool) martini.Handler {
repoName := params["reponame"]
refName := params["branchname"]
+ // TODO: need more advanced onwership and access level check.
// Collaborators who have write access can be seen as owners.
if ctx.IsSigned {
- ctx.Repo.IsOwner, err = models.HasAccess(ctx.User.Name, userName+"/"+repoName, models.AU_WRITABLE)
+ ctx.Repo.IsOwner, err = models.HasAccess(ctx.User.Name, userName+"/"+repoName, models.WRITABLE)
if err != nil {
ctx.Handle(500, "RepoAssignment(HasAccess)", err)
return
@@ -111,7 +108,7 @@ func RepoAssignment(redirect bool, args ...bool) martini.Handler {
return
}
- hasAccess, err := models.HasAccess(ctx.User.Name, ctx.Repo.Owner.Name+"/"+repo.Name, models.AU_READABLE)
+ hasAccess, err := models.HasAccess(ctx.User.Name, ctx.Repo.Owner.Name+"/"+repo.Name, models.READABLE)
if err != nil {
ctx.Handle(500, "RepoAssignment(HasAccess)", err)
return
diff --git a/modules/process/manager.go b/modules/process/manager.go
new file mode 100644
index 00000000..173b2aa4
--- /dev/null
+++ b/modules/process/manager.go
@@ -0,0 +1,89 @@
+// Copyright 2014 The Gogs Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package process
+
+import (
+ "bytes"
+ "fmt"
+ "os/exec"
+ "time"
+
+ "github.com/gogits/gogs/modules/log"
+)
+
+// Process represents a working process inherit from Gogs.
+type Process struct {
+ Pid int64 // Process ID, not system one.
+ Description string
+ Start time.Time
+ Cmd *exec.Cmd
+}
+
+// List of existing processes.
+var (
+ curPid int64 = 1
+ Processes []*Process
+)
+
+// Add adds a existing process and returns its PID.
+func Add(desc string, cmd *exec.Cmd) int64 {
+ pid := curPid
+ Processes = append(Processes, &Process{
+ Pid: pid,
+ Description: desc,
+ Start: time.Now(),
+ Cmd: cmd,
+ })
+ curPid++
+ return pid
+}
+
+func ExecDir(dir, desc, cmdName string, args ...string) (string, string, error) {
+ bufOut := new(bytes.Buffer)
+ bufErr := new(bytes.Buffer)
+
+ cmd := exec.Command(cmdName, args...)
+ cmd.Dir = dir
+ cmd.Stdout = bufOut
+ cmd.Stderr = bufErr
+
+ pid := Add(desc, cmd)
+ err := cmd.Run()
+ if errKill := Kill(pid); errKill != nil {
+ log.Error("Exec: %v", pid, desc, errKill)
+ }
+ return bufOut.String(), bufErr.String(), err
+}
+
+// Exec starts executing a command and record its process.
+func Exec(desc, cmdName string, args ...string) (string, string, error) {
+ return ExecDir("", desc, cmdName, args...)
+}
+
+// Remove removes a process from list.
+func Remove(pid int64) {
+ for i, proc := range Processes {
+ if proc.Pid == pid {
+ Processes = append(Processes[:i], Processes[i+1:]...)
+ return
+ }
+ }
+}
+
+// Kill kills and removes a process from list.
+func Kill(pid int64) error {
+ for i, proc := range Processes {
+ if proc.Pid == pid {
+ if proc.Cmd.Process != nil && proc.Cmd.ProcessState != nil && !proc.Cmd.ProcessState.Exited() {
+ if err := proc.Cmd.Process.Kill(); err != nil {
+ return fmt.Errorf("fail to kill process(%d/%s): %v", proc.Pid, proc.Description, err)
+ }
+ }
+ Processes = append(Processes[:i], Processes[i+1:]...)
+ return nil
+ }
+ }
+ return nil
+}
diff --git a/modules/setting/setting.go b/modules/setting/setting.go
index 8cca57ef..f03aa8ae 100644
--- a/modules/setting/setting.go
+++ b/modules/setting/setting.go
@@ -47,11 +47,16 @@ var (
StaticRootPath string
// Security settings.
- InstallLock bool
- SecretKey string
- LogInRememberDays int
- CookieUserName string
- CookieRememberName string
+ InstallLock bool
+ SecretKey string
+ LogInRememberDays int
+ CookieUserName string
+ CookieRememberName string
+ ReverseProxyAuthUser string
+
+ // Webhook settings.
+ WebhookTaskInterval int
+ WebhookDeliverTimeout int
// Repository settings.
RepoRootPath string
@@ -86,8 +91,7 @@ var (
RunUser string
)
-// WorkDir returns absolute path of work directory.
-func WorkDir() (string, error) {
+func ExecPath() (string, error) {
file, err := exec.LookPath(os.Args[0])
if err != nil {
return "", err
@@ -96,7 +100,13 @@ func WorkDir() (string, error) {
if err != nil {
return "", err
}
- return path.Dir(strings.Replace(p, "\\", "/", -1)), nil
+ return p, nil
+}
+
+// WorkDir returns absolute path of work directory.
+func WorkDir() (string, error) {
+ execPath, err := ExecPath()
+ return path.Dir(strings.Replace(execPath, "\\", "/", -1)), err
}
// NewConfigContext initializes configuration context.
@@ -154,6 +164,7 @@ func NewConfigContext() {
LogInRememberDays = Cfg.MustInt("security", "LOGIN_REMEMBER_DAYS")
CookieUserName = Cfg.MustValue("security", "COOKIE_USERNAME")
CookieRememberName = Cfg.MustValue("security", "COOKIE_REMEMBER_NAME")
+ ReverseProxyAuthUser = Cfg.MustValue("security", "REVERSE_PROXY_AUTHENTICATION_USER", "X-WEBAUTH-USER")
RunUser = Cfg.MustValue("", "RUN_USER")
curUser := os.Getenv("USER")
@@ -171,6 +182,12 @@ func NewConfigContext() {
log.Fatal("Fail to get home directory: %v", err)
}
RepoRootPath = Cfg.MustValue("repository", "ROOT", filepath.Join(homeDir, "gogs-repositories"))
+ if !filepath.IsAbs(RepoRootPath) {
+ RepoRootPath = filepath.Join(workDir, RepoRootPath)
+ } else {
+ RepoRootPath = filepath.Clean(RepoRootPath)
+ }
+
if err = os.MkdirAll(RepoRootPath, os.ModePerm); err != nil {
log.Fatal("Fail to create repository root path(%s): %v", RepoRootPath, err)
}
@@ -182,14 +199,15 @@ func NewConfigContext() {
}
var Service struct {
- RegisterEmailConfirm bool
- DisableRegistration bool
- RequireSignInView bool
- EnableCacheAvatar bool
- NotifyMail bool
- ActiveCodeLives int
- ResetPwdCodeLives int
- LdapAuth bool
+ RegisterEmailConfirm bool
+ DisableRegistration bool
+ RequireSignInView bool
+ EnableCacheAvatar bool
+ EnableNotifyMail bool
+ EnableReverseProxyAuth bool
+ LdapAuth bool
+ ActiveCodeLives int
+ ResetPwdCodeLives int
}
func newService() {
@@ -198,6 +216,7 @@ func newService() {
Service.DisableRegistration = Cfg.MustBool("service", "DISABLE_REGISTRATION")
Service.RequireSignInView = Cfg.MustBool("service", "REQUIRE_SIGNIN_VIEW")
Service.EnableCacheAvatar = Cfg.MustBool("service", "ENABLE_CACHE_AVATAR")
+ Service.EnableReverseProxyAuth = Cfg.MustBool("service", "ENABLE_REVERSE_PROXY_AUTHENTICATION")
}
var logLevels = map[string]string{
@@ -246,20 +265,20 @@ func newLogService() {
Cfg.MustBool(modeSec, "DAILY_ROTATE", true),
Cfg.MustInt(modeSec, "MAX_DAYS", 7))
case "conn":
- LogConfigs[i] = fmt.Sprintf(`{"level":"%s","reconnectOnMsg":%v,"reconnect":%v,"net":"%s","addr":"%s"}`, level,
+ LogConfigs[i] = fmt.Sprintf(`{"level":%s,"reconnectOnMsg":%v,"reconnect":%v,"net":"%s","addr":"%s"}`, level,
Cfg.MustBool(modeSec, "RECONNECT_ON_MSG"),
Cfg.MustBool(modeSec, "RECONNECT"),
Cfg.MustValueRange(modeSec, "PROTOCOL", "tcp", []string{"tcp", "unix", "udp"}),
Cfg.MustValue(modeSec, "ADDR", ":7020"))
case "smtp":
- LogConfigs[i] = fmt.Sprintf(`{"level":"%s","username":"%s","password":"%s","host":"%s","sendTos":"%s","subject":"%s"}`, level,
+ LogConfigs[i] = fmt.Sprintf(`{"level":%s,"username":"%s","password":"%s","host":"%s","sendTos":"%s","subject":"%s"}`, level,
Cfg.MustValue(modeSec, "USER", "example@example.com"),
Cfg.MustValue(modeSec, "PASSWD", "******"),
Cfg.MustValue(modeSec, "HOST", "127.0.0.1:25"),
Cfg.MustValue(modeSec, "RECEIVERS", "[]"),
Cfg.MustValue(modeSec, "SUBJECT", "Diagnostic message from serve"))
case "database":
- LogConfigs[i] = fmt.Sprintf(`{"level":"%s","driver":"%s","conn":"%s"}`, level,
+ LogConfigs[i] = fmt.Sprintf(`{"level":%s,"driver":"%s","conn":"%s"}`, level,
Cfg.MustValue(modeSec, "DRIVER"),
Cfg.MustValue(modeSec, "CONN"))
}
@@ -330,6 +349,7 @@ func newSessionService() {
type Mailer struct {
Name string
Host string
+ From string
User, Passwd string
}
@@ -363,6 +383,7 @@ func newMailService() {
User: Cfg.MustValue("mailer", "USER"),
Passwd: Cfg.MustValue("mailer", "PASSWD"),
}
+ MailService.From = Cfg.MustValue("mailer", "FROM", MailService.User)
log.Info("Mail Service Enabled")
}
@@ -384,10 +405,15 @@ func newNotifyMailService() {
log.Warn("Notify Mail Service: Mail Service is not enabled")
return
}
- Service.NotifyMail = true
+ Service.EnableNotifyMail = true
log.Info("Notify Mail Service Enabled")
}
+func newWebhookService() {
+ WebhookTaskInterval = Cfg.MustInt("webhook", "TASK_INTERVAL", 1)
+ WebhookDeliverTimeout = Cfg.MustInt("webhook", "DELIVER_TIMEOUT", 5)
+}
+
func NewServices() {
newService()
newLogService()
@@ -396,4 +422,5 @@ func NewServices() {
newMailService()
newRegisterMailService()
newNotifyMailService()
+ newWebhookService()
}
diff --git a/modules/social/social.go b/modules/social/social.go
index 62f4d518..326a463f 100644
--- a/modules/social/social.go
+++ b/modules/social/social.go
@@ -120,7 +120,7 @@ type SocialGithub struct {
}
func (s *SocialGithub) Type() int {
- return models.OT_GITHUB
+ return int(models.GITHUB)
}
func newGitHubOauth(config *oauth.Config) {
@@ -174,7 +174,7 @@ type SocialGoogle struct {
}
func (s *SocialGoogle) Type() int {
- return models.OT_GOOGLE
+ return int(models.GOOGLE)
}
func newGoogleOauth(config *oauth.Config) {
@@ -229,7 +229,7 @@ type SocialTencent struct {
}
func (s *SocialTencent) Type() int {
- return models.OT_QQ
+ return int(models.QQ)
}
func newTencentOauth(config *oauth.Config) {
@@ -295,7 +295,7 @@ type SocialTwitter struct {
}
func (s *SocialTwitter) Type() int {
- return models.OT_TWITTER
+ return int(models.TWITTER)
}
func newTwitterOauth(config *oauth.Config) {
@@ -351,7 +351,7 @@ type SocialWeibo struct {
}
func (s *SocialWeibo) Type() int {
- return models.OT_WEIBO
+ return int(models.WEIBO)
}
func newWeiboOauth(config *oauth.Config) {