forked from Ivasoft/mattermost-mobile
191 lines
5.8 KiB
TypeScript
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,
|
|
};
|