Full Code of GopeedLab/gopeed for AI

main a5cd53f94c18 cached
419 files
1.7 MB
508.4k tokens
2080 symbols
1 requests
Download .txt
Showing preview only (1,916K chars total). Download the full file or copy to clipboard to get everything.
Repository: GopeedLab/gopeed
Branch: main
Commit: a5cd53f94c18
Files: 419
Total size: 1.7 MB

Directory structure:
gitextract_l2o017xa/

├── .dockerignore
├── .github/
│   ├── CODEOWNERS
│   ├── FUNDING.yml
│   ├── ISSUE_TEMPLATE.md
│   ├── release-drafter.yml
│   └── workflows/
│       ├── build.yml
│       ├── release.yml
│       ├── scripts/
│       │   └── flutter_local_font.dart
│       ├── 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
├── _examples/
│   └── basic/
│       └── main.go
├── bind/
│   ├── desktop/
│   │   └── main.go
│   └── mobile/
│       └── main.go
├── cmd/
│   ├── api/
│   │   └── main.go
│   ├── banner.txt
│   ├── gopeed/
│   │   ├── flags.go
│   │   └── main.go
│   ├── host/
│   │   ├── dail_other.go
│   │   ├── dail_windows.go
│   │   └── main.go
│   ├── server.go
│   ├── updater/
│   │   ├── main.go
│   │   ├── updater_darwin.go
│   │   ├── updater_linux.go
│   │   └── updater_windows.go
│   └── web/
│       ├── flags.go
│       ├── flags_test.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
│   │   │   └── testdata/
│   │   │       ├── test.torrent
│   │   │       ├── test.unclean.torrent
│   │   │       └── ubuntu-22.04-live-server-amd64.iso.torrent
│   │   ├── ed2k/
│   │   │   ├── config.go
│   │   │   ├── fetcher.go
│   │   │   └── fetcher_test.go
│   │   └── http/
│   │       ├── config.go
│   │       ├── fetcher.go
│   │       ├── fetcher_manager.go
│   │       ├── fetcher_test.go
│   │       ├── filename_parse_test.go
│   │       ├── helper.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
│   │   │   │       └── tls_fingerprint.go
│   │   │   ├── polyfill/
│   │   │   │   ├── out/
│   │   │   │   │   └── index.js
│   │   │   │   ├── 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
│   │   ├── extract.go
│   │   ├── extract_7z.go
│   │   ├── extract_queue.go
│   │   ├── extract_queue_test.go
│   │   ├── extract_rar.go
│   │   ├── extract_test.go
│   │   ├── extract_zip.go
│   │   ├── model.go
│   │   ├── model_test.go
│   │   ├── script.go
│   │   ├── script_test.go
│   │   ├── script_unix_test.go
│   │   ├── script_windows_test.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
│   │   │   └── scripts/
│   │   │       ├── env_dump.bat
│   │   │       ├── env_dump.sh
│   │   │       ├── move.bat
│   │   │       ├── move.sh
│   │   │       ├── write_output1.bat
│   │   │       ├── write_output1.sh
│   │   │       ├── write_output2.bat
│   │   │       └── write_output2.sh
│   │   ├── webhook.go
│   │   └── webhook_test.go
│   ├── protocol/
│   │   ├── bt/
│   │   │   └── model.go
│   │   ├── ed2k/
│   │   │   └── model.go
│   │   └── http/
│   │       └── model.go
│   ├── rest/
│   │   ├── api.go
│   │   ├── config.go
│   │   ├── gizp_middleware.go
│   │   ├── model/
│   │   │   ├── extension.go
│   │   │   ├── result.go
│   │   │   ├── server.go
│   │   │   ├── task.go
│   │   │   └── webhook.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/
        │   │       │       │   └── launch_background.xml
        │   │       │       ├── drawable-v21/
        │   │       │       │   └── launch_background.xml
        │   │       │       ├── values/
        │   │       │       │   └── styles.xml
        │   │       │       └── values-night/
        │   │       │           └── styles.xml
        │   │       └── profile/
        │   │           └── AndroidManifest.xml
        │   ├── build.gradle
        │   ├── gradle/
        │   │   └── wrapper/
        │   │       └── gradle-wrapper.properties
        │   ├── gradle.properties
        │   └── settings.gradle
        ├── assets/
        │   └── exec/
        │       └── .gitkeep
        ├── build.yaml
        ├── distribute_options.yaml
        ├── include/
        │   └── libgopeed.h
        ├── ios/
        │   ├── .gitignore
        │   ├── Flutter/
        │   │   ├── AppFrameworkInfo.plist
        │   │   ├── Debug.xcconfig
        │   │   └── Release.xcconfig
        │   ├── Podfile
        │   ├── Runner/
        │   │   ├── AppDelegate.swift
        │   │   ├── Assets.xcassets/
        │   │   │   ├── AppIcon.appiconset/
        │   │   │   │   └── Contents.json
        │   │   │   └── LaunchImage.imageset/
        │   │   │       ├── Contents.json
        │   │   │       └── README.md
        │   │   ├── Base.lproj/
        │   │   │   ├── LaunchScreen.storyboard
        │   │   │   └── Main.storyboard
        │   │   ├── Info.plist
        │   │   ├── Runner-Bridging-Header.h
        │   │   └── Runner.entitlements
        │   ├── 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
        │   └── ShareExtension/
        │       ├── Base.lproj/
        │       │   └── MainInterface.storyboard
        │       ├── Info.plist
        │       ├── ShareExtension.entitlements
        │       └── ShareViewController.swift
        ├── lib/
        │   ├── api/
        │   │   ├── api.dart
        │   │   ├── gopeed_site_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
        │   │       ├── login.dart
        │   │       ├── login.g.dart
        │   │       ├── meta.dart
        │   │       ├── meta.g.dart
        │   │       ├── options.dart
        │   │       ├── options.g.dart
        │   │       ├── request.dart
        │   │       ├── request.g.dart
        │   │       ├── resolve_result.dart
        │   │       ├── resolve_result.g.dart
        │   │       ├── resolve_task.dart
        │   │       ├── resolve_task.g.dart
        │   │       ├── resource.dart
        │   │       ├── resource.g.dart
        │   │       ├── result.dart
        │   │       ├── result.g.dart
        │   │       ├── store_extension.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
        │   │   │   │   └── views/
        │   │   │   │       └── create_view.dart
        │   │   │   ├── extension/
        │   │   │   │   ├── bindings/
        │   │   │   │   │   └── extension_binding.dart
        │   │   │   │   ├── controllers/
        │   │   │   │   │   └── extension_controller.dart
        │   │   │   │   └── views/
        │   │   │   │       ├── extension_card.dart
        │   │   │   │       ├── extension_detail_view.dart
        │   │   │   │       └── extension_view.dart
        │   │   │   ├── history/
        │   │   │   │   └── views/
        │   │   │   │       └── history_view.dart
        │   │   │   ├── home/
        │   │   │   │   ├── bindings/
        │   │   │   │   │   └── home_binding.dart
        │   │   │   │   ├── controllers/
        │   │   │   │   │   └── home_controller.dart
        │   │   │   │   └── views/
        │   │   │   │       └── home_view.dart
        │   │   │   ├── login/
        │   │   │   │   ├── bindings/
        │   │   │   │   │   └── login_binding.dart
        │   │   │   │   ├── controllers/
        │   │   │   │   │   └── login_controller.dart
        │   │   │   │   └── views/
        │   │   │   │       └── login_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
        │   │   ├── rpc/
        │   │   │   └── rpc.dart
        │   │   ├── services/
        │   │   │   └── notification_service.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/
        │   │   │   ├── ca_es.dart
        │   │   │   ├── de_de.dart
        │   │   │   ├── 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
        │   │   │   ├── pt_br.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/
        │       ├── analytics.dart
        │       ├── 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
        │   ├── Runner/
        │   │   ├── AppDelegate.swift
        │   │   ├── Assets.xcassets/
        │   │   │   └── AppIcon.appiconset/
        │   │   │       └── Contents.json
        │   │   ├── Base.lproj/
        │   │   │   └── MainMenu.xib
        │   │   ├── Configs/
        │   │   │   ├── AppInfo.xcconfig
        │   │   │   ├── Debug.xcconfig
        │   │   │   ├── Release.xcconfig
        │   │   │   └── Warnings.xcconfig
        │   │   ├── DebugProfile.entitlements
        │   │   ├── Info.plist
        │   │   ├── MainFlutterWindow.swift
        │   │   └── Release.entitlements
        │   ├── Runner.xcodeproj/
        │   │   ├── project.pbxproj
        │   │   ├── project.xcworkspace/
        │   │   │   └── xcshareddata/
        │   │   │       └── IDEWorkspaceChecks.plist
        │   │   └── xcshareddata/
        │   │       └── xcschemes/
        │   │           └── Runner.xcscheme
        │   └── Runner.xcworkspace/
        │       ├── contents.xcworkspacedata
        │       └── xcshareddata/
        │           └── IDEWorkspaceChecks.plist
        ├── pubspec.yaml
        ├── test/
        │   └── widget_test.dart
        ├── web/
        │   ├── 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
                ├── runner.exe.manifest
                ├── utils.cpp
                ├── utils.h
                ├── win32_window.cpp
                └── win32_window.h

================================================
FILE CONTENTS
================================================

================================================
FILE: .dockerignore
================================================
.git

*.data
*.log

node_modules
**/node_modules

Dockerfile
.dockerignore

.github
_docs
_examples
bin
ui

================================================
FILE: .github/CODEOWNERS
================================================
* @monkeyWie


================================================
FILE: .github/FUNDING.yml
================================================
# These are supported funding model platforms

#github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
#patreon: # Replace with a single Patreon username
#open_collective: # Replace with a single Open Collective username
#ko_fi: # Replace with a single Ko-fi username
#tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
#community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
#liberapay: # Replace with a single Liberapay username
#issuehunt: # Replace with a single IssueHunt username
#otechie: # Replace with a single Otechie username
#custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
# github: monkeyWie
ko_fi: gopeed
custom: https://gopeed.com/docs/donate


================================================
FILE: .github/ISSUE_TEMPLATE.md
================================================
<!--
Please use the following template to create an issue.
-->
### Description(required)
### App Version(required)
### OS Version(required)
### Snapshots
### Log

================================================
FILE: .github/release-drafter.yml
================================================
name-template: "v$NEXT_PATCH_VERSION 🌈"
tag-template: "v$NEXT_PATCH_VERSION"
categories:
  - title: "🆕 Features"
    labels:
      - "feat"
      - "feature"
      - "enhancement"
  - title: "🐛 Bug Fixes"
    labels:
      - "fix"
      - "bugfix"
      - "bug"
  - title: "🔧 Performance Improvements"
    labels:
      - "perf"
  - title: "🧪 Tests"
    label: "test"
  - title: "🧰 Maintenance"
    label: "chore"
  - title: "📖 Document"
    label: "docs"
  - title: "🚀 CI/CD"
    label: "ci"
  - title: "🌎 Internationalization"
    label: "translation"
change-template: "- $TITLE @$AUTHOR (#$NUMBER)"
version-resolver:
  major:
    labels:
      - "major"
  minor:
    labels:
      - "minor"
  patch:
    labels:
      - "patch"
  default: patch
template: |
  <table>
    <tbody>
      <tr>
        <td rowspan="4">🪟 Windows</td>
        <td rowspan="2"><code>EXE</code></td>
        <td>amd64</td>
        <td><a href="https://gopeed.com/api/download?tpl=Gopeed-v$NEXT_PATCH_VERSION-windows-amd64.zip">📥</a></td>
      </tr>
      <tr>
        <td>arm64</td>
        <td><a href="https://gopeed.com/api/download?tpl=Gopeed-v$NEXT_PATCH_VERSION-windows-arm64.zip">📥</a></td>
      </tr>
      <tr>
        <td rowspan="2"><code>Portable</code></td>
        <td>amd64</td>
        <td><a href="https://gopeed.com/api/download?tpl=Gopeed-v$NEXT_PATCH_VERSION-windows-amd64-portable.zip">📥</a></td>
      </tr>
      <tr>
        <td>arm64</td>
        <td><a href="https://gopeed.com/api/download?tpl=Gopeed-v$NEXT_PATCH_VERSION-windows-arm64-portable.zip">📥</a></td>
      </tr>
      <tr>
        <td rowspan="3">🍎 MacOS</td>
        <td rowspan="3"><code>DMG</code></td>
        <td>universal</td>
        <td><a href="https://gopeed.com/api/download?tpl=Gopeed-v$NEXT_PATCH_VERSION-macos.dmg">📥</a></td>
      </tr>
      <tr>
        <td>amd64</td>
        <td><a href="https://gopeed.com/api/download?tpl=Gopeed-v$NEXT_PATCH_VERSION-macos-amd64.dmg">📥</a></td>
      </tr>
      <tr>
        <td>arm64</td>
        <td><a href="https://gopeed.com/api/download?tpl=Gopeed-v$NEXT_PATCH_VERSION-macos-arm64.dmg">📥</a></td>
      </tr>
      <tr>
        <td rowspan="6">🐧 Linux</td>
        <td><code>Flathub</code></td>
        <td>amd64</td>
        <td><a href="https://flathub.org/apps/com.gopeed.Gopeed">📥</a></td>
      </tr>
      <tr>
        <td><code>SNAP</code></td>
        <td>amd64</td>
        <td><a href="https://snapcraft.io/gopeed">📥</a></td>
      </tr>
      <tr>
        <td rowspan="2"><code>DEB</code></td>
        <td>amd64</td>
        <td><a href="https://gopeed.com/api/download?tpl=Gopeed-v$NEXT_PATCH_VERSION-linux-amd64.deb">📥</a></td>
      </tr>
      <tr>
        <td>arm64</td>
        <td><a href="https://gopeed.com/api/download?tpl=Gopeed-v$NEXT_PATCH_VERSION-linux-arm64.deb">📥</a></td>
      </tr>
      <tr>
        <td rowspan="2"><code>AppImage</code></td>
        <td>amd64</td>
        <td><a href="https://gopeed.com/api/download?tpl=Gopeed-v$NEXT_PATCH_VERSION-linux-amd64.AppImage">📥</a></td>
      </tr>
      <tr>
        <td>arm64</td>
        <td><a href="https://gopeed.com/api/download?tpl=Gopeed-v$NEXT_PATCH_VERSION-linux-arm64.AppImage">📥</a></td>
      </tr>
      <tr>
        <td rowspan="4">🤖 Android</td>
        <td rowspan="4"><code>APK</code></td>
        <td>universal</td>
        <td><a href="https://gopeed.com/api/download?tpl=Gopeed-v$NEXT_PATCH_VERSION-android.apk">📥</a></td>
      </tr>
      <tr>
        <td>armeabi-v7a</td>
        <td><a href="https://gopeed.com/api/download?tpl=Gopeed-v$NEXT_PATCH_VERSION-android-armeabi-v7a.apk">📥</a></td>
      </tr>
      <tr>
        <td>arm64-v8a</td>
        <td><a href="https://gopeed.com/api/download?tpl=Gopeed-v$NEXT_PATCH_VERSION-android-arm64-v8a.apk">📥</a></td>
      </tr>
      <tr>
        <td>x86_64</td>
        <td><a href="https://gopeed.com/api/download?tpl=Gopeed-v$NEXT_PATCH_VERSION-android-x86_64.apk">📥</a></td>
      </tr>
      <tr>
        <td>📱 iOS</td>
        <td><code>IPA</code></td>
        <td>universal</td>
        <td><a href="https://gopeed.com/api/download?tpl=Gopeed-v$NEXT_PATCH_VERSION-ios.ipa">📥</a></td>
      </tr>
      <tr>
        <td>🐳 Docker</td>
        <td>-</td>
        <td>universal</td>
        <td><a href="https://hub.docker.com/r/liwei2633/gopeed">📥</a></td>
      </tr>
      <tr>
        <td rowspan="2">💾 Qnap</td>
        <td rowspan="2"><code>QPKG</code></td>
        <td>amd64</td>
        <td><a href="https://gopeed.com/api/download?tpl=gopeed-v$NEXT_PATCH_VERSION-qnap-amd64.qpkg">📥</a></td>
      </tr>
      <tr>
        <td>arm64</td>
        <td><a href="https://gopeed.com/api/download?tpl=gopeed-v$NEXT_PATCH_VERSION-qnap-arm64.qpkg">📥</a></td>
      </tr>
      <tr>
        <td rowspan="8">🌐 Web</td>
        <td rowspan="3"><code>Windows</code></td>
        <td>amd64</td>
        <td><a href="https://gopeed.com/api/download?tpl=gopeed-web-v$NEXT_PATCH_VERSION-windows-amd64.zip">📥</a></td>
      </tr>
      <tr>
        <td>arm64</td>
        <td><a href="https://gopeed.com/api/download?tpl=gopeed-web-v$NEXT_PATCH_VERSION-windows-arm64.zip">📥</a></td>
      </tr>
      <tr>
        <td>386</td>
        <td><a href="https://gopeed.com/api/download?tpl=gopeed-web-v$NEXT_PATCH_VERSION-windows-386.zip">📥</a></td>
      </tr>
      <tr>
        <td rowspan="2"><code>MacOS</code></td>
        <td>amd64</td>
        <td><a href="https://gopeed.com/api/download?tpl=gopeed-web-v$NEXT_PATCH_VERSION-macos-amd64.zip">📥</a></td>
      </tr>
      <tr>
        <td>arm64</td>
        <td><a href="https://gopeed.com/api/download?tpl=gopeed-web-v$NEXT_PATCH_VERSION-macos-arm64.zip">📥</a></td>
      </tr>
      <tr>
        <td rowspan="3"><code>Linux</code></td>
        <td>amd64</td>
        <td><a href="https://gopeed.com/api/download?tpl=gopeed-web-v$NEXT_PATCH_VERSION-linux-amd64.zip">📥</a></td>
      </tr>
      <tr>
        <td>arm64</td>
        <td><a href="https://gopeed.com/api/download?tpl=gopeed-web-v$NEXT_PATCH_VERSION-linux-arm64.zip">📥</a></td>
      </tr>
      <tr>
        <td>386</td>
        <td><a href="https://gopeed.com/api/download?tpl=gopeed-web-v$NEXT_PATCH_VERSION-linux-386.zip">📥</a></td>
      </tr>
    </tbody>
  </table>

  # Release notes

  $CHANGES

  # 更新日志

  $CHANGES


================================================
FILE: .github/workflows/build.yml
================================================
name: build

on:
  workflow_dispatch:
    inputs:
      platform:
        description: "Build platform"
        required: true
        default: "all"
      test:
        description: "Test mode"
        required: true
        default: "false"

env:
  GO_VERSION: "1.24"
  FLUTTER_VERSION: "3.41.2"
  GA4_MEASUREMENT_ID: ${{ secrets.GA4_MEASUREMENT_ID }}
  GA4_API_SECRET: ${{ secrets.GA4_API_SECRET }}

permissions:
  contents: write

jobs:
  get-release:
    runs-on: ubuntu-latest
    outputs:
      version: ${{ steps.get-release.outputs.version }}
      upload_url: ${{ steps.get-release.outputs.upload_url }}

    steps:
      - uses: monkeyWie/get-latest-release@v2.1
        id: get-release
        with:
          myToken: ${{ github.token }}

  build-windows:
    if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'windows' }}
    needs: [get-release]
    strategy:
      fail-fast: false
      matrix:
        include:
          - arch: arm64
            os: windows-11-arm
            llvm_ver: "20251202" # Only ARM64
            flutter_channel: "main"
            flutter_version: "7e1c8868"
          - arch: amd64
            os: windows-2022
    runs-on: ${{ matrix.os }}

    steps:
      - uses: actions/checkout@v3
      - name: Enable long paths for flutter main branch checks
        run: |
          git config --global core.longpaths true

      # Install LLVM environment only on ARM64
      - name: Install llvm-mingw-ucrt-aarch64 (ARM64 only)
        if: matrix.arch == 'arm64'
        run: |
          $ver = "${{ matrix.llvm_ver }}"
          $url = "https://github.com/mstorsjo/llvm-mingw/releases/download/$ver/llvm-mingw-$ver-ucrt-aarch64.zip"
          $zip = "$env:RUNNER_TEMP\\llvm.zip"
          $extract = "$env:RUNNER_TEMP\\extract"
          $target = "C:\\clangarm64"

          curl -L $url -o $zip
          rm -r -fo $extract,$target -ea Ignore
          mkdir $extract | Out-Null

          tar -xf $zip -C $extract
          mv (Get-ChildItem $extract)[0].FullName $target

          $b = "$target\\bin"
          "CC=$b\\clang.exe"        >> $env:GITHUB_ENV
          "CXX=$b\\clang++.exe"     >> $env:GITHUB_ENV
          "CLANGARM64_BIN=$b"       >> $env:GITHUB_ENV
          "CGO_ENABLED=1"           >> $env:GITHUB_ENV
          "CLANGARM64_ROOT=$target" >> $env:GITHUB_ENV
          $b >> $env:GITHUB_PATH

      - uses: actions/setup-go@v4
        with:
          go-version: ${{ env.GO_VERSION }}
      - uses: subosito/flutter-action@v2
        with:
          channel: ${{ matrix.flutter_channel || 'stable' }}
          flutter-version: ${{ matrix.flutter_version || env.FLUTTER_VERSION }}
      - name: Build
        env:
          VERSION: ${{ needs.get-release.outputs.version }}
          GOARCH: ${{ matrix.arch }}
        run: |
          Invoke-WebRequest -Uri "https://github.com/jrsoftware/issrc/raw/main/Files/Languages/Unofficial/ChineseSimplified.isl" -OutFile "ChineseSimplified.isl"
          mv ChineseSimplified.isl "C:\Program Files (x86)\Inno Setup 6\Languages\"

          go build -tags nosqlite -ldflags="-w -s -X github.com/GopeedLab/gopeed/pkg/base.Version=$env:VERSION" -buildmode=c-shared -o ui/flutter/windows/libgopeed.dll github.com/GopeedLab/gopeed/bind/desktop
          go build -ldflags="-w -s" -o ui/flutter/assets/exec/host.exe github.com/GopeedLab/gopeed/cmd/host
          go build -ldflags="-w -s" -o ui/flutter/assets/exec/updater.exe github.com/GopeedLab/gopeed/cmd/updater
          cd ui/flutter
          $TAG = "v$env:VERSION"
          flutter build windows --dart-define="UPDATE_CHANNEL=windowsPortable" --dart-define="GA4_MEASUREMENT_ID=${{ env.GA4_MEASUREMENT_ID }}" --dart-define="GA4_API_SECRET=${{ env.GA4_API_SECRET }}"
          $system = "C:\Windows\System32"
          if ("${{ matrix.arch }}" -eq "amd64") {
              # for amd64
              $release = "build\windows\x64\runner\Release\"
              $mingw = "C:\Program Files\Git\mingw64\bin"
              cp $mingw\libstdc++-6.dll $release
              cp $mingw\libgcc_s_seh-1.dll $release
          } else {
              # for ARM64
              $release = "build\windows\arm64\runner\Release\"
              $mingw = "$env:CLANGARM64_ROOT\aarch64-w64-mingw32\bin"
              cp $mingw\libc++.dll $release
              cp $mingw\libunwind.dll $release
          }
          cp $mingw\libwinpthread-1.dll $release
          cp $system\msvcp140.dll $release
          cp $system\vcruntime140.dll $release
          cp $system\vcruntime140_1.dll $release

          New-Item -Path build\windows\Output -ItemType Directory
          Compress-Archive -Path "$release*" -DestinationPath "build\windows\Output\Gopeed-$TAG-windows-${{ matrix.arch }}-portable.zip"

          flutter build windows --dart-define="UPDATE_CHANNEL=windowsInstaller" --dart-define="GA4_MEASUREMENT_ID=${{ env.GA4_MEASUREMENT_ID }}" --dart-define="GA4_API_SECRET=${{ env.GA4_API_SECRET }}"
          cd build/windows
          echo @"
          ; Script generated by the Inno Setup Script Wizard.
          ; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!

          #define MyAppName "Gopeed"
          #define MyAppVersion "$env:VERSION"
          #define MyAppPublisher "monkeyWie"
          #define MyAppURL "https://gopeed.com"
          #define MyAppExeName "gopeed.exe"

          [Setup]
          ; NOTE: The value of AppId uniquely identifies this application. Do not use the same AppId value in installers for other applications.
          ; (To generate a new GUID, click Tools | Generate GUID inside the IDE.)
          AppId={{5960F34D-1E42-402C-8C85-DE2FF24CBAE4}
          AppName={#MyAppName}
          AppVersion={#MyAppVersion}
          ;AppVerName={#MyAppName} {#MyAppVersion}
          AppPublisher={#MyAppPublisher}
          AppPublisherURL={#MyAppURL}
          AppSupportURL={#MyAppURL}
          AppUpdatesURL={#MyAppURL}
          DefaultDirName={autopf}\gopeed
          DisableProgramGroupPage=yes
          LicenseFile=..\..\..\..\LICENSE
          ; Remove the following line to run in administrative install mode (install for all users.)
          PrivilegesRequired=lowest
          OutputBaseFilename=gopeed
          SetupIconFile=..\..\assets\icon\icon.ico
          UninstallDisplayIcon={app}\{#MyAppExeName}
          Compression=lzma
          SolidCompression=yes
          WizardStyle=modern
          LanguageDetectionMethod=uilanguage
          ShowLanguageDialog=yes
          CloseApplications=force

          [Languages]
          Name: "english"; MessagesFile: "compiler:Default.isl"
          Name: "chinesesimplified"; MessagesFile: "compiler:Languages\ChineseSimplified.isl"

          [Tasks]
          Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked

          [Files]
          Source: ".\${{ matrix.arch == 'amd64' && 'x64' || 'arm64' }}\runner\Release\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs;
          ; NOTE: Don't use "Flags: ignoreversion" on any shared system files

          [UninstallDelete]
          Type: filesandordirs; Name: "{app}"

          [Icons]
          Name: "{autoprograms}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"
          Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon

          [Run]
          Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent
          "@ > setup.iss
          iscc.exe setup.iss
          mv "Output\gopeed.exe" "Output\Gopeed-$TAG-windows-${{ matrix.arch }}.exe"
          Compress-Archive -Path "Output\Gopeed-$TAG-windows-${{ matrix.arch }}.exe" -DestinationPath "Output\Gopeed-$TAG-windows-${{ matrix.arch }}.zip"
      - name: Upload
        uses: shogo82148/actions-upload-release-asset@v1
        with:
          upload_url: ${{ needs.get-release.outputs.upload_url }}
          asset_path: ui/flutter/build/windows/Output/*
          overwrite: true
  build-macos-amd64-lib:
    if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'macos' }}
    runs-on: macos-15-intel
    needs: [get-release]
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-go@v4
        with:
          go-version: ${{ env.GO_VERSION }}
      - name: Build
        env:
          VERSION: ${{ needs.get-release.outputs.version }}
        run: |
          go build -tags nosqlite -ldflags="-w -s -X github.com/GopeedLab/gopeed/pkg/base.Version=$VERSION" -buildmode=c-shared -o libgopeed.dylib github.com/GopeedLab/gopeed/bind/desktop
          go build -ldflags="-w -s" github.com/GopeedLab/gopeed/cmd/host
          go build -ldflags="-w -s" github.com/GopeedLab/gopeed/cmd/updater
      - uses: actions/upload-artifact@v4
        with:
          name: macos-amd64-lib
          path: |
            libgopeed.dylib
            host
            updater
  build-macos:
    if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'macos' }}
    runs-on: macos-latest
    strategy:
      matrix:
        channel: [arm64, amd64, universal]
    needs: [get-release, build-macos-amd64-lib]
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-go@v4
        with:
          go-version: ${{ env.GO_VERSION }}
      - uses: actions/setup-node@v3
        with:
          node-version: 16
      - uses: subosito/flutter-action@v2
        with:
          flutter-version: ${{ env.FLUTTER_VERSION }}
      - name: Install appdmg
        run: |
          python3 -m pip install setuptools --break-system-packages
          npm install -g appdmg
      - uses: actions/download-artifact@v4
        with:
          name: macos-amd64-lib
          path: ui/flutter/lib-amd64
      - if: ${{ matrix.channel == 'arm64' }}
        name: Build Arm64 Libraries
        run: |
          go build -tags nosqlite -ldflags="-w -s -X github.com/GopeedLab/gopeed/pkg/base.Version=$VERSION" -buildmode=c-shared -o ui/flutter/macos/Frameworks/libgopeed.dylib github.com/GopeedLab/gopeed/bind/desktop
          go build -ldflags="-w -s" -o ui/flutter/assets/exec/host github.com/GopeedLab/gopeed/cmd/host
          go build -ldflags="-w -s" -o ui/flutter/assets/exec/updater github.com/GopeedLab/gopeed/cmd/updater
      - if: ${{ matrix.channel == 'amd64' }}
        name: Build Amd64 Libraries
        run: |
          mkdir -p ui/flutter/macos/Frameworks

          cp ui/flutter/lib-amd64/libgopeed.dylib ui/flutter/macos/Frameworks/libgopeed.dylib
          cp ui/flutter/lib-amd64/host ui/flutter/assets/exec/host
          cp ui/flutter/lib-amd64/updater ui/flutter/assets/exec/updater
      - if: ${{ matrix.channel == 'universal' }}
        name: Build Universal Libraries
        run: |
          mkdir -p ui/flutter/macos/Frameworks

          # arm64 lib
          go build -tags nosqlite -ldflags="-w -s -X github.com/GopeedLab/gopeed/pkg/base.Version=$VERSION" -buildmode=c-shared -o ui/flutter/.temp/libgopeed.dylib.arm64 github.com/GopeedLab/gopeed/bind/desktop
          go build -ldflags="-w -s" -o ui/flutter/.temp/host.arm64 github.com/GopeedLab/gopeed/cmd/host
          go build -ldflags="-w -s" -o ui/flutter/.temp/updater.arm64 github.com/GopeedLab/gopeed/cmd/updater

          # amd64 lib
          cp ui/flutter/lib-amd64/libgopeed.dylib ui/flutter/.temp/libgopeed.dylib.amd64
          cp ui/flutter/lib-amd64/host ui/flutter/.temp/host.amd64
          cp ui/flutter/lib-amd64/updater ui/flutter/.temp/updater.amd64

          # universal lib
          lipo -create -output ui/flutter/macos/Frameworks/libgopeed.dylib ui/flutter/.temp/libgopeed.dylib.arm64 ui/flutter/.temp/libgopeed.dylib.amd64
          lipo -create -output ui/flutter/assets/exec/host ui/flutter/.temp/host.arm64 ui/flutter/.temp/host.amd64
          lipo -create -output ui/flutter/assets/exec/updater ui/flutter/.temp/updater.arm64 ui/flutter/.temp/updater.amd64
      - name: Build
        env:
          VERSION: ${{ needs.get-release.outputs.version }}
        run: |
          cd ui/flutter
          flutter build macos --dart-define="UPDATE_CHANNEL=macosDmg" --dart-define="GA4_MEASUREMENT_ID=${{ env.GA4_MEASUREMENT_ID }}" --dart-define="GA4_API_SECRET=${{ env.GA4_API_SECRET }}"
          cd build/macos/Build/Products/Release
          cat>appdmg.json<<EOF
          {
            "title": "Gopeed",
            "icon": "Gopeed.app/Contents/Resources/AppIcon.icns",
            "contents": [
              { "x": 448, "y": 344, "type": "link", "path": "/Applications" },
              { "x": 192, "y": 344, "type": "file", "path": "Gopeed.app" }
            ]
          }
          EOF
          mkdir dist
          appdmg appdmg.json dist/Gopeed-v$VERSION-macos.dmg

          if [[ "${{ matrix.channel }}" == "arm64" ]]; then
            mv dist/Gopeed-v$VERSION-macos.dmg dist/Gopeed-v$VERSION-macos-arm64.dmg
          fi
          if [[ "${{ matrix.channel }}" == "amd64" ]]; then
            mv dist/Gopeed-v$VERSION-macos.dmg dist/Gopeed-v$VERSION-macos-amd64.dmg
          fi
      - name: Upload
        uses: shogo82148/actions-upload-release-asset@v1
        with:
          upload_url: ${{ needs.get-release.outputs.upload_url }}
          asset_path: ui/flutter/build/macos/Build/Products/Release/dist/*
          overwrite: true
  build-linux:
    if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'linux' }}
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-22.04, ubuntu-22.04-arm]
    needs: [get-release]
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-go@v4
        with:
          go-version: ${{ env.GO_VERSION }}
      - uses: subosito/flutter-action@v2
        with:
          flutter-version: ${{ env.FLUTTER_VERSION }}
          channel: master
      - run: |
          sudo apt update -y
          sudo apt install -y ninja-build libgtk-3-dev libayatana-appindicator3-1 libayatana-appindicator3-dev rpm patchelf libfuse2 locate libkeybinder-3.0-dev
          arch=$(uname -m)
          wget -O appimagetool "https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-${arch}.AppImage"
          chmod +x appimagetool
          sudo mv appimagetool /usr/local/bin/
      - name: Build
        env:
          VERSION: ${{ needs.get-release.outputs.version }}
        run: |
          go build -tags nosqlite -ldflags="-w -s -X github.com/GopeedLab/gopeed/pkg/base.Version=$VERSION" -buildmode=c-shared -o ui/flutter/linux/bundle/lib/libgopeed.so github.com/GopeedLab/gopeed/bind/desktop
          go build -ldflags="-w -s" -o ui/flutter/assets/exec/host github.com/GopeedLab/gopeed/cmd/host
          go build -ldflags="-w -s" -o ui/flutter/assets/exec/updater github.com/GopeedLab/gopeed/cmd/updater

          cd ui/flutter

          RPM_ARCH=$(uname -m)
          mkdir -p linux/packaging/rpm
          cat << EOF > linux/packaging/rpm/make_config.yaml
          display_name: Gopeed
          icon: assets/icon/icon.svg
          summary: High speed downloader.
          description: A high speed downloader that supports all platforms.
          group: Applications/Internet
          license: GPL-3.0
          url: https://github.com/GopeedLab/gopeed
          vendor: GopeedLab
          maintainer: GopeedLab <support@gopeed.com>
          build_arch: $RPM_ARCH
          EOF

          cat << EOF > distribute_options.yaml
          output: dist/
          releases:
            - name: linux
              jobs:
                - name: appimage
                  package:
                    platform: linux
                    target: appimage
                    build_args:
                      dart-define:
                        UPDATE_CHANNEL: linuxAppImage
                        GA4_MEASUREMENT_ID: ${{ env.GA4_MEASUREMENT_ID }}
                        GA4_API_SECRET: ${{ env.GA4_API_SECRET }}
                - name: deb
                  package:
                    platform: linux
                    target: deb
                    build_args:
                      dart-define:
                        UPDATE_CHANNEL: linuxDeb
                        GA4_MEASUREMENT_ID: ${{ env.GA4_MEASUREMENT_ID }}
                        GA4_API_SECRET: ${{ env.GA4_API_SECRET }}
                - name: rpm
                  package:
                    platform: linux
                    target: rpm
                    build_args:
                      dart-define:
                        UPDATE_CHANNEL: linuxRpm
                        GA4_MEASUREMENT_ID: ${{ env.GA4_MEASUREMENT_ID }}
                        GA4_API_SECRET: ${{ env.GA4_API_SECRET }}
          EOF
          dart pub global activate -sgit https://github.com/GopeedLab/flutter_distributor.git --git-path packages/flutter_distributor
          flutter_distributor release --name linux
          cd dist/*

          ARCH="amd64"
          if [[ "${{ matrix.os }}" == *-arm ]]; then
            ARCH="arm64"
          fi

          mv gopeed-*-linux.AppImage Gopeed-v${VERSION}-linux-${ARCH}.AppImage
          mv gopeed-*-linux.deb Gopeed-v${VERSION}-linux-${ARCH}.deb
          mv gopeed-*-linux.rpm Gopeed-v${VERSION}-linux-${ARCH}.rpm
      - name: Upload
        uses: shogo82148/actions-upload-release-asset@v1
        with:
          upload_url: ${{ needs.get-release.outputs.upload_url }}
          asset_path: ui/flutter/dist/*/*
          overwrite: true
  build-snap:
    if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'snap' }}
    runs-on: ubuntu-latest
    needs: [get-release]
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-go@v4
        with:
          go-version: ${{ env.GO_VERSION }}
      - name: Setup LXD
        uses: canonical/setup-lxd@v0.1.1
        with:
          channel: latest/stable
      - name: Build
        env:
          VERSION: ${{ needs.get-release.outputs.version }}
        run: |
          go build -tags nosqlite -ldflags="-w -s -X github.com/GopeedLab/gopeed/pkg/base.Version=$VERSION" -buildmode=c-shared -o ui/flutter/linux/bundle/lib/libgopeed.so github.com/GopeedLab/gopeed/bind/desktop
          go build -ldflags="-w -s" -o ui/flutter/assets/exec/host github.com/GopeedLab/gopeed/cmd/host
          go build -ldflags="-w -s" -o ui/flutter/assets/exec/updater github.com/GopeedLab/gopeed/cmd/updater
          cd ui/flutter

          sudo snap install snapcraft --classic
          mkdir -p snap/gui
          cp assets/icon/icon.svg snap/gui/gopeed.svg

          cat>snap/snapcraft.yaml<<EOF
          name: gopeed
          version: $VERSION
          summary: A modern download manager
          description: High speed downloader that supports all platforms.

          confinement: strict
          base: core24
          grade: stable

          platforms:
            amd64:
              build-on: amd64
              build-for: amd64

          slots:
            dbus-gopeed:
              interface: dbus
              bus: session
              name: com.gopeed.gopeed

          apps:
            gopeed:
              command-chain:
                - bin/gpu-2404-wrapper
              command: bin/gopeed
              extensions: [gnome]
              common-id: com.gopeed.Gopeed
              plugs:
                - network
                - home
          parts:
            flutter-git:
              source: https://github.com/flutter/flutter.git
              source-tag: $FLUTTER_VERSION
              source-depth: 1
              plugin: nil
              override-build: |
                mkdir -p \$CRAFT_PART_INSTALL/usr/bin
                mkdir -p \$CRAFT_PART_INSTALL/usr/libexec
                cp -r \$CRAFT_PART_SRC \$CRAFT_PART_INSTALL/usr/libexec/flutter
                ln -s \$CRAFT_PART_INSTALL/usr/libexec/flutter/bin/flutter \$CRAFT_PART_INSTALL/usr/bin/flutter
                ln -s \$SNAPCRAFT_PART_INSTALL/usr/libexec/flutter/bin/dart \$SNAPCRAFT_PART_INSTALL/usr/bin/dart
                \$CRAFT_PART_INSTALL/usr/bin/flutter doctor
              build-packages:
                - clang
                - cmake
                - curl
                - libgtk-3-dev
                - libkeybinder-3.0-dev
                - ninja-build
                - unzip
                - xz-utils
                - zip
              override-prime: ''
            zenity:
              plugin: nil
              stage-packages:
                - zenity
            gopeed:
              after: [flutter-git,zenity]
              source: .
              plugin: nil
              stage-packages:
                - libgtk-3-dev
                - libkeybinder-3.0-dev
                - libappindicator3-dev
              override-build: |
                # work around pub get stack overflow # https://github.com/dart-lang/sdk/issues/51068#issuecomment-1396588253
                set +e
                dart pub get
                set -eux
                flutter build linux --dart-define="UPDATE_CHANNEL=linuxSnap" --dart-define="GA4_MEASUREMENT_ID=$GA4_MEASUREMENT_ID" --dart-define="GA4_API_SECRET=$GA4_API_SECRET"
                mkdir -p \$CRAFT_PART_INSTALL/bin/
                cp -r build/linux/*/release/bundle/* \$CRAFT_PART_INSTALL/bin/
            gpu-2404:
              after: [gopeed]
              source: https://github.com/canonical/gpu-snap.git
              plugin: dump
              override-prime: |
                craftctl default
                \${CRAFT_PART_SRC}/bin/gpu-2404-cleanup mesa-2404
              prime:
                - bin/gpu-2404-wrapper

          plugs:
            gpu-2404:
              interface: content
              target: \$SNAP/gpu-2404
              default-provider: mesa-2404

          layout:
            /usr/share/libdrm:
              bind: \$SNAP/gpu-2404/libdrm
            /usr/share/drirc.d:
              symlink: \$SNAP/gpu-2404/drirc.d
            /usr/share/X11/XErrorDB:
              symlink: \$SNAP/gpu-2404/X11/XErrorDB
          EOF

          cp linux/assets/com.gopeed.Gopeed.desktop snap/gui/gopeed.desktop
          sed -i 's/Icon=com.gopeed.Gopeed/Icon=\${SNAP}\/meta\/gui\/gopeed.svg/g' snap/gui/gopeed.desktop

          snapcraft --use-lxd

          # Snapcraft login
          export SNAPCRAFT_STORE_CREDENTIALS=${{ secrets.SNAP_STORE_LOGIN }}
          snapcraft upload --release=${{ github.event.inputs.test == 'true' && 'edge' || 'stable' }} gopeed_${VERSION}_amd64.snap
  build-android:
    if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'android' }}
    runs-on: ubuntu-latest
    needs: [get-release]
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-go@v4
        with:
          go-version: ${{ env.GO_VERSION }}
      - uses: actions/setup-java@v3
        with:
          distribution: "zulu"
          java-version: "17"
      - uses: subosito/flutter-action@v2
        with:
          flutter-version: ${{ env.FLUTTER_VERSION }}
      - name: Build
        env:
          VERSION: ${{ needs.get-release.outputs.version }}
          APK_KEYSTORE: ${{ secrets.APK_KEYSTORE }}
          APK_KEY_PASSWORD: ${{ secrets.APK_KEY_PASSWORD }}
          APK_STORE_PASSWORD: ${{ secrets.APK_STORE_PASSWORD }}
        run: |
          go install golang.org/x/mobile/cmd/gomobile@latest
          go get golang.org/x/mobile/bind
          gomobile init
          gomobile bind -tags nosqlite -ldflags="-w -s -checklinkname=0 -X github.com/GopeedLab/gopeed/pkg/base.Version=$VERSION" -o ui/flutter/android/app/libs/libgopeed.aar -target=android -androidapi 21 -javapkg=com.gopeed github.com/GopeedLab/gopeed/bind/mobile
          cd ui/flutter
          echo $APK_KEYSTORE | base64 -di > android/app/upload-keystore.jks
          flutter build apk --dart-define="UPDATE_CHANNEL=androidApk" --dart-define="GA4_MEASUREMENT_ID=${{ env.GA4_MEASUREMENT_ID }}" --dart-define="GA4_API_SECRET=${{ env.GA4_API_SECRET }}"
          flutter build apk --split-per-abi --dart-define="UPDATE_CHANNEL=androidApk" --dart-define="GA4_MEASUREMENT_ID=${{ env.GA4_MEASUREMENT_ID }}" --dart-define="GA4_API_SECRET=${{ env.GA4_API_SECRET }}"
          mkdir dist
          cp build/app/outputs/flutter-apk/app-release.apk dist/Gopeed-v$VERSION-android.apk
          cp build/app/outputs/flutter-apk/app-armeabi-v7a-release.apk dist/Gopeed-v$VERSION-android-armeabi-v7a.apk
          cp build/app/outputs/flutter-apk/app-arm64-v8a-release.apk dist/Gopeed-v$VERSION-android-arm64-v8a.apk
          cp build/app/outputs/flutter-apk/app-x86_64-release.apk dist/Gopeed-v$VERSION-android-x86_64.apk
      - name: Upload
        uses: shogo82148/actions-upload-release-asset@v1
        with:
          upload_url: ${{ needs.get-release.outputs.upload_url }}
          asset_path: ui/flutter/dist/*
          overwrite: true
  build-ios:
    if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'ios' }}
    runs-on: macos-14
    needs: [get-release]
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-go@v4
        with:
          go-version: ${{ env.GO_VERSION }}
      - uses: subosito/flutter-action@v2
        with:
          flutter-version: ${{ env.FLUTTER_VERSION }}
      - name: Build
        env:
          VERSION: ${{ needs.get-release.outputs.version }}
        run: |
          go install golang.org/x/mobile/cmd/gomobile@latest
          go get golang.org/x/mobile/bind
          gomobile init
          gomobile bind -tags nosqlite -ldflags="-w -s -X github.com/GopeedLab/gopeed/pkg/base.Version=$VERSION" -o ui/flutter/ios/Frameworks/Libgopeed.xcframework -target=ios github.com/GopeedLab/gopeed/bind/mobile
          cd ui/flutter
          flutter build ios --no-codesign --dart-define="UPDATE_CHANNEL=iosIpa" --dart-define="GA4_MEASUREMENT_ID=${{ env.GA4_MEASUREMENT_ID }}" --dart-define="GA4_API_SECRET=${{ env.GA4_API_SECRET }}"
          mkdir Payload
          cp -r build/ios/iphoneos/Runner.app Payload
          zip -r -y Payload.zip Payload/Runner.app
          mkdir dist
          mv Payload.zip dist/Gopeed-v$VERSION-ios.ipa
      - name: Upload
        uses: shogo82148/actions-upload-release-asset@v1
        with:
          upload_url: ${{ needs.get-release.outputs.upload_url }}
          asset_path: ui/flutter/dist/*
          overwrite: true
  build-web:
    if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'web' || github.event.inputs.platform == 'qnap' }}
    runs-on: ubuntu-latest
    needs: [get-release]
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-go@v4
        with:
          go-version: ${{ env.GO_VERSION }}
      - uses: subosito/flutter-action@v2
        with:
          flutter-version: ${{ env.FLUTTER_VERSION }}
      - name: Build
        env:
          VERSION: ${{ needs.get-release.outputs.version }}
        run: |
          cd ui/flutter
          flutter build web --no-web-resources-cdn --dart-define="GA4_MEASUREMENT_ID=${{ env.GA4_MEASUREMENT_ID }}" --dart-define="GA4_API_SECRET=${{ env.GA4_API_SECRET }}"
          dart ../../.github/workflows/scripts/flutter_local_font.dart
          cd ../../
          cp -r ui/flutter/build/web cmd/web/dist
          mkdir -p dist/zip

          goos_arr=(windows darwin linux)
          goarch_arr=(386 amd64 arm64)
          export CGO_ENABLED=0
          for goos in "${goos_arr[@]}"; do
            for goarch in "${goarch_arr[@]}"; do
              goos_name=$goos
              if [ $goos = "darwin" ]; then
                goos_name="macos"
              fi
              name=gopeed-web-v$VERSION-$goos_name-$goarch
              dir="dist/$name/"
              (GOOS=$goos GOARCH=$goarch go build -tags nosqlite,web -ldflags="-s -w -X github.com/GopeedLab/gopeed/pkg/base.Version=$VERSION" -o $dir github.com/GopeedLab/gopeed/cmd/web \
              && cd $dir \
              && file=$(ls -AU | head -1) \
              && mkdir $name \
              && mv $file $name/$(echo $file | sed -e "s/web/gopeed/g") \
              && zip -r ../zip/$name.zip * \
              && cd ../..) \
              || true
            done
          done
      - uses: actions/upload-artifact@v4
        with:
          name: web-dist
          path: dist/zip/
      - name: Upload
        uses: shogo82148/actions-upload-release-asset@v1
        with:
          upload_url: ${{ needs.get-release.outputs.upload_url }}
          asset_path: dist/zip/*
          overwrite: true
  build-qnap:
    if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'qnap' }}
    runs-on: ubuntu-latest
    needs: [get-release, build-web]
    steps:
      - uses: actions/setup-python@v5
        with:
          python-version: "3.8.18"
      - uses: actions/download-artifact@v4
        with:
          name: web-dist
          path: dist/zip/
      - name: Build
        env:
          VERSION: ${{ needs.get-release.outputs.version }}
        run: |
          sudo apt update -y
          sudo apt install -y pv bsdmainutils
          wget -O qdk2_0.32.bionic_amd64.deb "https://github.com/qnap-dev/qdk2/releases/download/v0.32/qdk2_0.32.bionic_amd64.deb"
          dpkg -X qdk2_0.32.bionic_amd64.deb qdk2 # Direct installs will fail due to missing dependencies!
          [[ -d qdk2 ]] || exit 1

          export PATH=$(pwd)/qdk2/usr/bin:$(pwd)/qdk2/usr/share/qdk2/QDK/bin:${PATH}
          wget -O Gopeed.template.tar.gz "https://github.com/GopeedLab/QpkgBuild/raw/refs/heads/master/template/Gopeed.template.tar.gz"
          tar -zxf Gopeed.template.tar.gz
          [[ -d Gopeed ]] || exit 1

          goos=linux
          goarch_arr=(amd64 arm64)
          for goarch in "${goarch_arr[@]}"; do
            qarch=x86_64
            [[ "${goarch}" == "arm64" ]] && qarch=arm_64
            name=gopeed-web-v${VERSION}-${goos}-${goarch}
            unzip dist/zip/${name}.zip -d dist/${name}
            cp dist/${name}/${name}/* Gopeed/${qarch}/
          done
          cd Gopeed
          sed -i -e 's/__QPKG_VER__/${VERSION}/g' qpkg.cfg
          qbuild || exit 1

          mkdir -p ../dist/qnap
          goos=qnap
          for goarch in "${goarch_arr[@]}"; do
            qarch=x86_64
            [[ "${goarch}" == "arm64" ]] && qarch=arm_64
            sname=Gopeed_${VERSION}_${qarch}.qpkg
            dname=gopeed-v${VERSION}-${goos}-${goarch}.qpkg
            [[ -f build/${sname} ]] && cp -ra build/${sname} ../dist/qnap/${dname}
          done
      - name: Upload
        uses: shogo82148/actions-upload-release-asset@v1
        with:
          upload_url: ${{ needs.get-release.outputs.upload_url }}
          asset_path: dist/qnap/*
          overwrite: true
  build-docker:
    if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'docker' }}
    runs-on: ubuntu-latest
    needs: [get-release]
    steps:
      - name: Remove unnecessary files
        run: |
          sudo rm -rf /usr/share/dotnet
          sudo rm -rf "$AGENT_TOOLSDIRECTORY"
      - uses: subosito/flutter-action@v2
        with:
          flutter-version: ${{ env.FLUTTER_VERSION }}
      - uses: actions/checkout@v3
      - name: Set up QEMU
        uses: docker/setup-qemu-action@v2
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v2
      - name: Login to DockerHub
        uses: docker/login-action@v1
        with:
          username: liwei2633
          password: ${{ secrets.DOCKER_PASSWORD }}
      - name: Build flutter web
        run: |
          cd ui/flutter
          flutter build web --no-web-resources-cdn --dart-define="UPDATE_CHANNEL=docker" --dart-define="GA4_MEASUREMENT_ID=${{ env.GA4_MEASUREMENT_ID }}" --dart-define="GA4_API_SECRET=${{ env.GA4_API_SECRET }}"
          dart ../../.github/workflows/scripts/flutter_local_font.dart
          cd ../../
          rm -rf cmd/web/dist
          cp -r ui/flutter/build/web cmd/web/dist
      - name: Build and push
        uses: docker/build-push-action@v2
        env:
          VERSION: ${{ needs.get-release.outputs.version }}
        with:
          context: .
          push: ${{ github.event.inputs.test == 'true' && 'false' || 'true' }}
          build-args: |
            VERSION=${{ env.VERSION }}
            IN_DOCKER=true
          platforms: |
            linux/386
            linux/amd64
            linux/arm64
            linux/arm/v7
          tags: |
            liwei2633/gopeed:latest
            liwei2633/gopeed:v${{ env.VERSION }}


================================================
FILE: .github/workflows/release.yml
================================================
name: release

on:
  push:
    branches:
      - main
    paths:
      - "bind/**"
      - "cmd/**"
      - "internal/**"
      - "pkg/**"
      - "ui/**"
      - ".github/workflows/release.yml"
      - ".github/release-drafter.yml"
      - "go.mod"
      - "go.sum"
      - "Dockerfile"

env:
  GO_VERSION: "1.24"

permissions:
  contents: write

jobs:
  release:
    runs-on: ubuntu-latest
    outputs:
      tag_name: ${{ steps.create_release.outputs.tag_name }}
      upload_url: ${{ steps.create_release.outputs.upload_url }}

    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-go@v4
        with:
          go-version: ${{ env.GO_VERSION }}
      - uses: release-drafter/release-drafter@v5
        id: create_release
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}


================================================
FILE: .github/workflows/scripts/flutter_local_font.dart
================================================
// Localize external Google Fonts (fonts.gstatic.com) references in Flutter Web build output.
//
// Usage (post-build):
//   # Run AFTER: flutter build web --no-web-resources-cdn
//   dart ../../.github/workflows/scripts/flutter_local_font.dart
//
// What it does:
// In main.dart.js:
//    - Find any referenced resources under: https://fonts.gstatic.com/s/
//      (including the common pattern where Flutter concatenates the base URL with a relative path)
//    - Download them into: build/web/assets/gstatic/<path>
//    - Rewrite "https://fonts.gstatic.com/s/" -> "assets/gstatic/" so runtime loads locally
//    - Only download font subsets needed by supported locales (parsed from message.dart)
//
// Notes:
// - This script DOES NOT call `flutter build web`. It only patches the output.
// - This is a best-effort post-process. Always verify in browser devtools.

import 'dart:async';
import 'dart:io';

/// Get font families required for a specific locale based on language/script.
/// Uses language code prefix to automatically detect the script system.
/// Returns empty set if base fonts (Latin/Cyrillic/Greek) are sufficient.
Set<String> _getFontFamiliesForLocale(String locale) {
  final lang = locale.toLowerCase().split('_').first;

  // CJK languages - need specific large font files
  if (lang == 'zh') {
    // Simplified vs Traditional Chinese
    if (locale.contains('cn') || locale.contains('sg')) {
      return {'notosanssc'}; // Simplified Chinese
    }
    return {'notosanstc', 'notosanshk'}; // Traditional Chinese (TW, HK, MO)
  }
  if (lang == 'ja') return {'notosansjp'}; // Japanese
  if (lang == 'ko') return {'notosanskr'}; // Korean

  // Arabic script languages
  if (lang == 'ar') return {'notosansarabic'}; // Arabic
  if (lang == 'fa') return {'notosansarabic'}; // Persian/Farsi
  if (lang == 'ur') return {'notosansarabic'}; // Urdu
  if (lang == 'ps') return {'notosansarabic'}; // Pashto
  if (lang == 'ku') return {'notosansarabic'}; // Kurdish (Arabic script)

  // Hebrew script
  if (lang == 'he' || lang == 'yi') return {'notosanshebrew'};

  // South Asian scripts
  if (lang == 'hi' || lang == 'mr' || lang == 'ne' || lang == 'sa') {
    return {'notosansdevanagari'}; // Hindi, Marathi, Nepali, Sanskrit
  }
  if (lang == 'bn' || lang == 'as')
    return {'notosansbengali'}; // Bengali, Assamese
  if (lang == 'ta')
    return {'notosanstamil', 'notosanstamilsupplement'}; // Tamil
  if (lang == 'te') return {'notosanstelugu'}; // Telugu
  if (lang == 'kn') return {'notosanskannada'}; // Kannada
  if (lang == 'ml') return {'notosansmalayalam'}; // Malayalam
  if (lang == 'gu') return {'notosansgujarati'}; // Gujarati
  if (lang == 'pa') return {'notosansgurmukhi'}; // Punjabi (Gurmukhi)
  if (lang == 'or') return {'notosansoriya'}; // Odia/Oriya
  if (lang == 'si') return {'notosanssinhala'}; // Sinhala

  // Southeast Asian scripts
  if (lang == 'th') return {'notosansthai'}; // Thai
  if (lang == 'lo') return {'notosanslao'}; // Lao
  if (lang == 'my') return {'notosansmyanmar'}; // Myanmar/Burmese
  if (lang == 'km') return {'notosanskhmer'}; // Khmer/Cambodian
  if (lang == 'jv') return {'notosansjavanese'}; // Javanese

  // Other scripts
  if (lang == 'ka') return {'notosansgeorgian'}; // Georgian
  if (lang == 'hy') return {'notosansarmenian'}; // Armenian
  if (lang == 'am' || lang == 'ti')
    return {'notosansethiopic'}; // Amharic, Tigrinya
  if (lang == 'mn') return {'notosansmongolian'}; // Mongolian

  // Latin/Cyrillic/Greek based languages - base notosans is sufficient
  // Includes: English, German, French, Spanish, Italian, Portuguese,
  // Russian, Ukrainian, Polish, Turkish, Vietnamese, Greek, etc.
  return {};
}

/// Base font families that are always included regardless of locale.
/// These provide core functionality and are relatively small.
const _baseFontFamilies = <String>{
  'notosans', // Base Latin/Cyrillic/Greek/Vietnamese
  'roboto', // Material Design default font
  'notosanssymbols', // Common symbols
  'notosanssymbols2', // Additional symbols
  'notosansmath', // Math symbols
  'notomusic', // Music notation symbols
  // Note: notocoloremoji (~24MB) and notoemoji (~860KB) are excluded
  // to reduce bundle size. Add them back if emoji support is needed.
};

/// Parse supported locales from message.dart file.
/// Reads the import statements and extracts locale codes like 'zh_cn', 'en_us', etc.
Set<String> _parseSupportedLocales(File messageFile) {
  final locales = <String>{};

  if (!messageFile.existsSync()) {
    _fail('Warning: message.dart not found, only base fonts will be used');
  }

  final content = messageFile.readAsStringSync();

  // Match import statements like: import 'langs/zh_cn.dart';
  final importRegex = RegExp(r"import\s+'langs/(\w+)\.dart'");
  for (final match in importRegex.allMatches(content)) {
    final locale = match.group(1);
    if (locale != null) {
      locales.add(locale);
    }
  }

  if (locales.isEmpty) {
    _fail(
        'Warning: No locales found in message.dart, only base fonts will be used');
  }

  return locales;
}

/// Get required font families for the given locales.
/// Returns a set of font family names that should be included.
/// Automatically detects required fonts based on language code prefix.
Set<String> _getRequiredFontFamilies(Set<String> locales) {
  final families = <String>{..._baseFontFamilies};
  for (final locale in locales) {
    final localeFamilies = _getFontFamiliesForLocale(locale);
    families.addAll(localeFamilies);
  }
  return families;
}

/// Check if a font file path should be included based on required font families.
/// Font paths have the format: "fontfamily/version/filename.ext"
/// Example: "notosanssc/v36/xxx.ttf" -> font family is "notosanssc"
bool _fontMatchesRequiredFamilies(
    String fontPath, Set<String> requiredFamilies) {
  // Extract font family from path (first segment before '/')
  final slashIndex = fontPath.indexOf('/');
  if (slashIndex == -1) {
    // No slash found, include by default (unusual path format)
    return true;
  }

  final fontFamily = fontPath.substring(0, slashIndex).toLowerCase();
  return requiredFamilies.contains(fontFamily);
}

Future<void> main(List<String> args) async {
  try {
    // This script is intentionally argument-free for CI convenience.
    // It assumes it is executed from the Flutter project directory (ui/flutter),
    // but will also try to locate pubspec.yaml by walking up.
    final flutterDir = _findFlutterProjectRoot(Directory.current);

    // Parse supported locales from message.dart
    final messageFile =
        File(_join(flutterDir.path, 'lib', 'i18n', 'message.dart'));
    final supportedLocales = _parseSupportedLocales(messageFile);
    stdout.writeln('Supported locales: ${supportedLocales.join(', ')}');

    // Get required font families based on supported locales
    final requiredFamilies = _getRequiredFontFamilies(supportedLocales);
    stdout.writeln('Required font families: ${requiredFamilies.join(', ')}');

    final webDir = Directory(_join(flutterDir.path, 'build', 'web'));
    if (!webDir.existsSync()) {
      _fail(
        'Web build directory not found: ${webDir.path}\n'
        'Did you run: flutter build web --no-web-resources-cdn ?',
      );
    }

    final mainJs = File(_join(webDir.path, 'main.dart.js'));
    if (!mainJs.existsSync()) {
      _fail('main.dart.js not found at: ${mainJs.path}');
    }

    final gstaticRoot = Directory(_join(webDir.path, 'assets', 'gstatic'));
    if (!gstaticRoot.existsSync()) gstaticRoot.createSync(recursive: true);

    const gstaticSPrefix = 'https://fonts.gstatic.com/s/';
    final original = mainJs.readAsStringSync();

    // Relative font asset paths which Flutter's loader typically concatenates with:
    //   https://fonts.gstatic.com/s/
    // Keep this as a best-effort heuristic to cover the common concatenation pattern.
    // Some gstatic assets include extra dot segments like: notocoloremoji/v32/Yq6P-KqIXTD0t4D9z1ESnKM3-HpFabsE4tq3luCC7p-aXxcn.0.woff2
    // Allow dots in the path, while still restricting to font-like extensions.
    final relAssetRegex = RegExp(
      r"""["']([a-zA-Z0-9/_\.-]+\.(?:woff2|woff|ttf|otf|eot|svg))["']""",
      caseSensitive: false,
    );
    final relAssetsUnderS = <String>{};
    for (final m in relAssetRegex.allMatches(original)) {
      final p = m.group(1);
      if (p == null || p.isEmpty) continue;
      if (p.contains('://') || p.startsWith('data:')) continue;
      if (p.startsWith('/') ||
          p.startsWith('assets/') ||
          p.startsWith('packages/')) continue;
      if (p.contains('..')) continue;
      relAssetsUnderS.add(p);
    }

    if (relAssetsUnderS.isEmpty) {
      _fail('No fonts.gstatic.com assets found in main.dart.js.');
    }

    // Build a download plan: dest relative path (under assets/gstatic/) -> URL.
    // Filter fonts based on required font families to reduce package size.
    final downloads = <String, Uri>{};
    final skippedFonts = <String, Set<String>>{};

    for (final rel in relAssetsUnderS) {
      if (_fontMatchesRequiredFamilies(rel, requiredFamilies)) {
        downloads[rel] = Uri.parse('$gstaticSPrefix$rel');
      } else {
        // Track skipped fonts and their paths for fallback generation
        final slashIndex = rel.indexOf('/');
        final family = slashIndex > 0 ? rel.substring(0, slashIndex) : rel;
        skippedFonts.putIfAbsent(family, () => <String>{}).add(rel);
      }
    }

    if (skippedFonts.isNotEmpty) {
      stdout.writeln(
        'Skipped ${skippedFonts.length} font families not required by supported locales:',
      );
      stdout.writeln('  ${skippedFonts.keys.join(', ')}');
    }

    if (downloads.isNotEmpty) {
      stdout.writeln(
        'Found ${downloads.length} fonts.gstatic.com assets to download...',
      );

      // Reuse a single HttpClient to avoid creating hundreds of short-lived
      // connections (can be flaky on some environments).
      final httpClient = HttpClient()
        ..connectionTimeout = const Duration(seconds: 30)
        ..idleTimeout = const Duration(seconds: 30)
        ..maxConnectionsPerHost = 6;
      try {
        for (final entry in downloads.entries) {
          final relPath = entry.key;
          final url = entry.value;
          final destPath = relPath.replaceAll('/', Platform.pathSeparator);
          final dest = File(_join(gstaticRoot.path, destPath));
          await _downloadIfMissing(httpClient, url, dest);
        }
      } finally {
        httpClient.close(force: true);
      }
    }

    // Generate empty fallback font files for skipped fonts to prevent infinite loading
    // Empty files will cause browsers to fail fast instead of retrying indefinitely
    if (skippedFonts.isNotEmpty) {
      var fallbackCount = 0;
      for (final entry in skippedFonts.entries) {
        for (final relPath in entry.value) {
          final destPath = relPath.replaceAll('/', Platform.pathSeparator);
          final dest = File(_join(gstaticRoot.path, destPath));
          if (!dest.existsSync()) {
            dest.parent.createSync(recursive: true);
            // Create an empty file - browsers will recognize it as invalid and skip
            dest.writeAsBytesSync([]);
            fallbackCount++;
          }
        }
      }
      if (fallbackCount > 0) {
        stdout.writeln(
          'Generated $fallbackCount empty fallback font files to prevent infinite loading',
        );
      }
    }

    // Rewrite remote prefix so requests become:
    //   <origin>/assets/gstatic/<...>.woff2
    final replacedGstatic = original.replaceAll(
      gstaticSPrefix,
      'assets/gstatic/',
    );

    if (replacedGstatic == original && downloads.isEmpty) {
      stdout.writeln(
        'No changes applied (no fonts.gstatic.com references found).',
      );
      exitCode = 0;
      return;
    }

    mainJs.writeAsStringSync(replacedGstatic);

    stdout.writeln('Patched: ${mainJs.path}');
    if (downloads.isNotEmpty) {
      stdout.writeln(
        ' - downloaded ${downloads.length} files into: ${gstaticRoot.path}',
      );
    }
    stdout.writeln(' - fonts.gstatic.com rewritten -> assets/gstatic/');
  } catch (e, st) {
    stderr.writeln('ERROR: $e');
    stderr.writeln(st);
    exitCode = 1;
  }
}

Future<void> _downloadIfMissing(HttpClient client, Uri url, File dest) async {
  if (dest.existsSync() && dest.lengthSync() > 0) return;

  dest.parent.createSync(recursive: true);

  // Network can be flaky in CI. Retry a few times on transient errors.
  const maxAttempts = 4;

  for (var attempt = 1; attempt <= maxAttempts; attempt++) {
    final tmp = File('${dest.path}.tmp');
    try {
      try {
        final req = await client.getUrl(url);

        final resp = await req.close().timeout(const Duration(seconds: 60));
        if (resp.statusCode != 200) {
          // 404 is not transient in practice - fail fast with a clear message.
          if (resp.statusCode == 404) {
            _fail('Missing gstatic asset (404): $url');
          }
          throw HttpException('HTTP ${resp.statusCode}', uri: url);
        }

        // Stream to temp file first, then rename to avoid leaving partial files.
        if (tmp.existsSync()) tmp.deleteSync();
        final sink = tmp.openWrite();
        await resp.pipe(sink).timeout(const Duration(seconds: 120));

        if (!tmp.existsSync() || tmp.lengthSync() == 0) {
          throw const FormatException('Downloaded asset is empty');
        }
        if (dest.existsSync()) dest.deleteSync();
        tmp.renameSync(dest.path);
        return;
      } finally {
        // no-op: HttpClient lifecycle managed by the caller
      }
    } on TimeoutException {
      if (tmp.existsSync()) tmp.deleteSync();
      if (attempt == maxAttempts) {
        _fail('Timeout downloading asset: $url');
      }
    } on SocketException catch (e) {
      if (tmp.existsSync()) tmp.deleteSync();
      if (attempt == maxAttempts) {
        _fail('Socket error downloading asset: $url ($e)');
      }
    } on HttpException catch (e) {
      if (tmp.existsSync()) tmp.deleteSync();
      if (attempt == maxAttempts) {
        _fail('HTTP error downloading asset: $url ($e)');
      }
    } on FileSystemException catch (e) {
      if (tmp.existsSync()) tmp.deleteSync();
      _fail('File write error downloading asset: $url ($e)');
    } catch (e) {
      if (tmp.existsSync()) tmp.deleteSync();
      if (attempt == maxAttempts) {
        _fail('Unexpected error downloading asset: $url ($e)');
      }
    }

    // Backoff before retrying.
    final backoffSeconds = 1 << (attempt - 1);
    stdout.writeln(
      'Retrying (${attempt + 1}/$maxAttempts) for: $url (wait ${backoffSeconds}s)',
    );
    await Future.delayed(Duration(seconds: backoffSeconds));
  }
}

Directory _findFlutterProjectRoot(Directory start) {
  var dir = start;
  for (var i = 0; i < 10; i++) {
    final pubspec = File(_join(dir.path, 'pubspec.yaml'));
    if (pubspec.existsSync()) return dir;
    final parent = dir.parent;
    if (parent.path == dir.path) break;
    dir = parent;
  }
  // Fallback: use current directory, error messages will explain what is missing.
  return start;
}

Never _fail(String msg) {
  throw FormatException(msg);
}

String _join(String a, [String? b, String? c, String? d]) {
  final parts = <String>[
    a,
    if (b != null) b,
    if (c != null) c,
    if (d != null) d,
  ];
  return parts.join(Platform.pathSeparator);
}


================================================
FILE: .github/workflows/test.yml
================================================
name: test

on:
  pull_request:
    branches:
      - main
    paths:
      - "bind/**"
      - "cmd/**"
      - "internal/**"
      - "pkg/**"
      - "ui/**"
      - ".github/workflows/test.yml"
      - "go.mod"
      - "go.sum"
  push:
    branches:
      - main
    paths:
      - "bind/**"
      - "cmd/**"
      - "internal/**"
      - "pkg/**"
      - "ui/**"
      - ".github/workflows/test.yml"
      - "go.mod"
      - "go.sum"
  workflow_dispatch:

env:
  GO_VERSION: "1.24"
  FLUTTER_VERSION: "3.41.2"

jobs:
  #  lint:
  #    name: Lint
  #    runs-on: ubuntu-latest
  #    steps:
  #      - uses: actions/setup-go@v2
  #        with:
  #          go-version: '^1.19'
  #      - uses: actions/checkout@v2
  #      - name: Lint Go Code
  #        run: |
  #          export PATH=$PATH:$(go env GOPATH)/bin # temporary fix. See https://github.com/actions/setup-go/issues/14
  #          go get -u golang.org/x/lint/golint
  #          golint -set_exit_status ./...
  test:
    name: Test check
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-go@v4
        with:
          go-version: ${{ env.GO_VERSION }}
      - name: Run Unit tests.
        run: |
          # Get packages for testing and coverage (including cmd/web for flags_test.go)
          PACKAGES=$(go list ./... | grep -v /bind/ | grep -v -E '/cmd/(?!web)')
          go test -v -coverpkg=$(echo "$PACKAGES" | paste -sd "," -) -covermode=count -coverprofile=coverage.txt $PACKAGES
          # Filter out main.go from coverage report to avoid lowering coverage percentage
          grep -v "main.go" coverage.txt > coverage_filtered.txt || true
          mv coverage_filtered.txt coverage.txt
      - uses: codecov/codecov-action@v4
        with:
          files: ./coverage.txt
          token: ${{ secrets.CODECOV_UPLOAD_TOKEN }}
  build-desktop:
    strategy:
      matrix:
        include:
          - os: windows-2022
          - os: windows-11-arm
            llvm_ver: "20251202" # Only ARM64
            flutter_channel: "main"
            flutter_version: "7e1c8868"
          - os: macos-latest
          - os: ubuntu-22.04
          - os: ubuntu-22.04-arm
            flutter_channel: "main"
    name: Build desktop check (${{ matrix.os }})
    runs-on: ${{ matrix.os }}
    needs: [test]
    steps:
      - uses: actions/checkout@v3
      - name: Enable long paths for flutter main branch checks
        run: |
          git config --global core.longpaths true
      - name: Install llvm-mingw-ucrt-aarch64 (ARM64 only)
        if: matrix.os == 'windows-11-arm'
        run: |
          $ver = "${{ matrix.llvm_ver }}"
          $url = "https://github.com/mstorsjo/llvm-mingw/releases/download/$ver/llvm-mingw-$ver-ucrt-aarch64.zip"
          $zip = "$env:RUNNER_TEMP\\llvm.zip"
          $extract = "$env:RUNNER_TEMP\\extract"
          $target = "C:\\clangarm64"

          curl -L $url -o $zip
          rm -r -fo $extract,$target -ea Ignore
          mkdir $extract | Out-Null

          tar -xf $zip -C $extract
          mv (Get-ChildItem $extract)[0].FullName $target

          $b = "$target\\bin"
          "CC=$b\\clang.exe"        >> $env:GITHUB_ENV
          "CXX=$b\\clang++.exe"     >> $env:GITHUB_ENV
          "CLANGARM64_BIN=$b"       >> $env:GITHUB_ENV
          "CGO_ENABLED=1"           >> $env:GITHUB_ENV
          "CLANGARM64_ROOT=$target" >> $env:GITHUB_ENV
          $b >> $env:GITHUB_PATH
      - uses: actions/setup-go@v4
        with:
          go-version: ${{ env.GO_VERSION }}
      - uses: subosito/flutter-action@v2
        with:
          channel: ${{ matrix.flutter_channel || 'stable' }}
          flutter-version: ${{ matrix.flutter_version || env.FLUTTER_VERSION }}
      - if: runner.os == 'Windows'
        run: |
          go build -tags nosqlite -ldflags="-w -s" -buildmode=c-shared -o ui/flutter/windows/libgopeed.dll github.com/GopeedLab/gopeed/bind/desktop
          cd ui/flutter
          flutter build windows
      - if: runner.os == 'macOS'
        run: |
          go build -tags nosqlite -ldflags="-w -s" -buildmode=c-shared -o ui/flutter/macos/Frameworks/libgopeed.dylib github.com/GopeedLab/gopeed/bind/desktop
          cd ui/flutter
          flutter build macos
      - if: runner.os == 'Linux'
        run: |
          sudo apt-get update -y
          sudo apt-get install -y ninja-build libgtk-3-dev libayatana-appindicator3-dev libkeybinder-3.0-dev
          go build -tags nosqlite -ldflags="-w -s" -buildmode=c-shared -o ui/flutter/linux/bundle/lib/libgopeed.so github.com/GopeedLab/gopeed/bind/desktop
          cd ui/flutter
          flutter build linux
  build-mobile:
    name: Build mobile check
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [macos-14, ubuntu-latest]
    needs: [test]
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-go@v4
        with:
          go-version: ${{ env.GO_VERSION }}
      - uses: subosito/flutter-action@v2
        with:
          flutter-version: ${{ env.FLUTTER_VERSION }}
      - run: |
          go install golang.org/x/mobile/cmd/gomobile@latest
          go get golang.org/x/mobile/bind
          gomobile init
      - if: runner.os == 'macOS'
        run: |
          gomobile bind -tags nosqlite -ldflags="-w -s" -o ui/flutter/ios/Frameworks/Libgopeed.xcframework -target=ios github.com/GopeedLab/gopeed/bind/mobile
          cd ui/flutter
          flutter build ipa --no-codesign
      - if: runner.os == 'Linux'
        uses: actions/setup-java@v3
        with:
          distribution: "zulu"
          java-version: "17"
      - if: runner.os == 'Linux'
        run: |
          gomobile bind -tags nosqlite -ldflags="-w -s -checklinkname=0" -o ui/flutter/android/app/libs/libgopeed.aar -target=android -androidapi 21 -javapkg=com.gopeed github.com/GopeedLab/gopeed/bind/mobile
          cd ui/flutter
          flutter build apk

  build-web:
    name: Build web check
    runs-on: ubuntu-latest
    needs: [test]
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-go@v4
        with:
          go-version: ${{ env.GO_VERSION }}
      - uses: subosito/flutter-action@v2
        with:
          flutter-version: ${{ env.FLUTTER_VERSION }}
      - name: Build
        run: |
          cd ui/flutter
          flutter build web --no-web-resources-cdn
          dart ../../.github/workflows/scripts/flutter_local_font.dart
          cd ../../
          rm -rf cmd/web/dist
          cp -r ui/flutter/build/web cmd/web/dist
          go build -tags nosqlite,web -ldflags="-s -w" github.com/GopeedLab/gopeed/cmd/web
  build-docker:
    name: Build docker check
    runs-on: ubuntu-latest
    needs: [test]
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-go@v4
        with:
          go-version: ${{ env.GO_VERSION }}
      - uses: subosito/flutter-action@v2
        with:
          flutter-version: ${{ env.FLUTTER_VERSION }}
      - name: Build
        run: |
          cd ui/flutter
          flutter build web --no-web-resources-cdn
          cd ../../
          rm -rf cmd/web/dist
          cp -r ui/flutter/build/web cmd/web/dist
          docker build -t gopeed .


================================================
FILE: .github/workflows/translator.yml.bak
================================================
# name: 'translator'

# on:
#   issues:
#     types: [opened, edited]
#   issue_comment:
#     types: [created, edited]
#   discussion:
#     types: [created, edited]
#   discussion_comment:
#     types: [created, edited]
#   pull_request_target:
#     types: [opened, edited]
#   pull_request_review_comment:
#     types: [created, edited]

# jobs:
#   translate:
#     permissions:
#       issues: write
#       discussions: write
#       pull-requests: write
#     runs-on: ubuntu-latest
#     steps:
#       - uses: lizheming/github-translate-action@1.1.2
#         env:
#           GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
#         with:
#           IS_MODIFY_TITLE: true
#           APPEND_TRANSLATION: true


================================================
FILE: .gitignore
================================================
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
ui/flutter/dist/
# Test binary, built with `go test -c`
*.test

# Output of the go coverage tool, specifically when used with LiteIDE
*.out

.idea/

bin/

.torrent.db
.torrent.db-shm
.torrent.db-wal

*.data
*.db
*.log

.DS_Store

node_modules/
cmd/web/dist/
.test_storage/
.test_download/
/extensions

================================================
FILE: CONTRIBUTING.md
================================================
# Gopeed contributors guide

Firstly, thank you for your interest in contributing to Gopeed. This guide will help you better
participate in the development of Gopeed.

## Branch description

This project only has one main branch, namely the `main` branch. If you want to participate in the
development of Gopeed, please fork this project first, and then develop in your fork project. After
development is completed, submit a PR to this project and merge it into the `main` branch.

## Local development

It is recommended to develop and debug through the web. First, start the backend service, and start
it by the command line `go run cmd/api/main.go`, the default port of the service is `9999`, and then
start the front-end flutter project in `debug` mode to run.

## Translation

The internationalization files of Gopeed are located in the `ui/flutter/lib/i18n/langs` directory.
You only need to add the corresponding language file in this directory.

Please refer to `en_us.dart` for translation.
words prefixed with `@` are not meant to be translated.

## flutter development

Don't forget to run`dart format ./ui/flutter`before you commit to keep your code in standard dart format

Turn on build_runner watcher if you want to edit api/models:

```
flutter pub run build_runner watch
```

================================================
FILE: CONTRIBUTING_ja-JP.md
================================================
# Gopeed コントリビューターガイド

まず最初に、Gopeed への貢献に興味を持っていただきありがとうございます。このガイドは、あなたが Gopeed の
開発に参加するための手助けとなるでしょう。

## ブランチの説明

このプロジェクトのメインブランチは `main` ブランチのみです。Gopeed の開発に参加したい場合は、
まずこのプロジェクトをフォークし、フォークしたプロジェクトで開発を行ってください。開発が完了したら、
このプロジェクトに PR を提出し、`main` ブランチにマージしてください。

## ローカル開発

開発およびデバッグはウェブ上で行うことを推奨する。まずバックエンドのサービスを起動し、
コマンドライン `go run cmd/api/main.go` で起動する。サービスのデフォルトポートは `9999` で、
次にフロントエンドの flutter プロジェクトを `debug` モードで起動して実行します。

## 翻訳

Gopeed の国際化ファイルは `ui/flutter/lib/i18n/langs` ディレクトリにあります。
このディレクトリに対応する言語ファイルを追加するだけでよいです。

翻訳については `en_us.dart` を参照してください。

## flutter での開発

コミットする前に `dart format ./ui/flutter` を実行し、コードを標準の dart フォーマットにしておくことを忘れないでください

api/models を編集したい場合は build_runner watcher をオンにします:

```
flutter pub run build_runner watch
```


================================================
FILE: CONTRIBUTING_vi-VN.md
================================================
# Hướng dẫn đóng góp cho Gopeed

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
phát triển Gopeed một cách tốt hơn.

## Mô tả nhánh

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
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
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`.

## Phát triển cục bộ

Đề 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
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 đó
khởi động dự án flutter frontend trong chế độ `debug` để chạy.

## Dịch thuật

Các tệp quốc tế hóa của Gopeed được đặt trong thư mục `ui/flutter/lib/i18n/langs`.
Bạn chỉ cần thêm tệp ngôn ngữ tương ứng trong thư mục này.

Vui lòng tham khảo `en_us.dart` để biết cách dịch thuật.

## Phát triển flutter

Đừ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.

Bật build_runner watcher nếu bạn muốn chỉnh sửa api/models:


================================================
FILE: CONTRIBUTING_zh-CN.md
================================================
# Gopeed 贡献指南

首先感谢您对贡献代码感兴趣,这份指南将帮助您更好的参与到 Gopeed 的开发中来。

## 分支说明

本项目只有一个主分支,即 `main` 分支,如果您想要参与到 Gopeed 的开发中来,请先 fork 本项目,然后在您的 fork 项目中进行开发,开发完成后再向本项目提交
PR,合并到 `main` 分支。

## 本地开发

建议通过 web 端进行开发调试,首先启动后端服务,通过命令行 `go run cmd/api/main.go` 启动 ,服务启动默认端口为 `9999`,然后以 `debug` 模式启动前端
flutter 项目即可运行。

## 翻译
 
Gopeed 的国际化文件位于 `ui/flutter/lib/i18n/langs` 目录下,只需要在该目录下添加对应的语言文件即可。

请注意以 `en_us.dart` 为参照进行翻译。

## flutter开发

每次提交前请务必`dart format ./ui/flutter`

如果要编辑api/models,请打开build_runner watcher:

```
flutter pub run build_runner watch
```



================================================
FILE: CONTRIBUTING_zh-TW.md
================================================
# Gopeed 協助指南

首先感謝您願意幫助我們改進並優化該項目,這份指南將會幫助您更好的參與 Gopeed 的開發。

## 分支說明

本項目只有一個分支,即 `main` 分支,如果您想要參與 Gopeed 的開發,請先 fork 該項目,再在您自己的 fork 中進行開發,開發完成後再開啟PR,以合併至 `main` 分支。

## 離線開發

建議使用 web 端進行開發與調試,首先啟動服務,使用指令 `go run cmd/api/main.go` 啟動 ,該服務默認連接埠為 `9999`,接著以 `debug` 模式啟動前端 flutter 項目即可。

## 翻譯
 
Gopeed 的翻譯文件位於 `ui/flutter/lib/i18n/langs` 目錄中,只需要修改或新建翻譯文件即可。


請以 `en_us.dart` 作為參照。

## flutter開發

每次提交PR前請務必執行 `dart format ./ui/flutter`

如果需要編輯 api/models,請打開build_runner watcher:

```
flutter pub run build_runner watch
```


================================================
FILE: Dockerfile
================================================
FROM golang:1.24.11-alpine3.23 AS go
WORKDIR /app
COPY ./go.mod ./go.sum ./
RUN go mod download
COPY . .
ARG VERSION=dev
RUN CGO_ENABLED=0 go build -tags nosqlite,web \
      -ldflags="-s -w -X github.com/GopeedLab/gopeed/pkg/base.Version=$VERSION -X github.com/GopeedLab/gopeed/pkg/base.InDocker=true" \
      -o dist/gopeed github.com/GopeedLab/gopeed/cmd/web

FROM alpine:3.23
LABEL maintainer="monkeyWie"
WORKDIR /app
COPY --from=go /app/dist/gopeed ./
COPY entrypoint.sh ./entrypoint.sh
RUN apk update && \
    apk add --no-cache su-exec ; \
    chmod +x ./entrypoint.sh && \
    rm -rf /var/cache/apk/*
VOLUME ["/app/storage"]
ENV PUID=0 PGID=0 UMASK=022
EXPOSE 9999
ENTRYPOINT ["./entrypoint.sh"]


================================================
FILE: LICENSE
================================================
                    GNU GENERAL PUBLIC LICENSE
                       Version 3, 29 June 2007

 Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
 Everyone is permitted to copy and distribute verbatim copies
 of this license document, but changing it is not allowed.

                            Preamble

  The GNU General Public License is a free, copyleft license for
software and other kinds of works.

  The licenses for most software and other practical works are designed
to take away your freedom to share and change the works.  By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users.  We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors.  You can apply it to
your programs, too.

  When we speak of free software, we are referring to freedom, not
price.  Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.

  To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights.  Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.

  For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received.  You must make sure that they, too, receive
or can get the source code.  And you must show them these terms so they
know their rights.

  Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.

  For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software.  For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.

  Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so.  This is fundamentally incompatible with the aim of
protecting users' freedom to change the software.  The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable.  Therefore, we
have designed this version of the GPL to prohibit the practice for those
products.  If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.

  Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary.  To prevent this, the GPL assures that
patents cannot be used to render the program non-free.

  The precise terms and conditions for copying, distribution and
modification follow.

                       TERMS AND CONDITIONS

  0. Definitions.

  "This License" refers to version 3 of the GNU General Public License.

  "Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.

  "The Program" refers to any copyrightable work licensed under this
License.  Each licensee is addressed as "you".  "Licensees" and
"recipients" may be individuals or organizations.

  To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy.  The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.

  A "covered work" means either the unmodified Program or a work based
on the Program.

  To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy.  Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.

  To "convey" a work means any kind of propagation that enables other
parties to make or receive copies.  Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.

  An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License.  If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.

  1. Source Code.

  The "source code" for a work means the preferred form of the work
for making modifications to it.  "Object code" means any non-source
form of a work.

  A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.

  The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form.  A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.

  The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities.  However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work.  For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.

  The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.

  The Corresponding Source for a work in source code form is that
same work.

  2. Basic Permissions.

  All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met.  This License explicitly affirms your unlimited
permission to run the unmodified Program.  The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work.  This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.

  You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force.  You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright.  Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.

  Conveying under any other circumstances is permitted solely under
the conditions stated below.  Sublicensing is not allowed; section 10
makes it unnecessary.

  3. Protecting Users' Legal Rights From Anti-Circumvention Law.

  No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.

  When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.

  4. Conveying Verbatim Copies.

  You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.

  You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.

  5. Conveying Modified Source Versions.

  You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:

    a) The work must carry prominent notices stating that you modified
    it, and giving a relevant date.

    b) The work must carry prominent notices stating that it is
    released under this License and any conditions added under section
    7.  This requirement modifies the requirement in section 4 to
    "keep intact all notices".

    c) You must license the entire work, as a whole, under this
    License to anyone who comes into possession of a copy.  This
    License will therefore apply, along with any applicable section 7
    additional terms, to the whole of the work, and all its parts,
    regardless of how they are packaged.  This License gives no
    permission to license the work in any other way, but it does not
    invalidate such permission if you have separately received it.

    d) If the work has interactive user interfaces, each must display
    Appropriate Legal Notices; however, if the Program has interactive
    interfaces that do not display Appropriate Legal Notices, your
    work need not make them do so.

  A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit.  Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.

  6. Conveying Non-Source Forms.

  You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:

    a) Convey the object code in, or embodied in, a physical product
    (including a physical distribution medium), accompanied by the
    Corresponding Source fixed on a durable physical medium
    customarily used for software interchange.

    b) Convey the object code in, or embodied in, a physical product
    (including a physical distribution medium), accompanied by a
    written offer, valid for at least three years and valid for as
    long as you offer spare parts or customer support for that product
    model, to give anyone who possesses the object code either (1) a
    copy of the Corresponding Source for all the software in the
    product that is covered by this License, on a durable physical
    medium customarily used for software interchange, for a price no
    more than your reasonable cost of physically performing this
    conveying of source, or (2) access to copy the
    Corresponding Source from a network server at no charge.

    c) Convey individual copies of the object code with a copy of the
    written offer to provide the Corresponding Source.  This
    alternative is allowed only occasionally and noncommercially, and
    only if you received the object code with such an offer, in accord
    with subsection 6b.

    d) Convey the object code by offering access from a designated
    place (gratis or for a charge), and offer equivalent access to the
    Corresponding Source in the same way through the same place at no
    further charge.  You need not require recipients to copy the
    Corresponding Source along with the object code.  If the place to
    copy the object code is a network server, the Corresponding Source
    may be on a different server (operated by you or a third party)
    that supports equivalent copying facilities, provided you maintain
    clear directions next to the object code saying where to find the
    Corresponding Source.  Regardless of what server hosts the
    Corresponding Source, you remain obligated to ensure that it is
    available for as long as needed to satisfy these requirements.

    e) Convey the object code using peer-to-peer transmission, provided
    you inform other peers where the object code and Corresponding
    Source of the work are being offered to the general public at no
    charge under subsection 6d.

  A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.

  A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling.  In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage.  For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product.  A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.

  "Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source.  The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.

  If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information.  But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).

  The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed.  Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.

  Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.

  7. Additional Terms.

  "Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law.  If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.

  When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it.  (Additional permissions may be written to require their own
removal in certain cases when you modify the work.)  You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.

  Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:

    a) Disclaiming warranty or limiting liability differently from the
    terms of sections 15 and 16 of this License; or

    b) Requiring preservation of specified reasonable legal notices or
    author attributions in that material or in the Appropriate Legal
    Notices displayed by works containing it; or

    c) Prohibiting misrepresentation of the origin of that material, or
    requiring that modified versions of such material be marked in
    reasonable ways as different from the original version; or

    d) Limiting the use for publicity purposes of names of licensors or
    authors of the material; or

    e) Declining to grant rights under trademark law for use of some
    trade names, trademarks, or service marks; or

    f) Requiring indemnification of licensors and authors of that
    material by anyone who conveys the material (or modified versions of
    it) with contractual assumptions of liability to the recipient, for
    any liability that these contractual assumptions directly impose on
    those licensors and authors.

  All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10.  If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term.  If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.

  If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.

  Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.

  8. Termination.

  You may not propagate or modify a covered work except as expressly
provided under this License.  Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).

  However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.

  Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.

  Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License.  If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.

  9. Acceptance Not Required for Having Copies.

  You are not required to accept this License in order to receive or
run a copy of the Program.  Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance.  However,
nothing other than this License grants you permission to propagate or
modify any covered work.  These actions infringe copyright if you do
not accept this License.  Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.

  10. Automatic Licensing of Downstream Recipients.

  Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License.  You are not responsible
for enforcing compliance by third parties with this License.

  An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations.  If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.

  You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License.  For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.

  11. Patents.

  A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based.  The
work thus licensed is called the contributor's "contributor version".

  A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version.  For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.

  Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.

  In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement).  To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.

  If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients.  "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.

  If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.

  A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License.  You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.

  Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.

  12. No Surrender of Others' Freedom.

  If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License.  If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all.  For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.

  13. Use with the GNU Affero General Public License.

  Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work.  The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.

  14. Revised Versions of this License.

  The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time.  Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.

  Each version is given a distinguishing version number.  If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation.  If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.

  If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.

  Later license versions may give you additional or different
permissions.  However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.

  15. Disclaimer of Warranty.

  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.

  16. Limitation of Liability.

  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.

  17. Interpretation of Sections 15 and 16.

  If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.

                     END OF TERMS AND CONDITIONS

            How to Apply These Terms to Your New Programs

  If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.

  To do so, attach the following notices to the program.  It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.

    <one line to give the program's name and a brief idea of what it does.>
    Copyright (C) <year>  <name of author>

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <https://www.gnu.org/licenses/>.

Also add information on how to contact you by electronic and paper mail.

  If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:

    <program>  Copyright (C) <year>  <name of author>
    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
    This is free software, and you are welcome to redistribute it
    under certain conditions; type `show c' for details.

The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License.  Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".

  You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<https://www.gnu.org/licenses/>.

  The GNU General Public License does not permit incorporating your program
into proprietary programs.  If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library.  If this is what you want to do, use the GNU Lesser General
Public License instead of this License.  But first, please read
<https://www.gnu.org/licenses/why-not-lgpl.html>.


================================================
FILE: README.md
================================================
# [![](_docs/img/banner.svg)](https://gopeed.com)

[![Test Status](https://github.com/GopeedLab/gopeed/workflows/test/badge.svg)](https://github.com/GopeedLab/gopeed/actions?query=workflow%3Atest)
[![Codecov](https://codecov.io/gh/GopeedLab/gopeed/branch/main/graph/badge.svg)](https://codecov.io/gh/GopeedLab/gopeed)
[![Release](https://img.shields.io/github/release/GopeedLab/gopeed.svg)](https://github.com/GopeedLab/gopeed/releases)
[![Download](https://img.shields.io/github/downloads/GopeedLab/gopeed/total.svg)](https://github.com/GopeedLab/gopeed/releases)
[![Donate](https://img.shields.io/badge/%24-donate-ff69b4.svg)](https://gopeed.com/docs/donate)
[![WeChat](https://img.shields.io/badge/WeChat%20Official%20Account-07C160?logo=wechat&logoColor=white)](https://raw.githubusercontent.com/GopeedLab/gopeed/main/_docs/img/weixin.png)
[![Discord](https://img.shields.io/discord/1037992631881449472?label=Discord&logo=discord&style=social)](https://discord.gg/ZUJqJrwCGB)

<a href="https://trendshift.io/repositories/7953" target="_blank"><img src="https://trendshift.io/api/badge/repositories/7953" alt="GopeedLab%2Fgopeed | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>

[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/R6R6IJGN6)

[English](/README.md) | [中文](/README_zh-CN.md) | [日本語](/README_ja-JP.md) | [正體中文](/README_zh-TW.md) | [Tiếng Việt](/README_vi-VN.md)

## 🚀 Introduction

Gopeed (full name Go Speed), a high-speed downloader developed by `Golang` + `Flutter`, supports (HTTP, BitTorrent, Magnet, ED2K) protocol, and supports all platforms. In addition to basic download functions, Gopeed is also a highly customizable downloader that supports implementing more features through integration with [APIs](https://gopeed.com/docs/dev-api) or installation and development of [extensions](https://gopeed.com/docs/dev-extension).

Visit ✈ [Official Website](https://gopeed.com) | 📖 [Official Docs](https://gopeed.com/docs)

## ⬇️ Download

<table>
  <tbody>
    <tr>
      <td rowspan="4">🪟 Windows</td>
      <td rowspan="2"><code>EXE</code></td>
      <td>amd64</td>
      <td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-windows-amd64.zip">📥</a></td>
    </tr>
    <tr>
      <td>arm64</td>
      <td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-windows-arm64.zip">📥</a></td>
    </tr>
    <tr>
      <td rowspan="2"><code>Portable</code></td>
      <td>amd64</td>
      <td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-windows-amd64-portable.zip">📥</a></td>
    </tr>
    <tr>
      <td>arm64</td>
      <td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-windows-arm64-portable.zip">📥</a></td>
    </tr>
    <tr>
      <td rowspan="3">🍎 MacOS</td>
      <td rowspan="3"><code>DMG</code></td>
      <td>universal</td>
      <td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-macos.dmg">📥</a></td>
    </tr>
    <tr>
      <td>amd64</td>
      <td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-macos-amd64.dmg">📥</a></td>
    </tr>
    <tr>
      <td>arm64</td>
      <td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-macos-arm64.dmg">📥</a></td>
    </tr>
    <tr>
      <td rowspan="6">🐧 Linux</td>
      <td><code>Flathub</code></td>
      <td>amd64</td>
      <td><a href="https://flathub.org/apps/com.gopeed.Gopeed">📥</a></td>
    </tr>
    <tr>
      <td><code>SNAP</code></td>
      <td>amd64</td>
      <td><a href="https://snapcraft.io/gopeed">📥</a></td>
    </tr>
    <tr>
      <td rowspan="2"><code>DEB</code></td>
      <td>amd64</td>
      <td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-amd64.deb">📥</a></td>
    </tr>
    <tr>
      <td>arm64</td>
      <td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-arm64.deb">📥</a></td>
    </tr>
    <tr>
      <td rowspan="2"><code>AppImage</code></td>
      <td>amd64</td>
      <td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-amd64.AppImage">📥</a></td>
    </tr>
    <tr>
      <td>arm64</td>
      <td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-arm64.AppImage">📥</a></td>
    </tr>
    <tr>
      <td rowspan="4">🤖 Android</td>
      <td rowspan="4"><code>APK</code></td>
      <td>universal</td>
      <td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-android.apk">📥</a></td>
    </tr>
     <tr>
      <td>armeabi-v7a</td>
      <td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-android-armeabi-v7a.apk">📥</a></td>
    </tr>
     <tr>
      <td>arm64-v8a</td>
      <td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-android-arm64-v8a.apk">📥</a></td>
    </tr>
    <tr>
      <td>x86_64</td>
      <td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-android-x86_64.apk">📥</a></td>
    </tr>
    <tr>
      <td>📱 iOS</td>
      <td><code>IPA</code></td>
      <td>universal</td>
      <td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-ios.ipa">📥</a></td>
    </tr>
    <tr>
      <td>🐳 Docker</td>
      <td>-</td>
      <td>universal</td>
      <td><a href="https://hub.docker.com/r/liwei2633/gopeed">📥</a></td>
    </tr>
    <tr>
      <td rowspan="2">💾 Qnap</td>
      <td rowspan="2"><code>QPKG</code></td>
      <td>amd64</td>
      <td><a href="https://gopeed.com/api/download?tpl=gopeed-$version-qnap-amd64.qpkg">📥</a></td>
    </tr>
    <tr>
      <td>arm64</td>
      <td><a href="https://gopeed.com/api/download?tpl=gopeed-$version-qnap-arm64.qpkg">📥</a></td>
    </tr>
    <tr>
      <td rowspan="8">🌐 Web</td>
      <td rowspan="3"><code>Windows</code></td>
      <td>amd64</td>
      <td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-windows-amd64.zip">📥</a></td>
    </tr>
    <tr>
      <td>arm64</td>
      <td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-windows-arm64.zip">📥</a></td>
    </tr>
    <tr>
      <td>386</td>
      <td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-windows-386.zip">📥</a></td>
    </tr>
    <tr>
      <td rowspan="2"><code>MacOS</code></td>
      <td>amd64</td>
      <td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-macos-amd64.zip">📥</a></td>
    </tr>
    <tr>
      <td>arm64</td>
      <td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-macos-arm64.zip">📥</a></td>
    </tr>
    <tr>
      <td rowspan="3"><code>Linux</code></td>
      <td>amd64</td>
      <td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-linux-amd64.zip">📥</a></td>
    </tr>
    <tr>
      <td>arm64</td>
      <td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-linux-arm64.zip">📥</a></td>
    </tr>
    <tr>
      <td>386</td>
      <td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-linux-386.zip">📥</a></td>
    </tr>
  </tbody>
</table>

More about installation, please refer to [Installation](https://gopeed.com/docs/install)

### 🛠️ Command tool

use `go install`:

```bash
go install github.com/GopeedLab/gopeed/cmd/gopeed@latest
```

## 🔌 Browser Extension

Gopeed also provides a browser extension to take over browser downloads, supporting browsers such as Chrome, Edge, Firefox, etc., please refer to: [https://github.com/GopeedLab/browser-extension](https://github.com/GopeedLab/browser-extension)

## 📱 WeChat Official Account

Follow our WeChat Official Account to get the latest updates and news.

<img src="_docs/img/weixin.png" width="200" />

## 💝 Donate

If you like this project, please consider [donating](https://gopeed.com/docs/donate) to support the development of this project, thank you!

## 🖼️ Showcase

![](_docs/img/ui-demo.png)

## 👨‍💻 Development

This project is divided into two parts, the front end uses `flutter`, the back end uses `Golang`, and the two sides communicate through the `http` protocol. On the unix system, `unix socket` is used, and on the windows system, `tcp` protocol is used.

> The front code is located in the `ui/flutter` directory.

### 🌍 Environment

1. Golang 1.24+
2. Flutter 3.38+

### 📋 Clone

```bash
git clone git@github.com:GopeedLab/gopeed.git
```

### 🤝 Contributing

Please refer to [CONTRIBUTING.md](/CONTRIBUTING.md)

### 🏗️ Build

#### Desktop

First, you need to configure the environment according to the official [Flutter desktop website documention](https://docs.flutter.dev/development/platform-integration/desktop), then you will need to ensure the cgo environment is set up accordingly. For detailed instructions on setting up the cgo environment, please refer to relevant resources available online.

command:

- windows

```bash
go build -tags nosqlite -ldflags="-w -s" -buildmode=c-shared -o ui/flutter/windows/libgopeed.dll github.com/GopeedLab/gopeed/bind/desktop
cd ui/flutter
flutter build windows
```

- macos

```bash
go build -tags nosqlite -ldflags="-w -s" -buildmode=c-shared -o ui/flutter/macos/Frameworks/libgopeed.dylib github.com/GopeedLab/gopeed/bind/desktop
cd ui/flutter
flutter build macos
```

- linux

```bash
go build -tags nosqlite -ldflags="-w -s" -buildmode=c-shared -o ui/flutter/linux/bundle/lib/libgopeed.so github.com/GopeedLab/gopeed/bind/desktop
cd ui/flutter
flutter build linux
```

#### Mobile

Same as before, you also need to prepare the `cgo` environment, and then install `gomobile`:

```bash
go install golang.org/x/mobile/cmd/gomobile@latest
go get golang.org/x/mobile/bind
gomobile init
```

command:

- android

```bash
gomobile bind -tags nosqlite -ldflags="-w -s -checklinkname=0" -o ui/flutter/android/app/libs/libgopeed.aar -target=android -androidapi 21 -javapkg="com.gopeed" github.com/GopeedLab/gopeed/bind/mobile
cd ui/flutter
flutter build apk
```

- ios

```bash
gomobile bind -tags nosqlite -ldflags="-w -s" -o ui/flutter/ios/Frameworks/Libgopeed.xcframework -target=ios github.com/GopeedLab/gopeed/bind/mobile
cd ui/flutter
flutter build ios --no-codesign
```

#### Web

command:

```bash
cd ui/flutter
flutter build web
cd ../../
rm -rf cmd/web/dist
cp -r ui/flutter/build/web cmd/web/dist
go build -tags nosqlite,web -ldflags="-s -w" -o bin/ github.com/GopeedLab/gopeed/cmd/web
```

## ❤️ Credits

### 👥 Contributors

<a href="https://github.com/GopeedLab/gopeed/graphs/contributors">
  <img src="https://contrib.rocks/image?repo=GopeedLab/gopeed" />
</a>

### 🏢 JetBrains

[![goland](_docs/img/goland.svg)](https://www.jetbrains.com/?from=gopeed)

## 📄 License

[GPLv3](LICENSE)


================================================
FILE: README_ja-JP.md
================================================
# [![](_docs/img/banner.svg)](https://gopeed.com)

[![Test Status](https://github.com/GopeedLab/gopeed/workflows/test/badge.svg)](https://github.com/GopeedLab/gopeed/actions?query=workflow%3Atest)
[![Codecov](https://codecov.io/gh/GopeedLab/gopeed/branch/main/graph/badge.svg)](https://codecov.io/gh/GopeedLab/gopeed)
[![Release](https://img.shields.io/github/release/GopeedLab/gopeed.svg)](https://github.com/GopeedLab/gopeed/releases)
[![Download](https://img.shields.io/github/downloads/GopeedLab/gopeed/total.svg)](https://github.com/GopeedLab/gopeed/releases)
[![Donate](https://img.shields.io/badge/%24-donate-ff69b4.svg)](https://gopeed.com/docs/donate)
[![WeChat](https://img.shields.io/badge/WeChat%20Official%20Account-07C160?logo=wechat&logoColor=white)](https://raw.githubusercontent.com/GopeedLab/gopeed/main/_docs/img/weixin.png)
[![Discord](https://img.shields.io/discord/1037992631881449472?label=Discord&logo=discord&style=social)](https://discord.gg/ZUJqJrwCGB)

<a href="https://trendshift.io/repositories/7953" target="_blank"><img src="https://trendshift.io/api/badge/repositories/7953" alt="GopeedLab%2Fgopeed | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>

[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/R6R6IJGN6)

[English](/README.md) | [中文](/README_zh-CN.md) | [日本語](/README_ja-JP.md) | [正體中文](/README_zh-TW.md) | [Tiếng Việt](/README_vi-VN.md)

## 🚀 はじめに

Gopeed (正式名 Go Speed) は `Golang` + `Flutter` によって開発された高速ダウンローダーで、(HTTP、BitTorrent、Magnet、ED2K) プロトコルをサポートし、すべてのプラットフォームをサポートします。基本的なダウンロード機能に加え、[APIs](https://gopeed.com/docs/dev-api)との連動や[拡張機能](https://gopeed.com/docs/dev-extension)のインストール・開発による追加機能にも対応した、カスタマイズ性の高いダウンローダーです。

見て下さい ✈ [公式ウェブサイト](https://gopeed.com) | 📖 [開発ドキュメント](https://gopeed.com/docs)

## ⬇️ インストール

<table>
  <tbody>
    <tr>
      <td rowspan="2">🪟 Windows</td>
      <td><code>EXE</code></td>
      <td>amd64</td>
      <td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-windows-amd64.zip">📥</a></td>
    </tr>
    <tr>
      <td><code>Portable</code></td>
      <td>amd64</td>
      <td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-windows-amd64-portable.zip">📥</a></td>
    </tr>
    <tr>
      <td rowspan="3">🍎 MacOS</td>
      <td rowspan="3"><code>DMG</code></td>
      <td>universal</td>
      <td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-macos.dmg">📥</a></td>
    </tr>
    <tr>
      <td>amd64</td>
      <td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-macos-amd64.dmg">📥</a></td>
    </tr>
    <tr>
      <td>arm64</td>
      <td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-macos-arm64.dmg">📥</a></td>
    </tr>
    <tr>
      <td rowspan="6">🐧 Linux</td>
      <td><code>Flathub</code></td>
      <td>amd64</td>
      <td><a href="https://flathub.org/apps/com.gopeed.Gopeed">📥</a></td>
    </tr>
    <tr>
      <td><code>SNAP</code></td>
      <td>amd64</td>
      <td><a href="https://snapcraft.io/gopeed">📥</a></td>
    </tr>
    <tr>
      <td rowspan="2"><code>DEB</code></td>
      <td>amd64</td>
      <td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-amd64.deb">📥</a></td>
    </tr>
    <tr>
      <td>arm64</td>
      <td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-arm64.deb">📥</a></td>
    </tr>
    <tr>
      <td rowspan="2"><code>AppImage</code></td>
      <td>amd64</td>
      <td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-amd64.AppImage">📥</a></td>
    </tr>
    <tr>
      <td>arm64</td>
      <td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-arm64.AppImage">📥</a></td>
    </tr>
    <tr>
      <td rowspan="4">🤖 Android</td>
      <td rowspan="4"><code>APK</code></td>
      <td>universal</td>
      <td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-android.apk">📥</a></td>
    </tr>
     <tr>
      <td>armeabi-v7a</td>
      <td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-android-armeabi-v7a.apk">📥</a></td>
    </tr>
     <tr>
      <td>arm64-v8a</td>
      <td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-android-arm64-v8a.apk">📥</a></td>
    </tr>
    <tr>
      <td>x86_64</td>
      <td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-android-x86_64.apk">📥</a></td>
    </tr>
    <tr>
      <td>📱 iOS</td>
      <td><code>IPA</code></td>
      <td>universal</td>
      <td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-ios.ipa">📥</a></td>
    </tr>
    <tr>
      <td>🐳 Docker</td>
      <td>-</td>
      <td>universal</td>
      <td><a href="https://hub.docker.com/r/liwei2633/gopeed">📥</a></td>
    </tr>
    <tr>
      <td rowspan="2">💾 Qnap</td>
      <td rowspan="2"><code>QPKG</code></td>
      <td>amd64</td>
      <td><a href="https://gopeed.com/api/download?tpl=gopeed-$version-qnap-amd64.qpkg">📥</a></td>
    </tr>
    <tr>
      <td>arm64</td>
      <td><a href="https://gopeed.com/api/download?tpl=gopeed-$version-qnap-arm64.qpkg">📥</a></td>
    </tr>
    <tr>
      <td rowspan="8">🌐 Web</td>
      <td rowspan="3"><code>Windows</code></td>
      <td>amd64</td>
      <td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-windows-amd64.zip">📥</a></td>
    </tr>
    <tr>
      <td>arm64</td>
      <td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-windows-arm64.zip">📥</a></td>
    </tr>
    <tr>
      <td>386</td>
      <td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-windows-386.zip">📥</a></td>
    </tr>
    <tr>
      <td rowspan="2"><code>MacOS</code></td>
      <td>amd64</td>
      <td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-macos-amd64.zip">📥</a></td>
    </tr>
    <tr>
      <td>arm64</td>
      <td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-macos-arm64.zip">📥</a></td>
    </tr>
    <tr>
      <td rowspan="3"><code>Linux</code></td>
      <td>amd64</td>
      <td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-linux-amd64.zip">📥</a></td>
    </tr>
    <tr>
      <td>arm64</td>
      <td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-linux-arm64.zip">📥</a></td>
    </tr>
    <tr>
      <td>386</td>
      <td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-linux-386.zip">📥</a></td>
    </tr>
  </tbody>
</table>
インストールについての詳細は、[インストール](https://gopeed.com/docs/install)を参照してください。

### 🛠️ コマンドツール

## 📱 WeChat 公式アカウント

公式アカウントをフォローして、最新のアップデートやニュースを入手してください。

<img src="_docs/img/weixin.png" width="200" />

## 💝 寄付

もしこのプロジェクトがお気に召しましたら、このプロジェクトの発展を支援するために[寄付](https://gopeed.com/docs/donate)をご検討ください!

## 🖼️ ショーケース

![](_docs/img/ui-demo.png)

## 👨‍💻 開発

このプロジェクトは二つの部分に分かれており、フロントエンドでは `flutter` を、バックエンドでは `Golang` を使用し、両者は `http` プロトコルで通信する。ユニックスシステムでは `unix socket` を、ウィンドウズシステムでは `tcp` プロトコルを使用します。

> フロントコードは `ui/flutter` ディレクトリにあります。

### 🌍 環境

1. Go 言語 1.24+
2. Flutter 3.38+

### 📋 クローン

```bash
git clone git@github.com:GopeedLab/gopeed.git
```

### 🤝 コントリビュート

[CONTRIBUTING.md](/CONTRIBUTING_ja-JP.md) をご参照ください

### 🏗️ ビルド

#### デスクトップ

まず、[flutter デスクトップ公式サイトドキュメント](https://docs.flutter.dev/development/platform-integration/desktop)に従って環境を設定し、自分で検索できる `cgo` 環境を用意します。

コマンド:

- windows

```bash
go build -tags nosqlite -ldflags="-w -s" -buildmode=c-shared -o ui/flutter/windows/libgopeed.dll github.com/GopeedLab/gopeed/bind/desktop
cd ui/flutter
flutter build windows
```

- macos

```bash
go build -tags nosqlite -ldflags="-w -s" -buildmode=c-shared -o ui/flutter/macos/Frameworks/libgopeed.dylib github.com/GopeedLab/gopeed/bind/desktop
cd ui/flutter
flutter build macos
```

- linux

```bash
go build -tags nosqlite -ldflags="-w -s" -buildmode=c-shared -o ui/flutter/linux/bundle/lib/libgopeed.so github.com/GopeedLab/gopeed/bind/desktop
cd ui/flutter
flutter build linux
```

#### モバイル

先ほどと同じように、`cgo` 環境を準備し、`gomobile` をインストールする必要があります:

```bash
go install golang.org/x/mobile/cmd/gomobile@latest
go get golang.org/x/mobile/bind
gomobile init
```

コマンド:

- android

```bash
gomobile bind -tags nosqlite -ldflags="-w -s -checklinkname=0" -o ui/flutter/android/app/libs/libgopeed.aar -target=android -androidapi 21 -javapkg="com.gopeed" github.com/GopeedLab/gopeed/bind/mobile
cd ui/flutter
flutter build apk
```

- ios

```bash
gomobile bind -tags nosqlite -ldflags="-w -s" -o ui/flutter/ios/Frameworks/Libgopeed.xcframework -target=ios github.com/GopeedLab/gopeed/bind/mobile
cd ui/flutter
flutter build ios --no-codesign
```

#### Web

コマンド:

```bash
cd ui/flutter
flutter build web
cd ../../
rm -rf cmd/web/dist
cp -r ui/flutter/build/web cmd/web/dist
go build -tags nosqlite,web -ldflags="-s -w" -o bin/ github.com/GopeedLab/gopeed/cmd/web
```

## ❤️ 感謝

### コントリビューター

<a href="https://github.com/GopeedLab/gopeed/graphs/contributors">
  <img src="https://contrib.rocks/image?repo=GopeedLab/gopeed" />
</a>

### JetBrains

[![goland](_docs/img/goland.svg)](https://www.jetbrains.com/?from=gopeed)

## ライセンス

[GPLv3](LICENSE)


================================================
FILE: README_vi-VN.md
================================================
# [![](_docs/img/banner.svg)](https://gopeed.com)

[![Trạng thái kiểm tra](https://github.com/GopeedLab/gopeed/workflows/test/badge.svg)](https://github.com/GopeedLab/gopeed/actions?query=workflow%3Atest)
[![Codecov](https://codecov.io/gh/GopeedLab/gopeed/branch/main/graph/badge.svg)](https://codecov.io/gh/GopeedLab/gopeed)
[![Phiên bản](https://img.shields.io/github/release/GopeedLab/gopeed.svg)](https://github.com/GopeedLab/gopeed/releases)
[![Tải về](https://img.shields.io/github/downloads/GopeedLab/gopeed/total.svg)](https://github.com/GopeedLab/gopeed/releases)
[![Ủng hộ](https://img.shields.io/badge/%24-ủng%20hộ-ff69b4.svg)](https://gopeed.com/docs/donate)
[![WeChat](https://img.shields.io/badge/WeChat%20Official%20Account-07C160?logo=wechat&logoColor=white)](https://raw.githubusercontent.com/GopeedLab/gopeed/main/_docs/img/weixin.png)
[![Discord](https://img.shields.io/discord/1037992631881449472?label=Discord&logo=discord&style=social)](https://discord.gg/ZUJqJrwCGB)

<a href="https://trendshift.io/repositories/7953" target="_blank"><img src="https://trendshift.io/api/badge/repositories/7953" alt="GopeedLab%2Fgopeed | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>

[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/R6R6IJGN6)

[English](/README.md) | [中文](/README_zh-CN.md) | [日本語](/README_ja-JP.md) | [正體中文](/README_zh-TW.md) | [Tiếng Việt](/README_vi-VN.md)

## 🚀 Giới thiệu

Gopeed (tên đầy đủ Go Speed), một công cụ tải xuống tốc độ cao được phát triển bởi `Golang` + `Flutter`, hỗ trợ giao thức (HTTP, BitTorrent, Magnet, ED2K) và hỗ trợ tất cả các nền tảng. Ngoài các chức năng tải xuống cơ bản, Gopeed còn là một công cụ tải xuống có thể tùy chỉnh cao cho phép triển khai thêm tính năng thông qua việc tích hợp với [APIs](https://gopeed.com/docs/dev-api) hoặc cài đặt và phát triển các [tiện ích mở rộng](https://gopeed.com/docs/dev-extension).

Truy cập ✈ [Trang web chính thức](https://gopeed.com) | 📖 [Tài liệu chính thức](https://gopeed.com/docs)

## ⬇️ Tải về

<table>
  <tbody>
    <tr>
      <td rowspan="2">🪟 Windows</td>
      <td><code>EXE</code></td>
      <td>amd64</td>
      <td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-windows-amd64.zip">📥</a></td>
    </tr>
    <tr>
      <td><code>Portable</code></td>
      <td>amd64</td>
      <td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-windows-amd64-portable.zip">📥</a></td>
    </tr>
    <tr>
      <td rowspan="3">🍎 MacOS</td>
      <td rowspan="3"><code>DMG</code></td>
      <td>universal</td>
      <td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-macos.dmg">📥</a></td>
    </tr>
    <tr>
      <td>amd64</td>
      <td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-macos-amd64.dmg">📥</a></td>
    </tr>
    <tr>
      <td>arm64</td>
      <td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-macos-arm64.dmg">📥</a></td>
    </tr>
    <tr>
      <td rowspan="6">🐧 Linux</td>
      <td><code>Flathub</code></td>
      <td>amd64</td>
      <td><a href="https://flathub.org/apps/com.gopeed.Gopeed">📥</a></td>
    </tr>
    <tr>
      <td><code>SNAP</code></td>
      <td>amd64</td>
      <td><a href="https://snapcraft.io/gopeed">📥</a></td>
    </tr>
    <tr>
      <td rowspan="2"><code>DEB</code></td>
      <td>amd64</td>
      <td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-amd64.deb">📥</a></td>
    </tr>
    <tr>
      <td>arm64</td>
      <td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-arm64.deb">📥</a></td>
    </tr>
    <tr>
      <td rowspan="2"><code>AppImage</code></td>
      <td>amd64</td>
      <td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-amd64.AppImage">📥</a></td>
    </tr>
    <tr>
      <td>arm64</td>
      <td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-arm64.AppImage">📥</a></td>
    </tr>
    <tr>
      <td rowspan="4">🤖 Android</td>
      <td rowspan="4"><code>APK</code></td>
      <td>universal</td>
      <td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-android.apk">📥</a></td>
    </tr>
     <tr>
      <td>armeabi-v7a</td>
      <td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-android-armeabi-v7a.apk">📥</a></td>
    </tr>
     <tr>
      <td>arm64-v8a</td>
      <td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-android-arm64-v8a.apk">📥</a></td>
    </tr>
    <tr>
      <td>x86_64</td>
      <td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-android-x86_64.apk">📥</a></td>
    </tr>
    <tr>
      <td>📱 iOS</td>
      <td><code>IPA</code></td>
      <td>universal</td>
      <td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-ios.ipa">📥</a></td>
    </tr>
    <tr>
      <td>🐳 Docker</td>
      <td>-</td>
      <td>universal</td>
      <td><a href="https://hub.docker.com/r/liwei2633/gopeed">📥</a></td>
    </tr>
    <tr>
      <td rowspan="2">💾 Qnap</td>
      <td rowspan="2"><code>QPKG</code></td>
      <td>amd64</td>
      <td><a href="https://gopeed.com/api/download?tpl=gopeed-$version-qnap-amd64.qpkg">📥</a></td>
    </tr>
    <tr>
      <td>arm64</td>
      <td><a href="https://gopeed.com/api/download?tpl=gopeed-$version-qnap-arm64.qpkg">📥</a></td>
    </tr>
    <tr>
      <td rowspan="8">🌐 Web</td>
      <td rowspan="3"><code>Windows</code></td>
      <td>amd64</td>
      <td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-windows-amd64.zip">📥</a></td>
    </tr>
    <tr>
      <td>arm64</td>
      <td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-windows-arm64.zip">📥</a></td>
    </tr>
    <tr>
      <td>386</td>
      <td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-windows-386.zip">📥</a></td>
    </tr>
    <tr>
      <td rowspan="2"><code>MacOS</code></td>
      <td>amd64</td>
      <td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-macos-amd64.zip">📥</a></td>
    </tr>
    <tr>
      <td>arm64</td>
      <td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-macos-arm64.zip">📥</a></td>
    </tr>
    <tr>
      <td rowspan="3"><code>Linux</code></td>
      <td>amd64</td>
      <td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-linux-amd64.zip">📥</a></td>
    </tr>
    <tr>
      <td>arm64</td>
      <td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-linux-arm64.zip">📥</a></td>
    </tr>
    <tr>
      <td>386</td>
      <td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-linux-386.zip">📥</a></td>
    </tr>
  </tbody>
</table>
Thêm thông tin về cài đặt, vui lòng tham khảo [Cài đặt](https://gopeed.com/docs/install)

### 🛠️ Công cụ lệnh

Sử dụng `go install`:

```bash
go install github.com/GopeedLab/gopeed/cmd/gopeed@latest
```

## 📱 WeChat Official Account

Theo dõi tài khoản chính thức để nhận các cập nhật và tin tức mới nhất.

<img src="_docs/img/weixin.png" width="200" />

## 💝 Quyên góp

Nếu bạn thích dự án này, xin vui lòng xem xét [quyên góp](https://gopeed.com/docs/donate) để hỗ trợ phát triển dự án này, cảm ơn bạn!

## 🖼️ Trưng bày

![](_docs/img/ui-demo.png)

## 👨‍💻 Development

Dự án này được chia thành hai phần, phần giao diện sử dụng `flutter`, phần backend sử dụng `Golang`, và hai phía giao tiếp thông qua giao thức `http`. Trên hệ thống unix, sử dụng `unix socket`, và trên hệ thống windows, sử dụng giao thức `tcp`.

> Mã giao diện nằm trong thư mục `ui/flutter`.

### 🌍 Environment

1. Golang 1.24+
2. Flutter 3.38+

### 📋 Clone

```bash
git clone git@github.com:GopeedLab/gopeed.git
```

### 🤝 Đóng góp

Vui lòng tham khảo [CONTRIBUTING_vi-VN.md](/CONTRIBUTING_vi-VN.md)

### 🏗️ Xây dựng

#### Desktop

Trước tiên, bạn cần cấu hình môi trường theo tài liệu chính thức của [Tài liệu trang web máy tính để bàn Flutter](https://docs.flutter.dev/development/platform-integration/desktop), sau đó bạn cần đảm bảo môi trường cgo được thiết lập đúng. Để biết hướng dẫn chi tiết về cách thiết lập môi trường cgo, vui lòng tham khảo các tài liệu tương ứng có sẵn trực tuyến.

command:

- windows

```bash
go build -tags nosqlite -ldflags="-w -s" -buildmode=c-shared -o ui/flutter/windows/libgopeed.dll github.com/GopeedLab/gopeed/bind/desktop
cd ui/flutter
flutter build windows
```

- macos

```bash
go build -tags nosqlite -ldflags="-w -s" -buildmode=c-shared -o ui/flutter/macos/Frameworks/libgopeed.dylib github.com/GopeedLab/gopeed/bind/desktop
cd ui/flutter
flutter build macos
```

- linux

```bash
go build -tags nosqlite -ldflags="-w -s" -buildmode=c-shared -o ui/flutter/linux/bundle/lib/libgopeed.so github.com/GopeedLab/gopeed/bind/desktop
cd ui/flutter
flutter build linux
```

#### Mobile

Giống như trước đây, bạn cũng cần chuẩn bị môi trường `cgo` và sau đó cài đặt `gomobile`:

```bash
go install golang.org/x/mobile/cmd/gomobile@latest
go get golang.org/x/mobile/bind
gomobile init
```

command:

- android

```bash
gomobile bind -tags nosqlite -ldflags="-w -s -checklinkname=0" -o ui/flutter/android/app/libs/libgopeed.aar -target=android -androidapi 21 -javapkg="com.gopeed" github.com/GopeedLab/gopeed/bind/mobile
cd ui/flutter
flutter build apk
```

- ios

```bash
gomobile bind -tags nosqlite -ldflags="-w -s" -o ui/flutter/ios/Frameworks/Libgopeed.xcframework -target=ios github.com/GopeedLab/gopeed/bind/mobile
cd ui/flutter
flutter build ios --no-codesign
```

#### Web

command:

```bash
cd ui/flutter
flutter build web
cd ../../
rm -rf cmd/web/dist
cp -r ui/flutter/build/web cmd/web/dist
go build -tags nosqlite,web -ldflags="-s -w" -o bin/ github.com/GopeedLab/gopeed/cmd/web
```

## ❤️ Tín dụng

### Người đóng góp

<a href="https://github.com/GopeedLab/gopeed/graphs/contributors">
  <img src="https://contrib.rocks/image?repo=GopeedLab/gopeed" />
</a>

### JetBrains

[![goland](_docs/img/goland.svg)](https://www.jetbrains.com/?from=gopeed)

## Giấy phép

[GPLv3](LICENSE)


================================================
FILE: README_zh-CN.md
================================================
# [![](_docs/img/banner.svg)](https://gopeed.com)

[![Test Status](https://github.com/GopeedLab/gopeed/workflows/test/badge.svg)](https://github.com/GopeedLab/gopeed/actions?query=workflow%3Atest)
[![Codecov](https://codecov.io/gh/GopeedLab/gopeed/branch/main/graph/badge.svg)](https://codecov.io/gh/GopeedLab/gopeed)
[![Release](https://img.shields.io/github/release/GopeedLab/gopeed.svg)](https://github.com/GopeedLab/gopeed/releases)
[![Download](https://img.shields.io/github/downloads/GopeedLab/gopeed/total.svg)](https://github.com/GopeedLab/gopeed/releases)
[![Donate](https://img.shields.io/badge/%24-donate-ff69b4.svg)](https://gopeed.com/docs/donate)
[![WeChat](https://img.shields.io/badge/%E5%BE%AE%E4%BF%A1%E5%85%AC%E4%BC%97%E5%8F%B7-07C160?logo=wechat&logoColor=white)](https://raw.githubusercontent.com/GopeedLab/gopeed/main/_docs/img/weixin.png)
[![Discord](https://img.shields.io/discord/1037992631881449472?label=Discord&logo=discord&style=social)](https://discord.gg/ZUJqJrwCGB)

<a href="https://trendshift.io/repositories/7953" target="_blank"><img src="https://trendshift.io/api/badge/repositories/7953" alt="GopeedLab%2Fgopeed | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>

[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/R6R6IJGN6)

[English](/README.md) | [中文](/README_zh-CN.md) | [日本語](/README_ja-JP.md) | [正體中文](/README_zh-TW.md) | [Tiếng Việt](/README_vi-VN.md)

## 🚀 介绍

Gopeed(全称 Go Speed),直译过来中文名叫做`够快下载器`(不是狗屁下载器!),是一款由`Golang`+`Flutter`开发的高速下载器,支持(HTTP、BitTorrent、Magnet、ED2K)协议下载,并且支持全平台使用。除了基本的下载功能外,Gopeed 还是一款高度可定制化的下载器,支持通过对接[APIs](https://gopeed.com/docs/dev-api)或者安装和开发[扩展](https://gopeed.com/docs/dev-extension)来实现更多的功能。

访问 ✈ [官方网站](https://gopeed.com/zh-CN) | 📖 [官方文档](https://gopeed.com/docs)

## ⬇️ 下载

<table>
  <tbody>
    <tr>
      <td rowspan="2">🪟 Windows</td>
      <td><code>EXE</code></td>
      <td>amd64</td>
      <td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-windows-amd64.zip">📥</a></td>
    </tr>
    <tr>
      <td><code>Portable</code></td>
      <td>amd64</td>
      <td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-windows-amd64-portable.zip">📥</a></td>
    </tr>
    <tr>
      <td rowspan="3">🍎 MacOS</td>
      <td rowspan="3"><code>DMG</code></td>
      <td>universal</td>
      <td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-macos.dmg">📥</a></td>
    </tr>
    <tr>
      <td>amd64</td>
      <td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-macos-amd64.dmg">📥</a></td>
    </tr>
    <tr>
      <td>arm64</td>
      <td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-macos-arm64.dmg">📥</a></td>
    </tr>
    <tr>
      <td rowspan="6">🐧 Linux</td>
      <td><code>Flathub</code></td>
      <td>amd64</td>
      <td><a href="https://flathub.org/apps/com.gopeed.Gopeed">📥</a></td>
    </tr>
    <tr>
      <td><code>SNAP</code></td>
      <td>amd64</td>
      <td><a href="https://snapcraft.io/gopeed">📥</a></td>
    </tr>
    <tr>
      <td rowspan="2"><code>DEB</code></td>
      <td>amd64</td>
      <td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-amd64.deb">📥</a></td>
    </tr>
    <tr>
      <td>arm64</td>
      <td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-arm64.deb">📥</a></td>
    </tr>
    <tr>
      <td rowspan="2"><code>AppImage</code></td>
      <td>amd64</td>
      <td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-amd64.AppImage">📥</a></td>
    </tr>
    <tr>
      <td>arm64</td>
      <td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-arm64.AppImage">📥</a></td>
    </tr>
    <tr>
      <td rowspan="4">🤖 Android</td>
      <td rowspan="4"><code>APK</code></td>
      <td>universal</td>
      <td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-android.apk">📥</a></td>
    </tr>
     <tr>
      <td>armeabi-v7a</td>
      <td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-android-armeabi-v7a.apk">📥</a></td>
    </tr>
     <tr>
      <td>arm64-v8a</td>
      <td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-android-arm64-v8a.apk">📥</a></td>
    </tr>
    <tr>
      <td>x86_64</td>
      <td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-android-x86_64.apk">📥</a></td>
    </tr>
    <tr>
      <td>📱 iOS</td>
      <td><code>IPA</code></td>
      <td>universal</td>
      <td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-ios.ipa">📥</a></td>
    </tr>
    <tr>
      <td>🐳 Docker</td>
      <td>-</td>
      <td>universal</td>
      <td><a href="https://hub.docker.com/r/liwei2633/gopeed">📥</a></td>
    </tr>
    <tr>
      <td rowspan="2">💾 Qnap</td>
      <td rowspan="2"><code>QPKG</code></td>
      <td>amd64</td>
      <td><a href="https://gopeed.com/api/download?tpl=gopeed-$version-qnap-amd64.qpkg">📥</a></td>
    </tr>
    <tr>
      <td>arm64</td>
      <td><a href="https://gopeed.com/api/download?tpl=gopeed-$version-qnap-arm64.qpkg">📥</a></td>
    </tr>
    <tr>
      <td rowspan="8">🌐 Web</td>
      <td rowspan="3"><code>Windows</code></td>
      <td>amd64</td>
      <td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-windows-amd64.zip">📥</a></td>
    </tr>
    <tr>
      <td>arm64</td>
      <td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-windows-arm64.zip">📥</a></td>
    </tr>
    <tr>
      <td>386</td>
      <td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-windows-386.zip">📥</a></td>
    </tr>
    <tr>
      <td rowspan="2"><code>MacOS</code></td>
      <td>amd64</td>
      <td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-macos-amd64.zip">📥</a></td>
    </tr>
    <tr>
      <td>arm64</td>
      <td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-macos-arm64.zip">📥</a></td>
    </tr>
    <tr>
      <td rowspan="3"><code>Linux</code></td>
      <td>amd64</td>
      <td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-linux-amd64.zip">📥</a></td>
    </tr>
    <tr>
      <td>arm64</td>
      <td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-linux-arm64.zip">📥</a></td>
    </tr>
    <tr>
      <td>386</td>
      <td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-linux-386.zip">📥</a></td>
    </tr>
  </tbody>
</table>
更多关于安装的内容请参考[安装文档](https://gopeed.com/docs/install)

### 🛠️ 命令行工具

使用`go install`安装:

```bash
go install github.com/GopeedLab/gopeed/cmd/gopeed@latest
```

## 🔌 浏览器扩展

Gopeed 还提供了浏览器扩展用于接管浏览器下载,支持 Chrome、Edge、Firefox 等浏览器,具体请参考:[https://github.com/GopeedLab/browser-extension](https://github.com/GopeedLab/browser-extension)

## 📱 微信公众号

关注公众号获取项目最新动态和资讯。

<img src="_docs/img/weixin.png" width="200" />

## 💝 赞助

如果觉得项目对你有帮助,请考虑[赞助](https://gopeed.com/docs/donate)以支持这个项目的发展,非常感谢!

## 🖼️ 界面展示

![](_docs/img/ui-demo.png)

## 👨‍💻 开发

本项目分为前端和后端两个部分,前端使用`flutter`,后端使用`Golang`,两边通过`http`协议进行通讯,在 unix 系统下,使用的是`unix socket`,在 windows 系统下,使用的是`tcp`协议。

> 前端代码位于`ui/flutter`目录下。

### 🌍 环境要求

1. Golang 1.24+
2. Flutter 3.38+

### 📋 克隆项目

```bash
git clone git@github.com:GopeedLab/gopeed.git
```

### 🤝 贡献代码

请参考[贡献指南](CONTRIBUTING_zh-CN.md)

### 🏗️ 编译

#### 桌面端

首先需要按照[flutter desktop 官网文档](https://docs.flutter.dev/development/platform-integration/desktop)进行环境配置,然后需要准备好`cgo`环境,具体可以自行搜索。

构建命令:

- windows

```bash
go build -tags nosqlite -ldflags="-w -s" -buildmode=c-shared -o ui/flutter/windows/libgopeed.dll github.com/GopeedLab/gopeed/bind/desktop
cd ui/flutter
flutter build windows
```

- macos

```bash
go build -tags nosqlite -ldflags="-w -s" -buildmode=c-shared -o ui/flutter/macos/Frameworks/libgopeed.dylib github.com/GopeedLab/gopeed/bind/desktop
cd ui/flutter
flutter build macos
```

- linux

```bash
go build -tags nosqlite -ldflags="-w -s" -buildmode=c-shared -o ui/flutter/linux/bundle/lib/libgopeed.so github.com/GopeedLab/gopeed/bind/desktop
cd ui/flutter
flutter build linux
```

#### 移动端

同样的也是需要准备好`cgo`环境,接着安装`gomobile`:

```bash
go install golang.org/x/mobile/cmd/gomobile@latest
go get golang.org/x/mobile/bind
gomobile init
```

构建命令:

- android

```bash
gomobile bind -tags nosqlite -ldflags="-w -s -checklinkname=0" -o ui/flutter/android/app/libs/libgopeed.aar -target=android -androidapi 21 -javapkg="com.gopeed" github.com/GopeedLab/gopeed/bind/mobile
cd ui/flutter
flutter build apk
```

- ios

```bash
gomobile bind -tags nosqlite -ldflags="-w -s" -o ui/flutter/ios/Frameworks/Libgopeed.xcframework -target=ios github.com/GopeedLab/gopeed/bind/mobile
cd ui/flutter
flutter build ios --no-codesign
```

#### Web 端

构建命令:

```bash
cd ui/flutter
flutter build web
cd ../../
rm -rf cmd/web/dist
cp -r ui/flutter/build/web cmd/web/dist
go build -tags nosqlite,web -ldflags="-s -w" -o bin/ github.com/GopeedLab/gopeed/cmd/web
```

## ❤️ 感谢

### 贡献者

<a href="https://github.com/GopeedLab/gopeed/graphs/contributors">
  <img src="https://contrib.rocks/image?repo=GopeedLab/gopeed" />
</a>

### JetBrains

[![goland](_docs/img/goland.svg)](https://www.jetbrains.com/?from=gopeed)

## 开源许可

基于 [GPLv3](LICENSE) 协议开源。


================================================
FILE: README_zh-TW.md
================================================
# [![](_docs/img/banner.svg)](https://gopeed.com)

[![Test Status](https://github.com/GopeedLab/gopeed/workflows/test/badge.svg)](https://github.com/GopeedLab/gopeed/actions?query=workflow%3Atest)
[![Codecov](https://codecov.io/gh/GopeedLab/gopeed/branch/main/graph/badge.svg)](https://codecov.io/gh/GopeedLab/gopeed)
[![Release](https://img.shields.io/github/release/GopeedLab/gopeed.svg)](https://github.com/GopeedLab/gopeed/releases)
[![Download](https://img.shields.io/github/downloads/GopeedLab/gopeed/total.svg)](https://github.com/GopeedLab/gopeed/releases)
[![Donate](https://img.shields.io/badge/%24-donate-ff69b4.svg)](https://gopeed.com/docs/donate)
[![WeChat](https://img.shields.io/badge/%E5%BE%AE%E4%BF%A1%E5%85%AC%E4%BC%97%E5%8F%B7-07C160?logo=wechat&logoColor=white)](https://raw.githubusercontent.com/GopeedLab/gopeed/main/_docs/img/weixin.png)
[![Discord](https://img.shields.io/discord/1037992631881449472?label=Discord&logo=discord&style=social)](https://discord.gg/ZUJqJrwCGB)

<a href="https://trendshift.io/repositories/7953" target="_blank"><img src="https://trendshift.io/api/badge/repositories/7953" alt="GopeedLab%2Fgopeed | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>

[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/R6R6IJGN6)

[English](/README.md) | [中文](/README_zh-CN.md) | [日本語](/README_ja-JP.md) | [正體中文](/README_zh-TW.md) | [Tiếng Việt](/README_vi-VN.md)

## 🚀 簡介

Gopeed(全稱 Go Speed),是一款使用`Golang`+`Flutter`編寫的高速下載軟體,支援(HTTP、BitTorrent、Magnet、ED2K)協定,同時支援所有的平台。

前往 ✈ [主頁](https://gopeed.com/zh-CN) | 📖 [文檔](https://gopeed.com/docs)

## ⬇️ 下載

<table>
  <tbody>
    <tr>
      <td rowspan="2">🪟 Windows</td>
      <td><code>EXE</code></td>
      <td>amd64</td>
      <td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-windows-amd64.zip">📥</a></td>
    </tr>
    <tr>
      <td><code>Portable</code></td>
      <td>amd64</td>
      <td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-windows-amd64-portable.zip">📥</a></td>
    </tr>
    <tr>
      <td rowspan="3">🍎 MacOS</td>
      <td rowspan="3"><code>DMG</code></td>
      <td>universal</td>
      <td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-macos.dmg">📥</a></td>
    </tr>
    <tr>
      <td>amd64</td>
      <td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-macos-amd64.dmg">📥</a></td>
    </tr>
    <tr>
      <td>arm64</td>
      <td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-macos-arm64.dmg">📥</a></td>
    </tr>
    <tr>
      <td rowspan="6">🐧 Linux</td>
      <td><code>Flathub</code></td>
      <td>amd64</td>
      <td><a href="https://flathub.org/apps/com.gopeed.Gopeed">📥</a></td>
    </tr>
    <tr>
      <td><code>SNAP</code></td>
      <td>amd64</td>
      <td><a href="https://snapcraft.io/gopeed">📥</a></td>
    </tr>
    <tr>
      <td rowspan="2"><code>DEB</code></td>
      <td>amd64</td>
      <td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-amd64.deb">📥</a></td>
    </tr>
    <tr>
      <td>arm64</td>
      <td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-arm64.deb">📥</a></td>
    </tr>
    <tr>
      <td rowspan="2"><code>AppImage</code></td>
      <td>amd64</td>
      <td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-amd64.AppImage">📥</a></td>
    </tr>
    <tr>
      <td>arm64</td>
      <td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-linux-arm64.AppImage">📥</a></td>
    </tr>
    <tr>
      <td rowspan="4">🤖 Android</td>
      <td rowspan="4"><code>APK</code></td>
      <td>universal</td>
      <td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-android.apk">📥</a></td>
    </tr>
     <tr>
      <td>armeabi-v7a</td>
      <td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-android-armeabi-v7a.apk">📥</a></td>
    </tr>
     <tr>
      <td>arm64-v8a</td>
      <td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-android-arm64-v8a.apk">📥</a></td>
    </tr>
    <tr>
      <td>x86_64</td>
      <td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-android-x86_64.apk">📥</a></td>
    </tr>
    <tr>
      <td>📱 iOS</td>
      <td><code>IPA</code></td>
      <td>universal</td>
      <td><a href="https://gopeed.com/api/download?tpl=Gopeed-$version-ios.ipa">📥</a></td>
    </tr>
    <tr>
      <td>🐳 Docker</td>
      <td>-</td>
      <td>universal</td>
      <td><a href="https://hub.docker.com/r/liwei2633/gopeed">📥</a></td>
    </tr>
    <tr>
      <td rowspan="2">💾 Qnap</td>
      <td rowspan="2"><code>QPKG</code></td>
      <td>amd64</td>
      <td><a href="https://gopeed.com/api/download?tpl=gopeed-$version-qnap-amd64.qpkg">📥</a></td>
    </tr>
    <tr>
      <td>arm64</td>
      <td><a href="https://gopeed.com/api/download?tpl=gopeed-$version-qnap-arm64.qpkg">📥</a></td>
    </tr>
    <tr>
      <td rowspan="8">🌐 Web</td>
      <td rowspan="3"><code>Windows</code></td>
      <td>amd64</td>
      <td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-windows-amd64.zip">📥</a></td>
    </tr>
    <tr>
      <td>arm64</td>
      <td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-windows-arm64.zip">📥</a></td>
    </tr>
    <tr>
      <td>386</td>
      <td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-windows-386.zip">📥</a></td>
    </tr>
    <tr>
      <td rowspan="2"><code>MacOS</code></td>
      <td>amd64</td>
      <td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-macos-amd64.zip">📥</a></td>
    </tr>
    <tr>
      <td>arm64</td>
      <td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-macos-arm64.zip">📥</a></td>
    </tr>
    <tr>
      <td rowspan="3"><code>Linux</code></td>
      <td>amd64</td>
      <td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-linux-amd64.zip">📥</a></td>
    </tr>
    <tr>
      <td>arm64</td>
      <td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-linux-arm64.zip">📥</a></td>
    </tr>
    <tr>
      <td>386</td>
      <td><a href="https://gopeed.com/api/download?tpl=gopeed-web-$version-linux-386.zip">📥</a></td>
    </tr>
  </tbody>
</table>

更多關於安裝的內容請參考[安裝文檔](https://gopeed.com/docs/install)

### 🛠️ 使用 CLI 安裝

使用`go install`安裝:

```bash
go install github.com/GopeedLab/gopeed/cmd/gopeed@latest
```

## 📱 微信公眾號

關注公眾號獲取項目最新動態和資訊。

<img src="_docs/img/weixin.png" width="200" />

## 💝 贊助

如果你認為該項目對你有所幫助,請考慮[贊助](https://gopeed.com/docs/donate)以支持該項目的持續發展,謝謝!

## 🖼️ 軟體介面

![](_docs/img/ui-demo.png)

## 👨‍💻 開發

該項目分為前端與後端,前端使用`flutter`編寫,後端使用`Golang`編寫,兩邊通過`http`協定進行通訊,在 unix 系統下,則使用`unix socket`,在 windows 系統下,則使用`tcp`協定。

> 前端代碼位於`ui/flutter`目錄內。

### 🌍 開發環境

1. Golang 1.24+
2. Flutter 3.38+

### 📋 克隆項目

```bash
git clone git@github.com:GopeedLab/gopeed.git
```

### 🤝 協助開發

請參考[協助指南](CONTRIBUTING_zh-TW.md)

### 🏗️ 編譯

#### 桌面端

首先需要按照[flutter desktop 官方文檔](https://docs.flutter.dev/development/platform-integration/desktop)配置開發環境,並準備好`cgo`環境,具體方法可以自行搜索。

組建指令:

- windows

```bash
go build -tags nosqlite -ldflags="-w -s" -buildmode=c-shared -o ui/flutter/windows/libgopeed.dll github.com/GopeedLab/gopeed/bind/desktop
cd ui/flutter
flutter build windows
```

- macos

```bash
go build -tags nosqlite -ldflags="-w -s" -buildmode=c-shared -o ui/flutter/macos/Frameworks/libgopeed.dylib github.com/GopeedLab/gopeed/bind/desktop
cd ui/flutter
flutter build macos
```

- linux

```bash
go build -tags nosqlite -ldflags="-w -s" -buildmode=c-shared -o ui/flutter/linux/bundle/lib/libgopeed.so github.com/GopeedLab/gopeed/bind/desktop
cd ui/flutter
flutter build linux
```

#### 移動設備

需要`cgo`環境,並安裝`gomobile`:

```bash
go install golang.org/x/mobile/cmd/gomobile@latest
go get golang.org/x/mobile/bind
gomobile init
```

組建指令:

- android

```bash
gomobile bind -tags nosqlite -ldflags="-w -s -checklinkname=0" -o ui/flutter/android/app/libs/libgopeed.aar -target=android -androidapi 21 -javapkg="com.gopeed" github.com/GopeedLab/gopeed/bind/mobile
cd ui/flutter
flutter build apk
```

- ios

```bash
gomobile bind -tags nosqlite -ldflags="-w -s" -o ui/flutter/ios/Frameworks/Libgopeed.xcframework -target=ios github.com/GopeedLab/gopeed/bind/mobile
cd ui/flutter
flutter build ios --no-codesign
```

#### 網頁端

組建指令:

```bash
cd ui/flutter
flutter build web
cd ../../
rm -rf cmd/web/dist
cp -r ui/flutter/build/web cmd/web/dist
go build -tags nosqlite,web -ldflags="-s -w" -o bin/ github.com/GopeedLab/gopeed/cmd/web
```

## ❤️ 感謝

### 貢獻者

<a href="https://github.com/GopeedLab/gopeed/graphs/contributors">
  <img src="https://contrib.rocks/image?repo=GopeedLab/gopeed" />
</a>

### JetBrains

[![goland](_docs/img/goland.svg)](https://www.jetbrains.com/?from=gopeed)

## 軟體許可

該軟體遵循 [GPLv3](LICENSE) 。


================================================
FILE: _examples/basic/main.go
================================================
package main

import (
	"fmt"

	"github.com/GopeedLab/gopeed/pkg/base"
	"github.com/GopeedLab/gopeed/pkg/download"
	"github.com/GopeedLab/gopeed/pkg/protocol/http"
)

func main() {
	finallyCh := make(chan error)
	_, err := download.Boot().
		URL("https://www.baidu.com/index.html").
		Listener(func(event *download.Event) {
			if event.Key == download.EventKeyFinally {
				finallyCh <- event.Err
			}
		}).
		Create(&base.Options{
			Extra: http.OptsExtra{
				Connections: 8,
			},
		})
	if err != nil {
		panic(err)
	}
	err = <-finallyCh
	if err != nil {
		fmt.Printf("download fail:%v\n", err)
	} else {
		fmt.Println("download success")
	}
}


================================================
FILE: bind/desktop/main.go
================================================
package main

import "C"
import (
	"encoding/json"
	"github.com/GopeedLab/gopeed/pkg/rest"
	"github.com/GopeedLab/gopeed/pkg/rest/model"
)

func main() {}

//export Start
func Start(cfg *C.char) (int, *C.char) {
	var config model.StartConfig
	if err := json.Unmarshal([]byte(C.GoString(cfg)), &config); err != nil {
		return 0, C.CString(err.Error())
	}
	config.ProductionMode = true
	realPort, err := rest.Start(&config)
	if err != nil {
		return 0, C.CString(err.Error())
	}
	return realPort, nil
}

//export Stop
func Stop() {
	rest.Stop()
}


================================================
FILE: bind/mobile/main.go
================================================
package libgopeed

// #cgo LDFLAGS: -static-libstdc++
import "C"
import (
	"encoding/json"
	"github.com/GopeedLab/gopeed/pkg/rest"
	"github.com/GopeedLab/gopeed/pkg/rest/model"
)

func Start(cfg string) (int, error) {
	var config model.StartConfig
	if err := json.Unmarshal([]byte(cfg), &config); err != nil {
		return 0, err
	}
	config.ProductionMode = true
	return rest.Start(&config)
}

func Stop() {
	rest.Stop()
}


================================================
FILE: cmd/api/main.go
================================================
package main

import (
	"github.com/GopeedLab/gopeed/cmd"
	"github.com/GopeedLab/gopeed/pkg/rest/model"
)

// only for local development
func main() {
	cfg := &model.StartConfig{
		Network:   "tcp",
		Address:   "127.0.0.1:9999",
		Storage:   model.StorageBolt,
		WebEnable: true,
	}
	cmd.Start(cfg)
}


================================================
FILE: cmd/banner.txt
================================================

  _______   ______   .______    _______  _______  _______
 /  _____| /  __  \  |   _  \  |   ____||   ____||       \
|  |  __  |  |  |  | |  |_)  | |  |__   |  |__   |  .--.  |
|  | |_ | |  |  |  | |   ___/  |   __|  |   __|  |  |  |  |
|  |__| | |  `--'  | |  |      |  |____ |  |____ |  '--'  |
 \______|  \______/  | _|      |_______||_______||_______/


================================================
FILE: cmd/gopeed/flags.go
================================================
package main

import (
	"flag"
	"fmt"
	"os"
)

type args struct {
	url         string
	connections *int
	dir         *string
}

func parse() *args {
	dir, err := os.Getwd()
	if err != nil {
		panic(err)
	}
	var args args
	args.connections = flag.Int("C", 16, "Concurrent connections.")
	args.dir = flag.String("D", dir, "Store directory.")
	flag.Parse()
	t := flag.Args()
	if len(t) > 0 {
		args.url = t[0]
	} else {
		gPrintln("missing url parameter, for example: gopeed https://www.google.com or gopeed bt.torrent or gopeed magnet:?xt=urn:btih:...")
		gPrintln("try 'gopeed -h' for more information")
		os.Exit(1)
	}
	return &args
}

func gPrint(msg string) {
	fmt.Print("gopeed: " + msg)
}

func gPrintln(msg string) {
	gPrint(msg + "\n")
}


================================================
FILE: cmd/gopeed/main.go
================================================
package main

import (
	"fmt"
	"strings"
	"sync"

	"github.com/GopeedLab/gopeed/internal/fetcher"
	"github.com/GopeedLab/gopeed/pkg/base"
	"github.com/GopeedLab/gopeed/pkg/download"
	"github.com/GopeedLab/gopeed/pkg/protocol/http"
	"github.com/GopeedLab/gopeed/pkg/util"
)

const progressWidth = 20

func main() {
	args := parse()

	var wg sync.WaitGroup
	wg.Add(1)
	_, err := download.Boot().
		URL(args.url).
		Listener(func(event *download.Event) {
			if event.Key == download.EventKeyProgress {
				printProgress(event.Task, "downloading...")
			}
			if event.Key == download.EventKeyFinally {
				var title string
				if event.Err != nil {
					title = "fail"
				} else {
					title = "complete"
				}
				printProgress(event.Task, title)
				fmt.Println()
				if event.Err != nil {
					fmt.Printf("reason: %s", event.Err.Error())
				} else {
					fmt.Printf("saving path: %s", *args.dir)
				}
				wg.Done()
			}
		}).
		Create(&base.Options{
			Path:  *args.dir,
			Extra: http.OptsExtra{Connections: *args.connections},
		})
	if err != nil {
		panic(err)
	}
	printProgress(emptyTask, "downloading...")
	wg.Wait()
}

var (
	lastLineLen = 0
	sb          = new(strings.Builder)
	emptyTask   = &download.Task{
		Progress: &download.Progress{},
		Meta: &fetcher.FetcherMeta{
			Res: &base.Resource{},
		},
	}
)

func printProgress(task *download.Task, title string) {
	var rate float64
	if task.Meta.Res == nil {
		task = emptyTask
	}
	if task.Meta.Res.Size <= 0 {
		rate = 0
	} else {
		rate = float64(task.Progress.Downloaded) / float64(task.Meta.Res.Size)
	}
	completeWidth := int(progressWidth * rate)
	speed := util.ByteFmt(task.Progress.Speed)
	totalSize := util.ByteFmt(task.Meta.Res.Size)
	sb.WriteString(fmt.Sprintf("\r%s [", title))
	for i := 0; i < progressWidth; i++ {
		if i < completeWidth {
			sb.WriteString("■")
		} else {
			sb.WriteString("□")
		}
	}
	sb.WriteString(fmt.Sprintf("] %.1f%%    %s/s    %s", rate*100, speed, totalSize))
	if lastLineLen != 0 {
		paddingLen := lastLineLen - sb.Len()
		if paddingLen > 0 {
			sb.WriteString(strings.Repeat(" ", paddingLen))
		}
	}
	lastLineLen = sb.Len()
	fmt.Print(sb.String())
	sb.Reset()
}


================================================
FILE: cmd/host/dail_other.go
================================================
//go:build !windows
// +build !windows

package main

import (
	"net"
	"os"
	"path/filepath"
)

func Dial() (net.Conn, error) {
	// Get binary path
	exe, err := os.Executable()
	if err != nil {
		return nil, err
	}
	return net.Dial("unix", filepath.Join(filepath.Dir(exe), "gopeed_host.sock"))
}


================================================
FILE: cmd/host/dail_windows.go
================================================
package main

import (
	"net"

	"github.com/Microsoft/go-winio"
)

func Dial() (net.Conn, error) {
	return winio.DialPipe(`\\.\pipe\gopeed_host`, nil)
}


================================================
FILE: cmd/host/main.go
================================================
package main

import (
	"bytes"
	"context"
	"encoding/binary"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"net"
	"net/http"
	"os"
	"time"

	"github.com/pkg/browser"
)

type Message struct {
	Method string          `json:"method"`
	Meta   map[string]any  `json:"meta"`
	Params json.RawMessage `json:"params"`
}

type Response struct {
	Code    int    `json:"code"`
	Data    any    `json:"data,omitempty"`
	Message string `json:"message,omitempty"`
}

func check() (data bool, err error) {
	conn, err := Dial()
	if err != nil {
		return false, err
	}
	defer conn.Close()
	return true, nil
}

func wakeup(hidden bool) error {
	running, _ := check()
	if running {
		return nil
	}

	uri := "gopeed:"
	if hidden {
		uri = uri + "?hidden=true"
	}
	if err := browser.OpenURL(uri); err != nil {
		return err
	}

	for i := 0; i < 10; i++ {
		if ok, _ := check(); ok {
			return nil
		}
		time.Sleep(1 * time.Second)
	}
	return fmt.Errorf("start gopeed failed")
}

// postToFlutter sends a POST request to Flutter RPC server
func postToFlutter(path string, body []byte, headers map[string]string, timeout time.Duration) (*http.Response, error) {
	client := &http.Client{
		Transport: &http.Transport{
			DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
				return Dial()
			},
		},
		Timeout: timeout,
	}
	req, err := http.NewRequest("POST", "http://127.0.0.1"+path, bytes.NewBuffer(body))
	if err != nil {
		return nil, err
	}
	req.Header.Set("Content-Type", "application/json")
	for k, v := range headers {
		req.Header.Set(k, v)
	}
	return client.Do(req)
}

var apiMap = map[string]func(message *Message) (data any, err error){
	"ping": func(message *Message) (data any, err error) {
		return check()
	},
	"wakeup": func(message *Message) (data any, err error) {
		silent := false
		if v, ok := message.Meta["silent"]; ok {
			silent, _ = v.(bool)
		}
		err = wakeup(silent)
		return
	},
	"create": func(message *Message) (data any, err error) {
		buf, err := message.Params.MarshalJSON()
		if err != nil {
			return
		}

		silent := false
		if v, ok := message.Meta["silent"]; ok {
			silent, _ = v.(bool)
		}

		if err := wakeup(silent); err != nil {
			return nil, err
		}

		headers := make(map[string]string)
		if message.Meta != nil {
			metaJson, _ := json.Marshal(message.Meta)
			headers["X-Gopeed-Host-Meta"] = string(metaJson)
		}
		_, err = postToFlutter("/create", buf, headers, 10*time.Second)
		return
	},
	"forward": func(message *Message) (data any, err error) {
		buf, err := message.Params.MarshalJSON()
		if err != nil {
			return
		}

		resp, err := postToFlutter("/forward", buf, nil, 60*time.Second)
		if err != nil {
			return
		}
		defer resp.Body.Close()

		respBody, err := io.ReadAll(resp.Body)
		if err != nil {
			return nil, err
		}

		var respData map[string]json.RawMessage
		if err := json.Unmarshal(respBody, &respData); err != nil {
			return nil, err
		}
		return respData, nil
	},
}

// go build -ldflags="-s -w" -o ui/flutter/assets/exec/ github.com/GopeedLab/gopeed/cmd/host

func main() {
	for {
		// Read message length (first 4 bytes)
		var length uint32
		if err := binary.Read(os.Stdin, binary.NativeEndian, &length); err != nil {
			if err == io.EOF {
				return
			}
			sendError("Failed to read message length: " + err.Error())
			return
		}

		// Read the message
		input := make([]byte, length)
		if _, err := io.ReadFull(os.Stdin, input); err != nil {
			sendError("Failed to read message: " + err.Error())
			return
		}

		// Parse message
		var message Message
		if err := json.Unmarshal(input, &message); err != nil {
			sendError("Failed to parse message: " + err.Error())
			return
		}

		// Handle request
		var data any
		var err error
		if handler, ok := apiMap[message.Method]; ok {
			data, err = handler(&message)
		} else {
			err = errors.New("Unknown method: " + message.Method)
		}
		if err != nil {
			sendError(err.Error())
			continue
		}
		sendResponse(0, data, "")
	}
}

func sendResponse(code int, data interface{}, message string) {
	response := Response{
		Code:    code,
		Data:    data,
		Message: message,
	}

	// Encode response
	responseBytes, err := json.Marshal(response)
	if err != nil {
		sendError("Failed to encode response: " + err.Error())
		return
	}

	// Write message length
	binary.Write(os.Stdout, binary.NativeEndian, uint32(len(responseBytes)))
	// Write message
	os.Stdout.Write(responseBytes)
}

func sendError(msg string) {
	sendResponse(1, nil, msg)
}


================================================
FILE: cmd/server.go
================================================
package cmd

import (
	_ "embed"
	"fmt"
	"github.com/GopeedLab/gopeed/pkg/base"
	"github.com/GopeedLab/gopeed/pkg/rest"
	"github.com/GopeedLab/gopeed/pkg/rest/model"
	"net/http"
	"os"
	"os/signal"
	"path/filepath"
	"syscall"
)

//go:embed banner.txt
var banner string

func Start(cfg *model.StartConfig) {
	fmt.Println(banner)
	srv, listener, err := rest.BuildServer(cfg)
	if err != nil {
		panic(err)
	}
	downloadCfg, err := rest.Downloader.GetConfig()
	if err != nil {
		panic(err)
	}
	if downloadCfg.FirstLoad {
		// Set default download config
		if cfg.DownloadConfig != nil {
			cfg.DownloadConfig.Merge(downloadCfg)
			// TODO Use PatchConfig
			rest.Downloader.PutConfig(cfg.DownloadConfig)
			downloadCfg = cfg.DownloadConfig
		}

		downloadDir := downloadCfg.DownloadDir
		// Set default download dir, in docker, it will be ${exe}/Downloads, else it will be ${user}/Downloads
		if downloadDir == "" {
			if base.InDocker == "true" {
				downloadDir = filepath.Join(filepath.Dir(cfg.StorageDir), "Downloads")
			} else {
				userDir, err := os.UserHomeDir()
				if err == nil {
					downloadDir = filepath.Join(userDir, "Downloads")
				}
			}
			if downloadDir != "" {
				downloadCfg.DownloadDir = downloadDir
				rest.Downloader.PutConfig(downloadCfg)
			}
		}
	}
	watchExit()

	fmt.Printf("Server start success on http://%s\n", listener.Addr().String())
	if err := srv.Serve(listener); err != nil && err != http.ErrServerClosed {
		panic(err)
	}
}

func watchExit() {
	sigs := make(chan os.Signal, 1)
	signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
	go func() {
		sig := <-sigs
		fmt.Printf("Server is shutting down due to signal: %s\n", sig)
		rest.Downloader.Close()
		os.Exit(0)
	}()
}


================================================
FILE: cmd/updater/main.go
================================================
package main

import (
	"flag"
	"fmt"
	"log"
	"os"
	"syscall"
	"time"

	"github.com/pkg/browser"
	"github.com/pkg/errors"
)

// go build -ldflags="-s -w" -o ui/flutter/assets/exec/ github.com/GopeedLab/gopeed/cmd/updater

func main() {
	pid := flag.Int("pid", 0, "PID of the process to update")
	updateChannel := flag.String("channel", "", "Update channel")
	packagePath := flag.String("asset", "", "Path to the package asset")
	exeDir := flag.String("exeDir", "", "Directory of the entry executable")
	logPath := flag.String("log", "", "Log file path")
	flag.Parse()

	if *pid == 0 {
		log.Println("Invalid PID")
		os.Exit(1)
	}
	if *updateChannel == "" {
		log.Println("Invalid update channel")
		os.Exit(1)
	}

	if *logPath != "" {
		logFile, err := os.OpenFile(*logPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0666)
		if err == nil {
			defer logFile.Close()
			log.SetOutput(logFile)
		}
	}

	var (
		restart bool
		err     error
	)
	if restart, err = update(*pid, *updateChannel, *packagePath, *exeDir); err != nil {
		log.Printf("Update failed: %v\n", err)
		os.Exit(1)
	}

	// Restart the application
	if restart {
		browser.OpenURL("gopeed:///")
	}

	// Delete package asset
	if *packagePath != "" {
		os.Remove(*packagePath)
	}

	os.Exit(0)
}

func update(pid int, updateChannel, packagePath, exeDir string) (restart bool, err error) {
	killSignalChan := make(chan any, 1)

	go func() {
		<-killSignalChan

		if err = killProcess(pid); err != nil {
			log.Printf("Failed to kill process: %v\n", err)
		}

		if err = waitForProcessExit(pid); err != nil {
			log.Printf("Failed to wait for process exit: %v\n", err)
		}
	}()

	log.Printf("Updating process updateChannel=%s packagePath=%s exeDir=%s\n", updateChannel, packagePath, exeDir)
	if restart, err = install(killSignalChan, updateChannel, packagePath, exeDir); err != nil {
		return false, errors.Wrap(err, "failed to install package")
	}

	return
}

func waitForProcessExit(pid int) error {
	deadline := time.Now().Add(10 * time.Second)
	for time.Now().Before(deadline) {
		process, err := os.FindProcess(pid)
		if err != nil {
			// On some systems, error is returned if process doesn't exist
			return nil
		}

		// Send null signal to test if process exists
		err = process.Signal(syscall.Signal(0))
		if err != nil {
			// If error occurs, the process no longer exists
			return nil
		}

		time.Sleep(100 * time.Millisecond)
	}
	return fmt.Errorf("process %d still running after timeout", pid)
}

func killProcess(pid int) error {
	process, err := os.FindProcess(pid)
	if err != nil {
		return err
	}

	return process.Kill()
}


================================================
FILE: cmd/updater/updater_darwin.go
================================================
//go:build darwin

package main

import (
	"fmt"
	"os/exec"
	"path/filepath"
	"strings"
)

func install(killSignalChan chan<- any, updateChannel, packagePath, destDir string) (bool, error) {
	return true, installByDmg(killSignalChan, packagePath, destDir)
}

// installByDmg handles macOS dmg package installation
func installByDmg(killSignalChan chan<- any, packagePath, destDir string) error {
	// /Applications/Gopeed.app/Contents/MacOS -> /Applications
	appPath := getParentDir(getParentDir(getParentDir(destDir)))
	output, err := exec.Command("hdiutil", "attach", packagePath, "-nobrowse").Output()
	if err != nil {
		return err
	}

	mountPoint := ""
	for _, line := range strings.Split(string(output), "\n") {
		if strings.Contains(line, "/Volumes/") {
			// Find the /Volumes/ path in the line
			// hdiutil output format: /dev/disk4s1  Apple_HFS  /Volumes/Gopeed
			// or with sequence number: /dev/disk4s1  Apple_HFS  /Volumes/Gopeed 1
			idx := strings.Index(line, "/Volumes/")
			if idx != -1 {
				// Extract everything from /Volumes/ onwards and trim whitespace
				mountPoint = strings.TrimSpace(line[idx:])
				break
			}
		}
	}

	if mountPoint == "" {
		return fmt.Errorf("failed to get mount point from hdiutil output: %s", string(output))
	}

	// Detach the mounted DMG
	defer exec.Command("hdiutil", "detach", mountPoint, "-quiet").Run()

	matches, err := filepath.Glob(filepath.Join(mountPoint, "*.app"))
	if err != nil {
		return err
	}
	if len(matches) == 0 {
		return fmt.Errorf("no .app found in dmg, mountPoint: %s", mountPoint)
	}

	killSignalChan <- nil

	// Copy the new app to the destination
	// cp -Rf /Volumes/GoPeed/GoPeed.app /Applications
	if err := exec.Command("cp", "-Rf", matches[0], appPath).Run(); err != nil {
		return err
	}

	return nil
}

// Get parent directory safely, handling trailing separators
func getParentDir(path string) string {
	// Remove trailing separators if they exist
	path = strings.TrimRight(path, string(filepath.Separator))
	// Now get the parent directory
	return filepath.Dir(path)
}


================================================
FILE: cmd/updater/updater_linux.go
================================================
//go:build linux

package main

import (
	"fmt"
	"os/exec"
)

func install(killSignalChan chan<- any, updateChannel, packagePath, destDir string) (bool, error) {
	killSignalChan <- nil
	switch updateChannel {
	case "linuxDeb":
		return true, installByDeb(packagePath)
	case "linuxFlathub":
		return true, installByFlathub()
	case "linuxSnap":
		return true, installBySnap()
	default:
		return false, fmt.Errorf("unsupported update channel for Linux: %s", updateChannel)
	}
}

// executeInTerminal tries to execute a command in one of several common terminal emulators
func executeInTerminal(command string) error {
	terminals := []string{
		"gnome-terminal", // GNOME
		"konsole",        // KDE
		"xfce4-terminal", // XFCE
		"xterm",          // X11
	}

	command = fmt.Sprintf(`echo "Starting update..." && echo "[CMD] %s" && %s`, command, command)

	for _, term := range terminals {
		if _, err := exec.LookPath(term); err == nil {
			var cmd *exec.Cmd
			switch term {
			case "gnome-terminal", "xfce4-terminal":
				cmd = exec.Command(term, "--", "bash", "-c", command)
			case "konsole":
				cmd = exec.Command(term, "-e", "bash", "-c", command)
			case "xterm":
				cmd = exec.Command(term, "-e", command)
			}

			if err := cmd.Start(); err == nil {
				return nil
			}
		}
	}

	return fmt.Errorf("no suitable terminal emulator found. Please install gnome-terminal, konsole, xfce4-terminal or xterm")
}

// installByDeb installs the .deb package
func installByDeb(packagePath string) error {
	command := fmt.Sprintf(`sudo dpkg -i "%s"`, packagePath)
	return executeInTerminal(command)
}

// installByFlathub updates the application via Flathub
func installByFlathub() error {
	command := "flatpak update com.gopeed.Gopeed -y"
	return executeInTerminal(command)
}

// installBySnap updates the application via Snap
func installBySnap() error {
	command := "sudo snap refresh gopeed"
	return executeInTerminal(command)
}


================================================
FILE: cmd/updater/updater_windows.go
================================================
//go:build windows

package main

import (
	"archive/zip"
	"fmt"
	"io"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
)

func install(killSignalChan chan<- any, updateChannel, packagePath, destDir string) (bool, error) {
	switch updateChannel {
	case "windowsInstaller":
		return false, installByInstaller(killSignalChan, packagePath, destDir)
	default:
		return true, installByPortable(killSignalChan, packagePath, destDir)
	}
}

// installByInstaller extracts the installer from the zip file and runs it
func installByInstaller(killSignalChan chan<- any, packagePath, destDir string) error {
	// Create a temp directory for extraction
	tempDir, err := os.MkdirTemp("", "gopeed_update")
	if err != nil {
		return err
	}
	defer os.RemoveAll(tempDir)

	// Extract the zip file
	reader, err := zip.OpenReader(packagePath)
	if err != nil {
		return err
	}
	defer reader.Close()

	// Find the installer file
	var installerPath string
	for _, file := range reader.File {
		if file.FileInfo().IsDir() {
			continue
		}

		// Extract file
		path := filepath.Join(tempDir, file.Name)

		if err := os.MkdirAll(filepath.Dir(path), os.ModePerm); err != nil {
			return err
		}

		dstFile, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, file.Mode())
		if err != nil {
			return err
		}

		srcFile, err := file.Open()
		if err != nil {
			dstFile.Close()
			return err
		}

		_, err = io.Copy(dstFile, srcFile)
		srcFile.Close()
		dstFile.Close()
		if err != nil {
			return err
		}

		// If this is likely an installer (.exe, .msi), save its path
		ext := strings.ToLower(filepath.Ext(file.Name))
		if ext == ".exe" || ext == ".msi" {
			installerPath = path
		}
	}

	if installerPath == "" {
		return fmt.Errorf("no installer found in the update package")
	}

	// Run the installer
	cmd := exec.Command(installerPath)
	if err := cmd.Start(); err != nil {
		return err
	}

	killSignalChan <- nil
	return nil
}

// installByPortable extracts the portable version to the destination directory
func installByPortable(killSignalChan chan<- any, packagePath, destDir string) error {
	killSignalChan <- nil

	reader, err := zip.OpenReader(packagePath)
	if err != nil {
		return err
	}
	defer reader.Close()

	for _, file := range reader.File {
		path := filepath.Join(destDir, file.Name)

		if file.FileInfo().IsDir() {
			os.MkdirAll(path, file.Mode())
			continue
		}

		if err := os.MkdirAll(filepath.Dir(path), os.ModePerm); err != nil {
			return err
		}

		dstFile, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, file.Mode())
		if err != nil {
			return err
		}

		srcFile, err := file.Open()
		if err != nil {
			dstFile.Close()
			return err
		}

		_, err = io.Copy(dstFile, srcFile)
		srcFile.Close()
		dstFile.Close()
		if err != nil {
			return err
		}
	}
	return nil
}


================================================
FILE: cmd/web/flags.go
================================================
package main

import (
	"encoding/json"
	"flag"
	"os"
	"path/filepath"
	"reflect"
	"strconv"
	"strings"

	"github.com/GopeedLab/gopeed/pkg/base"
)

type args struct {
	Address           *string  `json:"address"`
	Port              *int     `json:"port"`
	Username          *string  `json:"username"`
	Password          *string  `json:"password"`
	ApiToken          *string  `json:"apiToken"`
	StorageDir        *string  `json:"storageDir"`
	WhiteDownloadDirs []string `json:"whiteDownloadDirs"`
	// DownloadConfig when the first time to start the server, it will be configured as initial value
	DownloadConfig *base.DownloaderStoreConfig `json:"downloadConfig"`

	configPath *string
}

func parse() *args {
	cfg := &args{}

	cliConfig := loadCliArgs()
	loadConfigFile(cfg, *cliConfig.configPath)
	loadEnvVars(cfg)
	// override with non-default command line arguments
	overrideWithCliArgs(cfg, cliConfig)
	// set default values
	setDefaults(cfg, cliConfig)
	return cfg
}

// loadCliArgs parses command line arguments and returns initial config
func loadCliArgs() *args {
	cfg := &args{}
	cfg.Address = flag.String("A", "0.0.0.0", "Bind Address")
	cfg.Port = flag.Int("P", 9999, "Bind Port")
	cfg.Username = flag.String("u", "gopeed", "Web Authentication Username")
	cfg.Password = flag.String("p", "", "Web Authentication Password, if no password is set, web authentication will not be enabled")
	cfg.ApiToken = flag.String("T", "", "API token, it must be configured when using HTTP API in the case of enabling web authentication")
	cfg.StorageDir = flag.String("d", "", "Storage directory")
	whiteDownloadDirs := flag.String("w", "", "White download directories, comma-separated")
	cfg.configPath = flag.String("c", "./config.json", "Config file path")
	flag.Parse()

	// Parse white download directories from comma-separated string
	if whiteDownloadDirs != nil && *whiteDownloadDirs != "" {
		dirs := strings.Split(*whiteDownloadDirs, ",")
		for i := range dirs {
			dirs[i] = strings.TrimSpace(dirs[i])
		}
		cfg.WhiteDownloadDirs = dirs
	}

	return cfg
}

// overrideWithCliArgs overrides config with non-empty command line arguments
func overrideWithCliArgs(cfg *args, cliConfig *args) {
	flag.Visit(func(f *flag.Flag) {
		switch f.Name {
		case "A":
			cfg.Address = cliConfig.Address
		case "P":
			cfg.Port = cliConfig.Port
		case "u":
			cfg.Username = cliConfig.Username
		case "p":
			cfg.Password = cliConfig.Password
		case "T":
			cfg.ApiToken = cliConfig.ApiToken
		case "d":
			cfg.StorageDir = cliConfig.StorageDir
		case "w":
			cfg.WhiteDownloadDirs = cliConfig.WhiteDownloadDirs
		case "c":
			cfg.configPath = cliConfig.configPath
		}
	})
}

// setDefaults sets default values for any unset configuration fields
func setDefaults(cfg *args, cliConfig *args) {
	if cfg.Address == nil {
		cfg.Address = cliConfig.Address
	}
	if cfg.Port == nil {
		cfg.Port = cliConfig.Port
	}
	if cfg.Username == nil {
		cfg.Username = cliConfig.Username
	}
	if cfg.Password == nil {
		cfg.Password = cliConfig.Password
	}
	if cfg.ApiToken == nil {
		cfg.ApiToken = cliConfig.ApiToken
	}
	if cfg.StorageDir == nil {
		cfg.StorageDir = cliConfig.StorageDir
	}
}

// loadConfigFile loads configuration from file
func loadConfigFile(cfg *args, configPath string) {
	if !filepath.IsAbs(configPath) {
		dir, err := os.Getwd()
		if err != nil {
			return
		}
		configPath = filepath.Join(dir, configPath)
	}

	file, err := os.ReadFile(configPath)
	if err != nil {
		if os.IsNotExist(err) {
			return
		}
		return
	}

	if err = json.Unmarshal(file, cfg); err != nil {
		return
	}
}

// loadEnvVars loads configuration from environment variables with prefix GOPEED_
func loadEnvVars(cfg *args) {
	v := reflect.ValueOf(cfg).Elem()
	t := reflect.TypeOf(cfg).Elem()

	for i := 0; i < v.NumField(); i++ {
		field := v.Field(i)
		fieldType := t.Field(i)

		// Get json tag as environment variable suffix
		jsonTag := fieldType.Tag.Get("json")
		if jsonTag == "" || jsonTag == "-" {
			continue
		}

		// Remove options like omitempty
		if commaIdx := strings.Index(jsonTag, ","); commaIdx != -1 {
			jsonTag = jsonTag[:commaIdx]
		}

		// Convert to uppercase and add GOPEED_ prefix
		envKey := "GOPEED_" + strings.ToUpper(jsonTag)
		envValue := os.Getenv(envKey)

		if envValue == "" {
			continue
		}

		// Set value based on field type
		if field.Kind() == reflect.Ptr {
			if field.IsNil() {
				// Create new pointer instance
				newVal := reflect.New(field.Type().Elem())
				field.Set(newVal)
			}

			switch field.Type().Elem().Kind() {
			case reflect.String:
				field.Elem().SetString(envValue)
			case reflect.Int:
				if intVal, err := strconv.Atoi(envValue); err == nil {
					field.Elem().SetInt(int64(intVal))
				}
			default:
				// For complex types like DownloadConfig, try JSON unmarshaling
				if field.Type().Elem() == reflect.TypeOf(base.DownloaderStoreConfig{}) {
					var config base.DownloaderStoreConfig
					if err := json.Unmarshal([]byte(envValue), &config); err == nil {
						field.Set(reflect.ValueOf(&config))
					}
				}
			}
		} else if field.Kind() == reflect.Slice {
			// Handle non-pointer slice types (like []string for WhiteDownloadDirs)
			if field.Type().Elem().Kind() == reflect.String {
				dirs := strings.Split(envValue, ",")
				for i := range dirs {
					dirs[i] = strings.TrimSpace(dirs[i])
				}
				field.Set(reflect.ValueOf(dirs))
			}
		}
	}
}


================================================
FILE: cmd/web/flags_test.go
================================================
package main

import (
	"fmt"
	"os"
	"path/filepath"
	"reflect"
	"testing"

	"github.com/GopeedLab/gopeed/pkg/base"
)

func TestSetDefaults(t *testing.T) {
	// Create mock CLI configuration with command line default values
	cliDefaults := &args{
		Address:    stringPtr("127.0.0.1"),
		Port:       intPtr(9999),
		Username:   stringPtr("gopeed"),
		Password:   stringPtr(""),
		ApiToken:   stringPtr(""),
		StorageDir: stringPtr(""),
	}

	tests := []struct {
		name      string
		input     *args
		cliConfig *args
		expected  *args
	}{
		{
			name:      "empty config should get CLI defaults",
			input:     &args{},
			cliConfig: cliDefaults,
			expected: &args{
				Address:    stringPtr("127.0.0.1"),
				Port:       intPtr(9999),
				Username:   stringPtr("gopeed"),
				Password:   stringPtr(""),
				ApiToken:   stringPtr(""),
				StorageDir: stringPtr(""),
			},
		},
		{
			name: "partial config should only fill missing fields with CLI defaults",
			input: &args{
				Address: stringPtr("192.168.1.1"),
				Port:    intPtr(8080),
			},
			cliConfig: cliDefaults,
			expected: &args{
				Address:    stringPtr("192.168.1.1"),
				Port:       intPtr(8080),
				Username:   stringPtr("gopeed"),
				Password:   stringPtr(""),
				ApiToken:   stringPtr(""),
				StorageDir: stringPtr(""),
			},
		},
		{
			name: "full config should remain unchanged",
			input: &args{
				Address:    stringPtr("192.168.1.1"),
				Port:       intPtr(8080),
				Username:   stringPtr("admin"),
				Password:   stringPtr("secret"),
				ApiToken:   stringPtr("token123"),
				StorageDir: stringPtr("/custom/storage"),
			},
			cliConfig: cliDefaults,
			expected: &args{
				Address:    stringPtr("192.168.1.1"),
				Port:       intPtr(8080),
				Username:   stringPtr("admin"),
				Password:   stringPtr("secret"),
				ApiToken:   stringPtr("token123"),
				StorageDir: stringPtr("/custom/storage"),
			},
		},
		{
			name: "custom CLI defaults should be used",
			input: &args{
				Address: stringPtr("10.0.0.1"),
			},
			cliConfig: &args{
				Address:    stringPtr("0.0.0.0"),
				Port:       intPtr(8888),
				Username:   stringPtr("customuser"),
				Password:   stringPtr("defaultpass"),
				ApiToken:   stringPtr("defaulttoken"),
				StorageDir: stringPtr("/default/storage"),
			},
			expected: &args{
				Address:    stringPtr("10.0.0.1"),
				Port:       intPtr(8888),
				Username:   stringPtr("customuser"),
				Password:   stringPtr("defaultpass"),
				ApiToken:   stringPtr("defaulttoken"),
				StorageDir: stringPtr("/default/storage"),
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			setDefaults(tt.input, tt.cliConfig)
			if !reflect.DeepEqual(tt.input, tt.expected) {
				t.Errorf("setDefaults() = %+v, want %+v", tt.input, tt.expected)
			}
		})
	}
}

func TestOverrideWithCliArgs(t *testing.T) {
	// Note: Since overrideWithCliArgs uses flag.Visit, we need to test its behavior
	// in a controlled environment. This test creates a comprehensive suite to verify
	// all flag handling branches.

	// Test the function behavior without any flags set
	t.Run("no flags set", func(t *testing.T) {
		config := &args{
			Address:           stringPtr("original.address"),
			Port:              intPtr(8080),
			Username:          stringPtr("original_user"),
			Password:          stringPtr("original_pass"),
			ApiToken:          stringPtr("original_token"),
			StorageDir:        stringPtr("/original/storage"),
			WhiteDownloadDirs: []string{"/original/dir1", "/original/dir2"},
		}

		cliConfig := &args{
			Address:           stringPtr("cli.address"),
			Port:              intPtr(9090),
			Username:          stringPtr("cli_user"),
			Password:          stringPtr("cli_pass"),
			ApiToken:          stringPtr("cli_token"),
			StorageDir:        stringPtr("/cli/storage"),
			WhiteDownloadDirs: []string{"/cli/dir1", "/cli/dir2"},
			configPath:        stringPtr("/cli/config.json"),
		}

		// Save original config for comparison
		original := &args{
			Address:           stringPtr("original.address"),
			Port:              intPtr(8080),
			Username:          stringPtr("original_user"),
			Password:          stringPtr("original_pass"),
			ApiToken:          stringPtr("original_token"),
			StorageDir:        stringPtr("/original/storage"),
			WhiteDownloadDirs: []string{"/original/dir1", "/original/dir2"},
		}

		overrideWithCliArgs(config, cliConfig)

		// Since no flags are actually set via command line, config should remain unchanged
		if !reflect.DeepEqual(config, original) {
			t.Logf("Configuration changed when no flags were set:")
			t.Logf("  Got: %+v", config)
			t.Logf("  Want: %+v", original)
			// This is expected behavior in test environment
		}
	})

	// Test individual flag override behavior by mocking flag.Visit
	// Since we can't easily mock flag.Visit, we document the expected behavior
	t.Run("flag override documentation", func(t *testing.T) {
		// Document the expected behavior for each flag:
		flagBehaviors := map[string]string{
			"A": "Should override cfg.Address with cliConfig.Address",
			"P": "Should override cfg.Port with cliConfig.Port",
			"u": "Should override cfg.Username with cliConfig.Username",
			"p": "Should override cfg.Password with cliConfig.Password",
			"T": "Should override cfg.ApiToken with cliConfig.ApiToken",
			"d": "Should override cfg.StorageDir with cliConfig.StorageDir",
			"w": "Should override cfg.WhiteDownloadDirs with cliConfig.WhiteDownloadDirs",
			"c": "Should override cfg.configPath with cliConfig.configPath",
		}

		t.Log("Expected flag override behaviors:")
		for flag, behavior := range flagBehaviors {
			t.Logf("  Flag '%s': %s", flag, behavior)
		}
	})

	// Test with mock implementation to verify switch case coverage
	t.Run("switch case coverage verification", func(t *testing.T) {
		// Create a mock function that simulates flag.Visit behavior
		mockFlagVisit := func(config *args, cliConfig *args, flagName string) {
			// Simulate the switch statement in overrideWithCliArgs
			switch flagName {
			case "A":
				config.Address = cliConfig.Address
			case "P":
				config.Port = cliConfig.Port
			case "u":
				config.Username = cliConfig.Username
			case "p":
				config.Password = cliConfig.Password
			case "T":
				config.ApiToken = cliConfig.ApiToken
			case "d":
				config.StorageDir = cliConfig.StorageDir
			case "w":
				config.WhiteDownloadDirs = cliConfig.WhiteDownloadDirs
			case "c":
				config.configPath = cliConfig.configPath
			default:
				t.Errorf("Unknown flag: %s", flagName)
			}
		}

		// Test each flag individually
		testCases := []struct {
			flagName       string
			setupConfig    func() *args
			setupCliConfig func() *args
			verify         func(*testing.T, *args)
		}{
			{
				flagName: "A",
				setupConfig: func() *args {
					return &args{Address: stringPtr("original.address")}
				},
				setupCliConfig: func() *args {
					return &args{Address: stringPtr("cli.address")}
				},
				verify: func(t *testing.T, cfg *args) {
					if cfg.Address == nil || *cfg.Address != "cli.address" {
						t.Errorf("Address flag override failed: got %v, want cli.address", cfg.Address)
					}
				},
			},
			{
				flagName: "P",
				setupConfig: func() *args {
					return &args{Port: intPtr(8080)}
				},
				setupCliConfig: func() *args {
					return &args{Port: intPtr(9090)}
				},
				verify: func(t *testing.T, cfg *args) {
					if cfg.Port == nil || *cfg.Port != 9090 {
						t.Errorf("Port flag override failed: got %v, want 9090", cfg.Port)
					}
				},
			},
			{
				flagName: "u",
				setupConfig: func() *args {
					return &args{Username: stringPtr("original_user")}
				},
				setupCliConfig: func() *args {
					return &args{Username: stringPtr("cli_user")}
				},
				verify: func(t *testing.T, cfg *args) {
					if cfg.Username == nil || *cfg.Username != "cli_user" {
						t.Errorf("Username flag override failed: got %v, want cli_user", cfg.Username)
					}
				},
			},
			{
				flagName: "p",
				setupConfig: func() *args {
					return &args{Password: stringPtr("original_pass")}
				},
				setupCliConfig: func() *args {
					return &args{Password: stringPtr("cli_pass")}
				},
				verify: func(t *testing.T, cfg *args) {
					if cfg.Password == nil || *cfg.Password != "cli_pass" {
						t.Errorf("Password flag override failed: got %v, want cli_pass", cfg.Password)
					}
				},
			},
			{
				flagName: "T",
				setupConfig: func() *args {
					return &args{ApiToken: stringPtr("original_token")}
				},
				setupCliConfig: func() *args {
					return &args{ApiToken: stringPtr("cli_token")}
				},
				verify: func(t *testing.T, cfg *args) {
					if cfg.ApiToken == nil || *cfg.ApiToken != "cli_token" {
						t.Errorf("ApiToken flag override failed: got %v, want cli_token", cfg.ApiToken)
					}
				},
			},
			{
				flagName: "d",
				setupConfig: func() *args {
					return &args{StorageDir: stringPtr("/original/storage")}
				},
				setupCliConfig: func() *args {
					return &args{StorageDir: stringPtr("/cli/storage")}
				},
				verify: func(t *testing.T, cfg *args) {
					if cfg.StorageDir == nil || *cfg.StorageDir != "/cli/storage" {
						t.Errorf("StorageDir flag override failed: got %v, want /cli/storage", cfg.StorageDir)
					}
				},
			},
			{
				flagName: "w",
				setupConfig: func() *args {
					return &args{WhiteDownloadDirs: []string{"/original/dir1", "/original/dir2"}}
				},
				setupCliConfig: func() *args {
					return &args{WhiteDownloadDirs: []string{"/cli/dir1", "/cli/dir2", "/cli/dir3"}}
				},
				verify: func(t *testing.T, cfg *args) {
					expected := []string{"/cli/dir1", "/cli/dir2", "/cli/dir3"}
					if !reflect.DeepEqual(cfg.WhiteDownloadDirs, expected) {
						t.Errorf("WhiteDownloadDirs flag override failed: got %v, want %v", cfg.WhiteDownloadDirs, expected)
					}
				},
			},
			{
				flagName: "c",
				setupConfig: func() *args {
					return &args{configPath: stringPtr("/original/config.json")}
				},
				setupCliConfig: func() *args {
					return &args{configPath: stringPtr("/cli/config.json")}
				},
				verify: func(t *testing.T, cfg *args) {
					if cfg.configPath == nil || *cfg.configPath != "/cli/config.json" {
						t.Errorf("configPath flag override failed: got %v, want /cli/config.json", cfg.configPath)
					}
				},
			},
		}

		for _, tc := range testCases {
			t.Run(fmt.Sprintf("flag_%s", tc.flagName), func(t *testing.T) {
				config := tc.setupConfig()
				cliConfig := tc.setupCliConfig()

				// Use mock function to simulate flag.Visit behavior
				mockFlagVisit(config, cliConfig, tc.flagName)

				// Verify the result
				tc.verify(t, config)
			})
		}
	})

	// Test edge cases
	t.Run("edge cases", func(t *testing.T) {
		t.Run("nil pointers", func(t *testing.T) {
			config := &args{}
			cliConfig := &args{
				Address:    stringPtr("new.address"),
				Port:       intPtr(9999),
				Username:   stringPtr("newuser"),
				Password:   stringPtr("newpass"),
				ApiToken:   stringPtr("newtoken"),
				StorageDir: stringPtr("/new/storage"),
			}

			// This should not panic when config has nil pointers
			overrideWithCliArgs(config, cliConfig)

			// Since no flags are set in test environment, config should remain with nil values
			if config.Address != nil {
				t.Log("Note: Address was set despite no flags being visited")
			}
		})

		t.Run("empty slice handling", func(t *testing.T) {
			config := &args{WhiteDownloadDirs: []string{"/existing/dir"}}
			cliConfig := &args{WhiteDownloadDirs: []string{}}

			overrideWithCliArgs(config, cliConfig)

			// In test environment without actual flags, original should be preserved
			if len(config.WhiteDownloadDirs) != 1 || config.WhiteDownloadDirs[0] != "/existing/dir" {
				t.Log("Note: WhiteDownloadDirs was modified despite no flags being visited")
			}
		})
	})
}

func TestLoadConfigFile(t *testing.T) {
	// Create temporary directory for test files
	tempDir, err := os.MkdirTemp("", "flags_test")
	if err != nil {
		t.Fatal(err)
	}
	defer os.RemoveAll(tempDir)

	tests := []struct {
		name       string
		configData string
		fileName   string
		expected   *args
	}{
		{
			name: "valid config file",
			configData: `{
				"address": "192.168.1.100",
				"port": 8080,
				"username": "testuser",
				"password": "testpass",
				"apiToken": "testtoken",
				"storageDir": "/test/storage",
				"downloadConfig": {
					"downloadDir": "/test/downloads",
					"maxRunning": 10
				}
			}`,
			fileName: "valid_config.json",
			expected: &args{
				Address:    stringPtr("192.168.1.100"),
				Port:       intPtr(8080),
				Username:   stringPtr("testuser"),
				Password:   stringPtr("testpass"),
				ApiToken:   stringPtr("testtoken"),
				StorageDir: stringPtr("/test/storage"),
				DownloadConfig: &base.DownloaderStoreConfig{
					DownloadDir: "/test/downloads",
					MaxRunning:  10,
				},
			},
		},
		{
			name: "partial config file",
			configData: `{
				"address": "10.0.0.1",
				"port": 3000
			}`,
			fileName: "partial_config.json",
			expected: &args{
				Address: stringPtr("10.0.0.1"),
				Port:    intPtr(3000),
			},
		},
		{
			name:       "invalid json should not panic",
			configData: `{invalid json}`,
			fileName:   "invalid_config.json",
			expected:   &args{},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			// Create test config file
			configPath := filepath.Join(tempDir, tt.fileName)
			err := os.WriteFile(configPath, []byte(tt.configData), 0644)
			if err != nil {
				t.Fatal(err)
			}

			cfg := &args{}
			loadConfigFile(cfg, configPath)

			if !reflect.DeepEqual(cfg, tt.expected) {
				t.Errorf("loadConfigFile() = %+v, want %+v", cfg, tt.expected)
			}
		})
	}

	// Test non-existent file
	t.Run("non-existent file", func(t *testing.T) {
		cfg := &args{}
		loadConfigFile(cfg, "/non/existent/file.json")
		expected := &args{}
		if !reflect.DeepEqual(cfg, expected) {
			t.Errorf("loadConfigFile() with non-existent file = %+v, want %+v", cfg, expected)
		}
	})
}

func TestLoadEnvVars(t *testing.T) {
	// Save original environment
	originalEnv := make(map[string]string)
	envKeys := []string{
		"GOPEED_ADDRESS", "GOPEED_PORT", "GOPEED_USERNAME",
		"GOPEED_PASSWORD", "GOPEED_APITOKEN", "GOPEED_STORAGEDIR",
		"GOPEED_DOWNLOADCONFIG", "GOPEED_WHITEDOWNLOADDIRS",
	}
	for _, key := range envKeys {
		originalEnv[key] = os.Getenv(key)
	}

	// Clean up function
	cleanup := func() {
		for _, key := range envKeys {
			if val, exists := originalEnv[key]; exists {
				os.Setenv(key, val)
			} else {
				os.Unsetenv(key)
			}
		}
	}
	defer cleanup()

	tests := []struct {
		name     string
		envVars  map[string]string
		expected *args
	}{
		{
			name: "all environment variables set",
			envVars: map[string]string{
				"GOPEED_ADDRESS":    "env.example.com",
				"GOPEED_PORT":       "7777",
				"GOPEED_USERNAME":   "envuser",
				"GOPEED_PASSWORD":   "envpass",
				"GOPEED_APITOKEN":   "envtoken",
				"GOPEED_STORAGEDIR": "/env/storage",
			},
			expected: &args{
				Address:    stringPtr("env.example.com"),
				Port:       intPtr(7777),
				Username:   stringPtr("envuser"),
				Password:   stringPtr("envpass"),
				ApiToken:   stringPtr("envtoken"),
				StorageDir: stringPtr("/env/storage"),
			},
		},
		{
			name: "partial environment variables",
			envVars: map[string]string{
				"GOPEED_ADDRESS": "partial.example.com",
				"GOPEED_PORT":    "5555",
			},
			expected: &args{
				Address: stringPtr("partial.example.com"),
				Port:    intPtr(5555),
			},
		},
		{
			name: "downloadConfig from environment",
			envVars: map[string]string{
				"GOPEED_DOWNLOADCONFIG": `{"downloadDir": "/env/downloads", "maxRunning": 15}`,
			},
			expected: &args{
				DownloadConfig: &base.DownloaderStoreConfig{
					DownloadDir: "/env/downloads",
					MaxRunning:  15,
				},
			},
		},
		{
			name: "invalid port should be ignored",
			envVars: map[string]string{
				"GOPEED_PORT": "invalid_port",
			},
			expected: &args{
				Port: intPtr(0), // Invalid port creates pointer with 0 value
			},
		},
		{
			name: "invalid json for downloadConfig should be ignored",
			envVars: map[string]string{
				"GOPEED_DOWNLOADCONFIG": `{invalid json}`,
			},
		
Download .txt
gitextract_l2o017xa/

├── .dockerignore
├── .github/
│   ├── CODEOWNERS
│   ├── FUNDING.yml
│   ├── ISSUE_TEMPLATE.md
│   ├── release-drafter.yml
│   └── workflows/
│       ├── build.yml
│       ├── release.yml
│       ├── scripts/
│       │   └── flutter_local_font.dart
│       ├── 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
├── _examples/
│   └── basic/
│       └── main.go
├── bind/
│   ├── desktop/
│   │   └── main.go
│   └── mobile/
│       └── main.go
├── cmd/
│   ├── api/
│   │   └── main.go
│   ├── banner.txt
│   ├── gopeed/
│   │   ├── flags.go
│   │   └── main.go
│   ├── host/
│   │   ├── dail_other.go
│   │   ├── dail_windows.go
│   │   └── main.go
│   ├── server.go
│   ├── updater/
│   │   ├── main.go
│   │   ├── updater_darwin.go
│   │   ├── updater_linux.go
│   │   └── updater_windows.go
│   └── web/
│       ├── flags.go
│       ├── flags_test.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
│   │   │   └── testdata/
│   │   │       ├── test.torrent
│   │   │       ├── test.unclean.torrent
│   │   │       └── ubuntu-22.04-live-server-amd64.iso.torrent
│   │   ├── ed2k/
│   │   │   ├── config.go
│   │   │   ├── fetcher.go
│   │   │   └── fetcher_test.go
│   │   └── http/
│   │       ├── config.go
│   │       ├── fetcher.go
│   │       ├── fetcher_manager.go
│   │       ├── fetcher_test.go
│   │       ├── filename_parse_test.go
│   │       ├── helper.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
│   │   │   │       └── tls_fingerprint.go
│   │   │   ├── polyfill/
│   │   │   │   ├── out/
│   │   │   │   │   └── index.js
│   │   │   │   ├── 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
│   │   ├── extract.go
│   │   ├── extract_7z.go
│   │   ├── extract_queue.go
│   │   ├── extract_queue_test.go
│   │   ├── extract_rar.go
│   │   ├── extract_test.go
│   │   ├── extract_zip.go
│   │   ├── model.go
│   │   ├── model_test.go
│   │   ├── script.go
│   │   ├── script_test.go
│   │   ├── script_unix_test.go
│   │   ├── script_windows_test.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
│   │   │   └── scripts/
│   │   │       ├── env_dump.bat
│   │   │       ├── env_dump.sh
│   │   │       ├── move.bat
│   │   │       ├── move.sh
│   │   │       ├── write_output1.bat
│   │   │       ├── write_output1.sh
│   │   │       ├── write_output2.bat
│   │   │       └── write_output2.sh
│   │   ├── webhook.go
│   │   └── webhook_test.go
│   ├── protocol/
│   │   ├── bt/
│   │   │   └── model.go
│   │   ├── ed2k/
│   │   │   └── model.go
│   │   └── http/
│   │       └── model.go
│   ├── rest/
│   │   ├── api.go
│   │   ├── config.go
│   │   ├── gizp_middleware.go
│   │   ├── model/
│   │   │   ├── extension.go
│   │   │   ├── result.go
│   │   │   ├── server.go
│   │   │   ├── task.go
│   │   │   └── webhook.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/
        │   │       │       │   └── launch_background.xml
        │   │       │       ├── drawable-v21/
        │   │       │       │   └── launch_background.xml
        │   │       │       ├── values/
        │   │       │       │   └── styles.xml
        │   │       │       └── values-night/
        │   │       │           └── styles.xml
        │   │       └── profile/
        │   │           └── AndroidManifest.xml
        │   ├── build.gradle
        │   ├── gradle/
        │   │   └── wrapper/
        │   │       └── gradle-wrapper.properties
        │   ├── gradle.properties
        │   └── settings.gradle
        ├── assets/
        │   └── exec/
        │       └── .gitkeep
        ├── build.yaml
        ├── distribute_options.yaml
        ├── include/
        │   └── libgopeed.h
        ├── ios/
        │   ├── .gitignore
        │   ├── Flutter/
        │   │   ├── AppFrameworkInfo.plist
        │   │   ├── Debug.xcconfig
        │   │   └── Release.xcconfig
        │   ├── Podfile
        │   ├── Runner/
        │   │   ├── AppDelegate.swift
        │   │   ├── Assets.xcassets/
        │   │   │   ├── AppIcon.appiconset/
        │   │   │   │   └── Contents.json
        │   │   │   └── LaunchImage.imageset/
        │   │   │       ├── Contents.json
        │   │   │       └── README.md
        │   │   ├── Base.lproj/
        │   │   │   ├── LaunchScreen.storyboard
        │   │   │   └── Main.storyboard
        │   │   ├── Info.plist
        │   │   ├── Runner-Bridging-Header.h
        │   │   └── Runner.entitlements
        │   ├── 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
        │   └── ShareExtension/
        │       ├── Base.lproj/
        │       │   └── MainInterface.storyboard
        │       ├── Info.plist
        │       ├── ShareExtension.entitlements
        │       └── ShareViewController.swift
        ├── lib/
        │   ├── api/
        │   │   ├── api.dart
        │   │   ├── gopeed_site_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
        │   │       ├── login.dart
        │   │       ├── login.g.dart
        │   │       ├── meta.dart
        │   │       ├── meta.g.dart
        │   │       ├── options.dart
        │   │       ├── options.g.dart
        │   │       ├── request.dart
        │   │       ├── request.g.dart
        │   │       ├── resolve_result.dart
        │   │       ├── resolve_result.g.dart
        │   │       ├── resolve_task.dart
        │   │       ├── resolve_task.g.dart
        │   │       ├── resource.dart
        │   │       ├── resource.g.dart
        │   │       ├── result.dart
        │   │       ├── result.g.dart
        │   │       ├── store_extension.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
        │   │   │   │   └── views/
        │   │   │   │       └── create_view.dart
        │   │   │   ├── extension/
        │   │   │   │   ├── bindings/
        │   │   │   │   │   └── extension_binding.dart
        │   │   │   │   ├── controllers/
        │   │   │   │   │   └── extension_controller.dart
        │   │   │   │   └── views/
        │   │   │   │       ├── extension_card.dart
        │   │   │   │       ├── extension_detail_view.dart
        │   │   │   │       └── extension_view.dart
        │   │   │   ├── history/
        │   │   │   │   └── views/
        │   │   │   │       └── history_view.dart
        │   │   │   ├── home/
        │   │   │   │   ├── bindings/
        │   │   │   │   │   └── home_binding.dart
        │   │   │   │   ├── controllers/
        │   │   │   │   │   └── home_controller.dart
        │   │   │   │   └── views/
        │   │   │   │       └── home_view.dart
        │   │   │   ├── login/
        │   │   │   │   ├── bindings/
        │   │   │   │   │   └── login_binding.dart
        │   │   │   │   ├── controllers/
        │   │   │   │   │   └── login_controller.dart
        │   │   │   │   └── views/
        │   │   │   │       └── login_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
        │   │   ├── rpc/
        │   │   │   └── rpc.dart
        │   │   ├── services/
        │   │   │   └── notification_service.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/
        │   │   │   ├── ca_es.dart
        │   │   │   ├── de_de.dart
        │   │   │   ├── 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
        │   │   │   ├── pt_br.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/
        │       ├── analytics.dart
        │       ├── 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
        │   ├── Runner/
        │   │   ├── AppDelegate.swift
        │   │   ├── Assets.xcassets/
        │   │   │   └── AppIcon.appiconset/
        │   │   │       └── Contents.json
        │   │   ├── Base.lproj/
        │   │   │   └── MainMenu.xib
        │   │   ├── Configs/
        │   │   │   ├── AppInfo.xcconfig
        │   │   │   ├── Debug.xcconfig
        │   │   │   ├── Release.xcconfig
        │   │   │   └── Warnings.xcconfig
        │   │   ├── DebugProfile.entitlements
        │   │   ├── Info.plist
        │   │   ├── MainFlutterWindow.swift
        │   │   └── Release.entitlements
        │   ├── Runner.xcodeproj/
        │   │   ├── project.pbxproj
        │   │   ├── project.xcworkspace/
        │   │   │   └── xcshareddata/
        │   │   │       └── IDEWorkspaceChecks.plist
        │   │   └── xcshareddata/
        │   │       └── xcschemes/
        │   │           └── Runner.xcscheme
        │   └── Runner.xcworkspace/
        │       ├── contents.xcworkspacedata
        │       └── xcshareddata/
        │           └── IDEWorkspaceChecks.plist
        ├── pubspec.yaml
        ├── test/
        │   └── widget_test.dart
        ├── web/
        │   ├── 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
                ├── runner.exe.manifest
                ├── utils.cpp
                ├── utils.h
                ├── win32_window.cpp
                └── win32_window.h
Download .txt
SYMBOL INDEX (2080 symbols across 245 files)

FILE: .github/workflows/scripts/flutter_local_font.dart
  function _getFontFamiliesForLocale (line 25) | Set<String> _getFontFamiliesForLocale(String locale)
  function _parseSupportedLocales (line 100) | Set<String> _parseSupportedLocales(File messageFile)
  function _getRequiredFontFamilies (line 129) | Set<String> _getRequiredFontFamilies(Set<String> locales)
  function _fontMatchesRequiredFamilies (line 141) | bool _fontMatchesRequiredFamilies(
  function main (line 154) | Future<void> main(List<String> args)
  function _downloadIfMissing (line 316) | Future<void> _downloadIfMissing(HttpClient client, Uri url, File dest)
  function _findFlutterProjectRoot (line 387) | Directory _findFlutterProjectRoot(Directory start)
  function _fail (line 400) | Never _fail(String msg)
  function _join (line 404) | String _join(String a, [String? b, String? c, String? d])

FILE: _examples/basic/main.go
  function main (line 11) | func main() {

FILE: bind/desktop/main.go
  function main (line 10) | func main() {}
  function Start (line 13) | func Start(cfg *C.char) (int, *C.char) {
  function Stop (line 27) | func Stop() {

FILE: bind/mobile/main.go
  function Start (line 11) | func Start(cfg string) (int, error) {
  function Stop (line 20) | func Stop() {

FILE: cmd/api/main.go
  function main (line 9) | func main() {

FILE: cmd/gopeed/flags.go
  type args (line 9) | type args struct
  function parse (line 15) | func parse() *args {
  function gPrint (line 35) | func gPrint(msg string) {
  function gPrintln (line 39) | func gPrintln(msg string) {

FILE: cmd/gopeed/main.go
  constant progressWidth (line 15) | progressWidth = 20
  function main (line 17) | func main() {
  function printProgress (line 67) | func printProgress(task *download.Task, title string) {

FILE: cmd/host/dail_other.go
  function Dial (line 12) | func Dial() (net.Conn, error) {

FILE: cmd/host/dail_windows.go
  function Dial (line 9) | func Dial() (net.Conn, error) {

FILE: cmd/host/main.go
  type Message (line 19) | type Message struct
  type Response (line 25) | type Response struct
  function check (line 31) | func check() (data bool, err error) {
  function wakeup (line 40) | func wakeup(hidden bool) error {
  function postToFlutter (line 64) | func postToFlutter(path string, body []byte, headers map[string]string, ...
  function main (line 146) | func main() {
  function sendResponse (line 188) | func sendResponse(code int, data interface{}, message string) {
  function sendError (line 208) | func sendError(msg string) {

FILE: cmd/server.go
  function Start (line 19) | func Start(cfg *model.StartConfig) {
  function watchExit (line 63) | func watchExit() {

FILE: cmd/updater/main.go
  function main (line 17) | func main() {
  function update (line 64) | func update(pid int, updateChannel, packagePath, exeDir string) (restart...
  function waitForProcessExit (line 87) | func waitForProcessExit(pid int) error {
  function killProcess (line 108) | func killProcess(pid int) error {

FILE: cmd/updater/updater_darwin.go
  function install (line 12) | func install(killSignalChan chan<- any, updateChannel, packagePath, dest...
  function installByDmg (line 17) | func installByDmg(killSignalChan chan<- any, packagePath, destDir string...
  function getParentDir (line 67) | func getParentDir(path string) string {

FILE: cmd/updater/updater_linux.go
  function install (line 10) | func install(killSignalChan chan<- any, updateChannel, packagePath, dest...
  function executeInTerminal (line 25) | func executeInTerminal(command string) error {
  function installByDeb (line 57) | func installByDeb(packagePath string) error {
  function installByFlathub (line 63) | func installByFlathub() error {
  function installBySnap (line 69) | func installBySnap() error {

FILE: cmd/updater/updater_windows.go
  function install (line 15) | func install(killSignalChan chan<- any, updateChannel, packagePath, dest...
  function installByInstaller (line 25) | func installByInstaller(killSignalChan chan<- any, packagePath, destDir ...
  function installByPortable (line 94) | func installByPortable(killSignalChan chan<- any, packagePath, destDir s...

FILE: cmd/web/flags.go
  type args (line 15) | type args struct
  function parse (line 29) | func parse() *args {
  function loadCliArgs (line 43) | func loadCliArgs() *args {
  function overrideWithCliArgs (line 68) | func overrideWithCliArgs(cfg *args, cliConfig *args) {
  function setDefaults (line 92) | func setDefaults(cfg *args, cliConfig *args) {
  function loadConfigFile (line 114) | func loadConfigFile(cfg *args, configPath string) {
  function loadEnvVars (line 137) | func loadEnvVars(cfg *args) {

FILE: cmd/web/flags_test.go
  function TestSetDefaults (line 13) | func TestSetDefaults(t *testing.T) {
  function TestOverrideWithCliArgs (line 113) | func TestOverrideWithCliArgs(t *testing.T) {
  function TestLoadConfigFile (line 383) | func TestLoadConfigFile(t *testing.T) {
  function TestLoadEnvVars (line 474) | func TestLoadEnvVars(t *testing.T) {
  function TestConfigPriority (line 587) | func TestConfigPriority(t *testing.T) {
  function TestCompleteConfigurationFlow (line 668) | func TestCompleteConfigurationFlow(t *testing.T) {
  function stringPtr (line 778) | func stringPtr(s string) *string {
  function intPtr (line 782) | func intPtr(i int) *int {
  function getStringValue (line 787) | func getStringValue(ptr *string) string {
  function getIntValue (line 794) | func getIntValue(ptr *int) string {
  function TestWhiteDownloadDirs (line 801) | func TestWhiteDownloadDirs(t *testing.T) {
  function TestParse (line 967) | func TestParse(t *testing.T) {

FILE: cmd/web/main.go
  function main (line 20) | func main() {
  function isNotBlank (line 62) | func isNotBlank(str *string) bool {

FILE: internal/controller/controller.go
  type Controller (line 11) | type Controller struct
  type FileController (line 18) | type FileController interface
  type DefaultFileController (line 22) | type DefaultFileController struct
    method Touch (line 35) | func (c *DefaultFileController) Touch(name string, size int64) (file *...
  function NewController (line 25) | func NewController() *Controller {

FILE: internal/fetcher/fetcher.go
  type Fetcher (line 13) | type Fetcher interface
  type Uploader (line 34) | type Uploader interface
  type FetcherMeta (line 41) | type FetcherMeta struct
    method FolderPath (line 48) | func (m *FetcherMeta) FolderPath() string {
    method SingleFilepath (line 58) | func (m *FetcherMeta) SingleFilepath() string {
    method RootDirPath (line 69) | func (m *FetcherMeta) RootDirPath() string {
  type FilterType (line 77) | type FilterType
  constant FilterTypeUrl (line 81) | FilterTypeUrl FilterType = iota
  constant FilterTypeFile (line 83) | FilterTypeFile
  constant FilterTypeBase64 (line 85) | FilterTypeBase64
  type SchemeFilter (line 88) | type SchemeFilter struct
    method Match (line 93) | func (s *SchemeFilter) Match(uri string) bool {
  type FetcherManager (line 108) | type FetcherManager interface
  type StatefulFetcherManager (line 132) | type StatefulFetcherManager interface
  type ProtocolStateStore (line 139) | type ProtocolStateStore interface
  type DefaultFetcher (line 145) | type DefaultFetcher struct
    method Setup (line 151) | func (f *DefaultFetcher) Setup(ctl *controller.Controller) (err error) {
    method Wait (line 157) | func (f *DefaultFetcher) Wait() (err error) {
  type Progress (line 162) | type Progress
    method TotalDownloaded (line 165) | func (p Progress) TotalDownloaded() int64 {

FILE: internal/fetcher/fetcher_test.go
  function TestSchemeFilter_Match (line 5) | func TestSchemeFilter_Match(t *testing.T) {

FILE: internal/logger/logger.go
  type Logger (line 11) | type Logger struct
    method CLose (line 16) | func (l *Logger) CLose() {
  function NewLogger (line 21) | func NewLogger(logFile bool, logPath string) *Logger {

FILE: internal/logger/logger_test.go
  function TestNewLogger (line 8) | func TestNewLogger(t *testing.T) {
  function TestNewLoggerFile (line 13) | func TestNewLoggerFile(t *testing.T) {

FILE: internal/protocol/bt/config.go
  type config (line 3) | type config struct

FILE: internal/protocol/bt/dns_cache_resolver.go
  type DnsCacheResolver (line 12) | type DnsCacheResolver struct
    method DialContext (line 18) | func (r *DnsCacheResolver) DialContext(ctx context.Context, network, a...
    method Run (line 38) | func (r *DnsCacheResolver) Run(ctx context.Context) {

FILE: internal/protocol/bt/fetcher.go
  type Fetcher (line 34) | type Fetcher struct
    method Setup (line 49) | func (f *Fetcher) Setup(ctl *controller.Controller) {
    method initClient (line 63) | func (f *Fetcher) initClient() (err error) {
    method Resolve (line 94) | func (f *Fetcher) Resolve(req *base.Request, opts *base.Options) error {
    method Start (line 107) | func (f *Fetcher) Start() (err error) {
    method Pause (line 137) | func (f *Fetcher) Pause() (err error) {
    method Close (line 142) | func (f *Fetcher) Close() (err error) {
    method safeDrop (line 152) | func (f *Fetcher) safeDrop() {
    method Meta (line 161) | func (f *Fetcher) Meta() *fetcher.FetcherMeta {
    method Stats (line 165) | func (f *Fetcher) Stats() any {
    method Progress (line 182) | func (f *Fetcher) Progress() fetcher.Progress {
    method Wait (line 194) | func (f *Fetcher) Wait() (err error) {
    method isDone (line 222) | func (f *Fetcher) isDone() bool {
    method Patch (line 237) | func (f *Fetcher) Patch(req *base.Request, opts *base.Options) error {
    method updateRes (line 288) | func (f *Fetcher) updateRes() {
    method Upload (line 312) | func (f *Fetcher) Upload() (err error) {
    method doUpload (line 316) | func (f *Fetcher) doUpload(fromUpload bool) {
    method torrentStats (line 374) | func (f *Fetcher) torrentStats() torrent.TorrentStats {
    method UploadedBytes (line 384) | func (f *Fetcher) UploadedBytes() int64 {
    method WaitUpload (line 388) | func (f *Fetcher) WaitUpload() (err error) {
    method addTorrent (line 393) | func (f *Fetcher) addTorrent(req *base.Request, fromUpload bool) (err ...
    method seedRadio (line 492) | func (f *Fetcher) seedRadio() float64 {
  type fetcherData (line 506) | type fetcherData struct
  function closeClient (line 513) | func closeClient() error {
  type FetcherManager (line 532) | type FetcherManager struct
    method Name (line 535) | func (fm *FetcherManager) Name() string {
    method Filters (line 539) | func (fm *FetcherManager) Filters() []*fetcher.SchemeFilter {
    method Build (line 556) | func (fm *FetcherManager) Build() fetcher.Fetcher {
    method ParseName (line 560) | func (fm *FetcherManager) ParseName(u string) string {
    method AutoRename (line 578) | func (fm *FetcherManager) AutoRename() bool {
    method DefaultConfig (line 582) | func (fm *FetcherManager) DefaultConfig() any {
    method Store (line 592) | func (fm *FetcherManager) Store(f fetcher.Fetcher) (data any, err erro...
    method Restore (line 597) | func (fm *FetcherManager) Restore() (v any, f func(meta *fetcher.Fetch...
    method Close (line 606) | func (fm *FetcherManager) Close() error {
  function parseBep20 (line 611) | func parseBep20() string {

FILE: internal/protocol/bt/fetcher_test.go
  function TestFetcher_Resolve_Torrent (line 20) | func TestFetcher_Resolve_Torrent(t *testing.T) {
  function TestFetcher_Resolve_DataUri_Torrent (line 24) | func TestFetcher_Resolve_DataUri_Torrent(t *testing.T) {
  function TestFetcher_Config (line 55) | func TestFetcher_Config(t *testing.T) {
  function TestFetcher_ResolveWithProxy (line 59) | func TestFetcher_ResolveWithProxy(t *testing.T) {
  function doResolve (line 74) | func doResolve(t *testing.T, fetcher fetcher.Fetcher) {
  function TestFetcherManager_ParseName (line 166) | func TestFetcherManager_ParseName(t *testing.T) {
  function buildFetcher (line 214) | func buildFetcher() fetcher.Fetcher {
  function buildConfigFetcher (line 225) | func buildConfigFetcher(proxyConfig *base.DownloaderProxyConfig) fetcher...
  function TestFetcher_Patch (line 245) | func TestFetcher_Patch(t *testing.T) {

FILE: internal/protocol/ed2k/config.go
  constant defaultServerList (line 4) | defaultServerList = "45.82.80.155:5687,176.123.5.89:4725,85.121.5.137:42...
  constant defaultServerMet (line 5) | defaultServerMet  = "ed2k://|serverlist|http://upd.emule-security.org/se...
  constant defaultNodesDat (line 6) | defaultNodesDat   = "https://upd.emule-security.org/nodes.dat"
  type config (line 9) | type config struct

FILE: internal/protocol/ed2k/fetcher.go
  type clientStateStore (line 20) | type clientStateStore struct
    method Load (line 24) | func (s *clientStateStore) Load() (*goed2k.ClientState, error) {
    method Save (line 39) | func (s *clientStateStore) Save(state *goed2k.ClientState) error {
  type Fetcher (line 49) | type Fetcher struct
    method Setup (line 61) | func (f *Fetcher) Setup(ctl *controller.Controller) {
    method Resolve (line 70) | func (f *Fetcher) Resolve(req *base.Request, opts *base.Options) error {
    method Start (line 85) | func (f *Fetcher) Start() error {
    method Patch (line 134) | func (f *Fetcher) Patch(req *base.Request, opts *base.Options) error {
    method Pause (line 177) | func (f *Fetcher) Pause() error {
    method Close (line 193) | func (f *Fetcher) Close() error {
    method Meta (line 209) | func (f *Fetcher) Meta() *fetcher.FetcherMeta {
    method Stats (line 213) | func (f *Fetcher) Stats() any {
    method Progress (line 232) | func (f *Fetcher) Progress() fetcher.Progress {
    method Wait (line 240) | func (f *Fetcher) Wait() error {
    method getClient (line 281) | func (f *Fetcher) getClient() (*goed2k.Client, error) {
    method currentHandle (line 288) | func (f *Fetcher) currentHandle() goed2k.TransferHandle {
    method hash (line 310) | func (f *Fetcher) hash() (gprotocol.Hash, error) {
  type FetcherManager (line 321) | type FetcherManager struct
    method SetStateStore (line 327) | func (fm *FetcherManager) SetStateStore(store fetcher.ProtocolStateSto...
    method Name (line 340) | func (fm *FetcherManager) Name() string {
    method Filters (line 344) | func (fm *FetcherManager) Filters() []*fetcher.SchemeFilter {
    method Build (line 353) | func (fm *FetcherManager) Build() fetcher.Fetcher {
    method ParseName (line 357) | func (fm *FetcherManager) ParseName(u string) string {
    method AutoRename (line 365) | func (fm *FetcherManager) AutoRename() bool {
    method DefaultConfig (line 369) | func (fm *FetcherManager) DefaultConfig() any {
    method Store (line 379) | func (fm *FetcherManager) Store(f fetcher.Fetcher) (any, error) {
    method Restore (line 383) | func (fm *FetcherManager) Restore() (v any, f func(meta *fetcher.Fetch...
    method Close (line 392) | func (fm *FetcherManager) Close() error {
    method getStateStoreLocked (line 443) | func (fm *FetcherManager) getStateStoreLocked() *clientStateStore {
    method currentClient (line 450) | func (fm *FetcherManager) currentClient() *goed2k.Client {
    method initClient (line 456) | func (fm *FetcherManager) initClient(cfg *config) (*goed2k.Client, err...
  function parseLink (line 403) | func parseLink(raw string) (goed2k.EMuleLink, error) {
  function buildResource (line 414) | func buildResource(link goed2k.EMuleLink) *base.Resource {
  function splitCommaList (line 428) | func splitCommaList(value string) []string {
  function bootstrapClient (line 489) | func bootstrapClient(client *goed2k.Client, cfg *config) {

FILE: internal/protocol/ed2k/fetcher_test.go
  constant testLink (line 10) | testLink = "ed2k://|file|cn_windows_10_multi-edition_vl_version_1709_upd...
  function TestFetcher_Resolve (line 12) | func TestFetcher_Resolve(t *testing.T) {
  function TestFetcherManager_ParseName (line 36) | func TestFetcherManager_ParseName(t *testing.T) {

FILE: internal/protocol/http/config.go
  type config (line 3) | type config struct

FILE: internal/protocol/http/fetcher.go
  constant connectTimeout (line 26) | connectTimeout     = 15 * time.Second
  constant readTimeout (line 27) | readTimeout        = 15 * time.Second
  constant minFastFailTimeout (line 28) | minFastFailTimeout = int64(3 * time.Second)
  constant stealThresholdSeconds (line 32) | stealThresholdSeconds = 3
  constant stealMinChunkSize (line 33) | stealMinChunkSize     = 512 * 1024
  type fetcherState (line 40) | type fetcherState
  constant stateIdle (line 43) | stateIdle      fetcherState = iota
  constant stateResolving (line 44) | stateResolving
  constant stateResolved (line 45) | stateResolved
  constant stateSlowStart (line 46) | stateSlowStart
  constant stateSteady (line 47) | stateSteady
  constant statePaused (line 48) | statePaused
  constant stateDone (line 49) | stateDone
  constant stateError (line 50) | stateError
  type connectionState (line 57) | type connectionState
  constant connNotStarted (line 60) | connNotStarted  connectionState = iota
  constant connConnecting (line 61) | connConnecting
  constant connDownloading (line 62) | connDownloading
  constant connCompleted (line 63) | connCompleted
  constant connFailed (line 64) | connFailed
  type connectionRole (line 67) | type connectionRole
  constant roleResolve (line 70) | roleResolve connectionRole = iota
  constant rolePrimary (line 71) | rolePrimary
  constant roleWorker (line 72) | roleWorker
  type chunk (line 75) | type chunk struct
    method remain (line 81) | func (c *chunk) remain() int64 {
  function newChunk (line 85) | func newChunk(begin int64, end int64) *chunk {
  type connection (line 92) | type connection struct
  type slowStartController (line 117) | type slowStartController struct
    method onConnectSuccess (line 138) | func (s *slowStartController) onConnectSuccess() bool {
    method onConnectFailed (line 155) | func (s *slowStartController) onConnectFailed() {
    method getNextBatchSize (line 175) | func (s *slowStartController) getNextBatchSize() int {
    method commitBatch (line 197) | func (s *slowStartController) commitBatch(count int) {
  function newSlowStartController (line 128) | func newSlowStartController(maxConnections int) *slowStartController {
  type Fetcher (line 211) | type Fetcher struct
    method Setup (line 273) | func (f *Fetcher) Setup(ctl *controller.Controller) {
    method getState (line 293) | func (f *Fetcher) getState() fetcherState {
    method setState (line 297) | func (f *Fetcher) setState(s fetcherState) {
    method updateMaxConnTime (line 302) | func (f *Fetcher) updateMaxConnTime(d time.Duration) {
    method Resolve (line 309) | func (f *Fetcher) Resolve(req *base.Request, opts *base.Options) error {
    method asyncPrefetch (line 450) | func (f *Fetcher) asyncPrefetch() {
    method stopPrefetchAndCopyData (line 516) | func (f *Fetcher) stopPrefetchAndCopyData() int64 {
    method cleanupPrefetchFile (line 563) | func (f *Fetcher) cleanupPrefetchFile() {
    method Start (line 574) | func (f *Fetcher) Start() error {
    method doStart (line 600) | func (f *Fetcher) doStart() error {
    method downloadLoop (line 691) | func (f *Fetcher) downloadLoop() {
    method startResolveDownload (line 742) | func (f *Fetcher) startResolveDownload() {
    method expandConnections (line 770) | func (f *Fetcher) expandConnections() {
    method runConnection (line 892) | func (f *Fetcher) runConnection(conn *connection) {
    method downloadChunkOnce (line 979) | func (f *Fetcher) downloadChunkOnce(conn *connection, client *http.Cli...
    method runConnectionWithResolveResp (line 1133) | func (f *Fetcher) runConnectionWithResolveResp(conn *connection) {
    method runConnectionFallback (line 1216) | func (f *Fetcher) runConnectionFallback(conn *connection) {
    method helpOtherConnection (line 1372) | func (f *Fetcher) helpOtherConnection(helper *connection) bool {
    method resetConnectionForRestart (line 1417) | func (f *Fetcher) resetConnectionForRestart(conn *connection) {
    method resumeConnections (line 1438) | func (f *Fetcher) resumeConnections() {
    method waitForCompletion (line 1477) | func (f *Fetcher) waitForCompletion() {
    method onDownloadComplete (line 1485) | func (f *Fetcher) onDownloadComplete() {
    method checkCompletion (line 1570) | func (f *Fetcher) checkCompletion() bool {
    method Patch (line 1609) | func (f *Fetcher) Patch(req *base.Request, opts *base.Options) error {
    method Pause (line 1662) | func (f *Fetcher) Pause() error {
    method Close (line 1715) | func (f *Fetcher) Close() error {
    method Meta (line 1719) | func (f *Fetcher) Meta() *fetcher.FetcherMeta {
    method Stats (line 1723) | func (f *Fetcher) Stats() any {
    method Progress (line 1741) | func (f *Fetcher) Progress() fetcher.Progress {
    method Wait (line 1759) | func (f *Fetcher) Wait() error {

FILE: internal/protocol/http/fetcher_manager.go
  type fetcherData (line 16) | type fetcherData struct
  type FetcherManager (line 25) | type FetcherManager struct
    method Name (line 28) | func (fm *FetcherManager) Name() string {
    method Filters (line 32) | func (fm *FetcherManager) Filters() []*fetcher.SchemeFilter {
    method Build (line 45) | func (fm *FetcherManager) Build() fetcher.Fetcher {
    method ParseName (line 49) | func (fm *FetcherManager) ParseName(u string) string {
    method AutoRename (line 62) | func (fm *FetcherManager) AutoRename() bool {
    method DefaultConfig (line 66) | func (fm *FetcherManager) DefaultConfig() any {
    method Store (line 73) | func (fm *FetcherManager) Store(f fetcher.Fetcher) (data any, err erro...
    method Restore (line 84) | func (fm *FetcherManager) Restore() (v any, f func(meta *fetcher.Fetch...
    method Close (line 103) | func (fm *FetcherManager) Close() error {

FILE: internal/protocol/http/fetcher_test.go
  function TestFetcher_Resolve (line 21) | func TestFetcher_Resolve(t *testing.T) {
  function TestFetcher_ResolveWithHostHeader (line 217) | func TestFetcher_ResolveWithHostHeader(t *testing.T) {
  function TestFetcher_ResolveWithInvalidHeader (line 239) | func TestFetcher_ResolveWithInvalidHeader(t *testing.T) {
  function testResolve (line 262) | func testResolve(startTestServer func() net.Listener, path string, t *te...
  function TestFetcher_DownloadNormal (line 282) | func TestFetcher_DownloadNormal(t *testing.T) {
  function TestFetcher_DownloadContinue (line 292) | func TestFetcher_DownloadContinue(t *testing.T) {
  function TestFetcher_DownloadContinue_NoRangeRestart (line 302) | func TestFetcher_DownloadContinue_NoRangeRestart(t *testing.T) {
  function TestFetcher_DownloadChunked (line 349) | func TestFetcher_DownloadChunked(t *testing.T) {
  function TestFetcher_DownloadPost (line 357) | func TestFetcher_DownloadPost(t *testing.T) {
  function TestFetcher_DownloadRetry (line 364) | func TestFetcher_DownloadRetry(t *testing.T) {
  function TestFetcher_DownloadError (line 371) | func TestFetcher_DownloadError(t *testing.T) {
  function TestFetcher_DownloadLimit (line 378) | func TestFetcher_DownloadLimit(t *testing.T) {
  function TestFetcher_DownloadResponseBodyReadTimeout (line 387) | func TestFetcher_DownloadResponseBodyReadTimeout(t *testing.T) {
  function TestFetcher_Download500Recovery (line 428) | func TestFetcher_Download500Recovery(t *testing.T) {
  function TestFetcher_DownloadOnBugFileServer (line 458) | func TestFetcher_DownloadOnBugFileServer(t *testing.T) {
  function TestFetcher_DownloadResume (line 466) | func TestFetcher_DownloadResume(t *testing.T) {
  function TestFetcher_DownloadWithProxy (line 476) | func TestFetcher_DownloadWithProxy(t *testing.T) {
  function TestFetcher_ConfigConnections (line 485) | func TestFetcher_ConfigConnections(t *testing.T) {
  function TestFetcher_ConfigUseServerCtime (line 506) | func TestFetcher_ConfigUseServerCtime(t *testing.T) {
  function TestFetcher_Stats (line 528) | func TestFetcher_Stats(t *testing.T) {
  function TestFetcher_DownloadOneTimeURL (line 560) | func TestFetcher_DownloadOneTimeURL(t *testing.T) {
  function TestFetcher_SlowStartExpansion (line 600) | func TestFetcher_SlowStartExpansion(t *testing.T) {
  function TestFetcher_AsyncPrefetch (line 687) | func TestFetcher_AsyncPrefetch(t *testing.T) {
  function TestFetcher_DownloadExpiringRedirectURL (line 830) | func TestFetcher_DownloadExpiringRedirectURL(t *testing.T) {
  function TestFetcher_RetryAfterError (line 924) | func TestFetcher_RetryAfterError(t *testing.T) {
  function TestFetcherManager_ParseName (line 990) | func TestFetcherManager_ParseName(t *testing.T) {
  function downloadReady (line 1038) | func downloadReady(listener net.Listener, connections int, t *testing.T)...
  function doDownloadReady (line 1042) | func doDownloadReady(f fetcher.Fetcher, listener net.Listener, connectio...
  function downloadNormal (line 1063) | func downloadNormal(listener net.Listener, connections int, t *testing.T) {
  function downloadPost (line 1080) | func downloadPost(listener net.Listener, connections int, t *testing.T) {
  function downloadContinue (line 1124) | func downloadContinue(listener net.Listener, connections int, t *testing...
  function downloadError (line 1149) | func downloadError(listener net.Listener, connections int, t *testing.T) {
  function downloadResume (line 1177) | func downloadResume(listener net.Listener, connections int, t *testing.T) {
  function downloadWithProxy (line 1212) | func downloadWithProxy(httpListener net.Listener, proxyListener net.List...
  function buildFetcher (line 1238) | func buildFetcher() *Fetcher {
  function buildConfigFetcher (line 1249) | func buildConfigFetcher(cfg config) fetcher.Fetcher {
  function TestFetcher_Patch_URLChange (line 1264) | func TestFetcher_Patch_URLChange(t *testing.T) {
  function TestFetcher_Patch_Labels (line 1330) | func TestFetcher_Patch_Labels(t *testing.T) {
  function TestFetcher_Patch_Extra (line 1386) | func TestFetcher_Patch_Extra(t *testing.T) {
  function TestFetcher_Patch_NilData (line 1475) | func TestFetcher_Patch_NilData(t *testing.T) {
  function TestFetcher_Patch_CookieExpired (line 1525) | func TestFetcher_Patch_CookieExpired(t *testing.T) {

FILE: internal/protocol/http/filename_parse_test.go
  function TestParseFilenameWithAmpersand (line 9) | func TestParseFilenameWithAmpersand(t *testing.T) {
  function TestFindParamValueEnd (line 58) | func TestFindParamValueEnd(t *testing.T) {
  function TestUnescapeHTMLEntities (line 97) | func TestUnescapeHTMLEntities(t *testing.T) {

FILE: internal/protocol/http/helper.go
  type RequestError (line 25) | type RequestError struct
    method Error (line 33) | func (re *RequestError) Error() string {
  function NewRequestError (line 29) | func NewRequestError(code int) *RequestError {
  function isFailureExemptHTTPCode (line 37) | func isFailureExemptHTTPCode(code int) bool {
  function shouldCountHTTPFailure (line 50) | func shouldCountHTTPFailure(err error) bool {
  function extractRequestError (line 59) | func extractRequestError(err error) *RequestError {
  method buildRequest (line 69) | func (f *Fetcher) buildRequest(ctx context.Context, req *base.Request) (...
  method buildRequestWithOriginalURL (line 75) | func (f *Fetcher) buildRequestWithOriginalURL(ctx context.Context, req *...
  method buildRequestWithURL (line 81) | func (f *Fetcher) buildRequestWithURL(ctx context.Context, req *base.Req...
  method updateRedirectURL (line 135) | func (f *Fetcher) updateRedirectURL(url string) {
  method hasRedirectURL (line 142) | func (f *Fetcher) hasRedirectURL() bool {
  function isRedirectExpiredError (line 150) | func isRedirectExpiredError(err error) bool {
  method tryFallbackToOriginalURL (line 168) | func (f *Fetcher) tryFallbackToOriginalURL(ctx context.Context, client *...
  method buildClient (line 194) | func (f *Fetcher) buildClient() *http.Client {
  method buildFastFailClient (line 200) | func (f *Fetcher) buildFastFailClient() *http.Client {
  method buildClientWithTimeout (line 218) | func (f *Fetcher) buildClientWithTimeout(timeout time.Duration) *http.Cl...
  function parseFilename (line 241) | func parseFilename(contentDisposition string) string {
  function parseFilenameExtended (line 261) | func parseFilenameExtended(cd string) string {
  function decodeFilenameParam (line 302) | func decodeFilenameParam(filename string) string {
  function unescapeHTMLEntities (line 332) | func unescapeHTMLEntities(s string) string {
  function tryDecodeGBK (line 351) | func tryDecodeGBK(s string) string {
  function parseFilenameFallback (line 369) | func parseFilenameFallback(cd string) string {
  function findParamValueEnd (line 400) | func findParamValueEnd(value string) int {
  function isValidHTMLEntityChars (line 464) | func isValidHTMLEntityChars(s string) bool {

FILE: internal/protocol/http/timeout_reader.go
  type TimeoutReader (line 9) | type TimeoutReader struct
    method Read (line 21) | func (tr *TimeoutReader) Read(p []byte) (n int, err error) {
  function NewTimeoutReader (line 14) | func NewTimeoutReader(r io.Reader, timeout time.Duration) *TimeoutReader {

FILE: internal/protocol/http/timeout_reader_test.go
  function TestTimeoutReader_Read (line 12) | func TestTimeoutReader_Read(t *testing.T) {
  function TestTimeoutReader_ReadTimeout (line 30) | func TestTimeoutReader_ReadTimeout(t *testing.T) {
  type slowReader (line 44) | type slowReader struct
    method Read (line 48) | func (sr *slowReader) Read(p []byte) (n int, err error) {

FILE: internal/test/httptest.go
  constant BuildName (line 25) | BuildName = "build.data"
  constant BuildSize (line 26) | BuildSize = 200 * 1024 * 1024
  constant Dir (line 27) | Dir       = "./"
  constant BuildFile (line 28) | BuildFile = Dir + BuildName
  constant ExternalDownloadUrl (line 30) | ExternalDownloadUrl  = "https://raw.githubusercontent.com/GopeedLab/gope...
  constant ExternalDownloadName (line 31) | ExternalDownloadName = "banner.png"
  constant ExternalDownloadSize (line 32) | ExternalDownloadSize = 26416
  constant DownloadName (line 35) | DownloadName       = "download.data"
  constant DownloadRename (line 36) | DownloadRename     = "download (1).data"
  constant DownloadFile (line 37) | DownloadFile       = Dir + DownloadName
  constant DownloadRenameFile (line 38) | DownloadRenameFile = Dir + DownloadRename
  constant TestChineseFileName (line 42) | TestChineseFileName = "测试.zip"
  function StartTestFileServer (line 45) | func StartTestFileServer() net.Listener {
  type SlowFileServer (line 51) | type SlowFileServer struct
    method ServeHTTP (line 56) | func (s *SlowFileServer) ServeHTTP(w http.ResponseWriter, r *http.Requ...
  function StartTestSlowFileServer (line 61) | func StartTestSlowFileServer(delay time.Duration) net.Listener {
  function StartTestCustomServer (line 70) | func StartTestCustomServer() net.Listener {
  function StartTestHostHeaderServer (line 237) | func StartTestHostHeaderServer() net.Listener {
  function StartTestRetryServer (line 254) | func StartTestRetryServer() net.Listener {
  function StartTestPostServer (line 275) | func StartTestPostServer() net.Listener {
  function StartTestErrorServer (line 298) | func StartTestErrorServer() net.Listener {
  function StartTestOneTimeServer (line 313) | func StartTestOneTimeServer() net.Listener {
  function StartTestNoRangeSlowServer (line 340) | func StartTestNoRangeSlowServer(delayPerChunk time.Duration) net.Listener {
  function StartTestExpiringRedirectServer (line 382) | func StartTestExpiringRedirectServer(requestsBeforeExpire int32, delayPe...
  function StartTestSlowStartServer (line 438) | func StartTestSlowStartServer(delayPerByte time.Duration) net.Listener {
  function slowCopyNWithDelay (line 456) | func slowCopyNWithDelay(sl *shutdownListener, dst io.Writer, src io.Read...
  function StartTestLimitServer (line 488) | func StartTestLimitServer(maxConnections int32, delay int64) net.Listener {
  function StartTestTimeoutOnceServer (line 525) | func StartTestTimeoutOnceServer(delay int64) net.Listener {
  function StartTestTemporary500Server (line 552) | func StartTestTemporary500Server(errorDuration time.Duration) net.Listen...
  function StartTestFailThenRecoverServer (line 605) | func StartTestFailThenRecoverServer(failedRequestsBeforeRecover int32) n...
  function StartTestRangeBugServer (line 650) | func StartTestRangeBugServer() net.Listener {
  function rangeFileHandle (line 676) | func rangeFileHandle(writer http.ResponseWriter, request *http.Request, ...
  function slowCopy (line 750) | func slowCopy(sl *shutdownListener, dst io.Writer, src io.Reader, delay ...
  function slowCopyN (line 784) | func slowCopyN(sl *shutdownListener, dst io.Writer, src io.Reader, n int...
  function slowCopyAfterDelay (line 797) | func slowCopyAfterDelay(sl *shutdownListener, dst io.Writer, src io.Read...
  function startTestServer (line 804) | func startTestServer(serverHandle func(sl *shutdownListener) http.Handle...
  type shutdownListener (line 841) | type shutdownListener struct
    method Close (line 847) | func (c *shutdownListener) Close() error {
  function ifExistAndRemove (line 863) | func ifExistAndRemove(name string) error {
  function StartSocks5Server (line 870) | func StartSocks5Server(usr, pwd string) net.Listener {
  function StartTestPatchURLServer (line 896) | func StartTestPatchURLServer() net.Listener {
  function StartTestCookieExpiringServer (line 924) | func StartTestCookieExpiringServer() net.Listener {
  function AssertResourceEqual (line 974) | func AssertResourceEqual(want, got *base.Resource) bool {

FILE: internal/test/util.go
  function FileMd5 (line 12) | func FileMd5(filePath string) string {
  function DirMd5 (line 30) | func DirMd5(dirPath string) string {
  function ToJson (line 48) | func ToJson(v interface{}) string {
  function JsonEqual (line 53) | func JsonEqual(v1 any, v2 any) bool {

FILE: pkg/base/constants.go
  type Status (line 3) | type Status
  constant DownloadStatusReady (line 6) | DownloadStatusReady   Status = "ready"
  constant DownloadStatusRunning (line 7) | DownloadStatusRunning Status = "running"
  constant DownloadStatusPause (line 8) | DownloadStatusPause   Status = "pause"
  constant DownloadStatusWait (line 9) | DownloadStatusWait    Status = "wait"
  constant DownloadStatusError (line 10) | DownloadStatusError   Status = "error"
  constant DownloadStatusDone (line 11) | DownloadStatusDone    Status = "done"
  constant HttpCodeOK (line 15) | HttpCodeOK             = 200
  constant HttpCodePartialContent (line 16) | HttpCodePartialContent = 206
  constant HttpHeaderHost (line 18) | HttpHeaderHost               = "Host"
  constant HttpHeaderRange (line 19) | HttpHeaderRange              = "Range"
  constant HttpHeaderAcceptRanges (line 20) | HttpHeaderAcceptRanges       = "Accept-Ranges"
  constant HttpHeaderContentLength (line 21) | HttpHeaderContentLength      = "Content-Length"
  constant HttpHeaderContentRange (line 22) | HttpHeaderContentRange       = "Content-Range"
  constant HttpHeaderContentDisposition (line 23) | HttpHeaderContentDisposition = "Content-Disposition"
  constant HttpHeaderUserAgent (line 24) | HttpHeaderUserAgent          = "User-Agent"
  constant HttpHeaderLastModified (line 25) | HttpHeaderLastModified       = "Last-Modified"
  constant HttpHeaderBytes (line 27) | HttpHeaderBytes       = "bytes"
  constant HttpHeaderRangeFormat (line 28) | HttpHeaderRangeFormat = "bytes=%d-%d"

FILE: pkg/base/info.go
  function init (line 7) | func init() {

FILE: pkg/base/model.go
  type Request (line 16) | type Request struct
    method Validate (line 27) | func (r *Request) Validate() error {
  type RequestProxyMode (line 34) | type RequestProxyMode
  constant RequestProxyModeFollow (line 38) | RequestProxyModeFollow RequestProxyMode = "follow"
  constant RequestProxyModeNone (line 40) | RequestProxyModeNone RequestProxyMode = "none"
  constant RequestProxyModeCustom (line 42) | RequestProxyModeCustom RequestProxyMode = "custom"
  type RequestProxy (line 45) | type RequestProxy struct
    method ToHandler (line 53) | func (p *RequestProxy) ToHandler() func(r *http.Request) (*url.URL, er...
  type Resource (line 66) | type Resource struct
    method Validate (line 77) | func (r *Resource) Validate() error {
    method CalcSize (line 92) | func (r *Resource) CalcSize(selectFiles []int) {
  type FileInfo (line 102) | type FileInfo struct
  type Options (line 112) | type Options struct
    method InitSelectFiles (line 123) | func (o *Options) InitSelectFiles(fileSize int) {
    method Clone (line 133) | func (o *Options) Clone() *Options {
  function ParseReqExtra (line 137) | func ParseReqExtra[E any](req *Request) error {
  function ParseOptExtra (line 152) | func ParseOptExtra[E any](opts *Options) error {
  type CreateTaskBatch (line 167) | type CreateTaskBatch struct
  type CreateTaskBatchItem (line 172) | type CreateTaskBatchItem struct
  type DownloaderStoreConfig (line 178) | type DownloaderStoreConfig struct
    method Init (line 193) | func (cfg *DownloaderStoreConfig) Init() *DownloaderStoreConfig {
    method Merge (line 224) | func (cfg *DownloaderStoreConfig) Merge(beforeCfg *DownloaderStoreConf...
  type WebhookConfig (line 259) | type WebhookConfig struct
  type ScriptConfig (line 265) | type ScriptConfig struct
  type AutoTorrentConfig (line 271) | type AutoTorrentConfig struct
  type ArchiveConfig (line 277) | type ArchiveConfig struct
  type DownloaderProxyConfig (line 282) | type DownloaderProxyConfig struct
    method ToHandler (line 292) | func (cfg *DownloaderProxyConfig) ToHandler() func(r *http.Request) (*...
    method ToUrl (line 307) | func (cfg *DownloaderProxyConfig) ToUrl() *url.URL {
  function safeProxyReloadConf (line 340) | func safeProxyReloadConf() {
  function parseUrlSafe (line 347) | func parseUrlSafe(rawUrl string) *url.URL {

FILE: pkg/base/model_test.go
  function TestDownloaderStoreConfig_Init (line 8) | func TestDownloaderStoreConfig_Init(t *testing.T) {
  function TestDownloaderStoreConfig_Merge (line 174) | func TestDownloaderStoreConfig_Merge(t *testing.T) {

FILE: pkg/download/downloader.go
  constant bucketTask (line 31) | bucketTask = "task"
  constant bucketSave (line 33) | bucketSave = "save"
  constant bucketProtocolState (line 35) | bucketProtocolState = "protocol_state"
  constant bucketConfig (line 37) | bucketConfig = "config"
  constant bucketExtension (line 39) | bucketExtension = "extension"
  constant bucketExtensionStorage (line 41) | bucketExtensionStorage = "extension_storage"
  type Listener (line 49) | type Listener
  type ExtractStatus (line 52) | type ExtractStatus
  constant ExtractStatusNone (line 56) | ExtractStatusNone ExtractStatus = ""
  constant ExtractStatusQueued (line 58) | ExtractStatusQueued ExtractStatus = "queued"
  constant ExtractStatusWaitingParts (line 60) | ExtractStatusWaitingParts ExtractStatus = "waitingParts"
  constant ExtractStatusExtracting (line 62) | ExtractStatusExtracting ExtractStatus = "extracting"
  constant ExtractStatusDone (line 64) | ExtractStatusDone ExtractStatus = "done"
  constant ExtractStatusError (line 66) | ExtractStatusError ExtractStatus = "error"
  type Progress (line 69) | type Progress struct
  type Downloader (line 92) | type Downloader struct
    method Setup (line 144) | func (d *Downloader) Setup() error {
    method cleanupNonExistingTasks (line 287) | func (d *Downloader) cleanupNonExistingTasks() {
    method parseFm (line 330) | func (d *Downloader) parseFm(url string) (fetcher.FetcherManager, erro...
    method setupFetcher (line 341) | func (d *Downloader) setupFetcher(fm fetcher.FetcherManager, fetcher f...
    method saveTask (line 363) | func (d *Downloader) saveTask(task *Task) error {
    method Resolve (line 387) | func (d *Downloader) Resolve(req *base.Request, opts *base.Options) (r...
    method notifyRunning (line 426) | func (d *Downloader) notifyRunning() {
    method remainRunningCount (line 443) | func (d *Downloader) remainRunningCount() int {
    method CreateDirect (line 453) | func (d *Downloader) CreateDirect(req *base.Request, opts *base.Option...
    method CreateDirectBatch (line 467) | func (d *Downloader) CreateDirectBatch(req *base.CreateTaskBatch) (tas...
    method Create (line 483) | func (d *Downloader) Create(rrId string) (taskId string, err error) {
    method Patch (line 501) | func (d *Downloader) Patch(id string, req *base.Request, opts *base.Op...
    method Pause (line 539) | func (d *Downloader) Pause(filter *TaskFilter) (err error) {
    method pauseAll (line 560) | func (d *Downloader) pauseAll() (err error) {
    method Continue (line 579) | func (d *Downloader) Continue(filter *TaskFilter) (err error) {
    method continueAll (line 636) | func (d *Downloader) continueAll() (err error) {
    method ContinueBatch (line 665) | func (d *Downloader) ContinueBatch(filter *TaskFilter) (err error) {
    method Delete (line 679) | func (d *Downloader) Delete(filter *TaskFilter, force bool) (err error) {
    method deleteAll (line 726) | func (d *Downloader) deleteAll(force bool) (err error) {
    method Stats (line 747) | func (d *Downloader) Stats(id string) (sr any, err error) {
    method doDelete (line 767) | func (d *Downloader) doDelete(task *Task, force bool) (err error) {
    method Close (line 803) | func (d *Downloader) Close() error {
    method Clear (line 824) | func (d *Downloader) Clear() error {
    method Listener (line 858) | func (d *Downloader) Listener(fn Listener) {
    method emit (line 862) | func (d *Downloader) emit(eventKey EventKey, task *Task, errs ...error) {
    method GetTask (line 876) | func (d *Downloader) GetTask(id string) *Task {
    method GetTasks (line 888) | func (d *Downloader) GetTasks() []*Task {
    method GetTasksByFilter (line 897) | func (d *Downloader) GetTasksByFilter(filter *TaskFilter) []*Task {
    method GetConfig (line 948) | func (d *Downloader) GetConfig() (*base.DownloaderStoreConfig, error) {
    method PutConfig (line 952) | func (d *Downloader) PutConfig(v *base.DownloaderStoreConfig) error {
    method getProtocolConfig (line 957) | func (d *Downloader) getProtocolConfig(name string, v any) bool {
    method watch (line 973) | func (d *Downloader) watch(task *Task) {
    method doOnError (line 1093) | func (d *Downloader) doOnError(task *Task, err error) {
    method restoreTask (line 1105) | func (d *Downloader) restoreTask(task *Task) error {
    method restoreFetcher (line 1115) | func (d *Downloader) restoreFetcher(task *Task) error {
    method doCreate (line 1140) | func (d *Downloader) doCreate(f fetcher.Fetcher, opts *base.Options) (...
    method initOptions (line 1183) | func (d *Downloader) initOptions(opts *base.Options) (*base.Options, e...
    method statusMut (line 1216) | func (d *Downloader) statusMut(task *Task, fn func() (bool, error)) (b...
    method doStart (line 1223) | func (d *Downloader) doStart(task *Task) (err error) {
    method doPause (line 1309) | func (d *Downloader) doPause(task *Task) (err error) {
    method assignFetcherManager (line 1369) | func (d *Downloader) assignFetcherManager(task *Task) error {
    method buildFetcher (line 1378) | func (d *Downloader) buildFetcher(url string) (fetcher.Fetcher, error) {
    method enqueueExtraction (line 1391) | func (d *Downloader) enqueueExtraction(task *Task, downloadFilePath st...
    method enqueueSingleExtraction (line 1404) | func (d *Downloader) enqueueSingleExtraction(task *Task, downloadFileP...
    method enqueueMultiPartExtraction (line 1425) | func (d *Downloader) enqueueMultiPartExtraction(task *Task, downloadFi...
    method checkMultiPartArchiveReady (line 1479) | func (d *Downloader) checkMultiPartArchiveReady(filePath string, destD...
    method checkAllMultiPartTasksDone (line 1491) | func (d *Downloader) checkAllMultiPartTasksDone(baseName string) (bool...
    method tryClaimMultiPartExtraction (line 1527) | func (d *Downloader) tryClaimMultiPartExtraction(task *Task, baseName ...
    method releaseMultiPartExtractionClaim (line 1541) | func (d *Downloader) releaseMultiPartExtractionClaim(baseName string) {
    method performExtraction (line 1546) | func (d *Downloader) performExtraction(task *Task, archivePath string,...
    method performMultiPartExtraction (line 1563) | func (d *Downloader) performMultiPartExtraction(task *Task, firstPartP...
    method collectMultiPartFiles (line 1594) | func (d *Downloader) collectMultiPartFiles(firstPartPath string) []str...
    method collectSequentialFiles (line 1621) | func (d *Downloader) collectSequentialFiles(dir, baseName, format stri...
    method collectRarNewStyleFiles (line 1640) | func (d *Downloader) collectRarNewStyleFiles(dir, baseName string) []s...
    method collectRarOldStyleFiles (line 1662) | func (d *Downloader) collectRarOldStyleFiles(dir, baseName string) []s...
    method collectZipSplitFiles (line 1686) | func (d *Downloader) collectZipSplitFiles(dir, baseName string) []stri...
    method handleExtractionResult (line 1710) | func (d *Downloader) handleExtractionResult(task *Task, extractErr err...
    method updateMultiPartTasksStatus (line 1738) | func (d *Downloader) updateMultiPartTasksStatus(sourceTask *Task, extr...
  function NewDownloader (line 116) | func NewDownloader(cfg *DownloaderConfig) *Downloader {
  type protocolStateStore (line 838) | type protocolStateStore struct
    method Load (line 843) | func (s *protocolStateStore) Load(v any) (bool, error) {
    method Save (line 847) | func (s *protocolStateStore) Save(v any) error {
    method Delete (line 854) | func (s *protocolStateStore) Delete() error {
  function logPanic (line 1358) | func logPanic(logDir string) {
  function initTask (line 1766) | func initTask(task *Task) {
  type boot (line 1777) | type boot struct
    method URL (line 1783) | func (b *boot) URL(url string) *boot {
    method Extra (line 1788) | func (b *boot) Extra(extra interface{}) *boot {
    method Listener (line 1793) | func (b *boot) Listener(listener Listener) *boot {
    method Create (line 1798) | func (b *boot) Create(opts *base.Options) (string, error) {
  function Boot (line 1806) | func Boot() *boot {

FILE: pkg/download/downloader_test.go
  function TestDownloader_Resolve (line 32) | func TestDownloader_Resolve(t *testing.T) {
  function TestDownloader_Create (line 64) | func TestDownloader_Create(t *testing.T) {
  function TestDownloader_CreateNotInWhite (line 100) | func TestDownloader_CreateNotInWhite(t *testing.T) {
  function TestDownloader_CreateDirectBatch (line 125) | func TestDownloader_CreateDirectBatch(t *testing.T) {
  function TestDownloader_CreateWithProxy (line 198) | func TestDownloader_CreateWithProxy(t *testing.T) {
  function doTestDownloaderCreateWithProxy (line 268) | func doTestDownloaderCreateWithProxy(t *testing.T, auth bool, buildReqPr...
  function TestDownloader_CreateRename (line 328) | func TestDownloader_CreateRename(t *testing.T) {
  function TestDownloader_StoreAndRestore (line 373) | func TestDownloader_StoreAndRestore(t *testing.T) {
  function TestDownloader_Protocol_Config (line 435) | func TestDownloader_Protocol_Config(t *testing.T) {
  function TestDownloader_GetTasksByFilter (line 483) | func TestDownloader_GetTasksByFilter(t *testing.T) {
  function TestDownloader_Stats (line 636) | func TestDownloader_Stats(t *testing.T) {
  function TestDownloader_Delete (line 686) | func TestDownloader_Delete(t *testing.T) {
  function TestDownloader_PauseAndContinue (line 761) | func TestDownloader_PauseAndContinue(t *testing.T) {
  function TestDownloader_PauseAllAndContinueAll (line 828) | func TestDownloader_PauseAllAndContinueAll(t *testing.T) {
  function TestDownloader_GetTask (line 913) | func TestDownloader_GetTask(t *testing.T) {
  function TestDownloader_Emit (line 927) | func TestDownloader_Emit(t *testing.T) {
  function TestDownloader_AutoExtract (line 948) | func TestDownloader_AutoExtract(t *testing.T) {
  function TestDownloader_AutoExtractWithProgress (line 975) | func TestDownloader_AutoExtractWithProgress(t *testing.T) {
  function TestDownloader_AutoExtractWithDeleteAfterExtract (line 1116) | func TestDownloader_AutoExtractWithDeleteAfterExtract(t *testing.T) {
  function TestDownloader_AutoExtractError (line 1201) | func TestDownloader_AutoExtractError(t *testing.T) {
  function TestExtractStatus (line 1312) | func TestExtractStatus(t *testing.T) {
  function TestProgress_ExtractFields (line 1334) | func TestProgress_ExtractFields(t *testing.T) {
  function TestProgress_MultiPartFields (line 1359) | func TestProgress_MultiPartFields(t *testing.T) {
  function TestExtractStatus_WaitingParts (line 1397) | func TestExtractStatus_WaitingParts(t *testing.T) {
  function createTestArchiveWithMultipleFiles (line 1404) | func createTestArchiveWithMultipleFiles(path string, count int) error {
  function startTestArchiveServer (line 1428) | func startTestArchiveServer(zipPath string) net.Listener {
  function createTestArchive (line 1454) | func createTestArchive(path string) error {
  function TestDownloader_Close (line 1474) | func TestDownloader_Close(t *testing.T) {
  function TestDownloader_DeleteAll (line 1493) | func TestDownloader_DeleteAll(t *testing.T) {
  function TestDownloader_ProtocolConfigNotExist (line 1548) | func TestDownloader_ProtocolConfigNotExist(t *testing.T) {
  function TestTaskFilter_IsEmpty (line 1563) | func TestTaskFilter_IsEmpty(t *testing.T) {
  function TestDownloader_CollectSequentialFiles (line 1607) | func TestDownloader_CollectSequentialFiles(t *testing.T) {
  function TestDownloader_CollectSequentialFiles_NoFiles (line 1638) | func TestDownloader_CollectSequentialFiles_NoFiles(t *testing.T) {
  function TestDownloader_CollectRarNewStyleFiles (line 1653) | func TestDownloader_CollectRarNewStyleFiles(t *testing.T) {
  function TestDownloader_CollectRarNewStyleFiles_SingleDigit (line 1676) | func TestDownloader_CollectRarNewStyleFiles_SingleDigit(t *testing.T) {
  function TestDownloader_CollectRarOldStyleFiles (line 1699) | func TestDownloader_CollectRarOldStyleFiles(t *testing.T) {
  function TestDownloader_CollectZipSplitFiles (line 1734) | func TestDownloader_CollectZipSplitFiles(t *testing.T) {
  function TestDownloader_CollectMultiPartFiles (line 1769) | func TestDownloader_CollectMultiPartFiles(t *testing.T) {
  function TestDownloader_CheckAllMultiPartTasksDone (line 1846) | func TestDownloader_CheckAllMultiPartTasksDone(t *testing.T) {
  function TestDownloader_CheckAllMultiPartTasksDone_NoRelatedTasks (line 1910) | func TestDownloader_CheckAllMultiPartTasksDone_NoRelatedTasks(t *testing...
  function TestDownloader_TryClaimMultiPartExtraction (line 1927) | func TestDownloader_TryClaimMultiPartExtraction(t *testing.T) {
  function TestDownloader_HandleExtractionResult_Success (line 1996) | func TestDownloader_HandleExtractionResult_Success(t *testing.T) {
  function TestDownloader_HandleExtractionResult_WithDelete (line 2043) | func TestDownloader_HandleExtractionResult_WithDelete(t *testing.T) {
  function TestDownloader_HandleExtractionResult_Error (line 2094) | func TestDownloader_HandleExtractionResult_Error(t *testing.T) {
  function TestDownloader_UpdateMultiPartTasksStatus (line 2128) | func TestDownloader_UpdateMultiPartTasksStatus(t *testing.T) {
  function TestDownloader_UpdateMultiPartTasksStatus_WithError (line 2196) | func TestDownloader_UpdateMultiPartTasksStatus_WithError(t *testing.T) {
  function TestDownloader_UpdateMultiPartTasksStatus_NoBaseName (line 2248) | func TestDownloader_UpdateMultiPartTasksStatus_NoBaseName(t *testing.T) {
  function TestDownloader_CheckMultiPartArchiveReady (line 2280) | func TestDownloader_CheckMultiPartArchiveReady(t *testing.T) {
  function TestDownloader_CheckMultiPartArchiveReady_EmptyBaseName (line 2320) | func TestDownloader_CheckMultiPartArchiveReady_EmptyBaseName(t *testing....
  function startTestTorrentServer (line 2338) | func startTestTorrentServer(torrentPath string) net.Listener {
  function TestDownloader_AutoTorrent (line 2362) | func TestDownloader_AutoTorrent(t *testing.T) {
  function TestDownloader_AutoTorrentWithDelete (line 2451) | func TestDownloader_AutoTorrentWithDelete(t *testing.T) {
  function TestDownloader_AutoTorrentDisabled (line 2569) | func TestDownloader_AutoTorrentDisabled(t *testing.T) {
  function TestDownloader_PatchTask_HTTP (line 2652) | func TestDownloader_PatchTask_HTTP(t *testing.T) {
  function TestDownloader_PatchTask_NotFound (line 2716) | func TestDownloader_PatchTask_NotFound(t *testing.T) {

FILE: pkg/download/engine/engine.go
  type Engine (line 21) | type Engine struct
    method RunString (line 29) | func (e *Engine) RunString(script string) (value any, err error) {
    method CallFunction (line 51) | func (e *Engine) CallFunction(fn goja.Callable, args ...any) (value an...
    method await (line 81) | func (e *Engine) await(value any) {
    method Close (line 108) | func (e *Engine) Close() {
  type Config (line 112) | type Config struct
  function NewEngine (line 116) | func NewEngine(cfg *Config) *Engine {
  function Run (line 161) | func Run(script string) (value any, err error) {
  function resolveResult (line 167) | func resolveResult(value goja.Value) (any, error) {

FILE: pkg/download/engine/engine_test.go
  function TestPolyfill (line 25) | func TestPolyfill(t *testing.T) {
  function TestError (line 36) | func TestError(t *testing.T) {
  function TestFetch (line 46) | func TestFetch(t *testing.T) {
  function TestFetchWithProxy (line 331) | func TestFetchWithProxy(t *testing.T) {
  function doTestFetchWithProxy (line 336) | func doTestFetchWithProxy(t *testing.T, usr, pwd string) {
  function TestVm (line 371) | func TestVm(t *testing.T) {
  function TestNonStopLoop (line 399) | func TestNonStopLoop(t *testing.T) {
  function doTestPolyfill (line 430) | func doTestPolyfill(t *testing.T, module string) {
  function startServer (line 442) | func startServer() net.Listener {
  function buildFile (line 525) | func buildFile(t *testing.T, runtime *goja.Runtime) (goja.Value, *file.F...
  function callTestFun (line 538) | func callTestFun(engine *Engine, fun string, args ...any) (any, error) {
  function calcMd5 (line 546) | func calcMd5(reader io.Reader) string {

FILE: pkg/download/engine/inject/error/module.go
  type MessageError (line 7) | type MessageError struct
    method Error (line 11) | func (e *MessageError) Error() string {
  function Enable (line 15) | func Enable(runtime *goja.Runtime) error {

FILE: pkg/download/engine/inject/file/module.go
  type File (line 9) | type File struct
  function NewJsFile (line 16) | func NewJsFile(runtime *goja.Runtime) (goja.Value, error) {
  function Enable (line 24) | func Enable(runtime *goja.Runtime) error {

FILE: pkg/download/engine/inject/formdata/module.go
  type FormData (line 5) | type FormData struct
    method Append (line 9) | func (fd *FormData) Append(name string, value any) {
    method Delete (line 13) | func (fd *FormData) Delete(name string) {
    method Entries (line 17) | func (fd *FormData) Entries() []any {
    method Get (line 25) | func (fd *FormData) Get(name string) any {
    method GetAll (line 29) | func (fd *FormData) GetAll(name string) []any {
    method Has (line 33) | func (fd *FormData) Has(name string) bool {
    method Keys (line 38) | func (fd *FormData) Keys() []string {
    method Set (line 46) | func (fd *FormData) Set(name string, value any) {
    method Values (line 50) | func (fd *FormData) Values() []any {
  function Enable (line 58) | func Enable(runtime *goja.Runtime) error {

FILE: pkg/download/engine/inject/vm/module.go
  type Vm (line 8) | type Vm struct
    method Set (line 12) | func (vm *Vm) Set(name string, value any) {
    method Get (line 18) | func (vm *Vm) Get(name string) (value any) {
    method RunString (line 25) | func (vm *Vm) RunString(script string) (value any, err error) {
  function Enable (line 38) | func Enable(runtime *goja.Runtime) error {

FILE: pkg/download/engine/inject/xhr/module.go
  constant eventLoad (line 22) | eventLoad             = "load"
  constant eventReadystatechange (line 23) | eventReadystatechange = "readystatechange"
  constant eventProgress (line 24) | eventProgress         = "progress"
  constant eventAbort (line 25) | eventAbort            = "abort"
  constant eventError (line 26) | eventError            = "error"
  constant eventTimeout (line 27) | eventTimeout          = "timeout"
  constant redirectError (line 31) | redirectError  = "error"
  constant redirectFollow (line 32) | redirectFollow = "follow"
  constant redirectManual (line 33) | redirectManual = "manual"
  type ProgressEvent (line 36) | type ProgressEvent struct
  type EventProp (line 43) | type EventProp struct
    method AddEventListener (line 52) | func (ep *EventProp) AddEventListener(event string, cb func(event *Pro...
    method RemoveEventListener (line 56) | func (ep *EventProp) RemoveEventListener(event string) {
    method callOnload (line 60) | func (ep *EventProp) callOnload() {
    method callOnprogress (line 71) | func (ep *EventProp) callOnprogress(loaded, total int64) {
    method callOnabort (line 84) | func (ep *EventProp) callOnabort() {
    method callOnerror (line 95) | func (ep *EventProp) callOnerror() {
    method callOntimeout (line 106) | func (ep *EventProp) callOntimeout() {
    method callEventListener (line 117) | func (ep *EventProp) callEventListener(event *ProgressEvent) {
  type XMLHttpRequestUpload (line 123) | type XMLHttpRequestUpload struct
  type XMLHttpRequest (line 127) | type XMLHttpRequest struct
    method Open (line 155) | func (xhr *XMLHttpRequest) Open(method, url string) {
    method SetRequestHeader (line 163) | func (xhr *XMLHttpRequest) SetRequestHeader(key, value string) {
    method Send (line 167) | func (xhr *XMLHttpRequest) Send(data goja.Value) {
    method Abort (line 301) | func (xhr *XMLHttpRequest) Abort() {
    method GetResponseHeader (line 308) | func (xhr *XMLHttpRequest) GetResponseHeader(key string) string {
    method GetAllResponseHeaders (line 315) | func (xhr *XMLHttpRequest) GetAllResponseHeaders() string {
    method callOnreadystatechange (line 326) | func (xhr *XMLHttpRequest) callOnreadystatechange() {
    method doReadystatechange (line 337) | func (xhr *XMLHttpRequest) doReadystatechange(state int) {
    method parseData (line 346) | func (xhr *XMLHttpRequest) parseData(data goja.Value) any {
  function Enable (line 365) | func Enable(runtime *goja.Runtime, proxyHandler func(r *http.Request) (*...
  type multipartWrapper (line 413) | type multipartWrapper struct
    method WriteField (line 430) | func (w *multipartWrapper) WriteField(fieldname string, value string) ...
    method WriteFile (line 435) | func (w *multipartWrapper) WriteFile(fieldname string, file *file.File...
    method Size (line 444) | func (w *multipartWrapper) Size() int64 {
    method Send (line 456) | func (w *multipartWrapper) Send() error {
    method FormDataContentType (line 476) | func (w *multipartWrapper) FormDataContentType() string {
    method Close (line 480) | func (w *multipartWrapper) Close() error {
  function NewMultipart (line 420) | func NewMultipart(w io.Writer) *multipartWrapper {

FILE: pkg/download/engine/inject/xhr/tls_fingerprint.go
  type Fingerprint (line 5) | type Fingerprint
  constant FingerprintMagicKey (line 8) | FingerprintMagicKey = "__gopeed_xhr_fingerprint"
  constant fingerprintChrome (line 10) | fingerprintChrome  = "chrome"
  constant fingerprintFirefox (line 11) | fingerprintFirefox = "firefox"
  constant fingerprintSafari (line 12) | fingerprintSafari  = "safari"
  function setFingerprint (line 15) | func setFingerprint(client *req.Client, fingerprint string) {

FILE: pkg/download/engine/polyfill/out/index.js
  function c (line 1) | function c(e){return e.map((function(e){if(e.buffer instanceof ArrayBuff...
  function h (line 1) | function h(e,r){r=r||{};var o=new t;return c(e).forEach((function(e){o.a...
  function d (line 1) | function d(e,t){return new o(c(e),t||{})}
  function b (line 1) | function b(){var t=!!i.ActiveXObject||"-ms-scroll-limit"in document.docu...
  function t (line 1) | function t(e){for(var t=new Array(e.byteLength),r=new Uint8Array(e),o=t....
  function o (line 1) | function o(e){for(var t="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstu...
  function t (line 1) | function t(){}
  function u (line 1) | function u(e){return Object.prototype.toString.call(e).slice(8,-1)}
  function f (line 1) | function f(e,t){return"object"==typeof e&&Object.prototype.isPrototypeOf...
  function c (line 1) | function c(e){return t=l,r=u(e),-1!==t.indexOf(r)||f(i.ArrayBuffer,e);va...
  function h (line 1) | function h(e,r){r=null==r?{}:r;for(var o=0,n=(e=e?e.slice():[]).length;o...
  function d (line 1) | function d(e,t,r){r=r||{};var o=h.call(this,e,r)||this;return o.name=t.r...
  function b (line 1) | function b(){if(!(this instanceof b))throw new TypeError("Failed to cons...
  function w (line 1) | function w(e,t,r){if(!(t instanceof h))throw new TypeError("Failed to ex...
  function v (line 1) | function v(e){return new Promise((function(t,r){e.onload=e.onerror=funct...
  function t (line 1) | function t(){}
  function r (line 1) | function r(){}
  function r (line 1) | function r(o){var n=t[o];if(void 0!==n)return n.exports;var i=t[o]={expo...
  method getRandomValues (line 1) | getRandomValues(e){for(let t=0,r=e.length;t<r;t++)e[t]=Math.floor(256*Ma...
  method randomUUID (line 1) | randomUUID(){return"10000000-1000-4000-8000-100000000000".replace(/[018]...
  function a (line 1) | function a(e){if("string"!=typeof e&&(e=String(e)),/[^a-z0-9\-#$%&'*+.^_...
  function s (line 1) | function s(e){return"string"!=typeof e&&(e=String(e)),e}
  function u (line 1) | function u(e){var t={next:function(){var t=e.shift();return{done:void 0=...
  function f (line 1) | function f(e){this.map={},e instanceof f?e.forEach((function(e,t){this.a...
  function l (line 1) | function l(e){if(!e._noBody)return e.bodyUsed?Promise.reject(new TypeErr...
  function c (line 1) | function c(e){return new Promise((function(t,r){e.onload=function(){t(e....
  function h (line 1) | function h(e){var t=new FileReader,r=c(t);return t.readAsArrayBuffer(e),r}
  function d (line 1) | function d(e){if(e.slice)return e.slice(0);var t=new Uint8Array(e.byteLe...
  function p (line 1) | function p(){return this.bodyUsed=!1,this._initBody=function(e){var t;th...
  function b (line 1) | function b(e,r){if(!(this instanceof b))throw new TypeError('Please use ...
  function w (line 1) | function w(e){var t=new FormData;return e.trim().split("&").forEach((fun...
  function m (line 1) | function m(e,t){if(!(this instanceof m))throw new TypeError('Please use ...
  function A (line 1) | function A(e,r){return new Promise((function(n,i){var u=new b(e,r);if(u....

FILE: pkg/download/engine/polyfill/src/crypto/index.js
  method getRandomValues (line 2) | getRandomValues(arr) {
  method randomUUID (line 8) | randomUUID() {

FILE: pkg/download/engine/util/util.go
  function ThrowTypeError (line 7) | func ThrowTypeError(vm *goja.Runtime, msg string) {
  function AssertError (line 11) | func AssertError[T error](err error) (t T, r bool) {
  function SafeGet (line 26) | func SafeGet[T any](vm *goja.Runtime, name string) T {

FILE: pkg/download/event.go
  type EventKey (line 3) | type EventKey
  constant EventKeyStart (line 6) | EventKeyStart    = "start"
  constant EventKeyPause (line 7) | EventKeyPause    = "pause"
  constant EventKeyProgress (line 8) | EventKeyProgress = "progress"
  constant EventKeyError (line 9) | EventKeyError    = "error"
  constant EventKeyDelete (line 10) | EventKeyDelete   = "delete"
  constant EventKeyDone (line 11) | EventKeyDone     = "done"
  constant EventKeyFinally (line 12) | EventKeyFinally  = "finally"
  type Event (line 15) | type Event struct

FILE: pkg/download/extension.go
  type ActivationEvent (line 36) | type ActivationEvent
  constant EventOnResolve (line 39) | EventOnResolve ActivationEvent = "onResolve"
  constant EventOnStart (line 40) | EventOnStart   ActivationEvent = "onStart"
  constant EventOnError (line 41) | EventOnError   ActivationEvent = "onError"
  constant EventOnDone (line 42) | EventOnDone    ActivationEvent = "onDone"
  method InstallExtensionByGit (line 45) | func (d *Downloader) InstallExtensionByGit(url string) (*Extension, erro...
  method InstallExtensionByFolder (line 49) | func (d *Downloader) InstallExtensionByFolder(path string, devMode bool)...
  method UpgradeCheckExtension (line 82) | func (d *Downloader) UpgradeCheckExtension(identity string) (newVersion ...
  method UpgradeExtension (line 104) | func (d *Downloader) UpgradeExtension(identity string) error {
  method UpdateExtensionSettings (line 119) | func (d *Downloader) UpdateExtensionSettings(identity string, settings m...
  method SwitchExtension (line 132) | func (d *Downloader) SwitchExtension(identity string, status bool) error {
  method DeleteExtension (line 141) | func (d *Downloader) DeleteExtension(identity string) error {
  method GetExtensions (line 168) | func (d *Downloader) GetExtensions() []*Extension {
  method GetExtension (line 172) | func (d *Downloader) GetExtension(identity string) (*Extension, error) {
  method getExtension (line 180) | func (d *Downloader) getExtension(identity string) *Extension {
  method fetchExtensionByGit (line 189) | func (d *Downloader) fetchExtensionByGit(url string, handler func(tempEx...
  method parseExtensionByPath (line 232) | func (d *Downloader) parseExtensionByPath(path string) (*Extension, erro...
  method triggerOnResolve (line 253) | func (d *Downloader) triggerOnResolve(req *base.Request) (res *base.Reso...
  method triggerOnStart (line 279) | func (d *Downloader) triggerOnStart(task *Task) {
  method triggerOnError (line 299) | func (d *Downloader) triggerOnError(task *Task, err error) {
  method triggerOnDone (line 311) | func (d *Downloader) triggerOnDone(task *Task) {
  function doTrigger (line 322) | func doTrigger[T any](d *Downloader, event ActivationEvent, req *base.Re...
  method ExtensionPath (line 400) | func (d *Downloader) ExtensionPath(ext *Extension) string {
  type Extension (line 407) | type Extension struct
    method validate (line 434) | func (e *Extension) validate() error {
    method buildIdentity (line 447) | func (e *Extension) buildIdentity() string {
    method buildInstallUrl (line 454) | func (e *Extension) buildInstallUrl() string {
    method update (line 472) | func (e *Extension) update(newExt *Extension) error {
  type Repository (line 528) | type Repository struct
  type Script (line 533) | type Script struct
    method match (line 542) | func (s *Script) match(event ActivationEvent, req *base.Request) bool {
  type Match (line 569) | type Match struct
  type SettingType (line 576) | type SettingType
  constant SettingTypeString (line 579) | SettingTypeString  SettingType = "string"
  constant SettingTypeNumber (line 580) | SettingTypeNumber  SettingType = "number"
  constant SettingTypeBoolean (line 581) | SettingTypeBoolean SettingType = "boolean"
  type Setting (line 584) | type Setting struct
  type Option (line 597) | type Option struct
  type Instance (line 603) | type Instance struct
  type InstanceEvents (line 611) | type InstanceEvents
    method register (line 613) | func (h InstanceEvents) register(name ActivationEvent, fn goja.Callabl...
    method OnResolve (line 617) | func (h InstanceEvents) OnResolve(fn goja.Callable) {
    method OnStart (line 621) | func (h InstanceEvents) OnStart(fn goja.Callable) {
    method OnError (line 625) | func (h InstanceEvents) OnError(fn goja.Callable) {
    method OnDone (line 629) | func (h InstanceEvents) OnDone(fn goja.Callable) {
  type ExtensionInfo (line 633) | type ExtensionInfo struct
  function NewExtensionInfo (line 641) | func NewExtensionInfo(ext *Extension) *ExtensionInfo {
  type InstanceLogger (line 651) | type InstanceLogger struct
    method Debug (line 657) | func (l *InstanceLogger) Debug(msg ...goja.Value) {
    method Info (line 663) | func (l *InstanceLogger) Info(msg ...goja.Value) {
    method Warn (line 667) | func (l *InstanceLogger) Warn(msg ...goja.Value) {
    method Error (line 671) | func (l *InstanceLogger) Error(msg ...goja.Value) {
    method append (line 675) | func (l *InstanceLogger) append(msg ...goja.Value) string {
  function newInstanceLogger (line 683) | func newInstanceLogger(extension *Extension, logger *logger.Logger) *Ins...
  type OnResolveContext (line 691) | type OnResolveContext struct
  type OnStartContext (line 696) | type OnStartContext struct
  type OnErrorContext (line 700) | type OnErrorContext struct
  type OnDoneContext (line 705) | type OnDoneContext struct
  type ExtensionTask (line 712) | type ExtensionTask struct
    method Continue (line 728) | func (t *ExtensionTask) Continue() error {
    method Pause (line 734) | func (t *ExtensionTask) Pause() error {
  function NewExtensionTask (line 718) | func NewExtensionTask(download *Downloader, task *Task) *ExtensionTask {
  function parseSettings (line 740) | func parseSettings(settings []*Setting) map[string]any {
  function tryParse (line 752) | func tryParse(val any, settingType SettingType) any {
  type ContextStorage (line 776) | type ContextStorage struct
    method Get (line 781) | func (s *ContextStorage) Get(key string) any {
    method Set (line 789) | func (s *ContextStorage) Set(key string, value string) {
    method Remove (line 795) | func (s *ContextStorage) Remove(key string) {
    method Keys (line 801) | func (s *ContextStorage) Keys() []string {
    method Clear (line 810) | func (s *ContextStorage) Clear() {
    method getRawData (line 814) | func (s *ContextStorage) getRawData() map[string]string {

FILE: pkg/download/extension_test.go
  function TestDownloader_InstallExtensionByFolder (line 15) | func TestDownloader_InstallExtensionByFolder(t *testing.T) {
  function TestDownloader_InstallExtensionByFolderDevMode (line 32) | func TestDownloader_InstallExtensionByFolderDevMode(t *testing.T) {
  function TestDownloader_InstallExtensionByGit (line 49) | func TestDownloader_InstallExtensionByGit(t *testing.T) {
  function TestDownloader_InstallExtensionByGitSimple (line 66) | func TestDownloader_InstallExtensionByGitSimple(t *testing.T) {
  function TestDownloader_InstallExtensionByGitFull (line 83) | func TestDownloader_InstallExtensionByGitFull(t *testing.T) {
  function TestDownloader_UpgradeExtension (line 100) | func TestDownloader_UpgradeExtension(t *testing.T) {
  function TestDownloader_Extension_OnStart (line 170) | func TestDownloader_Extension_OnStart(t *testing.T) {
  function TestDownloader_Extension_OnError (line 219) | func TestDownloader_Extension_OnError(t *testing.T) {
  function TestDownloader_Extension_OnDone (line 257) | func TestDownloader_Extension_OnDone(t *testing.T) {
  function TestDownloader_Extension_Errors (line 297) | func TestDownloader_Extension_Errors(t *testing.T) {
  function TestDownloader_Extension_Settings (line 349) | func TestDownloader_Extension_Settings(t *testing.T) {
  function TestDownloader_ExtensionStorage (line 387) | func TestDownloader_ExtensionStorage(t *testing.T) {
  function TestDownloader_SwitchExtension (line 404) | func TestDownloader_SwitchExtension(t *testing.T) {
  function TestDownloader_DeleteExtension (line 422) | func TestDownloader_DeleteExtension(t *testing.T) {
  function TestDownloader_Extension_Logger (line 439) | func TestDownloader_Extension_Logger(t *testing.T) {
  function setupDownloader (line 450) | func setupDownloader(fn func(downloader *Downloader)) {

FILE: pkg/download/extract.go
  type ArchivePartInfo (line 58) | type ArchivePartInfo struct
  type ExtractProgressCallback (line 72) | type ExtractProgressCallback
  function newZipFormat (line 77) | func newZipFormat() archives.Zip {
  function isArchiveFile (line 85) | func isArchiveFile(filename string) bool {
  type archiveInfo (line 97) | type archiveInfo struct
  function openArchive (line 105) | func openArchive(archivePath string, password string) (*archiveInfo, err...
  function createExtractionHandler (line 151) | func createExtractionHandler(destDir string, totalFiles int, progressCal...
  function extractArchive (line 167) | func extractArchive(archivePath string, destDir string, password string,...
  function createCountingHandler (line 247) | func createCountingHandler(count *int) func(ctx context.Context, fileInf...
  function countArchiveFiles (line 257) | func countArchiveFiles(archivePath string, password string) (int, error) {
  function extractFile (line 284) | func extractFile(ctx context.Context, fileInfo archives.FileInfo, destDi...
  function isMultiPartArchive (line 330) | func isMultiPartArchive(filename string) bool {
  function getArchivePartInfo (line 336) | func getArchivePartInfo(filename string) ArchivePartInfo {
  function parsePartNumber (line 385) | func parsePartNumber(s string, partNum *int) (int, error) {
  function determineFirstPartPath (line 405) | func determineFirstPartPath(dir, baseName, pattern string) string {
  function isFirstPart (line 432) | func isFirstPart(filename string) bool {
  function GetMultiPartArchiveBaseName (line 449) | func GetMultiPartArchiveBaseName(filename string) string {
  function extractMultiPartArchive (line 458) | func extractMultiPartArchive(firstPartPath string, destDir string, passw...

FILE: pkg/download/extract_7z.go
  function extractSevenZipMultiPart (line 15) | func extractSevenZipMultiPart(firstPartPath string, destDir string, pass...
  function extractSevenZipFile (line 77) | func extractSevenZipFile(f *sevenzip.File, destPath string) error {

FILE: pkg/download/extract_queue.go
  type ExtractionJob (line 8) | type ExtractionJob struct
    method Wait (line 27) | func (j *ExtractionJob) Wait() {
  function NewExtractionJob (line 18) | func NewExtractionJob(id string, execute func()) *ExtractionJob {
  type ExtractionQueue (line 33) | type ExtractionQueue struct
    method Start (line 52) | func (q *ExtractionQueue) Start() {
    method Stop (line 68) | func (q *ExtractionQueue) Stop() {
    method Enqueue (line 84) | func (q *ExtractionQueue) Enqueue(job *ExtractionJob) *ExtractionJob {
    method EnqueueAndWait (line 100) | func (q *ExtractionQueue) EnqueueAndWait(job *ExtractionJob) {
    method QueueLength (line 106) | func (q *ExtractionQueue) QueueLength() int {
    method IsRunning (line 113) | func (q *ExtractionQueue) IsRunning() bool {
    method HasPendingJob (line 120) | func (q *ExtractionQueue) HasPendingJob(id string) bool {
    method RemovePendingJob (line 135) | func (q *ExtractionQueue) RemovePendingJob(id string) bool {
    method worker (line 152) | func (q *ExtractionQueue) worker() {
  function NewExtractionQueue (line 43) | func NewExtractionQueue() *ExtractionQueue {
  function GetExtractionQueue (line 193) | func GetExtractionQueue() *ExtractionQueue {

FILE: pkg/download/extract_queue_test.go
  function TestNewExtractionQueue (line 10) | func TestNewExtractionQueue(t *testing.T) {
  function TestExtractionQueue_StartStop (line 29) | func TestExtractionQueue_StartStop(t *testing.T) {
  function TestExtractionQueue_EnqueueSingleJob (line 57) | func TestExtractionQueue_EnqueueSingleJob(t *testing.T) {
  function TestExtractionQueue_EnqueueAndWait (line 75) | func TestExtractionQueue_EnqueueAndWait(t *testing.T) {
  function TestExtractionQueue_FIFOOrder (line 92) | func TestExtractionQueue_FIFOOrder(t *testing.T) {
  function TestExtractionQueue_SequentialExecution (line 138) | func TestExtractionQueue_SequentialExecution(t *testing.T) {
  function TestExtractionQueue_QueueLength (line 177) | func TestExtractionQueue_QueueLength(t *testing.T) {
  function TestExtractionQueue_HasPendingJob (line 218) | func TestExtractionQueue_HasPendingJob(t *testing.T) {
  function TestExtractionQueue_RemovePendingJob (line 253) | func TestExtractionQueue_RemovePendingJob(t *testing.T) {
  function TestExtractionQueue_StopDiscardsPendingJobs (line 305) | func TestExtractionQueue_StopDiscardsPendingJobs(t *testing.T) {
  function TestExtractionQueue_EnqueueAfterShutdown (line 350) | func TestExtractionQueue_EnqueueAfterShutdown(t *testing.T) {
  function TestNewExtractionJob (line 375) | func TestNewExtractionJob(t *testing.T) {
  function TestExtractionJob_Wait (line 389) | func TestExtractionJob_Wait(t *testing.T) {
  function TestExtractionQueue_NilExecuteFunction (line 418) | func TestExtractionQueue_NilExecuteFunction(t *testing.T) {
  function TestExtractionQueue_ConcurrentEnqueue (line 435) | func TestExtractionQueue_ConcurrentEnqueue(t *testing.T) {
  function TestExtractionQueue_Restart (line 466) | func TestExtractionQueue_Restart(t *testing.T) {
  function TestGetExtractionQueue (line 496) | func TestGetExtractionQueue(t *testing.T) {
  function TestExtractionQueue_LongRunningJob (line 515) | func TestExtractionQueue_LongRunningJob(t *testing.T) {

FILE: pkg/download/extract_rar.go
  function extractRarMultiPart (line 12) | func extractRarMultiPart(firstPartPath string, destDir string, password ...

FILE: pkg/download/extract_test.go
  function TestIsArchiveFile (line 17) | func TestIsArchiveFile(t *testing.T) {
  function TestExtractArchive_Zip (line 63) | func TestExtractArchive_Zip(t *testing.T) {
  function TestExtractArchive_NonArchive (line 107) | func TestExtractArchive_NonArchive(t *testing.T) {
  function createTestZip (line 131) | func createTestZip(path string) error {
  function TestExtractArchive_Progress (line 163) | func TestExtractArchive_Progress(t *testing.T) {
  function createTestZipWithMultipleFiles (line 222) | func createTestZipWithMultipleFiles(path string, numFiles int) error {
  function createTestZipWithChineseFilenames (line 249) | func createTestZipWithChineseFilenames(path string) error {
  function TestOpenArchive_NonExistentFile (line 311) | func TestOpenArchive_NonExistentFile(t *testing.T) {
  function TestOpenArchive_InvalidFormat (line 318) | func TestOpenArchive_InvalidFormat(t *testing.T) {
  function TestOpenArchive_WithPassword (line 340) | func TestOpenArchive_WithPassword(t *testing.T) {
  function TestExtractArchive_NonExistentFile (line 365) | func TestExtractArchive_NonExistentFile(t *testing.T) {
  function TestExtractArchive_WithPassword (line 379) | func TestExtractArchive_WithPassword(t *testing.T) {
  function TestExtractArchive_Gzip (line 406) | func TestExtractArchive_Gzip(t *testing.T) {
  function createTestGzip (line 469) | func createTestGzip(path string, content string) error {
  function TestCountArchiveFiles (line 485) | func TestCountArchiveFiles(t *testing.T) {
  function TestCountArchiveFiles_NonExistent (line 508) | func TestCountArchiveFiles_NonExistent(t *testing.T) {
  function TestCountArchiveFiles_Gzip (line 515) | func TestCountArchiveFiles_Gzip(t *testing.T) {
  function TestExtractArchive_ProgressWithZeroFiles (line 539) | func TestExtractArchive_ProgressWithZeroFiles(t *testing.T) {
  function createEmptyZip (line 568) | func createEmptyZip(path string) error {
  function TestExtractArchive_WithDirectories (line 579) | func TestExtractArchive_WithDirectories(t *testing.T) {
  function createTestZipWithDirectories (line 611) | func createTestZipWithDirectories(path string) error {
  function TestExtractArchive_NilProgressCallback (line 643) | func TestExtractArchive_NilProgressCallback(t *testing.T) {
  function TestExtractArchive_GzipUppercase (line 664) | func TestExtractArchive_GzipUppercase(t *testing.T) {
  function TestExtractArchive_PathTraversalPrevention (line 691) | func TestExtractArchive_PathTraversalPrevention(t *testing.T) {
  function createMaliciousZip (line 719) | func createMaliciousZip(path string) error {
  function TestIsArchiveFile_AdditionalFormats (line 752) | func TestIsArchiveFile_AdditionalFormats(t *testing.T) {
  function TestExtractArchive_DestDirCreation (line 779) | func TestExtractArchive_DestDirCreation(t *testing.T) {
  function TestExtractArchive_ProgressCallbackValues (line 810) | func TestExtractArchive_ProgressCallbackValues(t *testing.T) {
  function TestCountArchiveFiles_WithDirectories (line 846) | func TestCountArchiveFiles_WithDirectories(t *testing.T) {
  function TestOpenArchive_FileStatError (line 870) | func TestOpenArchive_FileStatError(t *testing.T) {
  function TestExtractArchive_FilePermissions (line 905) | func TestExtractArchive_FilePermissions(t *testing.T) {
  function TestExtractArchive_TarGz (line 934) | func TestExtractArchive_TarGz(t *testing.T) {
  function createTestTarGz (line 977) | func createTestTarGz(path string) error {
  function TestCountArchiveFiles_TarGz (line 1021) | func TestCountArchiveFiles_TarGz(t *testing.T) {
  function TestExtractArchive_LargeFileCount (line 1044) | func TestExtractArchive_LargeFileCount(t *testing.T) {
  function TestExtractArchive_GzipNoExtension (line 1080) | func TestExtractArchive_GzipNoExtension(t *testing.T) {
  function TestExtractArchive_ReadOnlyDestDir (line 1107) | func TestExtractArchive_ReadOnlyDestDir(t *testing.T) {
  function TestSupportedArchiveExtensions (line 1143) | func TestSupportedArchiveExtensions(t *testing.T) {
  function TestExtractArchive_Tar (line 1153) | func TestExtractArchive_Tar(t *testing.T) {
  function createTestTar (line 1179) | func createTestTar(path string) error {
  function TestCountArchiveFiles_Tar (line 1205) | func TestCountArchiveFiles_Tar(t *testing.T) {
  function TestOpenArchive_ValidArchive (line 1227) | func TestOpenArchive_ValidArchive(t *testing.T) {
  function TestExtractArchive_NestedDirectoryStructure (line 1263) | func TestExtractArchive_NestedDirectoryStructure(t *testing.T) {
  function createDeeplyNestedZip (line 1289) | func createDeeplyNestedZip(path string) error {
  function TestExtractArchive_EmptyFileName (line 1310) | func TestExtractArchive_EmptyFileName(t *testing.T) {
  function TestExtractArchive_ProgressTracking (line 1331) | func TestExtractArchive_ProgressTracking(t *testing.T) {
  function TestArchiveInfo_Fields (line 1372) | func TestArchiveInfo_Fields(t *testing.T) {
  function TestIsMultiPartArchive (line 1402) | func TestIsMultiPartArchive(t *testing.T) {
  function TestGetArchivePartInfo_7z (line 1453) | func TestGetArchivePartInfo_7z(t *testing.T) {
  function TestGetArchivePartInfo_RarNewStyle (line 1482) | func TestGetArchivePartInfo_RarNewStyle(t *testing.T) {
  function TestGetArchivePartInfo_RarOldStyle (line 1511) | func TestGetArchivePartInfo_RarOldStyle(t *testing.T) {
  function TestGetArchivePartInfo_ZipMultiPart (line 1536) | func TestGetArchivePartInfo_ZipMultiPart(t *testing.T) {
  function TestGetArchivePartInfo_ZipSplit (line 1564) | func TestGetArchivePartInfo_ZipSplit(t *testing.T) {
  function TestGetArchivePartInfo_NonMultiPart (line 1592) | func TestGetArchivePartInfo_NonMultiPart(t *testing.T) {
  function TestIsFirstPart (line 1611) | func TestIsFirstPart(t *testing.T) {
  function TestGetMultiPartArchiveBaseName (line 1652) | func TestGetMultiPartArchiveBaseName(t *testing.T) {
  function TestIsArchiveFile_IncludesMultiPart (line 1681) | func TestIsArchiveFile_IncludesMultiPart(t *testing.T) {
  function TestArchivePartInfo_PatternField (line 1705) | func TestArchivePartInfo_PatternField(t *testing.T) {
  function TestArchivePartInfo_FirstPartPath (line 1731) | func TestArchivePartInfo_FirstPartPath(t *testing.T) {
  function TestMultiPartFileReader_Basic (line 1765) | func TestMultiPartFileReader_Basic(t *testing.T) {
  function TestMultiPartFileReader_ReadAt (line 1801) | func TestMultiPartFileReader_ReadAt(t *testing.T) {
  function TestMultiPartFileReader_ReadAtEOF (line 1853) | func TestMultiPartFileReader_ReadAtEOF(t *testing.T) {
  function TestMultiPartFileReader_Close (line 1879) | func TestMultiPartFileReader_Close(t *testing.T) {
  function TestMultiPartFileReader_InitError (line 1908) | func TestMultiPartFileReader_InitError(t *testing.T) {
  function TestFindZipMultiParts (line 1928) | func TestFindZipMultiParts(t *testing.T) {
  function TestFindZipMultiParts_NoParts (line 1962) | func TestFindZipMultiParts_NoParts(t *testing.T) {
  function TestFindZipMultiParts_SinglePart (line 1977) | func TestFindZipMultiParts_SinglePart(t *testing.T) {
  function TestExtractMultiPartArchive_NonExistentFile (line 2001) | func TestExtractMultiPartArchive_NonExistentFile(t *testing.T) {
  function TestExtractZipMultiPart_InvalidArchive (line 2024) | func TestExtractZipMultiPart_InvalidArchive(t *testing.T) {
  function TestExtractRarMultiPart_NonExistent (line 2046) | func TestExtractRarMultiPart_NonExistent(t *testing.T) {
  function TestExtractSevenZipMultiPart_NonExistent (line 2061) | func TestExtractSevenZipMultiPart_NonExistent(t *testing.T) {
  function TestExtractSevenZipMultiPart_Invalid (line 2076) | func TestExtractSevenZipMultiPart_Invalid(t *testing.T) {
  function TestDetermineFirstPartPath (line 2097) | func TestDetermineFirstPartPath(t *testing.T) {
  function TestParsePartNumber (line 2167) | func TestParsePartNumber(t *testing.T) {
  function TestMultiPartArchivePatterns (line 2196) | func TestMultiPartArchivePatterns(t *testing.T) {
  function TestExtractRarMultiPart_DestDirCreation (line 2249) | func TestExtractRarMultiPart_DestDirCreation(t *testing.T) {
  function TestGetArchivePartInfo_RarOldStyleWithRar (line 2273) | func TestGetArchivePartInfo_RarOldStyleWithRar(t *testing.T) {
  function TestExtractSevenZipMultiPart_DestDirCreation (line 2305) | func TestExtractSevenZipMultiPart_DestDirCreation(t *testing.T) {
  function TestMultiPartFileReader_SpanningRead (line 2329) | func TestMultiPartFileReader_SpanningRead(t *testing.T) {
  function TestExtractZipMultiPart_DestDirCreation (line 2368) | func TestExtractZipMultiPart_DestDirCreation(t *testing.T) {
  function TestExtractZipMultiPart_Progress (line 2413) | func TestExtractZipMultiPart_Progress(t *testing.T) {
  function TestExtractArchive_ChineseFilenames (line 2452) | func TestExtractArchive_ChineseFilenames(t *testing.T) {

FILE: pkg/download/extract_zip.go
  function extractZipMultiPart (line 16) | func extractZipMultiPart(firstPartPath string, destDir string, password ...
  function findZipMultiParts (line 59) | func findZipMultiParts(firstPartPath string) ([]string, error) {
  type multiPartFileReader (line 89) | type multiPartFileReader struct
    method init (line 101) | func (m *multiPartFileReader) init() error {
    method Size (line 133) | func (m *multiPartFileReader) Size() int64 {
    method ReadAt (line 140) | func (m *multiPartFileReader) ReadAt(p []byte, off int64) (n int, err ...
    method Close (line 183) | func (m *multiPartFileReader) Close() error {
  function newMultiPartFileReader (line 97) | func newMultiPartFileReader(parts []string) *multiPartFileReader {

FILE: pkg/download/model.go
  type ResolveResult (line 18) | type ResolveResult struct
  type Task (line 23) | type Task struct
    method Name (line 58) | func (t *Task) Name() string {
    method MarshalJSON (line 86) | func (t *Task) MarshalJSON() ([]byte, error) {
    method updateStatus (line 98) | func (t *Task) updateStatus(status base.Status) {
    method clone (line 103) | func (t *Task) clone() *Task {
    method updateSpeed (line 107) | func (t *Task) updateSpeed(downloaded int64, usedTime float64) int64 {
    method updateUploadSpeed (line 111) | func (t *Task) updateUploadSpeed(downloaded int64, usedTime float64) i...
  function NewTask (line 43) | func NewTask() *Task {
  function calcSpeed (line 115) | func calcSpeed(speedArr *[]int64, downloaded int64, usedTime float64) in...
  type TaskFilter (line 138) | type TaskFilter struct
    method IsEmpty (line 144) | func (f *TaskFilter) IsEmpty() bool {
  type DownloaderConfig (line 148) | type DownloaderConfig struct
    method Init (line 162) | func (cfg *DownloaderConfig) Init() *DownloaderConfig {

FILE: pkg/download/model_test.go
  function TestCalcSpeedResetOnRollback (line 5) | func TestCalcSpeedResetOnRollback(t *testing.T) {

FILE: pkg/download/script.go
  type ScriptEvent (line 13) | type ScriptEvent
  constant ScriptEventDownloadDone (line 16) | ScriptEventDownloadDone  ScriptEvent = "DOWNLOAD_DONE"
  constant ScriptEventDownloadError (line 17) | ScriptEventDownloadError ScriptEvent = "DOWNLOAD_ERROR"
  type ScriptData (line 21) | type ScriptData struct
  type ScriptPayload (line 28) | type ScriptPayload struct
  method getScriptPaths (line 33) | func (d *Downloader) getScriptPaths() []string {
  method executeScriptAtPath (line 57) | func (d *Downloader) executeScriptAtPath(scriptPath string, data *Script...
  method triggerScripts (line 130) | func (d *Downloader) triggerScripts(event ScriptEvent, task *Task, err e...
  method executeScripts (line 147) | func (d *Downloader) executeScripts(paths []string, data *ScriptData) {

FILE: pkg/download/script_test.go
  function TestScript_NoScriptConfigured (line 13) | func TestScript_NoScriptConfigured(t *testing.T) {
  function TestScript_GetScriptPaths_EmptyConfig (line 25) | func TestScript_GetScriptPaths_EmptyConfig(t *testing.T) {
  function TestScript_GetScriptPaths_NoScriptConfig (line 34) | func TestScript_GetScriptPaths_NoScriptConfig(t *testing.T) {
  function TestScript_GetScriptPaths_DisabledScript (line 47) | func TestScript_GetScriptPaths_DisabledScript(t *testing.T) {
  function TestScript_GetScriptPaths_EmptyPaths (line 63) | func TestScript_GetScriptPaths_EmptyPaths(t *testing.T) {
  function TestScript_GetScriptPaths_WithEmptyStrings (line 79) | func TestScript_GetScriptPaths_WithEmptyStrings(t *testing.T) {
  function TestScript_ExecuteScriptAtPath_EmptyPath (line 98) | func TestScript_ExecuteScriptAtPath_EmptyPath(t *testing.T) {
  function TestScript_ExecuteScriptAtPath_NonExistentFile (line 115) | func TestScript_ExecuteScriptAtPath_NonExistentFile(t *testing.T) {
  function createDownloadDoneTask (line 129) | func createDownloadDoneTask(t *testing.T, downloadDir, fileName string) ...
  function waitForFile (line 162) | func waitForFile(t *testing.T, path string, timeout time.Duration) {
  function getTestScriptPath (line 174) | func getTestScriptPath(t *testing.T, name string) string {
  function ensureScriptExecutable (line 183) | func ensureScriptExecutable(t *testing.T, scriptPath string) {
  function setupScriptTest (line 193) | func setupScriptTest(t *testing.T, fn func(downloader *Downloader)) {

FILE: pkg/download/script_unix_test.go
  function TestScript_TriggerOnDone_MoveFile (line 16) | func TestScript_TriggerOnDone_MoveFile(t *testing.T) {
  function TestScript_MultipleScripts (line 45) | func TestScript_MultipleScripts(t *testing.T) {
  function TestScript_EnvironmentVariables (line 74) | func TestScript_EnvironmentVariables(t *testing.T) {

FILE: pkg/download/script_windows_test.go
  function TestScript_TriggerOnDone_MoveFile (line 16) | func TestScript_TriggerOnDone_MoveFile(t *testing.T) {
  function TestScript_MultipleScripts (line 44) | func TestScript_MultipleScripts(t *testing.T) {
  function TestScript_EnvironmentVariables (line 71) | func TestScript_EnvironmentVariables(t *testing.T) {

FILE: pkg/download/storage.go
  type Storage (line 13) | type Storage interface
  function changeValue (line 25) | func changeValue(p any, v any) {
  type MemStorage (line 49) | type MemStorage struct
    method Setup (line 61) | func (n *MemStorage) Setup(buckets []string) error {
    method Put (line 72) | func (n *MemStorage) Put(bucket string, key string, v any) error {
    method Get (line 81) | func (n *MemStorage) Get(bucket string, key string, v any) (bool, erro...
    method List (line 91) | func (n *MemStorage) List(bucket string, v any) error {
    method Pop (line 103) | func (n *MemStorage) Pop(bucket string, key string, v any) error {
    method Delete (line 112) | func (n *MemStorage) Delete(bucket string, key string) error {
    method Close (line 119) | func (n *MemStorage) Close() error {
    method Clear (line 123) | func (n *MemStorage) Clear() error {
  function NewMemStorage (line 54) | func NewMemStorage() *MemStorage {
  constant dbFile (line 131) | dbFile = "gopeed.db"
  type BoltStorage (line 134) | type BoltStorage struct
    method Setup (line 154) | func (b *BoltStorage) Setup(buckets []string) error {
    method Put (line 166) | func (b *BoltStorage) Put(bucket string, key string, v any) error {
    method Get (line 177) | func (b *BoltStorage) Get(bucket string, key string, v any) (bool, err...
    method List (line 196) | func (b *BoltStorage) List(bucket string, v any) error {
    method Pop (line 219) | func (b *BoltStorage) Pop(bucket string, key string, v any) error {
    method Delete (line 236) | func (b *BoltStorage) Delete(bucket string, key string) error {
    method Close (line 243) | func (b *BoltStorage) Close() error {
    method Clear (line 247) | func (b *BoltStorage) Clear() error {
  function NewBoltStorage (line 139) | func NewBoltStorage(dir string) *BoltStorage {

FILE: pkg/download/webhook.go
  constant webhookTimeout (line 15) | webhookTimeout = 10 * time.Second
  type WebhookEvent (line 19) | type WebhookEvent
  constant WebhookEventDownloadDone (line 22) | WebhookEventDownloadDone  WebhookEvent = "DOWNLOAD_DONE"
  constant WebhookEventDownloadError (line 23) | WebhookEventDownloadError WebhookEvent = "DOWNLOAD_ERROR"
  type WebhookData (line 27) | type WebhookData struct
  type WebhookPayload (line 34) | type WebhookPayload struct
  method getWebhookUrls (line 40) | func (d *Downloader) getWebhookUrls() []string {
  method sendWebhookToUrl (line 99) | func (d *Downloader) sendWebhookToUrl(url string, data *WebhookData) (in...
  method triggerWebhooks (line 130) | func (d *Downloader) triggerWebhooks(event WebhookEvent, task *Task, err...
  method sendWebhooks (line 147) | func (d *Downloader) sendWebhooks(urls []string, data *WebhookData) {
  method SendTestWebhook (line 169) | func (d *Downloader) SendTestWebhook() error {
  method TestWebhookUrl (line 189) | func (d *Downloader) TestWebhookUrl(url string) error {

FILE: pkg/download/webhook_test.go
  function TestWebhook_TriggerOnDone (line 30) | func TestWebhook_TriggerOnDone(t *testing.T) {
  function TestWebhook_TriggerOnError (line 85) | func TestWebhook_TriggerOnError(t *testing.T) {
  function TestWebhook_SendTestWebhook (line 130) | func TestWebhook_SendTestWebhook(t *testing.T) {
  function TestWebhook_NoWebhookConfigured (line 175) | func TestWebhook_NoWebhookConfigured(t *testing.T) {
  function TestWebhook_MultipleUrls (line 193) | func TestWebhook_MultipleUrls(t *testing.T) {
  function TestWebhook_TestWebhookFailsOnNon200 (line 232) | func TestWebhook_TestWebhookFailsOnNon200(t *testing.T) {
  function TestWebhook_TestWebhookFailsOn201 (line 256) | func TestWebhook_TestWebhookFailsOn201(t *testing.T) {
  function TestWebhook_TestWebhookUrl (line 280) | func TestWebhook_TestWebhookUrl(t *testing.T) {
  function TestWebhook_TestWebhookUrlEmpty (line 314) | func TestWebhook_TestWebhookUrlEmpty(t *testing.T) {
  function TestWebhook_GetWebhookUrls_EmptyConfig (line 324) | func TestWebhook_GetWebhookUrls_EmptyConfig(t *testing.T) {
  function TestWebhook_GetWebhookUrls_NoExtraField (line 333) | func TestWebhook_GetWebhookUrls_NoExtraField(t *testing.T) {
  function TestWebhook_GetWebhookUrls_NoWebhookUrlsKey (line 346) | func TestWebhook_GetWebhookUrls_NoWebhookUrlsKey(t *testing.T) {
  function TestWebhook_GetWebhookUrls_StringSlice (line 359) | func TestWebhook_GetWebhookUrls_StringSlice(t *testing.T) {
  function TestWebhook_GetWebhookUrls_InterfaceSlice (line 378) | func TestWebhook_GetWebhookUrls_InterfaceSlice(t *testing.T) {
  function TestWebhook_GetWebhookUrls_EmptyStringSlice (line 397) | func TestWebhook_GetWebhookUrls_EmptyStringSlice(t *testing.T) {
  function TestWebhook_GetWebhookUrls_InterfaceSliceWithEmptyStrings (line 411) | func TestWebhook_GetWebhookUrls_InterfaceSliceWithEmptyStrings(t *testin...
  function TestWebhook_GetWebhookUrls_InterfaceSliceMixedTypes (line 427) | func TestWebhook_GetWebhookUrls_InterfaceSliceMixedTypes(t *testing.T) {
  function TestWebhook_GetWebhookUrls_InvalidType (line 446) | func TestWebhook_GetWebhookUrls_InvalidType(t *testing.T) {
  function TestWebhook_GetWebhookUrls_DisabledWebhook (line 459) | func TestWebhook_GetWebhookUrls_DisabledWebhook(t *testing.T) {
  function TestWebhook_SendWebhookToUrl_Success (line 475) | func TestWebhook_SendWebhookToUrl_Success(t *testing.T) {
  function TestWebhook_SendWebhookToUrl_EmptyUrl (line 528) | func TestWebhook_SendWebhookToUrl_EmptyUrl(t *testing.T) {
  function TestWebhook_SendWebhookToUrl_InvalidUrl (line 545) | func TestWebhook_SendWebhookToUrl_InvalidUrl(t *testing.T) {
  function TestWebhook_SendWebhookToUrl_NonExistentHost (line 559) | func TestWebhook_SendWebhookToUrl_NonExistentHost(t *testing.T) {
  function TestWebhook_SendWebhookToUrl_Timeout (line 573) | func TestWebhook_SendWebhookToUrl_Timeout(t *testing.T) {
  function TestWebhook_SendWebhookToUrl_VariousStatusCodes (line 594) | func TestWebhook_SendWebhookToUrl_VariousStatusCodes(t *testing.T) {
  function TestWebhook_TriggerWebhooks_EmptyUrlSkipped (line 632) | func TestWebhook_TriggerWebhooks_EmptyUrlSkipped(t *testing.T) {
  function TestWebhook_WebhookDataStructure (line 662) | func TestWebhook_WebhookDataStructure(t *testing.T) {
  function TestWebhook_SendTestWebhook_EmptyUrls (line 717) | func TestWebhook_SendTestWebhook_EmptyUrls(t *testing.T) {
  function TestWebhook_SendTestWebhook_MixedResults (line 731) | func TestWebhook_SendTestWebhook_MixedResults(t *testing.T) {
  function TestWebhook_TestWebhookUrl_InvalidUrl (line 760) | func TestWebhook_TestWebhookUrl_InvalidUrl(t *testing.T) {
  function TestWebhook_TestWebhookUrl_VerifyTestPayload (line 769) | func TestWebhook_TestWebhookUrl_VerifyTestPayload(t *testing.T) {
  function setupWebhookTest (line 817) | func setupWebhookTest(t *testing.T, fn func(downloader *Downloader)) {

FILE: pkg/protocol/bt/model.go
  type ReqExtra (line 3) | type ReqExtra struct
  type Stats (line 8) | type Stats struct

FILE: pkg/protocol/ed2k/model.go
  type Stats (line 3) | type Stats struct

FILE: pkg/protocol/http/model.go
  type ReqExtra (line 3) | type ReqExtra struct
  type OptsExtra (line 9) | type OptsExtra struct
  type Stats (line 27) | type Stats struct
  type StatsConnection (line 31) | type StatsConnection struct

FILE: pkg/rest/api.go
  function Info (line 15) | func Info(w http.ResponseWriter, r *http.Request) {
  function Resolve (line 26) | func Resolve(w http.ResponseWriter, r *http.Request) {
  function CreateTask (line 38) | func CreateTask(w http.ResponseWriter, r *http.Request) {
  function CreateTaskBatch (line 61) | func CreateTaskBatch(w http.ResponseWriter, r *http.Request) {
  function PatchTask (line 77) | func PatchTask(w http.ResponseWriter, r *http.Request) {
  function PauseTask (line 99) | func PauseTask(w http.ResponseWriter, r *http.Request) {
  function PauseTasks (line 113) | func PauseTasks(w http.ResponseWriter, r *http.Request) {
  function ContinueTask (line 127) | func ContinueTask(w http.ResponseWriter, r *http.Request) {
  function ContinueTasks (line 141) | func ContinueTasks(w http.ResponseWriter, r *http.Request) {
  function DeleteTask (line 155) | func DeleteTask(w http.ResponseWriter, r *http.Request) {
  function DeleteTasks (line 170) | func DeleteTasks(w http.ResponseWriter, r *http.Request) {
  function GetTask (line 185) | func GetTask(w http.ResponseWriter, r *http.Request) {
  function GetTasks (line 200) | func GetTasks(w http.ResponseWriter, r *http.Request) {
  function GetConfig (line 211) | func GetConfig(w http.ResponseWriter, r *http.Request) {
  function PutConfig (line 215) | func PutConfig(w http.ResponseWriter, r *http.Request) {
  function InstallExtension (line 226) | func InstallExtension(w http.ResponseWriter, r *http.Request) {
  function GetExtensions (line 246) | func GetExtensions(w http.ResponseWriter, r *http.Request) {
  function GetExtension (line 251) | func GetExtension(w http.ResponseWriter, r *http.Request) {
  function UpdateExtensionSettings (line 262) | func UpdateExtensionSettings(w http.ResponseWriter, r *http.Request) {
  function SwitchExtension (line 275) | func SwitchExtension(w http.ResponseWriter, r *http.Request) {
  function DeleteExtension (line 288) | func DeleteExtension(w http.ResponseWriter, r *http.Request) {
  function UpdateCheckExtension (line 298) | func UpdateCheckExtension(w http.ResponseWriter, r *http.Request) {
  function UpdateExtension (line 311) | func UpdateExtension(w http.ResponseWriter, r *http.Request) {
  function DoProxy (line 321) | func DoProxy(w http.ResponseWriter, r *http.Request) {
  function GetStats (line 357) | func GetStats(w http.ResponseWriter, r *http.Request) {
  function parseIdFilter (line 372) | func parseIdFilter(r *http.Request) (*download.TaskFilter, any) {
  function parseFilter (line 385) | func parseFilter(r *http.Request) (*download.TaskFilter, any) {
  function convertStatues (line 398) | func convertStatues(statues []string) []base.Status {
  function writeError (line 406) | func writeError(w http.ResponseWriter, msg string) {
  function getServerConfig (line 411) | func getServerConfig() *base.DownloaderStoreConfig {
  function TestWebhook (line 416) | func TestWebhook(w http.ResponseWriter, r *http.Request) {

FILE: pkg/rest/config.go
  type Config (line 3) | type Config struct

FILE: pkg/rest/gizp_middleware.go
  type gzipResponseWriter (line 10) | type gzipResponseWriter struct
    method Write (line 15) | func (g gzipResponseWriter) Write(b []byte) (int, error) {
  function gzipMiddleware (line 19) | func gzipMiddleware(next http.Handler) http.Handler {

FILE: pkg/rest/model/extension.go
  type InstallExtension (line 3) | type InstallExtension struct
  type UpdateExtensionSettings (line 8) | type UpdateExtensionSettings struct
  type SwitchExtension (line 12) | type SwitchExtension struct
  type UpdateCheckExtensionResp (line 16) | type UpdateCheckExtensionResp struct

FILE: pkg/rest/model/result.go
  type RespCode (line 3) | type RespCode
  constant CodeOk (line 6) | CodeOk RespCode = 0
  constant CodeError (line 8) | CodeError RespCode = 1000
  constant CodeUnauthorized (line 10) | CodeUnauthorized RespCode = 1001
  constant CodeInvalidParam (line 12) | CodeInvalidParam RespCode = 1002
  constant CodeTaskNotFound (line 14) | CodeTaskNotFound RespCode = 2001
  type Result (line 17) | type Result struct
  function NewOkResult (line 23) | func NewOkResult[T any](data T) *Result[T] {
  function NewNilResult (line 30) | func NewNilResult() *Result[any] {
  function NewErrorResult (line 36) | func NewErrorResult(msg string, code ...RespCode) *Result[any] {

FILE: pkg/rest/model/server.go
  type Storage (line 8) | type Storage
  constant StorageMem (line 11) | StorageMem  Storage = "mem"
  constant StorageBolt (line 12) | StorageBolt Storage = "bolt"
  type StartConfig (line 15) | type StartConfig struct
    method Init (line 32) | func (cfg *StartConfig) Init() *StartConfig {
  type WebAuth (line 51) | type WebAuth struct

FILE: pkg/rest/model/task.go
  type ResolveTask (line 5) | type ResolveTask struct
  type CreateTask (line 10) | type CreateTask struct

FILE: pkg/rest/model/webhook.go
  type TestWebhookReq (line 4) | type TestWebhookReq struct

FILE: pkg/rest/server.go
  function Start (line 38) | func Start(startCfg *model.StartConfig) (port int, err error) {
  function Stop (line 63) | func Stop() {
  function BuildServer (line 82) | func BuildServer(startCfg *model.StartConfig) (*http.Server, net.Listene...
  function resolvePath (line 263) | func resolvePath(urlPath string, prefix string) (identity string, path s...
  type taskFileSystem (line 281) | type taskFileSystem struct
    method Open (line 284) | func (e *taskFileSystem) Open(name string) (http.File, error) {
  type extensionFileSystem (line 298) | type extensionFileSystem struct
    method Open (line 301) | func (e *extensionFileSystem) Open(name string) (http.File, error) {
  type embedCacheFileSystem (line 315) | type embedCacheFileSystem struct
    method Open (line 340) | func (e *embedCacheFileSystem) Open(name string) (http.File, error) {
  function newEmbedCacheFileSystem (line 320) | func newEmbedCacheFileSystem(fs http.FileSystem) *embedCacheFileSystem {
  type embedFile (line 352) | type embedFile struct
    method Stat (line 366) | func (e *embedFile) Stat() (fs.FileInfo, error) {
  type embedFileInfo (line 357) | type embedFileInfo struct
    method ModTime (line 362) | func (e *embedFileInfo) ModTime() time.Time {
  function ReadJson (line 377) | func ReadJson(r *http.Request, w http.ResponseWriter, v any) bool {
  function WriteJson (line 385) | func WriteJson(w http.ResponseWriter, v any) {
  function WriteStatusJson (line 389) | func WriteStatusJson(w http.ResponseWriter, statusCode int, v any) {
  function aesEncrypt (line 395) | func aesEncrypt(key, data []byte) (string, error) {
  function aesDecrypt (line 415) | func aesDecrypt(key []byte, encryptedData string) ([]byte, error) {

FILE: pkg/rest/server_test.go
  function TestInfo (line 69) | func TestInfo(t *testing.T) {
  function TestResolve (line 81) | func TestResolve(t *testing.T) {
  function TestCreateTask (line 90) | func TestCreateTask(t *testing.T) {
  function TestCreateDirectTask (line 118) | func TestCreateDirectTask(t *testing.T) {
  function TestCreateDirectTaskBatch (line 142) | func TestCreateDirectTaskBatch(t *testing.T) {
  function TestCreateDirectTaskBatchWithOpt (line 159) | func TestCreateDirectTaskBatchWithOpt(t *testing.T) {
  function TestPauseAndContinueTask (line 198) | func TestPauseAndContinueTask(t *testing.T) {
  function TestPatchTask (line 235) | func TestPatchTask(t *testing.T) {
  function TestPatchTaskNotFound (line 272) | func TestPatchTaskNotFound(t *testing.T) {
  function TestPauseAllAndContinueALLTasks (line 289) | func TestPauseAllAndContinueALLTasks(t *testing.T) {
  function TestDeleteTask (line 325) | func TestDeleteTask(t *testing.T) {
  function TestDeleteTaskForce (line 335) | func TestDeleteTaskForce(t *testing.T) {
  function TestDeleteAllTasks (line 348) | func TestDeleteAllTasks(t *testing.T) {
  function TestDeleteTasksByStatues (line 374) | func TestDeleteTasksByStatues(t *testing.T) {
  function TestGetTasks (line 400) | func TestGetTasks(t *testing.T) {
  function TestGetAndPutConfig (line 428) | func TestGetAndPutConfig(t *testing.T) {
  function TestInstallExtension (line 448) | func TestInstallExtension(t *testing.T) {
  function TestGetExtensions (line 469) | func TestGetExtensions(t *testing.T) {
  function TestUpdateExtensionSettings (line 479) | func TestUpdateExtensionSettings(t *testing.T) {
  function TestSwitchExtension (line 501) | func TestSwitchExtension(t *testing.T) {
  function TestDeleteExtension (line 514) | func TestDeleteExtension(t *testing.T) {
  function TestUpdateCheckExtension (line 525) | func TestUpdateCheckExtension(t *testing.T) {
  function TestFsExtension (line 538) | func TestFsExtension(t *testing.T) {
  function TestFsExtensionFail (line 548) | func TestFsExtensionFail(t *testing.T) {
  function TestWebFsEnhance (line 557) | func TestWebFsEnhance(t *testing.T) {
  function TestDoProxy (line 617) | func TestDoProxy(t *testing.T) {
  function TestTestWebhook (line 642) | func TestTestWebhook(t *testing.T) {
  function TestApiToken (line 752) | func TestApiToken(t *testing.T) {
  function TestAuthorization (line 778) | func TestAuthorization(t *testing.T) {
  function doTest (line 878) | func doTest(handler func()) {
  function doTest0 (line 882) | func doTest0(onStart func(cfg *model.StartConfig), handler func()) {
  function doStart (line 913) | func doStart(cfg *model.StartConfig) net.Listener {
  function doHttpRequest0 (line 922) | func doHttpRequest0(method string, path string, headers map[string]strin...
  function doHttpRequest1 (line 927) | func doHttpRequest1(method string, path string, headers map[string]strin...
  function doHttpRequest (line 962) | func doHttpRequest[T any](method string, path string, headers map[string...
  function httpRequest (line 975) | func httpRequest[T any](method string, path string, body any) (int, *mod...
  function httpRequestCheckOk (line 979) | func httpRequestCheckOk[T any](method string, path string, body any) T {
  function checkOk (line 985) | func checkOk(code int) {
  function checkCode (line 989) | func checkCode(code int, exceptCode model.RespCode) {

FILE: pkg/util/bytefmt.go
  constant unknownSize (line 8) | unknownSize = "unknown"
  function ByteFmt (line 12) | func ByteFmt(size int64) string {

FILE: pkg/util/bytefmt_test.go
  function TestByteFmt (line 5) | func TestByteFmt(t *testing.T) {

FILE: pkg/util/json.go
  function MapToStruct (line 5) | func MapToStruct(s any, v any) error {
  function DeepClone (line 16) | func DeepClone[T any](v *T) *T {
  function Ptr (line 31) | func Ptr[T any](v T) *T {
  function BoolPtr (line 36) | func BoolPtr(v bool) *bool {

FILE: pkg/util/json_test.go
  function TestDeepClone (line 8) | func TestDeepClone(t *testing.T) {

FILE: pkg/util/matcher.go
  function Match (line 11) | func Match(pattern string, u string) bool {
  function parsePattern (line 29) | func parsePattern(pattern string) (scheme string, host string, path stri...
  function matchHost (line 48) | func matchHost(pattern string, host string) bool {
  function matchPath (line 58) | func matchPath(pattern string, path string) bool {

FILE: pkg/util/matcher_test.go
  function TestMatch (line 5) | func TestMatch(t *testing.T) {

FILE: pkg/util/path.go
  function Dir (line 15) | func Dir(path string) string {
  function Filepath (line 23) | func Filepath(path string, originName string, customName string) string {
  function SafeRemove (line 31) | func SafeRemove(name string) error {
  function CheckDuplicateAndRename (line 42) | func CheckDuplicateAndRename(path string) (string, error) {
  function CopyDir (line 78) | func CopyDir(source string, target string, excludeDir ...string) error {
  function copyForce (line 146) | func copyForce(source string, target string) error {
  function CreateDirIfNotExist (line 166) | func CreateDirIfNotExist(dir string) error {
  function IsExistsFile (line 174) | func IsExistsFile(path string) bool {
  constant MaxFilenameLength (line 185) | MaxFilenameLength = 100
  constant maxExtensionLength (line 189) | maxExtensionLength = 20
  function SafeFilename (line 197) | func SafeFilename(filename string) string {
  function ReplaceInvalidFilename (line 242) | func ReplaceInvalidFilename(path string) string {
  function TruncateFilename (line 254) | func TruncateFilename(filename string, maxLength int) string {
  function truncateAtValidUTF8Boundary (line 290) | func truncateAtValidUTF8Boundary(s string, maxBytes int) string {
  function ReplacePathPlaceholders (line 318) | func ReplacePathPlaceholders(path string) string {

FILE: pkg/util/path_test.go
  function TestDir (line 11) | func TestDir(t *testing.T) {
  function TestFilepath (line 51) | func TestFilepath(t *testing.T) {
  function TestSafeRemove (line 90) | func TestSafeRemove(t *testing.T) {
  function TestCheckDuplicateAndRename (line 111) | func TestCheckDuplicateAndRename(t *testing.T) {
  function doCheckDuplicateAndRename (line 136) | func doCheckDuplicateAndRename(t *testing.T, exitsPaths []string, path s...
  function TestIsExistsFile (line 160) | func TestIsExistsFile(t *testing.T) {
  function TestReplaceInvalidFilename (line 200) | func TestReplaceInvalidFilename(t *testing.T) {
  function TestSafeFilename (line 248) | func TestSafeFilename(t *testing.T) {
  function TestReplacePathPlaceholders (line 322) | func TestReplacePathPlaceholders(t *testing.T) {
  function TestTruncateFilename (line 395) | func TestTruncateFilename(t *testing.T) {

FILE: pkg/util/timer.go
  type Timer (line 5) | type Timer struct
    method Start (line 16) | func (t *Timer) Start() {
    method Pause (line 20) | func (t *Timer) Pause() {
    method Used (line 24) | func (t *Timer) Used() int64 {
  function NewTimer (line 10) | func NewTimer(used int64) *Timer {

FILE: pkg/util/url.go
  function ParseSchema (line 11) | func ParseSchema(url string) string {
  function ParseDataUri (line 21) | func ParseDataUri(uri string) (string, []byte) {
  function BuildProxyUrl (line 37) | func BuildProxyUrl(scheme, host, usr, pwd string) *url.URL {
  function ProxyUrlToHandler (line 50) | func ProxyUrlToHandler(proxyUrl *url.URL) func(*http.Request) (*url.URL,...
  function TryUrlQueryUnescape (line 63) | func TryUrlQueryUnescape(s string) string {
  function TryUrlPathUnescape (line 76) | func TryUrlPathUnescape(s string) string {

FILE: pkg/util/url_test.go
  function TestParseSchema (line 9) | func TestParseSchema(t *testing.T) {
  function TestParseDataUri (line 70) | func TestParseDataUri(t *testing.T) {
  function TestTryURLDecode (line 132) | func TestTryURLDecode(t *testing.T) {
  function TestTryUrlPathUnescape (line 153) | func TestTryUrlPathUnescape(t *testing.T) {

FILE: ui/flutter/include/libgopeed.h
  type _GoString_ (line 14) | typedef struct { const char *p; ptrdiff_t n; } _GoString_;
  type GoInt8 (line 33) | typedef signed char GoInt8;
  type GoUint8 (line 34) | typedef unsigned char GoUint8;
  type GoInt16 (line 35) | typedef short GoInt16;
  type GoUint16 (line 36) | typedef unsigned short GoUint16;
  type GoInt32 (line 37) | typedef int GoInt32;
  type GoUint32 (line 38) | typedef unsigned int GoUint32;
  type GoInt64 (line 39) | typedef long long GoInt64;
  type GoUint64 (line 40) | typedef unsigned long long GoUint64;
  type GoInt64 (line 41) | typedef GoInt64 GoInt;
  type GoUint64 (line 42) | typedef GoUint64 GoUint;
  type GoUintptr (line 43) | typedef size_t GoUintptr;
  type GoFloat32 (line 44) | typedef float GoFloat32;
  type GoFloat64 (line 45) | typedef double GoFloat64;
  type _Fcomplex (line 48) | typedef _Fcomplex GoComplex64;
  type _Dcomplex (line 49) | typedef _Dcomplex GoComplex128;
  type GoComplex64 (line 51) | typedef float _Complex GoComplex64;
  type GoComplex128 (line 52) | typedef double _Complex GoComplex128;
  type _GoString_ (line 62) | typedef _GoString_ GoString;
  type GoInterface (line 66) | typedef struct { void *t; void *v; } GoInterface;
  type GoSlice (line 67) | typedef struct { void *data; GoInt len; GoInt cap; } GoSlice;
  type Start_return (line 79) | struct Start_return {
  type Start_return (line 83) | struct Start_return

FILE: ui/flutter/lib/api/api.dart
  class _Client (line 25) | class _Client {
  class TimeoutException (line 90) | class TimeoutException implements Exception {
  function init (line 98) | void init(String network, String address, String apiToken)
  function _parse (line 102) | Future<T> _parse<T>(
  function resolve (line 126) | Future<ResolveResult> resolve(ResolveTask resolveTask)
  function createTask (line 132) | Future<String> createTask(CreateTask createTask)
  function createTaskBatch (line 138) | Future<List<String>> createTaskBatch(CreateTaskBatch createTaskBatch)
  function patchTask (line 144) | Future<void> patchTask(String id, ResolveTask patchTask)
  function getTasks (line 149) | Future<List<Task>> getTasks(List<Status> statuses)
  function pauseTask (line 156) | Future<void> pauseTask(String id)
  function continueTask (line 160) | Future<void> continueTask(String id)
  function pauseAllTasks (line 164) | Future<void> pauseAllTasks(List<String>? ids)
  function continueAllTasks (line 172) | Future<void> continueAllTasks(List<String>? ids)
  function deleteTask (line 180) | Future<void> deleteTask(String id, bool force)
  function deleteTasks (line 185) | Future<void> deleteTasks(List<String>? ids, bool force)
  function getConfig (line 194) | Future<DownloaderConfig> getConfig()
  function putConfig (line 199) | Future<void> putConfig(DownloaderConfig config)
  function installExtension (line 203) | Future<String> installExtension(InstallExtension installExtension)
  function getExtensions (line 209) | Future<List<Extension>> getExtensions()
  function updateExtensionSettings (line 214) | Future<void> updateExtensionSettings(
  function switchExtension (line 222) | Future<void> switchExtension(
  function deleteExtension (line 230) | Future<void> deleteExtension(String identity)
  function upgradeCheckExtension (line 234) | Future<UpdateCheckExtensionResp> upgradeCheckExtension(String identity)
  function updateExtension (line 239) | Future<void> updateExtension(String identity)
  function testWebhook (line 244) | Future<void> testWebhook(String url)
  function login (line 249) | Future<String> login(LoginReq loginReq)
  function proxyRequest (line 254) | Future<Response<String>> proxyRequest<T>(String uri,
  function join (line 267) | String join(String path)
  function forward (line 277) | Future<Response> forward(

FILE: ui/flutter/lib/api/gopeed_site_api.dart
  class GopeedSiteApi (line 8) | class GopeedSiteApi {
    method getRelease (line 15) | Future<Map<String, dynamic>> getRelease()
    method getExtensions (line 20) | Future<StoreExtensionPage> getExtensions({
    method reportExtensionInstall (line 37) | Future<void> reportExtensionInstall(String id)
    method _getJson (line 46) | Future<dynamic> _getJson(String path,

FILE: ui/flutter/lib/api/model/create_task.dart
  class CreateTask (line 8) | @JsonSerializable(explicitToJson: true)
    method toJson (line 25) | Map<String, dynamic> toJson()

FILE: ui/flutter/lib/api/model/create_task.g.dart
  function _$CreateTaskFromJson (line 9) | CreateTask _$CreateTaskFromJson(Map<String, dynamic> json)
  function _$CreateTaskToJson (line 19) | Map<String, dynamic> _$CreateTaskToJson(CreateTask instance)
  function writeNotNull (line 22) | void writeNotNull(String key, dynamic value)

FILE: ui/flutter/lib/api/model/create_task_batch.dart
  class CreateTaskBatch (line 8) | @JsonSerializable(explicitToJson: true)
    method toJson (line 23) | Map<String, dynamic> toJson()
  class CreateTaskBatchItem (line 26) | @JsonSerializable()
    method toJson (line 38) | Map<String, dynamic> toJson()

FILE: ui/flutter/lib/api/model/create_task_batch.g.dart
  function _$CreateTaskBatchFromJson (line 9) | CreateTaskBatch _$CreateTaskBatchFromJson(Map<String, dynamic> json)
  function _$CreateTaskBatchToJson (line 19) | Map<String, dynamic> _$CreateTaskBatchToJson(CreateTaskBatch instance)
  function writeNotNull (line 22) | void writeNotNull(String key, dynamic value)
  function _$CreateTaskBatchItemFromJson (line 33) | CreateTaskBatchItem _$CreateTaskBatchItemFromJson(Map<String, dynamic> j...
  function _$CreateTaskBatchItemToJson (line 43) | Map<String, dynamic> _$CreateTaskBatchItemToJson(CreateTaskBatchItem ins...
  function writeNotNull (line 46) | void writeNotNull(String key, dynamic value)

FILE: ui/flutter/lib/api/model/downloader_config.dart
  class DownloaderConfig (line 5) | @JsonSerializable(explicitToJson: true)
    method toJson (line 27) | Map<String, dynamic> toJson()
  class ProtocolConfig (line 30) | @JsonSerializable(explicitToJson: true)
    method toJson (line 41) | Map<String, dynamic> toJson()
  class HttpConfig (line 44) | @JsonSerializable()
    method toJson (line 59) | Map<String, dynamic> toJson()
  class BtConfig (line 62) | @JsonSerializable()
    method toJson (line 81) | Map<String, dynamic> toJson()
  class Ed2kConfig (line 84) | @JsonSerializable()
    method toJson (line 103) | Map<String, dynamic> toJson()
  class ExtraConfig (line 106) | @JsonSerializable(explicitToJson: true)
    method toJson (line 136) | Map<String, dynamic> toJson()
  class DownloadCategory (line 139) | @JsonSerializable()
    method toJson (line 159) | Map<String, dynamic> toJson()
  class WebhookConfig (line 162) | @JsonSerializable()
    method toJson (line 175) | Map<String, dynamic> toJson()
  class ScriptConfig (line 178) | @JsonSerializable()
    method toJson (line 191) | Map<String, dynamic> toJson()
  class ProxyConfig (line 194) | @JsonSerializable()
    method toJson (line 215) | Map<String, dynamic> toJson()
  class ExtraConfigBt (line 218) | @JsonSerializable()
    method toJson (line 232) | Map<String, dynamic> toJson()
  type GithubMirrorType (line 235) | enum GithubMirrorType {
  class GithubMirror (line 240) | @JsonSerializable()
    method toJson (line 257) | Map<String, dynamic> toJson()
  class ExtraConfigGithubMirror (line 260) | @JsonSerializable(explicitToJson: true)
    method toJson (line 275) | Map<String, dynamic> toJson()
  class AutoTorrentConfig (line 278) | @JsonSerializable()
    method toJson (line 291) | Map<String, dynamic> toJson()
  class ArchiveConfig (line 294) | @JsonSerializable()
    method toJson (line 307) | Map<String, dynamic> toJson()

FILE: ui/flutter/lib/api/model/downloader_config.g.dart
  function _$DownloaderConfigFromJson (line 9) | DownloaderConfig _$DownloaderConfigFromJson(Map<String, dynamic> json)
  function _$DownloaderConfigToJson (line 28) | Map<String, dynamic> _$DownloaderConfigToJson(DownloaderConfig instance)
  function _$ProtocolConfigFromJson (line 42) | ProtocolConfig _$ProtocolConfigFromJson(Map<String, dynamic> json)
  function _$ProtocolConfigToJson (line 49) | Map<String, dynamic> _$ProtocolConfigToJson(ProtocolConfig instance)
  function _$HttpConfigFromJson (line 56) | HttpConfig _$HttpConfigFromJson(Map<String, dynamic> json)
  function _$HttpConfigToJson (line 62) | Map<String, dynamic> _$HttpConfigToJson(HttpConfig instance)
  function _$BtConfigFromJson (line 69) | BtConfig _$BtConfigFromJson(Map<String, dynamic> json)
  function _$BtConfigToJson (line 80) | Map<String, dynamic> _$BtConfigToJson(BtConfig instance)
  function _$Ed2kConfigFromJson (line 88) | Ed2kConfig _$Ed2kConfigFromJson(Map<String, dynamic> json)
  function _$Ed2kConfigToJson (line 96) | Map<String, dynamic> _$Ed2kConfigToJson(Ed2kConfig instance)
  function _$ExtraConfigFromJson (line 105) | ExtraConfig _$ExtraConfigFromJson(Map<String, dynamic> json)
  function _$ExtraConfigToJson (line 123) | Map<String, dynamic> _$ExtraConfigToJson(ExtraConfig instance)
  function _$DownloadCategoryFromJson (line 139) | DownloadCategory _$DownloadCategoryFromJson(Map<String, dynamic> json)
  function _$DownloadCategoryToJson (line 148) | Map<String, dynamic> _$DownloadCategoryToJson(DownloadCategory instance)
  function writeNotNull (line 155) | void writeNotNull(String key, dynamic value)
  function _$WebhookConfigFromJson (line 166) | WebhookConfig _$WebhookConfigFromJson(Map<String, dynamic> json)
  function _$WebhookConfigToJson (line 174) | Map<String, dynamic> _$WebhookConfigToJson(WebhookConfig instance)
  function _$ScriptConfigFromJson (line 180) | ScriptConfig _$ScriptConfigFromJson(Map<String, dynamic> json)
  function _$ScriptConfigToJson (line 187) | Map<String, dynamic> _$ScriptConfigToJson(ScriptConfig instance)
  function _$ProxyConfigFromJson (line 193) | ProxyConfig _$ProxyConfigFromJson(Map<String, dynamic> json)
  function _$ProxyConfigToJson (line 202) | Map<String, dynamic> _$ProxyConfigToJson(ProxyConfig instance)
  function _$ExtraConfigBtFromJson (line 212) | ExtraConfigBt _$ExtraConfigBtFromJson(Map<String, dynamic> json)
  function _$ExtraConfigBtToJson (line 228) | Map<String, dynamic> _$ExtraConfigBtToJson(ExtraConfigBt instance)
  function writeNotNull (line 235) | void writeNotNull(String key, dynamic value)
  function _$GithubMirrorFromJson (line 247) | GithubMirror _$GithubMirrorFromJson(Map<String, dynamic> json)
  function _$GithubMirrorToJson (line 254) | Map<String, dynamic> _$GithubMirrorToJson(GithubMirror instance)
  function _$ExtraConfigGithubMirrorFromJson (line 267) | ExtraConfigGithubMirror _$ExtraConfigGithubMirrorFromJson(
  function _$ExtraConfigGithubMirrorToJson (line 277) | Map<String, dynamic> _$ExtraConfigGithubMirrorToJson(
  function _$AutoTorrentConfigFromJson (line 284) | AutoTorrentConfig _$AutoTorrentConfigFromJson(Map<String, dynamic> json)
  function _$AutoTorrentConfigToJson (line 290) | Map<String, dynamic> _$AutoTorrentConfigToJson(AutoTorrentConfig instance)
  function _$ArchiveConfigFromJson (line 296) | ArchiveConfig _$ArchiveConfigFromJson(Map<String, dynamic> json)
  function _$ArchiveConfigToJson (line 302) | Map<String, dynamic> _$ArchiveConfigToJson(ArchiveConfig instance)

FILE: ui/flutter/lib/api/model/extension.dart
  class Extension (line 5) | @JsonSerializable(explicitToJson: true)
    method toJson (line 38) | Map<String, dynamic> toJson()
  class Repository (line 41) | @JsonSerializable()
    method toJson (line 53) | Map<String, dynamic> toJson()
  class Setting (line 56) | @JsonSerializable()
    method toJson (line 76) | Map<String, dynamic> toJson()
  class Option (line 79) | @JsonSerializable()
    method toJson (line 90) | Map<String, dynamic> toJson()
  type SettingType (line 93) | enum SettingType {

FILE: ui/flutter/lib/api/model/extension.g.dart
  function _$ExtensionFromJson (line 9) | Extension _$ExtensionFromJson(Map<String, dynamic> json)
  function _$ExtensionToJson (line 28) | Map<String, dynamic> _$ExtensionToJson(Extension instance)
  function writeNotNull (line 40) | void writeNotNull(String key, dynamic value)
  function _$RepositoryFromJson (line 54) | Repository _$RepositoryFromJson(Map<String, dynamic> json)
  function _$RepositoryToJson (line 59) | Map<String, dynamic> _$RepositoryToJson(Repository instance)
  function _$SettingFromJson (line 65) | Setting _$SettingFromJson(Map<String, dynamic> json)
  function _$SettingToJson (line 77) | Map<String, dynamic> _$SettingToJson(Setting instance)
  function writeNotNull (line 86) | void writeNotNull(String key, dynamic value)
  function _$OptionFromJson (line 103) | Option _$OptionFromJson(Map<String, dynamic> json)
  function _$OptionToJson (line 108) | Map<String, dynamic> _$OptionToJson(Option instance)

FILE: ui/flutter/lib/api/model/install_extension.dart
  class InstallExtension (line 5) | @JsonSerializable()
    method toJson (line 17) | Map<String, dynamic> toJson()

FILE: ui/flutter/lib/api/model/install_extension.g.dart
  function _$InstallExtensionFromJson (line 9) | InstallExtension _$InstallExtensionFromJson(Map<String, dynamic> json)
  function _$InstallExtensionToJson (line 15) | Map<String, dynamic> _$InstallExtensionToJson(InstallExtension instance)

FILE: ui/flutter/lib/api/model/login.dart
  class LoginReq (line 5) | @JsonSerializable()
    method toJson (line 17) | Map<String, dynamic> toJson()

FILE: ui/flutter/lib/api/model/login.g.dart
  function _$LoginReqFromJson (line 9) | LoginReq _$LoginReqFromJson(Map<String, dynamic> json)
  function _$LoginReqToJson (line 14) | Map<String, dynamic> _$LoginReqToJson(LoginReq instance)

FILE: ui/flutter/lib/api/model/meta.dart
  class Meta (line 8) | @JsonSerializable(explicitToJson: true)
    method toJson (line 20) | Map<String, dynamic> toJson()

FILE: ui/flutter/lib/api/model/meta.g.dart
  function _$MetaFromJson (line 9) | Meta _$MetaFromJson(Map<String, dynamic> json)
  function _$MetaToJson (line 16) | Map<String, dynamic> _$MetaToJson(Meta instance)
  function writeNotNull (line 21) | void writeNotNull(String key, dynamic value)

FILE: ui/flutter/lib/api/model/options.dart
  class Options (line 5) | @JsonSerializable(explicitToJson: true)
    method toJson (line 22) | Map<String, dynamic> toJson()
  class OptsExtraHttp (line 25) | @JsonSerializable()
    method toJson (line 46) | Map<String, dynamic> toJson()

FILE: ui/flutter/lib/api/model/options.g.dart
  function _$OptionsFromJson (line 9) | Options _$OptionsFromJson(Map<String, dynamic> json)
  function _$OptionsToJson (line 19) | Map<String, dynamic> _$OptionsToJson(Options instance)
  function writeNotNull (line 26) | void writeNotNull(String key, dynamic value)
  function _$OptsExtraHttpFromJson (line 36) | OptsExtraHttp _$OptsExtraHttpFromJson(Map<String, dynamic> json)
  function _$OptsExtraHttpToJson (line 46) | Map<String, dynamic> _$OptsExtraHttpToJson(OptsExtraHttp instance)
  function writeNotNull (line 51) | void writeNotNull(String key, dynamic value)

FILE: ui/flutter/lib/api/model/request.dart
  class Request (line 5) | @JsonSerializable(explicitToJson: true)
    method toJson (line 24) | Map<String, dynamic> toJson()
  class ReqExtraHttp (line 27) | @JsonSerializable()
    method toJson (line 42) | Map<String, dynamic> toJson()
  class ReqExtraBt (line 45) | @JsonSerializable()
    method toJson (line 56) | Map<String, dynamic> toJson()
  type RequestProxyMode (line 59) | enum RequestProxyMode {
  class RequestProxy (line 65) | @JsonSerializable()
    method toJson (line 84) | Map<String, dynamic> toJson()

FILE: ui/flutter/lib/api/model/request.g.dart
  function _$RequestFromJson (line 9) | Request _$RequestFromJson(Map<String, dynamic> json)
  function _$RequestToJson (line 21) | Map<String, dynamic> _$RequestToJson(Request instance)
  function writeNotNull (line 26) | void writeNotNull(String key, dynamic value)
  function _$ReqExtraHttpFromJson (line 39) | ReqExtraHttp _$ReqExtraHttpFromJson(Map<String, dynamic> json)
  function _$ReqExtraHttpToJson (line 48) | Map<String, dynamic> _$ReqExtraHttpToJson(ReqExtraHttp instance)
  function _$ReqExtraBtFromJson (line 55) | ReqExtraBt _$ReqExtraBtFromJson(Map<String, dynamic> json)
  function _$ReqExtraBtToJson (line 62) | Map<String, dynamic> _$ReqExtraBtToJson(ReqExtraBt instance)
  function _$RequestProxyFromJson (line 67) | RequestProxy _$RequestProxyFromJson(Map<String, dynamic> json)
  function _$RequestProxyToJson (line 76) | Map<String, dynamic> _$RequestProxyToJson(RequestProxy instance)

FILE: ui/flutter/lib/api/model/resolve_result.dart
  class ResolveResult (line 7) | @JsonSerializable(explicitToJson: true)
    method toJson (line 19) | Map<String, dynamic> toJson()

FILE: ui/flutter/lib/api/model/resolve_result.g.dart
  function _$ResolveResultFromJson (line 9) | ResolveResult _$ResolveResultFromJson(Map<String, dynamic> json)
  function _$ResolveResultToJson (line 15) | Map<String, dynamic> _$ResolveResultToJson(ResolveResult instance)

FILE: ui/flutter/lib/api/model/resolve_task.dart
  class ResolveTask (line 8) | @JsonSerializable()
    method toJson (line 20) | Map<String, dynamic> toJson()

FILE: ui/flutter/lib/api/model/resolve_task.g.dart
  function _$ResolveTaskFromJson (line 9) | ResolveTask _$ResolveTaskFromJson(Map<String, dynamic> json)
  function _$ResolveTaskToJson (line 18) | Map<String, dynamic> _$ResolveTaskToJson(ResolveTask instance)
  function writeNotNull (line 21) | void writeNotNull(String key, dynamic value)

FILE: ui/flutter/lib/api/model/resource.dart
  class Resource (line 6) | @JsonSerializable(explicitToJson: true)
    method toJson (line 24) | Map<String, dynamic> toJson()
  class FileInfo (line 27) | @JsonSerializable(explicitToJson: true)
    method toJson (line 44) | Map<String, dynamic> toJson()

FILE: ui/flutter/lib/api/model/resource.g.dart
  function _$ResourceFromJson (line 9) | Resource _$ResourceFromJson(Map<String, dynamic> json)
  function _$ResourceToJson (line 19) | Map<String, dynamic> _$ResourceToJson(Resource instance)
  function _$FileInfoFromJson (line 27) | FileInfo _$FileInfoFromJson(Map<String, dynamic> json)
  function _$FileInfoToJson (line 36) | Map<String, dynamic> _$FileInfoToJson(FileInfo instance)
  function writeNotNull (line 43) | void writeNotNull(String key, dynamic value)

FILE: ui/flutter/lib/api/model/result.dart
  class Result (line 5) | @JsonSerializable(genericArgumentFactories: true)
    method toJson (line 22) | Map<String, dynamic> toJson()

FILE: ui/flutter/lib/api/model/result.g.dart
  function _$ResultFromJson (line 9) | Result<T> _$ResultFromJson<T>(
  function _$ResultToJson (line 19) | Map<String, dynamic> _$ResultToJson<T>(
  function writeNotNull (line 27) | void writeNotNull(String key, dynamic value)
  function _$nullableGenericFromJson (line 38) | T? _$nullableGenericFromJson<T>(
  function _$nullableGenericToJson (line 44) | Object? _$nullableGenericToJson<T>(

FILE: ui/flutter/lib/api/model/store_extension.dart
  class StoreExtensionPage (line 3) | class StoreExtensionPage {
  class StorePagination (line 20) | class StorePagination {
  class StoreExtension (line 49) | class StoreExtension {
    method _parseTopics (line 113) | List<String> _parseTopics(dynamic value)
    method _parseDate (line 128) | DateTime? _parseDate(dynamic value)
  type StoreExtensionSort (line 140) | enum StoreExtensionSort {
  type StoreSortOrder (line 146) | enum StoreSortOrder {

FILE: ui/flutter/lib/api/model/switch_extension.dart
  class SwitchExtension (line 5) | @JsonSerializable()
    method toJson (line 15) | Map<String, dynamic> toJson()

FILE: ui/flutter/lib/api/model/switch_extension.g.dart
  function _$SwitchExtensionFromJson (line 9) | SwitchExtension _$SwitchExtensionFromJson(Map<String, dynamic> json)
  function _$SwitchExtensionToJson (line 14) | Map<String, dynamic> _$SwitchExtensionToJson(SwitchExtension instance)

FILE: ui/flutter/lib/api/model/task.dart
  type Status (line 7) | enum Status { ready, running, pause, wait, error, done }
  type Protocol (line 9) | enum Protocol { http, bt, ed2k }
  type ExtractStatus (line 12) | enum ExtractStatus {
  class Task (line 25) | @JsonSerializable(explicitToJson: true)
    method toJson (line 50) | Map<String, dynamic> toJson()
  class Progress (line 53) | @JsonSerializable()
    method toJson (line 76) | Map<String, dynamic> toJson()

FILE: ui/flutter/lib/api/model/task.g.dart
  function _$TaskFromJson (line 9) | Task _$TaskFromJson(Map<String, dynamic> json)
  function _$TaskToJson (line 20) | Map<String, dynamic> _$TaskToJson(Task instance)
  function writeNotNull (line 26) | void writeNotNull(String key, dynamic value)
  function _$ProgressFromJson (line 57) | Progress _$ProgressFromJson(Map<String, dynamic> json)
  function _$ProgressToJson (line 69) | Map<String, dynamic> _$ProgressToJson(Progress instance)

FILE: ui/flutter/lib/api/model/update_check_extension_resp.dart
  class UpdateCheckExtensionResp (line 5) | @JsonSerializable()
    method toJson (line 15) | Map<String, dynamic> toJson()

FILE: ui/flutter/lib/api/model/update_check_extension_resp.g.dart
  function _$UpdateCheckExtensionRespFromJson (line 9) | UpdateCheckExtensionResp _$UpdateCheckExtensionRespFromJson(
  function _$UpdateCheckExtensionRespToJson (line 15) | Map<String, dynamic> _$UpdateCheckExtensionRespToJson(

FILE: ui/flutter/lib/api/model/update_extension_settings.dart
  class UpdateExtensionSettings (line 5) | @JsonSerializable()
    method toJson (line 15) | Map<String, dynamic> toJson()

FILE: ui/flutter/lib/api/model/update_extension_settings.g.dart
  function _$UpdateExtensionSettingsFromJson (line 9) | UpdateExtensionSettings _$UpdateExtensionSettingsFromJson(
  function _$UpdateExtensionSettingsToJson (line 15) | Map<String, dynamic> _$UpdateExtensionSettingsToJson(

FILE: ui/flutter/lib/app/modules/app/bindings/app_binding.dart
  class AppBinding (line 5) | class AppBinding extends Bindings {
    method dependencies (line 7) | void dependencies()

FILE: ui/flutter/lib/app/modules/app/controllers/app_controller.dart
  class PendingUpdateTask (line 65) | class PendingUpdateTask {
  class AppController (line 72) | class AppController extends GetxController with WindowListener, TrayList...
    method onReady (line 93) | void onReady()
    method onClose (line 126) | void onClose()
    method onWindowClose (line 133) | void onWindowClose()
    method onWindowFocus (line 142) | void onWindowFocus()
    method onTrayIconMouseDown (line 150) | void onTrayIconMouseDown()
    method onTrayIconRightMouseDown (line 155) | void onTrayIconRightMouseDown()
    method onWindowMaximize (line 160) | void onWindowMaximize()
    method onWindowUnmaximize (line 165) | void onWindowUnmaximize()
    method onWindowResize (line 176) | void onWindowResize()
    method _initDeepLinks (line 180) | Future<void> _initDeepLinks()
    method _initWindows (line 244) | Future<void> _initWindows()
    method _initTray (line 251) | Future<void> _initTray()
    method _initRpcServer (line 328) | Future<void> _initRpcServer()
    method _initForegroundTask (line 386) | Future<void> _initForegroundTask()
    method _handleDeepLink (line 427) | Future<void> _handleDeepLink(Uri uri)
    method runningAddress (line 469) | String runningAddress()
    method _initDefaultStartConfig (line 476) | Future<StartConfig> _initDefaultStartConfig()
    method loadStartConfig (line 494) | Future<StartConfig> loadStartConfig()
    method loadDownloaderConfig (line 503) | Future<DownloaderConfig> loadDownloaderConfig()
    method trackerUpdate (line 514) | Future<void> trackerUpdate()
    method _initTrackerUpdate (line 551) | Future<void> _initTrackerUpdate()
    method _fetchTrackers (line 566) | Future<List<String>> _fetchTrackers(String subscribeUrl)
    method _initDefaultDownloadCategories (line 625) | void _initDefaultDownloadCategories()
    method _initDefaultGithubMirrors (line 659) | void _initDefaultGithubMirrors()
    method _initLaunchAtStartup (line 677) | Future<void> _initLaunchAtStartup()
    method _initCheckUpdate (line 688) | Future<void> _initCheckUpdate()
    method saveConfig (line 700) | Future<void> saveConfig()
    method _decodeParams (line 708) | Map<String, dynamic> _decodeParams(String params)

FILE: ui/flutter/lib/app/modules/app/views/app_view.dart
  class AppView (line 14) | class AppView extends GetView<AppController> {
    method build (line 18) | Widget build(BuildContext context)

FILE: ui/flutter/lib/app/modules/create/bindings/create_binding.dart
  class CreateBinding (line 5) | class CreateBinding extends Bindings {
    method dependencies (line 7) | void dependencies()

FILE: ui/flutter/lib/app/modules/create/controllers/create_controller.dart
  class CreateController (line 10) | class CreateController extends GetxController
    method onInit (line 27) | void onInit()
    method onClose (line 38) | void onClose()
    method setFileDataUri (line 43) | void setFileDataUri(Uint8List bytes)
    method clearFileDataUri (line 48) | void clearFileDataUri()

FILE: ui/flutter/lib/app/modules/create/views/create_view.dart
  class CreateView (line 33) | class CreateView extends GetView<CreateController> {
    method build (line 73) | Widget build(BuildContext context)
    method _doConfirm (line 829) | Future<void> _doConfirm()
    method _showPendingUpdateDialog (line 920) | Future<bool?> _showPendingUpdateDialog(String taskId, String taskName)
    method _updatePendingTask (line 946) | Future<void> _updatePendingTask(String taskId, String newUrl)
    method parseProxy (line 984) | RequestProxy? parseProxy()
    method parseReqExtra (line 996) | Object? parseReqExtra(String url)
    method parseReqOptsExtra (line 1022) | Object? parseReqOptsExtra()
    method _hitText (line 1032) | String _hitText()
    method _showResolveDialog (line 1039) | Future<void> _showResolveDialog(ResolveResult rr)
    method _buildCategorySelector (line 1149) | Widget _buildCategorySelector(AppController appController)
    method getCategoryDisplayName (line 1159) | String getCategoryDisplayName(DownloadCategory category)

FILE: ui/flutter/lib/app/modules/extension/bindings/extension_binding.dart
  class ExtensionBinding (line 5) | class ExtensionBinding extends Bindings {
    method dependencies (line 7) | void dependencies()

FILE: ui/flutter/lib/app/modules/extension/controllers/extension_controller.dart
  type ExtensionListFilter (line 13) | enum ExtensionListFilter {
  class ExtensionListItem (line 18) | class ExtensionListItem {
  class ExtensionController (line 59) | class ExtensionController extends GetxController {
    method onInit (line 105) | Future<void> onInit()
    method loadInitialData (line 110) | Future<void> loadInitialData()
    method loadInstalled (line 114) | Future<void> loadInstalled({bool refreshUpdates = false})
    method checkUpdate (line 126) | Future<void> checkUpdate()
    method refreshStore (line 141) | Future<void> refreshStore()
    method loadMoreStore (line 157) | Future<void> loadMoreStore()
    method searchStore (line 177) | Future<void> searchStore(String query)
    method changeSort (line 182) | Future<void> changeSort(StoreExtensionSort sort)
    method changeFilter (line 191) | void changeFilter(ExtensionListFilter filter)
    method toggleInstallTools (line 195) | void toggleInstallTools()
    method findInstalled (line 199) | Extension? findInstalled(StoreExtension extension)
    method canUpdateFromStore (line 203) | bool canUpdateFromStore(StoreExtension extension)
    method canUpdateItem (line 209) | bool canUpdateItem(ExtensionListItem item)
    method installFromStore (line 217) | Future<void> installFromStore(StoreExtension extension)
    method installFromUrl (line 233) | Future<void> installFromUrl(String url,
    method toggleExtension (line 250) | Future<void> toggleExtension(Extension extension, bool enabled)
    method removeExtension (line 258) | Future<void> removeExtension(Extension extension)
    method upgradeExtension (line 266) | Future<void> upgradeExtension(Extension extension)
    method tryOpenDevMode (line 276) | void tryOpenDevMode()
    method _compareVersion (line 290) | int _compareVersion(String a, String b)
    method _toVersionNumbers (line 304) | List<int> _toVersionNumbers(String version)
    method _runBusy (line 312) | Future<void> _runBusy(String id, Future<void> Function() action)
    method _backgroundCheckUpdate (line 322) | void _backgroundCheckUpdate()
    method _reportInstallSafe (line 326) | void _reportInstallSafe(String id)
    method _bumpStoreInstallCount (line 336) | void _bumpStoreInstallCount(String id)

FILE: ui/flutter/lib/app/modules/extension/views/extension_card.dart
  class ExtensionCard (line 13) | class ExtensionCard extends StatelessWidget {
    method build (line 38) | Widget build(BuildContext context)
    method _buildCardIcon (line 196) | Widget _buildCardIcon(ExtensionListItem item)
    method _metricItem (line 252) | Widget _metricItem(BuildContext context, IconData icon, String text)
    method _updateDot (line 263) | Widget _updateDot()

FILE: ui/flutter/lib/app/modules/extension/views/extension_detail_view.dart
  class ExtensionDetailDrawer (line 15) | class ExtensionDetailDrawer extends GetView<ExtensionController> {
    method build (line 28) | Widget build(BuildContext context)
    method _buildReadme (line 171) | Widget _buildReadme(BuildContext context, Extension? installed)
    method _loadReadme (line 219) | Future<_ReadmeInfo> _loadReadme(Extension? installed)
    method _resolvePath (line 254) | String? _resolvePath(String raw, _ReadmeInfo? info,
    method _buildIcon (line 288) | Widget _buildIcon()
  type _ReadmeMode (line 309) | enum _ReadmeMode {
  class _ReadmeInfo (line 314) | class _ReadmeInfo {

FILE: ui/flutter/lib/app/modules/extension/views/extension_view.dart
  class ExtensionView (line 23) | class ExtensionView extends GetView<ExtensionController> {
    method _doInstall (line 30) | Future<void> _doInstall()
    method _installFromFolder (line 51) | Future<void> _installFromFolder()
    method build (line 67) | Widget build(BuildContext context)
    method _buildInstallPanel (line 136) | Widget _buildInstallPanel(BuildContext context)
    method _buildMarketToolbar (line 203) | Widget _buildMarketToolbar(BuildContext context)
    method decoration (line 205) | InputDecoration decoration()
    method _buildSortTabs (line 273) | Widget _buildSortTabs(BuildContext context)
    method tab (line 274) | Widget tab(StoreExtensionSort sort, String label)
    method _buildFilterBar (line 328) | Widget _buildFilterBar(BuildContext context)
    method option (line 330) | Widget option(ExtensionListFilter filter, String text)
    method _buildUnifiedGrid (line 399) | Widget _buildUnifiedGrid(BuildContext context)
    method _buildUnifiedCard (line 442) | Widget _buildUnifiedCard(ExtensionListItem item)
    method _showExtensionDrawer (line 489) | Future<void> _showExtensionDrawer(ExtensionListItem item)
    method _showSettingDialog (line 524) | Future<void> _showSettingDialog(Extension extension)
    method _buildSettingItem (line 629) | Widget _buildSettingItem(Setting setting)
    method buildTextField (line 633) | Widget buildTextField(TextInputFormatter? inputFormatter,
    method buildDropdown (line 648) | Widget buildDropdown()
    method _showDeleteDialog (line 688) | void _showDeleteDialog(Extension extension)

FILE: ui/flutter/lib/app/modules/history/views/history_view.dart
  class HistoryView (line 5) | class HistoryView extends StatefulWidget {
    method createState (line 16) | State<HistoryView> createState()
  class _HistoryViewState (line 19) | class _HistoryViewState extends State<HistoryView> {
    method build (line 21) | Widget build(BuildContext context)

FILE: ui/flutter/lib/app/modules/home/bindings/home_binding.dart
  class HomeBinding (line 5) | class HomeBinding extends Bindings {
    method dependencies (line 7) | void dependencies()

FILE: ui/flutter/lib/app/modules/home/controllers/home_controller.dart
  class HomeController (line 3) | class HomeController extends GetxController {

FILE: ui/flutter/lib/app/modules/home/views/home_view.dart
  class HomeView (line 8) | class HomeView extends GetView<HomeController> {
    method build (line 12) | Widget build(BuildContext context)

FILE: ui/flutter/lib/app/modules/login/bindings/login_binding.dart
  class LoginBinding (line 5) | class LoginBinding extends Bindings {
    method dependencies (line 7) | void dependencies()

FILE: ui/flutter/lib/app/modules/login/controllers/login_controller.dart
  class LoginController (line 12) | class LoginController extends GetxController {
    method onClose (line 21) | void onClose()
    method togglePasswordVisibility (line 27) | void togglePasswordVisibility()
    method login (line 31) | Future<void> login()

FILE: ui/flutter/lib/app/modules/login/views/login_view.dart
  class LoginView (line 8) | class LoginView extends GetView<LoginController> {
    method build (line 12) | Widget build(BuildContext context)

FILE: ui/flutter/lib/app/modules/redirect/bindings/redirect_binding.dart
  class RedirectBinding (line 5) | class RedirectBinding extends Bindings {
    method dependencies (line 7) | void dependencies()

FILE: ui/flutter/lib/app/modules/redirect/controllers/redirect_controller.dart
  class RedirectController (line 3) | class RedirectController extends GetxController {}

FILE: ui/flutter/lib/app/modules/redirect/views/redirect_view.dart
  class RedirectArgs (line 6) | class RedirectArgs {
  class RedirectView (line 13) | class RedirectView extends GetView<RedirectController> {
    method build (line 17) | Widget build(BuildContext context)

FILE: ui/flutter/lib/app/modules/root/bindings/root_binding.dart
  class RootBinding (line 5) | class RootBinding extends Bindings {
    method dependencies (line 7) | void dependencies()

FILE: ui/flutter/lib/app/modules/root/controllers/root_controller.dart
  class RootController (line 3) | class RootController extends GetxController {}

FILE: ui/flutter/lib/app/modules/root/views/root_view.dart
  class RootView (line 7) | class RootView extends GetView<RootController> {
    method build (line 11) | Widget build(BuildContext context)

FILE: ui/flutter/lib/app/modules/setting/bindings/setting_binding.dart
  class SettingBinding (line 5) | class SettingBinding extends Bindings {
    method dependencies (line 7) | void dependencies()

FILE: ui/flutter/lib/app/modules/setting/controllers/setting_controller.dart
  class SettingController (line 5) | class SettingController extends GetxController {
    method onInit (line 10) | void onInit()
    method clearTap (line 16) | void clearTap()
    method onTap (line 21) | void onTap(String key)
    method fetchLatestVersion (line 27) | void fetchLatestVersion()

FILE: ui/flutter/lib/app/modules/setting/views/setting_view.dart
  class SettingView (line 39) | class SettingView extends GetView<SettingController> {
    method _getCategoryDisplayName (line 43) | String _getCategoryDisplayName(DownloadCategory category)
    method build (line 51) | Widget build(BuildContext context)
    method debounceSave (line 57) | Future<bool> debounceSave(
    method onEditComplete (line 88) | void onEditComplete()
    method buildDownloadCategories (line 281) | buildDownloadCategories()
    method buildBrowserExtension (line 407) | buildBrowserExtension()
    method parseEd2kEntries (line 794) | List<String> parseEd2kEntries(String value)
    method summarizeEd2kEntries (line 802) | String summarizeEd2kEntries(String value)
    method formatEd2kMultilineValue (line 810) | String formatEd2kMultilineValue(String value)
    method buildHomepage (line 972) | buildHomepage()
    method buildVersion (line 983) | buildVersion()
    method buildAnalyticsEnabled (line 1020) | buildAnalyticsEnabled()
    method buildThanks (line 1034) | buildThanks()
    method updateAddress (line 1130) | updateAddress()
    method updateAuth (line 1171) | updateAuth()
    method customView (line 1207) | List<Widget> customView()
    method updateAddress (line 1404) | updateAddress()
    method buildLogsDir (line 1678) | buildLogsDir()
    method _showWebhookDialog (line 1873) | void _showWebhookDialog({int? index, String? initialUrl})
    method _showScriptDialog (line 1992) | void _showScriptDialog({int? index, String? initialPath})
    method _showGithubMirrorDialog (line 2090) | void _showGithubMirrorDialog({int? index, GithubMirror? initialMirror})
    method _tapInputWidget (line 2209) | void _tapInputWidget(GlobalKey key)
    method _buildConfigItem (line 2235) | Widget Function() _buildConfigItem(
    method _addPadding (line 2250) | List<Widget> _addPadding(List<Widget> widgets)
    method _addDivider (line 2259) | List<Widget> _addDivider(List<Widget?> widgets)
    method _getThemeName (line 2271) | String _getThemeName(String? themeMode)
    method _showCategoryDialog (line 2282) | void _showCategoryDialog(
  type ProxyModeEnum (line 2365) | enum ProxyModeEnum {

FILE: ui/flutter/lib/app/modules/task/bindings/task_binding.dart
  class TaskBinding (line 7) | class TaskBinding extends Bindings {
    method dependencies (line 9) | void dependencies()

FILE: ui/flutter/lib/app/modules/task/bindings/task_files_binding.dart
  class TaskFilesBinding (line 5) | class TaskFilesBinding extends Bindings {
    method dependencies (line 7) | void dependencies()

FILE: ui/flutter/lib/app/modules/task/controllers/task_controller.dart
  class TaskController (line 7) | class TaskController extends GetxController {
    method onInit (line 13) | void onInit()
    method onClose (line 21) | void onClose()

FILE: ui/flutter/lib/app/modules/task/controllers/task_downloaded_controller.dart
  class TaskDownloadedController (line 5) | class TaskDownloadedController extends TaskListController {

FILE: ui/flutter/lib/app/modules/task/controllers/task_downloading_controller.dart
  class TaskDownloadingController (line 4) | class TaskDownloadingController extends TaskListController {

FILE: ui/flutter/lib/app/modules/task/controllers/task_files_controller.dart
  class FileItem (line 6) | class FileItem {
    method filePath (line 16) | String filePath(String optName)
  class TaskFilesController (line 23) | class TaskFilesController extends GetxController {
    method onInit (line 30) | void onInit()
    method parseDirMap (line 40) | void parseDirMap(List<FileInfo> fileList)
    method findParent (line 52) | void findParent(String dir)
    method toDir (line 85) | void toDir(String dir)
    method dirItemCount (line 89) | int dirItemCount(String dir)

FILE: ui/flutter/lib/app/modules/task/controllers/task_list_controller.dart
  class TaskListController (line 8) | abstract class TaskListController extends GetxController {
    method onInit (line 21) | void onInit()
    method onClose (line 33) | void onClose()
    method start (line 38) | void start()
    method stop (line 43) | void stop()

FILE: ui/flutter/lib/app/modules/task/views/task_downloaded_view.dart
  class TaskDownloadedView (line 7) | class TaskDownloadedView extends GetView<TaskDownloadedController> {
    method build (line 11) | Widget build(BuildContext context)

FILE: ui/flutter/lib/app/modules/task/views/task_downloading_view.dart
  class TaskDownloadingView (line 7) | class TaskDownloadingView extends GetView<TaskDownloadingController> {
    method build (line 11) | Widget build(BuildContext context)

FILE: ui/flutter/lib/app/modules/task/views/task_files_view.dart
  class TaskFilesView (line 15) | class TaskFilesView extends GetView<TaskFilesController> {
    method build (line 19) | Widget build(BuildContext context)

FILE: ui/flutter/lib/app/modules/task/views/task_view.dart
  class TaskView (line 17) | class TaskView extends GetView<TaskController> {
    method build (line 21) | Widget build(BuildContext context)
    method buildTooltipSubtitle (line 110) | Widget buildTooltipSubtitle(String? text)
  function explorer (line 131) | Future<void> explorer()
  function open (line 139) | Future<void> open()

FILE: ui/flutter/lib/app/routes/app_pages.dart
  class AppPages (line 24) | class AppPages {

FILE: ui/flutter/lib/app/routes/app_routes.dart
  class Routes (line 4) | abstract class Routes {
  class _Paths (line 18) | abstract class _Paths {

FILE: ui/flutter/lib/app/rpc/rpc.dart
  class RpcContext (line 9) | class RpcContext {
    method readJSON (line 17) | Future<Map<String, dynamic>> readJSON()
    method readText (line 33) | Future<String> readText()
    method writeJSON (line 46) | Future<void> writeJSON(Map<String, dynamic> data)
    method writeError (line 53) | Future<void> writeError(String message, [int statusCode = 500])
  type RouteHandler (line 60) | typedef RouteHandler = Future<void> Function(RpcContext ctx);
  class RouteRegistry (line 63) | class RouteRegistry {
    method register (line 67) | void register(String path, RouteHandler handler)
    method getHandler (line 72) | RouteHandler? getHandler(String path)
  function startRpcServer (line 81) | Future<void> startRpcServer([Map<String, RouteHandler>? routes])

FILE: ui/flutter/lib/app/services/notification_service.dart
  class NotificationService (line 14) | class NotificationService extends GetxService {
    method onInit (line 23) | void onInit()
    method _initNotifications (line 29) | Future<void> _initNotifications()
    method onClose (line 77) | void onClose()
    method _startPolling (line 82) | void _startPolling()
    method _showNotification (line 131) | Future<void> _showNotification(

FILE: ui/flutter/lib/app/views/breadcrumb_view.dart
  class Breadcrumb (line 4) | class Breadcrumb extends StatelessWidget {
    method build (line 20) | Widget build(BuildContext context)

FILE: ui/flutter/lib/app/views/buid_task_list_view.dart
  class BuildTaskListView (line 20) | class BuildTaskListView extends GetView {
    method build (line 31) | Widget build(BuildContext context)
    method buildTaskList (line 45) | Widget buildTaskList(BuildContext context, tasks)
    method item (line 57) | Widget item(BuildContext context, Task task)
    method isDone (line 58) | bool isDone()
    method isRunning (line 62) | bool isRunning()
    method isSelect (line 66) | bool isSelect()
    method isFolderTask (line 70) | bool isFolderTask()
    method showDeleteDialog (line 74) | Future<void> showDeleteDialog(List<String> ids)
    method showUpdateUrlDialog (line 121) | Future<void> showUpdateUrlDialog(BuildContext context, Task task)
    method buildActions (line 277) | List<Widget> buildActions()
    method getProgress (line 320) | double getProgress()
    method getExtractionStatusText (line 325) | String getExtractionStatusText()
    method getProgressText (line 340) | String getProgressText()
    method getPercentText (line 353) | String getPercentText()
    method getEtaText (line 361) | String getEtaText()
    method twoDigits (line 387) | String twoDigits(int n)
    method filterSelectedTaskIds (line 403) | filterSelectedTaskIds(Iterable<String> selectedTaskIds)

FILE: ui/flutter/lib/app/views/check_list_view.dart
  class CheckListView (line 4) | class CheckListView extends StatefulWidget {
    method createState (line 17) | State<CheckListView> createState()
  class _CheckListView (line 20) | class _CheckListView extends State<CheckListView> {
    method build (line 28) | Widget build(BuildContext context)

FILE: ui/flutter/lib/app/views/compact_checkbox.dart
  class CompactCheckbox (line 3) | class CompactCheckbox extends StatefulWidget {
    method createState (line 20) | State<CompactCheckbox> createState()
  class _CompactCheckboxState (line 23) | class _CompactCheckboxState extends State<CompactCheckbox> {
    method initState (line 27) | void initState()
    method build (line 40) | Widget build(BuildContext context)

FILE: ui/flutter/lib/app/views/copy_button.dart
  class CopyButton (line 6) | class CopyButton extends StatefulWidget {
    method createState (line 12) | State<CopyButton> createState()
  class _CopyButtonState (line 15) | class _CopyButtonState extends State<CopyButton> {
    method build (line 38) | Widget build(BuildContext context)

FILE: ui/flutter/lib/app/views/directory_selector.dart
  class PathPlaceholder (line 18) | class PathPlaceholder {
  function getPathPlaceholders (line 31) | List<PathPlaceholder> getPathPlaceholders()
  function renderPathPlaceholders (line 62) | String renderPathPlaceholders(String path)
  class DirectorySelector (line 78) | class DirectorySelector extends StatefulWidget {
    method createState (line 99) | State<DirectorySelector> createState()
  class _DirectorySelectorState (line 102) | class _DirectorySelectorState extends State<DirectorySelector> {
    method build (line 104) | Widget build(BuildContext context)
    method buildSelectWidget (line 105) | Widget? buildSelectWidget()
    method buildPlaceholderButton (line 180) | Widget? buildPlaceholderButton()

FILE: ui/flutter/lib/app/views/file_icon.dart
  function fileExt (line 55) | String fileExt(String? name)
  function fileIcon (line 68) | IconData fileIcon(String? name,

FILE: ui/flutter/lib/app/views/file_tree_view.dart
  class FileTreeView (line 20) | class FileTreeView extends StatefulWidget {
    method createState (line 33) | State<FileTreeView> createState()
  class _FileTreeViewState (line 36) | class _FileTreeViewState extends State<FileTreeView> {
    method initState (line 42) | void initState()
    method build (line 51) | Widget build(BuildContext context)
    method calcSelectedSize (line 212) | int calcSelectedSize(TreeNode<int>? node)
    method buildTreeNodes (line 229) | List<TreeNode<int>> buildTreeNodes()

FILE: ui/flutter/lib/app/views/icon_button_loading.dart
  class IconButtonLoading (line 3) | class IconButtonLoading extends StatefulWidget {
    method createState (line 16) | State<IconButtonLoading> createState()
  class _IconButtonLoadingState (line 19) | class _IconButtonLoadingState extends State<IconButtonLoading> {
    method build (line 21) | Widget build(BuildContext context)
  class IconButtonLoadingController (line 43) | class IconButtonLoadingController extends ValueNotifier<bool> {
    method start (line 46) | void start()
    method stop (line 50) | void stop()

FILE: ui/flutter/lib/app/views/open_in_new.dart
  class OpenInNew (line 5) | class OpenInNew extends StatelessWidget {
    method build (line 12) | Widget build(BuildContext context)

FILE: ui/flutter/lib/app/views/outlined_button_loading.dart
  class OutlinedButtonLoading (line 3) | class OutlinedButtonLoading extends StatefulWidget {
    method createState (line 15) | State<OutlinedButtonLoading> createState()
  class _OutlinedButtonLoadingState (line 18) | class _OutlinedButtonLoadingState extends State<OutlinedButtonLoading> {
    method build (line 20) | Widget build(BuildContext context)
  class OutlinedButtonLoadingController (line 42) | class OutlinedButtonLoadingController extends ValueNotifier<bool> {
    method start (line 45) | void start()
    method stop (line 49) | void stop()

FILE: ui/flutter/lib/app/views/responsive_builder.dart
  class ResponsiveBuilder (line 3) | class ResponsiveBuilder extends StatelessWidget {
    method isNarrow (line 26) | bool isNarrow(BuildContext context)
    method isMedium (line 29) | bool isMedium(BuildContext context)
    method isWide (line 33) | bool isWide(BuildContext context)
    method build (line 37) | Widget build(BuildContext context)

FILE: ui/flutter/lib/app/views/sort_icon_button.dart
  type SortState (line 5) | enum SortState { none, asc, desc }
  class SortIconButton (line 7) | class SortIconButton extends StatefulWidget {
    method createState (line 24) | State<SortIconButton> createState()
  class _SortIconState (line 27) | class _SortIconState extends State<SortIconButton> {
    method initState (line 31) | void initState()
    method _toggleState (line 36) | void _toggleState()
    method build (line 54) | Widget build(BuildContext context)

FILE: ui/flutter/lib/app/views/text_button_loading.dart
  class TextButtonLoading (line 3) | class TextButtonLoading extends StatefulWidget {
    method createState (line 16) | State<TextButtonLoading> createState()
  class _TextButtonLoadingState (line 19) | class _TextButtonLoadingState extends State<TextButtonLoading> {
    method build (line 21) | Widget build(BuildContext context)
  class TextButtonLoadingController (line 43) | class TextButtonLoadingController extends ValueNotifier<bool> {
    method start (line 46) | void start()
    method stop (line 50) | void stop()

FILE: ui/flutter/lib/core/common/libgopeed_channel.dart
  class LibgopeedChannel (line 8) | class LibgopeedChannel implements LibgopeedInterface {
    method start (line 12) | Future<int> start(StartConfig cfg)
    method stop (line 20) | Future<void> stop()

FILE: ui/flutter/lib/core/common/libgopeed_ffi.dart
  class LibgopeedFFi (line 11) | class LibgopeedFFi implements LibgopeedInterface {
    method start (line 19) | Future<int> start(StartConfig cfg)
    method stop (line 31) | Future<void> stop()

FILE: ui/flutter/lib/core/common/libgopeed_interface.dart
  class LibgopeedInterface (line 3) | abstract class LibgopeedInterface {
    method start (line 4) | Future<int> start(StartConfig cfg)
    method stop (line 6) | Future<void> stop()

FILE: ui/flutter/lib/core/common/start_config.dart
  class StartConfig (line 5) | @JsonSerializable()
    method toJson (line 19) | Map<String, dynamic> toJson()

FILE: ui/flutter/lib/core/common/start_config.g.dart
  function _$StartConfigFromJson (line 9) | StartConfig _$StartConfigFromJson(Map<String, dynamic> json)
  function _$StartConfigToJson (line 17) | Map<String, dynamic> _$StartConfigToJson(StartConfig instance)

FILE: ui/flutter/lib/core/entry/libgopeed_boot_browser.dart
  function create (line 7) | LibgopeedBoot create()
  class LibgopeedBootBrowser (line 9) | class LibgopeedBootBrowser implements LibgopeedBoot {
    method start (line 12) | Future<int> start(StartConfig cfg)
    method stop (line 17) | Future<void> stop()

FILE: ui/flutter/lib/core/entry/libgopeed_boot_native.dart
  function create (line 13) | LibgopeedBoot create()
  class LibgopeedBootNative (line 15) | class LibgopeedBootNative implements LibgopeedBoot {
    method start (line 37) | Future<int> start(StartConfig cfg)
    method stop (line 46) | Future<void> stop()

FILE: ui/flutter/lib/core/ffi/libgopeed_bind.dart
  class LibgopeedBind (line 8) | class LibgopeedBind {
    method __va_start (line 23) | void __va_start(
    method __security_init_cookie (line 37) | void __security_init_cookie()
    method __security_check_cookie (line 47) | void __security_check_cookie(
    method __report_gsfailure (line 61) | void __report_gsfailure(
    method _invalid_parameter_noinfo (line 82) | void _invalid_parameter_noinfo()
    method _invalid_parameter_noinfo_noreturn (line 92) | void _invalid_parameter_noinfo_noreturn()
    method _invoke_watson (line 102) | void _invoke_watson(
    method _errno (line 130) | ffi.Pointer<ffi.Int> _errno()
    method _set_errno (line 138) | int _set_errno(
    method _get_errno (line 150) | int _get_errno(
    method __threadid (line 164) | int __threadid()
    method __threadhandle (line 172) | int __threadhandle()
    method cabs (line 180) | double cabs(
    method cacos (line 192) | _Dcomplex cacos(
    method cacosh (line 204) | _Dcomplex cacosh(
    method carg (line 216) | double carg(
    method casin (line 228) | _Dcomplex casin(
    method casinh (line 240) | _Dcomplex casinh(
    method catan (line 252) | _Dcomplex catan(
    method catanh (line 264) | _Dcomplex catanh(
    method ccos (line 276) | _Dcomplex ccos(
    method ccosh (line 288) | _Dcomplex ccosh(
    method cexp (line 300) | _Dcomplex cexp(
    method cimag (line 312) | double cimag(
    method clog (line 324) | _Dcomplex clog(
    method clog10 (line 336) | _Dcomplex clog10(
    method conj (line 348) | _Dcomplex conj(
    method cpow (line 360) | _Dcomplex cpow(
    method cproj (line 376) | _Dcomplex cproj(
    method creal (line 388) | double creal(
    method csin (line 400) | _Dcomplex csin(
    method csinh (line 412) | _Dcomplex csinh(
    method csqrt (line 424) | _Dcomplex csqrt(
    method ctan (line 436) | _Dcomplex ctan(
    method ctanh (line 448) | _Dcomplex ctanh(
    method norm (line 460) | double norm(
    method cabsf (line 472) | double cabsf(
    method cacosf (line 484) | _Fcomplex cacosf(
    method cacoshf (line 496) | _Fcomplex cacoshf(
    method cargf (line 508) | double cargf(
    method casinf (line 520) | _Fcomplex casinf(
    method casinhf (line 532) | _Fcomplex casinhf(
    method catanf (line 544) | _Fcomplex catanf(
    method catanhf (line 556) | _Fcomplex catanhf(
    method ccosf (line 568) | _Fcomplex ccosf(
    method ccoshf (line 580) | _Fcomplex ccoshf(
    method cexpf (line 592) | _Fcomplex cexpf(
    method cimagf (line 604) | double cimagf(
    method clogf (line 616) | _Fcomplex clogf(
    method clog10f (line 628) | _Fcomplex clog10f(
    method conjf (line 640) | _Fcomplex conjf(
    method cpowf (line 652) | _Fcomplex cpowf(
    method cprojf (line 668) | _Fcomplex cprojf(
    method crealf (line 680) | double crealf(
    method csinf (line 692) | _Fcomplex csinf(
    method csinhf (line 704) | _Fcomplex csinhf(
    method csqrtf (line 716) | _Fcomplex csqrtf(
    method ctanf (line 728) | _Fcomplex ctanf(
    method ctanhf (line 740) | _Fcomplex ctanhf(
    method normf (line 752) | double normf(
    method _Cbuild (line 764) | _Dcomplex _Cbuild(
    method _Cmulcc (line 780) | _Dcomplex _Cmulcc(
    method _Cmulcr (line 796) | _Dcomplex _Cmulcr(
    method _FCbuild (line 812) | _Fcomplex _FCbuild(
    method _FCmulcc (line 828) | _Fcomplex _FCmulcc(
    method _FCmulcr (line 844) | _Fcomplex _FCmulcr(
    method Start (line 860) | Start_return Start(
    method Stop (line 874) | void Stop()
  type va_list (line 883) | typedef va_list = ffi.Pointer<ffi.Char>;
  class __crt_locale_data_public (line 885) | final class __crt_locale_data_public extends ffi.Struct {
    method _locale_pctype (line 886) | ffi.Pointer<ffi.UnsignedShort> _locale_pctype;
  class __crt_locale_pointers (line 895) | final class __crt_locale_pointers extends ffi.Struct {
    method UnsignedLong (line 896) | ffi.Pointer<__crt_locale_data> locinfo;
  type errno_t (line 916) | typedef errno_t = ffi.Int;
  class _GoString_ (line 918) | final class _GoString_ extends ffi.Struct {
    method ptrdiff_t (line 919) | ffi.Pointer<ffi.Char> p;
  type ptrdiff_t (line 925) | typedef ptrdiff_t = ffi.LongLong;

FILE: ui/flutter/lib/core/libgopeed_boot.dart
  class LibgopeedBoot (line 6) | abstract class LibgopeedBoot {
    method start (line 16) | Future<int> start(StartConfig cfg)
    method stop (line 18) | Future<void> stop()

FILE: ui/flutter/lib/core/libgopeed_boot_stub.dart
  function create (line 3) | LibgopeedBoot create()

FILE: ui/flutter/lib/database/database.dart
  class Database (line 17) | class Database {
    method init (line 30) | Future<void> init()
    method save (line 35) | void save<T>(String key, T entity)
    method get (line 39) | T? get<T>(String key, T Function(dynamic json) fromJsonT)
    method clear (line 47) | void clear(String key)
    method saveStartConfig (line 51) | void saveStartConfig(StartConfigEntity entity)
    method getStartConfig (line 55) | StartConfigEntity? getStartConfig()
    method saveWindowState (line 61) | void saveWindowState(WindowStateEntity entity)
    method getWindowState (line 69) | WindowStateEntity? getWindowState()
    method saveBookmark (line 75) | void saveBookmark(MapEntry<String, String> entry)
    method getBookmark (line 81) | Map<String, String>? getBookmark()
    method saveWebToken (line 88) | void saveWebToken(String token)
    method getWebToken (line 92) | String? getWebToken()
    method saveCreateHistory (line 96) | void saveCreateHistory(String url)
    method getCreateHistory (line 106) | List<String>? getCreateHistory()
    method clearCreateHistory (line 112) | void clearCreateHistory()
    method saveRunAsMenubarApp (line 116) | void saveRunAsMenubarApp(bool value)
    method getRunAsMenubarApp (line 120) | bool getRunAsMenubarApp()
    method saveAnalyticsEnabled (line 124) | void saveAnalyticsEnabled(bool value)
    method getAnalyticsEnabled (line 128) | bool getAnalyticsEnabled()
    method saveAnalyticsClientId (line 132) | void saveAnalyticsClientId(String clientId)
    method getAnalyticsClientId (line 136) | String? getAnalyticsClientId()

FILE: ui/flutter/lib/database/entity.dart
  class StartConfigEntity (line 5) | @JsonSerializable()
    method toJson (line 19) | Map<String, dynamic> toJson()
  class WindowStateEntity (line 22) | @JsonSerializable()
    method toJson (line 36) | Map<String, dynamic> toJson()

FILE: ui/flutter/lib/database/entity.g.dart
  function _$StartConfigEntityFromJson (line 9) | StartConfigEntity _$StartConfigEntityFromJson(Map<String, dynamic> json)
  function _$StartConfigEntityToJson (line 16) | Map<String, dynamic> _$StartConfigEntityToJson(StartConfigEntity instance)
  function _$WindowStateEntityFromJson (line 23) | WindowStateEntity _$WindowStateEntityFromJson(Map<String, dynamic> json)
  function _$WindowStateEntityToJson (line 30) | Map<String, dynamic> _$WindowStateEntityToJson(WindowStateEntity instance)
  function writeNotNull (line 33) | void writeNotNull(String key, dynamic value)

FILE: ui/flutter/lib/i18n/message.dart
  class _Messages (line 25) | class _Messages extends Translations {

FILE: ui/flutter/lib/icon/gopeed_icons.dart
  class Gopeed (line 30) | class Gopeed {

FILE: ui/flutter/lib/main.dart
  class StartupArgs (line 25) | class StartupArgs {
    method parse (line 34) | StartupArgs parse(List<String> arguments)
  function main (line 47) | void main(List<String> arguments)
  function init (line 58) | Future<void> init(StartupArgs args)
  function onStart (line 168) | Future<void> onStart()

FILE: ui/flutter/lib/theme/theme.dart
  class GopeedTheme (line 3) | class GopeedTheme {

FILE: ui/flutter/lib/util/analytics.dart
  class Config (line 13) | class Config {
  class Analytics (line 23) | class Analytics {
    method init (line 34) | Future<void> init()
    method _getDeviceId (line 47) | Future<String> _getDeviceId()
    method _getPlatform (line 86) | String _getPlatform()
    method logAppOpen (line 96) | Future<void> logAppOpen()
    method logEvent (line 100) | Future<void> logEvent(String name, [Map<String, dynamic>? params])

FILE: ui/flutter/lib/util/arch/arch.dart
  type Architecture (line 6) | enum Architecture {
  function getArch (line 15) | Architecture getArch()

FILE: ui/flutter/lib/util/arch/arch_stub.dart
  function doGetArch (line 3) | Architecture doGetArch()

FILE: ui/flutter/lib/util/arch/entry/arch_native.dart
  function doGetArch (line 6) | Architecture doGetArch()

FILE: ui/flutter/lib/util/arch/entry/arch_web.dart
  function doGetArch (line 3) | Architecture doGetArch()

FILE: ui/flutter/lib/util/browser_download/browser_download.dart
  function download (line 4) | void download(String url, String name)

FILE: ui/flutter/lib/util/browser_download/browser_download_stub.dart
  function doDownload (line 1) | void doDownload(String url, String name)

FILE: ui/flutter/lib/util/browser_download/entry/browser_download_browser.dart
  function doDownload (line 4) | void doDownload(String url, String name)

FILE: ui/flutter/lib/util/browser_extension_host/browser_extension_host.dart
  type Browser (line 4) | enum Browser { chrome, edge, firefox }
  function installHost (line 7) | Future<void> installHost()
  function checkBrowserInstalled (line 10) | Future<bool> checkBrowserInstalled(Browser browser)
  function checkManifestInstalled (line 14) | Future<bool> checkManifestInstalled(Browser browser)
  function installManifest (line 18) | Future<void> installManifest(Browser browser)

FILE: ui/flutter/lib/util/browser_extension_host/browser_extension_host_stub.dart
  function doInstallHost (line 3) | Future<void> doInstallHost()
  function doCheckBrowserInstalled (line 4) | Future<bool> doCheckBrowserInstalled(Browser browser)
  function doCheckManifestInstalled (line 6) | Future<bool> doCheckManifestInstalled(Browser browser)
  function doInstallManifest (line 8) | Future<void> doInstallManifest(Browser browser)

FILE: ui/flutter/lib/util/browser_extension_host/entry/browser_extension_host_native.dart
  function doInstallHost (line 32) | Future<void> doInstallHost()
  function doCheckBrowserInstalled (line 39) | Future<bool> doCheckBrowserInstalled(Browser browser)
  function doCheckManifestInstalled (line 67) | Future<bool> doCheckManifestInstalled(Browser browser)
  function doInstallManifest (line 90) | Future<void> doInstallManifest(Browser browser)
  function _checkWindowsExecutable (line 106) | Future<bool> _checkWindowsExecutable(Browser browser)
  function _getWindowsExecutablePaths (line 116) | List<String> _getWindowsExecutablePaths(Browser browser)
  function _checkUnixExecutable (line 160) | Future<bool> _checkUnixExecutable(Browser browser)
  function _getUnixExecutablePaths (line 171) | List<String> _getUnixExecutablePaths(Browser browser)
  function _getManifestPath (line 228) | Future<String?> _getManifestPath(Browser browser)
  function _checkWindowsRegistry (line 267) | Future<bool> _checkWindowsRegistry(String keyPath)
  function _getManifestContent (line 277) | Future<String> _getManifestContent(Browser browser)
  function _getWindowsRegistryKey (line 295) | String _getWindowsRegistryKey(Browser browser)

FILE: ui/flutter/lib/util/extensions.dart
  function toTreeNodes (line 9) | List<TreeNode<int>> toTreeNodes()

FILE: ui/flutter/lib/util/file_explorer.dart
  class FileExplorer (line 6) | class FileExplorer {
    method openAndSelectFile (line 7) | Future<void> openAndSelectFile(String filePath)
    method _openDirectory (line 21) | Future<void> _openDirectory(String directoryPath)
    method _openFile (line 29) | Future<void> _openFile(String filePath)

FILE: ui/flutter/lib/util/github_mirror.dart
  type MirrorType (line 7) | enum MirrorType {
  function _getConfiguredMirrors (line 13) | List<GithubMirror> _getConfiguredMirrors()
  function githubAutoMirror (line 31) | Future<String> githubAutoMirror(String rawUrl, MirrorType type)
  function githubMirrorUrls (line 68) | List<String> githubMirrorUrls(String rawUrl, MirrorType type)

FILE: ui/flutter/lib/util/input_formatter.dart
  class NumericalRangeFormatter (line 4) | class NumericalRangeFormatter extends TextInputFormatter {
    method formatEditUpdate (line 11) | TextEditingValue formatEditUpdate(

FILE: ui/flutter/lib/util/locale_manager.dart
  function toLocale (line 3) | Locale toLocale(String key)
  function getLocaleKey (line 8) | String getLocaleKey(Locale locale)

FILE: ui/flutter/lib/util/log_util.dart
  function initLogger (line 9) | initLogger()
  function logsDir (line 18) | String logsDir()
  function _buildOutput (line 22) | _buildOutput()

FILE: ui/flutter/lib/util/message.dart
  function showErrorMessage (line 5) | void showErrorMessage(msg)
  function showMessage (line 27) | void showMessage(title, msg)

FILE: ui/flutter/lib/util/package_info.dart
  function initPackageInfo (line 5) | Future<void> initPackageInfo()

FILE: ui/flutter/lib/util/scheme_register/entry/scheme_register_native.dart
  function doRegisterUrlScheme (line 8) | doRegisterUrlScheme(String scheme)
  function doUnregisterUrlScheme (line 26) | doUnregisterUrlScheme(String scheme)
  function doRegisterDefaultTorrentClient (line 40) | doRegisterDefaultTorrentClient()
  function doUnregisterDefaultTorrentClient (line 70) | doUnregisterDefaultTorrentClient()

FILE: ui/flutter/lib/util/scheme_register/scheme_register.dart
  function registerUrlScheme (line 4) | registerUrlScheme(String scheme)
  function unregisterUrlScheme (line 6) | unregisterUrlScheme(String scheme)
  function registerDefaultTorrentClient (line 8) | registerDefaultTorrentClient()
  function unregisterDefaultTorrentClient (line 10) | unregisterDefaultTorrentClient()

FILE: ui/flutter/lib/util/scheme_register/scheme_register_stub.dart
  function doRegisterUrlScheme (line 1) | doRegisterUrlScheme(String scheme)
  function doUnregisterUrlScheme (line 3) | doUnregisterUrlScheme(String scheme)
  function doRegisterDefaultTorrentClient (line 5) | doRegisterDefaultTorrentClient()
  function doUnregisterDefaultTorrentClient (line 7) | doUnregisterDefaultTorrentClient()

FILE: ui/flutter/lib/util/updater.dart
  type Channel (line 23) | enum Channel {
  function installUpdater (line 54) | Future<void> installUpdater()
  class VersionInfo (line 60) | class VersionInfo {
  function checkUpdate (line 67) | Future<VersionInfo?> checkUpdate()
  function showUpdateDialog (line 107) | Future<void> showUpdateDialog(
  function _parseMarkdown (line 265) | List<Widget> _parseMarkdown(String markdown, BuildContext context)
  function _update (line 323) | Future<void> _update(String version, Function(int, int) onProgress)
  function _getAssetName (line 396) | String _getAssetName(String version)
  function commonArchName (line 399) | String commonArchName()
  function _getAssetPath (line 433) | Future<String> _getAssetPath(String version)

FILE: ui/flutter/lib/util/util.dart
  class Util (line 11) | class Util {
    method cleanPath (line 14) | String cleanPath(String path)
    method safeDir (line 25) | String safeDir(String path)
    method safePathJoin (line 32) | String safePathJoin(List<String> paths)
    method fmtByte (line 40) | String fmtByte(int byte)
    method initStorageDir (line 54) | Future<void> initStorageDir()
    method getStorageDir (line 77) | String getStorageDir()
    method isAndroid (line 81) | isAndroid()
    method isIOS (line 85) | isIOS()
    method isMobile (line 89) | isMobile()
    method isDesktop (line 93) | isDesktop()
    method isWindows (line 100) | isWindows()
    method isMacos (line 104) | isMacos()
    method isLinux (line 108) | isLinux()
    method isWeb (line 112) | isWeb()
    method supportUnixSocket (line 116) | supportUnixSocket()
    method textToLines (line 123) | List<String> textToLines(String text)
    method anyOk (line 132) | anyOk<T>(Iterable<Future<T>> futures)
    method debounce (line 152) | void Function() debounce(Function() fn, int ms)
    method homePathJoin (line 160) | Future<String> homePathJoin(String fileName)
    method installAsset (line 171) | Future<void> installAsset(String assetPath, String targetPath,
    method getAssetData (line 173) | Future<List<int>> getAssetData()
    method _md5 (line 198) | String _md5(List<int> data)
    method _md5File (line 202) | Future<String> _md5File(File file)

FILE: ui/flutter/lib/util/win32.dart
  function checkRegistry (line 5) | checkRegistry(String keyPath, String valueName, String value)
  function upsertRegistry (line 18) | upsertRegistry(String keyPath, String valueName, String value)

FILE: ui/flutter/linux/main.cc
  function main (line 3) | int main(int argc, char** argv) {

FILE: ui/flutter/linux/my_application.cc
  type _MyApplication (line 10) | struct _MyApplication {
  function my_application_activate (line 18) | static void my_application_activate(GApplication* application) {
  function gboolean (line 74) | static gboolean my_application_local_command_line(GApplication* applicat...
  function my_application_dispose (line 93) | static void my_application_dispose(GObject* object) {
  function my_application_class_init (line 99) | static void my_application_class_init(MyApplicationClass* klass) {
  function my_application_init (line 105) | static void my_application_init(MyApplication* self) {}
  function MyApplication (line 107) | MyApplication* my_application_new() {

FILE: ui/flutter/test/widget_test.dart
  function main (line 10) | void main()

FILE: ui/flutter/windows/runner/flutter_window.cpp
  function LRESULT (line 49) | LRESULT

FILE: ui/flutter/windows/runner/flutter_window.h
  function class (line 12) | class FlutterWindow : public Win32Window {

FILE: ui/flutter/windows/runner/main.cpp
  function SendAppLinkToInstance (line 9) | bool SendAppLinkToInstance(const std::wstring& title) {
  function wWinMain (line 44) | int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,

FILE: ui/flutter/windows/runner/utils.cpp
  function CreateAndAttachConsole (line 10) | void CreateAndAttachConsole() {
  function GetCommandLineArguments (line 24) | std::vector<std::string> GetCommandLineArguments() {
  function Utf8FromUtf16 (line 44) | std::string Utf8FromUtf16(const wchar_t* utf16_string) {

FILE: ui/flutter/windows/runner/win32_window.cpp
  function Scale (line 36) | int Scale(int source, double scale_factor) {
  function EnableFullDpiSupportIfAvailable (line 42) | void EnableFullDpiSupportIfAvailable(HWND hwnd) {
  class WindowClassRegistrar (line 59) | class WindowClassRegistrar {
    method WindowClassRegistrar (line 64) | static WindowClassRegistrar* GetInstance() {
    method WindowClassRegistrar (line 80) | WindowClassRegistrar() = default;
  function wchar_t (line 89) | const wchar_t* WindowClassRegistrar::GetWindowClass() {
  function LRESULT (line 157) | LRESULT CALLBACK Win32Window::WndProc(HWND const window,
  function LRESULT (line 176) | LRESULT
  function Win32Window (line 236) | Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept {
  function RECT (line 252) | RECT Win32Window::GetClientArea() {
  function HWND (line 258) | HWND Win32Window::GetHandle() {

FILE: ui/flutter/windows/runner/win32_window.h
  type Size (line 21) | struct Size {
Condensed preview — 419 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,984K chars).
[
  {
    "path": ".dockerignore",
    "chars": 106,
    "preview": ".git\n\n*.data\n*.log\n\nnode_modules\n**/node_modules\n\nDockerfile\n.dockerignore\n\n.github\n_docs\n_examples\nbin\nui"
  },
  {
    "path": ".github/CODEOWNERS",
    "chars": 13,
    "preview": "* @monkeyWie\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "chars": 794,
    "preview": "# These are supported funding model platforms\n\n#github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., ["
  },
  {
    "path": ".github/ISSUE_TEMPLATE.md",
    "chars": 161,
    "preview": "<!--\nPlease use the following template to create an issue.\n-->\n### Description(required)\n### App Version(required)\n### O"
  },
  {
    "path": ".github/release-drafter.yml",
    "chars": 6312,
    "preview": "name-template: \"v$NEXT_PATCH_VERSION 🌈\"\ntag-template: \"v$NEXT_PATCH_VERSION\"\ncategories:\n  - title: \"🆕 Features\"\n    lab"
  },
  {
    "path": ".github/workflows/build.yml",
    "chars": 32677,
    "preview": "name: build\n\non:\n  workflow_dispatch:\n    inputs:\n      platform:\n        description: \"Build platform\"\n        required"
  },
  {
    "path": ".github/workflows/release.yml",
    "chars": 810,
    "preview": "name: release\n\non:\n  push:\n    branches:\n      - main\n    paths:\n      - \"bind/**\"\n      - \"cmd/**\"\n      - \"internal/**"
  },
  {
    "path": ".github/workflows/scripts/flutter_local_font.dart",
    "chars": 15536,
    "preview": "// Localize external Google Fonts (fonts.gstatic.com) references in Flutter Web build output.\n//\n// Usage (post-build):\n"
  },
  {
    "path": ".github/workflows/test.yml",
    "chars": 7209,
    "preview": "name: test\n\non:\n  pull_request:\n    branches:\n      - main\n    paths:\n      - \"bind/**\"\n      - \"cmd/**\"\n      - \"intern"
  },
  {
    "path": ".github/workflows/translator.yml.bak",
    "chars": 716,
    "preview": "# name: 'translator'\n\n# on:\n#   issues:\n#     types: [opened, edited]\n#   issue_comment:\n#     types: [created, edited]\n"
  },
  {
    "path": ".gitignore",
    "chars": 368,
    "preview": "# Binaries for programs and plugins\n*.exe\n*.exe~\n*.dll\n*.so\n*.dylib\nui/flutter/dist/\n# Test binary, built with `go test "
  },
  {
    "path": "CONTRIBUTING.md",
    "chars": 1291,
    "preview": "# Gopeed contributors guide\n\nFirstly, thank you for your interest in contributing to Gopeed. This guide will help you be"
  },
  {
    "path": "CONTRIBUTING_ja-JP.md",
    "chars": 759,
    "preview": "# Gopeed コントリビューターガイド\n\nまず最初に、Gopeed への貢献に興味を持っていただきありがとうございます。このガイドは、あなたが Gopeed の\n開発に参加するための手助けとなるでしょう。\n\n## ブランチの説明\n\nこの"
  },
  {
    "path": "CONTRIBUTING_vi-VN.md",
    "chars": 1124,
    "preview": "# Hướng dẫn đóng góp cho Gopeed\n\nTrướ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 "
  },
  {
    "path": "CONTRIBUTING_zh-CN.md",
    "chars": 541,
    "preview": "# Gopeed 贡献指南\n\n首先感谢您对贡献代码感兴趣,这份指南将帮助您更好的参与到 Gopeed 的开发中来。\n\n## 分支说明\n\n本项目只有一个主分支,即 `main` 分支,如果您想要参与到 Gopeed 的开发中来,请先 fork"
  },
  {
    "path": "CONTRIBUTING_zh-TW.md",
    "chars": 528,
    "preview": "# Gopeed 協助指南\n\n首先感謝您願意幫助我們改進並優化該項目,這份指南將會幫助您更好的參與 Gopeed 的開發。\n\n## 分支說明\n\n本項目只有一個分支,即 `main` 分支,如果您想要參與 Gopeed 的開發,請先 fork"
  },
  {
    "path": "Dockerfile",
    "chars": 704,
    "preview": "FROM golang:1.24.11-alpine3.23 AS go\nWORKDIR /app\nCOPY ./go.mod ./go.sum ./\nRUN go mod download\nCOPY . .\nARG VERSION=dev"
  },
  {
    "path": "LICENSE",
    "chars": 35149,
    "preview": "                    GNU GENERAL PUBLIC LICENSE\n                       Version 3, 29 June 2007\n\n Copyright (C) 2007 Free "
  },
  {
    "path": "README.md",
    "chars": 10491,
    "preview": "# [![](_docs/img/banner.svg)](https://gopeed.com)\n\n[![Test Status](https://github.com/GopeedLab/gopeed/workflows/test/ba"
  },
  {
    "path": "README_ja-JP.md",
    "chars": 9044,
    "preview": "# [![](_docs/img/banner.svg)](https://gopeed.com)\n\n[![Test Status](https://github.com/GopeedLab/gopeed/workflows/test/ba"
  },
  {
    "path": "README_vi-VN.md",
    "chars": 9943,
    "preview": "# [![](_docs/img/banner.svg)](https://gopeed.com)\n\n[![Trạng thái kiểm tra](https://github.com/GopeedLab/gopeed/workflows"
  },
  {
    "path": "README_zh-CN.md",
    "chars": 9136,
    "preview": "# [![](_docs/img/banner.svg)](https://gopeed.com)\n\n[![Test Status](https://github.com/GopeedLab/gopeed/workflows/test/ba"
  },
  {
    "path": "README_zh-TW.md",
    "chars": 8791,
    "preview": "# [![](_docs/img/banner.svg)](https://gopeed.com)\n\n[![Test Status](https://github.com/GopeedLab/gopeed/workflows/test/ba"
  },
  {
    "path": "_examples/basic/main.go",
    "chars": 647,
    "preview": "package main\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/GopeedLab/gopeed/pkg/base\"\n\t\"github.com/GopeedLab/gopeed/pkg/download\"\n\t\"git"
  },
  {
    "path": "bind/desktop/main.go",
    "chars": 545,
    "preview": "package main\n\nimport \"C\"\nimport (\n\t\"encoding/json\"\n\t\"github.com/GopeedLab/gopeed/pkg/rest\"\n\t\"github.com/GopeedLab/gopeed"
  },
  {
    "path": "bind/mobile/main.go",
    "chars": 419,
    "preview": "package libgopeed\n\n// #cgo LDFLAGS: -static-libstdc++\nimport \"C\"\nimport (\n\t\"encoding/json\"\n\t\"github.com/GopeedLab/gopeed"
  },
  {
    "path": "cmd/api/main.go",
    "chars": 302,
    "preview": "package main\n\nimport (\n\t\"github.com/GopeedLab/gopeed/cmd\"\n\t\"github.com/GopeedLab/gopeed/pkg/rest/model\"\n)\n\n// only for l"
  },
  {
    "path": "cmd/banner.txt",
    "chars": 357,
    "preview": "\n  _______   ______   .______    _______  _______  _______\n /  _____| /  __  \\  |   _  \\  |   ____||   ____||       \\\n| "
  },
  {
    "path": "cmd/gopeed/flags.go",
    "chars": 744,
    "preview": "package main\n\nimport (\n\t\"flag\"\n\t\"fmt\"\n\t\"os\"\n)\n\ntype args struct {\n\turl         string\n\tconnections *int\n\tdir         *st"
  },
  {
    "path": "cmd/gopeed/main.go",
    "chars": 2163,
    "preview": "package main\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"sync\"\n\n\t\"github.com/GopeedLab/gopeed/internal/fetcher\"\n\t\"github.com/GopeedLab"
  },
  {
    "path": "cmd/host/dail_other.go",
    "chars": 296,
    "preview": "//go:build !windows\n// +build !windows\n\npackage main\n\nimport (\n\t\"net\"\n\t\"os\"\n\t\"path/filepath\"\n)\n\nfunc Dial() (net.Conn, e"
  },
  {
    "path": "cmd/host/dail_windows.go",
    "chars": 153,
    "preview": "package main\n\nimport (\n\t\"net\"\n\n\t\"github.com/Microsoft/go-winio\"\n)\n\nfunc Dial() (net.Conn, error) {\n\treturn winio.DialPip"
  },
  {
    "path": "cmd/host/main.go",
    "chars": 4471,
    "preview": "package main\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/binary\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"net/http\""
  },
  {
    "path": "cmd/server.go",
    "chars": 1707,
    "preview": "package cmd\n\nimport (\n\t_ \"embed\"\n\t\"fmt\"\n\t\"github.com/GopeedLab/gopeed/pkg/base\"\n\t\"github.com/GopeedLab/gopeed/pkg/rest\"\n"
  },
  {
    "path": "cmd/updater/main.go",
    "chars": 2594,
    "preview": "package main\n\nimport (\n\t\"flag\"\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\t\"syscall\"\n\t\"time\"\n\n\t\"github.com/pkg/browser\"\n\t\"github.com/pkg/errors"
  },
  {
    "path": "cmd/updater/updater_darwin.go",
    "chars": 2052,
    "preview": "//go:build darwin\n\npackage main\n\nimport (\n\t\"fmt\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"strings\"\n)\n\nfunc install(killSignalChan c"
  },
  {
    "path": "cmd/updater/updater_linux.go",
    "chars": 1925,
    "preview": "//go:build linux\n\npackage main\n\nimport (\n\t\"fmt\"\n\t\"os/exec\"\n)\n\nfunc install(killSignalChan chan<- any, updateChannel, pac"
  },
  {
    "path": "cmd/updater/updater_windows.go",
    "chars": 2789,
    "preview": "//go:build windows\n\npackage main\n\nimport (\n\t\"archive/zip\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"strings\"\n)\n\nf"
  },
  {
    "path": "cmd/web/flags.go",
    "chars": 5389,
    "preview": "package main\n\nimport (\n\t\"encoding/json\"\n\t\"flag\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"reflect\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/Go"
  },
  {
    "path": "cmd/web/flags_test.go",
    "chars": 39395,
    "preview": "package main\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"reflect\"\n\t\"testing\"\n\n\t\"github.com/GopeedLab/gopeed/pkg/base\"\n)\n\nf"
  },
  {
    "path": "cmd/web/main.go",
    "chars": 1280,
    "preview": "//go:build web\n// +build web\n\npackage main\n\nimport (\n\t\"embed\"\n\t\"fmt\"\n\t\"io/fs\"\n\t\"os\"\n\t\"path/filepath\"\n\n\t\"github.com/Gopee"
  },
  {
    "path": "docker-compose.yml",
    "chars": 491,
    "preview": "services:\n    gopeed:\n      container_name: gopeed\n      ports:\n        - 9999:9999 # HTTP port (host:container)\n      e"
  },
  {
    "path": "entrypoint.sh",
    "chars": 100,
    "preview": "#!/bin/sh\n\nchown -R ${PUID}:${PGID} /app\n\numask ${UMASK}\n\nexec su-exec ${PUID}:${PGID} ./gopeed \"$@\""
  },
  {
    "path": "go.mod",
    "chars": 7484,
    "preview": "module github.com/GopeedLab/gopeed\n\ngo 1.24.9\n\ntoolchain go1.24.11\n\nrequire (\n\tgithub.com/anacrolix/torrent v1.60.1-0.20"
  },
  {
    "path": "go.sum",
    "chars": 83967,
    "preview": "cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ncloud.google.com/go v0.34.0/go.mod h1"
  },
  {
    "path": "internal/controller/controller.go",
    "chars": 2182,
    "preview": "package controller\n\nimport (\n\t\"github.com/GopeedLab/gopeed/pkg/base\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"os\"\n\t\"path/filepath\"\n)\n\nty"
  },
  {
    "path": "internal/fetcher/fetcher.go",
    "chars": 4798,
    "preview": "package fetcher\n\nimport (\n\t\"path\"\n\t\"strings\"\n\n\t\"github.com/GopeedLab/gopeed/internal/controller\"\n\t\"github.com/GopeedLab/"
  },
  {
    "path": "internal/fetcher/fetcher_test.go",
    "chars": 1691,
    "preview": "package fetcher\n\nimport \"testing\"\n\nfunc TestSchemeFilter_Match(t *testing.T) {\n\ttype fields struct {\n\t\tType    FilterTyp"
  },
  {
    "path": "internal/logger/logger.go",
    "chars": 941,
    "preview": "package logger\n\nimport (\n\t\"github.com/GopeedLab/gopeed/pkg/util\"\n\t\"github.com/rs/zerolog\"\n\t\"io\"\n\t\"os\"\n\t\"path/filepath\"\n)"
  },
  {
    "path": "internal/logger/logger_test.go",
    "chars": 597,
    "preview": "package logger\n\nimport (\n\t\"os\"\n\t\"testing\"\n)\n\nfunc TestNewLogger(t *testing.T) {\n\tlogger := NewLogger(false, \"\")\n\tlogger."
  },
  {
    "path": "internal/protocol/bt/config.go",
    "chars": 459,
    "preview": "package bt\n\ntype config struct {\n\tListenPort int      `json:\"listenPort\"`\n\tTrackers   []string `json:\"trackers\"`\n\t// See"
  },
  {
    "path": "internal/protocol/bt/dns_cache_resolver.go",
    "chars": 954,
    "preview": "package bt\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"time\"\n\n\t\"github.com/rs/dnscache\"\n)\n\n// DnsCacheResolver resolves DNS requests f"
  },
  {
    "path": "internal/protocol/bt/fetcher.go",
    "chars": 13452,
    "preview": "package bt\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/url\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic"
  },
  {
    "path": "internal/protocol/bt/fetcher_test.go",
    "chars": 9602,
    "preview": "package bt\n\nimport (\n\t\"encoding/base64\"\n\t\"encoding/json\"\n\tgohttp \"net/http\"\n\t\"net/url\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"reflect\""
  },
  {
    "path": "internal/protocol/ed2k/config.go",
    "chars": 538,
    "preview": "package ed2k\n\nconst (\n\tdefaultServerList = \"45.82.80.155:5687,176.123.5.89:4725,85.121.5.137:4232,176.123.2.239:4232,145"
  },
  {
    "path": "internal/protocol/ed2k/fetcher.go",
    "chars": 10524,
    "preview": "package ed2k\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/GopeedLab/go"
  },
  {
    "path": "internal/protocol/ed2k/fetcher_test.go",
    "chars": 1360,
    "preview": "package ed2k\n\nimport (\n\t\"testing\"\n\n\t\"github.com/GopeedLab/gopeed/internal/controller\"\n\t\"github.com/GopeedLab/gopeed/pkg/"
  },
  {
    "path": "internal/protocol/http/config.go",
    "chars": 170,
    "preview": "package http\n\ntype config struct {\n\tUserAgent      string `json:\"userAgent\"`\n\tConnections    int    `json:\"connections\"`"
  },
  {
    "path": "internal/protocol/http/fetcher.go",
    "chars": 44272,
    "preview": "package http\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"os\"\n\t\"path\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sy"
  },
  {
    "path": "internal/protocol/http/fetcher_manager.go",
    "chars": 2537,
    "preview": "package http\n\nimport (\n\t\"net/url\"\n\t\"path\"\n\n\t\"github.com/GopeedLab/gopeed/internal/fetcher\"\n\t\"github.com/GopeedLab/gopeed"
  },
  {
    "path": "internal/protocol/http/fetcher_test.go",
    "chars": 43718,
    "preview": "package http\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net\"\n\tgohttp \"net/http\"\n\t\"net/url\"\n\t\"os\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n"
  },
  {
    "path": "internal/protocol/http/filename_parse_test.go",
    "chars": 3198,
    "preview": "package http\n\nimport (\n\t\"testing\"\n)\n\n// TestParseFilenameWithAmpersand tests the fix for filenames containing & characte"
  },
  {
    "path": "internal/protocol/http/helper.go",
    "chars": 13300,
    "preview": "package http\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"crypto/tls\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"mime\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/http/co"
  },
  {
    "path": "internal/protocol/http/timeout_reader.go",
    "chars": 652,
    "preview": "package http\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"time\"\n)\n\ntype TimeoutReader struct {\n\treader  io.Reader\n\ttimeout time.Duration"
  },
  {
    "path": "internal/protocol/http/timeout_reader_test.go",
    "chars": 1102,
    "preview": "package http\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\t\"testing\"\n\t\"time\"\n)\n\nfunc TestTimeoutReader_Read(t *testing."
  },
  {
    "path": "internal/test/httptest.go",
    "chars": 29310,
    "preview": "package test\n\nimport (\n\t\"context\"\n\t\"crypto/rand\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"net/http\"\n\t\"os\"\n\t\"refl"
  },
  {
    "path": "internal/test/util.go",
    "chars": 1073,
    "preview": "package test\n\nimport (\n\t\"crypto/md5\"\n\t\"encoding/hex\"\n\t\"encoding/json\"\n\t\"io\"\n\t\"os\"\n\t\"path/filepath\"\n)\n\nfunc FileMd5(fileP"
  },
  {
    "path": "pkg/base/constants.go",
    "chars": 857,
    "preview": "package base\n\ntype Status string\n\nconst (\n\tDownloadStatusReady   Status = \"ready\" // task create but not start\n\tDownload"
  },
  {
    "path": "pkg/base/info.go",
    "chars": 246,
    "preview": "package base\n\n// Version is the build version, set at build time, using `go build -ldflags \"-X github.com/GopeedLab/gope"
  },
  {
    "path": "pkg/base/model.go",
    "chars": 9498,
    "preview": "package base\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/GopeedLab/gopeed/pkg/util\"\n\t\"github.c"
  },
  {
    "path": "pkg/base/model_test.go",
    "chars": 9306,
    "preview": "package base\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n)\n\nfunc TestDownloaderStoreConfig_Init(t *testing.T) {\n\ttests := []struct {"
  },
  {
    "path": "pkg/download/downloader.go",
    "chars": 48276,
    "preview": "package download\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\tgohttp \"net/http\"\n\t\"net/url\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"runtime/debug"
  },
  {
    "path": "pkg/download/downloader_test.go",
    "chars": 72355,
    "preview": "package download\n\nimport (\n\t\"archive/zip\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\tgohttp \"net/http\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"str"
  },
  {
    "path": "pkg/download/engine/engine.go",
    "chars": 4592,
    "preview": "package engine\n\nimport (\n\t_ \"embed\"\n\t\"errors\"\n\t\"github.com/GopeedLab/gopeed/pkg/base\"\n\tgojaerror \"github.com/GopeedLab/g"
  },
  {
    "path": "pkg/download/engine/engine_test.go",
    "chars": 13119,
    "preview": "package engine\n\nimport (\n\t\"crypto/md5\"\n\t\"encoding/hex\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"net/http\"\n\t\"strc"
  },
  {
    "path": "pkg/download/engine/inject/error/module.go",
    "chars": 641,
    "preview": "package error\n\nimport (\n\t\"github.com/dop251/goja\"\n)\n\ntype MessageError struct {\n\tMessage string `json:\"message\"`\n}\n\nfunc"
  },
  {
    "path": "pkg/download/engine/inject/file/module.go",
    "chars": 715,
    "preview": "package file\n\nimport (\n\t\"errors\"\n\t\"github.com/dop251/goja\"\n\t\"io\"\n)\n\ntype File struct {\n\tio.Reader `json:\"\"`\n\tio.Closer `"
  },
  {
    "path": "pkg/download/engine/inject/formdata/module.go",
    "chars": 1315,
    "preview": "package formdata\n\nimport \"github.com/dop251/goja\"\n\ntype FormData struct {\n\tdata map[string]any\n}\n\nfunc (fd *FormData) Ap"
  },
  {
    "path": "pkg/download/engine/inject/vm/module.go",
    "chars": 835,
    "preview": "package vm\n\nimport (\n\t\"github.com/dop251/goja\"\n\t\"github.com/dop251/goja_nodejs/eventloop\"\n)\n\ntype Vm struct {\n\tloop *eve"
  },
  {
    "path": "pkg/download/engine/inject/xhr/module.go",
    "chars": 12029,
    "preview": "package xhr\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"io\"\n\t\"mime/multipart\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"strings\"\n\t\"time\"\n\n\t\"git"
  },
  {
    "path": "pkg/download/engine/inject/xhr/tls_fingerprint.go",
    "chars": 481,
    "preview": "package xhr\n\nimport \"github.com/imroc/req/v3\"\n\ntype Fingerprint string\n\nconst (\n\tFingerprintMagicKey = \"__gopeed_xhr_fin"
  },
  {
    "path": "pkg/download/engine/polyfill/out/index.js",
    "chars": 21551,
    "preview": "(()=>{var e={725:function(e,t,r){var o,n,i;i=\"undefined\"!=typeof self&&self||\"undefined\"!=typeof window&&window||void 0!"
  },
  {
    "path": "pkg/download/engine/polyfill/package.json",
    "chars": 608,
    "preview": "{\n  \"name\": \"polyfill\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"type\": \"module\",\n  \"scripts\""
  },
  {
    "path": "pkg/download/engine/polyfill/patches/whatwg-fetch+3.6.20.patch",
    "chars": 1746,
    "preview": "diff --git a/node_modules/whatwg-fetch/dist/fetch.umd.js b/node_modules/whatwg-fetch/dist/fetch.umd.js\nindex 7a0d852..60"
  },
  {
    "path": "pkg/download/engine/polyfill/src/blob/index.js",
    "chars": 62,
    "preview": "import { Blob } from \"blob-polyfill\";\n\nglobalThis.Blob = Blob;"
  },
  {
    "path": "pkg/download/engine/polyfill/src/crypto/index.js",
    "chars": 417,
    "preview": "globalThis.crypto = {\n    getRandomValues(arr) {\n        for (let i = 0, len = arr.length; i < len; i++) {\n            a"
  },
  {
    "path": "pkg/download/engine/polyfill/src/fetch/index.js",
    "chars": 21,
    "preview": "import 'whatwg-fetch'"
  },
  {
    "path": "pkg/download/engine/polyfill/src/index.js",
    "chars": 145,
    "preview": "import \"./blob/index.js\"\nimport \"./crypto/index.js\"\n// polyfill TextEncoder\nimport 'fastestsmallesttextencoderdecoder';\n"
  },
  {
    "path": "pkg/download/engine/polyfill/webpack.config.js",
    "chars": 249,
    "preview": "import path from \"path\";\nimport { fileURLToPath } from \"url\";\n\nconst __dirname = fileURLToPath(import.meta.url);\n\nexport"
  },
  {
    "path": "pkg/download/engine/util/util.go",
    "chars": 583,
    "preview": "package util\n\nimport (\n\t\"github.com/dop251/goja\"\n)\n\nfunc ThrowTypeError(vm *goja.Runtime, msg string) {\n\tpanic(vm.NewTyp"
  },
  {
    "path": "pkg/download/event.go",
    "chars": 313,
    "preview": "package download\n\ntype EventKey string\n\nconst (\n\tEventKeyStart    = \"start\"\n\tEventKeyPause    = \"pause\"\n\tEventKeyProgres"
  },
  {
    "path": "pkg/download/extension.go",
    "chars": 20518,
    "preview": "package download\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"path\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t"
  },
  {
    "path": "pkg/download/extension_test.go",
    "chars": 12382,
    "preview": "package download\n\nimport (\n\t\"errors\"\n\t\"os\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/GopeedLab/gopeed/internal/logger\"\n\t\"github.c"
  },
  {
    "path": "pkg/download/extract.go",
    "chars": 15212,
    "preview": "package download\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"math\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"regexp\"\n\t\"slices\"\n\t\"strings\"\n\t\"sync/atomic\""
  },
  {
    "path": "pkg/download/extract_7z.go",
    "chars": 2450,
    "preview": "package download\n\nimport (\n\t\"io\"\n\t\"os\"\n\t\"path/filepath\"\n\n\t\"github.com/bodgit/sevenzip\"\n)\n\n// extractSevenZipMultiPart ex"
  },
  {
    "path": "pkg/download/extract_queue.go",
    "chars": 4536,
    "preview": "package download\n\nimport (\n\t\"sync\"\n)\n\n// ExtractionJob represents a single extraction job in the queue\ntype ExtractionJo"
  },
  {
    "path": "pkg/download/extract_queue_test.go",
    "chars": 11478,
    "preview": "package download\n\nimport (\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"testing\"\n\t\"time\"\n)\n\nfunc TestNewExtractionQueue(t *testing.T) {\n\tq :"
  },
  {
    "path": "pkg/download/extract_rar.go",
    "chars": 1149,
    "preview": "package download\n\nimport (\n\t\"context\"\n\t\"os\"\n\t\"path/filepath\"\n\n\t\"github.com/mholt/archives\"\n)\n\n// extractRarMultiPart ext"
  },
  {
    "path": "pkg/download/extract_test.go",
    "chars": 63984,
    "preview": "package download\n\nimport (\n\t\"archive/tar\"\n\t\"archive/zip\"\n\t\"compress/gzip\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\""
  },
  {
    "path": "pkg/download/extract_zip.go",
    "chars": 4208,
    "preview": "package download\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/mholt/archives\"\n)\n\n//"
  },
  {
    "path": "pkg/download/model.go",
    "chars": 4173,
    "preview": "package download\n\nimport (\n\t\"encoding/json\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/GopeedLab/gopeed/internal/controller\"\n\t\"github"
  },
  {
    "path": "pkg/download/model_test.go",
    "chars": 464,
    "preview": "package download\n\nimport \"testing\"\n\nfunc TestCalcSpeedResetOnRollback(t *testing.T) {\n\tspeedArr := []int64{1024, 2048, 4"
  },
  {
    "path": "pkg/download/script.go",
    "chars": 4201,
    "preview": "package download\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"runtime\"\n\t\"time\"\n)\n\n// ScriptEvent represents the "
  },
  {
    "path": "pkg/download/script_test.go",
    "chars": 5322,
    "preview": "package download\n\nimport (\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/GopeedLab/gopeed/internal/fetcher\"\n\t\""
  },
  {
    "path": "pkg/download/script_unix_test.go",
    "chars": 3604,
    "preview": "//go:build !windows\n// +build !windows\n\npackage download\n\nimport (\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n"
  },
  {
    "path": "pkg/download/script_windows_test.go",
    "chars": 3448,
    "preview": "//go:build windows\n// +build windows\n\npackage download\n\nimport (\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t"
  },
  {
    "path": "pkg/download/storage.go",
    "chars": 5049,
    "preview": "package download\n\nimport (\n\t\"encoding/json\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"reflect\"\n\t\"sync\"\n\n\t\"go.etcd.io/bbolt\"\n)\n\ntype Stora"
  },
  {
    "path": "pkg/download/testdata/extensions/basic/index.js",
    "chars": 432,
    "preview": "gopeed.events.onResolve(async function (ctx) {\n    ctx.res = {\n        name: \"test\",\n        files: Array(2).fill(true)."
  },
  {
    "path": "pkg/download/testdata/extensions/basic/manifest.json",
    "chars": 359,
    "preview": "{\n  \"name\": \"basic\",\n  \"title\": \"gopeed extension basic test\",\n  \"version\": \"0.0.1\",\n  \"scripts\": [\n    {\n      \"event\":"
  },
  {
    "path": "pkg/download/testdata/extensions/extra/index.js",
    "chars": 503,
    "preview": "gopeed.events.onResolve(async function (ctx) {\n    ctx.res = {\n        name: \"test\",\n        files: Array(2).fill(true)."
  },
  {
    "path": "pkg/download/testdata/extensions/extra/manifest.json",
    "chars": 356,
    "preview": "{\n  \"name\": \"extra\",\n  \"title\": \"gopeed extension extra test\",\n  \"version\": \"0.0.1\",\n  \"scripts\": [\n    {\n      \"event\":"
  },
  {
    "path": "pkg/download/testdata/extensions/function_error/index.js",
    "chars": 422,
    "preview": "gopeed.events.onResolve(async function (ctx) {\n    const aaa = {};\n    // access undefined property\n    gopeed.logger.in"
  },
  {
    "path": "pkg/download/testdata/extensions/function_error/manifest.json",
    "chars": 377,
    "preview": "{\n  \"name\": \"function-error\",\n  \"title\": \"gopeed extension function error test\",\n  \"version\": \"0.0.1\",\n  \"scripts\": [\n  "
  },
  {
    "path": "pkg/download/testdata/extensions/message_error/index.js",
    "chars": 87,
    "preview": "gopeed.events.onResolve(async function (ctx) {\n    throw new MessageError(\"test\");\n});\n"
  },
  {
    "path": "pkg/download/testdata/extensions/message_error/manifest.json",
    "chars": 270,
    "preview": "{\n  \"name\": \"message-error\",\n  \"title\": \"gopeed extension message error test\",\n  \"version\": \"0.0.1\",\n  \"scripts\": [\n    "
  },
  {
    "path": "pkg/download/testdata/extensions/on_done/index.js",
    "chars": 154,
    "preview": "gopeed.events.onDone(async function (ctx) {\n    gopeed.logger.info(\"url\", ctx.task.meta.req.url);\n    ctx.task.meta.req."
  },
  {
    "path": "pkg/download/testdata/extensions/on_done/manifest.json",
    "chars": 261,
    "preview": "{\n  \"name\": \"on-done\",\n  \"title\": \"gopeed extension on done event test\",\n  \"version\": \"0.0.1\",\n  \"scripts\": [\n    {\n    "
  },
  {
    "path": "pkg/download/testdata/extensions/on_error/index.js",
    "chars": 223,
    "preview": "gopeed.events.onError(async function (ctx) {\n    gopeed.logger.info(\"url\", ctx.task.meta.req.url);\n    gopeed.logger.inf"
  },
  {
    "path": "pkg/download/testdata/extensions/on_error/manifest.json",
    "chars": 254,
    "preview": "{\n  \"name\": \"on-error\",\n  \"title\": \"gopeed extension on error event test\",\n  \"version\": \"0.0.1\",\n  \"scripts\": [\n    {\n  "
  },
  {
    "path": "pkg/download/testdata/extensions/on_start/index.js",
    "chars": 205,
    "preview": "gopeed.events.onStart(async function (ctx) {\n    gopeed.logger.info(\"url\", ctx.task.meta.req.url);\n    ctx.task.meta.req"
  },
  {
    "path": "pkg/download/testdata/extensions/on_start/manifest.json",
    "chars": 312,
    "preview": "{\n  \"name\": \"on-start\",\n  \"title\": \"gopeed extension on start event test\",\n  \"version\": \"0.0.1\",\n  \"scripts\": [\n    {\n  "
  },
  {
    "path": "pkg/download/testdata/extensions/script_error/index.js",
    "chars": 381,
    "preview": "const aaa = {};\ngopeed.logger.info(aaa.bbb.ccc);\n\ngopeed.events.onResolve(async function (ctx) {\n    ctx.res = {\n       "
  },
  {
    "path": "pkg/download/testdata/extensions/script_error/manifest.json",
    "chars": 373,
    "preview": "{\n  \"name\": \"script-error\",\n  \"title\": \"gopeed extension script error test\",\n  \"version\": \"0.0.1\",\n  \"scripts\": [\n    {\n"
  },
  {
    "path": "pkg/download/testdata/extensions/settings_all/index.js",
    "chars": 1311,
    "preview": "gopeed.events.onResolve(async function (ctx) {\n    if (gopeed.settings.string != null) {\n        throw new Error(\"string"
  },
  {
    "path": "pkg/download/testdata/extensions/settings_all/manifest.json",
    "chars": 1267,
    "preview": "{\n  \"name\": \"settings-all\",\n  \"title\": \"gopeed extension settings all type test\",\n  \"version\": \"0.0.1\",\n  \"scripts\": [\n "
  },
  {
    "path": "pkg/download/testdata/extensions/settings_empty/index.js",
    "chars": 438,
    "preview": "gopeed.events.onResolve(async function (ctx) {\n    if (Object.keys(gopeed.settings).length > 0){\n        throw new Error"
  },
  {
    "path": "pkg/download/testdata/extensions/settings_empty/manifest.json",
    "chars": 263,
    "preview": "{\n  \"name\": \"settings-empty\",\n  \"title\": \"gopeed extension settings empty test\",\n  \"version\": \"0.0.1\",\n  \"scripts\": [\n  "
  },
  {
    "path": "pkg/download/testdata/extensions/storage/index.js",
    "chars": 1316,
    "preview": "gopeed.events.onResolve(async function (ctx) {\n    const key = \"key\"\n    const value1 = \"value1\", value2 = JSON.stringif"
  },
  {
    "path": "pkg/download/testdata/extensions/storage/manifest.json",
    "chars": 354,
    "preview": "{\n  \"name\": \"storage\",\n  \"title\": \"gopeed extension storage test\",\n  \"version\": \"0.0.1\",\n  \"scripts\": [\n    {\n      \"eve"
  },
  {
    "path": "pkg/download/testdata/extensions/update/index.js",
    "chars": 78,
    "preview": "gopeed.events.onResolve(async function (ctx) {\n    // do nothing for test\n});\n"
  },
  {
    "path": "pkg/download/testdata/extensions/update/manifest.json",
    "chars": 989,
    "preview": "{\n  \"name\": \"extension-test\",\n  \"author\": \"gopeed\",\n  \"title\": \"Gopeed Extension Test\",\n  \"description\": \"Test extension"
  },
  {
    "path": "pkg/download/testdata/scripts/env_dump.bat",
    "chars": 416,
    "preview": "@echo off\nsetlocal\n\nif \"%GOPEED_TEST_OUTPUT_FILE%\"==\"\" exit /b 2\necho GOPEED_EVENT=%GOPEED_EVENT% > \"%GOPEED_TEST_OUTPUT"
  },
  {
    "path": "pkg/download/testdata/scripts/env_dump.sh",
    "chars": 473,
    "preview": "#!/bin/bash\nset -e\n\nif [ -z \"$GOPEED_TEST_OUTPUT_FILE\" ]; then\n  echo \"GOPEED_TEST_OUTPUT_FILE is empty\" >&2\n  exit 2\nfi"
  },
  {
    "path": "pkg/download/testdata/scripts/move.bat",
    "chars": 197,
    "preview": "@echo off\nsetlocal\n\nif \"%GOPEED_TEST_DEST_DIR%\"==\"\" exit /b 2\nset \"SRC=%GOPEED_TASK_PATH:/=\\%\"\nset \"DEST=%GOPEED_TEST_DE"
  },
  {
    "path": "pkg/download/testdata/scripts/move.sh",
    "chars": 197,
    "preview": "#!/bin/bash\nset -e\n\nif [ -z \"$GOPEED_TEST_DEST_DIR\" ]; then\n  echo \"GOPEED_TEST_DEST_DIR is empty\" >&2\n  exit 2\nfi\n\nmkdi"
  },
  {
    "path": "pkg/download/testdata/scripts/write_output1.bat",
    "chars": 113,
    "preview": "@echo off\nsetlocal\n\nif \"%GOPEED_TEST_OUTPUT_FILE_1%\"==\"\" exit /b 2\necho Script 1 > \"%GOPEED_TEST_OUTPUT_FILE_1%\"\n"
  },
  {
    "path": "pkg/download/testdata/scripts/write_output1.sh",
    "chars": 173,
    "preview": "#!/bin/bash\nset -e\n\nif [ -z \"$GOPEED_TEST_OUTPUT_FILE_1\" ]; then\n  echo \"GOPEED_TEST_OUTPUT_FILE_1 is empty\" >&2\n  exit "
  },
  {
    "path": "pkg/download/testdata/scripts/write_output2.bat",
    "chars": 113,
    "preview": "@echo off\nsetlocal\n\nif \"%GOPEED_TEST_OUTPUT_FILE_2%\"==\"\" exit /b 2\necho Script 2 > \"%GOPEED_TEST_OUTPUT_FILE_2%\"\n"
  },
  {
    "path": "pkg/download/testdata/scripts/write_output2.sh",
    "chars": 173,
    "preview": "#!/bin/bash\nset -e\n\nif [ -z \"$GOPEED_TEST_OUTPUT_FILE_2\" ]; then\n  echo \"GOPEED_TEST_OUTPUT_FILE_2 is empty\" >&2\n  exit "
  },
  {
    "path": "pkg/download/webhook.go",
    "chars": 5276,
    "preview": "package download\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"time\"\n\n\t\"github.com/GopeedLab/gopeed/internal/"
  },
  {
    "path": "pkg/download/webhook_test.go",
    "chars": 23080,
    "preview": "package download\n\nimport (\n\t\"encoding/json\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"os\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/Gope"
  },
  {
    "path": "pkg/protocol/bt/model.go",
    "chars": 537,
    "preview": "package bt\n\ntype ReqExtra struct {\n\tTrackers []string `json:\"trackers\"`\n}\n\n// Stats for torrent\ntype Stats struct {\n\t// "
  },
  {
    "path": "pkg/protocol/ed2k/model.go",
    "chars": 449,
    "preview": "package ed2k\n\ntype Stats struct {\n\tState         string `json:\"state\"`\n\tPaused        bool   `json:\"paused\"`\n\tActivePeer"
  },
  {
    "path": "pkg/protocol/http/model.go",
    "chars": 1441,
    "preview": "package http\n\ntype ReqExtra struct {\n\tMethod string            `json:\"method\"`\n\tHeader map[string]string `json:\"header\"`"
  },
  {
    "path": "pkg/rest/api.go",
    "chars": 10423,
    "preview": "package rest\n\nimport (\n\t\"io\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"runtime\"\n\n\t\"github.com/GopeedLab/gopeed/pkg/base\"\n\t\"github.com/Gop"
  },
  {
    "path": "pkg/rest/config.go",
    "chars": 91,
    "preview": "package rest\n\ntype Config struct {\n\tHost string `json:\"host\"`\n\tPort int    `json:\"port\"`\n}\n"
  },
  {
    "path": "pkg/rest/gizp_middleware.go",
    "chars": 672,
    "preview": "package rest\n\nimport (\n\t\"compress/gzip\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n)\n\ntype gzipResponseWriter struct {\n\tio.Writer\n\thtt"
  },
  {
    "path": "pkg/rest/model/extension.go",
    "chars": 337,
    "preview": "package model\n\ntype InstallExtension struct {\n\tDevMode bool   `json:\"devMode\"`\n\tURL     string `json:\"url\"`\n}\n\ntype Upda"
  },
  {
    "path": "pkg/rest/model/result.go",
    "chars": 935,
    "preview": "package model\n\ntype RespCode int\n\nconst (\n\tCodeOk RespCode = 0\n\t// CodeError is the common error code\n\tCodeError RespCod"
  },
  {
    "path": "pkg/rest/model/server.go",
    "chars": 1209,
    "preview": "package model\n\nimport (\n\t\"github.com/GopeedLab/gopeed/pkg/base\"\n\t\"io/fs\"\n)\n\ntype Storage string\n\nconst (\n\tStorageMem  St"
  },
  {
    "path": "pkg/rest/model/task.go",
    "chars": 278,
    "preview": "package model\n\nimport \"github.com/GopeedLab/gopeed/pkg/base\"\n\ntype ResolveTask struct {\n\tReq  *base.Request `json:\"req\"`"
  },
  {
    "path": "pkg/rest/model/webhook.go",
    "chars": 142,
    "preview": "package model\n\n// TestWebhookReq is the request body for testing a single webhook URL\ntype TestWebhookReq struct {\n\tURL "
  },
  {
    "path": "pkg/rest/server.go",
    "chars": 12229,
    "preview": "package rest\n\nimport (\n\t\"context\"\n\t\"crypto/aes\"\n\t\"crypto/cipher\"\n\t\"crypto/rand\"\n\t\"encoding/base64\"\n\t\"encoding/json\"\n\t\"fm"
  },
  {
    "path": "pkg/rest/server_test.go",
    "chars": 29170,
    "preview": "package rest\n\nimport (\n\t\"bytes\"\n\t\"crypto/md5\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"net/http\"\n\t\"os\"\n\t\"path/fi"
  },
  {
    "path": "pkg/util/bytefmt.go",
    "chars": 660,
    "preview": "package util\n\nimport (\n\t\"fmt\"\n\t\"math\"\n)\n\nconst unknownSize = \"unknown\"\n\nvar unitArr = []string{\"B\", \"KB\", \"MB\", \"GB\", \"T"
  },
  {
    "path": "pkg/util/bytefmt_test.go",
    "chars": 1325,
    "preview": "package util\n\nimport \"testing\"\n\nfunc TestByteFmt(t *testing.T) {\n\ttype args struct {\n\t\tsize int64\n\t}\n\ttests := []struct "
  },
  {
    "path": "pkg/util/json.go",
    "chars": 543,
    "preview": "package util\n\nimport \"encoding/json\"\n\nfunc MapToStruct(s any, v any) error {\n\tif s == nil {\n\t\treturn nil\n\t}\n\tb, err := j"
  },
  {
    "path": "pkg/util/json_test.go",
    "chars": 861,
    "preview": "package util\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n)\n\nfunc TestDeepClone(t *testing.T) {\n\ttype user struct {\n\t\tName string `js"
  },
  {
    "path": "pkg/util/matcher.go",
    "chars": 1530,
    "preview": "package util\n\nimport (\n\t\"net/url\"\n\t\"regexp\"\n\t\"strings\"\n)\n\n// Match url with pattern by chrome extension match pattern st"
  },
  {
    "path": "pkg/util/matcher_test.go",
    "chars": 1981,
    "preview": "package util\n\nimport \"testing\"\n\nfunc TestMatch(t *testing.T) {\n\ttests := []struct {\n\t\tpattern string\n\t\turls    []string\n"
  },
  {
    "path": "pkg/util/path.go",
    "chars": 9287,
    "preview": "package util\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\tsyspath \"path\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"time\"\n\t\"unicode/utf8\"\n"
  },
  {
    "path": "pkg/util/path_other.go",
    "chars": 96,
    "preview": "//go:build !windows\n// +build !windows\n\npackage util\n\nvar invalidPathChars = []string{`/`, `:`}\n"
  },
  {
    "path": "pkg/util/path_test.go",
    "chars": 12394,
    "preview": "package util\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n)\n\nfunc TestDir(t *testing.T) {\n\ttype args struct {\n\t\t"
  },
  {
    "path": "pkg/util/path_windows.go",
    "chars": 91,
    "preview": "package util\n\nvar invalidPathChars = []string{`\\`, `/`, `:`, `*`, `?`, `\"`, `<`, `>`, `|`}\n"
  },
  {
    "path": "pkg/util/timer.go",
    "chars": 353,
    "preview": "package util\n\nimport \"time\"\n\ntype Timer struct {\n\tt    int64\n\tused int64\n}\n\nfunc NewTimer(used int64) *Timer {\n\treturn &"
  },
  {
    "path": "pkg/util/url.go",
    "chars": 1990,
    "preview": "package util\n\nimport (\n\t\"encoding/base64\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"regexp\"\n\t\"strings\"\n)\n\nfunc ParseSchema(url string) st"
  },
  {
    "path": "pkg/util/url_test.go",
    "chars": 3428,
    "preview": "package util\n\nimport (\n\t\"encoding/base64\"\n\t\"reflect\"\n\t\"testing\"\n)\n\nfunc TestParseSchema(t *testing.T) {\n\ttype args struc"
  },
  {
    "path": "ui/flutter/.gitignore",
    "chars": 1410,
    "preview": "# Miscellaneous\n*.class\n*.log\n*.pyc\n*.swp\n.DS_Store\n.atom/\n.buildlog/\n.history\n.svn/\n\n# IntelliJ related\n*.iml\n*.ipr\n*.i"
  },
  {
    "path": "ui/flutter/.metadata",
    "chars": 930,
    "preview": "# This file tracks properties of this Flutter project.\n# Used by Flutter tool to assess capabilities and perform upgrade"
  },
  {
    "path": "ui/flutter/analysis_options.yaml",
    "chars": 1453,
    "preview": "# This file configures the analyzer, which statically analyzes Dart code to\n# check for errors, warnings, and lints.\n#\n#"
  },
  {
    "path": "ui/flutter/android/.gitignore",
    "chars": 285,
    "preview": "gradle-wrapper.jar\n/.gradle\n/captures/\n/gradlew\n/gradlew.bat\n/local.properties\nGeneratedPluginRegistrant.java\n\n# Remembe"
  },
  {
    "path": "ui/flutter/android/app/build.gradle",
    "chars": 2324,
    "preview": "plugins {\n    id 'com.android.application'\n    id 'kotlin-android'\n    id 'dev.flutter.flutter-gradle-plugin'\n}\n\ndef loc"
  },
  {
    "path": "ui/flutter/android/app/libs/.gitkeep",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "ui/flutter/android/app/src/debug/AndroidManifest.xml",
    "chars": 378,
    "preview": "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <!-- The INTERNET permission is required for d"
  },
  {
    "path": "ui/flutter/android/app/src/main/AndroidManifest.xml",
    "chars": 5957,
    "preview": "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <uses-permission android:name=\"android.permis"
  },
  {
    "path": "ui/flutter/android/app/src/main/kotlin/com/gopeed/gopeed/MainActivity.kt",
    "chars": 1483,
    "preview": "package com.gopeed.gopeed\n\nimport androidx.annotation.NonNull\nimport com.gopeed.libgopeed.Libgopeed\nimport io.flutter.em"
  },
  {
    "path": "ui/flutter/android/app/src/main/res/drawable/launch_background.xml",
    "chars": 434,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- Modify this file to customize your launch splash screen -->\n<layer-list xmln"
  },
  {
    "path": "ui/flutter/android/app/src/main/res/drawable-v21/launch_background.xml",
    "chars": 438,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- Modify this file to customize your launch splash screen -->\n<layer-list xmln"
  },
  {
    "path": "ui/flutter/android/app/src/main/res/values/styles.xml",
    "chars": 996,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <!-- Theme applied to the Android Window while the process is sta"
  },
  {
    "path": "ui/flutter/android/app/src/main/res/values-night/styles.xml",
    "chars": 995,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <!-- Theme applied to the Android Window while the process is sta"
  },
  {
    "path": "ui/flutter/android/app/src/profile/AndroidManifest.xml",
    "chars": 378,
    "preview": "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <!-- The INTERNET permission is required for d"
  },
  {
    "path": "ui/flutter/android/build.gradle",
    "chars": 1001,
    "preview": "allprojects {\n    repositories {\n        google()\n        mavenCentral()\n    }\n}\n\nrootProject.buildDir = '../build'\nsubp"
  },
  {
    "path": "ui/flutter/android/gradle/wrapper/gradle-wrapper.properties",
    "chars": 234,
    "preview": "#Fri Jun 23 08:50:38 CEST 2017\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\nzipStoreBase=GRADLE_USER"
  },
  {
    "path": "ui/flutter/android/gradle.properties",
    "chars": 220,
    "preview": "org.gradle.jvmargs=-Xmx4G -XX:+HeapDumpOnOutOfMemoryError\nandroid.useAndroidX=true\nandroid.enableJetifier=true\nandroid.n"
  },
  {
    "path": "ui/flutter/android/settings.gradle",
    "chars": 726,
    "preview": "pluginManagement {\n    def flutterSdkPath = {\n        def properties = new Properties()\n        file('local.properties')"
  },
  {
    "path": "ui/flutter/assets/exec/.gitkeep",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "ui/flutter/build.yaml",
    "chars": 603,
    "preview": "targets:\n  $default:\n    builders:\n      json_serializable:\n        options:\n          # Options configure how source co"
  },
  {
    "path": "ui/flutter/distribute_options.yaml",
    "chars": 13,
    "preview": "output: dist/"
  },
  {
    "path": "ui/flutter/include/libgopeed.h",
    "chars": 1805,
    "preview": "/* Code generated by cmd/cgo; DO NOT EDIT. */\n\n/* package github.com/GopeedLab/gopeed/bind/desktop */\n\n\n#line 1 \"cgo-bui"
  },
  {
    "path": "ui/flutter/ios/.gitignore",
    "chars": 569,
    "preview": "**/dgph\n*.mode1v3\n*.mode2v3\n*.moved-aside\n*.pbxuser\n*.perspectivev3\n**/*sync/\n.sconsign.dblite\n.tags*\n**/.vagrant/\n**/De"
  },
  {
    "path": "ui/flutter/ios/Flutter/AppFrameworkInfo.plist",
    "chars": 774,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "ui/flutter/ios/Flutter/Debug.xcconfig",
    "chars": 107,
    "preview": "#include? \"Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig\"\n#include \"Generated.xcconfig\"\n"
  },
  {
    "path": "ui/flutter/ios/Flutter/Release.xcconfig",
    "chars": 109,
    "preview": "#include? \"Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig\"\n#include \"Generated.xcconfig\"\n"
  },
  {
    "path": "ui/flutter/ios/Podfile",
    "chars": 1576,
    "preview": "# Uncomment this line to define a global platform for your project\n# platform :ios, '11.0'\n\n# CocoaPods analytics sends "
  },
  {
    "path": "ui/flutter/ios/Runner/AppDelegate.swift",
    "chars": 1947,
    "preview": "import UIKit\nimport Flutter\nimport Libgopeed\n\n@UIApplicationMain\n@objc class AppDelegate: FlutterAppDelegate {\n    overr"
  },
  {
    "path": "ui/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json",
    "chars": 2519,
    "preview": "{\n  \"images\" : [\n    {\n      \"size\" : \"20x20\",\n      \"idiom\" : \"iphone\",\n      \"filename\" : \"Icon-App-20x20@2x.png\",\n   "
  },
  {
    "path": "ui/flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json",
    "chars": 391,
    "preview": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"filename\" : \"LaunchImage.png\",\n      \"scale\" : \"1x\"\n    },\n  "
  },
  {
    "path": "ui/flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md",
    "chars": 336,
    "preview": "# Launch Screen Assets\n\nYou can customize the launch screen with your own desired assets by replacing the image files in"
  },
  {
    "path": "ui/flutter/ios/Runner/Base.lproj/LaunchScreen.storyboard",
    "chars": 2377,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<document type=\"com.apple.InterfaceBuilder3.CocoaTouch.Storyboard"
  },
  {
    "path": "ui/flutter/ios/Runner/Base.lproj/Main.storyboard",
    "chars": 1605,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<document type=\"com.apple.InterfaceBuilder3.CocoaTouch.Storyboard"
  }
]

// ... and 219 more files (download for full content)

About this extraction

This page contains the full source code of the GopeedLab/gopeed GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 419 files (1.7 MB), approximately 508.4k tokens, and a symbol index with 2080 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!