Ticket #1636: 0001-Share-Journal-entries-over-USB-1636.patch

File 0001-Share-Journal-entries-over-USB-1636.patch, 10.0 KB (added by erikos, 14 years ago)

Refactored patch from Martin and addressed the review comments from Tomeu

  • src/jarabe/journal/model.py

    From 7410946e0eb569925f1b8a376dff30c0ca946b7e Mon Sep 17 00:00:00 2001
    From: Simon Schampijer <simon@schampijer.de>
    Date: Thu, 9 Sep 2010 12:15:42 +0200
    Subject: [PATCH] Share Journal entries over USB #1636
    
    ---
     src/jarabe/journal/model.py |  160 +++++++++++++++++++++++++++++++++++++++----
     1 files changed, 146 insertions(+), 14 deletions(-)
    
    diff --git a/src/jarabe/journal/model.py b/src/jarabe/journal/model.py
    index f4186f0..4060221 100644
    a b import os 
    1919from datetime import datetime
    2020import time
    2121import shutil
     22import tempfile
    2223from stat import S_IFMT, S_IFDIR, S_IFREG
    2324import traceback
    2425import re
     26import json
    2527
    2628import gobject
    2729import dbus
    class InplaceResultSet(BaseResultSet): 
    308310        files = self._file_list[offset:offset + limit]
    309311
    310312        entries = []
    311         for file_path, stat, mtime_ in files:
    312             metadata = _get_file_metadata(file_path, stat)
     313        for file_path, stat, mtime_, metadata in files:
     314            if metadata is None:
     315                # FIXME: the find should fetch metadata
     316                metadata = _get_file_metadata(file_path, stat)
    313317            metadata['mountpoint'] = self._mount_point
    314318            entries.append(metadata)
    315319
    class InplaceResultSet(BaseResultSet): 
    333337
    334338                elif S_IFMT(stat.st_mode) == S_IFREG:
    335339                    add_to_list = True
     340                    metadata = None
    336341
    337342                    if self._regex is not None and \
    338343                            not self._regex.match(full_path):
    339344                        add_to_list = False
     345                        metadata = _get_file_metadata_from_json( \
     346                            dir_path, entry, preview=False)
     347                        if metadata is not None:
     348                            for f in ['fulltext', 'title',
     349                                      'description', 'tags']:
     350                                if f in metadata and \
     351                                        self._regex.match(metadata[f]):
     352                                    add_to_list = True
     353                                    break
    340354
    341355                    if None not in [self._date_start, self._date_end] and \
    342356                            (stat.st_mtime < self._date_start or
    class InplaceResultSet(BaseResultSet): 
    349363                            add_to_list = False
    350364
    351365                    if add_to_list:
    352                         file_info = (full_path, stat, int(stat.st_mtime))
     366                        file_info = (full_path, stat, int(stat.st_mtime), metadata)
    353367                        self._file_list.append(file_info)
    354368
    355369                    self.progress.send(self)
    class InplaceResultSet(BaseResultSet): 
    364378            self._pending_directories -= 1
    365379
    366380def _get_file_metadata(path, stat):
     381    """Returns the metadata from the corresponding file
     382    on the external device or does create the metadata
     383    based on the file properties.
     384
     385    """
     386    filename = os.path.basename(path)
     387    dir_path = os.path.dirname(path)
     388    metadata = _get_file_metadata_from_json(dir_path, filename, preview=True)
     389    if metadata:
     390        return metadata
     391
    367392    client = gconf.client_get_default()
    368393    return {'uid': path,
    369394            'title': os.path.basename(path),
    def _get_file_metadata(path, stat): 
    374399            'icon-color': client.get_string('/desktop/sugar/user/color'),
    375400            'description': path}
    376401
     402def _get_file_metadata_from_json(dir_path, filename, preview=False):
     403    """Returns the metadata from the json file and the preview
     404    stored on the external device.
     405
     406    """
     407    metadata = None
     408    metadata_path = os.path.join(dir_path,
     409                          '.' + filename + '.metadata')
     410    if os.path.exists(metadata_path):
     411        try:
     412            metadata = json.load(open(metadata_path))
     413        except ValueError:
     414            logging.debug("Could not read metadata for file %r on" \
     415                              "external device.", filename)
     416        else:
     417            metadata['uid'] = os.path.join(dir_path, filename)
     418    if preview:
     419        preview_path = os.path.join(dir_path,
     420                                    '.' + filename + '.preview')
     421        if os.path.exists(preview_path):
     422            try:
     423                metadata['preview'] = dbus.ByteArray(open(preview_path).read())
     424            except:
     425                logging.debug("Could not read preview for file %r on" \
     426                                  "external device.", filename)
     427    else:
     428        if metadata and 'preview' in metadata:
     429            del(metadata['preview'])
     430    return metadata
     431
    377432_datastore = None
    378433def _get_datastore():
    379434    global _datastore
    def delete(object_id): 
    460515    """
    461516    if os.path.exists(object_id):
    462517        os.unlink(object_id)
     518        dir_path = os.path.dirname(object_id)
     519        filename = os.path.basename(object_id)
     520        old_files = [os.path.join(dir_path, '.' + filename + '.metadata'),
     521                     os.path.join(dir_path, '.' + filename + '.preview')]
     522        for old_file in old_files:
     523            if os.path.exists(old_file):
     524                try:
     525                    os.unlink(old_file)
     526                except:
     527                    pass
    463528        deleted.send(None, object_id=object_id)
    464529    else:
    465530        _get_datastore().delete(object_id)
    def write(metadata, file_path='', update_mtime=True, transfer_ownership=True): 
    495560                                                 file_path,
    496561                                                 transfer_ownership)
    497562    else:
    498         if not os.path.exists(file_path):
    499             raise ValueError('Entries without a file cannot be copied to '
    500                              'removable devices')
     563        object_id = _write_entry_on_external_device(metadata, file_path)
    501564
    502         file_name = _get_file_name(metadata['title'], metadata['mime_type'])
    503         file_name = _get_unique_file_name(metadata['mountpoint'], file_name)
     565    return object_id
     566
     567def _write_entry_on_external_device(metadata, file_path):
     568    """This creates and updates an entry copied from the
     569    DS to external storage device. Besides copying the
     570    associated file a hidden file for the preview and one
     571    for the metadata are stored. We make sure that the
     572    metadata and preview file are in the same directory
     573    as the data file.
     574
     575    This function handles renames of an entry on the
     576    external device and avoids name collisions. Renames are
     577    handled failsafe.
     578
     579    """
     580    if 'uid' in metadata and os.path.exists(metadata['uid']):
     581        file_path = metadata['uid']
     582
     583    if not file_path or not os.path.exists(file_path):
     584        raise ValueError('Entries without a file cannot be copied to '
     585                         'removable devices')
    504586
     587    file_name = _get_file_name(metadata['title'], metadata['mime_type'])
     588
     589    destination_path = os.path.join(metadata['mountpoint'], file_name)
     590    if destination_path != file_path:
     591        file_name = _get_unique_file_name(metadata['mountpoint'], file_name)
    505592        destination_path = os.path.join(metadata['mountpoint'], file_name)
     593        clean_name, extension_ = os.path.splitext(file_name)
     594        metadata['title'] = clean_name
     595
     596    metadata_copy = metadata.copy()
     597    del metadata_copy['mountpoint']
     598    if 'uid' in metadata_copy:
     599        del metadata_copy['uid']
     600
     601    if 'preview' in metadata_copy:
     602        preview = metadata_copy['preview']
     603        preview_fname = '.' + file_name + '.preview'
     604        preview_path = os.path.join(metadata['mountpoint'], preview_fname)
     605        metadata_copy['preview'] = preview_fname
     606
     607        (fh, fn) = tempfile.mkstemp(dir=metadata['mountpoint'])
     608        os.write(fh, preview)
     609        os.close(fh)
     610        os.rename(fn, preview_path)
     611
     612    metadata_path = os.path.join(metadata['mountpoint'],
     613                                 '.' + file_name + '.metadata')
     614    (fh, fn) = tempfile.mkstemp(dir=metadata['mountpoint'])
     615    os.write(fh, json.dumps(metadata_copy))
     616    os.close(fh)
     617    os.rename(fn, metadata_path)
     618
     619    if os.path.dirname(destination_path) == os.path.dirname(file_path):
     620        old_file_path = file_path
     621        if old_file_path != destination_path:
     622            os.rename(file_path, destination_path)
     623            old_fname = os.path.basename(file_path)
     624            old_files = [os.path.join(metadata['mountpoint'],
     625                                      '.' + old_fname + '.metadata'),
     626                         os.path.join(metadata['mountpoint'],
     627                                      '.' + old_fname + '.preview')]
     628            for ofile in old_files:
     629                if os.path.exists(ofile):
     630                    try:
     631                        os.unlink(ofile)
     632                    except:
     633                        pass
     634    else:
    506635        shutil.copy(file_path, destination_path)
    507         object_id = destination_path
    508         created.send(None, object_id=object_id)
     636
     637    object_id = destination_path
     638    created.send(None, object_id=object_id)
    509639
    510640    return object_id
    511641
    512642def _get_file_name(title, mime_type):
    513643    file_name = title
    514644
    515     extension = '.' + mime.get_primary_extension(mime_type)
    516     if not file_name.endswith(extension):
    517         file_name += extension
     645    mime_extension = mime.get_primary_extension(mime_type)
     646    if mime_extension:
     647        extension = '.' + mime_extension
     648        if not file_name.endswith(extension):
     649            file_name += extension
    518650
    519651    # Invalid characters in VFAT filenames. From
    520652    # http://en.wikipedia.org/wiki/File_Allocation_Table
    def _get_file_name(title, mime_type): 
    534666def _get_unique_file_name(mount_point, file_name):
    535667    if os.path.exists(os.path.join(mount_point, file_name)):
    536668        i = 1
     669        name, extension = os.path.splitext(file_name)
    537670        while len(file_name) <= 255:
    538             name, extension = os.path.splitext(file_name)
    539671            file_name = name + '_' + str(i) + extension
    540672            if not os.path.exists(os.path.join(mount_point, file_name)):
    541673                break