summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/args.py27
-rw-r--r--src/connection.py27
-rw-r--r--src/plugin_manager.py65
-rw-r--r--src/text_buffer.py111
4 files changed, 165 insertions, 65 deletions
diff --git a/src/args.py b/src/args.py
index 05281fa0..6b0108f0 100644
--- a/src/args.py
+++ b/src/args.py
@@ -3,6 +3,7 @@ Module related to the argument parsing
There is a fallback to the deprecated optparse if argparse is not found
"""
+from gettext import gettext as _
from os import path
def parse_args(CONFIG_PATH=''):
@@ -15,20 +16,28 @@ def parse_args(CONFIG_PATH=''):
from optparse import OptionParser
from optparse import SUPPRESS_HELP as SUPPRESS
parser = OptionParser()
- parser.add_option("-f", "--file", dest="filename", default=path.join(CONFIG_PATH, 'poezio.cfg'),
- help="The config file you want to use", metavar="CONFIG_FILE")
+ parser.add_option("-f", "--file", dest="filename",
+ default=path.join(CONFIG_PATH, 'poezio.cfg'),
+ help=_("The config file you want to use"),
+ metavar="CONFIG_FILE")
parser.add_option("-d", "--debug", dest="debug",
- help="The file where debug will be written", metavar="DEBUG_FILE")
+ help=_("The file where debug will be written"),
+ metavar="DEBUG_FILE")
parser.add_option("-v", "--version", dest="version",
- help=SUPPRESS, metavar="VERSION", default="0.8.3-dev")
- (options, _) = parser.parse_args()
+ help=SUPPRESS, metavar="VERSION",
+ default="0.8.3-dev")
+ (options, __) = parser.parse_args()
else:
parser = ArgumentParser()
- parser.add_argument("-f", "--file", dest="filename", default=path.join(CONFIG_PATH, 'poezio.cfg'),
- help="The config file you want to use", metavar="CONFIG_FILE")
+ parser.add_argument("-f", "--file", dest="filename",
+ default=path.join(CONFIG_PATH, 'poezio.cfg'),
+ help=_("The config file you want to use"),
+ metavar="CONFIG_FILE")
parser.add_argument("-d", "--debug", dest="debug",
- help="The file where debug will be written", metavar="DEBUG_FILE")
+ help=_("The file where debug will be written"),
+ metavar="DEBUG_FILE")
parser.add_argument("-v", "--version", dest="version",
- help=SUPPRESS, metavar="VERSION", default="0.8-dev")
+ help=SUPPRESS, metavar="VERSION",
+ default="0.8.3-dev")
options = parser.parse_args()
return options
diff --git a/src/connection.py b/src/connection.py
index 5dc7c8e4..05c79716 100644
--- a/src/connection.py
+++ b/src/connection.py
@@ -29,9 +29,10 @@ class Connection(sleekxmpp.ClientXMPP):
def __init__(self):
resource = config.get('resource', '')
if config.get('jid', ''):
- self.anon = False # Field used to know if we are anonymous or not.
- # many features will be handled diferently
+ # Field used to know if we are anonymous or not.
+ # many features will be handled differently
# depending on this setting
+ self.anon = False
jid = '%s' % config.get('jid', '')
if resource:
jid = '%s/%s'% (jid, resource)
@@ -44,7 +45,8 @@ class Connection(sleekxmpp.ClientXMPP):
password = None
jid = safeJID(jid)
# TODO: use the system language
- sleekxmpp.ClientXMPP.__init__(self, jid, password, lang=config.get('lang', 'en'))
+ sleekxmpp.ClientXMPP.__init__(self, jid, password,
+ lang=config.get('lang', 'en'))
force_encryption = config.get('force_encryption', True)
if force_encryption:
@@ -59,7 +61,9 @@ class Connection(sleekxmpp.ClientXMPP):
self.auto_authorize = None
# prosody defaults, lowest is AES128-SHA, it should be a minimum
# for anything that came out after 2002
- self.ciphers = config.get('ciphers', 'HIGH+kEDH:HIGH+kEECDH:HIGH:!PSK:!SRP:!3DES:!aNULL')
+ self.ciphers = config.get('ciphers',
+ 'HIGH+kEDH:HIGH+kEECDH:HIGH:!PSK'
+ ':!SRP:!3DES:!aNULL')
self.ca_certs = config.get('ca_cert_path', '') or None
interval = config.get('whitespace_interval', '300')
if interval.isdecimal() and int(interval) > 0:
@@ -138,12 +142,15 @@ class Connection(sleekxmpp.ClientXMPP):
self.plugin['xep_0199'].disable_keepalive()
# If the ping_interval is 0 or less, we just disable the keepalive
if ping_interval > 0:
- self.plugin['xep_0199'].enable_keepalive(ping_interval, timeout_delay)
+ self.plugin['xep_0199'].enable_keepalive(ping_interval,
+ timeout_delay)
def start(self):
- # TODO, try multiple servers
- # With anon auth.
- # (domain, config.get('port', 5222))
+ """
+ Connect and process events.
+
+ TODO: try multiple servers with anon auth.
+ """
custom_host = config.get('custom_host', '')
custom_port = config.get('custom_port', 5222)
if custom_port == -1:
@@ -151,7 +158,8 @@ class Connection(sleekxmpp.ClientXMPP):
if custom_host:
res = self.connect((custom_host, custom_port), reattempt=True)
elif custom_port != 5222 and custom_port != -1:
- res = self.connect((self.boundjid.host, custom_port), reattempt=True)
+ res = self.connect((self.boundjid.host, custom_port),
+ reattempt=True)
else:
res = self.connect(reattempt=True)
if not res:
@@ -172,4 +180,5 @@ class MatchAll(sleekxmpp.xmlstream.matcher.base.MatcherBase):
Callback to retrieve all the stanzas for the XML tab
"""
def match(self, xml):
+ "match everything"
return True
diff --git a/src/plugin_manager.py b/src/plugin_manager.py
index b3816939..fc8de462 100644
--- a/src/plugin_manager.py
+++ b/src/plugin_manager.py
@@ -27,13 +27,21 @@ class PluginManager(object):
"""
def __init__(self, core):
self.core = core
- self.modules = {} # module name -> module object
- self.plugins = {} # module name -> plugin object
- self.commands = {} # module name -> dict of commands loaded for the module
- self.event_handlers = {} # module name -> list of event_name/handler pairs loaded for the module
- self.tab_commands = {} #module name -> dict of tab types; tab type -> commands loaded by the module
- self.keys = {} # module name → dict of keys/handlers loaded for the module
- self.tab_keys = {} #module name → dict of tab types; tab type → list of keybinds (tuples)
+ # module name -> module object
+ self.modules = {}
+ # module name -> plugin object
+ self.plugins = {}
+ # module name -> dict of commands loaded for the module
+ self.commands = {}
+ # module name -> list of event_name/handler pairs loaded for the module
+ self.event_handlers = {}
+ # module name -> dict of tab types; tab type -> commands
+ # loaded by the module
+ self.tab_commands = {}
+ # module name → dict of keys/handlers loaded for the module
+ self.keys = {}
+ # module name → dict of tab types; tab type → list of keybinds (tuples)
+ self.tab_keys = {}
self.roster_elements = {}
if version_info[1] >= 3: # 3.3 & >
@@ -67,7 +75,8 @@ class PluginManager(object):
imp.acquire_lock()
module = imp.reload(self.modules[name])
else:
- file, filename, info = imp.find_module(name, self.load_path)
+ file, filename, info = imp.find_module(name,
+ self.load_path)
imp.acquire_lock()
module = imp.load_module(name, file, filename, info)
else: # 3.3 & >
@@ -79,7 +88,8 @@ class PluginManager(object):
except Exception as e:
log.debug("Could not load plugin %s", name, exc_info=True)
- self.core.information("Could not load plugin %s: %s" % (name, e), 'Error')
+ self.core.information("Could not load plugin %s: %s" % (name, e),
+ 'Error')
finally:
if version_info[1] < 3 and imp.lock_held():
imp.release_lock()
@@ -94,11 +104,14 @@ class PluginManager(object):
self.event_handlers[name] = []
try:
self.plugins[name] = None
- self.plugins[name] = module.Plugin(self.plugin_api, self.core, self.plugins_conf_dir)
+ self.plugins[name] = module.Plugin(self.plugin_api, self.core,
+ self.plugins_conf_dir)
except Exception as e:
log.error('Error while loading the plugin %s', name, exc_info=True)
if notify:
- self.core.information('Unable to load the plugin %s: %s' % (name, e), 'Error')
+ self.core.information(_('Unable to load the plugin %s: %s') %
+ (name, e),
+ 'Error')
self.unload(name, notify=False)
else:
if notify:
@@ -113,7 +126,8 @@ class PluginManager(object):
del self.core.key_func[key]
for tab in list(self.tab_commands[name].keys()):
for command in self.tab_commands[name][tab][:]:
- self.del_tab_command(name, getattr(tabs, tab), command[0])
+ self.del_tab_command(name, getattr(tabs, tab),
+ command[0])
del self.tab_commands[name][tab]
for tab in list(self.tab_keys[name].keys()):
for key in self.tab_keys[name][tab][:]:
@@ -133,9 +147,12 @@ class PluginManager(object):
self.core.information('Plugin %s unloaded' % name, 'Info')
except Exception as e:
log.debug("Could not unload plugin %s", name, exc_info=True)
- self.core.information("Could not unload plugin %s: %s" % (name, e), 'Error')
+ self.core.information(_("Could not unload plugin %s: %s") %
+ (name, e),
+ 'Error')
- def add_command(self, module_name, name, handler, help, completion=None, short='', usage=''):
+ def add_command(self, module_name, name, handler, help,
+ completion=None, short='', usage=''):
"""
Add a global command.
"""
@@ -155,7 +172,8 @@ class PluginManager(object):
if name in self.core.commands:
del self.core.commands[name]
- def add_tab_command(self, module_name, tab_type, name, handler, help, completion=None, short='', usage=''):
+ def add_tab_command(self, module_name, tab_type, name, handler, help,
+ completion=None, short='', usage=''):
"""
Add a command only for a type of Tab.
"""
@@ -166,7 +184,8 @@ class PluginManager(object):
if not t in commands:
commands[t] = []
commands[t].append((name, handler, help, completion))
- tab_type.plugin_commands[name] = core.Command(handler, help, completion, short, usage)
+ tab_type.plugin_commands[name] = core.Command(handler, help,
+ completion, short, usage)
for tab in self.core.tabs:
if isinstance(tab, tab_type):
tab.update_commands()
@@ -282,14 +301,16 @@ class PluginManager(object):
and name != '__init__.py' and not name.startswith('.')]
plugins_files.sort()
position = the_input.get_argument_position(quoted=False)
- return the_input.new_completion(plugins_files, position, '', quotify=False)
+ return the_input.new_completion(plugins_files, position, '',
+ quotify=False)
def completion_unload(self, the_input):
"""
- completion function that completes the name of the plugins that are loaded
+ completion function that completes the name of loaded plugins
"""
position = the_input.get_argument_position(quoted=False)
- return the_input.new_completion(sorted(self.plugins.keys()), position, '', quotify=False)
+ return the_input.new_completion(sorted(self.plugins.keys()), position,
+ '', quotify=False)
def on_plugins_dir_change(self, new_value):
self.plugins_dir = new_value
@@ -334,7 +355,8 @@ class PluginManager(object):
plugins_dir = config.get('plugins_dir', '')
plugins_dir = plugins_dir or\
os.path.join(os.environ.get('XDG_DATA_HOME') or\
- os.path.join(os.environ.get('HOME'), '.local', 'share'),
+ os.path.join(os.environ.get('HOME'),
+ '.local', 'share'),
'poezio', 'plugins')
self.plugins_dir = os.path.expanduser(plugins_dir)
self.check_create_plugins_dir()
@@ -360,7 +382,8 @@ class PluginManager(object):
self.load_path = []
- default_plugin_path = path.join(path.dirname(path.dirname(__file__)), 'plugins')
+ default_plugin_path = path.join(path.dirname(path.dirname(__file__)),
+ 'plugins')
if os.access(default_plugin_path, os.R_OK | os.X_OK):
self.load_path.insert(0, default_plugin_path)
diff --git a/src/text_buffer.py b/src/text_buffer.py
index 71820b2f..ed213d2d 100644
--- a/src/text_buffer.py
+++ b/src/text_buffer.py
@@ -1,12 +1,11 @@
-# Copyright 2010-2011 Florent Le Coz <louiz@louiz.org>
-#
-# This file is part of Poezio.
-#
-# Poezio is free software: you can redistribute it and/or modify
-# it under the terms of the zlib license. See the COPYING file.
-
"""
Define the TextBuffer class
+
+A text buffer contains a list of intermediate representations of messages
+(not xml stanzas, but neither the Lines used in windows.py.
+
+Each text buffer can be linked to multiple windows, that will be rendered
+independantly by their TextWins.
"""
import logging
@@ -18,13 +17,15 @@ from datetime import datetime
from config import config
from theming import get_theme, dump_tuple
-message_fields = 'txt nick_color time str_time nickname user identifier highlight me old_message revisions jid'
+message_fields = ('txt nick_color time str_time nickname user identifier'
+ ' highlight me old_message revisions jid')
Message = collections.namedtuple('Message', message_fields)
class CorrectionError(Exception):
pass
def other_elems(self):
+ "Helper for the repr_message function"
acc = ['Message(']
fields = message_fields.split()
fields.remove('old_message')
@@ -33,6 +34,11 @@ def other_elems(self):
return ', '.join(acc) + ', old_message='
def repr_message(self):
+ """
+ repr() for the Message class, for debug purposes, since the default
+ repr() is recursive, so it can stack overflow given too many revisions
+ of a message
+ """
init = other_elems(self)
acc = [init]
next_message = self.old_message
@@ -55,12 +61,17 @@ class TextBuffer(object):
This class just keep trace of messages, in a list with various
informations and attributes.
"""
- def __init__(self, messages_nb_limit=config.get('max_messages_in_memory', 2048)):
+ def __init__(self, messages_nb_limit=None):
+
+ if messages_nb_limit is None:
+ messages_nb_limit = config.get('max_messages_in_memory', 2048)
self.messages_nb_limit = messages_nb_limit
- self.messages = [] # Message objects
- self.windows = [] # we keep track of one or more windows
+ # Message objects
+ self.messages = []
+ # we keep track of one or more windows
# so we can pass the new messages to them, as they are added, so
# they (the windows) can build the lines from the new message
+ self.windows = []
def add_window(self, win):
self.windows.append(win)
@@ -71,19 +82,35 @@ class TextBuffer(object):
@staticmethod
- def make_message(txt, time, nickname, nick_color, history, user, identifier, str_time=None, highlight=False, old_message=None, revisions=0, jid=None):
+ def make_message(txt, time, nickname, nick_color, history, user,
+ identifier, str_time=None, highlight=False,
+ old_message=None, revisions=0, jid=None):
+ """
+ Create a new Message object with parameters, check for /me messages,
+ and delayed messages
+ """
time = time or datetime.now()
- me = False
if txt.startswith('/me '):
me = True
- txt = '\x19%(info_col)s}' % {'info_col': get_theme().COLOR_ME_MESSAGE[0]} + txt[4:]
+ txt = '\x19%s}%s' % (dump_tuple(get_theme().COLOR_ME_MESSAGE),
+ txt[4:])
+ else:
+ me = False
if history:
- txt = txt.replace('\x19o', '\x19o\x19%s}' % dump_tuple(get_theme().COLOR_LOG_MSG))
+ txt = txt.replace('\x19o', '\x19o\x19%s}' %
+ dump_tuple(get_theme().COLOR_LOG_MSG))
+ str_time = time.strftime("%Y-%m-%d %H:%M:%S")
+ else:
+ if str_time is None:
+ str_time = time.strftime("%H:%M:%S")
+ else:
+ str_time = ''
+
msg = Message(
txt='%s\x19o'%(txt.replace('\t', ' '),),
nick_color=nick_color,
time=time,
- str_time=(time.strftime("%Y-%m-%d %H:%M:%S") if history else time.strftime("%H:%M:%S")) if str_time is None else '',
+ str_time=str_time,
nickname=nickname,
user=user,
identifier=identifier,
@@ -95,42 +122,74 @@ class TextBuffer(object):
log.debug('Set message %s with %s.', identifier, msg)
return msg
- def add_message(self, txt, time=None, nickname=None, nick_color=None, history=None, user=None, highlight=False, identifier=None, str_time=None, jid=None):
- msg = self.make_message(txt, time, nickname, nick_color, history, user, identifier, str_time=str_time, highlight=highlight, jid=jid)
+ def add_message(self, txt, time=None, nickname=None,
+ nick_color=None, history=None, user=None, highlight=False,
+ identifier=None, str_time=None, jid=None):
+ """
+ Create a message and add it to the text buffer
+ """
+ msg = self.make_message(txt, time, nickname, nick_color, history,
+ user, identifier, str_time=str_time,
+ highlight=highlight, jid=jid)
self.messages.append(msg)
+
while len(self.messages) > self.messages_nb_limit:
self.messages.pop(0)
+
ret_val = None
+ show_timestamps = config.get('show_timestamps', True)
for window in self.windows: # make the associated windows
- # build the lines from the new message
- nb = window.build_new_message(msg, history=history, highlight=highlight, timestamp=config.get("show_timestamps", True))
+ # build the lines from the new message
+ nb = window.build_new_message(msg, history=history,
+ highlight=highlight,
+ timestamp=show_timestamps)
if ret_val is None:
ret_val = nb
if window.pos != 0:
window.scroll_up(nb)
+
return ret_val or 1
- def modify_message(self, txt, old_id, new_id, highlight=False, time=None, user=None, jid=None):
+ def modify_message(self, txt, old_id, new_id, highlight=False,
+ time=None, user=None, jid=None):
+ """
+ Correct a message in a text buffer.
+ """
+
for i in range(len(self.messages) -1, -1, -1):
msg = self.messages[i]
+
if msg.identifier == old_id:
if msg.user and msg.user is not user:
raise CorrectionError("Different users")
elif len(msg.str_time) > 8: # ugly
raise CorrectionError("Delayed message")
elif not msg.user and (msg.jid is None or jid is None):
- raise CorrectionError('Could not check the identity of the sender')
+ raise CorrectionError('Could not check the '
+ 'identity of the sender')
elif not msg.user and msg.jid != jid:
- raise CorrectionError('Messages %s and %s have not been sent by the same fullJID' % (old_id, new_id))
- message = self.make_message(txt, time if time else msg.time, msg.nickname, msg.nick_color, None, msg.user, new_id, highlight=highlight, old_message=msg, revisions=msg.revisions + 1, jid=jid)
+ raise CorrectionError('Messages %s and %s have not been '
+ 'sent by the same fullJID' %
+ (old_id, new_id))
+
+ if not time:
+ time = msg.time
+ message = self.make_message(txt, time, msg.nickname,
+ msg.nick_color, None, msg.user,
+ new_id, highlight=highlight,
+ old_message=msg,
+ revisions=msg.revisions + 1,
+ jid=jid)
self.messages[i] = message
log.debug('Replacing message %s with %s.', old_id, new_id)
return message
- log.debug('Message %s not found in text_buffer, abort replacement.', old_id)
+ log.debug('Message %s not found in text_buffer, abort replacement.',
+ old_id)
raise CorrectionError("nothing to replace")
def del_window(self, win):
self.windows.remove(win)
def __del__(self):
- log.debug('** Deleting %s messages from textbuffer', len(self.messages))
+ size = len(self.messages)
+ log.debug('** Deleting %s messages from textbuffer', size)