From 06ff8b5a50c9a51ed1a36f6fa7f9393cf0fb5d82 Mon Sep 17 00:00:00 2001 From: HFO4 <912394456@qq.com> Date: Tue, 21 Jan 2020 13:27:20 +0800 Subject: [PATCH] Feat: get OneDrive thumbnails --- models/policy.go | 16 +++++++++ pkg/filesystem/driver/onedrive/api.go | 40 +++++++++++++++++++++- pkg/filesystem/driver/onedrive/handller.go | 28 ++++++++++++--- pkg/filesystem/driver/onedrive/types.go | 5 +++ pkg/filesystem/hooks.go | 2 ++ pkg/filesystem/image.go | 1 + routers/controllers/file.go | 14 ++++---- 7 files changed, 94 insertions(+), 12 deletions(-) diff --git a/models/policy.go b/models/policy.go index eeb9733..f545c43 100644 --- a/models/policy.go +++ b/models/policy.go @@ -161,6 +161,17 @@ func (policy *Policy) IsDirectlyPreview() bool { return policy.Type == "local" } +// IsTransitUpload 返回此策略上传给定size文件时是否需要服务端中转 +func (policy *Policy) IsTransitUpload(size uint64) bool { + if policy.Type == "local" { + return true + } + if policy.Type == "onedrive" && size < 4*1024*1024 { + return true + } + return false +} + // IsPathGenerateNeeded 返回此策略是否需要在生成上传凭证时生成存储路径 func (policy *Policy) IsPathGenerateNeeded() bool { return policy.Type != "remote" @@ -171,6 +182,11 @@ func (policy *Policy) IsThumbGenerateNeeded() bool { return policy.Type == "local" } +// IsMockThumbNeeded 返回此策略是否需要在上传后默认当图像文件 +func (policy *Policy) IsMockThumbNeeded() bool { + return policy.Type == "onedrive" +} + // GetUploadURL 获取文件上传服务API地址 func (policy *Policy) GetUploadURL() string { server, err := url.Parse(policy.Server) diff --git a/pkg/filesystem/driver/onedrive/api.go b/pkg/filesystem/driver/onedrive/api.go index 34a27cb..b77d611 100644 --- a/pkg/filesystem/driver/onedrive/api.go +++ b/pkg/filesystem/driver/onedrive/api.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "errors" + "fmt" model "github.com/HFO4/cloudreve/models" "github.com/HFO4/cloudreve/pkg/cache" "github.com/HFO4/cloudreve/pkg/request" @@ -18,6 +19,13 @@ import ( "time" ) +const ( + // SmallFileSize 单文件上传接口最大尺寸 + SmallFileSize uint64 = 4 * 1024 * 1024 + // ChunkSize 分片上传分片大小 + ChunkSize uint64 = 10 * 1024 * 1024 +) + // GetSourcePath 获取文件的绝对路径 func (info *FileInfo) GetSourcePath() string { res, err := url.PathUnescape( @@ -209,11 +217,41 @@ func (client *Client) makeBatchDeleteRequestsBody(files []string) string { return string(res) } +// GetThumbURL 获取给定尺寸的缩略图URL +func (client *Client) GetThumbURL(ctx context.Context, dst string, w, h uint) (string, error) { + dst = strings.TrimPrefix(dst, "/") + cropOption := fmt.Sprintf("c%dx%d_Crop", w, h) + requestURL := client.getRequestURL("me/drive/root:/"+dst+":/thumbnails") + "?select=" + cropOption + + res, err := client.requestWithStr(ctx, "GET", requestURL, "", 200) + if err != nil { + return "", err + } + + var ( + decodeErr error + thumbRes ThumbResponse + ) + decodeErr = json.Unmarshal([]byte(res), &thumbRes) + if decodeErr != nil { + return "", decodeErr + } + + if len(thumbRes.Value) == 1 { + if res, ok := thumbRes.Value[0][cropOption]; ok { + return res.(map[string]interface{})["url"].(string), nil + } + } + + return "", errors.New("无法生成缩略图") +} + // MonitorUpload 监控客户端分片上传进度 func (client *Client) MonitorUpload(uploadURL, callbackKey, path string, size uint64, ttl int64) { // 回调完成通知chan callbackChan := make(chan bool) callbackSignal.Store(callbackKey, callbackChan) + defer callbackSignal.Delete(callbackKey) timeout := model.GetIntSetting("onedrive_monitor_timeout", 600) interval := model.GetIntSetting("onedrive_callback_check", 20) @@ -339,7 +377,7 @@ func (client *Client) request(ctx context.Context, method string, url string, bo if res.Response.StatusCode != expectedCode { decodeErr = json.Unmarshal([]byte(respBody), &errResp) if decodeErr != nil { - return "", sysError(err) + return "", sysError(decodeErr) } return "", &errResp } diff --git a/pkg/filesystem/driver/onedrive/handller.go b/pkg/filesystem/driver/onedrive/handller.go index 6b2176d..27f0525 100644 --- a/pkg/filesystem/driver/onedrive/handller.go +++ b/pkg/filesystem/driver/onedrive/handller.go @@ -24,7 +24,9 @@ func (handler Driver) Get(ctx context.Context, path string) (response.RSCloser, // Put 将文件流保存到指定目录 func (handler Driver) Put(ctx context.Context, file io.ReadCloser, dst string, size uint64) error { - return errors.New("未实现") + defer file.Close() + _, err := handler.Client.PutFile(ctx, dst, file) + return err } // Delete 删除一个或多个文件, @@ -35,7 +37,25 @@ func (handler Driver) Delete(ctx context.Context, files []string) ([]string, err // Thumb 获取文件缩略图 func (handler Driver) Thumb(ctx context.Context, path string) (*response.ContentResponse, error) { - return nil, errors.New("未实现") + var ( + thumbSize = [2]uint{400, 300} + ok = false + ) + if thumbSize, ok = ctx.Value(fsctx.ThumbSizeCtx).([2]uint); !ok { + return nil, errors.New("无法获取缩略图尺寸设置") + } + + res, err := handler.Client.GetThumbURL(ctx, path, thumbSize[0], thumbSize[1]) + if err != nil { + // 如果出现异常,就清空文件的pic_info + if file, ok := ctx.Value(fsctx.FileModelCtx).(model.File); ok { + file.UpdatePicInfo("") + } + } + return &response.ContentResponse{ + Redirect: true, + URL: res, + }, err } // Source 获取外链URL @@ -63,8 +83,8 @@ func (handler Driver) Token(ctx context.Context, TTL int64, key string) (seriali return serializer.UploadCredential{}, errors.New("无法获取文件大小") } - // 如果小于10MB,则由服务端中转 - if fileSize <= 10*1024*1024 { + // 如果小于4MB,则由服务端中转 + if fileSize <= SmallFileSize { return serializer.UploadCredential{}, nil } diff --git a/pkg/filesystem/driver/onedrive/types.go b/pkg/filesystem/driver/onedrive/types.go index de41eee..b7b0970 100644 --- a/pkg/filesystem/driver/onedrive/types.go +++ b/pkg/filesystem/driver/onedrive/types.go @@ -74,4 +74,9 @@ type BatchResponse struct { Status int `json:"status"` } +// ThumbResponse 获取缩略图的响应 +type ThumbResponse struct { + Value []map[string]interface{} `json:"value"` +} + var callbackSignal sync.Map diff --git a/pkg/filesystem/hooks.go b/pkg/filesystem/hooks.go index 15c9d20..6561fca 100644 --- a/pkg/filesystem/hooks.go +++ b/pkg/filesystem/hooks.go @@ -271,6 +271,8 @@ func GenericAfterUpload(ctx context.Context, fs *FileSystem) error { // 异步尝试生成缩略图 if fs.User.Policy.IsThumbGenerateNeeded() { go fs.GenerateThumbnail(ctx, file) + } else if fs.User.Policy.IsMockThumbNeeded() { + file.UpdatePicInfo("1,1") } return nil diff --git a/pkg/filesystem/image.go b/pkg/filesystem/image.go index 7d23ea5..558f7d4 100644 --- a/pkg/filesystem/image.go +++ b/pkg/filesystem/image.go @@ -31,6 +31,7 @@ func (fs *FileSystem) GetThumb(ctx context.Context, id uint) (*response.ContentR w, h := fs.GenerateThumbnailSize(0, 0) ctx = context.WithValue(ctx, fsctx.ThumbSizeCtx, [2]uint{w, h}) + ctx = context.WithValue(ctx, fsctx.FileModelCtx, fs.FileTarget[0]) res, err := fs.Handler.Thumb(ctx, fs.FileTarget[0].SourceName) // TODO 出错时重新生成缩略图 diff --git a/routers/controllers/file.go b/routers/controllers/file.go index 8bdd682..c74fda2 100644 --- a/routers/controllers/file.go +++ b/routers/controllers/file.go @@ -3,7 +3,7 @@ package controllers import "C" import ( "context" - "github.com/HFO4/cloudreve/models" + model "github.com/HFO4/cloudreve/models" "github.com/HFO4/cloudreve/pkg/filesystem" "github.com/HFO4/cloudreve/pkg/filesystem/driver/local" "github.com/HFO4/cloudreve/pkg/filesystem/fsctx" @@ -244,12 +244,6 @@ func FileUploadStream(c *gin.Context) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - // 非本地策略时拒绝上传 - if user, ok := c.Get("user"); ok && user.(*model.User).Policy.Type != "local" { - c.JSON(200, serializer.Err(serializer.CodePolicyNotAllowed, "当前存储策略无法使用", nil)) - return - } - // 取得文件大小 fileSize, err := strconv.ParseUint(c.Request.Header.Get("Content-Length"), 10, 64) if err != nil { @@ -257,6 +251,12 @@ func FileUploadStream(c *gin.Context) { return } + // 非可用策略时拒绝上传 + if user, ok := c.Get("user"); ok && !user.(*model.User).Policy.IsTransitUpload(fileSize) { + c.JSON(200, serializer.Err(serializer.CodePolicyNotAllowed, "当前存储策略无法使用", nil)) + return + } + // 解码文件名和路径 fileName, err := url.QueryUnescape(c.Request.Header.Get("X-FileName")) filePath, err := url.QueryUnescape(c.Request.Header.Get("X-Path"))