1
2 """
3
4 @author: Fabio Erculiani <lxnay@sabayonlinux.org>
5 @contact: lxnay@sabayonlinux.org
6 @copyright: Fabio Erculiani
7 @license: GPL-2
8
9 B{EntropyTransceiver FTP URI Handler module}.
10
11 """
12 import os
13 import time
14
15 from entropy.tools import print_traceback, get_file_size, \
16 convert_seconds_to_fancy_output, bytes_into_human, spliturl
17 from entropy.output import blue, brown, darkgreen, red
18 from entropy.i18n import _
19 from entropy.exceptions import ConnectionError, TransceiverError
20 from entropy.transceivers.uri_handlers.skel import EntropyUriHandler
23
24 PLUGIN_API_VERSION = 0
25
26 """
27 EntropyUriHandler based FTP transceiver plugin.
28 """
29
30 _DEFAULT_TIMEOUT = 60
31
32 @staticmethod
34 if uri.startswith("ftp://"):
35 return True
36 return False
37
38 @staticmethod
40 myuri = spliturl(uri)[1]
41
42 myuri = myuri.split("@")[-1]
43 return myuri
44
45 @staticmethod
47 ftppassword = uri.split("@")[:-1]
48 if not ftppassword:
49 return uri
50 ftppassword = '@'.join(ftppassword)
51 ftppassword = ftppassword.split(":")[-1]
52 if not ftppassword:
53 return uri
54 newuri = uri.replace(ftppassword, "xxxxxxxx")
55 return newuri
56
58 EntropyUriHandler.__init__(self, uri)
59
60 import socket, ftplib
61 self.socket, self.ftplib = socket, ftplib
62 self.__connected = False
63 self.__ftpconn = None
64 self.__currentdir = '.'
65 self.__ftphost = EntropyFtpUriHandler.get_uri_name(self._uri)
66 self.__ftpuser, self.__ftppassword, self.__ftpport, self.__ftpdir = \
67 self.__extract_ftp_data(self._uri)
68
69 self._init_vars()
70
71
72
73 self._connect()
74 self._disconnect()
75
78
79 - def __exit__(self, exc_type, exc_value, traceback):
81
83 ftpuser = ftpuri.split("ftp://")[-1].split(":")[0]
84 if (not ftpuser):
85 ftpuser = "anonymous@"
86 ftppassword = "anonymous"
87 else:
88 ftppassword = ftpuri.split("@")[:-1]
89 if len(ftppassword) > 1:
90 ftppassword = '@'.join(ftppassword)
91 ftppassword = ftppassword.split(":")[-1]
92 if (not ftppassword):
93 ftppassword = "anonymous"
94 else:
95 ftppassword = ftppassword[0]
96 ftppassword = ftppassword.split(":")[-1]
97 if not ftppassword:
98 ftppassword = "anonymous"
99
100 ftpport = ftpuri.split(":")[-1]
101 try:
102 ftpport = int(ftpport)
103 except ValueError:
104 ftpport = 21
105
106 ftpdir = '/'
107 if ftpuri.count("/") > 2:
108 ftpdir = ftpuri[6:]
109 ftpdir = "/" + ftpdir.split("/", 1)[-1]
110 ftpdir = ftpdir.split(":")[0]
111 if not ftpdir:
112 ftpdir = '/'
113 elif ftpdir.endswith("/") and (ftpdir != "/"):
114 ftpdir = ftpdir[:-1]
115
116 return ftpuser, ftppassword, ftpport, ftpdir
117
119 """
120 Handy internal method.
121 """
122 if not self.__connected:
123 self._connect()
124 try:
125 self.keep_alive()
126 except ConnectionError:
127 self._connect()
128
130 """
131 Connect to FTP host.
132 """
133 timeout = self._timeout
134 if timeout is None:
135
136 timeout = EntropyFtpUriHandler._DEFAULT_TIMEOUT
137
138 count = 10
139 while True:
140 count -= 1
141 try:
142 self.__ftpconn = self.ftplib.FTP()
143 self.__ftpconn.connect(self.__ftphost, self.__ftpport, timeout)
144 break
145 except (self.socket.gaierror,) as e:
146 raise ConnectionError('ConnectionError: %s' % (e,))
147 except (self.socket.error,) as e:
148 if not count:
149 raise ConnectionError('ConnectionError: %s' % (e,))
150 except:
151 if not count:
152 raise
153
154 if self._verbose:
155 mytxt = _("connecting with user")
156 self.updateProgress(
157 "[ftp:%s] %s: %s" % (
158 darkgreen(self.__ftphost), mytxt, blue(self.__ftpuser),
159 ),
160 importance = 1,
161 type = "info",
162 header = darkgreen(" * ")
163 )
164 try:
165 self.__ftpconn.login(self.__ftpuser, self.__ftppassword)
166 except self.ftplib.error_perm as e:
167 raise ConnectionError('ConnectionError: %s' % (e,))
168
169 if self._verbose:
170 mytxt = _("switching to")
171 self.updateProgress(
172 "[ftp:%s] %s: %s" % (
173 darkgreen(self.__ftphost), mytxt, blue(self.__ftpdir),
174 ),
175 importance = 1,
176 type = "info",
177 header = darkgreen(" * ")
178 )
179
180 self._set_cwd(self.__ftpdir, dodir = True)
181 self.__connected = True
182
184 if self.__ftpconn is not None:
185
186 try:
187 self.__ftpconn.quit()
188 except (EOFError, self.socket, self.socket.timeout,
189 self.ftplib.error_reply,):
190
191
192
193 pass
194 self.__ftpconn = None
195 self.__connected = False
196
198 self._disconnect()
199 self._connect()
200
202 self.__oldprogress_t = time.time()
203 self.__datatransfer = 0
204 self.__filesize = 0
205 self.__filekbcount = 0
206 self.__transfersize = 0
207 self.__startingposition = 0
208 self.__elapsed = 0.0
209 self.__time_remaining_secs = 0
210 self.__time_remaining = "(%s)" % (_("infinite"),)
211 self.__starttime = time.time()
212
214 return self.__ftpconn.pwd()
215
216 - def _set_cwd(self, mydir, dodir = False):
217 try:
218 return self.__set_cwd(mydir, dodir)
219 except self.ftplib.error_perm as e:
220 raise TransceiverError('TransceiverError: %s' % (e,))
221
223 if self._verbose:
224 mytxt = _("switching to")
225 self.updateProgress(
226 "[ftp:%s] %s: %s" % (darkgreen(self.__ftphost),
227 mytxt, blue(mydir),),
228 importance = 1,
229 type = "info",
230 header = darkgreen(" * ")
231 )
232 try:
233 self.__ftpconn.cwd(mydir)
234 except self.ftplib.error_perm as e:
235 if e[0][:3] == '550' and dodir:
236 self.makedirs(mydir)
237 self.__ftpconn.cwd(mydir)
238 else:
239 raise
240 self.__currentdir = self._get_cwd()
241
243 self.__ftpconn.set_pasv(pasv)
244
246 return self.__ftpconn.voidcmd("SITE CHMOD " + str(chmodvalue) + " " + \
247 str(filename))
248
250 rc = self.__ftpconn.sendcmd("mdtm " + path)
251 return rc.split()[-1]
252
254 return self.__ftpconn.sendcmd(cmd)
255
257 current_time = time.time()
258 self.__elapsed = current_time - self.__starttime
259
260 pos_diff = self.__transfersize - self.__startingposition
261 self.__datatransfer = pos_diff / self.__elapsed
262 if self.__datatransfer < 0:
263 self.__datatransfer = 0
264 try:
265 round_fsize = int(round(self.__filesize*1024, 0))
266 round_rsize = int(round(self.__transfersize, 0))
267 self.__time_remaining_secs = int(round((round_fsize - \
268 round_rsize)/self.__datatransfer, 0))
269 self.__time_remaining = \
270 convert_seconds_to_fancy_output(self.__time_remaining_secs)
271 except (ValueError, TypeError,):
272 self.__time_remaining = "(%s)" % (_("infinite"),)
273
275 if self._speed_limit:
276 while self.__datatransfer > self._speed_limit * 1024:
277 time.sleep(0.1)
278 self._update_speed()
279 self._update_progress()
280
282
283 self.__filekbcount += float(buf_len)/1024
284 self.__transfersize += buf_len
285
287
288 upload_percent = 100.0
289 upload_size = round(self.__filekbcount, 1)
290
291 if self.__filesize >= 1:
292 kbcount_round = round(self.__filekbcount, 1)
293 upload_percent = round((kbcount_round / self.__filesize) * 100, 1)
294
295 delta_secs = 0.5
296 cur_t = time.time()
297 if (cur_t > (self.__oldprogress_t + delta_secs)) or force:
298
299 upload_percent = str(upload_percent)+"%"
300
301 mytxt = _("Transfer status")
302 current_txt = brown(" <-> %s: " % (mytxt,)) + \
303 darkgreen(str(upload_size)) + "/" + \
304 red(str(self.__filesize)) + " kB " + \
305 brown("[") + str(upload_percent) + brown("]") + \
306 " " + self.__time_remaining + " " + \
307 bytes_into_human(self.__datatransfer) + \
308 "/" + _("sec")
309
310 self.updateProgress(current_txt, back = True)
311 self.__oldprogress_t = cur_t
312
314 return self.__ftpconn.size(filename)
315
317 try:
318 sc = self.__ftpconn.sendcmd
319 data = [x.split() for x in sc("stat %s" % (filename,)).split("\n")]
320 except self.ftplib.error_temp:
321 return ""
322 for item in data:
323 if item[-1] == filename:
324 return item[4]
325 return ""
326
328 return self.__ftpconn.mkd(directory)
329
330 - def download(self, remote_path, save_path):
331
332 self.__connect_if_not()
333 path = os.path.join(self.__ftpdir, remote_path)
334 tmp_save_path = save_path + ".dtmp"
335
336 def writer(buf):
337
338 f.write(buf)
339 self._commit_buffer_update(len(buf))
340 self._update_speed()
341 self._update_progress()
342 self._speed_limit_loop()
343
344 tries = 10
345 while tries:
346 tries -= 1
347
348 self._init_vars()
349 try:
350
351 self.__filekbcount = 0
352
353 self.__filesize = self._get_file_size_compat(path)
354 if (self.__filesize):
355 self.__filesize = round(float(int(self.__filesize))/1024, 1)
356 if (self.__filesize == 0):
357 self.__filesize = 1
358 elif not self.is_path_available(path):
359 return False
360 else:
361 self.__filesize = 0
362
363 with open(tmp_save_path, "wb") as f:
364 rc = self.__ftpconn.retrbinary('RETR ' + path, writer, 8192)
365 f.flush()
366 self._update_progress(force = True)
367
368 done = rc.find("226") != -1
369 if done:
370
371 os.rename(tmp_save_path, save_path)
372
373 return done
374
375 except Exception as e:
376
377 print_traceback()
378 mytxt = red("%s: %s, %s... #%s") % (
379 _("Download issue"),
380 e,
381 _("retrying"),
382 tries+1,
383 )
384 self.updateProgress(
385 mytxt,
386 importance = 1,
387 type = "warning",
388 header = " "
389 )
390 self._reconnect()
391
392 - def upload(self, load_path, remote_path):
393
394 self.__connect_if_not()
395 path = os.path.join(self.__ftpdir, remote_path)
396
397 tmp_path = path + ".dtmp"
398 tries = 0
399
400 def updater(buf):
401 self._commit_buffer_update(len(buf))
402 self._update_speed()
403 self._update_progress()
404 self._speed_limit_loop()
405
406 while tries < 10:
407
408 tries += 1
409 self._init_vars()
410
411 try:
412
413 file_size = get_file_size(load_path)
414 self.__filesize = round(float(file_size)/ 1024, 1)
415 self.__filekbcount = 0
416
417 with open(load_path, "r") as f:
418 rc = self.__ftpconn.storbinary("STOR " + tmp_path, f,
419 8192, updater)
420
421 self._update_progress(force = True)
422
423 self.rename(tmp_path, path)
424
425 done = rc.find("226") != -1
426 return done
427
428 except Exception as e:
429
430 print_traceback()
431 mytxt = red("%s: %s, %s... #%s") % (
432 _("Upload issue"),
433 e,
434 _("retrying"),
435 tries+1,
436 )
437 self.updateProgress(
438 mytxt,
439 importance = 1,
440 type = "warning",
441 header = " "
442 )
443 self._reconnect()
444 self.delete(tmp_path)
445 self.delete(path)
446
447 - def rename(self, remote_path_old, remote_path_new):
448
449 self.__connect_if_not()
450
451 old = os.path.join(self.__ftpdir, remote_path_old)
452 new = os.path.join(self.__ftpdir, remote_path_new)
453 try:
454 rc = self.__ftpconn.rename(old, new)
455 except self.ftplib.error_perm as err:
456
457
458
459 self.delete(remote_path_new)
460 rc = self.__ftpconn.rename(old, new)
461
462 done = rc.find("250") != -1
463 return done
464
465 - def delete(self, remote_path):
466
467 self.__connect_if_not()
468 path = os.path.join(self.__ftpdir, remote_path)
469
470 done = False
471 try:
472 rc = self.__ftpconn.delete(path)
473 if rc.startswith("250"):
474 done = True
475 except self.ftplib.error_perm as err:
476 if err[0][:3] == '550':
477 done = True
478
479
480 return done
481
483
484 self.__connect_if_not()
485 path = os.path.join(self.__ftpdir, remote_path)
486
487 try:
488 rc_data = self.__ftpconn.sendcmd("SITE MD5 %s" % (path,))
489 except self.ftplib.error_perm:
490 return None
491
492 try:
493 return rc_data.split("\n")[0].split("\t")[0].split("-")[1]
494 except (IndexError, TypeError,):
495 return None
496
497 - def list_content(self, remote_path):
498 self.__connect_if_not()
499 path = os.path.join(self.__ftpdir, remote_path)
500 try:
501 return [os.path.basename(x) for x in self.__ftpconn.nlst(path)]
502 except self.ftplib.error_temp:
503 raise ValueError("No such file or directory")
504
506 self.__connect_if_not()
507 path = os.path.join(self.__ftpdir, remote_path)
508
509 mybuffer = []
510 def bufferizer(buf):
511 mybuffer.append(buf)
512
513 try:
514 self.__ftpconn.dir(path, bufferizer)
515 except self.ftplib.error_temp:
516 raise ValueError("No such file or directory")
517
518 data = []
519 for item in mybuffer:
520 item = item.split()
521 name, size, owner, group, perms = item[8], item[4], item[2], \
522 item[3], item[0]
523 data.append((name, size, owner, group, perms,))
524
525 return data
526
527 - def is_dir(self, remote_path):
528
529 self.__connect_if_not()
530 path = os.path.join(self.__ftpdir, remote_path)
531
532 cwd = self._get_cwd()
533 data = True
534 try:
535 self.__set_cwd(path)
536 except self.ftplib.error_perm:
537 data = False
538 finally:
539 self.__set_cwd(cwd)
540
541 return data
542
548
550
551 path, fn = os.path.split(full_path)
552 content = []
553 def cb(x):
554 y = os.path.basename(x)
555 if y == fn:
556 content.append(y)
557
558 try:
559 self.__ftpconn.retrlines('NLST %s' % (path,), cb)
560 except self.ftplib.error_temp as err:
561 if not str(err).startswith("450"):
562 raise
563
564
565 if content:
566 return True
567 return False
568
570 self.__connect_if_not()
571 path = os.path.join(self.__ftpdir, remote_path)
572 return self._is_path_available(path)
573
575 self.__connect_if_not()
576 path = os.path.join(self.__ftpdir, remote_path)
577 mydirs = [x for x in path.split("/") if x]
578
579 mycurpath = ""
580 for mydir in mydirs:
581 mycurpath = os.path.join(mycurpath, mydir)
582 if not self._is_path_available(mycurpath):
583 try:
584 self._mkdir(mycurpath)
585 except self.ftplib.error_perm as e:
586 if e[0].lower().find("permission denied") != -1:
587 raise
588 elif e[0][:3] != '550':
589 raise
590
592 """
593 Send a keep-alive ping to handler.
594 """
595 if not self.__connected:
596 raise ConnectionError("keep_alive when not connected")
597 try:
598 self.__ftpconn.sendcmd("NOOP")
599 except (self.ftplib.error_temp, self.ftplib.error_reply,):
600 raise ConnectionError("cannot execute keep_alive")
601
603 """ just call our disconnect method """
604 self._disconnect()
605