Mercurial > libervia-backend
comparison frontends/src/jp/jp @ 814:59c7bc51c323
jp: refactoring using ArgParse
author | Dal <kedals0@gmail.com> |
---|---|
date | Wed, 05 Feb 2014 14:35:26 +0100 |
parents | 1fe00f0c9a91 |
children | f8d534ed1d1e |
comparison
equal
deleted
inserted
replaced
813:1a1600491d9d | 814:59c7bc51c323 |
---|---|
1 #! /usr/bin/python | 1 #!/usr/bin/python |
2 # -*- coding: utf-8 -*- | |
3 | 2 |
4 # jp: a SAT command line tool | 3 import base |
5 # Copyright (C) 2009, 2010, 2011, 2012, 2013, 2014 Jérôme Poisson (goffi@goffi.org) | 4 import message |
6 | 5 import pipe |
7 # This program is free software: you can redistribute it and/or modify | 6 import profile |
8 # it under the terms of the GNU Affero General Public License as published by | 7 import file |
9 # the Free Software Foundation, either version 3 of the License, or | |
10 # (at your option) any later version. | |
11 | |
12 # This program is distributed in the hope that it will be useful, | |
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
15 # GNU Affero General Public License for more details. | |
16 | |
17 # You should have received a copy of the GNU Affero General Public License | |
18 # along with this program. If not, see <http://www.gnu.org/licenses/>. | |
19 | |
20 from __future__ import with_statement | |
21 from sat.core.i18n import _ | |
22 | |
23 #consts | |
24 name = u"jp" | |
25 about = name+u""" v%s (c) Jérôme Poisson (aka Goffi) 2009, 2010, 2011, 2012 | |
26 | |
27 --- | |
28 """+name+u""" Copyright (C) 2009, 2010, 2011, 2012, 2013, 2014 Jérôme Poisson (aka Goffi) | |
29 This program comes with ABSOLUTELY NO WARRANTY; | |
30 This is free software, and you are welcome to redistribute it | |
31 under certain conditions. | |
32 --- | |
33 | |
34 This software is a command line tool for jabber | |
35 Get the latest version at http://www.goffi.org | |
36 """ | |
37 | |
38 global pbar_available | |
39 pbar_available = True #checked before using ProgressBar | |
40 | |
41 ### logging ### | |
42 import logging | |
43 from logging import debug, info, error, warning | |
44 logging.basicConfig(level=logging.DEBUG, | |
45 format='%(message)s') | |
46 ### | |
47 | |
48 import sys | |
49 import os | |
50 from os.path import abspath, basename, dirname | |
51 from optparse import OptionParser | |
52 from sat.tools.jid import JID | |
53 import gobject | |
54 from sat_frontends.bridge.DBus import DBusBridgeFrontend | |
55 from sat.core.exceptions import BridgeExceptionNoService, BridgeInitError | |
56 from sat.tools.utils import clean_ustr | |
57 import tarfile | |
58 import tempfile | |
59 import shutil | |
60 try: | |
61 from progressbar import ProgressBar, Percentage, Bar, ETA, FileTransferSpeed | |
62 except ImportError, e: | |
63 info (_('ProgressBar not available, please download it at http://pypi.python.org/pypi/progressbar')) | |
64 info (_('Progress bar deactivated\n--\n')) | |
65 pbar_available=False | |
66 | |
67 | |
68 | |
69 | |
70 class JP(object): | |
71 def __init__(self): | |
72 try: | |
73 self.bridge=DBusBridgeFrontend() | |
74 except BridgeExceptionNoService: | |
75 print(_(u"Can't connect to SàT backend, are you sure it's launched ?")) | |
76 sys.exit(1) | |
77 except BridgeInitError: | |
78 print(_(u"Can't init bridge")) | |
79 sys.exit(1) | |
80 self.transfer_data = None | |
81 | |
82 def check_options(self): | |
83 """Check command line options""" | |
84 usage=_(""" | |
85 %prog [options] [FILE1 FILE2 ...] JID | |
86 %prog -w [options] [JID1 JID2 ...] | |
87 | |
88 %prog --help for options list | |
89 """) | |
90 version = unicode(self.bridge.getVersion()) | |
91 parser = OptionParser(usage=usage,version=about % version) | |
92 | |
93 parser.add_option("-p", "--profile", action="store", type="string", default='@DEFAULT@', | |
94 help=_("Use PROFILE profile key (default: %default)")) | |
95 parser.add_option("-b", "--bz2", action="store_true", default=False, | |
96 help=_("Make a bzip2 tarball")) | |
97 parser.add_option("-w", "--wait-file", action="store_true", default=False, | |
98 help=_("Wait for a file to be sent by a contact")) | |
99 parser.add_option("-m", "--multiple", action="store_true", default=False, | |
100 help=_("Accept multiple files (you'll have to stop manually)")) | |
101 parser.add_option("-f", "--force", action="store_true", default=False, | |
102 help=_("Force overwritting of existing files")) | |
103 parser.add_option("-g", "--progress", action="store_true", default=False, | |
104 help=_("Show progress bar")) | |
105 parser.add_option("-s", "--separate", action="store_true", default=False, | |
106 help=_("Separate xmpp messages: send one message per line instead of one message alone.")) | |
107 parser.add_option("-n", "--new-line", action="store_true", default=False, | |
108 help=_("Add a new line at the beginning of the input (usefull for ascii art ;))")) | |
109 parser.add_option("--list-profiles", action="store_true", default=False, | |
110 help=_("List available profiles")) | |
111 parser.add_option("-c", "--create-profile", action="store", type="string", nargs=3, | |
112 help=_("Create a profile (args: profile_name jid password)")) | |
113 parser.add_option("--get-profile", action="store", type="string", | |
114 help=_("Get profile informations (arg: profile_name)")) | |
115 parser.add_option("--rm-profile", action="store", type="string", | |
116 help=_("Remove profile")) | |
117 parser.add_option("--connect", action="store_true", default=False, | |
118 help=_("Connect the profile before doing anything else")) | |
119 parser.add_option("--pipe-in", action="store_true", default=False, | |
120 help=_("Wait for the reception of a pipe stream")) | |
121 parser.add_option("--pipe-out", action="store_true", default=False, | |
122 help=_("Pipe a stream out ")) | |
123 | |
124 (self.options, args) = parser.parse_args() | |
125 if self.options.list_profiles: | |
126 for p in self.bridge.getProfilesList(): | |
127 info(p) | |
128 exit(0) | |
129 if self.options.create_profile or self.options.get_profile: | |
130 self.start_loop = True | |
131 return args | |
132 if self.options.rm_profile: | |
133 self.start_loop = False | |
134 return args | |
135 | |
136 if len(args) < 1 and not self.options.wait_file: | |
137 parser.error(_("You must specify the destination JID (Jabber ID)").encode('utf-8')) | |
138 | |
139 if self.options.wait_file or self.options.pipe_in: | |
140 #several jid | |
141 self.dest_jids = [arg.decode('utf-8') for arg in args] | |
142 else: | |
143 #one dest_jid, other args are files | |
144 self.dest_jid = JID(args[-1].decode('utf-8')) | |
145 self.files = args[:-1] | |
146 | |
147 if not pbar_available and self.options.progress: | |
148 self.options.progress = False | |
149 error (_("Option progress is not available, deactivated.")) | |
150 | |
151 if self.options.progress or self.options.wait_file or self.options.connect or self.options.pipe_in: | |
152 self.start_loop = True #We have to use loop for these options | |
153 else: | |
154 self.start_loop = False | |
155 | |
156 | |
157 return args | |
158 | |
159 def create_profile(self): | |
160 """Create a new profile""" | |
161 profile, jid, password = self.options.create_profile | |
162 if profile in self.bridge.getProfilesList(): | |
163 error("Profile %s already exists."%profile) | |
164 exit(1) | |
165 self.bridge.asyncCreateProfile(profile, lambda : self._create_profile(profile, jid, password), None) | |
166 | |
167 def get_profile(self): | |
168 def setJID(jid): | |
169 info("jid: %s"%jid) | |
170 self.bridge.asyncGetParamA("Password", "Connection", profile_key=profile_name, callback=setPassword) | |
171 def setPassword(password): | |
172 info("pwd: %s"%password) | |
173 self.loop.quit() | |
174 profile_name = self.options.get_profile | |
175 if profile_name not in self.bridge.getProfilesList(): | |
176 error("Profile %s doesn't exist."%profile_name) | |
177 exit(1) | |
178 self.bridge.asyncGetParamA("JabberID", "Connection", profile_key=profile_name, callback=setJID) | |
179 | |
180 def rm_profile(self): | |
181 profile_name = self.options.rm_profile | |
182 if profile_name not in self.bridge.getProfilesList(): | |
183 error("Profile %s doesn't exist."%profile_name) | |
184 exit(1) | |
185 self.bridge.deleteProfile(profile_name) | |
186 | |
187 def _create_profile(self, profile_name, jid, password): | |
188 self.bridge.setParam("JabberID", jid, "Connection" ,profile_key=profile_name) | |
189 self.bridge.setParam("Server", JID(jid).domain, "Connection", profile_key=profile_name) | |
190 self.bridge.setParam("Password", password, "Connection", profile_key=profile_name) | |
191 self.loop.quit() | |
192 | |
193 def check_jabber_status(self): | |
194 """Check that jabber status is allright""" | |
195 def cantConnect(): | |
196 error(_(u"Can't connect profile")) | |
197 exit(1) | |
198 | |
199 | |
200 self.profile = self.bridge.getProfileName(self.options.profile) | |
201 if not self.profile: | |
202 error(_("The profile asked doesn't exist")) | |
203 exit(1) | |
204 | |
205 if self.options.connect: #if connection is asked, we connect the profile | |
206 self.bridge.asyncConnect(self.profile, self.connected, cantConnect) | |
207 return | |
208 elif not self.bridge.isConnected(self.profile): | |
209 error(_(u"Profile [%(profile)s] is not connected, please connect it before using jp, or use --connect option") % { "profile": self.profile }) | |
210 exit(1) | |
211 | |
212 self.connected() | |
213 | |
214 def check_jids(self): | |
215 """Check jids validity, transform roster name to corresponding jids""" | |
216 names2jid = {} | |
217 nodes2jid = {} | |
218 | |
219 for contact in self.bridge.getContacts(self.options.profile): | |
220 _jid, attr, groups = contact | |
221 if attr.has_key("name"): | |
222 names2jid[attr["name"].lower()] = _jid | |
223 nodes2jid[JID(_jid).node.lower()] = _jid | |
224 | |
225 def expandJid(jid): | |
226 _jid = jid.lower() | |
227 if _jid in names2jid: | |
228 expanded = names2jid[_jid] | |
229 elif _jid in nodes2jid: | |
230 expanded = nodes2jid[_jid] | |
231 else: | |
232 expanded = jid | |
233 return unicode(expanded) | |
234 | |
235 def check(jid): | |
236 if not jid.is_valid: | |
237 error (_("%s is not a valid JID !"), self.dest_jid) | |
238 exit(1) | |
239 | |
240 try: | |
241 self.dest_jid = expandJid(self.dest_jid) | |
242 check(self.dest_jid) | |
243 except AttributeError: | |
244 pass | |
245 try: | |
246 for i in range(len(self.dest_jids)): | |
247 self.dest_jids[i] = expandJid(self.dest_jids[i]) | |
248 check(self.dest_jids[i]) | |
249 except AttributeError: | |
250 pass | |
251 | |
252 | |
253 def send_stdin(self): | |
254 """Send incomming data on stdin to jabber contact""" | |
255 header = "\n" if self.options.new_line else "" | |
256 | |
257 if self.options.separate: #we send stdin in several messages | |
258 if header: | |
259 self.bridge.sendMessage(self.dest_jid, header, profile_key=self.profile, callback=lambda: None, errback=lambda ignore: ignore) | |
260 while (True): | |
261 line = clean_ustr(sys.stdin.readline().decode('utf-8','ignore')) | |
262 if not line: | |
263 break | |
264 self.bridge.sendMessage(self.dest_jid, line.replace("\n",""), profile_key=self.profile, callback=lambda: None, errback=lambda ignore: ignore) | |
265 else: | |
266 self.bridge.sendMessage(self.dest_jid, header + clean_ustr(u"".join([stream.decode('utf-8','ignore') for stream in sys.stdin.readlines()])), | |
267 profile_key=self.profile, callback=lambda: None, errback=lambda ignore: ignore) | |
268 | |
269 | |
270 def pipe_out(self): | |
271 """Create named pipe, and send stdin to it""" | |
272 tmp_dir = tempfile.mkdtemp() | |
273 fifopath = os.path.join(tmp_dir,"pipe_out") | |
274 os.mkfifo(fifopath) | |
275 self.bridge.pipeOut(self._getFullJid(self.dest_jid), fifopath, {}, self.profile) | |
276 with open(fifopath, 'w') as f: | |
277 shutil.copyfileobj(sys.stdin, f) | |
278 shutil.rmtree(tmp_dir) | |
279 | |
280 | |
281 def send_files(self): | |
282 """Send files to jabber contact""" | |
283 | |
284 for file in self.files: | |
285 if not os.path.exists(file): | |
286 error (_(u"File [%s] doesn't exist !") % file) | |
287 exit(1) | |
288 if not self.options.bz2 and os.path.isdir(file): | |
289 error (_("[%s] is a dir ! Please send files inside or use compression") % file) | |
290 exit(1) | |
291 | |
292 full_dest_jid = self._getFullJid(self.dest_jid) | |
293 if self.options.bz2: | |
294 tmpfile = (basename(self.files[0]) or basename(dirname(self.files[0])) ) + '.tar.bz2' #FIXME: tmp, need an algorithm to find a good name/path | |
295 if os.path.exists(tmpfile): | |
296 error (_("tmp file (%s) already exists ! Please remove it"), tmpfile) | |
297 exit(1) | |
298 warning(_("bz2 is an experimental option at an early dev stage, use with caution")) | |
299 #FIXME: check free space, writting perm, tmp dir, filename (watch for OS used) | |
300 info(_("Starting compression, please wait...")) | |
301 sys.stdout.flush() | |
302 bz2=tarfile.open(tmpfile, "w:bz2") | |
303 for file in self.files: | |
304 info(_("Adding %s"), file) | |
305 bz2.add(file) | |
306 bz2.close() | |
307 info(_("OK !")) | |
308 path = abspath(tmpfile) | |
309 self.transfer_data = self.bridge.sendFile(full_dest_jid, path, {}, self.profile) | |
310 else: | |
311 for file in self.files: | |
312 path = abspath(file) | |
313 self.transfer_data = self.bridge.sendFile(full_dest_jid, path, {}, self.profile) #FIXME: show progress only for last transfer_id | |
314 | |
315 | |
316 def _getFullJid(self, param_jid): | |
317 """Return the full jid if possible (add last resource when find a bare jid""" | |
318 _jid = JID(param_jid) | |
319 if not _jid.resource: | |
320 #if the resource is not given, we try to add the last known resource | |
321 last_resource = self.bridge.getLastResource(param_jid, self.options.profile) | |
322 if last_resource: | |
323 return "%s/%s" % (_jid.bare, last_resource) | |
324 return param_jid | |
325 | |
326 | |
327 def askConfirmation(self, confirm_id, confirm_type, data, profile): | |
328 """CB used for file transfer, accept files depending on parameters""" | |
329 if profile != self.profile: | |
330 debug("Ask confirmation ignored: not our profile") | |
331 return | |
332 answer_data={} | |
333 if confirm_type == "FILE_TRANSFER": | |
334 if not self.options.wait_file: | |
335 return | |
336 if self.dest_jids and not JID(data['from']).bare in [JID(_jid).bare for _jid in self.dest_jids]: | |
337 return #file is not sent by a filtered jid | |
338 | |
339 answer_data["dest_path"] = os.getcwd()+'/'+data['filename'] | |
340 | |
341 if self.options.force or not os.path.exists(answer_data["dest_path"]): | |
342 self.bridge.confirmationAnswer(confirm_id, True, answer_data, profile) | |
343 info(_("Accepted file [%(filename)s] from %(sender)s") % {'filename':data['filename'], 'sender':data['from']}) | |
344 self.transfer_data = confirm_id | |
345 else: | |
346 self.bridge.confirmationAnswer(confirm_id, False, answer_data, profile) | |
347 warning(_("Refused file [%(filename)s] from %(sender)s: a file with the same name already exist") % {'filename':data['filename'], 'sender':data['from']}) | |
348 | |
349 | |
350 if not self.options.multiple and not self.options.progress: | |
351 #we just accept one file | |
352 self.loop.quit() | |
353 elif confirm_type == "PIPE_TRANSFER": | |
354 if not self.options.pipe_in: | |
355 return | |
356 if self.dest_jids and not JID(data['from']).bare in [JID(_jid).bare for _jid in self.dest_jids]: | |
357 return #pipe stream is not sent by a filtered jid | |
358 | |
359 tmp_dir = tempfile.mkdtemp() | |
360 fifopath = os.path.join(tmp_dir,"pipe_in") | |
361 answer_data["dest_path"] = fifopath | |
362 os.mkfifo(fifopath) | |
363 self.bridge.confirmationAnswer(confirm_id, True, answer_data, profile) | |
364 with open(fifopath, 'r') as f: | |
365 shutil.copyfileobj(f, sys.stdout) | |
366 shutil.rmtree(tmp_dir) | |
367 self.loop.quit() | |
368 | |
369 | |
370 def actionResult(self, action_type, action_id, data, profile): | |
371 #FIXME | |
372 info (_("FIXME: actionResult not implemented")) | |
373 | |
374 def confirmation_reply(self): | |
375 """Auto reply to confirmations requests""" | |
376 #we register incoming confirmation | |
377 self.bridge.register("askConfirmation", self.askConfirmation) | |
378 | |
379 #and we ask those we have missed | |
380 for confirm_id, confirm_type, data in self.bridge.getWaitingConf(self.profile): | |
381 self.askConfirmation(confirm_id, confirm_type, data, self.profile) | |
382 | |
383 def progressCB(self): | |
384 if self.transfer_data: | |
385 transfer_id = self.transfer_data | |
386 data = self.bridge.getProgress(transfer_id, self.profile) | |
387 if data: | |
388 if not data['position']: | |
389 data['position'] = '0' | |
390 if not self.pbar: | |
391 #first answer, we must construct the bar | |
392 self.pbar = ProgressBar(int(data['size']),[_("Progress: "),Percentage()," ",Bar()," ",FileTransferSpeed()," ",ETA()]) | |
393 self.pbar.start() | |
394 | |
395 self.pbar.update(int(data['position'])) | |
396 elif self.pbar: | |
397 self.pbar.finish() | |
398 if not self.options.multiple: | |
399 self.loop.quit() | |
400 return False | |
401 | |
402 return True | |
403 | |
404 def go(self): | |
405 self.check_options() | |
406 if self.options.create_profile: | |
407 self.create_profile() | |
408 elif self.options.get_profile: | |
409 self.get_profile() | |
410 elif self.options.rm_profile: | |
411 self.rm_profile() | |
412 else: | |
413 self.check_jabber_status() | |
414 if self.start_loop: | |
415 self.loop = gobject.MainLoop() | |
416 try: | |
417 self.loop.run() | |
418 except KeyboardInterrupt: | |
419 info(_("User interruption: good bye")) | |
420 | |
421 def connected(self): | |
422 """This is called when the profile is connected""" | |
423 self.check_jids() | |
424 if self.options.wait_file or self.options.pipe_in: | |
425 self.confirmation_reply() | |
426 else: | |
427 if self.files: | |
428 self.send_files() | |
429 elif self.options.pipe_out: | |
430 self.pipe_out() | |
431 else: | |
432 self.send_stdin() | |
433 | |
434 if self.options.progress: | |
435 self.pbar = None | |
436 gobject.timeout_add(10, self.progressCB) | |
437 | |
438 if self.start_loop and not self.options.progress and not self.options.wait_file and not self.options.pipe_in: | |
439 self.loop.quit() | |
440 | |
441 | 8 |
442 if __name__ == "__main__": | 9 if __name__ == "__main__": |
443 jp = JP() | 10 args = base.parser.parse_args() |
444 jp.go() | 11 args.func(args) |
12 |