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]
")
- text = ''.join(build)
- return text.replace('\n', '
')
--
cgit v1.2.3