001package com.github.theholywaffle.teamspeak3;
002
003/*
004 * #%L
005 * TeamSpeak 3 Java API
006 * %%
007 * Copyright (C) 2014 Bert De Geyter
008 * %%
009 * Permission is hereby granted, free of charge, to any person obtaining a copy
010 * of this software and associated documentation files (the "Software"), to deal
011 * in the Software without restriction, including without limitation the rights
012 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
013 * copies of the Software, and to permit persons to whom the Software is
014 * furnished to do so, subject to the following conditions:
015 * 
016 * The above copyright notice and this permission notice shall be included in
017 * all copies or substantial portions of the Software.
018 * 
019 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
020 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
021 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
022 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
023 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
024 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
025 * THE SOFTWARE.
026 * #L%
027 */
028
029import com.github.theholywaffle.teamspeak3.api.*;
030import com.github.theholywaffle.teamspeak3.api.event.TS3EventType;
031import com.github.theholywaffle.teamspeak3.api.event.TS3Listener;
032import com.github.theholywaffle.teamspeak3.api.exception.TS3CommandFailedException;
033import com.github.theholywaffle.teamspeak3.api.exception.TS3Exception;
034import com.github.theholywaffle.teamspeak3.api.exception.TS3FileTransferFailedException;
035import com.github.theholywaffle.teamspeak3.api.wrapper.*;
036import com.github.theholywaffle.teamspeak3.commands.*;
037
038import java.io.ByteArrayInputStream;
039import java.io.ByteArrayOutputStream;
040import java.io.IOException;
041import java.io.InputStream;
042import java.io.OutputStream;
043import java.util.*;
044import java.util.concurrent.TimeUnit;
045import java.util.function.Function;
046import java.util.regex.Pattern;
047import java.util.stream.Collectors;
048
049/**
050 * Asynchronous version of {@link TS3Api} to interact with the {@link TS3Query}.
051 * <p>
052 * This class is used to easily interact with a {@link TS3Query}. It constructs commands,
053 * sends them to the TeamSpeak3 server, processes the response and returns the result.
054 * </p><p>
055 * All methods in this class are asynchronous (so they won't block) and
056 * will return a {@link CommandFuture} of the corresponding return type in {@link TS3Api}.
057 * If a command fails, no exception will be thrown directly. It will however be rethrown in
058 * {@link CommandFuture#get()} and {@link CommandFuture#get(long, TimeUnit)}.
059 * Usually, the thrown exception is a {@link TS3CommandFailedException}, which will get you
060 * access to the {@link QueryError} from which more information about the error can be obtained.
061 * </p><p>
062 * Also note that while these methods are asynchronous, the commands will still be sent through a
063 * synchronous command pipeline. That means if an asynchronous method is called immediately
064 * followed by a synchronous method, the synchronous method will first have to wait until the
065 * asynchronous method completed until it its command is sent.
066 * </p><p>
067 * You won't be able to execute most commands while you're not logged in due to missing permissions.
068 * Make sure to either pass your login credentials to the {@link TS3Config} object when
069 * creating the {@code TS3Query} or to call {@link #login(String, String)} to log in.
070 * </p><p>
071 * After that, most commands also require you to select a {@linkplain VirtualServer virtual server}.
072 * To do so, call either {@link #selectVirtualServerByPort(int)} or {@link #selectVirtualServerById(int)}.
073 * </p>
074 *
075 * @see TS3Api The synchronous version of the API
076 */
077public class TS3ApiAsync {
078
079        /**
080         * The TS3 query that holds the event manager and the file transfer helper.
081         */
082        private final TS3Query query;
083
084        /**
085         * The queue that this TS3ApiAsync sends its commands to.
086         */
087        private final CommandQueue commandQueue;
088
089        /**
090         * Creates a new asynchronous API object for the given {@code TS3Query}.
091         * <p>
092         * <b>Usually, this constructor should not be called.</b> Use {@link TS3Query#getAsyncApi()} instead.
093         * </p>
094         *
095         * @param query
096         *              the TS3Query to use
097         * @param commandQueue
098         *              the queue to send commands to
099         */
100        TS3ApiAsync(TS3Query query, CommandQueue commandQueue) {
101                this.query = query;
102                this.commandQueue = commandQueue;
103        }
104
105        /**
106         * Adds a new ban entry. At least one of the parameters {@code ip}, {@code name} or {@code uid} needs to be non-null.
107         * Returns the ID of the newly created ban entry.
108         *
109         * @param ip
110         *              a RegEx pattern to match a client's IP against, can be {@code null}
111         * @param name
112         *              a RegEx pattern to match a client's name against, can be {@code null}
113         * @param uid
114         *              the unique identifier of a client, can be {@code null}
115         * @param timeInSeconds
116         *              the duration of the ban in seconds. 0 equals a permanent ban
117         * @param reason
118         *              the reason for the ban, can be {@code null}
119         *
120         * @return the ID of the newly created ban entry
121         *
122         * @throws TS3CommandFailedException
123         *              if the execution of a command fails
124         * @querycommands 1
125         * @see Pattern RegEx Pattern
126         * @see #addBan(String, String, String, String, long, String)
127         * @see Client#getId()
128         * @see Client#getUniqueIdentifier()
129         * @see ClientInfo#getIp()
130         */
131        public CommandFuture<Integer> addBan(String ip, String name, String uid, long timeInSeconds, String reason) {
132                return addBan(ip, name, uid, null, timeInSeconds, reason);
133        }
134
135        /**
136         * Adds a new ban entry. At least one of the parameters {@code ip}, {@code name}, {@code uid}, or
137         * {@code myTSId} needs to be non-null. Returns the ID of the newly created ban entry.
138         * <p>
139         * Note that creating a ban entry for the {@code "empty"} "myTeamSpeak" ID will ban all clients who
140         * don't have a linked "myTeamSpeak" account.
141         * </p>
142         *
143         * @param ip
144         *              a RegEx pattern to match a client's IP against, can be {@code null}
145         * @param name
146         *              a RegEx pattern to match a client's name against, can be {@code null}
147         * @param uid
148         *              the unique identifier of a client, can be {@code null}
149         * @param myTSId
150         *              the "myTeamSpeak" ID of a client, the string {@code "empty"}, or {@code null}
151         * @param timeInSeconds
152         *              the duration of the ban in seconds. 0 equals a permanent ban
153         * @param reason
154         *              the reason for the ban, can be {@code null}
155         *
156         * @return the ID of the newly created ban entry
157         *
158         * @throws TS3CommandFailedException
159         *              if the execution of a command fails
160         * @querycommands 1
161         * @see Pattern RegEx Pattern
162         * @see Client#getId()
163         * @see Client#getUniqueIdentifier()
164         * @see ClientInfo#getIp()
165         */
166        public CommandFuture<Integer> addBan(String ip, String name, String uid, String myTSId, long timeInSeconds, String reason) {
167                Command cmd = BanCommands.banAdd(ip, name, uid, myTSId, timeInSeconds, reason);
168                return executeAndReturnIntProperty(cmd, "banid");
169        }
170
171        /**
172         * Adds a specified permission to a client in a specific channel.
173         *
174         * @param channelId
175         *              the ID of the channel wherein the permission should be granted
176         * @param clientDBId
177         *              the database ID of the client to add a permission to
178         * @param permName
179         *              the name of the permission to grant
180         * @param permValue
181         *              the numeric value of the permission (or for boolean permissions: 1 = true, 0 = false)
182         *
183         * @return a future to track the progress of this command
184         *
185         * @throws TS3CommandFailedException
186         *              if the execution of a command fails
187         * @querycommands 1
188         * @see Channel#getId()
189         * @see Client#getDatabaseId()
190         * @see Permission
191         */
192        public CommandFuture<Void> addChannelClientPermission(int channelId, int clientDBId, String permName, int permValue) {
193                Command cmd = PermissionCommands.channelClientAddPerm(channelId, clientDBId, permName, permValue);
194                return executeAndReturnError(cmd);
195        }
196
197        /**
198         * Creates a new channel group for clients using a given name and returns its ID.
199         * <p>
200         * To create channel group templates or ones for server queries,
201         * use {@link #addChannelGroup(String, PermissionGroupDatabaseType)}.
202         * </p>
203         *
204         * @param name
205         *              the name of the new channel group
206         *
207         * @return the ID of the newly created channel group
208         *
209         * @throws TS3CommandFailedException
210         *              if the execution of a command fails
211         * @querycommands 1
212         * @see ChannelGroup
213         */
214        public CommandFuture<Integer> addChannelGroup(String name) {
215                return addChannelGroup(name, null);
216        }
217
218        /**
219         * Creates a new channel group using a given name and returns its ID.
220         *
221         * @param name
222         *              the name of the new channel group
223         * @param type
224         *              the desired type of channel group
225         *
226         * @return the ID of the newly created channel group
227         *
228         * @throws TS3CommandFailedException
229         *              if the execution of a command fails
230         * @querycommands 1
231         * @see ChannelGroup
232         */
233        public CommandFuture<Integer> addChannelGroup(String name, PermissionGroupDatabaseType type) {
234                Command cmd = ChannelGroupCommands.channelGroupAdd(name, type);
235                return executeAndReturnIntProperty(cmd, "cgid");
236        }
237
238        /**
239         * Adds a specified permission to a channel group.
240         *
241         * @param groupId
242         *              the ID of the channel group to grant the permission
243         * @param permName
244         *              the name of the permission to be granted
245         * @param permValue
246         *              the numeric value of the permission (or for boolean permissions: 1 = true, 0 = false)
247         *
248         * @return a future to track the progress of this command
249         *
250         * @throws TS3CommandFailedException
251         *              if the execution of a command fails
252         * @querycommands 1
253         * @see ChannelGroup#getId()
254         * @see Permission
255         */
256        public CommandFuture<Void> addChannelGroupPermission(int groupId, String permName, int permValue) {
257                Command cmd = PermissionCommands.channelGroupAddPerm(groupId, permName, permValue);
258                return executeAndReturnError(cmd);
259        }
260
261        /**
262         * Adds a specified permission to a channel.
263         *
264         * @param channelId
265         *              the ID of the channel wherein the permission should be granted
266         * @param permName
267         *              the name of the permission to grant
268         * @param permValue
269         *              the numeric value of the permission (or for boolean permissions: 1 = true, 0 = false)
270         *
271         * @return a future to track the progress of this command
272         *
273         * @throws TS3CommandFailedException
274         *              if the execution of a command fails
275         * @querycommands 1
276         * @see Channel#getId()
277         * @see Permission
278         */
279        public CommandFuture<Void> addChannelPermission(int channelId, String permName, int permValue) {
280                Command cmd = PermissionCommands.channelAddPerm(channelId, permName, permValue);
281                return executeAndReturnError(cmd);
282        }
283
284        /**
285         * Adds a specified permission to a channel.
286         *
287         * @param clientDBId
288         *              the database ID of the client to grant the permission
289         * @param permName
290         *              the name of the permission to grant
291         * @param value
292         *              the numeric value of the permission (or for boolean permissions: 1 = true, 0 = false)
293         * @param skipped
294         *              if set to {@code true}, the permission will not be overridden by channel group permissions
295         *
296         * @return a future to track the progress of this command
297         *
298         * @throws TS3CommandFailedException
299         *              if the execution of a command fails
300         * @querycommands 1
301         * @see Client#getDatabaseId()
302         * @see Permission
303         */
304        public CommandFuture<Void> addClientPermission(int clientDBId, String permName, int value, boolean skipped) {
305                Command cmd = PermissionCommands.clientAddPerm(clientDBId, permName, value, skipped);
306                return executeAndReturnError(cmd);
307        }
308
309        /**
310         * Adds a client to the specified server group.
311         * <p>
312         * Please note that a client cannot be added to default groups or template groups.
313         * </p>
314         *
315         * @param groupId
316         *              the ID of the server group to add the client to
317         * @param clientDatabaseId
318         *              the database ID of the client to add
319         *
320         * @return a future to track the progress of this command
321         *
322         * @throws TS3CommandFailedException
323         *              if the execution of a command fails
324         * @querycommands 1
325         * @see ServerGroup#getId()
326         * @see Client#getDatabaseId()
327         */
328        public CommandFuture<Void> addClientToServerGroup(int groupId, int clientDatabaseId) {
329                Command cmd = ServerGroupCommands.serverGroupAddClient(groupId, clientDatabaseId);
330                return executeAndReturnError(cmd);
331        }
332
333        /**
334         * Submits a complaint about the specified client.
335         * The length of the message is limited to 200 UTF-8 bytes and BB codes in it will be ignored.
336         *
337         * @param clientDBId
338         *              the database ID of the client
339         * @param message
340         *              the message of the complaint, may not contain BB codes
341         *
342         * @return a future to track the progress of this command
343         *
344         * @throws TS3CommandFailedException
345         *              if the execution of a command fails
346         * @querycommands 1
347         * @see Client#getDatabaseId()
348         * @see Complaint#getMessage()
349         */
350        public CommandFuture<Void> addComplaint(int clientDBId, String message) {
351                Command cmd = ComplaintCommands.complainAdd(clientDBId, message);
352                return executeAndReturnError(cmd);
353        }
354
355        /**
356         * Adds a specified permission to all server groups of the type specified by {@code type} on all virtual servers.
357         *
358         * @param type
359         *              the kind of server group this permission should be added to
360         * @param permName
361         *              the name of the permission to be granted
362         * @param value
363         *              the numeric value of the permission (or for boolean permissions: 1 = true, 0 = false)
364         * @param negated
365         *              if set to true, the lowest permission value will be selected instead of the highest
366         * @param skipped
367         *              if set to true, this permission will not be overridden by client or channel group permissions
368         *
369         * @return a future to track the progress of this command
370         *
371         * @throws TS3CommandFailedException
372         *              if the execution of a command fails
373         * @querycommands 1
374         * @see ServerGroupType
375         * @see Permission
376         */
377        public CommandFuture<Void> addPermissionToAllServerGroups(ServerGroupType type, String permName, int value, boolean negated, boolean skipped) {
378                Command cmd = PermissionCommands.serverGroupAutoAddPerm(type, permName, value, negated, skipped);
379                return executeAndReturnError(cmd);
380        }
381
382        /**
383         * Create a new privilege key that allows one client to join a server or channel group.
384         * <ul>
385         * <li>If {@code type} is set to {@linkplain PrivilegeKeyType#SERVER_GROUP SERVER_GROUP},
386         * {@code groupId} is used as a server group ID and {@code channelId} is ignored.</li>
387         * <li>If {@code type} is set to {@linkplain PrivilegeKeyType#CHANNEL_GROUP CHANNEL_GROUP},
388         * {@code groupId} is used as a channel group ID and {@code channelId} is used as the channel in which the group should be set.</li>
389         * </ul>
390         *
391         * @param type
392         *              the type of token that should be created
393         * @param groupId
394         *              the ID of the server or channel group
395         * @param channelId
396         *              the ID of the channel, in case the token is channel group token
397         * @param description
398         *              the description for the token, can be null
399         *
400         * @return the created token for a client to use
401         *
402         * @throws TS3CommandFailedException
403         *              if the execution of a command fails
404         * @querycommands 1
405         * @see PrivilegeKeyType
406         * @see #addPrivilegeKeyServerGroup(int, String)
407         * @see #addPrivilegeKeyChannelGroup(int, int, String)
408         */
409        public CommandFuture<String> addPrivilegeKey(PrivilegeKeyType type, int groupId, int channelId, String description) {
410                Command cmd = PrivilegeKeyCommands.privilegeKeyAdd(type, groupId, channelId, description);
411                return executeAndReturnStringProperty(cmd, "token");
412        }
413
414        /**
415         * Creates a new privilege key for a channel group.
416         *
417         * @param channelGroupId
418         *              the ID of the channel group
419         * @param channelId
420         *              the ID of the channel in which the channel group should be set
421         * @param description
422         *              the description for the token, can be null
423         *
424         * @return the created token for a client to use
425         *
426         * @throws TS3CommandFailedException
427         *              if the execution of a command fails
428         * @querycommands 1
429         * @see ChannelGroup#getId()
430         * @see Channel#getId()
431         * @see #addPrivilegeKey(PrivilegeKeyType, int, int, String)
432         * @see #addPrivilegeKeyServerGroup(int, String)
433         */
434        public CommandFuture<String> addPrivilegeKeyChannelGroup(int channelGroupId, int channelId, String description) {
435                return addPrivilegeKey(PrivilegeKeyType.CHANNEL_GROUP, channelGroupId, channelId, description);
436        }
437
438        /**
439         * Creates a new privilege key for a server group.
440         *
441         * @param serverGroupId
442         *              the ID of the server group
443         * @param description
444         *              the description for the token, can be null
445         *
446         * @return the created token for a client to use
447         *
448         * @throws TS3CommandFailedException
449         *              if the execution of a command fails
450         * @querycommands 1
451         * @see ServerGroup#getId()
452         * @see #addPrivilegeKey(PrivilegeKeyType, int, int, String)
453         * @see #addPrivilegeKeyChannelGroup(int, int, String)
454         */
455        public CommandFuture<String> addPrivilegeKeyServerGroup(int serverGroupId, String description) {
456                return addPrivilegeKey(PrivilegeKeyType.SERVER_GROUP, serverGroupId, 0, description);
457        }
458
459        /**
460         * Creates a new server group for clients using a given name and returns its ID.
461         * <p>
462         * To create server group templates or ones for server queries,
463         * use {@link #addServerGroup(String, PermissionGroupDatabaseType)}.
464         * </p>
465         *
466         * @param name
467         *              the name of the new server group
468         *
469         * @return the ID of the newly created server group
470         *
471         * @throws TS3CommandFailedException
472         *              if the execution of a command fails
473         * @querycommands 1
474         * @see ServerGroup
475         */
476        public CommandFuture<Integer> addServerGroup(String name) {
477                return addServerGroup(name, PermissionGroupDatabaseType.REGULAR);
478        }
479
480        /**
481         * Creates a new server group using a given name and returns its ID.
482         *
483         * @param name
484         *              the name of the new server group
485         * @param type
486         *              the desired type of server group
487         *
488         * @return the ID of the newly created server group
489         *
490         * @throws TS3CommandFailedException
491         *              if the execution of a command fails
492         * @querycommands 1
493         * @see ServerGroup
494         * @see PermissionGroupDatabaseType
495         */
496        public CommandFuture<Integer> addServerGroup(String name, PermissionGroupDatabaseType type) {
497                Command cmd = ServerGroupCommands.serverGroupAdd(name, type);
498                return executeAndReturnIntProperty(cmd, "sgid");
499        }
500
501        /**
502         * Adds a specified permission to a server group.
503         *
504         * @param groupId
505         *              the ID of the channel group to which the permission should be added
506         * @param permName
507         *              the name of the permission to add
508         * @param value
509         *              the numeric value of the permission (or for boolean permissions: 1 = true, 0 = false)
510         * @param negated
511         *              if set to true, the lowest permission value will be selected instead of the highest
512         * @param skipped
513         *              if set to true, this permission will not be overridden by client or channel group permissions
514         *
515         * @return a future to track the progress of this command
516         *
517         * @throws TS3CommandFailedException
518         *              if the execution of a command fails
519         * @querycommands 1
520         * @see ServerGroup#getId()
521         * @see Permission
522         */
523        public CommandFuture<Void> addServerGroupPermission(int groupId, String permName, int value, boolean negated, boolean skipped) {
524                Command cmd = PermissionCommands.serverGroupAddPerm(groupId, permName, value, negated, skipped);
525                return executeAndReturnError(cmd);
526        }
527
528        /**
529         * Creates a server query login with name {@code loginName} for the client specified by {@code clientDBId}
530         * on the currently selected virtual server and returns the password of the created login.
531         * If the client already had a server query login, the existing login will be deleted and replaced.
532         * <p>
533         * Moreover, this method can be used to create new <i>global</i> server query logins that are not tied to any
534         * particular virtual server or client. To create such a server query login, make sure no virtual server is
535         * selected (e.g. use {@code selectVirtualServerById(0)}) and call this method with {@code clientDBId = 0}.
536         * </p>
537         *
538         * @param loginName
539         *              the name of the server query login to add
540         * @param clientDBId
541         *              the database ID of the client for which a server query login should be created
542         *
543         * @return an object containing the password of the new server query login
544         *
545         * @throws TS3CommandFailedException
546         *              if the execution of a command fails
547         * @querycommands 1
548         * @see #deleteServerQueryLogin(int)
549         * @see #getServerQueryLogins()
550         * @see #updateServerQueryLogin(String)
551         */
552        public CommandFuture<CreatedQueryLogin> addServerQueryLogin(String loginName, int clientDBId) {
553                Command cmd = QueryLoginCommands.queryLoginAdd(loginName, clientDBId);
554                return executeAndTransformFirst(cmd, CreatedQueryLogin::new);
555        }
556
557        /**
558         * Adds one or more {@link TS3Listener}s to the event manager of the query.
559         * These listeners will be notified when the TS3 server fires an event.
560         * <p>
561         * Note that for the TS3 server to fire events, you must first also register
562         * the event types you want to listen to.
563         * </p>
564         *
565         * @param listeners
566         *              one or more listeners to register
567         *
568         * @see #registerAllEvents()
569         * @see #registerEvent(TS3EventType, int)
570         * @see TS3Listener
571         * @see TS3EventType
572         */
573        public void addTS3Listeners(TS3Listener... listeners) {
574                query.getEventManager().addListeners(listeners);
575        }
576
577        /**
578         * Bans a client with a given client ID for a given time.
579         * <p>
580         * Please note that this will create up to three separate ban rules,
581         * one for the targeted client's IP address, one for their unique identifier,
582         * and potentially one more entry for their "myTeamSpeak" ID, if available.
583         * </p><p>
584         * <i>Exception:</i> If the banned client connects via a loopback address
585         * (i.e. {@code 127.0.0.1} or {@code localhost}), no IP ban is created.
586         * </p>
587         *
588         * @param clientId
589         *              the ID of the client
590         * @param timeInSeconds
591         *              the duration of the ban in seconds. 0 equals a permanent ban
592         *
593         * @return an array containing the IDs of the created ban entries
594         *
595         * @throws TS3CommandFailedException
596         *              if the execution of a command fails
597         * @querycommands 1
598         * @see Client#getId()
599         * @see #addBan(String, String, String, long, String)
600         */
601        public CommandFuture<int[]> banClient(int clientId, long timeInSeconds) {
602                return banClient(clientId, timeInSeconds, null);
603        }
604
605        /**
606         * Bans a client with a given client ID for a given time for the specified reason.
607         * <p>
608         * Please note that this will create up to three separate ban rules,
609         * one for the targeted client's IP address, one for their unique identifier,
610         * and potentially one more entry for their "myTeamSpeak" ID, if available.
611         * </p><p>
612         * <i>Exception:</i> If the banned client connects via a loopback address
613         * (i.e. {@code 127.0.0.1} or {@code localhost}), no IP ban is created.
614         * </p>
615         *
616         * @param clientId
617         *              the ID of the client
618         * @param timeInSeconds
619         *              the duration of the ban in seconds. 0 equals a permanent ban
620         * @param reason
621         *              the reason for the ban, can be null
622         *
623         * @return an array containing the IDs of the created ban entries
624         *
625         * @throws TS3CommandFailedException
626         *              if the execution of a command fails
627         * @querycommands 1
628         * @see Client#getId()
629         * @see #addBan(String, String, String, long, String)
630         */
631        public CommandFuture<int[]> banClient(int clientId, long timeInSeconds, String reason) {
632                Command cmd = BanCommands.banClient(new int[] {clientId}, timeInSeconds, reason, false);
633                return executeAndReturnIntArray(cmd, "banid");
634        }
635
636        /**
637         * Bans a client with a given client ID permanently for the specified reason.
638         * <p>
639         * Please note that this will create up to three separate ban rules,
640         * one for the targeted client's IP address, one for their unique identifier,
641         * and potentially one more entry for their "myTeamSpeak" ID, if available.
642         * </p><p>
643         * <i>Exception:</i> If the banned client connects via a loopback address
644         * (i.e. {@code 127.0.0.1} or {@code localhost}), no IP ban is created.
645         * </p>
646         *
647         * @param clientId
648         *              the ID of the client
649         * @param reason
650         *              the reason for the ban, can be null
651         *
652         * @return an array containing the IDs of the created ban entries
653         *
654         * @throws TS3CommandFailedException
655         *              if the execution of a command fails
656         * @querycommands 1
657         * @see Client#getId()
658         * @see #addBan(String, String, String, long, String)
659         */
660        public CommandFuture<int[]> banClient(int clientId, String reason) {
661                return banClient(clientId, 0, reason);
662        }
663
664        /**
665         * Bans multiple clients by their client ID for a given time for the specified reason.
666         * <p>
667         * Please note that this will create up to three separate ban rules for each client,
668         * one for the targeted client's IP address, one for their unique identifier,
669         * and potentially one more entry for their "myTeamSpeak" ID, if available.
670         * </p><p>
671         * <i>Exception:</i> If the banned client connects via a loopback address
672         * (i.e. {@code 127.0.0.1} or {@code localhost}), no IP ban is created.
673         * </p><p>
674         * <i>Exception:</i> If two or more clients are connecting from the
675         * same IP address, only one IP ban entry for that IP will be created.
676         * </p>
677         *
678         * @param clientIds
679         *              the IDs of the clients to be banned
680         * @param timeInSeconds
681         *              the duration of the ban in seconds. 0 equals a permanent ban
682         * @param reason
683         *              the reason for the ban, can be null
684         * @param continueOnError
685         *              if true, continue to the next client if banning one client fails, else do not create any bans on error
686         *
687         * @return an array containing the IDs of the created ban entries
688         *
689         * @throws TS3CommandFailedException
690         *              if the execution of a command fails
691         * @querycommands 1
692         * @see Client#getId()
693         * @see #addBan(String, String, String, long, String)
694         */
695        public CommandFuture<int[]> banClients(int[] clientIds, long timeInSeconds, String reason, boolean continueOnError) {
696                if (clientIds == null) throw new IllegalArgumentException("Client ID array was null");
697                if (clientIds.length == 0) return CommandFuture.immediate(new int[0]); // Success
698
699                Command cmd = BanCommands.banClient(clientIds, timeInSeconds, reason, continueOnError);
700                return executeAndReturnIntArray(cmd, "banid");
701        }
702
703        /**
704         * Sends a text message to all clients on all virtual servers.
705         * These messages will appear to clients in the tab for server messages.
706         *
707         * @param message
708         *              the message to be sent
709         *
710         * @return a future to track the progress of this command
711         *
712         * @throws TS3CommandFailedException
713         *              if the execution of a command fails
714         * @querycommands 1
715         */
716        public CommandFuture<Void> broadcast(String message) {
717                Command cmd = ServerCommands.gm(message);
718                return executeAndReturnError(cmd);
719        }
720
721        /**
722         * Creates a copy of the channel group specified by {@code sourceGroupId},
723         * overwriting any other channel group specified by {@code targetGroupId}.
724         * <p>
725         * The parameter {@code type} can be used to create server query and template groups.
726         * </p>
727         *
728         * @param sourceGroupId
729         *              the ID of the channel group to copy
730         * @param targetGroupId
731         *              the ID of another channel group to overwrite
732         * @param type
733         *              the desired type of channel group
734         *
735         * @return a future to track the progress of this command
736         *
737         * @throws TS3CommandFailedException
738         *              if the execution of a command fails
739         * @querycommands 1
740         * @see ChannelGroup#getId()
741         */
742        public CommandFuture<Void> copyChannelGroup(int sourceGroupId, int targetGroupId, PermissionGroupDatabaseType type) {
743                if (targetGroupId <= 0) {
744                        throw new IllegalArgumentException("To create a new channel group, use the method with a String argument");
745                }
746
747                Command cmd = ChannelGroupCommands.channelGroupCopy(sourceGroupId, targetGroupId, type);
748                return executeAndReturnError(cmd);
749        }
750
751        /**
752         * Creates a copy of the channel group specified by {@code sourceGroupId} with a given name
753         * and returns the ID of the newly created channel group.
754         *
755         * @param sourceGroupId
756         *              the ID of the channel group to copy
757         * @param targetName
758         *              the name for the copy of the channel group
759         * @param type
760         *              the desired type of channel group
761         *
762         * @return the ID of the newly created channel group
763         *
764         * @throws TS3CommandFailedException
765         *              if the execution of a command fails
766         * @querycommands 1
767         * @see ChannelGroup#getId()
768         */
769        public CommandFuture<Integer> copyChannelGroup(int sourceGroupId, String targetName, PermissionGroupDatabaseType type) {
770                Command cmd = ChannelGroupCommands.channelGroupCopy(sourceGroupId, targetName, type);
771                return executeAndReturnIntProperty(cmd, "cgid");
772        }
773
774        /**
775         * Creates a copy of the server group specified by {@code sourceGroupId},
776         * overwriting another server group specified by {@code targetGroupId}.
777         * <p>
778         * The parameter {@code type} can be used to create server query and template groups.
779         * </p>
780         *
781         * @param sourceGroupId
782         *              the ID of the server group to copy
783         * @param targetGroupId
784         *              the ID of another server group to overwrite
785         * @param type
786         *              the desired type of server group
787         *
788         * @return the ID of the newly created server group
789         *
790         * @throws TS3CommandFailedException
791         *              if the execution of a command fails
792         * @querycommands 1
793         * @see ServerGroup#getId()
794         */
795        public CommandFuture<Integer> copyServerGroup(int sourceGroupId, int targetGroupId, PermissionGroupDatabaseType type) {
796                if (targetGroupId <= 0) {
797                        throw new IllegalArgumentException("To create a new server group, use the method with a String argument");
798                }
799
800                Command cmd = ServerGroupCommands.serverGroupCopy(sourceGroupId, targetGroupId, type);
801                return executeAndReturnIntProperty(cmd, "sgid");
802        }
803
804        /**
805         * Creates a copy of the server group specified by {@code sourceGroupId} with a given name
806         * and returns the ID of the newly created server group.
807         *
808         * @param sourceGroupId
809         *              the ID of the server group to copy
810         * @param targetName
811         *              the name for the copy of the server group
812         * @param type
813         *              the desired type of server group
814         *
815         * @return the ID of the newly created server group
816         *
817         * @throws TS3CommandFailedException
818         *              if the execution of a command fails
819         * @querycommands 1
820         * @see ServerGroup#getId()
821         */
822        public CommandFuture<Integer> copyServerGroup(int sourceGroupId, String targetName, PermissionGroupDatabaseType type) {
823                Command cmd = ServerGroupCommands.serverGroupCopy(sourceGroupId, targetName, type);
824                return executeAndReturnIntProperty(cmd, "sgid");
825        }
826
827        /**
828         * Creates a new channel with a given name using the given properties and returns its ID.
829         *
830         * @param name
831         *              the name for the new channel
832         * @param options
833         *              a map of options that should be set for the channel
834         *
835         * @return the ID of the newly created channel
836         *
837         * @throws TS3CommandFailedException
838         *              if the execution of a command fails
839         * @querycommands 1
840         * @see Channel
841         */
842        public CommandFuture<Integer> createChannel(String name, Map<ChannelProperty, String> options) {
843                Command cmd = ChannelCommands.channelCreate(name, options);
844                return executeAndReturnIntProperty(cmd, "cid");
845        }
846
847        /**
848         * Creates a new directory on the file repository in the specified channel.
849         *
850         * @param directoryPath
851         *              the path to the directory that should be created
852         * @param channelId
853         *              the ID of the channel the directory should be created in
854         *
855         * @return a future to track the progress of this command
856         *
857         * @throws TS3CommandFailedException
858         *              if the execution of a command fails
859         * @querycommands 1
860         * @see FileInfo#getPath()
861         * @see Channel#getId()
862         */
863        public CommandFuture<Void> createFileDirectory(String directoryPath, int channelId) {
864                return createFileDirectory(directoryPath, channelId, null);
865        }
866
867        /**
868         * Creates a new directory on the file repository in the specified channel.
869         *
870         * @param directoryPath
871         *              the path to the directory that should be created
872         * @param channelId
873         *              the ID of the channel the directory should be created in
874         * @param channelPassword
875         *              the password of that channel
876         *
877         * @return a future to track the progress of this command
878         *
879         * @throws TS3CommandFailedException
880         *              if the execution of a command fails
881         * @querycommands 1
882         * @see FileInfo#getPath()
883         * @see Channel#getId()
884         */
885        public CommandFuture<Void> createFileDirectory(String directoryPath, int channelId, String channelPassword) {
886                Command cmd = FileCommands.ftCreateDir(directoryPath, channelId, channelPassword);
887                return executeAndReturnError(cmd);
888        }
889
890        /**
891         * Creates a new virtual server with the given name and returns an object containing the ID of the newly
892         * created virtual server, the default server admin token and the virtual server's voice port. Usually,
893         * the virtual server is also automatically started. This can be turned off on the TS3 server, though.
894         * <p>
895         * If {@link VirtualServerProperty#VIRTUALSERVER_PORT} is not specified in the virtual server properties,
896         * the server will test for the first unused UDP port.
897         * </p><p>
898         * Please also note that creating virtual servers usually requires the server query admin account
899         * and that there is a limit to how many virtual servers can be created, which is dependent on your license.
900         * Unlicensed TS3 server instances are limited to 1 virtual server with up to 32 client slots.
901         * </p>
902         *
903         * @param name
904         *              the name for the new virtual server
905         * @param options
906         *              a map of options that should be set for the virtual server
907         *
908         * @return information about the newly created virtual server
909         *
910         * @throws TS3CommandFailedException
911         *              if the execution of a command fails
912         * @querycommands 1
913         * @see VirtualServer
914         */
915        public CommandFuture<CreatedVirtualServer> createServer(String name, Map<VirtualServerProperty, String> options) {
916                Command cmd = VirtualServerCommands.serverCreate(name, options);
917                return executeAndTransformFirst(cmd, CreatedVirtualServer::new);
918        }
919
920        /**
921         * Creates a {@link Snapshot} of the selected virtual server containing all settings,
922         * groups and known client identities. The data from a server snapshot can be
923         * used to restore a virtual servers configuration.
924         *
925         * @return a snapshot of the virtual server
926         *
927         * @throws TS3CommandFailedException
928         *              if the execution of a command fails
929         * @querycommands 1
930         * @see #deployServerSnapshot(Snapshot)
931         */
932        public CommandFuture<Snapshot> createServerSnapshot() {
933                Command cmd = VirtualServerCommands.serverSnapshotCreate();
934                CommandFuture<Snapshot> future = cmd.getFuture()
935                                .map(result -> new Snapshot(result.getRawResponse()));
936
937                commandQueue.enqueueCommand(cmd);
938                return future;
939        }
940
941        /**
942         * Deletes all active ban rules from the server. Use with caution.
943         *
944         * @return a future to track the progress of this command
945         *
946         * @throws TS3CommandFailedException
947         *              if the execution of a command fails
948         * @querycommands 1
949         */
950        public CommandFuture<Void> deleteAllBans() {
951                Command cmd = BanCommands.banDelAll();
952                return executeAndReturnError(cmd);
953        }
954
955        /**
956         * Deletes all complaints about the client with specified database ID from the server.
957         *
958         * @param clientDBId
959         *              the database ID of the client
960         *
961         * @return a future to track the progress of this command
962         *
963         * @throws TS3CommandFailedException
964         *              if the execution of a command fails
965         * @querycommands 1
966         * @see Client#getDatabaseId()
967         * @see Complaint
968         */
969        public CommandFuture<Void> deleteAllComplaints(int clientDBId) {
970                Command cmd = ComplaintCommands.complainDelAll(clientDBId);
971                return executeAndReturnError(cmd);
972        }
973
974        /**
975         * Deletes the ban rule with the specified ID from the server.
976         *
977         * @param banId
978         *              the ID of the ban to delete
979         *
980         * @return a future to track the progress of this command
981         *
982         * @throws TS3CommandFailedException
983         *              if the execution of a command fails
984         * @querycommands 1
985         * @see Ban#getId()
986         */
987        public CommandFuture<Void> deleteBan(int banId) {
988                Command cmd = BanCommands.banDel(banId);
989                return executeAndReturnError(cmd);
990        }
991
992        /**
993         * Deletes an existing channel specified by its ID, kicking all clients out of the channel.
994         *
995         * @param channelId
996         *              the ID of the channel to delete
997         *
998         * @return a future to track the progress of this command
999         *
1000         * @throws TS3CommandFailedException
1001         *              if the execution of a command fails
1002         * @querycommands 1
1003         * @see Channel#getId()
1004         * @see #deleteChannel(int, boolean)
1005         * @see #kickClientFromChannel(String, int...)
1006         */
1007        public CommandFuture<Void> deleteChannel(int channelId) {
1008                return deleteChannel(channelId, true);
1009        }
1010
1011        /**
1012         * Deletes an existing channel with a given ID.
1013         * If {@code force} is true, the channel will be deleted even if there are clients within,
1014         * else the command will fail in this situation.
1015         *
1016         * @param channelId
1017         *              the ID of the channel to delete
1018         * @param force
1019         *              whether clients should be kicked out of the channel
1020         *
1021         * @return a future to track the progress of this command
1022         *
1023         * @throws TS3CommandFailedException
1024         *              if the execution of a command fails
1025         * @querycommands 1
1026         * @see Channel#getId()
1027         * @see #kickClientFromChannel(String, int...)
1028         */
1029        public CommandFuture<Void> deleteChannel(int channelId, boolean force) {
1030                Command cmd = ChannelCommands.channelDelete(channelId, force);
1031                return executeAndReturnError(cmd);
1032        }
1033
1034        /**
1035         * Removes a specified permission from a client in a specific channel.
1036         *
1037         * @param channelId
1038         *              the ID of the channel wherein the permission should be removed
1039         * @param clientDBId
1040         *              the database ID of the client
1041         * @param permName
1042         *              the name of the permission to revoke
1043         *
1044         * @return a future to track the progress of this command
1045         *
1046         * @throws TS3CommandFailedException
1047         *              if the execution of a command fails
1048         * @querycommands 1
1049         * @see Channel#getId()
1050         * @see Client#getDatabaseId()
1051         * @see Permission#getName()
1052         */
1053        public CommandFuture<Void> deleteChannelClientPermission(int channelId, int clientDBId, String permName) {
1054                Command cmd = PermissionCommands.channelClientDelPerm(channelId, clientDBId, permName);
1055                return executeAndReturnError(cmd);
1056        }
1057
1058        /**
1059         * Removes the channel group with the given ID.
1060         *
1061         * @param groupId
1062         *              the ID of the channel group
1063         *
1064         * @return a future to track the progress of this command
1065         *
1066         * @throws TS3CommandFailedException
1067         *              if the execution of a command fails
1068         * @querycommands 1
1069         * @see ChannelGroup#getId()
1070         */
1071        public CommandFuture<Void> deleteChannelGroup(int groupId) {
1072                return deleteChannelGroup(groupId, true);
1073        }
1074
1075        /**
1076         * Removes the channel group with the given ID.
1077         * If {@code force} is true, the channel group will be deleted even if it still contains clients,
1078         * else the command will fail in this situation.
1079         *
1080         * @param groupId
1081         *              the ID of the channel group
1082         * @param force
1083         *              whether the channel group should be deleted even if it still contains clients
1084         *
1085         * @return a future to track the progress of this command
1086         *
1087         * @throws TS3CommandFailedException
1088         *              if the execution of a command fails
1089         * @querycommands 1
1090         * @see ChannelGroup#getId()
1091         */
1092        public CommandFuture<Void> deleteChannelGroup(int groupId, boolean force) {
1093                Command cmd = ChannelGroupCommands.channelGroupDel(groupId, force);
1094                return executeAndReturnError(cmd);
1095        }
1096
1097        /**
1098         * Removes a permission from the channel group with the given ID.
1099         *
1100         * @param groupId
1101         *              the ID of the channel group
1102         * @param permName
1103         *              the name of the permission to revoke
1104         *
1105         * @return a future to track the progress of this command
1106         *
1107         * @throws TS3CommandFailedException
1108         *              if the execution of a command fails
1109         * @querycommands 1
1110         * @see ChannelGroup#getId()
1111         * @see Permission#getName()
1112         */
1113        public CommandFuture<Void> deleteChannelGroupPermission(int groupId, String permName) {
1114                Command cmd = PermissionCommands.channelGroupDelPerm(groupId, permName);
1115                return executeAndReturnError(cmd);
1116        }
1117
1118        /**
1119         * Removes a permission from the channel with the given ID.
1120         *
1121         * @param channelId
1122         *              the ID of the channel
1123         * @param permName
1124         *              the name of the permission to revoke
1125         *
1126         * @return a future to track the progress of this command
1127         *
1128         * @throws TS3CommandFailedException
1129         *              if the execution of a command fails
1130         * @querycommands 1
1131         * @see Channel#getId()
1132         * @see Permission#getName()
1133         */
1134        public CommandFuture<Void> deleteChannelPermission(int channelId, String permName) {
1135                Command cmd = PermissionCommands.channelDelPerm(channelId, permName);
1136                return executeAndReturnError(cmd);
1137        }
1138
1139        /**
1140         * Removes a permission from a client.
1141         *
1142         * @param clientDBId
1143         *              the database ID of the client
1144         * @param permName
1145         *              the name of the permission to revoke
1146         *
1147         * @return a future to track the progress of this command
1148         *
1149         * @throws TS3CommandFailedException
1150         *              if the execution of a command fails
1151         * @querycommands 1
1152         * @see Client#getDatabaseId()
1153         * @see Permission#getName()
1154         */
1155        public CommandFuture<Void> deleteClientPermission(int clientDBId, String permName) {
1156                Command cmd = PermissionCommands.clientDelPerm(clientDBId, permName);
1157                return executeAndReturnError(cmd);
1158        }
1159
1160        /**
1161         * Deletes the complaint about the client with database ID {@code targetClientDBId} submitted by
1162         * the client with database ID {@code fromClientDBId} from the server.
1163         *
1164         * @param targetClientDBId
1165         *              the database ID of the client the complaint is about
1166         * @param fromClientDBId
1167         *              the database ID of the client who added the complaint
1168         *
1169         * @return a future to track the progress of this command
1170         *
1171         * @throws TS3CommandFailedException
1172         *              if the execution of a command fails
1173         * @querycommands 1
1174         * @see Complaint
1175         * @see Client#getDatabaseId()
1176         */
1177        public CommandFuture<Void> deleteComplaint(int targetClientDBId, int fromClientDBId) {
1178                Command cmd = ComplaintCommands.complainDel(targetClientDBId, fromClientDBId);
1179                return executeAndReturnError(cmd);
1180        }
1181
1182        /**
1183         * Removes the {@code key} custom client property from a client.
1184         *
1185         * @param clientDBId
1186         *              the database ID of the target client
1187         * @param key
1188         *              the key of the custom property to delete, cannot be {@code null}
1189         *
1190         * @return a future to track the progress of this command
1191         *
1192         * @throws TS3CommandFailedException
1193         *              if the execution of a command fails
1194         * @querycommands 1
1195         * @see Client#getDatabaseId()
1196         */
1197        public CommandFuture<Void> deleteCustomClientProperty(int clientDBId, String key) {
1198                if (key == null) throw new IllegalArgumentException("Key cannot be null");
1199
1200                Command cmd = CustomPropertyCommands.customDelete(clientDBId, key);
1201                return executeAndReturnError(cmd);
1202        }
1203
1204        /**
1205         * Removes all stored database information about the specified client.
1206         * Please note that this data is also automatically removed after a configured time (usually 90 days).
1207         * <p>
1208         * See {@link DatabaseClientInfo} for a list of stored information about a client.
1209         * </p>
1210         *
1211         * @param clientDBId
1212         *              the database ID of the client
1213         *
1214         * @return a future to track the progress of this command
1215         *
1216         * @throws TS3CommandFailedException
1217         *              if the execution of a command fails
1218         * @querycommands 1
1219         * @see Client#getDatabaseId()
1220         * @see #getDatabaseClientInfo(int)
1221         * @see DatabaseClientInfo
1222         */
1223        public CommandFuture<Void> deleteDatabaseClientProperties(int clientDBId) {
1224                Command cmd = DatabaseClientCommands.clientDBDelete(clientDBId);
1225                return executeAndReturnError(cmd);
1226        }
1227
1228        /**
1229         * Deletes a file or directory from the file repository in the specified channel.
1230         *
1231         * @param filePath
1232         *              the path to the file or directory
1233         * @param channelId
1234         *              the ID of the channel the file or directory resides in
1235         *
1236         * @return a future to track the progress of this command
1237         *
1238         * @throws TS3CommandFailedException
1239         *              if the execution of a command fails
1240         * @querycommands 1
1241         * @see FileInfo#getPath()
1242         * @see Channel#getId()
1243         */
1244        public CommandFuture<Void> deleteFile(String filePath, int channelId) {
1245                return deleteFile(filePath, channelId, null);
1246        }
1247
1248        /**
1249         * Deletes a file or directory from the file repository in the specified channel.
1250         *
1251         * @param filePath
1252         *              the path to the file or directory
1253         * @param channelId
1254         *              the ID of the channel the file or directory resides in
1255         * @param channelPassword
1256         *              the password of that channel
1257         *
1258         * @return a future to track the progress of this command
1259         *
1260         * @throws TS3CommandFailedException
1261         *              if the execution of a command fails
1262         * @querycommands 1
1263         * @see FileInfo#getPath()
1264         * @see Channel#getId()
1265         */
1266        public CommandFuture<Void> deleteFile(String filePath, int channelId, String channelPassword) {
1267                Command cmd = FileCommands.ftDeleteFile(channelId, channelPassword, filePath);
1268                return executeAndReturnError(cmd);
1269        }
1270
1271        /**
1272         * Deletes multiple files or directories from the file repository in the specified channel.
1273         *
1274         * @param filePaths
1275         *              the paths to the files or directories
1276         * @param channelId
1277         *              the ID of the channel the file or directory resides in
1278         *
1279         * @return a future to track the progress of this command
1280         *
1281         * @throws TS3CommandFailedException
1282         *              if the execution of a command fails
1283         * @querycommands 1
1284         * @see FileInfo#getPath()
1285         * @see Channel#getId()
1286         */
1287        public CommandFuture<Void> deleteFiles(String[] filePaths, int channelId) {
1288                return deleteFiles(filePaths, channelId, null);
1289        }
1290
1291        /**
1292         * Deletes multiple files or directories from the file repository in the specified channel.
1293         *
1294         * @param filePaths
1295         *              the paths to the files or directories
1296         * @param channelId
1297         *              the ID of the channel the file or directory resides in
1298         * @param channelPassword
1299         *              the password of that channel
1300         *
1301         * @return a future to track the progress of this command
1302         *
1303         * @throws TS3CommandFailedException
1304         *              if the execution of a command fails
1305         * @querycommands 1
1306         * @see FileInfo#getPath()
1307         * @see Channel#getId()
1308         */
1309        public CommandFuture<Void> deleteFiles(String[] filePaths, int channelId, String channelPassword) {
1310                Command cmd = FileCommands.ftDeleteFile(channelId, channelPassword, filePaths);
1311                return executeAndReturnError(cmd);
1312        }
1313
1314        /**
1315         * Deletes an icon from the icon directory in the file repository.
1316         *
1317         * @param iconId
1318         *              the ID of the icon to delete
1319         *
1320         * @return a future to track the progress of this command
1321         *
1322         * @throws TS3CommandFailedException
1323         *              if the execution of a command fails
1324         * @querycommands 1
1325         * @see IconFile#getIconId()
1326         */
1327        public CommandFuture<Void> deleteIcon(long iconId) {
1328                String iconPath = "/icon_" + iconId;
1329                return deleteFile(iconPath, 0);
1330        }
1331
1332        /**
1333         * Deletes multiple icons from the icon directory in the file repository.
1334         *
1335         * @param iconIds
1336         *              the IDs of the icons to delete
1337         *
1338         * @return a future to track the progress of this command
1339         *
1340         * @throws TS3CommandFailedException
1341         *              if the execution of a command fails
1342         * @querycommands 1
1343         * @see IconFile#getIconId()
1344         */
1345        public CommandFuture<Void> deleteIcons(long... iconIds) {
1346                String[] iconPaths = new String[iconIds.length];
1347                for (int i = 0; i < iconIds.length; ++i) {
1348                        iconPaths[i] = "/icon_" + iconIds[i];
1349                }
1350                return deleteFiles(iconPaths, 0);
1351        }
1352
1353        /**
1354         * Deletes the offline message with the specified ID.
1355         *
1356         * @param messageId
1357         *              the ID of the offline message to delete
1358         *
1359         * @return a future to track the progress of this command
1360         *
1361         * @throws TS3CommandFailedException
1362         *              if the execution of a command fails
1363         * @querycommands 1
1364         * @see Message#getId()
1365         */
1366        public CommandFuture<Void> deleteOfflineMessage(int messageId) {
1367                Command cmd = MessageCommands.messageDel(messageId);
1368                return executeAndReturnError(cmd);
1369        }
1370
1371        /**
1372         * Removes a specified permission from all server groups of the type specified by {@code type} on all virtual servers.
1373         *
1374         * @param type
1375         *              the kind of server group this permission should be removed from
1376         * @param permName
1377         *              the name of the permission to remove
1378         *
1379         * @return a future to track the progress of this command
1380         *
1381         * @throws TS3CommandFailedException
1382         *              if the execution of a command fails
1383         * @querycommands 1
1384         * @see ServerGroupType
1385         * @see Permission#getName()
1386         */
1387        public CommandFuture<Void> deletePermissionFromAllServerGroups(ServerGroupType type, String permName) {
1388                Command cmd = PermissionCommands.serverGroupAutoDelPerm(type, permName);
1389                return executeAndReturnError(cmd);
1390        }
1391
1392        /**
1393         * Deletes the privilege key with the given token.
1394         *
1395         * @param token
1396         *              the token of the privilege key
1397         *
1398         * @return a future to track the progress of this command
1399         *
1400         * @throws TS3CommandFailedException
1401         *              if the execution of a command fails
1402         * @querycommands 1
1403         * @see PrivilegeKey
1404         */
1405        public CommandFuture<Void> deletePrivilegeKey(String token) {
1406                Command cmd = PrivilegeKeyCommands.privilegeKeyDelete(token);
1407                return executeAndReturnError(cmd);
1408        }
1409
1410        /**
1411         * Deletes the virtual server with the specified ID.
1412         * <p>
1413         * Only stopped virtual servers can be deleted.
1414         * </p>
1415         *
1416         * @param serverId
1417         *              the ID of the virtual server
1418         *
1419         * @return a future to track the progress of this command
1420         *
1421         * @throws TS3CommandFailedException
1422         *              if the execution of a command fails
1423         * @querycommands 1
1424         * @see VirtualServer#getId()
1425         * @see #stopServer(int)
1426         */
1427        public CommandFuture<Void> deleteServer(int serverId) {
1428                Command cmd = VirtualServerCommands.serverDelete(serverId);
1429                return executeAndReturnError(cmd);
1430        }
1431
1432        /**
1433         * Deletes the server group with the specified ID, even if the server group still contains clients.
1434         *
1435         * @param groupId
1436         *              the ID of the server group
1437         *
1438         * @return a future to track the progress of this command
1439         *
1440         * @throws TS3CommandFailedException
1441         *              if the execution of a command fails
1442         * @querycommands 1
1443         * @see ServerGroup#getId()
1444         */
1445        public CommandFuture<Void> deleteServerGroup(int groupId) {
1446                return deleteServerGroup(groupId, true);
1447        }
1448
1449        /**
1450         * Deletes a server group with the specified ID.
1451         * <p>
1452         * If {@code force} is true, the server group will be deleted even if it contains clients,
1453         * else the command will fail in this situation.
1454         * </p>
1455         *
1456         * @param groupId
1457         *              the ID of the server group
1458         * @param force
1459         *              whether the server group should be deleted if it still contains clients
1460         *
1461         * @return a future to track the progress of this command
1462         *
1463         * @throws TS3CommandFailedException
1464         *              if the execution of a command fails
1465         * @querycommands 1
1466         * @see ServerGroup#getId()
1467         */
1468        public CommandFuture<Void> deleteServerGroup(int groupId, boolean force) {
1469                Command cmd = ServerGroupCommands.serverGroupDel(groupId, force);
1470                return executeAndReturnError(cmd);
1471        }
1472
1473        /**
1474         * Removes a permission from the server group with the given ID.
1475         *
1476         * @param groupId
1477         *              the ID of the server group
1478         * @param permName
1479         *              the name of the permission to revoke
1480         *
1481         * @return a future to track the progress of this command
1482         *
1483         * @throws TS3CommandFailedException
1484         *              if the execution of a command fails
1485         * @querycommands 1
1486         * @see ServerGroup#getId()
1487         * @see Permission#getName()
1488         */
1489        public CommandFuture<Void> deleteServerGroupPermission(int groupId, String permName) {
1490                Command cmd = PermissionCommands.serverGroupDelPerm(groupId, permName);
1491                return executeAndReturnError(cmd);
1492        }
1493
1494        /**
1495         * Deletes the server query login with the specified client database ID.
1496         * <p>
1497         * If you only know the name of the server query login, use {@link #getServerQueryLoginsByName(String)} first.
1498         * </p>
1499         *
1500         * @param clientDBId
1501         *              the client database ID of the server query login (usually the ID of the associated client)
1502         *
1503         * @return a future to track the progress of this command
1504         *
1505         * @throws TS3CommandFailedException
1506         *              if the execution of a command fails
1507         * @querycommands 1
1508         * @see #addServerQueryLogin(String, int)
1509         * @see #getServerQueryLogins()
1510         * @see #updateServerQueryLogin(String)
1511         */
1512        public CommandFuture<Void> deleteServerQueryLogin(int clientDBId) {
1513                Command cmd = QueryLoginCommands.queryLoginDel(clientDBId);
1514                return executeAndReturnError(cmd);
1515        }
1516
1517        /**
1518         * Restores the selected virtual servers configuration using the data from a
1519         * previously created server snapshot.
1520         *
1521         * @param snapshot
1522         *              the snapshot to restore
1523         *
1524         * @return a future to track the progress of this command
1525         *
1526         * @throws TS3CommandFailedException
1527         *              if the execution of a command fails
1528         * @querycommands 1
1529         * @see #createServerSnapshot()
1530         */
1531        public CommandFuture<Void> deployServerSnapshot(Snapshot snapshot) {
1532                return deployServerSnapshot(snapshot.get());
1533        }
1534
1535        /**
1536         * Restores the configuration of the selected virtual server using the data from a
1537         * previously created server snapshot.
1538         *
1539         * @param snapshot
1540         *              the snapshot to restore
1541         *
1542         * @return a future to track the progress of this command
1543         *
1544         * @throws TS3CommandFailedException
1545         *              if the execution of a command fails
1546         * @querycommands 1
1547         * @see #createServerSnapshot()
1548         */
1549        public CommandFuture<Void> deployServerSnapshot(String snapshot) {
1550                Command cmd = VirtualServerCommands.serverSnapshotDeploy(snapshot);
1551                return executeAndReturnError(cmd);
1552        }
1553
1554        /**
1555         * Downloads a file from the file repository at a given path and channel
1556         * and writes the file's bytes to an open {@link OutputStream}.
1557         * <p>
1558         * It is the user's responsibility to ensure that the given {@code OutputStream} is
1559         * open and to close the stream again once the download has finished.
1560         * </p><p>
1561         * Note that this method will not read the entire file to memory and can thus
1562         * download arbitrarily sized files from the file repository.
1563         * </p>
1564         *
1565         * @param dataOut
1566         *              a stream that the downloaded data should be written to
1567         * @param filePath
1568         *              the path of the file on the file repository
1569         * @param channelId
1570         *              the ID of the channel to download the file from
1571         *
1572         * @return how many bytes were downloaded
1573         *
1574         * @throws TS3CommandFailedException
1575         *              if the execution of a command fails
1576         * @throws TS3FileTransferFailedException
1577         *              if the file transfer fails for any reason
1578         * @querycommands 1
1579         * @see FileInfo#getPath()
1580         * @see Channel#getId()
1581         * @see #downloadFileDirect(String, int)
1582         */
1583        public CommandFuture<Long> downloadFile(OutputStream dataOut, String filePath, int channelId) {
1584                return downloadFile(dataOut, filePath, channelId, null);
1585        }
1586
1587        /**
1588         * Downloads a file from the file repository at a given path and channel
1589         * and writes the file's bytes to an open {@link OutputStream}.
1590         * <p>
1591         * It is the user's responsibility to ensure that the given {@code OutputStream} is
1592         * open and to close the stream again once the download has finished.
1593         * </p><p>
1594         * Note that this method will not read the entire file to memory and can thus
1595         * download arbitrarily sized files from the file repository.
1596         * </p>
1597         *
1598         * @param dataOut
1599         *              a stream that the downloaded data should be written to
1600         * @param filePath
1601         *              the path of the file on the file repository
1602         * @param channelId
1603         *              the ID of the channel to download the file from
1604         * @param channelPassword
1605         *              that channel's password
1606         *
1607         * @return how many bytes were downloaded
1608         *
1609         * @throws TS3CommandFailedException
1610         *              if the execution of a command fails
1611         * @throws TS3FileTransferFailedException
1612         *              if the file transfer fails for any reason
1613         * @querycommands 1
1614         * @see FileInfo#getPath()
1615         * @see Channel#getId()
1616         * @see #downloadFileDirect(String, int, String)
1617         */
1618        public CommandFuture<Long> downloadFile(OutputStream dataOut, String filePath, int channelId, String channelPassword) {
1619                FileTransferHelper helper = query.getFileTransferHelper();
1620                int transferId = helper.getClientTransferId();
1621                Command cmd = FileCommands.ftInitDownload(transferId, filePath, channelId, channelPassword);
1622                CommandFuture<Long> future = new CommandFuture<>();
1623
1624                executeAndTransformFirst(cmd, FileTransferParameters::new).onSuccess(params -> {
1625                        QueryError error = params.getQueryError();
1626                        if (!error.isSuccessful()) {
1627                                future.fail(new TS3CommandFailedException(error, cmd.getName()));
1628                                return;
1629                        }
1630
1631                        try {
1632                                query.getFileTransferHelper().downloadFile(dataOut, params);
1633                        } catch (IOException e) {
1634                                future.fail(new TS3FileTransferFailedException("Download failed", e));
1635                                return;
1636                        }
1637                        future.set(params.getFileSize());
1638                }).forwardFailure(future);
1639
1640                return future;
1641        }
1642
1643        /**
1644         * Downloads a file from the file repository at a given path and channel
1645         * and returns the file's bytes as a byte array.
1646         * <p>
1647         * Note that this method <strong>will read the entire file to memory</strong>.
1648         * That means that if a file is larger than 2<sup>31</sup>-1 bytes in size,
1649         * the download will fail.
1650         * </p>
1651         *
1652         * @param filePath
1653         *              the path of the file on the file repository
1654         * @param channelId
1655         *              the ID of the channel to download the file from
1656         *
1657         * @return a byte array containing the file's data
1658         *
1659         * @throws TS3CommandFailedException
1660         *              if the execution of a command fails
1661         * @throws TS3FileTransferFailedException
1662         *              if the file transfer fails for any reason
1663         * @querycommands 1
1664         * @see FileInfo#getPath()
1665         * @see Channel#getId()
1666         * @see #downloadFile(OutputStream, String, int)
1667         */
1668        public CommandFuture<byte[]> downloadFileDirect(String filePath, int channelId) {
1669                return downloadFileDirect(filePath, channelId, null);
1670        }
1671
1672        /**
1673         * Downloads a file from the file repository at a given path and channel
1674         * and returns the file's bytes as a byte array.
1675         * <p>
1676         * Note that this method <strong>will read the entire file to memory</strong>.
1677         * That means that if a file is larger than 2<sup>31</sup>-1 bytes in size,
1678         * the download will fail.
1679         * </p>
1680         *
1681         * @param filePath
1682         *              the path of the file on the file repository
1683         * @param channelId
1684         *              the ID of the channel to download the file from
1685         * @param channelPassword
1686         *              that channel's password
1687         *
1688         * @return a byte array containing the file's data
1689         *
1690         * @throws TS3CommandFailedException
1691         *              if the execution of a command fails
1692         * @throws TS3FileTransferFailedException
1693         *              if the file transfer fails for any reason
1694         * @querycommands 1
1695         * @see FileInfo#getPath()
1696         * @see Channel#getId()
1697         * @see #downloadFile(OutputStream, String, int, String)
1698         */
1699        public CommandFuture<byte[]> downloadFileDirect(String filePath, int channelId, String channelPassword) {
1700                FileTransferHelper helper = query.getFileTransferHelper();
1701                int transferId = helper.getClientTransferId();
1702                Command cmd = FileCommands.ftInitDownload(transferId, filePath, channelId, channelPassword);
1703                CommandFuture<byte[]> future = new CommandFuture<>();
1704
1705                executeAndTransformFirst(cmd, FileTransferParameters::new).onSuccess(params -> {
1706                        QueryError error = params.getQueryError();
1707                        if (!error.isSuccessful()) {
1708                                future.fail(new TS3CommandFailedException(error, cmd.getName()));
1709                                return;
1710                        }
1711
1712                        long fileSize = params.getFileSize();
1713                        if (fileSize > Integer.MAX_VALUE) {
1714                                future.fail(new TS3FileTransferFailedException("File too big for byte array"));
1715                                return;
1716                        }
1717                        ByteArrayOutputStream dataOut = new ByteArrayOutputStream((int) fileSize);
1718
1719                        try {
1720                                query.getFileTransferHelper().downloadFile(dataOut, params);
1721                        } catch (IOException e) {
1722                                future.fail(new TS3FileTransferFailedException("Download failed", e));
1723                                return;
1724                        }
1725                        future.set(dataOut.toByteArray());
1726                }).forwardFailure(future);
1727
1728                return future;
1729        }
1730
1731        /**
1732         * Downloads an icon from the icon directory in the file repository
1733         * and writes the file's bytes to an open {@link OutputStream}.
1734         * <p>
1735         * It is the user's responsibility to ensure that the given {@code OutputStream} is
1736         * open and to close the stream again once the download has finished.
1737         * </p>
1738         *
1739         * @param dataOut
1740         *              a stream that the downloaded data should be written to
1741         * @param iconId
1742         *              the ID of the icon that should be downloaded
1743         *
1744         * @return a byte array containing the icon file's data
1745         *
1746         * @throws TS3CommandFailedException
1747         *              if the execution of a command fails
1748         * @throws TS3FileTransferFailedException
1749         *              if the file transfer fails for any reason
1750         * @querycommands 1
1751         * @see IconFile#getIconId()
1752         * @see #downloadIconDirect(long)
1753         * @see #uploadIcon(InputStream, long)
1754         */
1755        public CommandFuture<Long> downloadIcon(OutputStream dataOut, long iconId) {
1756                String iconPath = "/icon_" + iconId;
1757                return downloadFile(dataOut, iconPath, 0);
1758        }
1759
1760        /**
1761         * Downloads an icon from the icon directory in the file repository
1762         * and returns the file's bytes as a byte array.
1763         * <p>
1764         * Note that this method <strong>will read the entire file to memory</strong>.
1765         * </p>
1766         *
1767         * @param iconId
1768         *              the ID of the icon that should be downloaded
1769         *
1770         * @return a byte array containing the icon file's data
1771         *
1772         * @throws TS3CommandFailedException
1773         *              if the execution of a command fails
1774         * @throws TS3FileTransferFailedException
1775         *              if the file transfer fails for any reason
1776         * @querycommands 1
1777         * @see IconFile#getIconId()
1778         * @see #downloadIcon(OutputStream, long)
1779         * @see #uploadIconDirect(byte[])
1780         */
1781        public CommandFuture<byte[]> downloadIconDirect(long iconId) {
1782                String iconPath = "/icon_" + iconId;
1783                return downloadFileDirect(iconPath, 0);
1784        }
1785
1786        /**
1787         * Changes a channel's configuration using the given properties.
1788         *
1789         * @param channelId
1790         *              the ID of the channel to edit
1791         * @param options
1792         *              the map of properties to modify
1793         *
1794         * @return a future to track the progress of this command
1795         *
1796         * @throws TS3CommandFailedException
1797         *              if the execution of a command fails
1798         * @querycommands 1
1799         * @see Channel#getId()
1800         */
1801        public CommandFuture<Void> editChannel(int channelId, Map<ChannelProperty, String> options) {
1802                Command cmd = ChannelCommands.channelEdit(channelId, options);
1803                return executeAndReturnError(cmd);
1804        }
1805
1806        /**
1807         * Changes a single property of the given channel.
1808         * <p>
1809         * Note that one can set many properties at once with the overloaded method that
1810         * takes a map of channel properties and strings.
1811         * </p>
1812         *
1813         * @param channelId
1814         *              the ID of the channel to edit
1815         * @param property
1816         *              the channel property to modify, make sure it is editable
1817         * @param value
1818         *              the new value of the property
1819         *
1820         * @return a future to track the progress of this command
1821         *
1822         * @throws TS3CommandFailedException
1823         *              if the execution of a command fails
1824         * @querycommands 1
1825         * @see Channel#getId()
1826         * @see #editChannel(int, Map)
1827         */
1828        public CommandFuture<Void> editChannel(int channelId, ChannelProperty property, String value) {
1829                return editChannel(channelId, Collections.singletonMap(property, value));
1830        }
1831
1832        /**
1833         * Changes a client's configuration using given properties.
1834         * <p>
1835         * Only {@link ClientProperty#CLIENT_DESCRIPTION} can be changed for other clients.
1836         * To update the current client's properties, use {@link #updateClient(Map)}
1837         * or {@link #updateClient(ClientProperty, String)}.
1838         * </p>
1839         *
1840         * @param clientId
1841         *              the ID of the client to edit
1842         * @param options
1843         *              the map of properties to modify
1844         *
1845         * @return a future to track the progress of this command
1846         *
1847         * @throws TS3CommandFailedException
1848         *              if the execution of a command fails
1849         * @querycommands 1
1850         * @see Client#getId()
1851         * @see #updateClient(Map)
1852         */
1853        public CommandFuture<Void> editClient(int clientId, Map<ClientProperty, String> options) {
1854                Command cmd = ClientCommands.clientEdit(clientId, options);
1855                return executeAndReturnError(cmd);
1856        }
1857
1858        /**
1859         * Changes a single property of the given client.
1860         * <p>
1861         * Only {@link ClientProperty#CLIENT_DESCRIPTION} can be changed for other clients.
1862         * To update the current client's properties, use {@link #updateClient(Map)}
1863         * or {@link #updateClient(ClientProperty, String)}.
1864         * </p>
1865         *
1866         * @param clientId
1867         *              the ID of the client to edit
1868         * @param property
1869         *              the client property to modify, make sure it is editable
1870         * @param value
1871         *              the new value of the property
1872         *
1873         * @return a future to track the progress of this command
1874         *
1875         * @throws TS3CommandFailedException
1876         *              if the execution of a command fails
1877         * @querycommands 1
1878         * @see Client#getId()
1879         * @see #editClient(int, Map)
1880         * @see #updateClient(Map)
1881         */
1882        public CommandFuture<Void> editClient(int clientId, ClientProperty property, String value) {
1883                return editClient(clientId, Collections.singletonMap(property, value));
1884        }
1885
1886        /**
1887         * Changes a client's database settings using given properties.
1888         *
1889         * @param clientDBId
1890         *              the database ID of the client to edit
1891         * @param options
1892         *              the map of properties to modify
1893         *
1894         * @return a future to track the progress of this command
1895         *
1896         * @throws TS3CommandFailedException
1897         *              if the execution of a command fails
1898         * @querycommands 1
1899         * @see DatabaseClientInfo
1900         * @see Client#getDatabaseId()
1901         */
1902        public CommandFuture<Void> editDatabaseClient(int clientDBId, Map<ClientProperty, String> options) {
1903                Command cmd = DatabaseClientCommands.clientDBEdit(clientDBId, options);
1904                return executeAndReturnError(cmd);
1905        }
1906
1907        /**
1908         * Changes the server instance configuration using given properties.
1909         * If the given property is not changeable, {@code IllegalArgumentException} will be thrown.
1910         *
1911         * @param property
1912         *              the property to edit, must be changeable
1913         * @param value
1914         *              the new value for the edit
1915         *
1916         * @return a future to track the progress of this command
1917         *
1918         * @throws IllegalArgumentException
1919         *              if {@code property} is not changeable
1920         * @throws TS3CommandFailedException
1921         *              if the execution of a command fails
1922         * @querycommands 1
1923         * @see ServerInstanceProperty#isChangeable()
1924         */
1925        public CommandFuture<Void> editInstance(ServerInstanceProperty property, String value) {
1926                Command cmd = ServerCommands.instanceEdit(Collections.singletonMap(property, value));
1927                return executeAndReturnError(cmd);
1928        }
1929
1930        /**
1931         * Changes the configuration of the selected virtual server using given properties.
1932         *
1933         * @param options
1934         *              the map of properties to edit
1935         *
1936         * @return a future to track the progress of this command
1937         *
1938         * @throws TS3CommandFailedException
1939         *              if the execution of a command fails
1940         * @querycommands 1
1941         * @see VirtualServerProperty
1942         */
1943        public CommandFuture<Void> editServer(Map<VirtualServerProperty, String> options) {
1944                Command cmd = VirtualServerCommands.serverEdit(options);
1945                return executeAndReturnError(cmd);
1946        }
1947
1948        /**
1949         * Gets a list of all bans on the selected virtual server.
1950         *
1951         * @return a list of all bans on the virtual server
1952         *
1953         * @throws TS3CommandFailedException
1954         *              if the execution of a command fails
1955         * @querycommands 1
1956         * @see Ban
1957         */
1958        public CommandFuture<List<Ban>> getBans() {
1959                Command cmd = BanCommands.banList();
1960                return executeAndTransform(cmd, Ban::new);
1961        }
1962
1963        /**
1964         * Gets a list of IP addresses used by the server instance.
1965         *
1966         * @return the list of bound IP addresses
1967         *
1968         * @throws TS3CommandFailedException
1969         *              if the execution of a command fails
1970         * @querycommands 1
1971         * @see Binding
1972         */
1973        public CommandFuture<List<Binding>> getBindings() {
1974                Command cmd = ServerCommands.bindingList();
1975                return executeAndTransform(cmd, Binding::new);
1976        }
1977
1978        /**
1979         * Finds and returns the channel matching the given name exactly.
1980         *
1981         * @param name
1982         *              the name of the channel
1983         * @param ignoreCase
1984         *              whether the case of the name should be ignored
1985         *
1986         * @return the found channel or {@code null} if no channel was found
1987         *
1988         * @throws TS3CommandFailedException
1989         *              if the execution of a command fails
1990         * @querycommands 1
1991         * @see Channel
1992         * @see #getChannelsByName(String)
1993         */
1994        public CommandFuture<Channel> getChannelByNameExact(String name, boolean ignoreCase) {
1995                String caseName = ignoreCase ? name.toLowerCase(Locale.ROOT) : name;
1996
1997                return getChannels().map(allChannels -> {
1998                        for (Channel c : allChannels) {
1999                                String channelName = ignoreCase ? c.getName().toLowerCase(Locale.ROOT) : c.getName();
2000                                if (caseName.equals(channelName)) return c;
2001                        }
2002                        return null; // Not found
2003                });
2004        }
2005
2006        /**
2007         * Gets a list of channels whose names contain the given search string.
2008         *
2009         * @param name
2010         *              the name to search
2011         *
2012         * @return a list of all channels with names matching the search pattern
2013         *
2014         * @throws TS3CommandFailedException
2015         *              if the execution of a command fails
2016         * @querycommands 2
2017         * @see Channel
2018         * @see #getChannelByNameExact(String, boolean)
2019         */
2020        public CommandFuture<List<Channel>> getChannelsByName(String name) {
2021                Command cmd = ChannelCommands.channelFind(name);
2022                CommandFuture<List<Channel>> future = new CommandFuture<>();
2023
2024                CommandFuture<List<Integer>> channelIds = executeAndMap(cmd, response -> response.getInt("cid"));
2025                CommandFuture<List<Channel>> allChannels = getChannels();
2026
2027                findByKey(channelIds, allChannels, Channel::getId)
2028                                .forwardSuccess(future)
2029                                .onFailure(transformError(future, 768, Collections.emptyList()));
2030
2031                return future;
2032        }
2033
2034        /**
2035         * Displays a list of permissions defined for a client in a specific channel.
2036         *
2037         * @param channelId
2038         *              the ID of the channel
2039         * @param clientDBId
2040         *              the database ID of the client
2041         *
2042         * @return a list of permissions for the user in the specified channel
2043         *
2044         * @throws TS3CommandFailedException
2045         *              if the execution of a command fails
2046         * @querycommands 1
2047         * @see Channel#getId()
2048         * @see Client#getDatabaseId()
2049         * @see Permission
2050         */
2051        public CommandFuture<List<Permission>> getChannelClientPermissions(int channelId, int clientDBId) {
2052                Command cmd = PermissionCommands.channelClientPermList(channelId, clientDBId);
2053                return executeAndTransform(cmd, Permission::new);
2054        }
2055
2056        /**
2057         * Gets all client / channel ID combinations currently assigned to channel groups.
2058         * All three parameters are optional and can be turned off by setting it to {@code -1}.
2059         *
2060         * @param channelId
2061         *              restricts the search to the channel with a specified ID. Set to {@code -1} to ignore.
2062         * @param clientDBId
2063         *              restricts the search to the client with a specified database ID. Set to {@code -1} to ignore.
2064         * @param groupId
2065         *              restricts the search to the channel group with the specified ID. Set to {@code -1} to ignore.
2066         *
2067         * @return a list of combinations of channel ID, client database ID and channel group ID
2068         *
2069         * @throws TS3CommandFailedException
2070         *              if the execution of a command fails
2071         * @querycommands 1
2072         * @see Channel#getId()
2073         * @see Client#getDatabaseId()
2074         * @see ChannelGroup#getId()
2075         * @see ChannelGroupClient
2076         */
2077        public CommandFuture<List<ChannelGroupClient>> getChannelGroupClients(int channelId, int clientDBId, int groupId) {
2078                Command cmd = ChannelGroupCommands.channelGroupClientList(channelId, clientDBId, groupId);
2079                return executeAndTransform(cmd, ChannelGroupClient::new);
2080        }
2081
2082        /**
2083         * Gets all client / channel ID combinations currently assigned to the specified channel group.
2084         *
2085         * @param groupId
2086         *              the ID of the channel group whose client / channel assignments should be returned.
2087         *
2088         * @return a list of combinations of channel ID, client database ID and channel group ID
2089         *
2090         * @throws TS3CommandFailedException
2091         *              if the execution of a command fails
2092         * @querycommands 1
2093         * @see ChannelGroup#getId()
2094         * @see ChannelGroupClient
2095         * @see #getChannelGroupClients(int, int, int)
2096         */
2097        public CommandFuture<List<ChannelGroupClient>> getChannelGroupClientsByChannelGroupId(int groupId) {
2098                return getChannelGroupClients(-1, -1, groupId);
2099        }
2100
2101        /**
2102         * Gets all channel group assignments in the specified channel.
2103         *
2104         * @param channelId
2105         *              the ID of the channel whose channel group assignments should be returned.
2106         *
2107         * @return a list of combinations of channel ID, client database ID and channel group ID
2108         *
2109         * @throws TS3CommandFailedException
2110         *              if the execution of a command fails
2111         * @querycommands 1
2112         * @see Channel#getId()
2113         * @see ChannelGroupClient
2114         * @see #getChannelGroupClients(int, int, int)
2115         */
2116        public CommandFuture<List<ChannelGroupClient>> getChannelGroupClientsByChannelId(int channelId) {
2117                return getChannelGroupClients(channelId, -1, -1);
2118        }
2119
2120        /**
2121         * Gets all channel group assignments for the specified client.
2122         *
2123         * @param clientDBId
2124         *              the database ID of the client whose channel group
2125         *
2126         * @return a list of combinations of channel ID, client database ID and channel group ID
2127         *
2128         * @throws TS3CommandFailedException
2129         *              if the execution of a command fails
2130         * @querycommands 1
2131         * @see Client#getDatabaseId()
2132         * @see ChannelGroupClient
2133         * @see #getChannelGroupClients(int, int, int)
2134         */
2135        public CommandFuture<List<ChannelGroupClient>> getChannelGroupClientsByClientDBId(int clientDBId) {
2136                return getChannelGroupClients(-1, clientDBId, -1);
2137        }
2138
2139        /**
2140         * Gets a list of all permissions assigned to the specified channel group.
2141         *
2142         * @param groupId
2143         *              the ID of the channel group.
2144         *
2145         * @return a list of permissions assigned to the channel group
2146         *
2147         * @throws TS3CommandFailedException
2148         *              if the execution of a command fails
2149         * @querycommands 1
2150         * @see ChannelGroup#getId()
2151         * @see Permission
2152         */
2153        public CommandFuture<List<Permission>> getChannelGroupPermissions(int groupId) {
2154                Command cmd = PermissionCommands.channelGroupPermList(groupId);
2155                return executeAndTransform(cmd, Permission::new);
2156        }
2157
2158        /**
2159         * Gets a list of all channel groups on the selected virtual server.
2160         *
2161         * @return a list of all channel groups on the virtual server
2162         *
2163         * @throws TS3CommandFailedException
2164         *              if the execution of a command fails
2165         * @querycommands 1
2166         * @see ChannelGroup
2167         */
2168        public CommandFuture<List<ChannelGroup>> getChannelGroups() {
2169                Command cmd = ChannelGroupCommands.channelGroupList();
2170                return executeAndTransform(cmd, ChannelGroup::new);
2171        }
2172
2173        /**
2174         * Gets detailed configuration information about the channel specified channel.
2175         *
2176         * @param channelId
2177         *              the ID of the channel
2178         *
2179         * @return information about the channel
2180         *
2181         * @throws TS3CommandFailedException
2182         *              if the execution of a command fails
2183         * @querycommands 1
2184         * @see Channel#getId()
2185         * @see ChannelInfo
2186         */
2187        public CommandFuture<ChannelInfo> getChannelInfo(int channelId) {
2188                Command cmd = ChannelCommands.channelInfo(channelId);
2189                return executeAndTransformFirst(cmd, map -> new ChannelInfo(channelId, map));
2190        }
2191
2192        /**
2193         * Gets a list of all permissions assigned to the specified channel.
2194         *
2195         * @param channelId
2196         *              the ID of the channel
2197         *
2198         * @return a list of all permissions assigned to the channel
2199         *
2200         * @throws TS3CommandFailedException
2201         *              if the execution of a command fails
2202         * @querycommands 1
2203         * @see Channel#getId()
2204         * @see Permission
2205         */
2206        public CommandFuture<List<Permission>> getChannelPermissions(int channelId) {
2207                Command cmd = PermissionCommands.channelPermList(channelId);
2208                return executeAndTransform(cmd, Permission::new);
2209        }
2210
2211        /**
2212         * Gets a list of all channels on the selected virtual server.
2213         *
2214         * @return a list of all channels on the virtual server
2215         *
2216         * @throws TS3CommandFailedException
2217         *              if the execution of a command fails
2218         * @querycommands 1
2219         * @see Channel
2220         */
2221        public CommandFuture<List<Channel>> getChannels() {
2222                Command cmd = ChannelCommands.channelList();
2223                return executeAndTransform(cmd, Channel::new);
2224        }
2225
2226        /**
2227         * Finds and returns the client whose nickname matches the given name exactly.
2228         *
2229         * @param name
2230         *              the name of the client
2231         * @param ignoreCase
2232         *              whether the case of the name should be ignored
2233         *
2234         * @return the found client or {@code null} if no client was found
2235         *
2236         * @throws TS3CommandFailedException
2237         *              if the execution of a command fails
2238         * @querycommands 1
2239         * @see Client
2240         * @see #getClientsByName(String)
2241         */
2242        public CommandFuture<Client> getClientByNameExact(String name, boolean ignoreCase) {
2243                String caseName = ignoreCase ? name.toLowerCase(Locale.ROOT) : name;
2244
2245                return getClients().map(allClients -> {
2246                        for (Client c : allClients) {
2247                                String clientName = ignoreCase ? c.getNickname().toLowerCase(Locale.ROOT) : c.getNickname();
2248                                if (caseName.equals(clientName)) return c;
2249                        }
2250                        return null; // Not found
2251                });
2252        }
2253
2254        /**
2255         * Gets a list of clients whose nicknames contain the given search string.
2256         *
2257         * @param name
2258         *              the name to search
2259         *
2260         * @return a list of all clients with nicknames matching the search pattern
2261         *
2262         * @throws TS3CommandFailedException
2263         *              if the execution of a command fails
2264         * @querycommands 2
2265         * @see Client
2266         * @see #getClientByNameExact(String, boolean)
2267         */
2268        public CommandFuture<List<Client>> getClientsByName(String name) {
2269                Command cmd = ClientCommands.clientFind(name);
2270                CommandFuture<List<Client>> future = new CommandFuture<>();
2271
2272                CommandFuture<List<Integer>> clientIds = executeAndMap(cmd, response -> response.getInt("clid"));
2273                CommandFuture<List<Client>> allClients = getClients();
2274
2275                findByKey(clientIds, allClients, Client::getId)
2276                                .forwardSuccess(future)
2277                                .onFailure(transformError(future, 512, Collections.emptyList()));
2278
2279                return future;
2280        }
2281
2282        /**
2283         * Gets information about the client with the specified unique identifier.
2284         *
2285         * @param clientUId
2286         *              the unique identifier of the client
2287         *
2288         * @return information about the client
2289         *
2290         * @throws TS3CommandFailedException
2291         *              if the execution of a command fails
2292         * @querycommands 2
2293         * @see Client#getUniqueIdentifier()
2294         * @see ClientInfo
2295         */
2296        public CommandFuture<ClientInfo> getClientByUId(String clientUId) {
2297                Command cmd = ClientCommands.clientGetIds(clientUId);
2298                return executeAndReturnIntProperty(cmd, "clid")
2299                                .then(this::getClientInfo);
2300        }
2301
2302        /**
2303         * Gets information about the client with the specified client ID.
2304         *
2305         * @param clientId
2306         *              the client ID of the client
2307         *
2308         * @return information about the client
2309         *
2310         * @throws TS3CommandFailedException
2311         *              if the execution of a command fails
2312         * @querycommands 1
2313         * @see Client#getId()
2314         * @see ClientInfo
2315         */
2316        public CommandFuture<ClientInfo> getClientInfo(int clientId) {
2317                Command cmd = ClientCommands.clientInfo(clientId);
2318                return executeAndTransformFirst(cmd, map -> new ClientInfo(clientId, map));
2319        }
2320
2321        /**
2322         * Gets a list of all permissions assigned to the specified client.
2323         *
2324         * @param clientDBId
2325         *              the database ID of the client
2326         *
2327         * @return a list of all permissions assigned to the client
2328         *
2329         * @throws TS3CommandFailedException
2330         *              if the execution of a command fails
2331         * @querycommands 1
2332         * @see Client#getDatabaseId()
2333         * @see Permission
2334         */
2335        public CommandFuture<List<Permission>> getClientPermissions(int clientDBId) {
2336                Command cmd = PermissionCommands.clientPermList(clientDBId);
2337                return executeAndTransform(cmd, Permission::new);
2338        }
2339
2340        /**
2341         * Gets a list of all clients on the selected virtual server.
2342         *
2343         * @return a list of all clients on the virtual server
2344         *
2345         * @throws TS3CommandFailedException
2346         *              if the execution of a command fails
2347         * @querycommands 1
2348         * @see Client
2349         */
2350        public CommandFuture<List<Client>> getClients() {
2351                Command cmd = ClientCommands.clientList();
2352                return executeAndTransform(cmd, Client::new);
2353        }
2354
2355        /**
2356         * Gets a list of all complaints on the selected virtual server.
2357         *
2358         * @return a list of all complaints on the virtual server
2359         *
2360         * @throws TS3CommandFailedException
2361         *              if the execution of a command fails
2362         * @querycommands 1
2363         * @see Complaint
2364         * @see #getComplaints(int)
2365         */
2366        public CommandFuture<List<Complaint>> getComplaints() {
2367                return getComplaints(-1);
2368        }
2369
2370        /**
2371         * Gets a list of all complaints about the specified client.
2372         *
2373         * @param clientDBId
2374         *              the database ID of the client
2375         *
2376         * @return a list of all complaints about the specified client
2377         *
2378         * @throws TS3CommandFailedException
2379         *              if the execution of a command fails
2380         * @querycommands 1
2381         * @see Client#getDatabaseId()
2382         * @see Complaint
2383         */
2384        public CommandFuture<List<Complaint>> getComplaints(int clientDBId) {
2385                Command cmd = ComplaintCommands.complainList(clientDBId);
2386                return executeAndTransform(cmd, Complaint::new);
2387        }
2388
2389        /**
2390         * Gets detailed connection information about the selected virtual server.
2391         *
2392         * @return connection information about the selected virtual server
2393         *
2394         * @throws TS3CommandFailedException
2395         *              if the execution of a command fails
2396         * @querycommands 1
2397         * @see ConnectionInfo
2398         * @see #getServerInfo()
2399         */
2400        public CommandFuture<ConnectionInfo> getConnectionInfo() {
2401                Command cmd = VirtualServerCommands.serverRequestConnectionInfo();
2402                return executeAndTransformFirst(cmd, ConnectionInfo::new);
2403        }
2404
2405        /**
2406         * Gets a map of all custom client properties and their values
2407         * assigned to the client with database ID {@code clientDBId}.
2408         *
2409         * @param clientDBId
2410         *              the database ID of the target client
2411         *
2412         * @return a map of the client's custom client property assignments
2413         *
2414         * @throws TS3CommandFailedException
2415         *              if the execution of a command fails
2416         * @querycommands 1
2417         * @see Client#getDatabaseId()
2418         * @see #searchCustomClientProperty(String)
2419         * @see #searchCustomClientProperty(String, String)
2420         */
2421        public CommandFuture<Map<String, String>> getCustomClientProperties(int clientDBId) {
2422                Command cmd = CustomPropertyCommands.customInfo(clientDBId);
2423                CommandFuture<Map<String, String>> future = cmd.getFuture()
2424                                .map(result -> {
2425                                        List<Wrapper> response = result.getResponses();
2426                                        Map<String, String> properties = new HashMap<>(response.size());
2427                                        for (Wrapper wrapper : response) {
2428                                                properties.put(wrapper.get("ident"), wrapper.get("value"));
2429                                        }
2430
2431                                        return properties;
2432                                });
2433
2434                commandQueue.enqueueCommand(cmd);
2435                return future;
2436        }
2437
2438        /**
2439         * Gets all clients in the database whose last nickname matches the specified name <b>exactly</b>.
2440         *
2441         * @param name
2442         *              the nickname for the clients to match
2443         *
2444         * @return a list of all clients with a matching nickname
2445         *
2446         * @throws TS3CommandFailedException
2447         *              if the execution of a command fails
2448         * @querycommands 1 + n,
2449         * where n is the amount of database clients with a matching nickname
2450         * @see Client#getNickname()
2451         */
2452        public CommandFuture<List<DatabaseClientInfo>> getDatabaseClientsByName(String name) {
2453                Command cmd = DatabaseClientCommands.clientDBFind(name, false);
2454
2455                return executeAndMap(cmd, response -> response.getInt("cldbid"))
2456                                .then(dbClientIds -> {
2457                                        Collection<CommandFuture<DatabaseClientInfo>> infoFutures = new ArrayList<>(dbClientIds.size());
2458                                        for (int dbClientId : dbClientIds) {
2459                                                infoFutures.add(getDatabaseClientInfo(dbClientId));
2460                                        }
2461                                        return CommandFuture.ofAll(infoFutures);
2462                                });
2463        }
2464
2465        /**
2466         * Gets information about the client with the specified unique identifier in the server database.
2467         *
2468         * @param clientUId
2469         *              the unique identifier of the client
2470         *
2471         * @return the database client or {@code null} if no client was found
2472         *
2473         * @throws TS3CommandFailedException
2474         *              if the execution of a command fails
2475         * @querycommands 2
2476         * @see Client#getUniqueIdentifier()
2477         * @see DatabaseClientInfo
2478         */
2479        public CommandFuture<DatabaseClientInfo> getDatabaseClientByUId(String clientUId) {
2480                Command cmd = DatabaseClientCommands.clientDBFind(clientUId, true);
2481                CommandFuture<DatabaseClientInfo> future = cmd.getFuture()
2482                                .then(result -> {
2483                                        if (result.getResponses().isEmpty()) {
2484                                                return null;
2485                                        } else {
2486                                                int databaseId = result.getFirstResponse().getInt("cldbid");
2487                                                return getDatabaseClientInfo(databaseId);
2488                                        }
2489                                });
2490
2491                commandQueue.enqueueCommand(cmd);
2492                return future;
2493        }
2494
2495        /**
2496         * Gets information about the client with the specified database ID in the server database.
2497         *
2498         * @param clientDBId
2499         *              the database ID of the client
2500         *
2501         * @return the database client or {@code null} if no client was found
2502         *
2503         * @throws TS3CommandFailedException
2504         *              if the execution of a command fails
2505         * @querycommands 1
2506         * @see Client#getDatabaseId()
2507         * @see DatabaseClientInfo
2508         */
2509        public CommandFuture<DatabaseClientInfo> getDatabaseClientInfo(int clientDBId) {
2510                Command cmd = DatabaseClientCommands.clientDBInfo(clientDBId);
2511                return executeAndTransformFirst(cmd, DatabaseClientInfo::new);
2512        }
2513
2514        /**
2515         * Gets information about all clients in the server database.
2516         * <p>
2517         * As this method uses internal commands which can only return 200 clients at once,
2518         * this method can take quite some time to execute.
2519         * </p><p>
2520         * Also keep in mind that the client database can easily accumulate several thousand entries.
2521         * </p>
2522         *
2523         * @return a {@link List} of all database clients
2524         *
2525         * @throws TS3CommandFailedException
2526         *              if the execution of a command fails
2527         * @querycommands 1 + n,
2528         * where n = Math.ceil([amount of database clients] / 200)
2529         * @see DatabaseClient
2530         */
2531        public CommandFuture<List<DatabaseClient>> getDatabaseClients() {
2532                Command cmd = DatabaseClientCommands.clientDBList(0, 1, true);
2533
2534                return executeAndReturnIntProperty(cmd, "count")
2535                                .then(count -> {
2536                                        Collection<CommandFuture<List<DatabaseClient>>> futures = new ArrayList<>((count + 199) / 200);
2537                                        for (int i = 0; i < count; i += 200) {
2538                                                futures.add(getDatabaseClients(i, 200));
2539                                        }
2540                                        return CommandFuture.ofAll(futures);
2541                                }).map(listOfLists -> listOfLists.stream()
2542                                                .flatMap(List::stream)
2543                                                .collect(Collectors.toList()));
2544        }
2545
2546        /**
2547         * Gets information about a set number of clients in the server database, starting at {@code offset}.
2548         *
2549         * @param offset
2550         *              the index of the first database client to be returned.
2551         *              Note that this is <b>not</b> a database ID, but an arbitrary, 0-based index.
2552         * @param count
2553         *              the number of database clients that should be returned.
2554         *              Any integer greater than 200 might cause problems with the connection
2555         *
2556         * @return a {@link List} of database clients
2557         *
2558         * @throws TS3CommandFailedException
2559         *              if the execution of a command fails
2560         * @querycommands 1
2561         * @see DatabaseClient
2562         */
2563        public CommandFuture<List<DatabaseClient>> getDatabaseClients(int offset, int count) {
2564                Command cmd = DatabaseClientCommands.clientDBList(offset, count, false);
2565                return executeAndTransform(cmd, DatabaseClient::new);
2566        }
2567
2568        /**
2569         * Gets information about a file on the file repository in the specified channel.
2570         * <p>
2571         * Note that this method does not work on directories and the information returned by this
2572         * method is identical to the one returned by {@link #getFileList(String, int, String)}
2573         * </p>
2574         *
2575         * @param filePath
2576         *              the path to the file
2577         * @param channelId
2578         *              the ID of the channel the file resides in
2579         *
2580         * @return some information about the file
2581         *
2582         * @throws TS3CommandFailedException
2583         *              if the execution of a command fails
2584         * @querycommands 1
2585         * @see FileInfo#getPath()
2586         * @see Channel#getId()
2587         */
2588        public CommandFuture<FileInfo> getFileInfo(String filePath, int channelId) {
2589                return getFileInfo(filePath, channelId, null);
2590        }
2591
2592        /**
2593         * Gets information about a file on the file repository in the specified channel.
2594         * <p>
2595         * Note that this method does not work on directories and the information returned by this
2596         * method is identical to the one returned by {@link #getFileList(String, int, String)}
2597         * </p>
2598         *
2599         * @param filePath
2600         *              the path to the file
2601         * @param channelId
2602         *              the ID of the channel the file resides in
2603         * @param channelPassword
2604         *              the password of that channel
2605         *
2606         * @return some information about the file
2607         *
2608         * @throws TS3CommandFailedException
2609         *              if the execution of a command fails
2610         * @querycommands 1
2611         * @see FileInfo#getPath()
2612         * @see Channel#getId()
2613         */
2614        public CommandFuture<FileInfo> getFileInfo(String filePath, int channelId, String channelPassword) {
2615                Command cmd = FileCommands.ftGetFileInfo(channelId, channelPassword, filePath);
2616                return executeAndTransformFirst(cmd, FileInfo::new);
2617        }
2618
2619        /**
2620         * Gets information about multiple files on the file repository in the specified channel.
2621         * <p>
2622         * Note that this method does not work on directories and the information returned by this
2623         * method is identical to the one returned by {@link #getFileList(String, int, String)}
2624         * </p>
2625         *
2626         * @param filePaths
2627         *              the paths to the files
2628         * @param channelId
2629         *              the ID of the channel the file resides in
2630         *
2631         * @return some information about the file
2632         *
2633         * @throws TS3CommandFailedException
2634         *              if the execution of a command fails
2635         * @querycommands 1
2636         * @see FileInfo#getPath()
2637         * @see Channel#getId()
2638         */
2639        public CommandFuture<List<FileInfo>> getFileInfos(String[] filePaths, int channelId) {
2640                return getFileInfos(filePaths, channelId, null);
2641        }
2642
2643        /**
2644         * Gets information about multiple files on the file repository in the specified channel.
2645         * <p>
2646         * Note that this method does not work on directories and the information returned by this
2647         * method is identical to the one returned by {@link #getFileList(String, int, String)}
2648         * </p>
2649         *
2650         * @param filePaths
2651         *              the paths to the files
2652         * @param channelId
2653         *              the ID of the channel the file resides in
2654         * @param channelPassword
2655         *              the password of that channel
2656         *
2657         * @return some information about the file
2658         *
2659         * @throws TS3CommandFailedException
2660         *              if the execution of a command fails
2661         * @querycommands 1
2662         * @see FileInfo#getPath()
2663         * @see Channel#getId()
2664         */
2665        public CommandFuture<List<FileInfo>> getFileInfos(String[] filePaths, int channelId, String channelPassword) {
2666                Command cmd = FileCommands.ftGetFileInfo(channelId, channelPassword, filePaths);
2667                return executeAndTransform(cmd, FileInfo::new);
2668        }
2669
2670        /**
2671         * Gets information about multiple files on the file repository in multiple channels.
2672         * <p>
2673         * Note that this method does not work on directories and the information returned by this
2674         * method is identical to the one returned by {@link #getFileList(String, int, String)}
2675         * </p>
2676         *
2677         * @param filePaths
2678         *              the paths to the files, may not be {@code null} and may not contain {@code null} elements
2679         * @param channelIds
2680         *              the IDs of the channels the file resides in, may not be {@code null}
2681         * @param channelPasswords
2682         *              the passwords of those channels, may be {@code null} and may contain {@code null} elements
2683         *
2684         * @return some information about the files
2685         *
2686         * @throws IllegalArgumentException
2687         *              if the dimensions of {@code filePaths}, {@code channelIds} and {@code channelPasswords} don't match
2688         * @throws TS3CommandFailedException
2689         *              if the execution of a command fails
2690         * @querycommands 1
2691         * @see FileInfo#getPath()
2692         * @see Channel#getId()
2693         */
2694        public CommandFuture<List<FileInfo>> getFileInfos(String[] filePaths, int[] channelIds, String[] channelPasswords) {
2695                Command cmd = FileCommands.ftGetFileInfo(channelIds, channelPasswords, filePaths);
2696                return executeAndTransform(cmd, FileInfo::new);
2697        }
2698
2699        /**
2700         * Gets a list of files and directories in the specified parent directory and channel.
2701         *
2702         * @param directoryPath
2703         *              the path to the parent directory
2704         * @param channelId
2705         *              the ID of the channel the directory resides in
2706         *
2707         * @return the files and directories in the parent directory
2708         *
2709         * @throws TS3CommandFailedException
2710         *              if the execution of a command fails
2711         * @querycommands 1
2712         * @see FileInfo#getPath()
2713         * @see Channel#getId()
2714         */
2715        public CommandFuture<List<FileListEntry>> getFileList(String directoryPath, int channelId) {
2716                return getFileList(directoryPath, channelId, null);
2717        }
2718
2719        /**
2720         * Gets a list of files and directories in the specified parent directory and channel.
2721         *
2722         * @param directoryPath
2723         *              the path to the parent directory
2724         * @param channelId
2725         *              the ID of the channel the directory resides in
2726         * @param channelPassword
2727         *              the password of that channel
2728         *
2729         * @return the files and directories in the parent directory
2730         *
2731         * @throws TS3CommandFailedException
2732         *              if the execution of a command fails
2733         * @querycommands 1
2734         * @see FileInfo#getPath()
2735         * @see Channel#getId()
2736         */
2737        public CommandFuture<List<FileListEntry>> getFileList(String directoryPath, int channelId, String channelPassword) {
2738                Command cmd = FileCommands.ftGetFileList(directoryPath, channelId, channelPassword);
2739                return executeAndTransform(cmd, FileListEntry::new);
2740        }
2741
2742        /**
2743         * Gets a list of active or recently active file transfers.
2744         *
2745         * @return a list of file transfers
2746         *
2747         * @throws TS3CommandFailedException
2748         *              if the execution of a command fails
2749         * @querycommands 1
2750         */
2751        public CommandFuture<List<FileTransfer>> getFileTransfers() {
2752                Command cmd = FileCommands.ftList();
2753                return executeAndTransform(cmd, FileTransfer::new);
2754        }
2755
2756        /**
2757         * Displays detailed configuration information about the server instance including
2758         * uptime, number of virtual servers online, traffic information, etc.
2759         *
2760         * @return information about the host
2761         *
2762         * @throws TS3CommandFailedException
2763         *              if the execution of a command fails
2764         * @querycommands 1
2765         */
2766        public CommandFuture<HostInfo> getHostInfo() {
2767                Command cmd = ServerCommands.hostInfo();
2768                return executeAndTransformFirst(cmd, HostInfo::new);
2769        }
2770
2771        /**
2772         * Gets a list of all icon files on this virtual server.
2773         *
2774         * @return a list of all icons
2775         */
2776        public CommandFuture<List<IconFile>> getIconList() {
2777                return getFileList("/icons/", 0)
2778                                .map(result -> {
2779                                        List<IconFile> icons = new ArrayList<>(result.size());
2780                                        for (FileListEntry file : result) {
2781                                                if (file.isDirectory() || file.isStillUploading()) continue;
2782                                                icons.add(new IconFile(file.getMap()));
2783                                        }
2784                                        return icons;
2785                                });
2786        }
2787
2788        /**
2789         * Displays the server instance configuration including database revision number,
2790         * the file transfer port, default group IDs, etc.
2791         *
2792         * @return information about the TeamSpeak server instance.
2793         *
2794         * @throws TS3CommandFailedException
2795         *              if the execution of a command fails
2796         * @querycommands 1
2797         */
2798        public CommandFuture<InstanceInfo> getInstanceInfo() {
2799                Command cmd = ServerCommands.instanceInfo();
2800                return executeAndTransformFirst(cmd, InstanceInfo::new);
2801        }
2802
2803        /**
2804         * Fetches the specified amount of log entries from the server log.
2805         *
2806         * @param lines
2807         *              the amount of log entries to fetch, in the range between 1 and 100.
2808         *              Returns 100 entries if the argument is not in range
2809         *
2810         * @return a list of the latest log entries
2811         *
2812         * @throws TS3CommandFailedException
2813         *              if the execution of a command fails
2814         * @querycommands 1
2815         */
2816        public CommandFuture<List<String>> getInstanceLogEntries(int lines) {
2817                Command cmd = ServerCommands.logView(lines, true);
2818                return executeAndMap(cmd, response -> response.get("l"));
2819        }
2820
2821        /**
2822         * Fetches the last 100 log entries from the server log.
2823         *
2824         * @return a list of up to 100 log entries
2825         *
2826         * @throws TS3CommandFailedException
2827         *              if the execution of a command fails
2828         * @querycommands 1
2829         */
2830        public CommandFuture<List<String>> getInstanceLogEntries() {
2831                return getInstanceLogEntries(100);
2832        }
2833
2834        /**
2835         * Reads the message body of a message. This will not set the read flag, though.
2836         *
2837         * @param messageId
2838         *              the ID of the message to be read
2839         *
2840         * @return the body of the message with the specified ID or {@code null} if there was no message with that ID
2841         *
2842         * @throws TS3CommandFailedException
2843         *              if the execution of a command fails
2844         * @querycommands 1
2845         * @see Message#getId()
2846         * @see #setMessageRead(int)
2847         */
2848        public CommandFuture<String> getOfflineMessage(int messageId) {
2849                Command cmd = MessageCommands.messageGet(messageId);
2850                return executeAndReturnStringProperty(cmd, "message");
2851        }
2852
2853        /**
2854         * Reads the message body of a message. This will not set the read flag, though.
2855         *
2856         * @param message
2857         *              the message to be read
2858         *
2859         * @return the body of the message with the specified ID or {@code null} if there was no message with that ID
2860         *
2861         * @throws TS3CommandFailedException
2862         *              if the execution of a command fails
2863         * @querycommands 1
2864         * @see Message#getId()
2865         * @see #setMessageRead(Message)
2866         */
2867        public CommandFuture<String> getOfflineMessage(Message message) {
2868                return getOfflineMessage(message.getId());
2869        }
2870
2871        /**
2872         * Gets a list of all offline messages for the server query.
2873         * The returned messages lack their message body, though.
2874         * To read the actual message, use {@link #getOfflineMessage(int)} or {@link #getOfflineMessage(Message)}.
2875         *
2876         * @return a list of all offline messages this server query has received
2877         *
2878         * @throws TS3CommandFailedException
2879         *              if the execution of a command fails
2880         * @querycommands 1
2881         */
2882        public CommandFuture<List<Message>> getOfflineMessages() {
2883                Command cmd = MessageCommands.messageList();
2884                return executeAndTransform(cmd, Message::new);
2885        }
2886
2887        /**
2888         * Displays detailed information about all assignments of the permission specified
2889         * with {@code permName}. The output includes the type and the ID of the client,
2890         * channel or group associated with the permission.
2891         *
2892         * @param permName
2893         *              the name of the permission
2894         *
2895         * @return a list of permission assignments
2896         *
2897         * @throws TS3CommandFailedException
2898         *              if the execution of a command fails
2899         * @querycommands 1
2900         * @see #getPermissionOverview(int, int)
2901         */
2902        public CommandFuture<List<PermissionAssignment>> getPermissionAssignments(String permName) {
2903                Command cmd = PermissionCommands.permFind(permName);
2904                CommandFuture<List<PermissionAssignment>> future = new CommandFuture<>();
2905
2906                executeAndTransform(cmd, PermissionAssignment::new)
2907                                .forwardSuccess(future)
2908                                .onFailure(transformError(future, 2562, Collections.emptyList()));
2909
2910                return future;
2911        }
2912
2913        /**
2914         * Gets the ID of the permission specified by {@code permName}.
2915         * <p>
2916         * Note that the use of numeric permission IDs is deprecated
2917         * and that this API only uses the string variant of the IDs.
2918         * </p>
2919         *
2920         * @param permName
2921         *              the name of the permission
2922         *
2923         * @return the numeric ID of the specified permission
2924         *
2925         * @throws TS3CommandFailedException
2926         *              if the execution of a command fails
2927         * @querycommands 1
2928         */
2929        public CommandFuture<Integer> getPermissionIdByName(String permName) {
2930                Command cmd = PermissionCommands.permIdGetByName(permName);
2931                return executeAndReturnIntProperty(cmd, "permid");
2932        }
2933
2934        /**
2935         * Gets the IDs of the permissions specified by {@code permNames}.
2936         * <p>
2937         * Note that the use of numeric permission IDs is deprecated
2938         * and that this API only uses the string variant of the IDs.
2939         * </p>
2940         *
2941         * @param permNames
2942         *              the names of the permissions
2943         *
2944         * @return the numeric IDs of the specified permission
2945         *
2946         * @throws IllegalArgumentException
2947         *              if {@code permNames} is {@code null}
2948         * @throws TS3CommandFailedException
2949         *              if the execution of a command fails
2950         * @querycommands 1
2951         */
2952        public CommandFuture<int[]> getPermissionIdsByName(String... permNames) {
2953                Command cmd = PermissionCommands.permIdGetByName(permNames);
2954                return executeAndReturnIntArray(cmd, "permid");
2955        }
2956
2957        /**
2958         * Gets a list of all assigned permissions for a client in a specified channel.
2959         * If you do not care about channel permissions, set {@code channelId} to {@code 0}.
2960         *
2961         * @param channelId
2962         *              the ID of the channel
2963         * @param clientDBId
2964         *              the database ID of the client to create the overview for
2965         *
2966         * @return a list of all permission assignments for the client in the specified channel
2967         *
2968         * @throws TS3CommandFailedException
2969         *              if the execution of a command fails
2970         * @querycommands 1
2971         * @see Channel#getId()
2972         * @see Client#getDatabaseId()
2973         */
2974        public CommandFuture<List<PermissionAssignment>> getPermissionOverview(int channelId, int clientDBId) {
2975                Command cmd = PermissionCommands.permOverview(channelId, clientDBId);
2976                return executeAndTransform(cmd, PermissionAssignment::new);
2977        }
2978
2979        /**
2980         * Displays a list of all permissions, including ID, name and description.
2981         *
2982         * @return a list of all permissions
2983         *
2984         * @throws TS3CommandFailedException
2985         *              if the execution of a command fails
2986         * @querycommands 1
2987         */
2988        public CommandFuture<List<PermissionInfo>> getPermissions() {
2989                Command cmd = PermissionCommands.permissionList();
2990                return executeAndTransform(cmd, PermissionInfo::new);
2991        }
2992
2993        /**
2994         * Displays the current value of the specified permission for this server query instance.
2995         *
2996         * @param permName
2997         *              the name of the permission
2998         *
2999         * @return the permission value, usually ranging from 0 to 100
3000         *
3001         * @throws TS3CommandFailedException
3002         *              if the execution of a command fails
3003         * @querycommands 1
3004         */
3005        public CommandFuture<Integer> getPermissionValue(String permName) {
3006                Command cmd = PermissionCommands.permGet(permName);
3007                return executeAndReturnIntProperty(cmd, "permvalue");
3008        }
3009
3010        /**
3011         * Displays the current values of the specified permissions for this server query instance.
3012         *
3013         * @param permNames
3014         *              the names of the permissions
3015         *
3016         * @return the permission values, usually ranging from 0 to 100
3017         *
3018         * @throws IllegalArgumentException
3019         *              if {@code permNames} is {@code null}
3020         * @throws TS3CommandFailedException
3021         *              if the execution of a command fails
3022         * @querycommands 1
3023         */
3024        public CommandFuture<int[]> getPermissionValues(String... permNames) {
3025                Command cmd = PermissionCommands.permGet(permNames);
3026                return executeAndReturnIntArray(cmd, "permvalue");
3027        }
3028
3029        /**
3030         * Gets a list of all available tokens to join channel or server groups,
3031         * including their type and group IDs.
3032         *
3033         * @return a list of all generated, but still unclaimed privilege keys
3034         *
3035         * @throws TS3CommandFailedException
3036         *              if the execution of a command fails
3037         * @querycommands 1
3038         * @see #addPrivilegeKey(PrivilegeKeyType, int, int, String)
3039         * @see #usePrivilegeKey(String)
3040         */
3041        public CommandFuture<List<PrivilegeKey>> getPrivilegeKeys() {
3042                Command cmd = PrivilegeKeyCommands.privilegeKeyList();
3043                return executeAndTransform(cmd, PrivilegeKey::new);
3044        }
3045
3046        /**
3047         * Gets a list of all clients in the specified server group.
3048         *
3049         * @param serverGroupId
3050         *              the ID of the server group for which the clients should be looked up
3051         *
3052         * @return a list of all clients in the server group
3053         *
3054         * @throws TS3CommandFailedException
3055         *              if the execution of a command fails
3056         * @querycommands 1
3057         */
3058        public CommandFuture<List<ServerGroupClient>> getServerGroupClients(int serverGroupId) {
3059                Command cmd = ServerGroupCommands.serverGroupClientList(serverGroupId);
3060                return executeAndTransform(cmd, ServerGroupClient::new);
3061        }
3062
3063        /**
3064         * Gets a list of all clients in the specified server group.
3065         *
3066         * @param serverGroup
3067         *              the server group for which the clients should be looked up
3068         *
3069         * @return a list of all clients in the server group
3070         *
3071         * @throws TS3CommandFailedException
3072         *              if the execution of a command fails
3073         * @querycommands 1
3074         */
3075        public CommandFuture<List<ServerGroupClient>> getServerGroupClients(ServerGroup serverGroup) {
3076                return getServerGroupClients(serverGroup.getId());
3077        }
3078
3079        /**
3080         * Gets a list of all permissions assigned to the specified server group.
3081         *
3082         * @param serverGroupId
3083         *              the ID of the server group for which the permissions should be looked up
3084         *
3085         * @return a list of all permissions assigned to the server group
3086         *
3087         * @throws TS3CommandFailedException
3088         *              if the execution of a command fails
3089         * @querycommands 1
3090         * @see ServerGroup#getId()
3091         * @see #getServerGroupPermissions(ServerGroup)
3092         */
3093        public CommandFuture<List<Permission>> getServerGroupPermissions(int serverGroupId) {
3094                Command cmd = PermissionCommands.serverGroupPermList(serverGroupId);
3095                return executeAndTransform(cmd, Permission::new);
3096        }
3097
3098        /**
3099         * Gets a list of all permissions assigned to the specified server group.
3100         *
3101         * @param serverGroup
3102         *              the server group for which the permissions should be looked up
3103         *
3104         * @return a list of all permissions assigned to the server group
3105         *
3106         * @throws TS3CommandFailedException
3107         *              if the execution of a command fails
3108         * @querycommands 1
3109         */
3110        public CommandFuture<List<Permission>> getServerGroupPermissions(ServerGroup serverGroup) {
3111                return getServerGroupPermissions(serverGroup.getId());
3112        }
3113
3114        /**
3115         * Gets a list of all server groups on the virtual server.
3116         * <p>
3117         * Depending on your permissions, the output may also contain
3118         * global server query groups and template groups.
3119         * </p>
3120         *
3121         * @return a list of all server groups
3122         *
3123         * @throws TS3CommandFailedException
3124         *              if the execution of a command fails
3125         * @querycommands 1
3126         */
3127        public CommandFuture<List<ServerGroup>> getServerGroups() {
3128                Command cmd = ServerGroupCommands.serverGroupList();
3129                return executeAndTransform(cmd, ServerGroup::new);
3130        }
3131
3132        /**
3133         * Gets a list of all server groups set for a client.
3134         *
3135         * @param clientDatabaseId
3136         *              the database ID of the client for which the server groups should be looked up
3137         *
3138         * @return a list of all server groups set for the client
3139         *
3140         * @throws TS3CommandFailedException
3141         *              if the execution of a command fails
3142         * @querycommands 2
3143         * @see Client#getDatabaseId()
3144         * @see #getServerGroupsByClient(Client)
3145         */
3146        public CommandFuture<List<ServerGroup>> getServerGroupsByClientId(int clientDatabaseId) {
3147                Command cmd = ServerGroupCommands.serverGroupsByClientId(clientDatabaseId);
3148
3149                CommandFuture<List<Integer>> serverGroupIds = executeAndMap(cmd, response -> response.getInt("sgid"));
3150                CommandFuture<List<ServerGroup>> allServerGroups = getServerGroups();
3151
3152                return findByKey(serverGroupIds, allServerGroups, ServerGroup::getId);
3153        }
3154
3155        /**
3156         * Gets a list of all server groups set for a client.
3157         *
3158         * @param client
3159         *              the client for which the server groups should be looked up
3160         *
3161         * @return a list of all server group set for the client
3162         *
3163         * @throws TS3CommandFailedException
3164         *              if the execution of a command fails
3165         * @querycommands 2
3166         * @see #getServerGroupsByClientId(int)
3167         */
3168        public CommandFuture<List<ServerGroup>> getServerGroupsByClient(Client client) {
3169                return getServerGroupsByClientId(client.getDatabaseId());
3170        }
3171
3172        /**
3173         * Gets the ID of a virtual server by its port.
3174         *
3175         * @param port
3176         *              the port of a virtual server
3177         *
3178         * @return the ID of the virtual server
3179         *
3180         * @throws TS3CommandFailedException
3181         *              if the execution of a command fails
3182         * @querycommands 1
3183         * @see VirtualServer#getPort()
3184         * @see VirtualServer#getId()
3185         */
3186        public CommandFuture<Integer> getServerIdByPort(int port) {
3187                Command cmd = VirtualServerCommands.serverIdGetByPort(port);
3188                return executeAndReturnIntProperty(cmd, "server_id");
3189        }
3190
3191        /**
3192         * Gets detailed information about the virtual server the server query is currently in.
3193         *
3194         * @return information about the current virtual server
3195         *
3196         * @throws TS3CommandFailedException
3197         *              if the execution of a command fails
3198         * @querycommands 1
3199         */
3200        public CommandFuture<VirtualServerInfo> getServerInfo() {
3201                Command cmd = VirtualServerCommands.serverInfo();
3202                return executeAndTransformFirst(cmd, VirtualServerInfo::new);
3203        }
3204
3205        /**
3206         * Gets a list of all server query logins (containing login name, virtual server ID, and client database ID).
3207         * If a virtual server is selected, only the server query logins of the selected virtual server are returned.
3208         *
3209         * @return a list of {@code QueryLogin} objects describing existing server query logins
3210         *
3211         * @throws TS3CommandFailedException
3212         *              if the execution of a command fails
3213         * @querycommands 1
3214         * @see #addServerQueryLogin(String, int)
3215         * @see #deleteServerQueryLogin(int)
3216         * @see #getServerQueryLoginsByName(String)
3217         * @see #updateServerQueryLogin(String)
3218         */
3219        public CommandFuture<List<QueryLogin>> getServerQueryLogins() {
3220                return getServerQueryLoginsByName(null);
3221        }
3222
3223        /**
3224         * Gets a list of all server query logins (containing login name, virtual server ID, and client database ID)
3225         * whose login name matches the specified SQL-like pattern.
3226         * If a virtual server is selected, only the server query logins of the selected virtual server are returned.
3227         *
3228         * @param pattern
3229         *              the SQL-like pattern to match the server query login name against
3230         *
3231         * @return a list of {@code QueryLogin} objects describing existing server query logins
3232         *
3233         * @throws TS3CommandFailedException
3234         *              if the execution of a command fails
3235         * @querycommands 1
3236         * @see #addServerQueryLogin(String, int)
3237         * @see #deleteServerQueryLogin(int)
3238         * @see #getServerQueryLogins()
3239         * @see #updateServerQueryLogin(String)
3240         */
3241        public CommandFuture<List<QueryLogin>> getServerQueryLoginsByName(String pattern) {
3242                Command cmd = QueryLoginCommands.queryLoginList(pattern);
3243                return executeAndTransform(cmd, QueryLogin::new);
3244        }
3245
3246        /**
3247         * Gets the version, build number and platform of the TeamSpeak3 server.
3248         *
3249         * @return the version information of the server
3250         *
3251         * @throws TS3CommandFailedException
3252         *              if the execution of a command fails
3253         * @querycommands 1
3254         */
3255        public CommandFuture<Version> getVersion() {
3256                Command cmd = ServerCommands.version();
3257                return executeAndTransformFirst(cmd, Version::new);
3258        }
3259
3260        /**
3261         * Gets a list of all virtual servers including their ID, status, number of clients online, etc.
3262         *
3263         * @return a list of all virtual servers
3264         *
3265         * @throws TS3CommandFailedException
3266         *              if the execution of a command fails
3267         * @querycommands 1
3268         */
3269        public CommandFuture<List<VirtualServer>> getVirtualServers() {
3270                Command cmd = VirtualServerCommands.serverList();
3271                return executeAndTransform(cmd, VirtualServer::new);
3272        }
3273
3274        /**
3275         * Fetches the specified amount of log entries from the currently selected virtual server.
3276         * If no virtual server is selected, the entries will be read from the server log instead.
3277         *
3278         * @param lines
3279         *              the amount of log entries to fetch, in the range between 1 and 100.
3280         *              Returns 100 entries if the argument is not in range
3281         *
3282         * @return a list of the latest log entries
3283         *
3284         * @throws TS3CommandFailedException
3285         *              if the execution of a command fails
3286         * @querycommands 1
3287         */
3288        public CommandFuture<List<String>> getVirtualServerLogEntries(int lines) {
3289                Command cmd = ServerCommands.logView(lines, false);
3290                return executeAndMap(cmd, response -> response.get("l"));
3291        }
3292
3293        /**
3294         * Fetches the last 100 log entries from the currently selected virtual server.
3295         * If no virtual server is selected, the entries will be read from the server log instead.
3296         *
3297         * @return a list of up to 100 log entries
3298         *
3299         * @throws TS3CommandFailedException
3300         *              if the execution of a command fails
3301         * @querycommands 1
3302         */
3303        public CommandFuture<List<String>> getVirtualServerLogEntries() {
3304                return getVirtualServerLogEntries(100);
3305        }
3306
3307        /**
3308         * Checks whether the client with the specified client ID is online.
3309         * <p>
3310         * Please note that there is no guarantee that the client will still be
3311         * online by the time the next command is executed.
3312         * </p>
3313         *
3314         * @param clientId
3315         *              the ID of the client
3316         *
3317         * @return {@code true} if the client is online, {@code false} otherwise
3318         *
3319         * @querycommands 1
3320         * @see #getClientInfo(int)
3321         */
3322        public CommandFuture<Boolean> isClientOnline(int clientId) {
3323                Command cmd = ClientCommands.clientInfo(clientId);
3324                CommandFuture<Boolean> future = new CommandFuture<>();
3325
3326                cmd.getFuture()
3327                                .onSuccess(__ -> future.set(true))
3328                                .onFailure(transformError(future, 512, false));
3329
3330                commandQueue.enqueueCommand(cmd);
3331                return future;
3332        }
3333
3334        /**
3335         * Checks whether the client with the specified unique identifier is online.
3336         * <p>
3337         * Please note that there is no guarantee that the client will still be
3338         * online by the time the next command is executed.
3339         * </p>
3340         *
3341         * @param clientUId
3342         *              the unique ID of the client
3343         *
3344         * @return {@code true} if the client is online, {@code false} otherwise
3345         *
3346         * @querycommands 1
3347         * @see #getClientByUId(String)
3348         */
3349        public CommandFuture<Boolean> isClientOnline(String clientUId) {
3350                Command cmd = ClientCommands.clientGetIds(clientUId);
3351                CommandFuture<Boolean> future = cmd.getFuture()
3352                                .map(result -> !result.getResponses().isEmpty());
3353
3354                commandQueue.enqueueCommand(cmd);
3355                return future;
3356        }
3357
3358        /**
3359         * Kicks one or more clients from their current channels.
3360         * This will move the kicked clients into the default channel and
3361         * won't do anything if the clients are already in the default channel.
3362         *
3363         * @param clientIds
3364         *              the IDs of the clients to kick
3365         *
3366         * @return a future to track the progress of this command
3367         *
3368         * @throws TS3CommandFailedException
3369         *              if the execution of a command fails
3370         * @querycommands 1
3371         * @see #kickClientFromChannel(Client...)
3372         * @see #kickClientFromChannel(String, int...)
3373         */
3374        public CommandFuture<Void> kickClientFromChannel(int... clientIds) {
3375                return kickClients(ReasonIdentifier.REASON_KICK_CHANNEL, null, clientIds);
3376        }
3377
3378        /**
3379         * Kicks one or more clients from their current channels.
3380         * This will move the kicked clients into the default channel and
3381         * won't do anything if the clients are already in the default channel.
3382         *
3383         * @param clients
3384         *              the clients to kick
3385         *
3386         * @return a future to track the progress of this command
3387         *
3388         * @throws TS3CommandFailedException
3389         *              if the execution of a command fails
3390         * @querycommands 1
3391         * @see #kickClientFromChannel(int...)
3392         * @see #kickClientFromChannel(String, Client...)
3393         */
3394        public CommandFuture<Void> kickClientFromChannel(Client... clients) {
3395                return kickClients(ReasonIdentifier.REASON_KICK_CHANNEL, null, clients);
3396        }
3397
3398        /**
3399         * Kicks one or more clients from their current channels for the specified reason.
3400         * This will move the kicked clients into the default channel and
3401         * won't do anything if the clients are already in the default channel.
3402         *
3403         * @param message
3404         *              the reason message to display to the clients
3405         * @param clientIds
3406         *              the IDs of the clients to kick
3407         *
3408         * @return a future to track the progress of this command
3409         *
3410         * @throws TS3CommandFailedException
3411         *              if the execution of a command fails
3412         * @querycommands 1
3413         * @see Client#getId()
3414         * @see #kickClientFromChannel(int...)
3415         * @see #kickClientFromChannel(String, Client...)
3416         */
3417        public CommandFuture<Void> kickClientFromChannel(String message, int... clientIds) {
3418                return kickClients(ReasonIdentifier.REASON_KICK_CHANNEL, message, clientIds);
3419        }
3420
3421        /**
3422         * Kicks one or more clients from their current channels for the specified reason.
3423         * This will move the kicked clients into the default channel and
3424         * won't do anything if the clients are already in the default channel.
3425         *
3426         * @param message
3427         *              the reason message to display to the clients
3428         * @param clients
3429         *              the clients to kick
3430         *
3431         * @return a future to track the progress of this command
3432         *
3433         * @throws TS3CommandFailedException
3434         *              if the execution of a command fails
3435         * @querycommands 1
3436         * @see #kickClientFromChannel(Client...)
3437         * @see #kickClientFromChannel(String, int...)
3438         */
3439        public CommandFuture<Void> kickClientFromChannel(String message, Client... clients) {
3440                return kickClients(ReasonIdentifier.REASON_KICK_CHANNEL, message, clients);
3441        }
3442
3443        /**
3444         * Kicks one or more clients from the server.
3445         *
3446         * @param clientIds
3447         *              the IDs of the clients to kick
3448         *
3449         * @return a future to track the progress of this command
3450         *
3451         * @throws TS3CommandFailedException
3452         *              if the execution of a command fails
3453         * @querycommands 1
3454         * @see Client#getId()
3455         * @see #kickClientFromServer(Client...)
3456         * @see #kickClientFromServer(String, int...)
3457         */
3458        public CommandFuture<Void> kickClientFromServer(int... clientIds) {
3459                return kickClients(ReasonIdentifier.REASON_KICK_SERVER, null, clientIds);
3460        }
3461
3462        /**
3463         * Kicks one or more clients from the server.
3464         *
3465         * @param clients
3466         *              the clients to kick
3467         *
3468         * @return a future to track the progress of this command
3469         *
3470         * @throws TS3CommandFailedException
3471         *              if the execution of a command fails
3472         * @querycommands 1
3473         * @see #kickClientFromServer(int...)
3474         * @see #kickClientFromServer(String, Client...)
3475         */
3476        public CommandFuture<Void> kickClientFromServer(Client... clients) {
3477                return kickClients(ReasonIdentifier.REASON_KICK_SERVER, null, clients);
3478        }
3479
3480        /**
3481         * Kicks one or more clients from the server for the specified reason.
3482         *
3483         * @param message
3484         *              the reason message to display to the clients
3485         * @param clientIds
3486         *              the IDs of the clients to kick
3487         *
3488         * @return a future to track the progress of this command
3489         *
3490         * @throws TS3CommandFailedException
3491         *              if the execution of a command fails
3492         * @querycommands 1
3493         * @see Client#getId()
3494         * @see #kickClientFromServer(int...)
3495         * @see #kickClientFromServer(String, Client...)
3496         */
3497        public CommandFuture<Void> kickClientFromServer(String message, int... clientIds) {
3498                return kickClients(ReasonIdentifier.REASON_KICK_SERVER, message, clientIds);
3499        }
3500
3501        /**
3502         * Kicks one or more clients from the server for the specified reason.
3503         *
3504         * @param message
3505         *              the reason message to display to the clients
3506         * @param clients
3507         *              the clients to kick
3508         *
3509         * @return a future to track the progress of this command
3510         *
3511         * @throws TS3CommandFailedException
3512         *              if the execution of a command fails
3513         * @querycommands 1
3514         * @see #kickClientFromServer(Client...)
3515         * @see #kickClientFromServer(String, int...)
3516         */
3517        public CommandFuture<Void> kickClientFromServer(String message, Client... clients) {
3518                return kickClients(ReasonIdentifier.REASON_KICK_SERVER, message, clients);
3519        }
3520
3521        /**
3522         * Kicks a list of clients from either the channel or the server for a given reason.
3523         *
3524         * @param reason
3525         *              where to kick the clients from
3526         * @param message
3527         *              the reason message to display to the clients
3528         * @param clients
3529         *              the clients to kick
3530         *
3531         * @return a future to track the progress of this command
3532         *
3533         * @throws TS3CommandFailedException
3534         *              if the execution of a command fails
3535         * @querycommands 1
3536         */
3537        private CommandFuture<Void> kickClients(ReasonIdentifier reason, String message, Client... clients) {
3538                int[] clientIds = new int[clients.length];
3539                for (int i = 0; i < clients.length; ++i) {
3540                        clientIds[i] = clients[i].getId();
3541                }
3542                return kickClients(reason, message, clientIds);
3543        }
3544
3545        /**
3546         * Kicks a list of clients from either the channel or the server for a given reason.
3547         *
3548         * @param reason
3549         *              where to kick the clients from
3550         * @param message
3551         *              the reason message to display to the clients
3552         * @param clientIds
3553         *              the IDs of the clients to kick
3554         *
3555         * @return a future to track the progress of this command
3556         *
3557         * @throws TS3CommandFailedException
3558         *              if the execution of a command fails
3559         * @querycommands 1
3560         * @see Client#getId()
3561         */
3562        private CommandFuture<Void> kickClients(ReasonIdentifier reason, String message, int... clientIds) {
3563                Command cmd = ClientCommands.clientKick(reason, message, clientIds);
3564                return executeAndReturnError(cmd);
3565        }
3566
3567        /**
3568         * Logs the server query in using the specified username and password.
3569         * <p>
3570         * Note that you can also set the login in the {@link TS3Config},
3571         * so that you will be logged in right after the connection is established.
3572         * </p>
3573         *
3574         * @param username
3575         *              the username of the server query
3576         * @param password
3577         *              the password to use
3578         *
3579         * @return a future to track the progress of this command
3580         *
3581         * @throws TS3CommandFailedException
3582         *              if the execution of a command fails
3583         * @querycommands 1
3584         * @see #logout()
3585         */
3586        public CommandFuture<Void> login(String username, String password) {
3587                Command cmd = QueryCommands.logIn(username, password);
3588                return executeAndReturnError(cmd);
3589        }
3590
3591        /**
3592         * Logs the server query out and deselects the current virtual server.
3593         *
3594         * @return a future to track the progress of this command
3595         *
3596         * @throws TS3CommandFailedException
3597         *              if the execution of a command fails
3598         * @querycommands 1
3599         * @see #login(String, String)
3600         */
3601        public CommandFuture<Void> logout() {
3602                Command cmd = QueryCommands.logOut();
3603                return executeAndReturnError(cmd);
3604        }
3605
3606        /**
3607         * Moves a channel to a new parent channel specified by its ID.
3608         * To move a channel to root level, set {@code channelTargetId} to {@code 0}.
3609         * <p>
3610         * This will move the channel right below the specified parent channel, above all other child channels.
3611         * This command will fail if the channel already has the specified target channel as the parent channel.
3612         * </p>
3613         *
3614         * @param channelId
3615         *              the channel to move
3616         * @param channelTargetId
3617         *              the new parent channel for the specified channel
3618         *
3619         * @return a future to track the progress of this command
3620         *
3621         * @throws TS3CommandFailedException
3622         *              if the execution of a command fails
3623         * @querycommands 1
3624         * @see Channel#getId()
3625         * @see #moveChannel(int, int, int)
3626         */
3627        public CommandFuture<Void> moveChannel(int channelId, int channelTargetId) {
3628                return moveChannel(channelId, channelTargetId, 0);
3629        }
3630
3631        /**
3632         * Moves a channel to a new parent channel specified by its ID.
3633         * To move a channel to root level, set {@code channelTargetId} to {@code 0}.
3634         * <p>
3635         * The channel will be ordered below the channel with the ID specified by {@code order}.
3636         * To move the channel right below the parent channel, set {@code order} to {@code 0}.
3637         * </p><p>
3638         * Note that you can't re-order a channel without also changing its parent channel with this method.
3639         * Use {@link #editChannel(int, ChannelProperty, String)} to change {@link ChannelProperty#CHANNEL_ORDER} instead.
3640         * </p>
3641         *
3642         * @param channelId
3643         *              the channel to move
3644         * @param channelTargetId
3645         *              the new parent channel for the specified channel
3646         * @param order
3647         *              the channel to sort the specified channel below
3648         *
3649         * @return a future to track the progress of this command
3650         *
3651         * @throws TS3CommandFailedException
3652         *              if the execution of a command fails
3653         * @querycommands 1
3654         * @see Channel#getId()
3655         * @see #moveChannel(int, int)
3656         */
3657        public CommandFuture<Void> moveChannel(int channelId, int channelTargetId, int order) {
3658                Command cmd = ChannelCommands.channelMove(channelId, channelTargetId, order);
3659                return executeAndReturnError(cmd);
3660        }
3661
3662        /**
3663         * Moves a single client into a channel.
3664         * <p>
3665         * Consider using {@link #moveClients(int[], int)} to move multiple clients.
3666         * </p>
3667         *
3668         * @param clientId
3669         *              the ID of the client to move
3670         * @param channelId
3671         *              the ID of the channel to move the client into
3672         *
3673         * @return a future to track the progress of this command
3674         *
3675         * @throws TS3CommandFailedException
3676         *              if the execution of a command fails
3677         * @querycommands 1
3678         * @see Client#getId()
3679         * @see Channel#getId()
3680         */
3681        public CommandFuture<Void> moveClient(int clientId, int channelId) {
3682                return moveClient(clientId, channelId, null);
3683        }
3684
3685        /**
3686         * Moves multiple clients into a channel.
3687         * Immediately returns {@code true} for an empty client ID array.
3688         * <p>
3689         * Use this method instead of {@link #moveClient(int, int)} for moving
3690         * several clients as this will only send 1 command to the server and thus complete faster.
3691         * </p>
3692         *
3693         * @param clientIds
3694         *              the IDs of the clients to move, cannot be {@code null}
3695         * @param channelId
3696         *              the ID of the channel to move the clients into
3697         *
3698         * @return a future to track the progress of this command
3699         *
3700         * @throws IllegalArgumentException
3701         *              if {@code clientIds} is {@code null}
3702         * @throws TS3CommandFailedException
3703         *              if the execution of a command fails
3704         * @querycommands 1
3705         * @see Client#getId()
3706         * @see Channel#getId()
3707         */
3708        public CommandFuture<Void> moveClients(int[] clientIds, int channelId) {
3709                return moveClients(clientIds, channelId, null);
3710        }
3711
3712        /**
3713         * Moves a single client into a channel.
3714         * <p>
3715         * Consider using {@link #moveClients(Client[], ChannelBase)} to move multiple clients.
3716         * </p>
3717         *
3718         * @param client
3719         *              the client to move, cannot be {@code null}
3720         * @param channel
3721         *              the channel to move the client into, cannot be {@code null}
3722         *
3723         * @return a future to track the progress of this command
3724         *
3725         * @throws IllegalArgumentException
3726         *              if {@code client} or {@code channel} is {@code null}
3727         * @throws TS3CommandFailedException
3728         *              if the execution of a command fails
3729         * @querycommands 1
3730         */
3731        public CommandFuture<Void> moveClient(Client client, ChannelBase channel) {
3732                return moveClient(client, channel, null);
3733        }
3734
3735        /**
3736         * Moves multiple clients into a channel.
3737         * Immediately returns {@code true} for an empty client array.
3738         * <p>
3739         * Use this method instead of {@link #moveClient(Client, ChannelBase)} for moving
3740         * several clients as this will only send 1 command to the server and thus complete faster.
3741         * </p>
3742         *
3743         * @param clients
3744         *              the clients to move, cannot be {@code null}
3745         * @param channel
3746         *              the channel to move the clients into, cannot be {@code null}
3747         *
3748         * @return a future to track the progress of this command
3749         *
3750         * @throws IllegalArgumentException
3751         *              if {@code clients} or {@code channel} is {@code null}
3752         * @throws TS3CommandFailedException
3753         *              if the execution of a command fails
3754         * @querycommands 1
3755         */
3756        public CommandFuture<Void> moveClients(Client[] clients, ChannelBase channel) {
3757                return moveClients(clients, channel, null);
3758        }
3759
3760        /**
3761         * Moves a single client into a channel using the specified password.
3762         * <p>
3763         * Consider using {@link #moveClients(int[], int, String)} to move multiple clients.
3764         * </p>
3765         *
3766         * @param clientId
3767         *              the ID of the client to move
3768         * @param channelId
3769         *              the ID of the channel to move the client into
3770         * @param channelPassword
3771         *              the password of the channel, can be {@code null}
3772         *
3773         * @return a future to track the progress of this command
3774         *
3775         * @throws TS3CommandFailedException
3776         *              if the execution of a command fails
3777         * @querycommands 1
3778         * @see Client#getId()
3779         * @see Channel#getId()
3780         */
3781        public CommandFuture<Void> moveClient(int clientId, int channelId, String channelPassword) {
3782                Command cmd = ClientCommands.clientMove(clientId, channelId, channelPassword);
3783                return executeAndReturnError(cmd);
3784        }
3785
3786        /**
3787         * Moves multiple clients into a channel using the specified password.
3788         * Immediately returns {@code true} for an empty client ID array.
3789         * <p>
3790         * Use this method instead of {@link #moveClient(int, int, String)} for moving
3791         * several clients as this will only send 1 command to the server and thus complete faster.
3792         * </p>
3793         *
3794         * @param clientIds
3795         *              the IDs of the clients to move, cannot be {@code null}
3796         * @param channelId
3797         *              the ID of the channel to move the clients into
3798         * @param channelPassword
3799         *              the password of the channel, can be {@code null}
3800         *
3801         * @return a future to track the progress of this command
3802         *
3803         * @throws IllegalArgumentException
3804         *              if {@code clientIds} is {@code null}
3805         * @throws TS3CommandFailedException
3806         *              if the execution of a command fails
3807         * @querycommands 1
3808         * @see Client#getId()
3809         * @see Channel#getId()
3810         */
3811        public CommandFuture<Void> moveClients(int[] clientIds, int channelId, String channelPassword) {
3812                if (clientIds == null) throw new IllegalArgumentException("Client ID array was null");
3813                if (clientIds.length == 0) return CommandFuture.immediate(null); // Success
3814
3815                Command cmd = ClientCommands.clientMove(clientIds, channelId, channelPassword);
3816                return executeAndReturnError(cmd);
3817        }
3818
3819        /**
3820         * Moves a single client into a channel using the specified password.
3821         * <p>
3822         * Consider using {@link #moveClients(Client[], ChannelBase, String)} to move multiple clients.
3823         * </p>
3824         *
3825         * @param client
3826         *              the client to move, cannot be {@code null}
3827         * @param channel
3828         *              the channel to move the client into, cannot be {@code null}
3829         * @param channelPassword
3830         *              the password of the channel, can be {@code null}
3831         *
3832         * @return a future to track the progress of this command
3833         *
3834         * @throws IllegalArgumentException
3835         *              if {@code client} or {@code channel} is {@code null}
3836         * @throws TS3CommandFailedException
3837         *              if the execution of a command fails
3838         * @querycommands 1
3839         */
3840        public CommandFuture<Void> moveClient(Client client, ChannelBase channel, String channelPassword) {
3841                if (client == null) throw new IllegalArgumentException("Client cannot be null");
3842                if (channel == null) throw new IllegalArgumentException("Channel cannot be null");
3843
3844                return moveClient(client.getId(), channel.getId(), channelPassword);
3845        }
3846
3847        /**
3848         * Moves multiple clients into a channel using the specified password.
3849         * Immediately returns {@code true} for an empty client array.
3850         * <p>
3851         * Use this method instead of {@link #moveClient(Client, ChannelBase, String)} for moving
3852         * several clients as this will only send 1 command to the server and thus complete faster.
3853         * </p>
3854         *
3855         * @param clients
3856         *              the clients to move, cannot be {@code null}
3857         * @param channel
3858         *              the channel to move the clients into, cannot be {@code null}
3859         * @param channelPassword
3860         *              the password of the channel, can be {@code null}
3861         *
3862         * @return a future to track the progress of this command
3863         *
3864         * @throws IllegalArgumentException
3865         *              if {@code clients} or {@code channel} is {@code null}
3866         * @throws TS3CommandFailedException
3867         *              if the execution of a command fails
3868         * @querycommands 1
3869         */
3870        public CommandFuture<Void> moveClients(Client[] clients, ChannelBase channel, String channelPassword) {
3871                if (clients == null) throw new IllegalArgumentException("Client array cannot be null");
3872                if (channel == null) throw new IllegalArgumentException("Channel cannot be null");
3873
3874                int[] clientIds = new int[clients.length];
3875                for (int i = 0; i < clients.length; i++) {
3876                        clientIds[i] = clients[i].getId();
3877                }
3878                return moveClients(clientIds, channel.getId(), channelPassword);
3879        }
3880
3881        /**
3882         * Moves and renames a file on the file repository within the same channel.
3883         *
3884         * @param oldPath
3885         *              the current path to the file
3886         * @param newPath
3887         *              the desired new path
3888         * @param channelId
3889         *              the ID of the channel the file resides in
3890         *
3891         * @return a future to track the progress of this command
3892         *
3893         * @throws TS3CommandFailedException
3894         *              if the execution of a command fails
3895         * @querycommands 1
3896         * @see FileInfo#getPath()
3897         * @see Channel#getId()
3898         * @see #moveFile(String, String, int, int) moveFile to a different channel
3899         */
3900        public CommandFuture<Void> moveFile(String oldPath, String newPath, int channelId) {
3901                return moveFile(oldPath, newPath, channelId, null);
3902        }
3903
3904        /**
3905         * Renames a file on the file repository and moves it to a new path in a different channel.
3906         *
3907         * @param oldPath
3908         *              the current path to the file
3909         * @param newPath
3910         *              the desired new path
3911         * @param oldChannelId
3912         *              the ID of the channel the file currently resides in
3913         * @param newChannelId
3914         *              the ID of the channel the file should be moved to
3915         *
3916         * @return a future to track the progress of this command
3917         *
3918         * @throws TS3CommandFailedException
3919         *              if the execution of a command fails
3920         * @querycommands 1
3921         * @see FileInfo#getPath()
3922         * @see Channel#getId()
3923         * @see #moveFile(String, String, int) moveFile within the same channel
3924         */
3925        public CommandFuture<Void> moveFile(String oldPath, String newPath, int oldChannelId, int newChannelId) {
3926                return moveFile(oldPath, newPath, oldChannelId, null, newChannelId, null);
3927        }
3928
3929        /**
3930         * Moves and renames a file on the file repository within the same channel.
3931         *
3932         * @param oldPath
3933         *              the current path to the file
3934         * @param newPath
3935         *              the desired new path
3936         * @param channelId
3937         *              the ID of the channel the file resides in
3938         * @param channelPassword
3939         *              the password of the channel
3940         *
3941         * @return a future to track the progress of this command
3942         *
3943         * @throws TS3CommandFailedException
3944         *              if the execution of a command fails
3945         * @querycommands 1
3946         * @see FileInfo#getPath()
3947         * @see Channel#getId()
3948         * @see #moveFile(String, String, int, String, int, String) moveFile to a different channel
3949         */
3950        public CommandFuture<Void> moveFile(String oldPath, String newPath, int channelId, String channelPassword) {
3951                Command cmd = FileCommands.ftRenameFile(oldPath, newPath, channelId, channelPassword);
3952                return executeAndReturnError(cmd);
3953        }
3954
3955        /**
3956         * Renames a file on the file repository and moves it to a new path in a different channel.
3957         *
3958         * @param oldPath
3959         *              the current path to the file
3960         * @param newPath
3961         *              the desired new path
3962         * @param oldChannelId
3963         *              the ID of the channel the file currently resides in
3964         * @param oldPassword
3965         *              the password of the current channel
3966         * @param newChannelId
3967         *              the ID of the channel the file should be moved to
3968         * @param newPassword
3969         *              the password of the new channel
3970         *
3971         * @return a future to track the progress of this command
3972         *
3973         * @throws TS3CommandFailedException
3974         *              if the execution of a command fails
3975         * @querycommands 1
3976         * @see FileInfo#getPath()
3977         * @see Channel#getId()
3978         * @see #moveFile(String, String, int, String) moveFile within the same channel
3979         */
3980        public CommandFuture<Void> moveFile(String oldPath, String newPath, int oldChannelId, String oldPassword, int newChannelId, String newPassword) {
3981                Command cmd = FileCommands.ftRenameFile(oldPath, newPath, oldChannelId, oldPassword, newChannelId, newPassword);
3982                return executeAndReturnError(cmd);
3983        }
3984
3985        /**
3986         * Moves the server query into a channel.
3987         *
3988         * @param channelId
3989         *              the ID of the channel to move the server query into
3990         *
3991         * @return a future to track the progress of this command
3992         *
3993         * @throws TS3CommandFailedException
3994         *              if the execution of a command fails
3995         * @querycommands 1
3996         * @see Channel#getId()
3997         */
3998        public CommandFuture<Void> moveQuery(int channelId) {
3999                return moveClient(0, channelId, null);
4000        }
4001
4002        /**
4003         * Moves the server query into a channel.
4004         *
4005         * @param channel
4006         *              the channel to move the server query into, cannot be {@code null}
4007         *
4008         * @return a future to track the progress of this command
4009         *
4010         * @throws IllegalArgumentException
4011         *              if {@code channel} is {@code null}
4012         * @throws TS3CommandFailedException
4013         *              if the execution of a command fails
4014         * @querycommands 1
4015         */
4016        public CommandFuture<Void> moveQuery(ChannelBase channel) {
4017                if (channel == null) throw new IllegalArgumentException("Channel cannot be null");
4018
4019                return moveClient(0, channel.getId(), null);
4020        }
4021
4022        /**
4023         * Moves the server query into a channel using the specified password.
4024         *
4025         * @param channelId
4026         *              the ID of the channel to move the client into
4027         * @param channelPassword
4028         *              the password of the channel, can be {@code null}
4029         *
4030         * @return a future to track the progress of this command
4031         *
4032         * @throws TS3CommandFailedException
4033         *              if the execution of a command fails
4034         * @querycommands 1
4035         * @see Channel#getId()
4036         */
4037        public CommandFuture<Void> moveQuery(int channelId, String channelPassword) {
4038                return moveClient(0, channelId, channelPassword);
4039        }
4040
4041        /**
4042         * Moves the server query into a channel using the specified password.
4043         *
4044         * @param channel
4045         *              the channel to move the client into, cannot be {@code null}
4046         * @param channelPassword
4047         *              the password of the channel, can be {@code null}
4048         *
4049         * @return a future to track the progress of this command
4050         *
4051         * @throws IllegalArgumentException
4052         *              if {@code channel} is {@code null}
4053         * @throws TS3CommandFailedException
4054         *              if the execution of a command fails
4055         * @querycommands 1
4056         */
4057        public CommandFuture<Void> moveQuery(ChannelBase channel, String channelPassword) {
4058                if (channel == null) throw new IllegalArgumentException("Channel cannot be null");
4059
4060                return moveClient(0, channel.getId(), channelPassword);
4061        }
4062
4063        /**
4064         * Pokes the client with the specified client ID.
4065         * This opens up a small popup window for the client containing your message and plays a sound.
4066         * The displayed message will be formatted like this: <br>
4067         * {@code hh:mm:ss - "Your Nickname" poked you: <your message in green color>}
4068         * <p>
4069         * The displayed message length is limited to 100 UTF-8 bytes.
4070         * If a client has already received a poke message, all subsequent pokes will simply add a line
4071         * to the already opened popup window and will still play a sound.
4072         * </p>
4073         *
4074         * @param clientId
4075         *              the ID of the client to poke
4076         * @param message
4077         *              the message to send, may contain BB codes
4078         *
4079         * @return a future to track the progress of this command
4080         *
4081         * @throws TS3CommandFailedException
4082         *              if the execution of a command fails
4083         * @querycommands 1
4084         * @see Client#getId()
4085         */
4086        public CommandFuture<Void> pokeClient(int clientId, String message) {
4087                Command cmd = ClientCommands.clientPoke(clientId, message);
4088                return executeAndReturnError(cmd);
4089        }
4090
4091        /**
4092         * Terminates the connection with the TeamSpeak3 server.
4093         * <p>
4094         * This command should never be executed by a user of this API,
4095         * as it leaves the query in an undefined state. To terminate
4096         * a connection regularly, use {@link TS3Query#exit()}.
4097         * </p>
4098         *
4099         * @throws TS3CommandFailedException
4100         *              if the execution of a command fails
4101         * @querycommands 1
4102         */
4103        CommandFuture<Void> quit() {
4104                Command cmd = QueryCommands.quit();
4105                return executeAndReturnError(cmd);
4106        }
4107
4108        /**
4109         * Registers the server query to receive notifications about all server events.
4110         * <p>
4111         * This means that the following actions will trigger event notifications:
4112         * </p>
4113         * <ul>
4114         * <li>A client joins the server or disconnects from it</li>
4115         * <li>A client switches channels</li>
4116         * <li>A client sends a server message</li>
4117         * <li>A client sends a channel message <b>in the channel the query is in</b></li>
4118         * <li>A client sends a private message to <b>the server query</b></li>
4119         * <li>A client uses a privilege key</li>
4120         * </ul>
4121         * <p>
4122         * The limitations to when the query receives notifications about chat events cannot be circumvented.
4123         * </p>
4124         * To be able to process these events in your application, register an event listener.
4125         *
4126         * @return whether all commands succeeded or not
4127         *
4128         * @throws TS3CommandFailedException
4129         *              if the execution of a command fails
4130         * @querycommands 6
4131         * @see #addTS3Listeners(TS3Listener...)
4132         */
4133        public CommandFuture<Void> registerAllEvents() {
4134                Collection<CommandFuture<Void>> eventFutures = Arrays.asList(
4135                                registerEvent(TS3EventType.SERVER),
4136                                registerEvent(TS3EventType.TEXT_SERVER),
4137                                registerEvent(TS3EventType.CHANNEL, 0),
4138                                registerEvent(TS3EventType.TEXT_CHANNEL, 0),
4139                                registerEvent(TS3EventType.TEXT_PRIVATE),
4140                                registerEvent(TS3EventType.PRIVILEGE_KEY_USED)
4141                );
4142
4143                return CommandFuture.ofAll(eventFutures)
4144                                .map(__ -> null); // Return success as Void, not List<Void>
4145        }
4146
4147        /**
4148         * Registers the server query to receive notifications about a given event type.
4149         * <p>
4150         * If used with {@link TS3EventType#TEXT_CHANNEL}, this will listen to chat events in the current channel.
4151         * If used with {@link TS3EventType#CHANNEL}, this will listen to <b>all</b> channel events.
4152         * To specify a different channel for channel events, use {@link #registerEvent(TS3EventType, int)}.
4153         * </p>
4154         *
4155         * @param eventType
4156         *              the event type to be notified about
4157         *
4158         * @return a future to track the progress of this command
4159         *
4160         * @throws TS3CommandFailedException
4161         *              if the execution of a command fails
4162         * @querycommands 1
4163         * @see #addTS3Listeners(TS3Listener...)
4164         * @see #registerEvent(TS3EventType, int)
4165         * @see #registerAllEvents()
4166         */
4167        public CommandFuture<Void> registerEvent(TS3EventType eventType) {
4168                if (eventType == TS3EventType.CHANNEL || eventType == TS3EventType.TEXT_CHANNEL) {
4169                        return registerEvent(eventType, 0);
4170                } else {
4171                        return registerEvent(eventType, -1);
4172                }
4173        }
4174
4175        /**
4176         * Registers the server query to receive notifications about a given event type.
4177         *
4178         * @param eventType
4179         *              the event type to be notified about
4180         * @param channelId
4181         *              the ID of the channel to listen to, will be ignored if set to {@code -1}.
4182         *              Can be set to {@code 0} for {@link TS3EventType#CHANNEL} to receive notifications about all channel switches.
4183         *
4184         * @return a future to track the progress of this command
4185         *
4186         * @throws TS3CommandFailedException
4187         *              if the execution of a command fails
4188         * @querycommands 1
4189         * @see Channel#getId()
4190         * @see #addTS3Listeners(TS3Listener...)
4191         * @see #registerAllEvents()
4192         */
4193        public CommandFuture<Void> registerEvent(TS3EventType eventType, int channelId) {
4194                Command cmd = QueryCommands.serverNotifyRegister(eventType, channelId);
4195                return executeAndReturnError(cmd);
4196        }
4197
4198        /**
4199         * Registers the server query to receive notifications about multiple given event types.
4200         * <p>
4201         * If used with {@link TS3EventType#TEXT_CHANNEL}, this will listen to chat events in the current channel.
4202         * If used with {@link TS3EventType#CHANNEL}, this will listen to <b>all</b> channel events.
4203         * To specify a different channel for channel events, use {@link #registerEvent(TS3EventType, int)}.
4204         * </p>
4205         *
4206         * @param eventTypes
4207         *              the event types to be notified about
4208         *
4209         * @return a future to track the progress of this command
4210         *
4211         * @throws TS3CommandFailedException
4212         *              if the execution of a command fails
4213         * @querycommands n, one command per TS3EventType
4214         * @see #addTS3Listeners(TS3Listener...)
4215         * @see #registerEvent(TS3EventType, int)
4216         * @see #registerAllEvents()
4217         */
4218        public CommandFuture<Void> registerEvents(TS3EventType... eventTypes) {
4219                if (eventTypes.length == 0) return CommandFuture.immediate(null); // Success
4220
4221                Collection<CommandFuture<Void>> registerFutures = new ArrayList<>(eventTypes.length);
4222                for (TS3EventType type : eventTypes) {
4223                        registerFutures.add(registerEvent(type));
4224                }
4225
4226                return CommandFuture.ofAll(registerFutures)
4227                                .map(__ -> null); // Return success as Void, not List<Void>
4228        }
4229
4230        /**
4231         * Removes the client specified by its database ID from the specified server group.
4232         *
4233         * @param serverGroupId
4234         *              the ID of the server group
4235         * @param clientDatabaseId
4236         *              the database ID of the client
4237         *
4238         * @return a future to track the progress of this command
4239         *
4240         * @throws TS3CommandFailedException
4241         *              if the execution of a command fails
4242         * @querycommands 1
4243         * @see ServerGroup#getId()
4244         * @see Client#getDatabaseId()
4245         * @see #removeClientFromServerGroup(ServerGroup, Client)
4246         */
4247        public CommandFuture<Void> removeClientFromServerGroup(int serverGroupId, int clientDatabaseId) {
4248                Command cmd = ServerGroupCommands.serverGroupDelClient(serverGroupId, clientDatabaseId);
4249                return executeAndReturnError(cmd);
4250        }
4251
4252        /**
4253         * Removes the specified client from the specified server group.
4254         *
4255         * @param serverGroup
4256         *              the server group to remove the client from
4257         * @param client
4258         *              the client to remove from the server group
4259         *
4260         * @return a future to track the progress of this command
4261         *
4262         * @throws TS3CommandFailedException
4263         *              if the execution of a command fails
4264         * @querycommands 1
4265         * @see #removeClientFromServerGroup(int, int)
4266         */
4267        public CommandFuture<Void> removeClientFromServerGroup(ServerGroup serverGroup, Client client) {
4268                return removeClientFromServerGroup(serverGroup.getId(), client.getDatabaseId());
4269        }
4270
4271        /**
4272         * Removes one or more {@link TS3Listener}s to the event manager of the query.
4273         * <p>
4274         * If a listener was not actually registered, it will be ignored and no exception will be thrown.
4275         * </p>
4276         *
4277         * @param listeners
4278         *              one or more listeners to remove
4279         *
4280         * @see #addTS3Listeners(TS3Listener...)
4281         * @see TS3Listener
4282         * @see TS3EventType
4283         */
4284        public void removeTS3Listeners(TS3Listener... listeners) {
4285                query.getEventManager().removeListeners(listeners);
4286        }
4287
4288        /**
4289         * Renames the channel group with the specified ID.
4290         *
4291         * @param channelGroupId
4292         *              the ID of the channel group to rename
4293         * @param name
4294         *              the new name for the channel group
4295         *
4296         * @return a future to track the progress of this command
4297         *
4298         * @throws TS3CommandFailedException
4299         *              if the execution of a command fails
4300         * @querycommands 1
4301         * @see ChannelGroup#getId()
4302         * @see #renameChannelGroup(ChannelGroup, String)
4303         */
4304        public CommandFuture<Void> renameChannelGroup(int channelGroupId, String name) {
4305                Command cmd = ChannelGroupCommands.channelGroupRename(channelGroupId, name);
4306                return executeAndReturnError(cmd);
4307        }
4308
4309        /**
4310         * Renames the specified channel group.
4311         *
4312         * @param channelGroup
4313         *              the channel group to rename
4314         * @param name
4315         *              the new name for the channel group
4316         *
4317         * @return a future to track the progress of this command
4318         *
4319         * @throws TS3CommandFailedException
4320         *              if the execution of a command fails
4321         * @querycommands 1
4322         * @see #renameChannelGroup(int, String)
4323         */
4324        public CommandFuture<Void> renameChannelGroup(ChannelGroup channelGroup, String name) {
4325                return renameChannelGroup(channelGroup.getId(), name);
4326        }
4327
4328        /**
4329         * Renames the server group with the specified ID.
4330         *
4331         * @param serverGroupId
4332         *              the ID of the server group to rename
4333         * @param name
4334         *              the new name for the server group
4335         *
4336         * @return a future to track the progress of this command
4337         *
4338         * @throws TS3CommandFailedException
4339         *              if the execution of a command fails
4340         * @querycommands 1
4341         * @see ServerGroup#getId()
4342         * @see #renameServerGroup(ServerGroup, String)
4343         */
4344        public CommandFuture<Void> renameServerGroup(int serverGroupId, String name) {
4345                Command cmd = ServerGroupCommands.serverGroupRename(serverGroupId, name);
4346                return executeAndReturnError(cmd);
4347        }
4348
4349        /**
4350         * Renames the specified server group.
4351         *
4352         * @param serverGroup
4353         *              the server group to rename
4354         * @param name
4355         *              the new name for the server group
4356         *
4357         * @return a future to track the progress of this command
4358         *
4359         * @throws TS3CommandFailedException
4360         *              if the execution of a command fails
4361         * @querycommands 1
4362         * @see #renameServerGroup(int, String)
4363         */
4364        public CommandFuture<Void> renameServerGroup(ServerGroup serverGroup, String name) {
4365                return renameChannelGroup(serverGroup.getId(), name);
4366        }
4367
4368        /**
4369         * Resets all permissions and deletes all server / channel groups. Use carefully.
4370         *
4371         * @return a token for a new administrator account
4372         *
4373         * @throws TS3CommandFailedException
4374         *              if the execution of a command fails
4375         * @querycommands 1
4376         */
4377        public CommandFuture<String> resetPermissions() {
4378                Command cmd = PermissionCommands.permReset();
4379                return executeAndReturnStringProperty(cmd, "token");
4380        }
4381
4382        /**
4383         * Finds all clients that have any value associated with the {@code key} custom client property,
4384         * and returns the client's database ID and the key and value of the matching custom property.
4385         *
4386         * @param key
4387         *              the key to search for, cannot be {@code null}
4388         *
4389         * @return a list of client database IDs and their matching custom client properties
4390         *
4391         * @throws TS3CommandFailedException
4392         *              if the execution of a command fails
4393         * @querycommands 1
4394         * @see Client#getDatabaseId()
4395         * @see #searchCustomClientProperty(String, String)
4396         * @see #getCustomClientProperties(int)
4397         */
4398        public CommandFuture<List<CustomPropertyAssignment>> searchCustomClientProperty(String key) {
4399                return searchCustomClientProperty(key, "%");
4400        }
4401
4402        /**
4403         * Finds all clients whose value associated with the {@code key} custom client property matches the
4404         * SQL-like pattern {@code valuePattern}, and returns the client's database ID and the key and value
4405         * of the matching custom property.
4406         * <p>
4407         * Patterns are case insensitive. They support the wildcard characters {@code %}, which matches any sequence of
4408         * zero or more characters, and {@code _}, which matches exactly one arbitrary character.
4409         * </p>
4410         *
4411         * @param key
4412         *              the key to search for, cannot be {@code null}
4413         * @param valuePattern
4414         *              the pattern that values need to match to be included
4415         *
4416         * @return a list of client database IDs and their matching custom client properties
4417         *
4418         * @throws TS3CommandFailedException
4419         *              if the execution of a command fails
4420         * @querycommands 1
4421         * @see Client#getDatabaseId()
4422         * @see #searchCustomClientProperty(String)
4423         * @see #getCustomClientProperties(int)
4424         */
4425        public CommandFuture<List<CustomPropertyAssignment>> searchCustomClientProperty(String key, String valuePattern) {
4426                if (key == null) throw new IllegalArgumentException("Key cannot be null");
4427
4428                Command cmd = CustomPropertyCommands.customSearch(key, valuePattern);
4429                return executeAndTransform(cmd, CustomPropertyAssignment::new);
4430        }
4431
4432        /**
4433         * Moves the server query into the virtual server with the specified ID.
4434         *
4435         * @param id
4436         *              the ID of the virtual server
4437         *
4438         * @return a future to track the progress of this command
4439         *
4440         * @throws TS3CommandFailedException
4441         *              if the execution of a command fails
4442         * @querycommands 1
4443         * @see VirtualServer#getId()
4444         * @see #selectVirtualServerById(int, String)
4445         * @see #selectVirtualServerByPort(int)
4446         * @see #selectVirtualServer(VirtualServer)
4447         */
4448        public CommandFuture<Void> selectVirtualServerById(int id) {
4449                return selectVirtualServerById(id, null);
4450        }
4451
4452        /**
4453         * Moves the server query into the virtual server with the specified ID
4454         * and sets the server query's nickname.
4455         * <p>
4456         * The nickname must be between 3 and 30 UTF-8 bytes long. BB codes will be ignored.
4457         * </p>
4458         *
4459         * @param id
4460         *              the ID of the virtual server
4461         * @param nickname
4462         *              the nickname, or {@code null} if the nickname should not be set
4463         *
4464         * @return a future to track the progress of this command
4465         *
4466         * @throws TS3CommandFailedException
4467         *              if the execution of a command fails
4468         * @querycommands 1
4469         * @see VirtualServer#getId()
4470         * @see #selectVirtualServerById(int)
4471         * @see #selectVirtualServerByPort(int, String)
4472         * @see #selectVirtualServer(VirtualServer, String)
4473         */
4474        public CommandFuture<Void> selectVirtualServerById(int id, String nickname) {
4475                Command cmd = QueryCommands.useId(id, nickname);
4476                return executeAndReturnError(cmd);
4477        }
4478
4479        /**
4480         * Moves the server query into the virtual server with the specified voice port.
4481         *
4482         * @param port
4483         *              the voice port of the virtual server
4484         *
4485         * @return a future to track the progress of this command
4486         *
4487         * @throws TS3CommandFailedException
4488         *              if the execution of a command fails
4489         * @querycommands 1
4490         * @see VirtualServer#getPort()
4491         * @see #selectVirtualServerById(int)
4492         * @see #selectVirtualServerByPort(int, String)
4493         * @see #selectVirtualServer(VirtualServer)
4494         */
4495        public CommandFuture<Void> selectVirtualServerByPort(int port) {
4496                return selectVirtualServerByPort(port, null);
4497        }
4498
4499        /**
4500         * Moves the server query into the virtual server with the specified voice port
4501         * and sets the server query's nickname.
4502         * <p>
4503         * The nickname must be between 3 and 30 UTF-8 bytes long. BB codes will be ignored.
4504         * </p>
4505         *
4506         * @param port
4507         *              the voice port of the virtual server
4508         * @param nickname
4509         *              the nickname, or {@code null} if the nickname should not be set
4510         *
4511         * @return a future to track the progress of this command
4512         *
4513         * @throws TS3CommandFailedException
4514         *              if the execution of a command fails
4515         * @querycommands 1
4516         * @see VirtualServer#getPort()
4517         * @see #selectVirtualServerById(int, String)
4518         * @see #selectVirtualServerByPort(int)
4519         * @see #selectVirtualServer(VirtualServer, String)
4520         */
4521        public CommandFuture<Void> selectVirtualServerByPort(int port, String nickname) {
4522                Command cmd = QueryCommands.usePort(port, nickname);
4523                return executeAndReturnError(cmd);
4524        }
4525
4526        /**
4527         * Moves the server query into the specified virtual server.
4528         *
4529         * @param server
4530         *              the virtual server to move into
4531         *
4532         * @return a future to track the progress of this command
4533         *
4534         * @throws TS3CommandFailedException
4535         *              if the execution of a command fails
4536         * @querycommands 1
4537         * @see #selectVirtualServerById(int)
4538         * @see #selectVirtualServerByPort(int)
4539         * @see #selectVirtualServer(VirtualServer, String)
4540         */
4541        public CommandFuture<Void> selectVirtualServer(VirtualServer server) {
4542                return selectVirtualServerById(server.getId());
4543        }
4544
4545        /**
4546         * Moves the server query into the specified virtual server
4547         * and sets the server query's nickname.
4548         * <p>
4549         * The nickname must be between 3 and 30 UTF-8 bytes long. BB codes will be ignored.
4550         * </p>
4551         *
4552         * @param server
4553         *              the virtual server to move into
4554         * @param nickname
4555         *              the nickname, or {@code null} if the nickname should not be set
4556         *
4557         * @return a future to track the progress of this command
4558         *
4559         * @throws TS3CommandFailedException
4560         *              if the execution of a command fails
4561         * @querycommands 1
4562         * @see #selectVirtualServerById(int, String)
4563         * @see #selectVirtualServerByPort(int, String)
4564         * @see #selectVirtualServer(VirtualServer)
4565         */
4566        public CommandFuture<Void> selectVirtualServer(VirtualServer server, String nickname) {
4567                return selectVirtualServerById(server.getId(), nickname);
4568        }
4569
4570        /**
4571         * Sends an offline message to the client with the given unique identifier.
4572         * <p>
4573         * The message subject's length is limited to 200 UTF-8 bytes and BB codes in it will be ignored.
4574         * The message body's length is limited to 4096 UTF-8 bytes and accepts BB codes
4575         * </p>
4576         *
4577         * @param clientUId
4578         *              the unique identifier of the client to send the message to
4579         * @param subject
4580         *              the subject for the message, may not contain BB codes
4581         * @param message
4582         *              the actual message body, may contain BB codes
4583         *
4584         * @return a future to track the progress of this command
4585         *
4586         * @throws TS3CommandFailedException
4587         *              if the execution of a command fails
4588         * @querycommands 1
4589         * @see Client#getUniqueIdentifier()
4590         * @see Message
4591         */
4592        public CommandFuture<Void> sendOfflineMessage(String clientUId, String subject, String message) {
4593                Command cmd = MessageCommands.messageAdd(clientUId, subject, message);
4594                return executeAndReturnError(cmd);
4595        }
4596
4597        /**
4598         * Sends a text message either to the whole virtual server, a channel or specific client.
4599         * Your message may contain BB codes, but its length is limited to 1024 UTF-8 bytes.
4600         * <p>
4601         * To send a message to all virtual servers, use {@link #broadcast(String)}.
4602         * To send an offline message, use {@link #sendOfflineMessage(String, String, String)}.
4603         * </p>
4604         *
4605         * @param targetMode
4606         *              where the message should be sent to
4607         * @param targetId
4608         *              the client ID of the recipient of this message. This value is ignored unless {@code targetMode} is {@code CLIENT}
4609         * @param message
4610         *              the text message to send
4611         *
4612         * @return a future to track the progress of this command
4613         *
4614         * @throws TS3CommandFailedException
4615         *              if the execution of a command fails
4616         * @querycommands 1
4617         * @see Client#getId()
4618         */
4619        public CommandFuture<Void> sendTextMessage(TextMessageTargetMode targetMode, int targetId, String message) {
4620                Command cmd = ClientCommands.sendTextMessage(targetMode.getIndex(), targetId, message);
4621                return executeAndReturnError(cmd);
4622        }
4623
4624        /**
4625         * Sends a text message to the channel with the specified ID.
4626         * Your message may contain BB codes, but its length is limited to 1024 UTF-8 bytes.
4627         * <p>
4628         * This will move the client into the channel with the specified channel ID,
4629         * <b>but will not move it back to the original channel!</b>
4630         * </p>
4631         *
4632         * @param channelId
4633         *              the ID of the channel to which the message should be sent to
4634         * @param message
4635         *              the text message to send
4636         *
4637         * @return a future to track the progress of this command
4638         *
4639         * @throws TS3CommandFailedException
4640         *              if the execution of a command fails
4641         * @querycommands 1
4642         * @see #sendChannelMessage(String)
4643         * @see Channel#getId()
4644         */
4645        public CommandFuture<Void> sendChannelMessage(int channelId, String message) {
4646                return moveQuery(channelId)
4647                                .then(__ -> sendTextMessage(TextMessageTargetMode.CHANNEL, 0, message));
4648        }
4649
4650        /**
4651         * Sends a text message to the channel the server query is currently in.
4652         * Your message may contain BB codes, but its length is limited to 1024 UTF-8 bytes.
4653         *
4654         * @param message
4655         *              the text message to send
4656         *
4657         * @return a future to track the progress of this command
4658         *
4659         * @throws TS3CommandFailedException
4660         *              if the execution of a command fails
4661         * @querycommands 1
4662         */
4663        public CommandFuture<Void> sendChannelMessage(String message) {
4664                return sendTextMessage(TextMessageTargetMode.CHANNEL, 0, message);
4665        }
4666
4667        /**
4668         * Sends a text message to the virtual server with the specified ID.
4669         * Your message may contain BB codes, but its length is limited to 1024 UTF-8 bytes.
4670         * <p>
4671         * This will move the client to the virtual server with the specified server ID,
4672         * <b>but will not move it back to the original virtual server!</b>
4673         * </p>
4674         *
4675         * @param serverId
4676         *              the ID of the virtual server to which the message should be sent to
4677         * @param message
4678         *              the text message to send
4679         *
4680         * @return a future to track the progress of this command
4681         *
4682         * @throws TS3CommandFailedException
4683         *              if the execution of a command fails
4684         * @querycommands 1
4685         * @see #sendServerMessage(String)
4686         * @see VirtualServer#getId()
4687         */
4688        public CommandFuture<Void> sendServerMessage(int serverId, String message) {
4689                return selectVirtualServerById(serverId)
4690                                .then(__ -> sendTextMessage(TextMessageTargetMode.SERVER, 0, message));
4691        }
4692
4693        /**
4694         * Sends a text message to the virtual server the server query is currently in.
4695         * Your message may contain BB codes, but its length is limited to 1024 UTF-8 bytes.
4696         *
4697         * @param message
4698         *              the text message to send
4699         *
4700         * @return a future to track the progress of this command
4701         *
4702         * @throws TS3CommandFailedException
4703         *              if the execution of a command fails
4704         * @querycommands 1
4705         */
4706        public CommandFuture<Void> sendServerMessage(String message) {
4707                return sendTextMessage(TextMessageTargetMode.SERVER, 0, message);
4708        }
4709
4710        /**
4711         * Sends a private message to the client with the specified client ID.
4712         * Your message may contain BB codes, but its length is limited to 1024 UTF-8 bytes.
4713         *
4714         * @param clientId
4715         *              the ID of the client to send the message to
4716         * @param message
4717         *              the text message to send
4718         *
4719         * @return a future to track the progress of this command
4720         *
4721         * @throws TS3CommandFailedException
4722         *              if the execution of a command fails
4723         * @querycommands 1
4724         * @see Client#getId()
4725         */
4726        public CommandFuture<Void> sendPrivateMessage(int clientId, String message) {
4727                return sendTextMessage(TextMessageTargetMode.CLIENT, clientId, message);
4728        }
4729
4730        /**
4731         * Sets a channel group for a client in a specific channel.
4732         *
4733         * @param groupId
4734         *              the ID of the group the client should join
4735         * @param channelId
4736         *              the ID of the channel where the channel group should be assigned
4737         * @param clientDBId
4738         *              the database ID of the client for which the channel group should be set
4739         *
4740         * @return a future to track the progress of this command
4741         *
4742         * @throws TS3CommandFailedException
4743         *              if the execution of a command fails
4744         * @querycommands 1
4745         * @see ChannelGroup#getId()
4746         * @see Channel#getId()
4747         * @see Client#getDatabaseId()
4748         */
4749        public CommandFuture<Void> setClientChannelGroup(int groupId, int channelId, int clientDBId) {
4750                Command cmd = ChannelGroupCommands.setClientChannelGroup(groupId, channelId, clientDBId);
4751                return executeAndReturnError(cmd);
4752        }
4753
4754        /**
4755         * Sets the value of the multiple custom client properties for a client.
4756         * <p>
4757         * If any key present in the map already has a value assigned for this client,
4758         * the existing value will be overwritten.
4759         * This method does not delete keys not present in the map.
4760         * </p><p>
4761         * If {@code properties} contains an entry with {@code null} as its key,
4762         * that entry will be ignored and no exception will be thrown.
4763         * </p>
4764         *
4765         * @param clientDBId
4766         *              the database ID of the target client
4767         * @param properties
4768         *              the map of properties to set, cannot be {@code null}
4769         *
4770         * @return a future to track the progress of this command
4771         *
4772         * @throws TS3CommandFailedException
4773         *              if the execution of a command fails
4774         * @querycommands properties.size()
4775         * @see Client#getDatabaseId()
4776         * @see #setCustomClientProperty(int, String, String)
4777         * @see #deleteCustomClientProperty(int, String)
4778         */
4779        public CommandFuture<Void> setCustomClientProperties(int clientDBId, Map<String, String> properties) {
4780                Collection<CommandFuture<Void>> futures = new ArrayList<>(properties.size());
4781
4782                for (Map.Entry<String, String> entry : properties.entrySet()) {
4783                        String key = entry.getKey();
4784                        String value = entry.getValue();
4785
4786                        if (key != null) {
4787                                futures.add(setCustomClientProperty(clientDBId, key, value));
4788                        }
4789                }
4790
4791                return CommandFuture.ofAll(futures)
4792                                .map(__ -> null); // Return success as Void, not List<Void>
4793        }
4794
4795        /**
4796         * Sets the value of the {@code key} custom client property for a client.
4797         * <p>
4798         * If there is already an assignment of the {@code key} custom client property
4799         * for this client, the existing value will be overwritten.
4800         * </p>
4801         *
4802         * @param clientDBId
4803         *              the database ID of the target client
4804         * @param key
4805         *              the key of the custom property to set, cannot be {@code null}
4806         * @param value
4807         *              the (new) value of the custom property to set
4808         *
4809         * @return a future to track the progress of this command
4810         *
4811         * @throws TS3CommandFailedException
4812         *              if the execution of a command fails
4813         * @querycommands 1
4814         * @see Client#getDatabaseId()
4815         * @see #setCustomClientProperties(int, Map)
4816         * @see #deleteCustomClientProperty(int, String)
4817         */
4818        public CommandFuture<Void> setCustomClientProperty(int clientDBId, String key, String value) {
4819                if (key == null) throw new IllegalArgumentException("Key cannot be null");
4820
4821                Command cmd = CustomPropertyCommands.customSet(clientDBId, key, value);
4822                return executeAndReturnError(cmd);
4823        }
4824
4825        /**
4826         * Sets the read flag to {@code true} for a given message. This will not delete the message.
4827         *
4828         * @param messageId
4829         *              the ID of the message for which the read flag should be set
4830         *
4831         * @return a future to track the progress of this command
4832         *
4833         * @throws TS3CommandFailedException
4834         *              if the execution of a command fails
4835         * @querycommands 1
4836         * @see #setMessageReadFlag(int, boolean)
4837         */
4838        public CommandFuture<Void> setMessageRead(int messageId) {
4839                return setMessageReadFlag(messageId, true);
4840        }
4841
4842        /**
4843         * Sets the read flag to {@code true} for a given message. This will not delete the message.
4844         *
4845         * @param message
4846         *              the message for which the read flag should be set
4847         *
4848         * @return a future to track the progress of this command
4849         *
4850         * @throws TS3CommandFailedException
4851         *              if the execution of a command fails
4852         * @querycommands 1
4853         * @see #setMessageRead(int)
4854         * @see #setMessageReadFlag(Message, boolean)
4855         * @see #deleteOfflineMessage(int)
4856         */
4857        public CommandFuture<Void> setMessageRead(Message message) {
4858                return setMessageReadFlag(message.getId(), true);
4859        }
4860
4861        /**
4862         * Sets the read flag for a given message. This will not delete the message.
4863         *
4864         * @param messageId
4865         *              the ID of the message for which the read flag should be set
4866         * @param read
4867         *              the boolean value to which the read flag should be set
4868         *
4869         * @return a future to track the progress of this command
4870         *
4871         * @throws TS3CommandFailedException
4872         *              if the execution of a command fails
4873         * @querycommands 1
4874         * @see #setMessageRead(int)
4875         * @see #setMessageReadFlag(Message, boolean)
4876         * @see #deleteOfflineMessage(int)
4877         */
4878        public CommandFuture<Void> setMessageReadFlag(int messageId, boolean read) {
4879                Command cmd = MessageCommands.messageUpdateFlag(messageId, read);
4880                return executeAndReturnError(cmd);
4881        }
4882
4883        /**
4884         * Sets the read flag for a given message. This will not delete the message.
4885         *
4886         * @param message
4887         *              the message for which the read flag should be set
4888         * @param read
4889         *              the boolean value to which the read flag should be set
4890         *
4891         * @return a future to track the progress of this command
4892         *
4893         * @throws TS3CommandFailedException
4894         *              if the execution of a command fails
4895         * @querycommands 1
4896         * @see #setMessageRead(Message)
4897         * @see #setMessageReadFlag(int, boolean)
4898         * @see #deleteOfflineMessage(int)
4899         */
4900        public CommandFuture<Void> setMessageReadFlag(Message message, boolean read) {
4901                return setMessageReadFlag(message.getId(), read);
4902        }
4903
4904        /**
4905         * Sets the nickname of the server query client.
4906         * <p>
4907         * The nickname must be between 3 and 30 UTF-8 bytes long. BB codes will be ignored.
4908         * </p>
4909         *
4910         * @param nickname
4911         *              the new nickname, may not be {@code null}
4912         *
4913         * @return a future to track the progress of this command
4914         *
4915         * @throws TS3CommandFailedException
4916         *              if the execution of a command fails
4917         * @querycommands 1
4918         * @see #updateClient(Map)
4919         */
4920        public CommandFuture<Void> setNickname(String nickname) {
4921                Map<ClientProperty, String> options = Collections.singletonMap(ClientProperty.CLIENT_NICKNAME, nickname);
4922                return updateClient(options);
4923        }
4924
4925        /**
4926         * Starts the virtual server with the specified ID.
4927         *
4928         * @param serverId
4929         *              the ID of the virtual server
4930         *
4931         * @return a future to track the progress of this command
4932         *
4933         * @throws TS3CommandFailedException
4934         *              if the execution of a command fails
4935         * @querycommands 1
4936         */
4937        public CommandFuture<Void> startServer(int serverId) {
4938                Command cmd = VirtualServerCommands.serverStart(serverId);
4939                return executeAndReturnError(cmd);
4940        }
4941
4942        /**
4943         * Starts the specified virtual server.
4944         *
4945         * @param virtualServer
4946         *              the virtual server to start
4947         *
4948         * @return a future to track the progress of this command
4949         *
4950         * @throws TS3CommandFailedException
4951         *              if the execution of a command fails
4952         * @querycommands 1
4953         */
4954        public CommandFuture<Void> startServer(VirtualServer virtualServer) {
4955                return startServer(virtualServer.getId());
4956        }
4957
4958        /**
4959         * Stops the virtual server with the specified ID.
4960         *
4961         * @param serverId
4962         *              the ID of the virtual server
4963         *
4964         * @return a future to track the progress of this command
4965         *
4966         * @throws TS3CommandFailedException
4967         *              if the execution of a command fails
4968         * @querycommands 1
4969         */
4970        public CommandFuture<Void> stopServer(int serverId) {
4971                return stopServer(serverId, null);
4972        }
4973
4974        /**
4975         * Stops the virtual server with the specified ID.
4976         *
4977         * @param serverId
4978         *              the ID of the virtual server
4979         * @param reason
4980         *              the reason message to display to clients when they are disconnected
4981         *
4982         * @return a future to track the progress of this command
4983         *
4984         * @throws TS3CommandFailedException
4985         *              if the execution of a command fails
4986         * @querycommands 1
4987         */
4988        public CommandFuture<Void> stopServer(int serverId, String reason) {
4989                Command cmd = VirtualServerCommands.serverStop(serverId, reason);
4990                return executeAndReturnError(cmd);
4991        }
4992
4993        /**
4994         * Stops the specified virtual server.
4995         *
4996         * @param virtualServer
4997         *              the virtual server to stop
4998         *
4999         * @return a future to track the progress of this command
5000         *
5001         * @throws TS3CommandFailedException
5002         *              if the execution of a command fails
5003         * @querycommands 1
5004         */
5005        public CommandFuture<Void> stopServer(VirtualServer virtualServer) {
5006                return stopServer(virtualServer.getId(), null);
5007        }
5008
5009        /**
5010         * Stops the specified virtual server.
5011         *
5012         * @param virtualServer
5013         *              the virtual server to stop
5014         * @param reason
5015         *              the reason message to display to clients when they are disconnected
5016         *
5017         * @return a future to track the progress of this command
5018         *
5019         * @throws TS3CommandFailedException
5020         *              if the execution of a command fails
5021         * @querycommands 1
5022         */
5023        public CommandFuture<Void> stopServer(VirtualServer virtualServer, String reason) {
5024                return stopServer(virtualServer.getId(), reason);
5025        }
5026
5027        /**
5028         * Stops the entire TeamSpeak 3 Server instance by shutting down the process.
5029         * <p>
5030         * To have permission to use this command, you need to use the server query admin login.
5031         * </p>
5032         *
5033         * @return a future to track the progress of this command
5034         *
5035         * @throws TS3CommandFailedException
5036         *              if the execution of a command fails
5037         * @querycommands 1
5038         */
5039        public CommandFuture<Void> stopServerProcess() {
5040                return stopServerProcess(null);
5041        }
5042
5043        /**
5044         * Stops the entire TeamSpeak 3 Server instance by shutting down the process.
5045         * <p>
5046         * To have permission to use this command, you need to use the server query admin login.
5047         * </p>
5048         *
5049         * @param reason
5050         *              the reason message to display to clients when they are disconnected
5051         *
5052         * @return a future to track the progress of this command
5053         *
5054         * @throws TS3CommandFailedException
5055         *              if the execution of a command fails
5056         * @querycommands 1
5057         */
5058        public CommandFuture<Void> stopServerProcess(String reason) {
5059                Command cmd = ServerCommands.serverProcessStop(reason);
5060                return executeAndReturnError(cmd);
5061        }
5062
5063        /**
5064         * Unregisters the server query from receiving any event notifications.
5065         *
5066         * @return a future to track the progress of this command
5067         *
5068         * @throws TS3CommandFailedException
5069         *              if the execution of a command fails
5070         * @querycommands 1
5071         */
5072        public CommandFuture<Void> unregisterAllEvents() {
5073                Command cmd = QueryCommands.serverNotifyUnregister();
5074                return executeAndReturnError(cmd);
5075        }
5076
5077        /**
5078         * Updates several client properties for this server query instance.
5079         *
5080         * @param options
5081         *              the map of properties to update
5082         *
5083         * @return a future to track the progress of this command
5084         *
5085         * @throws TS3CommandFailedException
5086         *              if the execution of a command fails
5087         * @querycommands 1
5088         * @see #updateClient(ClientProperty, String)
5089         * @see #editClient(int, Map)
5090         */
5091        public CommandFuture<Void> updateClient(Map<ClientProperty, String> options) {
5092                Command cmd = ClientCommands.clientUpdate(options);
5093                return executeAndReturnError(cmd);
5094        }
5095
5096        /**
5097         * Changes a single client property for this server query instance.
5098         * <p>
5099         * Note that one can set many properties at once with the overloaded method that
5100         * takes a map of client properties and strings.
5101         * </p>
5102         *
5103         * @param property
5104         *              the client property to modify, make sure it is editable
5105         * @param value
5106         *              the new value of the property
5107         *
5108         * @return a future to track the progress of this command
5109         *
5110         * @throws TS3CommandFailedException
5111         *              if the execution of a command fails
5112         * @querycommands 1
5113         * @see #updateClient(Map)
5114         * @see #editClient(int, Map)
5115         */
5116        public CommandFuture<Void> updateClient(ClientProperty property, String value) {
5117                return updateClient(Collections.singletonMap(property, value));
5118        }
5119
5120        /**
5121         * Generates new login credentials for the currently connected server query instance, using the given name.
5122         * <p>
5123         * <b>This will remove the current login credentials!</b> You won't be logged out, but after disconnecting,
5124         * the old credentials will no longer work. Make sure to not lock yourselves out!
5125         * </p>
5126         *
5127         * @param loginName
5128         *              the name for the server query login
5129         *
5130         * @return the generated password for the server query login
5131         *
5132         * @throws TS3CommandFailedException
5133         *              if the execution of a command fails
5134         * @querycommands 1
5135         * @see #addServerQueryLogin(String, int)
5136         * @see #deleteServerQueryLogin(int)
5137         * @see #getServerQueryLogins()
5138         */
5139        public CommandFuture<String> updateServerQueryLogin(String loginName) {
5140                Command cmd = ClientCommands.clientSetServerQueryLogin(loginName);
5141                return executeAndReturnStringProperty(cmd, "client_login_password");
5142        }
5143
5144        /**
5145         * Uploads a file to the file repository at a given path and channel
5146         * by reading {@code dataLength} bytes from an open {@link InputStream}.
5147         * <p>
5148         * It is the user's responsibility to ensure that the given {@code InputStream} is
5149         * open and that {@code dataLength} bytes can eventually be read from it. The user is
5150         * also responsible for closing the stream once the upload has finished.
5151         * </p><p>
5152         * Note that this method will not read the entire file to memory and can thus
5153         * upload arbitrarily sized files to the file repository.
5154         * </p>
5155         *
5156         * @param dataIn
5157         *              a stream that contains the data that should be uploaded
5158         * @param dataLength
5159         *              how many bytes should be read from the stream
5160         * @param filePath
5161         *              the path the file should have after being uploaded
5162         * @param overwrite
5163         *              if {@code false}, fails if there's already a file at {@code filePath}
5164         * @param channelId
5165         *              the ID of the channel to upload the file to
5166         *
5167         * @return a future to track the progress of this command
5168         *
5169         * @throws TS3CommandFailedException
5170         *              if the execution of a command fails
5171         * @throws TS3FileTransferFailedException
5172         *              if the file transfer fails for any reason
5173         * @querycommands 1
5174         * @see FileInfo#getPath()
5175         * @see Channel#getId()
5176         * @see #uploadFileDirect(byte[], String, boolean, int, String)
5177         */
5178        public CommandFuture<Void> uploadFile(InputStream dataIn, long dataLength, String filePath, boolean overwrite, int channelId) {
5179                return uploadFile(dataIn, dataLength, filePath, overwrite, channelId, null);
5180        }
5181
5182        /**
5183         * Uploads a file to the file repository at a given path and channel
5184         * by reading {@code dataLength} bytes from an open {@link InputStream}.
5185         * <p>
5186         * It is the user's responsibility to ensure that the given {@code InputStream} is
5187         * open and that {@code dataLength} bytes can eventually be read from it. The user is
5188         * also responsible for closing the stream once the upload has finished.
5189         * </p><p>
5190         * Note that this method will not read the entire file to memory and can thus
5191         * upload arbitrarily sized files to the file repository.
5192         * </p>
5193         *
5194         * @param dataIn
5195         *              a stream that contains the data that should be uploaded
5196         * @param dataLength
5197         *              how many bytes should be read from the stream
5198         * @param filePath
5199         *              the path the file should have after being uploaded
5200         * @param overwrite
5201         *              if {@code false}, fails if there's already a file at {@code filePath}
5202         * @param channelId
5203         *              the ID of the channel to upload the file to
5204         * @param channelPassword
5205         *              that channel's password
5206         *
5207         * @return a future to track the progress of this command
5208         *
5209         * @throws TS3CommandFailedException
5210         *              if the execution of a command fails
5211         * @throws TS3FileTransferFailedException
5212         *              if the file transfer fails for any reason
5213         * @querycommands 1
5214         * @see FileInfo#getPath()
5215         * @see Channel#getId()
5216         * @see #uploadFileDirect(byte[], String, boolean, int, String)
5217         */
5218        public CommandFuture<Void> uploadFile(InputStream dataIn, long dataLength, String filePath, boolean overwrite, int channelId, String channelPassword) {
5219                FileTransferHelper helper = query.getFileTransferHelper();
5220                int transferId = helper.getClientTransferId();
5221                Command cmd = FileCommands.ftInitUpload(transferId, filePath, channelId, channelPassword, dataLength, overwrite);
5222                CommandFuture<Void> future = new CommandFuture<>();
5223
5224                executeAndTransformFirst(cmd, FileTransferParameters::new).onSuccess(params -> {
5225                        QueryError error = params.getQueryError();
5226                        if (!error.isSuccessful()) {
5227                                future.fail(new TS3CommandFailedException(error, cmd.getName()));
5228                                return;
5229                        }
5230
5231                        try {
5232                                query.getFileTransferHelper().uploadFile(dataIn, dataLength, params);
5233                        } catch (IOException e) {
5234                                future.fail(new TS3FileTransferFailedException("Upload failed", e));
5235                                return;
5236                        }
5237                        future.set(null); // Mark as successful
5238                }).forwardFailure(future);
5239
5240                return future;
5241        }
5242
5243        /**
5244         * Uploads a file that is already stored in memory to the file repository
5245         * at a given path and channel.
5246         *
5247         * @param data
5248         *              the file's data as a byte array
5249         * @param filePath
5250         *              the path the file should have after being uploaded
5251         * @param overwrite
5252         *              if {@code false}, fails if there's already a file at {@code filePath}
5253         * @param channelId
5254         *              the ID of the channel to upload the file to
5255         *
5256         * @return a future to track the progress of this command
5257         *
5258         * @throws TS3CommandFailedException
5259         *              if the execution of a command fails
5260         * @throws TS3FileTransferFailedException
5261         *              if the file transfer fails for any reason
5262         * @querycommands 1
5263         * @see FileInfo#getPath()
5264         * @see Channel#getId()
5265         * @see #uploadFile(InputStream, long, String, boolean, int)
5266         */
5267        public CommandFuture<Void> uploadFileDirect(byte[] data, String filePath, boolean overwrite, int channelId) {
5268                return uploadFileDirect(data, filePath, overwrite, channelId, null);
5269        }
5270
5271        /**
5272         * Uploads a file that is already stored in memory to the file repository
5273         * at a given path and channel.
5274         *
5275         * @param data
5276         *              the file's data as a byte array
5277         * @param filePath
5278         *              the path the file should have after being uploaded
5279         * @param overwrite
5280         *              if {@code false}, fails if there's already a file at {@code filePath}
5281         * @param channelId
5282         *              the ID of the channel to upload the file to
5283         * @param channelPassword
5284         *              that channel's password
5285         *
5286         * @return a future to track the progress of this command
5287         *
5288         * @throws TS3CommandFailedException
5289         *              if the execution of a command fails
5290         * @throws TS3FileTransferFailedException
5291         *              if the file transfer fails for any reason
5292         * @querycommands 1
5293         * @see FileInfo#getPath()
5294         * @see Channel#getId()
5295         * @see #uploadFile(InputStream, long, String, boolean, int, String)
5296         */
5297        public CommandFuture<Void> uploadFileDirect(byte[] data, String filePath, boolean overwrite, int channelId, String channelPassword) {
5298                return uploadFile(new ByteArrayInputStream(data), data.length, filePath, overwrite, channelId, channelPassword);
5299        }
5300
5301        /**
5302         * Uploads an icon to the icon directory in the file repository
5303         * by reading {@code dataLength} bytes from an open {@link InputStream}.
5304         * <p>
5305         * It is the user's responsibility to ensure that the given {@code InputStream} is
5306         * open and that {@code dataLength} bytes can eventually be read from it. The user is
5307         * also responsible for closing the stream once the upload has finished.
5308         * </p><p>
5309         * Note that unlike the file upload methods, this <strong>will read the entire file to memory</strong>.
5310         * This is because the CRC32 hash must be calculated before the icon can be uploaded.
5311         * That means that all icon files must be less than 2<sup>31</sup>-1 bytes in size.
5312         * </p>
5313         * Uploads  that is already stored in memory to the icon directory
5314         * in the file repository. If this icon has already been uploaded or
5315         * if a hash collision occurs (CRC32), this command will fail.
5316         *
5317         * @param dataIn
5318         *              a stream that contains the data that should be uploaded
5319         * @param dataLength
5320         *              how many bytes should be read from the stream
5321         *
5322         * @return the ID of the uploaded icon
5323         *
5324         * @throws TS3CommandFailedException
5325         *              if the execution of a command fails
5326         * @throws TS3FileTransferFailedException
5327         *              if the file transfer fails for any reason
5328         * @querycommands 1
5329         * @see IconFile#getIconId()
5330         * @see #uploadIconDirect(byte[])
5331         * @see #downloadIcon(OutputStream, long)
5332         */
5333        public CommandFuture<Long> uploadIcon(InputStream dataIn, long dataLength) {
5334                byte[] data;
5335                try {
5336                        data = FileTransferHelper.readFully(dataIn, dataLength);
5337                } catch (IOException e) {
5338                        throw new TS3FileTransferFailedException("Reading stream failed", e);
5339                }
5340                return uploadIconDirect(data);
5341        }
5342
5343        /**
5344         * Uploads an icon that is already stored in memory to the icon directory
5345         * in the file repository. If this icon has already been uploaded or
5346         * if a CRC32 hash collision occurs, this command will fail.
5347         *
5348         * @param data
5349         *              the icon's data as a byte array
5350         *
5351         * @return the ID of the uploaded icon
5352         *
5353         * @throws TS3CommandFailedException
5354         *              if the execution of a command fails
5355         * @throws TS3FileTransferFailedException
5356         *              if the file transfer fails for any reason
5357         * @querycommands 1
5358         * @see IconFile#getIconId()
5359         * @see #uploadIcon(InputStream, long)
5360         * @see #downloadIconDirect(long)
5361         */
5362        public CommandFuture<Long> uploadIconDirect(byte[] data) {
5363                CommandFuture<Long> future = new CommandFuture<>();
5364
5365                long iconId = FileTransferHelper.getIconId(data);
5366                String path = "/icon_" + iconId;
5367
5368                uploadFileDirect(data, path, false, 0)
5369                                .onSuccess(__ -> future.set(iconId))
5370                                .onFailure(transformError(future, 2050, iconId));
5371
5372                return future;
5373        }
5374
5375        /**
5376         * Uses an existing privilege key to join a server or channel group.
5377         *
5378         * @param token
5379         *              the privilege key to use
5380         *
5381         * @return a future to track the progress of this command
5382         *
5383         * @throws TS3CommandFailedException
5384         *              if the execution of a command fails
5385         * @querycommands 1
5386         * @see PrivilegeKey
5387         * @see #addPrivilegeKey(PrivilegeKeyType, int, int, String)
5388         * @see #usePrivilegeKey(PrivilegeKey)
5389         */
5390        public CommandFuture<Void> usePrivilegeKey(String token) {
5391                Command cmd = PrivilegeKeyCommands.privilegeKeyUse(token);
5392                return executeAndReturnError(cmd);
5393        }
5394
5395        /**
5396         * Uses an existing privilege key to join a server or channel group.
5397         *
5398         * @param privilegeKey
5399         *              the privilege key to use
5400         *
5401         * @return a future to track the progress of this command
5402         *
5403         * @throws TS3CommandFailedException
5404         *              if the execution of a command fails
5405         * @querycommands 1
5406         * @see PrivilegeKey
5407         * @see #addPrivilegeKey(PrivilegeKeyType, int, int, String)
5408         * @see #usePrivilegeKey(String)
5409         */
5410        public CommandFuture<Void> usePrivilegeKey(PrivilegeKey privilegeKey) {
5411                return usePrivilegeKey(privilegeKey.getToken());
5412        }
5413
5414        /**
5415         * Gets information about the current server query instance.
5416         *
5417         * @return information about the server query instance
5418         *
5419         * @throws TS3CommandFailedException
5420         *              if the execution of a command fails
5421         * @querycommands 1
5422         * @see #getClientInfo(int)
5423         */
5424        public CommandFuture<ServerQueryInfo> whoAmI() {
5425                Command cmd = QueryCommands.whoAmI();
5426                return executeAndTransformFirst(cmd, ServerQueryInfo::new);
5427        }
5428
5429        /**
5430         * Checks whether a given {@link TS3Exception} is a {@link TS3CommandFailedException} with the
5431         * specified error ID.
5432         *
5433         * @param exception
5434         *              the exception to check
5435         * @param errorId
5436         *              the error ID to match
5437         *
5438         * @return whether {@code exception} is a {@code TS3CommandFailedException} with error ID {@code errorId}.
5439         */
5440        private static boolean isQueryError(TS3Exception exception, int errorId) {
5441                if (exception instanceof TS3CommandFailedException) {
5442                        TS3CommandFailedException cfe = (TS3CommandFailedException) exception;
5443                        return (cfe.getError().getId() == errorId);
5444                } else {
5445                        return false;
5446                }
5447        }
5448
5449        /**
5450         * Creates a {@code FailureListener} that checks whether the caught exception is
5451         * a {@code TS3CommandFailedException} with error ID {@code errorId}.
5452         * <p>
5453         * If so, the listener makes {@code future} succeed by setting its result value to an empty
5454         * list with element type {@code T}. Else, the caught exception is forwarded to {@code future}.
5455         * </p>
5456         *
5457         * @param future
5458         *              the future to forward the result to
5459         * @param errorId
5460         *              the error ID to catch
5461         * @param replacement
5462         *              the value to
5463         * @param <T>
5464         *              the type of {@code replacement} and element type of {@code future}
5465         *
5466         * @return a {@code FailureListener} with the described properties
5467         */
5468        private static <T> CommandFuture.FailureListener transformError(CommandFuture<T> future, int errorId, T replacement) {
5469                return exception -> {
5470                        if (isQueryError(exception, errorId)) {
5471                                future.set(replacement);
5472                        } else {
5473                                future.fail(exception);
5474                        }
5475                };
5476        }
5477
5478        /**
5479         * Executes a command and sets the returned future to true if the command succeeded.
5480         *
5481         * @param command
5482         *              the command to execute
5483         *
5484         * @return a future to track the progress of this command
5485         */
5486        private CommandFuture<Void> executeAndReturnError(Command command) {
5487                CommandFuture<Void> future = command.getFuture()
5488                                .map(__ -> null); // Mark as successful
5489
5490                commandQueue.enqueueCommand(command);
5491                return future;
5492        }
5493
5494        /**
5495         * Executes a command, checking for failure and returning a single
5496         * {@code String} property from the first response map.
5497         *
5498         * @param command
5499         *              the command to execute
5500         * @param property
5501         *              the name of the property to return
5502         *
5503         * @return the value of the specified {@code String} property
5504         */
5505        private CommandFuture<String> executeAndReturnStringProperty(Command command, String property) {
5506                CommandFuture<String> future = command.getFuture()
5507                                .map(result -> result.getFirstResponse().get(property));
5508
5509                commandQueue.enqueueCommand(command);
5510                return future;
5511        }
5512
5513        /**
5514         * Executes a command and returns a single {@code Integer} property from the first response map.
5515         *
5516         * @param command
5517         *              the command to execute
5518         * @param property
5519         *              the name of the property to return
5520         *
5521         * @return the value of the specified {@code Integer} property
5522         */
5523        private CommandFuture<Integer> executeAndReturnIntProperty(Command command, String property) {
5524                CommandFuture<Integer> future = command.getFuture()
5525                                .map(result -> result.getFirstResponse().getInt(property));
5526
5527                commandQueue.enqueueCommand(command);
5528                return future;
5529        }
5530
5531        private CommandFuture<int[]> executeAndReturnIntArray(Command command, String property) {
5532                CommandFuture<int[]> future = command.getFuture()
5533                                .map(result -> {
5534                                        List<Wrapper> responses = result.getResponses();
5535                                        int[] values = new int[responses.size()];
5536                                        int i = 0;
5537
5538                                        for (Wrapper response : responses) {
5539                                                values[i++] = response.getInt(property);
5540                                        }
5541                                        return values;
5542                                });
5543
5544                commandQueue.enqueueCommand(command);
5545                return future;
5546        }
5547
5548        /**
5549         * Executes a command, checks for failure and transforms the first
5550         * response map by invoking {@code fn}.
5551         *
5552         * @param command
5553         *              the command to execute
5554         * @param fn
5555         *              the function that creates a new wrapper of type {@code T}
5556         * @param <T>
5557         *              the wrapper class the map should be wrapped with
5558         *
5559         * @return a future of a {@code T} wrapper of the first response map
5560         */
5561        private <T extends Wrapper> CommandFuture<T> executeAndTransformFirst(Command command, Function<Map<String, String>, T> fn) {
5562                return executeAndMapFirst(command, wrapper -> fn.apply(wrapper.getMap()));
5563        }
5564
5565        /**
5566         * Executes a command, checks for failure and maps the first
5567         * response wrapper by using {@code fn}.
5568         *
5569         * @param command
5570         *              the command to execute
5571         * @param fn
5572         *              a mapping function from {@code Wrapper} to {@code T}
5573         * @param <T>
5574         *              the result type of the mapping function {@code fn}
5575         *
5576         * @return a future of a {@code T}
5577         */
5578        private <T> CommandFuture<T> executeAndMapFirst(Command command, Function<Wrapper, T> fn) {
5579                CommandFuture<T> future = command.getFuture()
5580                                .map(result -> fn.apply(result.getFirstResponse()));
5581
5582                commandQueue.enqueueCommand(command);
5583                return future;
5584        }
5585
5586        /**
5587         * Executes a command, checks for failure and transforms all
5588         * response maps to a wrapper by invoking {@code fn} on each map.
5589         *
5590         * @param command
5591         *              the command to execute
5592         * @param fn
5593         *              the function that creates the new wrappers of type {@code T}
5594         * @param <T>
5595         *              the wrapper class the maps should be wrapped with
5596         *
5597         * @return a future of a list of wrapped response maps
5598         */
5599        private <T extends Wrapper> CommandFuture<List<T>> executeAndTransform(Command command, Function<Map<String, String>, T> fn) {
5600                return executeAndMap(command, wrapper -> fn.apply(wrapper.getMap()));
5601        }
5602
5603        /**
5604         * Executes a command, checks for failure and maps all response
5605         * wrappers by using {@code fn}.
5606         *
5607         * @param command
5608         *              the command to execute
5609         * @param fn
5610         *              a mapping function from {@code Wrapper} to {@code T}
5611         * @param <T>
5612         *              the result type of the mapping function {@code fn}
5613         *
5614         * @return a future of a list of {@code T}
5615         */
5616        private <T> CommandFuture<List<T>> executeAndMap(Command command, Function<Wrapper, T> fn) {
5617                CommandFuture<List<T>> future = command.getFuture()
5618                                .map(result -> {
5619                                        List<Wrapper> response = result.getResponses();
5620                                        List<T> transformed = new ArrayList<>(response.size());
5621                                        for (Wrapper wrapper : response) {
5622                                                transformed.add(fn.apply(wrapper));
5623                                        }
5624
5625                                        return transformed;
5626                                });
5627
5628                commandQueue.enqueueCommand(command);
5629                return future;
5630        }
5631
5632        /**
5633         * Computes a sub-list of the list of values produced by {@code valuesFuture} where
5634         * each value matches a key in the list of keys produced by {@code keysFuture}.
5635         * <p>
5636         * The returned future succeeds if {@code keysFuture} and {@code valuesFuture} succeed and
5637         * fails if {@code keysFuture} or {@code valuesFuture} fails.
5638         * </p><p>
5639         * {@code null} keys, {@code null} values, and keys without a matching value are ignored.
5640         * If multiple values map to the same key, only the first value is used.
5641         * </p><p>
5642         * The order of values in the resulting list follows the order of matching keys,
5643         * not the order of the original value list.
5644         * </p>
5645         *
5646         * @param keysFuture
5647         *              the future producing a list of keys of type {@code K}
5648         * @param valuesFuture
5649         *              the future producing a list of values of type {@code V}
5650         * @param keyMapper
5651         *              a function extracting keys from the value type
5652         * @param <K>
5653         *              the key type
5654         * @param <V>
5655         *              the value type
5656         *
5657         * @return a future of a list of values of type {@code V}
5658         */
5659        private static <K, V> CommandFuture<List<V>> findByKey(CommandFuture<List<K>> keysFuture, CommandFuture<List<V>> valuesFuture,
5660                                                               Function<? super V, ? extends K> keyMapper) {
5661                CommandFuture<List<V>> future = new CommandFuture<>();
5662
5663                keysFuture.onSuccess(keys ->
5664                                valuesFuture.onSuccess(values -> {
5665                                        Map<K, V> valueMap = values.stream().collect(Collectors.toMap(keyMapper, Function.identity(), (l, r) -> l));
5666                                        List<V> foundValues = new ArrayList<>(keys.size());
5667
5668                                        for (K key : keys) {
5669                                                if (key == null) continue;
5670                                                V value = valueMap.get(key);
5671                                                if (value == null) continue;
5672                                                foundValues.add(value);
5673                                        }
5674
5675                                        future.set(foundValues);
5676                                }).forwardFailure(future)
5677                ).forwardFailure(future);
5678
5679                return future;
5680        }
5681}