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