├── .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 |
--------------------------------------------------------------------------------