Skip to content

Commit

Permalink
feat: improve extension engine (#298)
Browse files Browse the repository at this point in the history
1. support onError event
2. add built-in MessageError
  • Loading branch information
monkeyWie authored Dec 12, 2023
1 parent ff32971 commit 2b5091b
Show file tree
Hide file tree
Showing 16 changed files with 284 additions and 79 deletions.
67 changes: 34 additions & 33 deletions pkg/download/downloader.go
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,10 @@ func (d *Downloader) Resolve(req *base.Request) (rr *ResolveResult, err error) {
return
}

res := d.triggerOnResolve(req)
res, err := d.triggerOnResolve(req)
if err != nil {
return
}
if res != nil && len(res.Files) > 0 {
rr = &ResolveResult{
Res: res,
Expand Down Expand Up @@ -525,9 +528,6 @@ func (d *Downloader) emit(eventKey EventKey, task *Task, errs ...error) {
Task: task,
Err: err,
})
if eventKey == EventKeyError {
d.emit(EventKeyFinally, task, err)
}
}
}

Expand Down Expand Up @@ -572,27 +572,36 @@ func (d *Downloader) getProtocolConfig(name string, v any) bool {
func (d *Downloader) watch(task *Task) {
err := task.fetcher.Wait()
if err != nil {
task.updateStatus(base.DownloadStatusError)
d.storage.Put(bucketTask, task.ID, task.clone())
d.doOnError(task, err)
return
}
task.Progress.Used = task.timer.Used()
if task.Meta.Res.Size == 0 {
task.Meta.Res.Size = task.fetcher.Progress().TotalDownloaded()
}
used := task.Progress.Used / int64(time.Second)
if used == 0 {
used = 1
}
totalSize := task.Meta.Res.Size
task.Progress.Speed = totalSize / used
task.Progress.Downloaded = totalSize
task.updateStatus(base.DownloadStatusDone)
d.storage.Put(bucketTask, task.ID, task.clone())
d.emit(EventKeyDone, task)
d.emit(EventKeyFinally, task, err)
d.notifyRunning()
}

func (d *Downloader) doOnError(task *Task, err error) {
d.Logger.Warn().Err(err).Msgf("task download failed, task id: %s", task.ID)
task.updateStatus(base.DownloadStatusError)
d.triggerOnError(task, err)
if task.Status == base.DownloadStatusError {
d.emit(EventKeyError, task, err)
} else {
task.Progress.Used = task.timer.Used()
if task.Meta.Res.Size == 0 {
task.Meta.Res.Size = task.fetcher.Progress().TotalDownloaded()
}
used := task.Progress.Used / int64(time.Second)
if used == 0 {
used = 1
}
totalSize := task.Meta.Res.Size
task.Progress.Speed = totalSize / used
task.Progress.Downloaded = totalSize
task.updateStatus(base.DownloadStatusDone)
d.storage.Put(bucketTask, task.ID, task.clone())
d.emit(EventKeyDone, task)
d.emit(EventKeyFinally, task, err)
d.notifyRunning()
}
d.notifyRunning()
}

func (d *Downloader) restoreFetcher(task *Task) error {
Expand Down Expand Up @@ -697,26 +706,18 @@ func (d *Downloader) doStart(task *Task) (err error) {
}
}

cloneTask := task.clone()
isCreate := task.Status == base.DownloadStatusReady
task.updateStatus(base.DownloadStatusRunning)

doStart := func() error {
task.lock.Lock()
defer task.lock.Unlock()

req := d.triggerOnStart(cloneTask)
if req != nil {
task.Meta.Req = req
task.fetcher.Meta().Req = req
}
d.triggerOnStart(task)
task.updateStatus(base.DownloadStatusRunning)

if task.Meta.Res == nil {
err := task.fetcher.Resolve(task.Meta.Req)
if err != nil {
task.updateStatus(base.DownloadStatusError)
d.storage.Put(bucketTask, task.ID, task.clone())
d.emit(EventKeyError, task, err)
return err
}
task.Meta.Res = task.fetcher.Meta().Res
Expand Down Expand Up @@ -762,7 +763,7 @@ func (d *Downloader) doStart(task *Task) (err error) {
go func() {
err := doStart()
if err != nil {
d.Logger.Error().Stack().Err(err).Msgf("start task failed, task id: %s", task.ID)
d.doOnError(task, err)
}
}()

Expand Down
10 changes: 9 additions & 1 deletion pkg/download/engine/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package engine
import (
_ "embed"
"errors"
gojaerror "github.com/GopeedLab/gopeed/pkg/download/engine/inject/error"
"github.com/GopeedLab/gopeed/pkg/download/engine/inject/file"
"github.com/GopeedLab/gopeed/pkg/download/engine/inject/formdata"
"github.com/GopeedLab/gopeed/pkg/download/engine/inject/vm"
Expand Down Expand Up @@ -125,6 +126,9 @@ func NewEngine(cfg *Config) *Engine {
runtime.SetFieldNameMapper(goja.TagFieldNameMapper("json", true))
vm.Enable(runtime)
gojaurl.Enable(runtime)
if err := gojaerror.Enable(runtime); err != nil {
return
}
if err := file.Enable(runtime); err != nil {
return
}
Expand Down Expand Up @@ -167,7 +171,11 @@ func resolveResult(value goja.Value) (any, error) {
case goja.PromiseStateFulfilled:
return p.Result().Export(), nil
case goja.PromiseStateRejected:
return nil, errors.New(p.Result().String())
if err, ok := p.Result().Export().(error); ok {
return nil, err
} else {
return nil, errors.New(p.Result().String())
}
}
}
return export, nil
Expand Down
13 changes: 13 additions & 0 deletions pkg/download/engine/engine_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ import (
"errors"
"fmt"
"github.com/GopeedLab/gopeed/internal/test"
gojaerror "github.com/GopeedLab/gopeed/pkg/download/engine/inject/error"
"github.com/GopeedLab/gopeed/pkg/download/engine/inject/file"
gojautil "github.com/GopeedLab/gopeed/pkg/download/engine/util"
"github.com/GopeedLab/gopeed/pkg/util"
"github.com/dop251/goja"
"io"
Expand All @@ -20,13 +22,24 @@ import (
)

func TestPolyfill(t *testing.T) {
doTestPolyfill(t, "MessageError")
doTestPolyfill(t, "XMLHttpRequest")
doTestPolyfill(t, "Blob")
doTestPolyfill(t, "FormData")
doTestPolyfill(t, "fetch")
doTestPolyfill(t, "__gopeed_create_vm")
}

func TestError(t *testing.T) {
engine := NewEngine(nil)
_, err := engine.RunString(`
throw new MessageError('test');
`)
if me, ok := gojautil.AssertError[*gojaerror.MessageError](err); !ok {
t.Fatalf("expect MessageError, but got %v", me)
}
}

func TestFetch(t *testing.T) {
server := startServer()
defer server.Close()
Expand Down
29 changes: 29 additions & 0 deletions pkg/download/engine/inject/error/module.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package error

import (
"github.com/dop251/goja"
)

type MessageError struct {
Message string `json:"message"`
}

func (e *MessageError) Error() string {
return e.Message
}

func Enable(runtime *goja.Runtime) error {
messageError := runtime.ToValue(func(call goja.ConstructorCall) *goja.Object {
var message string
if len(call.Arguments) > 0 {
message = call.Arguments[0].String()
}
instance := &MessageError{
Message: message,
}
instanceValue := runtime.ToValue(instance).(*goja.Object)
instanceValue.SetPrototype(call.This.Prototype())
return instanceValue
})
return runtime.Set("MessageError", messageError)
}
9 changes: 0 additions & 9 deletions pkg/download/engine/inject/util.go

This file was deleted.

4 changes: 2 additions & 2 deletions pkg/download/engine/inject/xhr/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ package xhr

import (
"bytes"
"github.com/GopeedLab/gopeed/pkg/download/engine/inject"
"github.com/GopeedLab/gopeed/pkg/download/engine/inject/file"
"github.com/GopeedLab/gopeed/pkg/download/engine/inject/formdata"
"github.com/GopeedLab/gopeed/pkg/download/engine/util"
"github.com/dop251/goja"
"io"
"mime/multipart"
Expand Down Expand Up @@ -316,7 +316,7 @@ func (xhr *XMLHttpRequest) parseData(data goja.Value) any {
func Enable(runtime *goja.Runtime, proxyUrl *url.URL) error {
progressEvent := runtime.ToValue(func(call goja.ConstructorCall) *goja.Object {
if len(call.Arguments) < 1 {
inject.ThrowTypeError(runtime, "Failed to construct 'ProgressEvent': 1 argument required, but only 0 present.")
util.ThrowTypeError(runtime, "Failed to construct 'ProgressEvent': 1 argument required, but only 0 present.")
}
instance := &ProgressEvent{
Type: call.Argument(0).String(),
Expand Down
24 changes: 24 additions & 0 deletions pkg/download/engine/util/util.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package util

import (
"github.com/dop251/goja"
)

func ThrowTypeError(vm *goja.Runtime, msg string) {
panic(vm.NewTypeError(msg))
}

func AssertError[T error](err error) (t T, r bool) {
if err == nil {
return
}
if e, ok := err.(T); ok {
return e, true
}
if e, ok := err.(*goja.Exception); ok {
if ee, okk := e.Value().Export().(T); okk {
return ee, true
}
}
return
}
Loading

0 comments on commit 2b5091b

Please sign in to comment.