mirror of
https://github.com/GopeedLab/gopeed.git
synced 2026-11-04 23:57:54 +00:00
feat: support start and pause all tasks (#121)
This commit is contained in:
1 parent
0e3ca912a7
commit
eb9d966855
13 files changed
+141
-1
No files matched your search
@@ -7,6 +7,7 @@ import (
|
||||
"github.com/GopeedLab/gopeed/pkg/base"
|
||||
"github.com/GopeedLab/gopeed/pkg/util"
|
||||
gonanoid "github.com/matoous/go-nanoid/v2"
|
||||
"math"
|
||||
"os"
|
||||
"path"
|
||||
"sort"
|
||||
@@ -332,6 +333,41 @@ func (d *Downloader) Continue(id string) (err error) {
|
||||
return d.doContinue(task)
|
||||
}
|
||||
|
||||
func (d *Downloader) PauseAll() (err error) {
|
||||
d.lock.Lock()
|
||||
defer d.lock.Unlock()
|
||||
for _, task := range d.tasks {
|
||||
if task.Status == base.DownloadStatusRunning {
|
||||
if err = d.doPause(task, base.DownloadStatusPause); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (d *Downloader) ContinueAll() (err error) {
|
||||
d.lock.Lock()
|
||||
defer d.lock.Unlock()
|
||||
runningCount := 0
|
||||
continueTasks := make([]*Task, 0)
|
||||
for _, task := range d.tasks {
|
||||
if task.Status == base.DownloadStatusRunning {
|
||||
runningCount++
|
||||
} else if task.Status != base.DownloadStatusDone {
|
||||
continueTasks = append(continueTasks, task)
|
||||
}
|
||||
}
|
||||
// calculate how many tasks can be continued, can't exceed maxRunning
|
||||
continueCount := int(math.Min(float64(d.cfg.MaxRunning-runningCount), float64(len(continueTasks))))
|
||||
for i := 0; i < continueCount; i++ {
|
||||
if err = d.doContinue(continueTasks[i]); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (d *Downloader) Delete(id string, force bool) (err error) {
|
||||
d.lock.Lock()
|
||||
defer d.lock.Unlock()
|
||||
|
||||
@@ -74,6 +74,22 @@ func ContinueTask(w http.ResponseWriter, r *http.Request) {
|
||||
WriteJson(w, model.NewNilResult())
|
||||
}
|
||||
|
||||
func PauseAllTask(w http.ResponseWriter, r *http.Request) {
|
||||
if err := Downloader.PauseAll(); err != nil {
|
||||
WriteJson(w, model.NewErrorResult(err.Error()))
|
||||
return
|
||||
}
|
||||
WriteJson(w, model.NewNilResult())
|
||||
}
|
||||
|
||||
func ContinueAllTask(w http.ResponseWriter, r *http.Request) {
|
||||
if err := Downloader.ContinueAll(); err != nil {
|
||||
WriteJson(w, model.NewErrorResult(err.Error()))
|
||||
return
|
||||
}
|
||||
WriteJson(w, model.NewNilResult())
|
||||
}
|
||||
|
||||
func DeleteTask(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
taskId := vars["id"]
|
||||
|
||||
@@ -85,6 +85,8 @@ func BuildServer(startCfg *model.StartConfig) (*http.Server, net.Listener, error
|
||||
r.Methods(http.MethodPost).Path("/api/v1/tasks").HandlerFunc(CreateTask)
|
||||
r.Methods(http.MethodPut).Path("/api/v1/tasks/{id}/pause").HandlerFunc(PauseTask)
|
||||
r.Methods(http.MethodPut).Path("/api/v1/tasks/{id}/continue").HandlerFunc(ContinueTask)
|
||||
r.Methods(http.MethodPut).Path("/api/v1/tasks/pause").HandlerFunc(PauseAllTask)
|
||||
r.Methods(http.MethodPut).Path("/api/v1/tasks/continue").HandlerFunc(ContinueAllTask)
|
||||
r.Methods(http.MethodDelete).Path("/api/v1/tasks/{id}").HandlerFunc(DeleteTask)
|
||||
r.Methods(http.MethodGet).Path("/api/v1/tasks/{id}").HandlerFunc(GetTask)
|
||||
r.Methods(http.MethodGet).Path("/api/v1/tasks").HandlerFunc(GetTasks)
|
||||
|
||||
@@ -144,6 +144,39 @@ func TestPauseAndContinueTask(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestPauseAllAndContinueALLTasks(t *testing.T) {
|
||||
doTest(func() {
|
||||
cfg, err := Downloader.GetConfig()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
createAndPause := func() {
|
||||
taskId := httpRequestCheckOk[string](http.MethodPost, "/api/v1/tasks", createReq)
|
||||
httpRequestCheckOk[*download.Task](http.MethodGet, "/api/v1/tasks/"+taskId, nil)
|
||||
}
|
||||
|
||||
total := cfg.MaxRunning + 2
|
||||
for i := 0; i < total; i++ {
|
||||
createAndPause()
|
||||
}
|
||||
|
||||
// continue all
|
||||
httpRequestCheckOk[any](http.MethodPut, "/api/v1/tasks/continue", nil)
|
||||
tasks := httpRequestCheckOk[[]*download.Task](http.MethodGet, fmt.Sprintf("/api/v1/tasks?status=%s", base.DownloadStatusRunning), nil)
|
||||
if len(tasks) != cfg.MaxRunning {
|
||||
t.Errorf("ContinueAllTasks() got = %v, want %v", len(tasks), cfg.MaxRunning)
|
||||
}
|
||||
// pause all
|
||||
httpRequestCheckOk[any](http.MethodPut, "/api/v1/tasks/pause", nil)
|
||||
tasks = httpRequestCheckOk[[]*download.Task](http.MethodGet, fmt.Sprintf("/api/v1/tasks?status=%s,%s", base.DownloadStatusReady, base.DownloadStatusPause), nil)
|
||||
if len(tasks) != total {
|
||||
t.Errorf("PauseAllTasks() got = %v, want %v", len(tasks), total)
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
func TestDeleteTask(t *testing.T) {
|
||||
doTest(func() {
|
||||
taskId := httpRequestCheckOk[string](http.MethodPost, "/api/v1/tasks", createReq)
|
||||
@@ -275,6 +308,16 @@ func doTest(handler func()) {
|
||||
Stop()
|
||||
}()
|
||||
defer Downloader.Clear()
|
||||
defer func() {
|
||||
tasks := Downloader.GetTasks()
|
||||
taskIds := make([]string, len(tasks))
|
||||
for i, task := range tasks {
|
||||
taskIds[i] = task.ID
|
||||
}
|
||||
for _, id := range taskIds {
|
||||
Downloader.Delete(id, true)
|
||||
}
|
||||
}()
|
||||
taskReq.URL = "http://" + fileListener.Addr().String() + "/" + test.BuildName
|
||||
handler()
|
||||
}
|
||||
|
||||
@@ -50,6 +50,8 @@
|
||||
"notSet": "NS",
|
||||
"set": "SET",
|
||||
"effectAfterRestart": "Effect after restart",
|
||||
"startAll": "Start All",
|
||||
"pauseAll": "Pause All",
|
||||
"deleteTask": "Delete Task",
|
||||
"deleteTaskTip": "Keep downloaded files",
|
||||
"delete": "Delete",
|
||||
|
||||
@@ -47,6 +47,8 @@
|
||||
"notSet": "NS",
|
||||
"set": "SET",
|
||||
"effectAfterRestart": "Effect after restart",
|
||||
"startAll": "شروع همه",
|
||||
"pauseAll": "توقف همه",
|
||||
"deleteTask": "پاک کردن کار",
|
||||
"deleteTaskTip": "فایل های دانلود شده را نگه دارد",
|
||||
"delete": "پاک کردن",
|
||||
|
||||
@@ -50,6 +50,8 @@
|
||||
"notSet": "Не установлено",
|
||||
"set": "Установлено",
|
||||
"effectAfterRestart": "Эффект после перезагрузки",
|
||||
"startAll": "Запустить все",
|
||||
"pauseAll": "Приостановить все",
|
||||
"deleteTask": "Удалить задачу",
|
||||
"deleteTaskTip": "Сохранить загруженные файлы",
|
||||
"delete": "Удалить",
|
||||
|
||||
@@ -50,6 +50,8 @@
|
||||
"notSet": "未设置",
|
||||
"set": "已设置",
|
||||
"effectAfterRestart": "此配置项将在重启应用后生效",
|
||||
"startAll": "全部开始",
|
||||
"pauseAll": "全部暂停",
|
||||
"deleteTask": "删除任务",
|
||||
"deleteTaskTip": "保留已下载的文件",
|
||||
"delete": "删除",
|
||||
|
||||
@@ -111,6 +111,14 @@ Future<void> continueTask(String id) async {
|
||||
return _parse(() => _client.dio.put("/api/v1/tasks/$id/continue"), null);
|
||||
}
|
||||
|
||||
Future<void> pauseAllTasks() async {
|
||||
return _parse(() => _client.dio.put("/api/v1/tasks/pause"), null);
|
||||
}
|
||||
|
||||
Future<void> continueAllTasks() async {
|
||||
return _parse(() => _client.dio.put("/api/v1/tasks/continue"), null);
|
||||
}
|
||||
|
||||
Future<void> deleteTask(String id, bool force) async {
|
||||
return _parse(
|
||||
() => _client.dio.delete("/api/v1/tasks/$id?force=$force"), null);
|
||||
|
||||
@@ -118,6 +118,11 @@ class AppController extends GetxController with WindowListener, TrayListener {
|
||||
}
|
||||
|
||||
Future<void> _initDeepLinks() async {
|
||||
// currently only support android
|
||||
if (!Util.isAndroid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
_appLinks = AppLinks();
|
||||
|
||||
// Handle link when app is in warm state (front or background)
|
||||
@@ -159,6 +164,15 @@ class AppController extends GetxController with WindowListener, TrayListener {
|
||||
await Get.rootDelegate.offAndToNamed(Routes.CREATE),
|
||||
},
|
||||
),
|
||||
MenuItem.separator(),
|
||||
MenuItem(
|
||||
label: "startAll".tr,
|
||||
onClick: (menuItem) async => {continueAllTasks()},
|
||||
),
|
||||
MenuItem(
|
||||
label: "pauseAll".tr,
|
||||
onClick: (menuItem) async => {pauseAllTasks()},
|
||||
),
|
||||
MenuItem(
|
||||
label: 'setting'.tr,
|
||||
onClick: (menuItem) async => {
|
||||
|
||||
@@ -5,4 +5,5 @@ class CreateController extends GetxController {
|
||||
final RxList fileInfos = [].obs;
|
||||
final RxList selectedIndexes = [].obs;
|
||||
final RxList openedFolders = [].obs;
|
||||
final isResolving = false.obs;
|
||||
}
|
||||
@@ -115,9 +115,10 @@ class CreateView extends GetView<CreateController> {
|
||||
}
|
||||
|
||||
Future<void> _doResolve() async {
|
||||
if (_confirmController.currentState == ButtonState.loading) {
|
||||
if (controller.isResolving.value) {
|
||||
return;
|
||||
}
|
||||
controller.isResolving.value = true;
|
||||
try {
|
||||
_confirmController.start();
|
||||
if (_resolveFormKey.currentState!.validate()) {
|
||||
@@ -130,6 +131,7 @@ class CreateView extends GetView<CreateController> {
|
||||
showErrorMessage(e);
|
||||
} finally {
|
||||
_confirmController.reset();
|
||||
controller.isResolving.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -64,6 +64,8 @@ class LocaleKeys {
|
||||
static const notSet = 'notSet';
|
||||
static const set = 'set';
|
||||
static const effectAfterRestart = 'effectAfterRestart';
|
||||
static const startAll = 'startAll';
|
||||
static const pauseAll = 'pauseAll';
|
||||
static const deleteTask = 'deleteTask';
|
||||
static const deleteTaskTip = 'deleteTaskTip';
|
||||
static const delete = 'delete';
|
||||
@@ -127,6 +129,8 @@ class Locales {
|
||||
'notSet': 'NS',
|
||||
'set': 'SET',
|
||||
'effectAfterRestart': 'Effect after restart',
|
||||
'startAll': 'Start All',
|
||||
'pauseAll': 'Pause All',
|
||||
'deleteTask': 'Delete Task',
|
||||
'deleteTaskTip': 'Keep downloaded files',
|
||||
'delete': 'Delete',
|
||||
@@ -184,6 +188,8 @@ class Locales {
|
||||
'notSet': 'NS',
|
||||
'set': 'SET',
|
||||
'effectAfterRestart': 'Effect after restart',
|
||||
'startAll': 'شروع همه',
|
||||
'pauseAll': 'توقف همه',
|
||||
'deleteTask': 'پاک کردن کار',
|
||||
'deleteTaskTip': 'فایل های دانلود شده را نگه دارد',
|
||||
'delete': 'پاک کردن',
|
||||
@@ -245,6 +251,8 @@ class Locales {
|
||||
'notSet': 'Не установлено',
|
||||
'set': 'Установлено',
|
||||
'effectAfterRestart': 'Эффект после перезагрузки',
|
||||
'startAll': 'Запустить все',
|
||||
'pauseAll': 'Приостановить все',
|
||||
'deleteTask': 'Удалить задачу',
|
||||
'deleteTaskTip': 'Сохранить загруженные файлы',
|
||||
'delete': 'Удалить',
|
||||
@@ -304,6 +312,8 @@ class Locales {
|
||||
'notSet': '未设置',
|
||||
'set': '已设置',
|
||||
'effectAfterRestart': '此配置项将在重启应用后生效',
|
||||
'startAll': '全部开始',
|
||||
'pauseAll': '全部暂停',
|
||||
'deleteTask': '删除任务',
|
||||
'deleteTaskTip': '保留已下载的文件',
|
||||
'delete': '删除',
|
||||
|
||||
Reference in new issue
Block a user