comparison frontends/src/jp/cmd_file.py @ 1606:de785fcf9a7b

jp (base, file): file command and progress fixes and adaptation to new API: - progress use new API, and signals to monitor progression and avoid use of progressGet before progress is actually started - progress behaviour on event is managed by callbacks which are Jp attributes - progress with unknown or 0 size don't show the progress bar - better handling of errors - CommandAnswering is update to manage actions instead of the deprecated askConfirmation - managed actions use callback easy to associate with an action type. - file command is updated to manage these changes and the recent changes in backend behaviour - verbosity is used to display more or less message when sending/receiving a file - destination path can be specified in file receive - file receive doesn't stop yet, still need some work in the backend
author Goffi <goffi@goffi.org>
date Sun, 15 Nov 2015 23:42:21 +0100
parents 069ad98b360d
children a17a91531fbe
comparison
equal deleted inserted replaced
1605:0aded9648c5c 1606:de785fcf9a7b
15 # GNU Affero General Public License for more details. 15 # GNU Affero General Public License for more details.
16 16
17 # You should have received a copy of the GNU Affero General Public License 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/>. 18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
19 19
20 from logging import debug, info, error, warning
21 20
22 import base 21 import base
23 import sys 22 import sys
24 import os 23 import os
25 import os.path 24 import os.path
26 import tarfile 25 import tarfile
27 from sat.core.i18n import _ 26 from sat.core.i18n import _
27 from sat_frontends.jp.constants import Const as C
28 from sat_frontends.tools import jid
29 import tempfile
30 import xml.etree.ElementTree as ET # FIXME: used temporarily to manage XMLUI
31
28 32
29 __commands__ = ["File"] 33 __commands__ = ["File"]
30 34
35
31 class Send(base.CommandBase): 36 class Send(base.CommandBase):
32 def __init__(self, host): 37 def __init__(self, host):
33 super(Send, self).__init__(host, 'send', use_progress=True, help=_('Send a file to a contact')) 38 super(Send, self).__init__(host, 'send', use_progress=True, use_verbose=True, help=_('Send a file to a contact'))
39 self.host.progress_started = lambda dummy: self.disp(_(u'File copy started'),2)
40 self.host.progress_success = lambda dummy: self.disp(_(u'File copied successfully'),2)
41 self.host.progress_failure = lambda dummy: self.disp(_(u'Error while transfering file'),error=True)
34 42
35 def add_parser_options(self): 43 def add_parser_options(self):
36 self.parser.add_argument("files", type=str, nargs = '+', help=_("A list of file")) 44 self.parser.add_argument("files", type=str, nargs = '+', help=_("A list of file"))
37 self.parser.add_argument("jid", type=base.unicode_decoder, help=_("The destination jid")) 45 self.parser.add_argument("jid", type=base.unicode_decoder, help=_("The destination jid"))
38 self.parser.add_argument("-b", "--bz2", action="store_true", help=_("Make a bzip2 tarball")) 46 self.parser.add_argument("-b", "--bz2", action="store_true", help=_("Make a bzip2 tarball"))
41 """Send files to jabber contact""" 49 """Send files to jabber contact"""
42 self.need_loop=True 50 self.need_loop=True
43 super(Send, self).connected() 51 super(Send, self).connected()
44 self.send_files() 52 self.send_files()
45 53
54 def gotId(self, data, file_):
55 """Called when a progress id has been received
56
57 @param pid(unicode): progress id
58 @param file_(str): file path
59 """
60 #FIXME: this show progress only for last progress_id
61 self.disp(_(u"File request sent to {jid}".format(jid=self.full_dest_jid)), 1)
62 self.progress_id = data['progress']
63
64 def error(self, failure):
65 self.disp(_("Error while trying to send a file: {reason}").format(reason=failure), error=True)
66 self.host.quit(1)
67
46 def send_files(self): 68 def send_files(self):
47 69
48 for file_ in self.args.files: 70 for file_ in self.args.files:
49 if not os.path.exists(file_): 71 if not os.path.exists(file_):
50 error (_(u"file [%s] doesn't exist !") % file_) 72 self.disp(_(u"file [{}] doesn't exist !").format(file_), error=True)
51 self.host.quit(1) 73 self.host.quit(1)
52 if not self.args.bz2 and os.path.isdir(file_): 74 if not self.args.bz2 and os.path.isdir(file_):
53 error (_("[%s] is a dir ! Please send files inside or use compression") % file_) 75 self.disp(_(u"[{}] is a dir ! Please send files inside or use compression").format(file_))
54 self.host.quit(1) 76 self.host.quit(1)
55 77
56 full_dest_jid = self.host.get_full_jid(self.args.jid) 78 self.full_dest_jid = self.host.get_full_jid(self.args.jid)
57 79
58 if self.args.bz2: 80 if self.args.bz2:
59 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 81 with tempfile.NamedTemporaryFile('wb', delete=False) as buf:
60 if os.path.exists(tmpfile): 82 self.host.addOnQuitCallback(os.unlink, buf.name)
61 error (_("tmp file_ (%s) already exists ! Please remove it"), tmpfile) 83 self.disp(_(u"bz2 is an experimental option, use with caution"))
62 exit(1) 84 #FIXME: check free space
63 warning(_("bz2 is an experimental option at an early dev stage, use with caution")) 85 self.disp(_(u"Starting compression, please wait..."))
64 #FIXME: check free space, writting perm, tmp dir, filename (watch for OS used) 86 sys.stdout.flush()
65 print _(u"Starting compression, please wait...") 87 bz2 = tarfile.open(mode="w:bz2", fileobj=buf)
66 sys.stdout.flush() 88 archive_name = u'{}.tar.bz2'.format(os.path.basename(self.args.files[0]) or u'compressed_files')
67 bz2 = tarfile.open(tmpfile, "w:bz2") 89 for file_ in self.args.files:
68 for file_ in self.args.files: 90 self.disp(_(u"Adding {}").format(file_), 1)
69 print _(u"Adding %s") % file_ 91 bz2.add(file_)
70 bz2.add(file_) 92 bz2.close()
71 bz2.close() 93 self.disp(_(u"Done !"), 1)
72 print _(u"Done !") 94
73 path = os.path.abspath(tmpfile) 95 self.host.bridge.fileSend(self.full_dest_jid, buf.name, archive_name, '', self.profile, callback=lambda pid, file_=buf.name: self.gotId(pid, file_), errback=self.error)
74 self.progress_id = self.host.bridge.sendFile(full_dest_jid, path, {}, self.profile)
75 else: 96 else:
76 for file_ in self.args.files: 97 for file_ in self.args.files:
77 path = os.path.abspath(file_) 98 path = os.path.abspath(file_)
78 self.progress_id = self.host.bridge.sendFile(full_dest_jid, path, {}, self.profile) #FIXME: show progress only for last progress_id 99 self.host.bridge.fileSend(self.full_dest_jid, path, '', '', self.profile, callback=lambda pid, file_=file_: self.gotId(pid, file_), errback=self.error)
79 100
80 101
81 class Receive(base.CommandAnswering): 102 class Receive(base.CommandAnswering):
82 confirm_type = "FILE_TRANSFER"
83 103
84 def __init__(self, host): 104 def __init__(self, host):
85 super(Receive, self).__init__(host, 'recv', use_progress=True, help=_('Wait for a file to be sent by a contact')) 105 super(Receive, self).__init__(host, 'receive', use_progress=True, use_verbose=True, help=_('Wait for a file to be sent by a contact'))
106 self._overwrite_refused = False # True when one overwrite as already been refused
107 self.action_callbacks = {C.META_TYPE_FILE: self.onFileAction,
108 C.META_TYPE_OVERWRITE: self.onOverwriteAction}
86 109
87 @property 110 def getXmluiId(self, action_data):
88 def dest_jids(self): 111 # FIXME: we temporarily use ElementTree, but a real XMLUI managing module
89 return self.args.jids 112 # should be available in the futur
113 # TODO: XMLUI module
114 try:
115 xml_ui = action_data['xmlui']
116 except KeyError:
117 self.disp(_(u"Action has no XMLUI"), 1)
118 else:
119 ui = ET.fromstring(xml_ui.encode('utf-8'))
120 xmlui_id = ui.get('submit')
121 if not xmlui_id:
122 self.disp(_(u"Invalid XMLUI received"), error=True)
123 return xmlui_id
124
125 def onFileAction(self, action_data, action_id, security_limit, profile):
126 xmlui_id = self.getXmluiId(action_data)
127 if xmlui_id is None:
128 return self.host.quitFromSignal(1)
129 try:
130 from_jid = jid.JID(action_data['meta_from_jid'])
131 except KeyError:
132 self.disp(_(u"Ignoring action without from_jid data"), 1)
133 return
134 try:
135 progress_id = action_data['meta_progress_id']
136 except KeyError:
137 self.disp(_(u"ignoring action without progress id"), 1)
138 return
139
140 if not self.bare_jids or from_jid.bare in self.bare_jids:
141 if self._overwrite_refused:
142 self.disp(_(u"File refused because overwrite is needed"), error=True)
143 self.host.bridge.launchAction(xmlui_id, {'cancelled': C.BOOL_TRUE}, profile_key=profile)
144 return self.host.quitFromSignal(2)
145 self.progress_id = progress_id
146 xmlui_data = {'path': self.path}
147 self.host.bridge.launchAction(xmlui_id, xmlui_data, profile_key=profile)
148
149 def onOverwriteAction(self, action_data, action_id, security_limit, profile):
150 xmlui_id = self.getXmluiId(action_data)
151 if xmlui_id is None:
152 return self.host.quitFromSignal(1)
153 try:
154 progress_id = action_data['meta_progress_id']
155 except KeyError:
156 self.disp(_(u"ignoring action without progress id"), 1)
157 return
158 self.disp(_(u"Overwriting needed"), 1)
159
160 if progress_id == self.progress_id:
161 if self.args.force:
162 self.disp(_(u"Overwrite accepted"), 2)
163 else:
164 self.disp(_(u"Refused to overwrite"), 2)
165 self._overwrite_refused = True
166
167 xmlui_data = {'answer': C.boolConst(self.args.force)}
168 self.host.bridge.launchAction(xmlui_id, xmlui_data, profile_key=profile)
90 169
91 def add_parser_options(self): 170 def add_parser_options(self):
92 self.parser.add_argument("jids", type=base.unicode_decoder, nargs="*", help=_('Jids accepted (none means "accept everything")')) 171 self.parser.add_argument("jids", type=base.unicode_decoder, nargs="*", help=_(u'JIDs accepted (accept everything if none is specified)'))
93 self.parser.add_argument("-m", "--multiple", action="store_true", help=_("Accept multiple files (you'll have to stop manually)")) 172 self.parser.add_argument("-m", "--multiple", action="store_true", help=_(u"accept multiple files (you'll have to stop manually)"))
94 self.parser.add_argument("-f", "--force", action="store_true", help=_("Force overwritting of existing files")) 173 self.parser.add_argument("-f", "--force", action="store_true", help=_(u"force overwritting of existing files (/!\\ name is choosed by sended)"))
95 174 self.parser.add_argument("--path", default='.', metavar='DIR', help=_(u"destination path (default: working directory)"))
96
97 def ask(self, data, confirm_id):
98 answer_data = {}
99 answer_data["dest_path"] = os.path.join(os.getcwd(), data['filename'])
100
101 if self.args.force or not os.path.exists(answer_data["dest_path"]):
102 self.host.bridge.confirmationAnswer(confirm_id, True, answer_data, self.profile)
103 info(_("Accepted file [%(filename)s] from %(sender)s") % {'filename':data['filename'], 'sender':data['from']})
104 self.progress_id = confirm_id
105 else:
106 self.host.bridge.confirmationAnswer(confirm_id, False, answer_data, self.profile)
107 warning(_("Refused file [%(filename)s] from %(sender)s: a file with the same name already exist") % {'filename':data['filename'], 'sender':data['from']})
108 if not self.args.multiple:
109 self.host.quit()
110
111 if not self.args.multiple and not self.args.progress:
112 #we just accept one file
113 self.host.quit()
114 175
115 def run(self): 176 def run(self):
116 super(Receive, self).run() 177 super(Receive, self).run()
178 self.bare_jids = [jid.JID(jid_).bare for jid_ in self.args.jids]
179 self.path = os.path.abspath(self.args.path)
180 if not os.path.isdir(self.path):
181 self.disp(_(u"Given path is not a directory !", error=True))
182 self.quit(2)
117 if self.args.multiple: 183 if self.args.multiple:
118 self.host.quit_on_progress_end = False 184 self.host.quit_on_progress_end = False
185 self.disp(_(u"waiting for incoming file request"),2)
119 186
120 187
121 class File(base.CommandBase): 188 class File(base.CommandBase):
122 subcommands = (Send, Receive) 189 subcommands = (Send, Receive)
123 190