Skip to content

feat(cache): auto clear stale download cache on startup - #1892

Open
icgnos wants to merge 1 commit into
mainfrom
feat/issue-1859
Open

feat(cache): auto clear stale download cache on startup#1892
icgnos wants to merge 1 commit into
mainfrom
feat/issue-1859

Conversation

@icgnos

@icgnos icgnos commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator

Checklist

  • Changes have been tested locally and work as expected.
  • All tests in workflows pass successfully.
  • Documentation has been updated if necessary.
  • Code formatting and commit messages align with the project's conventions.
  • Comments have been added for any complex logic or functionality if possible.

This PR is a ..

  • 🆕 New feature
  • 🐞 Bug fix
  • 🛠 Refactoring
  • ⚡️ Performance improvement
  • 🌐 Internationalization
  • 📄 Documentation improvement
  • 🎨 Code style optimization
  • ❓ Other (Please specify below)

Related Issues

Description

  • 启动时自动清理超过时间阈值的文件,并增加相关配置

Summary by Sourcery

Add configurable automatic clearing of stale download cache files on startup.

New Features:

  • Introduce an auto-clear setting for the download cache with a configurable retention period in hours.
  • Expose cache auto-clear and retention configuration in the download settings UI.

Enhancements:

  • Initialize and use cache auto-clear settings during launcher startup to asynchronously purge outdated cache files based on last modified time.

@sourcery-ai

sourcery-ai Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

Adds configurable automatic clearing of stale download cache entries on launcher startup, including UI controls, configuration model updates, and a Tauri-side async cleanup routine based on file modification time and a retention threshold.

Sequence diagram for auto-clearing stale download cache on startup

sequenceDiagram
  participant Run as run
  participant App as AppHandle
  participant Config as LauncherConfig
  participant Misc as auto_clear_download_cache
  participant FS as tokio_fs

  Run->>Config: read download.cache.auto_clear
  Run->>Config: read download.cache.retention_hours
  alt auto_clear_download_cache is true
    Run->>App: handle
    Run->>Misc: spawn auto_clear_download_cache(AppHandle)
    Misc->>Config: read download.cache.directory
    Misc->>Config: read download.cache.retention_hours
    Misc->>FS: read_dir(cache_dir)
    Misc->>FS: metadata(path)
    Misc->>FS: remove_file(stale_path)
  else auto_clear_download_cache is false
    Run->>Run: skip cache cleanup
  end
Loading

File-Level Changes

Change Details Files
Introduce UI controls for enabling automatic cache clearing and configuring retention duration in download settings.
  • Add React state for cache retention hours and slider value based on existing download configuration
  • Add a toggle switch to enable or disable automatic download cache clearing
  • Conditionally render slider and numeric input to adjust retention hours with validation and sync between controls
src/pages/settings/download/index.tsx
Implement backend logic to remove stale cache files older than a configured retention threshold when the app starts.
  • Add async helper that reads cache directory and retention settings from LauncherConfig
  • Compute cutoff timestamp using SystemTime and retention hours, skipping if retention is zero
  • Iterate cache directory entries, filter regular files, collect those with modified time older than cutoff, and asynchronously delete them while logging failures
src-tauri/src/launcher_config/helpers/misc.rs
Wire automatic cache clearing into the launcher startup flow, gated by configuration.
  • Read auto_clear flag from download cache configuration in run()
  • On startup, if enabled, spawn an async task that calls the auto_clear_download_cache helper using the app handle
src-tauri/src/lib.rs
Extend launcher configuration schema and defaults to support cache auto-clear behavior and retention settings on both Rust and TS sides.
  • Add auto_clear and retention_hours fields with defaults to the Rust LauncherConfig cache model
  • Add corresponding autoClear and retentionHours fields to the TypeScript LauncherConfig interface
  • Set default autoClear and retentionHours values in the frontend defaultConfig cache section
src-tauri/src/launcher_config/models.rs
src/models/config.ts
Update localization resources for the new cache auto-clear and retention settings UI labels (placeholders in diff).
  • Add or update English locale entries for cache auto-clear and retention setting titles and descriptions
  • Add or update Simplified Chinese locale entries for cache auto-clear and retention setting titles and descriptions
src/locales/en.json
src/locales/zh-Hans.json

Assessment against linked issues

Issue Objective Addressed Explanation
#1859 Implement an automatic mechanism in SJMCL to delete download cache files (temporary/intermediate files) without requiring manual user intervention.
#1859 Provide configurable global settings for automatic cache cleanup, including a global on/off switch (default on) and a retention time threshold for cache files.
#1859 Support per-download-task configuration to enable or disable automatic cache cleanup via API or UI options. The PR only introduces global settings (autoClear and retentionHours) and startup-time cleanup logic. It does not add any per-task level configuration or API/UI options to control cache cleanup individually for specific download tasks.

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@github-actions github-actions Bot added the size/L Denotes a PR that changes 100-499 lines, ignoring generated files. label Aug 6, 2026

@sourcery-ai sourcery-ai Bot left a comment

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.

Hey - I've found 2 issues, and left some high level feedback:

  • The NumberInput for retentionHours currently allows an empty string which becomes 0 on Number(value) and can briefly desync from the slider/max bounds; consider handling empty input explicitly (e.g., keeping previous value or defaulting to min) to avoid transient invalid state.
  • In auto_clear_download_cache, a retention_hours of 0 disables clearing even if auto_clear is true; if this is not intended, you may want to rely solely on the auto_clear flag or ensure the UI cannot drive the config into a contradictory state.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The `NumberInput` for `retentionHours` currently allows an empty string which becomes `0` on `Number(value)` and can briefly desync from the slider/max bounds; consider handling empty input explicitly (e.g., keeping previous value or defaulting to min) to avoid transient invalid state.
- In `auto_clear_download_cache`, a `retention_hours` of `0` disables clearing even if `auto_clear` is true; if this is not intended, you may want to rely solely on the `auto_clear` flag or ensure the UI cannot drive the config into a contradictory state.

## Individual Comments

### Comment 1
<location path="src/pages/settings/download/index.tsx" line_range="387-396" />
<code_context>
+                ),
+                children: (
+                  <HStack spacing={4}>
+                    <Slider
+                      min={1}
+                      max={168}
+                      step={1}
+                      w={32}
+                      colorScheme={primaryColor}
+                      value={sliderRetentionHours}
+                      onChange={(value) => {
+                        setSliderRetentionHours(value);
+                        setRetentionHours(value);
+                      }}
+                      onBlur={() => {
+                        update("download.cache.retentionHours", retentionHours);
+                      }}
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Keep slider value within min/max bounds when syncing from state

`sliderRetentionHours` is set directly from `retentionHours`, but the slider only supports values in [1, 168]. If `retentionHours` ever goes outside this range (e.g., via existing configs or future changes), the slider could receive an invalid value and display incorrectly. Please clamp `retentionHours` to the slider’s min/max both when assigning `sliderRetentionHours` and when calling `update`.

Suggested implementation:

```typescript
                    <Slider
                      min={1}
                      max={168}
                      step={1}
                      w={32}
                      colorScheme={primaryColor}
                      value={Math.min(168, Math.max(1, sliderRetentionHours))}
                      onChange={(value) => {
                        setSliderRetentionHours(value);
                        setRetentionHours(value);
                      }}
                      onBlur={() => {
                        const clampedRetentionHours = Math.min(
                          168,
                          Math.max(1, retentionHours)
                        );
                        update("download.cache.retentionHours", clampedRetentionHours);
                      }}

```

To fully implement your comment, also ensure that any place where `sliderRetentionHours` is initialized or synchronized from `retentionHours` clamps the value, for example:

- If you have something like `setSliderRetentionHours(retentionHours);` (e.g., in a `useEffect` or initialization), change it to:

```ts
setSliderRetentionHours(Math.min(168, Math.max(1, retentionHours)));
```

This guarantees that both the state driving the slider and the value persisted via `update` always stay within the slider’s `[1, 168]` bounds.
</issue_to_address>

### Comment 2
<location path="src-tauri/src/launcher_config/helpers/misc.rs" line_range="273-278" />
<code_context>
+    .saturating_sub(retention_hours as u64 * 3600);
+
+  let mut stale_paths = Vec::new();
+  let mut entries = match tokio::fs::read_dir(&cache_dir).await {
+    Ok(entries) => entries,
+    Err(_) => return Ok(()),
+  };
+  while let Some(entry) = entries.next_entry().await? {
+    let file_type = match entry.file_type().await {
+      Ok(ft) => ft,
+      Err(_) => continue,
</code_context>
<issue_to_address>
**suggestion (performance):** Reuse metadata from `DirEntry` to avoid extra syscalls

Within the loop, `tokio::fs::metadata(&path)` is called for every entry even though `DirEntry` can already provide metadata. For large caches this roughly doubles metadata syscalls. Prefer `entry.metadata().await` and derive both `file_type` and `modified` from that single result to cut IO and simplify error handling.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +387 to +396
<Slider
min={1}
max={168}
step={1}
w={32}
colorScheme={primaryColor}
value={sliderRetentionHours}
onChange={(value) => {
setSliderRetentionHours(value);
setRetentionHours(value);

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.

suggestion (bug_risk): Keep slider value within min/max bounds when syncing from state

sliderRetentionHours is set directly from retentionHours, but the slider only supports values in [1, 168]. If retentionHours ever goes outside this range (e.g., via existing configs or future changes), the slider could receive an invalid value and display incorrectly. Please clamp retentionHours to the slider’s min/max both when assigning sliderRetentionHours and when calling update.

Suggested implementation:

                    <Slider
                      min={1}
                      max={168}
                      step={1}
                      w={32}
                      colorScheme={primaryColor}
                      value={Math.min(168, Math.max(1, sliderRetentionHours))}
                      onChange={(value) => {
                        setSliderRetentionHours(value);
                        setRetentionHours(value);
                      }}
                      onBlur={() => {
                        const clampedRetentionHours = Math.min(
                          168,
                          Math.max(1, retentionHours)
                        );
                        update("download.cache.retentionHours", clampedRetentionHours);
                      }}

To fully implement your comment, also ensure that any place where sliderRetentionHours is initialized or synchronized from retentionHours clamps the value, for example:

  • If you have something like setSliderRetentionHours(retentionHours); (e.g., in a useEffect or initialization), change it to:
setSliderRetentionHours(Math.min(168, Math.max(1, retentionHours)));

This guarantees that both the state driving the slider and the value persisted via update always stay within the slider’s [1, 168] bounds.

Comment on lines +273 to +278
let mut entries = match tokio::fs::read_dir(&cache_dir).await {
Ok(entries) => entries,
Err(_) => return Ok(()),
};
while let Some(entry) = entries.next_entry().await? {
let file_type = match entry.file_type().await {

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.

suggestion (performance): Reuse metadata from DirEntry to avoid extra syscalls

Within the loop, tokio::fs::metadata(&path) is called for every entry even though DirEntry can already provide metadata. For large caches this roughly doubles metadata syscalls. Prefer entry.metadata().await and derive both file_type and modified from that single result to cut IO and simplify error handling.

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

Labels

size/L Denotes a PR that changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature] 自动删除 SJMCL 下载缓存

1 participant