You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tdepim/akregator/src/mk4storage/metakit/src/store.cpp

588 lines
14 KiB

// store.cpp --
// $Id$
// This is part of Metakit, the homepage is http://www.equi4.com/metakit/
/** @file
* Storage management and several other loose ends
*/
#include "header.h"
#include "handler.h" // 19990906
#include "store.h"
#include "field.h"
#include "persist.h"
#include "format.h" // 19990906
#include "mk4io.h" // 19991104
#if !q4_INLINE
#include "store.inl"
#endif
/////////////////////////////////////////////////////////////////////////////
c4_Dependencies::c4_Dependencies ()
{
_refs.SetSize(0, 3); // a little optimization
}
c4_Dependencies::~c4_Dependencies ()
{
}
void c4_Dependencies::Add(c4_Sequence* seq_)
{
for (int i = 0; i < _refs.GetSize(); ++i)
d4_assert(_refs.GetAt(i) != seq_);
_refs.Add(seq_);
}
bool c4_Dependencies::Remove(c4_Sequence* seq_)
{
int n = _refs.GetSize() - 1;
d4_assert(n >= 0);
for (int i = 0; i <= n; ++i)
if (_refs.GetAt(i) == seq_) {
_refs.SetAt(i, _refs.GetAt(n));
_refs.SetSize(n);
return n > 0;
}
d4_assert(0); // dependency not found
return true;
}
/////////////////////////////////////////////////////////////////////////////
c4_Notifier::~c4_Notifier ()
{
if (_type > kNone && _origin->GetDependencies()) {
c4_PtrArray& refs = _origin->GetDependencies()->_refs;
for (int i = 0; i < refs.GetSize(); ++i) {
c4_Sequence* seq = (c4_Sequence*) refs.GetAt(i);
d4_assert(seq != 0);
seq->PostChange(*this);
if (_chain && _chain->_origin == seq) {
c4_Notifier* next = _chain->_next;
_chain->_next = 0;
delete _chain;
_chain = next;
}
}
}
d4_assert(!_chain);
d4_assert(!_next);
}
void c4_Notifier::StartSetAt(int index_, c4_Cursor& cursor_)
{
_type = kSetAt;
_index = index_;
_cursor = &cursor_;
Notify();
}
void c4_Notifier::StartInsertAt(int i_, c4_Cursor& cursor_, int n_)
{
_type = kInsertAt;
_index = i_;
_cursor = &cursor_;
_count = n_;
Notify();
}
void c4_Notifier::StartRemoveAt(int index_, int count_)
{
_type = kRemoveAt;
_index = index_;
_count = count_;
Notify();
}
void c4_Notifier::StartMove(int from_, int to_)
{
_type = kMove;
_index = from_;
_count = to_;
Notify();
}
void c4_Notifier::StartSet(int i_, int propId_, const c4_Bytes& buf_)
{
_type = kSet;
_index = i_;
_propId = propId_;
_bytes = &buf_;
Notify();
}
void c4_Notifier::Notify()
{
d4_assert(_origin->GetDependencies() != 0);
c4_PtrArray& refs = _origin->GetDependencies()->_refs;
int n = refs.GetSize();
d4_assert(n > 0);
c4_Notifier** rover = &_chain;
for (int i = 0; i < n; ++i) {
c4_Sequence* seq = (c4_Sequence*) refs.GetAt(i);
d4_assert(seq != 0);
c4_Notifier* ptr = seq->PreChange(*this);
if (ptr) {
d4_assert(ptr->_origin == seq);
d4_assert(!*rover);
*rover = ptr;
rover = &ptr->_next;
}
}
}
/////////////////////////////////////////////////////////////////////////////
/** @class c4_Storage
*
* Manager for persistent storage of view structures.
*
* The storage class uses a view, with additional functionality to be able
* to store and reload the data it contains (including nested subviews).
*
* By default, data is loaded on demand, i.e. whenever data which has
* not yet been referenced is used for the first time. Loading is limited
* to the lifetime of this storage object, since the storage object carries
* the file descriptor with it that is needed to access the data file.
*
* To save changes, call the Commit member. This is the only time
* that data is written to file - when using a read-only file simply avoid
* calling Commit.
*
* The LoadFromStream and SaveToStream members can be used to
* serialize the contents of this storage row using only sequential I/O
* (no seeking, only read or write calls).
*
* The data storage mechanism implementation provides fail-safe operation:
* if anything prevents Commit from completing its task, the last
* succesfully committed version of the saved data will be recovered on
* the next open. This also includes changes made to the table structure.
*
* The following code creates a view with 1 row and stores it on file:
* @code
* c4_StringProp pName ("Name");
* c4_IntProp pAge ("Age");
*
* c4_Storage storage ("myfile.dat", true);
* c4_View myView = storage.GetAs("Musicians[Name:S,Age:I]");
*
* myView.Add(pName ["John Williams"] + pAge [43]);
*
* storage.Commit();
* @endcode
*/
c4_Storage::c4_Storage ()
{
// changed to r/o, now that commits don't crash on it anymore
Initialize(*d4_new c4_Strategy, true, 0);
}
c4_Storage::c4_Storage (c4_Strategy& strategy_, bool owned_, int mode_)
{
Initialize(strategy_, owned_, mode_);
Persist()->LoadAll();
}
c4_Storage::c4_Storage (const char* fname_, int mode_)
{
c4_FileStrategy* strat = d4_new c4_FileStrategy;
strat->DataOpen(fname_, mode_);
Initialize(*strat, true, mode_);
if (strat->IsValid())
Persist()->LoadAll();
}
c4_Storage::c4_Storage (const c4_View& root_)
{
if (root_.Persist() != 0) // only restore if view was indeed persistent
*(c4_View*) this = root_;
else // if this was not possible, start with a fresh empty storage
Initialize(*d4_new c4_Strategy, true, 0);
}
c4_Storage::~c4_Storage ()
{
// cannot unmap here, because there may still be an autocommit pending
//((c4_HandlerSeq*) _seq)->UnmapAll();
}
void c4_Storage::Initialize(c4_Strategy& strategy_, bool owned_, int mode_)
{
c4_Persist* pers = d4_new c4_Persist (strategy_, owned_, mode_);
c4_HandlerSeq* seq = d4_new c4_HandlerSeq (pers);
seq->DefineRoot();
*(c4_View*) this = seq;
pers->SetRoot(seq);
}
/// Get or set a named view in this storage object
c4_ViewRef c4_Storage::View(const char* name_)
{
/*
The easy solution would seem to be:
c4_ViewProp prop (name_);
return prop (Contents());
But this does not work, because the return value would point to
an object allocated on the stack.
Instead, make sure the view *has* such a property, and use the
one inside the c4_Handler for it (since this will stay around).
*/
// int n = _root->PropIndex(c4_ViewProp (name_));
c4_ViewProp prop (name_);
int n = AddProperty(prop);
d4_assert(n >= 0);
// the following is an expression of the form "property (rowref)"
return NthProperty(n) (GetAt(0));
}
/// Get a named view, redefining it to match the given structure
c4_View c4_Storage::GetAs(const char* description_)
{
d4_assert(description_ != 0);
// Dec 2001: now that GetAs is being used so much more frequently,
// add a quick check to see whether restructuring is needed at all
const char* q = strchr(description_, '[');
if (q != 0) {
c4_String vname (description_, q - description_);
const char* d = Description(vname);
if (d != 0) {
c4_String desc (d);
if (("[" + desc + "]").CompareNoCase(q) == 0)
return View(vname);
}
}
c4_Field* field = d4_new c4_Field (description_);
d4_assert(field != 0);
d4_assert(!*description_);
c4_String name = field->Name();
d4_assert(!name.IsEmpty());
c4_Field& curr = Persist()->Root().Definition();
c4_String newField = "," + field->Description();
bool keep = newField.Find('[') >= 0;
c4_String newDef;
// go through all subfields
for (int i = 0; i < curr.NumSubFields(); ++i) {
c4_Field& of = curr.SubField(i);
if (of.Name().CompareNoCase(name) == 0) {
if (field->IsRepeating())
newDef += newField;
// else new is not a repeating entry, so drop this entire field
newField.Empty(); // don't append it later on
continue;
}
newDef += "," + of.Description(); // keep original field
}
if (keep) // added 19990824 ignore if deletion
newDef += newField; // appends new definition if not found earlier
delete field;
const char* p = newDef;
SetStructure(*p ? ++p : p); // skip the leading comma
if (!keep) // 19990916: avoid adding an empty view again
return c4_View ();
return View(name);
}
/// Define the complete view structure of the storage
void c4_Storage::SetStructure(const char* description_)
{
d4_assert(description_ != 0);
if (description_ != Description()) {
c4_String s = "[" + c4_String (description_) + "]";
description_ = s;
c4_Field* field = d4_new c4_Field (description_);
d4_assert(!*description_);
d4_assert(field != 0);
Persist()->Root().Restructure(*field, false);
}
}
/// Return the strategy object associated with this storage
c4_Strategy& c4_Storage::Strategy() const
{
return Persist()->Strategy();
}
/// Return a description of the view structure (default is all)
const char* c4_Storage::Description(const char* name_)
{
if (name_ == 0 || *name_ == 0)
return c4_View::Description();
c4_View v = View(name_);
return v.Description();
}
/// Define the storage to use for differential commits
bool c4_Storage::SetAside(c4_Storage& aside_)
{
c4_Persist* pers = Persist();
bool f = pers->SetAside(aside_);
// adjust our copy when the root view has been replaced
*(c4_View*) this = &pers->Root();
return f;
}
/// Return storage used for differential commits, or null
c4_Storage* c4_Storage::GetAside() const
{
return Persist()->GetAside();
}
/// Flush pending changes to file right now
bool c4_Storage::Commit(bool full_)
{
return Strategy().IsValid() && Persist()->Commit(full_);
}
/** (Re)initialize for on-demand loading
*
* Calling Rollback will cancel all uncommitted changes.
*/
bool c4_Storage::Rollback(bool full_)
{
c4_Persist* pers = Persist();
bool f = Strategy().IsValid() && pers->Rollback(full_);
// adjust our copy when the root view has been replaced
*(c4_View*) this = &pers->Root();
return f;
}
/// Set storage up to always call Commit in the destructor
bool c4_Storage::AutoCommit(bool flag_)
{
return Persist()->AutoCommit(flag_);
}
/// Load contents from the specified input stream
bool c4_Storage::LoadFrom(c4_Stream& stream_)
{
c4_HandlerSeq* newRoot = c4_Persist::Load(&stream_);
if (newRoot == 0)
return false;
// fix commit-after-load bug, by using a full view copy
// this is inefficient, but avoids mapping/strategy problems
c4_View temp (newRoot);
SetSize(0);
SetStructure(temp.Description());
InsertAt(0, temp);
return true;
}
/// Save contents to the specified output stream
void c4_Storage::SaveTo(c4_Stream& stream_)
{
c4_Persist::Save(&stream_, Persist()->Root());
}
/////////////////////////////////////////////////////////////////////////////
c4_DerivedSeq::c4_DerivedSeq (c4_Sequence& seq_)
: _seq (seq_)
{
_seq.Attach(this);
}
c4_DerivedSeq::~c4_DerivedSeq ()
{
_seq.Detach(this);
}
int c4_DerivedSeq::RemapIndex(int index_, const c4_Sequence* seq_) const
{
return seq_ == this ? index_ : _seq.RemapIndex(index_, seq_);
}
int c4_DerivedSeq::NumRows() const
{
return _seq.NumRows();
}
int c4_DerivedSeq::NumHandlers() const
{
return _seq.NumHandlers();
}
c4_Handler& c4_DerivedSeq::NthHandler(int colNum_) const
{
return _seq.NthHandler(colNum_);
}
const c4_Sequence* c4_DerivedSeq::HandlerContext(int colNum_) const
{
return _seq.HandlerContext(colNum_);
}
int c4_DerivedSeq::AddHandler(c4_Handler* handler_)
{
return _seq.AddHandler(handler_);
}
c4_Handler* c4_DerivedSeq::CreateHandler(const c4_Property& prop_)
{
return _seq.CreateHandler(prop_);
}
void c4_DerivedSeq::SetNumRows(int size_)
{
_seq.SetNumRows(size_);
}
c4_Notifier* c4_DerivedSeq::PreChange(c4_Notifier& nf_)
{
if (!GetDependencies())
return 0;
c4_Notifier* chg = d4_new c4_Notifier (this);
switch (nf_._type)
{
case c4_Notifier::kSetAt:
chg->StartSetAt(nf_._index, *nf_._cursor);
break;
case c4_Notifier::kSet:
chg->StartSet(nf_._index, nf_._propId, *nf_._bytes);
break;
case c4_Notifier::kInsertAt:
chg->StartInsertAt(nf_._index, *nf_._cursor, nf_._count);
break;
case c4_Notifier::kRemoveAt:
chg->StartRemoveAt(nf_._index, nf_._count);
break;
case c4_Notifier::kMove:
chg->StartMove(nf_._index, nf_._count);
break;
}
return chg;
}
/////////////////////////////////////////////////////////////////////////////
c4_StreamStrategy::c4_StreamStrategy (t4_i32 buflen_)
: _stream (0), _buffer (d4_new t4_byte [buflen_]), _buflen (buflen_), _position (0)
{
_mapStart = _buffer;
_dataSize = buflen_;
}
c4_StreamStrategy::c4_StreamStrategy (c4_Stream* stream_)
: _stream (stream_), _buffer (0), _buflen (0), _position (0)
{
}
c4_StreamStrategy::~c4_StreamStrategy ()
{
_mapStart = 0;
_dataSize = 0;
if (_buffer != 0)
delete [] _buffer;
}
bool c4_StreamStrategy::IsValid() const
{
return true;
}
int c4_StreamStrategy::DataRead(t4_i32 pos_, void* buffer_, int length_)
{
if (_buffer != 0) {
d4_assert(pos_ <= _buflen);
_position = pos_ + _baseOffset;
if (length_ > _buflen - _position)
length_ = _buflen - _position;
if (length_ > 0)
memcpy(buffer_, _buffer + _position, length_);
} else {
d4_assert(_position == pos_ + _baseOffset);
length_ = _stream != 0 ? _stream->Read(buffer_, length_) : 0;
}
_position += length_;
return length_;
}
void c4_StreamStrategy::DataWrite(t4_i32 pos_, const void* buffer_, int length_)
{
if (_buffer != 0) {
d4_assert(pos_ <= _buflen);
_position = pos_ + _baseOffset;
int n = length_;
if (n > _buflen - _position)
n = _buflen - _position;
if (n > 0)
memcpy(_buffer + _position, buffer_, n);
} else {
d4_assert(_position == pos_ + _baseOffset);
if (_stream != 0 && !_stream->Write(buffer_, length_))
++_failure;
}
_position += length_;
}
t4_i32 c4_StreamStrategy::FileSize()
{
return _position;
}
/////////////////////////////////////////////////////////////////////////////