server.vala 5.67 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
/* 
 * 
 * Copyright (C) 2011 Colomban Wendling <ban@herbesfolles.org>
 *                    Jonathan Michalon <studios.chalmion@no-log.org>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 * 
 */

21
using OVCC;
22 23
using OVCC.Network;

24 25
static const string TILES_FILE   = "tiles.xml";
static const string TILESET_FILE = "tileset.xml";
26 27

/* TODO: add a Source to be notified of incoming messages asynchronously */
28

29 30
public class Server: ThreadedSocketService
{
31
  private List<Client>  clients = null;
32
  private List<Game>    tables  = null;
33 34 35 36 37 38
  
  public string name { get; construct; }
  public uint   port { get; construct; default = 0xdead; }
  
  public signal void stopped ();
  
39 40 41
  private bool send_all (Message      message,
                         Client?      exclude = null,
                         Cancellable? cancel = null)
42 43
    throws Error
  {
44 45 46 47 48 49 50 51
    lock (this.clients) {
      foreach (var c in this.clients) {
        if (c != exclude) {
          c.send (message, cancel);
        }
      }
    }
    return true;
52
  }
53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91

  private bool handle_login (LoginMessage msg, Client client)
  {
    bool ok = true;
    if (msg.status != LoginMessage.State.QUERY) {
      critical ("got LoginMessage with status different from QUERY");
      return false;
    }
    
    /* missing authentication check */
    if (msg.password == "") {
      debug ("rejecting auth as password is empty");
      msg.status = LoginMessage.State.MISSING_AUTHENTICATION;
      client.send (msg);
      return false;
    }

    /* already existing name check WARNING: lock needed */
    lock (this.clients) {
      foreach (Client c in this.clients) {
        if (c.login == msg.login) {
          ok = false;
          debug ("rejecting auth as login already connected");
          msg.status = LoginMessage.State.ALREADY_EXISTS;
          break;
        }
      }
      /* registration */
      if (ok) {
        debug ("accepting auth for %s", msg.login);
        msg.status = LoginMessage.State.OK;
        client.login    = msg.login;
        client.password = msg.password;
      }
    }
    client.send (msg);

    return ok;
  }
92 93 94 95 96 97 98 99 100 101 102

  private bool handle_join (JoinMessage msg, Client client)
  {
    client.join(tables.first().data);
    debug ("Sending game data");
    var data = new GamedataMessage (client.game.get_stack_ids(),
                                    client.game.get_player_nicks(),
                                    TILES_FILE, TILESET_FILE);
    client.send (data);
    return true;
  }
103 104 105 106 107 108
  
  private bool handle_connection (ThreadedSocketService service,
                                  SocketConnection      connection,
                                  Object?               source)
  {
    debug ("New connection");
109
    var client = new Client ();
110
    try {
111 112 113 114
      client.connect (connection);
    } catch (Error e) {
      warning ("error connecting client: %s", e.message);
      return false;
115 116
    }
    
117 118 119 120 121 122 123 124 125 126 127
    lock (this.clients) {
      this.clients.prepend (client);
    }
    try {
      client.send (new WelcomeMessage ("Welcome to server %s".printf (this.name)));
    } catch {}
    
    var running = true;
    while (running) {
      Message? msg;
      
128
      try {
129
        msg = client.receive ();
130 131 132 133
      } catch (Error e) {
        warning ("error receiving data: %s", e.message);
        break;
      }
134 135 136 137 138 139 140 141 142 143 144
      
      if (msg == null) {
        debug ("Invalid message received");
        continue;
      }
      
      debug ("Message type %s received", msg.get_type ().name ());
      if (msg is StringMessage) {
        debug (" --- %s", (msg as StringMessage).message);
      }
      switch (msg.message_type) {
145
        case MessageType.LOGIN:
146
          bool logged;
147
          debug ("Login for '%s' with '%s'", (msg as LoginMessage).login,
148
                                             (msg as LoginMessage).password);
149 150 151 152
          handle_login ((msg as LoginMessage), client);
          break;
        case MessageType.JOIN:
          handle_join (msg as JoinMessage, client);
153 154
          break;
        case MessageType.SIGNAL:
155
          client.signal_handle.emit_received (msg as SignalMessage);
156
          break;
157 158 159 160 161
        case MessageType.DISCONNECT: running = false; break;
      }
    }
    lock (this.clients) {
      this.clients.remove (client);
162
    }
163
    try { client.disconnect (); } catch { /* we don't care if disconnection failed */ }
164 165 166 167 168 169 170
    debug ("Closed connection");
    return true;
  }
  
  construct
  {
    try {
171
      this.add_inet_port ((uint16) port, null);
172 173 174 175 176 177 178 179
    } catch (Error e) {
      critical ("failed to add address: %s", e.message);
    }
    this.run.connect (handle_connection);
  }
  
  public Server (string name,
                 uint16 port = 0xdead)
180
    throws Error
181 182
  {
    Object (name: name, port: port);
183 184 185 186 187 188

    TilesDef tiles = new TilesDef ();
    TileSet  tileset = new TileSet ();
    tiles.load (File.new_for_path (TILES_FILE));
    tileset.load (tiles, File.new_for_path (TILESET_FILE));
    
189
    Game table = new Game (tileset, null);
190
    this.tables.prepend (table);
191 192 193 194 195 196 197 198 199 200
  }
  
  /* a bit ugly since a direct call to parent method will not trigger the
   * :stopped signal */
  public new void stop ()
  {
    base.stop ();
    this.stopped ();
  }
}