Using localForage and restoring from coldstore
This commit is contained in:
@ -5,46 +5,6 @@ import { uriSource } from '../../util/helpers';
|
||||
const spotifyActions = require('../../services/spotify/actions');
|
||||
const mopidyActions = require('../../services/mopidy/actions');
|
||||
|
||||
export function getBroadcasts() {
|
||||
return (dispatch, getState) => {
|
||||
const config = {
|
||||
method: 'GET',
|
||||
timeout: 15000,
|
||||
};
|
||||
|
||||
// Fetch the "iris_broadcasts.json" file from Gist (or "_test" for test mode)
|
||||
if (getState().ui.test_mode) {
|
||||
config.url = 'https://gist.githubusercontent.com/jaedb/cb3a5ee6909632abb2e0fe66d4c8c311/raw';
|
||||
} else {
|
||||
config.url = 'https://gist.githubusercontent.com/jaedb/b677dccf80daf3ccb2ef12e96e495677/raw';
|
||||
}
|
||||
|
||||
$.ajax(config).then(
|
||||
(response) => {
|
||||
dispatch({
|
||||
type: 'BROADCASTS_LOADED',
|
||||
broadcasts: JSON.parse(response),
|
||||
});
|
||||
},
|
||||
(xhr, status, error) => {
|
||||
dispatch(
|
||||
handleException(
|
||||
'Could not fetch broadcasts from GitHub',
|
||||
{
|
||||
config,
|
||||
xhr,
|
||||
status,
|
||||
error,
|
||||
},
|
||||
null,
|
||||
false,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
export function startSearch(search_type, query, only_mopidy = false) {
|
||||
return {
|
||||
type: 'SEARCH_STARTED',
|
||||
@ -111,6 +71,27 @@ export function cachebustHttpStream() {
|
||||
};
|
||||
}
|
||||
|
||||
export function clearStorage() {
|
||||
return {
|
||||
type: 'CLEAR_STORAGE',
|
||||
};
|
||||
}
|
||||
|
||||
export function restoreFromColdStore(item) {
|
||||
return {
|
||||
type: 'RESTORE_FROM_COLD_STORE',
|
||||
item,
|
||||
};
|
||||
}
|
||||
|
||||
export function updateColdStore(items) {
|
||||
return {
|
||||
type: 'UPDATE_COLD_STORE',
|
||||
items,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Record getters
|
||||
*
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
|
||||
import React from 'react';
|
||||
import ReactGA from 'react-ga';
|
||||
import localForage from 'localforage';
|
||||
import { arrayOf } from '../../util/arrays';
|
||||
import URILink from '../../components/URILink';
|
||||
import { uriSource, upgradeSpotifyPlaylistUris, uriType } from '../../util/helpers';
|
||||
@ -12,6 +13,7 @@ import {
|
||||
formatArtist,
|
||||
formatPlaylist,
|
||||
formatUser,
|
||||
formatSimpleObjects,
|
||||
} from '../../util/format';
|
||||
import { handleException } from './actions';
|
||||
|
||||
@ -21,9 +23,6 @@ const mopidyActions = require('../mopidy/actions.js');
|
||||
const spotifyActions = require('../spotify/actions.js');
|
||||
|
||||
const CoreMiddleware = (function () {
|
||||
/**
|
||||
* The actual middleware inteceptor
|
||||
* */
|
||||
return (store) => (next) => (action = {}) => {
|
||||
const {
|
||||
core,
|
||||
@ -32,6 +31,12 @@ const CoreMiddleware = (function () {
|
||||
spotify,
|
||||
} = store.getState();
|
||||
|
||||
window.localForage = localForage;
|
||||
|
||||
// Attach store to window variable. This enables debug tools to directly call on
|
||||
// store.getState()
|
||||
window._store = store;
|
||||
|
||||
switch (action.type) {
|
||||
case 'HANDLE_EXCEPTION':
|
||||
const state = store.getState();
|
||||
@ -343,6 +348,8 @@ const CoreMiddleware = (function () {
|
||||
break;
|
||||
|
||||
case 'LOAD_ALBUM':
|
||||
|
||||
// Load from Redux store
|
||||
if (
|
||||
!action.force_reload
|
||||
&& store.getState().core.albums[action.uri]
|
||||
@ -351,46 +358,84 @@ const CoreMiddleware = (function () {
|
||||
break;
|
||||
}
|
||||
|
||||
switch (uriSource(action.uri)) {
|
||||
case 'spotify':
|
||||
store.dispatch(spotifyActions.getAlbum(action.uri));
|
||||
// Try our cold storage
|
||||
localForage.getItem(action.uri).then((result) => {
|
||||
|
||||
if (spotify.me) {
|
||||
store.dispatch(spotifyActions.following(action.uri));
|
||||
if (result) {
|
||||
console.info(`Loading "${action.uri}" from database`);
|
||||
store.dispatch(coreActions.restoreFromColdStore(result));
|
||||
} else {
|
||||
switch (uriSource(action.uri)) {
|
||||
case 'spotify':
|
||||
store.dispatch(spotifyActions.getAlbum(action.uri));
|
||||
|
||||
if (spotify.me) {
|
||||
store.dispatch(spotifyActions.following(action.uri));
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
store.dispatch(mopidyActions.getAlbum(action.uri));
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
store.dispatch(mopidyActions.getAlbum(action.uri));
|
||||
break;
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
next(action);
|
||||
break;
|
||||
|
||||
// TODO: Relocate this
|
||||
case 'UPDATE_COLD_STORE':
|
||||
console.log(action);
|
||||
if (action.items) {
|
||||
action.items.map((item) => {
|
||||
localForage.getItem(item.uri).then((result) => {
|
||||
localForage.setItem(item.uri, { ...(result || {}), ...item });
|
||||
});
|
||||
});
|
||||
}
|
||||
break;
|
||||
case 'RESTORE_FROM_COLD_STORE':
|
||||
if (action.item) {
|
||||
store.dispatch({
|
||||
type: 'RESTORED_FROM_COLD_STORE',
|
||||
item_type: uriType(action.item.uri),
|
||||
item: action.item,
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
||||
case 'LOAD_ARTIST':
|
||||
if (
|
||||
!action.force_reload
|
||||
&& store.getState().core.artists[action.uri]
|
||||
&& store.getState().core.artists[action.uri].albums_uris
|
||||
&& store.getState().core.artists[action.uri].tracks_uris) {
|
||||
&& store.getState().core.artists[action.uri].albums
|
||||
&& store.getState().core.artists[action.uri].tracks) {
|
||||
console.info(`Loading "${action.uri}" from index`);
|
||||
break;
|
||||
}
|
||||
|
||||
switch (uriSource(action.uri)) {
|
||||
case 'spotify':
|
||||
store.dispatch(spotifyActions.getArtist(action.uri, true));
|
||||
// Try our cold storage
|
||||
localForage.getItem(action.uri).then((result) => {
|
||||
if (result) {
|
||||
console.info(`Loading "${action.uri}" from database`);
|
||||
store.dispatch(coreActions.restoreFromColdStore(result));
|
||||
} else {
|
||||
switch (uriSource(action.uri)) {
|
||||
case 'spotify':
|
||||
store.dispatch(spotifyActions.getArtist(action.uri, true));
|
||||
|
||||
if (spotify.me) {
|
||||
store.dispatch(spotifyActions.following(action.uri));
|
||||
if (spotify.me) {
|
||||
store.dispatch(spotifyActions.following(action.uri));
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
store.dispatch(mopidyActions.getArtist(action.uri));
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
store.dispatch(mopidyActions.getArtist(action.uri));
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
next(action);
|
||||
break;
|
||||
@ -529,13 +574,6 @@ const CoreMiddleware = (function () {
|
||||
|
||||
action.tracks = tracks_loaded;
|
||||
|
||||
if (artists_loaded.length > 0) {
|
||||
store.dispatch(coreActions.artistsLoaded(artists_loaded));
|
||||
}
|
||||
if (albums_loaded.length > 0) {
|
||||
store.dispatch(coreActions.albumsLoaded(albums_loaded));
|
||||
}
|
||||
|
||||
next(action);
|
||||
break;
|
||||
|
||||
@ -553,20 +591,14 @@ const CoreMiddleware = (function () {
|
||||
}
|
||||
|
||||
if (raw_album.tracks) {
|
||||
album.tracks_uris = [];
|
||||
|
||||
for (var track of raw_album.tracks) {
|
||||
if (!track.album) {
|
||||
track.album = formatSimpleObject(album);
|
||||
}
|
||||
album.tracks_uris.push(track.uri);
|
||||
tracks_loaded.push(track);
|
||||
}
|
||||
album.tracks = raw_album.tracks.map((track) => ({
|
||||
...formatTrack(track),
|
||||
album: formatSimpleObject(album)
|
||||
}));
|
||||
}
|
||||
|
||||
if (raw_album.artists) {
|
||||
album.artists_uris = arrayOf('uri', raw_album.artists);
|
||||
artists_loaded = [...artists_loaded, ...raw_album.artists];
|
||||
album.artists = formatSimpleObjects(raw_album.artists);
|
||||
}
|
||||
|
||||
albums_loaded.push(album);
|
||||
@ -581,6 +613,8 @@ const CoreMiddleware = (function () {
|
||||
store.dispatch(coreActions.tracksLoaded(tracks_loaded));
|
||||
}
|
||||
|
||||
store.dispatch(coreActions.updateColdStore(albums_loaded));
|
||||
|
||||
next(action);
|
||||
break;
|
||||
|
||||
@ -618,14 +652,14 @@ const CoreMiddleware = (function () {
|
||||
|
||||
artist = { ...artists_index[artist.uri], ...artist };
|
||||
}
|
||||
|
||||
/*
|
||||
// Migrate nested tracks objects into references to our tracks index
|
||||
if (raw_artist.tracks) {
|
||||
var tracks = formatTracks(raw_artist.tracks);
|
||||
var tracks_uris = arrayOf('uri', tracks);
|
||||
artist.tracks_uris = tracks_uris;
|
||||
tracks_loaded = [...tracks_loaded, ...tracks];
|
||||
}
|
||||
}*/
|
||||
|
||||
artists_loaded.push(artist);
|
||||
}
|
||||
@ -636,6 +670,8 @@ const CoreMiddleware = (function () {
|
||||
store.dispatch(coreActions.tracksLoaded(tracks_loaded));
|
||||
}
|
||||
|
||||
store.dispatch(coreActions.updateColdStore(artists_loaded));
|
||||
|
||||
next(action);
|
||||
break;
|
||||
|
||||
|
||||
@ -140,6 +140,46 @@ export default function reducer(core = {}, action) {
|
||||
return { ...core, users };
|
||||
|
||||
|
||||
case 'RESTORED_FROM_COLD_STORE':
|
||||
switch (action.item_type) {
|
||||
case 'artist':
|
||||
return {
|
||||
...core,
|
||||
artists: {
|
||||
...core.artists,
|
||||
[action.item.uri]: {
|
||||
...core.artists[action.item.uri],
|
||||
...action.item,
|
||||
},
|
||||
},
|
||||
};
|
||||
case 'album':
|
||||
return {
|
||||
...core,
|
||||
albums: {
|
||||
...core.albums,
|
||||
[action.item.uri]: {
|
||||
...core.albums[action.item.uri],
|
||||
...action.item,
|
||||
},
|
||||
},
|
||||
};
|
||||
case 'playlist':
|
||||
return {
|
||||
...core,
|
||||
playlists: {
|
||||
...core.playlists,
|
||||
[action.item.uri]: {
|
||||
...core.albums[action.item.uri],
|
||||
...action.item,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
default:
|
||||
return core;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an item from an index
|
||||
* */
|
||||
@ -294,12 +334,6 @@ export default function reducer(core = {}, action) {
|
||||
},
|
||||
};
|
||||
|
||||
case 'UPDATE_PINNED':
|
||||
return {
|
||||
...core,
|
||||
pinned: action.pinned,
|
||||
};
|
||||
|
||||
default:
|
||||
return core;
|
||||
}
|
||||
|
||||
6
src/js/services/localstorage/actions.js
Executable file
6
src/js/services/localstorage/actions.js
Executable file
@ -0,0 +1,6 @@
|
||||
export function restoreFromColdStore(item) {
|
||||
return {
|
||||
type: 'RESTORE_FROM_COLD_STORE',
|
||||
item,
|
||||
};
|
||||
}
|
||||
@ -264,7 +264,7 @@ const localstorageMiddleware = (function () {
|
||||
},
|
||||
);
|
||||
break;
|
||||
|
||||
/*
|
||||
case 'SUPPRESS_BROADCAST':
|
||||
var ui = storage.get('ui');
|
||||
if (ui.suppressed_broadcasts !== undefined) {
|
||||
@ -282,6 +282,7 @@ const localstorageMiddleware = (function () {
|
||||
},
|
||||
);
|
||||
break;
|
||||
*/
|
||||
|
||||
/**
|
||||
* Experimental saving of stores to localStorage
|
||||
|
||||
@ -15,6 +15,8 @@ import {
|
||||
formatPlaylist,
|
||||
formatUser,
|
||||
formatAlbum,
|
||||
formatArtists,
|
||||
formatAlbums,
|
||||
} from '../../util/format';
|
||||
import URILink from '../../components/URILink';
|
||||
import { i18n } from '../../locale';
|
||||
@ -1078,20 +1080,14 @@ export function getArtist(uri, full = false) {
|
||||
return (dispatch, getState) => {
|
||||
// Start with an empty object
|
||||
// As each requests completes, they'll add to this object
|
||||
const artist = {};
|
||||
let artist = {};
|
||||
|
||||
// We need our artist, obviously
|
||||
const requests = [
|
||||
request(dispatch, getState, `artists/${getFromUri('artistid', uri)}`, 'GET', false, true)
|
||||
.then(
|
||||
(response) => {
|
||||
Object.assign(artist, response);
|
||||
},
|
||||
(error) => {
|
||||
dispatch(coreActions.handleException(
|
||||
'Could not load artist',
|
||||
error,
|
||||
));
|
||||
artist = { ...artist, ...response };
|
||||
},
|
||||
),
|
||||
];
|
||||
@ -1102,13 +1098,7 @@ export function getArtist(uri, full = false) {
|
||||
request(dispatch, getState, `artists/${getFromUri('artistid', uri)}/top-tracks?country=${getState().spotify.country}`)
|
||||
.then(
|
||||
(response) => {
|
||||
Object.assign(artist, response);
|
||||
},
|
||||
(error) => {
|
||||
dispatch(coreActions.handleException(
|
||||
'Could not load artist\'s top tracks',
|
||||
error,
|
||||
));
|
||||
artist = { ...artist, tracks: formatTracks(response.tracks) };
|
||||
},
|
||||
),
|
||||
);
|
||||
@ -1117,47 +1107,38 @@ export function getArtist(uri, full = false) {
|
||||
request(dispatch, getState, `artists/${getFromUri('artistid', uri)}/related-artists`)
|
||||
.then(
|
||||
(response) => {
|
||||
dispatch(coreActions.artistsLoaded(response.artists));
|
||||
Object.assign(artist, { related_artists_uris: arrayOf('uri', response.artists) });
|
||||
},
|
||||
(error) => {
|
||||
dispatch(coreActions.handleException(
|
||||
'Could not load artist\'s related artists',
|
||||
error,
|
||||
));
|
||||
artist = { ...artist, related_artists: formatArtists(response.artists) };
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Run our requests
|
||||
$.when.apply($, requests).then(() => {
|
||||
dispatch(coreActions.artistLoaded(artist));
|
||||
|
||||
// Get supporting biography and imagery
|
||||
if (artist.musicbrainz_id) {
|
||||
dispatch(lastfmActions.getArtist(artist.uri, false, artist.musicbrainz_id));
|
||||
} else {
|
||||
dispatch(lastfmActions.getArtist(artist.uri, artist.name.replace('&', 'and')));
|
||||
}
|
||||
|
||||
dispatch(coreActions.artistLoaded(artist));
|
||||
|
||||
// Now go get our artist albums
|
||||
// Get all their albums
|
||||
if (full) {
|
||||
request(dispatch, getState, `artists/${getFromUri('artistid', uri)}/albums?market=${getState().spotify.country}`)
|
||||
.then(
|
||||
(response) => {
|
||||
dispatch({
|
||||
type: 'SPOTIFY_ARTIST_ALBUMS_LOADED',
|
||||
artist_uri: uri,
|
||||
data: response,
|
||||
});
|
||||
},
|
||||
(error) => {
|
||||
dispatch(coreActions.handleException(
|
||||
'Could not load artist\'s albums',
|
||||
error,
|
||||
));
|
||||
},
|
||||
);
|
||||
let albums = [];
|
||||
const fetchAlbums = (endpoint) => request(dispatch, getState, endpoint)
|
||||
.then((response) => {
|
||||
albums = [...albums, ...formatAlbums(response.items)];
|
||||
if (response.next) {
|
||||
fetchAlbums(response.next);
|
||||
} else {
|
||||
dispatch(coreActions.artistLoaded({
|
||||
uri: artist.uri,
|
||||
albums,
|
||||
}));
|
||||
}
|
||||
});
|
||||
fetchAlbums(`artists/${getFromUri('artistid', uri)}/albums?market=${getState().spotify.country}`);
|
||||
}
|
||||
});
|
||||
};
|
||||
@ -1333,21 +1314,18 @@ export function getAlbum(uri) {
|
||||
const album = {
|
||||
...formatAlbum(response),
|
||||
artists_uris: arrayOf('uri', response.artists),
|
||||
tracks_uris: arrayOf('uri', tracks),
|
||||
tracks_more: response.tracks.next,
|
||||
tracks_total: response.tracks.total,
|
||||
};
|
||||
|
||||
// add our album to all the tracks
|
||||
for (var i = 0; i < tracks.length; i++) {
|
||||
tracks[i].album = {
|
||||
album.tracks = tracks.map((track) => ({
|
||||
...track,
|
||||
album: {
|
||||
name: album.name,
|
||||
uri: album.uri,
|
||||
};
|
||||
}
|
||||
},
|
||||
}));
|
||||
|
||||
dispatch(coreActions.albumLoaded(album));
|
||||
dispatch(coreActions.tracksLoaded(tracks));
|
||||
|
||||
// now get all the artists for this album (full objects)
|
||||
// we do this to get the artist artwork
|
||||
|
||||
@ -1,5 +1,45 @@
|
||||
|
||||
import { generateGuid } from '../../util/helpers';
|
||||
import { handleException } from '../core/actions';
|
||||
|
||||
export function getBroadcasts() {
|
||||
return (dispatch, getState) => {
|
||||
const config = {
|
||||
method: 'GET',
|
||||
timeout: 15000,
|
||||
};
|
||||
|
||||
// Fetch the "iris_broadcasts.json" file from Gist (or "_test" for test mode)
|
||||
if (getState().ui.test_mode) {
|
||||
config.url = 'https://gist.githubusercontent.com/jaedb/cb3a5ee6909632abb2e0fe66d4c8c311/raw';
|
||||
} else {
|
||||
config.url = 'https://gist.githubusercontent.com/jaedb/b677dccf80daf3ccb2ef12e96e495677/raw';
|
||||
}
|
||||
|
||||
$.ajax(config).then(
|
||||
(response) => {
|
||||
dispatch({
|
||||
type: 'BROADCASTS_LOADED',
|
||||
broadcasts: JSON.parse(response),
|
||||
});
|
||||
},
|
||||
(xhr, status, error) => {
|
||||
dispatch(
|
||||
handleException(
|
||||
'Could not fetch broadcasts from GitHub',
|
||||
{
|
||||
config,
|
||||
xhr,
|
||||
status,
|
||||
error,
|
||||
},
|
||||
null,
|
||||
false,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
export function setWindowTitle(title = null, play_state = null) {
|
||||
return {
|
||||
|
||||
@ -197,6 +197,8 @@ export default function reducer(ui = {}, action) {
|
||||
}
|
||||
return { ...ui, processes };
|
||||
|
||||
case 'SUPPRESS_BROADCAST':
|
||||
return { ...ui, suppressed_broadcasts: [...(ui.suppressed_broadcasts || []), action.key] };
|
||||
|
||||
default:
|
||||
return ui;
|
||||
|
||||
Reference in New Issue
Block a user