From 7a7b94f57c755a73ede11e7b8e292a3d1914dbef Mon Sep 17 00:00:00 2001 From: DevilXD Date: Sat, 30 Jul 2022 16:14:57 +0200 Subject: [PATCH] Implement the status bar --- gui.py | 39 ++++++++++++++++++++++++++++----------- twitch.py | 29 ++++++++++++++++++++++++----- 2 files changed, 52 insertions(+), 16 deletions(-) diff --git a/gui.py b/gui.py index 49c1b61..2a9b99d 100644 --- a/gui.py +++ b/gui.py @@ -262,6 +262,20 @@ class LinkLabel(ttk.Label): return lambda e: webbrowser.open_new_tab(url) +class StatusBar: + def __init__(self, manager: GUIManager, master: ttk.Widget): + frame = ttk.LabelFrame(master, text="Status", padding=(4, 0, 4, 4)) + frame.grid(column=0, row=0, columnspan=3, sticky="nsew", padx=2) + self._label = ttk.Label(frame) + self._label.grid(column=0, row=0, sticky="nsew") + + def update(self, text: str): + self._label.config(text=text) + + def clear(self): + self._label.config(text='') + + class _WSEntry(TypedDict): status: str topics: int @@ -269,10 +283,10 @@ class _WSEntry(TypedDict): class WebsocketStatus: def __init__(self, manager: GUIManager, master: ttk.Widget): - self._status_var = StringVar(master) - self._topics_var = StringVar(master) frame = ttk.LabelFrame(master, text="Websocket Status", padding=(4, 0, 4, 4)) - frame.grid(column=0, row=0, sticky="nsew", padx=2) + frame.grid(column=0, row=1, sticky="nsew", padx=2) + self._status_var = StringVar(frame) + self._topics_var = StringVar(frame) ttk.Label( frame, text='\n'.join(f"Websocket #{i}:" for i in range(1, MAX_WEBSOCKETS + 1)), @@ -334,7 +348,7 @@ class LoginForm: self._manager = manager self._var = StringVar(master) frame = ttk.LabelFrame(master, text="Login Form", padding=(4, 0, 4, 4)) - frame.grid(column=1, row=0, sticky="nsew", padx=2) + frame.grid(column=1, row=1, sticky="nsew", padx=2) frame.columnconfigure(0, weight=2) frame.columnconfigure(1, weight=1) frame.rowconfigure(4, weight=1) @@ -425,7 +439,7 @@ class CampaignProgress: self._frame = frame = ttk.LabelFrame( master, text="Campaign Progress", padding=(4, 0, 4, 4) ) - frame.grid(column=0, row=1, columnspan=2, sticky="nsew", padx=2) + frame.grid(column=0, row=2, columnspan=2, sticky="nsew", padx=2) frame.columnconfigure(0, weight=2) frame.columnconfigure(1, weight=1) ttk.Label(frame, text="Campaign:").grid(column=0, row=0, columnspan=2) @@ -542,11 +556,11 @@ class CampaignProgress: class ConsoleOutput: def __init__(self, manager: GUIManager, master: ttk.Widget): frame = ttk.LabelFrame(master, text="Output", padding=(4, 0, 4, 4)) - frame.grid(column=0, row=2, columnspan=2, sticky="nsew", padx=2) + frame.grid(column=0, row=3, columnspan=2, sticky="nsew", padx=2) + # tell master frame that the containing row can expand + master.rowconfigure(3, weight=1) frame.rowconfigure(0, weight=1) # let the frame expand frame.columnconfigure(0, weight=1) - # tell master frame that the containing row can expand - master.rowconfigure(2, weight=1) xscroll = ttk.Scrollbar(frame, orient="horizontal") yscroll = ttk.Scrollbar(frame, orient="vertical") self._text = tk.Text( @@ -582,11 +596,11 @@ class ChannelList: def __init__(self, manager: GUIManager, master: ttk.Widget): self._manager = manager frame = ttk.LabelFrame(master, text="Channels", padding=(4, 0, 4, 4)) - frame.grid(column=2, row=0, rowspan=3, sticky="nsew", padx=2) - frame.rowconfigure(1, weight=1) - frame.columnconfigure(0, weight=1) + frame.grid(column=2, row=1, rowspan=3, sticky="nsew", padx=2) # tell master frame that the containing column can expand master.columnconfigure(2, weight=1) + frame.rowconfigure(1, weight=1) + frame.columnconfigure(0, weight=1) buttons_frame = ttk.Frame(frame) self._buttons: _Buttons = { "frame": buttons_frame, @@ -911,6 +925,7 @@ class InventoryOverview: "expired": IntVar(master, 1), "upcoming": IntVar(master, 1), } + # Filtering options filter_frame = ttk.LabelFrame(master, text="Filter", padding=(4, 0, 4, 4)) LABEL_SPACING = 20 filter_frame.grid(column=0, row=0, columnspan=2, sticky="nsew") @@ -928,6 +943,7 @@ class InventoryOverview: filter_frame, text="Upcoming", padding=(0, 0, LABEL_SPACING, 0) ).grid(column=6, row=0) ttk.Button(filter_frame, text="Refresh", command=self.refresh).grid(column=7, row=0) + # Inventory view self._canvas = tk.Canvas(master, scrollregion=(0, 0, 0, 0)) self._canvas.grid(column=0, row=1, sticky="nsew") master.rowconfigure(1, weight=1) @@ -1546,6 +1562,7 @@ class GUIManager: # Main tab main_frame = ttk.Frame(root_frame, padding=8) self.tabs.add_tab(main_frame, name="Main") + self.status = StatusBar(self, main_frame) self.websockets = WebsocketStatus(self, main_frame) self.login = LoginForm(self, main_frame) self.progress = CampaignProgress(self, main_frame) diff --git a/twitch.py b/twitch.py index a98710c..7e32117 100644 --- a/twitch.py +++ b/twitch.py @@ -202,6 +202,7 @@ class Twitch: self.change_state(State.INVENTORY_FETCH) while True: if self._state is State.IDLE: + self.gui.status.update("Idle") self.stop_watching() # clear the flag and wait until it's set again self._state_change.clear() @@ -244,6 +245,7 @@ class Twitch: self.restart_watching() self.change_state(State.CHANNELS_CLEANUP) elif self._state is State.CHANNELS_CLEANUP: + self.gui.status.update("Cleaning up channels...") if not self.games or full_cleanup: # no games selected or we're doing full cleanup: remove everything to_remove: list[Channel] = list(channels.values()) @@ -280,6 +282,7 @@ class Twitch: ) self.change_state(State.IDLE) elif self._state is State.CHANNELS_FETCH: + self.gui.status.update("Gathering channels...") # start with all current channels new_channels: OrderedSet[Channel] = OrderedSet(self.channels.values()) # gather and add ACL channels from campaigns @@ -360,6 +363,7 @@ class Twitch: break self.change_state(State.CHANNEL_SWITCH) elif self._state is State.CHANNEL_SWITCH: + self.gui.status.update("Switching the channel...") # Change into the selected channel, stay in the watching channel, # or select a new channel that meets the required conditions new_watching = None @@ -388,9 +392,14 @@ class Twitch: # if we have a better switch target - do so # otherwise, continue watching what we had before self.watch(new_watching) + # should immediately complete, since we'll definitely + # have a channel we're watching here + watching_channel = await self.watching_channel.get() + self.gui.status.update(f"Watching {watching_channel.name}") # break the state change chain by clearing the flag self._state_change.clear() elif self._state is State.EXIT: + self.gui.status.update("Exiting...") # we've been requested to exit the application break await self._state_change.wait() @@ -908,6 +917,8 @@ class Twitch: return DropsCampaign(self, campaign_data, claimed_benefits) async def fetch_inventory(self) -> None: + status_update = self.gui.status.update + status_update("Fetching inventory...") # fetch in-progress campaigns (inventory) response = await self.gql_request(GQL_OPERATIONS["Inventory"]) inventory: JsonType = response["data"]["currentUser"]["inventory"] @@ -934,19 +945,27 @@ class Twitch: ) } # add campaigns that remained, that can be earned but are not in-progress yet - fetched_campaigns: list[DropsCampaign] = await asyncio.gather( - *( + status_update(f"Fetching campaigns... (0/{len(available_campaigns)})") + fetched_campaigns: list[DropsCampaign] = [] + for i, coro in enumerate( + # specifically use an intermediate list per a Python bug + # https://github.com/python/cpython/issues/88342 + asyncio.as_completed([ self.fetch_campaign(campaign_id, available_data, claimed_benefits) for campaign_id, available_data in available_campaigns.items() - ) - ) + ]), + start=1, + ): + fetched_campaigns.append(await coro) + status_update(f"Fetching campaigns... ({i}/{len(available_campaigns)})") campaigns.extend(fetched_campaigns) campaigns.sort(key=lambda c: c.ends_at) campaigns.sort(key=lambda c: not c.linked) self._drops.clear() self.gui.inv.clear() self.inventory.clear() - for campaign in campaigns: + for i, campaign in enumerate(campaigns, start=1): + status_update(f"Adding campaigns to inventory... ({i}/{len(campaigns)})") self._drops.update({drop.id: drop for drop in campaign.drops}) await self.gui.inv.add_campaign(campaign) self.inventory.append(campaign)