Client class
class Client { /// Host the client will try to connect to final String host; /// Port the client will try to connnect on final int port; /// TCP-Socket that is used to do the communication Socket _zSocket; /// Data written to this stream is directly sent to the server. /// A newline is added automatically. final StreamController<String> _send = new StreamController(); /// Counts lines received on the stream int _received_lines = 0; /// Returns the amount of lines received on the stream int get received_lines => _received_lines; /// Queue of commands that should be send to the server. /// This is necessary because the TeamSpeak-protocol is sequential. final Queue<Command> _queue = new Queue(); /// Last command sent to the server Command _lastcommand; /// Command which was previously sent to the server and whose answer /// is currently pending Command get lastcommand => _lastcommand; /// Broadcast-Stream that holds lines received from the server Stream<String> _receive; /// Sets whether the client should throttle requests. This can be used /// to avoid getting disconnected by the server because of flooding. /// Requests are limited to one per two seconds. If there are more incoming /// requests, they will be buffered using the queue and are sent afterwards. bool enableThrottling = false; /// Returns the time the last command was sent to the server. DateTime _lastcommandsent = new DateTime.now(); /// Checks whether a command is pending at the moment and executes the next /// one if not. void _checkPendingQueue(){ // Wenn gerade kein Kommando ausgeführt wird... if(_lastcommand == null && _queue.isNotEmpty){ if(enableThrottling && _lastcommandsent.difference(new DateTime.now()).inSeconds.abs() < 2){ new Future.delayed(new Duration(seconds: 1)).then((dynamic pDyn){ _checkPendingQueue(); }); return; } if(enableThrottling){ _lastcommandsent = new DateTime.now(); } Command lCommand = _queue.removeFirst(); _send.add(lCommand.toString()); _lastcommand = lCommand; bool lErrReceived = false; List<Map<String, String>> lItems; _receive .takeWhile((String pLine) => !lErrReceived) .listen((String pLine){ if(pLine.startsWith("notify")){ // Notifications gehen uns nichts an return; } if(pLine.startsWith("error")){ lErrReceived = true; // ToDo: Error / Bestätigung ausparsen final Map<String, String> lErr = Packet.parseItem(pLine .substring(pLine.indexOf(" ")) .trim()); if(lErr["id"] == "0"){ final Answer lAnswer = new Answer(lItems, lErr); _lastcommand.gotAnswer(lAnswer); } else { _lastcommand.gotAnswerError(lErr); } _lastcommand = null; _checkPendingQueue(); } else { lItems = pLine .split("|") .map((String pItem) => Packet.parseItem(pItem)) .toList(growable: false); } }); } } /// Initializes a new TeamSpeak-Client that is bound to the given host and port. /// The client will connect as soon as [connect] is executed. Client(String this.host, int this.port); /// Sends a new command. To retrieve the result of this command, /// you may use the returned future which is fired as soon as /// a result is received. Future<Answer> send(Command pCommand){ _queue.add(pCommand); _checkPendingQueue(); return pCommand.answer; } /// Registers for one type of notifications that are sent by the server. /// The name of the notification is the name of the notification without /// the leading "notify", e. g. "cliententerview" or "clientleftview". /// Since every message sent by the server is now checked if it is a /// notification, the stream should be closed as soon as it isn't needed /// anymore. There may be multiple listeners for the same type of /// notifications. Stream<Notification> registerNotification(String pNotification){ return _receive.transform(new StreamTransformer<String, Notification>( handleData: (String pInput, EventSink pSink){ if(pInput.startsWith("notify" + pNotification)){ final String lString = pInput .substring(pInput.indexOf(" ")) .trim(); final Map<String, String> lParameters = Packet.parseItem(lString); final Notification lNotification = new Notification(pNotification, lParameters); pSink.add(lNotification); } })); } /// Connects to the host given when the client was initialized. Future connect(){ return Socket.connect(host, port).then((Socket pSocket){ _zSocket = pSocket; _receive = _zSocket .transform(new StringDecoder()) .transform(new LineTransformer()) .transform(new StreamTransformer<String, String>( handleData: (String pInput, EventSink<String> pSink){ if(pInput.trim() != "") pSink.add(pInput); })) .asBroadcastStream(); // ToDo: Dummy-Listener weg _receive.listen((pData){}); _send.stream .transform(new StreamTransformer<String, String>( handleData: (String pInput, EventSink<String> pSink){ pSink.add(pInput + "\n"); })) .transform(new StringEncoder()) .pipe(_zSocket); return _receive .take(2) .toList() .then((List<String> pList){ if(pList[0] != "TS3"){ return new Future.error("Invalid first message sent by server."); } }); }); } }
Constructors
Properties
bool enableThrottling #
Sets whether the client should throttle requests. This can be used to avoid getting disconnected by the server because of flooding. Requests are limited to one per two seconds. If there are more incoming requests, they will be buffered using the queue and are sent afterwards.
bool enableThrottling = false
Methods
Future connect() #
Connects to the host given when the client was initialized.
Future connect(){ return Socket.connect(host, port).then((Socket pSocket){ _zSocket = pSocket; _receive = _zSocket .transform(new StringDecoder()) .transform(new LineTransformer()) .transform(new StreamTransformer<String, String>( handleData: (String pInput, EventSink<String> pSink){ if(pInput.trim() != "") pSink.add(pInput); })) .asBroadcastStream(); // ToDo: Dummy-Listener weg _receive.listen((pData){}); _send.stream .transform(new StreamTransformer<String, String>( handleData: (String pInput, EventSink<String> pSink){ pSink.add(pInput + "\n"); })) .transform(new StringEncoder()) .pipe(_zSocket); return _receive .take(2) .toList() .then((List<String> pList){ if(pList[0] != "TS3"){ return new Future.error("Invalid first message sent by server."); } }); }); }
Stream<Notification> registerNotification(String pNotification) #
Registers for one type of notifications that are sent by the server. The name of the notification is the name of the notification without the leading "notify", e. g. "cliententerview" or "clientleftview". Since every message sent by the server is now checked if it is a notification, the stream should be closed as soon as it isn't needed anymore. There may be multiple listeners for the same type of notifications.
Stream<Notification> registerNotification(String pNotification){ return _receive.transform(new StreamTransformer<String, Notification>( handleData: (String pInput, EventSink pSink){ if(pInput.startsWith("notify" + pNotification)){ final String lString = pInput .substring(pInput.indexOf(" ")) .trim(); final Map<String, String> lParameters = Packet.parseItem(lString); final Notification lNotification = new Notification(pNotification, lParameters); pSink.add(lNotification); } })); }