Mercurial > libervia-backend
comparison frontends/src/primitivus/profile_manager.py @ 1265:e3a9ea76de35 frontends_multi_profiles
quick_frontend, primitivus: multi-profiles refactoring part 1 (big commit, sorry :p):
This refactoring allow primitivus to manage correctly several profiles at once, with various other improvments:
- profile_manager can now plug several profiles at once, requesting password when needed. No more profile plug specific method is used anymore in backend, instead a "validated" key is used in actions
- Primitivus widget are now based on a common "PrimitivusWidget" classe which mainly manage the decoration so far
- all widgets are treated in the same way (contactList, Chat, Progress, etc), no more chat_wins specific behaviour
- widgets are created in a dedicated manager, with facilities to react on new widget creation or other events
- quick_frontend introduce a new QuickWidget class, which aims to be as generic and flexible as possible. It can manage several targets (jids or something else), and several profiles
- each widget class return a Hash according to its target. For example if given a target jid and a profile, a widget class return a hash like (target.bare, profile), the same widget will be used for all resources of the same jid
- better management of CHAT_GROUP mode for Chat widgets
- some code moved from Primitivus to QuickFrontend, the final goal is to have most non backend code in QuickFrontend, and just graphic code in subclasses
- no more (un)escapePrivate/PRIVATE_PREFIX
- contactList improved a lot: entities not in roster and special entities (private MUC conversations) are better managed
- resources can be displayed in Primitivus, and their status messages
- profiles are managed in QuickFrontend with dedicated managers
This is work in progress, other frontends are broken. Urwid SàText need to be updated. Most of features of Primitivus should work as before (or in a better way ;))
author | Goffi <goffi@goffi.org> |
---|---|
date | Wed, 10 Dec 2014 19:00:09 +0100 |
parents | 49d39b619e5d |
children | 7cf32aeeebdb |
comparison
equal
deleted
inserted
replaced
1264:60dfa2f5d61f | 1265:e3a9ea76de35 |
---|---|
16 | 16 |
17 # You should have received a copy of the GNU Affero General Public License | 17 # You should have received a copy of the GNU Affero General Public License |
18 # along with this program. If not, see <http://www.gnu.org/licenses/>. | 18 # along with this program. If not, see <http://www.gnu.org/licenses/>. |
19 | 19 |
20 from sat.core.i18n import _ | 20 from sat.core.i18n import _ |
21 from sat.core import log as logging | |
22 log = logging.getLogger(__name__) | |
21 from sat_frontends.primitivus.constants import Const as C | 23 from sat_frontends.primitivus.constants import Const as C |
24 from sat_frontends.primitivus.keys import action_key_map as a_key | |
25 from urwid_satext import sat_widgets | |
22 import urwid | 26 import urwid |
23 from urwid_satext.sat_widgets import AdvancedEdit, Password, List, InputDialog, ConfirmDialog, Alert | 27 |
24 from sat_frontends.primitivus.keys import action_key_map as a_key | 28 class ProfileRecord(object): |
29 | |
30 def __init__(self, profile=None, login=None, password=None): | |
31 self._profile = profile | |
32 self._login = login | |
33 self._password = password | |
34 | |
35 @property | |
36 def profile(self): | |
37 return self._profile | |
38 | |
39 @profile.setter | |
40 def profile(self, value): | |
41 self._profile = value | |
42 # if we change the profile, | |
43 # we must have no login/password until backend give them | |
44 self._login = self._password = None | |
45 | |
46 @property | |
47 def login(self): | |
48 return self._login | |
49 | |
50 @login.setter | |
51 def login(self, value): | |
52 self._login = value | |
53 | |
54 @property | |
55 def password(self): | |
56 return self._password | |
57 | |
58 @password.setter | |
59 def password(self, value): | |
60 self._password = value | |
25 | 61 |
26 | 62 |
27 class ProfileManager(urwid.WidgetWrap): | 63 class ProfileManager(urwid.WidgetWrap): |
28 | 64 """Class with manage profiles creation/deletion/connection""" |
29 def __init__(self, host): | 65 |
66 def __init__(self, host, autoconnect=None): | |
67 """Create the manager | |
68 | |
69 @param host: %(doc_host)s | |
70 @param autoconnect(iterable): list of profiles to connect automatically | |
71 """ | |
30 self.host = host | 72 self.host = host |
31 #profiles list | 73 self._autoconnect = bool(autoconnect) |
74 self.current = ProfileRecord() | |
32 profiles = self.host.bridge.getProfilesList() | 75 profiles = self.host.bridge.getProfilesList() |
33 profiles.sort() | 76 profiles.sort() |
34 | 77 |
35 #login & password box must be created before list because of onProfileChange | 78 #login & password box must be created before list because of onProfileChange |
36 self.login_wid = AdvancedEdit(_('Login:'), align='center') | 79 self.login_wid = sat_widgets.AdvancedEdit(_('Login:'), align='center') |
37 self.pass_wid = Password(_('Password:'), align='center') | 80 self.pass_wid = sat_widgets.Password(_('Password:'), align='center') |
38 | 81 |
39 self.selected_profile = None # allow to reselect the previous selection until the profile is authenticated | 82 style = ['no_first_select'] |
40 style = ['single'] | 83 self.list_profile = sat_widgets.List(profiles, style=style, align='center', on_change=self.onProfileChange) |
41 if self.host.options.profile: | |
42 style.append('no_first_select') | |
43 self.list_profile = List(profiles, style=style, align='center', on_change=self.onProfileChange) | |
44 | 84 |
45 #new & delete buttons | 85 #new & delete buttons |
46 buttons = [urwid.Button(_("New"), self.onNewProfile), | 86 buttons = [urwid.Button(_("New"), self.onNewProfile), |
47 urwid.Button(_("Delete"), self.onDeleteProfile)] | 87 urwid.Button(_("Delete"), self.onDeleteProfile)] |
48 buttons_flow = urwid.GridFlow(buttons, max([len(button.get_label()) for button in buttons])+4, 1, 1, 'center') | 88 buttons_flow = urwid.GridFlow(buttons, max([len(button.get_label()) for button in buttons])+4, 1, 1, 'center') |
49 | 89 |
50 #second part: login information: | 90 #second part: login information: |
51 divider = urwid.Divider('-') | 91 divider = urwid.Divider('-') |
52 | 92 |
53 #connect button | 93 #connect button |
54 connect_button = urwid.Button(_("Connect"), self.onConnectProfile) | 94 connect_button = sat_widgets.CustomButton(_("Connect"), self.onConnectProfiles, align='center') |
55 | 95 |
56 #we now build the widget | 96 #we now build the widget |
57 list_walker = urwid.SimpleFocusListWalker([buttons_flow,self.list_profile,divider,self.login_wid, self.pass_wid, connect_button]) | 97 list_walker = urwid.SimpleFocusListWalker([buttons_flow,self.list_profile, divider, self.login_wid, self.pass_wid, connect_button]) |
58 frame_body = urwid.ListBox(list_walker) | 98 frame_body = urwid.ListBox(list_walker) |
59 frame = urwid.Frame(frame_body,urwid.AttrMap(urwid.Text(_("Profile Manager"),align='center'),'title')) | 99 frame = urwid.Frame(frame_body,urwid.AttrMap(urwid.Text(_("Profile Manager"),align='center'),'title')) |
60 self.main_widget = urwid.LineBox(frame) | 100 self.main_widget = urwid.LineBox(frame) |
61 urwid.WidgetWrap.__init__(self, self.main_widget) | 101 urwid.WidgetWrap.__init__(self, self.main_widget) |
102 if self._autoconnect: | |
103 self.autoconnect(autoconnect) | |
104 | |
105 def autoconnect(self, profile_keys): | |
106 """Automatically connect profiles | |
107 | |
108 @param profile_keys(iterable): list of profile keys to connect | |
109 """ | |
110 if not profile_keys: | |
111 log.warning("No profile given to autoconnect") | |
112 return | |
113 self._autoconnect = True | |
114 self._autoconnect_profiles=[] | |
115 self._do_autoconnect(profile_keys) | |
116 | |
62 | 117 |
63 def keypress(self, size, key): | 118 def keypress(self, size, key): |
64 if key == a_key['APP_QUIT']: | 119 if key == a_key['APP_QUIT']: |
65 self.host.onExit() | 120 self.host.onExit() |
66 raise urwid.ExitMainLoop() | 121 raise urwid.ExitMainLoop() |
78 list_box.set_focus(current_focus, 'above' if focus_diff == 1 else 'below') | 133 list_box.set_focus(current_focus, 'above' if focus_diff == 1 else 'below') |
79 list_box._invalidate() | 134 list_box._invalidate() |
80 return | 135 return |
81 return super(ProfileManager, self).keypress(size, key) | 136 return super(ProfileManager, self).keypress(size, key) |
82 | 137 |
83 def __refillProfiles(self): | 138 def _do_autoconnect(self, profile_keys): |
139 """Connect automatically given profiles | |
140 | |
141 @param profile_kes(iterable): profiles to connect | |
142 """ | |
143 assert self._autoconnect | |
144 | |
145 def authenticate_cb(callback_id, data, profile): | |
146 | |
147 if C.bool(data['validated']): | |
148 self._autoconnect_profiles.append(profile) | |
149 if len(self._autoconnect_profiles) == len(profile_keys): | |
150 # all the profiles have been validated | |
151 self.host.plug_profiles(self._autoconnect_profiles) | |
152 else: | |
153 # a profile is not validated, we go to manual mode | |
154 self._autoconnect=False | |
155 | |
156 for profile_key in profile_keys: | |
157 profile = self.host.bridge.getProfileName(profile_key) | |
158 if not profile: | |
159 self._autoconnect = False # manual mode | |
160 msg = _("Trying to plug an unknown profile key ({})".format(profile_key)) | |
161 log.warning(msg) | |
162 popup = sat_widgets.Alert(_("Profile plugging in error"), msg, ok_cb=self.host.removePopUp) | |
163 self.host.showPopUp(popup) | |
164 break | |
165 self.host.launchAction(C.AUTHENTICATE_PROFILE_ID, callback=authenticate_cb, profile=profile) | |
166 | |
167 def refillProfiles(self): | |
84 """Update the list of profiles""" | 168 """Update the list of profiles""" |
85 profiles = self.host.bridge.getProfilesList() | 169 profiles = self.host.bridge.getProfilesList() |
86 profiles.sort() | 170 profiles.sort() |
87 self.list_profile.changeValues(profiles) | 171 self.list_profile.changeValues(profiles) |
172 self.host.redraw() | |
88 | 173 |
89 def cancelDialog(self, button): | 174 def cancelDialog(self, button): |
90 self.host.removePopUp() | 175 self.host.removePopUp() |
91 | 176 |
92 def newProfile(self, button, edit): | 177 def newProfile(self, button, edit): |
93 """Create the profile""" | 178 """Create the profile""" |
94 name = edit.get_edit_text() | 179 name = edit.get_edit_text() |
95 self.host.bridge.asyncCreateProfile(name, callback=lambda: self._newProfileCreated(name), errback=self._profileCreationFailure) | 180 self.host.bridge.asyncCreateProfile(name, callback=lambda: self.newProfileCreated(name), errback=self.profileCreationFailure) |
96 | 181 |
97 def _newProfileCreated(self, name): | 182 def newProfileCreated(self, profile): |
98 self.__refillProfiles() | 183 self.host.removePopUp() |
99 #We select the profile created in the list | 184 self.refillProfiles() |
100 self.list_profile.selectValue(name) | 185 self.list_profile.selectValue(profile) |
101 self.host.removePopUp() | 186 self.current.profile=profile |
187 self.getConnectionParams(profile) | |
102 self.host.redraw() | 188 self.host.redraw() |
103 | 189 |
104 def _profileCreationFailure(self, reason): | 190 def profileCreationFailure(self, reason): |
105 self.host.removePopUp() | 191 self.host.removePopUp() |
106 if reason == "ConflictError": | 192 if reason == "ConflictError": |
107 message = _("A profile with this name already exists") | 193 message = _("A profile with this name already exists") |
108 elif reason == "CancelError": | 194 elif reason == "CancelError": |
109 message = _("Profile creation cancelled by backend") | 195 message = _("Profile creation cancelled by backend") |
196 elif reason == "ValueError": | |
197 message = _("You profile name is not valid") # TODO: print a more informative message (empty name, name starting with '@') | |
110 else: | 198 else: |
111 message = _("Unknown reason (%s)") % reason | 199 message = _("Can't create profile ({})").format(reason) |
112 popup = Alert(_("Can't create profile"), message, ok_cb=self.host.removePopUp) | 200 popup = sat_widgets.Alert(_("Can't create profile"), message, ok_cb=self.host.removePopUp) |
113 self.host.showPopUp(popup) | 201 self.host.showPopUp(popup) |
114 | 202 |
115 def deleteProfile(self, button): | 203 def deleteProfile(self, button): |
116 profile_name = self.list_profile.getSelectedValue() | 204 if self.current.profile: |
117 if profile_name: | 205 self.host.bridge.asyncDeleteProfile(self.current.profile, callback=self.refillProfiles) |
118 self.host.bridge.asyncDeleteProfile(profile_name, callback=self.__refillProfiles) | 206 self.resetFields() |
119 self.host.removePopUp() | 207 self.host.removePopUp() |
120 | 208 |
121 def onNewProfile(self, e): | 209 def onNewProfile(self, e): |
122 pop_up_widget = InputDialog(_("New profile"), _("Please enter a new profile name"), cancel_cb=self.cancelDialog, ok_cb=self.newProfile) | 210 pop_up_widget = sat_widgets.InputDialog(_("New profile"), _("Please enter a new profile name"), cancel_cb=self.cancelDialog, ok_cb=self.newProfile) |
123 self.host.showPopUp(pop_up_widget) | 211 self.host.showPopUp(pop_up_widget) |
124 | 212 |
125 def onDeleteProfile(self, e): | 213 def onDeleteProfile(self, e): |
126 pop_up_widget = ConfirmDialog(_("Are you sure you want to delete the profile %s ?") % self.list_profile.getSelectedValue(), no_cb=self.cancelDialog, yes_cb=self.deleteProfile) | 214 if self.current.profile: |
127 self.host.showPopUp(pop_up_widget) | 215 pop_up_widget = sat_widgets.ConfirmDialog(_("Are you sure you want to delete the profile {} ?").format(self.current.profile), no_cb=self.cancelDialog, yes_cb=self.deleteProfile) |
128 | 216 self.host.showPopUp(pop_up_widget) |
129 def getXMPPParams(self, profile): | 217 |
130 """This is called from PrimitivusApp.launchAction when the profile has been authenticated. | 218 def resetFields(self): |
219 """Set profile to None, and reset fields""" | |
220 self.current.profile=None | |
221 self.login_wid.set_edit_text("") | |
222 self.pass_wid.set_edit_text("") | |
223 self.list_profile.unselectAll(invisible=True) | |
224 | |
225 def getConnectionParams(self, profile): | |
226 """Get login and password and display them | |
131 | 227 |
132 @param profile: %(doc_profile)s | 228 @param profile: %(doc_profile)s |
133 """ | 229 """ |
134 def setJID(jabberID): | 230 def setJID(jabberID): |
135 self.login_wid.set_edit_text(jabberID) | 231 self.login_wid.set_edit_text(jabberID) |
136 self.host.redraw() | 232 self.current.login = jabberID |
233 self.host.redraw() # FIXME: redraw should be avoided | |
137 | 234 |
138 def setPassword(password): | 235 def setPassword(password): |
139 self.pass_wid.set_edit_text(password) | 236 self.pass_wid.set_edit_text(password) |
237 self.current.password = password | |
140 self.host.redraw() | 238 self.host.redraw() |
141 | 239 |
142 self.list_profile.selectValue(profile, move_focus=False) | |
143 self.selected_profile = profile | |
144 self.host.bridge.asyncGetParamA("JabberID", "Connection", profile_key=profile, callback=setJID, errback=self.getParamError) | 240 self.host.bridge.asyncGetParamA("JabberID", "Connection", profile_key=profile, callback=setJID, errback=self.getParamError) |
145 self.host.bridge.asyncGetParamA("Password", "Connection", profile_key=profile, callback=setPassword, errback=self.getParamError) | 241 self.host.bridge.asyncGetParamA("Password", "Connection", profile_key=profile, callback=setPassword, errback=self.getParamError) |
146 | 242 |
243 def updateConnectionParams(self): | |
244 """Check if connection parameters have changed, and update them if so""" | |
245 if self.current.profile: | |
246 login = self.login_wid.get_edit_text() | |
247 password = self.pass_wid.get_edit_text() | |
248 if login != self.current.login and self.current.login is not None: | |
249 self.current.login = login | |
250 self.host.bridge.setParam("JabberID", login, "Connection", profile_key=self.current.profile) | |
251 log.info("login updated for profile [{}]".format(self.current.profile)) | |
252 if password != self.current.password and self.current.password is not None: | |
253 self.current.password = password | |
254 self.host.bridge.setParam("Password", password, "Connection", profile_key=self.current.profile) | |
255 log.info("password updated for profile [{}]".format(self.current.profile)) | |
256 | |
147 def onProfileChange(self, list_wid): | 257 def onProfileChange(self, list_wid): |
148 """This is called when a profile is selected in the profile list. | 258 """This is called when a profile is selected in the profile list. |
149 | 259 |
150 @param list_wid: the List widget who sent the event | 260 @param list_wid: the List widget who sent the event |
151 """ | 261 """ |
152 profile_name = list_wid.getSelectedValue() | 262 self.updateConnectionParams() |
153 if not profile_name or profile_name == self.selected_profile: | 263 focused = list_wid.focus |
154 return # avoid infinite loop | 264 selected = focused.getState() |
155 if self.selected_profile: | 265 if not selected: # profile was just unselected |
156 list_wid.selectValue(self.selected_profile, move_focus=False) | 266 return |
157 else: | 267 focused.setState(False, invisible=True) # we don't want the widget to be selected until we are sure we can access it |
158 list_wid.unselectAll(invisible=True) | 268 def authenticate_cb(callback_id, data, profile): |
159 self.host.redraw() | 269 if C.bool(data['validated']): |
160 self.host.profile = profile_name # FIXME: EXTREMELY DIRTY, needed for sat_frontends.tools.xmlui.XMLUI._xmluiLaunchAction | 270 self.current.profile = profile |
161 self.host.launchAction(C.AUTHENTICATE_PROFILE_ID, {'caller': 'profile_manager'}, profile_key=profile_name) | 271 focused.setState(True, invisible=True) |
162 | 272 self.getConnectionParams(profile) |
163 def onConnectProfile(self, button): | 273 self.host.redraw() |
164 profile_name = self.list_profile.getSelectedValue() | 274 self.host.launchAction(C.AUTHENTICATE_PROFILE_ID, callback=authenticate_cb, profile=focused.text) |
165 assert(profile_name == self.selected_profile) # if not, there's a bug somewhere... | 275 |
166 if not profile_name: | 276 def onConnectProfiles(self, button): |
167 pop_up_widget = Alert(_('No profile selected'), _('You need to create and select a profile before connecting'), ok_cb=self.cancelDialog) | 277 """Connect the profiles and start the main widget |
278 | |
279 @param button: the connect button | |
280 """ | |
281 if self._autoconnect: | |
282 pop_up_widget = sat_widgets.Alert(_('Internal error'), _('You can connect manually and automatically at the same time'), ok_cb=self.cancelDialog) | |
168 self.host.showPopUp(pop_up_widget) | 283 self.host.showPopUp(pop_up_widget) |
169 elif profile_name[0] == '@': | 284 return |
170 pop_up_widget = Alert(_('Bad profile name'), _("A profile name can't start with a @"), ok_cb=self.cancelDialog) | 285 self.updateConnectionParams() |
286 profiles = self.list_profile.getSelectedValues() | |
287 if not profiles: | |
288 pop_up_widget = sat_widgets.Alert(_('No profile selected'), _('You need to create and select at least one profile before connecting'), ok_cb=self.cancelDialog) | |
171 self.host.showPopUp(pop_up_widget) | 289 self.host.showPopUp(pop_up_widget) |
172 else: | 290 else: |
173 profile = self.host.bridge.getProfileName(profile_name) | 291 # All profiles in the list are already validated, so we can plug them directly |
174 assert(profile) | 292 self.host.plug_profiles(profiles) |
175 #TODO: move this to quick_app | 293 |
176 self.host.bridge.asyncGetParamA("JabberID", "Connection", profile_key=profile, | 294 def getParamError(self, dummy): |
177 callback=lambda old_jid: self.__old_jidReceived(old_jid, profile), errback=self.getParamError) | 295 popup = sat_widgets.Alert("Error", _("Can't get profile parameter"), ok_cb=self.host.removePopUp) |
178 | |
179 def __old_jidReceived(self, old_jid, profile): | |
180 self.host.bridge.asyncGetParamA("Password", "Connection", profile_key=profile, | |
181 callback=lambda old_pass: self.__old_passReceived(old_jid, old_pass, profile), errback=self.getParamError) | |
182 | |
183 def __old_passReceived(self, old_jid, old_pass, profile): | |
184 """Check if we have new jid/pass, save them if it is the case, and plug profile""" | |
185 new_jid = self.login_wid.get_edit_text() | |
186 new_pass = self.pass_wid.get_edit_text() | |
187 | |
188 if old_jid != new_jid: | |
189 self.host.bridge.setParam("JabberID", new_jid, "Connection", profile_key=profile) | |
190 if old_pass != new_pass: | |
191 self.host.bridge.setParam("Password", new_pass, "Connection", profile_key=profile) | |
192 self.host.plug_profile(profile) | |
193 | |
194 def getParamError(self, ignore): | |
195 popup = Alert("Error", _("Can't get profile parameter"), ok_cb=self.host.removePopUp) | |
196 self.host.showPopUp(popup) | 296 self.host.showPopUp(popup) |