///////////////////////////////////////////////////////////////////////// // storage.cpp // // // // Copyright (C) 2010 Brian Pritchett // // Copyright (C) 2010 Marco Martin // // // // This library is free software; you can redistribute it and/or // // modify it under the terms of the GNU Lesser General Public // // License as published by the Free Software Foundation; either // // version 2.1 of the License, or (at your option) any later version. // // // // This library is distributed in the hope that it will be useful, // // but WITHOUT ANY WARRANTY; without even the implied warranty of // // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // // Lesser General Public License for more details. // // // // You should have received a copy of the GNU Lesser General Public // // License along with this library; if not, write to the Free Software // // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA // // 02110-1301 USA // ///////////////////////////////////////////////////////////////////////// #include "private/storage_p.h" //Qt #include #include #include #include //KDE #include #include //Plasma #include "applet.h" #include "dataengine.h" #include "abstractrunner.h" class RefCountedDatabase { public: void ref() { if (m_ref == 0) { m_db = QSqlDatabase::addDatabase("QSQLITE", QString("plasma-storage-%1").arg((ulong)this)); m_db.setDatabaseName(KStandardDirs::locateLocal("appdata", "plasma-storage.db")); } //Q_ASSERT(db.isValid()); m_ref.ref(); } bool deref() { //kill the database if the last one in use bool last = !m_ref.deref(); if (last) { m_db.close(); QString name = m_db.connectionName(); m_db = QSqlDatabase(); QSqlDatabase::removeDatabase(name); } return !last; } inline QSqlDatabase *database() { return &m_db; } private: QSqlDatabase m_db; QAtomicInt m_ref; }; static QThreadStorage s_databasePool; //Storage Job implentation StorageJob::StorageJob(const QString& destination, const QString& operation, const QMap& parameters, QObject *parent) : ServiceJob(destination, operation, parameters, parent), m_clientName(destination) { m_rdb = s_databasePool.localData(); if (m_rdb == 0) { s_databasePool.setLocalData(new RefCountedDatabase); m_rdb = s_databasePool.localData(); } m_rdb->ref(); if (!m_rdb->database()->open()) { kWarning() << "Unable to open the plasma storage cache database: " << m_rdb->database()->lastError(); } else if (!m_rdb->database()->tables().contains(m_clientName)) { QSqlQuery query(*m_rdb->database()); //bindValue doesn't seem to be able to replace stuff in create table query.prepare(QString("create table ")+m_clientName+" (valueGroup varchar(256), id varchar(256), data clob, creationTime datetime, accessTime datetime, primary key (valueGroup, id))"); query.exec(); } } StorageJob::~StorageJob() { if (!m_rdb->deref()) { s_databasePool.setLocalData(0); } } void StorageJob::start() { if (!m_rdb->database()->isOpen()) { return; } QMap params = parameters(); QString valueGroup = params["group"].toString(); if (valueGroup.isEmpty()) { valueGroup = "default"; } if (operationName() == "save") { QSqlQuery query(*m_rdb->database()); query.prepare("delete from "+m_clientName+" where valueGroup=:valueGroup and id = :id"); query.bindValue(":valueGroup", valueGroup); query.bindValue(":id", params["key"].toString()); query.exec(); query.prepare("insert into "+m_clientName+" values(:valueGroup, :id, :datavalue, date('now'), date('now'))"); query.bindValue(":id", params["key"].toString()); query.bindValue(":valueGroup", valueGroup); query.bindValue(":datavalue", params["data"].toString()); const bool success = query.exec(); setResult(success); } else if (operationName() == "retrieve") { QSqlQuery query(*m_rdb->database()); //a bit redundant but should be the faster way with less string concatenation as possible if (params["key"].toString().isEmpty()) { //update modification time query.prepare("update "+m_clientName+" set accessTime=date('now') where valueGroup=:valueGroup"); query.bindValue(":valueGroup", valueGroup); query.exec(); query.prepare("select * from "+m_clientName+" where valueGroup=:valueGroup"); query.bindValue(":valueGroup", valueGroup); } else { //update modification time query.prepare("update "+m_clientName+" set accessTime=date('now') where valueGroup=:valueGroup and id=:key"); query.bindValue(":valueGroup", valueGroup); query.bindValue(":key", params["key"].toString()); query.exec(); query.prepare("select * from "+m_clientName+" where valueGroup=:valueGroup and id=:key"); query.bindValue(":valueGroup", valueGroup); query.bindValue(":key", params["key"].toString()); } const bool success = query.exec(); QHash h; if (success) { QSqlRecord rec = query.record(); const int keyColumn = rec.indexOf("id"); const int dataColumn = rec.indexOf("data"); while (query.next()) { h[query.value(keyColumn).toString()] = query.value(dataColumn).toString(); } setResult(h); } } else if (operationName() == "delete") { QSqlQuery query(*m_rdb->database()); if (params["key"].toString().isEmpty()) { query.prepare("delete from "+m_clientName+" where valueGroup=:valueGroup"); query.bindValue(":valueGroup", valueGroup); } else { query.prepare("delete from "+m_clientName+" where valueGroup=:valueGroup and id=:key"); query.bindValue(":valueGroup", valueGroup); query.bindValue(":key", params["key"].toString()); } const bool success = query.exec(); setResult(success); } else if (operationName() == "expire") { QSqlQuery query(*m_rdb->database()); if (valueGroup.isEmpty()) { query.prepare("delete from "+m_clientName+" where accessTime < :date"); QDateTime time(QDateTime::currentDateTime()); time.addSecs(-params["age"].toUInt()); query.bindValue(":date", time.toTime_t()); } else { query.prepare("delete from "+m_clientName+" where valueGroup=:valueGroup and accessTime < :date"); query.bindValue(":valueGroup", valueGroup); QDateTime time(QDateTime::currentDateTime()); time.addSecs(-params["age"].toUInt()); query.bindValue(":date", time.toTime_t()); } const bool success = query.exec(); setResult(success); } else { setError(true); } m_rdb->database()->commit(); } Plasma::ServiceJob* Storage::createJob(const QString &operation, QMap ¶meters) { return new StorageJob(m_clientName, operation, parameters, this); } //Storage implementation Storage::Storage(QObject* parent) : Plasma::Service(parent) { //search among parents for an applet or dataengine: if found call the table as its plugin name QObject *parentObject = this; QString clientName("data"); while ((parentObject = parentObject->parent())) { Plasma::Applet *applet = qobject_cast(parentObject); if (applet) { m_clientName = applet->pluginName(); break; } Plasma::DataEngine *engine = qobject_cast(parentObject); if (engine) { m_clientName = engine->pluginName(); break; } Plasma::AbstractRunner *runner = qobject_cast(parentObject); if (runner) { m_clientName = runner->id(); break; } } m_clientName = m_clientName.replace('.', "_"); m_clientName = m_clientName.replace('-', "_"); setName("storage"); } Storage::~Storage() { } #include "storage_p.moc"