|
|
|
// Copyright (c) 2003 Charles Samuels <charles@kde.org>
|
|
|
|
// See the file COPYING for redistribution terms.
|
|
|
|
|
|
|
|
#include "base.h"
|
|
|
|
#include "file.h"
|
|
|
|
|
|
|
|
#include "kdbt.h"
|
|
|
|
#include "kbuffer.h"
|
|
|
|
|
|
|
|
#include <tqstringlist.h>
|
|
|
|
#include <tqmap.h>
|
|
|
|
#include <tqfile.h>
|
|
|
|
#include <tqdom.h>
|
|
|
|
|
|
|
|
#include <cstdlib>
|
|
|
|
#include <assert.h>
|
|
|
|
#include <db_cxx.h>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
struct Base::Private
|
|
|
|
{
|
|
|
|
Private() : db(0, DB_CXX_NO_EXCEPTIONS) { }
|
|
|
|
Db db;
|
|
|
|
typedef KDbt<FileId> Key;
|
|
|
|
typedef KDbt<TQStringList> Data;
|
|
|
|
|
|
|
|
FileId high;
|
|
|
|
|
|
|
|
FileId cachedId;
|
|
|
|
mutable TQMap<TQString,TQString> cachedProperties;
|
|
|
|
|
|
|
|
TQPtrList<Slice> slices;
|
|
|
|
int sliceHigh;
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
Base::Base(const TQString &file)
|
|
|
|
{
|
|
|
|
d = new Private;
|
|
|
|
d->cachedId = 0;
|
|
|
|
|
|
|
|
TQCString filename = TQFile::encodeName(file);
|
|
|
|
|
|
|
|
bool create = true;
|
|
|
|
if (d->db.open(
|
|
|
|
#if DB_VERSION_MINOR > 0 && DB_VERSION_MAJOR >= 4
|
|
|
|
NULL,
|
|
|
|
#endif
|
|
|
|
filename,
|
|
|
|
0, DB_BTREE, DB_NOMMAP, 0
|
|
|
|
)==0)
|
|
|
|
{ // success
|
|
|
|
Private::Data data;
|
|
|
|
Private::Key key(0);
|
|
|
|
if (d->db.get(0, &key, &data, 0)==0)
|
|
|
|
{
|
|
|
|
TQStringList strs;
|
|
|
|
data.get(strs);
|
|
|
|
|
|
|
|
mFormatVersion = strs[0].toUInt(0, 16); // TODO
|
|
|
|
d->high = strs[1].toUInt();
|
|
|
|
|
|
|
|
if (strs.count() == 3)
|
|
|
|
loadMetaXML(strs[2]);
|
|
|
|
else
|
|
|
|
loadMetaXML("");
|
|
|
|
|
|
|
|
create=false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (create)
|
|
|
|
{ // failure
|
|
|
|
TQFile(filename).remove();
|
|
|
|
d->db.open(
|
|
|
|
#if DB_VERSION_MINOR > 0 && DB_VERSION_MAJOR >= 4
|
|
|
|
NULL,
|
|
|
|
#endif
|
|
|
|
filename,0, DB_BTREE, DB_NOMMAP|DB_CREATE,0
|
|
|
|
);
|
|
|
|
|
|
|
|
d->high=0;
|
|
|
|
TQStringList strs;
|
|
|
|
strs << "00010002" << "0" << "";
|
|
|
|
resetFormatVersion();
|
|
|
|
loadMetaXML("");
|
|
|
|
Private::Data data(strs);
|
|
|
|
Private::Key key(0);
|
|
|
|
// in the stringlist for Key(0), we have the following list:
|
|
|
|
// { "version of the file",
|
|
|
|
// "the high extreme (auto-increment counter in SQL terminology)",
|
|
|
|
// "the metaxml"
|
|
|
|
// }
|
|
|
|
d->db.put(0, &key, &data, 0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Base::resetFormatVersion()
|
|
|
|
{
|
|
|
|
mFormatVersion = 0x00010002;
|
|
|
|
}
|
|
|
|
|
|
|
|
Base::~Base()
|
|
|
|
{
|
|
|
|
TQStringList strs;
|
|
|
|
strs << TQString::number(mFormatVersion, 16) << TQString::number(d->high);
|
|
|
|
strs << saveMetaXML();
|
|
|
|
|
|
|
|
Private::Data data(strs);
|
|
|
|
Private::Key key(0);
|
|
|
|
d->db.put(0, &key, &data, 0);
|
|
|
|
d->db.sync(0);
|
|
|
|
d->db.close(0);
|
|
|
|
delete d;
|
|
|
|
}
|
|
|
|
|
|
|
|
File Base::add(const TQString &file)
|
|
|
|
{
|
|
|
|
Private::Key key(++d->high);
|
|
|
|
TQStringList properties;
|
|
|
|
properties << "file" << file;
|
|
|
|
Private::Data data(properties);
|
|
|
|
|
|
|
|
unless (d->db.put(0, &key, &data, 0))
|
|
|
|
{
|
|
|
|
// success !
|
|
|
|
File f(this, d->high);
|
|
|
|
f.makeCache();
|
|
|
|
emit added(f);
|
|
|
|
return f;
|
|
|
|
}
|
|
|
|
|
|
|
|
return File();
|
|
|
|
}
|
|
|
|
|
|
|
|
File Base::tqfind(FileId id)
|
|
|
|
{
|
|
|
|
if (id == 0) return File();
|
|
|
|
|
|
|
|
Private::Key key(id);
|
|
|
|
Private::Data data;
|
|
|
|
|
|
|
|
unless (d->db.get(0, &key, &data, 0))
|
|
|
|
{
|
|
|
|
// exists
|
|
|
|
return File(this, id);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return File(); // null item
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Base::clear()
|
|
|
|
{
|
|
|
|
for (FileId id = high(); id >= 1; id--)
|
|
|
|
{
|
|
|
|
File f = tqfind(id);
|
|
|
|
if (f)
|
|
|
|
f.remove();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
FileId Base::high() const
|
|
|
|
{
|
|
|
|
return d->high;
|
|
|
|
}
|
|
|
|
|
|
|
|
File Base::first(FileId first)
|
|
|
|
{
|
|
|
|
if (first > high()) return File();
|
|
|
|
|
|
|
|
while (!tqfind(first))
|
|
|
|
{
|
|
|
|
++first;
|
|
|
|
if (first > high())
|
|
|
|
return File();
|
|
|
|
}
|
|
|
|
return File(this, first);
|
|
|
|
}
|
|
|
|
|
|
|
|
TQString Base::property(FileId id, const TQString &property) const
|
|
|
|
{
|
|
|
|
loadIntoCache(id);
|
|
|
|
if (!d->cachedProperties.tqcontains(property)) return TQString();
|
|
|
|
TQMap<TQString,TQString>::Iterator i = d->cachedProperties.tqfind(property);
|
|
|
|
return i.data();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Base::setProperty(FileId id, const TQString &key, const TQString &value)
|
|
|
|
{
|
|
|
|
loadIntoCache(id);
|
|
|
|
d->cachedProperties.insert(key, value);
|
|
|
|
// reinsert it into the DB
|
|
|
|
|
|
|
|
TQStringList props;
|
|
|
|
for (
|
|
|
|
TQMap<TQString,TQString>::Iterator i(d->cachedProperties.begin());
|
|
|
|
i != d->cachedProperties.end(); ++i
|
|
|
|
)
|
|
|
|
{
|
|
|
|
props << i.key() << i.data();
|
|
|
|
}
|
|
|
|
|
|
|
|
Private::Data data(props);
|
|
|
|
Private::Key dbkey(id);
|
|
|
|
d->db.put(0, &dbkey, &data, 0);
|
|
|
|
d->db.sync(0);
|
|
|
|
|
|
|
|
emit modified(File(this, id));
|
|
|
|
}
|
|
|
|
|
|
|
|
TQStringList Base::properties(FileId id) const
|
|
|
|
{
|
|
|
|
loadIntoCache(id);
|
|
|
|
TQStringList props;
|
|
|
|
for (
|
|
|
|
TQMap<TQString,TQString>::Iterator i(d->cachedProperties.begin());
|
|
|
|
i != d->cachedProperties.end(); ++i
|
|
|
|
)
|
|
|
|
{
|
|
|
|
props << i.key();
|
|
|
|
}
|
|
|
|
return props;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Base::clearProperty(FileId id, const TQString &key)
|
|
|
|
{
|
|
|
|
loadIntoCache(id);
|
|
|
|
d->cachedProperties.remove(key);
|
|
|
|
// reinsert it into the DB
|
|
|
|
|
|
|
|
TQStringList props;
|
|
|
|
for (
|
|
|
|
TQMap<TQString,TQString>::Iterator i(d->cachedProperties.begin());
|
|
|
|
i != d->cachedProperties.end(); ++i
|
|
|
|
)
|
|
|
|
{
|
|
|
|
if (i.key() != key)
|
|
|
|
props << i.key() << i.data();
|
|
|
|
}
|
|
|
|
|
|
|
|
Private::Data data(props);
|
|
|
|
Private::Key dbkey(id);
|
|
|
|
d->db.put(0, &dbkey, &data, 0);
|
|
|
|
d->db.sync(0);
|
|
|
|
|
|
|
|
emit modified(File(this, id));
|
|
|
|
}
|
|
|
|
|
|
|
|
void Base::remove(File file)
|
|
|
|
{
|
|
|
|
Private::Key key(file.id());
|
|
|
|
|
|
|
|
unless (d->db.del(0, &key, 0))
|
|
|
|
{
|
|
|
|
emit removed(file);
|
|
|
|
if (file.id() == d->high)
|
|
|
|
{
|
|
|
|
d->high--; // optimization
|
|
|
|
}
|
|
|
|
}
|
|
|
|
d->db.sync(0);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Base::loadIntoCache(FileId id) const
|
|
|
|
{
|
|
|
|
if (d->cachedId == id) return;
|
|
|
|
|
|
|
|
d->cachedId = id;
|
|
|
|
d->cachedProperties.clear();
|
|
|
|
|
|
|
|
Private::Key key(id);
|
|
|
|
Private::Data data;
|
|
|
|
unless (d->db.get(0, &key, &data, 0))
|
|
|
|
{
|
|
|
|
TQStringList props;
|
|
|
|
data.get(props);
|
|
|
|
|
|
|
|
if (props.count() % 2)
|
|
|
|
{ // corrupt?
|
|
|
|
const_cast<Base*>(this)->remove(File(const_cast<Base*>(this), id));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (TQStringList::Iterator i(props.begin()); i != props.end(); ++i)
|
|
|
|
{
|
|
|
|
TQString &key = *i;
|
|
|
|
TQString &value = *++i;
|
|
|
|
d->cachedProperties.insert(key, value);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
TQString Base::saveMetaXML()
|
|
|
|
{
|
|
|
|
TQDomDocument doc;
|
|
|
|
doc.setContent(TQString("<meta />"));
|
|
|
|
TQDomElement doce = doc.documentElement();
|
|
|
|
|
|
|
|
TQDomElement e = doc.createElement("slices");
|
|
|
|
e.setAttribute("highslice", TQString::number(d->sliceHigh));
|
|
|
|
doce.appendChild(e);
|
|
|
|
|
|
|
|
for (TQPtrListIterator<Slice> i(d->slices); *i; ++i)
|
|
|
|
{
|
|
|
|
TQDomElement slice = doc.createElement("slice");
|
|
|
|
slice.setAttribute("id", (*i)->id());
|
|
|
|
slice.setAttribute("name", (*i)->name());
|
|
|
|
e.appendChild(slice);
|
|
|
|
}
|
|
|
|
return doc.toString();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Base::move(FileId oldid, FileId newid)
|
|
|
|
{
|
|
|
|
Private::Key key(oldid);
|
|
|
|
Private::Data data;
|
|
|
|
unless (d->db.get(0, &key, &data, 0))
|
|
|
|
{
|
|
|
|
TQStringList props;
|
|
|
|
data.get(props);
|
|
|
|
d->db.del(0, &key, 0);
|
|
|
|
|
|
|
|
Private::Key key2(newid);
|
|
|
|
d->db.put(0, &key2, &data, 0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Base::dump()
|
|
|
|
{
|
|
|
|
for (FileId id=1; id <= high(); id++)
|
|
|
|
{
|
|
|
|
TQStringList props = properties(id);
|
|
|
|
std::cerr << id << '.';
|
|
|
|
for (TQStringList::Iterator i(props.begin()); i != props.end(); ++i)
|
|
|
|
{
|
|
|
|
TQString prop = *i;
|
|
|
|
std::cerr << ' ' << prop.latin1() << '=' << property(id, prop).latin1();
|
|
|
|
}
|
|
|
|
std::cerr << std::endl;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Base::notifyChanged(const File &file)
|
|
|
|
{
|
|
|
|
emit modified(file);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
TQPtrList<Slice> Base::slices()
|
|
|
|
{
|
|
|
|
return d->slices;
|
|
|
|
}
|
|
|
|
|
|
|
|
Slice *Base::addSlice(const TQString &name)
|
|
|
|
{
|
|
|
|
Slice *sl = new Slice(this, d->sliceHigh++, name);
|
|
|
|
d->slices.append(sl);
|
|
|
|
slicesModified();
|
|
|
|
return sl;
|
|
|
|
}
|
|
|
|
|
|
|
|
Slice *Base::defaultSlice()
|
|
|
|
{
|
|
|
|
for (TQPtrListIterator<Slice> i(d->slices); *i; ++i)
|
|
|
|
{
|
|
|
|
if ((*i)->id() == 0) return *i;
|
|
|
|
}
|
|
|
|
|
|
|
|
abort();
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Base::removeSlice(Slice *slice)
|
|
|
|
{
|
|
|
|
assert(slice->id() > 0);
|
|
|
|
d->slices.removeRef(slice);
|
|
|
|
delete slice;
|
|
|
|
}
|
|
|
|
|
|
|
|
Slice *Base::sliceById(int id)
|
|
|
|
{
|
|
|
|
for (TQPtrListIterator<Slice> i(d->slices); *i; ++i)
|
|
|
|
{
|
|
|
|
if ((*i)->id() == id) return *i;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Base::loadMetaXML(const TQString &xml)
|
|
|
|
{
|
|
|
|
d->slices.setAutoDelete(true);
|
|
|
|
d->slices.clear();
|
|
|
|
d->slices.setAutoDelete(false);
|
|
|
|
|
|
|
|
TQDomDocument doc;
|
|
|
|
doc.setContent(xml);
|
|
|
|
TQDomElement doce = doc.documentElement();
|
|
|
|
bool loadedId0=false;
|
|
|
|
|
|
|
|
for (TQDomNode n = doce.firstChild(); !n.isNull(); n = n.nextSibling())
|
|
|
|
{
|
|
|
|
TQDomElement e = n.toElement();
|
|
|
|
if (e.isNull()) continue;
|
|
|
|
|
|
|
|
if (e.tagName().lower() == "slices")
|
|
|
|
{
|
|
|
|
d->sliceHigh = e.attribute("highslice", "1").toInt();
|
|
|
|
for (TQDomNode n = e.firstChild(); !n.isNull(); n = n.nextSibling())
|
|
|
|
{
|
|
|
|
TQDomElement e = n.toElement();
|
|
|
|
if (e.isNull()) continue;
|
|
|
|
if (e.tagName().lower() == "slice")
|
|
|
|
{
|
|
|
|
int id = e.attribute("id").toInt();
|
|
|
|
if (id==0 && loadedId0) break;
|
|
|
|
loadedId0=true;
|
|
|
|
TQString name = e.attribute("name");
|
|
|
|
d->slices.append(new Slice(this, id, name));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (d->slices.count() == 0)
|
|
|
|
{
|
|
|
|
// we must have a default
|
|
|
|
d->slices.append(new Slice(this, 0, ""));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#include "base.moc"
|