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 ftplib import FTP, all_errors, error_temp
20 from netrc import netrc, NetrcParseError
21 from cumulus.store import Store, type_patterns, NotFoundError
23 class FtpStore (Store):
24 def __init__ (self, url, **kw):
27 upw, hp = self.netloc.split ('@')
32 host, port = hp.split (':')
38 user, passwd = upw.split (':')
45 user, acct, passwd = n.authenticators (host)
48 except (IOError, NetrcParseError):
54 self.prefix = self.path [1:] # skip *only* first '/'
58 def _get_path (self, type, name):
59 # we are in right directory
63 self.ftp.connect (self.host, self.port)
64 self.ftp.login (self.user, self.passwd)
65 self.ftp.cwd (self.prefix)
68 def list (self, type):
70 files = self.ftp.nlst ()
71 return (f for f in files if type_patterns[type].match (f))
73 def get (self, type, name):
75 self.ftp.sendcmd ('TYPE I')
76 sock = self.ftp.transfercmd ('RETR %s' % self._get_path (type, name))
78 return sock.makefile ()
80 def put (self, type, name, fp):
82 self.ftp.storbinary ("STOR %s" % self._get_path (type, name), fp)
84 def delete (self, type, name):
86 self.ftp.delete (self._get_path (type, name))
88 def stat (self, type, name):
89 """ Note that the size-command is non-standard but supported by
90 most ftp servers today. If size returns an error condition we
91 try nlst to detect if the file exists and return an bogus length
94 fn = self._get_path (type, name)
97 # my client doesn't accept size in ascii-mode
98 self.ftp.sendcmd ('TYPE I')
99 size = self.ftp.size (fn)
100 self.ftp.sendcmd ('TYPE A')
101 except all_errors as err:
105 return {'size': size}
106 print("nlst: %s" % fn, size)
107 l = self.ftp.nlst (fn)
110 raise NotFoundError(type, name)
113 """ After a get command at end of transfer a 2XX reply is still
114 in the input-queue, we have to get rid of that.
115 We also test here that the connection is still alive. If we get
116 a temporary error 421 ("error_temp") we reconnect: It was
122 self.ftp.sendcmd ('TYPE A')
123 except error_temp as err :
124 if not err.message.startswith ('421') :