001    /**
002     * Jetrix TetriNET Server
003     * Copyright (C) 2005  Emmanuel Bourg
004     *
005     * This program is free software; you can redistribute it and/or
006     * modify it under the terms of the GNU General Public License
007     * as published by the Free Software Foundation; either version 2
008     * of the License, or (at your option) any later version.
009     *
010     * This program is distributed in the hope that it will be useful,
011     * but WITHOUT ANY WARRANTY; without even the implied warranty of
012     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
013     * GNU General Public License for more details.
014     *
015     * You should have received a copy of the GNU General Public License
016     * along with this program; if not, write to the Free Software
017     * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
018     */
019    
020    package net.jetrix.agent;
021    
022    import java.io.BufferedInputStream;
023    import java.io.BufferedOutputStream;
024    import java.io.IOException;
025    import java.io.InputStream;
026    import java.io.OutputStream;
027    import java.net.InetSocketAddress;
028    import java.net.Socket;
029    import java.util.ArrayList;
030    import java.util.List;
031    import java.util.logging.Logger;
032    import java.util.regex.Matcher;
033    import java.util.regex.Pattern;
034    
035    import net.jetrix.Message;
036    import net.jetrix.protocols.QueryProtocol;
037    
038    /**
039     * Agent performing query requests on a TetriNET server.
040     *
041     * @see <a href="See http://jetrix.sourceforge.net/dev-guide.php#section2-4">Query Protocol Documentation</a>
042     *
043     * @author Emmanuel Bourg
044     * @version $Revision: 848 $, $Date: 2010-05-03 22:51:32 +0200 (lun., 03 mai 2010) $
045     */
046    public class QueryAgent implements Agent
047    {
048        private String hostname;
049        private Socket socket;
050        private InputStream in;
051        private OutputStream out;
052        private String encoding = "Cp1252";
053    
054        private Logger log = Logger.getLogger("net.jetrix");
055    
056        public void connect(String hostname) throws IOException
057        {
058            this.hostname = hostname;
059            socket = new Socket();
060            socket.connect(new InetSocketAddress(hostname, 31457), 5000);
061            in = new BufferedInputStream(socket.getInputStream());
062            out = new BufferedOutputStream(socket.getOutputStream());
063            socket.setSoTimeout(10000);
064        }
065    
066        public void disconnect() throws IOException
067        {
068            if (socket != null)
069            {
070                socket.close();
071            }
072        }
073    
074        public void send(String message) throws IOException
075        {
076            out.write(message.getBytes("ISO-8859-1"));
077            out.write(0xFF);
078            out.flush();
079        }
080    
081        public void send(Message message) throws IOException
082        {
083            throw new UnsupportedOperationException("QueryAgent is not asynchonous");
084        }
085    
086        public void receive(Message message) throws IOException
087        {
088            throw new UnsupportedOperationException("QueryAgent is not asynchonous");
089        }
090    
091        /**
092         * Fetch the information about the server.
093         */
094        public QueryInfo getInfo() throws IOException
095        {
096            QueryInfo info = new QueryInfo();
097    
098            info.setHostname(hostname);
099            info.setVersion(getVersion());
100            info.setPing(getPing());
101            info.setChannels(getChannels());
102            info.setPlayers(getPlayers());
103    
104            return info;
105        }
106    
107        /**
108         * Return the version of the server.
109         */
110        public String getVersion() throws IOException
111        {
112            // send the command
113            send("version");
114    
115            // read the result
116            QueryProtocol protocol = new QueryProtocol();
117            String version = protocol.readLine(in, encoding);
118            protocol.readLine(in, encoding);
119            
120            return version;
121        }
122    
123        /**
124         * Return the number of players logged in (usualy in the first channel only).
125         */
126        public int getPlayerNumber() throws IOException
127        {
128            // send the command
129            send("playerquery");
130    
131            // read the result
132            QueryProtocol protocol = new QueryProtocol();
133            String line = protocol.readLine(in, encoding);
134            
135            if (line.startsWith("Number of players logged in: "))
136            {
137                return Integer.parseInt(line.substring(line.indexOf(":") + 1).trim());
138            }
139            else
140            {
141                throw new IOException("Invalid response : " + line);
142            }
143        }
144    
145        public List<ChannelInfo> getChannels() throws IOException
146        {
147            // prepare the pattern matcher
148            Pattern pattern = Pattern.compile("\"(.*)\" \"(.*)\" ([0-9]+) ([0-9]+) ([0-9]+|N/A) ([0-9]+)");
149    
150            // send the command
151            send("listchan");
152    
153            List<ChannelInfo> channels = new ArrayList<ChannelInfo>();
154    
155            // read the result
156            String line = null;
157            QueryProtocol protocol = new QueryProtocol();
158            while (!QueryProtocol.OK.equals(line = protocol.readLine(in, encoding)))
159            {
160                Matcher matcher = pattern.matcher(line);
161    
162                if (matcher.matches())
163                {
164                    int i = 1;
165                    ChannelInfo channel = new ChannelInfo();
166                    channel.setName(matcher.group(i++));
167                    channel.setDescription(matcher.group(i++));
168                    channel.setPlayernum(Integer.parseInt(matcher.group(i++)));
169                    channel.setPlayermax(Integer.parseInt(matcher.group(i++)));
170                    String priority = matcher.group(i++);
171                    if (!"N/A".equals(priority))
172                    {
173                        channel.setPriority(Integer.parseInt(priority));
174                    }
175                    channel.setStatus(Integer.parseInt(matcher.group(i++)));
176    
177                    channels.add(channel);
178                }
179                else
180                {
181                    log.warning("Invalid response for the listchan message (" + hostname + ") : " + line);
182                }
183            }
184    
185            return channels;
186        }
187    
188        public List<PlayerInfo> getPlayers() throws IOException
189        {
190            // prepare the pattern matcher
191            Pattern pattern = Pattern.compile("\"(.*)\" \"(.*)\" \"(.*)\" ([0-9]+) ([0-9]+) ([0-9]+) \"(.*)\"");
192    
193            // send the command
194            send("listuser");
195    
196            List<PlayerInfo> players = new ArrayList<PlayerInfo>();
197    
198            // read the result
199            String line = null;
200            QueryProtocol protocol = new QueryProtocol();
201            while (!QueryProtocol.OK.equals(line = protocol.readLine(in, encoding)))
202            {
203                Matcher matcher = pattern.matcher(line);
204    
205                if (matcher.matches())
206                {
207                    int i = 1;
208                    PlayerInfo player = new PlayerInfo();
209                    player.setNick(matcher.group(i++));
210                    player.setTeam(matcher.group(i++));
211                    player.setVersion(matcher.group(i++));
212                    player.setSlot(Integer.parseInt(matcher.group(i++)));
213                    player.setStatus(Integer.parseInt(matcher.group(i++)));
214                    player.setAuthenticationLevel(Integer.parseInt(matcher.group(i++)));
215                    player.setChannel(matcher.group(i++));
216    
217                    players.add(player);
218                }
219                else
220                {
221                    log.warning("Invalid response for the listuser message (" + hostname + ") : " + line);
222                }
223            }
224    
225            return players;
226        }
227    
228        /**
229         * Return the round trip time to the server in milliseconds.
230         */
231        public int getPing() throws IOException
232        {
233            long time = System.currentTimeMillis();
234            getVersion();
235            return (int) (System.currentTimeMillis() - time);
236        }
237    
238    }