mirror of
https://github.com/GopeedLab/gopeed.git
synced 2026-11-06 00:25:43 +00:00
feat: support post-task script hooks (#1265)
This commit is contained in:
1 parent
3eb6df55df
commit
1b1631b41a
20 files changed
+911
No files matched your search
@@ -184,6 +184,7 @@ type DownloaderStoreConfig struct {
|
||||
Extra map[string]any `json:"extra"`
|
||||
Proxy *DownloaderProxyConfig `json:"proxy"`
|
||||
Webhook *WebhookConfig `json:"webhook"` // Webhook is the webhook configuration
|
||||
Script *ScriptConfig `json:"script"` // Script is the script execution configuration
|
||||
AutoTorrent *AutoTorrentConfig `json:"autoTorrent"` // AutoTorrent is the auto torrent task creation configuration
|
||||
Archive *ArchiveConfig `json:"archive"` // Archive is the archive extraction configuration
|
||||
AutoDeleteMissingFileTasks bool `json:"autoDeleteMissingFileTasks"` // AutoDeleteMissingFileTasks enables automatic deletion of tasks with missing files
|
||||
@@ -202,6 +203,9 @@ func (cfg *DownloaderStoreConfig) Init() *DownloaderStoreConfig {
|
||||
if cfg.Webhook == nil {
|
||||
cfg.Webhook = &WebhookConfig{}
|
||||
}
|
||||
if cfg.Script == nil {
|
||||
cfg.Script = &ScriptConfig{}
|
||||
}
|
||||
if cfg.AutoTorrent == nil {
|
||||
cfg.AutoTorrent = &AutoTorrentConfig{
|
||||
Enable: false,
|
||||
@@ -239,6 +243,9 @@ func (cfg *DownloaderStoreConfig) Merge(beforeCfg *DownloaderStoreConfig) *Downl
|
||||
if cfg.Webhook == nil {
|
||||
cfg.Webhook = beforeCfg.Webhook
|
||||
}
|
||||
if cfg.Script == nil {
|
||||
cfg.Script = beforeCfg.Script
|
||||
}
|
||||
if cfg.AutoTorrent == nil {
|
||||
cfg.AutoTorrent = beforeCfg.AutoTorrent
|
||||
}
|
||||
@@ -254,6 +261,12 @@ type WebhookConfig struct {
|
||||
URLs []string `json:"urls"` // URLs is the list of webhook URLs
|
||||
}
|
||||
|
||||
// ScriptConfig is the script execution configuration
|
||||
type ScriptConfig struct {
|
||||
Enable bool `json:"enable"` // Enable is the flag to enable/disable script execution
|
||||
Paths []string `json:"paths"` // Paths is the list of script paths to execute
|
||||
}
|
||||
|
||||
// AutoTorrentConfig is the auto torrent task creation configuration
|
||||
type AutoTorrentConfig struct {
|
||||
Enable bool `json:"enable"` // Enable enables automatic BT task creation when downloading .torrent files
|
||||
|
||||
@@ -19,6 +19,7 @@ func TestDownloaderStoreConfig_Init(t *testing.T) {
|
||||
ProtocolConfig: map[string]any{},
|
||||
Proxy: &DownloaderProxyConfig{},
|
||||
Webhook: &WebhookConfig{},
|
||||
Script: &ScriptConfig{},
|
||||
AutoTorrent: &AutoTorrentConfig{
|
||||
Enable: false,
|
||||
DeleteAfterDownload: false,
|
||||
@@ -39,6 +40,7 @@ func TestDownloaderStoreConfig_Init(t *testing.T) {
|
||||
ProtocolConfig: map[string]any{},
|
||||
Proxy: &DownloaderProxyConfig{},
|
||||
Webhook: &WebhookConfig{},
|
||||
Script: &ScriptConfig{},
|
||||
AutoTorrent: &AutoTorrentConfig{
|
||||
Enable: false,
|
||||
DeleteAfterDownload: false,
|
||||
@@ -63,6 +65,7 @@ func TestDownloaderStoreConfig_Init(t *testing.T) {
|
||||
},
|
||||
Proxy: &DownloaderProxyConfig{},
|
||||
Webhook: &WebhookConfig{},
|
||||
Script: &ScriptConfig{},
|
||||
AutoTorrent: &AutoTorrentConfig{
|
||||
Enable: false,
|
||||
DeleteAfterDownload: false,
|
||||
@@ -87,6 +90,7 @@ func TestDownloaderStoreConfig_Init(t *testing.T) {
|
||||
Enable: true,
|
||||
},
|
||||
Webhook: &WebhookConfig{},
|
||||
Script: &ScriptConfig{},
|
||||
AutoTorrent: &AutoTorrentConfig{
|
||||
Enable: false,
|
||||
DeleteAfterDownload: false,
|
||||
@@ -110,6 +114,7 @@ func TestDownloaderStoreConfig_Init(t *testing.T) {
|
||||
ProtocolConfig: map[string]any{},
|
||||
Proxy: &DownloaderProxyConfig{},
|
||||
Webhook: &WebhookConfig{},
|
||||
Script: &ScriptConfig{},
|
||||
AutoTorrent: &AutoTorrentConfig{
|
||||
Enable: true,
|
||||
DeleteAfterDownload: true,
|
||||
@@ -133,6 +138,7 @@ func TestDownloaderStoreConfig_Init(t *testing.T) {
|
||||
ProtocolConfig: map[string]any{},
|
||||
Proxy: &DownloaderProxyConfig{},
|
||||
Webhook: &WebhookConfig{},
|
||||
Script: &ScriptConfig{},
|
||||
AutoTorrent: &AutoTorrentConfig{
|
||||
Enable: false,
|
||||
DeleteAfterDownload: false,
|
||||
@@ -154,6 +160,7 @@ func TestDownloaderStoreConfig_Init(t *testing.T) {
|
||||
Extra: tt.fields.Extra,
|
||||
Proxy: tt.fields.Proxy,
|
||||
Webhook: tt.fields.Webhook,
|
||||
Script: tt.fields.Script,
|
||||
AutoTorrent: tt.fields.AutoTorrent,
|
||||
Archive: tt.fields.Archive,
|
||||
}
|
||||
|
||||
@@ -995,6 +995,7 @@ func (d *Downloader) watch(task *Task) {
|
||||
d.notifyRunning()
|
||||
d.triggerOnDone(task)
|
||||
d.triggerWebhooks(WebhookEventDownloadDone, task, nil)
|
||||
d.triggerScripts(ScriptEventDownloadDone, task, nil)
|
||||
|
||||
if e, ok := task.Meta.Opts.Extra.(*http.OptsExtra); ok {
|
||||
downloadFilePath := task.Meta.SingleFilepath()
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
package download
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ScriptEvent represents the type of script event
|
||||
type ScriptEvent string
|
||||
|
||||
const (
|
||||
ScriptEventDownloadDone ScriptEvent = "DOWNLOAD_DONE"
|
||||
ScriptEventDownloadError ScriptEvent = "DOWNLOAD_ERROR"
|
||||
)
|
||||
|
||||
// ScriptData is the internal data structure for passing script information
|
||||
type ScriptData struct {
|
||||
Event ScriptEvent
|
||||
Time int64 // Unix timestamp in milliseconds
|
||||
Payload *ScriptPayload
|
||||
}
|
||||
|
||||
// ScriptPayload contains the task data
|
||||
type ScriptPayload struct {
|
||||
Task *Task
|
||||
}
|
||||
|
||||
// getScriptPaths extracts script paths from config
|
||||
func (d *Downloader) getScriptPaths() []string {
|
||||
cfg := d.cfg.DownloaderStoreConfig
|
||||
if cfg == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check new script config
|
||||
if cfg.Script != nil && cfg.Script.Enable && len(cfg.Script.Paths) > 0 {
|
||||
paths := make([]string, 0, len(cfg.Script.Paths))
|
||||
for _, path := range cfg.Script.Paths {
|
||||
if path != "" {
|
||||
paths = append(paths, path)
|
||||
}
|
||||
}
|
||||
if len(paths) > 0 {
|
||||
return paths
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// executeScriptAtPath executes a single script with the given data
|
||||
// Returns any error that occurred during execution
|
||||
func (d *Downloader) executeScriptAtPath(scriptPath string, data *ScriptData) error {
|
||||
if scriptPath == "" {
|
||||
return fmt.Errorf("script path is empty")
|
||||
}
|
||||
|
||||
// Check if script file exists
|
||||
if _, err := os.Stat(scriptPath); os.IsNotExist(err) {
|
||||
return fmt.Errorf("script file does not exist: %s", scriptPath)
|
||||
}
|
||||
|
||||
// Determine the script interpreter based on file extension
|
||||
var cmd *exec.Cmd
|
||||
ext := filepath.Ext(scriptPath)
|
||||
|
||||
switch ext {
|
||||
case ".sh", ".bash":
|
||||
cmd = exec.Command("bash", scriptPath)
|
||||
case ".py":
|
||||
cmd = exec.Command("python3", scriptPath)
|
||||
case ".js":
|
||||
cmd = exec.Command("node", scriptPath)
|
||||
case ".bat", ".cmd":
|
||||
// Windows batch files
|
||||
if runtime.GOOS == "windows" {
|
||||
cmd = exec.Command("cmd", "/c", scriptPath)
|
||||
} else {
|
||||
// Batch files are Windows-specific
|
||||
return fmt.Errorf("batch files (.bat/.cmd) are only supported on Windows")
|
||||
}
|
||||
case ".ps1":
|
||||
// PowerShell scripts
|
||||
if runtime.GOOS == "windows" {
|
||||
cmd = exec.Command("powershell", "-ExecutionPolicy", "Bypass", "-File", scriptPath)
|
||||
} else {
|
||||
// Try pwsh (PowerShell Core) on non-Windows systems
|
||||
cmd = exec.Command("pwsh", "-File", scriptPath)
|
||||
}
|
||||
case "":
|
||||
// No extension, try to execute directly (assumes shebang or executable)
|
||||
cmd = exec.Command(scriptPath)
|
||||
default:
|
||||
// Unknown extension, try to execute directly
|
||||
cmd = exec.Command(scriptPath)
|
||||
}
|
||||
|
||||
// Set environment variables with task information
|
||||
cmd.Env = append(os.Environ(),
|
||||
fmt.Sprintf("GOPEED_EVENT=%s", data.Event),
|
||||
fmt.Sprintf("GOPEED_TASK_ID=%s", data.Payload.Task.ID),
|
||||
fmt.Sprintf("GOPEED_TASK_NAME=%s", data.Payload.Task.Name()),
|
||||
fmt.Sprintf("GOPEED_TASK_STATUS=%s", data.Payload.Task.Status),
|
||||
)
|
||||
|
||||
// Add task path using the same logic as task deletion
|
||||
if data.Payload.Task.Meta != nil && data.Payload.Task.Meta.Res != nil {
|
||||
var taskPath string
|
||||
if data.Payload.Task.Meta.Res.Name != "" {
|
||||
// Multi-file task (folder)
|
||||
taskPath = data.Payload.Task.Meta.FolderPath()
|
||||
} else {
|
||||
// Single file task
|
||||
taskPath = data.Payload.Task.Meta.SingleFilepath()
|
||||
}
|
||||
cmd.Env = append(cmd.Env,
|
||||
fmt.Sprintf("GOPEED_TASK_PATH=%s", taskPath),
|
||||
)
|
||||
}
|
||||
|
||||
// Start and wait for the command to complete (no timeout)
|
||||
return cmd.Run()
|
||||
}
|
||||
|
||||
// triggerScripts executes all configured scripts
|
||||
func (d *Downloader) triggerScripts(event ScriptEvent, task *Task, err error) {
|
||||
paths := d.getScriptPaths()
|
||||
if len(paths) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
data := &ScriptData{
|
||||
Event: event,
|
||||
Time: time.Now().UnixMilli(),
|
||||
Payload: &ScriptPayload{
|
||||
Task: task.clone(),
|
||||
},
|
||||
}
|
||||
|
||||
go d.executeScripts(paths, data)
|
||||
}
|
||||
|
||||
func (d *Downloader) executeScripts(paths []string, data *ScriptData) {
|
||||
for _, path := range paths {
|
||||
if path == "" {
|
||||
continue
|
||||
}
|
||||
go func(scriptPath string) {
|
||||
err := d.executeScriptAtPath(scriptPath, data)
|
||||
if err != nil {
|
||||
d.Logger.Warn().Err(err).Str("path", scriptPath).Msg("script: failed to execute")
|
||||
return
|
||||
}
|
||||
d.Logger.Debug().Str("path", scriptPath).Msg("script: executed successfully")
|
||||
}(path)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
package download
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/GopeedLab/gopeed/internal/fetcher"
|
||||
"github.com/GopeedLab/gopeed/pkg/base"
|
||||
)
|
||||
|
||||
func TestScript_NoScriptConfigured(t *testing.T) {
|
||||
setupScriptTest(t, func(downloader *Downloader) {
|
||||
// Create a mock task
|
||||
task := NewTask()
|
||||
task.Protocol = "http"
|
||||
task.Meta = &mockFetcherMeta
|
||||
|
||||
// Trigger script (should not panic with no scripts configured)
|
||||
downloader.triggerScripts(ScriptEventDownloadDone, task, nil)
|
||||
})
|
||||
}
|
||||
|
||||
func TestScript_GetScriptPaths_EmptyConfig(t *testing.T) {
|
||||
setupScriptTest(t, func(downloader *Downloader) {
|
||||
paths := downloader.getScriptPaths()
|
||||
if paths != nil {
|
||||
t.Errorf("Expected nil, got %v", paths)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestScript_GetScriptPaths_NoScriptConfig(t *testing.T) {
|
||||
setupScriptTest(t, func(downloader *Downloader) {
|
||||
cfg, _ := downloader.GetConfig()
|
||||
cfg.Script = nil
|
||||
downloader.PutConfig(cfg)
|
||||
|
||||
paths := downloader.getScriptPaths()
|
||||
if paths != nil {
|
||||
t.Errorf("Expected nil, got %v", paths)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestScript_GetScriptPaths_DisabledScript(t *testing.T) {
|
||||
setupScriptTest(t, func(downloader *Downloader) {
|
||||
cfg, _ := downloader.GetConfig()
|
||||
cfg.Script = &base.ScriptConfig{
|
||||
Enable: false,
|
||||
Paths: []string{"/path/to/script.sh"},
|
||||
}
|
||||
downloader.PutConfig(cfg)
|
||||
|
||||
paths := downloader.getScriptPaths()
|
||||
if paths != nil {
|
||||
t.Errorf("Expected nil for disabled script, got %v", paths)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestScript_GetScriptPaths_EmptyPaths(t *testing.T) {
|
||||
setupScriptTest(t, func(downloader *Downloader) {
|
||||
cfg, _ := downloader.GetConfig()
|
||||
cfg.Script = &base.ScriptConfig{
|
||||
Enable: true,
|
||||
Paths: []string{},
|
||||
}
|
||||
downloader.PutConfig(cfg)
|
||||
|
||||
paths := downloader.getScriptPaths()
|
||||
if paths != nil {
|
||||
t.Errorf("Expected nil for empty paths, got %v", paths)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestScript_GetScriptPaths_WithEmptyStrings(t *testing.T) {
|
||||
setupScriptTest(t, func(downloader *Downloader) {
|
||||
cfg, _ := downloader.GetConfig()
|
||||
cfg.Script = &base.ScriptConfig{
|
||||
Enable: true,
|
||||
Paths: []string{"/path/to/script1.sh", "", "/path/to/script2.sh", ""},
|
||||
}
|
||||
downloader.PutConfig(cfg)
|
||||
|
||||
paths := downloader.getScriptPaths()
|
||||
if len(paths) != 2 {
|
||||
t.Errorf("Expected 2 valid paths (ignoring empty strings), got %d: %v", len(paths), paths)
|
||||
}
|
||||
if paths[0] != "/path/to/script1.sh" || paths[1] != "/path/to/script2.sh" {
|
||||
t.Errorf("Paths don't match expected values: %v", paths)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestScript_ExecuteScriptAtPath_EmptyPath(t *testing.T) {
|
||||
setupScriptTest(t, func(downloader *Downloader) {
|
||||
data := &ScriptData{
|
||||
Event: ScriptEventDownloadDone,
|
||||
Time: time.Now().UnixMilli(),
|
||||
}
|
||||
|
||||
err := downloader.executeScriptAtPath("", data)
|
||||
if err == nil {
|
||||
t.Error("Expected error for empty path")
|
||||
}
|
||||
if err.Error() != "script path is empty" {
|
||||
t.Errorf("Expected 'script path is empty' error, got: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestScript_ExecuteScriptAtPath_NonExistentFile(t *testing.T) {
|
||||
setupScriptTest(t, func(downloader *Downloader) {
|
||||
data := &ScriptData{
|
||||
Event: ScriptEventDownloadDone,
|
||||
Time: time.Now().UnixMilli(),
|
||||
}
|
||||
|
||||
err := downloader.executeScriptAtPath("/non/existent/script.sh", data)
|
||||
if err == nil {
|
||||
t.Error("Expected error for non-existent script")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func createDownloadDoneTask(t *testing.T, downloadDir, fileName string) (*Task, string) {
|
||||
t.Helper()
|
||||
content := []byte("downloaded file")
|
||||
task := NewTask()
|
||||
task.Protocol = "http"
|
||||
task.Status = base.DownloadStatusDone
|
||||
task.Meta = &fetcher.FetcherMeta{
|
||||
Req: &base.Request{
|
||||
URL: "https://example.com/" + fileName,
|
||||
},
|
||||
Opts: &base.Options{
|
||||
Name: fileName,
|
||||
Path: filepath.ToSlash(downloadDir),
|
||||
},
|
||||
Res: &base.Resource{
|
||||
Size: int64(len(content)),
|
||||
Files: []*base.FileInfo{
|
||||
{Name: fileName, Size: int64(len(content))},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
filePath := task.Meta.SingleFilepath()
|
||||
filePathOS := filepath.FromSlash(filePath)
|
||||
if err := os.MkdirAll(filepath.Dir(filePathOS), 0755); err != nil {
|
||||
t.Fatalf("Failed to create download dir: %v", err)
|
||||
}
|
||||
if err := os.WriteFile(filePathOS, content, 0644); err != nil {
|
||||
t.Fatalf("Failed to create download file: %v", err)
|
||||
}
|
||||
return task, filePath
|
||||
}
|
||||
|
||||
func waitForFile(t *testing.T, path string, timeout time.Duration) {
|
||||
t.Helper()
|
||||
deadline := time.Now().Add(timeout)
|
||||
for time.Now().Before(deadline) {
|
||||
if _, err := os.Stat(path); err == nil {
|
||||
return
|
||||
}
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
}
|
||||
t.Fatalf("Timeout waiting for file: %s", path)
|
||||
}
|
||||
|
||||
func getTestScriptPath(t *testing.T, name string) string {
|
||||
t.Helper()
|
||||
path := filepath.Join("testdata", "scripts", name)
|
||||
if _, err := os.Stat(path); err != nil {
|
||||
t.Fatalf("Missing test script %s: %v", path, err)
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
func ensureScriptExecutable(t *testing.T, scriptPath string) {
|
||||
t.Helper()
|
||||
if filepath.Ext(scriptPath) != ".sh" {
|
||||
return
|
||||
}
|
||||
if err := os.Chmod(scriptPath, 0755); err != nil {
|
||||
t.Fatalf("Failed to chmod script: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func setupScriptTest(t *testing.T, fn func(downloader *Downloader)) {
|
||||
defaultDownloader.Setup()
|
||||
defaultDownloader.cfg.StorageDir = ".test_storage"
|
||||
defaultDownloader.cfg.DownloadDir = ".test_download"
|
||||
defer func() {
|
||||
defaultDownloader.Clear()
|
||||
os.RemoveAll(defaultDownloader.cfg.StorageDir)
|
||||
os.RemoveAll(defaultDownloader.cfg.DownloadDir)
|
||||
}()
|
||||
fn(defaultDownloader)
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
//go:build !windows
|
||||
// +build !windows
|
||||
|
||||
package download
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/GopeedLab/gopeed/pkg/base"
|
||||
)
|
||||
|
||||
func TestScript_TriggerOnDone_MoveFile(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
downloadDir := filepath.Join(tmpDir, "downloads")
|
||||
destDir := filepath.Join(tmpDir, "moved")
|
||||
task, taskPath := createDownloadDoneTask(t, downloadDir, "test.txt")
|
||||
srcPath := filepath.FromSlash(taskPath)
|
||||
destFile := filepath.Join(destDir, "test.txt")
|
||||
|
||||
scriptPath := getTestScriptPath(t, "move.sh")
|
||||
ensureScriptExecutable(t, scriptPath)
|
||||
|
||||
setupScriptTest(t, func(downloader *Downloader) {
|
||||
cfg, _ := downloader.GetConfig()
|
||||
cfg.Script = &base.ScriptConfig{
|
||||
Enable: true,
|
||||
Paths: []string{scriptPath},
|
||||
}
|
||||
downloader.PutConfig(cfg)
|
||||
|
||||
t.Setenv("GOPEED_TEST_DEST_DIR", destDir)
|
||||
downloader.triggerScripts(ScriptEventDownloadDone, task, nil)
|
||||
|
||||
waitForFile(t, destFile, 3*time.Second)
|
||||
if _, err := os.Stat(srcPath); !os.IsNotExist(err) {
|
||||
t.Errorf("Expected source file to be moved, but it still exists: %s", srcPath)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestScript_MultipleScripts(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
outputFile1 := filepath.Join(tmpDir, "output1.txt")
|
||||
outputFile2 := filepath.Join(tmpDir, "output2.txt")
|
||||
scriptPath1 := getTestScriptPath(t, "write_output1.sh")
|
||||
ensureScriptExecutable(t, scriptPath1)
|
||||
scriptPath2 := getTestScriptPath(t, "write_output2.sh")
|
||||
ensureScriptExecutable(t, scriptPath2)
|
||||
|
||||
setupScriptTest(t, func(downloader *Downloader) {
|
||||
cfg, _ := downloader.GetConfig()
|
||||
cfg.Script = &base.ScriptConfig{
|
||||
Enable: true,
|
||||
Paths: []string{scriptPath1, scriptPath2},
|
||||
}
|
||||
downloader.PutConfig(cfg)
|
||||
|
||||
downloadDir := filepath.Join(tmpDir, "downloads")
|
||||
task, _ := createDownloadDoneTask(t, downloadDir, "multi.txt")
|
||||
|
||||
t.Setenv("GOPEED_TEST_OUTPUT_FILE_1", outputFile1)
|
||||
t.Setenv("GOPEED_TEST_OUTPUT_FILE_2", outputFile2)
|
||||
downloader.triggerScripts(ScriptEventDownloadDone, task, nil)
|
||||
|
||||
waitForFile(t, outputFile1, 3*time.Second)
|
||||
waitForFile(t, outputFile2, 3*time.Second)
|
||||
})
|
||||
}
|
||||
|
||||
func TestScript_EnvironmentVariables(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
scriptPath := getTestScriptPath(t, "env_dump.sh")
|
||||
ensureScriptExecutable(t, scriptPath)
|
||||
outputFile := filepath.Join(tmpDir, "env_output.txt")
|
||||
|
||||
setupScriptTest(t, func(downloader *Downloader) {
|
||||
cfg, _ := downloader.GetConfig()
|
||||
cfg.Script = &base.ScriptConfig{
|
||||
Enable: true,
|
||||
Paths: []string{scriptPath},
|
||||
}
|
||||
downloader.PutConfig(cfg)
|
||||
|
||||
downloadDir := filepath.Join(tmpDir, "downloads")
|
||||
task, taskPath := createDownloadDoneTask(t, downloadDir, "env.txt")
|
||||
|
||||
t.Setenv("GOPEED_TEST_OUTPUT_FILE", outputFile)
|
||||
downloader.triggerScripts(ScriptEventDownloadDone, task, nil)
|
||||
|
||||
waitForFile(t, outputFile, 3*time.Second)
|
||||
content, err := os.ReadFile(outputFile)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to read output file: %v", err)
|
||||
}
|
||||
|
||||
output := string(content)
|
||||
if !strings.Contains(output, "GOPEED_EVENT=DOWNLOAD_DONE") {
|
||||
t.Errorf("Expected GOPEED_EVENT in output, got: %s", output)
|
||||
}
|
||||
if !strings.Contains(output, "GOPEED_TASK_ID="+task.ID) {
|
||||
t.Errorf("Expected GOPEED_TASK_ID in output, got: %s", output)
|
||||
}
|
||||
if !strings.Contains(output, "GOPEED_TASK_NAME="+task.Name()) {
|
||||
t.Errorf("Expected GOPEED_TASK_NAME in output, got: %s", output)
|
||||
}
|
||||
if !strings.Contains(output, "GOPEED_TASK_STATUS="+string(task.Status)) {
|
||||
t.Errorf("Expected GOPEED_TASK_STATUS in output, got: %s", output)
|
||||
}
|
||||
if !strings.Contains(output, "GOPEED_TASK_PATH="+taskPath) {
|
||||
t.Errorf("Expected GOPEED_TASK_PATH in output, got: %s", output)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package download
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/GopeedLab/gopeed/pkg/base"
|
||||
)
|
||||
|
||||
func TestScript_TriggerOnDone_MoveFile(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
downloadDir := filepath.Join(tmpDir, "downloads")
|
||||
destDir := filepath.Join(tmpDir, "moved")
|
||||
task, taskPath := createDownloadDoneTask(t, downloadDir, "test.txt")
|
||||
srcPath := filepath.FromSlash(taskPath)
|
||||
destFile := filepath.Join(destDir, "test.txt")
|
||||
|
||||
scriptPath := getTestScriptPath(t, "move.bat")
|
||||
|
||||
setupScriptTest(t, func(downloader *Downloader) {
|
||||
cfg, _ := downloader.GetConfig()
|
||||
cfg.Script = &base.ScriptConfig{
|
||||
Enable: true,
|
||||
Paths: []string{scriptPath},
|
||||
}
|
||||
downloader.PutConfig(cfg)
|
||||
|
||||
t.Setenv("GOPEED_TEST_DEST_DIR", destDir)
|
||||
downloader.triggerScripts(ScriptEventDownloadDone, task, nil)
|
||||
|
||||
waitForFile(t, destFile, 5*time.Second)
|
||||
if _, err := os.Stat(srcPath); !os.IsNotExist(err) {
|
||||
t.Errorf("Expected source file to be moved, but it still exists: %s", srcPath)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestScript_MultipleScripts(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
outputFile1 := filepath.Join(tmpDir, "output1.txt")
|
||||
outputFile2 := filepath.Join(tmpDir, "output2.txt")
|
||||
scriptPath1 := getTestScriptPath(t, "write_output1.bat")
|
||||
scriptPath2 := getTestScriptPath(t, "write_output2.bat")
|
||||
|
||||
setupScriptTest(t, func(downloader *Downloader) {
|
||||
cfg, _ := downloader.GetConfig()
|
||||
cfg.Script = &base.ScriptConfig{
|
||||
Enable: true,
|
||||
Paths: []string{scriptPath1, scriptPath2},
|
||||
}
|
||||
downloader.PutConfig(cfg)
|
||||
|
||||
downloadDir := filepath.Join(tmpDir, "downloads")
|
||||
task, _ := createDownloadDoneTask(t, downloadDir, "multi.txt")
|
||||
|
||||
t.Setenv("GOPEED_TEST_OUTPUT_FILE_1", outputFile1)
|
||||
t.Setenv("GOPEED_TEST_OUTPUT_FILE_2", outputFile2)
|
||||
downloader.triggerScripts(ScriptEventDownloadDone, task, nil)
|
||||
|
||||
waitForFile(t, outputFile1, 5*time.Second)
|
||||
waitForFile(t, outputFile2, 5*time.Second)
|
||||
})
|
||||
}
|
||||
|
||||
func TestScript_EnvironmentVariables(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
scriptPath := getTestScriptPath(t, "env_dump.bat")
|
||||
outputFile := filepath.Join(tmpDir, "env_output.txt")
|
||||
|
||||
setupScriptTest(t, func(downloader *Downloader) {
|
||||
cfg, _ := downloader.GetConfig()
|
||||
cfg.Script = &base.ScriptConfig{
|
||||
Enable: true,
|
||||
Paths: []string{scriptPath},
|
||||
}
|
||||
downloader.PutConfig(cfg)
|
||||
|
||||
downloadDir := filepath.Join(tmpDir, "downloads")
|
||||
task, taskPath := createDownloadDoneTask(t, downloadDir, "env.txt")
|
||||
|
||||
t.Setenv("GOPEED_TEST_OUTPUT_FILE", outputFile)
|
||||
downloader.triggerScripts(ScriptEventDownloadDone, task, nil)
|
||||
|
||||
waitForFile(t, outputFile, 5*time.Second)
|
||||
content, err := os.ReadFile(outputFile)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to read output file: %v", err)
|
||||
}
|
||||
|
||||
output := string(content)
|
||||
if !strings.Contains(output, "GOPEED_EVENT=DOWNLOAD_DONE") {
|
||||
t.Errorf("Expected GOPEED_EVENT in output, got: %s", output)
|
||||
}
|
||||
if !strings.Contains(output, "GOPEED_TASK_ID="+task.ID) {
|
||||
t.Errorf("Expected GOPEED_TASK_ID in output, got: %s", output)
|
||||
}
|
||||
if !strings.Contains(output, "GOPEED_TASK_NAME="+task.Name()) {
|
||||
t.Errorf("Expected GOPEED_TASK_NAME in output, got: %s", output)
|
||||
}
|
||||
if !strings.Contains(output, "GOPEED_TASK_STATUS="+string(task.Status)) {
|
||||
t.Errorf("Expected GOPEED_TASK_STATUS in output, got: %s", output)
|
||||
}
|
||||
if !strings.Contains(output, "GOPEED_TASK_PATH="+taskPath) {
|
||||
t.Errorf("Expected GOPEED_TASK_PATH in output, got: %s", output)
|
||||
}
|
||||
})
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
@echo off
|
||||
setlocal
|
||||
|
||||
if "%GOPEED_TEST_OUTPUT_FILE%"=="" exit /b 2
|
||||
echo GOPEED_EVENT=%GOPEED_EVENT% > "%GOPEED_TEST_OUTPUT_FILE%"
|
||||
echo GOPEED_TASK_ID=%GOPEED_TASK_ID% >> "%GOPEED_TEST_OUTPUT_FILE%"
|
||||
echo GOPEED_TASK_NAME=%GOPEED_TASK_NAME% >> "%GOPEED_TEST_OUTPUT_FILE%"
|
||||
echo GOPEED_TASK_STATUS=%GOPEED_TASK_STATUS% >> "%GOPEED_TEST_OUTPUT_FILE%"
|
||||
echo GOPEED_TASK_PATH=%GOPEED_TASK_PATH% >> "%GOPEED_TEST_OUTPUT_FILE%"
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
if [ -z "$GOPEED_TEST_OUTPUT_FILE" ]; then
|
||||
echo "GOPEED_TEST_OUTPUT_FILE is empty" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
echo "GOPEED_EVENT=$GOPEED_EVENT" > "$GOPEED_TEST_OUTPUT_FILE"
|
||||
echo "GOPEED_TASK_ID=$GOPEED_TASK_ID" >> "$GOPEED_TEST_OUTPUT_FILE"
|
||||
echo "GOPEED_TASK_NAME=$GOPEED_TASK_NAME" >> "$GOPEED_TEST_OUTPUT_FILE"
|
||||
echo "GOPEED_TASK_STATUS=$GOPEED_TASK_STATUS" >> "$GOPEED_TEST_OUTPUT_FILE"
|
||||
echo "GOPEED_TASK_PATH=$GOPEED_TASK_PATH" >> "$GOPEED_TEST_OUTPUT_FILE"
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
@echo off
|
||||
setlocal
|
||||
|
||||
if "%GOPEED_TEST_DEST_DIR%"=="" exit /b 2
|
||||
set "SRC=%GOPEED_TASK_PATH:/=\%"
|
||||
set "DEST=%GOPEED_TEST_DEST_DIR%"
|
||||
if not exist "%DEST%" mkdir "%DEST%"
|
||||
move /Y "%SRC%" "%DEST%\" >nul
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
if [ -z "$GOPEED_TEST_DEST_DIR" ]; then
|
||||
echo "GOPEED_TEST_DEST_DIR is empty" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
mkdir -p "$GOPEED_TEST_DEST_DIR"
|
||||
mv "$GOPEED_TASK_PATH" "$GOPEED_TEST_DEST_DIR/"
|
||||
@@ -0,0 +1,5 @@
|
||||
@echo off
|
||||
setlocal
|
||||
|
||||
if "%GOPEED_TEST_OUTPUT_FILE_1%"=="" exit /b 2
|
||||
echo Script 1 > "%GOPEED_TEST_OUTPUT_FILE_1%"
|
||||
@@ -0,0 +1,9 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
if [ -z "$GOPEED_TEST_OUTPUT_FILE_1" ]; then
|
||||
echo "GOPEED_TEST_OUTPUT_FILE_1 is empty" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
echo "Script 1" > "$GOPEED_TEST_OUTPUT_FILE_1"
|
||||
@@ -0,0 +1,5 @@
|
||||
@echo off
|
||||
setlocal
|
||||
|
||||
if "%GOPEED_TEST_OUTPUT_FILE_2%"=="" exit /b 2
|
||||
echo Script 2 > "%GOPEED_TEST_OUTPUT_FILE_2%"
|
||||
@@ -0,0 +1,9 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
if [ -z "$GOPEED_TEST_OUTPUT_FILE_2" ]; then
|
||||
echo "GOPEED_TEST_OUTPUT_FILE_2 is empty" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
echo "Script 2" > "$GOPEED_TEST_OUTPUT_FILE_2"
|
||||
Reference in new issue
Block a user