|
| 1 | +import { |
| 2 | + accountSelectors, |
| 3 | + APIActivityV2, |
| 4 | + decodeHashId, |
| 5 | + encodeHashId, |
| 6 | + getContext, |
| 7 | + removeNullable, |
| 8 | + responseAdapter, |
| 9 | + savedPageActions as actions, |
| 10 | + savedPageSelectors, |
| 11 | + savedPageTracksLineupActions as tracksActions, |
| 12 | + User, |
| 13 | + UserTrackMetadata, |
| 14 | + waitForValue |
| 15 | +} from '@audius/common' |
| 16 | +import { call, fork, put, select, takeLatest } from 'typed-redux-saga' |
| 17 | + |
| 18 | +import { processAndCacheTracks } from 'common/store/cache/tracks/utils' |
| 19 | +import { waitForRead } from 'utils/sagaHelpers' |
| 20 | + |
| 21 | +import tracksSagas from './lineups/sagas' |
| 22 | +const { getSaves } = savedPageSelectors |
| 23 | +const { getAccountUser } = accountSelectors |
| 24 | + |
| 25 | +function* fetchLineupMetadatas(offset: number, limit: number) { |
| 26 | + const isNativeMobile = yield* getContext('isNativeMobile') |
| 27 | + |
| 28 | + // Mobile currently uses infinite scroll instead of a virtualized list |
| 29 | + // so we need to apply the offset & limit |
| 30 | + if (isNativeMobile) { |
| 31 | + yield* put(tracksActions.fetchLineupMetadatas(offset, limit)) |
| 32 | + } else { |
| 33 | + yield* put(tracksActions.fetchLineupMetadatas()) |
| 34 | + } |
| 35 | +} |
| 36 | + |
| 37 | +type LibraryParams = { |
| 38 | + userId: number |
| 39 | + offset: number |
| 40 | + limit: number |
| 41 | + query: string |
| 42 | + sortMethod: string |
| 43 | + sortDirection: string |
| 44 | +} |
| 45 | + |
| 46 | +function* sendLibraryRequest({ |
| 47 | + userId, |
| 48 | + offset, |
| 49 | + limit, |
| 50 | + query, |
| 51 | + sortMethod, |
| 52 | + sortDirection |
| 53 | +}: LibraryParams) { |
| 54 | + const audiusBackendInstance = yield* getContext('audiusBackendInstance') |
| 55 | + const { data, signature } = yield* call([ |
| 56 | + audiusBackendInstance, |
| 57 | + audiusBackendInstance.signDiscoveryNodeRequest |
| 58 | + ]) |
| 59 | + const audiusSdk = yield* getContext('audiusSdk') |
| 60 | + const sdk = yield* call(audiusSdk) |
| 61 | + |
| 62 | + const savedTracksResponse = (yield* call( |
| 63 | + [sdk.full.users, sdk.full.users.getUserLibraryTracks as any], |
| 64 | + { |
| 65 | + id: encodeHashId(userId), |
| 66 | + userId: encodeHashId(userId), |
| 67 | + offset, |
| 68 | + limit, |
| 69 | + query, |
| 70 | + sortMethod, |
| 71 | + sortDirection, |
| 72 | + type: 'all', |
| 73 | + encodedDataMessage: data, |
| 74 | + encodedDataSignature: signature |
| 75 | + } |
| 76 | + )) as any |
| 77 | + const savedTracksResponseData = savedTracksResponse.data as APIActivityV2[] |
| 78 | + const tracks = savedTracksResponse.data |
| 79 | + ?.map(responseAdapter.makeActivity) |
| 80 | + .filter(removeNullable) as UserTrackMetadata[] |
| 81 | + if (!tracks) { |
| 82 | + throw new Error('Something went wrong with library tracks request.') |
| 83 | + } |
| 84 | + |
| 85 | + const saves = savedTracksResponseData |
| 86 | + .filter((save) => Boolean(save.timestamp && save.item)) |
| 87 | + .map((save) => ({ |
| 88 | + created_at: save.timestamp, |
| 89 | + save_item_id: decodeHashId(save.item.id!) |
| 90 | + })) |
| 91 | + |
| 92 | + return { |
| 93 | + saves, |
| 94 | + tracks |
| 95 | + } |
| 96 | +} |
| 97 | + |
| 98 | +function prepareParams({ |
| 99 | + account, |
| 100 | + params |
| 101 | +}: { |
| 102 | + account: User |
| 103 | + params: ReturnType<typeof actions.fetchSaves> |
| 104 | +}) { |
| 105 | + return { |
| 106 | + userId: account.user_id, |
| 107 | + offset: params.offset ?? 0, |
| 108 | + limit: params.limit ?? account.track_save_count, |
| 109 | + query: params.query ?? '', |
| 110 | + sortMethod: params.sortMethod || 'added_date', |
| 111 | + sortDirection: params.sortDirection || 'desc' |
| 112 | + } |
| 113 | +} |
| 114 | + |
| 115 | +function* watchFetchSaves() { |
| 116 | + let currentQuery = '' |
| 117 | + let currentSortMethod = '' |
| 118 | + let currentSortDirection = '' |
| 119 | + |
| 120 | + yield* takeLatest( |
| 121 | + actions.FETCH_SAVES, |
| 122 | + function* (rawParams: ReturnType<typeof actions.fetchSaves>) { |
| 123 | + yield* waitForRead() |
| 124 | + const account: User = yield* call(waitForValue, getAccountUser) |
| 125 | + const saves = yield* select(getSaves) |
| 126 | + const params = prepareParams({ account, params: rawParams }) |
| 127 | + const { query, sortDirection, sortMethod, offset, limit } = params |
| 128 | + const isSameParams = |
| 129 | + query === currentQuery && |
| 130 | + currentSortDirection === sortDirection && |
| 131 | + currentSortMethod === sortMethod |
| 132 | + |
| 133 | + // Don't refetch saves in the same session |
| 134 | + if (saves && saves.length && isSameParams) { |
| 135 | + yield* fork(fetchLineupMetadatas, offset, limit) |
| 136 | + } else { |
| 137 | + try { |
| 138 | + currentQuery = query |
| 139 | + currentSortDirection = sortDirection |
| 140 | + currentSortMethod = sortMethod |
| 141 | + yield* put(actions.fetchSavesRequested()) |
| 142 | + const { saves, tracks } = yield* call(sendLibraryRequest, params) |
| 143 | + |
| 144 | + yield* processAndCacheTracks(tracks) |
| 145 | + |
| 146 | + const fullSaves = Array(account.track_save_count) |
| 147 | + .fill(0) |
| 148 | + .map((_) => ({})) |
| 149 | + |
| 150 | + fullSaves.splice(offset, saves.length, ...saves) |
| 151 | + yield* put(actions.fetchSavesSucceeded(fullSaves)) |
| 152 | + if (limit > 0 && saves.length < limit) { |
| 153 | + yield* put(actions.endFetching(offset + saves.length)) |
| 154 | + } |
| 155 | + yield* fork(fetchLineupMetadatas, offset, limit) |
| 156 | + } catch (e) { |
| 157 | + yield* put(actions.fetchSavesFailed()) |
| 158 | + } |
| 159 | + } |
| 160 | + } |
| 161 | + ) |
| 162 | +} |
| 163 | + |
| 164 | +function* watchFetchMoreSaves() { |
| 165 | + yield* takeLatest( |
| 166 | + actions.FETCH_MORE_SAVES, |
| 167 | + function* (rawParams: ReturnType<typeof actions.fetchMoreSaves>) { |
| 168 | + yield* waitForRead() |
| 169 | + const account = yield* call(waitForValue, getAccountUser) |
| 170 | + const params = prepareParams({ account, params: rawParams }) |
| 171 | + const { limit, offset } = params |
| 172 | + |
| 173 | + try { |
| 174 | + const { saves, tracks } = yield* call(sendLibraryRequest, params) |
| 175 | + yield* processAndCacheTracks(tracks) |
| 176 | + yield* put(actions.fetchMoreSavesSucceeded(saves, offset)) |
| 177 | + |
| 178 | + if (limit > 0 && saves.length < limit) { |
| 179 | + yield* put(actions.endFetching(offset + saves.length)) |
| 180 | + } |
| 181 | + yield* fork(fetchLineupMetadatas, offset, limit) |
| 182 | + } catch (e) { |
| 183 | + yield* put(actions.fetchMoreSavesFailed()) |
| 184 | + } |
| 185 | + } |
| 186 | + ) |
| 187 | +} |
| 188 | + |
| 189 | +export default function sagas() { |
| 190 | + return [...tracksSagas(), watchFetchSaves, watchFetchMoreSaves] |
| 191 | +} |
0 commit comments