changeset 814:59c7bc51c323

jp: refactoring using ArgParse
author Dal <kedals0@gmail.com>
date Wed, 05 Feb 2014 14:35:26 +0100
parents 1a1600491d9d
children f8d534ed1d1e
files frontends/src/jp/base.py frontends/src/jp/common.py frontends/src/jp/file.py frontends/src/jp/jp frontends/src/jp/message.py frontends/src/jp/pipe.py frontends/src/jp/profile.py
diffstat 7 files changed, 631 insertions(+), 441 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/frontends/src/jp/base.py	Wed Feb 05 14:35:26 2014 +0100
@@ -0,0 +1,263 @@
+#! /usr/bin/python
+# -*- coding: utf-8 -*-
+
+# jp: a SAT command line tool
+# Copyright (C) 2009, 2010, 2011, 2012, 2013, 2014 Jérôme Poisson (goffi@goffi.org)
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Affero General Public License for more details.
+
+# You should have received a copy of the GNU Affero General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from __future__ import with_statement
+from sat.core.i18n import _
+
+#consts
+name = u"jp"
+about = name+u""" v%s (c) Jérôme Poisson (aka Goffi) 2009, 2010, 2011, 2012, 2013, 2014
+
+---
+"""+name+u""" Copyright (C) 2009, 2010, 2011, 2012, 2013, 2014 Jérôme Poisson (aka Goffi)
+This program comes with ABSOLUTELY NO WARRANTY;
+This is free software, and you are welcome to redistribute it
+under certain conditions.
+---
+
+This software is a command line tool for jabber
+Get the latest version at http://www.goffi.org
+"""
+
+global pbar_available
+pbar_available = True #checked before using ProgressBar
+
+### logging ###
+import logging
+from logging import debug, info, error, warning
+logging.basicConfig(level=logging.DEBUG,
+                    format='%(message)s')
+###
+
+import sys
+import os
+from os.path import abspath, basename, dirname
+from argparse import ArgumentParser
+from sat.tools.jid import JID
+import gobject
+from sat_frontends.bridge.DBus import DBusBridgeFrontend
+from sat.core.exceptions import BridgeExceptionNoService, BridgeInitError
+from sat.tools.utils import clean_ustr
+import tarfile
+import tempfile
+import shutil
+try:
+    from progressbar import ProgressBar, Percentage, Bar, ETA, FileTransferSpeed
+except ImportError, e:
+        info (_('ProgressBar not available, please download it at http://pypi.python.org/pypi/progressbar'))
+        info (_('Progress bar deactivated\n--\n'))
+        pbar_available=False
+
+
+#version = unicode(self.bridge.getVersion())
+version = "undefined"
+parser = ArgumentParser()
+parser.add_argument('--version', action='version', version=about % version)
+subparser = parser.add_subparsers(dest='subparser_name')
+# File managment
+
+
+
+class JP(object):
+    """
+    This class can be use to establish a connection with the
+    bridge. Moreover, it should manage a main loop.
+
+    To use it, you mainly have to redefine the method run to perform
+    specify what kind of operation you want to perform.
+
+    """
+    def __init__(self, start_mainloop = False):
+        try:
+            self.bridge=DBusBridgeFrontend()
+        except BridgeExceptionNoService:
+            print(_(u"Can't connect to SàT backend, are you sure it's launched ?"))
+            sys.exit(1)
+        except BridgeInitError:
+            print(_(u"Can't init bridge"))
+            sys.exit(1)
+
+        self._start_loop = start_mainloop
+
+    def run(self):
+        raise NotImplementedError
+
+    def _run(self):
+        """Call run and lauch a loop if needed"""
+        print "You are connected!"
+        self.run()
+        if self._start_loop:
+            print "Exiting loop..."
+            self.loop.quit()
+
+    def _loop_start(self):
+        self.loop = gobject.MainLoop()
+        try:
+            self.loop.run()
+        except KeyboardInterrupt:
+            info(_("User interruption: good bye"))
+
+    def start_mainloop(self):
+        self._start_loop = True
+
+    def go(self):
+        self.run()
+        if self._start_loop:
+            self._loop_start()
+
+
+class JPWithProfile(JP):
+    """Manage a bridge (inherit from :class:`JP`), but it also adds
+    profile managment, ie, connection to the profile.
+
+    Moreover, some useful methods are predefined such as
+    :py:meth:`check_jids`. The connection to XMPP is automatically
+    managed.
+    """
+
+    def __init__(self, profile_name, start_mainloop = False):
+        JP.__init__(self, start_mainloop)
+        self.profile_name = profile_name
+
+    def check_jids(self, jids):
+        """Check jids validity, transform roster name to corresponding jids
+
+        :param profile: A profile name
+        :param jids: A list of jids
+        :rtype: A list of jids
+        """
+        names2jid = {}
+        nodes2jid = {}
+
+        for contact in self.bridge.getContacts(self.profile):
+            _jid, attr, groups = contact
+            if attr.has_key("name"):
+                names2jid[attr["name"].lower()] = _jid
+            nodes2jid[JID(_jid).node.lower()] = _jid
+
+        def expandJid(jid):
+            _jid = jid.lower()
+            if _jid in names2jid:
+                expanded = names2jid[_jid]
+            elif _jid in nodes2jid:
+                expanded = nodes2jid[_jid]
+            else:
+                expanded = jid
+            return unicode(expanded)
+
+        def check(jid):
+            if not jid.is_valid:
+                error (_("%s is not a valid JID !"), jid)
+                exit(1)
+
+        dest_jids=[]
+        try:
+            for i in range(len(jids)):
+                dest_jids.append(expandJid(jids[i]))
+                check(dest_jids[i])
+        except AttributeError:
+            pass
+
+        return dest_jids
+
+    def check_jabber_connection(self):
+        """Check that jabber status is allright"""
+        def cantConnect(arg):
+            print arg
+            error(_(u"Can't connect profile"))
+            exit(1)
+
+        self.profile = self.bridge.getProfileName(self.profile_name)
+        if not self.profile:
+            error(_("The profile asked doesn't exist"))
+            exit(1)
+
+        if self.bridge.isConnected(self.profile):
+            print "Already connected"
+        else:
+            self._start_loop = True
+            self.bridge.asyncConnect(self.profile, self._run, cantConnect)
+            return
+        self.run()
+
+
+    def _getFullJid(self, param_jid):
+        """Return the full jid if possible (add last resource when find a bare jid"""
+        _jid = JID(param_jid)
+        if not _jid.resource:
+            #if the resource is not given, we try to add the last known resource
+            last_resource = self.bridge.getLastResource(param_jid, self.profile_name)
+            if last_resource:
+                return "%s/%s" % (_jid.bare, last_resource)
+        return param_jid
+
+    def go(self):
+        self.check_jabber_connection()
+        if self._start_loop:
+            self._loop_start()
+
+
+
+class JPAsk(JPWithProfile):
+    def confirm_type(self):
+        """Must return a string containing the confirm type. For instance,
+        FILE_TRANSFER or PIPE_TRANSFER, etc.
+
+        :rtype: str
+        """
+        raise NotImplemented
+
+    def dest_jids(self):
+        return None
+
+    def _askConfirmation(self, confirm_id, confirm_type, data, profile):
+        if profile != self.profile:
+            debug("Ask confirmation ignored: not our profile")
+            return
+        if confirm_type == self.confirm_type():
+            self._confirm_id = confirm_id
+            if self.dest_jids() and not JID(data['from']).bare in [JID(_jid).bare for _jid in self.dest_jids()]:
+                return #file is not sent by a filtered jid
+            else:
+                self.ask(data)
+
+    def ask(self):
+        """
+        The return value is used to answer to the bridge.
+        :rtype: (bool, dict)
+        """
+        raise NotImplementedError
+
+    def answer(self, accepted, answer_data):
+        """
+        :param accepted: boolean
+        :param aswer_data: dict of answer datas
+        """
+        self.bridge.confirmationAnswer(self._confirm_id, False, answer_data, self.profile)
+
+    def run(self):
+        """Auto reply to confirmations requests"""
+        #we register incoming confirmation
+        self.bridge.register("askConfirmation", self._askConfirmation)
+
+        #and we ask those we have missed
+        for confirm_id, confirm_type, data in self.bridge.getWaitingConf(self.profile):
+            self._askConfirmation(confirm_id, confirm_type, data, self.profile)
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/frontends/src/jp/common.py	Wed Feb 05 14:35:26 2014 +0100
@@ -0,0 +1,9 @@
+
+
+class MissingPlugin(Exception):
+    pass
+
+def require(plugin_name):
+    suported_plugins = ["profile"]
+    if plugin_name not in Supported_plugins:
+        raise MissingPlugin
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/frontends/src/jp/file.py	Wed Feb 05 14:35:26 2014 +0100
@@ -0,0 +1,119 @@
+from logging import debug, info, error, warning
+
+import base
+import sys
+import os
+import os.path
+import tarfile
+#import tempfile
+from os.path import abspath, basename, dirname
+from sat.core.i18n import _
+
+file_parser = base.subparser.add_parser('file',
+                                           help = "File managment")
+
+file_parser.add_argument("-p", "--profile", action="store", type=str, default='@DEFAULT@',
+            help=_("Use PROFILE profile key (default: %(default)s)"))
+file_subparser = file_parser.add_subparsers()
+file_send = file_subparser.add_parser('send',
+                                      help = "Send a file to a contact")
+file_send.add_argument("file", type=str, nargs = '*',
+                         help=_("A list of file"))
+file_send.add_argument("jid", type=str,
+                         help=_("The destination jid"))
+file_send.add_argument("-b", "--bz2", action="store_true", default=False,
+                       help=_("Make a bzip2 tarball"))
+
+file_send.set_defaults(func=lambda args : FileSend(args.profile, args.jid, args.file, args.bz2).go())
+
+
+file_recv = file_subparser.add_parser('recv',
+                                      help = "Receive a file to a contact")
+file_recv.add_argument("jids", type=str, nargs="*",
+                         help=_("A list of destination jids"))
+file_recv.add_argument("-m", "--multiple", action="store_true", default=False,
+                       help=_("Wait for a file to be sent by a contact"))
+file_recv.add_argument("-f", "--force", action="store_true", default=False,
+                  help=_("Force overwritting of existing files"))
+
+file_recv.set_defaults(func=lambda args : FileRecv(args.profile,
+                                                   args.jids,
+                                                   args.multiple,
+                                                   args.force).go())
+
+
+
+class FileSend(base.JPWithProfile):
+    def __init__(self, profile, dest_jid, files, bz2):
+        base.JPWithProfile.__init__(self,profile)
+        self.dest_jid = dest_jid
+        self.files = files
+        self.bz2 = bz2
+
+    def send_files(self):
+        """Send files to jabber contact"""
+        for file in self.files:
+            if not os.path.exists(file):
+                error (_(u"File [%s] doesn't exist !") % file)
+                exit(1)
+            if not self.bz2 and os.path.isdir(file):
+                error (_("[%s] is a dir ! Please send files inside or use compression") % file)
+                exit(1)
+
+        full_dest_jid = self._getFullJid(self.dest_jid)
+        if self.bz2:
+            tmpfile = (basename(self.files[0]) or basename(dirname(self.files[0])) ) + '.tar.bz2' #FIXME: tmp, need an algorithm to find a good name/path
+            if os.path.exists(tmpfile):
+                error (_("tmp file (%s) already exists ! Please remove it"), tmpfile)
+                exit(1)
+            warning(_("bz2 is an experimental option at an early dev stage, use with caution"))
+            #FIXME: check free space, writting perm, tmp dir, filename (watch for OS used)
+            info(_("Starting compression, please wait..."))
+            sys.stdout.flush()
+            bz2=tarfile.open(tmpfile, "w:bz2")
+            for file in self.files:
+                info(_("Adding %s"), file)
+                bz2.add(file)
+            bz2.close()
+            info(_("OK !"))
+            path = abspath(tmpfile)
+            self.transfer_data = self.bridge.sendFile(full_dest_jid, path, {}, self.profile)
+        else:
+            for file in self.files:
+                path = abspath(file)
+                self.transfer_data = self.bridge.sendFile(full_dest_jid, path, {}, self.profile) #FIXME: show progress only for last transfer_id
+
+    def run(self):
+        self.send_files()
+
+
+class FileRecv(base.JPAsk):
+    def __init__(self, profile, dest_jids, multiple, force, progress = False):
+        base.JPAsk.__init__(self,profile, start_mainloop = True)
+        self._dest_jids = dest_jids
+        self.multiple = multiple
+        self.force = force
+        self.progress = progress
+
+    def dest_jids(self):
+        return self._dest_jids
+
+    def confirm_type(self):
+        return "FILE_TRANSFER"
+
+    def ask(self, data):
+        answer_data = {}
+        answer_data["dest_path"] = os.getcwd()+'/'+data['filename']
+
+        if self.force or not os.path.exists(answer_data["dest_path"]):
+            self.answer(True, answer_data)
+            info(_("Accepted file [%(filename)s] from %(sender)s") % {'filename':data['filename'], 'sender':data['from']})
+        # self.transfer_data = self.confirm_id # Used by progress
+        else:
+            self.answer(False, answer_data)
+            warning(_("Refused file [%(filename)s] from %(sender)s: a file with the same name already exist") % {'filename':data['filename'], 'sender':data['from']})
+
+
+        if not self.multiple and not self.progress:
+            #we just accept one file
+            self.loop.quit()
--- a/frontends/src/jp/jp	Tue Feb 04 18:54:06 2014 +0100
+++ b/frontends/src/jp/jp	Wed Feb 05 14:35:26 2014 +0100
@@ -1,444 +1,12 @@
-#! /usr/bin/python
-# -*- coding: utf-8 -*-
-
-# jp: a SAT command line tool
-# Copyright (C) 2009, 2010, 2011, 2012, 2013, 2014 Jérôme Poisson (goffi@goffi.org)
-
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Affero General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU Affero General Public License for more details.
-
-# You should have received a copy of the GNU Affero General Public License
-# along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-from __future__ import with_statement
-from sat.core.i18n import _
-
-#consts
-name = u"jp"
-about = name+u""" v%s (c) Jérôme Poisson (aka Goffi) 2009, 2010, 2011, 2012
-
----
-"""+name+u""" Copyright (C) 2009, 2010, 2011, 2012, 2013, 2014 Jérôme Poisson (aka Goffi)
-This program comes with ABSOLUTELY NO WARRANTY;
-This is free software, and you are welcome to redistribute it
-under certain conditions.
----
-
-This software is a command line tool for jabber
-Get the latest version at http://www.goffi.org
-"""
-
-global pbar_available
-pbar_available = True #checked before using ProgressBar
-
-### logging ###
-import logging
-from logging import debug, info, error, warning
-logging.basicConfig(level=logging.DEBUG,
-                    format='%(message)s')
-###
-
-import sys
-import os
-from os.path import abspath, basename, dirname
-from optparse import OptionParser
-from sat.tools.jid import JID
-import gobject
-from sat_frontends.bridge.DBus import DBusBridgeFrontend
-from sat.core.exceptions import BridgeExceptionNoService, BridgeInitError
-from sat.tools.utils import clean_ustr
-import tarfile
-import tempfile
-import shutil
-try:
-    from progressbar import ProgressBar, Percentage, Bar, ETA, FileTransferSpeed
-except ImportError, e:
-        info (_('ProgressBar not available, please download it at http://pypi.python.org/pypi/progressbar'))
-        info (_('Progress bar deactivated\n--\n'))
-        pbar_available=False
-
-
-
-
-class JP(object):
-    def __init__(self):
-        try:
-            self.bridge=DBusBridgeFrontend()
-        except BridgeExceptionNoService:
-            print(_(u"Can't connect to SàT backend, are you sure it's launched ?"))
-            sys.exit(1)
-        except BridgeInitError:
-            print(_(u"Can't init bridge"))
-            sys.exit(1)
-        self.transfer_data = None
-
-    def check_options(self):
-        """Check command line options"""
-        usage=_("""
-        %prog [options] [FILE1 FILE2 ...] JID
-        %prog -w [options] [JID1 JID2 ...]
-
-        %prog --help for options list
-        """)
-        version = unicode(self.bridge.getVersion())
-        parser = OptionParser(usage=usage,version=about % version)
-
-        parser.add_option("-p", "--profile", action="store", type="string", default='@DEFAULT@',
-                    help=_("Use PROFILE profile key (default: %default)"))
-        parser.add_option("-b", "--bz2", action="store_true", default=False,
-                    help=_("Make a bzip2 tarball"))
-        parser.add_option("-w", "--wait-file", action="store_true", default=False,
-                    help=_("Wait for a file to be sent by a contact"))
-        parser.add_option("-m", "--multiple", action="store_true", default=False,
-                    help=_("Accept multiple files (you'll have to stop manually)"))
-        parser.add_option("-f", "--force", action="store_true", default=False,
-                    help=_("Force overwritting of existing files"))
-        parser.add_option("-g", "--progress", action="store_true", default=False,
-                    help=_("Show progress bar"))
-        parser.add_option("-s", "--separate", action="store_true", default=False,
-                    help=_("Separate xmpp messages: send one message per line instead of one message alone."))
-        parser.add_option("-n", "--new-line", action="store_true", default=False,
-                    help=_("Add a new line at the beginning of the input (usefull for ascii art ;))"))
-        parser.add_option("--list-profiles", action="store_true", default=False,
-                    help=_("List available profiles"))
-        parser.add_option("-c", "--create-profile", action="store", type="string", nargs=3,
-                    help=_("Create a profile (args: profile_name jid password)"))
-        parser.add_option("--get-profile", action="store", type="string",
-                    help=_("Get profile informations (arg: profile_name)"))
-        parser.add_option("--rm-profile", action="store", type="string",
-                    help=_("Remove profile"))
-        parser.add_option("--connect", action="store_true", default=False,
-                    help=_("Connect the profile before doing anything else"))
-        parser.add_option("--pipe-in", action="store_true", default=False,
-                    help=_("Wait for the reception of a pipe stream"))
-        parser.add_option("--pipe-out", action="store_true", default=False,
-                    help=_("Pipe a stream out "))
-
-        (self.options, args) = parser.parse_args()
-        if self.options.list_profiles:
-            for p in self.bridge.getProfilesList():
-                info(p)
-            exit(0)
-        if self.options.create_profile or self.options.get_profile:
-            self.start_loop = True
-            return args
-        if self.options.rm_profile:
-            self.start_loop = False
-            return args
-
-        if len(args) < 1 and not self.options.wait_file:
-            parser.error(_("You must specify the destination JID (Jabber ID)").encode('utf-8'))
-
-        if self.options.wait_file or self.options.pipe_in:
-            #several jid
-            self.dest_jids = [arg.decode('utf-8') for arg in args]
-        else:
-            #one dest_jid, other args are files
-            self.dest_jid = JID(args[-1].decode('utf-8'))
-            self.files = args[:-1]
-
-        if not pbar_available and self.options.progress:
-            self.options.progress = False
-            error (_("Option progress is not available, deactivated."))
-
-        if self.options.progress or self.options.wait_file or self.options.connect or self.options.pipe_in:
-            self.start_loop = True  #We have to use loop for these options
-        else:
-            self.start_loop = False
-
-
-        return args
-
-    def create_profile(self):
-        """Create a new profile"""
-        profile, jid, password = self.options.create_profile
-        if profile in self.bridge.getProfilesList():
-            error("Profile %s already exists."%profile)
-            exit(1)
-        self.bridge.asyncCreateProfile(profile, lambda : self._create_profile(profile, jid, password), None)
-
-    def get_profile(self):
-        def setJID(jid):
-            info("jid: %s"%jid)
-            self.bridge.asyncGetParamA("Password", "Connection", profile_key=profile_name, callback=setPassword)
-        def setPassword(password):
-            info("pwd: %s"%password)
-            self.loop.quit()
-        profile_name  = self.options.get_profile
-        if profile_name not in self.bridge.getProfilesList():
-            error("Profile %s doesn't exist."%profile_name)
-            exit(1)
-        self.bridge.asyncGetParamA("JabberID", "Connection", profile_key=profile_name, callback=setJID)
-
-    def rm_profile(self):
-        profile_name  = self.options.rm_profile
-        if profile_name not in self.bridge.getProfilesList():
-            error("Profile %s doesn't exist."%profile_name)
-            exit(1)
-        self.bridge.deleteProfile(profile_name)
-
-    def _create_profile(self, profile_name, jid, password):
-        self.bridge.setParam("JabberID", jid, "Connection" ,profile_key=profile_name)
-        self.bridge.setParam("Server", JID(jid).domain, "Connection", profile_key=profile_name)
-        self.bridge.setParam("Password", password, "Connection", profile_key=profile_name)
-        self.loop.quit()
-
-    def check_jabber_status(self):
-        """Check that jabber status is allright"""
-        def cantConnect():
-            error(_(u"Can't connect profile"))
-            exit(1)
-
-
-        self.profile = self.bridge.getProfileName(self.options.profile)
-        if not self.profile:
-            error(_("The profile asked doesn't exist"))
-            exit(1)
-
-        if self.options.connect: #if connection is asked, we connect the profile
-            self.bridge.asyncConnect(self.profile, self.connected, cantConnect)
-            return
-        elif not self.bridge.isConnected(self.profile):
-            error(_(u"Profile [%(profile)s] is not connected, please connect it before using jp, or use --connect option") % { "profile": self.profile })
-            exit(1)
-
-        self.connected()
-
-    def check_jids(self):
-        """Check jids validity, transform roster name to corresponding jids"""
-        names2jid = {}
-        nodes2jid = {}
+#!/usr/bin/python
 
-        for contact in self.bridge.getContacts(self.options.profile):
-            _jid, attr, groups = contact
-            if attr.has_key("name"):
-                names2jid[attr["name"].lower()] = _jid
-            nodes2jid[JID(_jid).node.lower()] = _jid
-
-        def expandJid(jid):
-            _jid = jid.lower()
-            if _jid in names2jid:
-                expanded = names2jid[_jid]
-            elif _jid in nodes2jid:
-                expanded = nodes2jid[_jid]
-            else:
-                expanded = jid
-            return unicode(expanded)
-
-        def check(jid):
-            if not jid.is_valid:
-                error (_("%s is not a valid JID !"), self.dest_jid)
-                exit(1)
-
-        try:
-            self.dest_jid = expandJid(self.dest_jid)
-            check(self.dest_jid)
-        except AttributeError:
-            pass
-        try:
-            for i in range(len(self.dest_jids)):
-                self.dest_jids[i] = expandJid(self.dest_jids[i])
-                check(self.dest_jids[i])
-        except AttributeError:
-            pass
-
-
-    def send_stdin(self):
-        """Send incomming data on stdin to jabber contact"""
-        header = "\n" if self.options.new_line else ""
-
-        if self.options.separate:  #we send stdin in several messages
-            if header:
-                self.bridge.sendMessage(self.dest_jid, header, profile_key=self.profile, callback=lambda: None, errback=lambda ignore: ignore)
-            while (True):
-                line = clean_ustr(sys.stdin.readline().decode('utf-8','ignore'))
-                if not line:
-                    break
-                self.bridge.sendMessage(self.dest_jid, line.replace("\n",""), profile_key=self.profile, callback=lambda: None, errback=lambda ignore: ignore)
-        else:
-            self.bridge.sendMessage(self.dest_jid, header + clean_ustr(u"".join([stream.decode('utf-8','ignore') for stream in sys.stdin.readlines()])),
-                                    profile_key=self.profile, callback=lambda: None, errback=lambda ignore: ignore)
-
-
-    def pipe_out(self):
-        """Create named pipe, and send stdin to it"""
-        tmp_dir = tempfile.mkdtemp()
-        fifopath = os.path.join(tmp_dir,"pipe_out")
-        os.mkfifo(fifopath)
-        self.bridge.pipeOut(self._getFullJid(self.dest_jid), fifopath, {}, self.profile)
-        with open(fifopath, 'w') as f:
-            shutil.copyfileobj(sys.stdin, f)
-        shutil.rmtree(tmp_dir)
-
-
-    def send_files(self):
-        """Send files to jabber contact"""
-
-        for file in self.files:
-            if not os.path.exists(file):
-                error (_(u"File [%s] doesn't exist !") % file)
-                exit(1)
-            if not self.options.bz2 and os.path.isdir(file):
-                error (_("[%s] is a dir ! Please send files inside or use compression") % file)
-                exit(1)
-
-        full_dest_jid = self._getFullJid(self.dest_jid)
-        if self.options.bz2:
-            tmpfile = (basename(self.files[0]) or basename(dirname(self.files[0])) ) + '.tar.bz2' #FIXME: tmp, need an algorithm to find a good name/path
-            if os.path.exists(tmpfile):
-                error (_("tmp file (%s) already exists ! Please remove it"), tmpfile)
-                exit(1)
-            warning(_("bz2 is an experimental option at an early dev stage, use with caution"))
-            #FIXME: check free space, writting perm, tmp dir, filename (watch for OS used)
-            info(_("Starting compression, please wait..."))
-            sys.stdout.flush()
-            bz2=tarfile.open(tmpfile, "w:bz2")
-            for file in self.files:
-                info(_("Adding %s"), file)
-                bz2.add(file)
-            bz2.close()
-            info(_("OK !"))
-            path = abspath(tmpfile)
-            self.transfer_data = self.bridge.sendFile(full_dest_jid, path, {}, self.profile)
-        else:
-            for file in self.files:
-                path = abspath(file)
-                self.transfer_data = self.bridge.sendFile(full_dest_jid, path, {}, self.profile) #FIXME: show progress only for last transfer_id
-
-
-    def _getFullJid(self, param_jid):
-        """Return the full jid if possible (add last resource when find a bare jid"""
-        _jid = JID(param_jid)
-        if not _jid.resource:
-            #if the resource is not given, we try to add the last known resource
-            last_resource = self.bridge.getLastResource(param_jid, self.options.profile)
-            if last_resource:
-                return "%s/%s" % (_jid.bare, last_resource)
-        return param_jid
-
-
-    def askConfirmation(self, confirm_id, confirm_type, data, profile):
-        """CB used for file transfer, accept files depending on parameters"""
-        if profile != self.profile:
-            debug("Ask confirmation ignored: not our profile")
-            return
-        answer_data={}
-        if confirm_type == "FILE_TRANSFER":
-            if not self.options.wait_file:
-                return
-            if self.dest_jids and not JID(data['from']).bare in [JID(_jid).bare for _jid in self.dest_jids]:
-                return #file is not sent by a filtered jid
-
-            answer_data["dest_path"] = os.getcwd()+'/'+data['filename']
-
-            if self.options.force or not os.path.exists(answer_data["dest_path"]):
-                self.bridge.confirmationAnswer(confirm_id, True, answer_data, profile)
-                info(_("Accepted file [%(filename)s] from %(sender)s") % {'filename':data['filename'], 'sender':data['from']})
-                self.transfer_data = confirm_id
-            else:
-                self.bridge.confirmationAnswer(confirm_id, False, answer_data, profile)
-                warning(_("Refused file [%(filename)s] from %(sender)s: a file with the same name already exist") % {'filename':data['filename'], 'sender':data['from']})
-
-
-            if not self.options.multiple and not self.options.progress:
-                #we just accept one file
-                self.loop.quit()
-        elif confirm_type == "PIPE_TRANSFER":
-            if not self.options.pipe_in:
-                return
-            if self.dest_jids and not JID(data['from']).bare in [JID(_jid).bare for _jid in self.dest_jids]:
-                return #pipe stream is not sent by a filtered jid
-
-            tmp_dir = tempfile.mkdtemp()
-            fifopath = os.path.join(tmp_dir,"pipe_in")
-            answer_data["dest_path"] = fifopath
-            os.mkfifo(fifopath)
-            self.bridge.confirmationAnswer(confirm_id, True, answer_data, profile)
-            with open(fifopath, 'r') as f:
-                shutil.copyfileobj(f, sys.stdout)
-            shutil.rmtree(tmp_dir)
-            self.loop.quit()
-
-
-    def actionResult(self, action_type, action_id, data, profile):
-        #FIXME
-        info (_("FIXME: actionResult not implemented"))
-
-    def confirmation_reply(self):
-        """Auto reply to confirmations requests"""
-        #we register incoming confirmation
-        self.bridge.register("askConfirmation", self.askConfirmation)
-
-        #and we ask those we have missed
-        for confirm_id, confirm_type, data in self.bridge.getWaitingConf(self.profile):
-            self.askConfirmation(confirm_id, confirm_type, data, self.profile)
-
-    def progressCB(self):
-        if self.transfer_data:
-            transfer_id = self.transfer_data
-            data = self.bridge.getProgress(transfer_id, self.profile)
-            if data:
-                if not data['position']:
-                    data['position'] = '0'
-                if not self.pbar:
-                    #first answer, we must construct the bar
-                    self.pbar = ProgressBar(int(data['size']),[_("Progress: "),Percentage()," ",Bar()," ",FileTransferSpeed()," ",ETA()])
-                    self.pbar.start()
-
-                self.pbar.update(int(data['position']))
-            elif self.pbar:
-                self.pbar.finish()
-                if not self.options.multiple:
-                    self.loop.quit()
-                return False
-
-        return True
-
-    def go(self):
-        self.check_options()
-        if self.options.create_profile:
-            self.create_profile()
-        elif self.options.get_profile:
-            self.get_profile()
-        elif self.options.rm_profile:
-            self.rm_profile()
-        else:
-            self.check_jabber_status()
-        if self.start_loop:
-            self.loop = gobject.MainLoop()
-            try:
-                self.loop.run()
-            except KeyboardInterrupt:
-                info(_("User interruption: good bye"))
-
-    def connected(self):
-        """This is called when the profile is connected"""
-        self.check_jids()
-        if self.options.wait_file or self.options.pipe_in:
-            self.confirmation_reply()
-        else:
-            if self.files:
-                self.send_files()
-            elif self.options.pipe_out:
-                self.pipe_out()
-            else:
-                self.send_stdin()
-
-        if self.options.progress:
-            self.pbar = None
-            gobject.timeout_add(10, self.progressCB)
-
-        if self.start_loop and not self.options.progress and not self.options.wait_file and not self.options.pipe_in:
-            self.loop.quit()
-
+import base
+import message
+import pipe
+import profile
+import file
 
 if __name__ == "__main__":
-    jp = JP()
-    jp.go()
+    args = base.parser.parse_args()
+    args.func(args)
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/frontends/src/jp/message.py	Wed Feb 05 14:35:26 2014 +0100
@@ -0,0 +1,49 @@
+import base
+import sys
+from sat.core.i18n import _
+
+message_parser = base.subparser.add_parser('message', help = "Send a message to a contact")
+message_parser.add_argument("-p", "--profile", action="store", type=str, default='@DEFAULT@',
+            help=_("Use PROFILE profile key (default: %(default)s)"))
+
+message_parser.add_argument("-s", "--separate", action="store_true", default=False,
+            help=_("Separate xmpp messages: send one message per line instead of one message alone."))
+message_parser.add_argument("-n", "--new-line", action="store_true", default=False,
+            help=_("Add a new line at the beginning of the input (usefull for ascii art ;))"))
+message_parser.add_argument("jid", type=str,
+            help=_("The destination jid"))
+
+def launch(args):
+    profile = args.profile
+    jp = Message(profile, args.jid, args.new_line, args.separate)
+    jp.go()
+
+message_parser.set_defaults(func=launch)
+
+class Message(base.JPWithProfile):
+    def __init__(self, profile, dest_jid, new_line = False, separate = False):
+        base.JPWithProfile.__init__(self,profile)
+        self.dest_jid = dest_jid
+        self.new_line = new_line
+        self.separate = separate
+
+
+    def send_stdin(self, dest_jid , new_line = False, separate = False):
+        """Send incomming data on stdin to jabber contact"""
+        header = "\n" if new_line else ""
+
+        if separate:  #we send stdin in several messages
+            if header:
+                self.bridge.sendMessage(dest_jid, header, profile_key=self.profile, callback=lambda: None, errback=lambda ignore: ignore)
+            while (True):
+                line = base.clean_ustr(sys.stdin.readline().decode('utf-8','ignore'))
+                if not line:
+                    break
+                self.bridge.sendMessage(dest_jid, line.replace("\n",""), profile_key=self.profile, callback=lambda: None, errback=lambda ignore: ignore)
+        else:
+            self.bridge.sendMessage(dest_jid, header + base.clean_ustr(u"".join([stream.decode('utf-8','ignore') for stream in sys.stdin.readlines()])), profile_key=self.profile, callback=lambda: None, errback=lambda ignore: ignore)
+
+    def run(self):
+        jids = self.check_jids([self.dest_jid])
+        jid = jids[0]
+        self.send_stdin(jid, self.new_line, self.separate)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/frontends/src/jp/pipe.py	Wed Feb 05 14:35:26 2014 +0100
@@ -0,0 +1,71 @@
+import base
+
+import tempfile
+import os
+import shutil
+from sat.core.i18n import _
+
+
+parser = base.subparser.add_parser('pipe',
+                                           help = "File managment")
+
+parser.add_argument("-p", "--profile", action="store", type=str, default='@DEFAULT@',
+            help=_("Use PROFILE profile key (default: %(default)s)"))
+
+subparser = parser.add_subparsers()
+pipein = subparser.add_parser('out',
+                                      help = "Send a pipe to a contact")
+pipein.add_argument("jid", type=str,
+                         help=_("The destination jid"))
+
+pipein.set_defaults(func=lambda args : PipeOut(args.profile, args.jid).go())
+
+pipeout = subparser.add_parser('in',
+                                      help = "Send a pipe to a contact")
+pipeout.add_argument("jids", type=str, nargs="*",
+                     help=_("The destination jid"))
+
+pipeout.set_defaults(func=lambda args : PipeIn(args.profile, args.jids).go())
+
+class PipeOut(base.JPWithProfile):
+    def __init__(self, profile, dest_jid):
+        base.JPWithProfile.__init__(self,profile)
+        self.dest_jid = dest_jid
+
+    def pipe_out(self, dest_jid):
+        """Create named pipe, and send stdin to it"""
+        tmp_dir = tempfile.mkdtemp()
+        fifopath = os.path.join(tmp_dir,"pipe_out")
+        os.mkfifo(fifopath)
+        self.bridge.pipeOut(self._getFullJid(dest_jid), fifopath, {}, self.profile)
+        with open(fifopath, 'w') as f:
+            shutil.copyfileobj(sys.stdin, f)
+        shutil.rmtree(tmp_dir)
+
+    def run(self):
+        jids = self.check_jids([self.dest_jid])
+        jid = jids[0]
+        self.pipe_out(jid)
+
+class PipeIn(base.JPAsk):
+    def __init__(self, profile, dest_jids):
+        base.JPAsk.__init__(self,profile, start_mainloop = True)
+        self._dest_jids = dest_jids
+
+    def dest_jids(self):
+        return self._dest_jids
+
+    def confirm_type(self):
+        return "PIPE_TRANSFER"
+
+    def ask(self, data):
+        answer_data = {}
+        tmp_dir = tempfile.mkdtemp()
+        fifopath = os.path.join(tmp_dir,"pipe_in")
+        answer_data["dest_path"] = fifopath
+        os.mkfifo(fifopath)
+        self.answer(True, answer_data)
+        with open(fifopath, 'r') as f:
+            shutil.copyfileobj(f, sys.stdout)
+        shutil.rmtree(tmp_dir)
+        self.loop.quit()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/frontends/src/jp/profile.py	Wed Feb 05 14:35:26 2014 +0100
@@ -0,0 +1,111 @@
+"""This module permits to manage profiles. It can list, create, delete
+and retrieve informations about a profile."""
+
+import base
+import sys
+
+from logging import debug, info, error, warning
+from sat.core.i18n import _
+
+profile_parser = base.subparser.add_parser('profile',
+                                          help = "Profile Commands")
+profile_subparser = profile_parser.add_subparsers()
+
+################################################################################
+#                                   DELETE                                     #
+################################################################################
+profile_delete = profile_subparser.add_parser('delete',
+                                              help = "Delete a profile")
+profile_delete.add_argument('profile', type=str,
+                                   help='the name of the profile')
+profile_delete.set_defaults(
+    func = lambda args : ProfileDelete(args.profile).go())
+
+class ProfileDelete(base.JP):
+    def __init__(self, profile_name):
+        base.JP.__init__(self)
+        self.profile_name = profile_name
+
+    def run(self):
+        if self.profile_name not in self.bridge.getProfilesList():
+            error("Profile %s doesn't exist."%self.profile_name)
+            exit(1)
+        self.bridge.deleteProfile(self.profile_name)
+
+################################################################################
+#                                   INFO                                       #
+################################################################################
+profile_info = profile_subparser.add_parser('info',
+                                              help = "Get informations about a profile")
+profile_info.add_argument('profile', type=str,
+                           help='the name of the profile')
+profile_info.set_defaults(
+    func = lambda args : ProfileInfo(args.profile).go())
+
+class ProfileInfo(base.JP):
+    def __init__(self, profile_name):
+        base.JP.__init__(self)
+        self.profile_name = profile_name
+
+    def run(self):
+        def getJID(jid):
+            info("jid: %s"%jid)
+            self.bridge.asyncGetParamA("Password", "Connection", profile_key=self.profile_name, callback=getPassword)
+        def getPassword(password):
+            info("pwd: %s"%password)
+            self.loop.quit()
+        if self.profile_name not in self.bridge.getProfilesList():
+            error("Profile %s doesn't exist."%self.profile_name)
+            exit(1)
+
+        self.start_mainloop()
+        self.bridge.asyncGetParamA("JabberID", "Connection", profile_key=self.profile_name, callback=getJID)
+
+################################################################################
+#                                   LIST                                       #
+################################################################################
+profile_list = profile_subparser.add_parser('list',
+                                              help = "List profiles")
+profile_list.set_defaults(
+    func = lambda args : ProfileList().go())
+
+class ProfileList(base.JP):
+    def run(self):
+        for p in self.bridge.getProfilesList():
+            info(p)
+
+################################################################################
+#                                   CREATE                                     #
+################################################################################
+create_parser = profile_subparser.add_parser('create',
+                                          help = "Create a new profile")
+create_parser.add_argument('profile', type=str,
+                           help='the name of the profile')
+create_parser.add_argument('jid', type=str,
+                           help='the jid of the profile')
+create_parser.add_argument('password', type=str,
+                           help='the password of the profile')
+create_parser.set_defaults(
+    func = lambda args : ProfileCreate(args.profile, args.jid, args.password).go())
+
+class ProfileCreate(base.JP):
+    def __init__(self, profile_name, jid, password):
+        base.JP.__init__(self)
+        self.profile_name = profile_name
+        self.jid = jid
+        self.password = password
+
+    def _create_profile(self, profile_name, jid, password):
+        self.bridge.setParam("JabberID", jid, "Connection" ,profile_key=profile_name)
+        self.bridge.setParam("Server", base.JID(jid).domain, "Connection", profile_key=profile_name)
+        self.bridge.setParam("Password", password, "Connection", profile_key=profile_name)
+        self.loop.quit()
+
+    def run(self):
+        """Create a new profile"""
+        if self.profile_name in self.bridge.getProfilesList():
+            error("Profile %s already exists."%self.profile_name)
+            exit(1)
+        self.start_mainloop()
+        self.bridge.asyncCreateProfile(self.profile_name, lambda : self._create_profile(self.profile_name, self.jid, self.password), None)
+