annotate eventLogger.mm @ 32:ab7c86d0f3d8

V0.3 SZBeta sent out. bristol tests.
author Robert Tubb <rt300@eecs.qmul.ac.uk>
date Fri, 08 Mar 2013 14:54:55 +0000
parents 23ef179c3748
children 92dba082d957
rev   line source
rt300@0 1 //
rt300@0 2 // eventLogger.mm
rt300@0 3 // oscSenderExample
rt300@0 4 //
rt300@0 5 // Created by Robert Tubb on 05/11/2012.
rt300@0 6 //
rt300@0 7 //
rt300@4 8
rt300@8 9 //---------------------------------------------------------------------------
rt300@0 10 #include "eventLogger.h"
rt300@1 11
rt300@1 12 EventLogger eventLogger;
rt300@32 13 extern PresetManager presetManager;
rt300@29 14
rt300@8 15 //---------------------------------------------------------------------------
rt300@1 16 EventLogger::EventLogger(){
rt300@8 17
rt300@31 18 consentGiven = true; // unless told otherwise firstAppOpen
rt300@8 19 loggingEnabled = true;
rt300@22 20 serverConnectionOK = false;
rt300@22 21 questionnaireCompleted = false;
rt300@22 22 questionnaireUploaded = false;
rt300@27 23 logUploadInProgress = false;
rt300@22 24 ofxiPhoneDeviceType iOSdeviceType = ofxiPhoneGetDeviceType();
rt300@22 25 cout << "Device: " << iOSdeviceType << '\n';
rt300@22 26
rt300@25 27 nextUploadQty = UPLOAD_CHUNK_SIZE; // amount of data uploaded is always more than UPLOAD_CHUNK_SIZE events
rt300@27 28
rt300@8 29 }
rt300@8 30 //---------------------------------------------------------------------------
rt300@24 31 // draw() - show path of last N scroll events - can be scrubbed along?
rt300@24 32 //
rt300@24 33 //---------------------------------------------------------------------------
rt300@29 34
rt300@8 35 void EventLogger::init(){
rt300@4 36
rt300@30 37 checkExistingLogFile(ofxiPhoneGetDocumentsDirectory() + EVENT_LOG_FILENAME);
rt300@8 38 sessionStartTime = ofGetSystemTime();
rt300@31 39
rt300@31 40 logEvent(APP_STARTED);
rt300@31 41
rt300@31 42 serverComms = [[ServerComms alloc] init];
rt300@9 43
rt300@9 44 testConnection();
rt300@7 45
rt300@31 46 }
rt300@31 47 //---------------------------------------------------------------------------
rt300@31 48 bool EventLogger::testConnection(){
rt300@31 49 Json::Value root;
rt300@31 50 root["x"] = "y";
rt300@31 51 cout << "testConnection\n";
rt300@31 52 sendToServer("testConnection", root, true);
rt300@31 53
rt300@30 54 }
rt300@30 55 //---------------------------------------------------------------------------
rt300@30 56 // this reads the persistent log file , checks if we've used the app before and
rt300@30 57 // if we've answered questionnaire or not
rt300@30 58 void EventLogger::checkExistingLogFile(const string &jsonFile){
rt300@30 59 Json::Value root;
rt300@30 60 Json::Reader reader;
rt300@30 61
rt300@30 62 /////////////
rt300@30 63 // read file
rt300@30 64
rt300@30 65 ifstream theFile(jsonFile.c_str());
rt300@30 66 stringstream fileText;
rt300@30 67 string line;
rt300@30 68 if(!theFile){
rt300@30 69
rt300@30 70 firstEverAppOpen();
rt300@30 71 return;
rt300@30 72 }else{
rt300@30 73 while(theFile){
rt300@30 74 theFile >> line;
rt300@30 75 // cout << line; // lots!!!!
rt300@30 76 fileText << line;
rt300@30 77 }
rt300@30 78 theFile.close();
rt300@30 79 }
rt300@30 80
rt300@30 81 // otherwise, we've been in here before
rt300@30 82
rt300@30 83 /////////////
rt300@30 84 // Parse json
rt300@30 85 cout << "size of log JSON string:" << fileText.str().length() << "BYTES \n";
rt300@30 86
rt300@30 87 bool parsingSuccessful = reader.parse( fileText.str(), root );
rt300@30 88
rt300@30 89 if ( !parsingSuccessful )
rt300@30 90 {
rt300@30 91 // report to the user the failure and their locations in the document.
rt300@30 92 std::cout << "Failed to parse event log JSON: \n"
rt300@30 93 << reader.getFormattedErrorMessages();
rt300@30 94 return;
rt300@30 95 }
rt300@30 96
rt300@30 97 /////////////////
rt300@30 98 // now put user deets into variables
rt300@30 99
rt300@30 100 userName = root["userName"].asString();
rt300@30 101 deviceID = root["deviceID"].asLargestInt();
rt300@30 102 nextUploadNumber = root["uploadNumber"].asInt();
rt300@30 103 savedInteractionTime = root["savedInteractionTime"].asLargestInt();
rt300@30 104 questionnaireCompleted = root["questionnaireCompleted"].asBool();
rt300@30 105 questionnaireUploaded = root["questionnaireUploaded"].asBool();
rt300@30 106 interfaceOrder = root["interfaceOrder"].asInt();
rt300@30 107
rt300@29 108
rt300@30 109 // check for unuploaded evts
rt300@30 110 const Json::Value jlogs = root["events"];
rt300@29 111
rt300@30 112 for ( int index = 0; index < jlogs.size(); ++index ) theEvents.push_back(lEvent(jlogs[index]));
rt300@30 113 if(theEvents.size() > nextUploadQty && ! logUploadInProgress){
rt300@30 114 //try to upload
rt300@30 115 uploadEventLog(true);
rt300@30 116 }
rt300@30 117
rt300@30 118 //////////////
rt300@30 119 // what stage is the experiment at??
rt300@30 120
rt300@30 121 if(questionnaireCompleted && !questionnaireUploaded){
rt300@30 122 // then read it in and upload it
rt300@30 123 Json::Value JQ = root["questionnaire"];
rt300@30 124 Json::Value JArray = JQ["qAnswers"];
rt300@30 125 if(JArray.size() < 2){
rt300@30 126 cout << "Error - status of questionnaire is wierd\n";
rt300@30 127 }
rt300@30 128 for ( unsigned int i = 0; i < JArray.size(); i++ )
rt300@30 129 {
rt300@30 130 questionnaireAnswers.push_back(JArray[i].asInt());
rt300@30 131 }
rt300@30 132 questionnaireComments = JQ["comments"].toStyledString();
rt300@30 133 uploadQuestionnaire();
rt300@30 134 }
rt300@30 135 //TODO if questionnaire wasn't reached but still opened then set the timer to something sensible
rt300@30 136
rt300@30 137 if(!questionnaireCompleted){
rt300@30 138 // for now sod it, start from scratch
rt300@30 139 firstEverAppOpen();
rt300@30 140 return;
rt300@30 141 }
rt300@30 142 cout << "Total interaction time: " << savedInteractionTime << '\n';
rt300@30 143
rt300@30 144 //timer.setInteractionTime(savedInteractionTime);
rt300@30 145 //timer.setOrderFromPrevious(interfaceOrder);
rt300@30 146
rt300@8 147 }
rt300@29 148
rt300@8 149 //---------------------------------------------------------------------------
rt300@30 150
rt300@30 151 void EventLogger::firstEverAppOpen(){
rt300@30 152 cout<<"no event log file - first APP open\n";
rt300@30 153 nextUploadNumber = 0;
rt300@30 154 deviceID = ofGetSystemTimeMicros();
rt300@30 155 savedInteractionTime = 0;
rt300@30 156 questionnaireCompleted = false;
rt300@30 157 questionnaireUploaded = false;
rt300@30 158
rt300@30 159 ((testApp *)ofGetAppPtr())->showIntro();
rt300@30 160 consentGiven = false;
rt300@30 161
rt300@30 162 }
rt300@30 163
rt300@30 164
rt300@30 165 //---------------------------------------------------------------------------
rt300@28 166 void EventLogger::questionnaireAnswersObtained(vector<int> answers, const char* userComments){
rt300@22 167
rt300@22 168 questionnaireCompleted = true;
rt300@22 169 questionnaireAnswers = answers;
rt300@28 170 questionnaireComments = userComments;
rt300@22 171 uploadQuestionnaire();
rt300@31 172 logEvent(QUESTIONNAIRE_COMPLETED);
rt300@22 173
rt300@22 174 }
rt300@22 175 //---------------------------------------------------------------------------
rt300@27 176 void EventLogger::uploadQuestionnaire(){
rt300@22 177 // show indicator
rt300@22 178 cout << "^^^^^^^^ UPLOADING QUESTIONNAIRE ^^^^^^^^ \n";
rt300@31 179 cout << questionnaireToJson() << "\n";
rt300@27 180 sendToServer("questionnaire", questionnaireToJson(), true);
rt300@27 181
rt300@22 182 }
rt300@22 183 //---------------------------------------------------------------------------
rt300@27 184 bool EventLogger::sendToServer(string functionName, Json::Value jsonData, bool async = false){
rt300@22 185
rt300@22 186 Json::FastWriter writer;
rt300@22 187 string jsontext = writer.write( jsonData );
rt300@22 188
rt300@27 189 // remove newline
rt300@22 190 if (!jsontext.empty() && jsontext[jsontext.length()-1] == '\n') {
rt300@22 191 jsontext.erase(jsontext.length()-1);
rt300@22 192 }
rt300@30 193 ostringstream jd;
rt300@30 194 jd << jsontext;
rt300@30 195 NSString *theData = [NSString stringWithUTF8String:jd.str().c_str()];
rt300@30 196 NSString *theType = [NSString stringWithUTF8String:functionName.c_str()];
rt300@22 197
rt300@30 198 if(async){
rt300@30 199 [serverComms doPostRequest:theType withData:theData];
rt300@30 200 }else{
rt300@30 201 bool success = [serverComms doSyncPostRequest:theType withData:theData];
rt300@30 202 return success;
rt300@30 203 }
rt300@29 204
rt300@27 205 }
rt300@27 206 //-----------------------------
rt300@30 207 void EventLogger::questionnaireOK(){
rt300@30 208 questionnaireUploaded = true;
rt300@30 209 }
rt300@30 210 //-----------------------------
rt300@30 211 void EventLogger::eventlogOK(){
rt300@31 212 // IF UPLAODING FROM IPAD TO XCODE
rt300@32 213 theEvents.clear();
rt300@30 214 cout << "EVENT LOG UPLOAD SUCCESS\n";
rt300@30 215 nextUploadNumber++;
rt300@30 216 logUploadInProgress = false;
rt300@30 217 }
rt300@30 218 //-----------------------------
rt300@30 219 void EventLogger::testConnectionOK(){
rt300@30 220 cout << "^^^^^^^^ server connection OK ^^^^^^^^ \n";
rt300@30 221 serverConnectionOK = true;
rt300@30 222 }
rt300@30 223 //-----------------------------
rt300@30 224 void EventLogger::questionnaireNotOK(){
rt300@32 225 cout << "XXXXX questionnaire NOT OK XXXXXXX \n";
rt300@30 226 questionnaireUploaded = false;
rt300@30 227 }
rt300@30 228 //-----------------------------
rt300@30 229 void EventLogger::eventlogNotOK(){
rt300@30 230 // try later
rt300@32 231 cout << "XXXXX event log NOT OK XXXXXXX \n";
rt300@30 232 nextUploadQty += UPLOAD_CHUNK_SIZE;
rt300@30 233 logUploadInProgress = false;
rt300@30 234 }
rt300@30 235 //-----------------------------
rt300@30 236 void EventLogger::testConnectionNotOK(){
rt300@32 237 cout << "XXXXX server connection NOT OK XXXXXXX \n";
rt300@30 238 serverConnectionOK = false;
rt300@30 239 // alert?
rt300@30 240
rt300@30 241 }
rt300@27 242
rt300@27 243
rt300@9 244 //---------------------------------------------------------------------------
rt300@9 245
rt300@27 246 bool EventLogger::uploadEventLog(bool async){
rt300@28 247
rt300@22 248 // show indicator
rt300@27 249 logUploadInProgress = true;
rt300@25 250 cout << "^^^^^^^^ ATTEMPTING TO UPLOAD " << theEvents.size() << " EVENTS ^^^^^^^^ .\n";
rt300@27 251 if(!async){
rt300@27 252 bool success = sendToServer("eventlog", logsToJson(), async);
rt300@27 253 if(!success){
rt300@29 254 // try later : NOPE has maximum size
rt300@27 255 nextUploadQty += UPLOAD_CHUNK_SIZE;
rt300@27 256 }else{
rt300@27 257
rt300@27 258 // if success - clear memory
rt300@31 259 // IF UPLAODING FROM IPAD TO XCODE COMMENT OUT
rt300@32 260 theEvents.clear();
rt300@27 261 cout << "UPLOAD SUCCESS\n";
rt300@27 262 nextUploadNumber++;
rt300@27 263 }
rt300@27 264 logUploadInProgress = false;
rt300@27 265 return success;
rt300@9 266 }else{
rt300@27 267 sendToServer("eventlog", logsToJson(), async);
rt300@9 268 }
rt300@1 269 }
rt300@8 270 //----------------------------------------------------------------------------
rt300@27 271
rt300@27 272 //----------------------------------------------------------------------------
rt300@8 273
rt300@28 274 //---------------------------------------------------------------------------
rt300@28 275 // only called when doing supervised tests
rt300@27 276 void EventLogger::newUser(){
rt300@32 277 // store old stuff
rt300@32 278
rt300@32 279 saveSessionToFile();
rt300@32 280 presetManager.saveSessionToFile(userName);
rt300@27 281 cout<<"setup new user\n";
rt300@29 282 deleteLogs();
rt300@27 283 nextUploadNumber = 0;
rt300@27 284 deviceID = ofGetSystemTimeMicros();
rt300@27 285 savedInteractionTime = 0;
rt300@28 286 totalInteractionTime = 0;
rt300@28 287 sessionStartTime = ofGetSystemTime();
rt300@27 288 questionnaireCompleted = false;
rt300@29 289 questionnaireUploaded = false;
rt300@29 290
rt300@29 291 ((testApp *)ofGetAppPtr())->showIntro();
rt300@27 292
rt300@27 293 }
rt300@8 294 //---------------------------------------------------------------------------
rt300@8 295 // called from alertView OK in iViewController
rt300@8 296 void EventLogger::setUsername(const char *u){
rt300@8 297 userName = u;
rt300@28 298 // start interaction clock
rt300@29 299
rt300@29 300 //timer.startInteractionClock();
rt300@29 301 [((testApp *)ofGetAppPtr())->tsc startTimer];
rt300@28 302 }
rt300@28 303 //---------------------------------------------------------------------------
rt300@28 304 void EventLogger::thinnedLogEvent(lEvent newEvent){
rt300@28 305 static lEvent previousEvent(APP_STARTED); // initialised as whatever. hopefully won't log
rt300@28 306 static int eventCounter = 0;
rt300@28 307
rt300@28 308 // if first event then log it. it won't be, but still.
rt300@28 309 if(theEvents.size() == 0){
rt300@28 310 theEvents.push_back(newEvent);
rt300@28 311 previousEvent = newEvent;
rt300@28 312 return;
rt300@28 313 }
rt300@28 314
rt300@31 315 // if previous event is more than 300ms ago, or event type has changed, log both of them (lots of events on move+zoom combinations!!)
rt300@28 316 int gap = newEvent.eventTime - previousEvent.eventTime;
rt300@28 317 if(gap > 300 || newEvent.eventType != previousEvent.eventType){
rt300@28 318 // if prev event not logged, log it
rt300@31 319 if(theEvents.back().eventTime != previousEvent.eventTime){
rt300@28 320 theEvents.push_back(previousEvent);
rt300@28 321 }
rt300@28 322 theEvents.push_back(newEvent);
rt300@7 323
rt300@28 324 }
rt300@28 325
rt300@28 326 // otherwise only record every Nth event
rt300@28 327 if(eventCounter >= EVENT_THIN_FACTOR){
rt300@28 328 theEvents.push_back(newEvent);
rt300@28 329 eventCounter = 0;
rt300@28 330
rt300@28 331 }
rt300@28 332 eventCounter++;
rt300@28 333 previousEvent = newEvent;
rt300@7 334 }
rt300@8 335 //---------------------------------------------------------------------------
rt300@5 336 void EventLogger::logEvent(const leventType& evtType,const TwoVector& centre, const double& scale, const int& sliderID, const double& sliderVal){
rt300@3 337 //cout << "log: " << evtType << "\n";
rt300@1 338
rt300@1 339 // scroll has 2 double coords
rt300@1 340 // zoom has 1 double scale
rt300@1 341 // save preset has 2 coords
rt300@1 342 // switch view has view type
rt300@9 343 // slider change has int slider index and 1 float value
rt300@1 344
rt300@4 345 // get time for key index
rt300@4 346
rt300@5 347 // thinFactor
rt300@5 348 if(!loggingEnabled) return;
rt300@4 349 switch ( evtType ) {
rt300@28 350 // data thinning here
rt300@28 351 case SCROLL:
rt300@28 352 thinnedLogEvent(lEvent(evtType,centre.x,centre.y));
rt300@28 353 break;
rt300@28 354 case ZOOM:
rt300@28 355 thinnedLogEvent(lEvent(evtType,scale));
rt300@28 356 break;
rt300@28 357 case CHANGE_SLIDER:
rt300@28 358 thinnedLogEvent(lEvent(evtType,sliderVal , 0.0 , sliderID));
rt300@28 359 break;
rt300@29 360 // non thinned data
rt300@4 361 case SAVE_PRESET:
rt300@5 362 theEvents.push_back(lEvent(evtType,centre.x,centre.y));
rt300@4 363 // Code
rt300@4 364 break;
rt300@4 365 case SAVE_DESET:
rt300@5 366 theEvents.push_back(lEvent(evtType,centre.x,centre.y));
rt300@4 367 break;
rt300@28 368
rt300@5 369 case SCROLL_STOPPED:
rt300@5 370 theEvents.push_back(lEvent(evtType,centre.x,centre.y));
rt300@22 371 cout << "SCROLL STOPPED EVENT\n";
rt300@22 372 break;
rt300@22 373 case SNAPPED_TO_PRESET:
rt300@22 374 theEvents.push_back(lEvent(evtType,centre.x,centre.y,sliderID));
rt300@22 375 cout << "SCROLL STOPPED EVENT\n";
rt300@4 376 break;
rt300@25 377 case SWAP_VIEW:
rt300@25 378 theEvents.push_back(lEvent(evtType,0.0 , 0.0 , sliderID)); // slider ID is which view
rt300@25 379 break;
rt300@4 380 default:
rt300@25 381 // default is just an event type with no values
rt300@25 382 theEvents.push_back(lEvent(evtType));
rt300@4 383 break;
rt300@1 384 }
rt300@27 385 if(theEvents.size() > nextUploadQty && !logUploadInProgress){
rt300@27 386 //try to upload asynchronously
rt300@27 387 uploadEventLog(true);
rt300@7 388 }
rt300@9 389 //sessionTime = (ofGetSystemTime() - sessionStartTime);
rt300@28 390 totalInteractionTime = savedInteractionTime + (ofGetSystemTime() - sessionStartTime); // milliseconds
rt300@28 391
rt300@1 392 }
rt300@29 393 //--------------------------------------------------------------------
rt300@29 394 // called from newUser
rt300@29 395 void EventLogger::deleteLogs(){
rt300@29 396 // the
rt300@29 397 theEvents.clear();
rt300@29 398 string fname = ofxiPhoneGetDocumentsDirectory() + EVENT_LOG_FILENAME;
rt300@29 399 ofFile logFile(fname,ofFile::WriteOnly);
rt300@29 400 logFile << "";
rt300@29 401 logFile.close();
rt300@29 402 }
rt300@8 403 //---------------------------------------------------------------------------
rt300@7 404
rt300@7 405 void EventLogger::exitAndSave(){
rt300@25 406
rt300@25 407 if(!consentGiven){
rt300@25 408 logEvent(CONSENT_DENIED);
rt300@25 409 Json::Value jlogs = logsToJson();
rt300@25 410 // try to upload TODO (no - might hang and prevent exit???)
rt300@27 411 uploadEventLog(true);
rt300@25 412 return;
rt300@25 413 }
rt300@25 414 logEvent(APP_EXITED);
rt300@25 415 savedInteractionTime = savedInteractionTime + (ofGetSystemTime() - sessionStartTime);
rt300@7 416 // save user details
rt300@8 417 string fname = ofxiPhoneGetDocumentsDirectory() + EVENT_LOG_FILENAME;
rt300@8 418
rt300@31 419 // try to upload
rt300@31 420 // do it sync because event list needs to be cleared to prevent saving on device
rt300@27 421 uploadEventLog(false);
rt300@7 422
rt300@8 423 // write to file
rt300@25 424 // json without the logs that were uploaded!
rt300@25 425 Json::Value jlogs = logsToJson();
rt300@8 426 ofFile logFile(fname,ofFile::WriteOnly);
rt300@8 427 logFile << jlogs;
rt300@29 428 logFile.close();
rt300@8 429
rt300@8 430 }
rt300@8 431 //---------------------------------------------------------------------------
rt300@8 432
rt300@8 433 Json::Value EventLogger::logsToJson(){
rt300@8 434 // put all logged events into Json formatted string
rt300@8 435 Json::Value root;
rt300@8 436
rt300@8 437 vector<lEvent>::iterator eventIter;
rt300@8 438
rt300@22 439 root["programVersion"] = PROGRAM_VERSION;
rt300@8 440 root["userName"] = userName;
rt300@8 441 root["deviceID"] = deviceID;
rt300@25 442 root["uploadNumber"] = nextUploadNumber;
rt300@22 443 root["iOSdeviceType"] = iOSdeviceType;
rt300@25 444 root["savedInteractionTime"] = savedInteractionTime;
rt300@22 445 root["questionnaireCompleted"] = questionnaireCompleted;
rt300@22 446 root["questionnaireUploaded"] = questionnaireUploaded;
rt300@30 447 root["questionnaire"] = questionnaireToJson();
rt300@30 448
rt300@8 449
rt300@8 450 int i = 0;
rt300@8 451 for(eventIter = theEvents.begin(); eventIter < theEvents.end(); eventIter++){
rt300@8 452 root["events"][i] = (*eventIter).eventToJson();
rt300@8 453 i++;
rt300@7 454 }
rt300@25 455 root["numEventsHere"] = i;
rt300@8 456 return root;
rt300@8 457 }
rt300@22 458
rt300@8 459 //---------------------------------------------------------------------------
rt300@22 460
rt300@22 461 Json::Value EventLogger::questionnaireToJson(){
rt300@22 462 // put all answers into Json formatted string
rt300@22 463 Json::Value root;
rt300@22 464
rt300@22 465 vector<int>::iterator aIter;
rt300@22 466
rt300@22 467 Json::Value questionnaire;
rt300@22 468
rt300@22 469 int i = 0;
rt300@22 470 for(aIter = questionnaireAnswers.begin(); aIter < questionnaireAnswers.end(); aIter++){
rt300@22 471 questionnaire[i] = (*aIter);
rt300@22 472 i++;
rt300@22 473 }
rt300@22 474
rt300@22 475 root["qAnswers"] = questionnaire;
rt300@28 476 root["comments"] = questionnaireComments;
rt300@22 477 root["userName"] = userName;
rt300@22 478 root["deviceID"] = deviceID;
rt300@22 479 root["iOSdeviceType"] = iOSdeviceType;
rt300@22 480 root["programVersion"] = PROGRAM_VERSION;
rt300@22 481
rt300@22 482 return root;
rt300@22 483 }
rt300@30 484
rt300@8 485 //---------------------------------------------------------------------------
rt300@30 486 void EventLogger::printAll(){
rt300@30 487 cout << "-----------------ALL LOGGED EVENTS----------------- \n";
rt300@30 488 vector<lEvent>::iterator evIter;
rt300@30 489 cout << logsToJson() << "\n";
rt300@30 490 cout << "---------------------QUESTIONNAIRE---------------- \n";
rt300@30 491 cout << questionnaireToJson() << "\n";
rt300@30 492 };
rt300@8 493 //---------------------------------------------------------------------------
rt300@32 494
rt300@32 495 void EventLogger::saveSessionToFile(){
rt300@32 496 string fname = ofxiPhoneGetDocumentsDirectory() + userName + '_' + EVENT_LOG_FILENAME;
rt300@32 497
rt300@32 498 // write to file
rt300@32 499 // json without the logs that were uploaded!
rt300@32 500 Json::Value jlogs = logsToJson();
rt300@32 501 ofFile logFile(fname,ofFile::WriteOnly);
rt300@32 502 logFile << jlogs;
rt300@32 503 logFile.close();
rt300@32 504
rt300@32 505 }