# HG changeset patch # User Goffi # Date 1392036249 -3600 # Node ID c39117d00f356ec6ed1436d1a9d9d5c956466bd1 # Parent 4429bd7d5efb71b5784f4546f48295a6fcb4919b jp: refactoring: - imports from sat_frontends.jp instead of local imports - added __init__.py - commands now inherits from a base class: each base.CommandBase instance is a subcommand - new arguments are added in CommandBase.add_parser_options methods, starting point si CommandBase.run or CommandBase.connected if a profile connection is needed - commands are exported using a __commands__ variable at the top of the module - sub-subcommand are easily added by using an other CommandBase instance as parent instead of using a Jp instance. In this case, the parent subcommand must be the one exported, and have a subcommands iterable (see cmd_file or cmd_pipe for examples). - options which are often used (like --profile) are automatically added on demand (use_profile=True, use_progress=True) - commands are automatically loaded when there are in a module named cmd_XXX - restored --connect option - restored progress bar - restored getVersion bridge call on jp --version - fixed file and pipe commands - fixed forgotten translations - fixed non SàT compliant docstrings - better about/version dialog diff -r 4429bd7d5efb -r c39117d00f35 frontends/src/jp/__init__.py diff -r 4429bd7d5efb -r c39117d00f35 frontends/src/jp/base.py --- a/frontends/src/jp/base.py Wed Feb 05 14:52:40 2014 +0100 +++ b/frontends/src/jp/base.py Mon Feb 10 13:44:09 2014 +0100 @@ -17,24 +17,8 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -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 @@ -46,35 +30,40 @@ ### import sys -import os -from os.path import abspath, basename, dirname -from argparse import ArgumentParser +import locale +import os.path +import argparse +import gobject +from glob import iglob +from importlib import import_module 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 +from sat.core import exceptions +import sat_frontends.jp 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 + import progressbar +except ImportError: + info (_('ProgressBar not available, please download it at http://pypi.python.org/pypi/progressbar')) + info (_('Progress bar deactivated\n--\n')) + progressbar=None + +#consts +prog_name = u"jp" +description = """This software is a command line tool for XMPP. +Get the latest version at http://sat.goffi.org""" + +copyleft = 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. +""" -#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 +def unicode_decoder(arg): + # Needed to have unicode strings from arguments + return arg.decode(locale.getpreferredencoding()) - -class JP(object): +class Jp(object): """ This class can be use to establish a connection with the bridge. Moreover, it should manage a main loop. @@ -83,64 +72,112 @@ specify what kind of operation you want to perform. """ - def __init__(self, start_mainloop = False): + def __init__(self): try: - self.bridge=DBusBridgeFrontend() - except BridgeExceptionNoService: + self.bridge = DBusBridgeFrontend() + except exceptions.BridgeExceptionNoService: print(_(u"Can't connect to SàT backend, are you sure it's launched ?")) sys.exit(1) - except BridgeInitError: + except excpetions.BridgeInitError: print(_(u"Can't init bridge")) sys.exit(1) - self._start_loop = start_mainloop + self.parser = argparse.ArgumentParser(formatter_class=argparse.RawDescriptionHelpFormatter, + description=description) + + self._make_parents() + self.add_parser_options() + self.subparsers = self.parser.add_subparsers(title=_('Available commands'), dest='subparser_name') + self._auto_loop = False # when loop is used for internal reasons + self.need_loop = False # to set by commands when loop is needed + self._progress_id = None # TODO: manage several progress ids + self.quit_on_progress_end = True # set to False if you manage yourself exiting, or if you want the user to stop by himself + + @property + def version(self): + return self.bridge.getVersion() - def run(self): - raise NotImplementedError + @property + def progress_id(self): + return self._progress_id + + @progress_id.setter + def progress_id(self, value): + self._progress_id = value + + def _make_parents(self): + self.parents = {} + + profile_parent = self.parents['profile'] = argparse.ArgumentParser(add_help=False) + profile_parent.add_argument("-p", "--profile", action="store", type=str, default='@DEFAULT@', help=_("Use PROFILE profile key (default: %(default)s)")) + profile_parent.add_argument("-c", "--connect", action="store_true", help=_("Connect the profile before doing anything else")) + + progress_parent = self.parents['progress'] = argparse.ArgumentParser(add_help=False) + if progressbar: + progress_parent.add_argument("-g", "--progress", action="store_true", help=_("Show progress bar")) - 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 add_parser_options(self): + self.parser.add_argument('--version', action='version', version=("%(name)s %(version)s %(copyleft)s" % {'name': prog_name, 'version': self.version, 'copyleft': copyleft})) + + def import_commands(self): + """ Automaticaly import commands to jp + looks from modules names cmd_*.py in jp path and import them + + """ + path = os.path.dirname(sat_frontends.jp.__file__) + modules = (os.path.splitext(module)[0] for module in map(os.path.basename, iglob(os.path.join(path, "cmd_*.py")))) + for module_name in modules: + module = import_module("sat_frontends.jp."+module_name) + try: + self.import_command_module(module) + except ImportError: + continue - def _loop_start(self): + def import_command_module(self, module): + """ Add commands from a module to jp + @param module: module containing commands + + """ + try: + for classname in module.__commands__: + cls = getattr(module, classname) + except AttributeError: + warning(_("Invalid module %s") % module) + raise ImportError + cls(self) + + + def run(self, args=None): + self.args = self.parser.parse_args(args) + self.args.func() + if self.need_loop or self._auto_loop: + self._start_loop() + + def _start_loop(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() - + def stop_loop(self): + try: + self.loop.quit() + except AttributeError: + pass -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 quit(self, errcode=0): + self.stop_loop() + if errcode: + sys.exit(errcode) 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 + @param profile: profile name + @param jids: list of jids + @return: List of jids + """ names2jid = {} nodes2jid = {} @@ -151,7 +188,7 @@ names2jid[attr["name"].lower()] = _jid nodes2jid[JID(_jid).node.lower()] = _jid - def expandJid(jid): + def expand_jid(jid): _jid = jid.lower() if _jid in names2jid: expanded = names2jid[_jid] @@ -164,100 +201,204 @@ def check(jid): if not jid.is_valid: error (_("%s is not a valid JID !"), jid) - exit(1) + self.quit(1) dest_jids=[] try: for i in range(len(jids)): - dest_jids.append(expandJid(jids[i])) + dest_jids.append(expand_jid(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 + def connect_profile(self, callback): + """ Check if the profile is connected + @param callback: method to call when profile is connected + @exit: - 1 when profile is not connected and --connect is not set + - 1 when the profile doesn't exists + - 1 when there is a connection error + """ + # FIXME: need better exit codes + + def cant_connect(): error(_(u"Can't connect profile")) - exit(1) + self.quit(1) - self.profile = self.bridge.getProfileName(self.profile_name) - if not self.profile: - error(_("The profile asked doesn't exist")) - exit(1) + self.profile = self.bridge.getProfileName(self.args.profile) - if self.bridge.isConnected(self.profile): - print "Already connected" - else: - self._start_loop = True - self.bridge.asyncConnect(self.profile, self._run, cantConnect) + if not self.profile: + error(_("The profile [%s] doesn't exist") % self.args.profile) + self.quit(1) + + if self.args.connect: #if connection is asked, we connect the profile + self.bridge.asyncConnect(self.profile, callback, cant_connect) + self._auto_loop = True return - self.run() + 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 }) + self.quit(1) - def _getFullJid(self, param_jid): - """Return the full jid if possible (add last resource when find a bare jid""" + callback() + + def get_full_jid(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) + last_resource = self.bridge.getLastResource(param_jid, self.profile) 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() + def watch_progress(self): + self.pbar = None + gobject.timeout_add(10, self._progress_cb) + def _progress_cb(self): + if self.progress_id: + data = self.bridge.getProgress(self.progress_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.ProgressBar(int(data['size']), + [_("Progress: "),progressbar.Percentage(), + " ", + progressbar.Bar(), + " ", + progressbar.FileTransferSpeed(), + " ", + progressbar.ETA()]) + self.pbar.start() + + self.pbar.update(int(data['position'])) + + elif self.pbar: + self.pbar.finish() + if self.quit_on_progress_end: + self.quit() + return False + + return True -class JPAsk(JPWithProfile): - def confirm_type(self): - """Must return a string containing the confirm type. For instance, - FILE_TRANSFER or PIPE_TRANSFER, etc. +class CommandBase(object): - :rtype: str + def __init__(self, host, name, use_profile=True, use_progress=False, help=None, **kwargs): + """ Initialise CommandBase + @param host: Jp instance + @param name: name of the new command + @param use_profile: if True, add profile selection/connection commands + @param use_progress: if True, add progress bar activation commands + @param help: help message to display + @param **kwargs: args passed to ArgumentParser + """ - raise NotImplemented + try: # If we have subcommands, host is a CommandBase and we need to use host.host + self.host = host.host + except AttributeError: + self.host = host + + parents = kwargs.setdefault('parents', set()) + if use_profile: + #self.host.parents['profile'] is an ArgumentParser with profile connection arguments + parents.add(self.host.parents['profile']) + if use_progress: + parents.add(self.host.parents['progress']) + + self.parser = host.subparsers.add_parser(name, help=help, **kwargs) + if hasattr(self, "subcommands"): + self.subparsers = self.parser.add_subparsers() + else: + self.parser.set_defaults(func=self.run) + self.add_parser_options() + + @property + def args(self): + return self.host.args + + @property + def need_loop(self): + return self.host.need_loop + + @need_loop.setter + def need_loop(self, value): + self.host.need_loop = value + + @property + def profile(self): + return self.host.profile + + @property + def progress_id(self): + return self.host.progress_id - def dest_jids(self): - return None + @progress_id.setter + def progress_id(self, value): + self.host.progress_id = value + + def add_parser_options(self): + try: + subcommands = self.subcommands + except AttributeError: + # We don't have subcommands, the class need to implements add_parser_options + raise NotImplementedError + + # now we add subcommands to ourself + for cls in subcommands: + cls(self) - def _askConfirmation(self, confirm_id, confirm_type, data, profile): + def run(self): + try: + if self.args.profile: + self.host.connect_profile(self.connected) + except AttributeError: + # the command doesn't need to connect profile + pass + try: + if self.args.progress: + self.host.watch_progress() + except AttributeError: + # the command doesn't use progress bar + pass + + def connected(self): + if not self.need_loop: + self.host.stop_loop() + + +class CommandAnswering(CommandBase): + #FIXME: temp, will be refactored when progress_bar/confirmations will be refactored + + def _ask_confirmation(self, confirm_id, confirm_type, data, profile): + """ Callback used for file transfer, accept files depending on parameters""" 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()]: + if confirm_type == self.confirm_type: + 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) + self.ask(data, confirm_id) def ask(self): """ The return value is used to answer to the bridge. - :rtype: (bool, dict) + @return: bool or 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): + def connected(self): """Auto reply to confirmations requests""" - #we register incoming confirmation - self.bridge.register("askConfirmation", self._askConfirmation) + self.need_loop = True + super(CommandAnswering, self).connected() + # we watch confirmation signals + self.host.bridge.register("ask_confirmation", self._ask_confirmation) #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) - - + for confirm_id, confirm_type, data in self.host.bridge.getWaitingConf(self.profile): + self._ask_confirmation(confirm_id, confirm_type, data, self.profile) diff -r 4429bd7d5efb -r c39117d00f35 frontends/src/jp/cmd_file.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/frontends/src/jp/cmd_file.py Mon Feb 10 13:44:09 2014 +0100 @@ -0,0 +1,126 @@ +#! /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 . + +from logging import debug, info, error, warning + +import base +import sys +import os +import os.path +import tarfile +#import tempfile +from sat.core.i18n import _ + +__commands__ = ["File"] + +class Send(base.CommandBase): + def __init__(self, host): + super(Send, self).__init__(host, 'send', use_progress=True, help=_('Send a file to a contact')) + + def add_parser_options(self): + self.parser.add_argument("files", type=str, nargs = '+', help=_("A list of file")) + self.parser.add_argument("jid", type=base.unicode_decoder, help=_("The destination jid")) + self.parser.add_argument("-b", "--bz2", action="store_true", help=_("Make a bzip2 tarball")) + + def connected(self): + """Send files to jabber contact""" + self.need_loop=True + super(Send, self).connected() + self.send_files() + + def send_files(self): + + for file_ in self.args.files: + if not os.path.exists(file_): + error (_(u"file [%s] doesn't exist !") % file_) + self.host.quit(1) + if not self.args.bz2 and os.path.isdir(file_): + error (_("[%s] is a dir ! Please send files inside or use compression") % file_) + self.host.quit(1) + + full_dest_jid = self.host.get_full_jid(self.args.jid) + + if self.args.bz2: + tmpfile = (os.path.basename(self.args.files[0]) or os.path.basename(os.path.dirname(self.args.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) + print _(u"Starting compression, please wait...") + sys.stdout.flush() + bz2 = tarfile.open(tmpfile, "w:bz2") + for file_ in self.args.files: + print _(u"Adding %s") % file_ + bz2.add(file_) + bz2.close() + print _(u"Done !") + path = os.path.abspath(tmpfile) + self.progress_id = self.host.bridge.sendFile(full_dest_jid, path, {}, self.profile) + else: + for file_ in self.args.files: + path = os.path.abspath(file_) + self.progress_id = self.host.bridge.sendFile(full_dest_jid, path, {}, self.profile) #FIXME: show progress only for last progress_id + + +class Receive(base.CommandAnswering): + confirm_type = "FILE_TRANSFER" + + def __init__(self, host): + super(Receive, self).__init__(host, 'recv', use_progress=True, help=_('Wait for a file to be sent by a contact')) + + @property + def dest_jids(self): + return self.args.jids + + def add_parser_options(self): + self.parser.add_argument("jids", type=base.unicode_decoder, nargs="*", help=_('Jids accepted (none means "accept everything")')) + self.parser.add_argument("-m", "--multiple", action="store_true", help=_("Accept multiple files (you'll have to stop manually)")) + self.parser.add_argument("-f", "--force", action="store_true", help=_("Force overwritting of existing files")) + + + def ask(self, data, confirm_id): + answer_data = {} + answer_data["dest_path"] = os.path.join(os.getcwd(), data['filename']) + + if self.args.force or not os.path.exists(answer_data["dest_path"]): + self.host.bridge.confirmationAnswer(confirm_id, True, answer_data, self.profile) + info(_("Accepted file [%(filename)s] from %(sender)s") % {'filename':data['filename'], 'sender':data['from']}) + self.progress_id = confirm_id + else: + self.host.bridge.confirmationAnswer(confirm_id, False, answer_data, self.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.args.multiple: + self.host.quit() + + if not self.args.multiple and not self.args.progress: + #we just accept one file + self.host.quit() + + def run(self): + super(Receive, self).run() + if self.args.multiple: + self.host.quit_on_progress_end = False + + +class File(base.CommandBase): + subcommands = (Send, Receive) + + def __init__(self, host): + super(File, self).__init__(host, 'file', use_profile=False, help=_('File sending/receiving')) diff -r 4429bd7d5efb -r c39117d00f35 frontends/src/jp/cmd_message.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/frontends/src/jp/cmd_message.py Mon Feb 10 13:44:09 2014 +0100 @@ -0,0 +1,61 @@ +#! /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 . + +from sat_frontends.jp import base +import sys +from sat.core.i18n import _ +from sat.tools.utils import clean_ustr + +__commands__ = ["Message"] + + +class Message(base.CommandBase): + + def __init__(self, host): + super(Message, self).__init__(host, 'message', help=_('Send a message to a contact')) + + def add_parser_options(self): + self.parser.add_argument("-s", "--separate", action="store_true", help=_("Separate xmpp messages: send one message per line instead of one message alone.")) + self.parser.add_argument("-n", "--new-line", action="store_true", help=_("Add a new line at the beginning of the input (usefull for ascii art ;))")) + self.parser.add_argument("jid", type=str, help=_("The destination jid")) + + def connected(self): + super(Message, self).connected() + jids = self.host.check_jids([self.args.jid]) + jid = jids[0] + self.send_stdin(jid) + + def send_stdin(self, dest_jid): + """Send incomming data on stdin to jabber contact + @param dest_jid: destination jid""" + header = "\n" if self.args.new_line else "" + + if self.args.separate: #we send stdin in several messages + + if header: + self.host.bridge.sendMessage(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.host.bridge.sendMessage(dest_jid, line.replace("\n",""), profile_key=self.host.profile, callback=lambda: None, errback=lambda ignore: ignore) + + else: + self.host.bridge.sendMessage(dest_jid, header + clean_ustr(u"".join([stream.decode('utf-8','ignore') for stream in sys.stdin.readlines()])), profile_key=self.host.profile, callback=lambda: None, errback=lambda ignore: ignore) diff -r 4429bd7d5efb -r c39117d00f35 frontends/src/jp/cmd_pipe.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/frontends/src/jp/cmd_pipe.py Mon Feb 10 13:44:09 2014 +0100 @@ -0,0 +1,87 @@ +#! /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 . + +from sat_frontends.jp import base + +import tempfile +import sys +import os +import os.path +import shutil +from sat.core.i18n import _ + +__commands__ = ["Pipe"] + +class PipeOut(base.CommandBase): + + def __init__(self, host): + super(PipeOut, self).__init__(host, 'out', help=_('Pipe a stream out')) + + def add_parser_options(self): + self.parser.add_argument("jid", type=base.unicode_decoder, help=_("The destination jid")) + + 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.host.bridge.pipeOut(self.host.get_full_jid(self.args.jid), fifopath, {}, self.profile) + with open(fifopath, 'w') as f: + shutil.copyfileobj(sys.stdin, f) + shutil.rmtree(tmp_dir) + self.host.quit() + + def connected(self): + # TODO: check_jids + self.need_loop = True + super(PipeOut, self).connected() + self.pipe_out() + + +class PipeIn(base.CommandAnswering): + confirm_type = "PIPE_TRANSFER" + + def __init__(self, host): + super(PipeIn, self).__init__(host, 'in', help=_('Wait for the reception of a pipe stream')) + + @property + def dest_jids(self): + return self.args.jids + + def add_parser_options(self): + self.parser.add_argument("jids", type=base.unicode_decoder, nargs="*", help=_('Jids accepted (none means "accept everything")')) + + def ask(self, data, confirm_id): + answer_data = {} + tmp_dir = tempfile.mkdtemp() + fifopath = os.path.join(tmp_dir,"pipe_in") + answer_data["dest_path"] = fifopath + os.mkfifo(fifopath) + self.host.bridge.confirmationAnswer(confirm_id, True, answer_data, self.profile) + with open(fifopath, 'r') as f: + shutil.copyfileobj(f, sys.stdout) + shutil.rmtree(tmp_dir) + self.host.quit() + + +class Pipe(base.CommandBase): + subcommands = (PipeOut, PipeIn) + + def __init__(self, host): + super(Pipe, self).__init__(host, 'pipe', use_profile=False, help=_('Stream piping through XMPP')) diff -r 4429bd7d5efb -r c39117d00f35 frontends/src/jp/cmd_profile.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/frontends/src/jp/cmd_profile.py Mon Feb 10 13:44:09 2014 +0100 @@ -0,0 +1,115 @@ +#! /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 . + +"""This module permits to manage profiles. It can list, create, delete +and retrieve informations about a profile.""" + +from logging import debug, info, error, warning +from sat.core.i18n import _ +from sat_frontends.jp import base +from sat.tools.jid import JID + +__commands__ = ["Profile"] + +PROFILE_HELP = _('The name of the profile') + + +class ProfileDelete(base.CommandBase): + def __init__(self, host): + super(ProfileDelete, self).__init__(host, 'delete', use_profile=False, help=_('Delete a profile')) + + def add_parser_options(self): + self.parser.add_argument('profile', type=str, help=PROFILE_HELP) + + def run(self): + super(ProfileDelete, self).run() + if self.args.profile not in self.host.bridge.getProfilesList(): + error("Profile %s doesn't exist." % self.args.profile) + self.host.quit(1) + self.host.bridge.deleteProfile(self.args.profile) + + +class ProfileInfo(base.CommandBase): + def __init__(self, host): + super(ProfileInfo, self).__init__(host, 'info', use_profile=False, help=_('Get informations about a profile')) + + def add_parser_options(self): + self.parser.add_argument('profile', type=str, help=PROFILE_HELP) + + def run(self): + super(ProfileInfo, self).run() + self.need_loop = True + + def getPassword(password): + print "pwd: %s" % password + self.host.quit() + + def getJID(jid): + print "jid: %s" % jid + self.host.bridge.asyncGetParamA("Password", "Connection", profile_key=self.args.profile, callback=getPassword) + + if self.args.profile not in self.host.bridge.getProfilesList(): + error("Profile %s doesn't exist." % self.args.profile) + self.host.quit(1) + + self.host.bridge.asyncGetParamA("JabberID", "Connection", profile_key=self.args.profile, callback=getJID) + + +class ProfileList(base.CommandBase): + def __init__(self, host): + super(ProfileList, self).__init__(host, 'list', use_profile=False, help=_('List profiles')) + + def add_parser_options(self): + pass + + def run(self): + super(ProfileList, self).run() + for profile in self.host.bridge.getProfilesList(): + print profile + + +class ProfileCreate(base.CommandBase): + def __init__(self, host): + super(ProfileCreate, self).__init__(host, 'create', use_profile=False, help=_('Create a new profile')) + + def add_parser_options(self): + self.parser.add_argument('profile', type=str, help=_('the name of the profile')) + self.parser.add_argument('jid', type=str, help=_('the jid of the profile')) + self.parser.add_argument('password', type=str, help=_('the password of the profile')) + + def _profile_created(self): + self.host.bridge.setParam("JabberID", self.args.jid, "Connection" ,profile_key=self.args.profile) + self.host.bridge.setParam("Server", JID(self.args.jid).domain, "Connection", profile_key=self.args.profile) + self.host.bridge.setParam("Password", self.args.password, "Connection", profile_key=self.args.profile) + self.host.quit() + + def run(self): + """Create a new profile""" + self.need_loop = True + if self.args.profile in self.host.bridge.getProfilesList(): + error("Profile %s already exists." % self.args.profile) + self.host.quit(1) + self.host.bridge.asyncCreateProfile(self.args.profile, self._profile_created, None) + + +class Profile(base.CommandBase): + subcommands = (ProfileDelete, ProfileInfo, ProfileList, ProfileCreate) + + def __init__(self, host): + super(Profile, self).__init__(host, 'profile', use_profile=False, help=_('Profile commands')) diff -r 4429bd7d5efb -r c39117d00f35 frontends/src/jp/common.py --- a/frontends/src/jp/common.py Wed Feb 05 14:52:40 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,28 +0,0 @@ -#! /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 . - - - -class MissingPlugin(Exception): - pass - -def require(plugin_name): - suported_plugins = ["profile"] - if plugin_name not in Supported_plugins: - raise MissingPlugin diff -r 4429bd7d5efb -r c39117d00f35 frontends/src/jp/file.py --- a/frontends/src/jp/file.py Wed Feb 05 14:52:40 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,138 +0,0 @@ -#! /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 . - -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() diff -r 4429bd7d5efb -r c39117d00f35 frontends/src/jp/jp --- a/frontends/src/jp/jp Wed Feb 05 14:52:40 2014 +0100 +++ b/frontends/src/jp/jp Mon Feb 10 13:44:09 2014 +0100 @@ -17,13 +17,9 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -import base -import message -import pipe -import profile -import file +from sat_frontends.jp import base if __name__ == "__main__": - args = base.parser.parse_args() - args.func(args) - + jp = base.Jp() + jp.import_commands() + jp.run() diff -r 4429bd7d5efb -r c39117d00f35 frontends/src/jp/message.py --- a/frontends/src/jp/message.py Wed Feb 05 14:52:40 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,68 +0,0 @@ -#! /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 . - -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) diff -r 4429bd7d5efb -r c39117d00f35 frontends/src/jp/pipe.py --- a/frontends/src/jp/pipe.py Wed Feb 05 14:52:40 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,90 +0,0 @@ -#! /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 . - -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() diff -r 4429bd7d5efb -r c39117d00f35 frontends/src/jp/profile.py --- a/frontends/src/jp/profile.py Wed Feb 05 14:52:40 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,130 +0,0 @@ -#! /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 . - -"""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) -