Make bare /quit disconnect only that server, keep windows open, bump to 0.1.9

/quit now disconnects the window's server without closing anything: every
buffer of that server and wIRC main get a "Disconnected" line, the server card
shows Disconnected with its Connect button, history is flushed to disk, and
there's no auto-reconnect. Connect rejoins the channels we were in (including
ones joined with /join, now tracked) into the still-open windows. Sending while
disconnected shows a "Not connected" line in that window. Exit wIRC still
disconnects everything and closes the app.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
weishaupt
2026-10-06 12:46:59 -04:00
co-authored by Claude Opus 5.5
parent d9a0297cc7
commit 3538274805
7 changed files with 162 additions and 31 deletions
+18 -2
View File
@@ -50,6 +50,9 @@ private fun translateOnConnectCommand(cmd: String): String {
class IrcConnection(
val config: ServerConfig,
private val scope: CoroutineScope,
// Channels to rejoin on top of config.autoJoinChannels - the ones a previous connection
// to this server was in (see ConnectionManager.connect).
initialChannels: Collection<String> = emptyList(),
private val onLine: (ParsedLine) -> Unit,
private val onStatus: (String) -> Unit
) {
@@ -60,13 +63,17 @@ class IrcConnection(
@Volatile private var socket: Socket? = null
@Volatile private var writer: PrintWriter? = null
private var readJob: Job? = null
private var wantConnected = false
// @Volatile: disconnect() clears it from the UI thread while the read loop checks it on
// an IO thread - it's what stops the loop from auto-reconnecting after a /quit.
@Volatile private var wantConnected = false
private var backoffMillis = 2000L
// Every channel we're currently in, whether from config.autoJoinChannels or joined
// manually mid-session - so a reconnect rejoins everything, not just the original
// auto-join list.
private val joinedChannels = java.util.concurrent.CopyOnWriteArraySet<String>()
private val joinedChannels = java.util.concurrent.CopyOnWriteArraySet<String>(initialChannels)
fun trackedChannels(): Set<String> = joinedChannels.toSet()
// Guards runPostRegistration() against running twice for the same connection cycle
// (376 and 422 are mutually exclusive per RFC, but this is cheap insurance) - reset
@@ -121,6 +128,9 @@ class IrcConnection(
Socket(config.host, config.port)
}
socket = sock
// disconnect() can't interrupt a socket that's still being opened, so
// check here before registering on a connection nobody wants anymore.
ensureActive()
val out = PrintWriter(OutputStreamWriter(sock.getOutputStream(), Charsets.UTF_8), true)
writer = out
@@ -161,6 +171,9 @@ class IrcConnection(
handleRawLine(line)
}
} catch (e: Exception) {
// After disconnect() the read fails because we closed the socket
// ourselves - that's not an error worth reporting.
if (!wantConnected) break
onStatus("Connection error: ${e.message}")
ircDebug(config.id, "connection dropped: ${e.message} - tracked channels at drop time: $joinedChannels")
} finally {
@@ -277,6 +290,9 @@ class IrcConnection(
try { writer?.println("QUIT :leaving") } catch (_: Exception) {}
readJob?.cancel()
closeSocketQuietly()
// Ends the outbox consumer - this connection is never reused (a reconnect
// makes a new IrcConnection), so it would otherwise idle forever.
outbox.close()
}
}