diff --git a/sleepyhead/Graphs/gGraph.cpp b/sleepyhead/Graphs/gGraph.cpp index 2e2fa10e..4202b3aa 100644 --- a/sleepyhead/Graphs/gGraph.cpp +++ b/sleepyhead/Graphs/gGraph.cpp @@ -516,15 +516,19 @@ void gGraph::roundY(EventDataType &miny, EventDataType &maxy) int m, t; bool ymin_good = false, ymax_good = false; + // rec_miny/maxy are the graph settings defined in preferences if (rec_miny != rec_maxy) { + // Clip min if (miny > rec_miny) { miny = rec_miny; } + // Clip max if (maxy < rec_maxy) { maxy = rec_maxy; } + // if (miny == rec_miny) { ymin_good = true; } @@ -534,6 +538,7 @@ void gGraph::roundY(EventDataType &miny, EventDataType &maxy) } } + // Have no minx/miny reference, have to create one if (maxy == miny) { m = ceil(maxy / 2.0); t = m * 2; @@ -561,6 +566,13 @@ void gGraph::roundY(EventDataType &miny, EventDataType &maxy) miny = t; } + + if (miny < 0) { + EventDataType tmp = qMax(qAbs(miny), qAbs(maxy)); + maxy = tmp; + miny = -tmp; + } + return; } @@ -609,6 +621,13 @@ void gGraph::roundY(EventDataType &miny, EventDataType &maxy) } } + if (miny < 0) { + EventDataType tmp = qMax(qAbs(miny), qAbs(maxy)); + maxy = tmp; + miny = -tmp; + } + + //if (m_enforceMinY) { miny=f_miny; } //if (m_enforceMaxY) { maxy=f_maxy; } } diff --git a/sleepyhead/Graphs/gLineChart.cpp b/sleepyhead/Graphs/gLineChart.cpp index d7b4f39f..6fb1e964 100644 --- a/sleepyhead/Graphs/gLineChart.cpp +++ b/sleepyhead/Graphs/gLineChart.cpp @@ -194,9 +194,17 @@ void gLineChart::paint(QPainter &painter, gGraph &w, const QRegion ®ion) } else { miny = w.min_y, maxy = w.max_y; } - w.roundY(miny, maxy); +#define DEBUG_AUTOSCALER +#ifdef DEBUG_AUTOSCALER + QString a = QString().sprintf("%.2f - %.2f",miny, maxy); + w.renderText(a,width/2,top-5); +#endif + + // the middle of minx and maxy does not have to be the center... + + double xx = maxx - minx; double xmult = double(width) / xx; diff --git a/sleepyhead/Graphs/gYAxis.cpp b/sleepyhead/Graphs/gYAxis.cpp index 15889388..4c5e62e8 100644 --- a/sleepyhead/Graphs/gYAxis.cpp +++ b/sleepyhead/Graphs/gYAxis.cpp @@ -69,12 +69,12 @@ void gXGrid::paint(QPainter &painter, gGraph &w, const QRegion ®ion) double max_yticks = round(height / (y + 14.0)); // plus spacing between lines //double yt=1/max_yticks; - double mxy = MAX(fabs(maxy), fabs(miny)); + double mxy = maxy; //MAX(fabs(maxy), fabs(miny)); double mny = miny; - if (miny < 0) { - mny = -mxy; - } +// if (miny < 0) { +// mny = -mxy; +// } double rxy = mxy - mny; @@ -304,13 +304,8 @@ void gYAxis::paint(QPainter &painter, gGraph &w, const QRegion ®ion) miny = w.physMinY(); maxy = w.physMaxY(); } else { - miny = w.min_y; maxy = w.max_y; - - if (miny < 0) { // even it up if it's starts negative - miny = -MAX(fabs(miny), fabs(maxy)); - } } w.roundY(miny, maxy); @@ -322,11 +317,11 @@ void gYAxis::paint(QPainter &painter, gGraph &w, const QRegion ®ion) double max_yticks = round(height / (y + 14.0)); // plus spacing between lines - double mxy = MAX(fabs(maxy), fabs(miny)); + double mxy = maxy; // MAX(fabs(maxy), fabs(miny)); double mny = miny; if (miny < 0) { - mny = -mxy; +// mny = -mxy; } double rxy = mxy - mny; diff --git a/sleepyhead/SleepLib/event.cpp b/sleepyhead/SleepLib/event.cpp index b5b39f4b..f4fc44f1 100644 --- a/sleepyhead/SleepLib/event.cpp +++ b/sleepyhead/SleepLib/event.cpp @@ -162,33 +162,43 @@ void EventList::AddWaveform(qint64 start, qint16 *data, int recs, qint64 duratio m_count += recs; m_data.resize(m_count); - EventStoreType *edata = m_data.data(); + // EventStoreType *edata = m_data.data(); EventStoreType raw; - qint16 *ep = data + recs; - qint16 *sp; - EventStoreType *dp = &edata[r]; +// qint16 *ep = data + recs; + qint16 *sp = data; +// EventStoreType *dp = &edata[r]; if (m_update_minmax) { EventDataType min = m_min, max = m_max, val, gain = m_gain; - for (sp = data; sp < ep; ++sp) { - *dp++ = raw = *sp; + for (int i=0; i < recs; ++i ) { + m_data[i] = raw = *sp; val = EventDataType(raw) * gain + m_offset; - if (min > val) { min = val; } - if (max < val) { max = val; } + sp++; } +// for (sp = data; sp < ep; ++sp) { +// *dp++ = raw = *sp; +// val = EventDataType(raw) * gain + m_offset; + +// if (min > val) { min = val; } + +// if (max < val) { max = val; } +// } m_min = min; m_max = max; } else { //register EventDataType val,gain=m_gain; - for (sp = data; sp < ep; ++sp) { - *dp++ = *sp; - //val=EventDataType(raw)*gain; + for (int i=0; i < recs; ++i) { + m_data[i] = *sp++; } +// for (sp = data; sp < ep; ++sp) { +// *dp++ = *sp; +// //val=EventDataType(raw)*gain; +// } } } diff --git a/sleepyhead/SleepLib/loader_plugins/cms50_loader.cpp b/sleepyhead/SleepLib/loader_plugins/cms50_loader.cpp index 61ab5e93..6fbfc479 100644 --- a/sleepyhead/SleepLib/loader_plugins/cms50_loader.cpp +++ b/sleepyhead/SleepLib/loader_plugins/cms50_loader.cpp @@ -42,6 +42,13 @@ CMS50Loader::CMS50Loader() CMS50Loader::~CMS50Loader() { } + +bool CMS50Loader::Detect(const QString &path) +{ + Q_UNUSED(path); + return false; +} + int CMS50Loader::Open(QString &path, Profile *profile) { // CMS50 folder structure detection stuff here. @@ -66,293 +73,12 @@ int CMS50Loader::Open(QString &path, Profile *profile) && dir.exists("Data")) { // SPO2Review/etc software - return OpenCMS50(tmp, profile); + // return OpenCMS50(tmp, profile); } return 0; } -int CMS50Loader::OpenCMS50(QString &path, Profile *profile) -{ - QString filename, pathname; - QList files; - QDir dir(path); - if (!dir.exists()) { - return 0; - } - - if (qprogress) { qprogress->setValue(0); } - - dir.setFilter(QDir::Files | QDir::Hidden | QDir::NoSymLinks); - dir.setSorting(QDir::Name); - QFileInfoList flist = dir.entryInfoList(); - - QString fn; - - for (int i = 0; i < flist.size(); i++) { - QFileInfo fi = flist.at(i); - fn = fi.fileName().toLower(); - - if (fn.endsWith(".spor") || fn.endsWith(".spo2")) { - files.push_back(fi.canonicalFilePath()); - } - - //if (loader_progress) loader_progress->Pulse(); - - } - - int size = files.size(); - - if (size == 0) { return 0; } - - Machine *mach = CreateMachine(profile); - int cnt = 0; - - for (QList::iterator n = files.begin(); n != files.end(); n++, ++cnt) { - if (qprogress) { qprogress->setValue((float(cnt) / float(size) * 50.0)); } - - QApplication::processEvents(); - OpenSPORFile((*n), mach, profile); - } - - mach->Save(); - - if (qprogress) { qprogress->setValue(100); } - - return 1; -} - -bool CMS50Loader::OpenSPORFile(QString path, Machine *mach, Profile *profile) -{ - if (!mach || !profile) { - return false; - } - - QFile f(path); - unsigned char tmp[256]; - - qint16 data_starts; - qint16 some_code; - qint16 some_more_code; - int seconds = 0, num_records; - int br; - - if (!f.open(QIODevice::ReadOnly)) { - return false; - } - - // Find everything after the last _ - - QString str = path.section("/", -1); - str = str.section("_", -1); - str = str.section(".", 0, 0); - - QDateTime dt; - - if (str.length() == 14) { - dt = QDateTime::fromString(str, "yyyyMMddHHmmss"); - } else if (str.length() == 12) { - dt = QDateTime::fromString(str, "yyyyMMddHHmm"); - } else { - qDebug() << "CMS50::Spo[r2] Dodgy date field"; - return false; - } - - if (!dt.isValid()) { - return false; - } - - SessionID sessid = dt.toTime_t(); // Import date becomes session id - - if (mach->SessionExists(sessid)) { - return false; // Already imported - } - - br = f.read((char *)tmp, 2); - - if (br != 2) { return false; } - - data_starts = tmp[0] | (tmp[1] << 8); - - br = f.read((char *)tmp, 2); - - if (br != 2) { return false; } - - some_code = tmp[0] | (tmp[1] << 8); // 512 or 256 observed - Q_UNUSED(some_code); - - br = f.read((char *)tmp, 2); - - if (br != 2) { return false; } - - seconds = tmp[0] | (tmp[1] << 8); - - if (!seconds) { - num_records = (f.size() - data_starts); - seconds = num_records / 2; - } else { - num_records = seconds << 1; - } - - if (seconds < 60) { - // Don't bother importing short sessions - return false; - } - - br = f.read((char *)tmp, 2); - - if (br != 2) { return false; } - - some_more_code = tmp[0] | (tmp[1] << 8); // == 0 - Q_UNUSED(some_more_code); - - br = f.read((char *)tmp, 34); // Read widechar date record - - if (br != 34) { return false; } - - for (int i = 0; i < 17; i++) { // Convert to 8bit - tmp[i] = tmp[i << 1]; - } - - tmp[17] = 0; - QString datestr = (char *)tmp; - QDateTime date; - qint64 starttime; - - if (datestr.isEmpty()) { // Has Internal date record, so use it - date = QDateTime::fromString(datestr, "MM/dd/yy HH:mm:ss"); - QDate d2 = date.date(); - - if (d2.year() < 2000) { // Nice to see CMS50 is Y2K friendly.. - d2.setDate(d2.year() + 100, d2.month(), d2.day()); - date.setDate(d2); - } - - if (!date.isValid()) { - qDebug() << "Invalid date time retreieved in CMS50::OpenSPO[R2]File"; - return false; - } - - starttime = qint64(date.toTime_t()) * 1000L; - } else if (dt.isValid()) { // Else take the filenames date - date = dt; - starttime = qint64(dt.toTime_t()) * 1000L; - } else { // Has nothing, so add it up to current time - qDebug() << "CMS50: Couldn't get any start date indication"; - date = QDateTime::currentDateTime(); - date = date.addSecs(-seconds); - starttime = qint64(date.toTime_t()) * 1000L; - } - - f.seek(data_starts); - - buffer = new char [num_records]; - br = f.read(buffer, num_records); - - if (br != num_records) { - qDebug() << "Short .spo[R2] File: " << path; - delete [] buffer; - return false; - } - - //QDateTime last_pulse_time=date; - //QDateTime last_spo2_time=date; - - EventDataType last_pulse = buffer[0]; - EventDataType last_spo2 = buffer[1]; - EventDataType cp = 0, cs = 0; - - Session *sess = new Session(mach, sessid); - sess->updateFirst(starttime); - EventList *oxip = sess->AddEventList(OXI_Pulse, EVL_Event); - EventList *oxis = sess->AddEventList(OXI_SPO2, EVL_Event); - - oxip->AddEvent(starttime, last_pulse); - oxis->AddEvent(starttime, last_spo2); - - EventDataType PMin = 0, PMax = 0, SMin = 0, SMax = 0, PAvg = 0, SAvg = 0; - int PCnt = 0, SCnt = 0; - qint64 tt = starttime; - //fixme: Need two lasttime values here.. - qint64 lasttime = starttime; - - bool first_p = true, first_s = true; - - for (int i = 2; i < num_records; i += 2) { - cp = buffer[i]; - cs = buffer[i + 1]; - - if (last_pulse != cp) { - oxip->AddEvent(tt, cp); - - if (tt > lasttime) { lasttime = tt; } - - if (cp > 0) { - if (first_p) { - PMin = cp; - first_p = false; - } else { - if (PMin > cp) { PMin = cp; } - } - - PAvg += cp; - PCnt++; - } - } - - if (last_spo2 != cs) { - oxis->AddEvent(tt, cs); - - if (tt > lasttime) { lasttime = tt; } - - if (cs > 0) { - if (first_s) { - SMin = cs; - first_s = false; - } else { - if (SMin > cs) { SMin = cs; } - } - - SAvg += cs; - SCnt++; - } - } - - last_pulse = cp; - last_spo2 = cs; - - if (PMax < cp) { PMax = cp; } - - if (SMax < cs) { SMax = cs; } - - tt += 1000; // An educated guess of 1 second. Verified by gcz@cpaptalk - } - - if (cp) { oxip->AddEvent(tt, cp); } - - if (cs) { oxis->AddEvent(tt, cs); } - - sess->updateLast(tt); - - EventDataType pa = 0, sa = 0; - - if (PCnt > 0) { pa = PAvg / double(PCnt); } - - if (SCnt > 0) { sa = SAvg / double(SCnt); } - - sess->setMin(OXI_Pulse, PMin); - sess->setMax(OXI_Pulse, PMax); - sess->setAvg(OXI_Pulse, pa); - sess->setMin(OXI_SPO2, SMin); - sess->setMax(OXI_SPO2, SMax); - sess->setAvg(OXI_SPO2, sa); - - mach->AddSession(sess, profile); - sess->SetChanged(true); - delete [] buffer; - - return true; -} Machine *CMS50Loader::CreateMachine(Profile *profile) { if (!profile) { @@ -396,7 +122,6 @@ void CMS50Loader::Register() qDebug() << "Registering CMS50Loader"; RegisterLoader(new CMS50Loader()); - //InitModelMap(); cms50_initialized = true; } diff --git a/sleepyhead/SleepLib/loader_plugins/cms50_loader.h b/sleepyhead/SleepLib/loader_plugins/cms50_loader.h index e329c1fb..eeffe3a4 100644 --- a/sleepyhead/SleepLib/loader_plugins/cms50_loader.h +++ b/sleepyhead/SleepLib/loader_plugins/cms50_loader.h @@ -29,9 +29,9 @@ class CMS50Loader : public MachineLoader CMS50Loader(); virtual ~CMS50Loader(); - virtual bool Detect(const QString &path) { Q_UNUSED(path); return false; } // bypass autoscanner - + virtual bool Detect(const QString &path); virtual int Open(QString &path, Profile *profile); + static void Register(); virtual int Version() { return cms50_data_version; } @@ -42,7 +42,7 @@ class CMS50Loader : public MachineLoader protected: int OpenCMS50(QString &path, Profile *profile); - bool OpenSPORFile(QString path, Machine *machine, Profile *profile); +// bool OpenSPORFile(QString path, Machine *machine, Profile *profile); private: char *buffer; diff --git a/sleepyhead/SleepLib/loader_plugins/resmed_loader.cpp b/sleepyhead/SleepLib/loader_plugins/resmed_loader.cpp index c7cadb62..982c905f 100644 --- a/sleepyhead/SleepLib/loader_plugins/resmed_loader.cpp +++ b/sleepyhead/SleepLib/loader_plugins/resmed_loader.cpp @@ -190,9 +190,13 @@ void ResmedLoader::ParseSTR(Machine *mach, QStringList strfiles) continue; } bool skip = false; + QMap::iterator sid = strsess.find(ontime); + // Record already exists? if (sid != strsess.end()) { - skip=true; + // then skip + laston = ontime; + continue; } // For every mask on, there will be a session within 1 minute either way @@ -204,17 +208,23 @@ void ResmedLoader::ParseSTR(Machine *mach, QStringList strfiles) R.maskoff = offtime; } - if (sig = str.lookupLabel("Mask Dur")) { + if ((sig = str.lookupLabel("Mask Dur"))) { R.maskdur = EventDataType(sig->data[rec]) * sig->gain + sig->offset; } - if (sig == str.lookupLabel("Leak Med")) { - R.leakmed = EventDataType(sig->data[rec]) * sig->gain + sig->offset; + if ((sig = str.lookupLabel("Leak Med"))) { + float gain = sig->gain * 60.0; + R.leakgain = gain; + R.leakmed = EventDataType(sig->data[rec]) * gain + sig->offset; } - if (sig == str.lookupLabel("Leak Max")) { - R.leakmax = EventDataType(sig->data[rec]) * sig->gain + sig->offset; + if ((sig = str.lookupLabel("Leak Max"))) { + float gain = sig->gain * 60.0; + R.leakgain = gain; + R.leakmax = EventDataType(sig->data[rec]) * gain + sig->offset; } - if (sig == str.lookupLabel("Leak 95")) { - R.leak95 = EventDataType(sig->data[rec]) * sig->gain + sig->offset; + if ((sig = str.lookupLabel("Leak 95"))) { + float gain = sig->gain * 60.0; + R.leakgain = gain; + R.leak95 = EventDataType(sig->data[rec]) * gain + sig->offset; } @@ -293,14 +303,12 @@ void ResmedLoader::ParseSTR(Machine *mach, QStringList strfiles) } laston = ontime; - if (skip) continue; - QDateTime dontime = QDateTime::fromTime_t(ontime); date = dontime.date(); R.date = date; - strsess[ontime] = R; - strdate[date].push_back(&strsess[ontime]); + strdate[date].push_back(&strsess.insert(ontime, R).value()); + QDateTime dofftime = QDateTime::fromTime_t(offtime); qDebug() << "Mask on" << dontime << "Mask off" << dofftime; @@ -563,25 +571,131 @@ badfile: return false; } -struct EDFGroup { - EDFGroup() { } - EDFGroup(QString brp, QString eve, QString pld, QString sad) { - BRP = brp; - EVE = eve; - PLD = pld; - SAD = sad; + +void ResmedImport::run() +{ + Session * sess = mach->SessionExists(sessionid); + if (sess) { + if (sess->setting(CPAP_SummaryOnly).toBool()) { + // Reuse this session + sess->wipeSummary(); + } else { + // Already imported + return; + } + } else { + // Could be importing from an older backup.. if so, destroy the summary only records + quint32 key = int(sessionid / 60) * 60; + sess = mach->SessionExists(key); + if (sess) { + if (sess->setting(CPAP_SummaryOnly).toBool()) { + sess->Destroy(); + delete sess; + } + } + + // Create the session + sess = new Session(mach, sessionid); } - EDFGroup(const EDFGroup & copy) { - BRP = copy.BRP; - EVE = copy.EVE; - PLD = copy.PLD; - SAD = copy.SAD; + + if (!group.EVE.isEmpty()) { + loader->LoadEVE(sess, group.EVE); } - QString BRP; - QString EVE; - QString PLD; - QString SAD; -}; + if (!group.BRP.isEmpty()) { + loader->LoadBRP(sess, group.BRP); + } + if (!group.PLD.isEmpty()) { + loader->LoadPLD(sess, group.PLD); + } + if (!group.SAD.isEmpty()) { + loader->LoadSAD(sess, group.SAD); + } + + if (!sess->first()) { + delete sess; + return; + } + + sess->settings[CPAP_SummaryOnly] = false; + sess->SetChanged(true); + + ///////////////////////////////////////////////////////////////////////////////// + // Process STR.edf now all valid Session data is imported + ///////////////////////////////////////////////////////////////////////////////// + + quint32 key = quint32(sessionid / 60) * 60; // round to 1 minute + + QMap::iterator strsess_end = loader->strsess.end(); + QMap::iterator it = loader->strsess.find(key); + + if (it == strsess_end) { + // ResMed merges mask on/off groups that are less than a minute apart + // this means have to jump back to the last session closest. + + it = loader->strsess.lowerBound(key); + if (it != loader->strsess.begin()) it--; + } + + if (it != strsess_end) { + STRRecord & R = it.value(); + + // calculate the time between session record and mask-on record. + int gap = sessionid - R.maskon; + + if (gap > 3600*6) { + QDateTime dt = QDateTime::fromTime_t(sessionid); + QDateTime rt = QDateTime::fromTime_t(R.maskon); + + qDebug() << "Warning: Closest matching STR record for" << dt << (sess->length() / 1000L) << "is" << rt << "by" << gap << "seconds"; + } + + // Claim this session + R.sessionid = sessionid; + + // Save maskon time in session setting so we can use it later to avoid doubleups. + sess->settings[RMS9_MaskOnTime] = R.maskon; + + // Grab all the system settings + if (R.set_pressure >= 0) sess->settings[RMS9_SetPressure] = R.set_pressure; + if (R.min_pressure >= 0) sess->settings[CPAP_PressureMin] = R.min_pressure; + if (R.max_pressure >= 0) sess->settings[CPAP_PressureMax] = R.max_pressure; + if (R.ps >= 0) sess->settings[CPAP_PS] = R.ps; + if (R.min_ps >= 0) sess->settings[CPAP_PSMin] = R.min_ps; + if (R.max_ps >= 0) sess->settings[CPAP_PSMax] = R.max_ps; + if (R.epap >= 0) sess->settings[CPAP_EPAP] = R.epap; + if (R.max_epap >= 0) sess->settings[CPAP_EPAPHi] = R.max_epap; + if (R.min_epap >= 0) sess->settings[CPAP_EPAPLo] = R.min_epap; + if (R.ipap >= 0) sess->settings[CPAP_IPAP] = R.ipap; + if (R.max_ipap >= 0) sess->settings[CPAP_IPAPHi] = R.max_ipap; + if (R.min_ipap >= 0) sess->settings[CPAP_IPAPLo] = R.min_ipap; + if (R.mode >= 0) sess->settings[CPAP_Mode] = R.mode; + if (R.epr >= 0) sess->settings[RMS9_EPR] = R.epr; + if (R.epr_set >= 0) sess->settings[RMS9_EPRSet] = R.epr_set; + + // Ignore all the rest of the sumary data, because there is enough available to calculate it with higher accuracy. + + if (sess->length() > 0) { + if (!mach->AddSession(sess, p_profile)) { + delete sess; + return; + } + } else { + delete sess; + return; + } + } + + // Update indexes, process waveform and perform flagging + sess->UpdateSummaries(); + + // Save is not threadsafe + loader->saveMutex.lock(); + sess->Store(p_profile->Get(mach->properties[STR_PROP_Path])); + loader->saveMutex.unlock(); + + // Free the memory used by this session + sess->TrashEvents(); +} ResmedLoader::ResmedLoader() { @@ -635,6 +749,8 @@ Machine *ResmedLoader::CreateMachine(QString serial, Profile *profile) return m; } + + long event_cnt = 0; const QString RMS9_STR_datalog = "DATALOG"; @@ -788,19 +904,21 @@ int ResmedLoader::Open(QString &path, Profile *profile) dir.setFilter(QDir::Files | QDir::Hidden | QDir::Readable); QFileInfoList flist = dir.entryInfoList(); - for (int i = 0; i < flist.size(); i++) { + { + int size = flist.size(); + for (int i = 0; i < size; i++) { QFileInfo fi = flist.at(i); filename = fi.fileName(); if (filename.startsWith("STR", Qt::CaseInsensitive)) { strfiles.push_back(fi.filePath()); } } + } strsess.clear(); ParseSTR(m, strfiles); EDFParser stredf(strpath); - if (!stredf.Parse()) { qDebug() << "Faulty file" << RMS9_STR_strfile; return 0; @@ -1043,7 +1161,7 @@ int ResmedLoader::Open(QString &path, Profile *profile) if (qprogress) { if ((i % 5) == 0) { - qprogress->setValue((float(i + 1) / float(size) * 10.0)); + qprogress->setValue((float(i + 1) / float(size) * 100.0)); QApplication::processEvents(); } } @@ -1051,15 +1169,10 @@ int ResmedLoader::Open(QString &path, Profile *profile) } - QString fn; Session *sess; int cnt = 0; size = filegroups.size(); - QHash sessday; - - EDFSignal *sig; - backup_path += RMS9_STR_datalog + "/"; // Have to sacrifice these features to get access to summary data. @@ -1067,152 +1180,14 @@ int ResmedLoader::Open(QString &path, Profile *profile) p_profile->session->setDaySplitTime(QTime(12,0,0)); p_profile->session->setIgnoreShortSessions(false); - QList trashstr; // list of strsess records to destroy afterwards - - ///////////////////////////////////////////////////////////////////////////// // Scan through new file list and import sessions ///////////////////////////////////////////////////////////////////////////// + m_totaltasks = filegroups.size(); for (fgit = filegroups.begin(); fgit != filegroups.end(); ++fgit) { - sessionid = fgit.key(); - - sess = m->SessionExists(sessionid); - if (sess) { - if (sess->setting(CPAP_SummaryOnly).toBool()) { - // Reuse this session - sess->wipeSummary(); - } else { - continue; - } - } else { - // Could be importing from an older backup.. if so, destroy the summary only records - quint32 key = int(sessionid / 60) * 60; - sess = m->SessionExists(key); - if (sess) { - if (sess->setting(CPAP_SummaryOnly).toBool()) { - sess->Destroy(); - delete sess; - } - } - - // Create the session - sess = new Session(m, sessionid); - } - - - if (!fgit.value().EVE.isEmpty()) { - EDFParser edf(fgit.value().EVE); - if (edf.Parse()) LoadEVE(sess,edf); - } - if (!fgit.value().BRP.isEmpty()) { - EDFParser edf(fgit.value().BRP); - if (edf.Parse()) LoadBRP(sess,edf); - } - if (!fgit.value().PLD.isEmpty()) { - EDFParser edf(fgit.value().PLD); - if (edf.Parse()) LoadPLD(sess,edf); - } - if (!fgit.value().SAD.isEmpty()) { - EDFParser edf(fgit.value().SAD); - if (edf.Parse()) LoadSAD(sess,edf); - } - - if ((++cnt % 10) == 0) { - // TODO: Change me to emit once MachineLoader is QObjectified... - if (qprogress) { qprogress->setValue(10.0 + (float(cnt) / float(size) * 90.0)); } - - QApplication::processEvents(); - } - - int mode = 0; - EventDataType prset = 0, prmode = 0; - qint64 dif; - int dn; - - if (!sess) { continue; } - - if (!sess->first()) { - delete sess; - continue; - } - sess->settings[CPAP_SummaryOnly] = false; - sess->SetChanged(true); - - ///////////////////////////////////////////////////////////////////////////////// - // Process STR.edf now all valid Session data is imported - ///////////////////////////////////////////////////////////////////////////////// - - - QMap::iterator strsess_end = strsess.end(); - quint32 key = int(sessionid / 60) * 60; // round to 1 minute - QMap::iterator it = strsess.find(key); - - if (it == strsess_end) { - // ResMed merges mask on/off groups that are less than a minute apart - // this means have to jump back to the last session closest. - - QMap::iterator sit; - int min = 86400; - - // Look for the closest matching str record that starts before sessionid. - for (sit = strsess.begin(); sit != strsess_end; ++sit) { - STRRecord & R = *sit; - - if (R.maskon > sessionid) - break; - - int t = sessionid - R.maskon; - - if (qAbs(t) < min) { - it = sit; - min = t; - } - } - } - if (it != strsess_end) { - // This is the right session ID - STRRecord & R = it.value(); - - QDateTime dt = QDateTime::fromTime_t(sessionid); - QDateTime rt = QDateTime::fromTime_t(R.maskon); - qDebug() << "Closest matching STR record for" << dt << (sess->length() / 1000L) << "is" << rt; - - // Claim this session - R.sessionid = sessionid; - - // Save maskon time in session setting so we can use it later to avoid doubleups. - sess->settings[RMS9_MaskOnTime]=R.maskon; - - // Grab all the system settings - if (R.set_pressure >= 0) sess->settings[RMS9_SetPressure] = R.set_pressure; - if (R.min_pressure >= 0) sess->settings[CPAP_PressureMin] = R.min_pressure; - if (R.max_pressure >= 0) sess->settings[CPAP_PressureMax] = R.max_pressure; - if (R.ps >= 0) sess->settings[CPAP_PS] = R.ps; - if (R.min_ps >= 0) sess->settings[CPAP_PSMin] = R.min_ps; - if (R.max_ps >= 0) sess->settings[CPAP_PSMax] = R.max_ps; - if (R.epap >= 0) sess->settings[CPAP_EPAP] = R.epap; - if (R.max_epap >= 0) sess->settings[CPAP_EPAPHi] = R.max_epap; - if (R.min_epap >= 0) sess->settings[CPAP_EPAPLo] = R.min_epap; - if (R.ipap >= 0) sess->settings[CPAP_IPAP] = R.ipap; - if (R.max_ipap >= 0) sess->settings[CPAP_IPAPHi] = R.max_ipap; - if (R.min_ipap >= 0) sess->settings[CPAP_IPAPLo] = R.min_ipap; - if (R.mode >= 0) sess->settings[CPAP_Mode] = R.mode; - if (R.epr >= 0) sess->settings[RMS9_EPR] = R.epr; - if (R.epr_set >= 0) sess->settings[RMS9_EPRSet] = R.epr_set; - - // Ignore all the rest of the sumary data, because there is enough available to calculate it with higher accuracy. - - if (sess->length() > 0) { - if (m->AddSession(sess, profile).isNull()) { - continue; - } - } else { - // Hmm.. this means a ton of these could slow down import. - // I could instead set these to disabled by default, or implement a dodgy session marker - delete sess; - } - } + queTask(new ResmedImport(this, fgit.key(), fgit.value(), m)); } + runTasks(); // Now look for any new summary data that can be extracted from STR.edf records QMap::iterator it; @@ -1223,16 +1198,11 @@ int ResmedLoader::Open(QString &path, Profile *profile) size = m->sessionlist.size(); cnt=0; + // Scan through all sessions, and remove any strsess records that have a matching session already for (sessit = m->sessionlist.begin(); sessit != sessend; ++sessit) { sess = *sessit; quint32 key = sess->settings[RMS9_MaskOnTime].toUInt(); - if ((++cnt % 10) == 0) { - // TODO: Change me to emit once MachineLoader is QObjectified... - if (qprogress) { qprogress->setValue(10.0 + (float(cnt) / float(size) * 90.0)); } - - QApplication::processEvents(); - } QMap::iterator e = strsess.find(key); if (e != end) { @@ -1245,23 +1215,38 @@ int ResmedLoader::Open(QString &path, Profile *profile) cnt=0; quint32 ignoreolder = PROFILE.session->ignoreOlderSessionsDate().toTime_t(); + // strsess end can change above. + end = strsess.end(); + + m->lockSaveMutex(); + m->setTotalTasks(m->totalTasks() + size); + m->unlockSaveMutex(); + + + m->StartSaveThreads(); // Look for the nearest matching str record for (it = strsess.begin(); it != end; ++it) { - STRRecord &R = *it; + STRRecord & R = it.value(); - if (R.maskon < ignoreolder) continue; + if (R.maskon < ignoreolder) { + m->skipSaveTask(); + continue; + } //Q_ASSERT(R.sessionid == 0); - if (R.sessionid > 0) continue; - - - if ((++cnt % 10) == 0) { - // TODO: Change me to emit once MachineLoader is QObjectified... - if (qprogress) { qprogress->setValue(10.0 + (float(cnt) / float(size) * 90.0)); } - - QApplication::processEvents(); + if (R.sessionid > 0) { + m->skipSaveTask(); + continue; } + +// if ((++cnt % 5) == 0) { +// // TODO: Change me to emit once MachineLoader is QObjectified... +// if (qprogress) { qprogress->setValue(10.0 + (float(cnt) / float(size) * 90.0)); } + +// QApplication::processEvents(); +// } + sess = new Session(m, R.maskon); sess->really_set_first(qint64(R.maskon) * 1000L); @@ -1291,6 +1276,12 @@ int ResmedLoader::Open(QString &path, Profile *profile) if (R.epr_set >= 0) sess->settings[RMS9_EPRSet] = R.epr_set; if (R.leakmax >= 0) sess->setMax(CPAP_Leak, R.leakmax); if (R.leakmax >= 0) sess->setMin(CPAP_Leak, 0); + if ((R.leakmed >= 0) && (R.leak95 >= 0) && (R.leakmax >= 0)) { + sess->m_timesummary[CPAP_Leak][short(R.leakmax / R.leakgain)]=1; + sess->m_timesummary[CPAP_Leak][short(R.leak95 / R.leakgain)]=9; + sess->m_timesummary[CPAP_Leak][short(R.leakmed / R.leakgain)]=65; + sess->m_timesummary[CPAP_Leak][0]=25; + } // Find the matching date group for this record @@ -1299,12 +1290,10 @@ int ResmedLoader::Open(QString &path, Profile *profile) // should not be possible, but my brain hurts... Q_ASSERT(dtit != strdate.end()); - if (dtit != strdate.end()) { QList & dayrecs = dtit.value(); - int entries = dayrecs.count(); bool hasdatasess=false; - EventDataType ai=0, hi=0, uai=0, time=0, totaltime=0; + EventDataType time=0, totaltime=0; for (int c=0; c < dayrecs.size(); ++c) { STRRecord *r = dayrecs[c]; @@ -1339,11 +1328,6 @@ int ResmedLoader::Open(QString &path, Profile *profile) sess->setCount(CPAP_ClearAirway, r->cai / ratio); sess->setCph(CPAP_ClearAirway, (r->ai / ratio) / (time / 3600.0)); } - if ((r->leakmed >= 0) && (r->leak95 >= 0) && (r->leakmax >= 0)) { - sess->m_valuesummary[CPAP_Leak][(short)r->leakmax]=100; - sess->m_valuesummary[CPAP_Leak][(short)r->leak95]=90; - sess->m_valuesummary[CPAP_Leak][(short)r->leakmed]=50; - } } @@ -1353,7 +1337,9 @@ int ResmedLoader::Open(QString &path, Profile *profile) m->AddSession(sess, profile); + m->queSaveList(sess); } + m->FinishSaveThreads(); #ifdef DEBUG_EFFICIENCY { @@ -1379,12 +1365,15 @@ int ResmedLoader::Open(QString &path, Profile *profile) } #endif - if (m) { - m->Save(); - } - if (qprogress) { qprogress->setValue(100); } + sessfiles.clear(); + strsess.clear(); + + strdate.clear(); + channel_efficiency.clear(); + channel_time.clear(); + qDebug() << "Total Events " << event_cnt; return 1; } @@ -1463,10 +1452,14 @@ QString ResmedLoader::backup(QString fullname, QString backup_path, bool compres } -bool ResmedLoader::LoadEVE(Session *sess, EDFParser &edf) +bool ResmedLoader::LoadEVE(Session *sess, const QString & path) { + EDFParser edf(path); + if (!edf.Parse()) + return false; QString t; + long recs; double duration; char *data; @@ -1604,8 +1597,13 @@ bool ResmedLoader::LoadEVE(Session *sess, EDFParser &edf) return true; } -bool ResmedLoader::LoadBRP(Session *sess, EDFParser &edf) + +bool ResmedLoader::LoadBRP(Session *sess, const QString & path) { + EDFParser edf(path); + if (!edf.Parse()) + return false; + sess->updateFirst(edf.startdate); qint64 duration = edf.GetNumDataRecords() * edf.GetDuration(); @@ -1799,8 +1797,12 @@ void ResmedLoader::ToTimeDelta(Session *sess, EDFParser &edf, EDFSignal &es, Cha } // Load SAD Oximetry Signals -bool ResmedLoader::LoadSAD(Session *sess, EDFParser &edf) +bool ResmedLoader::LoadSAD(Session *sess, const QString & path) { + EDFParser edf(path); + if (!edf.Parse()) + return false; + sess->updateFirst(edf.startdate); qint64 duration = edf.GetNumDataRecords() * edf.GetDuration(); sess->updateLast(edf.startdate + duration); @@ -1819,21 +1821,21 @@ bool ResmedLoader::LoadSAD(Session *sess, EDFParser &edf) break; } } - if (!hasdata) continue; + if (!hasdata) { + continue; + } if (matchSignal(OXI_Pulse, es.label)) { code = OXI_Pulse; ToTimeDelta(sess, edf, es, code, recs, duration); sess->setPhysMax(code, 180); sess->setPhysMin(code, 18); - } else if (matchSignal(OXI_SPO2, es.label)) { code = OXI_SPO2; es.physical_minimum = 60; ToTimeDelta(sess, edf, es, code, recs, duration); sess->setPhysMax(code, 100); sess->setPhysMin(code, 60); - } else { qDebug() << "Unobserved ResMed SAD Signal " << es.label; } @@ -1843,8 +1845,12 @@ bool ResmedLoader::LoadSAD(Session *sess, EDFParser &edf) } -bool ResmedLoader::LoadPLD(Session *sess, EDFParser &edf) +bool ResmedLoader::LoadPLD(Session *sess, const QString & path) { + EDFParser edf(path); + if (!edf.Parse()) + return false; + // Is it save to assume the order does not change here? enum PLDType { MaskPres = 0, TherapyPres, ExpPress, Leak, RR, Vt, Mv, SnoreIndex, FFLIndex, U1, U2 }; @@ -1967,6 +1973,7 @@ bool ResmedLoader::LoadPLD(Session *sess, EDFParser &edf) sess->setPhysMax(code, es.physical_maximum); a->setDimension(es.physical_dimension); } + } return true; diff --git a/sleepyhead/SleepLib/loader_plugins/resmed_loader.h b/sleepyhead/SleepLib/loader_plugins/resmed_loader.h index a33e8adf..67ef8bed 100644 --- a/sleepyhead/SleepLib/loader_plugins/resmed_loader.h +++ b/sleepyhead/SleepLib/loader_plugins/resmed_loader.h @@ -134,6 +134,8 @@ struct STRRecord leakmed = -1; leak95 = -1; leakmax = -1; + leakgain = 0; + date=QDate(); } STRRecord(const STRRecord & copy) { @@ -166,6 +168,7 @@ struct STRRecord leakmed = copy.leakmed; leak95 = copy.leak95; leakmax = copy.leakmax; + leakgain = copy.leakgain; } quint32 maskon; quint32 maskoff; @@ -195,9 +198,11 @@ struct STRRecord EventDataType leakmed; EventDataType leak95; EventDataType leakmax; + EventDataType leakgain; QDate date; }; + /*! \class EDFParser \author Mark Watkins \brief Parse an EDF+ data file into a list of EDFSignal's @@ -270,11 +275,49 @@ class EDFParser QString reserved44; }; +class ResmedLoader; + +struct EDFGroup { + EDFGroup() { } + EDFGroup(QString brp, QString eve, QString pld, QString sad) { + BRP = brp; + EVE = eve; + PLD = pld; + SAD = sad; + } + EDFGroup(const EDFGroup & copy) { + BRP = copy.BRP; + EVE = copy.EVE; + PLD = copy.PLD; + SAD = copy.SAD; + } + QString BRP; + QString EVE; + QString PLD; + QString SAD; +}; + +class ResmedImport:public ImportTask +{ +public: + ResmedImport(ResmedLoader * l, SessionID s, EDFGroup g, Machine * m): loader(l), sessionid(s), group(g), mach(m) {} + virtual ~ResmedImport() {} + virtual void run(); + +protected: + ResmedLoader * loader; + SessionID sessionid; + EDFGroup group; + Machine * mach; +}; + + /*! \class ResmedLoader \brief Importer for ResMed S9 Data */ class ResmedLoader : public MachineLoader { + friend class ResmedImport; public: ResmedLoader(); virtual ~ResmedLoader(); @@ -300,24 +343,25 @@ class ResmedLoader : public MachineLoader //! \brief Register the ResmedLoader with the list of other machine loaders static void Register(); - protected: - QHash ResmedList; //! \brief Parse the EVE Event annotation data, and save to Session * sess //! This contains all Hypopnea, Obstructive Apnea, Central and Apnea codes - bool LoadEVE(Session *sess, EDFParser &edf); + bool LoadEVE(Session *sess, const QString & path); //! \brief Parse the BRP High Resolution data, and save to Session * sess //! This contains Flow Rate, Mask Pressure, and Resp. Event data - bool LoadBRP(Session *sess, EDFParser &edf); + bool LoadBRP(Session *sess, const QString & path); //! \brief Parse the SAD Pulse oximetry attachment data, and save to Session * sess //! This contains Pulse Rate and SpO2 Oxygen saturation data - bool LoadSAD(Session *sess, EDFParser &edf); + bool LoadSAD(Session *sess, const QString & path); //! \brief Parse the PRD low resolution data, and save to Session * sess //! This contains the Pressure, Leak, Respiratory Rate, Minute Ventilation, Tidal Volume, etc.. - bool LoadPLD(Session *sess, EDFParser &edf); + bool LoadPLD(Session *sess, const QString & path); + +protected: + QHash ResmedList; void ParseSTR(Machine *mach, QStringList strfiles); @@ -328,6 +372,8 @@ class ResmedLoader : public MachineLoader QMap strsess; QMap > strdate; + QMutex saveMutex; + #ifdef DEBUG_EFFICIENCY QHash channel_efficiency; QHash channel_time; diff --git a/sleepyhead/SleepLib/machine.cpp b/sleepyhead/SleepLib/machine.cpp index 6e145df8..7b915c28 100644 --- a/sleepyhead/SleepLib/machine.cpp +++ b/sleepyhead/SleepLib/machine.cpp @@ -97,24 +97,16 @@ QDate Machine::pickDate(qint64 first) return date; } -QDate Machine::AddSession(Session *s, Profile *p) +bool 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(); - } + Q_ASSERT(s != nullptr); + Q_ASSERT(p != nullptr); if (profile->session->ignoreOlderSessions()) { qint64 ignorebefore = profile->session->ignoreOlderSessionsDate().toMSecsSinceEpoch(); if (s->last() < ignorebefore) { skipped_sessions++; - delete s; - return QDate(); + return false; } } @@ -122,7 +114,6 @@ QDate Machine::AddSession(Session *s, Profile *p) highest_sessionid = s->session(); } - QTime split_time = PROFILE.session->daySplitTime(); int combine_sessions = PROFILE.session->combineCloseSessions(); int ignore_sessions = PROFILE.session->ignoreShortSessions(); @@ -175,7 +166,7 @@ QDate Machine::AddSession(Session *s, Profile *p) if (session_length < ignore_sessions) { // keep the session to save importing it again, but don't add it to the day record this time - return QDate(); + return true; } if (!firstsession) { @@ -220,7 +211,7 @@ QDate Machine::AddSession(Session *s, Profile *p) day.erase(nextday); } - return date; + return true; } // This functions purpose is murder and mayhem... It deletes all of a machines data. @@ -381,13 +372,127 @@ bool Machine::SaveSession(Session *sess) return true; } +void Machine::queSaveList(Session * sess) +{ + if (!m_save_threads_running) { + // Threads aren't being used.. so run the actual immediately... + int i = (float(m_donetasks) / float(m_totaltasks) * 100.0); + qprogress->setValue(i); + QApplication::processEvents(); + + sess->UpdateSummaries(); + sess->Store(profile->Get(properties[STR_PROP_Path])); + + if (!PROFILE.session->cacheSessions()) { + sess->TrashEvents(); + } + + } else { + savelistMutex.lock(); + m_savelist.append(sess); + savelistMutex.unlock(); + } +} + +Session *Machine::popSaveList() +{ + Session *sess = nullptr; + savelistMutex.lock(); + + if (!m_savelist.isEmpty()) { + sess = m_savelist.at(0); + m_savelist.pop_front(); + m_donetasks++; + } + + savelistMutex.unlock(); + return sess; +} + +// Call any time queing starts +void Machine::StartSaveThreads() +{ + m_savelist.clear(); + + QString path = profile->Get(properties[STR_PROP_Path]); + + int threads = QThread::idealThreadCount(); + savelistSem = new QSemaphore(threads); + savelistSem->acquire(threads); + + m_save_threads_running = true; + m_donetasks=0; + m_totaltasks=0; + + 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(); + } + +} + +// Call when all queing is completed +void Machine::FinishSaveThreads() +{ + if (!m_save_threads_running) + return; + + m_save_threads_running = false; + + // Wait for all tasks to finish + while (!savelistSem->tryAcquire(thread.size(), 250)) { + if (qprogress) { + QApplication::processEvents(); + } + } + + for (int i = 0; i < thread.size(); ++i) { + while (thread[i]->isRunning()) { + SaveThread::msleep(250); + QApplication::processEvents(); + } + QObject::disconnect(thread[i], SIGNAL(UpdateProgress(int)), qprogress, SLOT(setValue(int))); + + delete thread[i]; + } + + delete savelistSem; +} + +void SaveThread::run() +{ + bool running = true; + while (running) { + Session *sess = machine->popSaveList(); + if (sess) { + if (machine->m_donetasks % 10 == 0) { + int i = (float(machine->m_donetasks) / float(machine->m_totaltasks) * 100.0); + emit UpdateProgress(i); + } + sess->UpdateSummaries(); + sess->Store(path); + + sess->TrashEvents(); + } else { + if (!machine->m_save_threads_running) { + break; // done + } else { + yieldCurrentThread(); // go do something else for a while + } + } + } + + machine->savelistSem->release(1); +} + + bool Machine::Save() { //int size; int cnt = 0; - QString path = profile->Get( - properties[STR_PROP_Path]); //STR_GEN_DataFolder)+"/"+m_class+"_"+hexid(); + QString path = profile->Get(properties[STR_PROP_Path]); QDir dir(path); if (!dir.exists()) { @@ -408,7 +513,6 @@ bool Machine::Save() savelistCnt = 0; savelistSize = m_savelist.size(); - bool cachesessions = PROFILE.session->cacheSessions(); if (!PROFILE.session->multithreading()) { for (int i = 0; i < savelistSize; i++) { @@ -422,10 +526,7 @@ bool Machine::Save() Session *s = m_savelist.at(i); s->UpdateSummaries(); s->Store(path); - - if (!cachesessions) { - s->TrashEvents(); - } + s->TrashEvents(); savelistCnt++; @@ -465,46 +566,6 @@ bool Machine::Save() return true; } -/*SaveThread::SaveThread(Machine *m,QString p) -{ - machine=m; - path=p; -} */ - -void SaveThread::run() -{ - bool cachesessions = PROFILE.session->cacheSessions(); - - while (Session *sess = machine->popSaveList()) { - int i = (float(machine->savelistCnt) / float(machine->savelistSize) * 100.0); - emit UpdateProgress(i); - sess->UpdateSummaries(); - sess->Store(path); - - if (!cachesessions) { - sess->TrashEvents(); - } - } - - machine->savelistSem->release(1); -} - -Session *Machine::popSaveList() -{ - - Session *sess = nullptr; - savelistMutex.lock(); - - if (m_savelist.size() > 0) { - sess = m_savelist.at(0); - m_savelist.pop_front(); - savelistCnt++; - } - - savelistMutex.unlock(); - return sess; -} - ////////////////////////////////////////////////////////////////////////////////////////// // CPAP implmementation ////////////////////////////////////////////////////////////////////////////////////////// diff --git a/sleepyhead/SleepLib/machine.h b/sleepyhead/SleepLib/machine.h index fa10914a..dd572c04 100644 --- a/sleepyhead/SleepLib/machine.h +++ b/sleepyhead/SleepLib/machine.h @@ -66,6 +66,8 @@ class SaveThread: public QThread */ class Machine { + friend class SaveThread; + public: /*! \fn Machine(Profile *p,MachineID id=0); \brief Constructs a Machine object in Profile p, and with MachineID id @@ -99,7 +101,7 @@ class Machine Session *SessionExists(SessionID session); //! \brief Adds the session to this machine object, and the Master Profile list. (used during load) - QDate AddSession(Session *s, Profile *p); + bool AddSession(Session *s, Profile *p); //! \brief Find the date this session belongs in, according to profile settings QDate pickDate(qint64 start); @@ -132,20 +134,39 @@ class Machine //! \brief Returns the date of the most recent loaded Session const QDate &LastDay() { return lastday; } + //! \brief Add a new task to the multithreaded save code + void queSaveList(Session * sess); + //! \brief Grab the next task in the multithreaded save code Session *popSaveList(); + //! \brief Start the save threads which handle indexing, file storage and waveform processing + void StartSaveThreads(); + + //! \brief Finish the save threads and safely close them + void FinishSaveThreads(); + //! \brief The list of sessions that need saving (for multithreaded save code) QList m_savelist; + QVectorthread; + + volatile int savelistCnt; int savelistSize; QMutex savelistMutex; QSemaphore *savelistSem; + void lockSaveMutex() { savelistMutex.lock(); } + void unlockSaveMutex() { savelistMutex.unlock(); } + void skipSaveTask() { lockSaveMutex(); m_donetasks++; unlockSaveMutex(); } + void clearSkipped() { skipped_sessions = 0; } int skippedSessions() { return skipped_sessions; } + inline int totalTasks() { return m_totaltasks; } + inline void setTotalTasks(int value) { m_totaltasks = value; } + inline int doneTasks() { return m_donetasks; } protected: QDate firstday, lastday; SessionID highest_sessionid; @@ -156,8 +177,11 @@ class Machine Profile *profile; bool changed; bool firstsession; + int m_totaltasks; + int m_donetasks; int skipped_sessions; + volatile bool m_save_threads_running; }; diff --git a/sleepyhead/SleepLib/machine_loader.cpp b/sleepyhead/SleepLib/machine_loader.cpp index aaa17333..ef35202b 100644 --- a/sleepyhead/SleepLib/machine_loader.cpp +++ b/sleepyhead/SleepLib/machine_loader.cpp @@ -9,17 +9,32 @@ * License. See the file COPYING in the main directory of the Linux * distribution for more details. */ +#include +#include #include #include +#include + +extern QProgressBar *qprogress; #include "machine_loader.h" // This crap moves to Profile QList m_loaders; -QList GetLoaders() +QList GetLoaders(MachineType mt) { - return m_loaders; + QList list; + for (int i=0; i < m_loaders.size(); ++i) { + if (mt == MT_UNKNOWN) { + list.push_back(m_loaders.at(i)); + } else { + if (m_loaders.at(i)->type() == mt) { + list.push_back(m_loaders.at(i)); + } + } + } + return list; } void RegisterLoader(MachineLoader *loader) @@ -35,7 +50,7 @@ void DestroyLoaders() m_loaders.clear(); } -MachineLoader::MachineLoader() +MachineLoader::MachineLoader() :QObject(nullptr) { } @@ -46,7 +61,7 @@ MachineLoader::~MachineLoader() } } -bool MachineLoader::compressFile(QString inpath, QString outpath) +bool compressFile(QString inpath, QString outpath) { if (outpath.isEmpty()) { outpath = inpath + ".gz"; @@ -92,6 +107,28 @@ bool MachineLoader::compressFile(QString inpath, QString outpath) return true; } +void MachineLoader::queTask(ImportTask * task) +{ + m_tasklist.push_back(task); +} + +void MachineLoader::runTasks() +{ + QThreadPool * threadpool = QThreadPool::globalInstance(); + m_totaltasks=m_tasklist.size(); + m_currenttask=0; + while (!m_tasklist.isEmpty()) { + if (threadpool->tryStart(m_tasklist.at(0))) { + m_tasklist.pop_front(); + float f = float(m_currenttask) / float(m_totaltasks) * 100.0; + qprogress->setValue(f); + m_currenttask++; + } + QApplication::processEvents(); + } + QThreadPool::globalInstance()->waitForDone(-1); +} + /*const QString machine_profile_name="MachineList.xml"; void MachineLoader::LoadMachineList() diff --git a/sleepyhead/SleepLib/machine_loader.h b/sleepyhead/SleepLib/machine_loader.h index 6612caa4..83d19d10 100644 --- a/sleepyhead/SleepLib/machine_loader.h +++ b/sleepyhead/SleepLib/machine_loader.h @@ -11,23 +11,38 @@ #ifndef MACHINE_LOADER_H #define MACHINE_LOADER_H + +#include +#include + #include "profiles.h" #include "machine.h" + #include "zlib.h" +class MachineLoader; + +class ImportTask:public QRunnable +{ +public: + explicit ImportTask() {} + virtual ~ImportTask() {} + virtual void run() {} +}; + + /*! \class MachineLoader \brief Base class to derive a new Machine importer from */ -class MachineLoader +class MachineLoader: public QObject { + Q_OBJECT + friend class ImportThread; public: MachineLoader(); virtual ~MachineLoader(); - - //virtual Machine * CreateMachine() {}; - //! \brief Detect if the given path contains a valid folder structure virtual bool Detect(const QString & path) = 0; @@ -39,41 +54,33 @@ class MachineLoader //! \brief Override to returns the class name of this MachineLoader virtual const QString &ClassName() = 0; + inline MachineType type() { return m_type; } - bool compressFile(QString inpath, QString outpath = ""); - - - /* - MachineLoader(Profile *profile,QString & classname); - virtual void LoadMachineList(); - virtual void SaveMachineList(); - virtual bool LoadSummaries(); - virtual bool LoadEvents(); - virtual bool LoadWaveforms(); - virtual bool Scan(QString &)=0; // Scans for new content - - virtual bool LoadAll(); - virtual bool SaveAll(); - - virtual bool LoadSummary(Machine * m, QString & filename); - virtual bool LoadEvent(Machine * m, QString & filename); - virtual bool LoadWaveform(Machine * m, QString & filename); - - virtual bool SaveSummary(Machine * m, QString & filename); - virtual bool SaveEvent(Machine * m, QString & filename); - virtual bool SaveWaveform(Machine * m, QString & filename);*/ + void queTask(ImportTask * task); + //! \brief Process Task list using all available threads. + void runTasks(); protected: //! \brief Contains a list of Machine records known by this loader QList m_machlist; - QString m_class; + MachineType m_type; + QString m_class; Profile *m_profile; + + int m_currenttask; + int m_totaltasks; + + private: + QList m_tasklist; }; + // Put in machine loader class as static?? void RegisterLoader(MachineLoader *loader); void DestroyLoaders(); -QList GetLoaders(); +bool compressFile(QString inpath, QString outpath = ""); + +QList GetLoaders(MachineType mt = MT_UNKNOWN); #endif //MACHINE_LOADER_H diff --git a/sleepyhead/SleepLib/session.cpp b/sleepyhead/SleepLib/session.cpp index 6e691778..e082942d 100644 --- a/sleepyhead/SleepLib/session.cpp +++ b/sleepyhead/SleepLib/session.cpp @@ -69,12 +69,18 @@ void Session::TrashEvents() for (i = eventlist.begin(); i != i_end; ++i) { j_end=i.value().end(); for (j = i.value().begin(); j != j_end; ++j) { - delete *j; + EventList * ev = *j; + ev->clear(); + ev->m_data.squeeze(); + ev->m_data2.squeeze(); + ev->m_time.squeeze(); + delete ev; } } s_events_loaded = false; eventlist.clear(); + eventlist.squeeze(); } //const int max_pack_size=128; @@ -150,7 +156,6 @@ bool Session::Store(QString path) s_changed = false; s_events_loaded = true; - //TrashEvents(); //} else { // qDebug() << "Session::Store() No event data saved" << s_session; //}