merge with default

This commit is contained in:
Yann Leboulanger 2010-03-10 13:52:54 +01:00
commit f0dde42f77
34 changed files with 4450 additions and 4775 deletions

10
AUTHORS
View File

@ -1,13 +1,8 @@
CURRENT DEVELOPERS: CURRENT DEVELOPERS:
Alexander Cherniuk (ts33kr AT gmail.com) Alexander Cherniuk (ts33kr AT gmail.com)
Nikos Kouremenos (kourem AT gmail.com)
Yann Leboulanger (asterix AT lagaule.org) Yann Leboulanger (asterix AT lagaule.org)
Julien Pivotto (roidelapluie AT gmail.com)
Jonathan Schleifer (js-gajim AT webkeks.org) Jonathan Schleifer (js-gajim AT webkeks.org)
Travis Shirk (travis AT pobox.com)
Brendan Taylor (whateley AT gmail.com)
Jean-Marie Traissard (jim AT lapin.org)
PAST DEVELOPERS: PAST DEVELOPERS:
@ -15,3 +10,8 @@ Stefan Bethge (stefan AT lanpartei.de)
Stephan Erb (steve-e AT h3c.de) Stephan Erb (steve-e AT h3c.de)
Vincent Hanquez (tab AT snarc.org) Vincent Hanquez (tab AT snarc.org)
Dimitur Kirov (dkirov AT gmail.com) Dimitur Kirov (dkirov AT gmail.com)
Nikos Kouremenos (kourem AT gmail.com)
Julien Pivotto (roidelapluie AT gmail.com)
Travis Shirk (travis AT pobox.com)
Brendan Taylor (whateley AT gmail.com)
Jean-Marie Traissard (jim AT lapin.org)

View File

@ -15,7 +15,7 @@
<child> <child>
<object class="GtkTable" id="table15"> <object class="GtkTable" id="table15">
<property name="visible">True</property> <property name="visible">True</property>
<property name="n_rows">7</property> <property name="n_rows">8</property>
<property name="n_columns">2</property> <property name="n_columns">2</property>
<property name="column_spacing">12</property> <property name="column_spacing">12</property>
<property name="row_spacing">6</property> <property name="row_spacing">6</property>
@ -110,8 +110,8 @@
<property name="label" translatable="yes">Password:</property> <property name="label" translatable="yes">Password:</property>
</object> </object>
<packing> <packing>
<property name="top_attach">4</property> <property name="top_attach">5</property>
<property name="bottom_attach">5</property> <property name="bottom_attach">6</property>
<property name="x_options">GTK_FILL</property> <property name="x_options">GTK_FILL</property>
<property name="y_options"></property> <property name="y_options"></property>
</packing> </packing>
@ -126,14 +126,14 @@
<packing> <packing>
<property name="left_attach">1</property> <property name="left_attach">1</property>
<property name="right_attach">2</property> <property name="right_attach">2</property>
<property name="top_attach">4</property> <property name="top_attach">5</property>
<property name="bottom_attach">5</property> <property name="bottom_attach">6</property>
<property name="y_options"></property> <property name="y_options"></property>
</packing> </packing>
</child> </child>
<child> <child>
<object class="GtkCheckButton" id="auto_join_checkbutton"> <object class="GtkCheckButton" id="auto_join_checkbutton">
<property name="label" translatable="yes">Join this room automatically when I connect</property> <property name="label" translatable="yes">Join this room _automatically when I connect</property>
<property name="visible">True</property> <property name="visible">True</property>
<property name="sensitive">False</property> <property name="sensitive">False</property>
<property name="can_focus">True</property> <property name="can_focus">True</property>
@ -143,8 +143,8 @@
</object> </object>
<packing> <packing>
<property name="right_attach">2</property> <property name="right_attach">2</property>
<property name="top_attach">6</property> <property name="top_attach">7</property>
<property name="bottom_attach">7</property> <property name="bottom_attach">8</property>
<property name="x_options">GTK_FILL</property> <property name="x_options">GTK_FILL</property>
<property name="y_options"></property> <property name="y_options"></property>
</packing> </packing>
@ -174,21 +174,69 @@
</child> </child>
<child> <child>
<object class="GtkCheckButton" id="bookmark_checkbutton"> <object class="GtkCheckButton" id="bookmark_checkbutton">
<property name="label" translatable="yes">Bookmark this room</property> <property name="label" translatable="yes">_Bookmark this room</property>
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">True</property> <property name="can_focus">True</property>
<property name="receives_default">False</property> <property name="receives_default">False</property>
<property name="use_underline">True</property>
<property name="draw_indicator">True</property> <property name="draw_indicator">True</property>
<signal name="toggled" handler="on_bookmark_checkbutton_toggled"/> <signal name="toggled" handler="on_bookmark_checkbutton_toggled"/>
</object> </object>
<packing> <packing>
<property name="right_attach">2</property> <property name="right_attach">2</property>
<property name="top_attach">5</property> <property name="top_attach">6</property>
<property name="bottom_attach">6</property> <property name="bottom_attach">7</property>
<property name="x_options">GTK_FILL</property> <property name="x_options">GTK_FILL</property>
<property name="y_options"></property> <property name="y_options"></property>
</packing> </packing>
</child> </child>
<child>
<object class="GtkLabel" id="label1">
<property name="visible">True</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">Server:</property>
</object>
<packing>
<property name="top_attach">4</property>
<property name="bottom_attach">5</property>
</packing>
</child>
<child>
<object class="GtkHBox" id="hbox1">
<property name="visible">True</property>
<property name="spacing">6</property>
<child>
<object class="GtkComboBoxEntry" id="server_comboboxentry">
<property name="visible">True</property>
<property name="model">liststore1</property>
<property name="text_column">0</property>
</object>
<packing>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="browse_rooms_button">
<property name="label" translatable="yes">Bro_wse Rooms</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="image">image1</property>
<property name="use_underline">True</property>
<signal name="clicked" handler="on_browse_rooms_button_clicked"/>
</object>
<packing>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">4</property>
<property name="bottom_attach">5</property>
</packing>
</child>
</object> </object>
<packing> <packing>
<property name="position">0</property> <property name="position">0</property>
@ -275,4 +323,14 @@
</object> </object>
</child> </child>
</object> </object>
<object class="GtkListStore" id="liststore1">
<columns>
<!-- column-name gchararray1 -->
<column type="gchararray"/>
</columns>
</object>
<object class="GtkImage" id="image1">
<property name="visible">True</property>
<property name="stock">gtk-find</property>
</object>
</interface> </interface>

View File

@ -14,6 +14,7 @@
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">True</property> <property name="can_focus">True</property>
<property name="scrollable">True</property> <property name="scrollable">True</property>
<property name="tab_border">0</property>
</object> </object>
</child> </child>
</object> </object>
@ -59,7 +60,6 @@
<child> <child>
<object class="GtkImage" id="image1329"> <object class="GtkImage" id="image1329">
<property name="visible">True</property> <property name="visible">True</property>
<property name="ypad">6</property>
<property name="stock">gtk-close</property> <property name="stock">gtk-close</property>
<property name="icon-size">1</property> <property name="icon-size">1</property>
</object> </object>

View File

@ -4,7 +4,7 @@
<!-- interface-naming-policy toplevel-contextual --> <!-- interface-naming-policy toplevel-contextual -->
<object class="GtkListStore" id="liststore1"> <object class="GtkListStore" id="liststore1">
<columns> <columns>
<!-- column-name item text --> <!-- column-name item -->
<column type="gchararray"/> <column type="gchararray"/>
</columns> </columns>
</object> </object>
@ -71,7 +71,6 @@ Agent JID - node</property>
<property name="visible">True</property> <property name="visible">True</property>
<property name="model">liststore1</property> <property name="model">liststore1</property>
<signal name="changed" handler="on_address_comboboxentry_changed"/> <signal name="changed" handler="on_address_comboboxentry_changed"/>
<signal name="key_press_event" handler="on_address_comboboxentry_key_press_event"/>
<child> <child>
<object class="GtkCellRendererText" id="cellrenderertext1"/> <object class="GtkCellRendererText" id="cellrenderertext1"/>
<attributes> <attributes>

8000
po/sk.po

File diff suppressed because it is too large Load Diff

View File

@ -14,7 +14,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
""" """
The command system providing scalable, clean and convenient architecture in The command system providing scalable, clean and convenient architecture
combination with declarative way of defining commands and a fair amount of in combination with declarative way of defining commands and a fair
automatization for routine processes. amount of automatization for routine processes.
""" """

View File

@ -14,9 +14,10 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
""" """
The backbone of the command system. Provides automatic dispatching which does The backbone of the command system. Provides automatic dispatching which
not require explicit registering commands or containers and remains active even does not require explicit registering commands or containers and remains
after everything is done, so new commands can be added during the runtime. active even after everything is done, so new commands can be added
during the runtime.
""" """
from types import NoneType from types import NoneType

View File

@ -15,8 +15,9 @@
class BaseError(Exception): class BaseError(Exception):
""" """
Common base for errors which relate to a specific command. Encapsulates Common base for errors which relate to a specific command.
everything needed to identify a command, by either its object or name. Encapsulates everything needed to identify a command, by either its
object or name.
""" """
def __init__(self, message, command=None, name=None): def __init__(self, message, command=None, name=None):

View File

@ -14,8 +14,9 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
""" """
Provides a tiny framework with simple, yet powerful and extensible architecture Provides a tiny framework with simple, yet powerful and extensible
to implement commands in a streight and flexible, declarative way. architecture to implement commands in a streight and flexible,
declarative way.
""" """
import re import re
@ -28,42 +29,43 @@ from errors import DefinitionError, CommandError
class CommandHost(object): class CommandHost(object):
""" """
Command host is a hub between numerous command processors and command Command host is a hub between numerous command processors and
containers. Aimed to participate in a dispatching process in order to command containers. Aimed to participate in a dispatching process in
provide clean and transparent architecture. order to provide clean and transparent architecture.
""" """
__metaclass__ = HostDispatcher __metaclass__ = HostDispatcher
class CommandContainer(object): class CommandContainer(object):
""" """
Command container is an entity which holds defined commands, allowing them Command container is an entity which holds defined commands,
to be dispatched and proccessed correctly. Each command container may be allowing them to be dispatched and proccessed correctly. Each
bound to a one or more command hosts. command container may be bound to a one or more command hosts.
Bounding is controlled by the HOSTS variable, which must be defined in the Bounding is controlled by the HOSTS variable, which must be defined
body of the command container. This variable should contain a list of hosts in the body of the command container. This variable should contain a
to bound to, as a tuple or list. list of hosts to bound to, as a tuple or list.
""" """
__metaclass__ = ContainerDispatcher __metaclass__ = ContainerDispatcher
class CommandProcessor(object): class CommandProcessor(object):
""" """
Command processor is an immediate command emitter. It does not participate Command processor is an immediate command emitter. It does not
in the dispatching process directly, but must define a host to bound to. participate in the dispatching process directly, but must define a
host to bound to.
Bounding is controlled by the COMMAND_HOST variable, which must be defined Bounding is controlled by the COMMAND_HOST variable, which must be
in the body of the command processor. This variable should be set to a defined in the body of the command processor. This variable should
specific command host. be set to a specific command host.
""" """
# This defines a command prefix (or an initializer), which should preceede a # This defines a command prefix (or an initializer), which should
# a text in order it to be processed as a command. # preceede a a text in order it to be processed as a command.
COMMAND_PREFIX = '/' COMMAND_PREFIX = '/'
def process_as_command(self, text): def process_as_command(self, text):
""" """
Try to process text as a command. Returns True if it has been processed Try to process text as a command. Returns True if it has been
as a command and False otherwise. processed as a command and False otherwise.
""" """
prefix = text.startswith(self.COMMAND_PREFIX) prefix = text.startswith(self.COMMAND_PREFIX)
length = len(text) > len(self.COMMAND_PREFIX) length = len(text) > len(self.COMMAND_PREFIX)
@ -97,28 +99,28 @@ class CommandProcessor(object):
def command_preprocessor(self, command, name, arguments, args, kwargs): def command_preprocessor(self, command, name, arguments, args, kwargs):
""" """
Redefine this method in the subclass to execute custom code before Redefine this method in the subclass to execute custom code
command gets executed. before command gets executed.
If returns True then command execution will be interrupted and command If returns True then command execution will be interrupted and
will not be executed. command will not be executed.
""" """
pass pass
def command_postprocessor(self, command, name, arguments, args, kwargs, value): def command_postprocessor(self, command, name, arguments, args, kwargs, value):
""" """
Redefine this method in the subclass to execute custom code after Redefine this method in the subclass to execute custom code
command gets executed. after command gets executed.
""" """
pass pass
def looks_like_command(self, text, body, name, arguments): def looks_like_command(self, text, body, name, arguments):
""" """
This hook is being called before any processing, but after it was This hook is being called before any processing, but after it
determined that text looks like a command. was determined that text looks like a command.
If returns value other then None - then further processing will be If returns value other then None - then further processing will
interrupted and that value will be used to return from be interrupted and that value will be used to return from
process_as_command. process_as_command.
""" """
pass pass
@ -136,8 +138,9 @@ class CommandProcessor(object):
class Command(object): class Command(object):
# These two regular expression patterns control how command documentation # These two regular expression patterns control how command
# will be formatted to be transformed to a normal, readable state. # documentation will be formatted to be transformed to a normal,
# readable state.
DOC_STRIP_PATTERN = re.compile(r'(?:^[ \t]+|\A\n)', re.MULTILINE) DOC_STRIP_PATTERN = re.compile(r'(?:^[ \t]+|\A\n)', re.MULTILINE)
DOC_FORMAT_PATTERN = re.compile(r'(?<!\n)\n(?!\n)', re.MULTILINE) DOC_FORMAT_PATTERN = re.compile(r'(?<!\n)\n(?!\n)', re.MULTILINE)
@ -145,8 +148,8 @@ class Command(object):
self.handler = handler self.handler = handler
self.names = names self.names = names
# Automatically set all the properties passed to a constructor by the # Automatically set all the properties passed to a constructor
# command decorator. # by the command decorator.
for key, value in properties.iteritems(): for key, value in properties.iteritems():
setattr(self, key, value) setattr(self, key, value)
@ -154,18 +157,20 @@ class Command(object):
try: try:
return self.handler(*args, **kwargs) return self.handler(*args, **kwargs)
# This allows to use a shortcuted way of raising an exception inside a # This allows to use a shortcuted way of raising an exception
# handler. That is to raise a CommandError without command or name # inside a handler. That is to raise a CommandError without
# attributes set. They will be set to a corresponding values right here # command or name attributes set. They will be set to a
# in case if they was not set by the one who raised an exception. # corresponding values right here in case if they was not set by
# the one who raised an exception.
except CommandError, error: except CommandError, error:
if not error.command and not error.name: if not error.command and not error.name:
raise CommandError(error.message, self) raise CommandError(error.message, self)
raise raise
# This one is a little bit too wide, but as Python does not have # This one is a little bit too wide, but as Python does not have
# anything more constrained - there is no other choice. Take a look here # anything more constrained - there is no other choice. Take a
# if command complains about invalid arguments while they are ok. # look here if command complains about invalid arguments while
# they are ok.
except TypeError: except TypeError:
raise CommandError("Command received invalid arguments", self) raise CommandError("Command received invalid arguments", self)
@ -185,8 +190,8 @@ class Command(object):
def extract_documentation(self): def extract_documentation(self):
""" """
Extract handler's documentation which is a doc-string and transform it Extract handler's documentation which is a doc-string and
to a usable format. transform it to a usable format.
Transformation is done based on the DOC_STRIP_PATTERN and Transformation is done based on the DOC_STRIP_PATTERN and
DOC_FORMAT_PATTERN regular expression patterns. DOC_FORMAT_PATTERN regular expression patterns.
@ -211,19 +216,19 @@ class Command(object):
def extract_specification(self): def extract_specification(self):
""" """
Extract handler's arguments specification, as it was defined preserving Extract handler's arguments specification, as it was defined
their order. preserving their order.
""" """
names, var_args, var_kwargs, defaults = getargspec(self.handler) names, var_args, var_kwargs, defaults = getargspec(self.handler)
# Behavior of this code need to be checked. Might yield incorrect # Behavior of this code need to be checked. Might yield
# results on some rare occasions. # incorrect results on some rare occasions.
spec_args = names[:-len(defaults) if defaults else len(names)] spec_args = names[:-len(defaults) if defaults else len(names)]
spec_kwargs = list(zip(names[-len(defaults):], defaults)) if defaults else {} spec_kwargs = list(zip(names[-len(defaults):], defaults)) if defaults else {}
# Removing self from arguments specification. Command handler should # Removing self from arguments specification. Command handler
# receive the processors as a first argument, which should be self by # should receive the processors as a first argument, which
# the canonical means. # should be self by the canonical means.
if spec_args.pop(0) != 'self': if spec_args.pop(0) != 'self':
raise DefinitionError("First argument must be self", self) raise DefinitionError("First argument must be self", self)
@ -231,44 +236,47 @@ class Command(object):
def command(*names, **properties): def command(*names, **properties):
""" """
A decorator for defining commands in a declarative way. Provides facilities A decorator for defining commands in a declarative way. Provides
for setting command's names and properties. facilities for setting command's names and properties.
Names should contain a set of names (aliases) by which the command can be Names should contain a set of names (aliases) by which the command
reached. If no names are given - the the native name (the one extracted from can be reached. If no names are given - the the native name (the one
the command handler) will be used. extracted from the command handler) will be used.
If include_native=True is given (default) and names is non-empty - then the If include_native=True is given (default) and names is non-empty -
native name of the command will be prepended in addition to the given names. then the native name of the command will be prepended in addition to
the given names.
If usage=True is given (default) - then command help will be appended with If usage=True is given (default) - then command help will be
autogenerated usage info, based of the command handler arguments appended with autogenerated usage info, based of the command handler
introspection. arguments introspection.
If source=True is given - then the first argument of the command will If source=True is given - then the first argument of the command
receive the source arguments, as a raw, unprocessed string. The further will receive the source arguments, as a raw, unprocessed string. The
mapping of arguments and options will not be affected. further mapping of arguments and options will not be affected.
If raw=True is given - then command considered to be raw and should define If raw=True is given - then command considered to be raw and should
positional arguments only. If it defines only one positional argument - this define positional arguments only. If it defines only one positional
argument will receive all the raw and unprocessed arguments. If the command argument - this argument will receive all the raw and unprocessed
defines more then one positional argument - then all the arguments except arguments. If the command defines more then one positional argument
the last one will be processed normally; the last argument will get what is - then all the arguments except the last one will be processed
left after the processing as raw and unprocessed string. normally; the last argument will get what is left after the
processing as raw and unprocessed string.
If empty=True is given - this will allow to call a raw command without If empty=True is given - this will allow to call a raw command
arguments. without arguments.
If extra=True is given - then all the extra arguments passed to a command If extra=True is given - then all the extra arguments passed to a
will be collected into a sequence and given to the last positional argument. command will be collected into a sequence and given to the last
positional argument.
If overlap=True is given - then all the extra arguments will be mapped as if If overlap=True is given - then all the extra arguments will be
they were values for the keyword arguments. mapped as if they were values for the keyword arguments.
If expand_short=True is given (default) - then short, one-letter options If expand_short=True is given (default) - then short, one-letter
will be expanded to a verbose ones, based of the comparison of the first options will be expanded to a verbose ones, based of the comparison
letter. If more then one option with the same first letter is given - then of the first letter. If more then one option with the same first
only first one will be used in the expansion. letter is given - then only first one will be used in the expansion.
""" """
names = list(names) names = list(names)
@ -300,22 +308,23 @@ def command(*names, **properties):
def decorator(handler): def decorator(handler):
""" """
Decorator which receives handler as a first argument and then wraps it Decorator which receives handler as a first argument and then
in the command which then returns back. wraps it in the command which then returns back.
""" """
command = Command(handler, *names, **properties) command = Command(handler, *names, **properties)
# Extract and inject a native name if either no other names are # Extract and inject a native name if either no other names are
# specified or include_native property is enabled, while making sure it # specified or include_native property is enabled, while making
# is going to be the first one in the list. # sure it is going to be the first one in the list.
if not names or include_native: if not names or include_native:
names.insert(0, command.native_name) names.insert(0, command.native_name)
command.names = tuple(names) command.names = tuple(names)
return command return command
# Workaround if we are getting called without parameters. Keep in mind that # Workaround if we are getting called without parameters. Keep in
# in that case - first item in the names will be the handler. # mind that in that case - first item in the names will be the
# handler.
if names and isinstance(names[0], FunctionType): if names and isinstance(names[0], FunctionType):
return decorator(names.pop(0)) return decorator(names.pop(0))
@ -323,11 +332,8 @@ def command(*names, **properties):
def documentation(text): def documentation(text):
""" """
This decorator is used to bind a documentation (a help) to a command. This decorator is used to bind a documentation (a help) to a
command.
Though this can be done easily by using doc-strings in a declarative and
Pythonic way - some of Gajim's developers are against it because of the
scaffolding needed to support the tranlation of such documentation.
""" """
def decorator(target): def decorator(target):
if isinstance(target, Command): if isinstance(target, Command):

View File

@ -14,6 +14,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
""" """
The implementation and auxilary systems which implement the standard Gajim The implementation and auxilary systems which implement the standard
commands and also provide an infrastructure for adding custom commands. Gajim commands and also provide an infrastructure for adding custom
commands.
""" """

View File

@ -14,11 +14,11 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
""" """
The module contains examples of how to create your own commands, by creating a The module contains examples of how to create your own commands, by
new command container and definding a set of commands. creating a new command container and definding a set of commands.
Keep in mind that this module is not being loaded, so the code will not be Keep in mind that this module is not being loaded, so the code will not
executed and commands defined here will not be detected. be executed and commands defined here will not be detected.
""" """
from ..framework import CommandContainer, command, documentation from ..framework import CommandContainer, command, documentation
@ -27,8 +27,9 @@ from hosts import ChatCommands, PrivateChatCommands, GroupChatCommands
class CustomCommonCommands(CommandContainer): class CustomCommonCommands(CommandContainer):
""" """
This command container bounds to all three available in the default This command container bounds to all three available in the default
implementation command hosts. This means that commands defined in this implementation command hosts. This means that commands defined in
container will be available to all - chat, private chat and a group chat. this container will be available to all - chat, private chat and a
group chat.
""" """
HOSTS = (ChatCommands, PrivateChatCommands, GroupChatCommands) HOSTS = (ChatCommands, PrivateChatCommands, GroupChatCommands)
@ -39,12 +40,13 @@ class CustomCommonCommands(CommandContainer):
First line of the doc string is called a description and will be First line of the doc string is called a description and will be
programmatically extracted and formatted. programmatically extracted and formatted.
After that you can give more help, like explanation of the options. This After that you can give more help, like explanation of the
one will be programatically extracted and formatted too. options. This one will be programatically extracted and
formatted too.
After all the documentation - there will be autogenerated (based on the After all the documentation - there will be autogenerated (based
method signature) usage information appended. You can turn it off on the method signature) usage information appended. You can
though, if you want. turn it off though, if you want.
""" """
return "I can't dance, you stupid fuck, I'm just a command system! A cool one, though..." return "I can't dance, you stupid fuck, I'm just a command system! A cool one, though..."
@ -63,8 +65,9 @@ class CustomChatCommands(CommandContainer):
class CustomPrivateChatCommands(CommandContainer): class CustomPrivateChatCommands(CommandContainer):
""" """
This command container bounds only to the PrivateChatCommands command host. This command container bounds only to the PrivateChatCommands
Therefore command defined here will be available only to a private chat. command host. Therefore command defined here will be available only
to a private chat.
""" """
HOSTS = (PrivateChatCommands,) HOSTS = (PrivateChatCommands,)
@ -75,8 +78,9 @@ class CustomPrivateChatCommands(CommandContainer):
class CustomGroupChatCommands(CommandContainer): class CustomGroupChatCommands(CommandContainer):
""" """
This command container bounds only to the GroupChatCommands command host. This command container bounds only to the GroupChatCommands command
Therefore command defined here will be available only to a group chat. host. Therefore command defined here will be available only to a
group chat.
""" """
HOSTS = (GroupChatCommands,) HOSTS = (GroupChatCommands,)

View File

@ -14,29 +14,29 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
""" """
The module defines a set of command hosts, which are bound to a different The module defines a set of command hosts, which are bound to a
command processors, which are the source of commands. different command processors, which are the source of commands.
""" """
from ..framework import CommandHost from ..framework import CommandHost
class ChatCommands(CommandHost): class ChatCommands(CommandHost):
""" """
This command host is bound to the command processor which processes commands This command host is bound to the command processor which processes
from a chat. commands from a chat.
""" """
pass pass
class PrivateChatCommands(CommandHost): class PrivateChatCommands(CommandHost):
""" """
This command host is bound to the command processor which processes commands This command host is bound to the command processor which processes
from a private chat. commands from a private chat.
""" """
pass pass
class GroupChatCommands(CommandHost): class GroupChatCommands(CommandHost):
""" """
This command host is bound to the command processor which processes commands This command host is bound to the command processor which processes
from a group chat. commands from a group chat.
""" """
pass pass

View File

@ -14,10 +14,10 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
""" """
Provides a glue to tie command system framework and the actual code where it Provides a glue to tie command system framework and the actual code
would be dropped in. Defines a little bit of scaffolding to support interaction where it would be dropped in. Defines a little bit of scaffolding to
between the two and a few utility methods so you don't need to dig up the code support interaction between the two and a few utility methods so you
itself code to write basic commands. don't need to dig up the code itself code to write basic commands.
""" """
from types import StringTypes from types import StringTypes
@ -30,8 +30,8 @@ from ..errors import CommandError
class ChatCommandProcessor(CommandProcessor): class ChatCommandProcessor(CommandProcessor):
""" """
A basic scaffolding to provide convenient interaction between the command A basic scaffolding to provide convenient interaction between the
system and chat controls. command system and chat controls.
""" """
def process_as_command(self, text): def process_as_command(self, text):
@ -76,8 +76,8 @@ class ChatCommandProcessor(CommandProcessor):
class CommandTools: class CommandTools:
""" """
Contains a set of basic tools and shortcuts you can use in your commands to Contains a set of basic tools and shortcuts you can use in your
performe some simple operations. commands to performe some simple operations.
""" """
def echo(self, text, kind='info'): def echo(self, text, kind='info'):
@ -107,8 +107,8 @@ class CommandTools:
def add_history(self, text): def add_history(self, text):
""" """
Add given text to the input history, so user can scroll through it using Add given text to the input history, so user can scroll through
ctrl + up/down arrow keys. it using ctrl + up/down arrow keys.
""" """
self.save_sent_message(text) self.save_sent_message(text)

View File

@ -38,8 +38,8 @@ lc = Constants()
class StandardCommonCommands(CommandContainer): class StandardCommonCommands(CommandContainer):
""" """
This command container contains standard commands which are common to all - This command container contains standard commands which are common
chat, private chat, group chat. to all - chat, private chat, group chat.
""" """
HOSTS = (ChatCommands, PrivateChatCommands, GroupChatCommands) HOSTS = (ChatCommands, PrivateChatCommands, GroupChatCommands)
@ -159,7 +159,8 @@ class StandardCommonCommands(CommandContainer):
class StandardChatCommands(CommandContainer): class StandardChatCommands(CommandContainer):
""" """
This command container contains standard command which are unique to a chat. This command container contains standard command which are unique to
a chat.
""" """
HOSTS = (ChatCommands,) HOSTS = (ChatCommands,)
@ -211,16 +212,16 @@ class StandardChatCommands(CommandContainer):
class StandardPrivateChatCommands(CommandContainer): class StandardPrivateChatCommands(CommandContainer):
""" """
This command container contains standard command which are unique to a This command container contains standard command which are unique to
private chat. a private chat.
""" """
HOSTS = (PrivateChatCommands,) HOSTS = (PrivateChatCommands,)
class StandardGroupchatCommands(CommandContainer): class StandardGroupchatCommands(CommandContainer):
""" """
This command container contains standard command which are unique to a group This command container contains standard command which are unique to
chat. a group chat.
""" """
HOSTS = (GroupChatCommands,) HOSTS = (GroupChatCommands,)

View File

@ -14,17 +14,16 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
""" """
The module contains routines to parse command arguments and map them to the The module contains routines to parse command arguments and map them to
command handler's positonal and keyword arguments. the command handler's positonal and keyword arguments.
Mapping is done in two stages: 1) parse arguments into positional arguments and Mapping is done in two stages: 1) parse arguments into positional
options; 2) adapt them to the specific command handler according to the command arguments and options; 2) adapt them to the specific command handler
properties. according to the command properties.
""" """
import re import re
from types import BooleanType, UnicodeType from types import BooleanType, UnicodeType
from types import TupleType, ListType
from operator import itemgetter from operator import itemgetter
from errors import DefinitionError, CommandError from errors import DefinitionError, CommandError
@ -34,28 +33,32 @@ from errors import DefinitionError, CommandError
ARG_PATTERN = re.compile(r'(\'|")?(?P<body>(?(1).+?|\S+))(?(1)\1)') ARG_PATTERN = re.compile(r'(\'|")?(?P<body>(?(1).+?|\S+))(?(1)\1)')
OPT_PATTERN = re.compile(r'(?<!\w)--?(?P<key>[\w-]+)(?:(?:=|\s)(\'|")?(?P<value>(?(2)[^-]+?|[^-\s]+))(?(2)\2))?') OPT_PATTERN = re.compile(r'(?<!\w)--?(?P<key>[\w-]+)(?:(?:=|\s)(\'|")?(?P<value>(?(2)[^-]+?|[^-\s]+))(?(2)\2))?')
# Option keys needs to be encoded to a specific encoding as Python does not # Option keys needs to be encoded to a specific encoding as Python does
# allow to expand dictionary with raw unicode strings as keys from a **kwargs. # not allow to expand dictionary with raw unicode strings as keys from a
# **kwargs.
KEY_ENCODING = 'UTF-8' KEY_ENCODING = 'UTF-8'
# Defines how complete representation of command usage (generated based on # Defines how complete representation of command usage (generated based
# command handler argument specification) will be rendered. # on command handler argument specification) will be rendered.
USAGE_PATTERN = 'Usage: %s %s' USAGE_PATTERN = 'Usage: %s %s'
def parse_arguments(arguments): def parse_arguments(arguments):
""" """
Simple yet effective and sufficient in most cases parser which parses Simple yet effective and sufficient in most cases parser which
command arguments and returns them as two lists. parses command arguments and returns them as two lists.
First list represents positional arguments as (argument, position), and First list represents positional arguments as (argument, position),
second representing options as (key, value, position) tuples, where position and second representing options as (key, value, position) tuples,
is a (start, end) span tuple of where it was found in the string. where position is a (start, end) span tuple of where it was found in
the string.
Options may be given in --long or -short format. As --option=value or Options may be given in --long or -short format. As --option=value
--option value or -option value. Keys without values will get None as value. or --option value or -option value. Keys without values will get
None as value.
Arguments and option values that contain spaces may be given as 'one two Arguments and option values that contain spaces may be given as 'one
three' or "one two three"; that is between single or double quotes. two three' or "one two three"; that is between single or double
quotes.
""" """
args, opts = [], [] args, opts = [], []
@ -90,14 +93,16 @@ def parse_arguments(arguments):
position = match.span() position = match.span()
args.append((body, position)) args.append((body, position))
# Primitive but sufficiently effective way of disposing of conflicted # Primitive but sufficiently effective way of disposing of
# sectors. Remove any arguments that intersect with options. # conflicted sectors. Remove any arguments that intersect with
# options.
for arg, position in args[:]: for arg, position in args[:]:
if intersects_opts(position): if intersects_opts(position):
args.remove((arg, position)) args.remove((arg, position))
# Primitive but sufficiently effective way of disposing of conflicted # Primitive but sufficiently effective way of disposing of
# sectors. Remove any options that intersect with arguments. # conflicted sectors. Remove any options that intersect with
# arguments.
for key, value, position in opts[:]: for key, value, position in opts[:]:
if intersects_args(position): if intersects_args(position):
opts.remove((key, value, position)) opts.remove((key, value, position))
@ -106,41 +111,40 @@ def parse_arguments(arguments):
def adapt_arguments(command, arguments, args, opts): def adapt_arguments(command, arguments, args, opts):
""" """
Adapt args and opts got from the parser to a specific handler by means of Adapt args and opts got from the parser to a specific handler by
arguments specified on command definition. That is transform them to *args means of arguments specified on command definition. That is
and **kwargs suitable for passing to a command handler. transform them to *args and **kwargs suitable for passing to a
command handler.
Dashes (-) in the option names will be converted to underscores. So you can Dashes (-) in the option names will be converted to underscores. So
map --one-more-option to a one_more_option=None. you can map --one-more-option to a one_more_option=None.
If the initial value of a keyword argument is a boolean (False in most If the initial value of a keyword argument is a boolean (False in
cases) - then this option will be treated as a switch, that is an option most cases) - then this option will be treated as a switch, that is
which does not take an argument. If a switch is followed by an argument - an option which does not take an argument. If a switch is followed
then this argument will be treated just like a normal positional argument. by an argument - then this argument will be treated just like a
normal positional argument.
If the initial value of a keyword argument is a sequence, that is a tuple or
list - then a value of this option will be considered correct only if it is
present in the sequence.
""" """
spec_args, spec_kwargs, var_args, var_kwargs = command.extract_specification() spec_args, spec_kwargs, var_args, var_kwargs = command.extract_specification()
norm_kwargs = dict(spec_kwargs) norm_kwargs = dict(spec_kwargs)
# Quite complex piece of neck-breaking logic to extract raw arguments if # Quite complex piece of neck-breaking logic to extract raw
# there is more, then one positional argument specified by the command. In # arguments if there is more, then one positional argument specified
# case if it's just one argument which is the collector - this is fairly # by the command. In case if it's just one argument which is the
# easy. But when it's more then one argument - the neck-breaking logic of # collector - this is fairly easy. But when it's more then one
# how to retrieve residual arguments as a raw, all in one piece string, # argument - the neck-breaking logic of how to retrieve residual
# kicks in. # arguments as a raw, all in one piece string, kicks in.
if command.raw: if command.raw:
if arguments: if arguments:
spec_fix = 1 if command.source else 0 spec_fix = 1 if command.source else 0
spec_len = len(spec_args) - spec_fix spec_len = len(spec_args) - spec_fix
arguments_end = len(arguments) - 1 arguments_end = len(arguments) - 1
# If there are any optional arguments given they should be either an # If there are any optional arguments given they should be
# unquoted postional argument or part of the raw argument. So we # either an unquoted postional argument or part of the raw
# find all optional arguments that can possibly be unquoted argument # argument. So we find all optional arguments that can
# and append them as is to the args. # possibly be unquoted argument and append them as is to the
# args.
for key, value, (start, end) in opts[:spec_len]: for key, value, (start, end) in opts[:spec_len]:
if value: if value:
end -= len(value) + 1 end -= len(value) + 1
@ -149,9 +153,9 @@ def adapt_arguments(command, arguments, args, opts):
else: else:
args.append((arguments[start:end], (start, end))) args.append((arguments[start:end], (start, end)))
# We need in-place sort here because after manipulations with # We need in-place sort here because after manipulations
# options order of arguments might be wrong and we just can't have # with options order of arguments might be wrong and we just
# more complex logic to not let that happen. # can't have more complex logic to not let that happen.
args.sort(key=itemgetter(1)) args.sort(key=itemgetter(1))
if spec_len > 1: if spec_len > 1:
@ -160,27 +164,28 @@ def adapt_arguments(command, arguments, args, opts):
except IndexError: except IndexError:
raise CommandError("Missing arguments", command) raise CommandError("Missing arguments", command)
# The essential point of the whole play. After boundaries are # The essential point of the whole play. After
# being determined (supposingly correct) we separate raw part # boundaries are being determined (supposingly correct)
# from the rest of arguments, which should be normally # we separate raw part from the rest of arguments, which
# processed. # should be normally processed.
raw = arguments[end:] raw = arguments[end:]
raw = raw.strip() or None raw = raw.strip() or None
if not raw and not command.empty: if not raw and not command.empty:
raise CommandError("Missing arguments", command) raise CommandError("Missing arguments", command)
# Discard residual arguments and all of the options as raw # Discard residual arguments and all of the options as
# command does not support options and if an option is given it # raw command does not support options and if an option
# is rather a part of a raw argument. # is given it is rather a part of a raw argument.
args = args[:spec_len - 1] args = args[:spec_len - 1]
opts = [] opts = []
args.append((raw, (end, arguments_end))) args.append((raw, (end, arguments_end)))
else: else:
# Substitue all of the arguments with only one, which contain # Substitue all of the arguments with only one, which
# raw and unprocessed arguments as a string. And discard all the # contain raw and unprocessed arguments as a string. And
# options, as raw command does not support them. # discard all the options, as raw command does not
# support them.
args = [(arguments, (0, arguments_end))] args = [(arguments, (0, arguments_end))]
opts = [] opts = []
else: else:
@ -189,16 +194,17 @@ def adapt_arguments(command, arguments, args, opts):
else: else:
raise CommandError("Missing arguments", command) raise CommandError("Missing arguments", command)
# The first stage of transforming options we have got to a format that can # The first stage of transforming options we have got to a format
# be used to associate them with declared keyword arguments. Substituting # that can be used to associate them with declared keyword
# dashes (-) in their names with underscores (_). # arguments. Substituting dashes (-) in their names with
# underscores (_).
for index, (key, value, position) in enumerate(opts): for index, (key, value, position) in enumerate(opts):
if '-' in key: if '-' in key:
opts[index] = (key.replace('-', '_'), value, position) opts[index] = (key.replace('-', '_'), value, position)
# The second stage of transforming options to an associatable state. # The second stage of transforming options to an associatable state.
# Expanding short, one-letter options to a verbose ones, if corresponding # Expanding short, one-letter options to a verbose ones, if
# optin has been given. # corresponding optin has been given.
if command.expand_short: if command.expand_short:
expanded = [] expanded = []
for spec_key, spec_value in norm_kwargs.iteritems(): for spec_key, spec_value in norm_kwargs.iteritems():
@ -210,26 +216,27 @@ def adapt_arguments(command, arguments, args, opts):
opts[index] = (spec_key, value, position) opts[index] = (spec_key, value, position)
break break
# Detect switches and set their values accordingly. If any of them carries a # Detect switches and set their values accordingly. If any of them
# value - append it to args. # carries a value - append it to args.
for index, (key, value, position) in enumerate(opts): for index, (key, value, position) in enumerate(opts):
if isinstance(norm_kwargs.get(key), BooleanType): if isinstance(norm_kwargs.get(key), BooleanType):
opts[index] = (key, True, position) opts[index] = (key, True, position)
if value: if value:
args.append((value, position)) args.append((value, position))
# Sorting arguments and options (just to be sure) in regarding to their # Sorting arguments and options (just to be sure) in regarding to
# positions in the string. # their positions in the string.
args.sort(key=itemgetter(1)) args.sort(key=itemgetter(1))
opts.sort(key=itemgetter(2)) opts.sort(key=itemgetter(2))
# Stripping down position information supplied with arguments and options as # Stripping down position information supplied with arguments and
# it won't be needed again. # options as it won't be needed again.
args = map(lambda (arg, position): arg, args) args = map(lambda (arg, position): arg, args)
opts = map(lambda (key, value, position): (key, value), opts) opts = map(lambda (key, value, position): (key, value), opts)
# If command has extra option enabled - collect all extra arguments and pass # If command has extra option enabled - collect all extra arguments
# them to a last positional argument command defines as a list. # and pass them to a last positional argument command defines as a
# list.
if command.extra: if command.extra:
if not var_args: if not var_args:
spec_fix = 1 if not command.source else 2 spec_fix = 1 if not command.source else 2
@ -240,9 +247,9 @@ def adapt_arguments(command, arguments, args, opts):
else: else:
raise DefinitionError("Can not have both, extra and *args") raise DefinitionError("Can not have both, extra and *args")
# Detect if positional arguments overlap keyword arguments. If so and this # Detect if positional arguments overlap keyword arguments. If so
# is allowed by command options - then map them directly to their options, # and this is allowed by command options - then map them directly to
# so they can get propert further processings. # their options, so they can get propert further processings.
spec_fix = 1 if command.source else 0 spec_fix = 1 if command.source else 0
spec_len = len(spec_args) - spec_fix spec_len = len(spec_args) - spec_fix
if len(args) > spec_len: if len(args) > spec_len:
@ -262,49 +269,32 @@ def adapt_arguments(command, arguments, args, opts):
if not isinstance(value, BooleanType): if not isinstance(value, BooleanType):
raise CommandError("%s: Switch can not take an argument" % key, command) raise CommandError("%s: Switch can not take an argument" % key, command)
# Detect every sequence constraint and ensure that if corresponding options # We need to encode every keyword argument to a simple string, not
# are given - they contain proper values, within the constraint range. # the unicode one, because ** expansion does not support it.
for key, value in opts:
initial = norm_kwargs.get(key)
if isinstance(initial, (TupleType, ListType)):
if value not in initial:
raise CommandError("%s: Invalid argument" % key, command)
# If argument to an option constrained by a sequence was not given - then
# it's value should be set to None.
for spec_key, spec_value in spec_kwargs:
if isinstance(spec_value, (TupleType, ListType)):
for key, value in opts:
if spec_key == key:
break
else:
opts.append((spec_key, None))
# We need to encode every keyword argument to a simple string, not the
# unicode one, because ** expansion does not support it.
for index, (key, value) in enumerate(opts): for index, (key, value) in enumerate(opts):
if isinstance(key, UnicodeType): if isinstance(key, UnicodeType):
opts[index] = (key.encode(KEY_ENCODING), value) opts[index] = (key.encode(KEY_ENCODING), value)
# Inject the source arguments as a string as a first argument, if command # Inject the source arguments as a string as a first argument, if
# has enabled the corresponding option. # command has enabled the corresponding option.
if command.source: if command.source:
args.insert(0, arguments) args.insert(0, arguments)
# Return *args and **kwargs in the form suitable for passing to a command # Return *args and **kwargs in the form suitable for passing to a
# handler and being expanded. # command handler and being expanded.
return tuple(args), dict(opts) return tuple(args), dict(opts)
def generate_usage(command, complete=True): def generate_usage(command, complete=True):
""" """
Extract handler's arguments specification and wrap them in a human-readable Extract handler's arguments specification and wrap them in a
format usage information. If complete is given - then USAGE_PATTERN will be human-readable format usage information. If complete is given - then
used to render the specification completly. USAGE_PATTERN will be used to render the specification completly.
""" """
spec_args, spec_kwargs, var_args, var_kwargs = command.extract_specification() spec_args, spec_kwargs, var_args, var_kwargs = command.extract_specification()
# Remove some special positional arguments from the specifiaction, but store # Remove some special positional arguments from the specifiaction,
# their names so they can be used for usage info generation. # but store their names so they can be used for usage info
# generation.
sp_source = spec_args.pop(0) if command.source else None sp_source = spec_args.pop(0) if command.source else None
sp_extra = spec_args.pop() if command.extra else None sp_extra = spec_args.pop() if command.extra else None
@ -317,8 +307,6 @@ def generate_usage(command, complete=True):
if isinstance(value, BooleanType): if isinstance(value, BooleanType):
value = str() value = str()
elif isinstance(value, (TupleType, ListType)):
value = '={%s}' % ', '.join(value)
else: else:
value = '=%s' % value value = '=%s' % value
@ -350,8 +338,8 @@ def generate_usage(command, complete=True):
if var_kwargs: if var_kwargs:
usage += (' ' if args else str()) + '[[%s]]' % var_kwargs usage += (' ' if args else str()) + '[[%s]]' % var_kwargs
# Native name will be the first one if it is included. Otherwise, names will # Native name will be the first one if it is included. Otherwise,
# be in the order they were specified. # names will be in the order they were specified.
if len(command.names) > 1: if len(command.names) > 1:
names = '%s (%s)' % (command.first_name, ', '.join(command.names[1:])) names = '%s (%s)' % (command.first_name, ', '.join(command.names[1:]))
else: else:

View File

@ -129,7 +129,9 @@ def split_db():
print 'spliting database' print 'spliting database'
if os.name == 'nt': if os.name == 'nt':
try: try:
OLD_LOG_DB_FOLDER = os.path.join(fse(os.environ[u'appdata']), u'Gajim') import configpaths
OLD_LOG_DB_FOLDER = os.path.join(configpaths.fse(
os.environ[u'appdata']), u'Gajim')
except KeyError: except KeyError:
OLD_LOG_DB_FOLDER = u'.' OLD_LOG_DB_FOLDER = u'.'
else: else:
@ -184,7 +186,8 @@ def check_and_possibly_move_config():
if os.name == 'nt': if os.name == 'nt':
try: try:
OLD_LOG_DB_FOLDER = os.path.join(fse(os.environ[u'appdata']), u'Gajim') OLD_LOG_DB_FOLDER = os.path.join(configpaths.fse(
os.environ[u'appdata']), u'Gajim')
except KeyError: except KeyError:
OLD_LOG_DB_FOLDER = u'.' OLD_LOG_DB_FOLDER = u'.'
else: else:

View File

@ -430,13 +430,15 @@ class CommonConnection:
try: try:
gajim.logger.write(kind, jid, log_msg) gajim.logger.write(kind, jid, log_msg)
except exceptions.PysqliteOperationalError, e: except exceptions.PysqliteOperationalError, e:
self.dispatch('ERROR', (_('Disk Write Error'), str(e))) self.dispatch('DB_ERROR', (_('Disk Write Error'),
str(e)))
except exceptions.DatabaseMalformed: except exceptions.DatabaseMalformed:
pritext = _('Database Error') pritext = _('Database Error')
sectext = _('The database file (%s) cannot be read. Try to ' sectext = _('The database file (%s) cannot be read. Try to '
'repair it (see http://trac.gajim.org/wiki/DatabaseBackup)' 'repair it (see http://trac.gajim.org/wiki/DatabaseBackup)'
' or remove it (all history will be lost).') % \ ' or remove it (all history will be lost).') % \
common.logger.LOG_DB_PATH common.logger.LOG_DB_PATH
self.dispatch('DB_ERROR', (pritext, sectext))
def ack_subscribed(self, jid): def ack_subscribed(self, jid):
""" """

View File

@ -49,7 +49,7 @@ from common.commands import ConnectionCommands
from common.pubsub import ConnectionPubSub from common.pubsub import ConnectionPubSub
from common.pep import ConnectionPEP from common.pep import ConnectionPEP
from common.protocol.caps import ConnectionCaps from common.protocol.caps import ConnectionCaps
from common.protocol.bytestream import ConnectionBytestream from common.protocol.bytestream import ConnectionSocks5Bytestream
import common.caps_cache as capscache import common.caps_cache as capscache
if gajim.HAVE_FARSIGHT: if gajim.HAVE_FARSIGHT:
from common.jingle import ConnectionJingle from common.jingle import ConnectionJingle
@ -915,13 +915,13 @@ class ConnectionHandlersBase:
return sess return sess
class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, class ConnectionHandlers(ConnectionVcard, ConnectionSocks5Bytestream,
ConnectionDisco, ConnectionCommands, ConnectionPubSub, ConnectionPEP, ConnectionDisco, ConnectionCommands, ConnectionPubSub, ConnectionPEP,
ConnectionCaps, ConnectionHandlersBase, ConnectionJingle): ConnectionCaps, ConnectionHandlersBase, ConnectionJingle):
def __init__(self): def __init__(self):
global HAS_IDLE global HAS_IDLE
ConnectionVcard.__init__(self) ConnectionVcard.__init__(self)
ConnectionBytestream.__init__(self) ConnectionSocks5Bytestream.__init__(self)
ConnectionCommands.__init__(self) ConnectionCommands.__init__(self)
ConnectionPubSub.__init__(self) ConnectionPubSub.__init__(self)
ConnectionPEP.__init__(self, account=self.name, dispatcher=self, ConnectionPEP.__init__(self, account=self.name, dispatcher=self,
@ -1566,13 +1566,13 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream,
gajim.logger.write('error', frm, error_msg, tim=tim, gajim.logger.write('error', frm, error_msg, tim=tim,
subject=subject) subject=subject)
except exceptions.PysqliteOperationalError, e: except exceptions.PysqliteOperationalError, e:
self.dispatch('ERROR', (_('Disk Write Error'), str(e))) self.dispatch('DB_ERROR', (_('Disk Write Error'), str(e)))
except exceptions.DatabaseMalformed: except exceptions.DatabaseMalformed:
pritext = _('Database Error') pritext = _('Database Error')
sectext = _('The database file (%s) cannot be read. Try to repair ' sectext = _('The database file (%s) cannot be read. Try to repair '
'it (see http://trac.gajim.org/wiki/DatabaseBackup) or remove ' 'it (see http://trac.gajim.org/wiki/DatabaseBackup) or remove '
'it (all history will be lost).') % common.logger.LOG_DB_PATH 'it (all history will be lost).') % common.logger.LOG_DB_PATH
self.dispatch('ERROR', (pritext, sectext)) self.dispatch('DB_ERROR', (pritext, sectext))
self.dispatch('MSGERROR', (frm, msg.getErrorCode(), error_msg, msgtxt, self.dispatch('MSGERROR', (frm, msg.getErrorCode(), error_msg, msgtxt,
tim, session)) tim, session))
@ -1617,13 +1617,13 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream,
self.last_history_time[jid] = mktime(tim) self.last_history_time[jid] = mktime(tim)
except exceptions.PysqliteOperationalError, e: except exceptions.PysqliteOperationalError, e:
self.dispatch('ERROR', (_('Disk Write Error'), str(e))) self.dispatch('DB_ERROR', (_('Disk Write Error'), str(e)))
except exceptions.DatabaseMalformed: except exceptions.DatabaseMalformed:
pritext = _('Database Error') pritext = _('Database Error')
sectext = _('The database file (%s) cannot be read. Try to repair ' sectext = _('The database file (%s) cannot be read. Try to repair '
'it (see http://trac.gajim.org/wiki/DatabaseBackup) or remove ' 'it (see http://trac.gajim.org/wiki/DatabaseBackup) or remove '
'it (all history will be lost).') % common.logger.LOG_DB_PATH 'it (all history will be lost).') % common.logger.LOG_DB_PATH
self.dispatch('ERROR', (pritext, sectext)) self.dispatch('DB_ERROR', (pritext, sectext))
def dispatch_invite_message(self, invite, frm): def dispatch_invite_message(self, invite, frm):
item = invite.getTag('invite') item = invite.getTag('invite')
@ -1811,14 +1811,15 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream,
try: try:
gajim.logger.write('gcstatus', who, st, show) gajim.logger.write('gcstatus', who, st, show)
except exceptions.PysqliteOperationalError, e: except exceptions.PysqliteOperationalError, e:
self.dispatch('ERROR', (_('Disk Write Error'), str(e))) self.dispatch('DB_ERROR', (_('Disk Write Error'),
str(e)))
except exceptions.DatabaseMalformed: except exceptions.DatabaseMalformed:
pritext = _('Database Error') pritext = _('Database Error')
sectext = _('The database file (%s) cannot be read. Try to ' sectext = _('The database file (%s) cannot be read. Try to '
'repair it (see http://trac.gajim.org/wiki/DatabaseBackup)' 'repair it (see http://trac.gajim.org/wiki/DatabaseBackup)'
' or remove it (all history will be lost).') % \ ' or remove it (all history will be lost).') % \
common.logger.LOG_DB_PATH common.logger.LOG_DB_PATH
self.dispatch('ERROR', (pritext, sectext)) self.dispatch('DB_ERROR', (pritext, sectext))
if avatar_sha or avatar_sha == '': if avatar_sha or avatar_sha == '':
if avatar_sha == '': if avatar_sha == '':
# contact has no avatar # contact has no avatar
@ -1961,14 +1962,14 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream,
try: try:
gajim.logger.write('status', jid_stripped, status, show) gajim.logger.write('status', jid_stripped, status, show)
except exceptions.PysqliteOperationalError, e: except exceptions.PysqliteOperationalError, e:
self.dispatch('ERROR', (_('Disk Write Error'), str(e))) self.dispatch('DB_ERROR', (_('Disk Write Error'), str(e)))
except exceptions.DatabaseMalformed: except exceptions.DatabaseMalformed:
pritext = _('Database Error') pritext = _('Database Error')
sectext = _('The database file (%s) cannot be read. Try to ' sectext = _('The database file (%s) cannot be read. Try to '
'repair it (see http://trac.gajim.org/wiki/DatabaseBackup) ' 'repair it (see http://trac.gajim.org/wiki/DatabaseBackup) '
'or remove it (all history will be lost).') % \ 'or remove it (all history will be lost).') % \
common.logger.LOG_DB_PATH common.logger.LOG_DB_PATH
self.dispatch('ERROR', (pritext, sectext)) self.dispatch('DB_ERROR', (pritext, sectext))
our_jid = gajim.get_jid_from_account(self.name) our_jid = gajim.get_jid_from_account(self.name)
if jid_stripped == our_jid and resource == self.server_resource: if jid_stripped == our_jid and resource == self.server_resource:
# We got our own presence # We got our own presence

View File

@ -496,11 +496,8 @@ class Contacts():
return self._contacts.keys() return self._contacts.keys()
def get_contacts_jid_list(self): def get_contacts_jid_list(self):
contacts = self._contacts.keys() return [jid for jid, contact in self._contacts.iteritems() if not
for jid in self._contacts.keys(): contact[0].is_groupchat()]
if self._contacts[jid][0].is_groupchat():
contacts.remove(jid)
return contacts
def get_contact_from_full_jid(self, fjid): def get_contact_from_full_jid(self, fjid):
""" """

View File

@ -42,6 +42,7 @@ except ImportError:
else: else:
try: try:
# test if dbus-x11 is installed # test if dbus-x11 is installed
bus = dbus.SystemBus()
bus = dbus.SessionBus() bus = dbus.SessionBus()
supported = True # does user have D-Bus bindings? supported = True # does user have D-Bus bindings?
except dbus.DBusException: except dbus.DBusException:
@ -49,6 +50,12 @@ else:
if not os.name == 'nt': # only say that to non Windows users if not os.name == 'nt': # only say that to non Windows users
print _('D-Bus does not run correctly on this machine') print _('D-Bus does not run correctly on this machine')
print _('D-Bus capabilities of Gajim cannot be used') print _('D-Bus capabilities of Gajim cannot be used')
except exceptions.SystemBusNotPresent:
print _('D-Bus does not run correctly on this machine: system bus not '
'present')
except exceptions.SessionBusNotPresent:
print _('D-Bus does not run correctly on this machine: session bus not '
'present')
class SystemBus: class SystemBus:
""" """

View File

@ -29,6 +29,7 @@
## along with Gajim. If not, see <http://www.gnu.org/licenses/>. ## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
## ##
import sys
import re import re
import locale import locale
import os import os

View File

@ -704,7 +704,9 @@ class OptionsParser:
""" """
Remove hardcoded ../data/sounds from config Remove hardcoded ../data/sounds from config
""" """
dirs = ('../data', gajim.gajimpaths.root, gajim.DATA_DIR) dirs = ['../data', gajim.gajimpaths.data_root, gajim.DATA_DIR]
if os.name != 'nt':
dirs.append(os.path.expanduser(u'~/.gajim'))
for evt in gajim.config.get_per('soundevents'): for evt in gajim.config.get_per('soundevents'):
path = gajim.config.get_per('soundevents', evt, 'path') path = gajim.config.get_per('soundevents', evt, 'path')
# absolute and relative passes are necessary # absolute and relative passes are necessary

View File

@ -75,6 +75,190 @@ class ConnectionBytestream:
def __init__(self): def __init__(self):
self.files_props = {} self.files_props = {}
def _ft_get_our_jid(self):
our_jid = gajim.get_jid_from_account(self.name)
resource = self.server_resource
return our_jid + '/' + resource
def _ft_get_receiver_jid(self, file_props):
return file_props['receiver'].jid + '/' + file_props['receiver'].resource
def _ft_get_from(self, iq_obj):
return helpers.get_full_jid_from_iq(iq_obj)
def _ft_get_streamhost_jid_attr(self, streamhost):
return helpers.parse_jid(streamhost.getAttr('jid'))
def send_file_request(self, file_props):
"""
Send iq for new FT request
"""
if not self.connection or self.connected < 2:
return
file_props['sender'] = self._ft_get_our_jid()
fjid = self._ft_get_receiver_jid(file_props)
iq = xmpp.Iq(to=fjid, typ='set')
iq.setID(file_props['sid'])
self.files_props[file_props['sid']] = file_props
si = iq.setTag('si', namespace=xmpp.NS_SI)
si.setAttr('profile', xmpp.NS_FILE)
si.setAttr('id', file_props['sid'])
file_tag = si.setTag('file', namespace=xmpp.NS_FILE)
file_tag.setAttr('name', file_props['name'])
file_tag.setAttr('size', file_props['size'])
desc = file_tag.setTag('desc')
if 'desc' in file_props:
desc.setData(file_props['desc'])
file_tag.setTag('range')
feature = si.setTag('feature', namespace=xmpp.NS_FEATURE)
_feature = xmpp.DataForm(typ='form')
feature.addChild(node=_feature)
field = _feature.setField('stream-method')
field.setAttr('type', 'list-single')
field.addOption(xmpp.NS_BYTESTREAM)
self.connection.send(iq)
def send_file_approval(self, file_props):
"""
Send iq, confirming that we want to download the file
"""
# user response to ConfirmationDialog may come after we've disconneted
if not self.connection or self.connected < 2:
return
iq = xmpp.Iq(to=unicode(file_props['sender']), typ='result')
iq.setAttr('id', file_props['request-id'])
si = iq.setTag('si', namespace=xmpp.NS_SI)
if 'offset' in file_props and file_props['offset']:
file_tag = si.setTag('file', namespace=xmpp.NS_FILE)
range_tag = file_tag.setTag('range')
range_tag.setAttr('offset', file_props['offset'])
feature = si.setTag('feature', namespace=xmpp.NS_FEATURE)
_feature = xmpp.DataForm(typ='submit')
feature.addChild(node=_feature)
field = _feature.setField('stream-method')
field.delAttr('type')
field.setValue(xmpp.NS_BYTESTREAM)
self.connection.send(iq)
def send_file_rejection(self, file_props, code='403', typ=None):
"""
Inform sender that we refuse to download the file
typ is used when code = '400', in this case typ can be 'strean' for
invalid stream or 'profile' for invalid profile
"""
# user response to ConfirmationDialog may come after we've disconneted
if not self.connection or self.connected < 2:
return
iq = xmpp.Iq(to=unicode(file_props['sender']), typ='error')
iq.setAttr('id', file_props['request-id'])
if code == '400' and typ in ('stream', 'profile'):
name = 'bad-request'
text = ''
else:
name = 'forbidden'
text = 'Offer Declined'
err = xmpp.ErrorNode(code=code, typ='cancel', name=name, text=text)
if code == '400' and typ in ('stream', 'profile'):
if typ == 'stream':
err.setTag('no-valid-streams', namespace=xmpp.NS_SI)
else:
err.setTag('bad-profile', namespace=xmpp.NS_SI)
iq.addChild(node=err)
self.connection.send(iq)
def _siResultCB(self, con, iq_obj):
file_props = self.files_props.get(iq_obj.getAttr('id'))
if not file_props:
return
if 'request-id' in file_props:
# we have already sent streamhosts info
return
file_props['receiver'] = self._ft_get_from(iq_obj)
si = iq_obj.getTag('si')
file_tag = si.getTag('file')
range_tag = None
if file_tag:
range_tag = file_tag.getTag('range')
if range_tag:
offset = range_tag.getAttr('offset')
if offset:
file_props['offset'] = int(offset)
length = range_tag.getAttr('length')
if length:
file_props['length'] = int(length)
feature = si.setTag('feature')
if feature.getNamespace() != xmpp.NS_FEATURE:
return
form_tag = feature.getTag('x')
form = xmpp.DataForm(node=form_tag)
field = form.getField('stream-method')
if field.getValue() != xmpp.NS_BYTESTREAM:
return
self._send_socks5_info(file_props)
raise xmpp.NodeProcessed
def _siSetCB(self, con, iq_obj):
jid = self._ft_get_from(iq_obj)
file_props = {'type': 'r'}
file_props['sender'] = jid
file_props['request-id'] = unicode(iq_obj.getAttr('id'))
si = iq_obj.getTag('si')
profile = si.getAttr('profile')
mime_type = si.getAttr('mime-type')
if profile != xmpp.NS_FILE:
self.send_file_rejection(file_props, code='400', typ='profile')
raise xmpp.NodeProcessed
feature_tag = si.getTag('feature', namespace=xmpp.NS_FEATURE)
if not feature_tag:
return
form_tag = feature_tag.getTag('x', namespace=xmpp.NS_DATA)
if not form_tag:
return
form = dataforms.ExtendForm(node=form_tag)
for f in form.iter_fields():
if f.var == 'stream-method' and f.type == 'list-single':
values = [o[1] for o in f.options]
if xmpp.NS_BYTESTREAM in values:
break
else:
self.send_file_rejection(file_props, code='400', typ='stream')
raise xmpp.NodeProcessed
file_tag = si.getTag('file')
for attribute in file_tag.getAttrs():
if attribute in ('name', 'size', 'hash', 'date'):
val = file_tag.getAttr(attribute)
if val is None:
continue
file_props[attribute] = val
file_desc_tag = file_tag.getTag('desc')
if file_desc_tag is not None:
file_props['desc'] = file_desc_tag.getData()
if mime_type is not None:
file_props['mime-type'] = mime_type
file_props['receiver'] = self._ft_get_our_jid()
file_props['sid'] = unicode(si.getAttr('id'))
file_props['transfered_size'] = []
gajim.socks5queue.add_file_props(self.name, file_props)
self.dispatch('FILE_REQUEST', (jid, file_props))
raise xmpp.NodeProcessed
def _siErrorCB(self, con, iq_obj):
si = iq_obj.getTag('si')
profile = si.getAttr('profile')
if profile != xmpp.NS_FILE:
return
file_props = self.files_props.get(iq_obj.getAttr('id'))
if not file_props:
return
jid = self._ft_get_from(iq_obj)
file_props['error'] = -3
self.dispatch('FILE_REQUEST_ERROR', (jid, file_props, ''))
raise xmpp.NodeProcessed
class ConnectionSocks5Bytestream(ConnectionBytestream):
def send_success_connect_reply(self, streamhost): def send_success_connect_reply(self, streamhost):
""" """
Send reply to the initiator of FT that we made a connection Send reply to the initiator of FT that we made a connection
@ -252,92 +436,6 @@ class ConnectionBytestream:
else: else:
return [] return []
def send_file_rejection(self, file_props, code='403', typ=None):
"""
Inform sender that we refuse to download the file
typ is used when code = '400', in this case typ can be 'strean' for
invalid stream or 'profile' for invalid profile
"""
# user response to ConfirmationDialog may come after we've disconneted
if not self.connection or self.connected < 2:
return
iq = xmpp.Iq(to=unicode(file_props['sender']), typ='error')
iq.setAttr('id', file_props['request-id'])
if code == '400' and typ in ('stream', 'profile'):
name = 'bad-request'
text = ''
else:
name = 'forbidden'
text = 'Offer Declined'
err = xmpp.ErrorNode(code=code, typ='cancel', name=name, text=text)
if code == '400' and typ in ('stream', 'profile'):
if typ == 'stream':
err.setTag('no-valid-streams', namespace=xmpp.NS_SI)
else:
err.setTag('bad-profile', namespace=xmpp.NS_SI)
iq.addChild(node=err)
self.connection.send(iq)
def send_file_approval(self, file_props):
"""
Send iq, confirming that we want to download the file
"""
# user response to ConfirmationDialog may come after we've disconneted
if not self.connection or self.connected < 2:
return
iq = xmpp.Iq(to=unicode(file_props['sender']), typ='result')
iq.setAttr('id', file_props['request-id'])
si = iq.setTag('si', namespace=xmpp.NS_SI)
if 'offset' in file_props and file_props['offset']:
file_tag = si.setTag('file', namespace=xmpp.NS_FILE)
range_tag = file_tag.setTag('range')
range_tag.setAttr('offset', file_props['offset'])
feature = si.setTag('feature', namespace=xmpp.NS_FEATURE)
_feature = xmpp.DataForm(typ='submit')
feature.addChild(node=_feature)
field = _feature.setField('stream-method')
field.delAttr('type')
field.setValue(xmpp.NS_BYTESTREAM)
self.connection.send(iq)
def _ft_get_our_jid(self):
our_jid = gajim.get_jid_from_account(self.name)
resource = self.server_resource
return our_jid + '/' + resource
def _ft_get_receiver_jid(self, file_props):
return file_props['receiver'].jid + '/' + file_props['receiver'].resource
def send_file_request(self, file_props):
"""
Send iq for new FT request
"""
if not self.connection or self.connected < 2:
return
file_props['sender'] = self._ft_get_our_jid()
fjid = self._ft_get_receiver_jid(file_props)
iq = xmpp.Iq(to=fjid, typ='set')
iq.setID(file_props['sid'])
self.files_props[file_props['sid']] = file_props
si = iq.setTag('si', namespace=xmpp.NS_SI)
si.setAttr('profile', xmpp.NS_FILE)
si.setAttr('id', file_props['sid'])
file_tag = si.setTag('file', namespace=xmpp.NS_FILE)
file_tag.setAttr('name', file_props['name'])
file_tag.setAttr('size', file_props['size'])
desc = file_tag.setTag('desc')
if 'desc' in file_props:
desc.setData(file_props['desc'])
file_tag.setTag('range')
feature = si.setTag('feature', namespace=xmpp.NS_FEATURE)
_feature = xmpp.DataForm(typ='form')
feature.addChild(node=_feature)
field = _feature.setField('stream-method')
field.setAttr('type', 'list-single')
field.addOption(xmpp.NS_BYTESTREAM)
self.connection.send(iq)
def _result_socks5_sid(self, sid, hash_id): def _result_socks5_sid(self, sid, hash_id):
""" """
Store the result of SHA message from auth Store the result of SHA message from auth
@ -406,9 +504,6 @@ class ConnectionBytestream:
self.dispatch('FILE_REQUEST_ERROR', (jid, file_props, '')) self.dispatch('FILE_REQUEST_ERROR', (jid, file_props, ''))
raise xmpp.NodeProcessed raise xmpp.NodeProcessed
def _ft_get_from(self, iq_obj):
return helpers.get_full_jid_from_iq(iq_obj)
def _bytestreamSetCB(self, con, iq_obj): def _bytestreamSetCB(self, con, iq_obj):
target = unicode(iq_obj.getAttr('to')) target = unicode(iq_obj.getAttr('to'))
id_ = unicode(iq_obj.getAttr('id')) id_ = unicode(iq_obj.getAttr('id'))
@ -466,9 +561,6 @@ class ConnectionBytestream:
gajim.socks5queue.activate_proxy(host['idx']) gajim.socks5queue.activate_proxy(host['idx'])
raise xmpp.NodeProcessed raise xmpp.NodeProcessed
def _ft_get_streamhost_jid_attr(self, streamhost):
return helpers.parse_jid(streamhost.getAttr('jid'))
def _bytestreamResultCB(self, con, iq_obj): def _bytestreamResultCB(self, con, iq_obj):
frm = self._ft_get_from(iq_obj) frm = self._ft_get_from(iq_obj)
real_id = unicode(iq_obj.getAttr('id')) real_id = unicode(iq_obj.getAttr('id'))
@ -542,98 +634,7 @@ class ConnectionBytestream:
raise xmpp.NodeProcessed raise xmpp.NodeProcessed
def _siResultCB(self, con, iq_obj): class ConnectionSocks5BytestreamZeroconf(ConnectionSocks5Bytestream):
file_props = self.files_props.get(iq_obj.getAttr('id'))
if not file_props:
return
if 'request-id' in file_props:
# we have already sent streamhosts info
return
file_props['receiver'] = self._ft_get_from(iq_obj)
si = iq_obj.getTag('si')
file_tag = si.getTag('file')
range_tag = None
if file_tag:
range_tag = file_tag.getTag('range')
if range_tag:
offset = range_tag.getAttr('offset')
if offset:
file_props['offset'] = int(offset)
length = range_tag.getAttr('length')
if length:
file_props['length'] = int(length)
feature = si.setTag('feature')
if feature.getNamespace() != xmpp.NS_FEATURE:
return
form_tag = feature.getTag('x')
form = xmpp.DataForm(node=form_tag)
field = form.getField('stream-method')
if field.getValue() != xmpp.NS_BYTESTREAM:
return
self._send_socks5_info(file_props)
raise xmpp.NodeProcessed
def _siSetCB(self, con, iq_obj):
jid = self._ft_get_from(iq_obj)
file_props = {'type': 'r'}
file_props['sender'] = jid
file_props['request-id'] = unicode(iq_obj.getAttr('id'))
si = iq_obj.getTag('si')
profile = si.getAttr('profile')
mime_type = si.getAttr('mime-type')
if profile != xmpp.NS_FILE:
self.send_file_rejection(file_props, code='400', typ='profile')
raise xmpp.NodeProcessed
feature_tag = si.getTag('feature', namespace=xmpp.NS_FEATURE)
if not feature_tag:
return
form_tag = feature_tag.getTag('x', namespace=xmpp.NS_DATA)
if not form_tag:
return
form = dataforms.ExtendForm(node=form_tag)
for f in form.iter_fields():
if f.var == 'stream-method' and f.type == 'list-single':
values = [o[1] for o in f.options]
if xmpp.NS_BYTESTREAM in values:
break
else:
self.send_file_rejection(file_props, code='400', typ='stream')
raise xmpp.NodeProcessed
file_tag = si.getTag('file')
for attribute in file_tag.getAttrs():
if attribute in ('name', 'size', 'hash', 'date'):
val = file_tag.getAttr(attribute)
if val is None:
continue
file_props[attribute] = val
file_desc_tag = file_tag.getTag('desc')
if file_desc_tag is not None:
file_props['desc'] = file_desc_tag.getData()
if mime_type is not None:
file_props['mime-type'] = mime_type
file_props['receiver'] = self._ft_get_our_jid()
file_props['sid'] = unicode(si.getAttr('id'))
file_props['transfered_size'] = []
gajim.socks5queue.add_file_props(self.name, file_props)
self.dispatch('FILE_REQUEST', (jid, file_props))
raise xmpp.NodeProcessed
def _siErrorCB(self, con, iq_obj):
si = iq_obj.getTag('si')
profile = si.getAttr('profile')
if profile != xmpp.NS_FILE:
return
file_props = self.files_props.get(iq_obj.getAttr('id'))
if not file_props:
return
jid = self._ft_get_from(iq_obj)
file_props['error'] = -3
self.dispatch('FILE_REQUEST_ERROR', (jid, file_props, ''))
raise xmpp.NodeProcessed
class ConnectionBytestreamZeroconf(ConnectionBytestream):
def _ft_get_from(self, iq_obj): def _ft_get_from(self, iq_obj):
return unicode(iq_obj.getFrom()) return unicode(iq_obj.getFrom())

View File

@ -29,6 +29,8 @@ import random
import itertools import itertools
import dispatcher_nb import dispatcher_nb
import hashlib import hashlib
import hmac
import hashlib
import logging import logging
log = logging.getLogger('gajim.c.x.auth_nb') log = logging.getLogger('gajim.c.x.auth_nb')
@ -113,6 +115,8 @@ def challenge_splitter(data):
quotes_open = False quotes_open = False
return dict_ return dict_
def scram_parse(chatter):
return dict(s.split('=', 1) for s in chatter.split(','))
class SASL(PlugIn): class SASL(PlugIn):
""" """
@ -231,6 +235,13 @@ class SASL(PlugIn):
raise NodeProcessed raise NodeProcessed
except kerberos.GSSError, e: except kerberos.GSSError, e:
log.info('GSSAPI authentication failed: %s' % str(e)) log.info('GSSAPI authentication failed: %s' % str(e))
if 'SCRAM-SHA-1' in self.mecs:
self.mecs.remove('SCRAM-SHA-1')
self.mechanism = 'SCRAM-SHA-1'
self._owner._caller.get_password(self.set_password, self.mechanism)
self.scram_step = 0
self.startsasl = SASL_IN_PROCESS
raise NodeProcessed
if 'DIGEST-MD5' in self.mecs: if 'DIGEST-MD5' in self.mecs:
self.mecs.remove('DIGEST-MD5') self.mecs.remove('DIGEST-MD5')
node = Node('auth', attrs={'xmlns': NS_SASL, 'mechanism': 'DIGEST-MD5'}) node = Node('auth', attrs={'xmlns': NS_SASL, 'mechanism': 'DIGEST-MD5'})
@ -241,12 +252,12 @@ class SASL(PlugIn):
if 'PLAIN' in self.mecs: if 'PLAIN' in self.mecs:
self.mecs.remove('PLAIN') self.mecs.remove('PLAIN')
self.mechanism = 'PLAIN' self.mechanism = 'PLAIN'
self._owner._caller.get_password(self.set_password, 'PLAIN') self._owner._caller.get_password(self.set_password, self.mechanism)
self.startsasl = SASL_IN_PROCESS self.startsasl = SASL_IN_PROCESS
raise NodeProcessed raise NodeProcessed
self.startsasl = SASL_FAILURE self.startsasl = SASL_FAILURE
log.info('I can only use EXTERNAL, DIGEST-MD5, GSSAPI and PLAIN ' log.info('I can only use EXTERNAL, SCRAM-SHA-1, DIGEST-MD5, GSSAPI and '
'mecanisms.') 'PLAIN mecanisms.')
if self.on_sasl: if self.on_sasl:
self.on_sasl() self.on_sasl()
return return
@ -273,6 +284,8 @@ class SASL(PlugIn):
self.on_sasl() self.on_sasl()
raise NodeProcessed raise NodeProcessed
elif challenge.getName() == 'success': elif challenge.getName() == 'success':
# TODO: Need to validate any data-with-success.
# TODO: Important for DIGEST-MD5 and SCRAM.
self.startsasl = SASL_SUCCESS self.startsasl = SASL_SUCCESS
log.info('Successfully authenticated with remote server.') log.info('Successfully authenticated with remote server.')
handlers = self._owner.Dispatcher.dumpHandlers() handlers = self._owner.Dispatcher.dumpHandlers()
@ -310,9 +323,73 @@ class SASL(PlugIn):
response = kerberos.authGSSClientResponse(self.gss_vc) response = kerberos.authGSSClientResponse(self.gss_vc)
if not response: if not response:
response = '' response = ''
self._owner.send(Node('response', attrs={'xmlns':NS_SASL}, self._owner.send(Node('response', attrs={'xmlns': NS_SASL},
payload=response).__str__()) payload=response).__str__())
raise NodeProcessed raise NodeProcessed
if self.mechanism == 'SCRAM-SHA-1':
hashfn = hashlib.sha1
def HMAC(k, s):
return hmac.HMAC(key=k, msg=s, digestmod=hashfn).digest()
def XOR(x, y):
r = (chr(ord(px) ^ ord(py)) for px, py in zip(x, y))
return ''.join(r)
def Hi(s, salt, iters):
ii = 1
try:
s = s.encode('utf-8')
except:
pass
ui_1 = HMAC(s, salt + '\0\0\0\01')
ui = ui_1
for i in range(iters - 1):
ii += 1
ui_1 = HMAC(s, ui_1)
ui = XOR(ui, ui_1)
return ui
def H(s):
return hashfn(s).digest()
def scram_base64(s):
return ''.join(s.encode('base64').split('\n'))
if self.scram_step == 0:
self.scram_step = 1
self.scram_soup += ',' + data + ','
data = scram_parse(data)
# TODO: Should check cnonce here.
# TODO: Channel binding data goes in here too.
r = 'c=' + scram_base64(self.scram_gs2)
r += ',r=' + data['r']
self.scram_soup += r
salt = data['s'].decode('base64')
iter = int(data['i'])
SaltedPassword = Hi(self.password, salt, iter)
# TODO: Could cache this, along with salt+iter.
ClientKey = HMAC(SaltedPassword, 'Client Key')
StoredKey = H(ClientKey)
ClientSignature = HMAC(StoredKey, self.scram_soup)
ClientProof = XOR(ClientKey, ClientSignature)
r += ',p=' + scram_base64(ClientProof)
ServerKey = HMAC(SaltedPassword, 'Server Key')
self.scram_ServerSignature = HMAC(ServerKey, self.scram_soup)
sasl_data = scram_base64(r)
node = Node('response', attrs={'xmlns': NS_SASL},
payload=[sasl_data])
self._owner.send(str(node))
raise NodeProcessed
if self.scram_step == 1:
data = scram_parse(data)
if data['v'].decode('base64') != self.scram_ServerSignature:
# TODO: Not clear what to do here - need to abort.
raise Exception
node = Node('response', attrs={'xmlns': NS_SASL});
self._owner.send(str(node))
raise NodeProcessed
# magic foo... # magic foo...
chal = challenge_splitter(data) chal = challenge_splitter(data)
@ -350,7 +427,16 @@ class SASL(PlugIn):
self.password = '' self.password = ''
else: else:
self.password = password self.password = password
if self.mechanism == 'DIGEST-MD5': if self.mechanism == 'SCRAM-SHA-1':
nonce = ''.join('%x' % randint(0, 2**28) for randint in \
itertools.repeat(random.randint, 7))
self.scram_soup = 'n=' + self.username + ',r=' + nonce
self.scram_gs2 = 'n,,' # No CB yet.
sasl_data = (self.scram_gs2 + self.scram_soup).encode('base64').\
replace('\n','')
node = Node('auth', attrs={'xmlns': NS_SASL,
'mechanism': self.mechanism}, payload=[sasl_data])
elif self.mechanism == 'DIGEST-MD5':
def convert_to_iso88591(string): def convert_to_iso88591(string):
try: try:
string = string.decode('utf-8').encode('iso-8859-1') string = string.decode('utf-8').encode('iso-8859-1')

View File

@ -362,6 +362,9 @@ class NonBlockingClient:
supported and desired. supported and desired.
""" """
self.stream_started = True self.stream_started = True
if not hasattr(self, 'onreceive'):
# we may already have been disconnected
return
self.onreceive(None) self.onreceive(None)
if self.connected == 'plain': if self.connected == 'plain':

View File

@ -469,10 +469,6 @@ class XMPPDispatcher(PlugIn):
# we have released dispatcher, so self._owner has no methods # we have released dispatcher, so self._owner has no methods
if not res: if not res:
return return
if 'remove_timeout' in self._owner.__dict__:
# When we receive data after we started disconnecting, Transport may
# already be plugged out
self._owner.remove_timeout()
for (_id, _iq) in self._expected.items(): for (_id, _iq) in self._expected.items():
if _iq is None: if _iq is None:
# If the expected Stanza would have arrived, ProcessNonBlocking # If the expected Stanza would have arrived, ProcessNonBlocking

View File

@ -351,6 +351,8 @@ class NonBlockingTLS(PlugIn):
tcpsock = self._owner tcpsock = self._owner
# See http://docs.python.org/dev/library/ssl.html # See http://docs.python.org/dev/library/ssl.html
tcpsock._sslContext = OpenSSL.SSL.Context(OpenSSL.SSL.SSLv23_METHOD) tcpsock._sslContext = OpenSSL.SSL.Context(OpenSSL.SSL.SSLv23_METHOD)
tcpsock._sslContext.set_options(OpenSSL.SSL.OP_NO_SSLv2 | \
OpenSSL.SSL.OP_NO_TICKET)
tcpsock.ssl_errnum = 0 tcpsock.ssl_errnum = 0
tcpsock._sslContext.set_verify(OpenSSL.SSL.VERIFY_PEER, tcpsock._sslContext.set_verify(OpenSSL.SSL.VERIFY_PEER,
self._ssl_verify_callback) self._ssl_verify_callback)

View File

@ -35,7 +35,7 @@ from common import gajim
from common.zeroconf import zeroconf from common.zeroconf import zeroconf
from common.commands import ConnectionCommands from common.commands import ConnectionCommands
from common.pep import ConnectionPEP from common.pep import ConnectionPEP
from common.protocol.bytestream import ConnectionBytestreamZeroconf from common.protocol.bytestream import ConnectionSocks5BytestreamZeroconf
import logging import logging
log = logging.getLogger('gajim.c.z.connection_handlers_zeroconf') log = logging.getLogger('gajim.c.z.connection_handlers_zeroconf')
@ -70,11 +70,12 @@ class ConnectionVcard(connection_handlers.ConnectionVcard):
pass pass
class ConnectionHandlersZeroconf(ConnectionVcard, ConnectionBytestreamZeroconf, class ConnectionHandlersZeroconf(ConnectionVcard,
ConnectionCommands, ConnectionPEP, connection_handlers.ConnectionHandlersBase): ConnectionSocks5BytestreamZeroconf, ConnectionCommands, ConnectionPEP,
connection_handlers.ConnectionHandlersBase):
def __init__(self): def __init__(self):
ConnectionVcard.__init__(self) ConnectionVcard.__init__(self)
ConnectionBytestreamZeroconf.__init__(self) ConnectionSocks5BytestreamZeroconf.__init__(self)
ConnectionCommands.__init__(self) ConnectionCommands.__init__(self)
connection_handlers.ConnectionHandlersBase.__init__(self) connection_handlers.ConnectionHandlersBase.__init__(self)

View File

@ -703,6 +703,7 @@ class ConversationTextview(gobject.GObject):
size = 2 * size - 1 size = 2 * size - 1
self.marks_queue = Queue.Queue(size) self.marks_queue = Queue.Queue(size)
self.focus_out_end_mark = None self.focus_out_end_mark = None
self.just_cleared = True
def visit_url_from_menuitem(self, widget, link): def visit_url_from_menuitem(self, widget, link):
""" """
@ -1167,6 +1168,7 @@ class ConversationTextview(gobject.GObject):
buffer_ = self.tv.get_buffer() buffer_ = self.tv.get_buffer()
end_iter = buffer_.get_end_iter() end_iter = buffer_.get_end_iter()
buffer_.insert_with_tags_by_name(end_iter, '\n', 'eol') buffer_.insert_with_tags_by_name(end_iter, '\n', 'eol')
self.just_cleared = False
def print_conversation_line(self, text, jid, kind, name, tim, def print_conversation_line(self, text, jid, kind, name, tim,
other_tags_for_name=[], other_tags_for_time=[], other_tags_for_name=[], other_tags_for_time=[],
@ -1246,7 +1248,7 @@ class ConversationTextview(gobject.GObject):
text_tags.append(other_text_tag) text_tags.append(other_text_tag)
else: # not status nor /me else: # not status nor /me
if gajim.config.get('chat_merge_consecutive_nickname'): if gajim.config.get('chat_merge_consecutive_nickname'):
if kind != old_kind: if kind != old_kind or self.just_cleared:
self.print_name(name, kind, other_tags_for_name) self.print_name(name, kind, other_tags_for_name)
else: else:
self.print_real_text(gajim.config.get( self.print_real_text(gajim.config.get(
@ -1269,6 +1271,7 @@ class ConversationTextview(gobject.GObject):
else: else:
gobject.idle_add(self.scroll_to_end) gobject.idle_add(self.scroll_to_end)
self.just_cleared = False
buffer_.end_user_action() buffer_.end_user_action()
def get_time_to_show(self, tim): def get_time_to_show(self, tim):

View File

@ -38,6 +38,7 @@ import vcard
import conversation_textview import conversation_textview
import message_control import message_control
import dataforms_widget import dataforms_widget
import disco
from random import randrange from random import randrange
from common import pep from common import pep
@ -2156,7 +2157,6 @@ class JoinGroupchatWindow:
self._nickname_entry = self.xml.get_object('nickname_entry') self._nickname_entry = self.xml.get_object('nickname_entry')
self._password_entry = self.xml.get_object('password_entry') self._password_entry = self.xml.get_object('password_entry')
self._room_jid_entry.set_text(room_jid)
self._nickname_entry.set_text(nick) self._nickname_entry.set_text(nick)
if password: if password:
self._password_entry.set_text(password) self._password_entry.set_text(password)
@ -2171,6 +2171,13 @@ class JoinGroupchatWindow:
title = _('Join Group Chat') title = _('Join Group Chat')
self.window.set_title(title) self.window.set_title(title)
self.server_comboboxentry = self.xml.get_object('server_comboboxentry')
self.server_model = self.server_comboboxentry.get_model()
server_list = []
# get the muc server of our server
if 'jabber' in gajim.connections[account].muc_jid:
server_list.append(gajim.connections[account].muc_jid['jabber'])
self.recently_combobox = self.xml.get_object('recently_combobox') self.recently_combobox = self.xml.get_object('recently_combobox')
liststore = gtk.ListStore(str) liststore = gtk.ListStore(str)
self.recently_combobox.set_model(liststore) self.recently_combobox.set_model(liststore)
@ -2180,6 +2187,16 @@ class JoinGroupchatWindow:
self.recently_groupchat = gajim.config.get('recently_groupchat').split() self.recently_groupchat = gajim.config.get('recently_groupchat').split()
for g in self.recently_groupchat: for g in self.recently_groupchat:
self.recently_combobox.append_text(g) self.recently_combobox.append_text(g)
server = gajim.get_server_from_jid(g)
if server not in server_list and not server.startswith('irc'):
server_list.append(server)
for s in server_list:
self.server_model.append([s])
self.server_comboboxentry.set_active(0)
self._set_room_jid(room_jid)
if len(self.recently_groupchat) == 0: if len(self.recently_groupchat) == 0:
self.recently_combobox.set_sensitive(False) self.recently_combobox.set_sensitive(False)
elif room_jid == '': elif room_jid == '':
@ -2229,11 +2246,38 @@ class JoinGroupchatWindow:
self.account = model[iter_][0].decode('utf-8') self.account = model[iter_][0].decode('utf-8')
self.on_required_entry_changed(self._nickname_entry) self.on_required_entry_changed(self._nickname_entry)
def _select_server(self, server):
i = 0
for s in self.server_model:
if s[0] == server:
self.server_comboboxentry.set_active(i)
break
i += 1
def _set_room_jid(self, room_jid):
room, server = gajim.get_name_and_server_from_jid(room_jid)
self._select_server(server)
self._room_jid_entry.set_text(room)
def on_recently_combobox_changed(self, widget): def on_recently_combobox_changed(self, widget):
model = widget.get_model() model = widget.get_model()
iter_ = widget.get_active_iter() iter_ = widget.get_active_iter()
room_jid = model[iter_][0].decode('utf-8') room_jid = model[iter_][0].decode('utf-8')
self._room_jid_entry.set_text(room_jid) self._set_room_jid(room_jid)
def on_browse_rooms_button_clicked(self, widget):
server = self.server_comboboxentry.child.get_text().decode('utf-8')
if server in gajim.interface.instances[self.account]['disco']:
gajim.interface.instances[self.account]['disco'][server].window.\
present()
else:
try:
# Object will add itself to the window dict
disco.ServiceDiscoveryWindow(self.account, server,
initial_identities=[{'category': 'conference',
'type': 'text'}])
except GajimGeneralException:
pass
def on_cancel_button_clicked(self, widget): def on_cancel_button_clicked(self, widget):
""" """
@ -2258,7 +2302,9 @@ class JoinGroupchatWindow:
'groupchat.')) 'groupchat.'))
return return
nickname = self._nickname_entry.get_text().decode('utf-8') nickname = self._nickname_entry.get_text().decode('utf-8')
room_jid = self._room_jid_entry.get_text().decode('utf-8') server = self.server_comboboxentry.child.get_text().decode('utf-8')
room = self._room_jid_entry.get_text().decode('utf-8')
room_jid = room + '@' + server
password = self._password_entry.get_text().decode('utf-8') password = self._password_entry.get_text().decode('utf-8')
try: try:
nickname = helpers.parse_resource(nickname) nickname = helpers.parse_resource(nickname)

View File

@ -492,8 +492,8 @@ class ServiceDiscoveryWindow(object):
Class that represents the Services Discovery window Class that represents the Services Discovery window
""" """
def __init__(self, account, jid = '', node = '', def __init__(self, account, jid='', node='', address_entry=False,
address_entry = False, parent = None): parent=None, initial_identities=None):
self.account = account self.account = account
self.parent = parent self.parent = parent
if not jid: if not jid:
@ -519,6 +519,9 @@ _('Without a connection, you can not browse available services'))
self.cache = ServicesCache(account) self.cache = ServicesCache(account)
gajim.connections[account].services_cache = self.cache gajim.connections[account].services_cache = self.cache
if initial_identities:
self.cache.agent_info(account, (jid, node, initial_identities, [],
None))
self.xml = gtkgui_helpers.get_gtk_builder('service_discovery_window.ui') self.xml = gtkgui_helpers.get_gtk_builder('service_discovery_window.ui')
self.window = self.xml.get_object('service_discovery_window') self.window = self.xml.get_object('service_discovery_window')
self.services_treeview = self.xml.get_object('services_treeview') self.services_treeview = self.xml.get_object('services_treeview')

View File

@ -627,7 +627,8 @@ def get_avatar_pixbuf_from_cache(fjid, use_local=True):
# don't show avatar for the transport itself # don't show avatar for the transport itself
return None return None
if any(jid in gajim.contacts.get_gc_list(acc) for acc in gajim.connections): if any(jid in gajim.contacts.get_gc_list(acc) for acc in \
gajim.contacts.get_accounts()):
is_groupchat_contact = True is_groupchat_contact = True
else: else:
is_groupchat_contact = False is_groupchat_contact = False

View File

@ -119,6 +119,15 @@ class Interface:
#('ERROR', account, (title_text, section_text)) #('ERROR', account, (title_text, section_text))
dialogs.ErrorDialog(data[0], data[1]) dialogs.ErrorDialog(data[0], data[1])
def handle_event_db_error(self, unused, data):
#('DB_ERROR', account, (title_text, section_text))
if self.db_error_dialog:
return
self.db_error_dialog = dialogs.ErrorDialog(data[0], data[1])
def destroyed(win):
self.db_error_dialog = None
self.db_error_dialog.connect('destroy', destroyed)
def handle_event_information(self, unused, data): def handle_event_information(self, unused, data):
#('INFORMATION', account, (title_text, section_text)) #('INFORMATION', account, (title_text, section_text))
dialogs.InformationDialog(data[0], data[1]) dialogs.InformationDialog(data[0], data[1])
@ -480,6 +489,8 @@ class Interface:
jid_ = info['jid'] jid_ = info['jid']
c_ = gajim.contacts.get_contact_with_highest_priority( c_ = gajim.contacts.get_contact_with_highest_priority(
acct_, jid_) acct_, jid_)
if not c_:
continue
if c_.show not in ('offline', 'error'): if c_.show not in ('offline', 'error'):
show_notif = False show_notif = False
break break
@ -507,6 +518,8 @@ class Interface:
jid_ = info['jid'] jid_ = info['jid']
c_ = gajim.contacts.get_contact_with_highest_priority( c_ = gajim.contacts.get_contact_with_highest_priority(
acct_, jid_) acct_, jid_)
if not c_:
continue
if c_.show not in ('offline', 'error'): if c_.show not in ('offline', 'error'):
show_notif = False show_notif = False
break break
@ -2086,6 +2099,7 @@ class Interface:
'ROSTER': [self.handle_event_roster], 'ROSTER': [self.handle_event_roster],
'WARNING': [self.handle_event_warning], 'WARNING': [self.handle_event_warning],
'ERROR': [self.handle_event_error], 'ERROR': [self.handle_event_error],
'DB_ERROR': [self.handle_event_db_error],
'INFORMATION': [self.handle_event_information], 'INFORMATION': [self.handle_event_information],
'ERROR_ANSWER': [self.handle_event_error_answer], 'ERROR_ANSWER': [self.handle_event_error_answer],
'STATUS': [self.handle_event_status], 'STATUS': [self.handle_event_status],
@ -3277,6 +3291,7 @@ class Interface:
self.status_sent_to_groups = {} self.status_sent_to_groups = {}
self.gpg_passphrase = {} self.gpg_passphrase = {}
self.pass_dialog = {} self.pass_dialog = {}
self.db_error_dialog = None
self.default_colors = { self.default_colors = {
'inmsgcolor': gajim.config.get('inmsgcolor'), 'inmsgcolor': gajim.config.get('inmsgcolor'),
'outmsgcolor': gajim.config.get('outmsgcolor'), 'outmsgcolor': gajim.config.get('outmsgcolor'),

View File

@ -1398,7 +1398,7 @@ class RosterWindow:
self.on_modelfilter_row_has_child_toggled) self.on_modelfilter_row_has_child_toggled)
self.tree.set_model(self.modelfilter) self.tree.set_model(self.modelfilter)
for acct in gajim.connections: for acct in gajim.contacts.get_accounts():
self.add_account(acct) self.add_account(acct)
self.add_account_contacts(acct) self.add_account_contacts(acct)
# Recalculate column width for ellipsizing # Recalculate column width for ellipsizing