initial cleanup of xmpppy perfomed, see #3260
This commit is contained in:
parent
cb2d629535
commit
6a15c9b9c9
50 changed files with 5273 additions and 9495 deletions
2
AUTHORS
2
AUTHORS
|
@ -6,10 +6,10 @@ Travis Shirk (travis AT pobox.com)
|
|||
Stephan Erb (steve-e AT h3c.de)
|
||||
Julien Pivotto (roidelapluie AT gmail.com)
|
||||
Jonathan Schleifer (js-gajim AT webkeks.org)
|
||||
Jean-Marie Traissard (jim AT lapin.org)
|
||||
|
||||
PAST DEVELOPERS:
|
||||
|
||||
Jean-Marie Traissard (jim AT lapin.org)
|
||||
Stefan Bethge (stefan AT lanpartei.de)
|
||||
Dimitur Kirov (dkirov AT gmail.com)
|
||||
Vincent Hanquez (tab AT snarc.org)
|
||||
|
|
|
@ -71,51 +71,6 @@
|
|||
<signal name="activate" handler="_on_toggle_e2e_menuitem_activate"/>
|
||||
</widget>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkMenuItem" id="otr_submenu">
|
||||
<property name="visible">True</property>
|
||||
<property name="sensitive">False</property>
|
||||
<property name="label" translatable="yes">Off-the-Record Encryption</property>
|
||||
<property name="use_underline">True</property>
|
||||
<child>
|
||||
<widget class="GtkMenu" id="otr_submenu_menu">
|
||||
<child>
|
||||
<widget class="GtkMenuItem" id="otr_settings_menuitem">
|
||||
<property name="visible">True</property>
|
||||
<property name="label" translatable="yes">OTR settings / fingerprint</property>
|
||||
<property name="use_underline">True</property>
|
||||
<signal name="activate" handler="_on_otr_settings_menuitem_activate"/>
|
||||
</widget>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkMenuItem" id="smp_otr_menuitem">
|
||||
<property name="visible">True</property>
|
||||
<property name="label" translatable="yes">Authenticate contact</property>
|
||||
<property name="use_underline">True</property>
|
||||
<signal name="activate" handler="_on_smp_otr_menuitem_activate"/>
|
||||
</widget>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkMenuItem" id="start_otr_menuitem">
|
||||
<property name="visible">True</property>
|
||||
<property name="label" translatable="yes">Start / Refresh OTR</property>
|
||||
<property name="use_underline">True</property>
|
||||
<signal name="activate" handler="_on_start_otr_menuitem_activate"/>
|
||||
</widget>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkMenuItem" id="end_otr_menuitem">
|
||||
<property name="visible">True</property>
|
||||
<property name="sensitive">False</property>
|
||||
<property name="label" translatable="yes">End OTR </property>
|
||||
<property name="use_underline">True</property>
|
||||
<signal name="activate" handler="_on_end_otr_menuitem_activate"/>
|
||||
</widget>
|
||||
</child>
|
||||
</widget>
|
||||
</child>
|
||||
</widget>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkSeparatorMenuItem" id="separatormenuitem1">
|
||||
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||
|
|
|
@ -1,322 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE glade-interface SYSTEM "glade-2.0.dtd">
|
||||
<!--Generated with glade3 3.4.2 on Wed Mar 26 13:56:40 2008 -->
|
||||
<glade-interface>
|
||||
<widget class="GtkWindow" id="otr_settings_window">
|
||||
<property name="resizable">False</property>
|
||||
<child>
|
||||
<widget class="GtkVBox" id="vbox1">
|
||||
<property name="visible">True</property>
|
||||
<child>
|
||||
<widget class="GtkNotebook" id="notebook1">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<child>
|
||||
<widget class="GtkVBox" id="otr_fp_vbox">
|
||||
<property name="visible">True</property>
|
||||
<property name="border_width">5</property>
|
||||
<property name="spacing">5</property>
|
||||
<property name="homogeneous">True</property>
|
||||
<child>
|
||||
<widget class="GtkLabel" id="our_fp_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="xalign">0</property>
|
||||
<property name="label" translatable="yes">Your fingerprint:
|
||||
<span weight="bold" face="monospace">01234567 89ABCDEF 01234567 89ABCDEF 01234567</span></property>
|
||||
<property name="use_markup">True</property>
|
||||
</widget>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkLabel" id="their_fp_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="xalign">0</property>
|
||||
<property name="label" translatable="yes">Purported fingerprint for asdfasdf@xyzxyzxyz.de:
|
||||
<span weight="bold" face="monospace">01234567 89ABCDEF 01234567 89ABCDEF 01234567</span></property>
|
||||
<property name="use_markup">True</property>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkHBox" id="hbox1">
|
||||
<property name="visible">True</property>
|
||||
<child>
|
||||
<widget class="GtkComboBox" id="verified_combobox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="active">1</property>
|
||||
<property name="items">I have NOT
|
||||
I have</property>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkLabel" id="label5">
|
||||
<property name="visible">True</property>
|
||||
<property name="xalign">0.20000000298023224</property>
|
||||
<property name="label" translatable="yes">verified that the purported fingerprint is in fact the correct fingerprint for that contact.</property>
|
||||
<property name="wrap">True</property>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</widget>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkLabel" id="label1">
|
||||
<property name="visible">True</property>
|
||||
<property name="label" translatable="yes">Authentication</property>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="type">tab</property>
|
||||
<property name="tab_fill">False</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkFrame" id="frame2">
|
||||
<property name="visible">True</property>
|
||||
<property name="label_xalign">0</property>
|
||||
<property name="shadow_type">GTK_SHADOW_NONE</property>
|
||||
<child>
|
||||
<widget class="GtkVBox" id="otr_settings_vbox">
|
||||
<property name="visible">True</property>
|
||||
<property name="border_width">5</property>
|
||||
<property name="spacing">5</property>
|
||||
<property name="homogeneous">True</property>
|
||||
<child>
|
||||
<widget class="GtkCheckButton" id="otr_policy_allow_v1_checkbutton">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="label" translatable="yes">OTR version 1 allowed</property>
|
||||
<property name="response_id">0</property>
|
||||
<property name="active">True</property>
|
||||
<property name="draw_indicator">True</property>
|
||||
</widget>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkCheckButton" id="otr_policy_allow_v2_checkbutton">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="label" translatable="yes">OTR version 2 allowed</property>
|
||||
<property name="response_id">0</property>
|
||||
<property name="active">True</property>
|
||||
<property name="draw_indicator">True</property>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkCheckButton" id="otr_policy_require_checkbutton">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="label" translatable="yes">Encryption required</property>
|
||||
<property name="response_id">0</property>
|
||||
<property name="draw_indicator">True</property>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkCheckButton" id="otr_policy_send_tag_checkbutton">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="label" translatable="yes">Show others we understand OTR</property>
|
||||
<property name="response_id">0</property>
|
||||
<property name="active">True</property>
|
||||
<property name="draw_indicator">True</property>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="position">3</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkCheckButton" id="otr_policy_start_on_tag_checkbutton">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="label" translatable="yes">Automatically initiate encryption if partner understands OTR</property>
|
||||
<property name="response_id">0</property>
|
||||
<property name="active">True</property>
|
||||
<property name="draw_indicator">True</property>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="position">4</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkCheckButton" id="otr_policy_start_on_error_checkbutton">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="label" translatable="yes">Automatically start OTR when an OTR error occured</property>
|
||||
<property name="response_id">0</property>
|
||||
<property name="draw_indicator">True</property>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="position">5</property>
|
||||
</packing>
|
||||
</child>
|
||||
</widget>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkCheckButton" id="otr_default_checkbutton">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="label" translatable="yes">Use the default settings</property>
|
||||
<property name="response_id">0</property>
|
||||
<property name="active">True</property>
|
||||
<property name="draw_indicator">True</property>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="type">label_item</property>
|
||||
</packing>
|
||||
</child>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkLabel" id="label2">
|
||||
<property name="visible">True</property>
|
||||
<property name="label" translatable="yes">OTR Settings</property>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="type">tab</property>
|
||||
<property name="position">1</property>
|
||||
<property name="tab_fill">False</property>
|
||||
</packing>
|
||||
</child>
|
||||
</widget>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkHButtonBox" id="hbuttonbox1">
|
||||
<property name="visible">True</property>
|
||||
<property name="layout_style">GTK_BUTTONBOX_END</property>
|
||||
<child>
|
||||
<widget class="GtkButton" id="settings_cancel_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="label" translatable="yes">gtk-cancel</property>
|
||||
<property name="use_stock">True</property>
|
||||
<property name="response_id">0</property>
|
||||
</widget>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkButton" id="settings_ok_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="can_default">True</property>
|
||||
<property name="has_default">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="label" translatable="yes">gtk-ok</property>
|
||||
<property name="use_stock">True</property>
|
||||
<property name="response_id">0</property>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</widget>
|
||||
</child>
|
||||
</widget>
|
||||
<widget class="GtkWindow" id="otr_smp_window">
|
||||
<property name="resizable">False</property>
|
||||
<child>
|
||||
<widget class="GtkVBox" id="vbox3">
|
||||
<property name="visible">True</property>
|
||||
<child>
|
||||
<widget class="GtkVBox" id="vbox4">
|
||||
<property name="visible">True</property>
|
||||
<property name="border_width">5</property>
|
||||
<property name="spacing">5</property>
|
||||
<child>
|
||||
<widget class="GtkLabel" id="desc_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="label" translatable="yes">label</property>
|
||||
<property name="use_markup">True</property>
|
||||
<property name="wrap">True</property>
|
||||
</widget>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkEntry" id="secret_entry">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</widget>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkHBox" id="hbox3">
|
||||
<property name="visible">True</property>
|
||||
<property name="border_width">5</property>
|
||||
<child>
|
||||
<widget class="GtkProgressBar" id="progressbar">
|
||||
<property name="visible">True</property>
|
||||
<property name="text" translatable="yes"></property>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkHButtonBox" id="hbuttonbox2">
|
||||
<property name="visible">True</property>
|
||||
<property name="layout_style">GTK_BUTTONBOX_END</property>
|
||||
<child>
|
||||
<widget class="GtkButton" id="smp_cancel_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="label" translatable="yes">gtk-cancel</property>
|
||||
<property name="use_stock">True</property>
|
||||
<property name="response_id">0</property>
|
||||
</widget>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkButton" id="smp_ok_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="label" translatable="yes">gtk-ok</property>
|
||||
<property name="use_stock">True</property>
|
||||
<property name="response_id">0</property>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</widget>
|
||||
</child>
|
||||
</widget>
|
||||
</glade-interface>
|
|
@ -91,13 +91,6 @@
|
|||
</child>
|
||||
</widget>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkImageMenuItem" id="tictactoe_menuitem">
|
||||
<property name="visible">True</property>
|
||||
<property name="label" translatable="yes">Play Tic Tac Toe</property>
|
||||
<property name="use_underline">True</property>
|
||||
</widget>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkImageMenuItem" id="manage_contact">
|
||||
<property name="visible">True</property>
|
||||
|
|
29
epydoc.conf
Normal file
29
epydoc.conf
Normal file
|
@ -0,0 +1,29 @@
|
|||
[epydoc]
|
||||
|
||||
# Information about the project.
|
||||
name: Gajim
|
||||
url: http://gajim.org
|
||||
|
||||
verbosity: 3
|
||||
imports: yes
|
||||
redundant-details: yes
|
||||
docformat: restructuredtext
|
||||
# top: gajim
|
||||
|
||||
# The list of modules to document. Modules can be named using
|
||||
# dotted names, module filenames, or package directory names.
|
||||
# This option may be repeated.
|
||||
modules: src/* test/*
|
||||
|
||||
# Write html output to the directory "apidocs"
|
||||
#output: pdf
|
||||
output: html
|
||||
target: apidocs/
|
||||
|
||||
# Include all automatically generated graphs. These graphs are
|
||||
# generated using Graphviz dot.
|
||||
graph: all
|
||||
dotpath: /usr/bin/dot
|
||||
graph-font: Sans
|
||||
graph-font-size: 10
|
||||
|
30
po/fr.po
30
po/fr.po
|
@ -1297,16 +1297,15 @@ msgstr "_Gérer le Salon"
|
|||
|
||||
#: ../data/glade/gc_control_popup_menu.glade.h:9
|
||||
msgid "_Minimize on close"
|
||||
msgstr "_Minimiser à la fermeteure"
|
||||
msgstr "_Minimiser à la fermeture"
|
||||
|
||||
#: ../data/glade/gc_occupants_menu.glade.h:1
|
||||
msgid "Mo_derator"
|
||||
msgstr "Mo_dérer"
|
||||
|
||||
#: ../data/glade/gc_occupants_menu.glade.h:2
|
||||
#, fuzzy
|
||||
msgid "Occupant Actions"
|
||||
msgstr "Actions des occupants"
|
||||
msgstr "Actions des Occupants"
|
||||
|
||||
#: ../data/glade/gc_occupants_menu.glade.h:5
|
||||
msgid "_Admin"
|
||||
|
@ -1538,52 +1537,44 @@ msgid "_Host:"
|
|||
msgstr "_Serveur :"
|
||||
|
||||
#: ../data/glade/message_window.glade.h:1
|
||||
#, fuzzy
|
||||
msgid "Add this contact to roster (Ctrl-D)"
|
||||
msgstr "Ajouter le contact à la liste"
|
||||
msgstr "Ajouter le contact à la liste (Ctrl-D)"
|
||||
|
||||
#: ../data/glade/message_window.glade.h:2
|
||||
#, fuzzy
|
||||
msgid "Bookmark this room (Ctrl-B)"
|
||||
msgstr "Enregistrer ce salon dans les marque-pages"
|
||||
msgstr "Enregistrer ce salon dans les marque-pages (Ctrl-B)"
|
||||
|
||||
#: ../data/glade/message_window.glade.h:3
|
||||
msgid "Browse the chat history (Ctrl-H)"
|
||||
msgstr "Parcourir l'historique des conversations (Ctrl+H)"
|
||||
|
||||
#: ../data/glade/message_window.glade.h:4
|
||||
#, fuzzy
|
||||
msgid "Change the room's subject (Ctrl-T)"
|
||||
msgstr "Changer le sujet du salon"
|
||||
msgstr "Changer le sujet du salon (Ctrl-T)"
|
||||
|
||||
#: ../data/glade/message_window.glade.h:5
|
||||
#, fuzzy
|
||||
msgid "Change your nickname (Ctrl-N)"
|
||||
msgstr "Changer votre surnom"
|
||||
msgstr "Changer votre surnom (Ctrl-N)"
|
||||
|
||||
#: ../data/glade/message_window.glade.h:6
|
||||
#, fuzzy
|
||||
msgid "Invite contacts to the conversation (Ctrl-G)"
|
||||
msgstr "Inviter des contacts dans la conversation"
|
||||
msgstr "Inviter des contacts dans la conversation (Ctrl-G)"
|
||||
|
||||
#: ../data/glade/message_window.glade.h:7
|
||||
#, fuzzy
|
||||
msgid "Send a file (Ctrl-F)"
|
||||
msgstr "Envoyer un fichier"
|
||||
msgstr "Envoyer un fichier (Ctrl-F)"
|
||||
|
||||
#: ../data/glade/message_window.glade.h:8
|
||||
msgid "Show a list of emoticons (Alt-M)"
|
||||
msgstr "Afficher une liste des émoticônes (Alt+M)"
|
||||
|
||||
#: ../data/glade/message_window.glade.h:9
|
||||
#, fuzzy
|
||||
msgid "Show a menu of advanced functions (Alt-A)"
|
||||
msgstr "Affiche un menu de fonctions avancées"
|
||||
msgstr "Affiche un menu de fonctions avancées (Alt-A)"
|
||||
|
||||
#: ../data/glade/message_window.glade.h:10
|
||||
#, fuzzy
|
||||
msgid "Show the contact's profile (Ctrl-I)"
|
||||
msgstr "Afficher le profil du contact (Ctrl+I)"
|
||||
msgstr "Afficher le profil du contact (Ctrl-I)"
|
||||
|
||||
#. Make sure the character after "_" is not M/m (conflicts with Alt+M that is supposed to show the Emoticon Selector)
|
||||
#: ../data/glade/message_window.glade.h:12
|
||||
|
@ -4509,7 +4500,6 @@ msgstr ""
|
|||
"« use_latex » à True dans l'éditeur de configuration avancée."
|
||||
|
||||
#: ../src/features_window.py:98
|
||||
#, fuzzy
|
||||
msgid "End to End Encryption"
|
||||
msgstr "Chiffrement de bout en bout"
|
||||
|
||||
|
|
|
@ -619,15 +619,16 @@ class ChatControlBase(MessageControl):
|
|||
'''Send the given message to the active tab. Doesn't return None if error
|
||||
'''
|
||||
if not message or message == '\n':
|
||||
return 1
|
||||
return None
|
||||
|
||||
ret = None
|
||||
|
||||
if not process_command or not self._process_command(message):
|
||||
ret = MessageControl.send_message(self, message, keyID, type = type,
|
||||
chatstate = chatstate, msg_id = msg_id,
|
||||
composing_xep = composing_xep, resource = resource,
|
||||
user_nick = self.user_nick)
|
||||
if ret:
|
||||
return ret
|
||||
|
||||
# Record message history
|
||||
self.save_sent_message(message)
|
||||
|
||||
|
@ -638,6 +639,8 @@ class ChatControlBase(MessageControl):
|
|||
message_buffer = self.msg_textview.get_buffer()
|
||||
message_buffer.set_text('') # clear message buffer (and tv of course)
|
||||
|
||||
return ret
|
||||
|
||||
def save_sent_message(self, message):
|
||||
# save the message, so user can scroll though the list with key up/down
|
||||
size = len(self.sent_history)
|
||||
|
@ -1112,7 +1115,9 @@ class ChatControl(ChatControlBase):
|
|||
self.on_avatar_eventbox_button_press_event)
|
||||
self.handlers[id] = widget
|
||||
|
||||
self.set_session(session)
|
||||
self.session = session
|
||||
if session:
|
||||
session.control = self
|
||||
|
||||
# Enable ecryption if needed
|
||||
e2e_is_active = hasattr(self, 'session') and self.session and self.session.enable_encryption
|
||||
|
@ -1133,9 +1138,6 @@ class ChatControl(ChatControlBase):
|
|||
|
||||
self.status_tooltip = gtk.Tooltips()
|
||||
|
||||
if gajim.otr_module:
|
||||
self.update_otr(True)
|
||||
|
||||
self.update_ui()
|
||||
# restore previous conversation
|
||||
self.restore_conversation()
|
||||
|
@ -1205,52 +1207,6 @@ class ChatControl(ChatControlBase):
|
|||
# The name banner is drawn here
|
||||
ChatControlBase.update_ui(self)
|
||||
|
||||
def get_otr_status(self):
|
||||
if not self.session:
|
||||
return 0
|
||||
|
||||
ctx = gajim.otr_module.otrl_context_find(
|
||||
self.session.conn.otr_userstates,
|
||||
self.contact.get_full_jid().encode(),
|
||||
gajim.get_jid_from_account(self.account).encode(),
|
||||
gajim.OTR_PROTO, 1, (gajim.otr_add_appdata,
|
||||
self.account))[0]
|
||||
|
||||
if ctx.msgstate == gajim.otr_module.OTRL_MSGSTATE_ENCRYPTED:
|
||||
if ctx.active_fingerprint.trust:
|
||||
return 2
|
||||
else:
|
||||
return 1
|
||||
elif ctx.msgstate == gajim.otr_module.OTRL_MSGSTATE_FINISHED:
|
||||
return 3
|
||||
return 0
|
||||
|
||||
def update_otr(self, print_status=False):
|
||||
otr_status_text = ''
|
||||
otr_status = self.get_otr_status()
|
||||
authenticated = False
|
||||
|
||||
if otr_status > 0:
|
||||
enc_status = True
|
||||
else:
|
||||
enc_status = False
|
||||
|
||||
if otr_status == 1:
|
||||
otr_status_text = u'*unauthenticated* secure OTR ' + \
|
||||
u'connection'
|
||||
elif otr_status == 2:
|
||||
otr_status_text = u'authenticated secure OTR ' + \
|
||||
u'connection'
|
||||
authenticated = True
|
||||
elif otr_status == 3:
|
||||
otr_status_text = u'finished OTR connection'
|
||||
|
||||
self._show_lock_image(enc_status, u'OTR', enc_status, True,
|
||||
authenticated)
|
||||
if print_status and otr_status_text != '':
|
||||
self.print_conversation_line(u'[OTR] %s' % \
|
||||
otr_status_text, 'status', '', None)
|
||||
|
||||
def _update_banner_state_image(self):
|
||||
contact = gajim.contacts.get_contact_with_highest_priority(self.account,
|
||||
self.contact.jid)
|
||||
|
@ -1497,7 +1453,7 @@ class ChatControl(ChatControlBase):
|
|||
def send_message(self, message, keyID = '', chatstate = None):
|
||||
'''Send a message to contact'''
|
||||
if message in ('', None, '\n') or self._process_command(message):
|
||||
return
|
||||
return None
|
||||
|
||||
# Do we need to process command for the message ?
|
||||
process_command = True
|
||||
|
@ -1540,7 +1496,7 @@ class ChatControl(ChatControlBase):
|
|||
# if peer supports jep85 and we are not 'ask', send 'active'
|
||||
# NOTE: first active and 'ask' is set in gajim.py
|
||||
elif composing_xep is not False:
|
||||
#send active chatstate on every message (as JEP says)
|
||||
# send active chatstate on every message (as XEP says)
|
||||
chatstate_to_send = 'active'
|
||||
contact.our_chatstate = 'active'
|
||||
|
||||
|
@ -1548,8 +1504,9 @@ class ChatControl(ChatControlBase):
|
|||
gobject.source_remove(self.possible_inactive_timeout_id)
|
||||
self._schedule_activity_timers()
|
||||
|
||||
if not ChatControlBase.send_message(self, message, keyID, type = 'chat',
|
||||
chatstate = chatstate_to_send, composing_xep = composing_xep,
|
||||
if ChatControlBase.send_message(self, message, keyID,
|
||||
type = 'chat', chatstate = chatstate_to_send,
|
||||
composing_xep = composing_xep,
|
||||
process_command = process_command):
|
||||
self.print_conversation(message, self.contact.jid,
|
||||
encrypted = encrypted)
|
||||
|
@ -1660,15 +1617,6 @@ class ChatControl(ChatControlBase):
|
|||
'NOT encrypted')
|
||||
ChatControlBase.print_conversation_line(
|
||||
self, msg, 'status', '', tim)
|
||||
elif gajim.otr_module and self.get_otr_status() > 0:
|
||||
# OTR
|
||||
# TODO: This is not shown when the window
|
||||
# isn't open - needs fixing!
|
||||
if not encrypted and frm == '':
|
||||
msg = _('The following message was ' + \
|
||||
'NOT encrypted')
|
||||
ChatControlBase.print_conversation_line(
|
||||
self, msg, 'status', '', tim)
|
||||
else:
|
||||
# GPG encryption
|
||||
if encrypted and not self.gpg_is_active:
|
||||
|
@ -1794,11 +1742,6 @@ class ChatControl(ChatControlBase):
|
|||
history_menuitem = xml.get_widget('history_menuitem')
|
||||
toggle_gpg_menuitem = xml.get_widget('toggle_gpg_menuitem')
|
||||
toggle_e2e_menuitem = xml.get_widget('toggle_e2e_menuitem')
|
||||
otr_submenu = xml.get_widget('otr_submenu')
|
||||
otr_settings_menuitem = xml.get_widget('otr_settings_menuitem')
|
||||
smp_otr_menuitem = xml.get_widget('smp_otr_menuitem')
|
||||
start_otr_menuitem = xml.get_widget('start_otr_menuitem')
|
||||
end_otr_menuitem = xml.get_widget('end_otr_menuitem')
|
||||
send_file_menuitem = xml.get_widget('send_file_menuitem')
|
||||
information_menuitem = xml.get_widget('information_menuitem')
|
||||
convert_to_gc_menuitem = xml.get_widget('convert_to_groupchat')
|
||||
|
@ -1891,32 +1834,6 @@ class ChatControl(ChatControlBase):
|
|||
self._on_convert_to_gc_menuitem_activate)
|
||||
self.handlers[id] = convert_to_gc_menuitem
|
||||
|
||||
if gajim.otr_module:
|
||||
otr_submenu.set_sensitive(True)
|
||||
id = otr_settings_menuitem.connect('activate',
|
||||
self._on_otr_settings_menuitem_activate)
|
||||
self.handlers[id] = otr_settings_menuitem
|
||||
id = start_otr_menuitem.connect('activate',
|
||||
self._on_start_otr_menuitem_activate)
|
||||
self.handlers[id] = start_otr_menuitem
|
||||
id = end_otr_menuitem.connect('activate',
|
||||
self._on_end_otr_menuitem_activate)
|
||||
self.handlers[id] = end_otr_menuitem
|
||||
id = smp_otr_menuitem.connect('activate',
|
||||
self._on_smp_otr_menuitem_activate)
|
||||
self.handlers[id] = smp_otr_menuitem
|
||||
|
||||
ctx = gajim.otr_module.otrl_context_find(gajim.connections[self.account].otr_userstates,
|
||||
self.contact.get_full_jid().encode(),
|
||||
gajim.get_jid_from_account(self.account).encode(), gajim.OTR_PROTO, 1,
|
||||
(gajim.otr_add_appdata, self.account))[0]
|
||||
# can end only when PLAINTEXT
|
||||
end_otr_menuitem.set_sensitive(ctx.msgstate !=
|
||||
gajim.otr_module.OTRL_MSGSTATE_PLAINTEXT)
|
||||
# can SMP only when ENCRYPTED
|
||||
smp_otr_menuitem.set_sensitive(ctx.msgstate ==
|
||||
gajim.otr_module.OTRL_MSGSTATE_ENCRYPTED)
|
||||
|
||||
menu.connect('selection-done', self.destroy_menu,
|
||||
send_file_menuitem, convert_to_gc_menuitem,
|
||||
information_menuitem, history_menuitem)
|
||||
|
@ -2037,7 +1954,7 @@ class ChatControl(ChatControlBase):
|
|||
# Clean events
|
||||
gajim.events.remove_events(self.account, self.get_full_jid(),
|
||||
types = ['printed_' + self.type_id, self.type_id])
|
||||
# remove all register handlers on wigets, created by self.xml
|
||||
# remove all register handlers on widgets, created by self.xml
|
||||
# to prevent circular references among objects
|
||||
for i in self.handlers.keys():
|
||||
if self.handlers[i].handler_is_connected(i):
|
||||
|
@ -2404,28 +2321,6 @@ class ChatControl(ChatControlBase):
|
|||
# XXX decide whether to use 4 or 3 message negotiation
|
||||
self.session.negotiate_e2e(False)
|
||||
|
||||
def _on_start_otr_menuitem_activate(self, widget):
|
||||
# ?OTR? gets replaced with a better message internally in otrl_message_sending
|
||||
MessageControl.send_message(self, u'?OTR?', type='chat')
|
||||
def _on_end_otr_menuitem_activate(self, widget):
|
||||
fjid = self.contact.get_full_jid()
|
||||
gajim.otr_module.otrl_message_disconnect(
|
||||
self.session.conn.otr_userstates, (gajim.otr_ui_ops,
|
||||
{'account': self.account, 'urgent': True}),
|
||||
gajim.get_jid_from_account(self.account).encode(),
|
||||
gajim.OTR_PROTO, fjid.encode())
|
||||
gajim.otr_ui_ops.gajim_log(_('Private conversation with ' \
|
||||
'%s lost.') % fjid, self.account, fjid.encode())
|
||||
self.update_otr()
|
||||
def _on_otr_settings_menuitem_activate(self, widget):
|
||||
gajim.otr_windows.ContactOtrWindow(self.contact, self.account, self)
|
||||
def _on_smp_otr_menuitem_activate(self, widget):
|
||||
ctx = gajim.otr_module.otrl_context_find(gajim.connections[self.account].otr_userstates,
|
||||
self.contact.get_full_jid().encode(),
|
||||
gajim.get_jid_from_account(self.account).encode(), gajim.OTR_PROTO, 1,
|
||||
(gajim.otr_add_appdata, self.account))[0]
|
||||
ctx.app_data.show(False)
|
||||
|
||||
def got_connected(self):
|
||||
ChatControlBase.got_connected(self)
|
||||
# Refreshing contact
|
||||
|
|
|
@ -250,6 +250,7 @@ class Config:
|
|||
'max_conversation_lines': [opt_int, 500, _('Maximum number of lines that are printed in conversations. Oldest lines are cleared.')],
|
||||
'attach_notifications_to_systray': [opt_bool, False, _('If True, notification windows from notification-daemon will be attached to systray icon.')],
|
||||
'check_idle_every_foo_seconds': [opt_int, 2, _('Choose interval between 2 checks of idleness.')],
|
||||
'latex_png_dpi': [opt_str, '108',_('Change the value to change the size of latex formulas displayed. The higher is larger.') ],
|
||||
}
|
||||
|
||||
__options_per_key = {
|
||||
|
@ -310,7 +311,8 @@ class Config:
|
|||
'zeroconf_jabber_id': [ opt_str, '', '', True ],
|
||||
'zeroconf_email': [ opt_str, '', '', True ],
|
||||
'use_env_http_proxy' : [opt_bool, False],
|
||||
'otr_flags': [opt_int, 58 ],
|
||||
'answer_receipt' : [opt_bool, True, _('Answer to receipt requests')],
|
||||
'request_receipt' : [opt_bool, True, _('Sent receipt requests')],
|
||||
'publish_mood': [opt_bool, True],
|
||||
'publish_activity': [opt_bool, True],
|
||||
'publish_tune': [opt_bool, False],
|
||||
|
@ -369,7 +371,6 @@ class Config:
|
|||
'contacts': ({
|
||||
'gpg_enabled': [ opt_bool, False, _('Is OpenPGP enabled for this contact?')],
|
||||
'speller_language': [ opt_str, '', _('Language for which we want to check misspelled words')],
|
||||
'otr_flags': [opt_int, -1 ],
|
||||
}, {}),
|
||||
'rooms': ({
|
||||
'speller_language': [ opt_str, '', _('Language for which we want to check misspelled words')],
|
||||
|
|
|
@ -364,9 +364,9 @@ class Connection(ConnectionHandlers):
|
|||
# data is (dict)
|
||||
self.dispatch('PRIVACY_LISTS_ACTIVE_DEFAULT', (data))
|
||||
elif realm == '':
|
||||
if event == common.xmpp.transports.DATA_RECEIVED:
|
||||
if event == common.xmpp.transports_nb.DATA_RECEIVED:
|
||||
self.dispatch('STANZA_ARRIVED', unicode(data, errors = 'ignore'))
|
||||
elif event == common.xmpp.transports.DATA_SENT:
|
||||
elif event == common.xmpp.transports_nb.DATA_SENT:
|
||||
self.dispatch('STANZA_SENT', unicode(data))
|
||||
|
||||
def select_next_host(self, hosts):
|
||||
|
@ -896,20 +896,6 @@ class Connection(ConnectionHandlers):
|
|||
self.on_connect_auth = self._init_roster
|
||||
self.connect_and_auth()
|
||||
|
||||
if gajim.otr_module:
|
||||
try:
|
||||
gajim.otr_module.otrl_privkey_read(self.otr_userstates,
|
||||
os.path.join(gajim.gajimpaths.root,
|
||||
'%s.key' % self.name).encode())
|
||||
gajim.otr_module.otrl_privkey_read_fingerprints(
|
||||
self.otr_userstates, os.path.join(
|
||||
gajim.gajimpaths.root, '%s.fpr' %
|
||||
self.name).encode(),
|
||||
(gajim.otr_add_appdata, self.name))
|
||||
except Exception, e:
|
||||
if not hasattr(e, 'os_errno') or e.os_errno != 2:
|
||||
raise
|
||||
|
||||
def _init_roster(self, con):
|
||||
self.connection = con
|
||||
if not self.connection:
|
||||
|
@ -1123,6 +1109,14 @@ class Connection(ConnectionHandlers):
|
|||
namespace=common.xmpp.NS_ADDRESS)
|
||||
addresses.addChild('address', attrs = {'type': 'ofrom',
|
||||
'jid': forward_from})
|
||||
|
||||
# TODO: We should also check if the other end supports it
|
||||
# as XEP 0184 says checking is a SHOULD. Maybe we should
|
||||
# implement section 6 of the XEP as well?
|
||||
if msgtxt and gajim.config.get_per('accounts', self.name,
|
||||
'request_receipt'):
|
||||
msg_iq.setTag('request', namespace='urn:xmpp:receipts')
|
||||
|
||||
if session:
|
||||
# XEP-0201
|
||||
session.last_send = time.time()
|
||||
|
@ -1132,8 +1126,7 @@ class Connection(ConnectionHandlers):
|
|||
if session.enable_encryption:
|
||||
msg_iq = session.encrypt_stanza(msg_iq)
|
||||
|
||||
|
||||
self.connection.send(msg_iq)
|
||||
msg_id = self.connection.send(msg_iq)
|
||||
if not forward_from and session and session.is_loggable():
|
||||
no_log_for = gajim.config.get_per('accounts', self.name, 'no_log_for')\
|
||||
.split()
|
||||
|
@ -1155,6 +1148,8 @@ class Connection(ConnectionHandlers):
|
|||
self.dispatch('ERROR', (_('Disk Write Error'), str(e)))
|
||||
self.dispatch('MSGSENT', (jid, msg, keyID))
|
||||
|
||||
return msg_id
|
||||
|
||||
def send_stanza(self, stanza):
|
||||
''' send a stanza untouched '''
|
||||
if not self.connection:
|
||||
|
@ -1532,6 +1527,15 @@ class Connection(ConnectionHandlers):
|
|||
# disconnect from jabber server
|
||||
self.connection.send(p)
|
||||
|
||||
def gc_got_disconnected(self, room_jid):
|
||||
''' A groupchat got disconnected. This can be or purpose or not.
|
||||
Save the time we quit to avoid duplicate logs AND be faster than get that
|
||||
date from DB. Save it in mem AND in a small table (with fast access)
|
||||
'''
|
||||
log_time = time_time()
|
||||
self.last_history_time[room_jid] = log_time
|
||||
gajim.logger.set_room_last_message_time(room_jid, log_time)
|
||||
|
||||
def gc_set_role(self, room_jid, nick, role, reason = ''):
|
||||
'''role is for all the life of the room so it's based on nick'''
|
||||
if not self.connection:
|
||||
|
|
|
@ -50,7 +50,8 @@ if dbus_support.supported:
|
|||
from music_track_listener import MusicTrackListener
|
||||
|
||||
from session import ChatControlSession
|
||||
import tictactoe
|
||||
|
||||
gajim.default_session_type = ChatControlSession
|
||||
|
||||
STATUS_LIST = ['offline', 'connecting', 'online', 'chat', 'away', 'xa', 'dnd',
|
||||
'invisible', 'error']
|
||||
|
@ -1217,9 +1218,6 @@ class ConnectionHandlersBase:
|
|||
# keep track of sessions this connection has with other JIDs
|
||||
self.sessions = {}
|
||||
|
||||
if gajim.otr_module:
|
||||
self.otr_userstates = gajim.otr_module.otrl_userstate_create()
|
||||
|
||||
def _FeatureNegCB(self, con, stanza, session):
|
||||
gajim.log.debug('FeatureNegCB')
|
||||
feature = stanza.getTag(name='feature', namespace=common.xmpp.NS_FEATURE)
|
||||
|
@ -1301,8 +1299,9 @@ sent a message to.'''
|
|||
# sessions that we haven't received a thread ID in
|
||||
idless = filter(lambda s: not s.received_thread_id, sessions)
|
||||
|
||||
# filter out everything exceptthe default session type
|
||||
chat_sessions = filter(lambda s: isinstance(s, ChatControlSession), idless)
|
||||
# filter out everything except the default session type
|
||||
p = lambda s: isinstance(s, gajim.default_session_type)
|
||||
chat_sessions = filter(p, idless)
|
||||
|
||||
if chat_sessions:
|
||||
# return the session that we last sent a message in
|
||||
|
@ -1311,10 +1310,25 @@ sent a message to.'''
|
|||
else:
|
||||
return None
|
||||
|
||||
# if deferred is true, the thread ID we're generating is tem
|
||||
def find_controlless_session(self, jid):
|
||||
'''find an active session that doesn't have a control attached'''
|
||||
|
||||
try:
|
||||
sessions = self.sessions[jid].values()
|
||||
|
||||
# filter out everything except the default session type
|
||||
p = lambda s: isinstance(s, gajim.default_session_type)
|
||||
chat_sessions = filter(p, sessions)
|
||||
|
||||
orphaned = filter(lambda s: not s.control, chat_sessions)
|
||||
|
||||
return orphaned[0]
|
||||
except KeyError:
|
||||
return None
|
||||
|
||||
def make_new_session(self, jid, thread_id=None, type='chat', cls=None):
|
||||
if not cls:
|
||||
cls = ChatControlSession
|
||||
cls = gajim.default_session_type
|
||||
|
||||
# determine if this session is a pm session
|
||||
# if not, discard the resource
|
||||
|
@ -1354,6 +1368,9 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
|
|||
except:
|
||||
HAS_IDLE = False
|
||||
|
||||
self.gmail_last_tid = None
|
||||
self.gmail_last_time = None
|
||||
|
||||
def build_http_auth_answer(self, iq_obj, answer):
|
||||
if answer == 'yes':
|
||||
self.connection.send(iq_obj.buildReply('result'))
|
||||
|
@ -1565,9 +1582,14 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
|
|||
jid = gajim.get_jid_from_account(self.name)
|
||||
gajim.log.debug('Got notification of new gmail e-mail on %s. Asking the server for more info.' % jid)
|
||||
iq = common.xmpp.Iq(typ = 'get')
|
||||
iq.setAttr('id', '13')
|
||||
iq.setID(self.connection.getAnID())
|
||||
query = iq.setTag('query')
|
||||
query.setNamespace(common.xmpp.NS_GMAILNOTIFY)
|
||||
# we want only be notified about newer mails
|
||||
if self.gmail_last_tid:
|
||||
query.setAttr('newer-than-tid', self.gmail_last_tid)
|
||||
if self.gmail_last_time:
|
||||
query.setAttr('newer-than-time', self.gmail_last_time)
|
||||
self.connection.send(iq)
|
||||
raise common.xmpp.NodeProcessed
|
||||
|
||||
|
@ -1584,16 +1606,34 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
|
|||
if gm.getTag('mailbox').getTag('mail-thread-info'):
|
||||
gmail_messages = gm.getTag('mailbox').getTags('mail-thread-info')
|
||||
for gmessage in gmail_messages:
|
||||
sender = gmessage.getTag('senders').getTag('sender')
|
||||
if not sender:
|
||||
unread_senders = []
|
||||
for sender in gmessage.getTag('senders').getTags('sender'):
|
||||
if sender.getAttr('unread') != '1':
|
||||
continue
|
||||
if sender.getAttr('name'):
|
||||
unread_senders.append(sender.getAttr('name') + '< ' + \
|
||||
sender.getAttr('address') + '>')
|
||||
else:
|
||||
unread_senders.append(sender.getAttr('address'))
|
||||
|
||||
if not unread_senders:
|
||||
continue
|
||||
gmail_from = sender.getAttr('address')
|
||||
gmail_subject = gmessage.getTag('subject').getData()
|
||||
gmail_snippet = gmessage.getTag('snippet').getData()
|
||||
tid = int(gmessage.getAttr('tid'))
|
||||
if not self.gmail_last_tid or tid > self.gmail_last_tid:
|
||||
self.gmail_last_tid = tid
|
||||
gmail_messages_list.append({ \
|
||||
'From': gmail_from, \
|
||||
'From': unread_senders, \
|
||||
'Subject': gmail_subject, \
|
||||
'Snippet': gmail_snippet})
|
||||
'Snippet': gmail_snippet, \
|
||||
'url': gmessage.getAttr('url'), \
|
||||
'participation': gmessage.getAttr('participation'), \
|
||||
'messages': gmessage.getAttr('messages'), \
|
||||
'date': gmessage.getAttr('date')})
|
||||
self.gmail_last_time = int(gm.getTag('mailbox').getAttr(
|
||||
'result-time'))
|
||||
|
||||
jid = gajim.get_jid_from_account(self.name)
|
||||
gajim.log.debug(('You have %s new gmail e-mails on %s.') % (newmsgs, jid))
|
||||
self.dispatch('GMAIL_NOTIFY', (jid, newmsgs, gmail_messages_list))
|
||||
|
@ -1650,96 +1690,21 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
|
|||
|
||||
encrypted = False
|
||||
xep_200_encrypted = msg.getTag('c', namespace=common.xmpp.NS_STANZA_CRYPTO)
|
||||
|
||||
# Receipt requested
|
||||
# TODO: We shouldn't answer if we're invisible!
|
||||
if msg.getTag('request', namespace='urn:xmpp:receipts') and \
|
||||
gajim.config.get_per('accounts', self.name, 'answer_receipt') \
|
||||
and gajim.contacts.get_contact_from_full_jid(self.name, frm). \
|
||||
sub not in (u'to', u'none'):
|
||||
receipt = common.xmpp.Message(to = jid, typ = 'chat')
|
||||
receipt.setID(msg.getID())
|
||||
receipt.setTag('received',
|
||||
namespace='urn:xmpp:receipts')
|
||||
receipt.setThread(thread_id)
|
||||
con.send(receipt)
|
||||
|
||||
# We don't trust libotr, that's why we only pass the message
|
||||
# to it if necessary. otrl_proto_message_type does this check.
|
||||
if gajim.otr_module and not xep_200_encrypted \
|
||||
and isinstance(msgtxt, unicode) and \
|
||||
gajim.otr_module.otrl_proto_message_type(msgtxt.encode()) != \
|
||||
gajim.otr_module.OTRL_MSGTYPE_NOTOTR:
|
||||
# set to encrypted if it's really encrypted.
|
||||
if gajim.otr_module.otrl_proto_message_type(
|
||||
msgtxt.encode()) != \
|
||||
gajim.otr_module.OTRL_MSGTYPE_TAGGEDPLAINTEXT:
|
||||
encrypted = True
|
||||
|
||||
# TODO: Do we really need .encode()?
|
||||
# yes we do. OTR can't handle unicode.
|
||||
otr_msg_tuple = \
|
||||
gajim.otr_module.otrl_message_receiving(
|
||||
self.otr_userstates,
|
||||
(gajim.otr_ui_ops, {'account': self.name}),
|
||||
gajim.get_jid_from_account(self.name).encode(),
|
||||
gajim.OTR_PROTO,
|
||||
frm.encode(),
|
||||
msgtxt.encode(),
|
||||
(gajim.otr_add_appdata, self.name))
|
||||
msgtxt = unicode(otr_msg_tuple[1])
|
||||
|
||||
html_node = msg.getTag('html')
|
||||
if html_node:
|
||||
msg.delChild(html_node)
|
||||
msg.setBody(msgtxt)
|
||||
|
||||
if gajim.otr_module.otrl_tlv_find(
|
||||
otr_msg_tuple[2],
|
||||
gajim.otr_module.OTRL_TLV_DISCONNECTED) != None:
|
||||
gajim.otr_ui_ops.gajim_log(_('%s ' \
|
||||
'has ended his/her private ' \
|
||||
'conversation with you. You should ' \
|
||||
'do the same.') % frm,
|
||||
self.name,
|
||||
frm.encode())
|
||||
|
||||
ctrls = gajim.interface.msg_win_mgr.get_chat_controls(jid, self.name)
|
||||
for ctrl in ctrls:
|
||||
ctrl.update_otr()
|
||||
|
||||
ctx = gajim.otr_module. \
|
||||
otrl_context_find(
|
||||
self.otr_userstates,
|
||||
frm.encode(),
|
||||
gajim.get_jid_from_account(
|
||||
self.name).encode(),
|
||||
gajim.OTR_PROTO, 1,
|
||||
(gajim.otr_add_appdata,
|
||||
self.name))[0]
|
||||
tlvs = otr_msg_tuple[2]
|
||||
ctx.app_data.handle_tlv(tlvs)
|
||||
|
||||
if msgtxt == '':
|
||||
return
|
||||
elif msgtxt != None and msgtxt != '':
|
||||
gajim.otr_dont_append_tag[frm] = True
|
||||
|
||||
# We're also here if we just don't
|
||||
# support OTR. Thus, we should strip
|
||||
# the tags from plaintext messages
|
||||
# since they look ugly.
|
||||
msgtxt = msgtxt.replace('\x20\x09\x20' \
|
||||
'\x20\x09\x09\x09\x09\x20\x09' \
|
||||
'\x20\x09\x20\x09\x20\x20', '')
|
||||
msgtxt = msgtxt.replace('\x20\x09\x20' \
|
||||
'\x09\x20\x20\x09\x20', '')
|
||||
msgtxt = msgtxt.replace('\x20\x20\x09' \
|
||||
'\x09\x20\x20\x09\x20', '')
|
||||
|
||||
game_invite = msg.getTag('invite', namespace='http://jabber.org/protocol/games')
|
||||
if game_invite:
|
||||
game = game_invite.getTag('game')
|
||||
|
||||
if game.getAttr('var') == \
|
||||
'http://jabber.org/protocol/games/tictactoe':
|
||||
cls = tictactoe.TicTacToeSession
|
||||
|
||||
# this assumes that the invitation came with a thread_id we haven't
|
||||
# seen
|
||||
session = self.make_new_session(frm, thread_id, cls=cls)
|
||||
|
||||
session.invited(msg)
|
||||
|
||||
return
|
||||
elif mtype != 'groupchat':
|
||||
if mtype != 'groupchat':
|
||||
session = self.get_or_create_session(frm, thread_id)
|
||||
|
||||
if thread_id and not session.received_thread_id:
|
||||
|
@ -1851,7 +1816,6 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
|
|||
no_log_for = ''
|
||||
|
||||
no_log_for = no_log_for.split()
|
||||
|
||||
tim_int = int(float(mktime(tim)))
|
||||
|
||||
if self.name not in no_log_for and jid not in no_log_for and not \
|
||||
|
@ -1861,8 +1825,6 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
|
|||
# so don't store it in logs
|
||||
try:
|
||||
gajim.logger.write('gc_msg', frm, msgtxt, tim=tim)
|
||||
# save the time we log to avoid duplicate logs
|
||||
self.last_history_time[jid] = tim_int
|
||||
except exceptions.PysqliteOperationalError, e:
|
||||
self.dispatch('ERROR', (_('Disk Write Error'), str(e)))
|
||||
|
||||
|
|
|
@ -32,7 +32,7 @@ class Contact:
|
|||
self.jid = jid
|
||||
self.name = name
|
||||
self.contact_name = '' # nick choosen by contact
|
||||
self.groups = groups
|
||||
self.groups = groups # See below for what we do if it's empty
|
||||
self.show = show
|
||||
self.status = status
|
||||
self.sub = sub
|
||||
|
@ -63,7 +63,11 @@ class Contact:
|
|||
# this is contact's chatstate
|
||||
self.chatstate = chatstate
|
||||
self.last_status_time = last_status_time
|
||||
|
||||
if not self.groups:
|
||||
if self.is_observer():
|
||||
self.groups = [_('Observers')]
|
||||
else:
|
||||
self.groups = [_('General')]
|
||||
self.mood = mood.copy()
|
||||
self.tune = tune.copy()
|
||||
self.activity = activity.copy()
|
||||
|
@ -246,9 +250,10 @@ class Contacts:
|
|||
return None
|
||||
|
||||
def iter_contacts(self, account):
|
||||
for jid in self._contacts[account]:
|
||||
for contact in self._contacts[account][jid]:
|
||||
yield contact
|
||||
if account in self._contacts:
|
||||
for jid in self._contacts[account]:
|
||||
for contact in self._contacts[account][jid]:
|
||||
yield contact
|
||||
|
||||
def get_contact_from_full_jid(self, account, fjid):
|
||||
''' Get Contact object for specific resource of given jid'''
|
||||
|
@ -310,18 +315,8 @@ class Contacts:
|
|||
if groups == []:
|
||||
in_groups = True
|
||||
else:
|
||||
contact_groups = contact.groups
|
||||
if not contact_groups:
|
||||
# Contact is not in a group, so count it in General or
|
||||
# Transports group
|
||||
if common.gajim.jid_is_transport(jid):
|
||||
contact_groups = [_('Transports')]
|
||||
if contact.is_observer():
|
||||
contact_groups = [_('Observers')]
|
||||
else:
|
||||
contact_groups = [_('General')]
|
||||
for group in groups:
|
||||
if group in contact_groups:
|
||||
if group in contact.groups:
|
||||
in_groups = True
|
||||
break
|
||||
|
||||
|
|
|
@ -29,8 +29,11 @@ _GAJIM_ERROR_IFACE = 'org.gajim.dbus.Error'
|
|||
|
||||
try:
|
||||
if sys.platform == 'darwin':
|
||||
import osx.dbus
|
||||
osx.dbus.load(True)
|
||||
try:
|
||||
import osx.dbus
|
||||
osx.dbus.load(True)
|
||||
except ImportError:
|
||||
pass
|
||||
import dbus
|
||||
import dbus.service
|
||||
import dbus.glib
|
||||
|
|
|
@ -165,13 +165,6 @@ else:
|
|||
if system('gpg -h >/dev/null 2>&1'):
|
||||
HAVE_GPG = False
|
||||
|
||||
OTR_PROTO = "xmpp"
|
||||
otr_userstates = {}
|
||||
otr_policy = {}
|
||||
|
||||
# list of (full) jids not to attempt OTR with
|
||||
otr_dont_append_tag = {}
|
||||
|
||||
gajim_identity = {'type': 'pc', 'category': 'client', 'name': 'Gajim'}
|
||||
gajim_common_features = [xmpp.NS_BYTESTREAM, xmpp.NS_SI,
|
||||
xmpp.NS_FILE, xmpp.NS_MUC, xmpp.NS_MUC_USER,
|
||||
|
|
|
@ -53,8 +53,10 @@ except ImportError:
|
|||
hash_md5 = md5.new
|
||||
hash_sha1 = sha.new
|
||||
|
||||
if sys.platform == 'darwin':
|
||||
try:
|
||||
from osx import nsapp
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
try:
|
||||
import winsound # windows-only built-in module for playing wav
|
||||
|
@ -570,7 +572,10 @@ def play_sound_file(path_to_soundfile):
|
|||
if path_to_soundfile is None or not os.path.exists(path_to_soundfile):
|
||||
return
|
||||
if sys.platform == 'darwin':
|
||||
nsapp.playFile(path_to_soundfile)
|
||||
try:
|
||||
nsapp.playFile(path_to_soundfile)
|
||||
except NameError:
|
||||
pass
|
||||
elif os.name == 'nt':
|
||||
try:
|
||||
winsound.PlaySound(path_to_soundfile,
|
||||
|
|
|
@ -26,7 +26,7 @@ and use only methods for access all values you should not have any problems.
|
|||
|
||||
"""
|
||||
|
||||
import simplexml,protocol,debug,auth_nb,auth,transports,transports_nb,roster_nb,roster,dispatcher_nb,features_nb,features,browser,filetransfer,commands, idlequeue
|
||||
import simplexml,protocol,debug,auth_nb,transports_nb,roster_nb,dispatcher_nb,features_nb,idlequeue
|
||||
from client_nb import *
|
||||
from client import *
|
||||
from protocol import *
|
||||
|
|
|
@ -1,306 +0,0 @@
|
|||
## auth.py
|
||||
##
|
||||
## Copyright (C) 2003-2005 Alexey "Snake" Nezhdanov
|
||||
##
|
||||
## This program is free software; you can redistribute it and/or modify
|
||||
## it under the terms of the GNU General Public License as published by
|
||||
## the Free Software Foundation; either version 2, or (at your option)
|
||||
## any later version.
|
||||
##
|
||||
## This program is distributed in the hope that it will be useful,
|
||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
## GNU General Public License for more details.
|
||||
|
||||
# $Id: auth.py,v 1.35 2006/01/18 19:26:43 normanr Exp $
|
||||
|
||||
"""
|
||||
Provides library with all Non-SASL and SASL authentication mechanisms.
|
||||
Can be used both for client and transport authentication.
|
||||
"""
|
||||
|
||||
from protocol import *
|
||||
from client import PlugIn
|
||||
import sha,base64,random,dispatcher
|
||||
|
||||
import md5
|
||||
def HH(some): return md5.new(some).hexdigest()
|
||||
def H(some): return md5.new(some).digest()
|
||||
def C(some): return ':'.join(some)
|
||||
|
||||
class NonSASL(PlugIn):
|
||||
""" Implements old Non-SASL (JEP-0078) authentication used in jabberd1.4 and transport authentication."""
|
||||
def __init__(self,user,password,resource):
|
||||
""" Caches username, password and resource for auth. """
|
||||
PlugIn.__init__(self)
|
||||
self.DBG_LINE='gen_auth'
|
||||
self.user=user
|
||||
self.password=password
|
||||
self.resource=resource
|
||||
|
||||
def plugin(self,owner):
|
||||
""" Determine the best auth method (digest/0k/plain) and use it for auth.
|
||||
Returns used method name on success. Used internally. """
|
||||
if not self.resource: return self.authComponent(owner)
|
||||
self.DEBUG('Querying server about possible auth methods','start')
|
||||
resp=owner.Dispatcher.SendAndWaitForResponse(Iq('get',NS_AUTH,payload=[Node('username',payload=[self.user])]))
|
||||
if not isResultNode(resp):
|
||||
self.DEBUG('No result node arrived! Aborting...','error')
|
||||
return
|
||||
iq=Iq(typ='set',node=resp)
|
||||
query=iq.getTag('query')
|
||||
query.setTagData('username',self.user)
|
||||
query.setTagData('resource',self.resource)
|
||||
|
||||
if query.getTag('digest'):
|
||||
self.DEBUG("Performing digest authentication",'ok')
|
||||
query.setTagData('digest',sha.new(owner.Dispatcher.Stream._document_attrs['id']+self.password).hexdigest())
|
||||
if query.getTag('password'): query.delChild('password')
|
||||
method='digest'
|
||||
elif query.getTag('token'):
|
||||
token=query.getTagData('token')
|
||||
seq=query.getTagData('sequence')
|
||||
self.DEBUG("Performing zero-k authentication",'ok')
|
||||
hash = sha.new(sha.new(self.password).hexdigest()+token).hexdigest()
|
||||
for foo in xrange(int(seq)): hash = sha.new(hash).hexdigest()
|
||||
query.setTagData('hash',hash)
|
||||
method='0k'
|
||||
else:
|
||||
self.DEBUG("Sequre methods unsupported, performing plain text authentication",'warn')
|
||||
query.setTagData('password',self.password)
|
||||
method='plain'
|
||||
resp=owner.Dispatcher.SendAndWaitForResponse(iq)
|
||||
if isResultNode(resp):
|
||||
self.DEBUG('Sucessfully authenticated with remove host.','ok')
|
||||
owner.User=self.user
|
||||
owner.Resource=self.resource
|
||||
owner._registered_name=owner.User+'@'+owner.Server+'/'+owner.Resource
|
||||
return method
|
||||
self.DEBUG('Authentication failed!','error')
|
||||
|
||||
def authComponent(self,owner):
|
||||
""" Authenticate component. Send handshake stanza and wait for result. Returns "ok" on success. """
|
||||
self.handshake=0
|
||||
owner.send(Node(NS_COMPONENT_ACCEPT+' handshake',payload=[sha.new(owner.Dispatcher.Stream._document_attrs['id']+self.password).hexdigest()]))
|
||||
owner.RegisterHandler('handshake',self.handshakeHandler,xmlns=NS_COMPONENT_ACCEPT)
|
||||
while not self.handshake:
|
||||
self.DEBUG("waiting on handshake",'notify')
|
||||
owner.Process(1)
|
||||
owner._registered_name=self.user
|
||||
if self.handshake+1: return 'ok'
|
||||
|
||||
def handshakeHandler(self,disp,stanza):
|
||||
""" Handler for registering in dispatcher for accepting transport authentication. """
|
||||
if stanza.getName()=='handshake': self.handshake=1
|
||||
else: self.handshake=-1
|
||||
|
||||
class SASL(PlugIn):
|
||||
""" Implements SASL authentication. """
|
||||
def __init__(self,username,password):
|
||||
PlugIn.__init__(self)
|
||||
self.username=username
|
||||
self.password=password
|
||||
|
||||
def plugin(self,owner):
|
||||
if not self._owner.Dispatcher.Stream._document_attrs.has_key('version'): self.startsasl='not-supported'
|
||||
elif self._owner.Dispatcher.Stream.features:
|
||||
try: self.FeaturesHandler(self._owner.Dispatcher,self._owner.Dispatcher.Stream.features)
|
||||
except NodeProcessed: pass
|
||||
else: self.startsasl=None
|
||||
|
||||
def auth(self):
|
||||
""" Start authentication. Result can be obtained via "SASL.startsasl" attribute and will be
|
||||
either "success" or "failure". Note that successfull auth will take at least
|
||||
two Dispatcher.Process() calls. """
|
||||
if self.startsasl: pass
|
||||
elif self._owner.Dispatcher.Stream.features:
|
||||
try: self.FeaturesHandler(self._owner.Dispatcher,self._owner.Dispatcher.Stream.features)
|
||||
except NodeProcessed: pass
|
||||
else: self._owner.RegisterHandler('features',self.FeaturesHandler,xmlns=NS_STREAMS)
|
||||
|
||||
def plugout(self):
|
||||
""" Remove SASL handlers from owner's dispatcher. Used internally. """
|
||||
self._owner.UnregisterHandler('features',self.FeaturesHandler,xmlns=NS_STREAMS)
|
||||
self._owner.UnregisterHandler('challenge',self.SASLHandler,xmlns=NS_SASL)
|
||||
self._owner.UnregisterHandler('failure',self.SASLHandler,xmlns=NS_SASL)
|
||||
self._owner.UnregisterHandler('success',self.SASLHandler,xmlns=NS_SASL)
|
||||
|
||||
def FeaturesHandler(self,conn,feats):
|
||||
""" Used to determine if server supports SASL auth. Used internally. """
|
||||
if not feats.getTag('mechanisms',namespace=NS_SASL):
|
||||
self.startsasl='not-supported'
|
||||
self.DEBUG('SASL not supported by server','error')
|
||||
return
|
||||
mecs=[]
|
||||
for mec in feats.getTag('mechanisms',namespace=NS_SASL).getTags('mechanism'):
|
||||
mecs.append(mec.getData())
|
||||
self._owner.RegisterHandler('challenge',self.SASLHandler,xmlns=NS_SASL)
|
||||
self._owner.RegisterHandler('failure',self.SASLHandler,xmlns=NS_SASL)
|
||||
self._owner.RegisterHandler('success',self.SASLHandler,xmlns=NS_SASL)
|
||||
if "DIGEST-MD5" in mecs:
|
||||
node=Node('auth',attrs={'xmlns':NS_SASL,'mechanism':'DIGEST-MD5'})
|
||||
elif "PLAIN" in mecs:
|
||||
sasl_data='%s\x00%s\x00%s'%(self.username+'@'+self._owner.Server,self.username,self.password)
|
||||
node=Node('auth',attrs={'xmlns':NS_SASL,'mechanism':'PLAIN'},payload=[base64.encodestring(sasl_data)])
|
||||
else:
|
||||
self.startsasl='failure'
|
||||
self.DEBUG('I can only use DIGEST-MD5 and PLAIN mecanisms.','error')
|
||||
return
|
||||
self.startsasl='in-process'
|
||||
self._owner.send(node.__str__())
|
||||
raise NodeProcessed
|
||||
|
||||
def SASLHandler(self,conn,challenge):
|
||||
""" Perform next SASL auth step. Used internally. """
|
||||
if challenge.getNamespace()<>NS_SASL: return
|
||||
if challenge.getName()=='failure':
|
||||
self.startsasl='failure'
|
||||
try: reason=challenge.getChildren()[0]
|
||||
except: reason=challenge
|
||||
self.DEBUG('Failed SASL authentification: %s'%reason,'error')
|
||||
raise NodeProcessed
|
||||
elif challenge.getName()=='success':
|
||||
self.startsasl='success'
|
||||
self.DEBUG('Successfully authenticated with remote server.','ok')
|
||||
handlers=self._owner.Dispatcher.dumpHandlers()
|
||||
self._owner.Dispatcher.PlugOut()
|
||||
dispatcher.Dispatcher().PlugIn(self._owner)
|
||||
self._owner.Dispatcher.restoreHandlers(handlers)
|
||||
self._owner.User=self.username
|
||||
raise NodeProcessed
|
||||
########################################3333
|
||||
incoming_data=challenge.getData()
|
||||
chal={}
|
||||
data=base64.decodestring(incoming_data)
|
||||
self.DEBUG('Got challenge:'+data,'ok')
|
||||
for pair in data.split(','):
|
||||
key,value=pair.split('=', 1)
|
||||
if value[:1]=='"' and value[-1:]=='"': value=value[1:-1]
|
||||
chal[key]=value
|
||||
if chal.has_key('qop') and chal['qop']=='auth':
|
||||
resp={}
|
||||
resp['username']=self.username
|
||||
resp['realm']=self._owner.Server
|
||||
resp['nonce']=chal['nonce']
|
||||
cnonce=''
|
||||
for i in range(7):
|
||||
cnonce+=hex(int(random.random()*65536*4096))[2:]
|
||||
resp['cnonce']=cnonce
|
||||
resp['nc']=('00000001')
|
||||
resp['qop']='auth'
|
||||
resp['digest-uri']='xmpp/'+self._owner.Server
|
||||
A1=C([H(C([resp['username'],resp['realm'],self.password])),resp['nonce'],resp['cnonce']])
|
||||
A2=C(['AUTHENTICATE',resp['digest-uri']])
|
||||
response= HH(C([HH(A1),resp['nonce'],resp['nc'],resp['cnonce'],resp['qop'],HH(A2)]))
|
||||
resp['response']=response
|
||||
resp['charset']='utf-8'
|
||||
sasl_data=''
|
||||
for key in ['charset','username','realm','nonce','nc','cnonce','digest-uri','response','qop']:
|
||||
if key in ['nc','qop','response','charset']: sasl_data+="%s=%s,"%(key,resp[key])
|
||||
else: sasl_data+='%s="%s",'%(key,resp[key])
|
||||
########################################3333
|
||||
node=Node('response',attrs={'xmlns':NS_SASL},payload=[base64.encodestring(sasl_data[:-1]).replace('\r','').replace('\n','')])
|
||||
self._owner.send(node.__str__())
|
||||
elif chal.has_key('rspauth'): self._owner.send(Node('response',attrs={'xmlns':NS_SASL}).__str__())
|
||||
else:
|
||||
self.startsasl='failure'
|
||||
self.DEBUG('Failed SASL authentification: unknown challenge','error')
|
||||
raise NodeProcessed
|
||||
|
||||
class Bind(PlugIn):
|
||||
""" Bind some JID to the current connection to allow router know of our location."""
|
||||
def __init__(self):
|
||||
PlugIn.__init__(self)
|
||||
self.DBG_LINE='bind'
|
||||
self.bound=None
|
||||
|
||||
def plugin(self,owner):
|
||||
""" Start resource binding, if allowed at this time. Used internally. """
|
||||
if self._owner.Dispatcher.Stream.features:
|
||||
try: self.FeaturesHandler(self._owner.Dispatcher,self._owner.Dispatcher.Stream.features)
|
||||
except NodeProcessed: pass
|
||||
else: self._owner.RegisterHandler('features',self.FeaturesHandler,xmlns=NS_STREAMS)
|
||||
|
||||
def plugout(self):
|
||||
""" Remove Bind handler from owner's dispatcher. Used internally. """
|
||||
self._owner.UnregisterHandler('features',self.FeaturesHandler,xmlns=NS_STREAMS)
|
||||
|
||||
def FeaturesHandler(self,conn,feats):
|
||||
""" Determine if server supports resource binding and set some internal attributes accordingly. """
|
||||
if not feats.getTag('bind',namespace=NS_BIND):
|
||||
self.bound='failure'
|
||||
self.DEBUG('Server does not requested binding.','error')
|
||||
return
|
||||
if feats.getTag('session',namespace=NS_SESSION): self.session=1
|
||||
else: self.session=-1
|
||||
self.bound=[]
|
||||
|
||||
def Bind(self,resource=None):
|
||||
""" Perform binding. Use provided resource name or random (if not provided). """
|
||||
while self.bound is None and self._owner.Process(1): pass
|
||||
if resource: resource=[Node('resource',payload=[resource])]
|
||||
else: resource=[]
|
||||
resp=self._owner.SendAndWaitForResponse(Protocol('iq',typ='set',payload=[Node('bind',attrs={'xmlns':NS_BIND},payload=resource)]))
|
||||
if isResultNode(resp):
|
||||
self.bound.append(resp.getTag('bind').getTagData('jid'))
|
||||
self.DEBUG('Successfully bound %s.'%self.bound[-1],'ok')
|
||||
jid=JID(resp.getTag('bind').getTagData('jid'))
|
||||
self._owner.User=jid.getNode()
|
||||
self._owner.Resource=jid.getResource()
|
||||
resp=self._owner.SendAndWaitForResponse(Protocol('iq',typ='set',payload=[Node('session',attrs={'xmlns':NS_SESSION})]))
|
||||
if isResultNode(resp):
|
||||
self.DEBUG('Successfully opened session.','ok')
|
||||
self.session=1
|
||||
return 'ok'
|
||||
else:
|
||||
self.DEBUG('Session open failed.','error')
|
||||
self.session=0
|
||||
elif resp: self.DEBUG('Binding failed: %s.'%resp.getTag('error'),'error')
|
||||
else:
|
||||
self.DEBUG('Binding failed: timeout expired.','error')
|
||||
return ''
|
||||
|
||||
class ComponentBind(PlugIn):
|
||||
""" ComponentBind some JID to the current connection to allow router know of our location."""
|
||||
def __init__(self):
|
||||
PlugIn.__init__(self)
|
||||
self.DBG_LINE='bind'
|
||||
self.bound=None
|
||||
self.needsUnregister=None
|
||||
|
||||
def plugin(self,owner):
|
||||
""" Start resource binding, if allowed at this time. Used internally. """
|
||||
if self._owner.Dispatcher.Stream.features:
|
||||
try: self.FeaturesHandler(self._owner.Dispatcher,self._owner.Dispatcher.Stream.features)
|
||||
except NodeProcessed: pass
|
||||
else:
|
||||
self._owner.RegisterHandler('features',self.FeaturesHandler,xmlns=NS_STREAMS)
|
||||
self.needsUnregister=1
|
||||
|
||||
def plugout(self):
|
||||
""" Remove ComponentBind handler from owner's dispatcher. Used internally. """
|
||||
if self.needsUnregister:
|
||||
self._owner.UnregisterHandler('features',self.FeaturesHandler,xmlns=NS_STREAMS)
|
||||
|
||||
def FeaturesHandler(self,conn,feats):
|
||||
""" Determine if server supports resource binding and set some internal attributes accordingly. """
|
||||
if not feats.getTag('bind',namespace=NS_BIND):
|
||||
self.bound='failure'
|
||||
self.DEBUG('Server does not requested binding.','error')
|
||||
return
|
||||
if feats.getTag('session',namespace=NS_SESSION): self.session=1
|
||||
else: self.session=-1
|
||||
self.bound=[]
|
||||
|
||||
def Bind(self,domain=None):
|
||||
""" Perform binding. Use provided domain name (if not provided). """
|
||||
while self.bound is None and self._owner.Process(1): pass
|
||||
resp=self._owner.SendAndWaitForResponse(Protocol('bind',attrs={'name':domain},xmlns=NS_COMPONENT_1))
|
||||
if resp and resp.getAttr('error'):
|
||||
self.DEBUG('Binding failed: %s.'%resp.getAttr('error'),'error')
|
||||
elif resp:
|
||||
self.DEBUG('Successfully bound.','ok')
|
||||
return 'ok'
|
||||
else:
|
||||
self.DEBUG('Binding failed: timeout expired.','error')
|
||||
return ''
|
|
@ -19,10 +19,14 @@ Can be used both for client and transport authentication.
|
|||
'''
|
||||
import sys
|
||||
from protocol import *
|
||||
from auth import *
|
||||
from client import PlugIn
|
||||
import sha,base64,random,dispatcher_nb
|
||||
|
||||
import md5
|
||||
def HH(some): return md5.new(some).hexdigest()
|
||||
def H(some): return md5.new(some).digest()
|
||||
def C(some): return ':'.join(some)
|
||||
|
||||
def challenge_splitter(data):
|
||||
''' Helper function that creates a dict from challenge string.
|
||||
Sample chalenge string:
|
||||
|
@ -235,6 +239,7 @@ class NonBlockingNonSASL(PlugIn):
|
|||
self.resource = resource
|
||||
self.on_auth = on_auth
|
||||
|
||||
|
||||
def plugin(self, owner):
|
||||
''' Determine the best auth method (digest/0k/plain) and use it for auth.
|
||||
Returns used method name on success. Used internally. '''
|
||||
|
@ -316,8 +321,24 @@ class NonBlockingNonSASL(PlugIn):
|
|||
else:
|
||||
self.handshake=-1
|
||||
|
||||
class NonBlockingBind(Bind):
|
||||
class NonBlockingBind(PlugIn):
|
||||
''' Bind some JID to the current connection to allow router know of our location.'''
|
||||
|
||||
def __init__(self):
|
||||
PlugIn.__init__(self)
|
||||
self.DBG_LINE='bind'
|
||||
self.bound=None
|
||||
|
||||
def FeaturesHandler(self,conn,feats):
|
||||
""" Determine if server supports resource binding and set some internal attributes accordingly. """
|
||||
if not feats.getTag('bind',namespace=NS_BIND):
|
||||
self.bound='failure'
|
||||
self.DEBUG('Server does not requested binding.','error')
|
||||
return
|
||||
if feats.getTag('session',namespace=NS_SESSION): self.session=1
|
||||
else: self.session=-1
|
||||
self.bound=[]
|
||||
|
||||
def plugin(self, owner):
|
||||
''' Start resource binding, if allowed at this time. Used internally. '''
|
||||
if self._owner.Dispatcher.Stream.features:
|
||||
|
@ -381,10 +402,16 @@ class NonBlockingBind(Bind):
|
|||
self.session = 0
|
||||
self.on_bound(None)
|
||||
|
||||
class NBComponentBind(ComponentBind):
|
||||
class NBComponentBind(PlugIn):
|
||||
''' ComponentBind some JID to the current connection to allow
|
||||
router know of our location.
|
||||
'''
|
||||
def __init__(self):
|
||||
PlugIn.__init__(self)
|
||||
self.DBG_LINE='bind'
|
||||
self.bound=None
|
||||
self.needsUnregister=None
|
||||
|
||||
def plugin(self,owner):
|
||||
''' Start resource binding, if allowed at this time. Used internally. '''
|
||||
if self._owner.Dispatcher.Stream.features:
|
||||
|
@ -427,3 +454,26 @@ class NBComponentBind(ComponentBind):
|
|||
self.DEBUG('Binding failed: timeout expired.', 'error')
|
||||
if self.on_bind:
|
||||
self.on_bind(None)
|
||||
|
||||
def FeaturesHandler(self,conn,feats):
|
||||
""" Determine if server supports resource binding and set some internal attributes accordingly. """
|
||||
if not feats.getTag('bind',namespace=NS_BIND):
|
||||
self.bound='failure'
|
||||
self.DEBUG('Server does not requested binding.','error')
|
||||
return
|
||||
if feats.getTag('session',namespace=NS_SESSION): self.session=1
|
||||
else: self.session=-1
|
||||
self.bound=[]
|
||||
|
||||
def Bind(self,domain=None):
|
||||
""" Perform binding. Use provided domain name (if not provided). """
|
||||
while self.bound is None and self._owner.Process(1): pass
|
||||
resp=self._owner.SendAndWaitForResponse(Protocol('bind',attrs={'name':domain},xmlns=NS_COMPONENT_1))
|
||||
if resp and resp.getAttr('error'):
|
||||
self.DEBUG('Binding failed: %s.'%resp.getAttr('error'),'error')
|
||||
elif resp:
|
||||
self.DEBUG('Successfully bound.','ok')
|
||||
return 'ok'
|
||||
else:
|
||||
self.DEBUG('Binding failed: timeout expired.','error')
|
||||
return ''
|
||||
|
|
|
@ -1,216 +0,0 @@
|
|||
## browser.py
|
||||
##
|
||||
## Copyright (C) 2004 Alexey "Snake" Nezhdanov
|
||||
##
|
||||
## This program is free software; you can redistribute it and/or modify
|
||||
## it under the terms of the GNU General Public License as published by
|
||||
## the Free Software Foundation; either version 2, or (at your option)
|
||||
## any later version.
|
||||
##
|
||||
## This program is distributed in the hope that it will be useful,
|
||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
## GNU General Public License for more details.
|
||||
|
||||
# $Id: browser.py,v 1.11 2005/10/07 23:17:09 normanr Exp $
|
||||
|
||||
"""Browser module provides DISCO server framework for your application.
|
||||
This functionality can be used for very different purposes - from publishing
|
||||
software version and supported features to building of "jabber site" that users
|
||||
can navigate with their disco browsers and interact with active content.
|
||||
|
||||
Such functionality is achieved via registering "DISCO handlers" that are
|
||||
automatically called when user requests some node of your disco tree.
|
||||
"""
|
||||
|
||||
from dispatcher import *
|
||||
from client import PlugIn
|
||||
|
||||
class Browser(PlugIn):
|
||||
""" WARNING! This class is for components only. It will not work in client mode!
|
||||
|
||||
Standart xmpppy class that is ancestor of PlugIn and can be attached
|
||||
to your application.
|
||||
All processing will be performed in the handlers registered in the browser
|
||||
instance. You can register any number of handlers ensuring that for each
|
||||
node/jid combination only one (or none) handler registered.
|
||||
You can register static information or the fully-blown function that will
|
||||
calculate the answer dynamically.
|
||||
Example of static info (see JEP-0030, examples 13-14):
|
||||
# cl - your xmpppy connection instance.
|
||||
b=xmpp.browser.Browser()
|
||||
b.PlugIn(cl)
|
||||
items=[]
|
||||
item={}
|
||||
item['jid']='catalog.shakespeare.lit'
|
||||
item['node']='books'
|
||||
item['name']='Books by and about Shakespeare'
|
||||
items.append(item)
|
||||
item={}
|
||||
item['jid']='catalog.shakespeare.lit'
|
||||
item['node']='clothing'
|
||||
item['name']='Wear your literary taste with pride'
|
||||
items.append(item)
|
||||
item={}
|
||||
item['jid']='catalog.shakespeare.lit'
|
||||
item['node']='music'
|
||||
item['name']='Music from the time of Shakespeare'
|
||||
items.append(item)
|
||||
info={'ids':[], 'features':[]}
|
||||
b.setDiscoHandler({'items':items,'info':info})
|
||||
|
||||
items should be a list of item elements.
|
||||
every item element can have any of these four keys: 'jid', 'node', 'name', 'action'
|
||||
info should be a dicionary and must have keys 'ids' and 'features'.
|
||||
Both of them should be lists:
|
||||
ids is a list of dictionaries and features is a list of text strings.
|
||||
Example (see JEP-0030, examples 1-2)
|
||||
# cl - your xmpppy connection instance.
|
||||
b=xmpp.browser.Browser()
|
||||
b.PlugIn(cl)
|
||||
items=[]
|
||||
ids=[]
|
||||
ids.append({'category':'conference','type':'text','name':'Play-Specific Chatrooms'})
|
||||
ids.append({'category':'directory','type':'chatroom','name':'Play-Specific Chatrooms'})
|
||||
features=[NS_DISCO_INFO,NS_DISCO_ITEMS,NS_MUC,NS_REGISTER,NS_SEARCH,NS_TIME,NS_VERSION]
|
||||
info={'ids':ids,'features':features}
|
||||
# info['xdata']=xmpp.protocol.DataForm() # JEP-0128
|
||||
b.setDiscoHandler({'items':[],'info':info})
|
||||
"""
|
||||
def __init__(self):
|
||||
"""Initialises internal variables. Used internally."""
|
||||
PlugIn.__init__(self)
|
||||
DBG_LINE='browser'
|
||||
self._exported_methods=[]
|
||||
self._handlers={'':{}}
|
||||
|
||||
def plugin(self, owner):
|
||||
""" Registers it's own iq handlers in your application dispatcher instance.
|
||||
Used internally."""
|
||||
owner.RegisterHandler('iq',self._DiscoveryHandler,typ='get',ns=NS_DISCO_INFO)
|
||||
owner.RegisterHandler('iq',self._DiscoveryHandler,typ='get',ns=NS_DISCO_ITEMS)
|
||||
|
||||
def plugout(self):
|
||||
""" Unregisters browser's iq handlers from your application dispatcher instance.
|
||||
Used internally."""
|
||||
self._owner.UnregisterHandler('iq',self._DiscoveryHandler,typ='get',ns=NS_DISCO_INFO)
|
||||
self._owner.UnregisterHandler('iq',self._DiscoveryHandler,typ='get',ns=NS_DISCO_ITEMS)
|
||||
|
||||
def _traversePath(self,node,jid,set=0):
|
||||
""" Returns dictionary and key or None,None
|
||||
None - root node (w/o "node" attribute)
|
||||
/a/b/c - node
|
||||
/a/b/ - branch
|
||||
Set returns '' or None as the key
|
||||
get returns '' or None as the key or None as the dict.
|
||||
Used internally."""
|
||||
if self._handlers.has_key(jid): cur=self._handlers[jid]
|
||||
elif set:
|
||||
self._handlers[jid]={}
|
||||
cur=self._handlers[jid]
|
||||
else: cur=self._handlers['']
|
||||
if node is None: node=[None]
|
||||
else: node=node.replace('/',' /').split('/')
|
||||
for i in node:
|
||||
if i<>'' and cur.has_key(i): cur=cur[i]
|
||||
elif set and i<>'': cur[i]={dict:cur,str:i}; cur=cur[i]
|
||||
elif set or cur.has_key(''): return cur,''
|
||||
else: return None,None
|
||||
if cur.has_key(1) or set: return cur,1
|
||||
raise "Corrupted data"
|
||||
|
||||
def setDiscoHandler(self,handler,node='',jid=''):
|
||||
""" This is the main method that you will use in this class.
|
||||
It is used to register supplied DISCO handler (or dictionary with static info)
|
||||
as handler of some disco tree branch.
|
||||
If you do not specify the node this handler will be used for all queried nodes.
|
||||
If you do not specify the jid this handler will be used for all queried JIDs.
|
||||
|
||||
Usage:
|
||||
cl.Browser.setDiscoHandler(someDict,node,jid)
|
||||
or
|
||||
cl.Browser.setDiscoHandler(someDISCOHandler,node,jid)
|
||||
where
|
||||
|
||||
someDict={
|
||||
'items':[
|
||||
{'jid':'jid1','action':'action1','node':'node1','name':'name1'},
|
||||
{'jid':'jid2','action':'action2','node':'node2','name':'name2'},
|
||||
{'jid':'jid3','node':'node3','name':'name3'},
|
||||
{'jid':'jid4','node':'node4'}
|
||||
],
|
||||
'info' :{
|
||||
'ids':[
|
||||
{'category':'category1','type':'type1','name':'name1'},
|
||||
{'category':'category2','type':'type2','name':'name2'},
|
||||
{'category':'category3','type':'type3','name':'name3'},
|
||||
],
|
||||
'features':['feature1','feature2','feature3','feature4'],
|
||||
'xdata':DataForm
|
||||
}
|
||||
}
|
||||
|
||||
and/or
|
||||
|
||||
def someDISCOHandler(session,request,TYR):
|
||||
# if TYR=='items': # returns items list of the same format as shown above
|
||||
# elif TYR=='info': # returns info dictionary of the same format as shown above
|
||||
# else: # this case is impossible for now.
|
||||
"""
|
||||
self.DEBUG('Registering handler %s for "%s" node->%s'%(handler,jid,node), 'info')
|
||||
node,key=self._traversePath(node,jid,1)
|
||||
node[key]=handler
|
||||
|
||||
def getDiscoHandler(self,node='',jid=''):
|
||||
""" Returns the previously registered DISCO handler
|
||||
that is resonsible for this node/jid combination.
|
||||
Used internally."""
|
||||
node,key=self._traversePath(node,jid)
|
||||
if node: return node[key]
|
||||
|
||||
def delDiscoHandler(self,node='',jid=''):
|
||||
""" Unregisters DISCO handler that is resonsible for this
|
||||
node/jid combination. When handler is unregistered the branch
|
||||
is handled in the same way that it's parent branch from this moment.
|
||||
"""
|
||||
node,key=self._traversePath(node,jid)
|
||||
if node:
|
||||
handler=node[key]
|
||||
del node[dict][node[str]]
|
||||
return handler
|
||||
|
||||
def _DiscoveryHandler(self,conn,request):
|
||||
""" Servers DISCO iq request from the remote client.
|
||||
Automatically determines the best handler to use and calls it
|
||||
to handle the request. Used internally.
|
||||
"""
|
||||
handler=self.getDiscoHandler(request.getQuerynode(),request.getTo())
|
||||
if not handler:
|
||||
self.DEBUG("No Handler for request with jid->%s node->%s ns->%s"%(request.getTo(),request.getQuerynode(),request.getQueryNS()),'error')
|
||||
conn.send(Error(request,ERR_ITEM_NOT_FOUND))
|
||||
raise NodeProcessed
|
||||
self.DEBUG("Handling request with jid->%s node->%s ns->%s"%(request.getTo(),request.getQuerynode(),request.getQueryNS()),'ok')
|
||||
rep=request.buildReply('result')
|
||||
if request.getQuerynode(): rep.setQuerynode(request.getQuerynode())
|
||||
q=rep.getTag('query')
|
||||
if request.getQueryNS()==NS_DISCO_ITEMS:
|
||||
# handler must return list: [{jid,action,node,name}]
|
||||
if type(handler)==dict: lst=handler['items']
|
||||
else: lst=handler(conn,request,'items')
|
||||
if lst==None:
|
||||
conn.send(Error(request,ERR_ITEM_NOT_FOUND))
|
||||
raise NodeProcessed
|
||||
for item in lst: q.addChild('item',item)
|
||||
elif request.getQueryNS()==NS_DISCO_INFO:
|
||||
if type(handler)==dict: dt=handler['info']
|
||||
else: dt=handler(conn,request,'info')
|
||||
if dt==None:
|
||||
conn.send(Error(request,ERR_ITEM_NOT_FOUND))
|
||||
raise NodeProcessed
|
||||
# handler must return dictionary:
|
||||
# {'ids':[{},{},{},{}], 'features':[fe,at,ur,es], 'xdata':DataForm}
|
||||
for id in dt['ids']: q.addChild('identity',id)
|
||||
for feature in dt['features']: q.addChild('feature',{'var':feature})
|
||||
if dt.has_key('xdata'): q.addChild(node=dt['xdata'])
|
||||
conn.send(rep)
|
||||
raise NodeProcessed
|
|
@ -51,272 +51,38 @@ DBG_CLIENT='client'
|
|||
DBG_COMPONENT='component'
|
||||
|
||||
class PlugIn:
|
||||
""" Common xmpppy plugins infrastructure: plugging in/out, debugging. """
|
||||
def __init__(self):
|
||||
self._exported_methods=[]
|
||||
self.DBG_LINE=self.__class__.__name__.lower()
|
||||
""" Common xmpppy plugins infrastructure: plugging in/out, debugging. """
|
||||
def __init__(self):
|
||||
self._exported_methods=[]
|
||||
self.DBG_LINE=self.__class__.__name__.lower()
|
||||
|
||||
def PlugIn(self,owner):
|
||||
""" Attach to main instance and register ourself and all our staff in it. """
|
||||
self._owner=owner
|
||||
if self.DBG_LINE not in owner.debug_flags:
|
||||
owner.debug_flags.append(self.DBG_LINE)
|
||||
self.DEBUG('Plugging %s into %s'%(self,self._owner),'start')
|
||||
if owner.__dict__.has_key(self.__class__.__name__):
|
||||
return self.DEBUG('Plugging ignored: another instance already plugged.','error')
|
||||
self._old_owners_methods=[]
|
||||
for method in self._exported_methods:
|
||||
if owner.__dict__.has_key(method.__name__):
|
||||
self._old_owners_methods.append(owner.__dict__[method.__name__])
|
||||
owner.__dict__[method.__name__]=method
|
||||
owner.__dict__[self.__class__.__name__]=self
|
||||
if self.__class__.__dict__.has_key('plugin'): return self.plugin(owner)
|
||||
def PlugIn(self,owner):
|
||||
""" Attach to main instance and register ourself and all our staff in it. """
|
||||
self._owner=owner
|
||||
if self.DBG_LINE not in owner.debug_flags:
|
||||
owner.debug_flags.append(self.DBG_LINE)
|
||||
self.DEBUG('Plugging %s into %s'%(self,self._owner),'start')
|
||||
if owner.__dict__.has_key(self.__class__.__name__):
|
||||
return self.DEBUG('Plugging ignored: another instance already plugged.','error')
|
||||
self._old_owners_methods=[]
|
||||
for method in self._exported_methods:
|
||||
if owner.__dict__.has_key(method.__name__):
|
||||
self._old_owners_methods.append(owner.__dict__[method.__name__])
|
||||
owner.__dict__[method.__name__]=method
|
||||
owner.__dict__[self.__class__.__name__]=self
|
||||
if self.__class__.__dict__.has_key('plugin'): return self.plugin(owner)
|
||||
|
||||
def PlugOut(self):
|
||||
""" Unregister all our staff from main instance and detach from it. """
|
||||
self.DEBUG('Plugging %s out of %s.'%(self,self._owner),'stop')
|
||||
self._owner.debug_flags.remove(self.DBG_LINE)
|
||||
for method in self._exported_methods: del self._owner.__dict__[method.__name__]
|
||||
for method in self._old_owners_methods: self._owner.__dict__[method.__name__]=method
|
||||
del self._owner.__dict__[self.__class__.__name__]
|
||||
if self.__class__.__dict__.has_key('plugout'): return self.plugout()
|
||||
del self._owner
|
||||
def PlugOut(self):
|
||||
""" Unregister all our staff from main instance and detach from it. """
|
||||
self.DEBUG('Plugging %s out of %s.'%(self,self._owner),'stop')
|
||||
self._owner.debug_flags.remove(self.DBG_LINE)
|
||||
for method in self._exported_methods: del self._owner.__dict__[method.__name__]
|
||||
for method in self._old_owners_methods: self._owner.__dict__[method.__name__]=method
|
||||
del self._owner.__dict__[self.__class__.__name__]
|
||||
if self.__class__.__dict__.has_key('plugout'): return self.plugout()
|
||||
del self._owner
|
||||
|
||||
def DEBUG(self,text,severity='info'):
|
||||
""" Feed a provided debug line to main instance's debug facility along with our ID string. """
|
||||
self._owner.DEBUG(self.DBG_LINE,text,severity)
|
||||
def DEBUG(self,text,severity='info'):
|
||||
""" Feed a provided debug line to main instance's debug facility along with our ID string. """
|
||||
self._owner.DEBUG(self.DBG_LINE,text,severity)
|
||||
|
||||
import transports,dispatcher,auth,roster
|
||||
class CommonClient:
|
||||
""" Base for Client and Component classes."""
|
||||
def __init__(self,server,port=5222,debug=['always', 'nodebuilder'],caller=None):
|
||||
""" Caches server name and (optionally) port to connect to. "debug" parameter specifies
|
||||
the debug IDs that will go into debug output. You can either specifiy an "include"
|
||||
or "exclude" list. The latter is done via adding "always" pseudo-ID to the list.
|
||||
Full list: ['nodebuilder', 'dispatcher', 'gen_auth', 'SASL_auth', 'bind', 'socket',
|
||||
'CONNECTproxy', 'TLS', 'roster', 'browser', 'ibb'] . """
|
||||
if self.__class__.__name__=='Client': self.Namespace,self.DBG='jabber:client',DBG_CLIENT
|
||||
elif self.__class__.__name__=='Component': self.Namespace,self.DBG=dispatcher.NS_COMPONENT_ACCEPT,DBG_COMPONENT
|
||||
self.defaultNamespace=self.Namespace
|
||||
self.disconnect_handlers=[]
|
||||
self.Server=server
|
||||
self.Port=port
|
||||
# Who initiated this client
|
||||
# Used to register the EventDispatcher
|
||||
self._caller=caller
|
||||
if debug and type(debug)<>list: debug=['always', 'nodebuilder']
|
||||
self._DEBUG=Debug.Debug(debug)
|
||||
self.DEBUG=self._DEBUG.Show
|
||||
self.debug_flags=self._DEBUG.debug_flags
|
||||
self.debug_flags.append(self.DBG)
|
||||
self._owner=self
|
||||
self._registered_name=None
|
||||
self.RegisterDisconnectHandler(self.DisconnectHandler)
|
||||
self.connected=''
|
||||
self._component=0
|
||||
|
||||
def RegisterDisconnectHandler(self,handler):
|
||||
""" Register handler that will be called on disconnect."""
|
||||
self.disconnect_handlers.append(handler)
|
||||
|
||||
def UnregisterDisconnectHandler(self,handler):
|
||||
""" Unregister handler that is called on disconnect."""
|
||||
self.disconnect_handlers.remove(handler)
|
||||
|
||||
def disconnected(self):
|
||||
""" Called on disconnection. Calls disconnect handlers and cleans things up. """
|
||||
self.connected=''
|
||||
self.DEBUG(self.DBG,'Disconnect detected','stop')
|
||||
self.disconnect_handlers.reverse()
|
||||
for i in self.disconnect_handlers: i()
|
||||
self.disconnect_handlers.reverse()
|
||||
if self.__dict__.has_key('TLS'): self.TLS.PlugOut()
|
||||
|
||||
def DisconnectHandler(self):
|
||||
""" Default disconnect handler. Just raises an IOError.
|
||||
If you choosed to use this class in your production client,
|
||||
override this method or at least unregister it. """
|
||||
raise IOError('Disconnected from server.')
|
||||
|
||||
def event(self,eventName,args={}):
|
||||
""" Default event handler. To be overriden. """
|
||||
print "Event: ",(eventName,args)
|
||||
|
||||
def isConnected(self):
|
||||
""" Returns connection state. F.e.: None / 'tls' / 'tcp+non_sasl' . """
|
||||
return self.connected
|
||||
|
||||
def reconnectAndReauth(self):
|
||||
""" Example of reconnection method. In fact, it can be used to batch connection and auth as well. """
|
||||
handlerssave=self.Dispatcher.dumpHandlers()
|
||||
self.Dispatcher.PlugOut()
|
||||
if self.__dict__.has_key('NonSASL'): self.NonSASL.PlugOut()
|
||||
if self.__dict__.has_key('SASL'): self.SASL.PlugOut()
|
||||
if self.__dict__.has_key('TLS'): self.TLS.PlugOut()
|
||||
if self.__dict__.has_key('HTTPPROXYsocket'): self.HTTPPROXYsocket.PlugOut()
|
||||
if self.__dict__.has_key('TCPsocket'): self.TCPsocket.PlugOut()
|
||||
if not self.connect(server=self._Server,proxy=self._Proxy): return
|
||||
if not self.auth(self._User,self._Password,self._Resource): return
|
||||
self.Dispatcher.restoreHandlers(handlerssave)
|
||||
return self.connected
|
||||
|
||||
def get_peerhost(self):
|
||||
''' get the ip address of the account, from which is made connection
|
||||
to the server , (e.g. me).
|
||||
We will create listening socket on the same ip '''
|
||||
if hasattr(self, 'Connection'):
|
||||
return self.Connection._sock.getsockname()
|
||||
|
||||
def connect(self,server=None,proxy=None,ssl=None,use_srv=None):
|
||||
""" Make a tcp/ip connection, protect it with tls/ssl if possible and start XMPP stream.
|
||||
Returns None or 'tcp' or 'tls', depending on the result."""
|
||||
if not server: server=(self.Server,self.Port)
|
||||
if proxy: socket=transports.HTTPPROXYsocket(proxy,server,use_srv)
|
||||
else: socket=transports.TCPsocket(server,use_srv)
|
||||
connected=socket.PlugIn(self)
|
||||
if not connected:
|
||||
socket.PlugOut()
|
||||
return
|
||||
self._Server,self._Proxy=server,proxy
|
||||
self.connected='tcp'
|
||||
if (ssl is None and self.Connection.getPort() in (5223, 443)) or ssl:
|
||||
try: # FIXME. This should be done in transports.py
|
||||
transports.TLS().PlugIn(self,now=1)
|
||||
self.connected='ssl'
|
||||
except socket.sslerror:
|
||||
return
|
||||
dispatcher.Dispatcher().PlugIn(self)
|
||||
while self.Dispatcher.Stream._document_attrs is None:
|
||||
if not self.Process(1): return
|
||||
if self.Dispatcher.Stream._document_attrs.has_key('version') and self.Dispatcher.Stream._document_attrs['version']=='1.0':
|
||||
while not self.Dispatcher.Stream.features and self.Process(): pass # If we get version 1.0 stream the features tag MUST BE presented
|
||||
return self.connected
|
||||
|
||||
class Client(CommonClient):
|
||||
""" Example client class, based on CommonClient. """
|
||||
def connect(self,server=None,proxy=None,secure=None,use_srv=True):
|
||||
""" Connect to jabber server. If you want to specify different ip/port to connect to you can
|
||||
pass it as tuple as first parameter. If there is HTTP proxy between you and server
|
||||
specify it's address and credentials (if needed) in the second argument.
|
||||
If you want ssl/tls support to be discovered and enable automatically - leave third argument as None. (ssl will be autodetected only if port is 5223 or 443)
|
||||
If you want to force SSL start (i.e. if port 5223 or 443 is remapped to some non-standard port) then set it to 1.
|
||||
If you want to disable tls/ssl support completely, set it to 0.
|
||||
Example: connect(('192.168.5.5',5222),{'host':'proxy.my.net','port':8080,'user':'me','password':'secret'})
|
||||
Returns '' or 'tcp' or 'tls', depending on the result."""
|
||||
if not CommonClient.connect(self,server,proxy,secure,use_srv) or secure<>None and not secure: return self.connected
|
||||
transports.TLS().PlugIn(self)
|
||||
if not self.Dispatcher.Stream._document_attrs.has_key('version') or not self.Dispatcher.Stream._document_attrs['version']=='1.0': return self.connected
|
||||
while not self.Dispatcher.Stream.features and self.Process(): pass # If we get version 1.0 stream the features tag MUST BE presented
|
||||
if not self.Dispatcher.Stream.features.getTag('starttls'): return self.connected # TLS not supported by server
|
||||
while not self.TLS.starttls and self.Process(): pass
|
||||
if not hasattr(self, 'TLS') or self.TLS.starttls!='success': self.event('tls_failed'); return self.connected
|
||||
self.connected='tls'
|
||||
return self.connected
|
||||
|
||||
def auth(self,user,password,resource='',sasl=1):
|
||||
""" Authenticate connnection and bind resource. If resource is not provided
|
||||
random one or library name used. """
|
||||
self._User,self._Password,self._Resource=user,password,resource
|
||||
while not self.Dispatcher.Stream._document_attrs and self.Process(): pass
|
||||
if self.Dispatcher.Stream._document_attrs.has_key('version') and self.Dispatcher.Stream._document_attrs['version']=='1.0':
|
||||
while not self.Dispatcher.Stream.features and self.Process(): pass # If we get version 1.0 stream the features tag MUST BE presented
|
||||
if sasl: auth.SASL(user,password).PlugIn(self)
|
||||
if not sasl or self.SASL.startsasl=='not-supported':
|
||||
if not resource: resource='xmpppy'
|
||||
if auth.NonSASL(user,password,resource).PlugIn(self):
|
||||
self.connected+='+old_auth'
|
||||
return 'old_auth'
|
||||
return
|
||||
self.SASL.auth()
|
||||
while self.SASL.startsasl=='in-process' and self.Process(): pass
|
||||
if self.SASL.startsasl=='success':
|
||||
auth.Bind().PlugIn(self)
|
||||
while self.Bind.bound is None and self.Process(): pass
|
||||
if self.Bind.Bind(resource):
|
||||
self.connected+='+sasl'
|
||||
return 'sasl'
|
||||
|
||||
def initRoster(self):
|
||||
""" Plug in the roster. """
|
||||
if not self.__dict__.has_key('Roster'): roster.Roster().PlugIn(self)
|
||||
|
||||
def getRoster(self):
|
||||
""" Return the Roster instance, previously plugging it in and
|
||||
requesting roster from server if needed. """
|
||||
self.initRoster()
|
||||
return self.Roster.getRoster()
|
||||
|
||||
def sendInitPresence(self,requestRoster=1):
|
||||
""" Send roster request and initial <presence/>.
|
||||
You can disable the first by setting requestRoster argument to 0. """
|
||||
self.sendPresence(requestRoster=requestRoster)
|
||||
|
||||
def sendPresence(self,jid=None,typ=None,requestRoster=0):
|
||||
""" Send some specific presence state.
|
||||
Can also request roster from server if according agrument is set."""
|
||||
if requestRoster: roster.Roster().PlugIn(self)
|
||||
self.send(dispatcher.Presence(to=jid, typ=typ))
|
||||
|
||||
class Component(CommonClient):
|
||||
""" Component class. The only difference from CommonClient is ability to perform component authentication. """
|
||||
def __init__(self,server,port=5347,typ=None,debug=['always', 'nodebuilder'],domains=None,component=0):
|
||||
""" Init function for Components.
|
||||
As components use a different auth mechanism which includes the namespace of the component.
|
||||
Jabberd1.4 and Ejabberd use the default namespace then for all client messages.
|
||||
Jabberd2 uses jabber:client.
|
||||
'server' argument is a server name that you are connecting to (f.e. "localhost").
|
||||
'port' can be specified if 'server' resolves to correct IP. If it is not then you'll need to specify IP
|
||||
and port while calling "connect()"."""
|
||||
CommonClient.__init__(self,server,port=port,debug=debug)
|
||||
self.typ=typ
|
||||
self.component=component
|
||||
if domains:
|
||||
self.domains=domains
|
||||
else:
|
||||
self.domains=[server]
|
||||
|
||||
def connect(self,server=None,proxy=None):
|
||||
""" This will connect to the server, and if the features tag is found then set
|
||||
the namespace to be jabber:client as that is required for jabberd2.
|
||||
'server' and 'proxy' arguments have the same meaning as in xmpp.Client.connect() """
|
||||
if self.component:
|
||||
self.Namespace=auth.NS_COMPONENT_1
|
||||
self.Server=server[0]
|
||||
CommonClient.connect(self,server=server,proxy=proxy)
|
||||
if self.connected and (self.typ=='jabberd2' or not self.typ and self.Dispatcher.Stream.features is not None):
|
||||
self.defaultNamespace=auth.NS_CLIENT
|
||||
self.Dispatcher.RegisterNamespace(self.defaultNamespace)
|
||||
self.Dispatcher.RegisterProtocol('iq',dispatcher.Iq)
|
||||
self.Dispatcher.RegisterProtocol('message',dispatcher.Message)
|
||||
self.Dispatcher.RegisterProtocol('presence',dispatcher.Presence)
|
||||
return self.connected
|
||||
|
||||
def auth(self,name,password,dup=None,sasl=0):
|
||||
""" Authenticate component "name" with password "password"."""
|
||||
self._User,self._Password,self._Resource=name,password,''
|
||||
try:
|
||||
if self.component: sasl=1
|
||||
if sasl: auth.SASL(name,password).PlugIn(self)
|
||||
if not sasl or self.SASL.startsasl=='not-supported':
|
||||
if auth.NonSASL(name,password,'').PlugIn(self):
|
||||
self.connected+='+old_auth'
|
||||
return 'old_auth'
|
||||
return
|
||||
self.SASL.auth()
|
||||
while self.SASL.startsasl=='in-process' and self.Process(): pass
|
||||
if self.SASL.startsasl=='success':
|
||||
if self.component:
|
||||
self._component=self.component
|
||||
for domain in self.domains:
|
||||
auth.ComponentBind().PlugIn(self)
|
||||
while self.ComponentBind.bound is None: self.Process()
|
||||
if (not self.ComponentBind.Bind(domain)):
|
||||
self.ComponentBind.PlugOut()
|
||||
return
|
||||
self.ComponentBind.PlugOut()
|
||||
self.connected+='+sasl'
|
||||
return 'sasl'
|
||||
else:
|
||||
raise auth.NotAuthorized(self.SASL.startsasl)
|
||||
except:
|
||||
self.DEBUG(self.DBG,"Failed to authenticate %s"%name,'error')
|
||||
|
|
|
@ -1,194 +0,0 @@
|
|||
## client_bosh.py
|
||||
##
|
||||
## Copyright (C) 2008 Tomas Karasek <tom.to.the.k@gmail.com>
|
||||
##
|
||||
## This program is free software; you can redistribute it and/or modify
|
||||
## it under the terms of the GNU General Public License as published by
|
||||
## the Free Software Foundation; either version 2, or (at your option)
|
||||
## any later version.
|
||||
##
|
||||
## This program is distributed in the hope that it will be useful,
|
||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
## GNU General Public License for more details.
|
||||
|
||||
import locale, random
|
||||
import protocol
|
||||
import simplexml
|
||||
import debug
|
||||
import dispatcher_nb
|
||||
from client_nb import NBCommonClient
|
||||
|
||||
DBG_BOSHCLIENT='boshclient'
|
||||
|
||||
class BOSHClient(NBCommonClient):
|
||||
'''
|
||||
BOSH (XMPP over HTTP) client implementation. It should provide the same
|
||||
methods and functionality as NonBlockingClient.
|
||||
'''
|
||||
|
||||
|
||||
def __init__(self, server, bosh_conn_mgr, port=5222, bosh_port=5280,
|
||||
on_connect=None, on_connect_failure=None, on_proxy_failure=None, caller=None):
|
||||
'''
|
||||
Class constuctor has the same parameters as NBCommonClient plus bosh_conn_mgr
|
||||
and bosh_port - Connection manager address and port. bosh_conn_mgr should be
|
||||
in form: 'http://httpcm.jabber.org/http-bind/'
|
||||
Tcp connection will be opened to bosh_conn_mgr:bosh_port instead of
|
||||
server:port.
|
||||
'''
|
||||
self.bosh_protocol, self.bosh_host, self.bosh_uri = self.urisplit(bosh_conn_mgr)
|
||||
if self.bosh_protocol is None:
|
||||
self.bosh_protocol = 'http'
|
||||
|
||||
self.bosh_port = bosh_port
|
||||
|
||||
if self.bosh_uri == '':
|
||||
bosh_uri = '/'
|
||||
|
||||
self.xmpp_server = server
|
||||
self.xmpp_port = port
|
||||
|
||||
self.bosh_hold = 1
|
||||
self.bosh_wait=60
|
||||
self.bosh_rid=-1
|
||||
self.bosh_httpversion = 'HTTP/1.1'
|
||||
|
||||
NBCommonClient.__init__(self, self.bosh_host, self.bosh_port, caller=caller,
|
||||
on_connect=on_connect, on_connect_failure=on_connect_failure,
|
||||
on_proxy_failure=on_proxy_failure)
|
||||
|
||||
# Namespace and DBG are detected in NBCommonClient constructor
|
||||
# with isinstance(). Since BOSHClient is descendant of NBCommonClient
|
||||
# and client_bosh.py is NOT imported in client_nb.py, NB_COMPONENT_ACCEPT
|
||||
# is put to namespace. This is not very nice, thus:
|
||||
# TODO: refactor Namespace and DBG recognition in NBCommonClient or
|
||||
# derived classes
|
||||
self.Namespace, self.DBG = protocol.NS_HTTP_BIND, DBG_BOSHCLIENT
|
||||
# pop of DBG_COMPONENT
|
||||
self.debug_flags.pop()
|
||||
self.debug_flags.append(self.DBG)
|
||||
self.debug_flags.append(simplexml.DBG_NODEBUILDER)
|
||||
|
||||
|
||||
def urisplit(self, uri):
|
||||
'''
|
||||
Function for splitting URI string to tuple (protocol, host, path).
|
||||
e.g. urisplit('http://httpcm.jabber.org/webclient') returns
|
||||
('http', 'httpcm.jabber.org', '/webclient')
|
||||
'''
|
||||
import re
|
||||
regex = '(([^:/]+)(://))?([^/]*)(/?.*)'
|
||||
grouped = re.match(regex, uri).groups()
|
||||
proto, host, path = grouped[1], grouped[3], grouped[4]
|
||||
return proto, host, path
|
||||
|
||||
|
||||
def _on_connected(self):
|
||||
'''
|
||||
method called after socket starts connecting from NonBlockingTcp._do_connect
|
||||
'''
|
||||
self.onreceive(self.on_bosh_session_init_response)
|
||||
dispatcher_nb.Dispatcher().PlugIn(self)
|
||||
|
||||
|
||||
def parse_http_message(self, message):
|
||||
'''
|
||||
splits http message to tuple (
|
||||
statusline - list of e.g. ['HTTP/1.1', '200', 'OK'],
|
||||
headers - dictionary of headers e.g. {'Content-Length': '604',
|
||||
'Content-Type': 'text/xml; charset=utf-8'},
|
||||
httpbody - string with http body
|
||||
)
|
||||
'''
|
||||
message = message.replace('\r','')
|
||||
(header, httpbody) = message.split('\n\n')
|
||||
header = header.split('\n')
|
||||
statusline = header[0].split(' ')
|
||||
header = header[1:]
|
||||
headers = {}
|
||||
for dummy in header:
|
||||
row = dummy.split(' ',1)
|
||||
headers[row[0][:-1]] = row[1]
|
||||
return (statusline, headers, httpbody)
|
||||
|
||||
|
||||
def on_bosh_session_init_response(self, data):
|
||||
'''
|
||||
Called on init response - should check relevant attributes from body tag
|
||||
'''
|
||||
if data:
|
||||
statusline, headers, httpbody = self.parse_http_message(data)
|
||||
|
||||
if statusline[1] != '200':
|
||||
self.DEBUG(self.DBG, "HTTP Error in received session init response: %s"
|
||||
% statusline, 'error')
|
||||
# error handling TBD!
|
||||
|
||||
# ATM, whole <body> tag is pass to ProcessNonBocking.
|
||||
# Question is how to peel off the body tag from incoming stanzas and make
|
||||
# use of ordinar xmpp traffic handling.
|
||||
self.Dispatcher.ProcessNonBlocking(httpbody)
|
||||
|
||||
|
||||
def _check_stream_start(self, ns, tag, attrs):
|
||||
'''
|
||||
callback stub called from XML Parser when <stream..> is discovered
|
||||
'''
|
||||
self.DEBUG(self.DBG, 'CHECK_STREAM_START: ns: %s, tag: %s, attrs: %s'
|
||||
% (ns, tag, attrs), 'info')
|
||||
|
||||
|
||||
def StreamInit(self):
|
||||
'''
|
||||
Initiation of BOSH session. Called instead of Dispatcher.StreamInit()
|
||||
Initial body tag is created and sent to Conn Manager.
|
||||
'''
|
||||
self.Dispatcher.Stream = simplexml.NodeBuilder()
|
||||
self.Dispatcher.Stream._dispatch_depth = 2
|
||||
self.Dispatcher.Stream.dispatch = self.Dispatcher.dispatch
|
||||
self.Dispatcher.Stream.stream_header_received = self._check_stream_start
|
||||
self.debug_flags.append(simplexml.DBG_NODEBUILDER)
|
||||
self.Dispatcher.Stream.DEBUG = self.DEBUG
|
||||
self.Dispatcher.Stream.features = None
|
||||
|
||||
initial_body_tag = simplexml.Node('body')
|
||||
initial_body_tag.setNamespace(self.Namespace)
|
||||
initial_body_tag.setAttr('content', 'text/xml; charset=utf-8')
|
||||
initial_body_tag.setAttr('hold', str(self.bosh_hold))
|
||||
initial_body_tag.setAttr('to', self.xmpp_server)
|
||||
initial_body_tag.setAttr('wait', str(self.bosh_wait))
|
||||
|
||||
r = random.Random()
|
||||
r.seed()
|
||||
# with 50-bit random initial rid, session would have to go up
|
||||
# to 7881299347898368 messages to raise rid over 2**53
|
||||
# (see http://www.xmpp.org/extensions/xep-0124.html#rids)
|
||||
self.bosh_rid = r.getrandbits(50)
|
||||
initial_body_tag.setAttr('rid', str(self.bosh_rid))
|
||||
|
||||
if locale.getdefaultlocale()[0]:
|
||||
initial_body_tag.setAttr('xml:lang',
|
||||
locale.getdefaultlocale()[0].split('_')[0])
|
||||
initial_body_tag.setAttr('xmpp:version', '1.0')
|
||||
initial_body_tag.setAttr('xmlns:xmpp', 'urn:xmpp:xbosh')
|
||||
|
||||
self.send(self.build_bosh_message(initial_body_tag))
|
||||
|
||||
|
||||
def build_bosh_message(self, httpbody):
|
||||
'''
|
||||
Builds bosh http message with given body.
|
||||
Values for headers and status line fields are taken from class variables.
|
||||
)
|
||||
'''
|
||||
headers = ['POST %s HTTP/1.1' % self.bosh_uri,
|
||||
'Host: %s' % self.bosh_host,
|
||||
'Content-Type: text/xml; charset=utf-8',
|
||||
'Content-Length: %s' % len(str(httpbody)),
|
||||
'\r\n']
|
||||
headers = '\r\n'.join(headers)
|
||||
return('%s%s\r\n' % (headers, httpbody))
|
||||
|
||||
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
## client_nb.py
|
||||
## based on client.py
|
||||
## based on client.py
|
||||
##
|
||||
## Copyright (C) 2003-2005 Alexey "Snake" Nezhdanov
|
||||
## modified by Dimitur Kirov <dkirov@gmail.com>
|
||||
## modified by Dimitur Kirov <dkirov@gmail.com>
|
||||
##
|
||||
## This program is free software; you can redistribute it and/or modify
|
||||
## it under the terms of the GNU General Public License as published by
|
||||
|
@ -29,7 +29,7 @@ import debug
|
|||
import transports_nb, dispatcher_nb, auth_nb, roster_nb
|
||||
from client import *
|
||||
|
||||
class NBCommonClient(CommonClient):
|
||||
class NBCommonClient:
|
||||
''' Base for Client and Component classes.'''
|
||||
def __init__(self, server, port=5222, debug=['always', 'nodebuilder'], caller=None,
|
||||
on_connect=None, on_proxy_failure=None, on_connect_failure=None):
|
||||
|
@ -185,6 +185,37 @@ class NBCommonClient(CommonClient):
|
|||
self.on_stream_start = None
|
||||
return True
|
||||
|
||||
# moved from client.CommonClient:
|
||||
def RegisterDisconnectHandler(self,handler):
|
||||
""" Register handler that will be called on disconnect."""
|
||||
self.disconnect_handlers.append(handler)
|
||||
|
||||
def UnregisterDisconnectHandler(self,handler):
|
||||
""" Unregister handler that is called on disconnect."""
|
||||
self.disconnect_handlers.remove(handler)
|
||||
|
||||
def DisconnectHandler(self):
|
||||
""" Default disconnect handler. Just raises an IOError.
|
||||
If you choosed to use this class in your production client,
|
||||
override this method or at least unregister it. """
|
||||
raise IOError('Disconnected from server.')
|
||||
|
||||
def event(self,eventName,args={}):
|
||||
""" Default event handler. To be overriden. """
|
||||
print "Event: ",(eventName,args)
|
||||
|
||||
def isConnected(self):
|
||||
""" Returns connection state. F.e.: None / 'tls' / 'tcp+non_sasl' . """
|
||||
return self.connected
|
||||
|
||||
def get_peerhost(self):
|
||||
''' get the ip address of the account, from which is made connection
|
||||
to the server , (e.g. me).
|
||||
We will create listening socket on the same ip '''
|
||||
# moved from client.CommonClient
|
||||
if hasattr(self, 'Connection'):
|
||||
return self.Connection._sock.getsockname()
|
||||
|
||||
class NonBlockingClient(NBCommonClient):
|
||||
''' Example client class, based on CommonClient. '''
|
||||
def connect(self,server=None,proxy=None,secure=None,use_srv=True):
|
||||
|
|
|
@ -1,330 +0,0 @@
|
|||
## $Id: commands.py,v 1.11 2005/11/30 17:03:11 normanr Exp $
|
||||
|
||||
## Ad-Hoc Command manager
|
||||
## Mike Albon (c) 5th January 2005
|
||||
|
||||
## This program is free software; you can redistribute it and/or modify
|
||||
## it under the terms of the GNU General Public License as published by
|
||||
## the Free Software Foundation; either version 2, or (at your option)
|
||||
## any later version.
|
||||
##
|
||||
## This program is distributed in the hope that it will be useful,
|
||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
## GNU General Public License for more details.
|
||||
|
||||
|
||||
"""This module is a ad-hoc command processor for xmpppy. It uses the plug-in mechanism like most of the core library. It depends on a DISCO browser manager.
|
||||
|
||||
There are 3 classes here, a command processor Commands like the Browser, and a command template plugin Command, and an example command.
|
||||
|
||||
To use this module:
|
||||
|
||||
Instansiate the module with the parent transport and disco browser manager as parameters.
|
||||
'Plug in' commands using the command template.
|
||||
The command feature must be added to existing disco replies where neccessary.
|
||||
|
||||
What it supplies:
|
||||
|
||||
Automatic command registration with the disco browser manager.
|
||||
Automatic listing of commands in the public command list.
|
||||
A means of handling requests, by redirection though the command manager.
|
||||
"""
|
||||
|
||||
from protocol import *
|
||||
from client import PlugIn
|
||||
|
||||
class Commands(PlugIn):
|
||||
"""Commands is an ancestor of PlugIn and can be attached to any session.
|
||||
|
||||
The commands class provides a lookup and browse mechnism. It follows the same priciple of the Browser class, for Service Discovery to provide the list of commands, it adds the 'list' disco type to your existing disco handler function.
|
||||
|
||||
How it works:
|
||||
The commands are added into the existing Browser on the correct nodes. When the command list is built the supplied discovery handler function needs to have a 'list' option in type. This then gets enumerated, all results returned as None are ignored.
|
||||
The command executed is then called using it's Execute method. All session management is handled by the command itself.
|
||||
"""
|
||||
def __init__(self, browser):
|
||||
"""Initialises class and sets up local variables"""
|
||||
PlugIn.__init__(self)
|
||||
DBG_LINE='commands'
|
||||
self._exported_methods=[]
|
||||
self._handlers={'':{}}
|
||||
self._browser = browser
|
||||
|
||||
def plugin(self, owner):
|
||||
"""Makes handlers within the session"""
|
||||
# Plug into the session and the disco manager
|
||||
# We only need get and set, results are not needed by a service provider, only a service user.
|
||||
owner.RegisterHandler('iq',self._CommandHandler,typ='set',ns=NS_COMMANDS)
|
||||
owner.RegisterHandler('iq',self._CommandHandler,typ='get',ns=NS_COMMANDS)
|
||||
self._browser.setDiscoHandler(self._DiscoHandler,node=NS_COMMANDS,jid='')
|
||||
|
||||
def plugout(self):
|
||||
"""Removes handlers from the session"""
|
||||
# unPlug from the session and the disco manager
|
||||
self._owner.UnregisterHandler('iq',self_CommandHandler,ns=NS_COMMANDS)
|
||||
for jid in self._handlers:
|
||||
self._browser.delDiscoHandler(self._DiscoHandler,node=NS_COMMANDS)
|
||||
|
||||
def _CommandHandler(self,conn,request):
|
||||
"""The internal method to process the routing of command execution requests"""
|
||||
# This is the command handler itself.
|
||||
# We must:
|
||||
# Pass on command execution to command handler
|
||||
# (Do we need to keep session details here, or can that be done in the command?)
|
||||
jid = str(request.getTo())
|
||||
try:
|
||||
node = request.getTagAttr('command','node')
|
||||
except:
|
||||
conn.send(Error(request,ERR_BAD_REQUEST))
|
||||
raise NodeProcessed
|
||||
if self._handlers.has_key(jid):
|
||||
if self._handlers[jid].has_key(node):
|
||||
self._handlers[jid][node]['execute'](conn,request)
|
||||
else:
|
||||
conn.send(Error(request,ERR_ITEM_NOT_FOUND))
|
||||
raise NodeProcessed
|
||||
elif self._handlers[''].has_key(node):
|
||||
self._handlers[''][node]['execute'](conn,request)
|
||||
else:
|
||||
conn.send(Error(request,ERR_ITEM_NOT_FOUND))
|
||||
raise NodeProcessed
|
||||
|
||||
def _DiscoHandler(self,conn,request,typ):
|
||||
"""The internal method to process service discovery requests"""
|
||||
# This is the disco manager handler.
|
||||
if typ == 'items':
|
||||
# We must:
|
||||
# Generate a list of commands and return the list
|
||||
# * This handler does not handle individual commands disco requests.
|
||||
# Pseudo:
|
||||
# Enumerate the 'item' disco of each command for the specified jid
|
||||
# Build responce and send
|
||||
# To make this code easy to write we add an 'list' disco type, it returns a tuple or 'none' if not advertised
|
||||
list = []
|
||||
items = []
|
||||
jid = str(request.getTo())
|
||||
# Get specific jid based results
|
||||
if self._handlers.has_key(jid):
|
||||
for each in self._handlers[jid].keys():
|
||||
items.append((jid,each))
|
||||
else:
|
||||
# Get generic results
|
||||
for each in self._handlers[''].keys():
|
||||
items.append(('',each))
|
||||
if items != []:
|
||||
for each in items:
|
||||
i = self._handlers[each[0]][each[1]]['disco'](conn,request,'list')
|
||||
if i is not None:
|
||||
list.append(Node(tag='item',attrs={'jid':i[0],'node':i[1],'name':i[2]}))
|
||||
iq = request.buildReply('result')
|
||||
if request.getQuerynode(): iq.setQuerynode(request.getQuerynode())
|
||||
iq.setQueryPayload(list)
|
||||
conn.send(iq)
|
||||
else:
|
||||
conn.send(Error(request,ERR_ITEM_NOT_FOUND))
|
||||
raise NodeProcessed
|
||||
elif typ == 'info':
|
||||
return {'ids':[{'category':'automation','type':'command-list'}],'features':[]}
|
||||
|
||||
def addCommand(self,name,cmddisco,cmdexecute,jid=''):
|
||||
"""The method to call if adding a new command to the session, the requred parameters of cmddisco and cmdexecute are the methods to enable that command to be executed"""
|
||||
# This command takes a command object and the name of the command for registration
|
||||
# We must:
|
||||
# Add item into disco
|
||||
# Add item into command list
|
||||
if not self._handlers.has_key(jid):
|
||||
self._handlers[jid]={}
|
||||
self._browser.setDiscoHandler(self._DiscoHandler,node=NS_COMMANDS,jid=jid)
|
||||
if self._handlers[jid].has_key(name):
|
||||
raise NameError,'Command Exists'
|
||||
else:
|
||||
self._handlers[jid][name]={'disco':cmddisco,'execute':cmdexecute}
|
||||
# Need to add disco stuff here
|
||||
self._browser.setDiscoHandler(cmddisco,node=name,jid=jid)
|
||||
|
||||
def delCommand(self,name,jid=''):
|
||||
"""Removed command from the session"""
|
||||
# This command takes a command object and the name used for registration
|
||||
# We must:
|
||||
# Remove item from disco
|
||||
# Remove item from command list
|
||||
if not self._handlers.has_key(jid):
|
||||
raise NameError,'Jid not found'
|
||||
if not self._handlers[jid].has_key(name):
|
||||
raise NameError, 'Command not found'
|
||||
else:
|
||||
#Do disco removal here
|
||||
command = self.getCommand(name,jid)['disco']
|
||||
del self._handlers[jid][name]
|
||||
self._browser.delDiscoHandler(command,node=name,jid=jid)
|
||||
|
||||
def getCommand(self,name,jid=''):
|
||||
"""Returns the command tuple"""
|
||||
# This gets the command object with name
|
||||
# We must:
|
||||
# Return item that matches this name
|
||||
if not self._handlers.has_key(jid):
|
||||
raise NameError,'Jid not found'
|
||||
elif not self._handlers[jid].has_key(name):
|
||||
raise NameError,'Command not found'
|
||||
else:
|
||||
return self._handlers[jid][name]
|
||||
|
||||
class Command_Handler_Prototype(PlugIn):
|
||||
"""This is a prototype command handler, as each command uses a disco method
|
||||
and execute method you can implement it any way you like, however this is
|
||||
my first attempt at making a generic handler that you can hang process
|
||||
stages on too. There is an example command below.
|
||||
|
||||
The parameters are as follows:
|
||||
name : the name of the command within the jabber environment
|
||||
description : the natural language description
|
||||
discofeatures : the features supported by the command
|
||||
initial : the initial command in the from of {'execute':commandname}
|
||||
|
||||
All stages set the 'actions' dictionary for each session to represent the possible options available.
|
||||
"""
|
||||
name = 'examplecommand'
|
||||
count = 0
|
||||
description = 'an example command'
|
||||
discofeatures = [NS_COMMANDS,NS_DATA]
|
||||
# This is the command template
|
||||
def __init__(self,jid=''):
|
||||
"""Set up the class"""
|
||||
PlugIn.__init__(self)
|
||||
DBG_LINE='command'
|
||||
self.sessioncount = 0
|
||||
self.sessions = {}
|
||||
# Disco information for command list pre-formatted as a tuple
|
||||
self.discoinfo = {'ids':[{'category':'automation','type':'command-node','name':self.description}],'features': self.discofeatures}
|
||||
self._jid = jid
|
||||
|
||||
def plugin(self,owner):
|
||||
"""Plug command into the commands class"""
|
||||
# The owner in this instance is the Command Processor
|
||||
self._commands = owner
|
||||
self._owner = owner._owner
|
||||
self._commands.addCommand(self.name,self._DiscoHandler,self.Execute,jid=self._jid)
|
||||
|
||||
def plugout(self):
|
||||
"""Remove command from the commands class"""
|
||||
self._commands.delCommand(name,self._jid)
|
||||
|
||||
def getSessionID(self):
|
||||
"""Returns an id for the command session"""
|
||||
self.count = self.count+1
|
||||
return 'cmd-%s-%d'%(self.name,self.count)
|
||||
|
||||
def Execute(self,conn,request):
|
||||
"""The method that handles all the commands, and routes them to the correct method for that stage."""
|
||||
# New request or old?
|
||||
try:
|
||||
session = request.getTagAttr('command','sessionid')
|
||||
except:
|
||||
session = None
|
||||
try:
|
||||
action = request.getTagAttr('command','action')
|
||||
except:
|
||||
action = None
|
||||
if action is None: action = 'execute'
|
||||
# Check session is in session list
|
||||
if self.sessions.has_key(session):
|
||||
if self.sessions[session]['jid']==request.getFrom():
|
||||
# Check action is vaild
|
||||
if self.sessions[session]['actions'].has_key(action):
|
||||
# Execute next action
|
||||
self.sessions[session]['actions'][action](conn,request)
|
||||
else:
|
||||
# Stage not presented as an option
|
||||
self._owner.send(Error(request,ERR_BAD_REQUEST))
|
||||
raise NodeProcessed
|
||||
else:
|
||||
# Jid and session don't match. Go away imposter
|
||||
self._owner.send(Error(request,ERR_BAD_REQUEST))
|
||||
raise NodeProcessed
|
||||
elif session is not None:
|
||||
# Not on this sessionid you won't.
|
||||
self._owner.send(Error(request,ERR_BAD_REQUEST))
|
||||
raise NodeProcessed
|
||||
else:
|
||||
# New session
|
||||
self.initial[action](conn,request)
|
||||
|
||||
def _DiscoHandler(self,conn,request,type):
|
||||
"""The handler for discovery events"""
|
||||
if type == 'list':
|
||||
return (request.getTo(),self.name,self.description)
|
||||
elif type == 'items':
|
||||
return []
|
||||
elif type == 'info':
|
||||
return self.discoinfo
|
||||
|
||||
class TestCommand(Command_Handler_Prototype):
|
||||
""" Example class. You should read source if you wish to understate how it works.
|
||||
Generally, it presents a "master" that giudes user through to calculate something.
|
||||
"""
|
||||
name = 'testcommand'
|
||||
description = 'a noddy example command'
|
||||
def __init__(self,jid=''):
|
||||
""" Init internal constants. """
|
||||
Command_Handler_Prototype.__init__(self,jid)
|
||||
self.pi = 3.14
|
||||
self.initial = {'execute':self.cmdFirstStage}
|
||||
|
||||
def cmdFirstStage(self,conn,request):
|
||||
""" Determine """
|
||||
# This is the only place this should be repeated as all other stages should have SessionIDs
|
||||
try:
|
||||
session = request.getTagAttr('command','sessionid')
|
||||
except:
|
||||
session = None
|
||||
if session is None:
|
||||
session = self.getSessionID()
|
||||
sessions[session]={'jid':request.getFrom(),'actions':{'cancel':self.cmdCancel,'next':self.cmdSecondStage},'data':{'type':None}}
|
||||
# As this is the first stage we only send a form
|
||||
reply = request.buildReply('result')
|
||||
form = DataForm(title='Select type of operation',data=['Use the combobox to select the type of calculation you would like to do, then click Next',DataField(name='calctype',label='Calculation Type',value=sessions[session]['data']['type'],options=[['circlediameter','Calculate the Diameter of a circle'],['circlearea','Calculate the area of a circle']],typ='list-single',required=1)])
|
||||
replypayload = [Node('actions',attrs={'execute':'next'},payload=[Node('next')]),form]
|
||||
reply.addChild(name='command',attrs={'xmlns':NS_COMMAND,'node':request.getTagAttr('command','node'),'sessionid':session,'status':'executing'},payload=replypayload)
|
||||
self._owner.send(reply)
|
||||
raise NodeProcessed
|
||||
|
||||
def cmdSecondStage(self,conn,request):
|
||||
form = DataForm(node = result.getTag(name='command').getTag(name='x',namespace=NS_DATA))
|
||||
sessions[request.getTagAttr('command','sessionid')]['data']['type']=form.getField('calctype')
|
||||
sessions[request.getTagAttr('command','sessionid')]['actions']={'cancel':self.cmdCancel,None:self.cmdThirdStage,'previous':cmdFirstStage}
|
||||
# The form generation is split out to another method as it may be called by cmdThirdStage
|
||||
self.cmdSecondStageReply(conn,request)
|
||||
|
||||
def cmdSecondStageReply(self,conn,request):
|
||||
reply = request.buildReply('result')
|
||||
form = DataForm(title = 'Enter the radius', data=['Enter the radius of the circle (numbers only)',DataField(label='Radius',name='radius',typ='text-single')])
|
||||
replypayload = [Node('actions',attrs={'execute':'complete'},payload=[Node('complete'),Node('prev')]),form]
|
||||
reply.addChild(name='command',attrs={'xmlns':NS_COMMAND,'node':request.getTagAttr('command','node'),'sessionid':request.getTagAttr('command','sessionid'),'status':'executing'},payload=replypayload)
|
||||
self._owner.send(reply)
|
||||
raise NodeProcessed
|
||||
|
||||
def cmdThirdStage(self,conn,request):
|
||||
form = DataForm(node = result.getTag(name='command').getTag(name='x',namespace=NS_DATA))
|
||||
try:
|
||||
num = float(form.getField('radius'))
|
||||
except:
|
||||
self.cmdSecondStageReply(conn,request)
|
||||
if sessions[request.getTagAttr('command','sessionid')]['data']['type'] == 'circlearea':
|
||||
result = num*(pi**2)
|
||||
else:
|
||||
result = num*2*pi
|
||||
reply = result.buildReply(request)
|
||||
form = DataForm(typ='result',data=[DataField(label='result',name='result',value=result)])
|
||||
reply.addChild(name='command',attrs={'xmlns':NS_COMMAND,'node':request.getTagAttr('command','node'),'sessionid':request.getTagAttr('command','sessionid'),'status':'completed'},payload=form)
|
||||
self._owner.send(reply)
|
||||
raise NodeProcessed
|
||||
|
||||
def cmdCancel(self,conn,request):
|
||||
reply = request.buildReply('result')
|
||||
reply.addChild(name='command',attrs={'xmlns':NS_COMMAND,'node':request.getTagAttr('command','node'),'sessionid':request.getTagAttr('command','sessionid'),'status':'cancelled'})
|
||||
self._owner.send(reply)
|
||||
del sessions[request.getTagAttr('command','sessionid')]
|
||||
|
||||
|
|
@ -1,383 +0,0 @@
|
|||
## transports.py
|
||||
##
|
||||
## Copyright (C) 2003-2005 Alexey "Snake" Nezhdanov
|
||||
##
|
||||
## This program is free software; you can redistribute it and/or modify
|
||||
## it under the terms of the GNU General Public License as published by
|
||||
## the Free Software Foundation; either version 2, or (at your option)
|
||||
## any later version.
|
||||
##
|
||||
## This program is distributed in the hope that it will be useful,
|
||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
## GNU General Public License for more details.
|
||||
|
||||
# $Id: dispatcher.py,v 1.40 2006/01/18 19:26:43 normanr Exp $
|
||||
|
||||
"""
|
||||
Main xmpppy mechanism. Provides library with methods to assign different handlers
|
||||
to different XMPP stanzas.
|
||||
Contains one tunable attribute: DefaultTimeout (25 seconds by default). It defines time that
|
||||
Dispatcher.SendAndWaitForResponce method will wait for reply stanza before giving up.
|
||||
"""
|
||||
|
||||
import simplexml,time,sys
|
||||
from protocol import *
|
||||
from client import PlugIn
|
||||
|
||||
DefaultTimeout=25
|
||||
ID=0
|
||||
|
||||
class Dispatcher(PlugIn):
|
||||
""" Ancestor of PlugIn class. Handles XMPP stream, i.e. aware of stream headers.
|
||||
Can be plugged out/in to restart these headers (used for SASL f.e.). """
|
||||
def __init__(self):
|
||||
PlugIn.__init__(self)
|
||||
DBG_LINE='dispatcher'
|
||||
self.handlers={}
|
||||
self._expected={}
|
||||
self._defaultHandler=None
|
||||
self._pendingExceptions=[]
|
||||
self._eventHandler=None
|
||||
self._cycleHandlers=[]
|
||||
self._exported_methods=[self.Process,self.RegisterHandler,self.RegisterDefaultHandler,\
|
||||
self.RegisterEventHandler,self.UnregisterCycleHandler,self.RegisterCycleHandler,\
|
||||
self.RegisterHandlerOnce,self.UnregisterHandler,self.RegisterProtocol,\
|
||||
self.WaitForResponse,self.SendAndWaitForResponse,self.send,self.disconnect,\
|
||||
self.SendAndCallForResponse, self.getAnID, ]
|
||||
|
||||
def getAnID(self):
|
||||
global ID
|
||||
ID+=1
|
||||
return `ID`
|
||||
|
||||
def dumpHandlers(self):
|
||||
""" Return set of user-registered callbacks in it's internal format.
|
||||
Used within the library to carry user handlers set over Dispatcher replugins. """
|
||||
return self.handlers
|
||||
def restoreHandlers(self,handlers):
|
||||
""" Restores user-registered callbacks structure from dump previously obtained via dumpHandlers.
|
||||
Used within the library to carry user handlers set over Dispatcher replugins. """
|
||||
self.handlers=handlers
|
||||
|
||||
def _init(self):
|
||||
""" Registers default namespaces/protocols/handlers. Used internally. """
|
||||
self.RegisterNamespace('unknown')
|
||||
self.RegisterNamespace(NS_STREAMS)
|
||||
self.RegisterNamespace(self._owner.defaultNamespace)
|
||||
self.RegisterProtocol('iq',Iq)
|
||||
self.RegisterProtocol('presence',Presence)
|
||||
self.RegisterProtocol('message',Message)
|
||||
self.RegisterDefaultHandler(self.returnStanzaHandler)
|
||||
# Register Gajim's event handler as soon as dispatcher begins
|
||||
self.RegisterEventHandler(self._owner._caller._event_dispatcher)
|
||||
# self.RegisterHandler('error',self.streamErrorHandler,xmlns=NS_STREAMS)
|
||||
|
||||
def plugin(self, owner):
|
||||
""" Plug the Dispatcher instance into Client class instance and send initial stream header. Used internally."""
|
||||
self._init()
|
||||
for method in self._old_owners_methods:
|
||||
if method.__name__=='send': self._owner_send=method; break
|
||||
self._owner.lastErrNode=None
|
||||
self._owner.lastErr=None
|
||||
self._owner.lastErrCode=None
|
||||
self.StreamInit()
|
||||
|
||||
def plugout(self):
|
||||
""" Prepares instance to be destructed. """
|
||||
self.Stream.dispatch=None
|
||||
self.Stream.DEBUG=None
|
||||
self.Stream.features=None
|
||||
self.Stream.destroy()
|
||||
|
||||
def StreamInit(self):
|
||||
""" Send an initial stream header. """
|
||||
self.Stream=simplexml.NodeBuilder()
|
||||
self.Stream._dispatch_depth=2
|
||||
self.Stream.dispatch=self.dispatch
|
||||
self.Stream.stream_header_received=self._check_stream_start
|
||||
self._owner.debug_flags.append(simplexml.DBG_NODEBUILDER)
|
||||
self.Stream.DEBUG=self._owner.DEBUG
|
||||
self.Stream.features=None
|
||||
self._metastream=Node('stream:stream')
|
||||
self._metastream.setNamespace(self._owner.Namespace)
|
||||
self._metastream.setAttr('version','1.0')
|
||||
self._metastream.setAttr('xmlns:stream',NS_STREAMS)
|
||||
self._metastream.setAttr('to',self._owner.Server)
|
||||
self._owner.send("<?xml version='1.0'?>%s>"%str(self._metastream)[:-2])
|
||||
|
||||
def _check_stream_start(self,ns,tag,attrs):
|
||||
if ns<>NS_STREAMS or tag<>'stream':
|
||||
raise ValueError('Incorrect stream start: (%s,%s). Terminating.'%(tag,ns))
|
||||
|
||||
def Process(self, timeout=0):
|
||||
""" Check incoming stream for data waiting. If "timeout" is positive - block for as max. this time.
|
||||
Returns:
|
||||
1) length of processed data if some data were processed;
|
||||
2) '0' string if no data were processed but link is alive;
|
||||
3) 0 (zero) if underlying connection is closed.
|
||||
Take note that in case of disconnection detect during Process() call
|
||||
disconnect handlers are called automatically.
|
||||
"""
|
||||
for handler in self._cycleHandlers: handler(self)
|
||||
if len(self._pendingExceptions) > 0:
|
||||
_pendingException = self._pendingExceptions.pop()
|
||||
raise _pendingException[0], _pendingException[1], _pendingException[2]
|
||||
if self._owner.Connection.pending_data(timeout):
|
||||
try: data=self._owner.Connection.receive()
|
||||
except IOError: return
|
||||
self.Stream.Parse(data)
|
||||
if len(self._pendingExceptions) > 0:
|
||||
_pendingException = self._pendingExceptions.pop()
|
||||
raise _pendingException[0], _pendingException[1], _pendingException[2]
|
||||
return len(data)
|
||||
return '0' # It means that nothing is received but link is alive.
|
||||
|
||||
def RegisterNamespace(self,xmlns,order='info'):
|
||||
""" Creates internal structures for newly registered namespace.
|
||||
You can register handlers for this namespace afterwards. By default one namespace
|
||||
already registered (jabber:client or jabber:component:accept depending on context. """
|
||||
self.DEBUG('Registering namespace "%s"'%xmlns,order)
|
||||
self.handlers[xmlns]={}
|
||||
self.RegisterProtocol('unknown',Protocol,xmlns=xmlns)
|
||||
self.RegisterProtocol('default',Protocol,xmlns=xmlns)
|
||||
|
||||
def RegisterProtocol(self,tag_name,Proto,xmlns=None,order='info'):
|
||||
""" Used to declare some top-level stanza name to dispatcher.
|
||||
Needed to start registering handlers for such stanzas.
|
||||
Iq, message and presence protocols are registered by default. """
|
||||
if not xmlns: xmlns=self._owner.defaultNamespace
|
||||
self.DEBUG('Registering protocol "%s" as %s(%s)'%(tag_name,Proto,xmlns), order)
|
||||
self.handlers[xmlns][tag_name]={type:Proto, 'default':[]}
|
||||
|
||||
def RegisterNamespaceHandler(self,xmlns,handler,typ='',ns='', makefirst=0, system=0):
|
||||
""" Register handler for processing all stanzas for specified namespace. """
|
||||
self.RegisterHandler('default', handler, typ, ns, xmlns, makefirst, system)
|
||||
|
||||
def RegisterHandler(self,name,handler,typ='',ns='',xmlns=None, makefirst=0, system=0):
|
||||
"""Register user callback as stanzas handler of declared type. Callback must take
|
||||
(if chained, see later) arguments: dispatcher instance (for replying), incomed
|
||||
return of previous handlers.
|
||||
The callback must raise xmpp.NodeProcessed just before return if it want preven
|
||||
callbacks to be called with the same stanza as argument _and_, more importantly
|
||||
library from returning stanza to sender with error set (to be enabled in 0.2 ve
|
||||
Arguments:
|
||||
"name" - name of stanza. F.e. "iq".
|
||||
"handler" - user callback.
|
||||
"typ" - value of stanza's "type" attribute. If not specified any value match
|
||||
"ns" - namespace of child that stanza must contain.
|
||||
"chained" - chain together output of several handlers.
|
||||
"makefirst" - insert handler in the beginning of handlers list instead of
|
||||
adding it to the end. Note that more common handlers (i.e. w/o "typ" and "
|
||||
will be called first nevertheless.
|
||||
"system" - call handler even if NodeProcessed Exception were raised already.
|
||||
"""
|
||||
if not xmlns: xmlns=self._owner.defaultNamespace
|
||||
self.DEBUG('Registering handler %s for "%s" type->%s ns->%s(%s)'%(handler,name,typ,ns,xmlns), 'info')
|
||||
if not typ and not ns: typ='default'
|
||||
if not self.handlers.has_key(xmlns): self.RegisterNamespace(xmlns,'warn')
|
||||
if not self.handlers[xmlns].has_key(name): self.RegisterProtocol(name,Protocol,xmlns,'warn')
|
||||
if not self.handlers[xmlns][name].has_key(typ+ns): self.handlers[xmlns][name][typ+ns]=[]
|
||||
if makefirst: self.handlers[xmlns][name][typ+ns].insert(0,{'func':handler,'system':system})
|
||||
else: self.handlers[xmlns][name][typ+ns].append({'func':handler,'system':system})
|
||||
|
||||
def RegisterHandlerOnce(self,name,handler,typ='',ns='',xmlns=None,makefirst=0, system=0):
|
||||
""" Unregister handler after first call (not implemented yet). """
|
||||
if not xmlns: xmlns=self._owner.defaultNamespace
|
||||
self.RegisterHandler(name, handler, typ, ns, xmlns, makefirst, system)
|
||||
|
||||
def UnregisterHandler(self,name,handler,typ='',ns='',xmlns=None):
|
||||
""" Unregister handler. "typ" and "ns" must be specified exactly the same as with registering."""
|
||||
if not xmlns: xmlns=self._owner.defaultNamespace
|
||||
if not typ and not ns: typ='default'
|
||||
if not self.handlers[xmlns].has_key(name): return
|
||||
if not self.handlers[xmlns][name].has_key(typ+ns): return
|
||||
for pack in self.handlers[xmlns][name][typ+ns]:
|
||||
if handler==pack['func']: break
|
||||
else: pack=None
|
||||
try: self.handlers[xmlns][name][typ+ns].remove(pack)
|
||||
except ValueError: pass
|
||||
|
||||
def RegisterDefaultHandler(self,handler):
|
||||
""" Specify the handler that will be used if no NodeProcessed exception were raised.
|
||||
This is returnStanzaHandler by default. """
|
||||
self._defaultHandler=handler
|
||||
|
||||
def RegisterEventHandler(self,handler):
|
||||
""" Register handler that will process events. F.e. "FILERECEIVED" event. """
|
||||
self._eventHandler=handler
|
||||
|
||||
def returnStanzaHandler(self,conn,stanza):
|
||||
""" Return stanza back to the sender with <feature-not-implemennted/> error set. """
|
||||
if stanza.getType() in ['get','set']:
|
||||
conn.send(Error(stanza,ERR_FEATURE_NOT_IMPLEMENTED))
|
||||
|
||||
def streamErrorHandler(self,conn,error):
|
||||
name,text='error',error.getData()
|
||||
for tag in error.getChildren():
|
||||
if tag.getNamespace()==NS_XMPP_STREAMS:
|
||||
if tag.getName()=='text': text=tag.getData()
|
||||
else: name=tag.getName()
|
||||
if name in stream_exceptions.keys(): exc=stream_exceptions[name]
|
||||
else: exc=StreamError
|
||||
raise exc((name,text))
|
||||
|
||||
def RegisterCycleHandler(self,handler):
|
||||
""" Register handler that will be called on every Dispatcher.Process() call. """
|
||||
if handler not in self._cycleHandlers: self._cycleHandlers.append(handler)
|
||||
|
||||
def UnregisterCycleHandler(self,handler):
|
||||
""" Unregister handler that will is called on every Dispatcher.Process() call."""
|
||||
if handler in self._cycleHandlers: self._cycleHandlers.remove(handler)
|
||||
|
||||
def Event(self,realm,event,data):
|
||||
""" Raise some event. Takes three arguments:
|
||||
1) "realm" - scope of event. Usually a namespace.
|
||||
2) "event" - the event itself. F.e. "SUCESSFULL SEND".
|
||||
3) data that comes along with event. Depends on event."""
|
||||
if self._eventHandler: self._eventHandler(realm,event,data)
|
||||
|
||||
def dispatch(self,stanza,session=None,direct=0):
|
||||
""" Main procedure that performs XMPP stanza recognition and calling apppropriate handlers for it.
|
||||
Called internally. """
|
||||
if not session: session=self
|
||||
session.Stream._mini_dom=None
|
||||
name=stanza.getName()
|
||||
|
||||
if not direct and self._owner._component:
|
||||
if name == 'route':
|
||||
if stanza.getAttr('error') is None:
|
||||
if len(stanza.getChildren()) == 1:
|
||||
stanza = stanza.getChildren()[0]
|
||||
name=stanza.getName()
|
||||
else:
|
||||
for each in stanza.getChildren():
|
||||
self.dispatch(each,session,direct=1)
|
||||
return
|
||||
elif name == 'presence':
|
||||
return
|
||||
elif name in ('features','bind'):
|
||||
pass
|
||||
else:
|
||||
raise UnsupportedStanzaType(name)
|
||||
|
||||
if name=='features': session.Stream.features=stanza
|
||||
|
||||
xmlns=stanza.getNamespace()
|
||||
if not self.handlers.has_key(xmlns):
|
||||
self.DEBUG("Unknown namespace: " + xmlns,'warn')
|
||||
xmlns='unknown'
|
||||
if not self.handlers[xmlns].has_key(name):
|
||||
self.DEBUG("Unknown stanza: " + name,'warn')
|
||||
name='unknown'
|
||||
else:
|
||||
self.DEBUG("Got %s/%s stanza"%(xmlns,name), 'ok')
|
||||
|
||||
if stanza.__class__.__name__=='Node': stanza=self.handlers[xmlns][name][type](node=stanza)
|
||||
|
||||
typ=stanza.getType()
|
||||
if not typ: typ=''
|
||||
stanza.props=stanza.getProperties()
|
||||
ID=stanza.getID()
|
||||
|
||||
session.DEBUG("Dispatching %s stanza with type->%s props->%s id->%s"%(name,typ,stanza.props,ID),'ok')
|
||||
|
||||
list=['default'] # we will use all handlers:
|
||||
if self.handlers[xmlns][name].has_key(typ): list.append(typ) # from very common...
|
||||
for prop in stanza.props:
|
||||
if self.handlers[xmlns][name].has_key(prop): list.append(prop)
|
||||
if typ and self.handlers[xmlns][name].has_key(typ+prop): list.append(typ+prop) # ...to very particular
|
||||
|
||||
chain=self.handlers[xmlns]['default']['default']
|
||||
for key in list:
|
||||
if key: chain = chain + self.handlers[xmlns][name][key]
|
||||
|
||||
output=''
|
||||
if session._expected.has_key(ID):
|
||||
user=0
|
||||
if type(session._expected[ID])==type(()):
|
||||
cb,args=session._expected[ID]
|
||||
session.DEBUG("Expected stanza arrived. Callback %s(%s) found!"%(cb,args),'ok')
|
||||
try: cb(session,stanza,**args)
|
||||
except Exception, typ:
|
||||
if typ.__class__.__name__<>'NodeProcessed': raise
|
||||
else:
|
||||
session.DEBUG("Expected stanza arrived!",'ok')
|
||||
session._expected[ID]=stanza
|
||||
else: user=1
|
||||
for handler in chain:
|
||||
if user or handler['system']:
|
||||
try:
|
||||
handler['func'](session,stanza)
|
||||
except Exception, typ:
|
||||
if typ.__class__.__name__<>'NodeProcessed':
|
||||
self._pendingExceptions.insert(0, sys.exc_info())
|
||||
return
|
||||
user=0
|
||||
if user and self._defaultHandler: self._defaultHandler(session,stanza)
|
||||
|
||||
def WaitForResponse(self, ID, timeout=None):
|
||||
""" Block and wait until stanza with specific "id" attribute will come.
|
||||
If no such stanza is arrived within timeout, return None.
|
||||
If operation failed for some reason then owner's attributes
|
||||
lastErrNode, lastErr and lastErrCode are set accordingly. """
|
||||
if timeout is None: timeout=DefaultTimeout
|
||||
self._expected[ID]=None
|
||||
has_timed_out=0
|
||||
abort_time=time.time() + timeout
|
||||
self.DEBUG("Waiting for ID:%s with timeout %s..." % (ID,timeout),'wait')
|
||||
while not self._expected[ID]:
|
||||
if not self.Process(0.04):
|
||||
self._owner.lastErr="Disconnect"
|
||||
return None
|
||||
if time.time() > abort_time:
|
||||
self._owner.lastErr="Timeout"
|
||||
return None
|
||||
response=self._expected[ID]
|
||||
del self._expected[ID]
|
||||
if response.getErrorCode():
|
||||
self._owner.lastErrNode=response
|
||||
self._owner.lastErr=response.getError()
|
||||
self._owner.lastErrCode=response.getErrorCode()
|
||||
return response
|
||||
|
||||
def SendAndWaitForResponse(self, stanza, timeout=None):
|
||||
""" Put stanza on the wire and wait for recipient's response to it. """
|
||||
if timeout is None: timeout=DefaultTimeout
|
||||
return self.WaitForResponse(self.send(stanza),timeout)
|
||||
|
||||
def SendAndCallForResponse(self, stanza, func, args={}):
|
||||
""" Put stanza on the wire and call back when recipient replies.
|
||||
Additional callback arguments can be specified in args. """
|
||||
self._expected[self.send(stanza)]=(func,args)
|
||||
|
||||
def send(self,stanza):
|
||||
""" Serialise stanza and put it on the wire. Assign an unique ID to it before send.
|
||||
Returns assigned ID."""
|
||||
if type(stanza) in [type(''), type(u'')]: return self._owner_send(stanza)
|
||||
if not isinstance(stanza,Protocol): _ID=None
|
||||
elif not stanza.getID():
|
||||
global ID
|
||||
ID+=1
|
||||
_ID=`ID`
|
||||
stanza.setID(_ID)
|
||||
else: _ID=stanza.getID()
|
||||
if self._owner._registered_name and not stanza.getAttr('from'): stanza.setAttr('from',self._owner._registered_name)
|
||||
if self._owner._component and stanza.getName()!='bind':
|
||||
to=self._owner.Server
|
||||
if stanza.getTo() and stanza.getTo().getDomain():
|
||||
to=stanza.getTo().getDomain()
|
||||
frm=stanza.getFrom()
|
||||
if frm.getDomain():
|
||||
frm=frm.getDomain()
|
||||
route=Protocol('route',to=to,frm=frm,payload=[stanza])
|
||||
stanza=route
|
||||
stanza.setNamespace(self._owner.Namespace)
|
||||
stanza.setParent(self._metastream)
|
||||
self._owner_send(stanza)
|
||||
return _ID
|
||||
|
||||
def disconnect(self):
|
||||
""" Send a stream terminator and and handle all incoming stanzas before stream closure. """
|
||||
self._owner_send('</stream:stream>')
|
||||
while self.Process(1): pass
|
|
@ -1,89 +0,0 @@
|
|||
# Example script for usage of BOSHClient class
|
||||
# --------------------------------------------
|
||||
# run `python run_client_bosh.py` in gajim/src/common/xmpp/examples/ directory
|
||||
# and quit with CTRL + c.
|
||||
# Script will open TCP connection to Connection Manager, send BOSH initial
|
||||
# request and receive initial response. Handling of init response is not
|
||||
# done yet.
|
||||
|
||||
|
||||
# imports gtk because of gobject.timeout_add() which is used for processing
|
||||
# idlequeue
|
||||
# TODO: rewrite to thread timer
|
||||
import gtk
|
||||
import gobject
|
||||
import sys, os.path
|
||||
|
||||
xmpppy_dir = os.path.join(os.path.abspath(os.path.dirname(__file__)), '..')
|
||||
sys.path.append(xmpppy_dir)
|
||||
|
||||
import idlequeue
|
||||
from client_bosh import BOSHClient
|
||||
|
||||
|
||||
class DummyConnection():
|
||||
'''
|
||||
Dummy class for test run of bosh_client. I use it because in Connection class
|
||||
the gajim.py module is imported and stuff from it is read in there, so it
|
||||
would be difficult to debug IMHO. On the other hand, I will have to test with
|
||||
Connection class sooner or later somehow because that's the main place where
|
||||
BOSHClient should be used.
|
||||
DummyConnection class holds and processes IdleQueue for BOSHClient.
|
||||
'''
|
||||
|
||||
def __init__(self, iq_interval_ms=1000):
|
||||
self.classname = self.__class__.__name__
|
||||
self.iq_interval_ms = iq_interval_ms
|
||||
self.idlequeue = idlequeue.IdleQueue()
|
||||
self.timer=gobject.timeout_add(iq_interval_ms, self.process_idlequeue)
|
||||
|
||||
|
||||
def process_idlequeue(self):
|
||||
'''
|
||||
called each iq_interval_ms miliseconds. Checks for idlequeue timeouts.
|
||||
'''
|
||||
self.idlequeue.process()
|
||||
return True
|
||||
|
||||
# callback stubs follows
|
||||
def _event_dispatcher(self, realm, event, data):
|
||||
print "\n>>> %s._event_dispatcher called:" % self.classname
|
||||
print ">>> realm: %s, event: %s, data: %s\n" % (realm, event, data)
|
||||
|
||||
|
||||
def onconsucc(self):
|
||||
print '%s: CONNECTION SUCCEEDED' % self.classname
|
||||
|
||||
|
||||
def onconfail(self, retry=None):
|
||||
print '%s: CONNECTION FAILED.. retry?: %s' % (self.classname, retry)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
dc = DummyConnection()
|
||||
|
||||
# you can use my instalation of ejabberd2:
|
||||
server = 'star.securitynet.cz'
|
||||
bosh_conn_mgr = 'http://star.securitynet.cz/http-bind/'
|
||||
|
||||
#server='jabbim.cz'
|
||||
#bosh_conn_mgr='http://bind.jabbim.cz/'
|
||||
|
||||
bc = BOSHClient(
|
||||
server = server,
|
||||
bosh_conn_mgr = bosh_conn_mgr,
|
||||
bosh_port = 80,
|
||||
on_connect = dc.onconsucc,
|
||||
on_connect_failure = dc.onconfail,
|
||||
caller = dc
|
||||
)
|
||||
|
||||
bc.set_idlequeue(dc.idlequeue)
|
||||
|
||||
bc.connect()
|
||||
|
||||
try:
|
||||
gtk.main()
|
||||
except KeyboardInterrupt:
|
||||
dc.process_idlequeue()
|
||||
|
|
@ -1,184 +0,0 @@
|
|||
## features.py
|
||||
##
|
||||
## Copyright (C) 2003-2004 Alexey "Snake" Nezhdanov
|
||||
##
|
||||
## This program is free software; you can redistribute it and/or modify
|
||||
## it under the terms of the GNU General Public License as published by
|
||||
## the Free Software Foundation; either version 2, or (at your option)
|
||||
## any later version.
|
||||
##
|
||||
## This program is distributed in the hope that it will be useful,
|
||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
## GNU General Public License for more details.
|
||||
|
||||
# $Id: features.py,v 1.22 2005/09/30 20:13:04 mikealbon Exp $
|
||||
|
||||
"""
|
||||
This module contains variable stuff that is not worth splitting into separate modules.
|
||||
Here is:
|
||||
DISCO client and agents-to-DISCO and browse-to-DISCO emulators.
|
||||
IBR and password manager.
|
||||
jabber:iq:privacy methods
|
||||
All these methods takes 'disp' first argument that should be already connected
|
||||
(and in most cases already authorised) dispatcher instance.
|
||||
"""
|
||||
|
||||
from protocol import *
|
||||
|
||||
REGISTER_DATA_RECEIVED='REGISTER DATA RECEIVED'
|
||||
PRIVACY_LISTS_RECEIVED='PRIVACY LISTS RECEIVED'
|
||||
PRIVACY_LIST_RECEIVED='PRIVACY LIST RECEIVED'
|
||||
PRIVACY_LISTS_ACTIVE_DEFAULT='PRIVACY LISTS ACTIVE DEFAULT'
|
||||
|
||||
### DISCO ### http://jabber.org/protocol/disco ### JEP-0030 ####################
|
||||
### Browse ### jabber:iq:browse ### JEP-0030 ###################################
|
||||
### Agents ### jabber:iq:agents ### JEP-0030 ###################################
|
||||
def _discover(disp,ns,jid,node=None,fb2b=0,fb2a=1):
|
||||
""" Try to obtain info from the remote object.
|
||||
If remote object doesn't support disco fall back to browse (if fb2b is true)
|
||||
and if it doesnt support browse (or fb2b is not true) fall back to agents protocol
|
||||
(if gb2a is true). Returns obtained info. Used internally. """
|
||||
iq=Iq(to=jid,typ='get',queryNS=ns)
|
||||
if node: iq.setQuerynode(node)
|
||||
rep=disp.SendAndWaitForResponse(iq)
|
||||
if fb2b and not isResultNode(rep): rep=disp.SendAndWaitForResponse(Iq(to=jid,typ='get',queryNS=NS_BROWSE)) # Fallback to browse
|
||||
if fb2a and not isResultNode(rep): rep=disp.SendAndWaitForResponse(Iq(to=jid,typ='get',queryNS=NS_AGENTS)) # Fallback to agents
|
||||
if isResultNode(rep): return rep.getQueryPayload()
|
||||
return []
|
||||
|
||||
def discoverItems(disp,jid,node=None):
|
||||
""" Query remote object about any items that it contains. Return items list. """
|
||||
""" According to JEP-0030:
|
||||
query MAY have node attribute
|
||||
item: MUST HAVE jid attribute and MAY HAVE name, node, action attributes.
|
||||
action attribute of item can be either of remove or update value."""
|
||||
ret=[]
|
||||
for i in _discover(disp,NS_DISCO_ITEMS,jid,node):
|
||||
if i.getName()=='agent' and i.getTag('name'): i.setAttr('name',i.getTagData('name'))
|
||||
ret.append(i.attrs)
|
||||
return ret
|
||||
|
||||
def discoverInfo(disp,jid,node=None):
|
||||
""" Query remote object about info that it publishes. Returns identities and features lists."""
|
||||
""" According to JEP-0030:
|
||||
query MAY have node attribute
|
||||
identity: MUST HAVE category and name attributes and MAY HAVE type attribute.
|
||||
feature: MUST HAVE var attribute"""
|
||||
identities , features = [] , []
|
||||
for i in _discover(disp,NS_DISCO_INFO,jid,node):
|
||||
if i.getName()=='identity': identities.append(i.attrs)
|
||||
elif i.getName()=='feature': features.append(i.getAttr('var'))
|
||||
elif i.getName()=='agent':
|
||||
if i.getTag('name'): i.setAttr('name',i.getTagData('name'))
|
||||
if i.getTag('description'): i.setAttr('name',i.getTagData('description'))
|
||||
identities.append(i.attrs)
|
||||
if i.getTag('groupchat'): features.append(NS_GROUPCHAT)
|
||||
if i.getTag('register'): features.append(NS_REGISTER)
|
||||
if i.getTag('search'): features.append(NS_SEARCH)
|
||||
return identities , features
|
||||
|
||||
### Registration ### jabber:iq:register ### JEP-0077 ###########################
|
||||
def getRegInfo(disp,host,info={},sync=True):
|
||||
""" Gets registration form from remote host.
|
||||
You can pre-fill the info dictionary.
|
||||
F.e. if you are requesting info on registering user joey than specify
|
||||
info as {'username':'joey'}. See JEP-0077 for details.
|
||||
'disp' must be connected dispatcher instance."""
|
||||
iq=Iq('get',NS_REGISTER,to=host)
|
||||
for i in info.keys(): iq.setTagData(i,info[i])
|
||||
if sync:
|
||||
resp=disp.SendAndWaitForResponse(iq)
|
||||
_ReceivedRegInfo(disp.Dispatcher,resp, host)
|
||||
else: disp.SendAndCallForResponse(iq,_ReceivedRegInfo, {'agent': host})
|
||||
|
||||
def _ReceivedRegInfo(con, resp, agent):
|
||||
iq=Iq('get',NS_REGISTER,to=agent)
|
||||
if not isResultNode(resp): return
|
||||
df=resp.getTag('query',namespace=NS_REGISTER).getTag('x',namespace=NS_DATA)
|
||||
if df:
|
||||
con.Event(NS_REGISTER,REGISTER_DATA_RECEIVED,(agent,DataForm(node=df),True))
|
||||
return
|
||||
df=DataForm(typ='form')
|
||||
for i in resp.getQueryPayload():
|
||||
if type(i)<>type(iq): pass
|
||||
elif i.getName()=='instructions': df.addInstructions(i.getData())
|
||||
else: df.setField(i.getName()).setValue(i.getData())
|
||||
con.Event(NS_REGISTER,REGISTER_DATA_RECEIVED,(agent,df,False))
|
||||
|
||||
def register(disp,host,info):
|
||||
""" Perform registration on remote server with provided info.
|
||||
disp must be connected dispatcher instance.
|
||||
Returns true or false depending on registration result.
|
||||
If registration fails you can get additional info from the dispatcher's owner
|
||||
attributes lastErrNode, lastErr and lastErrCode.
|
||||
"""
|
||||
iq=Iq('set',NS_REGISTER,to=host)
|
||||
if type(info)<>type({}): info=info.asDict()
|
||||
for i in info.keys(): iq.setTag('query').setTagData(i,info[i])
|
||||
resp=disp.SendAndWaitForResponse(iq)
|
||||
if isResultNode(resp): return 1
|
||||
|
||||
def unregister(disp,host):
|
||||
""" Unregisters with host (permanently removes account).
|
||||
disp must be connected and authorized dispatcher instance.
|
||||
Returns true on success."""
|
||||
resp=disp.SendAndWaitForResponse(Iq('set',NS_REGISTER,to=host,payload=[Node('remove')]))
|
||||
if isResultNode(resp): return 1
|
||||
|
||||
def changePasswordTo(disp,newpassword,host=None):
|
||||
""" Changes password on specified or current (if not specified) server.
|
||||
disp must be connected and authorized dispatcher instance.
|
||||
Returns true on success."""
|
||||
if not host: host=disp._owner.Server
|
||||
resp=disp.SendAndWaitForResponse(Iq('set',NS_REGISTER,to=host,payload=[Node('username',payload=[disp._owner.Server]),Node('password',payload=[newpassword])]))
|
||||
if isResultNode(resp): return 1
|
||||
|
||||
### Privacy ### jabber:iq:privacy ### draft-ietf-xmpp-im-19 ####################
|
||||
#type=[jid|group|subscription]
|
||||
#action=[allow|deny]
|
||||
|
||||
def getPrivacyLists(disp):
|
||||
""" Requests privacy lists from connected server.
|
||||
Returns dictionary of existing lists on success."""
|
||||
try:
|
||||
dict={'lists':[]}
|
||||
resp=disp.SendAndWaitForResponse(Iq('get',NS_PRIVACY))
|
||||
if not isResultNode(resp): return
|
||||
for list in resp.getQueryPayload():
|
||||
if list.getName()=='list': dict['lists'].append(list.getAttr('name'))
|
||||
else: dict[list.getName()]=list.getAttr('name')
|
||||
return dict
|
||||
except: pass
|
||||
|
||||
def getPrivacyList(disp,listname):
|
||||
""" Requests specific privacy list listname. Returns list of XML nodes (rules)
|
||||
taken from the server responce."""
|
||||
try:
|
||||
resp=disp.SendAndWaitForResponse(Iq('get',NS_PRIVACY,payload=[Node('list',{'name':listname})]))
|
||||
if isResultNode(resp): return resp.getQueryPayload()[0]
|
||||
except: pass
|
||||
|
||||
def setActivePrivacyList(disp,listname=None,typ='active'):
|
||||
""" Switches privacy list 'listname' to specified type.
|
||||
By default the type is 'active'. Returns true on success."""
|
||||
if listname: attrs={'name':listname}
|
||||
else: attrs={}
|
||||
resp=disp.SendAndWaitForResponse(Iq('set',NS_PRIVACY,payload=[Node(typ,attrs)]))
|
||||
if isResultNode(resp): return 1
|
||||
|
||||
def setDefaultPrivacyList(disp,listname=None):
|
||||
""" Sets the default privacy list as 'listname'. Returns true on success."""
|
||||
return setActivePrivacyList(disp,listname,'default')
|
||||
|
||||
def setPrivacyList(disp,list):
|
||||
""" Set the ruleset. 'list' should be the simpleXML node formatted
|
||||
according to RFC 3921 (XMPP-IM) (I.e. Node('list',{'name':listname},payload=[...]) )
|
||||
Returns true on success."""
|
||||
resp=disp.SendAndWaitForResponse(Iq('set',NS_PRIVACY,payload=[list]))
|
||||
if isResultNode(resp): return 1
|
||||
|
||||
def delPrivacyList(disp,listname):
|
||||
""" Deletes privacy list 'listname'. Returns true on success."""
|
||||
resp=disp.SendAndWaitForResponse(Iq('set',NS_PRIVACY,payload=[Node('list',{'name':listname})]))
|
||||
if isResultNode(resp): return 1
|
|
@ -15,9 +15,13 @@
|
|||
|
||||
# $Id: features.py,v 1.22 2005/09/30 20:13:04 mikealbon Exp $
|
||||
|
||||
from features import REGISTER_DATA_RECEIVED, PRIVACY_LISTS_RECEIVED, PRIVACY_LIST_RECEIVED, PRIVACY_LISTS_ACTIVE_DEFAULT
|
||||
from protocol import *
|
||||
|
||||
REGISTER_DATA_RECEIVED='REGISTER DATA RECEIVED'
|
||||
PRIVACY_LISTS_RECEIVED='PRIVACY LISTS RECEIVED'
|
||||
PRIVACY_LIST_RECEIVED='PRIVACY LIST RECEIVED'
|
||||
PRIVACY_LISTS_ACTIVE_DEFAULT='PRIVACY LISTS ACTIVE DEFAULT'
|
||||
|
||||
def _on_default_response(disp, iq, cb):
|
||||
def _on_response(resp):
|
||||
if isResultNode(resp):
|
||||
|
|
|
@ -1,199 +0,0 @@
|
|||
## filetransfer.py
|
||||
##
|
||||
## Copyright (C) 2004 Alexey "Snake" Nezhdanov
|
||||
##
|
||||
## This program is free software; you can redistribute it and/or modify
|
||||
## it under the terms of the GNU General Public License as published by
|
||||
## the Free Software Foundation; either version 2, or (at your option)
|
||||
## any later version.
|
||||
##
|
||||
## This program is distributed in the hope that it will be useful,
|
||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
## GNU General Public License for more details.
|
||||
|
||||
# $Id: filetransfer.py,v 1.6 2004/12/25 20:06:59 snakeru Exp $
|
||||
|
||||
"""
|
||||
This module contains IBB class that is the simple implementation of JEP-0047.
|
||||
Note that this is just a transport for data. You have to negotiate data transfer before
|
||||
(via StreamInitiation most probably). Unfortunately SI is not implemented yet.
|
||||
"""
|
||||
|
||||
from protocol import *
|
||||
from dispatcher import PlugIn
|
||||
import base64
|
||||
|
||||
class IBB(PlugIn):
|
||||
""" IBB used to transfer small-sized data chunk over estabilished xmpp connection.
|
||||
Data is split into small blocks (by default 3000 bytes each), encoded as base 64
|
||||
and sent to another entity that compiles these blocks back into the data chunk.
|
||||
This is very inefficiend but should work under any circumstances. Note that
|
||||
using IBB normally should be the last resort.
|
||||
"""
|
||||
def __init__(self):
|
||||
""" Initialise internal variables. """
|
||||
PlugIn.__init__(self)
|
||||
self.DBG_LINE='ibb'
|
||||
self._exported_methods=[self.OpenStream]
|
||||
self._streams={}
|
||||
self._ampnode=Node(NS_AMP+' amp',payload=[Node('rule',{'condition':'deliver-at','value':'stored','action':'error'}),Node('rule',{'condition':'match-resource','value':'exact','action':'error'})])
|
||||
|
||||
def plugin(self,owner):
|
||||
""" Register handlers for receiving incoming datastreams. Used internally. """
|
||||
self._owner.RegisterHandlerOnce('iq',self.StreamOpenReplyHandler) # Move to StreamOpen and specify stanza id
|
||||
self._owner.RegisterHandler('iq',self.IqHandler,ns=NS_IBB)
|
||||
self._owner.RegisterHandler('message',self.ReceiveHandler,ns=NS_IBB)
|
||||
|
||||
def IqHandler(self,conn,stanza):
|
||||
""" Handles streams state change. Used internally. """
|
||||
typ=stanza.getType()
|
||||
self.DEBUG('IqHandler called typ->%s'%typ,'info')
|
||||
if typ=='set' and stanza.getTag('open',namespace=NS_IBB): self.StreamOpenHandler(conn,stanza)
|
||||
elif typ=='set' and stanza.getTag('close',namespace=NS_IBB): self.StreamCloseHandler(conn,stanza)
|
||||
elif typ=='result': self.StreamCommitHandler(conn,stanza)
|
||||
elif typ=='error': self.StreamOpenReplyHandler(conn,stanza)
|
||||
else: conn.send(Error(stanza,ERR_BAD_REQUEST))
|
||||
raise NodeProcessed
|
||||
|
||||
def StreamOpenHandler(self,conn,stanza):
|
||||
""" Handles opening of new incoming stream. Used internally. """
|
||||
"""
|
||||
<iq type='set'
|
||||
from='romeo@montague.net/orchard'
|
||||
to='juliet@capulet.com/balcony'
|
||||
id='inband_1'>
|
||||
<open sid='mySID'
|
||||
block-size='4096'
|
||||
xmlns='http://jabber.org/protocol/ibb'/>
|
||||
</iq>
|
||||
"""
|
||||
err=None
|
||||
sid,blocksize=stanza.getTagAttr('open','sid'),stanza.getTagAttr('open','block-size')
|
||||
self.DEBUG('StreamOpenHandler called sid->%s blocksize->%s'%(sid,blocksize),'info')
|
||||
try: blocksize=int(blocksize)
|
||||
except: err=ERR_BAD_REQUEST
|
||||
if not sid or not blocksize: err=ERR_BAD_REQUEST
|
||||
elif sid in self._streams.keys(): err=ERR_UNEXPECTED_REQUEST
|
||||
if err: rep=Error(stanza,err)
|
||||
else:
|
||||
self.DEBUG("Opening stream: id %s, block-size %s"%(sid,blocksize),'info')
|
||||
rep=Protocol('iq',stanza.getFrom(),'result',stanza.getTo(),{'id':stanza.getID()})
|
||||
self._streams[sid]={'direction':'<'+str(stanza.getFrom()),'block-size':blocksize,'fp':open('/tmp/xmpp_file_'+sid,'w'),'seq':0,'syn_id':stanza.getID()}
|
||||
conn.send(rep)
|
||||
|
||||
def OpenStream(self,sid,to,fp,blocksize=3000):
|
||||
""" Start new stream. You should provide stream id 'sid', the endpoind jid 'to',
|
||||
the file object containing info for send 'fp'. Also the desired blocksize can be specified.
|
||||
Take into account that recommended stanza size is 4k and IBB uses base64 encoding
|
||||
that increases size of data by 1/3."""
|
||||
if sid in self._streams.keys(): return
|
||||
if not JID(to).getResource(): return
|
||||
self._streams[sid]={'direction':'|>'+to,'block-size':blocksize,'fp':fp,'seq':0}
|
||||
self._owner.RegisterCycleHandler(self.SendHandler)
|
||||
syn=Protocol('iq',to,'set',payload=[Node(NS_IBB+' open',{'sid':sid,'block-size':blocksize})])
|
||||
self._owner.send(syn)
|
||||
self._streams[sid]['syn_id']=syn.getID()
|
||||
return self._streams[sid]
|
||||
|
||||
def SendHandler(self,conn):
|
||||
""" Send next portion of data if it is time to do it. Used internally. """
|
||||
self.DEBUG('SendHandler called','info')
|
||||
for sid in self._streams.keys():
|
||||
stream=self._streams[sid]
|
||||
if stream['direction'][:2]=='|>': cont=1
|
||||
elif stream['direction'][0]=='>':
|
||||
chunk=stream['fp'].read(stream['block-size'])
|
||||
if chunk:
|
||||
datanode=Node(NS_IBB+' data',{'sid':sid,'seq':stream['seq']},base64.encodestring(chunk))
|
||||
stream['seq']+=1
|
||||
if stream['seq']==65536: stream['seq']=0
|
||||
conn.send(Protocol('message',stream['direction'][1:],payload=[datanode,self._ampnode]))
|
||||
else:
|
||||
""" notify the other side about stream closing
|
||||
notify the local user about sucessfull send
|
||||
delete the local stream"""
|
||||
conn.send(Protocol('iq',stream['direction'][1:],'set',payload=[Node(NS_IBB+' close',{'sid':sid})]))
|
||||
conn.Event(self.DBG_LINE,'SUCCESSFULL SEND',stream)
|
||||
del self._streams[sid]
|
||||
self._owner.UnregisterCycleHandler(self.SendHandler)
|
||||
|
||||
"""
|
||||
<message from='romeo@montague.net/orchard' to='juliet@capulet.com/balcony' id='msg1'>
|
||||
<data xmlns='http://jabber.org/protocol/ibb' sid='mySID' seq='0'>
|
||||
qANQR1DBwU4DX7jmYZnncmUQB/9KuKBddzQH+tZ1ZywKK0yHKnq57kWq+RFtQdCJ
|
||||
WpdWpR0uQsuJe7+vh3NWn59/gTc5MDlX8dS9p0ovStmNcyLhxVgmqS8ZKhsblVeu
|
||||
IpQ0JgavABqibJolc3BKrVtVV1igKiX/N7Pi8RtY1K18toaMDhdEfhBRzO/XB0+P
|
||||
AQhYlRjNacGcslkhXqNjK5Va4tuOAPy2n1Q8UUrHbUd0g+xJ9Bm0G0LZXyvCWyKH
|
||||
kuNEHFQiLuCY6Iv0myq6iX6tjuHehZlFSh80b5BVV9tNLwNR5Eqz1klxMhoghJOA
|
||||
</data>
|
||||
<amp xmlns='http://jabber.org/protocol/amp'>
|
||||
<rule condition='deliver-at' value='stored' action='error'/>
|
||||
<rule condition='match-resource' value='exact' action='error'/>
|
||||
</amp>
|
||||
</message>
|
||||
"""
|
||||
|
||||
def ReceiveHandler(self,conn,stanza):
|
||||
""" Receive next portion of incoming datastream and store it write
|
||||
it to temporary file. Used internally.
|
||||
"""
|
||||
sid,seq,data=stanza.getTagAttr('data','sid'),stanza.getTagAttr('data','seq'),stanza.getTagData('data')
|
||||
self.DEBUG('ReceiveHandler called sid->%s seq->%s'%(sid,seq),'info')
|
||||
try: seq=int(seq); data=base64.decodestring(data)
|
||||
except: seq=''; data=''
|
||||
err=None
|
||||
if not sid in self._streams.keys(): err=ERR_ITEM_NOT_FOUND
|
||||
else:
|
||||
stream=self._streams[sid]
|
||||
if not data: err=ERR_BAD_REQUEST
|
||||
elif seq<>stream['seq']: err=ERR_UNEXPECTED_REQUEST
|
||||
else:
|
||||
self.DEBUG('Successfull receive sid->%s %s+%s bytes'%(sid,stream['fp'].tell(),len(data)),'ok')
|
||||
stream['seq']+=1
|
||||
stream['fp'].write(data)
|
||||
if err:
|
||||
self.DEBUG('Error on receive: %s'%err,'error')
|
||||
conn.send(Error(Iq(to=stanza.getFrom(),frm=stanza.getTo(),payload=[Node(NS_IBB+' close')]),err,reply=0))
|
||||
|
||||
def StreamCloseHandler(self,conn,stanza):
|
||||
""" Handle stream closure due to all data transmitted.
|
||||
Raise xmpppy event specifying successfull data receive. """
|
||||
sid=stanza.getTagAttr('close','sid')
|
||||
self.DEBUG('StreamCloseHandler called sid->%s'%sid,'info')
|
||||
if sid in self._streams.keys():
|
||||
conn.send(stanza.buildReply('result'))
|
||||
conn.Event(self.DBG_LINE,'SUCCESSFULL RECEIVE',self._streams[sid])
|
||||
del self._streams[sid]
|
||||
else: conn.send(Error(stanza,ERR_ITEM_NOT_FOUND))
|
||||
|
||||
def StreamBrokenHandler(self,conn,stanza):
|
||||
""" Handle stream closure due to all some error while receiving data.
|
||||
Raise xmpppy event specifying unsuccessfull data receive. """
|
||||
syn_id=stanza.getID()
|
||||
self.DEBUG('StreamBrokenHandler called syn_id->%s'%syn_id,'info')
|
||||
for sid in self._streams.keys():
|
||||
stream=self._streams[sid]
|
||||
if stream['syn_id']==syn_id:
|
||||
if stream['direction'][0]=='<': conn.Event(self.DBG_LINE,'ERROR ON RECEIVE',stream)
|
||||
else: conn.Event(self.DBG_LINE,'ERROR ON SEND',stream)
|
||||
del self._streams[sid]
|
||||
|
||||
def StreamOpenReplyHandler(self,conn,stanza):
|
||||
""" Handle remote side reply about is it agree or not to receive our datastream.
|
||||
Used internally. Raises xmpppy event specfiying if the data transfer
|
||||
is agreed upon."""
|
||||
syn_id=stanza.getID()
|
||||
self.DEBUG('StreamOpenReplyHandler called syn_id->%s'%syn_id,'info')
|
||||
for sid in self._streams.keys():
|
||||
stream=self._streams[sid]
|
||||
if stream['syn_id']==syn_id:
|
||||
if stanza.getType()=='error':
|
||||
if stream['direction'][0]=='<': conn.Event(self.DBG_LINE,'ERROR ON RECEIVE',stream)
|
||||
else: conn.Event(self.DBG_LINE,'ERROR ON SEND',stream)
|
||||
del self._streams[sid]
|
||||
elif stanza.getType()=='result':
|
||||
if stream['direction'][0]=='|':
|
||||
stream['direction']=stream['direction'][1:]
|
||||
conn.Event(self.DBG_LINE,'STREAM COMMITTED',stream)
|
||||
else: conn.send(Error(stanza,ERR_UNEXPECTED_REQUEST))
|
|
@ -1,195 +0,0 @@
|
|||
## roster.py
|
||||
##
|
||||
## Copyright (C) 2003-2005 Alexey "Snake" Nezhdanov
|
||||
##
|
||||
## This program is free software; you can redistribute it and/or modify
|
||||
## it under the terms of the GNU General Public License as published by
|
||||
## the Free Software Foundation; either version 2, or (at your option)
|
||||
## any later version.
|
||||
##
|
||||
## This program is distributed in the hope that it will be useful,
|
||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
## GNU General Public License for more details.
|
||||
|
||||
# $Id: roster.py,v 1.17 2005/05/02 08:38:49 snakeru Exp $
|
||||
|
||||
"""
|
||||
Simple roster implementation. Can be used though for different tasks like
|
||||
mass-renaming of contacts.
|
||||
"""
|
||||
|
||||
from protocol import *
|
||||
from client import PlugIn
|
||||
|
||||
class Roster(PlugIn):
|
||||
""" Defines a plenty of methods that will allow you to manage roster.
|
||||
Also automatically track presences from remote JIDs taking into
|
||||
account that every JID can have multiple resources connected. Does not
|
||||
currently support 'error' presences.
|
||||
You can also use mapping interface for access to the internal representation of
|
||||
contacts in roster.
|
||||
"""
|
||||
def __init__(self):
|
||||
""" Init internal variables. """
|
||||
PlugIn.__init__(self)
|
||||
self.DBG_LINE='roster'
|
||||
self._data = {}
|
||||
self.set=None
|
||||
self._exported_methods=[self.getRoster]
|
||||
|
||||
def plugin(self,owner,request=1):
|
||||
""" Register presence and subscription trackers in the owner's dispatcher.
|
||||
Also request roster from server if the 'request' argument is set.
|
||||
Used internally."""
|
||||
self._owner.RegisterHandler('iq',self.RosterIqHandler,'result',NS_ROSTER,makefirst=1)
|
||||
self._owner.RegisterHandler('iq',self.RosterIqHandler,'set',NS_ROSTER)
|
||||
self._owner.RegisterHandler('presence',self.PresenceHandler)
|
||||
if request: self.Request()
|
||||
|
||||
def Request(self,force=0):
|
||||
""" Request roster from server if it were not yet requested
|
||||
(or if the 'force' argument is set). """
|
||||
if self.set is None: self.set=0
|
||||
elif not force: return
|
||||
self._owner.send(Iq('get',NS_ROSTER))
|
||||
self.DEBUG('Roster requested from server','start')
|
||||
|
||||
def getRoster(self):
|
||||
""" Requests roster from server if neccessary and returns self."""
|
||||
if not self.set: self.Request()
|
||||
while not self.set: self._owner.Process(10)
|
||||
return self
|
||||
|
||||
def RosterIqHandler(self,dis,stanza):
|
||||
""" Subscription tracker. Used internally for setting items state in
|
||||
internal roster representation. """
|
||||
sender = stanza.getAttr('from')
|
||||
if not sender is None and not sender.bareMatch(
|
||||
self._owner.User + '@' + self._owner.Server):
|
||||
return
|
||||
for item in stanza.getTag('query').getTags('item'):
|
||||
jid=item.getAttr('jid')
|
||||
if item.getAttr('subscription')=='remove':
|
||||
if self._data.has_key(jid): del self._data[jid]
|
||||
return
|
||||
self.DEBUG('Setting roster item %s...'%jid,'ok')
|
||||
if not self._data.has_key(jid): self._data[jid]={}
|
||||
self._data[jid]['name']=item.getAttr('name')
|
||||
self._data[jid]['ask']=item.getAttr('ask')
|
||||
self._data[jid]['subscription']=item.getAttr('subscription')
|
||||
self._data[jid]['groups']=[]
|
||||
if not self._data[jid].has_key('resources'): self._data[jid]['resources']={}
|
||||
for group in item.getTags('group'): self._data[jid]['groups'].append(group.getData())
|
||||
self._data[self._owner.User+'@'+self._owner.Server]={'resources':{},'name':None,'ask':None,'subscription':None,'groups':None,}
|
||||
self.set=1
|
||||
|
||||
def PresenceHandler(self,dis,pres):
|
||||
""" Presence tracker. Used internally for setting items' resources state in
|
||||
internal roster representation. """
|
||||
jid=pres.getFrom()
|
||||
if not jid:
|
||||
# If no from attribue, it's from server
|
||||
jid=self._owner.Server
|
||||
jid=JID(jid)
|
||||
if not self._data.has_key(jid.getStripped()): self._data[jid.getStripped()]={'name':None,'ask':None,'subscription':'none','groups':['Not in roster'],'resources':{}}
|
||||
if type(self._data[jid.getStripped()]['resources'])!=type(dict()):
|
||||
self._data[jid.getStripped()]['resources']={}
|
||||
item=self._data[jid.getStripped()]
|
||||
typ=pres.getType()
|
||||
|
||||
if not typ:
|
||||
self.DEBUG('Setting roster item %s for resource %s...'%(jid.getStripped(),jid.getResource()),'ok')
|
||||
item['resources'][jid.getResource()]=res={'show':None,'status':None,'priority':'0','timestamp':None}
|
||||
if pres.getTag('show'): res['show']=pres.getShow()
|
||||
if pres.getTag('status'): res['status']=pres.getStatus()
|
||||
if pres.getTag('priority'): res['priority']=pres.getPriority()
|
||||
if not pres.getTimestamp(): pres.setTimestamp()
|
||||
res['timestamp']=pres.getTimestamp()
|
||||
elif typ=='unavailable' and item['resources'].has_key(jid.getResource()): del item['resources'][jid.getResource()]
|
||||
# Need to handle type='error' also
|
||||
|
||||
def _getItemData(self,jid,dataname):
|
||||
""" Return specific jid's representation in internal format. Used internally. """
|
||||
jid=jid[:(jid+'/').find('/')]
|
||||
return self._data[jid][dataname]
|
||||
def _getResourceData(self,jid,dataname):
|
||||
""" Return specific jid's resource representation in internal format. Used internally. """
|
||||
if jid.find('/')+1:
|
||||
jid,resource=jid.split('/',1)
|
||||
if self._data[jid]['resources'].has_key(resource): return self._data[jid]['resources'][resource][dataname]
|
||||
elif self._data[jid]['resources'].keys():
|
||||
lastpri=-129
|
||||
for r in self._data[jid]['resources'].keys():
|
||||
if int(self._data[jid]['resources'][r]['priority'])>lastpri: resource,lastpri=r,int(self._data[jid]['resources'][r]['priority'])
|
||||
return self._data[jid]['resources'][resource][dataname]
|
||||
def delItem(self,jid):
|
||||
""" Delete contact 'jid' from roster."""
|
||||
self._owner.send(Iq('set',NS_ROSTER,payload=[Node('item',{'jid':jid,'subscription':'remove'})]))
|
||||
def getAsk(self,jid):
|
||||
""" Returns 'ask' value of contact 'jid'."""
|
||||
return self._getItemData(jid,'ask')
|
||||
def getGroups(self,jid):
|
||||
""" Returns groups list that contact 'jid' belongs to."""
|
||||
return self._getItemData(jid,'groups')
|
||||
def getName(self,jid):
|
||||
""" Returns name of contact 'jid'."""
|
||||
return self._getItemData(jid,'name')
|
||||
def getPriority(self,jid):
|
||||
""" Returns priority of contact 'jid'. 'jid' should be a full (not bare) JID."""
|
||||
return self._getResourceData(jid,'priority')
|
||||
def getRawRoster(self):
|
||||
""" Returns roster representation in internal format. """
|
||||
return self._data
|
||||
def getRawItem(self,jid):
|
||||
""" Returns roster item 'jid' representation in internal format. """
|
||||
return self._data[jid[:(jid+'/').find('/')]]
|
||||
def getShow(self, jid):
|
||||
""" Returns 'show' value of contact 'jid'. 'jid' should be a full (not bare) JID."""
|
||||
return self._getResourceData(jid,'show')
|
||||
def getStatus(self, jid):
|
||||
""" Returns 'status' value of contact 'jid'. 'jid' should be a full (not bare) JID."""
|
||||
return self._getResourceData(jid,'status')
|
||||
def getSubscription(self,jid):
|
||||
""" Returns 'subscription' value of contact 'jid'."""
|
||||
return self._getItemData(jid,'subscription')
|
||||
def getResources(self,jid):
|
||||
""" Returns list of connected resources of contact 'jid'."""
|
||||
return self._data[jid[:(jid+'/').find('/')]]['resources'].keys()
|
||||
def setItem(self,jid,name=None,groups=[]):
|
||||
""" Renames contact 'jid' and sets the groups list that it now belongs to."""
|
||||
iq=Iq('set',NS_ROSTER)
|
||||
query=iq.getTag('query')
|
||||
attrs={'jid':jid}
|
||||
if name: attrs['name']=name
|
||||
item=query.setTag('item',attrs)
|
||||
for group in groups: item.addChild(node=Node('group',payload=[group]))
|
||||
self._owner.send(iq)
|
||||
def getItems(self):
|
||||
""" Return list of all [bare] JIDs that the roster is currently tracks."""
|
||||
return self._data.keys()
|
||||
def keys(self):
|
||||
""" Same as getItems. Provided for the sake of dictionary interface."""
|
||||
return self._data.keys()
|
||||
def __getitem__(self,item):
|
||||
""" Get the contact in the internal format. Raises KeyError if JID 'item' is not in roster."""
|
||||
return self._data[item]
|
||||
def getItem(self,item):
|
||||
""" Get the contact in the internal format (or None if JID 'item' is not in roster)."""
|
||||
if self._data.has_key(item): return self._data[item]
|
||||
def Subscribe(self,jid):
|
||||
""" Send subscription request to JID 'jid'."""
|
||||
self._owner.send(Presence(jid,'subscribe'))
|
||||
def Unsubscribe(self,jid):
|
||||
""" Ask for removing our subscription for JID 'jid'."""
|
||||
self._owner.send(Presence(jid,'unsubscribe'))
|
||||
def Authorize(self,jid):
|
||||
""" Authorise JID 'jid'. Works only if these JID requested auth previously. """
|
||||
self._owner.send(Presence(jid,'subscribed'))
|
||||
def Unauthorize(self,jid):
|
||||
""" Unauthorise JID 'jid'. Use for declining authorisation request
|
||||
or for removing existing authorization. """
|
||||
self._owner.send(Presence(jid,'unsubscribed'))
|
||||
def getRaw(self):
|
||||
"""Returns the internal data representation of the roster."""
|
||||
return self._data
|
|
@ -1,8 +1,8 @@
|
|||
## roster_nb.py
|
||||
## based on roster.py
|
||||
## based on roster.py
|
||||
##
|
||||
## Copyright (C) 2003-2005 Alexey "Snake" Nezhdanov
|
||||
## modified by Dimitur Kirov <dkirov@gmail.com>
|
||||
## modified by Dimitur Kirov <dkirov@gmail.com>
|
||||
##
|
||||
## This program is free software; you can redistribute it and/or modify
|
||||
## it under the terms of the GNU General Public License as published by
|
||||
|
@ -21,10 +21,168 @@ Simple roster implementation. Can be used though for different tasks like
|
|||
mass-renaming of contacts.
|
||||
'''
|
||||
|
||||
from roster import Roster
|
||||
from protocol import NS_ROSTER
|
||||
from protocol import *
|
||||
from client import PlugIn
|
||||
|
||||
class NonBlockingRoster(PlugIn):
|
||||
""" Defines a plenty of methods that will allow you to manage roster.
|
||||
Also automatically track presences from remote JIDs taking into
|
||||
account that every JID can have multiple resources connected. Does not
|
||||
currently support 'error' presences.
|
||||
You can also use mapping interface for access to the internal representation of
|
||||
contacts in roster.
|
||||
"""
|
||||
def __init__(self):
|
||||
""" Init internal variables. """
|
||||
PlugIn.__init__(self)
|
||||
self.DBG_LINE='roster'
|
||||
self._data = {}
|
||||
self.set=None
|
||||
self._exported_methods=[self.getRoster]
|
||||
|
||||
def Request(self,force=0):
|
||||
""" Request roster from server if it were not yet requested
|
||||
(or if the 'force' argument is set). """
|
||||
if self.set is None: self.set=0
|
||||
elif not force: return
|
||||
self._owner.send(Iq('get',NS_ROSTER))
|
||||
self.DEBUG('Roster requested from server','start')
|
||||
|
||||
def RosterIqHandler(self,dis,stanza):
|
||||
""" Subscription tracker. Used internally for setting items state in
|
||||
internal roster representation. """
|
||||
sender = stanza.getAttr('from')
|
||||
if not sender is None and not sender.bareMatch(
|
||||
self._owner.User + '@' + self._owner.Server):
|
||||
return
|
||||
for item in stanza.getTag('query').getTags('item'):
|
||||
jid=item.getAttr('jid')
|
||||
if item.getAttr('subscription')=='remove':
|
||||
if self._data.has_key(jid): del self._data[jid]
|
||||
return
|
||||
self.DEBUG('Setting roster item %s...'%jid,'ok')
|
||||
if not self._data.has_key(jid): self._data[jid]={}
|
||||
self._data[jid]['name']=item.getAttr('name')
|
||||
self._data[jid]['ask']=item.getAttr('ask')
|
||||
self._data[jid]['subscription']=item.getAttr('subscription')
|
||||
self._data[jid]['groups']=[]
|
||||
if not self._data[jid].has_key('resources'): self._data[jid]['resources']={}
|
||||
for group in item.getTags('group'): self._data[jid]['groups'].append(group.getData())
|
||||
self._data[self._owner.User+'@'+self._owner.Server]={'resources':{},'name':None,'ask':None,'subscription':None,'groups':None,}
|
||||
self.set=1
|
||||
|
||||
def PresenceHandler(self,dis,pres):
|
||||
""" Presence tracker. Used internally for setting items' resources state in
|
||||
internal roster representation. """
|
||||
jid=pres.getFrom()
|
||||
if not jid:
|
||||
# If no from attribue, it's from server
|
||||
jid=self._owner.Server
|
||||
jid=JID(jid)
|
||||
if not self._data.has_key(jid.getStripped()): self._data[jid.getStripped()]={'name':None,'ask':None,'subscription':'none','groups':['Not in roster'],'resources':{}}
|
||||
if type(self._data[jid.getStripped()]['resources'])!=type(dict()):
|
||||
self._data[jid.getStripped()]['resources']={}
|
||||
item=self._data[jid.getStripped()]
|
||||
typ=pres.getType()
|
||||
|
||||
if not typ:
|
||||
self.DEBUG('Setting roster item %s for resource %s...'%(jid.getStripped(),jid.getResource()),'ok')
|
||||
item['resources'][jid.getResource()]=res={'show':None,'status':None,'priority':'0','timestamp':None}
|
||||
if pres.getTag('show'): res['show']=pres.getShow()
|
||||
if pres.getTag('status'): res['status']=pres.getStatus()
|
||||
if pres.getTag('priority'): res['priority']=pres.getPriority()
|
||||
if not pres.getTimestamp(): pres.setTimestamp()
|
||||
res['timestamp']=pres.getTimestamp()
|
||||
elif typ=='unavailable' and item['resources'].has_key(jid.getResource()): del item['resources'][jid.getResource()]
|
||||
# Need to handle type='error' also
|
||||
|
||||
def _getItemData(self,jid,dataname):
|
||||
""" Return specific jid's representation in internal format. Used internally. """
|
||||
jid=jid[:(jid+'/').find('/')]
|
||||
return self._data[jid][dataname]
|
||||
def _getResourceData(self,jid,dataname):
|
||||
""" Return specific jid's resource representation in internal format. Used internally. """
|
||||
if jid.find('/')+1:
|
||||
jid,resource=jid.split('/',1)
|
||||
if self._data[jid]['resources'].has_key(resource): return self._data[jid]['resources'][resource][dataname]
|
||||
elif self._data[jid]['resources'].keys():
|
||||
lastpri=-129
|
||||
for r in self._data[jid]['resources'].keys():
|
||||
if int(self._data[jid]['resources'][r]['priority'])>lastpri: resource,lastpri=r,int(self._data[jid]['resources'][r]['priority'])
|
||||
return self._data[jid]['resources'][resource][dataname]
|
||||
def delItem(self,jid):
|
||||
""" Delete contact 'jid' from roster."""
|
||||
self._owner.send(Iq('set',NS_ROSTER,payload=[Node('item',{'jid':jid,'subscription':'remove'})]))
|
||||
def getAsk(self,jid):
|
||||
""" Returns 'ask' value of contact 'jid'."""
|
||||
return self._getItemData(jid,'ask')
|
||||
def getGroups(self,jid):
|
||||
""" Returns groups list that contact 'jid' belongs to."""
|
||||
return self._getItemData(jid,'groups')
|
||||
def getName(self,jid):
|
||||
""" Returns name of contact 'jid'."""
|
||||
return self._getItemData(jid,'name')
|
||||
def getPriority(self,jid):
|
||||
""" Returns priority of contact 'jid'. 'jid' should be a full (not bare) JID."""
|
||||
return self._getResourceData(jid,'priority')
|
||||
def getRawRoster(self):
|
||||
""" Returns roster representation in internal format. """
|
||||
return self._data
|
||||
def getRawItem(self,jid):
|
||||
""" Returns roster item 'jid' representation in internal format. """
|
||||
return self._data[jid[:(jid+'/').find('/')]]
|
||||
def getShow(self, jid):
|
||||
""" Returns 'show' value of contact 'jid'. 'jid' should be a full (not bare) JID."""
|
||||
return self._getResourceData(jid,'show')
|
||||
def getStatus(self, jid):
|
||||
""" Returns 'status' value of contact 'jid'. 'jid' should be a full (not bare) JID."""
|
||||
return self._getResourceData(jid,'status')
|
||||
def getSubscription(self,jid):
|
||||
""" Returns 'subscription' value of contact 'jid'."""
|
||||
return self._getItemData(jid,'subscription')
|
||||
def getResources(self,jid):
|
||||
""" Returns list of connected resources of contact 'jid'."""
|
||||
return self._data[jid[:(jid+'/').find('/')]]['resources'].keys()
|
||||
def setItem(self,jid,name=None,groups=[]):
|
||||
""" Renames contact 'jid' and sets the groups list that it now belongs to."""
|
||||
iq=Iq('set',NS_ROSTER)
|
||||
query=iq.getTag('query')
|
||||
attrs={'jid':jid}
|
||||
if name: attrs['name']=name
|
||||
item=query.setTag('item',attrs)
|
||||
for group in groups: item.addChild(node=Node('group',payload=[group]))
|
||||
self._owner.send(iq)
|
||||
def getItems(self):
|
||||
""" Return list of all [bare] JIDs that the roster is currently tracks."""
|
||||
return self._data.keys()
|
||||
def keys(self):
|
||||
""" Same as getItems. Provided for the sake of dictionary interface."""
|
||||
return self._data.keys()
|
||||
def __getitem__(self,item):
|
||||
""" Get the contact in the internal format. Raises KeyError if JID 'item' is not in roster."""
|
||||
return self._data[item]
|
||||
def getItem(self,item):
|
||||
""" Get the contact in the internal format (or None if JID 'item' is not in roster)."""
|
||||
if self._data.has_key(item): return self._data[item]
|
||||
def Subscribe(self,jid):
|
||||
""" Send subscription request to JID 'jid'."""
|
||||
self._owner.send(Presence(jid,'subscribe'))
|
||||
def Unsubscribe(self,jid):
|
||||
""" Ask for removing our subscription for JID 'jid'."""
|
||||
self._owner.send(Presence(jid,'unsubscribe'))
|
||||
def Authorize(self,jid):
|
||||
""" Authorise JID 'jid'. Works only if these JID requested auth previously. """
|
||||
self._owner.send(Presence(jid,'subscribed'))
|
||||
def Unauthorize(self,jid):
|
||||
""" Unauthorise JID 'jid'. Use for declining authorisation request
|
||||
or for removing existing authorization. """
|
||||
self._owner.send(Presence(jid,'unsubscribed'))
|
||||
def getRaw(self):
|
||||
"""Returns the internal data representation of the roster."""
|
||||
return self._data
|
||||
# copypasted methods for roster.py from constructor to here
|
||||
|
||||
|
||||
class NonBlockingRoster(Roster):
|
||||
def plugin(self, owner, request=1):
|
||||
''' Register presence and subscription trackers in the owner's dispatcher.
|
||||
Also request roster from server if the 'request' argument is set.
|
||||
|
|
|
@ -1,350 +0,0 @@
|
|||
##
|
||||
## XMPP server
|
||||
##
|
||||
## Copyright (C) 2004 Alexey "Snake" Nezhdanov
|
||||
##
|
||||
## This program is free software; you can redistribute it and/or modify
|
||||
## it under the terms of the GNU General Public License as published by
|
||||
## the Free Software Foundation; either version 2, or (at your option)
|
||||
## any later version.
|
||||
##
|
||||
## This program is distributed in the hope that it will be useful,
|
||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
## GNU General Public License for more details.
|
||||
|
||||
__version__="$Id"
|
||||
|
||||
"""
|
||||
When your handler is called it is getting the session instance as the first argument.
|
||||
This is the difference from xmpppy 0.1 where you got the "Client" instance.
|
||||
With Session class you can have "multi-session" client instead of having
|
||||
one client for each connection. Is is specifically important when you are
|
||||
writing the server.
|
||||
"""
|
||||
|
||||
from protocol import *
|
||||
|
||||
# Transport-level flags
|
||||
SOCKET_UNCONNECTED =0
|
||||
SOCKET_ALIVE =1
|
||||
SOCKET_DEAD =2
|
||||
# XML-level flags
|
||||
STREAM__NOT_OPENED =1
|
||||
STREAM__OPENED =2
|
||||
STREAM__CLOSING =3
|
||||
STREAM__CLOSED =4
|
||||
# XMPP-session flags
|
||||
SESSION_NOT_AUTHED =1
|
||||
SESSION_AUTHED =2
|
||||
SESSION_BOUND =3
|
||||
SESSION_OPENED =4
|
||||
SESSION_CLOSED =5
|
||||
|
||||
class Session:
|
||||
"""
|
||||
The Session class instance is used for storing all session-related info like
|
||||
credentials, socket/xml stream/session state flags, roster items (in case of
|
||||
client type connection) etc.
|
||||
Session object have no means of discovering is any info is ready to be read.
|
||||
Instead you should use poll() (recomended) or select() methods for this purpose.
|
||||
Session can be one of two types: 'server' and 'client'. 'server' session handles
|
||||
inbound connection and 'client' one used to create an outbound one.
|
||||
Session instance have multitude of internal attributes. The most imporant is the 'peer' one.
|
||||
It is set once the peer is authenticated (client).
|
||||
"""
|
||||
def __init__(self,socket,owner,xmlns=None,peer=None):
|
||||
""" When the session is created it's type (client/server) is determined from the beginning.
|
||||
socket argument is the pre-created socket-like object.
|
||||
It must have the following methods: send, recv, fileno, close.
|
||||
owner is the 'master' instance that have Dispatcher plugged into it and generally
|
||||
will take care about all session events.
|
||||
xmlns is the stream namespace that will be used. Client must set this argument
|
||||
If server sets this argument than stream will be dropped if opened with some another namespace.
|
||||
peer is the name of peer instance. This is the flag that differentiates client session from
|
||||
server session. Client must set it to the name of the server that will be connected, server must
|
||||
leave this argument alone.
|
||||
"""
|
||||
self.xmlns=xmlns
|
||||
if peer:
|
||||
self.TYP='client'
|
||||
self.peer=peer
|
||||
self._socket_state=SOCKET_UNCONNECTED
|
||||
else:
|
||||
self.TYP='server'
|
||||
self.peer=None
|
||||
self._socket_state=SOCKET_ALIVE
|
||||
self._sock=socket
|
||||
self._send=socket.send
|
||||
self._recv=socket.recv
|
||||
self.fileno=socket.fileno
|
||||
self._registered=0
|
||||
|
||||
self.Dispatcher=owner.Dispatcher
|
||||
self.DBG_LINE='session'
|
||||
self.DEBUG=owner.Dispatcher.DEBUG
|
||||
self._expected={}
|
||||
self._owner=owner
|
||||
if self.TYP=='server': self.ID=`random.random()`[2:]
|
||||
else: self.ID=None
|
||||
|
||||
self.sendbuffer=''
|
||||
self._stream_pos_queued=None
|
||||
self._stream_pos_sent=0
|
||||
self.deliver_key_queue=[]
|
||||
self.deliver_queue_map={}
|
||||
self.stanza_queue=[]
|
||||
|
||||
self._session_state=SESSION_NOT_AUTHED
|
||||
self.waiting_features=[]
|
||||
for feature in [NS_TLS,NS_SASL,NS_BIND,NS_SESSION]:
|
||||
if feature in owner.features: self.waiting_features.append(feature)
|
||||
self.features=[]
|
||||
self.feature_in_process=None
|
||||
self.slave_session=None
|
||||
self.StartStream()
|
||||
|
||||
def StartStream(self):
|
||||
""" This method is used to initialise the internal xml expat parser
|
||||
and to send initial stream header (in case of client connection).
|
||||
Should be used after initial connection and after every stream restart."""
|
||||
self._stream_state=STREAM__NOT_OPENED
|
||||
self.Stream=simplexml.NodeBuilder()
|
||||
self.Stream._dispatch_depth=2
|
||||
self.Stream.dispatch=self._dispatch
|
||||
self.Parse=self.Stream.Parse
|
||||
self.Stream.stream_footer_received=self._stream_close
|
||||
if self.TYP=='client':
|
||||
self.Stream.stream_header_received=self._catch_stream_id
|
||||
self._stream_open()
|
||||
else:
|
||||
self.Stream.stream_header_received=self._stream_open
|
||||
|
||||
def receive(self):
|
||||
""" Reads all pending incoming data.
|
||||
Raises IOError on disconnection.
|
||||
Blocks until at least one byte is read."""
|
||||
try: received = self._recv(10240)
|
||||
except: received = ''
|
||||
|
||||
if len(received): # length of 0 means disconnect
|
||||
self.DEBUG(`self.fileno()`+' '+received,'got')
|
||||
else:
|
||||
self.DEBUG('Socket error while receiving data','error')
|
||||
self.set_socket_state(SOCKET_DEAD)
|
||||
raise IOError("Peer disconnected")
|
||||
return received
|
||||
|
||||
def sendnow(self,chunk):
|
||||
""" Put chunk into "immidiatedly send" queue.
|
||||
Should only be used for auth/TLS stuff and like.
|
||||
If you just want to shedule regular stanza for delivery use enqueue method.
|
||||
"""
|
||||
if isinstance(chunk,Node): chunk = chunk.__str__().encode('utf-8')
|
||||
elif type(chunk)==type(u''): chunk = chunk.encode('utf-8')
|
||||
self.enqueue(chunk)
|
||||
|
||||
def enqueue(self,stanza):
|
||||
""" Takes Protocol instance as argument.
|
||||
Puts stanza into "send" fifo queue. Items into the send queue are hold until
|
||||
stream authenticated. After that this method is effectively the same as "sendnow" method."""
|
||||
if isinstance(stanza,Protocol):
|
||||
self.stanza_queue.append(stanza)
|
||||
else: self.sendbuffer+=stanza
|
||||
if self._socket_state>=SOCKET_ALIVE: self.push_queue()
|
||||
|
||||
def push_queue(self,failreason=ERR_RECIPIENT_UNAVAILABLE):
|
||||
""" If stream is authenticated than move items from "send" queue to "immidiatedly send" queue.
|
||||
Else if the stream is failed then return all queued stanzas with error passed as argument.
|
||||
Otherwise do nothing."""
|
||||
# If the stream authed - convert stanza_queue into sendbuffer and set the checkpoints
|
||||
|
||||
if self._stream_state>=STREAM__CLOSED or self._socket_state>=SOCKET_DEAD: # the stream failed. Return all stanzas that are still waiting for delivery.
|
||||
self._owner.deactivatesession(self)
|
||||
for key in self.deliver_key_queue: # Not sure. May be I
|
||||
self._dispatch(Error(self.deliver_queue_map[key],failreason),trusted=1) # should simply re-dispatch it?
|
||||
for stanza in self.stanza_queue: # But such action can invoke
|
||||
self._dispatch(Error(stanza,failreason),trusted=1) # Infinite loops in case of S2S connection...
|
||||
self.deliver_queue_map,self.deliver_key_queue,self.stanza_queue={},[],[]
|
||||
return
|
||||
elif self._session_state>=SESSION_AUTHED: # FIXME! äÏÌÖÅÎ ÂÙÔØ ËÁËÏÊ-ÔÏ ÄÒÕÇÏÊ ÆÌÁÇ.
|
||||
#### LOCK_QUEUE
|
||||
for stanza in self.stanza_queue:
|
||||
txt=stanza.__str__().encode('utf-8')
|
||||
self.sendbuffer+=txt
|
||||
self._stream_pos_queued+=len(txt) # should be re-evaluated for SSL connection.
|
||||
self.deliver_queue_map[self._stream_pos_queued]=stanza # position of the stream when stanza will be successfully and fully sent
|
||||
self.deliver_key_queue.append(self._stream_pos_queued)
|
||||
self.stanza_queue=[]
|
||||
#### UNLOCK_QUEUE
|
||||
|
||||
def flush_queue(self):
|
||||
""" Put the "immidiatedly send" queue content on the wire. Blocks until at least one byte sent."""
|
||||
if self.sendbuffer:
|
||||
try:
|
||||
# LOCK_QUEUE
|
||||
sent=self._send(self.sendbuffer) # blocking socket
|
||||
except:
|
||||
# UNLOCK_QUEUE
|
||||
self.set_socket_state(SOCKET_DEAD)
|
||||
self.DEBUG("Socket error while sending data",'error')
|
||||
return self.terminate_stream()
|
||||
self.DEBUG(`self.fileno()`+' '+self.sendbuffer[:sent],'sent')
|
||||
self._stream_pos_sent+=sent
|
||||
self.sendbuffer=self.sendbuffer[sent:]
|
||||
self._stream_pos_delivered=self._stream_pos_sent # Should be acquired from socket somehow. Take SSL into account.
|
||||
while self.deliver_key_queue and self._stream_pos_delivered>self.deliver_key_queue[0]:
|
||||
del self.deliver_queue_map[self.deliver_key_queue[0]]
|
||||
self.deliver_key_queue.remove(self.deliver_key_queue[0])
|
||||
# UNLOCK_QUEUE
|
||||
|
||||
def _dispatch(self,stanza,trusted=0):
|
||||
""" This is callback that is used to pass the received stanza forth to owner's dispatcher
|
||||
_if_ the stream is authorised. Otherwise the stanza is just dropped.
|
||||
The 'trusted' argument is used to emulate stanza receive.
|
||||
This method is used internally.
|
||||
"""
|
||||
self._owner.packets+=1
|
||||
print self._owner.packets
|
||||
if self._stream_state==STREAM__OPENED or trusted: # if the server really should reject all stanzas after he is closed stream (himeself)?
|
||||
self.DEBUG(stanza.__str__(),'dispatch')
|
||||
stanza.trusted=trusted
|
||||
return self.Dispatcher.dispatch(stanza,self)
|
||||
|
||||
def _catch_stream_id(self,ns=None,tag='stream',attrs={}):
|
||||
""" This callback is used to detect the stream namespace of incoming stream. Used internally. """
|
||||
if not attrs.has_key('id') or not attrs['id']:
|
||||
return self.terminate_stream(STREAM_INVALID_XML)
|
||||
self.ID=attrs['id']
|
||||
if not attrs.has_key('version'): self._owner.Dialback(self)
|
||||
|
||||
def _stream_open(self,ns=None,tag='stream',attrs={}):
|
||||
""" This callback is used to handle opening stream tag of the incoming stream.
|
||||
In the case of client session it just make some validation.
|
||||
Server session also sends server headers and if the stream valid the features node.
|
||||
Used internally. """
|
||||
text='<?xml version="1.0" encoding="utf-8"?>\n<stream:stream'
|
||||
if self.TYP=='client':
|
||||
text+=' to="%s"'%self.peer
|
||||
else:
|
||||
text+=' id="%s"'%self.ID
|
||||
if not attrs.has_key('to'): text+=' from="%s"'%self._owner.servernames[0]
|
||||
else: text+=' from="%s"'%attrs['to']
|
||||
if attrs.has_key('xml:lang'): text+=' xml:lang="%s"'%attrs['xml:lang']
|
||||
if self.xmlns: xmlns=self.xmlns
|
||||
else: xmlns=NS_SERVER
|
||||
text+=' xmlns:db="%s" xmlns:stream="%s" xmlns="%s"'%(NS_DIALBACK,NS_STREAMS,xmlns)
|
||||
if attrs.has_key('version') or self.TYP=='client': text+=' version="1.0"'
|
||||
self.sendnow(text+'>')
|
||||
self.set_stream_state(STREAM__OPENED)
|
||||
if self.TYP=='client': return
|
||||
if tag<>'stream': return self.terminate_stream(STREAM_INVALID_XML)
|
||||
if ns<>NS_STREAMS: return self.terminate_stream(STREAM_INVALID_NAMESPACE)
|
||||
if self.Stream.xmlns<>self.xmlns: return self.terminate_stream(STREAM_BAD_NAMESPACE_PREFIX)
|
||||
if not attrs.has_key('to'): return self.terminate_stream(STREAM_IMPROPER_ADDRESSING)
|
||||
if attrs['to'] not in self._owner.servernames: return self.terminate_stream(STREAM_HOST_UNKNOWN)
|
||||
self.ourname=attrs['to'].lower()
|
||||
if self.TYP=='server' and attrs.has_key('version'):
|
||||
# send features
|
||||
features=Node('stream:features')
|
||||
if NS_TLS in self.waiting_features:
|
||||
features.T.starttls.setNamespace(NS_TLS)
|
||||
features.T.starttls.T.required
|
||||
if NS_SASL in self.waiting_features:
|
||||
features.T.mechanisms.setNamespace(NS_SASL)
|
||||
for mec in self._owner.SASL.mechanisms:
|
||||
features.T.mechanisms.NT.mechanism=mec
|
||||
else:
|
||||
if NS_BIND in self.waiting_features: features.T.bind.setNamespace(NS_BIND)
|
||||
if NS_SESSION in self.waiting_features: features.T.session.setNamespace(NS_SESSION)
|
||||
self.sendnow(features)
|
||||
|
||||
def feature(self,feature):
|
||||
""" Declare some stream feature as activated one. """
|
||||
if feature not in self.features: self.features.append(feature)
|
||||
self.unfeature(feature)
|
||||
|
||||
def unfeature(self,feature):
|
||||
""" Declare some feature as illegal. Illegal features can not be used.
|
||||
Example: BIND feature becomes illegal after Non-SASL auth. """
|
||||
if feature in self.waiting_features: self.waiting_features.remove(feature)
|
||||
|
||||
def _stream_close(self,unregister=1):
|
||||
""" Write the closing stream tag and destroy the underlaying socket. Used internally. """
|
||||
if self._stream_state>=STREAM__CLOSED: return
|
||||
self.set_stream_state(STREAM__CLOSING)
|
||||
self.sendnow('</stream:stream>')
|
||||
self.set_stream_state(STREAM__CLOSED)
|
||||
self.push_queue() # decompose queue really since STREAM__CLOSED
|
||||
self._owner.flush_queues()
|
||||
if unregister: self._owner.unregistersession(self)
|
||||
self._destroy_socket()
|
||||
|
||||
def terminate_stream(self,error=None,unregister=1):
|
||||
""" Notify the peer about stream closure.
|
||||
Ensure that xmlstream is not brokes - i.e. if the stream isn't opened yet -
|
||||
open it before closure.
|
||||
If the error condition is specified than create a stream error and send it along with
|
||||
closing stream tag.
|
||||
Emulate receiving 'unavailable' type presence just before stream closure.
|
||||
"""
|
||||
if self._stream_state>=STREAM__CLOSING: return
|
||||
if self._stream_state<STREAM__OPENED:
|
||||
self.set_stream_state(STREAM__CLOSING)
|
||||
self._stream_open()
|
||||
else:
|
||||
self.set_stream_state(STREAM__CLOSING)
|
||||
p=Presence(typ='unavailable')
|
||||
p.setNamespace(NS_CLIENT)
|
||||
self._dispatch(p,trusted=1)
|
||||
if error:
|
||||
if isinstance(error,Node): self.sendnow(error)
|
||||
else: self.sendnow(ErrorNode(error))
|
||||
self._stream_close(unregister=unregister)
|
||||
if self.slave_session:
|
||||
self.slave_session.terminate_stream(STREAM_REMOTE_CONNECTION_FAILED)
|
||||
|
||||
def _destroy_socket(self):
|
||||
""" Break cyclic dependancies to let python's GC free memory right now."""
|
||||
self.Stream.dispatch=None
|
||||
self.Stream.stream_footer_received=None
|
||||
self.Stream.stream_header_received=None
|
||||
self.Stream.destroy()
|
||||
self._sock.close()
|
||||
self.set_socket_state(SOCKET_DEAD)
|
||||
|
||||
def start_feature(self,f):
|
||||
""" Declare some feature as "negotiating now" to prevent other features from start negotiating. """
|
||||
if self.feature_in_process: raise "Starting feature %s over %s !"%(f,self.feature_in_process)
|
||||
self.feature_in_process=f
|
||||
|
||||
def stop_feature(self,f):
|
||||
""" Declare some feature as "negotiated" to allow other features start negotiating. """
|
||||
if self.feature_in_process<>f: raise "Stopping feature %s instead of %s !"%(f,self.feature_in_process)
|
||||
self.feature_in_process=None
|
||||
|
||||
def set_socket_state(self,newstate):
|
||||
""" Change the underlaying socket state.
|
||||
Socket starts with SOCKET_UNCONNECTED state
|
||||
and then proceeds (possibly) to SOCKET_ALIVE
|
||||
and then to SOCKET_DEAD """
|
||||
if self._socket_state<newstate: self._socket_state=newstate
|
||||
|
||||
def set_session_state(self,newstate):
|
||||
""" Change the session state.
|
||||
Session starts with SESSION_NOT_AUTHED state
|
||||
and then comes through
|
||||
SESSION_AUTHED, SESSION_BOUND, SESSION_OPENED and SESSION_CLOSED states.
|
||||
"""
|
||||
if self._session_state<newstate:
|
||||
if self._session_state<SESSION_AUTHED and \
|
||||
newstate>=SESSION_AUTHED: self._stream_pos_queued=self._stream_pos_sent
|
||||
self._session_state=newstate
|
||||
|
||||
def set_stream_state(self,newstate):
|
||||
""" Change the underlaying XML stream state
|
||||
Stream starts with STREAM__NOT_OPENED and then proceeds with
|
||||
STREAM__OPENED, STREAM__CLOSING and STREAM__CLOSED states.
|
||||
Note that some features (like TLS and SASL)
|
||||
requires stream re-start so this state can have non-linear changes. """
|
||||
if self._stream_state<newstate: self._stream_state=newstate
|
|
@ -1,289 +0,0 @@
|
|||
## transports.py
|
||||
##
|
||||
## Copyright (C) 2003-2004 Alexey "Snake" Nezhdanov
|
||||
##
|
||||
## This program is free software; you can redistribute it and/or modify
|
||||
## it under the terms of the GNU General Public License as published by
|
||||
## the Free Software Foundation; either version 2, or (at your option)
|
||||
## any later version.
|
||||
##
|
||||
## This program is distributed in the hope that it will be useful,
|
||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
## GNU General Public License for more details.
|
||||
|
||||
# $Id: transports.py,v 1.20 2005/05/12 07:35:55 snakeru Exp $
|
||||
|
||||
"""
|
||||
This module contains the low-level implementations of xmpppy connect methods or
|
||||
(in other words) transports for xmpp-stanzas.
|
||||
Currently here is three transports:
|
||||
direct TCP connect - TCPsocket class
|
||||
proxied TCP connect - HTTPPROXYsocket class (CONNECT proxies)
|
||||
TLS connection - TLS class. Can be used for SSL connections also.
|
||||
|
||||
Transports are stackable so you - f.e. TLS use HTPPROXYsocket or TCPsocket as more low-level transport.
|
||||
|
||||
Also exception 'error' is defined to allow capture of this module specific exceptions.
|
||||
"""
|
||||
|
||||
import socket,select,base64,dispatcher
|
||||
from simplexml import ustr
|
||||
from client import PlugIn
|
||||
from protocol import *
|
||||
import sys
|
||||
import os
|
||||
import errno
|
||||
|
||||
DATA_RECEIVED='DATA RECEIVED'
|
||||
DATA_SENT='DATA SENT'
|
||||
|
||||
def temp_failure_retry(func, *args, **kwargs):
|
||||
while True:
|
||||
try:
|
||||
return func(*args, **kwargs)
|
||||
except (os.error, IOError, select.error), ex:
|
||||
if hasattr(ex, 'errno'):
|
||||
errnum = ex.errno
|
||||
elif hasattr(ex, 'args') and ex.args is not None and len(ex.args) > 0:
|
||||
errnum = ex.args[0]
|
||||
else:
|
||||
errnum = -1
|
||||
if errnum == errno.EINTR:
|
||||
continue
|
||||
else:
|
||||
raise
|
||||
|
||||
class error:
|
||||
"""An exception to be raised in case of low-level errors in methods of 'transports' module."""
|
||||
def __init__(self,comment):
|
||||
"""Cache the descriptive string"""
|
||||
self._comment=comment
|
||||
|
||||
def __str__(self):
|
||||
"""Serialise exception into pre-cached descriptive string."""
|
||||
return self._comment
|
||||
|
||||
class TCPsocket(PlugIn):
|
||||
""" This class defines direct TCP connection method. """
|
||||
def __init__(self, server=None, use_srv=True):
|
||||
""" Cache connection point 'server'. 'server' is the tuple of (host, port)
|
||||
absolutely the same as standard tcp socket uses. """
|
||||
PlugIn.__init__(self)
|
||||
self.DBG_LINE='socket'
|
||||
self._exported_methods=[self.send,self.disconnect]
|
||||
|
||||
self._server = server
|
||||
|
||||
def plugin(self, owner):
|
||||
""" Fire up connection. Return non-empty string on success.
|
||||
Also registers self.disconnected method in the owner's dispatcher.
|
||||
Called internally. """
|
||||
if not self._server: self._server=(self._owner.Server,5222)
|
||||
if not self.connect(self._server): return
|
||||
self._owner.Connection=self
|
||||
self._owner.RegisterDisconnectHandler(self.disconnected)
|
||||
return 'ok'
|
||||
|
||||
def getHost(self):
|
||||
""" Return the 'host' value that is connection is [will be] made to."""
|
||||
return self._server[0]
|
||||
def getPort(self):
|
||||
""" Return the 'port' value that is connection is [will be] made to."""
|
||||
return self._server[1]
|
||||
|
||||
def connect(self,server=None):
|
||||
""" Try to connect. Returns non-empty string on success. """
|
||||
try:
|
||||
if not server:
|
||||
server=self._server
|
||||
for ai in socket.getaddrinfo(server[0],server[1],socket.AF_UNSPEC,socket.SOCK_STREAM):
|
||||
try:
|
||||
self._sock=socket.socket(*ai[:3])
|
||||
self._sock.connect(ai[4])
|
||||
self._send=self._sock.sendall
|
||||
self._recv=self._sock.recv
|
||||
self.DEBUG("Successfully connected to remote host %s"%`server`,'start')
|
||||
return 'ok'
|
||||
except: continue
|
||||
except: pass
|
||||
|
||||
def plugout(self):
|
||||
""" Disconnect from the remote server and unregister self.disconnected method from
|
||||
the owner's dispatcher. """
|
||||
self._owner.DeregisterDisconnectHandler(self.disconnected)
|
||||
self.shutdown()
|
||||
del self._owner.Connection
|
||||
|
||||
def receive(self):
|
||||
""" Reads all pending incoming data. Calls owner's disconnected() method if appropriate."""
|
||||
try: received = self._recv(1024000)
|
||||
except: received = ''
|
||||
|
||||
while temp_failure_retry(select.select,[self._sock],[],[],0)[0]:
|
||||
try: add = self._recv(1024000)
|
||||
except: add=''
|
||||
received +=add
|
||||
if not add: break
|
||||
|
||||
if len(received): # length of 0 means disconnect
|
||||
self.DEBUG(received,'got')
|
||||
if hasattr(self._owner, 'Dispatcher'):
|
||||
self._owner.Dispatcher.Event('', DATA_RECEIVED, received)
|
||||
else:
|
||||
self.DEBUG('Socket error while receiving data','error')
|
||||
self._owner.disconnected()
|
||||
return received
|
||||
|
||||
def send(self,raw_data):
|
||||
""" Writes raw outgoing data. Blocks until done.
|
||||
If supplied data is unicode string, encodes it to utf-8 before send."""
|
||||
if type(raw_data)==type(u''): raw_data = raw_data.encode('utf-8')
|
||||
elif type(raw_data)<>type(''): raw_data = ustr(raw_data).encode('utf-8')
|
||||
try:
|
||||
self._send(raw_data)
|
||||
# Avoid printing messages that are empty keepalive packets.
|
||||
if raw_data.strip():
|
||||
self.DEBUG(raw_data,'sent')
|
||||
self._owner.Dispatcher.Event('', DATA_SENT, raw_data)
|
||||
except:
|
||||
self.DEBUG("Socket error while sending data",'error')
|
||||
self._owner.disconnected()
|
||||
|
||||
def pending_data(self,timeout=0):
|
||||
""" Returns true if there is a data ready to be read. """
|
||||
return temp_failure_retry(select.select,[self._sock],[],[],timeout)[0]
|
||||
|
||||
def disconnect(self):
|
||||
""" Closes the socket. """
|
||||
self.DEBUG("Closing socket",'stop')
|
||||
self._sock.close()
|
||||
|
||||
def disconnected(self):
|
||||
""" Called when a Network Error or disconnection occurs.
|
||||
Designed to be overidden. """
|
||||
self.DEBUG("Socket operation failed",'error')
|
||||
|
||||
DBG_CONNECT_PROXY='CONNECTproxy'
|
||||
class HTTPPROXYsocket(TCPsocket):
|
||||
""" HTTP (CONNECT) proxy connection class. Uses TCPsocket as the base class
|
||||
redefines only connect method. Allows to use HTTP proxies like squid with
|
||||
(optionally) simple authentication (using login and password). """
|
||||
def __init__(self,proxy,server,use_srv=True):
|
||||
""" Caches proxy and target addresses.
|
||||
'proxy' argument is a dictionary with mandatory keys 'host' and 'port' (proxy address)
|
||||
and optional keys 'user' and 'password' to use for authentication.
|
||||
'server' argument is a tuple of host and port - just like TCPsocket uses. """
|
||||
TCPsocket.__init__(self,server,use_srv)
|
||||
self.DBG_LINE=DBG_CONNECT_PROXY
|
||||
self._proxy=proxy
|
||||
|
||||
def plugin(self, owner):
|
||||
""" Starts connection. Used interally. Returns non-empty string on success."""
|
||||
owner.debug_flags.append(DBG_CONNECT_PROXY)
|
||||
return TCPsocket.plugin(self,owner)
|
||||
|
||||
def connect(self,dupe=None):
|
||||
""" Starts connection. Connects to proxy, supplies login and password to it
|
||||
(if were specified while creating instance). Instructs proxy to make
|
||||
connection to the target server. Returns non-empty sting on success. """
|
||||
if not TCPsocket.connect(self,(self._proxy['host'],self._proxy['port'])): return
|
||||
self.DEBUG("Proxy server contacted, performing authentification",'start')
|
||||
connector = ['CONNECT %s:%s HTTP/1.0'%self._server,
|
||||
'Proxy-Connection: Keep-Alive',
|
||||
'Pragma: no-cache',
|
||||
'Host: %s:%s'%self._server,
|
||||
'User-Agent: HTTPPROXYsocket/v0.1']
|
||||
if self._proxy.has_key('user') and self._proxy.has_key('password'):
|
||||
credentials = '%s:%s'%(self._proxy['user'],self._proxy['password'])
|
||||
credentials = base64.encodestring(credentials).strip()
|
||||
connector.append('Proxy-Authorization: Basic '+credentials)
|
||||
connector.append('\r\n')
|
||||
self.send('\r\n'.join(connector))
|
||||
try: reply = self.receive().replace('\r','')
|
||||
except IOError:
|
||||
self.DEBUG('Proxy suddenly disconnected','error')
|
||||
self._owner.disconnected()
|
||||
return
|
||||
try: proto,code,desc=reply.split('\n')[0].split(' ',2)
|
||||
except: raise error('Invalid proxy reply')
|
||||
if code<>'200':
|
||||
self.DEBUG('Invalid proxy reply: %s %s %s'%(proto,code,desc),'error')
|
||||
self._owner.disconnected()
|
||||
return
|
||||
while reply.find('\n\n') == -1:
|
||||
try: reply += self.receive().replace('\r','')
|
||||
except IOError:
|
||||
self.DEBUG('Proxy suddenly disconnected','error')
|
||||
self._owner.disconnected()
|
||||
return
|
||||
self.DEBUG("Authentification successfull. Jabber server contacted.",'ok')
|
||||
return 'ok'
|
||||
|
||||
def DEBUG(self,text,severity):
|
||||
"""Overwrites DEBUG tag to allow debug output be presented as "CONNECTproxy"."""
|
||||
return self._owner.DEBUG(DBG_CONNECT_PROXY,text,severity)
|
||||
|
||||
class TLS(PlugIn):
|
||||
""" TLS connection used to encrypts already estabilished tcp connection."""
|
||||
def PlugIn(self,owner,now=0):
|
||||
""" If the 'now' argument is true then starts using encryption immidiatedly.
|
||||
If 'now' in false then starts encryption as soon as TLS feature is
|
||||
declared by the server (if it were already declared - it is ok).
|
||||
"""
|
||||
if owner.__dict__.has_key('TLS'): return # Already enabled.
|
||||
PlugIn.PlugIn(self,owner)
|
||||
DBG_LINE='TLS'
|
||||
if now: return self._startSSL()
|
||||
if self._owner.Dispatcher.Stream.features:
|
||||
try: self.FeaturesHandler(self._owner.Dispatcher,self._owner.Dispatcher.Stream.features)
|
||||
except NodeProcessed: pass
|
||||
else: self._owner.RegisterHandlerOnce('features',self.FeaturesHandler,xmlns=NS_STREAMS)
|
||||
self.starttls=None
|
||||
|
||||
def plugout(self,now=0):
|
||||
""" Unregisters TLS handler's from owner's dispatcher. Take note that encription
|
||||
can not be stopped once started. You can only break the connection and start over."""
|
||||
self._owner.UnregisterHandler('features',self.FeaturesHandler,xmlns=NS_STREAMS)
|
||||
# self._owner.UnregisterHandlerOnce('proceed',self.StartTLSHandler,xmlns=NS_TLS)
|
||||
# self._owner.UnregisterHandlerOnce('failure',self.StartTLSHandler,xmlns=NS_TLS)
|
||||
|
||||
def FeaturesHandler(self, conn, feats):
|
||||
""" Used to analyse server <features/> tag for TLS support.
|
||||
If TLS is supported starts the encryption negotiation. Used internally"""
|
||||
if not feats.getTag('starttls',namespace=NS_TLS):
|
||||
self.DEBUG("TLS unsupported by remote server.",'warn')
|
||||
return
|
||||
self.DEBUG("TLS supported by remote server. Requesting TLS start.",'ok')
|
||||
self._owner.RegisterHandlerOnce('proceed',self.StartTLSHandler,xmlns=NS_TLS)
|
||||
self._owner.RegisterHandlerOnce('failure',self.StartTLSHandler,xmlns=NS_TLS)
|
||||
self._owner.Connection.send('<starttls xmlns="%s"/>'%NS_TLS)
|
||||
raise NodeProcessed
|
||||
|
||||
def pending_data(self,timeout=0):
|
||||
""" Returns true if there possible is a data ready to be read. """
|
||||
return self._tcpsock._seen_data or select.select([self._tcpsock._sock],[],[],timeout)[0]
|
||||
|
||||
def _startSSL(self):
|
||||
""" Immidiatedly switch socket to TLS mode. Used internally."""
|
||||
""" Here we should switch pending_data to hint mode."""
|
||||
tcpsock=self._owner.Connection
|
||||
tcpsock._sslObj = socket.ssl(tcpsock._sock, None, None)
|
||||
tcpsock._sslIssuer = tcpsock._sslObj.issuer()
|
||||
tcpsock._sslServer = tcpsock._sslObj.server()
|
||||
tcpsock._recv = tcpsock._sslObj.read
|
||||
tcpsock._send = tcpsock._sslObj.write
|
||||
self.starttls='success'
|
||||
|
||||
def StartTLSHandler(self, conn, starttls):
|
||||
""" Handle server reply if TLS is allowed to process. Behaves accordingly.
|
||||
Used internally."""
|
||||
if starttls.getNamespace()<>NS_TLS: return
|
||||
self.starttls=starttls.getName()
|
||||
if self.starttls=='failure':
|
||||
self.DEBUG("Got starttls response: "+self.starttls,'error')
|
||||
return
|
||||
self.DEBUG("Got starttls proceed response. Switching to TLS/SSL...",'ok')
|
||||
self._startSSL()
|
||||
self._owner.Dispatcher.PlugOut()
|
||||
dispatcher.Dispatcher().PlugIn(self._owner)
|
|
@ -20,7 +20,6 @@ from simplexml import ustr
|
|||
from client import PlugIn
|
||||
from idlequeue import IdleObject
|
||||
from protocol import *
|
||||
from transports import *
|
||||
|
||||
import sys
|
||||
import os
|
||||
|
@ -34,16 +33,19 @@ import logging
|
|||
log = logging.getLogger('gajim.c.x.transports_nb')
|
||||
|
||||
# I don't need to load gajim.py just because of few TLS variables, so I changed
|
||||
# :%s/common\.gajim\.DATA_DIR/\'\.\.\/data\'/c
|
||||
# :%s/common\.gajim\.MY_CACERTS/\'\%s\/\.gajim\/cacerts\.pem\' \% os\.environ\[\'HOME\'\]/c
|
||||
# %s/common\.gajim\.DATA_DIR/\'\.\.\/data\'/c
|
||||
# %s/common\.gajim\.MY_CACERTS/\'\%s\/\.gajim\/cacerts\.pem\' \% os\.environ\[\'HOME\'\]/c
|
||||
|
||||
# To change it back do:
|
||||
# %s/\'\.\.\/data\'/common\.gajim\.DATA_DIR/c
|
||||
# :%s/\'\%s\/\.gajim\/cacerts\.pem\' \% os\.environ\[\'HOME\'\]/common\.gajim\.MY_CACERTS/c
|
||||
# %s/\'%s\/\.gajim\/cacerts\.pem\'\ %\ os\.environ\[\'HOME\'\]/common\.gajim\.MY_CACERTS/c
|
||||
|
||||
# import common.gajim
|
||||
import common.gajim
|
||||
|
||||
|
||||
DATA_RECEIVED='DATA RECEIVED'
|
||||
DATA_SENT='DATA SENT'
|
||||
|
||||
USE_PYOPENSSL = False
|
||||
|
||||
try:
|
||||
|
@ -771,16 +773,16 @@ class NonBlockingTLS(PlugIn):
|
|||
#tcpsock._sslContext = OpenSSL.SSL.Context(OpenSSL.SSL.SSLv23_METHOD)
|
||||
tcpsock.ssl_errnum = 0
|
||||
tcpsock._sslContext.set_verify(OpenSSL.SSL.VERIFY_PEER, self._ssl_verify_callback)
|
||||
cacerts = os.path.join('../data', 'other', 'cacerts.pem')
|
||||
cacerts = os.path.join(common.gajim.DATA_DIR, 'other', 'cacerts.pem')
|
||||
try:
|
||||
tcpsock._sslContext.load_verify_locations(cacerts)
|
||||
except:
|
||||
log.warning('Unable to load SSL certificats from file %s' % \
|
||||
os.path.abspath(cacerts))
|
||||
# load users certs
|
||||
if os.path.isfile('%s/.gajim/cacerts.pem' % os.environ['HOME']):
|
||||
if os.path.isfile(common.gajim.MY_CACERTS):
|
||||
store = tcpsock._sslContext.get_cert_store()
|
||||
f = open('%s/.gajim/cacerts.pem' % os.environ['HOME'])
|
||||
f = open(common.gajim.MY_CACERTS)
|
||||
lines = f.readlines()
|
||||
i = 0
|
||||
begin = -1
|
||||
|
@ -795,11 +797,11 @@ class NonBlockingTLS(PlugIn):
|
|||
store.add_cert(X509cert)
|
||||
except OpenSSL.crypto.Error, exception_obj:
|
||||
log.warning('Unable to load a certificate from file %s: %s' %\
|
||||
('%s/.gajim/cacerts.pem' % os.environ['HOME'], exception_obj.args[0][0][2]))
|
||||
(common.gajim.MY_CACERTS, exception_obj.args[0][0][2]))
|
||||
except:
|
||||
log.warning(
|
||||
'Unknown error while loading certificate from file %s' % \
|
||||
'%s/.gajim/cacerts.pem' % os.environ['HOME'])
|
||||
common.gajim.MY_CACERTS)
|
||||
begin = -1
|
||||
i += 1
|
||||
tcpsock._sslObj = OpenSSL.SSL.Connection(tcpsock._sslContext, tcpsock._sock)
|
||||
|
|
|
@ -515,11 +515,11 @@ class PreferencesWindow:
|
|||
|
||||
def on_sort_by_show_checkbutton_toggled(self, widget):
|
||||
self.on_checkbutton_toggled(widget, 'sort_by_show')
|
||||
gajim.interface.roster.draw_roster()
|
||||
gajim.interface.roster.setup_and_draw_roster()
|
||||
|
||||
def on_show_avatars_in_roster_checkbutton_toggled(self, widget):
|
||||
self.on_checkbutton_toggled(widget, 'show_avatars_in_roster')
|
||||
gajim.interface.roster.draw_roster()
|
||||
gajim.interface.roster.setup_and_draw_roster()
|
||||
# Redraw connected groupchats (in an ugly way)
|
||||
for account in gajim.connections:
|
||||
if gajim.connections[account].connected:
|
||||
|
@ -530,14 +530,14 @@ class PreferencesWindow:
|
|||
|
||||
def on_show_status_msgs_in_roster_checkbutton_toggled(self, widget):
|
||||
self.on_checkbutton_toggled(widget, 'show_status_msgs_in_roster')
|
||||
gajim.interface.roster.draw_roster()
|
||||
gajim.interface.roster.setup_and_draw_roster()
|
||||
for ctl in gajim.interface.msg_win_mgr.controls():
|
||||
if ctl.type_id == message_control.TYPE_GC:
|
||||
ctl.update_ui()
|
||||
|
||||
def on_sort_by_show_checkbutton_toggled(self, widget):
|
||||
self.on_checkbutton_toggled(widget, 'sort_by_show')
|
||||
gajim.interface.roster.draw_roster()
|
||||
gajim.interface.roster.setup_and_draw_roster()
|
||||
|
||||
def on_emoticons_combobox_changed(self, widget):
|
||||
active = widget.get_active()
|
||||
|
@ -570,7 +570,7 @@ class PreferencesWindow:
|
|||
ctl.chat_buttons_set_visible(active)
|
||||
gajim.config.set('compact_view', active)
|
||||
gajim.interface.save_config()
|
||||
gajim.interface.roster.draw_roster()
|
||||
gajim.interface.roster.setup_and_draw_roster()
|
||||
|
||||
def on_xhtml_checkbutton_toggled(self, widget):
|
||||
self.on_checkbutton_toggled(widget, 'ignore_incoming_xhtml')
|
||||
|
@ -1417,7 +1417,6 @@ class AccountsWindow:
|
|||
self.current_account = account
|
||||
if account == gajim.ZEROCONF_ACC_NAME:
|
||||
self.remove_button.set_sensitive(False)
|
||||
self.rename_button.set_sensitive(False)
|
||||
self.init_account()
|
||||
self.update_proxy_list()
|
||||
|
||||
|
@ -1752,8 +1751,10 @@ class AccountsWindow:
|
|||
gajim.config.del_per('accounts', old_name)
|
||||
if self.current_account == old_name:
|
||||
self.current_account = new_name
|
||||
if old_name == gajim.ZEROCONF_ACC_NAME:
|
||||
gajim.ZEROCONF_ACC_NAME = new_name
|
||||
# refresh roster
|
||||
gajim.interface.roster.draw_roster()
|
||||
gajim.interface.roster.setup_and_draw_roster()
|
||||
self.init_accounts()
|
||||
self.select_account(new_name)
|
||||
|
||||
|
@ -2077,7 +2078,7 @@ class AccountsWindow:
|
|||
gajim.interface.roster.regroup = gajim.config.get('mergeaccounts')
|
||||
else:
|
||||
gajim.interface.roster.regroup = False
|
||||
gajim.interface.roster.draw_roster()
|
||||
gajim.interface.roster.setup_and_draw_roster()
|
||||
|
||||
def on_enable_zeroconf_checkbutton2_toggled(self, widget):
|
||||
# don't do anything if there is an account with the local name but is a
|
||||
|
@ -2119,7 +2120,7 @@ class AccountsWindow:
|
|||
gajim.interface.roster.regroup = gajim.config.get('mergeaccounts')
|
||||
else:
|
||||
gajim.interface.roster.regroup = False
|
||||
gajim.interface.roster.draw_roster()
|
||||
gajim.interface.roster.setup_and_draw_roster()
|
||||
gajim.interface.roster.set_actions_menu_needs_rebuild()
|
||||
|
||||
elif not gajim.config.get('enable_zeroconf') and widget.get_active():
|
||||
|
@ -2156,7 +2157,7 @@ class AccountsWindow:
|
|||
gajim.interface.roster.regroup = gajim.config.get('mergeaccounts')
|
||||
else:
|
||||
gajim.interface.roster.regroup = False
|
||||
gajim.interface.roster.draw_roster()
|
||||
gajim.interface.roster.setup_and_draw_roster()
|
||||
gajim.interface.roster.set_actions_menu_needs_rebuild()
|
||||
gajim.interface.save_config()
|
||||
|
||||
|
@ -2587,7 +2588,7 @@ class RemoveAccountWindow:
|
|||
gajim.interface.roster.regroup = gajim.config.get('mergeaccounts')
|
||||
else:
|
||||
gajim.interface.roster.regroup = False
|
||||
gajim.interface.roster.draw_roster()
|
||||
gajim.interface.roster.setup_and_draw_roster()
|
||||
gajim.interface.roster.set_actions_menu_needs_rebuild()
|
||||
if gajim.interface.instances.has_key('accounts'):
|
||||
gajim.interface.instances['accounts'].init_accounts()
|
||||
|
@ -3421,7 +3422,7 @@ class AccountCreationWizardWindow:
|
|||
gajim.interface.roster.regroup = gajim.config.get('mergeaccounts')
|
||||
else:
|
||||
gajim.interface.roster.regroup = False
|
||||
gajim.interface.roster.draw_roster()
|
||||
gajim.interface.roster.setup_and_draw_roster()
|
||||
gajim.interface.roster.set_actions_menu_needs_rebuild()
|
||||
gajim.interface.save_config()
|
||||
|
||||
|
|
|
@ -854,15 +854,8 @@ class ConversationTextview:
|
|||
exitcode = p.wait()
|
||||
|
||||
if exitcode == 0:
|
||||
convert_version = helpers.get_output_of_command(
|
||||
'convert -version')[0].split()[2]
|
||||
convert_version = [int(n) for n in convert_version.split('.')]
|
||||
if convert_version > [6, 3, 4]:
|
||||
# -alpha option was added in 6.3.5 release
|
||||
alpha = ['-alpha', 'off']
|
||||
else:
|
||||
alpha = []
|
||||
p = Popen(['convert'] + alpha + [tmpfile + '.ps', tmpfile + '.png'],
|
||||
latex_png_dpi = gajim.config.get('latex_png_dpi')
|
||||
p = Popen(['convert', '-background', 'white', '-flatten', '-density', latex_png_dpi, tmpfile + '.ps', tmpfile + '.png'],
|
||||
cwd=gettempdir())
|
||||
exitcode = p.wait()
|
||||
|
||||
|
|
|
@ -99,10 +99,6 @@ class FeaturesWindow:
|
|||
_('Encrypting chatmessages.'),
|
||||
_('Requires python-crypto.'),
|
||||
_('Requires python-crypto.')),
|
||||
_('Off the Record Encryption'): (self.otr_available,
|
||||
_('Encrypting chatmessages in a way that even works through gateways.'),
|
||||
_('Requires pyotr and libotr (see http://trac.gajim.org/wiki/OTR).'),
|
||||
_('Requires pyotr and libotr (see http://trac.gajim.org/wiki/OTR).')),
|
||||
_('RST Generator'): (self.docutils_available,
|
||||
_('Generate XHTML output from RST code (see http://docutils.sourceforge.net/docs/ref/rst/restructuredtext.html).'),
|
||||
_('Requires python-docutils.'),
|
||||
|
@ -312,11 +308,6 @@ class FeaturesWindow:
|
|||
from common import gajim
|
||||
return gajim.HAVE_PYCRYPTO
|
||||
|
||||
def otr_available(self):
|
||||
if gajim.otr_module:
|
||||
return True
|
||||
return False
|
||||
|
||||
def docutils_available(self):
|
||||
try:
|
||||
import docutils
|
||||
|
|
326
src/gajim.py
326
src/gajim.py
|
@ -142,8 +142,19 @@ try:
|
|||
import gtk
|
||||
except Warning, msg:
|
||||
if str(msg) == 'could not open display':
|
||||
print >> sys.stderr, _('Gajim needs X server to run. Quiting...')
|
||||
sys.exit()
|
||||
if sys.platform == 'darwin':
|
||||
# It seems there is no way to open X11 without also
|
||||
# opening an xterm. Even Apple's open-x11 script
|
||||
# opens the application AND an xterm.
|
||||
os.system('/Applications/Utilities/X11.app/Contents/MacOS/X11 &')
|
||||
try:
|
||||
import gtk
|
||||
except Warning, msg:
|
||||
print >> sys.stderr, _('No X11 running and failed to start it! Quitting...')
|
||||
sys.exit()
|
||||
else:
|
||||
print >> sys.stderr, _('Gajim needs X server to run. Quiting...')
|
||||
sys.exit()
|
||||
warnings.resetwarnings()
|
||||
|
||||
if os.name == 'nt':
|
||||
|
@ -255,214 +266,6 @@ from common import helpers
|
|||
from common import optparser
|
||||
from common import dataforms
|
||||
|
||||
from common.xmpp import Message as XmppMessage
|
||||
|
||||
try:
|
||||
import otr, otr_windows
|
||||
gajim.otr_module = otr
|
||||
gajim.otr_windows = otr_windows
|
||||
except ImportError:
|
||||
gajim.otr_module = None
|
||||
gajim.otr_windows = None
|
||||
|
||||
def add_appdata(data, context):
|
||||
account = data
|
||||
context.app_data = otr_windows.ContactOtrSMPWindow(
|
||||
unicode(context.username), account)
|
||||
|
||||
gajim.otr_add_appdata = add_appdata
|
||||
|
||||
def otr_dialog_destroy(widget, *args, **kwargs):
|
||||
widget.destroy()
|
||||
|
||||
class OtrlMessageAppOps:
|
||||
def gajim_log(self, msg, account, fjid, no_print=False):
|
||||
if not isinstance(fjid, unicode):
|
||||
fjid = unicode(fjid)
|
||||
if not isinstance(account, unicode):
|
||||
account = unicode(account)
|
||||
resource=gajim.get_resource_from_jid(fjid)
|
||||
tim = time.localtime()
|
||||
|
||||
if not no_print:
|
||||
ctrl = self.get_control(fjid, account)
|
||||
if ctrl:
|
||||
ctrl.print_conversation_line(u'[OTR] %s' % \
|
||||
msg, 'status', '', None)
|
||||
id = gajim.logger.write('chat_msg_recv', fjid,
|
||||
message='[OTR: %s]' % msg, tim=tim)
|
||||
# gajim.logger.write() only marks a message as unread
|
||||
# (and so only returns an id) when fjid is a real contact
|
||||
# (NOT if it's a GC private chat)
|
||||
if id:
|
||||
gajim.logger.set_read_messages([id])
|
||||
|
||||
def get_control(self, fjid, account):
|
||||
# first try to get the window with the full jid
|
||||
ctrls = gajim.interface.msg_win_mgr.get_chat_controls(fjid, account)
|
||||
if ctrls:
|
||||
# got one, be happy
|
||||
return ctrls[0]
|
||||
|
||||
# otherwise try without the resource
|
||||
ctrls = gajim.interface.msg_win_mgr.get_chat_controls(
|
||||
gajim.get_jid_without_resource(fjid), account)
|
||||
# but only use it when it is not a GC window
|
||||
if ctrls and ctrls[0].TYPE_ID == message_control.TYPE_CHAT:
|
||||
return ctrls[0]
|
||||
|
||||
def policy(self, opdata=None, context=None):
|
||||
policy = gajim.config.get_per('contacts', context.username,
|
||||
"otr_flags")
|
||||
if policy <= 0:
|
||||
policy = gajim.config.get_per('contacts',
|
||||
gajim.get_jid_without_resource(
|
||||
context.username), 'otr_flags')
|
||||
if policy <= 0:
|
||||
policy = gajim.config.get_per('accounts',
|
||||
opdata['account'], 'otr_flags')
|
||||
return policy
|
||||
|
||||
def create_privkey(self, opdata='', accountname='', protocol=''):
|
||||
dialog = gtk.Dialog(
|
||||
title = _('Generating...'),
|
||||
parent = gajim.interface.roster.window,
|
||||
flags = gtk.DIALOG_MODAL,
|
||||
buttons = (gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE))
|
||||
permlabel = gtk.Label(_('Generating a private key for %s...') \
|
||||
% accountname)
|
||||
permlabel.set_padding(20, 20)
|
||||
dialog.set_response_sensitive(gtk.RESPONSE_CLOSE, False)
|
||||
dialog.connect('destroy', otr_dialog_destroy)
|
||||
dialog.connect('response', otr_dialog_destroy)
|
||||
dialog.vbox.pack_start(permlabel)
|
||||
dialog.get_root_window().raise_()
|
||||
dialog.show_all()
|
||||
dialog.map()
|
||||
for c in dialog.get_children():
|
||||
c.show_now()
|
||||
c.map()
|
||||
|
||||
while gtk.events_pending():
|
||||
gtk.main_iteration(block = False)
|
||||
|
||||
otr.otrl_privkey_generate(
|
||||
gajim.connections[opdata['account']].otr_userstates,
|
||||
os.path.join(gajimpaths.root,
|
||||
'%s.key' % opdata['account']).encode(),
|
||||
accountname, gajim.OTR_PROTO)
|
||||
permlabel.set_text(_('Generating a private key for %s...\n' \
|
||||
'done.') % accountname)
|
||||
dialog.set_response_sensitive(gtk.RESPONSE_CLOSE, True)
|
||||
|
||||
def is_logged_in(self, opdata={}, accountname='', protocol='',
|
||||
recipient=""):
|
||||
contact = gajim.contacts.get_contact_from_full_jid(
|
||||
opdata['account'], recipient)
|
||||
if contact:
|
||||
return contact.show \
|
||||
in ['dnd', 'xa', 'chat', 'online', 'away',
|
||||
'invisible']
|
||||
return 0
|
||||
|
||||
def inject_message(self, opdata=None, accountname='', protocol='',
|
||||
recipient='', message=''):
|
||||
msg_type = otr.otrl_proto_message_type(message)
|
||||
|
||||
if 'kwargs' not in opdata or 'urgent' in opdata:
|
||||
# don't use send_message here to have the message
|
||||
# sent immediatly. This results in being able to
|
||||
# disconnect from OTR sessions before quitting
|
||||
stanza = XmppMessage(to = recipient,
|
||||
body = message, typ='chat')
|
||||
gajim.connections[opdata['account']].connection. \
|
||||
send(stanza, now = True)
|
||||
return
|
||||
|
||||
if msg_type == otr.OTRL_MSGTYPE_QUERY:
|
||||
# split away XHTML-contaminated explanatory message
|
||||
message = unicode(message.splitlines()[0])
|
||||
message += _(u'\nThis user has requested an ' \
|
||||
'Off-the-Record private conversation. ' \
|
||||
'However, you do not have a plugin to ' \
|
||||
'support that.\n' \
|
||||
'See http://otr.cypherpunks.ca/ for more ' \
|
||||
'information.')
|
||||
|
||||
gajim.connections[opdata['account']].connection.send(
|
||||
common.xmpp.Message(to = recipient,
|
||||
body = message, typ = 'chat'))
|
||||
return
|
||||
|
||||
gajim.connections[opdata['account']].send_message(recipient,
|
||||
message, **opdata['kwargs'])
|
||||
|
||||
def notify(sef, opdata=None, username='', **kwargs):
|
||||
self.gajim_log('Notify: ' + str(kwargs), opdata['account'],
|
||||
username)
|
||||
|
||||
def display_otr_message(self, opdata=None, username="", msg="", **kwargs):
|
||||
self.gajim_log('OTR Message: ' + msg, opdata['account'],
|
||||
username)
|
||||
return 0
|
||||
|
||||
def update_context_list(self, **kwargs):
|
||||
# FIXME stub FIXME #
|
||||
pass
|
||||
|
||||
def protocol_name(self, opdata=None, protocol=""):
|
||||
return 'XMPP'
|
||||
|
||||
def new_fingerprint(self, opdata=None, username='', fingerprint='',
|
||||
**kwargs):
|
||||
self.gajim_log('New fingerprint for %s: %s' % (username,
|
||||
otr.otrl_privkey_hash_to_human(fingerprint)),
|
||||
opdata['account'], username)
|
||||
|
||||
def write_fingerprints(self, opdata=''):
|
||||
otr.otrl_privkey_write_fingerprints(
|
||||
gajim.connections[opdata['account']].otr_userstates,
|
||||
os.path.join(gajimpaths.root, '%s.fpr' % \
|
||||
opdata['account']).encode())
|
||||
|
||||
def gone_secure(self, opdata='', context=None):
|
||||
trust = context.active_fingerprint.trust \
|
||||
and 'verified' or 'unverified'
|
||||
self.gajim_log('%s secured OTR connection started' % trust,
|
||||
opdata['account'], context.username, no_print = True)
|
||||
|
||||
ctrl = self.get_control(context.username, opdata['account'])
|
||||
if ctrl:
|
||||
ctrl.update_otr(True)
|
||||
|
||||
def gone_insecure(self, opdata='', context=None):
|
||||
self.gajim_log('Private conversation with %s lost.',
|
||||
opdata['account'], context.username)
|
||||
|
||||
ctrl = self.get_control(context.username, opdata['account'])
|
||||
if ctrl:
|
||||
ctrl.update_otr(True)
|
||||
|
||||
def still_secure(self, opdata=None, context=None, is_reply=0):
|
||||
ctrl = self.get_control(context.username, opdata['account'])
|
||||
if ctrl:
|
||||
ctrl.update_otr(True)
|
||||
|
||||
self.gajim_log('OTR connection was refreshed',
|
||||
opdata['account'], context.username)
|
||||
|
||||
def log_message(self, opdata=None, message=''):
|
||||
gajim.log.debug(message)
|
||||
|
||||
def max_message_size(self, **kwargs):
|
||||
return 0
|
||||
|
||||
def account_name(self, opdata=None, account='', protocol=''):
|
||||
return gajim.get_name_from_jid(opdata['account'],
|
||||
unicode(account))
|
||||
|
||||
gajim.otr_ui_ops = OtrlMessageAppOps()
|
||||
|
||||
if verbose: gajim.verbose = True
|
||||
del verbose
|
||||
|
||||
|
@ -600,8 +403,11 @@ def on_exit():
|
|||
if os.path.exists(pid_filename):
|
||||
os.remove(pid_filename)
|
||||
if sys.platform == 'darwin':
|
||||
import osx
|
||||
osx.shutdown()
|
||||
try:
|
||||
import osx
|
||||
osx.shutdown()
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
import atexit
|
||||
atexit.register(on_exit)
|
||||
|
@ -858,7 +664,7 @@ class Interface:
|
|||
if resource == gajim.connections[account].server_resource:
|
||||
return
|
||||
contact1 = gajim.contacts.create_contact(jid = ji,
|
||||
name = gajim.nicks[account], groups = [],
|
||||
name = gajim.nicks[account], groups = ['self_contact'],
|
||||
show = array[1], status = status_message, sub = 'both',
|
||||
ask = 'none', priority = priority, keyID = keyID,
|
||||
resource = resource)
|
||||
|
@ -1648,18 +1454,26 @@ class Interface:
|
|||
gmail_new_messages, gmail_new_messages)
|
||||
|
||||
if gajim.config.get('notify_on_new_gmail_email_extra'):
|
||||
cnt = 0
|
||||
for gmessage in gmail_messages_list:
|
||||
#FIXME: emulate Gtalk client popups. find out what they parse and how
|
||||
#they decide what to show
|
||||
# each message has a 'From', 'Subject' and 'Snippet' field
|
||||
text += _('\nFrom: %(from_address)s') % \
|
||||
{'from_address': gmessage['From']}
|
||||
#FIXME: emulate Gtalk client popups. find out what they parse and
|
||||
# how they decide what to show each message has a 'From',
|
||||
# 'Subject' and 'Snippet' field
|
||||
if cnt >=5:
|
||||
break
|
||||
senders = reduce(lambda b, a: a + ',\n ' + b,
|
||||
gmessage['From'])
|
||||
text += _('\n\nFrom: %(from_address)s\nSubject: %(subject)s\n%(snippet)s') % \
|
||||
{'from_address': senders, 'subject': gmessage['Subject'],
|
||||
'snippet': gmessage['Snippet']}
|
||||
cnt += 1
|
||||
|
||||
if gajim.config.get_per('soundevents', 'gmail_received', 'enabled'):
|
||||
helpers.play_sound('gmail_received')
|
||||
path = gtkgui_helpers.get_path_to_generic_or_avatar(img)
|
||||
notify.popup(_('New E-mail'), jid, account, 'gmail',
|
||||
path_to_image = path, title = title, text = text)
|
||||
path_to_image=path, title=title,
|
||||
text=gobject.markup_escape_text(text))
|
||||
|
||||
if self.remote_ctrl:
|
||||
self.remote_ctrl.raise_signal('NewGmail', (account, array))
|
||||
|
@ -2041,7 +1855,7 @@ class Interface:
|
|||
connection = gajim.connections[account]
|
||||
contact = gajim.contacts.create_contact(jid = jid.getStripped(),
|
||||
resource = resource, show = connection.get_status())
|
||||
self.new_chat(session, contact, account, resource = resource)
|
||||
self.new_chat(contact, account, resource = resource, session = session)
|
||||
|
||||
negotiation.FeatureNegotiationWindow(account, jid, session, form)
|
||||
|
||||
|
@ -2446,7 +2260,7 @@ class Interface:
|
|||
if not session:
|
||||
session = gajim.connections[account].get_or_create_session(fjid, None)
|
||||
|
||||
self.new_chat(session, contact, account, resource = resource)
|
||||
self.new_chat(contact, account, resource = resource, session = session)
|
||||
ctrl = session.control
|
||||
|
||||
gajim.last_message_time[account][jid] = 0 # long time ago
|
||||
|
@ -2514,7 +2328,6 @@ class Interface:
|
|||
self.roster.draw_contact(jid, account)
|
||||
if w:
|
||||
w.set_active_tab(ctrl)
|
||||
w.window.present()
|
||||
w.window.window.focus()
|
||||
# Using isinstance here because we want to catch all derived types
|
||||
if isinstance(ctrl, ChatControlBase):
|
||||
|
@ -2750,11 +2563,12 @@ class Interface:
|
|||
def join_gc_room(self, account, room_jid, nick, password, minimize=False,
|
||||
is_continued=False):
|
||||
'''joins the room immediately'''
|
||||
if not nick:
|
||||
nick = gajim.nicks[account]
|
||||
if self.msg_win_mgr.has_window(room_jid, account) and \
|
||||
gajim.gc_connected[account][room_jid]:
|
||||
gc_ctrl = self.msg_win_mgr.get_gc_control(room_jid, account)
|
||||
win = gc_ctrl.parent_win
|
||||
win.window.present()
|
||||
win.set_active_tab(gc_ctrl)
|
||||
dialogs.ErrorDialog(_('You are already in group chat %s') % room_jid)
|
||||
return
|
||||
|
@ -2782,7 +2596,6 @@ class Interface:
|
|||
if not minimized_control_exists:
|
||||
gc_control = self.msg_win_mgr.get_gc_control(room_jid, account)
|
||||
gc_control.parent_win.set_active_tab(gc_control)
|
||||
gc_control.parent_win.window.present()
|
||||
gajim.connections[account].join_gc(nick, room_jid, password)
|
||||
if password:
|
||||
gajim.gc_passwords[room_jid] = password
|
||||
|
@ -2841,7 +2654,7 @@ class Interface:
|
|||
# We call this here to avoid race conditions with widget validation
|
||||
session.control.read_queue()
|
||||
|
||||
def new_chat(self, session, contact, account, resource = None):
|
||||
def new_chat(self, contact, account, resource = None, session = None):
|
||||
# Get target window, create a control, and associate it with the window
|
||||
type_ = message_control.TYPE_CHAT
|
||||
|
||||
|
@ -2875,14 +2688,13 @@ class Interface:
|
|||
session = gajim.connections[account].get_or_create_session(fjid, None)
|
||||
|
||||
if not self.msg_win_mgr.has_window(fjid, account):
|
||||
session.control = self.new_chat(session, contact, account,
|
||||
resource=resource)
|
||||
session.control = self.new_chat(contact, account,
|
||||
resource=resource, session=session)
|
||||
if len(gajim.events.get_events(account, fjid)):
|
||||
session.control.read_queue()
|
||||
|
||||
mw = session.control.parent_win
|
||||
mw.set_active_tab(session.control)
|
||||
mw.window.present()
|
||||
# For JEP-0172
|
||||
if added_to_roster:
|
||||
session.control.user_nick = gajim.nicks[account]
|
||||
|
@ -2896,46 +2708,31 @@ class Interface:
|
|||
if resource:
|
||||
fjid += '/' + resource
|
||||
|
||||
conn = gajim.connections[account]
|
||||
ctrl = None
|
||||
|
||||
if not session and fjid in conn.sessions:
|
||||
sessions = filter(lambda s: isinstance(s, ChatControlSession),
|
||||
conn.sessions[fjid].values())
|
||||
if session:
|
||||
ctrl = session.control
|
||||
else:
|
||||
win = self.msg_win_mgr.get_window(fjid, account)
|
||||
|
||||
# look for an existing session with a chat control
|
||||
for s in sessions:
|
||||
if s.control:
|
||||
session = s
|
||||
break
|
||||
|
||||
if not session and not len(sessions) == 0:
|
||||
# there are no sessions with chat controls, just take the first one
|
||||
session = sessions[0]
|
||||
|
||||
if not session:
|
||||
# couldn't find an existing ChatControlSession, just make a new one
|
||||
session = conn.make_new_session(fjid, None, 'chat')
|
||||
|
||||
if not session.control:
|
||||
# open a new chat control
|
||||
session.control = self.new_chat(session, contact, account,
|
||||
resource=resource)
|
||||
|
||||
if len(gajim.events.get_events(account, fjid)):
|
||||
session.control.read_queue()
|
||||
if win:
|
||||
ctrl = win.get_controls(fjid, account)[0]
|
||||
|
||||
if not ctrl:
|
||||
ctrl = self.new_chat(contact, account,
|
||||
resource = resource, session = session)
|
||||
# last message is long time ago
|
||||
gajim.last_message_time[account][session.control.get_full_jid()] = 0
|
||||
gajim.last_message_time[account][ctrl.get_full_jid()] = 0
|
||||
|
||||
win = session.control.parent_win
|
||||
win.set_active_tab(session.control)
|
||||
win = ctrl.parent_win
|
||||
|
||||
if conn.is_zeroconf and conn.status in ('offline', 'invisible'):
|
||||
win.set_active_tab(ctrl)
|
||||
|
||||
if gajim.connections[account].is_zeroconf and \
|
||||
gajim.connections[account].status in ('offline', 'invisible'):
|
||||
for ctrl in win.get_controls(fjid, account):
|
||||
ctrl.got_disconnected()
|
||||
|
||||
win.window.present()
|
||||
|
||||
################################################################################
|
||||
### Other Methods
|
||||
################################################################################
|
||||
|
@ -3222,6 +3019,10 @@ class Interface:
|
|||
if os.name != 'nt' and gajim.config.get('check_if_gajim_is_default'):
|
||||
gtkgui_helpers.possibly_set_gajim_as_xmpp_handler()
|
||||
|
||||
for account in gajim.config.get_per('accounts'):
|
||||
if gajim.config.get_per('accounts', account, 'is_zeroconf'):
|
||||
gajim.ZEROCONF_ACC_NAME = account
|
||||
break
|
||||
# Is gnome configured to activate row on single click ?
|
||||
try:
|
||||
import gconf
|
||||
|
@ -3466,8 +3267,11 @@ if __name__ == '__main__':
|
|||
check_paths.check_and_possibly_create_paths()
|
||||
|
||||
if sys.platform == 'darwin':
|
||||
import osx
|
||||
osx.init()
|
||||
try:
|
||||
import osx
|
||||
osx.init()
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
Interface()
|
||||
|
||||
|
|
|
@ -916,6 +916,8 @@ class GroupchatControl(ChatControlBase):
|
|||
gajim.contacts.remove_gc_contact(self.account, gc_contact)
|
||||
gajim.gc_connected[self.account][self.room_jid] = False
|
||||
ChatControlBase.got_disconnected(self)
|
||||
# Tell connection to note the date we disconnect to avoid duplicate logs
|
||||
gajim.connections[self.account].gc_got_disconnected(self.room_jid)
|
||||
# We don't redraw the whole banner here, because only icon change
|
||||
self._update_banner_state_image()
|
||||
if self.parent_win:
|
||||
|
@ -1638,12 +1640,13 @@ class GroupchatControl(ChatControlBase):
|
|||
def shutdown(self, status='offline'):
|
||||
# destroy banner tooltip - bug #pygtk for that!
|
||||
self.subject_tooltip.destroy()
|
||||
if gajim.gc_connected[self.account][self.room_jid]:
|
||||
# Tell connection to note the date we disconnect to avoid duplicate
|
||||
# logs. We do it only when connected because if connection was lost
|
||||
# there may be new messages since disconnection.
|
||||
gajim.connections[self.account].gc_got_disconnected(self.room_jid)
|
||||
gajim.connections[self.account].send_gc_status(self.nick, self.room_jid,
|
||||
show='offline', status=status)
|
||||
# save in fast table in DB at what time we had last message
|
||||
last_history_time = \
|
||||
gajim.connections[self.account].last_history_time[self.room_jid]
|
||||
gajim.logger.set_room_last_message_time(self.room_jid, last_history_time)
|
||||
nick_list = gajim.contacts.get_nick_list(self.account, self.room_jid)
|
||||
for nick in nick_list:
|
||||
# Update pm chat window
|
||||
|
@ -2073,7 +2076,6 @@ class GroupchatControl(ChatControlBase):
|
|||
ctrl = win.get_controls(nick_jid, self.account)[0]
|
||||
|
||||
win.set_active_tab(ctrl)
|
||||
win.window.present()
|
||||
|
||||
return ctrl
|
||||
|
||||
|
|
|
@ -65,8 +65,6 @@ class HistoryWindow:
|
|||
'''Class for browsing logs of conversations with contacts'''
|
||||
|
||||
def __init__(self, jid = None, account = None):
|
||||
self.mark_days_idle_call_id = None
|
||||
|
||||
xml = gtkgui_helpers.get_glade('history_window.glade')
|
||||
self.window = xml.get_widget('history_window')
|
||||
self.jid_entry = xml.get_widget('jid_entry')
|
||||
|
@ -117,22 +115,25 @@ class HistoryWindow:
|
|||
self.completion_dict = {}
|
||||
self.accounts_seen_online = [] # Update dict when new accounts connect
|
||||
self.jids_to_search = []
|
||||
self._fill_completion_dict()
|
||||
|
||||
# This will load history too
|
||||
gobject.idle_add(self._fill_completion_dict().next)
|
||||
|
||||
if jid:
|
||||
self.jid_entry.set_text(jid)
|
||||
|
||||
xml.signal_autoconnect(self)
|
||||
self._load_history(jid, account)
|
||||
self.window.show_all()
|
||||
|
||||
def _fill_completion_dict(self):
|
||||
'''Fill completion_dict for key auto completion.
|
||||
'''Fill completion_dict for key auto completion. Then load history for
|
||||
current jid (by calling another function).
|
||||
|
||||
Key will be either jid or full_completion_name
|
||||
(contact name or long description like "pm-contact from groupchat....")
|
||||
|
||||
{key : (jid, account, nick_name, full_completion_name}
|
||||
this is a generator and does pseudo-threading via idle_add()
|
||||
'''
|
||||
liststore = gtkgui_helpers.get_completion_liststore(self.jid_entry)
|
||||
|
||||
|
@ -150,8 +151,14 @@ class HistoryWindow:
|
|||
muc_active_img = gtkgui_helpers.load_icon('muc_active')
|
||||
contact_img = gajim.interface.jabber_state_images['16']['online']
|
||||
muc_active_pix = muc_active_img.get_pixbuf()
|
||||
contact_pix = contact_img.get_pixbuf()
|
||||
contact_pix = contact_img.get_pixbuf()
|
||||
|
||||
keys = self.completion_dict.keys()
|
||||
# Move the actual jid at first so we load history faster
|
||||
actual_jid = self.jid_entry.get_text().decode('utf-8')
|
||||
if actual_jid in keys:
|
||||
keys.remove(actual_jid)
|
||||
keys.insert(0, actual_jid)
|
||||
# Map jid to info tuple
|
||||
# Warning : This for is time critical with big DB
|
||||
for key in keys:
|
||||
|
@ -180,13 +187,17 @@ class HistoryWindow:
|
|||
info_name = nick
|
||||
else:
|
||||
pix = contact_pix
|
||||
|
||||
|
||||
liststore.append((pix, completed))
|
||||
self.completion_dict[key] = (info_jid, info_acc, info_name,
|
||||
info_completion)
|
||||
self.completion_dict[completed] = (info_jid, info_acc,
|
||||
info_name, info_completion)
|
||||
if key == actual_jid:
|
||||
self._load_history(info_jid, info_acc)
|
||||
yield True
|
||||
keys.sort()
|
||||
yield False
|
||||
|
||||
def _get_account_for_jid(self, jid):
|
||||
'''Return the corresponding account of the jid.
|
||||
|
@ -202,10 +213,6 @@ class HistoryWindow:
|
|||
return account
|
||||
|
||||
def on_history_window_destroy(self, widget):
|
||||
if self.mark_days_idle_call_id:
|
||||
# if user destroys the window, and we have a generator filling mark days
|
||||
# stop him!
|
||||
gobject.source_remove(self.mark_days_idle_call_id)
|
||||
self.history_textview.del_handlers()
|
||||
del gajim.interface.instances['logs']
|
||||
|
||||
|
@ -265,11 +272,10 @@ class HistoryWindow:
|
|||
|
||||
# select logs for last date we have logs with contact
|
||||
self.calendar.set_sensitive(True)
|
||||
self.calendar.emit('month-changed')
|
||||
lastlog = gajim.logger.get_last_date_that_has_logs(self.jid, self.account)
|
||||
last_log = \
|
||||
gajim.logger.get_last_date_that_has_logs(self.jid, self.account)
|
||||
|
||||
tim = lastlog
|
||||
date = time.localtime(tim)
|
||||
date = time.localtime(last_log)
|
||||
|
||||
y, m, d = date[0], date[1], date[2]
|
||||
gtk_month = gtkgui_helpers.make_python_month_gtk_month(m)
|
||||
|
@ -305,31 +311,21 @@ class HistoryWindow:
|
|||
month = gtkgui_helpers.make_gtk_month_python_month(month)
|
||||
self._add_lines_for_date(year, month, day)
|
||||
|
||||
def do_possible_mark_for_days_in_this_month(self, widget, year, month):
|
||||
'''this is a generator and does pseudo-threading via idle_add()
|
||||
so it runs progressively! yea :)
|
||||
asks for days in this month if they have logs it bolds them (marks them)'''
|
||||
def on_calendar_month_changed(self, widget):
|
||||
'''asks for days in this month if they have logs it bolds them (marks
|
||||
them)
|
||||
'''
|
||||
year, month, day = widget.get_date() # integers
|
||||
# in gtk January is 1, in python January is 0,
|
||||
# I want the second
|
||||
# first day of month is 1 not 0
|
||||
widget.clear_marks()
|
||||
month = gtkgui_helpers.make_gtk_month_python_month(month)
|
||||
weekday, days_in_this_month = calendar.monthrange(year, month)
|
||||
log_days = gajim.logger.get_days_with_logs(self.jid, year,
|
||||
month, days_in_this_month, self.account)
|
||||
for day in log_days:
|
||||
widget.mark_day(day)
|
||||
yield True
|
||||
yield False
|
||||
|
||||
def on_calendar_month_changed(self, widget):
|
||||
year, month, day = widget.get_date() # integers
|
||||
# in gtk January is 1, in python January is 0,
|
||||
# I want the second
|
||||
# first day of month is 1 not 0
|
||||
if self.mark_days_idle_call_id:
|
||||
# if user changed month, and we have a generator filling mark days
|
||||
# stop him from marking dates for the previously selected month
|
||||
gobject.source_remove(self.mark_days_idle_call_id)
|
||||
widget.clear_marks()
|
||||
month = gtkgui_helpers.make_gtk_month_python_month(month)
|
||||
self.mark_days_idle_call_id = gobject.idle_add(
|
||||
self.do_possible_mark_for_days_in_this_month(widget, year, month).next)
|
||||
|
||||
def _get_string_show_from_constant_int(self, show):
|
||||
if show == constants.SHOW_ONLINE:
|
||||
|
@ -480,7 +476,8 @@ class HistoryWindow:
|
|||
message = row[4]
|
||||
local_time = time.localtime(tim)
|
||||
date = time.strftime('%x', local_time)
|
||||
# jid (to which log is assigned to), name, date, message, time (full unix time)
|
||||
# jid (to which log is assigned to), name, date, message,
|
||||
# time (full unix time)
|
||||
model.append((jid, contact_name, date, message, tim))
|
||||
|
||||
def on_query_combobox_changed(self, widget):
|
||||
|
@ -573,9 +570,12 @@ class HistoryWindow:
|
|||
|
||||
def open_history(self, jid, account):
|
||||
'''Load chat history of the specified jid'''
|
||||
self.jid_entry.set_text(jid)
|
||||
if account and account not in self.accounts_seen_online:
|
||||
# Update dict to not only show bare jid
|
||||
self._fill_completion_dict
|
||||
self.jid_entry.set_text(jid)
|
||||
self._load_history(jid, account)
|
||||
gobject.idle_add(self._fill_completion_dict().next)
|
||||
else:
|
||||
# Only in that case because it's called by self._fill_completion_dict()
|
||||
# otherwise
|
||||
self._load_history(jid, account)
|
||||
self.results_window.set_property('visible', False)
|
||||
|
|
|
@ -134,12 +134,17 @@ class MessageControl:
|
|||
new_key = session.thread_id
|
||||
|
||||
if oldsession:
|
||||
self.parent_win.change_thread_key(
|
||||
self.contact.jid, self.account,
|
||||
jid = self.contact.jid
|
||||
if self.resource:
|
||||
jid += '/' + self.resource
|
||||
|
||||
self.parent_win.change_thread_key(jid, self.account,
|
||||
oldsession.thread_id, new_key)
|
||||
|
||||
if oldsession.enable_encryption:
|
||||
self.print_esession_details()
|
||||
else:
|
||||
self.parent_win.move_from_sessionless(self)
|
||||
|
||||
def send_message(self, message, keyID = '', type = 'chat',
|
||||
chatstate = None, msg_id = None, composing_xep = None, resource = None,
|
||||
|
@ -148,56 +153,18 @@ class MessageControl:
|
|||
# Doesn't return None if error
|
||||
jid = self.contact.jid
|
||||
original_message = message
|
||||
conn = gajim.connections[self.account]
|
||||
|
||||
if not self.session:
|
||||
sess = gajim.connections[self.account].make_new_session(jid)
|
||||
sess = conn.find_controlless_session(jid)
|
||||
|
||||
if not sess:
|
||||
sess = conn.make_new_session(jid)
|
||||
|
||||
self.set_session(sess)
|
||||
self.parent_win.move_from_sessionless(self)
|
||||
|
||||
xep_200 = bool(self.session) and self.session.enable_encryption
|
||||
|
||||
if gajim.otr_module and not xep_200 and (jid not in gajim.otr_dont_append_tag):
|
||||
if type == 'chat' and isinstance(message, unicode):
|
||||
d = {'kwargs': {'keyID': keyID, 'type': type,
|
||||
'chatstate': chatstate,
|
||||
'msg_id': msg_id,
|
||||
'composing_xep': composing_xep,
|
||||
'resource': self.resource,
|
||||
'user_nick': user_nick,
|
||||
'session': self.session,
|
||||
'original_message': original_message},
|
||||
'account': self.account}
|
||||
|
||||
new_msg = gajim.otr_module.otrl_message_sending(
|
||||
self.session.conn.otr_userstates,
|
||||
(gajim.otr_ui_ops, d),
|
||||
gajim.get_jid_from_account(
|
||||
self.account).encode(),
|
||||
gajim.OTR_PROTO,
|
||||
self.contact.get_full_jid().encode(),
|
||||
message.encode(), None,
|
||||
(gajim.otr_add_appdata, self.account))
|
||||
|
||||
ctx = gajim.otr_module.otrl_context_find(
|
||||
self.session.conn.otr_userstates,
|
||||
self.contact.get_full_jid().encode(),
|
||||
gajim.get_jid_from_account(
|
||||
self.account).encode(),
|
||||
gajim.OTR_PROTO, 1,
|
||||
(gajim.otr_add_appdata,
|
||||
self.account))[0]
|
||||
|
||||
# we send all because inject_message can filter
|
||||
# on HTML stuff then
|
||||
gajim.otr_module.otrl_message_fragment_and_send(
|
||||
(gajim.otr_ui_ops, d), ctx, new_msg,
|
||||
gajim.otr_module.OTRL_FRAGMENT_SEND_ALL)
|
||||
return
|
||||
|
||||
# Send and update history
|
||||
return gajim.connections[self.account].send_message(jid,
|
||||
message, keyID, type = type, chatstate = chatstate,
|
||||
msg_id = msg_id, composing_xep = composing_xep,
|
||||
return conn.send_message(jid, message, keyID, type = type,
|
||||
chatstate = chatstate, msg_id = msg_id, composing_xep = composing_xep,
|
||||
resource = self.resource, user_nick = user_nick,
|
||||
session = self.session,
|
||||
original_message = original_message)
|
||||
session = self.session, original_message = original_message)
|
||||
|
|
|
@ -448,6 +448,7 @@ class MessageWindow(object):
|
|||
def set_active_tab(self, ctrl):
|
||||
ctrl_page = self.notebook.page_num(ctrl.widget)
|
||||
self.notebook.set_current_page(ctrl_page)
|
||||
self.window.present()
|
||||
|
||||
def remove_tab(self, ctrl, method, reason = None, force = False):
|
||||
'''reason is only for gc (offline status message)
|
||||
|
@ -692,10 +693,13 @@ class MessageWindow(object):
|
|||
|
||||
del self.sessionless_ctrls[acct][jid][idx]
|
||||
|
||||
if len(self.sessionless_ctrls[acct][jid]) == 0:
|
||||
del self.sessionless_ctrls[acct][jid]
|
||||
|
||||
if not self._controls.has_key(acct):
|
||||
self._controls[acct] = {}
|
||||
|
||||
if not self.sessionless_ctrls[acct].has_key(jid):
|
||||
if not self._controls[acct].has_key(jid):
|
||||
self._controls[acct][jid] = {}
|
||||
|
||||
thread_id = ctrl.session.thread_id
|
||||
|
|
|
@ -1,15 +1,12 @@
|
|||
import sys, commands
|
||||
from network_manager_listener import device_now_active, device_no_longer_active
|
||||
import nsapp
|
||||
|
||||
|
||||
if sys.platform != "darwin":
|
||||
raise ImportError("System platform is not OS/X")
|
||||
|
||||
if sys.platform != 'darwin':
|
||||
raise ImportError('System platform is not OS X')
|
||||
|
||||
net_device_active = True
|
||||
|
||||
|
||||
###
|
||||
### Utility functions
|
||||
###
|
||||
|
@ -21,6 +18,7 @@ def checkPID(pid, procname):
|
|||
return True
|
||||
return False
|
||||
|
||||
import nsapp
|
||||
|
||||
def init():
|
||||
nsapp.init()
|
||||
|
|
|
@ -1,357 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
## otr_windows.py
|
||||
##
|
||||
##
|
||||
## Copyright (C) 2008 Kjell Braden <fnord@pentabarf.de>
|
||||
##
|
||||
## This file is part of Gajim.
|
||||
##
|
||||
## Gajim is free software; you can redistribute it and/or modify
|
||||
## it under the terms of the GNU General Public License as published
|
||||
## by the Free Software Foundation; version 3 only.
|
||||
##
|
||||
## Gajim is distributed in the hope that it will be useful,
|
||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
## GNU General Public License for more details.
|
||||
##
|
||||
## You should have received a copy of the GNU General Public License
|
||||
## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
|
||||
##
|
||||
|
||||
import gtkgui_helpers
|
||||
from common import gajim
|
||||
|
||||
our_fp_text = _('Your fingerprint:\n' \
|
||||
'<span weight="bold" face="monospace">%s</span>')
|
||||
their_fp_text = _('Purported fingerprint for %s:\n' \
|
||||
'<span weight="bold" face="monospace">%s</span>')
|
||||
|
||||
class ContactOtrSMPWindow:
|
||||
def gw(self, n):
|
||||
# shorthand for self.xml.get_widget(n)
|
||||
return self.xml.get_widget(n)
|
||||
|
||||
def __init__(self, fjid, account):
|
||||
self.fjid = fjid
|
||||
self.account = account
|
||||
|
||||
self.xml = gtkgui_helpers.get_glade('contact_otr_window.glade')
|
||||
self.window = self.xml.get_widget('otr_smp_window')
|
||||
|
||||
# the contact may be unknown to gajim if ContactOtrSMPWindow
|
||||
# is created very early
|
||||
self.contact = gajim.contacts.get_contact_from_full_jid(
|
||||
account, fjid)
|
||||
if self.contact:
|
||||
self.window.set_title(_('OTR settings for %s') % \
|
||||
self.contact.get_full_jid())
|
||||
|
||||
self.ctx = gajim.otr_module.otrl_context_find(
|
||||
gajim.connections[self.account].otr_userstates,
|
||||
self.fjid.encode(), gajim.get_jid_from_account(
|
||||
self.account).encode(), gajim.OTR_PROTO, 1,
|
||||
(gajim.otr_add_appdata, self.account))[0]
|
||||
|
||||
self.gw('smp_cancel_button').connect('clicked',
|
||||
self._on_destroy)
|
||||
self.gw('smp_ok_button').connect('clicked', self._apply)
|
||||
|
||||
def show(self, response):
|
||||
# re-initialize if contact was unknown when we
|
||||
# initially initialized
|
||||
if not self.contact:
|
||||
self.__init__(self.fjid, self.account)
|
||||
# the contact MUST be known when showing the dialog
|
||||
assert(self.contact)
|
||||
|
||||
self.smp_running = False
|
||||
self.finished = False
|
||||
|
||||
self.gw('smp_cancel_button').set_sensitive(True)
|
||||
self.gw('smp_ok_button').set_sensitive(True)
|
||||
self.gw('progressbar').set_fraction(0)
|
||||
self.gw('secret_entry').set_text('')
|
||||
|
||||
self.response = response
|
||||
if response:
|
||||
self.gw('desc_label').set_markup(_('<b>%s is trying ' \
|
||||
'to authenticate you using a secret only ' \
|
||||
'known to him/her and you.</b> Please enter ' \
|
||||
'your secret below.') % \
|
||||
self.contact.get_full_jid())
|
||||
else:
|
||||
self.gw('desc_label').set_markup(_('<b>You are ' \
|
||||
'trying to authenticate %s using a secret' \
|
||||
'only known to him/her and yourself.</b>' \
|
||||
'Please enter your secret below.') % \
|
||||
self.contact.get_full_jid())
|
||||
|
||||
self.window.show_all()
|
||||
|
||||
def _abort(self, text=None):
|
||||
self.smp_running = False
|
||||
gajim.otr_module.otrl_message_abort_smp(
|
||||
gajim.connections[self.account].otr_userstates,
|
||||
(gajim.otr_ui_ops, {'account': self.account}), self.ctx)
|
||||
if text:
|
||||
gajim.otr_ui_ops.gajim_log(text, self.account,
|
||||
self.contact.get_full_jid())
|
||||
|
||||
def _finish(self, text):
|
||||
self.smp_running = False
|
||||
self.finished = True
|
||||
self.gw('smp_cancel_button').set_sensitive(False)
|
||||
self.gw('smp_ok_button').set_sensitive(True)
|
||||
self.gw('progressbar').set_fraction(1)
|
||||
gajim.otr_ui_ops.gajim_log(text, self.account,
|
||||
self.contact.get_full_jid())
|
||||
self.gw('desc_label').set_markup(text)
|
||||
for ctrl in gajim.interface.msg_win_mgr.get_chat_controls(
|
||||
self.contact.jid, self.account):
|
||||
ctrl.update_otr(True)
|
||||
gajim.otr_ui_ops.write_fingerprints({'account': self.account})
|
||||
|
||||
def handle_tlv(self, tlvs):
|
||||
if not self.contact:
|
||||
self.__init__(self.fjid, self.account)
|
||||
|
||||
if tlvs:
|
||||
nextTLV = self.ctx.smstate.nextExpected;
|
||||
tlv = gajim.otr_module.otrl_tlv_find(tlvs,
|
||||
gajim.otr_module.OTRL_TLV_SMP1)
|
||||
if tlv:
|
||||
if nextTLV != \
|
||||
gajim.otr_module.OTRL_SMP_EXPECT1:
|
||||
self._abort()
|
||||
else:
|
||||
self.show(True)
|
||||
self.gw('progressbar'). \
|
||||
set_fraction(0.3)
|
||||
tlv = gajim.otr_module.otrl_tlv_find(tlvs,
|
||||
gajim.otr_module.OTRL_TLV_SMP2)
|
||||
if tlv:
|
||||
if nextTLV != gajim.otr_module.OTRL_SMP_EXPECT2:
|
||||
self._abort()
|
||||
else:
|
||||
self.ctx.smstate.nextExpected = \
|
||||
gajim.otr_module. \
|
||||
OTRL_SMP_EXPECT4
|
||||
self.gw('progressbar').set_fraction(0.6)
|
||||
tlv = gajim.otr_module.otrl_tlv_find(tlvs,
|
||||
gajim.otr_module.OTRL_TLV_SMP3)
|
||||
if tlv:
|
||||
if nextTLV != gajim.otr_module.OTRL_SMP_EXPECT3:
|
||||
self._abort()
|
||||
else:
|
||||
self.ctx.smstate.nextExpected = \
|
||||
gajim.otr_module. \
|
||||
OTRL_SMP_EXPECT1;
|
||||
if self.ctx.active_fingerprint.trust:
|
||||
self._finish(_('SMP ' \
|
||||
'verifying succeeded'))
|
||||
else:
|
||||
self._finish(_('SMP ' \
|
||||
'verifying failed'))
|
||||
tlv = gajim.otr_module.otrl_tlv_find(tlvs,
|
||||
gajim.otr_module.OTRL_TLV_SMP4)
|
||||
if tlv:
|
||||
if nextTLV != gajim.otr_module.OTRL_SMP_EXPECT4:
|
||||
self._abort()
|
||||
else:
|
||||
self.ctx.smstate.nextExpected = \
|
||||
gajim.otr_module. \
|
||||
OTRL_SMP_EXPECT1;
|
||||
if self.ctx.active_fingerprint.trust:
|
||||
self._finish(_('SMP ' \
|
||||
'verifying succeeded'))
|
||||
else:
|
||||
self._finish(_('SMP ' \
|
||||
'verifying failed'))
|
||||
tlv = gajim.otr_module.otrl_tlv_find(tlvs,
|
||||
gajim.otr_module.OTRL_TLV_SMP_ABORT)
|
||||
if tlv:
|
||||
self._finish(_('SMP verifying aborted'))
|
||||
|
||||
def _on_destroy(self, widget):
|
||||
if self.smp_running:
|
||||
self._abort(_('user aborted SMP authentication'))
|
||||
self.window.hide_all()
|
||||
|
||||
def _apply(self, widget):
|
||||
if self.finished:
|
||||
self.window.hide_all()
|
||||
return
|
||||
secret = self.gw('secret_entry').get_text()
|
||||
if self.response:
|
||||
gajim.otr_module.otrl_message_respond_smp(
|
||||
gajim.connections[self.account].otr_userstates,
|
||||
(gajim.otr_ui_ops, {'account': self.account}),
|
||||
self.ctx, secret)
|
||||
else:
|
||||
gajim.otr_module.otrl_message_initiate_smp(
|
||||
gajim.connections[self.account].otr_userstates,
|
||||
(gajim.otr_ui_ops, {'account': self.account}),
|
||||
self.ctx, secret)
|
||||
self.gw('progressbar').set_fraction(0.3)
|
||||
self.smp_running = True
|
||||
widget.set_sensitive(False)
|
||||
|
||||
class ContactOtrWindow:
|
||||
def gw(self, n):
|
||||
# shorthand for self.xml.get_widget(n)
|
||||
return self.xml.get_widget(n)
|
||||
|
||||
def __init__(self, contact, account, ctrl=None):
|
||||
self.contact = contact
|
||||
self.account = account
|
||||
self.ctrl = ctrl
|
||||
|
||||
self.ctx = gajim.otr_module.otrl_context_find(
|
||||
gajim.connections[self.account].otr_userstates,
|
||||
self.contact.get_full_jid().encode(),
|
||||
gajim.get_jid_from_account(self.account).encode(),
|
||||
gajim.OTR_PROTO, 1, (gajim.otr_add_appdata,
|
||||
self.account))[0]
|
||||
|
||||
self.xml = gtkgui_helpers.get_glade('contact_otr_window.glade')
|
||||
self.window = self.xml.get_widget('otr_settings_window')
|
||||
|
||||
self.gw('settings_cancel_button').connect('clicked',
|
||||
self._on_destroy)
|
||||
self.gw('settings_ok_button').connect('clicked', self._apply)
|
||||
self.gw('otr_default_checkbutton').connect('toggled',
|
||||
self._otr_default_checkbutton_toggled)
|
||||
|
||||
self.window.set_title(_('OTR settings for %s') % \
|
||||
self.contact.get_full_jid())
|
||||
|
||||
# always set the label containing our fingerprint
|
||||
self.gw('our_fp_label').set_markup(our_fp_text % \
|
||||
gajim.otr_module.otrl_privkey_fingerprint(
|
||||
gajim.connections[self.account].otr_userstates,
|
||||
gajim.get_jid_from_account(self.account).encode(),
|
||||
gajim.OTR_PROTO))
|
||||
|
||||
if self.ctx.msgstate != \
|
||||
gajim.otr_module.OTRL_MSGSTATE_ENCRYPTED:
|
||||
# make the fingerprint widgets insensitive
|
||||
# when not encrypted
|
||||
for widget in self.gw('otr_fp_vbox').get_children():
|
||||
widget.set_sensitive(False)
|
||||
# show that the fingerprint is unknown
|
||||
self.gw('their_fp_label').set_markup(
|
||||
their_fp_text % (self.contact.get_full_jid(),
|
||||
_('unknown')))
|
||||
self.gw('verified_combobox').set_active(-1)
|
||||
else:
|
||||
# make the fingerprint widgets sensitive when encrypted
|
||||
for widget in self.gw('otr_fp_vbox').get_children():
|
||||
widget.set_sensitive(True)
|
||||
# show their fingerprint
|
||||
self.gw('their_fp_label').set_markup(
|
||||
their_fp_text%(self.contact.get_full_jid(),
|
||||
gajim.otr_module.otrl_privkey_hash_to_human(
|
||||
self.ctx.active_fingerprint.fingerprint)))
|
||||
# set the trust combobox
|
||||
if self.ctx.active_fingerprint.trust:
|
||||
self.gw('verified_combobox').set_active(1)
|
||||
else:
|
||||
self.gw('verified_combobox').set_active(0)
|
||||
|
||||
otr_flags = gajim.config.get_per('contacts', self.contact.jid,
|
||||
'otr_flags')
|
||||
|
||||
if otr_flags >= 0:
|
||||
self.gw('otr_default_checkbutton').set_active(0)
|
||||
for w in self.gw('otr_settings_vbox').get_children():
|
||||
w.set_sensitive(True)
|
||||
else:
|
||||
# per-user settings not available,
|
||||
# using default settings
|
||||
otr_flags = gajim.config.get_per('accounts',
|
||||
self.account, 'otr_flags')
|
||||
self.gw('otr_default_checkbutton').set_active(1)
|
||||
for w in self.gw('otr_settings_vbox').get_children():
|
||||
w.set_sensitive(False)
|
||||
|
||||
self.gw('otr_policy_allow_v1_checkbutton').set_active(
|
||||
otr_flags & gajim.otr_module.OTRL_POLICY_ALLOW_V1)
|
||||
self.gw('otr_policy_allow_v2_checkbutton').set_active(
|
||||
otr_flags & gajim.otr_module.OTRL_POLICY_ALLOW_V2)
|
||||
self.gw('otr_policy_require_checkbutton').set_active(
|
||||
otr_flags & gajim.otr_module.OTRL_POLICY_REQUIRE_ENCRYPTION)
|
||||
self.gw('otr_policy_send_tag_checkbutton').set_active(
|
||||
otr_flags & \
|
||||
gajim.otr_module.OTRL_POLICY_SEND_WHITESPACE_TAG)
|
||||
self.gw('otr_policy_start_on_tag_checkbutton').set_active(
|
||||
otr_flags & \
|
||||
gajim.otr_module.OTRL_POLICY_WHITESPACE_START_AKE)
|
||||
self.gw('otr_policy_start_on_error_checkbutton').set_active(
|
||||
otr_flags & \
|
||||
gajim.otr_module.OTRL_POLICY_ERROR_START_AKE)
|
||||
|
||||
self.window.show_all()
|
||||
|
||||
def _on_destroy(self, widget):
|
||||
self.window.destroy()
|
||||
|
||||
def _apply(self, widget):
|
||||
# -1 when nothing is selected
|
||||
# (ie. the connection is not encrypted)
|
||||
trust_state = self.gw('verified_combobox').get_active()
|
||||
if trust_state == 1 and not self.ctx.active_fingerprint.trust:
|
||||
gajim.otr_module.otrl_context_set_trust(
|
||||
self.ctx.active_fingerprint, 'verified')
|
||||
gajim.otr_ui_ops.write_fingerprints(
|
||||
{'account': self.account})
|
||||
elif trust_state == 0:
|
||||
gajim.otr_module.otrl_context_set_trust(
|
||||
self.ctx.active_fingerprint, '')
|
||||
gajim.otr_ui_ops.write_fingerprints(
|
||||
{'account': self.account})
|
||||
|
||||
if not self.ctrl:
|
||||
self.ctrl = gajim.interface.msg_win_mgr.get_control(
|
||||
self.contact.jid, self.account)
|
||||
if self.ctrl:
|
||||
self.ctrl.update_otr(True)
|
||||
|
||||
if self.gw('otr_default_checkbutton').get_active():
|
||||
# default is enabled, so remove any user-specific
|
||||
# settings if available
|
||||
gajim.config.set_per('contacts',
|
||||
self.contact.jid, 'otr_flags', -1)
|
||||
else:
|
||||
# build the flags using the checkboxes
|
||||
flags = 0
|
||||
flags += self.gw('otr_policy_allow_v1_checkbutton'). \
|
||||
get_active() and gajim.otr_module. \
|
||||
OTRL_POLICY_ALLOW_V1
|
||||
flags += self.gw('otr_policy_allow_v2_checkbutton'). \
|
||||
get_active() and gajim.otr_module. \
|
||||
OTRL_POLICY_ALLOW_V2
|
||||
flags += self.gw('otr_policy_require_checkbutton'). \
|
||||
get_active() and gajim.otr_module. \
|
||||
OTRL_POLICY_REQUIRE_ENCRYPTION
|
||||
flags += self.gw('otr_policy_send_tag_checkbutton'). \
|
||||
get_active() and gajim.otr_module. \
|
||||
OTRL_POLICY_SEND_WHITESPACE_TAG
|
||||
flags += self.gw(
|
||||
'otr_policy_start_on_tag_checkbutton'). \
|
||||
get_active() and gajim.otr_module. \
|
||||
OTRL_POLICY_WHITESPACE_START_AKE
|
||||
flags += self.gw(
|
||||
'otr_policy_start_on_error_checkbutton'). \
|
||||
get_active() and gajim.otr_module. \
|
||||
OTRL_POLICY_ERROR_START_AKE
|
||||
|
||||
gajim.config.add_per('contacts', self.contact.jid)
|
||||
gajim.config.set_per('contacts', self.contact.jid,
|
||||
'otr_flags', flags)
|
||||
|
||||
self._on_destroy(widget)
|
||||
|
||||
def _otr_default_checkbutton_toggled(self, widget):
|
||||
for w in self.gw('otr_settings_vbox').get_children():
|
||||
w.set_sensitive(not widget.get_active())
|
File diff suppressed because it is too large
Load diff
|
@ -186,7 +186,6 @@ class ChatControlSession(stanza_session.EncryptedStanzaSession):
|
|||
if ctrl:
|
||||
self.control = ctrl
|
||||
self.control.set_session(self)
|
||||
self.control.parent_win.move_from_sessionless(self.control)
|
||||
first = False
|
||||
|
||||
if pm:
|
||||
|
@ -317,8 +316,8 @@ class ChatControlSession(stanza_session.EncryptedStanzaSession):
|
|||
|
||||
if popup:
|
||||
if not self.control:
|
||||
self.control = gajim.interface.new_chat(self, contact,
|
||||
self.conn.name, resource=resource_for_chat)
|
||||
self.control = gajim.interface.new_chat(contact,
|
||||
self.conn.name, resource=resource_for_chat, session=self)
|
||||
|
||||
if len(gajim.events.get_events(self.conn.name, fjid)):
|
||||
self.control.read_queue()
|
||||
|
|
|
@ -27,7 +27,10 @@ from common import gajim
|
|||
from common import helpers
|
||||
|
||||
if sys.platform == 'darwin':
|
||||
import osx
|
||||
try:
|
||||
import osx
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
class StatusIcon(systray.Systray):
|
||||
'''Class for the notification area icon'''
|
||||
|
@ -69,7 +72,10 @@ class StatusIcon(systray.Systray):
|
|||
self.status_icon.set_tooltip(text)
|
||||
if gajim.events.get_nb_systray_events():
|
||||
if sys.platform == 'darwin':
|
||||
osx.nsapp.requestUserAttention()
|
||||
try:
|
||||
osx.nsapp.requestUserAttention()
|
||||
except NameError:
|
||||
pass
|
||||
state = 'event'
|
||||
self.status_icon.set_blinking(True)
|
||||
else:
|
||||
|
|
|
@ -109,7 +109,6 @@ class Systray:
|
|||
if gajim.interface.msg_win_mgr.has_window(jid, account):
|
||||
gajim.interface.msg_win_mgr.get_window(jid, account).set_active_tab(
|
||||
jid, account)
|
||||
gajim.interface.msg_win_mgr.get_window(jid, account).window.present()
|
||||
elif contact:
|
||||
gajim.interface.new_chat(contact, account)
|
||||
gajim.interface.msg_win_mgr.get_window(jid, account).set_active_tab(
|
||||
|
|
427
src/tictactoe.py
427
src/tictactoe.py
|
@ -1,427 +0,0 @@
|
|||
from common import stanza_session
|
||||
from common import xmpp
|
||||
|
||||
import pygtk
|
||||
pygtk.require('2.0')
|
||||
import gtk
|
||||
from gtk import gdk
|
||||
import cairo
|
||||
|
||||
# implements <http://pidgin-games.sourceforge.net/xep/tictactoe.html#invite>
|
||||
|
||||
games_ns = 'http://jabber.org/protocol/games'
|
||||
|
||||
class InvalidMove(Exception):
|
||||
pass
|
||||
|
||||
class TicTacToeSession(stanza_session.StanzaSession):
|
||||
# initiate a session
|
||||
def begin(self, rows = 3, cols = 3, role_s = 'x'):
|
||||
self.rows = rows
|
||||
self.cols = cols
|
||||
|
||||
self.role_s = role_s
|
||||
|
||||
self.strike = 3
|
||||
|
||||
if self.role_s == 'x':
|
||||
self.role_o = 'o'
|
||||
else:
|
||||
self.role_o = 'x'
|
||||
|
||||
self.send_invitation()
|
||||
|
||||
self.next_move_id = 1
|
||||
self.received = self.wait_for_invite_response
|
||||
|
||||
def send_invitation(self):
|
||||
msg = xmpp.Message()
|
||||
|
||||
invite = msg.NT.invite
|
||||
invite.setNamespace(games_ns)
|
||||
|
||||
game = invite.NT.game
|
||||
game.setAttr('var', games_ns + '/tictactoe')
|
||||
|
||||
x = xmpp.DataForm(typ='submit')
|
||||
|
||||
game.addChild(node=x)
|
||||
|
||||
self.send(msg)
|
||||
|
||||
def read_invitation(self, msg):
|
||||
invite = msg.getTag('invite', namespace=games_ns)
|
||||
game = invite.getTag('game')
|
||||
x = game.getTag('x', namespace='jabber:x:data')
|
||||
|
||||
form = xmpp.DataForm(node=x)
|
||||
|
||||
if form.getField('role'):
|
||||
self.role_o = form.getField('role').getValues()[0]
|
||||
else:
|
||||
self.role_o = 'x'
|
||||
|
||||
if form.getField('rows'):
|
||||
self.rows = int(form.getField('rows').getValues()[0])
|
||||
else:
|
||||
self.rows = 3
|
||||
|
||||
if form.getField('cols'):
|
||||
self.cols = int(form.getField('cols').getValues()[0])
|
||||
else:
|
||||
self.cols = 3
|
||||
|
||||
# number in a row needed to win
|
||||
if form.getField('strike'):
|
||||
self.strike = int(form.getField('strike').getValues()[0])
|
||||
else:
|
||||
self.strike = 3
|
||||
|
||||
# received an invitation
|
||||
def invited(self, msg):
|
||||
self.read_invitation(msg)
|
||||
|
||||
# XXX prompt user
|
||||
# "accept, reject, ignore"
|
||||
|
||||
# the number of the move about to be made
|
||||
self.next_move_id = 1
|
||||
|
||||
# display the board
|
||||
self.board = TicTacToeBoard(self, self.rows, self.cols)
|
||||
|
||||
# accept the invitation, join the game
|
||||
response = xmpp.Message()
|
||||
|
||||
join = response.NT.join
|
||||
join.setNamespace(games_ns)
|
||||
|
||||
self.send(response)
|
||||
|
||||
if self.role_o == 'x':
|
||||
self.role_s = 'o'
|
||||
|
||||
self.their_turn()
|
||||
else:
|
||||
self.role_s = 'x'
|
||||
self.role_o = 'o'
|
||||
|
||||
self.our_turn()
|
||||
|
||||
# just sent an invitation, expecting a reply
|
||||
def wait_for_invite_response(self, msg):
|
||||
if msg.getTag('join', namespace=games_ns):
|
||||
self.board = TicTacToeBoard(self, self.rows, self.cols)
|
||||
|
||||
if self.role_s == 'x':
|
||||
self.our_turn()
|
||||
else:
|
||||
self.their_turn()
|
||||
|
||||
elif msg.getTag('decline', namespace=games_ns):
|
||||
# XXX notify the user
|
||||
|
||||
# XXX end session
|
||||
pass
|
||||
|
||||
# silently ignores any received messages
|
||||
def ignore(self, msg):
|
||||
pass
|
||||
|
||||
def game_over(self, msg):
|
||||
invite = msg.getTag('invite', namespace=games_ns)
|
||||
|
||||
# ignore messages unless they're renewing the game
|
||||
if invite and invite.getAttr('type') == 'renew':
|
||||
self.invited(msg)
|
||||
|
||||
def wait_for_move(self, msg):
|
||||
turn = msg.getTag('turn', namespace=games_ns)
|
||||
move = turn.getTag('move', namespace='http://jabber.org/protocol/games/tictactoe')
|
||||
|
||||
row = int(move.getAttr('row'))
|
||||
col = int(move.getAttr('col'))
|
||||
id = int(move.getAttr('id'))
|
||||
|
||||
if id != self.next_move_id:
|
||||
print 'unexpected move id, lost a move somewhere?'
|
||||
return
|
||||
|
||||
try:
|
||||
self.board.mark(row, col, self.role_o)
|
||||
except InvalidMove, e:
|
||||
# received an invalid move, end the game.
|
||||
|
||||
# XXX notify the user
|
||||
self.end_game('cheating')
|
||||
return
|
||||
|
||||
# check win conditions
|
||||
if self.board.check_for_strike(self.role_o, row, col, self.strike):
|
||||
self.lost()
|
||||
elif self.board.full():
|
||||
self.drawn()
|
||||
else:
|
||||
self.next_move_id += 1
|
||||
|
||||
self.our_turn()
|
||||
|
||||
def is_my_turn(self):
|
||||
# XXX not great semantics
|
||||
return self.received == self.ignore
|
||||
|
||||
def our_turn(self):
|
||||
# ignore messages until we've made our move
|
||||
self.received = self.ignore
|
||||
self.board.set_title('your turn')
|
||||
|
||||
def their_turn(self):
|
||||
self.received = self.wait_for_move
|
||||
self.board.set_title('their turn')
|
||||
|
||||
# called when the board receives input
|
||||
def move(self, row, col):
|
||||
try:
|
||||
self.board.mark(row, col, self.role_s)
|
||||
except InvalidMove, e:
|
||||
print 'you made an invalid move'
|
||||
return
|
||||
|
||||
self.send_move(row, col)
|
||||
|
||||
# check win conditions
|
||||
if self.board.check_for_strike(self.role_s, row, col, self.strike):
|
||||
self.won()
|
||||
elif self.board.full():
|
||||
self.drawn()
|
||||
else:
|
||||
self.next_move_id += 1
|
||||
|
||||
self.their_turn()
|
||||
|
||||
# sends a move message
|
||||
def send_move(self, row, column):
|
||||
msg = xmpp.Message()
|
||||
msg.setType('chat')
|
||||
|
||||
turn = msg.NT.turn
|
||||
turn.setNamespace(games_ns)
|
||||
|
||||
move = turn.NT.move
|
||||
move.setNamespace(games_ns+'/tictactoe')
|
||||
|
||||
move.setAttr('row', str(row))
|
||||
move.setAttr('col', str(column))
|
||||
move.setAttr('id', str(self.next_move_id))
|
||||
|
||||
self.send(msg)
|
||||
|
||||
# sends a termination message and ends the game
|
||||
def end_game(self, reason):
|
||||
msg = xmpp.Message()
|
||||
|
||||
terminate = msg.NT.terminate
|
||||
terminate.setNamespace(games_ns)
|
||||
terminate.setAttr('reason', reason)
|
||||
|
||||
self.send(msg)
|
||||
|
||||
self.received = self.game_over
|
||||
|
||||
def won(self):
|
||||
self.end_game('won')
|
||||
self.board.won()
|
||||
|
||||
def lost(self):
|
||||
self.end_game('lost')
|
||||
self.board.lost()
|
||||
|
||||
def drawn(self):
|
||||
self.end_game('draw')
|
||||
self.board.drawn()
|
||||
|
||||
class TicTacToeBoard:
|
||||
def __init__(self, session, rows, cols):
|
||||
self.session = session
|
||||
|
||||
self.state = 'None'
|
||||
|
||||
self.rows = rows
|
||||
self.cols = cols
|
||||
|
||||
self.board = [ [None] * self.cols for r in xrange(self.rows) ]
|
||||
|
||||
self.setup_window()
|
||||
|
||||
# check if the last move (at row r and column c) won the game
|
||||
def check_for_strike(self, p, r, c, strike):
|
||||
# number in a row: up and down, left and right
|
||||
tallyI = 0
|
||||
tally_ = 0
|
||||
|
||||
# number in a row: diagonal
|
||||
# (imagine L or F as two sides of a right triangle: L\ or F/)
|
||||
tallyL = 0
|
||||
tallyF = 0
|
||||
|
||||
# convert real columns to internal columns
|
||||
r -= 1
|
||||
c -= 1
|
||||
|
||||
for d in xrange(-strike, strike):
|
||||
r_in_range = 0 <= r+d < self.rows
|
||||
c_in_range = 0 <= c+d < self.cols
|
||||
|
||||
# vertical check
|
||||
if r_in_range:
|
||||
tallyI = tallyI + 1
|
||||
if self.board[r+d][c] != p:
|
||||
tallyI = 0
|
||||
|
||||
# horizontal check
|
||||
if c_in_range:
|
||||
tally_ = tally_ + 1
|
||||
if self.board[r][c+d] != p:
|
||||
tally_ = 0
|
||||
|
||||
# diagonal checks
|
||||
if r_in_range and c_in_range:
|
||||
tallyL = tallyL + 1
|
||||
if self.board[r+d][c+d] != p:
|
||||
tallyL = 0
|
||||
|
||||
if r_in_range and 0 <= c-d < self.cols:
|
||||
tallyF = tallyF + 1
|
||||
if self.board[r+d][c-d] != p:
|
||||
tallyF = 0
|
||||
|
||||
if any([t == strike for t in (tallyL, tallyF, tallyI, tally_)]):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
# is the board full?
|
||||
def full(self):
|
||||
for r in xrange(self.rows):
|
||||
for c in xrange(self.cols):
|
||||
if self.board[r][c] == None:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def setup_window(self):
|
||||
self.win = gtk.Window()
|
||||
|
||||
self.title_prefix = 'tic-tac-toe with %s' % self.session.jid
|
||||
self.set_title()
|
||||
|
||||
self.win.set_app_paintable(True)
|
||||
|
||||
self.win.add_events(gdk.BUTTON_PRESS_MASK)
|
||||
self.win.connect('button-press-event', self.clicked)
|
||||
self.win.connect('expose-event', self.expose)
|
||||
|
||||
self.win.show_all()
|
||||
|
||||
def clicked(self, widget, event):
|
||||
if not self.session.is_my_turn():
|
||||
return
|
||||
|
||||
(width, height) = widget.get_size()
|
||||
|
||||
# convert click co-ordinates to row and column
|
||||
|
||||
row_height = height // self.rows
|
||||
col_width = width // self.cols
|
||||
|
||||
row = int(event.y // row_height) + 1
|
||||
column = int(event.x // col_width) + 1
|
||||
|
||||
self.session.move(row, column)
|
||||
|
||||
# this actually draws the board
|
||||
def expose(self, widget, event):
|
||||
win = widget.window
|
||||
|
||||
cr = win.cairo_create()
|
||||
|
||||
cr.set_source_rgb(1.0, 1.0, 1.0)
|
||||
|
||||
cr.set_operator(cairo.OPERATOR_SOURCE)
|
||||
cr.paint()
|
||||
|
||||
(width, height) = widget.get_size()
|
||||
|
||||
row_height = height // self.rows
|
||||
col_width = width // self.cols
|
||||
|
||||
for i in xrange(self.rows):
|
||||
for j in xrange(self.cols):
|
||||
if self.board[i][j] == 'x':
|
||||
self.draw_x(cr, i, j, row_height, col_width)
|
||||
elif self.board[i][j] == 'o':
|
||||
self.draw_o(cr, i, j, row_height, col_width)
|
||||
|
||||
# XXX draw 'won', 'lost', 'draw'
|
||||
|
||||
def draw_x(self, cr, row, col, row_height, col_width):
|
||||
cr.set_source_rgb(0, 0, 0)
|
||||
|
||||
top = row_height * (row + 0.2)
|
||||
bottom = row_height * (row + 0.8)
|
||||
|
||||
left = col_width * (col + 0.2)
|
||||
right = col_width * (col + 0.8)
|
||||
|
||||
cr.set_line_width(row_height / 5)
|
||||
|
||||
cr.move_to(left, top)
|
||||
cr.line_to(right, bottom)
|
||||
|
||||
cr.move_to(right, top)
|
||||
cr.line_to(left, bottom)
|
||||
|
||||
cr.stroke()
|
||||
|
||||
def draw_o(self, cr, row, col, row_height, col_width):
|
||||
cr.set_source_rgb(0, 0, 0)
|
||||
|
||||
x = col_width * (col + 0.5)
|
||||
y = row_height * (row + 0.5)
|
||||
|
||||
cr.arc(x, y, row_height/4, 0, 2.0*3.2) # slightly further than 2*pi
|
||||
|
||||
cr.set_line_width(row_height / 5)
|
||||
cr.stroke()
|
||||
|
||||
# mark a move on the board
|
||||
def mark(self, row, column, player):
|
||||
if self.board[row-1][column-1]:
|
||||
raise InvalidMove
|
||||
else:
|
||||
self.board[row-1][column-1] = player
|
||||
|
||||
self.win.queue_draw()
|
||||
|
||||
def set_title(self, suffix = None):
|
||||
str = self.title_prefix
|
||||
|
||||
if suffix:
|
||||
str += ': ' + suffix
|
||||
|
||||
self.win.set_title(str)
|
||||
|
||||
def won(self):
|
||||
self.state = 'won'
|
||||
self.set_title('you won!')
|
||||
self.win.queue_draw()
|
||||
|
||||
def lost(self):
|
||||
self.state = 'lost'
|
||||
self.set_title('you lost.')
|
||||
self.win.queue_draw()
|
||||
|
||||
def drawn(self):
|
||||
self.state = 'drawn'
|
||||
self.win.set_title(self.title_prefix + ': a draw.')
|
||||
self.win.queue_draw()
|
68
test/mocks.py
Normal file
68
test/mocks.py
Normal file
|
@ -0,0 +1,68 @@
|
|||
# gajim-specific mock objects
|
||||
from mock import Mock
|
||||
|
||||
from common import gajim
|
||||
|
||||
class MockConnection(Mock):
|
||||
def __init__(self, name, *args):
|
||||
Mock.__init__(self, *args)
|
||||
self.name = name
|
||||
gajim.connections[name] = self
|
||||
|
||||
class MockWindow(Mock):
|
||||
def __init__(self, *args):
|
||||
Mock.__init__(self, *args)
|
||||
self.window = Mock()
|
||||
|
||||
class MockChatControl(Mock):
|
||||
def __init__(self, *args):
|
||||
Mock.__init__(self, *args)
|
||||
|
||||
self.parent_win = MockWindow({'get_active_control': self})
|
||||
self.session = None
|
||||
|
||||
def set_session(self, sess):
|
||||
self.session = sess
|
||||
|
||||
def __nonzero__(self):
|
||||
return True
|
||||
|
||||
def __eq__(self, other):
|
||||
return self is other
|
||||
|
||||
class MockInterface(Mock):
|
||||
def __init__(self, acct, *args):
|
||||
Mock.__init__(self, *args)
|
||||
self.msg_win_mgr = Mock()
|
||||
self.roster = Mock()
|
||||
|
||||
self.remote_ctrl = None
|
||||
self.minimized_controls = { acct: {} }
|
||||
|
||||
class MockLogger(Mock):
|
||||
def __init__(self):
|
||||
Mock.__init__(self, {'write': None})
|
||||
|
||||
class MockContact(Mock):
|
||||
def __nonzero__(self):
|
||||
return True
|
||||
|
||||
import random
|
||||
|
||||
class MockSession(Mock):
|
||||
def __init__(self, conn, jid, thread_id, type):
|
||||
Mock.__init__(self)
|
||||
|
||||
self.conn = conn
|
||||
self.jid = jid
|
||||
self.type = type
|
||||
self.thread_id = thread_id
|
||||
|
||||
if not self.thread_id:
|
||||
self.thread_id = '%0x' % random.randint(0, 10000)
|
||||
|
||||
def __repr__(self):
|
||||
print '<MockSession %s>' % self.thread_id
|
||||
|
||||
def __nonzero__(self):
|
||||
return True
|
|
@ -10,10 +10,6 @@ sys.path.append(gajim_root + '/src')
|
|||
# a temporary version of ~/.gajim for testing
|
||||
configdir = gajim_root + '/test/tmp'
|
||||
|
||||
import time
|
||||
|
||||
from mock import Mock
|
||||
|
||||
# define _ for i18n
|
||||
import __builtin__
|
||||
__builtin__._ = lambda x: x
|
||||
|
@ -30,10 +26,15 @@ import common.configpaths
|
|||
common.configpaths.gajimpaths.init(configdir)
|
||||
common.configpaths.gajimpaths.init_profile()
|
||||
|
||||
import time
|
||||
|
||||
# for some reason common.gajim needs to be imported before xmpppy?
|
||||
from common import gajim
|
||||
from common import xmpp
|
||||
|
||||
from mock import Mock, expectParams
|
||||
from mocks import *
|
||||
|
||||
gajim.DATA_DIR = gajim_root + '/data'
|
||||
|
||||
from common.stanza_session import StanzaSession
|
||||
|
@ -41,16 +42,10 @@ from common.stanza_session import StanzaSession
|
|||
# name to use for the test account
|
||||
account_name = 'test'
|
||||
|
||||
class MockConnection(Mock):
|
||||
def __init__(self, *args):
|
||||
Mock.__init__(self, *args)
|
||||
self.name = account_name
|
||||
gajim.connections[self.name] = self
|
||||
|
||||
class TestStanzaSession(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.jid = 'test@example.org/Gajim'
|
||||
self.conn = MockConnection({'send_stanza': None})
|
||||
self.conn = MockConnection(account_name, {'send_stanza': None})
|
||||
self.sess = StanzaSession(self.conn, self.jid, None, 'chat')
|
||||
|
||||
def test_generate_thread_id(self):
|
||||
|
@ -77,7 +72,7 @@ class TestStanzaSession(unittest.TestCase):
|
|||
|
||||
self.assertEqual(msg.getThread(), self.sess.thread_id)
|
||||
|
||||
def test_terminate_without_sendng(self):
|
||||
def test_terminate_without_sending(self):
|
||||
# no termination is sent if no messages have been sent in the session
|
||||
self.sess.terminate()
|
||||
|
||||
|
@ -100,45 +95,7 @@ class TestStanzaSession(unittest.TestCase):
|
|||
|
||||
from session import ChatControlSession
|
||||
|
||||
class MockWindow(Mock):
|
||||
def __init__(self, *args):
|
||||
Mock.__init__(self, *args)
|
||||
self.window = Mock()
|
||||
|
||||
class MockChatControl(Mock):
|
||||
def __init__(self, *args):
|
||||
Mock.__init__(self, *args)
|
||||
|
||||
self.parent_win = MockWindow({'get_active_control': self})
|
||||
self.session = None
|
||||
|
||||
def set_session(self, sess):
|
||||
self.session = sess
|
||||
|
||||
def __nonzero__(self):
|
||||
return True
|
||||
|
||||
def __eq__(self, other):
|
||||
return self is other
|
||||
|
||||
class MockInterface(Mock):
|
||||
def __init__(self, *args):
|
||||
Mock.__init__(self, *args)
|
||||
self.msg_win_mgr = Mock()
|
||||
self.roster = Mock()
|
||||
|
||||
self.remote_ctrl = None
|
||||
self.minimized_controls = { account_name: {} }
|
||||
|
||||
class MockLogger(Mock):
|
||||
def __init__(self):
|
||||
Mock.__init__(self, {'write': None})
|
||||
|
||||
class MockContact(Mock):
|
||||
def __nonzero__(self):
|
||||
return True
|
||||
|
||||
gajim.interface = MockInterface()
|
||||
gajim.interface = MockInterface(account_name)
|
||||
gajim.contacts.add_account(account_name)
|
||||
|
||||
import notify
|
||||
|
@ -146,7 +103,7 @@ import notify
|
|||
class TestChatControlSession(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.jid = 'test@example.org/Gajim'
|
||||
self.conn = MockConnection({'send_stanza': None})
|
||||
self.conn = MockConnection(account_name, {'send_stanza': None})
|
||||
self.sess = ChatControlSession(self.conn, self.jid, None)
|
||||
gajim.logger = MockLogger()
|
||||
|
||||
|
@ -217,6 +174,7 @@ class TestChatControlSession(unittest.TestCase):
|
|||
|
||||
def test_receive_already_has_control(self):
|
||||
'''test receiving a message with a session already attached to a control'''
|
||||
|
||||
jid = 'bct@necronomicorp.com/Gajim'
|
||||
msgtxt = 'testing one two three'
|
||||
|
||||
|
@ -240,13 +198,16 @@ class TestChatControlSession(unittest.TestCase):
|
|||
def test_received_orphaned_control(self):
|
||||
'''test receiving a message when a control that doesn't have a session attached exists'''
|
||||
|
||||
jid = 'bct@necronomicorp.com/Gajim'
|
||||
jid = 'bct@necronomicorp.com'
|
||||
fjid = jid + '/Gajim'
|
||||
msgtxt = 'testing one two three'
|
||||
|
||||
ctrl = MockChatControl()
|
||||
gajim.interface.msg_win_mgr = Mock({'get_sessionless_ctrl': ctrl})
|
||||
gajim.interface.msg_win_mgr.mockSetExpectation('get_sessionless_ctrl',
|
||||
expectParams(account_name, jid))
|
||||
|
||||
self.receive_chat_msg(jid, msgtxt)
|
||||
self.receive_chat_msg(fjid, msgtxt)
|
||||
|
||||
# message was logged
|
||||
calls = gajim.logger.mockGetNamedCalls('write')
|
||||
|
|
Loading…
Add table
Reference in a new issue