File indexing completed on 2024-12-22 04:17:18
0001 /*************************************************************************** 0002 datasource.cpp - abstract data source 0003 ------------------- 0004 begin : Thu Oct 16 2003 0005 copyright : (C) 2003 The University of Toronto 0006 email : netterfield@astro.utoronto.ca 0007 ***************************************************************************/ 0008 0009 /*************************************************************************** 0010 * * 0011 * This program is free software; you can redistribute it and/or modify * 0012 * it under the terms of the GNU General Public License as published by * 0013 * the Free Software Foundation; either version 2 of the License, or * 0014 * (at your option) any later version. * 0015 * * 0016 ***************************************************************************/ 0017 0018 #include "datasource.h" 0019 0020 #include <assert.h> 0021 0022 #include <QApplication> 0023 #include <QDebug> 0024 #include <QDir> 0025 #include <QFile> 0026 #include <QFileInfo> 0027 #include <QLibraryInfo> 0028 #include <QPluginLoader> 0029 #include <QTextDocument> 0030 #include <QUrl> 0031 #include <QXmlStreamWriter> 0032 #include <QTimer> 0033 #include <QFileSystemWatcher> 0034 0035 0036 #include "datacollection.h" 0037 #include "debug.h" 0038 #include "objectstore.h" 0039 #include "scalar.h" 0040 #include "string.h" 0041 #include "nextcolor.h" 0042 #include "updatemanager.h" 0043 0044 #include "dataplugin.h" 0045 0046 0047 // TODO DataSource should not need the plugin code 0048 #include "datasourcepluginmanager.h" 0049 0050 0051 using namespace Kst; 0052 0053 template<class T> 0054 struct NotSupportedImp : public DataSource::DataInterface<T> 0055 { 0056 // read one element 0057 int read(const QString&, typename T::ReadInfo&) { return -1; } 0058 0059 // named elements 0060 QStringList list() const { return QStringList(); } 0061 bool isListComplete() const { return false; } 0062 bool isValid(const QString&) const { return false; } 0063 0064 // T specific 0065 const typename T::DataInfo dataInfo(const QString&, int frame=0) const { Q_UNUSED(frame) return typename T::DataInfo(); } 0066 void setDataInfo(const QString&, const typename T::DataInfo&) {} 0067 0068 // meta data 0069 QMap<QString, double> metaScalars(const QString&) { return QMap<QString, double>(); } 0070 QMap<QString, QString> metaStrings(const QString&) { return QMap<QString, QString>(); } 0071 }; 0072 0073 0074 const QString DataSource::staticTypeString = "Data Source"; 0075 const QString DataSource::staticTypeTag = "source"; 0076 0077 Object::UpdateType DataSource::objectUpdate(qint64 newSerial) { 0078 if (_serial==newSerial) { 0079 return NoChange; 0080 } 0081 0082 UpdateType updated = NoChange; 0083 0084 if (!UpdateManager::self()->paused()) { 0085 // update the datasource 0086 updated = internalDataSourceUpdate(); 0087 if (updated == Updated) { 0088 _serialOfLastChange = newSerial; // tell data objects it is new 0089 } 0090 } 0091 0092 _serial = newSerial; 0093 0094 return updated; 0095 } 0096 0097 0098 void DataSource::_initializeShortName() { 0099 _shortName = QString("DS%1").arg(_datasourcenum); 0100 if (_datasourcenum>max_datasourcenum) 0101 max_datasourcenum = _datasourcenum; 0102 _datasourcenum++; 0103 } 0104 0105 bool Kst::DataSource::isValid() const 0106 { 0107 return _valid; 0108 } 0109 0110 0111 bool DataSource::hasConfigWidget() const { 0112 return DataSourcePluginManager::sourceHasConfigWidget(_filename, fileType()); 0113 } 0114 0115 0116 DataSourceConfigWidget* DataSource::configWidget() { 0117 if (!hasConfigWidget()) 0118 return 0; 0119 0120 DataSourceConfigWidget *w = DataSourcePluginManager::configWidgetForSource(_filename, fileType()); 0121 Q_ASSERT(w); 0122 0123 //This is still ugly to me... 0124 w->_instance = this; 0125 0126 // TODO check if not all plugins already have load() called 0127 w->load(); 0128 0129 return w; 0130 } 0131 0132 0133 0134 DataSource::DataSource(ObjectStore *store, QSettings *cfg, const QString& filename, const QString& type) : 0135 Object(), 0136 _filename(filename), 0137 _alternateFilename(QString()), 0138 _cfg(cfg), 0139 interf_scalar(new NotSupportedImp<DataScalar>), 0140 interf_string(new NotSupportedImp<DataString>), 0141 interf_vector(new NotSupportedImp<DataVector>), 0142 interf_matrix(new NotSupportedImp<DataMatrix>), 0143 _watcher(0), 0144 _color(NextColor::self().current()) 0145 { 0146 Q_UNUSED(type) 0147 Q_UNUSED(store) 0148 0149 _valid = false; 0150 _reusable = true; 0151 _writable = false; 0152 _watcher = 0L; 0153 0154 _initializeShortName(); 0155 0156 // Timer needs to be the default: File sometimes fails. 0157 startUpdating(Timer); 0158 } 0159 0160 DataSource::~DataSource() { 0161 resetFileWatcher(); 0162 delete interf_scalar; 0163 delete interf_string; 0164 delete interf_vector; 0165 delete interf_matrix; 0166 } 0167 0168 QString DataSource::cleanPath(QString abs_path) { 0169 QString name = QDir::cleanPath(abs_path); 0170 0171 // qdir::cleanpath doesn't seem to remove leading /.. from paths (!) 0172 while (name.startsWith("/..")) { 0173 name.remove(QRegExp("^/..")); 0174 } 0175 0176 return name; 0177 } 0178 0179 QMap<QString, QString> DataSource::fileMetas() const 0180 { 0181 QMap<QString, QString> map; 0182 QFileInfo info(_filename); 0183 map["File name"] = info.fileName(); 0184 map["File path"] = info.path(); 0185 map["File creation"] = info.created().toString(Qt::ISODate).replace('T', ' '); 0186 map["File modification"] = info.lastModified().toString(Qt::ISODate).replace('T', ' '); 0187 return map; 0188 } 0189 0190 0191 void DataSource::resetFileWatcher() { 0192 if (_watcher) { 0193 disconnect(_watcher, SIGNAL(fileChanged(QString)), this, SLOT(checkUpdate())); 0194 disconnect(_watcher, SIGNAL(directoryChanged(QString)), this, SLOT(checkUpdate())); 0195 delete _watcher; 0196 _watcher = 0L; 0197 } 0198 } 0199 0200 0201 void DataSource::setInterface(DataInterface<DataScalar>* i) { 0202 delete interf_scalar; 0203 interf_scalar = i; 0204 } 0205 0206 void DataSource::setInterface(DataInterface<DataString>* i) { 0207 delete interf_string; 0208 interf_string = i; 0209 } 0210 0211 void DataSource::setInterface(DataInterface<DataVector>* i) { 0212 delete interf_vector; 0213 interf_vector = i; 0214 } 0215 0216 void DataSource::setInterface(DataInterface<DataMatrix>* i) { 0217 delete interf_matrix; 0218 interf_matrix = i; 0219 } 0220 0221 0222 DataSource::UpdateCheckType DataSource::updateType() const 0223 { 0224 return _updateCheckType; 0225 } 0226 0227 void DataSource::setUpdateType(UpdateCheckType updateType) 0228 { 0229 _updateCheckType = updateType; 0230 } 0231 0232 void DataSource::startUpdating(UpdateCheckType updateType, const QString& file) 0233 { 0234 setUpdateType(updateType); 0235 resetFileWatcher(); 0236 if (_updateCheckType == Timer) { 0237 QTimer::singleShot(UpdateManager::self()->minimumUpdatePeriod()-1, this, SLOT(checkUpdate())); 0238 } else if (_updateCheckType == File) { 0239 // TODO only works on local files: 0240 // http://bugreports.qt.nokia.com/browse/QTBUG-8351 0241 // http://bugreports.qt.nokia.com/browse/QTBUG-13248 0242 _watcher = new QFileSystemWatcher(); 0243 const QString usedfile = (file.isEmpty() ? _filename : file); 0244 _watcher->addPath(usedfile); 0245 connect(_watcher, SIGNAL(fileChanged(QString)), this, SLOT(checkUpdate())); 0246 connect(_watcher, SIGNAL(directoryChanged(QString)), this, SLOT(checkUpdate())); 0247 } 0248 } 0249 0250 0251 void DataSource::checkUpdate() { 0252 if (!UpdateManager::self()->paused()) { 0253 UpdateManager::self()->doUpdates(false); 0254 } 0255 0256 if (_updateCheckType == Timer) { 0257 QTimer::singleShot(UpdateManager::self()->minimumUpdatePeriod()-1, this, SLOT(checkUpdate())); 0258 } 0259 } 0260 0261 0262 void DataSource::deleteDependents() { 0263 0264 ObjectList<Primitive> primitiveList = _store->getObjects<Primitive>(); 0265 foreach (PrimitivePtr primitive, primitiveList) { 0266 DataPrimitive* dp = qobject_cast<DataPrimitive*>(primitive); 0267 if (dp && (dp->dataSource()->Name() == Name())) { 0268 primitive->deleteDependents(); 0269 _store->removeObject(primitive); 0270 } 0271 } 0272 0273 QList<ObjectPtr> Objects = _store->objectList(); 0274 0275 foreach (const PrimitivePtr &p, slavePrimitives) { 0276 ObjectPtr op = kst_cast<Object>(p); 0277 foreach (ObjectPtr object, Objects) { 0278 if (object->uses(op)) { 0279 _store->removeObject(object); 0280 } 0281 } 0282 store()->removeObject(p); 0283 } 0284 } 0285 0286 0287 const QString& DataSource::typeString() const { 0288 return staticTypeString; 0289 } 0290 0291 0292 QString DataSource::fileName() const { 0293 // Look to see if it was a URL and save the URL instead 0294 const QMap<QString,QString> urlMap = DataSourcePluginManager::urlMap(); 0295 for (QMap<QString,QString>::ConstIterator i = urlMap.begin(); i != urlMap.end(); ++i) { 0296 if (i.value() == _filename) { 0297 return i.key(); 0298 } 0299 } 0300 0301 return DataSource::cleanPath(_filename); 0302 } 0303 0304 QString DataSource::alternateFilename() const { 0305 return DataSource::cleanPath(_alternateFilename); 0306 } 0307 0308 void DataSource::setAlternateFilename(const QString &file) { 0309 _alternateFilename = file; 0310 } 0311 0312 QString DataSource::fileType() const { 0313 return QString(); 0314 } 0315 0316 void DataSource::save(QXmlStreamWriter &s) { 0317 Q_UNUSED(s) 0318 } 0319 0320 0321 void DataSource::saveSource(QXmlStreamWriter &s) { 0322 QString name = _filename; 0323 // Look to see if it was a URL and save the URL instead 0324 const QMap<QString,QString> urlMap = DataSourcePluginManager::urlMap(); 0325 for (QMap<QString,QString>::ConstIterator i = urlMap.begin(); i != urlMap.end(); ++i) { 0326 if (i.value() == _filename) { 0327 name = i.key(); 0328 break; 0329 } 0330 } 0331 s.writeStartElement("source"); 0332 s.writeAttribute("reader", fileType()); 0333 s.writeAttribute("updateType", QString::number(int(_updateCheckType))); 0334 DataPrimitive::saveFilename(name, s); 0335 save(s); 0336 0337 saveNameInfo(s, DATASOURCENUM | SCALARNUM | STRINGNUM); 0338 s.writeEndElement(); 0339 } 0340 0341 0342 0343 void DataSource::parseProperties(QXmlStreamAttributes &properties) { 0344 Q_UNUSED(properties); 0345 } 0346 0347 0348 bool DataSource::isEmpty() const { 0349 return true; 0350 } 0351 0352 0353 void DataSource::reset() { 0354 Object::reset(); 0355 } 0356 0357 0358 bool DataSource::supportsTimeConversions() const { 0359 return false; 0360 } 0361 0362 QString DataSource::timeFormat() const { 0363 return QString(); 0364 } 0365 0366 int DataSource::sampleForTime(const QDateTime& time, bool *ok) { 0367 Q_UNUSED(time) 0368 if (ok) { 0369 *ok = false; 0370 } 0371 return 0; 0372 } 0373 0374 0375 0376 int DataSource::sampleForTime(double ms, bool *ok) { 0377 Q_UNUSED(ms) 0378 if (ok) { 0379 *ok = false; 0380 } 0381 return 0; 0382 } 0383 0384 0385 0386 QDateTime DataSource::timeForSample(int sample, bool *ok) { 0387 Q_UNUSED(sample) 0388 if (ok) { 0389 *ok = false; 0390 } 0391 return QDateTime::currentDateTime(); 0392 } 0393 0394 0395 bool DataSource::isTime(const QString& field) const { 0396 return (_timeFields.contains(field)); 0397 } 0398 0399 0400 double DataSource::relativeTimeForSample(int sample, bool *ok) { 0401 Q_UNUSED(sample) 0402 if (ok) { 0403 *ok = false; 0404 } 0405 return 0; 0406 } 0407 0408 0409 double DataSource::frameToIndex(int frame, const QString &field) { 0410 return readDespikedIndex(frame+1, field); 0411 } 0412 0413 0414 int DataSource::indexToFrame(double X, const QString &field) { 0415 const DataVector::DataInfo info = vector().dataInfo(field); 0416 int Fmin = 0; 0417 int Fmax = info.frameCount-1; 0418 double Xmin = readDespikedIndex(Fmin, field); 0419 double Xmax = readDespikedIndex(Fmax, field); 0420 int F0 = (Fmin + Fmax)/2; 0421 double X0; 0422 0423 if (X>=Xmax) { 0424 return Fmax; 0425 } 0426 if (X<=Xmin) { 0427 return Fmin; 0428 } 0429 0430 while (F0 != Fmin) { 0431 X0 = readDespikedIndex(F0, field); 0432 if ((X0>Xmax) || (X0<Xmin)) { // not monotoically rising! 0433 return (-1); 0434 } 0435 0436 if (X <= X0) { 0437 Xmax = X0; 0438 Fmax = F0; 0439 } else { 0440 Xmin = X0; 0441 Fmin = F0; 0442 } 0443 F0 = (Fmin + Fmax)/2; 0444 } 0445 return F0; 0446 } 0447 0448 /* generic determination of frames per index (eg, frames per unit "TIME") 0449 * This is a challenge because: 0450 * i) the index may not change every frame (eg, ctime index w/ 5 samples/frame 0451 * ii) the starting frame may not be frame 0 0452 * Here, as a hacky heuristic, we will see how much index changes in the last 0453 * 1000 frames... hoping that index changes enough to make this accurate, and that 0454 * there are at least 1000 valid frames in the file, or that the first frame is frame 0. 0455 */ 0456 double DataSource::framePerIndex(const QString &field) { 0457 // FIXME: for now, calculate the sample rate, but later allow us to define it 0458 const DataVector::DataInfo info = vector().dataInfo(field); 0459 int fn = info.frameCount-2; 0460 int f0 = fn-1000.0; //FIXME: not general (but maybe nothing is...) 0461 if (f0<0) { 0462 f0 = 0; 0463 } 0464 if (f0 == fn) { 0465 return 1.0; 0466 } 0467 0468 double x0 = readDespikedIndex(f0, field); 0469 double xn = readDespikedIndex(fn, field); 0470 0471 if (xn == x0) { 0472 return 1.0; 0473 } 0474 0475 return double(fn-f0)/(xn - x0); 0476 } 0477 0478 double DataSource::readDespikedIndex(int frame_in, const QString &field) { 0479 0480 // we want a despike buffer which is an integer number of frames and 0481 // at least 5 samples on each side of our desired sample 0482 int frame = frame_in; 0483 const int min_despike_margin = 5; // samples 0484 const DataVector::DataInfo info = vector().dataInfo(field); 0485 int margin_frames = qMax(1,min_despike_margin/info.samplesPerFrame); 0486 int margin_samp = margin_frames * info.samplesPerFrame; 0487 double *data = new double[2*margin_samp]; 0488 double x; 0489 0490 frame -= margin_frames; 0491 if (frame<0) { 0492 frame = 0; 0493 } 0494 if (frame + 2*margin_frames >= info.frameCount) { 0495 frame = info.frameCount - 2*margin_frames; 0496 } 0497 DataVector::ReadInfo par = {data, frame, 2*margin_frames, -1}; 0498 0499 vector().read(field, par); 0500 0501 bool spike_found; 0502 int n = 2*margin_samp-1; 0503 do { 0504 int j=0; 0505 spike_found = false; 0506 for (int i = 0; i < n; i++) { 0507 if (data[i+1] >= data[i]) { // increasing. This point is probably ok. 0508 data[j++] = data[i]; 0509 } else { 0510 i++; // this point and the next one are now suspect - skip them 0511 spike_found = true; 0512 } 0513 } 0514 n = j; 0515 } while(spike_found); // We found a spike - better check for wider ones. 0516 0517 x = data[n/2]; // FIXME: we might be off by a couple samples if there were spikes. 0518 0519 delete[] data; 0520 0521 return(x); 0522 } 0523 0524 0525 QStringList &DataSource::timeFields() { 0526 if (_timeFields.size() == 0) { 0527 // FIXME: this must be created by the UI somehow. 0528 // or by the datasource itself. Or something 0529 // different than this! 0530 QStringList requestedFields; 0531 requestedFields.append("TIME"); 0532 requestedFields.append("Time"); 0533 requestedFields.append("time"); 0534 requestedFields.append("Temps"); 0535 requestedFields.append("TEMPS"); 0536 requestedFields.append("temps"); 0537 0538 // Make sure the requested fields actually exist. 0539 foreach (const QString &field, requestedFields) { 0540 if (vector().list().contains(field)) { 0541 _timeFields.append(field); 0542 } 0543 } 0544 } 0545 0546 return(_timeFields); 0547 } 0548 0549 QStringList &DataSource::indexFields() { 0550 if (_frameFields.size() == 0) { 0551 _frameFields.append(tr("frames")); 0552 _frameFields.append(timeFields()); 0553 } 0554 0555 return(_frameFields); 0556 } 0557 0558 bool DataSource::reusable() const { 0559 return _reusable; 0560 } 0561 0562 0563 void DataSource::disableReuse() { 0564 _reusable = false; 0565 } 0566 0567 QString DataSource::_automaticDescriptiveName() const { 0568 return QFileInfo(_filename).fileName(); 0569 } 0570 0571 QString DataSource::descriptionTip() const { 0572 return fileName(); 0573 } 0574 0575 QColor DataSource::color() const { 0576 return _color; 0577 } 0578 0579 void DataSource::setColor(const QColor& color) { 0580 _color = color; 0581 } 0582 0583 0584 ///////////////////////////////////////////////////////////////////////////// 0585 DataSourceConfigWidget::DataSourceConfigWidget(QSettings& settings) 0586 : QWidget(0L), _cfg(settings) { 0587 } 0588 0589 0590 DataSourceConfigWidget::~DataSourceConfigWidget() { 0591 } 0592 0593 0594 void DataSourceConfigWidget::setInstance(DataSourcePtr inst) { 0595 _instance = inst; 0596 } 0597 0598 0599 DataSourcePtr DataSourceConfigWidget::instance() const { 0600 return _instance; 0601 } 0602 0603 0604 QSettings& DataSourceConfigWidget::settings() const { 0605 return _cfg; 0606 } 0607 0608 bool DataSourceConfigWidget::isOkAcceptabe() const { 0609 return true; 0610 } 0611 0612 bool DataSourceConfigWidget::hasInstance() const { 0613 return _instance != 0L; 0614 } 0615 0616 0617 ValidateDataSourceThread::ValidateDataSourceThread(const QString& file, const int requestID) : QRunnable(), 0618 _file(file), 0619 _requestID(requestID) { 0620 } 0621 0622 static QMutex _mutex; 0623 void ValidateDataSourceThread::run() { 0624 0625 QFileInfo info(_file); 0626 if (!info.exists()) { 0627 emit dataSourceInvalid(_requestID); 0628 return; 0629 } 0630 0631 // FIXME validSource(_file) is not thread safe, so wait 0632 // if there is another one running 0633 QMutexLocker locker(&_mutex); 0634 if (!DataSourcePluginManager::validSource(_file)) { 0635 emit dataSourceInvalid(_requestID); 0636 return; 0637 } 0638 0639 emit dataSourceValid(_file, _requestID); 0640 } 0641 0642 0643 // vim: ts=2 sw=2 et 0644 0645