Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/src/jarabe/journal
diff options
context:
space:
mode:
Diffstat (limited to 'src/jarabe/journal')
-rw-r--r--src/jarabe/journal/Makefile.am5
-rw-r--r--src/jarabe/journal/journalactivity.py3
-rw-r--r--src/jarabe/journal/journaltoolbox.py7
-rw-r--r--src/jarabe/journal/listview.py9
-rw-r--r--src/jarabe/journal/model.py205
-rw-r--r--src/jarabe/journal/palettes.py79
-rw-r--r--src/jarabe/journal/volumestoolbar.py94
-rw-r--r--src/jarabe/journal/webdavmanager.py256
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()