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