Mercurial > hg > soniczoomios
view eventLogger.mm @ 49:178642d134a7 tip
xtra files
author | Robert Tubb <rt300@eecs.qmul.ac.uk> |
---|---|
date | Wed, 01 May 2013 17:34:33 +0100 |
parents | a1e75b94c505 |
children |
line wrap: on
line source
// // eventLogger.mm // oscSenderExample // // Created by Robert Tubb on 05/11/2012. // // //--------------------------------------------------------------------------- #include "eventLogger.h" #include "grid.h" extern Grid theGridView; EventLogger eventLogger; extern PresetManager presetManager; //--------------------------------------------------------------------------- void lEvent::draw(){ double size = 2.0; TwoVector pos = theGridView.coordToPixel(TwoVector(val1,val2)); // euch -rely on grid view? if(eventType == SCROLL){ ofSetColor(255,123,56); ofRect(pos.x, pos.y,size,size); }else if(eventType == EVALUATION_POINT){ size = sliderID/150.0; // slider id is the hover time. sorry sorry. if(size > 40) size = 40; ofSetColor(25,123,216); ofNoFill(); ofEllipse(pos.x, pos.y, size, size); } } //--------------------------------------------------------------------------- EventLogger::EventLogger(){ consentGiven = true; // unless told otherwise firstAppOpen loggingEnabled = true; serverConnectionOK = false; questionnaireCompleted = false; questionnaireUploaded = false; logUploadInProgress = false; ofxiPhoneDeviceType iOSdeviceType = ofxiPhoneGetDeviceType(); cout << "Device: " << iOSdeviceType << '\n'; nextUploadQty = UPLOAD_CHUNK_SIZE; // amount of data uploaded is always more than UPLOAD_CHUNK_SIZE events serverComms = [[ServerComms alloc] init]; } //--------------------------------------------------------------------------- void EventLogger::startLoadAll(){ loadExistingLogFile(ofxiPhoneGetDocumentsDirectory() + EVENT_LOG_FILENAME); sessionStartTime = ofGetSystemTime(); testConnection(); // if we have a back log of events upload them if(theEvents.size() > nextUploadQty && ! logUploadInProgress){ //try to upload uploadEventLog(true); } if(questionnaireCompleted && !questionnaireUploaded){ uploadQuestionnaire(); } //TODO if questionnaire wasn't reached but still opened then set the timer to something sensible //timer.setInteractionTime(savedInteractionTime); //timer.setOrderFromPrevious(interfaceOrder); // for now sod it, start from scratch if(!questionnaireCompleted){ cout<<"Questionnaire NOT completed - first APP open called\n"; firstEverAppOpen(); } } //--------------------------------------------------------------------------- void EventLogger::testConnection(){ Json::Value root; root["x"] = "y"; cout << "testConnection\n"; sendToServer("testConnection", root, true); } //--------------------------------------------------------------------------- // this reads the persistent log file , checks if we've used the app before and // if we've answered questionnaire or not // should just store stuff. the start up state should be handled by testAPp void EventLogger::loadExistingLogFile(const string &jsonFile){ Json::Value root; Json::Reader reader; ///////////// // read file ifstream theFile(jsonFile.c_str()); stringstream fileText; string line; if(!theFile){ cout<<"No event log file found - first APP open called\n"; firstEverAppOpen(); return; }else{ while(theFile){ theFile >> line; // cout << line; // lots!!!! fileText << line; } theFile.close(); } cout << "size of log JSON string:" << fileText.str().length() << "BYTES \n"; bool parsingSuccessful = reader.parse( fileText.str(), root ); if ( !parsingSuccessful ) { // report to the user the failure and their locations in the document. std::cout << "Failed to parse event log JSON: \n" << reader.getFormattedErrorMessages(); return; } ///////////////// // now put user deets into variables userName = root["userName"].asString(); deviceID = root["deviceID"].asLargestInt(); nextUploadNumber = root["uploadNumber"].asInt(); savedInteractionTime = root["savedInteractionTime"].asLargestInt(); questionnaireCompleted = root["questionnaireCompleted"].asBool(); questionnaireUploaded = root["questionnaireUploaded"].asBool(); interfaceOrder = root["interfaceOrder"].asInt(); // check for unuploaded evts const Json::Value jlogs = root["events"]; for ( int index = 0; index < jlogs.size(); ++index ) theEvents.push_back(lEvent(jlogs[index])); ////////////// if(questionnaireCompleted && !questionnaireUploaded){ // then read it in and upload it Json::Value JQ = root["questionnaire"]; Json::Value JArray = JQ["qAnswers"]; if(JArray.size() < 2){ cout << "Error - status of questionnaire is wierd\n"; } for ( unsigned int i = 0; i < JArray.size(); i++ ) { questionnaireAnswers.push_back(JArray[i].asInt()); } questionnaireComments = JQ["comments"].toStyledString(); //uploadQuestionnaire(); } cout << "Total interaction time: " << savedInteractionTime << '\n'; } //--------------------------------------------------------------------------- void EventLogger::firstEverAppOpen(){ nextUploadNumber = 0; deviceID = ofGetSystemTimeMicros(); savedInteractionTime = 0; questionnaireCompleted = false; questionnaireUploaded = false; } //--------------------------------------------------------------------------- void EventLogger::questionnaireAnswersObtained(vector<int> answers, const char* userComments){ questionnaireCompleted = true; questionnaireAnswers = answers; questionnaireComments = userComments; uploadQuestionnaire(); logEvent(QUESTIONNAIRE_COMPLETED); } //--------------------------------------------------------------------------- void EventLogger::uploadQuestionnaire(){ // show indicator cout << "^^^^^^^^ UPLOADING QUESTIONNAIRE ^^^^^^^^ \n"; cout << questionnaireToJson() << "\n"; sendToServer("questionnaire", questionnaireToJson(), true); } //--------------------------------------------------------------------------- bool EventLogger::sendToServer(string functionName, Json::Value jsonData, bool async = false){ Json::FastWriter writer; string jsontext = writer.write( jsonData ); // remove newline if (!jsontext.empty() && jsontext[jsontext.length()-1] == '\n') { jsontext.erase(jsontext.length()-1); } ostringstream jd; jd << jsontext; NSString *theData = [NSString stringWithUTF8String:jd.str().c_str()]; NSString *theType = [NSString stringWithUTF8String:functionName.c_str()]; if(async){ [serverComms doPostRequest:theType withData:theData]; }else{ bool success = [serverComms doSyncPostRequest:theType withData:theData]; return success; } } //----------------------------- void EventLogger::questionnaireOK(){ questionnaireUploaded = true; questionnaireComments = ""; } //----------------------------- void EventLogger::eventlogOK(){ // COMMENT THIS IF UPLAODING FROM IPAD TO XCODE // it's a bad idea to do this in another thread... theEvents.clear(); cout << "EVENT LOG UPLOAD SUCCESS\n"; nextUploadNumber++; logUploadInProgress = false; } //----------------------------- void EventLogger::testConnectionOK(){ cout << "^^^^^^^^ server connection OK ^^^^^^^^ \n"; serverConnectionOK = true; } //----------------------------- void EventLogger::questionnaireNotOK(){ cout << "XXXXX questionnaire NOT OK XXXXXXX \n"; questionnaireUploaded = false; } //----------------------------- void EventLogger::eventlogNotOK(){ // try later cout << "XXXXX event log NOT OK XXXXXXX \n"; nextUploadQty += UPLOAD_CHUNK_SIZE; logUploadInProgress = false; } //----------------------------- void EventLogger::testConnectionNotOK(){ cout << "XXXXX server connection NOT OK XXXXXXX \n"; serverConnectionOK = false; // alert? } //--------------------------------------------------------------------------- bool EventLogger::uploadEventLog(bool async){ // show indicator logUploadInProgress = true; cout << "^^^^^^^^ ATTEMPTING TO UPLOAD " << theEvents.size() << " EVENTS ^^^^^^^^ .\n"; if(!async){ bool success = sendToServer("eventlog", logsToJson(), async); if(!success){ // try later : NOPE has maximum size nextUploadQty += UPLOAD_CHUNK_SIZE; }else{ // if success - clear memory // IF UPLAODING FROM IPAD TO XCODE COMMENT OUT theEvents.clear(); cout << "UPLOAD SUCCESS\n"; nextUploadNumber++; } logUploadInProgress = false; return success; }else{ sendToServer("eventlog", logsToJson(), async); } } //---------------------------------------------------------------------------- bool eventWasInRegion(deque<lEvent>::iterator eiter, TwoVector regionTopLeft, TwoVector regionBottomRight){ if( ((*eiter).val1 > regionTopLeft.x ) && ((*eiter).val2 > regionTopLeft.y ) && ((*eiter).val1 < regionBottomRight.x ) && ((*eiter).val2 < regionBottomRight.y )){ return true; }else{ return false; } } //---------------------------------------------------------------------------- void EventLogger::clearTrail(){ eventsToDraw.clear(); } //---------------------------------------------------------------------------- vector<lEvent *> EventLogger::getEvaluationPointsInRange(const TwoVector min, const TwoVector max){ vector<lEvent *> results; if(eventsToDraw.size() <= 0){ return results; } deque<lEvent>::iterator eiter; deque<lEvent>::iterator preveiter; for(eiter = --eventsToDraw.end(); eiter >= eventsToDraw.begin(); eiter--){ if((*eiter).eventType == EVALUATION_POINT){ if( ((*eiter).val1 > min.x ) && ((*eiter).val2 > min.y ) && ((*eiter).val1 < max.x ) && ((*eiter).val2 < max.y )){ results.push_back(&(*eiter)); } } } return results; } //---------------------------------------------------------------------------- void EventLogger::drawTrail(const TwoVector min, const TwoVector max){ if(eventsToDraw.size() <= 0){ return; } deque<lEvent>::iterator eiter; deque<lEvent>::iterator preveiter; int i = 0; preveiter = --eventsToDraw.end(); TwoVector start = TwoVector(ofGetWidth()*0.5,ofGetHeight()*0.5); TwoVector end = theGridView.coordToPixel(TwoVector((*preveiter).val1,(*preveiter).val2)); ofSetColor(255,255,255,96); ofLine(start.x,start.y, end.x, end.y); for(eiter = --eventsToDraw.end(); eiter >= eventsToDraw.begin(); eiter--){ //cout << i << '\n'; if( (*eiter).eventType == SCROLL || (*eiter).eventType == EVALUATION_POINT || (*eiter).eventType == RANDOMISE || (*eiter).eventType == CHANGE_SLIDER) { i++; if(i > SCROLL_TRAIL_LENGTH){ return; } if(eventWasInRegion(eiter, min, max) || eventWasInRegion(preveiter, min, max)){ // draw a line between prev and this if(eiter != eventsToDraw.begin()){ TwoVector start = theGridView.coordToPixel(TwoVector((*preveiter).val1,(*preveiter).val2)); TwoVector end = theGridView.coordToPixel(TwoVector((*eiter).val1,(*eiter).val2)); ofSetColor(255,255,255,96); ofLine(start.x,start.y, end.x, end.y); } } preveiter = eiter; } if( (*eiter).eventType == EVALUATION_POINT){ if( ((*eiter).val1 > min.x ) && ((*eiter).val2 > min.y ) && ((*eiter).val1 < max.x ) && ((*eiter).val2 < max.y )){ // draw it (*eiter).draw(); } } } } //--------------------------------------------------------------------------- // only called when doing supervised tests void EventLogger::newUser(){ // store old stuff saveSessionToFile(); presetManager.saveSessionToFile(userName); cout<<"setup new user\n"; deleteLogs(); nextUploadNumber = 0; deviceID = ofGetSystemTimeMicros(); savedInteractionTime = 0; totalInteractionTime = 0; sessionStartTime = ofGetSystemTime(); questionnaireCompleted = false; questionnaireUploaded = false; ((testApp *)ofGetAppPtr())->showIntro(); } //--------------------------------------------------------------------------- // called from alertView OK in iViewController void EventLogger::setUsername(const char *u){ userName = u; } //--------------------------------------------------------------------------- void EventLogger::thinnedScrollEvent(lEvent newEvent){ static lEvent previousEvent(EMPTY_EVENT); // initialised as whatever static int eventCounter = 0; // if first event then log it. it won't be, but still. if(theEvents.size() == 0){ theEvents.push_back(newEvent); previousEvent = newEvent; return; } // if previous event is more than 300ms ago, or event type has changed, log both of them (lots of events on move+zoom combinations!!) int gap = newEvent.eventTime - previousEvent.eventTime; if(gap > 300){ // log previous event as a evaluation point lEvent evalEvt(EVALUATION_POINT, previousEvent.val1, previousEvent.val2, gap); theEvents.push_back(evalEvt); eventsToDraw.push_back(evalEvt); if(eventsToDraw.size() > SCROLL_TRAIL_LENGTH){ eventsToDraw.pop_front(); } // and now new event as scroll theEvents.push_back(newEvent); eventsToDraw.push_back(newEvent); if(eventsToDraw.size() > SCROLL_TRAIL_LENGTH){ eventsToDraw.pop_front(); } eventCounter = 0; }else if(eventCounter >= EVENT_THIN_FACTOR){ // otherwise only record every Nth event theEvents.push_back(newEvent); eventsToDraw.push_back(newEvent); if(eventsToDraw.size() > SCROLL_TRAIL_LENGTH){ eventsToDraw.pop_front(); } eventCounter = 0; } eventCounter++; previousEvent = newEvent; }//--------------------------------------------------------------------------- void EventLogger::thinnedSliderEvent(lEvent newEvent){ static lEvent previousEvent(EMPTY_EVENT); // initialised as whatever. hopefully won't log static int eventCounter = 0; // if first event then log it. if(theEvents.size() == 0){ theEvents.push_back(newEvent); previousEvent = newEvent; return; } // if previous event is more than 300ms ago log both of them int gap = newEvent.eventTime - previousEvent.eventTime; if(gap > 300){ // we need the PREV grid coord though, which is lost!! TwoVector c = theGridView.getCoord(); lEvent evalEvt(EVALUATION_POINT, c.x, c.y, gap); theEvents.push_back(evalEvt); eventsToDraw.push_back(evalEvt); if(eventsToDraw.size() > SCROLL_TRAIL_LENGTH){ eventsToDraw.pop_front(); } // if prev slider event not logged, log it if(theEvents.back().eventTime != previousEvent.eventTime){ theEvents.push_back(previousEvent); } // also log new slider evt theEvents.push_back(newEvent); eventCounter = 0; }else if(eventCounter >= EVENT_THIN_FACTOR){ // otherwise only record every Nth event theEvents.push_back(newEvent); eventCounter = 0; } eventCounter++; previousEvent = newEvent; } //--------------------------------------------------------------------------- void EventLogger::thinnedZoomEvent(lEvent newEvent){ static lEvent previousEvent(EMPTY_EVENT); // initialised as whatever. hopefully won't log static int eventCounter = 0; // if first event then log it. it won't be, but still. if(theEvents.size() == 0){ theEvents.push_back(newEvent); previousEvent = newEvent; return; } int gap = newEvent.eventTime - previousEvent.eventTime; if(gap > 400){ // if prev event not logged, log it if(theEvents.back().eventTime != previousEvent.eventTime){ theEvents.push_back(previousEvent); } theEvents.push_back(newEvent); eventCounter = 0; }else if(eventCounter >= EVENT_THIN_FACTOR){ theEvents.push_back(newEvent); eventCounter = 0; } eventCounter++; previousEvent = newEvent; } //----------------------------------------------------------------------------- void EventLogger::thinnedSnapEvent(lEvent nextEvent){ static lEvent previousEvent(EMPTY_EVENT); if(nextEvent.val1 != previousEvent.val1 || nextEvent.val2 != previousEvent.val2){ theEvents.push_back(nextEvent); cout << "Snapped EVENT\n"; } previousEvent = nextEvent; } //--------------------------------------------------------------------------- void EventLogger::logEvent(const leventType& evtType,const TwoVector& centre, const double& scale, const int& sliderID, const double& sliderVal){ // scroll has 2 double coords // zoom has 1 double scale // save preset has 2 coords // switch view has view type // slider change has int slider index and 1 float value // get time for key index // thinFactor if(!loggingEnabled) return; switch ( evtType ) { // data thinning here case SCROLL: thinnedScrollEvent(lEvent(evtType,centre.x,centre.y)); //theEvents.push_back(lEvent(evtType,centre.x,centre.y)); break; case ZOOM: thinnedZoomEvent(lEvent(evtType,scale)); break; case CHANGE_SLIDER: thinnedSliderEvent(lEvent(evtType,sliderVal , 0.0 , sliderID)); break; case SNAPPED_TO_PRESET: thinnedSnapEvent(lEvent(evtType,centre.x,centre.y,sliderID)); break; // non thinned data case SAVE_PRESET: theEvents.push_back(lEvent(evtType,centre.x,centre.y)); // Code break; case SAVE_DESET: theEvents.push_back(lEvent(evtType,centre.x,centre.y)); break; case SCROLL_STOPPED: theEvents.push_back(lEvent(evtType,centre.x,centre.y)); break; case SWAP_VIEW: theEvents.push_back(lEvent(evtType,0.0 , 0.0 , sliderID)); // slider ID is which view break; // these cases are move events that need to be put in events to draw case RANDOMISE: theEvents.push_back(lEvent(evtType,centre.x,centre.y)); eventsToDraw.push_back(lEvent(evtType,centre.x,centre.y)); if(eventsToDraw.size() > SCROLL_TRAIL_LENGTH){ eventsToDraw.pop_front(); } break; default: // default is just an event type with no values theEvents.push_back(lEvent(evtType)); break; } if(theEvents.size() > nextUploadQty && !logUploadInProgress){ //try to upload asynchronously uploadEventLog(true); } //sessionTime = (ofGetSystemTime() - sessionStartTime); totalInteractionTime = savedInteractionTime + (ofGetSystemTime() - sessionStartTime); // milliseconds } //-------------------------------------------------------------------- // called from newUser void EventLogger::deleteLogs(){ // the theEvents.clear(); string fname = ofxiPhoneGetDocumentsDirectory() + EVENT_LOG_FILENAME; ofFile logFile(fname,ofFile::WriteOnly); logFile << ""; logFile.close(); } //--------------------------------------------------------------------------- void EventLogger::exitAndSave(){ if(!consentGiven){ logEvent(CONSENT_DENIED); Json::Value jlogs = logsToJson(); // try to upload TODO (no - might hang and prevent exit???) uploadEventLog(true); return; } logEvent(APP_EXITED); savedInteractionTime = savedInteractionTime + (ofGetSystemTime() - sessionStartTime); // save user details string fname = ofxiPhoneGetDocumentsDirectory() + EVENT_LOG_FILENAME; // try to upload // do it sync because event list needs to be cleared to prevent saving on device uploadEventLog(false); // write to file // json without the logs that were uploaded! Json::Value jlogs = logsToJson(); ofFile logFile(fname,ofFile::WriteOnly); logFile << jlogs; logFile.close(); } //--------------------------------------------------------------------------- Json::Value EventLogger::logsToJson(){ // put all logged events into Json formatted string Json::Value root; vector<lEvent>::iterator eventIter; root["programVersion"] = PROGRAM_VERSION; root["userName"] = userName; root["deviceID"] = deviceID; root["uploadNumber"] = nextUploadNumber; root["iOSdeviceType"] = iOSdeviceType; root["savedInteractionTime"] = savedInteractionTime; root["questionnaireCompleted"] = questionnaireCompleted; root["questionnaireUploaded"] = questionnaireUploaded; if(!questionnaireUploaded) root["questionnaire"] = questionnaireToJson(); int i = 0; for(eventIter = theEvents.begin(); eventIter < theEvents.end(); eventIter++){ root["events"][i] = (*eventIter).eventToJson(); i++; } root["numEventsHere"] = i; return root; } //--------------------------------------------------------------------------- Json::Value EventLogger::questionnaireToJson(){ // put all answers into Json formatted string Json::Value root; vector<int>::iterator aIter; Json::Value questionnaire; int i = 0; for(aIter = questionnaireAnswers.begin(); aIter < questionnaireAnswers.end(); aIter++){ questionnaire[i] = (*aIter); i++; } root["qAnswers"] = questionnaire; root["comments"] = questionnaireComments; root["userName"] = userName; root["deviceID"] = deviceID; root["iOSdeviceType"] = iOSdeviceType; root["programVersion"] = PROGRAM_VERSION; return root; } //--------------------------------------------------------------------------- void EventLogger::printAll(){ cout << "-----------------ALL LOGGED EVENTS----------------- \n"; vector<lEvent>::iterator evIter; cout << logsToJson() << "\n"; cout << "---------------------QUESTIONNAIRE---------------- \n"; cout << questionnaireToJson() << "\n"; }; //--------------------------------------------------------------------------- void EventLogger::saveSessionToFile(){ string fname = ofxiPhoneGetDocumentsDirectory() + userName + '_' + EVENT_LOG_FILENAME; // write to file // json without the logs that were uploaded! Json::Value jlogs = logsToJson(); ofFile logFile(fname,ofFile::WriteOnly); logFile << jlogs; logFile.close(); } //---------------------------------------------------------------------------- // TODO this path thing vector<TwoVector> EventLogger::getRecentPath(int numEvents){ vector<TwoVector> thePath; TwoVector lastScrollPos; vector<lEvent>::iterator eventIndex; eventIndex = theEvents.end(); int numScrolls = 0; while(numScrolls < numEvents){ // go back check for scrolls, check for end of array etc. thePath.push_back(lastScrollPos); numScrolls++; } return thePath; }