Ticket #1636: 0001-Journal-entry-sharing.patch

File 0001-Journal-entry-sharing.patch, 10.7 KB (added by erikos, 12 years ago)

Sharing/Backup Journal entries using a storage device (branch master)

  • src/jarabe/journal/model.py

    From f6dd32244133525a0966662f1b13deef0367a412 Mon Sep 17 00:00:00 2001
    From: Simon Schampijer <simon@schampijer.de>
    Date: Tue, 8 Feb 2011 12:18:59 -0500
    Subject: [PATCH] Journal entry sharing
    
    http://wiki.sugarlabs.org/go/Features/Journal_Entry_Sharing
    ---
     src/jarabe/journal/model.py |  182 +++++++++++++++++++++++++++++++++++++++----
     1 files changed, 166 insertions(+), 16 deletions(-)
    
    diff --git a/src/jarabe/journal/model.py b/src/jarabe/journal/model.py
    index 320e577..4228768 100644
    a b import errno 
    2020from datetime import datetime
    2121import time
    2222import shutil
     23import tempfile
    2324from stat import S_IFLNK, S_IFMT, S_IFDIR, S_IFREG
    2425import re
    2526from operator import itemgetter
     27import json
     28from gettext import gettext as _
    2629
    2730import gobject
    2831import dbus
    29 import gconf
    3032import gio
    3133
    3234from sugar import dispatch
    PROPERTIES = ['activity', 'activity_id', 'buddies', 'bundle_id', 
    4648MIN_PAGES_TO_CACHE = 3
    4749MAX_PAGES_TO_CACHE = 5
    4850
     51JOURNAL_METADATA_DIR = '.Sugar-Metadata'
     52
    4953_datastore = None
    5054created = dispatch.Signal()
    5155updated = dispatch.Signal()
    class InplaceResultSet(BaseResultSet): 
    295299        files = self._file_list[offset:offset + limit]
    296300
    297301        entries = []
    298         for file_path, stat, mtime_, size_ in files:
    299             metadata = _get_file_metadata(file_path, stat)
     302        for file_path, stat, mtime_, metadata, size_ in files:
     303            if metadata is None:
     304                metadata = _get_file_metadata(file_path, stat)
    300305            metadata['mountpoint'] = self._mount_point
    301306            entries.append(metadata)
    302307
    class InplaceResultSet(BaseResultSet): 
    324329
    325330    def _scan_a_file(self):
    326331        full_path = self._pending_files.pop(0)
     332        metadata = None
    327333
    328334        try:
    329335            stat = os.lstat(full_path)
    class InplaceResultSet(BaseResultSet): 
    365371
    366372        if self._regex is not None and \
    367373                not self._regex.match(full_path):
    368             return
     374            filename = os.path.basename(full_path)
     375            dir_path = os.path.dirname(full_path)
     376            metadata = _get_file_metadata_from_json( \
     377                dir_path, filename, preview=False)
     378            add_to_list = False
     379            if metadata is not None:
     380                for f in ['fulltext', 'title',
     381                          'description', 'tags']:
     382                    if f in metadata and \
     383                            self._regex.match(metadata[f]):
     384                        add_to_list = True
     385                        break
     386            if not add_to_list:
     387                return
    369388
    370389        if self._date_start is not None and stat.st_mtime < self._date_start:
    371390            return
    class InplaceResultSet(BaseResultSet): 
    378397            if mime_type not in self._mime_types:
    379398                return
    380399
    381         file_info = (full_path, stat, int(stat.st_mtime), stat.st_size)
     400        file_info = (full_path, stat, int(stat.st_mtime), metadata,
     401                     stat.st_size)
    382402        self._file_list.append(file_info)
    383403
    384404        return
    class InplaceResultSet(BaseResultSet): 
    401421
    402422
    403423def _get_file_metadata(path, stat):
    404     client = gconf.client_get_default()
     424    """Returns the metadata from the corresponding file
     425    on the external device or does create the metadata
     426    based on the file properties.
     427
     428    """
     429    filename = os.path.basename(path)
     430    dir_path = os.path.dirname(path)
     431    metadata = _get_file_metadata_from_json(dir_path, filename, preview=True)
     432    if metadata:
     433        return metadata
     434
    405435    return {'uid': path,
    406436            'title': os.path.basename(path),
    407437            'timestamp': stat.st_mtime,
    def _get_file_metadata(path, stat): 
    409439            'mime_type': gio.content_type_guess(filename=path),
    410440            'activity': '',
    411441            'activity_id': '',
    412             'icon-color': client.get_string('/desktop/sugar/user/color'),
     442            'icon-color': '#000000,#ffffff',
    413443            'description': path}
    414444
    415445
     446def _get_file_metadata_from_json(dir_path, filename, preview=False):
     447    """Returns the metadata from the json file and the preview
     448    stored on the external device.
     449
     450    """
     451    metadata = None
     452    metadata_path = os.path.join(dir_path, JOURNAL_METADATA_DIR,
     453                                 filename + '.metadata')
     454    if os.path.exists(metadata_path):
     455        try:
     456            metadata = json.load(open(metadata_path))
     457        except ValueError:
     458            logging.debug("Could not read metadata for file %r on" \
     459                              "external device.", filename)
     460        else:
     461            metadata['uid'] = os.path.join(dir_path, filename)
     462    if preview:
     463        preview_path = os.path.join(dir_path, JOURNAL_METADATA_DIR,
     464                                    filename + '.preview')
     465        if os.path.exists(preview_path):
     466            try:
     467                metadata['preview'] = dbus.ByteArray(open(preview_path).read())
     468            except:
     469                logging.debug("Could not read preview for file %r on" \
     470                                  "external device.", filename)
     471    else:
     472        if metadata and 'preview' in metadata:
     473            del(metadata['preview'])
     474    return metadata
     475
     476
    416477def _get_datastore():
    417478    global _datastore
    418479    if _datastore is None:
    def delete(object_id): 
    519580    """
    520581    if os.path.exists(object_id):
    521582        os.unlink(object_id)
     583        dir_path = os.path.dirname(object_id)
     584        filename = os.path.basename(object_id)
     585        old_files = [os.path.join(dir_path, JOURNAL_METADATA_DIR,
     586                                  filename + '.metadata'),
     587                     os.path.join(dir_path, JOURNAL_METADATA_DIR,
     588                                  filename + '.preview')]
     589        for old_file in old_files:
     590            if os.path.exists(old_file):
     591                try:
     592                    os.unlink(old_file)
     593                except:
     594                    pass
    522595        deleted.send(None, object_id=object_id)
    523596    else:
    524597        _get_datastore().delete(object_id)
    def write(metadata, file_path='', update_mtime=True, transfer_ownership=True): 
    556629                                                 file_path,
    557630                                                 transfer_ownership)
    558631    else:
    559         if not os.path.exists(file_path):
    560             raise ValueError('Entries without a file cannot be copied to '
    561                              'removable devices')
     632        object_id = _write_entry_on_external_device(metadata, file_path)
     633
     634    return object_id
    562635
    563         file_name = _get_file_name(metadata['title'], metadata['mime_type'])
    564         file_name = _get_unique_file_name(metadata['mountpoint'], file_name)
    565636
     637def _write_entry_on_external_device(metadata, file_path):
     638    """This creates and updates an entry copied from the
     639    DS to external storage device. Besides copying the
     640    associated file a hidden file for the preview and one
     641    for the metadata are stored. We make sure that the
     642    metadata and preview file are in the same directory
     643    as the data file.
     644
     645    This function handles renames of an entry on the
     646    external device and avoids name collisions. Renames are
     647    handled failsafe.
     648
     649    """
     650    if 'uid' in metadata and os.path.exists(metadata['uid']):
     651        file_path = metadata['uid']
     652
     653    if not file_path or not os.path.exists(file_path):
     654        raise ValueError('Entries without a file cannot be copied to '
     655                         'removable devices')
     656
     657    if metadata['title'] == '':
     658        metadata['title'] = _('Untitled')
     659    file_name = get_file_name(metadata['title'], metadata['mime_type'])
     660
     661    destination_path = os.path.join(metadata['mountpoint'], file_name)
     662    if destination_path != file_path:
     663        file_name = get_unique_file_name(metadata['mountpoint'], file_name)
    566664        destination_path = os.path.join(metadata['mountpoint'], file_name)
     665        clean_name, extension_ = os.path.splitext(file_name)
     666        metadata['title'] = clean_name
     667
     668    metadata_copy = metadata.copy()
     669    del metadata_copy['mountpoint']
     670    if 'uid' in metadata_copy:
     671        del metadata_copy['uid']
     672
     673    metadata_dir_path = os.path.join(metadata['mountpoint'],
     674                                     JOURNAL_METADATA_DIR)
     675    if not os.path.exists(metadata_dir_path):
     676        os.mkdir(metadata_dir_path)
     677
     678    if 'preview' in metadata_copy:
     679        preview = metadata_copy['preview']
     680        preview_fname = file_name + '.preview'
     681        preview_path = os.path.join(metadata['mountpoint'],
     682                                    JOURNAL_METADATA_DIR, preview_fname)
     683        metadata_copy['preview'] = preview_fname
     684
     685        (fh, fn) = tempfile.mkstemp(dir=metadata['mountpoint'])
     686        os.write(fh, preview)
     687        os.close(fh)
     688        os.rename(fn, preview_path)
     689
     690    metadata_path = os.path.join(metadata['mountpoint'],
     691                                 JOURNAL_METADATA_DIR,
     692                                 file_name + '.metadata')
     693    (fh, fn) = tempfile.mkstemp(dir=metadata['mountpoint'])
     694    os.write(fh, json.dumps(metadata_copy))
     695    os.close(fh)
     696    os.rename(fn, metadata_path)
     697
     698    if os.path.dirname(destination_path) == os.path.dirname(file_path):
     699        old_file_path = file_path
     700        if old_file_path != destination_path:
     701            os.rename(file_path, destination_path)
     702            old_fname = os.path.basename(file_path)
     703            old_files = [os.path.join(metadata['mountpoint'],
     704                                      JOURNAL_METADATA_DIR,
     705                                      old_fname + '.metadata'),
     706                         os.path.join(metadata['mountpoint'],
     707                                      JOURNAL_METADATA_DIR,
     708                                      old_fname + '.preview')]
     709            for ofile in old_files:
     710                if os.path.exists(ofile):
     711                    try:
     712                        os.unlink(ofile)
     713                    except:
     714                        pass
     715    else:
    567716        shutil.copy(file_path, destination_path)
    568         object_id = destination_path
    569         created.send(None, object_id=object_id)
     717
     718    object_id = destination_path
     719    created.send(None, object_id=object_id)
    570720
    571721    return object_id
    572722
    573723
    574 def _get_file_name(title, mime_type):
     724def get_file_name(title, mime_type):
    575725    file_name = title
    576726
    577727    extension = mime.get_primary_extension(mime_type)
    def _get_file_name(title, mime_type): 
    596746    return file_name
    597747
    598748
    599 def _get_unique_file_name(mount_point, file_name):
     749def get_unique_file_name(mount_point, file_name):
    600750    if os.path.exists(os.path.join(mount_point, file_name)):
    601751        i = 1
    602752        name, extension = os.path.splitext(file_name)