Browse Source

Updated.

v0.6
Mingcai SHEN 6 years ago
parent
commit
5ee0891dbb
21 changed files with 2238 additions and 0 deletions
  1. +177
    -0
      auth/admin.go
  2. +337
    -0
      auth/auth.go
  3. +179
    -0
      auth/cmd.go
  4. +90
    -0
      auth/models.go
  5. +1
    -0
      auth/types.go
  6. +184
    -0
      filesystem/filemanager.go
  7. +49
    -0
      filesystem/helps.go
  8. +90
    -0
      filesystem/storage.go
  9. +2
    -0
      go.mod
  10. +16
    -0
      handlers/file.go
  11. +19
    -0
      handlers/filemanager/filemng_test.go
  12. +138
    -0
      handlers/filemanager/fs.go
  13. +183
    -0
      handlers/filemanager/handlers.go
  14. +1
    -0
      handlers/filemanager/helpers.go
  15. +65
    -0
      handlers/filemanager/images.go
  16. +29
    -0
      handlers/filemanager/types.go
  17. +260
    -0
      handlers/filemanager/upload.go
  18. +123
    -0
      handlers/filemanager/videos.go
  19. +3
    -0
      handlers/upload.go
  20. +25
    -0
      mime/ffmpeg.go
  21. +267
    -0
      mime/mediainfo.go

+ 177
- 0
auth/admin.go View File

@ -0,0 +1,177 @@
package auth
import (
"fmt"
log "github.com/Sirupsen/logrus"
uuid "github.com/archsh/go.uuid"
"github.com/archsh/go.xql"
"github.com/archsh/go.xql/dialects/postgres"
"cygnux.net/kepler/misc"
"cygnux.net/kepler/restlet"
)
var (
groupTable = xql.DeclareTable(&Group{})
groupPermissionTable = xql.DeclareTable(&GroupPermission{})
groupUserTable = xql.DeclareTable(&GroupUser{})
userTable = xql.DeclareTable(&User{})
sessionTable = xql.DeclareTable(&Session{})
)
func Initialize(ctx restlet.TaskContext, params ...interface{}) error {
if e := postgres.CreateSchema(ctx.SQL(), ctx.Schema()); nil != e {
log.Fatalln("Create SCHEMA on database failed:>", e)
return e
}
if e := postgres.Initialize_HSTORE(ctx.SQL(), ctx.Schema()); nil != e {
log.Fatalln("Initialize hstore on database failed:>", e)
return e
}
if e := postgres.Initialize_UUID(ctx.SQL(), ctx.Schema()); nil != e {
log.Fatalln("Initialize uuid on database failed:>", e)
return e
}
session := xql.MakeSession(ctx.SQL(), "postgres", true)
defer session.Close()
tables := []xql.TableIdentified{
&Group{},
&GroupPermission{},
&User{},
&GroupUser{},
&Session{},
}
if e := session.Begin(); nil != e {
return e
}
for _, x := range tables {
log.Debugf("Initializing '%s' ...\n", x.TableName())
t := xql.DeclareTable(x, ctx.Schema())
if e := session.Create(t); nil != e {
log.Fatalf("Create table '%s' failed:> %s \n", t.TableName(), e)
return e
}
}
// Create Group
groupId, e := AddGroup(session, "administrators", "Administrators", "*")
if nil != e {
return e
}
// Create User
if _, e := AddUser(session, "admin", "nimda", "admin@hutaotv.cn", true, groupId); nil != e {
return e
}
if e := session.Commit(); nil != e {
session.Rollback()
return e
}
return nil
}
func DeleteGroup(session *xql.Session, names ...string) error {
for _, name := range names {
if _, e := session.Table(groupTable).Where("name", name).Delete(); nil != e {
return e
}
}
return nil
}
func AddGroup(session *xql.Session, name, desc string, perms ...string) (string, error) {
var groupId string
if n, e := session.Table(groupTable).Where("name", name).Count(); nil != e {
return "", e
} else if n > 0 {
if e := session.Table(groupTable, "id").Where("name", name).One().Scan(&groupId); nil != e {
return "", e
}
} else {
group := &Group{Name: name, Description: desc}
groupId = misc.MakeObjectId(group, group.Name)
group.Id = groupId
if _, e := session.Table(groupTable).Insert(group); nil != e {
return "", e
}
}
for _, perm := range perms {
if n, e := session.Table(groupPermissionTable).Where("group_id", groupId).Where("role", perm).Count(); nil != e {
return "", e
} else if n > 0 {
continue
} else if _, e := session.Table(groupPermissionTable).Insert(
&GroupPermission{GroupId: groupId, Role: perm}); nil != e {
return "", e
}
}
return groupId, nil
}
func GetGroup(session *xql.Session, q string) (string, error) {
var groupId string
qs := session.Table(groupTable, "id")
if _, e := uuid.FromString(q); nil != e {
qs = qs.Where("name", q)
} else {
qs = qs.Where("id", q)
}
if n, e := qs.Count(); nil != e {
return "", e
} else if n > 0 {
if e := qs.One().Scan(&groupId); nil != e {
return "", e
}
} else {
return "", fmt.Errorf("not found")
}
return groupId, nil
}
func DeleteUser(session *xql.Session, names ...string) error {
for _, name := range names {
if _, e := session.Table(userTable).Where("username", name).Delete(); nil != e {
return e
}
}
return nil
}
func AddUser(session *xql.Session, name, password, email string, activate bool, groups ...string) (string, error) {
user := &User{Username: name, Password: misc.MD5Hash(password), Email: email, Activate: activate}
if n, e := session.Table(userTable).Where("username", user.Username).Count(); nil != e {
return "", e
} else if n > 0 {
if e := session.Table(userTable, "id").Where("username", user.Username).One().Scan(&user.Id); nil != e {
return "", nil
}
} else {
user.Id = misc.MakeObjectId(user, user.Username)
if _, e := session.Table(userTable).Insert(user); nil != e {
return "", e
}
}
for _, g := range groups {
var gId string
if uid, e := uuid.FromString(g); nil != e {
gId, e = GetGroup(session, g)
if nil != e {
continue
}
} else {
gId = uid.String()
}
if _, e := session.Table(groupUserTable).Insert(
&GroupUser{GroupId: gId, UserId: user.Id}); nil != e {
return "", e
}
}
return user.Id, nil
}
func ModGroup(session *xql.Session, group string, opts ...string) error {
return fmt.Errorf("not implemented")
}
func ModUser(session *xql.Session, user string, opts ...string) error {
return fmt.Errorf("not implemented")
}

+ 337
- 0
auth/auth.go View File

@ -1 +1,338 @@
package auth
import (
"encoding/json"
"fmt"
"net/http"
"strings"
"time"
log "github.com/Sirupsen/logrus"
"github.com/archsh/go.xql"
"github.com/dgrijalva/jwt-go"
"cygnux.net/kepler/misc"
"cygnux.net/kepler/restlet"
)
// Login:
// Form :> { "username":"xxxxx", "password":"xxxxx","redirect":"xxxxxxx","token":xxxx}
// Password Hash: password = MD5(username+":"+token+MD5(password_input))
// Password Stored: MD5(orig_password)
// Token: epoch, NOW()-3MIN <= token <= NOW()+3MIN
const (
tokenMaxAge = 5 * time.Minute
sessionMaxAge = 1 * time.Hour
configAuthTokenMaxAgeKey = "service.auth.token_max_age"
configAuthSessionMaxAgeKey = "service.auth.session_max_age"
configAuthJwtSecretKey = "service.auth.jwt_secret"
configAutoCookieDomainKey = "service.auth.cookie_domain"
SessionCookieName = "MC_AUTH_SESS"
)
type loginRequest struct {
Username string `json:"username"`
Password string `json:"password"`
Redirect string `json:"redirect"`
Token int64 `json:"token"`
}
type SessionUser struct {
Username string `json:"u"`
Token string `json:"t"`
Roles []string `json:"roles"`
}
type sessionClaims struct {
jwt.StandardClaims
Username string `json:"u"`
Token string `json:"t"`
Roles []string `json:"roles"`
}
func GetUserIdByName(session *xql.Session, username string) (string, error) {
var id string
if n, e := session.Table(userTable, "id").Where("username", username).Count(); nil != e {
return "", e
} else if n < 1 {
return "", fmt.Errorf("not found user: %s", username)
} else if e := session.Table(userTable, "id").Where("username", username).One().Scan(&id); nil != e {
return "", e
} else {
return id, nil
}
}
func GetUserPermissions(session *xql.Session, usrId string) ([]string, error) {
var permissions []string
if rows, e := session.Table(groupPermissionTable).Filter(xql.QueryFilter{Field: fmt.Sprintf("group_id IN (SELECT group_id FROM %s WHERE user_id='%s')", groupUserTable.TableName(), usrId)}).All(); nil != e {
return nil, e
} else {
defer rows.Close()
for rows.Next() {
var p GroupPermission
if e := rows.Scan(&p); nil != e {
return nil, e
} else {
permissions = append(permissions, p.Role)
}
}
}
return permissions, nil
}
func makeToken(tk int64, username string, secret string) string {
ttk := fmt.Sprintf("%x", tk)
return ttk + "_" + username
}
func decodeToken(token string, secret string) (string, error) {
ss := strings.SplitN(token, "_", 2)
return ss[1], nil
}
func LoginHandler(ctx restlet.RequestContext, params restlet.Parameters, queries restlet.Parameters, data []byte) (*restlet.RestletResult, error) {
if len(data) < 30 {
log.Errorln("LoginHandler:> invalid post data:", string(data))
return restlet.Failure_Response(restlet.ERROR_BAD_REQUEST, "invalid request data")
}
var loginReq loginRequest
var tokenAge time.Duration
tokenAge = ctx.Config().GetDuration(configAuthTokenMaxAgeKey, tokenMaxAge)
if e := json.Unmarshal(data, &loginReq); nil != e {
log.Errorln("LoginHandler:> invalid json data:", e)
return restlet.Failure_Response(restlet.ERROR_BAD_REQUEST, "invalid request data")
}
if loginReq.Username == "" || loginReq.Password == "" {
log.Errorln("LoginHandler:> invalid post data: empty username or password")
return restlet.Failure_Response(restlet.ERROR_BAD_REQUEST, "username or password can not be empty")
}
if loginReq.Token <= 0 || loginReq.Token < time.Now().Add(-1 * tokenAge).Unix() || loginReq.Token > time.Now().Add(1 * tokenAge).Unix() {
log.Errorln("LoginHandler:> invalid post data: invalid token:", loginReq.Token)
return restlet.Failure_Response(restlet.ERROR_BAD_REQUEST, "invalid token")
}
session := xql.MakeSession(ctx.SQL(), "postgres")
defer session.Close()
var id, passwd string
var activate bool
if e := session.Table(userTable, "id", "password", "activate").Where("username", loginReq.Username).One().Scan(&id, &passwd, &activate); nil != e {
log.Errorln("LoginHandler:> can not find user:", loginReq.Username)
return restlet.Failure_Response(restlet.ERROR_OBJECT_NOT_FOUND, "can not find user")
} else if !activate {
log.Errorln("LoginHandler:> user disabled")
return restlet.Failure_Response(restlet.ERROR_RESOURCE_LOCKED, "user disabled")
} else {
np := misc.MD5Hash(loginReq.Username + ":" + fmt.Sprintf("%d", loginReq.Token) + passwd)
if np != loginReq.Password {
log.Errorln("LoginHandler:> invalid password:", np, loginReq.Password)
return restlet.Failure_Response(restlet.ERROR_RESOURCE_LOCKED, "incorrect credential")
} else {
maxAge := ctx.Config().GetDuration(configAuthSessionMaxAgeKey, sessionMaxAge)
secret := ctx.Config().GetString(configAuthJwtSecretKey, "mediacube")
t := time.Now().Add(maxAge)
jwtClaims := sessionClaims{
Username: loginReq.Username,
}
if p, e := GetUserPermissions(session, id); nil != e {
log.Errorln("LoginHandler:> get permission failed:", e)
return restlet.Failure_Response(restlet.FATAL_DB_READ_FAILED, fmt.Sprint(e))
} else {
jwtClaims.Roles = p
}
jwtClaims.ExpiresAt = t.Unix()
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwtClaims)
ss, e := token.SignedString([]byte(secret))
if nil != e {
log.Errorln("LoginHandler:> sign token failed:", e)
return restlet.Failure_Response(restlet.FATAL_DATA_ENCODE_FAILED, fmt.Sprint(e))
}
jwtClaims.Token = makeToken(loginReq.Token, loginReq.Username, secret)
domain := ctx.Config().GetString(configAutoCookieDomainKey, "*")
return &restlet.RestletResult{
Cookies: []*http.Cookie{
{
Name: SessionCookieName,
Value: ss,
Domain: domain,
Path: "/",
SameSite: http.SameSiteStrictMode,
MaxAge: int(maxAge / time.Second),
},
},
Code: restlet.SUCCESS_OK,
Data: jwtClaims}, nil
}
}
return &restlet.RestletResult{Code: restlet.SUCCESS_OK, Data: "OK"}, nil
}
func LoginTokenHandler(ctx restlet.RequestContext, params restlet.Parameters, queries restlet.Parameters, data []byte) (*restlet.RestletResult, error) {
secret := ctx.Config().GetString(configAuthJwtSecretKey, "mediacube")
var jwtClaims sessionClaims
if e := restlet.ExtractClaimsViaCookie(ctx.Request(), SessionCookieName, secret, &jwtClaims); nil == e {
log.Debugln("LoginTokenHandler:> Session:", jwtClaims)
}
return &restlet.RestletResult{
Code: restlet.SUCCESS_OK,
Data: map[string]int64{"token": time.Now().Unix()},
}, nil
}
func LogoutHandler(ctx restlet.RequestContext, params restlet.Parameters, queries restlet.Parameters, data []byte) (*restlet.RestletResult, error) {
domain := ctx.Config().GetString(configAutoCookieDomainKey, "*")
maxAge := ctx.Config().GetDuration(configAuthSessionMaxAgeKey, sessionMaxAge)
return &restlet.RestletResult{
Cookies: []*http.Cookie{{
Name: SessionCookieName,
Value: "",
Domain: domain,
Path: "/",
SameSite: http.SameSiteStrictMode,
MaxAge: int(maxAge / time.Second),
}},
Code: restlet.SUCCESS_OK,
Data: nil}, nil
}
type UserActionLog struct {
Username string `json:"u"`
Method string `json:"m"`
Request string `json:"r"`
Timestamp *time.Time `json:"t"`
}
func KeepAliveSessionHandler(ctx restlet.RestletContext, next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.Debugln("keepAliveSessionHandler:>", r.URL.String())
secret := ctx.Config().GetString(configAuthJwtSecretKey, "mediacube")
var jwtClaims sessionClaims
if e := restlet.ExtractClaimsViaCookie(r, SessionCookieName, secret, &jwtClaims); nil == e {
log.Debugln("keepAliveSessionHandler:> Got session")
maxAge := ctx.Config().GetDuration(configAuthSessionMaxAgeKey, sessionMaxAge)
t := time.Now().Add(maxAge)
jwtClaims.ExpiresAt = t.Unix()
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwtClaims)
ss, e := token.SignedString([]byte(secret))
if nil == e {
log.Debugln("keepAliveSessionHandler:> Session Updated")
domain := ctx.Config().GetString(configAutoCookieDomainKey, "*")
http.SetCookie(w, &http.Cookie{
Name: SessionCookieName,
Value: ss,
Domain: domain,
Path: "/",
SameSite: http.SameSiteStrictMode,
MaxAge: int(maxAge / time.Second),
}, )
}
t = time.Now()
action := UserActionLog{
Username: jwtClaims.Username,
Method: r.Method,
Request: r.URL.Path,
Timestamp: &t,
}
if e := ctx.Chan("admin.auth.log", &action); nil != e {
log.Errorln("KeepAliveSessionHandler:> chan msg failed:", e)
}
}
log.Debugln("keepAliveSessionHandler:> Done")
next.ServeHTTP(w, r)
})
}
func UserActionLogger(ctx restlet.TaskContext, objects ...interface{}) error {
for _, obj := range objects {
log.Infoln("UserActionLogger:>", obj)
}
return nil
}
func SessionHandler(ctx restlet.RequestContext, params restlet.Parameters, queries restlet.Parameters, data []byte) (*restlet.RestletResult, error) {
secret := ctx.Config().GetString(configAuthJwtSecretKey, "mediacube")
var jwtClaims sessionClaims
if e := restlet.ExtractClaimsViaCookie(ctx.Request(), SessionCookieName, secret, &jwtClaims); nil != e {
return restlet.Failure_Response(restlet.ERROR_FORBIDDEN, "no in session")
} else {
sessionMaxAge := ctx.Config().GetDuration(configAuthSessionMaxAgeKey, sessionMaxAge)
t := time.Now().Add(sessionMaxAge)
jwtClaims.ExpiresAt = t.Unix()
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwtClaims)
ss, e := token.SignedString([]byte(secret))
if nil != e {
return restlet.Failure_Response(restlet.FATAL_DATA_ENCODE_FAILED, fmt.Sprint(e))
}
domain := ctx.Config().GetString(configAutoCookieDomainKey, "*")
return &restlet.RestletResult{
Cookies: []*http.Cookie{{Name: SessionCookieName, Value: ss, Domain: domain}},
Code: restlet.SUCCESS_OK,
Data: jwtClaims}, nil
}
return &restlet.RestletResult{Code: restlet.SUCCESS_OK, Data: "OK"}, nil
}
func ProfileHandler(ctx restlet.RequestContext, params restlet.Parameters, queries restlet.Parameters, data []byte) (*restlet.RestletResult, error) {
secret := ctx.Config().GetString(configAuthJwtSecretKey, "mediacube")
var username string
var jwtClaims sessionClaims
if e := restlet.ExtractClaimsViaCookie(ctx.Request(), SessionCookieName, secret, &jwtClaims); nil != e {
return restlet.Failure_Response(restlet.ERROR_FORBIDDEN, "no in session")
} else {
username = jwtClaims.Username
}
session := xql.MakeSession(ctx.SQL(), "postgres")
defer session.Close()
if userId, e := GetUserIdByName(session, username); nil != e {
return restlet.Failure_Response(restlet.ERROR_NOT_FOUND, "can not get user")
} else if perms, e := GetUserPermissions(session, userId); nil != e {
return restlet.Failure_Response(restlet.FATAL_DB_READ_FAILED, "can not get perms")
} else {
jwtClaims.Username = username
jwtClaims.Roles = perms
//return &restlet.RestletResult{Code: restlet.SUCCESS_OK, Data: jwtClaims}, nil
}
sessionMaxAge := ctx.Config().GetDuration(configAuthSessionMaxAgeKey, sessionMaxAge)
t := time.Now().Add(sessionMaxAge)
jwtClaims.ExpiresAt = t.Unix()
//token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwtClaims)
//ss, e := token.SignedString([]byte(secret))
//if nil != e {
// log.Errorln("LoginHandler:> sign token failed:", e)
// return restlet.Failure_Response(restlet.FATAL_DATA_ENCODE_FAILED, fmt.Sprint(e))
//}
//jwtClaims.Token = makeToken(loginReq.Token, loginReq.Username, secret)
//domain := ctx.Config().GetString(configAutoCookieDomainKey, "*")
return &restlet.RestletResult{
//Cookies: []*http.Cookie{{Name: SessionCookieName, Value: ss, Domain: domain}},
Code: restlet.SUCCESS_OK,
Data: jwtClaims}, nil
//return &restlet.RestletResult{Code: restlet.SUCCESS_OK, Data: "OK"}, nil
}
func ChangePasswordHandler(ctx restlet.RequestContext, params restlet.Parameters, queries restlet.Parameters, data []byte) (*restlet.RestletResult, error) {
secret := ctx.Config().GetString(configAuthJwtSecretKey, "mediacube")
var jwtClaims sessionClaims
if e := restlet.ExtractClaimsViaCookie(ctx.Request(), SessionCookieName, secret, &jwtClaims); nil != e {
return restlet.Failure_Response(restlet.ERROR_FORBIDDEN, "no in session")
} else {
}
return &restlet.RestletResult{Code: restlet.SUCCESS_OK, Data: "OK"}, nil
}
func GetSessionUser(ctx restlet.RequestContext) (*SessionUser, bool) {
var jwtClaims sessionClaims
secret := ctx.Config().GetString(configAuthJwtSecretKey, "mediacube")
if e := restlet.ExtractClaimsViaCookie(ctx.Request(), SessionCookieName, secret, &jwtClaims); nil != e {
log.Debugln("GetSessionUser:> ExtractClaimsViaCookie failed", e)
return nil, false
} else {
return &SessionUser{
Username: jwtClaims.Username,
Token: jwtClaims.Token,
Roles: jwtClaims.Roles,
}, true
}
}

+ 179
- 0
auth/cmd.go View File

@ -0,0 +1,179 @@
package auth
import (
"fmt"
"os"
log "github.com/Sirupsen/logrus"
xql "github.com/archsh/go.xql"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"cygnux.net/kepler/config"
"cygnux.net/kepler/restlet"
)
func validateEmptyString(s string, k string) {
if s == "" {
fmt.Printf("'%s' can not be empty\n", k)
os.Exit(1)
}
}
var addGroupCmd = &cobra.Command{
Use: "add_group",
Short: "add Group",
Run: func(cmd *cobra.Command, args []string) {
name, _ := cmd.Flags().GetString("name")
validateEmptyString(name, "name")
desc, _ := cmd.Flags().GetString("desc")
validateEmptyString(desc, "desc")
roles, _ := cmd.Flags().GetStringArray("roles")
log.SetFormatter(&log.TextFormatter{})
cfg := config.MakeVConfig(viper.GetViper())
ctx, e := restlet.NewDummyContext(cfg)
if nil != e {
log.Errorln("> Setup Context failed:>", e)
os.Exit(1)
}
session := xql.MakeSession(ctx.SQL(), "postgres", true)
defer session.Close()
if e := session.Begin(); nil != e {
log.Errorln("> Session begin failed:>", e)
os.Exit(1)
}
if _, e := AddGroup(session, name, desc, roles...); nil != e {
log.Errorln("> Add group failed:>", e)
os.Exit(1)
}
if e := session.Commit(); nil != e {
session.Rollback()
log.Errorln("> Session commit failed:>", e)
os.Exit(1)
}
},
}
var delGroupCmd = &cobra.Command{
Use: "del_group",
Short: "Delete Group",
Run: func(cmd *cobra.Command, args []string) {
//name, _ := cmd.Flags().GetString("name")
//validateEmptyString(name, "name")
//desc, _ := cmd.Flags().GetString("desc")
//validateEmptyString(desc, "desc")
//roles, _ := cmd.Flags().GetStringArray("roles")
log.SetFormatter(&log.TextFormatter{})
cfg := config.MakeVConfig(viper.GetViper())
ctx, e := restlet.NewDummyContext(cfg)
if nil != e {
log.Errorln("> Setup Context failed:>", e)
os.Exit(1)
}
session := xql.MakeSession(ctx.SQL(), "postgres", true)
defer session.Close()
if e := session.Begin(); nil != e {
log.Errorln("> Session begin failed:>", e)
os.Exit(1)
}
if e := DeleteGroup(session, args...); nil != e {
log.Errorln("> Delete group failed:>", e)
os.Exit(1)
}
if e := session.Commit(); nil != e {
session.Rollback()
log.Errorln("> Session commit failed:>", e)
os.Exit(1)
}
},
}
var addUserCmd = &cobra.Command{
Use: "add_user",
Short: "add User",
Run: func(cmd *cobra.Command, args []string) {
name, _ := cmd.Flags().GetString("name")
validateEmptyString(name, "name")
password, _ := cmd.Flags().GetString("password")
validateEmptyString(password, "password")
email, _ := cmd.Flags().GetString("email")
validateEmptyString(email, "email")
groups, _ := cmd.Flags().GetStringArray("groups")
log.SetFormatter(&log.TextFormatter{})
cfg := config.MakeVConfig(viper.GetViper())
ctx, e := restlet.NewDummyContext(cfg)
if nil != e {
log.Errorln("> Setup Context failed:>", e)
os.Exit(1)
}
session := xql.MakeSession(ctx.SQL(), "postgres", true)
defer session.Close()
if e := session.Begin(); nil != e {
log.Errorln("> Session begin failed:>", e)
os.Exit(1)
}
if _, e := AddUser(session, name, password, email, true, groups...); nil != e {
log.Errorln("> Add group failed:>", e)
os.Exit(1)
}
if e := session.Commit(); nil != e {
session.Rollback()
log.Errorln("> Session commit failed:>", e)
os.Exit(1)
}
},
}
var delUserCmd = &cobra.Command{
Use: "del_user",
Short: "Delete User",
Run: func(cmd *cobra.Command, args []string) {
//name, _ := cmd.Flags().GetString("name")
//validateEmptyString(name, "name")
//password, _ := cmd.Flags().GetString("password")
//validateEmptyString(password, "password")
//email, _ := cmd.Flags().GetString("email")
//validateEmptyString(email, "email")
//groups, _ := cmd.Flags().GetStringArray("groups")
log.SetFormatter(&log.TextFormatter{})
cfg := config.MakeVConfig(viper.GetViper())
ctx, e := restlet.NewDummyContext(cfg)
if nil != e {
log.Errorln("> Setup Context failed:>", e)
os.Exit(1)
}
session := xql.MakeSession(ctx.SQL(), "postgres", true)
defer session.Close()
if e := session.Begin(); nil != e {
log.Errorln("> Session begin failed:>", e)
os.Exit(1)
}
if e := DeleteUser(session, args...); nil != e {
log.Errorln("> Delete user failed:>", e)
os.Exit(1)
}
if e := session.Commit(); nil != e {
session.Rollback()
log.Errorln("> Session commit failed:>", e)
os.Exit(1)
}
},
}
var Command = &cobra.Command{
Use: "auth",
Short: "User & Group managements",
}
func init() {
Command.AddCommand(addGroupCmd, addUserCmd, delGroupCmd, delUserCmd)
addGroupCmd.Flags().StringP("name", "N", "", "Name of Group")
addGroupCmd.Flags().StringP("desc", "C", "", "Comment of Group")
addGroupCmd.Flags().StringArrayP("roles", "R", nil, "Roles of Group")
addUserCmd.Flags().StringP("name", "N", "", "Name of User")
addUserCmd.Flags().StringP("password", "P", "", "Password of User")
addUserCmd.Flags().StringP("email", "M", "", "Email of User")
addUserCmd.Flags().StringArrayP("groups", "G", nil, "Groups of User")
}

+ 90
- 0
auth/models.go View File

@ -0,0 +1,90 @@
package auth
import (
"fmt"
"time"
)
const dbPrefix = ""
type Group struct {
Id string `json:"id" xql:"type=uuid,pk,default=uuid_generate_v4()"`
Name string `json:"name" xql:"size=32,unique,index"`
Description string `json:"description" xql:"size=256,nullable"`
Created *time.Time `json:"created,omitempty" xql:"type=timestamp,default=Now()"`
Updated *time.Time `json:"updated,omitempty" xql:"type=timestamp,default=Now()"`
}
func (Group) TableName() string {
return dbPrefix + "auth_groups"
}
type GroupPermission struct {
Id string `json:"id" xql:"type=uuid,pk,default=uuid_generate_v4()"`
GroupId string `json:"groupId" xql:"type=uuid"`
Role string `json:"role" xql:"size=32,index"`
Created *time.Time `json:"created,omitempty" xql:"type=timestamp,default=Now()"`
Updated *time.Time `json:"updated,omitempty" xql:"type=timestamp,default=Now()"`
}
func (GroupPermission) TableName() string {
return dbPrefix + "auth_group_permissions"
}
func (GroupPermission) Constraints() [][3]string {
return [][3]string{
{"fk", "group_id", fmt.Sprintf("%s (id) ON DELETE CASCADE", Group{}.TableName())},
}
}
type GroupUser struct {
Id string `json:"id" xql:"type=uuid,pk,default=uuid_generate_v4()"`
GroupId string `json:"groupId" xql:"name=group_id,type=uuid"`
UserId string `json:"userId" xql:"name=user_id,type=uuid"`
Created *time.Time `json:"created,omitempty" xql:"type=timestamp,default=Now()"`
Updated *time.Time `json:"updated,omitempty" xql:"type=timestamp,default=Now()"`
}
func (GroupUser) TableName() string {
return dbPrefix + "auth_group_users"
}
func (GroupUser) Constraints() [][3]string {
return [][3]string{
{"fk", "group_id", fmt.Sprintf("%s (id) ON DELETE CASCADE", Group{}.TableName())},
{"fk", "user_id", fmt.Sprintf("%s (id) ON DELETE CASCADE", User{}.TableName())},
}
}
type User struct {
Id string `json:"id" xql:"type=uuid,pk,default=uuid_generate_v4()"`
Username string `json:"username" xql:"size=32,unique,index"`
Password string `json:"password" xql:"size=64,default=''"`
Email string `json:"email" xql:"size=64,default=''"`
Contact string `json:"description" xql:"size=256,,default=''"`
Activate bool `json:"activate" xql:"default=false"`
Created *time.Time `json:"created,omitempty" xql:"type=timestamp,default=Now()"`
Updated *time.Time `json:"updated,omitempty" xql:"type=timestamp,default=Now()"`
}
func (User) TableName() string {
return dbPrefix + "auth_users"
}
type Session struct {
Id string `json:"id" xql:"type=uuid,pk,default=uuid_generate_v4()"`
UserId string `json:"userId" xql:"type=uuid"`
Login *time.Time `json:"login,omitempty" xql:"type=timestamp,default=Now()"`
Logout *time.Time `json:"login,omitempty" xql:"type=timestamp,default=NULL"`
Updated *time.Time `json:"updated,omitempty" xql:"type=timestamp,default=Now()"`
}
func (Session) TableName() string {
return dbPrefix + "auth_sessions"
}
func (Session) Constraints() [][3]string {
return [][3]string{
{"fk", "user_id", fmt.Sprintf("%s (id) ON DELETE CASCADE", User{}.TableName())},
}
}

+ 1
- 0
auth/types.go View File

@ -0,0 +1 @@
package auth

+ 184
- 0
filesystem/filemanager.go View File

@ -0,0 +1,184 @@
package filesystem
import (
"fmt"
"io"
"os"
"path"
"cygnux.net/kepler/config"
log "github.com/Sirupsen/logrus"
)
const (
KB int64 = 1024
MB = 1024 * KB
GB = 1024 * MB
TB = 1024 * GB
)
type FileObject struct {
Root string `json:"r"`
Prefix string `json:"p"`
Filename string `json:"f"`
Size int64 `json:"n"`
}
type FileManager struct {
root string
prefix string
minFreeSize int64
allowZeroFile bool
autoCreateDirectory bool
removeEmptyDirectory bool
defaultFilePerm os.FileMode
defaultDirPerm os.FileMode
}
func (fileManager *FileManager) Initialize(cfg config.Config) error {
if root := cfg.GetString("root"); root == "" {
return fmt.Errorf("root can not be empty")
} else if !path.IsAbs(root) {
return fmt.Errorf("root '%s' should be an albsolute path", root)
} else if rootInfo, e := os.Stat(root); nil != e {
return e
} else if !rootInfo.IsDir() {
return fmt.Errorf("root '%s' is not a directory", root)
} else {
fileManager.root = root
}
fileManager.prefix = cfg.GetString("prefix", "/")
fileManager.minFreeSize = cfg.GetInt64("min_free_size", 10*GB)
fileManager.allowZeroFile = cfg.GetBool("allow_zero_file", false)
fileManager.removeEmptyDirectory = cfg.GetBool("remove_empty_directory", false)
fileManager.autoCreateDirectory = cfg.GetBool("auto_create_directory", true)
fileManager.defaultDirPerm = os.FileMode(cfg.GetInt32("default_dir_perm", 0755))
fileManager.defaultFilePerm = os.FileMode(cfg.GetInt32("default_file_perm", 0644))
return nil
}
func (fileManager FileManager) Root() string {
return fileManager.root
}
func (fileManager FileManager) Prefix() string {
return fileManager.prefix
}
func (fileManager FileManager) SaveFile(src interface{}, destination string) (*FileObject, error) {
switch st := src.(type) {
case string:
return fileManager.copyFile(st, destination)
case io.Reader:
return fileManager.saveIOReader(st, destination)
case []byte:
return fileManager.saveBytes(st, destination)
default:
return nil, fmt.Errorf("unknow source type")
}
return nil, fmt.Errorf("unknow source type")
}
func (fileManager FileManager) copyFile(src string, destination string) (*FileObject, error) {
log.Debugln("FileManager.copyFile:>", src, destination)
if fp, e := os.Open(src); nil != e {
log.Errorln("FileManager.copyFile:> open src file failed:", e)
return nil, e
} else if out, e := fileManager.createFile(destination); nil != e {
log.Errorln("FileManager.copyFile:> create file failed:", e)
return nil, e
} else if n, e := io.Copy(out, fp); nil != e {
log.Errorln("FileManager.copyFile:> Copy failed:", e)
return nil, e
} else {
return &FileObject{
Root: fileManager.root,
Prefix: fileManager.prefix,
Filename: destination,
Size: n,
}, nil
}
}
func (fileManager FileManager) saveBytes(src []byte, destination string) (*FileObject, error) {
log.Debugln("FileManager.saveBytes:>", len(src), destination)
if len(src) < 1 && !fileManager.allowZeroFile {
log.Errorln("FileManager.saveBytes:> not allow to create zero file")
return nil, fmt.Errorf("saving zero bytes file is not allowed")
}
if out, e := fileManager.createFile(destination); nil != e {
log.Errorln("FileManager.saveBytes:> create file failed:", e)
return nil, e
} else if n, e := out.Write(src); nil != e {
log.Errorln("FileManager.saveBytes:> write file failed:", e)
return nil, e
} else {
return &FileObject{
Root: fileManager.root,
Prefix: fileManager.prefix,
Filename: destination,
Size: int64(n),
}, nil
}
}
func (fileManager FileManager) saveIOReader(src io.Reader, destination string) (*FileObject, error) {
log.Debugln("FileManager.saveIOReader:> ", src, destination)
if out, e := fileManager.createFile(destination); nil != e {
log.Errorln("FileManager.saveIOReader:> create file failed:", e)
return nil, e
} else if n, e := io.Copy(out, src); nil != e {
log.Errorln("FileManager.saveIOReader:> copy file failed:", e)
return nil, e
} else {
return &FileObject{
Root: fileManager.root,
Prefix: fileManager.prefix,
Filename: destination,
Size: n,
}, nil
}
}
func (fileManager FileManager) createFile(destination string) (*os.File, error) {
destination = path.Join(fileManager.root, destination)
if _, e := os.Stat(destination); nil == e {
log.Errorln("FileManager.createFile:> target file already exists!", destination)
return nil, fmt.Errorf("target file '%s' already exists", destination)
}
dir := path.Dir(destination)
if dirInfo, e := os.Stat(dir); nil != e {
if fileManager.autoCreateDirectory {
if e := fileManager.createDirectory(dir); nil != e {
log.Errorln("FileManager.createFile:> createDirector failed:", e)
return nil, e
}
} else {
log.Errorln("FileManager.createFile:> target path is not exists:", dir)
return nil, fmt.Errorf("target path '%s' is not exists", dir)
}
} else if !dirInfo.IsDir() {
log.Errorln("FileManager.createFile:> target path is not a dir:", dir)
return nil, fmt.Errorf("target path '%s' is already exists and it is not a directory", dir)
}
if fp, e := os.OpenFile(destination, os.O_CREATE|os.O_WRONLY, fileManager.defaultFilePerm); nil != e {
log.Errorln("FileManager.createFile:> OpenFile failed:", e)
return nil, e
} else {
return fp, nil
}
}
func (fileManager FileManager) createDirectory(path string) error {
if path == "" || path == "." || path == ".." {
return nil
}
return os.MkdirAll(path, fileManager.defaultDirPerm)
}
func (fileManager FileManager) DeleteFile(fileObj *FileObject) error {
if e := os.Remove(path.Join(fileObj.Root, fileObj.Filename)); nil != e {
return e
}
return nil
}

+ 49
- 0
filesystem/helps.go View File

@ -0,0 +1,49 @@
package filesystem
import (
"fmt"
"io"
"os"
"path"
log "github.com/Sirupsen/logrus"
)
func CopyFile(src string, destination string) error {
log.Debugln("FileManager.copyFile:>", src, destination)
if fp, e := os.Open(src); nil != e {
log.Errorln("FileManager.copyFile:> open src file failed:", e)
return e
} else if out, e := CreateFile(destination); nil != e {
log.Errorln("FileManager.copyFile:> create file failed:", e)
return e
} else if _, e := io.Copy(out, fp); nil != e {
log.Errorln("FileManager.copyFile:> Copy failed:", e)
return e
} else {
return nil
}
}
func CreateFile(destination string) (*os.File, error) {
//destination = path.Join(fileManager.root, destination)
if _, e := os.Stat(destination); nil == e {
log.Errorln("FileManager.createFile:> target file already exists!", destination)
return nil, fmt.Errorf("target file '%s' already exists", destination)
}
dir := path.Dir(destination)
if dirInfo, e := os.Stat(dir); nil != e {
if e := os.MkdirAll(dir, 0755); nil != e {
return nil, e
}
} else if !dirInfo.IsDir() {
log.Errorln("FileManager.createFile:> target path is not a dir:", dir)
return nil, fmt.Errorf("target path '%s' is already exists and it is not a directory", dir)
}
if fp, e := os.OpenFile(destination, os.O_CREATE|os.O_WRONLY, 0644); nil != e {
log.Errorln("FileManager.createFile:> OpenFile failed:", e)
return nil, e
} else {
return fp, nil
}
}

+ 90
- 0
filesystem/storage.go View File

@ -0,0 +1,90 @@
package filesystem
import (
"fmt"
"strings"
"cygnux.net/kepler/config"
)
var (
builtinFileManagers map[string]*FileManager
)
// Initialize init builtin file manager with corresponding configurations
func Initialize(cfg config.Config) error {
var fms = make(map[string]*FileManager)
var categories []string
if s := cfg.GetString("categories"); s != "" {
categories = strings.Split(s, ",")
}
if len(categories) > 0 {
for _, x := range categories {
if fm, e := NewFileManager(cfg.Sub(x)); nil != e {
return e
} else {
fms[x] = fm
}
}
} else {
if fm, e := NewFileManager(cfg); nil != e {
return e
} else {
fms["*"] = fm
}
}
builtinFileManagers = fms
return nil
}
func NewFileManager(cfg config.Config) (*FileManager, error) {
var fm = &FileManager{}
if e := fm.Initialize(cfg); nil != e {
return nil, e
}
return fm, nil
}
func Parameter(category string) (string, string, error) {
if nil == builtinFileManagers {
return "", "", fmt.Errorf("not initialized")
} else {
if fm, b := builtinFileManagers[category]; b {
return fm.Root(), fm.Prefix(), nil
} else if fm, b = builtinFileManagers["*"]; b {
return fm.Root(), fm.Prefix(), nil
} else {
return "", "", fmt.Errorf("no storage configured for category '%s'", category)
}
}
}
// Save to a file, with specific category and suffix or filename, source can be a: a filename in string, bytes in []byte, io reader in Reader
// suffix can be estimated if src is a filename.
func Save(src interface{}, category string, destination string) (*FileObject, error) {
if nil == builtinFileManagers {
panic("not initialized")
} else {
if fm, b := builtinFileManagers[category]; b {
return fm.SaveFile(src, destination)
} else if fm, b = builtinFileManagers["*"]; b {
return fm.SaveFile(src, destination)
} else {
return nil, fmt.Errorf("no storage configured for category '%s'", category)
}
}
}
// Delete a file by file manager
func Delete(fileObj *FileObject) error {
if nil == builtinFileManagers {
panic("not initialized")
} else {
for _, fm := range builtinFileManagers {
if fm.Root() == fileObj.Root && fm.Prefix() == fileObj.Prefix {
return fm.DeleteFile(fileObj)
}
}
return fmt.Errorf("can not find storage for (root=%s,prefix=%s,filename=%s)", fileObj.Root, fileObj.Prefix, fileObj.Filename)
}
}

+ 2
- 0
go.mod View File

@ -18,10 +18,12 @@ require (
github.com/magiconair/properties v1.8.0
github.com/mitchellh/mapstructure v1.1.2
github.com/mozillazg/go-pinyin v0.15.0
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646
github.com/nsqio/go-nsq v1.0.7
github.com/pelletier/go-toml v1.2.0
github.com/spf13/afero v1.2.1
github.com/spf13/cast v1.3.0
github.com/spf13/cobra v0.0.5
github.com/spf13/jwalterweatherman v1.1.0
github.com/spf13/pflag v1.0.3
github.com/spf13/viper v1.3.2

+ 16
- 0
handlers/file.go View File

@ -0,0 +1,16 @@
package handlers
import (
"net/http"
"cygnux.net/kepler/restlet"
)
func FileHandler(ctx restlet.RequestContext, params restlet.Parameters, w http.ResponseWriter, r *http.Request) {
filename := r.URL.Query().Get("filename")
if filename != "" {
http.ServeFile(w, r, filename)
} else {
http.Error(w, "invalid access", 403)
}
}

+ 19
- 0
handlers/filemanager/filemng_test.go View File

@ -0,0 +1,19 @@
package filemanager
import "testing"
func TestGetFiles(t *testing.T) {
root := "/home"
p := "/shenmc/Works"
if fl, e := GetFiles(root, p); nil != e {
t.Error(">>> Failure:", e)
} else {
t.Error(">> Root:", root)
t.Error(">> Path:", fl.Path)
t.Error(">> Up:", fl.Up)
t.Error(">> Amount:", fl.Amount)
for _, f := range fl.Files {
t.Error(" > ", f.Filename, f.Size, f.Ext, f.Type, f.Modified)
}
}
}

+ 138
- 0
handlers/filemanager/fs.go View File

@ -0,0 +1,138 @@
package filemanager
import (
"fmt"
"os"
"path"
"strings"
"github.com/Sirupsen/logrus"
"cygnux.net/kepler/misc"
)
func GetFiles(root, p string, filters ...string) (*FileList, error) {
var fileList FileList
var filterMap map[string]bool
p = strings.TrimLeft(p, "/")
if p == "" {
fileList.Up = false
} else {
fileList.Up = true
}
if len(filters) > 0 {
filterMap = make(map[string]bool, len(filters))
for _, x := range filters {
filterMap[strings.ToLower(x)] = true
}
}
fileList.Path = p
full := path.Join(root, p)
logrus.Debugln("GetFileInfo:>", root, p, full)
if s, e := os.Stat(full); nil != e {
return nil, e
} else if !s.IsDir() {
return nil, fmt.Errorf("'%s' is not a directory", p)
}
if fp, e := os.Open(full); nil != e {
return nil, e
} else {
defer fp.Close()
if finfos, e := fp.Readdir(-1); nil != e {
return nil, e
} else {
for _, f := range finfos {
var iii FileInfo
iii.Path = p
iii.Filename = f.Name()
iii.Modified = f.ModTime()
if f.IsDir() {
iii.Type = T_DIRECTORY
fileList.Files = append(fileList.Files, &iii)
} else {
iii.Type = T_FILE
iii.Size = f.Size()
iii.Ext = path.Ext(f.Name())
if filterMap != nil {
if _, b := filterMap[strings.ToLower(iii.Ext)]; !b {
continue
}
}
fileList.Files = append(fileList.Files, &iii)
}
fileList.Amount += 1
}
}
}
return &fileList, nil
}
func GetFileInfo(root, filename string, withMd5 bool) (*FileInfo, error) {
full := path.Join(root, filename)
logrus.Debugln("GetFileInfo:>", root, filename, full)
var fileInfo FileInfo
if s, e := os.Stat(full); nil != e {
return nil, e
} else if s.IsDir() {
return nil, fmt.Errorf("target file is a directory")
} else {
fileInfo = FileInfo{
Path: path.Dir(filename),
Filename: s.Name(),
Modified: s.ModTime(),
Type: T_FILE,
Size: s.Size(),
}
if withMd5 {
if s, e := misc.FileMD5(full); nil != e {
return nil, e
} else {
fileInfo.MD5 = s
}
}
}
return &fileInfo, nil
}
func DetectPathType(root, p string) (*FileInfo, error) {
full := path.Join(root, p)
logrus.Debugln("DetectPathType:>", root, p, full)
if s, e := os.Stat(full); nil != e {
return nil, e
} else if s.IsDir() {
return &FileInfo{
Path: p,
Filename: s.Name(),
Modified: s.ModTime(),
Type: T_DIRECTORY,
}, nil
} else {
return &FileInfo{
Path: p,
Filename: s.Name(),
Modified: s.ModTime(),
Type: T_FILE,
}, nil
}
}
func DeleteFile(root, filename string) error {
full := path.Join(root, filename)
if s, e := os.Stat(full); nil != e {
return e
} else if s.IsDir() {
return fmt.Errorf("target file is a directory")
} else {
return os.Remove(full)
}
}
func DeleteAll(root, p string) error {
full := path.Join(root, p)
if _, e := os.Stat(full); nil != e {
return e
} else {
return os.RemoveAll(full)
}
}

+ 183
- 0
handlers/filemanager/handlers.go View File

@ -0,0 +1,183 @@
package filemanager
import (
"net/http"
"os"
"path"
"strings"
"time"
log "github.com/Sirupsen/logrus"
"cygnux.net/kepler/restlet"
)
func getFile(root, temp, filename string, opts *ResumbleOptions, w http.ResponseWriter, r *http.Request, filters []string, rawReader, thumbReader FileReader) {
if opts.Filename != "" {
filename = path.Join(filename, opts.Filename)
}
log.Debugln("getFile:>", root, filename)
if fi, e := DetectPathType(root, filename); nil != e {
if opts.TotalChunks > 1 && opts.CurrentChunkSize > 0 && opts.ChunkNumber > 0 {
if fi, e := CheckTempFile(temp, filename, opts); nil != e {
JsonResponse(w, 404, "file not found")
} else {
JsonResponse(w, 200, fi)
}
} else {
JsonResponse(w, 404, "file not found")
}
} else if fi.Type == T_DIRECTORY {
if files, e := GetFiles(root, filename, filters...); nil != e {
JsonResponse(w, 403, "can not open")
} else {
JsonResponse(w, 200, files)
}
} else if fi.Type == T_FILE {
var withMd5 bool
if md5 := r.URL.Query().Get("md5"); md5 == "y" {
withMd5 = true
}
if raw := r.URL.Query().Get("raw"); raw == "y" {
if rawReader != nil {
if bs, ct, e := rawReader(path.Join(root, filename)); nil != e {
log.Errorln("getFile:> Read raw failed:", e)
JsonResponse(w, 403, "can not open")
} else {
w.Header().Set("Content-Type", ct)
if _, e := w.Write(bs); nil != e {
log.Errorln("getFile:> Write failed:", e)
}
}
} else {
http.ServeFile(w, r, path.Join(root, filename))
}
} else if thumb := r.URL.Query().Get("thumb"); thumb == "y" {
if thumbReader != nil {
if bs, ct, e := thumbReader(path.Join(root, filename)); nil != e {
log.Errorln("getFile:> Read thumb failed:", e)
JsonResponse(w, 403, "can not open")
} else {
w.Header().Set("Content-Type", ct)
if _, e := w.Write(bs); nil != e {
log.Errorln("getFile:> Write failed:", e)
}
}
} else {
http.ServeFile(w, r, path.Join(root, filename))
}
} else if file, e := GetFileInfo(root, filename, withMd5); nil != e {
JsonResponse(w, 403, "can not open")
} else {
JsonResponse(w, 200, file)
}
} else {
JsonResponse(w, 403, e.Error())
return
}
}
func postFile(root, temp, filename string, opts *ResumbleOptions, w http.ResponseWriter, r *http.Request) {
if mkdir := r.URL.Query().Get("mkdir"); mkdir == "y" {
fullPath := path.Join(root, filename)
if e := os.Mkdir(fullPath, 0755); nil != e {
JsonResponse(w, 403, e.Error())
} else {
JsonResponse(w, 200, FileInfo{
Path: path.Dir(filename),
Filename: path.Base(filename),
Type: T_DIRECTORY,
Size: 0,
Modified: time.Now(),
})
}
} else if fi, e := UploadFile(root, temp, filename, opts.Filename, opts, r); nil != e {
JsonResponse(w, 400, e.Error())
} else {
JsonResponse(w, 200, fi)
}
}
func deleteFile(root, temp, filename string, opts *ResumbleOptions, w http.ResponseWriter, r *http.Request) {
if fi, e := DetectPathType(root, filename); nil != e {
JsonResponse(w, 404, "file not found")
} else if fi.Type == T_DIRECTORY {
if e := DeleteAll(root, filename); nil != e {
JsonResponse(w, 403, e.Error())
} else {
JsonResponse(w, 204, "OK")
}
} else if fi.Type == T_FILE {
if e := DeleteFile(root, filename); nil != e {
JsonResponse(w, 403, e.Error())
} else {
JsonResponse(w, 204, "OK")
}
} else {
JsonResponse(w, 403, e.Error())
return
}
}
func MakeFileManagerHandler(configPrefix string, rawReader, thumbReader FileReader) func(ctx restlet.RequestContext, params restlet.Parameters, w http.ResponseWriter, r *http.Request) {
var ff = func(ctx restlet.RequestContext, params restlet.Parameters, w http.ResponseWriter, r *http.Request) {
root := ctx.Config().GetString(configPrefix + ".root")
temp := ctx.Config().GetString(configPrefix + ".temp")
filename, _ := params.GetString("filename")
ffs := ctx.Config().GetString(configPrefix + ".filters")
var filters []string
if ffs != "" {
filters = strings.Split(ffs, ",")
}
if filename == "" {
filename = r.URL.Query().Get("filename")
}
opts := ParseResumbleOptions(r)
switch strings.ToLower(r.Method) {
case "get":
getFile(root, temp, filename, opts, w, r, filters, rawReader, thumbReader)
case "post":
postFile(root, temp, filename, opts, w, r)
case "delete":
deleteFile(root, temp, filename, opts, w, r)
}
}
return ff
}
func MakeDiskFileManagerHandler(rawReader, thumbReader FileReader) func(ctx restlet.RequestContext, params restlet.Parameters, w http.ResponseWriter, r *http.Request) {
var ff = func(ctx restlet.RequestContext, params restlet.Parameters, w http.ResponseWriter, r *http.Request) {
filename, _ := params.GetString("filename")
if filename == "" {
filename = r.URL.Query().Get("filename")
}
opts := ParseResumbleOptions(r)
switch strings.ToLower(r.Method) {
case "get":
getFile("", "", filename, opts, w, r, []string{}, rawReader, thumbReader)
}
}
return ff
}
func FileManagerOptionsHandler(ctx restlet.RequestContext, params restlet.Parameters, queries restlet.Parameters, data []byte) (*restlet.RestletResult, error) {
var opts = make(map[string]interface{})
opts["simultaneous"] = ctx.Config().GetInt("materials.simultaneous", 5)
opts["max_upload_size"] = ctx.Config().GetInt("materials.max_upload_size", 2*1024*1024)
return &restlet.RestletResult{
Data: opts,
}, nil
}
func init() {
//service.RegisterRawletHandleFunc(MakeFileManagerHandler("materials.images", nil, ImageThumbReader),
// nil,"*", "materials/images") //, "materials/images/{filename}")
//
//service.RegisterRawletHandleFunc(MakeFileManagerHandler("materials.videos", nil, VideoThumbReader),
// nil,"*", "materials/videos") //, "materials/videos/{filename}")
//
//service.RegisterRawletHandleFunc(MakeDiskFileManagerHandler(nil, ImageThumbReader),
// nil,"GET", "disk/images") //, "materials/videos/{filename}")
//
//service.RegisterRestletHandleFunc(materialOptions, "GET", nil, nil,"materials/options")
}

+ 1
- 0
handlers/filemanager/helpers.go View File

@ -0,0 +1 @@
package filemanager

+ 65
- 0
handlers/filemanager/images.go View File

@ -0,0 +1,65 @@
package filemanager
import (
"bytes"
"fmt"
"image"
"image/jpeg"
_ "image/jpeg"
_ "image/png"
"io/ioutil"
"os"
log "github.com/Sirupsen/logrus"
"github.com/nfnt/resize"
)
func ImageThumbReader(filename string) ([]byte, string, error) {
if fp, e := os.Open(filename); nil != e {
return nil, "", e
} else {
defer fp.Close()
if img, _, e := image.Decode(fp); nil != e {
return nil, "", e
} else if m := resize.Thumbnail(80, 60, img, resize.Lanczos2); nil == m {
return nil, "", fmt.Errorf("nil result")
} else {
//w := &bufio.ReadWriter{}
w := &bytes.Buffer{}
//w := bytes.NewBuffer(bs)
if e := jpeg.Encode(w, m, &jpeg.Options{jpeg.DefaultQuality}); nil != e {
log.Errorln("ImageThumbReader:> failed 1:", e)
return nil, "", e
} else if bs, e := ioutil.ReadAll(w); nil != e {
log.Errorln("ImageThumbReader:> failed 2:", e)
return nil, "", e
} else {
log.Debugln("ImageThumbReader:> Read bytes:", len(bs))
return bs, "image/jpeg", nil
}
}
}
}
func ImageRawReader(filename string) ([]byte, string, error) {
var mime string
if fp, e := os.Open(filename); nil != e {
return nil, "", e
} else {
defer fp.Close()
if _, name, e := image.DecodeConfig(fp); nil != e {
return nil, "", e
} else {
mime = "image/" + name
}
if _, e := fp.Seek(0, 0); nil != e {
return nil, "", e
}
if bs, e := ioutil.ReadAll(fp); nil != e {
return nil, "", e
} else {
log.Debugln("ImageRawReader:> Read bytes:", len(bs))
return bs, mime, nil
}
}
}

+ 29
- 0
handlers/filemanager/types.go View File

@ -0,0 +1,29 @@
package filemanager
import "time"
type FileType uint8
const (
T_INVALID FileType = 0
T_FILE FileType = 1
T_DIRECTORY FileType = 2
T_SYMBOL FileType = 3
)
type FileInfo struct {
Path string `json:"path"`
Filename string `json:"filename"`
Size int64 `json:"size"`
Type FileType `json:"type"`
Ext string `json:"ext"`
Modified time.Time `json:"modified,omitempty"`
MD5 string `json:"md5,omitempty"`
}
type FileList struct {
Path string `json:"path"`
Up bool `json:"up"`
Files []*FileInfo `json:"files"`
Amount int `json:"amount"`
}

+ 260
- 0
handlers/filemanager/upload.go View File

@ -0,0 +1,260 @@
package filemanager
import (
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"path"
"strconv"
"time"
log "github.com/Sirupsen/logrus"
"cygnux.net/kepler/misc"
)
type ResumbleOptions struct {
ChunkNumber int
ChunkSize uint64
CurrentChunkSize uint64
Filename string
Identifier string
RelativePath string
TotalChunks int
TotalSize uint64
Type string
}
type FileReader func(filename string) ([]byte, string, error)
func ParseResumbleOptions(request *http.Request) *ResumbleOptions {
var opts ResumbleOptions
if s := request.URL.Query().Get("resumableChunkNumber"); s != "" {
if n, e := strconv.Atoi(s); nil == e {
opts.ChunkNumber = n
}
}
if s := request.URL.Query().Get("resumableChunkSize"); s != "" {
if n, e := strconv.ParseUint(s, 10, 64); nil == e {
opts.ChunkSize = n
}
}
if s := request.URL.Query().Get("resumableCurrentChunkSize"); s != "" {
if n, e := strconv.ParseUint(s, 10, 64); nil == e {
opts.CurrentChunkSize = n
}
}
if s := request.URL.Query().Get("resumableFilename"); s != "" {
opts.Filename = s
}
if s := request.URL.Query().Get("resumableIdentifier"); s != "" {
opts.Identifier = s
}
if s := request.URL.Query().Get("resumableRelativePath"); s != "" {
opts.RelativePath = s
}
if s := request.URL.Query().Get("resumableTotalChunks"); s != "" {
if n, e := strconv.Atoi(s); nil == e {
opts.TotalChunks = n
}
}
if s := request.URL.Query().Get("resumableTotalSize"); s != "" {
if n, e := strconv.ParseUint(s, 10, 64); nil == e {
opts.TotalSize = n
}
}
if s := request.URL.Query().Get("resumableType"); s != "" {
opts.Type = s
}
log.Debugln("ParseResumbleOptions>", opts)
return &opts
}
func TempFilename(temp, filename string, chunkNum int) (string, string) {
tempDir := path.Join(temp, misc.MD5Hash(filename))
tempFile := path.Join(tempDir, fmt.Sprintf("%d.tmp", chunkNum))
return tempDir, tempFile
}
func CheckTempFile(temp, filename string, opts *ResumbleOptions) (*FileInfo, error) {
tempDir, tempFile := TempFilename(temp, filename, opts.ChunkNumber)
if s, e := os.Stat(tempDir); nil != e {
return nil, e
} else if !s.IsDir() {
return nil, fmt.Errorf("temp dir is not a directory")
}
if s, e := os.Stat(tempFile); nil != e {
return nil, e
} else if uint64(s.Size()) != opts.CurrentChunkSize {
return nil, fmt.Errorf("size not match")
} else {
return &FileInfo{
Path: tempDir,
Filename: path.Base(tempFile),
Size: s.Size(),
Type: T_FILE,
Ext: ".tmp",
Modified: s.ModTime(),
}, nil
}
}
func JsonResponse(w http.ResponseWriter, code int, result interface{}) {
w.Header().Set("Content-Type", "application/json")
if bs, e := json.Marshal(result); nil != e {
w.WriteHeader(http.StatusInternalServerError)
if _, e := w.Write([]byte(e.Error())); nil != e {
log.Errorf("JsonResponse:> failed:", e)
}
} else {
w.WriteHeader(code)
if _, e := w.Write(bs); nil != e {
log.Errorf("JsonResponse:> failed:", e)
}
}
}
func makeDirectory(dir string) error {
if s, e := os.Stat(dir); nil != e {
return os.MkdirAll(dir, 0755)
} else if s.IsDir() {
return nil
} else {
return fmt.Errorf("target directory exists and not a directory")
}
}
func UploadFile(root, temp, dir, filename string, opts *ResumbleOptions, request *http.Request) (*FileInfo, error) {
log.Debugln("UploadFile:>", root, temp, dir, filename)
var fileInfo FileInfo
file, header, err := request.FormFile("file")
if err != nil {
log.Errorln("UploadFile:> get upload file failed:", err)
return nil, err
}
defer file.Close()
if dir == "" {
if p := request.URL.Query().Get("path"); p != "" {
dir = p
}
}
if filename == "" {
filename = header.Filename
}
var fullFilename = path.Join(root, dir, filename)
var fullDir = path.Dir(fullFilename)
fileInfo.Ext = path.Ext(filename)
fileInfo.Filename = filename
fileInfo.Path = path.Join(dir)
fileInfo.Type = T_FILE
fileInfo.Size = 0
if s, e := os.Stat(fullFilename); nil == e {
log.Debugln("UploadFile:> file already exists:", fullFilename)
fileInfo.Size = s.Size()
fileInfo.Modified = s.ModTime()
return &fileInfo, nil
}
if opts.TotalChunks <= 1 {
if e := os.MkdirAll(fullDir, 0755); nil != e {
log.Errorln("UploadFile:> Make directories failed:", e)
return nil, e
}
if fp, e := os.OpenFile(fullFilename, os.O_CREATE|os.O_WRONLY, 0644); nil != e {
log.Errorln("UploadFile:> Create file failed:", e)
return nil, e
} else {
defer fp.Close()
if n, e := io.Copy(fp, file); nil != e {
log.Errorln("UploadFile:> Write to file failed:", e)
return nil, e
} else {
fileInfo.Size = n
fileInfo.Modified = time.Now()
}
}
return &fileInfo, nil
} else {
//
tempDir, tempFile := TempFilename(temp, path.Join(dir, filename), opts.ChunkNumber)
log.Debugln("UploadFile:> ", tempDir, tempFile, fullFilename)
if e := os.MkdirAll(tempDir, 0755); nil != e {
log.Errorln("UploadFile:> Make directories failed:", e)
return nil, e
} else if fp, e := os.OpenFile(tempFile, os.O_CREATE|os.O_WRONLY, 0644); nil != e {
log.Errorln("UploadFile:> Create temp file failed:", e)
return nil, e
} else {
defer fp.Close()
if n, e := io.Copy(fp, file); nil != e {
log.Errorln("UploadFile:> Write to temp file failed:", e)
return nil, e
} else {
fileInfo.Size = n
fileInfo.Modified = time.Now()
}
}
if (opts.TotalChunks - opts.ChunkNumber) < 5 {
var fullFill = true
for i := 1; i <= opts.TotalChunks; i++ {
_, chunkFilename := TempFilename(temp, path.Join(dir, filename), i)
if s, e := os.Stat(chunkFilename); nil != e {
fullFill = false
break
} else if s.IsDir() {
fullFill = false
break
} else if s.Size() <= 0 {
fullFill = false
break
}
}
// Merge
if fullFill {
var fullWrote bool
if e := os.MkdirAll(fullDir, 0755); nil != e {
log.Errorln("UploadFile:> Make directories failed:", e)
return nil, e
}
if fp, e := os.OpenFile(fullFilename, os.O_CREATE|os.O_WRONLY, 0644); nil != e {
log.Errorln("UploadFile:> Create file failed:", e)
return nil, e
} else {
defer func() {
if e := fp.Close(); nil != e {
log.Errorln("UploadFile:> Close file failed:", e)
}
if !fullWrote {
if e := os.Remove(fullFilename); nil != e {
log.Errorln("UploadFile:> Remove file failed:", e)
}
}
}()
for i := 1; i <= opts.TotalChunks; i++ {
_, chunkFilename := TempFilename(temp, path.Join(dir, filename), i)
if in, e := os.Open(chunkFilename); nil != e {
return nil, e
} else if n, e := io.Copy(fp, in); nil != e {
log.Errorln("UploadFile:> Write to file failed:", e)
in.Close()
return nil, e
} else {
in.Close()
fileInfo.Size += n
fileInfo.Modified = time.Now()
}
}
fullWrote = true
if e := os.RemoveAll(tempDir); nil != e {
log.Errorln("UploadFile:> Remove temp dir failed:", e)
}
}
}
}
return &fileInfo, nil
//return nil, fmt.Errorf("not implemented")
}
return &fileInfo, nil
}

+ 123
- 0
handlers/filemanager/videos.go View File

@ -0,0 +1,123 @@
package filemanager
import (
"fmt"
_ "image/jpeg"
_ "image/png"
"time"
"cygnux.net/kepler/mime"
)
type VideoSpec struct {
Width int
Height int
Size uint64
Duration time.Duration
ADec string
VDec string
}
func GetVideoSpec(filename string, mediaInfoexe ...string) (*VideoSpec, error) {
var vSpec VideoSpec
if mi, e := mime.MediaInfo(filename, mediaInfoexe...); nil != e {
return nil, e
} else {
if len(mi.Videos) < 0 {
return nil, fmt.Errorf("invalid file")
} else {
vSpec.Duration = time.Duration(mi.Videos[0].Duration) * time.Second
vSpec.Width = mi.Videos[0].Width
vSpec.Height = mi.Videos[0].Height
vSpec.VDec = mi.Videos[0].Format
}
if len(mi.Audios) < 0 {
return nil, fmt.Errorf("invalid file")
} else {
vSpec.ADec = mi.Audios[0].Format
}
return &vSpec, nil
}
//if fp, e := avutil.Open(filename); nil != e {
// return nil, e
//} else {
// defer fp.Close()
// if streams, e := fp.Streams(); nil != e {
// return nil, e
// } else {
// var vSpec VideoSpec
// for _, s := range streams {
// fmt.Println(">> GetVideoSpec:", s.Type().String())
// if s.Type().IsVideo() {
// vSpec.VDec = s.Type().String()
// vs, _ := s.(av.VideoCodecData)
// vSpec.Width = vs.Width()
// vSpec.Height = vs.Height()
// } else if s.Type().IsAudio() {
// vSpec.ADec = s.Type().String()
// }
// }
// for pkt, e := fp.ReadPacket(); nil == e; pkt, e = fp.ReadPacket() {
// vSpec.Duration += pkt.CompositionTime
// }
// return &vSpec, nil
// }
//}
}
func VideoThumbReader(filename string) ([]byte, string, error) {
//if fp, e := os.Open(filename); nil != e {
// return nil, "", e
//} else {
// defer fp.Close()
// if img, _, e := image.Decode(fp); nil != e {
// return nil, "", e
// } else if m := resize.Thumbnail(80, 60, img, resize.Lanczos2); nil == m {
// return nil, "", fmt.Errorf("nil result")
// } else {
// //w := &bufio.ReadWriter{}
// w := &bytes.Buffer{}
// //w := bytes.NewBuffer(bs)
// if e := jpeg.Encode(w, m, &jpeg.Options{jpeg.DefaultQuality}); nil != e {
// log.Errorln("VideoThumbReader:> failed 1:", e)
// return nil, "", e
// } else if bs, e := ioutil.ReadAll(w); nil != e {
// log.Errorln("VideoThumbReader:> failed 2:", e)
// return nil, "", e
// } else {
// log.Debugln("VideoThumbReader:> Read bytes:", len(bs))
// return bs, "image/jpeg", nil
// }
// }
//}
return nil, "", fmt.Errorf("not implemented")
}
func VideoRawReader(filename string) ([]byte, string, error) {
//var mime string
//if fp, e := os.Open(filename); nil != e {
// return nil, "", e
//} else {
// defer fp.Close()
// if _, name, e := image.DecodeConfig(fp); nil != e {
// return nil, "", e
// } else {
// mime = "image/" + name
// }
// if _, e := fp.Seek(0, 0); nil != e {
// return nil, "", e
// }
// if bs, e := ioutil.ReadAll(fp); nil != e {
// return nil, "", e
// } else {
// log.Debugln("VideoRawReader:> Read bytes:", len(bs))
// return bs, mime, nil
// }
//}
return nil, "", fmt.Errorf("not implemented")
}
func init() {
//format.RegisterAll()
}

+ 3
- 0
handlers/upload.go View File

@ -0,0 +1,3 @@
package handlers

+ 25
- 0
mime/ffmpeg.go View File

@ -0,0 +1,25 @@
package mime
import (
"fmt"
"os"
"os/exec"
"path"
)
func Convert2Mp4(source, destination string) (string, error) {
//log.Infof("Converting '%s' to '%s' ...\n", source, destination)
if e := os.MkdirAll(path.Dir(destination), 0755); nil != e {
//log.Errorln("Create destination path failed:", path.Dir(destination), e)
return destination, e
}
cmd := exec.Command("ffmpeg", "-i", source, "-c", "copy", destination)
if e := cmd.Run(); nil != e {
//log.Errorln("Run convert failed:", e)
return destination, e
} else {
//log.Debugln("Run convert done.")
return destination, nil
}
return "", fmt.Errorf("not implemented")
}

+ 267
- 0
mime/mediainfo.go View File

@ -0,0 +1,267 @@
package mime
import (
"bytes"
"encoding/xml"
"fmt"
"io"
"os/exec"
)
type MediaGeneral struct {
// "@type": "General",
// "ID": "1",
ID int
// "VideoCount": "1",
VideoCount int
// "AudioCount": "1",
AudioCount int
// "MenuCount": "1",
MenuCount int
// "FileExtension": "ts",
FileExtension string
// "Format": "MPEG-TS",
Format string
// "FileSize": "105970524",
FileSize int64
// "Duration": "359.971062500",
Duration float32
// "OverallBitRate_Mode": "CBR",
OverallBitRate_Mode string
// "OverallBitRate": "2354906",
OverallBitRate int64
// "FrameRate": "25.000",
FrameRate float32
// "FrameCount": "9000",
FrameCount int64
// "File_Modified_Date": "UTC 2018-11-23 05:39:50",
// "File_Modified_Date_Local": "2018-11-23 13:39:50",
// "extra": {
// "OverallBitRate_Precision_Min": "2354903",
// "OverallBitRate_Precision_Max": "2354909"
// }
}
type MediaMenu struct {
//"@type": "Menu",
// "StreamOrder": "0",
StreamOrder int
// "ID": "256",
ID int
// "MenuID": "1",
MenuID int
// "Format": "AVC / AAC",
Format string
// "Duration": "359.971062500",
Duration float32
// "Delay": "0.701953333",
Delay float32
// "List_StreamKind": "1 / 2",
List_StreamKind string
// "List_StreamPos": "0 / 0",
List_StreamPos string
// "ServiceType": "digital television",
ServiceType string
// "extra": {
// "pointer_field": "0",
// "section_length": "29"
// }
}
type MediaVideo struct {
//"@type": "Video",
//"StreamOrder": "0-0",
StreamOrder string
//"ID": "4113",
ID int
//"MenuID": "1",
MenuID int
//"Format": "AVC",
Format string
//"Format_Profile": "High",
Format_Profile string
//"Format_Level": "3.2",
Format_Level string
//"Format_Settings_CABAC": "Yes",
Format_Settings_CABAC string
//"Format_Settings_RefFrames": "1",
Format_Settings_RefFrames int
//"Format_Settings_GOP": "M=1, N=30",
Format_Settings_GOP string
//"CodecID": "27",
CodecID string
//"Duration": "359.980",
Duration float32
//"BitRate_Mode": "CBR",
BitRate_Mode string
//"BitRate_Nominal": "2000000",
BitRate_Nominal int64
//"Width": "1280",
Width int
//"Height": "720",
Height int
//"Sampled_Width": "1280",
Sampled_Width int
//"Sampled_Height": "720",
Sampled_Height int
//"PixelAspectRatio": "1.000",
PixelAspectRatio float32
//"DisplayAspectRatio": "1.778",
DisplayAspectRatio float32
//"FrameRate": "25.000",
FrameRate float32
//"FrameCount": "9000",
FrameCount int64
//"ColorSpace": "YUV",
ColorSpace string
//"ChromaSubsampling": "4:2:0",
ChromaSubsampling string
//"BitDepth": "8",
BitDepth int
//"ScanType": "Progressive",
ScanType string
//"Delay": "1.400000",
Delay float32
//"Encoded_Library": "2.2.0.629",
Encoded_Library string
//"Encoded_Library_Name": "2.2.0.629",
Encoded_Library_Name string
//"BufferSize": "1400000"
BufferSize int64
}
type MediaAudio struct {
//"@type": "Audio",
//"StreamOrder": "0-1",
StreamOrder string
//"ID": "4352",
ID int
//"MenuID": "1",
MenuID int
//"Format": "AAC",
Format string
//"Format_Version": "4",
Format_Version string
//"Format_AdditionalFeatures": "LC",
Format_AdditionalFeatures string
//"MuxingMode": "ADTS",
MuxingMode string
//"CodecID": "15-2",
CodecID string
//"Duration": "360.021",
Duration float32
//"BitRate_Mode": "VBR",
BitRate_Mode string
//"Channels": "2",
Channels string
//"ChannelPositions": "Front: L R",
ChannelPositions string
//"ChannelLayout": "L R",
ChannelLayout string
//"SamplesPerFrame": "1024",
SamplesPerFrame int
//"SamplingRate": "48000",
SamplingRate int
//"SamplingCount": "17281008",
SamplingCount int64
//"FrameRate": "46.875",
FrameRate float32
//"Compression_Mode": "Lossy",
Compression_Mode string
//"Delay": "1.400000",
Delay float32
//"Delay_Source": "Container"
Delay_Source string
}
type MediaInfoObject struct {
RefFile string `json:"refFile"`
Menu *MediaMenu `json:"menu"`
General *MediaGeneral `json:"general"`
Videos []*MediaVideo `json:"videos"`
Audios []*MediaAudio `json:"audios"`
}
func decodeMediaInfoObjectXML(bs []byte) (*MediaInfoObject, error) {
d := xml.NewDecoder(bytes.NewReader(bs))
var mediaObj = MediaInfoObject{}
for {
t, tokenErr := d.Token()
if tokenErr != nil {
if tokenErr == io.EOF {
break
}
// handle error
}
switch t := t.(type) {
case xml.StartElement:
if t.Name.Local == "media" {
fmt.Println(">Attr:", t.Attr)
for _, x := range t.Attr {
if x.Name.Local == "ref" {
mediaObj.RefFile = x.Value
break
}
}
} else if t.Name.Local == "track" {
//fmt.Println("> Tag:", t.Name.Local)
var tp string
for _, x := range t.Attr {
if x.Name.Local == "type" {
tp = x.Value
break
}
}
switch tp {
case "General":
var o = MediaGeneral{}
if e := d.DecodeElement(&o, &t); nil == e {
mediaObj.General = &o
} else {
fmt.Println("ERROR:>", e)
}
case "Menu":
var o = MediaMenu{}
if e := d.DecodeElement(&o, &t); nil == e {
mediaObj.Menu = &o
} else {
fmt.Println("ERROR:>", e)
}
case "Video":
var o = MediaVideo{}
if e := d.DecodeElement(&o, &t); nil == e {
mediaObj.Videos = append(mediaObj.Videos, &o)
} else {
fmt.Println("ERROR:>", e)
}
case "Audio":
var o = MediaAudio{}
if e := d.DecodeElement(&o, &t); nil == e {
mediaObj.Audios = append(mediaObj.Audios, &o)
} else {
fmt.Println("ERROR:>", e)
}
}
}
}
}
return &mediaObj, nil
}
func MediaInfo(filename string, mediaInfoexe ...string) (*MediaInfoObject, error) {
var exe = "/usr/bin/mediainfo"
if len(mediaInfoexe) > 0 && mediaInfoexe[0] != "" {
exe = mediaInfoexe[0]
}
cmd := exec.Command(exe, "--output=XML", filename)
if bs, e := cmd.Output(); nil != e {
return nil, e
} else if mo, e := decodeMediaInfoObjectXML(bs); nil != e {
return nil, e
} else if mo.RefFile != filename {
//fmt.Println(string(bs))
return nil, fmt.Errorf("invalid output")
} else if len(mo.Videos) < 1 || len(mo.Audios) < 1 || mo.General == nil /* || mo.Menu == nil */ {
fmt.Println(">>>", len(mo.Videos), len(mo.Audios), mo.General, mo.Menu)
return nil, fmt.Errorf("invalid output: can not get enough information")
} else {
return mo, nil
}
return nil, fmt.Errorf("not implemented")
}

Loading…
Cancel
Save