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
This commit is contained in:
Goffi 2014-02-10 13:44:09 +01:00
commit 7ca3c657f4
12 changed files with 662 additions and 590 deletions

View file

View file

@ -17,24 +17,8 @@
# 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
@ -46,35 +30,40 @@ logging.basicConfig(level=logging.DEBUG,
###
import sys
import os
from os.path import abspath, basename, dirname
from argparse import ArgumentParser
from sat.tools.jid import JID
import locale
import os.path
import argparse
import gobject
from glob import iglob
from importlib import import_module
from sat.tools.jid import JID
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 @@ class JP(object):
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)
def run(self):
raise NotImplementedError
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
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()
@property
def version(self):
return self.bridge.getVersion()
def _loop_start(self):
@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 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 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 stop_loop(self):
try:
self.loop.quit()
except AttributeError:
pass
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 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 @@ class JPWithProfile(JP):
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 @@ class JPWithProfile(JP):
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.args.profile)
self.profile = self.bridge.getProfileName(self.profile_name)
if not self.profile:
error(_("The profile asked doesn't exist"))
exit(1)
error(_("The profile [%s] doesn't exist") % self.args.profile)
self.quit(1)
if self.bridge.isConnected(self.profile):
print "Already connected"
else:
self._start_loop = True
self.bridge.asyncConnect(self.profile, self._run, cantConnect)
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 CommandBase(object):
class JPAsk(JPWithProfile):
def confirm_type(self):
"""Must return a string containing the confirm type. For instance,
FILE_TRANSFER or PIPE_TRANSFER, etc.
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
:rtype: str
"""
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
def dest_jids(self):
return None
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'])
def _askConfirmation(self, confirm_id, confirm_type, data, profile):
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
@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 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)

View file

@ -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 <http://www.gnu.org/licenses/>.
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'))

View file

@ -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 <http://www.gnu.org/licenses/>.
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)

View file

@ -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 <http://www.gnu.org/licenses/>.
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'))

View file

@ -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 <http://www.gnu.org/licenses/>.
"""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'))

View file

@ -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 <http://www.gnu.org/licenses/>.
class MissingPlugin(Exception):
pass
def require(plugin_name):
suported_plugins = ["profile"]
if plugin_name not in Supported_plugins:
raise MissingPlugin

View file

@ -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 <http://www.gnu.org/licenses/>.
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()

View file

@ -17,13 +17,9 @@
# 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/>.
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()

View file

@ -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 <http://www.gnu.org/licenses/>.
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)

View file

@ -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 <http://www.gnu.org/licenses/>.
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()

View file

@ -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 <http://www.gnu.org/licenses/>.
"""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)