Package translate :: Package storage :: Module bundleprojstore

[hide private]
[frames] | no frames]

# Source Code for Module translate.storage.bundleprojstore

      1  #!/usr/bin/env python
      2  # -*- coding: utf-8 -*-
      3  #
      4  # Copyright 2010 Zuza Software Foundation
      5  #
      6  # This file is part of the Translate Toolkit.
      7  #
      8  # This program is free software; you can redistribute it and/or modify
      9  # it under the terms of the GNU General Public License as published by
     10  # the Free Software Foundation; either version 2 of the License, or
     11  # (at your option) any later version.
     12  #
     13  # This program is distributed in the hope that it will be useful,
     14  # but WITHOUT ANY WARRANTY; without even the implied warranty of
     15  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     16  # GNU General Public License for more details.
     17  #
     18  # You should have received a copy of the GNU General Public License
     19  # along with this program; if not, see <http://www.gnu.org/licenses/>.
     20
     21  import os
     22  import tempfile
     23  from zipfile import ZipFile
     24
     25  from translate.storage.projstore import *
     26
     27  __all__ = ['BundleProjectStore', 'InvalidBundleError']
     28
     29
     30 -class InvalidBundleError(Exception):
     31      pass
     32
     33
     34 -class BundleProjectStore(ProjectStore):
     35      """Represents a translate project bundle (zip archive)."""
     36
     37      # INITIALIZERS #
     38 -    def __init__(self, fname):
     39          super(BundleProjectStore, self).__init__()
     40          self._tempfiles = {}
     41          if fname and os.path.isfile(fname):
     42              self.load(fname)
     43          else:
     44              self.zip = ZipFile(fname, 'w')
     45              self.save()
     46              self.zip.close()
     47              self.zip = ZipFile(fname, 'a')
     48
     49
     50      # CLASS METHODS #
     51      @classmethod
     52 -    def from_project(cls, proj, fname=None):
     53          if fname is None:
     54              fname = 'bundle.zip'
     55
     56          bundle = BundleProjectStore(fname)
     57          for fn in proj.sourcefiles:
     58              bundle.append_sourcefile(proj.get_file(fn))
     59          for fn in proj.transfiles:
     60              bundle.append_transfile(proj.get_file(fn))
     61          for fn in proj.targetfiles:
     62              bundle.append_targetfile(proj.get_file(fn))
     63          bundle.settings = proj.settings.copy()
     64          bundle.save()
     65          return bundle
     66
     67
     68      # METHODS #
     69 -    def append_file(self, afile, fname, ftype='trans', delete_orig=False):
     70          """Append the given file to the project with the given filename, marked
     71              to be of type C{ftype} ('src', 'trans', 'tgt').
     72
     73              @param delete_orig: If C{True}, as set by
     74                                  L{project.convert_forward()}, C{afile} is
     75                                  deleted after appending, if possible.
     76              NOTE: For this implementation, the appended file will be deleted
     77                    from disk if C{delete_orig} is C{True}."""
     78          if fname and fname in self.zip.namelist():
     79              raise ValueError("File already in bundle archive: %s" % (fname))
     80          if not fname and isinstance(afile, basestring) and afile in self.zip.namelist():
     81              raise ValueError("File already in bundle archive: %s" % (afile))
     82
     83          afile, fname = super(BundleProjectStore, self).append_file(afile, fname, ftype)
     84          self._zip_add(fname, afile)
     85
     86          if delete_orig and hasattr(afile, 'name') and afile.name not in self._tempfiles:
     87              try:
     88                  os.unlink(afile.name)
     89              except Exception:
     90                  pass
     91
     92          return self.get_file(fname), fname
     93
     94 -    def remove_file(self, fname, ftype=None):
     95          """Remove the file with the given project name from the project."""
     96          super(BundleProjectStore, self).remove_file(fname, ftype)
     97          self._zip_delete([fname])
     98          tempfiles = [tmpf for tmpf, prjf in self._tempfiles.iteritems() if prjf == fname]
     99          if tempfiles:
    100              for tmpf in tempfiles:
    101                  try:
    102                      os.unlink(tmpf)
    103                  except Exception:
    104                      pass
    105                  del self._tempfiles[tmpf]
    106
    107 -    def close(self):
    108          super(BundleProjectStore, self).close()
    109          self.cleanup()
    110          self.zip.close()
    111
    112 -    def cleanup(self):
    113          """Clean up our mess: remove temporary files."""
    114          for tempfname in self._tempfiles:
    115              if os.path.isfile(tempfname):
    116                  os.unlink(tempfname)
    117          self._tempfiles = {}
    118
    119 -    def get_file(self, fname):
    120          """Retrieve a project file (source, translation or target file) from the
    121              project archive."""
    122          retfile = None
    123          if fname in self._files or fname in self.zip.namelist():
    124              # Check if the file has not already been extracted to a temp file
    125              tempfname = [tfn for tfn in self._tempfiles if self._tempfiles[tfn] == fname]
    126              if tempfname and os.path.isfile(tempfname[0]):
    127                  tempfname = tempfname[0]
    128              else:
    129                  tempfname = ''
    130              if not tempfname:
    131                  # Extract the file to a temporary file
    132                  zfile = self.zip.open(fname)
    133                  tempfname = os.path.split(fname)[-1]
    134                  tempfd, tempfname = tempfile.mkstemp(suffix='_'+tempfname)
    135                  os.close(tempfd)
    136                  open(tempfname, 'w').write(zfile.read())
    137              retfile = open(tempfname)
    138              self._tempfiles[tempfname] = fname
    139
    140          if not retfile:
    141              raise FileNotInProjectError(fname)
    142          return retfile
    143
    144 -    def get_proj_filename(self, realfname):
    145          """Try and find a project file name for the given real file name."""
    146          try:
    147              fname = super(BundleProjectStore, self).get_proj_filename(realfname)
    148          except ValueError, ve:
    149              fname = None
    150          if fname:
    151              return fname
    152          if realfname in self._tempfiles:
    153              return self._tempfiles[realfname]
    154          raise ValueError('Real file not in project store: %s' % (realfname))
    155
    156 -    def load(self, zipname):
    157          """Load the bundle project from the zip file of the given name."""
    158          self.zip = ZipFile(zipname, mode='a')
    159          self._load_settings()
    160
    161          append_section = {
    162              'sources':    self._sourcefiles.append,
    163              'targets':    self._targetfiles.append,
    164              'transfiles': self._transfiles.append,
    165          }
    166          for section in ('sources', 'targets', 'transfiles'):
    167              if section in self.settings:
    168                  for fname in self.settings[section]:
    169                      append_section[section](fname)
    170                      self._files[fname] = None
    171
    172 -    def save(self, filename=None):
    173          """Save all project files to the bundle zip file."""
    174          self._update_from_tempfiles()
    175
    176          if filename:
    177              newzip = ZipFile(filename, 'w')
    178          else:
    179              newzip = self._create_temp_zipfile()
    180
    181          # Write project file for the new zip bundle
    182          newzip.writestr('project.xtp', self._generate_settings())
    183          # Copy project files from project to the new zip file
    184          project_files = self._sourcefiles + self._transfiles + self._targetfiles
    185          for fname in project_files:
    186              newzip.writestr(fname, self.get_file(fname).read())
    187          # Copy any extra (non-project) files from the current zip
    188          for fname in self.zip.namelist():
    189              if fname in project_files or fname == 'project.xtp':
    190                  continue
    191              newzip.writestr(fname, self.zip.read(fname))
    192
    193          self._replace_project_zip(newzip)
    194
    195 -    def update_file(self, pfname, infile):
    196          """Updates the file with the given project file name with the contents
    197              of C{infile}.
    198
    199              @returns: the results from L{self.append_file}."""
    200          if pfname not in self._files:
    201              raise FileNotInProjectError(pfname)
    202
    203          if pfname not in self.zip.namelist():
    204              return super(BundleProjectStore, self).update_file(pfname, infile)
    205
    206          self._zip_delete([pfname])
    207          self._zip_add(pfname, infile)
    208
    209 -    def _load_settings(self):
    210          """Grab the project.xtp file from the zip file and load it."""
    211          if 'project.xtp' not in self.zip.namelist():
    212              raise InvalidBundleError('Not a translate project bundle')
    213          super(BundleProjectStore, self)._load_settings(self.zip.open('project.xtp').read())
    214
    215 -    def _create_temp_zipfile(self):
    216          """Create a new zip file with a temporary file name (with mode 'w')."""
    217          newzipfd, newzipfname = tempfile.mkstemp(prefix='translate_bundle', suffix='.zip')
    218          os.close(newzipfd)
    219          return ZipFile(newzipfname, 'w')
    220
    221 -    def _replace_project_zip(self, zfile):
    222          """Replace the currently used zip file (C{self.zip}) with the given zip
    223              file. Basically, C{os.rename(zfile.filename, self.zip.filename)}."""
    224          if not zfile.fp.closed:
    225              zfile.close()
    226          if not self.zip.fp.closed:
    227              self.zip.close()
    228          os.rename(zfile.filename, self.zip.filename)
    229          self.zip = ZipFile(self.zip.filename, mode='a')
    230
    231 -    def _update_from_tempfiles(self):
    232          """Update project files from temporary files."""
    233          for tempfname in self._tempfiles:
    234              tmp = open(tempfname)
    235              self.update_file(self._tempfiles[tempfname], tmp)
    236              if not tmp.closed:
    237                  tmp.close()
    238
    239 -    def _zip_add(self, pfname, infile):
    240          """Add the contents of C{infile} to the zip with file name C{pfname}."""
    241          if hasattr(infile, 'seek'):
    242              infile.seek(0)
    243          self.zip.writestr(pfname, infile.read())
    244          self._files[pfname] = None # Clear the cached file object to force the
    245                                    # file to be read from the zip file.
    246
    247 -    def _zip_delete(self, fnames):
    248          """Delete the files with the given names from the zip file (C{self.zip})."""
    249          # Sanity checking
    250          if not isinstance(fnames, (list, tuple)):
    251              raise ValueError("fnames must be list or tuple: %s" % (fnames))
    252          if not self.zip:
    253              raise ValueError("No zip file to work on")
    254          zippedfiles = self.zip.namelist()
    255          for fn in fnames:
    256              if fn not in zippedfiles:
    257                  raise KeyError("File not in zip archive: %s" % (fn))
    258
    259          newzip = self._create_temp_zipfile()
    260          newzip.writestr('project.xtp', self._generate_settings())
    261
    262          for fname in zippedfiles:
    263              # Copy all files from self.zip that are not project.xtp (already
    264              # in the new zip file) or in fnames (they are to be removed, after
    265              # all.
    266              if fname in fnames or fname == 'project.xtp':
    267                  continue
    268              newzip.writestr(fname, self.zip.read(fname))
    269
    270          self._replace_project_zip(newzip)
    271