Files

360 lines
9.2 KiB
Go

package backend
import (
. "github.com/mickael-kerjean/filestash/server/common"
"golang.org/x/net/context"
"golang.org/x/oauth2"
"golang.org/x/oauth2/google"
"google.golang.org/api/drive/v3"
"io"
"os"
"path/filepath"
"regexp"
"strconv"
"strings"
"time"
)
const gdriveFolderMarker = "application/vnd.google-apps.folder"
type GDrive struct {
Client *drive.Service
Config *oauth2.Config
}
func init() {
Backend.Register("gdrive", GDrive{})
}
func (g GDrive) Init(params map[string]string, app *App) (IBackend, error) {
backend := GDrive{}
config := &oauth2.Config{
Endpoint: google.Endpoint,
ClientID: Config.Get("auth.gdrive.client_id").Default(os.Getenv("GDRIVE_CLIENT_ID")).String(),
ClientSecret: Config.Get("auth.gdrive.client_secret").Default(os.Getenv("GDRIVE_CLIENT_SECRET")).String(),
RedirectURL: "https://" + Config.Get("general.host").String() + "/login",
Scopes: []string{"https://www.googleapis.com/auth/drive"},
}
if config.ClientID == "" {
return backend, NewError("Missing Client ID: Contact your admin", 502)
} else if config.ClientSecret == "" {
return backend, NewError("Missing Client Secret: Contact your admin", 502)
} else if config.RedirectURL == "/login" {
return backend, NewError("Missing Hostname: Contact your admin", 502)
}
token := &oauth2.Token{
AccessToken: params["token"],
RefreshToken: params["refresh"],
Expiry: func(t string) time.Time {
expiry, err := strconv.ParseInt(t, 10, 64)
if err != nil {
return time.Now()
}
return time.Unix(expiry, 0)
}(params["expiry"]),
TokenType: "bearer",
}
client := config.Client(context.Background(), token)
srv, err := drive.New(client)
if err != nil {
return nil, NewError(err.Error(), 400)
}
backend.Client = srv
backend.Config = config
return backend, nil
}
func (g GDrive) LoginForm() Form {
return Form{
Elmnts: []FormElement{
FormElement{
Name: "type",
Type: "hidden",
Value: "gdrive",
},
FormElement{
ReadOnly: true,
Name: "oauth2",
Type: "text",
Value: "/api/session/auth/gdrive",
},
FormElement{
ReadOnly: true,
Name: "image",
Type: "image",
Value: "data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMTM5IDEyMC40IiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIj4KICA8cGF0aCBkPSJtMjQuMiAxMjAuNC0yNC4yLTQxLjkgNDUuMy03OC41IDI0LjIgNDEuOXoiIGZpbGw9IiMwZGE5NjAiLz4KICA8cGF0aCBkPSJtNTguOSA2MC4yIDEwLjYtMTguMy0yNC4yLTQxLjl6IiBmaWxsPSIjMGRhOTYwIi8+CiAgPHBhdGggZD0ibTI0LjIgMTIwLjQgMjQuMi00MS45aDkwLjZsLTI0LjIgNDEuOXoiIGZpbGw9IiMyZDZmZGQiLz4KICA8cGF0aCBkPSJtNjkuNSA3OC41aC0yMS4xbDEwLjUtMTguMy0zNC43IDYwLjJ6IiBmaWxsPSIjMmQ2ZmRkIi8+ICAKICA8cGF0aCBkPSJtMTM5IDc4LjVoLTQ4LjRsLTQ1LjMtNzguNWg0OC40eiIgZmlsbD0iI2ZmZDI0ZCIvPgogIDxwYXRoIGQ9Im05MC42IDc4LjVoNDguNGwtNTguOS0xOC4zeiIgZmlsbD0iI2ZmZDI0ZCIvPgo8L3N2Zz4K",
},
},
}
}
func (g GDrive) OAuthURL() string {
return g.Config.AuthCodeURL("gdrive", oauth2.AccessTypeOnline)
}
func (g GDrive) OAuthToken(ctx *map[string]interface{}) error {
code := ""
if str, ok := (*ctx)["code"].(string); ok {
code = str
}
token, err := g.Config.Exchange(oauth2.NoContext, code)
if err != nil {
return err
}
(*ctx)["token"] = token.AccessToken
(*ctx)["refresh"] = token.RefreshToken
(*ctx)["expiry"] = strconv.FormatInt(token.Expiry.UnixNano()/1000, 10)
delete(*ctx, "code")
return nil
}
func (g GDrive) Ls(path string) ([]os.FileInfo, error) {
files := make([]os.FileInfo, 0)
file, err := g.infoPath(path)
if err != nil {
return nil, err
}
res, err := g.Client.Files.List().Q("'" + file.id + "' in parents AND trashed = false").Fields("nextPageToken, files(name, size, modifiedTime, mimeType)").PageSize(500).Do()
if err != nil {
return nil, NewError(err.Error(), 404)
}
for _, obj := range res.Files {
files = append(files, File{
FName: obj.Name,
FType: func(mType string) string {
if mType == gdriveFolderMarker {
return "directory"
}
return "file"
}(obj.MimeType),
FTime: func(t string) int64 {
a, err := time.Parse(time.RFC3339, t)
if err != nil {
return 0
}
return a.UnixNano() / 1000
}(obj.ModifiedTime),
FSize: obj.Size,
})
}
return files, nil
}
func (g GDrive) Cat(path string) (io.ReadCloser, error) {
file, err := g.infoPath(path)
if err != nil {
return nil, err
}
if strings.HasPrefix(file.mType, "application/vnd.google-apps") {
mType := "text/plain"
if file.mType == "application/vnd.google-apps.spreadsheet" {
mType = "text/csv"
}
data, err := g.Client.Files.Export(file.id, mType).Download()
if err != nil {
return nil, err
}
return data.Body, nil
}
data, err := g.Client.Files.Get(file.id).Download()
if err != nil {
return nil, err
}
return data.Body, nil
}
func (g GDrive) Mkdir(path string) error {
parent, err := g.infoPath(getParentPath(path))
if err != nil {
return NewError("Directory already exists", 409)
}
_, err = g.Client.Files.Create(&drive.File{
Name: filepath.Base(path),
Parents: []string{parent.id},
MimeType: gdriveFolderMarker,
}).Do()
return err
}
func (g GDrive) Rm(path string) error {
file, err := g.infoPath(path)
if err != nil {
return err
}
if err = g.Client.Files.Delete(file.id).Do(); err != nil {
return err
}
return nil
}
func (g GDrive) Mv(from string, to string) error {
ffile, err := g.infoPath(from)
if err != nil {
return err
}
tfile, err := g.infoPath(getParentPath(to))
if err != nil {
return err
}
_, err = g.Client.Files.Update(ffile.id, &drive.File{
Name: filepath.Base(to),
}).RemoveParents(ffile.parent).AddParents(tfile.id).Do()
return err
}
func (g GDrive) Touch(path string) error {
file, err := g.infoPath(getParentPath(path))
if err != nil {
return NewError("Base folder not found", 404)
}
_, err = g.Client.Files.Create(&drive.File{
Name: filepath.Base(path),
Parents: []string{file.id},
}).Media(strings.NewReader("")).Do()
return err
}
func (g GDrive) Save(path string, reader io.Reader) error {
if file, err := g.infoPath(path); err == nil {
_, err = g.Client.Files.Update(file.id, &drive.File{}).Media(reader).Do()
return err
}
file, err := g.infoPath(getParentPath(path))
if err != nil {
return err
}
_, err = g.Client.Files.Create(&drive.File{
Name: filepath.Base(path),
Parents: []string{file.id},
}).Media(reader).Do()
return err
}
func (g GDrive) infoPath(p string) (*GDriveMarker, error) {
FindSolutions := func(level int, folder string) ([]GDriveMarker, error) {
res, err := g.Client.Files.List().Q("name = '" + folder + "' AND trashed = false").Fields("files(parents, id, name, mimeType)").PageSize(500).Do()
if err != nil {
return nil, err
}
solutions := make([]GDriveMarker, 0)
for _, file := range res.Files {
if len(file.Parents) == 0 {
continue
}
solutions = append(solutions, GDriveMarker{
file.Id,
file.Parents[0],
file.Name,
level,
file.MimeType,
})
}
return solutions, nil
}
FindRoot := func(level int) ([]GDriveMarker, error) {
root := make([]GDriveMarker, 0)
res, err := g.Client.Files.List().Q("'root' in parents").Fields("files(parents, id, name, mimeType)").PageSize(1).Do()
if err != nil {
return nil, err
}
if len(res.Files) == 0 || len(res.Files[0].Parents) == 0 {
root = append(root, GDriveMarker{
"root",
"root",
"root",
level,
gdriveFolderMarker,
})
return root, nil
}
root = append(root, GDriveMarker{
res.Files[0].Parents[0],
"root",
"root",
level,
gdriveFolderMarker,
})
return root, nil
}
MergeSolutions := func(solutions_bag []GDriveMarker, solutions_new []GDriveMarker) []GDriveMarker {
if len(solutions_bag) == 0 {
return solutions_new
}
solutions := make([]GDriveMarker, 0)
for _, new := range solutions_new {
for _, old := range solutions_bag {
if new.id == old.parent && new.level+1 == old.level {
old.level = new.level
old.parent = new.id
solutions = append(solutions, old)
}
}
}
return solutions
}
var FindId func(folders []string, solutions_bag []GDriveMarker) (*GDriveMarker, error)
FindId = func(folders []string, solutions_bag []GDriveMarker) (*GDriveMarker, error) {
var solutions_new []GDriveMarker
var err error
if len(folders) == 0 {
solutions_new, err = FindRoot(0)
} else {
solutions_new, err = FindSolutions(len(folders), folders[len(folders)-1])
}
if err != nil {
return nil, NewError("Can't get data", 500)
}
solutions_bag = MergeSolutions(solutions_bag, solutions_new)
if len(solutions_bag) == 0 {
return nil, NewError("Doesn't exist", 404)
} else if len(solutions_bag) == 1 {
return &solutions_bag[0], nil
} else {
return FindId(folders[:len(folders)-1], solutions_bag)
}
}
path := make([]string, 0)
for _, chunk := range strings.Split(p, "/") {
if chunk == "" {
continue
}
path = append(path, chunk)
}
if len(path) == 0 {
return &GDriveMarker{
"root",
"",
"root",
0,
gdriveFolderMarker,
}, nil
}
return FindId(path, make([]GDriveMarker, 0))
}
type GDriveMarker struct {
id string
parent string
name string
level int
mType string
}
func getParentPath(path string) string {
re := regexp.MustCompile("/$")
path = re.ReplaceAllString(path, "")
return filepath.Dir(path) + "/"
}