Skip to content

Conversation

JASIM0021
Copy link
Contributor

@JASIM0021 JASIM0021 commented Oct 15, 2025

Proposed changes

Implemented download caching for chat attachments to improve user experience by reducing download times and saving data usage. Files are now cached locally after first download, and subsequent access uses the cached version instead of re-downloading the same file.

Issue(s)

Closes #6756

How to test or reproduce

  1. Open a chat with PDF or document attachments
  2. Click on an attachment to download it
  3. Click on the same attachment again
  4. Observe that the file opens instantly (using cached version) instead of re-downloading
  5. Test with different file types (PDF, DOC, images, etc.)

Screenshots

Before Cache Implementation After Cache Implementation
without_cache.mp4
with_cache.mp4
File downloads completely each time File opens instantly from cache

Types of changes

  • Bugfix (non-breaking change which fixes an issue)
  • Improvement (non-breaking change which improves a current function)
  • New feature (non-breaking change which adds functionality)
  • Documentation update (if none of the other choices apply)

Checklist

  • I have read the CONTRIBUTING doc
  • I have signed the CLA
  • Lint and unit tests pass locally with my changes
  • I have added tests that prove my fix is effective or that my feature works (if applicable)
  • I have added necessary documentation (if applicable)
  • Any dependent changes have been merged and published in downstream modules

Further comments

The implementation leverages the existing getMediaCache function to check if files are already cached before initiating downloads. This approach maintains consistency with the existing caching system while providing the performance benefits of file caching for attachments.


<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

- New Features
  - Added support for an additional media type (“other”) and new file extensions (DOC, DOCX, PDF) for more accurate handling of diverse attachments.

- Refactor
  - Switched file downloads to a cache-first approach with fallback downloading, reducing repeat downloads and improving load times for previously accessed files.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Copy link
Contributor

coderabbitai bot commented Oct 15, 2025

Walkthrough

Introduces a new media type "other", exports getExtension with expanded extension resolution, and changes file download to check a media cache before downloading, using getMediaCache and downloadMediaFile instead of direct FileSystem.downloadAsync.

Changes

Cohort / File(s) Summary of Changes
Media type and extension utilities
app/lib/methods/handleMediaDownload.ts
- Added MediaTypes variant: 'other'
- getExtension made public/exported
- getExtension extended to resolve doc, docx, pdf; retains mime-type and URL-based resolution and fallback behavior
File download flow with caching
app/lib/methods/helpers/fileDownload.ts
- Replaced direct FileSystem.downloadAsync with cache-first flow
- Integrates getMediaCache (type 'other', mimeType, url) and downloadMediaFile as fallback
- Updated imports; preserved path assembly logic

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant C as Caller
  participant FD as fileDownload()
  participant MC as getMediaCache
  participant DM as downloadMediaFile

  C->>FD: request download (url, mimeType, messageId, ...)
  FD->>MC: lookup cached URI (type: "other", mimeType, url)
  alt cache hit
    MC-->>FD: cachedUri
    FD-->>C: return cachedUri
  else cache miss
    MC-->>FD: not found
    FD->>DM: downloadMediaFile({ messageId, type:"other", downloadUrl })
    DM-->>FD: downloadedUri
    FD-->>C: return downloadedUri
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

A rabbit taps the cache with glee,
“Found it!”—no need to fetch the sea.
New ‘other’ burrow joins the crew,
Extensions learn a trick or two.
If holes are empty, off I hop—
Download, stash, then back to shop. 🥕

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Title Check ⚠️ Warning The title contains spelling errors and does not clearly or professionally convey the main change of implementing caching for downloaded files, making it difficult for reviewers to immediately understand the purpose of the PR. Please update the title to a clear, concise description such as “Implement file caching for attachment downloads” to accurately reflect the PR’s primary change.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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 and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
app/lib/methods/helpers/fileDownload.ts (1)

22-29: Consider extracting the media type constant.

The 'other' as const is repeated twice. For better maintainability, consider extracting it to a constant at the top of the file.

+const FILE_MEDIA_TYPE = 'other' as const;
+
 export const getLocalFilePathFromFile = (localPath: string, attachment: IAttachment): string => `${localPath}${attachment.title}`;
 
 export const fileDownload = async (url: string, attachment?: IAttachment, fileName?: string): Promise<string> => {
 	let path = `${FileSystem.documentDirectory}`;
 	if (fileName) {
 		path = `${path}${sanitizeFileName(fileName)}`;
 	}
 	if (attachment?.title) {
 		path = `${path}${sanitizeFileName(attachment.title)}`;
 	}
 
-	const cache = await getMediaCache({ type: 'other' as const, mimeType: attachment?.format, urlToCache: url });
+	const cache = await getMediaCache({ type: FILE_MEDIA_TYPE, mimeType: attachment?.format, urlToCache: url });
 
 	if (cache?.exists) {
 		return cache.uri;
 	}
 	const option = {
 		messageId: url,
-		type: 'other' as const,
+		type: FILE_MEDIA_TYPE,
 		downloadUrl: url
 	};
app/lib/methods/handleMediaDownload.ts (1)

77-90: Refactor repetitive extension checks.

The extension checks for doc, docx, and pdf follow the same pattern and can be consolidated to reduce code duplication.

Apply this diff to refactor the extension checks:

-	// support url with doc extension
-	if (url?.split('.').pop() === 'doc') {
-		return 'doc';
-	}
-
-	// support url with docx extension
-	if (url?.split('.').pop() === 'docx') {
-		return 'docx';
-	}
-
-	// support url with pdf extension
-	if (url?.split('.').pop() === 'pdf') {
-		return 'pdf';
-	}
+	// support document extensions from URL
+	const urlExtension = url?.split('.').pop();
+	if (urlExtension && ['doc', 'docx', 'pdf'].includes(urlExtension)) {
+		return urlExtension;
+	}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between aa94a59 and 72ecc6b.

📒 Files selected for processing (2)
  • app/lib/methods/handleMediaDownload.ts (3 hunks)
  • app/lib/methods/helpers/fileDownload.ts (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
app/lib/methods/helpers/fileDownload.ts (1)
app/lib/methods/handleMediaDownload.ts (2)
  • getMediaCache (162-187)
  • downloadMediaFile (268-315)
🔇 Additional comments (3)
app/lib/methods/helpers/fileDownload.ts (1)

9-9: LGTM!

The import additions are correct and all imported functions are properly utilized in the caching flow.

app/lib/methods/handleMediaDownload.ts (2)

71-71: LGTM!

Exporting getExtension enables broader reuse across the codebase and supports the new caching functionality.


71-90: Verify extension resolution priority is correct.

The current implementation prioritizes URL extensions (gif, doc, docx, pdf) over mimeType. This means if a URL ends with .pdf but has a different mimeType (e.g., application/msword), it will return 'pdf' instead of using the mimeType-based resolution. Ensure this behavior aligns with your requirements, especially for cases where URL extensions might be misleading or incorrect.

Consider whether mimeType should take precedence for more accurate file type detection, or if the current URL-first approach is intentional for your use case.

import { headers } from './helpers/fetch';

export type MediaTypes = 'audio' | 'image' | 'video';
export type MediaTypes = 'audio' | 'image' | 'video' | 'other';
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add default extension for 'other' media type.

The MediaTypes union now includes 'other', but the defaultType object (lines 20-24) doesn't have an entry for it. This means getExtension (line 92) will return undefined when type is 'other' and mimeType is undefined, which could break filename generation in getFilename.

Apply this diff to add a default extension for the 'other' type:

 const defaultType = {
 	audio: 'mp3',
 	image: 'jpg',
-	video: 'mp4'
+	video: 'mp4',
+	other: 'bin'
 };

Alternatively, if the 'other' type should always have a mimeType, add validation to ensure it's provided.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export type MediaTypes = 'audio' | 'image' | 'video' | 'other';
const defaultType = {
audio: 'mp3',
image: 'jpg',
video: 'mp4',
other: 'bin'
};
🤖 Prompt for AI Agents
In app/lib/methods/handleMediaDownload.ts around lines 18–24 and 92, the
MediaTypes union now includes 'other' but the defaultType mapping lacks an entry
for it, causing getExtension to return undefined for type 'other' when mimeType
is missing; add a default extension for 'other' in the defaultType object (for
example 'bin' or 'dat') so getExtension always returns a sane value, or
alternatively add validation at the start of getFilename/getExtension that
throws or requires a mimeType when type === 'other'.

Comment on lines +22 to +34
const cache = await getMediaCache({ type: 'other' as const, mimeType: attachment?.format, urlToCache: url });

if (cache?.exists) {
return cache.uri;
}
const option = {
messageId: url,
type: 'other' as const,
downloadUrl: url
};

const uri = await downloadMediaFile(option);
return uri;
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical issues with messageId and encryption handling.

This implementation has two critical problems:

  1. Line 28: Passing url as messageId is incorrect. The downloadMediaFile function calls persistMessage(messageId, ...) which attempts to look up the message by this ID in the database. Using the URL instead of an actual message ID will cause the persistence to fail silently, potentially corrupting the message state.

  2. Lines 27-31: The option object is missing encryption and originalChecksum parameters. This means encrypted attachments won't be decrypted properly. Looking at fileDownloadAndPreview (lines 44-49), encryption is handled separately there, but downloadMediaFile also handles encryption (lines 300-302 in handleMediaDownload.ts), creating an inconsistency.

The fileDownload function needs to accept messageId as a parameter and pass encryption details when available:

-export const fileDownload = async (url: string, attachment?: IAttachment, fileName?: string): Promise<string> => {
+export const fileDownload = async (url: string, attachment?: IAttachment, fileName?: string, messageId?: string): Promise<string> => {
 	let path = `${FileSystem.documentDirectory}`;
 	if (fileName) {
 		path = `${path}${sanitizeFileName(fileName)}`;
 	}
 	if (attachment?.title) {
 		path = `${path}${sanitizeFileName(attachment.title)}`;
 	}
 
 	const cache = await getMediaCache({ type: 'other' as const, mimeType: attachment?.format, urlToCache: url });
 
 	if (cache?.exists) {
 		return cache.uri;
 	}
 	const option = {
-		messageId: url,
+		messageId: messageId || url,
 		type: 'other' as const,
-		downloadUrl: url
+		downloadUrl: url,
+		...(attachment?.encryption && attachment?.hashes?.sha256 && {
+			encryption: attachment.encryption,
+			originalChecksum: attachment.hashes.sha256
+		})
 	};
 
 	const uri = await downloadMediaFile(option);
 	return uri;
 };

Then update the call in fileDownloadAndPreview:

 		if (!file.startsWith('file://')) {
-			file = await fileDownload(file, attachment);
+			file = await fileDownload(file, attachment, undefined, messageId);
 
-			if (attachment.encryption) {
-				if (!attachment.hashes?.sha256) {
-					throw new Error('Missing checksum');
-				}
-				await Encryption.addFileToDecryptFileQueue(messageId, file, attachment.encryption, attachment.hashes?.sha256);
-			}
+			// Encryption is now handled by downloadMediaFile
 		}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const cache = await getMediaCache({ type: 'other' as const, mimeType: attachment?.format, urlToCache: url });
if (cache?.exists) {
return cache.uri;
}
const option = {
messageId: url,
type: 'other' as const,
downloadUrl: url
};
const uri = await downloadMediaFile(option);
return uri;
// app/lib/methods/helpers/fileDownload.ts
export const fileDownload = async (
url: string,
attachment?: IAttachment,
fileName?: string,
messageId?: string
): Promise<string> => {
let path = `${FileSystem.documentDirectory}`;
if (fileName) {
path = `${path}${sanitizeFileName(fileName)}`;
}
if (attachment?.title) {
path = `${path}${sanitizeFileName(attachment.title)}`;
}
const cache = await getMediaCache({
type: 'other' as const,
mimeType: attachment?.format,
urlToCache: url
});
if (cache?.exists) {
return cache.uri;
}
const option = {
messageId: messageId || url,
type: 'other' as const,
downloadUrl: url,
...(attachment?.encryption && attachment?.hashes?.sha256 && {
encryption: attachment.encryption,
originalChecksum: attachment.hashes.sha256
})
};
const uri = await downloadMediaFile(option);
return uri;
};
// … later in fileDownloadAndPreview (around lines 44–49):
if (!file.startsWith('file://')) {
file = await fileDownload(file, attachment, undefined, messageId);
// Encryption is now handled inside downloadMediaFile,
// so we no longer need to enqueue decryption here.
}
🤖 Prompt for AI Agents
In app/lib/methods/helpers/fileDownload.ts around lines 22 to 34, the function
wrongly uses the URL as messageId and omits encryption and originalChecksum in
the download options; change the function signature to accept a real messageId
parameter (and encryption/originalChecksum when available), pass that messageId
(not the URL) into the option.messageId field, and include option.encryption and
option.originalChecksum populated from the attachment or caller arguments; also
update the caller (fileDownloadAndPreview) to forward the correct messageId and
any encryption/originalChecksum values when invoking this fileDownload helper.

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.

1 participant