Skip to content

Support Android and fix slow torrent - #85

Merged
YueMiyuki merged 8 commits into
masterfrom
next-dev
May 29, 2026
Merged

Support Android and fix slow torrent#85
YueMiyuki merged 8 commits into
masterfrom
next-dev

Conversation

@YueMiyuki

@YueMiyuki YueMiyuki commented May 28, 2026

Copy link
Copy Markdown
Owner

Summary by cubic

Adds full Android support with SAF directory picker, external storage access, foreground download notifications, and CI to build/sign/upload split-per-ABI APKs. Improves torrent speeds with µTP transport and better DHT, fixes Android picker/layout issues, and updates docs/scripts for Android dev.

  • New Features

    • Android app under src-tauri/gen/android (Gradle + Kotlin, Tauri activity).
    • SAF directory picker and “All files” access (Android 11+); reliable folder reveal via DocumentsContract.
    • Foreground service notifications with progress and active-count.
    • BitTorrent: enable µTP and improve DHT to address slow torrents.
    • Build/signing/CI: pnpm android:*, scripts/android-env.mjs, scripts/sign-android-apks.mjs; release builds split-per-ABI, signed, and uploaded; workflows force Node 24 for JS actions.
    • Docs: README adds Android build/signing steps and notes on autogenerated Tauri files.
  • Refactors

    • Capabilities split: desktop-only permissions moved to src-tauri/capabilities/desktop.json; tighten shell:allow-open allow-list.
    • Platform deps: add Android jni/ndk-context; move desktop-only plugins off Android targets.
    • Version bump to 0.3.4 across Rust workspace and @risuko/* packages.

Written for commit e1fb4ca. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features

    • Full Android support: native integrations (system bars, storage permission, directory picker), bottom navigation, back-to-quit guard, and native download notifications.
    • Locale "auto" with system detection and a selectable custom app log directory.
  • Bug Fixes & Improvements

    • Mobile-first UI overhaul, touch-friendly behaviors, improved reveal/open flows, and smarter filename inference from content-type.
    • Selection, pagination, and task UX refinements.
  • Chores

    • Version bumped to 0.3.4; README updated with Android build/docs and CI release workflow tweaks.

Review Change Stack

Copilot AI review requested due to automatic review settings May 28, 2026 11:06
@coderabbitai

coderabbitai Bot commented May 28, 2026

Copy link
Copy Markdown

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: da7cd646-f1b1-4534-92a8-773c6048ea25

📥 Commits

Reviewing files that changed from the base of the PR and between f965769 and fe85c85.

📒 Files selected for processing (5)
  • src-tauri/risuko-bt/src/session.rs
  • src-tauri/risuko-bt/src/torrent.rs
  • src-tauri/risuko-bt/src/wire/extended.rs
  • src-tauri/risuko-engine/src/engine/torrent.rs
  • src/renderer/utils/native.ts

📝 Walkthrough

Walkthrough

Adds Android support and packaging: Node24 Actions pin, Android build/sign CI job and helper scripts, JNI-backed Tauri Android commands, renderer Android UX and stores, engine/network (µTP/DHT/magnet) updates, CSS/locales/config changes, and workspace version bumps.

Changes

Android Enablement and Packaging

Layer / File(s) Summary
CI, release job, and helper scripts
.github/workflows/*, scripts/android-env.mjs, scripts/sign-android-apks.mjs, .gitignore, README*.md
Adds FORCE_JAVASCRIPT_ACTIONS_TO_NODE24 env, new android-release job, Android env helper and APK signing script, and README docs; updates .gitignore for Tauri gen android files.
Tauri JNI commands & app wiring
src-tauri/src/commands/android_intent.rs, src-tauri/src/commands/*, src-tauri/capabilities/*, src-tauri/tauri*.conf.json
Adds JNI module for MainActivity interactions, async directory-picker callback, Android-only Tauri commands, capability JSON updates, and tauri Android config and platform gating.
Tauri command wiring & platform gating
src-tauri/src/commands/*.rs, src-tauri/src/managers/*, src-tauri/src/lib.rs
Centralizes open-at-login/autostart checks, no-op menu/tray on Android, log-dir-override resolution, and platform-gated startup/plugins.
Renderer Android UX, stores, styles, utils
src/renderer/**/*, src/shared/*
Adds Android bottom nav, back-gesture guard, directory selection flow, open/reveal intents, Android download notification sync, selection model and task store changes, locale “auto”, Android CSS, and native utilities (getTaskRevealDir, sidecar cleanup).
Rust engine: BT, µTP, DHT, magnet
src-tauri/risuko-bt/*, src-tauri/risuko-engine/*
Introduces µTP transport (packet/socket/stream), BEP-10/52 handshake plumbing, shared DHT singleton, magnet resolver/background task, torrent/session/peer updates, merkle v2 checks, and tests.
Manifests, versions, locales, config
package.json, packages/**/package.json, pnpm-workspace.yaml, src-tauri/Cargo.toml, tsconfig.json, locale files, types`
Bumps workspace and package versions to 0.3.4, synchronizes platform package versions, adds release exclusion list, updates Cargo workspace deps/targets for Android, adds log-dir-override config key/type, and tsconfig flags.

Estimated code review effort
🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

  • YueMiyuki/Risuko#83: Earlier Android support work touching release.yml, Android gating, and capability JSONs; strongly related.
  • YueMiyuki/Risuko#29: Related BitTorrent/engine changes that this PR builds upon (µTP/connection and handshake integration).
  • YueMiyuki/Risuko#80: Related cookie-import and Android gating changes affecting risuko-cookies behavior.

"A rabbit taps a tiny screen, hop-hop,
JNI burrows where intents pop!
Panels slide and toasts alight,
APKs signed beneath the night.
Auto-locale, logs that flow,
Android paths where downloads go."

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

Comment thread scripts/android-env.mjs Fixed

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds first-class Android support to Risuko, including a Tauri Android target, JNI helpers for native intents (file/folder reveal, directory picker, notifications, system bars), a mobile UI layer (bottom nav, swipe-back guard, mobile toolbar), a configurable log directory override, and an auto locale that follows the system language. Bumps the app version to 0.3.4.

Changes:

  • New Android Tauri target: Gradle project under src-tauri/gen/android/, Kotlin MainActivity/RisukoForegroundService, Rust commands/android_intent.rs JNI bridge, Android-only Cargo.toml deps, conditional compilation of desktop-only managers (tray/menu/autostart/trash/nosleep), and signing/build scripts.
  • Renderer mobile experience: platform.ts detects android, Main.vue gains a bottom nav + back-gesture guard, App.vue hides the title bar and syncs system bars, Task/* and DragSelect adapt to touch, SelectDirectory.vue uses SAF + translates URIs to filesystem paths, and a new logDirOverride preference with localized strings (en-US, zh-CN, zh-TW).
  • Misc: selectedGidList now tracks row keys (with selectedGids/selectedTaskRows accessors), task-list per-page selector moved to the toolbar, version 0.3.3 → 0.3.4 across all package manifests, default locale changed to auto, default file-allocation set to none on Android, default download dir uses public Downloads on Android.

Reviewed changes

Copilot reviewed 122 out of 193 changed files in this pull request and generated no comments.

Show a summary per file
File Description
src-tauri/src/commands/android_intent.rs New JNI bridge for intents, directory picker, notifications, system bars
src-tauri/src/commands/file_cmds.rs Splits reveal_in_folder/open_path/trash_item per platform; Android uses MainActivity.revealFolder and FileProvider
src-tauri/src/commands/{app_cmds,event_cmds,config_cmds,health_cmds}.rs Android-stub branches for tray/progress/autolaunch and new Android commands
src-tauri/src/managers/{menu,tray,mod}.rs Gate desktop tray/menu behind cfg(not(target_os = "android"))
src-tauri/risuko-engine/src/{config/defaults.rs,engine/http.rs}, config/mod.rs Android default download dir, file-allocation=none, locale=auto
src-tauri/risuko-cookies/{src/lib.rs,Cargo.toml} rookie is desktop-only; Android returns empty results
src-tauri/Cargo.toml + Cargo.lock Add jni/ndk-context on Android; move trash/autostart/nosleep/single-instance to non-Android deps
src-tauri/capabilities/{default,desktop}.json Split desktop-only capabilities; restrict shell:allow-open URL scopes
src-tauri/tauri.{conf,android.conf}.json Bump to 0.3.4 and set Android identifier app.risuko.mobile
src-tauri/gen/android/** New Android Gradle project, Kotlin sources, resources, manifest
src-tauri/icons/{icon.svg,android/values/ic_launcher_background.xml} New 512x512 background + adaptive icon background color
src/renderer/components/Main.vue Android bottom nav + back-gesture handling
src/renderer/pages/index/App.vue Hide title bar on Android, sync system bars, add platform class
src/renderer/components/Task/{Index,TaskList,TaskItem,TaskItemActions,TaskActions,AddTask}.vue Row-keyed selection, Android-friendly tap selection, per-row delete, mobile toolbar
src/renderer/components/TaskDetail/{Index,TaskGeneral}.vue Sheet close button, Android-aware reveal path
src/renderer/components/Native/{SelectDirectory,ShowInFolder}.vue SAF picker + URI translation; new button styling
src/renderer/components/Preference/Advanced.vue Log-dir-override field + localized tip
src/renderer/components/Subnav/SubnavSwitcher.vue, Health/Index.vue, Rss/Index.vue, DragSelect/Index.vue, ui/confirm-dialog/ConfirmDialog.vue Mobile layout tweaks, touch action gating
src/renderer/store/{task,preference,app}.ts Row-keyed selection helpers, auto locale resolution, Android download notification sync
src/renderer/utils/native.ts getTaskRevealDir, chunk meta cleanup, sidecar helper
src/renderer/api/Api.ts Android notification IPCs
src/renderer/shims/platform.ts, router/index.ts, pages/index/main.ts Android UA detection, /preference redirect, locale resolution at init
src/shared/locales/{en-US,zh-CN,zh-TW}/{app,preferences,task}.ts, index.ts New strings + auto language + getSystemLocale
src/shared/{configKeys.ts,types/config.ts} log-dir-override key
src/renderer/styles/{app,components/preferences,components/task,components/task-detail,components/input}.css Mobile/responsive styles
static/logo.svg, src/renderer/assets/logo.svg Expanded viewBox for padding
scripts/android-env.mjs, scripts/sign-android-apks.mjs Toolchain wrapper + APK signing helper
package.json, packages/**/package.json, pnpm-workspace.yaml 0.3.4 bump + Android scripts
README.md, README-CN.md Android build instructions
.github/workflows/{release,publish-npm,codeql-analysis,bump-homebrew-tap}.yml Android release job + Node 24 flag
.gitignore Track src-tauri/gen/android/

@coderabbitai coderabbitai Bot added the next The "next" steps label May 28, 2026

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

8 issues found across 193 files

Note: This PR contains a large number of files. cubic only reviews up to 100 files per PR, so some files may not have been reviewed. cubic prioritizes the most important files to review.
On a pro plan you can use ultrareview for larger PRs.

Re-trigger cubic

Comment thread scripts/android-env.mjs Outdated
Comment thread .github/workflows/release.yml Outdated
Comment thread src-tauri/risuko-engine/src/config/defaults.rs Outdated
Comment thread pnpm-workspace.yaml Outdated
Comment thread scripts/sign-android-apks.mjs
Comment thread scripts/sign-android-apks.mjs
Comment thread scripts/sign-android-apks.mjs Outdated
Comment thread scripts/sign-android-apks.mjs Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 19

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/release.yml:
- Around line 57-58: The release workflow currently runs "pnpm install
--no-frozen-lockfile" which permits lockfile drift; update the step in
.github/workflows/release.yml so the Install dependencies job uses a frozen
lockfile by replacing the flag with "--frozen-lockfile" (or simply "pnpm
install" if your CI uses strict locking) to ensure deterministic, auditable
installs for releases.
- Line 21: Pin every workflow action `uses:` entry to its immutable commit SHA
instead of tag names (e.g., replace `actions/checkout@v4`,
`pnpm/action-setup@v4`, `actions/setup-node@v4`, `actions/setup-java@v4`,
`android-actions/setup-android@v3`, `dtolnay/rust-toolchain@stable`,
`softprops/action-gh-release@v2`, `actions/upload-artifact@v4`,
`tauri-apps/tauri-action@v0` with their corresponding full commit SHAs), add
`with: persist-credentials: false` to both `actions/checkout` steps, and change
both `pnpm install --no-frozen-lockfile` invocations to use a frozen lockfile
(e.g., `--frozen-lockfile`) to ensure reproducible, hardened CI runs.
- Around line 20-21: The checkout steps using actions/checkout@v4 (the steps
named "Check out Git repository" in the android-release and release jobs)
currently use the default persist-credentials behavior; update both checkout
steps to set persist-credentials: false so the GITHUB_TOKEN is not written into
.git/config (i.e., add the persist-credentials: false input to the
actions/checkout@v4 step in each job).

In `@scripts/android-env.mjs`:
- Line 95: The CodeQL finding is a false positive for the console.log call;
update the console.log(`JAVA_HOME=${env.JAVA_HOME}`) line to include a short
inline comment clarifying this is a non-sensitive filesystem path and not a
secret so the warning can be ignored (e.g. add "// CodeQL: false positive -
JAVA_HOME is a path, not a secret"). Keep the logging behavior unchanged but add
the explanatory comment adjacent to the console.log statement.

In `@scripts/sign-android-apks.mjs`:
- Line 7: Replace the URL-based pathname conversion for projectRoot with a
proper file URL to filesystem path conversion: use
fileURLToPath(import.meta.url) and pass that result to resolve instead of new
URL("..", import.meta.url).pathname; update the reference around the projectRoot
initialization (the const projectRoot = ... line) to import fileURLToPath from
'url' and call resolve(fileURLToPath(new URL("..", import.meta.url))) or first
compute fileURLToPath(new URL("..", import.meta.url)) then resolve it so the
path is platform-safe.
- Around line 19-22: The fail() helper currently calls process.exit(1) which
aborts the process immediately and prevents finally blocks (such as the keystore
cleanup in the signing flow) from running; change fail() to throw a descriptive
Error instead (e.g., throw new Error(message)) so normal exception propagation
triggers finally cleanup, and update all call sites of fail() if they expect a
non-returning function; additionally, when computing projectRoot from a file://
URL replace using URL(...).pathname with Node's fileURLToPath to produce correct
Windows paths (import fileURLToPath from 'url' and use fileURLToPath(fileUrl)
where URL(...) was used).

In `@src-tauri/risuko-engine/src/engine/http.rs`:
- Around line 877-891: Add a brief inline comment above the Android-specific
branch in the falloc_mode computation explaining why we force Mode::None on
Android (e.g., Android's fallocate may be unsupported or unreliable on many
filesystems/devices), so future maintainers understand the special-case; insert
the comment near the block that compares mode == super::falloc::Mode::Falloc and
returns super::falloc::Mode::None inside the #[cfg(target_os = "android")]
section.

In `@src-tauri/src/lib.rs`:
- Around line 90-98: resolve_log_dir currently treats
std::fs::create_dir_all(&candidate) success as proof the override is writable,
but that can be false if the directory exists and is not writable; after
create_dir_all(&candidate) attempt to verify write access by trying to open (and
then close/remove) a file in the candidate directory (for example use
std::fs::OpenOptions on candidate.join("risuko.log") or create a temporary file)
and if that open fails, log the error and return default_log_dir.to_path_buf();
update resolve_log_dir to perform this write check and fall back to
default_log_dir on failure so init_logging can reliably create risuko.log.

In `@src/renderer/components/Native/ShowInFolder.vue`:
- Around line 2-4: The button in ShowInFolder.vue is icon-only and lacks an
accessible name; update the <button class="show-in-folder"
`@click.stop`="onFolderClick"> to provide an accessible label (e.g., add
aria-label or aria-labelledby) using the existing localized string for "show in
folder" (use the existing i18n key) so screen readers announce the action for
the FolderOpen icon (preserve size and onFolderClick handler).

In `@src/renderer/components/Preference/Advanced.vue`:
- Around line 1105-1115: The mo-show-in-folder button is wired to
:path="logPath" while the textarea displays visibleLogPath, causing a mismatch
when an override is selected; update the component to pass the same displayed
path to mo-show-in-folder (use visibleLogPath instead of logPath, or derive the
path the button receives from visibleLogPath) so the "show in folder" action
always reveals the directory shown in the textarea (ensure handleLogDirSelected
still updates visibleLogPath as needed).

In `@src/renderer/components/Task/Index.vue`:
- Around line 35-41: The aggregate progress row in Task/Index.vue is being shown
on desktop as well and duplicates the desktop summary from TaskActions.vue;
limit it to mobile by gating the element with a mobile/platform check (e.g.,
change the v-if to "showTotalProgress && isMobile"). Add or reuse a
computed/property named isMobile (or import the existing isMobile() helper from
your platform/utils or store) in the component script so the template can
evaluate it; alternatively, remove or gate the redundant desktop rendering in
TaskActions.vue instead if you prefer centralizing the toolbar display logic.

In `@src/renderer/components/Task/TaskItem.vue`:
- Line 5: The template adds the selected class directly on .task-item via the
:class binding in TaskItem.vue (e.g., ":class='{ ... selected: selected }'"),
but the stylesheet expects a parent selector ".selected .task-item", so the
styles never apply; update the CSS selector in
src/renderer/styles/components/task.css to target ".task-item.selected" (or
change the CSS rules from ".selected .task-item" to ".task-item.selected") and
make the same change for the other binding instance in this component (the
second :class block around the action/secondary element) so the selected
highlight activates when the selected prop is true.

In `@src/renderer/components/Task/TaskList.vue`:
- Around line 11-13: The conditional fallback `item._displayKey || item.gid` is
redundant because DisplayTask always sets `_displayKey`; update TaskList.vue to
use `item._displayKey` consistently (remove the `|| item.gid` fallback) in the
row `:attr`/`:key` and any selection logic (e.g., where `isItemSelected(item)`
or `handleItemClick(item, $event)` relies on the key) so it matches the
`key-field="_displayKey"` used by the virtual scroller and the `v-for` key
semantics.

In `@src/renderer/store/task.ts`:
- Around line 474-481: The current filter only checks underlying gid against
orderedData, allowing stale file-row keys (like "gid#f0") to persist; instead
build a Set of actual visible row keys from orderedData (e.g.
orderedData.map(row => row.key) or the property that represents the rendered row
key) and then filter this.selectedGidList to keep only entries that exactly
match one of those visible keys; update the block that uses orderedData and
this.selectedGidList to use the visibleKeys set for pruning.

In `@src/renderer/styles/android.css`:
- Around line 499-503: The CSS for selectors html.platform-android .subnav-badge
and html.mobile-phone .subnav-badge (and the other affected blocks noted) must
be linted: remove stray blank lines between declarations, change the keyword
casing for currentColor to the linter-expected form (e.g., currentcolor), and
reformat any multiline calc() expressions so each line ends with the operator
(e.g., "calc(100% -" on the same line as the operator) or keep the operator
attached to its operand per the scss/operator-no-newline-after rule; update the
blocks around the indicated selectors (and similar blocks at the other ranges)
accordingly to satisfy value-keyword-case, declaration-empty-line-before, and
scss/operator-no-newline-after.

In `@src/renderer/styles/components/preferences.css`:
- Around line 297-314: The .dev-log-path-input rule removes the focus outline
making keyboard focus invisible; restore an accessible focus indicator by
removing the hard outline: 0 change and instead add a :focus-visible rule for
.dev-log-path-input that applies a visible, high-contrast ring (e.g., thin
outline or box-shadow) matching the UI theme variables (use
--foreground/--accent or a dedicated --focus color) and preserve keyboard-only
visibility; ensure the focus style has sufficient contrast and does not rely on
color alone so keyboard users can track focus.

In `@src/renderer/utils/native.ts`:
- Around line 17-23: The dirname function only checks for '/' so Windows paths
with backslashes aren't handled; update dirname (and callers like
getTaskRevealDir) to compute the last separator by considering both '/' and '\\'
(e.g., get lastIndexOf for '/' and for '\\' and use the larger index) or use a
platform-aware API (path.dirname) if available; ensure the index comparison and
slice logic use that combined index so Windows paths return the parent directory
instead of the full file path.

In `@src/shared/locales/zh-CN/app.ts`:
- Around line 4-11: The new locale keys primary-navigation, nav-tasks, nav-rss,
nav-health, nav-settings, android-back-again-to-quit, and
android-storage-access-required were added to zh-CN/app.ts but are missing from
the other locale app.ts files; open each other locale's app.ts (e.g., en-US,
zh-TW and any remaining locales) and add these keys with appropriate
translations (or copy the en-US text as placeholders) to keep all locale objects
consistent, preserving the same key names and string types as used in
functions/components that read them.

In `@src/shared/locales/zh-TW/preferences.ts`:
- Around line 205-207: The new locale keys "log-dir-override-placeholder" and
"log-dir-override-tips" were added in zh-TW but are missing from most other
locale preference files; add these two keys to every
src/shared/locales/*/preferences.ts (or implement a runtime fallback) so the
labels won't be undefined when switching languages—either copy the en-US values
into each locale file (and translate where appropriate) or update the locale
loader to fall back to en-US for missing keys; ensure the keys are exported in
the same object shape as other preference strings so existing consumers can
access "log-dir-override-placeholder" and "log-dir-override-tips" safely.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 41cb60b5-93bf-4624-9043-ad15d178da1e

📥 Commits

Reviewing files that changed from the base of the PR and between 3b5e459 and 9f6451d.

⛔ Files ignored due to path filters (100)
  • src-tauri/Cargo.lock is excluded by !**/*.lock
  • src-tauri/gen/android/.editorconfig is excluded by !**/gen/**
  • src-tauri/gen/android/.gitignore is excluded by !**/gen/**
  • src-tauri/gen/android/app/.gitignore is excluded by !**/gen/**
  • src-tauri/gen/android/app/build.gradle.kts is excluded by !**/gen/**
  • src-tauri/gen/android/app/proguard-rules.pro is excluded by !**/gen/**
  • src-tauri/gen/android/app/proguard-tauri.pro is excluded by !**/gen/**
  • src-tauri/gen/android/app/src/main/AndroidManifest.xml is excluded by !**/gen/**
  • src-tauri/gen/android/app/src/main/java/app/risuko/mobile/MainActivity.kt is excluded by !**/gen/**
  • src-tauri/gen/android/app/src/main/java/app/risuko/mobile/RisukoForegroundService.kt is excluded by !**/gen/**
  • src-tauri/gen/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml is excluded by !**/gen/**
  • src-tauri/gen/android/app/src/main/res/drawable/empty_splash_icon.xml is excluded by !**/gen/**
  • src-tauri/gen/android/app/src/main/res/drawable/ic_launcher_background.xml is excluded by !**/gen/**
  • src-tauri/gen/android/app/src/main/res/drawable/ic_notification.xml is excluded by !**/gen/**
  • src-tauri/gen/android/app/src/main/res/layout/activity_main.xml is excluded by !**/gen/**
  • src-tauri/gen/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml is excluded by !**/gen/**
  • src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher.png is excluded by !**/*.png, !**/gen/**
  • src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png is excluded by !**/*.png, !**/gen/**
  • src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png is excluded by !**/*.png, !**/gen/**
  • src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher.png is excluded by !**/*.png, !**/gen/**
  • src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png is excluded by !**/*.png, !**/gen/**
  • src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png is excluded by !**/*.png, !**/gen/**
  • src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png is excluded by !**/*.png, !**/gen/**
  • src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png is excluded by !**/*.png, !**/gen/**
  • src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png is excluded by !**/*.png, !**/gen/**
  • src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png is excluded by !**/*.png, !**/gen/**
  • src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png is excluded by !**/*.png, !**/gen/**
  • src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png is excluded by !**/*.png, !**/gen/**
  • src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png is excluded by !**/*.png, !**/gen/**
  • src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png is excluded by !**/*.png, !**/gen/**
  • src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png is excluded by !**/*.png, !**/gen/**
  • src-tauri/gen/android/app/src/main/res/values-night/themes.xml is excluded by !**/gen/**
  • src-tauri/gen/android/app/src/main/res/values-v31/themes.xml is excluded by !**/gen/**
  • src-tauri/gen/android/app/src/main/res/values/colors.xml is excluded by !**/gen/**
  • src-tauri/gen/android/app/src/main/res/values/ic_launcher_background.xml is excluded by !**/gen/**
  • src-tauri/gen/android/app/src/main/res/values/strings.xml is excluded by !**/gen/**
  • src-tauri/gen/android/app/src/main/res/values/themes.xml is excluded by !**/gen/**
  • src-tauri/gen/android/app/src/main/res/xml/file_paths.xml is excluded by !**/gen/**
  • src-tauri/gen/android/build.gradle.kts is excluded by !**/gen/**
  • src-tauri/gen/android/buildSrc/build.gradle.kts is excluded by !**/gen/**
  • src-tauri/gen/android/buildSrc/src/main/java/app/risuko/mobile/kotlin/BuildTask.kt is excluded by !**/gen/**
  • src-tauri/gen/android/buildSrc/src/main/java/app/risuko/mobile/kotlin/RustPlugin.kt is excluded by !**/gen/**
  • src-tauri/gen/android/gradle.properties is excluded by !**/gen/**
  • src-tauri/gen/android/gradle/wrapper/gradle-wrapper.jar is excluded by !**/*.jar, !**/gen/**
  • src-tauri/gen/android/gradle/wrapper/gradle-wrapper.properties is excluded by !**/gen/**
  • src-tauri/gen/android/gradlew is excluded by !**/gen/**
  • src-tauri/gen/android/gradlew.bat is excluded by !**/gen/**
  • src-tauri/gen/android/settings.gradle is excluded by !**/gen/**
  • src-tauri/icons/128x128.png is excluded by !**/*.png
  • src-tauri/icons/128x128@2x.png is excluded by !**/*.png
  • src-tauri/icons/32x32.png is excluded by !**/*.png
  • src-tauri/icons/64x64.png is excluded by !**/*.png
  • src-tauri/icons/Square107x107Logo.png is excluded by !**/*.png
  • src-tauri/icons/Square142x142Logo.png is excluded by !**/*.png
  • src-tauri/icons/Square150x150Logo.png is excluded by !**/*.png
  • src-tauri/icons/Square284x284Logo.png is excluded by !**/*.png
  • src-tauri/icons/Square30x30Logo.png is excluded by !**/*.png
  • src-tauri/icons/Square310x310Logo.png is excluded by !**/*.png
  • src-tauri/icons/Square44x44Logo.png is excluded by !**/*.png
  • src-tauri/icons/Square71x71Logo.png is excluded by !**/*.png
  • src-tauri/icons/Square89x89Logo.png is excluded by !**/*.png
  • src-tauri/icons/StoreLogo.png is excluded by !**/*.png
  • src-tauri/icons/android/mipmap-hdpi/ic_launcher.png is excluded by !**/*.png
  • src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png is excluded by !**/*.png
  • src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png is excluded by !**/*.png
  • src-tauri/icons/android/mipmap-mdpi/ic_launcher.png is excluded by !**/*.png
  • src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png is excluded by !**/*.png
  • src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png is excluded by !**/*.png
  • src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png is excluded by !**/*.png
  • src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png is excluded by !**/*.png
  • src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png is excluded by !**/*.png
  • src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png is excluded by !**/*.png
  • src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png is excluded by !**/*.png
  • src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png is excluded by !**/*.png
  • src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png is excluded by !**/*.png
  • src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png is excluded by !**/*.png
  • src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png is excluded by !**/*.png
  • src-tauri/icons/icon.ico is excluded by !**/*.ico
  • src-tauri/icons/icon.png is excluded by !**/*.png
  • src-tauri/icons/icon.svg is excluded by !**/*.svg
  • src-tauri/icons/ios/AppIcon-20x20@1x.png is excluded by !**/*.png
  • src-tauri/icons/ios/AppIcon-20x20@2x-1.png is excluded by !**/*.png
  • src-tauri/icons/ios/AppIcon-20x20@2x.png is excluded by !**/*.png
  • src-tauri/icons/ios/AppIcon-20x20@3x.png is excluded by !**/*.png
  • src-tauri/icons/ios/AppIcon-29x29@1x.png is excluded by !**/*.png
  • src-tauri/icons/ios/AppIcon-29x29@2x-1.png is excluded by !**/*.png
  • src-tauri/icons/ios/AppIcon-29x29@2x.png is excluded by !**/*.png
  • src-tauri/icons/ios/AppIcon-29x29@3x.png is excluded by !**/*.png
  • src-tauri/icons/ios/AppIcon-40x40@1x.png is excluded by !**/*.png
  • src-tauri/icons/ios/AppIcon-40x40@2x-1.png is excluded by !**/*.png
  • src-tauri/icons/ios/AppIcon-40x40@2x.png is excluded by !**/*.png
  • src-tauri/icons/ios/AppIcon-40x40@3x.png is excluded by !**/*.png
  • src-tauri/icons/ios/AppIcon-512@2x.png is excluded by !**/*.png
  • src-tauri/icons/ios/AppIcon-60x60@2x.png is excluded by !**/*.png
  • src-tauri/icons/ios/AppIcon-60x60@3x.png is excluded by !**/*.png
  • src-tauri/icons/ios/AppIcon-76x76@1x.png is excluded by !**/*.png
  • src-tauri/icons/ios/AppIcon-76x76@2x.png is excluded by !**/*.png
  • src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png is excluded by !**/*.png
  • src/renderer/assets/logo.svg is excluded by !**/*.svg
  • static/logo.svg is excluded by !**/*.svg
📒 Files selected for processing (93)
  • .github/workflows/bump-homebrew-tap.yml
  • .github/workflows/codeql-analysis.yml
  • .github/workflows/publish-npm.yml
  • .github/workflows/release.yml
  • .gitignore
  • README-CN.md
  • README.md
  • package.json
  • packages/risuko-app/package.json
  • packages/risuko-cli/npm/darwin-arm64/package.json
  • packages/risuko-cli/npm/darwin-x64/package.json
  • packages/risuko-cli/npm/linux-arm64-gnu/package.json
  • packages/risuko-cli/npm/linux-x64-gnu/package.json
  • packages/risuko-cli/npm/win32-arm64-msvc/package.json
  • packages/risuko-cli/npm/win32-x64-msvc/package.json
  • packages/risuko-cli/package.json
  • packages/risuko-js/npm/darwin-arm64/package.json
  • packages/risuko-js/npm/darwin-x64/package.json
  • packages/risuko-js/npm/linux-arm64-gnu/package.json
  • packages/risuko-js/npm/linux-x64-gnu/package.json
  • packages/risuko-js/npm/win32-arm64-msvc/package.json
  • packages/risuko-js/npm/win32-x64-msvc/package.json
  • packages/risuko-js/package.json
  • pnpm-workspace.yaml
  • scripts/android-env.mjs
  • scripts/sign-android-apks.mjs
  • src-tauri/Cargo.toml
  • src-tauri/capabilities/default.json
  • src-tauri/capabilities/desktop.json
  • src-tauri/icons/android/values/ic_launcher_background.xml
  • src-tauri/icons/icon.icns
  • src-tauri/risuko-cookies/Cargo.toml
  • src-tauri/risuko-cookies/src/lib.rs
  • src-tauri/risuko-engine/src/config/defaults.rs
  • src-tauri/risuko-engine/src/config/mod.rs
  • src-tauri/risuko-engine/src/engine/http.rs
  • src-tauri/src/commands/android_intent.rs
  • src-tauri/src/commands/app_cmds.rs
  • src-tauri/src/commands/config_cmds.rs
  • src-tauri/src/commands/event_cmds.rs
  • src-tauri/src/commands/file_cmds.rs
  • src-tauri/src/commands/health_cmds.rs
  • src-tauri/src/commands/mod.rs
  • src-tauri/src/lib.rs
  • src-tauri/src/managers/menu.rs
  • src-tauri/src/managers/mod.rs
  • src-tauri/src/managers/tray.rs
  • src-tauri/tauri.android.conf.json
  • src-tauri/tauri.conf.json
  • src/renderer/api/Api.ts
  • src/renderer/components/DragSelect/Index.vue
  • src/renderer/components/Health/Index.vue
  • src/renderer/components/Main.vue
  • src/renderer/components/Native/SelectDirectory.vue
  • src/renderer/components/Native/ShowInFolder.vue
  • src/renderer/components/Preference/Advanced.vue
  • src/renderer/components/Rss/Index.vue
  • src/renderer/components/Subnav/SubnavSwitcher.vue
  • src/renderer/components/Task/AddTask.vue
  • src/renderer/components/Task/Index.vue
  • src/renderer/components/Task/TaskActions.vue
  • src/renderer/components/Task/TaskItem.vue
  • src/renderer/components/Task/TaskItemActions.vue
  • src/renderer/components/Task/TaskList.vue
  • src/renderer/components/TaskDetail/Index.vue
  • src/renderer/components/TaskDetail/TaskGeneral.vue
  • src/renderer/components/ui/confirm-dialog/ConfirmDialog.vue
  • src/renderer/pages/index/App.vue
  • src/renderer/pages/index/main.ts
  • src/renderer/router/index.ts
  • src/renderer/shims/platform.ts
  • src/renderer/store/app.ts
  • src/renderer/store/preference.ts
  • src/renderer/store/task.ts
  • src/renderer/styles/android.css
  • src/renderer/styles/app.css
  • src/renderer/styles/components/input.css
  • src/renderer/styles/components/preferences.css
  • src/renderer/styles/components/task-detail.css
  • src/renderer/styles/components/task.css
  • src/renderer/utils/native.ts
  • src/shared/configKeys.ts
  • src/shared/locales/en-US/app.ts
  • src/shared/locales/en-US/preferences.ts
  • src/shared/locales/en-US/task.ts
  • src/shared/locales/index.ts
  • src/shared/locales/zh-CN/app.ts
  • src/shared/locales/zh-CN/preferences.ts
  • src/shared/locales/zh-CN/task.ts
  • src/shared/locales/zh-TW/app.ts
  • src/shared/locales/zh-TW/preferences.ts
  • src/shared/locales/zh-TW/task.ts
  • src/shared/types/config.ts

Comment thread .github/workflows/release.yml
Comment thread .github/workflows/release.yml
Comment thread .github/workflows/release.yml Outdated
Comment thread scripts/android-env.mjs Outdated
Comment thread scripts/sign-android-apks.mjs Outdated
Comment thread src/renderer/store/task.ts Outdated
Comment thread src/renderer/styles/android.css
Comment thread src/renderer/styles/components/preferences.css
Comment thread src/shared/locales/zh-CN/app.ts
Comment thread src/shared/locales/zh-TW/preferences.ts
@YueMiyuki YueMiyuki changed the title Support Android Support Android and fix slow torrent May 29, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/renderer/utils/native.ts (1)

17-23: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Handle root-level POSIX paths in dirname().

Line 20 still treats index === 0 as “no parent”, so dirname("/file.part") returns the file path instead of /. That leaves _isFileEntry reveal targets wrong for files directly under the filesystem root.

Suggested fix
 function dirname(path = ""): string {
 	const value = `${path || ""}`.replace(/[/\\]+$/g, "");
 	const index = Math.max(value.lastIndexOf("/"), value.lastIndexOf("\\"));
-	if (index <= 0) {
+	if (index < 0) {
 		return value;
 	}
+	if (index === 0) {
+		return value[0];
+	}
 	return value.slice(0, index);
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/renderer/utils/native.ts` around lines 17 - 23, The dirname() helper
incorrectly treats index === 0 as “no parent” so paths like "/file.part" return
the full path; update dirname(path) to return the root separator for root-level
POSIX paths: when index === 0 and the original input starts with "/", return "/"
instead of value; otherwise keep existing behavior (including existing handling
for backslashes/Windows). Ensure this change is made inside the dirname function
referenced above so files directly under "/" yield "/" as their directory.
♻️ Duplicate comments (1)
src/renderer/store/task.ts (1)

475-480: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Build visibleKeys from display rows, not orderedData.

orderedData is still DownloadTask[], so _displayKey is missing here. That makes visibleKeys effectively {undefined} and prunes every selected row on refresh.

💡 Suggested fix
-				const visibleKeys = new Set(
-					orderedData.map((task) => task._displayKey),
-				);
+				const visibleKeys = new Set(
+					this.displayTaskList.map((task) => task._displayKey || task.gid),
+				);
 				this.selectedGidList = this.selectedGidList.filter((key) =>
 					visibleKeys.has(key),
 				);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/renderer/store/task.ts` around lines 475 - 480, visibleKeys is being
built from orderedData (DownloadTask[]) which lacks _displayKey, so visibleKeys
contains undefined and clears selectedGidList; change the source to the
rendered/display rows that include _displayKey (e.g. this.displayData or the
variable holding display rows) — e.g. construct visibleKeys with new
Set(this.displayData.map(row => row._displayKey)) and then filter
this.selectedGidList against that set so only currently visible display keys are
kept.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@scripts/android-env.mjs`:
- Line 95: The current console.log call in scripts/android-env.mjs masks
JAVA_HOME while the comment claims a CodeQL false positive; decide and apply one
of two fixes: either restore the original logging by changing the console.log to
print the actual environment value (use env.JAVA_HOME) and keep the explanatory
comment about CodeQL false positive, or if you intentionally want to suppress
the path, keep the masked output but remove or edit the "CodeQL: false positive"
phrase so the comment no longer asserts it’s a false positive; update the line
that references JAVA_HOME (the console.log call) and the adjacent comment
accordingly.

In `@src-tauri/risuko-bt/src/torrent.rs`:
- Around line 366-370: serve_v2_layers is being used as a proxy for "we can
answer BEP-52/hash requests" but it's computed before building the hybrid
piece-layer tables (from_layer_bytes) so a failed build leaves hash_tables =
None while we still announce truncated v2 hashes; change the logic so that
announcing or serving v2 layers requires successful table construction: after
calling from_layer_bytes(...) and assigning hash_tables, set or update
serve_v2_layers to supports_v2_wire(&init.meta) && hash_tables.is_some(), and
update announce_hashes and any other checks that rely on serve_v2_layers (e.g.,
where you decide to announce v2/truncated info-hashes) to also explicitly verify
hash_tables.is_some() (or use the new combined flag) before advertising or
responding to BEP-52 requests.

In `@src-tauri/risuko-bt/src/utp/stream.rs`:
- Around line 736-739: The loop copies bytes one-by-one from st.recv_ready into
buf which causes many function calls; instead compute n, get contiguous slices
from the VecDeque via st.recv_ready.as_slices() (or call
st.recv_ready.make_contiguous() then as_slices()), copy up to n bytes from the
first slice then (if needed) from the second slice into buf with one or two
buf.put_slice calls, and finally remove the bytes with
st.recv_ready.drain(0..n); update the code around st.recv_ready, buf.put_slice,
as_slices/make_contiguous and drain to perform batched copies.

In `@src-tauri/risuko-engine/src/engine/http.rs`:
- Around line 956-968: The code is currently applying
adopt_content_type_extension (via probe_for_name/ adopt_content_type_extension)
to any extensionless filename, which incorrectly rewrites explicit user output
names; update the logic to only adopt a content-type extension when the filename
was URL-derived by gating these branches with the existing
filename_was_url_derived boolean (or add an explicit allow_content_type_adoption
flag passed into run_single_download and checked here), i.e., before calling
adopt_content_type_extension and before setting
adopted_filename/filename/part_path ensure filename_was_url_derived (or the new
flag) is true so user-provided out values are never modified; apply the same
guard to the identical block around lines 2045–2049.

In `@src-tauri/risuko-engine/src/engine/manager.rs`:
- Around line 551-559: The code checks still_active before calling
resolve_and_add_magnet().await but does not re-check after the await, so if the
task was paused/removed while resolve was in flight you may incorrectly set its
status to Error and emit DownloadError; update the logic around
resolve_and_add_magnet (and the similar block at 577-625) to re-acquire
tasks.read().await and recompute still_active (matching task.gid,
TaskKind::Torrent, TaskStatus::Active, and uri == &magnet_uri) after the await
completes and only transition the task to TaskStatus::Error and send
DownloadError when the re-checked still_active is true. Ensure you reference and
use the same guard/conditions (tasks, TaskKind::Torrent, TaskStatus::Active,
magnet_uri, gid) when deciding whether to mutate state or emit errors.
- Around line 2283-2310: ensure_active_magnet_resolvers() currently holds
tasks.read() while awaiting torrent_ids.read() and pending_magnets.read(), which
can deadlock with remove() that locks torrent_ids → pending_magnets → tasks; fix
by normalizing lock order: acquire torrent_ids.read() and pending_magnets.read()
before tasks.read(), or else grab tasks (clone the minimal data needed) and drop
the tasks guard before reading torrent_ids and pending_magnets. Update the block
that builds jobs (the let jobs = { ... } snippet) so that reads occur in the
same order as remove() (torrent_ids, pending_magnets, then tasks) or so tasks is
not held while awaiting the other two; ensure the code still clones task.gid,
uris and options (using options.merge_task_options) while not holding
conflicting locks.

In `@src-tauri/risuko-engine/src/engine/torrent.rs`:
- Around line 301-360: Extract the duplicated save-metadata block from both
resolve_and_add_magnet and add_magnet into a single helper function (e.g.,
save_torrent_metadata_if_enabled(bytes: &[u8], options: &Map<String, Value>,
output_dir: &Path) -> impl Future<Output=()>) and call it from both places; move
the logic that reads "bt-save-metadata" from options, resolves dir (using
options.get("dir") or output_dir), calls bt::parse_torrent, builds the safe
filename with sanitize_file_stem, creates parent dirs and writes the .torrent
file (logging info/warn) into that helper, and replace the inline blocks in
resolve_and_add_magnet and add_magnet with a call to this new function before
invoking add_torrent_bytes/other downstream calls.

In `@src/renderer/components/Task/TaskActions.vue`:
- Around line 16-22: The new select-all control in TaskActions.vue is an
interactive, click-only <i> element (class "task-action") which is not
keyboard-focusable or accessible; replace the <i> with a semantic <button
type="button"> (still using class "task-action") wired to onSelectAllClick, bind
the disabled state to selectableTaskCount === 0, and add an accessible name
(e.g., aria-label="Select all tasks" or visible text) so keyboard and
assistive-tech users can focus and activate the control; keep the ListChecks
icon as the visual child.

In `@src/renderer/styles/responsive.css`:
- Around line 104-108: The calc() expression assigned to the bottom property
breaks the stylelint scss/operator-no-newline-after rule because the operators
are on their own lines; inline the operators with their operands so the
expression is on one line (e.g., change bottom: calc(env(safe-area-inset-bottom)
+ var(--android-bottom-bar, 72px) - 14px);), keeping the same terms
env(safe-area-inset-bottom), var(--android-bottom-bar, 72px) and - 14px and
preserving the calc() semantics.

---

Outside diff comments:
In `@src/renderer/utils/native.ts`:
- Around line 17-23: The dirname() helper incorrectly treats index === 0 as “no
parent” so paths like "/file.part" return the full path; update dirname(path) to
return the root separator for root-level POSIX paths: when index === 0 and the
original input starts with "/", return "/" instead of value; otherwise keep
existing behavior (including existing handling for backslashes/Windows). Ensure
this change is made inside the dirname function referenced above so files
directly under "/" yield "/" as their directory.

---

Duplicate comments:
In `@src/renderer/store/task.ts`:
- Around line 475-480: visibleKeys is being built from orderedData
(DownloadTask[]) which lacks _displayKey, so visibleKeys contains undefined and
clears selectedGidList; change the source to the rendered/display rows that
include _displayKey (e.g. this.displayData or the variable holding display rows)
— e.g. construct visibleKeys with new Set(this.displayData.map(row =>
row._displayKey)) and then filter this.selectedGidList against that set so only
currently visible display keys are kept.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 437f09d2-0043-4865-8d0f-ef1aa4ffd383

📥 Commits

Reviewing files that changed from the base of the PR and between 9f6451d and 358cd03.

⛔ Files ignored due to path filters (3)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
  • src-tauri/gen/android/app/src/main/java/app/risuko/mobile/MainActivity.kt is excluded by !**/gen/**
  • src/renderer/pages/index/favicon.ico is excluded by !**/*.ico
📒 Files selected for processing (69)
  • .github/workflows/release.yml
  • biome.json
  • package.json
  • packages/risuko-app/package.json
  • packages/risuko-cli/npm/darwin-arm64/package.json
  • packages/risuko-cli/npm/darwin-x64/package.json
  • packages/risuko-cli/npm/linux-arm64-gnu/package.json
  • packages/risuko-cli/npm/linux-x64-gnu/package.json
  • packages/risuko-cli/npm/win32-arm64-msvc/package.json
  • packages/risuko-cli/npm/win32-x64-msvc/package.json
  • packages/risuko-cli/package.json
  • packages/risuko-js/npm/darwin-arm64/package.json
  • packages/risuko-js/npm/darwin-x64/package.json
  • packages/risuko-js/npm/linux-arm64-gnu/package.json
  • packages/risuko-js/npm/linux-x64-gnu/package.json
  • packages/risuko-js/npm/win32-arm64-msvc/package.json
  • packages/risuko-js/npm/win32-x64-msvc/package.json
  • packages/risuko-js/package.json
  • pnpm-workspace.yaml
  • scripts/android-env.mjs
  • scripts/sign-android-apks.mjs
  • src-tauri/risuko-bt/Cargo.toml
  • src-tauri/risuko-bt/examples/download_magnet.rs
  • src-tauri/risuko-bt/examples/probe_handshake.rs
  • src-tauri/risuko-bt/src/core/hash.rs
  • src-tauri/risuko-bt/src/core/magnet.rs
  • src-tauri/risuko-bt/src/core/merkle.rs
  • src-tauri/risuko-bt/src/core/mod.rs
  • src-tauri/risuko-bt/src/dht.rs
  • src-tauri/risuko-bt/src/lib.rs
  • src-tauri/risuko-bt/src/magnet.rs
  • src-tauri/risuko-bt/src/peer.rs
  • src-tauri/risuko-bt/src/peer/connection.rs
  • src-tauri/risuko-bt/src/session.rs
  • src-tauri/risuko-bt/src/torrent.rs
  • src-tauri/risuko-bt/src/utp.rs
  • src-tauri/risuko-bt/src/utp/packet.rs
  • src-tauri/risuko-bt/src/utp/socket.rs
  • src-tauri/risuko-bt/src/utp/stream.rs
  • src-tauri/risuko-bt/src/wire/extended.rs
  • src-tauri/risuko-bt/src/wire/handshake.rs
  • src-tauri/risuko-engine/src/config/defaults.rs
  • src-tauri/risuko-engine/src/engine/http.rs
  • src-tauri/risuko-engine/src/engine/manager.rs
  • src-tauri/risuko-engine/src/engine/torrent.rs
  • src-tauri/src/commands/android_intent.rs
  • src-tauri/src/commands/file_cmds.rs
  • src-tauri/src/lib.rs
  • src/renderer/components/Native/ShowInFolder.vue
  • src/renderer/components/Preference/Advanced.vue
  • src/renderer/components/Preference/Basic.vue
  • src/renderer/components/Preference/CloudSinks.vue
  • src/renderer/components/Task/AddTask.vue
  • src/renderer/components/Task/Index.vue
  • src/renderer/components/Task/SelectTorrent.vue
  • src/renderer/components/Task/TaskActions.vue
  • src/renderer/components/Task/TaskItem.vue
  • src/renderer/components/Task/TaskList.vue
  • src/renderer/components/ui/confirm-dialog/ConfirmDialog.vue
  • src/renderer/pages/index/index.html
  • src/renderer/store/task.ts
  • src/renderer/styles/android.css
  • src/renderer/styles/components/preferences.css
  • src/renderer/styles/components/task.css
  • src/renderer/styles/responsive.css
  • src/renderer/utils/native.ts
  • src/shared/locales/en-US/task.ts
  • src/shared/utils/taskSelection.ts
  • tsconfig.json
💤 Files with no reviewable changes (2)
  • src-tauri/risuko-bt/src/core/hash.rs
  • src-tauri/risuko-bt/src/core/magnet.rs

Comment thread scripts/android-env.mjs Outdated
Comment thread src-tauri/risuko-bt/src/peer/connection.rs
Comment thread src-tauri/risuko-bt/src/torrent.rs Outdated
Comment thread src-tauri/risuko-bt/src/utp/stream.rs
Comment thread src-tauri/risuko-engine/src/engine/http.rs Outdated
Comment thread src-tauri/risuko-engine/src/engine/manager.rs
Comment thread src-tauri/risuko-engine/src/engine/manager.rs
Comment thread src-tauri/risuko-engine/src/engine/torrent.rs
Comment thread src/renderer/components/Task/TaskActions.vue Outdated
Comment thread src/renderer/styles/responsive.css Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

6 issues found across 72 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src-tauri/risuko-engine/src/config/defaults.rs">

<violation number="1" location="src-tauri/risuko-engine/src/config/defaults.rs:100">
P2: Write probe uses fixed file name and truncate. Can clobber existing file. Use unique probe name and create_new.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread src-tauri/src/lib.rs Outdated
Comment thread src-tauri/risuko-engine/src/config/defaults.rs
Comment thread src-tauri/risuko-bt/src/wire/extended.rs
Comment thread src-tauri/risuko-bt/examples/probe_handshake.rs Outdated
Comment thread src-tauri/risuko-engine/src/engine/torrent.rs
Comment thread packages/risuko-js/package.json Outdated
Comment thread scripts/android-env.mjs Fixed

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src-tauri/risuko-bt/src/torrent.rs (1)

362-421: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Clamp BEP-52 v2 reserved-bit advertisement to Merkle table availability (outbound, and mirror for inbound too)

SpawnPeer.advertise_v2 is written into the BT handshake via Handshake::new_with_v2(..., advertise_v2), setting the BEP-52 v2 reserved bit. Outbound dials currently pass init.advertise_v2 even when serve_v2_layers is false (Merkle table build failed), so peers can observe v2 capability but then receive HashReject for HASH_REQUEST because build_hash_response rejects when tables is None.

🛠️ Suggested change
-    let advertise_v2 = init.advertise_v2;
+    let requested_advertise_v2 = init.advertise_v2;
...
     let serve_v2_layers = supports_v2 && hash_tables.is_some();
+    let advertise_v2 = requested_advertise_v2 && serve_v2_layers;

Also check the inbound path: Session builds KnownInfoHash { advertise_v2: handle.advertise_v2 }, and handle.advertise_v2 is still set from init.advertise_v2 regardless of serve_v2_layers in torrent.rs, so inbound handshakes can diverge as well.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src-tauri/risuko-bt/src/torrent.rs` around lines 362 - 421, The PR shows
advertise_v2 can be set true even when Merkle tables failed to build; clamp
advertisement to actual serving ability by forcing advertise_v2 =
init.advertise_v2 && serve_v2_layers before it's stored/propagated. Concretely,
replace usages that copy init.advertise_v2 into SpawnPeer.advertise_v2,
Handshake::new_with_v2(..., advertise_v2) and KnownInfoHash /
handle.advertise_v2 with the clamped value (referencing advertise_v2,
serve_v2_layers, init.advertise_v2, SpawnPeer.advertise_v2,
Handshake::new_with_v2, KnownInfoHash and handle.advertise_v2) so outbound and
inbound handshakes only set the BEP-52 v2 reserved bit when hash_tables is Some.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src-tauri/risuko-bt/src/wire/extended.rs`:
- Around line 120-139: Add round-trip tests for the extended handshake's yourip
field by creating two new test cases that serialize and deserialize the
extension with yourip set to an IPv4 (4-byte) and an IPv6 (16-byte) address
respectively; use the existing encode/parse helpers used by the current
handshake tests to build a dict with key b"yourip" and raw bytes for the
addresses, call the same code paths that produce and consume the struct (the
code that constructs Self { ..., yourip, ... } and the corresponding
serializer), and assert the deserialized yourip matches the original IpAddr::V4
and IpAddr::V6 values to ensure round-trip coverage.

In `@src/renderer/utils/native.ts`:
- Around line 17-26: The dirname function currently returns "C:" for Windows
drive-root parents (e.g. "C:\file.tmp") which drops the trailing separator;
update dirname (the function named dirname) so when you compute the slice for
parent directories you preserve the separator for drive roots: after computing
index = Math.max(...), if index > 0 and the character before index is ':'
(detecting a Windows drive like "C:"), return value.slice(0, index + 1) instead
of value.slice(0, index); keep the existing special-case handling for index ===
0 and the trailing-separators trimming.

---

Outside diff comments:
In `@src-tauri/risuko-bt/src/torrent.rs`:
- Around line 362-421: The PR shows advertise_v2 can be set true even when
Merkle tables failed to build; clamp advertisement to actual serving ability by
forcing advertise_v2 = init.advertise_v2 && serve_v2_layers before it's
stored/propagated. Concretely, replace usages that copy init.advertise_v2 into
SpawnPeer.advertise_v2, Handshake::new_with_v2(..., advertise_v2) and
KnownInfoHash / handle.advertise_v2 with the clamped value (referencing
advertise_v2, serve_v2_layers, init.advertise_v2, SpawnPeer.advertise_v2,
Handshake::new_with_v2, KnownInfoHash and handle.advertise_v2) so outbound and
inbound handshakes only set the BEP-52 v2 reserved bit when hash_tables is Some.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: f2e185a8-1443-451b-8bc6-71cd04feb613

📥 Commits

Reviewing files that changed from the base of the PR and between 358cd03 and e1fb4ca.

📒 Files selected for processing (15)
  • packages/risuko-js/package.json
  • scripts/android-env.mjs
  • src-tauri/risuko-bt/examples/probe_handshake.rs
  • src-tauri/risuko-bt/src/torrent.rs
  • src-tauri/risuko-bt/src/utp/stream.rs
  • src-tauri/risuko-bt/src/wire/extended.rs
  • src-tauri/risuko-engine/src/config/defaults.rs
  • src-tauri/risuko-engine/src/engine/http.rs
  • src-tauri/risuko-engine/src/engine/manager.rs
  • src-tauri/risuko-engine/src/engine/torrent.rs
  • src-tauri/src/lib.rs
  • src/renderer/components/Task/TaskActions.vue
  • src/renderer/store/task.ts
  • src/renderer/styles/responsive.css
  • src/renderer/utils/native.ts

Comment thread packages/risuko-js/package.json
Comment thread src-tauri/risuko-bt/src/wire/extended.rs
Comment thread src/renderer/utils/native.ts

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 15 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread src/renderer/utils/native.ts
Comment thread src-tauri/risuko-engine/src/engine/torrent.rs Outdated
…f sensitive information'

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@scripts/android-env.mjs`:
- Around line 92-95: The logging is inconsistent: ANDROID_HOME and
ANDROID_NDK_HOME are printed but JAVA_HOME is only reported as "set"; update the
check that references env.JAVA_HOME so it prints the actual path like the others
(e.g., replace the console message in the block that checks env.JAVA_HOME to log
JAVA_HOME=${env.JAVA_HOME}) to restore consistent debugging output for all three
environment paths; if you need to silence a CodeQL secret-warning, add an inline
suppression comment adjacent to that log statement after making it print the
value.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: f96d18a0-ed0c-45a4-aa17-7153d9c6c6fb

📥 Commits

Reviewing files that changed from the base of the PR and between e1fb4ca and f965769.

📒 Files selected for processing (1)
  • scripts/android-env.mjs

Comment thread scripts/android-env.mjs
@YueMiyuki
YueMiyuki merged commit 56f978a into master May 29, 2026
5 checks passed
@YueMiyuki

Copy link
Copy Markdown
Owner Author

#79 is probably related too

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

next The "next" steps

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants