Table of Contents

Class ServerViewModel

Namespace
McServerLauncher.ViewModels
Assembly
McServerLauncher.dll

The crossplay half of ServerViewModel: Geyser, the Bedrock tunnel and the address.

public sealed class ServerViewModel : ObservableObject, INotifyPropertyChanged, INotifyPropertyChanging, IDisposable
Inheritance
ServerViewModel
Implements
Inherited Members

Remarks

Split out because the file had reached two thousand lines carrying the process, the console, the players, the ports, the tunnels, crossplay, the backups, Java and the notifications. This is the part that has grown most and is likeliest to grow again, and it is a closed subject: everything here is about one question, and nothing outside it needs any of these members.

A move, not a rewrite. The class was already partial, so not one instruction changed — which is what makes it safe to do at all, and why the whole test suite must give the identical result.

Constructors

ServerViewModel(ServerConfig)

Assembles the view model. It does not start anything — see Activate().

public ServerViewModel(ServerConfig config)

Parameters

config ServerConfig

Remarks

The split is the difference between a server that exists and one that is being watched. Everything here reads: the config, the console palette, the server's own files. Nothing here polls, opens a socket, subscribes to a process-wide singleton or goes to the network, so building one costs a few file reads and leaves nothing running behind it.

That is what makes the class reachable from a test at all. It is also the right shape on its own: a constructor that started three timers and two background requests had already begun doing its job before its caller had the chance to decide whether it wanted it to.

Properties

AddToWhitelistCommand

Gets an IAsyncRelayCommand instance wrapping McServerLauncher.ViewModels.ServerViewModel.AddToWhitelist().

[ExcludeFromCodeCoverage]
public IAsyncRelayCommand AddToWhitelistCommand { get; }

Property Value

IAsyncRelayCommand

Backups

public ServerBackupsViewModel Backups { get; }

Property Value

ServerBackupsViewModel

BanPlayerCommand

Gets an IAsyncRelayCommand<T> instance wrapping BanPlayer(string).

[ExcludeFromCodeCoverage]
public IAsyncRelayCommand<string?> BanPlayerCommand { get; }

Property Value

IAsyncRelayCommand<string>

BannedPlayers

Banned players (banned-players.json).

public BulkObservableCollection<string> BannedPlayers { get; }

Property Value

BulkObservableCollection<string>

BedrockHost

The Bedrock host and port, shown separately. Null when there is no Bedrock tunnel.

[ExcludeFromCodeCoverage]
public string? BedrockHost { get; set; }

Property Value

string

BedrockLocalPortText

The local UDP port Geyser listens on. Known from the moment crossplay is set up.

public string BedrockLocalPortText { get; }

Property Value

string

BedrockPortText

[ExcludeFromCodeCoverage]
public string? BedrockPortText { get; set; }

Property Value

string

BedrockPortsInUse

The Bedrock ports the other servers have already been given.

public Func<IEnumerable<int>>? BedrockPortsInUse { get; set; }

Property Value

Func<IEnumerable<int>>

Remarks

Supplied by MainViewModel, which is the only thing that knows the other servers exist. It matters because the free-port search asks the operating system which UDP ports are bound right now: set crossplay up on two stopped servers and both are handed 19132, and the collision only shows up later as the second Geyser failing to bind.

BedrockState

How far along the Bedrock address is. Drives the explanation under the panel.

[ExcludeFromCodeCoverage]
public BedrockAddressState BedrockState { get; set; }

Property Value

BedrockAddressState

BedrockStateText

One line saying what is happening, so the panel is never blank without a reason.

public string BedrockStateText { get; }

Property Value

string

CanStart

public bool CanStart { get; }

Property Value

bool

CanStop

public bool CanStop { get; }

Property Value

bool

ClearConsoleCommand

Gets an IRelayCommand instance wrapping McServerLauncher.ViewModels.ServerViewModel.ClearConsole().

[ExcludeFromCodeCoverage]
public IRelayCommand ClearConsoleCommand { get; }

Property Value

IRelayCommand

CommandHelp

Common commands with their explanation (for the console help).

public IReadOnlyList<CommandHelp> CommandHelp { get; }

Property Value

IReadOnlyList<CommandHelp>

CommandText

[ExcludeFromCodeCoverage]
public string CommandText { get; set; }

Property Value

string

Config

public ServerConfig Config { get; }

Property Value

ServerConfig

ConnectedPlayers

Players connected right now (live, read from the console).

public BulkObservableCollection<string> ConnectedPlayers { get; }

Property Value

BulkObservableCollection<string>

ConnectionTest

The connection test on the network page, kept with its last result.

public ConnectionTestViewModel ConnectionTest { get; }

Property Value

ConnectionTestViewModel

ConsoleFilter

[ExcludeFromCodeCoverage]
public string ConsoleFilter { get; set; }

Property Value

string

ConsoleKinds

One switch per kind of line, in the order they appear along the top of the console.

public IReadOnlyList<ConsoleKindFilter> ConsoleKinds { get; }

Property Value

IReadOnlyList<ConsoleKindFilter>

ConsoleLines

Lines of the embedded console (server stdout/stderr + the launcher's own).

public BulkObservableCollection<ConsoleLine> ConsoleLines { get; }

Property Value

BulkObservableCollection<ConsoleLine>

CopyBedrockAddressCommand

Gets an IAsyncRelayCommand instance wrapping McServerLauncher.ViewModels.ServerViewModel.CopyBedrockAddress().

[ExcludeFromCodeCoverage]
public IAsyncRelayCommand CopyBedrockAddressCommand { get; }

Property Value

IAsyncRelayCommand

CopyTunnelAddressCommand

Gets an IAsyncRelayCommand instance wrapping McServerLauncher.ViewModels.ServerViewModel.CopyTunnelAddress().

[ExcludeFromCodeCoverage]
public IAsyncRelayCommand CopyTunnelAddressCommand { get; }

Property Value

IAsyncRelayCommand

CpuSeries

[ExcludeFromCodeCoverage]
public IReadOnlyList<double>? CpuSeries { get; set; }

Property Value

IReadOnlyList<double>

CpuText

[ExcludeFromCodeCoverage]
public string CpuText { get; set; }

Property Value

string

DeopPlayerCommand

Gets an IAsyncRelayCommand<T> instance wrapping DeopPlayer(string).

[ExcludeFromCodeCoverage]
public IAsyncRelayCommand<string?> DeopPlayerCommand { get; }

Property Value

IAsyncRelayCommand<string>

DismissPerformanceBandCommand

Gets an IRelayCommand instance wrapping McServerLauncher.ViewModels.ServerViewModel.DismissPerformanceBand().

[ExcludeFromCodeCoverage]
public IRelayCommand DismissPerformanceBandCommand { get; }

Property Value

IRelayCommand

GameVersionText

Minecraft version (empty until known).

public string GameVersionText { get; }

Property Value

string

HasBedrockAddress

True once there is a public Bedrock address worth showing.

public bool HasBedrockAddress { get; }

Property Value

bool

HasIcon

public bool HasIcon { get; }

Property Value

bool

HasIdleCountdown

True while there is a countdown to show.

public bool HasIdleCountdown { get; }

Property Value

bool

HasPerformanceSubText

True when there is a line under the figure.

public bool HasPerformanceSubText { get; }

Property Value

bool

HasRamDetail

True when there is a Java reading to show under the RAM figure.

public bool HasRamDetail { get; }

Property Value

bool

History

Everyone who has been on this server, and each one's profile.

public PlayerHistoryViewModel History { get; }

Property Value

PlayerHistoryViewModel

IdleCountdownText

How long until the empty server stops itself, or null when nothing is counting.

[ExcludeFromCodeCoverage]
public string? IdleCountdownText { get; set; }

Property Value

string

Remarks

Only set while the clock is actually running, so the UI can bind its visibility straight to this: an idle countdown showing "0:00" on a server nobody configured to stop would be a lie.

IsCommandHelpOpen

[ExcludeFromCodeCoverage]
public bool IsCommandHelpOpen { get; set; }

Property Value

bool

IsConsoleFiltered

True while anything at all is being hidden, so the UI can offer to undo it.

public bool IsConsoleFiltered { get; }

Property Value

bool

IsConverting

True while the settings page installs another loader into this server's folder.

[ExcludeFromCodeCoverage]
public bool IsConverting { get; set; }

Property Value

bool

Remarks

Starting meanwhile would launch a jar that is being replaced. The button follows this, and so does every start without one — waking on demand, restarting after a crash.

IsCrossplayOn

Whether to show the Bedrock panel at all: the user asked for crossplay.

public bool IsCrossplayOn { get; }

Property Value

bool

Remarks

Deliberately not "we successfully looked a tunnel up", which is what the panel used to be tied to. Everything the user needs in the first minute — the local port, and the fact that the app is still waiting on playit — is knowable before any lookup succeeds, and hiding the whole block until one did is what made a working server look like a broken one.

Announced through ServerConfigEffects when the config's own flag changes, which is why this panel no longer has a hand-written refresh of its own.

IsModded

public bool IsModded { get; }

Property Value

bool

IsPreparing

True from the moment a start begins until the process is up or the start gives up.

[ExcludeFromCodeCoverage]
public bool IsPreparing { get; set; }

Property Value

bool

Remarks

Before the process exists a start spends seconds — minutes, with a big world to back up — checking the port, the path, the dependencies and the Java, and making the backup, and all that time the state is still Stopped. Without this, anything that starts a server without the button (a second knock while waking on demand, an auto-restart, the button itself once it had finished its own run) began a second start beside the first: a second backup of the same world, and an error when it found the first one's process already there.

IsRestoring

True while a backup is being put back in place of the world.

public bool IsRestoring { get; }

Property Value

bool

Remarks

Nothing may start the server meanwhile: it would open a world that is being swapped out from under it. The button follows this, and so does every start that has no button — waking on demand, restarting after a crash.

IsRunning

public bool IsRunning { get; }

Property Value

bool

KeepAutomaticMemoryCommand

Gets an IRelayCommand instance wrapping McServerLauncher.ViewModels.ServerViewModel.KeepAutomaticMemory().

[ExcludeFromCodeCoverage]
public IRelayCommand KeepAutomaticMemoryCommand { get; }

Property Value

IRelayCommand

KickPlayerCommand

Gets an IAsyncRelayCommand<T> instance wrapping KickPlayer(string).

[ExcludeFromCodeCoverage]
public IAsyncRelayCommand<string?> KickPlayerCommand { get; }

Property Value

IAsyncRelayCommand<string>

KnownPlayers

Players who have ever joined (usercache.json).

public BulkObservableCollection<string> KnownPlayers { get; }

Property Value

BulkObservableCollection<string>

MemoryNoticeManualText

The notice's way back, with the numbers it goes back to.

public string MemoryNoticeManualText { get; }

Property Value

string

MemoryPlan

What the current, or last, run started with. Null before the first start.

public MemoryPlan? MemoryPlan { get; }

Property Value

MemoryPlan

Mods

public ServerModsViewModel Mods { get; }

Property Value

ServerModsViewModel

MotdText

[ExcludeFromCodeCoverage]
public string MotdText { get; set; }

Property Value

string

Name

[ExcludeFromCodeCoverage]
public string Name { get; set; }

Property Value

string

NewWhitelistName

[ExcludeFromCodeCoverage]
public string NewWhitelistName { get; set; }

Property Value

string

OpPlayerCommand

Gets an IAsyncRelayCommand<T> instance wrapping OpPlayer(string).

[ExcludeFromCodeCoverage]
public IAsyncRelayCommand<string?> OpPlayerCommand { get; }

Property Value

IAsyncRelayCommand<string>

OpPlayers

Operators (ops.json).

public BulkObservableCollection<string> OpPlayers { get; }

Property Value

BulkObservableCollection<string>

PardonPlayerCommand

Gets an IAsyncRelayCommand<T> instance wrapping PardonPlayer(string).

[ExcludeFromCodeCoverage]
public IAsyncRelayCommand<string?> PardonPlayerCommand { get; }

Property Value

IAsyncRelayCommand<string>

PerformanceBandIsSevere

The band's colours: amber when tight, red when saturated.

[ExcludeFromCodeCoverage]
public bool PerformanceBandIsSevere { get; set; }

Property Value

bool

PerformanceBandText

The band's one line.

[ExcludeFromCodeCoverage]
public string PerformanceBandText { get; set; }

Property Value

string

PerformanceBrush

The colour of the figure: green, amber or red.

[ExcludeFromCodeCoverage]
public IBrush PerformanceBrush { get; set; }

Property Value

IBrush

PerformanceSubText

The line under it.

[ExcludeFromCodeCoverage]
public string PerformanceSubText { get; set; }

Property Value

string

PerformanceText

The big figure on the Performance card: TPS when they can be measured, else a word.

[ExcludeFromCodeCoverage]
public string PerformanceText { get; set; }

Property Value

string

PerformanceTip

What the card's tooltip says.

[ExcludeFromCodeCoverage]
public string PerformanceTip { get; set; }

Property Value

string

PlayerCountText

[ExcludeFromCodeCoverage]
public string PlayerCountText { get; set; }

Property Value

string

PlayitState

[ExcludeFromCodeCoverage]
public PlayitState PlayitState { get; set; }

Property Value

PlayitState

PlayitStatusText

[ExcludeFromCodeCoverage]
public string PlayitStatusText { get; set; }

Property Value

string

PortText

[ExcludeFromCodeCoverage]
public string PortText { get; set; }

Property Value

string

RamDetailText

The line under the RAM figure: what Java has in use and what it holds, or empty.

[ExcludeFromCodeCoverage]
public string RamDetailText { get; set; }

Property Value

string

RamSeries

[ExcludeFromCodeCoverage]
public IReadOnlyList<double>? RamSeries { get; set; }

Property Value

IReadOnlyList<double>

RamText

[ExcludeFromCodeCoverage]
public string RamText { get; set; }

Property Value

string

RefreshPlayersCommand

Gets an IRelayCommand instance wrapping McServerLauncher.ViewModels.ServerViewModel.RefreshPlayers().

[ExcludeFromCodeCoverage]
public IRelayCommand RefreshPlayersCommand { get; }

Property Value

IRelayCommand

RemoveFromWhitelistCommand

[ExcludeFromCodeCoverage]
public IAsyncRelayCommand<string?> RemoveFromWhitelistCommand { get; }

Property Value

IAsyncRelayCommand<string>

RestartCommand

Gets an IAsyncRelayCommand instance wrapping McServerLauncher.ViewModels.ServerViewModel.Restart().

[ExcludeFromCodeCoverage]
public IAsyncRelayCommand RestartCommand { get; }

Property Value

IAsyncRelayCommand

ReturnToManualMemoryCommand

Gets an IRelayCommand instance wrapping McServerLauncher.ViewModels.ServerViewModel.ReturnToManualMemory().

[ExcludeFromCodeCoverage]
public IRelayCommand ReturnToManualMemoryCommand { get; }

Property Value

IRelayCommand

SendCommandCommand

Gets an IRelayCommand instance wrapping McServerLauncher.ViewModels.ServerViewModel.SendCommand().

[ExcludeFromCodeCoverage]
public IRelayCommand SendCommandCommand { get; }

Property Value

IRelayCommand

ServerIcon

[ExcludeFromCodeCoverage]
public Bitmap? ServerIcon { get; set; }

Property Value

Bitmap

ServerTypeBrush

Badge color per type (shared palette; unknown/future types fall back to gray).

public IBrush ServerTypeBrush { get; }

Property Value

IBrush

ServerTypeText

Server type shown as a badge (Vanilla/Fabric/Forge, and any future type).

public string ServerTypeText { get; }

Property Value

string

ShowAllConsoleKindsCommand

Gets an IRelayCommand instance wrapping McServerLauncher.ViewModels.ServerViewModel.ShowAllConsoleKinds().

[ExcludeFromCodeCoverage]
public IRelayCommand ShowAllConsoleKindsCommand { get; }

Property Value

IRelayCommand

ShowMemoryNotice

True while the server runs Automatic without its owner having chosen it.

public bool ShowMemoryNotice { get; }

Property Value

bool

ShowPerformanceBand

True while a sustained problem is on and the owner has not closed the band.

[ExcludeFromCodeCoverage]
public bool ShowPerformanceBand { get; set; }

Property Value

bool

ShowPerformanceSolutionsCommand

Gets an IRelayCommand instance wrapping McServerLauncher.ViewModels.ServerViewModel.ShowPerformanceSolutions().

[ExcludeFromCodeCoverage]
public IRelayCommand ShowPerformanceSolutionsCommand { get; }

Property Value

IRelayCommand

ShowTunnelWarning

[ExcludeFromCodeCoverage]
public bool ShowTunnelWarning { get; set; }

Property Value

bool

ShowingSolutions

public bool ShowingSolutions { get; }

Property Value

bool

ShowingTabs

public bool ShowingTabs { get; }

Property Value

bool

SignalBrush

[ExcludeFromCodeCoverage]
public IBrush SignalBrush { get; set; }

Property Value

IBrush

SignalHint

[ExcludeFromCodeCoverage]
public string SignalHint { get; set; }

Property Value

string

Solutions

The page that replaces the tabs, or null while the tabs show.

[ExcludeFromCodeCoverage]
public PerformanceSolutionsViewModel? Solutions { get; set; }

Property Value

PerformanceSolutionsViewModel

StartCommand

Gets an IAsyncRelayCommand instance wrapping McServerLauncher.ViewModels.ServerViewModel.Start().

[ExcludeFromCodeCoverage]
public IAsyncRelayCommand StartCommand { get; }

Property Value

IAsyncRelayCommand

State

[ExcludeFromCodeCoverage]
public ServerState State { get; set; }

Property Value

ServerState

StatusBrush

[ExcludeFromCodeCoverage]
public IBrush StatusBrush { get; set; }

Property Value

IBrush

StatusText

[ExcludeFromCodeCoverage]
public string StatusText { get; set; }

Property Value

string

StopCommand

Gets an IAsyncRelayCommand instance wrapping McServerLauncher.ViewModels.ServerViewModel.Stop().

[ExcludeFromCodeCoverage]
public IAsyncRelayCommand StopCommand { get; }

Property Value

IAsyncRelayCommand

ToggleWhitelistCommand

Gets an IAsyncRelayCommand instance wrapping McServerLauncher.ViewModels.ServerViewModel.ToggleWhitelist().

[ExcludeFromCodeCoverage]
public IAsyncRelayCommand ToggleWhitelistCommand { get; }

Property Value

IAsyncRelayCommand

TunnelAddress

[ExcludeFromCodeCoverage]
public string? TunnelAddress { get; set; }

Property Value

string

TunnelState

How far along the public address is. Drives the line under the Playit row.

[ExcludeFromCodeCoverage]
public TunnelAddressState TunnelState { get; set; }

Property Value

TunnelAddressState

TunnelStateText

One line saying what is happening, so the box is never blank without a reason.

public string TunnelStateText { get; }

Property Value

string

UptimeText

[ExcludeFromCodeCoverage]
public string UptimeText { get; set; }

Property Value

string

UseCommandCommand

Gets an IRelayCommand<T> instance wrapping UseCommand(CommandHelp).

[ExcludeFromCodeCoverage]
public IRelayCommand<CommandHelp?> UseCommandCommand { get; }

Property Value

IRelayCommand<CommandHelp>

VisibleConsoleLines

The lines currently shown in the console UI: all of ConsoleLines when nothing is filtered, or the matching subset (kept in order, updated incrementally as new lines arrive) while a search is typed or a category is switched off.

public BulkObservableCollection<ConsoleLine> VisibleConsoleLines { get; }

Property Value

BulkObservableCollection<ConsoleLine>

WhitelistEnabled

[ExcludeFromCodeCoverage]
public bool WhitelistEnabled { get; set; }

Property Value

bool

WhitelistPlayers

Players currently in the whitelist (names).

public BulkObservableCollection<string> WhitelistPlayers { get; }

Property Value

BulkObservableCollection<string>

Methods

Activate()

Starts watching: the polling timer, the shared Playit state, the tunnel addresses and the wake-on-demand listener. ShutdownAsync() is its mirror.

public void Activate()

Remarks

Called by MainViewModel once the window is up, rather than by the constructor, so that building a server and switching it on are two decisions instead of one. Everything in here reaches outside the object — a shared singleton, a socket, the network — and none of it is wanted by code that only needs to look at a server.

CreateBedrockTunnelAsync(string)

Creates the Bedrock tunnel of a crossplay server that has none (the tunnels screen offers it).

public Task CreateBedrockTunnelAsync(string playitKey)

Parameters

playitKey string

Returns

Task

Remarks

The port is on record by definition — it comes from this server's own config — so a tunnel already sitting on it is taken to be ours, which is what the setup flow assumes too.

CreateTunnelAsync(string)

Creates (if it doesn't exist) the Playit tunnel for this server's port, using the write key. Messages are shown in the server's console.

public Task CreateTunnelAsync(string writeKey)

Parameters

writeKey string

Returns

Task

Dispose()

Lets go of what this server owns, once it is off the list for good.

public void Dispose()

Remarks

After ShutdownAsync(), and only for a server that is being forgotten: shutting down is not the end (a failed update brings the same view models back), so it keeps everything usable and this is the one that does not.

LogLauncher(string)

Writes a line the launcher itself says into this server's console.

public void LogLauncher(string line)

Parameters

line string

Remarks

For the tunnels screen, which acts on a server that is not the one on screen: its messages belong in that server's console, not in a dialog nobody asked for.

MoveBedrockPortAsync(string?)

Moves this server's Bedrock port to a free one: when it shares its port with another server, only one of the two can be reached and the other looks broken for no visible reason.

public Task MoveBedrockPortAsync(string? playitKey)

Parameters

playitKey string

A key that may create tunnels, or null when the only one available can only read. Then the console says exactly which tunnel to make on playit.gg instead of failing quietly.

Returns

Task

Remarks

The port picked avoids the other servers' Bedrock ports, the UDP tunnels already on the account and whatever is bound right now — the same search a new crossplay server gets — so moving cannot land on another collision.

RefreshConsoleColours()

Repaints the console and its switches after the colours change in the settings.

public void RefreshConsoleColours()

RefreshFromDisk()

Re-reads data from disk (after editing server.properties).

public void RefreshFromDisk()

RefreshTunnelInfoAsync()

Re-reads this server's tunnel addresses now, instead of waiting for the 30 s timer.

public Task RefreshTunnelInfoAsync()

Returns

Task

SetUpBedrockModContentAsync()

Installs Hydraulic and Fabric API, so Bedrock players see what the mods add.

public Task SetUpBedrockModContentAsync()

Returns

Task

Remarks

The flag is only set once the install has actually succeeded. A server remembering it has this when the download failed would leave Bedrock players staring at invisible blocks with the app insisting it was handled.

SetUpCrossplayAsync(string?)

Installs Geyser and Floodgate, creates the Bedrock tunnel and points Geyser at it.

public Task SetUpCrossplayAsync(string? playitKey)

Parameters

playitKey string

Returns

Task

Remarks

The order matters. The tunnel has to exist before the config is written, because the one value Geyser cannot work out for itself is the tunnel's public port — and writing the config first would leave it advertising the wrong one until something happened to rewrite it.

SetUpMultiVersionAsync()

Installs ViaVersion and ViaBackwards, so other Minecraft versions can join.

public Task SetUpMultiVersionAsync()

Returns

Task

Remarks

Nothing to configure and no tunnel involved: both plugins work as installed. The flag is only set once the install has actually succeeded — a server remembering it has them when the download failed would turn players away and show no reason for it.

ShutdownAsync()

The mirror of Activate(): stops watching, and stops the server if it is running. Does NOT touch the Playit service, which keeps running in the background.

public Task ShutdownAsync()

Returns

Task

Remarks

Safe on a view model that was never activated, which is the normal case in a test. Only the two unsubscriptions would be wrong to run unpaired, and they are guarded; stopping an inert timer and closing a listener that was never opened cost nothing.

Events

ConfigChanged

Raised when something persistable about the server changes (to save).

public event Action? ConfigChanged

Event Type

Action