remove all the redux stuff and do the same as file upload

This commit is contained in:
Reinaldo Neto 2023-05-19 01:18:57 -03:00
parent f136dfb03c
commit 95cbe25ae7
10 changed files with 107 additions and 219 deletions

View File

@ -84,4 +84,3 @@ export const ENCRYPTION = createRequestTypes('ENCRYPTION', ['INIT', 'STOP', 'DEC
export const PERMISSIONS = createRequestTypes('PERMISSIONS', ['SET', 'UPDATE']); export const PERMISSIONS = createRequestTypes('PERMISSIONS', ['SET', 'UPDATE']);
export const ROLES = createRequestTypes('ROLES', ['SET', 'UPDATE', 'REMOVE']); export const ROLES = createRequestTypes('ROLES', ['SET', 'UPDATE', 'REMOVE']);
export const MEDIA_DOWNLOAD = createRequestTypes('MEDIA_DOWNLOAD', ['IN_PROGRESS', 'REMOVE']);

View File

@ -1,50 +0,0 @@
import { Action } from 'redux';
import { DownloadResumable } from 'expo-file-system';
import { MEDIA_DOWNLOAD } from './actionsTypes';
import { MediaTypes, mediaDownloadKey } from '../lib/methods/handleMediaDownload';
interface IMediaDownloadInProgressAction extends Action {
key: string;
downloadResumable: DownloadResumable;
}
interface IMediaDownloadRemoveAction extends Action {
key: string;
}
export type TActionMediaDownload = IMediaDownloadInProgressAction & IMediaDownloadRemoveAction;
interface IMediaDownloadInprogress {
mediaType: MediaTypes;
messageId: string;
downloadResumable: DownloadResumable;
}
interface IMediaDownloadRemove {
mediaType: MediaTypes;
messageId: string;
}
export const mediaDownloadInProgress = ({
mediaType,
messageId,
downloadResumable
}: IMediaDownloadInprogress): IMediaDownloadInProgressAction => {
const key = mediaDownloadKey(mediaType, messageId);
return {
type: MEDIA_DOWNLOAD.IN_PROGRESS,
key,
downloadResumable
};
};
export const mediaDownloadRemove = ({ mediaType, messageId }: IMediaDownloadRemove): IMediaDownloadRemoveAction => {
const key = mediaDownloadKey(mediaType, messageId);
return {
type: MEDIA_DOWNLOAD.REMOVE,
key
};
};

View File

@ -1,7 +1,6 @@
import React, { useContext, useLayoutEffect, useRef, useState } from 'react'; import React, { useContext, useEffect, useRef, useState } from 'react';
import { StyleProp, StyleSheet, TextStyle, View, Text } from 'react-native'; import { StyleProp, StyleSheet, TextStyle, View, Text } from 'react-native';
import { dequal } from 'dequal'; import { dequal } from 'dequal';
import * as FileSystem from 'expo-file-system';
import Touchable from './Touchable'; import Touchable from './Touchable';
import Markdown from '../markdown'; import Markdown from '../markdown';
@ -18,10 +17,16 @@ import RCActivityIndicator from '../ActivityIndicator';
import { TGetCustomEmoji } from '../../definitions/IEmoji'; import { TGetCustomEmoji } from '../../definitions/IEmoji';
import { useTheme } from '../../theme'; import { useTheme } from '../../theme';
import { formatAttachmentUrl } from '../../lib/methods/helpers/formatAttachmentUrl'; import { formatAttachmentUrl } from '../../lib/methods/helpers/formatAttachmentUrl';
import { MediaTypes, downloadMediaFile, searchMediaFileAsync } from '../../lib/methods/handleMediaDownload'; import {
LOCAL_DOCUMENT_PATH,
MediaTypes,
cancelDownload,
downloadMediaFile,
isDownloadActive,
searchMediaFileAsync
} from '../../lib/methods/handleMediaDownload';
import { isAutoDownloadEnabled } from './helpers/mediaDownload/autoDownloadPreference'; import { isAutoDownloadEnabled } from './helpers/mediaDownload/autoDownloadPreference';
import sharedStyles from '../../views/Styles'; import sharedStyles from '../../views/Styles';
import userPreferences from '../../lib/methods/userPreferences';
const SUPPORTED_TYPES = ['video/quicktime', 'video/mp4', ...(isIOS ? [] : ['video/3gp', 'video/mkv'])]; const SUPPORTED_TYPES = ['video/quicktime', 'video/mp4', ...(isIOS ? [] : ['video/3gp', 'video/mkv'])];
const isTypeSupported = (type: string) => SUPPORTED_TYPES.indexOf(type) !== -1; const isTypeSupported = (type: string) => SUPPORTED_TYPES.indexOf(type) !== -1;
@ -70,18 +75,16 @@ const DownloadIndicator = ({ handleCancelDownload }: { handleCancelDownload(): v
); );
}; };
const downloadResumableKey = (video: string) => `DownloadResumable${video}`;
const Video = React.memo( const Video = React.memo(
({ file, showAttachment, getCustomEmoji, style, isReply, messageId }: IMessageVideo) => { ({ file, showAttachment, getCustomEmoji, style, isReply, messageId }: IMessageVideo) => {
const [newFile, setNewFile] = useState(file);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const { baseUrl, user } = useContext(MessageContext); const { baseUrl, user } = useContext(MessageContext);
const { theme } = useTheme(); const { theme } = useTheme();
const filePath = useRef(''); const filePath = useRef('');
const downloadResumable = useRef<FileSystem.DownloadResumable | null>(null);
const video = formatAttachmentUrl(file.video_url, user.id, user.token, baseUrl); const video = formatAttachmentUrl(file.video_url, user.id, user.token, baseUrl);
useLayoutEffect(() => { useEffect(() => {
const handleAutoDownload = async () => { const handleAutoDownload = async () => {
if (video) { if (video) {
const searchVideoCached = await searchMediaFileAsync({ const searchVideoCached = await searchMediaFileAsync({
@ -89,16 +92,25 @@ const Video = React.memo(
mimeType: file.video_type, mimeType: file.video_type,
messageId messageId
}); });
console.log('🚀 ~ file: Video.tsx:100 ~ handleAutoDownload ~ searchVideoCached:', searchVideoCached);
filePath.current = searchVideoCached.filePath; filePath.current = searchVideoCached.filePath;
handleDownloadResumableSnapshot(); const downloadActive = isDownloadActive(MediaTypes.video, messageId);
if (searchVideoCached.file?.exists) { if (searchVideoCached.file?.exists) {
file.video_url = searchVideoCached.file.uri; setNewFile(prev => ({
...prev,
video_url: searchVideoCached.file?.uri
}));
if (downloadActive) {
cancelDownload(MediaTypes.video, messageId);
}
return; return;
} }
if (downloadActive) return setLoading(true);
// We don't pass the author to avoid auto-download what the user sent // We don't pass the author to avoid auto-download what the user sent
const autoDownload = await isAutoDownloadEnabled('imagesPreferenceDownload', { user }); const autoDownload = await isAutoDownloadEnabled('imagesPreferenceDownload', { user });
if (autoDownload && !downloadResumable.current) { if (autoDownload) {
await handleDownload(); await handleDownload();
} }
} }
@ -110,44 +122,31 @@ const Video = React.memo(
return null; return null;
} }
const handleDownloadResumableSnapshot = () => {
if (video) {
const result = userPreferences.getString(downloadResumableKey(video));
if (result) {
const snapshot = JSON.parse(result);
downloadResumable.current = new FileSystem.DownloadResumable(
video,
filePath.current,
{},
() => {},
snapshot.resumeData
);
setLoading(true);
}
}
};
const handleDownload = async () => { const handleDownload = async () => {
setLoading(true); setLoading(true);
downloadResumable.current = FileSystem.createDownloadResumable(video, filePath.current);
userPreferences.setString(downloadResumableKey(video), JSON.stringify(downloadResumable.current.savable()));
const videoUri = await downloadMediaFile({ const videoUri = await downloadMediaFile({
url: video, downloadUrl: video,
filePath: filePath.current, mediaType: MediaTypes.video,
downloadResumable: downloadResumable.current messageId,
path: filePath.current
}); });
userPreferences.removeItem(downloadResumableKey(video)); console.log('🚀 ~ file: Video.tsx:137 ~ handleDownload ~ videoUri:', videoUri);
if (videoUri) { if (videoUri) {
file.video_url = videoUri; setNewFile(prev => ({
...prev,
video_url: videoUri
}));
} }
setLoading(false); setLoading(false);
}; };
const onPress = async () => { const onPress = async () => {
if (file.video_type && isTypeSupported(file.video_type) && showAttachment) { if (file.video_type && isTypeSupported(file.video_type) && showAttachment) {
if (!newFile.video_url?.startsWith(LOCAL_DOCUMENT_PATH) && !loading) {
// Keep the video downloading while showing the video buffering // Keep the video downloading while showing the video buffering
handleDownload(); handleDownload();
return showAttachment(file); }
return showAttachment(newFile);
} }
if (!isIOS && file.video_url) { if (!isIOS && file.video_url) {
@ -158,9 +157,8 @@ const Video = React.memo(
}; };
const handleCancelDownload = () => { const handleCancelDownload = () => {
if (loading && downloadResumable.current) { if (loading) {
downloadResumable.current.cancelAsync(); cancelDownload(MediaTypes.video, messageId);
userPreferences.removeItem(downloadResumableKey(video));
return setLoading(false); return setLoading(false);
} }
}; };

View File

@ -16,7 +16,6 @@ import { TActionSortPreferences } from '../../actions/sortPreferences';
import { TActionUserTyping } from '../../actions/usersTyping'; import { TActionUserTyping } from '../../actions/usersTyping';
import { TActionPermissions } from '../../actions/permissions'; import { TActionPermissions } from '../../actions/permissions';
import { TActionEnterpriseModules } from '../../actions/enterpriseModules'; import { TActionEnterpriseModules } from '../../actions/enterpriseModules';
import { TActionMediaDownload } from '../../actions/mediaDownload';
// REDUCERS // REDUCERS
import { IActiveUsers } from '../../reducers/activeUsers'; import { IActiveUsers } from '../../reducers/activeUsers';
import { IApp } from '../../reducers/app'; import { IApp } from '../../reducers/app';
@ -35,7 +34,6 @@ import { IShare } from '../../reducers/share';
import { IInquiry } from '../../ee/omnichannel/reducers/inquiry'; import { IInquiry } from '../../ee/omnichannel/reducers/inquiry';
import { IPermissionsState } from '../../reducers/permissions'; import { IPermissionsState } from '../../reducers/permissions';
import { IEnterpriseModules } from '../../reducers/enterpriseModules'; import { IEnterpriseModules } from '../../reducers/enterpriseModules';
import { IDownloads } from '../../reducers/mediaDownload';
export interface IApplicationState { export interface IApplicationState {
settings: TSettingsState; settings: TSettingsState;
@ -59,7 +57,6 @@ export interface IApplicationState {
encryption: IEncryption; encryption: IEncryption;
permissions: IPermissionsState; permissions: IPermissionsState;
roles: IRoles; roles: IRoles;
mediaDownload: IDownloads;
} }
export type TApplicationActions = TActionActiveUsers & export type TApplicationActions = TActionActiveUsers &
@ -78,5 +75,4 @@ export type TApplicationActions = TActionActiveUsers &
TActionApp & TActionApp &
TActionInquiry & TActionInquiry &
TActionPermissions & TActionPermissions &
TActionEnterpriseModules & TActionEnterpriseModules;
TActionMediaDownload;

View File

@ -1,5 +1,7 @@
import * as FileSystem from 'expo-file-system'; import * as FileSystem from 'expo-file-system';
import * as mime from 'react-native-mime-types'; import * as mime from 'react-native-mime-types';
import { isEmpty } from 'lodash';
import RNFetchBlob, { FetchBlobResponse, StatefulPromise } from 'rn-fetch-blob';
import { sanitizeLikeString } from '../database/utils'; import { sanitizeLikeString } from '../database/utils';
import { store } from '../store/auxStore'; import { store } from '../store/auxStore';
@ -22,8 +24,70 @@ const defaultType = {
[MediaTypes.video]: 'mp4' [MediaTypes.video]: 'mp4'
}; };
const downloadQueue: { [index: string]: StatefulPromise<FetchBlobResponse> } = {};
export const mediaDownloadKey = (mediaType: MediaTypes, messageId: string) => `${mediaType}-${messageId}`; export const mediaDownloadKey = (mediaType: MediaTypes, messageId: string) => `${mediaType}-${messageId}`;
export function isDownloadActive(mediaType: MediaTypes, messageId: string): boolean {
return !!downloadQueue[mediaDownloadKey(mediaType, messageId)];
}
export async function cancelDownload(mediaType: MediaTypes, messageId: string): Promise<void> {
const downloadKey = mediaDownloadKey(mediaType, messageId);
if (!isEmpty(downloadQueue[downloadKey])) {
console.log('🚀 ~ file: handleMediaDownload.ts:38 ~ cancelDownload ~ downloadQueue:', downloadQueue);
try {
await downloadQueue[downloadKey].cancel();
} catch {
// Do nothing
}
delete downloadQueue[downloadKey];
console.log('🚀 ~ file: handleMediaDownload.ts:47 ~ cancelDownload ~ downloadQueue:', downloadQueue);
}
}
export function downloadMediaFile({
mediaType,
messageId,
downloadUrl,
path
}: {
mediaType: MediaTypes;
messageId: string;
downloadUrl: string;
path: string;
}): Promise<string | null> {
return new Promise((resolve, reject) => {
const downloadKey = mediaDownloadKey(mediaType, messageId);
const options = {
timeout: 10000,
indicator: true,
overwrite: true,
path: path.replace('file://', '')
};
downloadQueue[downloadKey] = RNFetchBlob.config(options).fetch('GET', downloadUrl);
downloadQueue[downloadKey].then(response => {
if (response.respInfo.status >= 200 && response.respInfo.status < 400) {
// If response is all good...
try {
console.log('🚀 ~ file: handleMediaDownload.ts:71 ~ returnnewPromise ~ response:', response, response.path());
resolve(response.data);
delete downloadQueue[downloadKey];
} catch (e) {
log(e);
}
} else {
reject(null);
}
});
downloadQueue[downloadKey].catch(error => {
console.log('🚀 ~ file: handleMediaDownload.ts:82 ~ returnnewPromise ~ error:', error);
delete downloadQueue[downloadKey];
reject(null);
});
});
}
export const LOCAL_DOCUMENT_PATH = `${FileSystem.documentDirectory}`; export const LOCAL_DOCUMENT_PATH = `${FileSystem.documentDirectory}`;
const sanitizeString = (value: string) => sanitizeLikeString(value.substring(value.lastIndexOf('/') + 1)); const sanitizeString = (value: string) => sanitizeLikeString(value.substring(value.lastIndexOf('/') + 1));
@ -72,30 +136,6 @@ export const searchMediaFileAsync = async ({
return { file, filePath }; return { file, filePath };
}; };
export const downloadMediaFile = async ({
url,
filePath,
downloadResumable
}: {
url: string;
filePath: string;
downloadResumable?: FileSystem.DownloadResumable;
}) => {
let uri = '';
try {
if (downloadResumable) {
const downloadFile = await downloadResumable.downloadAsync();
uri = downloadFile?.uri || '';
} else {
const downloadedFile = await FileSystem.downloadAsync(url, filePath);
uri = downloadedFile.uri;
}
} catch (error) {
log(error);
}
return uri;
};
export const deleteAllSpecificMediaFiles = async (type: MediaTypes, serverUrl: string): Promise<void> => { export const deleteAllSpecificMediaFiles = async (type: MediaTypes, serverUrl: string): Promise<void> => {
try { try {
const serverUrlParsed = sanitizeString(serverUrl); const serverUrlParsed = sanitizeString(serverUrl);

View File

@ -1,7 +1,7 @@
import * as FileSystem from 'expo-file-system'; import { LOCAL_DOCUMENT_PATH } from '../handleMediaDownload';
export const formatAttachmentUrl = (attachmentUrl: string | undefined, userId: string, token: string, server: string): string => { export const formatAttachmentUrl = (attachmentUrl: string | undefined, userId: string, token: string, server: string): string => {
if (attachmentUrl?.startsWith(`${FileSystem.documentDirectory}`)) { if (attachmentUrl?.startsWith(LOCAL_DOCUMENT_PATH)) {
return attachmentUrl; return attachmentUrl;
} }
if (attachmentUrl && attachmentUrl.startsWith('http')) { if (attachmentUrl && attachmentUrl.startsWith('http')) {

View File

@ -21,7 +21,6 @@ import enterpriseModules from './enterpriseModules';
import encryption from './encryption'; import encryption from './encryption';
import permissions from './permissions'; import permissions from './permissions';
import roles from './roles'; import roles from './roles';
import mediaDownload from './mediaDownload';
export default combineReducers({ export default combineReducers({
settings, settings,
@ -44,6 +43,5 @@ export default combineReducers({
enterpriseModules, enterpriseModules,
encryption, encryption,
permissions, permissions,
roles, roles
mediaDownload
}); });

View File

@ -1,41 +0,0 @@
import { DownloadResumable } from 'expo-file-system';
import { mediaDownloadInProgress, mediaDownloadRemove } from '../actions/mediaDownload';
import { IDownloads, initialState } from './mediaDownload';
import { mockedStore } from './mockedStore';
import { MediaTypes } from '../lib/methods/handleMediaDownload';
describe('test reducer', () => {
const downloadResumable = 'downloadResumable' as unknown as DownloadResumable;
const downloadResumableTwo = 'downloadResumableTwo' as unknown as DownloadResumable;
it('should return initial state', () => {
const state = mockedStore.getState().mediaDownload;
expect(state).toEqual(initialState);
});
it('should return modified store after action', () => {
const expectState: IDownloads = { [`${MediaTypes.video}-id`]: downloadResumable };
mockedStore.dispatch(mediaDownloadInProgress({ mediaType: MediaTypes.video, messageId: 'id', downloadResumable }));
const state = mockedStore.getState().mediaDownload;
expect(state).toEqual({ ...expectState });
});
it('should return the state correct after add second download', () => {
mockedStore.dispatch(
mediaDownloadInProgress({ mediaType: MediaTypes.audio, messageId: 'id', downloadResumable: downloadResumableTwo })
);
const expectState = {
[`${MediaTypes.video}-id`]: downloadResumable,
[`${MediaTypes.audio}-id`]: downloadResumableTwo
};
const state = mockedStore.getState().mediaDownload;
expect(state).toEqual({ ...expectState });
});
it('should remove one download', () => {
mockedStore.dispatch(mediaDownloadRemove({ mediaType: MediaTypes.video, messageId: 'id' }));
const expectState = {
[`${MediaTypes.audio}-id`]: downloadResumableTwo
};
const state = mockedStore.getState().mediaDownload;
expect(state).toEqual({ ...expectState });
});
});

View File

@ -1,26 +0,0 @@
import { DownloadResumable } from 'expo-file-system';
import { MEDIA_DOWNLOAD } from '../actions/actionsTypes';
import { TApplicationActions } from '../definitions';
export interface IDownloads {
[key: string]: DownloadResumable;
}
export const initialState: IDownloads = {};
export default function mediaDownload(state = initialState, action: TApplicationActions): IDownloads {
switch (action.type) {
case MEDIA_DOWNLOAD.IN_PROGRESS:
return {
...state,
[action.key]: action.downloadResumable
};
case MEDIA_DOWNLOAD.REMOVE:
const newState = { ...state };
delete newState[action.key];
return newState;
default:
return state;
}
}

View File

@ -1,26 +0,0 @@
import { createSelector } from 'reselect';
import { IApplicationState } from '../definitions';
import { MediaTypes, mediaDownloadKey } from '../lib/methods/handleMediaDownload';
import { IDownloads } from '../reducers/mediaDownload';
const selectMediaDownload = (state: IApplicationState) => state.mediaDownload;
const getMediaDownload = (mediaDownload: IDownloads, { mediaType, messageId }: { mediaType: MediaTypes; messageId: string }) => {
console.log('🚀 ~ file: mediaDownload.ts:10 ~ getMediaDownload ~ { mediaType, messageId }:', { mediaType, messageId });
console.log('🚀 ~ file: mediaDownload.ts:10 ~ getMediaDownload ~ mediaDownload:', mediaDownload);
const key = mediaDownloadKey(mediaType, messageId);
if (mediaDownload[key]) return mediaDownload[key];
return null;
};
export const getDownloadResumable = createSelector(
[
selectMediaDownload,
(_state: IApplicationState, { mediaType, messageId }: { mediaType: MediaTypes; messageId: string }) => ({
mediaType,
messageId
})
],
getMediaDownload
);