/stdhome

To get this branch, use:
bzr branch http://bzr.ed.am/stdhome

« back to all changes in this revision

Viewing changes to lib/stdhome/walker/copy_base.py

  • Committer: Tim Marston
  • Date: 2021-09-01 12:28:59 UTC
  • Revision ID: tim@ed.am-20210901122859-tkvlmry7hef8ahwc
moved bzr -> brz

Show diffs side-by-side

added added

removed removed

Lines of Context:
20
20
 
21
21
 
22
22
import filecmp, os, shutil
23
 
from walker import Walker
24
 
from stdhome.file_matcher import FileMatcher
25
 
import stdhome.the as the
 
23
from .walker import Walker
 
24
from stdhome import the
 
25
from stdhome import util
26
26
 
27
27
 
28
28
class CopyBaseWalker( Walker ):
29
29
        """The copy-base walker traverses a walklist ruthlessly mirroring src to dst.
30
30
        It is designed to be the base class of both the copy-in and copy-out walker,
31
31
        both of which are specialisations of this purpose.  See them for more
32
 
        information.
 
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 destination 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:
 
37
        ignored).
 
38
 
33
39
        """
34
40
 
35
 
        def __init__( self, updated_files = None ):
36
 
                self.accept_list = FileMatcher()
 
41
 
 
42
        def __init__( self ):
 
43
                self.check_src_symlinks = False
 
44
                self.check_dst_symlinks = False
 
45
                self.check_dst_ignores = False
 
46
                self.report = False
 
47
 
 
48
 
 
49
        def print_op( self, rel_file, src, op, dst ):
 
50
 
 
51
                # report changes
 
52
                if self.report and op == '*':
 
53
                        if dst == '_':
 
54
                                print(" N  %s" % ( rel_file ))
 
55
                        elif src == '_':
 
56
                                print(" D  %s" % ( rel_file ))
 
57
                        elif src == dst:
 
58
                                print(" M  %s" % ( rel_file ))
 
59
                        else:
 
60
                                print(" K  %s" % ( rel_file ))
37
61
 
38
62
 
39
63
        def process( self, rel_file, src, dst ):
40
64
 
 
65
                # ignore?
 
66
                if self.check_dst_ignores and the.config.ignores.matches( rel_file ):
 
67
                        self.print_op( rel_file, src.type, '#', dst.type )
 
68
                        return True
 
69
 
41
70
                # src entity is directory
42
71
                if src.type == 'd':
43
72
 
44
 
                        # if dst entity doesn't exist, create directory (no need to recurse,
45
 
                        # since we're copying the whole directory)
 
73
                        # if dst entity doesn't exist, create it (and recurse)
46
74
                        if dst.type == '_':
47
 
                                if the.verbose > 1: self.print_op( rel_file, 'd>_' )
 
75
                                self.print_op( rel_file, 'd', '*', '_' )
48
76
                                os.mkdir( dst.file )
49
77
                                shutil.copystat( src.file, dst.file )
50
 
                                return False
 
78
                                return True
51
79
 
52
80
                        # if dst entity is a directory, copy permissions, as required (and
53
81
                        # recurse)
54
82
                        elif dst.type == 'd':
55
 
                                # TODO: should check permission and only do as necessary
56
 
                                if the.verbose > 1: self.print_op( rel_file, 'd>d' )
57
 
                                shutil.copystat( src.file, dst.file )
 
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 )
 
86
                                else:
 
87
                                        self.print_op( rel_file, 'd', '=', 'd' )
58
88
                                return True
59
89
 
60
 
                        # TODO: if dst entity is a symlink to a directory, and rel_file is
61
 
                        # matched by the acceptable symlinks file matcher, just recurse
62
 
                        elif dst.link_type == 'd' and self.accept_list.match( rel_file ):
63
 
                                if the.verbose > 1: 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' )
64
95
                                return True
65
96
 
66
97
                        # if dst entity is a file or symlink in home dir, replace it with
67
98
                        # directory (no need to recurse, since we're copying the whole
68
99
                        # directory)
69
100
                        elif dst.type == 'f' or dst.type == 'l':
70
 
                                if the.verbose > 1: self.print_op( rel_file, 'd>' + dst.type )
 
101
                                self.print_op( rel_file, 'd', '*', dst.type )
71
102
                                os.unlink( dst.file )
72
 
                                os.mkdir( dst.file )
73
 
                                shutil.copystat( src.file, dst.file )
74
 
                                return False
 
103
                                shutil.copytree( src.file, dst.file, True )
75
104
 
76
105
                        else:
77
106
                                raise NotImplementedError()
78
107
 
79
108
                # src entity is file
80
 
                if src.type == 'f':
 
109
                elif src.type == 'f':
81
110
 
82
111
                        # if dst entity doesn't exist, copy file
83
112
                        if dst.type == '_':
84
 
                                if the.verbose > 1: self.print_op( rel_file, 'f>_' )
 
113
                                self.print_op( rel_file, 'f', '*', '_' )
85
114
                                shutil.copy( src.file, dst.file )
86
115
                                shutil.copystat( src.file, dst.file )
87
116
 
88
117
                        # if dst entity is a file, replace it only if it differs
89
118
                        elif dst.type == 'f':
90
119
                                if not filecmp.cmp( src.file, dst.file ):
91
 
                                        if the.verbose > 1: self.print_op( rel_file, 'f>f' )
 
120
                                        self.print_op( rel_file, 'f', '*', 'f' )
92
121
                                        os.unlink( dst.file )
93
122
                                        shutil.copy( src.file, dst.file )
94
123
                                        shutil.copystat( src.file, dst.file )
95
124
                                else:
96
 
                                        if the.verbose > 1: self.print_op( rel_file, 'f=f' )
 
125
                                        self.print_op( rel_file, 'f', '=', 'f' )
97
126
 
98
127
                        # if dst entity is a directory, replace it with file
99
128
                        elif dst.type == 'd':
100
 
                                if the.verbose > 1: self.print_op( rel_file, 'f>d' )
 
129
                                self.print_op( rel_file, 'f', '*', 'd' )
101
130
                                shutil.rmtree( dst.file )
102
131
                                shutil.copy( src.file, dst.file )
103
132
                                shutil.copystat( src.file, dst.file )
104
133
 
105
134
                        # if dst entity is a symlink, replace it with file
106
135
                        elif dst.type == 'l':
107
 
                                if the.verbose > 1: self.print_op( rel_file, 'f>l' )
 
136
                                self.print_op( rel_file, 'f', '*', 'l' )
108
137
                                os.unlink( dst.file )
109
138
                                shutil.copy( src.file, dst.file )
110
139
                                shutil.copystat( src.file, dst.file )
113
142
                                raise NotImplementedError()
114
143
 
115
144
                # src entity is a symlink
116
 
                if src.type == 'l':
 
145
                elif src.type == 'l':
117
146
 
118
147
                        # if dst entity doesn't exist, copy symlink
119
148
                        if dst.type == '_':
120
 
                                if the.verbose > 1: self.print_op( rel_file, 'l>_' )
 
149
                                self.print_op( rel_file, 'l', '*', '_' )
121
150
                                os.symlink( os.readlink( src.file ), dst.file )
122
151
 
123
152
                        # if dst entity is a symlink, replace it only if it differs
124
153
                        elif dst.type == 'l':
125
154
                                if os.readlink( src.file ) != os.readlink( dst.file ):
126
 
                                        if the.verbose > 1: self.print_op( rel_file, 'l>l' )
 
155
                                        self.print_op( rel_file, 'l', '*', 'l' )
127
156
                                        os.unlink( dst.file )
128
157
                                        os.symlink( os.readlink( src.file ), dst.file )
129
158
                                else:
130
 
                                        if the.verbose > 1: self.print_op( rel_file, 'l=l' )
 
159
                                        self.print_op( rel_file, 'l', '=', 'l' )
131
160
 
132
161
                        # if dst entity is a file, replace it with symlink
133
162
                        elif dst.type == 'f':
134
 
                                if the.verbose > 1: self.print_op( rel_file, 'l>f' )
 
163
                                self.print_op( rel_file, 'l', '*', 'f' )
135
164
                                os.unlink( dst.file )
136
165
                                os.symlink( os.readlink( src.file ), dst.file )
137
166
 
138
 
                        # if dst entity is a directory, replace it with symlink
 
167
                        # if dst entity is a directory...
139
168
                        elif dst.type == 'd':
140
 
                                if the.verbose > 1: self.print_op( rel_file, 'l>d' )
 
169
 
 
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' )
 
175
                                        return True
 
176
 
 
177
                                # else replace it with a symlink
 
178
                                self.print_op( rel_file, 'l', '*', 'd' )
141
179
                                shutil.rmtree( dst.file )
142
180
                                os.symlink( os.readlink( src.file ), dst.file )
143
181
 
145
183
                                raise NotImplementedError()
146
184
 
147
185
                # src entity is missing
148
 
                if src.type == '_':
 
186
                elif src.type == '_':
149
187
 
150
188
                        # if dst entity doesn't exist, we're good
151
189
                        if dst.type == '_':
153
191
 
154
192
                        # if dst entity is a file or symlink, delete it
155
193
                        elif dst.type == 'f' or dst.type == 'l':
156
 
                                if the.verbose > 1: self.print_op( rel_file, '_>' + dst.type )
 
194
                                self.print_op( rel_file, '_', '*', dst.type )
157
195
                                os.unlink( dst.file )
158
196
 
159
197
                        # if dst entity is a directory, delete it
160
198
                        elif dst.type == 'd':
161
 
                                if the.verbose > 1: self.print_op( rel_file, '_>d' )
 
199
                                self.print_op( rel_file, '_', '*', 'd' )
162
200
                                shutil.rmtree( dst.file )
163
201
 
164
202
                        else:
165
203
                                raise NotImplementedError()
166
204
 
167
 
                # non-directories can not be recursed in to
 
205
                # if we got here, we don't want to recurse...
168
206
                return False