merge with default
This commit is contained in:
commit
f0dde42f77
10
AUTHORS
10
AUTHORS
|
@ -1,13 +1,8 @@
|
|||
CURRENT DEVELOPERS:
|
||||
|
||||
Alexander Cherniuk (ts33kr AT gmail.com)
|
||||
Nikos Kouremenos (kourem AT gmail.com)
|
||||
Yann Leboulanger (asterix AT lagaule.org)
|
||||
Julien Pivotto (roidelapluie AT gmail.com)
|
||||
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:
|
||||
|
||||
|
@ -15,3 +10,8 @@ Stefan Bethge (stefan AT lanpartei.de)
|
|||
Stephan Erb (steve-e AT h3c.de)
|
||||
Vincent Hanquez (tab AT snarc.org)
|
||||
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)
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
<child>
|
||||
<object class="GtkTable" id="table15">
|
||||
<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="column_spacing">12</property>
|
||||
<property name="row_spacing">6</property>
|
||||
|
@ -110,8 +110,8 @@
|
|||
<property name="label" translatable="yes">Password:</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="top_attach">4</property>
|
||||
<property name="bottom_attach">5</property>
|
||||
<property name="top_attach">5</property>
|
||||
<property name="bottom_attach">6</property>
|
||||
<property name="x_options">GTK_FILL</property>
|
||||
<property name="y_options"></property>
|
||||
</packing>
|
||||
|
@ -126,14 +126,14 @@
|
|||
<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>
|
||||
<property name="top_attach">5</property>
|
||||
<property name="bottom_attach">6</property>
|
||||
<property name="y_options"></property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<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="sensitive">False</property>
|
||||
<property name="can_focus">True</property>
|
||||
|
@ -143,8 +143,8 @@
|
|||
</object>
|
||||
<packing>
|
||||
<property name="right_attach">2</property>
|
||||
<property name="top_attach">6</property>
|
||||
<property name="bottom_attach">7</property>
|
||||
<property name="top_attach">7</property>
|
||||
<property name="bottom_attach">8</property>
|
||||
<property name="x_options">GTK_FILL</property>
|
||||
<property name="y_options"></property>
|
||||
</packing>
|
||||
|
@ -174,21 +174,69 @@
|
|||
</child>
|
||||
<child>
|
||||
<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="can_focus">True</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="use_underline">True</property>
|
||||
<property name="draw_indicator">True</property>
|
||||
<signal name="toggled" handler="on_bookmark_checkbutton_toggled"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="right_attach">2</property>
|
||||
<property name="top_attach">5</property>
|
||||
<property name="bottom_attach">6</property>
|
||||
<property name="top_attach">6</property>
|
||||
<property name="bottom_attach">7</property>
|
||||
<property name="x_options">GTK_FILL</property>
|
||||
<property name="y_options"></property>
|
||||
</packing>
|
||||
</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>
|
||||
<packing>
|
||||
<property name="position">0</property>
|
||||
|
@ -275,4 +323,14 @@
|
|||
</object>
|
||||
</child>
|
||||
</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>
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="scrollable">True</property>
|
||||
<property name="tab_border">0</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
|
@ -59,7 +60,6 @@
|
|||
<child>
|
||||
<object class="GtkImage" id="image1329">
|
||||
<property name="visible">True</property>
|
||||
<property name="ypad">6</property>
|
||||
<property name="stock">gtk-close</property>
|
||||
<property name="icon-size">1</property>
|
||||
</object>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<!-- interface-naming-policy toplevel-contextual -->
|
||||
<object class="GtkListStore" id="liststore1">
|
||||
<columns>
|
||||
<!-- column-name item text -->
|
||||
<!-- column-name item -->
|
||||
<column type="gchararray"/>
|
||||
</columns>
|
||||
</object>
|
||||
|
@ -71,7 +71,6 @@ Agent JID - node</property>
|
|||
<property name="visible">True</property>
|
||||
<property name="model">liststore1</property>
|
||||
<signal name="changed" handler="on_address_comboboxentry_changed"/>
|
||||
<signal name="key_press_event" handler="on_address_comboboxentry_key_press_event"/>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="cellrenderertext1"/>
|
||||
<attributes>
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
The command system providing scalable, clean and convenient architecture in
|
||||
combination with declarative way of defining commands and a fair amount of
|
||||
automatization for routine processes.
|
||||
The command system providing scalable, clean and convenient architecture
|
||||
in combination with declarative way of defining commands and a fair
|
||||
amount of automatization for routine processes.
|
||||
"""
|
||||
|
|
|
@ -14,9 +14,10 @@
|
|||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
The backbone of the command system. Provides automatic dispatching which does
|
||||
not require explicit registering commands or containers and remains active even
|
||||
after everything is done, so new commands can be added during the runtime.
|
||||
The backbone of the command system. Provides automatic dispatching which
|
||||
does not require explicit registering commands or containers and remains
|
||||
active even after everything is done, so new commands can be added
|
||||
during the runtime.
|
||||
"""
|
||||
|
||||
from types import NoneType
|
||||
|
|
|
@ -15,8 +15,9 @@
|
|||
|
||||
class BaseError(Exception):
|
||||
"""
|
||||
Common base for errors which relate to a specific command. Encapsulates
|
||||
everything needed to identify a command, by either its object or name.
|
||||
Common base for errors which relate to a specific command.
|
||||
Encapsulates everything needed to identify a command, by either its
|
||||
object or name.
|
||||
"""
|
||||
|
||||
def __init__(self, message, command=None, name=None):
|
||||
|
|
|
@ -14,8 +14,9 @@
|
|||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
Provides a tiny framework with simple, yet powerful and extensible architecture
|
||||
to implement commands in a streight and flexible, declarative way.
|
||||
Provides a tiny framework with simple, yet powerful and extensible
|
||||
architecture to implement commands in a streight and flexible,
|
||||
declarative way.
|
||||
"""
|
||||
|
||||
import re
|
||||
|
@ -28,42 +29,43 @@ from errors import DefinitionError, CommandError
|
|||
|
||||
class CommandHost(object):
|
||||
"""
|
||||
Command host is a hub between numerous command processors and command
|
||||
containers. Aimed to participate in a dispatching process in order to
|
||||
provide clean and transparent architecture.
|
||||
Command host is a hub between numerous command processors and
|
||||
command containers. Aimed to participate in a dispatching process in
|
||||
order to provide clean and transparent architecture.
|
||||
"""
|
||||
__metaclass__ = HostDispatcher
|
||||
|
||||
class CommandContainer(object):
|
||||
"""
|
||||
Command container is an entity which holds defined commands, allowing them
|
||||
to be dispatched and proccessed correctly. Each command container may be
|
||||
bound to a one or more command hosts.
|
||||
Command container is an entity which holds defined commands,
|
||||
allowing them to be dispatched and proccessed correctly. Each
|
||||
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
|
||||
body of the command container. This variable should contain a list of hosts
|
||||
to bound to, as a tuple or list.
|
||||
Bounding is controlled by the HOSTS variable, which must be defined
|
||||
in the body of the command container. This variable should contain a
|
||||
list of hosts to bound to, as a tuple or list.
|
||||
"""
|
||||
__metaclass__ = ContainerDispatcher
|
||||
|
||||
class CommandProcessor(object):
|
||||
"""
|
||||
Command processor is an immediate command emitter. It does not participate
|
||||
in the dispatching process directly, but must define a host to bound to.
|
||||
Command processor is an immediate command emitter. It does not
|
||||
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
|
||||
in the body of the command processor. This variable should be set to a
|
||||
specific command host.
|
||||
Bounding is controlled by the COMMAND_HOST variable, which must be
|
||||
defined in the body of the command processor. This variable should
|
||||
be set to a specific command host.
|
||||
"""
|
||||
|
||||
# This defines a command prefix (or an initializer), which should preceede a
|
||||
# a text in order it to be processed as a command.
|
||||
# This defines a command prefix (or an initializer), which should
|
||||
# preceede a a text in order it to be processed as a command.
|
||||
COMMAND_PREFIX = '/'
|
||||
|
||||
def process_as_command(self, text):
|
||||
"""
|
||||
Try to process text as a command. Returns True if it has been processed
|
||||
as a command and False otherwise.
|
||||
Try to process text as a command. Returns True if it has been
|
||||
processed as a command and False otherwise.
|
||||
"""
|
||||
prefix = text.startswith(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):
|
||||
"""
|
||||
Redefine this method in the subclass to execute custom code before
|
||||
command gets executed.
|
||||
Redefine this method in the subclass to execute custom code
|
||||
before command gets executed.
|
||||
|
||||
If returns True then command execution will be interrupted and command
|
||||
will not be executed.
|
||||
If returns True then command execution will be interrupted and
|
||||
command will not be executed.
|
||||
"""
|
||||
pass
|
||||
|
||||
def command_postprocessor(self, command, name, arguments, args, kwargs, value):
|
||||
"""
|
||||
Redefine this method in the subclass to execute custom code after
|
||||
command gets executed.
|
||||
Redefine this method in the subclass to execute custom code
|
||||
after command gets executed.
|
||||
"""
|
||||
pass
|
||||
|
||||
def looks_like_command(self, text, body, name, arguments):
|
||||
"""
|
||||
This hook is being called before any processing, but after it was
|
||||
determined that text looks like a command.
|
||||
This hook is being called before any processing, but after it
|
||||
was determined that text looks like a command.
|
||||
|
||||
If returns value other then None - then further processing will be
|
||||
interrupted and that value will be used to return from
|
||||
If returns value other then None - then further processing will
|
||||
be interrupted and that value will be used to return from
|
||||
process_as_command.
|
||||
"""
|
||||
pass
|
||||
|
@ -136,8 +138,9 @@ class CommandProcessor(object):
|
|||
|
||||
class Command(object):
|
||||
|
||||
# These two regular expression patterns control how command documentation
|
||||
# will be formatted to be transformed to a normal, readable state.
|
||||
# These two regular expression patterns control how command
|
||||
# documentation will be formatted to be transformed to a normal,
|
||||
# readable state.
|
||||
DOC_STRIP_PATTERN = re.compile(r'(?:^[ \t]+|\A\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.names = names
|
||||
|
||||
# Automatically set all the properties passed to a constructor by the
|
||||
# command decorator.
|
||||
# Automatically set all the properties passed to a constructor
|
||||
# by the command decorator.
|
||||
for key, value in properties.iteritems():
|
||||
setattr(self, key, value)
|
||||
|
||||
|
@ -154,18 +157,20 @@ class Command(object):
|
|||
try:
|
||||
return self.handler(*args, **kwargs)
|
||||
|
||||
# This allows to use a shortcuted way of raising an exception inside a
|
||||
# handler. That is to raise a CommandError without command or name
|
||||
# attributes set. They will be set to a corresponding values right here
|
||||
# in case if they was not set by the one who raised an exception.
|
||||
# This allows to use a shortcuted way of raising an exception
|
||||
# inside a handler. That is to raise a CommandError without
|
||||
# command or name attributes set. They will be set to a
|
||||
# corresponding values right here in case if they was not set by
|
||||
# the one who raised an exception.
|
||||
except CommandError, error:
|
||||
if not error.command and not error.name:
|
||||
raise CommandError(error.message, self)
|
||||
raise
|
||||
|
||||
# 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
|
||||
# if command complains about invalid arguments while they are ok.
|
||||
# anything more constrained - there is no other choice. Take a
|
||||
# look here if command complains about invalid arguments while
|
||||
# they are ok.
|
||||
except TypeError:
|
||||
raise CommandError("Command received invalid arguments", self)
|
||||
|
||||
|
@ -185,8 +190,8 @@ class Command(object):
|
|||
|
||||
def extract_documentation(self):
|
||||
"""
|
||||
Extract handler's documentation which is a doc-string and transform it
|
||||
to a usable format.
|
||||
Extract handler's documentation which is a doc-string and
|
||||
transform it to a usable format.
|
||||
|
||||
Transformation is done based on the DOC_STRIP_PATTERN and
|
||||
DOC_FORMAT_PATTERN regular expression patterns.
|
||||
|
@ -211,19 +216,19 @@ class Command(object):
|
|||
|
||||
def extract_specification(self):
|
||||
"""
|
||||
Extract handler's arguments specification, as it was defined preserving
|
||||
their order.
|
||||
Extract handler's arguments specification, as it was defined
|
||||
preserving their order.
|
||||
"""
|
||||
names, var_args, var_kwargs, defaults = getargspec(self.handler)
|
||||
|
||||
# Behavior of this code need to be checked. Might yield incorrect
|
||||
# results on some rare occasions.
|
||||
# Behavior of this code need to be checked. Might yield
|
||||
# incorrect results on some rare occasions.
|
||||
spec_args = names[:-len(defaults) if defaults else len(names)]
|
||||
spec_kwargs = list(zip(names[-len(defaults):], defaults)) if defaults else {}
|
||||
|
||||
# Removing self from arguments specification. Command handler should
|
||||
# receive the processors as a first argument, which should be self by
|
||||
# the canonical means.
|
||||
# Removing self from arguments specification. Command handler
|
||||
# should receive the processors as a first argument, which
|
||||
# should be self by the canonical means.
|
||||
if spec_args.pop(0) != 'self':
|
||||
raise DefinitionError("First argument must be self", self)
|
||||
|
||||
|
@ -231,44 +236,47 @@ class Command(object):
|
|||
|
||||
def command(*names, **properties):
|
||||
"""
|
||||
A decorator for defining commands in a declarative way. Provides facilities
|
||||
for setting command's names and properties.
|
||||
A decorator for defining commands in a declarative way. Provides
|
||||
facilities for setting command's names and properties.
|
||||
|
||||
Names should contain a set of names (aliases) by which the command can be
|
||||
reached. If no names are given - the the native name (the one extracted from
|
||||
the command handler) will be used.
|
||||
Names should contain a set of names (aliases) by which the command
|
||||
can be reached. If no names are given - the the native name (the one
|
||||
extracted from the command handler) will be used.
|
||||
|
||||
If include_native=True is given (default) and names is non-empty - then the
|
||||
native name of the command will be prepended in addition to the given names.
|
||||
If include_native=True is given (default) and names is non-empty -
|
||||
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
|
||||
autogenerated usage info, based of the command handler arguments
|
||||
introspection.
|
||||
If usage=True is given (default) - then command help will be
|
||||
appended with autogenerated usage info, based of the command handler
|
||||
arguments introspection.
|
||||
|
||||
If source=True is given - then the first argument of the command will
|
||||
receive the source arguments, as a raw, unprocessed string. The further
|
||||
mapping of arguments and options will not be affected.
|
||||
If source=True is given - then the first argument of the command
|
||||
will receive the source arguments, as a raw, unprocessed string. The
|
||||
further mapping of arguments and options will not be affected.
|
||||
|
||||
If raw=True is given - then command considered to be raw and should define
|
||||
positional arguments only. If it defines only one positional argument - this
|
||||
argument will receive all the raw and unprocessed arguments. If the command
|
||||
defines more then one positional argument - then all the arguments except
|
||||
the last one will be processed normally; the last argument will get what is
|
||||
left after the processing as raw and unprocessed string.
|
||||
If raw=True is given - then command considered to be raw and should
|
||||
define positional arguments only. If it defines only one positional
|
||||
argument - this argument will receive all the raw and unprocessed
|
||||
arguments. If the command defines more then one positional argument
|
||||
- then all the arguments except the last one will be processed
|
||||
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
|
||||
arguments.
|
||||
If empty=True is given - this will allow to call a raw command
|
||||
without arguments.
|
||||
|
||||
If extra=True is given - then all the extra arguments passed to a command
|
||||
will be collected into a sequence and given to the last positional argument.
|
||||
If extra=True is given - then all the extra arguments passed to a
|
||||
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
|
||||
they were values for the keyword arguments.
|
||||
If overlap=True is given - then all the extra arguments will be
|
||||
mapped as if they were values for the keyword arguments.
|
||||
|
||||
If expand_short=True is given (default) - then short, one-letter options
|
||||
will be expanded to a verbose ones, based of the comparison of the first
|
||||
letter. If more then one option with the same first letter is given - then
|
||||
only first one will be used in the expansion.
|
||||
If expand_short=True is given (default) - then short, one-letter
|
||||
options will be expanded to a verbose ones, based of the comparison
|
||||
of the first letter. If more then one option with the same first
|
||||
letter is given - then only first one will be used in the expansion.
|
||||
"""
|
||||
names = list(names)
|
||||
|
||||
|
@ -300,22 +308,23 @@ def command(*names, **properties):
|
|||
|
||||
def decorator(handler):
|
||||
"""
|
||||
Decorator which receives handler as a first argument and then wraps it
|
||||
in the command which then returns back.
|
||||
Decorator which receives handler as a first argument and then
|
||||
wraps it in the command which then returns back.
|
||||
"""
|
||||
command = Command(handler, *names, **properties)
|
||||
|
||||
# Extract and inject a native name if either no other names are
|
||||
# specified or include_native property is enabled, while making sure it
|
||||
# is going to be the first one in the list.
|
||||
# specified or include_native property is enabled, while making
|
||||
# sure it is going to be the first one in the list.
|
||||
if not names or include_native:
|
||||
names.insert(0, command.native_name)
|
||||
command.names = tuple(names)
|
||||
|
||||
return command
|
||||
|
||||
# Workaround if we are getting called without parameters. Keep in mind that
|
||||
# in that case - first item in the names will be the handler.
|
||||
# Workaround if we are getting called without parameters. Keep in
|
||||
# mind that in that case - first item in the names will be the
|
||||
# handler.
|
||||
if names and isinstance(names[0], FunctionType):
|
||||
return decorator(names.pop(0))
|
||||
|
||||
|
@ -323,11 +332,8 @@ def command(*names, **properties):
|
|||
|
||||
def documentation(text):
|
||||
"""
|
||||
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.
|
||||
This decorator is used to bind a documentation (a help) to a
|
||||
command.
|
||||
"""
|
||||
def decorator(target):
|
||||
if isinstance(target, Command):
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
The implementation and auxilary systems which implement the standard Gajim
|
||||
commands and also provide an infrastructure for adding custom commands.
|
||||
The implementation and auxilary systems which implement the standard
|
||||
Gajim commands and also provide an infrastructure for adding custom
|
||||
commands.
|
||||
"""
|
||||
|
|
|
@ -14,11 +14,11 @@
|
|||
# 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
|
||||
new command container and definding a set of commands.
|
||||
The module contains examples of how to create your own commands, by
|
||||
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
|
||||
executed and commands defined here will not be detected.
|
||||
Keep in mind that this module is not being loaded, so the code will not
|
||||
be executed and commands defined here will not be detected.
|
||||
"""
|
||||
|
||||
from ..framework import CommandContainer, command, documentation
|
||||
|
@ -27,8 +27,9 @@ from hosts import ChatCommands, PrivateChatCommands, GroupChatCommands
|
|||
class CustomCommonCommands(CommandContainer):
|
||||
"""
|
||||
This command container bounds to all three available in the default
|
||||
implementation command hosts. This means that commands defined in this
|
||||
container will be available to all - chat, private chat and a group chat.
|
||||
implementation command hosts. This means that commands defined in
|
||||
this container will be available to all - chat, private chat and a
|
||||
group chat.
|
||||
"""
|
||||
|
||||
HOSTS = (ChatCommands, PrivateChatCommands, GroupChatCommands)
|
||||
|
@ -39,12 +40,13 @@ class CustomCommonCommands(CommandContainer):
|
|||
First line of the doc string is called a description and will be
|
||||
programmatically extracted and formatted.
|
||||
|
||||
After that you can give more help, like explanation of the options. This
|
||||
one will be programatically extracted and formatted too.
|
||||
After that you can give more help, like explanation of the
|
||||
options. This one will be programatically extracted and
|
||||
formatted too.
|
||||
|
||||
After all the documentation - there will be autogenerated (based on the
|
||||
method signature) usage information appended. You can turn it off
|
||||
though, if you want.
|
||||
After all the documentation - there will be autogenerated (based
|
||||
on the method signature) usage information appended. You can
|
||||
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..."
|
||||
|
||||
|
@ -63,8 +65,9 @@ class CustomChatCommands(CommandContainer):
|
|||
|
||||
class CustomPrivateChatCommands(CommandContainer):
|
||||
"""
|
||||
This command container bounds only to the PrivateChatCommands command host.
|
||||
Therefore command defined here will be available only to a private chat.
|
||||
This command container bounds only to the PrivateChatCommands
|
||||
command host. Therefore command defined here will be available only
|
||||
to a private chat.
|
||||
"""
|
||||
|
||||
HOSTS = (PrivateChatCommands,)
|
||||
|
@ -75,8 +78,9 @@ class CustomPrivateChatCommands(CommandContainer):
|
|||
|
||||
class CustomGroupChatCommands(CommandContainer):
|
||||
"""
|
||||
This command container bounds only to the GroupChatCommands command host.
|
||||
Therefore command defined here will be available only to a group chat.
|
||||
This command container bounds only to the GroupChatCommands command
|
||||
host. Therefore command defined here will be available only to a
|
||||
group chat.
|
||||
"""
|
||||
|
||||
HOSTS = (GroupChatCommands,)
|
||||
|
|
|
@ -14,29 +14,29 @@
|
|||
# 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
|
||||
command processors, which are the source of commands.
|
||||
The module defines a set of command hosts, which are bound to a
|
||||
different command processors, which are the source of commands.
|
||||
"""
|
||||
|
||||
from ..framework import CommandHost
|
||||
|
||||
class ChatCommands(CommandHost):
|
||||
"""
|
||||
This command host is bound to the command processor which processes commands
|
||||
from a chat.
|
||||
This command host is bound to the command processor which processes
|
||||
commands from a chat.
|
||||
"""
|
||||
pass
|
||||
|
||||
class PrivateChatCommands(CommandHost):
|
||||
"""
|
||||
This command host is bound to the command processor which processes commands
|
||||
from a private chat.
|
||||
This command host is bound to the command processor which processes
|
||||
commands from a private chat.
|
||||
"""
|
||||
pass
|
||||
|
||||
class GroupChatCommands(CommandHost):
|
||||
"""
|
||||
This command host is bound to the command processor which processes commands
|
||||
from a group chat.
|
||||
This command host is bound to the command processor which processes
|
||||
commands from a group chat.
|
||||
"""
|
||||
pass
|
||||
|
|
|
@ -14,10 +14,10 @@
|
|||
# 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
|
||||
would be dropped in. Defines a little bit of scaffolding to support interaction
|
||||
between the two and a few utility methods so you don't need to dig up the code
|
||||
itself code to write basic commands.
|
||||
Provides a glue to tie command system framework and the actual code
|
||||
where it would be dropped in. Defines a little bit of scaffolding to
|
||||
support interaction between the two and a few utility methods so you
|
||||
don't need to dig up the code itself code to write basic commands.
|
||||
"""
|
||||
|
||||
from types import StringTypes
|
||||
|
@ -30,8 +30,8 @@ from ..errors import CommandError
|
|||
|
||||
class ChatCommandProcessor(CommandProcessor):
|
||||
"""
|
||||
A basic scaffolding to provide convenient interaction between the command
|
||||
system and chat controls.
|
||||
A basic scaffolding to provide convenient interaction between the
|
||||
command system and chat controls.
|
||||
"""
|
||||
|
||||
def process_as_command(self, text):
|
||||
|
@ -76,8 +76,8 @@ class ChatCommandProcessor(CommandProcessor):
|
|||
|
||||
class CommandTools:
|
||||
"""
|
||||
Contains a set of basic tools and shortcuts you can use in your commands to
|
||||
performe some simple operations.
|
||||
Contains a set of basic tools and shortcuts you can use in your
|
||||
commands to performe some simple operations.
|
||||
"""
|
||||
|
||||
def echo(self, text, kind='info'):
|
||||
|
@ -107,8 +107,8 @@ class CommandTools:
|
|||
|
||||
def add_history(self, text):
|
||||
"""
|
||||
Add given text to the input history, so user can scroll through it using
|
||||
ctrl + up/down arrow keys.
|
||||
Add given text to the input history, so user can scroll through
|
||||
it using ctrl + up/down arrow keys.
|
||||
"""
|
||||
self.save_sent_message(text)
|
||||
|
||||
|
|
|
@ -38,8 +38,8 @@ lc = Constants()
|
|||
|
||||
class StandardCommonCommands(CommandContainer):
|
||||
"""
|
||||
This command container contains standard commands which are common to all -
|
||||
chat, private chat, group chat.
|
||||
This command container contains standard commands which are common
|
||||
to all - chat, private chat, group chat.
|
||||
"""
|
||||
|
||||
HOSTS = (ChatCommands, PrivateChatCommands, GroupChatCommands)
|
||||
|
@ -159,7 +159,8 @@ class StandardCommonCommands(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,)
|
||||
|
@ -211,16 +212,16 @@ class StandardChatCommands(CommandContainer):
|
|||
|
||||
class StandardPrivateChatCommands(CommandContainer):
|
||||
"""
|
||||
This command container contains standard command which are unique to a
|
||||
private chat.
|
||||
This command container contains standard command which are unique to
|
||||
a private chat.
|
||||
"""
|
||||
|
||||
HOSTS = (PrivateChatCommands,)
|
||||
|
||||
class StandardGroupchatCommands(CommandContainer):
|
||||
"""
|
||||
This command container contains standard command which are unique to a group
|
||||
chat.
|
||||
This command container contains standard command which are unique to
|
||||
a group chat.
|
||||
"""
|
||||
|
||||
HOSTS = (GroupChatCommands,)
|
||||
|
|
|
@ -14,17 +14,16 @@
|
|||
# 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
|
||||
command handler's positonal and keyword arguments.
|
||||
The module contains routines to parse command arguments and map them to
|
||||
the command handler's positonal and keyword arguments.
|
||||
|
||||
Mapping is done in two stages: 1) parse arguments into positional arguments and
|
||||
options; 2) adapt them to the specific command handler according to the command
|
||||
properties.
|
||||
Mapping is done in two stages: 1) parse arguments into positional
|
||||
arguments and options; 2) adapt them to the specific command handler
|
||||
according to the command properties.
|
||||
"""
|
||||
|
||||
import re
|
||||
from types import BooleanType, UnicodeType
|
||||
from types import TupleType, ListType
|
||||
from operator import itemgetter
|
||||
|
||||
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)')
|
||||
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
|
||||
# allow to expand dictionary with raw unicode strings as keys from a **kwargs.
|
||||
# Option keys needs to be encoded to a specific encoding as Python does
|
||||
# not allow to expand dictionary with raw unicode strings as keys from a
|
||||
# **kwargs.
|
||||
KEY_ENCODING = 'UTF-8'
|
||||
|
||||
# Defines how complete representation of command usage (generated based on
|
||||
# command handler argument specification) will be rendered.
|
||||
# Defines how complete representation of command usage (generated based
|
||||
# on command handler argument specification) will be rendered.
|
||||
USAGE_PATTERN = 'Usage: %s %s'
|
||||
|
||||
def parse_arguments(arguments):
|
||||
"""
|
||||
Simple yet effective and sufficient in most cases parser which parses
|
||||
command arguments and returns them as two lists.
|
||||
Simple yet effective and sufficient in most cases parser which
|
||||
parses command arguments and returns them as two lists.
|
||||
|
||||
First list represents positional arguments as (argument, position), and
|
||||
second representing options as (key, value, position) tuples, where position
|
||||
is a (start, end) span tuple of where it was found in the string.
|
||||
First list represents positional arguments as (argument, position),
|
||||
and second representing options as (key, value, position) tuples,
|
||||
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
|
||||
--option value or -option value. Keys without values will get None as value.
|
||||
Options may be given in --long or -short format. As --option=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
|
||||
three' or "one two three"; that is between single or double quotes.
|
||||
Arguments and option values that contain spaces may be given as 'one
|
||||
two three' or "one two three"; that is between single or double
|
||||
quotes.
|
||||
"""
|
||||
args, opts = [], []
|
||||
|
||||
|
@ -90,14 +93,16 @@ def parse_arguments(arguments):
|
|||
position = match.span()
|
||||
args.append((body, position))
|
||||
|
||||
# Primitive but sufficiently effective way of disposing of conflicted
|
||||
# sectors. Remove any arguments that intersect with options.
|
||||
# Primitive but sufficiently effective way of disposing of
|
||||
# conflicted sectors. Remove any arguments that intersect with
|
||||
# options.
|
||||
for arg, position in args[:]:
|
||||
if intersects_opts(position):
|
||||
args.remove((arg, position))
|
||||
|
||||
# Primitive but sufficiently effective way of disposing of conflicted
|
||||
# sectors. Remove any options that intersect with arguments.
|
||||
# Primitive but sufficiently effective way of disposing of
|
||||
# conflicted sectors. Remove any options that intersect with
|
||||
# arguments.
|
||||
for key, value, position in opts[:]:
|
||||
if intersects_args(position):
|
||||
opts.remove((key, value, position))
|
||||
|
@ -106,41 +111,40 @@ def parse_arguments(arguments):
|
|||
|
||||
def adapt_arguments(command, arguments, args, opts):
|
||||
"""
|
||||
Adapt args and opts got from the parser to a specific handler by means of
|
||||
arguments specified on command definition. That is transform them to *args
|
||||
and **kwargs suitable for passing to a command handler.
|
||||
Adapt args and opts got from the parser to a specific handler by
|
||||
means of arguments specified on command definition. That is
|
||||
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
|
||||
map --one-more-option to a one_more_option=None.
|
||||
Dashes (-) in the option names will be converted to underscores. So
|
||||
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
|
||||
cases) - then this option will be treated as a switch, that is an option
|
||||
which does not take an argument. If a switch is followed 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.
|
||||
If the initial value of a keyword argument is a boolean (False in
|
||||
most cases) - then this option will be treated as a switch, that is
|
||||
an option which does not take an argument. If a switch is followed
|
||||
by an argument - then this argument will be treated just like a
|
||||
normal positional argument.
|
||||
"""
|
||||
spec_args, spec_kwargs, var_args, var_kwargs = command.extract_specification()
|
||||
norm_kwargs = dict(spec_kwargs)
|
||||
|
||||
# Quite complex piece of neck-breaking logic to extract raw arguments if
|
||||
# there is more, then one positional argument specified by the command. In
|
||||
# case if it's just one argument which is the collector - this is fairly
|
||||
# easy. But when it's more then one argument - the neck-breaking logic of
|
||||
# how to retrieve residual arguments as a raw, all in one piece string,
|
||||
# kicks in.
|
||||
# Quite complex piece of neck-breaking logic to extract raw
|
||||
# arguments if there is more, then one positional argument specified
|
||||
# by the command. In case if it's just one argument which is the
|
||||
# collector - this is fairly easy. But when it's more then one
|
||||
# argument - the neck-breaking logic of how to retrieve residual
|
||||
# arguments as a raw, all in one piece string, kicks in.
|
||||
if command.raw:
|
||||
if arguments:
|
||||
spec_fix = 1 if command.source else 0
|
||||
spec_len = len(spec_args) - spec_fix
|
||||
arguments_end = len(arguments) - 1
|
||||
|
||||
# If there are any optional arguments given they should be either an
|
||||
# unquoted postional argument or part of the raw argument. So we
|
||||
# find all optional arguments that can possibly be unquoted argument
|
||||
# and append them as is to the args.
|
||||
# If there are any optional arguments given they should be
|
||||
# either an unquoted postional argument or part of the raw
|
||||
# argument. So we find all optional arguments that can
|
||||
# possibly be unquoted argument and append them as is to the
|
||||
# args.
|
||||
for key, value, (start, end) in opts[:spec_len]:
|
||||
if value:
|
||||
end -= len(value) + 1
|
||||
|
@ -149,9 +153,9 @@ def adapt_arguments(command, arguments, args, opts):
|
|||
else:
|
||||
args.append((arguments[start:end], (start, end)))
|
||||
|
||||
# We need in-place sort here because after manipulations with
|
||||
# options order of arguments might be wrong and we just can't have
|
||||
# more complex logic to not let that happen.
|
||||
# We need in-place sort here because after manipulations
|
||||
# with options order of arguments might be wrong and we just
|
||||
# can't have more complex logic to not let that happen.
|
||||
args.sort(key=itemgetter(1))
|
||||
|
||||
if spec_len > 1:
|
||||
|
@ -160,27 +164,28 @@ def adapt_arguments(command, arguments, args, opts):
|
|||
except IndexError:
|
||||
raise CommandError("Missing arguments", command)
|
||||
|
||||
# The essential point of the whole play. After boundaries are
|
||||
# being determined (supposingly correct) we separate raw part
|
||||
# from the rest of arguments, which should be normally
|
||||
# processed.
|
||||
# The essential point of the whole play. After
|
||||
# boundaries are being determined (supposingly correct)
|
||||
# we separate raw part from the rest of arguments, which
|
||||
# should be normally processed.
|
||||
raw = arguments[end:]
|
||||
raw = raw.strip() or None
|
||||
|
||||
if not raw and not command.empty:
|
||||
raise CommandError("Missing arguments", command)
|
||||
|
||||
# Discard residual arguments and all of the options as raw
|
||||
# command does not support options and if an option is given it
|
||||
# is rather a part of a raw argument.
|
||||
# Discard residual arguments and all of the options as
|
||||
# raw command does not support options and if an option
|
||||
# is given it is rather a part of a raw argument.
|
||||
args = args[:spec_len - 1]
|
||||
opts = []
|
||||
|
||||
args.append((raw, (end, arguments_end)))
|
||||
else:
|
||||
# Substitue all of the arguments with only one, which contain
|
||||
# raw and unprocessed arguments as a string. And discard all the
|
||||
# options, as raw command does not support them.
|
||||
# Substitue all of the arguments with only one, which
|
||||
# contain raw and unprocessed arguments as a string. And
|
||||
# discard all the options, as raw command does not
|
||||
# support them.
|
||||
args = [(arguments, (0, arguments_end))]
|
||||
opts = []
|
||||
else:
|
||||
|
@ -189,16 +194,17 @@ def adapt_arguments(command, arguments, args, opts):
|
|||
else:
|
||||
raise CommandError("Missing arguments", command)
|
||||
|
||||
# The first stage of transforming options we have got to a format that can
|
||||
# be used to associate them with declared keyword arguments. Substituting
|
||||
# dashes (-) in their names with underscores (_).
|
||||
# The first stage of transforming options we have got to a format
|
||||
# that can be used to associate them with declared keyword
|
||||
# arguments. Substituting dashes (-) in their names with
|
||||
# underscores (_).
|
||||
for index, (key, value, position) in enumerate(opts):
|
||||
if '-' in key:
|
||||
opts[index] = (key.replace('-', '_'), value, position)
|
||||
|
||||
# The second stage of transforming options to an associatable state.
|
||||
# Expanding short, one-letter options to a verbose ones, if corresponding
|
||||
# optin has been given.
|
||||
# Expanding short, one-letter options to a verbose ones, if
|
||||
# corresponding optin has been given.
|
||||
if command.expand_short:
|
||||
expanded = []
|
||||
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)
|
||||
break
|
||||
|
||||
# Detect switches and set their values accordingly. If any of them carries a
|
||||
# value - append it to args.
|
||||
# Detect switches and set their values accordingly. If any of them
|
||||
# carries a value - append it to args.
|
||||
for index, (key, value, position) in enumerate(opts):
|
||||
if isinstance(norm_kwargs.get(key), BooleanType):
|
||||
opts[index] = (key, True, position)
|
||||
if value:
|
||||
args.append((value, position))
|
||||
|
||||
# Sorting arguments and options (just to be sure) in regarding to their
|
||||
# positions in the string.
|
||||
# Sorting arguments and options (just to be sure) in regarding to
|
||||
# their positions in the string.
|
||||
args.sort(key=itemgetter(1))
|
||||
opts.sort(key=itemgetter(2))
|
||||
|
||||
# Stripping down position information supplied with arguments and options as
|
||||
# it won't be needed again.
|
||||
# Stripping down position information supplied with arguments and
|
||||
# options as it won't be needed again.
|
||||
args = map(lambda (arg, position): arg, args)
|
||||
opts = map(lambda (key, value, position): (key, value), opts)
|
||||
|
||||
# If command has extra option enabled - collect all extra arguments and pass
|
||||
# them to a last positional argument command defines as a list.
|
||||
# If command has extra option enabled - collect all extra arguments
|
||||
# and pass them to a last positional argument command defines as a
|
||||
# list.
|
||||
if command.extra:
|
||||
if not var_args:
|
||||
spec_fix = 1 if not command.source else 2
|
||||
|
@ -240,9 +247,9 @@ def adapt_arguments(command, arguments, args, opts):
|
|||
else:
|
||||
raise DefinitionError("Can not have both, extra and *args")
|
||||
|
||||
# Detect if positional arguments overlap keyword arguments. If so and this
|
||||
# is allowed by command options - then map them directly to their options,
|
||||
# so they can get propert further processings.
|
||||
# Detect if positional arguments overlap keyword arguments. If so
|
||||
# and this is allowed by command options - then map them directly to
|
||||
# their options, so they can get propert further processings.
|
||||
spec_fix = 1 if command.source else 0
|
||||
spec_len = len(spec_args) - spec_fix
|
||||
if len(args) > spec_len:
|
||||
|
@ -262,49 +269,32 @@ def adapt_arguments(command, arguments, args, opts):
|
|||
if not isinstance(value, BooleanType):
|
||||
raise CommandError("%s: Switch can not take an argument" % key, command)
|
||||
|
||||
# Detect every sequence constraint and ensure that if corresponding options
|
||||
# are given - they contain proper values, within the constraint range.
|
||||
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.
|
||||
# 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):
|
||||
if isinstance(key, UnicodeType):
|
||||
opts[index] = (key.encode(KEY_ENCODING), value)
|
||||
|
||||
# Inject the source arguments as a string as a first argument, if command
|
||||
# has enabled the corresponding option.
|
||||
# Inject the source arguments as a string as a first argument, if
|
||||
# command has enabled the corresponding option.
|
||||
if command.source:
|
||||
args.insert(0, arguments)
|
||||
|
||||
# Return *args and **kwargs in the form suitable for passing to a command
|
||||
# handler and being expanded.
|
||||
# Return *args and **kwargs in the form suitable for passing to a
|
||||
# command handler and being expanded.
|
||||
return tuple(args), dict(opts)
|
||||
|
||||
def generate_usage(command, complete=True):
|
||||
"""
|
||||
Extract handler's arguments specification and wrap them in a human-readable
|
||||
format usage information. If complete is given - then USAGE_PATTERN will be
|
||||
used to render the specification completly.
|
||||
Extract handler's arguments specification and wrap them in a
|
||||
human-readable format usage information. If complete is given - then
|
||||
USAGE_PATTERN will be used to render the specification completly.
|
||||
"""
|
||||
spec_args, spec_kwargs, var_args, var_kwargs = command.extract_specification()
|
||||
|
||||
# Remove some special positional arguments from the specifiaction, but store
|
||||
# their names so they can be used for usage info generation.
|
||||
# Remove some special positional arguments from the specifiaction,
|
||||
# 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_extra = spec_args.pop() if command.extra else None
|
||||
|
||||
|
@ -317,8 +307,6 @@ def generate_usage(command, complete=True):
|
|||
|
||||
if isinstance(value, BooleanType):
|
||||
value = str()
|
||||
elif isinstance(value, (TupleType, ListType)):
|
||||
value = '={%s}' % ', '.join(value)
|
||||
else:
|
||||
value = '=%s' % value
|
||||
|
||||
|
@ -350,8 +338,8 @@ def generate_usage(command, complete=True):
|
|||
if var_kwargs:
|
||||
usage += (' ' if args else str()) + '[[%s]]' % var_kwargs
|
||||
|
||||
# Native name will be the first one if it is included. Otherwise, names will
|
||||
# be in the order they were specified.
|
||||
# Native name will be the first one if it is included. Otherwise,
|
||||
# names will be in the order they were specified.
|
||||
if len(command.names) > 1:
|
||||
names = '%s (%s)' % (command.first_name, ', '.join(command.names[1:]))
|
||||
else:
|
||||
|
|
|
@ -129,7 +129,9 @@ def split_db():
|
|||
print 'spliting database'
|
||||
if os.name == 'nt':
|
||||
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:
|
||||
OLD_LOG_DB_FOLDER = u'.'
|
||||
else:
|
||||
|
@ -184,7 +186,8 @@ def check_and_possibly_move_config():
|
|||
|
||||
if os.name == 'nt':
|
||||
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:
|
||||
OLD_LOG_DB_FOLDER = u'.'
|
||||
else:
|
||||
|
|
|
@ -430,13 +430,15 @@ class CommonConnection:
|
|||
try:
|
||||
gajim.logger.write(kind, jid, log_msg)
|
||||
except exceptions.PysqliteOperationalError, e:
|
||||
self.dispatch('ERROR', (_('Disk Write Error'), str(e)))
|
||||
self.dispatch('DB_ERROR', (_('Disk Write Error'),
|
||||
str(e)))
|
||||
except exceptions.DatabaseMalformed:
|
||||
pritext = _('Database Error')
|
||||
sectext = _('The database file (%s) cannot be read. Try to '
|
||||
'repair it (see http://trac.gajim.org/wiki/DatabaseBackup)'
|
||||
' or remove it (all history will be lost).') % \
|
||||
common.logger.LOG_DB_PATH
|
||||
self.dispatch('DB_ERROR', (pritext, sectext))
|
||||
|
||||
def ack_subscribed(self, jid):
|
||||
"""
|
||||
|
|
|
@ -49,7 +49,7 @@ from common.commands import ConnectionCommands
|
|||
from common.pubsub import ConnectionPubSub
|
||||
from common.pep import ConnectionPEP
|
||||
from common.protocol.caps import ConnectionCaps
|
||||
from common.protocol.bytestream import ConnectionBytestream
|
||||
from common.protocol.bytestream import ConnectionSocks5Bytestream
|
||||
import common.caps_cache as capscache
|
||||
if gajim.HAVE_FARSIGHT:
|
||||
from common.jingle import ConnectionJingle
|
||||
|
@ -915,13 +915,13 @@ class ConnectionHandlersBase:
|
|||
|
||||
return sess
|
||||
|
||||
class ConnectionHandlers(ConnectionVcard, ConnectionBytestream,
|
||||
ConnectionDisco, ConnectionCommands, ConnectionPubSub, ConnectionPEP,
|
||||
ConnectionCaps, ConnectionHandlersBase, ConnectionJingle):
|
||||
class ConnectionHandlers(ConnectionVcard, ConnectionSocks5Bytestream,
|
||||
ConnectionDisco, ConnectionCommands, ConnectionPubSub, ConnectionPEP,
|
||||
ConnectionCaps, ConnectionHandlersBase, ConnectionJingle):
|
||||
def __init__(self):
|
||||
global HAS_IDLE
|
||||
ConnectionVcard.__init__(self)
|
||||
ConnectionBytestream.__init__(self)
|
||||
ConnectionSocks5Bytestream.__init__(self)
|
||||
ConnectionCommands.__init__(self)
|
||||
ConnectionPubSub.__init__(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,
|
||||
subject=subject)
|
||||
except exceptions.PysqliteOperationalError, e:
|
||||
self.dispatch('ERROR', (_('Disk Write Error'), str(e)))
|
||||
self.dispatch('DB_ERROR', (_('Disk Write Error'), str(e)))
|
||||
except exceptions.DatabaseMalformed:
|
||||
pritext = _('Database Error')
|
||||
sectext = _('The database file (%s) cannot be read. Try to repair '
|
||||
'it (see http://trac.gajim.org/wiki/DatabaseBackup) or remove '
|
||||
'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,
|
||||
tim, session))
|
||||
|
||||
|
@ -1617,13 +1617,13 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream,
|
|||
self.last_history_time[jid] = mktime(tim)
|
||||
|
||||
except exceptions.PysqliteOperationalError, e:
|
||||
self.dispatch('ERROR', (_('Disk Write Error'), str(e)))
|
||||
self.dispatch('DB_ERROR', (_('Disk Write Error'), str(e)))
|
||||
except exceptions.DatabaseMalformed:
|
||||
pritext = _('Database Error')
|
||||
sectext = _('The database file (%s) cannot be read. Try to repair '
|
||||
'it (see http://trac.gajim.org/wiki/DatabaseBackup) or remove '
|
||||
'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):
|
||||
item = invite.getTag('invite')
|
||||
|
@ -1811,14 +1811,15 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream,
|
|||
try:
|
||||
gajim.logger.write('gcstatus', who, st, show)
|
||||
except exceptions.PysqliteOperationalError, e:
|
||||
self.dispatch('ERROR', (_('Disk Write Error'), str(e)))
|
||||
self.dispatch('DB_ERROR', (_('Disk Write Error'),
|
||||
str(e)))
|
||||
except exceptions.DatabaseMalformed:
|
||||
pritext = _('Database Error')
|
||||
sectext = _('The database file (%s) cannot be read. Try to '
|
||||
'repair it (see http://trac.gajim.org/wiki/DatabaseBackup)'
|
||||
' or remove it (all history will be lost).') % \
|
||||
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 == '':
|
||||
# contact has no avatar
|
||||
|
@ -1961,14 +1962,14 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream,
|
|||
try:
|
||||
gajim.logger.write('status', jid_stripped, status, show)
|
||||
except exceptions.PysqliteOperationalError, e:
|
||||
self.dispatch('ERROR', (_('Disk Write Error'), str(e)))
|
||||
self.dispatch('DB_ERROR', (_('Disk Write Error'), str(e)))
|
||||
except exceptions.DatabaseMalformed:
|
||||
pritext = _('Database Error')
|
||||
sectext = _('The database file (%s) cannot be read. Try to '
|
||||
'repair it (see http://trac.gajim.org/wiki/DatabaseBackup) '
|
||||
'or remove it (all history will be lost).') % \
|
||||
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)
|
||||
if jid_stripped == our_jid and resource == self.server_resource:
|
||||
# We got our own presence
|
||||
|
|
|
@ -496,11 +496,8 @@ class Contacts():
|
|||
return self._contacts.keys()
|
||||
|
||||
def get_contacts_jid_list(self):
|
||||
contacts = self._contacts.keys()
|
||||
for jid in self._contacts.keys():
|
||||
if self._contacts[jid][0].is_groupchat():
|
||||
contacts.remove(jid)
|
||||
return contacts
|
||||
return [jid for jid, contact in self._contacts.iteritems() if not
|
||||
contact[0].is_groupchat()]
|
||||
|
||||
def get_contact_from_full_jid(self, fjid):
|
||||
"""
|
||||
|
|
|
@ -42,6 +42,7 @@ except ImportError:
|
|||
else:
|
||||
try:
|
||||
# test if dbus-x11 is installed
|
||||
bus = dbus.SystemBus()
|
||||
bus = dbus.SessionBus()
|
||||
supported = True # does user have D-Bus bindings?
|
||||
except dbus.DBusException:
|
||||
|
@ -49,6 +50,12 @@ else:
|
|||
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 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:
|
||||
"""
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
|
||||
##
|
||||
|
||||
import sys
|
||||
import re
|
||||
import locale
|
||||
import os
|
||||
|
|
|
@ -704,7 +704,9 @@ class OptionsParser:
|
|||
"""
|
||||
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'):
|
||||
path = gajim.config.get_per('soundevents', evt, 'path')
|
||||
# absolute and relative passes are necessary
|
||||
|
|
|
@ -75,6 +75,190 @@ class ConnectionBytestream:
|
|||
def __init__(self):
|
||||
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):
|
||||
"""
|
||||
Send reply to the initiator of FT that we made a connection
|
||||
|
@ -252,92 +436,6 @@ class ConnectionBytestream:
|
|||
else:
|
||||
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):
|
||||
"""
|
||||
Store the result of SHA message from auth
|
||||
|
@ -406,9 +504,6 @@ class ConnectionBytestream:
|
|||
self.dispatch('FILE_REQUEST_ERROR', (jid, file_props, ''))
|
||||
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):
|
||||
target = unicode(iq_obj.getAttr('to'))
|
||||
id_ = unicode(iq_obj.getAttr('id'))
|
||||
|
@ -466,9 +561,6 @@ class ConnectionBytestream:
|
|||
gajim.socks5queue.activate_proxy(host['idx'])
|
||||
raise xmpp.NodeProcessed
|
||||
|
||||
def _ft_get_streamhost_jid_attr(self, streamhost):
|
||||
return helpers.parse_jid(streamhost.getAttr('jid'))
|
||||
|
||||
def _bytestreamResultCB(self, con, iq_obj):
|
||||
frm = self._ft_get_from(iq_obj)
|
||||
real_id = unicode(iq_obj.getAttr('id'))
|
||||
|
@ -542,98 +634,7 @@ class ConnectionBytestream:
|
|||
|
||||
raise xmpp.NodeProcessed
|
||||
|
||||
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 ConnectionBytestreamZeroconf(ConnectionBytestream):
|
||||
class ConnectionSocks5BytestreamZeroconf(ConnectionSocks5Bytestream):
|
||||
|
||||
def _ft_get_from(self, iq_obj):
|
||||
return unicode(iq_obj.getFrom())
|
||||
|
|
|
@ -29,6 +29,8 @@ import random
|
|||
import itertools
|
||||
import dispatcher_nb
|
||||
import hashlib
|
||||
import hmac
|
||||
import hashlib
|
||||
|
||||
import logging
|
||||
log = logging.getLogger('gajim.c.x.auth_nb')
|
||||
|
@ -113,6 +115,8 @@ def challenge_splitter(data):
|
|||
quotes_open = False
|
||||
return dict_
|
||||
|
||||
def scram_parse(chatter):
|
||||
return dict(s.split('=', 1) for s in chatter.split(','))
|
||||
|
||||
class SASL(PlugIn):
|
||||
"""
|
||||
|
@ -231,6 +235,13 @@ class SASL(PlugIn):
|
|||
raise NodeProcessed
|
||||
except kerberos.GSSError, 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:
|
||||
self.mecs.remove('DIGEST-MD5')
|
||||
node = Node('auth', attrs={'xmlns': NS_SASL, 'mechanism': 'DIGEST-MD5'})
|
||||
|
@ -241,12 +252,12 @@ class SASL(PlugIn):
|
|||
if 'PLAIN' in self.mecs:
|
||||
self.mecs.remove('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
|
||||
raise NodeProcessed
|
||||
self.startsasl = SASL_FAILURE
|
||||
log.info('I can only use EXTERNAL, DIGEST-MD5, GSSAPI and PLAIN '
|
||||
'mecanisms.')
|
||||
log.info('I can only use EXTERNAL, SCRAM-SHA-1, DIGEST-MD5, GSSAPI and '
|
||||
'PLAIN mecanisms.')
|
||||
if self.on_sasl:
|
||||
self.on_sasl()
|
||||
return
|
||||
|
@ -273,6 +284,8 @@ class SASL(PlugIn):
|
|||
self.on_sasl()
|
||||
raise NodeProcessed
|
||||
elif challenge.getName() == 'success':
|
||||
# TODO: Need to validate any data-with-success.
|
||||
# TODO: Important for DIGEST-MD5 and SCRAM.
|
||||
self.startsasl = SASL_SUCCESS
|
||||
log.info('Successfully authenticated with remote server.')
|
||||
handlers = self._owner.Dispatcher.dumpHandlers()
|
||||
|
@ -310,9 +323,73 @@ class SASL(PlugIn):
|
|||
response = kerberos.authGSSClientResponse(self.gss_vc)
|
||||
if not response:
|
||||
response = ''
|
||||
self._owner.send(Node('response', attrs={'xmlns':NS_SASL},
|
||||
self._owner.send(Node('response', attrs={'xmlns': NS_SASL},
|
||||
payload=response).__str__())
|
||||
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...
|
||||
chal = challenge_splitter(data)
|
||||
|
@ -350,7 +427,16 @@ class SASL(PlugIn):
|
|||
self.password = ''
|
||||
else:
|
||||
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):
|
||||
try:
|
||||
string = string.decode('utf-8').encode('iso-8859-1')
|
||||
|
|
|
@ -362,6 +362,9 @@ class NonBlockingClient:
|
|||
supported and desired.
|
||||
"""
|
||||
self.stream_started = True
|
||||
if not hasattr(self, 'onreceive'):
|
||||
# we may already have been disconnected
|
||||
return
|
||||
self.onreceive(None)
|
||||
|
||||
if self.connected == 'plain':
|
||||
|
|
|
@ -469,10 +469,6 @@ class XMPPDispatcher(PlugIn):
|
|||
# we have released dispatcher, so self._owner has no methods
|
||||
if not res:
|
||||
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():
|
||||
if _iq is None:
|
||||
# If the expected Stanza would have arrived, ProcessNonBlocking
|
||||
|
|
|
@ -351,6 +351,8 @@ class NonBlockingTLS(PlugIn):
|
|||
tcpsock = self._owner
|
||||
# See http://docs.python.org/dev/library/ssl.html
|
||||
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._sslContext.set_verify(OpenSSL.SSL.VERIFY_PEER,
|
||||
self._ssl_verify_callback)
|
||||
|
|
|
@ -35,7 +35,7 @@ from common import gajim
|
|||
from common.zeroconf import zeroconf
|
||||
from common.commands import ConnectionCommands
|
||||
from common.pep import ConnectionPEP
|
||||
from common.protocol.bytestream import ConnectionBytestreamZeroconf
|
||||
from common.protocol.bytestream import ConnectionSocks5BytestreamZeroconf
|
||||
|
||||
import logging
|
||||
log = logging.getLogger('gajim.c.z.connection_handlers_zeroconf')
|
||||
|
@ -70,11 +70,12 @@ class ConnectionVcard(connection_handlers.ConnectionVcard):
|
|||
pass
|
||||
|
||||
|
||||
class ConnectionHandlersZeroconf(ConnectionVcard, ConnectionBytestreamZeroconf,
|
||||
ConnectionCommands, ConnectionPEP, connection_handlers.ConnectionHandlersBase):
|
||||
class ConnectionHandlersZeroconf(ConnectionVcard,
|
||||
ConnectionSocks5BytestreamZeroconf, ConnectionCommands, ConnectionPEP,
|
||||
connection_handlers.ConnectionHandlersBase):
|
||||
def __init__(self):
|
||||
ConnectionVcard.__init__(self)
|
||||
ConnectionBytestreamZeroconf.__init__(self)
|
||||
ConnectionSocks5BytestreamZeroconf.__init__(self)
|
||||
ConnectionCommands.__init__(self)
|
||||
connection_handlers.ConnectionHandlersBase.__init__(self)
|
||||
|
||||
|
|
|
@ -703,6 +703,7 @@ class ConversationTextview(gobject.GObject):
|
|||
size = 2 * size - 1
|
||||
self.marks_queue = Queue.Queue(size)
|
||||
self.focus_out_end_mark = None
|
||||
self.just_cleared = True
|
||||
|
||||
def visit_url_from_menuitem(self, widget, link):
|
||||
"""
|
||||
|
@ -1167,6 +1168,7 @@ class ConversationTextview(gobject.GObject):
|
|||
buffer_ = self.tv.get_buffer()
|
||||
end_iter = buffer_.get_end_iter()
|
||||
buffer_.insert_with_tags_by_name(end_iter, '\n', 'eol')
|
||||
self.just_cleared = False
|
||||
|
||||
def print_conversation_line(self, text, jid, kind, name, tim,
|
||||
other_tags_for_name=[], other_tags_for_time=[],
|
||||
|
@ -1246,7 +1248,7 @@ class ConversationTextview(gobject.GObject):
|
|||
text_tags.append(other_text_tag)
|
||||
else: # not status nor /me
|
||||
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)
|
||||
else:
|
||||
self.print_real_text(gajim.config.get(
|
||||
|
@ -1269,6 +1271,7 @@ class ConversationTextview(gobject.GObject):
|
|||
else:
|
||||
gobject.idle_add(self.scroll_to_end)
|
||||
|
||||
self.just_cleared = False
|
||||
buffer_.end_user_action()
|
||||
|
||||
def get_time_to_show(self, tim):
|
||||
|
|
|
@ -38,6 +38,7 @@ import vcard
|
|||
import conversation_textview
|
||||
import message_control
|
||||
import dataforms_widget
|
||||
import disco
|
||||
|
||||
from random import randrange
|
||||
from common import pep
|
||||
|
@ -2156,7 +2157,6 @@ class JoinGroupchatWindow:
|
|||
self._nickname_entry = self.xml.get_object('nickname_entry')
|
||||
self._password_entry = self.xml.get_object('password_entry')
|
||||
|
||||
self._room_jid_entry.set_text(room_jid)
|
||||
self._nickname_entry.set_text(nick)
|
||||
if password:
|
||||
self._password_entry.set_text(password)
|
||||
|
@ -2171,6 +2171,13 @@ class JoinGroupchatWindow:
|
|||
title = _('Join Group Chat')
|
||||
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')
|
||||
liststore = gtk.ListStore(str)
|
||||
self.recently_combobox.set_model(liststore)
|
||||
|
@ -2180,6 +2187,16 @@ class JoinGroupchatWindow:
|
|||
self.recently_groupchat = gajim.config.get('recently_groupchat').split()
|
||||
for g in self.recently_groupchat:
|
||||
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:
|
||||
self.recently_combobox.set_sensitive(False)
|
||||
elif room_jid == '':
|
||||
|
@ -2229,11 +2246,38 @@ class JoinGroupchatWindow:
|
|||
self.account = model[iter_][0].decode('utf-8')
|
||||
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):
|
||||
model = widget.get_model()
|
||||
iter_ = widget.get_active_iter()
|
||||
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):
|
||||
"""
|
||||
|
@ -2258,7 +2302,9 @@ class JoinGroupchatWindow:
|
|||
'groupchat.'))
|
||||
return
|
||||
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')
|
||||
try:
|
||||
nickname = helpers.parse_resource(nickname)
|
||||
|
|
|
@ -492,8 +492,8 @@ class ServiceDiscoveryWindow(object):
|
|||
Class that represents the Services Discovery window
|
||||
"""
|
||||
|
||||
def __init__(self, account, jid = '', node = '',
|
||||
address_entry = False, parent = None):
|
||||
def __init__(self, account, jid='', node='', address_entry=False,
|
||||
parent=None, initial_identities=None):
|
||||
self.account = account
|
||||
self.parent = parent
|
||||
if not jid:
|
||||
|
@ -519,6 +519,9 @@ _('Without a connection, you can not browse available services'))
|
|||
self.cache = ServicesCache(account)
|
||||
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.window = self.xml.get_object('service_discovery_window')
|
||||
self.services_treeview = self.xml.get_object('services_treeview')
|
||||
|
|
|
@ -627,7 +627,8 @@ def get_avatar_pixbuf_from_cache(fjid, use_local=True):
|
|||
# don't show avatar for the transport itself
|
||||
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
|
||||
else:
|
||||
is_groupchat_contact = False
|
||||
|
|
|
@ -119,6 +119,15 @@ class Interface:
|
|||
#('ERROR', account, (title_text, section_text))
|
||||
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):
|
||||
#('INFORMATION', account, (title_text, section_text))
|
||||
dialogs.InformationDialog(data[0], data[1])
|
||||
|
@ -480,6 +489,8 @@ class Interface:
|
|||
jid_ = info['jid']
|
||||
c_ = gajim.contacts.get_contact_with_highest_priority(
|
||||
acct_, jid_)
|
||||
if not c_:
|
||||
continue
|
||||
if c_.show not in ('offline', 'error'):
|
||||
show_notif = False
|
||||
break
|
||||
|
@ -507,6 +518,8 @@ class Interface:
|
|||
jid_ = info['jid']
|
||||
c_ = gajim.contacts.get_contact_with_highest_priority(
|
||||
acct_, jid_)
|
||||
if not c_:
|
||||
continue
|
||||
if c_.show not in ('offline', 'error'):
|
||||
show_notif = False
|
||||
break
|
||||
|
@ -2086,6 +2099,7 @@ class Interface:
|
|||
'ROSTER': [self.handle_event_roster],
|
||||
'WARNING': [self.handle_event_warning],
|
||||
'ERROR': [self.handle_event_error],
|
||||
'DB_ERROR': [self.handle_event_db_error],
|
||||
'INFORMATION': [self.handle_event_information],
|
||||
'ERROR_ANSWER': [self.handle_event_error_answer],
|
||||
'STATUS': [self.handle_event_status],
|
||||
|
@ -3277,6 +3291,7 @@ class Interface:
|
|||
self.status_sent_to_groups = {}
|
||||
self.gpg_passphrase = {}
|
||||
self.pass_dialog = {}
|
||||
self.db_error_dialog = None
|
||||
self.default_colors = {
|
||||
'inmsgcolor': gajim.config.get('inmsgcolor'),
|
||||
'outmsgcolor': gajim.config.get('outmsgcolor'),
|
||||
|
|
|
@ -1398,7 +1398,7 @@ class RosterWindow:
|
|||
self.on_modelfilter_row_has_child_toggled)
|
||||
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_contacts(acct)
|
||||
# Recalculate column width for ellipsizing
|
||||
|
|
Loading…
Reference in New Issue