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(){
+//    
+//    
+//}