diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml index 79522326..fa23f61c 100644 --- a/.github/release-drafter.yml +++ b/.github/release-drafter.yml @@ -1,5 +1,5 @@ -name-template: 'v$NEXT_PATCH_VERSION 🌈' -tag-template: 'v$NEXT_PATCH_VERSION' +name-template: 'v1.3.0 🌈' +tag-template: 'v1.3.0' categories: - title: '🆕 Features' labels: diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 538fc321..7bb878c0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -2,6 +2,10 @@ name: build on: workflow_dispatch: + +permissions: + contents: write + jobs: get-release: runs-on: ubuntu-latest diff --git a/cmd/api/main.go b/cmd/api/main.go index fa44c6eb..4c87cb9e 100644 --- a/cmd/api/main.go +++ b/cmd/api/main.go @@ -2,28 +2,15 @@ package main import ( _ "embed" - "fmt" - "github.com/GopeedLab/gopeed/pkg/rest" + "github.com/GopeedLab/gopeed/cmd" "github.com/GopeedLab/gopeed/pkg/rest/model" - "net/http" ) -//go:embed banner.txt -var banner string - func main() { cfg := &model.StartConfig{ Network: "tcp", Address: "127.0.0.1:9999", Storage: model.StorageBolt, } - fmt.Println(banner) - srv, listener, err := rest.BuildServer(cfg) - if err != nil { - panic(err) - } - fmt.Printf("Server start success on http://%s\n", listener.Addr().String()) - if err := srv.Serve(listener); err != nil && err != http.ErrServerClosed { - panic(err) - } + cmd.Start(cfg) } diff --git a/cmd/api/banner.txt b/cmd/banner.txt similarity index 100% rename from cmd/api/banner.txt rename to cmd/banner.txt diff --git a/cmd/server.go b/cmd/server.go new file mode 100644 index 00000000..a4cb70f8 --- /dev/null +++ b/cmd/server.go @@ -0,0 +1,42 @@ +package cmd + +import ( + _ "embed" + "fmt" + "github.com/GopeedLab/gopeed/pkg/download" + "github.com/GopeedLab/gopeed/pkg/rest" + "github.com/GopeedLab/gopeed/pkg/rest/model" + "net/http" + "os" + "path/filepath" +) + +//go:embed banner.txt +var banner string + +func Start(cfg *model.StartConfig) { + fmt.Println(banner) + srv, listener, err := rest.BuildServer(cfg) + if err != nil { + panic(err) + } + exist, _, err := rest.Downloader.GetConfig() + if err != nil { + panic(err) + } + if !exist { + // Set default download dir to user download dir + userDir, err := os.UserHomeDir() + if err == nil { + downloadDir := filepath.Join(userDir, "Downloads") + rest.Downloader.PutConfig(&download.DownloaderStoreConfig{ + DownloadDir: downloadDir, + }) + } + + } + fmt.Printf("Server start success on http://%s\n", listener.Addr().String()) + if err := srv.Serve(listener); err != nil && err != http.ErrServerClosed { + panic(err) + } +} diff --git a/cmd/web/banner.txt b/cmd/web/banner.txt deleted file mode 100644 index d667ca0f..00000000 --- a/cmd/web/banner.txt +++ /dev/null @@ -1,7 +0,0 @@ - - _______ ______ .______ _______ _______ _______ - / _____| / __ \ | _ \ | ____|| ____|| \ -| | __ | | | | | |_) | | |__ | |__ | .--. | -| | |_ | | | | | | ___/ | __| | __| | | | | -| |__| | | `--' | | | | |____ | |____ | '--' | - \______| \______/ | _| |_______||_______||_______/ diff --git a/cmd/web/main.go b/cmd/web/main.go index 131b311f..aa3b62c3 100644 --- a/cmd/web/main.go +++ b/cmd/web/main.go @@ -5,11 +5,8 @@ package main import ( "embed" - "fmt" - "github.com/GopeedLab/gopeed/pkg/rest" "github.com/GopeedLab/gopeed/pkg/rest/model" "io/fs" - "net/http" ) //go:embed banner.txt @@ -30,13 +27,5 @@ func main() { WebEnable: true, WebFS: sub, } - fmt.Println(banner) - srv, listener, err := rest.BuildServer(cfg) - if err != nil { - panic(err) - } - fmt.Printf("Server start success on http://%s\n", listener.Addr().String()) - if err := srv.Serve(listener); err != nil && err != http.ErrServerClosed { - panic(err) - } + cmd.Start(cfg) } diff --git a/pkg/rest/api.go b/pkg/rest/api.go index 36548b3c..059aba35 100644 --- a/pkg/rest/api.go +++ b/pkg/rest/api.go @@ -1,7 +1,6 @@ package rest import ( - "compress/gzip" "github.com/GopeedLab/gopeed/pkg/base" "github.com/GopeedLab/gopeed/pkg/download" "github.com/GopeedLab/gopeed/pkg/rest/model" @@ -164,24 +163,30 @@ func DoProxy(w http.ResponseWriter, r *http.Request) { return } defer resp.Body.Close() - w.WriteHeader(resp.StatusCode) for k, vv := range resp.Header { for _, v := range vv { - w.Header().Add(k, v) + w.Header().Set(k, v) } } - var reader io.ReadCloser - switch resp.Header.Get("Content-Encoding") { - case "gzip": - reader, err = gzip.NewReader(resp.Body) - defer reader.Close() - default: - reader = resp.Body - } - if _, err := io.Copy(w, reader); err != nil { + w.WriteHeader(resp.StatusCode) + buf, err := io.ReadAll(resp.Body) + if err != nil { writeError(w, err.Error()) return } + w.Write(buf) + //var reader io.ReadCloser + //switch resp.Header.Get("Content-Encoding") { + //case "gzip": + // reader, err = gzip.NewReader(resp.Body) + // defer reader.Close() + //default: + // reader = resp.Body + //} + //if _, err := io.Copy(w, resp.Body); err != nil { + // writeError(w, err.Error()) + // return + //} } func writeError(w http.ResponseWriter, msg string) { diff --git a/ui/flutter/assets/locales/en_US.json b/ui/flutter/assets/locales/en_US.json index c8b51b7d..7f50a151 100644 --- a/ui/flutter/assets/locales/en_US.json +++ b/ui/flutter/assets/locales/en_US.json @@ -49,6 +49,9 @@ "effectAfterRestart": "Effect after restart", "deleteTask": "Delete Task", "deleteTaskTip": "Keep downloaded files", - "delete": "Delete" + "delete": "Delete", + "newVersionTitle": "Discover new version @version", + "newVersionUpdate": "Download Now", + "newVersionLater": "Later" } diff --git a/ui/flutter/assets/locales/fa_IR.json b/ui/flutter/assets/locales/fa_IR.json index 78df871d..fec3764e 100644 --- a/ui/flutter/assets/locales/fa_IR.json +++ b/ui/flutter/assets/locales/fa_IR.json @@ -8,9 +8,12 @@ "task": "کار", "setting": "تنظیمات", "create": "ایجاد کار", + "downloading": "دانلود کردن", + "downloaded": "دانلود شدن", "downloadLink": "لینک دانلود", "downloadLinkValid": "لطفا لینک دانلود را وارد کنید", "download": "دانلود", + "noFileSelected": "لطفا حداقل یک فایل را جهت دانلود انتخاب کنید.", "noStoragePermission": "مجوز ذخیره سازی مورد نیاز است", "selectFile": "انتخاب فایل", "basic": "پایه", @@ -44,6 +47,8 @@ "effectAfterRestart": "Effect after restart", "deleteTask": "پاک کردن کار", "deleteTaskTip": "فایل های دانلود شده را نگه دارد", - "delete": "پاک کردن" -} - + "delete": "پاک کردن", + "newVersionTitle": "@version عنوان: کشف نسخه جدید", + "newVersionUpdate": "دریافت اکنون", + "newVersionLater": "بعداً" +} \ No newline at end of file diff --git a/ui/flutter/assets/locales/ru_RU.json b/ui/flutter/assets/locales/ru_RU.json index 4b550cbc..37a69e5f 100644 --- a/ui/flutter/assets/locales/ru_RU.json +++ b/ui/flutter/assets/locales/ru_RU.json @@ -6,6 +6,8 @@ "cancel": "Отмена", "selectAll": "Выбрать все", "task": "Задачи", + "downloading": "Загрузка", + "downloaded": "Загружено", "setting": "Настройки", "create": "Создать задачу", "downloadLink": "Ссылка для скачивание", @@ -13,6 +15,7 @@ "downloadLinkHit": "Пожалуйста, введите ссылку для скачивания, HTTP/HTTPS/MAGNET supported@append", "downloadLinkHitDesktop": ", или перетащите сюда торрент-файл", "download": "Скачать", + "noFileSelected": "Файл не выбран", "noStoragePermission": "Требуется доступ к хранилищу", "selectFile": "Выберать файл", "basic": "Основные параметры", @@ -46,5 +49,8 @@ "effectAfterRestart": "Эффект после перезагрузки", "deleteTask": "Удалить задачу", "deleteTaskTip": "Сохранить загруженные файлы", - "delete": "Удалить" -} + "delete": "Удалить", + "newVersionTitle": "Обнаружена новая версия @version", + "newVersionUpdate": "Скачать сейчас", + "newVersionLater": "позже" +} \ No newline at end of file diff --git a/ui/flutter/assets/locales/zh_CN.json b/ui/flutter/assets/locales/zh_CN.json index f04ded94..c809491a 100644 --- a/ui/flutter/assets/locales/zh_CN.json +++ b/ui/flutter/assets/locales/zh_CN.json @@ -49,5 +49,8 @@ "effectAfterRestart": "此配置项将在重启应用后生效", "deleteTask": "删除任务", "deleteTaskTip": "保留已下载的文件", - "delete": "删除" + "delete": "删除", + "newVersionTitle": "发现新版本 @version", + "newVersionUpdate": "立即下载", + "newVersionLater": "稍后再说" } diff --git a/ui/flutter/lib/api/api.dart b/ui/flutter/lib/api/api.dart index 2655e6a4..7668dc5d 100644 --- a/ui/flutter/lib/api/api.dart +++ b/ui/flutter/lib/api/api.dart @@ -1,7 +1,8 @@ +import 'dart:convert'; import 'dart:io'; -import 'package:dio/adapter.dart'; import 'package:dio/dio.dart'; +import 'package:dio/io.dart'; import 'package:flutter/foundation.dart'; import 'model/resolve_result.dart'; import '../util/util.dart'; @@ -33,6 +34,9 @@ class _Client { } } dio.options.baseUrl = baseUrl; + dio.options.contentType = Headers.jsonContentType; + dio.options.connectTimeout = const Duration(seconds: 5); + dio.options.receiveTimeout = const Duration(seconds: 60); dio.interceptors.add(InterceptorsWrapper(onRequest: (options, handler) { if (apiToken.isNotEmpty) { options.headers['X-Api-Token'] = apiToken; @@ -42,7 +46,7 @@ class _Client { _instance!.dio = dio; if (isUnixSocket) { - (_instance!.dio.httpClientAdapter as DefaultHttpClientAdapter) + (_instance!.dio.httpClientAdapter as IOHttpClientAdapter) .onHttpClientCreate = (client) { client.connectionFactory = (Uri uri, String? proxyHost, int? proxyPort) { @@ -131,5 +135,7 @@ Future> proxyRequest(String uri, options.headers ??= {}; options.headers!["X-Target-Uri"] = uri; - return _client.dio.request("/api/v1/proxy", data: data, options: options); + // add timestamp to avoid cache + return _client.dio.request("/api/v1/proxy?t=${DateTime.now()}", + data: data, options: options); } diff --git a/ui/flutter/lib/app/modules/setting/controllers/setting_controller.dart b/ui/flutter/lib/app/modules/setting/controllers/setting_controller.dart index 924f6dcf..eb9569d3 100644 --- a/ui/flutter/lib/app/modules/setting/controllers/setting_controller.dart +++ b/ui/flutter/lib/app/modules/setting/controllers/setting_controller.dart @@ -1,7 +1,17 @@ +import 'dart:convert'; + import 'package:get/get.dart'; +import 'package:gopeed/api/api.dart'; class SettingController extends GetxController { final tapStatues = {}.obs; + final latestVersion = "".obs; + + @override + void onInit() { + super.onInit(); + fetchLatestVersion(); + } // set all tap status to false void clearTap() { @@ -13,4 +23,22 @@ class SettingController extends GetxController { clearTap(); tapStatues[key] = true; } + + // fetch latest version + void fetchLatestVersion() async { + String? releaseDataStr; + try { + releaseDataStr = + (await proxyRequest("https://gopeed.com/api/release")).data; + } catch (e) { + releaseDataStr = (await proxyRequest( + "https://api.github.com/repos/GopeedLab/gopeed/releases/latest")) + .data; + } + if (releaseDataStr == null) { + return; + } + final releaseData = jsonDecode(releaseDataStr); + latestVersion.value = releaseData["tag_name"].substring(1); + } } diff --git a/ui/flutter/lib/app/modules/setting/views/setting_view.dart b/ui/flutter/lib/app/modules/setting/views/setting_view.dart index 5ebef00b..ceaae55d 100644 --- a/ui/flutter/lib/app/modules/setting/views/setting_view.dart +++ b/ui/flutter/lib/app/modules/setting/views/setting_view.dart @@ -7,6 +7,7 @@ import 'package:get/get.dart'; import '../../../../util/message.dart'; import 'package:intl/intl.dart'; import 'package:url_launcher/url_launcher.dart'; +import 'package:badges/badges.dart' as badges; import '../../../../generated/locales.g.dart'; import '../../../../util/localeManager.dart'; @@ -236,10 +237,45 @@ class SettingView extends GetView { mode: LaunchMode.externalApplication); }, ); - buildVersion() => ListTile( - title: Text('version'.tr), - subtitle: Text(packageInfo.version), - ); + buildVersion() { + final hasNewVersion = controller.latestVersion.value != '' && + controller.latestVersion.value != packageInfo.version; + return ListTile( + title: hasNewVersion + ? badges.Badge( + position: badges.BadgePosition.topStart(start: 36), + child: Text('version'.tr)) + : Text('version'.tr), + subtitle: Text(packageInfo.version), + onTap: () { + if (hasNewVersion) { + showDialog( + context: context, + builder: (context) => AlertDialog( + content: Text('newVersionTitle'.trParams( + {'version': controller.latestVersion.value})), + actions: [ + TextButton( + onPressed: () { + Get.back(); + }, + child: Text('newVersionLater'.tr), + ), + TextButton( + onPressed: () { + launchUrl( + Uri.parse( + 'https://github.com/GopeedLab/gopeed/releases/latest'), + mode: LaunchMode.externalApplication); + }, + child: Text('newVersionUpdate'.tr), + ), + ], + )); + } + }, + ); + } // advanced config items start final buildApiProtocol = _buildConfigItem( diff --git a/ui/flutter/lib/app/views/views/buid_task_list_view.dart b/ui/flutter/lib/app/views/views/buid_task_list_view.dart index f060359f..385aacbf 100644 --- a/ui/flutter/lib/app/views/views/buid_task_list_view.dart +++ b/ui/flutter/lib/app/views/views/buid_task_list_view.dart @@ -24,30 +24,37 @@ class BuildTaskListView extends GetView { Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - actions: [ - IconButton( - icon: const Icon(Icons.add), - tooltip: 'create'.tr, - onPressed: () { - Get.rootDelegate.toNamed(Routes.CREATE); - }, + // actions: [ + // // IconButton( + // // icon: const Icon(Icons.add), + // // tooltip: 'create'.tr, + // // onPressed: () { + // // Get.rootDelegate.toNamed(Routes.CREATE); + // // }, + // // ), + // //TODO appBar toggleALl/start selected/delete selected/ + // // IconButton( + // // icon: const Icon(Icons.pause), + // // tooltip: 'title'.tr, + // // onPressed: () { + // // // pause all + // // }, + // // ), + // // IconButton( + // // icon: const Icon(Icons.delete), + // // tooltip: 'title'.tr, + // // onPressed: () { + // // // delete all + // // }, + // // ) + // ], ), - //TODO appBar toggleALl/start selected/delete selected/ - // IconButton( - // icon: const Icon(Icons.pause), - // tooltip: 'title'.tr, - // onPressed: () { - // // pause all - // }, - // ), - // IconButton( - // icon: const Icon(Icons.delete), - // tooltip: 'title'.tr, - // onPressed: () { - // // delete all - // }, - // ) - ], + floatingActionButton: FloatingActionButton( + onPressed: () { + Get.rootDelegate.toNamed(Routes.CREATE); + }, + tooltip: 'create'.tr, + child: const Icon(Icons.add), ), body: Obx(() { return buildTaskList(context, tasks); diff --git a/ui/flutter/lib/generated/locales.g.dart b/ui/flutter/lib/generated/locales.g.dart index 9421ba42..1aae9d06 100644 --- a/ui/flutter/lib/generated/locales.g.dart +++ b/ui/flutter/lib/generated/locales.g.dart @@ -64,6 +64,9 @@ class LocaleKeys { static const deleteTask = 'deleteTask'; static const deleteTaskTip = 'deleteTaskTip'; static const delete = 'delete'; + static const newVersionTitle = 'newVersionTitle'; + static const newVersionUpdate = 'newVersionUpdate'; + static const newVersionLater = 'newVersionLater'; } class Locales { @@ -121,6 +124,9 @@ class Locales { 'deleteTask': 'Delete Task', 'deleteTaskTip': 'Keep downloaded files', 'delete': 'Delete', + 'newVersionTitle': 'Discover new version @version', + 'newVersionUpdate': 'Download Now', + 'newVersionLater': 'Later', }; static const fa_IR = { 'label': 'فارسی', @@ -132,9 +138,12 @@ class Locales { 'task': 'کار', 'setting': 'تنظیمات', 'create': 'ایجاد کار', + 'downloading': 'دانلود کردن', + 'downloaded': 'دانلود شدن', 'downloadLink': 'لینک دانلود', 'downloadLinkValid': 'لطفا لینک دانلود را وارد کنید', 'download': 'دانلود', + 'noFileSelected': 'لطفا حداقل یک فایل را جهت دانلود انتخاب کنید.', 'noStoragePermission': 'مجوز ذخیره سازی مورد نیاز است', 'selectFile': 'انتخاب فایل', 'basic': 'پایه', @@ -170,6 +179,9 @@ class Locales { 'deleteTask': 'پاک کردن کار', 'deleteTaskTip': 'فایل های دانلود شده را نگه دارد', 'delete': 'پاک کردن', + 'newVersionTitle': '@version عنوان: کشف نسخه جدید', + 'newVersionUpdate': 'دریافت اکنون', + 'newVersionLater': 'بعداً', }; static const ru_RU = { 'label': 'Русский(Россия)', @@ -179,6 +191,8 @@ class Locales { 'cancel': 'Отмена', 'selectAll': 'Выбрать все', 'task': 'Задачи', + 'downloading': 'Загрузка', + 'downloaded': 'Загружено', 'setting': 'Настройки', 'create': 'Создать задачу', 'downloadLink': 'Ссылка для скачивание', @@ -187,6 +201,7 @@ class Locales { 'Пожалуйста, введите ссылку для скачивания, HTTP/HTTPS/MAGNET supported@append', 'downloadLinkHitDesktop': ', или перетащите сюда торрент-файл', 'download': 'Скачать', + 'noFileSelected': 'Файл не выбран', 'noStoragePermission': 'Требуется доступ к хранилищу', 'selectFile': 'Выберать файл', 'basic': 'Основные параметры', @@ -222,6 +237,9 @@ class Locales { 'deleteTask': 'Удалить задачу', 'deleteTaskTip': 'Сохранить загруженные файлы', 'delete': 'Удалить', + 'newVersionTitle': 'Обнаружена новая версия @version', + 'newVersionUpdate': 'Скачать сейчас', + 'newVersionLater': 'позже', }; static const zh_CN = { 'label': '中文(简体)', @@ -275,5 +293,8 @@ class Locales { 'deleteTask': '删除任务', 'deleteTaskTip': '保留已下载的文件', 'delete': '删除', + 'newVersionTitle': '发现新版本 @version', + 'newVersionUpdate': '立即下载', + 'newVersionLater': '稍后再说', }; } diff --git a/ui/flutter/pubspec.lock b/ui/flutter/pubspec.lock index 20aaf4a6..5311afce 100644 --- a/ui/flutter/pubspec.lock +++ b/ui/flutter/pubspec.lock @@ -41,6 +41,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "2.10.0" + badges: + dependency: "direct main" + description: + name: badges + sha256: d33d4e07197d6e61ddc940640f2b4dc303d3c80887011344940e24d7522f8d26 + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.0.3" boolean_selector: dependency: transitive description: @@ -221,10 +229,10 @@ packages: dependency: "direct main" description: name: dio - sha256: "7d328c4d898a61efc3cd93655a0955858e29a0aa647f0f9e02d59b3bb275e2e8" + sha256: "0894a098594263fe1caaba3520e3016d8a855caeb010a882273189cca10f11e9" url: "https://pub.flutter-io.cn" source: hosted - version: "4.0.6" + version: "5.1.1" fake_async: dependency: transitive description: @@ -466,10 +474,10 @@ packages: dependency: transitive description: name: logging_appenders - sha256: "013e8548b79e3b8dc0333f3efae706184356b5926c6bea59150efa126c91598c" + sha256: c2ea00fb779a81e995943f1e3e6e6969d463de3882d134d78ad58e76f2b6f1b1 url: "https://pub.flutter-io.cn" source: hosted - version: "1.0.0" + version: "1.0.2" lottie: dependency: "direct main" description: diff --git a/ui/flutter/pubspec.yaml b/ui/flutter/pubspec.yaml index 3d1c5292..f107ad48 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.2.3+1 +version: 1.3.0+1 environment: sdk: ">=2.17.6 <3.0.0" @@ -40,7 +40,7 @@ dependencies: styled_widget: ^0.4.0+3 context_menus: ^1.0.2 json_annotation: ^4.6.0 - dio: ^4.0.6 + dio: ^5.1.1 path_provider: ^2.0.11 file_picker: ^5.2.5 macos_secure_bookmarks: ^0.2.1 @@ -53,6 +53,7 @@ dependencies: package_info_plus: ^3.0.2 fluent_ui: ^4.4.0 path: any + badges: ^3.0.3 dev_dependencies: flutter_test: sdk: flutter