Release 8.
[jlew:xo-file-distro.git] / FileShare.activity / bundle.py
1 # Copyright (C) 2007, Red Hat, Inc.
2 #
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.
7 #
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.
12 #
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.
17
18 """Sugar bundle file handler"""
19
20 import os
21 import StringIO
22 import zipfile
23
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
30
31 class Bundle:
32     """A Sugar activity, content module, etc.
33     
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.
36     
37     This is an abstract base class. See ActivityBundle and
38     ContentBundle for more details on those bundle types.
39     """
40     def __init__(self, path):
41         self._path = path
42         
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
46             z.close()
47
48         # manifest = self._get_file(self._infodir + '/contents')
49         # if manifest is None:
50         #     raise MalformedBundleException('No manifest file')
51         # 
52         # signature = self._get_file(self._infodir + '/contents.sig')
53         # if signature is None:
54         #     raise MalformedBundleException('No signature file')
55
56     def _check_zip_bundle(self):
57         try:
58             zip_file = zipfile.ZipFile(self._path,'r')
59             file_names = zip_file.namelist()
60         except:
61             raise MalformedBundleException
62         
63         if len(file_names) == 0:
64             raise MalformedBundleException('Empty zip file')
65
66         if file_names[0] == 'mimetype':
67             del file_names[0]
68
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)
77
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')
83
84     def _get_file(self, filename):
85         file = None
86
87         zip_file = zipfile.ZipFile(self._path)
88         path = os.path.join(self._zip_root_dir, filename)
89         try:
90             data = zip_file.read(path)
91             file = StringIO.StringIO(data)
92         except KeyError:
93             # == "file not found"
94             pass
95         zip_file.close()
96
97         return file
98
99     def get_path(self):
100         """Get the bundle path."""
101         return self._path
102
103     def _unzip(self, install_dir):
104         if not os.path.isdir(install_dir):
105             os.mkdir(install_dir, 0775)
106
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
115
116     def _add_files(self, files_path):
117         # FIXME: maintain manifest
118         try:
119             ziproot = self._get_ziproot()
120         except MalformedBundleException:
121             ziproot = files_path
122         zip = zipfile.ZipFile(bundle_path, 'w', zipfile.ZIP_DEFLATED)
123         for root, dirs, files in os.walk(files_path):
124             for name in files:
125                 zip.write(name, os.path.join(ziproot, name))
126         zip.close()
127     
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')
133
134         if file_names[0] == 'mimetype':
135             del file_names[0]
136
137         zip_root_dir = file_names[0].split('/')[0]
138         return zip_root_dir
139
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
147
148         for root, dirs, files in os.walk(install_path, topdown=False):
149             for name in files:
150                 os.remove(os.path.join(root, name))
151             for name in dirs:
152                 os.rmdir(os.path.join(root, name))
153         os.rmdir(install_path)