Saturday, 28 February 2009

Receiving files

Gadu protocol can now receive files (again) - in Dcc6 mode ;) It now uses FileTransferService to do that. Now it needs support for incoming transfers from Gadu Gadu 7 and newer and support for outcoming transfers; file_transfer modue will get all functionality of old dcc module (like asking for accepting incoming connection) and everybody will be happy now.

I've also found (and fixed) some fundamental mistakes in whole SocketNotifiers code. I hope now file transfers will be more reliable (eg: will work 100% of the time).

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())
return;

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())
return;

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 =
FileTransfer::loadFromStorage(fileTransferStoragePoint);

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());
gft->setStorage(fileTransferStoragePoint);
gft->loadConfiguration();
Done.

Friday, 20 February 2009

Dcc Porting

I'm in hell now. I've got working ChatService, ChatImagerService, PersonalInfoService, ContactListService and almost-working SerachService... But the FileTransferService need so much porting of DCC module, that I'm starting to cry... The worst part is that I've rewritten it before.

The good news is that White Eagle is writing Adium chat styles support for Kadu ;)

Thursday, 12 February 2009

ULEs removed

I've finally managed to remove last uses of UserListElement, UserListElements, UserGroup, UserList in Kadu (not including modules, but it is work for post-alpha releases).

Uzi is working on new ContactDataWindow (for editing contacts). After that we can restore contact management functionality - it is hard to use IM without that :(

I'll work on removing UserStatus and renaming Status to Presence. After that - rest of Gadu-Gadu protocol refactorisations (FileTransferService, VoiceService, SearchService...).

Protocol services

Some more thoughts about refactorisation of Protocol and GaduProtocol classes. When I was extracting contact list export-import functionality from GaduProtocol, I've got an excellent idea about how-to-split the whole big class into smaller ones. It is very simple and widelly used in other projects (so why did't I thought about it before?).

We will have services: ContactListService, ChatService, FileTransferService, VoiceService and many others. Each of these will be an abstract class (call it an interface). Protocol class will implement methods like ChatService * chatService() - it will return 0 (no service implementation) by default. GaduProtocol for example will return GaduChatService object created in GaduProtocol's contructor.

Advantages? Smaller classes, possibility to query protocols for capabilities (if chatService() == 0 we cannot chat by this protocol, fileTransferService() == 0 - we cannot send/receive files).

Tuesday, 10 February 2009

Sometimes a function can became a class...

...and it happens after the function became too big (or complicated). Like account-management functions for GaduProtocol (registerAccount, unregisterAccount, remindPassword, changePassword). Each of these functions used getToken function and a Mode field of GaduProtocol object, so the getToken could choose right function to call after its work was done (it works asynchronically). And each of these has own slot!

Why I don't like this solution? Without true reason it adds a new state field to GaduProtocol class - Mode (Register, Unregister, RemindPassword, ChangePassword). It makes GaduProtocol do things that are not really involved with normal IM communication - account management. It uses copy-and-pasted code, becouse these methods are nearly identical.

So I've decided to promote each of this funtion to a class of its own. With, of course, one base class: GaduServerConnector (ok, the name suck, it will be changed when I found a better one). This class does most of the work: it connects to server, fetches token (captcha) image, asks user for token value (using external object for that, so method of asking can be changed) and calls abstract method performAction(tokenId, tokenValue). Classes like GaduServerRegisterAccount (this name sucks too) only implement performAction method (and should call finished(bool), so GaduServerConnector can emit a signal for higher level code).

For example (there is only one right now) GaduServerRegisterAccount implements performAction using gg_register3 libgadu function. It wraps the connection in GaduPubdirSocketNotifiers and connects to its signal: done(bool, gg_http *). So after registration if over, it receives that signal and sets its Uin field. The caller can then read GaduServerConnector::Result field (so it knows if it was successfully) and the GaduPubdirSocketNotifiers::Uin (so it knows id of the newly registered account). Voila.

It makes GaduProtocol smaller and the flow of Register/Unregister/RemindPassword/ChangePassword a lot simples (I hope so...).

BTW: it still don't work, it segfaults every time. But it is just a technical problem, not design one.

UPDATE: I've fixed the crash. Registration does not work because (as I think) we are no longer compatible with GG protocol. I'll fix that later.

Is there somebody who wants to promote last 3 functions to objects?

UPDATE 2: I've found error with registration - it was just sending 2x password instead of password and e-mail ;)

Tuesday, 3 February 2009

Time for GaduProtocol

Unfortunately it is impossible to do full ULE to ContactList transition without making some changes in Gadu-Gadu protocol implementation. Code responsible for exporting/importing list of contacts depends on old classes. So I had a look into gadu_protocol module and...

And I started refactoring it a bit. I know I've done it about year ago, but now I see that it was not well done: files and classes are too large. Some method also. Yesterday three classes were moved to their own files. Next step require removing all references to old UserStatus (and GaduUserStatus classes) and replacing it with the brand-new Status class.

But what was wrong with UserStatus? It does tasks that only Protocol class should have: it has slots, signals (for informing it was changed). Both protocol classes and Kadu main class conencted to it. The code flow:
  • Kadu: protocol->writeableStatus().setStatus(newStatus)
  • Status: emit goOnline("description")
  • Protocol: slot iWantGoOnline("description")
  • Protocol: communication with server
  • Protocol: CurrentStatus->setStatus(newStatus)
  • CurrentStatus emit goOnline("description")
  • Kadu: wentOnline("description")
Many unnecessary slots/signals. The right way to do it:
  • Kadu: protocol->setStatus(newStatus)
  • Protocol: communication with server
  • Protocol: emit statusChanged(newStatus)
We need the statusChanged signal, because the status setting can be asynchronous.

So the Status class is now very lightweight and does just what it should do - stores 2 pieces of data. Protocol and Kadu classes lost lot of signals and slots. And logic is much simpler ;)

After I sort out the Status class I'll look if splitting GaduProtocol into smaller classes (like GaduConnection) makes sense - I just like small classes ;)