From b5b948e58f8249d7e3128de9daa3baf341b77dd4 Mon Sep 17 00:00:00 2001 From: Elias Nahum Date: Thu, 11 Oct 2018 14:11:05 -0300 Subject: [PATCH] Properly handle max file size (#2248) * Properly handle max file size * Feedback review --- app/components/attachment_button.js | 19 ++++++++++++-- .../file_upload_preview.js | 15 +++++++++-- app/components/post_textbox/index.js | 2 ++ app/components/post_textbox/post_textbox.js | 25 +++++++++++++++++++ assets/base/i18n/en.json | 1 + .../android/extension_post/extension_post.js | 18 +++++++++---- 6 files changed, 71 insertions(+), 9 deletions(-) diff --git a/app/components/attachment_button.js b/app/components/attachment_button.js index 44024a573..b7198a2cf 100644 --- a/app/components/attachment_button.js +++ b/app/components/attachment_button.js @@ -10,6 +10,8 @@ import { StyleSheet, TouchableOpacity, } from 'react-native'; +import RNFetchBlob from 'rn-fetch-blob'; + import Icon from 'react-native-vector-icons/Ionicons'; import {DocumentPicker} from 'react-native-document-picker'; import ImagePicker from 'react-native-image-picker'; @@ -27,8 +29,10 @@ export default class AttachmentButton extends PureComponent { children: PropTypes.node, fileCount: PropTypes.number, maxFileCount: PropTypes.number.isRequired, + maxFileSize: PropTypes.number.isRequired, navigator: PropTypes.object.isRequired, onShowFileMaxWarning: PropTypes.func, + onShowFileSizeWarning: PropTypes.func, theme: PropTypes.object.isRequired, uploadFiles: PropTypes.func.isRequired, wrapper: PropTypes.bool, @@ -293,8 +297,19 @@ export default class AttachmentButton extends PureComponent { return true; }; - uploadFiles = (images) => { - this.props.uploadFiles(images); + uploadFiles = async (files) => { + const file = files[0]; + if (!file.fileSize | !file.fileName) { + const fileInfo = await RNFetchBlob.fs.stat(file.path); + file.fileSize = fileInfo.size; + file.fileName = fileInfo.filename; + } + + if (file.fileSize > this.props.maxFileSize) { + this.props.onShowFileSizeWarning(file.fileName); + } else { + this.props.uploadFiles(files); + } }; handleFileAttachmentOption = (action) => { diff --git a/app/components/file_upload_preview/file_upload_preview.js b/app/components/file_upload_preview/file_upload_preview.js index 06bfdf1b0..c33987c5d 100644 --- a/app/components/file_upload_preview/file_upload_preview.js +++ b/app/components/file_upload_preview/file_upload_preview.js @@ -6,6 +6,7 @@ import PropTypes from 'prop-types'; import { ScrollView, StyleSheet, + Text, View, } from 'react-native'; @@ -20,6 +21,7 @@ export default class FileUploadPreview extends PureComponent { deviceHeight: PropTypes.number.isRequired, files: PropTypes.array.isRequired, filesUploadingForCurrentChannel: PropTypes.bool.isRequired, + fileSizeWarning: PropTypes.string, rootId: PropTypes.string, showFileMaxWarning: PropTypes.bool.isRequired, theme: PropTypes.object.isRequired, @@ -42,12 +44,17 @@ export default class FileUploadPreview extends PureComponent { render() { const { showFileMaxWarning, + fileSizeWarning, channelIsLoading, filesUploadingForCurrentChannel, deviceHeight, files, } = this.props; - if (channelIsLoading || (!files.length && !filesUploadingForCurrentChannel)) { + + if ( + !fileSizeWarning && !showFileMaxWarning && + (channelIsLoading || (!files.length && !filesUploadingForCurrentChannel)) + ) { return null; } @@ -68,7 +75,11 @@ export default class FileUploadPreview extends PureComponent { defaultMessage='Uploads limited to 5 files maximum.' /> )} - + {Boolean(fileSizeWarning) && + + {fileSizeWarning} + + } ); diff --git a/app/components/post_textbox/index.js b/app/components/post_textbox/index.js index ca00d1f5e..303bedb56 100644 --- a/app/components/post_textbox/index.js +++ b/app/components/post_textbox/index.js @@ -20,6 +20,7 @@ import {handleCommentDraftChanged, handleCommentDraftSelectionChanged} from 'app import {userTyping} from 'app/actions/views/typing'; import {getCurrentChannelDraft, getThreadDraft} from 'app/selectors/views'; import {getChannelMembersForDm} from 'app/selectors/channel'; +import {getAllowedServerMaxFileSize} from 'app/utils/file'; import PostTextbox from './post_textbox'; @@ -53,6 +54,7 @@ function mapStateToProps(state, ownProps) { userIsOutOfOffice, deactivatedChannel, files: currentDraft.files, + maxFileSize: getAllowedServerMaxFileSize(config), maxMessageLength: (config && parseInt(config.MaxPostSize || 0, 10)) || MAX_MESSAGE_LENGTH, theme: getTheme(state), uploadFileRequestStatus: state.requests.files.uploadFiles.status, diff --git a/app/components/post_textbox/post_textbox.js b/app/components/post_textbox/post_textbox.js index d6156771c..68c4201a9 100644 --- a/app/components/post_textbox/post_textbox.js +++ b/app/components/post_textbox/post_textbox.js @@ -8,6 +8,7 @@ import {intlShape} from 'react-intl'; import Button from 'react-native-button'; import {General, RequestStatus} from 'mattermost-redux/constants'; import EventEmitter from 'mattermost-redux/utils/event_emitter'; +import {getFormattedFileSize} from 'mattermost-redux/utils/file_utils'; import AttachmentButton from 'app/components/attachment_button'; import Autocomplete from 'app/components/autocomplete'; @@ -51,6 +52,7 @@ export default class PostTextbox extends PureComponent { currentUserId: PropTypes.string.isRequired, deactivatedChannel: PropTypes.bool.isRequired, files: PropTypes.array, + maxFileSize: PropTypes.number.isRequired, maxMessageLength: PropTypes.number.isRequired, navigator: PropTypes.object, rootId: PropTypes.string, @@ -79,6 +81,7 @@ export default class PostTextbox extends PureComponent { contentHeight: INITIAL_HEIGHT, cursorPosition: 0, keyboardType: 'default', + fileSizeWarning: null, top: 0, value: props.value, showFileMaxWarning: false, @@ -458,6 +461,23 @@ export default class PostTextbox extends PureComponent { }); }; + onShowFileSizeWarning = (filename) => { + const {formatMessage} = this.context.intl; + const fileSizeWarning = formatMessage({ + id: 'file_upload.fileAbove', + defaultMessage: 'File above {max}MB cannot be uploaded: {filename}', + }, { + max: getFormattedFileSize({size: this.props.maxFileSize}), + filename, + }); + + this.setState({fileSizeWarning}, () => { + setTimeout(() => { + this.setState({fileSizeWarning: null}); + }, 3000); + }); + }; + onCloseChannelPress = () => { const {onCloseChannel, channelTeamId} = this.props; this.props.actions.selectPenultimateChannel(channelTeamId); @@ -502,6 +522,7 @@ export default class PostTextbox extends PureComponent { channelIsReadOnly, deactivatedChannel, files, + maxFileSize, navigator, rootId, theme, @@ -523,6 +544,7 @@ export default class PostTextbox extends PureComponent { const { contentHeight, cursorPosition, + fileSizeWarning, showFileMaxWarning, top, value, @@ -549,8 +571,10 @@ export default class PostTextbox extends PureComponent { theme={theme} navigator={navigator} fileCount={files.length} + maxFileSize={maxFileSize} maxFileCount={MAX_FILE_COUNT} onShowFileMaxWarning={this.onShowFileMaxWarning} + onShowFileSizeWarning={this.onShowFileSizeWarning} uploadFiles={this.handleUploadFiles} /> ); @@ -564,6 +588,7 @@ export default class PostTextbox extends PureComponent { diff --git a/assets/base/i18n/en.json b/assets/base/i18n/en.json index 29328d675..0520bbfd6 100644 --- a/assets/base/i18n/en.json +++ b/assets/base/i18n/en.json @@ -69,6 +69,7 @@ "edit_post.save": "Save", "error.team_not_found.title": "Team Not Found", "file_attachment.download": "Download", + "file_upload.fileAbove": "File above {max}MB cannot be uploaded: {filename}", "get_post_link_modal.title": "Copy Permalink", "integrations.add": "Add", "intro_messages.anyMember": " Any member can join and read this channel.", diff --git a/share_extension/android/extension_post/extension_post.js b/share_extension/android/extension_post/extension_post.js index e96aae3b9..af3c53116 100644 --- a/share_extension/android/extension_post/extension_post.js +++ b/share_extension/android/extension_post/extension_post.js @@ -24,7 +24,9 @@ import RNFetchBlob from 'rn-fetch-blob'; import {Preferences} from 'mattermost-redux/constants'; import {getFormattedFileSize, lookupMimeType} from 'mattermost-redux/utils/file_utils'; +import Loading from 'app/components/loading'; import PaperPlane from 'app/components/paper_plane'; +import {MAX_FILE_COUNT} from 'app/constants/post_textbox'; import mattermostManaged from 'app/mattermost_managed'; import {getExtensionFromMime} from 'app/utils/file'; import {emptyFunction} from 'app/utils/general'; @@ -263,7 +265,7 @@ export default class ExtensionPost extends PureComponent { }; loadData = async (items) => { - const {token, url} = this.props; + const {maxFileSize, token, url} = this.props; if (token && url) { const text = []; const files = []; @@ -312,13 +314,13 @@ export default class ExtensionPost extends PureComponent { const value = text.join('\n'); - if (!error) { + if (!error && files.length <= MAX_FILE_COUNT && totalSize <= maxFileSize) { this.props.navigation.setParams({ post: this.onPost, }); } - this.setState({error, files, value, hasPermission: true, totalSize}); + this.setState({error, files, value, hasPermission: true, totalSize, loaded: true}); } }; @@ -489,7 +491,13 @@ export default class ExtensionPost extends PureComponent { render() { const {formatMessage} = this.context.intl; const {maxFileSize, token, url} = this.props; - const {error, hasPermission, files, totalSize} = this.state; + const {error, hasPermission, files, totalSize, loaded} = this.state; + + if (!loaded) { + return ( + + ); + } if (error) { return this.renderErrorMessage(error); @@ -503,7 +511,7 @@ export default class ExtensionPost extends PureComponent { }); return this.renderErrorMessage(storage); - } else if (files.length > 5) { + } else if (files.length > MAX_FILE_COUNT) { const fileCount = formatMessage({ id: 'mobile.extension.file_limit', defaultMessage: 'Sharing is limited to a maximum of 5 files.',