mirror of https://github.com/Xhofe/alist
feat: add onedrive driver
parent
c95a7c2a04
commit
f551dc76d0
|
@ -2,6 +2,7 @@ package drivers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
_ "github.com/alist-org/alist/v3/drivers/local"
|
_ "github.com/alist-org/alist/v3/drivers/local"
|
||||||
|
_ "github.com/alist-org/alist/v3/drivers/onedrive"
|
||||||
_ "github.com/alist-org/alist/v3/drivers/virtual"
|
_ "github.com/alist-org/alist/v3/drivers/virtual"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
package base
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-resty/resty/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
var NoRedirectClient *resty.Client
|
||||||
|
var RestyClient = resty.New()
|
||||||
|
var HttpClient = &http.Client{}
|
||||||
|
var UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36"
|
||||||
|
var DefaultTimeout = time.Second * 10
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
NoRedirectClient = resty.New().SetRedirectPolicy(
|
||||||
|
resty.RedirectPolicyFunc(func(req *http.Request, via []*http.Request) error {
|
||||||
|
return http.ErrUseLastResponse
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
NoRedirectClient.SetHeader("user-agent", UserAgent)
|
||||||
|
RestyClient.SetHeader("user-agent", UserAgent)
|
||||||
|
RestyClient.SetRetryCount(3)
|
||||||
|
RestyClient.SetTimeout(DefaultTimeout)
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
package base
|
||||||
|
|
||||||
|
type Json map[string]interface{}
|
||||||
|
|
||||||
|
type TokenResp struct {
|
||||||
|
AccessToken string `json:"access_token"`
|
||||||
|
RefreshToken string `json:"refresh_token"`
|
||||||
|
}
|
|
@ -40,7 +40,6 @@ func (d *Local) Init(ctx context.Context, storage model.Storage) error {
|
||||||
}
|
}
|
||||||
if !utils.Exists(d.RootFolder) {
|
if !utils.Exists(d.RootFolder) {
|
||||||
err = errors.Errorf("root folder %s not exists", d.RootFolder)
|
err = errors.Errorf("root folder %s not exists", d.RootFolder)
|
||||||
d.SetStatus(err.Error())
|
|
||||||
} else {
|
} else {
|
||||||
if !filepath.IsAbs(d.RootFolder) {
|
if !filepath.IsAbs(d.RootFolder) {
|
||||||
d.RootFolder, err = filepath.Abs(d.RootFolder)
|
d.RootFolder, err = filepath.Abs(d.RootFolder)
|
||||||
|
@ -48,7 +47,6 @@ func (d *Local) Init(ctx context.Context, storage model.Storage) error {
|
||||||
return errors.Wrap(err, "error while get abs path")
|
return errors.Wrap(err, "error while get abs path")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
d.SetStatus("OK")
|
|
||||||
}
|
}
|
||||||
operations.MustSaveDriverStorage(d)
|
operations.MustSaveDriverStorage(d)
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -11,7 +11,7 @@ type Addition struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
var config = driver.Config{
|
var config = driver.Config{
|
||||||
Name: "Local",
|
Name: "local",
|
||||||
OnlyLocal: true,
|
OnlyLocal: true,
|
||||||
LocalSort: true,
|
LocalSort: true,
|
||||||
NoCache: true,
|
NoCache: true,
|
||||||
|
|
|
@ -0,0 +1,157 @@
|
||||||
|
package onedrive
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
stdpath "path"
|
||||||
|
|
||||||
|
"github.com/alist-org/alist/v3/drivers/base"
|
||||||
|
"github.com/alist-org/alist/v3/internal/driver"
|
||||||
|
"github.com/alist-org/alist/v3/internal/errs"
|
||||||
|
"github.com/alist-org/alist/v3/internal/model"
|
||||||
|
"github.com/alist-org/alist/v3/pkg/utils"
|
||||||
|
"github.com/go-resty/resty/v2"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Onedrive struct {
|
||||||
|
model.Storage
|
||||||
|
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, storage model.Storage) error {
|
||||||
|
d.Storage = storage
|
||||||
|
err := utils.Json.UnmarshalFromString(d.Storage.Addition, &d.Addition)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "error while unmarshal addition")
|
||||||
|
}
|
||||||
|
return d.refreshToken()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Onedrive) Drop(ctx context.Context) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Onedrive) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) {
|
||||||
|
files, err := d.GetFiles(dir.GetID())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
objs := make([]model.Obj, len(files))
|
||||||
|
for i := 0; i < len(files); i++ {
|
||||||
|
objs[i] = fileToObj(files[i])
|
||||||
|
}
|
||||||
|
return objs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Onedrive) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
|
||||||
|
f, err := d.GetFile(file.GetID())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if f.File == nil {
|
||||||
|
return nil, errs.NotFile
|
||||||
|
}
|
||||||
|
return &model.Link{
|
||||||
|
URL: f.Url,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Onedrive) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error {
|
||||||
|
url := d.GetMetaUrl(false, parentDir.GetID()) + "/children"
|
||||||
|
data := base.Json{
|
||||||
|
"name": dirName,
|
||||||
|
"folder": base.Json{},
|
||||||
|
"@microsoft.graph.conflictBehavior": "rename",
|
||||||
|
}
|
||||||
|
_, err := d.Request(url, http.MethodPost, func(req *resty.Request) {
|
||||||
|
req.SetBody(data)
|
||||||
|
}, nil)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Onedrive) Move(ctx context.Context, srcObj, dstDir model.Obj) error {
|
||||||
|
dst, err := d.GetFile(dstDir.GetID())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
data := base.Json{
|
||||||
|
"parentReference": base.Json{
|
||||||
|
"id": dst.Id,
|
||||||
|
},
|
||||||
|
"name": srcObj.GetName(),
|
||||||
|
}
|
||||||
|
url := d.GetMetaUrl(false, srcObj.GetID())
|
||||||
|
_, err = d.Request(url, http.MethodPatch, func(req *resty.Request) {
|
||||||
|
req.SetBody(data)
|
||||||
|
}, nil)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Onedrive) Rename(ctx context.Context, srcObj model.Obj, newName string) error {
|
||||||
|
dstDir, err := d.GetFile(stdpath.Dir(srcObj.GetID()))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
data := base.Json{
|
||||||
|
"parentReference": base.Json{
|
||||||
|
"id": dstDir.Id,
|
||||||
|
},
|
||||||
|
"name": newName,
|
||||||
|
}
|
||||||
|
url := d.GetMetaUrl(false, srcObj.GetID())
|
||||||
|
_, err = d.Request(url, http.MethodPatch, func(req *resty.Request) {
|
||||||
|
req.SetBody(data)
|
||||||
|
}, nil)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Onedrive) Copy(ctx context.Context, srcObj, dstDir model.Obj) error {
|
||||||
|
dst, err := d.GetFile(dstDir.GetID())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
data := base.Json{
|
||||||
|
"parentReference": base.Json{
|
||||||
|
"driveId": dst.ParentReference.DriveId,
|
||||||
|
"id": dst.Id,
|
||||||
|
},
|
||||||
|
"name": srcObj.GetName(),
|
||||||
|
}
|
||||||
|
url := d.GetMetaUrl(false, srcObj.GetID()) + "/copy"
|
||||||
|
_, err = d.Request(url, http.MethodPost, func(req *resty.Request) {
|
||||||
|
req.SetBody(data)
|
||||||
|
}, nil)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Onedrive) Remove(ctx context.Context, obj model.Obj) error {
|
||||||
|
url := d.GetMetaUrl(false, obj.GetID())
|
||||||
|
_, err := d.Request(url, http.MethodDelete, nil, nil)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Onedrive) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error {
|
||||||
|
var err error
|
||||||
|
if stream.GetSize() <= 4*1024*1024 {
|
||||||
|
err = d.upSmall(dstDir, stream)
|
||||||
|
} else {
|
||||||
|
err = d.upBig(ctx, dstDir, stream, up)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Onedrive) Other(ctx context.Context, args model.OtherArgs) (interface{}, error) {
|
||||||
|
return nil, errs.NotSupport
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ driver.Driver = (*Onedrive)(nil)
|
|
@ -0,0 +1,31 @@
|
||||||
|
package onedrive
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/alist-org/alist/v3/internal/driver"
|
||||||
|
"github.com/alist-org/alist/v3/internal/operations"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Addition struct {
|
||||||
|
driver.RootFolderPath
|
||||||
|
Region string `json:"region" type:"select" required:"true" options:"global,cn,us,de"`
|
||||||
|
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:"https://tool.nn.ci/onedrive/callback"`
|
||||||
|
RefreshToken string `json:"refresh_token" required:"true"`
|
||||||
|
SiteId string `json:"site_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var config = driver.Config{
|
||||||
|
Name: "onedrive",
|
||||||
|
LocalSort: true,
|
||||||
|
DefaultRoot: "/",
|
||||||
|
}
|
||||||
|
|
||||||
|
func New() driver.Driver {
|
||||||
|
return &Onedrive{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
operations.RegisterDriver(config, New)
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
package onedrive
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/alist-org/alist/v3/internal/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
type File struct {
|
||||||
|
Id string `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Size int64 `json:"size"`
|
||||||
|
LastModifiedDateTime time.Time `json:"lastModifiedDateTime"`
|
||||||
|
Url string `json:"@microsoft.graph.downloadUrl"`
|
||||||
|
File *struct {
|
||||||
|
MimeType string `json:"mimeType"`
|
||||||
|
} `json:"file"`
|
||||||
|
Thumbnails []struct {
|
||||||
|
Medium struct {
|
||||||
|
Url string `json:"url"`
|
||||||
|
} `json:"medium"`
|
||||||
|
} `json:"thumbnails"`
|
||||||
|
ParentReference struct {
|
||||||
|
DriveId string `json:"driveId"`
|
||||||
|
} `json:"parentReference"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func fileToObj(f File) *model.ObjectThumbnail {
|
||||||
|
thumb := ""
|
||||||
|
if len(f.Thumbnails) > 0 {
|
||||||
|
thumb = f.Thumbnails[0].Medium.Url
|
||||||
|
}
|
||||||
|
return &model.ObjectThumbnail{
|
||||||
|
Object: model.Object{
|
||||||
|
//ID: f.Id,
|
||||||
|
Name: f.Name,
|
||||||
|
Size: f.Size,
|
||||||
|
Modified: f.LastModifiedDateTime,
|
||||||
|
IsFolder: f.File == nil,
|
||||||
|
},
|
||||||
|
Thumbnail: model.Thumbnail{Thumbnail: thumb},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Files struct {
|
||||||
|
Value []File `json:"value"`
|
||||||
|
NextLink string `json:"@odata.nextLink"`
|
||||||
|
}
|
|
@ -0,0 +1,217 @@
|
||||||
|
package onedrive
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
stdpath "path"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/alist-org/alist/v3/drivers/base"
|
||||||
|
"github.com/alist-org/alist/v3/internal/driver"
|
||||||
|
"github.com/alist-org/alist/v3/internal/errs"
|
||||||
|
"github.com/alist-org/alist/v3/internal/model"
|
||||||
|
"github.com/alist-org/alist/v3/internal/operations"
|
||||||
|
"github.com/alist-org/alist/v3/pkg/utils"
|
||||||
|
"github.com/go-resty/resty/v2"
|
||||||
|
jsoniter "github.com/json-iterator/go"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Host struct {
|
||||||
|
Oauth string
|
||||||
|
Api string
|
||||||
|
}
|
||||||
|
|
||||||
|
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 (d *Onedrive) GetMetaUrl(auth bool, path string) string {
|
||||||
|
host, _ := onedriveHostMap[d.Region]
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
type TokenErr struct {
|
||||||
|
Error string `json:"error"`
|
||||||
|
ErrorDescription string `json:"error_description"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Onedrive) _refreshToken() error {
|
||||||
|
url := d.GetMetaUrl(true, "") + "/common/oauth2/v2.0/token"
|
||||||
|
var resp base.TokenResp
|
||||||
|
var e TokenErr
|
||||||
|
_, 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
|
||||||
|
}
|
||||||
|
if e.Error != "" {
|
||||||
|
return fmt.Errorf("%s", e.ErrorDescription)
|
||||||
|
}
|
||||||
|
if resp.RefreshToken == "" {
|
||||||
|
return errs.EmptyToken
|
||||||
|
}
|
||||||
|
d.RefreshToken, d.AccessToken = resp.RefreshToken, resp.AccessToken
|
||||||
|
operations.MustSaveDriverStorage(d)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type RespErr struct {
|
||||||
|
Error struct {
|
||||||
|
Code string `json:"code"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
} `json:"error"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Onedrive) Request(url string, method string, callback func(*resty.Request), 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, errors.WithStack(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 (d *Onedrive) GetFiles(path string) ([]File, error) {
|
||||||
|
var res []File
|
||||||
|
nextLink := d.GetMetaUrl(false, path) + "/children?$expand=thumbnails"
|
||||||
|
for nextLink != "" {
|
||||||
|
var files Files
|
||||||
|
_, err := d.Request(nextLink, http.MethodGet, nil, &files)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
res = append(res, files.Value...)
|
||||||
|
nextLink = files.NextLink
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Onedrive) GetFile(path string) (*File, error) {
|
||||||
|
var file File
|
||||||
|
u := d.GetMetaUrl(false, path)
|
||||||
|
_, err := d.Request(u, http.MethodGet, nil, &file)
|
||||||
|
return &file, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Onedrive) upSmall(dstDir model.Obj, stream model.FileStreamer) error {
|
||||||
|
url := d.GetMetaUrl(false, stdpath.Join(dstDir.GetID(), stream.GetName())) + "/content"
|
||||||
|
data, err := io.ReadAll(stream)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = d.Request(url, http.MethodPut, func(req *resty.Request) {
|
||||||
|
req.SetBody(data)
|
||||||
|
}, nil)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Onedrive) upBig(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error {
|
||||||
|
url := d.GetMetaUrl(false, stdpath.Join(dstDir.GetID(), stream.GetName())) + "/createUploadSession"
|
||||||
|
res, err := d.Request(url, http.MethodPost, nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
uploadUrl := jsoniter.Get(res, "uploadUrl").ToString()
|
||||||
|
var finish int64 = 0
|
||||||
|
const DEFAULT = 4 * 1024 * 1024
|
||||||
|
for finish < stream.GetSize() {
|
||||||
|
if utils.IsCanceled(ctx) {
|
||||||
|
return ctx.Err()
|
||||||
|
}
|
||||||
|
log.Debugf("upload: %d", finish)
|
||||||
|
var byteSize int64 = DEFAULT
|
||||||
|
left := stream.GetSize() - finish
|
||||||
|
if left < DEFAULT {
|
||||||
|
byteSize = left
|
||||||
|
}
|
||||||
|
byteData := make([]byte, byteSize)
|
||||||
|
n, err := io.ReadFull(stream, byteData)
|
||||||
|
log.Debug(err, n)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
req, err := http.NewRequest("PUT", uploadUrl, bytes.NewBuffer(byteData))
|
||||||
|
req.Header.Set("Content-Length", strconv.Itoa(int(byteSize)))
|
||||||
|
req.Header.Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", finish, finish+byteSize-1, stream.GetSize()))
|
||||||
|
finish += byteSize
|
||||||
|
res, err := base.HttpClient.Do(req)
|
||||||
|
if res.StatusCode != 201 && res.StatusCode != 202 {
|
||||||
|
data, _ := io.ReadAll(res.Body)
|
||||||
|
res.Body.Close()
|
||||||
|
return errors.New(string(data))
|
||||||
|
}
|
||||||
|
res.Body.Close()
|
||||||
|
up(int(finish / stream.GetSize()))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -35,10 +35,6 @@ func (d *Virtual) Drop(ctx context.Context) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Virtual) GetStorage() model.Storage {
|
|
||||||
return d.Storage
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Virtual) GetAddition() driver.Additional {
|
func (d *Virtual) GetAddition() driver.Additional {
|
||||||
return d.Addition
|
return d.Addition
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@ type Addition struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
var config = driver.Config{
|
var config = driver.Config{
|
||||||
Name: "Virtual",
|
Name: "virtual",
|
||||||
OnlyLocal: true,
|
OnlyLocal: true,
|
||||||
LocalSort: true,
|
LocalSort: true,
|
||||||
NeedMs: true,
|
NeedMs: true,
|
||||||
|
|
5
go.mod
5
go.mod
|
@ -30,6 +30,7 @@ require (
|
||||||
github.com/go-playground/locales v0.14.0 // indirect
|
github.com/go-playground/locales v0.14.0 // indirect
|
||||||
github.com/go-playground/universal-translator v0.18.0 // indirect
|
github.com/go-playground/universal-translator v0.18.0 // indirect
|
||||||
github.com/go-playground/validator/v10 v10.11.0 // indirect
|
github.com/go-playground/validator/v10 v10.11.0 // indirect
|
||||||
|
github.com/go-resty/resty/v2 v2.7.0 // indirect
|
||||||
github.com/go-sql-driver/mysql v1.6.0 // indirect
|
github.com/go-sql-driver/mysql v1.6.0 // indirect
|
||||||
github.com/goccy/go-json v0.9.7 // indirect
|
github.com/goccy/go-json v0.9.7 // indirect
|
||||||
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||||
|
@ -53,8 +54,8 @@ require (
|
||||||
github.com/ugorji/go/codec v1.2.7 // indirect
|
github.com/ugorji/go/codec v1.2.7 // indirect
|
||||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d // indirect
|
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d // indirect
|
||||||
golang.org/x/image v0.0.0-20220722155232-062f8c9fd539 // indirect
|
golang.org/x/image v0.0.0-20220722155232-062f8c9fd539 // indirect
|
||||||
golang.org/x/net v0.0.0-20220531201128-c960675eff93 // indirect
|
golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b // indirect
|
||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect
|
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 // indirect
|
||||||
golang.org/x/text v0.3.7 // indirect
|
golang.org/x/text v0.3.7 // indirect
|
||||||
google.golang.org/protobuf v1.28.0 // indirect
|
google.golang.org/protobuf v1.28.0 // indirect
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
|
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
|
||||||
|
|
7
go.sum
7
go.sum
|
@ -39,6 +39,8 @@ github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/j
|
||||||
github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
|
github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
|
||||||
github.com/go-playground/validator/v10 v10.11.0 h1:0W+xRM511GY47Yy3bZUbJVitCNg2BOGlCyvTqsp/xIw=
|
github.com/go-playground/validator/v10 v10.11.0 h1:0W+xRM511GY47Yy3bZUbJVitCNg2BOGlCyvTqsp/xIw=
|
||||||
github.com/go-playground/validator/v10 v10.11.0/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU=
|
github.com/go-playground/validator/v10 v10.11.0/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU=
|
||||||
|
github.com/go-resty/resty/v2 v2.7.0 h1:me+K9p3uhSmXtrBZ4k9jcEAfJmuC8IivWHwaLZwPrFY=
|
||||||
|
github.com/go-resty/resty/v2 v2.7.0/go.mod h1:9PWDzw47qPphMRFfhsyk0NnSgvluHcljSMVIq3w7q0I=
|
||||||
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
|
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
|
||||||
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||||
|
@ -238,9 +240,12 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
|
golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.0.0-20220531201128-c960675eff93 h1:MYimHLfoXEpOhqd/zgoA/uoXzHB86AEky4LAx5ij9xA=
|
golang.org/x/net v0.0.0-20220531201128-c960675eff93 h1:MYimHLfoXEpOhqd/zgoA/uoXzHB86AEky4LAx5ij9xA=
|
||||||
golang.org/x/net v0.0.0-20220531201128-c960675eff93/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
golang.org/x/net v0.0.0-20220531201128-c960675eff93/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
|
golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b h1:ZmngSVLe/wycRns9MKikG9OWIEjGcGAkacif7oYQaUY=
|
||||||
|
golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
@ -259,6 +264,8 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||||
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k=
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k=
|
||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 h1:WIoqL4EROvwiPdUtaip4VcDdpZ4kha7wBWZrbVKCIZg=
|
||||||
|
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
|
|
@ -43,7 +43,7 @@ func TestDown(t *testing.T) {
|
||||||
ID: 0,
|
ID: 0,
|
||||||
MountPath: "/",
|
MountPath: "/",
|
||||||
Index: 0,
|
Index: 0,
|
||||||
Driver: "Local",
|
Driver: "local",
|
||||||
Status: "",
|
Status: "",
|
||||||
Addition: `{"root_folder":"../../data"}`,
|
Addition: `{"root_folder":"../../data"}`,
|
||||||
Remark: "",
|
Remark: "",
|
||||||
|
|
|
@ -15,7 +15,7 @@ func initDevData() {
|
||||||
err := operations.CreateStorage(context.Background(), model.Storage{
|
err := operations.CreateStorage(context.Background(), model.Storage{
|
||||||
MountPath: "/",
|
MountPath: "/",
|
||||||
Index: 0,
|
Index: 0,
|
||||||
Driver: "Local",
|
Driver: "local",
|
||||||
Status: "",
|
Status: "",
|
||||||
Addition: `{"root_folder":"."}`,
|
Addition: `{"root_folder":"."}`,
|
||||||
})
|
})
|
||||||
|
|
|
@ -15,13 +15,14 @@ type Driver interface {
|
||||||
|
|
||||||
type Meta interface {
|
type Meta interface {
|
||||||
Config() Config
|
Config() Config
|
||||||
|
// GetStorage just get raw storage, no need to implement, because model.Storage have implemented
|
||||||
|
GetStorage() *model.Storage
|
||||||
|
// GetAddition Additional can't be modified externally, so needn't return pointer
|
||||||
|
GetAddition() Additional
|
||||||
// Init If already initialized, drop first
|
// Init If already initialized, drop first
|
||||||
// need to unmarshal string to addition first
|
// need to unmarshal string to addition first
|
||||||
Init(ctx context.Context, storage model.Storage) error
|
Init(ctx context.Context, storage model.Storage) error
|
||||||
Drop(ctx context.Context) error
|
Drop(ctx context.Context) error
|
||||||
// GetStorage just get raw storage
|
|
||||||
GetStorage() model.Storage
|
|
||||||
GetAddition() Additional
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Other interface {
|
type Other interface {
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
package errs
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
|
var (
|
||||||
|
EmptyToken = errors.New("empty token")
|
||||||
|
)
|
|
@ -29,12 +29,12 @@ type Proxy struct {
|
||||||
DownProxyUrl string `json:"down_proxy_url"`
|
DownProxyUrl string `json:"down_proxy_url"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Storage) GetStorage() Storage {
|
func (s *Storage) GetStorage() *Storage {
|
||||||
return *a
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Storage) SetStatus(status string) {
|
func (s *Storage) SetStatus(status string) {
|
||||||
a.Status = status
|
s.Status = status
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p Proxy) Webdav302() bool {
|
func (p Proxy) Webdav302() bool {
|
||||||
|
|
|
@ -128,12 +128,13 @@ func getAdditionalItems(t reflect.Type, defaultRoot string) []driver.Item {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
tag := field.Tag
|
tag := field.Tag
|
||||||
ignore, ok := tag.Lookup("ignore")
|
ignore, ok1 := tag.Lookup("ignore")
|
||||||
if ok && ignore == "true" {
|
name, ok2 := tag.Lookup("json")
|
||||||
|
if (ok1 && ignore == "true") || !ok2 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
item := driver.Item{
|
item := driver.Item{
|
||||||
Name: tag.Get("json"),
|
Name: name,
|
||||||
Type: strings.ToLower(field.Type.Name()),
|
Type: strings.ToLower(field.Type.Name()),
|
||||||
Default: tag.Get("default"),
|
Default: tag.Get("default"),
|
||||||
Options: tag.Get("options"),
|
Options: tag.Get("options"),
|
||||||
|
|
|
@ -2,6 +2,7 @@ package operations
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
@ -49,7 +50,12 @@ func CreateStorage(ctx context.Context, storage model.Storage) error {
|
||||||
// already has an id
|
// already has an id
|
||||||
err = storageDriver.Init(ctx, storage)
|
err = storageDriver.Init(ctx, storage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
storageDriver.GetStorage().SetStatus(fmt.Sprintf("%+v", err.Error()))
|
||||||
|
MustSaveDriverStorage(storageDriver)
|
||||||
return errors.WithMessage(err, "failed init storage but storage is already created")
|
return errors.WithMessage(err, "failed init storage but storage is already created")
|
||||||
|
} else {
|
||||||
|
storageDriver.GetStorage().SetStatus("work")
|
||||||
|
MustSaveDriverStorage(storageDriver)
|
||||||
}
|
}
|
||||||
log.Debugf("storage %+v is created", storageDriver)
|
log.Debugf("storage %+v is created", storageDriver)
|
||||||
storagesMap.Store(storage.MountPath, storageDriver)
|
storagesMap.Store(storage.MountPath, storageDriver)
|
||||||
|
@ -204,7 +210,7 @@ func saveDriverStorage(driver driver.Driver) error {
|
||||||
return errors.Wrap(err, "error while marshal addition")
|
return errors.Wrap(err, "error while marshal addition")
|
||||||
}
|
}
|
||||||
storage.Addition = string(bytes)
|
storage.Addition = string(bytes)
|
||||||
err = db.UpdateStorage(&storage)
|
err = db.UpdateStorage(storage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithMessage(err, "failed update storage in database")
|
return errors.WithMessage(err, "failed update storage in database")
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,8 +25,8 @@ func TestCreateStorage(t *testing.T) {
|
||||||
storage model.Storage
|
storage model.Storage
|
||||||
isErr bool
|
isErr bool
|
||||||
}{
|
}{
|
||||||
{storage: model.Storage{Driver: "Local", MountPath: "/local", Addition: `{"root_folder":"."}`}, isErr: false},
|
{storage: model.Storage{Driver: "local", MountPath: "/local", Addition: `{"root_folder":"."}`}, isErr: false},
|
||||||
{storage: model.Storage{Driver: "Local", MountPath: "/local", Addition: `{"root_folder":"."}`}, isErr: true},
|
{storage: model.Storage{Driver: "local", MountPath: "/local", Addition: `{"root_folder":"."}`}, isErr: true},
|
||||||
{storage: model.Storage{Driver: "None", MountPath: "/none", Addition: `{"root_folder":"."}`}, isErr: true},
|
{storage: model.Storage{Driver: "None", MountPath: "/none", Addition: `{"root_folder":"."}`}, isErr: true},
|
||||||
}
|
}
|
||||||
for _, storage := range storages {
|
for _, storage := range storages {
|
||||||
|
@ -70,11 +70,11 @@ func TestGetBalancedStorage(t *testing.T) {
|
||||||
|
|
||||||
func setupStorages(t *testing.T) {
|
func setupStorages(t *testing.T) {
|
||||||
var storages = []model.Storage{
|
var storages = []model.Storage{
|
||||||
{Driver: "Local", MountPath: "/a/b", Index: 0, Addition: `{"root_folder":"."}`},
|
{Driver: "local", MountPath: "/a/b", Index: 0, Addition: `{"root_folder":"."}`},
|
||||||
{Driver: "Local", MountPath: "/a/c", Index: 1, Addition: `{"root_folder":"."}`},
|
{Driver: "local", MountPath: "/a/c", Index: 1, Addition: `{"root_folder":"."}`},
|
||||||
{Driver: "Local", MountPath: "/a/d", Index: 2, Addition: `{"root_folder":"."}`},
|
{Driver: "local", MountPath: "/a/d", Index: 2, Addition: `{"root_folder":"."}`},
|
||||||
{Driver: "Local", MountPath: "/a/d/e", Index: 3, Addition: `{"root_folder":"."}`},
|
{Driver: "local", MountPath: "/a/d/e", Index: 3, Addition: `{"root_folder":"."}`},
|
||||||
{Driver: "Local", MountPath: "/a/d/e.balance", Index: 4, Addition: `{"root_folder":"."}`},
|
{Driver: "local", MountPath: "/a/d/e.balance", Index: 4, Addition: `{"root_folder":"."}`},
|
||||||
}
|
}
|
||||||
for _, storage := range storages {
|
for _, storage := range storages {
|
||||||
err := operations.CreateStorage(context.Background(), storage)
|
err := operations.CreateStorage(context.Background(), storage)
|
||||||
|
|
Loading…
Reference in New Issue