1 | | # Copyright (C) IBM Corporation 2008 |
2 | | # |
3 | | # This program is free software; you can redistribute it and/or modify |
4 | | # it under the terms of the GNU General Public License as published by |
5 | | # the Free Software Foundation; either version 2 of the License, or |
6 | | # (at your option) any later version. |
7 | | # |
8 | | # This program 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 |
11 | | # GNU General Public License for more details. |
12 | | # |
13 | | # You should have received a copy of the GNU General Public License |
14 | | # along with this program; if not, write to the Free Software |
15 | | # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
16 | | |
17 | | import os |
18 | | import gtk |
19 | | import zipfile |
20 | | import uuid |
21 | | import logging |
22 | | from glob import glob |
23 | | from gettext import gettext as _ |
24 | | |
25 | | from sugar.activity.activity import get_bundle_path, get_activity_root, get_bundle_name |
26 | | from sugar.datastore import datastore |
27 | | from sugar import activity |
28 | | |
29 | | from infoslicer.processing.NewtifulSoup import NewtifulStoneSoup \ |
30 | | as BeautifulStoneSoup |
31 | | import book |
32 | | |
33 | | logger = logging.getLogger('infoslicer') |
34 | | |
35 | | def publish(activity, force=False): |
36 | | if not [i for i in book.custom.index if i['ready']]: |
37 | | activity.notify_alert( |
38 | | _('Nothing to publish'), |
39 | | _('Mark arcticles from "Custom" panel and try again.')) |
40 | | return |
41 | | |
42 | | title = activity.metadata['title'] |
43 | | jobject = datastore.find({ |
44 | | 'activity_id': activity.get_id(), |
45 | | 'activity' : book.custom.uid})[0] or None |
46 | | |
47 | | logger.debug('publish: title=%s jobject=%s force=%s' \ |
48 | | % (title, jobject and jobject[0].metadata['activity'], force)) |
49 | | |
50 | | if jobject: |
51 | | if force: |
52 | | jobject = jobject[0] |
53 | | else: |
54 | | try: |
55 | | # check for 0.84 code |
56 | | from jarabe import config |
57 | | except: |
58 | | # 0.82 couldn't override .xol bundles |
59 | | activity.notify_alert( |
60 | | _('Bundle exists'), |
61 | | _('A bundle by "%s" name already exists. Please ' \ |
62 | | 'click "Erase" in the Journal. You can click ' \ |
63 | | '"Publish" again afterwards.') % \ |
64 | | jobject[0].metadata['title']) |
65 | | return |
66 | | |
67 | | activity.confirmation_alert( |
68 | | _('Overwrite existed bundle?'), |
69 | | _('A bundle for current object was already created. ' \ |
70 | | 'Click "OK" to overwrite it.'), |
71 | | publish, activity, True) |
72 | | jobject[0].destroy() |
73 | | return |
74 | | else: |
75 | | jobject = datastore.create() |
76 | | jobject.metadata['activity_id'] = activity.get_id() |
77 | | jobject.metadata['activity'] = book.custom.uid |
78 | | jobject.metadata['mime_type'] = 'application/vnd.olpc-content' |
79 | | jobject.metadata['description'] = \ |
80 | | 'This is a bundle containing articles on %s.\n' \ |
81 | | 'To view these articles, open the \'Browse\' Activity.\n' \ |
82 | | 'Go to \'Books\', and select \'%s\'.' % (title, title) |
83 | | |
84 | | book.custom.sync_article() |
85 | | book.custom.revision += 1 |
86 | | |
87 | | jobject.metadata['title'] = title |
88 | | _publish(title, jobject) |
89 | | jobject.destroy() |
90 | | |
91 | | book.custom.sync_index() |
92 | | |
93 | | """ |
94 | | @author: Matthew Bailey |
95 | | |
96 | | This class deals with the creation of content packages, comprised of articles from |
97 | | themes, with are zipped up and installed in the relevant OS specific location. From |
98 | | here they can be distributed to the consumers |
99 | | """ |
100 | | def _publish(title, jobject): |
101 | | zipfilename = '/tmp/infoslicer.xol' |
102 | | zip = zipfile.ZipFile(zipfilename, 'w') |
103 | | |
104 | | uid = book.custom.uid |
105 | | |
106 | | for i in glob(os.path.join(get_bundle_path(), 'Stylesheets', '*')): |
107 | | zip.write(i, os.path.join(uid, 'slicecontent', os.path.basename(i))) |
108 | | |
109 | | _info_file(zip, uid, title) |
110 | | _index_redirect(zip, uid) |
111 | | _dita_management(zip, uid, title) |
112 | | |
113 | | zip.close() |
114 | | |
115 | | jobject.set_file_path(zipfilename) |
116 | | datastore.write(jobject, transfer_ownership=True) |
117 | | |
118 | | def _dita_management(zip, uid, title): |
119 | | """ |
120 | | Creates a DITA map, and copies the requested articles and their associated images into the zipped directories |
121 | | """ |
122 | | map = [ '<?xml version=\'1.0\' encoding=\'utf-8\'?>',\ |
123 | | '<!DOCTYPE map PUBLIC "-//IBM//DTD DITA IBM Map//EN" ' \ |
124 | | '"ibm-map.dtd">',\ |
125 | | '<?xml-stylesheet type="text/xsl" href="mapstylesheet.xsl"?>',\ |
126 | | '<map title="%s">' % title ] |
127 | | |
128 | | images = {} |
129 | | |
130 | | for entry in book.custom.index: |
131 | | if not entry['ready']: |
132 | | continue |
133 | | |
134 | | atitle = entry['title'] |
135 | | auid = entry['uid'] |
136 | | |
137 | | content = BeautifulStoneSoup(book.custom._load(auid)) |
138 | | |
139 | | for image in content.findAll('image'): |
140 | | image_path = book.wiki.root + '/' + image['href'] |
141 | | image_name = os.path.basename(image_path) |
142 | | image_ext = os.path.splitext(image_name)[1] |
143 | | |
144 | | image_num = images.get(image_name, len(images)) |
145 | | images[image_name] = image_num |
146 | | image_name = ('%08d%s' % (image_num, image_ext)).encode('CP437') |
147 | | |
148 | | zip.write(image_path, os.path.join(uid, 'slicecontent', image_name)) |
149 | | image['href'] = image_name |
150 | | |
151 | | content.insert(1, '<?xml-stylesheet type="text/xsl" ' \ |
152 | | 'href="ditastylesheet.xsl"?>') |
153 | | zipstr(zip, os.path.join(uid, 'slicecontent', '%s.dita' % auid), |
154 | | content.prettify()) |
155 | | |
156 | | map.append('<topicref href="%s.dita" navtitle="%s">' % (auid, atitle)) |
157 | | map.append('</topicref>') |
158 | | |
159 | | map.append('</map>') |
160 | | zipstr(zip, os.path.join(uid, 'slicecontent', 'librarymap.ditamap'), |
161 | | "\n".join(map)) |
162 | | |
163 | | def _index_redirect(zip, uid): |
164 | | """ |
165 | | Creates the redirecting index.html |
166 | | """ |
167 | | redirect_loc = 'slicecontent/librarymap.ditamap' |
168 | | |
169 | | html = ['<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">',\ |
170 | | '<html>',\ |
171 | | '<head>',\ |
172 | | '<title>Redirecting to index</title>',\ |
173 | | '<meta http-equiv="REFRESH" content="0;url=%s">' % redirect_loc,\ |
174 | | '</head>',\ |
175 | | '<body>',\ |
176 | | '</body>',\ |
177 | | '</html>'] |
178 | | |
179 | | zipstr(zip, os.path.join(uid, 'index.html'), "\n".join(html)) |
180 | | |
181 | | def _info_file(zip, uid, title): |
182 | | """ |
183 | | Creates the library.info file |
184 | | """ |
185 | | libraryfile = ['[Library]',\ |
186 | | 'name = %s' % title,\ |
187 | | 'bundle_class = %s' % uid,\ |
188 | | 'global_name = info.slice.%s' % title,\ |
189 | | 'long_name = %s' % title,\ |
190 | | 'library_version = %d' % book.custom.revision,\ |
191 | | 'host_version = 1',\ |
192 | | 'l10n = false',\ |
193 | | 'locale = en',\ |
194 | | 'category = books',\ |
195 | | 'subcategory = slice',\ |
196 | | 'icon = book.png',\ |
197 | | 'activity = Web',\ |
198 | | 'activity_start = index.html'] |
199 | | |
200 | | zipstr(zip, os.path.join(uid, 'library', 'library.info'), |
201 | | "\n".join(libraryfile)) |
202 | | |
203 | | # XXX setup mode_t for files written by writestr() |
204 | | def zipstr(zip, arcname, str): |
205 | | import copy |
206 | | zipinfo = copy.copy(zip.infolist()[0]) |
207 | | zipinfo.filename = arcname |
208 | | zip.writestr(zipinfo, str) |
| 1 | # Copyright (C) IBM Corporation 2008 |
| 2 | # |
| 3 | # This program is free software; you can redistribute it and/or modify |
| 4 | # it under the terms of the GNU General Public License as published by |
| 5 | # the Free Software Foundation; either version 2 of the License, or |
| 6 | # (at your option) any later version. |
| 7 | # |
| 8 | # This program 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 |
| 11 | # GNU General Public License for more details. |
| 12 | # |
| 13 | # You should have received a copy of the GNU General Public License |
| 14 | # along with this program; if not, write to the Free Software |
| 15 | # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
| 16 | |
| 17 | import os |
| 18 | import gtk |
| 19 | import zipfile |
| 20 | import uuid |
| 21 | import logging |
| 22 | from glob import glob |
| 23 | from gettext import gettext as _ |
| 24 | |
| 25 | from sugar.activity.activity import get_bundle_path, get_activity_root, get_bundle_name |
| 26 | from sugar.datastore import datastore |
| 27 | from sugar import activity |
| 28 | |
| 29 | from infoslicer.processing.NewtifulSoup import NewtifulStoneSoup \ |
| 30 | as BeautifulStoneSoup |
| 31 | import book |
| 32 | |
| 33 | logger = logging.getLogger('infoslicer') |
| 34 | |
| 35 | def publish(activity, force=False): |
| 36 | if not [i for i in book.custom.index if i['ready']]: |
| 37 | activity.notify_alert( |
| 38 | _('Nothing to publish'), |
| 39 | _('Mark arcticles from "Custom" panel and try again.')) |
| 40 | return |
| 41 | |
| 42 | title = activity.metadata['title'] |
| 43 | jobject = datastore.find({ |
| 44 | 'activity_id': activity.get_id(), |
| 45 | 'activity' : book.custom.uid})[0] or None |
| 46 | |
| 47 | logger.debug('publish: title=%s jobject=%s force=%s' \ |
| 48 | % (title, jobject and jobject[0].metadata['activity'], force)) |
| 49 | |
| 50 | if jobject: |
| 51 | if force: |
| 52 | jobject = jobject[0] |
| 53 | else: |
| 54 | try: |
| 55 | # check for 0.84 code |
| 56 | from jarabe import config |
| 57 | except: |
| 58 | # 0.82 couldn't override .xol bundles |
| 59 | activity.notify_alert( |
| 60 | _('Bundle exists'), |
| 61 | _('A bundle by "%s" name already exists. Please ' \ |
| 62 | 'click "Erase" in the Journal. You can click ' \ |
| 63 | '"Publish" again afterwards.') % \ |
| 64 | jobject[0].metadata['title']) |
| 65 | return |
| 66 | |
| 67 | activity.confirmation_alert( |
| 68 | _('Overwrite existed bundle?'), |
| 69 | _('A bundle for current object was already created. ' |
| 70 | 'Click "OK" to overwrite it.'), |
| 71 | publish, activity, True) |
| 72 | jobject[0].destroy() |
| 73 | return |
| 74 | else: |
| 75 | jobject = datastore.create() |
| 76 | jobject.metadata['activity_id'] = activity.get_id() |
| 77 | jobject.metadata['activity'] = book.custom.uid |
| 78 | jobject.metadata['mime_type'] = 'application/vnd.olpc-content' |
| 79 | jobject.metadata['description'] = \ |
| 80 | 'This is a bundle containing articles on %s.\n' \ |
| 81 | 'To view these articles, open the \'Browse\' Activity.\n' \ |
| 82 | 'Go to \'Books\', and select \'%s\'.' % (title, title) |
| 83 | |
| 84 | book.custom.sync_article() |
| 85 | book.custom.revision += 1 |
| 86 | |
| 87 | jobject.metadata['title'] = title |
| 88 | _publish(title, jobject) |
| 89 | jobject.destroy() |
| 90 | |
| 91 | book.custom.sync_index() |
| 92 | |
| 93 | """ |
| 94 | @author: Matthew Bailey |
| 95 | |
| 96 | This class deals with the creation of content packages, comprised of articles from |
| 97 | themes, with are zipped up and installed in the relevant OS specific location. From |
| 98 | here they can be distributed to the consumers |
| 99 | """ |
| 100 | def _publish(title, jobject): |
| 101 | zipfilename = '/tmp/infoslicer.xol' |
| 102 | zip = zipfile.ZipFile(zipfilename, 'w') |
| 103 | |
| 104 | uid = book.custom.uid |
| 105 | |
| 106 | for i in glob(os.path.join(get_bundle_path(), 'Stylesheets', '*')): |
| 107 | zip.write(i, os.path.join(uid, 'slicecontent', os.path.basename(i))) |
| 108 | |
| 109 | _info_file(zip, uid, title) |
| 110 | _index_redirect(zip, uid) |
| 111 | _dita_management(zip, uid, title) |
| 112 | |
| 113 | zip.close() |
| 114 | |
| 115 | jobject.set_file_path(zipfilename) |
| 116 | datastore.write(jobject, transfer_ownership=True) |
| 117 | |
| 118 | def _dita_management(zip, uid, title): |
| 119 | """ |
| 120 | Creates a DITA map, and copies the requested articles and their associated images into the zipped directories |
| 121 | """ |
| 122 | map = [ '<?xml version=\'1.0\' encoding=\'utf-8\'?>',\ |
| 123 | '<!DOCTYPE map PUBLIC "-//IBM//DTD DITA IBM Map//EN" ' \ |
| 124 | '"ibm-map.dtd">',\ |
| 125 | '<?xml-stylesheet type="text/xsl" href="mapstylesheet.xsl"?>',\ |
| 126 | '<map title="%s">' % title ] |
| 127 | |
| 128 | images = {} |
| 129 | |
| 130 | for entry in book.custom.index: |
| 131 | if not entry['ready']: |
| 132 | continue |
| 133 | |
| 134 | atitle = entry['title'] |
| 135 | auid = entry['uid'] |
| 136 | |
| 137 | content = BeautifulStoneSoup(book.custom._load(auid)) |
| 138 | |
| 139 | for image in content.findAll('image'): |
| 140 | image_path = book.wiki.root + '/' + image['href'] |
| 141 | image_name = os.path.basename(image_path) |
| 142 | image_ext = os.path.splitext(image_name)[1] |
| 143 | |
| 144 | image_num = images.get(image_name, len(images)) |
| 145 | images[image_name] = image_num |
| 146 | image_name = ('%08d%s' % (image_num, image_ext)).encode('CP437') |
| 147 | |
| 148 | zip.write(image_path, os.path.join(uid, 'slicecontent', image_name)) |
| 149 | image['href'] = image_name |
| 150 | |
| 151 | content.insert(1, '<?xml-stylesheet type="text/xsl" ' \ |
| 152 | 'href="ditastylesheet.xsl"?>') |
| 153 | zipstr(zip, os.path.join(uid, 'slicecontent', '%s.dita' % auid), |
| 154 | content.prettify()) |
| 155 | |
| 156 | map.append('<topicref href="%s.dita" navtitle="%s">' % (auid, atitle)) |
| 157 | map.append('</topicref>') |
| 158 | |
| 159 | map.append('</map>') |
| 160 | zipstr(zip, os.path.join(uid, 'slicecontent', 'librarymap.ditamap'), |
| 161 | "\n".join(map)) |
| 162 | |
| 163 | def _index_redirect(zip, uid): |
| 164 | """ |
| 165 | Creates the redirecting index.html |
| 166 | """ |
| 167 | redirect_loc = 'slicecontent/librarymap.ditamap' |
| 168 | |
| 169 | html = ['<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">',\ |
| 170 | '<html>',\ |
| 171 | '<head>',\ |
| 172 | '<title>Redirecting to index</title>',\ |
| 173 | '<meta http-equiv="REFRESH" content="0;url=%s">' % redirect_loc,\ |
| 174 | '</head>',\ |
| 175 | '<body>',\ |
| 176 | '</body>',\ |
| 177 | '</html>'] |
| 178 | |
| 179 | zipstr(zip, os.path.join(uid, 'index.html'), "\n".join(html)) |
| 180 | |
| 181 | def _info_file(zip, uid, title): |
| 182 | """ |
| 183 | Creates the library.info file |
| 184 | """ |
| 185 | libraryfile = ['[Library]',\ |
| 186 | 'name = %s' % title,\ |
| 187 | 'bundle_class = %s' % uid,\ |
| 188 | 'global_name = info.slice.%s' % title,\ |
| 189 | 'long_name = %s' % title,\ |
| 190 | 'library_version = %d' % book.custom.revision,\ |
| 191 | 'host_version = 1',\ |
| 192 | 'l10n = false',\ |
| 193 | 'locale = en',\ |
| 194 | 'category = books',\ |
| 195 | 'subcategory = slice',\ |
| 196 | 'icon = book.png',\ |
| 197 | 'activity = Web',\ |
| 198 | 'activity_start = index.html'] |
| 199 | |
| 200 | zipstr(zip, os.path.join(uid, 'library', 'library.info'), |
| 201 | "\n".join(libraryfile)) |
| 202 | |
| 203 | # XXX setup mode_t for files written by writestr() |
| 204 | def zipstr(zip, arcname, str): |
| 205 | import copy |
| 206 | zipinfo = copy.copy(zip.infolist()[0]) |
| 207 | zipinfo.filename = arcname |
| 208 | zip.writestr(zipinfo, str) |