class ServerApp(App): """Class defining all methods necessary to the server app.""" __duration = 10 # In minutes the duration to watch for tendencies __refuel_duration = 15 # In minutes the duration of one refuel request_refuel = pyqtSignal(str, str, float, bool, bool) def __init__(self): """Constructor.""" self.__database = Database(DB_NAME) #création d'une table history_champagne max_drink = self.__database.select("SELECT MAX(id) FROM drinks")[0][0] self.history_champagne = np.zeros((5, max_drink)) self._socket = ServerSocket() self.__reset_rooms() App.__init__(self) # Set at the end because it launches the app def __reset_rooms(self): """Destructor.""" print("Setting all rooms to disconnected.") self.__database.execute("UPDATE rooms SET connected=0") self.__database.execute("UPDATE rooms SET ip=Null") def _open_window(self): """Method replacing the App's one opening the server window.""" self._window = Window(self, Locations.RESERVE) def _connect_signals(self): """Method connecting the signals of the app.""" # Connecting quit signal self._window.quit_app.connect(self.__reset_rooms) # Connecting the socket on connection self._socket.listening.connect(self.connection_established.emit) # Connecting the socket messages self._socket.message_received.connect(lambda m: self.decode_message( m[0], client=m[1].peerAddress().toString())) # Connecting the clients disconnection # self._socket.client_disconnected.connect( # lambda x: self.__database.execute("UPDATE rooms SET connected=0 WHERE ip='{}'".format(x))) # Connecting the export signal self._window.export_db.connect(self.__exportDB) # Connecting the drop base signal self._window.drop_base.connect(self.__drop_base) # Sending the connection infos when requested self._window.get_connection_infos.connect( lambda: self.send_connection_infos.emit(self._get_connection_infos( ))) # Connecting the socket when requested self._window.set_connection_infos.connect(self.__listen) # Sending the bar names when requested self._window.get_bar_names.connect( lambda: self.send_bar_names.emit(self._get_bar_names())) # Sending the champagne names when requested self._window.get_champagne_names.connect( lambda: self.send_champagne_names.emit(self._get_champagne_names() )) # Setting the name when requested self._window.set_bar_name.connect(self.__set_name) # Connecting the preferences fill self._window.ask_preferences.connect( lambda: self.fill_preferences.emit((self._socket.get_address( ).toString(), self._socket.get_port()), self._name)) # Connecting the send message signal self._window.send_message.connect( lambda x: self.encode_message(action="ME", message=x)) # Connecting the send urgent message signal self._window.send_message_urgent.connect( lambda x: self.encode_message(action="UR", message=x)) # Connecting the cancellation of an order self._window.cancel_order.connect(lambda x: self.cancel_sale(x)) # Connecting the sending refuel signal self._window.sending_refuel.connect(lambda x: self.encode_message( action="RT", drink=x[1], quantity=x[2], recipient=x[0])) # Connecting for the connection dialog self._window.request_connection_infos.connect( lambda: self.open_connection_dialog.emit(self.__get_devices_state( ))) def __listen(self, args): """Method handling the socket.""" try: self._socket.listen(*args) except Exception as error: self._window.open_dialog( "Erreur de connection", "L'erreur suivante est survenue : {}.".format(error), type="error") def __get_devices_state(self): """Method returning devices name and their state of connection""" return self.__database.select("SELECT name, connected FROM rooms") def __exportDB(self, file): """Method exporting the data in the DB to an excel file.""" print('Exporting workbook...', end="") wb = Workbook() wb.active.title = "Historique" for title in ["Stocks", "Boissons", "Salles"]: wb.create_sheet(title) tables = [] for table_name in ["history", "stocks", "drinks", "rooms"]: columns = [ c[1] for c in self.__database.select( "PRAGMA table_info({})".format(table_name)) ] content = self.__database.select( "SELECT * FROM {}".format(table_name)) tables.append([columns] + content) for sheet, table in zip(wb, tables): for i, entry in enumerate(table): for j, attribute in enumerate(entry): sheet.cell( row=i + 1, column=j + 1).value = attribute # Excel is set to begin at 1 wb.save(file) self._window.open_dialog( "Export Excel terminé !", "L'export des données sous format Excel s'est achevé.") print('Done.') def __drop_base(self): """Method called to drop the data base.""" import os self.__database.close() os.remove(DB_NAME) self.__database = Database(DB_NAME) self.__database.import_(SQL_FILE) self._window.open_dialog("Importation terminée !", "La base a été remise à zéro.") def _get_connection_infos(self): """Method getting the connection infos (available ips).""" return [ip.toString() for ip in self._socket.get_available_ips()] def _get_bar_names(self): """Method getting the bar names (bars not connected).""" resp = self.__database.select( "SELECT name FROM rooms WHERE connected=0") return [b[0] for b in resp] def _get_champagne_names(self): """Method getting the champagnes names.""" resp = self.__database.select( "SELECT name FROM drinks WHERE is_champagne=1") print("get champ: ", resp) return [b[0] for b in resp] def __set_name(self, name): """Method called to set the name of the server.""" self._name = name self._window.chat_panel.place_name = name # Search for a client with the same name in base id_ = None resp = self.__database.select( "SELECT id FROM rooms WHERE name='{}' AND connected=0".format( name)) if len(resp) > 0: # Found id_ = resp[-1][0] else: print("No match found for name '{}'".format(name)) # Modifying data if id_: # Resuming self.__database.execute( "UPDATE rooms SET ip='{}', connected=1 WHERE id='{}'".format( self._socket.get_address().toString(), id_)) else: # New self.__database.execute( "INSERT INTO rooms (name, ip) VALUES ('{}', '{}')".format( name, self._socket.get_address().toString())) self.name_set.emit() # def transaction(self, id_drink, id_room, number, is_sale=True, cancel=False): # """Method used to change the quantity of a drink in the table stocks. # If you want to cancel a previous transaction, set cancel to the desired id.""" # # resp = self.__database.select( # # "SELECT quantity FROM stocks WHERE drink={} AND room={}".format(id_drink, id_room)) # # if len(resp) == 0: # # quantity = resp[0][0] # # else: # # print("Unable to find a unique record for drink '{}' and bar '{}'.".format(id_drink, id_room)) # self.__database.execute("UPDATE stocks SET quantity=quantity+{} WHERE drink='{}' AND room='{}'".format( # (-1 ** is_sale) * number, id_drink, id_room)) # if not cancel: # self.__database.execute( # "INSERT INTO history(stamp, drink, room, quantity, is_sale) VALUES (datetime('now','+1 hour'), '{}', '{}', {}, {})".format( # id_drink, id_room, number, int(is_sale))) # # else: # # self.__notification # # # if not is_sale: # # self.__notification.remove([id_drink, id_room]) # self.forecast_all() # It would be better reforecasting only the drink concerned def transaction_v2(self, id_room, id_drink, quantity, sale=True, cancellation=False): """Method used to perform a transaction on the database. To make a resupply, set sale to False.""" # Updating the stocks self.__database.execute( "UPDATE stocks SET quantity=quantity+{} WHERE drink='{}' AND room='{}'" .format(((-1)**sale) * quantity, id_drink, id_room)) self.__database.execute( "UPDATE stocks SET consommation = consommation + {} WHERE drink='{}' AND room='{}'" .format(sale * quantity, id_drink, id_room)) if cancellation: self.__database.execute( "UPDATE stocks SET consommation = consommation + {} WHERE drink='{}' AND room='{}'" .format(-1 * quantity, id_drink, id_room)) list_champagne = self.__database.select( "SELECT id FROM drinks WHERE is_champagne = 1") for i in range(len(list_champagne)): list_champagne[i] = list_champagne[i][0] if int(id_drink) in list_champagne: list_emit = [] self.__database.execute( "UPDATE stocks SET quantity_sold=quantity_sold+{} WHERE drink='{}' AND room='{}'" .format(sale * quantity, id_drink, id_room)) qteSoldChamp = self.__database.select( "SELECT s.drink, s.room, s.consommation FROM stocks AS s JOIN drinks AS d WHERE d.id = s.drink AND d.is_champagne = 1" ) qteChamp = np.zeros((len(list_champagne), 5), int) for i in qteSoldChamp: qteChamp[i[0] - 1][i[1] - 1] = i[2] self.encode_message(action="CH", qteChamp=qteChamp.transpose().tolist()) if not cancellation: # Inserting a row in the history self.__database.execute( "INSERT INTO history(stamp, drink, room, quantity, is_sale) VALUES (datetime('now','+1 hour'), '{}', '{}', {}, {})" .format(id_drink, id_room, quantity, int(sale))) # updating quantity_sold for the CDF if int(id_drink) in list_champagne: list_emit = [] self.__database.execute( "UPDATE stocks SET quantity_sold=quantity_sold+{} WHERE drink='{}' AND room='{}'" .format(sale * quantity, id_drink, id_room)) qteSoldChamp = self.__database.select( "SELECT s.drink, s.room, s.consommation FROM stocks AS s JOIN drinks AS d WHERE d.id = s.drink AND d.is_champagne = 1" ) qteChamp = np.zeros((len(list_champagne), 5), int) for i in qteSoldChamp: qteChamp[i[0] - 1][i[1] - 1] = i[2] self.encode_message(action="CH", qteChamp=qteChamp.transpose().tolist()) ##self.history_champagne[id_room-1, id_drink-1]=(self.__database.select("SELECT LAST_INSERT_ID() FROM history WHERE is_sale")) # Getting the id of the sale resp = self.__database.select("SELECT last_insert_rowid()") if len(resp) == 1: id = resp[0][0] # Re forecasting the drink in the room time = self.forecast_v2(id_room, id_drink) # Fetching the bar name bar_name = "" resp = self.__database.select( "SELECT name, ip FROM rooms WHERE id='{}'".format(id_room)) if len(resp) == 1: bar_name = resp[0][0] # Fetching drink name drink_name = "" resp = self.__database.select( "SELECT name FROM drinks WHERE id='{}'".format(id_drink)) if len(resp) == 1: drink_name = resp[0][0] # Emitting signal for refuel if bar_name and drink_name: # Infos were found self.request_refuel.emit( bar_name, drink_name, time, time <= 2 * self.__refuel_duration, not sale ) # 2 times the duration of one delivery (it becomes critical) else: print( "Unable to request refuel for parameters ({}, {}, {})".format( bar_name, drink_name, time)) try: print("Bar '{}' can wait for drink '{}' ({}min)".format( id_room, id_drink, int(time))) except OverflowError: print("Bar '{}' can wait for drink '{}' forever".format( id_room, id_drink)) # Send confirmation except if it is a cancellation if sale and not cancellation: drink_name = "" bar_name = "" # Getting ip and name of the bar resp = self.__database.select( "SELECT name, ip FROM rooms WHERE id='{}'".format(id_room)) if len(resp) == 1: bar_name = resp[0][0] ip = resp[0][1] else: print( "Unable to retrieve the ip of bar '{}' to send a sale confirmation." .format(id_room)) # Getting the name of the drink resp = self.__database.select( "SELECT name FROM drinks WHERE id='{}'".format(id_drink)) if len(resp) == 1: drink_name = resp[0][0] else: print( "Unable to retrieve the name of drink '{}' to send a sale confirmation." .format(id_drink)) # Sending the confirmation if drink_name and bar_name: self._window.add_order(id, drink_name, quantity, bar_name) self.encode_message(action="VE", id=id, drink=id_drink, quantity=quantity, ip=ip) def cancel_sale(self, id_order): """Method used to cancel an order""" print(id_order, type(id_order)) # Update history table self.__database.execute( "UPDATE history SET is_cancelled='{}' WHERE id='{}'".format( 1, id_order)) liste = self.__database.select( "SELECT drink, room FROM history WHERE id='{}'".format(id_order)) id_drink, id_room = liste[0] self.history_champagne[id_room - 1, id_drink - 1] = 0 # Cancelling sale in stocks resp = self.__database.select( "SELECT quantity, drink, room FROM history WHERE id='{}'".format( id_order)) if len(resp) == 1: quantity, drink, room = resp[0] print("Recrediting the bar '{}' of quantity '{}' of drink '{}'". format(room, quantity, drink)) self.transaction_v2(room, drink, quantity, False, True) # Cancelling the sale # Getting client's ip resp = self.__database.select( "SELECT ip FROM rooms WHERE id='{}' AND connected=1".format( room)) if len(resp) > 0: ip = resp[-1][0] self._window.remove_cancelled_order(id_order) self.encode_message(action="AE", canceled_sale_id=id_order, recipient=ip) else: print( "Unable to cancel sale '{}' because room '{}' ip could not be found" .format(id_order, room)) else: print( "Unable to retrieve and cancel sale '{}' from history".format( id_order)) # def get_sale_sum(self, id_drink, id_room, duration=__duration): # "Method used to get the history of orders up until a few minutes" # [[total_sales]] = self.__database.select("SELECT sum(quantity) FROM history \ # WHERE drink = {} AND room = {} AND is_sale = 1 AND is_cancelled = 0 \ # AND (stamp >= datetime('now','+1 hour','-{} minutes'))".format( # id_drink, id_room, duration)) # # What about last refuelling ? # # if type(total_sales) == int: # return total_sales # else: # return 0 # def forecast(self, id_drink, id_room): # """Method used to forecast the need for a resupply for ONE drink""" # slope = self.get_sale_sum(id_drink, id_room) / self.__duration # [[limit]] = self.__database.select("SELECT treshhold FROM drinks WHERE id = '{}'".format(id_drink)) # [[qty]] = self.__database.select( # "SELECT quantity FROM stocks WHERE drink = {} AND room = {}".format(id_drink, id_room)) # # if slope != 0: # time_appro = (qty - limit) / slope # else: # time_appro = 10000 # This is made just because we need to return # # something from the function # # return time_appro, slope def forecast_v2(self, id_room, id_drink): """Method used to forecast the duration after which the need of refuel will become critical.""" # Getting the current state of sales resp = self.__database.select( "SELECT quantity FROM stocks WHERE room='{}' AND drink='{}'". format(id_room, id_drink)) if len(resp) > 0: current = resp[0][0] # Getting the total quantity of drinks sold during the duration resp = self.__database.select( "SELECT sum(quantity) FROM history WHERE drink='{}' AND room='{}' AND is_sale=1 AND is_cancelled=0 AND stamp>=datetime('now', '+1 hour', '-{} minutes')" .format(id_drink, id_room, self.__duration)) if len(resp) == 1 and resp[0][0]: quantity = resp[0][0] print("A quantity of '{}' of drink '{}' was sold in bar '{}'". format(quantity, id_drink, id_room)) # Getting the threshold for the drink resp = self.__database.select( "SELECT threshold from drinks WHERE id='{}'".format( id_drink)) if len(resp) == 1: threshold = resp[0][0] else: threshold = 0 # Computing the time of shortage time = ( current - threshold ) * self.__duration / quantity # Time to shortage from now in minutes print( "There will be a shortage of drink '{}' in bar '{}' in {} minutes" .format(id_drink, id_room, time)) return time else: # No drink were sold in the last period return float("inf") else: print("The drink '{}' is not sold in the bar '{}'.".format( id_drink, id_room)) # def forecast_all(self): # """Method used to forecast the need for a resuply for ALL drinks""" # # list_id_room = [element for sublist in self.__database.select("SELECT id FROM rooms") for element in sublist] # list_id_room = [room[0] for room in self.__database.select( # "SELECT id FROM rooms")] # Ringing a bell ? It is called not being a complete moron # list_id_drink = [element for sublist in self.__database.select("SELECT id FROM drinks") for element in sublist] # # # Section to determine a list of restock querries # restock_query_list = [] # temporary_query_list = [] # # for id_room in list_id_room: # No! No! No! All drinks are not in all rooms!! # for id_drink in list_id_drink: # (time, slope) = self.forecast(id_drink, id_room) # quantity = ( # 2 * self.__refuel_duration - time) * slope # This quantity has been calculated to set the next refuel time at 30 minutes after the query # if [id_drink, id_room] in self.__notification: # pass # Do nothing # if time <= self.__refuel_duration: # restock_query_list.append([id_drink, id_room, quantity]) # elif time <= 2 * self._refuel_time: # temporary_query_list.append([id_drink, id_room, quantity]) # if len(restock_query_list) != 0: # restock_query_list += temporary_query_list # return restock_query_list # def restock_query(self, query_list): # """Method used to add new notifications for the Zi Bar""" # if len(query_list) == 0: # pass # else: # for query in query_list: # if query[:2] not in self.__notification: # self.__notification.append(query_list) # # Further uses for notifications ... def decode_message(self, message, client=None): """Method used to understand received messages.""" print("Decoding message '{}'".format(message)) message_split = message[1:-1].split('||') if len(message_split) > 1: # Several messages are queued for m in message_split: self.decode_message('|' + m + '|', client) return else: message = message_split[0] message_split = message.split('|') if message_split[0] == "NO": # selected name print("Client '{}' set its name to '{}'".format( client, message_split[1])) # Search for a client with the same name in base connected = False client_id = None resp = self.__database.select( "SELECT id, connected FROM rooms WHERE name='{}'".format( message_split[1])) if len(resp) > 0: # Client found client_id, connected = resp[-1] else: print("No client disconnected found matching the name '{}'". format(message_split[1])) if not connected: # The client is not already connected somewhere else # Modifying data if client_id: # A client is resuming its connection self.__database.execute( "UPDATE rooms SET ip='{}', connected=1 WHERE id='{}'". format(client, client_id)) else: # New client self.__database.execute( "INSERT INTO rooms (name, ip) VALUES ('{}', '{}')". format(message_split[1], client)) client_id = self.__database.select( "SELECT last_inserted_rowid()") # Sending the list of drinks self.encode_message(action="LO", client_id=client_id, recipient=client) # Sending the list of food self.encode_message(action="LE", recipient=client) else: print( "A client tried to set its name to one already taken '{}'". format(message_split[1])) elif message_split[0] == "VE": # sale # Simpler solution but most efficient would be to compact it resp = self.__database.select( "SELECT id FROM rooms WHERE ip='{}'AND connected=1".format( client)) if len(resp) > 0: id_bar = resp[-1][0] self.transaction_v2( id_bar, message_split[1], int(message_split[2]) ) # Id of the bar, id of the drink and number of drinks else: print("No bar found matching the given ip '{}'...".format( client)) elif message_split[0] == "AR": # client cancel a sale print(message_split[1]) self.cancel_sale(message_split[1]) elif message_split[0] == "ME": # Message by a client # Getting the client's name client_name = "" resp = self.__database.select( "SELECT name FROM rooms WHERE ip='{}' AND connected=1".format( client)) if len(resp) > 0: # Client found client_name = resp[-1][0] new_message = client_name + '|' + message[3:] else: new_message = message print("No client found matching the ip '{}'".format(client)) # Checking for the receiver dests = [ word[1:] for word in message[3:].split(' ') if word.startswith('@') ] # Words starting with '@' in the message ips = [] for d in dests: resp = self.__database.select( "SELECT ip FROM rooms WHERE name='{}'".format(d.lower())) if len(resp) > 0: # At least one match found ips.append(resp[-1][0]) # print("Relaying message '{}' to ips '{}'".format(new_message, ips)) # Preparing the message for the UI if client_name: infos = (message[3:], client_name) else: infos = (message[3:], ) # Sending the message if ips: # Some specific receivers were found # Fetching CDF ip resp = self.__database.select( "SELECT ip FROM rooms WHERE name='cdf'") if len(resp) > 0 and resp[-1][0] not in ips: ips.append(resp[-1][0]) for ip in ips: if ip == self._socket.get_address().toString(): self.message_received.emit( infos) # Sending the message to the UI else: self._socket.send_message( "|ME|{}|".format(new_message), ip) if client not in ips: self._socket.send_message("|ME|{}|".format(new_message), client) else: self._socket.send_message("|ME|{}|".format(new_message)) self.message_received.emit( infos) # Sending the message to the UI # self._socket.send_message(message) # Broadcasting the message. # self.message_received.emit(message_split[1]) elif message_split[0] == "UR": # Message by a client # Getting the client's name client_name = "" resp = self.__database.select( "SELECT name FROM rooms WHERE ip='{}' AND connected=1".format( client)) if len(resp) > 0: # Client found client_name = resp[-1][0] new_message = client_name + '|' + message[3:] else: new_message = message print("No client found matching the ip '{}'".format(client)) # Checking for the receiver dests = [ word[1:] for word in message[3:].split(' ') if word.startswith('@') ] # Words starting with '@' in the message ips = [] for d in dests: resp = self.__database.select( "SELECT ip FROM rooms WHERE name='{}'".format(d.lower())) if len(resp) > 0: # At least one match found ips.append(resp[-1][0]) # print("Relaying message '{}' to ips '{}'".format(new_message, ips)) # Preparing the message for the UI if client_name: infos = (message[3:], client_name) else: infos = (message[3:], ) # Sending the message if ips: # Some specific receivers were found # Fetching CDF ip resp = self.__database.select( "SELECT ip FROM rooms WHERE name='cdf'") if len(resp) > 0 and resp[-1][0] not in ips: ips.append(resp[-1][0]) for ip in ips: if ip == self._socket.get_address().toString(): self.urgent_message_received.emit( infos) # Sending the message to the UI else: self._socket.send_message( "|UR|{}|".format(new_message), ip) if client not in ips: self._socket.send_message("|UR|{}|".format(new_message), client) else: self._socket.send_message("|UR|{}|".format(new_message)) self.urgent_message_received.emit( infos) # Sending the message to the UI elif message_split[0] == "RE": # refueled # Getting readable parameters _, id_drink, quantity, id_refuel = message_split resp = self.__database.select( "SELECT id FROM rooms WHERE ip='{}'AND connected=1".format( client)) if len(resp) > 0: id_bar = resp[-1][0] # Getting the size of a container resp = self.__database.select( "SELECT container_size FROM drinks WHERE id='{}'".format( id_drink)) if len(resp) == 1: container_size = resp[0][0] # We suppose the reserve is never out of stocks self.transaction_v2( id_bar, id_drink, int(quantity) * float(container_size), sale=False ) # Setting sale to False makes the transaction positive # Notifying the client self.encode_message(action="RE", id_refuel=id_refuel, recipient=client) else: print( "Unable to retrieve the size of a container of drink '{}' to perform a refuel." .format(id_drink)) else: print("No bar found matching the given ip '{}'...".format( client)) elif message_split[0] == "RS": # Restal # Fetching restal ip resp = self.__database.select( "SELECT ip FROM rooms WHERE name='{}'".format('restal')) if len(resp) > 0: ip = resp[-1][0] # Fetching sender bar name print(client) resp = self.__database.select( "SELECT name FROM rooms WHERE ip='{}'".format(client)) if len(resp) > 0: bar = resp[-1][0] # Warning the restal about the sale self.encode_message(action="RS", bar=bar, food_id=message_split[1], quantity=message_split[2], recipient=ip) elif message_split[0] == "LA": # Requesting a list of bars self.encode_message(action='LA', recipient=client) elif message_split[0] == "GC": self.encode_message(action='GC', recipient=client) elif message_split[0] == "CH": pass else: self._window.open_dialog( "Message incompréhensible", "Le message suivant n'a pas pu être décodé : {}".format( message), type="warning") print("Error : message '%s' could not be decoded" % message) def encode_message(self, **kwargs): """ :param kwargs: key 'action' related to agreed values: LA - AE - LO - DE - RT - RS - ME - CH with following keys 'action' : LA : use key 'recipient' AE : use key 'canceled_sale_id' and 'recipient' LO : use key 'bar_id' and 'recipient' DE : use key 'recipient' RT : use key 'drink_id' RS : no effect for the moment ME : use key 'message' CH : use key 'qteChamp' :return: """ if kwargs["action"] == "LA": # not connected bars list names = self.__database.select( "SELECT name FROM rooms WHERE connected=0") new_message = "|LA|" + ','.join([str(n[0]) for n in names]) + '|' print("Sending list of bars '{}' to client '{}'".format( new_message[3:], kwargs['recipient'])) self._socket.send_message(new_message, kwargs['recipient']) elif kwargs["action"] == "GC": names = self.__database.select( "SELECT name FROM drinks WHERE is_champagne=1") rooms = self.__database.select( "SELECT name FROM rooms WHERE is_bar=1") new_message = "|GC|" + ','.join([str(n[0]) for n in names]) + '|' + ','.join( [str(n[0]) for n in rooms]) + '|' self._socket.send_message(new_message, kwargs['recipient']) elif kwargs["action"] == "AE": # inform that one sale was canceled self._socket.send_message( "|%s|%s|" % (kwargs["action"], kwargs["canceled_sale_id"]), kwargs["recipient"]) elif kwargs["action"] == "LO": # available drinks list # Fetching and sending the list of drinks drinks = self.__database.select( "SELECT drinks.id, drinks.name, drinks.by_bottle FROM stocks JOIN drinks ON stocks.drink=drinks.id WHERE stocks.room='{}'" .format(kwargs['client_id'])) print("Query returned '{}'".format(drinks)) print("Sending list message '{}'".format(','.join([ str(d[0]) + ':' + str(d[1]) + ':' + str(d[2]) for d in drinks ]))) self._socket.send_message( "|LO|" + ','.join([ str(d[0]) + ':' + str(d[1]) + ':' + str(d[2]) for d in drinks ]) + '|', kwargs['recipient']) elif kwargs['action'] == "LE": # available food # Fetching and sending the list of food food = self.__database.select("SELECT id, name FROM food") self._socket.send_message( "|LE|" + ','.join([str(f[0]) + ':' + str(f[1]) for f in food]) + '|', kwargs['recipient']) elif kwargs["action"] == "RT": # refuel in progress # Getting the id of the drink drink = "" resp = self.__database.select( "SELECT id FROM drinks WHERE name='{}'".format( kwargs['drink'])) if len(resp) == 1: drink = resp[0][0] # Getting the ip of the recipient ip = "" resp = self.__database.select( "SELECT ip FROM rooms WHERE name='{}' AND connected=1".format( kwargs['recipient'])) if len(resp) > 0: ip = resp[-1][0] if drink and ip: # Both drink id and ip found self._socket.send_message( "|RT|{}|{}|".format(drink, kwargs['quantity']), ip) elif kwargs["action"] == "RS": # Restal self._socket.send_message("|RS|{}|{}|{}|".format( kwargs['bar'], kwargs['food_id'], kwargs['quantity']), recipient=kwargs['recipient']) elif kwargs["action"] == "ME": # Message by the server print("Encoding message '{}'".format(kwargs["message"])) # Getting the server's name name = "" resp = self.__database.select( "SELECT name FROM rooms WHERE ip='{}' AND connected=1".format( self._socket.get_address().toString())) if len(resp) > 0: # At least one match was found name = resp[-1][0] # By default we select the last one message = "|ME|" + name + '|' + kwargs[ "message"] + '|' # Rewriting the message else: message = "|ME|" + kwargs["message"] + '|' print("Message became '{}'".format(message)) # Checking for the receiver dests = [ word[1:] for word in kwargs["message"].split(' ') if word.startswith('@') ] # Words starting with '@' in the message ips = [] for d in dests: resp = self.__database.select( "SELECT ip FROM rooms WHERE name='{}'".format(d.lower())) if len(resp) > 0: # At least one match found ips.append(resp[-1][0]) print("Sending message '{}' to ips '{}'.".format(message, ips)) # Sending the message if ips: # Some specific receivers were found # Fetching CDF ip resp = self.__database.select( "SELECT ip FROM rooms WHERE name='cdf'") if len(resp) > 0 and resp[-1][0] not in ips: ips.append(resp[-1][0]) for ip in ips: self._socket.send_message(message, ip) # No need to send the message to himself else: self._socket.send_message(message) # Sending the message to the UI if name: infos = (kwargs["message"], name) else: infos = (kwargs["message"], ) self.message_received.emit(infos) # if kwargs["message"][0] == "@": # recipient = kwargs["message"] # for c in kwargs["message"][1:]: # if c != " ": # recipient += c # else: # break # Not the best practice # # Fetch recipient ip from database # resp = self.__database.select("SELECT ip FROM rooms WHERE name={}".format(recipient.lower())) # if len(resp) == 0: # No result # print("No room matching recipient '{}'.".format(recipient)) # self._socket.send_message("ME|{}".format(kwargs["message"])) # else: # At least one room was found # ip = resp[-1][0] # We send the message to the last one # self._socket.send_message("ME|%s" % (kwargs["message"]), recipient=ip) # else: # No recipient provided # self._socket.send_message("ME|{}".format(kwargs["message"])) # self.message_received.emit(kwargs["message"]) elif kwargs["action"] == "UR": # Urgent-Message by the server print("Encoding message '{}'".format(kwargs["message"])) # Getting the server's name name = "" resp = self.__database.select( "SELECT name FROM rooms WHERE ip='{}' AND connected=1".format( self._socket.get_address().toString())) if len(resp) > 0: # At least one match was found name = resp[-1][0] # By default we select the last one message = "|UR|" + name + '|' + kwargs[ "message"] + '|' # Rewriting the message else: message = "|UR|" + kwargs["message"] + '|' print("Message became '{}'".format(message)) # Checking for the receiver dests = [ word[1:] for word in kwargs["message"].split(' ') if word.startswith('@') ] # Words starting with '@' in the message ips = [] for d in dests: resp = self.__database.select( "SELECT ip FROM rooms WHERE name='{}'".format(d.lower())) if len(resp) > 0: # At least one match found ips.append(resp[-1][0]) print("Sending message '{}' to ips '{}'.".format(message, ips)) # Sending the message if ips: # Some specific receivers were found # Fetching CDF ip resp = self.__database.select( "SELECT ip FROM rooms WHERE name='cdf'") if len(resp) > 0 and resp[-1][0] not in ips: ips.append(resp[-1][0]) for ip in ips: self._socket.send_message(message, ip) # No need to send the message to himself else: self._socket.send_message(message) # Sending the message to the UI if name: infos = (kwargs["message"], name) else: infos = (kwargs["message"], ) self.urgent_message_received.emit(infos) elif kwargs["action"] == "VE": # Confirmation of sales try: message = "|VE|" + str(kwargs["id"]) + '|' + str( kwargs["drink"]) + '|' + str(kwargs["quantity"]) + '|' except KeyError: print( "Unable to send a confirmation of sales: a parameter is missing." ) else: try: self._socket.send_message(message, kwargs["ip"]) except KeyError: print("The 'ip' argument must be specified.") elif kwargs['action'] == "RE": self._socket.send_message("|RE|{}|".format(kwargs['id_refuel']), kwargs['recipient']) elif kwargs["action"] == "CH": # Champagne self._socket.send_message("|CH|{}|".format(kwargs["qteChamp"])) else: self._window.open_dialog( "Impossible d'envoyer un message", "Le message suivant n'a pas pu être envoyé car mal encodé : {}" .format(kwargs), type="warning") print("Error during encoding \n arguments : %s" % kwargs)
class ClientApp(App): """Class defining all methods necessary to the client app.""" refuelling_in_progress = pyqtSignal(str, int) def __init__(self): """Constructor.""" self.__tcpSocket = QTcpSocket() # print("client descriptor", int(self.__tcpSocket.socketDescriptor())) # print("set descriptor ?",self.__tcpSocket.setSocketDescriptor(122315546)) # Why?? Just Why? # self.blockSize = 0 # ? self.__drinks = {} for k in [ '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '.', '/', '*', '-', '+' ]: self.__drinks[k] = None App.__init__(self) # Used at the end because it launches the app def _open_window(self): """Method replacing the App one's to launch the user interface.""" # Creating the window self._window = Window(self, Locations.BAR) def _connect_signals(self): """Method used to connect the signals.""" App._connect_signals(self) # Connecting socket signals self.__tcpSocket.error.connect(self.__display_error) self.__tcpSocket.readyRead.connect(self.__read_message) # Connecting the socket when requested self._window.set_connection_infos.connect( lambda x: self.connect_to_server(*x)) # Connecting the bar names request self._window.get_bar_names.connect( lambda: self.encode_message(action="LA")) # Setting the bar name when requested self._window.set_bar_name.connect(self.__set_name) # Connecting the preferences self._window.ask_preferences.connect( lambda: self.fill_preferences.emit((self.__tcpSocket.peerAddress( ).toString(), self.__tcpSocket.peerPort()), self._name)) # Connecting the send message signal self._window.send_message.connect( lambda x: self.encode_message(action="ME", message=x)) # Connecting the send urgent message signal self._window.send_message_urgent.connect( lambda x: self.encode_message(action="UR", message=x)) # Connecting the drink orders self._window.order_drink.connect(self.__order_drink) # Connecting the cancellation of an order self._window.cancel_order.connect( lambda x: print("Cancelling sale '{}'".format(x))) self._window.cancel_order.connect( lambda x: self.encode_message(action="AR", sale_id=x)) # Connecting the refuel complete signal self._window.refuel_completed.connect( lambda d, q, i: self.encode_message( action="RE", key=d, quantity=q, id=i)) # Connecting for the connection dialog self._window.request_connection_infos.connect( lambda: self.open_connection_dialog.emit([[ self.__tcpSocket.peerAddress().toString(), self.__tcpSocket.state() == QAbstractSocket.ConnectedState ]])) def __display_error(self, socket_error): """Method displaying the error message from the socket.""" if socket_error == QAbstractSocket.RemoteHostClosedError: self._window.open_dialog("Serveur déconnecté", "Le serveur s'est déconnecté !") # Add signal to be emitted that pops up a dialog window elif socket_error == QAbstractSocket.OperationError: # Raised when the socket already is connected pass else: self._window.open_dialog( "Erreur de connection", "L'erreur suivante est survenue : {}.".format( self.__tcpSocket.errorString()), type="error") def __read_message(self): """Method reading the messages received by the socket.""" # instr = QDataStream(self.__tcpSocket) # instr.setVersion(QDataStream.Qt_5_0) # if self.blockSize == 0: # if self.__tcpSocket.bytesAvailable() < 2: # return # self.blockSize = instr.readUInt16() # if self.__tcpSocket.bytesAvailable() < self.blockSize: # return # # Print response to terminal, we could use it anywhere else we wanted. # message = str(instr.readString(), encoding='utf8') # print("New message received : '{}'.".format(message)) # self.decode_message(message) instr = self.__tcpSocket.readAll() message = str(instr, encoding="utf8") self.decode_message(message) def __order_drink(self, key, is_restal): """Method handling the order of drink or food.""" if not is_restal: self.encode_message(action="VE", drink_id=self.__drinks[key][0], quantity=self.__drinks[key][1]) else: self.encode_message(action="RS", food_id=self.__drinks[key][0], quantity=self.__drinks[key][1]) def __set_name(self, name): """Method called to set the client's name.""" self._name = name self._window.chat_panel.place_name = name self.encode_message(action="NO", selected_name=name) def connect_to_server(self, host=HOST, port=PORT): """Method connecting the client to a given server.""" # HOST = server.ipAddress # PORT = int(server.port) # self.tcpSocket.disconnectFromHost() # self.tcpSocket.waitForDisconnected () # print(HOST, PORT) # self.__tcpSocket.connectToHost(host, port, QIODevice.ReadWrite) self.__tcpSocket.connectToHost(host, port, QIODevice.ReadWrite) if self.__tcpSocket.waitForConnected(5000): print('Client connected to server.') self.connection_established.emit((host, port)) else: self._window.open_dialog( "Impossible de se connecter au serveur !", "Vérifiez que les paramètres que vous avez entré sont corrects et que le serveur est en fonctionnement.", type="warning") print('Unable to connect...') def send_message(self, message): """Method sending a message to the connected server.""" self.__tcpSocket.write(message.encode('utf8')) def decode_message(self, message): """Method decoding the message received from the server.""" print("Decoding message '{}'".format(message)) message_split = message[1:-1].split('||') if len(message_split) > 1: # Several messages are queued for m in message_split: self.decode_message('|' + m + '|') return else: message = message_split[0] message_split = message.split('|') if message_split[0] == 'LA': list_bars = message_split[1].split(',') self.send_bar_names.emit(list_bars) # Sending the list to the UI elif message_split[0] == 'AE': self._window.remove_cancelled_order(message_split[1]) elif message_split[0] == 'UR': print("New message received : '{}'".format(message)) if len(message_split) == 3: # Author was found infos = (message_split[2], message_split[1]) elif len(message_split) == 2: # No author infos = (message_split[1], ) try: self.urgent_message_received.emit(infos) except UnboundLocalError: self._window.open_dialog( "Message de chat incompréhensible", "Le message de chat suivant n'a pas pu être décodé : {}". format(message), type="warning") elif message_split[0] == 'ME': print("New message received : '{}'".format(message)) if len(message_split) == 3: # Author was found infos = (message_split[2], message_split[1]) elif len(message_split) == 2: # No author infos = (message_split[1], ) try: self.message_received.emit(infos) except UnboundLocalError: self._window.open_dialog( "Message de chat incompréhensible", "Le message de chat suivant n'a pas pu être décodé : {}". format(message), type="warning") elif message_split[ 0] == 'LO': # Message is respecting convention 'LO|code1:drink1,code2:drink2...' self.name_set.emit() # Warning the UI about the name being set if message_split[1]: tuples = message_split[1].split(',') drinks = [tuple(t.split(':')) for t in tuples] print('drinks', drinks) drinkss = [] for j, (i, d, b) in enumerate(drinks): drinkss.append((i, d, 1)) if b == '1': drinkss.append((i, "Bouteille\n" + d, 7)) names = {} if len(drinkss) <= 15: # We got enough keys for k, d in zip(self.__drinks.keys(), drinkss): self.__drinks[k] = (d[0], d[2]) names[k] = d[1] else: print( "Unable to select keys for drinks '{}': too many drinks ({})" .format(drinkss, len(drinks))) self._window.open_dialog( "Trop de boissons.", "Impossible de sélectionner des clés pour les boissons '{}': trop de boissons ({})" .format(drinkss, len(drinkss)), type="error") self._window.set_drinks(names) else: self._window.open_dialog( "Aucune boissons associées à ce bar.", "Aucune boisson n'a été trouvée associée à ce bar. Essayez un autre bar ou contactez vos ZiT&lek'ss." ) elif message_split[0] == 'LE': if message_split[1]: tuples = message_split[1].split(',') food = [tuple(t.split(':')) for t in tuples] available_keys = [] for k, d in self.__drinks.items(): if not d: available_keys.append(k) names = {} if len(food) <= len(available_keys): # We got enough keys for k, d in zip(available_keys, food): self.__drinks[k] = (d[0], 1) names[k] = d[1] else: print( "Unable to select keys for food '{}': too many items ({})" .format(food, len(food))) self._window.open_dialog( "Trop de plats.", "Impossible de sélectionner des clés pour les plats '{}': plats trop nombreux ({})" .format(food, len(food)), type="error") self._window.set_drinks(names, is_restal=True) else: self._window.open_dialog( "Aucune boissons associées à ce bar.", "Aucune boisson n'a été trouvée associée à ce bar. Essayez un autre bar ou contactez vos ZiT&lek'ss." ) elif message_split[0] == "RE": id_refuel = message_split[1] self._window.remove_refuel_notif(id_refuel) elif message_split[0] == "RT": key = "" for k, d, in self.__drinks.items(): if d[0] == message_split[1]: key = k break self.refuelling_in_progress.emit(key, int(message_split[2])) elif message_split[0] == "VE": # Confirmation of sale try: id, drink, quantity = message[3:].split('|') except ValueError: print("Unable to add order from message '{}'".format(message)) else: key = "" for k, d, in self.__drinks.items(): if d[0] == drink: key = k break self._window.add_order(id, key, quantity) elif message_split[0] == "CH": pass elif message_split[0] == 'RS': pass else: self._window.open_dialog( "Message du serveur incompréhensible", "Le message suivant n'a pas pu être décodé : {}".format( message), type="warning") print("Error : message '{}' could not be decoded".format(message)) def encode_message(self, **kwargs): """Method encoding a message to be sent to the server. :param kwargs: key 'action' related to agreed values: NO - VE - AR - ME - DE - RE - RS - LA with following keys 'action' : NO : use key 'selected_name' to send the bar's selected name VE : use key 'drink_id' and 'quantity' AR : use key 'sale_id' ME : use key 'message' DE : use no key RE : use key 'drink' RS : no effect for the moment LA : no key """ if kwargs["action"] == "NO": self.send_message("|%s|%s|" % (kwargs["action"], kwargs["selected_name"])) elif kwargs["action"] == "VE": if kwargs["drink_id"]: self.send_message("|VE|{}|{}|".format(kwargs["drink_id"], kwargs["quantity"])) else: print("Unknown drink") elif kwargs["action"] == "AR": if kwargs["sale_id"]: self.send_message("|%s|%s|" % (kwargs["action"], kwargs["sale_id"])) else: print("You must specify a sale to be cancelled.") elif kwargs["action"] == "ME": print("Sending tchat message '{}'".format(kwargs['message'])) self.send_message("|%s|%s|" % (kwargs["action"], kwargs["message"])) elif kwargs["action"] == "UR": print("Sending tchat message '{}'".format(kwargs['message'])) self.send_message("|%s|%s|" % (kwargs["action"], kwargs["message"])) elif kwargs["action"] == "RE": try: drink_id = self.__drinks[kwargs["key"]][0] except KeyError: print( "Unable to send confirmation of refuel because drink at key '{}' could not be identified." .format(kwargs["key"])) else: self.send_message("|RE|{}|{}|{}|".format( drink_id, kwargs['quantity'], kwargs['id'])) elif kwargs["action"] == "RS": print("Asking server for food '{}'".format(kwargs['food_id'])) self.send_message("|RS|{}|{}|".format(kwargs['food_id'], kwargs['quantity'])) elif kwargs["action"] == "LA": self.send_message("|LA|") else: self._window.open_dialog( "Impossible d'envoyer un message", "Le message suivant n'a pas pu être envoyé car mal encodé : {}" .format(kwargs), type="warning") print("Error during encoding with arguments : %s" % kwargs)