mirror of https://github.com/cloudreve/Cloudreve
Fix: cannot edit file in remote server / Modify: separate preview and text-file content controller
parent
93dc25aabb
commit
d94896041e
|
@ -93,7 +93,6 @@ func (fs *FileSystem) Compress(ctx context.Context, folderIDs, fileIDs []uint) (
|
||||||
}
|
}
|
||||||
|
|
||||||
// cancelCompress 取消压缩进程
|
// cancelCompress 取消压缩进程
|
||||||
// TODO 测试
|
|
||||||
func (fs *FileSystem) cancelCompress(ctx context.Context, zipWriter *zip.Writer, file *os.File, path string) {
|
func (fs *FileSystem) cancelCompress(ctx context.Context, zipWriter *zip.Writer, file *os.File, path string) {
|
||||||
util.Log().Debug("客户端取消压缩请求")
|
util.Log().Debug("客户端取消压缩请求")
|
||||||
zipWriter.Close()
|
zipWriter.Close()
|
||||||
|
|
|
@ -88,14 +88,23 @@ func (fs *FileSystem) GetPhysicalFileContent(ctx context.Context, path string) (
|
||||||
}
|
}
|
||||||
|
|
||||||
// Preview 预览文件
|
// Preview 预览文件
|
||||||
func (fs *FileSystem) Preview(ctx context.Context, path string) (*response.ContentResponse, error) {
|
// path - 文件虚拟路径
|
||||||
|
// isText - 是否为文本文件,文本文件会忽略重定向,直接由
|
||||||
|
// 服务端拉取中转给用户,故会对文件大小进行限制
|
||||||
|
func (fs *FileSystem) Preview(ctx context.Context, path string, isText bool) (*response.ContentResponse, error) {
|
||||||
err := fs.resetFileIfNotExist(ctx, path)
|
err := fs.resetFileIfNotExist(ctx, path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 如果是文本文件预览,需要检查大小限制
|
||||||
|
sizeLimit := model.GetIntSetting("maxEditSize", 2<<20)
|
||||||
|
if fs.FileTarget[0].Size > uint64(sizeLimit) {
|
||||||
|
return nil, ErrFileSizeTooBig
|
||||||
|
}
|
||||||
|
|
||||||
// 是否直接返回文件内容
|
// 是否直接返回文件内容
|
||||||
if fs.Policy.IsDirectlyPreview() {
|
if isText || fs.Policy.IsDirectlyPreview() {
|
||||||
resp, err := fs.GetDownloadContent(ctx, path)
|
resp, err := fs.GetDownloadContent(ctx, path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
@ -510,7 +510,7 @@ func TestFileSystem_Preview(t *testing.T) {
|
||||||
User: &model.User{},
|
User: &model.User{},
|
||||||
}
|
}
|
||||||
mock.ExpectQuery("SELECT(.+)").WillReturnRows(sqlmock.NewRows([]string{"id"}))
|
mock.ExpectQuery("SELECT(.+)").WillReturnRows(sqlmock.NewRows([]string{"id"}))
|
||||||
resp, err := fs.Preview(ctx, "/1.txt")
|
resp, err := fs.Preview(ctx, "/1.txt", false)
|
||||||
asserts.NoError(mock.ExpectationsWereMet())
|
asserts.NoError(mock.ExpectationsWereMet())
|
||||||
asserts.Error(err)
|
asserts.Error(err)
|
||||||
asserts.Nil(resp)
|
asserts.Nil(resp)
|
||||||
|
@ -530,7 +530,7 @@ func TestFileSystem_Preview(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
resp, err := fs.Preview(ctx, "/1.txt")
|
resp, err := fs.Preview(ctx, "/1.txt", false)
|
||||||
asserts.Error(err)
|
asserts.Error(err)
|
||||||
asserts.Nil(resp)
|
asserts.Nil(resp)
|
||||||
}
|
}
|
||||||
|
@ -550,7 +550,7 @@ func TestFileSystem_Preview(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
resp, err := fs.Preview(ctx, "/1.txt")
|
resp, err := fs.Preview(ctx, "/1.txt", false)
|
||||||
asserts.NoError(err)
|
asserts.NoError(err)
|
||||||
asserts.NotNil(resp)
|
asserts.NotNil(resp)
|
||||||
asserts.False(resp.Redirect)
|
asserts.False(resp.Redirect)
|
||||||
|
@ -573,9 +573,31 @@ func TestFileSystem_Preview(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
asserts.NoError(cache.Set("setting_preview_timeout", "233", 0))
|
asserts.NoError(cache.Set("setting_preview_timeout", "233", 0))
|
||||||
resp, err := fs.Preview(ctx, "/1.txt")
|
resp, err := fs.Preview(ctx, "/1.txt", false)
|
||||||
asserts.NoError(err)
|
asserts.NoError(err)
|
||||||
asserts.NotNil(resp)
|
asserts.NotNil(resp)
|
||||||
asserts.True(resp.Redirect)
|
asserts.True(resp.Redirect)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 文本文件,大小超出限制
|
||||||
|
{
|
||||||
|
fs := FileSystem{
|
||||||
|
User: &model.User{},
|
||||||
|
}
|
||||||
|
fs.FileTarget = []model.File{
|
||||||
|
{
|
||||||
|
SourceName: "tests/file1.txt",
|
||||||
|
PolicyID: 1,
|
||||||
|
Policy: model.Policy{
|
||||||
|
Model: gorm.Model{ID: 1},
|
||||||
|
Type: "remote",
|
||||||
|
},
|
||||||
|
Size: 11,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
asserts.NoError(cache.Set("setting_maxEditSize", "10", 0))
|
||||||
|
resp, err := fs.Preview(ctx, "/1.txt", true)
|
||||||
|
asserts.Equal(ErrFileSizeTooBig, err)
|
||||||
|
asserts.Nil(resp)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -151,6 +151,7 @@ func (resp *Response) CheckHTTPResponse(status int) *Response {
|
||||||
|
|
||||||
type nopRSCloser struct {
|
type nopRSCloser struct {
|
||||||
body io.ReadCloser
|
body io.ReadCloser
|
||||||
|
size int64
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetRSCloser 返回带有空seeker的body reader
|
// GetRSCloser 返回带有空seeker的body reader
|
||||||
|
@ -161,6 +162,7 @@ func (resp *Response) GetRSCloser() (response.RSCloser, error) {
|
||||||
|
|
||||||
return nopRSCloser{
|
return nopRSCloser{
|
||||||
body: resp.Response.Body,
|
body: resp.Response.Body,
|
||||||
|
size: resp.Response.ContentLength,
|
||||||
}, resp.Err
|
}, resp.Err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -174,7 +176,16 @@ func (instance nopRSCloser) Close() error {
|
||||||
return instance.body.Close()
|
return instance.body.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 实现 nopRSCloser seeker
|
// 实现 nopRSCloser seeker, 只实现seek开头/结尾以便http.ServeContent用于确定正文大小
|
||||||
func (instance nopRSCloser) Seek(offset int64, whence int) (int64, error) {
|
func (instance nopRSCloser) Seek(offset int64, whence int) (int64, error) {
|
||||||
|
if offset == 0 {
|
||||||
|
switch whence {
|
||||||
|
case io.SeekStart:
|
||||||
|
return 0, nil
|
||||||
|
case io.SeekEnd:
|
||||||
|
return instance.size, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
return 0, errors.New("未实现")
|
return 0, errors.New("未实现")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -156,14 +156,20 @@ func TestResponse_GetRSCloser(t *testing.T) {
|
||||||
// 正常
|
// 正常
|
||||||
{
|
{
|
||||||
resp := Response{
|
resp := Response{
|
||||||
Response: &http.Response{Body: ioutil.NopCloser(strings.NewReader("123"))},
|
Response: &http.Response{ContentLength: 3, Body: ioutil.NopCloser(strings.NewReader("123"))},
|
||||||
}
|
}
|
||||||
res, err := resp.GetRSCloser()
|
res, err := resp.GetRSCloser()
|
||||||
asserts.NoError(err)
|
asserts.NoError(err)
|
||||||
content, err := ioutil.ReadAll(res)
|
content, err := ioutil.ReadAll(res)
|
||||||
asserts.NoError(err)
|
asserts.NoError(err)
|
||||||
asserts.Equal("123", string(content))
|
asserts.Equal("123", string(content))
|
||||||
_, err = res.Seek(0, 0)
|
offset, err := res.Seek(0, 0)
|
||||||
|
asserts.NoError(err)
|
||||||
|
asserts.Equal(int64(0), offset)
|
||||||
|
offset, err = res.Seek(0, 2)
|
||||||
|
asserts.NoError(err)
|
||||||
|
asserts.Equal(int64(3), offset)
|
||||||
|
_, err = res.Seek(1, 2)
|
||||||
asserts.Error(err)
|
asserts.Error(err)
|
||||||
asserts.NoError(res.Close())
|
asserts.NoError(res.Close())
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,7 +26,7 @@ func TestRemoteCallback(t *testing.T) {
|
||||||
"http://test/test/url",
|
"http://test/test/url",
|
||||||
testMock.Anything,
|
testMock.Anything,
|
||||||
testMock.Anything,
|
testMock.Anything,
|
||||||
).Return(Response{
|
).Return(&Response{
|
||||||
Err: nil,
|
Err: nil,
|
||||||
Response: &http.Response{
|
Response: &http.Response{
|
||||||
StatusCode: 200,
|
StatusCode: 200,
|
||||||
|
|
|
@ -230,7 +230,7 @@ func (h *Handler) handleGetHeadPost(w http.ResponseWriter, r *http.Request, fs *
|
||||||
|
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
rs, err := fs.Preview(ctx, reqPath)
|
rs, err := fs.Preview(ctx, reqPath, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == filesystem.ErrObjectNotExist {
|
if err == filesystem.ErrObjectNotExist {
|
||||||
return http.StatusNotFound, err
|
return http.StatusNotFound, err
|
||||||
|
|
|
@ -143,7 +143,7 @@ func Preview(c *gin.Context) {
|
||||||
|
|
||||||
var service explorer.SingleFileService
|
var service explorer.SingleFileService
|
||||||
if err := c.ShouldBindUri(&service); err == nil {
|
if err := c.ShouldBindUri(&service); err == nil {
|
||||||
res := service.PreviewContent(ctx, c)
|
res := service.PreviewContent(ctx, c, false)
|
||||||
// 是否需要重定向
|
// 是否需要重定向
|
||||||
if res.Code == -301 {
|
if res.Code == -301 {
|
||||||
c.Redirect(301, res.Data.(string))
|
c.Redirect(301, res.Data.(string))
|
||||||
|
@ -158,6 +158,24 @@ func Preview(c *gin.Context) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PreviewText 预览文本文件
|
||||||
|
func PreviewText(c *gin.Context) {
|
||||||
|
// 创建上下文
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
var service explorer.SingleFileService
|
||||||
|
if err := c.ShouldBindUri(&service); err == nil {
|
||||||
|
res := service.PreviewContent(ctx, c, true)
|
||||||
|
// 是否有错误发生
|
||||||
|
if res.Code != 0 {
|
||||||
|
c.JSON(200, res)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
c.JSON(200, ErrorResponse(err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// GetDocPreview 获取DOC文件预览地址
|
// GetDocPreview 获取DOC文件预览地址
|
||||||
func GetDocPreview(c *gin.Context) {
|
func GetDocPreview(c *gin.Context) {
|
||||||
// 创建上下文
|
// 创建上下文
|
||||||
|
|
|
@ -166,6 +166,8 @@ func InitMasterRouter() *gin.Engine {
|
||||||
file.PUT("download/*path", controllers.CreateDownloadSession)
|
file.PUT("download/*path", controllers.CreateDownloadSession)
|
||||||
// 预览文件
|
// 预览文件
|
||||||
file.GET("preview/*path", controllers.Preview)
|
file.GET("preview/*path", controllers.Preview)
|
||||||
|
// 获取文本文件内容
|
||||||
|
file.GET("content/*path", controllers.PreviewText)
|
||||||
// 取得Office文档预览地址
|
// 取得Office文档预览地址
|
||||||
file.GET("doc/*path", controllers.GetDocPreview)
|
file.GET("doc/*path", controllers.GetDocPreview)
|
||||||
// 获取缩略图
|
// 获取缩略图
|
||||||
|
|
|
@ -208,8 +208,9 @@ func (service *DownloadService) Download(ctx context.Context, c *gin.Context) se
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// PreviewContent 预览文件,需要登录会话
|
// PreviewContent 预览文件,需要登录会话, isText - 是否为文本文件,文本文件会
|
||||||
func (service *SingleFileService) PreviewContent(ctx context.Context, c *gin.Context) serializer.Response {
|
// 强制经由服务端中转
|
||||||
|
func (service *SingleFileService) PreviewContent(ctx context.Context, c *gin.Context, isText bool) serializer.Response {
|
||||||
// 创建文件系统
|
// 创建文件系统
|
||||||
fs, err := filesystem.NewFileSystemFromContext(c)
|
fs, err := filesystem.NewFileSystemFromContext(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -218,7 +219,7 @@ func (service *SingleFileService) PreviewContent(ctx context.Context, c *gin.Con
|
||||||
defer fs.Recycle()
|
defer fs.Recycle()
|
||||||
|
|
||||||
// 获取文件预览响应
|
// 获取文件预览响应
|
||||||
resp, err := fs.Preview(ctx, service.Path)
|
resp, err := fs.Preview(ctx, service.Path, isText)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return serializer.Err(serializer.CodeNotSet, err.Error(), err)
|
return serializer.Err(serializer.CodeNotSet, err.Error(), err)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue