Source

quakim / server / model / database.hpp

Full commit
/*
   Copyright 2011-2012 Haiko Schol <hs@haikoschol.com>

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/
#ifndef DATABASE_HPP
#define DATABASE_HPP

#include <QBuffer>
#include <QFile>
#include <QVector>

#include <cstring>

namespace quakim {
namespace model {

template <typename RecordType>
class Database
{
public:
    explicit Database(const QString &filename) :
        m_nextId(1),
        m_storage(new QFile(filename))
    {}

    explicit Database(QIODevice *storage) :
        m_nextId(1),
        m_storage(storage)
    {}

    ~Database() { delete m_storage; }
    qint64 size() const { return m_storage->size(); }
    bool isOpen() const { return m_storage->isOpen(); }

    bool open()
    {
        if (m_storage->isOpen())
            return true;

        if (!m_storage->open(QIODevice::ReadWrite | QIODevice::Append))
            return false;

        QVector<RecordType> records = fetchAll();
        foreach (const RecordType &record, records) {
            if (record.id > m_nextId) {
                m_nextId = record.id + 1;
            }
        }

        return true;
    }

    void close() { m_storage->close(); }
    bool insert(RecordType &record);
    bool update(RecordType &record);
    bool remove(quint64 id);
    bool fetch(quint64 id, RecordType &out);
    // inefficient/not scalable for large RecordType TODO implement iterator
    QVector<RecordType> fetchAll();

private:
    Database(const Database &other);
    Database& operator=(const Database &rhs);

    quint64 m_nextId;
    QIODevice *m_storage;
};

template <typename RecordType>
QVector<RecordType> Database<RecordType>::fetchAll()
{
    if (!isOpen()
        || static_cast<size_t>(m_storage->size()) < sizeof(RecordType))
        return QVector<RecordType>();

    m_storage->seek(0);

    QVector<RecordType> records(m_storage->bytesAvailable()
                                / sizeof(RecordType));

    char *dst = reinterpret_cast<char*>(&records[0]);
    m_storage->read(dst, m_storage->bytesAvailable());
    return records;
}

template <typename RecordType>
bool Database<RecordType>::insert(RecordType &record)
{
    if (!isOpen()) return false;

    record.id = m_nextId;
    if (!m_storage->atEnd()) {
        m_storage->seek(m_storage->bytesAvailable());
    }

    char *data = reinterpret_cast<char*>(&record);
    bool written = m_storage->write(data, sizeof record);

    if (written) {
        m_nextId++;
    }
    return written;
}

template <typename RecordType>
bool Database<RecordType>::update(RecordType &record)
{
    if (record.id >= m_nextId) return false;

    m_storage->seek(0);

    while (!m_storage->atEnd()) {
        RecordType current;
        std::memset(&current, 0, sizeof current);
        m_storage->read(reinterpret_cast<char*>(&current), sizeof current);
        if (current.id == record.id) {
            m_storage->seek(m_storage->pos() - sizeof record);
            m_storage->write(reinterpret_cast<char*>(&record), sizeof record);
            return true;
        }
    }
    return false;
}

template <typename RecordType>
bool Database<RecordType>::fetch(quint64 id, RecordType &out)
{
    if (id >= m_nextId) return false;

    m_storage->seek(0);

    while (!m_storage->atEnd()) {
        RecordType record;
        std::memset(&record, 0, sizeof record);
        m_storage->read(reinterpret_cast<char*>(&record), sizeof record);
        if (record.id == id) {
            out = record;
            return true;
        }
    }

    return false;
}

// FIXME ridiculous inefficiency is ridiculous
template <typename RecordType>
bool Database<RecordType>::remove(quint64 id)
{
    if (id >= m_nextId) return false;

    m_storage->seek(0);

    QBuffer buffer;
    if (!buffer.open(QIODevice::ReadWrite)) return false;

    while (!m_storage->atEnd()) {
        RecordType record;
        m_storage->read(reinterpret_cast<char*>(&record), sizeof record);

        if (record.id != id) {
            buffer.write(reinterpret_cast<char*>(&record), sizeof record);
        }
    }

    buffer.seek(0);
    m_storage->close();
    if (!m_storage->open(QIODevice::ReadWrite | QIODevice::Truncate))
        return false;

    m_storage->write(buffer.readAll());
    return true;
}

}
}
#endif // DATABASE_HPP