diff options
Diffstat (limited to 'src/jarabe/journal/model.py')
-rw-r--r-- | src/jarabe/journal/model.py | 189 |
1 files changed, 128 insertions, 61 deletions
diff --git a/src/jarabe/journal/model.py b/src/jarabe/journal/model.py index 81ca7d4..320e577 100644 --- a/src/jarabe/journal/model.py +++ b/src/jarabe/journal/model.py @@ -1,4 +1,4 @@ -# Copyright (C) 2007-2008, One Laptop Per Child +# Copyright (C) 2007-2010, 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 @@ -16,10 +16,11 @@ import logging import os +import errno from datetime import datetime import time import shutil -from stat import S_IFMT, S_IFDIR, S_IFREG +from stat import S_IFLNK, S_IFMT, S_IFDIR, S_IFREG import re from operator import itemgetter @@ -32,18 +33,25 @@ from sugar import dispatch from sugar import mime from sugar import util + DS_DBUS_SERVICE = 'org.laptop.sugar.DataStore' DS_DBUS_INTERFACE = 'org.laptop.sugar.DataStore' DS_DBUS_PATH = '/org/laptop/sugar/DataStore' # Properties the journal cares about. -PROPERTIES = ['uid', 'title', 'mtime', 'timestamp', 'creation_time', 'filesize', - 'keep', 'buddies', 'icon-color', 'mime_type', 'progress', - 'activity', 'mountpoint', 'activity_id', 'bundle_id'] +PROPERTIES = ['activity', 'activity_id', 'buddies', 'bundle_id', + 'creation_time', 'filesize', 'icon-color', 'keep', 'mime_type', + 'mountpoint', 'mtime', 'progress', 'timestamp', 'title', 'uid'] MIN_PAGES_TO_CACHE = 3 MAX_PAGES_TO_CACHE = 5 +_datastore = None +created = dispatch.Signal() +updated = dispatch.Signal() +deleted = dispatch.Signal() + + class _Cache(object): __gtype_name__ = 'model_Cache' @@ -74,7 +82,7 @@ class BaseResultSet(object): """ def __init__(self, query, page_size): - self._total_count = -1 + self._total_count = -1 self._position = -1 self._query = query self._page_size = page_size @@ -141,7 +149,8 @@ class BaseResultSet(object): self._cache.append_all(entries) self._offset = offset - elif remaining_forward_entries <= 0 and remaining_backwards_entries > 0: + elif (remaining_forward_entries <= 0 and + remaining_backwards_entries > 0): # Add one page to the end of cache logging.debug('appending one more page, offset: %r', @@ -186,6 +195,7 @@ class BaseResultSet(object): return self._cache[self._position - self._offset] + class DatastoreResultSet(BaseResultSet): """Encapsulates the result of a query on the datastore """ @@ -213,6 +223,7 @@ class DatastoreResultSet(BaseResultSet): return entries, total_count + class InplaceResultSet(BaseResultSet): """Encapsulates the result of a query on a mount point """ @@ -220,7 +231,9 @@ class InplaceResultSet(BaseResultSet): BaseResultSet.__init__(self, query, page_size) self._mount_point = mount_point self._file_list = None - self._pending_directories = 0 + self._pending_directories = [] + self._visited_directories = [] + self._pending_files = [] self._stopped = False query_text = query.get('query', '') @@ -247,7 +260,10 @@ class InplaceResultSet(BaseResultSet): def setup(self): self._file_list = [] - self._recurse_dir(self._mount_point) + self._pending_directories = [self._mount_point] + self._visited_directories = [] + self._pending_files = [] + gobject.idle_add(self._scan) def stop(self): self._stopped = True @@ -256,10 +272,11 @@ class InplaceResultSet(BaseResultSet): if self._sort[1:] == 'filesize': keygetter = itemgetter(3) else: - keygetter = itemgetter(2) # timestamp - self._file_list.sort(lambda a, b: b - a, + # timestamp + keygetter = itemgetter(2) + self._file_list.sort(lambda a, b: cmp(b, a), key=keygetter, - reverse=self._sort[0]=='-') + reverse=(self._sort[0] == '-')) self.ready.send(self) def find(self, query): @@ -272,7 +289,7 @@ class InplaceResultSet(BaseResultSet): t = time.time() offset = int(query.get('offset', 0)) - limit = int(query.get('limit', len(self._file_list))) + limit = int(query.get('limit', len(self._file_list))) total_count = len(self._file_list) files = self._file_list[offset:offset + limit] @@ -287,62 +304,101 @@ class InplaceResultSet(BaseResultSet): return entries, total_count - def _recurse_dir(self, dir_path): - self._pending_directories += 1 - gobject.idle_add(self._idle_recurse_dir, dir_path) + def _scan(self): + if self._stopped: + return False - def _idle_recurse_dir(self, dir_path): - try: - self._real_recurse_dir(dir_path) - finally: - self._pending_directories -= 1 - if self._pending_directories == 0: - self.setup_ready() + self.progress.send(self) - def _real_recurse_dir(self, dir_path): - if self._stopped: - return + if self._pending_files: + self._scan_a_file() + return True + + if self._pending_directories: + self._scan_a_directory() + return True + + self.setup_ready() + self._visited_directories = [] + return False + + def _scan_a_file(self): + full_path = self._pending_files.pop(0) try: - dirs = os.listdir(dir_path) - except Exception: - logging.exception('Error reading directory %r', dir_path) - dirs = [] + stat = os.lstat(full_path) + except OSError, e: + if e.errno != errno.ENOENT: + logging.exception( + 'Error reading metadata of file %r', full_path) + return + + if S_IFMT(stat.st_mode) == S_IFLNK: + try: + link = os.readlink(full_path) + except OSError, e: + logging.exception( + 'Error reading target of link %r', full_path) + return + + if not os.path.abspath(link).startswith(self._mount_point): + return - for entry in dirs: - if entry.startswith('.'): - continue - full_path = dir_path + '/' + entry try: stat = os.stat(full_path) - if S_IFMT(stat.st_mode) == S_IFDIR: - self._recurse_dir(full_path) - elif S_IFMT(stat.st_mode) == S_IFREG: - add_to_list = True + except OSError, e: + if e.errno != errno.ENOENT: + logging.exception( + 'Error reading metadata of linked file %r', full_path) + return + + if S_IFMT(stat.st_mode) == S_IFDIR: + id_tuple = stat.st_ino, stat.st_dev + if not id_tuple in self._visited_directories: + self._visited_directories.append(id_tuple) + self._pending_directories.append(full_path) + return - if self._regex is not None and \ - not self._regex.match(full_path): - add_to_list = False + if S_IFMT(stat.st_mode) != S_IFREG: + return - if None not in [self._date_start, self._date_end] and \ - (stat.st_mtime < self._date_start or - stat.st_mtime > self._date_end): - add_to_list = False + if self._regex is not None and \ + not self._regex.match(full_path): + return - if self._mime_types: - mime_type = gio.content_type_guess(filename=full_path) - if mime_type not in self._mime_types: - add_to_list = False + if self._date_start is not None and stat.st_mtime < self._date_start: + return - if add_to_list: - file_info = (full_path, stat, int(stat.st_mtime), stat.st_size) - self._file_list.append(file_info) + if self._date_end is not None and stat.st_mtime > self._date_end: + return - self.progress.send(self) + if self._mime_types: + mime_type = gio.content_type_guess(filename=full_path) + if mime_type not in self._mime_types: + return + + file_info = (full_path, stat, int(stat.st_mtime), stat.st_size) + self._file_list.append(file_info) + + return + + def _scan_a_directory(self): + dir_path = self._pending_directories.pop(0) + + try: + entries = os.listdir(dir_path) + except OSError, e: + if e.errno != errno.EACCES: + logging.exception('Error reading directory %r', dir_path) + return + + for entry in entries: + if entry.startswith('.'): + continue + self._pending_files.append(dir_path + '/' + entry) + return - except Exception: - logging.exception('Error reading file %r', full_path) def _get_file_metadata(path, stat): client = gconf.client_get_default() @@ -356,7 +412,7 @@ def _get_file_metadata(path, stat): 'icon-color': client.get_string('/desktop/sugar/user/color'), 'description': path} -_datastore = None + def _get_datastore(): global _datastore if _datastore is None: @@ -370,15 +426,19 @@ def _get_datastore(): return _datastore + def _datastore_created_cb(object_id): created.send(None, object_id=object_id) + def _datastore_updated_cb(object_id): updated.send(None, object_id=object_id) + def _datastore_deleted_cb(object_id): deleted.send(None, object_id=object_id) + def find(query_, page_size): """Returns a ResultSet """ @@ -393,6 +453,7 @@ def find(query_, page_size): else: return InplaceResultSet(query, page_size, mount_points[0]) + def _get_mount_point(path): dir_path = os.path.dirname(path) while True: @@ -401,6 +462,7 @@ def _get_mount_point(path): else: dir_path = dir_path.rsplit(os.sep, 1)[0] + def get(object_id): """Returns the metadata for an object """ @@ -413,6 +475,7 @@ def get(object_id): metadata['mountpoint'] = '/' return metadata + def get_file(object_id): """Returns the file for an object """ @@ -427,6 +490,7 @@ def get_file(object_id): else: return None + def get_file_size(object_id): """Return the file size for an object """ @@ -442,12 +506,14 @@ def get_file_size(object_id): return 0 + def get_unique_values(key): """Returns a list with the different values a property has taken """ empty_dict = dbus.Dictionary({}, signature='ss') return _get_datastore().get_uniquevaluesfor(key, empty_dict) + def delete(object_id): """Removes an object from persistent storage """ @@ -457,6 +523,7 @@ def delete(object_id): else: _get_datastore().delete(object_id) + def copy(metadata, mount_point): """Copies an object to another mount point """ @@ -468,6 +535,7 @@ def copy(metadata, mount_point): return write(metadata, file_path, transfer_ownership=False) + def write(metadata, file_path='', update_mtime=True, transfer_ownership=True): """Creates or updates an entry for that id """ @@ -502,6 +570,7 @@ def write(metadata, file_path='', update_mtime=True, transfer_ownership=True): return object_id + def _get_file_name(title, mime_type): file_name = title @@ -526,11 +595,12 @@ def _get_file_name(title, mime_type): return file_name + def _get_unique_file_name(mount_point, file_name): if os.path.exists(os.path.join(mount_point, file_name)): i = 1 + name, extension = os.path.splitext(file_name) while len(file_name) <= 255: - name, extension = os.path.splitext(file_name) file_name = name + '_' + str(i) + extension if not os.path.exists(os.path.join(mount_point, file_name)): break @@ -538,10 +608,7 @@ def _get_unique_file_name(mount_point, file_name): return file_name + def is_editable(metadata): mountpoint = metadata.get('mountpoint', '/') return mountpoint == '/' - -created = dispatch.Signal() -updated = dispatch.Signal() -deleted = dispatch.Signal() |