1 # Copyright (C) 2007, Red Hat, Inc.
3 # This library is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU Lesser General Public
5 # License as published by the Free Software Foundation; either
6 # version 2 of the License, or (at your option) any later version.
8 # This library is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 # Lesser General Public License for more details.
13 # You should have received a copy of the GNU Lesser General Public
14 # License along with this library; if not, write to the
15 # Free Software Foundation, Inc., 59 Temple Place - Suite 330,
16 # Boston, MA 02111-1307, USA.
18 """Sugar bundle file handler"""
24 class AlreadyInstalledException(Exception): pass
25 class NotInstalledException(Exception): pass
26 class InvalidPathException(Exception): pass
27 class ZipExtractException(Exception): pass
28 class RegistrationException(Exception): pass
29 class MalformedBundleException(Exception): pass
32 """A Sugar activity, content module, etc.
34 The bundle itself must be a zip file. If no zip file is provided, it will
35 be initialized once components have been added.
37 This is an abstract base class. See ActivityBundle and
38 ContentBundle for more details on those bundle types.
40 def __init__(self, path):
43 if not os.path.exists(self._path):
44 z = zipfile.ZipFile(self._path, 'w')
45 z._didModify = True #workaround for python-2.5 bug
48 # manifest = self._get_file(self._infodir + '/contents')
49 # if manifest is None:
50 # raise MalformedBundleException('No manifest file')
52 # signature = self._get_file(self._infodir + '/contents.sig')
53 # if signature is None:
54 # raise MalformedBundleException('No signature file')
56 def _check_zip_bundle(self):
58 zip_file = zipfile.ZipFile(self._path,'r')
59 file_names = zip_file.namelist()
61 raise MalformedBundleException
63 if len(file_names) == 0:
64 raise MalformedBundleException('Empty zip file')
66 if file_names[0] == 'mimetype':
69 self._zip_root_dir = file_names[0].split('/')[0]
70 if self._unzipped_extension is not None:
71 (name, ext) = os.path.splitext(self._zip_root_dir)
72 if ext != self._unzipped_extension:
73 raise MalformedBundleException(
74 'All files in the bundle must be inside a single ' +
75 'directory whose name ends with "%s"' %
76 self._unzipped_extension)
78 for file_name in file_names:
79 if not file_name.startswith(self._zip_root_dir):
80 raise MalformedBundleException(
81 'All files in the bundle must be inside a single ' +
82 'top-level directory')
84 def _get_file(self, filename):
87 zip_file = zipfile.ZipFile(self._path)
88 path = os.path.join(self._zip_root_dir, filename)
90 data = zip_file.read(path)
91 file = StringIO.StringIO(data)
100 """Get the bundle path."""
103 def _unzip(self, install_dir):
104 if not os.path.isdir(install_dir):
105 os.mkdir(install_dir, 0775)
107 # zipfile provides API that in theory would let us do this
108 # correctly by hand, but handling all the oddities of
109 # Windows/UNIX mappings, extension attributes, deprecated
110 # features, etc makes it impractical.
111 # FIXME: use manifest
112 if os.spawnlp(os.P_WAIT, 'unzip', 'unzip', '-o', self._path,
113 '-x', 'mimetype', '-d', install_dir):
114 raise ZipExtractException
116 def _add_files(self, files_path):
117 # FIXME: maintain manifest
119 ziproot = self._get_ziproot()
120 except MalformedBundleException:
122 zip = zipfile.ZipFile(bundle_path, 'w', zipfile.ZIP_DEFLATED)
123 for root, dirs, files in os.walk(files_path):
125 zip.write(name, os.path.join(ziproot, name))
128 def _get_ziproot(self):
129 zip_file = zipfile.ZipFile(self._path,'r')
130 file_names = zip_file.namelist()
131 if len(file_names) == 0:
132 raise MalformedBundleException('Empty zip file')
134 if file_names[0] == 'mimetype':
137 zip_root_dir = file_names[0].split('/')[0]
140 def _uninstall(self, install_path):
141 if not os.path.isdir(install_path):
142 raise InvalidPathException
143 if self._unzipped_extension is not None:
144 ext = os.path.splitext(install_path)[1]
145 if ext != self._unzipped_extension:
146 raise InvalidPathException
148 for root, dirs, files in os.walk(install_path, topdown=False):
150 os.remove(os.path.join(root, name))
152 os.rmdir(os.path.join(root, name))
153 os.rmdir(install_path)