Major improvements to upload progress.

This commit is contained in:
Patrick Fic
2025-10-31 08:18:32 -07:00
parent 8e63ef0d6d
commit ab8703a524
7 changed files with 185 additions and 61 deletions

View File

@@ -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)
]);
}