1 # Cumulus: Efficient Filesystem Backup to the Cloud
2 # Copyright (C) 2009 The Cumulus Developers
3 # See the AUTHORS file for a list of contributors.
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 2 of the License, or
8 # (at your option) any later version.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
15 # You should have received a copy of the GNU General Public License along
16 # with this program; if not, write to the Free Software Foundation, Inc.,
17 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 from __future__ import division, print_function, unicode_literals
21 from ftplib import FTP, all_errors, error_temp
22 from netrc import netrc, NetrcParseError
23 from cumulus.store import Store, type_patterns, NotFoundError
25 class FtpStore (Store):
26 def __init__ (self, url, **kw):
29 upw, hp = self.netloc.split ('@')
34 host, port = hp.split (':')
40 user, passwd = upw.split (':')
47 user, acct, passwd = n.authenticators (host)
50 except (IOError, NetrcParseError):
56 self.prefix = self.path [1:] # skip *only* first '/'
60 def _get_path (self, type, name):
61 # we are in right directory
65 self.ftp.connect (self.host, self.port)
66 self.ftp.login (self.user, self.passwd)
67 self.ftp.cwd (self.prefix)
70 def list (self, type):
72 files = self.ftp.nlst ()
73 return (f for f in files if type_patterns[type].match (f))
75 def get (self, type, name):
77 self.ftp.sendcmd ('TYPE I')
78 sock = self.ftp.transfercmd ('RETR %s' % self._get_path (type, name))
80 return sock.makefile ()
82 def put (self, type, name, fp):
84 self.ftp.storbinary ("STOR %s" % self._get_path (type, name), fp)
86 def delete (self, type, name):
88 self.ftp.delete (self._get_path (type, name))
90 def stat (self, type, name):
91 """ Note that the size-command is non-standard but supported by
92 most ftp servers today. If size returns an error condition we
93 try nlst to detect if the file exists and return an bogus length
96 fn = self._get_path (type, name)
99 # my client doesn't accept size in ascii-mode
100 self.ftp.sendcmd ('TYPE I')
101 size = self.ftp.size (fn)
102 self.ftp.sendcmd ('TYPE A')
103 except all_errors as err:
107 return {'size': size}
108 print("nlst: %s" % fn, size)
109 l = self.ftp.nlst (fn)
112 raise NotFoundError(type, name)
115 """ After a get command at end of transfer a 2XX reply is still
116 in the input-queue, we have to get rid of that.
117 We also test here that the connection is still alive. If we get
118 a temporary error 421 ("error_temp") we reconnect: It was
124 self.ftp.sendcmd ('TYPE A')
125 except error_temp as err :
126 if not err.message.startswith ('421') :