File indexing completed on 2024-05-12 16:39:36

0001 /* This file is part of the KDE project
0002    Copyright (C) 2005-2011 Jarosław Staniek <staniek@kde.org>
0003 
0004    This library is free software; you can redistribute it and/or
0005    modify it under the terms of the GNU Library General Public
0006    License as published by the Free Software Foundation; either
0007    version 2 of the License, or (at your option) any later version.
0008 
0009    This library is distributed in the hope that it will be useful,
0010    but WITHOUT ANY WARRANTY; without even the implied warranty of
0011    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0012    Library General Public License for more details.
0013 
0014    You should have received a copy of the GNU Library General Public License
0015    along with this library; see the file COPYING.LIB.  If not, write to
0016    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0017  * Boston, MA 02110-1301, USA.
0018 */
0019 
0020 #include "kexiblobbuffer.h"
0021 #include <kexiutils/utils.h>
0022 
0023 #include <assert.h>
0024 
0025 #include <QFile>
0026 #include <QFileInfo>
0027 #include <QBuffer>
0028 #include <QPixmap>
0029 #include <QHash>
0030 #include <QMimeDatabase>
0031 #include <QMimeType>
0032 #include <QDebug>
0033 #include <QImageReader>
0034 
0035 #include <KDbConnection>
0036 #include <KDbQuerySchema>
0037 #include <KDbRecordData>
0038 
0039 Q_GLOBAL_STATIC(KexiBLOBBuffer, _buffer)
0040 
0041 //-----------------
0042 
0043 class Q_DECL_HIDDEN KexiBLOBBuffer::Private
0044 {
0045 public:
0046     Private()
0047             : maxId(0)
0048             , conn(0)
0049     {
0050     }
0051     ~Private() {
0052         foreach(Item* item, inMemoryItems) {
0053             delete item;
0054         }
0055         inMemoryItems.clear();
0056         foreach(Item* item, storedItems) {
0057             delete item;
0058         }
0059         storedItems.clear();
0060     }
0061     Id_t maxId; //!< Used to compute maximal recently used identifier for unstored BLOB
0062 //! @todo will be changed to QHash<quint64, Item>
0063     QHash<Id_t, Item*> inMemoryItems; //!< for unstored BLOBs
0064     QHash<Id_t, Item*> storedItems; //!< for stored items
0065     QHash<QString, Item*> itemsByURL;
0066     //! @todo KEXI3 use equivalent of QPointer<KDbConnection>
0067     KDbConnection *conn;
0068 };
0069 
0070 //-----------------
0071 
0072 KexiBLOBBuffer::Handle::Handle(Item* item)
0073         : m_item(item)
0074 {
0075     if (m_item)
0076         m_item->refs++;
0077 }
0078 
0079 KexiBLOBBuffer::Handle::Handle(const Handle& handle)
0080 {
0081     *this = handle;
0082 }
0083 
0084 KexiBLOBBuffer::Handle::Handle()
0085         : m_item(0)
0086 {
0087 }
0088 
0089 KexiBLOBBuffer::Handle::~Handle()
0090 {
0091     if (m_item) {
0092         m_item->refs--;
0093         if (m_item->refs <= 0)
0094             KexiBLOBBuffer::self()->removeItem(m_item->id, m_item->stored);
0095     }
0096 }
0097 
0098 KexiBLOBBuffer::Handle& KexiBLOBBuffer::Handle::operator=(const Handle & handle)
0099 {
0100     m_item = handle.m_item;
0101     if (m_item)
0102         m_item->refs++;
0103     return *this;
0104 }
0105 
0106 void KexiBLOBBuffer::Handle::setStoredWidthID(KexiBLOBBuffer::Id_t id)
0107 {
0108     if (!m_item)
0109         return;
0110     if (m_item->stored) {
0111         qWarning() << "object for id=" << id << " is aleady stored";
0112         return;
0113     }
0114 
0115     KexiBLOBBuffer::self()->takeItem(m_item);
0116     m_item->id = id; //new id
0117     m_item->stored = true;
0118 //! @todo What about other handles for this item?
0119 //! @todo They were assuming it's unstored item, but it's stored now....
0120     KexiBLOBBuffer::self()->insertItem(m_item);
0121 }
0122 
0123 //-----------------
0124 
0125 KexiBLOBBuffer::Item::Item(const QByteArray& data, KexiBLOBBuffer::Id_t ident, bool _stored,
0126                            const QString& _name, const QString& _caption, const QString& _mimeType,
0127                            Id_t _folderId, const QPixmap& pixmap)
0128         : name(_name), caption(_caption), mimeType(_mimeType), refs(0),
0129         id(ident), folderId(_folderId), stored(_stored),
0130         m_pixmapLoaded(new bool(false)/*workaround for pixmap() const*/)
0131 {
0132     if (pixmap.isNull())
0133         m_pixmap = new QPixmap();
0134     else
0135         m_pixmap = new QPixmap(pixmap);
0136 
0137     if (data.isEmpty())
0138         m_data = new QByteArray();
0139     else
0140         m_data = new QByteArray(data);
0141 }
0142 
0143 KexiBLOBBuffer::Item::~Item()
0144 {
0145     delete m_pixmap;
0146     m_pixmap = 0;
0147     delete m_data;
0148     m_data = 0;
0149     delete m_pixmapLoaded;
0150 }
0151 
0152 QPixmap KexiBLOBBuffer::Item::pixmap() const
0153 {
0154 //! @todo ...
0155     if (!*m_pixmapLoaded && m_pixmap->isNull() && !m_data->isEmpty()) {
0156         const QMimeDatabase db;
0157         const QMimeType mime(db.mimeTypeForName(mimeType));
0158         if (!mime.isValid()
0159             || !QImageReader::supportedMimeTypes().contains(mimeType.toLatin1())
0160             || !KexiUtils::loadPixmapFromData(m_pixmap, *m_data, mime.preferredSuffix().toLatin1()))
0161         {
0162             //! @todo inform about error?
0163         }
0164         *m_pixmapLoaded = true;
0165     }
0166     return *m_pixmap;
0167 }
0168 
0169 /*! @return Extension for QPixmap::save() from @a mimeType.
0170     @todo default PNG ok? */
0171 static QString formatFromMimeType(const QString& mimeType, const QString& defaultFormat = "PNG")
0172 {
0173     QMimeDatabase db;
0174     const QMimeType mime = db.mimeTypeForName(mimeType);
0175     if (!mime.isValid()) {
0176         return defaultFormat;
0177     }
0178     return mime.preferredSuffix();
0179 }
0180 
0181 QByteArray KexiBLOBBuffer::Item::data() const
0182 {
0183     if (!m_data->isEmpty())
0184         return *m_data;
0185 
0186     if (m_data->isEmpty() && m_pixmap->isNull())
0187         return QByteArray();
0188 
0189     if (m_data->isEmpty() && !m_pixmap->isNull()) {
0190         //convert pixmap to byte array
0191         //(do it only on demand)
0192         QBuffer buffer(m_data);
0193         if (!buffer.open(QIODevice::WriteOnly)) {
0194             //! @todo err msg
0195             qWarning() << "!QBuffer::open()";
0196         }
0197         if (!m_pixmap->save(&buffer, formatFromMimeType(mimeType).toLatin1()))
0198         {
0199             //! @todo err msg
0200             qWarning() << "!QPixmap::save()";
0201         }
0202     }
0203     return *m_data;
0204 }
0205 
0206 //-----------------
0207 
0208 KexiBLOBBuffer::KexiBLOBBuffer()
0209         : QObject()
0210         , d(new Private())
0211 {
0212 }
0213 
0214 KexiBLOBBuffer::~KexiBLOBBuffer()
0215 {
0216     delete d;
0217 }
0218 
0219 KexiBLOBBuffer::Handle KexiBLOBBuffer::insertPixmap(const QUrl &url)
0220 {
0221     if (url.isEmpty())
0222         return KexiBLOBBuffer::Handle();
0223     if (!url.isValid()) {
0224         qWarning() << "INVALID URL" << url;
0225         return KexiBLOBBuffer::Handle();
0226     }
0227 //! @todo what about searching by filename only and then compare data?
0228     Item * item = d->itemsByURL.value(url.toDisplayString());
0229     if (item)
0230         return KexiBLOBBuffer::Handle(item);
0231 
0232     QString fileName = url.isLocalFile() ? url.toLocalFile() : url.toDisplayString();
0233 //! @todo download the file if remote, then set fileName properly
0234     QFile f(fileName);
0235     if (!f.open(QIODevice::ReadOnly)) {
0236         //! @todo err msg
0237         return KexiBLOBBuffer::Handle();
0238     }
0239     QByteArray data(f.readAll());
0240     if (f.error() != QFile::NoError) {
0241         //! @todo err msg
0242         return KexiBLOBBuffer::Handle();
0243     }
0244     QFileInfo fi(url.fileName());
0245     QString caption(fi.baseName().replace('_', ' ').simplified());
0246     QMimeDatabase db;
0247     const QMimeType mimeType(db.mimeTypeForFileNameAndData(fileName, data));
0248 
0249     item = new Item(data, ++d->maxId, /*!stored*/false, url.fileName(), caption, mimeType.name());
0250     insertItem(item);
0251 
0252     //cache
0253     item->prettyURL = url.toDisplayString();
0254     d->itemsByURL.insert(url.toDisplayString(), item);
0255     return KexiBLOBBuffer::Handle(item);
0256 }
0257 
0258 KexiBLOBBuffer::Handle KexiBLOBBuffer::insertObject(const QByteArray& data,
0259         const QString& name, const QString& caption, const QString& mimeType, KexiBLOBBuffer::Id_t identifier)
0260 {
0261     KexiBLOBBuffer::Id_t newIdentifier;
0262     if (identifier > 0)
0263         newIdentifier = identifier;
0264     else
0265         newIdentifier = ++d->maxId;
0266 
0267     Item *item = new Item(data, newIdentifier, identifier > 0, name, caption, mimeType);
0268     insertItem(item);
0269     return KexiBLOBBuffer::Handle(item);
0270 }
0271 
0272 KexiBLOBBuffer::Handle KexiBLOBBuffer::insertPixmap(const QPixmap& pixmap)
0273 {
0274     if (pixmap.isNull())
0275         return KexiBLOBBuffer::Handle();
0276 
0277     Item * item = new Item(
0278         QByteArray(), //(pixmap will be converted to byte array on demand)
0279         ++d->maxId,
0280         false, //not stored
0281         QString(),
0282         QString(),
0283         "image/png", //!< @todo OK? What about jpegs?
0284         0, //folder id
0285         pixmap);
0286 
0287     insertItem(item);
0288     return KexiBLOBBuffer::Handle(item);
0289 }
0290 
0291 KexiBLOBBuffer::Handle KexiBLOBBuffer::objectForId(Id_t id, bool stored)
0292 {
0293     if (id <= 0)
0294         return KexiBLOBBuffer::Handle();
0295     if (stored) {
0296         Item *item = d->storedItems.value(id);
0297         if (item || !d->conn)
0298             return KexiBLOBBuffer::Handle(item);
0299         //retrieve stored BLOB:
0300         Q_ASSERT(d->conn);
0301         KDbTableSchema *blobsTable = d->conn->tableSchema("kexi__blobs");
0302         if (!blobsTable) {
0303             //! @todo err msg
0304             return KexiBLOBBuffer::Handle();
0305         }
0306         /*  QStringList where;
0307             where << "o_id";
0308             KDbPreparedStatement::Ptr st = d->conn->prepareStatement(
0309               KDbPreparedStatement::SelectStatement, *blobsTable, where);*/
0310 //! @todo use KDbPreparedStatement
0311         KDbQuerySchema schema;
0312         schema.addField(blobsTable->field("o_data"));
0313         schema.addField(blobsTable->field("o_name"));
0314         schema.addField(blobsTable->field("o_caption"));
0315         schema.addField(blobsTable->field("o_mime"));
0316         schema.addField(blobsTable->field("o_folder_id"));
0317         QString errorMessage;
0318         QString errorDescription;
0319         if (!schema.addToWhereExpression(blobsTable->field("o_id"), QVariant((qint64)id),
0320                                          '=', &errorMessage, &errorDescription))
0321         {
0322             qWarning() << "message=" << errorMessage
0323                        << "description=" << errorDescription;
0324             return KexiBLOBBuffer::Handle();
0325         }
0326         KDbRecordData recordData;
0327         tristate res = d->conn->querySingleRecord(&schema, &recordData);
0328         if (res != true || recordData.size() < 4) {
0329             //! @todo err msg
0330             qWarning() << "id=" << id << "stored=" << stored
0331                 << ": res!=true || recordData.size()<4; res==" << res.toString()
0332                 << "recordData.size()==" << recordData.size();
0333             return KexiBLOBBuffer::Handle();
0334         }
0335 
0336         item = new Item(
0337             recordData.at(0).toByteArray(),
0338             id,
0339             true, //stored
0340             recordData.at(1).toString(),
0341             recordData.at(2).toString(),
0342             recordData.at(3).toString(),
0343             (Id_t)recordData.at(4).toInt() //!< @todo folder id: fix Id_t for Qt4
0344         );
0345 
0346         insertItem(item);
0347         return KexiBLOBBuffer::Handle(item);
0348     }
0349     return KexiBLOBBuffer::Handle(d->inMemoryItems.value(id));
0350 }
0351 
0352 KexiBLOBBuffer::Handle KexiBLOBBuffer::objectForId(Id_t id)
0353 {
0354     KexiBLOBBuffer::Handle h(objectForId(id, false/* !stored */));
0355     if (h)
0356         return h;
0357     return objectForId(id, true/*stored*/);
0358 }
0359 
0360 void KexiBLOBBuffer::removeItem(Id_t id, bool stored)
0361 {
0362     Item *item;
0363     if (stored)
0364         item = d->storedItems.take(id);
0365     else
0366         item = d->inMemoryItems.take(id);
0367 
0368     if (item && !item->prettyURL.isEmpty()) {
0369         d->itemsByURL.remove(item->prettyURL);
0370     }
0371     delete item;
0372 }
0373 
0374 void KexiBLOBBuffer::takeItem(Item *item)
0375 {
0376     Q_ASSERT(item);
0377     if (item->stored)
0378         d->storedItems.take(item->id);
0379     else
0380         d->inMemoryItems.take(item->id);
0381 }
0382 
0383 void KexiBLOBBuffer::insertItem(Item *item)
0384 {
0385     Q_ASSERT(item);
0386     if (item->stored)
0387         d->storedItems.insert(item->id, item);
0388     else
0389         d->inMemoryItems.insert(item->id, item);
0390 }
0391 
0392 void KexiBLOBBuffer::setConnection(KDbConnection *conn)
0393 {
0394     KexiBLOBBuffer::self()->d->conn = conn;
0395 }
0396 
0397 KexiBLOBBuffer* KexiBLOBBuffer::self()
0398 {
0399     return _buffer;
0400 }
0401