Skip to content

speed up plugin installation with native ZIP extraction - #2715

Open
codewithchai0605 wants to merge 5 commits into
Acode-Foundation:mainfrom
codewithchai0605:feat/better-plugin-installer
Open

speed up plugin installation with native ZIP extraction#2715
codewithchai0605 wants to merge 5 commits into
Acode-Foundation:mainfrom
codewithchai0605:feat/better-plugin-installer

Conversation

@codewithchai0605

Copy link
Copy Markdown

Summary

This PR improves plugin installation performance, especially for plugins containing many small files such as file/folder icon providers.

The previous installer extracted archive entries in JavaScript with a concurrency limit of 2. This caused significant overhead from repeated JS-to-native filesystem calls.

This change moves archive extraction to native Android code and writes files directly from the ZIP archive.

Changes

  • Extract plugin ZIP archives natively using ZipFile.
  • Stream archive entries directly to a staging directory.
  • Activate updates through a directory swap, preserving the existing plugin if extraction fails.
  • Restore the previous plugin installation if an earlier update was interrupted during the swap.
  • Prevent concurrent installations of the same plugin.
  • Validate plugin IDs and archive paths to prevent path traversal.
  • Add archive size and entry-count limits.
  • Optimize extraction for many small files:
    • shared 64 KiB buffer
    • direct output streams
    • no per-entry canonical filesystem lookup

@greptile-apps

greptile-apps Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR moves plugin ZIP extraction into the native Android bridge and activates installations through staging-directory swaps.

  • Adds plugin-ID, archive-path, entry-count, and extracted-size validation.
  • Adds per-plugin extraction locking and interrupted-swap recovery.
  • Replaces incremental JavaScript extraction with cache-backed native ZipFile streaming.

Confidence Score: 4/5

The interrupted-update state misclassification should be fixed before merging because a subsequent plugin load failure can remove both the recovered installation and its backup.

Native recovery can restore a missing destination after JavaScript has recorded it as a fresh install, causing the error path to delete the plugin, while interrupted staging directories can also accumulate indefinitely.

Files Needing Attention: src/lib/installPlugin.js and src/plugins/pluginContext/src/android/Tee.java

Important Files Changed

Filename Overview
src/lib/installPlugin.js Delegates extraction to native code, but snapshots installation state before native recovery and can consequently delete a recovered update after a load failure.
src/plugins/pluginContext/src/android/Tee.java Adds bounded native ZIP extraction and swap recovery, but restart cleanup leaves interrupted staging directories orphaned.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
  A[Download and validate ZIP in JavaScript] --> B[Write ZIP to cache]
  B --> C[Invoke Tee.extractPluginArchive]
  C --> D[Restore interrupted backup]
  D --> E[Extract into staging directory]
  E --> F[Move current plugin to backup]
  F --> G[Move staging to destination]
  G --> H[Delete backup]
  H --> I[Load installed plugin]
  I -->|Load error on recovered update| J[JavaScript cleanup may delete destination]
Loading

Reviews (1): Last reviewed commit: "perf: reduce native plugin extraction ov..." | Re-trigger Greptile

Comment thread src/lib/installPlugin.js
}
assertSafePluginId(id);
pluginDir = Url.join(PLUGIN_DIR, id);
pluginWasInstalled = await fsOperation(pluginDir).exists();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Recovered update misclassified as fresh

When an interrupted update leaves only a backup, pluginWasInstalled is recorded as false before native recovery restores that backup. If the subsequent plugin load fails, the catch block treats the recovered update as a fresh installation and deletes the activated directory after its backup has been removed, leaving the plugin uninstalled.

Knowledge Base Used: Plugin System

Comment on lines +191 to +194
staging = new File(
parent,
"." + destination.getName() + ".install-" + UUID.randomUUID()
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Interrupted staging directories accumulate

If Android terminates the process after creating a UUID-suffixed .install-* directory, the finally cleanup never runs and restart recovery scans only .backup-* directories. These abandoned extractions can retain hundreds of megabytes each and consume application storage over time.

Knowledge Base Used: Plugin System

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

Labels

enhancement New feature or request

Projects

Status: Backlog

Development

Successfully merging this pull request may close these issues.

2 participants