rt300@0: // rt300@0: // eventLogger.mm rt300@0: // oscSenderExample rt300@0: // rt300@0: // Created by Robert Tubb on 05/11/2012. rt300@0: // rt300@0: // rt300@4: rt300@8: //--------------------------------------------------------------------------- rt300@0: #include "eventLogger.h" rt300@1: rt300@1: EventLogger eventLogger; rt300@29: rt300@8: //--------------------------------------------------------------------------- rt300@1: EventLogger::EventLogger(){ rt300@14: //QuestionnaireViewController * questionnaireViewController; rt300@8: rt300@25: consentGiven = true; // unless told otherwise by introView rt300@8: loggingEnabled = true; rt300@22: serverConnectionOK = false; rt300@22: questionnaireCompleted = false; rt300@22: questionnaireUploaded = false; rt300@27: currentHTTPRequestID = -1; rt300@27: logUploadInProgress = false; rt300@22: ofxiPhoneDeviceType iOSdeviceType = ofxiPhoneGetDeviceType(); rt300@22: cout << "Device: " << iOSdeviceType << '\n'; rt300@22: rt300@25: nextUploadQty = UPLOAD_CHUNK_SIZE; // amount of data uploaded is always more than UPLOAD_CHUNK_SIZE events rt300@27: rt300@8: } rt300@8: //--------------------------------------------------------------------------- rt300@24: // draw() - show path of last N scroll events - can be scrubbed along? rt300@24: // rt300@24: //--------------------------------------------------------------------------- rt300@29: rt300@8: void EventLogger::init(){ rt300@4: rt300@30: checkExistingLogFile(ofxiPhoneGetDocumentsDirectory() + EVENT_LOG_FILENAME); rt300@8: sessionStartTime = ofGetSystemTime(); rt300@9: rt300@9: testConnection(); rt300@7: rt300@25: logEvent(APP_STARTED); rt300@29: rt300@30: NSString *jsondata; rt300@30: jsondata=@"{\"test\":\"test\""; rt300@30: serverComms = [[ServerComms alloc] init]; rt300@29: rt300@30: } rt300@30: //--------------------------------------------------------------------------- rt300@30: // this reads the persistent log file , checks if we've used the app before and rt300@30: // if we've answered questionnaire or not rt300@30: void EventLogger::checkExistingLogFile(const string &jsonFile){ rt300@30: Json::Value root; rt300@30: Json::Reader reader; rt300@30: rt300@30: ///////////// rt300@30: // read file rt300@30: rt300@30: ifstream theFile(jsonFile.c_str()); rt300@30: stringstream fileText; rt300@30: string line; rt300@30: if(!theFile){ rt300@30: rt300@30: firstEverAppOpen(); rt300@30: return; rt300@30: }else{ rt300@30: while(theFile){ rt300@30: theFile >> line; rt300@30: // cout << line; // lots!!!! rt300@30: fileText << line; rt300@30: } rt300@30: theFile.close(); rt300@30: } rt300@30: rt300@30: // otherwise, we've been in here before rt300@30: rt300@30: ///////////// rt300@30: // Parse json rt300@30: cout << "size of log JSON string:" << fileText.str().length() << "BYTES \n"; rt300@30: rt300@30: bool parsingSuccessful = reader.parse( fileText.str(), root ); rt300@30: rt300@30: if ( !parsingSuccessful ) rt300@30: { rt300@30: // report to the user the failure and their locations in the document. rt300@30: std::cout << "Failed to parse event log JSON: \n" rt300@30: << reader.getFormattedErrorMessages(); rt300@30: return; rt300@30: } rt300@30: rt300@30: ///////////////// rt300@30: // now put user deets into variables rt300@30: rt300@30: userName = root["userName"].asString(); rt300@30: deviceID = root["deviceID"].asLargestInt(); rt300@30: nextUploadNumber = root["uploadNumber"].asInt(); rt300@30: savedInteractionTime = root["savedInteractionTime"].asLargestInt(); rt300@30: questionnaireCompleted = root["questionnaireCompleted"].asBool(); rt300@30: questionnaireUploaded = root["questionnaireUploaded"].asBool(); rt300@30: interfaceOrder = root["interfaceOrder"].asInt(); rt300@30: rt300@29: rt300@30: // check for unuploaded evts rt300@30: const Json::Value jlogs = root["events"]; rt300@29: rt300@30: for ( int index = 0; index < jlogs.size(); ++index ) theEvents.push_back(lEvent(jlogs[index])); rt300@30: if(theEvents.size() > nextUploadQty && ! logUploadInProgress){ rt300@30: //try to upload rt300@30: uploadEventLog(true); rt300@30: } rt300@30: rt300@30: ////////////// rt300@30: // what stage is the experiment at?? rt300@30: rt300@30: if(questionnaireCompleted && !questionnaireUploaded){ rt300@30: // then read it in and upload it rt300@30: Json::Value JQ = root["questionnaire"]; rt300@30: Json::Value JArray = JQ["qAnswers"]; rt300@30: if(JArray.size() < 2){ rt300@30: cout << "Error - status of questionnaire is wierd\n"; rt300@30: } rt300@30: for ( unsigned int i = 0; i < JArray.size(); i++ ) rt300@30: { rt300@30: questionnaireAnswers.push_back(JArray[i].asInt()); rt300@30: } rt300@30: questionnaireComments = JQ["comments"].toStyledString(); rt300@30: uploadQuestionnaire(); rt300@30: } rt300@30: //TODO if questionnaire wasn't reached but still opened then set the timer to something sensible rt300@30: rt300@30: if(!questionnaireCompleted){ rt300@30: // for now sod it, start from scratch rt300@30: firstEverAppOpen(); rt300@30: return; rt300@30: } rt300@30: cout << "Total interaction time: " << savedInteractionTime << '\n'; rt300@30: rt300@30: //timer.setInteractionTime(savedInteractionTime); rt300@30: //timer.setOrderFromPrevious(interfaceOrder); rt300@30: rt300@8: } rt300@29: rt300@8: //--------------------------------------------------------------------------- rt300@30: rt300@30: void EventLogger::firstEverAppOpen(){ rt300@30: cout<<"no event log file - first APP open\n"; rt300@30: nextUploadNumber = 0; rt300@30: deviceID = ofGetSystemTimeMicros(); rt300@30: savedInteractionTime = 0; rt300@30: questionnaireCompleted = false; rt300@30: questionnaireUploaded = false; rt300@30: rt300@30: ((testApp *)ofGetAppPtr())->showIntro(); rt300@30: consentGiven = false; rt300@30: rt300@30: } rt300@30: rt300@30: rt300@30: //--------------------------------------------------------------------------- rt300@28: void EventLogger::questionnaireAnswersObtained(vector answers, const char* userComments){ rt300@22: rt300@22: questionnaireCompleted = true; rt300@22: questionnaireAnswers = answers; rt300@28: questionnaireComments = userComments; rt300@22: uploadQuestionnaire(); rt300@22: rt300@22: } rt300@22: //--------------------------------------------------------------------------- rt300@27: void EventLogger::uploadQuestionnaire(){ rt300@22: // show indicator rt300@22: cout << "^^^^^^^^ UPLOADING QUESTIONNAIRE ^^^^^^^^ \n"; rt300@22: rt300@27: sendToServer("questionnaire", questionnaireToJson(), true); rt300@27: rt300@22: } rt300@22: //--------------------------------------------------------------------------- rt300@27: bool EventLogger::sendToServer(string functionName, Json::Value jsonData, bool async = false){ rt300@22: rt300@22: Json::FastWriter writer; rt300@22: string jsontext = writer.write( jsonData ); rt300@22: rt300@27: // remove newline rt300@22: if (!jsontext.empty() && jsontext[jsontext.length()-1] == '\n') { rt300@22: jsontext.erase(jsontext.length()-1); rt300@22: } rt300@30: ostringstream jd; rt300@30: jd << jsontext; rt300@30: NSString *theData = [NSString stringWithUTF8String:jd.str().c_str()]; rt300@30: NSString *theType = [NSString stringWithUTF8String:functionName.c_str()]; rt300@22: rt300@30: if(async){ rt300@30: [serverComms doPostRequest:theType withData:theData]; rt300@30: }else{ rt300@30: bool success = [serverComms doSyncPostRequest:theType withData:theData]; rt300@30: return success; rt300@30: } rt300@29: rt300@27: } rt300@27: //----------------------------- rt300@30: void EventLogger::questionnaireOK(){ rt300@30: questionnaireUploaded = true; rt300@30: } rt300@30: //----------------------------- rt300@30: void EventLogger::eventlogOK(){ rt300@30: //theEvents.clear(); rt300@30: cout << "EVENT LOG UPLOAD SUCCESS\n"; rt300@30: nextUploadNumber++; rt300@30: logUploadInProgress = false; rt300@30: } rt300@30: //----------------------------- rt300@30: void EventLogger::testConnectionOK(){ rt300@30: cout << "^^^^^^^^ server connection OK ^^^^^^^^ \n"; rt300@30: serverConnectionOK = true; rt300@30: } rt300@30: //----------------------------- rt300@30: void EventLogger::questionnaireNotOK(){ rt300@30: questionnaireUploaded = false; rt300@30: } rt300@30: //----------------------------- rt300@30: void EventLogger::eventlogNotOK(){ rt300@30: // try later rt300@30: nextUploadQty += UPLOAD_CHUNK_SIZE; rt300@30: logUploadInProgress = false; rt300@30: } rt300@30: //----------------------------- rt300@30: void EventLogger::testConnectionNotOK(){ rt300@30: cout << "^^^^^^^^ server connection NOT OK ^^^^^^^^ \n"; rt300@30: serverConnectionOK = false; rt300@30: // alert? rt300@30: rt300@30: } rt300@30: /* rt300@27: void EventLogger::urlResponse(ofHttpResponse & response){ rt300@27: cout << "gotHTTPRequestStatus\n"; rt300@27: cout << "HTTP REQUEST NAME " << response.request.name << "\n"; rt300@27: cout << "HTTP STATUS " << response.status << "\n"; rt300@27: cout << "HTTP ERROR " << response.error << "\n"; rt300@27: cout << "HTTP DATA " << response.data << "\n"; // ofBuffer rt300@9: rt300@27: bool sent; rt300@27: stringstream respStr; rt300@27: respStr << response.data; rt300@22: rt300@27: if (response.status == 200){ rt300@27: if(respStr.str() == "OK"){ rt300@22: rt300@22: sent = true; rt300@22: }else{ rt300@22: // not ok rt300@22: // problem serverside rt300@22: sent = false; rt300@22: } rt300@9: }else{ rt300@22: rt300@22: sent = false; rt300@9: // SHOW AN ALERT TO USER? rt300@9: } rt300@27: rt300@27: // now do request specific stuff rt300@27: if(response.request.name == "eventlog"){ rt300@27: if(!sent){ rt300@27: // try later rt300@27: nextUploadQty += UPLOAD_CHUNK_SIZE; rt300@27: }else{ rt300@27: rt300@27: // if success - clear memory rt300@27: theEvents.clear(); rt300@27: cout << "UPLOAD SUCCESS\n"; rt300@27: nextUploadNumber++; rt300@27: } rt300@27: logUploadInProgress = false; rt300@27: }else if(response.request.name == "questionnaire"){ rt300@27: if(sent){ rt300@27: questionnaireUploaded = true; rt300@27: }else{ rt300@27: questionnaireUploaded = false; // will try next time... when? rt300@27: } rt300@27: }else if(response.request.name == "testConnection"){ rt300@27: rt300@27: if (sent){ rt300@27: cout << "^^^^^^^^ server connection OK ^^^^^^^^ \n"; rt300@27: serverConnectionOK = true; rt300@27: }else{ rt300@28: serverConnectionOK = false; rt300@27: cout << "server connection ERROR \n"; rt300@27: } rt300@27: } rt300@27: rt300@22: } rt300@30: */ rt300@22: //--------------------------------------------------------------------------- rt300@22: bool EventLogger::testConnection(){ rt300@22: Json::Value root; rt300@30: root["test"] = "test"; rt300@27: sendToServer("testConnection", root, true); rt300@27: rt300@9: } rt300@9: //--------------------------------------------------------------------------- rt300@9: rt300@27: bool EventLogger::uploadEventLog(bool async){ rt300@28: rt300@22: // show indicator rt300@27: logUploadInProgress = true; rt300@25: cout << "^^^^^^^^ ATTEMPTING TO UPLOAD " << theEvents.size() << " EVENTS ^^^^^^^^ .\n"; rt300@27: if(!async){ rt300@27: bool success = sendToServer("eventlog", logsToJson(), async); rt300@27: if(!success){ rt300@29: // try later : NOPE has maximum size rt300@27: nextUploadQty += UPLOAD_CHUNK_SIZE; rt300@27: }else{ rt300@27: rt300@27: // if success - clear memory rt300@30: //theEvents.clear(); rt300@27: cout << "UPLOAD SUCCESS\n"; rt300@27: nextUploadNumber++; rt300@27: } rt300@27: logUploadInProgress = false; rt300@27: return success; rt300@9: }else{ rt300@27: sendToServer("eventlog", logsToJson(), async); rt300@9: } rt300@1: } rt300@8: //---------------------------------------------------------------------------- rt300@27: rt300@27: //---------------------------------------------------------------------------- rt300@9: //void EventLogger::deleteLogFile(){ rt300@8: rt300@8: rt300@28: //--------------------------------------------------------------------------- rt300@28: // only called when doing supervised tests rt300@27: void EventLogger::newUser(){ rt300@27: cout<<"setup new user\n"; rt300@29: deleteLogs(); rt300@27: nextUploadNumber = 0; rt300@27: deviceID = ofGetSystemTimeMicros(); rt300@27: savedInteractionTime = 0; rt300@28: totalInteractionTime = 0; rt300@28: sessionStartTime = ofGetSystemTime(); rt300@27: questionnaireCompleted = false; rt300@29: questionnaireUploaded = false; rt300@29: rt300@29: ((testApp *)ofGetAppPtr())->showIntro(); rt300@27: rt300@27: } rt300@8: //--------------------------------------------------------------------------- rt300@8: // called from alertView OK in iViewController rt300@8: void EventLogger::setUsername(const char *u){ rt300@8: userName = u; rt300@28: // start interaction clock rt300@29: rt300@29: //timer.startInteractionClock(); rt300@29: [((testApp *)ofGetAppPtr())->tsc startTimer]; rt300@28: } rt300@28: //--------------------------------------------------------------------------- rt300@28: void EventLogger::thinnedLogEvent(lEvent newEvent){ rt300@28: static lEvent previousEvent(APP_STARTED); // initialised as whatever. hopefully won't log rt300@28: static int eventCounter = 0; rt300@28: rt300@28: // if first event then log it. it won't be, but still. rt300@28: if(theEvents.size() == 0){ rt300@28: theEvents.push_back(newEvent); rt300@28: previousEvent = newEvent; rt300@28: return; rt300@28: } rt300@28: rt300@28: // if previous event is more than 300ms ago, or event type has changed, log both of them rt300@28: int gap = newEvent.eventTime - previousEvent.eventTime; rt300@28: if(gap > 300 || newEvent.eventType != previousEvent.eventType){ rt300@28: // if prev event not logged, log it rt300@28: if((*theEvents.end()).eventTime != previousEvent.eventTime){ rt300@28: theEvents.push_back(previousEvent); rt300@28: } rt300@28: theEvents.push_back(newEvent); rt300@7: rt300@28: } rt300@28: rt300@28: // otherwise only record every Nth event rt300@28: if(eventCounter >= EVENT_THIN_FACTOR){ rt300@28: theEvents.push_back(newEvent); rt300@28: eventCounter = 0; rt300@28: rt300@28: } rt300@28: eventCounter++; rt300@28: previousEvent = newEvent; rt300@7: } rt300@8: //--------------------------------------------------------------------------- rt300@5: void EventLogger::logEvent(const leventType& evtType,const TwoVector& centre, const double& scale, const int& sliderID, const double& sliderVal){ rt300@3: //cout << "log: " << evtType << "\n"; rt300@1: rt300@1: // scroll has 2 double coords rt300@1: // zoom has 1 double scale rt300@1: // save preset has 2 coords rt300@1: // switch view has view type rt300@9: // slider change has int slider index and 1 float value rt300@1: rt300@4: // get time for key index rt300@4: rt300@5: // thinFactor rt300@5: if(!loggingEnabled) return; rt300@4: switch ( evtType ) { rt300@28: // data thinning here rt300@28: case SCROLL: rt300@28: thinnedLogEvent(lEvent(evtType,centre.x,centre.y)); rt300@28: break; rt300@28: case ZOOM: rt300@28: thinnedLogEvent(lEvent(evtType,scale)); rt300@28: break; rt300@28: case CHANGE_SLIDER: rt300@28: thinnedLogEvent(lEvent(evtType,sliderVal , 0.0 , sliderID)); rt300@28: break; rt300@29: // non thinned data rt300@4: case SAVE_PRESET: rt300@5: theEvents.push_back(lEvent(evtType,centre.x,centre.y)); rt300@4: // Code rt300@4: break; rt300@4: case SAVE_DESET: rt300@5: theEvents.push_back(lEvent(evtType,centre.x,centre.y)); rt300@4: break; rt300@28: rt300@5: case SCROLL_STOPPED: rt300@5: theEvents.push_back(lEvent(evtType,centre.x,centre.y)); rt300@22: cout << "SCROLL STOPPED EVENT\n"; rt300@22: break; rt300@22: case SNAPPED_TO_PRESET: rt300@22: theEvents.push_back(lEvent(evtType,centre.x,centre.y,sliderID)); rt300@22: cout << "SCROLL STOPPED EVENT\n"; rt300@4: break; rt300@25: case SWAP_VIEW: rt300@25: theEvents.push_back(lEvent(evtType,0.0 , 0.0 , sliderID)); // slider ID is which view rt300@25: break; rt300@4: default: rt300@25: // default is just an event type with no values rt300@25: theEvents.push_back(lEvent(evtType)); rt300@4: break; rt300@1: } rt300@27: if(theEvents.size() > nextUploadQty && !logUploadInProgress){ rt300@27: //try to upload asynchronously rt300@27: uploadEventLog(true); rt300@7: } rt300@9: //sessionTime = (ofGetSystemTime() - sessionStartTime); rt300@28: totalInteractionTime = savedInteractionTime + (ofGetSystemTime() - sessionStartTime); // milliseconds rt300@28: rt300@1: } rt300@29: //-------------------------------------------------------------------- rt300@29: // called from newUser rt300@29: void EventLogger::deleteLogs(){ rt300@29: // the rt300@29: theEvents.clear(); rt300@29: string fname = ofxiPhoneGetDocumentsDirectory() + EVENT_LOG_FILENAME; rt300@29: ofFile logFile(fname,ofFile::WriteOnly); rt300@29: logFile << ""; rt300@29: logFile.close(); rt300@29: } rt300@8: //--------------------------------------------------------------------------- rt300@7: rt300@7: void EventLogger::exitAndSave(){ rt300@25: rt300@25: if(!consentGiven){ rt300@25: logEvent(CONSENT_DENIED); rt300@25: Json::Value jlogs = logsToJson(); rt300@25: // try to upload TODO (no - might hang and prevent exit???) rt300@27: uploadEventLog(true); rt300@25: return; rt300@25: } rt300@25: logEvent(APP_EXITED); rt300@25: savedInteractionTime = savedInteractionTime + (ofGetSystemTime() - sessionStartTime); rt300@7: // save user details rt300@8: string fname = ofxiPhoneGetDocumentsDirectory() + EVENT_LOG_FILENAME; rt300@8: rt300@25: // try to upload TODO (no - might hang and prevent exit???) rt300@27: // do it async because event list needs to be cleared to prevent saving on device rt300@27: uploadEventLog(false); rt300@7: rt300@8: // write to file rt300@25: // json without the logs that were uploaded! rt300@25: Json::Value jlogs = logsToJson(); rt300@8: ofFile logFile(fname,ofFile::WriteOnly); rt300@8: logFile << jlogs; rt300@29: logFile.close(); rt300@8: rt300@8: } rt300@8: //--------------------------------------------------------------------------- rt300@8: rt300@8: Json::Value EventLogger::logsToJson(){ rt300@8: // put all logged events into Json formatted string rt300@8: Json::Value root; rt300@8: rt300@8: vector::iterator eventIter; rt300@8: rt300@22: root["programVersion"] = PROGRAM_VERSION; rt300@8: root["userName"] = userName; rt300@8: root["deviceID"] = deviceID; rt300@25: root["uploadNumber"] = nextUploadNumber; rt300@22: root["iOSdeviceType"] = iOSdeviceType; rt300@25: root["savedInteractionTime"] = savedInteractionTime; rt300@22: root["questionnaireCompleted"] = questionnaireCompleted; rt300@22: root["questionnaireUploaded"] = questionnaireUploaded; rt300@30: root["questionnaire"] = questionnaireToJson(); rt300@30: rt300@8: rt300@8: int i = 0; rt300@8: for(eventIter = theEvents.begin(); eventIter < theEvents.end(); eventIter++){ rt300@8: root["events"][i] = (*eventIter).eventToJson(); rt300@8: i++; rt300@7: } rt300@25: root["numEventsHere"] = i; rt300@8: return root; rt300@8: } rt300@22: rt300@8: //--------------------------------------------------------------------------- rt300@22: rt300@22: Json::Value EventLogger::questionnaireToJson(){ rt300@22: // put all answers into Json formatted string rt300@22: Json::Value root; rt300@22: rt300@22: vector::iterator aIter; rt300@22: rt300@22: Json::Value questionnaire; rt300@22: rt300@22: int i = 0; rt300@22: for(aIter = questionnaireAnswers.begin(); aIter < questionnaireAnswers.end(); aIter++){ rt300@22: questionnaire[i] = (*aIter); rt300@22: i++; rt300@22: } rt300@22: rt300@22: root["qAnswers"] = questionnaire; rt300@28: root["comments"] = questionnaireComments; rt300@22: root["userName"] = userName; rt300@22: root["deviceID"] = deviceID; rt300@22: root["iOSdeviceType"] = iOSdeviceType; rt300@22: root["programVersion"] = PROGRAM_VERSION; rt300@22: rt300@22: return root; rt300@22: } rt300@30: rt300@8: //--------------------------------------------------------------------------- rt300@30: void EventLogger::printAll(){ rt300@30: cout << "-----------------ALL LOGGED EVENTS----------------- \n"; rt300@30: vector::iterator evIter; rt300@30: cout << logsToJson() << "\n"; rt300@30: cout << "---------------------QUESTIONNAIRE---------------- \n"; rt300@30: cout << questionnaireToJson() << "\n"; rt300@30: }; rt300@8: //---------------------------------------------------------------------------