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