From 83c9db573e8d8237b9ef72e1ed287b0924cde899 Mon Sep 17 00:00:00 2001 From: Manuel Cortez Date: Fri, 8 Jul 2022 11:39:14 -0500 Subject: [PATCH] Added first stage of refactor for autocompletion users --- src/controller/buffers/mastodon/__init__.py | 2 + src/controller/buffers/mastodon/base.py | 141 +++++++++++++++++++ src/controller/settings.py | 8 +- src/extra/autocompletionUsers/__init__.py | 1 - src/extra/autocompletionUsers/manage.py | 16 ++- src/extra/autocompletionUsers/scan.py | 124 ++++++++++++++++ src/extra/autocompletionUsers/settings.py | 59 -------- src/extra/autocompletionUsers/wx_scan.py | 35 +++++ src/extra/autocompletionUsers/wx_settings.py | 27 ---- src/wxUI/dialogs/configuration.py | 9 +- 10 files changed, 326 insertions(+), 96 deletions(-) create mode 100644 src/controller/buffers/mastodon/__init__.py create mode 100644 src/controller/buffers/mastodon/base.py create mode 100644 src/extra/autocompletionUsers/scan.py delete mode 100644 src/extra/autocompletionUsers/settings.py create mode 100644 src/extra/autocompletionUsers/wx_scan.py delete mode 100644 src/extra/autocompletionUsers/wx_settings.py diff --git a/src/controller/buffers/mastodon/__init__.py b/src/controller/buffers/mastodon/__init__.py new file mode 100644 index 00000000..3f036c87 --- /dev/null +++ b/src/controller/buffers/mastodon/__init__.py @@ -0,0 +1,2 @@ +# -*- coding: utf-8 -*- +from .base import BaseBuffer \ No newline at end of file diff --git a/src/controller/buffers/mastodon/base.py b/src/controller/buffers/mastodon/base.py new file mode 100644 index 00000000..178dbd10 --- /dev/null +++ b/src/controller/buffers/mastodon/base.py @@ -0,0 +1,141 @@ +# -*- coding: utf-8 -*- +""" Common logic to all buffers in TWBlue.""" +import logging +import wx +import output +import sound +import widgetUtils + +log = logging.getLogger("controller.buffers.base.base") + +class Buffer(object): + """ A basic buffer object. This should be the base class for all other derived buffers.""" + + def __init__(self, parent=None, function=None, session=None, *args, **kwargs): + """Inits the main controller for this buffer: + @ parent wx.Treebook object: Container where we will put this buffer. + @ function str or None: function to be called periodically and update items on this buffer. + @ session sessionmanager.session object or None: Session handler for settings, database and data access. + """ + super(Buffer, self).__init__() + self.function = function + # Compose_function will be used to render an object on this buffer. Normally, signature is as follows: + # compose_function(item, db, relative_times, show_screen_names=False, session=None) + # Read more about compose functions in sessions/twitter/compose.py. + self.compose_function = None + self.args = args + self.kwargs = kwargs + # This will be used as a reference to the wx.Panel object wich stores the buffer GUI. + self.buffer = None + # This should countains the account associated to this buffer. + self.account = "" + # This controls whether the start_stream function should be called when starting the program. + self.needs_init = True + # if this is set to False, the buffer will be ignored on the invisible interface. + self.invisible = False + # Control variable, used to track time of execution for calls to start_stream. + self.execution_time = 0 + + def clear_list(self): + pass + + def get_event(self, ev): + """ Catch key presses in the WX interface and generate the corresponding event names.""" + if ev.GetKeyCode() == wx.WXK_RETURN and ev.ControlDown(): event = "audio" + elif ev.GetKeyCode() == wx.WXK_RETURN: event = "url" + elif ev.GetKeyCode() == wx.WXK_F5: event = "volume_down" + elif ev.GetKeyCode() == wx.WXK_F6: event = "volume_up" + elif ev.GetKeyCode() == wx.WXK_DELETE and ev.ShiftDown(): event = "clear_list" + elif ev.GetKeyCode() == wx.WXK_DELETE: event = "destroy_status" + # Raise a Special event when pressed Shift+F10 because Wx==4.1.x does not seems to trigger this by itself. + # See https://github.com/manuelcortez/TWBlue/issues/353 + elif ev.GetKeyCode() == wx.WXK_F10 and ev.ShiftDown(): event = "show_menu" + else: + event = None + ev.Skip() + if event != None: + try: + ### ToDo: Remove after WX fixes issue #353 in the widgets. + if event == "show_menu": + return self.show_menu(widgetUtils.MENU, pos=self.buffer.list.list.GetPosition()) + getattr(self, event)() + except AttributeError: + pass + + def volume_down(self): + """ Decreases volume by 5%""" + if self.session.settings["sound"]["volume"] > 0.0: + if self.session.settings["sound"]["volume"] <= 0.05: + self.session.settings["sound"]["volume"] = 0.0 + else: + self.session.settings["sound"]["volume"] -=0.05 + sound.URLPlayer.player.audio_set_volume(int(self.session.settings["sound"]["volume"]*100.0)) + self.session.sound.play("volume_changed.ogg") + self.session.settings.write() + + def volume_up(self): + """ Increases volume by 5%.""" + if self.session.settings["sound"]["volume"] < 1.0: + if self.session.settings["sound"]["volume"] >= 0.95: + self.session.settings["sound"]["volume"] = 1.0 + else: + self.session.settings["sound"]["volume"] +=0.05 + sound.URLPlayer.player.audio_set_volume(int(self.session.settings["sound"]["volume"]*100)) + self.session.sound.play("volume_changed.ogg") + self.session.settings.write() + + def start_stream(self, mandatory=False, play_sound=True): + pass + + def get_more_items(self): + output.speak(_(u"This action is not supported for this buffer"), True) + + def put_items_on_list(self, items): + pass + + def remove_buffer(self): + return False + + def remove_item(self, item): + f = self.buffer.list.get_selected() + self.buffer.list.remove_item(item) + self.buffer.list.select_item(f) + + def bind_events(self): + pass + + def get_object(self): + return self.buffer + + def get_message(self): + pass + + def set_list_position(self, reversed=False): + if reversed == False: + self.buffer.list.select_item(-1) + else: + self.buffer.list.select_item(0) + + def reply(self): + pass + + def send_message(self): + pass + + def share_item(self): + pass + + def can_share(self): + pass + + def destroy_status(self): + pass + + def post_status(self, *args, **kwargs): + pass + + def save_positions(self): + try: + self.session.db[self.name+"_pos"]=self.buffer.list.get_selected() + except AttributeError: + pass \ No newline at end of file diff --git a/src/controller/settings.py b/src/controller/settings.py index c4ab8d2f..c6d7f5cc 100644 --- a/src/controller/settings.py +++ b/src/controller/settings.py @@ -16,7 +16,7 @@ from pubsub import pub from mysc import autostart as autostart_windows from wxUI.dialogs import configuration from wxUI import commonMessageDialogs -from extra.autocompletionUsers import settings +from extra.autocompletionUsers import scan, manage from extra.ocr import OCRSpace from .editTemplateController import EditTemplate @@ -142,7 +142,7 @@ class accountSettingsController(globalSettingsController): def create_config(self): self.dialog.create_general_account() - widgetUtils.connect_event(self.dialog.general.au, widgetUtils.BUTTON_PRESSED, self.manage_autocomplete) + widgetUtils.connect_event(self.dialog.general.userAutocompletionStart, widgetUtils.BUTTON_PRESSED, self.on_autocompletion_start) self.dialog.set_value("general", "relative_time", self.config["general"]["relative_times"]) self.dialog.set_value("general", "show_screen_names", self.config["general"]["show_screen_names"]) self.dialog.set_value("general", "hide_emojis", self.config["general"]["hide_emojis"]) @@ -304,8 +304,8 @@ class accountSettingsController(globalSettingsController): def toggle_state(self,*args,**kwargs): return self.dialog.buffers.change_selected_item() - def manage_autocomplete(self, *args, **kwargs): - configuration = settings.autocompletionSettings(self.buffer.session.settings, self.buffer, self.window) + def on_autocompletion_start(self, *args, **kwargs): + configuration = scan.autocompletionScan(self.buffer.session.settings, self.buffer, self.window) def add_ignored_client(self, *args, **kwargs): client = commonMessageDialogs.get_ignored_client() diff --git a/src/extra/autocompletionUsers/__init__.py b/src/extra/autocompletionUsers/__init__.py index 475e8255..6b498407 100644 --- a/src/extra/autocompletionUsers/__init__.py +++ b/src/extra/autocompletionUsers/__init__.py @@ -1,4 +1,3 @@ # -*- coding: utf-8 -*- # -*- coding: utf-8 -*- """ Autocompletion users feature for TWBlue. This package contains all needed code to support this feature, including automatic addition of users, management and code to show the autocompletion menu when an user is composing a tweet. """ -from . import completion, settings diff --git a/src/extra/autocompletionUsers/manage.py b/src/extra/autocompletionUsers/manage.py index a1acd603..729c1bfb 100644 --- a/src/extra/autocompletionUsers/manage.py +++ b/src/extra/autocompletionUsers/manage.py @@ -1,15 +1,25 @@ # -*- coding: utf-8 -*- +""" Management of users in the local database for autocompletion. """ +import time import widgetUtils +from tweepy.cursor import Cursor from tweepy.errors import TweepyException from . import storage, wx_manage from wxUI import commonMessageDialogs -class autocompletionManage(object): +class autocompletionManager(object): def __init__(self, session): - super(autocompletionManage, self).__init__() + """ class constructor. Manages everything related to user autocompletion. + + :param session: Sessiom where the autocompletion management has been requested. + :type session: sessions.base.Session. + """ + super(autocompletionManager, self).__init__() self.session = session - self.dialog = wx_manage.autocompletionManageDialog() self.database = storage.storage(self.session.session_id) + + def show_settings(self): + self.dialog = wx_manage.autocompletionManageDialog() self.users = self.database.get_all_users() self.dialog.put_users(self.users) widgetUtils.connect_event(self.dialog.add, widgetUtils.BUTTON_PRESSED, self.add_user) diff --git a/src/extra/autocompletionUsers/scan.py b/src/extra/autocompletionUsers/scan.py new file mode 100644 index 00000000..aaa72daf --- /dev/null +++ b/src/extra/autocompletionUsers/scan.py @@ -0,0 +1,124 @@ +# -*- coding: utf-8 -*- +import time +import widgetUtils +import output +from tweepy.cursor import Cursor +from tweepy.errors import TweepyException +from pubsub import pub +from . import wx_scan +from . import manage +from . import storage +from mysc.thread_utils import call_threaded + +class autocompletionScan(object): + def __init__(self, config, buffer, window): + super(autocompletionScan, self).__init__() + self.config = config + self.buffer = buffer + self.window = window + self.dialog = wx_scan.autocompletionScanDialog() + self.dialog.set("friends", self.config["mysc"]["save_friends_in_autocompletion_db"]) + self.dialog.set("followers", self.config["mysc"]["save_followers_in_autocompletion_db"]) + if self.dialog.get_response() == widgetUtils.OK: + confirmation = wx_scan.confirm() + if confirmation == True: + self.progress_dialog = wx_scan.get_progress_dialog() + call_threaded(self.scan) + # connect method to update progress dialog + pub.subscribe(self.on_update_progress, "on-update-progress") + + def on_update_progress(self, percent): + print(percent) + if percent > 100: + percent = 100 + self.progress_dialog.Update(percent) + + def scan(self): + """ Attempts to add all users selected by current user to the autocomplete database. """ + ids = [] + self.config["mysc"]["save_friends_in_autocompletion_db"] = self.dialog.get("friends") + self.config["mysc"]["save_followers_in_autocompletion_db"] = self.dialog.get("followers") + output.speak(_("Updating database... You can close this window now. A message will tell you when the process finishes.")) + database = storage.storage(self.buffer.session.session_id) + total_steps = 0 + if self.dialog.get("friends") == True: + total_steps = total_steps + 2 + if self.dialog.get("followers") == True: + total_steps = total_steps + 2 + max_per_stage = 100/total_steps + percent = 0 + # Retrieve ids of all following users + if self.dialog.get("friends") == True: + for i in Cursor(self.buffer.session.twitter.get_friend_ids, count=5000).items(): + if str(i) not in ids: + ids.append(str(i)) + percent = percent + (100*max_per_stage/100) + pub.sendMessage("on-update-progress", percent=percent) + # same step, but for followers. + if self.dialog.get("followers") == True: + for i in Cursor(self.buffer.session.twitter.get_follower_ids, count=5000).items(): + if str(i) not in ids: + ids.append(str(i)) + percent = percent + (100*max_per_stage/100) + pub.sendMessage("on-update-progress", percent=percent) + # As next step requires batches of 100s users, let's split our user Ids so we won't break the param rules. + split_users = [ids[i:i + 100] for i in range(0, len(ids), 100)] + # store returned users in this list. + users = [] + for z in split_users: + if len(z) == 0: + print("Invalid user count") + continue + print(len(z)) + results = self.buffer.session.twitter.lookup_users(user_id=z) + users.extend(results) + time.sleep(1) + percent = percent + (max_per_stage/len(split_users)) + pub.sendMessage("on-update-progress", percent=percent) + for user in users: + database.set_user(user.screen_name, user.name, 1) + self.progress_dialog.Destroy() + wx_scan.show_success_dialog() + self.dialog.Destroy() + + def add_users_to_database(self): + self.config["mysc"]["save_friends_in_autocompletion_db"] = self.dialog.get("friends_buffer") + self.config["mysc"]["save_followers_in_autocompletion_db"] = self.dialog.get("followers_buffer") + output.speak(_(u"Updating database... You can close this window now. A message will tell you when the process finishes.")) + database = storage.storage(self.buffer.session.session_id) + if self.dialog.get("followers_buffer") == True: + buffer = self.window.search_buffer("followers", self.config["twitter"]["user_name"]) + for i in buffer.session.db[buffer.name]: + database.set_user(i.screen_name, i.name, 1) + else: + database.remove_by_buffer(1) + if self.dialog.get("friends_buffer") == True: + buffer = self.window.search_buffer("friends", self.config["twitter"]["user_name"]) + for i in buffer.session.db[buffer.name]: + database.set_user(i.screen_name, i.name, 2) + else: + database.remove_by_buffer(2) + wx_settings.show_success_dialog() + self.dialog.destroy() + + def view_list(self, ev): + q = manage.autocompletionManager(self.buffer.session) + q.show_settings() + +def execute_at_startup(window, buffer, config): + database = storage.storage(buffer.session.session_id) + if config["mysc"]["save_followers_in_autocompletion_db"] == True and config["other_buffers"]["show_followers"] == True: + buffer = window.search_buffer("followers", config["twitter"]["user_name"]) + for i in buffer.session.db[buffer.name]: + database.set_user(i.screen_name, i.name, 1) + else: + database.remove_by_buffer(1) + if config["mysc"]["save_friends_in_autocompletion_db"] == True and config["other_buffers"]["show_friends"] == True: + buffer = window.search_buffer("friends", config["twitter"]["user_name"]) + for i in buffer.session.db[buffer.name]: + database.set_user(i.screen_name, i.name, 2) + else: + database.remove_by_buffer(2) + + def __del__(self): + pub.unsubscribe(self.on_update_progress, "on-update-progress") \ No newline at end of file diff --git a/src/extra/autocompletionUsers/settings.py b/src/extra/autocompletionUsers/settings.py deleted file mode 100644 index d875fb0d..00000000 --- a/src/extra/autocompletionUsers/settings.py +++ /dev/null @@ -1,59 +0,0 @@ -# -*- coding: utf-8 -*- -import widgetUtils -import output -from . import wx_settings -from . import manage -from . import storage -from mysc.thread_utils import call_threaded - -class autocompletionSettings(object): - def __init__(self, config, buffer, window): - super(autocompletionSettings, self).__init__() - self.config = config - self.buffer = buffer - self.window = window - self.dialog = wx_settings.autocompletionSettingsDialog() - self.dialog.set("friends_buffer", self.config["mysc"]["save_friends_in_autocompletion_db"]) - self.dialog.set("followers_buffer", self.config["mysc"]["save_followers_in_autocompletion_db"]) - widgetUtils.connect_event(self.dialog.viewList, widgetUtils.BUTTON_PRESSED, self.view_list) - if self.dialog.get_response() == widgetUtils.OK: - call_threaded(self.add_users_to_database) - - def add_users_to_database(self): - self.config["mysc"]["save_friends_in_autocompletion_db"] = self.dialog.get("friends_buffer") - self.config["mysc"]["save_followers_in_autocompletion_db"] = self.dialog.get("followers_buffer") - output.speak(_(u"Updating database... You can close this window now. A message will tell you when the process finishes.")) - database = storage.storage(self.buffer.session.session_id) - if self.dialog.get("followers_buffer") == True: - buffer = self.window.search_buffer("followers", self.config["twitter"]["user_name"]) - for i in buffer.session.db[buffer.name]: - database.set_user(i.screen_name, i.name, 1) - else: - database.remove_by_buffer(1) - if self.dialog.get("friends_buffer") == True: - buffer = self.window.search_buffer("friends", self.config["twitter"]["user_name"]) - for i in buffer.session.db[buffer.name]: - database.set_user(i.screen_name, i.name, 2) - else: - database.remove_by_buffer(2) - wx_settings.show_success_dialog() - self.dialog.destroy() - - def view_list(self, ev): - q = manage.autocompletionManage(self.buffer.session) - - -def execute_at_startup(window, buffer, config): - database = storage.storage(buffer.session.session_id) - if config["mysc"]["save_followers_in_autocompletion_db"] == True and config["other_buffers"]["show_followers"] == True: - buffer = window.search_buffer("followers", config["twitter"]["user_name"]) - for i in buffer.session.db[buffer.name]: - database.set_user(i.screen_name, i.name, 1) - else: - database.remove_by_buffer(1) - if config["mysc"]["save_friends_in_autocompletion_db"] == True and config["other_buffers"]["show_friends"] == True: - buffer = window.search_buffer("friends", config["twitter"]["user_name"]) - for i in buffer.session.db[buffer.name]: - database.set_user(i.screen_name, i.name, 2) - else: - database.remove_by_buffer(2) diff --git a/src/extra/autocompletionUsers/wx_scan.py b/src/extra/autocompletionUsers/wx_scan.py new file mode 100644 index 00000000..a9554572 --- /dev/null +++ b/src/extra/autocompletionUsers/wx_scan.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- +import wx +import widgetUtils +import application + +class autocompletionScanDialog(widgetUtils.BaseDialog): + def __init__(self): + super(autocompletionScanDialog, self).__init__(parent=None, id=-1, title=_(u"Autocomplete users' settings")) + panel = wx.Panel(self) + sizer = wx.BoxSizer(wx.VERTICAL) + self.followers = wx.CheckBox(panel, -1, _("Add followers to the database")) + self.friends = wx.CheckBox(panel, -1, _("Add friends to database")) + sizer.Add(self.followers, 0, wx.ALL, 5) + sizer.Add(self.friends, 0, wx.ALL, 5) + ok = wx.Button(panel, wx.ID_OK) + cancel = wx.Button(panel, wx.ID_CANCEL) + sizerBtn = wx.BoxSizer(wx.HORIZONTAL) + sizerBtn.Add(ok, 0, wx.ALL, 5) + sizer.Add(cancel, 0, wx.ALL, 5) + sizer.Add(sizerBtn, 0, wx.ALL, 5) + panel.SetSizer(sizer) + self.SetClientSize(sizer.CalcMin()) + +def show_success_dialog(): + with wx.MessageDialog(None, _("{0}'s database of users has been updated.").format(application.name,), _(u"Done"), wx.OK)as dlg: + dlg.ShowModal() + +def confirm(): + with wx.MessageDialog(None, _("This process will retrieve the users you selected from Twitter, and add them to the user autocomplete database. Please note that if there are many users or you have tried to perform this action less than 15 minutes ago, TWBlue may reach a limit in Twitter API calls when trying to load the users into the database. If this happens, we will show you an error, in which case you will have to try this process again in a few minutes. Do you want to continue?"), _("Attention"), style=wx.ICON_QUESTION|wx.YES_NO) as result: + if result.ShowModal() == wx.ID_YES: + return True + return False + +def get_progress_dialog(): + return wx.ProgressDialog(_("Retrieving Twitter users from account..."), _("working..."), parent=None, maximum=100) diff --git a/src/extra/autocompletionUsers/wx_settings.py b/src/extra/autocompletionUsers/wx_settings.py deleted file mode 100644 index 3d635935..00000000 --- a/src/extra/autocompletionUsers/wx_settings.py +++ /dev/null @@ -1,27 +0,0 @@ -# -*- coding: utf-8 -*- -import wx -import widgetUtils -import application - -class autocompletionSettingsDialog(widgetUtils.BaseDialog): - def __init__(self): - super(autocompletionSettingsDialog, self).__init__(parent=None, id=-1, title=_(u"Autocomplete users' settings")) - panel = wx.Panel(self) - sizer = wx.BoxSizer(wx.VERTICAL) - self.followers_buffer = wx.CheckBox(panel, -1, _(u"Add users from followers buffer")) - self.friends_buffer = wx.CheckBox(panel, -1, _(u"Add users from friends buffer")) - sizer.Add(self.followers_buffer, 0, wx.ALL, 5) - sizer.Add(self.friends_buffer, 0, wx.ALL, 5) - self.viewList = wx.Button(panel, -1, _(u"Manage database...")) - sizer.Add(self.viewList, 0, wx.ALL, 5) - ok = wx.Button(panel, wx.ID_OK) - cancel = wx.Button(panel, wx.ID_CANCEL) - sizerBtn = wx.BoxSizer(wx.HORIZONTAL) - sizerBtn.Add(ok, 0, wx.ALL, 5) - sizer.Add(cancel, 0, wx.ALL, 5) - sizer.Add(sizerBtn, 0, wx.ALL, 5) - panel.SetSizer(sizer) - self.SetClientSize(sizer.CalcMin()) - -def show_success_dialog(): - wx.MessageDialog(None, _(u"{0}'s database of users has been updated.").format(application.name,), _(u"Done"), wx.OK).ShowModal() diff --git a/src/wxUI/dialogs/configuration.py b/src/wxUI/dialogs/configuration.py index ba5345c2..31405a66 100644 --- a/src/wxUI/dialogs/configuration.py +++ b/src/wxUI/dialogs/configuration.py @@ -99,8 +99,13 @@ class generalAccount(wx.Panel, baseDialog.BaseWXDialog): def __init__(self, parent): super(generalAccount, self).__init__(parent) sizer = wx.BoxSizer(wx.VERTICAL) - self.au = wx.Button(self, wx.ID_ANY, _(u"Autocompletion settings...")) - sizer.Add(self.au, 0, wx.ALL, 5) + userAutocompletionBox = wx.StaticBox(self, label=_("User autocompletion settings")) + self.userAutocompletionStart = wx.Button(self, wx.ID_ANY, _("Scan account and add friends and followers to the user autocompletion database")) + self.userAutocompletionManage = wx.Button(self, wx.ID_ANY, _("Manage autocompletion database")) + autocompletionSizer = wx.StaticBoxSizer(userAutocompletionBox, wx.HORIZONTAL) + autocompletionSizer.Add(self.userAutocompletionStart, 0, wx.ALL, 5) + autocompletionSizer.Add(self.userAutocompletionManage, 0, wx.ALL, 5) + sizer.Add(autocompletionSizer, 0, wx.ALL, 5) self.relative_time = wx.CheckBox(self, wx.ID_ANY, _(U"Relative timestamps")) sizer.Add(self.relative_time, 0, wx.ALL, 5) tweetsPerCallBox = wx.BoxSizer(wx.HORIZONTAL)