mirror of
https://github.com/GopeedLab/gopeed.git
synced 2026-11-07 17:10:29 +00:00
feat: extension on start event (#239)
This commit is contained in:
23 files changed
+292
-101
No files matched your search
@@ -11,6 +11,8 @@ categories:
|
||||
- 'fix'
|
||||
- 'bugfix'
|
||||
- 'bug'
|
||||
- title: '🧪 Tests'
|
||||
label: 'test'
|
||||
- title: '🧰 Maintenance'
|
||||
label: 'chore'
|
||||
- title: '📖 Document'
|
||||
|
||||
+9
-1
@@ -9,6 +9,15 @@ import (
|
||||
type Request struct {
|
||||
URL string `json:"url"`
|
||||
Extra any `json:"extra"`
|
||||
// Labels is used to mark the download task
|
||||
Labels map[string]string `json:"labels"`
|
||||
}
|
||||
|
||||
func (r *Request) Validate() error {
|
||||
if r.URL == "" {
|
||||
return fmt.Errorf("invalid request url")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Resource download resource
|
||||
@@ -36,7 +45,6 @@ func (r *Resource) Validate() error {
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func (r *Resource) CalcSize() {
|
||||
|
||||
@@ -429,6 +429,9 @@ func (d *Downloader) emit(eventKey EventKey, task *Task, errs ...error) {
|
||||
Task: task,
|
||||
Err: err,
|
||||
})
|
||||
if eventKey == EventKeyError {
|
||||
d.emit(EventKeyFinally, task, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -494,8 +497,8 @@ func (d *Downloader) watch(task *Task) {
|
||||
task.Status = base.DownloadStatusDone
|
||||
d.storage.Put(bucketTask, task.ID, task.clone())
|
||||
d.emit(EventKeyDone, task)
|
||||
d.emit(EventKeyFinally, task, err)
|
||||
}
|
||||
d.emit(EventKeyFinally, task, err)
|
||||
if task.Status == EventKeyDone {
|
||||
d.notifyRunning()
|
||||
}
|
||||
@@ -622,6 +625,8 @@ func (d *Downloader) doPauseAll() (err error) {
|
||||
func (d *Downloader) start(task *Task) error {
|
||||
task.lock.Lock()
|
||||
defer task.lock.Unlock()
|
||||
d.triggerOnStart(task)
|
||||
|
||||
isCreate := task.Status == base.DownloadStatusReady
|
||||
task.Status = base.DownloadStatusRunning
|
||||
if task.Meta.Res == nil {
|
||||
|
||||
+118
-35
@@ -33,6 +33,7 @@ type ActivationEvent string
|
||||
|
||||
const (
|
||||
EventOnResolve ActivationEvent = "onResolve"
|
||||
EventOnStart ActivationEvent = "onStart"
|
||||
//EventOnError ActivationEvent = "onError"
|
||||
//EventOnDone ActivationEvent = "onDone"
|
||||
)
|
||||
@@ -47,7 +48,7 @@ func (d *Downloader) InstallExtensionByFolder(path string, devMode bool) (*Exten
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// if dev mode, don't copy the extension to the extensions' directory
|
||||
// if dev mode, don't copy to the extensions' directory
|
||||
if devMode {
|
||||
ext.DevMode = true
|
||||
ext.DevPath, _ = filepath.Abs(path)
|
||||
@@ -229,23 +230,71 @@ func (d *Downloader) parseExtensionByPath(path string) (*Extension, error) {
|
||||
}
|
||||
|
||||
func (d *Downloader) triggerOnResolve(req *base.Request) (res *base.Resource) {
|
||||
doTrigger(d,
|
||||
EventOnResolve,
|
||||
req,
|
||||
&OnResolveContext{
|
||||
Req: req,
|
||||
},
|
||||
func(ext *Extension, gopeed *Instance, ctx *OnResolveContext) {
|
||||
// Validate resource structure
|
||||
if ctx.Res != nil && len(ctx.Res.Files) > 0 {
|
||||
if err := ctx.Res.Validate(); err != nil {
|
||||
gopeed.Logger.logger.Warn().Err(err).Msgf("[%s] resource invalid", ext.buildIdentity())
|
||||
return
|
||||
}
|
||||
ctx.Res.CalcSize()
|
||||
}
|
||||
res = ctx.Res
|
||||
},
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
func (d *Downloader) triggerOnStart(task *Task) {
|
||||
doTrigger(d,
|
||||
EventOnStart,
|
||||
task.Meta.Req,
|
||||
&OnStartContext{
|
||||
Task: task.clone(),
|
||||
},
|
||||
func(ext *Extension, gopeed *Instance, ctx *OnStartContext) {
|
||||
// Validate request structure
|
||||
if ctx.Task.Meta.Req != nil {
|
||||
if err := ctx.Task.Meta.Req.Validate(); err != nil {
|
||||
gopeed.Logger.logger.Warn().Err(err).Msgf("[%s] request invalid", ext.buildIdentity())
|
||||
return
|
||||
}
|
||||
// Modify real task request
|
||||
task.Meta.Req = ctx.Task.Meta.Req
|
||||
}
|
||||
},
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
func doTrigger[T any](d *Downloader, event ActivationEvent, req *base.Request, ctx T, handler func(ext *Extension, gopeed *Instance, ctx T)) {
|
||||
// init extension global object
|
||||
gopeed := &Instance{
|
||||
Events: make(InstanceEvents),
|
||||
}
|
||||
ctx := &Context{
|
||||
Req: req,
|
||||
}
|
||||
var err error
|
||||
for _, ext := range d.extensions {
|
||||
if ext.Disabled {
|
||||
continue
|
||||
}
|
||||
for _, script := range ext.Scripts {
|
||||
if script.match(EventOnResolve, req.URL) {
|
||||
if script.match(event, req) {
|
||||
gopeed.Info = NewExtensionInfo(ext)
|
||||
gopeed.Logger = newInstanceLogger(ext, d.ExtensionLogger)
|
||||
gopeed.Settings = parseSettings(ext.Settings)
|
||||
gopeed.Storage = &ContextStorage{
|
||||
storage: d.storage,
|
||||
identity: ext.buildIdentity(),
|
||||
}
|
||||
scriptFilePath := filepath.Join(d.ExtensionPath(ext), script.Entry)
|
||||
if _, err = os.Stat(scriptFilePath); os.IsNotExist(err) {
|
||||
gopeed.Logger.logger.Error().Err(err).Msgf("[%s] script file not exist", ext.buildIdentity())
|
||||
continue
|
||||
}
|
||||
func() {
|
||||
@@ -256,17 +305,16 @@ func (d *Downloader) triggerOnResolve(req *base.Request) (res *base.Resource) {
|
||||
return
|
||||
}
|
||||
defer scriptFile.Close()
|
||||
ctx.Settings = parseSettings(ext.Settings)
|
||||
ctx.Storage = &ContextStorage{
|
||||
storage: d.storage,
|
||||
identity: ext.buildIdentity(),
|
||||
}
|
||||
var scriptBuf []byte
|
||||
scriptBuf, err = io.ReadAll(scriptFile)
|
||||
if err != nil {
|
||||
gopeed.Logger.logger.Error().Err(err).Msgf("[%s] read script file failed", ext.buildIdentity())
|
||||
return
|
||||
}
|
||||
// Init request labels
|
||||
if req.Labels == nil {
|
||||
req.Labels = make(map[string]string)
|
||||
}
|
||||
engine := engine.NewEngine()
|
||||
defer engine.Close()
|
||||
err = engine.Runtime.Set("gopeed", gopeed)
|
||||
@@ -279,21 +327,13 @@ func (d *Downloader) triggerOnResolve(req *base.Request) (res *base.Resource) {
|
||||
gopeed.Logger.logger.Error().Err(err).Msgf("[%s] run script failed", ext.buildIdentity())
|
||||
return
|
||||
}
|
||||
if fn, ok := gopeed.Events[EventOnResolve]; ok {
|
||||
if fn, ok := gopeed.Events[event]; ok {
|
||||
_, err = engine.CallFunction(fn, ctx)
|
||||
if err != nil {
|
||||
gopeed.Logger.logger.Error().Err(err).Msgf("[%s] call function failed: %s", EventOnResolve, ext.buildIdentity())
|
||||
gopeed.Logger.logger.Error().Err(err).Msgf("[%s] call function failed: %s", ext.buildIdentity(), event)
|
||||
return
|
||||
}
|
||||
// calculate resource total size
|
||||
if ctx.Res != nil && len(ctx.Res.Files) > 0 {
|
||||
if err = ctx.Res.Validate(); err != nil {
|
||||
gopeed.Logger.logger.Warn().Err(err).Msgf("[%s] resource invalid", ext.buildIdentity())
|
||||
return
|
||||
}
|
||||
ctx.Res.CalcSize()
|
||||
}
|
||||
res = ctx.Res
|
||||
handler(ext, gopeed, ctx)
|
||||
}
|
||||
}()
|
||||
}
|
||||
@@ -396,30 +436,46 @@ type Repository struct {
|
||||
type Script struct {
|
||||
// Event active event name
|
||||
Event string `json:"event"`
|
||||
// Matches match request url pattern list
|
||||
Matches []string `json:"matches"`
|
||||
// Match rules
|
||||
Match *Match `json:"match"`
|
||||
// Entry js script file path
|
||||
Entry string `json:"entry"`
|
||||
}
|
||||
|
||||
func (s *Script) match(event ActivationEvent, url string) bool {
|
||||
func (s *Script) match(event ActivationEvent, req *base.Request) bool {
|
||||
if s.Event == "" {
|
||||
return false
|
||||
}
|
||||
if s.Event != string(event) {
|
||||
return false
|
||||
}
|
||||
if len(s.Matches) == 0 {
|
||||
if s.Match == nil || (len(s.Match.Urls) == 0 && len(s.Match.Labels) == 0) {
|
||||
return false
|
||||
}
|
||||
for _, match := range s.Matches {
|
||||
if util.Match(match, url) {
|
||||
|
||||
// match url
|
||||
for _, url := range s.Match.Urls {
|
||||
if util.Match(url, req.URL) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// match label
|
||||
for _, label := range s.Match.Labels {
|
||||
if _, ok := req.Labels[label]; ok {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type Match struct {
|
||||
// Urls match expression, refer to https://developer.chrome.com/docs/extensions/mv3/match_patterns/
|
||||
Urls []string `json:"urls"`
|
||||
// Labels match request labels
|
||||
Labels []string `json:"labels"`
|
||||
}
|
||||
|
||||
type SettingType string
|
||||
|
||||
const (
|
||||
@@ -446,10 +502,13 @@ type Option struct {
|
||||
Value any `json:"value"`
|
||||
}
|
||||
|
||||
// Instance inject to js context
|
||||
// Instance inject to js context when extension script is activated
|
||||
type Instance struct {
|
||||
Events InstanceEvents `json:"events"`
|
||||
Logger *InstanceLogger `json:"logger"`
|
||||
Events InstanceEvents `json:"events"`
|
||||
Info *ExtensionInfo `json:"info"`
|
||||
Logger *InstanceLogger `json:"logger"`
|
||||
Settings map[string]any `json:"settings"`
|
||||
Storage *ContextStorage `json:"storage"`
|
||||
}
|
||||
|
||||
type InstanceEvents map[ActivationEvent]goja.Callable
|
||||
@@ -462,6 +521,10 @@ func (h InstanceEvents) OnResolve(fn goja.Callable) {
|
||||
h.register(EventOnResolve, fn)
|
||||
}
|
||||
|
||||
func (h InstanceEvents) OnStart(fn goja.Callable) {
|
||||
h.register(EventOnStart, fn)
|
||||
}
|
||||
|
||||
//func (h InstanceEvents) OnError(fn goja.Callable) {
|
||||
// h.register(HookEventOnError, fn)
|
||||
//}
|
||||
@@ -470,6 +533,24 @@ func (h InstanceEvents) OnResolve(fn goja.Callable) {
|
||||
// h.register(HookEventOnDone, fn)
|
||||
//}
|
||||
|
||||
type ExtensionInfo struct {
|
||||
Identity string `json:"identity"`
|
||||
Name string `json:"name"`
|
||||
Author string `json:"author"`
|
||||
Title string `json:"title"`
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
func NewExtensionInfo(ext *Extension) *ExtensionInfo {
|
||||
return &ExtensionInfo{
|
||||
Identity: ext.buildIdentity(),
|
||||
Name: ext.Name,
|
||||
Author: ext.Author,
|
||||
Title: ext.Title,
|
||||
Version: ext.Version,
|
||||
}
|
||||
}
|
||||
|
||||
type InstanceLogger struct {
|
||||
identity string
|
||||
devMode bool
|
||||
@@ -510,11 +591,13 @@ func newInstanceLogger(extension *Extension, logger *logger.Logger) *InstanceLog
|
||||
}
|
||||
}
|
||||
|
||||
type Context struct {
|
||||
Req *base.Request `json:"req"`
|
||||
Res *base.Resource `json:"res"`
|
||||
Settings map[string]any `json:"settings"`
|
||||
Storage *ContextStorage `json:"storage"`
|
||||
type OnResolveContext struct {
|
||||
Req *base.Request `json:"req"`
|
||||
Res *base.Resource `json:"res"`
|
||||
}
|
||||
|
||||
type OnStartContext struct {
|
||||
Task *Task `json:"task"`
|
||||
}
|
||||
|
||||
func parseSettings(settings []*Setting) map[string]any {
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
package download
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/GopeedLab/gopeed/internal/logger"
|
||||
"github.com/GopeedLab/gopeed/pkg/base"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestDownloader_InstallExtensionByFolder(t *testing.T) {
|
||||
@@ -132,20 +134,52 @@ func TestDownloader_UpgradeExtension(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestDownloader_ResolveWithExtra(t *testing.T) {
|
||||
setupDownloader(func(downloader *Downloader) {
|
||||
if _, err := downloader.InstallExtensionByFolder("./testdata/extensions/extra", false); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
rr, err := downloader.Resolve(&base.Request{
|
||||
URL: "https://github.com/test",
|
||||
func TestDownloader_ExtensionByOnStart(t *testing.T) {
|
||||
downloadAndCheck := func(req *base.Request) {
|
||||
setupDownloader(func(downloader *Downloader) {
|
||||
if _, err := downloader.InstallExtensionByFolder("./testdata/extensions/on_start", false); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
errCh := make(chan error, 1)
|
||||
downloader.Listener(func(event *Event) {
|
||||
if event.Key == EventKeyFinally {
|
||||
errCh <- event.Err
|
||||
}
|
||||
})
|
||||
id, err := downloader.CreateDirect(req, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
select {
|
||||
case err = <-errCh:
|
||||
break
|
||||
case <-time.After(time.Second * 10):
|
||||
err = errors.New("timeout")
|
||||
}
|
||||
if err != nil {
|
||||
panic("extension on start download error: " + err.Error())
|
||||
}
|
||||
task := downloader.GetTask(id)
|
||||
if task.Meta.Req.URL != "https://github.com" {
|
||||
panic("extension on start modify url error")
|
||||
}
|
||||
if task.Meta.Req.Labels["modified"] != "true" {
|
||||
panic("extension on start modify label error")
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(rr.Res.Files) != 2 {
|
||||
t.Fatal("resolve error")
|
||||
}
|
||||
}
|
||||
|
||||
// url match
|
||||
downloadAndCheck(&base.Request{
|
||||
URL: "https://github.com/gopeed/test/404",
|
||||
})
|
||||
|
||||
// label match
|
||||
downloadAndCheck(&base.Request{
|
||||
URL: "https://xxx.com",
|
||||
Labels: map[string]string{
|
||||
"test": "true",
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,9 @@ gopeed.events.onResolve(async function (ctx) {
|
||||
size: 1024,
|
||||
req: {
|
||||
url: ctx.req.url + "/" + i,
|
||||
labels:{
|
||||
"from": gopeed.info.name,
|
||||
}
|
||||
}
|
||||
}),
|
||||
),
|
||||
|
||||
+5
-3
@@ -5,9 +5,11 @@
|
||||
"scripts": [
|
||||
{
|
||||
"event": "onResolve",
|
||||
"matches": [
|
||||
"*://github.com/*"
|
||||
],
|
||||
"match": {
|
||||
"urls": [
|
||||
"*://github.com/*"
|
||||
]
|
||||
},
|
||||
"entry": "index.js"
|
||||
}
|
||||
],
|
||||
|
||||
@@ -5,9 +5,11 @@
|
||||
"scripts": [
|
||||
{
|
||||
"event": "onResolve",
|
||||
"matches": [
|
||||
"*://github.com/*"
|
||||
],
|
||||
"match": {
|
||||
"urls": [
|
||||
"*://github.com/*"
|
||||
]
|
||||
},
|
||||
"entry": "index.js"
|
||||
}
|
||||
],
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
gopeed.events.onStart(async function (ctx) {
|
||||
ctx.task.meta.req.url = "https://github.com";
|
||||
ctx.task.meta.req.labels['modified'] = 'true';
|
||||
});
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "on-start",
|
||||
"title": "gopeed extension on start event test",
|
||||
"version": "0.0.1",
|
||||
"scripts": [
|
||||
{
|
||||
"event": "onStart",
|
||||
"match": {
|
||||
"urls": [
|
||||
"*://github.com/*"
|
||||
],
|
||||
"labels": [
|
||||
"test"
|
||||
]
|
||||
},
|
||||
"entry": "index.js"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -5,9 +5,11 @@
|
||||
"scripts": [
|
||||
{
|
||||
"event": "onResolve",
|
||||
"matches": [
|
||||
"*://github.com/*"
|
||||
],
|
||||
"match": {
|
||||
"urls": [
|
||||
"*://github.com/*"
|
||||
]
|
||||
},
|
||||
"entry": "index.js"
|
||||
}
|
||||
],
|
||||
|
||||
+9
-9
@@ -1,31 +1,31 @@
|
||||
gopeed.events.onResolve(async function (ctx) {
|
||||
if (ctx.settings.string != null) {
|
||||
if (gopeed.settings.string != null) {
|
||||
throw new Error("string is not null");
|
||||
}
|
||||
if (ctx.settings.number != null) {
|
||||
if (gopeed.settings.number != null) {
|
||||
throw new Error("number is not null");
|
||||
}
|
||||
if (ctx.settings.boolean != null) {
|
||||
if (gopeed.settings.boolean != null) {
|
||||
throw new Error("boolean is not null");
|
||||
}
|
||||
|
||||
if (ctx.settings.stringDefault !== "default") {
|
||||
if (gopeed.settings.stringDefault !== "default") {
|
||||
throw new Error("string default value is incorrect");
|
||||
}
|
||||
if (ctx.settings.numberDefault !== 1) {
|
||||
if (gopeed.settings.numberDefault !== 1) {
|
||||
throw new Error("number default value is incorrect");
|
||||
}
|
||||
if (ctx.settings.booleanDefault !== true) {
|
||||
if (gopeed.settings.booleanDefault !== true) {
|
||||
throw new Error("boolean default value is incorrect");
|
||||
}
|
||||
|
||||
if (ctx.settings.stringValued !== "valued") {
|
||||
if (gopeed.settings.stringValued !== "valued") {
|
||||
throw new Error("string value is incorrect");
|
||||
}
|
||||
if (ctx.settings.numberValued !== 1.1) {
|
||||
if (gopeed.settings.numberValued !== 1.1) {
|
||||
throw new Error("number value is incorrect");
|
||||
}
|
||||
if (ctx.settings.booleanValued !== true) {
|
||||
if (gopeed.settings.booleanValued !== true) {
|
||||
throw new Error("boolean value is incorrect");
|
||||
}
|
||||
|
||||
|
||||
@@ -5,9 +5,11 @@
|
||||
"scripts": [
|
||||
{
|
||||
"event": "onResolve",
|
||||
"matches": [
|
||||
"*://*/*"
|
||||
],
|
||||
"match": {
|
||||
"urls": [
|
||||
"*://*/*"
|
||||
]
|
||||
},
|
||||
"entry": "index.js"
|
||||
}
|
||||
],
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
gopeed.events.onResolve(async function (ctx) {
|
||||
if (Object.keys(ctx.settings).length > 0){
|
||||
if (Object.keys(gopeed.settings).length > 0){
|
||||
throw new Error("settings is not empty");
|
||||
}
|
||||
|
||||
|
||||
@@ -5,9 +5,11 @@
|
||||
"scripts": [
|
||||
{
|
||||
"event": "onResolve",
|
||||
"matches": [
|
||||
"*://*/*"
|
||||
],
|
||||
"match": {
|
||||
"urls": [
|
||||
"*://*/*"
|
||||
]
|
||||
},
|
||||
"entry": "index.js"
|
||||
}
|
||||
]
|
||||
|
||||
+13
-13
@@ -2,36 +2,36 @@ gopeed.events.onResolve(async function (ctx) {
|
||||
const key = "key"
|
||||
const value1 = "value1", value2 = JSON.stringify({a: 1, b: "2"})
|
||||
|
||||
if (ctx.storage.get(key) !== null) {
|
||||
if (gopeed.storage.get(key) !== null) {
|
||||
throw new Error("storage get null error")
|
||||
}
|
||||
ctx.storage.remove(key)
|
||||
if(ctx.storage.keys().length !== 0) {
|
||||
gopeed.storage.remove(key)
|
||||
if(gopeed.storage.keys().length !== 0) {
|
||||
throw new Error("storage keys null error")
|
||||
}
|
||||
|
||||
ctx.storage.set(key, value1)
|
||||
if (ctx.storage.get(key) !== value1) {
|
||||
gopeed.storage.set(key, value1)
|
||||
if (gopeed.storage.get(key) !== value1) {
|
||||
throw new Error("storage put1 error")
|
||||
}
|
||||
|
||||
ctx.storage.set(key, value2)
|
||||
if (ctx.storage.get(key) !== value2) {
|
||||
gopeed.storage.set(key, value2)
|
||||
if (gopeed.storage.get(key) !== value2) {
|
||||
throw new Error("storage put2 error")
|
||||
}
|
||||
|
||||
if(ctx.storage.keys().length !== 1) {
|
||||
if(gopeed.storage.keys().length !== 1) {
|
||||
throw new Error("storage keys error")
|
||||
}
|
||||
|
||||
ctx.storage.remove(key)
|
||||
if (ctx.storage.get(key) !== null) {
|
||||
gopeed.storage.remove(key)
|
||||
if (gopeed.storage.get(key) !== null) {
|
||||
throw new Error("storage delete error")
|
||||
}
|
||||
|
||||
ctx.storage.set(key, value1)
|
||||
ctx.storage.clear()
|
||||
if (ctx.storage.get(key) !== null) {
|
||||
gopeed.storage.set(key, value1)
|
||||
gopeed.storage.clear()
|
||||
if (gopeed.storage.get(key) !== null) {
|
||||
throw new Error("storage clear error")
|
||||
}
|
||||
|
||||
|
||||
+5
-3
@@ -5,9 +5,11 @@
|
||||
"scripts": [
|
||||
{
|
||||
"event": "onResolve",
|
||||
"matches": [
|
||||
"*://github.com/*"
|
||||
],
|
||||
"match": {
|
||||
"urls": [
|
||||
"*://*/*"
|
||||
]
|
||||
},
|
||||
"entry": "index.js"
|
||||
}
|
||||
],
|
||||
|
||||
+5
-3
@@ -12,9 +12,11 @@
|
||||
"scripts": [
|
||||
{
|
||||
"event": "onResolve",
|
||||
"matches": [
|
||||
"*://github.com/*/releases/*"
|
||||
],
|
||||
"match": {
|
||||
"urls": [
|
||||
"*://*/*"
|
||||
]
|
||||
},
|
||||
"entry": "index.js"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -16,6 +16,7 @@ class Extension {
|
||||
List<Setting>? settings;
|
||||
bool disabled;
|
||||
bool devMode;
|
||||
String devPath;
|
||||
|
||||
Extension({
|
||||
required this.identity,
|
||||
@@ -29,6 +30,7 @@ class Extension {
|
||||
required this.repository,
|
||||
required this.disabled,
|
||||
required this.devMode,
|
||||
required this.devPath,
|
||||
});
|
||||
|
||||
factory Extension.fromJson(Map<String, dynamic> json) =>
|
||||
|
||||
@@ -20,6 +20,7 @@ Extension _$ExtensionFromJson(Map<String, dynamic> json) => Extension(
|
||||
: Repository.fromJson(json['repository'] as Map<String, dynamic>),
|
||||
disabled: json['disabled'] as bool,
|
||||
devMode: json['devMode'] as bool,
|
||||
devPath: json['devPath'] as String,
|
||||
)..settings = (json['settings'] as List<dynamic>?)
|
||||
?.map((e) => Setting.fromJson(e as Map<String, dynamic>))
|
||||
.toList();
|
||||
@@ -46,6 +47,7 @@ Map<String, dynamic> _$ExtensionToJson(Extension instance) {
|
||||
writeNotNull('settings', instance.settings?.map((e) => e.toJson()).toList());
|
||||
val['disabled'] = instance.disabled;
|
||||
val['devMode'] = instance.devMode;
|
||||
val['devPath'] = instance.devPath;
|
||||
return val;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,10 +6,12 @@ part 'request.g.dart';
|
||||
class Request {
|
||||
String url;
|
||||
Object? extra;
|
||||
Map<String, String>? labels = {};
|
||||
|
||||
Request({
|
||||
required this.url,
|
||||
this.extra,
|
||||
this.labels,
|
||||
});
|
||||
|
||||
factory Request.fromJson(Map<String, dynamic> json) =>
|
||||
|
||||
@@ -9,6 +9,9 @@ part of 'request.dart';
|
||||
Request _$RequestFromJson(Map<String, dynamic> json) => Request(
|
||||
url: json['url'] as String,
|
||||
extra: json['extra'],
|
||||
labels: (json['labels'] as Map<String, dynamic>?)?.map(
|
||||
(k, e) => MapEntry(k, e as String),
|
||||
),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$RequestToJson(Request instance) {
|
||||
@@ -23,6 +26,7 @@ Map<String, dynamic> _$RequestToJson(Request instance) {
|
||||
}
|
||||
|
||||
writeNotNull('extra', instance.extra);
|
||||
writeNotNull('labels', instance.labels);
|
||||
return val;
|
||||
}
|
||||
|
||||
|
||||
@@ -110,14 +110,22 @@ class ExtensionView extends GetView<ExtensionController> {
|
||||
height: 48,
|
||||
)
|
||||
: Util.isWeb()
|
||||
? Image.network(join(
|
||||
'/fs/extensions/${extension.identity}/${extension.icon}'))
|
||||
? Image.network(
|
||||
join(
|
||||
'/fs/extensions/${extension.identity}/${extension.icon}'),
|
||||
width: 48,
|
||||
height: 48,
|
||||
)
|
||||
: Image.file(
|
||||
File(path.join(
|
||||
Util.getStorageDir(),
|
||||
"extensions",
|
||||
extension.identity,
|
||||
extension.icon)),
|
||||
extension.devMode
|
||||
? File(path.join(
|
||||
extension.devPath,
|
||||
extension.icon))
|
||||
: File(path.join(
|
||||
Util.getStorageDir(),
|
||||
"extensions",
|
||||
extension.identity,
|
||||
extension.icon)),
|
||||
width: 48,
|
||||
height: 48,
|
||||
),
|
||||
|
||||
Reference in new issue
Block a user