ohif-viewer/platform/core/src/utils/progressTrackingUtils.js

327 lines
8.1 KiB
JavaScript

import makeDeferred from './makeDeferred';
/**
* Constants
*/
const TYPE = Symbol('Type');
const TASK = Symbol('Task');
const LIST = Symbol('List');
/**
* Public Methods
*/
/**
* Creates an instance of a task list
* @returns {Object} A task list object
*/
function createList() {
return objectWithType(LIST, {
head: null,
named: Object.create(null),
observers: [],
});
}
/**
* Checks if the given argument is a List instance
* @param {any} subject The value to be tested
* @returns {boolean} true if a valid List instance is given, false otherwise
*/
function isList(subject) {
return isOfType(LIST, subject);
}
/**
* Creates an instance of a task
* @param {Object} list The List instance related to this task
* @param {Object} next The next Task instance to link to
* @returns {Object} A task object
*/
function createTask(list, next) {
return objectWithType(TASK, {
list: isList(list) ? list : null,
next: isTask(next) ? next : null,
failed: false,
awaiting: null,
progress: 0.0,
});
}
/**
* Checks if the given argument is a Task instance
* @param {any} subject The value to be tested
* @returns {boolean} true if a valid Task instance is given, false otherwise
*/
function isTask(subject) {
return isOfType(TASK, subject);
}
/**
* Appends a new Task to the given List instance and notifies the list observers
* @param {Object} list A List instance
* @returns {Object} The new Task instance appended to the List or null if the
* given List instanc is not valid
*/
function increaseList(list) {
if (isList(list)) {
const task = createTask(list, list.head);
list.head = task;
notify(list, getOverallProgress(list));
return task;
}
return null;
}
/**
* Updates the internal progress value of the given Task instance and notifies
* the observers of the associated list.
* @param {Object} task The Task instance to be updated
* @param {number} value A number between 0 (inclusive) and 1 (exclusive)
* indicating the progress of the task;
* @returns {void} Nothing is returned
*/
function update(task, value) {
if (isTask(task) && isValidProgress(value) && value < 1.0) {
if (task.progress !== value) {
task.progress = value;
if (isList(task.list)) {
notify(task.list, getOverallProgress(task.list));
}
}
}
}
/**
* Sets a Task instance as finished (progress = 1.0), freezes it in order to
* prevent further modifications and notifies the observers of the associated
* list.
* @param {Object} task The Task instance to be finalized
* @returns {void} Nothing is returned
*/
function finish(task) {
if (isTask(task)) {
task.progress = 1.0;
task.awaiting = null;
Object.freeze(task);
if (isList(task.list)) {
notify(task.list, getOverallProgress(task.list));
}
}
}
/**
* Generate a summarized snapshot of the current status of the given task List
* @param {Object} list The List instance to be scanned
* @returns {Object} An object representing the summarized status of the list
*/
function getOverallProgress(list) {
const status = createStatus();
if (isList(list)) {
let task = list.head;
while (isTask(task)) {
status.total++;
if (isValidProgress(task.progress)) {
status.partial += task.progress;
if (task.progress === 1.0 && task.failed) {
status.failures++;
}
}
task = task.next;
}
}
if (status.total > 0) {
status.progress = status.partial / status.total;
}
return Object.freeze(status);
}
/**
* Adds a Task instance to the given list that waits on a given "thenable". When
* the thenable resolves the "finish" method is called on the newly created
* instance thus notifying the observers of the list.
* @param {Object} list The List instance to which the new task will be added
* @param {Object|Promise} thenable The thenable to be waited on
* @returns {Object} A reference to the newly created Task;
*/
function waitOn(list, thenable) {
const task = increaseList(list);
if (isTask(task)) {
task.awaiting = Promise.resolve(thenable).then(
function () {
finish(task);
},
function () {
task.failed = true;
finish(task);
}
);
return task;
}
return null;
}
/**
* Adds a Task instance to the given list using a deferred (a Promise that can
* be externally resolved) notifying the observers of the list.
* @param {Object} list The List instance to which the new task will be added
* @returns {Object} An object with references to the created deferred and task
*/
function addDeferred(list) {
const deferred = makeDeferred();
const task = waitOn(list, deferred.promise);
return Object.freeze({
deferred,
task,
});
}
/**
* Assigns a name to a specific task of the list
* @param {Object} list The List instance whose task will be named
* @param {Object} task The specified Task instance
* @param {string} name The name of the task
* @returns {boolean} Returns true on success, false otherwise
*/
function setTaskName(list, task, name) {
if (
contains(list, task) &&
list.named !== null &&
typeof list.named === 'object' &&
typeof name === 'string'
) {
list.named[name] = task;
return true;
}
return false;
}
/**
* Retrieves a task by name
* @param {Object} list The List instance whose task will be retrieved
* @param {string} name The name of the task to be retrieved
* @returns {Object} The Task instance or null if not found
*/
function getTaskByName(list, name) {
if (
isList(list) &&
list.named !== null &&
typeof list.named === 'object' &&
typeof name === 'string'
) {
const task = list.named[name];
if (isTask(task)) {
return task;
}
}
return null;
}
/**
* Adds an observer (callback function) to a given List instance
* @param {Object} list The List instance to which the observer will be appended
* @param {Function} observer The observer (function) that will be executed
* every time a change happens within the list
* @returns {boolean} Returns true on success and false otherwise
*/
function addObserver(list, observer) {
if (isList(list) && Array.isArray(list.observers) && typeof observer === 'function') {
list.observers.push(observer);
return true;
}
return false;
}
/**
* Removes an observer (callback function) from a given List instance
* @param {Object} list The instance List from which the observer will removed
* @param {Function} observer The observer function to be removed
* @returns {boolean} Returns true on success and false otherwise
*/
function removeObserver(list, observer) {
if (isList(list) && Array.isArray(list.observers) && list.observers.length > 0) {
const index = list.observers.indexOf(observer);
if (index >= 0) {
list.observers.splice(index, 1);
return true;
}
}
return false;
}
/**
* Utils
*/
function createStatus() {
return Object.seal({
total: 0,
partial: 0.0,
progress: 0.0,
failures: 0,
});
}
function objectWithType(type, object) {
return Object.seal(Object.defineProperty(object, TYPE, { value: type }));
}
function isOfType(type, subject) {
return subject !== null && typeof subject === 'object' && subject[TYPE] === type;
}
function isValidProgress(value) {
return typeof value === 'number' && value >= 0.0 && value <= 1.0;
}
function contains(list, task) {
if (isList(list) && isTask(task)) {
let item = list.head;
while (isTask(item)) {
if (item === task) {
return true;
}
item = item.next;
}
}
return false;
}
function notify(list, data) {
if (isList(list) && Array.isArray(list.observers) && list.observers.length > 0) {
list.observers.slice().forEach(function (observer) {
if (typeof observer === 'function') {
try {
observer(data, list);
} catch (e) {
/* Oops! */
}
}
});
}
}
/**
* Exports
*/
const progressTrackingUtils = {
createList,
isList,
createTask,
isTask,
increaseList,
update,
finish,
getOverallProgress,
waitOn,
addDeferred,
setTaskName,
getTaskByName,
addObserver,
removeObserver,
};
export default progressTrackingUtils;