Release 8.
[jlew:xo-file-distro.git] / FileShare.activity / journalentrybundle.py
1 # Copyright (C) 2007, One Laptop Per Child
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 tempfile
19 import logging
20 import shutil
21 import zipfile
22 import stat
23
24 import simplejson as json
25
26 import dbus
27 from sugar.datastore import datastore
28 #from sugar.bundle.bundle import Bundle, MalformedBundleException, \
29 #    NotInstalledException, InvalidPathException
30
31 from bundle import Bundle, MalformedBundleException, \
32     NotInstalledException, InvalidPathException
33
34 RWXR_XR_X = stat.S_IRUSR|stat.S_IWUSR|stat.S_IXUSR|stat.S_IRGRP|stat.S_IXGRP|stat.S_IROTH|stat.S_IXOTH
35 RW_R__R__ = stat.S_IRUSR|stat.S_IWUSR|stat.S_IRGRP|stat.S_IROTH
36
37 # FIXME: We should not be doing this for every entry. Cannot get JSON to accept
38 # the dbus types?
39 def _sanitize_dbus_dict(dbus_dict):
40     base_dict = {}
41     for key in dbus_dict.keys():
42         k = str(key)
43         v = str(dbus_dict[key])
44         base_dict[k] = v
45     return base_dict
46
47 def from_jobject(jobject, bundle_path):
48     b = JournalEntryBundle(bundle_path)
49     b.set_metadata(jobject.get_metadata())
50     if jobject.get_file_path():
51         b.set_file(jobject.get_file_path())
52     return b
53
54 class JournalEntryBundle(Bundle):
55     """A Journal entry bundle
56
57     See http://wiki.laptop.org/go/Journal_entry_bundles for details
58     """
59
60     MIME_TYPE = 'application/vnd.olpc-journal-entry'
61
62     _zipped_extension = '.xoj'
63     _unzipped_extension = None
64     _infodir = None
65
66     def __init__(self, path):
67         Bundle.__init__(self, path)
68
69     def get_entry_id(self):
70         try:
71             zip_file = zipfile.ZipFile(self._path,'r')
72             file_names = zip_file.namelist()
73         except:
74             raise MalformedBundleException
75         if len(file_names) == 0:
76             raise MalformedBundleException('Empty zip file')
77
78         if file_names[0] == 'mimetype':
79             del file_names[0]
80
81         zip_root_dir = file_names[0].split('/')[0]
82         return zip_root_dir
83
84     def set_entry_id(self, entry_id):
85         try:
86             zip_file = zipfile.ZipFile(self._path,'a')
87         except:
88             zip_file = zipfile.ZipFile(self._path,'w')
89         file_names = zip_file.namelist()
90         if len(file_names) == 0:
91             base_dir = zipfile.ZipInfo(entry_id + '/')
92             zip_file.writestr(base_dir, '')
93         else:
94             raise MalformedBundleException("entry_id already set")
95
96     def install(self):
97         if os.environ.has_key('SUGAR_ACTIVITY_ROOT'):
98             install_dir = os.path.join(os.environ['SUGAR_ACTIVITY_ROOT'], 'instance')
99         else:
100             install_dir = tempfile.gettempdir()
101         uid = self.get_entry_id()
102         bundle_dir = os.path.join(install_dir, uid)
103         self._unzip(install_dir)
104         try:
105             metadata = self.get_metadata()
106             jobject = datastore.create()
107             try:
108                 for key, value in metadata.iteritems():
109                     jobject.metadata[key] = value
110
111                 preview = self.get_preview()
112                 if preview != '':
113                     jobject.metadata['preview'] = dbus.ByteArray(preview)
114                 jobject.metadata['uid'] = ''
115
116                 if jobject.metadata.has_key('mountpoint'):
117                     del jobject.metadata['mountpoint']
118
119                 os.chmod(bundle_dir, RWXR_XR_X)
120
121                 if( os.path.exists( os.path.join(bundle_dir, uid) ) ):
122                     jobject.file_path = os.path.join(bundle_dir, uid)
123                     os.chmod(jobject.file_path, RW_R__R__)
124
125                 datastore.write(jobject)
126             finally:
127                 jobject.destroy()
128         finally:
129             shutil.rmtree(bundle_dir, ignore_errors=True)
130
131     def set_preview(self, preview_data):
132         entry_id = self.get_entry_id()
133         preview_path = os.path.join(entry_id, 'preview', entry_id)
134         zip_file = zipfile.ZipFile(self._path,'a')
135         zip_file.writestr(preview_path, preview_data)
136         zip_file.close()
137
138     def get_preview(self):
139         entry_id = self.get_entry_id()
140         preview_path = os.path.join(entry_id, 'preview', entry_id)
141         zip_file = zipfile.ZipFile(self._path,'r')
142         try:
143             preview_data = zip_file.read(preview_path)
144         except:
145             preview_data = ''
146         zip_file.close()
147         return preview_data
148
149     def is_installed(self):
150         # These bundles can be reinstalled as many times as desired.
151         return False
152
153     def set_metadata(self, metadata):
154         metadata = _sanitize_dbus_dict(metadata)
155         try:
156             entry_id = self.get_entry_id()
157             #if entry_id != metadata[uid]:
158             #    raise InvalidPathException("metadata's entry id is different from my entry id")
159         except MalformedBundleException:
160             entry_id = metadata['activity_id']
161             if( entry_id == "" ):
162                 #If the entry_id is empty, (file not activity) then make an entryid
163                 import hashlib
164
165                 if metadata.has_key('timestamp'):
166                     entry_id = hashlib.sha1(metadata['timestamp']).hexdigest()
167                 else:
168                     import time
169                     entry_id = hashlib.sha1( str(time.time()) ).hexdigest()
170             self.set_entry_id(entry_id)
171
172         if 'preview' in metadata:
173             self.set_preview(str(metadata['preview']))
174             metadata['preview'] = entry_id
175
176         encoded_metadata = json.dumps(metadata)
177
178         zip_file = zipfile.ZipFile(self._path,'a')
179         zip_file.writestr(os.path.join(entry_id, "_metadata.json"), encoded_metadata)
180         zip_file.close()
181
182     def get_metadata(self):
183         entry_id = self.get_entry_id()
184         zip_file = zipfile.ZipFile(self._path,'r')
185         metadata_path = os.path.join(entry_id,"_metadata.json")
186         try:
187             encoded_data = zip_file.read(metadata_path)
188         except:
189             raise MalformedBundleException('Bundle must contain the file "_metadata.json".')
190         zip_file.close()
191
192         return json.loads(encoded_data)
193
194     def set_file(self, infile):
195         entry_id = self.get_entry_id()
196         file_path = os.path.join(entry_id, entry_id)
197         zip_file = zipfile.ZipFile(self._path, 'a')
198         zip_file.write(infile, file_path)
199         zip_file.close()
200
201     def get_file(self):
202         entry_id = self.get_entry_id()
203         file_path = os.path.join(entry_id, entry_id)
204         try:
205             zip_file = zipfile.ZipFile(self._path,'r')
206             file_data = zip_file.read(file_path)
207             zip_file.close()
208         except:
209             file_data = ''
210         return file_data