Basic download implementation

This commit is contained in:
Lei Nelissen
2022-01-02 02:28:52 +01:00
parent 464747d0c4
commit d2fb4a4aea
30 changed files with 605 additions and 102 deletions

View File

@@ -0,0 +1,54 @@
import { createAction, createAsyncThunk, createEntityAdapter, EntityId } from '@reduxjs/toolkit';
import { AppState } from 'store';
import { generateTrackUrl } from 'utility/JellyfinApi';
import { downloadFile, unlink, DocumentDirectoryPath } from 'react-native-fs';
import { DownloadEntity } from './types';
export const downloadAdapter = createEntityAdapter<DownloadEntity>({
selectId: (entity) => entity.id,
});
export const initializeDownload = createAction<{ id: EntityId, size?: number, jobId?: number }>('download/initialize');
export const progressDownload = createAction<{ id: EntityId, progress: number, jobId?: number }>('download/progress');
export const completeDownload = createAction<{ id: EntityId, location: string, size?: number }>('download/complete');
export const failDownload = createAction<{ id: EntityId }>('download/fail');
export const downloadTrack = createAsyncThunk(
'/downloads/track',
async (id: EntityId, { dispatch, getState }) => {
// Get the credentials from the store
const { settings: { jellyfin: credentials } } = (getState() as AppState);
// Generate the URL we can use to download the file
const url = generateTrackUrl(id as string, credentials);
const location = `${DocumentDirectoryPath}/${id}.mp3`;
// Actually kick off the download
const { promise } = await downloadFile({
fromUrl: url,
progressInterval: 50,
background: true,
begin: ({ jobId, contentLength }) => {
// Dispatch the initialization
dispatch(initializeDownload({ id, jobId, size: contentLength }));
},
progress: (result) => {
// Dispatch a progress update
dispatch(progressDownload({ id, progress: result.bytesWritten / result.contentLength }));
},
toFile: location,
});
// Await job completion
const result = await promise;
dispatch(completeDownload({ id, location, size: result.bytesWritten }));
},
);
export const removeDownloadedTrack = createAsyncThunk(
'/downloads/track/remove',
async(id: EntityId) => {
return unlink(`${DocumentDirectoryPath}/${id}.mp3`);
}
);

View File

@@ -0,0 +1,60 @@
import { createSlice, Dictionary, EntityId } from '@reduxjs/toolkit';
import { completeDownload, downloadAdapter, failDownload, initializeDownload, progressDownload, removeDownloadedTrack } from './actions';
import { DownloadEntity } from './types';
interface State {
entities: Dictionary<DownloadEntity>;
ids: EntityId[];
}
const initialState: State = {
entities: {},
ids: [],
};
const downloads = createSlice({
name: 'downloads',
initialState,
reducers: {},
extraReducers: builder => {
builder.addCase(initializeDownload, (state, action) => {
downloadAdapter.upsertOne(state, {
...action.payload,
progress: 0,
isFailed: false,
isComplete: false,
});
});
builder.addCase(progressDownload, (state, action) => {
downloadAdapter.updateOne(state, {
id: action.payload.id,
changes: action.payload
});
});
builder.addCase(completeDownload, (state, action) => {
downloadAdapter.updateOne(state, {
id: action.payload.id,
changes: {
...action.payload,
isFailed: false,
isComplete: true,
}
});
});
builder.addCase(failDownload, (state, action) => {
downloadAdapter.updateOne(state, {
id: action.payload.id,
changes: {
isComplete: false,
isFailed: true,
progress: 0,
}
});
});
builder.addCase(removeDownloadedTrack.fulfilled, (state, action) => {
downloadAdapter.removeOne(state, action.meta.arg);
});
},
});
export default downloads;

View File

@@ -0,0 +1,11 @@
import { EntityId } from '@reduxjs/toolkit';
export interface DownloadEntity {
id: EntityId;
progress: number;
isFailed: boolean;
isComplete: boolean;
size?: number;
location?: string;
jobId?: number;
}

View File

@@ -3,7 +3,6 @@ import { useSelector, TypedUseSelectorHook, useDispatch } from 'react-redux';
import AsyncStorage from '@react-native-community/async-storage';
import { persistStore, persistReducer, PersistConfig } from 'redux-persist';
import autoMergeLevel2 from 'redux-persist/es/stateReconciler/autoMergeLevel2';
// import logger from 'redux-logger';
const persistConfig: PersistConfig<AppState> = {
key: 'root',
@@ -13,10 +12,12 @@ const persistConfig: PersistConfig<AppState> = {
import settings from './settings';
import music from './music';
import downloads from './downloads';
const reducers = combineReducers({
settings,
music: music.reducer,
downloads: downloads.reducer,
});
const persistedReducer = persistReducer(persistConfig, reducers);
@@ -24,7 +25,8 @@ const persistedReducer = persistReducer(persistConfig, reducers);
const store = configureStore({
reducer: persistedReducer,
middleware: getDefaultMiddleware({ serializableCheck: false, immutableCheck: false }).concat(
// logger
// logger,
__DEV__ ? require('redux-flipper').default() : undefined,
),
});