From 332a5c2553db41de777473a1e1be9cd1522c9496 Mon Sep 17 00:00:00 2001 From: Emmanuel Gil Peyrot Date: Thu, 31 Mar 2016 18:54:41 +0100 Subject: Move the src directory to poezio, for better cython compatibility. --- src/__init__.py | 1 - src/args.py | 28 - src/bookmarks.py | 289 ----- src/common.py | 483 --------- src/config.py | 685 ------------ src/connection.py | 223 ---- src/contact.py | 197 ---- src/core/__init__.py | 8 - src/core/commands.py | 999 ------------------ src/core/completions.py | 387 ------- src/core/core.py | 2102 ------------------------------------- src/core/handlers.py | 1354 ------------------------ src/core/structs.py | 49 - src/daemon.py | 82 -- src/decorators.py | 139 --- src/events.py | 87 -- src/fifo.py | 71 -- src/fixes.py | 97 -- src/keyboard.py | 168 --- src/logger.py | 284 ----- src/multiuserchat.py | 196 ---- src/pep.py | 221 ---- src/plugin.py | 485 --------- src/plugin_manager.py | 384 ------- src/poezio.py | 115 -- src/poezio_shlex.py | 276 ----- src/pooptmodule.c | 486 --------- src/roster.py | 334 ------ src/roster_sorting.py | 90 -- src/singleton.py | 20 - src/size_manager.py | 46 - src/tabs/__init__.py | 13 - src/tabs/adhoc_commands_list.py | 57 - src/tabs/basetabs.py | 881 ---------------- src/tabs/bookmarkstab.py | 145 --- src/tabs/conversationtab.py | 484 --------- src/tabs/data_forms.py | 75 -- src/tabs/listtab.py | 202 ---- src/tabs/muclisttab.py | 70 -- src/tabs/muctab.py | 1720 ------------------------------ src/tabs/privatetab.py | 362 ------- src/tabs/rostertab.py | 1280 ---------------------- src/tabs/xmltab.py | 360 ------- src/text_buffer.py | 242 ----- src/theming.py | 534 ---------- src/timed_events.py | 58 - src/user.py | 121 --- src/windows/__init__.py | 20 - src/windows/base_wins.py | 168 --- src/windows/bookmark_forms.py | 278 ----- src/windows/data_forms.py | 472 --------- src/windows/funcs.py | 54 - src/windows/info_bar.py | 106 -- src/windows/info_wins.py | 311 ------ src/windows/input_placeholders.py | 77 -- src/windows/inputs.py | 768 -------------- src/windows/list.py | 236 ----- src/windows/misc.py | 60 -- src/windows/muc.py | 143 --- src/windows/roster_win.py | 387 ------- src/windows/text_win.py | 597 ----------- src/xhtml.py | 543 ---------- 62 files changed, 21210 deletions(-) delete mode 100644 src/__init__.py delete mode 100644 src/args.py delete mode 100644 src/bookmarks.py delete mode 100644 src/common.py delete mode 100644 src/config.py delete mode 100644 src/connection.py delete mode 100644 src/contact.py delete mode 100644 src/core/__init__.py delete mode 100644 src/core/commands.py delete mode 100644 src/core/completions.py delete mode 100644 src/core/core.py delete mode 100644 src/core/handlers.py delete mode 100644 src/core/structs.py delete mode 100755 src/daemon.py delete mode 100644 src/decorators.py delete mode 100644 src/events.py delete mode 100644 src/fifo.py delete mode 100644 src/fixes.py delete mode 100755 src/keyboard.py delete mode 100644 src/logger.py delete mode 100644 src/multiuserchat.py delete mode 100644 src/pep.py delete mode 100644 src/plugin.py delete mode 100644 src/plugin_manager.py delete mode 100644 src/poezio.py delete mode 100644 src/poezio_shlex.py delete mode 100644 src/pooptmodule.c delete mode 100644 src/roster.py delete mode 100644 src/roster_sorting.py delete mode 100644 src/singleton.py delete mode 100644 src/size_manager.py delete mode 100644 src/tabs/__init__.py delete mode 100644 src/tabs/adhoc_commands_list.py delete mode 100644 src/tabs/basetabs.py delete mode 100644 src/tabs/bookmarkstab.py delete mode 100644 src/tabs/conversationtab.py delete mode 100644 src/tabs/data_forms.py delete mode 100644 src/tabs/listtab.py delete mode 100644 src/tabs/muclisttab.py delete mode 100644 src/tabs/muctab.py delete mode 100644 src/tabs/privatetab.py delete mode 100644 src/tabs/rostertab.py delete mode 100644 src/tabs/xmltab.py delete mode 100644 src/text_buffer.py delete mode 100755 src/theming.py delete mode 100644 src/timed_events.py delete mode 100644 src/user.py delete mode 100644 src/windows/__init__.py delete mode 100644 src/windows/base_wins.py delete mode 100644 src/windows/bookmark_forms.py delete mode 100644 src/windows/data_forms.py delete mode 100644 src/windows/funcs.py delete mode 100644 src/windows/info_bar.py delete mode 100644 src/windows/info_wins.py delete mode 100644 src/windows/input_placeholders.py delete mode 100644 src/windows/inputs.py delete mode 100644 src/windows/list.py delete mode 100644 src/windows/misc.py delete mode 100644 src/windows/muc.py delete mode 100644 src/windows/roster_win.py delete mode 100644 src/windows/text_win.py delete mode 100644 src/xhtml.py (limited to 'src') diff --git a/src/__init__.py b/src/__init__.py deleted file mode 100644 index 9fdbcc02..00000000 --- a/src/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from poezio.poezio import main diff --git a/src/args.py b/src/args.py deleted file mode 100644 index 63e77927..00000000 --- a/src/args.py +++ /dev/null @@ -1,28 +0,0 @@ -""" -Module related to the argument parsing - -There is a fallback to the deprecated optparse if argparse is not found -""" -from os import path -from argparse import ArgumentParser, SUPPRESS - -def parse_args(CONFIG_PATH=''): - """ - Parse the arguments from the command line - """ - parser = ArgumentParser('poezio') - parser.add_argument("-c", "--check-config", dest="check_config", - action='store_true', - help='Check the config file') - parser.add_argument("-d", "--debug", dest="debug", - help="The file where debug will be written", - metavar="DEBUG_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("-v", "--version", dest="version", - help=SUPPRESS, metavar="VERSION", - default="1.0-dev") - options = parser.parse_args() - return options diff --git a/src/bookmarks.py b/src/bookmarks.py deleted file mode 100644 index c7d26a51..00000000 --- a/src/bookmarks.py +++ /dev/null @@ -1,289 +0,0 @@ -""" -Bookmarks module - -Therein the bookmark class is defined, representing one conference room. -This object is used to generate elements for both local and remote -bookmark storage. It can also parse xml Elements. - -This module also defines several functions for retrieving and updating -bookmarks, both local and remote. - -Poezio start scenario: - -- upon inital connection, poezio will disco#info the server -- the available storage methods will be stored in the available_storage dict - (either 'pep' or 'privatexml') -- if only one is available, poezio will set the use_bookmarks_method config option - to it. If both are, it will be set to 'privatexml' (or if it was previously set, the - value will be kept). -- it will then query the preferred storages for bookmarks and cache them locally - (Bookmark objects with a method='remote' attribute) - -Adding a remote bookmark: - -- New Bookmark object added to the list with storage='remote' -- All bookmarks are sent to the storage selected in use_bookmarks_method - if there was an error, the user is notified. - - -""" - -import functools -import logging - -from slixmpp.plugins.xep_0048 import Bookmarks, Conference, URL -from slixmpp import JID -from common import safeJID -from config import config - -log = logging.getLogger(__name__) - - -class Bookmark(object): - - def __init__(self, jid, name=None, autojoin=False, nick=None, password=None, method='local'): - self.jid = jid - self.name = name or jid - self.autojoin = autojoin - self.nick = nick - self.password = password - self._method = method - - @property - def method(self): - return self._method - - @method.setter - def method(self, value): - if value not in ('local', 'remote'): - log.debug('Could not set bookmark storing method: %s', value) - return - self._method = value - - def __repr__(self): - return '<%s%s|%s>' % (self.jid, - ('/'+self.nick) if self.nick else '', - self.method) - - def stanza(self): - """ - Generate a stanza from the instance - """ - el = Conference() - el['name'] = self.name - el['jid'] = self.jid - el['autojoin'] = 'true' if self.autojoin else 'false' - if self.nick: - el['nick'] = self.nick - if self.password: - el['password'] = self.password - return el - - def local(self): - """Generate a str for local storage""" - local = self.jid - if self.nick: - local += '/%s' % self.nick - local += ':' - if self.password: - config.set_and_save('password', self.password, section=self.jid) - return local - - @functools.singledispatch - @staticmethod - def parse(el): - """ - Generate a Bookmark object from a element - (this is a fallback for raw XML Elements) - """ - jid = el.get('jid') - name = el.get('name') - autojoin = True if el.get('autojoin', 'false').lower() in ('true', '1') else False - nick = None - for n in el.iter('nick'): - nick = n.text - password = None - for p in el.iter('password'): - password = p.text - - return Bookmark(jid, name, autojoin, nick, password, method='remote') - - @staticmethod - @parse.register(Conference) - def parse_from_stanza(el): - """ - Parse a Conference element into a Bookmark object - """ - jid = el['jid'] - autojoin = el['autojoin'] - password = el['password'] - nick = el['nick'] - name = el['name'] - return Bookmark(jid, name, autojoin, nick, password, method='remote') - -class BookmarkList(object): - - def __init__(self): - self.bookmarks = [] - preferred = config.get('use_bookmarks_method').lower() - if preferred not in ('pep', 'privatexml'): - preferred = 'privatexml' - self.preferred = preferred - self.available_storage = { - 'privatexml': False, - 'pep': False, - } - - def __getitem__(self, key): - if isinstance(key, (str, JID)): - for i in self.bookmarks: - if key == i.jid: - return i - else: - return self.bookmarks[key] - - def __in__(self, key): - if isinstance(key, (str, JID)): - for bookmark in self.bookmarks: - if bookmark.jid == key: - return True - else: - return key in self.bookmarks - return False - - def remove(self, key): - if isinstance(key, (str, JID)): - for i in self.bookmarks[:]: - if i.jid == key: - self.bookmarks.remove(i) - else: - self.bookmarks.remove(key) - - def __iter__(self): - return iter(self.bookmarks) - - def local(self): - return [bm for bm in self.bookmarks if bm.method == 'local'] - - def remote(self): - return [bm for bm in self.bookmarks if bm.method == 'remote'] - - def set(self, new): - self.bookmarks = new - - def append(self, bookmark): - bookmark_exists = self[bookmark.jid] - if not bookmark_exists: - self.bookmarks.append(bookmark) - else: - self.bookmarks.remove(bookmark_exists) - self.bookmarks.append(bookmark) - - def set_bookmarks_method(self, value): - if self.available_storage.get(value): - self.preferred = value - config.set_and_save('use_bookmarks_method', value) - - def save_remote(self, xmpp, callback): - """Save the remote bookmarks.""" - if not any(self.available_storage.values()): - return - method = 'xep_0049' if self.preferred == 'privatexml' else 'xep_0223' - - if method: - xmpp.plugin['xep_0048'].set_bookmarks(stanza_storage(self.bookmarks), - method=method, - callback=callback) - def save_local(self): - """Save the local bookmarks.""" - local = ''.join(bookmark.local() for bookmark in self if bookmark.method == 'local') - config.set_and_save('rooms', local) - - def save(self, xmpp, core=None, callback=None): - """Save all the bookmarks.""" - self.save_local() - def _cb(iq): - if callback: - callback(iq) - if iq["type"] == "error" and core: - core.information('Could not save remote bookmarks.', 'Error') - elif core: - core.information('Bookmarks saved', 'Info') - if config.get('use_remote_bookmarks'): - self.save_remote(xmpp, _cb) - - def get_pep(self, xmpp, callback): - """Add the remotely stored bookmarks via pep to the list.""" - def _cb(iq): - if iq['type'] == 'result': - for conf in iq['pubsub']['items']['item']['bookmarks']['conferences']: - if isinstance(conf, URL): - continue - b = Bookmark.parse(conf) - self.append(b) - if callback: - callback(iq) - - xmpp.plugin['xep_0048'].get_bookmarks(method='xep_0223', callback=_cb) - - def get_privatexml(self, xmpp, callback): - """ - Fetch the remote bookmarks stored via privatexml. - """ - def _cb(iq): - if iq['type'] == 'result': - for conf in iq['private']['bookmarks']['conferences']: - b = Bookmark.parse(conf) - self.append(b) - if callback: - callback(iq) - - xmpp.plugin['xep_0048'].get_bookmarks(method='xep_0049', callback=_cb) - - def get_remote(self, xmpp, information, callback): - """Add the remotely stored bookmarks to the list.""" - force = config.get('force_remote_bookmarks') - if xmpp.anon or not (any(self.available_storage.values()) or force): - information('No remote bookmark storage available', 'Warning') - return - - if force and not any(self.available_storage.values()): - old_callback = callback - method = 'pep' if self.preferred == 'pep' else 'privatexml' - def new_callback(result): - if result['type'] != 'error': - self.available_storage[method] = True - old_callback(result) - else: - information('No remote bookmark storage available', 'Warning') - callback = new_callback - - if self.preferred == 'pep': - self.get_pep(xmpp, callback=callback) - else: - self.get_privatexml(xmpp, callback=callback) - - def get_local(self): - """Add the locally stored bookmarks to the list.""" - rooms = config.get('rooms') - if not rooms: - return - rooms = rooms.split(':') - for room in rooms: - jid = safeJID(room) - if jid.bare == '': - continue - if jid.resource != '': - nick = jid.resource - else: - nick = None - passwd = config.get_by_tabname('password', jid.bare, fallback=False) or None - b = Bookmark(jid.bare, autojoin=True, nick=nick, password=passwd, method='local') - self.append(b) - -def stanza_storage(bookmarks): - """Generate a stanza with the conference elements.""" - storage = Bookmarks() - for b in (b for b in bookmarks if b.method == 'remote'): - storage.append(b.stanza()) - return storage diff --git a/src/common.py b/src/common.py deleted file mode 100644 index a62c83f1..00000000 --- a/src/common.py +++ /dev/null @@ -1,483 +0,0 @@ -# Copyright 2010-2011 Florent Le Coz -# -# 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. - -""" -Various useful functions. -""" - -from sys import version_info -from datetime import datetime, timedelta -from slixmpp import JID, InvalidJID - -import base64 -import os -import mimetypes -import hashlib -import subprocess -import time -import string -import poezio_shlex as shlex - - -# Needed to avoid datetime.datetime.timestamp() -# on python < 3.3. Older versions do not get good dst detection. -OLD_PYTHON = (version_info.major + version_info.minor/10) < 3.3 - -ROOM_STATE_NONE = 11 -ROOM_STATE_CURRENT = 10 -ROOM_STATE_PRIVATE = 15 -ROOM_STATE_MESSAGE = 12 -ROOM_STATE_HL = 13 - -def get_base64_from_file(path): - """ - Convert the content of a file to base64 - - :param str path: The path of the file to convert. - :return: A tuple of (encoded data, mime type, sha1 hash) if - the file exists and does not exceeds the upper size limit of 16384. - :return: (None, None, error message) if it fails - :rtype: :py:class:`tuple` - - """ - if not os.path.isfile(path): - return (None, None, "File does not exist") - size = os.path.getsize(path) - if size > 16384: - return (None, None,"File is too big") - fdes = open(path, 'rb') - data = fdes.read() - encoded = base64.encodestring(data) - sha1 = hashlib.sha1(data).hexdigest() - mime_type = mimetypes.guess_type(path)[0] - return (encoded, mime_type, sha1) - -def get_output_of_command(command): - """ - Runs a command and returns its output. - - :param str command: The command to run. - :return: The output or None - :rtype: :py:class:`str` - """ - try: - return subprocess.check_output(command.split()).decode('utf-8').split('\n') - except subprocess.CalledProcessError: - return None - -def is_in_path(command, return_abs_path=False): - """ - Check if *command* is in the $PATH or not. - - :param str command: The command to be checked. - :param bool return_abs_path: Return the absolute path of the command instead - of True if the command is found. - :return: True if the command is found, the command path if the command is found - and *return_abs_path* is True, otherwise False. - - """ - for directory in os.getenv('PATH').split(os.pathsep): - try: - if command in os.listdir(directory): - if return_abs_path: - return os.path.join(directory, command) - else: - return True - except OSError: - # If the user has non directories in his path - pass - return False - -DISTRO_INFO = { - 'Arch Linux': '/etc/arch-release', - 'Aurox Linux': '/etc/aurox-release', - 'Conectiva Linux': '/etc/conectiva-release', - 'CRUX': '/usr/bin/crux', - 'Debian GNU/Linux': '/etc/debian_version', - 'Fedora Linux': '/etc/fedora-release', - 'Gentoo Linux': '/etc/gentoo-release', - 'Linux from Scratch': '/etc/lfs-release', - 'Mandrake Linux': '/etc/mandrake-release', - 'Slackware Linux': '/etc/slackware-version', - 'Solaris/Sparc': '/etc/release', - 'Source Mage': '/etc/sourcemage_version', - 'SUSE Linux': '/etc/SuSE-release', - 'Sun JDS': '/etc/sun-release', - 'PLD Linux': '/etc/pld-release', - 'Yellow Dog Linux': '/etc/yellowdog-release', - # many distros use the /etc/redhat-release for compatibility - # so Redhat is the last - 'Redhat Linux': '/etc/redhat-release' -} - -def get_os_info(): - """ - Returns a detailed and well formated string containing - informations about the operating system - - :rtype: str - """ - if os.name == 'posix': - executable = 'lsb_release' - params = ' --description --codename --release --short' - full_path_to_executable = is_in_path(executable, return_abs_path = True) - if full_path_to_executable: - command = executable + params - process = subprocess.Popen([command], shell=True, - stdin=subprocess.PIPE, - stdout=subprocess.PIPE, - close_fds=True) - process.wait() - output = process.stdout.readline().decode('utf-8').strip() - # some distros put n/a in places, so remove those - output = output.replace('n/a', '').replace('N/A', '') - return output - - # lsb_release executable not available, so parse files - for distro_name in DISTRO_INFO: - path_to_file = DISTRO_INFO[distro_name] - if os.path.exists(path_to_file): - if os.access(path_to_file, os.X_OK): - # the file is executable (f.e. CRUX) - # yes, then run it and get the first line of output. - text = get_output_of_command(path_to_file)[0] - else: - fdes = open(path_to_file, encoding='utf-8') - text = fdes.readline().strip() # get only first line - fdes.close() - if path_to_file.endswith('version'): - # sourcemage_version and slackware-version files - # have all the info we need (name and version of distro) - if not os.path.basename(path_to_file).startswith( - 'sourcemage') or not\ - os.path.basename(path_to_file).startswith('slackware'): - text = distro_name + ' ' + text - elif path_to_file.endswith('aurox-release') or \ - path_to_file.endswith('arch-release'): - # file doesn't have version - text = distro_name - elif path_to_file.endswith('lfs-release'): - # file just has version - text = distro_name + ' ' + text - os_info = text.replace('\n', '') - return os_info - - # our last chance, ask uname and strip it - uname_output = get_output_of_command('uname -sr') - if uname_output is not None: - os_info = uname_output[0] # only first line - return os_info - os_info = 'N/A' - return os_info - -def datetime_tuple(timestamp): - """ - Convert a timestamp using strptime and the format: %Y%m%dT%H:%M:%S. - - Because various datetime formats are used, the following exceptions - are handled: - - * Optional milliseconds appened to the string are removed - * Optional Z (that means UTC) appened to the string are removed - * XEP-082 datetime strings have all '-' chars removed to meet the above format. - - :param str timestamp: The string containing the formatted date. - :return: The date. - :rtype: :py:class:`datetime.datetime` - """ - timestamp = timestamp.replace('-', '', 2).replace(':', '') - date = timestamp[:15] - tz_msg = timestamp[15:] - try: - ret = datetime.strptime(date, '%Y%m%dT%H%M%S') - except Exception: - ret = datetime.now() - # add the message timezone if any - try: - if tz_msg and tz_msg != 'Z': - tz_mod = -1 if tz_msg[0] == '-' else 1 - tz_msg = time.strptime(tz_msg[1:], '%H%M') - tz_msg = tz_msg.tm_hour * 3600 + tz_msg.tm_min * 60 - tz_msg = timedelta(seconds=tz_mod * tz_msg) - ret -= tz_msg - except Exception: - pass # ignore if we got a badly-formatted offset - # convert UTC to local time, with DST etc. - if time.daylight and time.localtime().tm_isdst: - tz = timedelta(seconds=-time.altzone) - else: - tz = timedelta(seconds=-time.timezone) - ret += tz - return ret - -def get_utc_time(local_time=None): - """ - Get the current UTC time - - :param datetime local_time: The current local time - :return: The current UTC time - """ - if local_time is None: - local_time = datetime.now() - isdst = time.localtime().tm_isdst - else: - if OLD_PYTHON: - isdst = time.localtime(int(local_time.strftime("%s"))).tm_isdst - else: - isdst = time.localtime(int(local_time.timestamp())).tm_isdst - - if time.daylight and isdst: - tz = timedelta(seconds=time.altzone) - else: - tz = timedelta(seconds=time.timezone) - - utc_time = local_time + tz - - return utc_time - -def get_local_time(utc_time): - """ - Get the local time from an UTC time - """ - if OLD_PYTHON: - isdst = time.localtime(int(utc_time.strftime("%s"))).tm_isdst - else: - isdst = time.localtime(int(utc_time.timestamp())).tm_isdst - - if time.daylight and isdst: - tz = timedelta(seconds=time.altzone) - else: - tz = timedelta(seconds=time.timezone) - - local_time = utc_time - tz - - return local_time - -def find_delayed_tag(message): - """ - Check if a message is delayed or not. - - :param slixmpp.Message message: The message to check. - :return: A tuple containing (True, the datetime) or (False, None) - :rtype: :py:class:`tuple` - """ - - delay_tag = message.find('{urn:xmpp:delay}delay') - if delay_tag is not None: - delayed = True - date = datetime_tuple(delay_tag.attrib['stamp']) - else: - # We support the OLD and deprecated XEP: http://xmpp.org/extensions/xep-0091.html - # But it sucks, please, Jabber servers, don't do this :( - delay_tag = message.find('{jabber:x:delay}x') - if delay_tag is not None: - delayed = True - date = datetime_tuple(delay_tag.attrib['stamp']) - else: - delayed = False - date = None - return (delayed, date) - -def shell_split(st): - """ - Split a string correctly according to the quotes - around the elements. - - :param str st: The string to split. - :return: A list of the different of the string. - :rtype: :py:class:`list` - - >>> shell_split('"sdf 1" "toto 2"') - ['sdf 1', 'toto 2'] - """ - sh = shlex.shlex(st) - ret = [] - w = sh.get_token() - while w and w[2] is not None: - ret.append(w[2]) - if w[1] == len(st): - return ret - w = sh.get_token() - return ret - -def find_argument(pos, text, quoted=True): - """ - Split an input into a list of arguments, return the number of the - argument selected by pos. - - If the position searched is outside the string, or in a space between words, - then it will return the position of an hypothetical new argument. - - See the doctests of the two methods for example behaviors. - - :param int pos: The position to search. - :param str text: The text to analyze. - :param quoted: Whether to take quotes into account or not. - :rtype: int - """ - if quoted: - return find_argument_quoted(pos, text) - else: - return find_argument_unquoted(pos, text) - -def find_argument_quoted(pos, text): - """ - Get the number of the argument at position pos in - a string with possibly quoted text. - """ - sh = shlex.shlex(text) - count = -1 - w = sh.get_token() - while w and w[2] is not None: - count += 1 - if w[0] <= pos < w[1]: - return count - w = sh.get_token() - - return count + 1 - -def find_argument_unquoted(pos, text): - """ - Get the number of the argument at position pos in - a string without interpreting quotes. - """ - ret = text.split() - search = 0 - argnum = 0 - for i, elem in enumerate(ret): - elem_start = text.find(elem, search) - elem_end = elem_start + len(elem) - search = elem_end - if elem_start <= pos < elem_end: - return i - argnum = i - return argnum + 1 - -def parse_str_to_secs(duration=''): - """ - Parse a string of with a number of d, h, m, s. - - :param str duration: The formatted string. - :return: The number of seconds represented by the string - :rtype: :py:class:`int` - - >>> parse_str_to_secs("1d3m1h") - 90180 - """ - values = {'s': 1, 'm': 60, 'h': 3600, 'd': 86400} - result = 0 - tmp = '0' - for char in duration: - if char in string.digits: - tmp += char - elif char in values: - tmp_i = int(tmp) - result += tmp_i * values[char] - tmp = '0' - else: - return 0 - if tmp != '0': - result += int(tmp) - return result - -def parse_secs_to_str(duration=0): - """ - Do the reverse operation of :py:func:`parse_str_to_secs`. - - Parse a number of seconds to a human-readable string. - The string has the form XdXhXmXs. 0 units are removed. - - :param int duration: The duration, in seconds. - :return: A formatted string containing the duration. - :rtype: :py:class:`str` - - >>> parse_secs_to_str(3601) - '1h1s' - """ - secs, mins, hours, days = 0, 0, 0, 0 - result = '' - secs = duration % 60 - mins = (duration % 3600) // 60 - hours = (duration % 86400) // 3600 - days = duration // 86400 - - result += '%sd' % days if days else '' - result += '%sh' % hours if hours else '' - result += '%sm' % mins if mins else '' - result += '%ss' % secs if secs else '' - if not result: - result = '0s' - return result - -def format_tune_string(infos): - """ - Contruct a string from a dict created from an "User tune" event. - - :param dict infos: The informations - :return: The formatted string - :rtype: :py:class:`str` - """ - elems = [] - track = infos.get('track') - if track: - elems.append(track) - title = infos.get('title') - if title: - elems.append(title) - else: - elems.append('Unknown title') - elems.append('-') - artist = infos.get('artist') - if artist: - elems.append(artist) - else: - elems.append('Unknown artist') - - rating = infos.get('rating') - if rating: - elems.append('[ ' + rating + '/10' + ' ]') - length = infos.get('length') - if length: - length = int(length) - secs = length % 60 - mins = length // 60 - secs = str(secs).zfill(2) - mins = str(mins).zfill(2) - elems.append('[' + mins + ':' + secs + ']') - return ' '.join(elems) - -def format_gaming_string(infos): - """ - Construct a string from a dict containing the "user gaming" - informations. - (for now, only use address and name) - - :param dict infos: The informations - :returns: The formatted string - :rtype: :py:class:`str` - """ - name = infos.get('name') - if not name: - return '' - - server_address = infos.get('server_address') - if server_address: - return '%s on %s' % (name, server_address) - return name - -def safeJID(*args, **kwargs): - """ - Construct a :py:class:`slixmpp.JID` object from a string. - - Used to avoid tracebacks during is stringprep fails - (fall back to a JID with an empty string). - """ - try: - return JID(*args, **kwargs) - except InvalidJID: - return JID('') - diff --git a/src/config.py b/src/config.py deleted file mode 100644 index 7f0c75f6..00000000 --- a/src/config.py +++ /dev/null @@ -1,685 +0,0 @@ -""" -Defines the global config instance, used to get or set (and save) values -from/to the config file. - -This module has the particularity that some imports and global variables -are delayed because it would mean doing an incomplete setup of the python -loggers. - -TODO: get http://bugs.python.org/issue1410680 fixed, one day, in order -to remove our ugly custom I/O methods. -""" - -DEFSECTION = "Poezio" - -import logging.config -import os -import stat -import sys -import pkg_resources - -from configparser import RawConfigParser, NoOptionError, NoSectionError -from os import environ, makedirs, path, remove -from shutil import copy2 -from args import parse_args - -DEFAULT_CONFIG = { - 'Poezio': { - 'ack_message_receipts': True, - 'add_space_after_completion': True, - 'after_completion': ',', - 'alternative_nickname': '', - 'auto_reconnect': True, - 'autorejoin_delay': '5', - 'autorejoin': False, - 'beep_on': 'highlight private invite', - 'ca_cert_path': '', - 'certificate': '', - 'certfile': '', - 'ciphers': 'HIGH+kEDH:HIGH+kEECDH:HIGH:!PSK:!SRP:!3DES:!aNULL', - 'connection_check_interval': 60, - 'connection_timeout_delay': 10, - 'create_gaps': False, - 'custom_host': '', - 'custom_port': '', - 'default_nick': '', - 'deterministic_nick_colors': True, - 'nick_color_aliases': True, - 'display_activity_notifications': False, - 'display_gaming_notifications': False, - 'display_mood_notifications': False, - 'display_tune_notifications': False, - 'display_user_color_in_join_part': True, - 'enable_carbons': True, - 'enable_user_activity': True, - 'enable_user_gaming': True, - 'enable_user_mood': True, - 'enable_user_nick': True, - 'enable_user_tune': True, - 'enable_vertical_tab_list': False, - 'enable_xhtml_im': True, - 'eval_password': '', - 'exec_remote': False, - 'extract_inline_images': True, - 'filter_info_messages': '', - 'force_encryption': True, - 'force_remote_bookmarks': False, - 'go_to_previous_tab_on_alt_number': False, - 'group_corrections': True, - 'hide_exit_join': -1, - 'hide_status_change': 120, - 'hide_user_list': False, - 'highlight_on': '', - 'ignore_certificate': False, - 'ignore_private': False, - 'information_buffer_popup_on': 'error roster warning help info', - 'jid': '', - 'keyfile': '', - 'lang': 'en', - 'lazy_resize': True, - 'load_log': 10, - 'log_dir': '', - 'log_errors': True, - 'max_lines_in_memory': 2048, - 'max_messages_in_memory': 2048, - 'max_nick_length': 25, - 'muc_history_length': 50, - 'notify_messages': True, - 'open_all_bookmarks': False, - 'password': '', - 'plugins_autoload': '', - 'plugins_conf_dir': '', - 'plugins_dir': '', - 'popup_time': 4, - 'private_auto_response': '', - 'remote_fifo_path': './', - 'request_message_receipts': True, - 'resource': '', - 'rooms': '', - 'roster_group_sort': 'name', - 'roster_show_offline': False, - 'roster_sort': 'jid:show', - 'save_status': True, - 'self_ping_delay': 0, - 'send_chat_states': True, - 'send_initial_presence': True, - 'send_os_info': True, - 'send_poezio_info': True, - 'send_time': True, - 'separate_history': False, - 'server': 'anon.jeproteste.info', - 'show_composing_tabs': 'direct', - 'show_inactive_tabs': True, - 'show_jid_in_conversations': True, - 'show_muc_jid': True, - 'show_roster_jids': True, - 'show_roster_subscriptions': '', - 'show_s2s_errors': True, - 'show_tab_names': False, - 'show_tab_numbers': True, - 'show_timestamps': True, - 'show_useless_separator': True, - 'status': '', - 'status_message': '', - 'theme': 'default', - 'themes_dir': '', - 'tmp_image_dir': '', - 'use_bookmarks_method': '', - 'use_log': False, - 'use_remote_bookmarks': True, - 'user_list_sort': 'desc', - 'use_tab_nicks': True, - 'vertical_tab_list_size': 20, - 'vertical_tab_list_sort': 'desc', - 'whitespace_interval': 300, - 'words': '' - }, - 'bindings': { - 'M-i': '^I' - }, - 'var': { - 'folded_roster_groups': '', - 'info_win_height': 2 - }, - 'muc_colors': { - } -} - -class Config(RawConfigParser): - """ - load/save the config to a file - """ - def __init__(self, file_name, default=None): - RawConfigParser.__init__(self, None) - # make the options case sensitive - self.optionxform = str - self.file_name = file_name - self.read_file() - self.default = default - - def read_file(self): - try: - RawConfigParser.read(self, self.file_name, encoding='utf-8') - except TypeError: # python < 3.2 sucks - RawConfigParser.read(self, self.file_name) - # Check config integrity and fix it if it’s wrong - # only when the object is the main config - if self.__class__ is Config: - for section in ('bindings', 'var'): - if not self.has_section(section): - self.add_section(section) - - def get(self, option, default=None, section=DEFSECTION): - """ - get a value from the config but return - a default value if it is not found - The type of default defines the type - returned - """ - if default is None: - if self.default: - default = self.default.get(section, {}).get(option) - else: - default = '' - - try: - if type(default) == int: - res = self.getint(option, section) - elif type(default) == float: - res = self.getfloat(option, section) - elif type(default) == bool: - res = self.getboolean(option, section) - else: - res = self.getstr(option, section) - except (NoOptionError, NoSectionError, ValueError, AttributeError): - return default - - if res is None: - return default - return res - - def get_by_tabname(self, option, tabname, - fallback=True, fallback_server=True, default=''): - """ - Try to get the value for the option. First we look in - a section named `tabname`, if the option is not present - in the section, we search for the global option if fallback is - True. And we return `default` as a fallback as a last resort. - """ - if self.default and (not default) and fallback: - default = self.default.get(DEFSECTION, {}).get(option, '') - if tabname in self.sections(): - if option in self.options(tabname): - # We go the tab-specific option - return self.get(option, default, tabname) - if fallback_server: - return self.get_by_servname(tabname, option, default, fallback) - if fallback: - # We fallback to the global option - return self.get(option, default) - return default - - def get_by_servname(self, jid, option, default, fallback=True): - """ - Try to get the value of an option for a server - """ - server = safeJID(jid).server - if server: - server = '@' + server - if server in self.sections() and option in self.options(server): - return self.get(option, default, server) - if fallback: - return self.get(option, default) - return default - - - def __get(self, option, section=DEFSECTION, **kwargs): - """ - facility for RawConfigParser.get - """ - return RawConfigParser.get(self, section, option, **kwargs) - - def _get(self, section, conv, option, **kwargs): - """ - Redirects RawConfigParser._get - """ - return conv(self.__get(option, section, **kwargs)) - - def getstr(self, option, section=DEFSECTION): - """ - get a value and returns it as a string - """ - return self.__get(option, section) - - def getint(self, option, section=DEFSECTION): - """ - get a value and returns it as an int - """ - return RawConfigParser.getint(self, section, option) - - def getfloat(self, option, section=DEFSECTION): - """ - get a value and returns it as a float - """ - return RawConfigParser.getfloat(self, section, option) - - def getboolean(self, option, section=DEFSECTION): - """ - get a value and returns it as a boolean - """ - return RawConfigParser.getboolean(self, section, option) - - def write_in_file(self, section, option, value): - """ - Our own way to save write the value in the file - Just find the right section, and then find the - right option, and edit it. - """ - result = self._parse_file() - if not result: - return False - else: - sections, result_lines = result - - if not section in sections: - result_lines.append('[%s]' % section) - result_lines.append('%s = %s' % (option, value)) - else: - begin, end = sections[section] - pos = find_line(result_lines, begin, end, option) - - if pos is -1: - result_lines.insert(end, '%s = %s' % (option, value)) - else: - result_lines[pos] = '%s = %s' % (option, value) - - return self._write_file(result_lines) - - def remove_in_file(self, section, option): - """ - Our own way to remove an option from the file. - """ - result = self._parse_file() - if not result: - return False - else: - sections, result_lines = result - - if not section in sections: - log.error('Tried to remove the option %s from a non-' - 'existing section (%s)', option, section) - return True - else: - begin, end = sections[section] - pos = find_line(result_lines, begin, end, option) - - if pos is -1: - log.error('Tried to remove a non-existing option %s' - ' from section %s', option, section) - return True - else: - del result_lines[pos] - - return self._write_file(result_lines) - - def _write_file(self, lines): - """ - Write the config file, write to a temporary file - before copying it to the final destination - """ - try: - prefix, file = path.split(self.file_name) - filename = path.join(prefix, '.%s.tmp' % file) - fd = os.fdopen( - os.open( - filename, - os.O_WRONLY | os.O_CREAT, - 0o600), - 'w') - for line in lines: - fd.write('%s\n' % line) - fd.close() - copy2(filename, self.file_name) - remove(filename) - except: - success = False - log.error('Unable to save the config file.', exc_info=True) - else: - success = True - return success - - def _parse_file(self): - """ - Parse the config file and return the list of sections with - their start and end positions, and the lines in the file. - - Duplicate sections are preserved but ignored for the parsing. - - Returns an empty tuple if reading fails - """ - if file_ok(self.file_name): - try: - with open(self.file_name, 'r', encoding='utf-8') as df: - lines_before = [line.strip() for line in df] - except: - log.error('Unable to read the config file %s', - self.file_name, - exc_info=True) - return tuple() - else: - lines_before = [] - - sections = {} - duplicate_section = False - current_section = '' - current_line = 0 - - for line in lines_before: - if line.startswith('['): - if not duplicate_section and current_section: - sections[current_section][1] = current_line - - duplicate_section = False - current_section = line[1:-1] - - if current_section in sections: - log.error('Error while reading the configuration file,' - ' skipping until next section') - duplicate_section = True - else: - sections[current_section] = [current_line, current_line] - - current_line += 1 - if not duplicate_section and current_section: - sections[current_section][1] = current_line - - return (sections, lines_before) - - def set_and_save(self, option, value, section=DEFSECTION): - """ - set the value in the configuration then save it - to the file - """ - # Special case for a 'toggle' value. We take the current value - # and set the opposite. Warning if the no current value exists - # or it is not a bool. - if value == "toggle": - current = self.get(option, "", section) - if isinstance(current, bool): - value = str(not current) - else: - if current.lower() == "false": - value = "true" - elif current.lower() == "true": - value = "false" - else: - return ('Could not toggle option: %s.' - ' Current value is %s.' % - (option, current or "empty"), - 'Warning') - if self.has_section(section): - RawConfigParser.set(self, section, option, value) - else: - self.add_section(section) - RawConfigParser.set(self, section, option, value) - if not self.write_in_file(section, option, value): - return ('Unable to write in the config file', 'Error') - return ("%s=%s" % (option, value), 'Info') - - def remove_and_save(self, option, section=DEFSECTION): - """ - Remove an option and then save it the config file - """ - if self.has_section(section): - RawConfigParser.remove_option(self, section, option) - if not self.remove_in_file(section, option): - return ('Unable to save the config file', 'Error') - return ('Option %s deleted' % option, 'Info') - - def silent_set(self, option, value, section=DEFSECTION): - """ - Set a value, save, and return True on success and False on failure - """ - if self.has_section(section): - RawConfigParser.set(self, section, option, value) - else: - self.add_section(section) - RawConfigParser.set(self, section, option, value) - return self.write_in_file(section, option, value) - - def set(self, option, value, section=DEFSECTION): - """ - Set the value of an option temporarily - """ - try: - RawConfigParser.set(self, section, option, value) - except NoSectionError: - pass - - def to_dict(self): - """ - Returns a dict of the form {section: {option: value, option: value}, …} - """ - res = {} - for section in self.sections(): - res[section] = {} - for option in self.options(section): - res[section][option] = self.get(option, "", section) - return res - - -def find_line(lines, start, end, option): - """ - Get the number of the line containing the option in the - relevant part of the config file. - - Returns -1 if the option isn’t found - """ - current = start - for line in lines[start:end]: - if (line.startswith('%s ' % option) or - line.startswith('%s=' % option)): - return current - current += 1 - return -1 - -def file_ok(filepath): - """ - Returns True if the file exists and is readable and writeable, - False otherwise. - """ - val = path.exists(filepath) - val &= os.access(filepath, os.R_OK | os.W_OK) - return bool(val) - -def check_create_config_dir(): - """ - create the configuration directory if it doesn't exist - """ - CONFIG_HOME = environ.get("XDG_CONFIG_HOME") - if not CONFIG_HOME: - CONFIG_HOME = path.join(environ.get('HOME'), '.config') - CONFIG_PATH = path.join(CONFIG_HOME, 'poezio') - - try: - makedirs(CONFIG_PATH) - except OSError: - pass - return CONFIG_PATH - -def check_create_cache_dir(): - """ - create the cache directory if it doesn't exist - also create the subdirectories - """ - global CACHE_DIR - CACHE_HOME = environ.get("XDG_CACHE_HOME") - if not CACHE_HOME: - CACHE_HOME = path.join(environ.get('HOME'), '.cache') - CACHE_DIR = path.join(CACHE_HOME, 'poezio') - - try: - makedirs(CACHE_DIR) - makedirs(path.join(CACHE_DIR, 'images')) - except OSError: - pass - -def check_config(): - """ - Check the config file and print results - """ - result = {'missing': [], 'changed': []} - for option in DEFAULT_CONFIG['Poezio']: - value = config.get(option) - if value != DEFAULT_CONFIG['Poezio'][option]: - result['changed'].append((option, value, DEFAULT_CONFIG['Poezio'][option])) - else: - value = config.get(option, default='') - upper = value.upper() - default = str(DEFAULT_CONFIG['Poezio'][option]).upper() - if upper != default: - result['missing'].append(option) - - result['changed'].sort(key=lambda x: x[0]) - result['missing'].sort() - if result['changed']: - print('\033[1mOptions changed from the default configuration:\033[0m\n') - for option, new_value, default in result['changed']: - print(' \033[1m%s\033[0m = \033[33m%s\033[0m (default: \033[32m%s\033[0m)' % (option, new_value, default)) - - if result['missing']: - print('\n\033[1mMissing options:\033[0m (the defaults are used)\n') - for option in result['missing']: - print(' \033[31m%s\033[0m' % option) - -def run_cmdline_args(CONFIG_PATH): - "Parse the command line arguments" - global options - options = parse_args(CONFIG_PATH) - - # Copy a default file if none exists - if not path.isfile(options.filename): - default = path.join(path.dirname(__file__), '../data/default_config.cfg') - other = pkg_resources.resource_filename('poezio', 'default_config.cfg') - if path.isfile(default): - copy2(default, options.filename) - elif path.isfile(other): - copy2(other, options.filename) - - # Inside the nixstore and possibly other distributions, the reference - # file is readonly, so is the copy. - # Make it writable by the user who just created it. - if os.path.exists(options.filename): - os.chmod(options.filename, - os.stat(options.filename).st_mode | stat.S_IWUSR) - - global firstrun - firstrun = True - -def create_global_config(): - "Create the global config object, or crash" - try: - global config - config = Config(options.filename, DEFAULT_CONFIG) - except: - import traceback - sys.stderr.write('Poezio was unable to read or' - ' parse the config file.\n') - traceback.print_exc(limit=0) - sys.exit(1) - -def check_create_log_dir(): - "Create the poezio logging directory if it doesn’t exist" - global LOG_DIR - LOG_DIR = config.get('log_dir') - - if not LOG_DIR: - - data_dir = environ.get('XDG_DATA_HOME') - if not data_dir: - home = environ.get('HOME') - data_dir = path.join(home, '.local', 'share') - - LOG_DIR = path.join(data_dir, 'poezio', 'logs') - - LOG_DIR = path.expanduser(LOG_DIR) - - try: - makedirs(LOG_DIR) - except: - pass - -def setup_logging(): - "Change the logging config according to the cmdline options and config" - if config.get('log_errors'): - LOGGING_CONFIG['root']['handlers'].append('error') - LOGGING_CONFIG['handlers']['error'] = { - 'level': 'ERROR', - 'class': 'logging.FileHandler', - 'filename': path.join(LOG_DIR, 'errors.log'), - 'formatter': 'simple', - } - - if options.debug: - LOGGING_CONFIG['root']['handlers'].append('debug') - LOGGING_CONFIG['handlers']['debug'] = { - 'level':'DEBUG', - 'class':'logging.FileHandler', - 'filename': options.debug, - 'formatter': 'simple', - } - - - if LOGGING_CONFIG['root']['handlers']: - logging.config.dictConfig(LOGGING_CONFIG) - else: - logging.basicConfig(level=logging.CRITICAL) - - global log - log = logging.getLogger(__name__) - -def post_logging_setup(): - # common imports slixmpp, which creates then its loggers, so - # it needs to be after logger configuration - from common import safeJID as JID - global safeJID - safeJID = JID - -LOGGING_CONFIG = { - 'version': 1, - 'disable_existing_loggers': True, - 'formatters': { - 'simple': { - 'format': '%(asctime)s %(levelname)s:%(module)s:%(message)s' - } - }, - 'handlers': { - }, - 'root': { - 'handlers': [], - 'propagate': True, - 'level': 'DEBUG', - } -} - -# True if this is the first run, in this case we will display -# some help in the info buffer -firstrun = False - -# Global config object. Is setup in poezio.py -config = None - -# The logger object for this module -log = None - -# The command-line options -options = None - -# delayed import from common.py -safeJID = None - -# the global log dir -LOG_DIR = '' - -# the global cache dir -CACHE_DIR = '' diff --git a/src/connection.py b/src/connection.py deleted file mode 100644 index c4cc8b6b..00000000 --- a/src/connection.py +++ /dev/null @@ -1,223 +0,0 @@ -# Copyright 2010-2011 Florent Le Coz -# -# 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. - -""" -Defines the Connection class -""" - -import logging -log = logging.getLogger(__name__) - - -import getpass -import subprocess -import sys - -import slixmpp -from slixmpp.plugins.xep_0184 import XEP_0184 - -import common -import fixes -from common import safeJID -from config import config, options - -class Connection(slixmpp.ClientXMPP): - """ - Receives everything from Jabber and emits the - appropriate signals - """ - __init = False - def __init__(self): - resource = config.get('resource') - - keyfile = config.get('keyfile') - certfile = config.get('certfile') - - if config.get('jid'): - # 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) - password = config.get('password') - eval_password = config.get('eval_password') - if not password and not eval_password and not (keyfile and certfile): - password = getpass.getpass() - elif not password and not (keyfile and certfile): - sys.stderr.write("No password or certificates provided, using the eval_password command.\n") - process = subprocess.Popen(['sh', '-c', eval_password], stdin=subprocess.PIPE, - stdout=subprocess.PIPE, close_fds=True) - code = process.wait() - if code != 0: - sys.stderr.write('The eval_password command (%s) returned a ' - 'nonzero status code: %s.\n' % (eval_password, code)) - sys.stderr.write('Poezio will now exit\n') - sys.exit(code) - password = process.stdout.readline().decode('utf-8').strip('\n') - else: # anonymous auth - self.anon = True - jid = config.get('server') - if resource: - jid = '%s/%s' % (jid, resource) - password = None - jid = safeJID(jid) - # TODO: use the system language - slixmpp.ClientXMPP.__init__(self, jid, password, - lang=config.get('lang')) - - force_encryption = config.get('force_encryption') - if force_encryption: - self['feature_mechanisms'].unencrypted_plain = False - self['feature_mechanisms'].unencrypted_digest = False - self['feature_mechanisms'].unencrypted_cram = False - self['feature_mechanisms'].unencrypted_scram = False - - self.keyfile = config.get('keyfile') - self.certfile = config.get('certfile') - if keyfile and not certfile: - log.error('keyfile is present in configuration file without certfile') - elif certfile and not keyfile: - log.error('certfile is present in configuration file without keyfile') - - self.core = None - self.auto_reconnect = config.get('auto_reconnect') - self.reconnect_max_attempts = 0 - 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.ca_certs = config.get('ca_cert_path') or None - interval = config.get('whitespace_interval') - if int(interval) > 0: - self.whitespace_keepalive_interval = int(interval) - else: - self.whitespace_keepalive = False - self.register_plugin('xep_0004') - self.register_plugin('xep_0012') - self.register_plugin('xep_0030') - self.register_plugin('xep_0045') - self.register_plugin('xep_0048') - self.register_plugin('xep_0050') - self.register_plugin('xep_0054') - self.register_plugin('xep_0060') - self.register_plugin('xep_0066') - self.register_plugin('xep_0071') - self.register_plugin('xep_0077') - self.plugin['xep_0077'].create_account = False - self.register_plugin('xep_0085') - self.register_plugin('xep_0115') - - # monkey-patch xep_0184 to avoid requesting receipts for messages - # without a body - XEP_0184._filter_add_receipt_request = fixes._filter_add_receipt_request - self.register_plugin('xep_0184') - self.plugin['xep_0184'].auto_ack = config.get('ack_message_receipts') - self.plugin['xep_0184'].auto_request = config.get('request_message_receipts') - - self.register_plugin('xep_0191') - self.register_plugin('xep_0198') - self.register_plugin('xep_0199') - - if config.get('enable_user_tune'): - self.register_plugin('xep_0118') - - if config.get('enable_user_nick'): - self.register_plugin('xep_0172') - - if config.get('enable_user_mood'): - self.register_plugin('xep_0107') - - if config.get('enable_user_activity'): - self.register_plugin('xep_0108') - - if config.get('enable_user_gaming'): - self.register_plugin('xep_0196') - - if config.get('send_poezio_info'): - info = {'name':'poezio', - 'version': options.version} - if config.get('send_os_info'): - info['os'] = common.get_os_info() - self.plugin['xep_0030'].set_identities( - identities=set([('client', 'pc', None, 'Poezio')])) - else: - info = {'name': '', 'version': ''} - self.plugin['xep_0030'].set_identities( - identities=set([('client', 'pc', None, '')])) - self.register_plugin('xep_0092', pconfig=info) - if config.get('send_time'): - self.register_plugin('xep_0202') - self.register_plugin('xep_0224') - self.register_plugin('xep_0231') - self.register_plugin('xep_0249') - self.register_plugin('xep_0257') - self.register_plugin('xep_0280') - self.register_plugin('xep_0297') - self.register_plugin('xep_0308') - self.register_plugin('xep_0319') - self.register_plugin('xep_0334') - self.register_plugin('xep_0352') - self.init_plugins() - - def set_keepalive_values(self, option=None, value=None): - """ - Called after the XMPP session has been started, or triggered when one of - "connection_timeout_delay" and "connection_check_interval" options - is changed. Unload and reload the ping plugin, with the new values. - """ - if not self.is_connected(): - # Happens when we change the value with /set while we are not - # connected. Do nothing in that case - return - ping_interval = config.get('connection_check_interval') - timeout_delay = config.get('connection_timeout_delay') - if timeout_delay <= 0: - # We help the stupid user (with a delay of 0, poezio will try to - # reconnect immediately because the timeout is immediately - # passed) - # 1 second is short, but, well - timeout_delay = 1 - 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) - - def start(self): - """ - Connect and process events. - """ - custom_host = config.get('custom_host') - custom_port = config.get('custom_port', 5222) - if custom_port == -1: - custom_port = 5222 - if custom_host: - self.connect((custom_host, custom_port)) - elif custom_port != 5222 and custom_port != -1: - self.connect((self.boundjid.host, custom_port)) - else: - self.connect() - - def send_raw(self, data): - """ - Overrides XMLStream.send_raw, with an event added - """ - if self.core: - self.core.outgoing_stanza(data) - slixmpp.ClientXMPP.send_raw(self, data) - -class MatchAll(slixmpp.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/contact.py b/src/contact.py deleted file mode 100644 index c670e5bc..00000000 --- a/src/contact.py +++ /dev/null @@ -1,197 +0,0 @@ -# Copyright 2010-2011 Florent Le Coz -# -# 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. - -""" -Defines the Resource and Contact classes, which are used in -the roster. -""" - -import logging -log = logging.getLogger(__name__) - -from common import safeJID -from collections import defaultdict - -class Resource(object): - """ - Defines a roster item. - It's a precise resource. - """ - def __init__(self, jid, data): - """ - data: the dict to use as a source - """ - self._jid = jid # Full jid - self._data = data - - @property - def jid(self): - return self._jid - - @property - def priority(self): - return self._data.get('priority') or 0 - - @property - def presence(self): - return self._data.get('show') or '' - - show = presence - - @property - def status(self): - return self._data.get('status') or '' - - def __repr__(self): - return '<%s>' % self._jid - - def __eq__(self, value): - if not isinstance(value, Resource): - return False - return self.jid == value.jid and self._data == value._data - -class Contact(object): - """ - This a way to gather multiple resources from the same bare JID. - This class contains zero or more Resource object and useful methods - to get the resource with the highest priority, etc - """ - def __init__(self, item): - """ - item: a slixmpp RosterItem pointing to that contact - """ - self.__item = item - self.folded_states = defaultdict(lambda: True) - self._name = '' - self.error = None - self.tune = {} - self.gaming = {} - self.mood = '' - self.activity = '' - - @property - def groups(self): - """Name of the groups the contact is in""" - return self.__item['groups'] or ['none'] - - @property - def bare_jid(self): - """The bare jid of the contact""" - return self.__item.jid - - @property - def name(self): - """The name of the contact or an empty string.""" - return self.__item['name'] or self._name or '' - - @name.setter - def name(self, value): - """Set the name of the contact with user nickname""" - self._name = value - - @property - def ask(self): - if self.__item['pending_out']: - return 'asked' - - @property - def pending_in(self): - """We received a subscribe stanza from this contact.""" - return self.__item['pending_in'] - - @pending_in.setter - def pending_in(self, value): - self.__item['pending_in'] = value - - @property - def pending_out(self): - """We sent a subscribe stanza to this contact.""" - return self.__item['pending_out'] - - @pending_out.setter - def pending_out(self, value): - self.__item['pending_out'] = value - - @property - def resources(self): - """List of the available resources as Resource objects""" - return (Resource( - '%s%s' % (self.bare_jid, ('/' + key) if key else ''), - self.__item.resources[key] - ) for key in self.__item.resources.keys()) - - @property - def subscription(self): - return self.__item['subscription'] - - def __contains__(self, value): - return value in self.__item.resources or safeJID(value).resource in self.__item.resources - - def __len__(self): - """Number of resources""" - return len(self.__item.resources) - - def __bool__(self): - """This contacts exists even when he has no resources""" - return True - - def __getitem__(self, key): - """Return the corresponding Resource object, or None""" - res = safeJID(key).resource - resources = self.__item.resources - item = resources.get(res, None) or resources.get(key, None) - return Resource(key, item) if item else None - - def subscribe(self): - """Subscribe to this JID""" - self.__item.subscribe() - - def authorize(self): - """Authorize this JID""" - self.__item.authorize() - - def unauthorize(self): - """Unauthorize this JID""" - self.__item.unauthorize() - - def unsubscribe(self): - """Unsubscribe from this JID""" - self.__item.unsubscribe() - - def get(self, key, default=None): - """Same as __getitem__, but with a configurable default""" - return self[key] or default - - def get_resources(self): - """Return all resources, sorted by priority """ - compare_resources = lambda x: x.priority - return sorted(self.resources, key=compare_resources, reverse=True) - - def get_highest_priority_resource(self): - """Return the resource with the highest priority""" - resources = self.get_resources() - if resources: - return resources[0] - return None - - def folded(self, group_name='none'): - """ - Return the Folded state of a contact for this group - """ - return self.folded_states[group_name] - - def toggle_folded(self, group='none'): - """ - Fold if it's unfolded, and vice versa - """ - self.folded_states[group] = not self.folded_states[group] - - def __repr__(self): - ret = '\n' diff --git a/src/core/__init__.py b/src/core/__init__.py deleted file mode 100644 index 6a82e2bb..00000000 --- a/src/core/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -""" -Core class, splitted into smaller chunks -""" - -from . core import Core -from . structs import Command, Status, possible_show, DEPRECATED_ERRORS, \ - ERROR_AND_STATUS_CODES - diff --git a/src/core/commands.py b/src/core/commands.py deleted file mode 100644 index a0a636c1..00000000 --- a/src/core/commands.py +++ /dev/null @@ -1,999 +0,0 @@ -""" -Global commands which are to be linked to the Core class -""" - -import logging - -log = logging.getLogger(__name__) - -import os -from datetime import datetime -from xml.etree import cElementTree as ET - -from slixmpp.xmlstream.stanzabase import StanzaBase -from slixmpp.xmlstream.handler import Callback -from slixmpp.xmlstream.matcher import StanzaPath - -import common -import fixes -import pep -import tabs -from bookmarks import Bookmark -from common import safeJID -from config import config, DEFAULT_CONFIG, options as config_opts -import multiuserchat as muc -from plugin import PluginConfig -from roster import roster -from theming import dump_tuple, get_theme -from decorators import command_args_parser - -from . structs import Command, possible_show - - -@command_args_parser.quoted(0, 1) -def command_help(self, args): - """ - /help [command_name] - """ - if not args: - color = dump_tuple(get_theme().COLOR_HELP_COMMANDS) - acc = [] - buff = ['Global commands:'] - for command in self.commands: - if isinstance(self.commands[command], Command): - acc.append(' \x19%s}%s\x19o - %s' % ( - color, - command, - self.commands[command].short)) - else: - acc.append(' \x19%s}%s\x19o' % (color, command)) - acc = sorted(acc) - buff.extend(acc) - acc = [] - buff.append('Tab-specific commands:') - commands = self.current_tab().commands - for command in commands: - if isinstance(commands[command], Command): - acc.append(' \x19%s}%s\x19o - %s' % ( - color, - command, - commands[command].short)) - else: - acc.append(' \x19%s}%s\x19o' % (color, command)) - acc = sorted(acc) - buff.extend(acc) - - msg = '\n'.join(buff) - msg += "\nType /help to know what each command does" - else: - command = args[0].lstrip('/').strip() - - if command in self.current_tab().commands: - tup = self.current_tab().commands[command] - elif command in self.commands: - tup = self.commands[command] - else: - self.information('Unknown command: %s' % command, 'Error') - return - if isinstance(tup, Command): - msg = 'Usage: /%s %s\n' % (command, tup.usage) - msg += tup.desc - else: - msg = tup[1] - self.information(msg, 'Help') - -@command_args_parser.quoted(1) -def command_runkey(self, args): - """ - /runkey - """ - def replace_line_breaks(key): - "replace ^J with \n" - if key == '^J': - return '\n' - return key - if args is None: - return self.command_help('runkey') - char = args[0] - func = self.key_func.get(char, None) - if func: - func() - else: - res = self.do_command(replace_line_breaks(char), False) - if res: - self.refresh_window() - -@command_args_parser.quoted(1, 1, [None]) -def command_status(self, args): - """ - /status [msg] - """ - if args is None: - return self.command_help('status') - - if not args[0] in possible_show.keys(): - return self.command_help('status') - - show = possible_show[args[0]] - msg = args[1] - - pres = self.xmpp.make_presence() - if msg: - pres['status'] = msg - pres['type'] = show - self.events.trigger('send_normal_presence', pres) - pres.send() - current = self.current_tab() - is_muctab = isinstance(current, tabs.MucTab) - if is_muctab and current.joined and show in ('away', 'xa'): - current.send_chat_state('inactive') - for tab in self.tabs: - if isinstance(tab, tabs.MucTab) and tab.joined: - muc.change_show(self.xmpp, tab.name, tab.own_nick, show, msg) - if hasattr(tab, 'directed_presence'): - del tab.directed_presence - self.set_status(show, msg) - if is_muctab and current.joined and show not in ('away', 'xa'): - current.send_chat_state('active') - -@command_args_parser.quoted(1, 2, [None, None]) -def command_presence(self, args): - """ - /presence [type] [status] - """ - if args is None: - return self.command_help('presence') - - jid, type, status = args[0], args[1], args[2] - if jid == '.' and isinstance(self.current_tab(), tabs.ChatTab): - jid = self.current_tab().name - if type == 'available': - type = None - try: - pres = self.xmpp.make_presence(pto=jid, ptype=type, pstatus=status) - self.events.trigger('send_normal_presence', pres) - pres.send() - except: - self.information('Could not send directed presence', 'Error') - log.debug('Could not send directed presence to %s', jid, exc_info=True) - return - tab = self.get_tab_by_name(jid) - if tab: - if type in ('xa', 'away'): - tab.directed_presence = False - chatstate = 'inactive' - else: - tab.directed_presence = True - chatstate = 'active' - if tab == self.current_tab(): - tab.send_chat_state(chatstate, True) - if isinstance(tab, tabs.MucTab): - for private in tab.privates: - private.directed_presence = tab.directed_presence - if self.current_tab() in tab.privates: - self.current_tab().send_chat_state(chatstate, True) - -@command_args_parser.quoted(1) -def command_theme(self, args=None): - """/theme """ - if args is None: - return self.command_help('theme') - self.command_set('theme %s' % (args[0],)) - -@command_args_parser.quoted(1) -def command_win(self, args): - """ - /win - """ - if args is None: - return self.command_help('win') - - nb = args[0] - try: - nb = int(nb) - except ValueError: - pass - if self.current_tab_nb == nb: - return - self.previous_tab_nb = self.current_tab_nb - old_tab = self.current_tab() - if isinstance(nb, int): - if 0 <= nb < len(self.tabs): - if not self.tabs[nb]: - return - self.current_tab_nb = nb - else: - matchs = [] - for tab in self.tabs: - for name in tab.matching_names(): - if nb.lower() in name[1].lower(): - matchs.append((name[0], tab)) - self.current_tab_nb = tab.nb - if not matchs: - return - tab = min(matchs, key=lambda m: m[0])[1] - self.current_tab_nb = tab.nb - old_tab.on_lose_focus() - self.current_tab().on_gain_focus() - self.refresh_window() - -@command_args_parser.quoted(2) -def command_move_tab(self, args): - """ - /move_tab old_pos new_pos - """ - if args is None: - return self.command_help('move_tab') - - current_tab = self.current_tab() - if args[0] == '.': - args[0] = current_tab.nb - if args[1] == '.': - args[1] = current_tab.nb - - def get_nb_from_value(value): - "parse the cmdline to guess the tab the users wants" - ref = None - try: - ref = int(value) - except ValueError: - old_tab = None - for tab in self.tabs: - if not old_tab and value == tab.name: - old_tab = tab - if not old_tab: - self.information("Tab %s does not exist" % args[0], "Error") - return None - ref = old_tab.nb - return ref - old = get_nb_from_value(args[0]) - new = get_nb_from_value(args[1]) - if new is None or old is None: - return self.information('Unable to move the tab.', 'Info') - result = self.insert_tab(old, new) - if not result: - self.information('Unable to move the tab.', 'Info') - else: - self.current_tab_nb = self.tabs.index(current_tab) - self.refresh_window() - -@command_args_parser.quoted(0, 1) -def command_list(self, args): - """ - /list [server] - Opens a MucListTab containing the list of the room in the specified server - """ - if args is None: - return self.command_help('list') - elif args: - jid = safeJID(args[0]) - else: - if not isinstance(self.current_tab(), tabs.MucTab): - return self.information('Please provide a server', 'Error') - jid = safeJID(self.current_tab().name).server - list_tab = tabs.MucListTab(jid) - self.add_tab(list_tab, True) - cb = list_tab.on_muc_list_item_received - self.xmpp.plugin['xep_0030'].get_items(jid=jid, - callback=cb) - -@command_args_parser.quoted(1) -def command_version(self, args): - """ - /version - """ - def callback(res): - "Callback for /version" - if not res: - return self.information('Could not get the software' - ' version from %s' % jid, - 'Warning') - version = '%s is running %s version %s on %s' % ( - jid, - res.get('name') or 'an unknown software', - res.get('version') or 'unknown', - res.get('os') or 'an unknown platform') - self.information(version, 'Info') - - if args is None: - return self.command_help('version') - - jid = safeJID(args[0]) - if jid.resource or jid not in roster: - fixes.get_version(self.xmpp, jid, callback=callback) - elif jid in roster: - for resource in roster[jid].resources: - fixes.get_version(self.xmpp, resource.jid, callback=callback) - else: - fixes.get_version(self.xmpp, jid, callback=callback) - -@command_args_parser.quoted(0, 2) -def command_join(self, args): - """ - /join [room][/nick] [password] - """ - password = None - if len(args) == 0: - tab = self.current_tab() - if not isinstance(tab, (tabs.MucTab, tabs.PrivateTab)): - return - room = safeJID(tab.name).bare - nick = tab.own_nick - else: - if args[0].startswith('@'): # we try to join a server directly - server_root = True - info = safeJID(args[0][1:]) - else: - info = safeJID(args[0]) - server_root = False - if info == '' and len(args[0]) > 1 and args[0][0] == '/': - nick = args[0][1:] - elif info.resource == '': - nick = self.own_nick - else: - nick = info.resource - if info.bare == '': # happens with /join /nickname, which is OK - tab = self.current_tab() - if not isinstance(tab, tabs.MucTab): - return - room = tab.name - if nick == '': - nick = tab.own_nick - else: - room = info.bare - # no server is provided, like "/join hello": - # use the server of the current room if available - # check if the current room's name has a server - if room.find('@') == -1 and not server_root: - if isinstance(self.current_tab(), tabs.MucTab) and\ - self.current_tab().name.find('@') != -1: - domain = safeJID(self.current_tab().name).domain - room += '@%s' % domain - else: - room = args[0] - room = room.lower() - if room in self.pending_invites: - del self.pending_invites[room] - tab = self.get_tab_by_name(room, tabs.MucTab) - if tab is not None: - self.focus_tab_named(tab.name) - if tab.own_nick == nick and tab.joined: - self.information('/join: Nothing to do.', 'Info') - else: - tab.command_part('') - tab.own_nick = nick - tab.join() - - return - - if room.startswith('@'): - room = room[1:] - if len(args) == 2: # a password is provided - password = args[1] - if password is None: # try to use a saved password - password = config.get_by_tabname('password', room, fallback=False) - if tab is not None: - if password: - tab.password = password - tab.join() - else: - tab = self.open_new_room(room, nick, password=password) - tab.join() - - if tab.joined: - self.enable_private_tabs(room) - tab.state = "normal" - if tab == self.current_tab(): - tab.refresh() - self.doupdate() - -@command_args_parser.quoted(0, 2) -def command_bookmark_local(self, args): - """ - /bookmark_local [room][/nick] [password] - """ - if not args and not isinstance(self.current_tab(), tabs.MucTab): - return - password = args[1] if len(args) > 1 else None - jid = args[0] if args else None - - _add_bookmark(self, jid, True, password, 'local') - -@command_args_parser.quoted(0, 3) -def command_bookmark(self, args): - """ - /bookmark [room][/nick] [autojoin] [password] - """ - if not args and not isinstance(self.current_tab(), tabs.MucTab): - return - jid = args[0] if args else '' - password = args[2] if len(args) > 2 else None - - if not config.get('use_remote_bookmarks'): - return _add_bookmark(self, jid, True, password, 'local') - - if len(args) > 1: - autojoin = False if args[1].lower() != 'true' else True - else: - autojoin = True - - _add_bookmark(self, jid, autojoin, password, 'remote') - -def _add_bookmark(self, jid, autojoin, password, method): - nick = None - if not jid: - tab = self.current_tab() - roomname = tab.name - if tab.joined and tab.own_nick != self.own_nick: - nick = tab.own_nick - if password is None and tab.password is not None: - password = tab.password - elif jid == '*': - return _add_wildcard_bookmarks(self, method) - else: - info = safeJID(jid) - roomname, nick = info.bare, info.resource - if roomname == '': - if not isinstance(self.current_tab(), tabs.MucTab): - return - roomname = self.current_tab().name - bookmark = self.bookmarks[roomname] - if bookmark is None: - bookmark = Bookmark(roomname) - self.bookmarks.append(bookmark) - bookmark.method = method - bookmark.autojoin = autojoin - if nick: - bookmark.nick = nick - if password: - bookmark.password = password - def callback(iq): - if iq["type"] != "error": - self.information('Bookmark added.', 'Info') - else: - self.information("Could not add the bookmarks.", "Info") - self.bookmarks.save_local() - self.bookmarks.save_remote(self.xmpp, callback) - -def _add_wildcard_bookmarks(self, method): - new_bookmarks = [] - for tab in self.get_tabs(tabs.MucTab): - bookmark = self.bookmarks[tab.name] - if not bookmark: - bookmark = Bookmark(tab.name, autojoin=True, - method=method) - new_bookmarks.append(bookmark) - else: - bookmark.method = method - new_bookmarks.append(bookmark) - self.bookmarks.remove(bookmark) - new_bookmarks.extend(self.bookmarks.bookmarks) - self.bookmarks.set(new_bookmarks) - def _cb(iq): - if iq["type"] != "error": - self.information("Bookmarks saved.", "Info") - else: - self.information("Could not save the remote bookmarks.", "Info") - self.bookmarks.save_local() - self.bookmarks.save_remote(self.xmpp, _cb) - -@command_args_parser.ignored -def command_bookmarks(self): - """/bookmarks""" - tab = self.get_tab_by_name('Bookmarks', tabs.BookmarksTab) - old_tab = self.current_tab() - if tab: - self.current_tab_nb = tab.nb - else: - tab = tabs.BookmarksTab(self.bookmarks) - self.tabs.append(tab) - self.current_tab_nb = tab.nb - old_tab.on_lose_focus() - tab.on_gain_focus() - self.refresh_window() - -@command_args_parser.quoted(0, 1) -def command_remove_bookmark(self, args): - """/remove_bookmark [jid]""" - - def cb(success): - if success: - self.information('Bookmark deleted', 'Info') - else: - self.information('Error while deleting the bookmark', 'Error') - - if not args: - tab = self.current_tab() - if isinstance(tab, tabs.MucTab) and self.bookmarks[tab.name]: - self.bookmarks.remove(tab.name) - self.bookmarks.save(self.xmpp, callback=cb) - else: - self.information('No bookmark to remove', 'Info') - else: - if self.bookmarks[args[0]]: - self.bookmarks.remove(args[0]) - self.bookmarks.save(self.xmpp, callback=cb) - else: - self.information('No bookmark to remove', 'Info') - -@command_args_parser.quoted(0, 3) -def command_set(self, args): - """ - /set [module|][section]