353 lines
12 KiB
TypeScript
353 lines
12 KiB
TypeScript
import {LaterPromise} from "../utils/LaterPromise";
|
|
import {LogCategory, logDebug, logError, logWarn} from "../log";
|
|
import {PermissionManager, PermissionValue} from "../permission/PermissionManager";
|
|
import {ServerCommand} from "../connection/ConnectionBase";
|
|
import {CommandResult} from "../connection/ServerConnectionDeclaration";
|
|
import {ConnectionEvents, ConnectionHandler, ConnectionState} from "../ConnectionHandler";
|
|
import {AbstractCommandHandler} from "../connection/AbstractCommandHandler";
|
|
import {Registry} from "../events";
|
|
import {tr} from "../i18n/localize";
|
|
|
|
export enum GroupType {
|
|
QUERY,
|
|
TEMPLATE,
|
|
NORMAL
|
|
}
|
|
|
|
export enum GroupTarget {
|
|
SERVER,
|
|
CHANNEL
|
|
}
|
|
|
|
export class GroupProperties {
|
|
iconid: number = 0;
|
|
|
|
sortid: number = 0;
|
|
savedb: boolean = false;
|
|
namemode: number = 0;
|
|
}
|
|
|
|
export class GroupPermissionRequest {
|
|
group_id: number;
|
|
promise: LaterPromise<PermissionValue[]>;
|
|
}
|
|
|
|
export type GroupUpdate = { key: GroupProperty, group: Group, oldValue: any, newValue: any };
|
|
|
|
export interface GroupManagerEvents {
|
|
notify_reset: {},
|
|
notify_groups_created: {
|
|
groups: Group[],
|
|
cause: "list-update" | "initialize" | "user-action"
|
|
},
|
|
notify_groups_deleted: {
|
|
groups: Group[],
|
|
cause: "list-update" | "reset" | "user-action"
|
|
},
|
|
notify_groups_updated: { updates: GroupUpdate[] },
|
|
|
|
/* will be fired when all server and channel groups have been received */
|
|
notify_groups_received: {}
|
|
}
|
|
|
|
export type GroupProperty = "name" | "icon" | "sort-id" | "save-db" | "name-mode";
|
|
export interface GroupEvents {
|
|
notify_group_deleted: { },
|
|
|
|
notify_properties_updated: {
|
|
updated_properties: GroupProperty[];
|
|
},
|
|
|
|
notify_needed_powers_updated: { }
|
|
}
|
|
|
|
export class Group {
|
|
readonly handle: GroupManager;
|
|
|
|
readonly events: Registry<GroupEvents>;
|
|
readonly properties: GroupProperties = new GroupProperties();
|
|
|
|
readonly id: number;
|
|
readonly target: GroupTarget;
|
|
readonly type: GroupType;
|
|
|
|
name: string;
|
|
requiredModifyPower: number = 0;
|
|
requiredMemberAddPower: number = 0;
|
|
requiredMemberRemovePower: number = 0;
|
|
|
|
|
|
constructor(handle: GroupManager, id: number, target: GroupTarget, type: GroupType, name: string) {
|
|
this.events = new Registry<GroupEvents>();
|
|
|
|
this.handle = handle;
|
|
this.id = id;
|
|
this.target = target;
|
|
this.type = type;
|
|
this.name = name;
|
|
}
|
|
|
|
updatePropertiesFromGroupList(data: any) : GroupUpdate[] {
|
|
const updates = [] as GroupUpdate[];
|
|
|
|
if(this.name !== data["name"]) {
|
|
updates.push({ key: "name", group: this, oldValue: this.name, newValue: data["name"] });
|
|
this.name = data["name"];
|
|
}
|
|
|
|
/* icon */
|
|
let value = parseInt(data["iconid"]) >>> 0;
|
|
if(value !== this.properties.iconid) {
|
|
updates.push({ key: "icon", group: this, oldValue: this.properties.iconid, newValue: value });
|
|
this.properties.iconid = value;
|
|
}
|
|
|
|
value = parseInt(data["sortid"]);
|
|
if(value !== this.properties.sortid) {
|
|
updates.push({ key: "sort-id", group: this, oldValue: this.properties.sortid, newValue: value });
|
|
this.properties.sortid = value;
|
|
}
|
|
|
|
let flag = parseInt(data["savedb"]) >= 1;
|
|
if(flag !== this.properties.savedb) {
|
|
updates.push({ key: "save-db", group: this, oldValue: this.properties.savedb, newValue: flag });
|
|
this.properties.savedb = flag;
|
|
}
|
|
|
|
value = parseInt(data["namemode"]);
|
|
if(value !== this.properties.namemode) {
|
|
updates.push({ key: "name-mode", group: this, oldValue: this.properties.namemode, newValue: flag });
|
|
this.properties.namemode = value;
|
|
}
|
|
|
|
if(updates.length > 0) {
|
|
this.events.fire("notify_properties_updated", { updated_properties: updates.map(e => e.key) });
|
|
}
|
|
return updates;
|
|
}
|
|
}
|
|
|
|
export class GroupManager extends AbstractCommandHandler {
|
|
static sorter() : (a: Group, b: Group) => number {
|
|
return (a, b) => {
|
|
if(!a)
|
|
return b ? 1 : 0;
|
|
if(!b)
|
|
return a ? -1 : 0;
|
|
|
|
if(a.properties.sortid > b.properties.sortid)
|
|
return 1;
|
|
if(a.properties.sortid < b.properties.sortid)
|
|
return -1;
|
|
|
|
if(a.id < b.id)
|
|
return -1;
|
|
if(a.id > b.id)
|
|
return 1;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
readonly events = new Registry<GroupManagerEvents>();
|
|
readonly connectionHandler: ConnectionHandler;
|
|
|
|
serverGroups: Group[] = [];
|
|
channelGroups: Group[] = [];
|
|
|
|
private allGroupsReceived = false;
|
|
private readonly connectionStateListener;
|
|
private groupPermissionRequests: GroupPermissionRequest[] = [];
|
|
|
|
constructor(client: ConnectionHandler) {
|
|
super(client.serverConnection);
|
|
this.connectionHandler = client;
|
|
|
|
this.connectionStateListener = (event: ConnectionEvents["notify_connection_state_changed"]) => {
|
|
if(event.newState === ConnectionState.DISCONNECTING || event.newState === ConnectionState.UNCONNECTED || event.newState === ConnectionState.CONNECTING) {
|
|
this.reset();
|
|
}
|
|
};
|
|
|
|
client.serverConnection.getCommandHandler().registerHandler(this);
|
|
client.events().on("notify_connection_state_changed", this.connectionStateListener);
|
|
|
|
this.reset();
|
|
}
|
|
|
|
destroy() {
|
|
this.reset();
|
|
this.connectionHandler.events().off("notify_connection_state_changed", this.connectionStateListener);
|
|
this.connectionHandler.serverConnection?.getCommandHandler().unregisterHandler(this);
|
|
this.serverGroups = undefined;
|
|
this.channelGroups = undefined;
|
|
}
|
|
|
|
reset() {
|
|
if(this.serverGroups.length === 0 && this.channelGroups.length === 0) {
|
|
return;
|
|
}
|
|
|
|
logDebug(LogCategory.PERMISSIONS, tr("Resetting server/channel groups"));
|
|
this.serverGroups = [];
|
|
this.channelGroups = [];
|
|
this.allGroupsReceived = false;
|
|
|
|
for(const permission of this.groupPermissionRequests) {
|
|
permission.promise.rejected(tr("Group manager reset"));
|
|
}
|
|
this.groupPermissionRequests = [];
|
|
this.events.fire("notify_reset");
|
|
}
|
|
|
|
handle_command(command: ServerCommand): boolean {
|
|
switch (command.command) {
|
|
case "notifyservergrouplist":
|
|
this.handleGroupList(command.arguments, GroupTarget.SERVER);
|
|
return true;
|
|
case "notifychannelgrouplist":
|
|
this.handleGroupList(command.arguments, GroupTarget.CHANNEL);
|
|
return true;
|
|
|
|
case "notifyservergrouppermlist":
|
|
case "notifychannelgrouppermlist":
|
|
this.handleGroupPermissionList(command.arguments);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
requestGroups(){
|
|
this.connectionHandler.serverConnection.send_command("servergrouplist", {}, { process_result: false }).catch(error => {
|
|
logWarn(LogCategory.PERMISSIONS, tr("Failed to request the server group list: %o"), error);
|
|
});
|
|
|
|
this.connectionHandler.serverConnection.send_command("channelgrouplist", {}, { process_result: false }).catch(error => {
|
|
logWarn(LogCategory.PERMISSIONS, tr("Failed to request the channel group list: %o"), error);
|
|
});
|
|
}
|
|
|
|
findServerGroup(id: number) : Group | undefined {
|
|
for(let group of this.serverGroups) {
|
|
if(group.id === id) {
|
|
return group;
|
|
}
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
findChannelGroup(id: number) : Group | undefined {
|
|
for(let group of this.channelGroups)
|
|
if(group.id === id)
|
|
return group;
|
|
return undefined;
|
|
}
|
|
|
|
private handleGroupList(json: any[], target: GroupTarget) {
|
|
let groupList = target == GroupTarget.SERVER ? this.serverGroups : this.channelGroups;
|
|
const deleteGroups = groupList.slice(0);
|
|
const newGroups: Group[] = [];
|
|
const groupUpdates: GroupUpdate[] = [];
|
|
|
|
const isInitialList = groupList.length === 0;
|
|
for(const groupData of json) {
|
|
let type : GroupType;
|
|
switch (parseInt(groupData["type"])) {
|
|
case 0: type = GroupType.TEMPLATE; break;
|
|
case 1: type = GroupType.NORMAL; break;
|
|
case 2: type = GroupType.QUERY; break;
|
|
default:
|
|
logError(LogCategory.CLIENT, tr("Invalid group type: %o for group %s"), groupData["type"], groupData["name"]);
|
|
continue;
|
|
}
|
|
|
|
const groupId = parseInt(target == GroupTarget.SERVER ? groupData["sgid"] : groupData["cgid"]);
|
|
let groupIndex = deleteGroups.findIndex(e => e.id === groupId);
|
|
|
|
let group: Group;
|
|
if(groupIndex === -1) {
|
|
group = new Group(this, groupId, target, type, groupData["name"]);
|
|
groupList.push(group);
|
|
newGroups.push(group);
|
|
group.updatePropertiesFromGroupList(groupData);
|
|
} else {
|
|
group = deleteGroups.splice(groupIndex, 1)[0];
|
|
groupUpdates.push(...group.updatePropertiesFromGroupList(groupData));
|
|
}
|
|
|
|
group.requiredMemberRemovePower = parseInt(groupData["n_member_removep"]);
|
|
group.requiredMemberAddPower = parseInt(groupData["n_member_addp"]);
|
|
group.requiredModifyPower = parseInt(groupData["n_modifyp"]);
|
|
group.events.fire("notify_needed_powers_updated");
|
|
}
|
|
|
|
if(newGroups.length !== 0) {
|
|
this.events.fire("notify_groups_created", { groups: newGroups, cause: isInitialList ? "initialize" : "list-update" });
|
|
}
|
|
|
|
for(const deleted of deleteGroups) {
|
|
groupList.remove(deleted);
|
|
deleted.events.fire("notify_group_deleted");
|
|
}
|
|
|
|
if(deleteGroups.length !== 0) {
|
|
this.events.fire("notify_groups_deleted", { groups: deleteGroups, cause: "list-update" });
|
|
}
|
|
|
|
this.events.fire("notify_groups_updated", { updates: groupUpdates });
|
|
|
|
if(!this.allGroupsReceived && this.serverGroups.length > 0 && this.channelGroups.length > 0) {
|
|
this.allGroupsReceived = true;
|
|
this.events.fire("notify_groups_received");
|
|
}
|
|
}
|
|
|
|
request_permissions(group: Group) : Promise<PermissionValue[]> {
|
|
for(let request of this.groupPermissionRequests)
|
|
if(request.group_id == group.id && request.promise.time() + 1000 > Date.now())
|
|
return request.promise;
|
|
let req = new GroupPermissionRequest();
|
|
req.group_id = group.id;
|
|
req.promise = new LaterPromise<PermissionValue[]>();
|
|
this.groupPermissionRequests.push(req);
|
|
|
|
this.connectionHandler.serverConnection.send_command(group.target == GroupTarget.SERVER ? "servergrouppermlist" : "channelgrouppermlist", {
|
|
cgid: group.id,
|
|
sgid: group.id
|
|
}).catch(error => {
|
|
if(error instanceof CommandResult && error.id == 0x0501)
|
|
req.promise.resolved([]);
|
|
else
|
|
req.promise.rejected(error);
|
|
}).then(() => {
|
|
//No notify handler
|
|
setTimeout(() => {
|
|
if(this.groupPermissionRequests.remove(req))
|
|
req.promise.rejected(tr("no response"));
|
|
}, 1000);
|
|
});
|
|
return req.promise;
|
|
}
|
|
|
|
private handleGroupPermissionList(json: any[]) {
|
|
let group = json[0]["sgid"] ? this.findServerGroup(parseInt(json[0]["sgid"])) : this.findChannelGroup(parseInt(json[0]["cgid"]));
|
|
if(!group) {
|
|
logError(LogCategory.PERMISSIONS, tr("Got group permissions for group %o/%o, but its not a registered group!"), json[0]["sgid"], json[0]["cgid"]);
|
|
return;
|
|
}
|
|
let requests: GroupPermissionRequest[] = [];
|
|
for(let req of this.groupPermissionRequests)
|
|
if(req.group_id == group.id)
|
|
requests.push(req);
|
|
|
|
if(requests.length == 0) {
|
|
logWarn(LogCategory.PERMISSIONS, tr("Got group permissions for group %o/%o, but it was never requested!"), json[0]["sgid"], json[0]["cgid"]);
|
|
return;
|
|
}
|
|
|
|
let permissions = PermissionManager.parse_permission_bulk(json, this.connectionHandler.permissions);
|
|
for(let req of requests) {
|
|
this.groupPermissionRequests.remove(req);
|
|
req.promise.resolved(permissions);
|
|
}
|
|
}
|
|
} |