Monday, 23 February 2009

New (alternative) configuration API

When I was updating Contacts API, then Accounts API and some more Kadu APIs ;) I've realized that current configuration API needs too much code to use. You basically have to use QXml to do anything more complicated that write one simple value (config_file.writeEntry() is not always good enough).

Contacts, Accounts, File Transfers are complicated structures that needs simple API to save/restore (some kind of serialization). This API is now provided by StorableObject class (I think I'll rename it to Persistent or PersistentObject in API-review time). Class that need to be stored (persistent) must inherit from StorableObject. It only requires reimplementation of one method: virtual StoragePoint * createStoragePoint() const, where StoragePoint class contains pointer to XmlConfigFile and QDomElement that is root node for storing this object's configuration.

For example, ContactData class implements it as:
StoragePoint *parent = ContactManager::instance()->storage();
if (!parent)
return 0;

QDomElement contactNode = parent->storage()->getUuidNode(parent->point(),
"Contact", Uuid.toString());
return new StoragePoint(parent->storage(), contactNode);
So we get new node under main ContactManager configuration tagged with the contact's Uuid. The storage() method is inherited and implemented in StorableObject class - it calls createStoragePoint when needed.

Implementation of createStoragePoint in ContactManager:
QDomElement contactsNewNode = xml_config_file->getNode("ContactsNew");
return new StoragePoint(xml_config_file, contactsNewNode);
It creates ContactsNew node under root node of main configuration file.

StorableObject supports external setting of StoragePoint - see setStorage() method.

When we have all of that nice stuff, we can store object. For example, FileTransfer storage looks like that:
void FileTransfer::storeConfiguration()
if (!isValidStorage())

storeValue("Account", CurrentAccount->uuid().toString());
storeValue("Peer", Peer.uuid().toString());
storeValue("LocalFileName", LocalFileName);
storeValue("RemoteFileName", RemoteFileName);
storeValue("TransferType", TypeSend == TransferType ? "Send" : "Receive");
storeValue("FileSize", (qulonglong)FileSize);
storeValue("TransferredSize", (qulonglong)TransferredSize);
Condition !isValidStorage() check if storage point exist, if not, it creates it (with all the needed hierarchy). It if fails, it returns false - we cannot do anything here about it. After that we store all values needed to recreate object in future. storeValue() method of StorableObject class writes data to subnodes of StoragePoint of this object (BTW: this method is not completed yet, it does not store objects Uuid, and I think that should be done be StorableObject or StorableObjectWithUuid class).

Loading object is possible when setStorage() method was called from somewhere (object does not know anything about its StoragePoint before loading). Then we could do:
void FileTransfer::loadConfiguration()
if (!isValidStorage())

CurrentAccount = AccountManager::instance()->byUuid(QUuid(loadValue<QString>("Account")));
Peer = ContactManager::instance()->byUuid(QUuid(loadValue<QString>("Peer")));
LocalFileName = loadValue<QString>("LocalFileName");
RemoteFileName = loadValue<QString>("RemoteFileName");
TransferType = ("Send" == loadValue<QString>("TransferType")) ? TypeSend : TypeReceive;
FileSize = loadValue<qulonglong>("FileSize");
TransferredSize = loadValue<qulonglong>("TransferredSize");
Everyting is as simple as possible.

And who sets the StoragePoint? FileTransferManager:
StoragePoint *fileTransferStoragePoint =
new StoragePoint(configurationStorage, fileTransferElement);
FileTransfer *fileTransfer =

The static method loadFromStorage does some magic to determine which Account this FileTransfer belongs to. Then it creates empty FileTransfer for this Account and calls something like this (for Gadu):
GaduFileTransfer *gft = new GaduFileTransfer(Protocol->account());

No comments:

Post a Comment