+ }
+}
+
+void CoreNetwork::retryCapsIndividually()
+{
+ // The most recent set of capabilities got denied by the IRC server. As we don't know what got
+ // denied, try each capability individually.
+ if (_capsQueuedLastBundle.empty()) {
+ // No most recently tried capability set, just return.
+ return;
+ // Note: there's little point in retrying individually requested caps during negotiation.
+ // We know the individual capability was the one that failed, and it's not likely it'll
+ // suddenly start working within a few seconds. 'cap-notify' provides a better system for
+ // handling capability removal and addition.
+ }
+
+ // This should be fairly rare, e.g. services restarting during negotiation, so simplicity wins
+ // over efficiency. If this becomes an issue, implement a binary splicing system instead,
+ // keeping track of which halves of the group fail, dividing the set each time.
+
+ // Add most recently tried capability set to individual list, re-requesting them one at a time
+ _capsQueuedIndividual.append(_capsQueuedLastBundle);
+ // Warn of this issue to explain the slower login. Servers usually shouldn't trigger this.
+ showMessage(NetworkInternalMessage(
+ Message::Server,
+ BufferInfo::StatusBuffer,
+ "",
+ tr("Could not negotiate some capabilities, retrying individually (%1)...").arg(_capsQueuedLastBundle.join(", "))
+ ));
+ // Capabilities are already removed from the capability bundle queue via takeQueuedCaps(), no
+ // need to remove them here.
+ // Clear the most recently tried set to reduce risk that mistakes elsewhere causes retrying
+ // indefinitely.
+ _capsQueuedLastBundle.clear();
+}
+
+void CoreNetwork::beginCapNegotiation()
+{
+ // Check if any available capabilities have been disabled
+ QStringList capsSkipped;
+ if (!skipCaps().isEmpty() && !caps().isEmpty()) {
+ // Find the entries that are common to skipCaps() and caps(). This represents any
+ // capabilities supported by the server that were skipped.
+
+ // Both skipCaps() and caps() are already lowercase
+ // std::set_intersection requires sorted lists, and we can't modify the original lists.
+ //
+ // skipCaps() should already be sorted. caps() is intentionally not sorted elsewhere so
+ // Quassel can show the capabilities in the order transmitted by the network.
+ auto sortedCaps = caps();
+ sortedCaps.sort();
+
+ // Find the intersection between skipped caps and server-supplied caps
+ std::set_intersection(skipCaps().cbegin(), skipCaps().cend(),
+ sortedCaps.cbegin(), sortedCaps.cend(),
+ std::back_inserter(capsSkipped));
+ }
+
+ if (!capsPendingNegotiation()) {
+ // No capabilities are queued for request, determine the reason why
+ QString capStatusMsg;
+ if (caps().empty()) {
+ // The server doesn't provide any capabilities, but supports CAP LS
+ capStatusMsg = tr("No capabilities available");
+ }
+ else if (capsEnabled().empty()) {
+ // The server supports capabilities (caps() is not empty) but Quassel doesn't support
+ // anything offered. This should be uncommon.
+ capStatusMsg =
+ tr("None of the capabilities provided by the server are supported (found: %1)")
+ .arg(caps().join(", "));
+ }
+ else {
+ // Quassel has enabled some capabilities, but there are no further capabilities that can
+ // be negotiated.
+ // (E.g. the user has manually run "/cap ls 302" after initial negotiation.)
+ capStatusMsg =
+ tr("No additional capabilities are supported (found: %1; currently enabled: %2)")
+ .arg(caps().join(", "), capsEnabled().join(", "));
+ }
+ // Inform the user of the situation
+ showMessage(NetworkInternalMessage(
+ Message::Server,
+ BufferInfo::StatusBuffer,
+ "",
+ capStatusMsg
+ ));
+
+ if (!capsSkipped.isEmpty()) {
+ // Mention that some capabilities are skipped
+ showMessage(NetworkInternalMessage(
+ Message::Server,
+ BufferInfo::StatusBuffer,
+ "",
+ tr("Quassel is configured to ignore some capabilities (skipped: %1)").arg(capsSkipped.join(", "))
+ ));
+ }
+
+ // End any ongoing capability negotiation, allowing connection to continue
+ endCapNegotiation();
+ return;
+ }
+
+ _capNegotiationActive = true;
+ showMessage(NetworkInternalMessage(
+ Message::Server,
+ BufferInfo::StatusBuffer,
+ "",
+ tr("Ready to negotiate (found: %1)").arg(caps().join(", "))
+ ));
+
+ if (!capsSkipped.isEmpty()) {
+ // Mention that some capabilities are skipped
+ showMessage(NetworkInternalMessage(
+ Message::Server,
+ BufferInfo::StatusBuffer,
+ "",
+ tr("Quassel is configured to ignore some capabilities (skipped: %1)").arg(capsSkipped.join(", "))
+ ));
+ }
+
+ // Build a list of queued capabilities, starting with individual, then bundled, only adding the
+ // comma separator between the two if needed (both individual and bundled caps exist).
+ QString queuedCapsDisplay = _capsQueuedIndividual.join(", ")
+ + ((!_capsQueuedIndividual.empty() && !_capsQueuedBundled.empty()) ? ", " : "")
+ + _capsQueuedBundled.join(", ");
+ showMessage(NetworkInternalMessage(
+ Message::Server,
+ BufferInfo::StatusBuffer,
+ "",
+ tr("Negotiating capabilities (requesting: %1)...").arg(queuedCapsDisplay)
+ ));
+
+ sendNextCap();