Source code for nxpy.core.backup_file
# nxpy.core package ----------------------------------------------------------
# Copyright Nicola Musatti 2008 - 2012
# Use, modification, and distribution are subject to the Boost Software
# License, Version 1.0. (See accompanying file LICENSE.txt or copy at
# http://www.boost.org/LICENSE_1_0.txt)
# See http://sourceforge.net/nxpy for library home page. ---------------------
r"""
A file with automatic backup.
Implement the context manager protocol, so as to be suitable to be used with
the 'with' statement. When used in this fashion changes are discarded when an
exception is thrown.
"""
import os.path
import shutil
import tempfile
import nxpy.core.error
import nxpy.core.file_object
[docs]class NotSavedError(Exception):
r"""Raised when commit or rollback is called on an inactive BackUpFile"""
[docs]class RemovalError(Exception):
r"""Raised to signal errors in the removal of backup files"""
[docs]class MissingBackupError(Exception):
r"""raised when a backup file isn't found"""
[docs]class BackupFile(nxpy.core.file_object.ReadOnlyFileObject):
r"""
Implements a read only file object used to automatically back up a file that has to be
modified.
"""
_prefix = "_BackupFile"
MOVE = 1
COPY = 2
def __init__(self, file, ext=".BAK", dir=".", mode=COPY):
r"""
Prepare to backup 'file', either a file-like object or a path.
The backup file will be created in directory 'dir' with extension 'ext'. If 'mode' is
'COPY' the original file will be copied to the backup destination; if 'mode' is 'MOVE' it
will be moved there.
"""
super(BackupFile, self).__init__()
if mode not in ( BackupFile.MOVE, BackupFile.COPY ):
raise nxpy.core.error.ArgumentError(self.mode + ": Invalid mode")
if isinstance(file, basestring):
self._orig_name = file
self._orig_file = None
self._bck_name = os.path.join(dir, self._orig_name) + ext
else:
self._orig_name = None
self._orig_file = file
self._bck_name = tempfile.mktemp(ext, BackupFile._prefix, dir)
if mode == BackupFile.MOVE and self._orig_name is None:
raise nxpy.core.error.ArgumentError("Mode can only be MOVE when file is a path.")
self._bck_file = None
self._mode = mode
self._on = False
self._missing = False
@property
[docs] def name(self):
r"""The name of the file to be backed up."""
if self._orig_name is not None:
return self._orig_name
else:
return self._orig_file.name
def __enter__(self):
r"""When the controlling 'with' statement is entered, create the backup file."""
self.save()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
r"""
When the controlling 'with' statement is exited normally discard the backup file,
otherwise restore it to its original place.
"""
if self._on:
if exc_type is None:
self.commit()
else:
self.rollback()
return False
[docs] def save(self):
r"""Create a backup copy of the original file."""
self.close()
if self._mode == BackupFile.MOVE:
try:
shutil.move(self._orig_name, self._bck_name)
except:
self._missing = True
elif self._mode == BackupFile.COPY:
try:
if self._orig_file is None:
self._orig_file = open(self._orig_name, "r+")
self._tell = self._orig_file.tell()
else:
self._tell = self._orig_file.tell()
self._orig_file.seek(0, os.SEEK_SET)
self._bck_file = open(self._bck_name,"w+")
shutil.copyfileobj(self._orig_file, self._bck_file, -1)
self._orig_file.seek(self._tell, os.SEEK_SET)
self._bck_file.close()
self._bck_file = None
except:
self._missing = True
self._on = True
[docs] def commit(self):
r"""Discard the backup, i.e. keep the supposedly modified file."""
if not self._on:
raise NotSavedError(self._name + ": File not saved")
self.close()
try:
os.remove(self._bck_name)
except:
if not self._missing:
raise RemovalError("Error removing file " + self._bck_name)
self._on = False
[docs] def rollback(self):
r"""Replace the original file with the backup copy."""
if not self._on:
raise NotSavedError(self._name + ": File not saved")
self.close()
try:
if self._mode == BackupFile.MOVE:
shutil.move(self._bck_name, self._orig_name)
elif self._mode == BackupFile.COPY:
self._bck_file = open(self._bck_name,"r")
self._orig_file.seek(0, os.SEEK_SET)
shutil.copyfileobj(self._bck_file, self._orig_file, -1)
if self._orig_name is None:
self._orig_file.seek(self._tell, os.SEEK_SET)
else:
self._orig_file.close()
self._bck_file.close()
self._bck_file = None
os.remove(self._bck_name)
except:
if not self._missing:
raise MissingBackupError("File " + self._bck_name +
" not found")
else:
if os.access(self._orig_name, os.F_OK):
os.remove(self._orig_name)
self._on = False
BINARY = 3
TEXT = 4
[docs] def open(self, mode=TEXT):
r"""Open the backup file for reading. 'mode' may be either 'TEXT' or 'BINARY'."""
if not self._on:
raise NotSavedError(self._name + ": File not saved")
if mode == BackupFile.TEXT:
self._bck_file = open(self._bck_name,"r")
elif mode == BackupFile.BINARY:
self._bck_file = open(self._bck_name,"rb")
self.setFile(self._bck_file)
[docs] def close(self):
r"""
Close the backup file and release the corresponding reference.
The backup file may not be reopened.
"""
if self._bck_file:
self._bck_file.close()
self._bck_file = None
self.setFile(None)