mirror of
https://github.com/usatiuk/photos.git
synced 2025-10-28 15:27:49 +01:00
upload photos in parallel (not all at once)
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
import { Position, Toaster } from "@blueprintjs/core";
|
import { Position, Toaster } from "@blueprintjs/core";
|
||||||
|
import { isNumber } from "class-validator";
|
||||||
import { IPhotoReqJSON } from "~../../src/entity/Photo";
|
import { IPhotoReqJSON } from "~../../src/entity/Photo";
|
||||||
|
|
||||||
export const AppToaster = Toaster.create({
|
export const AppToaster = Toaster.create({
|
||||||
@@ -43,11 +44,12 @@ export function showPhotoCreateFailToast(f: File, e: string): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function showPhotoUploadJSONFailToast(
|
export function showPhotoUploadJSONFailToast(
|
||||||
p: IPhotoReqJSON,
|
p: IPhotoReqJSON | number,
|
||||||
e: string,
|
e: string,
|
||||||
): void {
|
): void {
|
||||||
|
const photoMsg = typeof p === "number" ? p : p.hash;
|
||||||
AppToaster.show({
|
AppToaster.show({
|
||||||
message: `Failed to upload ${p.hash}: ${e}`,
|
message: `Failed to upload ${photoMsg}: ${e}`,
|
||||||
intent: "danger",
|
intent: "danger",
|
||||||
timeout: 1000,
|
timeout: 1000,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -14,8 +14,12 @@ export enum PhotoTypes {
|
|||||||
PHOTO_LOAD_SUCCESS = "PHOTO_LOAD_SUCCESS",
|
PHOTO_LOAD_SUCCESS = "PHOTO_LOAD_SUCCESS",
|
||||||
PHOTO_LOAD_FAIL = "PHOTO_LOAD_FAIL",
|
PHOTO_LOAD_FAIL = "PHOTO_LOAD_FAIL",
|
||||||
PHOTOS_UPLOAD_START = "PHOTOS_UPLOAD",
|
PHOTOS_UPLOAD_START = "PHOTOS_UPLOAD",
|
||||||
|
PHOTO_CREATE_QUEUE = "PHOTO_CREATE_QUEUE",
|
||||||
|
PHOTO_CREATE_START = "PHOTO_CREATE_START",
|
||||||
PHOTO_CREATE_SUCCESS = "PHOTO_CREATE_SUCCESS",
|
PHOTO_CREATE_SUCCESS = "PHOTO_CREATE_SUCCESS",
|
||||||
PHOTO_CREATE_FAIL = "PHOTO_CREATE_FAIL",
|
PHOTO_CREATE_FAIL = "PHOTO_CREATE_FAIL",
|
||||||
|
PHOTO_UPLOAD_QUEUE = "PHOTO_UPLOAD_QUEUE",
|
||||||
|
PHOTO_UPLOAD_START = "PHOTO_UPLOAD_START",
|
||||||
PHOTO_UPLOAD_SUCCESS = "PHOTO_UPLOAD_SUCCESS",
|
PHOTO_UPLOAD_SUCCESS = "PHOTO_UPLOAD_SUCCESS",
|
||||||
PHOTO_UPLOAD_FAIL = "PHOTO_UPLOAD_FAIL",
|
PHOTO_UPLOAD_FAIL = "PHOTO_UPLOAD_FAIL",
|
||||||
PHOTOS_START_FETCHING_SPINNER = "PHOTOS_START_FETCHING_SPINNER",
|
PHOTOS_START_FETCHING_SPINNER = "PHOTOS_START_FETCHING_SPINNER",
|
||||||
@@ -60,6 +64,28 @@ export interface IPhotosUploadStartAction extends Action {
|
|||||||
files: FileList;
|
files: FileList;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IPhotoCreateQueue extends Action {
|
||||||
|
type: PhotoTypes.PHOTO_CREATE_QUEUE;
|
||||||
|
file: File;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IPhotoUploadQueue extends Action {
|
||||||
|
type: PhotoTypes.PHOTO_UPLOAD_QUEUE;
|
||||||
|
file: File;
|
||||||
|
id: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IPhotoCreateStart extends Action {
|
||||||
|
type: PhotoTypes.PHOTO_CREATE_START;
|
||||||
|
file: File;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IPhotoUploadStart extends Action {
|
||||||
|
type: PhotoTypes.PHOTO_UPLOAD_START;
|
||||||
|
file: File;
|
||||||
|
id: number;
|
||||||
|
}
|
||||||
|
|
||||||
export interface IPhotoUploadSuccessAction extends Action {
|
export interface IPhotoUploadSuccessAction extends Action {
|
||||||
type: PhotoTypes.PHOTO_UPLOAD_SUCCESS;
|
type: PhotoTypes.PHOTO_UPLOAD_SUCCESS;
|
||||||
photo: IPhotoReqJSON;
|
photo: IPhotoReqJSON;
|
||||||
@@ -67,13 +93,14 @@ export interface IPhotoUploadSuccessAction extends Action {
|
|||||||
|
|
||||||
export interface IPhotoUploadFailAction extends Action {
|
export interface IPhotoUploadFailAction extends Action {
|
||||||
type: PhotoTypes.PHOTO_UPLOAD_FAIL;
|
type: PhotoTypes.PHOTO_UPLOAD_FAIL;
|
||||||
photo: IPhotoReqJSON;
|
photo: IPhotoReqJSON | number;
|
||||||
error: string;
|
error: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IPhotoCreateSuccessAction extends Action {
|
export interface IPhotoCreateSuccessAction extends Action {
|
||||||
type: PhotoTypes.PHOTO_CREATE_SUCCESS;
|
type: PhotoTypes.PHOTO_CREATE_SUCCESS;
|
||||||
photo: IPhotoReqJSON;
|
photo: IPhotoReqJSON;
|
||||||
|
file: File;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IPhotoCreateFailAction extends Action {
|
export interface IPhotoCreateFailAction extends Action {
|
||||||
@@ -107,6 +134,22 @@ export interface IPhotosStartFetchingSpinner extends Action {
|
|||||||
type: PhotoTypes.PHOTOS_START_FETCHING_SPINNER;
|
type: PhotoTypes.PHOTOS_START_FETCHING_SPINNER;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function photoCreateQueue(file: File): IPhotoCreateQueue {
|
||||||
|
return { type: PhotoTypes.PHOTO_CREATE_QUEUE, file };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function photoUploadQueue(file: File, id: number): IPhotoUploadQueue {
|
||||||
|
return { type: PhotoTypes.PHOTO_UPLOAD_QUEUE, file, id };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function photoCreateStart(file: File): IPhotoCreateStart {
|
||||||
|
return { type: PhotoTypes.PHOTO_CREATE_START, file };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function photoUploadStart(file: File, id: number): IPhotoUploadStart {
|
||||||
|
return { type: PhotoTypes.PHOTO_UPLOAD_START, file, id };
|
||||||
|
}
|
||||||
|
|
||||||
export function photosLoadStart(): IPhotosLoadStartAction {
|
export function photosLoadStart(): IPhotosLoadStartAction {
|
||||||
return { type: PhotoTypes.PHOTOS_LOAD_START };
|
return { type: PhotoTypes.PHOTOS_LOAD_START };
|
||||||
}
|
}
|
||||||
@@ -126,7 +169,7 @@ export function photoUploadSuccess(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function photoUploadFail(
|
export function photoUploadFail(
|
||||||
photo: IPhotoReqJSON,
|
photo: IPhotoReqJSON | number,
|
||||||
error: string,
|
error: string,
|
||||||
): IPhotoUploadFailAction {
|
): IPhotoUploadFailAction {
|
||||||
showPhotoUploadJSONFailToast(photo, error);
|
showPhotoUploadJSONFailToast(photo, error);
|
||||||
@@ -134,7 +177,7 @@ export function photoUploadFail(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function photoUploadFailWithFile(
|
export function photoUploadFailWithFile(
|
||||||
photo: IPhotoReqJSON,
|
photo: IPhotoReqJSON | number,
|
||||||
file: File,
|
file: File,
|
||||||
error: string,
|
error: string,
|
||||||
): IPhotoUploadFailAction {
|
): IPhotoUploadFailAction {
|
||||||
@@ -144,8 +187,9 @@ export function photoUploadFailWithFile(
|
|||||||
|
|
||||||
export function photoCreateSuccess(
|
export function photoCreateSuccess(
|
||||||
photo: IPhotoReqJSON,
|
photo: IPhotoReqJSON,
|
||||||
|
file: File,
|
||||||
): IPhotoCreateSuccessAction {
|
): IPhotoCreateSuccessAction {
|
||||||
return { type: PhotoTypes.PHOTO_CREATE_SUCCESS, photo };
|
return { type: PhotoTypes.PHOTO_CREATE_SUCCESS, photo, file };
|
||||||
}
|
}
|
||||||
|
|
||||||
export function photoCreateFail(
|
export function photoCreateFail(
|
||||||
@@ -172,8 +216,8 @@ export function photoLoadSuccess(
|
|||||||
return { type: PhotoTypes.PHOTO_LOAD_SUCCESS, photo };
|
return { type: PhotoTypes.PHOTO_LOAD_SUCCESS, photo };
|
||||||
}
|
}
|
||||||
|
|
||||||
export function photoLoadFail(id:number,error: string): IPhotoLoadFailAction {
|
export function photoLoadFail(id: number, error: string): IPhotoLoadFailAction {
|
||||||
return { type: PhotoTypes.PHOTO_LOAD_FAIL,id, error };
|
return { type: PhotoTypes.PHOTO_LOAD_FAIL, id, error };
|
||||||
}
|
}
|
||||||
|
|
||||||
export function photoDeleteStart(
|
export function photoDeleteStart(
|
||||||
@@ -219,4 +263,8 @@ export type PhotoAction =
|
|||||||
| IPhotoDeleteCancelAction
|
| IPhotoDeleteCancelAction
|
||||||
| IPhotoLoadFailAction
|
| IPhotoLoadFailAction
|
||||||
| IPhotoLoadStartAction
|
| IPhotoLoadStartAction
|
||||||
| IPhotoLoadSuccessAction;
|
| IPhotoLoadSuccessAction
|
||||||
|
| IPhotoUploadQueue
|
||||||
|
| IPhotoCreateQueue
|
||||||
|
| IPhotoCreateStart
|
||||||
|
| IPhotoUploadStart;
|
||||||
|
|||||||
@@ -18,6 +18,11 @@ export interface IPhotosState {
|
|||||||
overviewFetchingError: string | null;
|
overviewFetchingError: string | null;
|
||||||
overviewFetchingSpinner: boolean;
|
overviewFetchingSpinner: boolean;
|
||||||
|
|
||||||
|
photoCreateQueue: File[];
|
||||||
|
photosCreating: number;
|
||||||
|
photoUploadQueue: Record<number, File>;
|
||||||
|
photosUploading: number;
|
||||||
|
|
||||||
deleteCache: Record<number, IPhotoReqJSON>;
|
deleteCache: Record<number, IPhotoReqJSON>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28,6 +33,11 @@ const defaultPhotosState: IPhotosState = {
|
|||||||
overviewFetchingError: null,
|
overviewFetchingError: null,
|
||||||
overviewFetchingSpinner: false,
|
overviewFetchingSpinner: false,
|
||||||
|
|
||||||
|
photoCreateQueue: [],
|
||||||
|
photosCreating: 0,
|
||||||
|
photoUploadQueue: {},
|
||||||
|
photosUploading: 0,
|
||||||
|
|
||||||
photoStates: {},
|
photoStates: {},
|
||||||
|
|
||||||
deleteCache: {},
|
deleteCache: {},
|
||||||
@@ -104,31 +114,102 @@ export const photosReducer: Reducer<IPhotosState, PhotoAction> = (
|
|||||||
photoStates,
|
photoStates,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
case PhotoTypes.PHOTO_CREATE_SUCCESS:
|
case PhotoTypes.PHOTO_CREATE_QUEUE: {
|
||||||
|
const { photoCreateQueue } = state;
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
photoCreateQueue: [...photoCreateQueue, action.file],
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case PhotoTypes.PHOTO_CREATE_START: {
|
||||||
|
const { photoCreateQueue } = state;
|
||||||
|
const cleanQueue = photoCreateQueue.filter((f) => f != action.file);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
photosCreating: state.photosCreating + 1,
|
||||||
|
photoCreateQueue: cleanQueue,
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case PhotoTypes.PHOTO_UPLOAD_START: {
|
||||||
|
const newQueue = state.photoUploadQueue;
|
||||||
|
delete newQueue[action.id];
|
||||||
|
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
photosUploading: state.photosUploading + 1,
|
||||||
|
photoUploadQueue: newQueue,
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case PhotoTypes.PHOTO_UPLOAD_QUEUE: {
|
||||||
|
const newQueue = state.photoUploadQueue;
|
||||||
|
newQueue[action.id] = action.file;
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
photoUploadQueue: newQueue,
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case PhotoTypes.PHOTO_CREATE_SUCCESS: {
|
||||||
|
const { photoCreateQueue } = state;
|
||||||
|
const cleanQueue = photoCreateQueue.filter((f) => f != action.file);
|
||||||
|
|
||||||
if (state.photos) {
|
if (state.photos) {
|
||||||
const photos = state.photos;
|
const photos = state.photos;
|
||||||
const photosNoDup = photos.filter(
|
const photosNoDup = photos.filter(
|
||||||
(p) => p.id !== action.photo.id,
|
(p) => p.id !== action.photo.id,
|
||||||
);
|
);
|
||||||
const updPhotos = [action.photo, ...photosNoDup];
|
const updPhotos = [action.photo, ...photosNoDup];
|
||||||
return { ...state, photos: updPhotos };
|
return {
|
||||||
|
...state,
|
||||||
|
photos: updPhotos,
|
||||||
|
photoCreateQueue: cleanQueue,
|
||||||
|
photosCreating: state.photosCreating - 1,
|
||||||
|
};
|
||||||
} else {
|
} else {
|
||||||
return state;
|
return {
|
||||||
|
...state,
|
||||||
|
photoCreateQueue: cleanQueue,
|
||||||
|
photosCreating: state.photosCreating - 1,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
case PhotoTypes.PHOTO_CREATE_FAIL:
|
}
|
||||||
|
case PhotoTypes.PHOTO_CREATE_FAIL: {
|
||||||
// TODO: Handle photo create fail
|
// TODO: Handle photo create fail
|
||||||
return state;
|
const { photoCreateQueue } = state;
|
||||||
case PhotoTypes.PHOTO_UPLOAD_SUCCESS:
|
const cleanQueue = photoCreateQueue.filter((f) => f != action.file);
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
photoCreateQueue: cleanQueue,
|
||||||
|
photosCreating: state.photosCreating - 1,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case PhotoTypes.PHOTO_UPLOAD_SUCCESS: {
|
||||||
|
const newQueue = state.photoUploadQueue;
|
||||||
|
delete newQueue[action.photo.id];
|
||||||
if (state.photos) {
|
if (state.photos) {
|
||||||
const photos = state.photos;
|
const photos = state.photos;
|
||||||
const photosNoDup = photos.filter(
|
const photosNoDup = photos.filter(
|
||||||
(p) => p.id !== action.photo.id,
|
(p) => p.id !== action.photo.id,
|
||||||
);
|
);
|
||||||
const updPhotos = [action.photo, ...photosNoDup];
|
const updPhotos = [action.photo, ...photosNoDup];
|
||||||
return { ...state, photos: updPhotos };
|
return {
|
||||||
|
...state,
|
||||||
|
photos: updPhotos,
|
||||||
|
photoUploadQueue: newQueue,
|
||||||
|
photosUploading: state.photosUploading - 1,
|
||||||
|
};
|
||||||
} else {
|
} else {
|
||||||
return state;
|
return {
|
||||||
|
...state,
|
||||||
|
photoUploadQueue: newQueue,
|
||||||
|
photosUploading: state.photosUploading - 1,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
}
|
||||||
case PhotoTypes.PHOTO_DELETE_START:
|
case PhotoTypes.PHOTO_DELETE_START:
|
||||||
if (state.photos) {
|
if (state.photos) {
|
||||||
const photos = state.photos;
|
const photos = state.photos;
|
||||||
@@ -172,9 +253,20 @@ export const photosReducer: Reducer<IPhotosState, PhotoAction> = (
|
|||||||
return { ...state, deleteCache: delCache, photos };
|
return { ...state, deleteCache: delCache, photos };
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case PhotoTypes.PHOTO_UPLOAD_FAIL:
|
case PhotoTypes.PHOTO_UPLOAD_FAIL: {
|
||||||
// TODO: Handle photo upload fail
|
// TODO: Handle photo upload fail
|
||||||
return state;
|
const newQueue = state.photoUploadQueue;
|
||||||
|
if (typeof action.photo === "number") {
|
||||||
|
delete newQueue[action.photo];
|
||||||
|
} else {
|
||||||
|
delete newQueue[action.photo.id];
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
photoUploadQueue: newQueue,
|
||||||
|
photosUploading: state.photosUploading - 1,
|
||||||
|
};
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
takeLatest,
|
takeLatest,
|
||||||
takeEvery,
|
takeEvery,
|
||||||
take,
|
take,
|
||||||
|
select,
|
||||||
} from "redux-saga/effects";
|
} from "redux-saga/effects";
|
||||||
import * as SparkMD5 from "spark-md5";
|
import * as SparkMD5 from "spark-md5";
|
||||||
import {
|
import {
|
||||||
@@ -23,6 +24,8 @@ import {
|
|||||||
IPhotoLoadStartAction,
|
IPhotoLoadStartAction,
|
||||||
IPhotosUploadStartAction,
|
IPhotosUploadStartAction,
|
||||||
photoCreateFail,
|
photoCreateFail,
|
||||||
|
photoCreateQueue,
|
||||||
|
photoCreateStart,
|
||||||
photoCreateSuccess,
|
photoCreateSuccess,
|
||||||
photoDeleteFail,
|
photoDeleteFail,
|
||||||
photoDeleteSuccess,
|
photoDeleteSuccess,
|
||||||
@@ -34,6 +37,8 @@ import {
|
|||||||
PhotoTypes,
|
PhotoTypes,
|
||||||
photoUploadFail,
|
photoUploadFail,
|
||||||
photoUploadFailWithFile,
|
photoUploadFailWithFile,
|
||||||
|
photoUploadQueue,
|
||||||
|
photoUploadStart,
|
||||||
photoUploadSuccess,
|
photoUploadSuccess,
|
||||||
} from "./actions";
|
} from "./actions";
|
||||||
import { IPhotosNewRespBody } from "~../../src/routes/photos";
|
import { IPhotosNewRespBody } from "~../../src/routes/photos";
|
||||||
@@ -160,57 +165,84 @@ function* photoLoad(action: IPhotoLoadStartAction) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function* photoUpload(f: File) {
|
function* photoCreate() {
|
||||||
try {
|
const store = yield select();
|
||||||
const hash = yield call(computeChecksumMd5, f);
|
const photosCreating = store.photos.photosCreating;
|
||||||
const size = yield call(computeSize, f);
|
if (photosCreating < 2) {
|
||||||
const format = f.type;
|
const createQueue = store.photos.photoCreateQueue as File[];
|
||||||
|
if (createQueue.length === 0) {
|
||||||
const { response, timeout } = yield race({
|
|
||||||
response: call(createPhoto, hash, size, format),
|
|
||||||
timeout: delay(10000),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (timeout) {
|
|
||||||
yield put(photoCreateFail(f, "Timeout"));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (response.data || response.error === "Photo already exists") {
|
const f = createQueue[0];
|
||||||
const photo = (response as IPhotosNewRespBody).data;
|
yield put(photoCreateStart(f));
|
||||||
yield put(photoCreateSuccess(photo));
|
try {
|
||||||
|
const hash = yield call(computeChecksumMd5, f);
|
||||||
|
const size = yield call(computeSize, f);
|
||||||
|
const format = f.type;
|
||||||
|
|
||||||
try {
|
const { response, timeout } = yield race({
|
||||||
const { response, timeout } = yield race({
|
response: call(createPhoto, hash, size, format),
|
||||||
response: call(uploadPhoto, f, photo.id),
|
timeout: delay(10000),
|
||||||
timeout: delay(10000),
|
});
|
||||||
});
|
|
||||||
|
|
||||||
if (timeout) {
|
if (timeout) {
|
||||||
yield put(photoUploadFailWithFile(photo, f, "Timeout"));
|
yield put(photoCreateFail(f, "Timeout"));
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
if (response.data) {
|
|
||||||
const photo = response.data;
|
|
||||||
yield put(photoUploadSuccess(photo));
|
|
||||||
} else {
|
|
||||||
yield put(
|
|
||||||
photoUploadFailWithFile(photo, f, response.error),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
yield put(photoUploadFailWithFile(photo, f, "Internal error"));
|
|
||||||
}
|
}
|
||||||
} else {
|
if (response.data || response.error === "Photo already exists") {
|
||||||
yield put(photoCreateFail(f, response.error));
|
const photo = (response as IPhotosNewRespBody).data;
|
||||||
|
yield put(photoCreateSuccess(photo, f));
|
||||||
|
yield put(photoUploadQueue(f, photo.id));
|
||||||
|
} else {
|
||||||
|
yield put(photoCreateFail(f, response.error));
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
yield put(photoCreateFail(f, "Internal error"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function* photoUpload() {
|
||||||
|
const store = yield select();
|
||||||
|
const photosUploading = store.photos.photosUploading;
|
||||||
|
if (photosUploading < 2) {
|
||||||
|
const createQueue = store.photos.photoUploadQueue as Record<
|
||||||
|
number,
|
||||||
|
File
|
||||||
|
>;
|
||||||
|
if (Object.keys(createQueue).length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const pId = parseInt(Object.keys(createQueue)[0]);
|
||||||
|
const f = createQueue[pId];
|
||||||
|
yield put(photoUploadStart(f, pId));
|
||||||
|
try {
|
||||||
|
const { response, timeout } = yield race({
|
||||||
|
response: call(uploadPhoto, f, pId),
|
||||||
|
timeout: delay(10000),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (timeout) {
|
||||||
|
yield put(photoUploadFailWithFile(pId, f, "Timeout"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (response.data) {
|
||||||
|
const photo = response.data;
|
||||||
|
yield put(photoUploadSuccess(photo));
|
||||||
|
} else {
|
||||||
|
yield put(photoUploadFailWithFile(pId, f, response.error));
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
yield put(photoUploadFailWithFile(pId, f, "Internal error"));
|
||||||
}
|
}
|
||||||
} catch (e) {
|
|
||||||
yield put(photoCreateFail(f, "Internal error"));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function* photosUpload(action: IPhotosUploadStartAction) {
|
function* photosUpload(action: IPhotosUploadStartAction) {
|
||||||
const files = Array.from(action.files);
|
const files = Array.from(action.files);
|
||||||
yield all(files.map((f) => call(photoUpload, f)));
|
for (const file of files) {
|
||||||
|
yield put(photoCreateQueue(file));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function* photoDelete(action: IPhotoDeleteStartAction) {
|
function* photoDelete(action: IPhotoDeleteStartAction) {
|
||||||
@@ -250,5 +282,11 @@ export function* photosSaga() {
|
|||||||
takeLatest(PhotoTypes.PHOTOS_UPLOAD_START, photosUpload),
|
takeLatest(PhotoTypes.PHOTOS_UPLOAD_START, photosUpload),
|
||||||
takeLatest(PhotoTypes.PHOTO_LOAD_START, photoLoad),
|
takeLatest(PhotoTypes.PHOTO_LOAD_START, photoLoad),
|
||||||
takeEvery(PhotoTypes.PHOTO_DELETE_START, photoDelete),
|
takeEvery(PhotoTypes.PHOTO_DELETE_START, photoDelete),
|
||||||
|
takeEvery(PhotoTypes.PHOTO_CREATE_QUEUE, photoCreate),
|
||||||
|
takeEvery(PhotoTypes.PHOTO_CREATE_SUCCESS, photoCreate),
|
||||||
|
takeEvery(PhotoTypes.PHOTO_CREATE_FAIL, photoCreate),
|
||||||
|
takeEvery(PhotoTypes.PHOTO_UPLOAD_QUEUE, photoUpload),
|
||||||
|
takeEvery(PhotoTypes.PHOTO_UPLOAD_SUCCESS, photoUpload),
|
||||||
|
takeEvery(PhotoTypes.PHOTO_UPLOAD_FAIL, photoUpload),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user