def test_replace_file(self): workdir = fileutil.abspath_expanduser_unicode(u"test_replace_file") fileutil.make_dirs(workdir) replaced_path = os.path.join(workdir, "replaced") replacement_path = os.path.join(workdir, "replacement") # when none of the files exist self.failUnlessRaises(fileutil.ConflictError, fileutil.replace_file, replaced_path, replacement_path) # when only replaced exists fileutil.write(replaced_path, b"foo") self.failUnlessRaises(fileutil.ConflictError, fileutil.replace_file, replaced_path, replacement_path) self.failUnlessEqual(fileutil.read(replaced_path), b"foo") # when both replaced and replacement exist fileutil.write(replacement_path, b"bar") fileutil.replace_file(replaced_path, replacement_path) self.failUnlessEqual(fileutil.read(replaced_path), b"bar") self.failIf(os.path.exists(replacement_path)) # when only replacement exists os.remove(replaced_path) fileutil.write(replacement_path, b"bar") fileutil.replace_file(replaced_path, replacement_path) self.failUnlessEqual(fileutil.read(replaced_path), b"bar") self.failIf(os.path.exists(replacement_path))
def _write_downloaded_file(self, local_path_u, abspath_u, file_contents, is_conflict=False, now=None): self._log( "_write_downloaded_file(%r, <%d bytes>, is_conflict=%r, now=%r)" % (abspath_u, len(file_contents), is_conflict, now)) # 1. Write a temporary file, say .foo.tmp. # 2. is_conflict determines whether this is an overwrite or a conflict. # 3. Set the mtime of the replacement file to be T seconds before the # current local time. # 4. Perform a file replacement with backup filename foo.backup, # replaced file foo, and replacement file .foo.tmp. If any step of # this operation fails, reclassify as a conflict and stop. # # Returns the path of the destination file. precondition_abspath(abspath_u) replacement_path_u = abspath_u + u".tmp" # FIXME more unique backup_path_u = abspath_u + u".backup" if now is None: now = time.time() initial_path_u = os.path.dirname(abspath_u) fileutil.make_dirs_with_absolute_mode(local_path_u, initial_path_u, (~self._umask) & 0777) fileutil.write(replacement_path_u, file_contents) os.chmod(replacement_path_u, (~self._umask) & 0777) # FUDGE_SECONDS is used to determine if another process # has written to the same file concurrently. This is described # in the Earth Dragon section of our design document: # docs/proposed/magic-folder/remote-to-local-sync.rst os.utime(replacement_path_u, (now, now - self.FUDGE_SECONDS)) if is_conflict: return self._rename_conflicted_file(abspath_u, replacement_path_u) else: try: fileutil.replace_file(abspath_u, replacement_path_u, backup_path_u) return abspath_u except fileutil.ConflictError: return self._rename_conflicted_file(abspath_u, replacement_path_u)
def _write_downloaded_file(self, local_path_u, abspath_u, file_contents, is_conflict=False, now=None): self._log("_write_downloaded_file(%r, <%d bytes>, is_conflict=%r, now=%r)" % (abspath_u, len(file_contents), is_conflict, now)) # 1. Write a temporary file, say .foo.tmp. # 2. is_conflict determines whether this is an overwrite or a conflict. # 3. Set the mtime of the replacement file to be T seconds before the # current local time. # 4. Perform a file replacement with backup filename foo.backup, # replaced file foo, and replacement file .foo.tmp. If any step of # this operation fails, reclassify as a conflict and stop. # # Returns the path of the destination file. precondition_abspath(abspath_u) replacement_path_u = abspath_u + u".tmp" # FIXME more unique backup_path_u = abspath_u + u".backup" if now is None: now = time.time() initial_path_u = os.path.dirname(abspath_u) fileutil.make_dirs_with_absolute_mode(local_path_u, initial_path_u, (~ self._umask) & 0777) fileutil.write(replacement_path_u, file_contents) os.chmod(replacement_path_u, (~ self._umask) & 0777) # FUDGE_SECONDS is used to determine if another process # has written to the same file concurrently. This is described # in the Earth Dragon section of our design document: # docs/proposed/magic-folder/remote-to-local-sync.rst os.utime(replacement_path_u, (now, now - self.FUDGE_SECONDS)) if is_conflict: return self._rename_conflicted_file(abspath_u, replacement_path_u) else: try: fileutil.replace_file(abspath_u, replacement_path_u, backup_path_u) return abspath_u except fileutil.ConflictError: return self._rename_conflicted_file(abspath_u, replacement_path_u)