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