Source code for saveset_files_mod

#!/usr/bin/env python

"""A simple class for operating on the file list portion of a saveset."""

import errno
import functools
import os

import backshift_file_mod
import compressed_file_mod
import constants_mod
import db_mod
import dirops_mod
import helpers


[docs]def convert_filename(filename): """Convert a filename to a sort of normalized form: Make it "binary", and strip off the leading / (os.path.sep).""" bytes_filename = helpers.string_to_binary(filename) bytes_path_sep = helpers.string_to_binary(os.path.sep) stripped_filename = bytes_filename.lstrip(bytes_path_sep) return stripped_filename
[docs]def prepend_dir_prefix(path, start_from=0): """Prepend dir- prefixes.""" # FIXME: Can this becomes just a path.split on 3.x-only? if isinstance(path, bytes): path_components = path.split(helpers.string_to_binary(os.path.sep)) else: path_components = path prefix = constants_mod.Constants.b_dir_dash prefixed_path_components = [] for dirno, directory in enumerate(path_components): if dirno >= start_from: prefixed_path_components.append(prefix + directory) else: prefixed_path_components.append(directory) bytes_path_sep = helpers.string_to_binary(os.path.sep) rejoined = bytes_path_sep.join(prefixed_path_components) return rejoined
[docs]class Saveset_files(object): # pylint: disable=R0903 # R0903: We don't need a lot of public methods. At least, not yet. """A simple class for operating on the file list portion of a saveset.""" def __init__(self, repo, saveset_summary=None): """Initialize.""" self.repo = repo if saveset_summary is None: backup_id = repo.backup_id else: backup_id = saveset_summary.backup_id self.backup_id = helpers.string_to_binary(backup_id) files = constants_mod.Constants.b_files self.early_dirs = os.path.join(files, self.backup_id) def _get_database_and_basename(self, filename, mode): """Look up the database and key corresponding to filename, if any.""" converted_filename = convert_filename(filename) filename_plus_early_dirs = os.path.join(self.early_dirs, converted_filename) directory = os.path.dirname(filename_plus_early_dirs) prefixed_directory = prepend_dir_prefix(directory, start_from=2) bytes_entries = constants_mod.Constants.b_entries entries_path = os.path.join(prefixed_directory, bytes_entries) basename = os.path.basename(converted_filename) if directory in self.repo.database_cache: database = self.repo.database_cache[directory] else: # we only need to create the directory if the database isn't in the cache - actually, less than that, # but this heuristic helps dirops_mod.my_mkdir(prefixed_directory) # This is our primary deviation from standard *dbm files: We save data compressed. # Drop the backend_open argument, and we have regular, uncompressed output for metadata, # suitable for use with gdbm or similar. See also where these are read, in repo_mod.py. backend_open = functools.partial(compressed_file_mod.Compressed_file, start_empty=True, zero_length_ok=True) database = db_mod.open(entries_path, mode, backend_open=backend_open) self.repo.database_cache[directory] = database return database, basename
[docs] def put_filename(self, filename, backshift_file): """ Write one Backshift_file to a database containing file metadata. Includes hashes and lengths of chunks where appropriate. """ database, basename = self._get_database_and_basename(filename, 'wb') database[basename] = backshift_file.as_string()
[docs] def get_filename(self, filename): """Read one file back from its containing directory, returning it as a Backshift_file.""" try: database, basename = self._get_database_and_basename(filename, 'rb') except (OSError, IOError) as extra: if extra.errno == errno.ENOENT: return None else: raise if basename in database: return backshift_file_mod.Backshift_file(self.repo, database[basename], filename) return None