@ -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") | |||
} |
@ -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 | |||
} | |||
} |
@ -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") | |||
} |
@ -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())}, | |||
} | |||
} |
@ -0,0 +1 @@ | |||
package auth |
@ -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 | |||
} |
@ -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 | |||
} | |||
} |
@ -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) | |||
} | |||
} |
@ -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) | |||
} | |||
} |
@ -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) | |||
} | |||
} | |||
} |
@ -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) | |||
} | |||
} |
@ -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") | |||
} |
@ -0,0 +1 @@ | |||
package filemanager |
@ -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 | |||
} | |||
} | |||
} |
@ -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"` | |||
} |
@ -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 | |||
} |
@ -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() | |||
} |
@ -0,0 +1,3 @@ | |||
package handlers | |||
@ -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") | |||
} |
@ -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") | |||
} |