Index: conf/poker.server.xml.in
===================================================================
--- conf/poker.server.xml.in	(révision 6419)
+++ conf/poker.server.xml.in	(copie de travail)
@@ -8,6 +8,9 @@
  -->
 <server xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="server.xsd" ping="20" autodeal="yes" max_joined="4000" max_queued_client_packets="500" cleanup="yes" simultaneous="4" max_missed_round="5" verbose="0" chat="yes" memcached="127.0.0.1:11211" session_timeout="60" session_check="10" cookie_timeout="1200" sng_timeout="3600" remove_completed="1" admin="false" poker_network_version="1.7.7">
 
+    <!-- remaining buy-in -->
+  <remaining-buy-in activate="yes" period="1800" />
+  
 <!-- max_queued_client_packets defaults to 500 if you leave it out.
      max_missed_round defaults to 10 if you leave it out.
      max_joined defaults to 4000 if you leave it out.  -->
Index: pokernetwork/pokerpackets.py
===================================================================
--- pokernetwork/pokerpackets.py	(révision 6419)
+++ pokernetwork/pokerpackets.py	(copie de travail)
@@ -1732,6 +1732,7 @@
 
     format = "!I"
     format_size = calcsize(format)
+    TIME_BEFORE_YOU_CAN_REBUY_CODE = "1"
 
     def __init__(self, *args, **kwargs):
         self.amount = kwargs.get("amount",0)
@@ -1849,6 +1850,7 @@
    info = PacketPokerId.info + ( ('name', 'noname', 's'),
                                  ('outfit', 'random', 's'),
                                  ('url', 'random', 's'),
+                                  ('tables_max', -1, 'I'),
                                  # FIXME_PokerPlayerInfoLocale: 
                                  # (see also sr #2262 )
                                  # should "locale" be here?  It's
@@ -1863,20 +1865,22 @@
        self.name = kwargs.get('name', "noname")
        self.url = kwargs.get('url', "random")
        self.outfit = kwargs.get('outfit',"random")
+       self.tables_max = kwargs.get('tables_max',-1)
        PacketPokerId.__init__(self, *args, **kwargs)
 
    def pack(self):
-       return PacketPokerId.pack(self) + self.packstring(self.name) + self.packstring(self.outfit) + self.packstring(self.url)
+       return PacketPokerId.pack(self) + self.packstring(self.name) + self.packstring(self.outfit) + self.packstring(self.url) + pack(PacketPokerPlayerInfo.format, self.tables_max)
 
    def unpack(self, block):
        block = PacketPokerId.unpack(self, block)
        (block, self.name) = self.unpackstring(block)
        (block, self.outfit) = self.unpackstring(block)
        (block, self.url) = self.unpackstring(block)
-       return block
+       (self.tables_max,) = unpack(PacketPokerPlayerInfo.format, block[:PacketPokerPlayerInfo.format_size]) 
+       return block[PacketPokerPlayerInfo.format_size:]
 
    def calcsize(self):
-       return PacketPokerId.calcsize(self) + self.calcsizestring(self.name) + self.calcsizestring(self.outfit) + self.calcsizestring(self.url)
+       return PacketPokerId.calcsize(self) + self.calcsizestring(self.name) + self.calcsizestring(self.outfit) + self.calcsizestring(self.url) + PacketPokerPlayerInfo.format_size
 
    def __str__(self):
        return PacketPokerId.__str__(self) + " name = %s, url = %s, outfit = %s " % ( self.name , self.url, self.outfit )
@@ -4600,6 +4604,32 @@
 
 _TYPES = range(50,169)
 
+class PacketPokerRemainingBuyIn(Packet):
+    """\
+
+Direction: server => client
+
+Configuration: <remaining-buy-in activate="yes" period="1800" /> 
+
+Context: fair-play feature - you can buyin in only if the amount is equal or more than your carpet before leaving the table 
+
+game_id: integer uniquely identifying a game.
+period : period before expiry in secondes
+min : minimum buyin permitted before expiry
+is_buy_in_max : True if the amount of the carpet in the last game exceeds max buy (then min = max_buy_in)
+
+"""
+    info = Packet.info + (
+        ('game_id', 0, 'I'),
+        ('period', 0, 'I'),
+        ('min', 0, 'I'),
+        ('is_buy_in_max', 0, 'bool'),
+        )
+
+Packet.infoDeclare(globals(), PacketPokerRemainingBuyIn, Packet, "POKER_REMAINING_BUY_IN", 167) 
+
+########################################
+
 # Interpreted by emacs
 # Local Variables:
 # compile-command: "perl -pi -e 'if(/%SEQ%/) { $s = 49 if(!defined($s)); $s++; $h = sprintf(q/0x%x/, $s); s/\\d+[ \\w#]+#/$s # $h #/; }' pokerpackets.py"
Index: pokernetwork/pokerservice.py
===================================================================
--- pokernetwork/pokerservice.py	(révision 6419)
+++ pokernetwork/pokerservice.py	(copie de travail)
@@ -84,6 +84,7 @@
 UPDATE_TOURNEYS_SCHEDULE_DELAY = 10 * 60
 CHECK_TOURNEYS_SCHEDULE_DELAY = 60
 DELETE_OLD_TOURNEYS_DELAY = 1 * 60 * 60
+CLEANUP_SERIAL2LEAVETIME_DELAY = 1 * 60 * 60
 
 class IPokerService(Interface):
 
@@ -277,6 +278,7 @@
             self.createTable(0, description)
         self.cleanupTourneys()
         self.updateTourneysSchedule()
+        self.cleanupSerial2leavetimeTimer()
         self.messageCheck()
         self.poker_auth.SetLevel(PACKET_POKER_SEAT, User.REGULAR)
         self.poker_auth.SetLevel(PACKET_POKER_GET_USER_INFO, User.REGULAR)
@@ -286,6 +288,19 @@
         self.poker_auth.SetLevel(PACKET_POKER_HAND_SELECT_ALL, User.ADMIN)
         service.Service.startService(self)
         self.down = False
+        
+    
+    def cleanupSerial2leavetimeTimer(self):
+        for table in self.tables.values():
+                serialsToDelete = {}
+                for (player_serial , leavetime ) in table.serial2leavetime.iteritems():
+                    if (seconds() - leavetime[0]) > table.timeBeforeYouCanRebuyMin :
+                        serialsToDelete[player_serial] = player_serial;                               
+                for serial in serialsToDelete:
+                    del table.serial2leavetime[serial]    
+                               
+        self.timer['cleanupSerial2leavetimeTimer'] = reactor.callLater(CLEANUP_SERIAL2LEAVETIME_DELAY, self.cleanupSerial2leavetimeTimer)
+                             
 
     def message(self, string):
         print "PokerService: " + str(string)
@@ -431,6 +446,7 @@
 
     def shutdown(self):
         self.shutting_down = True
+        self.cancelTimer('cleanupSerial2leavetimeTimer')
         self.cancelTimer('checkTourney')
         self.cancelTimer('updateTourney')
         self.cancelTimer('messages')
@@ -1880,6 +1896,7 @@
                                             name = "anonymous",
                                             url= "random",
                                             outfit = "random",
+                                            tables_max = -1,
                                             # FIXME_PokerPlayerInfoLocale:
                                             # (see also sr #2262 )
                                             # this sets locale but
@@ -1895,19 +1912,20 @@
             return placeholder
 
         cursor = self.db.cursor()
-        sql = ( "select locale,name,skin_url,skin_outfit from users where serial = " + str(serial) )
+        sql = ( "select locale,name,skin_url,skin_outfit,tables_max from users where serial = " + str(serial) )
         cursor.execute(sql)
         if cursor.rowcount != 1:
             self.error("getPlayerInfo(%d) expected one row got %d" % ( serial, cursor.rowcount ))
             return placeholder
-        (locale,name,skin_url,skin_outfit) = cursor.fetchone()
+        (locale,name,skin_url,skin_outfit,tables_max) = cursor.fetchone()
         if skin_outfit == None:
             skin_outfit = "random"
         cursor.close()
         packet = PacketPokerPlayerInfo(serial = serial,
                                        name = name,
                                        url = skin_url,
-                                       outfit = skin_outfit)
+                                       outfit = skin_outfit,
+                                       tables_max = tables_max)
         # pokerservice generally provides playerInfo() internally to
         # methods like pokeravatar.(re)?login.  Since this is the central
         # internal location where the query occurs, we hack in the locale
Index: pokernetwork/pokeravatar.py
===================================================================
--- pokernetwork/pokeravatar.py	(révision 6419)
+++ pokernetwork/pokeravatar.py	(copie de travail)
@@ -127,6 +127,7 @@
         self.user.privilege = User.REGULAR
         self.user.url = player_info.url
         self.user.outfit = player_info.outfit
+        self.user.tablesMax = player_info.tables_max
         self._setDefaultLocale(player_info.locale)
 
         if self.explain:
@@ -138,10 +139,11 @@
         self.loginTableUpdates(serial)
     
     def login(self, info):
-        (serial, name, privilege) = info
+        (serial, name, privilege, tables_max) = info
         self.user.serial = serial
         self.user.name = name
         self.user.privilege = privilege
+        self.user.tablesMax = tables_max
 
         player_info = self.service.getPlayerInfo(serial)
         self.user.url = player_info.url
@@ -222,6 +224,9 @@
     def getName(self):
         return self.user.name
 
+    def getTablesMax(self):
+        return self.user.tablesMax
+
     def getUrl(self):
         return self.user.url
 
@@ -972,7 +977,8 @@
             return PacketPokerPlayerInfo(serial = self.getSerial(),
                                          name = self.getName(),
                                          url = self.user.url,
-                                         outfit = self.user.outfit)
+                                         outfit = self.user.outfit,
+                                         tables_max = self.user.tablesMax)
         else:
             return PacketError(code = PacketPokerGetPlayerInfo.NOT_LOGGED,
                                message = "Not logged in",
@@ -1096,7 +1102,8 @@
                                                            auto = player.auto,
                                                            auto_blind_ante = player.auto_blind_ante,
                                                            wait_for = player.wait_for,
-                                                           seat = player.seat))
+                                                           seat = player.seat,
+                                                           buy_in_payed = player.buy_in_payed))
             # Send the stats packet, generated by the UserStats package.
             self.sendPacketVerbose(self.service.getUserStatsLookup().getAttrsAsPacket(table = table, serial = player.serial))
             if not game.isPlaying(player.serial):
Index: pokernetwork/pokertable.py
===================================================================
--- pokernetwork/pokertable.py	(révision 6419)
+++ pokernetwork/pokertable.py	(copie de travail)
@@ -35,6 +35,7 @@
 from types import *
 from string import split, join
 import time
+import datetime
 import traceback
 
 from pokerengine.pokergame import PokerGameServer, history2messages
@@ -93,6 +94,14 @@
         self.temporaryPlayersPattern = settings.headerGet("/server/users/@temporary")
         self.cache = self.createCache()
         self.owner = 0
+        self.timeBeforeYouCanRebuyMinProps = settings.headerGetProperties("/server/remaining-buy-in")
+        self.isTimeBeforeYouCanRebuyMin= False                 
+        if len(self.timeBeforeYouCanRebuyMinProps) != 0:
+            if self.timeBeforeYouCanRebuyMinProps[0].get("activate") == "yes":
+                self.isTimeBeforeYouCanRebuyMin= True
+                self.timeBeforeYouCanRebuyMin = int (self.timeBeforeYouCanRebuyMinProps[0].get("period", 1800))
+            
+        self.serial2leavetime = {} # key : serial ; value : [ time , money ]        
         self.serial2client = {}
         self.timer_info = {
             "playerTimeout": None,
@@ -936,6 +945,9 @@
             #
             # If not on a closed table, stand up
             #
+            if(not self.game.isTournament() and self.isTimeBeforeYouCanRebuyMin and (self.game.minMoney() < self.game.serial2player[serial].money)):
+                self.serial2leavetime[serial] = [ seconds() , self.game.serial2player[serial].money ]       
+                      
             if self.isOpen():
                 if client.removePlayer(self, serial):
                     self.seated2observer(client)
@@ -1155,11 +1167,23 @@
             return False
 
         # Next, test to see if joining this table will cause the client to
-        # exceed the maximum permitted by the server.
-        if len(client.tables) >= self.factory.simultaneous:
+        # exceed the maximum permitted by the server 
+        clientTablesLen = len(client.tables)
+        if clientTablesLen >= self.factory.simultaneous:        
             if self.factory.verbose:
-                self.error("joinPlayer: %d seated at %d tables (max %d)" % ( serial, len(client.tables), self.factory.simultaneous ))
+                self.message("joinPlayer: %d seated at %d tables (max %d)" % ( serial, clientTablesLen, self.factory.simultaneous ))
+            client.sendPacketVerbose(PacketPokerError(message = "You have reached the maximum number of tables available by the server (%d)" % (self.factory.simultaneous ),
+                                                      code=100 ))                                
             return False
+         
+        # Next, test to see if joining this table will cause the client to
+        # exceed the maximum permitted by the maximum specified for him
+        if client.getTablesMax() != -1 and clientTablesLen >= client.getTablesMax():       
+            if self.factory.verbose:
+                self.message("joinPlayer: %d seated at %d tables (specific user max %d)" % ( serial, clientTablesLen, client.getTablesMax()))                                         
+            client.sendPacketVerbose(PacketPokerError(message = "You have reached the maximum number of tables available with your access (%d)" % (client.getTablesMax() ),
+                                                      code=101 ))                                                                        
+            return False
 
         #
         # Player is now an observer, unless he is seated
@@ -1305,6 +1329,25 @@
             self.error("player %d already payed the buy-in" % client.getSerial())
             return False
 
+        if amount < game.max_buy_in and self.isTimeBeforeYouCanRebuyMin and self.serial2leavetime.has_key(client.getSerial()) :     
+            diff = seconds() - self.serial2leavetime[client.getSerial()][0]
+            amount_last = self.serial2leavetime[client.getSerial()][1]
+            if (  diff  < self.timeBeforeYouCanRebuyMin  ) and amount_last > amount :
+                rest = self.timeBeforeYouCanRebuyMin - diff                
+                if amount_last > game.max_buy_in :
+                    amount_rebuy = game.max_buy_in
+                    is_buy_in_max = True
+                else:
+                    amount_rebuy = amount_last
+                    is_buy_in_max = False                
+                if self.factory.verbose:
+                    period = strftime('%M minutes and %S secondes', gmtime(rest))
+                    self.message("player %d can't for the next %s buyin less than %d " % (client.getSerial(), period, amount_rebuy ))                              
+                self.broadcast(PacketPokerRemainingBuyIn( game_id = game.id , period = rest, min = amount_rebuy, is_buy_in_max = is_buy_in_max ))              
+                return False
+            else:
+                del self.serial2leavetime[client.getSerial()]
+                
         amount = self.factory.buyInPlayer(client.getSerial(), game.id, self.currency_serial, max(amount, game.buyIn()))
         return client.setMoney(self, amount)
         
Index: pokernetwork/pokerauth.py
===================================================================
--- pokernetwork/pokerauth.py	(révision 6419)
+++ pokernetwork/pokerauth.py	(copie de travail)
@@ -52,11 +52,12 @@
 
     def auth(self, name, password):
         cursor = self.db.cursor()
-        cursor.execute("SELECT serial, password, privilege FROM users "
+        cursor.execute("SELECT serial, password, privilege, tables_max FROM users "
                        "WHERE name = '%s'" % name)
         numrows = int(cursor.rowcount)
         serial = 0
         privilege = User.REGULAR
+        tables_max = -1
         if numrows <= 0:
             if self.auto_create_account:
                 if self.verbose > 1:
@@ -73,13 +74,13 @@
             cursor.close()
             return ( False, "Invalid login or password" )
         else:
-            (serial, password_sql, privilege) = cursor.fetchone()
+            (serial, password_sql, privilege, tables_max) = cursor.fetchone()
             cursor.close()
             if password_sql != password:
                 self.message("password mismatch for %s" % name)
                 return ( False, "Invalid login or password" )
 
-        return ( (serial, name, privilege), None )
+        return ( (serial, name, privilege, tables_max), None )
 
     def userCreate(self, name, password):
         if self.verbose:
Index: pokernetwork/user.py
===================================================================
--- pokernetwork/user.py	(révision 6419)
+++ pokernetwork/user.py	(copie de travail)
@@ -73,6 +73,7 @@
         self.url = "random"
         self.outfit = "random"
         self.privilege = None
+        self.tablesMax = -1
 
     def logout(self):
         self.serial = 0
@@ -80,6 +81,7 @@
         self.url = "random"
         self.outfit = "random"
         self.privilege = None
+        self.tablesMax = -1
         
     def isLogged(self):
         return not self.serial == 0
Index: tests/test-pokeravatar.py.in
===================================================================
--- tests/test-pokeravatar.py.in	(révision 6419)
+++ tests/test-pokeravatar.py.in	(copie de travail)
@@ -1167,7 +1167,7 @@
     def playerInfoUnpriv(self, (client, packet)):
         return self.pingThenExpectPrivilegeFailure((client, packet), 
             PacketPokerPlayerInfo(serial= client.getSerial(), name = "The Naked Guy",
-                                  outfit = "Naked", url = "http://example.org"))
+                                  outfit = "Naked", url = "http://example.org", tables_max = -1))
     # -------------------------------------------------------------------------
     def test17_4_tourneyTourneyRegisterUnpriv(self):
         self.createClients(1)
@@ -1203,7 +1203,7 @@
                               'packet': 
                               PacketPokerPlayerInfo(serial = someoneElseSerial,
                                       name = "YOU_BEEN_CRACKED",
-                                      url = "http://example.com/myhack", outfit = "Naked") },
+                                      url = "http://example.com/myhack", outfit = "Naked", tables_max = -1) },
             'player_image' : { 'output' :
                                "%sattempt to set player image%s" \
                                    % (messageStart, forPlayerByPlayer),
@@ -1786,7 +1786,8 @@
         avatar.handlePacketLogic(PacketPokerPlayerInfo(serial= client.getSerial(),
                                                        name = "The Naked Guy",
                                                        outfit = "Naked",
-                                                       url = "http://example.org"))
+                                                       url = "http://example.org",
+                                                       tables_max = -1))
         found = False
         for packet in avatar.resetPacketsQueue():
             if packet.type == PACKET_POKER_PLAYER_INFO:
@@ -1818,7 +1819,8 @@
         avatar.handlePacketLogic(PacketPokerPlayerInfo(serial= client.getSerial(),
                                                        name = "The Naked Guy",
                                                        outfit = "Naked",
-                                                       url = "http://example.org"))
+                                                       url = "http://example.org",
+                                                       tables_max = -1))
         found = False
         for packet in avatar.resetPacketsQueue():
             if packet.type == PACKET_ERROR:
@@ -2237,7 +2239,7 @@
         avatar0 = self.service.avatars[0]
         avatar0.logout()
         table.observers.append(avatar0)
-        avatar0.login((4, "user0", 32767))
+        avatar0.login((4, "user0", 32767, -1))
         avatar0.queuePackets()
         count = 0
         for packet in avatar0.resetPacketsQueue():
@@ -2292,7 +2294,7 @@
         avatar0 = self.service.avatars[0]
         avatar0.logout()
         table.observers.append(avatar0)
-        avatar0.login((4, "user0", 32767))
+        avatar0.login((4, "user0", 32767,-1))
         avatar0.queuePackets()
         count = 0
         for packet in avatar0.resetPacketsQueue():
@@ -3933,6 +3935,7 @@
             mpiSelf.outfit = "naked"
             mpiSelf.locale = 'mylocale'
             mpiSelf.name = 'Doyle Brunson'
+            mpiSelf.tables_max = -1
     class MockService:
         def __init__(msSelf):
             msSelf.verbose = 6
Index: tests/test-pokerclient.py.in
===================================================================
--- tests/test-pokerclient.py.in	(révision 6419)
+++ tests/test-pokerclient.py.in	(copie de travail)
@@ -1138,7 +1138,8 @@
         client.handlePlayerInfo(PacketPokerPlayerInfo(name = "test",
                                                   url = "http://thatisone/",
                                                   outfit = "Stablize",
-                                                  serial = client.getSerial()))
+                                                  serial = client.getSerial(),
+                                                  tables_max = -1))
         if forceCrash:
             self.assertEquals(get_messages(), 
                               ['ERROR *CRITICAL*: PACKET_POKER_PLAYER_INFO: may enter loop packet.url = http://thatisone/\n url = http://thatistwo\n url_check = http://thatisthree\npacket.outfit = Stablize\n outfit = Stablize\n outfit_check = Stablize'])
@@ -1182,7 +1183,8 @@
         client.handlePlayerInfo(PacketPokerPlayerInfo(name = "test",
                                                   url = "http://stable/",
                                                   outfit = "OutfitOne",
-                                                  serial = client.getSerial()))
+                                                  serial = client.getSerial(),
+                                                  tables_max = -1))
         if forceCrash:
             self.assertEquals(get_messages(), 
                               ['ERROR *CRITICAL*: PACKET_POKER_PLAYER_INFO: may enter loop packet.url = http://stable/\n url = http://stable/\n url_check = http://stable/\npacket.outfit = OutfitOne\n outfit = OutfitTwo\n outfit_check = OutfitThree'])
Index: tests/conf/poker.server.xml.in
===================================================================
--- tests/conf/poker.server.xml.in	(révision 6419)
+++ tests/conf/poker.server.xml.in	(copie de travail)
@@ -7,6 +7,8 @@
 	chat="yes" >
 
   <delays autodeal="3" round="2" position="1" showdown="5" finish="3" />
+    <!-- remaining buy-in -->
+  <remaining-buy-in activate="yes" period="1800" />
   
   <table name="One"	variant="holdem" betting_structure="2-4-limit" seats="10" timeout="60" custom_money="0" />
   <table name="Two"	variant="holdem" betting_structure="10-15-limit" seats="10" timeout="60" custom_money="0" />
Index: tests/test-pokertable.py.in
===================================================================
--- tests/test-pokertable.py.in	(révision 6419)
+++ tests/test-pokertable.py.in	(copie de travail)
@@ -62,6 +62,8 @@
 <server verbose="4" autodeal="yes" max_missed_round="5">
   <delays autodeal_tournament_min="2" autodeal="2" autodeal_max="2" autodeal_check="0" round="0" position="0" showdown="0" finish="0" />
 
+  <remaining-buy-in activate="yes" period="1" />
+  
   <path>@POKER_ENGINE_PKGSYSCONFDIR@ @POKER_NETWORK_PKGSYSCONFDIR@</path>
   <users temporary="BOT"/>
 </server>
@@ -70,6 +72,8 @@
 <server verbose="4" autodeal="no" >
   <delays autodeal_tournament_min="2" autodeal="2" autodeal_max="2" autodeal_check="0" round="0" position="0" showdown="0" finish="0" />
 
+  <remaining-buy-in activate="yes" period="1" />
+  
   <decks>
     <deck>9c 9d 9h Ts Tc Td Th Ts Jc Jd Jh Js Qc Qd Qh Qs Kc Kd Kh Ks Ac Ad Ah As</deck>
   </decks>
@@ -152,6 +156,9 @@
         self.joined_max = 1000
         self.chat_messages = []
 
+    def setSimultaneous(self, tables_max):
+        self.simultaneous = tables_max
+        
     def getMissedRoundMax(self):
         return 5  # if you change this, change it in settings_xml above
 
@@ -330,6 +337,7 @@
         self.user = MockClient.User()
         self.testObject = testObject
         self.reasonExpected = expectedReason
+        self.tables_max = -1
 
     def __str__(self):
         return "MockClient of Player%d" % self.serial
@@ -414,7 +422,15 @@
                 self.name = self.player.getName()
                 self.url = "http://fake"
                 self.outfit = None
+                self.tables_max = -1
         return MockPlayerInfo(self)
+                
+    def getTablesMax(self):
+        return self.tables_max   
+        
+    def setTablesMax(self, table_max):
+        self.tables_max = table_max        
+             
 
 if verbose < 0: redirect_messages(MockClient)
 
@@ -628,9 +644,18 @@
 
         self.assertEqual(True, self.table.rebuyPlayerRequest(player[5], \
                                               self.table.game.maxBuyIn()))
-        # finally, player5 tries to join table 2, which isn't permitted since
+        # now, player5 tries to join table 2, which isn't permitted since
         # we've set MockService.simultaneous to 1
         self.assertEqual(False, self.table2.joinPlayer(player[5], 5))
+        
+        # finally, apply simultaneous = 2, player5 tries to join table 2, which isn't permitted since
+        # we've set tablesMax for specific user to 1       
+        self.service.setSimultaneous(2) 
+        player[5].setTablesMax(1)
+        self.assertEqual(False, self.table2.joinPlayer(player[5], 5))  
+        player[5].setTablesMax(2)   
+        self.assertEqual(True, self.table2.joinPlayer(player[5], 5))    
+        
     # -------------------------------------------------------------------
     def test08_2_brokenSeatFactory(self):
         player = self.createPlayer(1, False)
@@ -1423,6 +1448,18 @@
         self.tableSave = self.table
 
         return deferredMustBeCalledBackForSuccess
+        
+	# -------------------------------------------------------------------    
+    def test47_timeBeforeYouCanRebuyMin(self):
+        # note : createPlayer apply maxBuyIn
+        p = self.createPlayer(1)
+     	self.table.quitPlayer(p, 1)
+     	self.table.joinPlayer(p, p.serial)
+     	self.table.seatPlayer(p, p.serial, -1)
+     	if self.table.game.isTournament() == False:
+        	self.assertEqual(False,self.table.buyInPlayer(p, self.table.game.minMoney()))
+         	self.assertEqual(True,self.table.buyInPlayer(p, self.table.game.maxBuyIn()))
+   
     # -------------------------------------------------------------------
     def test48_muckTimeoutTimerShouldEmptyMuckableSerials(self):
         """
@@ -1863,6 +1900,11 @@
         """SKIP THIS TEST IN THIS SUBCLASS
         """
         return True
+    # -------------------------------------------------------------------    
+    def test47_timeBeforeYouCanRebuyMin(self):
+        """SKIP THIS TEST IN THIS SUBCLASS
+        """
+        return True          
 # --------------------------------------------------------------------------------
 class MockServiceWithUserStats(MockService):
     def __init__(self, settings):
Index: tests/test-pokerservice.py.in
===================================================================
--- tests/test-pokerservice.py.in	(révision 6419)
+++ tests/test-pokerservice.py.in	(copie de travail)
@@ -67,6 +67,7 @@
 from pokernetwork.attrpack  import AttrsLookup
 from MySQLdb.cursors import DictCursor
 
+from twisted.python.runtime import seconds
 
 class ConstantDeckShuffler:
     def shuffle(self, what):
@@ -220,9 +221,9 @@
             cursor.execute("INSERT INTO users (name, password, created) VALUES ('user%d', 'password%d', 0)" % ( user_number, user_number ))
             self.assertEqual(1, cursor.rowcount)
 
-        ( (self.user1_serial, name, privilege), message ) = self.service.auth("user1", "password1", "role1")
-        ( (self.user2_serial, name, privilege), message ) = self.service.auth("user2", "password2", "role1")
-        ( (self.user3_serial, name, privilege), message ) = self.service.auth("user3", "password3", "role1")
+        ( (self.user1_serial, name, privilege, tables_max), message ) = self.service.auth("user1", "password1", "role1")
+        ( (self.user2_serial, name, privilege, tables_max), message ) = self.service.auth("user2", "password2", "role1")
+        ( (self.user3_serial, name, privilege, tables_max), message ) = self.service.auth("user3", "password3", "role1")
 
         for user_number in (self.user1_serial, self.user2_serial, self.user3_serial):
             if self.default_money > 0 and user_number == self.user3_serial:
@@ -1309,7 +1310,7 @@
     # ----------------------------------------------------------------
     def test01_auth(self):
         self.service.startService()
-        ( (serial, name, privilege), message ) = self.service.auth("user1", "password1", "role1")
+        ( (serial, name, privilege, tables_max), message ) = self.service.auth("user1", "password1", "role1")
         self.assertEquals(None, message)
         self.assertEquals(4, serial)
         self.assertEquals("user1", name)
@@ -1332,7 +1333,7 @@
                 return "user1"
 
         self.service.startService()
-        ( (serial, name, privilege), message ) = self.service.auth("user1", "password1", sets.Set('role1'))
+        ( (serial, name, privilege, tables_max), message ) = self.service.auth("user1", "password1", sets.Set('role1'))
         self.service.serial2client[serial] = Client()
         ( status, message ) = self.service.auth("user1", "password1", sets.Set('role1'))
         self.assertEquals('Already logged in from somewhere else', message)
@@ -1972,7 +1973,7 @@
     def test_refill(self):
         self.service.startService()
         refill = 10000
-        ( (serial, name, privilege), message ) = self.service.auth("user1", "password1", "role1")
+        ( (serial, name, privilege, tables_max), message ) = self.service.auth("user1", "password1", "role1")
         self.assertEquals(0, self.service.autorefill(serial))
         table_money = 1000
         table_serial = 200
@@ -4483,7 +4484,7 @@
                         found = True
                         break
                 cursorSelf.rowcount = 1
-                cursorSelf.row = ('ourlocal','ourname','ourskinurl',None)
+                cursorSelf.row = ('ourlocal','ourname','ourskinurl',None,-1)
                 self.failUnless(found)
                 return cursorSelf.rowcount
             def fetchone(cursorSelf): return cursorSelf.row
@@ -4505,6 +4506,7 @@
         self.assertEquals(pack.name, 'ourname')
         self.assertEquals(pack.url, 'ourskinurl')
         self.assertEquals(pack.outfit, 'random')
+        self.assertEquals(pack.tables_max, -1)
 
         self.service.db = oldDb
     # ----------------------------------------------------------------
@@ -5304,6 +5306,7 @@
         self.assertEquals(packet.name, "anonymous")
         self.assertEquals(packet.url, "random")
         self.assertEquals(packet.outfit, "random")
+        self.assertEquals(packet.tables_max, -1)
         # FIXME_PokerPlayerInfoLocale: (see also sr #2262 )
         # PokerService.getPlayerInfo() sends locale argument when creating
         # the PokerPlayerInfo() packet, but that argument is not used.
@@ -5319,7 +5322,7 @@
                 self.failUnless(sql.find("serial = 235") > 0, "serial wrong")
             def __init__(cursorSelf):
                 MockCursorBase.__init__(cursorSelf, self, 
-                                      ["select locale,name,skin_url,skin_outfit from users"])
+                                      ["select locale,name,skin_url,skin_outfit,tables_max from users"])
         self.service = pokerservice.PokerService(self.settings)
 
         oldDb = self.service.db
@@ -5334,6 +5337,7 @@
         self.assertEquals(packet.name, "anonymous")
         self.assertEquals(packet.url, "random")
         self.assertEquals(packet.outfit, "random")
+        self.assertEquals(packet.tables_max, -1)
         # FIXME_PokerPlayerInfoLocale: (see also sr #2262 )
         # PokerService.getPlayerInfo() sends locale argument when creating
         # the PokerPlayerInfo() packet, but that argument is not used.
@@ -6211,7 +6215,31 @@
         self.assertEquals(get_messages(), [])
 
         clear_all_messages()
-        return deferredMessageCheck
+        return deferredMessageCheck        
+    # ----------------------------------------------------------------    
+    def test69_cleanupSerial2leavetimeTimer(self):
+        pokerservice.CLEANUP_SERIAL2LEAVETIME_DELAY = 0
+        self.service = pokerservice.PokerService(self.settings)        
+
+        class MockTable:
+            def __init__(mgSelf):
+                mgSelf.id = 7775
+                mgSelf.timeBeforeYouCanRebuyMin = 0
+                mgSelf.serial2leavetime = {100: {0:seconds()} }                
+
+        ourTable  = MockTable()
+        class MockGame:
+            def __init__(mgSelf):
+                mgSelf.id = 7775
+
+        ourTable.game = MockGame()
+        self.service.tables = { 7775: ourTable }
+        
+        self.assertEquals(len(self.service.tables[7775].serial2leavetime),1)   
+        self.service.cleanupSerial2leavetimeTimer()  
+        self.assertEquals(len(self.service.tables[7775].serial2leavetime),0)      
+        
+        self.service.cancelTimer('cleanupSerial2leavetimeTimer')                
 ##############################################################################
 class SSLContextFactoryCoverage(unittest.TestCase):
     # ----------------------------------------------------------------
Index: tests/poker.server.xml
===================================================================
--- tests/poker.server.xml	(révision 6419)
+++ tests/poker.server.xml	(copie de travail)
@@ -6,6 +6,8 @@
     verbose="3"
     chat="yes" >
 
+  <remaining-buy-in activate="yes" period="1800" />
+
   <delays autodeal="3" round="2" position="1" showdown="7" finish="3" />
   
   <table name="Fourty"	variant="7stud" betting_structure="ante-5-10-limit" seats="8" player_timeout="60" custom_money="0" />
Index: tests/test-tourneytablebalance.py.in
===================================================================
--- tests/test-tourneytablebalance.py.in	(révision 6419)
+++ tests/test-tourneytablebalance.py.in	(copie de travail)
@@ -121,6 +121,7 @@
                 miSelf.name =  "PLAYER INFO: %d" % self.serial
                 miSelf.url  = "http://example.org"
                 miSelf.outfit  = "naked"
+                miSelf.tables_max = -1
         return MockInfo()
 
     def sendPacket(self, packet):
Index: tests/test-pokerauth.py.in
===================================================================
--- tests/test-pokerauth.py.in	(révision 6419)
+++ tests/test-pokerauth.py.in	(copie de travail)
@@ -149,7 +149,7 @@
         auth = pokerauth.get_auth_instance(db, settings)
 
         clear_all_messages()
-        self.assertEquals(auth.auth('joe_schmoe', 'foo'), ((4, 'joe_schmoe', 1), None))
+        self.assertEquals(auth.auth('joe_schmoe', 'foo'), ((4, 'joe_schmoe', 1, -1), None))
         self.assertEquals(get_messages(), ['user joe_schmoe does not exist, create it',
                                        'creating user joe_schmoe', 'create user with serial 4'])
         self.failUnless(len(self.checkIfUserExistsInDB('joe_schmoe')) == 1)
@@ -174,6 +174,7 @@
         cursor.execute("""CREATE TABLE users (
  	    serial int unsigned not null auto_increment,
 	    name varchar(32), password varchar(32), privilege int default 1,
+	    tables_max int default -1,	    
             primary key (serial))""")
         for ii in [ 1 , 2 ]:
             cursor.execute("INSERT INTO users (name, password) values ('%s', '%s')" %
@@ -198,7 +199,7 @@
         auth = pokerauth.get_auth_instance(self.db, self.settings)
 
         clear_all_messages()
-        self.assertEquals(auth.auth('dan_harrington', 'bar'), ((4L, 'dan_harrington', 1L), None))
+        self.assertEquals(auth.auth('dan_harrington', 'bar'), ((4L, 'dan_harrington', 1L, -1), None))
         self.assertEquals(get_messages(), [])
 
         clear_all_messages()
Index: database/schema.sql.in
===================================================================
--- database/schema.sql.in	(révision 6419)
+++ database/schema.sql.in	(copie de travail)
@@ -60,6 +60,8 @@
   password VARCHAR(32),
   -- 1 is a regular player, 0 is an observer and cannot play, 2 is admin
   privilege INT DEFAULT 1,
+  -- max tables allowed , no check if -1
+  tables_max INT DEFAULT -1,    
   -- locale
   locale VARCHAR(32) DEFAULT "en_US",
 
