# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4; encoding:utf-8 -*-
#
# Copyright 2002 Ben Escoto <ben@emerose.org>
# Copyright 2007 Kenneth Loafman <kenneth@loafman.com>
#
# This file is part of duplicity.
#
# Duplicity is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the
# Free Software Foundation; either version 2 of the License, or (at your
# option) any later version.
#
# Duplicity is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with duplicity; if not, write to the Free Software Foundation,
# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
u"""Manage temporary files"""
from __future__ import print_function
from future import standard_library
standard_library.install_aliases()
from builtins import object
import os
import sys
import shutil
from duplicity import log
from duplicity import path
from duplicity import file_naming
from duplicity import tempdir
from duplicity import config
from duplicity import gpg
[docs]def new_temppath():
u"""
Return a new TempPath
"""
filename = tempdir.default().mktemp()
return TempPath(filename)
[docs]class TempPath(path.Path):
u"""
Path object used as a temporary file
"""
[docs] def delete(self):
u"""
Forget and delete
"""
path.Path.delete(self)
tempdir.default().forget(self.name)
[docs] def open_with_delete(self, mode):
u"""
Returns a fileobj. When that is closed, delete file
"""
fh = FileobjHooked(path.Path.open(self, mode))
fh.addhook(self.delete)
return fh
[docs]def get_fileobj_duppath(dirpath, partname, permname, remname, overwrite=False):
u"""
Return a file object open for writing, will write to filename
Data will be processed and written to a temporary file. When the
return fileobject is closed, rename to final position. filename
must be a recognizable duplicity data file.
"""
if not config.restart:
td = tempdir.TemporaryDirectory(dirpath.name)
tdpname = td.mktemp()
tdp = TempDupPath(tdpname, parseresults=file_naming.parse(partname))
fh = FileobjHooked(tdp.filtered_open(u"wb"), tdp=tdp, dirpath=dirpath,
partname=partname, permname=permname, remname=remname)
else:
dp = path.DupPath(dirpath.name, index=(partname,))
mode = u"ab"
if overwrite:
mode = u"wb"
fh = FileobjHooked(dp.filtered_open(mode), tdp=None, dirpath=dirpath,
partname=partname, permname=permname, remname=remname)
def rename_and_forget():
tdp.rename(dirpath.append(partname))
td.forget(tdpname)
if not config.restart:
fh.addhook(rename_and_forget)
return fh
[docs]def new_tempduppath(parseresults):
u"""
Return a new TempDupPath, using settings from parseresults
"""
filename = tempdir.default().mktemp()
return TempDupPath(filename, parseresults=parseresults)
[docs]class TempDupPath(path.DupPath):
u"""
Like TempPath, but build around DupPath
"""
[docs] def delete(self):
u"""
Forget and delete
"""
path.DupPath.delete(self)
tempdir.default().forget(self.name)
[docs] def filtered_open_with_delete(self, mode):
u"""
Returns a filtered fileobj. When that is closed, delete file
"""
fh = FileobjHooked(path.DupPath.filtered_open(self, mode))
fh.addhook(self.delete)
return fh
[docs] def open_with_delete(self, mode=u"rb"):
u"""
Returns a fileobj. When that is closed, delete file
"""
assert mode == u"rb" # Why write a file and then close it immediately?
fh = FileobjHooked(path.DupPath.open(self, mode))
fh.addhook(self.delete)
return fh
[docs]class FileobjHooked(object):
u"""
Simulate a file, but add hook on close
"""
[docs] def __init__(self, fileobj, tdp=None, dirpath=None,
partname=None, permname=None, remname=None):
u"""
Initializer. fileobj is the file object to simulate
"""
self.fileobj = fileobj # the actual file object
self.closed = False # True if closed
self.hooklist = [] # filled later with thunks to run on close
self.tdp = tdp # TempDupPath object
self.dirpath = dirpath # path to directory
self.partname = partname # partial filename
self.permname = permname # permanent filename
self.remname = remname # remote filename
[docs] def write(self, buf):
u"""
Write fileobj, return result of write()
"""
return self.fileobj.write(buf)
[docs] def flush(self):
u"""
Flush fileobj and force sync.
"""
self.fileobj.flush()
os.fsync(self.fileobj.fileno())
[docs] def to_partial(self):
u"""
We have achieved the first checkpoint, make file visible and permanent.
"""
assert not config.restart
self.tdp.rename(self.dirpath.append(self.partname))
self.fileobj.flush()
del self.hooklist[0]
[docs] def to_remote(self):
u"""
We have written the last checkpoint, now encrypt or compress
and send a copy of it to the remote for final storage.
"""
log.Debug(u"TO_REMOTE")
pr = file_naming.parse(self.remname)
src = self.dirpath.append(self.partname)
tgt = self.dirpath.append(self.remname)
src_iter = SrcIter(src)
if pr.compressed:
gpg.GzipWriteFile(src_iter, tgt.name, size=sys.maxsize)
elif pr.encrypted:
gpg.GPGWriteFile(src_iter, tgt.name, config.gpg_profile, size=sys.maxsize)
else:
shutil.copyfile(src.name, tgt.name)
config.backend.move(tgt)
[docs] def to_final(self):
u"""
We are finished, rename to final, gzip if needed.
"""
src = self.dirpath.append(self.partname)
tgt = self.dirpath.append(self.permname)
src_iter = SrcIter(src)
pr = file_naming.parse(self.permname)
if pr.compressed:
gpg.GzipWriteFile(src_iter, tgt.name, size=sys.maxsize)
os.unlink(src.name)
else:
os.rename(src.name, tgt.name)
[docs] def read(self, length=-1):
u"""
Read fileobj, return result of read()
"""
return self.fileobj.read(length)
[docs] def tell(self):
u"""
Returns current location of fileobj
"""
return self.fileobj.tell()
[docs] def seek(self, offset):
u"""
Seeks to a location of fileobj
"""
return self.fileobj.seek(offset)
[docs] def close(self):
u"""
Close fileobj, running hooks right afterwards
"""
assert not self.fileobj.close()
for hook in self.hooklist:
hook()
[docs] def addhook(self, hook):
u"""
Add hook (function taking no arguments) to run upon closing
"""
self.hooklist.append(hook)
[docs] def get_name(self):
u"""
Return the name of the file
"""
return self.fileobj.name
name = property(get_name)
[docs]class Block(object):
u"""
Data block to return from SrcIter
"""
[docs] def __init__(self, data):
self.data = data
[docs]class SrcIter(object):
u"""
Iterate over source and return Block of data.
"""
[docs] def __init__(self, src):
self.src = src
self.fp = src.open(u"rb")
def __next__(self):
try:
res = Block(self.fp.read(self.get_read_size()))
except Exception:
log.FatalError(_(u"Failed to read %s: %s") %
(self.src.uc_name, sys.exc_info()),
log.ErrorCode.generic)
if not res.data:
self.fp.close()
raise StopIteration
return res
[docs] def get_read_size(self):
return 128 * 1024