Re: [Sugar-devel] [PATCH] Journal entry sharing using a mass storage device
On 02/18/2011 03:25 PM, Sascha Silbe wrote: Excerpts from Simon Schampijer's message of Mon Feb 14 23:13:40 +0100 2011: http://wiki.sugarlabs.org/go/Features/Journal_Entry_Sharing We should come up with a better description before we push the patch. I would name it: Add functionality to copy a Journal entry including the metadata to a removable device [src/jarabe/journal/model.py] +import json As agreed on in IRC, we should stick with simplejson for 0.92. Done. +JOURNAL_METADATA_DIR = '.Sugar-Metadata' Do you intend to use this from other modules? Yes, from from [PATCH] Convert Journal entries that have been saved to a storage device in 0.82. [InplaceResultSet.find()] -for file_path, stat, mtime_, size_ in files: -metadata = _get_file_metadata(file_path, stat) +for file_path, stat, mtime_, metadata, size_ in files: +if metadata is None: +metadata = _get_file_metadata(file_path, stat) I would prefer to handle metadata reading / construction in a single place, i.e. to move the _get_file_metadata() call down below the _get_file_metadata_from_json() call. Actually, I did that but then the scanning time took longer. So I reverted to the previous behavior. As mentioned on IRC we should remove the unused parts from the list. I don't mind doing it in a follow-up patch, though. Actually, I think you need these in order to do the filter the entries. [InplaceResultSet._scan_a_file()] @@ -365,7 +371,20 @@ class InplaceResultSet(BaseResultSet): if self._regex is not None and \ not self._regex.match(full_path): -return +filename = os.path.basename(full_path) +dir_path = os.path.dirname(full_path) +metadata = _get_file_metadata_from_json( \ +dir_path, filename, preview=False) +add_to_list = False +if metadata is not None: +for f in ['fulltext', 'title', + 'description', 'tags']: +if f in metadata and \ +self._regex.match(metadata[f]): +add_to_list = True +break +if not add_to_list: +return This is getting rather deeply nested again. If we move it below the other checks we can unconditionally parse resp. construct the metadata and move the full text matches two indentation levels up. I was able to remove one level. I hope this is ok now. BTW, is it intentional that you search only a subset of the properties, unlike what the data store does? Before this patch you could only search for filenames on a removable device. Now you can as well search for title, description, tags for items that do have metadata. In general searches like 'Write activity entries' are not supported for a removable device. Maybe we could add this later, for now I think it is fine like that. [InplaceResultSet._get_file_metadata()] def _get_file_metadata(path, stat): We should consider renaming this to make clears it's constructing the metadata based on file system metadata and heuristics (MIME type), as opposed to reading the serialised metadata from disk as _get_file_metadata_from_json() does. The method gets now the metadata for a file, if available from the json file otherwise it constructs it. So I think this is the correct name now. +def _get_file_metadata_from_json(dir_path, filename, preview=False): The reference to preview in the code below confused me for a few minutes. We should rename it to e.g. fetch_preview. Good catch, done. +Returns the metadata from the json file and the preview +stored on the external device. If the metadata is corrupted +we do remove it and the preview as well. PEP 257 (referenced by PEP 8) says: The docstring is a phrase ending in a period. It prescribes the function or method's effect as a command (Do this, Return that), not as a description; e.g. don't write Returns the pathname [...] Multi-line docstrings consist of a summary line just like a one-line docstring, followed by a blank line, followed by a more elaborate description. Done. +metadata = None +metadata_path = os.path.join(dir_path, JOURNAL_METADATA_DIR, + filename + '.metadata') +preview_path = os.path.join(dir_path, JOURNAL_METADATA_DIR, +filename + '.preview') + +if os.path.exists(metadata_path): +try: Please change this to guardian style to a) get rid of the indentation and b) prevent an exception (that currently gets caught by your catch-all except:) if the preview exists, but the metadata file doesn't. I.e. please use: if not os.path.exists(metadata_path): return None Done. +metadata = json.load(open(metadata_path)) +except ValueError: We should catch EnvironmentError (base class of IOError
Re: [Sugar-devel] [PATCH] Journal entry sharing using a mass storage device
Excerpts from Simon Schampijer's message of Mon Feb 14 23:13:40 +0100 2011: http://wiki.sugarlabs.org/go/Features/Journal_Entry_Sharing We should come up with a better description before we push the patch. [src/jarabe/journal/model.py] +import json As agreed on in IRC, we should stick with simplejson for 0.92. +JOURNAL_METADATA_DIR = '.Sugar-Metadata' Do you intend to use this from other modules? [InplaceResultSet.find()] -for file_path, stat, mtime_, size_ in files: -metadata = _get_file_metadata(file_path, stat) +for file_path, stat, mtime_, metadata, size_ in files: +if metadata is None: +metadata = _get_file_metadata(file_path, stat) I would prefer to handle metadata reading / construction in a single place, i.e. to move the _get_file_metadata() call down below the _get_file_metadata_from_json() call. As mentioned on IRC we should remove the unused parts from the list. I don't mind doing it in a follow-up patch, though. [InplaceResultSet._scan_a_file()] @@ -365,7 +371,20 @@ class InplaceResultSet(BaseResultSet): if self._regex is not None and \ not self._regex.match(full_path): -return +filename = os.path.basename(full_path) +dir_path = os.path.dirname(full_path) +metadata = _get_file_metadata_from_json( \ +dir_path, filename, preview=False) +add_to_list = False +if metadata is not None: +for f in ['fulltext', 'title', + 'description', 'tags']: +if f in metadata and \ +self._regex.match(metadata[f]): +add_to_list = True +break +if not add_to_list: +return This is getting rather deeply nested again. If we move it below the other checks we can unconditionally parse resp. construct the metadata and move the full text matches two indentation levels up. BTW, is it intentional that you search only a subset of the properties, unlike what the data store does? [InplaceResultSet._get_file_metadata()] def _get_file_metadata(path, stat): We should consider renaming this to make clears it's constructing the metadata based on file system metadata and heuristics (MIME type), as opposed to reading the serialised metadata from disk as _get_file_metadata_from_json() does. +def _get_file_metadata_from_json(dir_path, filename, preview=False): The reference to preview in the code below confused me for a few minutes. We should rename it to e.g. fetch_preview. +Returns the metadata from the json file and the preview +stored on the external device. If the metadata is corrupted +we do remove it and the preview as well. PEP 257 (referenced by PEP 8) says: The docstring is a phrase ending in a period. It prescribes the function or method's effect as a command (Do this, Return that), not as a description; e.g. don't write Returns the pathname [...] Multi-line docstrings consist of a summary line just like a one-line docstring, followed by a blank line, followed by a more elaborate description. +metadata = None +metadata_path = os.path.join(dir_path, JOURNAL_METADATA_DIR, + filename + '.metadata') +preview_path = os.path.join(dir_path, JOURNAL_METADATA_DIR, +filename + '.preview') + +if os.path.exists(metadata_path): +try: Please change this to guardian style to a) get rid of the indentation and b) prevent an exception (that currently gets caught by your catch-all except:) if the preview exists, but the metadata file doesn't. I.e. please use: if not os.path.exists(metadata_path): return None +metadata = json.load(open(metadata_path)) +except ValueError: We should catch EnvironmentError (base class of IOError and OSError), too. The external medium might be formatted using a non-FAT file system (= permission denied) or be defective (many different errors). +os.unlink(metadata_path) +if os.path.exists(preview_path): +os.unlink(preview_path) Dito. +logging.debug(Could not read metadata for file %r on \ + external device., filename) Since the file existed and gets removed by us, we should use log level error. +else: +metadata['uid'] = os.path.join(dir_path, filename) If we can't read the metadata (file does not exist or is corrupt), we should call _get_file_metadata(). That will fill in several properties, including uid. I'm not sure if we should try reading the preview if we couldn't read the metadata file. +if preview: Again, please use guardian style. +if os.path.exists(preview_path): +try: +metadata['preview'] =
[Sugar-devel] [PATCH] Journal entry sharing using a mass storage device
http://wiki.sugarlabs.org/go/Features/Journal_Entry_Sharing --- src/jarabe/journal/model.py | 188 +++ 1 files changed, 171 insertions(+), 17 deletions(-) diff --git a/src/jarabe/journal/model.py b/src/jarabe/journal/model.py index 320e577..67d2f19 100644 --- a/src/jarabe/journal/model.py +++ b/src/jarabe/journal/model.py @@ -1,4 +1,4 @@ -# Copyright (C) 2007-2010, One Laptop per Child +# Copyright (C) 2007-2011, One Laptop per Child # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -20,13 +20,15 @@ import errno from datetime import datetime import time import shutil +import tempfile from stat import S_IFLNK, S_IFMT, S_IFDIR, S_IFREG import re from operator import itemgetter +import json +from gettext import gettext as _ import gobject import dbus -import gconf import gio from sugar import dispatch @@ -46,6 +48,8 @@ PROPERTIES = ['activity', 'activity_id', 'buddies', 'bundle_id', MIN_PAGES_TO_CACHE = 3 MAX_PAGES_TO_CACHE = 5 +JOURNAL_METADATA_DIR = '.Sugar-Metadata' + _datastore = None created = dispatch.Signal() updated = dispatch.Signal() @@ -295,8 +299,9 @@ class InplaceResultSet(BaseResultSet): files = self._file_list[offset:offset + limit] entries = [] -for file_path, stat, mtime_, size_ in files: -metadata = _get_file_metadata(file_path, stat) +for file_path, stat, mtime_, metadata, size_ in files: +if metadata is None: +metadata = _get_file_metadata(file_path, stat) metadata['mountpoint'] = self._mount_point entries.append(metadata) @@ -324,6 +329,7 @@ class InplaceResultSet(BaseResultSet): def _scan_a_file(self): full_path = self._pending_files.pop(0) +metadata = None try: stat = os.lstat(full_path) @@ -365,7 +371,20 @@ class InplaceResultSet(BaseResultSet): if self._regex is not None and \ not self._regex.match(full_path): -return +filename = os.path.basename(full_path) +dir_path = os.path.dirname(full_path) +metadata = _get_file_metadata_from_json( \ +dir_path, filename, preview=False) +add_to_list = False +if metadata is not None: +for f in ['fulltext', 'title', + 'description', 'tags']: +if f in metadata and \ +self._regex.match(metadata[f]): +add_to_list = True +break +if not add_to_list: +return if self._date_start is not None and stat.st_mtime self._date_start: return @@ -378,7 +397,8 @@ class InplaceResultSet(BaseResultSet): if mime_type not in self._mime_types: return -file_info = (full_path, stat, int(stat.st_mtime), stat.st_size) +file_info = (full_path, stat, int(stat.st_mtime), metadata, + stat.st_size) self._file_list.append(file_info) return @@ -401,7 +421,17 @@ class InplaceResultSet(BaseResultSet): def _get_file_metadata(path, stat): -client = gconf.client_get_default() +Returns the metadata from the corresponding file +on the external device or does create the metadata +based on the file properties. + + +filename = os.path.basename(path) +dir_path = os.path.dirname(path) +metadata = _get_file_metadata_from_json(dir_path, filename, preview=True) +if metadata: +return metadata + return {'uid': path, 'title': os.path.basename(path), 'timestamp': stat.st_mtime, @@ -409,10 +439,46 @@ def _get_file_metadata(path, stat): 'mime_type': gio.content_type_guess(filename=path), 'activity': '', 'activity_id': '', -'icon-color': client.get_string('/desktop/sugar/user/color'), +'icon-color': '#00,#ff', 'description': path} +def _get_file_metadata_from_json(dir_path, filename, preview=False): +Returns the metadata from the json file and the preview +stored on the external device. If the metadata is corrupted +we do remove it and the preview as well. + + +metadata = None +metadata_path = os.path.join(dir_path, JOURNAL_METADATA_DIR, + filename + '.metadata') +preview_path = os.path.join(dir_path, JOURNAL_METADATA_DIR, +filename + '.preview') + +if os.path.exists(metadata_path): +try: +metadata = json.load(open(metadata_path)) +except ValueError: +os.unlink(metadata_path) +if os.path.exists(preview_path): +os.unlink(preview_path) +logging.debug(Could not read