diff --git a/packages/common/src/services/audius-backend/AudiusBackend.ts b/packages/common/src/services/audius-backend/AudiusBackend.ts index f9a233b4215..1a51af5d64a 100644 --- a/packages/common/src/services/audius-backend/AudiusBackend.ts +++ b/packages/common/src/services/audius-backend/AudiusBackend.ts @@ -1018,21 +1018,26 @@ export const audiusBackend = ({ trackFile: File, coverArtFile: File, metadata: TrackMetadata, - onProgress: (loaded: number, total: number) => void + onProgress: (loaded: number, total: number) => void, + trackId?: number ) { try { - const { trackId, updatedMetadata, txReceipt } = - await audiusLibs.Track.uploadTrackV2AndWriteToChain( - trackFile, - coverArtFile, - metadata, - onProgress - ) + const { + trackId: updatedTrackId, + updatedMetadata, + txReceipt + } = await audiusLibs.Track.uploadTrackV2AndWriteToChain( + trackFile, + coverArtFile, + metadata, + onProgress, + trackId + ) // Return with properties that confirmer expects return { blockHash: txReceipt.blockHash, blockNumber: txReceipt.blockNumber, - trackId, + trackId: updatedTrackId, transcodedTrackCID: updatedMetadata.track_cid, error: false } diff --git a/packages/common/src/store/upload/actions.ts b/packages/common/src/store/upload/actions.ts index ae7a961af62..380a1a7dfca 100644 --- a/packages/common/src/store/upload/actions.ts +++ b/packages/common/src/store/upload/actions.ts @@ -1,4 +1,4 @@ -import { TrackMetadata } from '../../models' +import { StemUploadWithFile } from '../../models' import { ExtendedCollectionMetadata, @@ -48,7 +48,7 @@ export const uploadTracks = ( tracks: UploadTrack[], metadata?: ExtendedCollectionMetadata, uploadType?: UploadType, - stems?: TrackMetadata[] + stems?: StemUploadWithFile[] ) => { return { type: UPLOAD_TRACKS, tracks, metadata, uploadType, stems } } @@ -61,7 +61,7 @@ export const uploadTracksRequested = ( tracks: UploadTrack[], metadata?: ExtendedCollectionMetadata, uploadType?: UploadType, - stems?: TrackMetadata[] + stems?: StemUploadWithFile[][] ) => { return { type: UPLOAD_TRACKS_REQUESTED, diff --git a/packages/common/src/store/upload/reducer.ts b/packages/common/src/store/upload/reducer.ts index c59f1db0ab4..a34dc0809fc 100644 --- a/packages/common/src/store/upload/reducer.ts +++ b/packages/common/src/store/upload/reducer.ts @@ -15,7 +15,8 @@ import { updateProgress, uploadSingleTrackFailed } from './actions' -import { ProgressStatus, UploadState } from './types' +import { ProgressStatus, UploadState, UploadTrack } from './types' +import { StemUploadWithFile } from '~/models' const initialState: UploadState = { openMultiTrackNotification: true, @@ -52,6 +53,13 @@ const initialUploadState = { transcode: 0 } } +const getInitialProgress = (upload: UploadTrack | StemUploadWithFile) => { + const res = cloneDeep(initialUploadState) + res.art.total = + 'artwork' in upload.metadata ? upload.metadata.artwork?.file?.size ?? 0 : 0 + res.audio.total = upload.file?.size ?? 0 + return res +} const actionsMap = { [TOGGLE_MULTI_TRACK_NOTIFICATION]( @@ -70,12 +78,13 @@ const actionsMap = { const newState = { ...state } newState.uploading = true newState.tracks = action.tracks - newState.uploadProgress = action.tracks.map(() => - cloneDeep(initialUploadState) - ) + newState.uploadProgress = action.tracks + .map(getInitialProgress) + .concat(action.stems?.map((t) => t.map(getInitialProgress)).flat(1) ?? []) newState.metadata = action.metadata ?? null newState.uploadType = action.uploadType ?? null newState.stems = action.stems ?? newState.stems + newState.error = false return newState }, [UPLOAD_TRACKS_SUCCEEDED]( @@ -107,6 +116,7 @@ const actionsMap = { newState.tracks = null newState.metadata = null newState.stems = [] + newState.error = true return newState }, [UPDATE_PROGRESS]( diff --git a/packages/common/src/store/upload/selectors.ts b/packages/common/src/store/upload/selectors.ts index 07468e0eb64..88bc31bac99 100644 --- a/packages/common/src/store/upload/selectors.ts +++ b/packages/common/src/store/upload/selectors.ts @@ -1,6 +1,7 @@ -import { round, clamp } from 'lodash' +import { floor, clamp } from 'lodash' import { CommonState } from '../commonStore' +import { ProgressStatus } from './types' export const getStems = (state: CommonState) => state.upload.stems export const getUploadProgress = (state: CommonState) => @@ -22,7 +23,10 @@ const getKeyUploadProgress = (state: CommonState, key: 'art' | 'audio') => { const uploadProgress = getUploadProgress(state) if (uploadProgress == null) return 0 - const filteredProgress = uploadProgress.filter((progress) => key in progress) + const filteredProgress = uploadProgress.filter( + (progress) => + key in progress && progress[key].status !== ProgressStatus.ERROR + ) if (filteredProgress.length === 0) return 0 const loaded = filteredProgress.reduce( @@ -53,7 +57,7 @@ const getKeyUploadProgress = (state: CommonState, key: 'art' | 'audio') => { export const getCombinedUploadPercentage = (state: CommonState) => { const artProgress = getKeyUploadProgress(state, 'art') const audioProgress = getKeyUploadProgress(state, 'audio') - const percent = round( + const percent = floor( 100 * (ART_WEIGHT * artProgress + AUDIO_WEIGHT * audioProgress) ) return clamp(percent, 0, 100) diff --git a/packages/common/src/store/upload/types.ts b/packages/common/src/store/upload/types.ts index 6d35737b2ea..63f3f9190f0 100644 --- a/packages/common/src/store/upload/types.ts +++ b/packages/common/src/store/upload/types.ts @@ -1,4 +1,4 @@ -import { CollectionMetadata, TrackMetadata } from '../../models' +import { CollectionMetadata, StemUpload, TrackMetadata } from '../../models' import { Nullable } from '../../utils/typeUtils' export type NativeFile = { @@ -68,6 +68,6 @@ export interface UploadState { error: boolean shouldReset: boolean completionId: Nullable - stems: TrackMetadata[] + stems: StemUpload[][] failedTrackIndices: number[] } diff --git a/packages/libs/src/api/Track.ts b/packages/libs/src/api/Track.ts index 5d869292b54..24462004238 100644 --- a/packages/libs/src/api/Track.ts +++ b/packages/libs/src/api/Track.ts @@ -371,7 +371,8 @@ export class Track extends Base { trackFile: File, coverArtFile: File, metadata: TrackMetadata, - onProgress: () => void + onProgress: () => void, + trackId?: number ) { const updatedMetadata = await this.uploadTrackV2( trackFile, @@ -379,11 +380,12 @@ export class Track extends Base { metadata, onProgress ) - const { trackId, metadataCid, txReceipt } = await this.writeTrackToChain( - updatedMetadata, - Action.CREATE - ) - return { trackId, metadataCid, updatedMetadata, txReceipt } + const { + trackId: updatedTrackId, + metadataCid, + txReceipt + } = await this.writeTrackToChain(updatedMetadata, Action.CREATE, trackId) + return { trackId: updatedTrackId, metadataCid, updatedMetadata, txReceipt } } /** @@ -474,7 +476,7 @@ export class Track extends Base { throw new Error('No users loaded for this wallet') } - if (!trackId) trackId = await this._generateTrackId() + if (!trackId) trackId = await this.generateTrackId() const metadataCid = await Utils.fileHasher.generateMetadataCidV1( trackMetadata ) @@ -575,6 +577,14 @@ export class Track extends Base { ) } + async generateTrackId(): Promise { + const encodedId = await this.discoveryProvider.getUnclaimedId('tracks') + if (!encodedId) { + throw new Error('No unclaimed track IDs') + } + return decodeHashId(encodedId)! + } + /* ------- PRIVATE ------- */ // Throws an error upon validation failure @@ -582,12 +592,4 @@ export class Track extends Base { this.OBJECT_HAS_PROPS(metadata, TRACK_PROPS, TRACK_REQUIRED_PROPS) this.creatorNode.validateTrackSchema(metadata) } - - async _generateTrackId(): Promise { - const encodedId = await this.discoveryProvider.getUnclaimedId('tracks') - if (!encodedId) { - throw new Error('No unclaimed track IDs') - } - return decodeHashId(encodedId)! - } } diff --git a/packages/web/src/common/store/upload/sagas.js b/packages/web/src/common/store/upload/sagas.js index 4a047591d82..cdf818bea36 100644 --- a/packages/web/src/common/store/upload/sagas.js +++ b/packages/web/src/common/store/upload/sagas.js @@ -15,7 +15,6 @@ import { confirmTransaction } from '@audius/common/store' import { - formatUrlName, makeUid, actionChannelDispatcher, waitForAccount @@ -31,8 +30,7 @@ import { fork, cancel, all, - getContext, - delay + getContext } from 'redux-saga/effects' import { make } from 'common/store/analytics/actions' @@ -48,14 +46,14 @@ import { updateAndFlattenStems } from 'pages/upload-page/store/utils/stems' import * as errorActions from 'store/errors/actions' import { waitForWrite } from 'utils/sagaHelpers' -import { processAndCacheTracks } from '../cache/tracks/utils' +import { retrieveTracks } from '../cache/tracks/utils' import { adjustUserField } from '../cache/users/sagas' import { watchUploadErrors } from './errorSagas' const { getUser } = cacheUsersSelectors const { addLocalCollection } = savedPageActions -const { getAccountUser, getUserHandle, getUserId } = accountSelectors +const { getAccountUser } = accountSelectors const { getStems } = uploadSelectors const MAX_CONCURRENT_UPLOADS = 4 @@ -115,7 +113,6 @@ const getNumWorkers = (trackFiles) => { // index: ... // artwork?: ..., // isCollection: boolean -// updateProgress: boolean // } // // or to signal to the worker that it should shut down: @@ -182,14 +179,7 @@ function* uploadWorker(requestChan, respChan, progressChan) { // If it's not a collection (e.g. we're just uploading multiple tracks) // we can call uploadTrack, which uploads to creator node and then writes to chain. - const makeConfirmerCall = ( - track, - metadata, - artwork, - index, - id, - updateProgress - ) => { + const makeConfirmerCall = (track, metadata, artwork, index, id) => { return function* () { const audiusBackendInstance = yield getContext('audiusBackendInstance') console.debug( @@ -200,7 +190,9 @@ function* uploadWorker(requestChan, respChan, progressChan) { track.file, artwork, metadata, - updateProgress ? makeOnProgress(index) : (progress) => {} + makeOnProgress(index), + // If the track id isn't a temporary string, but a real id, use it + typeof metadata.id !== 'number' ? undefined : metadata.id ) // b/c we can't pass extra info (phase) into the confirmer fail call, we need to clean up here. @@ -219,7 +211,7 @@ function* uploadWorker(requestChan, respChan, progressChan) { throw new Error('') } - console.debug(`Got new ID ${trackId} for track ${metadata.title}}`) + console.debug(`Got new ID ${trackId} for track ${metadata.title}`) const confirmed = yield call(confirmTransaction, blockHash, blockNumber) if (!confirmed) { @@ -246,22 +238,20 @@ function* uploadWorker(requestChan, respChan, progressChan) { } } - const makeConfirmerSuccess = (id, index, updateProgress) => { + const makeConfirmerSuccess = (id, index) => { return function* (newTrackId) { - if (updateProgress) { - yield put( - progressChan, - uploadActions.updateProgress(index, 'art', { - status: ProgressStatus.COMPLETE - }) - ) - yield put( - progressChan, - uploadActions.updateProgress(index, 'audio', { - status: ProgressStatus.COMPLETE - }) - ) - } + yield put( + progressChan, + uploadActions.updateProgress(index, 'art', { + status: ProgressStatus.COMPLETE + }) + ) + yield put( + progressChan, + uploadActions.updateProgress(index, 'audio', { + status: ProgressStatus.COMPLETE + }) + ) // Now we need to tell the response channel that we finished const resp = { originalId: id, newId: newTrackId } @@ -333,15 +323,7 @@ function* uploadWorker(requestChan, respChan, progressChan) { // worker runloop while (true) { const request = yield take(requestChan) - const { - track, - metadata, - id, - index, - artwork, - isCollection, - updateProgress - } = request + const { track, metadata, id, index, artwork, isCollection } = request yield put( confirmerActions.requestConfirmation( @@ -351,12 +333,11 @@ function* uploadWorker(requestChan, respChan, progressChan) { metadata, artwork, index, - id, - updateProgress + id ), (isCollection ? makeConfirmerSuccessForCollection - : makeConfirmerSuccess)(id, index, updateProgress), + : makeConfirmerSuccess)(id, index), isCollection ? makeConfirmerFailureCollection(id) : confirmerFailure, () => {}, UPLOAD_TIMEOUT_MILLIS @@ -372,18 +353,15 @@ function* uploadWorker(requestChan, respChan, progressChan) { * * tracks is of type [{ track: ..., metadata: ... }] */ -export function* handleUploads({ - tracks, - isCollection, - isStem = false, - isAlbum = false -}) { +export function* handleUploads({ tracks, isCollection, isAlbum = false }) { const audiusBackendInstance = yield getContext('audiusBackendInstance') const numWorkers = getNumWorkers(tracks.map((t) => t.track.file)) // Map of shape {[trackId]: { track: track, metadata: object, artwork?: file, index: number }} const idToTrackMap = tracks.reduce((prev, cur, idx) => { - const newId = `${cur.metadata.title}_${idx}` + const newId = cur.metadata.id + ? cur.metadata.id + : `${cur.metadata.title}_${idx}` prev[newId] = { track: cur.track, metadata: cur.metadata, @@ -418,7 +396,7 @@ export function* handleUploads({ // Give our workers jobs to do const ids = Object.keys(idToTrackMap) for (let i = 0; i < ids.length; i++) { - const id = ids[i] + const id = isNaN(ids[i]) ? ids[i] : Number(ids[i]) const value = idToTrackMap[id] const request = { id, @@ -429,8 +407,7 @@ export function* handleUploads({ }, index: value.index, artwork: isCollection ? null : value.artwork, - isCollection, - updateProgress: !isStem + isCollection } yield put(requestChan, request) yield put( @@ -452,27 +429,19 @@ export function* handleUploads({ } // Set some sensible progress values - if (!isStem) { - for (let i = 0; i < ids.length; i++) { - const trackFile = tracks[i].track.file - yield put( - progressChan, - uploadActions.updateProgress(i, 'art', { - loaded: 0, - total: trackFile.size, - status: ProgressStatus.UPLOADING - }) - ) - yield put( - progressChan, - - uploadActions.updateProgress(i, 'audio', { - loaded: 0, - total: trackFile.size, - status: ProgressStatus.UPLOADING - }) - ) - } + for (let i = 0; i < ids.length; i++) { + yield put( + progressChan, + uploadActions.updateProgress(i, 'art', { + status: ProgressStatus.UPLOADING + }) + ) + yield put( + progressChan, + uploadActions.updateProgress(i, 'audio', { + status: ProgressStatus.UPLOADING + }) + ) } // Now wait for our workers to finish or error @@ -507,11 +476,25 @@ export function* handleUploads({ console.error(`Worker errored: ${error}`) const index = idToTrackMap[originalId].index - if (!isStem) { + if (!metadata?.stem_of?.parent_track_id) { yield put(uploadActions.uploadSingleTrackFailed(index)) } - if (message.includes('403')) { + // Set progress to errored for this track + yield put( + progressChan, + uploadActions.updateProgress(index, 'art', { + status: ProgressStatus.ERROR + }) + ) + yield put( + progressChan, + uploadActions.updateProgress(index, 'audio', { + status: ProgressStatus.ERROR + }) + ) + + if (typeof message?.includes === 'function' && message.includes('403')) { // This is a rejection not a failure, record it as so rejectedRequests.push({ originalId, timeout, message, phase }) } else { @@ -652,26 +635,25 @@ export function* handleUploads({ } else { console.debug('Tracks registered successfully') // Update all the progress - if (!isStem) { - yield all( - range(tracks.length).flatMap((i) => { - return [ - put( - progressChan, - uploadActions.updateProgress(i, 'art', { - status: ProgressStatus.COMPLETE - }) - ), - put( - progressChan, - uploadActions.updateProgress(i, 'audio', { - status: ProgressStatus.COMPLETE - }) - ) - ] - }) - ) - } + yield all( + range(tracks.length).flatMap((i) => { + return [ + put( + progressChan, + uploadActions.updateProgress(i, 'art', { + status: ProgressStatus.COMPLETE + }) + ), + put( + progressChan, + uploadActions.updateProgress(i, 'audio', { + status: ProgressStatus.COMPLETE + }) + ) + ] + }) + ) + returnVal = { trackIds } } } else { @@ -685,7 +667,29 @@ export function* handleUploads({ } returnVal = { error: true } } - } else if (!isCollection && failedRequests.length > 0) { + } else if ( + !isCollection && + (failedRequests.length > 0 || rejectedRequests.length > 0) + ) { + // If any track didn't upload was a stem parent, delete associated stems + const failedTrackIds = failedRequests + .concat(rejectedRequests) + .map((r) => r.originalId) + const stemsToDelete = tracks + .map((t, i) => + failedTrackIds.includes(t.metadata?.stem_of?.parent_track_id) + ? trackIds[i] + : undefined + ) + .filter((t) => t !== undefined) + + if (stemsToDelete.length > 0) { + console.debug(`Deleting ${stemsToDelete.length} orphaned stems...`) + yield all( + stemsToDelete.map((id) => audiusBackendInstance.deleteTrack(id)) + ) + } + // If some requests failed for multitrack, log em yield all( failedRequests.map((r) => { @@ -696,8 +700,7 @@ export function* handleUploads({ uploadActions.multiTrackUploadError( r.message, r.phase, - tracks.length, - isStem + tracks.length ) ) } @@ -898,262 +901,99 @@ function* uploadCollection(tracks, userId, collectionMetadata, isAlbum) { ) } -function* uploadSingleTrack(track) { - yield waitForWrite() +function* uploadMultipleTracks(tracks) { + // Cheating here - getting the track ID early so we can parallelize stems const audiusBackendInstance = yield getContext('audiusBackendInstance') - const apiClient = yield getContext('apiClient') - // Need an object to hold phase error info that - // can get captured by confirmer closure - // while remaining mutable. - const phaseContainer = { phase: null } - const progressChan = yield call(channel) - - // When the upload finishes, it should return - // either a { track_id } or { error } object, - // which is then used to upload stems if they exist. - const responseChan = yield call(channel) - - const dispatcher = yield fork(actionChannelDispatcher, progressChan) - const recordEvent = make(Name.TRACK_UPLOAD_TRACK_UPLOADING, { - artworkSource: track.metadata.artwork.source, - genre: track.metadata.genre, - mood: track.metadata.mood, - size: track.file.size, - type: track.file.type, - name: track.file.name, - downloadable: - track.metadata.download && track.metadata.download.is_downloadable - ? track.metadata.download.requires_follow - ? 'follow' - : 'yes' - : 'no' - }) - yield put(recordEvent) - - const onProgress = (progress) => { - const key = 'audio' in progress ? 'audio' : 'art' in progress ? 'art' : null - if (!key) return - const { upload, transcode } = progress[key] - const loaded = upload?.loaded - const total = upload?.total - progressChan.put( - uploadActions.updateProgress(0, key, { - loaded, - total, - transcode: transcode?.decimal, - status: - loaded !== total - ? ProgressStatus.UPLOADING - : ProgressStatus.PROCESSING - }) - ) - } - - yield put( - confirmerActions.requestConfirmation( - `${track.metadata.title}`, - function* () { - const { blockHash, blockNumber, trackId, error, phase } = yield call( - audiusBackendInstance.uploadTrack, - track.file, - track.metadata.artwork.file, - track.metadata, - onProgress - ) - - if (error) { - phaseContainer.phase = phase - throw new Error(error) - } - - yield waitForWrite() - const userId = yield select(getUserId) - const handle = yield select(getUserHandle) - const confirmed = yield call(confirmTransaction, blockHash, blockNumber) - if (!confirmed) { - throw new Error(`Could not confirm upload single track ${trackId}`) - } - - let retries = 30 - while (retries !== 0) { - const maybeTrackRes = yield apiClient.getTrack({ - id: trackId, - currentUserId: userId, - unlistedArgs: { - urlTitle: formatUrlName(track.metadata.title), - handle - } - }) - if (maybeTrackRes !== undefined && maybeTrackRes !== null) { - return maybeTrackRes + const audiusLibs = yield call([ + audiusBackendInstance, + audiusBackendInstance.getAudiusLibs + ]) + const tracksWithMetadata = yield all( + tracks.map((track) => + call(function* () { + const id = yield call([ + audiusLibs.Track, + audiusLibs.Track.generateTrackId + ]) + return { + track, + metadata: { + ...track.metadata, + id } - // lil bit of delay for retries - yield delay(1500) - retries -= 1 - } - - throw new Error(`Exhausted retries querying for track ${trackId}`) - }, - function* onSuccess(confirmedTrack) { - yield call(responseChan.put, { confirmedTrack }) - }, - function* ({ timeout, message }) { - yield put(uploadActions.uploadTrackFailed()) - - if (timeout) { - yield put(uploadActions.singleTrackTimeoutError()) - } else { - yield put( - uploadActions.singleTrackUploadError( - message, - phaseContainer.phase, - track.file.size - ) - ) } - yield cancel(dispatcher) - yield call(responseChan.put, { error: message }) - }, - () => {}, - UPLOAD_TIMEOUT_MILLIS + }) ) ) - const { confirmedTrack, error } = yield take(responseChan) - const isRejected = error === 'Request failed with status code 403' + // Get stem metadatas + const stems = yield select(getStems) + const updatedStems = updateAndFlattenStems( + stems, + tracksWithMetadata.map((t) => t.metadata.id) + ) + const allUploads = tracksWithMetadata.concat(updatedStems) - yield reportResultEvents({ - numSuccess: error ? 0 : 1, - numFailure: error && !isRejected ? 1 : 0, - numRejected: isRejected ? 1 : 0, - uploadType: 'single_track', - errors: error ? [error] : [] + // Upload tracks and stems parallel together + const { trackIds } = yield call(handleUploads, { + tracks: allUploads, + isCollection: false }) - if (error) { - if (isRejected) { - yield put( - make(Name.TRACK_UPLOAD_COMPLETE_UPLOAD, { - count: 1, - kind: 'tracks' - }) + // Record stem upload events + for (let i = tracksWithMetadata.length; i < trackIds.length; i += 1) { + const trackId = trackIds[i] + const parentTrackId = allUploads[i]?.metadata?.stem_of?.parent_track_id + const category = allUploads[i]?.metadata?.stem_of?.category + if (!trackIds.includes(parentTrackId)) { + console.debug( + `Stem ${trackId} (${allUploads[i].metadata.title}) was not successful as its parent wasn't uploaded` ) - yield call(recordGatedTracks, [track.metadata]) + continue } - return - } - - const stems = yield select(getStems) - if (stems.length) { - yield call(uploadStems, { - parentTrackIds: [confirmedTrack.track_id], - stems + const recordEvent = make(Name.STEM_COMPLETE_UPLOAD, { + id: trackId, + parent_track_id: parentTrackId, + category }) + yield put(recordEvent) } - // Finish up the upload - progressChan.put( - uploadActions.updateProgress(0, 'art', { - status: ProgressStatus.COMPLETE - }) - ) - progressChan.put( - uploadActions.updateProgress(0, 'audio', { - status: ProgressStatus.COMPLETE - }) - ) - - yield put( - uploadActions.uploadTracksSucceeded(confirmedTrack.track_id, [ - confirmedTrack - ]) - ) - yield put( - make(Name.TRACK_UPLOAD_COMPLETE_UPLOAD, { - count: 1, - kind: 'tracks' - }) - ) - yield call(recordGatedTracks, [confirmedTrack]) + // If EVERYTHING failed, don't mark this as a success! + if (trackIds.length === 0 || trackIds.every((t) => t === undefined)) { + yield put(uploadActions.uploadTrackFailed()) + yield put( + errorActions.handleError({ + name: 'Upload Failed', + message: 'All uploads failed!', + shouldRedirect: true, + shouldReport: true, + additionalInfo: { + tracks, + stems + } + }) + ) + return + } yield waitForAccount() const account = yield select(getAccountUser) - yield put(cacheActions.setExpired(Kind.USERS, account.user_id)) + const tracksToFetch = trackIds.filter((id) => !!id) - yield call(processAndCacheTracks, [confirmedTrack]) + const newTracks = yield call(retrieveTracks, { + trackIds: tracksToFetch, + forceRetrieveFromSource: true + }) yield call(adjustUserField, { user: account, fieldName: 'track_count', - delta: 1 - }) - - // If the hide remixes is turned on, send analytics event - if ( - confirmedTrack.field_visibility && - !confirmedTrack.field_visibility.remixes - ) { - yield put( - make(Name.REMIX_HIDE, { - id: confirmedTrack.track_id, - handle: account.handle - }) - ) - } - - if ( - confirmedTrack.remix_of && - Array.isArray(confirmedTrack.remix_of.tracks) && - confirmedTrack.remix_of.tracks.length > 0 - ) { - yield call(trackNewRemixEvent, confirmedTrack) - } - - yield cancel(dispatcher) -} - -export function* uploadStems({ parentTrackIds, stems }) { - const updatedStems = updateAndFlattenStems(stems, parentTrackIds) - - const uploadedTracks = yield call(handleUploads, { - tracks: updatedStems, - isCollection: false, - isStem: true + delta: newTracks.length }) - if (uploadedTracks.trackIds) { - for (let i = 0; i < uploadedTracks.trackIds.length; i += 1) { - const trackId = uploadedTracks.trackIds[i] - const parentTrackId = updatedStems[i].metadata.stem_of.parent_track_id - const category = updatedStems[i].metadata.stem_of.category - const recordEvent = make(Name.STEM_COMPLETE_UPLOAD, { - id: trackId, - parent_track_id: parentTrackId, - category - }) - yield put(recordEvent) - } - } -} - -function* uploadMultipleTracks(tracks) { - const tracksWithMetadata = tracks.map((track) => ({ - track, - metadata: track.metadata - })) - const { trackIds } = yield call(handleUploads, { - tracks: tracksWithMetadata, - isCollection: false - }) - const stems = yield select(getStems) - if (stems.length) { - yield call(uploadStems, { - parentTrackIds: trackIds, - stems - }) - } + yield put(uploadActions.uploadTracksSucceeded(trackIds[0], newTracks)) - yield put(uploadActions.uploadTracksSucceeded()) yield put( make(Name.TRACK_UPLOAD_COMPLETE_UPLOAD, { count: tracksWithMetadata.length, @@ -1165,9 +1005,6 @@ function* uploadMultipleTracks(tracks) { tracksWithMetadata.map((track) => track.metadata) ) - yield waitForAccount() - const account = yield select(getAccountUser) - // If the hide remixes is turned on, send analytics event for (let i = 0; i < tracks.length; i += 1) { const track = tracks[i] @@ -1235,11 +1072,8 @@ function* uploadTracksAsync(action) { // Upload content. const isPlaylist = action.uploadType === UploadType.PLAYLIST const isAlbum = action.uploadType === UploadType.ALBUM - const isSingleTrack = tracks.length === 1 if (isPlaylist || isAlbum) { yield call(uploadCollection, tracks, user.user_id, action.metadata, isAlbum) - } else if (isSingleTrack) { - yield call(uploadSingleTrack, tracks[0]) } else { yield call(uploadMultipleTracks, tracks) } diff --git a/packages/web/src/pages/upload-page/pages/FinishPage.tsx b/packages/web/src/pages/upload-page/pages/FinishPage.tsx index 6b58b5a8717..e961a36372a 100644 --- a/packages/web/src/pages/upload-page/pages/FinishPage.tsx +++ b/packages/web/src/pages/upload-page/pages/FinishPage.tsx @@ -124,14 +124,21 @@ export const FinishPage = (props: FinishPageProps) => { const dispatch = useDispatch() const uploadComplete = useMemo(() => { - if (!upload.uploadProgress || upload.uploading || !upload.success) + if ( + !upload.uploadProgress || + upload.uploading || + !upload.success || + upload.error + ) return false return upload.uploadProgress.reduce((acc, progress) => { return ( acc && - progress.art.status === ProgressStatus.COMPLETE && - progress.audio.status === ProgressStatus.COMPLETE + (progress.art.status === ProgressStatus.COMPLETE || + progress.art.status === ProgressStatus.ERROR) && + (progress.audio.status === ProgressStatus.COMPLETE || + progress.audio.status === ProgressStatus.ERROR) ) }, true) }, [upload])