Mercurial > hg > tweakathon2ios
view eventLogger.mm @ 52:89944ab3e129 tip
fix oF linker errors ios8
author | Robert Tubb <rt300@eecs.qmul.ac.uk> |
---|---|
date | Tue, 03 Feb 2015 13:18:23 +0000 |
parents | 3af380769779 |
children |
line wrap: on
line source
// // 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]; } // for RIFTATHON this replaces startLoadAll // because we can't look at logs or presets until we know user void EventLogger::onUsernameEntered(){ sessionStartTime = ofGetSystemTime(); // userName = name; done already by dialog // check for eventlog with this name string path = ofxiPhoneGetDocumentsDirectory(); ofDirectory dir(path); //only show png files dir.allowExt("json"); //populate the directory object dir.listDir(); //chek for username's logs bool userHasLogs = false; int numExistingLogs = 0; for(int i = 0; i < dir.numFiles(); i++){ string fname = dir.getName(i); if( 0 == fname.compare(0, userName.length() + 10, userName + "_eventlog_" )){ numExistingLogs++; userHasLogs = true; } } // if found, load other details such as device id loadUserDetailsFromLastLogFile(numExistingLogs); nextLogFileIndex = numExistingLogs+1; } //--------------------------------------------------------------------------- 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); } //--------------------------------------------------------------------------- void EventLogger::loadUserDetailsFromLastLogFile(int numExistingLogs){ } //--------------------------------------------------------------------------- // 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; } // new riftathon save as we go if (theEvents.size() > SAVE_CHUNK_SIZE){ saveSessionToFile(); theEvents.clear(); } //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); } // new riftathon save as we go NEVER REACHES HERE? if (theEvents.size() > SAVE_CHUNK_SIZE){ saveSessionToFile(); } //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(){ return; // the theEvents.clear(); string fname = ofxiPhoneGetDocumentsDirectory() + EVENT_LOG_FILENAME; ofFile logFile(fname,ofFile::WriteOnly); logFile << ""; 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 << "_eventlog_" << nextLogFileIndexString() << ".json"; string fname = fn.str(); //ofxiPhoneGetDocumentsDirectory() + userName + '_' + "TESTSAVE.json"; // write to file Json::Value jlogs = logsToJson(); ofFile logFile(fname,ofFile::WriteOnly); logFile << jlogs; logFile.close(); nextLogFileIndex++; theEvents.clear(); } string EventLogger::nextLogFileIndexString(){ string num = ofToString(nextLogFileIndex); string zero = ofToString(0); int numzeros = 4 - num.length(); for (int i=0; i< numzeros;i++){ num = zero + num; } return num; } //---------------------------------------------------------------------------- // this builds up a file incrementally, which can be recovered on crash //void EventLogger::appendToFile(){ // // //} //--------------------------------------------------------------------------- void EventLogger::exitAndSave(){ saveSessionToFile(); return; 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(); }