Commit 840e8c16 authored by Shashwat Jolly's avatar Shashwat Jolly
Browse files

Fix memory leaks

Implemented std::unique_ptr with custom deleter class for all raw pointers returned from the adapter
parent 29469586
......@@ -37,12 +37,13 @@ void EntriesFetchJob::start()
void EntriesFetchJob::fetchEntries()
{
EteSyncEntryManager *entryManager = etesync_entry_manager_new(mClient, mJournalUid);
mEntries = etesync_entry_manager_list(entryManager, mPrevUid, 0);
EteSyncEntryManagerPtr entryManager(etesync_entry_manager_new(mClient, mJournalUid));
mEntries = etesync_entry_manager_list(entryManager.get(), mPrevUid, 0);
if (!mEntries) {
setError(UserDefinedError);
setErrorText(QStringLiteral("EntriesFetchJob failed to fetch entries"));
CharPtr err(etesync_get_error_message());
setErrorText(QStringFromCharPtr(err));
}
emitResult();
......
......@@ -15,38 +15,30 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include <etesyncadapter.h>
#include "etesyncadapter.h"
QString QStringFromCharArr(const char *str)
#include "etesync_debug.h"
QString QStringFromCharPtr(const CharPtr &str)
{
if (str == NULL) {
if (str.get() == NULL) {
return QString();
}
return QLatin1String(str);
}
const char *charArrFromQString(const QString &str)
{
if (str.isNull()) return NULL;
QByteArray ba = str.toLocal8Bit();
const char *ret = qstrdup(ba.data());
QString ret = QString::fromLatin1(str.get());
return ret;
}
QString etesync_auth_get_token(const EteSync *etesync, const QString &username,
const QString &password)
{
const char *uname = charArrFromQString(username);
const char *pass = charArrFromQString(password);
char *token = etesync_auth_get_token(etesync, uname, pass);
return QStringFromCharArr(token);
CharPtr token(etesync_auth_get_token(etesync, charArrFromQString(username), charArrFromQString(password)));
return QStringFromCharPtr(token);
}
qint32 etesync_auth_invalidate_token(const EteSync *etesync,
const QString &token)
{
const char *tok = charArrFromQString(token);
return etesync_auth_invalidate_token(etesync, tok);
return etesync_auth_invalidate_token(etesync, charArrFromQString(token));
}
EteSyncCollectionInfo *etesync_collection_info_new(const QString &col_type,
......@@ -54,111 +46,91 @@ EteSyncCollectionInfo *etesync_collection_info_new(const QString &col_type,
const QString &description,
qint32 color)
{
const char *colType = charArrFromQString(col_type);
const char *displayName = charArrFromQString(display_name);
const char *desc = charArrFromQString(description);
return etesync_collection_info_new(colType, displayName, desc, color);
return etesync_collection_info_new(charArrFromQString(col_type), charArrFromQString(display_name), charArrFromQString(description), color);
}
QString etesync_crypto_derive_key(const EteSync *_etesync, const QString &salt,
QString etesync_crypto_derive_key(const EteSync *etesync, const QString &salt,
const QString &password)
{
const char *s = charArrFromQString(salt);
const char *pass = charArrFromQString(password);
char *ret = etesync_crypto_derive_key(_etesync, s, pass);
return QStringFromCharArr(ret);
CharPtr ret(etesync_crypto_derive_key(etesync, charArrFromQString(salt), charArrFromQString(password)));
return QStringFromCharPtr(ret);
}
EteSyncEntry *etesync_entry_from_sync_entry(const EteSyncCryptoManager *crypto_manager,
const EteSyncSyncEntry *sync_entry,
const QString &prev_uid)
{
const char *prevUid = charArrFromQString(prev_uid);
return etesync_entry_from_sync_entry(crypto_manager, sync_entry, prevUid);
return etesync_entry_from_sync_entry(crypto_manager, sync_entry, charArrFromQString(prev_uid));
}
EteSyncSyncEntry *etesync_entry_get_sync_entry(const EteSyncEntry *entry,
const EteSyncCryptoManager *crypto_manager,
const QString &prev_uid)
{
const char *prevUid = charArrFromQString(prev_uid);
return etesync_entry_get_sync_entry(entry, crypto_manager, prevUid);
return etesync_entry_get_sync_entry(entry, crypto_manager, charArrFromQString(prev_uid));
}
qint32 etesync_entry_manager_create(const EteSyncEntryManager *entry_manager,
const EteSyncEntry *const *entries,
const QString &prev_uid)
{
const char *prevUid = charArrFromQString(prev_uid);
return etesync_entry_manager_create(entry_manager, entries, prevUid);
return etesync_entry_manager_create(entry_manager, entries, charArrFromQString(prev_uid));
}
EteSyncEntry **etesync_entry_manager_list(const EteSyncEntryManager *entry_manager,
const QString &prev_uid, uintptr_t limit)
{
const char *prevUid = charArrFromQString(prev_uid);
return etesync_entry_manager_list(entry_manager, prevUid, limit);
return etesync_entry_manager_list(entry_manager, charArrFromQString(prev_uid), limit);
}
EteSyncEntryManager *etesync_entry_manager_new(const EteSync *etesync,
const QString &journal_uid)
{
const char *journalUid = charArrFromQString(journal_uid);
return etesync_entry_manager_new(etesync, journalUid);
return etesync_entry_manager_new(etesync, charArrFromQString(journal_uid));
}
EteSyncCryptoManager *etesync_journal_get_crypto_manager(const EteSyncJournal *journal,
const QString &key,
const EteSyncAsymmetricKeyPair *keypair)
{
const char *k = charArrFromQString(key);
return etesync_journal_get_crypto_manager(journal, k, keypair);
return etesync_journal_get_crypto_manager(journal, charArrFromQString(key), keypair);
}
EteSyncJournal *etesync_journal_manager_fetch(const EteSyncJournalManager *journal_manager,
const QString &journal_uid)
{
const char *journalUid = charArrFromQString(journal_uid);
return etesync_journal_manager_fetch(journal_manager, journalUid);
return etesync_journal_manager_fetch(journal_manager, charArrFromQString(journal_uid));
}
EteSyncJournal *etesync_journal_new(const QString &uid, uint8_t version)
{
const char *id = charArrFromQString(uid);
return etesync_journal_new(id, version);
return etesync_journal_new(charArrFromQString(uid), version);
}
EteSync *etesync_new(const QString &client_name, const QString &server_url)
{
const char *clientName = charArrFromQString(client_name);
const char *serverUrl = charArrFromQString(server_url);
return etesync_new(clientName, serverUrl);
return etesync_new(charArrFromQString(client_name), charArrFromQString(server_url));
}
void etesync_set_auth_token(EteSync *etesync, const QString &token)
{
const char *tok = charArrFromQString(token);
etesync_set_auth_token(etesync, tok);
etesync_set_auth_token(etesync, charArrFromQString(token));
}
EteSyncSyncEntry *etesync_sync_entry_new(const QString &action,
const QString &content)
{
const char *act = charArrFromQString(action);
const char *cont = charArrFromQString(content);
return etesync_sync_entry_new(act, cont);
return etesync_sync_entry_new(charArrFromQString(action), charArrFromQString(content));
}
EteSyncCryptoManager *etesync_user_info_get_crypto_manager(const EteSyncUserInfo *user_info,
const QString &key)
{
const char *k = charArrFromQString(key);
return etesync_user_info_get_crypto_manager(user_info, k);
return etesync_user_info_get_crypto_manager(user_info, charArrFromQString(key));
}
EteSyncUserInfo *etesync_user_info_manager_fetch(const EteSyncUserInfoManager *user_info_manager,
const QString &owner)
{
const char *own = charArrFromQString(owner);
return etesync_user_info_manager_fetch(user_info_manager, own);
return etesync_user_info_manager_fetch(user_info_manager, charArrFromQString(owner));
}
......@@ -21,10 +21,76 @@
#include <etesync/etesync.h>
#include <QString>
QString QStringFromCharArr(const char *str);
const char *charArrFromQString(const QString &str);
#include <memory>
#define charArrFromQString(str) str.isNull() ? NULL : qUtf8Printable(str)
struct EteSyncDeleter
{
void operator()(EteSync *ptr)
{
etesync_destroy(ptr);
}
void operator()(EteSyncJournalManager *ptr)
{
etesync_journal_manager_destroy(ptr);
}
void operator()(EteSyncAsymmetricKeyPair *ptr)
{
etesync_keypair_destroy(ptr);
}
void operator()(EteSyncJournal *ptr)
{
etesync_journal_destroy(ptr);
}
void operator()(EteSyncEntry *ptr)
{
etesync_entry_destroy(ptr);
}
void operator()(EteSyncSyncEntry *ptr)
{
etesync_sync_entry_destroy(ptr);
}
void operator()(EteSyncCryptoManager *ptr)
{
etesync_crypto_manager_destroy(ptr);
}
void operator()(EteSyncUserInfoManager *ptr)
{
etesync_user_info_manager_destroy(ptr);
}
void operator()(EteSyncUserInfo *ptr)
{
etesync_user_info_destroy(ptr);
}
void operator()(EteSyncCollectionInfo *ptr)
{
etesync_collection_info_destroy(ptr);
}
void operator()(EteSyncEntryManager *ptr)
{
etesync_entry_manager_destroy(ptr);
}
void operator()(char *ptr)
{
std::free(ptr);
}
};
using EteSyncPtr = std::unique_ptr<EteSync, EteSyncDeleter>;
using EteSyncJournalManagerPtr = std::unique_ptr<EteSyncJournalManager, EteSyncDeleter>;
using EteSyncAsymmetricKeyPairPtr = std::unique_ptr<EteSyncAsymmetricKeyPair, EteSyncDeleter>;
using EteSyncJournalPtr = std::unique_ptr<EteSyncJournal, EteSyncDeleter>;
using EteSyncEntryPtr = std::unique_ptr<EteSyncEntry, EteSyncDeleter>;
using EteSyncSyncEntryPtr = std::unique_ptr<EteSyncSyncEntry, EteSyncDeleter>;
using EteSyncCryptoManagerPtr = std::unique_ptr<EteSyncCryptoManager, EteSyncDeleter>;
using EteSyncUserInfoManagerPtr = std::unique_ptr<EteSyncUserInfoManager, EteSyncDeleter>;
using EteSyncUserInfoPtr = std::unique_ptr<EteSyncUserInfo, EteSyncDeleter>;
using EteSyncCollectionInfoPtr = std::unique_ptr<EteSyncCollectionInfo, EteSyncDeleter>;
using EteSyncEntryManagerPtr = std::unique_ptr<EteSyncEntryManager, EteSyncDeleter>;
using CharPtr = std::unique_ptr<char, EteSyncDeleter>;
QString QStringFromCharPtr(const CharPtr &str);
QString etesync_auth_get_token(const EteSync *etesync, const QString &username,
const QString &password);
......
......@@ -31,7 +31,7 @@
using namespace EteSyncAPI;
using namespace Akonadi;
etesyncResource::etesyncResource(const QString &id)
EteSyncResource::EteSyncResource(const QString &id)
: ResourceBase(id)
{
Settings::instance(KSharedConfig::openConfig());
......@@ -50,19 +50,18 @@ etesyncResource::etesyncResource(const QString &id)
password = Settings::self()->password();
encryptionPassword = Settings::self()->encryptionPassword();
connect(this, &Akonadi::AgentBase::reloadConfiguration, this, &etesyncResource::onReloadConfiguration);
connect(this, &Akonadi::AgentBase::reloadConfiguration, this, &EteSyncResource::onReloadConfiguration);
qCDebug(ETESYNC_LOG) << "Resource started";
}
etesyncResource::~etesyncResource()
EteSyncResource::~EteSyncResource()
{
etesync_destroy(etesync);
}
void etesyncResource::retrieveCollections()
void EteSyncResource::retrieveCollections()
{
auto job = new JournalsFetchJob(etesync, this);
auto job = new JournalsFetchJob(mClient.get(), this);
connect(job, &JournalsFetchJob::finished, this, [this](KJob *job) {
if (job->error()) {
qCWarning(ETESYNC_LOG) << "Error in fetching journals";
......@@ -72,16 +71,14 @@ void etesyncResource::retrieveCollections()
EteSyncJournal **journals = qobject_cast<JournalsFetchJob *>(job)->journals();
Collection::List list;
for (EteSyncJournal **iter = journals; *iter; iter++) {
EteSyncJournal *journal = *iter;
EteSyncJournalPtr journal(*iter);
Collection collection;
/// TODO: Remove temporary hack to handle only addressbooks
if (setupCollection(collection, journal)) continue;
if (setupCollection(collection, journal.get())) continue;
list << collection;
etesync_journal_destroy(journal);
}
free(journals);
collectionsRetrieved(list);
......@@ -90,14 +87,14 @@ void etesyncResource::retrieveCollections()
}
/// TODO: Remove temporary hack, make this void
int etesyncResource::setupCollection(Collection &collection, EteSyncJournal *journal)
int EteSyncResource::setupCollection(Collection &collection, EteSyncJournal *journal)
{
int typeNum;
EteSyncCryptoManager *cryptoManager = etesync_journal_get_crypto_manager(journal, derived, keypair);
EteSyncCryptoManagerPtr cryptoManager(etesync_journal_get_crypto_manager(journal, derived, keypair.get()));
EteSyncCollectionInfo *info = etesync_journal_get_info(journal, cryptoManager);
EteSyncCollectionInfoPtr info(etesync_journal_get_info(journal, cryptoManager.get()));
const QString type = QStringFromCharArr(etesync_collection_info_get_type(info));
const QString type = QStringFromCharPtr(CharPtr(etesync_collection_info_get_type(info.get())));
QStringList mimeTypes;
......@@ -114,27 +111,27 @@ int etesyncResource::setupCollection(Collection &collection, EteSyncJournal *jou
qCWarning(ETESYNC_LOG) << "Unknown journal type. Cannot set collection mime type.";
}
const QString journalUid = QStringFromCharPtr(CharPtr(etesync_journal_get_uid(journal)));
const QString displayName = QStringFromCharPtr(CharPtr(etesync_collection_info_get_display_name(info.get())));
collection.setParentCollection(Collection::root());
collection.setRemoteId(QStringFromCharArr(etesync_journal_get_uid(journal)));
collection.setName(QStringFromCharArr(etesync_collection_info_get_display_name(info)));
collection.setRemoteId(journalUid);
collection.setName(displayName);
collection.setContentMimeTypes(mimeTypes);
etesync_collection_info_destroy(info);
etesync_crypto_manager_destroy(cryptoManager);
return typeNum;
}
void etesyncResource::retrieveItems(const Akonadi::Collection &collection)
void EteSyncResource::retrieveItems(const Akonadi::Collection &collection)
{
QString journalUid = collection.remoteId();
QString prevUid = collection.remoteRevision();
auto job = new EntriesFetchJob(etesync, journalUid, prevUid, this);
auto job = new EntriesFetchJob(mClient.get(), journalUid, prevUid, this);
connect(job, &EntriesFetchJob::finished, this, [this](KJob *job) {
if (job->error()) {
qCWarning(ETESYNC_LOG) << "Error in fetching entries";
qCWarning(ETESYNC_LOG) << job->errorText();
return;
}
......@@ -151,8 +148,8 @@ void etesyncResource::retrieveItems(const Akonadi::Collection &collection)
qCDebug(ETESYNC_LOG) << "Retrieving entries";
EteSyncEntry **entries = qobject_cast<EntriesFetchJob *>(job)->entries();
EteSyncJournal *journal = etesync_journal_manager_fetch(journalManager, journalUid);
EteSyncCryptoManager *cryptoManager = etesync_journal_get_crypto_manager(journal, derived, keypair);
EteSyncJournalPtr journal(etesync_journal_manager_fetch(journalManager.get(), journalUid));
EteSyncCryptoManagerPtr cryptoManager(etesync_journal_get_crypto_manager(journal.get(), derived, keypair.get()));
QMap<QString, KContacts::Addressee> contacts;
QMap<QString, QString> remoteIDs;
......@@ -161,20 +158,20 @@ void etesyncResource::retrieveItems(const Akonadi::Collection &collection)
Item::List removedItems;
for (EteSyncEntry **iter = entries; *iter; iter++) {
EteSyncEntry *entry = *iter;
EteSyncSyncEntry *syncEntry = etesync_entry_get_sync_entry(entry, cryptoManager, prevUid);
EteSyncEntryPtr entry(*iter);
EteSyncSyncEntryPtr syncEntry(etesync_entry_get_sync_entry(entry.get(), cryptoManager.get(), prevUid));
KContacts::VCardConverter converter;
const char *contentStr = etesync_sync_entry_get_content(syncEntry);
QByteArray content(contentStr);
CharPtr contentStr(etesync_sync_entry_get_content(syncEntry.get()));
QByteArray content(contentStr.get());
const KContacts::Addressee contact = converter.parseVCard(content);
const QString action = QStringFromCharArr(etesync_sync_entry_get_action(syncEntry));
const QString action = QStringFromCharPtr(CharPtr(etesync_sync_entry_get_action(syncEntry.get())));
// qCDebug(ETESYNC_LOG) << action;
// qCDebug(ETESYNC_LOG) << contact.uid();
if (action == QStringLiteral(ETESYNC_SYNC_ENTRY_ACTION_ADD) || action == QStringLiteral(ETESYNC_SYNC_ENTRY_ACTION_CHANGE)) {
contacts[contact.uid()] = contact;
remoteIDs[contact.uid()] = QStringFromCharArr(etesync_entry_get_uid(entry));
remoteIDs[contact.uid()] = QStringFromCharPtr(CharPtr(etesync_entry_get_uid(entry.get())));
} else if (action == QStringLiteral(ETESYNC_SYNC_ENTRY_ACTION_DELETE)) {
if (contacts.contains(contact.uid())) {
contacts.remove(contact.uid());
......@@ -188,11 +185,11 @@ void etesyncResource::retrieveItems(const Akonadi::Collection &collection)
}
}
prevUid = QStringFromCharArr(etesync_entry_get_uid(entry));
etesync_sync_entry_destroy(syncEntry);
etesync_entry_destroy(entry);
prevUid = QStringFromCharPtr(CharPtr(etesync_entry_get_uid(entry.get())));
}
free(entries);
collection.setRemoteRevision(prevUid);
new CollectionModifyJob(collection, this);
......@@ -214,7 +211,7 @@ void etesyncResource::retrieveItems(const Akonadi::Collection &collection)
job->start();
}
bool etesyncResource::retrieveItem(const Akonadi::Item &item,
bool EteSyncResource::retrieveItem(const Akonadi::Item &item,
const QSet<QByteArray> &parts)
{
// TODO: this method is called when Akonadi wants more data for a given
......@@ -224,13 +221,13 @@ bool etesyncResource::retrieveItem(const Akonadi::Item &item,
return true;
}
void etesyncResource::aboutToQuit()
void EteSyncResource::aboutToQuit()
{
// TODO: any cleanup you need to do while there is still an active
// event loop. The resource will terminate after this method returns
}
void etesyncResource::onReloadConfiguration()
void EteSyncResource::onReloadConfiguration()
{
Settings::self()->load();
serverUrl = Settings::self()->baseUrl();
......@@ -245,7 +242,7 @@ void etesyncResource::onReloadConfiguration()
qCDebug(ETESYNC_LOG) << "Resource config reload";
connect(this, &etesyncResource::initialiseDone, this, [this](bool successful) {
connect(this, &EteSyncResource::initialiseDone, this, [this](bool successful) {
qCDebug(ETESYNC_LOG) << "Resource intialised";
if (successful) synchronize();
});
......@@ -253,39 +250,36 @@ void etesyncResource::onReloadConfiguration()
initialise();
}
void etesyncResource::initialise()
void EteSyncResource::initialise()
{
// Initialise EteSync client state
etesync = etesync_new(QStringLiteral("Akonadi EteSync Resource"), serverUrl);
QString token = etesync_auth_get_token(etesync, username, password);
mClient = EteSyncPtr(etesync_new(QStringLiteral("Akonadi EteSync Resource"), serverUrl));
QString token = etesync_auth_get_token(mClient.get(), username, password);
if (token.isEmpty()) {
qCWarning(ETESYNC_LOG) << "Unable to obtain token from server";
Q_EMIT initialiseDone(false);
return;
}
etesync_set_auth_token(etesync, token);
journalManager = etesync_journal_manager_new(etesync);
derived = etesync_crypto_derive_key(etesync, username, encryptionPassword);
etesync_set_auth_token(mClient.get(), token);
journalManager = EteSyncJournalManagerPtr(etesync_journal_manager_new(mClient.get()));
derived = etesync_crypto_derive_key(mClient.get(), username, encryptionPassword);
// Get user keypair
EteSyncUserInfoManager *userInfoManager = etesync_user_info_manager_new(etesync);
EteSyncUserInfo *userInfo = etesync_user_info_manager_fetch(userInfoManager, username);
EteSyncUserInfoManagerPtr userInfoManager(etesync_user_info_manager_new(mClient.get()));
EteSyncUserInfoPtr userInfo(etesync_user_info_manager_fetch(userInfoManager.get(), username));
if (!userInfo) {
qCWarning(ETESYNC_LOG) << "User info obtained from server is NULL";
Q_EMIT initialiseDone(false);
return;
}
EteSyncCryptoManager *userInfoCryptoManager = etesync_user_info_get_crypto_manager(userInfo, derived);
EteSyncCryptoManagerPtr userInfoCryptoManager(etesync_user_info_get_crypto_manager(userInfo.get(), derived));
keypair = etesync_user_info_get_keypair(userInfo, userInfoCryptoManager);
etesync_crypto_manager_destroy(userInfoCryptoManager);
etesync_user_info_destroy(userInfo);
etesync_user_info_manager_destroy(userInfoManager);
keypair = EteSyncAsymmetricKeyPairPtr(etesync_user_info_get_keypair(userInfo.get(), userInfoCryptoManager.get()));
Q_EMIT initialiseDone(true);
}
void etesyncResource::itemAdded(const Akonadi::Item &item,
void EteSyncResource::itemAdded(const Akonadi::Item &item,
const Akonadi::Collection &collection)
{
// TODO: this method is called when somebody else, e.g. a client
......@@ -296,7 +290,7 @@ void etesyncResource::itemAdded(const Akonadi::Item &item,
// of this template code to keep it simple
}
void etesyncResource::itemChanged(const Akonadi::Item &item,
void EteSyncResource::itemChanged(const Akonadi::Item &item,
const QSet<QByteArray> &parts)
{
// TODO: this method is called when somebody else, e.g. a client
......@@ -306,7 +300,7 @@ void etesyncResource::itemChanged(const Akonadi::Item &item,
// of this template code to keep it simple
}
void etesyncResource::itemRemoved(const Akonadi::Item &item)
void EteSyncResource::itemRemoved(const Akonadi::Item &item)
{
// TODO: this method is called when somebody else, e.g. a client
// application, has deleted an item managed by your resource.
......@@ -315,4 +309,4 @@ void etesyncResource::itemRemoved(const Akonadi::Item &item)
// of this template code to keep it simple
}
AKONADI_RESOURCE_MAIN(etesyncResource)
AKONADI_RESOURCE_MAIN(EteSyncResource)
......@@ -18,20 +18,21 @@
#ifndef ETESYNCRESOURCE_H
#define ETESYNCRESOURCE_H
#include <etesync/etesync.h>
#include <kcontacts/addressee.h>
#include <kcontacts/vcardconverter.h>
#include <AkonadiAgentBase/ResourceBase>
class etesyncResource : public Akonadi::ResourceBase,
#include "etesyncadapter.h"
class EteSyncResource : public Akonadi::ResourceBase,
public Akonadi::AgentBase::Observer
{
Q_OBJECT
public:
explicit etesyncResource(const QString &id);
~etesyncResource() override;
explicit EteSyncResource(const QString &id);
~EteSyncResource() override;
Q_SIGNALS:
......@@ -60,10 +61,10 @@ private Q_SLOTS:
void onReloadConfiguration();
private:
EteSync *etesync;
EteSyncPtr mClient = nullptr;
QString derived;
EteSyncJournalManager *journalManager;
EteSyncAsymmetricKeyPair *keypair;
EteSyncJournalManagerPtr journalManager = nullptr;
EteSyncAsymmetricKeyPairPtr keypair = nullptr;
QString username, password, serverUrl, encryptionPassword;
};
......
......@@ -39,7 +39,8 @@ void JournalsFetchJob::fetchJournals()
mJournals = etesync_journal_manager_list(journalManager);
if (!mJournals) {
setError(UserDefinedError);
setErrorText(QStringLiteral("JournalsFetchJob failed to fetch journals"));
CharPtr err(etesync_get_error_message());
setErrorText(QStringFromCharPtr(err));
}
emitResult();
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment