Commit 34156198 authored by mathieui's avatar mathieui

80-columns wrapping and some docstrings

also bump version, and add some gettext wraps
parent b14aceaa
...@@ -3,6 +3,7 @@ Module related to the argument parsing ...@@ -3,6 +3,7 @@ Module related to the argument parsing
There is a fallback to the deprecated optparse if argparse is not found There is a fallback to the deprecated optparse if argparse is not found
""" """
from gettext import gettext as _
from os import path from os import path
def parse_args(CONFIG_PATH=''): def parse_args(CONFIG_PATH=''):
...@@ -15,20 +16,28 @@ def parse_args(CONFIG_PATH=''): ...@@ -15,20 +16,28 @@ def parse_args(CONFIG_PATH=''):
from optparse import OptionParser from optparse import OptionParser
from optparse import SUPPRESS_HELP as SUPPRESS from optparse import SUPPRESS_HELP as SUPPRESS
parser = OptionParser() parser = OptionParser()
parser.add_option("-f", "--file", dest="filename", default=path.join(CONFIG_PATH, 'poezio.cfg'), parser.add_option("-f", "--file", dest="filename",
help="The config file you want to use", metavar="CONFIG_FILE") 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", 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", parser.add_option("-v", "--version", dest="version",
help=SUPPRESS, metavar="VERSION", default="0.8.3-dev") help=SUPPRESS, metavar="VERSION",
(options, _) = parser.parse_args() default="0.8.3-dev")
(options, __) = parser.parse_args()
else: else:
parser = ArgumentParser() parser = ArgumentParser()
parser.add_argument("-f", "--file", dest="filename", default=path.join(CONFIG_PATH, 'poezio.cfg'), parser.add_argument("-f", "--file", dest="filename",
help="The config file you want to use", metavar="CONFIG_FILE") 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", 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", 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() options = parser.parse_args()
return options return options
...@@ -29,9 +29,10 @@ class Connection(sleekxmpp.ClientXMPP): ...@@ -29,9 +29,10 @@ class Connection(sleekxmpp.ClientXMPP):
def __init__(self): def __init__(self):
resource = config.get('resource', '') resource = config.get('resource', '')
if config.get('jid', ''): if config.get('jid', ''):
self.anon = False # Field used to know if we are anonymous or not. # Field used to know if we are anonymous or not.
# many features will be handled diferently # many features will be handled differently
# depending on this setting # depending on this setting
self.anon = False
jid = '%s' % config.get('jid', '') jid = '%s' % config.get('jid', '')
if resource: if resource:
jid = '%s/%s'% (jid, resource) jid = '%s/%s'% (jid, resource)
...@@ -44,7 +45,8 @@ class Connection(sleekxmpp.ClientXMPP): ...@@ -44,7 +45,8 @@ class Connection(sleekxmpp.ClientXMPP):
password = None password = None
jid = safeJID(jid) jid = safeJID(jid)
# TODO: use the system language # 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) force_encryption = config.get('force_encryption', True)
if force_encryption: if force_encryption:
...@@ -59,7 +61,9 @@ class Connection(sleekxmpp.ClientXMPP): ...@@ -59,7 +61,9 @@ class Connection(sleekxmpp.ClientXMPP):
self.auto_authorize = None self.auto_authorize = None
# prosody defaults, lowest is AES128-SHA, it should be a minimum # prosody defaults, lowest is AES128-SHA, it should be a minimum
# for anything that came out after 2002 # 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 self.ca_certs = config.get('ca_cert_path', '') or None
interval = config.get('whitespace_interval', '300') interval = config.get('whitespace_interval', '300')
if interval.isdecimal() and int(interval) > 0: if interval.isdecimal() and int(interval) > 0:
...@@ -138,12 +142,15 @@ class Connection(sleekxmpp.ClientXMPP): ...@@ -138,12 +142,15 @@ class Connection(sleekxmpp.ClientXMPP):
self.plugin['xep_0199'].disable_keepalive() self.plugin['xep_0199'].disable_keepalive()
# If the ping_interval is 0 or less, we just disable the keepalive # If the ping_interval is 0 or less, we just disable the keepalive
if ping_interval > 0: 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): def start(self):
# TODO, try multiple servers """
# With anon auth. Connect and process events.
# (domain, config.get('port', 5222))
TODO: try multiple servers with anon auth.
"""
custom_host = config.get('custom_host', '') custom_host = config.get('custom_host', '')
custom_port = config.get('custom_port', 5222) custom_port = config.get('custom_port', 5222)
if custom_port == -1: if custom_port == -1:
...@@ -151,7 +158,8 @@ class Connection(sleekxmpp.ClientXMPP): ...@@ -151,7 +158,8 @@ class Connection(sleekxmpp.ClientXMPP):
if custom_host: if custom_host:
res = self.connect((custom_host, custom_port), reattempt=True) res = self.connect((custom_host, custom_port), reattempt=True)
elif custom_port != 5222 and custom_port != -1: 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: else:
res = self.connect(reattempt=True) res = self.connect(reattempt=True)
if not res: if not res:
...@@ -172,4 +180,5 @@ class MatchAll(sleekxmpp.xmlstream.matcher.base.MatcherBase): ...@@ -172,4 +180,5 @@ class MatchAll(sleekxmpp.xmlstream.matcher.base.MatcherBase):
Callback to retrieve all the stanzas for the XML tab Callback to retrieve all the stanzas for the XML tab
""" """
def match(self, xml): def match(self, xml):
"match everything"
return True return True
...@@ -27,13 +27,21 @@ class PluginManager(object): ...@@ -27,13 +27,21 @@ class PluginManager(object):
""" """
def __init__(self, core): def __init__(self, core):
self.core = core self.core = core
self.modules = {} # module name -> module object # module name -> module object
self.plugins = {} # module name -> plugin object self.modules = {}
self.commands = {} # module name -> dict of commands loaded for the module # module name -> plugin object
self.event_handlers = {} # module name -> list of event_name/handler pairs loaded for the module self.plugins = {}
self.tab_commands = {} #module name -> dict of tab types; tab type -> commands loaded by the module # module name -> dict of commands loaded for the module
self.keys = {} # module name → dict of keys/handlers loaded for the module self.commands = {}
self.tab_keys = {} #module name → dict of tab types; tab type → list of keybinds (tuples) # 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 = {} self.roster_elements = {}
if version_info[1] >= 3: # 3.3 & > if version_info[1] >= 3: # 3.3 & >
...@@ -67,7 +75,8 @@ class PluginManager(object): ...@@ -67,7 +75,8 @@ class PluginManager(object):
imp.acquire_lock() imp.acquire_lock()
module = imp.reload(self.modules[name]) module = imp.reload(self.modules[name])
else: else:
file, filename, info = imp.find_module(name, self.load_path) file, filename, info = imp.find_module(name,
self.load_path)
imp.acquire_lock() imp.acquire_lock()
module = imp.load_module(name, file, filename, info) module = imp.load_module(name, file, filename, info)
else: # 3.3 & > else: # 3.3 & >
...@@ -79,7 +88,8 @@ class PluginManager(object): ...@@ -79,7 +88,8 @@ class PluginManager(object):
except Exception as e: except Exception as e:
log.debug("Could not load plugin %s", name, exc_info=True) 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: finally:
if version_info[1] < 3 and imp.lock_held(): if version_info[1] < 3 and imp.lock_held():
imp.release_lock() imp.release_lock()
...@@ -94,11 +104,14 @@ class PluginManager(object): ...@@ -94,11 +104,14 @@ class PluginManager(object):
self.event_handlers[name] = [] self.event_handlers[name] = []
try: try:
self.plugins[name] = None 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: except Exception as e:
log.error('Error while loading the plugin %s', name, exc_info=True) log.error('Error while loading the plugin %s', name, exc_info=True)
if notify: 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) self.unload(name, notify=False)
else: else:
if notify: if notify:
...@@ -113,7 +126,8 @@ class PluginManager(object): ...@@ -113,7 +126,8 @@ class PluginManager(object):
del self.core.key_func[key] del self.core.key_func[key]
for tab in list(self.tab_commands[name].keys()): for tab in list(self.tab_commands[name].keys()):
for command in self.tab_commands[name][tab][:]: 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] del self.tab_commands[name][tab]
for tab in list(self.tab_keys[name].keys()): for tab in list(self.tab_keys[name].keys()):
for key in self.tab_keys[name][tab][:]: for key in self.tab_keys[name][tab][:]:
...@@ -133,9 +147,12 @@ class PluginManager(object): ...@@ -133,9 +147,12 @@ class PluginManager(object):
self.core.information('Plugin %s unloaded' % name, 'Info') self.core.information('Plugin %s unloaded' % name, 'Info')
except Exception as e: except Exception as e:
log.debug("Could not unload plugin %s", name, exc_info=True) 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. Add a global command.
""" """
...@@ -155,7 +172,8 @@ class PluginManager(object): ...@@ -155,7 +172,8 @@ class PluginManager(object):
if name in self.core.commands: if name in self.core.commands:
del self.core.commands[name] 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. Add a command only for a type of Tab.
""" """
...@@ -166,7 +184,8 @@ class PluginManager(object): ...@@ -166,7 +184,8 @@ class PluginManager(object):
if not t in commands: if not t in commands:
commands[t] = [] commands[t] = []
commands[t].append((name, handler, help, completion)) 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: for tab in self.core.tabs:
if isinstance(tab, tab_type): if isinstance(tab, tab_type):
tab.update_commands() tab.update_commands()
...@@ -282,14 +301,16 @@ class PluginManager(object): ...@@ -282,14 +301,16 @@ class PluginManager(object):
and name != '__init__.py' and not name.startswith('.')] and name != '__init__.py' and not name.startswith('.')]
plugins_files.sort() plugins_files.sort()
position = the_input.get_argument_position(quoted=False) 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): 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) 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): def on_plugins_dir_change(self, new_value):
self.plugins_dir = new_value self.plugins_dir = new_value
...@@ -334,7 +355,8 @@ class PluginManager(object): ...@@ -334,7 +355,8 @@ class PluginManager(object):
plugins_dir = config.get('plugins_dir', '') plugins_dir = config.get('plugins_dir', '')
plugins_dir = plugins_dir or\ plugins_dir = plugins_dir or\
os.path.join(os.environ.get('XDG_DATA_HOME') 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') 'poezio', 'plugins')
self.plugins_dir = os.path.expanduser(plugins_dir) self.plugins_dir = os.path.expanduser(plugins_dir)
self.check_create_plugins_dir() self.check_create_plugins_dir()
...@@ -360,7 +382,8 @@ class PluginManager(object): ...@@ -360,7 +382,8 @@ class PluginManager(object):
self.load_path = [] 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): if os.access(default_plugin_path, os.R_OK | os.X_OK):
self.load_path.insert(0, default_plugin_path) self.load_path.insert(0, default_plugin_path)
......
# 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 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 import logging
...@@ -18,13 +17,15 @@ from datetime import datetime ...@@ -18,13 +17,15 @@ from datetime import datetime
from config import config from config import config
from theming import get_theme, dump_tuple 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) Message = collections.namedtuple('Message', message_fields)
class CorrectionError(Exception): class CorrectionError(Exception):
pass pass
def other_elems(self): def other_elems(self):
"Helper for the repr_message function"
acc = ['Message('] acc = ['Message(']
fields = message_fields.split() fields = message_fields.split()
fields.remove('old_message') fields.remove('old_message')
...@@ -33,6 +34,11 @@ def other_elems(self): ...@@ -33,6 +34,11 @@ def other_elems(self):
return ', '.join(acc) + ', old_message=' return ', '.join(acc) + ', old_message='
def repr_message(self): 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) init = other_elems(self)
acc = [init] acc = [init]
next_message = self.old_message next_message = self.old_message
...@@ -55,12 +61,17 @@ class TextBuffer(object): ...@@ -55,12 +61,17 @@ class TextBuffer(object):
This class just keep trace of messages, in a list with various This class just keep trace of messages, in a list with various
informations and attributes. 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_nb_limit = messages_nb_limit
self.messages = [] # Message objects # Message objects
self.windows = [] # we keep track of one or more windows self.messages = []
# we keep track of one or more windows
# so we can pass the new messages to them, as they are added, so # 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 # they (the windows) can build the lines from the new message
self.windows = []
def add_window(self, win): def add_window(self, win):
self.windows.append(win) self.windows.append(win)
...@@ -71,19 +82,35 @@ class TextBuffer(object): ...@@ -71,19 +82,35 @@ class TextBuffer(object):
@staticmethod @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() time = time or datetime.now()
me = False
if txt.startswith('/me '): if txt.startswith('/me '):
me = True 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: 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( msg = Message(
txt='%s\x19o'%(txt.replace('\t', ' '),), txt='%s\x19o'%(txt.replace('\t', ' '),),
nick_color=nick_color, nick_color=nick_color,
time=time, 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, nickname=nickname,
user=user, user=user,
identifier=identifier, identifier=identifier,
...@@ -95,42 +122,74 @@ class TextBuffer(object): ...@@ -95,42 +122,74 @@ class TextBuffer(object):
log.debug('Set message %s with %s.', identifier, msg) log.debug('Set message %s with %s.', identifier, msg)
return 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): def add_message(self, txt, time=None, nickname=None,
msg = self.make_message(txt, time, nickname, nick_color, history, user, identifier, str_time=str_time, highlight=highlight, jid=jid) 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) self.messages.append(msg)
while len(self.messages) > self.messages_nb_limit: while len(self.messages) > self.messages_nb_limit:
self.messages.pop(0) self.messages.pop(0)
ret_val = None ret_val = None
show_timestamps = config.get('show_timestamps', True)
for window in self.windows: # make the associated windows for window in self.windows: # make the associated windows
# build the lines from the new message # build the lines from the new message
nb = window.build_new_message(msg, history=history, highlight=highlight, timestamp=config.get("show_timestamps", True)) nb = window.build_new_message(msg, history=history,
highlight=highlight,
timestamp=show_timestamps)
if ret_val is None: if ret_val is None:
ret_val = nb ret_val = nb
if window.pos != 0: if window.pos != 0:
window.scroll_up(nb) window.scroll_up(nb)
return ret_val or 1 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): for i in range(len(self.messages) -1, -1, -1):
msg = self.messages[i] msg = self.messages[i]
if msg.identifier == old_id: if msg.identifier == old_id:
if msg.user and msg.user is not user: if msg.user and msg.user is not user:
raise CorrectionError("Different users") raise CorrectionError("Different users")
elif len(msg.str_time) > 8: # ugly elif len(msg.str_time) > 8: # ugly
raise CorrectionError("Delayed message") raise CorrectionError("Delayed message")
elif not msg.user and (msg.jid is None or jid is None): 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: 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)) raise CorrectionError('Messages %s and %s have not been '
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) '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 self.messages[i] = message
log.debug('Replacing message %s with %s.', old_id, new_id) log.debug('Replacing message %s with %s.', old_id, new_id)
return message 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") raise CorrectionError("nothing to replace")
def del_window(self, win): def del_window(self, win):
self.windows.remove(win) self.windows.remove(win)
def __del__(self): 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)
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment