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@1: 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@8: void EventLogger::init(){ rt300@4: rt300@8: readJsonToLog(ofxiPhoneGetDocumentsDirectory() + EVENT_LOG_FILENAME); rt300@8: sessionStartTime = ofGetSystemTime(); rt300@9: rt300@9: testConnection(); rt300@7: rt300@25: logEvent(APP_STARTED); rt300@8: } rt300@8: //--------------------------------------------------------------------------- rt300@22: void EventLogger::questionnaireAnswersObtained(vector answers){ rt300@22: rt300@22: questionnaireCompleted = true; rt300@22: questionnaireAnswers = answers; rt300@22: 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: bool sent; rt300@22: string request; rt300@22: rt300@22: if(functionName == "testConnection"){ rt300@22: request = "http://127.0.0.1:8080/testservice/testConnection?jsontext="; rt300@22: }else if(functionName == "questionnaire"){ rt300@22: request = "http://127.0.0.1:8080/testservice/questionnaire?jsontext="; rt300@22: }else if(functionName == "eventlog"){ rt300@22: request = "http://127.0.0.1:8080/testservice/eventlog?jsontext="; 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@22: rt300@22: request.append(jsontext); rt300@27: rt300@27: if(!async){ rt300@27: ofURLFileLoader fileLoader; rt300@27: ofHttpResponse resp; rt300@27: resp = fileLoader.get(request); rt300@22: rt300@27: cout << "HTTP STATUS " << resp.status << "\n"; rt300@27: cout << "HTTP ERROR " << resp.error << "\n"; rt300@27: cout << "HTTP DATA " << resp.data << "\n"; // ofBuffer rt300@27: rt300@27: stringstream response; rt300@27: response << resp.data; rt300@27: rt300@27: if (resp.status == 200){ rt300@27: if(response.str() == "OK"){ rt300@27: rt300@27: sent = true; rt300@27: }else{ rt300@27: // not ok rt300@27: // problem serverside rt300@27: sent = false; rt300@27: } rt300@27: }else{ rt300@27: rt300@27: sent = false; rt300@27: // SHOW AN ALERT TO USER? rt300@27: } rt300@27: rt300@27: return sent; rt300@27: }else{ // async rt300@27: ofURLFileLoader fileLoader; rt300@27: currentHTTPRequestID = ofLoadURLAsync(request, functionName); rt300@27: ofRegisterURLNotification(this); rt300@27: rt300@27: return true; // ??? rt300@27: } rt300@27: } rt300@27: //----------------------------- 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@27: cout << "server connection ERROR \n"; rt300@27: } rt300@27: } rt300@27: rt300@22: } rt300@22: //--------------------------------------------------------------------------- rt300@22: bool EventLogger::testConnection(){ rt300@22: Json::Value root; rt300@22: root["test"] = "test"; rt300@22: rt300@22: rt300@27: sendToServer("testConnection", root, true); rt300@27: rt300@9: } rt300@9: //--------------------------------------------------------------------------- rt300@8: void EventLogger::readJsonToLog(const string &jsonFile){ rt300@8: Json::Value root; rt300@8: Json::Reader reader; rt300@5: rt300@5: rt300@8: ifstream theFile(jsonFile.c_str()); rt300@8: stringstream fileText; rt300@8: string line; rt300@8: if(!theFile){ rt300@8: rt300@8: firstEverAppOpen(); rt300@8: rt300@8: return; rt300@8: }else{ rt300@8: while(theFile){ rt300@8: theFile >> line; rt300@9: // cout << line; // lots!!!! rt300@8: fileText << line; rt300@8: } rt300@8: theFile.close(); rt300@8: } rt300@8: rt300@9: cout << "size of log JSON string:" << fileText.str().length() << "BYTES \n"; rt300@9: rt300@8: bool parsingSuccessful = reader.parse( fileText.str(), root ); rt300@8: rt300@8: if ( !parsingSuccessful ) rt300@8: { rt300@8: // report to the user the failure and their locations in the document. rt300@22: std::cout << "Failed to parse event log JSON: \n" rt300@8: << reader.getFormattedErrorMessages(); rt300@8: return; rt300@8: } rt300@8: rt300@8: // now put user deets into variables rt300@8: userName = root["userName"].asString(); rt300@8: deviceID = root["deviceID"].asLargestInt(); rt300@25: nextUploadNumber = root["uploadNumber"].asInt(); rt300@25: savedInteractionTime = root["savedInteractionTime"].asLargestInt(); rt300@22: questionnaireCompleted = root["questionnaireCompleted"].asBool(); rt300@22: questionnaireUploaded = root["questionnaireUploaded"].asBool(); rt300@22: rt300@22: rt300@22: if(questionnaireCompleted && !questionnaireUploaded){ rt300@22: // then read it in and upload it rt300@22: Json::Value JArray = root["questionnaireAnswers"]; rt300@22: if(JArray.size() < 2){ rt300@22: cout << "Error - status of questionnaire is wierd\n"; rt300@22: } rt300@22: for ( unsigned int i = 0; i < JArray.size(); i++ ) rt300@22: { rt300@22: questionnaireAnswers.push_back(JArray[1].asInt()); rt300@22: } rt300@22: uploadQuestionnaire(); rt300@22: } rt300@8: rt300@8: // check for unuploaded evts rt300@8: const Json::Value jlogs = root["events"]; rt300@8: rt300@8: for ( int index = 0; index < jlogs.size(); ++index ) theEvents.push_back(lEvent(jlogs[index])); rt300@27: if(theEvents.size() > nextUploadQty && ! logUploadInProgress){ rt300@8: //try to upload rt300@27: uploadEventLog(true); rt300@8: } rt300@8: // TODO if the total interaction time is greater than a certain amount && no questions answered - questionnaire time! rt300@25: cout << "Total interaction time: " << savedInteractionTime << '\n'; rt300@9: rt300@8: rt300@9: } rt300@9: rt300@9: rt300@9: //--------------------------------------------------------------------------- rt300@9: rt300@27: bool EventLogger::uploadEventLog(bool async){ 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@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: 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@8: rt300@8: void EventLogger::firstEverAppOpen(){ rt300@25: cout<<"no event log file - first APP open\n"; rt300@25: nextUploadNumber = 0; rt300@8: deviceID = ofGetSystemTimeMicros(); rt300@25: savedInteractionTime = 0; rt300@22: questionnaireCompleted = false; rt300@22: questionnaireUploaded = false; rt300@8: rt300@24: ((testApp *)ofGetAppPtr())->showIntro(); rt300@25: consentGiven = false; rt300@8: rt300@8: } rt300@27: void EventLogger::newUser(){ rt300@27: cout<<"setup new user\n"; rt300@27: nextUploadNumber = 0; rt300@27: deviceID = ofGetSystemTimeMicros(); rt300@27: savedInteractionTime = 0; rt300@27: questionnaireCompleted = false; rt300@27: questionnaireUploaded = false; rt300@27: consentGiven = true; // other wise we wouldn't be doing this rt300@27: ((testApp *)ofGetAppPtr())->introHidden(true); // hacky 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@7: rt300@7: } rt300@8: rt300@8: //--------------------------------------------------------------------------- rt300@8: // log zoom event 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@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@4: case SCROLL: rt300@5: theEvents.push_back(lEvent(evtType,centre.x,centre.y)); rt300@5: break; 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@4: case ZOOM: rt300@5: theEvents.push_back(lEvent(evtType,scale)); rt300@4: break; rt300@4: case CHANGE_SLIDER: rt300@5: theEvents.push_back(lEvent(evtType,sliderVal , 0.0 , sliderID)); 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@25: totalInteractionTime = savedInteractionTime + (ofGetSystemTime() - sessionStartTime); rt300@25: if(totalInteractionTime > QUESTIONNAIRE_ENABLE_TIME){ rt300@25: TopButtonViewController *tbvc = ((testApp *)ofGetAppPtr())->topButtonViewController; rt300@25: [tbvc enableQuestionButton]; rt300@25: } rt300@1: } rt300@8: 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@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@22: if(questionnaireCompleted && !questionnaireUploaded){ rt300@22: root["qAnswers"] = questionnaireToJson(); rt300@22: } 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@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@8: //--------------------------------------------------------------------------- rt300@8: //--------------------------------------------------------------------------- rt300@22: //---------------------------------------------------------------------------