Files
mattermost-mobile/app/init/draft_upload_manager/index.ts
Daniel Espino García 7e4b8b4dd9 [Gekidou] [MM-41524] Add tests to draft Upload Manager (#5990)
* Add tests to draft Upload Manager

* Address feedback
2022-03-11 14:00:09 -03:00

191 lines
5.8 KiB
TypeScript

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {ClientResponse, ClientResponseError} from '@mattermost/react-native-network-client';
import {AppState, AppStateStatus} from 'react-native';
import {updateDraftFile} from '@actions/local/draft';
import {uploadFile} from '@actions/remote/file';
import {PROGRESS_TIME_TO_STORE} from '@constants/files';
type FileHandler = {
[clientId: string]: {
cancel?: () => void;
fileInfo: FileInfo;
serverUrl: string;
channelId: string;
rootId: string;
lastTimeStored: number;
onError: Array<(msg: string) => void>;
onProgress: Array<(p: number, b: number) => void>;
};
}
class DraftUploadManager {
private handlers: FileHandler = {};
private previousAppState: AppStateStatus;
constructor() {
this.previousAppState = AppState.currentState;
AppState.addEventListener('change', this.onAppStateChange);
}
public prepareUpload = (
serverUrl: string,
file: FileInfo,
channelId: string,
rootId: string,
skipBytes = 0,
) => {
this.handlers[file.clientId!] = {
fileInfo: file,
serverUrl,
channelId,
rootId,
lastTimeStored: 0,
onError: [],
onProgress: [],
};
const onProgress = (progress: number, bytesRead?: number | null | undefined) => {
this.handleProgress(file.clientId!, progress, bytesRead || 0);
};
const onComplete = (response: ClientResponse) => {
this.handleComplete(response, file.clientId!);
};
const onError = (response: ClientResponseError) => {
const message = response.message || 'Unkown error';
this.handleError(message, file.clientId!);
};
const {error, cancel} = uploadFile(serverUrl, file, channelId, onProgress, onComplete, onError, skipBytes);
if (error) {
this.handleError(error.message, file.clientId!);
return;
}
this.handlers[file.clientId!].cancel = cancel;
};
public cancel = (clientId: string) => {
const h = this.handlers[clientId];
delete this.handlers[clientId];
h?.cancel?.();
};
public isUploading = (clientId: string) => {
return Boolean(this.handlers[clientId]);
};
public registerProgressHandler = (clientId: string, callback: (progress: number, bytes: number) => void) => {
if (!this.handlers[clientId]) {
return null;
}
this.handlers[clientId].onProgress.push(callback);
return () => {
if (!this.handlers[clientId]) {
return;
}
this.handlers[clientId].onProgress = this.handlers[clientId].onProgress.filter((v) => v !== callback);
};
};
public registerErrorHandler = (clientId: string, callback: (errMessage: string) => void) => {
if (!this.handlers[clientId]) {
return null;
}
this.handlers[clientId].onError.push(callback);
return () => {
if (!this.handlers[clientId]) {
return;
}
this.handlers[clientId].onError = this.handlers[clientId].onError.filter((v) => v !== callback);
};
};
private handleProgress = (clientId: string, progress: number, bytes: number) => {
const h = this.handlers[clientId];
if (!h) {
return;
}
h.fileInfo.bytesRead = bytes;
h.onProgress.forEach((c) => c(progress, bytes));
if (AppState.currentState !== 'active' && h.lastTimeStored + PROGRESS_TIME_TO_STORE < Date.now()) {
updateDraftFile(h.serverUrl, h.channelId, h.rootId, this.handlers[clientId].fileInfo);
h.lastTimeStored = Date.now();
}
};
private handleComplete = (response: ClientResponse, clientId: string) => {
const h = this.handlers[clientId];
if (!h) {
return;
}
if (response.code !== 201) {
this.handleError((response.data as any).message, clientId);
return;
}
if (!response.data) {
this.handleError('Failed to upload the file: no data received', clientId);
return;
}
const data = response.data.file_infos as FileInfo[];
if (!data || !data.length) {
this.handleError('Failed to upload the file: no data received', clientId);
return;
}
delete this.handlers[clientId];
const fileInfo = data[0];
fileInfo.clientId = h.fileInfo.clientId;
fileInfo.localPath = h.fileInfo.localPath;
updateDraftFile(h.serverUrl, h.channelId, h.rootId, fileInfo);
};
private handleError = (errorMessage: string, clientId: string) => {
const h = this.handlers[clientId];
if (!h) {
return;
}
delete this.handlers[clientId];
h.onError.forEach((c) => c(errorMessage));
const fileInfo = {...h.fileInfo};
fileInfo.failed = true;
updateDraftFile(h.serverUrl, h.channelId, h.rootId, fileInfo);
};
private onAppStateChange = async (appState: AppStateStatus) => {
if (appState !== 'active' && this.previousAppState === 'active') {
await this.storeProgress();
}
this.previousAppState = appState;
};
private storeProgress = async () => {
for (const h of Object.values(this.handlers)) {
// eslint-disable-next-line no-await-in-loop
await updateDraftFile(h.serverUrl, h.channelId, h.rootId, h.fileInfo);
h.lastTimeStored = Date.now();
}
};
}
export default new DraftUploadManager();
export const exportedForTesting = {
DraftUploadManager,
};