Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Append current time to the downloaded file name #23531

Merged
21 changes: 17 additions & 4 deletions src/libs/fileDownload/FileUtils.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {Alert, Linking} from 'react-native';
import moment from 'moment';
import CONST from '../../CONST';
import * as Localize from '../Localize';
import DateUtils from '../DateUtils';

/**
* Show alert on successful attachment download
Expand Down Expand Up @@ -57,7 +57,7 @@ function getAttachmentName(url) {
if (!url) {
return '';
}
return `${moment().format('DDMMYYYYHHmmss')}.${url.split(/[#?]/)[0].split('.').pop().trim()}`;
return `${DateUtils.getDBTime()}.${url.split(/[#?]/)[0].split('.').pop().trim()}`;
}

/**
Expand Down Expand Up @@ -104,7 +104,7 @@ function getFileType(fileUrl) {
function splitExtensionFromFileName(fullFileName) {
const fileName = fullFileName.trim();
const splitFileName = fileName.split('.');
const fileExtension = splitFileName.pop();
const fileExtension = splitFileName.length > 1 ? splitFileName.pop() : '';
return {fileName: splitFileName.join('.'), fileExtension};
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The change is to fix the file name that is returned as the extension when the file name does not have an extension, for example, if the file name is anyfilename, the fileExtension is anyfilename and the fileName is empty.

This function is being used for attachment validation. Before this change, we can upload a file name jpg because jpg is a valid extension.

Copy link
Member

Choose a reason for hiding this comment

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

Lovely. Thanks.

}

Expand All @@ -118,4 +118,17 @@ function cleanFileName(fileName) {
return fileName.replace(/[^a-zA-Z0-9\-._]/g, '_');
}

export {showGeneralErrorAlert, showSuccessAlert, showPermissionErrorAlert, splitExtensionFromFileName, getAttachmentName, getFileType, cleanFileName};
/**
* @param {String} fileName
* @returns {String}
*/
function appendTimeToFileName(fileName) {
bernhardoj marked this conversation as resolved.
Show resolved Hide resolved
const file = splitExtensionFromFileName(fileName);
let newFileName = `${file.fileName} - ${DateUtils.getDBTime()}`;
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
let newFileName = `${file.fileName} - ${DateUtils.getDBTime()}`;
let newFileName = `${file.fileName}-${DateUtils.getDBTime()}`;

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

if (file.fileExtension) {
newFileName += `.${file.fileExtension}`;
}
return newFileName;
}

export {showGeneralErrorAlert, showSuccessAlert, showPermissionErrorAlert, splitExtensionFromFileName, getAttachmentName, getFileType, cleanFileName, appendTimeToFileName};
2 changes: 1 addition & 1 deletion src/libs/fileDownload/getAttachmentDetails.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import tryResolveUrlFromApiRoot from '../tryResolveUrlFromApiRoot';
* @param {String} html
* @returns {Object}
*/
export default function getAttachmentName(html) {
export default function getAttachmentDetails(html) {
// Files can be rendered either as anchor tag or as an image so based on that we have to form regex.
const IS_IMAGE_TAG = /<img([\w\W]+?)\/>/i.test(html);
const PREVIEW_SOURCE_REGEX = new RegExp(`${CONST.ATTACHMENT_PREVIEW_ATTRIBUTE}*=*"(.+?)"`, 'i');
Expand Down
2 changes: 1 addition & 1 deletion src/libs/fileDownload/index.android.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ function handleDownload(url, fileName) {

// Android files will download to Download directory
const path = dirs.DownloadDir;
const attachmentName = fileName || FileUtils.getAttachmentName(url);
const attachmentName = FileUtils.appendTimeToFileName(fileName) || FileUtils.getAttachmentName(url);
Copy link
Contributor

Choose a reason for hiding this comment

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

The current logic for determining attachmentName doesn't adequately handle fallback scenarios:

const attachmentName = FileUtils.appendTimeToFileName(fileName) || FileUtils.getAttachmentName(url);
  1. If fileName is undefined, the application will throw an error due to the attempt to append time to it, leading to a failed download.
  2. FileUtils.appendTimeToFileName always returns a value, so we won't ever fall back to FileUtils.getAttachmentName, even in cases where fileName is an empty string.

The previous version of the function handled both of these cases

Copy link
Contributor 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 think we should do

const attachmentName = fileName ? FileUtils.appendTimeToFileName(fileName) : FileUtils.getAttachmentName(url);

instead. Or should appendTimeToFileName handle the empty (undefined, null, empty string) case?

Btw, are you coming from an issue that is caused by this change? I'm curious to know in what case the fileName is empty. If not, what should I do here? Should I open a PR?

cc: @parasharrajat

Copy link
Contributor

Choose a reason for hiding this comment

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

I've stumbled on the problem, when I was testing my PR on iOS: #25556

I've already included a fix in my PR

It seems fileName can be undefined for attachments that are send by Concierge, when they embed images via Drag & Drop

Copy link
Member

Choose a reason for hiding this comment

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

I see. Thanks @kidroca for handling that. Let's know if you want any refactors.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It seems fileName can be undefined for attachments that are send by Concierge, when they embed images via Drag & Drop

I see. What a unique case. Thanks for catching and handling it!


Copy link
Contributor Author

Choose a reason for hiding this comment

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

If the fileName is empty (idk when this case could happen), it will generate the file name from the url. FileUtils.getAttachmentName itself appends the current time to the generated file name with a different format, that's why I append the time to fileName only.

function getAttachmentName(url) {
if (!url) {
return '';
}
return `${moment().format('DDMMYYYYHHmmss')}.${url.split(/[#?]/)[0].split('.').pop().trim()}`;
}

Copy link
Member

Choose a reason for hiding this comment

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

Let's follow the same pattern for date.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

which one to follow, the format from getAttachmentName or the new one I created?
getAttachmentName: 24102000163544.[extension],
if we follow the new one, this becomes 2023-07-26T03:58:14.737Z.[extension]

new: [name] - 2023-07-26T03:58:14.737Z.[extension],
if we follow the above, this becomes [name] - 24102000163544.[extension]

Also, I don't found any case for getAttachmentName yet, so we can't test its usage.

Copy link
Member

@parasharrajat parasharrajat Jul 26, 2023

Choose a reason for hiding this comment

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

I would say let's do the old one (24102000163544). But I don't have any preference. Both works. Feel free to use the one that look best to you.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I decided to use the date format from DateUtils.getDBTime(). It is the same with the new format, except T is replaced with space and Z is removed. The old format does not have milliseconds which I think is really important to keep it.

const isLocalFile = url.startsWith('file://');

Expand Down
2 changes: 1 addition & 1 deletion src/libs/fileDownload/index.ios.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ export default function fileDownload(fileUrl, fileName) {
return new Promise((resolve) => {
let fileDownloadPromise = null;
const fileType = FileUtils.getFileType(fileUrl);
const attachmentName = fileName || FileUtils.getAttachmentName(fileUrl);
const attachmentName = FileUtils.appendTimeToFileName(fileName) || FileUtils.getAttachmentName(fileUrl);

switch (fileType) {
case CONST.ATTACHMENT_FILE_TYPE.IMAGE:
Expand Down
2 changes: 1 addition & 1 deletion src/libs/fileDownload/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export default function fileDownload(url, fileName) {
link.style.display = 'none';
link.setAttribute(
'download',
fileName || FileUtils.getAttachmentName(url), // generating the file name
FileUtils.appendTimeToFileName(fileName) || FileUtils.getAttachmentName(url), // generating the file name
);

// Append to html link element page
Expand Down
5 changes: 3 additions & 2 deletions src/libs/localFileDownload/index.android.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,14 @@ import * as FileUtils from '../fileDownload/FileUtils';
* @param {String} textContent
*/
export default function localFileDownload(fileName, textContent) {
const newFileName = FileUtils.appendTimeToFileName(fileName);
const dir = RNFetchBlob.fs.dirs.DocumentDir;
const path = `${dir}/${fileName}.txt`;
const path = `${dir}/${newFileName}.txt`;

RNFetchBlob.fs.writeFile(path, textContent, 'utf8').then(() => {
RNFetchBlob.MediaCollection.copyToMediaStore(
{
name: fileName,
name: newFileName,
parentFolder: '', // subdirectory in the Media Store, empty goes to 'Downloads'
mimeType: 'text/plain',
},
Expand Down
6 changes: 4 additions & 2 deletions src/libs/localFileDownload/index.ios.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {Share} from 'react-native';
import RNFetchBlob from 'react-native-blob-util';
import * as FileUtils from '../fileDownload/FileUtils';

/**
* Writes a local file to the app's internal directory with the given fileName
Expand All @@ -10,11 +11,12 @@ import RNFetchBlob from 'react-native-blob-util';
* @param {String} textContent
*/
export default function localFileDownload(fileName, textContent) {
const newFileName = FileUtils.appendTimeToFileName(fileName);
const dir = RNFetchBlob.fs.dirs.DocumentDir;
const path = `${dir}/${fileName}.txt`;
const path = `${dir}/${newFileName}.txt`;

RNFetchBlob.fs.writeFile(path, textContent, 'utf8').then(() => {
Share.share({url: path, title: fileName}).finally(() => {
Share.share({url: path, title: newFileName}).finally(() => {
RNFetchBlob.fs.unlink(path);
});
});
Expand Down
4 changes: 3 additions & 1 deletion src/libs/localFileDownload/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import * as FileUtils from '../fileDownload/FileUtils';

/**
* Creates a Blob with the given fileName and textContent, then dynamically
* creates a temporary anchor, just to programmatically click it, so the file
Expand All @@ -10,7 +12,7 @@ export default function localFileDownload(fileName, textContent) {
const blob = new Blob([textContent], {type: 'text/plain'});
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.download = `${fileName}.txt`;
link.download = FileUtils.appendTimeToFileName(`${fileName}.txt`);
link.href = url;
link.click();
}
38 changes: 38 additions & 0 deletions tests/unit/FileUtilsTest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import DateUtils from '../../src/libs/DateUtils';
import * as FileUtils from '../../src/libs/fileDownload/FileUtils';

describe('FileUtils', () => {
describe('splitExtensionFromFileName', () => {
it('should return correct file name and extension', () => {
const file = FileUtils.splitExtensionFromFileName('image.jpg');
expect(file.fileName).toEqual('image');
expect(file.fileExtension).toEqual('jpg');
});

it('should return correct file name and extension even with multiple dots on the file name', () => {
const file = FileUtils.splitExtensionFromFileName('image.pdf.jpg');
expect(file.fileName).toEqual('image.pdf');
expect(file.fileExtension).toEqual('jpg');
});

it('should return empty extension if the file name does not have it', () => {
const file = FileUtils.splitExtensionFromFileName('image');
expect(file.fileName).toEqual('image');
expect(file.fileExtension).toEqual('');
});
});

describe('appendTimeToFileName', () => {
it('should append current time to the end of the file name', () => {
const actualFileName = FileUtils.appendTimeToFileName('image.jpg');
const expectedFileName = `image - ${DateUtils.getDBTime()}.jpg`;
expect(actualFileName).toEqual(expectedFileName);
});

it('should append current time to the end of the file name without extension', () => {
const actualFileName = FileUtils.appendTimeToFileName('image');
const expectedFileName = `image - ${DateUtils.getDBTime()}`;
expect(actualFileName).toEqual(expectedFileName);
});
});
});
Loading