link пре 1 година
родитељ
комит
5f1df76dbf

+ 7 - 1
.github/workflows/push_test_server.yml

@@ -21,7 +21,7 @@ jobs:
       - name: git global
         run: sudo git config --global --add safe.directory '*'
       - name: set version
-        run: sudo git tag v99.99.99-alpha
+        run: sudo git tag v00.00.00-alpha
         
       - name: Fetch all tags
         run: sudo git fetch --force --tags
@@ -50,6 +50,12 @@ jobs:
           args: release --rm-dist --snapshot
         env:
           GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+          GoogleID: ${{ secrets.GoogleID }} 
+          GoogleSecret: ${{ secrets.GoogleSecret }}
+          DropboxKey: ${{ secrets.DropboxKey }}
+          DropboxSecret: ${{ secrets.DropboxSecret }}
+          OneDriveID: ${{ secrets.OneDriveID }}
+          OneDriveSecret: ${{ secrets.OneDriveSecret }}
           # Your GoReleaser Pro key, if you are using the 'goreleaser-pro' distribution
           # GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }}
        

+ 6 - 0
.github/workflows/release.yml

@@ -42,6 +42,12 @@ jobs:
           args: release --rm-dist
         env:
           GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+          GoogleID: ${{ secrets.GoogleID }} 
+          GoogleSecret: ${{ secrets.GoogleSecret }}
+          DropboxKey: ${{ secrets.DropboxKey }}
+          DropboxSecret: ${{ secrets.DropboxSecret }}
+          OneDriveID: ${{ secrets.OneDriveID }}
+          OneDriveSecret: ${{ secrets.OneDriveSecret }}
           # Your GoReleaser Pro key, if you are using the 'goreleaser-pro' distribution
           # GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }}
 

+ 19 - 1
.goreleaser.yaml

@@ -19,6 +19,12 @@ builds:
     ldflags:
       - -X main.commit={{.Commit}}
       - -X main.date={{.Date}}
+      - -X github.com/IceWhaleTech/CasaOS/drivers/google_drive.client_id={{.Env.GoogleID}}
+      - -X github.com/IceWhaleTech/CasaOS/drivers/google_drive.client_secret={{.Env.GoogleSecret}}
+      - -X github.com/IceWhaleTech/CasaOS/drivers/onedrive.client_id={{.Env.OneDriveID}}
+      - -X github.com/IceWhaleTech/CasaOS/drivers/onedrive.client_secret={{.Env.OneDriveSecret}}
+      - -X github.com/IceWhaleTech/CasaOS/drivers/dropbox.app_key={{.Env.DropboxKey}}
+      - -X github.com/IceWhaleTech/CasaOS/drivers/dropbox.app_secret={{.Env.DropboxSecret}}
       - -s
       - -w
       - -extldflags "-static"
@@ -40,6 +46,12 @@ builds:
     ldflags:
       - -X main.commit={{.Commit}}
       - -X main.date={{.Date}}
+      - -X github.com/IceWhaleTech/CasaOS/drivers/google_drive.client_id={{.Env.GoogleID}}
+      - -X github.com/IceWhaleTech/CasaOS/drivers/google_drive.client_secret={{.Env.GoogleSecret}}
+      - -X github.com/IceWhaleTech/CasaOS/drivers/onedrive.client_id={{.Env.OneDriveID}}
+      - -X github.com/IceWhaleTech/CasaOS/drivers/onedrive.client_secret={{.Env.OneDriveSecret}}
+      - -X github.com/IceWhaleTech/CasaOS/drivers/dropbox.app_key={{.Env.DropboxKey}}
+      - -X github.com/IceWhaleTech/CasaOS/drivers/dropbox.app_secret={{.Env.DropboxSecret}}
       - -s
       - -w
       - -extldflags "-static"
@@ -61,6 +73,12 @@ builds:
     ldflags:
       - -X main.commit={{.Commit}}
       - -X main.date={{.Date}}
+      - -X github.com/IceWhaleTech/CasaOS/drivers/google_drive.client_id={{.Env.GoogleID}}
+      - -X github.com/IceWhaleTech/CasaOS/drivers/google_drive.client_secret={{.Env.GoogleSecret}}
+      - -X github.com/IceWhaleTech/CasaOS/drivers/onedrive.client_id={{.Env.OneDriveID}}
+      - -X github.com/IceWhaleTech/CasaOS/drivers/onedrive.client_secret={{.Env.OneDriveSecret}}
+      - -X github.com/IceWhaleTech/CasaOS/drivers/dropbox.app_key={{.Env.DropboxKey}}
+      - -X github.com/IceWhaleTech/CasaOS/drivers/dropbox.app_secret={{.Env.DropboxSecret}}
       - -s
       - -w
       - -extldflags "-static"
@@ -143,7 +161,7 @@ builds:
     goarm:
       - "7"
 archives:
-  - name_template: >-
+  - name_template:  >- 
       {{ .Os }}-{{- if eq .Arch "arm" }}arm-7{{- else }}{{ .Arch }}{{- end }}-{{ .ProjectName }}-v{{ .Version }}
     id: casaos
     builds:

+ 2 - 2
common/constants.go

@@ -2,7 +2,7 @@ package common
 
 const (
 	SERVICENAME = "casaos"
-	VERSION     = "0.4.4"
-	BODY        = ""
+	VERSION     = "0.4.4.1"
+	BODY        = " "
 	RANW_NAME   = "IceWhale-RemoteAccess"
 )

+ 1 - 0
drivers/all.go

@@ -3,6 +3,7 @@ package drivers
 import (
 	_ "github.com/IceWhaleTech/CasaOS/drivers/dropbox"
 	_ "github.com/IceWhaleTech/CasaOS/drivers/google_drive"
+	_ "github.com/IceWhaleTech/CasaOS/drivers/onedrive"
 )
 
 // All do nothing,just for import

+ 3 - 0
drivers/dropbox/drive.go

@@ -96,5 +96,8 @@ func (d *Dropbox) Remove(ctx context.Context, obj model.Obj) error {
 func (d *Dropbox) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error {
 	return nil
 }
+func (d *Dropbox) GetInfo(ctx context.Context) (string, string, string, error) {
+	return "", "", "", nil
+}
 
 var _ driver.Driver = (*Dropbox)(nil)

+ 1 - 10
drivers/dropbox/meta.go

@@ -2,12 +2,9 @@ package dropbox
 
 import (
 	"github.com/IceWhaleTech/CasaOS/internal/driver"
-	"github.com/IceWhaleTech/CasaOS/internal/op"
 )
 
 const ICONURL = "./img/driver/Dropbox.svg"
-const APPKEY = "tciqajyazzdygt9"
-const APPSECRET = "e7gtmv441cwdf0n"
 
 type Addition struct {
 	driver.RootID
@@ -15,7 +12,7 @@ type Addition struct {
 	AppKey         string `json:"app_key" type:"string" default:"tciqajyazzdygt9" omit:"true"`
 	AppSecret      string `json:"app_secret" type:"string" default:"e7gtmv441cwdf0n" omit:"true"`
 	OrderDirection string `json:"order_direction" type:"select" options:"asc,desc" omit:"true"`
-	AuthUrl        string `json:"auth_url" type:"string" default:"https://www.dropbox.com/oauth2/authorize?client_id=tciqajyazzdygt9&redirect_uri=https://cloudoauth.files.casaos.app&response_type=code&token_access_type=offline&state=${HOST}%2Fv1%2Frecover%2FDropbox&&force_reapprove=true&force_reauthentication=true"`
+	AuthUrl        string `json:"auth_url" type:"string" default:""`
 	Icon           string `json:"icon" type:"string" default:"./img/driver/Dropbox.svg"`
 	Code           string `json:"code" type:"string" help:"code from auth_url" omit:"true"`
 }
@@ -25,9 +22,3 @@ var config = driver.Config{
 	OnlyProxy:   true,
 	DefaultRoot: "root",
 }
-
-func init() {
-	op.RegisterDriver(func() driver.Driver {
-		return &Dropbox{}
-	})
-}

+ 14 - 0
drivers/dropbox/util.go

@@ -10,6 +10,11 @@ import (
 	"go.uber.org/zap"
 )
 
+var (
+	app_key    = "private build"
+	app_secret = "private build"
+)
+
 func (d *Dropbox) getRefreshToken() error {
 	url := "https://api.dropbox.com/oauth2/token"
 	var resp base.TokenResp
@@ -100,3 +105,12 @@ func (d *Dropbox) getFiles(path string) ([]File, error) {
 
 	return res, nil
 }
+func GetConfig() Dropbox {
+	dp := Dropbox{}
+	dp.RootFolderID = ""
+	dp.AuthUrl = "https://www.dropbox.com/oauth2/authorize?client_id=" + app_key + "&redirect_uri=https://cloudoauth.files.casaos.app&response_type=code&token_access_type=offline&state=${HOST}%2Fv2%2Frecover%2FDropbox&&force_reapprove=true&force_reauthentication=true"
+	dp.AppKey = app_key
+	dp.AppSecret = app_secret
+	dp.Icon = "./img/driver/Dropbox.svg"
+	return dp
+}

+ 3 - 0
drivers/google_drive/drive.go

@@ -80,6 +80,9 @@ func (d *GoogleDrive) GetUserInfo(ctx context.Context) (string, error) {
 	return user.User.EmailAddress, nil
 }
 
+func (d *GoogleDrive) GetInfo(ctx context.Context) (string, string, string, error) {
+	return "", "", "", nil
+}
 func (d *GoogleDrive) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error {
 	data := base.Json{
 		"name":     dirName,

+ 3 - 12
drivers/google_drive/meta.go

@@ -2,22 +2,19 @@ package google_drive
 
 import (
 	"github.com/IceWhaleTech/CasaOS/internal/driver"
-	"github.com/IceWhaleTech/CasaOS/internal/op"
 )
 
 const ICONURL = "./img/driver/GoogleDrive.svg"
-const CLIENTID = "921743327851-urr4f7jjfp4ts639evqb3i4m4qb4u4cc.apps.googleusercontent.com"
-const CLIENTSECRET = "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"
 
 type Addition struct {
 	driver.RootID
 	RefreshToken   string `json:"refresh_token" required:"true" omit:"true"`
 	OrderBy        string `json:"order_by" type:"string" help:"such as: folder,name,modifiedTime" omit:"true"`
 	OrderDirection string `json:"order_direction" type:"select" options:"asc,desc" omit:"true"`
-	ClientID       string `json:"client_id" required:"true" default:"921743327851-urr4f7jjfp4ts639evqb3i4m4qb4u4cc.apps.googleusercontent.com" omit:"true"`
-	ClientSecret   string `json:"client_secret" required:"true" default:"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" omit:"true"`
+	ClientID       string `json:"client_id" required:"true" default:"" omit:"true"`
+	ClientSecret   string `json:"client_secret" required:"true" default:"" omit:"true"`
 	ChunkSize      int64  `json:"chunk_size" type:"number" help:"chunk size while uploading (unit: MB)" omit:"true"`
-	AuthUrl        string `json:"auth_url" type:"string" default:"https://accounts.google.com/o/oauth2/auth/oauthchooseaccount?response_type=code&client_id=921743327851-urr4f7jjfp4ts639evqb3i4m4qb4u4cc.apps.googleusercontent.com&redirect_uri=https%3A%2F%2Fcloudoauth.files.casaos.app&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fdrive&access_type=offline&approval_prompt=force&state=${HOST}%2Fv1%2Frecover%2FGoogleDrive&service=lso&o2v=1&flowName=GeneralOAuthFlow"`
+	AuthUrl        string `json:"auth_url" type:"string" default:""`
 	Icon           string `json:"icon" type:"string" default:"./img/driver/GoogleDrive.svg"`
 	Code           string `json:"code" type:"string" help:"code from auth_url" omit:"true"`
 }
@@ -27,9 +24,3 @@ var config = driver.Config{
 	OnlyProxy:   true,
 	DefaultRoot: "root",
 }
-
-func init() {
-	op.RegisterDriver(func() driver.Driver {
-		return &GoogleDrive{}
-	})
-}

+ 15 - 0
drivers/google_drive/util.go

@@ -16,6 +16,11 @@ import (
 	"go.uber.org/zap"
 )
 
+var (
+	client_id     = "private build"
+	client_secret = "private build"
+)
+
 // do others that not defined in Driver interface
 
 func (d *GoogleDrive) getRefreshToken() error {
@@ -150,3 +155,13 @@ func (d *GoogleDrive) chunkUpload(ctx context.Context, stream model.FileStreamer
 	}
 	return nil
 }
+func GetConfig() GoogleDrive {
+	config := GoogleDrive{}
+	config.ClientID = client_id
+	config.ClientSecret = client_secret
+	config.RootFolderID = "root"
+	config.AuthUrl = "https://accounts.google.com/o/oauth2/auth/oauthchooseaccount?response_type=code&client_id=" + client_id + "&redirect_uri=https%3A%2F%2Fcloudoauth.files.casaos.app&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fdrive&access_type=offline&approval_prompt=force&state=${HOST}%2Fv2%2Frecover%2FGoogleDrive&service=lso&o2v=1&flowName=GeneralOAuthFlow"
+	config.Icon = "./img/driver/GoogleDrive.svg"
+
+	return config
+}

+ 70 - 0
drivers/onedrive/drive.go

@@ -0,0 +1,70 @@
+package onedrive
+
+import (
+	"context"
+	"fmt"
+	"net/http"
+	"strconv"
+
+	"github.com/IceWhaleTech/CasaOS-Common/utils/logger"
+
+	"github.com/IceWhaleTech/CasaOS/internal/driver"
+	"github.com/IceWhaleTech/CasaOS/model"
+	"go.uber.org/zap"
+)
+
+type Onedrive struct {
+	model.StorageA
+	Addition
+	AccessToken string
+}
+
+func (d *Onedrive) Config() driver.Config {
+	return config
+}
+
+func (d *Onedrive) GetAddition() driver.Additional {
+	return &d.Addition
+}
+func (d *Onedrive) Init(ctx context.Context) error {
+	if d.ChunkSize < 1 {
+		d.ChunkSize = 5
+	}
+	if len(d.RefreshToken) == 0 {
+		return d.getRefreshToken()
+	}
+	return d.refreshToken()
+}
+func (d *Onedrive) GetUserInfo(ctx context.Context) (string, error) {
+	return "", nil
+}
+func (d *Onedrive) GetInfo(ctx context.Context) (string, string, string, error) {
+	url := d.GetMetaUrl(false, "/")
+	user := Info{}
+	resp, err := d.Request(url, http.MethodGet, nil, &user)
+	if err != nil {
+		return "", "", "", err
+	}
+
+	logger.Info("resp", zap.Any("resp", resp))
+	return user.LastModifiedBy.User.DisplayName, user.ParentReference.DriveID, user.ParentReference.DriveType, nil
+}
+
+func (d *Onedrive) GetSpaceSize(ctx context.Context) (used string, total string, err error) {
+	host := onedriveHostMap[d.Region]
+	url := fmt.Sprintf("%s/v1.0/me/drive/quota", host.Api)
+	size := About{}
+	resp, err := d.Request(url, http.MethodGet, nil, &size)
+	if err != nil {
+		return used, total, err
+	}
+	logger.Info("resp", zap.Any("resp", resp))
+	used = strconv.Itoa(size.Used)
+	total = strconv.Itoa(size.Total)
+	return
+}
+func (d *Onedrive) Drop(ctx context.Context) error {
+	return nil
+}
+
+var _ driver.Driver = (*Onedrive)(nil)

+ 67 - 0
drivers/onedrive/meta.go

@@ -0,0 +1,67 @@
+package onedrive
+
+import (
+	"github.com/IceWhaleTech/CasaOS/internal/driver"
+)
+
+type Host struct {
+	Oauth string
+	Api   string
+}
+
+type TokenErr struct {
+	Error            string `json:"error"`
+	ErrorDescription string `json:"error_description"`
+}
+
+type RespErr struct {
+	Error struct {
+		Code    string `json:"code"`
+		Message string `json:"message"`
+	} `json:"error"`
+}
+type Addition struct {
+	Region       string `json:"region" type:"select" required:"true" options:"global,cn,us,de" default:"global"`
+	IsSharepoint bool   `json:"is_sharepoint"`
+	ClientID     string `json:"client_id" required:"true"`
+	ClientSecret string `json:"client_secret" required:"true"`
+	RedirectUri  string `json:"redirect_uri" required:"true" default:""`
+	RefreshToken string `json:"refresh_token" required:"true"`
+	SiteId       string `json:"site_id"`
+	ChunkSize    int64  `json:"chunk_size" type:"number" default:"5"`
+	RootFolderID string `json:"root_folder_id"`
+	AuthUrl      string `json:"auth_url" type:"string" default:""`
+	Icon         string `json:"icon" type:"string" default:""`
+	Code         string `json:"code" type:"string" help:"code from auth_url" omit:"true"`
+}
+type About struct {
+	Total int    `json:"total"`
+	Used  int    `json:"used"`
+	State string `json:"state"`
+}
+
+type Info struct {
+	LastModifiedBy struct {
+		Application struct {
+			DisplayName string `json:"displayName"`
+			ID          string `json:"id"`
+		} `json:"application"`
+		Device struct {
+			ID string `json:"id"`
+		} `json:"device"`
+		User struct {
+			DisplayName string `json:"displayName"`
+			ID          string `json:"id"`
+		} `json:"user"`
+	} `json:"lastModifiedBy"`
+	ParentReference struct {
+		DriveID   string `json:"driveId"`
+		DriveType string `json:"driveType"`
+	} `json:"parentReference"`
+}
+
+var config = driver.Config{
+	Name:        "Onedrive",
+	LocalSort:   true,
+	DefaultRoot: "/",
+}

+ 182 - 0
drivers/onedrive/util.go

@@ -0,0 +1,182 @@
+package onedrive
+
+import (
+	"errors"
+	"fmt"
+	"net/url"
+	"strings"
+
+	"github.com/IceWhaleTech/CasaOS-Common/utils/logger"
+	"github.com/IceWhaleTech/CasaOS/drivers/base"
+	"go.uber.org/zap"
+)
+
+var (
+	client_id     = "private build"
+	client_secret = "private build"
+)
+var onedriveHostMap = map[string]Host{
+	"global": {
+		Oauth: "https://login.microsoftonline.com",
+		Api:   "https://graph.microsoft.com",
+	},
+	"cn": {
+		Oauth: "https://login.chinacloudapi.cn",
+		Api:   "https://microsoftgraph.chinacloudapi.cn",
+	},
+	"us": {
+		Oauth: "https://login.microsoftonline.us",
+		Api:   "https://graph.microsoft.us",
+	},
+	"de": {
+		Oauth: "https://login.microsoftonline.de",
+		Api:   "https://graph.microsoft.de",
+	},
+}
+
+func EncodePath(path string, all ...bool) string {
+	seg := strings.Split(path, "/")
+	toReplace := []struct {
+		Src string
+		Dst string
+	}{
+		{Src: "%", Dst: "%25"},
+		{"%", "%25"},
+		{"?", "%3F"},
+		{"#", "%23"},
+	}
+	for i := range seg {
+		if len(all) > 0 && all[0] {
+			seg[i] = url.PathEscape(seg[i])
+		} else {
+			for j := range toReplace {
+				seg[i] = strings.ReplaceAll(seg[i], toReplace[j].Src, toReplace[j].Dst)
+			}
+		}
+	}
+	return strings.Join(seg, "/")
+}
+func (d *Onedrive) GetMetaUrl(auth bool, path string) string {
+	host := onedriveHostMap[d.Region]
+	path = EncodePath(path, true)
+	if auth {
+		return host.Oauth
+	}
+	if d.IsSharepoint {
+		if path == "/" || path == "\\" {
+			return fmt.Sprintf("%s/v1.0/sites/%s/drive/root", host.Api, d.SiteId)
+		} else {
+			return fmt.Sprintf("%s/v1.0/sites/%s/drive/root:%s:", host.Api, d.SiteId, path)
+		}
+	} else {
+		if path == "/" || path == "\\" {
+			return fmt.Sprintf("%s/v1.0/me/drive/root", host.Api)
+		} else {
+			return fmt.Sprintf("%s/v1.0/me/drive/root:%s:", host.Api, path)
+		}
+	}
+}
+
+func (d *Onedrive) refreshToken() error {
+	var err error
+	for i := 0; i < 3; i++ {
+		err = d._refreshToken()
+		if err == nil {
+			break
+		}
+	}
+	return err
+}
+
+func (d *Onedrive) getRefreshToken() error {
+	url := d.GetMetaUrl(true, "") + "/common/oauth2/v2.0/token"
+	var resp base.TokenResp
+	var e TokenErr
+
+	res, err := base.RestyClient.R().SetResult(&resp).SetError(&e).SetFormData(map[string]string{
+		"grant_type":    "authorization_code",
+		"client_id":     d.ClientID,
+		"client_secret": d.ClientSecret,
+		"code":          d.Code,
+		"redirect_uri":  d.RedirectUri,
+	}).Post(url)
+	if err != nil {
+		return err
+	}
+	logger.Info("get refresh token", zap.String("res", res.String()))
+	if e.Error != "" {
+		return fmt.Errorf("%s", e.ErrorDescription)
+	}
+	if resp.RefreshToken == "" {
+		return errors.New("refresh token is empty")
+	}
+	d.RefreshToken, d.AccessToken = resp.RefreshToken, resp.AccessToken
+	return nil
+}
+
+func (d *Onedrive) _refreshToken() error {
+	url := d.GetMetaUrl(true, "") + "/common/oauth2/v2.0/token"
+	var resp base.TokenResp
+	var e TokenErr
+
+	res, err := base.RestyClient.R().SetResult(&resp).SetError(&e).SetFormData(map[string]string{
+		"grant_type":    "refresh_token",
+		"client_id":     d.ClientID,
+		"client_secret": d.ClientSecret,
+		"redirect_uri":  d.RedirectUri,
+		"refresh_token": d.RefreshToken,
+	}).Post(url)
+	if err != nil {
+		return err
+	}
+	logger.Info("get refresh token", zap.String("res", res.String()))
+	if e.Error != "" {
+		return fmt.Errorf("%s", e.ErrorDescription)
+	}
+	if resp.RefreshToken == "" {
+		return errors.New("refresh token is empty")
+	}
+	d.RefreshToken, d.AccessToken = resp.RefreshToken, resp.AccessToken
+	return nil
+}
+
+func (d *Onedrive) Request(url string, method string, callback base.ReqCallback, resp interface{}) ([]byte, error) {
+	req := base.RestyClient.R()
+	req.SetHeader("Authorization", "Bearer "+d.AccessToken)
+	if callback != nil {
+		callback(req)
+	}
+	if resp != nil {
+		req.SetResult(resp)
+	}
+	var e RespErr
+	req.SetError(&e)
+	res, err := req.Execute(method, url)
+	if err != nil {
+		return nil, err
+	}
+	if e.Error.Code != "" {
+		if e.Error.Code == "InvalidAuthenticationToken" {
+			err = d.refreshToken()
+			if err != nil {
+				return nil, err
+			}
+			return d.Request(url, method, callback, resp)
+		}
+		return nil, errors.New(e.Error.Message)
+	}
+	return res.Body(), nil
+}
+
+func GetConfig() Onedrive {
+	config := Onedrive{}
+	config.ClientID = client_id
+	config.ClientSecret = client_secret
+	config.RootFolderID = "/"
+	config.AuthUrl = "https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id=" + client_id + "&response_type=code&redirect_uri=https%3A%2F%2Fcloudoauth.files.casaos.app%2Fcallback&scope=offline_access+files.readwrite.all&state=${HOST}%2Fv2%2Frecover%2FOnedrive"
+	config.Icon = "./img/driver/OneDrive.svg"
+	config.Region = "global"
+	config.RedirectUri = "https://cloudoauth.files.casaos.app"
+
+	return config
+}

+ 4 - 2
internal/driver/driver.go

@@ -34,14 +34,16 @@ type Reader interface {
 	// List files in the path
 	// if identify files by path, need to set ID with path,like path.Join(dir.GetID(), obj.GetName())
 	// if identify files by id, need to set ID with corresponding id
-	List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error)
+	// List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error)
 	// Link get url/filepath/reader of file
-	Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error)
+	// Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error)
 }
 type User interface {
 	// GetRoot get root directory of user
 	GetUserInfo(ctx context.Context) (string, error)
+	GetInfo(ctx context.Context) (string, string, string, error)
 }
+
 type Getter interface {
 	GetRoot(ctx context.Context) (model.Obj, error)
 }

+ 7 - 0
model/drive.go

@@ -0,0 +1,7 @@
+package model
+
+type Drive struct {
+	Name    string `json:"name"`
+	Icon    string `json:"icon"`
+	AuthUrl string `json:"auth_url"`
+}

+ 25 - 3
route/v1/driver.go

@@ -1,12 +1,34 @@
 package v1
 
 import (
-	"github.com/IceWhaleTech/CasaOS-Common/model"
 	"github.com/IceWhaleTech/CasaOS-Common/utils/common_err"
-	"github.com/IceWhaleTech/CasaOS/internal/op"
+	"github.com/IceWhaleTech/CasaOS/drivers/dropbox"
+	"github.com/IceWhaleTech/CasaOS/drivers/google_drive"
+	"github.com/IceWhaleTech/CasaOS/drivers/onedrive"
+	"github.com/IceWhaleTech/CasaOS/model"
 	"github.com/gin-gonic/gin"
 )
 
 func ListDriverInfo(c *gin.Context) {
-	c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: op.GetDriverInfoMap()})
+	list := []model.Drive{}
+
+	google := google_drive.GetConfig()
+	list = append(list, model.Drive{
+		Name:    "Google Drive",
+		Icon:    google.Icon,
+		AuthUrl: google.AuthUrl,
+	})
+	dp := dropbox.GetConfig()
+	list = append(list, model.Drive{
+		Name:    "Dropbox",
+		Icon:    dp.Icon,
+		AuthUrl: dp.AuthUrl,
+	})
+	od := onedrive.GetConfig()
+	list = append(list, model.Drive{
+		Name:    "OneDrive",
+		Icon:    od.Icon,
+		AuthUrl: od.AuthUrl,
+	})
+	c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: list})
 }

+ 106 - 23
route/v1/recover.go

@@ -8,6 +8,7 @@ import (
 	"github.com/IceWhaleTech/CasaOS-Common/utils/logger"
 	"github.com/IceWhaleTech/CasaOS/drivers/dropbox"
 	"github.com/IceWhaleTech/CasaOS/drivers/google_drive"
+	"github.com/IceWhaleTech/CasaOS/drivers/onedrive"
 	"github.com/IceWhaleTech/CasaOS/service"
 	"github.com/gin-gonic/gin"
 	"go.uber.org/zap"
@@ -20,23 +21,17 @@ func GetRecoverStorage(c *gin.Context) {
 	currentDate := time.Now().UTC().Format("2006-01-02")
 	notify := make(map[string]interface{})
 	if t == "GoogleDrive" {
-		add := google_drive.Addition{}
-		add.Code = c.Query("code")
-		if len(add.Code) == 0 {
+		google_drive := google_drive.GetConfig()
+		google_drive.Code = c.Query("code")
+		if len(google_drive.Code) == 0 {
 			c.String(200, `<p>Code cannot be empty</p><script>window.close()</script>`)
 			notify["status"] = "fail"
 			notify["message"] = "Code cannot be empty"
-			logger.Error("Then code is empty: ", zap.String("code", add.Code), zap.Any("name", "google_drive"))
+			logger.Error("Then code is empty: ", zap.String("code", google_drive.Code), zap.Any("name", "google_drive"))
 			service.MyService.Notify().SendNotify("casaos:file:recover", notify)
 			return
 		}
 
-		add.RootFolderID = "root"
-		add.ClientID = google_drive.CLIENTID
-		add.ClientSecret = google_drive.CLIENTSECRET
-
-		var google_drive google_drive.GoogleDrive
-		google_drive.Addition = add
 		err := google_drive.Init(c)
 		if err != nil {
 			c.String(200, `<p>Initialization failure:`+err.Error()+`</p><script>window.close()</script>`)
@@ -93,8 +88,8 @@ func GetRecoverStorage(c *gin.Context) {
 		//username = fileutil.NameAccumulation(username, "/mnt")
 		username += "_google_drive_" + strconv.FormatInt(time.Now().Unix(), 10)
 
-		dmap["client_id"] = add.ClientID
-		dmap["client_secret"] = add.ClientSecret
+		dmap["client_id"] = google_drive.ClientID
+		dmap["client_secret"] = google_drive.ClientSecret
 		dmap["scope"] = "drive"
 		dmap["mount_point"] = "/mnt/" + username
 		dmap["token"] = `{"access_token":"` + google_drive.AccessToken + `","token_type":"Bearer","refresh_token":"` + google_drive.RefreshToken + `","expiry":"` + currentDate + `T` + currentTime.Add(time.Hour*1).Add(time.Minute*50).Format("15:04:05") + `Z"}`
@@ -106,21 +101,17 @@ func GetRecoverStorage(c *gin.Context) {
 		notify["driver"] = "GoogleDrive"
 		service.MyService.Notify().SendNotify("casaos:file:recover", notify)
 	} else if t == "Dropbox" {
-		add := dropbox.Addition{}
-		add.Code = c.Query("code")
-		if len(add.Code) == 0 {
+		dropbox := dropbox.GetConfig()
+		dropbox.Code = c.Query("code")
+		if len(dropbox.Code) == 0 {
 			c.String(200, `<p>Code cannot be empty</p><script>window.close()</script>`)
 			notify["status"] = "fail"
 			notify["message"] = "Code cannot be empty"
-			logger.Error("Then code is empty error: ", zap.String("code", add.Code), zap.Any("name", "dropbox"))
+			logger.Error("Then code is empty error: ", zap.String("code", dropbox.Code), zap.Any("name", "dropbox"))
 			service.MyService.Notify().SendNotify("casaos:file:recover", notify)
 			return
 		}
-		add.RootFolderID = ""
-		add.AppKey = dropbox.APPKEY
-		add.AppSecret = dropbox.APPSECRET
-		var dropbox dropbox.Dropbox
-		dropbox.Addition = add
+
 		err := dropbox.Init(c)
 		if err != nil {
 			c.String(200, `<p>Initialization failure:`+err.Error()+`</p><script>window.close()</script>`)
@@ -176,8 +167,8 @@ func GetRecoverStorage(c *gin.Context) {
 		}
 		username += "_dropbox_" + strconv.FormatInt(time.Now().Unix(), 10)
 
-		dmap["client_id"] = add.AppKey
-		dmap["client_secret"] = add.AppSecret
+		dmap["client_id"] = dropbox.AppKey
+		dmap["client_secret"] = dropbox.AppSecret
 		dmap["token"] = `{"access_token":"` + dropbox.AccessToken + `","token_type":"bearer","refresh_token":"` + dropbox.Addition.RefreshToken + `","expiry":"` + currentDate + `T` + currentTime.Add(time.Hour*3).Add(time.Minute*50).Format("15:04:05") + `.780385354Z"}`
 		dmap["mount_point"] = "/mnt/" + username
 		// data.SetValue(username, "type", "dropbox")
@@ -199,6 +190,98 @@ func GetRecoverStorage(c *gin.Context) {
 		notify["message"] = "Success"
 		notify["driver"] = "Dropbox"
 		service.MyService.Notify().SendNotify("casaos:file:recover", notify)
+	} else if t == "Onedrive" {
+		onedrive := onedrive.GetConfig()
+		onedrive.Code = c.Query("code")
+		if len(onedrive.Code) == 0 {
+			c.String(200, `<p>Code cannot be empty</p><script>window.close()</script>`)
+			notify["status"] = "fail"
+			notify["message"] = "Code cannot be empty"
+			logger.Error("Then code is empty error: ", zap.String("code", onedrive.Code), zap.Any("name", "onedrive"))
+			service.MyService.Notify().SendNotify("casaos:file:recover", notify)
+			return
+		}
+
+		err := onedrive.Init(c)
+		if err != nil {
+			c.String(200, `<p>Initialization failure:`+err.Error()+`</p><script>window.close()</script>`)
+			notify["status"] = "fail"
+			notify["message"] = "Initialization failure"
+			logger.Error("Then init error: ", zap.Error(err), zap.Any("name", "onedrive"))
+			service.MyService.Notify().SendNotify("casaos:file:recover", notify)
+			return
+		}
+		username, driveId, driveType, err := onedrive.GetInfo(c)
+		if err != nil {
+			c.String(200, `<p>Failed to get user information:`+err.Error()+`</p><script>window.close()</script>`)
+			notify["status"] = "fail"
+			notify["message"] = "Failed to get user information"
+			logger.Error("Then get user information: ", zap.Error(err), zap.Any("name", "onedrive"))
+			service.MyService.Notify().SendNotify("casaos:file:recover", notify)
+			return
+		}
+		dmap := make(map[string]string)
+		dmap["username"] = username
+
+		configs, err := service.MyService.Storage().GetConfig()
+		if err != nil {
+			c.String(200, `<p>Failed to get rclone config:`+err.Error()+`</p><script>window.close()</script>`)
+			notify["status"] = "fail"
+			notify["message"] = "Failed to get rclone config"
+			logger.Error("Then get config error: ", zap.Error(err), zap.Any("name", "onedrive"))
+			service.MyService.Notify().SendNotify("casaos:file:recover", notify)
+			return
+		}
+		for _, v := range configs.Remotes {
+			cf, err := service.MyService.Storage().GetConfigByName(v)
+			if err != nil {
+				logger.Error("then get config by name error: ", zap.Error(err), zap.Any("name", v))
+				continue
+			}
+			if cf["type"] == "onedrive" && cf["username"] == dmap["username"] {
+				c.String(200, `<p>The same configuration has been added</p><script>window.close()</script>`)
+				err := service.MyService.Storage().CheckAndMountByName(v)
+				if err != nil {
+					logger.Error("check and mount by name error: ", zap.Error(err), zap.Any("name", cf["username"]))
+				}
+
+				notify["status"] = "warn"
+				notify["message"] = "The same configuration has been added"
+				service.MyService.Notify().SendNotify("casaos:file:recover", notify)
+				return
+			}
+		}
+		if len(username) > 0 {
+			a := strings.Split(username, "@")
+			username = a[0]
+		}
+		username += "_dropbox_" + strconv.FormatInt(time.Now().Unix(), 10)
+
+		dmap["client_id"] = onedrive.ClientID
+		dmap["client_secret"] = onedrive.ClientSecret
+		dmap["token"] = `{"access_token":"` + onedrive.AccessToken + `","token_type":"bearer","refresh_token":"` + onedrive.RefreshToken + `","expiry":"` + currentDate + `T` + currentTime.Add(time.Hour*3).Add(time.Minute*50).Format("15:04:05") + `.780385354Z"}`
+		dmap["mount_point"] = "/mnt/" + username
+		dmap["drive_id"] = driveId
+		dmap["drive_type"] = driveType
+		// data.SetValue(username, "type", "dropbox")
+		// data.SetValue(username, "client_id", add.AppKey)
+		// data.SetValue(username, "client_secret", add.AppSecret)
+		// data.SetValue(username, "mount_point", "/mnt/"+username)
+
+		// data.SetValue(username, "token", `{"access_token":"`+dropbox.AccessToken+`","token_type":"bearer","refresh_token":"`+dropbox.Addition.RefreshToken+`","expiry":"`+currentDate+`T`+currentTime.Add(time.Hour*3).Format("15:04:05")+`.780385354Z"}`)
+		// e = data.Save()
+		// if e != nil {
+		// 	c.String(200, `<p>保存配置失败:`+e.Error()+`</p>`)
+
+		// 	return
+		// }
+		service.MyService.Storage().CreateConfig(dmap, username, "onedrive")
+		service.MyService.Storage().MountStorage("/mnt/"+username, username+":")
+
+		notify["status"] = "success"
+		notify["message"] = "Success"
+		notify["driver"] = "Onedrive"
+		service.MyService.Notify().SendNotify("casaos:file:recover", notify)
 	}
 
 	c.String(200, `<p>Just close the page</p><script>window.close()</script>`)

+ 119 - 0
route/v2.go

@@ -2,13 +2,17 @@ package route
 
 import (
 	"crypto/ecdsa"
+	"log"
 	"net/http"
 	"net/url"
+	"path"
+	"path/filepath"
 	"strconv"
 	"strings"
 
 	"github.com/IceWhaleTech/CasaOS/codegen"
 	"github.com/IceWhaleTech/CasaOS/pkg/config"
+	"github.com/IceWhaleTech/CasaOS/pkg/utils/file"
 
 	"github.com/IceWhaleTech/CasaOS-Common/external"
 	"github.com/IceWhaleTech/CasaOS-Common/utils/jwt"
@@ -134,3 +138,118 @@ func InitV2DocRouter(docHTML string, docYAML string) http.Handler {
 		}
 	})
 }
+
+func InitFile() http.Handler {
+	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		token := r.URL.Query().Get("token")
+		if len(token) == 0 {
+			w.Header().Set("Content-Type", "application/json")
+			w.WriteHeader(http.StatusUnauthorized)
+			w.Write([]byte(`{"message": "token not found"}`))
+			return
+		}
+
+		valid, _, errs := jwt.Validate(token, func() (*ecdsa.PublicKey, error) { return external.GetPublicKey(config.CommonInfo.RuntimePath) })
+		if errs != nil || !valid {
+			w.Header().Set("Content-Type", "application/json")
+			w.WriteHeader(http.StatusUnauthorized)
+			w.Write([]byte(`{"message": "validation failure"}`))
+			return
+		}
+		filePath := r.URL.Query().Get("path")
+		fileName := path.Base(filePath)
+		w.Header().Add("Content-Disposition", "attachment; filename*=utf-8''"+url.PathEscape(fileName))
+		http.ServeFile(w, r, filePath)
+		//http.ServeFile(w, r, filePath)
+	})
+}
+
+func InitDir() http.Handler {
+	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		token := r.URL.Query().Get("token")
+		if len(token) == 0 {
+			w.Header().Set("Content-Type", "application/json")
+			w.WriteHeader(http.StatusUnauthorized)
+			w.Write([]byte(`{"message": "token not found"}`))
+			return
+		}
+
+		valid, _, errs := jwt.Validate(token, func() (*ecdsa.PublicKey, error) { return external.GetPublicKey(config.CommonInfo.RuntimePath) })
+		if errs != nil || !valid {
+			w.Header().Set("Content-Type", "application/json")
+			w.WriteHeader(http.StatusUnauthorized)
+			w.Write([]byte(`{"message": "validation failure"}`))
+			return
+		}
+		t := r.URL.Query().Get("format")
+		files := r.URL.Query().Get("files")
+
+		if len(files) == 0 {
+			// w.JSON(common_err.CLIENT_ERROR, model.Result{
+			// 	Success: common_err.INVALID_PARAMS,
+			// 	Message: common_err.GetMsg(common_err.INVALID_PARAMS),
+			// })
+			return
+		}
+		list := strings.Split(files, ",")
+		for _, v := range list {
+			if !file.Exists(v) {
+				// c.JSON(common_err.SERVICE_ERROR, model.Result{
+				// 	Success: common_err.FILE_DOES_NOT_EXIST,
+				// 	Message: common_err.GetMsg(common_err.FILE_DOES_NOT_EXIST),
+				// })
+				return
+			}
+		}
+		w.Header().Add("Content-Type", "application/octet-stream")
+		w.Header().Add("Content-Transfer-Encoding", "binary")
+		w.Header().Add("Cache-Control", "no-cache")
+		// handles only single files not folders and multiple files
+		//		if len(list) == 1 {
+
+		// filePath := list[0]
+		//			info, err := os.Stat(filePath)
+		//			if err != nil {
+
+		// w.JSON(http.StatusOK, model.Result{
+		// 	Success: common_err.FILE_DOES_NOT_EXIST,
+		// 	Message: common_err.GetMsg(common_err.FILE_DOES_NOT_EXIST),
+		// })
+		//return
+		//			}
+		//}
+
+		extension, ar, err := file.GetCompressionAlgorithm(t)
+		if err != nil {
+			// w.JSON(common_err.CLIENT_ERROR, model.Result{
+			// 	Success: common_err.INVALID_PARAMS,
+			// 	Message: common_err.GetMsg(common_err.INVALID_PARAMS),
+			// })
+			return
+		}
+
+		err = ar.Create(w)
+		if err != nil {
+			//  c.JSON(common_err.SERVICE_ERROR, model.Result{
+			// 	Success: common_err.SERVICE_ERROR,
+			// 	Message: common_err.GetMsg(common_err.SERVICE_ERROR),
+			// 	Data:    err.Error(),
+			// })
+			return
+		}
+		defer ar.Close()
+		commonDir := file.CommonPrefix(filepath.Separator, list...)
+
+		currentPath := filepath.Base(commonDir)
+
+		name := "_" + currentPath
+		name += extension
+		w.Header().Add("Content-Disposition", "attachment; filename*=utf-8''"+url.PathEscape(name))
+		for _, fname := range list {
+			err = file.AddFile(ar, fname, commonDir)
+			if err != nil {
+				log.Printf("Failed to archive %s: %v", fname, err)
+			}
+		}
+	})
+}

+ 0 - 1
route/v2/health.go

@@ -46,7 +46,6 @@ func (s *CasaOS) GetHealthPorts(ctx echo.Context) error {
 		},
 	})
 }
-
 func (c *CasaOS) GetHealthlogs(ctx echo.Context) error {
 	var name, currentPath, commonDir, extension string
 	var err error