[Sugar-devel] [PATCH] Add support for JEBs containing multiple entries

Sascha Silbe sascha-pgp at silbe.org
Sun Jul 4 17:11:24 EDT 2010


By allowing Journal Entry Bundles (JEBs) to carry more than one entry,
we can use them to back up an entire data store.

Signed-off-by: Sascha Silbe <sascha-pgp at silbe.org>

---
 src/jarabe/journal/journalentrybundle.py |  139 ++++++++++++++++++++----------
 src/jarabe/model/bundleregistry.py       |    6 +-
 2 files changed, 97 insertions(+), 48 deletions(-)

Since even after repeating asking I couldn't get hold of any existing JEB,
I could only test against the JEBs created by my backup implementation.


diff --git a/src/jarabe/journal/journalentrybundle.py b/src/jarabe/journal/journalentrybundle.py
index 41777c7..a3735ca 100644
--- a/src/jarabe/journal/journalentrybundle.py
+++ b/src/jarabe/journal/journalentrybundle.py
@@ -14,24 +14,30 @@
 # along with this program; if not, write to the Free Software
 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 
+import logging
 import os
 import tempfile
-import shutil
 
-import simplejson
 import dbus
 
-from sugar.bundle.bundle import Bundle, MalformedBundleException
+try:
+    import json
+except ImportError:
+    import simplejson as json
 
+from sugar.bundle.bundle import Bundle, MalformedBundleException
 from jarabe.journal import model
 
+
 class JournalEntryBundle(Bundle):
     """A Journal entry bundle
 
     See http://wiki.laptop.org/go/Journal_entry_bundles for details
     """
+    # TODO: migrate to SL wiki
 
     MIME_TYPE = 'application/vnd.olpc-journal-entry'
+    _METADATA_JSON_NAME = '_metadata.json'
 
     _zipped_extension = '.xoj'
     _unzipped_extension = None
@@ -40,55 +46,98 @@ class JournalEntryBundle(Bundle):
     def __init__(self, path):
         Bundle.__init__(self, path)
 
-    def install(self, uid=''):
-        if os.environ.has_key('SUGAR_ACTIVITY_ROOT'):
-            install_dir = os.path.join(os.environ['SUGAR_ACTIVITY_ROOT'],
-                                       'data')
-        else:
-            install_dir = tempfile.gettempdir()
-        bundle_dir = os.path.join(install_dir, self._zip_root_dir)
-        temp_uid = self._zip_root_dir
-        self._unzip(install_dir)
-        try:
-            metadata = self._read_metadata(bundle_dir)
-            metadata['uid'] = uid
-
-            preview = self._read_preview(temp_uid, bundle_dir)
-            if preview is not None:
-                metadata['preview'] = dbus.ByteArray(preview)
-
-            file_path = os.path.join(bundle_dir, temp_uid)
-            model.write(metadata, file_path)
-        finally:
-            shutil.rmtree(bundle_dir, ignore_errors=True)
+    def _check_zip_bundle(self):
+        # potentially expensive, but avoids trouble during unpacking
+        if self._zip_file.testzip() is not None:
+            raise MalformedBundleException('Corrupt zip file')
+
+        file_names = self._zip_file.namelist()
+        if len(file_names) == 0:
+            raise MalformedBundleException('Empty zip file')
+
+        metadata_seen = False
+        for name in file_names:
+            for part in name.split('/'):
+                if part.startswith('.'):
+                    raise MalformedBundleException(
+                        'Path component starts with dot: %r', name)
+
+            if name.split('/')[-1] == self._METADATA_JSON_NAME:
+                metadata_seen = True
+
+        if not metadata_seen:
+            raise MalformedBundleException('No metadata file found')
+
+    def install(self):
+        for object_id, file_paths in self._get_directories().items():
+            if len(object_id) < 36:
+                logging.warning('Ignoring unknown directory %r', object_id)
+                continue
+
+            if self._METADATA_JSON_NAME not in file_paths:
+                logging.warning('Ignoring directory %r without %s',
+                    object_id, self._METADATA_JSON_NAME)
+                continue
+
+            try:
+                self._install_entry(object_id, file_paths)
+            except Exception:
+                logging.exception('Error installing Journal entry %r:',
+                    object_id)
+
+    def _install_entry(self, object_id, file_paths):
+        file_paths.remove(self._METADATA_JSON_NAME)
+        metadata = self._read_metadata(object_id)
+
+        data_file_name = ''
+        if object_id in file_paths:
+            file_paths.remove(object_id)
+            data_file_name = self._read_data(object_id)
+
+        for path in file_paths:
+            components = path.split('/')
+            if len(components) != 2 or components[1] != object_id:
+                logging.warning('Ignoring unknown file %r', path)
+
+            name = components[0]
+            value = self._zip_file.read(os.path.join(object_id, path))
+            metadata[name] = dbus.ByteArray(value)
+
+        model.write(metadata, data_file_name, transfer_ownership=True)
 
     def get_bundle_id(self):
         return None
 
-    def _read_metadata(self, bundle_dir):
-        metadata_path = os.path.join(bundle_dir, '_metadata.json')
-        if not os.path.exists(metadata_path):
-            raise MalformedBundleException(
-                    'Bundle must contain the file "_metadata.json"')
-        f = open(metadata_path, 'r')
+    def _read_data(self, object_id):
+        data_fd, data_file_name = tempfile.mkstemp(prefix='JEB')
+        data_file = os.fdopen(data_fd, 'w')
         try:
-            json_data = f.read()
+            # TODO: handle large files better
+            # TODO: predict disk-full
+            data_file.write(self._zip_file.read(
+                os.path.join(object_id, object_id)))
+            return data_file_name
         finally:
-            f.close()
-        return simplejson.loads(json_data)
-
-    def _read_preview(self, uid, bundle_dir):
-        preview_path = os.path.join(bundle_dir, 'preview', uid)
-        if not os.path.exists(preview_path):
-            return ''
-        f = open(preview_path, 'r')
-        try:
-            preview_data = f.read()
-        finally:
-            f.close()
-        return preview_data
+            data_file.close()
+
+    def _read_metadata(self, object_id):
+        metadata_path = os.path.join(object_id, self._METADATA_JSON_NAME)
+        json_data = self._zip_file.read(metadata_path)
+        return json.loads(json_data)
+
+    def _get_directories(self):
+        """Return the names of all top-level directories and their contents.
+        """
+        contents = {}
+        for path in self._zip_file.namelist():
+            if path.endswith('/'):
+                continue
+
+            directory, file_name = path.lstrip('/').split('/', 1)
+            contents.setdefault(directory, []).append(file_name)
+
+        return contents
 
     def is_installed(self):
         # These bundles can be reinstalled as many times as desired.
         return False
-
diff --git a/src/jarabe/model/bundleregistry.py b/src/jarabe/model/bundleregistry.py
index 86a2738..f4c7275 100644
--- a/src/jarabe/model/bundleregistry.py
+++ b/src/jarabe/model/bundleregistry.py
@@ -164,7 +164,7 @@ class BundleRegistry(gobject.GObject):
             if bundle.get_bundle_id() == bundle_id:
                 return bundle
         return None
-    
+
     def __iter__(self):
         return self._bundles.__iter__()
 
@@ -364,7 +364,7 @@ class BundleRegistry(gobject.GObject):
 
         install_dir = env.get_user_activities_path()
         if isinstance(bundle, JournalEntryBundle):
-            install_path = bundle.install(uid)
+            install_path = bundle.install()
         else:
             install_path = bundle.install(install_dir)
 
@@ -376,7 +376,7 @@ class BundleRegistry(gobject.GObject):
         elif not self.add_bundle(install_path):
             raise RegistrationException
 
-    def uninstall(self, bundle, force=False):        
+    def uninstall(self, bundle, force=False):
         # TODO treat ContentBundle in special way
         # needs rethinking while fixing ContentBundle support
         if isinstance(bundle, ContentBundle) or \
-- 
tg: (6b92d37..) t/jeb-multi (depends on: upstream/master)


More information about the Sugar-devel mailing list