Compare commits

..

1 commit

Author SHA1 Message Date
Weidi Deng
67e663c0fe fix file redirect status code 2023-07-15 18:30:52 +08:00
26 changed files with 82 additions and 560 deletions

View file

@ -1,5 +1,4 @@
[中文版本](https://github.com/cloudreve/Cloudreve/blob/master/README_zh-CN.md) •
[✨V4 版本前瞻](https://forum.cloudreve.org/d/4456)
[中文版本](https://github.com/cloudreve/Cloudreve/blob/master/README_zh-CN.md)
<h1 align="center">
<br>

2
assets

@ -1 +1 @@
Subproject commit 5d4d01a797a1ba2d6866799684bf05de20006e31
Subproject commit 0feca325f4b74d1c99f71ebced032f50da689da0

View file

@ -116,11 +116,6 @@ func WebDAVAuth() gin.HandlerFunc {
return
}
// 用户组已启用WebDAV代理
if !expectedUser.Group.OptionsSerialized.WebDAVProxy {
webdav.UseProxy = false
}
c.Set("user", &expectedUser)
c.Set("webdav", webdav)
c.Next()

View file

@ -121,9 +121,6 @@ Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; verti
{Name: "thumb_proxy_enabled", Value: "0", Type: "thumb"},
{Name: "thumb_proxy_policy", Value: "[]", Type: "thumb"},
{Name: "thumb_max_src_size", Value: "31457280", Type: "thumb"},
{Name: "thumb_libraw_path", Value: "simple_dcraw", Type: "thumb"},
{Name: "thumb_libraw_enabled", Value: "0", Type: "thumb"},
{Name: "thumb_libraw_exts", Value: "arw,raf,dng", Type: "thumb"},
{Name: "pwa_small_icon", Value: "/static/img/favicon.ico", Type: "pwa"},
{Name: "pwa_medium_icon", Value: "/static/img/logo192.png", Type: "pwa"},
{Name: "pwa_large_icon", Value: "/static/img/logo512.png", Type: "pwa"},

View file

@ -18,8 +18,7 @@ type Folder struct {
OwnerID uint `gorm:"index:owner_id"`
// 数据库忽略字段
Position string `gorm:"-"`
WebdavDstName string `gorm:"-"`
Position string `gorm:"-"`
}
// Create 创建目录
@ -170,11 +169,6 @@ func (folder *Folder) MoveOrCopyFileTo(files []uint, dstFolder *Folder, isCopy b
oldFile.FolderID = dstFolder.ID
oldFile.UserID = dstFolder.OwnerID
// webdav目标名重置
if dstFolder.WebdavDstName != "" {
oldFile.Name = dstFolder.WebdavDstName
}
if err := DB.Create(&oldFile).Error; err != nil {
return copiedSize, err
}
@ -183,14 +177,6 @@ func (folder *Folder) MoveOrCopyFileTo(files []uint, dstFolder *Folder, isCopy b
}
} else {
var updates = map[string]interface{}{
"folder_id": dstFolder.ID,
}
// webdav目标名重置
if dstFolder.WebdavDstName != "" {
updates["name"] = dstFolder.WebdavDstName
}
// 更改顶级要移动文件的父目录指向
err := DB.Model(File{}).Where(
"id in (?) and user_id = ? and folder_id = ?",
@ -198,7 +184,9 @@ func (folder *Folder) MoveOrCopyFileTo(files []uint, dstFolder *Folder, isCopy b
folder.OwnerID,
folder.ID,
).
Update(updates).
Update(map[string]interface{}{
"folder_id": dstFolder.ID,
}).
Error
if err != nil {
return 0, err
@ -233,10 +221,6 @@ func (folder *Folder) CopyFolderTo(folderID uint, dstFolder *Folder) (size uint6
// 顶级目录直接指向新的目的目录
if folder.ID == folderID {
newID = dstFolder.ID
// webdav目标名重置
if dstFolder.WebdavDstName != "" {
folder.Name = dstFolder.WebdavDstName
}
} else if IDCache, ok := newIDCache[*folder.ParentID]; ok {
newID = IDCache
} else {
@ -298,21 +282,15 @@ func (folder *Folder) MoveFolderTo(dirs []uint, dstFolder *Folder) error {
return errors.New("cannot move a folder into itself")
}
var updates = map[string]interface{}{
"parent_id": dstFolder.ID,
}
// webdav目标名重置
if dstFolder.WebdavDstName != "" {
updates["name"] = dstFolder.WebdavDstName
}
// 更改顶级要移动目录的父目录指向
err := DB.Model(Folder{}).Where(
"id in (?) and owner_id = ? and parent_id = ?",
dirs,
folder.OwnerID,
folder.ID,
).Update(updates).Error
).Update(map[string]interface{}{
"parent_id": dstFolder.ID,
}).Error
return err

View file

@ -35,7 +35,6 @@ type GroupOption struct {
RedirectedSource bool `json:"redirected_source,omitempty"`
Aria2BatchSize int `json:"aria2_batch,omitempty"`
AdvanceDelete bool `json:"advance_delete,omitempty"`
WebDAVProxy bool `json:"webdav_proxy,omitempty"`
}
// GetGroupByID 用ID获取用户组

View file

@ -12,7 +12,6 @@ type Webdav struct {
UserID uint `gorm:"unique_index:password_only_on"` // 用户ID
Root string `gorm:"type:text"` // 根目录
Readonly bool `gorm:"type:bool"` // 是否只读
UseProxy bool `gorm:"type:bool"` // 是否进行反代
}
// Create 创建账户
@ -42,7 +41,7 @@ func DeleteWebDAVAccountByID(id, uid uint) {
DB.Where("user_id = ? and id = ?", uid, id).Delete(&Webdav{})
}
// UpdateWebDAVAccountByID 根据账户ID和UID更新账户
func UpdateWebDAVAccountByID(id, uid uint, updates map[string]interface{}) {
DB.Model(&Webdav{Model: gorm.Model{ID: id}, UserID: uid}).Updates(updates)
// UpdateWebDAVAccountReadonlyByID 根据账户ID和UID更新账户的只读性
func UpdateWebDAVAccountReadonlyByID(id, uid uint, readonly bool) {
DB.Model(&Webdav{Model: gorm.Model{ID: id}, UserID: uid}).UpdateColumn("readonly", readonly)
}

View file

@ -1,13 +1,13 @@
package conf
// BackendVersion 当前后端版本号
var BackendVersion = "3.8.3"
var BackendVersion = "3.8.0"
// RequiredDBVersion 与当前版本匹配的数据库版本
var RequiredDBVersion = "3.8.1"
var RequiredDBVersion = "3.8.0-beta1"
// RequiredStaticVersion 与当前版本匹配的静态资源版本
var RequiredStaticVersion = "3.8.3"
var RequiredStaticVersion = "3.8.0-beta1"
// IsPro 是否为Pro版本
var IsPro = "false"

View file

@ -1,8 +1,6 @@
package email
import (
"fmt"
"github.com/google/uuid"
"time"
"github.com/cloudreve/Cloudreve/v3/pkg/util"
@ -52,7 +50,6 @@ func (client *SMTP) Send(to, title, body string) error {
m.SetAddressHeader("Reply-To", client.Config.ReplyTo, client.Config.Name)
m.SetHeader("To", to)
m.SetHeader("Subject", title)
m.SetHeader("Message-ID", fmt.Sprintf("<%s@%s>", uuid.NewString(), "cloudreve"))
m.SetBody("text/html", body)
client.ch <- m
return nil

View file

@ -35,10 +35,4 @@ const (
CancelFuncCtx
// 文件在从机节点中的路径
SlaveSrcPath
// Webdav目标名称
WebdavDstName
// WebDAVCtx WebDAV
WebDAVCtx
// WebDAV反代Url
WebDAVProxyUrlCtx
)

View file

@ -145,7 +145,6 @@ func (fs *FileSystem) generateThumbnail(ctx context.Context, file *model.File) e
"thumb_vips_enabled",
"thumb_ffmpeg_enabled",
"thumb_libreoffice_enabled",
"thumb_libraw_enabled",
))
if err != nil {
_ = updateThumbStatus(file, model.ThumbStatusNotAvailable)

View file

@ -69,11 +69,6 @@ func (fs *FileSystem) Copy(ctx context.Context, dirs, files []uint, src, dst str
// 记录复制的文件的总容量
var newUsedStorage uint64
// 设置webdav目标名
if dstName, ok := ctx.Value(fsctx.WebdavDstName).(string); ok {
dstFolder.WebdavDstName = dstName
}
// 复制目录
if len(dirs) > 0 {
subFileSizes, err := srcFolder.CopyFolderTo(dirs[0], dstFolder)
@ -108,11 +103,6 @@ func (fs *FileSystem) Move(ctx context.Context, dirs, files []uint, src, dst str
return ErrPathNotExist
}
// 设置webdav目标名
if dstName, ok := ctx.Value(fsctx.WebdavDstName).(string); ok {
dstFolder.WebdavDstName = dstName
}
// 处理目录及子文件移动
err := srcFolder.MoveFolderTo(dirs, dstFolder)
if err != nil {

View file

@ -42,7 +42,6 @@ type group struct {
WebDAVEnabled bool `json:"webdav"`
SourceBatchSize int `json:"sourceBatch"`
AdvanceDelete bool `json:"advanceDelete"`
AllowWebDAVProxy bool `json:"allowWebDAVProxy"`
}
type tag struct {
@ -101,7 +100,6 @@ func BuildUser(user model.User) User {
ShareDownload: user.Group.OptionsSerialized.ShareDownload,
CompressEnabled: user.Group.OptionsSerialized.ArchiveTask,
WebDAVEnabled: user.Group.WebDAVEnabled,
AllowWebDAVProxy: user.Group.OptionsSerialized.WebDAVProxy,
SourceBatchSize: user.Group.OptionsSerialized.SourceBatchSize,
AdvanceDelete: user.Group.OptionsSerialized.AdvanceDelete,
},

View file

@ -4,15 +4,14 @@ import (
"bytes"
"context"
"fmt"
model "github.com/cloudreve/Cloudreve/v3/models"
"github.com/cloudreve/Cloudreve/v3/pkg/util"
"github.com/gofrs/uuid"
"io"
"os"
"os/exec"
"path/filepath"
"strings"
model "github.com/cloudreve/Cloudreve/v3/models"
"github.com/cloudreve/Cloudreve/v3/pkg/util"
"github.com/gofrs/uuid"
)
func init() {
@ -25,18 +24,10 @@ type FfmpegGenerator struct {
}
func (f *FfmpegGenerator) Generate(ctx context.Context, file io.Reader, src, name string, options map[string]string) (*Result, error) {
const (
thumbFFMpegPath = "thumb_ffmpeg_path"
thumbFFMpegExts = "thumb_ffmpeg_exts"
thumbFFMpegSeek = "thumb_ffmpeg_seek"
thumbEncodeMethod = "thumb_encode_method"
tempPath = "temp_path"
)
ffmpegOpts := model.GetSettingByNames(thumbFFMpegPath, thumbFFMpegExts, thumbFFMpegSeek, thumbEncodeMethod, tempPath)
ffmpegOpts := model.GetSettingByNames("thumb_ffmpeg_path", "thumb_ffmpeg_exts", "thumb_ffmpeg_seek", "thumb_encode_method", "temp_path")
if f.lastRawExts != ffmpegOpts[thumbFFMpegExts] {
f.exts = strings.Split(ffmpegOpts[thumbFFMpegExts], ",")
f.lastRawExts = ffmpegOpts[thumbFFMpegExts]
if f.lastRawExts != ffmpegOpts["thumb_ffmpeg_exts"] {
f.exts = strings.Split(ffmpegOpts["thumb_ffmpeg_exts"], ",")
}
if !util.IsInExtensionList(f.exts, name) {
@ -44,16 +35,16 @@ func (f *FfmpegGenerator) Generate(ctx context.Context, file io.Reader, src, nam
}
tempOutputPath := filepath.Join(
util.RelativePath(ffmpegOpts[tempPath]),
util.RelativePath(ffmpegOpts["temp_path"]),
"thumb",
fmt.Sprintf("thumb_%s.%s", uuid.Must(uuid.NewV4()).String(), ffmpegOpts[thumbEncodeMethod]),
fmt.Sprintf("thumb_%s.%s", uuid.Must(uuid.NewV4()).String(), ffmpegOpts["thumb_encode_method"]),
)
tempInputPath := src
if tempInputPath == "" {
// If not local policy files, download to temp folder
tempInputPath = filepath.Join(
util.RelativePath(ffmpegOpts[tempPath]),
util.RelativePath(ffmpegOpts["temp_path"]),
"thumb",
fmt.Sprintf("ffmpeg_%s%s", uuid.Must(uuid.NewV4()).String(), filepath.Ext(name)),
)
@ -76,8 +67,9 @@ func (f *FfmpegGenerator) Generate(ctx context.Context, file io.Reader, src, nam
// Invoke ffmpeg
scaleOpt := fmt.Sprintf("scale=%s:%s:force_original_aspect_ratio=decrease", options["thumb_width"], options["thumb_height"])
inputFormat := filepath.Ext(name)[1:]
cmd := exec.CommandContext(ctx,
ffmpegOpts[thumbFFMpegPath], "-ss", ffmpegOpts[thumbFFMpegSeek], "-i", tempInputPath,
ffmpegOpts["thumb_ffmpeg_path"], "-ss", ffmpegOpts["thumb_ffmpeg_seek"], "-f", inputFormat, "-i", tempInputPath,
"-vf", scaleOpt, "-vframes", "1", tempOutputPath)
// Redirect IO

View file

@ -1,283 +0,0 @@
package thumb
import (
"bytes"
"context"
"errors"
"fmt"
"image"
"image/jpeg"
"image/png"
"io"
"os"
"os/exec"
"path/filepath"
"strings"
model "github.com/cloudreve/Cloudreve/v3/models"
"github.com/cloudreve/Cloudreve/v3/pkg/util"
"github.com/gofrs/uuid"
)
func init() {
RegisterGenerator(&LibRawGenerator{})
}
type LibRawGenerator struct {
exts []string
lastRawExts string
}
func (f *LibRawGenerator) Generate(ctx context.Context, file io.Reader, _ string, name string, options map[string]string) (*Result, error) {
const (
thumbLibRawPath = "thumb_libraw_path"
thumbLibRawExt = "thumb_libraw_exts"
thumbTempPath = "temp_path"
)
opts := model.GetSettingByNames(thumbLibRawPath, thumbLibRawExt, thumbTempPath)
if f.lastRawExts != opts[thumbLibRawExt] {
f.exts = strings.Split(opts[thumbLibRawExt], ",")
f.lastRawExts = opts[thumbLibRawExt]
}
if !util.IsInExtensionList(f.exts, name) {
return nil, fmt.Errorf("unsupported image format: %w", ErrPassThrough)
}
inputFilePath := filepath.Join(
util.RelativePath(opts[thumbTempPath]),
"thumb",
fmt.Sprintf("thumb_%s", uuid.Must(uuid.NewV4()).String()),
)
defer func() { _ = os.Remove(inputFilePath) }()
inputFile, err := util.CreatNestedFile(inputFilePath)
if err != nil {
return nil, fmt.Errorf("failed to create temp file: %w", err)
}
if _, err = io.Copy(inputFile, file); err != nil {
_ = inputFile.Close()
return nil, fmt.Errorf("failed to write input file: %w", err)
}
_ = inputFile.Close()
cmd := exec.CommandContext(ctx, opts[thumbLibRawPath], "-e", inputFilePath)
var stdErr bytes.Buffer
cmd.Stderr = &stdErr
if err = cmd.Run(); err != nil {
util.Log().Warning("Failed to invoke LibRaw: %s", stdErr.String())
return nil, fmt.Errorf("failed to invoke LibRaw: %w", err)
}
outputFilePath := inputFilePath + ".thumb.jpg"
defer func() { _ = os.Remove(outputFilePath) }()
ff, err := os.Open(outputFilePath)
if err != nil {
return nil, fmt.Errorf("failed to open temp file: %w", err)
}
defer func() { _ = ff.Close() }()
// use builtin generator
result, err := new(Builtin).Generate(ctx, ff, outputFilePath, filepath.Base(outputFilePath), options)
if err != nil {
return nil, fmt.Errorf("failed to generate thumbnail: %w", err)
}
orientation, err := getJpegOrientation(outputFilePath)
if err != nil {
return nil, fmt.Errorf("failed to get jpeg orientation: %w", err)
}
if orientation == 1 {
return result, nil
}
if err = rotateImg(result.Path, orientation); err != nil {
return nil, fmt.Errorf("failed to rotate image: %w", err)
}
return result, nil
}
func rotateImg(filePath string, orientation int) error {
resultImg, err := os.OpenFile(filePath, os.O_RDWR, 0777)
if err != nil {
return err
}
defer func() { _ = resultImg.Close() }()
imgFlag := make([]byte, 3)
if _, err = io.ReadFull(resultImg, imgFlag); err != nil {
return err
}
if _, err = resultImg.Seek(0, 0); err != nil {
return err
}
var img image.Image
if bytes.Equal(imgFlag, []byte{0xFF, 0xD8, 0xFF}) {
img, err = jpeg.Decode(resultImg)
} else {
img, err = png.Decode(resultImg)
}
if err != nil {
return err
}
switch orientation {
case 8:
img = rotate90(img)
case 3:
img = rotate90(rotate90(img))
case 6:
img = rotate90(rotate90(rotate90(img)))
case 2:
img = mirrorImg(img)
case 7:
img = rotate90(mirrorImg(img))
case 4:
img = rotate90(rotate90(mirrorImg(img)))
case 5:
img = rotate90(rotate90(rotate90(mirrorImg(img))))
}
if err = resultImg.Truncate(0); err != nil {
return err
}
if _, err = resultImg.Seek(0, 0); err != nil {
return err
}
if bytes.Equal(imgFlag, []byte{0xFF, 0xD8, 0xFF}) {
return jpeg.Encode(resultImg, img, nil)
}
return png.Encode(resultImg, img)
}
func getJpegOrientation(fileName string) (int, error) {
f, err := os.Open(fileName)
if err != nil {
return 0, err
}
defer func() { _ = f.Close() }()
header := make([]byte, 6)
defer func() { header = nil }()
if _, err = io.ReadFull(f, header); err != nil {
return 0, err
}
// jpeg format header
if !bytes.Equal(header[:3], []byte{0xFF, 0xD8, 0xFF}) {
return 0, errors.New("not a jpeg")
}
// not a APP1 marker
if header[3] != 0xE1 {
return 1, nil
}
// exif data total length
totalLen := int(header[4])<<8 + int(header[5]) - 2
buf := make([]byte, totalLen)
defer func() { buf = nil }()
if _, err = io.ReadFull(f, buf); err != nil {
return 0, err
}
// remove Exif identifier code
buf = buf[6:]
// byte order
parse16, parse32, err := initParseMethod(buf[:2])
if err != nil {
return 0, err
}
// version
_ = buf[2:4]
// first IFD offset
offset := parse32(buf[4:8])
// first DE offset
offset += 2
buf = buf[offset:]
const (
orientationTag = 0x112
deEntryLength = 12
)
for len(buf) > deEntryLength {
tag := parse16(buf[:2])
if tag == orientationTag {
return int(parse32(buf[8:12])), nil
}
buf = buf[deEntryLength:]
}
return 0, errors.New("orientation not found")
}
func initParseMethod(buf []byte) (func([]byte) int16, func([]byte) int32, error) {
if bytes.Equal(buf, []byte{0x49, 0x49}) {
return littleEndian16, littleEndian32, nil
}
if bytes.Equal(buf, []byte{0x4D, 0x4D}) {
return bigEndian16, bigEndian32, nil
}
return nil, nil, errors.New("invalid byte order")
}
func littleEndian16(buf []byte) int16 {
return int16(buf[0]) | int16(buf[1])<<8
}
func bigEndian16(buf []byte) int16 {
return int16(buf[1]) | int16(buf[0])<<8
}
func littleEndian32(buf []byte) int32 {
return int32(buf[0]) | int32(buf[1])<<8 | int32(buf[2])<<16 | int32(buf[3])<<24
}
func bigEndian32(buf []byte) int32 {
return int32(buf[3]) | int32(buf[2])<<8 | int32(buf[1])<<16 | int32(buf[0])<<24
}
func rotate90(img image.Image) image.Image {
bounds := img.Bounds()
width, height := bounds.Dx(), bounds.Dy()
newImg := image.NewRGBA(image.Rect(0, 0, height, width))
for x := 0; x < width; x++ {
for y := 0; y < height; y++ {
newImg.Set(y, width-x-1, img.At(x, y))
}
}
return newImg
}
func mirrorImg(img image.Image) image.Image {
bounds := img.Bounds()
width, height := bounds.Dx(), bounds.Dy()
newImg := image.NewRGBA(image.Rect(0, 0, width, height))
for x := 0; x < width; x++ {
for y := 0; y < height; y++ {
newImg.Set(width-x-1, y, img.At(x, y))
}
}
return newImg
}
func (f *LibRawGenerator) Priority() int {
return 250
}
func (f *LibRawGenerator) EnableFlag() string {
return "thumb_libraw_enabled"
}
var _ Generator = (*LibRawGenerator)(nil)

View file

@ -4,15 +4,14 @@ import (
"bytes"
"context"
"fmt"
model "github.com/cloudreve/Cloudreve/v3/models"
"github.com/cloudreve/Cloudreve/v3/pkg/util"
"github.com/gofrs/uuid"
"io"
"os"
"os/exec"
"path/filepath"
"strings"
model "github.com/cloudreve/Cloudreve/v3/models"
"github.com/cloudreve/Cloudreve/v3/pkg/util"
"github.com/gofrs/uuid"
)
func init() {
@ -25,17 +24,10 @@ type LibreOfficeGenerator struct {
}
func (l *LibreOfficeGenerator) Generate(ctx context.Context, file io.Reader, src string, name string, options map[string]string) (*Result, error) {
const (
thumbLibreOfficePath = "thumb_libreoffice_path"
thumbLibreOfficeExts = "thumb_libreoffice_exts"
thumbEncodeMethod = "thumb_encode_method"
tempPath = "temp_path"
)
sofficeOpts := model.GetSettingByNames(thumbLibreOfficePath, thumbLibreOfficeExts, thumbEncodeMethod, tempPath)
sofficeOpts := model.GetSettingByNames("thumb_libreoffice_path", "thumb_libreoffice_exts", "thumb_encode_method", "temp_path")
if l.lastRawExts != sofficeOpts[thumbLibreOfficeExts] {
l.exts = strings.Split(sofficeOpts[thumbLibreOfficeExts], ",")
l.lastRawExts = sofficeOpts[thumbLibreOfficeExts]
if l.lastRawExts != sofficeOpts["thumb_libreoffice_exts"] {
l.exts = strings.Split(sofficeOpts["thumb_libreoffice_exts"], ",")
}
if !util.IsInExtensionList(l.exts, name) {
@ -43,7 +35,7 @@ func (l *LibreOfficeGenerator) Generate(ctx context.Context, file io.Reader, src
}
tempOutputPath := filepath.Join(
util.RelativePath(sofficeOpts[tempPath]),
util.RelativePath(sofficeOpts["temp_path"]),
"thumb",
fmt.Sprintf("soffice_%s", uuid.Must(uuid.NewV4()).String()),
)
@ -52,7 +44,7 @@ func (l *LibreOfficeGenerator) Generate(ctx context.Context, file io.Reader, src
if tempInputPath == "" {
// If not local policy files, download to temp folder
tempInputPath = filepath.Join(
util.RelativePath(sofficeOpts[tempPath]),
util.RelativePath(sofficeOpts["temp_path"]),
"thumb",
fmt.Sprintf("soffice_%s%s", uuid.Must(uuid.NewV4()).String(), filepath.Ext(name)),
)
@ -74,9 +66,9 @@ func (l *LibreOfficeGenerator) Generate(ctx context.Context, file io.Reader, src
}
// Convert the document to an image
cmd := exec.CommandContext(ctx, sofficeOpts[thumbLibreOfficePath], "--headless",
cmd := exec.CommandContext(ctx, sofficeOpts["thumb_libreoffice_path"], "--headless",
"-nologo", "--nofirststartwizard", "--invisible", "--norestore", "--convert-to",
sofficeOpts[thumbEncodeMethod], "--outdir", tempOutputPath, tempInputPath)
sofficeOpts["thumb_encode_method"], "--outdir", tempOutputPath, tempInputPath)
// Redirect IO
var stdErr bytes.Buffer
@ -91,7 +83,7 @@ func (l *LibreOfficeGenerator) Generate(ctx context.Context, file io.Reader, src
return &Result{
Path: filepath.Join(
tempOutputPath,
strings.TrimSuffix(filepath.Base(tempInputPath), filepath.Ext(tempInputPath))+"."+sofficeOpts[thumbEncodeMethod],
strings.TrimSuffix(filepath.Base(tempInputPath), filepath.Ext(tempInputPath))+"."+sofficeOpts["thumb_encode_method"],
),
Continue: true,
Cleanup: []func(){func() { _ = os.RemoveAll(tempOutputPath) }},

View file

@ -23,8 +23,6 @@ func TestGenerator(ctx context.Context, name, executable string) (string, error)
return testFfmpegGenerator(ctx, executable)
case "libreOffice":
return testLibreOfficeGenerator(ctx, executable)
case "libRaw":
return testLibRawGenerator(ctx, executable)
default:
return "", ErrUnknownGenerator
}
@ -74,18 +72,3 @@ func testLibreOfficeGenerator(ctx context.Context, executable string) (string, e
return output.String(), nil
}
func testLibRawGenerator(ctx context.Context, executable string) (string, error) {
cmd := exec.CommandContext(ctx, executable)
var output bytes.Buffer
cmd.Stdout = &output
if err := cmd.Run(); err != nil {
return "", fmt.Errorf("failed to invoke libraw executable: %w", err)
}
if !strings.Contains(output.String(), "LibRaw") {
return "", ErrUnknownOutput
}
return output.String(), nil
}

View file

@ -4,14 +4,13 @@ import (
"bytes"
"context"
"fmt"
model "github.com/cloudreve/Cloudreve/v3/models"
"github.com/cloudreve/Cloudreve/v3/pkg/util"
"github.com/gofrs/uuid"
"io"
"os/exec"
"path/filepath"
"strings"
model "github.com/cloudreve/Cloudreve/v3/models"
"github.com/cloudreve/Cloudreve/v3/pkg/util"
"github.com/gofrs/uuid"
)
func init() {
@ -24,18 +23,10 @@ type VipsGenerator struct {
}
func (v *VipsGenerator) Generate(ctx context.Context, file io.Reader, src, name string, options map[string]string) (*Result, error) {
const (
thumbVipsPath = "thumb_vips_path"
thumbVipsExts = "thumb_vips_exts"
thumbEncodeQuality = "thumb_encode_quality"
thumbEncodeMethod = "thumb_encode_method"
tempPath = "temp_path"
)
vipsOpts := model.GetSettingByNames(thumbVipsPath, thumbVipsExts, thumbEncodeQuality, thumbEncodeMethod, tempPath)
vipsOpts := model.GetSettingByNames("thumb_vips_path", "thumb_vips_exts", "thumb_encode_quality", "thumb_encode_method", "temp_path")
if v.lastRawExts != vipsOpts[thumbVipsExts] {
v.exts = strings.Split(vipsOpts[thumbVipsExts], ",")
v.lastRawExts = vipsOpts[thumbVipsExts]
if v.lastRawExts != vipsOpts["thumb_vips_exts"] {
v.exts = strings.Split(vipsOpts["thumb_vips_exts"], ",")
}
if !util.IsInExtensionList(v.exts, name) {
@ -43,21 +34,21 @@ func (v *VipsGenerator) Generate(ctx context.Context, file io.Reader, src, name
}
outputOpt := ".png"
if vipsOpts[thumbEncodeMethod] == "jpg" {
outputOpt = fmt.Sprintf(".jpg[Q=%s]", vipsOpts[thumbEncodeQuality])
if vipsOpts["thumb_encode_method"] == "jpg" {
outputOpt = fmt.Sprintf(".jpg[Q=%s]", vipsOpts["thumb_encode_quality"])
}
cmd := exec.CommandContext(ctx,
vipsOpts[thumbVipsPath], "thumbnail_source", "[descriptor=0]", outputOpt, options["thumb_width"],
vipsOpts["thumb_vips_path"], "thumbnail_source", "[descriptor=0]", outputOpt, options["thumb_width"],
"--height", options["thumb_height"])
outTempPath := filepath.Join(
util.RelativePath(vipsOpts[tempPath]),
tempPath := filepath.Join(
util.RelativePath(vipsOpts["temp_path"]),
"thumb",
fmt.Sprintf("thumb_%s", uuid.Must(uuid.NewV4()).String()),
)
thumbFile, err := util.CreatNestedFile(outTempPath)
thumbFile, err := util.CreatNestedFile(tempPath)
if err != nil {
return nil, fmt.Errorf("failed to create temp file: %w", err)
}
@ -75,7 +66,7 @@ func (v *VipsGenerator) Generate(ctx context.Context, file io.Reader, src, name
return nil, fmt.Errorf("failed to invoke vips: %w", err)
}
return &Result{Path: outTempPath}, nil
return &Result{Path: tempPath}, nil
}
func (v *VipsGenerator) Priority() int {

View file

@ -9,12 +9,9 @@ import (
"net/http"
"path"
"path/filepath"
"strconv"
"time"
model "github.com/cloudreve/Cloudreve/v3/models"
"github.com/cloudreve/Cloudreve/v3/pkg/filesystem"
"github.com/cloudreve/Cloudreve/v3/pkg/filesystem/fsctx"
)
// slashClean is equivalent to but slightly more efficient than
@ -26,31 +23,6 @@ func slashClean(name string) string {
return path.Clean(name)
}
// 更新Copy或Move后的修改时间
func updateCopyMoveModtime(req *http.Request, fs *filesystem.FileSystem, dst string) error {
var modtime time.Time
if timeVal := req.Header.Get("X-OC-Mtime"); timeVal != "" {
timeUnix, err := strconv.ParseInt(timeVal, 10, 64)
if err == nil {
modtime = time.Unix(timeUnix, 0)
}
}
if modtime.IsZero() {
return nil
}
ok, fi := isPathExist(req.Context(), fs, dst)
if !ok {
return nil
}
if fi.IsDir() {
return model.DB.Model(fi.(*model.Folder)).UpdateColumn("updated_at", modtime).Error
}
return model.DB.Model(fi.(*model.File)).UpdateColumn("updated_at", modtime).Error
}
// moveFiles moves files and/or directories from src to dst.
//
// See section 9.9.4 for when various HTTP status codes apply.
@ -72,17 +44,20 @@ func moveFiles(ctx context.Context, fs *filesystem.FileSystem, src FileInfo, dst
}
}
// 判断是否需要移动
if src.GetPosition() != path.Dir(dst) {
err = fs.Move(
context.WithValue(ctx, fsctx.WebdavDstName, path.Base(dst)),
ctx,
folderIDs,
fileIDs,
src.GetPosition(),
path.Dir(dst),
)
} else if src.GetName() != path.Base(dst) {
// 判断是否需要重命名
}
// 判断是否需要重命名
if err == nil && src.GetName() != path.Base(dst) {
err = fs.Rename(
ctx,
folderIDs,
@ -106,6 +81,7 @@ func copyFiles(ctx context.Context, fs *filesystem.FileSystem, src FileInfo, dst
}
recursion++
var (
fileIDs []uint
folderIDs []uint
@ -124,7 +100,7 @@ func copyFiles(ctx context.Context, fs *filesystem.FileSystem, src FileInfo, dst
}
err = fs.Copy(
context.WithValue(ctx, fsctx.WebdavDstName, path.Base(dst)),
ctx,
folderIDs,
fileIDs,
src.GetPosition(),

View file

@ -49,7 +49,7 @@ func (file *FileDeadProps) Patch(proppatches []Proppatch) ([]Propstat, error) {
var modtimeUnix int64
modtimeUnix, err = strconv.ParseInt(string(prop.InnerXML), 10, 64)
if err == nil {
err = model.DB.Model(file.File).UpdateColumn("updated_at", time.Unix(modtimeUnix, 0)).Error
err = model.DB.Model(file).UpdateColumn("updated_at", time.Unix(modtimeUnix, 0)).Error
}
}
}
@ -78,7 +78,7 @@ func (folder *FolderDeadProps) Patch(proppatches []Proppatch) ([]Propstat, error
var modtimeUnix int64
modtimeUnix, err = strconv.ParseInt(string(prop.InnerXML), 10, 64)
if err == nil {
err = model.DB.Model(folder.Folder).UpdateColumn("updated_at", time.Unix(modtimeUnix, 0)).Error
err = model.DB.Model(folder).UpdateColumn("updated_at", time.Unix(modtimeUnix, 0)).Error
}
}
}

View file

@ -10,7 +10,6 @@ import (
"errors"
"fmt"
"net/http"
"net/http/httputil"
"net/url"
"path"
"strconv"
@ -242,23 +241,6 @@ func (h *Handler) handleOptions(w http.ResponseWriter, r *http.Request, fs *file
return 0, nil
}
var proxy = &httputil.ReverseProxy{
Director: func(request *http.Request) {
if target, ok := request.Context().Value(fsctx.WebDAVProxyUrlCtx).(*url.URL); ok {
request.URL.Scheme = target.Scheme
request.URL.Host = target.Host
request.URL.Path = target.Path
request.URL.RawPath = target.RawPath
request.URL.RawQuery = target.RawQuery
request.Host = target.Host
request.Header.Del("Authorization")
}
},
ErrorHandler: func(writer http.ResponseWriter, request *http.Request, err error) {
writer.WriteHeader(http.StatusInternalServerError)
},
}
// OK
func (h *Handler) handleGetHeadPost(w http.ResponseWriter, r *http.Request, fs *filesystem.FileSystem) (status int, err error) {
defer fs.Recycle()
@ -297,23 +279,7 @@ func (h *Handler) handleGetHeadPost(w http.ResponseWriter, r *http.Request, fs *
return 0, nil
}
if application, ok := r.Context().Value(fsctx.WebDAVCtx).(*model.Webdav); ok && application.UseProxy {
target, err := url.Parse(rs.URL)
if err != nil {
return http.StatusInternalServerError, err
}
r = r.Clone(context.WithValue(r.Context(), fsctx.WebDAVProxyUrlCtx, target))
// 忽略反向代理在传输错误时报错
defer func() {
if err := recover(); err != nil && err != http.ErrAbortHandler {
panic(err)
}
}()
proxy.ServeHTTP(w, r)
} else {
http.Redirect(w, r, rs.URL, 301)
}
http.Redirect(w, r, rs.URL, 302)
return 0, nil
}
@ -530,16 +496,7 @@ func (h *Handler) handleCopyMove(w http.ResponseWriter, r *http.Request, fs *fil
return http.StatusBadRequest, errInvalidDepth
}
}
status, err = copyFiles(ctx, fs, target, dst, r.Header.Get("Overwrite") != "F", depth, 0)
if err != nil {
return status, err
}
err = updateCopyMoveModtime(r, fs, dst)
if err != nil {
return http.StatusInternalServerError, err
}
return status, nil
return copyFiles(ctx, fs, target, dst, r.Header.Get("Overwrite") != "F", depth, 0)
}
// windows下某些情况下网盘根目录下Office保存文件时附带的锁token只包含源文件
@ -558,16 +515,7 @@ func (h *Handler) handleCopyMove(w http.ResponseWriter, r *http.Request, fs *fil
return http.StatusBadRequest, errInvalidDepth
}
}
status, err = moveFiles(ctx, fs, target, dst, r.Header.Get("Overwrite") == "T")
if err != nil {
return status, err
}
err = updateCopyMoveModtime(r, fs, dst)
if err != nil {
return http.StatusInternalServerError, err
}
return status, nil
return moveFiles(ctx, fs, target, dst, r.Header.Get("Overwrite") == "T")
}
// OK

View file

@ -1,10 +1,8 @@
package controllers
import (
"context"
model "github.com/cloudreve/Cloudreve/v3/models"
"github.com/cloudreve/Cloudreve/v3/pkg/filesystem"
"github.com/cloudreve/Cloudreve/v3/pkg/filesystem/fsctx"
"github.com/cloudreve/Cloudreve/v3/pkg/util"
"github.com/cloudreve/Cloudreve/v3/pkg/webdav"
"github.com/cloudreve/Cloudreve/v3/service/setting"
@ -51,9 +49,6 @@ func ServeWebDAV(c *gin.Context) {
return
}
}
// 更新Context
c.Request = c.Request.WithContext(context.WithValue(c.Request.Context(), fsctx.WebDAVCtx, application))
}
handler.ServeHTTP(c.Writer, c.Request, fs)
@ -81,9 +76,9 @@ func DeleteWebDAVAccounts(c *gin.Context) {
}
}
// UpdateWebDAVAccounts 更改WebDAV账户只读性和是否使用代理服务
func UpdateWebDAVAccounts(c *gin.Context) {
var service setting.WebDAVAccountUpdateService
// UpdateWebDAVAccountsReadonly 更改WebDAV账户只读性
func UpdateWebDAVAccountsReadonly(c *gin.Context) {
var service setting.WebDAVAccountUpdateReadonlyService
if err := c.ShouldBindJSON(&service); err == nil {
res := service.Update(c, CurrentUser(c))
c.JSON(200, res)

View file

@ -225,7 +225,7 @@ func InitMasterRouter() *gin.Engine {
middleware.StaticResourceCache(),
controllers.AnonymousGetContent,
)
// 文件外链(301跳转)
// 文件外链(302跳转)
file.GET("source/:id/:name", controllers.AnonymousPermLinkDeprecated)
// 下载文件
file.GET("download/:id",
@ -721,8 +721,8 @@ func InitMasterRouter() *gin.Engine {
webdav.POST("accounts", controllers.CreateWebDAVAccounts)
// 删除账号
webdav.DELETE("accounts/:id", controllers.DeleteWebDAVAccounts)
// 更新账号可读性和是否使用代理服务
webdav.PATCH("accounts", controllers.UpdateWebDAVAccounts)
// 更新账号可读性
webdav.PATCH("accounts", controllers.UpdateWebDAVAccountsReadonly)
}
}

View file

@ -112,13 +112,8 @@ func (service *AddUserService) Add() serializer.Response {
user.TwoFactor = service.User.TwoFactor
// 检查愚蠢操作
if user.ID == 1 {
if user.GroupID != 1 {
return serializer.Err(serializer.CodeChangeGroupForDefaultUser, "", nil)
}
if user.Status != model.Active {
return serializer.Err(serializer.CodeInvalidActionOnDefaultUser, "", nil)
}
if user.ID == 1 && user.GroupID != 1 {
return serializer.Err(serializer.CodeChangeGroupForDefaultUser, "", nil)
}
if err := model.DB.Save(&user).Error; err != nil {

View file

@ -227,14 +227,8 @@ func (service *FileIDService) CreateDocPreviewSession(ctx context.Context, c *gi
return serializer.Err(serializer.CodeNotSet, err.Error(), err)
}
// For newer version of Cloudreve - Local Policy
// When do not use a cdn, the downloadURL withouts hosts, like "/api/v3/file/download/xxx"
if strings.HasPrefix(downloadURL, "/") {
downloadURI, err := url.Parse(downloadURL)
if err != nil {
return serializer.Err(serializer.CodeNotSet, err.Error(), err)
}
downloadURL = model.GetSiteURL().ResolveReference(downloadURI).String()
downloadURL = path.Join(model.GetSiteURL().String(), downloadURL)
}
var resp serializer.DocPreviewSession

View file

@ -22,11 +22,10 @@ type WebDAVAccountCreateService struct {
Name string `json:"name" binding:"required,min=1,max=255"`
}
// WebDAVAccountUpdateService WebDAV 修改只读性和是否使用代理服务
type WebDAVAccountUpdateService struct {
ID uint `json:"id" binding:"required,min=1"`
Readonly *bool `json:"readonly" binding:"required_without=UseProxy"`
UseProxy *bool `json:"use_proxy" binding:"required_without=Readonly"`
// WebDAVAccountUpdateReadonlyService WebDAV 修改只读性服务
type WebDAVAccountUpdateReadonlyService struct {
ID uint `json:"id" binding:"required,min=1"`
Readonly bool `json:"readonly"`
}
// WebDAVMountCreateService WebDAV 挂载创建服务
@ -63,17 +62,12 @@ func (service *WebDAVAccountService) Delete(c *gin.Context, user *model.User) se
return serializer.Response{}
}
// Update 修改WebDAV账户只读性和是否使用代理服务
func (service *WebDAVAccountUpdateService) Update(c *gin.Context, user *model.User) serializer.Response {
var updates = make(map[string]interface{})
if service.Readonly != nil {
updates["readonly"] = *service.Readonly
}
if service.UseProxy != nil {
updates["use_proxy"] = *service.UseProxy
}
model.UpdateWebDAVAccountByID(service.ID, user.ID, updates)
return serializer.Response{Data: updates}
// Update 修改WebDAV账户的只读性
func (service *WebDAVAccountUpdateReadonlyService) Update(c *gin.Context, user *model.User) serializer.Response {
model.UpdateWebDAVAccountReadonlyByID(service.ID, user.ID, service.Readonly)
return serializer.Response{Data: map[string]bool{
"readonly": service.Readonly,
}}
}
// Accounts 列出WebDAV账号