2016-08-10 21:36:11 +02:00
|
|
|
/**
|
|
|
|
* This implements the global entities reducer.
|
|
|
|
*/
|
|
|
|
|
|
|
|
// NPM imports
|
|
|
|
import Immutable from "immutable";
|
|
|
|
|
|
|
|
// Local imports
|
|
|
|
import { createReducer } from "../utils";
|
|
|
|
|
|
|
|
// Models
|
|
|
|
import { stateRecord } from "../models/entities";
|
|
|
|
|
|
|
|
// Actions
|
|
|
|
import {
|
|
|
|
API_REQUEST,
|
|
|
|
API_FAILURE,
|
|
|
|
PUSH_ENTITIES,
|
|
|
|
INCREMENT_REFCOUNT,
|
|
|
|
DECREMENT_REFCOUNT,
|
2016-08-10 23:50:23 +02:00
|
|
|
INVALIDATE_STORE,
|
2016-08-10 21:36:11 +02:00
|
|
|
} from "../actions";
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Helper methods
|
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Update the reference counter for a given item.
|
|
|
|
*
|
|
|
|
* Do not do any garbage collection.
|
|
|
|
*
|
|
|
|
* @param state The state object to update.
|
|
|
|
* @param keyPath The keyPath to update, from the refCount key.
|
|
|
|
* @param incr The increment (or decrement) for the reference counter.
|
|
|
|
*
|
|
|
|
* @return An updated state.
|
|
|
|
*/
|
|
|
|
function updateRefCount(state, keyPath, incr) {
|
|
|
|
// Prepend refCounts to keyPath
|
|
|
|
const refCountKeyPath = Array.concat(["refCounts"], keyPath);
|
|
|
|
// Get updated value
|
|
|
|
let newRefCount = state.getIn(refCountKeyPath) + incr;
|
|
|
|
if (isNaN(newRefCount)) {
|
|
|
|
// If NaN, reference does not exist, so set it to ±1
|
|
|
|
newRefCount = Math.sign(incr);
|
|
|
|
}
|
|
|
|
// Update state
|
|
|
|
return state.setIn(refCountKeyPath, newRefCount);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Update the reference counter of a given entity, taking into account the
|
|
|
|
* nested objects.
|
|
|
|
*
|
|
|
|
* Do not do any garbage collection.
|
|
|
|
*
|
|
|
|
* @param state The state object to update.
|
|
|
|
* @param itemName The type of the entity object.
|
|
|
|
* @param id The id of the entity.
|
|
|
|
* @param entity The entity object, as Immutable.
|
|
|
|
* @param incr The increment (or decrement) for the reference counter.
|
|
|
|
*
|
|
|
|
* @return An updated state.
|
|
|
|
*/
|
|
|
|
function updateEntityRefCount(state, itemName, id, entity, incr) {
|
|
|
|
let newState = state;
|
|
|
|
let albums = null;
|
|
|
|
let tracks = null;
|
|
|
|
switch (itemName) {
|
|
|
|
case "artist":
|
|
|
|
// Update artist refCount
|
|
|
|
newState = updateRefCount(newState, ["artist", id], incr);
|
|
|
|
// Update nested albums refCount
|
|
|
|
albums = entity.get("albums");
|
|
|
|
if (Immutable.List.isList(albums)) {
|
|
|
|
albums.forEach(function (id) {
|
|
|
|
newState = updateRefCount(newState, ["album", id], incr);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
// Update nested tracks refCount
|
|
|
|
tracks = entity.get("songs");
|
|
|
|
if (Immutable.List.isList(tracks)) {
|
|
|
|
tracks.forEach(function (id) {
|
|
|
|
newState = updateRefCount(newState, ["song", id], incr);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case "album":
|
|
|
|
// Update album refCount
|
|
|
|
newState = updateRefCount(newState, ["album", id], incr);
|
|
|
|
// Update nested artist refCount
|
|
|
|
newState = updateRefCount(newState, ["artist", entity.get("artist")], incr);
|
|
|
|
// Update nested tracks refCount
|
|
|
|
tracks = entity.get("tracks");
|
|
|
|
if (Immutable.List.isList(tracks)) {
|
|
|
|
tracks.forEach(function (id) {
|
|
|
|
newState = updateRefCount(newState, ["song", id], incr);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case "song":
|
|
|
|
// Update track refCount
|
|
|
|
newState = updateRefCount(newState, ["song", id], incr);
|
|
|
|
// Update nested artist refCount
|
|
|
|
newState = updateRefCount(newState, ["artist", entity.get("artist")], incr);
|
|
|
|
// Update nested album refCount
|
|
|
|
newState = updateRefCount(newState, ["album", entity.get("album")], incr);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
// Just update the entity, no nested entities
|
|
|
|
newState = updateRefCount(newState, [itemName, id], incr);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return newState;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
function garbageCollection(state) {
|
|
|
|
let newState = state;
|
|
|
|
state.refCounts.forEach(function (refCounts, itemName) {
|
|
|
|
refCounts.forEach(function (refCount, id) {
|
|
|
|
if (refCount < 1) {
|
|
|
|
// Garbage collection
|
|
|
|
newState = newState.deleteIn(["entities", itemName, id]);
|
|
|
|
newState = newState.deleteIn(["refCounts", itemName, id]);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
return newState;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Initial state
|
|
|
|
*/
|
|
|
|
var initialState = new stateRecord();
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Reducer
|
|
|
|
*/
|
|
|
|
export default createReducer(initialState, {
|
|
|
|
[API_REQUEST]: (state) => {
|
|
|
|
return (
|
|
|
|
state
|
|
|
|
.set("isFetching", true)
|
|
|
|
.set("error", null)
|
|
|
|
);
|
|
|
|
},
|
|
|
|
[API_FAILURE]: (state, payload) => {
|
|
|
|
return (
|
|
|
|
state
|
|
|
|
.set("isFetching", false)
|
|
|
|
.set("error", payload.error)
|
|
|
|
);
|
|
|
|
},
|
|
|
|
[PUSH_ENTITIES]: (state, payload) => {
|
|
|
|
let newState = state;
|
|
|
|
|
|
|
|
// Unset error and isFetching
|
|
|
|
newState = state.set("isFetching", false).set("error", payload.error);
|
|
|
|
|
|
|
|
// Merge entities
|
|
|
|
newState = newState.mergeIn(["entities"], payload.entities);
|
|
|
|
|
|
|
|
// Increment reference counter
|
|
|
|
payload.refCountType.forEach(function (itemName) {
|
2016-08-10 23:50:23 +02:00
|
|
|
const entities = payload.entities[itemName];
|
|
|
|
for (let id in entities) {
|
|
|
|
const entity = newState.getIn(["entities", itemName, id]);
|
2016-08-10 21:36:11 +02:00
|
|
|
newState = updateEntityRefCount(newState, itemName, id, entity, 1);
|
2016-08-10 23:50:23 +02:00
|
|
|
}
|
2016-08-10 21:36:11 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
return newState;
|
|
|
|
},
|
|
|
|
[INCREMENT_REFCOUNT]: (state, payload) => {
|
|
|
|
let newState = state;
|
|
|
|
|
|
|
|
// Increment reference counter
|
|
|
|
for (let itemName in payload.entities) {
|
2016-08-10 23:50:23 +02:00
|
|
|
const entities = payload.entities[itemName];
|
|
|
|
entities.forEach(function (id) {
|
|
|
|
const entity = newState.getIn(["entities", itemName, id]);
|
2016-08-10 21:36:11 +02:00
|
|
|
newState = updateEntityRefCount(newState, itemName, id, entity, 1);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
return newState;
|
|
|
|
},
|
|
|
|
[DECREMENT_REFCOUNT]: (state, payload) => {
|
|
|
|
let newState = state;
|
|
|
|
|
|
|
|
// Decrement reference counter
|
|
|
|
for (let itemName in payload.entities) {
|
2016-08-10 23:50:23 +02:00
|
|
|
const entities = payload.entities[itemName];
|
|
|
|
entities.forEach(function (id) {
|
|
|
|
const entity = newState.getIn(["entities", itemName, id]);
|
2016-08-10 21:36:11 +02:00
|
|
|
newState = updateEntityRefCount(newState, itemName, id, entity, -1);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
// Perform garbage collection
|
|
|
|
newState = garbageCollection(newState);
|
|
|
|
|
|
|
|
return newState;
|
|
|
|
},
|
|
|
|
[INVALIDATE_STORE]: () => {
|
|
|
|
return new stateRecord();
|
2016-08-10 23:50:23 +02:00
|
|
|
},
|
2016-08-10 21:36:11 +02:00
|
|
|
});
|