Package duplicity :: Package backends :: Module rsyncbackend
[hide private]
[frames] | no frames]

Source Code for Module duplicity.backends.rsyncbackend

  1  # -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*- 
  2  # 
  3  # Copyright 2002 Ben Escoto <ben@emerose.org> 
  4  # Copyright 2007 Kenneth Loafman <kenneth@loafman.com> 
  5  # 
  6  # This file is part of duplicity. 
  7  # 
  8  # Duplicity is free software; you can redistribute it and/or modify it 
  9  # under the terms of the GNU General Public License as published by the 
 10  # Free Software Foundation; either version 2 of the License, or (at your 
 11  # option) any later version. 
 12  # 
 13  # Duplicity is distributed in the hope that it will be useful, but 
 14  # WITHOUT ANY WARRANTY; without even the implied warranty of 
 15  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU 
 16  # General Public License for more details. 
 17  # 
 18  # You should have received a copy of the GNU General Public License 
 19  # along with duplicity; if not, write to the Free Software Foundation, 
 20  # Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
 21   
 22  import os, re 
 23  import tempfile 
 24   
 25  import duplicity.backend 
 26  from duplicity.errors import * #@UnusedWildImport 
 27  from duplicity import globals, tempdir, util 
 28   
29 -class RsyncBackend(duplicity.backend.Backend):
30 """Connect to remote store using rsync 31 32 rsync backend contributed by Sebastian Wilhelmi <seppi@seppi.de> 33 rsyncd auth, alternate port support 34 Copyright 2010 by Edgar Soldin <edgar.soldin@web.de> 35 36 """
37 - def __init__(self, parsed_url):
38 """rsyncBackend initializer""" 39 duplicity.backend.Backend.__init__(self, parsed_url) 40 """ 41 rsyncd module url: rsync://[user:password@]host[:port]::[/]modname/path 42 Note: 3.0.7 is picky about syntax use either 'rsync://' or '::' 43 cmd: rsync [--port=port] host::modname/path 44 -or- 45 rsync via ssh/rsh url: rsync://user@host[:port]://some_absolute_path 46 -or- rsync://user@host[:port]:/some_relative_path 47 cmd: rsync -e 'ssh [-p=port]' [user@]host:[/]path 48 """ 49 host = parsed_url.hostname 50 port = "" 51 # RSYNC_RSH from calling shell might conflict with our settings 52 if 'RSYNC_RSH' in os.environ: 53 del os.environ['RSYNC_RSH'] 54 if self.over_rsyncd(): 55 # its a module path 56 (path, port) = self.get_rsync_path() 57 self.url_string = "%s::%s" % (host, path.lstrip('/:')) 58 if port: 59 port = " --port=%s" % port 60 else: 61 if parsed_url.path.startswith("//"): 62 # its an absolute path 63 self.url_string = "%s:/%s" % (host, parsed_url.path.lstrip('/')) 64 else: 65 # its a relative path 66 self.url_string = "%s:%s" % (host, parsed_url.path.lstrip('/')) 67 if parsed_url.port: 68 port = " -p %s" % parsed_url.port 69 # add trailing slash if missing 70 if self.url_string[-1] != '/': 71 self.url_string += '/' 72 # user? 73 if parsed_url.username: 74 if self.over_rsyncd(): 75 os.environ['USER'] = parsed_url.username 76 else: 77 self.url_string = parsed_url.username + "@" + self.url_string 78 # password?, don't ask if none was given 79 self.use_getpass = False 80 password = self.get_password() 81 if password: 82 os.environ['RSYNC_PASSWORD'] = password 83 if self.over_rsyncd(): 84 portOption = port 85 else: 86 portOption = " -e 'ssh -oBatchMode=yes%s'" % port 87 rsyncOptions = globals.rsync_options 88 if rsyncOptions: 89 rsyncOptions= " " + rsyncOptions 90 # build cmd 91 self.cmd = "rsync%s%s" % (portOption, rsyncOptions)
92
93 - def over_rsyncd(self):
94 url = self.parsed_url.url_string 95 if re.search('::[^:]*$', url): 96 return True 97 else: 98 return False
99
100 - def get_rsync_path(self):
101 url = self.parsed_url.url_string 102 m = re.search("(:\d+|)?::([^:]*)$", url) 103 if m: 104 return m.group(2), m.group(1).lstrip(':') 105 raise InvalidBackendURL("Could not determine rsync path: %s" 106 "" % self.munge_password( url ) )
107
108 - def run_command(self, commandline):
109 result, stdout, stderr = self.subprocess_popen_persist(commandline) 110 return result, stdout
111
112 - def put(self, source_path, remote_filename = None):
113 """Use rsync to copy source_dir/filename to remote computer""" 114 if not remote_filename: 115 remote_filename = source_path.get_filename() 116 remote_path = os.path.join(self.url_string, remote_filename) 117 commandline = "%s %s %s" % (self.cmd, source_path.name, remote_path) 118 self.run_command(commandline)
119
120 - def get(self, remote_filename, local_path):
121 """Use rsync to get a remote file""" 122 remote_path = os.path.join (self.url_string, remote_filename) 123 commandline = "%s %s %s" % (self.cmd, remote_path, local_path.name) 124 self.run_command(commandline) 125 local_path.setdata() 126 if not local_path.exists(): 127 raise BackendException("File %s not found" % local_path.name)
128
129 - def list(self):
130 """List files""" 131 def split (str): 132 line = str.split () 133 if len (line) > 4 and line[4] != '.': 134 return line[4] 135 else: 136 return None
137 commandline = "%s %s" % (self.cmd, self.url_string) 138 result, stdout = self.run_command(commandline) 139 return filter(lambda x: x, map (split, stdout.split('\n')))
140
141 - def delete(self, filename_list):
142 """Delete files.""" 143 delete_list = filename_list 144 dont_delete_list = [] 145 for file in self.list (): 146 if file in delete_list: 147 delete_list.remove (file) 148 else: 149 dont_delete_list.append (file) 150 if len (delete_list) > 0: 151 raise BackendException("Files %s not found" % str (delete_list)) 152 153 dir = tempfile.mkdtemp() 154 exclude, exclude_name = tempdir.default().mkstemp_file() 155 to_delete = [exclude_name] 156 for file in dont_delete_list: 157 path = os.path.join (dir, file) 158 to_delete.append (path) 159 f = open (path, 'w') 160 print >>exclude, file 161 f.close() 162 exclude.close() 163 commandline = ("%s --recursive --delete --exclude-from=%s %s/ %s" % 164 (self.cmd, exclude_name, dir, self.url_string)) 165 self.run_command(commandline) 166 for file in to_delete: 167 util.ignore_missing(os.unlink, file) 168 os.rmdir (dir)
169 170 duplicity.backend.register_backend("rsync", RsyncBackend) 171