1 # Copyright (C) 2012, Ansgar Burchardt <ansgar@debian.org>
3 # This program is free software; you can redistribute it and/or modify
4 # it under the terms of the GNU General Public License as published by
5 # the Free Software Foundation; either version 2 of the License, or
6 # (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License along
14 # with this program; if not, write to the Free Software Foundation, Inc.,
15 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17 """Transactions for filesystem actions
24 class _FilesystemAction(object):
26 def temporary_name(self):
27 raise NotImplementedError()
29 def check_for_temporary(self):
31 if os.path.exists(self.temporary_name):
32 raise IOError("Temporary file '{0}' already exists.".format(self.temporary_name))
33 except NotImplementedError:
36 class _FilesystemCopyAction(_FilesystemAction):
37 def __init__(self, source, destination, link=True, symlink=False, mode=None):
38 self.destination = destination
39 self.need_cleanup = False
43 dirmode = 0o2700 | mode
44 # Allow +x for group and others if they have +r.
46 dirmode = dirmode | 0o0010
48 dirmode = dirmode | 0o0001
50 self.check_for_temporary()
51 destdir = os.path.dirname(self.destination)
52 if not os.path.exists(destdir):
53 os.makedirs(destdir, dirmode)
55 os.symlink(source, self.destination)
58 os.link(source, self.destination)
60 shutil.copy2(source, self.destination)
62 shutil.copy2(source, self.destination)
64 self.need_cleanup = True
66 os.chmod(self.destination, mode)
69 def temporary_name(self):
70 return self.destination
77 os.unlink(self.destination)
78 self.need_cleanup = False
80 class _FilesystemUnlinkAction(_FilesystemAction):
81 def __init__(self, path):
83 self.need_cleanup = False
85 self.check_for_temporary()
86 os.rename(self.path, self.temporary_name)
87 self.need_cleanup = True
90 def temporary_name(self):
91 return "{0}.dak-rm".format(self.path)
95 os.unlink(self.temporary_name)
96 self.need_cleanup = False
100 os.rename(self.temporary_name, self.path)
101 self.need_cleanup = False
103 class _FilesystemCreateAction(_FilesystemAction):
104 def __init__(self, path):
106 self.need_cleanup = True
109 def temporary_name(self):
116 if self.need_cleanup:
118 self.need_cleanup = False
120 class FilesystemTransaction(object):
121 """transactions for filesystem actions"""
125 def copy(self, source, destination, link=False, symlink=False, mode=None):
126 """copy C{source} to C{destination}
129 @param source: source file
131 @type destination: str
132 @param destination: destination file
135 @param link: try hardlinking, falling back to copying
138 @param symlink: create a symlink instead of copying
141 @param mode: permissions to change C{destination} to
143 if isinstance(mode, str) or isinstance(mode, unicode):
146 self.actions.append(_FilesystemCopyAction(source, destination, link=link, symlink=symlink, mode=mode))
148 def move(self, source, destination, mode=None):
149 """move C{source} to C{destination}
152 @param source: source file
154 @type destination: str
155 @param destination: destination file
158 @param mode: permissions to change C{destination} to
160 self.copy(source, destination, link=True, mode=mode)
163 def unlink(self, path):
167 @param path: file to unlink
169 self.actions.append(_FilesystemUnlinkAction(path))
171 def create(self, path, mode=None):
172 """create C{filename} and return file handle
175 @param filename: file to create
178 @param mode: permissions for the new file
180 @return: file handle of the new file
182 if isinstance(mode, str) or isinstance(mode, unicode):
185 destdir = os.path.dirname(path)
186 if not os.path.exists(destdir):
187 os.makedirs(destdir, 0o2775)
188 if os.path.exists(path):
189 raise IOError("File '{0}' already exists.".format(path))
191 self.actions.append(_FilesystemCreateAction(path))
197 """Commit all recorded actions."""
199 for action in self.actions:
208 """Undo all recorded actions."""
210 for action in self.actions:
218 def __exit__(self, type, value, traceback):