Skip to content

feat: enhance file download and caching mechanisms - #404

Open
egalvis27 wants to merge 10 commits into
mainfrom
feat/improve-download-speed
Open

feat: enhance file download and caching mechanisms#404
egalvis27 wants to merge 10 commits into
mainfrom
feat/improve-download-speed

Conversation

@egalvis27

@egalvis27 egalvis27 commented Jun 29, 2026

Copy link
Copy Markdown

What is Changed / Added


  • Fixed invalid EOF and out-of-bounds range handling in virtual drive hydration and ranged downloads.
  • Added guards to skip zero-length and invalid block prefetch/download work.
  • Extended block prefetching from thumbnail reads to normal file reads used for open/copy flows.
  • Added environment-based tuning for normal read prefetch size.
  • Unified duplicated prefetch constants in the read callback path.
  • Removed noisy success metrics from ranged download logs and kept failure warnings only.
  • Added and updated regression tests for hydration bounds, prefetch behavior, and download range handling.

Why

  • Prevent crashes caused by negative range lengths and invalid buffer allocations near end-of-file.
  • Stop wasted work and memory pressure from scheduling blocks outside real file bounds.
  • Improve perceived file open and copy performance, not only thumbnail loading.
  • Make prefetch behavior easier to tune without editing code.
  • Reduce small code duplication and keep prefetch defaults consistent.
  • Keep logs useful during failures without adding per-block noise during normal operation.
  • Lock behavior down with focused tests so performance changes do not reintroduce correctness bugs.

Summary by CodeRabbit

  • New Features

    • Downloads now prefetch upcoming blocks for smoother playback and file access.
    • Progress updates are paced for a cleaner experience while always reporting completion.
    • Concurrent requests can be rate-limited independently, improving responsiveness during retries.
  • Bug Fixes

    • Invalid, empty, and out-of-range download requests are handled safely.
    • Reads beyond file boundaries no longer trigger unnecessary downloads or cache updates.
    • Rate-limit headers are validated more reliably, preventing incorrect retry delays.

const ALLOWED_BLOCK_SIZE_MB = new Set([1, 2, 4, 8]);

function getConfiguredBlockSizeInMb() {
const configuredValue = process.env.INTERNXT_DRIVE_DOWNLOAD_BLOCK_SIZE_MB;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Why this needs to be a enviroment variable

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

My idea is that these values should be easy to change. Right now, the optimal settings are 4 MB per download and 3 prefetch blocks. These values can be increased if changes are made to the backend to reduce the latency of download requests, which would greatly improve performance.

Looking back, we can adjust these values to achieve better performance if the conditions are right; that’s why I set them as environment variables so they can be easily modified.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I would just make it a constant

blockLength,
}: Props): Promise<Result<void, Error>> {
if (isAborted(state)) return { data: undefined };
if (blockLength <= 0 || blockStart >= virtualFile.size) return { data: undefined };

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Is this something that can happen? meaning: Fuse can ask for a block that is negative?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

It's not just to validate negative ranges; it's also to prevent readings in the 0 range—which fuse does sometimes—and to ensure that invalid ranges aren't attempted, which is rare but does happen.

return { blockStart: Math.max(0, Math.min(range.position, fileSize)), blockLength: 0 };
}

const clampedStart = Math.max(0, range.position);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Why? i assume that range will always be 0 or more

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

We might assume that's the case, but there are very rare instances where it might not be; this change is a simple safeguard that definitively prevents that from happening.

import { type HandleReadDeps, type ReadRange } from './types';
import { isThumbnailProcess } from './thumbnail-processes';

const PREFETCH_DEFAULT_BLOCKS_AHEAD = 5;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

shouldn this be on constants?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

You're right, I've moved it.

Comment on lines +30 to +40
function getThumbnailPrefetchBlocksAhead() {
return getPrefetchBlocksAhead({
configuredValue: process.env.INTERNXT_DRIVE_THUMBNAIL_PREFETCH_BLOCKS_AHEAD,
});
}

function getReadPrefetchBlocksAhead() {
return getPrefetchBlocksAhead({
configuredValue: process.env.INTERNXT_DRIVE_READ_PREFETCH_BLOCKS_AHEAD,
});
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Why

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

As I mentioned earlier, the idea is to be able to easily change the values.

return Math.max(0, Math.min(parsed, PREFETCH_MAX_BLOCKS_AHEAD));
}

function getThumbnailPrefetchBlocksAhead() {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Why, for a thumbnail, we need to do a prefetch? would not this slow the initial load of the file explorer?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I've been running tests focused on thumbnails, and you're right—the improvement is very small, and it slows down the network for other processes.

Comment thread src/backend/features/fuse/on-read/read-or-hydrate.ts
Comment thread src/backend/features/fuse/on-read/read-or-hydrate.ts
const downloads = missingBlocks.map((block) => {
const start = block * BLOCK_SIZE;
const end = Math.min(start + BLOCK_SIZE, virtualFile.size);
const boundedBlockLength = end - start;

@AlexisMora AlexisMora Jul 7, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Same, i feel like there has to be a way to prevent this whole "negative block request" instead of just adding everywhere the check, this just adds unnecessary complexity dont you think

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

You're right—the extra validation was too conservative; the first validation at the beginning of the function is enough.

Comment on lines +63 to +73
if (
!shouldEmitProgress({
now: Date.now(),
bytesDownloaded,
fileSize,
state: progressReporterState,
})
) {
return;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Why is this necessary?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

This is just to avoid spamming progress updates.
Downloads can report progress very frequently, and without this check we would send too many UI updates for very small changes.
So this acts like a small throttle: it only emits from time to time, but still allows the final update through when the download reaches 100%.

Comment on lines +16 to +43
const PROGRESS_UPDATE_INTERVAL_MS = 250;

type ProgressReporterState = {
lastUpdateAt: number;
};

function shouldEmitProgress({
now,
bytesDownloaded,
fileSize,
state,
}: {
now: number;
bytesDownloaded: number;
fileSize: number;
state: ProgressReporterState;
}) {
const reachedEnd = bytesDownloaded >= fileSize;
const elapsedSinceLastUpdate = now - state.lastUpdateAt;

if (!reachedEnd && elapsedSinceLastUpdate < PROGRESS_UPDATE_INTERVAL_MS) {
return false;
}

state.lastUpdateAt = now;
return true;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

IF this is necessary this should be in its own file, where we should test it separatedly

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Done

Comment thread src/infra/environment/download-file/download-file.ts
@egalvis27
egalvis27 requested a review from AlexisMora July 7, 2026 21:05
@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@egalvis27, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 47 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: bb9f4b38-4acd-4900-8077-4812ef226091

📥 Commits

Reviewing files that changed from the base of the PR and between 7591ae6 and 888f290.

📒 Files selected for processing (2)
  • src/infra/drive-server/client/interceptors/rate-limiter/create-request-interceptor.ts
  • src/infra/drive-server/client/interceptors/rate-limiter/wait-for-delay.ts
📝 Walkthrough

Walkthrough

The change adds download-cache boundary validation and block prefetching, throttles download progress updates, scopes rate-limit delays by request key, and validates ranged downloads before network access.

Changes

Download cache reads

Layer / File(s) Summary
Cache configuration and boundary validation
src/backend/features/fuse/on-read/download-cache/constants.ts, src/backend/features/fuse/on-read/download-cache/*
Cache constants define block size and prefetch settings. Range expansion, hydration checks, and block downloads handle invalid and out-of-file ranges.
Hydration and prefetch orchestration
src/backend/features/fuse/on-read/read-or-hydrate.ts, src/backend/features/fuse/on-read/read-or-hydrate.test.ts
readOrHydrate asynchronously downloads missing upcoming blocks and skips covered or out-of-file blocks.
FUSE read integration
src/backend/features/fuse/on-read/handle-read-callback.ts, src/backend/features/fuse/on-read/handle-read-callback.test.ts
Thumbnail reads use cache hydration. Normal reads enable configured prefetching. Tests cover hydration, failures, allocation, and prefetching.

Download progress throttling

Layer / File(s) Summary
Progress reporting contract and integrations
src/backend/features/virtual-drive/services/operations/*, src/context/storage/StorageFiles/application/download/*
shouldEmitProgress limits updates to one every 250 ms and emits completion immediately. Read and repository downloads use the helper.

Rate-limit request scoping

Layer / File(s) Summary
Request-key-scoped delay state
src/infra/drive-server/client/interceptors/rate-limiter/{rate-limiter.types.ts,get-request-key.ts,attach-rate-limiter-interceptors.ts,create-*,wait-for-delay.*}
Pending delays are stored by METHOD:URL. Matching requests share delays, while different request keys proceed independently.
Rate-limit header normalization
src/infra/drive-server/client/interceptors/rate-limiter/{constants.ts,update-state-from-headers.*}
Header parsing accepts strict numeric values, preserves state for invalid values, and normalizes reasonable reset values.

Ranged download handling

Layer / File(s) Summary
Ranged download validation and buffering
src/infra/environment/download-file/download-file.*
Download ranges are validated before SDK or HTTP calls. Non-positive lengths return empty buffers. Stream responses use expanding buffers and structured failure logging.

Estimated code review effort: 4 (Complex) | ~60 minutes

Suggested reviewers: alexismora

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 6.90% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the primary changes to file downloading and caching.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/improve-download-speed

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

const ALLOWED_BLOCK_SIZE_MB = new Set([1, 2, 4, 8]);

function getConfiguredBlockSizeInMb() {
const configuredValue = process.env.INTERNXT_DRIVE_DOWNLOAD_BLOCK_SIZE_MB;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I would just make it a constant

Comment on lines +60 to +63
// Thumbnail reads should not spam progress updates in UI.
onDownloadProgress: () => undefined,
// Thumbnail reads should not register files as offline available.
saveToRepository: async () => undefined,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Why not? imagine a file gets fully hydrated because a thumbnail call, why not save locally? same goes for progress

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

We could do that, but the goal of this path is to keep the thumbnails' behavior as lightweight as possible. If a thumbnail hydrates the file, it could indeed be saved locally, but that would also trigger additional side effects, such as progress tracking and offline availability logging. To avoid clutter and keep the flow as isolated as possible, for now I prefer that this scenario not alter the cache state or the UI.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

but then a file would be downloaded 2 times no?

virtualFile,
filePath,
range,
prefetchBlocksAhead: 0,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

this is not necessary because you are already adding default value to 0

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Done

@@ -0,0 +1,24 @@
const PROGRESS_UPDATE_INTERVAL_MS = 250;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This should go into the constants.ts file

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Since we're using this function as the central point, and that value isn't needed anywhere else but here, I don't think it's worth creating a new file just to store this value.

import { File } from '../../../../../context/virtual-drive/files/domain/File';
import { StorageFile } from '../../domain/StorageFile';

const PROGRESS_UPDATE_INTERVAL_MS = 250;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

you have a duplicated constant that should go into the constants.ts

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

The dependency on this constant has been removed; now the same function from the previous comment is used.

Comment on lines +5 to +8
const method = config.method?.toUpperCase() ?? 'GET';
const url = config.url ?? '';

return `${method}:${url}`;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Should we realy return a key if no method and url? also, what happens if there are multiple pending requests?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Yes, that makes sense. If there's no method or URL, the fallback is still valid, but the key isn't as precise. The important thing is that the block only applies to requests with the same key; if several arrive at once, they are chained to the same pending wait—they aren't all serialized globally.

Comment on lines +9 to +11
function getRequestKey({ method, url }: { method?: string; url?: string }) {
return `${method?.toUpperCase() ?? 'GET'}:${url ?? ''}`;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This method is dupliacted, why not unify them?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Done

import { AxiosResponse } from 'axios';
import { RateLimitState } from './rate-limiter.types';

const MAX_REASONABLE_RESET_SECONDS = 120;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This could go into constants

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Done

Comment thread .env.example Outdated
Comment on lines +15 to +18

# Download and prefetch settings
INTERNXT_DRIVE_DOWNLOAD_BLOCK_SIZE_MB=
INTERNXT_DRIVE_READ_PREFETCH_BLOCKS_AHEAD=

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I would just add them as a constant, way simpler and it does not do any harm to have it "hardcoded" just as we do with any other value

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Done

@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: 6

Caution

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

⚠️ Outside diff range comments (1)
src/backend/features/fuse/on-read/read-or-hydrate.test.ts (1)

1-30: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Keep mock calls isolated between tests.

beforeEach resets values, but the module-level mocks keep call history across tests. Reset the relevant mock at the start of each test before asserting exact call counts, or add a Vitest mock-clearing global setting.

🤖 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/backend/features/fuse/on-read/read-or-hydrate.test.ts` around lines 1 -
30, Isolate module-level mock call history between tests in the readOrHydrate
test suite. Clear the relevant mocks created by partialSpyOn—especially
readChunkFromDiskMock, fileExistsOnDiskMock, allocateFileMock, and
downloadAndCacheBlockMock—before tests assert exact call counts, or enable the
project’s Vitest mock-clearing configuration.
🧹 Nitpick comments (3)
src/backend/features/fuse/on-read/read-or-hydrate.ts (1)

147-150: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract duplicated block-range arithmetic.

schedulePrefetchBlocksAhead (Lines 148-150) and ensureRangeDownloaded's missingBlocks.map (Lines 257-259) both compute a block's byte start, clamped end, and length from block * BLOCK_SIZE and virtualFile.size. Extract a shared helper to keep this arithmetic in one place and reduce the risk of the two call sites drifting apart.

♻️ Proposed refactor
+function getBlockByteRange(block: number, fileSize: number): { start: number; end: number; length: number } {
+  const start = block * BLOCK_SIZE;
+  const end = Math.min(start + BLOCK_SIZE, fileSize);
+  return { start, end, length: end - start };
+}
+
 function schedulePrefetchBlocksAhead({
   ...
   for (const block of prefetchedBlocks) {
-    const start = block * BLOCK_SIZE;
-    const end = Math.min(start + BLOCK_SIZE, virtualFile.size);
-    const blockLength = end - start;
+    const { start, blockLength } = getBlockByteRange(block, virtualFile.size);
     ...

Apply the equivalent change inside ensureRangeDownloaded's missingBlocks.map.

Also applies to: 256-259

🤖 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/backend/features/fuse/on-read/read-or-hydrate.ts` around lines 147 - 150,
Extract the duplicated block-range calculation into a shared helper near the
existing read/hydration utilities, returning the block’s start, clamped end, and
length from block, BLOCK_SIZE, and virtualFile.size. Update both
schedulePrefetchBlocksAhead and ensureRangeDownloaded’s missingBlocks.map to use
this helper while preserving their existing behavior.
src/infra/environment/download-file/download-file.test.ts (1)

71-84: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Cover negative lengths as well.

The test name claims coverage for non-positive lengths, but it only exercises length: 0. Add a length: -1 case to protect the other branch of the <= 0 condition.

🤖 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/infra/environment/download-file/download-file.test.ts` around lines 71 -
84, Update the test around downloadFileRange to also cover a negative range
length, such as length: -1, while preserving the assertions that it returns an
empty buffer and skips both sdkDownloadFileMock and axiosGetMock. Keep the
existing zero-length coverage or parameterize both non-positive cases.
src/infra/environment/download-file/download-file.ts (1)

108-124: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Release unused buffer capacity after growth.

Buffer.subarray shares the original backing storage. When the response exceeds the requested length, the returned view keeps the larger buffer alive. Copy only when spare capacity exists.

[details]

Proposed fix
-    response.data.on('end', () => resolve(buffer.subarray(0, bytesRead)));
+    response.data.on('end', () => {
+      const result = buffer.subarray(0, bytesRead);
+      resolve(bytesRead === buffer.length ? result : Buffer.from(result));
+    });

[/details]

🤖 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/infra/environment/download-file/download-file.ts` around lines 108 - 124,
Update the response.data end handler to avoid returning a subarray that retains
an oversized buffer: when bytesRead is less than buffer.length, copy the valid
bytes into an exact-sized Buffer before resolving; otherwise preserve the
existing zero-copy result. Keep the growth logic in the data handler unchanged.
🤖 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/backend/features/fuse/on-read/download-cache/constants.ts`:
- Around line 6-9: Update PREFETCH_BLOCKS_AHEAD in
src/backend/features/fuse/on-read/download-cache/constants.ts:6-9 from 3 to 5.
Do not modify
src/backend/features/fuse/on-read/download-cache/constants.test.ts:21-27 or
src/backend/features/fuse/on-read/handle-read-callback.test.ts:133-153; both are
corrected by the constant change.

In `@src/context/shared/domain/progress-constants.test.ts`:
- Line 1: Resolve the missing progress module referenced by
progress-constants.test.ts: either add ./progress-constants exporting
PROGRESS_UPDATE_INTERVAL_MS and shouldEmitProgress, or move the tests to
should-emit-progress.ts and update them to use its existing helper API. Ensure
npm run test:main can resolve the import and the tests exercise the actual
progress implementation.

In `@src/infra/drive-server/client/interceptors/rate-limiter/constants.ts`:
- Line 1: Run the configured Prettier formatter on the file containing
MAX_REASONABLE_RESET_SECONDS and apply its generated formatting changes, without
manually altering the constant’s behavior.

In
`@src/infra/drive-server/client/interceptors/rate-limiter/create-response-interceptor.ts`:
- Around line 53-55: Update the response interceptor’s delay tracking to store
pending state per request key instead of sharing delayState.requestKey and
delayState.pending across requests. Ensure overlapping 429 handlers for
different keys do not overwrite each other, and clear only the completed key
after waitForDelay finishes. Add an integration test covering overlapping 429
responses for two distinct request keys and verifying each key’s required delay
is preserved.

In
`@src/infra/drive-server/client/interceptors/rate-limiter/update-state-from-headers.ts`:
- Around line 5-15: Update parseNumberHeader to reject partially numeric header
strings such as “10seconds” rather than returning the truncated integer;
validate that the entire trimmed value is a valid integer before parsing. Add
regression coverage confirming malformed partial values return null and do not
update state.reset.

In `@src/infra/environment/download-file/download-file.ts`:
- Around line 18-19: Update downloadFileRange and fetchEncryptedRange to
validate range.position and range.length before constructing requests or
allocating buffers: require finite, non-negative safe integers, and reject any
range whose computed end offset is not a safe integer. Preserve the existing
empty-buffer behavior only for valid zero-length ranges, and ensure invalid
values are rejected before sending the HTTP Range header.

---

Outside diff comments:
In `@src/backend/features/fuse/on-read/read-or-hydrate.test.ts`:
- Around line 1-30: Isolate module-level mock call history between tests in the
readOrHydrate test suite. Clear the relevant mocks created by
partialSpyOn—especially readChunkFromDiskMock, fileExistsOnDiskMock,
allocateFileMock, and downloadAndCacheBlockMock—before tests assert exact call
counts, or enable the project’s Vitest mock-clearing configuration.

---

Nitpick comments:
In `@src/backend/features/fuse/on-read/read-or-hydrate.ts`:
- Around line 147-150: Extract the duplicated block-range calculation into a
shared helper near the existing read/hydration utilities, returning the block’s
start, clamped end, and length from block, BLOCK_SIZE, and virtualFile.size.
Update both schedulePrefetchBlocksAhead and ensureRangeDownloaded’s
missingBlocks.map to use this helper while preserving their existing behavior.

In `@src/infra/environment/download-file/download-file.test.ts`:
- Around line 71-84: Update the test around downloadFileRange to also cover a
negative range length, such as length: -1, while preserving the assertions that
it returns an empty buffer and skips both sdkDownloadFileMock and axiosGetMock.
Keep the existing zero-length coverage or parameterize both non-positive cases.

In `@src/infra/environment/download-file/download-file.ts`:
- Around line 108-124: Update the response.data end handler to avoid returning a
subarray that retains an oversized buffer: when bytesRead is less than
buffer.length, copy the valid bytes into an exact-sized Buffer before resolving;
otherwise preserve the existing zero-copy result. Keep the growth logic in the
data handler unchanged.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 92278d37-0702-4994-b676-d7df8abdbd77

📥 Commits

Reviewing files that changed from the base of the PR and between 031268e and ff9bb2b.

📒 Files selected for processing (31)
  • .env.example
  • src/backend/features/fuse/on-read/download-cache/constants.test.ts
  • src/backend/features/fuse/on-read/download-cache/constants.ts
  • src/backend/features/fuse/on-read/download-cache/download-and-save-block.ts
  • src/backend/features/fuse/on-read/download-cache/expand-to-block-boundaries.test.ts
  • src/backend/features/fuse/on-read/download-cache/expand-to-block-boundaries.ts
  • src/backend/features/fuse/on-read/download-cache/hydration-state.test.ts
  • src/backend/features/fuse/on-read/download-cache/hydration-state.ts
  • src/backend/features/fuse/on-read/handle-read-callback.test.ts
  • src/backend/features/fuse/on-read/handle-read-callback.ts
  • src/backend/features/fuse/on-read/read-or-hydrate.test.ts
  • src/backend/features/fuse/on-read/read-or-hydrate.ts
  • src/backend/features/virtual-drive/services/operations/read.service.ts
  • src/backend/features/virtual-drive/services/operations/should-emit-progress.test.ts
  • src/backend/features/virtual-drive/services/operations/should-emit-progress.ts
  • src/context/shared/domain/progress-constants.test.ts
  • src/context/storage/StorageFiles/application/download/download-with-progress-tracking.test.ts
  • src/context/storage/StorageFiles/application/download/download-with-progress-tracking.ts
  • src/infra/drive-server/client/interceptors/rate-limiter/attach-rate-limiter-interceptors.test.ts
  • src/infra/drive-server/client/interceptors/rate-limiter/attach-rate-limiter-interceptors.ts
  • src/infra/drive-server/client/interceptors/rate-limiter/constants.ts
  • src/infra/drive-server/client/interceptors/rate-limiter/create-request-interceptor.test.ts
  • src/infra/drive-server/client/interceptors/rate-limiter/create-request-interceptor.ts
  • src/infra/drive-server/client/interceptors/rate-limiter/create-response-interceptor.test.ts
  • src/infra/drive-server/client/interceptors/rate-limiter/create-response-interceptor.ts
  • src/infra/drive-server/client/interceptors/rate-limiter/get-request-key.ts
  • src/infra/drive-server/client/interceptors/rate-limiter/rate-limiter.types.ts
  • src/infra/drive-server/client/interceptors/rate-limiter/update-state-from-headers.test.ts
  • src/infra/drive-server/client/interceptors/rate-limiter/update-state-from-headers.ts
  • src/infra/environment/download-file/download-file.test.ts
  • src/infra/environment/download-file/download-file.ts

Comment thread src/backend/features/fuse/on-read/download-cache/constants.ts
Comment thread src/context/shared/domain/progress-constants.test.ts Outdated
Comment thread src/infra/drive-server/client/interceptors/rate-limiter/constants.ts Outdated
Comment on lines +53 to +55
delayState.requestKey = getRequestKey(config);
await waitForDelay(delayState, waitMs);
delayState.requestKey = null;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Track pending delays by request key.

If two 429 responses overlap, the second handler replaces the first handler's requestKey while delayState.pending still belongs to the first request. A new request with the first key then bypasses its required delay.

Replace the single pending and requestKey fields with per-key delay state. Clear only the completed key. Add an integration test with overlapping 429 responses for two different keys.

🤖 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/infra/drive-server/client/interceptors/rate-limiter/create-response-interceptor.ts`
around lines 53 - 55, Update the response interceptor’s delay tracking to store
pending state per request key instead of sharing delayState.requestKey and
delayState.pending across requests. Ensure overlapping 429 handlers for
different keys do not overwrite each other, and clear only the completed key
after waitForDelay finishes. Add an integration test covering overlapping 429
responses for two distinct request keys and verifying each key’s required delay
is preserved.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@egalvis27 check this

Comment thread src/infra/environment/download-file/download-file.ts

@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: 2

🤖 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/infra/drive-server/client/interceptors/rate-limiter/attach-rate-limiter-interceptors.test.ts`:
- Line 63: Update the test around the delayState setup to capture both
interceptor factory call arguments, then assert that the request and response
factories receive the identical DelayState object by reference rather than
merely equivalent object contents.

In
`@src/infra/drive-server/client/interceptors/rate-limiter/wait-for-delay.test.ts`:
- Line 16: Strengthen the assertions in the rate-limiter test around
pendingByKey so the stored promise is explicitly verified with toBeDefined()
before any identity comparison. In the same-key caller scenario, also assert the
fake-timer count shows only one delay was scheduled, covering the shared-delay
behavior.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 8c6b38d4-91e3-4840-b74b-c1716741b29d

📥 Commits

Reviewing files that changed from the base of the PR and between ff9bb2b and 7591ae6.

📒 Files selected for processing (16)
  • src/backend/features/fuse/on-read/download-cache/constants.test.ts
  • src/backend/features/fuse/on-read/handle-read-callback.test.ts
  • src/infra/drive-server/client/interceptors/rate-limiter/attach-rate-limiter-interceptors.test.ts
  • src/infra/drive-server/client/interceptors/rate-limiter/attach-rate-limiter-interceptors.ts
  • src/infra/drive-server/client/interceptors/rate-limiter/constants.ts
  • src/infra/drive-server/client/interceptors/rate-limiter/create-request-interceptor.test.ts
  • src/infra/drive-server/client/interceptors/rate-limiter/create-request-interceptor.ts
  • src/infra/drive-server/client/interceptors/rate-limiter/create-response-interceptor.test.ts
  • src/infra/drive-server/client/interceptors/rate-limiter/create-response-interceptor.ts
  • src/infra/drive-server/client/interceptors/rate-limiter/rate-limiter.types.ts
  • src/infra/drive-server/client/interceptors/rate-limiter/update-state-from-headers.test.ts
  • src/infra/drive-server/client/interceptors/rate-limiter/update-state-from-headers.ts
  • src/infra/drive-server/client/interceptors/rate-limiter/wait-for-delay.test.ts
  • src/infra/drive-server/client/interceptors/rate-limiter/wait-for-delay.ts
  • src/infra/environment/download-file/download-file.test.ts
  • src/infra/environment/download-file/download-file.ts
🚧 Files skipped from review as they are similar to previous changes (9)
  • src/infra/drive-server/client/interceptors/rate-limiter/constants.ts
  • src/infra/drive-server/client/interceptors/rate-limiter/update-state-from-headers.test.ts
  • src/infra/environment/download-file/download-file.test.ts
  • src/infra/drive-server/client/interceptors/rate-limiter/create-request-interceptor.ts
  • src/infra/drive-server/client/interceptors/rate-limiter/update-state-from-headers.ts
  • src/infra/drive-server/client/interceptors/rate-limiter/create-request-interceptor.test.ts
  • src/infra/drive-server/client/interceptors/rate-limiter/create-response-interceptor.ts
  • src/backend/features/fuse/on-read/handle-read-callback.test.ts
  • src/infra/environment/download-file/download-file.ts


it('should share the same delay state between request and response interceptors', () => {
const delayState = { pending: null };
const delayState = { pendingByKey: {} };

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Verify that both interceptors receive the same DelayState object.

The current structural assertions pass when each factory receives a separate { pendingByKey: {} } object. Capture the factory call arguments and assert reference equality between the request and response delay-state arguments.

🤖 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/infra/drive-server/client/interceptors/rate-limiter/attach-rate-limiter-interceptors.test.ts`
at line 63, Update the test around the delayState setup to capture both
interceptor factory call arguments, then assert that the request and response
factories receive the identical DelayState object by reference rather than
merely equivalent object contents.

const promise = waitForDelay(state, 100);
expect(state.pending).not.toBeNull();
const promise = waitForDelay(state, 'GET:/test', 100);
expect(state.pendingByKey['GET:/test']).not.toBeNull();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Assert that pendingByKey contains a promise before comparing it.

undefined passes not.toBeNull() on Line 16. It also makes the identity assertion on Line 33 pass when no entry exists. Assert toBeDefined() for the stored promise. Also verify that same-key callers create one delay, for example by checking the fake-timer count.

Also applies to: 28-33

🤖 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/infra/drive-server/client/interceptors/rate-limiter/wait-for-delay.test.ts`
at line 16, Strengthen the assertions in the rate-limiter test around
pendingByKey so the stored promise is explicitly verified with toBeDefined()
before any identity comparison. In the same-key caller scenario, also assert the
fake-timer count shows only one delay was scheduled, covering the shared-delay
behavior.

@sonarqubecloud

Copy link
Copy Markdown

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants