3
# Copyright (C) 2013 to 2014 Tim Marston <tim@edm.am>
5
# This file is part of stdhome (hereafter referred to as "this program").
6
# See http://ed.am/dev/stdhome for more information.
8
# This program is free software: you can redistribute it and/or modify
9
# it under the terms of the GNU General Public License as published by
10
# the Free Software Foundation, either version 3 of the License, or
11
# (at your option) any later version.
13
# This program is distributed in the hope that it will be useful,
14
# but WITHOUT ANY WARRANTY; without even the implied warranty of
15
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
# GNU General Public License for more details.
18
# You should have received a copy of the GNU General Public License
19
# along with this program. If not, see <http://www.gnu.org/licenses/>.
22
import filecmp, os, shutil
23
from walker import Walker
24
from stdhome import the
25
from stdhome import util
28
class CopyBaseWalker( Walker ):
29
"""The copy-base walker traverses a walklist ruthlessly mirroring src to dst.
30
It is designed to be the base class of both the copy-in and copy-out walker,
31
both of which are specialisations of this purpose. See them for more
32
information. The report method, which can be overridden in derived classes,
33
takes, in addition to the relative filename, a source file type, an
34
operation, and a sestination file type. Valid file types are f (file), l
35
(symlink), d (directory) and _ (non-existant). Valid operations are *
36
(modify), = (skip: same), @ (skip: symlink substitute) and # (skip:
43
self.check_src_symlinks = False
44
self.check_dst_symlinks = False
45
self.check_dst_ignores = False
49
def print_op( self, rel_file, src, op, dst ):
52
if self.report and the.verbose < 2 and op == '*':
54
print " N %s" % ( rel_file )
56
print " D %s" % ( rel_file )
58
print " M %s" % ( rel_file )
60
print " K %s" % ( rel_file )
63
def process( self, rel_file, src, dst ):
66
if self.check_dst_ignores and the.config.ignores.matches( rel_file ):
67
self.print_op( rel_file, src.type, '#', dst.type )
70
# src entity is directory
73
# if dst entity doesn't exist, create it (and recurse)
75
self.print_op( rel_file, 'd', '*', '_' )
77
shutil.copystat( src.file, dst.file )
80
# if dst entity is a directory, copy permissions, as required (and
83
if os.stat( src.file ).st_mode != os.stat( dst.file ).st_mode:
84
self.print_op( rel_file, 'd', '*', 'd' )
85
shutil.copystat( src.file, dst.file )
87
self.print_op( rel_file, 'd', '=', 'd' )
90
# if dst entity is a symlink to a directory, and this is an
91
# acceptable substitute, just recurse
92
elif self.check_dst_symlinks and dst.link_type == 'd' and \
93
the.config.symlinks.matches( rel_file ):
94
self.print_op( rel_file, 'd', '@', 'd' )
97
# if dst entity is a file or symlink in home dir, replace it with
98
# directory (no need to recurse, since we're copying the whole
100
elif dst.type == 'f' or dst.type == 'l':
101
self.print_op( rel_file, 'd', '*', dst.type )
102
os.unlink( dst.file )
103
shutil.copytree( src.file, dst.file, True )
106
raise NotImplementedError()
109
elif src.type == 'f':
111
# if dst entity doesn't exist, copy file
113
self.print_op( rel_file, 'f', '*', '_' )
114
shutil.copy( src.file, dst.file )
115
shutil.copystat( src.file, dst.file )
117
# if dst entity is a file, replace it only if it differs
118
elif dst.type == 'f':
119
if not filecmp.cmp( src.file, dst.file ):
120
self.print_op( rel_file, 'f', '*', 'f' )
121
os.unlink( dst.file )
122
shutil.copy( src.file, dst.file )
123
shutil.copystat( src.file, dst.file )
125
self.print_op( rel_file, 'f', '=', 'f' )
127
# if dst entity is a directory, replace it with file
128
elif dst.type == 'd':
129
self.print_op( rel_file, 'f', '*', 'd' )
130
shutil.rmtree( dst.file )
131
shutil.copy( src.file, dst.file )
132
shutil.copystat( src.file, dst.file )
134
# if dst entity is a symlink, replace it with file
135
elif dst.type == 'l':
136
self.print_op( rel_file, 'f', '*', 'l' )
137
os.unlink( dst.file )
138
shutil.copy( src.file, dst.file )
139
shutil.copystat( src.file, dst.file )
142
raise NotImplementedError()
144
# src entity is a symlink
145
elif src.type == 'l':
147
# if dst entity doesn't exist, copy symlink
149
self.print_op( rel_file, 'l', '*', '_' )
150
os.symlink( os.readlink( src.file ), dst.file )
152
# if dst entity is a symlink, replace it only if it differs
153
elif dst.type == 'l':
154
if os.readlink( src.file ) != os.readlink( dst.file ):
155
self.print_op( rel_file, 'l', '*', 'l' )
156
os.unlink( dst.file )
157
os.symlink( os.readlink( src.file ), dst.file )
159
self.print_op( rel_file, 'l', '=', 'l' )
161
# if dst entity is a file, replace it with symlink
162
elif dst.type == 'f':
163
self.print_op( rel_file, 'l', '*', 'f' )
164
os.unlink( dst.file )
165
os.symlink( os.readlink( src.file ), dst.file )
167
# if dst entity is a directory...
168
elif dst.type == 'd':
170
# if src entity is a symlink to a directory, and this is an
171
# acceptable substitute, just recurse
172
if self.check_src_symlinks and src.link_type == 'd' and \
173
the.config.symlinks.matches( rel_file ):
174
self.print_op( rel_file, 'd', '@', 'd' )
177
# else replace it with a symlink
178
self.print_op( rel_file, 'l', '*', 'd' )
179
shutil.rmtree( dst.file )
180
os.symlink( os.readlink( src.file ), dst.file )
183
raise NotImplementedError()
185
# src entity is missing
186
elif src.type == '_':
188
# if dst entity doesn't exist, we're good
192
# if dst entity is a file or symlink, delete it
193
elif dst.type == 'f' or dst.type == 'l':
194
self.print_op( rel_file, '_', '*', dst.type )
195
os.unlink( dst.file )
197
# if dst entity is a directory, delete it
198
elif dst.type == 'd':
199
self.print_op( rel_file, '_', '*', 'd' )
200
shutil.rmtree( dst.file )
203
raise NotImplementedError()
205
# if we got here, we don't want to recurse...