init
This commit is contained in:
175
pkg/task/compress.go
Normal file
175
pkg/task/compress.go
Normal file
@ -0,0 +1,175 @@
|
||||
package task
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
model "github.com/cloudreve/Cloudreve/v3/models"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/filesystem"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/util"
|
||||
)
|
||||
|
||||
// CompressTask 文件压缩任务
|
||||
type CompressTask struct {
|
||||
User *model.User
|
||||
TaskModel *model.Task
|
||||
TaskProps CompressProps
|
||||
Err *JobError
|
||||
|
||||
zipPath string
|
||||
}
|
||||
|
||||
// CompressProps 压缩任务属性
|
||||
type CompressProps struct {
|
||||
Dirs []uint `json:"dirs"`
|
||||
Files []uint `json:"files"`
|
||||
Dst string `json:"dst"`
|
||||
}
|
||||
|
||||
// Props 获取任务属性
|
||||
func (job *CompressTask) Props() string {
|
||||
res, _ := json.Marshal(job.TaskProps)
|
||||
return string(res)
|
||||
}
|
||||
|
||||
// Type 获取任务状态
|
||||
func (job *CompressTask) Type() int {
|
||||
return CompressTaskType
|
||||
}
|
||||
|
||||
// Creator 获取创建者ID
|
||||
func (job *CompressTask) Creator() uint {
|
||||
return job.User.ID
|
||||
}
|
||||
|
||||
// Model 获取任务的数据库模型
|
||||
func (job *CompressTask) Model() *model.Task {
|
||||
return job.TaskModel
|
||||
}
|
||||
|
||||
// SetStatus 设定状态
|
||||
func (job *CompressTask) SetStatus(status int) {
|
||||
job.TaskModel.SetStatus(status)
|
||||
}
|
||||
|
||||
// SetError 设定任务失败信息
|
||||
func (job *CompressTask) SetError(err *JobError) {
|
||||
job.Err = err
|
||||
res, _ := json.Marshal(job.Err)
|
||||
job.TaskModel.SetError(string(res))
|
||||
|
||||
// 删除压缩文件
|
||||
job.removeZipFile()
|
||||
}
|
||||
|
||||
func (job *CompressTask) removeZipFile() {
|
||||
if job.zipPath != "" {
|
||||
if err := os.Remove(job.zipPath); err != nil {
|
||||
util.Log().Warning("Failed to delete temp zip file %q: %s", job.zipPath, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SetErrorMsg 设定任务失败信息
|
||||
func (job *CompressTask) SetErrorMsg(msg string) {
|
||||
job.SetError(&JobError{Msg: msg})
|
||||
}
|
||||
|
||||
// GetError 返回任务失败信息
|
||||
func (job *CompressTask) GetError() *JobError {
|
||||
return job.Err
|
||||
}
|
||||
|
||||
// Do 开始执行任务
|
||||
func (job *CompressTask) Do() {
|
||||
// 创建文件系统
|
||||
fs, err := filesystem.NewFileSystem(job.User)
|
||||
if err != nil {
|
||||
job.SetErrorMsg(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
util.Log().Debug("Starting compress file...")
|
||||
job.TaskModel.SetProgress(CompressingProgress)
|
||||
|
||||
// 创建临时压缩文件
|
||||
saveFolder := "compress"
|
||||
zipFilePath := filepath.Join(
|
||||
util.RelativePath(model.GetSettingByName("temp_path")),
|
||||
saveFolder,
|
||||
fmt.Sprintf("archive_%d.zip", time.Now().UnixNano()),
|
||||
)
|
||||
zipFile, err := util.CreatNestedFile(zipFilePath)
|
||||
if err != nil {
|
||||
util.Log().Warning("%s", err)
|
||||
job.SetErrorMsg(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
defer zipFile.Close()
|
||||
|
||||
// 开始压缩
|
||||
ctx := context.Background()
|
||||
err = fs.Compress(ctx, zipFile, job.TaskProps.Dirs, job.TaskProps.Files, false)
|
||||
if err != nil {
|
||||
job.SetErrorMsg(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
job.zipPath = zipFilePath
|
||||
zipFile.Close()
|
||||
util.Log().Debug("Compressed file saved to %q, start uploading it...", zipFile)
|
||||
job.TaskModel.SetProgress(TransferringProgress)
|
||||
|
||||
// 上传文件
|
||||
err = fs.UploadFromPath(ctx, zipFilePath, job.TaskProps.Dst, 0)
|
||||
if err != nil {
|
||||
job.SetErrorMsg(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
job.removeZipFile()
|
||||
}
|
||||
|
||||
// NewCompressTask 新建压缩任务
|
||||
func NewCompressTask(user *model.User, dst string, dirs, files []uint) (Job, error) {
|
||||
newTask := &CompressTask{
|
||||
User: user,
|
||||
TaskProps: CompressProps{
|
||||
Dirs: dirs,
|
||||
Files: files,
|
||||
Dst: dst,
|
||||
},
|
||||
}
|
||||
|
||||
record, err := Record(newTask)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
newTask.TaskModel = record
|
||||
|
||||
return newTask, nil
|
||||
}
|
||||
|
||||
// NewRelocateTaskFromModel 从数据库记录中恢复迁移任务
|
||||
func NewCompressTaskFromModel(task *model.Task) (Job, error) {
|
||||
user, err := model.GetActiveUserByID(task.UserID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
newTask := &CompressTask{
|
||||
User: &user,
|
||||
TaskModel: task,
|
||||
}
|
||||
|
||||
err = json.Unmarshal([]byte(task.Props), &newTask.TaskProps)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return newTask, nil
|
||||
}
|
131
pkg/task/decompress.go
Normal file
131
pkg/task/decompress.go
Normal file
@ -0,0 +1,131 @@
|
||||
package task
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
|
||||
model "github.com/cloudreve/Cloudreve/v3/models"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/filesystem"
|
||||
)
|
||||
|
||||
// DecompressTask 文件压缩任务
|
||||
type DecompressTask struct {
|
||||
User *model.User
|
||||
TaskModel *model.Task
|
||||
TaskProps DecompressProps
|
||||
Err *JobError
|
||||
|
||||
zipPath string
|
||||
}
|
||||
|
||||
// DecompressProps 压缩任务属性
|
||||
type DecompressProps struct {
|
||||
Src string `json:"src"`
|
||||
Dst string `json:"dst"`
|
||||
Encoding string `json:"encoding"`
|
||||
}
|
||||
|
||||
// Props 获取任务属性
|
||||
func (job *DecompressTask) Props() string {
|
||||
res, _ := json.Marshal(job.TaskProps)
|
||||
return string(res)
|
||||
}
|
||||
|
||||
// Type 获取任务状态
|
||||
func (job *DecompressTask) Type() int {
|
||||
return DecompressTaskType
|
||||
}
|
||||
|
||||
// Creator 获取创建者ID
|
||||
func (job *DecompressTask) Creator() uint {
|
||||
return job.User.ID
|
||||
}
|
||||
|
||||
// Model 获取任务的数据库模型
|
||||
func (job *DecompressTask) Model() *model.Task {
|
||||
return job.TaskModel
|
||||
}
|
||||
|
||||
// SetStatus 设定状态
|
||||
func (job *DecompressTask) SetStatus(status int) {
|
||||
job.TaskModel.SetStatus(status)
|
||||
}
|
||||
|
||||
// SetError 设定任务失败信息
|
||||
func (job *DecompressTask) SetError(err *JobError) {
|
||||
job.Err = err
|
||||
res, _ := json.Marshal(job.Err)
|
||||
job.TaskModel.SetError(string(res))
|
||||
}
|
||||
|
||||
// SetErrorMsg 设定任务失败信息
|
||||
func (job *DecompressTask) SetErrorMsg(msg string, err error) {
|
||||
jobErr := &JobError{Msg: msg}
|
||||
if err != nil {
|
||||
jobErr.Error = err.Error()
|
||||
}
|
||||
job.SetError(jobErr)
|
||||
}
|
||||
|
||||
// GetError 返回任务失败信息
|
||||
func (job *DecompressTask) GetError() *JobError {
|
||||
return job.Err
|
||||
}
|
||||
|
||||
// Do 开始执行任务
|
||||
func (job *DecompressTask) Do() {
|
||||
// 创建文件系统
|
||||
fs, err := filesystem.NewFileSystem(job.User)
|
||||
if err != nil {
|
||||
job.SetErrorMsg("Failed to create filesystem.", err)
|
||||
return
|
||||
}
|
||||
|
||||
job.TaskModel.SetProgress(DecompressingProgress)
|
||||
|
||||
err = fs.Decompress(context.Background(), job.TaskProps.Src, job.TaskProps.Dst, job.TaskProps.Encoding)
|
||||
if err != nil {
|
||||
job.SetErrorMsg("Failed to decompress file.", err)
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// NewDecompressTask 新建压缩任务
|
||||
func NewDecompressTask(user *model.User, src, dst, encoding string) (Job, error) {
|
||||
newTask := &DecompressTask{
|
||||
User: user,
|
||||
TaskProps: DecompressProps{
|
||||
Src: src,
|
||||
Dst: dst,
|
||||
Encoding: encoding,
|
||||
},
|
||||
}
|
||||
|
||||
record, err := Record(newTask)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
newTask.TaskModel = record
|
||||
|
||||
return newTask, nil
|
||||
}
|
||||
|
||||
// NewDecompressTaskFromModel 从数据库记录中恢复压缩任务
|
||||
func NewDecompressTaskFromModel(task *model.Task) (Job, error) {
|
||||
user, err := model.GetActiveUserByID(task.UserID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
newTask := &DecompressTask{
|
||||
User: &user,
|
||||
TaskModel: task,
|
||||
}
|
||||
|
||||
err = json.Unmarshal([]byte(task.Props), &newTask.TaskProps)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return newTask, nil
|
||||
}
|
8
pkg/task/errors.go
Normal file
8
pkg/task/errors.go
Normal file
@ -0,0 +1,8 @@
|
||||
package task
|
||||
|
||||
import "errors"
|
||||
|
||||
var (
|
||||
// ErrUnknownTaskType 未知任务类型
|
||||
ErrUnknownTaskType = errors.New("unknown task type")
|
||||
)
|
220
pkg/task/import.go
Normal file
220
pkg/task/import.go
Normal file
@ -0,0 +1,220 @@
|
||||
package task
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"path"
|
||||
|
||||
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"
|
||||
)
|
||||
|
||||
// ImportTask 导入务
|
||||
type ImportTask struct {
|
||||
User *model.User
|
||||
TaskModel *model.Task
|
||||
TaskProps ImportProps
|
||||
Err *JobError
|
||||
}
|
||||
|
||||
// ImportProps 导入任务属性
|
||||
type ImportProps struct {
|
||||
PolicyID uint `json:"policy_id"` // 存储策略ID
|
||||
Src string `json:"src"` // 原始路径
|
||||
Recursive bool `json:"is_recursive"` // 是否递归导入
|
||||
Dst string `json:"dst"` // 目的目录
|
||||
}
|
||||
|
||||
// Props 获取任务属性
|
||||
func (job *ImportTask) Props() string {
|
||||
res, _ := json.Marshal(job.TaskProps)
|
||||
return string(res)
|
||||
}
|
||||
|
||||
// Type 获取任务状态
|
||||
func (job *ImportTask) Type() int {
|
||||
return ImportTaskType
|
||||
}
|
||||
|
||||
// Creator 获取创建者ID
|
||||
func (job *ImportTask) Creator() uint {
|
||||
return job.User.ID
|
||||
}
|
||||
|
||||
// Model 获取任务的数据库模型
|
||||
func (job *ImportTask) Model() *model.Task {
|
||||
return job.TaskModel
|
||||
}
|
||||
|
||||
// SetStatus 设定状态
|
||||
func (job *ImportTask) SetStatus(status int) {
|
||||
job.TaskModel.SetStatus(status)
|
||||
}
|
||||
|
||||
// SetError 设定任务失败信息
|
||||
func (job *ImportTask) SetError(err *JobError) {
|
||||
job.Err = err
|
||||
res, _ := json.Marshal(job.Err)
|
||||
job.TaskModel.SetError(string(res))
|
||||
}
|
||||
|
||||
// SetErrorMsg 设定任务失败信息
|
||||
func (job *ImportTask) SetErrorMsg(msg string, err error) {
|
||||
jobErr := &JobError{Msg: msg}
|
||||
if err != nil {
|
||||
jobErr.Error = err.Error()
|
||||
}
|
||||
job.SetError(jobErr)
|
||||
}
|
||||
|
||||
// GetError 返回任务失败信息
|
||||
func (job *ImportTask) GetError() *JobError {
|
||||
return job.Err
|
||||
}
|
||||
|
||||
// Do 开始执行任务
|
||||
func (job *ImportTask) Do() {
|
||||
ctx := context.Background()
|
||||
|
||||
// 查找存储策略
|
||||
policy, err := model.GetPolicyByID(job.TaskProps.PolicyID)
|
||||
if err != nil {
|
||||
job.SetErrorMsg("Policy not exist.", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 创建文件系统
|
||||
fs, err := filesystem.NewFileSystem(job.User)
|
||||
if err != nil {
|
||||
job.SetErrorMsg(err.Error(), nil)
|
||||
return
|
||||
}
|
||||
defer fs.Recycle()
|
||||
|
||||
fs.Policy = &policy
|
||||
if err := fs.DispatchHandler(); err != nil {
|
||||
job.SetErrorMsg("Failed to dispatch policy.", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 注册钩子
|
||||
fs.Use("BeforeAddFile", filesystem.HookValidateFile)
|
||||
fs.Use("BeforeAddFile", filesystem.HookValidateCapacity)
|
||||
|
||||
// 列取目录、对象
|
||||
job.TaskModel.SetProgress(ListingProgress)
|
||||
coxIgnoreConflict := context.WithValue(context.Background(), fsctx.IgnoreDirectoryConflictCtx,
|
||||
true)
|
||||
objects, err := fs.Handler.List(ctx, job.TaskProps.Src, job.TaskProps.Recursive)
|
||||
if err != nil {
|
||||
job.SetErrorMsg("Failed to list files.", err)
|
||||
return
|
||||
}
|
||||
|
||||
job.TaskModel.SetProgress(InsertingProgress)
|
||||
|
||||
// 虚拟目录路径与folder对象ID的对应
|
||||
pathCache := make(map[string]*model.Folder, len(objects))
|
||||
|
||||
// 插入目录记录到用户文件系统
|
||||
for _, object := range objects {
|
||||
if object.IsDir {
|
||||
// 创建目录
|
||||
virtualPath := path.Join(job.TaskProps.Dst, object.RelativePath)
|
||||
folder, err := fs.CreateDirectory(coxIgnoreConflict, virtualPath)
|
||||
if err != nil {
|
||||
util.Log().Warning("Importing task cannot create user directory %q: %s", virtualPath, err)
|
||||
} else if folder.ID > 0 {
|
||||
pathCache[virtualPath] = folder
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 插入文件记录到用户文件系统
|
||||
for _, object := range objects {
|
||||
if !object.IsDir {
|
||||
// 创建文件信息
|
||||
virtualPath := path.Dir(path.Join(job.TaskProps.Dst, object.RelativePath))
|
||||
fileHeader := fsctx.FileStream{
|
||||
Size: object.Size,
|
||||
VirtualPath: virtualPath,
|
||||
Name: object.Name,
|
||||
SavePath: object.Source,
|
||||
}
|
||||
|
||||
// 查找父目录
|
||||
parentFolder := &model.Folder{}
|
||||
if parent, ok := pathCache[virtualPath]; ok {
|
||||
parentFolder = parent
|
||||
} else {
|
||||
folder, err := fs.CreateDirectory(context.Background(), virtualPath)
|
||||
if err != nil {
|
||||
util.Log().Warning("Importing task cannot create user directory %q: %s",
|
||||
virtualPath, err)
|
||||
continue
|
||||
}
|
||||
parentFolder = folder
|
||||
|
||||
}
|
||||
|
||||
// 插入文件记录
|
||||
_, err := fs.AddFile(context.Background(), parentFolder, &fileHeader)
|
||||
if err != nil {
|
||||
util.Log().Warning("Importing task cannot insert user file %q: %s",
|
||||
object.RelativePath, err)
|
||||
if err == filesystem.ErrInsufficientCapacity {
|
||||
job.SetErrorMsg("Insufficient storage capacity.", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NewImportTask 新建导入任务
|
||||
func NewImportTask(user, policy uint, src, dst string, recursive bool) (Job, error) {
|
||||
creator, err := model.GetActiveUserByID(user)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
newTask := &ImportTask{
|
||||
User: &creator,
|
||||
TaskProps: ImportProps{
|
||||
PolicyID: policy,
|
||||
Recursive: recursive,
|
||||
Src: src,
|
||||
Dst: dst,
|
||||
},
|
||||
}
|
||||
|
||||
record, err := Record(newTask)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
newTask.TaskModel = record
|
||||
|
||||
return newTask, nil
|
||||
}
|
||||
|
||||
// NewImportTaskFromModel 从数据库记录中恢复导入任务
|
||||
func NewImportTaskFromModel(task *model.Task) (Job, error) {
|
||||
user, err := model.GetActiveUserByID(task.UserID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
newTask := &ImportTask{
|
||||
User: &user,
|
||||
TaskModel: task,
|
||||
}
|
||||
|
||||
err = json.Unmarshal([]byte(task.Props), &newTask.TaskProps)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return newTask, nil
|
||||
}
|
127
pkg/task/job.go
Normal file
127
pkg/task/job.go
Normal file
@ -0,0 +1,127 @@
|
||||
package task
|
||||
|
||||
import (
|
||||
model "github.com/cloudreve/Cloudreve/v3/models"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/util"
|
||||
)
|
||||
|
||||
// 任务类型
|
||||
const (
|
||||
// CompressTaskType 压缩任务
|
||||
CompressTaskType = iota
|
||||
// DecompressTaskType 解压缩任务
|
||||
DecompressTaskType
|
||||
// TransferTaskType 中转任务
|
||||
TransferTaskType
|
||||
// ImportTaskType 导入任务
|
||||
ImportTaskType
|
||||
// RelocateTaskType 存储策略迁移任务
|
||||
RelocateTaskType
|
||||
// RecycleTaskType 回收任务
|
||||
RecycleTaskType
|
||||
)
|
||||
|
||||
// 任务状态
|
||||
const (
|
||||
// Queued 排队中
|
||||
Queued = iota
|
||||
// Processing 处理中
|
||||
Processing
|
||||
// Error 失败
|
||||
Error
|
||||
// Canceled 取消
|
||||
Canceled
|
||||
// Complete 完成
|
||||
Complete
|
||||
)
|
||||
|
||||
// 任务进度
|
||||
const (
|
||||
// PendingProgress 等待中
|
||||
PendingProgress = iota
|
||||
// Compressing 压缩中
|
||||
CompressingProgress
|
||||
// Decompressing 解压缩中
|
||||
DecompressingProgress
|
||||
// Downloading 下载中
|
||||
DownloadingProgress
|
||||
// Transferring 转存中
|
||||
TransferringProgress
|
||||
// ListingProgress 索引中
|
||||
ListingProgress
|
||||
// InsertingProgress 插入中
|
||||
InsertingProgress
|
||||
)
|
||||
|
||||
// Job 任务接口
|
||||
type Job interface {
|
||||
Type() int // 返回任务类型
|
||||
Creator() uint // 返回创建者ID
|
||||
Props() string // 返回序列化后的任务属性
|
||||
Model() *model.Task // 返回对应的数据库模型
|
||||
SetStatus(int) // 设定任务状态
|
||||
Do() // 开始执行任务
|
||||
SetError(*JobError) // 设定任务失败信息
|
||||
GetError() *JobError // 获取任务执行结果,返回nil表示成功完成执行
|
||||
}
|
||||
|
||||
// JobError 任务失败信息
|
||||
type JobError struct {
|
||||
Msg string `json:"msg,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// Record 将任务记录到数据库中
|
||||
func Record(job Job) (*model.Task, error) {
|
||||
record := model.Task{
|
||||
Status: Queued,
|
||||
Type: job.Type(),
|
||||
UserID: job.Creator(),
|
||||
Progress: 0,
|
||||
Error: "",
|
||||
Props: job.Props(),
|
||||
}
|
||||
_, err := record.Create()
|
||||
return &record, err
|
||||
}
|
||||
|
||||
// Resume 从数据库中恢复未完成任务
|
||||
func Resume(p Pool) {
|
||||
tasks := model.GetTasksByStatus(Queued, Processing)
|
||||
if len(tasks) == 0 {
|
||||
return
|
||||
}
|
||||
util.Log().Info("Resume %d unfinished task(s) from database.", len(tasks))
|
||||
|
||||
for i := 0; i < len(tasks); i++ {
|
||||
job, err := GetJobFromModel(&tasks[i])
|
||||
if err != nil {
|
||||
util.Log().Warning("Failed to resume task: %s", err)
|
||||
continue
|
||||
}
|
||||
|
||||
if job != nil {
|
||||
p.Submit(job)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GetJobFromModel 从数据库给定模型获取任务
|
||||
func GetJobFromModel(task *model.Task) (Job, error) {
|
||||
switch task.Type {
|
||||
case CompressTaskType:
|
||||
return NewCompressTaskFromModel(task)
|
||||
case DecompressTaskType:
|
||||
return NewDecompressTaskFromModel(task)
|
||||
case TransferTaskType:
|
||||
return NewTransferTaskFromModel(task)
|
||||
case ImportTaskType:
|
||||
return NewImportTaskFromModel(task)
|
||||
case RelocateTaskType:
|
||||
return NewRelocateTaskFromModel(task)
|
||||
case RecycleTaskType:
|
||||
return NewRecycleTaskFromModel(task)
|
||||
default:
|
||||
return nil, ErrUnknownTaskType
|
||||
}
|
||||
}
|
68
pkg/task/pool.go
Normal file
68
pkg/task/pool.go
Normal file
@ -0,0 +1,68 @@
|
||||
package task
|
||||
|
||||
import (
|
||||
model "github.com/cloudreve/Cloudreve/v3/models"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/conf"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/util"
|
||||
)
|
||||
|
||||
// TaskPoll 要使用的任务池
|
||||
var TaskPoll Pool
|
||||
|
||||
type Pool interface {
|
||||
Add(num int)
|
||||
Submit(job Job)
|
||||
}
|
||||
|
||||
// AsyncPool 带有最大配额的任务池
|
||||
type AsyncPool struct {
|
||||
// 容量
|
||||
idleWorker chan int
|
||||
}
|
||||
|
||||
// Add 增加可用Worker数量
|
||||
func (pool *AsyncPool) Add(num int) {
|
||||
for i := 0; i < num; i++ {
|
||||
pool.idleWorker <- 1
|
||||
}
|
||||
}
|
||||
|
||||
// ObtainWorker 阻塞直到获取新的Worker
|
||||
func (pool *AsyncPool) obtainWorker() Worker {
|
||||
select {
|
||||
case <-pool.idleWorker:
|
||||
// 有空闲Worker名额时,返回新Worker
|
||||
return &GeneralWorker{}
|
||||
}
|
||||
}
|
||||
|
||||
// FreeWorker 添加空闲Worker
|
||||
func (pool *AsyncPool) freeWorker() {
|
||||
pool.Add(1)
|
||||
}
|
||||
|
||||
// Submit 开始提交任务
|
||||
func (pool *AsyncPool) Submit(job Job) {
|
||||
go func() {
|
||||
util.Log().Debug("Waiting for Worker.")
|
||||
worker := pool.obtainWorker()
|
||||
util.Log().Debug("Worker obtained.")
|
||||
worker.Do(job)
|
||||
util.Log().Debug("Worker released.")
|
||||
pool.freeWorker()
|
||||
}()
|
||||
}
|
||||
|
||||
// Init 初始化任务池
|
||||
func Init() {
|
||||
maxWorker := model.GetIntSetting("max_worker_num", 10)
|
||||
TaskPoll = &AsyncPool{
|
||||
idleWorker: make(chan int, maxWorker),
|
||||
}
|
||||
TaskPoll.Add(maxWorker)
|
||||
util.Log().Info("Initialize task queue with WorkerNum = %d", maxWorker)
|
||||
|
||||
if conf.SystemConfig.Mode == "master" {
|
||||
Resume(TaskPoll)
|
||||
}
|
||||
}
|
130
pkg/task/recycle.go
Normal file
130
pkg/task/recycle.go
Normal file
@ -0,0 +1,130 @@
|
||||
package task
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
model "github.com/cloudreve/Cloudreve/v3/models"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/cluster"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/util"
|
||||
)
|
||||
|
||||
// RecycleTask 文件回收任务
|
||||
type RecycleTask struct {
|
||||
User *model.User
|
||||
TaskModel *model.Task
|
||||
TaskProps RecycleProps
|
||||
Err *JobError
|
||||
}
|
||||
|
||||
// RecycleProps 回收任务属性
|
||||
type RecycleProps struct {
|
||||
// 下载任务 GID
|
||||
DownloadGID string `json:"download_gid"`
|
||||
}
|
||||
|
||||
// Props 获取任务属性
|
||||
func (job *RecycleTask) Props() string {
|
||||
res, _ := json.Marshal(job.TaskProps)
|
||||
return string(res)
|
||||
}
|
||||
|
||||
// Type 获取任务状态
|
||||
func (job *RecycleTask) Type() int {
|
||||
return RecycleTaskType
|
||||
}
|
||||
|
||||
// Creator 获取创建者ID
|
||||
func (job *RecycleTask) Creator() uint {
|
||||
return job.User.ID
|
||||
}
|
||||
|
||||
// Model 获取任务的数据库模型
|
||||
func (job *RecycleTask) Model() *model.Task {
|
||||
return job.TaskModel
|
||||
}
|
||||
|
||||
// SetStatus 设定状态
|
||||
func (job *RecycleTask) SetStatus(status int) {
|
||||
job.TaskModel.SetStatus(status)
|
||||
}
|
||||
|
||||
// SetError 设定任务失败信息
|
||||
func (job *RecycleTask) SetError(err *JobError) {
|
||||
job.Err = err
|
||||
res, _ := json.Marshal(job.Err)
|
||||
job.TaskModel.SetError(string(res))
|
||||
}
|
||||
|
||||
// SetErrorMsg 设定任务失败信息
|
||||
func (job *RecycleTask) SetErrorMsg(msg string, err error) {
|
||||
jobErr := &JobError{Msg: msg}
|
||||
if err != nil {
|
||||
jobErr.Error = err.Error()
|
||||
}
|
||||
job.SetError(jobErr)
|
||||
}
|
||||
|
||||
// GetError 返回任务失败信息
|
||||
func (job *RecycleTask) GetError() *JobError {
|
||||
return job.Err
|
||||
}
|
||||
|
||||
// Do 开始执行任务
|
||||
func (job *RecycleTask) Do() {
|
||||
download, err := model.GetDownloadByGid(job.TaskProps.DownloadGID, job.User.ID)
|
||||
if err != nil {
|
||||
util.Log().Warning("Recycle task %d cannot found download record.", job.TaskModel.ID)
|
||||
job.SetErrorMsg("Cannot found download task.", err)
|
||||
return
|
||||
}
|
||||
nodeID := download.GetNodeID()
|
||||
node := cluster.Default.GetNodeByID(nodeID)
|
||||
if node == nil {
|
||||
util.Log().Warning("Recycle task %d cannot found node.", job.TaskModel.ID)
|
||||
job.SetErrorMsg("Invalid slave node.", nil)
|
||||
return
|
||||
}
|
||||
err = node.GetAria2Instance().DeleteTempFile(download)
|
||||
if err != nil {
|
||||
util.Log().Warning("Failed to delete transfer temp folder %q: %s", download.Parent, err)
|
||||
job.SetErrorMsg("Failed to recycle files.", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// NewRecycleTask 新建回收任务
|
||||
func NewRecycleTask(download *model.Download) (Job, error) {
|
||||
newTask := &RecycleTask{
|
||||
User: download.GetOwner(),
|
||||
TaskProps: RecycleProps{
|
||||
DownloadGID: download.GID,
|
||||
},
|
||||
}
|
||||
|
||||
record, err := Record(newTask)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
newTask.TaskModel = record
|
||||
|
||||
return newTask, nil
|
||||
}
|
||||
|
||||
// NewRecycleTaskFromModel 从数据库记录中恢复回收任务
|
||||
func NewRecycleTaskFromModel(task *model.Task) (Job, error) {
|
||||
user, err := model.GetActiveUserByID(task.UserID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
newTask := &RecycleTask{
|
||||
User: &user,
|
||||
TaskModel: task,
|
||||
}
|
||||
|
||||
err = json.Unmarshal([]byte(task.Props), &newTask.TaskProps)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return newTask, nil
|
||||
}
|
176
pkg/task/relocate.go
Normal file
176
pkg/task/relocate.go
Normal file
@ -0,0 +1,176 @@
|
||||
package task
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
model "github.com/cloudreve/Cloudreve/v3/models"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/filesystem"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/util"
|
||||
)
|
||||
|
||||
// RelocateTask 存储策略迁移任务
|
||||
type RelocateTask struct {
|
||||
User *model.User
|
||||
TaskModel *model.Task
|
||||
TaskProps RelocateProps
|
||||
Err *JobError
|
||||
}
|
||||
|
||||
// RelocateProps 存储策略迁移任务属性
|
||||
type RelocateProps struct {
|
||||
Dirs []uint `json:"dirs"`
|
||||
Files []uint `json:"files"`
|
||||
DstPolicyID uint `json:"dst_policy_id"`
|
||||
}
|
||||
|
||||
// Props 获取任务属性
|
||||
func (job *RelocateTask) Props() string {
|
||||
res, _ := json.Marshal(job.TaskProps)
|
||||
return string(res)
|
||||
}
|
||||
|
||||
// Type 获取任务状态
|
||||
func (job *RelocateTask) Type() int {
|
||||
return RelocateTaskType
|
||||
}
|
||||
|
||||
// Creator 获取创建者ID
|
||||
func (job *RelocateTask) Creator() uint {
|
||||
return job.User.ID
|
||||
}
|
||||
|
||||
// Model 获取任务的数据库模型
|
||||
func (job *RelocateTask) Model() *model.Task {
|
||||
return job.TaskModel
|
||||
}
|
||||
|
||||
// SetStatus 设定状态
|
||||
func (job *RelocateTask) SetStatus(status int) {
|
||||
job.TaskModel.SetStatus(status)
|
||||
}
|
||||
|
||||
// SetError 设定任务失败信息
|
||||
func (job *RelocateTask) SetError(err *JobError) {
|
||||
job.Err = err
|
||||
res, _ := json.Marshal(job.Err)
|
||||
job.TaskModel.SetError(string(res))
|
||||
}
|
||||
|
||||
// SetErrorMsg 设定任务失败信息
|
||||
func (job *RelocateTask) SetErrorMsg(msg string) {
|
||||
job.SetError(&JobError{Msg: msg})
|
||||
}
|
||||
|
||||
// GetError 返回任务失败信息
|
||||
func (job *RelocateTask) GetError() *JobError {
|
||||
return job.Err
|
||||
}
|
||||
|
||||
// Do 开始执行任务
|
||||
func (job *RelocateTask) Do() {
|
||||
// 创建文件系统
|
||||
fs, err := filesystem.NewFileSystem(job.User)
|
||||
if err != nil {
|
||||
job.SetErrorMsg(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
job.TaskModel.SetProgress(ListingProgress)
|
||||
util.Log().Debug("Start migration task.")
|
||||
|
||||
// ----------------------------
|
||||
// 索引出所有待迁移的文件
|
||||
// ----------------------------
|
||||
targetFiles := make([]model.File, 0, len(job.TaskProps.Files))
|
||||
|
||||
// 索引用户选择的单独的文件
|
||||
outerFiles, err := model.GetFilesByIDs(job.TaskProps.Files, job.User.ID)
|
||||
if err != nil {
|
||||
job.SetError(&JobError{
|
||||
Msg: "Failed to index files.",
|
||||
Error: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
targetFiles = append(targetFiles, outerFiles...)
|
||||
|
||||
// 索引用户选择目录下的所有递归子文件
|
||||
subFolders, err := model.GetRecursiveChildFolder(job.TaskProps.Dirs, job.User.ID, true)
|
||||
if err != nil {
|
||||
job.SetError(&JobError{
|
||||
Msg: "Failed to index child folders.",
|
||||
Error: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
subFiles, err := model.GetChildFilesOfFolders(&subFolders)
|
||||
if err != nil {
|
||||
job.SetError(&JobError{
|
||||
Msg: "Failed to index child files.",
|
||||
Error: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
targetFiles = append(targetFiles, subFiles...)
|
||||
|
||||
// 查找目标存储策略
|
||||
policy, err := model.GetPolicyByID(job.TaskProps.DstPolicyID)
|
||||
if err != nil {
|
||||
job.SetError(&JobError{
|
||||
Msg: "Invalid policy.",
|
||||
Error: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 开始转移文件
|
||||
job.TaskModel.SetProgress(TransferringProgress)
|
||||
ctx := context.Background()
|
||||
err = fs.Relocate(ctx, targetFiles, &policy)
|
||||
if err != nil {
|
||||
job.SetErrorMsg(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// NewRelocateTask 新建转移任务
|
||||
func NewRelocateTask(user *model.User, dstPolicyID uint, dirs, files []uint) (Job, error) {
|
||||
newTask := &RelocateTask{
|
||||
User: user,
|
||||
TaskProps: RelocateProps{
|
||||
Dirs: dirs,
|
||||
Files: files,
|
||||
DstPolicyID: dstPolicyID,
|
||||
},
|
||||
}
|
||||
|
||||
record, err := Record(newTask)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
newTask.TaskModel = record
|
||||
|
||||
return newTask, nil
|
||||
}
|
||||
|
||||
// NewCompressTaskFromModel 从数据库记录中恢复压缩任务
|
||||
func NewRelocateTaskFromModel(task *model.Task) (Job, error) {
|
||||
user, err := model.GetActiveUserByID(task.UserID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
newTask := &RelocateTask{
|
||||
User: &user,
|
||||
TaskModel: task,
|
||||
}
|
||||
|
||||
err = json.Unmarshal([]byte(task.Props), &newTask.TaskProps)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return newTask, nil
|
||||
}
|
138
pkg/task/slavetask/transfer.go
Normal file
138
pkg/task/slavetask/transfer.go
Normal file
@ -0,0 +1,138 @@
|
||||
package slavetask
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
|
||||
model "github.com/cloudreve/Cloudreve/v3/models"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/cluster"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/filesystem"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/filesystem/fsctx"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/mq"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/serializer"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/task"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/util"
|
||||
)
|
||||
|
||||
// TransferTask 文件中转任务
|
||||
type TransferTask struct {
|
||||
Err *task.JobError
|
||||
Req *serializer.SlaveTransferReq
|
||||
MasterID string
|
||||
}
|
||||
|
||||
// Props 获取任务属性
|
||||
func (job *TransferTask) Props() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// Type 获取任务类型
|
||||
func (job *TransferTask) Type() int {
|
||||
return 0
|
||||
}
|
||||
|
||||
// Creator 获取创建者ID
|
||||
func (job *TransferTask) Creator() uint {
|
||||
return 0
|
||||
}
|
||||
|
||||
// Model 获取任务的数据库模型
|
||||
func (job *TransferTask) Model() *model.Task {
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetStatus 设定状态
|
||||
func (job *TransferTask) SetStatus(status int) {
|
||||
}
|
||||
|
||||
// SetError 设定任务失败信息
|
||||
func (job *TransferTask) SetError(err *task.JobError) {
|
||||
job.Err = err
|
||||
|
||||
}
|
||||
|
||||
// SetErrorMsg 设定任务失败信息
|
||||
func (job *TransferTask) SetErrorMsg(msg string, err error) {
|
||||
jobErr := &task.JobError{Msg: msg}
|
||||
if err != nil {
|
||||
jobErr.Error = err.Error()
|
||||
}
|
||||
|
||||
job.SetError(jobErr)
|
||||
|
||||
notifyMsg := mq.Message{
|
||||
TriggeredBy: job.MasterID,
|
||||
Event: serializer.SlaveTransferFailed,
|
||||
Content: serializer.SlaveTransferResult{
|
||||
Error: err.Error(),
|
||||
},
|
||||
}
|
||||
|
||||
if err := cluster.DefaultController.SendNotification(job.MasterID, job.Req.Hash(job.MasterID), notifyMsg); err != nil {
|
||||
util.Log().Warning("Failed to send transfer failure notification to master node: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// GetError 返回任务失败信息
|
||||
func (job *TransferTask) GetError() *task.JobError {
|
||||
return job.Err
|
||||
}
|
||||
|
||||
// Do 开始执行任务
|
||||
func (job *TransferTask) Do() {
|
||||
fs, err := filesystem.NewAnonymousFileSystem()
|
||||
if err != nil {
|
||||
job.SetErrorMsg("Failed to initialize anonymous filesystem.", err)
|
||||
return
|
||||
}
|
||||
|
||||
fs.Policy = job.Req.Policy
|
||||
if err := fs.DispatchHandler(); err != nil {
|
||||
job.SetErrorMsg("Failed to dispatch policy.", err)
|
||||
return
|
||||
}
|
||||
|
||||
master, err := cluster.DefaultController.GetMasterInfo(job.MasterID)
|
||||
if err != nil {
|
||||
job.SetErrorMsg("Cannot found master node ID.", err)
|
||||
return
|
||||
}
|
||||
|
||||
fs.SwitchToShadowHandler(master.Instance, master.URL.String(), master.ID)
|
||||
file, err := os.Open(util.RelativePath(job.Req.Src))
|
||||
if err != nil {
|
||||
job.SetErrorMsg("Failed to read source file.", err)
|
||||
return
|
||||
}
|
||||
|
||||
defer file.Close()
|
||||
|
||||
// 获取源文件大小
|
||||
fi, err := file.Stat()
|
||||
if err != nil {
|
||||
job.SetErrorMsg("Failed to get source file size.", err)
|
||||
return
|
||||
}
|
||||
|
||||
size := fi.Size()
|
||||
|
||||
err = fs.Handler.Put(context.Background(), &fsctx.FileStream{
|
||||
File: file,
|
||||
SavePath: job.Req.Dst,
|
||||
Size: uint64(size),
|
||||
})
|
||||
if err != nil {
|
||||
job.SetErrorMsg("Upload failed.", err)
|
||||
return
|
||||
}
|
||||
|
||||
msg := mq.Message{
|
||||
TriggeredBy: job.MasterID,
|
||||
Event: serializer.SlaveTransferSuccess,
|
||||
Content: serializer.SlaveTransferResult{},
|
||||
}
|
||||
|
||||
if err := cluster.DefaultController.SendNotification(job.MasterID, job.Req.Hash(job.MasterID), msg); err != nil {
|
||||
util.Log().Warning("Failed to send transfer success notification to master node: %s", err)
|
||||
}
|
||||
}
|
192
pkg/task/tranfer.go
Normal file
192
pkg/task/tranfer.go
Normal file
@ -0,0 +1,192 @@
|
||||
package task
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
model "github.com/cloudreve/Cloudreve/v3/models"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/cluster"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/filesystem"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/filesystem/fsctx"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/util"
|
||||
)
|
||||
|
||||
// TransferTask 文件中转任务
|
||||
type TransferTask struct {
|
||||
User *model.User
|
||||
TaskModel *model.Task
|
||||
TaskProps TransferProps
|
||||
Err *JobError
|
||||
|
||||
zipPath string
|
||||
}
|
||||
|
||||
// TransferProps 中转任务属性
|
||||
type TransferProps struct {
|
||||
Src []string `json:"src"` // 原始文件
|
||||
SrcSizes map[string]uint64 `json:"src_size"` // 原始文件的大小信息,从机转存时使用
|
||||
Parent string `json:"parent"` // 父目录
|
||||
Dst string `json:"dst"` // 目的目录ID
|
||||
// 将会保留原始文件的目录结构,Src 除去 Parent 开头作为最终路径
|
||||
TrimPath bool `json:"trim_path"`
|
||||
// 负责处理中专任务的节点ID
|
||||
NodeID uint `json:"node_id"`
|
||||
}
|
||||
|
||||
// Props 获取任务属性
|
||||
func (job *TransferTask) Props() string {
|
||||
res, _ := json.Marshal(job.TaskProps)
|
||||
return string(res)
|
||||
}
|
||||
|
||||
// Type 获取任务状态
|
||||
func (job *TransferTask) Type() int {
|
||||
return TransferTaskType
|
||||
}
|
||||
|
||||
// Creator 获取创建者ID
|
||||
func (job *TransferTask) Creator() uint {
|
||||
return job.User.ID
|
||||
}
|
||||
|
||||
// Model 获取任务的数据库模型
|
||||
func (job *TransferTask) Model() *model.Task {
|
||||
return job.TaskModel
|
||||
}
|
||||
|
||||
// SetStatus 设定状态
|
||||
func (job *TransferTask) SetStatus(status int) {
|
||||
job.TaskModel.SetStatus(status)
|
||||
}
|
||||
|
||||
// SetError 设定任务失败信息
|
||||
func (job *TransferTask) SetError(err *JobError) {
|
||||
job.Err = err
|
||||
res, _ := json.Marshal(job.Err)
|
||||
job.TaskModel.SetError(string(res))
|
||||
|
||||
}
|
||||
|
||||
// SetErrorMsg 设定任务失败信息
|
||||
func (job *TransferTask) SetErrorMsg(msg string, err error) {
|
||||
jobErr := &JobError{Msg: msg}
|
||||
if err != nil {
|
||||
jobErr.Error = err.Error()
|
||||
}
|
||||
job.SetError(jobErr)
|
||||
}
|
||||
|
||||
// GetError 返回任务失败信息
|
||||
func (job *TransferTask) GetError() *JobError {
|
||||
return job.Err
|
||||
}
|
||||
|
||||
// Do 开始执行任务
|
||||
func (job *TransferTask) Do() {
|
||||
// 创建文件系统
|
||||
fs, err := filesystem.NewFileSystem(job.User)
|
||||
if err != nil {
|
||||
job.SetErrorMsg(err.Error(), nil)
|
||||
return
|
||||
}
|
||||
defer fs.Recycle()
|
||||
|
||||
successCount := 0
|
||||
errorList := make([]string, 0, len(job.TaskProps.Src))
|
||||
for _, file := range job.TaskProps.Src {
|
||||
dst := path.Join(job.TaskProps.Dst, filepath.Base(file))
|
||||
if job.TaskProps.TrimPath {
|
||||
// 保留原始目录
|
||||
trim := util.FormSlash(job.TaskProps.Parent)
|
||||
src := util.FormSlash(file)
|
||||
dst = path.Join(job.TaskProps.Dst, strings.TrimPrefix(src, trim))
|
||||
}
|
||||
|
||||
if job.TaskProps.NodeID > 1 {
|
||||
// 指定为从机中转
|
||||
|
||||
// 获取从机节点
|
||||
node := cluster.Default.GetNodeByID(job.TaskProps.NodeID)
|
||||
if node == nil {
|
||||
job.SetErrorMsg("Invalid slave node.", nil)
|
||||
}
|
||||
|
||||
// 切换为从机节点处理上传
|
||||
fs.SetPolicyFromPath(path.Dir(dst))
|
||||
fs.SwitchToSlaveHandler(node)
|
||||
err = fs.UploadFromStream(context.Background(), &fsctx.FileStream{
|
||||
File: nil,
|
||||
Size: job.TaskProps.SrcSizes[file],
|
||||
Name: path.Base(dst),
|
||||
VirtualPath: path.Dir(dst),
|
||||
Src: file,
|
||||
}, false)
|
||||
} else {
|
||||
// 主机节点中转
|
||||
err = fs.UploadFromPath(context.Background(), file, dst, 0)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
errorList = append(errorList, err.Error())
|
||||
} else {
|
||||
successCount++
|
||||
job.TaskModel.SetProgress(successCount)
|
||||
}
|
||||
}
|
||||
|
||||
if len(errorList) > 0 {
|
||||
job.SetErrorMsg("Failed to transfer one or more file(s).", fmt.Errorf(strings.Join(errorList, "\n")))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// NewTransferTask 新建中转任务
|
||||
func NewTransferTask(user uint, src []string, dst, parent string, trim bool, node uint, sizes map[string]uint64) (Job, error) {
|
||||
creator, err := model.GetActiveUserByID(user)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
newTask := &TransferTask{
|
||||
User: &creator,
|
||||
TaskProps: TransferProps{
|
||||
Src: src,
|
||||
Parent: parent,
|
||||
Dst: dst,
|
||||
TrimPath: trim,
|
||||
NodeID: node,
|
||||
SrcSizes: sizes,
|
||||
},
|
||||
}
|
||||
|
||||
record, err := Record(newTask)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
newTask.TaskModel = record
|
||||
|
||||
return newTask, nil
|
||||
}
|
||||
|
||||
// NewTransferTaskFromModel 从数据库记录中恢复中转任务
|
||||
func NewTransferTaskFromModel(task *model.Task) (Job, error) {
|
||||
user, err := model.GetActiveUserByID(task.UserID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
newTask := &TransferTask{
|
||||
User: &user,
|
||||
TaskModel: task,
|
||||
}
|
||||
|
||||
err = json.Unmarshal([]byte(task.Props), &newTask.TaskProps)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return newTask, nil
|
||||
}
|
44
pkg/task/worker.go
Normal file
44
pkg/task/worker.go
Normal file
@ -0,0 +1,44 @@
|
||||
package task
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/util"
|
||||
)
|
||||
|
||||
// Worker 处理任务的对象
|
||||
type Worker interface {
|
||||
Do(Job) // 执行任务
|
||||
}
|
||||
|
||||
// GeneralWorker 通用Worker
|
||||
type GeneralWorker struct {
|
||||
}
|
||||
|
||||
// Do 执行任务
|
||||
func (worker *GeneralWorker) Do(job Job) {
|
||||
util.Log().Debug("Start executing task.")
|
||||
job.SetStatus(Processing)
|
||||
|
||||
defer func() {
|
||||
// 致命错误捕获
|
||||
if err := recover(); err != nil {
|
||||
util.Log().Debug("Failed to execute task: %s", err)
|
||||
job.SetError(&JobError{Msg: "Fatal error.", Error: fmt.Sprintf("%s", err)})
|
||||
job.SetStatus(Error)
|
||||
}
|
||||
}()
|
||||
|
||||
// 开始执行任务
|
||||
job.Do()
|
||||
|
||||
// 任务执行失败
|
||||
if err := job.GetError(); err != nil {
|
||||
util.Log().Debug("Failed to execute task.")
|
||||
job.SetStatus(Error)
|
||||
return
|
||||
}
|
||||
|
||||
util.Log().Debug("Task finished.")
|
||||
// 执行完成
|
||||
job.SetStatus(Complete)
|
||||
}
|
Reference in New Issue
Block a user