diff options
Diffstat (limited to 'src/jarabe/journal')
-rw-r--r-- | src/jarabe/journal/Makefile.am | 5 | ||||
-rw-r--r-- | src/jarabe/journal/journalactivity.py | 3 | ||||
-rw-r--r-- | src/jarabe/journal/journaltoolbox.py | 7 | ||||
-rw-r--r-- | src/jarabe/journal/listview.py | 9 | ||||
-rw-r--r-- | src/jarabe/journal/model.py | 205 | ||||
-rw-r--r-- | src/jarabe/journal/palettes.py | 79 | ||||
-rw-r--r-- | src/jarabe/journal/volumestoolbar.py | 94 | ||||
-rw-r--r-- | src/jarabe/journal/webdavmanager.py | 256 |
8 files changed, 642 insertions, 16 deletions
diff --git a/src/jarabe/journal/Makefile.am b/src/jarabe/journal/Makefile.am index f24dcfe..8efca6d 100644 --- a/src/jarabe/journal/Makefile.am +++ b/src/jarabe/journal/Makefile.am @@ -15,5 +15,6 @@ sugar_PYTHON = \ model.py \ objectchooser.py \ palettes.py \ - volumestoolbar.py \ - processdialog.py + volumestoolbar.py \ + processdialog.py \ + webdavmanager.py diff --git a/src/jarabe/journal/journalactivity.py b/src/jarabe/journal/journalactivity.py index fa308cd..1b841e9 100644 --- a/src/jarabe/journal/journalactivity.py +++ b/src/jarabe/journal/journalactivity.py @@ -485,6 +485,9 @@ class JournalActivity(JournalWindow): def is_editing_mode_present(self): return self._editing_mode + def get_volumes_toolbar(self): + return self._volumes_toolbar + def get_journal(): global _journal diff --git a/src/jarabe/journal/journaltoolbox.py b/src/jarabe/journal/journaltoolbox.py index fd14826..c3614d7 100644 --- a/src/jarabe/journal/journaltoolbox.py +++ b/src/jarabe/journal/journaltoolbox.py @@ -624,6 +624,13 @@ class BatchEraseButton(ToolButton, palettes.ActionItem): show_not_completed_ops_info=True) self.props.tooltip = _('Erase') + # De-sensitize Batch-Erase button, for locally-mounted-remote-shares. + from jarabe.journal.journalactivity import get_mount_point + current_mount_point = get_mount_point() + + if model.is_mount_point_for_locally_mounted_remote_share(current_mount_point): + self.set_sensitive(False) + def _get_actionable_signal(self): return 'clicked' diff --git a/src/jarabe/journal/listview.py b/src/jarabe/journal/listview.py index 8522dca..10f468f 100644 --- a/src/jarabe/journal/listview.py +++ b/src/jarabe/journal/listview.py @@ -634,6 +634,15 @@ class ListView(BaseListView): self.emit('volume-error', message, severity) def __icon_clicked_cb(self, cell, path): + # For locally-mounted remote shares, we do not want to launch + # by clicking on the icons. + # So, check if this is a part of locally-mounted-remote share, + # and if yes, return, without doing anything. + from jarabe.journal.journalactivity import get_mount_point + current_mount_point = get_mount_point() + if model.is_mount_point_for_locally_mounted_remote_share(current_mount_point): + return + row = self.tree_view.get_model()[path] metadata = model.get(row[ListModel.COLUMN_UID]) misc.resume(metadata) diff --git a/src/jarabe/journal/model.py b/src/jarabe/journal/model.py index 83e216f..527f78d 100644 --- a/src/jarabe/journal/model.py +++ b/src/jarabe/journal/model.py @@ -20,6 +20,7 @@ import logging import os +import stat import errno import subprocess from datetime import datetime @@ -36,11 +37,14 @@ import gobject import dbus import gio import gconf +import string from sugar import dispatch from sugar import mime from sugar import util +from jarabe.journal.webdavmanager import get_remote_webdav_share_metadata + DS_DBUS_SERVICE = 'org.laptop.sugar.DataStore' DS_DBUS_INTERFACE = 'org.laptop.sugar.DataStore' DS_DBUS_PATH = '/org/laptop/sugar/DataStore' @@ -425,6 +429,123 @@ class InplaceResultSet(BaseResultSet): return +class RemoteShareResultSet(object): + def __init__(self, ip_address, query): + self._ip_address = ip_address + self._file_list = [] + + self.ready = dispatch.Signal() + self.progress = dispatch.Signal() + + # First time, query is none. + if query is None: + return + + query_text = query.get('query', '') + if query_text.startswith('"') and query_text.endswith('"'): + self._regex = re.compile('*%s*' % query_text.strip(['"'])) + elif query_text: + expression = '' + for word in query_text.split(' '): + expression += '(?=.*%s.*)' % word + self._regex = re.compile(expression, re.IGNORECASE) + else: + self._regex = None + + if query.get('timestamp', ''): + self._date_start = int(query['timestamp']['start']) + self._date_end = int(query['timestamp']['end']) + else: + self._date_start = None + self._date_end = None + + self._mime_types = query.get('mime_type', []) + + self._sort = query.get('order_by', ['+timestamp'])[0] + + def setup(self): + metadata_list_complete = get_remote_webdav_share_metadata(self._ip_address) + for metadata in metadata_list_complete: + + add_to_list = False + if self._regex 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 + else: + add_to_list = True + if not add_to_list: + continue + + add_to_list = False + if self._date_start is not None: + if metadata['timestamp'] > self._date_start: + add_to_list = True + else: + add_to_list = True + if not add_to_list: + continue + + add_to_list = False + if self._date_end is not None: + if metadata['timestamp'] < self._date_end: + add_to_list = True + else: + add_to_list = True + if not add_to_list: + continue + + add_to_list = False + if self._mime_types: + mime_type = metadata['mime_type'] + if mime_type in self._mime_types: + add_to_list = True + else: + add_to_list = True + if not add_to_list: + continue + + # If control reaches here, the current metadata has passed + # out all filter-tests. + file_info = (metadata['timestamp'], + metadata['creation_time'], + metadata['filesize'], + metadata) + self._file_list.append(file_info) + + if self._sort[1:] == 'filesize': + keygetter = itemgetter(2) + elif self._sort[1:] == 'creation_time': + keygetter = itemgetter(1) + else: + # timestamp + keygetter = itemgetter(0) + + self._file_list.sort(lambda a, b: cmp(b, a), + key=keygetter, + reverse=(self._sort[0] == '-')) + + self.ready.send(self) + + def get_length(self): + return len(self._file_list) + + length = property(get_length) + + def seek(self, position): + self._position = position + + def read(self): + modified_timestamp, creation_timestamp, filesize, metadata = self._file_list[self._position] + return metadata + + def stop(self): + self._stopped = True + + def _get_file_metadata(path, stat, fetch_preview=True): """Return the metadata from the corresponding file. @@ -436,10 +557,16 @@ def _get_file_metadata(path, stat, fetch_preview=True): dir_path = os.path.dirname(path) metadata = _get_file_metadata_from_json(dir_path, filename, fetch_preview) if metadata: + # For Documents/Shares/Mounted-Drives. + # Special case: for locally-mounted-remote-files, ensure that + # "metadata['filesize' is already present before-hand. This + # will have to be done at the time of fetching + # webdav-properties per resource. if 'filesize' not in metadata: metadata['filesize'] = stat.st_size return metadata + # For Journal. return {'uid': path, 'title': os.path.basename(path), 'timestamp': stat.st_mtime, @@ -529,11 +656,33 @@ def find(query_, page_size): raise ValueError('Exactly one mount point must be specified') if mount_points[0] == '/': + """ + For Journal. + """ return DatastoreResultSet(query, page_size) + elif is_mount_point_for_locally_mounted_remote_share(mount_points[0]): + """ + For Locally-Mounted-Remote-Shares. + Regex Matching is used, to ensure that the mount-point is an + IP-Address. + """ + return RemoteShareResultSet(mount_points[0], query) else: + """ + For Documents/Shares/Mounted-Drives. + """ return InplaceResultSet(query, page_size, mount_points[0]) +def is_mount_point_for_locally_mounted_remote_share(mount_point): + import re + + pattern = '[1-9][0-9]{0,2}\.[0-9]{0,3}\.[0-9]{0,3}\.[0-9]{0,3}' + if re.match(pattern, mount_point) is None: + return False + return True + + def _get_mount_point(path): dir_path = os.path.dirname(path) while dir_path: @@ -544,14 +693,45 @@ def _get_mount_point(path): return None +def is_locally_mounted_remote_share(path): + return string.find(path, '/tmp/') == 0 + + +def extract_ip_address_from_locally_mounted_remote_share_path(path): + """ + Path is of type :: + + /tmp/127.0.0.1/webdav/a.txt + """ + return path.split('/')[2] + + def get(object_id): """Returns the metadata for an object """ - if os.path.exists(object_id): - stat = os.stat(object_id) + if (os.path.exists(object_id) or (is_locally_mounted_remote_share(object_id))): + """ + For Documents/Shares/Mounted-Drives/Locally-Mounted-Remote-Shares, + where ".Sugar-Metadata" folder exists. + + The only thing is that, for locally-mounted-remote-shares, the + "file" is not physically present. + """ + if os.path.exists(object_id): + # if the file is physically present, derive file-metadata + # by physical examination of the file. + stat = os.stat(object_id) + else: + # if the file is remote, derive file-metadata by fetching + # properties remotely (webdav properties). + stat = None + metadata = _get_file_metadata(object_id, stat) metadata['mountpoint'] = _get_mount_point(object_id) else: + """ + For journal, where ".Sugar-Metadata" folder does not exists. + """ metadata = _get_datastore().get_properties(object_id, byte_arrays=True) metadata['mountpoint'] = '/' return metadata @@ -561,9 +741,16 @@ def get_file(object_id): """Returns the file for an object """ if os.path.exists(object_id): + """ + For Documents/Shares/Mounted-Drives/ + Locally-Mounted-Remote-Shares-in-case-when-it-is-present-already. + """ logging.debug('get_file asked for file with path %r', object_id) return object_id else: + """ + For Journal. + """ logging.debug('get_file asked for entry with id %r', object_id) file_path = _get_datastore().get_filename(object_id) if file_path: @@ -754,6 +941,20 @@ def _write_entry_on_external_device(metadata, file_path): _rename_entry_on_external_device(file_path, destination_path, metadata_dir_path) + # For "Shares" folder, we need to set the permissions of the newly + # copied file to 0777, else it will not be accessible by "httpd" + # service. + if metadata['mountpoint'] == '/var/www/web1/web': + fd = os.open(destination_path, os.O_RDONLY) + os.fchmod(fd, stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO) + os.close(fd) + + metadata_file_path = os.path.join(metadata_dir_path, file_name + '.metadata') + fd = os.open(metadata_file_path, os.O_RDONLY) + os.fchmod(fd, stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO) + os.close(fd) + + object_id = destination_path created.send(None, object_id=object_id) diff --git a/src/jarabe/journal/palettes.py b/src/jarabe/journal/palettes.py index 740b65a..46ade98 100644 --- a/src/jarabe/journal/palettes.py +++ b/src/jarabe/journal/palettes.py @@ -29,6 +29,7 @@ import gconf import gio import glib import time +import socket from sugar import _sugarext @@ -45,6 +46,10 @@ from jarabe.model import mimeregistry from jarabe.journal import misc from jarabe.journal import model +from webdav.Connection import WebdavError +from jarabe.journal.webdavmanager import get_resource_by_ip_address_and_resource_key + + friends_model = friends.get_model() _copy_menu_helper = None @@ -77,6 +82,9 @@ class ObjectPalette(Palette): Palette.__init__(self, primary_text=title, icon=activity_icon) + from jarabe.journal.journalactivity import get_mount_point + current_mount_point = get_mount_point() + if misc.get_activities(metadata) or misc.is_bundle(metadata): if metadata.get('activity_id', ''): resume_label = _('Resume') @@ -86,10 +94,15 @@ class ObjectPalette(Palette): resume_with_label = _('Start with') menu_item = MenuItem(resume_label, 'activity-start') menu_item.connect('activate', self.__start_activate_cb) + if model.is_mount_point_for_locally_mounted_remote_share(current_mount_point): + menu_item.set_sensitive(False) self.menu.append(menu_item) menu_item.show() menu_item = MenuItem(resume_with_label, 'activity-start') + if model.is_mount_point_for_locally_mounted_remote_share(current_mount_point): + menu_item.set_sensitive(False) + self.menu.append(menu_item) menu_item.show() start_with_menu = StartWithMenu(self._metadata) @@ -101,6 +114,7 @@ class ObjectPalette(Palette): self.menu.append(menu_item) menu_item.show() + menu_item = MenuItem(_('Copy to')) icon = Icon(icon_name='edit-copy', xo_color=color, icon_size=gtk.ICON_SIZE_MENU) @@ -120,16 +134,21 @@ class ObjectPalette(Palette): copy_menu.connect('volume-error', self.__volume_error_cb) menu_item.set_submenu(copy_menu) + if self._metadata['mountpoint'] == '/': menu_item = MenuItem(_('Duplicate')) icon = Icon(icon_name='edit-duplicate', xo_color=color, icon_size=gtk.ICON_SIZE_MENU) menu_item.set_image(icon) menu_item.connect('activate', self.__duplicate_activate_cb) + if model.is_mount_point_for_locally_mounted_remote_share(current_mount_point): + menu_item.set_sensitive(False) self.menu.append(menu_item) menu_item.show() menu_item = MenuItem(_('Send to'), 'document-send') + if model.is_mount_point_for_locally_mounted_remote_share(current_mount_point): + menu_item.set_sensitive(False) self.menu.append(menu_item) menu_item.show() @@ -140,14 +159,19 @@ class ObjectPalette(Palette): if detail == True: menu_item = MenuItem(_('View Details'), 'go-right') menu_item.connect('activate', self.__detail_activate_cb) + if model.is_mount_point_for_locally_mounted_remote_share(current_mount_point): + menu_item.set_sensitive(False) self.menu.append(menu_item) menu_item.show() menu_item = MenuItem(_('Erase'), 'list-remove') menu_item.connect('activate', self.__erase_activate_cb) + if model.is_mount_point_for_locally_mounted_remote_share(current_mount_point): + menu_item.set_sensitive(False) self.menu.append(menu_item) menu_item.show() + def __start_activate_cb(self, menu_item): misc.resume(self._metadata) @@ -541,6 +565,31 @@ class ActionItem(gobject.GObject): self._post_operate_per_metadata_per_action(metadata) def _file_path_valid(self, metadata): + from jarabe.journal.journalactivity import get_mount_point + current_mount_point = get_mount_point() + + # Now, for locally mounted remote-shares, download the file. + # Note that, always download the file, to avoid the problems + # of stale-cache. + if model.is_mount_point_for_locally_mounted_remote_share(current_mount_point): + file_path = metadata['uid'] + filename = os.path.basename(file_path) + ip_address = model.extract_ip_address_from_locally_mounted_remote_share_path(file_path) + resource = get_resource_by_ip_address_and_resource_key(ip_address, '/webdav/' + filename) + download_file_path = '/tmp/' + ip_address + '/' + filename + try: + resource.downloadFile(download_file_path) + return True + except (WebdavError, socket.error), e: + error_message = e + logging.warn(error_message) + if self._batch_mode: + self._handle_error_alert(error_message, metadata) + else: + self.emit('volume-error', error_message, + _('Error')) + return False + file_path = model.get_file(metadata['uid']) if not file_path or not os.path.exists(file_path): logging.warn('Entries without a file cannot be copied.') @@ -708,6 +757,25 @@ class DocumentsMenu(BaseCopyMenuItem): self._post_operate_per_metadata_per_action(metadata) +class SharesMenu(BaseCopyMenuItem): + def __init__(self, metadata_list, show_editing_alert, + show_progress_info_alert, batch_mode): + BaseCopyMenuItem.__init__(self, metadata_list, _('Shares'), + show_editing_alert, + show_progress_info_alert, + batch_mode) + + def _operate(self, metadata): + if not self._file_path_valid(metadata): + return False + if not self._metadata_copy_valid(metadata, + '/var/www/web1/web'): + return False + + # This is sync-operation. Call the post-operation now. + self._post_operate_per_metadata_per_action(metadata) + + class FriendsMenu(gtk.Menu): __gtype_name__ = 'JournalFriendsMenu' @@ -835,6 +903,17 @@ class CopyMenuHelper(gtk.Menu): menu.append(documents_menu) documents_menu.show() + if get_mount_point() != '/var/www/web1/web': + documents_menu = SharesMenu(metadata_list, + show_editing_alert, + show_progress_info_alert, + batch_mode) + documents_menu.set_image(Icon(icon_name='emblem-neighborhood-shared', + icon_size=gtk.ICON_SIZE_MENU)) + documents_menu.connect('volume-error', self.__volume_error_cb) + menu.append(documents_menu) + documents_menu.show() + if get_mount_point() != '/': client = gconf.client_get_default() color = XoColor(client.get_string('/desktop/sugar/user/color')) diff --git a/src/jarabe/journal/volumestoolbar.py b/src/jarabe/journal/volumestoolbar.py index 94914e6..1f6e3ec 100644 --- a/src/jarabe/journal/volumestoolbar.py +++ b/src/jarabe/journal/volumestoolbar.py @@ -37,8 +37,10 @@ from sugar.graphics.palette import Palette from sugar.graphics.xocolor import XoColor from sugar import env +from jarabe.frame.notification import NotificationIcon from jarabe.journal import model -from jarabe.view.palettes import JournalVolumePalette, JournalXSPalette +from jarabe.view.palettes import JournalVolumePalette, JournalXSPalette, RemoteSharePalette +import jarabe.frame _JOURNAL_0_METADATA_DIR = '.olpc.store' @@ -209,6 +211,7 @@ class VolumesToolbar(gtk.Toolbar): def _set_up_volumes(self): self._set_up_documents_button() + self._set_up_shares_button() volume_monitor = gio.volume_monitor_get() self._mount_added_hid = volume_monitor.connect('mount-added', @@ -219,12 +222,11 @@ class VolumesToolbar(gtk.Toolbar): for mount in volume_monitor.get_mounts(): self._add_button(mount) - def _set_up_documents_button(self): - documents_path = model.get_documents_path() - if documents_path is not None: - button = DocumentsButton(documents_path) + def _set_up_directory_button(self, dir_path, icon_name, label_text): + if dir_path is not None: + button = DirectoryButton(dir_path, icon_name) button.props.group = self._volume_buttons[0] - label = glib.markup_escape_text(_('Documents')) + label = glib.markup_escape_text(label_text) button.set_palette(Palette(label)) button.connect('toggled', self._button_toggled_cb) button.show() @@ -234,6 +236,39 @@ class VolumesToolbar(gtk.Toolbar): self._volume_buttons.append(button) self.show() + def _set_up_documents_button(self): + documents_path = model.get_documents_path() + self._set_up_directory_button(documents_path, + 'user-documents', + _('Documents')) + + def _set_up_shares_button(self): + shares_dir_path = '/var/www/web1/web' + self._set_up_directory_button(shares_dir_path, + 'emblem-neighborhood-shared', + _('Shares')) + + def _add_remote_share_button(self, buddy): + button = RemoteSharesButton(buddy) + button.props.group = self._volume_buttons[0] + label = glib.markup_escape_text(_('%s\'s share') % \ + buddy.props.nick) + button.set_palette(RemoteSharePalette(buddy, button)) + button.connect('toggled', self._button_toggled_cb) + button.show() + + position = self.get_item_index(self._volume_buttons[-1]) + 1 + self.insert(button, position) + self._volume_buttons.append(button) + self.show() + + frame = jarabe.frame.get_view() + notif_icon = NotificationIcon() + notif_icon.props.icon_name = 'emblem-neighborhood-shared' + notif_icon.props.xo_color = buddy.props.color + frame.add_notification(notif_icon, + gtk.CORNER_BOTTOM_RIGHT) + def __mount_added_cb(self, volume_monitor, mount): self._add_button(mount) @@ -271,8 +306,8 @@ class VolumesToolbar(gtk.Toolbar): def __volume_error_cb(self, button, strerror, severity): self.emit('volume-error', strerror, severity) - def _button_toggled_cb(self, button): - if button.props.active: + def _button_toggled_cb(self, button, force_toggle=False): + if button.props.active or force_toggle: self.emit('volume-changed', button.mount_point) def _unmount_activated_cb(self, menu_item, mount): @@ -300,6 +335,19 @@ class VolumesToolbar(gtk.Toolbar): if len(self.get_children()) < 2: self.hide() + def _remove_remote_share_button(self, mount_point): + # Here, IP_Address is the mount_point. + for button in self.get_children(): + if type(button) == RemoteSharesButton and \ + button.mount_point == mount_point: + self._volume_buttons.remove(button) + self.remove(button) + self.get_children()[0].props.active = True + + if len(sel.get_children()) < 2: + self.hide() + break; + def set_active_volume(self, mount): button = self._get_button_for_mount(mount) button.props.active = True @@ -313,6 +361,12 @@ class VolumesToolbar(gtk.Toolbar): if button.mount_point != mount_point: button.set_sensitive(sensitive) + def get_journal_button(self): + return self._volume_buttons[0] + + def get_button_toggled_cb(self): + return self._button_toggled_cb + class BaseButton(RadioToolButton): __gsignals__ = { @@ -427,18 +481,34 @@ class JournalButtonPalette(Palette): {'free_space': free_space / (1024 * 1024)} -class DocumentsButton(BaseButton): +class DirectoryButton(BaseButton): - def __init__(self, documents_path): - BaseButton.__init__(self, mount_point=documents_path) + def __init__(self, dir_path, icon_name): + BaseButton.__init__(self, mount_point=dir_path) - self.props.named_icon = 'user-documents' + self.props.named_icon = icon_name client = gconf.client_get_default() color = XoColor(client.get_string('/desktop/sugar/user/color')) self.props.xo_color = color +class RemoteSharesButton(BaseButton): + + def __init__(self, buddy): + BaseButton.__init__(self, mount_point=buddy.props.ip_address) + + self._buddy = buddy + self.props.named_icon = 'emblem-neighborhood-shared' + self.props.xo_color = buddy.props.color + self._buddy_ip_address = buddy.props.ip_address + + def create_palette(self): + palette = RemoteSharePalette(self._buddy) + return palette + + + class XSButton(ToolButton): def __init__(self): ToolButton.__init__(self) diff --git a/src/jarabe/journal/webdavmanager.py b/src/jarabe/journal/webdavmanager.py new file mode 100644 index 0000000..6cd0713 --- /dev/null +++ b/src/jarabe/journal/webdavmanager.py @@ -0,0 +1,256 @@ +from gettext import gettext as _ + +import os +import sys + +import gobject +import simplejson +import shutil + +from webdav.Connection import AuthorizationError, WebdavError +from webdav.WebdavClient import CollectionStorer + +def get_key_from_resource(resource): + return resource.path + +def ensure_correct_remote_webdav_hierarchy(remote_webdav_share_resources, + remote_webdav_share_collections): + pass + #assert len(remote_webdav_share_collections.keys()) == 1 + +class WebDavUrlManager(gobject.GObject): + """ + This class holds all data, relevant to a WebDavUrl. + + One thing must be noted, that a valid WebDavUrl is the one which + may contain zero or more resources (files), or zero or more + collections (directories). + + Thus, following are valid WebDavUrls :: + + dav://1.2.3.4/webdav + dav://1.2.3.4/webdav/dir_1 + dav://1.2.3.4/webdav/dir_1/dir_2 + + but following are not :: + + dav://1.2.3.4/webdav/a.txt + dav://1.2.3.4/webdav/dir_1/b.jpg + dav://1.2.3.4/webdab/dir_1/dir_2/c.avi + """ + + def __init__(self, WebDavUrl, username, password): + self._WebDavUrl = WebDavUrl + self._username = username + self._password = password + + def _get_key_from_resource(self, resource): + return resource.path.encode(sys.getfilesystemencoding()) + + def _get_number_of_collections(self): + return len(self._remote_webdav_share_collections) + + def _get_resources_dict(self): + return self._remote_webdav_share_resources + + def _get_collections_dict(self): + return self._remote_webdav_share_collections + + def _get_resource_by_key(self, key): + return self._remote_webdav_share_resources[key]['resource'] + + def _get_metadata_list(self): + metadata_list = [] + for key in self._remote_webdav_share_resources.keys(): + metadata_list.append(self._remote_webdav_share_resources[key]['metadata']) + return metadata_list + + def _get_live_properties(self, resource_key): + resource_container = self._remote_webdav_share_resources[resource_key] + return resource_container['webdav-properties'] + + def _set_metadata_for_resource(self, key, metadata): + self._remote_webdav_share_resources[key]['metadata'] = metadata + + def _fetch_resources_and_collections(self): + webdavConnection = CollectionStorer(self._WebDavUrl, validateResourceNames=False) + + authFailures = 0 + while authFailures < 2: + try: + self._remote_webdav_share_resources = {} + self._remote_webdav_share_collections = {} + + try: + self._collection_contents = webdavConnection.getCollectionContents() + for resource, properties in self._collection_contents: + try: + key = self._get_key_from_resource(resource) + selected_dict = None + + if properties.getResourceType() == 'resource': + selected_dict = self._remote_webdav_share_resources + else: + selected_dict = self._remote_webdav_share_collections + + selected_dict[key] = {} + selected_dict[key]['resource'] = resource + selected_dict[key]['webdav-properties'] = properties + except UnicodeEncodeError: + print("Cannot encode resource path or properties.") + + return True + + except WebdavError, e: + # Note that, we need to deal with all errors, + # except "AuthorizationError", as that is not + # really an error from our perspective. + if not type(e) == AuthorizationError: + from jarabe.journal.journalactivity import get_journal + + error_message = e + get_journal()._volume_error_cb(None, error_message,_('Error')) + + # Simply return, in case of connection-not-available. + return False + + else: + # If this indeed is an Authorization Error, + # re-raise it, so that it is caught by the outer + # "except" block. + raise e + + + except AuthorizationError, e: + if self._username is None or self._password is None: + raise Exception("WebDav username or password is None. Please specify appropriate values.") + + if e.authType == "Basic": + webdavConnection.connection.addBasicAuthorization(self._username, self._password) + elif e.authType == "Digest": + info = parseDigestAuthInfo(e.authInfo) + webdavConnection.connection.addDigestAuthorization(self._username, self._password, realm=info["realm"], qop=info["qop"], nonce=info["nonce"]) + else: + raise + authFailures += 1 + + return False + +webdav_manager = {} + + +def get_resource_by_ip_address_and_resource_key(ip_address, key): + global webdav_manager + + if ip_address in webdav_manager.keys(): + root_webdav = webdav_manager[ip_address] + resources_dict = root_webdav._get_resources_dict() + resource_dict = resources_dict[key] + resource = resource_dict['resource'] + + return resource + + +def get_remote_webdav_share_metadata(ip_address): + protocol = 'dav://' + + root_webdav_url = '/webdav' + + complete_root_url = protocol + ip_address + root_webdav_url + + root_webdav = WebDavUrlManager(complete_root_url, 'test', 'olpc') + if root_webdav._fetch_resources_and_collections() is False: + # Return empty metadata list. + return [] + + # Keep reference to the "WebDavUrlManager", keyed by IP-Address. + global webdav_manager + webdav_manager[ip_address] = root_webdav + + + # Assert that the number of collections is only one at this url + # (i.e. only ".Sugar-Metadata" is present). + assert root_webdav._get_number_of_collections() == 1 + + root_sugar_metadata_url = root_webdav_url + '/.Sugar-Metadata' + + complete_root_sugar_metadata_url = protocol + ip_address + root_sugar_metadata_url + root_webdav_sugar_metadata = WebDavUrlManager(complete_root_sugar_metadata_url, 'test', 'olpc') + if root_webdav_sugar_metadata._fetch_resources_and_collections() is False: + # Return empty metadata list. + return [] + + # assert that the number of collections is zero at this url. + assert root_webdav_sugar_metadata._get_number_of_collections() == 0 + + # Now. associate sugar-metadata with each of the "root-webdav" + # resource. + root_webdav_resources = root_webdav._get_resources_dict() + root_webdav_sugar_metadata_resources = root_webdav_sugar_metadata._get_resources_dict() + + # Prepare the metadata-download folder. + downloaded_data_root_dir = '/tmp/' + ip_address + downloaded_metadata_file_dir = downloaded_data_root_dir + '/.Sugar-Metadata' + if os.path.isdir(downloaded_data_root_dir): + shutil.rmtree(downloaded_data_root_dir) + os.makedirs(downloaded_metadata_file_dir) + + for root_webdav_resource_name in root_webdav_resources.keys(): + """ + root_webdav_resource_name is of the type :: + + /webdav/a.txt + """ + split_tokens_array = root_webdav_resource_name.split('/') + + # This will provide us with "a.txt" + basename = split_tokens_array[len(split_tokens_array) - 1] + + # This will provide us with "a.txt.metadata" + sugar_metadata_basename = basename + '.metadata' + + # Thus will provide us with "/webdav/.Sugar-Metadata/a.txt.metadata" + sugar_metadata_url = root_sugar_metadata_url + '/' + sugar_metadata_basename + + # Ensure that "sugar_metadata_url" is present as one of the + # keys in "root_webdav_sugar_metadata_resources" + assert sugar_metadata_url in root_webdav_sugar_metadata_resources.keys() + + # Now download the metadata file, read its contents, and store + # the metadata in memory. + # It is assumed that the metadata-file is small enough to be + # read in one call to "read". + + downloaded_metadata_file_path = downloaded_metadata_file_dir + '/' + sugar_metadata_basename + metadata_resource = root_webdav_sugar_metadata._get_resource_by_key(sugar_metadata_url) + metadata_resource.downloadFile(downloaded_metadata_file_path) + + file_pointer = open(downloaded_metadata_file_path) + metadata = eval(file_pointer.read()) + file_pointer.close() + + # Very critical. + # 1. CRITICAL ONE: + # Fill in the uid. + # Note that the file is not physically present. + metadata['uid'] = downloaded_data_root_dir + '/' + basename + + # 2. CRITICAL TWO: + # Fill in the properties, that can only be done by reading + # in the webdav-properties. + live_properties = root_webdav._get_live_properties(root_webdav_resource_name) + metadata['filesize'] = live_properties.getContentLength() + metadata['timestamp'] = live_properties.getLastModified() + metadata['creation_time'] = live_properties.getCreationDate() + + # Now, write this to the metadata-file, so that + # webdav-properties get gelled into sugar-metadata. + + file_pointer = open(downloaded_metadata_file_path, 'w') + file_pointer.write(simplejson.dumps(metadata)) + file_pointer.close() + + root_webdav._set_metadata_for_resource(root_webdav_resource_name, + metadata) + + return root_webdav._get_metadata_list() |