# (Be in -*- python -*- mode.) # # ==================================================================== # Copyright (c) 2000-2006 CollabNet. All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://subversion.tigris.org/license-1.html. # If newer versions of this license are posted there, you may use a # newer version instead, at your option. # # This software consists of voluntary contributions made by many # individuals. For exact contribution history, see the revision # history and logs, available at http://cvs2svn.tigris.org/. # ==================================================================== """This module manages the artifacts produced by conversion passes.""" import os from cvs2svn_lib.boolean import * from cvs2svn_lib.set_support import * from cvs2svn_lib.context import Ctx from cvs2svn_lib.log import Log class Artifact: """An artifact that can be created, used across cvs2svn passes, then cleaned up.""" def __init__(self, name): self.name = name # A set of passes that need this artifact. This field is # maintained by ArtifactManager. self._passes_needed = set() def cleanup(self): """This artifact is no longer needed; clean it up.""" pass def __str__(self): return self.name class TempFileArtifact(Artifact): """A temporary file that can be used across cvs2svn passes.""" def __init__(self, basename): Artifact.__init__(self, basename) self.filename = Ctx().get_temp_filename(basename) def cleanup(self): Log().verbose("Deleting", self.filename) os.unlink(self.filename) class ArtifactNotActiveError(Exception): """An artifact was requested when no passes that have registered that they need it are active.""" def __init__(self, artifact_name): Exception.__init__( self, 'Artifact %s is not currently active' % artifact_name) class ArtifactManager: """Manager artifacts that are created by one pass but needed by others. This class is responsible for cleaning up artifacts once they are no longer needed. The trick is that cvs2svn can be run pass by pass, so not all passes might be executed during a specific program run. To use this class: - Call register_artifact() or register_temp_file() for all possible artifacts (even those that should have been created by previous cvs2svn runs). - Call register_artifact_needed() or register_temp_file_needed() for any artifact that are needed by any pass (even those passes that won't be executed during this cvs2svn run). Then, in pass order: - Call pass_skipped() for any passes that were already executed during a previous cvs2svn run. - Call pass_started() when a pass is about to start execution. - If a pass that has been started will be continued during the next program run, then call pass_continued(). - If a pass that has been started finishes execution, call pass_done(), to allow any artifacts that won't be needed anymore to be cleaned up. - Call pass_deferred() for any passes that have been deferred to a future cvs2svn run. Finally: - Call check_clean() to verify that all artifacts have been accounted for.""" def __init__(self): # A map { artifact_name : artifact } of known artifacts. self._artifacts = { } # A map { pass : set_of_artifacts }, where set_of_artifacts is a # set of artifacts needed by the pass. self._pass_needs = { } # A set of passes that are currently being executed. self._active_passes = set() def register_artifact(self, artifact, which_pass): """Register a new ARTIFACT for management by this class. WHICH_PASS is the pass that creates ARTIFACT, and is also assumed to need it. It is an error to registier the same artifact more than once.""" assert artifact.name not in self._artifacts self._artifacts[artifact.name] = artifact self.register_artifact_needed(artifact.name, which_pass) def register_temp_file(self, basename, which_pass): """Register a temporary file with base name BASENAME as an artifact. Return the filename of the temporary file.""" artifact = TempFileArtifact(basename) self.register_artifact(artifact, which_pass) return artifact.filename def get_artifact(self, artifact_name): """Return the artifact with the specified name. If the artifact does not currently exist, raise a KeyError. If it is not registered as being needed by one of the active passes, raise an ArtifactNotActiveError.""" artifact = self._artifacts[artifact_name] for active_pass in self._active_passes: if artifact in self._pass_needs[active_pass]: # OK break else: raise ArtifactNotActiveError(artifact_name) return artifact def get_temp_file(self, basename): """Return the filename of the temporary file with the specified BASENAME. If the temporary file is not an existing, registered TempFileArtifact, raise a KeyError.""" return self.get_artifact(basename).filename def register_artifact_needed(self, artifact_name, which_pass): """Register that WHICH_PASS needs the artifact named ARTIFACT_NAME. An artifact with this name must already have been registered.""" artifact = self._artifacts[artifact_name] artifact._passes_needed.add(which_pass) if which_pass in self._pass_needs: self._pass_needs[which_pass].add(artifact) else: self._pass_needs[which_pass] = set([artifact,]) def register_temp_file_needed(self, basename, which_pass): """Register that the temporary file with base name BASENAME is needed by WHICH_PASS.""" self.register_artifact_needed(basename, which_pass) def _unregister_artifacts(self, which_pass): """Unregister any artifacts that were needed for WHICH_PASS. Return a list of artifacts that are no longer needed at all.""" try: artifacts = list(self._pass_needs[which_pass]) except KeyError: # No artifacts were needed for that pass: return [] del self._pass_needs[which_pass] unneeded_artifacts = [] for artifact in artifacts: artifact._passes_needed.remove(which_pass) if not artifact._passes_needed: unneeded_artifacts.append(artifact) return unneeded_artifacts def pass_skipped(self, which_pass): """WHICH_PASS was executed during a previous cvs2svn run. Its artifacts were created then, and any artifacts that would normally be cleaned up after this pass have already been cleaned up.""" self._unregister_artifacts(which_pass) def pass_started(self, which_pass): """WHICH_PASS is starting.""" self._active_passes.add(which_pass) def pass_continued(self, which_pass): """WHICH_PASS, which has already been started, will be continued during the next program run. Unregister any artifacts that would be cleaned up at the end of WHICH_PASS without actually cleaning them up.""" self._active_passes.remove(which_pass) self._unregister_artifacts(which_pass) def pass_done(self, which_pass): """WHICH_PASS is done. Clean up all artifacts that are no longer needed.""" self._active_passes.remove(which_pass) artifacts = self._unregister_artifacts(which_pass) if not Ctx().skip_cleanup: for artifact in artifacts: artifact.cleanup() def pass_deferred(self, which_pass): """WHICH_PASS is being deferred until a future cvs2svn run. Unregister any artifacts that would be cleaned up during WHICH_PASS.""" self._unregister_artifacts(which_pass) def check_clean(self): """All passes have been processed. Output a warning messages if all artifacts have not been accounted for. (This is mainly a consistency check, that no artifacts were registered under nonexistent passes.)""" unclean_artifact_names = [ artifact.name for artifact in self._artifacts.values() if artifact._passes_needed] if unclean_artifact_names: Log().warn( 'INTERNAL: The following artifacts were not cleaned up:\n %s\n' % ('\n '.join(unclean_artifact_names))) # The default ArtifactManager instance: artifact_manager = ArtifactManager()