// 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" "crypto/md5" "crypto/rand" "crypto/sha1" "encoding/hex" "encoding/json" "fmt" "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) } // 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 } 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 = `%s created repository %s` TPL_COMMIT_REPO = `%s pushed to %s at %s%s` TPL_COMMIT_REPO_LI = `
%s
to %s`
)
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(``, 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"
}