server.vala 5.26 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_original.xml";
static const string TILESET_FILE = "tileset_original.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
  
  private bool handle_connection (ThreadedSocketService service,
                                  SocketConnection      connection,
                                  Object?               source)
  {
    debug ("New connection");
98
    var client = new Client ();
99
    try {
100 101 102 103
      client.connect (connection);
    } catch (Error e) {
      warning ("error connecting client: %s", e.message);
      return false;
104 105
    }
    
106 107 108 109 110 111 112 113 114 115 116
    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;
      
117
      try {
118
        msg = client.receive ();
119 120 121 122
      } catch (Error e) {
        warning ("error receiving data: %s", e.message);
        break;
      }
123 124 125 126 127 128 129 130 131 132 133
      
      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) {
134
        case MessageType.LOGIN:
135
          bool logged;
136
          debug ("Login for '%s' with '%s'", (msg as LoginMessage).login,
137
                                             (msg as LoginMessage).password);
138 139 140 141 142 143 144
          logged = handle_login ((msg as LoginMessage), client);
          if (logged) {
            client.join(tables.first().data);
          }
          break;
        case MessageType.SIGNAL:
          
145
          break;
146 147 148 149 150
        case MessageType.DISCONNECT: running = false; break;
      }
    }
    lock (this.clients) {
      this.clients.remove (client);
151
    }
152
    try { client.disconnect (); } catch { /* we don't care if disconnection failed */ }
153 154 155 156 157 158 159
    debug ("Closed connection");
    return true;
  }
  
  construct
  {
    try {
160
      this.add_inet_port ((uint16) port, null);
161 162 163 164 165 166 167 168
    } catch (Error e) {
      critical ("failed to add address: %s", e.message);
    }
    this.run.connect (handle_connection);
  }
  
  public Server (string name,
                 uint16 port = 0xdead)
169
    throws Error
170 171
  {
    Object (name: name, port: port);
172 173 174 175 176 177 178 179

    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));
    
    Game table = new Game.from_tileset(tileset);
    this.tables.prepend (table);
180 181 182 183 184 185 186 187 188 189
  }
  
  /* a bit ugly since a direct call to parent method will not trigger the
   * :stopped signal */
  public new void stop ()
  {
    base.stop ();
    this.stopped ();
  }
}