1
0
mirror of https://github.com/psychopy/psychojs.git synced 2025-05-10 10:40:54 +00:00

various cosmetic changes

This commit is contained in:
Alain Pitiot 2018-10-08 17:07:15 +02:00
parent 965814fa90
commit cc9a4202e9
2 changed files with 5 additions and 741 deletions

View File

@ -10,7 +10,7 @@ Many studies in behavioural sciences (e.g. psychology, neuroscience, linguistics
With high-speed broadband, improved web technologies and smart devices everywhere, studies can now go online without sacrificing too much temporal precision. This is a “game changer”. Data can be collected on larger, more varied, international populations. We can study people in environments they do not find intimidating. Experiments can be run multiple times per day, without data collection becoming impractical.
The idea behind PsychoJS is to make PsychoPy experiments available online, from a web page, so participants can run them on any device equipped with a web browser such as desktops, laptops, or tablets. In some circumstance, they can even use their phone!
The idea behind PsychoJs is to make PsychoPy experiments available online, from a web page, so participants can run them on any device equipped with a web browser such as desktops, laptops, or tablets. In some circumstance, they can even use their phone!
## Getting Started
@ -29,7 +29,7 @@ Under the hood PsychoJs relies on [PixiJs](http://www.pixijs.com) to present sti
### Hosting Experiments
A convenient way to make experiment available to participants is to host them on [pavlovia.org](https://www.pavlovia.org), an open-science server under active development. PsychoPy Builder offers the possibility of uploading the experiment directly to pavlovia.
A convenient way to make experiment available to participants is to host them on [pavlovia.org](https://www.pavlovia.org), an open-science server under active development. PsychoPy Builder offers the possibility of uploading the experiment directly to pavlovia.org.
## Which PsychoPy Components are supported by PsychoJs?
@ -38,11 +38,11 @@ PsychoJs currently supports the following Components:
### Stimuli:
* ImageStim
* TextStim
* BaseShapeStim
* BaseShapeStim (Polygon)
* Rect
* Sound (tones and tracks)
### Event:
### Events:
* Mouse
* Keyboard
@ -50,7 +50,7 @@ We are constantly adding new Components and are regularly updating this list.
## Authors
The PsychoJs library is written and maintained by Ilixa Ltd. (http://www.ilixa.com). The PsychoPy Builder's javascript code generator is built and maintained by the creators of PsychoPy, at the [University of Nottingham](https://www.nottingham.ac.uk). Both efforts are generously supported by the Wellcome Trust (https://wellcome.ac.uk).
The PsychoJs library is written and maintained by Ilixa Ltd. (http://www.ilixa.com). The PsychoPy Builder's javascript code generator is built and maintained by the creators of PsychoPy, at the University of Nottingham (https://www.nottingham.ac.uk). Both efforts are generously supported by the Wellcome Trust (https://wellcome.ac.uk).
## License

View File

@ -1,736 +0,0 @@
/**
* I/O component of PsychoJS
*
*
* This file is part of the PsychoJS javascript engine of PsychoPy.
* Copyright (c) 2018 Ilixa Ltd. (www.ilixa.com)
*
* Distributed under the terms of the GNU General Public License (GPL).
*/
import {PsychObject} from './PsychObject';
import {PsychoJS} from './PsychoJS';
import {Scheduler} from './Scheduler';
import {Clock} from './Clock';
import * as util from './util';
/**
* Resource manager
*
* <p>The resource manager synchronously or asynchronously uploads resources
* to and from a local or distant data repository, possibly via an experiment server.</p>
* <p>Note: The parameters are set in the [set function]{@link psychoJS.io.ResourceManager#set}.</p>
*
* @constructor
*/
export class ResourceManager extends PsychObject
{
static Event = {
RESOURCE: Symbol.for('RESOURCE'),
STATUS: Symbol.for('STATUS')
};
static Status = {
READY: Symbol.for('READY'),
REGISTERING: Symbol.for('REGISTERING'),
BUSY: Symbol.for('BUSY'),
ERROR: Symbol.for('ERROR')
};
static Type = {
PUBLIC: Symbol.for('PUBLIC'),
PRIVATE: Symbol.for('PRIVATE'),
};
static Repository = {
OSF: Symbol.for('OSF'),
EXPERIMENT_SERVER: Symbol.for('EXPERIMENT_SERVER'),
};
get status() { return this._status; }
/**
* constructor
*/
constructor({
psychoJS,
repository = ResourceManager.Repository.EXPERIMENT_SERVER,
projectId,
projectName,
projectContributor,
type = ResourceManager.Type.PUBLIC,
username,
password,
clock = new Clock(),
autoLog = false
} = {})
{
super(psychoJS);
this._addAttributes(ResourceManager, repository, projectId, projectName, projectContributor, type, username, password, clock, autoLog);
this._status = ResourceManager.Status.READY;
// resources:
this._experimentServerResourceDirectory = null;
this._resources = {};
// OSF specific:
this._OsfUrl = 'https://api.osf.io/v2/';
this._CorsProxyUrl = ''; // e.g. 'https://cors-anywhere.herokuapp.com/'
this._OsfAjaxSettings = {type: "GET", async: true, crossDomain: true, dataType: 'json'};
}
/**
* Set the resource manager status.
*
* <p> Note: the status callback function is called, if it has been previously set with
* [setStatusCallback]{@link psychoJS.io.ResourceManager#setStatusCallback}.</p>
* @param {(ResourceManager.Status.READY|ResourceManager.Status.REGISTERING|ResourceManager.Status.BUSY|ResourceManager.Status.ERROR)} status - the new status
*/
setStatus(status) {
var errorPrefix = '{ "function" : "io.ResourceManager.setStatus", "context" : "when changing the status of the resource manager", '
+ '"error" : ';
// check status:
if (typeof status !== 'symbol')
throw errorPrefix + '"unknown status: ' + JSON.stringify(status) + '" }';
else {
let strStatus = Symbol.keyFor(status);
if (!ResourceManager.Status.hasOwnProperty(strStatus))
throw errorPrefix + '"unknown status: ' + strStatus + '" }';
}
this._status = status;
// inform status listeners:
this.emit(ResourceManager.Event.STATUS, this._status);
return this._status;
}
/**
* Reset the resource manager status to ResourceManager.Status.READY.
*
* @return {ResourceManager.Status.READY} the new status
*/
resetStatus() {
return this.setStatus(ResourceManager.Status.READY);
}
/**
* Schedule the registration of all available resources for this experiment.
*
* <p>Note: The scheduler will wait for the registration to complete before moving onto the next task.</p>
*
* @param {psychoJS.Scheduler} scheduler - the registration [scheduler] {@link psychoJS.Scheduler}
*/
scheduleRegistration(scheduler) {
this._RegistrationComponent = [];
scheduler.add(this.Loop(this, this._RegistrationComponent, this._registerAvailableResources));
}
/**
* Register all available resources for this experiment.
*
* <p>registerAvailableResources first queries the list of resources from either
* the OSF server or the experiment server, before registering each of them
* with this resource manager.</p>
*
* <p>Note: We assume that the server.php file is in the same directory on the
* experiment server as the experiment html file itself.</p>
*
* @param {psychoJS.io.ResourceManager} resourceManager - the [resource manager]{@link psychoJS.io.ResourceManager}
* @param {Object} component - dummy component used by the [Loop function]{@link psychoJS.io.ResourceManager#Loop} to block
* the scheduler passed to [scheduleRegistration]{@link psychoJS.io.ResourceManager#scheduleRegistration} until the registration has completed
* @param {Object} [arg] - argument (currently unused)
*
* @throws {String} Throws a JSON string exception if the registration failed.
*/
_registerAvailableResources(resourceManager, component, arg) {
var errorPrefix = '{ "origin" : "ResourceManager.registerAvailableResources", "context" : "when registering all available resources", '
+ '"error" : ';
resourceManager.emit(ResourceManager.Event.RESOURCE, '{ "message" : "resource registration started" }');
resourceManager.setStatus(ResourceManager.Status.REGISTERING);
// query the list of resources directly from the OSF server:
if (resourceManager.repository === ResourceManager.Repository.OSF) {
// TODO
}
// query the list of resources from the experiment server:
else if (resourceManager.repository === ResourceManager.Repository.EXPERIMENT_SERVER)
{
$.get(resourceManager._psychoJS.config.psychoJsManager.URL, {
'command': 'list_resources',
'experimentFullPath': resourceManager._psychoJS.config.experiment.fullpath
})
.then(
function (result) {
try {
var json = JSON.parse(result);
} catch (exception) {
resourceManager.setStatus(ResourceManager.Status.ERROR);
// JSON.parse will throw a SyntaxError if result is not a JSON string
// this might happens if php is not available on the server running server.php,
// in which case an HTTP POST request to server.php returns the code of server.php
throw errorPrefix + '"unexpected answer from the experiment server", "stack" : ' + util.getErrorStack() + ' }';
}
if ('resources' in json)
{
resourceManager._experimentServerResourceDirectory = json.resourceDirectory;
var nbResource = json.resources.length;
for (var i = 0; i < nbResource; i++) {
resourceManager._registerResource(json.resources[i]);
}
resourceManager.emit(ResourceManager.Event.RESOURCE, '{ "message" : "all resources registered", "number" : ' + nbResource.toString() + ' }');
resourceManager.setStatus(ResourceManager.Status.READY);
// leave the generic loop:
if (typeof component !== 'undefined')
component.status = PsychoJS.Status.FINISHED;
} else {
resourceManager.setStatus(ResourceManager.Status.ERROR);
throw errorPrefix + $.trim(result) + ', "stack" : ' + util.getErrorStack() + ' }';
}
},
function (error){
resourceManager.setStatus(ResourceManager.Status.ERROR);
if ('statusText' in error)
throw errorPrefix + '"' + $.trim(error.statusText) + '", "stack" : ' + util.getErrorStack() + ' }';
else
throw errorPrefix + error + ', "stack" : ' + util.getErrorStack() + ' }';
}
);
}
}
/**
* Register a resource.
*
* <p>Note: the [callback function]{@link psychoJS.io.ResourceManager#setResourceCallback} is called with
* the following stringified json object: <blockquote>{"message" : "resource registered", "resource" : "&lt;resource name&gt;"}</blockquote></p>
*
* @param {string} resourceName - name of the resource to be registered
*/
_registerResource(resourceName) {
this._resources[resourceName] = undefined;
this.emit(ResourceManager.Event.RESOURCE, '{ "message" : "resource registered", "resource" : "' + resourceName + '" }');
}
/**
* Query the value of a resource.
*
* @param {string} name of the requested resource
* @return {Object} value of the resource or exception if resource is unknown
*/
getResource(resourceName) {
var errorPrefix = '{ "origin" : "io.ResourceManager.getResource", "context" : "when getting resource", "error" : ';
if (!this._resources.hasOwnProperty(resourceName)) {
throw errorPrefix + '"unknown resource: ' + resourceName + '", "stack" : ' + util.getErrorStack() + ' }';
}
return this._resources[resourceName];
}
/**
* Schedule the asynchronous download of the registered resources.
*
* <p>Note: The scheduler will wait for the download of all registered
* resources to be initiated (rather than completed) before moving onto the next task.</p>
*
* @param {Object} scheduler - the [resource scheduler]{@link psychoJS.Scheduler}
**/
scheduleDownload(scheduler) {
// download resources from OSF:
if (this.repository === ResourceManager.Repository.OSF)
{
// if project is private, we need to authenticate:
if (this.type === ResourceManager.Status.PRIVATE) {
// authenticate and get token:
this._Authenticate = [];
scheduler.add(this.Loop(this, this._Authenticate, OSFAuthenticate));
//this._Authenticate.status = PsychoJS.Status.NOT_STARTED;
//scheduler.add(AuthenticateLoop(this));
}
/* DEPRECATED: we now use projectID by default
// get project ID:
this._ProjectIDComponent = [];
scheduler.add(this.Loop(this, this._ProjectIDComponent, OSFProjectID));
*/
// get storage provider:
this._StorageProviderComponent = [];
scheduler.add(this.Loop(this, this._StorageProviderComponent, this.OSFStorageProvider));
// get download links for all resources:
this._downloadLinkDictionary = [];
this._DowloadLinkComponent = [];
scheduler.add(this.Loop(this, this._DowloadLinkComponent, this.OSFDownloadLink));
// schedule download of resources:
this._DowloadResourceComponents = {};
for (resourceName in this._resources)
if (this._resources.hasOwnProperty(resourceName)) {
this._DowloadResourceComponents[resourceName] = [];
scheduler.add(this.Loop(this, this._DowloadResourceComponents[resourceName], this.OSFDownloadResource, resourceName));
}
}
// download resources from experiment server:
else if (this.repository === ResourceManager.Repository.EXPERIMENT_SERVER)
{
// schedule download of resources:
this._DowloadResourceComponents = [];
scheduler.add(this.Loop(this, this._DowloadResourceComponents, this.EXPDownloadResources));
}
}
/**
* Get the experimenter's authentication token for the project on OSF
*
* @param {psychoJS.io.ResourceManager} resourceManager - the [resource manager]{@link psychoJS.io.ResourceManager}
* @param {Object} component - dummy component used by the [Loop function]{@link psychoJS.io.ResourceManager#Loop} to block
* the scheduler passed to [scheduleRegistration]{@link psychoJS.io.ResourceManager#scheduleRegistration} until the download has completed
* @param {Object} [arg] - argument (currently unused)
*/
OSFAuthenticate(resourceManager, component, arg) {
resourceManager.emit(ResourceManager.Event.RESOURCE, '{ "message" : "getting OSF authentication token" }');
resourceManager.setStatus(ResourceManager.Status.BUSY);
var errorPrefix = '{ "function" : "io.ResourceManager.OSFAuthenticate", "context" : "when getting authentication token from OSF", "error" : ';
$.ajax({
type: "POST",
url: resourceManager._CorsProxyUrl + resourceManager._OsfUrl + 'tokens/',
async: true,
headers: {
// see the following URL for the need for authentication details to be sent preemptively, in the headers:
// http://stackoverflow.com/questions/5507234/how-to-use-basic-auth-with-jquery-and-ajax/11960692#11960692
"Authorization" : "Basic " + btoa(resourceManager.username + ":" + resourceManager.password)
},
data: '{ "data" : {"type" : "tokens" , "attributes" : {"name" : "' + resourceManager.projectName + '", "scopes": "osf.full_write"} } }',
contentType: 'application/json; charset=utf-8',
dataType: 'json',
crossDomain: true,
}).then(
function (result) {
resourceManager._tokenID = result.data.attributes.token_id;
if (resourceManager._psychoJS.debug) console.log('\tgot token: ' + resourceManager._tokenID);
// update ajax setting with token:
this._OsfAjaxSettings = {type: "GET", async: true, headers: { "Authorization" : "Bearer " + resourceManager._tokenID }, crossDomain: true, dataType: 'json'};
resourceManager.setStatus(ResourceManager.Status.READY);
// leave the generic loop:
component.status = PsychoJS.Status.FINISHED;
},
function (error) {
resourceManager.setStatus(ResourceManager.Status.ERROR);
throw errorPrefix + '"' + error + '", "stack" : ' + util.getErrorStack() + ' }';
}
);
}
/**
* Get the ID of the project on OSF
*
* @param {psychoJS.io.ResourceManager} resourceManager - the [resource manager]{@link psychoJS.io.ResourceManager}
* @param {Object} component - dummy component used by the [Loop function]{@link psychoJS.io.ResourceManager#Loop} to block
* the scheduler passed to [scheduleRegistration]{@link psychoJS.io.ResourceManager#scheduleRegistration} until the download has completed
* @param {Object} [arg] - argument (currently unused)
*/
OSFProjectID(resourceManager, component, arg) {
resourceManager.emit(ResourceManager.Event.RESOURCE, '{ "message" : "getting OSF project ID" }');
resourceManager.setStatus(ResourceManager.Status.BUSY);
var errorPrefix = '{ "function" : "io.ResourceManager.OSFProjectID", "context" : "when getting project ID from OSF", "error" : ';
resourceManager._OsfAjaxSettings.url = resourceManager._CorsProxyUrl + resourceManager._OsfUrl + 'nodes/?filter[title]=' + resourceManager.projectName;
$.ajax(resourceManager._OsfAjaxSettings)
.then(
function (result) {
resourceManager._projectId = result.data[1].id;
if (resourceManager._psychoJS.debug) console.log("\tgot project ID: " + resourceManager._projectId);
resourceManager.emit(ResourceManager.Event.RESOURCE, '{ "message" : "got OSF project ID", "projectID" : "' + resourceManager._projectId + '" }');
resourceManager.setStatus(ResourceManager.Status.READY);
// leave the generic loop:
component.status = PsychoJS.Status.FINISHED;
},
function (error) {
resourceManager.setStatus(ResourceManager.Status.ERROR);
throw errorPrefix + '"' + error + '", "stack" : ' + util.getErrorStack() + ' }';
}
);
}
/**
* Get the storage provider of the project on OSF
*
* @param {psychoJS.io.ResourceManager} resourceManager - the [resource manager]{@link psychoJS.io.ResourceManager}
* @param {Object} component - dummy component used by the [Loop function]{@link psychoJS.io.ResourceManager#Loop} to block
* the scheduler passed to [scheduleRegistration]{@link psychoJS.io.ResourceManager#scheduleRegistration} until the download has completed
* @param {Object} [arg] - argument (currently unused)
*/
OSFStorageProvider(resourceManager, component, arg) {
resourceManager._resourceCallback('{ "message" : "getting OSF storage provider" }');
resourceManager.setStatus(ResourceManager.Status.BUSY);
var errorPrefix = '{ "function" : "io.ResourceManager.OSFStorageProvider", "context" : "when getting storage provider from OSF", "error" : ';
resourceManager._OsfAjaxSettings.url = resourceManager._CorsProxyUrl + resourceManager._OsfUrl + 'nodes/' + resourceManager.projectId + '/files/';
$.ajax(resourceManager._OsfAjaxSettings)
.then(
function (result){
resourceManager._storageProviderURL = result.data[0].relationships.files.links.related.href;
if (resourceManager._psychoJS.debug) console.log("\tgot storage provider: " + resourceManager._storageProviderURL);
resourceManager.setStatus(ResourceManager.Status.READY);
// leave the generic loop:
component.status = PsychoJS.Status.FINISHED;
},
function (error){
resourceManager.setStatus(ResourceManager.Status.ERROR);
throw errorPrefix + '"' + error + '", "stack" : ' + util.getErrorStack() + ' }';
}
);
}
/**
* Get the download links for the registered resources directly from OSF
* (without going through the experiment server).
*
* @param {psychoJS.io.ResourceManager} resourceManager - the [resource manager]{@link psychoJS.io.ResourceManager}
* @param {Object} component - dummy component used by the [Loop function]{@link psychoJS.io.ResourceManager#Loop} to block
* the scheduler passed to [scheduleRegistration]{@link psychoJS.io.ResourceManager#scheduleRegistration} until the download has completed
* @param {Object} [arg] - argument (currently unused)
*/
OSFDownloadLink(resourceManager, component, arg) {
resourceManager._resourceCallback('{ "message" : "getting OSF download links" }');
resourceManager.setStatus(ResourceManager.Status.BUSY);
var errorPrefix = '{ "function" : "io.ResourceManager.OSFDownloadLink", "context" : "when getting download links from OSF", "error" : ';
resourceManager._OsfAjaxSettings.url = resourceManager._CorsProxyUrl + resourceManager._storageProviderURL;
$.ajax(resourceManager._OsfAjaxSettings)
.then(
function (result){
for (var i = 0; i < result.data.length; i++) {
var name = result.data[i].attributes.name;
resourceManager._downloadLinkDictionary[name] = result.data[i].links.download;
if (resourceManager._psychoJS.debug) console.log("\tgot download link for resource '" + name + "' : " + resourceManager._downloadLinkDictionary[name]);
}
resourceManager.setStatus(ResourceManager.Status.READY);
// leave the generic loop:
component.status = PsychoJS.Status.FINISHED;
},
function (error){
resourceManager.setStatus(ResourceManager.Status.ERROR);
throw errorPrefix + '"' + error + '", "stack" : ' + util.getErrorStack() + ' }';
}
);
}
/**
* Download resources directly from OSF (without going through the experiment server).
*
* <p>Note: we assume that the experiment server's resources subdirectory is in the same directory as
* the experiment html file.</p>
*
* @param {psychoJS.io.ResourceManager} resourceManager - the [resource manager]{@link psychoJS.io.ResourceManager}
* @param {Object} component - dummy component used by the [Loop function]{@link psychoJS.io.ResourceManager#Loop} to block
* the scheduler passed to [scheduleDownload]{@link psychoJS.io.ResourceManager#scheduleDownload} until the download has completed
* @param {Object} arg - index of the resource in the resource name array
*/
OSFDownloadResource(resourceManager, component, arg) {
var resourceName = arg;
resourceManager._resourceCallback('{ "message" : "downloading resource", "resource" : "' + resourceName + '" }');
resourceManager.setStatus(ResourceManager.Status.BUSY);
resourceManager._OsfAjaxSettings.url = resourceManager._CorsProxyUrl + resourceManager._downloadLinkDictionary[resourceName];
$.ajax(resourceManager._OsfAjaxSettings)
.then(
function (result){
console.log(result);
},
// we get a parser error with cors-anywhere, but we still get the file in error.responseText
function (error){
resourceManager._resources[resourceName] = error.responseText;
resourceManager._resourceCallback('{ "message" : "resource downloaded", "resource" : "' + resourceName + '" }');
resourceManager.setStatus(ResourceManager.Status.READY);
if (resourceManager._psychoJS.debug) {
console.log('\tgot file:');
console.log(resourceManager._resources[resourceName]);
}
// leave the generic loop:
component.status = PsychoJS.Status.FINISHED;
}
);
}
/**
* Download resources from the experiment server.
*
* <p>Note: we assume that the experiment server's resources subdirectory is in the same directory as
* the experiment html file.</p>
*
* @param {ResourceManager} resourceManager - the [resource manager]{@link ResourceManager}
* @param {Object} component - dummy component used by the [Loop function]{@link ResourceManager#Loop} to block
* the scheduler passed to [scheduleRegistration]{@link ResourceManager#scheduleRegistration} until the downloads have been initiated
* @param {Object} [arg] - argument (currently unused)
*/
EXPDownloadResources(resourceManager, component, arg) {
resourceManager.setStatus(ResourceManager.Status.BUSY);
// set-up preload queue
resourceManager._nbLoadedResources = 0;
resourceManager.resourceQueue = new createjs.LoadQueue(true);
resourceManager.resourceQueue.addEventListener("filestart", function(event) {
resourceManager.emit(ResourceManager.Event.RESOURCE, '{ "message" : "downloading resource", "resource" : "' + event.item.id + '" }');
});
// note: strangely, possibly because of timing, the value of the resource
// may not be available immediately upon the firing of "fileload", we have to
// get it upon the firing of "complete", instead.
resourceManager.resourceQueue.addEventListener("fileload", function(event) {
++resourceManager._nbLoadedResources;
resourceManager.emit(ResourceManager.Event.RESOURCE, '{ "message" : "resource downloaded", "resource" : "' + event.item.id + '" }');
});
// loading completed: we get the value of the resources and exit the generic Loop
resourceManager.resourceQueue.addEventListener("complete", function(event) {
// get the values of all resources:
for (var resourceName in resourceManager._resources)
if (resourceManager._resources.hasOwnProperty(resourceName)) {
resourceManager._resources[resourceName] = resourceManager.resourceQueue.getResult(resourceName, false); // true: load raw result
}
// clean house and leave Loop:
resourceManager.emit(ResourceManager.Event.RESOURCE, '{ "message" : "all resources downloaded", "number" : ' + resourceManager._nbLoadedResources.toString() + ' }');
resourceManager.resourceQueue.destroy();
resourceManager.setStatus(ResourceManager.Status.READY);
});
// error: we throw an exception
resourceManager.resourceQueue.addEventListener("error", function(event) {
resourceManager.setStatus(ResourceManager.Status.ERROR);
throw '{ "function" : "io.ResourceManager.EXPDownloadResources", "context" : "when downloading resource: ' + event.data.id + '", "error" : "' + event.title + '", "stack" : ' + util.getErrorStack() + ' }';
});
// queue the resources:
for (var resourceName in resourceManager._resources)
if (resourceManager._resources.hasOwnProperty(resourceName)) {
// loading type depends on extension:
var resourceExtension = resourceName.split('.').pop();
if (['csv', 'odp', 'xls', 'xlsx'].indexOf(resourceExtension) > -1)
{
// force binary for xls and xlsx:
resourceManager.resourceQueue.loadFile({id: resourceName, src: resourceManager._experimentServerResourceDirectory + "/" + resourceName, type: createjs.Types.BINARY}, false);
} else {
// let createjs decide the type for the other extensions:
resourceManager.resourceQueue.loadFile({id: resourceName, src: resourceManager._experimentServerResourceDirectory + "/" + resourceName}, false);
}
}
// start loading:
resourceManager.resourceQueue.load();
component.status = PsychoJS.Status.FINISHED;
}
/**
* Upload session information and experiment data to OSF via the experiment server.
*
* <p>Sends the session information and experiment data to the experiment server using a POST
* request and instruct it to upload them to the OSF repository.</p>
*
* <p>Note: we assume that the server.php file is in the same directory on the
* experiment server as the experiment html file itself.</p>
*
* @param {Object} session - session information (e.g. experiment name, participant name, etc.)
* @param {{('RESULT'|'LOG')}} dataType - type of the data to be saved
* @param {Object} data - data to be saved (e.g. a .csv string)
* @return {Object} JSON string OSF representation of the file to which the data was saved
*/
OSFEXPUploadData(session, dataType, data) {
var errorPrefix = '{ "function" : "io.ResourceManager.OSFEXPUploadData", "context" : "when uploading data to OSF via the experiment server", '
+ '"error" : ';
this.setStatus(ResourceManager.Status.BUSY);
if (['RESULT', 'LOG'].indexOf(dataType) == -1) {
this.setStatus(ResourceManager.Status.ERROR);
throw errorPrefix + '"unknown data type: ' + dataType + '", "stack" : ' + util.getErrorStack() + ' }';
}
var self = this;
$.post('./server.php',
{'command' : 'OSF_UPLOAD',
'session' : JSON.stringify(session),
'dataType' : dataType,
'data' : data})
.then(
function (result) {
try {
var json = JSON.parse(result);
} catch (exception) {
self.setStatus(ResourceManager.Status.ERROR);
// JSON.parse will throw a SyntaxError if result is not a JSON string
// this might happens if php is not available on the server running server.php,
// in which case an HTTP POST request to server.php returns the code of server.php
// or if the experiment server ran into an error.
if (self._psychoJS.debug) console.log(result);
throw errorPrefix + '"unexpected answer from the experiment server", "stack" : ' + util.getErrorStack() + ' }';
}
if ('representation' in json) {
self.setStatus(ResourceManager.Status.READY);
return result;
} else {
self.setStatus(ResourceManager.Status.ERROR);
throw errorPrefix + $.trim(result) + ', "stack" : ' + util.getErrorStack() + ' }';
}
},
function (error) {
self.setStatus(ResourceManager.Status.ERROR);
if ('statusText' in error)
throw errorPrefix + '"' + $.trim(error.statusText) + '", "stack" : ' + util.getErrorStack() + ' }';
else
throw errorPrefix + error + ', "stack" : ' + util.getErrorStack() + ' }';
}
);
}
/**
* Upload session information and experiment data to the experiment server.
*
* <p>Sends the session information and experiment data to the experiment server using a POST
* request.</p>
*
* <p>Note: we assume that the server.php file is in the same directory on the
* experiment server as the experiment html file itself.</p>
*
* @param {Object} session - session information (e.g. experiment name, participant name, etc.)
* @param {{('RESULT'|'LOG')}} dataType - type of the data to be saved
* @param {Object} data - data to be saved (e.g. a .csv string)
* @return {Object} JSON string representation of the file to which the data was saved
*/
EXPUploadData(session, dataType, data) {
var errorPrefix = '{ "origin" : "ResourceManager.EXPUploadData", "context" : "when uploading participant\' results to the server", "error" : ';
this.setStatus(ResourceManager.Status.BUSY);
if (['RESULT', 'LOG'].indexOf(dataType) == -1) {
this.setStatus(ResourceManager.Status.ERROR);
throw errorPrefix + '"unknown data type: ' + dataType + '", "stack" : ' + util.getErrorStack() + ' }';
}
let queryData = {
'experimentFullPath': this._psychoJS.config.experiment.fullpath,
'session' : JSON.stringify(session),
'dataType' : dataType,
'data' : data
};
const gitlabConfig = this._psychoJS.config.gitlab;
if (typeof gitlabConfig !== 'undefined' && typeof gitlabConfig.projectId !== 'undefined')
queryData.projectId = gitlabConfig.projectId;
var self = this;
$.post(this._psychoJS.config.psychoJsManager.URL + '?command=save_results', queryData)
.then(
function (result) {
try {
var json = JSON.parse(result);
} catch (exception) {
self.setStatus(ResourceManager.Status.ERROR);
// JSON.parse will throw a SyntaxError if result is not a JSON string
// this might happens if php is not available on the server running server.php,
// in which case an HTTP POST request to server.php returns the code of server.php
throw errorPrefix + '"unexpected answer from the server", "stack" : ' + util.getErrorStack() + ' }';
}
if ('error' in json) {
self.setStatus(ResourceManager.Status.ERROR);
throw errorPrefix + $.trim(result) + ', "stack" : ' + util.getErrorStack() + ' }';
} else {
self.setStatus(ResourceManager.Status.READY);
return result;
}
},
function (error) {
self.setStatus(ResourceManager.Status.ERROR);
if ('statusText' in error)
throw errorPrefix + '"' + $.trim(error.statusText) + '", "stack" : ' + util.getErrorStack() + ' }';
else
throw errorPrefix + error + ', "stack" : ' + util.getErrorStack() + ' }';
}
);
}
/**
* Generic loop waiting for an asynchronous resource operation to finish
*
* @param {psychoJS.io.ResourceManager} resourceManager - the [resource manager]{@link psychoJS.io.ResourceManager}
* @param {Object} component - dummy component used to block a scheduler, e.g. one passed to
* [scheduleRegistration]{@link psychoJS.io.ResourceManager#scheduleRegistration}, until 'resourceFunction' has completed
* @param {Object} resourceFunction - the potentially asynchronous function, the end of which Loop is waiting for
* @param {Object} [arg] - argument passed to 'resourceFunction'
**/
Loop(resourceManager, component, resourceFunction, arg)
{
component.status = PsychoJS.Status.NOT_STARTED;
let localArg = arg;
return () => {
// get current time
const t = resourceManager.clock.getTime();
if (t >= 0.0 && component.status === PsychoJS.Status.NOT_STARTED) {
// keep track of start time/frame for later
component.tStart = t; // underestimates by a little under one frame
component.status = PsychoJS.Status.STARTED;
resourceFunction(resourceManager, component, localArg);
}
// check for quit (the Esc key)
if (resourceManager._psychoJS.experiment.experimentEnded || resourceManager._psychoJS.eventManager.getKeys({keyList:["escape"]}).length > 0) {
resourceManager.resetStatus();
resourceManager._psychoJS.quit('The [Escape] key was pressed. Goodbye!');
}
// repeat until the resourceFunction has changed the status to FINISHED:
if (component.status === PsychoJS.Status.FINISHED)
return Scheduler.Event.NEXT;
else
return Scheduler.Event.FLIP_REPEAT;
};
}
}