Major improvements to upload progress.
This commit is contained in:
@@ -53,3 +53,17 @@ export const clearUploadError = () => ({
|
||||
type: PhotosActionTypes.CLEAR_UPLOAD_ERROR,
|
||||
});
|
||||
|
||||
|
||||
export const addUploadCancelTask = (payload) => ({
|
||||
type: PhotosActionTypes.ADD_UPLOAD_CANCEL_TASK,
|
||||
payload,
|
||||
});
|
||||
|
||||
export const removeUploadCancelTask = (payload) => ({
|
||||
type: PhotosActionTypes.REMOVE_UPLOAD_CANCEL_TASK,
|
||||
payload,
|
||||
});
|
||||
|
||||
export const cancelUploads = () => ({
|
||||
type: PhotosActionTypes.CANCEL_UPLOADS,
|
||||
});
|
||||
@@ -5,7 +5,9 @@ const INITIAL_STATE = {
|
||||
uploadInProgress: true,
|
||||
uploadError: null,
|
||||
jobid: null,
|
||||
progress: {}
|
||||
progress: {},
|
||||
cancelTasks: {},
|
||||
cancelTriggered: false,
|
||||
};
|
||||
|
||||
const photosReducer = (state = INITIAL_STATE, action) => {
|
||||
@@ -17,7 +19,9 @@ const photosReducer = (state = INITIAL_STATE, action) => {
|
||||
jobid: action.payload.jobid,
|
||||
uploadInProgress: true,
|
||||
uploadError: null,
|
||||
progress: action.payload.progress || {}
|
||||
progress: action.payload.progress || {},
|
||||
cancelTasks: {},
|
||||
cancelTriggered: false,
|
||||
};
|
||||
case PhotosActionTypes.MEDIA_UPLOAD_FAILURE:
|
||||
return {
|
||||
@@ -31,9 +35,12 @@ const photosReducer = (state = INITIAL_STATE, action) => {
|
||||
progress: { ...state.progress, [action.payload.assetId]: { ...state.progress[action.payload.assetId], ...action.payload } }
|
||||
};
|
||||
case PhotosActionTypes.MEDIA_UPLOAD_SUCCESS_ONE:
|
||||
const { [action.payload.assetId]: _, ...remainingTasks } = state.cancelTasks;
|
||||
|
||||
return {
|
||||
...state,
|
||||
progress: { ...state.progress, [action.payload.assetId]: { ...state.progress[action.payload.assetId], progress: 100, status: 'completed', endTime: new Date() } }
|
||||
progress: { ...state.progress, [action.payload.assetId]: { ...state.progress[action.payload.assetId], progress: 100, status: 'completed', endTime: new Date() } },
|
||||
cancelTasks: remainingTasks
|
||||
};
|
||||
case PhotosActionTypes.MEDIA_UPLOAD_PROGRESS_UPDATE_BULK:
|
||||
return {
|
||||
@@ -46,13 +53,35 @@ const photosReducer = (state = INITIAL_STATE, action) => {
|
||||
uploadInProgress: false,
|
||||
uploadError: null,
|
||||
photos: [],
|
||||
progress: {}
|
||||
progress: {},
|
||||
cancelTasks: {}
|
||||
};
|
||||
case PhotosActionTypes.CLEAR_UPLOAD_ERROR:
|
||||
return {
|
||||
...state,
|
||||
photos: [], progress: {},
|
||||
photos: [],
|
||||
progress: {},
|
||||
uploadError: null,
|
||||
cancelTasks: {},
|
||||
};
|
||||
case PhotosActionTypes.ADD_UPLOAD_CANCEL_TASK:
|
||||
return {
|
||||
...state,
|
||||
cancelTasks: {
|
||||
...state.cancelTasks,
|
||||
[action.payload.assetId]: action.payload.cancelTask,
|
||||
},
|
||||
};
|
||||
case PhotosActionTypes.REMOVE_UPLOAD_CANCEL_TASK:
|
||||
const { [action.payload.assetId]: _2, ...remainingTasks2 } = state.cancelTasks; //2 added for scoped variable conflict.
|
||||
return {
|
||||
...state,
|
||||
cancelTasks: remainingTasks2,
|
||||
};
|
||||
case PhotosActionTypes.CANCEL_UPLOADS:
|
||||
return {
|
||||
...state,
|
||||
cancelTriggered: true,
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import axios from "axios";
|
||||
import Constants from "expo-constants";
|
||||
import * as FileSystem from "expo-file-system/legacy";
|
||||
import * as ImagePicker from "expo-image-picker";
|
||||
import * as MediaLibrary from "expo-media-library";
|
||||
import _ from 'lodash';
|
||||
@@ -15,6 +16,7 @@ import { selectDeleteAfterUpload } from "../app/app.selectors";
|
||||
import { store } from "../store";
|
||||
import { selectBodyshop, selectCurrentUser } from "../user/user.selectors";
|
||||
import {
|
||||
addUploadCancelTask,
|
||||
deleteMediaSuccess,
|
||||
mediaUploadCompleted,
|
||||
mediaUploadFailure,
|
||||
@@ -84,7 +86,7 @@ export function* openImagePickerAction({ payload: jobid }) {
|
||||
yield put(mediaUploadStart({ photos: result.assets, jobid, progress: _.keyBy(result.assets, 'assetId') }));
|
||||
}
|
||||
} catch (error) {
|
||||
// console.log("Saga Error: open Picker", error);
|
||||
console.log("Saga Error: open Picker", error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,13 +121,17 @@ export function* mediaUploadStartAction({ payload: { photos, jobid } }) {
|
||||
}
|
||||
// Process each batch sequentially, but photos within batch concurrently
|
||||
for (const batch of batches) {
|
||||
const uploadTasks = batch.map((photo, index) =>
|
||||
call(uploadSinglePhoto, photo, bodyshop, index, jobid)
|
||||
const isCancelTriggered = yield select((state) => state.photos.cancelTriggered
|
||||
);
|
||||
// Wait for current batch to complete before starting next batch
|
||||
yield all(uploadTasks);
|
||||
// Small delay between batches to prevent overwhelming the server
|
||||
yield delay(100);
|
||||
if (!isCancelTriggered) {
|
||||
const uploadTasks = batch.map((photo, index) =>
|
||||
call(uploadSinglePhoto, photo, bodyshop, index, jobid)
|
||||
);
|
||||
// Wait for current batch to complete before starting next batch
|
||||
yield all(uploadTasks);
|
||||
// Small delay between batches to prevent overwhelming the server
|
||||
yield delay(100);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -260,39 +266,53 @@ function* uploadToImageProxy(photo, photoBlob, extension, key, bodyshop, jobid)
|
||||
|
||||
let uploadResult
|
||||
try {
|
||||
uploadResult = yield new Promise((resolve, reject) => {
|
||||
|
||||
console.log("Starting XHR")
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.upload.onprogress = (e) => {
|
||||
console.log("Upload Progress:", e.loaded, e.total);
|
||||
store.dispatch({ ...photo, progress: e.loaded / e.total, loaded: e.loaded });
|
||||
put(mediaUploadProgressOne({ ...photo, progress: e.loaded / e.total, loaded: e.loaded }));
|
||||
// const s3PutResponse = yield call(cleanAxios.put,
|
||||
// preSignedUploadUrlToS3,
|
||||
// photoBlob,
|
||||
// {
|
||||
// headers: {
|
||||
// "Content-Type": photoBlob.type
|
||||
// },
|
||||
// onUploadProgress: (e) => {
|
||||
// const progress = e.loaded / e.total;
|
||||
// console.log("Event Progress", e)
|
||||
// put(mediaUploadProgressOne({ ...photo, progress, loaded: e.loaded }));
|
||||
// },
|
||||
// }
|
||||
// );
|
||||
|
||||
const task = FileSystem.createUploadTask(
|
||||
preSignedUploadUrlToS3,
|
||||
photo.uri,
|
||||
{
|
||||
//fieldName: FIELD_NAME_OF_THE_FILE_IN_REQUEST,
|
||||
httpMethod: "PUT",
|
||||
uploadType: FileSystem.FileSystemUploadType.BINARY_CONTENT,
|
||||
mimeType: photoBlob.type,
|
||||
headers: {},
|
||||
//parameters: {...OTHER PARAMS IN REQUEST},
|
||||
},
|
||||
(progressData) => {
|
||||
const sent = progressData.totalBytesSent;
|
||||
const total = progressData.totalBytesExpectedToSend;
|
||||
const progress = sent / total;
|
||||
console.log(progress, sent)
|
||||
store.dispatch(mediaUploadProgressOne({ ...photo, progress, loaded: sent }));
|
||||
// onUpload(Number(progress.toFixed(2)) * 100);
|
||||
},
|
||||
);
|
||||
|
||||
yield put(addUploadCancelTask({ assetId: photo.assetId, cancelTask: task.cancelAsync }));
|
||||
uploadResult = yield task.uploadAsync();
|
||||
|
||||
};
|
||||
xhr.open("PUT", preSignedUploadUrlToS3);
|
||||
xhr.setRequestHeader("Content-Type", photoBlob.type);
|
||||
xhr.onload = () => {
|
||||
if (xhr.status === 200) {
|
||||
console.log("XHR Done. Resolve promise.")
|
||||
resolve(true);
|
||||
} else {
|
||||
reject(new Error(`Upload failed: ${xhr.statusText}`));
|
||||
}
|
||||
};
|
||||
xhr.onerror = (req, event) => {
|
||||
reject(new Error("Network error"));
|
||||
};
|
||||
console.log("Sending XHR")
|
||||
xhr.send(photoBlob);
|
||||
|
||||
});
|
||||
} catch (error) {
|
||||
console.log("Error uploading to S3", error.message, error.stack);
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (uploadResult) {
|
||||
if (uploadResult.status === 200) {
|
||||
//Create doc record.
|
||||
const uploaded_by = yield select(selectCurrentUser);
|
||||
|
||||
@@ -328,26 +348,34 @@ function* uploadToImageProxy(photo, photoBlob, extension, key, bodyshop, jobid)
|
||||
},
|
||||
}));
|
||||
console.log("Upload and record creation successful for", photo.uri);
|
||||
} else {
|
||||
console.log("Error uploading to Cloud", uploadResult);
|
||||
throw new Error(`Cloud upload failed: ${uploadResult}`);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.log("Error uploading to image proxy", JSON.stringify(error));
|
||||
throw new Error(`Image proxy upload failed: ${error.message}`);
|
||||
console.log("Error uploading to Cloud", JSON.stringify(error));
|
||||
throw new Error(`Cloud upload failed: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle cancellation of uploads
|
||||
function* onCancelUpload() {
|
||||
yield takeEvery(PhotosActionTypes.CANCEL_UPLOAD, cancelUploadAction);
|
||||
yield takeEvery(PhotosActionTypes.CANCEL_UPLOADS, cancelUploadAction);
|
||||
}
|
||||
|
||||
function* cancelUploadAction({ payload: photoId }) {
|
||||
// const task = uploadTasks.get(photoId);
|
||||
// if (task) {
|
||||
// yield cancel(task);
|
||||
// uploadTasks.delete(photoId);
|
||||
// yield put(mediaUploadFailure({ photoId, error: 'Upload cancelled' }));
|
||||
// }
|
||||
function* cancelUploadAction() {
|
||||
const cancelTasks = yield select((state) => state.photos.cancelTasks);
|
||||
try {
|
||||
const tasksToCancel = Object.values(cancelTasks);
|
||||
for (const cancelTask of tasksToCancel) {
|
||||
console.log("*** ~ cancelUploadAction ~ cancelTask:", cancelTask);
|
||||
cancelTask();
|
||||
}
|
||||
yield put({ type: PhotosActionTypes.CLEAR_UPLOAD_ERROR });
|
||||
} catch (error) {
|
||||
console.log("Error cancelling upload", error);
|
||||
}
|
||||
}
|
||||
|
||||
function* onMediaUploadCompleted() {
|
||||
@@ -414,7 +442,7 @@ export function* photosSagas() {
|
||||
call(onOpenImagePicker),
|
||||
call(onMediaUploadStart),
|
||||
call(onMediaUploadCompleted),
|
||||
call(onMediaUploadFailure)
|
||||
//call(onCancelUpload)
|
||||
call(onMediaUploadFailure),
|
||||
call(onCancelUpload)
|
||||
]);
|
||||
}
|
||||
@@ -10,5 +10,8 @@ const PhotosActionTypes = {
|
||||
DELETE_MEDIA_SUCCESS: "DELETE_MEDIA_SUCCESS",
|
||||
DELETE_MEDIA_FAILURE: "DELETE_MEDIA_FAILURE",
|
||||
CLEAR_UPLOAD_ERROR: "CLEAR_UPLOAD_ERROR",
|
||||
ADD_UPLOAD_CANCEL_TASK: "ADD_UPLOAD_CANCEL_TASK",
|
||||
REMOVE_UPLOAD_CANCEL_TASK: "REMOVE_UPLOAD_CANCEL_TASK",
|
||||
CANCEL_UPLOADS: "CANCEL_UPLOADS",
|
||||
};
|
||||
export default PhotosActionTypes;
|
||||
|
||||
Reference in New Issue
Block a user