Mercurial > hg > tweakathon2ios
diff eventLogger.mm @ 0:a223551fdc1f
First commit - copy from tweakathlon.
author | Robert Tubb <rt300@eecs.qmul.ac.uk> |
---|---|
date | Fri, 10 Oct 2014 11:46:42 +0100 |
parents | |
children | 36cdb73691da |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/eventLogger.mm Fri Oct 10 11:46:42 2014 +0100 @@ -0,0 +1,547 @@ +// +// eventLogger.mm +// oscSenderExample +// +// Created by Robert Tubb on 05/11/2012. +// +// + +//--------------------------------------------------------------------------- +#include "eventLogger.h" + +EventLogger eventLogger; + +//--------------------------------------------------------------------------- +EventLogger::EventLogger(){ + + consentGiven = true; // unless told otherwise firstAppOpen + loggingEnabled = true; + serverConnectionOK = false; + questionnaireCompleted = false; + questionnaireUploaded = false; + logUploadInProgress = false; + + nextUploadQty = UPLOAD_CHUNK_SIZE; // amount of data uploaded is always more than UPLOAD_CHUNK_SIZE events + serverComms = [[ServerComms alloc] init]; + +} +//--------------------------------------------------------------------------- +void EventLogger::startLoadAll(){ + + // TODO pilot doesn't check existing + //loadExistingLogFile(ofxiPhoneGetDocumentsDirectory() + EVENT_LOG_FILENAME); + + + sessionStartTime = ofGetSystemTime(); + + // TODO pilot + // testConnection(); + + // if we have a back log of events upload them + if(theEvents.size() > nextUploadQty && ! logUploadInProgress){ + //try to upload + + // TODO pilot doesn't 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(); + + // 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(); + } + + // TODO interaction time seems to be balls + 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 + nextUploadQty += UPLOAD_CHUNK_SIZE; + // increment upload number? + }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); + } +} +//--------------------------------------------------------------------------- +// only called when doing supervised tests +void EventLogger::newUser(){ + // store old stuff + + saveSessionToFile(); + 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; + +} +//--------------------------------------------------------------------------- +//new tweakathlon event logger with vector +void EventLogger::logEvent(const leventType& evtType, const vector<int> eventData){ + Poco::Mutex::ScopedLock lock(_mutex); + if(!loggingEnabled) return; + + switch ( evtType ) { + +// case CANDIDATE_PARAM_ADJUSTED: +// // TODO thinning here. maybe. was a pain in the butt. +// thinnedSliderEvent(lEvent(evtType,eventData)); +// break; + default: + // default is just an event type with vector of ints. matlab will know what they are. + + + theEvents.push_back(lEvent(evtType, eventData)); + + break; + } + + if(theEvents.size() > nextUploadQty && !logUploadInProgress){ + //try to upload asynchronously + // TODO pilot doesn't upload + //uploadEventLog(true); + } + //TODO thiswrong? + totalInteractionTime = savedInteractionTime + (ofGetSystemTime() - sessionStartTime); // milliseconds + +} +//--------------------------------------------------------------------------- +// OLD SONIC ZOOM EVT - still used for simple events with no data +void EventLogger::logEvent(const leventType& evtType){ + Poco::Mutex::ScopedLock lock(_mutex); + if(!loggingEnabled) return; + + switch ( evtType ) { + // data thinning here + case APP_LOADED: + theEvents.push_back(lEvent(evtType)); + default: + // default is just an event type with vector of ints. matlab will know what they are. + + + theEvents.push_back(lEvent(evtType)); + + } + + if(theEvents.size() > nextUploadQty && !logUploadInProgress){ + //try to upload asynchronously + // TODO pilot doesn't upload + //uploadEventLog(true); + } + //TODO thiswrong? + totalInteractionTime = savedInteractionTime + (ofGetSystemTime() - sessionStartTime); // milliseconds + +} +// UnaryPredicate +template <class T> +bool EventLogger::matchID(T thing){ + return true; +} +bool EventLogger::matchID2(){ + return true; +} +//--------------------------------------------------------------------------- +void EventLogger::thinnedSliderEvent(lEvent newEvent){ + static vector<lEvent> unloggedEventsCache; // list of all different slider events + static int eventCounter = 0; + + // if first event then log it. + if(theEvents.size() == 0){ + theEvents.push_back(newEvent); + } + + // look for last event in the cache that had this mappingID + cout << unloggedEventsCache.size() << endl; + + vector<lEvent>::iterator lastMatching; + for(lastMatching = unloggedEventsCache.begin(); + lastMatching != unloggedEventsCache.end() && ( (*lastMatching).eventData[0] != newEvent.eventData[0]); + lastMatching++); + + + if (lastMatching != unloggedEventsCache.end()){ + //cout << "matching id logevent: " << (*lastMatching).eventData[0] << " " << newEvent.eventData[0] << endl; + // we have another, check gap + int gap = newEvent.eventTime - (*lastMatching).eventTime; + if(gap > 300){ + theEvents.push_back((*lastMatching)); + + // 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; + // DELETE THAT PREVIOUS EVENT + unloggedEventsCache.erase(lastMatching); + + }else{ + // ignore for now but put in unlogged + unloggedEventsCache.push_back(newEvent); + } + + }else{ + // this event type wasnt in the cache + if(eventCounter >= EVENT_THIN_FACTOR){ // otherwise only record every Nth event + theEvents.push_back(newEvent); + eventCounter = 0; + }else{ + unloggedEventsCache.push_back(newEvent); + } + + } + // always count the new one + eventCounter++; + + + +} + +//-------------------------------------------------------------------- +// 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){ + Json::Value jlogs = logsToJson(); + // try to upload TODO (no - might hang and prevent exit???) + + // TODO pilot doesn't upload + // uploadEventLog(true); + return; + } + + savedInteractionTime = savedInteractionTime + (ofGetSystemTime() - sessionStartTime); + // save user details + string fname = ofxiPhoneGetDocumentsDirectory() + userName + '_' + EVENT_LOG_FILENAME; + + // try to upload + // do it sync because event list needs to be cleared to prevent saving on device + + // TODO for pilot store to ipad + // uploadEventLog(false); + + // write to file + // json without the logs that were uploaded! + Json::Value jlogs = logsToJson(); + ofFile logFile(fname,ofFile::WriteOnly); + logFile << jlogs; + cout << 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["savedInteractionTime"] = savedInteractionTime; + root["questionnaireCompleted"] = questionnaireCompleted; + root["questionnaireUploaded"] = questionnaireUploaded; + + // this can mess up matlab script if it doesn't find this field + 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["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(){ + stringstream fn; + fn << ofxiPhoneGetDocumentsDirectory() << userName << '_' << sessionStartTime << "_BACKUP.json"; + + string fname = fn.str(); //ofxiPhoneGetDocumentsDirectory() + userName + '_' + "TESTSAVE.json"; + + // write to file + // json without the logs that were uploaded! + Json::Value jlogs = logsToJson(); + ofFile logFile(fname,ofFile::WriteOnly); + logFile << jlogs; + logFile.close(); + +} +//---------------------------------------------------------------------------- + +// this builds up a file incrementally, which can be recovered on crash +//void EventLogger::appendToFile(){ +// +// +//}