-
-
Notifications
You must be signed in to change notification settings - Fork 5.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
Co-authored-by: Andy Hsu <i@nn.ci>
- Loading branch information
Showing
8 changed files
with
556 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,224 @@ | ||
package dropbox | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"io" | ||
"math" | ||
"net/http" | ||
"time" | ||
|
||
"github.com/alist-org/alist/v3/drivers/base" | ||
"github.com/alist-org/alist/v3/internal/driver" | ||
"github.com/alist-org/alist/v3/internal/model" | ||
"github.com/alist-org/alist/v3/pkg/utils" | ||
"github.com/go-resty/resty/v2" | ||
log "github.com/sirupsen/logrus" | ||
) | ||
|
||
type Dropbox struct { | ||
model.Storage | ||
Addition | ||
base string | ||
contentBase string | ||
} | ||
|
||
func (d *Dropbox) Config() driver.Config { | ||
return config | ||
} | ||
|
||
func (d *Dropbox) GetAddition() driver.Additional { | ||
return &d.Addition | ||
} | ||
|
||
func (d *Dropbox) Init(ctx context.Context) error { | ||
query := "foo" | ||
res, err := d.request("/2/check/user", http.MethodPost, func(req *resty.Request) { | ||
req.SetBody(base.Json{ | ||
"query": query, | ||
}) | ||
}) | ||
if err != nil { | ||
return err | ||
} | ||
result := utils.Json.Get(res, "result").ToString() | ||
if result != query { | ||
return fmt.Errorf("failed to check user: %s", string(res)) | ||
} | ||
return nil | ||
} | ||
|
||
func (d *Dropbox) Drop(ctx context.Context) error { | ||
return nil | ||
} | ||
|
||
func (d *Dropbox) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) { | ||
files, err := d.getFiles(ctx, dir.GetPath()) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return utils.SliceConvert(files, func(src File) (model.Obj, error) { | ||
return fileToObj(src), nil | ||
}) | ||
} | ||
|
||
func (d *Dropbox) link(ctx context.Context, file model.Obj) (*model.Link, error) { | ||
res, err := d.request("/2/files/get_temporary_link", http.MethodPost, func(req *resty.Request) { | ||
req.SetBody(base.Json{ | ||
"path": file.GetID(), | ||
}) | ||
}) | ||
if err != nil { | ||
return nil, err | ||
} | ||
url := utils.Json.Get(res, "link").ToString() | ||
exp := time.Hour | ||
return &model.Link{ | ||
URL: url, | ||
Expiration: &exp, | ||
}, nil | ||
} | ||
|
||
func (d *Dropbox) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) { | ||
return d.link(ctx, file) | ||
} | ||
|
||
func (d *Dropbox) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error { | ||
_, err := d.request("/2/files/create_folder_v2", http.MethodPost, func(req *resty.Request) { | ||
req.SetBody(base.Json{ | ||
"autorename": false, | ||
"path": parentDir.GetPath() + "/" + dirName, | ||
}) | ||
}) | ||
return err | ||
} | ||
|
||
func (d *Dropbox) Move(ctx context.Context, srcObj, dstDir model.Obj) error { | ||
toPath := dstDir.GetPath() + "/" + srcObj.GetName() | ||
|
||
_, err := d.request("/2/files/move_v2", http.MethodPost, func(req *resty.Request) { | ||
req.SetBody(base.Json{ | ||
"allow_ownership_transfer": false, | ||
"allow_shared_folder": false, | ||
"autorename": false, | ||
"from_path": srcObj.GetID(), | ||
"to_path": toPath, | ||
}) | ||
}) | ||
return err | ||
} | ||
|
||
func (d *Dropbox) Rename(ctx context.Context, srcObj model.Obj, newName string) error { | ||
path := srcObj.GetPath() | ||
fileName := srcObj.GetName() | ||
toPath := path[:len(path)-len(fileName)] + newName | ||
|
||
_, err := d.request("/2/files/move_v2", http.MethodPost, func(req *resty.Request) { | ||
req.SetBody(base.Json{ | ||
"allow_ownership_transfer": false, | ||
"allow_shared_folder": false, | ||
"autorename": false, | ||
"from_path": srcObj.GetID(), | ||
"to_path": toPath, | ||
}) | ||
}) | ||
return err | ||
} | ||
|
||
func (d *Dropbox) Copy(ctx context.Context, srcObj, dstDir model.Obj) error { | ||
toPath := dstDir.GetPath() + "/" + srcObj.GetName() | ||
_, err := d.request("/2/files/copy_v2", http.MethodPost, func(req *resty.Request) { | ||
req.SetBody(base.Json{ | ||
"allow_ownership_transfer": false, | ||
"allow_shared_folder": false, | ||
"autorename": false, | ||
"from_path": srcObj.GetID(), | ||
"to_path": toPath, | ||
}) | ||
}) | ||
return err | ||
} | ||
|
||
func (d *Dropbox) Remove(ctx context.Context, obj model.Obj) error { | ||
uri := "/2/files/delete_v2" | ||
_, err := d.request(uri, http.MethodPost, func(req *resty.Request) { | ||
req.SetBody(base.Json{ | ||
"path": obj.GetID(), | ||
}) | ||
}) | ||
return err | ||
} | ||
|
||
func (d *Dropbox) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error { | ||
// 1. start | ||
sessionId, err := d.startUploadSession(ctx) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
// 2.append | ||
// A single request should not upload more than 150 MB, and each call must be multiple of 4MB (except for last call) | ||
const PartSize = 20971520 | ||
count := 1 | ||
if stream.GetSize() > PartSize { | ||
count = int(math.Ceil(float64(stream.GetSize()) / float64(PartSize))) | ||
} | ||
offset := int64(0) | ||
|
||
for i := 0; i < count; i++ { | ||
if utils.IsCanceled(ctx) { | ||
return ctx.Err() | ||
} | ||
|
||
start := i * PartSize | ||
byteSize := stream.GetSize() - int64(start) | ||
if byteSize > PartSize { | ||
byteSize = PartSize | ||
} | ||
|
||
url := d.contentBase + "/2/files/upload_session/append_v2" | ||
reader := io.LimitReader(stream, PartSize) | ||
req, err := http.NewRequest(http.MethodPost, url, reader) | ||
if err != nil { | ||
log.Errorf("failed to update file when append to upload session, err: %+v", err) | ||
return err | ||
} | ||
req = req.WithContext(ctx) | ||
req.Header.Set("Content-Type", "application/octet-stream") | ||
req.Header.Set("Authorization", "Bearer "+d.AccessToken) | ||
|
||
args := UploadAppendArgs{ | ||
Close: false, | ||
Cursor: UploadCursor{ | ||
Offset: offset, | ||
SessionID: sessionId, | ||
}, | ||
} | ||
argsJson, _ := utils.Json.MarshalToString(args) | ||
req.Header.Set("Dropbox-API-Arg", argsJson) | ||
|
||
res, err := base.HttpClient.Do(req) | ||
_ = res.Body.Close() | ||
|
||
if err != nil { | ||
return err | ||
} | ||
|
||
if count > 0 { | ||
up((i + 1) * 100 / count) | ||
} | ||
|
||
offset += byteSize | ||
|
||
} | ||
// 3.finish | ||
toPath := dstDir.GetPath() + "/" + stream.GetName() | ||
err2 := d.finishUploadSession(ctx, toPath, offset, sessionId) | ||
if err2 != nil { | ||
return err2 | ||
} | ||
|
||
return err | ||
} | ||
|
||
var _ driver.Driver = (*Dropbox)(nil) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
package dropbox | ||
|
||
import ( | ||
"github.com/alist-org/alist/v3/internal/driver" | ||
"github.com/alist-org/alist/v3/internal/op" | ||
) | ||
|
||
const ( | ||
DefaultClientID = "76lrwrklhdn1icb" | ||
) | ||
|
||
type Addition struct { | ||
RefreshToken string `json:"refresh_token" required:"true"` | ||
driver.RootPath | ||
|
||
OauthTokenURL string `json:"oauth_token_url" default:"https://api.xhofe.top/alist/dropbox/token"` | ||
ClientID string `json:"client_id" required:"false" help:"Keep it empty if you don't have one"` | ||
ClientSecret string `json:"client_secret" required:"false" help:"Keep it empty if you don't have one"` | ||
|
||
AccessToken string | ||
} | ||
|
||
var config = driver.Config{ | ||
Name: "Dropbox", | ||
LocalSort: false, | ||
OnlyLocal: false, | ||
OnlyProxy: false, | ||
NoCache: false, | ||
NoUpload: false, | ||
NeedMs: false, | ||
DefaultRoot: "", | ||
NoOverwriteUpload: true, | ||
} | ||
|
||
func init() { | ||
op.RegisterDriver(func() driver.Driver { | ||
return &Dropbox{ | ||
base: "https://api.dropboxapi.com", | ||
contentBase: "https://content.dropboxapi.com", | ||
} | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
package dropbox | ||
|
||
import ( | ||
"github.com/alist-org/alist/v3/internal/model" | ||
"time" | ||
) | ||
|
||
type TokenResp struct { | ||
AccessToken string `json:"access_token"` | ||
TokenType string `json:"token_type"` | ||
ExpiresIn int `json:"expires_in"` | ||
} | ||
|
||
type ErrorResp struct { | ||
Error struct { | ||
Tag string `json:".tag"` | ||
} `json:"error"` | ||
ErrorSummary string `json:"error_summary"` | ||
} | ||
|
||
type RefreshTokenErrorResp struct { | ||
Error string `json:"error"` | ||
ErrorDescription string `json:"error_description"` | ||
} | ||
|
||
type File struct { | ||
Tag string `json:".tag"` | ||
Name string `json:"name"` | ||
PathLower string `json:"path_lower"` | ||
PathDisplay string `json:"path_display"` | ||
ID string `json:"id"` | ||
ClientModified time.Time `json:"client_modified"` | ||
ServerModified time.Time `json:"server_modified"` | ||
Rev string `json:"rev"` | ||
Size int `json:"size"` | ||
IsDownloadable bool `json:"is_downloadable"` | ||
ContentHash string `json:"content_hash"` | ||
} | ||
|
||
type ListResp struct { | ||
Entries []File `json:"entries"` | ||
Cursor string `json:"cursor"` | ||
HasMore bool `json:"has_more"` | ||
} | ||
|
||
type UploadCursor struct { | ||
Offset int64 `json:"offset"` | ||
SessionID string `json:"session_id"` | ||
} | ||
|
||
type UploadAppendArgs struct { | ||
Close bool `json:"close"` | ||
Cursor UploadCursor `json:"cursor"` | ||
} | ||
|
||
type UploadFinishArgs struct { | ||
Commit struct { | ||
Autorename bool `json:"autorename"` | ||
Mode string `json:"mode"` | ||
Mute bool `json:"mute"` | ||
Path string `json:"path"` | ||
StrictConflict bool `json:"strict_conflict"` | ||
} `json:"commit"` | ||
Cursor UploadCursor `json:"cursor"` | ||
} | ||
|
||
func fileToObj(f File) *model.ObjThumb { | ||
return &model.ObjThumb{ | ||
Object: model.Object{ | ||
ID: f.ID, | ||
Path: f.PathDisplay, | ||
Name: f.Name, | ||
Size: int64(f.Size), | ||
Modified: f.ServerModified, | ||
IsFolder: f.Tag == "folder", | ||
}, | ||
Thumbnail: model.Thumbnail{}, | ||
} | ||
} |
Oops, something went wrong.
cfee536
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
请问这是什么问题? 所有文件/任意文件都是这样的错误
参数填写配置
也尝试过使用自己新建的客户端和秘钥也是一样的错误
cfee536
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
我只测试了一下list的方法 之后再看一下
cfee536
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
2ae9cd8 需要重新获取一下token
cfee536
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
已重新获取了token,可以获取列表,不过出现新的错误。
cfee536
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
我本地测试已经没问题了,可能是因为access_token还没有失效的原因,所以使用的还是之前的token,你可以去数据库中手动删除access_token或者重新添加一个存储。
cfee536
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
这样可以了,(以前遇到过类似的也是删除驱动重新加了一个就好了)😃😃😃
cfee536
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
可以使用自己创建的app进行授权访问,大致步骤如下
1 . 访问https://www.dropbox.com/developers创建自己的app
2. 配置权限,最小权限集
3. 浏览器登陆需要挂载的Dropbox,访问https://www.dropbox.com/oauth2/authorize?client_id=&response_type=code&token_access_type=offline, APPKEY改为自己创建的app key完成授权, 记录code,下一步需要用
4. curl https://api.dropbox.com/oauth2/token
-d code=<上一步授权的临时code>
-d grant_type=authorization_code
-u <app_key>:<app_secret>
这一步会返回refresh_token,是长期有效的,除非主动吊销
配置自己的appkey,appsecret和refresh_token即可
cfee536
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
哈哈 写的很好,不过还是 xhofe的小工具方便些