Answer what the local client may do from the model
The Swing client menu decided on its own whether a group can be (un)assigned and whether a client's channel can be joined, and the server groups dialog borrowed the menu's check. These are rules about the server, not about menus, so ServerModel answers them now and any frontend asks it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -43,6 +43,11 @@ public final class ClientEntry {
|
||||
return type == 1;
|
||||
}
|
||||
|
||||
public boolean inServerGroup(int groupId) {
|
||||
for (int id : serverGroupIds) if (id == groupId) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Adds a server group id, if not already present. */
|
||||
public void addServerGroup(int groupId) {
|
||||
for (int id : serverGroupIds) if (id == groupId) return;
|
||||
|
||||
@@ -132,12 +132,6 @@ public final class ServerModel {
|
||||
selfPermissionValues.put(permId, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* The local client's resolved value for a named permission (e.g.
|
||||
* {@code "i_group_needed_member_add_power"}), or {@code 0} if it isn't known
|
||||
* yet (the {@code permissionlist} request is still in flight, or the server
|
||||
* never reported a non-default value for it).
|
||||
*/
|
||||
/** Whether the server's {@code permissionlist} has arrived, so ids can be named at all. */
|
||||
public synchronized boolean hasPermissionNames() {
|
||||
return !permissionNames.isEmpty();
|
||||
@@ -148,6 +142,12 @@ public final class ServerModel {
|
||||
return permissionNames.get(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* The local client's resolved value for a named permission (e.g.
|
||||
* {@code "i_group_needed_member_add_power"}), or {@code 0} if it isn't known
|
||||
* yet (the {@code permissionlist} request is still in flight, or the server
|
||||
* never reported a non-default value for it).
|
||||
*/
|
||||
public synchronized int selfPermissionValue(String name) {
|
||||
for (Map.Entry<Integer, String> entry : permissionNames.entrySet()) {
|
||||
if (entry.getValue().equals(name)) {
|
||||
@@ -158,6 +158,49 @@ public final class ServerModel {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---- what the local client may do ----
|
||||
|
||||
/**
|
||||
* Whether the local client has enough power to assign {@code group}, or to take it
|
||||
* away when {@code assigned}: the add power always gates it, and the remove power
|
||||
* additionally gates a group already held. {@code -1} is TS3's "unlimited" sentinel on
|
||||
* either side: an unlimited local power always passes, and a group that needs
|
||||
* unlimited power can only be touched by a local client that has it.
|
||||
*/
|
||||
public synchronized boolean canToggleGroup(Group group, boolean assigned) {
|
||||
return hasPower(group.neededMemberAddPower, selfPermissionValue("i_group_needed_member_add_power"))
|
||||
&& (!assigned || hasPower(group.neededMemberRemovePower,
|
||||
selfPermissionValue("i_group_needed_member_remove_power")));
|
||||
}
|
||||
|
||||
private static boolean hasPower(int needed, int own) {
|
||||
if (own == -1) return true;
|
||||
if (needed == -1) return false;
|
||||
return own >= needed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a channel shows in the tree at all — it doesn't while we are not subscribed
|
||||
* to it (e.g. it needs more subscribe power than we have).
|
||||
*/
|
||||
public synchronized boolean isChannelVisible(int channelId) {
|
||||
ChannelNode channel = channels.get(channelId);
|
||||
return channel != null && channel.subscribed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether we can expect a join to succeed: the channel must be visible to us, and
|
||||
* not already full. This can't account for a join-power permission requirement —
|
||||
* unlike group membership, TS3 doesn't expose a channel's join-power threshold as
|
||||
* a plain property, only as a permission resolved through channel/channel-group/
|
||||
* server-group inheritance, which isn't something a regular client can query.
|
||||
*/
|
||||
public synchronized boolean canJoinChannel(int channelId) {
|
||||
ChannelNode channel = channels.get(channelId);
|
||||
if (channel == null || !channel.subscribed) return false;
|
||||
return channel.maxClients < 0 || channel.clients.size() < channel.maxClients;
|
||||
}
|
||||
|
||||
public synchronized String getServerName() {
|
||||
return serverName;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
package com.ts3client.net;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class ServerModelRulesTest {
|
||||
|
||||
private final ServerModel model = new ServerModel();
|
||||
|
||||
private void grant(String permission, int value) {
|
||||
model.putPermissionName(permission);
|
||||
model.putSelfPermissionValue(permissionId(permission), value);
|
||||
}
|
||||
|
||||
private int permissionId(String name) {
|
||||
for (int id = 0; ; id++) {
|
||||
if (name.equals(model.permissionName(id))) return id;
|
||||
}
|
||||
}
|
||||
|
||||
private static Group group(int addPower, int removePower) {
|
||||
return new Group(9, "Group", 0, 0, 1, addPower, removePower);
|
||||
}
|
||||
|
||||
@Test
|
||||
void groupNeedsEnoughAddPowerAndRemovePowerToTakeAway() {
|
||||
grant("i_group_needed_member_add_power", 50);
|
||||
grant("i_group_needed_member_remove_power", 20);
|
||||
|
||||
assertTrue(model.canToggleGroup(group(50, 0), false));
|
||||
assertFalse(model.canToggleGroup(group(51, 0), false));
|
||||
assertTrue(model.canToggleGroup(group(50, 20), true));
|
||||
assertFalse(model.canToggleGroup(group(50, 21), true));
|
||||
}
|
||||
|
||||
@Test
|
||||
void unlimitedPowerIsMinusOneOnEitherSide() {
|
||||
grant("i_group_needed_member_add_power", 75);
|
||||
grant("i_group_needed_member_remove_power", -1);
|
||||
|
||||
assertFalse(model.canToggleGroup(group(-1, 0), false), "a group needing unlimited power");
|
||||
assertTrue(model.canToggleGroup(group(75, 1000), true), "unlimited remove power");
|
||||
}
|
||||
|
||||
@Test
|
||||
void joiningNeedsAVisibleChannelWithRoom() {
|
||||
ChannelNode channel = model.putChannel(1, "Full", 0, 0);
|
||||
model.putClient(5, "Someone", 1);
|
||||
assertFalse(model.canJoinChannel(1), "not subscribed");
|
||||
|
||||
channel.subscribed = true;
|
||||
assertTrue(model.canJoinChannel(1));
|
||||
channel.maxClients = 1;
|
||||
channel.clients.add(model.getClient(5));
|
||||
assertFalse(model.canJoinChannel(1), "full");
|
||||
assertFalse(model.canJoinChannel(2), "unknown");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user