From: <Blu...@us...> - 2009-10-24 20:27:42
|
Revision: 306 http://virtplayground.svn.sourceforge.net/virtplayground/?rev=306&view=rev Author: BlueWolf_ Date: 2009-10-24 18:56:45 +0000 (Sat, 24 Oct 2009) Log Message: ----------- * Will now uses the defaults in the config * Send will not crash when the connection is dead * connection_close renamed to connection_closed * Some changes in the callback Modified Paths: -------------- trunk/server/core/__init__.py trunk/server/core/callback.py trunk/server/core/server.py Modified: trunk/server/core/__init__.py =================================================================== --- trunk/server/core/__init__.py 2009-10-21 22:34:25 UTC (rev 305) +++ trunk/server/core/__init__.py 2009-10-24 18:56:45 UTC (rev 306) @@ -1,3 +1,7 @@ +""" +This is the server core for Virtual Playground. +You can use this by importing Server and Callback +""" ## This file is part of Virtual Playground ## Copyright (c) 2009 Jos Ratsma + Koen Koning @@ -19,3 +23,4 @@ Server = server.Server Callback = callback.Callback +__all__ = ['Server', 'Callback'] Modified: trunk/server/core/callback.py =================================================================== --- trunk/server/core/callback.py 2009-10-21 22:34:25 UTC (rev 305) +++ trunk/server/core/callback.py 2009-10-24 18:56:45 UTC (rev 306) @@ -16,40 +16,54 @@ ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. class Callback: - def server_online(self, clients): + def server_online(self): """ - server_online(self, clients) Called when the server is online and ready to accept incoming connections. This means it has successfully bounded itself to a port. Most of the times this will be called immediately after calling Server.start() - This is a placeholder. - If you want to catch this event, overwrite it. + + This is placeholder. If you want to catch this event, + overwrite this in your own callback. """ pass def connection_opened(self, uid, client, host, port): """ - connection_opened(self, uid, client, host, port) - Called when a new client connects, and we accepted the + Called when a new client connects and we accepted the connection. You can, however, still kill the connection by returning True in this function. - This is a placeholder. - If you want to catch this event, overwrite it. + uid: + The unique ID for this connection. This is usually + 'ip:port'. + client: + The client class. Normally, you don't need this. + host: + The IP adress for this user + port: + The current port this connection uses. + + + This is placeholder. If you want to catch this event, + overwrite this in your own callback. """ pass def connection_closed(self, uid): """ - connection_closed(self, uid) Called when a connection with a client is closed. This can be when they closed their connection themselves, or when we disconnect someone. - This is a placeholder. - If you want to catch this event, overwrite it. + uid: + The unique ID for this connection. This is usually + 'ip:port'. + + + This is placeholder. If you want to catch this event, + overwrite this in your own callback. """ #TODO: Reason? pass @@ -57,34 +71,53 @@ def connection_limit_exceeded(self, ip, current_connections, max_connections): """ - connection_limit_exceeded(self, ip, current_connections, - max_connections) Called when a new client connects, but the maximum number of connections is exceeded. You can, however, still accept the connection by returning True in this function. - This is a placeholder. - If you want to catch this event, overwrite it. + ip: + The IP-adress for this connection + current_connections: + Amount of current open connections + max_connections: + This is the same as in the config + 'max_connections'. + + + This is placeholder. If you want to catch this event, + overwrite this in your own callback. """ pass def data_received(self, uid, data): """ - data_received(self, uid, data) Called when the server received data from one of the clients. + Normally, you don't need this. - This is a placeholder. - If you want to catch this event, overwrite it. + uid: + The unique ID for this connection. This is usually + 'ip:port'. + + + This is placeholder. If you want to catch this event, + overwrite this in your own callback. """ pass def data_send(self, uid, data): """ - data_send(self, uid, data) Called when the server send data to one of the clients. + Normally, you don't need this. - This is a placeholder. - If you want to catch this event, overwrite it. + uid: + The unique ID for this connection. This is usually + 'ip:port'. + data: + Dict with the data that will be send. + + + This is placeholder. If you want to catch this event, + overwrite this in your own callback. """ pass Modified: trunk/server/core/server.py =================================================================== --- trunk/server/core/server.py 2009-10-21 22:34:25 UTC (rev 305) +++ trunk/server/core/server.py 2009-10-24 18:56:45 UTC (rev 306) @@ -19,46 +19,76 @@ from parser import Parser class Server(threading.Thread): - def __init__(self, config, callback_class): - """ - Will handle all connections from clients. It has one main - socket, and creates all (client)sockets. + """ + This is the server-core for Virtual Playground. This will handle all + connections from clients. It has one main socket, and it will create + all (client)sockets. + + + config: + This should be a dict with settings for the server. See the + bottom of this documentation for a list of all possible + settings. + callback_class: + The callback class will be used to notify you for certain events + that happens in the core. It has to look something like this: - Config: (maybe this explanation should be moved? maybe provide - example config with the core? maybe this function - should check the given config for error, raise - exceptions, and fill in standard values for some - missing keys) - Should be dict with settings for the server. Below a list of all - posible keys and an explanation. + class Callback(core.Callback): + ... - -host: Host to which the server binds itself(am i saying this - correctly?) standard value is '', an empty string, which - means everyone can connect to the server. - -port: Port the server should listen on. Should be int, and can - be anything. - -max_connections: Maximum ammount of clients that can be - connected simultaneously. If this number is exceeded, - the client will dropped, unless specified otherwise (see - callback `connection_limit_exceeded`). Should be int. If - this value is either None or 0, there will be no limit. - """ + See the doc in core.Callback for more information about this. + + -------- + + The settings: + -host: + Host to which the server binds itself. The default is an + empty string, an empty string, which means everyone can + connect to the server. + -port: + The port the server should listen on. Should be int + between 1 and 65535. Note that everything lower than 100 + usually needs to be run as root under Unix/Linux. + Default is 5162. + + -max_connections: + Maximum amount of clients that can be connected + simultaneously. If this number is exceeded, the new + connection will dropped, unless specified otherwise + (see callback `connection_limit_exceeded`). Should be + int. If this value is either None or 0, there will be no + limit. Default is 0. But it is wise to specify a limit. + """ + + + def __init__(self, config, callback_class): self.__sock = None - self.__call = callback_class() + self.__call = callback_class + + # Create all default settings + self.__config_default(config) self.__config = config - self.__clients = {} - self.__parse = Parser(self.__call, self.__clients) + self.clients = {} + self.__parse = Parser(self.__call, self.clients) + threading.Thread.__init__(self) + + def __config_default(self, config): + """ + Create all the config-defaults + """ + config.setdefault('host', '') + config.setdefault('port', 5162) + config.setdefault('max_connections', 0) + def start_server(self): """ - start_server() => None - - This will start the server. It will listen on the port - specified in the config. + This will start the server. It will listen on the port specified + in the config. """ #Isn't the server running already? @@ -90,19 +120,19 @@ time.sleep(1) self.__sock.listen(20) - self.__call.server_online(self.__clients) + self.__call.server_online(self.clients) #Infinite loop that will wait for incomming connections while 1: sock, addr = self.__sock.accept() - if self.__config['max_connections'] != None and \ - len(self.__clients) >= \ + if self.__config['max_connections'] and \ + len(self.clients) >= \ self.__config['max_connections']: #We exceeded our connection limit, but maybe #this is a special occasion? Send callback! if not self.__call.connection_limit_exceeded( - addr[0], len(self.__clients), + addr[0], len(self.clients), self.__config['max_connections']): #We're full, kick him out @@ -111,24 +141,25 @@ continue uid = addr[0] + ':' + str(addr[1]) - self.__clients[uid] = Client(uid, sock, self.__clients, + self.clients[uid] = Client(uid, sock, self.clients, self.__call, self.__parse) - self.__clients[uid].start() + self.clients[uid].start() if self.__call.connection_opened(uid, - self.__clients[uid], *addr): + self.clients[uid], *addr): #User returned True -> drop user time.sleep(0.1) #Let the `run` start - self.__clients[uid].close() + self.clients[uid].close() class Client(threading.Thread): + """ + This class manages the socket for the connection to clients. + Each client has it's own class. + """ + def __init__(self, uid, sock, clients, callback, parser): - """ - This class manages the socket for the connection to clients. - Each client has it's own class. - """ self.__uid = uid self.__sock = sock @@ -176,26 +207,27 @@ def close(self): """ - close() => None - Closes this connection, and kills the socket. + Closes the connection for this client and kills the socket. """ self.__sock.close() self.is_online = False - self.__call.connection_close(self.__uid) + self.__call.connection_closed(self.__uid) del self.__clients[self.__uid] def send(self, data_header, data_body = {}): """ - send(data_header, data_body = {}) => None Sends `data_body` of type `data_header` to the client. It will automatically be encoded as JSON, and the delimeter character (chr(1)) will be send automatically too. - `Data_header` is a string, `data_body` a dict. + `data_header` is a string, `data_body` a dict. """ self.__call.data_send(self.__uid, {data_header:data_body}) data = simplejson.dumps({data_header:data_body}) - self.__sock.send(data + chr(1)) + + try: + self.__sock.send(data + chr(1)) + except: pass This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <Blu...@us...> - 2009-11-28 23:27:02
|
Revision: 310 http://virtplayground.svn.sourceforge.net/virtplayground/?rev=310&view=rev Author: BlueWolf_ Date: 2009-11-28 23:26:54 +0000 (Sat, 28 Nov 2009) Log Message: ----------- * uid is now a md5-hash, for privacy reasons * It will tell the client when the server is full * It will now kick a client properly after returning True in callback.connection_opened * Parser calls the function it needs * Changed the arguments in callback.connection_limit_exceeded * callback.connection_closed now has a reason! * Uses it own exceptions Modified Paths: -------------- trunk/server/core/callback.py trunk/server/core/server.py Modified: trunk/server/core/callback.py =================================================================== --- trunk/server/core/callback.py 2009-11-15 16:19:20 UTC (rev 309) +++ trunk/server/core/callback.py 2009-11-28 23:26:54 UTC (rev 310) @@ -41,9 +41,9 @@ client: The client class. Normally, you don't need this. host: - The IP adress for this user + The IP adress for this user. port: - The current port this connection uses. + The current port for this connection. This is placeholder. If you want to catch this event, @@ -51,7 +51,7 @@ """ pass - def connection_closed(self, uid): + def connection_closed(self, uid, reason): """ Called when a connection with a client is closed. This can be when they closed their connection themselves, or when we @@ -60,28 +60,37 @@ uid: The unique ID for this connection. This is usually 'ip:port'. + reason: + A string, representing the reason why it disconnected. + It currently has these options: + * "closed" - Client dropped the connection + unexpectedly + * "timeout" - Client timed out (No data for 45s) + * "manual" - Server (you) closed the connection + with .close() This is placeholder. If you want to catch this event, overwrite this in your own callback. """ - #TODO: Reason? pass - def connection_limit_exceeded(self, ip, current_connections, - max_connections): + def connection_limit_exceeded(self, current_connections, + max_connections, host, port): """ Called when a new client connects, but the maximum number of connections is exceeded. You can, however, still accept the connection by returning True in this function. - ip: - The IP-adress for this connection current_connections: Amount of current open connections max_connections: This is the same as in the config 'max_connections'. + host: + The IP adress for this user. + port: + The current port for this connection. This is placeholder. If you want to catch this event, Modified: trunk/server/core/server.py =================================================================== --- trunk/server/core/server.py 2009-11-15 16:19:20 UTC (rev 309) +++ trunk/server/core/server.py 2009-11-28 23:26:54 UTC (rev 310) @@ -15,7 +15,7 @@ ## along with this program; if not, write to the Free Software ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -import simplejson, socket, threading, time +import simplejson, socket, threading, time, md5 from parser import Parser class Server(threading.Thread): @@ -93,7 +93,7 @@ #Isn't the server running already? if self.__sock: - raise Exception, "The server is already online!" + raise ConnectionError("The server is already online!") #Load our server socket self.__sock = socket.socket(socket.AF_INET, @@ -132,25 +132,34 @@ #We exceeded our connection limit, but maybe #this is a special occasion? Send callback! if not self.__call.connection_limit_exceeded( - addr[0], len(self.clients), - self.__config['max_connections']): + len(self.clients), + self.__config['max_connections'], + addr[0], addr[1]): #We're full, kick him out - #TODO: Tell him we're full? + #Tell him we're full + data = simplejson.dumps({ + "disconnect":{"reason":"full"} + }) + try: sock.send(data + chr(1)) + except: pass + sock.close() continue - uid = addr[0] + ':' + str(addr[1]) + # Chozo wanted the 8 so badly... + uid = md5.new(addr[0] + str(addr[1])).hexdigest()[:8] self.clients[uid] = Client(uid, sock, self.clients, self.__call, self.__parse) - self.clients[uid].start() if self.__call.connection_opened(uid, self.clients[uid], *addr): #User returned True -> drop user - time.sleep(0.1) #Let the `run` start self.clients[uid].close() + continue + self.clients[uid].start() + class Client(threading.Thread): @@ -186,12 +195,14 @@ while 1: try: data = self.__sock.recv(1024) + except socket.timeout: + if self.is_online: self.close("timeout") + return except: - #Ping timeout? - if self.is_online: self.close() + if self.is_online: self.close("closed") return if not data: - if self.is_online: self.close() + if self.is_online: self.close("closed") return buffer += data @@ -205,15 +216,17 @@ self.__parser(self.__uid, simplejson.loads(msg)) - def close(self): + def close(self, reason = "manual"): """ Closes the connection for this client and kills the socket. """ - + + try: self.__sock.shutdown(0); + except: pass self.__sock.close() self.is_online = False - self.__call.connection_closed(self.__uid) + self.__call.connection_closed(self.__uid, reason) del self.__clients[self.__uid] @@ -231,3 +244,5 @@ self.__sock.send(data + chr(1)) except: pass +class ConnectionError(Exception): + pass This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <Blu...@us...> - 2009-12-14 19:46:33
|
Revision: 313 http://virtplayground.svn.sourceforge.net/virtplayground/?rev=313&view=rev Author: BlueWolf_ Date: 2009-12-14 19:46:21 +0000 (Mon, 14 Dec 2009) Log Message: ----------- * You can now log in as VP-client, as user (not as bot) * It uses the database to verify an user * It uses RSA to get the password from the user * client is now a dict, not a connection-class (the connection-class is now in client['con']) * In parser, it now calls the appropriate function Don't forget to insert database.sql in your database! Modified Paths: -------------- trunk/server/core/callback.py trunk/server/core/parser.py trunk/server/core/server.py Added Paths: ----------- trunk/server/core/database.py trunk/server/core/database.sql trunk/server/core/rsa.py Modified: trunk/server/core/callback.py =================================================================== --- trunk/server/core/callback.py 2009-11-29 20:26:22 UTC (rev 312) +++ trunk/server/core/callback.py 2009-12-14 19:46:21 UTC (rev 313) @@ -24,7 +24,7 @@ calling Server.start() - This is placeholder. If you want to catch this event, + This is a placeholder. If you want to catch this event, overwrite this in your own callback. """ pass @@ -39,14 +39,14 @@ The unique ID for this connection. This is usually 'ip:port'. client: - The client class. Normally, you don't need this. + The client info. Normally, you don't need this. host: The IP adress for this user. port: The current port for this connection. - This is placeholder. If you want to catch this event, + This is a placeholder. If you want to catch this event, overwrite this in your own callback. """ pass @@ -68,9 +68,12 @@ * "timeout" - Client timed out (No data for 45s) * "manual" - Server (you) closed the connection with .close() + * "duplicate" - Another client has logged in on this + account. This connection has been + kicked - This is placeholder. If you want to catch this event, + This is a placeholder. If you want to catch this event, overwrite this in your own callback. """ pass @@ -108,7 +111,7 @@ 'ip:port'. - This is placeholder. If you want to catch this event, + This is a placeholder. If you want to catch this event, overwrite this in your own callback. """ pass @@ -125,7 +128,7 @@ Dict with the data that will be send. - This is placeholder. If you want to catch this event, + This is a placeholder. If you want to catch this event, overwrite this in your own callback. """ pass Added: trunk/server/core/database.py =================================================================== --- trunk/server/core/database.py (rev 0) +++ trunk/server/core/database.py 2009-12-14 19:46:21 UTC (rev 313) @@ -0,0 +1,72 @@ +## This file is part of Virtual Playground +## Copyright (c) 2009 Jos Ratsma + Koen Koning + +## 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 2 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;guaranteed if not, write to the Free Software +## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +import MySQLdb + +class Database(): + def __init__(self, host, user, passwd, db): + self.host = host + self.user = user + self.passwd = passwd + self.db = db + + def connect(self): + try: + self.dbcon = MySQLdb.Connect( + host = self.host, + user = self.user, + passwd = self.passwd, + db = self.db + ) + self.db = self.dbcon.cursor(MySQLdb.cursors.DictCursor) + + except: + raise DBConnectionError( + "Could not connect to the database!") + + def __getattr__(self, attr): + """ + Called when a functions is not in our class. We pass everything + through to our MySQLdb + """ + + def exc(*arg): + """ + Will return the real function from MySQLdb. Will ping + before executing the command, so it will automatically + reconnect. + """ + + # Uncomment for raw mysql-debugging! Fun guaranteed! + # print '\tMySQLdb.' + attr + repr(arg) + + func = getattr(self.db, attr) + try: + dbfunc = func(*arg) + except MySQLdb.OperationalError, message: + if message[0] == 2006: # Mysql has gone away + self._connect() + dbfunc = func(*arg) + else: #Some other error we don't care about + raise MySQLdb.OperationalError, message + + return dbfunc + + return exc + +class DBConnectionError(Exception): + pass Added: trunk/server/core/database.sql =================================================================== --- trunk/server/core/database.sql (rev 0) +++ trunk/server/core/database.sql 2009-12-14 19:46:21 UTC (rev 313) @@ -0,0 +1,33 @@ +-- phpMyAdmin SQL Dump +-- version 3.1.2deb1ubuntu0.2 +-- http://www.phpmyadmin.net +-- +-- Host: localhost +-- Generation Time: Dec 14, 2009 at 08:38 PM +-- Server version: 5.0.75 +-- PHP Version: 5.2.6-3ubuntu4.4 + +SET SQL_MODE="NO_AUTO_VALUE_ON_ZERO"; + + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!40101 SET NAMES utf8 */; + +-- +-- Database: `VP` +-- + +-- -------------------------------------------------------- + +-- +-- Table structure for table `users` +-- + +CREATE TABLE IF NOT EXISTS `users` ( + `id` int(11) NOT NULL auto_increment COMMENT 'Also known as uid', + `username` varchar(255) NOT NULL, + `password` varchar(40) NOT NULL COMMENT 'Password in sha1', + PRIMARY KEY (`id`) +) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=2 ; Modified: trunk/server/core/parser.py =================================================================== --- trunk/server/core/parser.py 2009-11-29 20:26:22 UTC (rev 312) +++ trunk/server/core/parser.py 2009-12-14 19:46:21 UTC (rev 313) @@ -15,21 +15,98 @@ ## along with this program; if not, write to the Free Software ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +import rsa + class Parser(): - def __init__(self, callback, clients): + def __init__(self, callback, server): """ - This class parses all received messages from client. - It may need a better name... + This class parses all received messages from the client. """ - self.__call = callback - self.__clients = clients + self.callback = callback + self.server = server + # Shortkeys + self.db = self.server.database + self.clients = self.server.clients + def __call__(self, uid, msg): - self.__call.data_received(uid, msg) + self.callback.data_received(uid, msg) head = msg.keys()[0] body = msg[head] - if head == 'PNG': #Ping - pass #Should this have it's own callback/do anything? + func = getattr(self, str(head), None) + if (func): + func(uid, body) + + + def login(self, uid, msg): + """ + Send when the users wants to log in + usr - The username + pwd - The (rsa encrypted) sha-password + bot - Whether the it's an bot or an user + for - For what services it's logging in + "VP" - VP client + client - list with [app_name, app_version] + """ + + client = self.clients[uid] + data = client['con'] + + # Ignore when user is already logged in + if client['status'] != "": return + + # Decrypt the password + pwd = rsa.decrypt(msg['pwd'], client['rsa']) + + if msg['for'] == 'VP': + + self.db.execute(""" + SELECT + id, username, password + FROM + users + WHERE + username = %(user)s + AND password = %(password)s + """, {'user': msg['usr'], 'password': pwd}) + user = self.db.fetchone() + + if user == None: # No login, bad user! + data.send("login", { + "succeed": False, + "reason": "bad login" + }) + return; + + + if msg['bot'] == False: # Just an ordinary user + + # Check if the user is already logged in + for id, cl in self.clients.items(): + if cl['status'] == "VP" and \ + cl['bot'] == False and \ + cl['id'] == user['id']: + # Disconnect user + cl['con'].send("disconnect", + {'reason':'duplicate'}) + cl['con'].close('duplicate') + + + # Log the user in + client['id'] = user['id'] + client['user'] = user['username'] + client['bot'] = False + client['status'] = "VP" + + data.send("login", { + "succeed": True, + "username": user['username'], + "uid": uid, + "id": user['id'] + }) + + else: # Client is bot + pass #TODO Added: trunk/server/core/rsa.py =================================================================== --- trunk/server/core/rsa.py (rev 0) +++ trunk/server/core/rsa.py 2009-12-14 19:46:21 UTC (rev 313) @@ -0,0 +1,427 @@ +"""RSA module + +Module for calculating large primes, and RSA encryption, decryption, +signing and verification. Includes generating public and private keys. +""" + +__author__ = "Sybren Stuvel, Marloes de Boer and Ivo Tamboer" +__date__ = "2009-01-22" + +# NOTE: Python's modulo can return negative numbers. We compensate for +# this behaviour using the abs() function + +from cPickle import dumps, loads +import base64 +import math +import os +import random +import sys +import types +import zlib + +def gcd(p, q): + """Returns the greatest common divisor of p and q + + + >>> gcd(42, 6) + 6 + """ + if p<q: return gcd(q, p) + if q == 0: return p + return gcd(q, abs(p%q)) + +def bytes2int(bytes): + """Converts a list of bytes or a string to an integer + + >>> (128*256 + 64)*256 + + 15 + 8405007 + >>> l = [128, 64, 15] + >>> bytes2int(l) + 8405007 + """ + + if not (type(bytes) is types.ListType or type(bytes) is types.StringType): + raise TypeError("You must pass a string or a list") + + # Convert byte stream to integer + integer = 0 + for byte in bytes: + integer *= 256 + if type(byte) is types.StringType: byte = ord(byte) + integer += byte + + return integer + +def int2bytes(number): + """Converts a number to a string of bytes + + >>> bytes2int(int2bytes(123456789)) + 123456789 + """ + + if not (type(number) is types.LongType or type(number) is types.IntType): + raise TypeError("You must pass a long or an int") + + string = "" + + while number > 0: + string = "%s%s" % (chr(number & 0xFF), string) + number /= 256 + + return string + +def fast_exponentiation(a, p, n): + """Calculates r = a^p mod n + """ + result = a % n + remainders = [] + while p != 1: + remainders.append(p & 1) + p = p >> 1 + while remainders: + rem = remainders.pop() + result = ((a ** rem) * result ** 2) % n + return result + +def read_random_int(nbits): + """Reads a random integer of approximately nbits bits rounded up + to whole bytes""" + + nbytes = ceil(nbits/8) + randomdata = os.urandom(nbytes) + return bytes2int(randomdata) + +def ceil(x): + """ceil(x) -> int(math.ceil(x))""" + + return int(math.ceil(x)) + +def randint(minvalue, maxvalue): + """Returns a random integer x with minvalue <= x <= maxvalue""" + + # Safety - get a lot of random data even if the range is fairly + # small + min_nbits = 32 + + # The range of the random numbers we need to generate + range = maxvalue - minvalue + + # Which is this number of bytes + rangebytes = ceil(math.log(range, 2) / 8) + + # Convert to bits, but make sure it's always at least min_nbits*2 + rangebits = max(rangebytes * 8, min_nbits * 2) + + # Take a random number of bits between min_nbits and rangebits + nbits = random.randint(min_nbits, rangebits) + + return (read_random_int(nbits) % range) + minvalue + +def fermat_little_theorem(p): + """Returns 1 if p may be prime, and something else if p definitely + is not prime""" + + a = randint(1, p-1) + return fast_exponentiation(a, p-1, p) + +def jacobi(a, b): + """Calculates the value of the Jacobi symbol (a/b) + """ + + if a % b == 0: + return 0 + result = 1 + while a > 1: + if a & 1: + if ((a-1)*(b-1) >> 2) & 1: + result = -result + b, a = a, b % a + else: + if ((b ** 2 - 1) >> 3) & 1: + result = -result + a = a >> 1 + return result + +def jacobi_witness(x, n): + """Returns False if n is an Euler pseudo-prime with base x, and + True otherwise. + """ + + j = jacobi(x, n) % n + f = fast_exponentiation(x, (n-1)/2, n) + + if j == f: return False + return True + +def randomized_primality_testing(n, k): + """Calculates whether n is composite (which is always correct) or + prime (which is incorrect with error probability 2**-k) + + Returns False if the number if composite, and True if it's + probably prime. + """ + + q = 0.5 # Property of the jacobi_witness function + + # t = int(math.ceil(k / math.log(1/q, 2))) + t = ceil(k / math.log(1/q, 2)) + for i in range(t+1): + x = randint(1, n-1) + if jacobi_witness(x, n): return False + + return True + +def is_prime(number): + """Returns True if the number is prime, and False otherwise. + + >>> is_prime(42) + 0 + >>> is_prime(41) + 1 + """ + + """ + if not fermat_little_theorem(number) == 1: + # Not prime, according to Fermat's little theorem + return False + """ + + if randomized_primality_testing(number, 5): + # Prime, according to Jacobi + return True + + # Not prime + return False + + +def getprime(nbits): + """Returns a prime number of max. 'math.ceil(nbits/8)*8' bits. In + other words: nbits is rounded up to whole bytes. + + >>> p = getprime(8) + >>> is_prime(p-1) + 0 + >>> is_prime(p) + 1 + >>> is_prime(p+1) + 0 + """ + + nbytes = int(math.ceil(nbits/8)) + + while True: + integer = read_random_int(nbits) + + # Make sure it's odd + integer |= 1 + + # Test for primeness + if is_prime(integer): break + + # Retry if not prime + + return integer + +def are_relatively_prime(a, b): + """Returns True if a and b are relatively prime, and False if they + are not. + + >>> are_relatively_prime(2, 3) + 1 + >>> are_relatively_prime(2, 4) + 0 + """ + + d = gcd(a, b) + return (d == 1) + +def find_p_q(nbits): + """Returns a tuple of two different primes of nbits bits""" + + p = getprime(nbits) + while True: + q = getprime(nbits) + if not q == p: break + + return (p, q) + +def extended_euclid_gcd(a, b): + """Returns a tuple (d, i, j) such that d = gcd(a, b) = ia + jb + """ + + if b == 0: + return (a, 1, 0) + + q = abs(a % b) + r = long(a / b) + (d, k, l) = extended_euclid_gcd(b, q) + + return (d, l, k - l*r) + +# Main function: calculate encryption and decryption keys +def calculate_keys(p, q, nbits): + """Calculates an encryption and a decryption key for p and q, and + returns them as a tuple (e, d)""" + + n = p * q + phi_n = (p-1) * (q-1) + + while True: + # Make sure e has enough bits so we ensure "wrapping" through + # modulo n + e = getprime(max(8, nbits/2)) + if are_relatively_prime(e, n) and are_relatively_prime(e, phi_n): break + + (d, i, j) = extended_euclid_gcd(e, phi_n) + + if not d == 1: + raise Exception("e (%d) and phi_n (%d) are not relatively prime" % (e, phi_n)) + + if not (e * i) % phi_n == 1: + raise Exception("e (%d) and i (%d) are not mult. inv. modulo phi_n (%d)" % (e, i, phi_n)) + + return (e, i) + + +def gen_keys(nbits): + """Generate RSA keys of nbits bits. Returns (p, q, e, d). + + Note: this can take a long time, depending on the key size. + """ + + while True: + (p, q) = find_p_q(nbits) + (e, d) = calculate_keys(p, q, nbits) + + # For some reason, d is sometimes negative. We don't know how + # to fix it (yet), so we keep trying until everything is shiny + if d > 0: break + + return (p, q, e, d) + +def gen_pubpriv_keys(nbits): + """Generates public and private keys, and returns them as (pub, + priv). + + The public key consists of a dict {e: ..., , n: ....). The private + key consists of a dict {d: ...., p: ...., q: ....). + """ + + (p, q, e, d) = gen_keys(nbits) + + return ( {'e': e, 'n': p*q}, {'d': d, 'p': p, 'q': q} ) + +def encrypt_int(message, ekey, n): + """Encrypts a message using encryption key 'ekey', working modulo + n""" + + if type(message) is types.IntType: + return encrypt_int(long(message), ekey, n) + + if not type(message) is types.LongType: + raise TypeError("You must pass a long or an int") + + if message > 0 and \ + math.floor(math.log(message, 2)) > math.floor(math.log(n, 2)): + raise OverflowError("The message is too long") + + return fast_exponentiation(message, ekey, n) + +def decrypt_int(cyphertext, dkey, n): + """Decrypts a cypher text using the decryption key 'dkey', working + modulo n""" + + return encrypt_int(cyphertext, dkey, n) + +def sign_int(message, dkey, n): + """Signs 'message' using key 'dkey', working modulo n""" + + return decrypt_int(message, dkey, n) + +def verify_int(signed, ekey, n): + """verifies 'signed' using key 'ekey', working modulo n""" + + return encrypt_int(signed, ekey, n) + +def picklechops(chops): + """Pickles and base64encodes it's argument chops""" + + value = zlib.compress(dumps(chops)) + encoded = base64.encodestring(value) + return encoded.strip() + +def unpicklechops(string): + """base64decodes and unpickes it's argument string into chops""" + + return loads(zlib.decompress(base64.decodestring(string))) + +def chopstring(message, key, n, funcref): + """Splits 'message' into chops that are at most as long as n, + converts these into integers, and calls funcref(integer, key, n) + for each chop. + + Used by 'encrypt' and 'sign'. + """ + + msglen = len(message) + mbits = msglen * 8 + nbits = int(math.floor(math.log(n, 2))) + nbytes = nbits / 8 + blocks = msglen / nbytes + + if msglen % nbytes > 0: + blocks += 1 + + cypher = [] + + for bindex in range(blocks): + offset = bindex * nbytes + block = message[offset:offset+nbytes] + value = bytes2int(block) + cypher.append(funcref(value, key, n)) + + return picklechops(cypher) + +def gluechops(chops, key, n, funcref): + """Glues chops back together into a string. calls + funcref(integer, key, n) for each chop. + + Used by 'decrypt' and 'verify'. + """ + message = "" + + chops = unpicklechops(chops) + + for cpart in chops: + mpart = funcref(cpart, key, n) + message += int2bytes(mpart) + + return message + +def encrypt(message, key): + """Encrypts a string 'message' with the public key 'key'""" + + return chopstring(message, key['e'], key['n'], encrypt_int) + +def sign(message, key): + """Signs a string 'message' with the private key 'key'""" + + return chopstring(message, key['d'], key['p']*key['q'], decrypt_int) + +def decrypt(cypher, key): + """Decrypts a cypher with the private key 'key'""" + + return gluechops(cypher, key['d'], key['p']*key['q'], decrypt_int) + +def verify(cypher, key): + """Verifies a cypher with the public key 'key'""" + + return gluechops(cypher, key['e'], key['n'], encrypt_int) + +# Do doctest if we're not imported +if __name__ == "__main__": + import doctest + doctest.testmod() + +__all__ = ["gen_pubpriv_keys", "encrypt", "decrypt", "sign", "verify"] + Modified: trunk/server/core/server.py =================================================================== --- trunk/server/core/server.py 2009-11-29 20:26:22 UTC (rev 312) +++ trunk/server/core/server.py 2009-12-14 19:46:21 UTC (rev 313) @@ -17,6 +17,8 @@ import simplejson, socket, threading, time, md5, random from parser import Parser +from database import Database +import rsa class Server(threading.Thread): """ @@ -60,11 +62,16 @@ (see callback `connection_limit_exceeded`). Should be int. If this value is either None or 0, there will be no limit. Default is 0. But it is wise to specify a limit. + + -rsa_bits: + How many bits the rsa uses for encrypting the password. + More = more secure but slower to generate. Default is 64 """ - def __init__(self, config, callback_class): + def __init__(self, config, callback_class, database): self.__sock = None + self.database = Database(**database) self.__call = callback_class # Create all default settings @@ -72,7 +79,7 @@ self.__config = config self.clients = {} - self.__parse = Parser(self.__call, self.clients) + self.__parse = Parser(self.__call, self) threading.Thread.__init__(self) @@ -83,6 +90,7 @@ config.setdefault('host', '') config.setdefault('port', 5162) config.setdefault('max_connections', 0) + config.setdefault('rsa_bits', 64) def start_server(self): @@ -95,6 +103,9 @@ if self.__sock: raise ConnectionError("The server is already online!") + # Connect to the database + self.database.connect() + #Load our server socket self.__sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) @@ -153,16 +164,20 @@ uid = md5.new(addr[0] + str(addr[1]) + str(random.random())).hexdigest()[:8] - self.clients[uid] = Client(uid, sock, self.clients, - self.__call, self.__parse) + self.clients[uid] = { + "status": "", + "con": Client(uid, sock, self.clients, + self.__call, self.__parse, + self.__config) + } if self.__call.connection_opened(uid, self.clients[uid], *addr): #User returned True -> drop user - self.clients[uid].close() + self.clients[uid]['con'].close() continue - self.clients[uid].start() + self.clients[uid]['con'].start() @@ -172,13 +187,14 @@ Each client has it's own class. """ - def __init__(self, uid, sock, clients, callback, parser): + def __init__(self, uid, sock, clients, callback, parser, config): self.__uid = uid self.__sock = sock self.__clients = clients self.__call = callback self.__parser = parser + self.__config = config self.is_online = True threading.Thread.__init__(self) @@ -190,10 +206,19 @@ """ Used by threading, not for external usage. """ + #Client must ping (at least) every 30 seconds. So we set a #timeout of 45 seconds. self.__sock.settimeout(45) + # Before the client can log in, we first need to create a + # rsa-key + public, private = rsa.gen_pubpriv_keys( + self.__config['rsa_bits']) + self.__clients[self.__uid]['rsa'] = private + self.send("rsa", {"public": public}) + + #Infinite loop that receives data buffer = '' while 1: @@ -225,10 +250,12 @@ Closes the connection for this client and kills the socket. """ + if self.is_online == False: return + self.is_online = False + try: self.__sock.shutdown(0); except: pass self.__sock.close() - self.is_online = False self.__call.connection_closed(self.__uid, reason) This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <Blu...@us...> - 2009-12-14 21:22:43
|
Revision: 315 http://virtplayground.svn.sourceforge.net/virtplayground/?rev=315&view=rev Author: BlueWolf_ Date: 2009-12-14 21:22:34 +0000 (Mon, 14 Dec 2009) Log Message: ----------- Changed uid(and id) now means the user-id and cid now means connection/client-id. Fixing some minor things. Server does another sha for the password Modified Paths: -------------- trunk/server/core/callback.py trunk/server/core/database.sql trunk/server/core/parser.py trunk/server/core/server.py Modified: trunk/server/core/callback.py =================================================================== --- trunk/server/core/callback.py 2009-12-14 19:48:14 UTC (rev 314) +++ trunk/server/core/callback.py 2009-12-14 21:22:34 UTC (rev 315) @@ -29,15 +29,14 @@ """ pass - def connection_opened(self, uid, client, host, port): + def connection_opened(self, cid, client, host, port): """ Called when a new client connects and we accepted the connection. You can, however, still kill the connection by returning True in this function. - uid: - The unique ID for this connection. This is usually - 'ip:port'. + cid: + The unique ID for this connection (connection-id). client: The client info. Normally, you don't need this. host: @@ -51,15 +50,14 @@ """ pass - def connection_closed(self, uid, reason): + def connection_closed(self, cid, reason): """ Called when a connection with a client is closed. This can be when they closed their connection themselves, or when we disconnect someone. - uid: - The unique ID for this connection. This is usually - 'ip:port'. + cid: + The unique ID for this connection (connection-id). reason: A string, representing the reason why it disconnected. It currently has these options: @@ -101,14 +99,13 @@ """ pass - def data_received(self, uid, data): + def data_received(self, cid, data): """ Called when the server received data from one of the clients. Normally, you don't need this. - uid: - The unique ID for this connection. This is usually - 'ip:port'. + cid: + The unique ID for this connection (connection-id) This is a placeholder. If you want to catch this event, @@ -116,14 +113,13 @@ """ pass - def data_send(self, uid, data): + def data_send(self, cid, data): """ Called when the server send data to one of the clients. Normally, you don't need this. - uid: - The unique ID for this connection. This is usually - 'ip:port'. + cid: + The unique ID for this connection. (connectin-id) data: Dict with the data that will be send. Modified: trunk/server/core/database.sql =================================================================== --- trunk/server/core/database.sql 2009-12-14 19:48:14 UTC (rev 314) +++ trunk/server/core/database.sql 2009-12-14 21:22:34 UTC (rev 315) @@ -1,33 +1,10 @@ --- phpMyAdmin SQL Dump --- version 3.1.2deb1ubuntu0.2 --- http://www.phpmyadmin.net -- --- Host: localhost --- Generation Time: Dec 14, 2009 at 08:38 PM --- Server version: 5.0.75 --- PHP Version: 5.2.6-3ubuntu4.4 - -SET SQL_MODE="NO_AUTO_VALUE_ON_ZERO"; - - -/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; -/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; -/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; -/*!40101 SET NAMES utf8 */; - --- --- Database: `VP` --- - --- -------------------------------------------------------- - --- -- Table structure for table `users` -- CREATE TABLE IF NOT EXISTS `users` ( `id` int(11) NOT NULL auto_increment COMMENT 'Also known as uid', `username` varchar(255) NOT NULL, - `password` varchar(40) NOT NULL COMMENT 'Password in sha1', + `password` varchar(40) NOT NULL COMMENT 'Password in double sha1', PRIMARY KEY (`id`) -) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=2 ; +) ENGINE=MyISAM DEFAULT CHARSET=latin1; Modified: trunk/server/core/parser.py =================================================================== --- trunk/server/core/parser.py 2009-12-14 19:48:14 UTC (rev 314) +++ trunk/server/core/parser.py 2009-12-14 21:22:34 UTC (rev 315) @@ -15,7 +15,7 @@ ## along with this program; if not, write to the Free Software ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -import rsa +import rsa, sha class Parser(): def __init__(self, callback, server): @@ -29,18 +29,18 @@ self.db = self.server.database self.clients = self.server.clients - def __call__(self, uid, msg): - self.callback.data_received(uid, msg) + def __call__(self, cid, msg): + self.callback.data_received(cid, msg) head = msg.keys()[0] body = msg[head] func = getattr(self, str(head), None) if (func): - func(uid, body) + func(cid, body) - def login(self, uid, msg): + def login(self, cid, msg): """ Send when the users wants to log in usr - The username @@ -51,7 +51,7 @@ client - list with [app_name, app_version] """ - client = self.clients[uid] + client = self.clients[cid] data = client['con'] # Ignore when user is already logged in @@ -61,6 +61,9 @@ # Decrypt the password pwd = rsa.decrypt(msg['pwd'], client['rsa']) + # Double sha, so one can not insert "raw" sha + pwd = sha.new(pwd).hexdigest() + if msg['for'] == 'VP': self.db.execute(""" @@ -104,8 +107,8 @@ data.send("login", { "succeed": True, "username": user['username'], - "uid": uid, - "id": user['id'] + "cid": cid, + "uid": user['id'] }) else: # Client is bot Modified: trunk/server/core/server.py =================================================================== --- trunk/server/core/server.py 2009-12-14 19:48:14 UTC (rev 314) +++ trunk/server/core/server.py 2009-12-14 21:22:34 UTC (rev 315) @@ -158,26 +158,26 @@ sock.close() continue - # Create unique user-id - uid = md5.new(addr[0] + str(addr[1])).hexdigest()[:8] - while uid in self.clients: - uid = md5.new(addr[0] + str(addr[1]) + + # Create unique client-id + cid = md5.new(addr[0] + str(addr[1])).hexdigest()[:8] + while cid in self.clients: + cid = md5.new(addr[0] + str(addr[1]) + str(random.random())).hexdigest()[:8] - self.clients[uid] = { + self.clients[cid] = { "status": "", - "con": Client(uid, sock, self.clients, + "con": Client(cid, sock, self.clients, self.__call, self.__parse, self.__config) } - if self.__call.connection_opened(uid, - self.clients[uid], *addr): + if self.__call.connection_opened(cid, + self.clients[cid], *addr): #User returned True -> drop user - self.clients[uid]['con'].close() + self.clients[cid]['con'].close() continue - self.clients[uid]['con'].start() + self.clients[cid]['con'].start() @@ -187,9 +187,9 @@ Each client has it's own class. """ - def __init__(self, uid, sock, clients, callback, parser, config): + def __init__(self, cid, sock, clients, callback, parser, config): - self.__uid = uid + self.__cid = cid self.__sock = sock self.__clients = clients self.__call = callback @@ -200,7 +200,7 @@ threading.Thread.__init__(self) def __repr__(self): - return '<Client(%s)>'%self.__uid + return '<Client(%s)>'%self.__cid def run(self): """ @@ -215,7 +215,7 @@ # rsa-key public, private = rsa.gen_pubpriv_keys( self.__config['rsa_bits']) - self.__clients[self.__uid]['rsa'] = private + self.__clients[self.__cid]['rsa'] = private self.send("rsa", {"public": public}) @@ -242,7 +242,7 @@ data = data[:-1] for msg in data: - self.__parser(self.__uid, + self.__parser(self.__cid, simplejson.loads(msg)) def close(self, reason = "manual"): @@ -257,9 +257,9 @@ except: pass self.__sock.close() - self.__call.connection_closed(self.__uid, reason) + self.__call.connection_closed(self.__cid, reason) - del self.__clients[self.__uid] + del self.__clients[self.__cid] def send(self, data_header, data_body = {}): """ @@ -268,7 +268,7 @@ (chr(1)) will be send automatically too. `data_header` is a string, `data_body` a dict. """ - self.__call.data_send(self.__uid, {data_header:data_body}) + self.__call.data_send(self.__cid, {data_header:data_body}) data = simplejson.dumps({data_header:data_body}) try: This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <Blu...@us...> - 2009-12-15 20:56:55
|
Revision: 317 http://virtplayground.svn.sourceforge.net/virtplayground/?rev=317&view=rev Author: BlueWolf_ Date: 2009-12-15 20:56:45 +0000 (Tue, 15 Dec 2009) Log Message: ----------- It now catches crashes from the parser Modified Paths: -------------- trunk/server/core/callback.py trunk/server/core/server.py Modified: trunk/server/core/callback.py =================================================================== --- trunk/server/core/callback.py 2009-12-14 21:23:28 UTC (rev 316) +++ trunk/server/core/callback.py 2009-12-15 20:56:45 UTC (rev 317) @@ -69,6 +69,10 @@ * "duplicate" - Another client has logged in on this account. This connection has been kicked + * "crash" - A crash in the parser happened. This + could be because of malicious data was + send. Use callback.debug_traceback to + get the precise error This is a placeholder. If you want to catch this event, @@ -128,4 +132,19 @@ overwrite this in your own callback. """ pass + + + def debug_crash(self, tracback): + """ + Usefull for debugging. Normally, when the parser (our any code + in the parser, like the callback) crashes, the connection will + be kicked and the error will be ignored. If you want to know the + precise error, use this function to get the trackback + + traceback: + A string... with a traceback, duh :-) + + This is a placeholder. If you want to catch this event, + overwrite this in your own callback. + """ Modified: trunk/server/core/server.py =================================================================== --- trunk/server/core/server.py 2009-12-14 21:23:28 UTC (rev 316) +++ trunk/server/core/server.py 2009-12-15 20:56:45 UTC (rev 317) @@ -15,7 +15,7 @@ ## along with this program; if not, write to the Free Software ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -import simplejson, socket, threading, time, md5, random +import simplejson, socket, threading, time, md5, random, sys from parser import Parser from database import Database import rsa @@ -201,7 +201,25 @@ def __repr__(self): return '<Client(%s)>'%self.__cid + + + def __parser_crash(self): + """ + Will create a traceback and call the callback + """ + #Get some debugging info + et, ev, tb = sys.exc_info() + traceback = "" + while tb: + co = tb.tb_frame.f_code + traceback += str(co.co_filename) + ':' + \ + str(tb.tb_lineno) + '\n' + tb = tb.tb_next + traceback += ev.__class__.__name__ + ': ' + str(ev) + + self.__call.debug_crash(traceback) + def run(self): """ Used by threading, not for external usage. @@ -242,8 +260,16 @@ data = data[:-1] for msg in data: - self.__parser(self.__cid, + try: + self.__parser(self.__cid, simplejson.loads(msg)) + except Exception, Er: + self.__parser_crash() + + # Kick connection + self.send("disconnect", + {"reason":"crash"}) + self.close("crash") def close(self, reason = "manual"): """ This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <Blu...@us...> - 2009-12-16 20:26:15
|
Revision: 320 http://virtplayground.svn.sourceforge.net/virtplayground/?rev=320&view=rev Author: BlueWolf_ Date: 2009-12-16 20:26:07 +0000 (Wed, 16 Dec 2009) Log Message: ----------- Forgot it's up to the app to check for logins, not the core Modified Paths: -------------- trunk/server/core/callback.py trunk/server/core/parser.py trunk/server/core/server.py Removed Paths: ------------- trunk/server/core/database.py trunk/server/core/database.sql Modified: trunk/server/core/callback.py =================================================================== --- trunk/server/core/callback.py 2009-12-15 21:01:38 UTC (rev 319) +++ trunk/server/core/callback.py 2009-12-16 20:26:07 UTC (rev 320) @@ -147,4 +147,25 @@ This is a placeholder. If you want to catch this event, overwrite this in your own callback. """ - + + pass + + def check_login(self, usr, pwd): + """ + This is used to verify the user's login. Return the real + username* when it's correct and False else if it's not. This function will + return False by default. + + * This is in case you decide to not make the username case + intensive + + usr: + The username + pwd: + The (double) sha-ed password + + This is a placeholder. If you want to catch this event, + overwrite this in your own callback. + """ + + return False Deleted: trunk/server/core/database.py =================================================================== --- trunk/server/core/database.py 2009-12-15 21:01:38 UTC (rev 319) +++ trunk/server/core/database.py 2009-12-16 20:26:07 UTC (rev 320) @@ -1,72 +0,0 @@ -## This file is part of Virtual Playground -## Copyright (c) 2009 Jos Ratsma + Koen Koning - -## 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 2 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;guaranteed if not, write to the Free Software -## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - -import MySQLdb - -class Database(): - def __init__(self, host, user, passwd, db): - self.host = host - self.user = user - self.passwd = passwd - self.db = db - - def connect(self): - try: - self.dbcon = MySQLdb.Connect( - host = self.host, - user = self.user, - passwd = self.passwd, - db = self.db - ) - self.db = self.dbcon.cursor(MySQLdb.cursors.DictCursor) - - except: - raise DBConnectionError( - "Could not connect to the database!") - - def __getattr__(self, attr): - """ - Called when a functions is not in our class. We pass everything - through to our MySQLdb - """ - - def exc(*arg): - """ - Will return the real function from MySQLdb. Will ping - before executing the command, so it will automatically - reconnect. - """ - - # Uncomment for raw mysql-debugging! Fun guaranteed! - # print '\tMySQLdb.' + attr + repr(arg) - - func = getattr(self.db, attr) - try: - dbfunc = func(*arg) - except MySQLdb.OperationalError, message: - if message[0] == 2006: # Mysql has gone away - self._connect() - dbfunc = func(*arg) - else: #Some other error we don't care about - raise MySQLdb.OperationalError, message - - return dbfunc - - return exc - -class DBConnectionError(Exception): - pass Deleted: trunk/server/core/database.sql =================================================================== --- trunk/server/core/database.sql 2009-12-15 21:01:38 UTC (rev 319) +++ trunk/server/core/database.sql 2009-12-16 20:26:07 UTC (rev 320) @@ -1,10 +0,0 @@ --- --- Table structure for table `users` --- - -CREATE TABLE IF NOT EXISTS `users` ( - `id` int(11) NOT NULL auto_increment COMMENT 'Also known as uid', - `username` varchar(255) NOT NULL, - `password` varchar(40) NOT NULL COMMENT 'Password in double sha1', - PRIMARY KEY (`id`) -) ENGINE=MyISAM DEFAULT CHARSET=latin1; Modified: trunk/server/core/parser.py =================================================================== --- trunk/server/core/parser.py 2009-12-15 21:01:38 UTC (rev 319) +++ trunk/server/core/parser.py 2009-12-16 20:26:07 UTC (rev 320) @@ -26,7 +26,6 @@ self.server = server # Shortkeys - self.db = self.server.database self.clients = self.server.clients def __call__(self, cid, msg): @@ -66,18 +65,11 @@ if msg['for'] == 'VP': - self.db.execute(""" - SELECT - id, username, password - FROM - users - WHERE - username = %(user)s - AND password = %(password)s - """, {'user': msg['usr'], 'password': pwd}) - user = self.db.fetchone() + # Check for login + username = self.callback.check_login(msg['usr'], pwd) - if user == None: # No login, bad user! + if username == False: + # No login, bad user! data.send("login", { "succeed": False, "reason": "bad login" @@ -91,7 +83,7 @@ for id, cl in self.clients.items(): if cl['status'] == "VP" and \ cl['bot'] == False and \ - cl['id'] == user['id']: + cl['user'] == username: # Disconnect user cl['con'].send("disconnect", {'reason':'duplicate'}) @@ -99,16 +91,14 @@ # Log the user in - client['id'] = user['id'] - client['user'] = user['username'] + client['user'] = username client['bot'] = False client['status'] = "VP" data.send("login", { "succeed": True, - "username": user['username'], - "cid": cid, - "uid": user['id'] + "username": username, + "cid": cid }) else: # Client is bot Modified: trunk/server/core/server.py =================================================================== --- trunk/server/core/server.py 2009-12-15 21:01:38 UTC (rev 319) +++ trunk/server/core/server.py 2009-12-16 20:26:07 UTC (rev 320) @@ -17,7 +17,6 @@ import simplejson, socket, threading, time, md5, random, sys from parser import Parser -from database import Database import rsa class Server(threading.Thread): @@ -69,9 +68,8 @@ """ - def __init__(self, config, callback_class, database): + def __init__(self, config, callback_class): self.__sock = None - self.database = Database(**database) self.__call = callback_class # Create all default settings @@ -103,9 +101,6 @@ if self.__sock: raise ConnectionError("The server is already online!") - # Connect to the database - self.database.connect() - #Load our server socket self.__sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <Blu...@us...> - 2009-12-18 19:07:17
|
Revision: 324 http://virtplayground.svn.sourceforge.net/virtplayground/?rev=324&view=rev Author: BlueWolf_ Date: 2009-12-18 19:07:07 +0000 (Fri, 18 Dec 2009) Log Message: ----------- Uses hashlib.sha1 instead of md5 for the cids Modified Paths: -------------- trunk/server/core/parser.py trunk/server/core/server.py Modified: trunk/server/core/parser.py =================================================================== --- trunk/server/core/parser.py 2009-12-18 19:06:56 UTC (rev 323) +++ trunk/server/core/parser.py 2009-12-18 19:07:07 UTC (rev 324) @@ -15,7 +15,7 @@ ## along with this program; if not, write to the Free Software ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -import rsa, sha +import rsa, hashlib class Parser(): def __init__(self, callback, server): @@ -60,8 +60,8 @@ # Decrypt the password pwd = rsa.decrypt(msg['pwd'], client['rsa']) - # Double sha, so one can not insert "raw" sha - pwd = sha.new(pwd).hexdigest() + # Double sha, so n one can insert a "raw" sha + pwd = hashlib.sha1(pwd).hexdigest() if msg['for'] == 'VP': Modified: trunk/server/core/server.py =================================================================== --- trunk/server/core/server.py 2009-12-18 19:06:56 UTC (rev 323) +++ trunk/server/core/server.py 2009-12-18 19:07:07 UTC (rev 324) @@ -15,7 +15,7 @@ ## along with this program; if not, write to the Free Software ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -import simplejson, socket, threading, time, md5, random, sys +import simplejson, socket, threading, time, random, sys, hashlib from parser import Parser import rsa @@ -154,9 +154,9 @@ continue # Create unique client-id - cid = md5.new(addr[0] + str(addr[1])).hexdigest()[:8] + cid = hashlib.sha1(addr[0] + str(addr[1])).hexdigest()[:8] while cid in self.clients: - cid = md5.new(addr[0] + str(addr[1]) + + cid = hashlib.sha1(addr[0] + str(addr[1]) + str(random.random())).hexdigest()[:8] self.clients[cid] = { This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <Blu...@us...> - 2009-12-19 20:59:38
|
Revision: 327 http://virtplayground.svn.sourceforge.net/virtplayground/?rev=327&view=rev Author: BlueWolf_ Date: 2009-12-19 20:59:27 +0000 (Sat, 19 Dec 2009) Log Message: ----------- A login-error will now go through {"error": {...}} instead of login. Added a callback so the app can now block users or bots Modified Paths: -------------- trunk/server/core/callback.py trunk/server/core/parser.py trunk/server/core/server.py Modified: trunk/server/core/callback.py =================================================================== --- trunk/server/core/callback.py 2009-12-18 19:33:06 UTC (rev 326) +++ trunk/server/core/callback.py 2009-12-19 20:59:27 UTC (rev 327) @@ -153,11 +153,11 @@ def check_login(self, usr, pwd): """ This is used to verify the user's login. Return the real - username* when it's correct and False else if it's not. This function will - return False by default. + username* when it's correct and False when it's not. This + function will return False by default. * This is in case you decide to not make the username case - intensive + intensive. Now the clients knows the real username usr: The username @@ -169,3 +169,32 @@ """ return False + + def may_login(self, username, ip, bot, bots): + """ + This is called after (a succesfull) callback.check_login. Use + this to block certain users or IP's or block an additional bot + when the users reached the limit of connected bots. + + Return True when the user (or bot) may log in and False when it + may not. This will send an "not allowed" error. If you wish to + send an other error, return the error. An example may be: + * "login not allowed" + * "bot limit reached" + * "login blocked" + This function will return True by default. + + username: + The username for this user + ip: + The IP-adress this user has + bot: + If it wants to log in as a bot (True/False) + bots: + List with the cids of all active active bots for this + user. + + This is a placeholder. If you want to catch this event, + overwrite this in your own callback. + """ + return True Modified: trunk/server/core/parser.py =================================================================== --- trunk/server/core/parser.py 2009-12-18 19:33:06 UTC (rev 326) +++ trunk/server/core/parser.py 2009-12-19 20:59:27 UTC (rev 327) @@ -60,7 +60,7 @@ # Decrypt the password pwd = rsa.decrypt(msg['pwd'], client['rsa']) - # Double sha, so n one can insert a "raw" sha + # Double sha, so no one can insert "raw" sha pwd = hashlib.sha1(pwd).hexdigest() if msg['for'] == 'VP': @@ -70,13 +70,34 @@ if username == False: # No login, bad user! - data.send("login", { - "succeed": False, - "reason": "bad login" - }) - return; + data.send("error", {"reason":"bad login"}) + return + + + + # Find out how many bots this user has running + bots = [] + for id, client in self.clients.items(): + if client['status'] == 'VP' and \ + client['user'] == username and \ + client['bot'] == True: + bots.append(id) + + # Check if the user may log in + may_login = self.callback.may_login(username, + data.get_ip(), msg['bot'], bots) + + if may_login != True: # User may not login + if may_login == False: + reason = "login not allowed" + else: + reason = may_login + data.send("error", {"reason":reason}) + return + + if msg['bot'] == False: # Just an ordinary user # Check if the user is already logged in @@ -96,10 +117,10 @@ client['status'] = "VP" data.send("login", { - "succeed": True, "username": username, "cid": cid }) else: # Client is bot pass #TODO + Modified: trunk/server/core/server.py =================================================================== --- trunk/server/core/server.py 2009-12-18 19:33:06 UTC (rev 326) +++ trunk/server/core/server.py 2009-12-19 20:59:27 UTC (rev 327) @@ -331,6 +331,13 @@ try: self.__sock.send(data + chr(1)) except: pass + + def get_ip(self): + """ + Get the client's IP-adress + """ + + return self.__sock.getpeername()[0] class ConnectionError(Exception): pass This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <Blu...@us...> - 2009-12-23 13:16:43
|
Revision: 329 http://virtplayground.svn.sourceforge.net/virtplayground/?rev=329&view=rev Author: BlueWolf_ Date: 2009-12-23 13:16:28 +0000 (Wed, 23 Dec 2009) Log Message: ----------- Client can now log in as bot! The core is now using a dict to share variables across all modules. Fixing a bot where a client sometimes couldn't log in. Modified Paths: -------------- trunk/server/core/callback.py trunk/server/core/parser.py trunk/server/core/server.py Modified: trunk/server/core/callback.py =================================================================== --- trunk/server/core/callback.py 2009-12-19 20:59:37 UTC (rev 328) +++ trunk/server/core/callback.py 2009-12-23 13:16:28 UTC (rev 329) @@ -170,7 +170,7 @@ return False - def may_login(self, username, ip, bot, bots): + def may_login(self, username, screenname, ip, bot, bots): """ This is called after (a succesfull) callback.check_login. Use this to block certain users or IP's or block an additional bot @@ -179,13 +179,17 @@ Return True when the user (or bot) may log in and False when it may not. This will send an "not allowed" error. If you wish to send an other error, return the error. An example may be: - * "login not allowed" - * "bot limit reached" - * "login blocked" + * "login not allowed" - Login is not allowed right now + * "bot limit reached" - User has to much bots running + * "login blocked" - User is blocked + This function will return True by default. username: - The username for this user + The username for this user / owner for this bot + screenname: + The name the users see. This is only different from + username when a bot logs in ip: The IP-adress this user has bot: Modified: trunk/server/core/parser.py =================================================================== --- trunk/server/core/parser.py 2009-12-19 20:59:37 UTC (rev 328) +++ trunk/server/core/parser.py 2009-12-23 13:16:28 UTC (rev 329) @@ -18,18 +18,19 @@ import rsa, hashlib class Parser(): - def __init__(self, callback, server): + def __init__(self, sh): """ This class parses all received messages from the client. """ - self.callback = callback - self.server = server + self.sh = sh + self.call = sh['call'] + self.core = sh['core'] # Shortkeys - self.clients = self.server.clients + self.clients = self.core.clients def __call__(self, cid, msg): - self.callback.data_received(cid, msg) + self.call.data_received(cid, msg) head = msg.keys()[0] body = msg[head] @@ -38,7 +39,35 @@ if (func): func(cid, body) + def _check_double_user(self, isbot, screenname, data): + """ + Checks for an user or bot with the same name + """ + + # Check if there isn't already a bot online + # with this name + for id, cl in self.clients.items(): + if cl['status'] == "VP" and \ + cl['bot'] == True and \ + cl['user'] == screenname: + # Our client cannot log in + data.send("error", { + "reason": "duplicate user" + }) + return False + + if isbot == False: + # Check for the same user + for id, cl in self.clients.items(): + if cl['status'] == "VP" and \ + cl['bot'] == False and \ + cl['user'] == screenname: + # Disconnect this user + cl['con'].close_msg('duplicate') + + return True + def login(self, cid, msg): """ Send when the users wants to log in @@ -64,9 +93,9 @@ pwd = hashlib.sha1(pwd).hexdigest() if msg['for'] == 'VP': - + # Check for login - username = self.callback.check_login(msg['usr'], pwd) + username = self.call.check_login(msg['usr'], pwd) if username == False: # No login, bad user! @@ -77,15 +106,21 @@ # Find out how many bots this user has running bots = [] - for id, client in self.clients.items(): - if client['status'] == 'VP' and \ - client['user'] == username and \ - client['bot'] == True: + for id, cl in self.clients.items(): + if cl['status'] == 'VP' and \ + cl['owner'] == username and \ + cl['bot'] == True: bots.append(id) + # Get screenname + screenname = username + if msg['bot']: + screenname = self.sh['config']['bot_names'] % \ + msg['name'] + # Check if the user may log in - may_login = self.callback.may_login(username, - data.get_ip(), msg['bot'], bots) + may_login = self.call.may_login(username, + screenname, data.get_ip(), msg['bot'], bots) if may_login != True: # User may not login if may_login == False: @@ -99,20 +134,14 @@ if msg['bot'] == False: # Just an ordinary user - - # Check if the user is already logged in - for id, cl in self.clients.items(): - if cl['status'] == "VP" and \ - cl['bot'] == False and \ - cl['user'] == username: - # Disconnect user - cl['con'].send("disconnect", - {'reason':'duplicate'}) - cl['con'].close('duplicate') - - + + if self._check_double_user(False, username, + data) == False: + return + + # Log the user in - client['user'] = username + client['user'] = screenname client['bot'] = False client['status'] = "VP" @@ -122,5 +151,21 @@ }) else: # Client is bot - pass #TODO + if self._check_double_user(True, screenname, + data) == False: + return + + + # log the bot in + client['user'] = screenname + client['bot'] = True + client['owner'] = username + client['status'] = "VP" + + data.send("login", { + "username": screenname, + "owner": username, + "cid": cid + }) + Modified: trunk/server/core/server.py =================================================================== --- trunk/server/core/server.py 2009-12-19 20:59:37 UTC (rev 328) +++ trunk/server/core/server.py 2009-12-23 13:16:28 UTC (rev 329) @@ -65,6 +65,13 @@ -rsa_bits: How many bits the rsa uses for encrypting the password. More = more secure but slower to generate. Default is 64 + + -bot_names + How the name for a bot should be. Use %s where the + suggested name should be. Default is [%s]. When a bot + logs in as "test", it will be displayed as "[test]". + Choose something that won't interfere with existing + users. """ @@ -72,12 +79,18 @@ self.__sock = None self.__call = callback_class + # This will be available ([sh]ared) to al modules in the core + self.__sh = {} + + self.__sh['call'] = callback_class + self.__sh['core'] = self + # Create all default settings self.__config_default(config) - self.__config = config + self.__sh['config'] = config self.clients = {} - self.__parse = Parser(self.__call, self) + self.__parse = Parser(self.__sh) threading.Thread.__init__(self) @@ -85,10 +98,12 @@ """ Create all the config-defaults """ - config.setdefault('host', '') - config.setdefault('port', 5162) - config.setdefault('max_connections', 0) - config.setdefault('rsa_bits', 64) + config['host'] = str(config.get('host', '')) + config['port'] = int(config.get('port', 5162)) + config['max_connections'] = int( + config.get('max_connections', 0)) + config['rsa_bits'] = int(config.get('rsa_bits', 64)) + config['bot_names'] = str(config.get('bot_names', "[%s]")) def start_server(self): @@ -119,8 +134,8 @@ #Try to claim the pre-specified port while 1: try: - self.__sock.bind((self.__config['host'], - self.__config['port'])) + self.__sock.bind((self.__sh['config']['host'], + self.__sh['config']['port'])) break except: time.sleep(1) @@ -135,14 +150,14 @@ # Server has gone offline return - if self.__config['max_connections'] and \ + if self.__sh['config']['max_connections'] and \ len(self.clients) >= \ - self.__config['max_connections']: + self.__sh['config']['max_connections']: #We exceeded our connection limit, but maybe #this is a special occasion? Send callback! if not self.__call.connection_limit_exceeded( len(self.clients), - self.__config['max_connections'], + self.__sh['config']['max_connections'], addr[0], addr[1]): #We're full, kick him out @@ -164,9 +179,8 @@ self.clients[cid] = { "status": "", - "con": Client(cid, sock, self.clients, - self.__call, self.__parse, - self.__config) + "con": Client(cid, sock, self.__sh, + self.__parse) } if self.__call.connection_opened(cid, @@ -202,14 +216,14 @@ Each client has it's own class. """ - def __init__(self, cid, sock, clients, callback, parser, config): + def __init__(self, cid, sock, sh, parser): self.__cid = cid self.__sock = sock - self.__clients = clients - self.__call = callback + self.__sh = sh self.__parser = parser - self.__config = config + self.__clients = sh['core'].clients + self.__call = sh['call'] self.is_online = True threading.Thread.__init__(self) @@ -247,7 +261,7 @@ # Before the client can log in, we first need to create a # rsa-key public, private = rsa.gen_pubpriv_keys( - self.__config['rsa_bits']) + self.__sh['config']['rsa_bits']) self.__clients[self.__cid]['rsa'] = private self.send("rsa", {"public": public}) This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <Blu...@us...> - 2010-01-03 16:04:18
|
Revision: 331 http://virtplayground.svn.sourceforge.net/virtplayground/?rev=331&view=rev Author: BlueWolf_ Date: 2010-01-03 16:04:11 +0000 (Sun, 03 Jan 2010) Log Message: ----------- [Server-core] It will now save the client app/version, protocolversion and client position. On login, it also will send a list of all connected clients with position (if allowed). When going offline, it will notify all other clients too Modified Paths: -------------- trunk/server/core/callback.py trunk/server/core/parser.py trunk/server/core/server.py Modified: trunk/server/core/callback.py =================================================================== --- trunk/server/core/callback.py 2009-12-23 13:17:00 UTC (rev 330) +++ trunk/server/core/callback.py 2010-01-03 16:04:11 UTC (rev 331) @@ -202,3 +202,11 @@ overwrite this in your own callback. """ return True + + def enters_vp(self, client): + """ + Called when a client is logged in and enters VP. + + client: + Same as server.clients[cid] + """ Modified: trunk/server/core/parser.py =================================================================== --- trunk/server/core/parser.py 2009-12-23 13:17:00 UTC (rev 330) +++ trunk/server/core/parser.py 2010-01-03 16:04:11 UTC (rev 331) @@ -67,7 +67,32 @@ return True + def _is_client_visible(self, client1, client2): + """ + Checks is client1 can see client2 + """ + + client1 = client1['pos'] + client2 = client2['pos'] + + # Check height + if client1[2] != client2[2]: return False + + # Check horizontal + if client1[0]-client2[0] > self.sh['config']['viewport'][0]/2 \ + or client1[0]-client2[0] < -self.sh['config']['viewport'][0]/2: + return False + + # Check vertical + if client1[1]-client2[1] > self.sh['config']['viewport'][1]/2 \ + or client1[1]-client2[1] < -self.sh['config']['viewport'][1]/2: + return False + + # Clients see each other! + return True + + def login(self, cid, msg): """ Send when the users wants to log in @@ -77,6 +102,7 @@ for - For what services it's logging in "VP" - VP client client - list with [app_name, app_version] + version - string with (protocol) version (xx.xx.xx) """ client = self.clients[cid] @@ -108,8 +134,8 @@ bots = [] for id, cl in self.clients.items(): if cl['status'] == 'VP' and \ - cl['owner'] == username and \ - cl['bot'] == True: + cl['bot'] == True and \ + cl['owner'] == username: bots.append(id) # Get screenname @@ -132,40 +158,107 @@ + # Check for double logins if msg['bot'] == False: # Just an ordinary user - if self._check_double_user(False, username, data) == False: return - - - # Log the user in - client['user'] = screenname + + else: # Client is a bot + if self._check_double_user(True, screenname, + data) == False: + return + + + # Log the client in + + client['user'] = screenname + client['app'] = tuple(msg['client']) + client['version'] = str(msg['version']) + client['pos'] = [0, 0, 0] # XYZ + client['status'] = "VP" + + if msg['bot'] == False: client['bot'] = False - client['status'] = "VP" data.send("login", { "username": username, "cid": cid }) - else: # Client is bot - - if self._check_double_user(True, screenname, - data) == False: - return - - - # log the bot in - client['user'] = screenname + else: client['bot'] = True client['owner'] = username - client['status'] = "VP" data.send("login", { "username": screenname, "owner": username, "cid": cid }) + + + # TODO: Send world + + + + user = {} # This will be send to others + user['cid'] = cid + user['user'] = client['user'] + user['app'] = client['app'] + user['version'] = client['version'] + user['bot'] = client['bot'] + if client['bot']: user['owner'] = client['owner'] + + # Send online clients + userlist = [] + + for cid_o, client_o in self.clients.items(): + if client_o['status'] != "VP": continue + # To clear some things up: + # client = local user info + # cid = local user cid + # client_o = other user info + # cid_o = other user cid + # + # user = local user info that will send to + # client_o + # user_o = other user info that will send to + # client + + user_o = {} + + user_o['cid'] = cid_o + user_o['user'] = client_o['user'] + user_o['app'] = client_o['app'] + user_o['version'] = client_o['version'] + + user_o['bot'] = client_o['bot'] + if client_o['bot']: + user_o['owner'] = client_o['owner'] + + # Positions + # Is this user visible for this client? + if self._is_client_visible(client, client_o): + user_o['pos'] = client_o['pos'] + user['pos'] = client['pos'] + else: + user_o['pos'] = None + user['pos'] = None + + + # Send data to other user + if cid != cid_o: + client_o['con'].send("useronline", user) + + # Send data to this user + userlist.append(user_o) + + + # Send list + data.send("userlist", userlist) + + + self.call.enters_vp(client) + Modified: trunk/server/core/server.py =================================================================== --- trunk/server/core/server.py 2009-12-23 13:17:00 UTC (rev 330) +++ trunk/server/core/server.py 2010-01-03 16:04:11 UTC (rev 331) @@ -72,6 +72,12 @@ logs in as "test", it will be displayed as "[test]". Choose something that won't interfere with existing users. + + -viewport + A tuple of the client's viewport in pixels. This is very + important because it's used to calculate which objects + and people the client is able to see. By default, this + is (1000, 700). """ @@ -79,7 +85,7 @@ self.__sock = None self.__call = callback_class - # This will be available ([sh]ared) to al modules in the core + # This will be available ([sh]ared) to all modules in the core self.__sh = {} self.__sh['call'] = callback_class @@ -104,6 +110,7 @@ config.get('max_connections', 0)) config['rsa_bits'] = int(config.get('rsa_bits', 64)) config['bot_names'] = str(config.get('bot_names', "[%s]")) + config['viewport'] = tuple(config.get('viewport', (1000,700))) def start_server(self): @@ -248,7 +255,29 @@ traceback += ev.__class__.__name__ + ': ' + str(ev) self.__call.debug_crash(traceback) + + def __notify_offline(self, reason): + """ + Used by close and close_msg to notify other users this client + has gone offline. + """ + if reason == "gone offline": return # Server is going offline + + # Was this user already online? + client = self.__clients[self.__cid] + if client['status'] == 'VP': + + # Tell other users this one has gone offline + for cid, cl in self.__clients.items(): + if cid == self.__cid: continue + + cl['con'].send("useroffline", { + "cid": self.__cid, + "reason": reason + }) + + def run(self): """ Used by threading, not for external usage. @@ -311,6 +340,8 @@ self.__call.connection_closed(self.__cid, reason) + self.__notify_offline(reason) + del self.__clients[self.__cid] def close_msg(self, reason): @@ -329,6 +360,9 @@ self.__sock.close() self.__call.connection_closed(self.__cid, reason) + + self.__notify_offline(reason) + del self.__clients[self.__cid] This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <ch...@us...> - 2010-01-08 23:02:35
|
Revision: 333 http://virtplayground.svn.sourceforge.net/virtplayground/?rev=333&view=rev Author: chozone Date: 2010-01-08 23:02:27 +0000 (Fri, 08 Jan 2010) Log Message: ----------- [VPS-core] Added functions.py (global functions). Added check_version function, to that file. Modified Paths: -------------- trunk/server/core/parser.py trunk/server/core/server.py Added Paths: ----------- trunk/server/core/functions.py Added: trunk/server/core/functions.py =================================================================== --- trunk/server/core/functions.py (rev 0) +++ trunk/server/core/functions.py 2010-01-08 23:02:27 UTC (rev 333) @@ -0,0 +1,129 @@ +## This file is part of Virtual Playground +## Copyright (c) 2009 Jos Ratsma + Koen Koning + +## 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 2 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, write to the Free Software +## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + +def check_version(version, expr): + """ + Checks whether a version meets certain conditions. + The first argument, `version`, contains the version you want to check. + The second argument, `expr`, contains the condition (expression). + + Examples of some expression are: + + Exact match: + '1.0.3' match ONLY that version (True if version equal given) + + Greater than: + '>0.9.48' match all versions above version '0.9.48', AND '0.9.48' + itself (True if same or above given) + + Lower than: + '<10.4.0' match all versions below version '10.4.0', AND '10.4.0' + itself (True if same or below given) + + Range: + '0.9.3-0.9.11' match all versions between '0.9.3' and '0.9.11', + including those values themselves (True if same or + between two values) + + + If all conditions are met, this function will return True, otherwise + False. This function will raise a ValueError in case that there were + errors in the provided version/expression. + + As an extra note, remember that 1.0.2 is lower than 1.0.10 (10 > 2). In + other words, there is no preceding zero. + This is because it's not a decimal number. + """ + + # Filter out invalid versions/expressions, so this function works + # correctly later on. + if not expr: return True + if not version: return False + version = str(version) + expr = str(expr) + + try: [int(i) for i in version.split('.')] + except ValueError: raise ValueError, "No valid version: '%s'"%version + + try: [[int(i) for i in p.split('.')] \ + for p in expr.strip('<>').split('-')] + except ValueError: raise ValueError, "No valid expression: '%s'"%expr + + + # Our first real tests. If version matches expression, we return True. + # Also, if the expression, stripped of '<' and '>' characters at the + # beginning (or end...) matched our version, we return True. + # This is for the 'Greater/Lower OR EQUAL than' behaviour of those + # characters. + if version == expr: return True + if version == expr.strip('<>'): return True + + if expr[0] == '>': + # Greater than + + # Get rid of the signs, so we have normal version to work with. + expr = expr.strip('<>') + + # Save the three int that make up the version into an array, so + # we can access them easily to do our check, a few lines below. + c1 = [int(i) for i in version.split('.')] + c2 = [int(i) for i in expr.split('.')] + + # Loops through major, minor, revision, in that order, and + # it tests whether the version is higher/lower. Note that if the + # version are the same, it has already been filtered out, before + # this if block. + for p1, p2 in zip(c1, c2): + if p1 > p2: return True + if p1 < p2: return False + elif expr[0] == '<': + # Less than + + # Mostly same as above + + # Get rid of the signs, so we have normal version to work with. + expr = expr.strip('<>') + + # Save the three int that make up the version into an array, so + # we can access them easily to do our check, a few lines below. + c1 = [int(i) for i in version.split('.')] + c2 = [int(i) for i in expr.split('.')] + + # Loops through major, minor, revision, in that order, and + # it tests whether the version is higher/lower. Note that if the + # version are the same, it has already been filtered out, before + # this if block. + for p1, p2 in zip(c1, c2): + if p1 < p2: return True + if p1 > p2: return False + elif expr.find('-') > -1: + # Range + + # Get the two versions + r1, r2 = expr.split('-') + + # Use this function to see if it's higher than the first, and + # lower than the second value :-) + if _check_version(version, '>' + r1) \ + and _check_version(version, '<' + r2): + return True + + # Better luck next time :-( + return False + + Modified: trunk/server/core/parser.py =================================================================== --- trunk/server/core/parser.py 2010-01-03 16:04:22 UTC (rev 332) +++ trunk/server/core/parser.py 2010-01-08 23:02:27 UTC (rev 333) @@ -15,8 +15,10 @@ ## along with this program; if not, write to the Free Software ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +from functions import * import rsa, hashlib + class Parser(): def __init__(self, sh): """ Modified: trunk/server/core/server.py =================================================================== --- trunk/server/core/server.py 2010-01-03 16:04:22 UTC (rev 332) +++ trunk/server/core/server.py 2010-01-08 23:02:27 UTC (rev 333) @@ -16,9 +16,12 @@ ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. import simplejson, socket, threading, time, random, sys, hashlib + +from functions import * from parser import Parser import rsa + class Server(threading.Thread): """ This is the server-core for Virtual Playground. This will handle all This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <Blu...@us...> - 2010-04-21 15:18:44
|
Revision: 336 http://virtplayground.svn.sourceforge.net/virtplayground/?rev=336&view=rev Author: BlueWolf_ Date: 2010-04-21 15:18:38 +0000 (Wed, 21 Apr 2010) Log Message: ----------- Added callback.leaves_vp. callback.enters_vp and callback.leaves_vp both have a cid as argument now. Modified Paths: -------------- trunk/server/core/callback.py trunk/server/core/parser.py trunk/server/core/server.py Modified: trunk/server/core/callback.py =================================================================== --- trunk/server/core/callback.py 2010-04-19 19:44:41 UTC (rev 335) +++ trunk/server/core/callback.py 2010-04-21 15:18:38 UTC (rev 336) @@ -203,10 +203,18 @@ """ return True - def enters_vp(self, client): + def enters_vp(self, cid): """ Called when a client is logged in and enters VP. - client: - Same as server.clients[cid] + cid: + The unique ID for this connection. (connection-id) """ + + def leaves_vp(self, cid): + """ + Called when a logged-in client has gone offline. + + cid: + The unique ID for this connection. (connection-id) + """ Modified: trunk/server/core/parser.py =================================================================== --- trunk/server/core/parser.py 2010-04-19 19:44:41 UTC (rev 335) +++ trunk/server/core/parser.py 2010-04-21 15:18:38 UTC (rev 336) @@ -71,7 +71,7 @@ def _is_client_visible(self, client1, client2): """ - Checks is client1 can see client2 + Checks if client1 can see client2 """ client1 = client1['pos'] @@ -100,7 +100,7 @@ Send when the users wants to log in usr - The username pwd - The (rsa encrypted) sha-password - bot - Whether the it's an bot or an user + bot - Whether it's an bot or an user for - For what services it's logging in "VP" - VP client client - list with [app_name, app_version] @@ -262,5 +262,5 @@ data.send("userlist", userlist) - self.call.enters_vp(client) + self.call.enters_vp(cid) Modified: trunk/server/core/server.py =================================================================== --- trunk/server/core/server.py 2010-04-19 19:44:41 UTC (rev 335) +++ trunk/server/core/server.py 2010-04-21 15:18:38 UTC (rev 336) @@ -270,16 +270,16 @@ # Was this user already online? client = self.__clients[self.__cid] if client['status'] == 'VP': - + # Tell other users this one has gone offline for cid, cl in self.__clients.items(): if cid == self.__cid: continue + if cl['status'] == 'VP': + cl['con'].send("useroffline", { + "cid": self.__cid, + "reason": reason + }) - cl['con'].send("useroffline", { - "cid": self.__cid, - "reason": reason - }) - def run(self): """ @@ -341,6 +341,11 @@ except: pass self.__sock.close() + # Was this user already online? + client = self.__clients[self.__cid] + if client['status'] == 'VP': + self.__call.leaves_vp(self.__cid) + self.__call.connection_closed(self.__cid, reason) self.__notify_offline(reason) This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <Blu...@us...> - 2010-07-27 19:28:01
|
Revision: 339 http://virtplayground.svn.sourceforge.net/virtplayground/?rev=339&view=rev Author: BlueWolf_ Date: 2010-07-27 19:27:54 +0000 (Tue, 27 Jul 2010) Log Message: ----------- Added a max speed option in the config. Does -of course- nothing right now. It's just there for the decoration :D Modified Paths: -------------- trunk/server/core/functions.py trunk/server/core/server.py Modified: trunk/server/core/functions.py =================================================================== --- trunk/server/core/functions.py 2010-04-24 22:05:37 UTC (rev 338) +++ trunk/server/core/functions.py 2010-07-27 19:27:54 UTC (rev 339) @@ -15,7 +15,6 @@ ## along with this program; if not, write to the Free Software ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - def check_version(version, expr): """ Checks whether a version meets certain conditions. Modified: trunk/server/core/server.py =================================================================== --- trunk/server/core/server.py 2010-04-24 22:05:37 UTC (rev 338) +++ trunk/server/core/server.py 2010-07-27 19:27:54 UTC (rev 339) @@ -69,18 +69,24 @@ How many bits the rsa uses for encrypting the password. More = more secure but slower to generate. Default is 64 - -bot_names + -bot_names: How the name for a bot should be. Use %s where the suggested name should be. Default is [%s]. When a bot logs in as "test", it will be displayed as "[test]". Choose something that won't interfere with existing users. - -viewport + -viewport: A tuple of the client's viewport in pixels. This is very important because it's used to calculate which objects - and people the client is able to see. By default, this + and people the client is able to see. By default this is (1000, 700). + + -max_speed: + The amount of pixels a user may move in a certain time. + The default is (50, 1.0) which basically means 50px per + 1 second. When a user exceeds this, the server will just + reset his position to his last known location. """ @@ -115,6 +121,8 @@ config['bot_names'] = str(config.get('bot_names', "[%s]")) config['viewport'] = tuple(config.get('viewport', (1000,700))) + config['max_speed'] = tuple(config.get('max_speed', (50, 1.0))) + def start_server(self): """ This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <Blu...@us...> - 2010-09-15 19:04:39
|
Revision: 408 http://virtplayground.svn.sourceforge.net/virtplayground/?rev=408&view=rev Author: BlueWolf_ Date: 2010-09-15 19:04:32 +0000 (Wed, 15 Sep 2010) Log Message: ----------- error.message is decrepated. Changing it to str(error) Modified Paths: -------------- trunk/server/core/parser.py trunk/server/core/server.py Modified: trunk/server/core/parser.py =================================================================== --- trunk/server/core/parser.py 2010-09-15 18:41:24 UTC (rev 407) +++ trunk/server/core/parser.py 2010-09-15 19:04:32 UTC (rev 408) @@ -116,7 +116,7 @@ pwd = rsa.decrypt(msg['pwd'], client['rsa']) except Exception, error: self.call.error("rsa", error.__class__.__name__ + " - " + \ - error.message) + str(error)) return True @@ -271,7 +271,7 @@ pwd = rsa.decrypt(msg['pwd'], client['rsa']) except Exception, error: self.call.error("rsa", error.__class__.__name__ + " - " + \ - error.message) + str(error)) return True # Double sha, so no one can insert "raw" sha Modified: trunk/server/core/server.py =================================================================== --- trunk/server/core/server.py 2010-09-15 18:41:24 UTC (rev 407) +++ trunk/server/core/server.py 2010-09-15 19:04:32 UTC (rev 408) @@ -330,7 +330,7 @@ parsed = simplejson.loads(msg) except Exception, error: self.__call.error("json", error.__class__.__name__ \ - + " - " + error.message + " - " + repr(msg)) + + " - " + str(error) + " - " + repr(msg)) self.close_msg("crash") return This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <Blu...@us...> - 2010-09-28 22:00:34
|
Revision: 414 http://virtplayground.svn.sourceforge.net/virtplayground/?rev=414&view=rev Author: BlueWolf_ Date: 2010-09-28 22:00:28 +0000 (Tue, 28 Sep 2010) Log Message: ----------- Added a tick-module for the core. It will check for the user's position but does not send them to other clients yet Modified Paths: -------------- trunk/server/core/functions.py trunk/server/core/parser.py trunk/server/core/server.py Added Paths: ----------- trunk/server/core/tick.py Modified: trunk/server/core/functions.py =================================================================== --- trunk/server/core/functions.py 2010-09-28 21:59:12 UTC (rev 413) +++ trunk/server/core/functions.py 2010-09-28 22:00:28 UTC (rev 414) @@ -15,6 +15,8 @@ ## along with this program; if not, write to the Free Software ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +import threading, time, random, sys + def check_version(version, expr): """ Checks whether a version meets certain conditions. @@ -123,3 +125,141 @@ return False +def check_double_user(clients, isbot, screenname, data): + """ + Checks for an user or bot with the same name + """ + + # Check if there isn't already a bot online with this name + for i, client in clients.items(): + if client['status'] == "VP" and client['bot'] == True and \ + client['user'] == screenname: + # Our client cannot log in + return "duplicate user" + + if isbot == False: + # Check for the same user + for i, client in clients.items(): + if client['status'] == "VP" and client['bot'] == False and \ + client['user'] == screenname: + # Disconnect this user + client['con'].close_msg('duplicate') + + return True + +def get_visible_clients(viewport, client, clients): + """ + Get a list of all visible clients for this user + """ + + visible = [] + + margin = client['speed']*2 + view = ( + client['pos'][0] - (viewport[0]/2) - margin, + client['pos'][1] - (viewport[1]/2) - margin, + viewport[0] + margin, + viewport[1] + margin + ) + + for other in clients.values(): + if other['status'] != "VP": continue + if client['pos'][2] != other['pos'][2]: continue + + print client['user'], "->", other['user'] + + # Create his viewport + margin_other = other['speed']*2 + view_other = ( + other['pos'][0] - (viewport[0]/2) - margin, + other['pos'][1] - (viewport[1]/2) - margin, + viewport[0] + margin, + viewport[1] + margin + ) + + print view, view_other + # Is this without our viewport? + if (view[0]+view[2] >= view_other[0] and view[0] <= view_other[0]+view_other[2]) and \ + (view[1]+view[3] >= view_other[1] and view[1] <= view_other[1]+view_other[3]): + visible.append(other) + + + return visible + +def get_current_position(client): + """ + Returns the current position for this user + """ + + if client['moving']: + starttime, direction, speed = client['moving'] + pos = client['pos'][:] + timeline = time.time() - starttime + move = timeline*speed + + if direction == 0: + pos[1] = int(pos[1] - move) + elif direction == 1: + pos[1] = int(pos[1] - (move/1.7)) + pos[0] = int(pos[0] + (move/1.7)) + elif direction == 2: + pos[0] = int(pos[0] + move) + elif direction == 3: + pos[0] = int(pos[0] + (move/1.7)) + pos[1] = int(pos[1] + (move/1.7)) + elif direction == 4: + pos[1] = int(pos[1] + move) + elif direction == 5: + pos[1] = int(pos[1] + (move/1.7)) + pos[0] = int(pos[0] - (move/1.7)) + elif direction == 6: + pos[0] = int(pos[0] - move) + elif direction == 7: + pos[0] = int(pos[0] - (move/1.7)) + pos[1] = int(pos[1] - (move/1.7)) + + return pos + else: + return client['pos'] + +def position_check(sh, client, proposed_pos = None): + """ + Check the current position of the user for collisions or inaccurate data + """ + + # Check if newpos is valid + if client['moving'] == False: + # Just check if it's the same pos + if client['pos'] != proposed_pos: + client['con'].send("move", { + "cid": client['cid'], + "pos": client['pos'] + }) + + else: + current_pos = get_current_position(client) + + if proposed_pos != None: + # Compare current_pos with proposed_pos how much they differ + wrong = False + for p1, p2 in zip(current_pos[:2], proposed_pos[:2]): + diff = p2-p1 + if diff < 0: diff = -diff + if diff > sh['config']['position_error_margin']: + wrong = True + + if wrong: # Send correction + client['con'].send("move", { + "cid": client['cid'], + "pos": current_pos, + }) + else: + current_pos = proposed_pos + + + # Check for collisions between lastpos and current_pos + # [TODO] + print "Check", client['lastpos'], "->", current_pos + + client['lastpos'] = current_pos + Modified: trunk/server/core/parser.py =================================================================== --- trunk/server/core/parser.py 2010-09-28 21:59:12 UTC (rev 413) +++ trunk/server/core/parser.py 2010-09-28 22:00:28 UTC (rev 414) @@ -28,7 +28,7 @@ self.call = sh['call'] self.core = sh['core'] - # Shortkeys + # Shortcut self.clients = self.core.clients def __call__(self, cid, msg): @@ -41,57 +41,6 @@ if (func): return func(cid, body) - def _check_double_user(self, isbot, screenname, data): - """ - Checks for an user or bot with the same name - """ - - # Check if there isn't already a bot online with this name - for id, cl in self.clients.items(): - if cl['status'] == "VP" and cl['bot'] == True and \ - cl['user'] == screenname: - # Our client cannot log in - data.send("error", { - "reason": "duplicate user" - }) - return False - - if isbot == False: - # Check for the same user - for id, cl in self.clients.items(): - if cl['status'] == "VP" and cl['bot'] == False and \ - cl['user'] == screenname: - # Disconnect this user - cl['con'].close_msg('duplicate') - - return True - - def _is_client_visible(self, client1, client2): - """ - Checks if client1 can see client2 - """ - - client1 = client1['pos'] - client2 = client2['pos'] - - # Check height - if client1[2] != client2[2]: return False - - # Check horizontal - if client1[0]-client2[0] > self.sh['config']['viewport'][0]/2 \ - or client1[0]-client2[0] < -self.sh['config']['viewport'][0]/2: - return False - - # Check vertical - if client1[1]-client2[1] > self.sh['config']['viewport'][1]/2 \ - or client1[1]-client2[1] < -self.sh['config']['viewport'][1]/2: - return False - - # Clients see each other! - return True - - - def login(self, cid, msg): """ Send when the users wants to log in @@ -164,12 +113,18 @@ # Check for double logins if msg['bot'] == False: # Just an ordinary user - if self._check_double_user(False, username, data) == False: - return + resp = check_double_user(self.clients, False, username, data) + if resp != True: + data.send("error", { + "reason": resp + }) else: # Client is a bot - if self._check_double_user(True, screenname, data) == False: - return + resp = check_double_user(self.clients, True, screenname, data) + if resp != True: + data.send("error", { + "reason": resp + }) # Log the client in @@ -177,8 +132,14 @@ client['user'] = screenname client['app'] = tuple(msg['client']) client['version'] = str(msg['version']) - client['pos'] = [0, 0, 0] # XYZ + client['pos'] = [ + int(random.random()*500-250), # X + int(random.random()*500-250), # Y + 0] # Z + client['lastpos'] = client['pos'][:] # Last pos that is checked client['status'] = "VP" + client['speed'] = self.sh['config']['default_speed'] + client['moving'] = False if msg['bot'] == False: client['bot'] = False @@ -202,7 +163,6 @@ # TODO: Send world - user = {} # This will be send to others user['cid'] = cid user['user'] = client['user'] @@ -214,6 +174,9 @@ # Send online clients userlist = [] + visible = get_visible_clients(self.sh['config']['viewport'], client, + self.clients) + for cid_o, client_o in self.clients.items(): if client_o['status'] != "VP": continue @@ -239,7 +202,7 @@ # Positions # Is this user visible for this client? - if self._is_client_visible(client, client_o): + if client_o in visible: user_o['pos'] = client_o['pos'] user['pos'] = client['pos'] else: @@ -298,4 +261,28 @@ """ self.call.custom(cid, msg['header'], msg['body']) + + def move(self, cid, msg): + """ + An user is moving to a different position + * moving: + Boolean, if the user is walking or not + * pos: + Current position of the user + * direction + Current direction of this user (None when moving = False) + """ + + client = self.clients[cid] + + if msg['moving']: + position_check(self.sh, client, msg['pos']) + client['pos'] = msg['pos'] + client['moving'] = (time.time(), msg['direction'], client['speed']) + self.sh['tick'].start("moving") + else: + position_check(self.sh, client, msg['pos']) + client['pos'] = msg['pos'] + client['moving'] = False + Modified: trunk/server/core/server.py =================================================================== --- trunk/server/core/server.py 2010-09-28 21:59:12 UTC (rev 413) +++ trunk/server/core/server.py 2010-09-28 22:00:28 UTC (rev 414) @@ -15,10 +15,11 @@ ## along with this program; if not, write to the Free Software ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -import simplejson, socket, threading, time, random, sys, hashlib, select +import simplejson, socket, hashlib, select from functions import * from parser import Parser +from tick import Tick import rsa @@ -78,11 +79,16 @@ because it's used to calculate which objects and people the client is able to see. By default this is (1000, 700). - -max_speed: - The amount of pixels a user may move in a certain time. - The default is (50, 1.0) which basically means 50px per 1 second. - When a user exceeds this, the server will just reset his position - to his last known location. + -default_speed: + How fast an user may walk by default. By default this is 175. You + can change this on the fly with client.change_speed (for example, + when the user starts running or uses a vehicle). The viewport for + this user will be changed based on the speed. Margin on all sides + will be speed*2 + + -position_error_margin + How much pixels is tolerated by the server before it starts + correcting user's positions. Default is 25 """ @@ -103,6 +109,8 @@ self.clients = {} self.__parse = Parser(self.__sh) + self.__sh['tick'] = Tick(self.__sh) + threading.Thread.__init__(self) def __config_default(self, config): @@ -115,7 +123,9 @@ config['rsa_bits'] = int(config.get('rsa_bits', 64)) config['bot_names'] = str(config.get('bot_names', "[%s]")) config['viewport'] = tuple(config.get('viewport', (1000,700))) - config['max_speed'] = tuple(config.get('max_speed', (50, 1.0))) + config['default_speed'] = int(config.get('max_speed', 175)) + config['position_error_margin'] = int(config.get( + "position_error_margin", 25)) def start_server(self): @@ -194,6 +204,7 @@ str(random.random())).hexdigest()[:8] self.clients[cid] = { + "cid": cid, "status": "", "con": Client(cid, sock, self.__sh, self.__parse) } Added: trunk/server/core/tick.py =================================================================== --- trunk/server/core/tick.py (rev 0) +++ trunk/server/core/tick.py 2010-09-28 22:00:28 UTC (rev 414) @@ -0,0 +1,83 @@ +#!/usr/bin/env python + +## This file is part of Virtual Playground +## Copyright (c) 2010 Jos Ratsma + Koen Koning + +## 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 2 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, write to the Free Software +## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +from functions import * + +TICK_SPEED = 1 + +class Tick(): + """ + This will tick every second when needed + """ + def __init__(self, sh): + self.sh = sh + self.call = sh['call'] + self.core = sh['core'] + + # Shortcut + self.clients = self.core.clients + + self.callers = [] + self.timer = None + + def start(self, name, *extra): + # Start a timer for this call + + if name in self.callers: return + self.callers.append(name) + + if self.timer == None: # Start a timer + self.timer = threading.Timer(TICK_SPEED, self.update) + self.timer.start() + + def stop(self, name): + try: self.callers.remove(name) + except: pass + + if self.callers == [] and self.timer: + self.timer.cancel() + self.timer = None + + def update(self): + # Execute stuff + for name in self.callers: + if self.tick(name): + try: self.callers.remove(name) + except: pass # This can happen due to threading. It's not bad + + # Start the next timer + if self.callers != []: + self.timer = threading.Timer(TICK_SPEED, self.update) + self.timer.start() + else: + self.timer = None + + def tick(self, name): + if name == "moving": + # Find which users are moving + amount = 0 + for client in self.clients.values(): + if client['status'] != "VP" or client['moving'] == False: + continue + + amount += 1 + position_check(self.sh, client) + + if amount == 0: + return True This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <Blu...@us...> - 2010-09-30 10:18:48
|
Revision: 415 http://virtplayground.svn.sourceforge.net/virtplayground/?rev=415&view=rev Author: BlueWolf_ Date: 2010-09-30 10:18:42 +0000 (Thu, 30 Sep 2010) Log Message: ----------- Server can now check which user can see an other user and will pipe the move-data to those clients Modified Paths: -------------- trunk/server/core/functions.py trunk/server/core/parser.py trunk/server/core/tick.py Modified: trunk/server/core/functions.py =================================================================== --- trunk/server/core/functions.py 2010-09-28 22:00:28 UTC (rev 414) +++ trunk/server/core/functions.py 2010-09-30 10:18:42 UTC (rev 415) @@ -147,45 +147,44 @@ return True -def get_visible_clients(viewport, client, clients): +def get_visible_users(viewport, client, clients, pos = None): """ - Get a list of all visible clients for this user + Get a list of all visible users for this user """ visible = [] + if pos == None: pos = get_current_position(client) - margin = client['speed']*2 + margin = client['speed'] view = ( - client['pos'][0] - (viewport[0]/2) - margin, - client['pos'][1] - (viewport[1]/2) - margin, - viewport[0] + margin, - viewport[1] + margin + pos[0] - (viewport[0]/2) - margin, + pos[1] - (viewport[1]/2) - margin, + viewport[0] + (margin*2), + viewport[1] + (margin*2) ) for other in clients.values(): if other['status'] != "VP": continue if client['pos'][2] != other['pos'][2]: continue - print client['user'], "->", other['user'] + pos_other = get_current_position(other) # Create his viewport - margin_other = other['speed']*2 + margin_other = other['speed'] view_other = ( - other['pos'][0] - (viewport[0]/2) - margin, - other['pos'][1] - (viewport[1]/2) - margin, - viewport[0] + margin, - viewport[1] + margin + pos_other[0] - margin, + pos_other[1] - margin, + margin*2, + margin*2 ) - - print view, view_other # Is this without our viewport? if (view[0]+view[2] >= view_other[0] and view[0] <= view_other[0]+view_other[2]) and \ (view[1]+view[3] >= view_other[1] and view[1] <= view_other[1]+view_other[3]): visible.append(other) - return visible + def get_current_position(client): """ Returns the current position for this user @@ -228,15 +227,8 @@ """ # Check if newpos is valid - if client['moving'] == False: - # Just check if it's the same pos - if client['pos'] != proposed_pos: - client['con'].send("move", { - "cid": client['cid'], - "pos": client['pos'] - }) - - else: + if client['moving'] != False: + # Client is moving. CHeck is proposed_pos is near our calculated pos current_pos = get_current_position(client) if proposed_pos != None: @@ -255,11 +247,47 @@ }) else: current_pos = proposed_pos + + # [TODO] Check for collisions between lastpos and current_pos + + else: + # Just check if it's the same pos + if proposed_pos != None and client['pos'] != proposed_pos: + client['con'].send("move", { + "cid": client['cid'], + "pos": client['pos'] + }) + current_pos = client['pos'] + + # Check which other clients see this user + new_visible = get_visible_users(sh['config']['viewport'], client, + sh['core'].clients, current_pos) + + # Find those who left his/her viewport + for other in [cl for cl in client['visible']['users'] \ + if cl not in new_visible]: + client['con'].send("move", { + "cid": other['cid'], + "pos": None, + }) + + # Send all the new users who entered his/her viewport + for other in [cl for cl in new_visible \ + if cl not in client['visible']['users']]: + data = { + "cid": other['cid'], + "pos": get_current_position(other), + "moving": False + } + if other['moving']: + data['moving'] = True + data['direction'] = other['moving'][1] + data['speed'] = other['moving'][2] + + client['con'].send("move", data) + + client['visible']['users'] = new_visible + + client['lastpos'] = current_pos - # Check for collisions between lastpos and current_pos - # [TODO] - print "Check", client['lastpos'], "->", current_pos - - client['lastpos'] = current_pos - Modified: trunk/server/core/parser.py =================================================================== --- trunk/server/core/parser.py 2010-09-28 22:00:28 UTC (rev 414) +++ trunk/server/core/parser.py 2010-09-30 10:18:42 UTC (rev 415) @@ -132,14 +132,12 @@ client['user'] = screenname client['app'] = tuple(msg['client']) client['version'] = str(msg['version']) - client['pos'] = [ - int(random.random()*500-250), # X - int(random.random()*500-250), # Y - 0] # Z + client['pos'] = [0, 0, 0] # X, Y, Z client['lastpos'] = client['pos'][:] # Last pos that is checked client['status'] = "VP" client['speed'] = self.sh['config']['default_speed'] client['moving'] = False + client['visible'] = {'users': [], 'objects': []} if msg['bot'] == False: client['bot'] = False @@ -174,8 +172,9 @@ # Send online clients userlist = [] - visible = get_visible_clients(self.sh['config']['viewport'], client, + visible = get_visible_users(self.sh['config']['viewport'], client, self.clients) + client['visible']['users'] = visible for cid_o, client_o in self.clients.items(): if client_o['status'] != "VP": continue @@ -204,12 +203,17 @@ # Is this user visible for this client? if client_o in visible: user_o['pos'] = client_o['pos'] + else: + user_o['pos'] = None + + # Is this client visible to the other user? + if client in get_visible_users(self.sh['config']['viewport'], + client_o, self.clients): + client_o['visible']['users'].append(client) user['pos'] = client['pos'] else: - user_o['pos'] = None user['pos'] = None - # Send data to other user if cid != cid_o: client_o['con'].send("useronline", user) @@ -264,7 +268,7 @@ def move(self, cid, msg): """ - An user is moving to a different position + A user is moving to a different position * moving: Boolean, if the user is walking or not @@ -285,4 +289,44 @@ position_check(self.sh, client, msg['pos']) client['pos'] = msg['pos'] client['moving'] = False + + # Check for the user who could see this client, but can't anymore + for other in self.clients.values(): + if other == client or other['status'] != "VP" or \ + other['pos'][2] != client['pos'][2]: + continue + if client not in other['visible']['users']: continue + + if client not in get_visible_users( + self.sh['config']['viewport'], other, self.clients): + other['visible']['users'].remove(client) + other['con'].send("move", { + "cid": client['cid'], + "pos": None + }) + + # Send to other clients + for other in self.clients.values(): + if other == client or other['status'] != "VP" or \ + other['pos'][2] != client['pos'][2]: + continue + + if client in get_visible_users(self.sh['config']['viewport'], other, + self.clients): + + if client not in other['visible']['users']: + other['visible']['users'].append(client) + + data = { + "cid": client['cid'], + "pos": client['pos'], + "moving": False, + } + if client['moving']: + data['moving'] = True + data['direction'] = client['moving'][1] + data['speed'] = client['moving'][2] + + other['con'].send("move", data) + Modified: trunk/server/core/tick.py =================================================================== --- trunk/server/core/tick.py 2010-09-28 22:00:28 UTC (rev 414) +++ trunk/server/core/tick.py 2010-09-30 10:18:42 UTC (rev 415) @@ -79,5 +79,12 @@ amount += 1 position_check(self.sh, client) - if amount == 0: - return True + if amount == 0: return True + + # Go through all non-moving users to check if someone entered + # their viewport + for client in self.clients.values(): + if client['status'] != "VP" or client['moving'] != False: + continue + + position_check(self.sh, client) This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |