├── .dockerignore ├── .github ├── CODEOWNERS ├── FUNDING.yml ├── ISSUE_TEMPLATE.md ├── release-drafter.yml └── workflows │ ├── build.yml │ ├── release.yml │ ├── test.yml │ └── translator.yml.bak ├── .gitignore ├── CONTRIBUTING.md ├── CONTRIBUTING_ja-JP.md ├── CONTRIBUTING_vi-VN.md ├── CONTRIBUTING_zh-CN.md ├── CONTRIBUTING_zh-TW.md ├── Dockerfile ├── LICENSE ├── README.md ├── README_ja-JP.md ├── README_vi-VN.md ├── README_zh-CN.md ├── README_zh-TW.md ├── _docs └── img │ ├── banner.png │ ├── cli-demo.gif │ ├── goland.svg │ └── ui-demo.png ├── _examples └── basic │ └── main.go ├── bind ├── desktop │ └── main.go └── mobile │ └── main.go ├── cmd ├── api │ └── main.go ├── banner.txt ├── gopeed │ ├── flags.go │ └── main.go ├── host │ └── main.go ├── server.go ├── updater │ ├── main.go │ ├── updater_darwin.go │ ├── updater_linux.go │ └── updater_windows.go └── web │ ├── flags.go │ └── main.go ├── docker-compose.yml ├── entrypoint.sh ├── go.mod ├── go.sum ├── internal ├── controller │ └── controller.go ├── fetcher │ ├── fetcher.go │ └── fetcher_test.go ├── logger │ ├── logger.go │ └── logger_test.go ├── protocol │ ├── bt │ │ ├── config.go │ │ ├── dns_cache_resolver.go │ │ ├── fetcher.go │ │ ├── fetcher_test.go │ │ ├── file.go │ │ ├── file_misc.go │ │ ├── file_piece.go │ │ └── testdata │ │ │ ├── test.torrent │ │ │ ├── test.unclean.torrent │ │ │ └── ubuntu-22.04-live-server-amd64.iso.torrent │ └── http │ │ ├── config.go │ │ ├── fetcher.go │ │ ├── fetcher_test.go │ │ ├── timeout_reader.go │ │ └── timeout_reader_test.go └── test │ ├── httptest.go │ └── util.go ├── pkg ├── base │ ├── constants.go │ ├── info.go │ ├── model.go │ └── model_test.go ├── download │ ├── downloader.go │ ├── downloader_test.go │ ├── engine │ │ ├── engine.go │ │ ├── engine_test.go │ │ ├── inject │ │ │ ├── error │ │ │ │ └── module.go │ │ │ ├── file │ │ │ │ └── module.go │ │ │ ├── formdata │ │ │ │ └── module.go │ │ │ ├── vm │ │ │ │ └── module.go │ │ │ └── xhr │ │ │ │ └── module.go │ │ ├── polyfill │ │ │ ├── out │ │ │ │ └── index.js │ │ │ ├── package-lock.json │ │ │ ├── package.json │ │ │ ├── patches │ │ │ │ └── whatwg-fetch+3.6.20.patch │ │ │ ├── src │ │ │ │ ├── blob │ │ │ │ │ └── index.js │ │ │ │ ├── crypto │ │ │ │ │ └── index.js │ │ │ │ ├── fetch │ │ │ │ │ └── index.js │ │ │ │ └── index.js │ │ │ └── webpack.config.js │ │ └── util │ │ │ └── util.go │ ├── event.go │ ├── extension.go │ ├── extension_test.go │ ├── model.go │ ├── storage.go │ └── testdata │ │ └── extensions │ │ ├── basic │ │ ├── index.js │ │ └── manifest.json │ │ ├── extra │ │ ├── index.js │ │ └── manifest.json │ │ ├── function_error │ │ ├── index.js │ │ └── manifest.json │ │ ├── message_error │ │ ├── index.js │ │ └── manifest.json │ │ ├── on_done │ │ ├── index.js │ │ └── manifest.json │ │ ├── on_error │ │ ├── index.js │ │ └── manifest.json │ │ ├── on_start │ │ ├── index.js │ │ └── manifest.json │ │ ├── script_error │ │ ├── index.js │ │ └── manifest.json │ │ ├── settings_all │ │ ├── index.js │ │ └── manifest.json │ │ ├── settings_empty │ │ ├── index.js │ │ └── manifest.json │ │ ├── storage │ │ ├── index.js │ │ └── manifest.json │ │ └── update │ │ ├── index.js │ │ └── manifest.json ├── protocol │ ├── bt │ │ └── model.go │ └── http │ │ └── model.go ├── rest │ ├── api.go │ ├── config.go │ ├── gizp_middleware.go │ ├── model │ │ ├── extension.go │ │ ├── result.go │ │ ├── server.go │ │ └── task.go │ ├── server.go │ └── server_test.go └── util │ ├── bytefmt.go │ ├── bytefmt_test.go │ ├── json.go │ ├── json_test.go │ ├── matcher.go │ ├── matcher_test.go │ ├── path.go │ ├── path_other.go │ ├── path_test.go │ ├── path_windows.go │ ├── timer.go │ ├── url.go │ └── url_test.go └── ui └── flutter ├── .gitignore ├── .metadata ├── analysis_options.yaml ├── android ├── .gitignore ├── app │ ├── build.gradle │ ├── libs │ │ └── .gitkeep │ └── src │ │ ├── debug │ │ └── AndroidManifest.xml │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── kotlin │ │ │ └── com │ │ │ │ └── gopeed │ │ │ │ └── gopeed │ │ │ │ └── MainActivity.kt │ │ └── res │ │ │ ├── drawable-v21 │ │ │ └── launch_background.xml │ │ │ ├── drawable │ │ │ └── launch_background.xml │ │ │ ├── mipmap-hdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── values-night │ │ │ └── styles.xml │ │ │ └── values │ │ │ └── styles.xml │ │ └── profile │ │ └── AndroidManifest.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties └── settings.gradle ├── assets ├── exec │ └── .gitkeep ├── extension │ └── default_icon.png ├── fonts │ └── Gopeed.ttf ├── icon │ ├── icon.ico │ ├── icon.svg │ ├── icon_1024.png │ ├── icon_512.png │ └── icon_macos_1024.png └── tray_icon │ ├── icon.ico │ ├── icon.png │ └── icon_mac.png ├── build.yaml ├── distribute_options.yaml ├── include └── libgopeed.h ├── ios ├── .gitignore ├── Flutter │ ├── AppFrameworkInfo.plist │ ├── Debug.xcconfig │ └── Release.xcconfig ├── Podfile ├── Podfile.lock ├── Runner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings ├── Runner │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-50x50@1x.png │ │ │ ├── Icon-App-50x50@2x.png │ │ │ ├── Icon-App-57x57@1x.png │ │ │ ├── Icon-App-57x57@2x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-72x72@1x.png │ │ │ ├── Icon-App-72x72@2x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ └── Icon-App-83.5x83.5@2x.png │ │ └── LaunchImage.imageset │ │ │ ├── Contents.json │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ └── README.md │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── Info.plist │ ├── Runner-Bridging-Header.h │ └── Runner.entitlements └── ShareExtension │ ├── Base.lproj │ └── MainInterface.storyboard │ ├── Info.plist │ ├── ShareExtension.entitlements │ └── ShareViewController.swift ├── lib ├── api │ ├── api.dart │ └── model │ │ ├── create_task.dart │ │ ├── create_task.g.dart │ │ ├── create_task_batch.dart │ │ ├── create_task_batch.g.dart │ │ ├── downloader_config.dart │ │ ├── downloader_config.g.dart │ │ ├── extension.dart │ │ ├── extension.g.dart │ │ ├── install_extension.dart │ │ ├── install_extension.g.dart │ │ ├── meta.dart │ │ ├── meta.g.dart │ │ ├── options.dart │ │ ├── options.g.dart │ │ ├── request.dart │ │ ├── request.g.dart │ │ ├── resolve_result.dart │ │ ├── resolve_result.g.dart │ │ ├── resource.dart │ │ ├── resource.g.dart │ │ ├── result.dart │ │ ├── result.g.dart │ │ ├── switch_extension.dart │ │ ├── switch_extension.g.dart │ │ ├── task.dart │ │ ├── task.g.dart │ │ ├── update_check_extension_resp.dart │ │ ├── update_check_extension_resp.g.dart │ │ ├── update_extension_settings.dart │ │ └── update_extension_settings.g.dart ├── app │ ├── modules │ │ ├── app │ │ │ ├── bindings │ │ │ │ └── app_binding.dart │ │ │ ├── controllers │ │ │ │ └── app_controller.dart │ │ │ └── views │ │ │ │ └── app_view.dart │ │ ├── create │ │ │ ├── bindings │ │ │ │ └── create_binding.dart │ │ │ ├── controllers │ │ │ │ └── create_controller.dart │ │ │ ├── dto │ │ │ │ ├── create_router_params.dart │ │ │ │ └── create_router_params.g.dart │ │ │ └── views │ │ │ │ └── create_view.dart │ │ ├── extension │ │ │ ├── bindings │ │ │ │ └── extension_binding.dart │ │ │ ├── controllers │ │ │ │ └── extension_controller.dart │ │ │ └── views │ │ │ │ └── extension_view.dart │ │ ├── history │ │ │ └── views │ │ │ │ └── history_view.dart │ │ ├── home │ │ │ ├── bindings │ │ │ │ └── home_binding.dart │ │ │ ├── controllers │ │ │ │ └── home_controller.dart │ │ │ └── views │ │ │ │ └── home_view.dart │ │ ├── redirect │ │ │ ├── bindings │ │ │ │ └── redirect_binding.dart │ │ │ ├── controllers │ │ │ │ └── redirect_controller.dart │ │ │ └── views │ │ │ │ └── redirect_view.dart │ │ ├── root │ │ │ ├── bindings │ │ │ │ └── root_binding.dart │ │ │ ├── controllers │ │ │ │ └── root_controller.dart │ │ │ └── views │ │ │ │ └── root_view.dart │ │ ├── setting │ │ │ ├── bindings │ │ │ │ └── setting_binding.dart │ │ │ ├── controllers │ │ │ │ └── setting_controller.dart │ │ │ └── views │ │ │ │ └── setting_view.dart │ │ └── task │ │ │ ├── bindings │ │ │ ├── task_binding.dart │ │ │ └── task_files_binding.dart │ │ │ ├── controllers │ │ │ ├── task_controller.dart │ │ │ ├── task_downloaded_controller.dart │ │ │ ├── task_downloading_controller.dart │ │ │ ├── task_files_controller.dart │ │ │ └── task_list_controller.dart │ │ │ └── views │ │ │ ├── task_downloaded_view.dart │ │ │ ├── task_downloading_view.dart │ │ │ ├── task_files_view.dart │ │ │ └── task_view.dart │ ├── routes │ │ ├── app_pages.dart │ │ └── app_routes.dart │ └── views │ │ ├── breadcrumb_view.dart │ │ ├── buid_task_list_view.dart │ │ ├── check_list_view.dart │ │ ├── compact_checkbox.dart │ │ ├── copy_button.dart │ │ ├── directory_selector.dart │ │ ├── file_icon.dart │ │ ├── file_tree_view.dart │ │ ├── icon_button_loading.dart │ │ ├── open_in_new.dart │ │ ├── outlined_button_loading.dart │ │ ├── responsive_builder.dart │ │ ├── sort_icon_button.dart │ │ └── text_button_loading.dart ├── core │ ├── common │ │ ├── libgopeed_channel.dart │ │ ├── libgopeed_ffi.dart │ │ ├── libgopeed_interface.dart │ │ ├── start_config.dart │ │ └── start_config.g.dart │ ├── entry │ │ ├── libgopeed_boot_browser.dart │ │ └── libgopeed_boot_native.dart │ ├── ffi │ │ └── libgopeed_bind.dart │ ├── libgopeed_boot.dart │ └── libgopeed_boot_stub.dart ├── database │ ├── database.dart │ ├── entity.dart │ └── entity.g.dart ├── i18n │ ├── langs │ │ ├── en_us.dart │ │ ├── es_es.dart │ │ ├── fa_ir.dart │ │ ├── fr_fr.dart │ │ ├── hu_hu.dart │ │ ├── id_id.dart │ │ ├── it_it.dart │ │ ├── ja_jp.dart │ │ ├── pl_pl.dart │ │ ├── ru_ru.dart │ │ ├── ta_ta.dart │ │ ├── tr_tr.dart │ │ ├── uk_ua.dart │ │ ├── vi_vn.dart │ │ ├── zh_cn.dart │ │ └── zh_tw.dart │ └── message.dart ├── icon │ └── gopeed_icons.dart ├── main.dart ├── theme │ └── theme.dart └── util │ ├── arch │ ├── arch.dart │ ├── arch_stub.dart │ └── entry │ │ ├── arch_native.dart │ │ └── arch_web.dart │ ├── browser_download │ ├── browser_download.dart │ ├── browser_download_stub.dart │ └── entry │ │ └── browser_download_browser.dart │ ├── browser_extension_host │ ├── browser_extension_host.dart │ ├── browser_extension_host_stub.dart │ └── entry │ │ └── browser_extension_host_native.dart │ ├── extensions.dart │ ├── file_explorer.dart │ ├── github_mirror.dart │ ├── input_formatter.dart │ ├── locale_manager.dart │ ├── log_util.dart │ ├── message.dart │ ├── package_info.dart │ ├── scheme_register │ ├── entry │ │ └── scheme_register_native.dart │ ├── scheme_register.dart │ └── scheme_register_stub.dart │ ├── updater.dart │ ├── util.dart │ └── win32.dart ├── linux ├── .gitignore ├── CMakeLists.txt ├── assets │ └── com.gopeed.Gopeed.desktop ├── flutter │ └── CMakeLists.txt ├── main.cc ├── my_application.cc ├── my_application.h └── packaging │ ├── appimage │ └── make_config.yaml │ └── deb │ └── make_config.yaml ├── macos ├── .gitignore ├── Flutter │ ├── Flutter-Debug.xcconfig │ └── Flutter-Release.xcconfig ├── Podfile ├── Podfile.lock ├── Runner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── Runner │ ├── AppDelegate.swift │ ├── Assets.xcassets │ └── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── app_icon_1024.png │ │ ├── app_icon_128.png │ │ ├── app_icon_16.png │ │ ├── app_icon_256.png │ │ ├── app_icon_32.png │ │ ├── app_icon_512.png │ │ └── app_icon_64.png │ ├── Base.lproj │ └── MainMenu.xib │ ├── Configs │ ├── AppInfo.xcconfig │ ├── Debug.xcconfig │ ├── Release.xcconfig │ └── Warnings.xcconfig │ ├── DebugProfile.entitlements │ ├── Info.plist │ ├── MainFlutterWindow.swift │ └── Release.entitlements ├── pubspec.lock ├── pubspec.yaml ├── test └── widget_test.dart ├── web ├── favicon.png ├── icons │ ├── Icon-192.png │ ├── Icon-512.png │ ├── Icon-maskable-192.png │ └── Icon-maskable-512.png ├── index.html └── manifest.json └── windows ├── .gitignore ├── CMakeLists.txt ├── flutter └── CMakeLists.txt └── runner ├── CMakeLists.txt ├── Runner.rc ├── flutter_window.cpp ├── flutter_window.h ├── main.cpp ├── resource.h ├── resources └── app_icon.ico ├── runner.exe.manifest ├── utils.cpp ├── utils.h ├── win32_window.cpp └── win32_window.h /.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | 3 | *.data 4 | *.log 5 | 6 | node_modules 7 | **/node_modules 8 | 9 | Dockerfile 10 | .dockerignore 11 | 12 | .github 13 | _docs 14 | _examples 15 | bin 16 | ui -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @monkeyWie 2 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | #github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | #patreon: # Replace with a single Patreon username 5 | #open_collective: # Replace with a single Open Collective username 6 | #ko_fi: # Replace with a single Ko-fi username 7 | #tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | #community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | #liberapay: # Replace with a single Liberapay username 10 | #issuehunt: # Replace with a single IssueHunt username 11 | #otechie: # Replace with a single Otechie username 12 | #custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | # github: monkeyWie 14 | ko_fi: gopeed 15 | custom: https://docs.gopeed.com/donate.html 16 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 4 | ### Description(required) 5 | ### App Version(required) 6 | ### OS Version(required) 7 | ### Snapshots 8 | ### Log -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: release 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | paths: 8 | - "bind/**" 9 | - "cmd/**" 10 | - "internal/**" 11 | - "pkg/**" 12 | - "ui/**" 13 | - ".github/workflows/release.yml" 14 | - ".github/release-drafter.yml" 15 | - "go.mod" 16 | - "go.sum" 17 | - "Dockerfile" 18 | 19 | env: 20 | GO_VERSION: "1.24" 21 | 22 | permissions: 23 | contents: write 24 | 25 | jobs: 26 | release: 27 | runs-on: ubuntu-latest 28 | outputs: 29 | tag_name: ${{ steps.create_release.outputs.tag_name }} 30 | upload_url: ${{ steps.create_release.outputs.upload_url }} 31 | 32 | steps: 33 | - uses: actions/checkout@v3 34 | - uses: actions/setup-go@v4 35 | with: 36 | go-version: ${{ env.GO_VERSION }} 37 | - uses: release-drafter/release-drafter@v5 38 | id: create_release 39 | env: 40 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 41 | -------------------------------------------------------------------------------- /.github/workflows/translator.yml.bak: -------------------------------------------------------------------------------- 1 | # name: 'translator' 2 | 3 | # on: 4 | # issues: 5 | # types: [opened, edited] 6 | # issue_comment: 7 | # types: [created, edited] 8 | # discussion: 9 | # types: [created, edited] 10 | # discussion_comment: 11 | # types: [created, edited] 12 | # pull_request_target: 13 | # types: [opened, edited] 14 | # pull_request_review_comment: 15 | # types: [created, edited] 16 | 17 | # jobs: 18 | # translate: 19 | # permissions: 20 | # issues: write 21 | # discussions: write 22 | # pull-requests: write 23 | # runs-on: ubuntu-latest 24 | # steps: 25 | # - uses: lizheming/github-translate-action@1.1.2 26 | # env: 27 | # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 28 | # with: 29 | # IS_MODIFY_TITLE: true 30 | # APPEND_TRANSLATION: true 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | ui/flutter/dist/ 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | .idea/ 15 | 16 | bin/ 17 | 18 | .torrent.db 19 | .torrent.db-shm 20 | .torrent.db-wal 21 | 22 | *.data 23 | *.db 24 | *.log 25 | 26 | .DS_Store 27 | 28 | node_modules/ 29 | cmd/web/dist/ 30 | .test_storage/ 31 | .test_download/ 32 | /extensions -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Gopeed contributors guide 2 | 3 | Firstly, thank you for your interest in contributing to Gopeed. This guide will help you better 4 | participate in the development of Gopeed. 5 | 6 | ## Branch description 7 | 8 | This project only has one main branch, namely the `main` branch. If you want to participate in the 9 | development of Gopeed, please fork this project first, and then develop in your fork project. After 10 | development is completed, submit a PR to this project and merge it into the `main` branch. 11 | 12 | ## Local development 13 | 14 | It is recommended to develop and debug through the web. First, start the backend service, and start 15 | it by the command line `go run cmd/api/main.go`, the default port of the service is `9999`, and then 16 | start the front-end flutter project in `debug` mode to run. 17 | 18 | ## Translation 19 | 20 | The internationalization files of Gopeed are located in the `ui/flutter/lib/i18n/langs` directory. 21 | You only need to add the corresponding language file in this directory. 22 | 23 | Please refer to `en_us.dart` for translation. 24 | words prefixed with `@` are not meant to be translated. 25 | 26 | ## flutter development 27 | 28 | Don't forget to run`dart format ./ui/flutter`before you commit to keep your code in standard dart format 29 | 30 | Turn on build_runner watcher if you want to edit api/models: 31 | 32 | ``` 33 | flutter pub run build_runner watch 34 | ``` -------------------------------------------------------------------------------- /CONTRIBUTING_ja-JP.md: -------------------------------------------------------------------------------- 1 | # Gopeed コントリビューターガイド 2 | 3 | まず最初に、Gopeed への貢献に興味を持っていただきありがとうございます。このガイドは、あなたが Gopeed の 4 | 開発に参加するための手助けとなるでしょう。 5 | 6 | ## ブランチの説明 7 | 8 | このプロジェクトのメインブランチは `main` ブランチのみです。Gopeed の開発に参加したい場合は、 9 | まずこのプロジェクトをフォークし、フォークしたプロジェクトで開発を行ってください。開発が完了したら、 10 | このプロジェクトに PR を提出し、`main` ブランチにマージしてください。 11 | 12 | ## ローカル開発 13 | 14 | 開発およびデバッグはウェブ上で行うことを推奨する。まずバックエンドのサービスを起動し、 15 | コマンドライン `go run cmd/api/main.go` で起動する。サービスのデフォルトポートは `9999` で、 16 | 次にフロントエンドの flutter プロジェクトを `debug` モードで起動して実行します。 17 | 18 | ## 翻訳 19 | 20 | Gopeed の国際化ファイルは `ui/flutter/lib/i18n/langs` ディレクトリにあります。 21 | このディレクトリに対応する言語ファイルを追加するだけでよいです。 22 | 23 | 翻訳については `en_us.dart` を参照してください。 24 | 25 | ## flutter での開発 26 | 27 | コミットする前に `dart format ./ui/flutter` を実行し、コードを標準の dart フォーマットにしておくことを忘れないでください 28 | 29 | api/models を編集したい場合は build_runner watcher をオンにします: 30 | 31 | ``` 32 | flutter pub run build_runner watch 33 | ``` 34 | -------------------------------------------------------------------------------- /CONTRIBUTING_vi-VN.md: -------------------------------------------------------------------------------- 1 | # Hướng dẫn đóng góp cho Gopeed 2 | 3 | Trước tiên, cảm ơn bạn đã quan tâm đến việc đóng góp cho Gopeed. Hướng dẫn này sẽ giúp bạn tham gia 4 | phát triển Gopeed một cách tốt hơn. 5 | 6 | ## Mô tả nhánh 7 | 8 | Dự án này chỉ có một nhánh chính duy nhất, đó là nhánh `main`. Nếu bạn muốn tham gia vào 9 | phát triển Gopeed, hãy fork dự án này trước, sau đó phát triển trong dự án fork của bạn. Sau khi 10 | hoàn thành phát triển, gửi một PR đến dự án này và merge vào nhánh `main`. 11 | 12 | ## Phát triển cục bộ 13 | 14 | Đề nghị phát triển và gỡ lỗi thông qua web. Đầu tiên, khởi động dịch vụ backend bằng cách chạy 15 | lệnh `go run cmd/api/main.go` trong dòng lệnh, cổng mặc định của dịch vụ là `9999`, sau đó 16 | khởi động dự án flutter frontend trong chế độ `debug` để chạy. 17 | 18 | ## Dịch thuật 19 | 20 | Các tệp quốc tế hóa của Gopeed được đặt trong thư mục `ui/flutter/lib/i18n/langs`. 21 | Bạn chỉ cần thêm tệp ngôn ngữ tương ứng trong thư mục này. 22 | 23 | Vui lòng tham khảo `en_us.dart` để biết cách dịch thuật. 24 | 25 | ## Phát triển flutter 26 | 27 | Đừng quên chạy `dart format ./ui/flutter` trước khi commit để giữ mã của bạn theo định dạng dart chuẩn. 28 | 29 | Bật build_runner watcher nếu bạn muốn chỉnh sửa api/models: 30 | -------------------------------------------------------------------------------- /CONTRIBUTING_zh-CN.md: -------------------------------------------------------------------------------- 1 | # Gopeed 贡献指南 2 | 3 | 首先感谢您对贡献代码感兴趣,这份指南将帮助您更好的参与到 Gopeed 的开发中来。 4 | 5 | ## 分支说明 6 | 7 | 本项目只有一个主分支,即 `main` 分支,如果您想要参与到 Gopeed 的开发中来,请先 fork 本项目,然后在您的 fork 项目中进行开发,开发完成后再向本项目提交 8 | PR,合并到 `main` 分支。 9 | 10 | ## 本地开发 11 | 12 | 建议通过 web 端进行开发调试,首先启动后端服务,通过命令行 `go run cmd/api/main.go` 启动 ,服务启动默认端口为 `9999`,然后以 `debug` 模式启动前端 13 | flutter 项目即可运行。 14 | 15 | ## 翻译 16 | 17 | Gopeed 的国际化文件位于 `ui/flutter/lib/i18n/langs` 目录下,只需要在该目录下添加对应的语言文件即可。 18 | 19 | 请注意以 `en_us.dart` 为参照进行翻译。 20 | 21 | ## flutter开发 22 | 23 | 每次提交前请务必`dart format ./ui/flutter` 24 | 25 | 如果要编辑api/models,请打开build_runner watcher: 26 | 27 | ``` 28 | flutter pub run build_runner watch 29 | ``` 30 | 31 | -------------------------------------------------------------------------------- /CONTRIBUTING_zh-TW.md: -------------------------------------------------------------------------------- 1 | # Gopeed 協助指南 2 | 3 | 首先感謝您願意幫助我們改進並優化該項目,這份指南將會幫助您更好的參與 Gopeed 的開發。 4 | 5 | ## 分支說明 6 | 7 | 本項目只有一個分支,即 `main` 分支,如果您想要參與 Gopeed 的開發,請先 fork 該項目,再在您自己的 fork 中進行開發,開發完成後再開啟PR,以合併至 `main` 分支。 8 | 9 | ## 離線開發 10 | 11 | 建議使用 web 端進行開發與調試,首先啟動服務,使用指令 `go run cmd/api/main.go` 啟動 ,該服務默認連接埠為 `9999`,接著以 `debug` 模式啟動前端 flutter 項目即可。 12 | 13 | ## 翻譯 14 | 15 | Gopeed 的翻譯文件位於 `ui/flutter/lib/i18n/langs` 目錄中,只需要修改或新建翻譯文件即可。 16 | 17 | 18 | 請以 `en_us.dart` 作為參照。 19 | 20 | ## flutter開發 21 | 22 | 每次提交PR前請務必執行 `dart format ./ui/flutter` 23 | 24 | 如果需要編輯 api/models,請打開build_runner watcher: 25 | 26 | ``` 27 | flutter pub run build_runner watch 28 | ``` 29 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.23.3 AS go 2 | WORKDIR /app 3 | COPY ./go.mod ./go.sum ./ 4 | RUN go mod download 5 | COPY . . 6 | ARG VERSION=dev 7 | RUN CGO_ENABLED=0 go build -tags nosqlite,web \ 8 | -ldflags="-s -w -X github.com/GopeedLab/gopeed/pkg/base.Version=$VERSION -X github.com/GopeedLab/gopeed/pkg/base.InDocker=true" \ 9 | -o dist/gopeed github.com/GopeedLab/gopeed/cmd/web 10 | 11 | FROM alpine:3.18 12 | LABEL maintainer="monkeyWie" 13 | WORKDIR /app 14 | COPY --from=go /app/dist/gopeed ./ 15 | COPY entrypoint.sh ./entrypoint.sh 16 | RUN apk update && \ 17 | apk add --no-cache su-exec ; \ 18 | chmod +x ./entrypoint.sh && \ 19 | rm -rf /var/cache/apk/* 20 | VOLUME ["/app/storage"] 21 | ENV PUID=0 PGID=0 UMASK=022 22 | EXPOSE 9999 23 | ENTRYPOINT ["./entrypoint.sh"] 24 | -------------------------------------------------------------------------------- /_docs/img/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GopeedLab/gopeed/7c541e40479b5e574a4ab4c40ab4d328ab275377/_docs/img/banner.png -------------------------------------------------------------------------------- /_docs/img/cli-demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GopeedLab/gopeed/7c541e40479b5e574a4ab4c40ab4d328ab275377/_docs/img/cli-demo.gif -------------------------------------------------------------------------------- /_docs/img/goland.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /_docs/img/ui-demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GopeedLab/gopeed/7c541e40479b5e574a4ab4c40ab4d328ab275377/_docs/img/ui-demo.png -------------------------------------------------------------------------------- /_examples/basic/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/GopeedLab/gopeed/pkg/base" 6 | "github.com/GopeedLab/gopeed/pkg/download" 7 | "github.com/GopeedLab/gopeed/pkg/protocol/http" 8 | ) 9 | 10 | func main() { 11 | finallyCh := make(chan error) 12 | _, err := download.Boot(). 13 | URL("https://www.baidu.com/index.html"). 14 | Listener(func(event *download.Event) { 15 | if event.Key == download.EventKeyFinally { 16 | finallyCh <- event.Err 17 | } 18 | }). 19 | Create(&base.Options{ 20 | Extra: http.OptsExtra{ 21 | Connections: 8, 22 | }, 23 | }) 24 | if err != nil { 25 | panic(err) 26 | } 27 | err = <-finallyCh 28 | if err != nil { 29 | fmt.Printf("download fail:%v\n", err) 30 | } else { 31 | fmt.Println("download success") 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /bind/desktop/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "C" 4 | import ( 5 | "encoding/json" 6 | "github.com/GopeedLab/gopeed/pkg/rest" 7 | "github.com/GopeedLab/gopeed/pkg/rest/model" 8 | ) 9 | 10 | func main() {} 11 | 12 | //export Start 13 | func Start(cfg *C.char) (int, *C.char) { 14 | var config model.StartConfig 15 | if err := json.Unmarshal([]byte(C.GoString(cfg)), &config); err != nil { 16 | return 0, C.CString(err.Error()) 17 | } 18 | config.ProductionMode = true 19 | realPort, err := rest.Start(&config) 20 | if err != nil { 21 | return 0, C.CString(err.Error()) 22 | } 23 | return realPort, nil 24 | } 25 | 26 | //export Stop 27 | func Stop() { 28 | rest.Stop() 29 | } 30 | -------------------------------------------------------------------------------- /bind/mobile/main.go: -------------------------------------------------------------------------------- 1 | package libgopeed 2 | 3 | // #cgo LDFLAGS: -static-libstdc++ 4 | import "C" 5 | import ( 6 | "encoding/json" 7 | "github.com/GopeedLab/gopeed/pkg/rest" 8 | "github.com/GopeedLab/gopeed/pkg/rest/model" 9 | ) 10 | 11 | func Start(cfg string) (int, error) { 12 | var config model.StartConfig 13 | if err := json.Unmarshal([]byte(cfg), &config); err != nil { 14 | return 0, err 15 | } 16 | config.ProductionMode = true 17 | return rest.Start(&config) 18 | } 19 | 20 | func Stop() { 21 | rest.Stop() 22 | } 23 | -------------------------------------------------------------------------------- /cmd/api/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/GopeedLab/gopeed/cmd" 5 | "github.com/GopeedLab/gopeed/pkg/rest/model" 6 | ) 7 | 8 | // only for local development 9 | func main() { 10 | cfg := &model.StartConfig{ 11 | Network: "tcp", 12 | Address: "127.0.0.1:9999", 13 | Storage: model.StorageBolt, 14 | WebEnable: true, 15 | } 16 | cmd.Start(cfg) 17 | } 18 | -------------------------------------------------------------------------------- /cmd/banner.txt: -------------------------------------------------------------------------------- 1 | 2 | _______ ______ .______ _______ _______ _______ 3 | / _____| / __ \ | _ \ | ____|| ____|| \ 4 | | | __ | | | | | |_) | | |__ | |__ | .--. | 5 | | | |_ | | | | | | ___/ | __| | __| | | | | 6 | | |__| | | `--' | | | | |____ | |____ | '--' | 7 | \______| \______/ | _| |_______||_______||_______/ 8 | -------------------------------------------------------------------------------- /cmd/gopeed/flags.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "os" 7 | ) 8 | 9 | type args struct { 10 | url string 11 | connections *int 12 | dir *string 13 | } 14 | 15 | func parse() *args { 16 | dir, err := os.Getwd() 17 | if err != nil { 18 | panic(err) 19 | } 20 | var args args 21 | args.connections = flag.Int("C", 16, "Concurrent connections.") 22 | args.dir = flag.String("D", dir, "Store directory.") 23 | flag.Parse() 24 | t := flag.Args() 25 | if len(t) > 0 { 26 | args.url = t[0] 27 | } else { 28 | gPrintln("missing url parameter, for example: gopeed https://www.google.com or gopeed bt.torrent or gopeed magnet:?xt=urn:btih:...") 29 | gPrintln("try 'gopeed -h' for more information") 30 | os.Exit(1) 31 | } 32 | return &args 33 | } 34 | 35 | func gPrint(msg string) { 36 | fmt.Print("gopeed: " + msg) 37 | } 38 | 39 | func gPrintln(msg string) { 40 | gPrint(msg + "\n") 41 | } 42 | -------------------------------------------------------------------------------- /cmd/web/main.go: -------------------------------------------------------------------------------- 1 | //go:build web 2 | // +build web 3 | 4 | package main 5 | 6 | import ( 7 | "embed" 8 | "fmt" 9 | "github.com/GopeedLab/gopeed/cmd" 10 | "github.com/GopeedLab/gopeed/pkg/rest/model" 11 | "io/fs" 12 | "os" 13 | "path/filepath" 14 | ) 15 | 16 | //go:embed dist/* 17 | var dist embed.FS 18 | 19 | func main() { 20 | sub, err := fs.Sub(dist, "dist") 21 | if err != nil { 22 | panic(err) 23 | } 24 | 25 | args := parse() 26 | var webBasicAuth *model.WebBasicAuth 27 | if isNotBlank(args.Username) && isNotBlank(args.Password) { 28 | webBasicAuth = &model.WebBasicAuth{ 29 | Username: *args.Username, 30 | Password: *args.Password, 31 | } 32 | } 33 | 34 | var dir string 35 | if args.StorageDir != nil && *args.StorageDir != "" { 36 | dir = *args.StorageDir 37 | } else { 38 | exe, err := os.Executable() 39 | if err != nil { 40 | panic(err) 41 | } 42 | dir = filepath.Dir(exe) 43 | } 44 | 45 | cfg := &model.StartConfig{ 46 | Network: "tcp", 47 | Address: fmt.Sprintf("%s:%d", *args.Address, *args.Port), 48 | Storage: model.StorageBolt, 49 | StorageDir: filepath.Join(dir, "storage"), 50 | ApiToken: *args.ApiToken, 51 | DownloadConfig: args.DownloadConfig, 52 | ProductionMode: true, 53 | WebEnable: true, 54 | WebFS: sub, 55 | WebBasicAuth: webBasicAuth, 56 | } 57 | cmd.Start(cfg) 58 | } 59 | 60 | func isNotBlank(str *string) bool { 61 | return str != nil && *str != "" 62 | } 63 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | gopeed: 3 | container_name: gopeed 4 | ports: 5 | - 9999:9999 # HTTP port (host:container) 6 | environment: 7 | - PUID=0 8 | - PGID=0 9 | - UMASK=022 10 | volumes: 11 | - ~/gopeed/Downloads:/app/Downloads # mount download path 12 | #- ~/gopeed/storage:/app/storage # if you need to mount storage path, uncomment this line 13 | restart: unless-stopped 14 | image: liwei2633/gopeed 15 | # command: -u Username -p Password # optional authentication -------------------------------------------------------------------------------- /entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | chown -R ${PUID}:${PGID} /app 4 | 5 | umask ${UMASK} 6 | 7 | exec su-exec ${PUID}:${PGID} ./gopeed "$@" -------------------------------------------------------------------------------- /internal/logger/logger.go: -------------------------------------------------------------------------------- 1 | package logger 2 | 3 | import ( 4 | "github.com/GopeedLab/gopeed/pkg/util" 5 | "github.com/rs/zerolog" 6 | "io" 7 | "os" 8 | "path/filepath" 9 | ) 10 | 11 | type Logger struct { 12 | zerolog.Logger 13 | logFile *os.File 14 | } 15 | 16 | func (l *Logger) CLose() { 17 | l.logFile.Close() 18 | } 19 | 20 | // NewLogger create a new logger 21 | func NewLogger(logFile bool, logPath string) *Logger { 22 | var out io.Writer 23 | if logFile { 24 | // log to file 25 | logDir := filepath.Dir(logPath) 26 | if err := util.CreateDirIfNotExist(logDir); err != nil { 27 | panic(err) 28 | } 29 | var ( 30 | logfile *os.File 31 | err error 32 | ) 33 | logfile, err = os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) 34 | if err != nil { 35 | panic(err) 36 | } 37 | out = logfile 38 | } else { 39 | out = os.Stdout 40 | } 41 | 42 | logger := &Logger{} 43 | if logFile { 44 | logger.logFile = out.(*os.File) 45 | } 46 | logger.Logger = zerolog.New(zerolog.ConsoleWriter{ 47 | NoColor: true, 48 | Out: out, 49 | TimeFormat: "2006-01-02 15:04:05", 50 | }).With().Timestamp().Logger() 51 | return logger 52 | } 53 | -------------------------------------------------------------------------------- /internal/logger/logger_test.go: -------------------------------------------------------------------------------- 1 | package logger 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | ) 7 | 8 | func TestNewLogger(t *testing.T) { 9 | logger := NewLogger(false, "") 10 | logger.Info().Msg("test") 11 | } 12 | 13 | func TestNewLoggerFile(t *testing.T) { 14 | logPath := "./testdata/test.log" 15 | logger := NewLogger(true, logPath) 16 | defer func() { 17 | logger.CLose() 18 | os.Remove(logPath) 19 | }() 20 | logger.Info().Msg("test") 21 | 22 | // read log file, if not exist or empty, test fail. 23 | file, err := os.Open(logPath) 24 | if err != nil { 25 | t.Fatal(err) 26 | } 27 | defer file.Close() 28 | stat, err := file.Stat() 29 | if err != nil { 30 | t.Fatal(err) 31 | } 32 | if stat.Size() == 0 { 33 | t.Fatal("log file is empty") 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /internal/protocol/bt/config.go: -------------------------------------------------------------------------------- 1 | package bt 2 | 3 | type config struct { 4 | ListenPort int `json:"listenPort"` 5 | Trackers []string `json:"trackers"` 6 | // SeedKeep is always keep seeding after downloading is complete, unless manually stopped. 7 | SeedKeep bool `json:"seedKeep"` 8 | // SeedRatio is the ratio of uploaded data to downloaded data to seed. 9 | SeedRatio float64 `json:"seedRatio"` 10 | // SeedTime is the time in seconds to seed after downloading is complete. 11 | SeedTime int64 `json:"seedTime"` 12 | } 13 | -------------------------------------------------------------------------------- /internal/protocol/bt/dns_cache_resolver.go: -------------------------------------------------------------------------------- 1 | package bt 2 | 3 | import ( 4 | "context" 5 | "net" 6 | "time" 7 | 8 | "github.com/rs/dnscache" 9 | ) 10 | 11 | // DnsCacheResolver resolves DNS requests for an HTTP client using an in-memory cache. 12 | type DnsCacheResolver struct { 13 | RefreshTimeout time.Duration 14 | 15 | resolver dnscache.Resolver 16 | } 17 | 18 | func (r *DnsCacheResolver) DialContext(ctx context.Context, network, address string) (net.Conn, error) { 19 | host, port, err := net.SplitHostPort(address) 20 | if err != nil { 21 | return nil, err 22 | } 23 | ips, err := r.resolver.LookupHost(ctx, host) 24 | if err != nil { 25 | return nil, err 26 | } 27 | var conn net.Conn 28 | for _, ip := range ips { 29 | var dialer net.Dialer 30 | conn, err = dialer.DialContext(ctx, network, net.JoinHostPort(ip, port)) 31 | if err == nil { 32 | break 33 | } 34 | } 35 | return conn, err 36 | } 37 | 38 | func (r *DnsCacheResolver) Run(ctx context.Context) { 39 | ticker := time.NewTicker(r.RefreshTimeout) 40 | defer ticker.Stop() 41 | for { 42 | select { 43 | case <-ctx.Done(): 44 | return 45 | case <-ticker.C: 46 | r.resolver.Refresh(true) 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /internal/protocol/bt/file_misc.go: -------------------------------------------------------------------------------- 1 | // modify from github.com/anacrolix/torrent/storage/file_misc.go 2 | 3 | package bt 4 | 5 | import "github.com/anacrolix/torrent/metainfo" 6 | 7 | type requiredLength struct { 8 | fileIndex int 9 | length int64 10 | } 11 | 12 | func extentCompleteRequiredLengths(info *metainfo.Info, off, n int64) (ret []requiredLength) { 13 | if n == 0 { 14 | return 15 | } 16 | for i, fi := range info.UpvertedFiles() { 17 | if off >= fi.Length { 18 | off -= fi.Length 19 | continue 20 | } 21 | n1 := n 22 | if off+n1 > fi.Length { 23 | n1 = fi.Length - off 24 | } 25 | ret = append(ret, requiredLength{ 26 | fileIndex: i, 27 | length: off + n1, 28 | }) 29 | n -= n1 30 | if n == 0 { 31 | return 32 | } 33 | off = 0 34 | } 35 | return 36 | } 37 | -------------------------------------------------------------------------------- /internal/protocol/bt/file_piece.go: -------------------------------------------------------------------------------- 1 | // modify from github.com/anacrolix/torrent/storage/file_piece.go 2 | 3 | package bt 4 | 5 | import ( 6 | "github.com/anacrolix/torrent/metainfo" 7 | "github.com/anacrolix/torrent/storage" 8 | "io" 9 | "log" 10 | "os" 11 | ) 12 | 13 | type filePieceImpl struct { 14 | *fileTorrentImpl 15 | p metainfo.Piece 16 | io.WriterAt 17 | io.ReaderAt 18 | } 19 | 20 | var _ storage.PieceImpl = (*filePieceImpl)(nil) 21 | 22 | func (fs *filePieceImpl) pieceKey() metainfo.PieceKey { 23 | return metainfo.PieceKey{InfoHash: fs.infoHash, Index: fs.p.Index()} 24 | } 25 | 26 | func (fs *filePieceImpl) Completion() storage.Completion { 27 | c, err := fs.completion.Get(fs.pieceKey()) 28 | if err != nil { 29 | log.Printf("error getting piece completion: %s", err) 30 | c.Ok = false 31 | return c 32 | } 33 | 34 | verified := true 35 | if c.Complete { 36 | // If it's allegedly complete, check that its constituent files have the necessary length. 37 | for _, fi := range extentCompleteRequiredLengths(fs.p.Info, fs.p.Offset(), fs.p.Length()) { 38 | s, err := os.Stat(fs.files[fi.fileIndex].path) 39 | if err != nil || s.Size() < fi.length { 40 | verified = false 41 | break 42 | } 43 | } 44 | } 45 | 46 | if !verified { 47 | // The completion was wrong, fix it. 48 | c.Complete = false 49 | fs.completion.Set(fs.pieceKey(), false) 50 | } 51 | 52 | return c 53 | } 54 | 55 | func (fs *filePieceImpl) MarkComplete() error { 56 | return fs.completion.Set(fs.pieceKey(), true) 57 | } 58 | 59 | func (fs *filePieceImpl) MarkNotComplete() error { 60 | return fs.completion.Set(fs.pieceKey(), false) 61 | } 62 | -------------------------------------------------------------------------------- /internal/protocol/bt/testdata/test.torrent: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GopeedLab/gopeed/7c541e40479b5e574a4ab4c40ab4d328ab275377/internal/protocol/bt/testdata/test.torrent -------------------------------------------------------------------------------- /internal/protocol/bt/testdata/test.unclean.torrent: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GopeedLab/gopeed/7c541e40479b5e574a4ab4c40ab4d328ab275377/internal/protocol/bt/testdata/test.unclean.torrent -------------------------------------------------------------------------------- /internal/protocol/bt/testdata/ubuntu-22.04-live-server-amd64.iso.torrent: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GopeedLab/gopeed/7c541e40479b5e574a4ab4c40ab4d328ab275377/internal/protocol/bt/testdata/ubuntu-22.04-live-server-amd64.iso.torrent -------------------------------------------------------------------------------- /internal/protocol/http/config.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | type config struct { 4 | UserAgent string `json:"userAgent"` 5 | Connections int `json:"connections"` 6 | UseServerCtime bool `json:"useServerCtime"` 7 | } 8 | -------------------------------------------------------------------------------- /internal/protocol/http/timeout_reader.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "context" 5 | "io" 6 | "time" 7 | ) 8 | 9 | type TimeoutReader struct { 10 | reader io.Reader 11 | timeout time.Duration 12 | } 13 | 14 | func NewTimeoutReader(r io.Reader, timeout time.Duration) *TimeoutReader { 15 | return &TimeoutReader{ 16 | reader: r, 17 | timeout: timeout, 18 | } 19 | } 20 | 21 | func (tr *TimeoutReader) Read(p []byte) (n int, err error) { 22 | ctx, cancel := context.WithTimeout(context.Background(), tr.timeout) 23 | defer cancel() 24 | 25 | done := make(chan struct{}) 26 | var readErr error 27 | var bytesRead int 28 | 29 | go func() { 30 | bytesRead, readErr = tr.reader.Read(p) 31 | close(done) 32 | }() 33 | 34 | select { 35 | case <-done: 36 | return bytesRead, readErr 37 | case <-ctx.Done(): 38 | return 0, ctx.Err() 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /internal/protocol/http/timeout_reader_test.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "errors" 7 | "io" 8 | "testing" 9 | "time" 10 | ) 11 | 12 | func TestTimeoutReader_Read(t *testing.T) { 13 | data := []byte("Hello, World!") 14 | reader := bytes.NewReader(data) 15 | timeoutReader := NewTimeoutReader(reader, 1*time.Second) 16 | 17 | buf := make([]byte, len(data)) 18 | n, err := timeoutReader.Read(buf) 19 | if err != nil { 20 | t.Fatalf("expected no error, got %v", err) 21 | } 22 | if n != len(data) { 23 | t.Fatalf("expected to read %d bytes, read %d", len(data), n) 24 | } 25 | if !bytes.Equal(buf, data) { 26 | t.Fatalf("expected %s, got %s", data, buf) 27 | } 28 | } 29 | 30 | func TestTimeoutReader_ReadTimeout(t *testing.T) { 31 | reader := &slowReader{delay: 2 * time.Second} 32 | timeoutReader := NewTimeoutReader(reader, 1*time.Second) 33 | 34 | buf := make([]byte, 8192) 35 | _, err := timeoutReader.Read(buf) 36 | if err == nil { 37 | t.Fatal("expected timeout error, got nil") 38 | } 39 | if !errors.Is(err, context.DeadlineExceeded) { 40 | t.Fatalf("expected %v, got %v", context.DeadlineExceeded, err) 41 | } 42 | } 43 | 44 | type slowReader struct { 45 | delay time.Duration 46 | } 47 | 48 | func (sr *slowReader) Read(p []byte) (n int, err error) { 49 | time.Sleep(sr.delay) 50 | return 0, io.EOF 51 | } 52 | -------------------------------------------------------------------------------- /internal/test/util.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "crypto/md5" 5 | "encoding/hex" 6 | "encoding/json" 7 | "io" 8 | "os" 9 | "path/filepath" 10 | ) 11 | 12 | func FileMd5(filePath string) string { 13 | file, err := os.Open(filePath) 14 | if err != nil { 15 | panic(err) 16 | } 17 | // Tell the program to call the following function when the current function returns 18 | defer file.Close() 19 | 20 | // Open a new hash interface to write to 21 | hash := md5.New() 22 | 23 | // Copy the file in the hash interface and check for any error 24 | if _, err := io.Copy(hash, file); err != nil { 25 | return "" 26 | } 27 | return hex.EncodeToString(hash.Sum(nil)) 28 | } 29 | 30 | func DirMd5(dirPath string) string { 31 | hash := md5.New() 32 | filepath.Walk(dirPath, func(path string, info os.FileInfo, err error) error { 33 | if info.IsDir() { 34 | return nil 35 | } 36 | file, err := os.Open(path) 37 | if err != nil { 38 | return err 39 | } 40 | if _, err := io.Copy(hash, file); err != nil { 41 | return err 42 | } 43 | return nil 44 | }) 45 | return hex.EncodeToString(hash.Sum(nil)) 46 | } 47 | 48 | func ToJson(v interface{}) string { 49 | buf, _ := json.Marshal(v) 50 | return string(buf) 51 | } 52 | 53 | func JsonEqual(v1 any, v2 any) bool { 54 | return ToJson(v1) == ToJson(v2) 55 | } 56 | -------------------------------------------------------------------------------- /pkg/base/constants.go: -------------------------------------------------------------------------------- 1 | package base 2 | 3 | type Status string 4 | 5 | const ( 6 | DownloadStatusReady Status = "ready" // task create but not start 7 | DownloadStatusRunning Status = "running" 8 | DownloadStatusPause Status = "pause" 9 | DownloadStatusWait Status = "wait" // task is wait for running 10 | DownloadStatusError Status = "error" 11 | DownloadStatusDone Status = "done" 12 | ) 13 | 14 | const ( 15 | HttpCodeOK = 200 16 | HttpCodePartialContent = 206 17 | 18 | HttpHeaderHost = "Host" 19 | HttpHeaderRange = "Range" 20 | HttpHeaderAcceptRanges = "Accept-Ranges" 21 | HttpHeaderContentLength = "Content-Length" 22 | HttpHeaderContentRange = "Content-Range" 23 | HttpHeaderContentDisposition = "Content-Disposition" 24 | HttpHeaderUserAgent = "User-Agent" 25 | HttpHeaderLastModified = "Last-Modified" 26 | 27 | HttpHeaderBytes = "bytes" 28 | HttpHeaderRangeFormat = "bytes=%d-%d" 29 | ) 30 | -------------------------------------------------------------------------------- /pkg/base/info.go: -------------------------------------------------------------------------------- 1 | package base 2 | 3 | // Version is the build version, set at build time, using `go build -ldflags "-X github.com/GopeedLab/gopeed/pkg/base.Version=1.0.0"`. 4 | var Version string 5 | var InDocker string 6 | 7 | func init() { 8 | if Version == "" { 9 | Version = "dev" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /pkg/download/engine/inject/error/module.go: -------------------------------------------------------------------------------- 1 | package error 2 | 3 | import ( 4 | "github.com/dop251/goja" 5 | ) 6 | 7 | type MessageError struct { 8 | Message string `json:"message"` 9 | } 10 | 11 | func (e *MessageError) Error() string { 12 | return e.Message 13 | } 14 | 15 | func Enable(runtime *goja.Runtime) error { 16 | messageError := runtime.ToValue(func(call goja.ConstructorCall) *goja.Object { 17 | var message string 18 | if len(call.Arguments) > 0 { 19 | message = call.Arguments[0].String() 20 | } 21 | instance := &MessageError{ 22 | Message: message, 23 | } 24 | instanceValue := runtime.ToValue(instance).(*goja.Object) 25 | instanceValue.SetPrototype(call.This.Prototype()) 26 | return instanceValue 27 | }) 28 | return runtime.Set("MessageError", messageError) 29 | } 30 | -------------------------------------------------------------------------------- /pkg/download/engine/inject/file/module.go: -------------------------------------------------------------------------------- 1 | package file 2 | 3 | import ( 4 | "errors" 5 | "github.com/dop251/goja" 6 | "io" 7 | ) 8 | 9 | type File struct { 10 | io.Reader `json:""` 11 | io.Closer `json:""` 12 | Name string `json:"name"` 13 | Size int64 `json:"size"` 14 | } 15 | 16 | func NewJsFile(runtime *goja.Runtime) (goja.Value, error) { 17 | fileCtor, ok := goja.AssertConstructor(runtime.Get("File")) 18 | if !ok { 19 | return nil, errors.New("file is not defined") 20 | } 21 | return fileCtor(nil) 22 | } 23 | 24 | func Enable(runtime *goja.Runtime) error { 25 | file := runtime.ToValue(func(call goja.ConstructorCall) *goja.Object { 26 | instance := &File{} 27 | instanceValue := runtime.ToValue(instance).(*goja.Object) 28 | instanceValue.SetPrototype(call.This.Prototype()) 29 | return instanceValue 30 | }) 31 | return runtime.Set("File", file) 32 | } 33 | -------------------------------------------------------------------------------- /pkg/download/engine/inject/formdata/module.go: -------------------------------------------------------------------------------- 1 | package formdata 2 | 3 | import "github.com/dop251/goja" 4 | 5 | type FormData struct { 6 | data map[string]any 7 | } 8 | 9 | func (fd *FormData) Append(name string, value any) { 10 | fd.data[name] = value 11 | } 12 | 13 | func (fd *FormData) Delete(name string) { 14 | delete(fd.data, name) 15 | } 16 | 17 | func (fd *FormData) Entries() []any { 18 | var entries []any 19 | for k, v := range fd.data { 20 | entries = append(entries, []any{k, v}) 21 | } 22 | return entries 23 | } 24 | 25 | func (fd *FormData) Get(name string) any { 26 | return fd.data[name] 27 | } 28 | 29 | func (fd *FormData) GetAll(name string) []any { 30 | return []any{fd.data[name]} 31 | } 32 | 33 | func (fd *FormData) Has(name string) bool { 34 | _, ok := fd.data[name] 35 | return ok 36 | } 37 | 38 | func (fd *FormData) Keys() []string { 39 | var keys []string 40 | for k := range fd.data { 41 | keys = append(keys, k) 42 | } 43 | return keys 44 | } 45 | 46 | func (fd *FormData) Set(name string, value any) { 47 | fd.data[name] = value 48 | } 49 | 50 | func (fd *FormData) Values() []any { 51 | var values []any 52 | for _, v := range fd.data { 53 | values = append(values, v) 54 | } 55 | return values 56 | } 57 | 58 | func Enable(runtime *goja.Runtime) error { 59 | file := runtime.ToValue(func(call goja.ConstructorCall) *goja.Object { 60 | instance := &FormData{ 61 | data: make(map[string]any), 62 | } 63 | instanceValue := runtime.ToValue(instance).(*goja.Object) 64 | instanceValue.SetPrototype(call.This.Prototype()) 65 | return instanceValue 66 | }) 67 | return runtime.Set("FormData", file) 68 | } 69 | -------------------------------------------------------------------------------- /pkg/download/engine/inject/vm/module.go: -------------------------------------------------------------------------------- 1 | package vm 2 | 3 | import ( 4 | "github.com/dop251/goja" 5 | "github.com/dop251/goja_nodejs/eventloop" 6 | ) 7 | 8 | type Vm struct { 9 | loop *eventloop.EventLoop 10 | } 11 | 12 | func (vm *Vm) Set(name string, value any) { 13 | vm.loop.Run(func(runtime *goja.Runtime) { 14 | runtime.Set(name, value) 15 | }) 16 | } 17 | 18 | func (vm *Vm) Get(name string) (value any) { 19 | vm.loop.Run(func(runtime *goja.Runtime) { 20 | value = runtime.Get(name) 21 | }) 22 | return 23 | } 24 | 25 | func (vm *Vm) RunString(script string) (value any, err error) { 26 | defer func() { 27 | if r := recover(); r != nil { 28 | err = r.(error) 29 | } 30 | }() 31 | 32 | vm.loop.Run(func(runtime *goja.Runtime) { 33 | value, err = runtime.RunString(script) 34 | }) 35 | return 36 | } 37 | 38 | func Enable(runtime *goja.Runtime) error { 39 | return runtime.Set("__gopeed_create_vm", func(call goja.FunctionCall) goja.Value { 40 | return runtime.ToValue(&Vm{ 41 | loop: eventloop.NewEventLoop(), 42 | }) 43 | }) 44 | } 45 | -------------------------------------------------------------------------------- /pkg/download/engine/polyfill/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "polyfill", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "type": "module", 7 | "scripts": { 8 | "test": "echo \"Error: no test specified\" && exit 1", 9 | "dev": "webpack --mode production --watch", 10 | "build": "webpack --mode production", 11 | "postinstall": "patch-package" 12 | }, 13 | "author": "", 14 | "license": "ISC", 15 | "devDependencies": { 16 | "patch-package": "^8.0.0", 17 | "webpack": "^5.75.0", 18 | "webpack-cli": "^5.0.1" 19 | }, 20 | "dependencies": { 21 | "blob-polyfill": "^7.0.20220408", 22 | "fastestsmallesttextencoderdecoder": "^1.0.22", 23 | "whatwg-fetch": "^3.6.20" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /pkg/download/engine/polyfill/src/blob/index.js: -------------------------------------------------------------------------------- 1 | import { Blob } from "blob-polyfill"; 2 | 3 | globalThis.Blob = Blob; -------------------------------------------------------------------------------- /pkg/download/engine/polyfill/src/crypto/index.js: -------------------------------------------------------------------------------- 1 | globalThis.crypto = { 2 | getRandomValues(arr) { 3 | for (let i = 0, len = arr.length; i < len; i++) { 4 | arr[i] = Math.floor(Math.random() * 256); 5 | } 6 | return arr; 7 | }, 8 | randomUUID() { 9 | return '10000000-1000-4000-8000-100000000000'.replace(/[018]/g, (c) => { 10 | return (c ^ (this.getRandomValues(new Uint8Array(1))[0] & (15 >> (c / 4)))).toString(16) 11 | }) 12 | } 13 | } -------------------------------------------------------------------------------- /pkg/download/engine/polyfill/src/fetch/index.js: -------------------------------------------------------------------------------- 1 | import 'whatwg-fetch' -------------------------------------------------------------------------------- /pkg/download/engine/polyfill/src/index.js: -------------------------------------------------------------------------------- 1 | import "./blob/index.js" 2 | import "./crypto/index.js" 3 | // polyfill TextEncoder 4 | import 'fastestsmallesttextencoderdecoder'; 5 | import "./fetch/index.js" -------------------------------------------------------------------------------- /pkg/download/engine/polyfill/webpack.config.js: -------------------------------------------------------------------------------- 1 | import path from "path"; 2 | import { fileURLToPath } from "url"; 3 | 4 | const __dirname = fileURLToPath(import.meta.url); 5 | 6 | export default { 7 | entry: "./src/index.js", 8 | output: { 9 | filename: "index.js", 10 | path: path.resolve(__dirname, "../out"), 11 | }, 12 | }; 13 | -------------------------------------------------------------------------------- /pkg/download/engine/util/util.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "github.com/dop251/goja" 5 | ) 6 | 7 | func ThrowTypeError(vm *goja.Runtime, msg string) { 8 | panic(vm.NewTypeError(msg)) 9 | } 10 | 11 | func AssertError[T error](err error) (t T, r bool) { 12 | if err == nil { 13 | return 14 | } 15 | if e, ok := err.(T); ok { 16 | return e, true 17 | } 18 | if e, ok := err.(*goja.Exception); ok { 19 | if ee, okk := e.Value().Export().(T); okk { 20 | return ee, true 21 | } 22 | } 23 | return 24 | } 25 | -------------------------------------------------------------------------------- /pkg/download/event.go: -------------------------------------------------------------------------------- 1 | package download 2 | 3 | type EventKey string 4 | 5 | const ( 6 | EventKeyStart = "start" 7 | EventKeyPause = "pause" 8 | EventKeyProgress = "progress" 9 | EventKeyError = "error" 10 | EventKeyDelete = "delete" 11 | EventKeyDone = "done" 12 | EventKeyFinally = "finally" 13 | ) 14 | 15 | type Event struct { 16 | Key EventKey 17 | Task *Task 18 | Err error 19 | } 20 | -------------------------------------------------------------------------------- /pkg/download/testdata/extensions/basic/index.js: -------------------------------------------------------------------------------- 1 | gopeed.events.onResolve(async function (ctx) { 2 | ctx.res = { 3 | name: "test", 4 | files: Array(2).fill(true).map((_, i) => ({ 5 | name: `test-${i}.txt`, 6 | size: 1024, 7 | req: { 8 | url: ctx.req.url + "/" + i, 9 | labels:{ 10 | "from": gopeed.info.name, 11 | } 12 | } 13 | }), 14 | ), 15 | }; 16 | }); 17 | -------------------------------------------------------------------------------- /pkg/download/testdata/extensions/basic/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "basic", 3 | "title": "gopeed extension basic test", 4 | "version": "0.0.1", 5 | "scripts": [ 6 | { 7 | "event": "onResolve", 8 | "match": { 9 | "urls": [ 10 | "*://github.com/*" 11 | ] 12 | }, 13 | "entry": "index.js" 14 | } 15 | ], 16 | "settings": [ 17 | { 18 | "name": "ua", 19 | "title": "User-Agent", 20 | "type": "string" 21 | } 22 | ] 23 | } -------------------------------------------------------------------------------- /pkg/download/testdata/extensions/extra/index.js: -------------------------------------------------------------------------------- 1 | gopeed.events.onResolve(async function (ctx) { 2 | ctx.res = { 3 | name: "test", 4 | files: Array(2).fill(true).map((_, i) => ({ 5 | name: `test-${i}.txt`, 6 | size: 1024, 7 | req: { 8 | url: ctx.req.url + "/" + i, 9 | extra: { 10 | headers: { 11 | 'User-Agent': ctx.settings.ua, 12 | }, 13 | } 14 | } 15 | }), 16 | ), 17 | }; 18 | }); 19 | -------------------------------------------------------------------------------- /pkg/download/testdata/extensions/extra/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "extra", 3 | "title": "gopeed extension extra test", 4 | "version": "0.0.1", 5 | "scripts": [ 6 | { 7 | "event": "onResolve", 8 | "matches": [ 9 | "*://github.com/*" 10 | ], 11 | "entry": "index.js" 12 | } 13 | ], 14 | "settings": [ 15 | { 16 | "name": "ua", 17 | "title": "User-Agent", 18 | "type": "string", 19 | "value": "gopeed" 20 | } 21 | ] 22 | } -------------------------------------------------------------------------------- /pkg/download/testdata/extensions/function_error/index.js: -------------------------------------------------------------------------------- 1 | gopeed.events.onResolve(async function (ctx) { 2 | const aaa = {}; 3 | // access undefined property 4 | gopeed.logger.info(aaa.bbb.ccc); 5 | 6 | ctx.res = { 7 | name: "test", 8 | files: Array(2).fill(true).map((_, i) => ({ 9 | name: `test-${i}.txt`, 10 | size: 1024, 11 | req: { 12 | url: ctx.req.url + "/" + i, 13 | } 14 | }), 15 | ), 16 | }; 17 | }); 18 | -------------------------------------------------------------------------------- /pkg/download/testdata/extensions/function_error/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "function-error", 3 | "title": "gopeed extension function error test", 4 | "version": "0.0.1", 5 | "scripts": [ 6 | { 7 | "event": "onResolve", 8 | "match": { 9 | "urls": [ 10 | "*://github.com/*" 11 | ] 12 | }, 13 | "entry": "index.js" 14 | } 15 | ], 16 | "settings": [ 17 | { 18 | "name": "ua", 19 | "title": "User-Agent", 20 | "type": "string" 21 | } 22 | ] 23 | } -------------------------------------------------------------------------------- /pkg/download/testdata/extensions/message_error/index.js: -------------------------------------------------------------------------------- 1 | gopeed.events.onResolve(async function (ctx) { 2 | throw new MessageError("test"); 3 | }); 4 | -------------------------------------------------------------------------------- /pkg/download/testdata/extensions/message_error/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "message-error", 3 | "title": "gopeed extension message error test", 4 | "version": "0.0.1", 5 | "scripts": [ 6 | { 7 | "event": "onResolve", 8 | "match": { 9 | "urls": [ 10 | "*://github.com/*" 11 | ] 12 | }, 13 | "entry": "index.js" 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /pkg/download/testdata/extensions/on_done/index.js: -------------------------------------------------------------------------------- 1 | gopeed.events.onDone(async function (ctx) { 2 | gopeed.logger.info("url", ctx.task.meta.req.url); 3 | ctx.task.meta.req.labels['modified'] = 'true'; 4 | }); 5 | 6 | -------------------------------------------------------------------------------- /pkg/download/testdata/extensions/on_done/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "on-done", 3 | "title": "gopeed extension on done event test", 4 | "version": "0.0.1", 5 | "scripts": [ 6 | { 7 | "event": "onDone", 8 | "match": { 9 | "urls": [ 10 | "*://github.com/*" 11 | ] 12 | }, 13 | "entry": "index.js" 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /pkg/download/testdata/extensions/on_error/index.js: -------------------------------------------------------------------------------- 1 | gopeed.events.onError(async function (ctx) { 2 | gopeed.logger.info("url", ctx.task.meta.req.url); 3 | gopeed.logger.info("error", ctx.error); 4 | ctx.task.meta.req.url = "https://github.com"; 5 | ctx.task.continue(); 6 | }); 7 | 8 | -------------------------------------------------------------------------------- /pkg/download/testdata/extensions/on_error/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "on-error", 3 | "title": "gopeed extension on error event test", 4 | "version": "0.0.1", 5 | "scripts": [ 6 | { 7 | "event": "onError", 8 | "match": { 9 | "labels": [ 10 | "test" 11 | ] 12 | }, 13 | "entry": "index.js" 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /pkg/download/testdata/extensions/on_start/index.js: -------------------------------------------------------------------------------- 1 | gopeed.events.onStart(async function (ctx) { 2 | gopeed.logger.info("url", ctx.task.meta.req.url); 3 | ctx.task.meta.req.url = "https://github.com"; 4 | ctx.task.meta.req.labels['modified'] = 'true'; 5 | }); 6 | 7 | -------------------------------------------------------------------------------- /pkg/download/testdata/extensions/on_start/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "on-start", 3 | "title": "gopeed extension on start event test", 4 | "version": "0.0.1", 5 | "scripts": [ 6 | { 7 | "event": "onStart", 8 | "match": { 9 | "urls": [ 10 | "*://github.com/*" 11 | ], 12 | "labels": [ 13 | "test" 14 | ] 15 | }, 16 | "entry": "index.js" 17 | } 18 | ] 19 | } -------------------------------------------------------------------------------- /pkg/download/testdata/extensions/script_error/index.js: -------------------------------------------------------------------------------- 1 | const aaa = {}; 2 | gopeed.logger.info(aaa.bbb.ccc); 3 | 4 | gopeed.events.onResolve(async function (ctx) { 5 | ctx.res = { 6 | name: "test", 7 | files: Array(2).fill(true).map((_, i) => ({ 8 | name: `test-${i}.txt`, 9 | size: 1024, 10 | req: { 11 | url: ctx.req.url + "/" + i, 12 | } 13 | }), 14 | ), 15 | }; 16 | }); 17 | -------------------------------------------------------------------------------- /pkg/download/testdata/extensions/script_error/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "script-error", 3 | "title": "gopeed extension script error test", 4 | "version": "0.0.1", 5 | "scripts": [ 6 | { 7 | "event": "onResolve", 8 | "match": { 9 | "urls": [ 10 | "*://github.com/*" 11 | ] 12 | }, 13 | "entry": "index.js" 14 | } 15 | ], 16 | "settings": [ 17 | { 18 | "name": "ua", 19 | "title": "User-Agent", 20 | "type": "string" 21 | } 22 | ] 23 | } -------------------------------------------------------------------------------- /pkg/download/testdata/extensions/settings_all/index.js: -------------------------------------------------------------------------------- 1 | gopeed.events.onResolve(async function (ctx) { 2 | if (gopeed.settings.string != null) { 3 | throw new Error("string is not null"); 4 | } 5 | if (gopeed.settings.number != null) { 6 | throw new Error("number is not null"); 7 | } 8 | if (gopeed.settings.boolean != null) { 9 | throw new Error("boolean is not null"); 10 | } 11 | 12 | if (gopeed.settings.stringDefault !== "default") { 13 | throw new Error("string default value is incorrect"); 14 | } 15 | if (gopeed.settings.numberDefault !== 1) { 16 | throw new Error("number default value is incorrect"); 17 | } 18 | if (gopeed.settings.booleanDefault !== true) { 19 | throw new Error("boolean default value is incorrect"); 20 | } 21 | 22 | if (gopeed.settings.stringValued !== "valued") { 23 | throw new Error("string value is incorrect"); 24 | } 25 | if (gopeed.settings.numberValued !== 1.1) { 26 | throw new Error("number value is incorrect"); 27 | } 28 | if (gopeed.settings.booleanValued !== true) { 29 | throw new Error("boolean value is incorrect"); 30 | } 31 | 32 | ctx.res = { 33 | name: "test", 34 | files: Array(2).fill(true).map((_, i) => ({ 35 | name: `test-${i}.txt`, 36 | size: 1024, 37 | req: { 38 | url: ctx.req.url + "/" + i, 39 | } 40 | }), 41 | ), 42 | }; 43 | }); 44 | -------------------------------------------------------------------------------- /pkg/download/testdata/extensions/settings_all/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "settings-all", 3 | "title": "gopeed extension settings all type test", 4 | "version": "0.0.1", 5 | "scripts": [ 6 | { 7 | "event": "onResolve", 8 | "match": { 9 | "urls": [ 10 | "*://*/*" 11 | ] 12 | }, 13 | "entry": "index.js" 14 | } 15 | ], 16 | "settings": [ 17 | { 18 | "name": "string", 19 | "title": "string null test", 20 | "type": "string" 21 | }, 22 | { 23 | "name": "number", 24 | "title": "number null test", 25 | "type": "number" 26 | }, 27 | { 28 | "name": "boolean", 29 | "title": "boolean null test", 30 | "type": "boolean" 31 | }, 32 | { 33 | "name": "stringDefault", 34 | "title": "string default test", 35 | "type": "string", 36 | "value": "default" 37 | }, 38 | { 39 | "name": "numberDefault", 40 | "title": "number default test", 41 | "type": "number", 42 | "value": 1 43 | }, 44 | { 45 | "name": "booleanDefault", 46 | "title": "boolean default test", 47 | "type": "boolean", 48 | "value": true 49 | }, 50 | { 51 | "name": "stringValued", 52 | "title": "string valued test", 53 | "type": "string" 54 | }, 55 | { 56 | "name": "numberValued", 57 | "title": "number valued test", 58 | "type": "number" 59 | }, 60 | { 61 | "name": "booleanValued", 62 | "title": "boolean valued test", 63 | "type": "boolean" 64 | } 65 | ] 66 | } -------------------------------------------------------------------------------- /pkg/download/testdata/extensions/settings_empty/index.js: -------------------------------------------------------------------------------- 1 | gopeed.events.onResolve(async function (ctx) { 2 | if (Object.keys(gopeed.settings).length > 0){ 3 | throw new Error("settings is not empty"); 4 | } 5 | 6 | ctx.res = { 7 | name: "test", 8 | files: Array(2).fill(true).map((_, i) => ({ 9 | name: `test-${i}.txt`, 10 | size: 1024, 11 | req: { 12 | url: ctx.req.url + "/" + i, 13 | } 14 | }), 15 | ), 16 | }; 17 | }); 18 | -------------------------------------------------------------------------------- /pkg/download/testdata/extensions/settings_empty/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "settings-empty", 3 | "title": "gopeed extension settings empty test", 4 | "version": "0.0.1", 5 | "scripts": [ 6 | { 7 | "event": "onResolve", 8 | "match": { 9 | "urls": [ 10 | "*://*/*" 11 | ] 12 | }, 13 | "entry": "index.js" 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /pkg/download/testdata/extensions/storage/index.js: -------------------------------------------------------------------------------- 1 | gopeed.events.onResolve(async function (ctx) { 2 | const key = "key" 3 | const value1 = "value1", value2 = JSON.stringify({a: 1, b: "2"}) 4 | 5 | if (gopeed.storage.get(key) !== null) { 6 | throw new Error("storage get null error") 7 | } 8 | gopeed.storage.remove(key) 9 | if(gopeed.storage.keys().length !== 0) { 10 | throw new Error("storage keys null error") 11 | } 12 | 13 | gopeed.storage.set(key, value1) 14 | if (gopeed.storage.get(key) !== value1) { 15 | throw new Error("storage put1 error") 16 | } 17 | 18 | gopeed.storage.set(key, value2) 19 | if (gopeed.storage.get(key) !== value2) { 20 | throw new Error("storage put2 error") 21 | } 22 | 23 | if(gopeed.storage.keys().length !== 1) { 24 | throw new Error("storage keys error") 25 | } 26 | 27 | gopeed.storage.remove(key) 28 | if (gopeed.storage.get(key) !== null) { 29 | throw new Error("storage delete error") 30 | } 31 | 32 | gopeed.storage.set(key, value1) 33 | gopeed.storage.clear() 34 | if (gopeed.storage.get(key) !== null) { 35 | throw new Error("storage clear error") 36 | } 37 | 38 | ctx.res = { 39 | name: "test", 40 | files: Array(2).fill(true).map((_, i) => ({ 41 | name: `test-${i}.txt`, 42 | size: 1024, 43 | req: { 44 | url: ctx.req.url + "/" + i, 45 | } 46 | }), 47 | ), 48 | }; 49 | }); 50 | -------------------------------------------------------------------------------- /pkg/download/testdata/extensions/storage/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "storage", 3 | "title": "gopeed extension storage test", 4 | "version": "0.0.1", 5 | "scripts": [ 6 | { 7 | "event": "onResolve", 8 | "match": { 9 | "urls": [ 10 | "*://*/*" 11 | ] 12 | }, 13 | "entry": "index.js" 14 | } 15 | ], 16 | "settings": [ 17 | { 18 | "name": "ua", 19 | "title": "User-Agent", 20 | "type": "string" 21 | } 22 | ] 23 | } -------------------------------------------------------------------------------- /pkg/download/testdata/extensions/update/index.js: -------------------------------------------------------------------------------- 1 | gopeed.events.onResolve(async function (ctx) { 2 | // do nothing for test 3 | }); 4 | -------------------------------------------------------------------------------- /pkg/download/testdata/extensions/update/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "extension-test", 3 | "author": "gopeed", 4 | "title": "Gopeed Extension Test", 5 | "description": "Test extension settings and upgrade", 6 | "version": "0.0.1", 7 | "homepage": "https://gopeed.com", 8 | "repository": { 9 | "url": "https://github.com/GopeedLab/gopeed-extension-samples", 10 | "directory": "extension-test" 11 | }, 12 | "scripts": [ 13 | { 14 | "event": "onResolve", 15 | "match": { 16 | "urls": [ 17 | "*://*/*" 18 | ] 19 | }, 20 | "entry": "index.js" 21 | } 22 | ], 23 | "settings": [ 24 | { 25 | "name": "s1", 26 | "title": "S1 old", 27 | "description": "Test setting update", 28 | "type": "string", 29 | "required": true 30 | }, 31 | { 32 | "name": "s2", 33 | "title": "s2 number old", 34 | "description": "Test setting type update", 35 | "type": "number", 36 | "required": true, 37 | "value": 1 38 | }, 39 | { 40 | "name": "d1", 41 | "title": "Delete test", 42 | "description": "Test setting delete", 43 | "type": "string", 44 | "required": true 45 | } 46 | ] 47 | } -------------------------------------------------------------------------------- /pkg/protocol/bt/model.go: -------------------------------------------------------------------------------- 1 | package bt 2 | 3 | type ReqExtra struct { 4 | Trackers []string `json:"trackers"` 5 | } 6 | 7 | // Stats for torrent 8 | type Stats struct { 9 | // health indicators of torrents, from large to small, ConnectedSeeders are also the key to the health of seed resources 10 | TotalPeers int `json:"totalPeers"` 11 | ActivePeers int `json:"activePeers"` 12 | ConnectedSeeders int `json:"connectedSeeders"` 13 | // Total seed bytes 14 | SeedBytes int64 `json:"seedBytes"` 15 | // Seed ratio 16 | SeedRatio float64 `json:"seedRatio"` 17 | // Total seed time 18 | SeedTime int64 `json:"seedTime"` 19 | } 20 | -------------------------------------------------------------------------------- /pkg/protocol/http/model.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | type ReqExtra struct { 4 | Method string `json:"method"` 5 | Header map[string]string `json:"header"` 6 | Body string `json:"body"` 7 | } 8 | 9 | type OptsExtra struct { 10 | Connections int `json:"connections"` 11 | // AutoTorrent when task download complete, and it is a .torrent file, it will be auto create a new task for the torrent file 12 | AutoTorrent bool `json:"autoTorrent"` 13 | } 14 | 15 | // Stats for download 16 | type Stats struct { 17 | Connections []*StatsConnection `json:"connections"` 18 | } 19 | 20 | type StatsConnection struct { 21 | Downloaded int64 `json:"downloaded"` 22 | Completed bool `json:"completed"` 23 | Failed bool `json:"failed"` 24 | RetryTimes int `json:"retryTimes"` 25 | } 26 | -------------------------------------------------------------------------------- /pkg/rest/config.go: -------------------------------------------------------------------------------- 1 | package rest 2 | 3 | type Config struct { 4 | Host string `json:"host"` 5 | Port int `json:"port"` 6 | } 7 | -------------------------------------------------------------------------------- /pkg/rest/gizp_middleware.go: -------------------------------------------------------------------------------- 1 | package rest 2 | 3 | import ( 4 | "compress/gzip" 5 | "io" 6 | "net/http" 7 | "strings" 8 | ) 9 | 10 | type gzipResponseWriter struct { 11 | io.Writer 12 | http.ResponseWriter 13 | } 14 | 15 | func (g gzipResponseWriter) Write(b []byte) (int, error) { 16 | return g.Writer.Write(b) 17 | } 18 | 19 | func gzipMiddleware(next http.Handler) http.Handler { 20 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 21 | if !strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") { 22 | next.ServeHTTP(w, r) 23 | return 24 | } 25 | 26 | w.Header().Set("Content-Encoding", "gzip") 27 | w.Header().Add("Vary", "Accept-Encoding") 28 | 29 | gz := gzip.NewWriter(w) 30 | defer gz.Close() 31 | 32 | next.ServeHTTP(gzipResponseWriter{Writer: gz, ResponseWriter: w}, r) 33 | }) 34 | } 35 | -------------------------------------------------------------------------------- /pkg/rest/model/extension.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | type InstallExtension struct { 4 | DevMode bool `json:"devMode"` 5 | URL string `json:"url"` 6 | } 7 | 8 | type UpdateExtensionSettings struct { 9 | Settings map[string]any `json:"settings"` 10 | } 11 | 12 | type SwitchExtension struct { 13 | Status bool `json:"status"` 14 | } 15 | 16 | type UpdateCheckExtensionResp struct { 17 | NewVersion string `json:"newVersion"` 18 | } 19 | -------------------------------------------------------------------------------- /pkg/rest/model/result.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | type RespCode int 4 | 5 | const ( 6 | CodeOk RespCode = 0 7 | // CodeError is the common error code 8 | CodeError RespCode = 1000 9 | // CodeUnauthorized is the error code for unauthorized 10 | CodeUnauthorized RespCode = 1001 11 | // CodeInvalidParam is the error code for invalid parameter 12 | CodeInvalidParam RespCode = 1002 13 | // CodeTaskNotFound is the error code for task not found 14 | CodeTaskNotFound RespCode = 2001 15 | ) 16 | 17 | type Result[T any] struct { 18 | Code RespCode `json:"code"` 19 | Msg string `json:"msg"` 20 | Data T `json:"data"` 21 | } 22 | 23 | func NewOkResult[T any](data T) *Result[T] { 24 | return &Result[T]{ 25 | Code: CodeOk, 26 | Data: data, 27 | } 28 | } 29 | 30 | func NewNilResult() *Result[any] { 31 | return &Result[any]{ 32 | Code: CodeOk, 33 | } 34 | } 35 | 36 | func NewErrorResult(msg string, code ...RespCode) *Result[any] { 37 | // if code is not provided, the default code is CodeError 38 | c := CodeError 39 | if len(code) > 0 { 40 | c = code[0] 41 | } 42 | 43 | return &Result[any]{ 44 | Code: c, 45 | Msg: msg, 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /pkg/rest/model/server.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "encoding/base64" 5 | "github.com/GopeedLab/gopeed/pkg/base" 6 | "io/fs" 7 | ) 8 | 9 | type Storage string 10 | 11 | const ( 12 | StorageMem Storage = "mem" 13 | StorageBolt Storage = "bolt" 14 | ) 15 | 16 | type StartConfig struct { 17 | Network string `json:"network"` 18 | Address string `json:"address"` 19 | RefreshInterval int `json:"refreshInterval"` 20 | Storage Storage `json:"storage"` 21 | StorageDir string `json:"storageDir"` 22 | WhiteDownloadDirs []string `json:"whiteDownloadDirs"` 23 | ApiToken string `json:"apiToken"` 24 | DownloadConfig *base.DownloaderStoreConfig `json:"downloadConfig"` 25 | 26 | ProductionMode bool 27 | 28 | WebEnable bool 29 | WebFS fs.FS 30 | WebBasicAuth *WebBasicAuth 31 | } 32 | 33 | func (cfg *StartConfig) Init() *StartConfig { 34 | if cfg.Network == "" { 35 | cfg.Network = "tcp" 36 | } 37 | if cfg.Address == "" { 38 | cfg.Address = "127.0.0.1:0" 39 | } 40 | if cfg.RefreshInterval == 0 { 41 | cfg.RefreshInterval = 350 42 | } 43 | if cfg.Storage == "" { 44 | cfg.Storage = StorageBolt 45 | } 46 | if cfg.StorageDir == "" { 47 | cfg.StorageDir = "./" 48 | } 49 | return cfg 50 | } 51 | 52 | type WebBasicAuth struct { 53 | Username string 54 | Password string 55 | } 56 | 57 | // Authorization returns the value of the Authorization header to be used in HTTP requests. 58 | func (cfg *WebBasicAuth) Authorization() string { 59 | userId := cfg.Username + ":" + cfg.Password 60 | return "Basic " + base64.StdEncoding.EncodeToString([]byte(userId)) 61 | } 62 | -------------------------------------------------------------------------------- /pkg/rest/model/task.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import "github.com/GopeedLab/gopeed/pkg/base" 4 | 5 | type CreateTask struct { 6 | Rid string `json:"rid"` 7 | Req *base.Request `json:"req"` 8 | Opt *base.Options `json:"opt"` 9 | } 10 | -------------------------------------------------------------------------------- /pkg/util/bytefmt.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | ) 7 | 8 | var unitArr = []string{"B", "KB", "MB", "GB", "TB", "PB", "EB"} 9 | 10 | func ByteFmt(size int64) string { 11 | if size == 0 { 12 | return "unknown" 13 | } 14 | fs := float64(size) 15 | p := int(math.Log(fs) / math.Log(1024)) 16 | val := fs / math.Pow(1024, float64(p)) 17 | _, frac := math.Modf(val) 18 | if frac > 0 { 19 | return fmt.Sprintf("%.1f%s", math.Floor(val*10)/10, unitArr[p]) 20 | } else { 21 | return fmt.Sprintf("%d%s", int(val), unitArr[p]) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /pkg/util/bytefmt_test.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import "testing" 4 | 5 | func TestByteFmt(t *testing.T) { 6 | type args struct { 7 | size int64 8 | } 9 | tests := []struct { 10 | name string 11 | args args 12 | want string 13 | }{ 14 | { 15 | name: "unknown", 16 | args: args{size: int64(0)}, 17 | want: "unknown", 18 | }, 19 | { 20 | name: "100B", 21 | args: args{size: int64(100)}, 22 | want: "100B", 23 | }, 24 | { 25 | name: "1KB", 26 | args: args{size: int64(1024)}, 27 | want: "1KB", 28 | }, 29 | { 30 | name: "1.9KB", 31 | args: args{size: int64(1024*2 - 1)}, 32 | want: "1.9KB", 33 | }, 34 | { 35 | name: "2KB", 36 | args: args{size: int64(1024 * 2)}, 37 | want: "2KB", 38 | }, 39 | { 40 | name: "1MB", 41 | args: args{size: int64(1024 * 1024)}, 42 | want: "1MB", 43 | }, 44 | { 45 | name: "1.9MB", 46 | args: args{size: int64(1024*1024*2 - 1)}, 47 | want: "1.9MB", 48 | }, 49 | { 50 | name: "2MB", 51 | args: args{size: int64(1024 * 1024 * 2)}, 52 | want: "2MB", 53 | }, 54 | } 55 | for _, tt := range tests { 56 | t.Run(tt.name, func(t *testing.T) { 57 | if got := ByteFmt(tt.args.size); got != tt.want { 58 | t.Errorf("ByteFmt() = %v, want %v", got, tt.want) 59 | } 60 | }) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /pkg/util/json.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import "encoding/json" 4 | 5 | func MapToStruct(s any, v any) error { 6 | if s == nil { 7 | return nil 8 | } 9 | b, err := json.Marshal(s) 10 | if err != nil { 11 | return err 12 | } 13 | return json.Unmarshal(b, v) 14 | } 15 | 16 | func DeepClone[T any](v *T) *T { 17 | if v == nil { 18 | return nil 19 | } 20 | 21 | var t T 22 | b, err := json.Marshal(v) 23 | if err != nil { 24 | return &t 25 | } 26 | json.Unmarshal(b, &t) 27 | return &t 28 | } 29 | -------------------------------------------------------------------------------- /pkg/util/json_test.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestDeepClone(t *testing.T) { 9 | type user struct { 10 | Name string `json:"name"` 11 | Age int `json:"age"` 12 | 13 | v int 14 | } 15 | 16 | type args[T any] struct { 17 | v *T 18 | } 19 | type testCase[T any] struct { 20 | name string 21 | args args[T] 22 | want *T 23 | } 24 | tests := []testCase[user]{ 25 | { 26 | name: "case 1", 27 | args: args[user]{ 28 | v: &user{ 29 | Name: "test", 30 | Age: 10, 31 | }, 32 | }, 33 | want: &user{ 34 | Name: "test", 35 | Age: 10, 36 | }, 37 | }, 38 | { 39 | name: "case 2", 40 | args: args[user]{ 41 | v: &user{ 42 | Name: "test", 43 | Age: 10, 44 | v: 1, 45 | }, 46 | }, 47 | want: &user{ 48 | Name: "test", 49 | Age: 10, 50 | }, 51 | }, 52 | } 53 | for _, tt := range tests { 54 | t.Run(tt.name, func(t *testing.T) { 55 | if got := DeepClone(tt.args.v); !reflect.DeepEqual(got, tt.want) { 56 | t.Errorf("DeepClone() = %v, want %v", got, tt.want) 57 | } 58 | }) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /pkg/util/matcher.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "net/url" 5 | "regexp" 6 | "strings" 7 | ) 8 | 9 | // Match url with pattern by chrome extension match pattern style 10 | // https://developer.chrome.com/docs/extensions/mv3/match_patterns/ 11 | func Match(pattern string, u string) bool { 12 | scheme, host, path := parsePattern(pattern) 13 | url, err := url.Parse(u) 14 | if err != nil { 15 | return false 16 | } 17 | if scheme != "*" && scheme != url.Scheme { 18 | return false 19 | } 20 | if !matchHost(host, url.Hostname()) { 21 | return false 22 | } 23 | if !matchPath(path, url.Path) { 24 | return false 25 | } 26 | return true 27 | } 28 | 29 | func parsePattern(pattern string) (scheme string, host string, path string) { 30 | parts := strings.Split(pattern, "://") 31 | if len(parts) == 2 { 32 | scheme = parts[0] 33 | pattern = parts[1] 34 | } else { 35 | scheme = "" 36 | } 37 | parts = strings.SplitN(pattern, "/", 2) 38 | if len(parts) == 2 { 39 | host = parts[0] 40 | path = "/" + parts[1] 41 | } else { 42 | host = pattern 43 | path = "/" 44 | } 45 | return 46 | } 47 | 48 | func matchHost(pattern string, host string) bool { 49 | if pattern == "*" { 50 | return true 51 | } 52 | if strings.HasPrefix(pattern, "*.") { 53 | return strings.HasSuffix(host, pattern[1:]) 54 | } 55 | return pattern == host 56 | } 57 | 58 | func matchPath(pattern string, path string) bool { 59 | if pattern == "*" { 60 | return true 61 | } 62 | if !strings.HasSuffix(pattern, "*") && !strings.HasSuffix(pattern, "/") { 63 | pattern += "/" 64 | } 65 | if !strings.HasSuffix(path, "/") { 66 | path += "/" 67 | } 68 | 69 | if strings.Contains(pattern, "*") { 70 | pattern = strings.Replace(pattern, "*", ".*", -1) 71 | matched, _ := regexp.MatchString("^"+pattern+"$", path) 72 | return matched 73 | } 74 | return pattern == path 75 | } 76 | -------------------------------------------------------------------------------- /pkg/util/path_other.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | // +build !windows 3 | 4 | package util 5 | 6 | var invalidPathChars = []string{`/`, `:`} 7 | -------------------------------------------------------------------------------- /pkg/util/path_windows.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | var invalidPathChars = []string{`\`, `/`, `:`, `*`, `?`, `"`, `<`, `>`, `|`} 4 | -------------------------------------------------------------------------------- /pkg/util/timer.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import "time" 4 | 5 | type Timer struct { 6 | t int64 7 | used int64 8 | } 9 | 10 | func NewTimer(used int64) *Timer { 11 | return &Timer{ 12 | used: used, 13 | } 14 | } 15 | 16 | func (t *Timer) Start() { 17 | t.t = time.Now().UnixNano() 18 | } 19 | 20 | func (t *Timer) Pause() { 21 | t.used += time.Now().UnixNano() - t.t 22 | } 23 | 24 | func (t *Timer) Used() int64 { 25 | return (time.Now().UnixNano() - t.t) + t.used 26 | } 27 | -------------------------------------------------------------------------------- /pkg/util/url.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "encoding/base64" 5 | "net/http" 6 | "net/url" 7 | "regexp" 8 | "strings" 9 | ) 10 | 11 | func ParseSchema(url string) string { 12 | index := strings.Index(url, ":") 13 | if index == -1 || index == 1 { 14 | return "" 15 | } 16 | schema := url[:index] 17 | return strings.ToUpper(schema) 18 | } 19 | 20 | // ParseDataUri parses a data URI and returns the MIME type and decode data. 21 | func ParseDataUri(uri string) (string, []byte) { 22 | re := regexp.MustCompile(`^data:(.*);base64,(.*)$`) 23 | matches := re.FindStringSubmatch(uri) 24 | if len(matches) != 3 { 25 | return "", nil 26 | } 27 | mime := matches[1] 28 | base64Data := matches[2] 29 | data, err := base64.StdEncoding.DecodeString(base64Data) 30 | if err != nil { 31 | return "", nil 32 | } 33 | return mime, data 34 | } 35 | 36 | // BuildProxyUrl builds a proxy url with given host, username and password. 37 | func BuildProxyUrl(scheme, host, usr, pwd string) *url.URL { 38 | var user *url.Userinfo 39 | if usr != "" && pwd != "" { 40 | user = url.UserPassword(usr, pwd) 41 | } 42 | return &url.URL{ 43 | Scheme: scheme, 44 | User: user, 45 | Host: host, 46 | } 47 | } 48 | 49 | // ProxyUrlToHandler gets the proxy handler from the proxy url. 50 | func ProxyUrlToHandler(proxyUrl *url.URL) func(*http.Request) (*url.URL, error) { 51 | if proxyUrl == nil { 52 | return nil 53 | } 54 | if proxyUrl.Scheme == "system" { 55 | return http.ProxyFromEnvironment 56 | } 57 | return http.ProxyURL(proxyUrl) 58 | } 59 | -------------------------------------------------------------------------------- /ui/flutter/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | **/ios/Flutter/.last_build_id 26 | .dart_tool/ 27 | .flutter-plugins 28 | .flutter-plugins-dependencies 29 | .packages 30 | .pub-cache/ 31 | .pub/ 32 | build/ 33 | .fvm/ 34 | 35 | # Web related 36 | 37 | # Symbolication related 38 | app.*.symbols 39 | 40 | # Obfuscation related 41 | app.*.map.json 42 | 43 | # Exceptions to above rules. 44 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 45 | 46 | # Libgopeed 47 | windows/libgopeed.h 48 | windows/libgopeed.dll 49 | macos/Frameworks/libgopeed.h 50 | macos/Frameworks/libgopeed.dylib 51 | linux/bundle/lib/libgopeed.h 52 | linux/bundle/lib/libgopeed.dylib 53 | android/app/libs/libgopeed.aar 54 | android/app/libs/libgopeed-sources.jar 55 | 56 | debian/packages 57 | 58 | linux/flutter/generated_plugin_registrant.cc 59 | linux/flutter/generated_plugin_registrant.h 60 | linux/flutter/generated_plugins.cmake 61 | macos/Flutter/GeneratedPluginRegistrant.swift 62 | windows/flutter/generated_plugin_registrant.cc 63 | windows/flutter/generated_plugin_registrant.h 64 | windows/flutter/generated_plugins.cmake 65 | 66 | /extensions 67 | 68 | # Hive database files 69 | database.hive 70 | database.lock 71 | 72 | assets/exec/host 73 | assets/exec/host.exe 74 | assets/exec/updater 75 | assets/exec/updater.exe 76 | -------------------------------------------------------------------------------- /ui/flutter/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled. 5 | 6 | version: 7 | revision: 7048ed95a5ad3e43d697e0c397464193991fc230 8 | channel: stable 9 | 10 | project_type: app 11 | 12 | # Tracks metadata for the flutter migrate command 13 | migration: 14 | platforms: 15 | - platform: root 16 | create_revision: 7048ed95a5ad3e43d697e0c397464193991fc230 17 | base_revision: 7048ed95a5ad3e43d697e0c397464193991fc230 18 | - platform: windows 19 | create_revision: 7048ed95a5ad3e43d697e0c397464193991fc230 20 | base_revision: 7048ed95a5ad3e43d697e0c397464193991fc230 21 | 22 | # User provided section 23 | 24 | # List of Local paths (relative to this file) that should be 25 | # ignored by the migrate tool. 26 | # 27 | # Files that are not part of the templates will be ignored by default. 28 | unmanaged_files: 29 | - 'lib/main.dart' 30 | - 'ios/Runner.xcodeproj/project.pbxproj' 31 | -------------------------------------------------------------------------------- /ui/flutter/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # This file configures the analyzer, which statically analyzes Dart code to 2 | # check for errors, warnings, and lints. 3 | # 4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled 5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be 6 | # invoked from the command line by running `flutter analyze`. 7 | 8 | # The following line activates a set of recommended lints for Flutter apps, 9 | # packages, and plugins designed to encourage good coding practices. 10 | include: package:flutter_lints/flutter.yaml 11 | 12 | linter: 13 | # The lint rules applied to this project can be customized in the 14 | # section below to disable rules from the `package:flutter_lints/flutter.yaml` 15 | # included above or to enable additional rules. A list of all available lints 16 | # and their documentation is published at 17 | # https://dart-lang.github.io/linter/lints/index.html. 18 | # 19 | # Instead of disabling a lint rule for the entire project in the 20 | # section below, it can also be suppressed for a single line of code 21 | # or a specific dart file by using the `// ignore: name_of_lint` and 22 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file 23 | # producing the lint. 24 | rules: 25 | # avoid_print: false # Uncomment to disable the `avoid_print` rule 26 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule 27 | 28 | # Additional information about this file can be found at 29 | # https://dart.dev/guides/language/analysis-options 30 | -------------------------------------------------------------------------------- /ui/flutter/android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app 11 | key.properties 12 | **/*.keystore 13 | **/*.jks 14 | -------------------------------------------------------------------------------- /ui/flutter/android/app/libs/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GopeedLab/gopeed/7c541e40479b5e574a4ab4c40ab4d328ab275377/ui/flutter/android/app/libs/.gitkeep -------------------------------------------------------------------------------- /ui/flutter/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ui/flutter/android/app/src/main/kotlin/com/gopeed/gopeed/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.gopeed.gopeed 2 | 3 | import androidx.annotation.NonNull 4 | import com.gopeed.libgopeed.Libgopeed 5 | import io.flutter.embedding.android.FlutterActivity 6 | import io.flutter.embedding.engine.FlutterEngine 7 | import io.flutter.plugin.common.MethodChannel 8 | import io.flutter.plugin.common.StandardMethodCodec 9 | 10 | class MainActivity : FlutterActivity() { 11 | private val CHANNEL = "gopeed.com/libgopeed" 12 | 13 | override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) { 14 | super.configureFlutterEngine(flutterEngine) 15 | val taskQueue = 16 | flutterEngine.dartExecutor.binaryMessenger.makeBackgroundTaskQueue() 17 | MethodChannel( 18 | flutterEngine.dartExecutor.binaryMessenger, 19 | CHANNEL, 20 | StandardMethodCodec.INSTANCE, 21 | taskQueue 22 | ).setMethodCallHandler { call, result -> 23 | when (call.method) { 24 | "start" -> { 25 | val cfg = call.argument("cfg") 26 | try { 27 | val port = Libgopeed.start(cfg) 28 | result.success(port) 29 | } catch (e: Exception) { 30 | result.error("ERROR", e.message, null) 31 | } 32 | } 33 | "stop" -> { 34 | Libgopeed.stop() 35 | result.success(null) 36 | } 37 | else -> { 38 | result.notImplemented() 39 | } 40 | } 41 | } 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /ui/flutter/android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /ui/flutter/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /ui/flutter/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GopeedLab/gopeed/7c541e40479b5e574a4ab4c40ab4d328ab275377/ui/flutter/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /ui/flutter/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GopeedLab/gopeed/7c541e40479b5e574a4ab4c40ab4d328ab275377/ui/flutter/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /ui/flutter/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GopeedLab/gopeed/7c541e40479b5e574a4ab4c40ab4d328ab275377/ui/flutter/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /ui/flutter/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GopeedLab/gopeed/7c541e40479b5e574a4ab4c40ab4d328ab275377/ui/flutter/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /ui/flutter/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GopeedLab/gopeed/7c541e40479b5e574a4ab4c40ab4d328ab275377/ui/flutter/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /ui/flutter/android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /ui/flutter/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /ui/flutter/android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ui/flutter/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.8.22' 3 | repositories { 4 | google() 5 | mavenCentral() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:7.1.2' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google() 17 | mavenCentral() 18 | } 19 | } 20 | 21 | rootProject.buildDir = '../build' 22 | subprojects { 23 | project.buildDir = "${rootProject.buildDir}/${project.name}" 24 | } 25 | subprojects { 26 | project.evaluationDependsOn(':app') 27 | } 28 | 29 | tasks.register("clean", Delete) { 30 | delete rootProject.buildDir 31 | } 32 | -------------------------------------------------------------------------------- /ui/flutter/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /ui/flutter/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip 7 | -------------------------------------------------------------------------------- /ui/flutter/android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def localPropertiesFile = new File(rootProject.projectDir, "local.properties") 4 | def properties = new Properties() 5 | 6 | assert localPropertiesFile.exists() 7 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } 8 | 9 | def flutterSdkPath = properties.getProperty("flutter.sdk") 10 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 11 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" 12 | -------------------------------------------------------------------------------- /ui/flutter/assets/exec/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GopeedLab/gopeed/7c541e40479b5e574a4ab4c40ab4d328ab275377/ui/flutter/assets/exec/.gitkeep -------------------------------------------------------------------------------- /ui/flutter/assets/extension/default_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GopeedLab/gopeed/7c541e40479b5e574a4ab4c40ab4d328ab275377/ui/flutter/assets/extension/default_icon.png -------------------------------------------------------------------------------- /ui/flutter/assets/fonts/Gopeed.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GopeedLab/gopeed/7c541e40479b5e574a4ab4c40ab4d328ab275377/ui/flutter/assets/fonts/Gopeed.ttf -------------------------------------------------------------------------------- /ui/flutter/assets/icon/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GopeedLab/gopeed/7c541e40479b5e574a4ab4c40ab4d328ab275377/ui/flutter/assets/icon/icon.ico -------------------------------------------------------------------------------- /ui/flutter/assets/icon/icon_1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GopeedLab/gopeed/7c541e40479b5e574a4ab4c40ab4d328ab275377/ui/flutter/assets/icon/icon_1024.png -------------------------------------------------------------------------------- /ui/flutter/assets/icon/icon_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GopeedLab/gopeed/7c541e40479b5e574a4ab4c40ab4d328ab275377/ui/flutter/assets/icon/icon_512.png -------------------------------------------------------------------------------- /ui/flutter/assets/icon/icon_macos_1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GopeedLab/gopeed/7c541e40479b5e574a4ab4c40ab4d328ab275377/ui/flutter/assets/icon/icon_macos_1024.png -------------------------------------------------------------------------------- /ui/flutter/assets/tray_icon/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GopeedLab/gopeed/7c541e40479b5e574a4ab4c40ab4d328ab275377/ui/flutter/assets/tray_icon/icon.ico -------------------------------------------------------------------------------- /ui/flutter/assets/tray_icon/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GopeedLab/gopeed/7c541e40479b5e574a4ab4c40ab4d328ab275377/ui/flutter/assets/tray_icon/icon.png -------------------------------------------------------------------------------- /ui/flutter/assets/tray_icon/icon_mac.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GopeedLab/gopeed/7c541e40479b5e574a4ab4c40ab4d328ab275377/ui/flutter/assets/tray_icon/icon_mac.png -------------------------------------------------------------------------------- /ui/flutter/build.yaml: -------------------------------------------------------------------------------- 1 | targets: 2 | $default: 3 | builders: 4 | json_serializable: 5 | options: 6 | # Options configure how source code is generated for every 7 | # `@JsonSerializable`-annotated class in the package. 8 | # 9 | # The default value for each is listed. 10 | any_map: false 11 | checked: false 12 | create_factory: true 13 | create_to_json: true 14 | disallow_unrecognized_keys: false 15 | explicit_to_json: false 16 | field_rename: none 17 | generic_argument_factories: false 18 | ignore_unannotated: false 19 | include_if_null: false -------------------------------------------------------------------------------- /ui/flutter/distribute_options.yaml: -------------------------------------------------------------------------------- 1 | output: dist/ -------------------------------------------------------------------------------- /ui/flutter/ios/.gitignore: -------------------------------------------------------------------------------- 1 | **/dgph 2 | *.mode1v3 3 | *.mode2v3 4 | *.moved-aside 5 | *.pbxuser 6 | *.perspectivev3 7 | **/*sync/ 8 | .sconsign.dblite 9 | .tags* 10 | **/.vagrant/ 11 | **/DerivedData/ 12 | Icon? 13 | **/Pods/ 14 | **/.symlinks/ 15 | profile 16 | xcuserdata 17 | **/.generated/ 18 | Flutter/App.framework 19 | Flutter/Flutter.framework 20 | Flutter/Flutter.podspec 21 | Flutter/Generated.xcconfig 22 | Flutter/ephemeral/ 23 | Flutter/app.flx 24 | Flutter/app.zip 25 | Flutter/flutter_assets/ 26 | Flutter/flutter_export_environment.sh 27 | ServiceDefinitions.json 28 | Runner/GeneratedPluginRegistrant.* 29 | 30 | # Exceptions to above rules. 31 | !default.mode1v3 32 | !default.mode2v3 33 | !default.pbxuser 34 | !default.perspectivev3 35 | -------------------------------------------------------------------------------- /ui/flutter/ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 11.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /ui/flutter/ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ui/flutter/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ui/flutter/ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | # platform :ios, '11.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | project 'Runner', { 8 | 'Debug' => :debug, 9 | 'Profile' => :release, 10 | 'Release' => :release, 11 | } 12 | 13 | def flutter_root 14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) 15 | unless File.exist?(generated_xcode_build_settings_path) 16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" 17 | end 18 | 19 | File.foreach(generated_xcode_build_settings_path) do |line| 20 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 21 | return matches[1].strip if matches 22 | end 23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" 24 | end 25 | 26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 27 | 28 | flutter_ios_podfile_setup 29 | 30 | target 'Runner' do 31 | use_frameworks! 32 | use_modular_headers! 33 | 34 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) 35 | 36 | # share_handler addition start 37 | target 'ShareExtension' do 38 | inherit! :search_paths 39 | pod "share_handler_ios_models", :path => ".symlinks/plugins/share_handler_ios/ios/Models" 40 | end 41 | # share_handler addition end 42 | end 43 | 44 | post_install do |installer| 45 | installer.pods_project.targets.each do |target| 46 | flutter_additional_ios_build_settings(target) 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /ui/flutter/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ui/flutter/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ui/flutter/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ui/flutter/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ui/flutter/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ui/flutter/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GopeedLab/gopeed/7c541e40479b5e574a4ab4c40ab4d328ab275377/ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GopeedLab/gopeed/7c541e40479b5e574a4ab4c40ab4d328ab275377/ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GopeedLab/gopeed/7c541e40479b5e574a4ab4c40ab4d328ab275377/ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GopeedLab/gopeed/7c541e40479b5e574a4ab4c40ab4d328ab275377/ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GopeedLab/gopeed/7c541e40479b5e574a4ab4c40ab4d328ab275377/ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GopeedLab/gopeed/7c541e40479b5e574a4ab4c40ab4d328ab275377/ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GopeedLab/gopeed/7c541e40479b5e574a4ab4c40ab4d328ab275377/ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GopeedLab/gopeed/7c541e40479b5e574a4ab4c40ab4d328ab275377/ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GopeedLab/gopeed/7c541e40479b5e574a4ab4c40ab4d328ab275377/ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GopeedLab/gopeed/7c541e40479b5e574a4ab4c40ab4d328ab275377/ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GopeedLab/gopeed/7c541e40479b5e574a4ab4c40ab4d328ab275377/ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png -------------------------------------------------------------------------------- /ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GopeedLab/gopeed/7c541e40479b5e574a4ab4c40ab4d328ab275377/ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png -------------------------------------------------------------------------------- /ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GopeedLab/gopeed/7c541e40479b5e574a4ab4c40ab4d328ab275377/ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png -------------------------------------------------------------------------------- /ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GopeedLab/gopeed/7c541e40479b5e574a4ab4c40ab4d328ab275377/ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png -------------------------------------------------------------------------------- /ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GopeedLab/gopeed/7c541e40479b5e574a4ab4c40ab4d328ab275377/ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GopeedLab/gopeed/7c541e40479b5e574a4ab4c40ab4d328ab275377/ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GopeedLab/gopeed/7c541e40479b5e574a4ab4c40ab4d328ab275377/ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png -------------------------------------------------------------------------------- /ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GopeedLab/gopeed/7c541e40479b5e574a4ab4c40ab4d328ab275377/ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png -------------------------------------------------------------------------------- /ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GopeedLab/gopeed/7c541e40479b5e574a4ab4c40ab4d328ab275377/ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GopeedLab/gopeed/7c541e40479b5e574a4ab4c40ab4d328ab275377/ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GopeedLab/gopeed/7c541e40479b5e574a4ab4c40ab4d328ab275377/ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /ui/flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /ui/flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GopeedLab/gopeed/7c541e40479b5e574a4ab4c40ab4d328ab275377/ui/flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ui/flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GopeedLab/gopeed/7c541e40479b5e574a4ab4c40ab4d328ab275377/ui/flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ui/flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GopeedLab/gopeed/7c541e40479b5e574a4ab4c40ab4d328ab275377/ui/flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /ui/flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /ui/flutter/ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /ui/flutter/ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | #import 3 | -------------------------------------------------------------------------------- /ui/flutter/ios/Runner/Runner.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.application-groups 6 | 7 | group.com.gopeed.gopeed 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ui/flutter/ios/ShareExtension/Base.lproj/MainInterface.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /ui/flutter/ios/ShareExtension/ShareExtension.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.application-groups 6 | 7 | group.com.gopeed.gopeed 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ui/flutter/ios/ShareExtension/ShareViewController.swift: -------------------------------------------------------------------------------- 1 | import share_handler_ios_models 2 | 3 | class ShareViewController: ShareHandlerIosViewController {} -------------------------------------------------------------------------------- /ui/flutter/lib/api/model/create_task.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | 3 | import 'options.dart'; 4 | import 'request.dart'; 5 | 6 | part 'create_task.g.dart'; 7 | 8 | @JsonSerializable(explicitToJson: true) 9 | class CreateTask { 10 | String? rid; 11 | Request? req; 12 | Options? opt; 13 | 14 | CreateTask({ 15 | this.rid, 16 | this.req, 17 | this.opt, 18 | }); 19 | 20 | factory CreateTask.fromJson( 21 | Map json, 22 | ) => 23 | _$CreateTaskFromJson(json); 24 | 25 | Map toJson() => _$CreateTaskToJson(this); 26 | } 27 | -------------------------------------------------------------------------------- /ui/flutter/lib/api/model/create_task.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'create_task.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | CreateTask _$CreateTaskFromJson(Map json) => CreateTask( 10 | rid: json['rid'] as String?, 11 | req: json['req'] == null 12 | ? null 13 | : Request.fromJson(json['req'] as Map), 14 | opt: json['opt'] == null 15 | ? null 16 | : Options.fromJson(json['opt'] as Map), 17 | ); 18 | 19 | Map _$CreateTaskToJson(CreateTask instance) { 20 | final val = {}; 21 | 22 | void writeNotNull(String key, dynamic value) { 23 | if (value != null) { 24 | val[key] = value; 25 | } 26 | } 27 | 28 | writeNotNull('rid', instance.rid); 29 | writeNotNull('req', instance.req?.toJson()); 30 | writeNotNull('opt', instance.opt?.toJson()); 31 | return val; 32 | } 33 | -------------------------------------------------------------------------------- /ui/flutter/lib/api/model/create_task_batch.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | 3 | import 'options.dart'; 4 | import 'request.dart'; 5 | 6 | part 'create_task_batch.g.dart'; 7 | 8 | @JsonSerializable(explicitToJson: true) 9 | class CreateTaskBatch { 10 | List? reqs; 11 | Options? opt; 12 | 13 | CreateTaskBatch({ 14 | this.reqs, 15 | this.opt, 16 | }); 17 | 18 | factory CreateTaskBatch.fromJson( 19 | Map json, 20 | ) => 21 | _$CreateTaskBatchFromJson(json); 22 | 23 | Map toJson() => _$CreateTaskBatchToJson(this); 24 | } 25 | 26 | @JsonSerializable() 27 | class CreateTaskBatchItem { 28 | Request? req; 29 | Options? opts; 30 | 31 | CreateTaskBatchItem({ 32 | this.req, 33 | this.opts, 34 | }); 35 | 36 | factory CreateTaskBatchItem.fromJson(Map json) => 37 | _$CreateTaskBatchItemFromJson(json); 38 | Map toJson() => _$CreateTaskBatchItemToJson(this); 39 | } 40 | -------------------------------------------------------------------------------- /ui/flutter/lib/api/model/create_task_batch.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'create_task_batch.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | CreateTaskBatch _$CreateTaskBatchFromJson(Map json) => 10 | CreateTaskBatch( 11 | reqs: (json['reqs'] as List?) 12 | ?.map((e) => CreateTaskBatchItem.fromJson(e as Map)) 13 | .toList(), 14 | opt: json['opt'] == null 15 | ? null 16 | : Options.fromJson(json['opt'] as Map), 17 | ); 18 | 19 | Map _$CreateTaskBatchToJson(CreateTaskBatch instance) { 20 | final val = {}; 21 | 22 | void writeNotNull(String key, dynamic value) { 23 | if (value != null) { 24 | val[key] = value; 25 | } 26 | } 27 | 28 | writeNotNull('reqs', instance.reqs?.map((e) => e.toJson()).toList()); 29 | writeNotNull('opt', instance.opt?.toJson()); 30 | return val; 31 | } 32 | 33 | CreateTaskBatchItem _$CreateTaskBatchItemFromJson(Map json) => 34 | CreateTaskBatchItem( 35 | req: json['req'] == null 36 | ? null 37 | : Request.fromJson(json['req'] as Map), 38 | opts: json['opts'] == null 39 | ? null 40 | : Options.fromJson(json['opts'] as Map), 41 | ); 42 | 43 | Map _$CreateTaskBatchItemToJson(CreateTaskBatchItem instance) { 44 | final val = {}; 45 | 46 | void writeNotNull(String key, dynamic value) { 47 | if (value != null) { 48 | val[key] = value; 49 | } 50 | } 51 | 52 | writeNotNull('req', instance.req); 53 | writeNotNull('opts', instance.opts); 54 | return val; 55 | } 56 | -------------------------------------------------------------------------------- /ui/flutter/lib/api/model/install_extension.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | 3 | part 'install_extension.g.dart'; 4 | 5 | @JsonSerializable() 6 | class InstallExtension { 7 | bool devMode; 8 | String url; 9 | 10 | InstallExtension({ 11 | this.devMode = false, 12 | required this.url, 13 | }); 14 | 15 | factory InstallExtension.fromJson(Map json) => 16 | _$InstallExtensionFromJson(json); 17 | Map toJson() => _$InstallExtensionToJson(this); 18 | } 19 | -------------------------------------------------------------------------------- /ui/flutter/lib/api/model/install_extension.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'install_extension.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | InstallExtension _$InstallExtensionFromJson(Map json) => 10 | InstallExtension( 11 | devMode: json['devMode'] as bool? ?? false, 12 | url: json['url'] as String, 13 | ); 14 | 15 | Map _$InstallExtensionToJson(InstallExtension instance) => 16 | { 17 | 'devMode': instance.devMode, 18 | 'url': instance.url, 19 | }; 20 | -------------------------------------------------------------------------------- /ui/flutter/lib/api/model/meta.dart: -------------------------------------------------------------------------------- 1 | import 'options.dart'; 2 | import 'request.dart'; 3 | import 'resource.dart'; 4 | import 'package:json_annotation/json_annotation.dart'; 5 | 6 | part 'meta.g.dart'; 7 | 8 | @JsonSerializable(explicitToJson: true) 9 | class Meta { 10 | Request req; 11 | Resource? res; 12 | Options opts; 13 | 14 | Meta({ 15 | required this.req, 16 | required this.opts, 17 | }); 18 | 19 | factory Meta.fromJson(Map json) => _$MetaFromJson(json); 20 | Map toJson() => _$MetaToJson(this); 21 | } 22 | -------------------------------------------------------------------------------- /ui/flutter/lib/api/model/meta.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'meta.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | Meta _$MetaFromJson(Map json) => Meta( 10 | req: Request.fromJson(json['req'] as Map), 11 | opts: Options.fromJson(json['opts'] as Map), 12 | )..res = json['res'] == null 13 | ? null 14 | : Resource.fromJson(json['res'] as Map); 15 | 16 | Map _$MetaToJson(Meta instance) { 17 | final val = { 18 | 'req': instance.req.toJson(), 19 | }; 20 | 21 | void writeNotNull(String key, dynamic value) { 22 | if (value != null) { 23 | val[key] = value; 24 | } 25 | } 26 | 27 | writeNotNull('res', instance.res?.toJson()); 28 | val['opts'] = instance.opts.toJson(); 29 | return val; 30 | } 31 | -------------------------------------------------------------------------------- /ui/flutter/lib/api/model/options.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | 3 | part 'options.g.dart'; 4 | 5 | @JsonSerializable(explicitToJson: true) 6 | class Options { 7 | String name; 8 | String path; 9 | List selectFiles; 10 | Object? extra; 11 | 12 | Options({ 13 | this.name = '', 14 | this.path = '', 15 | this.selectFiles = const [], 16 | this.extra, 17 | }); 18 | 19 | factory Options.fromJson(Map json) => 20 | _$OptionsFromJson(json); 21 | 22 | Map toJson() => _$OptionsToJson(this); 23 | } 24 | 25 | @JsonSerializable() 26 | class OptsExtraHttp { 27 | int connections; 28 | bool autoTorrent; 29 | 30 | OptsExtraHttp({ 31 | this.connections = 0, 32 | this.autoTorrent = false, 33 | }); 34 | 35 | factory OptsExtraHttp.fromJson(Map json) => 36 | _$OptsExtraHttpFromJson(json); 37 | 38 | Map toJson() => _$OptsExtraHttpToJson(this); 39 | } 40 | -------------------------------------------------------------------------------- /ui/flutter/lib/api/model/options.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'options.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | Options _$OptionsFromJson(Map json) => Options( 10 | name: json['name'] as String? ?? '', 11 | path: json['path'] as String? ?? '', 12 | selectFiles: (json['selectFiles'] as List?) 13 | ?.map((e) => (e as num).toInt()) 14 | .toList() ?? 15 | const [], 16 | extra: json['extra'], 17 | ); 18 | 19 | Map _$OptionsToJson(Options instance) { 20 | final val = { 21 | 'name': instance.name, 22 | 'path': instance.path, 23 | 'selectFiles': instance.selectFiles, 24 | }; 25 | 26 | void writeNotNull(String key, dynamic value) { 27 | if (value != null) { 28 | val[key] = value; 29 | } 30 | } 31 | 32 | writeNotNull('extra', instance.extra); 33 | return val; 34 | } 35 | 36 | OptsExtraHttp _$OptsExtraHttpFromJson(Map json) => 37 | OptsExtraHttp( 38 | connections: (json['connections'] as num?)?.toInt() ?? 0, 39 | autoTorrent: json['autoTorrent'] as bool? ?? false, 40 | ); 41 | 42 | Map _$OptsExtraHttpToJson(OptsExtraHttp instance) => 43 | { 44 | 'connections': instance.connections, 45 | 'autoTorrent': instance.autoTorrent, 46 | }; 47 | -------------------------------------------------------------------------------- /ui/flutter/lib/api/model/resolve_result.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | 3 | import 'resource.dart'; 4 | 5 | part 'resolve_result.g.dart'; 6 | 7 | @JsonSerializable(explicitToJson: true) 8 | class ResolveResult { 9 | String id; 10 | Resource res; 11 | 12 | ResolveResult({ 13 | this.id = "", 14 | required this.res, 15 | }); 16 | 17 | factory ResolveResult.fromJson(Map json) => 18 | _$ResolveResultFromJson(json); 19 | Map toJson() => _$ResolveResultToJson(this); 20 | } 21 | -------------------------------------------------------------------------------- /ui/flutter/lib/api/model/resolve_result.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'resolve_result.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | ResolveResult _$ResolveResultFromJson(Map json) => 10 | ResolveResult( 11 | id: json['id'] as String? ?? "", 12 | res: Resource.fromJson(json['res'] as Map), 13 | ); 14 | 15 | Map _$ResolveResultToJson(ResolveResult instance) => 16 | { 17 | 'id': instance.id, 18 | 'res': instance.res.toJson(), 19 | }; 20 | -------------------------------------------------------------------------------- /ui/flutter/lib/api/model/resource.dart: -------------------------------------------------------------------------------- 1 | import 'package:gopeed/api/model/request.dart'; 2 | import 'package:json_annotation/json_annotation.dart'; 3 | 4 | part 'resource.g.dart'; 5 | 6 | @JsonSerializable(explicitToJson: true) 7 | class Resource { 8 | String name; 9 | int size; 10 | bool range; 11 | List files; 12 | String hash; 13 | 14 | Resource( 15 | {this.name = "", 16 | this.size = 0, 17 | this.range = false, 18 | required this.files, 19 | this.hash = ""}); 20 | 21 | factory Resource.fromJson(Map json) => 22 | _$ResourceFromJson(json); 23 | 24 | Map toJson() => _$ResourceToJson(this); 25 | } 26 | 27 | @JsonSerializable(explicitToJson: true) 28 | class FileInfo { 29 | String path; 30 | String name; 31 | int size; 32 | Request? req; 33 | 34 | FileInfo({ 35 | this.path = "", 36 | required this.name, 37 | this.size = 0, 38 | this.req, 39 | }); 40 | 41 | factory FileInfo.fromJson(Map json) => 42 | _$FileInfoFromJson(json); 43 | 44 | Map toJson() => _$FileInfoToJson(this); 45 | } 46 | -------------------------------------------------------------------------------- /ui/flutter/lib/api/model/resource.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'resource.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | Resource _$ResourceFromJson(Map json) => Resource( 10 | name: json['name'] as String? ?? "", 11 | size: (json['size'] as num?)?.toInt() ?? 0, 12 | range: json['range'] as bool? ?? false, 13 | files: (json['files'] as List) 14 | .map((e) => FileInfo.fromJson(e as Map)) 15 | .toList(), 16 | hash: json['hash'] as String? ?? "", 17 | ); 18 | 19 | Map _$ResourceToJson(Resource instance) => { 20 | 'name': instance.name, 21 | 'size': instance.size, 22 | 'range': instance.range, 23 | 'files': instance.files.map((e) => e.toJson()).toList(), 24 | 'hash': instance.hash, 25 | }; 26 | 27 | FileInfo _$FileInfoFromJson(Map json) => FileInfo( 28 | path: json['path'] as String? ?? "", 29 | name: json['name'] as String, 30 | size: (json['size'] as num?)?.toInt() ?? 0, 31 | req: json['req'] == null 32 | ? null 33 | : Request.fromJson(json['req'] as Map), 34 | ); 35 | 36 | Map _$FileInfoToJson(FileInfo instance) { 37 | final val = { 38 | 'path': instance.path, 39 | 'name': instance.name, 40 | 'size': instance.size, 41 | }; 42 | 43 | void writeNotNull(String key, dynamic value) { 44 | if (value != null) { 45 | val[key] = value; 46 | } 47 | } 48 | 49 | writeNotNull('req', instance.req?.toJson()); 50 | return val; 51 | } 52 | -------------------------------------------------------------------------------- /ui/flutter/lib/api/model/result.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | 3 | part 'result.g.dart'; 4 | 5 | @JsonSerializable(genericArgumentFactories: true) 6 | class Result { 7 | int code; 8 | String? msg; 9 | T? data; 10 | 11 | Result({ 12 | required this.code, 13 | this.msg, 14 | this.data, 15 | }); 16 | 17 | factory Result.fromJson( 18 | Map json, 19 | T Function(dynamic json) fromJsonT, 20 | ) => 21 | _$ResultFromJson(json, fromJsonT); 22 | Map toJson() => { 23 | 'code': code, 24 | 'msg': msg, 25 | 'data': data is List 26 | ? (data as dynamic)?.map((e) => e.toJson()).toList() 27 | : (data as dynamic)?.toJson(), 28 | }; 29 | } 30 | -------------------------------------------------------------------------------- /ui/flutter/lib/api/model/result.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'result.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | Result _$ResultFromJson( 10 | Map json, 11 | T Function(Object? json) fromJsonT, 12 | ) => 13 | Result( 14 | code: (json['code'] as num).toInt(), 15 | msg: json['msg'] as String?, 16 | data: _$nullableGenericFromJson(json['data'], fromJsonT), 17 | ); 18 | 19 | Map _$ResultToJson( 20 | Result instance, 21 | Object? Function(T value) toJsonT, 22 | ) { 23 | final val = { 24 | 'code': instance.code, 25 | }; 26 | 27 | void writeNotNull(String key, dynamic value) { 28 | if (value != null) { 29 | val[key] = value; 30 | } 31 | } 32 | 33 | writeNotNull('msg', instance.msg); 34 | writeNotNull('data', _$nullableGenericToJson(instance.data, toJsonT)); 35 | return val; 36 | } 37 | 38 | T? _$nullableGenericFromJson( 39 | Object? input, 40 | T Function(Object? json) fromJson, 41 | ) => 42 | input == null ? null : fromJson(input); 43 | 44 | Object? _$nullableGenericToJson( 45 | T? input, 46 | Object? Function(T value) toJson, 47 | ) => 48 | input == null ? null : toJson(input); 49 | -------------------------------------------------------------------------------- /ui/flutter/lib/api/model/switch_extension.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | 3 | part 'switch_extension.g.dart'; 4 | 5 | @JsonSerializable() 6 | class SwitchExtension { 7 | bool status; 8 | 9 | SwitchExtension({ 10 | required this.status, 11 | }); 12 | 13 | factory SwitchExtension.fromJson(Map json) => 14 | _$SwitchExtensionFromJson(json); 15 | Map toJson() => _$SwitchExtensionToJson(this); 16 | } 17 | -------------------------------------------------------------------------------- /ui/flutter/lib/api/model/switch_extension.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'switch_extension.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | SwitchExtension _$SwitchExtensionFromJson(Map json) => 10 | SwitchExtension( 11 | status: json['status'] as bool, 12 | ); 13 | 14 | Map _$SwitchExtensionToJson(SwitchExtension instance) => 15 | { 16 | 'status': instance.status, 17 | }; 18 | -------------------------------------------------------------------------------- /ui/flutter/lib/api/model/task.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | 3 | import 'meta.dart'; 4 | 5 | part 'task.g.dart'; 6 | 7 | enum Status { ready, running, pause, wait, error, done } 8 | 9 | enum Protocol { http, bt } 10 | 11 | @JsonSerializable(explicitToJson: true) 12 | class Task { 13 | String id; 14 | String name; 15 | Protocol? protocol; 16 | Meta meta; 17 | Status status; 18 | bool uploading; 19 | Progress progress; 20 | DateTime createdAt; 21 | DateTime updatedAt; 22 | 23 | Task({ 24 | required this.id, 25 | required this.name, 26 | required this.meta, 27 | required this.status, 28 | required this.uploading, 29 | required this.progress, 30 | required this.createdAt, 31 | required this.updatedAt, 32 | }); 33 | 34 | factory Task.fromJson(Map json) => _$TaskFromJson(json); 35 | 36 | Map toJson() => _$TaskToJson(this); 37 | } 38 | 39 | @JsonSerializable() 40 | class Progress { 41 | int used; 42 | int speed; 43 | int downloaded; 44 | int uploadSpeed; 45 | int uploaded; 46 | 47 | Progress({ 48 | required this.used, 49 | required this.speed, 50 | required this.downloaded, 51 | required this.uploadSpeed, 52 | required this.uploaded, 53 | }); 54 | 55 | factory Progress.fromJson(Map json) => 56 | _$ProgressFromJson(json); 57 | 58 | Map toJson() => _$ProgressToJson(this); 59 | } 60 | -------------------------------------------------------------------------------- /ui/flutter/lib/api/model/update_check_extension_resp.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | 3 | part 'update_check_extension_resp.g.dart'; 4 | 5 | @JsonSerializable() 6 | class UpdateCheckExtensionResp { 7 | String newVersion; 8 | 9 | UpdateCheckExtensionResp({ 10 | required this.newVersion, 11 | }); 12 | 13 | factory UpdateCheckExtensionResp.fromJson(Map json) => 14 | _$UpdateCheckExtensionRespFromJson(json); 15 | Map toJson() => _$UpdateCheckExtensionRespToJson(this); 16 | } 17 | -------------------------------------------------------------------------------- /ui/flutter/lib/api/model/update_check_extension_resp.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'update_check_extension_resp.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | UpdateCheckExtensionResp _$UpdateCheckExtensionRespFromJson( 10 | Map json) => 11 | UpdateCheckExtensionResp( 12 | newVersion: json['newVersion'] as String, 13 | ); 14 | 15 | Map _$UpdateCheckExtensionRespToJson( 16 | UpdateCheckExtensionResp instance) => 17 | { 18 | 'newVersion': instance.newVersion, 19 | }; 20 | -------------------------------------------------------------------------------- /ui/flutter/lib/api/model/update_extension_settings.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | 3 | part 'update_extension_settings.g.dart'; 4 | 5 | @JsonSerializable() 6 | class UpdateExtensionSettings { 7 | Map settings; 8 | 9 | UpdateExtensionSettings({ 10 | required this.settings, 11 | }); 12 | 13 | factory UpdateExtensionSettings.fromJson(Map json) => 14 | _$UpdateExtensionSettingsFromJson(json); 15 | Map toJson() => _$UpdateExtensionSettingsToJson(this); 16 | } 17 | -------------------------------------------------------------------------------- /ui/flutter/lib/api/model/update_extension_settings.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'update_extension_settings.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | UpdateExtensionSettings _$UpdateExtensionSettingsFromJson( 10 | Map json) => 11 | UpdateExtensionSettings( 12 | settings: json['settings'] as Map, 13 | ); 14 | 15 | Map _$UpdateExtensionSettingsToJson( 16 | UpdateExtensionSettings instance) => 17 | { 18 | 'settings': instance.settings, 19 | }; 20 | -------------------------------------------------------------------------------- /ui/flutter/lib/app/modules/app/bindings/app_binding.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | import '../controllers/app_controller.dart'; 4 | 5 | class AppBinding extends Bindings { 6 | @override 7 | void dependencies() { 8 | Get.lazyPut( 9 | () => AppController(), 10 | ); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /ui/flutter/lib/app/modules/create/bindings/create_binding.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | import '../controllers/create_controller.dart'; 4 | 5 | class CreateBinding extends Bindings { 6 | @override 7 | void dependencies() { 8 | Get.lazyPut( 9 | () => CreateController(), 10 | ); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /ui/flutter/lib/app/modules/create/controllers/create_controller.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'dart:typed_data'; 3 | 4 | import 'package:flutter/material.dart'; 5 | import 'package:get/get.dart'; 6 | import 'package:gopeed/api/model/request.dart'; 7 | 8 | import '../../app/controllers/app_controller.dart'; 9 | 10 | class CreateController extends GetxController 11 | with GetSingleTickerProviderStateMixin { 12 | final RxList fileInfos = [].obs; 13 | final RxList openedFolders = [].obs; 14 | final selectedIndexes = [].obs; 15 | final isConfirming = false.obs; 16 | final showAdvanced = false.obs; 17 | final directDownload = false.obs; 18 | final proxyConfig = Rx(null); 19 | late TabController advancedTabController; 20 | final oldUrl = "".obs; 21 | final fileDataUri = "".obs; 22 | 23 | @override 24 | void onInit() { 25 | super.onInit(); 26 | advancedTabController = TabController(length: 2, vsync: this); 27 | directDownload.value = Get.find() 28 | .downloaderConfig 29 | .value 30 | .extra 31 | .defaultDirectDownload; 32 | } 33 | 34 | @override 35 | void onClose() { 36 | advancedTabController.dispose(); 37 | super.onClose(); 38 | } 39 | 40 | void setFileDataUri(Uint8List bytes) { 41 | fileDataUri.value = 42 | "data:application/x-bittorrent;base64,${base64.encode(bytes)}"; 43 | } 44 | 45 | void clearFileDataUri() { 46 | fileDataUri.value = ""; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /ui/flutter/lib/app/modules/create/dto/create_router_params.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | 3 | import '../../../../api/model/options.dart'; 4 | import '../../../../api/model/request.dart'; 5 | 6 | part 'create_router_params.g.dart'; 7 | 8 | @JsonSerializable(explicitToJson: true) 9 | class CreateRouterParams { 10 | Request? req; 11 | Options? opt; 12 | 13 | CreateRouterParams({ 14 | this.req, 15 | this.opt, 16 | }); 17 | 18 | factory CreateRouterParams.fromJson( 19 | Map json, 20 | ) => 21 | _$CreateRouterParamsFromJson(json); 22 | 23 | Map toJson() => _$CreateRouterParamsToJson(this); 24 | } 25 | -------------------------------------------------------------------------------- /ui/flutter/lib/app/modules/create/dto/create_router_params.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'create_router_params.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | CreateRouterParams _$CreateRouterParamsFromJson(Map json) => 10 | CreateRouterParams( 11 | req: json['req'] == null 12 | ? null 13 | : Request.fromJson(json['req'] as Map), 14 | opt: json['opt'] == null 15 | ? null 16 | : Options.fromJson(json['opt'] as Map), 17 | ); 18 | 19 | Map _$CreateRouterParamsToJson(CreateRouterParams instance) { 20 | final val = {}; 21 | 22 | void writeNotNull(String key, dynamic value) { 23 | if (value != null) { 24 | val[key] = value; 25 | } 26 | } 27 | 28 | writeNotNull('req', instance.req?.toJson()); 29 | writeNotNull('opt', instance.opt?.toJson()); 30 | return val; 31 | } 32 | -------------------------------------------------------------------------------- /ui/flutter/lib/app/modules/extension/bindings/extension_binding.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | import '../controllers/extension_controller.dart'; 4 | 5 | class ExtensionBinding extends Bindings { 6 | @override 7 | void dependencies() { 8 | Get.lazyPut( 9 | () => ExtensionController(), 10 | ); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /ui/flutter/lib/app/modules/extension/controllers/extension_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | import 'package:gopeed/api/api.dart'; 3 | 4 | import '../../../../api/model/extension.dart'; 5 | 6 | class ExtensionController extends GetxController { 7 | final extensions = [].obs; 8 | final updateFlags = {}.obs; 9 | final devMode = false.obs; 10 | var _devModeCount = 0; 11 | 12 | @override 13 | void onInit() async { 14 | super.onInit(); 15 | await load(); 16 | checkUpdate(); 17 | } 18 | 19 | Future load() async { 20 | extensions.value = await getExtensions(); 21 | } 22 | 23 | Future checkUpdate() async { 24 | for (final ext in extensions) { 25 | final resp = await upgradeCheckExtension(ext.identity); 26 | if (resp.newVersion.isNotEmpty) { 27 | updateFlags[ext.identity] = resp.newVersion; 28 | } 29 | } 30 | } 31 | 32 | // Try to open dev mode when install button is clicked 5 times in 2 seconds 33 | void tryOpenDevMode() { 34 | if (_devModeCount == 0) { 35 | Future.delayed(const Duration(seconds: 2), () { 36 | if (devMode.value) return; 37 | devMode.value = false; 38 | _devModeCount = 0; 39 | }); 40 | } 41 | _devModeCount++; 42 | if (_devModeCount >= 5) { 43 | devMode.value = true; 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /ui/flutter/lib/app/modules/home/bindings/home_binding.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | import '../controllers/home_controller.dart'; 4 | 5 | class HomeBinding extends Bindings { 6 | @override 7 | void dependencies() { 8 | Get.lazyPut( 9 | () => HomeController(), 10 | fenix: true, 11 | ); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /ui/flutter/lib/app/modules/home/controllers/home_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | class HomeController extends GetxController { 4 | var currentIndex = 0.obs; 5 | } 6 | -------------------------------------------------------------------------------- /ui/flutter/lib/app/modules/redirect/bindings/redirect_binding.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | import '../controllers/redirect_controller.dart'; 4 | 5 | class RedirectBinding extends Bindings { 6 | @override 7 | void dependencies() { 8 | Get.lazyPut( 9 | () => RedirectController(), 10 | ); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /ui/flutter/lib/app/modules/redirect/controllers/redirect_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | class RedirectController extends GetxController {} 4 | -------------------------------------------------------------------------------- /ui/flutter/lib/app/modules/redirect/views/redirect_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | 4 | import '../controllers/redirect_controller.dart'; 5 | 6 | class RedirectArgs { 7 | final String page; 8 | final dynamic arguments; 9 | 10 | RedirectArgs(this.page, {this.arguments}); 11 | } 12 | 13 | class RedirectView extends GetView { 14 | const RedirectView({Key? key}) : super(key: key); 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | final redirectArgs = Get.rootDelegate.arguments() as RedirectArgs; 19 | // Waiting for previous page controller to delete, avoid deleting controller that route page after redirect 20 | WidgetsBinding.instance.addPostFrameCallback((_) async { 21 | await Future.delayed(const Duration(milliseconds: 350)); 22 | Get.rootDelegate 23 | .offAndToNamed(redirectArgs.page, arguments: redirectArgs.arguments); 24 | }); 25 | return const SizedBox(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /ui/flutter/lib/app/modules/root/bindings/root_binding.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | import '../controllers/root_controller.dart'; 4 | 5 | class RootBinding extends Bindings { 6 | @override 7 | void dependencies() { 8 | Get.lazyPut(() => RootController(), fenix: true); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /ui/flutter/lib/app/modules/root/controllers/root_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | class RootController extends GetxController {} 4 | -------------------------------------------------------------------------------- /ui/flutter/lib/app/modules/root/views/root_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | 4 | import '../../../routes/app_pages.dart'; 5 | import '../controllers/root_controller.dart'; 6 | 7 | class RootView extends GetView { 8 | const RootView({Key? key}) : super(key: key); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | return GetRouterOutlet.builder( 13 | builder: (context, delegate, current) { 14 | return GetRouterOutlet( 15 | initialRoute: Routes.HOME, 16 | // anchorRoute: '/', 17 | // filterPages: (afterAnchor) { 18 | // return afterAnchor.take(1); 19 | // }, 20 | ); 21 | }, 22 | ); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /ui/flutter/lib/app/modules/setting/bindings/setting_binding.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | import '../controllers/setting_controller.dart'; 4 | 5 | class SettingBinding extends Bindings { 6 | @override 7 | void dependencies() { 8 | Get.lazyPut( 9 | () => SettingController(), 10 | ); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /ui/flutter/lib/app/modules/setting/controllers/setting_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | import '../../../../util/updater.dart'; 4 | 5 | class SettingController extends GetxController { 6 | final tapStatues = {}.obs; 7 | final latestVersion = Rxn(); 8 | 9 | @override 10 | void onInit() { 11 | super.onInit(); 12 | fetchLatestVersion(); 13 | } 14 | 15 | // set all tap status to false 16 | void clearTap() { 17 | tapStatues.updateAll((key, value) => false); 18 | } 19 | 20 | // set one tap status to true 21 | void onTap(String key) { 22 | clearTap(); 23 | tapStatues[key] = true; 24 | } 25 | 26 | // fetch latest version 27 | void fetchLatestVersion() async { 28 | latestVersion.value = await checkUpdate(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /ui/flutter/lib/app/modules/task/bindings/task_binding.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | import '../controllers/task_controller.dart'; 4 | import '../controllers/task_downloaded_controller.dart'; 5 | import '../controllers/task_downloading_controller.dart'; 6 | 7 | class TaskBinding extends Bindings { 8 | @override 9 | void dependencies() { 10 | Get.lazyPut( 11 | () => TaskController(), 12 | ); 13 | Get.lazyPut( 14 | () => TaskDownloadingController(), 15 | ); 16 | Get.lazyPut( 17 | () => TaskDownloadedController(), 18 | ); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /ui/flutter/lib/app/modules/task/bindings/task_files_binding.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | import '../controllers/task_files_controller.dart'; 4 | 5 | class TaskFilesBinding extends Bindings { 6 | @override 7 | void dependencies() { 8 | Get.lazyPut( 9 | () => TaskFilesController(), 10 | ); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /ui/flutter/lib/app/modules/task/controllers/task_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter/services.dart'; 4 | import 'package:get/get.dart'; 5 | import 'package:gopeed/api/model/task.dart'; 6 | 7 | class TaskController extends GetxController { 8 | final tabIndex = 0.obs; 9 | final scaffoldKey = GlobalKey(); 10 | final selectTask = Rx(null); 11 | 12 | @override 13 | void onInit() { 14 | super.onInit(); 15 | if (kIsWeb) { 16 | BrowserContextMenu.disableContextMenu(); 17 | } 18 | } 19 | 20 | @override 21 | void onClose() { 22 | super.onClose(); 23 | if (kIsWeb) { 24 | BrowserContextMenu.enableContextMenu(); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /ui/flutter/lib/app/modules/task/controllers/task_downloaded_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:gopeed/app/modules/task/controllers/task_list_controller.dart'; 2 | 3 | import '../../../../api/model/task.dart'; 4 | 5 | class TaskDownloadedController extends TaskListController { 6 | TaskDownloadedController() 7 | : super([Status.done], (a, b) => b.updatedAt.compareTo(a.updatedAt)); 8 | } 9 | -------------------------------------------------------------------------------- /ui/flutter/lib/app/modules/task/controllers/task_downloading_controller.dart: -------------------------------------------------------------------------------- 1 | import '../../../../api/model/task.dart'; 2 | import 'task_list_controller.dart'; 3 | 4 | class TaskDownloadingController extends TaskListController { 5 | TaskDownloadingController() 6 | : super([ 7 | Status.ready, 8 | Status.running, 9 | Status.pause, 10 | Status.wait, 11 | Status.error 12 | ], (a, b) { 13 | if (a.status == Status.running && b.status != Status.running) { 14 | return -1; 15 | } else if (a.status != Status.running && b.status == Status.running) { 16 | return 1; 17 | } else { 18 | return b.updatedAt.compareTo(a.updatedAt); 19 | } 20 | }); 21 | } 22 | -------------------------------------------------------------------------------- /ui/flutter/lib/app/modules/task/controllers/task_list_controller.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:get/get.dart'; 4 | 5 | import '../../../../api/api.dart'; 6 | import '../../../../api/model/task.dart'; 7 | 8 | abstract class TaskListController extends GetxController { 9 | List statuses; 10 | int Function(Task a, Task b) compare; 11 | 12 | TaskListController(this.statuses, this.compare); 13 | 14 | final tasks = [].obs; 15 | final selectedTaskIds = [].obs; 16 | final isRunning = false.obs; 17 | 18 | late final Timer _timer; 19 | 20 | @override 21 | void onInit() async { 22 | super.onInit(); 23 | 24 | start(); 25 | _timer = Timer.periodic(const Duration(milliseconds: 1000), (timer) async { 26 | if (isRunning.value) { 27 | await getTasksState(); 28 | } 29 | }); 30 | } 31 | 32 | @override 33 | void onClose() { 34 | super.onClose(); 35 | _timer.cancel(); 36 | } 37 | 38 | void start() async { 39 | await getTasksState(); 40 | isRunning.value = true; 41 | } 42 | 43 | void stop() { 44 | isRunning.value = false; 45 | } 46 | 47 | getTasksState() async { 48 | final tasks = await getTasks(statuses); 49 | // sort tasks by create time 50 | tasks.sort(compare); 51 | this.tasks.value = tasks; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /ui/flutter/lib/app/modules/task/views/task_downloaded_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | 4 | import '../../../views/buid_task_list_view.dart'; 5 | import '../controllers/task_downloaded_controller.dart'; 6 | 7 | class TaskDownloadedView extends GetView { 8 | const TaskDownloadedView({Key? key}) : super(key: key); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | return BuildTaskListView( 13 | tasks: controller.tasks, selectedTaskIds: controller.selectedTaskIds); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /ui/flutter/lib/app/modules/task/views/task_downloading_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | 4 | import '../../../views/buid_task_list_view.dart'; 5 | import '../controllers/task_downloading_controller.dart'; 6 | 7 | class TaskDownloadingView extends GetView { 8 | const TaskDownloadingView({Key? key}) : super(key: key); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | return BuildTaskListView( 13 | tasks: controller.tasks, selectedTaskIds: controller.selectedTaskIds); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /ui/flutter/lib/app/routes/app_routes.dart: -------------------------------------------------------------------------------- 1 | part of 'app_pages.dart'; 2 | // DO NOT EDIT. This is code generated via package:get_cli/get_cli.dart 3 | 4 | abstract class Routes { 5 | Routes._(); 6 | 7 | static const ROOT = _Paths.ROOT; 8 | static const HOME = _Paths.HOME; 9 | static const CREATE = _Paths.CREATE; 10 | static const TASK = _Paths.HOME + _Paths.TASK; 11 | static const TASK_FILES = TASK + _Paths.TASK_FILES; 12 | static const EXTENSION = _Paths.HOME + _Paths.EXTENSION; 13 | static const SETTING = _Paths.HOME + _Paths.SETTING; 14 | static const REDIRECT = _Paths.REDIRECT; 15 | } 16 | 17 | abstract class _Paths { 18 | _Paths._(); 19 | 20 | static const ROOT = '/'; 21 | static const HOME = '/home'; 22 | static const CREATE = '/create'; 23 | static const TASK = '/task'; 24 | static const TASK_FILES = '/files'; 25 | static const EXTENSION = '/extension'; 26 | static const SETTING = '/setting'; 27 | static const REDIRECT = '/redirect'; 28 | } 29 | -------------------------------------------------------------------------------- /ui/flutter/lib/app/views/breadcrumb_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | 4 | class Breadcrumb extends StatelessWidget { 5 | final List items; 6 | final Function(int)? onItemTap; 7 | final TextStyle textStyle; 8 | final TextStyle activeTextStyle; 9 | 10 | const Breadcrumb({ 11 | super.key, 12 | required this.items, 13 | this.onItemTap, 14 | this.textStyle = const TextStyle(fontSize: 16), 15 | this.activeTextStyle = 16 | const TextStyle(fontSize: 16, fontWeight: FontWeight.bold), 17 | }); 18 | 19 | @override 20 | Widget build(BuildContext context) { 21 | List children = []; 22 | for (int i = 0; i < items.length; i++) { 23 | children.add( 24 | GestureDetector( 25 | onTap: () { 26 | if (onItemTap != null) { 27 | onItemTap!(i); 28 | } 29 | }, 30 | child: Text( 31 | items[i], 32 | style: i == items.length - 1 ? activeTextStyle : textStyle, 33 | ), 34 | ), 35 | ); 36 | if (i != items.length - 1) { 37 | children.add(const Text(" > ")); 38 | } 39 | } 40 | return Row( 41 | children: [ 42 | ...(children.length == 1 43 | ? children.sublist(0, 1) 44 | : children.sublist(0, 2)), 45 | children.length > 2 46 | ? Expanded( 47 | child: SingleChildScrollView( 48 | reverse: true, 49 | scrollDirection: Axis.horizontal, 50 | child: Row( 51 | children: children.sublist(2), 52 | ), 53 | ), 54 | ) 55 | : null, 56 | ].where((e) => e != null).map((e) => e!).toList(), 57 | ).paddingOnly(right: 12); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /ui/flutter/lib/app/views/compact_checkbox.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class CompactCheckbox extends StatefulWidget { 4 | final String label; 5 | final bool value; 6 | final ValueChanged? onChanged; 7 | final double? scale; 8 | final TextStyle? textStyle; 9 | 10 | const CompactCheckbox({ 11 | super.key, 12 | required this.label, 13 | required this.value, 14 | this.onChanged, 15 | this.scale, 16 | this.textStyle, 17 | }); 18 | 19 | @override 20 | State createState() => _CompactCheckboxState(); 21 | } 22 | 23 | class _CompactCheckboxState extends State { 24 | late bool _value; 25 | 26 | @override 27 | void initState() { 28 | super.initState(); 29 | _value = widget.value; 30 | } 31 | 32 | valueChanged(bool? value) { 33 | setState(() { 34 | _value = value!; 35 | }); 36 | widget.onChanged?.call(_value); 37 | } 38 | 39 | @override 40 | Widget build(BuildContext context) { 41 | final checkbox = Checkbox( 42 | value: _value, 43 | onChanged: valueChanged, 44 | ); 45 | 46 | return TextButton( 47 | onPressed: () { 48 | valueChanged(!_value); 49 | }, 50 | child: Row( 51 | children: [ 52 | widget.scale == null 53 | ? checkbox 54 | : Transform.scale( 55 | scale: widget.scale, 56 | child: checkbox, 57 | ), 58 | Text( 59 | widget.label, 60 | style: widget.textStyle, 61 | ), 62 | ], 63 | ), 64 | ); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /ui/flutter/lib/app/views/copy_button.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/services.dart'; 3 | 4 | import '../../util/message.dart'; 5 | 6 | class CopyButton extends StatefulWidget { 7 | final String? url; 8 | 9 | const CopyButton(this.url, {Key? key}) : super(key: key); 10 | 11 | @override 12 | State createState() => _CopyButtonState(); 13 | } 14 | 15 | class _CopyButtonState extends State { 16 | bool success = false; 17 | 18 | copy() { 19 | final url = widget.url; 20 | if (url != null) { 21 | try { 22 | Clipboard.setData(ClipboardData(text: url)); 23 | setState(() { 24 | success = true; 25 | }); 26 | Future.delayed(const Duration(milliseconds: 300), () { 27 | setState(() { 28 | success = false; 29 | }); 30 | }); 31 | } catch (e) { 32 | showErrorMessage(e); 33 | } 34 | } 35 | } 36 | 37 | @override 38 | Widget build(BuildContext context) { 39 | return IconButton( 40 | icon: success ? const Icon(Icons.check_circle) : const Icon(Icons.copy), 41 | onPressed: copy, 42 | ); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /ui/flutter/lib/app/views/icon_button_loading.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class IconButtonLoading extends StatefulWidget { 4 | final Widget icon; 5 | final VoidCallback? onPressed; 6 | final IconButtonLoadingController controller; 7 | 8 | const IconButtonLoading( 9 | {Key? key, 10 | required this.icon, 11 | required this.onPressed, 12 | required this.controller}) 13 | : super(key: key); 14 | 15 | @override 16 | State createState() => _IconButtonLoadingState(); 17 | } 18 | 19 | class _IconButtonLoadingState extends State { 20 | @override 21 | Widget build(BuildContext context) { 22 | return ValueListenableBuilder( 23 | valueListenable: widget.controller, 24 | builder: (context, value, child) { 25 | return IconButton( 26 | key: widget.key, 27 | onPressed: value ? null : widget.onPressed, 28 | icon: value 29 | ? const SizedBox( 30 | height: 20, 31 | width: 20, 32 | child: CircularProgressIndicator( 33 | strokeWidth: 2, 34 | ), 35 | ) 36 | : widget.icon, 37 | ); 38 | }, 39 | ); 40 | } 41 | } 42 | 43 | class IconButtonLoadingController extends ValueNotifier { 44 | IconButtonLoadingController() : super(false); 45 | 46 | void start() { 47 | value = true; 48 | } 49 | 50 | void stop() { 51 | value = false; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /ui/flutter/lib/app/views/open_in_new.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import 'package:url_launcher/url_launcher.dart'; 4 | 5 | class OpenInNew extends StatelessWidget { 6 | final String text; 7 | final String url; 8 | 9 | const OpenInNew({super.key, required this.text, required this.url}); 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | return Stack( 14 | children: [ 15 | ElevatedButton( 16 | onPressed: () { 17 | launchUrl(Uri.parse(url), mode: LaunchMode.externalApplication); 18 | }, 19 | style: ElevatedButton.styleFrom( 20 | backgroundColor: Get.theme.colorScheme.background, 21 | ), 22 | child: Row( 23 | mainAxisSize: MainAxisSize 24 | .min, // Set the row's size to be as small as possible 25 | children: [ 26 | Text(text), 27 | const SizedBox( 28 | width: 4), // Add some space between the text and the icon 29 | const Icon( 30 | Icons.open_in_new, 31 | size: 14, 32 | ), // The icon is after the text 33 | ], 34 | ), 35 | ) 36 | ], 37 | ); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /ui/flutter/lib/app/views/outlined_button_loading.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class OutlinedButtonLoading extends StatefulWidget { 4 | final Widget child; 5 | final VoidCallback onPressed; 6 | final OutlinedButtonLoadingController controller; 7 | 8 | const OutlinedButtonLoading( 9 | {super.key, 10 | required this.child, 11 | required this.onPressed, 12 | required this.controller}); 13 | 14 | @override 15 | State createState() => _OutlinedButtonLoadingState(); 16 | } 17 | 18 | class _OutlinedButtonLoadingState extends State { 19 | @override 20 | Widget build(BuildContext context) { 21 | return ValueListenableBuilder( 22 | valueListenable: widget.controller, 23 | builder: (context, value, child) { 24 | return OutlinedButton( 25 | key: widget.key, 26 | onPressed: value ? null : widget.onPressed, 27 | child: value 28 | ? const SizedBox( 29 | height: 20, 30 | width: 20, 31 | child: CircularProgressIndicator( 32 | strokeWidth: 2, 33 | ), 34 | ) 35 | : widget.child, 36 | ); 37 | }, 38 | ); 39 | } 40 | } 41 | 42 | class OutlinedButtonLoadingController extends ValueNotifier { 43 | OutlinedButtonLoadingController() : super(false); 44 | 45 | void start() { 46 | value = true; 47 | } 48 | 49 | void stop() { 50 | value = false; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /ui/flutter/lib/app/views/responsive_builder.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class ResponsiveBuilder extends StatelessWidget { 4 | const ResponsiveBuilder({ 5 | required this.narrowBuilder, 6 | required this.mediumBuilder, 7 | required this.wideBuilder, 8 | Key? key, 9 | }) : super(key: key); 10 | 11 | final Widget Function( 12 | BuildContext context, 13 | BoxConstraints constraints, 14 | ) narrowBuilder; 15 | 16 | final Widget Function( 17 | BuildContext context, 18 | BoxConstraints constraints, 19 | ) mediumBuilder; 20 | 21 | final Widget Function( 22 | BuildContext context, 23 | BoxConstraints constraints, 24 | ) wideBuilder; 25 | 26 | static bool isNarrow(BuildContext context) => 27 | MediaQuery.of(context).size.width < 768; 28 | 29 | static bool isMedium(BuildContext context) => 30 | MediaQuery.of(context).size.width < 992 && 31 | MediaQuery.of(context).size.width >= 768; 32 | 33 | static bool isWide(BuildContext context) => 34 | MediaQuery.of(context).size.width >= 992; 35 | 36 | @override 37 | Widget build(BuildContext context) { 38 | return LayoutBuilder( 39 | builder: (context, constraints) { 40 | if (constraints.maxWidth >= 992) { 41 | return wideBuilder(context, constraints); 42 | } else if (constraints.maxWidth >= 768) { 43 | return mediumBuilder(context, constraints); 44 | } else { 45 | return narrowBuilder(context, constraints); 46 | } 47 | }, 48 | ); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /ui/flutter/lib/app/views/text_button_loading.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class TextButtonLoading extends StatefulWidget { 4 | final Widget child; 5 | final VoidCallback? onPressed; 6 | final TextButtonLoadingController controller; 7 | 8 | const TextButtonLoading( 9 | {Key? key, 10 | required this.child, 11 | required this.onPressed, 12 | required this.controller}) 13 | : super(key: key); 14 | 15 | @override 16 | State createState() => _TextButtonLoadingState(); 17 | } 18 | 19 | class _TextButtonLoadingState extends State { 20 | @override 21 | Widget build(BuildContext context) { 22 | return ValueListenableBuilder( 23 | valueListenable: widget.controller, 24 | builder: (context, value, child) { 25 | return TextButton( 26 | key: widget.key, 27 | onPressed: value ? null : widget.onPressed, 28 | child: value 29 | ? const SizedBox( 30 | height: 20, 31 | width: 20, 32 | child: CircularProgressIndicator( 33 | strokeWidth: 2, 34 | ), 35 | ) 36 | : widget.child, 37 | ); 38 | }, 39 | ); 40 | } 41 | } 42 | 43 | class TextButtonLoadingController extends ValueNotifier { 44 | TextButtonLoadingController() : super(false); 45 | 46 | void start() { 47 | value = true; 48 | } 49 | 50 | void stop() { 51 | value = false; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /ui/flutter/lib/core/common/libgopeed_channel.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:flutter/services.dart'; 4 | 5 | import 'libgopeed_interface.dart'; 6 | import 'start_config.dart'; 7 | 8 | class LibgopeedChannel implements LibgopeedInterface { 9 | static const _channel = MethodChannel('gopeed.com/libgopeed'); 10 | 11 | @override 12 | Future start(StartConfig cfg) async { 13 | final port = await _channel.invokeMethod('start', { 14 | 'cfg': jsonEncode(cfg), 15 | }); 16 | return port as int; 17 | } 18 | 19 | @override 20 | Future stop() async { 21 | return await _channel.invokeMethod('stop'); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /ui/flutter/lib/core/common/libgopeed_ffi.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:convert'; 3 | import 'dart:ffi'; 4 | 5 | import 'package:ffi/ffi.dart'; 6 | 7 | import '../ffi/libgopeed_bind.dart'; 8 | import 'libgopeed_interface.dart'; 9 | import 'start_config.dart'; 10 | 11 | class LibgopeedFFi implements LibgopeedInterface { 12 | late LibgopeedBind _libgopeed; 13 | 14 | LibgopeedFFi(LibgopeedBind libgopeed) { 15 | _libgopeed = libgopeed; 16 | } 17 | 18 | @override 19 | Future start(StartConfig cfg) { 20 | var completer = Completer(); 21 | var result = _libgopeed.Start(jsonEncode(cfg).toNativeUtf8().cast()); 22 | if (result.r1 != nullptr) { 23 | completer.completeError(Exception(result.r1.cast().toDartString())); 24 | } else { 25 | completer.complete(result.r0); 26 | } 27 | return completer.future; 28 | } 29 | 30 | @override 31 | Future stop() { 32 | var completer = Completer(); 33 | _libgopeed.Stop(); 34 | completer.complete(); 35 | return completer.future; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /ui/flutter/lib/core/common/libgopeed_interface.dart: -------------------------------------------------------------------------------- 1 | import 'start_config.dart'; 2 | 3 | abstract class LibgopeedInterface { 4 | Future start(StartConfig cfg); 5 | 6 | Future stop(); 7 | } 8 | -------------------------------------------------------------------------------- /ui/flutter/lib/core/common/start_config.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | 3 | part 'start_config.g.dart'; 4 | 5 | @JsonSerializable() 6 | class StartConfig { 7 | late String network; 8 | late String address; 9 | late String storage; 10 | late String storageDir; 11 | late int refreshInterval; 12 | late String apiToken; 13 | 14 | StartConfig(); 15 | 16 | factory StartConfig.fromJson(Map json) => 17 | _$StartConfigFromJson(json); 18 | 19 | Map toJson() => _$StartConfigToJson(this); 20 | } 21 | -------------------------------------------------------------------------------- /ui/flutter/lib/core/common/start_config.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'start_config.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | StartConfig _$StartConfigFromJson(Map json) => StartConfig() 10 | ..network = json['network'] as String 11 | ..address = json['address'] as String 12 | ..storage = json['storage'] as String 13 | ..storageDir = json['storageDir'] as String 14 | ..refreshInterval = (json['refreshInterval'] as num).toInt() 15 | ..apiToken = json['apiToken'] as String; 16 | 17 | Map _$StartConfigToJson(StartConfig instance) => 18 | { 19 | 'network': instance.network, 20 | 'address': instance.address, 21 | 'storage': instance.storage, 22 | 'storageDir': instance.storageDir, 23 | 'refreshInterval': instance.refreshInterval, 24 | 'apiToken': instance.apiToken, 25 | }; 26 | -------------------------------------------------------------------------------- /ui/flutter/lib/core/entry/libgopeed_boot_browser.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import '../common/start_config.dart'; 4 | 5 | import '../libgopeed_boot.dart'; 6 | 7 | LibgopeedBoot create() => LibgopeedBootBrowser(); 8 | 9 | class LibgopeedBootBrowser implements LibgopeedBoot { 10 | // do nothing 11 | @override 12 | Future start(StartConfig cfg) async { 13 | return 0; 14 | } 15 | 16 | @override 17 | Future stop() async {} 18 | } 19 | -------------------------------------------------------------------------------- /ui/flutter/lib/core/entry/libgopeed_boot_native.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:ffi'; 3 | import 'dart:io'; 4 | 5 | import '../../util/util.dart'; 6 | import '../common/libgopeed_channel.dart'; 7 | import '../common/libgopeed_ffi.dart'; 8 | import '../common/libgopeed_interface.dart'; 9 | import '../common/start_config.dart'; 10 | import '../ffi/libgopeed_bind.dart'; 11 | import '../libgopeed_boot.dart'; 12 | 13 | LibgopeedBoot create() => LibgopeedBootNative(); 14 | 15 | class LibgopeedBootNative implements LibgopeedBoot { 16 | late LibgopeedInterface _libgopeed; 17 | 18 | LibgopeedBootNative() { 19 | if (Util.isDesktop()) { 20 | var libName = "libgopeed."; 21 | if (Platform.isWindows) { 22 | libName += "dll"; 23 | } 24 | if (Platform.isMacOS) { 25 | libName += "dylib"; 26 | } 27 | if (Platform.isLinux) { 28 | libName += "so"; 29 | } 30 | _libgopeed = LibgopeedFFi(LibgopeedBind(DynamicLibrary.open(libName))); 31 | } else { 32 | _libgopeed = LibgopeedChannel(); 33 | } 34 | } 35 | 36 | @override 37 | Future start(StartConfig cfg) async { 38 | cfg.storage = 'bolt'; 39 | cfg.storageDir = Util.getStorageDir(); 40 | cfg.refreshInterval = 0; 41 | var port = await _libgopeed.start(cfg); 42 | return port; 43 | } 44 | 45 | @override 46 | Future stop() async { 47 | await _libgopeed.stop(); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /ui/flutter/lib/core/libgopeed_boot.dart: -------------------------------------------------------------------------------- 1 | import 'common/start_config.dart'; 2 | import "libgopeed_boot_stub.dart" 3 | if (dart.library.html) 'entry/libgopeed_boot_browser.dart' 4 | if (dart.library.io) 'entry/libgopeed_boot_native.dart'; 5 | 6 | abstract class LibgopeedBoot { 7 | static LibgopeedBoot? _instance; 8 | 9 | static LibgopeedBoot get instance { 10 | _instance ??= LibgopeedBoot(); 11 | return _instance!; 12 | } 13 | 14 | factory LibgopeedBoot() => create(); 15 | 16 | Future start(StartConfig cfg); 17 | 18 | Future stop(); 19 | } 20 | -------------------------------------------------------------------------------- /ui/flutter/lib/core/libgopeed_boot_stub.dart: -------------------------------------------------------------------------------- 1 | import 'libgopeed_boot.dart'; 2 | 3 | LibgopeedBoot create() => throw UnimplementedError(); 4 | -------------------------------------------------------------------------------- /ui/flutter/lib/database/entity.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | 3 | part 'entity.g.dart'; 4 | 5 | @JsonSerializable() 6 | class StartConfigEntity { 7 | String network; 8 | String address; 9 | String apiToken; 10 | 11 | StartConfigEntity({ 12 | required this.network, 13 | required this.address, 14 | required this.apiToken, 15 | }); 16 | 17 | factory StartConfigEntity.fromJson(Map json) => 18 | _$StartConfigEntityFromJson(json); 19 | Map toJson() => _$StartConfigEntityToJson(this); 20 | } 21 | 22 | @JsonSerializable() 23 | class WindowStateEntity { 24 | bool? isMaximized; 25 | double? width; 26 | double? height; 27 | 28 | WindowStateEntity({ 29 | this.isMaximized, 30 | this.width, 31 | this.height, 32 | }); 33 | 34 | factory WindowStateEntity.fromJson(Map json) => 35 | _$WindowStateEntityFromJson(json); 36 | Map toJson() => _$WindowStateEntityToJson(this); 37 | } 38 | -------------------------------------------------------------------------------- /ui/flutter/lib/database/entity.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'entity.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | StartConfigEntity _$StartConfigEntityFromJson(Map json) => 10 | StartConfigEntity( 11 | network: json['network'] as String, 12 | address: json['address'] as String, 13 | apiToken: json['apiToken'] as String, 14 | ); 15 | 16 | Map _$StartConfigEntityToJson(StartConfigEntity instance) => 17 | { 18 | 'network': instance.network, 19 | 'address': instance.address, 20 | 'apiToken': instance.apiToken, 21 | }; 22 | 23 | WindowStateEntity _$WindowStateEntityFromJson(Map json) => 24 | WindowStateEntity( 25 | isMaximized: json['isMaximized'] as bool?, 26 | width: (json['width'] as num?)?.toDouble(), 27 | height: (json['height'] as num?)?.toDouble(), 28 | ); 29 | 30 | Map _$WindowStateEntityToJson(WindowStateEntity instance) { 31 | final val = {}; 32 | 33 | void writeNotNull(String key, dynamic value) { 34 | if (value != null) { 35 | val[key] = value; 36 | } 37 | } 38 | 39 | writeNotNull('isMaximized', instance.isMaximized); 40 | writeNotNull('width', instance.width); 41 | writeNotNull('height', instance.height); 42 | return val; 43 | } 44 | -------------------------------------------------------------------------------- /ui/flutter/lib/i18n/message.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | import 'langs/en_us.dart'; 4 | import 'langs/fa_ir.dart'; 5 | import 'langs/fr_fr.dart'; 6 | import 'langs/id_id.dart'; 7 | import 'langs/it_it.dart'; 8 | import 'langs/ja_jp.dart'; 9 | import 'langs/pl_pl.dart'; 10 | import 'langs/ru_ru.dart'; 11 | import 'langs/ta_ta.dart'; 12 | import 'langs/tr_tr.dart'; 13 | import 'langs/vi_vn.dart'; 14 | import 'langs/zh_cn.dart'; 15 | import 'langs/zh_tw.dart'; 16 | import 'langs/es_es.dart'; 17 | import 'langs/uk_ua.dart'; 18 | import 'langs/hu_hu.dart'; 19 | 20 | final messages = _Messages(); 21 | 22 | class _Messages extends Translations { 23 | // just include available locales here 24 | @override 25 | Map> get keys => { 26 | ...zhCN, 27 | ...enUS, 28 | ...ruRU, 29 | ...zhTW, 30 | ...faIR, 31 | ...jaJP, 32 | ...viVN, 33 | ...taTA, 34 | ...trTR, 35 | ...plPL, 36 | ...itIT, 37 | ...idID, 38 | ...frFR, 39 | ...esES, 40 | ...ukUA, 41 | ...huHU, 42 | }; 43 | } 44 | -------------------------------------------------------------------------------- /ui/flutter/lib/theme/theme.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class GopeedTheme { 4 | static const _gopeedreenPrimaryValue = 0xFF79C476; 5 | static const _gopeedreen = 6 | MaterialColor(_gopeedreenPrimaryValue, { 7 | 50: Color(0xFFEFF8EF), 8 | 100: Color(0xFFD7EDD6), 9 | 200: Color(0xFFBCE2BB), 10 | 300: Color(0xFFA1D69F), 11 | 400: Color(0xFF8DCD8B), 12 | 500: Color(_gopeedreenPrimaryValue), 13 | 600: Color(0xFF71BE6E), 14 | 700: Color(0xFF66B663), 15 | 800: Color(0xFF5CAF59), 16 | 900: Color(0xFF49A246), 17 | }); 18 | 19 | static const _gopeedreenAccentValue = 0xFFC9FFC7; 20 | static const _gopeedreenAccent = 21 | MaterialColor(_gopeedreenAccentValue, { 22 | 100: Color(0xFFFAFFFA), 23 | 200: Color(_gopeedreenAccentValue), 24 | 400: Color(0xFF97FF94), 25 | 700: Color(0xFF7FFF7A), 26 | }); 27 | 28 | static final _light = ThemeData( 29 | useMaterial3: false, 30 | brightness: Brightness.light, 31 | primarySwatch: _gopeedreen); 32 | static final light = _light.copyWith( 33 | colorScheme: _light.colorScheme.copyWith(secondary: _gopeedreenAccent)); 34 | 35 | static final _dark = ThemeData( 36 | useMaterial3: false, 37 | brightness: Brightness.dark, 38 | primarySwatch: _gopeedreen); 39 | static final dark = _dark.copyWith( 40 | colorScheme: _dark.colorScheme.copyWith(secondary: _gopeedreenAccent)); 41 | } 42 | -------------------------------------------------------------------------------- /ui/flutter/lib/util/arch/arch.dart: -------------------------------------------------------------------------------- 1 | import 'arch_stub.dart' 2 | if (dart.library.io) 'entry/arch_native.dart' 3 | if (dart.library.html) 'entry/arch_web.dart'; 4 | 5 | // Copy from pkg/sky_engine/lib/ffi/abi.dart 6 | enum Architecture { 7 | arm, 8 | arm64, 9 | ia32, 10 | x64, 11 | riscv32, 12 | riscv64, 13 | } 14 | 15 | Architecture getArch() => doGetArch(); 16 | -------------------------------------------------------------------------------- /ui/flutter/lib/util/arch/arch_stub.dart: -------------------------------------------------------------------------------- 1 | import 'arch.dart'; 2 | 3 | Architecture doGetArch() => throw UnimplementedError(); 4 | -------------------------------------------------------------------------------- /ui/flutter/lib/util/arch/entry/arch_native.dart: -------------------------------------------------------------------------------- 1 | // ignore: avoid_web_libraries_in_flutter 2 | import 'dart:ffi'; 3 | 4 | import '../arch.dart'; 5 | 6 | Architecture doGetArch() { 7 | final currentAbi = Abi.current().toString(); 8 | final archName = currentAbi.split("_")[1]; 9 | final arch = Architecture.values.firstWhere( 10 | (element) => element.name == archName, 11 | orElse: () => Architecture.x64); 12 | return arch; 13 | } 14 | -------------------------------------------------------------------------------- /ui/flutter/lib/util/arch/entry/arch_web.dart: -------------------------------------------------------------------------------- 1 | import '../arch.dart'; 2 | 3 | Architecture doGetArch() { 4 | return Architecture.x64; 5 | } 6 | -------------------------------------------------------------------------------- /ui/flutter/lib/util/browser_download/browser_download.dart: -------------------------------------------------------------------------------- 1 | import 'browser_download_stub.dart' 2 | if (dart.library.html) 'entry/browser_download_browser.dart'; 3 | 4 | void download(String url, String name) => doDownload(url, name); 5 | -------------------------------------------------------------------------------- /ui/flutter/lib/util/browser_download/browser_download_stub.dart: -------------------------------------------------------------------------------- 1 | void doDownload(String url, String name) => throw UnimplementedError(); 2 | -------------------------------------------------------------------------------- /ui/flutter/lib/util/browser_download/entry/browser_download_browser.dart: -------------------------------------------------------------------------------- 1 | // ignore: avoid_web_libraries_in_flutter 2 | import 'dart:html' as html; 3 | 4 | void doDownload(String url, String name) { 5 | final anchorElement = html.AnchorElement(href: url); 6 | anchorElement.download = name; 7 | anchorElement.target = '_blank'; 8 | anchorElement.click(); 9 | } 10 | -------------------------------------------------------------------------------- /ui/flutter/lib/util/browser_extension_host/browser_extension_host.dart: -------------------------------------------------------------------------------- 1 | import 'browser_extension_host_stub.dart' 2 | if (dart.library.io) 'entry/browser_extension_host_native.dart'; 3 | 4 | enum Browser { chrome, edge, firefox } 5 | 6 | /// Install host binary for browser extension 7 | Future installHost() => doInstallHost(); 8 | 9 | /// Check if specified browser is installed 10 | Future checkBrowserInstalled(Browser browser) => 11 | doCheckBrowserInstalled(browser); 12 | 13 | /// Check if browser extension manifest is properly installed 14 | Future checkManifestInstalled(Browser browser) => 15 | doCheckManifestInstalled(browser); 16 | 17 | /// Install browser extension manifest 18 | Future installManifest(Browser browser) => doInstallManifest(browser); 19 | -------------------------------------------------------------------------------- /ui/flutter/lib/util/browser_extension_host/browser_extension_host_stub.dart: -------------------------------------------------------------------------------- 1 | import 'browser_extension_host.dart'; 2 | 3 | Future doInstallHost() => throw UnimplementedError(); 4 | Future doCheckBrowserInstalled(Browser browser) => 5 | throw UnimplementedError(); 6 | Future doCheckManifestInstalled(Browser browser) => 7 | throw UnimplementedError(); 8 | Future doInstallManifest(Browser browser) => throw UnimplementedError(); 9 | -------------------------------------------------------------------------------- /ui/flutter/lib/util/file_explorer.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:url_launcher/url_launcher_string.dart'; 4 | 5 | class FileExplorer { 6 | static Future openAndSelectFile(String filePath) async { 7 | if (await FileSystemEntity.isFile(filePath)) { 8 | _openFile(filePath); 9 | } else if (await FileSystemEntity.isDirectory(filePath)) { 10 | _openDirectory(filePath); 11 | } 12 | } 13 | 14 | static Future _openDirectory(String directoryPath) async { 15 | await launchUrlString("file://$directoryPath"); 16 | } 17 | 18 | static Future _openFile(String filePath) async { 19 | if (Platform.isWindows) { 20 | Process.run('explorer.exe', ['/select,', filePath]); 21 | } else if (Platform.isMacOS) { 22 | Process.run('open', ['-R', filePath]); 23 | } else if (Platform.isLinux) { 24 | _linuxOpen(filePath); 25 | } 26 | } 27 | 28 | static Future _linuxOpen(String filePath) async { 29 | if (await Process.run('which', ['xdg-open']) 30 | .then((value) => value.exitCode == 0)) { 31 | final result = await Process.run('xdg-open', [filePath]); 32 | if (result.exitCode != 0) { 33 | _openWithFileManager(filePath); 34 | } 35 | } else { 36 | _openWithFileManager(filePath); 37 | } 38 | } 39 | 40 | static Future _openWithFileManager(String filePath) async { 41 | final desktop = Platform.environment['XDG_CURRENT_DESKTOP']; 42 | if (desktop == null) { 43 | throw Exception('XDG_CURRENT_DESKTOP is not set'); 44 | } 45 | if (desktop == 'GNOME') { 46 | await Process.run('nautilus', ['--select', filePath]); 47 | } else if (desktop == 'KDE') { 48 | await Process.run('dolphin', ['--select', filePath]); 49 | } else { 50 | throw Exception('Unsupported desktop environment'); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /ui/flutter/lib/util/input_formatter.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/services.dart'; 2 | 3 | /// A [TextInputFormatter] that restricts input to a numerical range between [min] and [max]. 4 | class NumericalRangeFormatter extends TextInputFormatter { 5 | final int min; 6 | final int max; 7 | 8 | NumericalRangeFormatter({required this.min, required this.max}); 9 | 10 | @override 11 | TextEditingValue formatEditUpdate( 12 | TextEditingValue oldValue, 13 | TextEditingValue newValue, 14 | ) { 15 | if (newValue.text.isEmpty) { 16 | return newValue; 17 | } 18 | var intVal = int.tryParse(newValue.text); 19 | if (intVal == null) { 20 | return oldValue; 21 | } 22 | if (intVal < min) { 23 | return newValue.copyWith(text: min.toString()); 24 | } else if (intVal > max) { 25 | return oldValue.copyWith(text: max.toString()); 26 | } else { 27 | return newValue; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /ui/flutter/lib/util/locale_manager.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | Locale toLocale(String key) { 4 | final arr = key.split('_'); 5 | return Locale(arr[0], arr[1]); 6 | } 7 | 8 | String getLocaleKey(Locale locale) { 9 | return '${locale.languageCode}_${locale.countryCode}'; 10 | } 11 | 12 | const debugLocale = Locale('zh', 'CN'); 13 | const fallbackLocale = Locale('en', 'US'); 14 | -------------------------------------------------------------------------------- /ui/flutter/lib/util/log_util.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'package:flutter/foundation.dart'; 3 | import 'package:logger/logger.dart'; 4 | import 'package:path/path.dart' as path; 5 | import 'util.dart'; 6 | 7 | late final Logger logger; 8 | 9 | initLogger() { 10 | // if is debug mode, don't log to file 11 | logger = Logger( 12 | filter: ProductionFilter(), 13 | printer: SimplePrinter(printTime: true, colors: false), 14 | output: _buildOutput(), 15 | ); 16 | } 17 | 18 | String logsDir() { 19 | return path.join(Util.getStorageDir(), 'logs'); 20 | } 21 | 22 | _buildOutput() { 23 | // if is debug mode, don't log to file 24 | if (!kDebugMode && Util.isDesktop()) { 25 | final logDirPath = logsDir(); 26 | var logDir = Directory(logsDir()); 27 | if (!logDir.existsSync()) { 28 | logDir.createSync(); 29 | } 30 | return FileOutput(file: File('$logDirPath/client.log')); 31 | } 32 | return null; 33 | } 34 | -------------------------------------------------------------------------------- /ui/flutter/lib/util/message.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | import '../api/model/result.dart'; 4 | 5 | void showErrorMessage(msg) { 6 | final title = 'error'.tr; 7 | if (msg is Result) { 8 | Get.snackbar(title, msg.msg!); 9 | return; 10 | } 11 | if (msg is Exception) { 12 | final message = (msg as dynamic).message; 13 | if (message is Result) { 14 | Get.snackbar(title, ((msg as dynamic).message as Result).msg!); 15 | return; 16 | } 17 | if (message is String) { 18 | Get.snackbar(title, message); 19 | return; 20 | } 21 | } 22 | Get.snackbar(title, msg.toString()); 23 | } 24 | 25 | var _showMessageFlag = true; 26 | 27 | void showMessage(title, msg) { 28 | if (_showMessageFlag) { 29 | _showMessageFlag = false; 30 | Get.snackbar(title, msg); 31 | Future.delayed(const Duration(seconds: 3), () { 32 | _showMessageFlag = true; 33 | }); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /ui/flutter/lib/util/package_info.dart: -------------------------------------------------------------------------------- 1 | import 'package:package_info_plus/package_info_plus.dart'; 2 | 3 | late PackageInfo packageInfo; 4 | 5 | Future initPackageInfo() async { 6 | packageInfo = await PackageInfo.fromPlatform(); 7 | } 8 | -------------------------------------------------------------------------------- /ui/flutter/lib/util/scheme_register/scheme_register.dart: -------------------------------------------------------------------------------- 1 | import 'scheme_register_stub.dart' 2 | if (dart.library.io) 'entry/scheme_register_native.dart'; 3 | 4 | registerUrlScheme(String scheme) => doRegisterUrlScheme(scheme); 5 | 6 | unregisterUrlScheme(String scheme) => doUnregisterUrlScheme(scheme); 7 | 8 | registerDefaultTorrentClient() => doRegisterDefaultTorrentClient(); 9 | 10 | unregisterDefaultTorrentClient() => doUnregisterDefaultTorrentClient(); 11 | 12 | -------------------------------------------------------------------------------- /ui/flutter/lib/util/scheme_register/scheme_register_stub.dart: -------------------------------------------------------------------------------- 1 | doRegisterUrlScheme(String scheme) => throw UnimplementedError(); 2 | 3 | doUnregisterUrlScheme(String scheme) => throw UnimplementedError(); 4 | 5 | doRegisterDefaultTorrentClient() => throw UnimplementedError(); 6 | 7 | doUnregisterDefaultTorrentClient() => throw UnimplementedError(); 8 | -------------------------------------------------------------------------------- /ui/flutter/lib/util/win32.dart: -------------------------------------------------------------------------------- 1 | import 'package:win32_registry/win32_registry.dart'; 2 | 3 | /// Check registry key 4 | /// If the key does not exist or the value is different, return false 5 | checkRegistry(String keyPath, String valueName, String value) { 6 | RegistryKey regKey; 7 | try { 8 | regKey = Registry.openPath(RegistryHive.currentUser, path: keyPath); 9 | return regKey.getValueAsString(valueName) == value; 10 | } catch (e) { 11 | return false; 12 | } 13 | } 14 | 15 | /// Upsert registry key 16 | /// If the key does not exist, create it 17 | /// If the value does not exist or is different, update it 18 | upsertRegistry(String keyPath, String valueName, String value) { 19 | RegistryKey regKey; 20 | try { 21 | regKey = Registry.openPath(RegistryHive.currentUser, 22 | path: keyPath, desiredAccessRights: AccessRights.allAccess); 23 | } catch (e) { 24 | regKey = Registry.currentUser.createKey(keyPath); 25 | } 26 | 27 | if (regKey.getValueAsString(valueName) != value) { 28 | regKey 29 | .createValue(RegistryValue(valueName, RegistryValueType.string, value)); 30 | } 31 | regKey.close(); 32 | } 33 | -------------------------------------------------------------------------------- /ui/flutter/linux/.gitignore: -------------------------------------------------------------------------------- 1 | flutter/ephemeral 2 | -------------------------------------------------------------------------------- /ui/flutter/linux/assets/com.gopeed.Gopeed.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Name=Gopeed 3 | GenericName=Download Manager 4 | GenericName[zh_CN]=下载器 5 | GenericName[zh_TW]=下載器 6 | Comment=A modern download manager for all platforms 7 | Comment[zh_CN]=支持全平台的高速下载器 8 | Comment[zh_TW]=支持全平臺的高速下載器 9 | Terminal=false 10 | Exec=gopeed %U 11 | Icon=com.gopeed.Gopeed 12 | Type=Application 13 | Categories=Utility;Network; 14 | Keywords=Bittorrent;Downloader; 15 | MimeType=x-scheme-handler/gopeed;x-scheme-handler/magnet;application/x-bittorrent; 16 | -------------------------------------------------------------------------------- /ui/flutter/linux/main.cc: -------------------------------------------------------------------------------- 1 | #include "my_application.h" 2 | 3 | int main(int argc, char** argv) { 4 | g_autoptr(MyApplication) app = my_application_new(); 5 | return g_application_run(G_APPLICATION(app), argc, argv); 6 | } 7 | -------------------------------------------------------------------------------- /ui/flutter/linux/my_application.h: -------------------------------------------------------------------------------- 1 | #ifndef FLUTTER_MY_APPLICATION_H_ 2 | #define FLUTTER_MY_APPLICATION_H_ 3 | 4 | #include 5 | 6 | G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, 7 | GtkApplication) 8 | 9 | /** 10 | * my_application_new: 11 | * 12 | * Creates a new Flutter-based application. 13 | * 14 | * Returns: a new #MyApplication. 15 | */ 16 | MyApplication* my_application_new(); 17 | 18 | #endif // FLUTTER_MY_APPLICATION_H_ 19 | -------------------------------------------------------------------------------- /ui/flutter/linux/packaging/appimage/make_config.yaml: -------------------------------------------------------------------------------- 1 | display_name: Gopeed 2 | 3 | icon: assets/icon/icon.svg 4 | 5 | keywords: 6 | - Application 7 | - DownloadManager 8 | - Network 9 | - Utility 10 | 11 | generic_name: Download Manager 12 | 13 | categories: 14 | - Network 15 | - Utility 16 | 17 | supported_mime_type: 18 | - x-scheme-handler/gopeed 19 | - x-scheme-handler/magnet 20 | - application/x-bittorrent 21 | 22 | startup_notify: true -------------------------------------------------------------------------------- /ui/flutter/linux/packaging/deb/make_config.yaml: -------------------------------------------------------------------------------- 1 | display_name: Gopeed 2 | package_name: gopeed 3 | maintainer: 4 | name: monkeyWie 5 | email: liwei-8466@qq.com 6 | co_authors: 7 | - name: madoka773 8 | email: valigarmanda55@gmail.com 9 | priority: optional 10 | section: x11 11 | installed_size: 52360 12 | essential: false 13 | icon: assets/icon/icon.svg 14 | 15 | dependencies: 16 | - libayatana-appindicator3-1 17 | - gir1.2-ayatanaappindicator3-0.1 18 | 19 | keywords: 20 | - Application 21 | - DownloadManager 22 | - Network 23 | - Utility 24 | 25 | generic_name: Download Manager 26 | 27 | categories: 28 | - Network 29 | - Utility 30 | 31 | supported_mime_type: 32 | - x-scheme-handler/gopeed 33 | - x-scheme-handler/magnet 34 | - application/x-bittorrent 35 | 36 | startup_notify: true 37 | -------------------------------------------------------------------------------- /ui/flutter/macos/.gitignore: -------------------------------------------------------------------------------- 1 | # Flutter-related 2 | **/Flutter/ephemeral/ 3 | **/Pods/ 4 | 5 | # Xcode-related 6 | **/dgph 7 | **/xcuserdata/ 8 | -------------------------------------------------------------------------------- /ui/flutter/macos/Flutter/Flutter-Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "ephemeral/Flutter-Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ui/flutter/macos/Flutter/Flutter-Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "ephemeral/Flutter-Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ui/flutter/macos/Podfile: -------------------------------------------------------------------------------- 1 | platform :osx, '10.14' 2 | 3 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 4 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 5 | 6 | project 'Runner', { 7 | 'Debug' => :debug, 8 | 'Profile' => :release, 9 | 'Release' => :release, 10 | } 11 | 12 | def flutter_root 13 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) 14 | unless File.exist?(generated_xcode_build_settings_path) 15 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" 16 | end 17 | 18 | File.foreach(generated_xcode_build_settings_path) do |line| 19 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 20 | return matches[1].strip if matches 21 | end 22 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" 23 | end 24 | 25 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 26 | 27 | flutter_macos_podfile_setup 28 | 29 | target 'Runner' do 30 | use_frameworks! 31 | use_modular_headers! 32 | 33 | flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) 34 | end 35 | 36 | post_install do |installer| 37 | installer.pods_project.targets.each do |target| 38 | flutter_additional_macos_build_settings(target) 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /ui/flutter/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ui/flutter/macos/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ui/flutter/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ui/flutter/macos/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 3 | import app_links 4 | 5 | @main 6 | class AppDelegate: FlutterAppDelegate { 7 | public override func application(_ application: NSApplication, 8 | continue userActivity: NSUserActivity, 9 | restorationHandler: @escaping ([any NSUserActivityRestoring]) -> Void) -> Bool { 10 | 11 | guard let url = AppLinks.shared.getUniversalLink(userActivity) else { 12 | return false 13 | } 14 | 15 | AppLinks.shared.handleLink(link: url.absoluteString) 16 | 17 | return false // Returning true will stop the propagation to other packages 18 | } 19 | 20 | override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { 21 | return false 22 | } 23 | 24 | override func applicationShouldHandleReopen(_ sender: NSApplication, hasVisibleWindows flag: Bool) -> Bool { 25 | if !flag { 26 | for window in NSApp.windows { 27 | if !window.isVisible { 28 | window.setIsVisible(true) 29 | } 30 | window.makeKeyAndOrderFront(self) 31 | NSApp.activate(ignoringOtherApps: true) 32 | } 33 | } 34 | return true 35 | } 36 | 37 | override func application(_ sender: NSApplication, openFile filename: String) -> Bool { 38 | let url = URL(fileURLWithPath: filename) 39 | AppLinks.shared.handleLink(link: url.absoluteString) 40 | return false; 41 | } 42 | 43 | override func application(_ application: NSApplication, open urls: [URL]) { 44 | // Only handle the first file 45 | if(urls.isEmpty) { 46 | return 47 | } 48 | AppLinks.shared.handleLink(link: urls.first!.absoluteString) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /ui/flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GopeedLab/gopeed/7c541e40479b5e574a4ab4c40ab4d328ab275377/ui/flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png -------------------------------------------------------------------------------- /ui/flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GopeedLab/gopeed/7c541e40479b5e574a4ab4c40ab4d328ab275377/ui/flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png -------------------------------------------------------------------------------- /ui/flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GopeedLab/gopeed/7c541e40479b5e574a4ab4c40ab4d328ab275377/ui/flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png -------------------------------------------------------------------------------- /ui/flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GopeedLab/gopeed/7c541e40479b5e574a4ab4c40ab4d328ab275377/ui/flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png -------------------------------------------------------------------------------- /ui/flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GopeedLab/gopeed/7c541e40479b5e574a4ab4c40ab4d328ab275377/ui/flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png -------------------------------------------------------------------------------- /ui/flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GopeedLab/gopeed/7c541e40479b5e574a4ab4c40ab4d328ab275377/ui/flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png -------------------------------------------------------------------------------- /ui/flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GopeedLab/gopeed/7c541e40479b5e574a4ab4c40ab4d328ab275377/ui/flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png -------------------------------------------------------------------------------- /ui/flutter/macos/Runner/Configs/AppInfo.xcconfig: -------------------------------------------------------------------------------- 1 | // Application-level settings for the Runner target. 2 | // 3 | // This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the 4 | // future. If not, the values below would default to using the project name when this becomes a 5 | // 'flutter create' template. 6 | 7 | // The application's name. By default this is also the title of the Flutter window. 8 | PRODUCT_NAME = Gopeed 9 | 10 | // The application's bundle identifier 11 | PRODUCT_BUNDLE_IDENTIFIER = com.gopeed.gopeed 12 | 13 | // The copyright displayed in application information 14 | PRODUCT_COPYRIGHT = Copyright © 2022 com.example. All rights reserved. 15 | -------------------------------------------------------------------------------- /ui/flutter/macos/Runner/Configs/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "../../Flutter/Flutter-Debug.xcconfig" 2 | #include "Warnings.xcconfig" 3 | -------------------------------------------------------------------------------- /ui/flutter/macos/Runner/Configs/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "../../Flutter/Flutter-Release.xcconfig" 2 | #include "Warnings.xcconfig" 3 | -------------------------------------------------------------------------------- /ui/flutter/macos/Runner/Configs/Warnings.xcconfig: -------------------------------------------------------------------------------- 1 | WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings 2 | GCC_WARN_UNDECLARED_SELECTOR = YES 3 | CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES 4 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE 5 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES 6 | CLANG_WARN_PRAGMA_PACK = YES 7 | CLANG_WARN_STRICT_PROTOTYPES = YES 8 | CLANG_WARN_COMMA = YES 9 | GCC_WARN_STRICT_SELECTOR_MATCH = YES 10 | CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES 11 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES 12 | GCC_WARN_SHADOW = YES 13 | CLANG_WARN_UNREACHABLE_CODE = YES 14 | -------------------------------------------------------------------------------- /ui/flutter/macos/Runner/DebugProfile.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.cs.allow-jit 6 | 7 | com.apple.security.files.bookmarks.app-scope 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ui/flutter/macos/Runner/MainFlutterWindow.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 3 | import window_manager 4 | 5 | class MainFlutterWindow: NSWindow { 6 | override func awakeFromNib() { 7 | let flutterViewController = FlutterViewController.init() 8 | let windowFrame = self.frame 9 | self.contentViewController = flutterViewController 10 | self.setFrame(windowFrame, display: true) 11 | 12 | RegisterGeneratedPlugins(registry: flutterViewController) 13 | 14 | super.awakeFromNib() 15 | } 16 | 17 | override public func order(_ place: NSWindow.OrderingMode, relativeTo otherWin: Int) { 18 | super.order(place, relativeTo: otherWin) 19 | hiddenWindowAtLaunch() 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /ui/flutter/macos/Runner/Release.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.files.bookmarks.app-scope 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ui/flutter/test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility that Flutter provides. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | 8 | import 'package:flutter_test/flutter_test.dart'; 9 | 10 | 11 | void main() { 12 | testWidgets('Counter increments smoke test', (WidgetTester tester) async {}); 13 | } 14 | -------------------------------------------------------------------------------- /ui/flutter/web/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GopeedLab/gopeed/7c541e40479b5e574a4ab4c40ab4d328ab275377/ui/flutter/web/favicon.png -------------------------------------------------------------------------------- /ui/flutter/web/icons/Icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GopeedLab/gopeed/7c541e40479b5e574a4ab4c40ab4d328ab275377/ui/flutter/web/icons/Icon-192.png -------------------------------------------------------------------------------- /ui/flutter/web/icons/Icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GopeedLab/gopeed/7c541e40479b5e574a4ab4c40ab4d328ab275377/ui/flutter/web/icons/Icon-512.png -------------------------------------------------------------------------------- /ui/flutter/web/icons/Icon-maskable-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GopeedLab/gopeed/7c541e40479b5e574a4ab4c40ab4d328ab275377/ui/flutter/web/icons/Icon-maskable-192.png -------------------------------------------------------------------------------- /ui/flutter/web/icons/Icon-maskable-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GopeedLab/gopeed/7c541e40479b5e574a4ab4c40ab4d328ab275377/ui/flutter/web/icons/Icon-maskable-512.png -------------------------------------------------------------------------------- /ui/flutter/web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | Gopeed 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /ui/flutter/web/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gopeed", 3 | "short_name": "gopeed", 4 | "start_url": ".", 5 | "display": "standalone", 6 | "background_color": "#hexcode", 7 | "theme_color": "#hexcode", 8 | "description": "A new Flutter project.", 9 | "orientation": "portrait-primary", 10 | "prefer_related_applications": false, 11 | "icons": [ 12 | { 13 | "src": "icons/Icon-192.png", 14 | "sizes": "192x192", 15 | "type": "image/png" 16 | }, 17 | { 18 | "src": "icons/Icon-512.png", 19 | "sizes": "512x512", 20 | "type": "image/png" 21 | }, 22 | { 23 | "src": "icons/Icon-maskable-192.png", 24 | "sizes": "192x192", 25 | "type": "image/png", 26 | "purpose": "maskable" 27 | }, 28 | { 29 | "src": "icons/Icon-maskable-512.png", 30 | "sizes": "512x512", 31 | "type": "image/png", 32 | "purpose": "maskable" 33 | } 34 | ] 35 | } -------------------------------------------------------------------------------- /ui/flutter/windows/.gitignore: -------------------------------------------------------------------------------- 1 | flutter/ephemeral/ 2 | 3 | # Visual Studio user-specific files. 4 | *.suo 5 | *.user 6 | *.userosscache 7 | *.sln.docstates 8 | 9 | # Visual Studio build-related files. 10 | x64/ 11 | x86/ 12 | 13 | # Visual Studio cache files 14 | # files ending in .cache can be ignored 15 | *.[Cc]ache 16 | # but keep track of directories ending in .cache 17 | !*.[Cc]ache/ 18 | -------------------------------------------------------------------------------- /ui/flutter/windows/runner/flutter_window.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_FLUTTER_WINDOW_H_ 2 | #define RUNNER_FLUTTER_WINDOW_H_ 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #include "win32_window.h" 10 | 11 | // A window that does nothing but host a Flutter view. 12 | class FlutterWindow : public Win32Window { 13 | public: 14 | // Creates a new FlutterWindow hosting a Flutter view running |project|. 15 | explicit FlutterWindow(const flutter::DartProject& project); 16 | virtual ~FlutterWindow(); 17 | 18 | protected: 19 | // Win32Window: 20 | bool OnCreate() override; 21 | void OnDestroy() override; 22 | LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, 23 | LPARAM const lparam) noexcept override; 24 | 25 | private: 26 | // The project to run. 27 | flutter::DartProject project_; 28 | 29 | // The Flutter instance hosted by this window. 30 | std::unique_ptr flutter_controller_; 31 | }; 32 | 33 | #endif // RUNNER_FLUTTER_WINDOW_H_ 34 | -------------------------------------------------------------------------------- /ui/flutter/windows/runner/resource.h: -------------------------------------------------------------------------------- 1 | //{{NO_DEPENDENCIES}} 2 | // Microsoft Visual C++ generated include file. 3 | // Used by Runner.rc 4 | // 5 | #define IDI_APP_ICON 101 6 | 7 | // Next default values for new objects 8 | // 9 | #ifdef APSTUDIO_INVOKED 10 | #ifndef APSTUDIO_READONLY_SYMBOLS 11 | #define _APS_NEXT_RESOURCE_VALUE 102 12 | #define _APS_NEXT_COMMAND_VALUE 40001 13 | #define _APS_NEXT_CONTROL_VALUE 1001 14 | #define _APS_NEXT_SYMED_VALUE 101 15 | #endif 16 | #endif 17 | -------------------------------------------------------------------------------- /ui/flutter/windows/runner/resources/app_icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GopeedLab/gopeed/7c541e40479b5e574a4ab4c40ab4d328ab275377/ui/flutter/windows/runner/resources/app_icon.ico -------------------------------------------------------------------------------- /ui/flutter/windows/runner/runner.exe.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PerMonitorV2 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /ui/flutter/windows/runner/utils.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_UTILS_H_ 2 | #define RUNNER_UTILS_H_ 3 | 4 | #include 5 | #include 6 | 7 | // Creates a console for the process, and redirects stdout and stderr to 8 | // it for both the runner and the Flutter library. 9 | void CreateAndAttachConsole(); 10 | 11 | // Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string 12 | // encoded in UTF-8. Returns an empty std::string on failure. 13 | std::string Utf8FromUtf16(const wchar_t* utf16_string); 14 | 15 | // Gets the command line arguments passed in as a std::vector, 16 | // encoded in UTF-8. Returns an empty std::vector on failure. 17 | std::vector GetCommandLineArguments(); 18 | 19 | #endif // RUNNER_UTILS_H_ 20 | --------------------------------------------------------------------------------