Simplify clean-up-on-network-disconnect handling
[quassel.git] / src / common / network.cpp
index 4ce07fe..09c33ff 100644 (file)
@@ -1,5 +1,5 @@
 /***************************************************************************
- *   Copyright (C) 2005-2012 by the Quassel Project                        *
+ *   Copyright (C) 2005-2014 by the Quassel Project                        *
  *   devel@quassel-irc.org                                                 *
  *                                                                         *
  *   This program is free software; you can redistribute it and/or modify  *
@@ -234,8 +234,12 @@ IrcUser *Network::newIrcUser(const QString &hostmask, const QVariantMap &initDat
 
         _ircUsers[nick] = ircuser;
 
-        SYNC_OTHER(addIrcUser, ARG(hostmask))
-        // emit ircUserAdded(hostmask);
+        // This method will be called with a nick instead of hostmask by setInitIrcUsersAndChannels().
+        // Not a problem because initData contains all we need; however, making sure here to get the real
+        // hostmask out of the IrcUser afterwards.
+        QString mask = ircuser->hostmask();
+        SYNC_OTHER(addIrcUser, ARG(mask));
+        // emit ircUserAdded(mask);
         emit ircUserAdded(ircuser);
     }
 
@@ -284,20 +288,6 @@ void Network::removeChansAndUsers()
     QList<IrcChannel *> channels = ircChannels();
     _ircChannels.clear();
 
-    foreach(IrcChannel *channel, channels) {
-        proxy()->detachObject(channel);
-        disconnect(channel, 0, this, 0);
-    }
-    foreach(IrcUser *user, users) {
-        proxy()->detachObject(user);
-        disconnect(user, 0, this, 0);
-    }
-
-    // the second loop is needed because quit can have sideffects
-    foreach(IrcUser *user, users) {
-        user->quit();
-    }
-
     qDeleteAll(users);
     qDeleteAll(channels);
 }
@@ -774,27 +764,54 @@ QVariantMap Network::initSupports() const
 }
 
 
+// There's potentially a lot of users and channels, so it makes sense to optimize the format of this.
+// Rather than sending a thousand maps with identical keys, we convert this into one map containing lists
+// where each list index corresponds to a particular IrcUser. This saves sending the key names a thousand times.
+// Benchmarks have shown space savings of around 56%, resulting in saving several MBs worth of data on sync
+// (without compression) with a decent amount of IrcUsers.
 QVariantMap Network::initIrcUsersAndChannels() const
 {
     QVariantMap usersAndChannels;
-    QVariantMap users;
-    QVariantMap channels;
-
-    QHash<QString, IrcUser *>::const_iterator userIter = _ircUsers.constBegin();
-    QHash<QString, IrcUser *>::const_iterator userIterEnd = _ircUsers.constEnd();
-    while (userIter != userIterEnd) {
-        users[userIter.value()->hostmask()] = userIter.value()->toVariantMap();
-        userIter++;
+
+    if (_ircUsers.count()) {
+        QHash<QString, QVariantList> users;
+        QHash<QString, IrcUser *>::const_iterator it = _ircUsers.begin();
+        QHash<QString, IrcUser *>::const_iterator end = _ircUsers.end();
+        while (it != end) {
+            const QVariantMap &map = it.value()->toVariantMap();
+            QVariantMap::const_iterator mapiter = map.begin();
+            while (mapiter != map.end()) {
+                users[mapiter.key()] << mapiter.value();
+                ++mapiter;
+            }
+            ++it;
+        }
+        // Can't have a container with a value type != QVariant in a QVariant :(
+        // However, working directly on a QVariantMap is awkward for appending, thus the detour via the hash above.
+        QVariantMap userMap;
+        foreach(const QString &key, users.keys())
+            userMap[key] = users[key];
+        usersAndChannels["Users"] = userMap;
     }
-    usersAndChannels["users"] = users;
 
-    QHash<QString, IrcChannel *>::const_iterator channelIter = _ircChannels.constBegin();
-    QHash<QString, IrcChannel *>::const_iterator channelIterEnd = _ircChannels.constEnd();
-    while (channelIter != channelIterEnd) {
-        channels[channelIter.value()->name()] = channelIter.value()->toVariantMap();
-        channelIter++;
+    if (_ircChannels.count()) {
+        QHash<QString, QVariantList> channels;
+        QHash<QString, IrcChannel *>::const_iterator it = _ircChannels.begin();
+        QHash<QString, IrcChannel *>::const_iterator end = _ircChannels.end();
+        while (it != end) {
+            const QVariantMap &map = it.value()->toVariantMap();
+            QVariantMap::const_iterator mapiter = map.begin();
+            while (mapiter != map.end()) {
+                channels[mapiter.key()] << mapiter.value();
+                ++mapiter;
+            }
+            ++it;
+        }
+        QVariantMap channelMap;
+        foreach(const QString &key, channels.keys())
+            channelMap[key] = channels[key];
+        usersAndChannels["Channels"] = channelMap;
     }
-    usersAndChannels["channels"] = channels;
 
     return usersAndChannels;
 }
@@ -804,24 +821,49 @@ void Network::initSetIrcUsersAndChannels(const QVariantMap &usersAndChannels)
 {
     Q_ASSERT(proxy());
     if (isInitialized()) {
-        qWarning() << "Network" << networkId() << "received init data for users and channels allthough there allready are known users or channels!";
+        qWarning() << "Network" << networkId() << "received init data for users and channels although there already are known users or channels!";
         return;
     }
 
-    QVariantMap users = usersAndChannels.value("users").toMap();
-    QVariantMap::const_iterator userIter = users.constBegin();
-    QVariantMap::const_iterator userIterEnd = users.constEnd();
-    while (userIter != userIterEnd) {
-        newIrcUser(userIter.key(), userIter.value().toMap());
-        userIter++;
+    // toMap() and toList() are cheap, so we can avoid copying to lists...
+    // However, we really have to make sure to never accidentally detach from the shared data!
+
+    const QVariantMap &users = usersAndChannels["Users"].toMap();
+
+    // sanity check
+    int count = users["nick"].toList().count();
+    foreach(const QString &key, users.keys()) {
+        if (users[key].toList().count() != count) {
+            qWarning() << "Received invalid usersAndChannels init data, sizes of attribute lists don't match!";
+            return;
+        }
+    }
+
+    // now create the individual IrcUsers
+    for(int i = 0; i < count; i++) {
+        QVariantMap map;
+        foreach(const QString &key, users.keys())
+            map[key] = users[key].toList().at(i);
+        newIrcUser(map["nick"].toString(), map); // newIrcUser() properly handles the hostmask being just the nick
     }
 
-    QVariantMap channels = usersAndChannels.value("channels").toMap();
-    QVariantMap::const_iterator channelIter = channels.constBegin();
-    QVariantMap::const_iterator channelIterEnd = channels.constEnd();
-    while (channelIter != channelIterEnd) {
-        newIrcChannel(channelIter.key(), channelIter.value().toMap());
-        channelIter++;
+    // same thing for IrcChannels
+    const QVariantMap &channels = usersAndChannels["Channels"].toMap();
+
+    // sanity check
+    count = channels["name"].toList().count();
+    foreach(const QString &key, channels.keys()) {
+        if (channels[key].toList().count() != count) {
+            qWarning() << "Received invalid usersAndChannels init data, sizes of attribute lists don't match!";
+            return;
+        }
+    }
+    // now create the individual IrcChannels
+    for(int i = 0; i < count; i++) {
+        QVariantMap map;
+        foreach(const QString &key, channels.keys())
+            map[key] = channels[key].toList().at(i);
+        newIrcChannel(map["name"].toString(), map);
     }
 }