0

I am working on my own UI. For now I have just shaped the main screen. But what I need to do is to run some functions in background while the UI is working and the user is using the UI(i.e. some functions get activated and the user gets redirected to another screen). I have tried the function that uses multiprocessing module:

def runInParallel(self, *fns):
    proc = []
    for fn in fns:
        p = Process(target=fn)
        p.start()
        proc.append(p)
    for p in proc:
        p.join()

But unfortunately it does not work, and UI isn't updating. I have conducted a trial for this method(experimentally) but for some reason when I run this function with arguments self.runInParallel(self.xtb_main_screen, self.getAllSymbols_xtb_api) inside log_in_xtb_api function after clicking log in button another instance of the UI just opens up.

Below is the code for the UI class. What I want to achieve is to get functions like getAllSymbols or getChartLastRequest, work always when the UI is running. For the first time when launching the program it gots to download the data for the first time. But after the startup I need to actualise the data every 1,5,15 or etc [minutes]. So my UI can't be constanly lagged due to the code execution. How do I make any function I want run in parallel/background so the UI won't lag? This is code for the UI class:

Note that to use the functions like log_in_xtb_api or others from xtb broker api, you need to download the wrapper connector and import it and iside it change ports from 5112 and 5113 to 5124 and 5125 if you have a demo account or leave them as they are if you have real account: http://developers.xstore.pro/api/wrappers/2.5.0

class UI:
    def __init__(self):
        self.UI_main = tk.Tk()
        self.width_screen, self.height_screen = resolution()
        self.canvas_main = tk.Canvas(self.UI_main, height=self.height_screen, width=self.width_screen)
        self.canvas_main.pack()

        self.login_screen()

        self.UI_main.mainloop()

    def login_screen(self):

        self.frame_login = Frame(self.canvas_main, bg='#131d47')
        self.frame_login.pack()
        self.frame_login.place(relx=0.5, rely=0.5, anchor=CENTER)

        self.login_font = tkFont.Font(family='Calibri', size=30)

        self.login_xtb_btn = tk.Button(self.frame_login, text='Log in to XTB', width=25, height=2,
                                       command=self.login_xtb_ui)
        self.login_xtb_btn['font'] = self.login_font
        self.login_xtb_btn.pack()
        self.login_tradingview_btn = tk.Button(self.frame_login, text='Log in to Tradingview', width=25, height=2,
                                               command=self.login_tradingview_choose)
        self.login_tradingview_btn['font'] = self.login_font
        self.login_tradingview_btn.pack()

    def login_xtb_ui(self):
        for widgets in self.canvas_main.winfo_children():
            widgets.destroy()

        self.login_xtb_font = tkFont.Font(family='Calibri', size=25)

        self.xtb_login = StringVar()
        self.xtb_login.set("Enter user ID")
        self.xtb_password = StringVar()
        self.xtb_password.set("Enter password here")
        self.frame_xtb_login = Frame(self.canvas_main)
        self.frame_xtb_login.pack()
        self.frame_xtb_login.place(relx=0.5, rely=0.5, anchor=CENTER)

        self.xtb_login_entry = Entry(self.frame_xtb_login, textvariable=self.xtb_login, width=25)
        self.xtb_login_entry.pack()
        self.xtb_login_entry['font'] = self.login_xtb_font
        self.xtb_password_entry = Entry(self.frame_xtb_login, textvariable=self.xtb_password, width=25)
        self.xtb_password_entry.pack()
        self.xtb_password_entry['font'] = self.login_xtb_font

        self.xtb_log_in_btn = tk.Button(self.frame_xtb_login, text='Log in', command=self.xtb_log_in)
        self.xtb_log_in_btn.pack()
        self.xtb_log_in_btn['font'] = self.login_xtb_font



        return 'apple'

    def xtb_log_in(self):
        self.user_id_xtb = self.xtb_login_entry.get()
        self.password_xtb = self.xtb_password_entry.get()
        '''
        self.host = 'xapi.xtb.com'
        self.port = 5112
        self.host = socket.getaddrinfo(self.host, self.port)[0][4][0]
        self.s = socket.socket()
        self.s.connect((self.host, self.port))
        self.s = ssl.wrap_socket(self.s)

        self.END = b'\n\n'
        '''
        self.log_in_xtb_api(userId=self.user_id_xtb, password=self.password_xtb)

    def xtb_main_screen(self):
        for widgets in self.canvas_main.winfo_children():
            widgets.destroy()

        canvas_top_height = math.floor(0.07 * self.height_screen)
        canvas_charting_tools_width = math.floor(0.03 * self.width_screen)
        canvas_charting_tools_height = self.height_screen - canvas_top_height # takes full screen

        paned_window_charting_main_width = self.width_screen - canvas_charting_tools_width
        paned_window_charting_main_height = self.height_screen - canvas_top_height

        canvas_charting_top_width = self.width_screen - canvas_charting_tools_width
        canvas_charting_top_height = self.height_screen - canvas_top_height - math.floor(0.3 * self.height_screen)

        canvas_charting_bottom_width = self.width_screen - canvas_charting_tools_width
        canvas_charting_bottom_height = self.height_screen - canvas_charting_top_height

        canvas_charting_indicators_width = math.floor(0.14 * self.width_screen)
        canvas_charting_indicators_height = self.height_screen - canvas_top_height - math.floor(0.3 * self.height_screen)

        canvas_charting_chart_width = math.floor(0.65 * self.width_screen)
        canvas_charting_chart_height = self.height_screen - canvas_top_height - math.floor(0.3 * self.height_screen)

        paned_window_charting_downloads_info_width = self.width_screen - canvas_charting_tools_width - canvas_charting_indicators_width - canvas_charting_chart_width
        paned_window_charting_downloads_info_height = canvas_charting_chart_height

        canvas_charting_downloads_width = paned_window_charting_downloads_info_width
        canvas_charting_downloads_height = math.floor(0.5 * paned_window_charting_downloads_info_height)

        canvas_charting_info_width = paned_window_charting_downloads_info_width
        canvas_charting_info_height = paned_window_charting_downloads_info_height - canvas_charting_downloads_height

        menu_btn_width = 20
        menu_btn_height = math.floor(0.7 * canvas_top_height)

        menu_xtb_btn_font = tkFont.Font(family='Calibri', size=20)

        # Canvas top xtb main aka Menu
        # Contains:
        # Charting screen
        # Symbol search
        # Trading Bot
        # neural nets
        # Machine learning
        self.canvas_top_xtb_main = Canvas(self.canvas_main, width=self.width_screen, height=canvas_top_height, bg='#131d47')
        self.canvas_top_xtb_main.pack()
        # position (0.0, 0.0) top
        self.canvas_top_xtb_main.place(relx=0.0, rely=0.0, anchor=NW)

        self.frame_menu_xtb_main_top = Frame(self.canvas_top_xtb_main)
        self.frame_menu_xtb_main_top.pack(expand=False)
        self.frame_menu_xtb_main_top.place(relx=0.0, rely=0.0, anchor=NW)

        # Charting screen Button
        self.charting_screen_btn = Button(self.frame_menu_xtb_main_top, text='Main Screen')
        self.charting_screen_btn.grid(row=0, column=0, padx=5, pady=5)
        self.charting_screen_btn['font'] = menu_xtb_btn_font

        # Symbol search button
        self.symbol_search_btn = Button(self.frame_menu_xtb_main_top, text='Symbol Search', command=self.symbol_search_btn_xtb)
        self.symbol_search_btn.grid(row=0, column=1, padx=5, pady=5)
        self.symbol_search_btn['font'] = menu_xtb_btn_font

        # Trading Bot Button
        self.trading_bot_btn = Button(self.frame_menu_xtb_main_top, text='Trading Bot')
        self.trading_bot_btn.grid(row=0, column=2, padx=5, pady=5)
        self.trading_bot_btn['font'] = menu_xtb_btn_font

        # Neural Nets Button
        self.neural_nets_btn = Button(self.frame_menu_xtb_main_top, text='Neural Nets')
        self.neural_nets_btn.grid(row=0, column=3, padx=5, pady=5)
        self.neural_nets_btn['font'] = menu_xtb_btn_font

        # Machine Learning Button
        self.machine_learning_btn = Button(self.frame_menu_xtb_main_top, text='Machine Learning')
        self.machine_learning_btn.grid(row=0, column=4, padx=5, pady=5)
        self.machine_learning_btn['font'] = menu_xtb_btn_font



        # main charting container
        self.canvas_charting_xtb_main = Canvas(self.canvas_main, width=self.width_screen, height=(self.height_screen - canvas_top_height), bg='#ff9100')
        self.canvas_charting_xtb_main.pack()
        # position (0.0, -100) lowered by the height of frame_top_xtb_main height
        self.canvas_charting_xtb_main.place(relx=0.0, y=canvas_top_height, anchor=NW)

        # charting tools container
        # contains tools to draw on chart
        self.canvas_charting_tools = Canvas(self.canvas_charting_xtb_main, width=canvas_charting_tools_width, height=canvas_charting_tools_height, bg='#e80000')
        self.canvas_charting_tools.pack()
        # position (0.0, 0.0) relative to the frame_charting_xtb_main
        self.canvas_charting_tools.place(relx=0.0, rely=0.0, anchor=NW)

        # Secondary charting container
        self.canvas_charting_xtb_sec = Canvas(self.canvas_charting_xtb_main, width=(self.width_screen - canvas_charting_tools_width), height=(self.height_screen - canvas_top_height), bg='#ff9100')
        self.canvas_charting_xtb_sec.pack()
        # position (0.0, -100) lowered by the height of frame_top_xtb_main height
        self.canvas_charting_xtb_sec.place(x=canvas_charting_tools_width, y=0, anchor=NW)

        # Paned Widow main
        # Contains:
        # Indicator panel
        # Chart panel
        # dowloades panel
        # neural nets panel
        self.paned_window_charting_main = PanedWindow(self.canvas_charting_xtb_sec, width=paned_window_charting_main_width, height=paned_window_charting_main_height, bg='#3700ff', orient=VERTICAL)
        self.paned_window_charting_main.pack()
        self.paned_window_charting_main.place(x=0, y=0)

        # Canvas charting Bottom
        # Contains:
        # Available trained neural nets
        self.canvas_charting_bottom = Canvas(width=canvas_charting_bottom_width, height=canvas_charting_bottom_height, bg='#000000')
        self.canvas_charting_bottom.pack()

        # Paned Window Charting Top
        # Contains:
        # Indicator panel
        # Chart panel
        # dowloads panel
        self.paned_window_charting_top = PanedWindow(width=canvas_charting_top_width, height=canvas_charting_top_height, bg='#3700ff', orient=HORIZONTAL)
        self.paned_window_charting_top.pack()
        self.paned_window_charting_top.place(relx=0.0, rely=0.0, anchor=NW)

        self.paned_window_charting_main.add(self.paned_window_charting_top)
        self.paned_window_charting_main.add(self.canvas_charting_bottom)

        # Canvas charting Indicators
        # Contains:
        # Indicators list available for chosen dataset
        self.canvas_charting_indicators = Canvas(width=canvas_charting_indicators_width, height=canvas_charting_indicators_height)
        self.canvas_charting_indicators.pack()

        # Canvas charting chart
        # Contains:
        # Chart panel
        self.canvas_charting_chart = Canvas(width=canvas_charting_chart_width, height=canvas_charting_chart_height)
        self.canvas_charting_chart.pack()

        # Paned Window Downloads And Sell/Buy Info
        # Contains:
        # Downloads Panel
        # Symbol BUY/SELL Info
        self.paned_window_charting_downloads_info = PanedWindow(width=paned_window_charting_downloads_info_width, height=paned_window_charting_downloads_info_height, orient=VERTICAL, bg='#3700ff')
        self.paned_window_charting_downloads_info.pack()


        # Canvas charting downloads
        # Contains:
        # Downloads panel
        self.canvas_charting_downloads = Canvas(width=canvas_charting_downloads_width, height=canvas_charting_downloads_height)
        self.canvas_charting_downloads.pack()

        # Canvas charting info
        # Contains:
        # Symbol BUY/SELL Info
        self.canvas_charting_info = Canvas(width=canvas_charting_info_width, height=canvas_charting_info_height)
        self.canvas_charting_info.pack()

        self.paned_window_charting_downloads_info.add(self.canvas_charting_downloads)
        self.paned_window_charting_downloads_info.add(self.canvas_charting_info)

        self.paned_window_charting_top.add(self.canvas_charting_indicators)
        self.paned_window_charting_top.add(self.canvas_charting_chart)
        self.paned_window_charting_top.add(self.paned_window_charting_downloads_info)

        #self.getAllSymbols_xtb_api()

        #resp = self.client.commandExecute(commandName='getSymbol', arguments=21)
        #print(resp)
        #price = self.getChartLastRequest(symbol_name=self.symbols_unique[0], period=5, time=1262944112000)
        #print(price)





    def login_tradingview_choose(self):
        for widgets in self.canvas_main.winfo_children():
            widgets.destroy()

        self.login_tradingview_font = tkFont.Font(family='Calibri', size=25)

        self.frame_tradingview_login = Frame(self.canvas_main)
        self.frame_tradingview_login.pack()
        self.frame_tradingview_login.place(relx=0.5, rely=0.5, anchor=CENTER)

        self.tradingview_login_google_btn = tk.Button(self.frame_tradingview_login, text='Log in with Google', width=25, height=2, command=self.login_tradingview_google)
        self.tradingview_login_google_btn.pack()
        self.tradingview_login_google_btn['font'] = self.login_tradingview_font
        self.tradingview_login_username_btn = tk.Button(self.frame_tradingview_login, text='log in with tradingview', width=25, height=2, command=self.login_tradingview_username)
        self.tradingview_login_username_btn.pack()
        self.tradingview_login_username_btn['font'] = self.login_tradingview_font

    def login_tradingview_google(self):
        for widgets in self.canvas_main.winfo_children():
            widgets.destroy()

        self.login_tradingview_google_font = tkFont.Font(family='Calibri', size=25)

        self.tradingview_google_login = StringVar()
        self.tradingview_google_login.set("Enter login or email here")
        self.tradingview_google_password = StringVar()
        self.tradingview_google_password.set("Enter password here")
        self.frame_tradingview_google_login = Frame(self.canvas_main)
        self.frame_tradingview_google_login.pack()
        self.frame_tradingview_google_login.place(relx=0.5, rely=0.5, anchor=CENTER)

        self.tradingview_google_login_entry = Entry(self.frame_tradingview_google_login, textvariable=self.tradingview_google_login, width=25)
        self.tradingview_google_login_entry.pack()
        self.tradingview_google_login_entry['font'] = self.login_tradingview_google_font
        self.tradingview_google_password_entry = Entry(self.frame_tradingview_google_login, textvariable=self.tradingview_google_password, width=25)
        self.tradingview_google_password_entry.pack()
        self.tradingview_google_password_entry['font'] = self.login_tradingview_google_font

        self.tradingview_google_log_in_btn = tk.Button(self.frame_tradingview_google_login, text='Log in')
        self.tradingview_google_log_in_btn.pack()
        self.tradingview_google_log_in_btn['font'] = self.login_tradingview_google_font

    def login_tradingview_username(self):
        for widgets in self.canvas_main.winfo_children():
            widgets.destroy()

        self.login_tradingview_username_font = tkFont.Font(family='Calibri', size=25)

        self.tradingview_username_login = StringVar()
        self.tradingview_username_login.set("Enter login or email here")
        self.tradingview_username_password = StringVar()
        self.tradingview_username_password.set("Enter password here")
        self.frame_tradingview_username_login = Frame(self.canvas_main)
        self.frame_tradingview_username_login.pack()
        self.frame_tradingview_username_login.place(relx=0.5, rely=0.5, anchor=CENTER)

        self.tradingview_username_login_entry = Entry(self.frame_tradingview_username_login, textvariable=self.tradingview_username_login, width=25)
        self.tradingview_username_login_entry.pack()
        self.tradingview_username_login_entry['font'] = self.login_tradingview_username_font
        self.tradingview_username_password_entry = Entry(self.frame_tradingview_username_login, textvariable=self.tradingview_username_password, width=25)
        self.tradingview_username_password_entry.pack()
        self.tradingview_username_password_entry['font'] = self.login_tradingview_username_font

        self.tradingview_username_log_in_btn = tk.Button(self.frame_tradingview_username_login, text='Log in')
        self.tradingview_username_log_in_btn.pack()
        self.tradingview_username_log_in_btn['font'] = self.login_tradingview_username_font

    def log_in_xtb_api(self, userId, password):
        # enter your login credentials here
        userId = userId
        password = "password"

        # create & connect to RR socket
        self.client = xAPIConnector.APIClient()

        # connect to RR socket, login
        loginResponse = self.client.execute(xAPIConnector.loginCommand(userId=userId, password=password))
        xAPIConnector.logger.info(str(loginResponse))

        # check if user logged in correctly
        if (loginResponse['status'] == False):
            print('Login failed. Error code: {0}'.format(loginResponse['errorCode']))
            return

        # get ssId from login response
        ssid = loginResponse['streamSessionId']

        self.runInParallel(self.xtb_main_screen, self.getAllSymbols_xtb_api)

    def getAllSymbols_xtb_api(self):
        symbols_init = self.client.commandExecute('getAllSymbols')
        symbols_list_init = list(symbols_init.items())
        symbols_list_final = list()
        for i in range(len(symbols_list_init[1][1][:])):
            symbols_list_final.append(symbols_list_init[1][1][i])

        self.symbols_dataframe = pd.DataFrame(symbols_list_final)

        self.unique_category = self.symbols_dataframe['categoryName'].unique()
        self.unique_group = self.symbols_dataframe['groupName'].unique()
        self.symbols_unique = self.symbols_dataframe['symbol'].unique()
        print(self.unique_category)
        print(self.unique_group)

    def getChartLastRequest(self, symbol_name : str, period : int, time : int):

        parameters = {"info" : {
            "period": period,
            "start": time,
            "symbol": symbol_name
        }}

        price_init = self.client.commandExecute(commandName='getChartLastRequest', arguments=parameters)
        print(price_init)

    def symbol_search_btn_xtb(self):
        for widgets in self.canvas_charting_xtb_sec.winfo_children():
            widgets.place_forget()

        self.symbol_search_xtb_ui()

    def symbol_search_xtb_ui(self):

        symbol_search_xtb_btn_font = tkFont.Font(family='Calibri', size=20)

        self.frame_search_xtb_btns = Frame(self.canvas_charting_xtb_sec, width=(self.width_screen - math.floor(0.03 * self.width_screen)))
        self.frame_search_xtb_btns.pack()
        self.frame_search_xtb_btns.place(relx=0.0, rely=0.0, anchor=NW)

        pixel = tk.PhotoImage(width=1, height=1)

        self.label_search_xtb = Label(self.frame_search_xtb_btns, text='', image=pixel, width=(self.width_screen - math.floor(0.03 * self.width_screen)), anchor=W)
        self.label_search_xtb.pack()

        self.stocks_xtb_btn = Button(self.frame_search_xtb_btns, text='Stocks', anchor=W)
        self.stocks_xtb_btn.pack(fill=X)
        self.stocks_xtb_btn['font'] = symbol_search_xtb_btn_font

        self.CRT_xtb_btn = Button(self.frame_search_xtb_btns, text='Stocks', anchor=W)
        self.CRT_xtb_btn.pack(fill=X)
        self.CRT_xtb_btn['font'] = symbol_search_xtb_btn_font

        self.ETF_xtb_btn = Button(self.frame_search_xtb_btns, text='Stocks', anchor=W)
        self.ETF_xtb_btn.pack(fill=X)
        self.ETF_xtb_btn['font'] = symbol_search_xtb_btn_font

        self.indexes_xtb_btn = Button(self.frame_search_xtb_btns, text='Indexes', anchor=W)
        self.indexes_xtb_btn.pack(fill=X)
        self.indexes_xtb_btn['font'] = symbol_search_xtb_btn_font

        self.forex_xtb_btn = Button(self.frame_search_xtb_btns, text='Forex', anchor=W)
        self.forex_xtb_btn.pack(fill=X)
        self.forex_xtb_btn['font'] = symbol_search_xtb_btn_font

        self.STK_xtb_btn = Button(self.frame_search_xtb_btns, text='Stocks', anchor=W)
        self.STK_xtb_btn.pack(fill=X)
        self.STK_xtb_btn['font'] = symbol_search_xtb_btn_font

        self.CMD_xtb_btn = Button(self.frame_search_xtb_btns, text='Stocks', anchor=W)
        self.CMD_xtb_btn.pack(fill=X)
        self.CMD_xtb_btn['font'] = symbol_search_xtb_btn_font

    def runInParallel(self, *fns):
        proc = []
        for fn in fns:
            p = Process(target=fn)
            p.start()
            proc.append(p)
        for p in proc:
            p.join()

EDIT: After deleting the last loop in runInParallel function the code looks like this:

def runInParallel(self, *fns):
    proc = []
    for fn in fns:
        p = Process(target=fn)
        p.start()
        proc.append(p)
        p.join()

And I got an error after this error is raised new instance of the UI class opens up:

Traceback (most recent call last):
  File "C:\Users\...\AppData\Local\Programs\Python\Python39\lib\tkinter\__init__.py", line 1892, in __call__
    return self.func(*args)
  File "C:\Users\...\PycharmProjects\LSTM_multiple_indicators\UI.py", line 92, in xtb_log_in
    self.log_in_xtb_api(userId=self.user_id_xtb, password=self.password_xtb)
  File "C:\Users\...\PycharmProjects\LSTM_multiple_indicators\UI.py", line 358, in log_in_xtb_api
    self.runInParallel(self.xtb_main_screen, self.getAllSymbols_xtb_api)
  File "C:\Users\...\PycharmProjects\LSTM_multiple_indicators\UI.py", line 439, in runInParallel
    p.start()
  File "C:\Users\...\AppData\Local\Programs\Python\Python39\lib\multiprocessing\process.py", line 121, in start
    self._popen = self._Popen(self)
  File "C:\Users\...\AppData\Local\Programs\Python\Python39\lib\multiprocessing\context.py", line 224, in _Popen
    return _default_context.get_context().Process._Popen(process_obj)
  File "C:\Users\...\AppData\Local\Programs\Python\Python39\lib\multiprocessing\context.py", line 327, in _Popen
    return Popen(process_obj)
  File "C:\Users\...\AppData\Local\Programs\Python\Python39\lib\multiprocessing\popen_spawn_win32.py", line 93, in __init__
    reduction.dump(process_obj, to_child)
  File "C:\Users\...\AppData\Local\Programs\Python\Python39\lib\multiprocessing\reduction.py", line 60, in dump
    ForkingPickler(file, protocol).dump(obj)
TypeError: cannot pickle '_tkinter.tkapp' object
  • Why don't you just remove the last `for` loop in your `runInParallel()` function? As it is it just sits there waiting for the functions to run, all the time blocking `tkinter`. – quamrana Aug 23 '22 at 13:43
  • @quamrana, ok but after doing so new instance of still opens up. I get an error - attached on the bottom of the post – Jakub Szurlej Aug 23 '22 at 13:49
  • Ok, so what you are asking is unsuitable for multiple threads or multiple processes. Certainly `tkinter` being a crude UI won't work like this. The best you can do is to run entirely unrelated code. – quamrana Aug 23 '22 at 13:59
  • @quamrana ok. so you would advise me to make a second program(project), pass the login and password and download data? Did I understood it right? If so how do I pass attributes like password and login to unrelated program? – Jakub Szurlej Aug 23 '22 at 14:05
  • @quamrana another question: If tkinter is so "unpolished" is there any other good gui module that supports multithreading? – Jakub Szurlej Aug 23 '22 at 14:08
  • 1
    There's just too much code here for a stackoverflow question. Unfortunately the onus is on you to come up with a tightly focused question. You should do some research first on UIs and running code in parallel. – quamrana Aug 23 '22 at 14:10
  • I've never used it with python, but QT might work. – quamrana Aug 23 '22 at 14:10
  • To be run in a separate process it seems like the functions would have to be self contained (not instance methods) with a way to pass info back and forth. It might be easier (once you get past the learning curve) to use asyncio and run the *self-contained* functions in the same process using a timer to run them periodically - it will be easier for them to share data with the UI. caveat: no idea how running a UI in an eventloop would effect its responsiveness. – wwii Aug 23 '22 at 14:12
  • ... This might be worth looking at first, there were other results searching with `python tkinter asyncio`: [Use asyncio and Tkinter (or another GUI lib) together without freezing the GUI](https://stackoverflow.com/questions/47895765/use-asyncio-and-tkinter-or-another-gui-lib-together-without-freezing-the-gui) – wwii Aug 23 '22 at 14:18

1 Answers1

2

You could define a function with the code you want to run and the UI_Main.after() method, for example something like this:

def runInParallel():
    print('I am doing something.')
    UI_Main.after(100, runInParallel)

UI_Main.after(100, runInParallel)

Something like this should work for you.

bePrivate
  • 76
  • 6