diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 80b2ed48..8051f60f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -441,6 +441,7 @@ jobs: env: VERSION: ${{ needs.get-release.outputs.version }} run: | + VERSION=${VERSION:-$(date +%y%m%d%H%M)} # Use current time as version if not specified cd ui/flutter flutter build web --web-renderer html cd ../../ @@ -468,6 +469,39 @@ jobs: || true done done + sudo apt update -y + sudo apt install -y pv python2 bsdmainutils + wget -O qdk2_0.32.bionic_amd64.deb "https://github.com/qnap-dev/qdk2/releases/download/v0.32/qdk2_0.32.bionic_amd64.deb" + dpkg -X qdk2_0.32.bionic_amd64.deb qdk2 # Direct installs will fail due to missing dependencies! + [[ -d qdk2 ]] || exit 1 + export PATH=$(pwd)/qdk2/usr/bin:$(pwd)/qdk2/usr/share/qdk2/QDK/bin:${PATH} + wget -O Gopeed.template.tar.gz "https://github.com/GopeedLab/QpkgBuild/raw/refs/heads/master/template/Gopeed.template.tar.gz" + tar -zxf Gopeed.template.tar.gz + [[ -d Gopeed ]] || exit 1 + goos=linux + goarch_arr=(amd64 arm64) + for goarch in "${goarch_arr[@]}"; do + qarch=x86_64 + [[ "${goarch}" == "arm64" ]] && qarch=arm_64 + name=gopeed-web-v${VERSION}-${goos}-${goarch} + cp dist/${name}/${name}/* Gopeed/${qarch}/ + done + cd Gopeed + sed -i -e 's/__QPKG_VER__/${VERSION}/g' qpkg.cfg + qbuild || exit 1 + for f in build/*; do echo ">>> build=[${f}]"; done + goos=qnap + goarch_arr=(amd64 arm64) + for goarch in "${goarch_arr[@]}"; do + qarch=x86_64 + [[ "${goarch}" == "arm64" ]] && qarch=arm_64 + sname=Gopeed_${VERSION}_${qarch}.qpkg + dname=gopeed-${goos}-v${VERSION}-${goarch}.qpkg + echo "sname=${sname}, dname=${dname}" + [[ -f build/${sname} ]] && cp -ra build/${sname} ../dist/zip/${dname} + done + for f in ../dist/zip/*; do echo ">>> dist=[${f}]"; done + cd .. - name: Upload uses: shogo82148/actions-upload-release-asset@v1 with: diff --git a/Dockerfile b/Dockerfile index 1f83147c..2f699210 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,14 +8,13 @@ RUN CGO_ENABLED=0 go build -tags nosqlite,web \ -ldflags="-s -w -X github.com/GopeedLab/gopeed/pkg/base.Version=$VERSION -X github.com/GopeedLab/gopeed/pkg/base.InDocker=true" \ -o dist/gopeed github.com/GopeedLab/gopeed/cmd/web -FROM alpine:3.14.2 +FROM alpine:3.18 LABEL maintainer="monkeyWie" WORKDIR /app COPY --from=go /app/dist/gopeed ./ COPY entrypoint.sh ./entrypoint.sh RUN apk update && \ - apk upgrade --no-cache && \ - apk add --no-cache bash su-exec; \ + apk add --no-cache su-exec ; \ chmod +x ./entrypoint.sh && \ rm -rf /var/cache/apk/* VOLUME ["/app/storage"] diff --git a/docker-compose.yml b/docker-compose.yml index 1f816d92..58f0d078 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,12 +1,15 @@ -version: '3' - services: - gopeed: - container_name: gopeed - ports: - - "9999:9999" # HTTP port (host:container) - image: liwei2633/gopeed - volumes: - - ~/gopeed/Downloads:/app/Downloads # mount download path - #- ~/gopeed/storage:/app/storage # if you need to mount storage path, uncomment this line - restart: unless-stopped + gopeed: + container_name: gopeed + ports: + - 9999:9999 # HTTP port (host:container) + environment: + - PUID=0 + - PGID=0 + - UMASK=022 + volumes: + - ~/gopeed/Downloads:/app/Downloads # mount download path + #- ~/gopeed/storage:/app/storage # if you need to mount storage path, uncomment this line + restart: unless-stopped + image: liwei2633/gopeed + # command: -u Username -p Password # optional authentication \ No newline at end of file diff --git a/internal/protocol/http/fetcher.go b/internal/protocol/http/fetcher.go index 769a6041..33c5f521 100644 --- a/internal/protocol/http/fetcher.go +++ b/internal/protocol/http/fetcher.go @@ -162,9 +162,6 @@ func (f *Fetcher) Resolve(req *base.Request) error { func (f *Fetcher) Create(opts *base.Options) error { f.meta.Opts = opts - if err := base.ParseReqExtra[fhttp.ReqExtra](f.meta.Req); err != nil { - return err - } if err := base.ParseOptsExtra[fhttp.OptsExtra](f.meta.Opts); err != nil { return err } @@ -198,6 +195,12 @@ func (f *Fetcher) Start() (err error) { if err != nil { return err } + + // Avoid request extra modified by extension + if err = base.ParseReqExtra[fhttp.ReqExtra](f.meta.Req); err != nil { + return err + } + if f.chunks == nil { f.chunks = f.splitChunk() } diff --git a/pkg/download/downloader.go b/pkg/download/downloader.go index ff7b003b..e4c3c45f 100644 --- a/pkg/download/downloader.go +++ b/pkg/download/downloader.go @@ -204,33 +204,29 @@ func (d *Downloader) Setup() error { current := task.fetcher.Progress().TotalDownloaded() tick := float64(d.cfg.RefreshInterval) / 1000 + downloadDataChanged := false if task.Status == base.DownloadStatusRunning { + downloadDataChanged = current != task.Progress.Downloaded task.Progress.Used = task.timer.Used() task.Progress.Speed = task.calcSpeed(task.speedArr, current-task.Progress.Downloaded, tick) task.Progress.Downloaded = current } + + uploadDataChanged := false if task.Uploading { uploader := task.fetcher.(fetcher.Uploader) currentUploaded := uploader.UploadedBytes() + uploadDataChanged = currentUploaded != task.Progress.Uploaded task.Progress.UploadSpeed = task.calcSpeed(task.uploadSpeedArr, currentUploaded-task.Progress.Uploaded, tick) task.Progress.Uploaded = currentUploaded } d.emit(EventKeyProgress, task) - // store fetcher progress - data, err := task.fetcherManager.Store(task.fetcher) - if err != nil { - d.Logger.Error().Stack().Err(err).Msgf("serialize fetcher failed: %s", task.ID) - return - } - if err := d.storage.Put(bucketSave, task.ID, data); err != nil { - d.Logger.Error().Stack().Err(err).Msgf("persist fetcher failed: %s", task.ID) - return - } - if err := d.storage.Put(bucketTask, task.ID, task); err != nil { - d.Logger.Error().Stack().Err(err).Msgf("persist task failed: %s", task.ID) + // store fetcher progress when download/upload data changed + if !downloadDataChanged && !uploadDataChanged { return } + d.saveTask(task) }() } } @@ -273,6 +269,23 @@ func (d *Downloader) setupFetcher(fm fetcher.FetcherManager, fetcher fetcher.Fet fetcher.Setup(ctl) } +func (d *Downloader) saveTask(task *Task) error { + data, err := task.fetcherManager.Store(task.fetcher) + if err != nil { + d.Logger.Error().Stack().Err(err).Msgf("serialize fetcher failed: %s", task.ID) + return err + } + if err := d.storage.Put(bucketSave, task.ID, data); err != nil { + d.Logger.Error().Stack().Err(err).Msgf("persist fetcher failed: %s", task.ID) + return err + } + if err := d.storage.Put(bucketTask, task.ID, task); err != nil { + d.Logger.Error().Stack().Err(err).Msgf("persist task failed: %s", task.ID) + return err + } + return nil +} + func (d *Downloader) Resolve(req *base.Request) (rr *ResolveResult, err error) { rrId, err := gonanoid.New() if err != nil { @@ -1008,8 +1021,6 @@ func (d *Downloader) doStart(task *Task) (err error) { return } isCreate = task.Status == base.DownloadStatusReady - - d.triggerOnStart(task) task.updateStatus(base.DownloadStatusRunning) return @@ -1026,6 +1037,7 @@ func (d *Downloader) doStart(task *Task) (err error) { task.lock.Lock() defer task.lock.Unlock() + d.triggerOnStart(task) if task.Meta.Res == nil { err := task.fetcher.Resolve(task.Meta.Req) if err != nil { @@ -1067,7 +1079,7 @@ func (d *Downloader) doStart(task *Task) (err error) { if err := task.fetcher.Start(); err != nil { return err } - if err := d.storage.Put(bucketTask, task.ID, task.clone()); err != nil { + if err := d.saveTask(task); err != nil { return err } d.emit(EventKeyStart, task) diff --git a/pkg/download/extension.go b/pkg/download/extension.go index 67362f65..fc3ad215 100644 --- a/pkg/download/extension.go +++ b/pkg/download/extension.go @@ -703,31 +703,37 @@ type OnDoneContext struct { } type ExtensionTask struct { - *Task - download *Downloader + task *Task + + Meta *ExtensionTaskMeta `json:"meta"` +} + +// ExtensionTaskMeta restricts extension scripts to only modify request info +type ExtensionTaskMeta struct { + Req *base.Request `json:"req"` } func NewExtensionTask(download *Downloader, task *Task) *ExtensionTask { newTask := task.clone() - // Assign the pointer of the properties that the extension supports modification - newTask.Meta = task.Meta - newTask.Status = task.Status return &ExtensionTask{ - Task: newTask, + task: newTask, download: download, + Meta: &ExtensionTaskMeta{ + Req: task.Meta.Req, + }, } } func (t *ExtensionTask) Continue() error { return t.download.Continue(&TaskFilter{ - ID: []string{t.ID}, + ID: []string{t.task.ID}, }) } func (t *ExtensionTask) Pause() error { return t.download.Pause(&TaskFilter{ - ID: []string{t.ID}, + ID: []string{t.task.ID}, }) } diff --git a/ui/flutter/lib/util/message.dart b/ui/flutter/lib/util/message.dart index 713ef62c..313caaf5 100644 --- a/ui/flutter/lib/util/message.dart +++ b/ui/flutter/lib/util/message.dart @@ -8,9 +8,16 @@ void showErrorMessage(msg) { Get.snackbar(title, msg.msg!); return; } - if (msg is Exception && (msg as dynamic).message is Result) { - Get.snackbar(title, ((msg as dynamic).message as Result).msg!); - return; + if (msg is Exception) { + final message = (msg as dynamic).message; + if (message is Result) { + Get.snackbar(title, ((msg as dynamic).message as Result).msg!); + return; + } + if (message is String) { + Get.snackbar(title, message); + return; + } } Get.snackbar(title, msg.toString()); } diff --git a/ui/flutter/pubspec.yaml b/ui/flutter/pubspec.yaml index acc3391d..136348e9 100644 --- a/ui/flutter/pubspec.yaml +++ b/ui/flutter/pubspec.yaml @@ -15,7 +15,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 1.6.3+1 +version: 1.6.4+1 environment: sdk: ">=3.0.0 <4.0.0"