Exemplo n.º 1
0
    def __init__(self,
        meals: [],
        form: int,
        meal_codes: [] = [],
        timestamp: int = 0,
        id: int = -1,
        state: int = 0,
        price: float = 0.0
    ):
        # Properties
        self._timestamp = timestamp
        if timestamp == 0:
            self._timestamp: int = int(time.time())

        self._id: int = id
        self._state: int = state
        self._form: int = form
        self._meals: [] = meals
        self._meal_codes: [] = meal_codes
        self._price = price

        self._is_id_set: bool = False

        if self._id != -1:
            self._is_id_set = True

        # Events
        self._state_changed_event: Event = Event()
Exemplo n.º 2
0
    def __init__(self, parent, height: int, background="white"):
        super().__init__(master=parent, cnf={}, background=background)

        self._on_height_changed_event: Event = Event()

        self._height = height
        self._initial_height = height
Exemplo n.º 3
0
    def __init__(self, root):
        # Timer to trigger the main periodical thread
        self._main_cycle_timer: Timer
        self._secondary_cycle_timer: Timer

        # Event that is triggered on every execution of the main cycle
        self._on_cycle_event: Event = Event()
        
        # Initializing the timer handler
        self.timer_handler = TimerHandler(root_object=root)

        self._notification_service = NotificationService(root=root)

        # Event that is triggered, when the MealsHandler established the connection to the database
        self._db_connection_ready_event: Event = Event()

        # Event that is triggered, when the content of the body changes
        self._body_content_changed_event: Event = Event()

        # Holds the value of the currently displayed time
        self._current_time = "<TIME>"
Exemplo n.º 4
0
    def __init__(self,
                 on_page_changed,
                 items_per_page: int,
                 numbering_mode: int = 0):
        self.on_page_changed_event: Event = Event()
        self.on_page_changed_event.add(on_page_changed)

        self._current_items = []

        self.items_per_page = items_per_page
        self.page_index = 0
        self.max_pages = 1

        self.numbering_mode = numbering_mode
Exemplo n.º 5
0
class OrdersService():
    TIMESTAMP_FORMAT="%H:%M:%S"
    TIMESTAMP_FORMAT_EXTENDED="%d.%m.%Y, %H:%M:%S"

    COLUMN_NAMES=None

    initialized = False
    counter = 0

    # Events
    on_order_created_event: Event = Event()
    on_orders_changed: Event = Event()

    def __init__(self):
        # TODO: ?
        OrdersService.initialized = True

    @staticmethod
    def convert_form(form: int) -> str:
        if form < 0 or form >= len(REFS.ORDER_FORMS):
            raise RuntimeError(f"Form index {form} is invalid for an order")

        return REFS.ORDER_FORMS[form]

    @staticmethod
    def convert_status(state: int) -> str:
        if state < 0 or state >= len(REFS.ORDER_STATES):
            raise RuntimeError(f"State index {state} is invalid for an order")

        return REFS.ORDER_STATES[state]

    @staticmethod
    def convert_active(active: str) -> str:
        if active != REFS.ORDERS_TABLE_ACTIVE_TRUE and active != REFS.ORDERS_TABLE_ACTIVE_FALSE:
            raise RuntimeError(f"Active character is invalid, must be {REFS.ORDERS_TABLE_ACTIVE_TRUE} or {REFS.ORDERS_TABLE_ACTIVE_FALSE}")

        if active == REFS.ORDERS_TABLE_ACTIVE_TRUE:
            return REFS.ORDERS_TABLE_ACTIVE_TRUE_GER
        elif active == REFS.ORDERS_TABLE_ACTIVE_FALSE:
            return REFS.ORDERS_TABLE_ACTIVE_FALSE_GER

    @staticmethod
    def convert_timestamp(timestamp: int, extended: bool = False) -> str:
        dateTimeObj = datetime.fromtimestamp(timestamp)

        tformat = OrdersService.TIMESTAMP_FORMAT
        if extended:
            tformat = OrdersService.TIMESTAMP_FORMAT_EXTENDED
        
        return dateTimeObj.strftime(tformat)

    @staticmethod
    def get_column_names() -> []:
        if OrdersService.COLUMN_NAMES == None:
            OrdersService.COLUMN_NAMES = DatabaseHandler.get_table_information(table_name=REFS.ORDERS_TABLE_NAME)[1]
            
        return OrdersService.COLUMN_NAMES

    @staticmethod
    def convert_to_order_object(db_content: []) -> Order:
        """ Converts the db content array to an order object
        """
        col_names = OrdersService.get_column_names()

        id_i = col_names.index(REFS.ORDERS_TABLE_ID)
        id_db = db_content[id_i]

        time_i = col_names.index(REFS.ORDERS_TABLE_TIMESTAMP)
        time_db = db_content[time_i]

        form_i = col_names.index(REFS.ORDERS_TABLE_FORM)
        form_db = db_content[form_i]

        state_i = col_names.index(REFS.ORDERS_TABLE_STATE)
        state_db = db_content[state_i]

        price_i = col_names.index(REFS.ORDERS_TABLE_PRICE)
        price_db = db_content[price_i]

        meals_i = col_names.index(REFS.ORDERS_TABLE_MEALS)
        meals_db = db_content[meals_i]
        meals_list = []
        for meal_code in meals_db.split(REFS.MEAL_CODES_DELIMITER):
            meals_list.append(Meal.get_meal_from_code(meal_code))

        order = Order(
            meals=meals_list,
            form=form_db,
            timestamp=time_db,
            id=id_db,
            state=state_db,
            price=price_db
        )

        return order

    @staticmethod
    def delete_from_table(condition: str = "", confirm: bool = True) -> bool:
        if not OrdersService.initialized:
            return None

        confirmed = True

        if confirm:
            confirmed = messagebox.askyesno(
                title="Clear order history",
                message="Are you sure you want to clear the order history? This will delete all inactive orders.\n\nNOTE: This will not reset the counter for the order number!",
                default='no'
            )

        if confirmed:
            DatabaseHandler.delete_from_table(
                table_name=REFS.ORDERS_TABLE_NAME,
                row_filter=condition
            )
        else:
            print(f"Truncating '{REFS.ORDERS_TABLE_NAME}' table canceled.")
            
        return confirmed

    @staticmethod
    def truncate_table(confirm: bool = True) -> bool:
        if not OrdersService.initialized:
            return None

        confirmed = True

        if confirm:
            confirmed = messagebox.askyesno(
                title="Clear order history",
                message="Are you sure you want to clear the whole order history?\n\nThis will delete every order from the database and reset the order counter for the order nubmer.",
                default='no'
            )

        if confirmed:
            DatabaseHandler.truncate_table(table_name=REFS.ORDERS_TABLE_NAME)
        else:
            print(f"Truncating '{REFS.ORDERS_TABLE_NAME}' table canceled.")
            
        return confirmed

    @staticmethod
    def get_orders(order_by: str = "", row_filter: str = "", columns: [] = []) -> []:
        if not OrdersService.initialized:
            return None

        return DatabaseHandler.select_from_table(
            table_name=REFS.ORDERS_TABLE_NAME,
            order_by=order_by,
            row_filter=row_filter,
            columns=columns
        )

    @staticmethod
    def update_order(order: Order, active: bool = None):
        if not OrdersService.initialized or order == None:
            return None

        # First: get the order's current data
        result = OrdersService.get_orders(
            row_filter=f"{REFS.ORDERS_TABLE_ID}={order.id}"
        )

        if result == None or len(result) == 0:
            raise RuntimeError("The given order can not be changed because it's not in the database.")

        old_order = OrdersService.convert_to_order_object(result[0])

        # Only if the new and old order state are both OPEN
        if order.state == old_order.state and order.state == REFS.OPEN:
            # Check if the order was changed in the form or the meals list
            if order.form != old_order.form or order.meals != old_order.meals: # TODO: Does this actually work?
                # If so: set its state to CHANGED
                order.state = REFS.CHANGED

        columns = [
            REFS.ORDERS_TABLE_FORM,
            REFS.ORDERS_TABLE_STATE,
            REFS.ORDERS_TABLE_MEALS
        ]

        meal_codes = REFS.MEAL_CODES_DELIMITER.join(order.meal_codes)

        values = [
            order.form,
            order.state,
            f"'{meal_codes}'"
        ]

        if active != None:
            active_label = REFS.ORDERS_TABLE_ACTIVE_FALSE

            if active == True:
                active_label = REFS.ORDERS_TABLE_ACTIVE_TRUE

            columns.append(REFS.ORDERS_TABLE_ACTIVE)
            values.append(f"'{active_label}'")
        
        DatabaseHandler.update_table(
            table_name=REFS.ORDERS_TABLE_NAME,
            columns=columns,
            values=values,
            condition=f"{REFS.ORDERS_TABLE_ID}={order.id}"
        )

        print("##### UPDATED DATABASE WITH ORDER CHANGE, order state, id = ", order.state, order.id)

        if active != False:
            OrdersService.handle_timer(order)

    @staticmethod
    def handle_timer(order):
        if order.state == REFS.PREPARED or order.state == REFS.CANCELED:
            print("## Creating timer")
            OrdersService._create_timer(order)
        else:
            print("## Stopping timer if existant")
            OrdersService._stop_timer_if_existant(order)

    @staticmethod
    def create_new_order(meals_list: [], order_form: int) -> Order:
        if not OrdersService.initialized or meals_list == None or order_form == None:
            return None

        # Create Order object with the given information
        #   meals:      given from the arguments of this function
        #   form:       given from the arguments of this function
        #   timestamp:  set in the constructor of the order class
        #   id:         later set after insertion in the db table
        #   state:      reset in the constructor to value of zero
        new_order: Order = Order(meals=meals_list, form=order_form)

        # Defining sql query contents
        columns = [
            REFS.ORDERS_TABLE_TIMESTAMP,
            REFS.ORDERS_TABLE_FORM,
            REFS.ORDERS_TABLE_PRICE,
            REFS.ORDERS_TABLE_MEALS
        ]

        # Joins the individual meal codes into one string, separated by a pipe symbol
        meals_code = REFS.MEAL_CODES_DELIMITER.join(new_order.meal_codes)

        # Defines whether the order is active or not: Y = Yes, it is active, since it's new
        # active = REFS.ORDERS_TABLE_ACTIVE_TRUE
        # ACTIVE DEFAULTS TO 'Y' IF NOT SET! -> CAN BE LEFT OUT

        order_price = new_order.calculate_price()

        values = [
            new_order.timestamp,
            new_order.form,
            # new_order.state, # -> Defaults to 0 in the database, so not necessary here
            # f"'{active}'",   # -> Defaults to 'Y' in the database, so not necessary here
            order_price,
            f"'{meals_code}'"
        ]

        # Insert new order into the database table
        DatabaseHandler.insert_into_table(REFS.ORDERS_TABLE_NAME, columns, values)

        # Get the highest id value of database
        highest_id = DatabaseHandler.select_from_table(
            table_name=REFS.ORDERS_TABLE_NAME,
            columns=[f"MAX({REFS.ORDERS_TABLE_ID})"]
        )

        # Set the order's internal id property to the id value of the database
        new_order.id = int(highest_id[0][0])

        # Increment counter of added orders
        OrdersService.counter = OrdersService.counter + 1

        # Trigger event to inform others of a change in the orders-list
        OrdersService.on_order_created_event(new_order)

        # Return the newly created order object with the updated information
        return new_order

    @staticmethod
    def _create_timer(order):
        """ Creates and starts a new timer for the given order.
        """
        OrderTimerPair(order=order).start_timer()

    @staticmethod
    def _stop_timer_if_existant(order):
        """ Checks if there is a timer running for the given order.
        If so: stop the timer and delete it from the registry.
        """
        pair = OrderTimerPair.get_order_timer_pair(order)

        if pair == None:
            return
            
        pair.stop_timer()
Exemplo n.º 6
0
    def __init__(self, parent, background, shown: bool = False):
        super().__init__(
            master=parent,
            cnf={},
            background=background
        )

        CurrentOrderView.NUM_COLUMNS = CurrentOrderView.NUM_COLUMNS - REFS.MOBILE

        self._meal_number_changed_event: Event = Event()
        self._meal_number_changed_event.add(self._update_price)

        self._meals_frame = Frame(
            master=self,
            background='#F4F4F4'
        )
        self._meals_frame.pack(side=LEFT, fill='both', expand=1)

        self._info_frame = Frame(
            master=self,
            background=background
        )
        self._info_frame.pack(side=RIGHT, fill='y')

        form_radiobutton_group = RadioButtonGroup()

        self._order_form = REFS.DEFAULT_FORM

        def _form_button_pressed(button_form):
            self._order_form = button_form

        for idx, form in enumerate(REFS.ORDER_FORMS):
            command = partial(_form_button_pressed, idx)

            form_radio_button = RadioButton(
                master=self._info_frame,
                text=form,
                group=form_radiobutton_group,
                highlight=REFS.LIGHT_CYAN,
                font=Fonts.medium(),
                command=command,
                initial_state=self._order_form,
                height=3
            )
            form_radio_button.pack(side=TOP, fill='x', padx=10, pady=10)

        self._price_label = Label(
            master=self._info_frame,
            text='<price>',
            font=Fonts.medium(),
            background=background
        )
        self._price_label.pack(side=BOTTOM, fill='x', padx=10, pady=10)

        # Assure that the frame won't resize with the contained widgets
        self.pack_propagate(0)
        self.grid_propagate(0)

        # Private members
        self._is_hidden = shown
        self._background = background

        # Initialize visibility-state
        if not shown:
            self.hide_view()
        else:
            self.show_view()

        self._added_meal_tiles = []
        self._update_price()
Exemplo n.º 7
0
class OrderMessagingService(Messenger):
    """ Provides methods for interaction with the NetworkHandler, in order
    to act, whenever a specific message is coming in or to construct the
    message body for a new message about ot be sent.
    """

    # TODO ###################################################
    # TODO ###################################################
    # TODO ###################################################
    # TODO ###################################################
    # TODO ###################################################
    # TODO ###################################################

    initialized = False

    AUTO_REFRESH_ENABLED = False

    on_database_changed_event: Event = Event()

    def __init__(self):
        super().__init__(self)

        OrderMessagingService.IDENTIFIER = self.identifier
        OrderMessagingService.initialized = True

        timer_callback = partial(OrderMessagingService.notify_of_changes, None,
                                 REFS.ORDER_CHANGED_PREFIX)

        OrdersService.on_orders_changed.add(timer_callback)

    def process_message(self, message: str):
        """ Gets called, whenever the network handler receives a message,
        that is for this specific service.

        message: contains only the main body; no service- and msg-id
        """
        print("Message to process:", message)

        # Message says: DB content has changed
        if message.startswith(REFS.DB_CHANGED_PREFIX):
            if not message[1:].startswith(REFS.SILENT_PREFIX):
                order_id = message[2:]

                if order_id == "0":
                    if OrderMessagingService.AUTO_REFRESH_ENABLED:
                        OrderMessagingService.on_database_changed_event()
                    return

                toast_title = "DB CHANGED"
                toast_text = "<text>"

                # More precise: a new order has been created
                if message[1:].startswith(REFS.ORDER_CREATED_PREFIX):
                    order_timestamp = OrdersService.get_orders(
                        row_filter=f"{REFS.ORDERS_TABLE_ID}={order_id}",
                        columns=[f"{REFS.ORDERS_TABLE_TIMESTAMP}"])[0][0]
                    order_timestamp = OrdersService.convert_timestamp(
                        order_timestamp)

                    toast_title = REFS.ORDER_CREATED_TOAST[0]
                    toast_text = REFS.ORDER_CREATED_TOAST[1].format(
                        order_id, order_timestamp)
                # More precise: a new order has been changed
                elif message[1:].startswith(REFS.ORDER_CHANGED_PREFIX):
                    # First: get the order's current data
                    result = OrdersService.get_orders(
                        row_filter=f"{REFS.ORDERS_TABLE_ID}={order_id}")

                    if result == None or len(result) == 0:
                        raise RuntimeError(
                            "The given order can not be changed because it's not in the database."
                        )

                    changed_order = OrdersService.convert_to_order_object(
                        result[0])

                    # order_details = OrdersService.get_orders(
                    #     row_filter=f"{REFS.ORDERS_TABLE_ID}={order_id}",
                    #     columns=[f"{REFS.ORDERS_TABLE_TIMESTAMP}", f"{REFS.ORDERS_TABLE_STATE}"]
                    # )[0]
                    order_timestamp = OrdersService.convert_timestamp(
                        changed_order.timestamp)

                    # order_state = int(order_details[1])
                    order_change = f"Status > {REFS.ORDER_STATES[changed_order.state]}"

                    # OrdersService.handle_timer(changed_order)

                    toast_title = REFS.ORDER_CHANGED_TOAST[0]
                    toast_text = REFS.ORDER_CHANGED_TOAST[1].format(
                        order_id, order_timestamp, order_change)

                #NotificationService.show_toast(
                #    title=toast_title,
                #    text=toast_text,
                #    keep_alive=False
                #)
            else:  # SILENT prefix
                if message[2:].startswith(REFS.DELETING_NOT_CONFIRMED):
                    messagebox.showwarning(
                        title="Delete response",
                        message="Deleting from table has been denied.")
                elif message[2:].startswith(REFS.DELETING_CONFIRMED):
                    print("Deleting worked")

            # Fire event to inform subscribed classes, like views
            OrderMessagingService.on_database_changed_event()
        # Message says: Request to change given order in DB
        elif message.startswith(
                REFS.ORDER_CHANGE_REQUEST_PREFIX) and REFS.MAIN_STATION:
            order_id = message[2:-1]
            change = message[-1:]

            print("Order id:", order_id)
            print("Change to:", change)

            # First: get the order's current data
            result = OrdersService.get_orders(
                row_filter=f"{REFS.ORDERS_TABLE_ID}={order_id}")

            if result == None or len(result) == 0:
                raise RuntimeError(
                    "The given order can not be changed because it's not in the database."
                )

            old_order = OrdersService.convert_to_order_object(result[0])

            if message[1:].startswith(REFS.ORDER_STATUS_CHANGED_PREFIX):
                old_order.state = int(change)
            elif message[1:].startswith(REFS.ORDER_TYPE_CHANGED_PREFIX):
                old_order.form = int(change)

            OrdersService.update_order(old_order, active=True)

            ## Send Message to other station about order creation
            #OrderMessagingService.notify_of_changes(
            #    changed_order=old_order,
            #    prefix=REFS.ORDER_CHANGED_PREFIX
            #)

            # Fire event to inform subscribed classes, like views
            OrderMessagingService.on_database_changed_event()
        # Message says: Request to delete rows in orders table
        elif message.startswith(
                REFS.CLEAR_TABLE_REQUEST_PREFIX) and REFS.MAIN_STATION:
            clear_type = message[1:]
            result = False

            if clear_type == REFS.DELETE_INACTIVE_PREFIX:
                result = OrdersService.delete_from_table(
                    condition=
                    f"{REFS.ORDERS_TABLE_ACTIVE}='{REFS.ORDERS_TABLE_ACTIVE_FALSE}'",
                    confirm=True)
            elif clear_type == REFS.DELETE_ALL_PREFIX:
                result = OrdersService.truncate_table()

            addinfo = REFS.DELETING_NOT_CONFIRMED

            if result:
                # Fire event to inform subscribed classes, like views
                OrderMessagingService.on_database_changed_event()
                addinfo = REFS.DELETING_CONFIRMED

            OrderMessagingService.notify_of_changes(changed_order=None,
                                                    prefix=REFS.SILENT_PREFIX,
                                                    additional_prefix=addinfo)

    @staticmethod
    def notify_of_changes(changed_order: Order,
                          prefix: str,
                          additional_prefix: str = "") -> bool:
        if not NetworkHandler.CONNECTION_READY:
            return False

        order_id = "0"

        if changed_order != None:
            order_id = changed_order.id

        # CONSTRUCT MESSAGE BODY
        message_body = f"{REFS.DB_CHANGED_PREFIX}" \
            f"{prefix}" \
            f"{order_id}" \
            f"{additional_prefix}"

        message_body = Messenger.attach_service_id(
            service_id=OrderMessagingService.IDENTIFIER, message=message_body)

        new_thread = CustomThread(
            2, "MessangerThread-2",
            partial(OrderMessagingService._send, message_body))
        new_thread.start()

        return True

    @staticmethod
    def request_order_update(order: Order, state: int = -1, form: int = -1):
        if not NetworkHandler.CONNECTION_READY:
            return False

        if state != -1:
            prefix = REFS.ORDER_STATUS_CHANGED_PREFIX
            change = state
        elif form != -1:
            prefix = REFS.ORDER_TYPE_CHANGED_PREFIX
            change = form
        else:
            return False

        # CONSTRUCT MESSAGE BODY
        message_body = f"{REFS.ORDER_CHANGE_REQUEST_PREFIX}" \
            f"{prefix}" \
            f"{order.id}" \
            f"{change}"

        message_body = Messenger.attach_service_id(
            service_id=OrderMessagingService.IDENTIFIER, message=message_body)

        print("Message body to send:", message_body)

        new_thread = CustomThread(
            3, "MessangerThread-3",
            partial(OrderMessagingService._send, message_body))
        new_thread.start()

        return True

    @staticmethod
    def request_table_deletion(only_inactive: bool = True) -> bool:
        if not NetworkHandler.CONNECTION_READY:
            return False

        if only_inactive:
            prefix = REFS.DELETE_INACTIVE_PREFIX
        else:
            prefix = REFS.DELETE_ALL_PREFIX

        # CONSTRUCT MESSAGE BODY
        message_body = f"{REFS.CLEAR_TABLE_REQUEST_PREFIX}" \
            f"{prefix}"

        message_body = Messenger.attach_service_id(
            service_id=OrderMessagingService.IDENTIFIER, message=message_body)

        new_thread = CustomThread(
            4, "MessangerThread-4",
            partial(OrderMessagingService._send, message_body))
        new_thread.start()

        return True

    @staticmethod
    def _send(message):
        NetworkHandler.send_with_handshake(message)
    def __init__(self,
                 parent,
                 meal,
                 index,
                 remove_meal_cb,
                 on_amount_changed_cb=None,
                 background='white'):
        super().__init__(master=parent,
                         cnf={},
                         background=background,
                         highlightbackground='#606060',
                         highlightthickness=2)

        self.padding = 15

        if REFS.MOBILE:
            self.padding = 5

        if AddedMealTile.PADDING != self.padding:
            AddedMealTile.PADDING = self.padding

        self.on_amount_changed_event: Event = Event()

        if on_amount_changed_cb != None and callable(on_amount_changed_cb):
            self.on_amount_changed_event.add(on_amount_changed_cb)

        self._meal = meal

        self.close_img = IMAGES.create(IMAGES.CLOSE)
        self.up_img = IMAGES.create(IMAGES.UP)
        self.down_img = IMAGES.create(IMAGES.DOWN)

        # TODO: Maybe as subheading to the name title we can add the last category of the meal

        #### Setup header

        std_button_width = 20 + 20 * (not REFS.MOBILE)

        self._header = Frame(master=self, background=background)
        self._header.pack(side=TOP,
                          padx=self.padding,
                          pady=self.padding,
                          fill='x')

        self._l_name = Label(master=self._header,
                             text=meal.name,
                             background=background,
                             foreground='black',
                             font=Fonts.large(bold=True))
        self._l_name.pack(side=LEFT, fill='x', expand=1)

        self._b_delete = Button(master=self._header,
                                image=self.close_img,
                                command=partial(remove_meal_cb, self),
                                width=std_button_width,
                                height=std_button_width)
        self._b_delete.pack(side=RIGHT, padx=(5, 0))

        self.body_frame = Frame(master=self, background=background)
        self.body_frame.pack(side=TOP,
                             padx=self.padding,
                             pady=(0, self.padding),
                             fill='both',
                             expand=1)

        self.left_frame = Frame(master=self.body_frame, background=background)
        self.left_frame.pack(side=LEFT, fill='both', expand=1)

        #### Counter buttons

        self.right_frame = Frame(master=self.body_frame, background=background)
        self.right_frame.pack(side=RIGHT, fill='y')

        self._b_count_up = Button(master=self.right_frame,
                                  image=self.up_img,
                                  command=partial(self.change_amount, +1),
                                  width=std_button_width,
                                  height=std_button_width * 2)
        self._b_count_up.pack(side=TOP, pady=(0, 5))

        self._b_count_down = Button(master=self.right_frame,
                                    image=self.down_img,
                                    command=partial(self.change_amount, -1),
                                    width=std_button_width,
                                    height=std_button_width * 2)
        self._b_count_down.pack(side=TOP)
        self._b_count_down.config(state="disabled")

        #### Setup size
        if len(meal.size_objects) > 0:
            size_text = f"{meal.size_objects[0].name}"

            if not REFS.MOBILE:
                size_text = f"{REFS.SIZE_LABEL}:  " + size_text

            self._l_size = Label(master=self.left_frame,
                                 text=size_text,
                                 background=background,
                                 foreground='black',
                                 font=Fonts.xxsmall(),
                                 justify='left')
            self._l_size.pack(side=TOP,
                              padx=self.padding,
                              pady=(0, self.padding),
                              anchor='nw')

        (meal_title, meal_text) = MealsService.meal_content_to_text(meal,
                                                                    indent="")

        self._l_content = Label(master=self.left_frame,
                                text=meal_text,
                                background=background,
                                foreground='black',
                                font=Fonts.xsmall(),
                                justify='left')
        self._l_content.pack(side=TOP,
                             padx=self.padding,
                             pady=(0, self.padding),
                             anchor='nw')

        # #### Setup ingredients list

        # # Only add ingredients, if there are any
        # if len(meal.ingredient_objects) > 0:
        #     self._l_ingredients = Label(
        #         master=self.left_frame,
        #         text='',
        #         background=background,
        #         foreground='black',
        #         font=Fonts.xxsmall(),
        #         justify='left'
        #     )
        #     self._l_ingredients.pack(side=TOP, padx=15, pady=(0, 15), anchor='nw')

        #     for idx,ingr in enumerate(meal.ingredients):
        #         curr_text = self._l_ingredients.cget('text')
        #         nl = '\n'
        #         if idx == len(meal.ingredients) - 1:
        #             nl = ''
        #         self._l_ingredients.config(text=f"{curr_text}- OHNE  {ingr}{nl}")

        # #### Setup extras list

        # # Only add extras, if there are any
        # if len(meal.addons) > 0:
        #     self._l_addons = Label(
        #         master=self.left_frame,
        #         text='',
        #         background=background,
        #         foreground='black',
        #         font=Fonts.xxsmall(),
        #         justify='left'
        #     )
        #     self._l_addons.pack(side=TOP, padx=15, pady=(0, 15), anchor='nw')

        #     for idx,addon in enumerate(meal.addons):
        #         curr_text = self._l_addons.cget('text')
        #         nl = '\n'
        #         if idx == len(meal.addons) - 1:
        #             nl = ''
        #         self._l_addons.config(text=f"{curr_text}+ MIT  {addon}{nl}")

        self.set_position(0, index)

        self.grid_propagate(0)
        self.pack_propagate(0)
Exemplo n.º 9
0
    def __init__(self, parent, background='white'):
        super().__init__(
            parent=parent,
            height=HistoryItem.HEIGHT,
            background=background  #'#F4F4F4'
        )

        self.meal_added_event: Event = Event()

        self.expanded = False
        self.edit_view_shown = False
        self.details_view_shown = False

        self.row_frame = Frame(master=self,
                               background=background,
                               height=HistoryItem.HEIGHT)
        self.row_frame.pack(side=TOP, fill='x')
        self.row_frame.pack_propagate(0)

        self.details_frame = Frame(master=self, background='#F4F4F4')
        self.set_details_content()

        self._undo_img = IMAGES.create(IMAGES.UNDO)
        self._add_img = IMAGES.create(IMAGES.ADD)
        self._down_img = IMAGES.create(IMAGES.DOWN)
        self._up_img = IMAGES.create(IMAGES.UP)

        self.index = Label(master=self.row_frame,
                           font=Fonts.xsmall(),
                           text="",
                           background=background,
                           width=MealsSettingsView.INDEX_WIDTH)
        self.index.pack(side=LEFT, padx=MealsSettingsView.PADDING)

        self.category = Text(master=self.row_frame,
                             font=Fonts.xsmall(),
                             background=background,
                             width=MealsSettingsView.CATEGORY_WIDTH,
                             height=1)
        self.category.pack(side=LEFT, padx=MealsSettingsView.PADDING)

        self.name = Text(master=self.row_frame,
                         font=Fonts.xsmall(bold=True),
                         background=background,
                         width=MealsSettingsView.NAME_WIDTH + 3,
                         height=1)
        self.name.pack(side=LEFT, padx=MealsSettingsView.PADDING)
        self.name_bg = self.name.cget('background')

        ##### ADD BUTTON #####

        self.add_container = Frame(master=self.row_frame,
                                   width=MealsSettingsView.DELETE_HEADER_WIDTH,
                                   height=60,
                                   bg=background)
        self.add_container.pack(side=RIGHT, padx=MealsSettingsView.PADDING)

        self.add = Button(master=self.add_container,
                          image=self._add_img,
                          command=self.add_meal_command,
                          background=CButton.GREEN)
        self.add.place(relx=0.5, rely=0.5, anchor="center")

        ##### UNDO BUTTON #####

        self.undo_container = Frame(master=self.row_frame,
                                    width=MealsSettingsView.EDIT_HEADER_WIDTH,
                                    height=60,
                                    bg=background)
        self.undo_container.pack(side=RIGHT,
                                 padx=(MealsSettingsView.PADDING, 0))

        self.undo = Button(master=self.undo_container,
                           image=self._undo_img,
                           command=self.undo_command)
        self.undo.place(relx=0.5, rely=0.5, anchor="center")

        # self.initial_button_background = self.edit.cget('background')

        # if self._order.state != REFS.OPEN and self._order.state != REFS.CHANGED:
        #     self.edit.config(state="disabled")

        ##### EXPAND BUTTON #####

        self.expand_container = Frame(
            master=self.row_frame,
            width=MealsSettingsView.EXPAND_HEADER_WIDTH,
            height=60,
            bg=background)
        self.expand_container.pack(side=RIGHT,
                                   padx=(MealsSettingsView.PADDING, 0))

        # self.expand = Button(
        #     master=self.expand_container,
        #     image=self._down_img,
        #     command=self.expand_button_command
        # )
        # self.expand.place(relx=0.5, rely=0.5, anchor="center")

        ##### BASE PRICE #####

        self.base_price = Text(master=self.row_frame,
                               font=Fonts.xsmall(),
                               background=background,
                               width=MealsSettingsView.PRICE_WIDTH,
                               height=1)
        self.base_price.pack(side=RIGHT, padx=MealsSettingsView.PADDING)
        self.base_price_bg = self.base_price.cget('background')
Exemplo n.º 10
0
    def __init__(self, parent, meal, index, background='white'):
        super().__init__(
            parent=parent,
            height=HistoryItem.HEIGHT,
            background=background  #'#F4F4F4'
        )

        self.meal = meal
        self.meal_deleted_event: Event = Event()

        self.expanded = False
        self.edit_view_shown = False
        self.details_view_shown = False

        self.row_frame = Frame(master=self,
                               background=background,
                               height=HistoryItem.HEIGHT)
        self.row_frame.pack(side=TOP, fill='x')
        self.row_frame.pack_propagate(0)

        self.details_frame = Frame(master=self, background='#F4F4F4')

        self.set_details_content()

        self._edit_img = IMAGES.create(IMAGES.EDIT)
        self._check_img = IMAGES.create(IMAGES.CHECK_MARK)
        self._trashcan_img = IMAGES.create(IMAGES.TRASH_CAN_LIGHT)
        self._down_img = IMAGES.create(IMAGES.DOWN)
        self._up_img = IMAGES.create(IMAGES.UP)

        self.index = Label(master=self.row_frame,
                           text=f"{index}",
                           font=Fonts.xsmall(),
                           background=background,
                           width=MealsSettingsView.INDEX_WIDTH)
        self.index.pack(side=LEFT, padx=MealsSettingsView.PADDING)

        self.category = Label(master=self.row_frame,
                              text=meal.category_raw,
                              font=Fonts.xsmall(),
                              background=background,
                              width=MealsSettingsView.CATEGORY_WIDTH)
        self.category.pack(side=LEFT, padx=MealsSettingsView.PADDING)

        self.name = Label(master=self.row_frame,
                          text=meal.name,
                          font=Fonts.xsmall(bold=True),
                          background=background,
                          width=MealsSettingsView.NAME_WIDTH)
        self.name.pack(side=LEFT, padx=MealsSettingsView.PADDING)

        ##### DELETE BUTTON #####

        self.delete_container = Frame(
            master=self.row_frame,
            width=MealsSettingsView.DELETE_HEADER_WIDTH,
            height=60,
            bg=background)
        self.delete_container.pack(side=RIGHT, padx=MealsSettingsView.PADDING)

        self.delete = Button(master=self.delete_container,
                             image=self._trashcan_img,
                             command=self.delete_meal_command,
                             background=CButton.DARK_RED)
        self.delete.place(relx=0.5, rely=0.5, anchor="center")

        ##### EDIT BUTTON #####

        self.edit_container = Frame(master=self.row_frame,
                                    width=MealsSettingsView.EDIT_HEADER_WIDTH,
                                    height=60,
                                    bg=background)
        self.edit_container.pack(side=RIGHT,
                                 padx=(MealsSettingsView.PADDING, 0))

        self.edit = Button(
            master=self.edit_container,
            image=self._edit_img,
            command=None  #self.edit_order_command
        )
        self.edit.place(relx=0.5, rely=0.5, anchor="center")
        self.edit.config(state="disabled")

        # self.initial_button_background = self.edit.cget('background')

        # if self._order.state != REFS.OPEN and self._order.state != REFS.CHANGED:
        #     self.edit.config(state="disabled")

        ##### EXPAND BUTTON #####

        self.expand_container = Frame(
            master=self.row_frame,
            width=MealsSettingsView.EXPAND_HEADER_WIDTH,
            height=60,
            bg=background)
        self.expand_container.pack(side=RIGHT,
                                   padx=(MealsSettingsView.PADDING, 0))

        self.expand = Button(master=self.expand_container,
                             image=self._down_img,
                             command=self.expand_button_command)
        self.expand.place(relx=0.5, rely=0.5, anchor="center")
        self.expand.config(state="disabled")

        ##### BASE PRICE #####

        self.base_price_head = Label(master=self.row_frame,
                                     text=f"{meal.price_str}{REFS.CURRENCY}",
                                     font=Fonts.xsmall(),
                                     background=background,
                                     width=MealsSettingsView.PRICE_WIDTH)
        self.base_price_head.pack(side=RIGHT, padx=MealsSettingsView.PADDING)
Exemplo n.º 11
0
class NetworkHandler:
    DEBUG = True
    initialized = False

    CONNECTION_READY = False

    HEADERSIZE = 5

    IP_CONFIG = ("127.0.0.1", 80)
    IP_CONFIG_PARTNER = ("127.0.0.1", 80)

    KITCHEN_IP_CONFIG = (REFS.KITCHEN_SERVER_IP, REFS.KITCHEN_SERVER_PORT)
    CASHDESK_IP_CONFIG = (REFS.CASHDESK_SERVER_IP, REFS.CASHDESK_SERVER_PORT)

    SERVER_SOCKET = None

    on_message_received_event: Event = Event()

    def __init__(self, main_station: bool):
        """ Initializes the network handler.

        main_station: True, if the ordering system runs on the cashdesk station. False, for the kitchen station.
        """
        if main_station:
            NetworkHandler.IP_CONFIG = NetworkHandler.CASHDESK_IP_CONFIG
            NetworkHandler.IP_CONFIG_PARTNER = NetworkHandler.KITCHEN_IP_CONFIG
        else:
            NetworkHandler.IP_CONFIG = NetworkHandler.KITCHEN_IP_CONFIG
            NetworkHandler.IP_CONFIG_PARTNER = NetworkHandler.CASHDESK_IP_CONFIG

        NetworkHandler.setup_server_connection()

    @staticmethod
    def setup_server_connection():
        try:
            NetworkHandler.SERVER_SOCKET = socket.socket(
                socket.AF_INET, socket.SOCK_STREAM)
            NetworkHandler.SERVER_SOCKET.bind(NetworkHandler.IP_CONFIG)
            NetworkHandler.SERVER_SOCKET.listen()

            NetworkHandler.initialized = True
        except OSError as err:
            messagebox.showerror(
                title="TCP/IP server socket failed",
                message=
                "Setting up a TCP/IP server socket for this station failed. Make sure the other station is reachable over WIFI.\n\nError message: "
                + err.strerror)

    def check_connection_ready(self) -> bool:
        try:
            s = NetworkHandler.connect(suppress_error=True)

            if s == None:
                NetworkHandler.CONNECTION_READY = False
                return False

            s.shutdown(socket.SHUT_RDWR)
            s.close()

            NetworkHandler.CONNECTION_READY = True
            return True
        except:
            NetworkHandler.CONNECTION_READY = False
            return False

######################################  SERVER METHODES (RECEIVING DATA) ######################################

    @staticmethod
    def string_to_byte(text):
        return bytes(text, "utf-8")

    @staticmethod
    def receive(socket):
        if not NetworkHandler.initialized:
            print(
                "NetworkHandler not initialized yet. Aborting the receive method."
            )
            return

        if not EncryptionHandler.initialized:
            print(
                "EncryptionHandler not initialized yet. Aborting the receive method."
            )
            return

        # Array to store all received message junks
        msg_chunks = []
        # Counter of received bytes
        bytes_received = 0

        while bytes_received < REFS.MESSAGE_LENGTH:
            try:
                chunk = socket.recv(
                    min(REFS.MESSAGE_LENGTH - bytes_received, 2048))
            except OSError as err:
                print("NetworkHandler send error: {0}".format(err))
                raise err

            if chunk == b'':
                raise RuntimeError("Socket connection broken while receiving")

            # Decode the chunk to UTF-8
            chunk = chunk.decode("utf-8")

            # Add the chunk to the list of all received chunks
            msg_chunks.append(chunk)

            # Update the amount of bytes received
            bytes_received = bytes_received + len(chunk)

        string_fullmsg = ''.join(msg_chunks)

        return EncryptionHandler.decrypt(string_fullmsg)

    def start_receive_loop(self):
        """ Continuos loop that will constantly wait for messages to arrive
        and if so, send a response to the client and also call events so that
        services can react to the message.

        This can not be a static method and has to be called by the object created 
        in the cashdesk-gui-model class.
        """
        try:
            if not NetworkHandler.initialized:
                if NetworkHandler.DEBUG:
                    print(
                        "NetworkHandler has not been initialized yet. Skipping the receive loop."
                    )
                return
                # raise RuntimeError("NetworkHandler has not been initialized yet.")

            read_list = [NetworkHandler.SERVER_SOCKET]
            write_list = [NetworkHandler.SERVER_SOCKET]

            readable, writable, inerror = select.select(
                read_list, write_list, [], 0)

            for s in readable:
                if s is NetworkHandler.SERVER_SOCKET:
                    # Waits for client to connect
                    (clientsocket,
                     address) = NetworkHandler.SERVER_SOCKET.accept()

                    try:
                        # Receiving data from client
                        received_msg = NetworkHandler.receive(clientsocket)
                    except:
                        raise RuntimeError("Receive failed")

                    print(f"Received: '{received_msg}'")

                    # TODO: call event and give along the received message
                    # TODO: if event returns anything, attach it to the handshake

                    service_response = NetworkHandler.on_message_received_event(
                        received_msg)

                    if service_response == None:
                        service_response = ""

                    try:
                        recved_id = received_msg[0:REFS.IDENTIFIER_LENGTH]
                        # Responding to client to indicate a successfull message transmission
                        # clientsocket.send(NetworkHandler.string_to_byte(REFS.HANDSHAKE_MSG))
                        NetworkHandler.send(
                            recved_id + REFS.HANDSHAKE_MSG + service_response,
                            clientsocket)
                    except OSError as err:
                        print("NetworkHandler send error: {0}".format(err))
                        raise err
        except RuntimeError:
            pass
        except OSError as err:
            print("Unexpected error:", err)
            raise
        finally:
            # Run this function again after <delay_ms> milliseconds
            TimerHandler.start_timer(callback=self.start_receive_loop,
                                     delay_ms=REFS.RECEIVE_REFRESH_DELAY,
                                     store_id=False)

###################################### CLIENT METHODES (SENDING DATA)  ######################################

    @staticmethod
    def send_with_handshake(raw_message) -> bool:
        if not NetworkHandler.initialized:
            print(
                "NetworkHandler has not been initialized yet. Skipping the send process."
            )
            return False

        if not EncryptionHandler.initialized:
            print(
                "EncryptionHandler has not been initialized yet. Skipping the send process."
            )
            return False

        success = True

        identifier = REFS.FORMAT_STRING.format(
            random.randint(0, REFS.MAX_IDENTIFIER))

        raw_message = identifier + raw_message

        # Create client connection to the server of the other station
        _socket = NetworkHandler.connect()

        # If message has been sent successfully
        if NetworkHandler.send(raw_message, _socket) is True:
            response = ""

            try:
                if NetworkHandler.DEBUG:
                    print("Waiting for ACK response...", end=' ')
                # Receive any response
                response = _socket.recv(1024)
            except OSError as err:
                print("NetworkHandler receive error: {0}".format(err))
                _socket.close()
                success = False
                return False
            #     raise err
            # finally:
            #     _socket.close()

            #     if not success:
            #         return False

            decrypted_response = EncryptionHandler.decrypt(
                response.decode("utf-8"))

            # Check response for handshake identifier
            if f"{identifier}{REFS.HANDSHAKE_MSG}" in decrypted_response:
                if NetworkHandler.DEBUG:
                    print("Success!")

                additional_text = decrypted_response.replace(
                    f"{identifier}{REFS.HANDSHAKE_MSG}", "")
                if additional_text != "":
                    print(f"Additional handshake content: '{additional_text}'")
            else:
                if NetworkHandler.DEBUG:
                    print("Failed!")
                # TODO ---> raise error? Resend?
                success = False

        _socket.close()

        return success

    @staticmethod
    def send(raw_message, _socket) -> bool:
        if not NetworkHandler.initialized:
            print(
                "NetworkHandler has not been initialized yet. Skipping the send process."
            )
            return

        if not EncryptionHandler.initialized:
            print(
                "EncryptionHandler has not been initialized yet. Skipping the send process."
            )
            return

        if NetworkHandler.DEBUG:
            print(f"Sending: '{raw_message}'")

        # Encrypt the raw message
        msg = EncryptionHandler.encrypt(raw_message)

        if len(msg) < REFS.MESSAGE_LENGTH:
            null_bytes = [b'\0' for x in range(REFS.MESSAGE_LENGTH - len(msg))]
            msg = msg + b''.join(null_bytes)

        totalsent = 0

        # Repeat as long as we haven't sent every message chunk
        while totalsent < REFS.MESSAGE_LENGTH:
            sent = 0

            try:
                # Send as much of the message as possible
                sent = _socket.send(msg[totalsent:])
            except OSError as err:
                print("NetworkHandler send error: {0}".format(err))
                raise err

            # "sent" contains the actual number of bytes sent
            # If 0, then something went wong
            if sent == 0:
                raise RuntimeError(
                    "Socket connection broken while sending message")

            # Increment the amount of bytes sent in total
            totalsent = totalsent + sent

            # Did we send all enough bytes?
            if totalsent == REFS.MESSAGE_LENGTH:
                if NetworkHandler.DEBUG:
                    print(
                        f"Full message (length = {totalsent} Bytes) was sent successfully."
                    )

                return True

        return False

    @staticmethod
    def connect(suppress_error: bool = False):
        if not NetworkHandler.initialized:
            print(
                "NetworkHandler has not been initialized yet. Skipping the connection process."
            )
            return None

        _socket = None

        try:
            _socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            _socket.connect(NetworkHandler.IP_CONFIG_PARTNER)
        except OSError as err:
            if suppress_error:
                return None

            print("NetworkHandler connect error: {0}".format(err))
            raise err

        return _socket

    @staticmethod
    def get_message_components(message) -> (str, str):
        """ Splits the given message into its main components:
        The identifier and the raw message body
        """
        if len(message) < REFS.IDENTIFIER_LENGTH:
            return "", ""

        identifier = message[0:REFS.IDENTIFIER_LENGTH]
        body = message[REFS.IDENTIFIER_LENGTH:]

        return identifier, body