/* SleepLib Machine Class Implementation Copyright (c)2011 Mark Watkins <jedimark@users.sourceforge.net> License: GPL */ #include <QApplication> #include <QDir> #include <QProgressBar> #include <QDebug> #include <QString> #include <QObject> #include <time.h> #include "machine.h" #include "profiles.h" #include <algorithm> #include "SleepLib/schema.h" extern QProgressBar * qprogress; ////////////////////////////////////////////////////////////////////////////////////////// // Machine Base-Class implmementation ////////////////////////////////////////////////////////////////////////////////////////// Machine::Machine(Profile *p,MachineID id) { day.clear(); highest_sessionid=0; profile=p; if (!id) { srand(time(NULL)); MachineID temp; do { temp = rand(); } while (profile->machlist.find(temp)!=profile->machlist.end()); m_id=temp; } else m_id=id; //qDebug() << "Create Machine: " << hex << m_id; //%lx",m_id); m_type=MT_UNKNOWN; firstsession=true; } Machine::~Machine() { qDebug() << "Destroy Machine"; for (QMap<QDate,Day *>::iterator d=day.begin();d!=day.end();d++) { delete d.value(); } } Session *Machine::SessionExists(SessionID session) { if (sessionlist.find(session)!=sessionlist.end()) { return sessionlist[session]; } else { return NULL; } } // Find date this session belongs in QDate Machine::pickDate(qint64 first) { QTime split_time=PROFILE.session->daySplitTime(); int combine_sessions=PROFILE.session->combineCloseSessions(); QDateTime d2=QDateTime::fromTime_t(first/1000); QDate date=d2.date(); QTime time=d2.time(); int closest_session=0; if (time<split_time) { date=date.addDays(-1); } else if (combine_sessions > 0) { QMap<QDate,Day *>::iterator dit=day.find(date.addDays(-1)); // Check Day Before if (dit != day.end()) { QDateTime lt=QDateTime::fromTime_t(dit.value()->last()/1000L); closest_session=lt.secsTo(d2)/60; if (closest_session < combine_sessions) { date=date.addDays(-1); } } } return date; } QDate Machine::AddSession(Session *s,Profile *p) { if (!s) { qWarning() << "Empty Session in Machine::AddSession()"; return QDate(); } if (!p) { qWarning() << "Empty Profile in Machine::AddSession()"; return QDate(); } if (s->session()>highest_sessionid) highest_sessionid=s->session(); QTime split_time=PROFILE.session->daySplitTime(); int combine_sessions=PROFILE.session->combineCloseSessions(); int ignore_sessions=PROFILE.session->ignoreShortSessions(); int session_length=s->last()-s->first(); session_length/=60000; sessionlist[s->session()]=s; // To make sure it get's saved later even if it's not wanted. QDateTime d2=QDateTime::fromTime_t(s->first()/1000); QDate date=d2.date(); QTime time=d2.time(); QMap<QDate,Day *>::iterator dit,nextday; bool combine_next_day=false; int closest_session=0; if (time<split_time) { date=date.addDays(-1); } else if (combine_sessions > 0) { dit=day.find(date.addDays(-1)); // Check Day Before if (dit!=day.end()) { QDateTime lt=QDateTime::fromTime_t(dit.value()->last()/1000); closest_session=lt.secsTo(d2)/60; if (closest_session<combine_sessions) { date=date.addDays(-1); } } else { nextday=day.find(date.addDays(1));// Check Day Afterwards if (nextday!=day.end()) { QDateTime lt=QDateTime::fromTime_t(nextday.value()->first()/1000); closest_session=d2.secsTo(lt)/60; if (closest_session < combine_sessions) { // add todays here. pull all tomorrows records to this date. combine_next_day=true; } } } } if (session_length<ignore_sessions) { //if (!closest_session || (closest_session>=60)) return QDate(); } if (!firstsession) { if (firstday>date) firstday=date; if (lastday<date) lastday=date; } else { firstday=lastday=date; firstsession=false; } Day *dd=NULL; dit=day.find(date); if (dit==day.end()) { //QString dstr=date.toString("yyyyMMdd"); //qDebug("Adding Profile Day %s",dstr.toAscii().data()); dd=new Day(this); day[date]=dd; // Add this Day record to profile p->AddDay(date,dd,m_type); } else { dd=*dit; } dd->AddSession(s); if (combine_next_day) { for (QVector<Session *>::iterator i=nextday.value()->begin();i!=nextday.value()->end();i++) { dd->AddSession(*i); } QMap<QDate,QList<Day *> >::iterator nd=p->daylist.find(date.addDays(1)); for (QList<Day *>::iterator i=nd->begin();i!=nd->end();i++) { if (*i==nextday.value()) { nd.value().erase(i); } } day.erase(nextday); } return date; } // This functions purpose is murder and mayhem... It deletes all of a machines data. // Therefore this is the most dangerous function in this software.. bool Machine::Purge(int secret) { // Boring api key to stop this function getting called by accident :) if (secret!=3478216) return false; // It would be joyous if this function screwed up.. QString path=profile->Get(properties[STR_PROP_Path]); //STR_GEN_DataFolder)+"/"+m_class+"_"; //if (properties.contains(STR_PROP_Serial)) path+=properties[STR_PROP_Serial]; else path+=hexid(); QDir dir(path); if (!dir.exists()) // It doesn't exist anyway. return true; if (!dir.isReadable()) return false; qDebug() << "Purging " << QDir::toNativeSeparators(path); dir.setFilter(QDir::Files | QDir::Hidden | QDir::NoSymLinks); dir.setSorting(QDir::Name); QFileInfoList list=dir.entryInfoList(); int could_not_kill=0; for (int i=0;i<list.size();++i) { QFileInfo fi=list.at(i); QString fullpath=fi.canonicalFilePath(); //int j=fullpath.lastIndexOf("."); QString ext_s=fullpath.section('.',-1);//right(j); bool ok; ext_s.toInt(&ok,10); if (ok) { qDebug() << "Deleting " << fullpath; dir.remove(fullpath); } else could_not_kill++; } if (could_not_kill>0) { // qWarning() << "Could not purge path\n" << path << "\n\n" << could_not_kill << " file(s) remain.. Suggest manually deleting this path\n"; // return false; } return true; } const quint32 channel_version=1; bool Machine::Load() { QString path=profile->Get(properties[STR_PROP_Path]); //STR_GEN_DataFolder)+"/"+m_class+"_"+hexid(); QDir dir(path); qDebug() << "Loading " << path; if (!dir.exists() || !dir.isReadable()) return false; dir.setFilter(QDir::Files | QDir::Hidden | QDir::NoSymLinks); dir.setSorting(QDir::Name); QFileInfoList list=dir.entryInfoList(); typedef QVector<QString> StringList; QMap<SessionID,StringList> sessfiles; QMap<SessionID,StringList>::iterator s; QString fullpath,ext_s,sesstr; int ext; SessionID sessid; bool ok; for (int i=0;i<list.size();i++) { QFileInfo fi=list.at(i); fullpath=fi.canonicalFilePath(); ext_s=fi.fileName().section(".",-1); ext=ext_s.toInt(&ok,10); if (!ok) continue; sesstr=fi.fileName().section(".",0,-2); sessid=sesstr.toLong(&ok,16); if (!ok) continue; if (sessfiles[sessid].capacity()==0) sessfiles[sessid].resize(3); sessfiles[sessid][ext]=fi.canonicalFilePath(); } int size=sessfiles.size(); int cnt=0; for (s=sessfiles.begin(); s!=sessfiles.end(); s++) { cnt++; if ((cnt % 10)==0) { if (qprogress) qprogress->setValue((float(cnt)/float(size)*100.0)); } QApplication::processEvents(); Session *sess=new Session(this,s.key()); if (sess->LoadSummary(s.value()[0])) { sess->SetEventFile(s.value()[1]); //sess->OpenEvents(); AddSession(sess,profile); } else { qWarning() << "Error unpacking summary data"; delete sess; } } if (qprogress) qprogress->setValue(100); return true; } bool Machine::SaveSession(Session *sess) { QString path=profile->Get(properties[STR_PROP_Path]); //STR_GEN_DataFolder)+"/"+m_class+"_"+hexid(); if (sess->IsChanged()) sess->Store(path); return true; } bool Machine::Save() { //int size; int cnt=0; QString path=profile->Get(properties[STR_PROP_Path]); //STR_GEN_DataFolder)+"/"+m_class+"_"+hexid(); QDir dir(path); if (!dir.exists()) { dir.mkdir(path); } QHash<SessionID,Session *>::iterator s; m_savelist.clear(); for (s=sessionlist.begin(); s!=sessionlist.end(); s++) { cnt++; if ((*s)->IsChanged()) { m_savelist.push_back(*s); } } savelistCnt=0; savelistSize=m_savelist.size(); if (!PROFILE.session->multithreading()) { for (int i=0;i<savelistSize;i++) { qprogress->setValue(0+(float(savelistCnt)/float(savelistSize)*100.0)); QApplication::processEvents(); Session *s=m_savelist.at(i); s->UpdateSummaries(); s->Store(path); s->TrashEvents(); savelistCnt++; } return true; } int threads=QThread::idealThreadCount(); savelistSem=new QSemaphore(threads); savelistSem->acquire(threads); QVector<SaveThread*>thread; for (int i=0;i<threads;i++) { thread.push_back(new SaveThread(this,path)); QObject::connect(thread[i],SIGNAL(UpdateProgress(int)),qprogress,SLOT(setValue(int))); thread[i]->start(); } while (!savelistSem->tryAcquire(threads,250)) { if (qprogress) { // qprogress->setValue(66.0+(float(savelistCnt)/float(savelistSize)*33.0)); QApplication::processEvents(); } } for (int i=0;i<threads;i++) { while (thread[i]->isRunning()) { SaveThread::msleep(250); QApplication::processEvents(); } delete thread[i]; } delete savelistSem; return true; } /*SaveThread::SaveThread(Machine *m,QString p) { machine=m; path=p; } */ void SaveThread::run() { while (Session *sess=machine->popSaveList()) { int i=(float(machine->savelistCnt)/float(machine->savelistSize)*100.0); emit UpdateProgress(i); sess->UpdateSummaries(); sess->Store(path); sess->TrashEvents(); } machine->savelistSem->release(1); } Session *Machine::popSaveList() { Session *sess=NULL; savelistMutex.lock(); if (m_savelist.size()>0) { sess=m_savelist.at(0); m_savelist.pop_front(); savelistCnt++; } savelistMutex.unlock(); return sess; } ////////////////////////////////////////////////////////////////////////////////////////// // CPAP implmementation ////////////////////////////////////////////////////////////////////////////////////////// CPAP::CPAP(Profile *p,MachineID id):Machine(p,id) { m_type=MT_CPAP; } CPAP::~CPAP() { } ////////////////////////////////////////////////////////////////////////////////////////// // Oximeter Class implmementation ////////////////////////////////////////////////////////////////////////////////////////// Oximeter::Oximeter(Profile *p,MachineID id):Machine(p,id) { m_type=MT_OXIMETER; } Oximeter::~Oximeter() { } ////////////////////////////////////////////////////////////////////////////////////////// // SleepStage Class implmementation ////////////////////////////////////////////////////////////////////////////////////////// SleepStage::SleepStage(Profile *p,MachineID id):Machine(p,id) { m_type=MT_SLEEPSTAGE; } SleepStage::~SleepStage() { } ChannelID NoChannel, SESSION_ENABLED; ChannelID CPAP_IPAP, CPAP_IPAPLo, CPAP_IPAPHi, CPAP_EPAP, CPAP_Pressure, CPAP_PS, CPAP_Mode, CPAP_AHI, CPAP_PressureMin, CPAP_PressureMax, CPAP_RampTime, CPAP_RampPressure, CPAP_Obstructive, CPAP_Hypopnea, CPAP_ClearAirway, CPAP_Apnea, CPAP_CSR, CPAP_LeakFlag, CPAP_ExP, CPAP_NRI, CPAP_VSnore, CPAP_VSnore2, CPAP_RERA, CPAP_PressurePulse, CPAP_FlowLimit, CPAP_FlowRate, CPAP_MaskPressure, CPAP_MaskPressureHi, CPAP_RespEvent, CPAP_Snore, CPAP_MinuteVent, CPAP_RespRate, CPAP_TidalVolume, CPAP_PTB, CPAP_Leak, CPAP_LeakMedian, CPAP_LeakTotal, CPAP_MaxLeak, CPAP_FLG, CPAP_IE, CPAP_Te, CPAP_Ti, CPAP_TgMV, CPAP_UserFlag1, CPAP_UserFlag2, CPAP_BrokenSummary, CPAP_BrokenWaveform, CPAP_RDI, CPAP_PresReliefSet, CPAP_PresReliefMode, CPAP_PresReliefType, CPAP_PSMin, CPAP_PSMax; ChannelID RMS9_E01, RMS9_E02, RMS9_EPR, RMS9_EPRSet, RMS9_SetPressure; ChannelID INTP_SmartFlex; ChannelID INTELLIPAP_Unknown1, INTELLIPAP_Unknown2; ChannelID PRS1_00, PRS1_01, PRS1_08, PRS1_0A, PRS1_0B, PRS1_0C, PRS1_0E, PRS1_0F, PRS1_10, PRS1_12, PRS1_FlexMode, PRS1_FlexSet, PRS1_HumidStatus, PRS1_HumidSetting, PRS1_SysLock, PRS1_SysOneResistStat, PRS1_SysOneResistSet, PRS1_HoseDiam, PRS1_AutoOn, PRS1_AutoOff, PRS1_MaskAlert, PRS1_ShowAHI; ChannelID OXI_Pulse, OXI_SPO2, OXI_PulseChange, OXI_SPO2Drop, OXI_Plethy; ChannelID Journal_Notes, Journal_Weight, Journal_BMI, Journal_ZombieMeter, Bookmark_Start, Bookmark_End, Bookmark_Notes;