diff options
author | zhsso <zhssoge@gmail.com> | 2014-04-10 14:20:58 -0400 |
---|---|---|
committer | zhsso <zhssoge@gmail.com> | 2014-04-10 14:20:58 -0400 |
commit | a4cbe79567072befd96cf1b7eb319de1e2809ca3 (patch) | |
tree | 3dff34e53f34632532fd7a05e00e6f06b3e7fb82 /modules/base | |
parent | f3ed11d177d76bcb1850c6670c1516d25a66eb2c (diff) |
fix
Diffstat (limited to 'modules/base')
-rw-r--r-- | modules/base/base.go | 10 | ||||
-rw-r--r-- | modules/base/conf.go | 340 | ||||
-rw-r--r-- | modules/base/markdown.go | 168 | ||||
-rw-r--r-- | modules/base/template.go | 196 | ||||
-rw-r--r-- | modules/base/tool.go | 514 |
5 files changed, 1228 insertions, 0 deletions
diff --git a/modules/base/base.go b/modules/base/base.go new file mode 100644 index 00000000..7c08dcc5 --- /dev/null +++ b/modules/base/base.go @@ -0,0 +1,10 @@ +// 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 base + +type ( + // Type TmplData represents data in the templates. + TmplData map[string]interface{} +) diff --git a/modules/base/conf.go b/modules/base/conf.go new file mode 100644 index 00000000..871595e4 --- /dev/null +++ b/modules/base/conf.go @@ -0,0 +1,340 @@ +// 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 base + +import ( + "fmt" + "os" + "os/exec" + "path" + "path/filepath" + "strings" + + "github.com/Unknwon/com" + "github.com/Unknwon/goconfig" + qlog "github.com/qiniu/log" + + "github.com/gogits/cache" + "github.com/gogits/session" + + "github.com/gogits/gogs/modules/log" +) + +// Mailer represents mail service. +type Mailer struct { + Name string + Host string + User, Passwd string +} + +// Oauther represents oauth service. +type Oauther struct { + GitHub struct { + Enabled bool + ClientId, ClientSecret string + Scopes string + } +} + +var ( + AppVer string + AppName string + AppLogo string + AppUrl string + IsProdMode bool + Domain string + SecretKey string + RunUser string + RepoRootPath string + + InstallLock bool + + LogInRememberDays int + CookieUserName string + CookieRememberName string + + Cfg *goconfig.ConfigFile + MailService *Mailer + OauthService *Oauther + + LogMode string + LogConfig string + + Cache cache.Cache + CacheAdapter string + CacheConfig string + + SessionProvider string + SessionConfig *session.Config + SessionManager *session.Manager + + PictureService string +) + +var Service struct { + RegisterEmailConfirm bool + DisenableRegisteration bool + RequireSignInView bool + EnableCacheAvatar bool + NotifyMail bool + ActiveCodeLives int + ResetPwdCodeLives int +} + +func ExecDir() (string, error) { + file, err := exec.LookPath(os.Args[0]) + if err != nil { + return "", err + } + p, err := filepath.Abs(file) + if err != nil { + return "", err + } + return path.Dir(strings.Replace(p, "\\", "/", -1)), nil +} + +var logLevels = map[string]string{ + "Trace": "0", + "Debug": "1", + "Info": "2", + "Warn": "3", + "Error": "4", + "Critical": "5", +} + +func newService() { + Service.ActiveCodeLives = Cfg.MustInt("service", "ACTIVE_CODE_LIVE_MINUTES", 180) + Service.ResetPwdCodeLives = Cfg.MustInt("service", "RESET_PASSWD_CODE_LIVE_MINUTES", 180) + Service.DisenableRegisteration = Cfg.MustBool("service", "DISENABLE_REGISTERATION", false) + Service.RequireSignInView = Cfg.MustBool("service", "REQUIRE_SIGNIN_VIEW", false) + Service.EnableCacheAvatar = Cfg.MustBool("service", "ENABLE_CACHE_AVATAR", false) +} + +func newLogService() { + // Get and check log mode. + LogMode = Cfg.MustValue("log", "MODE", "console") + modeSec := "log." + LogMode + if _, err := Cfg.GetSection(modeSec); err != nil { + qlog.Fatalf("Unknown log mode: %s\n", LogMode) + } + + // Log level. + levelName := Cfg.MustValue("log."+LogMode, "LEVEL", "Trace") + level, ok := logLevels[levelName] + if !ok { + qlog.Fatalf("Unknown log level: %s\n", levelName) + } + + // Generate log configuration. + switch LogMode { + case "console": + LogConfig = fmt.Sprintf(`{"level":%s}`, level) + case "file": + logPath := Cfg.MustValue(modeSec, "FILE_NAME", "log/gogs.log") + os.MkdirAll(path.Dir(logPath), os.ModePerm) + LogConfig = fmt.Sprintf( + `{"level":%s,"filename":"%s","rotate":%v,"maxlines":%d,"maxsize":%d,"daily":%v,"maxdays":%d}`, level, + logPath, + Cfg.MustBool(modeSec, "LOG_ROTATE", true), + Cfg.MustInt(modeSec, "MAX_LINES", 1000000), + 1<<uint(Cfg.MustInt(modeSec, "MAX_SIZE_SHIFT", 28)), + Cfg.MustBool(modeSec, "DAILY_ROTATE", true), + Cfg.MustInt(modeSec, "MAX_DAYS", 7)) + case "conn": + LogConfig = fmt.Sprintf(`{"level":"%s","reconnectOnMsg":%v,"reconnect":%v,"net":"%s","addr":"%s"}`, level, + Cfg.MustBool(modeSec, "RECONNECT_ON_MSG", false), + Cfg.MustBool(modeSec, "RECONNECT", false), + Cfg.MustValue(modeSec, "PROTOCOL", "tcp"), + Cfg.MustValue(modeSec, "ADDR", ":7020")) + case "smtp": + LogConfig = 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": + LogConfig = fmt.Sprintf(`{"level":"%s","driver":"%s","conn":"%s"}`, level, + Cfg.MustValue(modeSec, "Driver"), + Cfg.MustValue(modeSec, "CONN")) + } + + log.Info("%s %s", AppName, AppVer) + log.NewLogger(Cfg.MustInt64("log", "BUFFER_LEN", 10000), LogMode, LogConfig) + log.Info("Log Mode: %s(%s)", strings.Title(LogMode), levelName) +} + +func newCacheService() { + CacheAdapter = Cfg.MustValue("cache", "ADAPTER", "memory") + + switch CacheAdapter { + case "memory": + CacheConfig = fmt.Sprintf(`{"interval":%d}`, Cfg.MustInt("cache", "INTERVAL", 60)) + case "redis", "memcache": + CacheConfig = fmt.Sprintf(`{"conn":"%s"}`, Cfg.MustValue("cache", "HOST")) + default: + qlog.Fatalf("Unknown cache adapter: %s\n", CacheAdapter) + } + + var err error + Cache, err = cache.NewCache(CacheAdapter, CacheConfig) + if err != nil { + qlog.Fatalf("Init cache system failed, adapter: %s, config: %s, %v\n", + CacheAdapter, CacheConfig, err) + } + + log.Info("Cache Service Enabled") +} + +func newSessionService() { + SessionProvider = Cfg.MustValue("session", "PROVIDER", "memory") + + SessionConfig = new(session.Config) + SessionConfig.ProviderConfig = Cfg.MustValue("session", "PROVIDER_CONFIG") + SessionConfig.CookieName = Cfg.MustValue("session", "COOKIE_NAME", "i_like_gogits") + SessionConfig.CookieSecure = Cfg.MustBool("session", "COOKIE_SECURE") + SessionConfig.EnableSetCookie = Cfg.MustBool("session", "ENABLE_SET_COOKIE", true) + SessionConfig.GcIntervalTime = Cfg.MustInt64("session", "GC_INTERVAL_TIME", 86400) + SessionConfig.SessionLifeTime = Cfg.MustInt64("session", "SESSION_LIFE_TIME", 86400) + SessionConfig.SessionIDHashFunc = Cfg.MustValue("session", "SESSION_ID_HASHFUNC", "sha1") + SessionConfig.SessionIDHashKey = Cfg.MustValue("session", "SESSION_ID_HASHKEY") + + if SessionProvider == "file" { + os.MkdirAll(path.Dir(SessionConfig.ProviderConfig), os.ModePerm) + } + + var err error + SessionManager, err = session.NewManager(SessionProvider, *SessionConfig) + if err != nil { + qlog.Fatalf("Init session system failed, provider: %s, %v\n", + SessionProvider, err) + } + + log.Info("Session Service Enabled") +} + +func newMailService() { + // Check mailer setting. + if !Cfg.MustBool("mailer", "ENABLED") { + return + } + + MailService = &Mailer{ + Name: Cfg.MustValue("mailer", "NAME", AppName), + Host: Cfg.MustValue("mailer", "HOST"), + User: Cfg.MustValue("mailer", "USER"), + Passwd: Cfg.MustValue("mailer", "PASSWD"), + } + log.Info("Mail Service Enabled") +} + +func newRegisterMailService() { + if !Cfg.MustBool("service", "REGISTER_EMAIL_CONFIRM") { + return + } else if MailService == nil { + log.Warn("Register Mail Service: Mail Service is not enabled") + return + } + Service.RegisterEmailConfirm = true + log.Info("Register Mail Service Enabled") +} + +func newNotifyMailService() { + if !Cfg.MustBool("service", "ENABLE_NOTIFY_MAIL") { + return + } else if MailService == nil { + log.Warn("Notify Mail Service: Mail Service is not enabled") + return + } + Service.NotifyMail = true + log.Info("Notify Mail Service Enabled") +} + +func newOauthService() { + if !Cfg.MustBool("oauth", "ENABLED") { + return + } + + OauthService = &Oauther{} + oauths := make([]string, 0, 10) + + // GitHub. + if Cfg.MustBool("oauth.github", "ENABLED") { + OauthService.GitHub.Enabled = true + OauthService.GitHub.ClientId = Cfg.MustValue("oauth.github", "CLIENT_ID") + OauthService.GitHub.ClientSecret = Cfg.MustValue("oauth.github", "CLIENT_SECRET") + OauthService.GitHub.Scopes = Cfg.MustValue("oauth.github", "SCOPES") + oauths = append(oauths, "GitHub") + } + + log.Info("Oauth Service Enabled %s", oauths) +} + +func NewConfigContext() { + //var err error + workDir, err := ExecDir() + if err != nil { + qlog.Fatalf("Fail to get work directory: %s\n", err) + } + + cfgPath := filepath.Join(workDir, "conf/app.ini") + Cfg, err = goconfig.LoadConfigFile(cfgPath) + if err != nil { + qlog.Fatalf("Cannot load config file(%s): %v\n", cfgPath, err) + } + Cfg.BlockMode = false + + cfgPath = filepath.Join(workDir, "custom/conf/app.ini") + if com.IsFile(cfgPath) { + if err = Cfg.AppendFiles(cfgPath); err != nil { + qlog.Fatalf("Cannot load config file(%s): %v\n", cfgPath, err) + } + } + + AppName = Cfg.MustValue("", "APP_NAME", "Gogs: Go Git Service") + AppLogo = Cfg.MustValue("", "APP_LOGO", "img/favicon.png") + AppUrl = Cfg.MustValue("server", "ROOT_URL") + Domain = Cfg.MustValue("server", "DOMAIN") + SecretKey = Cfg.MustValue("security", "SECRET_KEY") + + InstallLock = Cfg.MustBool("security", "INSTALL_LOCK", false) + + RunUser = Cfg.MustValue("", "RUN_USER") + curUser := os.Getenv("USERNAME") + if len(curUser) == 0 { + curUser = os.Getenv("USER") + } + // Does not check run user when the install lock is off. + if InstallLock && RunUser != curUser { + qlog.Fatalf("Expect user(%s) but current user is: %s\n", RunUser, curUser) + } + + LogInRememberDays = Cfg.MustInt("security", "LOGIN_REMEMBER_DAYS") + CookieUserName = Cfg.MustValue("security", "COOKIE_USERNAME") + CookieRememberName = Cfg.MustValue("security", "COOKIE_REMEMBER_NAME") + + PictureService = Cfg.MustValue("picture", "SERVICE") + + // Determine and create root git reposiroty path. + homeDir, err := com.HomeDir() + if err != nil { + qlog.Fatalf("Fail to get home directory): %v\n", err) + } + RepoRootPath = Cfg.MustValue("repository", "ROOT", filepath.Join(homeDir, "gogs-repositories")) + if err = os.MkdirAll(RepoRootPath, os.ModePerm); err != nil { + qlog.Fatalf("Fail to create RepoRootPath(%s): %v\n", RepoRootPath, err) + } +} + +func NewServices() { + newService() + newLogService() + newCacheService() + newSessionService() + newMailService() + newRegisterMailService() + newNotifyMailService() + newOauthService() +} diff --git a/modules/base/markdown.go b/modules/base/markdown.go new file mode 100644 index 00000000..cc180775 --- /dev/null +++ b/modules/base/markdown.go @@ -0,0 +1,168 @@ +// 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 base + +import ( + "bytes" + "fmt" + "net/http" + "path" + "path/filepath" + "regexp" + "strings" + + "github.com/gogits/gfm" +) + +func isletter(c byte) bool { + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') +} + +func isalnum(c byte) bool { + return (c >= '0' && c <= '9') || isletter(c) +} + +var validLinks = [][]byte{[]byte("http://"), []byte("https://"), []byte("ftp://"), []byte("mailto://")} + +func isLink(link []byte) bool { + for _, prefix := range validLinks { + if len(link) > len(prefix) && bytes.Equal(bytes.ToLower(link[:len(prefix)]), prefix) && isalnum(link[len(prefix)]) { + return true + } + } + + return false +} + +func IsMarkdownFile(name string) bool { + name = strings.ToLower(name) + switch filepath.Ext(name) { + case ".md", ".markdown", ".mdown": + return true + } + return false +} + +func IsTextFile(data []byte) (string, bool) { + contentType := http.DetectContentType(data) + if strings.Index(contentType, "text/") != -1 { + return contentType, true + } + return contentType, false +} + +func IsImageFile(data []byte) (string, bool) { + contentType := http.DetectContentType(data) + if strings.Index(contentType, "image/") != -1 { + return contentType, true + } + return contentType, false +} + +func IsReadmeFile(name string) bool { + name = strings.ToLower(name) + if len(name) < 6 { + return false + } + if name[:6] == "readme" { + return true + } + return false +} + +type CustomRender struct { + gfm.Renderer + urlPrefix string +} + +func (options *CustomRender) Link(out *bytes.Buffer, link []byte, title []byte, content []byte) { + if len(link) > 0 && !isLink(link) { + if link[0] == '#' { + // link = append([]byte(options.urlPrefix), link...) + } else { + link = []byte(path.Join(options.urlPrefix, string(link))) + } + } + + options.Renderer.Link(out, link, title, content) +} + +var ( + MentionPattern = regexp.MustCompile(`@[0-9a-zA-Z_]{1,}`) + commitPattern = regexp.MustCompile(`(\s|^)https?.*commit/[0-9a-zA-Z]+(#+[0-9a-zA-Z-]*)?`) + issueFullPattern = regexp.MustCompile(`(\s|^)https?.*issues/[0-9]+(#+[0-9a-zA-Z-]*)?`) + issueIndexPattern = regexp.MustCompile(`#[0-9]+`) +) + +func RenderSpecialLink(rawBytes []byte, urlPrefix string) []byte { + ms := MentionPattern.FindAll(rawBytes, -1) + for _, m := range ms { + rawBytes = bytes.Replace(rawBytes, m, + []byte(fmt.Sprintf(`<a href="/user/%s">%s</a>`, m[1:], m)), -1) + } + ms = commitPattern.FindAll(rawBytes, -1) + for _, m := range ms { + m = bytes.TrimSpace(m) + i := strings.Index(string(m), "commit/") + j := strings.Index(string(m), "#") + if j == -1 { + j = len(m) + } + rawBytes = bytes.Replace(rawBytes, m, []byte(fmt.Sprintf( + ` <code><a href="%s">%s</a></code>`, m, ShortSha(string(m[i+7:j])))), -1) + } + ms = issueFullPattern.FindAll(rawBytes, -1) + for _, m := range ms { + m = bytes.TrimSpace(m) + i := strings.Index(string(m), "issues/") + j := strings.Index(string(m), "#") + if j == -1 { + j = len(m) + } + rawBytes = bytes.Replace(rawBytes, m, []byte(fmt.Sprintf( + ` <a href="%s">#%s</a>`, m, ShortSha(string(m[i+7:j])))), -1) + } + ms = issueIndexPattern.FindAll(rawBytes, -1) + for _, m := range ms { + rawBytes = bytes.Replace(rawBytes, m, []byte(fmt.Sprintf( + `<a href="%s/issues/%s">%s</a>`, urlPrefix, m[1:], m)), -1) + } + return rawBytes +} + +func RenderMarkdown(rawBytes []byte, urlPrefix string) []byte { + body := RenderSpecialLink(rawBytes, urlPrefix) + // fmt.Println(string(body)) + htmlFlags := 0 + // htmlFlags |= gfm.HTML_USE_XHTML + // htmlFlags |= gfm.HTML_USE_SMARTYPANTS + // htmlFlags |= gfm.HTML_SMARTYPANTS_FRACTIONS + // htmlFlags |= gfm.HTML_SMARTYPANTS_LATEX_DASHES + // htmlFlags |= gfm.HTML_SKIP_HTML + htmlFlags |= gfm.HTML_SKIP_STYLE + htmlFlags |= gfm.HTML_SKIP_SCRIPT + htmlFlags |= gfm.HTML_GITHUB_BLOCKCODE + htmlFlags |= gfm.HTML_OMIT_CONTENTS + // htmlFlags |= gfm.HTML_COMPLETE_PAGE + renderer := &CustomRender{ + Renderer: gfm.HtmlRenderer(htmlFlags, "", ""), + urlPrefix: urlPrefix, + } + + // set up the parser + extensions := 0 + extensions |= gfm.EXTENSION_NO_INTRA_EMPHASIS + extensions |= gfm.EXTENSION_TABLES + extensions |= gfm.EXTENSION_FENCED_CODE + extensions |= gfm.EXTENSION_AUTOLINK + extensions |= gfm.EXTENSION_STRIKETHROUGH + extensions |= gfm.EXTENSION_HARD_LINE_BREAK + extensions |= gfm.EXTENSION_SPACE_HEADERS + extensions |= gfm.EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK + + body = gfm.Markdown(body, renderer, extensions) + // fmt.Println(string(body)) + return body +} diff --git a/modules/base/template.go b/modules/base/template.go new file mode 100644 index 00000000..5a42107c --- /dev/null +++ b/modules/base/template.go @@ -0,0 +1,196 @@ +// 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 base + +import ( + "bytes" + "container/list" + "encoding/json" + "fmt" + "html/template" + "strings" + "time" +) + +func Str2html(raw string) template.HTML { + return template.HTML(raw) +} + +func Range(l int) []int { + return make([]int, l) +} + +func List(l *list.List) chan interface{} { + e := l.Front() + c := make(chan interface{}) + go func() { + for e != nil { + c <- e.Value + e = e.Next() + } + close(c) + }() + return c +} + +func ShortSha(sha1 string) string { + if len(sha1) == 40 { + return sha1[:10] + } + return sha1 +} + +var mailDomains = map[string]string{ + "gmail.com": "gmail.com", +} + +var TemplateFuncs template.FuncMap = map[string]interface{}{ + "AppName": func() string { + return AppName + }, + "AppVer": func() string { + return AppVer + }, + "AppDomain": func() string { + return Domain + }, + "IsProdMode": func() bool { + return IsProdMode + }, + "LoadTimes": func(startTime time.Time) string { + return fmt.Sprint(time.Since(startTime).Nanoseconds()/1e6) + "ms" + }, + "AvatarLink": AvatarLink, + "str2html": Str2html, + "TimeSince": TimeSince, + "FileSize": FileSize, + "Subtract": Subtract, + "ActionIcon": ActionIcon, + "ActionDesc": ActionDesc, + "DateFormat": DateFormat, + "List": List, + "Mail2Domain": func(mail string) string { + if !strings.Contains(mail, "@") { + return "try.gogits.org" + } + + suffix := strings.SplitN(mail, "@", 2)[1] + domain, ok := mailDomains[suffix] + if !ok { + return "mail." + suffix + } + return domain + }, + "SubStr": func(str string, start, length int) string { + return str[start : start+length] + }, + "DiffTypeToStr": DiffTypeToStr, + "DiffLineTypeToStr": DiffLineTypeToStr, + "ShortSha": ShortSha, +} + +type Actioner interface { + GetOpType() int + GetActUserName() string + GetActEmail() string + GetRepoName() string + GetBranch() string + GetContent() string +} + +// ActionIcon accepts a int that represents action operation type +// and returns a icon class name. +func ActionIcon(opType int) string { + switch opType { + case 1: // Create repository. + return "plus-circle" + case 5: // Commit repository. + return "arrow-circle-o-right" + case 6: // Create issue. + return "exclamation-circle" + case 8: // Transfer repository. + return "share" + default: + return "invalid type" + } +} + +const ( + TPL_CREATE_REPO = `<a href="/user/%s">%s</a> created repository <a href="/%s">%s</a>` + TPL_COMMIT_REPO = `<a href="/user/%s">%s</a> pushed to <a href="/%s/src/%s">%s</a> at <a href="/%s">%s</a>%s` + TPL_COMMIT_REPO_LI = `<div><img src="%s?s=16" alt="user-avatar"/> <a href="/%s/commit/%s">%s</a> %s</div>` + TPL_CREATE_ISSUE = `<a href="/user/%s">%s</a> opened issue <a href="/%s/issues/%s">%s#%s</a> +<div><img src="%s?s=16" alt="user-avatar"/> %s</div>` + TPL_TRANSFER_REPO = `<a href="/user/%s">%s</a> transfered repository <code>%s</code> to <a href="/%s">%s</a>` +) + +type PushCommit struct { + Sha1 string + Message string + AuthorEmail string + AuthorName string +} + +type PushCommits struct { + Len int + Commits []*PushCommit +} + +// ActionDesc accepts int that represents action operation type +// and returns the description. +func ActionDesc(act Actioner) string { + actUserName := act.GetActUserName() + email := act.GetActEmail() + repoName := act.GetRepoName() + repoLink := actUserName + "/" + repoName + branch := act.GetBranch() + content := act.GetContent() + switch act.GetOpType() { + case 1: // Create repository. + return fmt.Sprintf(TPL_CREATE_REPO, actUserName, actUserName, repoLink, repoName) + case 5: // Commit repository. + var push *PushCommits + if err := json.Unmarshal([]byte(content), &push); err != nil { + return err.Error() + } + buf := bytes.NewBuffer([]byte("\n")) + for _, commit := range push.Commits { + buf.WriteString(fmt.Sprintf(TPL_COMMIT_REPO_LI, AvatarLink(commit.AuthorEmail), repoLink, commit.Sha1, commit.Sha1[:7], commit.Message) + "\n") + } + if push.Len > 3 { + buf.WriteString(fmt.Sprintf(`<div><a href="/%s/%s/commits/%s">%d other commits >></a></div>`, actUserName, repoName, branch, push.Len)) + } + return fmt.Sprintf(TPL_COMMIT_REPO, actUserName, actUserName, repoLink, branch, branch, repoLink, repoLink, + buf.String()) + case 6: // Create issue. + infos := strings.SplitN(content, "|", 2) + return fmt.Sprintf(TPL_CREATE_ISSUE, actUserName, actUserName, repoLink, infos[0], repoLink, infos[0], + AvatarLink(email), infos[1]) + case 8: // Transfer repository. + newRepoLink := content + "/" + repoName + return fmt.Sprintf(TPL_TRANSFER_REPO, actUserName, actUserName, repoLink, newRepoLink, newRepoLink) + default: + return "invalid type" + } +} + +func DiffTypeToStr(diffType int) string { + diffTypes := map[int]string{ + 1: "add", 2: "modify", 3: "del", + } + return diffTypes[diffType] +} + +func DiffLineTypeToStr(diffType int) string { + switch diffType { + case 2: + return "add" + case 3: + return "del" + case 4: + return "tag" + } + return "same" +} diff --git a/modules/base/tool.go b/modules/base/tool.go new file mode 100644 index 00000000..0f06b3e0 --- /dev/null +++ b/modules/base/tool.go @@ -0,0 +1,514 @@ +// 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 base + +import ( + "crypto/hmac" + "crypto/md5" + "crypto/rand" + "crypto/sha1" + "encoding/hex" + "fmt" + "hash" + "math" + "strconv" + "strings" + "time" +) + +// Encode string to md5 hex value +func EncodeMd5(str string) string { + m := md5.New() + m.Write([]byte(str)) + return hex.EncodeToString(m.Sum(nil)) +} + +// GetRandomString generate random string by specify chars. +func GetRandomString(n int, alphabets ...byte) string { + const alphanum = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + var bytes = make([]byte, n) + rand.Read(bytes) + for i, b := range bytes { + if len(alphabets) == 0 { + bytes[i] = alphanum[b%byte(len(alphanum))] + } else { + bytes[i] = alphabets[b%byte(len(alphabets))] + } + } + return string(bytes) +} + +// http://code.google.com/p/go/source/browse/pbkdf2/pbkdf2.go?repo=crypto +func PBKDF2(password, salt []byte, iter, keyLen int, h func() hash.Hash) []byte { + prf := hmac.New(h, password) + hashLen := prf.Size() + numBlocks := (keyLen + hashLen - 1) / hashLen + + var buf [4]byte + dk := make([]byte, 0, numBlocks*hashLen) + U := make([]byte, hashLen) + for block := 1; block <= numBlocks; block++ { + // N.B.: || means concatenation, ^ means XOR + // for each block T_i = U_1 ^ U_2 ^ ... ^ U_iter + // U_1 = PRF(password, salt || uint(i)) + prf.Reset() + prf.Write(salt) + buf[0] = byte(block >> 24) + buf[1] = byte(block >> 16) + buf[2] = byte(block >> 8) + buf[3] = byte(block) + prf.Write(buf[:4]) + dk = prf.Sum(dk) + T := dk[len(dk)-hashLen:] + copy(U, T) + + // U_n = PRF(password, U_(n-1)) + for n := 2; n <= iter; n++ { + prf.Reset() + prf.Write(U) + U = U[:0] + U = prf.Sum(U) + for x := range U { + T[x] ^= U[x] + } + } + } + return dk[:keyLen] +} + +// verify time limit code +func VerifyTimeLimitCode(data string, minutes int, code string) bool { + if len(code) <= 18 { + return false + } + + // split code + start := code[:12] + lives := code[12:18] + if d, err := StrTo(lives).Int(); err == nil { + minutes = d + } + + // right active code + retCode := CreateTimeLimitCode(data, minutes, start) + if retCode == code && minutes > 0 { + // check time is expired or not + before, _ := DateParse(start, "YmdHi") + now := time.Now() + if before.Add(time.Minute*time.Duration(minutes)).Unix() > now.Unix() { + return true + } + } + + return false +} + +const TimeLimitCodeLength = 12 + 6 + 40 + +// create a time limit code +// code format: 12 length date time string + 6 minutes string + 40 sha1 encoded string +func CreateTimeLimitCode(data string, minutes int, startInf interface{}) string { + format := "YmdHi" + + var start, end time.Time + var startStr, endStr string + + if startInf == nil { + // Use now time create code + start = time.Now() + startStr = DateFormat(start, format) + } else { + // use start string create code + startStr = startInf.(string) + start, _ = DateParse(startStr, format) + startStr = DateFormat(start, format) + } + + end = start.Add(time.Minute * time.Duration(minutes)) + endStr = DateFormat(end, format) + + // create sha1 encode string + sh := sha1.New() + sh.Write([]byte(data + SecretKey + startStr + endStr + ToStr(minutes))) + encoded := hex.EncodeToString(sh.Sum(nil)) + + code := fmt.Sprintf("%s%06d%s", startStr, minutes, encoded) + return code +} + +// AvatarLink returns avatar link by given e-mail. +func AvatarLink(email string) string { + if Service.EnableCacheAvatar { + return "/avatar/" + EncodeMd5(email) + } + return "http://1.gravatar.com/avatar/" + EncodeMd5(email) +} + +// Seconds-based time units +const ( + Minute = 60 + Hour = 60 * Minute + Day = 24 * Hour + Week = 7 * Day + Month = 30 * Day + Year = 12 * Month +) + +func computeTimeDiff(diff int64) (int64, string) { + diffStr := "" + switch { + case diff <= 0: + diff = 0 + diffStr = "now" + case diff < 2: + diff = 0 + diffStr = "1 second" + case diff < 1*Minute: + diffStr = fmt.Sprintf("%d seconds", diff) + diff = 0 + + case diff < 2*Minute: + diff -= 1 * Minute + diffStr = "1 minute" + case diff < 1*Hour: + diffStr = fmt.Sprintf("%d minutes", diff/Minute) + diff -= diff / Minute * Minute + + case diff < 2*Hour: + diff -= 1 * Hour + diffStr = "1 hour" + case diff < 1*Day: + diffStr = fmt.Sprintf("%d hours", diff/Hour) + diff -= diff / Hour * Hour + + case diff < 2*Day: + diff -= 1 * Day + diffStr = "1 day" + case diff < 1*Week: + diffStr = fmt.Sprintf("%d days", diff/Day) + diff -= diff / Day * Day + + case diff < 2*Week: + diff -= 1 * Week + diffStr = "1 week" + case diff < 1*Month: + diffStr = fmt.Sprintf("%d weeks", diff/Week) + diff -= diff / Week * Week + + case diff < 2*Month: + diff -= 1 * Month + diffStr = "1 month" + case diff < 1*Year: + diffStr = fmt.Sprintf("%d months", diff/Month) + diff -= diff / Month * Month + + case diff < 2*Year: + diff -= 1 * Year + diffStr = "1 year" + default: + diffStr = fmt.Sprintf("%d years", diff/Year) + diff = 0 + } + return diff, diffStr +} + +// TimeSincePro calculates the time interval and generate full user-friendly string. +func TimeSincePro(then time.Time) string { + now := time.Now() + diff := now.Unix() - then.Unix() + + if then.After(now) { + return "future" + } + + var timeStr, diffStr string + for { + if diff == 0 { + break + } + + diff, diffStr = computeTimeDiff(diff) + timeStr += ", " + diffStr + } + return strings.TrimPrefix(timeStr, ", ") +} + +// TimeSince calculates the time interval and generate user-friendly string. +func TimeSince(then time.Time) string { + now := time.Now() + + lbl := "ago" + diff := now.Unix() - then.Unix() + if then.After(now) { + lbl = "from now" + diff = then.Unix() - now.Unix() + } + + switch { + case diff <= 0: + return "now" + case diff <= 2: + return fmt.Sprintf("1 second %s", lbl) + case diff < 1*Minute: + return fmt.Sprintf("%d seconds %s", diff, lbl) + + case diff < 2*Minute: + return fmt.Sprintf("1 minute %s", lbl) + case diff < 1*Hour: + return fmt.Sprintf("%d minutes %s", diff/Minute, lbl) + + case diff < 2*Hour: + return fmt.Sprintf("1 hour %s", lbl) + case diff < 1*Day: + return fmt.Sprintf("%d hours %s", diff/Hour, lbl) + + case diff < 2*Day: + return fmt.Sprintf("1 day %s", lbl) + case diff < 1*Week: + return fmt.Sprintf("%d days %s", diff/Day, lbl) + + case diff < 2*Week: + return fmt.Sprintf("1 week %s", lbl) + case diff < 1*Month: + return fmt.Sprintf("%d weeks %s", diff/Week, lbl) + + case diff < 2*Month: + return fmt.Sprintf("1 month %s", lbl) + case diff < 1*Year: + return fmt.Sprintf("%d months %s", diff/Month, lbl) + + case diff < 2*Year: + return fmt.Sprintf("1 year %s", lbl) + default: + return fmt.Sprintf("%d years %s", diff/Year, lbl) + } + return then.String() +} + +const ( + Byte = 1 + KByte = Byte * 1024 + MByte = KByte * 1024 + GByte = MByte * 1024 + TByte = GByte * 1024 + PByte = TByte * 1024 + EByte = PByte * 1024 +) + +var bytesSizeTable = map[string]uint64{ + "b": Byte, + "kb": KByte, + "mb": MByte, + "gb": GByte, + "tb": TByte, + "pb": PByte, + "eb": EByte, +} + +func logn(n, b float64) float64 { + return math.Log(n) / math.Log(b) +} + +func humanateBytes(s uint64, base float64, sizes []string) string { + if s < 10 { + return fmt.Sprintf("%dB", s) + } + e := math.Floor(logn(float64(s), base)) + suffix := sizes[int(e)] + val := float64(s) / math.Pow(base, math.Floor(e)) + f := "%.0f" + if val < 10 { + f = "%.1f" + } + + return fmt.Sprintf(f+"%s", val, suffix) +} + +// FileSize calculates the file size and generate user-friendly string. +func FileSize(s int64) string { + sizes := []string{"B", "KB", "MB", "GB", "TB", "PB", "EB"} + return humanateBytes(uint64(s), 1024, sizes) +} + +// Subtract deals with subtraction of all types of number. +func Subtract(left interface{}, right interface{}) interface{} { + var rleft, rright int64 + var fleft, fright float64 + var isInt bool = true + switch left.(type) { + case int: + rleft = int64(left.(int)) + case int8: + rleft = int64(left.(int8)) + case int16: + rleft = int64(left.(int16)) + case int32: + rleft = int64(left.(int32)) + case int64: + rleft = left.(int64) + case float32: + fleft = float64(left.(float32)) + isInt = false + case float64: + fleft = left.(float64) + isInt = false + } + + switch right.(type) { + case int: + rright = int64(right.(int)) + case int8: + rright = int64(right.(int8)) + case int16: + rright = int64(right.(int16)) + case int32: + rright = int64(right.(int32)) + case int64: + rright = right.(int64) + case float32: + fright = float64(left.(float32)) + isInt = false + case float64: + fleft = left.(float64) + isInt = false + } + + if isInt { + return rleft - rright + } else { + return fleft + float64(rleft) - (fright + float64(rright)) + } +} + +// DateFormat pattern rules. +var datePatterns = []string{ + // year + "Y", "2006", // A full numeric representation of a year, 4 digits Examples: 1999 or 2003 + "y", "06", //A two digit representation of a year Examples: 99 or 03 + + // month + "m", "01", // Numeric representation of a month, with leading zeros 01 through 12 + "n", "1", // Numeric representation of a month, without leading zeros 1 through 12 + "M", "Jan", // A short textual representation of a month, three letters Jan through Dec + "F", "January", // A full textual representation of a month, such as January or March January through December + + // day + "d", "02", // Day of the month, 2 digits with leading zeros 01 to 31 + "j", "2", // Day of the month without leading zeros 1 to 31 + + // week + "D", "Mon", // A textual representation of a day, three letters Mon through Sun + "l", "Monday", // A full textual representation of the day of the week Sunday through Saturday + + // time + "g", "3", // 12-hour format of an hour without leading zeros 1 through 12 + "G", "15", // 24-hour format of an hour without leading zeros 0 through 23 + "h", "03", // 12-hour format of an hour with leading zeros 01 through 12 + "H", "15", // 24-hour format of an hour with leading zeros 00 through 23 + + "a", "pm", // Lowercase Ante meridiem and Post meridiem am or pm + "A", "PM", // Uppercase Ante meridiem and Post meridiem AM or PM + + "i", "04", // Minutes with leading zeros 00 to 59 + "s", "05", // Seconds, with leading zeros 00 through 59 + + // time zone + "T", "MST", + "P", "-07:00", + "O", "-0700", + + // RFC 2822 + "r", time.RFC1123Z, +} + +// Parse Date use PHP time format. +func DateParse(dateString, format string) (time.Time, error) { + replacer := strings.NewReplacer(datePatterns...) + format = replacer.Replace(format) + return time.ParseInLocation(format, dateString, time.Local) +} + +// Date takes a PHP like date func to Go's time format. +func DateFormat(t time.Time, format string) string { + replacer := strings.NewReplacer(datePatterns...) + format = replacer.Replace(format) + return t.Format(format) +} + +// convert string to specify type + +type StrTo string + +func (f StrTo) Exist() bool { + return string(f) != string(0x1E) +} + +func (f StrTo) Int() (int, error) { + v, err := strconv.ParseInt(f.String(), 10, 32) + return int(v), err +} + +func (f StrTo) Int64() (int64, error) { + v, err := strconv.ParseInt(f.String(), 10, 64) + return int64(v), err +} + +func (f StrTo) String() string { + if f.Exist() { + return string(f) + } + return "" +} + +// convert any type to string +func ToStr(value interface{}, args ...int) (s string) { + switch v := value.(type) { + case bool: + s = strconv.FormatBool(v) + case float32: + s = strconv.FormatFloat(float64(v), 'f', argInt(args).Get(0, -1), argInt(args).Get(1, 32)) + case float64: + s = strconv.FormatFloat(v, 'f', argInt(args).Get(0, -1), argInt(args).Get(1, 64)) + case int: + s = strconv.FormatInt(int64(v), argInt(args).Get(0, 10)) + case int8: + s = strconv.FormatInt(int64(v), argInt(args).Get(0, 10)) + case int16: + s = strconv.FormatInt(int64(v), argInt(args).Get(0, 10)) + case int32: + s = strconv.FormatInt(int64(v), argInt(args).Get(0, 10)) + case int64: + s = strconv.FormatInt(v, argInt(args).Get(0, 10)) + case uint: + s = strconv.FormatUint(uint64(v), argInt(args).Get(0, 10)) + case uint8: + s = strconv.FormatUint(uint64(v), argInt(args).Get(0, 10)) + case uint16: + s = strconv.FormatUint(uint64(v), argInt(args).Get(0, 10)) + case uint32: + s = strconv.FormatUint(uint64(v), argInt(args).Get(0, 10)) + case uint64: + s = strconv.FormatUint(v, argInt(args).Get(0, 10)) + case string: + s = v + case []byte: + s = string(v) + default: + s = fmt.Sprintf("%v", v) + } + return s +} + +type argInt []int + +func (a argInt) Get(i int, args ...int) (r int) { + if i >= 0 && i < len(a) { + r = a[i] + } + if len(args) > 0 { + r = args[0] + } + return +} |