Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAjay Garg <ajay@activitycentral.com>2012-10-02 14:59:50 (GMT)
committer Ajay Garg <ajay@activitycentral.com>2012-10-02 14:59:50 (GMT)
commit136ee50fb8588b3850cd5511c0958cb465417bf3 (patch)
treec5d54436650a1d8e82c384e2b151b5d0c98f6b2a
parent356d2706f016cbc223480036e800f5a01d0cde54 (diff)
Unifying things as far as possible.
-rw-r--r--rpms/sugar/0130-1-to-N-feature-via-School-Server.patch2109
-rw-r--r--rpms/sugar/0134-translation-fix-closes-sdxo-2218.patch4
-rw-r--r--rpms/sugar/0138-sdxo-2338-Hide-the-alert-if-any-automatically-when-s.patch26
-rw-r--r--rpms/sugar/0139-sdxo-2341-Now-renaming-journal-entries-work-in-Local.patch35
-rw-r--r--rpms/sugar/0140-sdxo-2342-Enable-View-Details-for-remote-shares.patch26
-rw-r--r--rpms/sugar/0141-sdxo-2343-sdxo-2344-Disable-Start-and-Erase-buttons-.patch100
-rw-r--r--rpms/sugar/0142-sdxo-2345-Now-favorite-buttons-work-as-desired.patch77
-rw-r--r--rpms/sugar/0143-sdxo-2337-Remove-the-trailing-x00-character-from-ser.patch30
-rw-r--r--rpms/sugar/0144-sdxo-2339-Metadata-and-Preview-files-must-never-be-d.patch41
-rw-r--r--rpms/sugar/0146-sdxo-2361-Minor-fix-for-major-bug.patch31
-rw-r--r--rpms/sugar/0147-sdxo-2364-Do-not-show-checkboxes-in-ObjectChooser.patch77
-rw-r--r--rpms/sugar/0148-sdxo-2370-Hide-the-alert-if-any-when-remote-share-is.patch27
-rw-r--r--rpms/sugar/0149-sdxo-2379-Fix-the-logic-of-computing-XO-Serial-Numbe.patch62
-rw-r--r--rpms/sugar/0150-Removing-duplicate-code-instead-using-the-helper-mis.patch65
-rw-r--r--rpms/sugar/0151-sdxo-2346-Now-hovering-clicking-of-favorite-icons-in.patch132
-rw-r--r--rpms/sugar/0152-sdxo-2336-Now-copy-to-options-are-shown-even-without.patch35
-rw-r--r--rpms/sugar/0153-Disable-1-to-N-feature.patch86
-rw-r--r--rpms/sugar/0154-sdxo-1592-PART-1-Bringing-no-hippo-intro-pages-into-.patch352
-rw-r--r--rpms/sugar/0155-sdxo-1592-PART-2-Incorporating-Age-Page-at-boot-up.patch155
19 files changed, 3468 insertions, 2 deletions
diff --git a/rpms/sugar/0130-1-to-N-feature-via-School-Server.patch b/rpms/sugar/0130-1-to-N-feature-via-School-Server.patch
new file mode 100644
index 0000000..1b63172
--- /dev/null
+++ b/rpms/sugar/0130-1-to-N-feature-via-School-Server.patch
@@ -0,0 +1,2109 @@
+From 507b3ad4a7392c0bab27d51180c4be75c5d5078f Mon Sep 17 00:00:00 2001
+From: Ajay Garg <ajay@activitycentral.com>
+Date: Tue, 18 Sep 2012 15:04:16 +0530
+Subject: [sugar PATCH] 1-to-N-feature via Peer-to-Peer mechanism; and Via-School-Server mechanism
+Organization: Sugar Labs Foundation
+Signed-off-by: Ajay Garg <ajay@activitycentral.com>
+---
+ src/jarabe/frame/activitiestray.py | 5 +
+ src/jarabe/frame/frame.py | 3 +
+ src/jarabe/intro/window.py | 12 +
+ src/jarabe/journal/expandedentry.py | 24 ++-
+ src/jarabe/journal/journalactivity.py | 19 ++-
+ src/jarabe/journal/journaltoolbox.py | 44 ++++-
+ src/jarabe/journal/listview.py | 15 +-
+ src/jarabe/journal/model.py | 388 +++++++++++++++++++++++----------
+ src/jarabe/journal/palettes.py | 303 ++++++++++++++++++++++---
+ src/jarabe/journal/volumestoolbar.py | 135 ++++++++----
+ src/jarabe/journal/webdavmanager.py | 172 ++++++++++-----
+ src/jarabe/view/buddymenu.py | 45 ++++-
+ src/jarabe/view/palettes.py | 35 +++-
+ src/webdav/Connection.py | 11 +-
+ src/webdav/WebdavClient.py | 16 +-
+ src/webdav/davlib.py | 9 +-
+ 16 files changed, 942 insertions(+), 294 deletions(-)
+
+diff --git a/src/jarabe/frame/activitiestray.py b/src/jarabe/frame/activitiestray.py
+index 55e0c31..7cbeefb 100644
+--- a/src/jarabe/frame/activitiestray.py
++++ b/src/jarabe/frame/activitiestray.py
+@@ -249,6 +249,8 @@ class ActivitiesTray(HTray):
+ # JournalActivity is always the first activity to be added.
+ # Broadcast the signal-of-its-creation.
+ if group is None:
++ self._journal_button = button
++ self._journal_activity = home_activity
+ self._signal_addition_of_journal_activity()
+
+ def _signal_addition_of_journal_activity(self):
+@@ -306,6 +308,9 @@ class ActivitiesTray(HTray):
+ if window:
+ window.activate(gtk.get_current_event_time())
+
++ def _show_journal_activity(self):
++ self.__activity_clicked_cb(self._journal_button, self._journal_activity)
++
+ def __remove_invite_cb(self, icon, invite):
+ self._invites.remove_invite(invite)
+
+diff --git a/src/jarabe/frame/frame.py b/src/jarabe/frame/frame.py
+index cd1dc20..821e31f 100644
+--- a/src/jarabe/frame/frame.py
++++ b/src/jarabe/frame/frame.py
+@@ -449,3 +449,6 @@ class Frame(object):
+ # Do nothing for now. Our notification UI is so simple, there's no
+ # point yet.
+ pass
++
++ def switch_to_journal_activity(self):
++ self._activities_tray._show_journal_activity()
+diff --git a/src/jarabe/intro/window.py b/src/jarabe/intro/window.py
+index df19fbf..c4ae56e 100644
+--- a/src/jarabe/intro/window.py
++++ b/src/jarabe/intro/window.py
+@@ -56,6 +56,18 @@ def create_profile(name, color=None):
+ else:
+ logging.error('Keypair exists, skip generation.')
+
++ # Also, generate SSL key and cert pair; to be used in secure
++ # sharing of webdav entries via httpd.
++ keypath = os.path.join(env.get_profile_path(), 'ssl.key')
++ certpath = os.path.join(env.get_profile_path(), 'ssl.crt')
++
++ cmd = 'openssl req -new -newkey rsa:1024 -days 365 -nodes ' + \
++ '-x509 -subj "/C=US/ST=Denial/L=Springfield/O=Dis/CN=www.example.com" ' + \
++ '-keyout %s -out %s' % (keypath, certpath)
++ (s, o) = commands.getstatusoutput(cmd)
++ if s != 0:
++ logging.error('Could not generate ssl key and cert: %d %s', s, o)
++
+
+ class _Page(hippo.CanvasBox):
+ __gproperties__ = {
+diff --git a/src/jarabe/journal/expandedentry.py b/src/jarabe/journal/expandedentry.py
+index 03f8cd1..1e857ba 100644
+--- a/src/jarabe/journal/expandedentry.py
++++ b/src/jarabe/journal/expandedentry.py
+@@ -166,13 +166,35 @@ class ExpandedEntry(hippo.CanvasBox):
+ self._buddy_list.append(self._create_buddy_list())
+
+ description = self._description.text_view_widget
+- description.props.buffer.props.text = metadata.get('description', '')
++
++ # TRANS: Do not translate the """%s""".
++ uploader_nick_text = self.__create_text_description(
++ _('Source XO Nick :: \n%s'), metadata.get('uploader-nick', ''))
++
++ # TRANS: Do not translate the """%s""".
++ uploader_serial_text = self.__create_text_description(
++ _('Source XO Serial Number :: \n%s'), metadata.get('uploader-serial', ''))
++
++ # TRANS: Do not translate the """%s""".
++ misc_info_text = self.__create_text_description(
++ _('Misellaneous Information :: \n%s'), metadata.get('description', ''))
++
++ description.props.buffer.props.text = uploader_nick_text + \
++ uploader_serial_text + \
++ misc_info_text
++
+ description.props.editable = model.is_editable(metadata)
+
+ tags = self._tags.text_view_widget
+ tags.props.buffer.props.text = metadata.get('tags', '')
+ tags.props.editable = model.is_editable(metadata)
+
++ def __create_text_description(self, heading, value):
++ if (value == '') or (value is None):
++ return ''
++
++ return ((heading % value) + '\n\n')
++
+ def _create_keep_icon(self):
+ keep_icon = KeepIcon(False)
+ keep_icon.connect('activated', self._keep_icon_activated_cb)
+diff --git a/src/jarabe/journal/journalactivity.py b/src/jarabe/journal/journalactivity.py
+index fc4773d..f902027 100644
+--- a/src/jarabe/journal/journalactivity.py
++++ b/src/jarabe/journal/journalactivity.py
+@@ -180,10 +180,14 @@ class JournalActivity(JournalWindow):
+ self._check_available_space()
+
+ def __volume_error_cb(self, gobject, message, severity):
+- alert = ErrorAlert(title=severity, msg=message)
+- alert.connect('response', self.__alert_response_cb)
+- self.add_alert(alert)
+- alert.show()
++ self.update_title_and_message(self._error_alert, severity,
++ message)
++ self._callback = None
++ self._data = None
++ self.update_alert(self._error_alert)
++
++ def _show_alert(self, message, severity):
++ self.__volume_error_cb(None, message, severity)
+
+ def _volume_error_cb(self, gobject, message, severity):
+ self.update_error_alert(severity, message, None, None)
+@@ -418,9 +422,11 @@ class JournalActivity(JournalWindow):
+ # (re)-switch, only if not already.
+ if (switch) and (not self._editing_mode):
+ self._editing_mode = True
++ self.get_list_view().disable_drag_and_copy()
+ self.show_main_view()
+ elif (not switch) and (self._editing_mode):
+ self._editing_mode = False
++ self.get_list_view().enable_drag_and_copy()
+ self.show_main_view()
+
+ def get_list_view(self):
+@@ -449,6 +455,8 @@ class JournalActivity(JournalWindow):
+ self.remove_alert(self._current_alert)
+ self.add_alert(alert)
+
++ self.remove_alert(self._current_alert)
++ self.add_alert(alert)
+ self._current_alert = alert
+ self._current_alert.show()
+ show_normal_cursor()
+@@ -475,6 +483,9 @@ class JournalActivity(JournalWindow):
+ self._data = data
+ self.update_alert(self._confirmation_alert)
+
++ def update_progress(self, fraction):
++ self.get_toolbar_box().update_progress(fraction)
++
+ def get_metadata_list(self, selected_state):
+ metadata_list = []
+
+diff --git a/src/jarabe/journal/journaltoolbox.py b/src/jarabe/journal/journaltoolbox.py
+index 6b2494e..b1c0cac 100644
+--- a/src/jarabe/journal/journaltoolbox.py
++++ b/src/jarabe/journal/journaltoolbox.py
+@@ -78,6 +78,16 @@ class MainToolbox(Toolbox):
+ self.add_toolbar(_('Search'), self.search_toolbar)
+ self.search_toolbar.show()
+
++ self._info_widget = MultiSelectEntriesInfoWidget()
++ self.add(self._info_widget)
++ self._info_widget.hide()
++
++ def update_progress(self, fraction):
++ self._info_widget.update_progress(fraction)
++
++ def hide_info_widget(self):
++ self._info_widget.hide()
++
+
+ class SearchToolbar(gtk.Toolbar):
+ __gtype_name__ = 'SearchToolbar'
+@@ -532,6 +542,9 @@ class EditToolbox(Toolbox):
+ def get_current_entry_number(self):
+ return self.edit_toolbar._get_current_entry_number()
+
++ def update_progress(self, fraction):
++ self.edit_toolbar.get_multi_select_info_widget().update_progress(fraction)
++
+
+ class EditToolbar(gtk.Toolbar):
+ def __init__(self):
+@@ -552,6 +565,9 @@ class EditToolbar(gtk.Toolbar):
+
+ self.show_all()
+
++ def get_multi_select_info_widget(self):
++ return self._multi_select_info_widget
++
+ def _set_total_number_of_entries(self, total):
+ self._multi_select_info_widget.set_total_number_of_entries(total)
+
+@@ -703,16 +719,40 @@ class MultiSelectEntriesInfoWidget(gtk.ToolItem):
+ def __init__(self):
+ gtk.ToolItem.__init__(self)
+
++ self._box = gtk.VBox()
+ self._selected_entries = 0
+
+ self._label = gtk.Label()
+- self.add(self._label)
++ self._box.pack_start(self._label, expand=False)
++
++ self._progress_label = gtk.Label()
++ self._box.pack_start(self._progress_label, expand=False)
++
++ self.add(self._box)
+
+ self.show_all()
++ self._box.show_all()
++ self._progress_label.hide()
+
+ def set_total_number_of_entries(self, total):
+ self._total = total
+
++ def update_progress(self, fraction):
++ percent = '%.02f' % (fraction * 100)
++
++ # TRANS: Do not translate %.02f
++ text = '%.02f%% complete' % (fraction * 100)
++ if (str(percent) != '100.00') and (str(percent).endswith('00')):
++ self._progress_label.set_text(text)
++ self._progress_label.show()
++ self.show_all()
++ gtk.gdk.window_process_all_updates()
++ else:
++ self._progress_label.hide()
++ from jarabe.journal.journalactivity import get_journal
++ if not get_journal().is_editing_mode_present():
++ self.hide()
++
+ def update_text(self, primary_text, secondary_text, special_action,
+ update_selected_entries):
+ # If "special_action" is None,
+@@ -749,6 +789,8 @@ class MultiSelectEntriesInfoWidget(gtk.ToolItem):
+ self._label.set_text(message)
+ self._label.show()
+
++ gtk.gdk.window_process_all_updates()
++
+ def get_current_entry_number(self):
+ return self._selected_entries
+
+diff --git a/src/jarabe/journal/listview.py b/src/jarabe/journal/listview.py
+index 333d88a..93ca98d 100644
+--- a/src/jarabe/journal/listview.py
++++ b/src/jarabe/journal/listview.py
+@@ -106,10 +106,7 @@ class BaseListView(gtk.Bin):
+ self._inhibit_refresh = False
+ self._selected_entries = 0
+
+- self.tree_view.enable_model_drag_source(gtk.gdk.BUTTON1_MASK,
+- [('text/uri-list', 0, 0),
+- ('journal-object-id', 0, 0)],
+- gtk.gdk.ACTION_COPY)
++ self.enable_drag_and_copy()
+
+ # Auto-update stuff
+ self._fully_obscured = True
+@@ -121,6 +118,16 @@ class BaseListView(gtk.Bin):
+ model.updated.connect(self.__model_updated_cb)
+ model.deleted.connect(self.__model_deleted_cb)
+
++
++ def enable_drag_and_copy(self):
++ self.tree_view.enable_model_drag_source(gtk.gdk.BUTTON1_MASK,
++ [('text/uri-list', 0, 0),
++ ('journal-object-id', 0, 0)],
++ gtk.gdk.ACTION_COPY)
++
++ def disable_drag_and_copy(self):
++ self.tree_view.unset_rows_drag_source()
++
+ def __model_created_cb(self, sender, signal, object_id):
+ if self._is_new_item_visible(object_id):
+ self._set_dirty()
+diff --git a/src/jarabe/journal/model.py b/src/jarabe/journal/model.py
+index 422e947..1a47556 100644
+--- a/src/jarabe/journal/model.py
++++ b/src/jarabe/journal/model.py
+@@ -43,7 +43,7 @@ from sugar import dispatch
+ from sugar import mime
+ from sugar import util
+
+-from jarabe.journal.webdavmanager import get_remote_webdav_share_metadata
++from jarabe.journal import webdavmanager
+
+ DS_DBUS_SERVICE = 'org.laptop.sugar.DataStore'
+ DS_DBUS_INTERFACE = 'org.laptop.sugar.DataStore'
+@@ -58,6 +58,9 @@ PROPERTIES = ['activity', 'activity_id', 'buddies', 'bundle_id',
+ MIN_PAGES_TO_CACHE = 3
+ MAX_PAGES_TO_CACHE = 5
+
++WEBDAV_MOUNT_POINT = '/tmp/'
++LOCAL_SHARES_MOUNT_POINT = '/var/www/web1/web/'
++
+ JOURNAL_METADATA_DIR = '.Sugar-Metadata'
+
+ _datastore = None
+@@ -66,6 +69,65 @@ updated = dispatch.Signal()
+ deleted = dispatch.Signal()
+
+
++SCHOOL_SERVER_IP_ADDRESS_OR_DNS_NAME_PATH = \
++ '/desktop/sugar/network/school_server_ip_address_or_dns_name'
++
++client = gconf.client_get_default()
++SCHOOL_SERVER_IP_ADDRESS_OR_DNS_NAME = client.get_string(SCHOOL_SERVER_IP_ADDRESS_OR_DNS_NAME_PATH)
++
++
++def is_school_server_present():
++ return not (SCHOOL_SERVER_IP_ADDRESS_OR_DNS_NAME is None)
++
++
++def _get_mount_point(path):
++ dir_path = os.path.dirname(path)
++ while dir_path:
++ if os.path.ismount(dir_path):
++ return dir_path
++ else:
++ dir_path = dir_path.rsplit(os.sep, 1)[0]
++ return None
++
++
++def _check_remote_sharing_mount_point(mount_point, share_type):
++ from jarabe.journal.journalactivity import get_journal
++
++ mount_point_button = get_journal().get_volumes_toolbar()._get_button_for_mount_point(mount_point)
++ if mount_point_button._share_type == share_type:
++ return True
++ return False
++
++
++def is_mount_point_for_school_server(mount_point):
++ from jarabe.journal.volumestoolbar import SHARE_TYPE_SCHOOL_SERVER
++ return _check_remote_sharing_mount_point(mount_point, SHARE_TYPE_SCHOOL_SERVER)
++
++
++def is_mount_point_for_peer_share(mount_point):
++ from jarabe.journal.volumestoolbar import SHARE_TYPE_PEER
++ return _check_remote_sharing_mount_point(mount_point, SHARE_TYPE_PEER)
++
++
++def extract_ip_address_or_dns_name_from_locally_mounted_remote_share_path(path):
++ """
++ Path is of type ::
++
++ /tmp/1.2.3.4/webdav/a.txt; OR
++ /tmp/this.is.dns.name/a.txt
++ """
++ return path.split('/')[2]
++
++
++def is_mount_point_for_locally_mounted_remote_share(mount_point):
++ """
++ The mount-point can be either of the ip-Address, or the DNS name.
++ More importantly, whatever the "name" be, it does NOT have a
++ forward-slash.
++ """
++ return mount_point.find(WEBDAV_MOUNT_POINT) == 0
++
++
+ class _Cache(object):
+
+ __gtype_name__ = 'model_Cache'
+@@ -430,8 +492,8 @@ class InplaceResultSet(BaseResultSet):
+
+
+ class RemoteShareResultSet(object):
+- def __init__(self, ip_address, query):
+- self._ip_address = ip_address
++ def __init__(self, ip_address_or_dns_name, query):
++ self._ip_address_or_dns_name = ip_address_or_dns_name
+ self._file_list = []
+
+ self.ready = dispatch.Signal()
+@@ -464,7 +526,11 @@ class RemoteShareResultSet(object):
+ self._sort = query.get('order_by', ['+timestamp'])[0]
+
+ def setup(self):
+- metadata_list_complete = get_remote_webdav_share_metadata(self._ip_address)
++ try:
++ metadata_list_complete = webdavmanager.get_remote_webdav_share_metadata(self._ip_address_or_dns_name)
++ except Exception, e:
++ metadata_list_complete = []
++
+ for metadata in metadata_list_complete:
+
+ add_to_list = False
+@@ -553,15 +619,8 @@ def _get_file_metadata(path, stat, fetch_preview=True):
+ metadata based on the file properties.
+
+ """
+- filename = os.path.basename(path)
+- dir_path = os.path.dirname(path)
+- metadata = _get_file_metadata_from_json(dir_path, filename, fetch_preview)
++ metadata = _get_file_metadata_from_json(path, 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:
+ if stat is not None:
+ metadata['filesize'] = stat.st_size
+@@ -582,17 +641,26 @@ def _get_file_metadata(path, stat, fetch_preview=True):
+ 'description': path}
+
+
+-def _get_file_metadata_from_json(dir_path, filename, fetch_preview):
++def _get_file_metadata_from_json(path, fetch_preview):
+ """Read the metadata from the json file and the preview
+ stored on the external device.
+
+ If the metadata is corrupted we do remove it and the preview as well.
+
+ """
++ filename = os.path.basename(path)
++ dir_path = os.path.dirname(path)
++
++ # In case of nested mount-points, (eg. ~/Documents/in1/in2/in3.txt),
++ # "dir_path = ~/Documents/in1/in2"; while
++ # "metadata_dir_path = ~/Documents".
++ from jarabe.journal.journalactivity import get_mount_point
++ metadata_dir_path = get_mount_point()
++
+ metadata = None
+- metadata_path = os.path.join(dir_path, JOURNAL_METADATA_DIR,
++ metadata_path = os.path.join(metadata_dir_path, JOURNAL_METADATA_DIR,
+ filename + '.metadata')
+- preview_path = os.path.join(dir_path, JOURNAL_METADATA_DIR,
++ preview_path = os.path.join(metadata_dir_path, JOURNAL_METADATA_DIR,
+ filename + '.preview')
+
+ if not os.path.exists(metadata_path):
+@@ -670,7 +738,8 @@ def find(query_, page_size):
+ Regex Matching is used, to ensure that the mount-point is an
+ IP-Address.
+ """
+- return RemoteShareResultSet(mount_points[0], query)
++ ip_address = extract_ip_address_or_dns_name_from_locally_mounted_remote_share_path(mount_points[0])
++ return RemoteShareResultSet(ip_address, query)
+ else:
+ """
+ For Documents/Shares/Mounted-Drives.
+@@ -678,56 +747,13 @@ def find(query_, page_size):
+ 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:
+- if os.path.ismount(dir_path):
+- return dir_path
+- else:
+- dir_path = dir_path.rsplit(os.sep, 1)[0]
+- 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 (object_id[0] == '/'):
+- """
+- 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 filesize-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)
+@@ -812,7 +838,21 @@ def delete(object_id):
+ def copy(metadata, mount_point):
+ """Copies an object to another mount point
+ """
++ # In all cases (except one), "copy" means the actual duplication of
++ # the content.
++ # Only in case of remote downloading, the content is first copied
++ # to "/tmp" folder. In those cases, copying would refer to a mere
++ # renaming.
++ transfer_ownership = False
++
++ from jarabe.journal.journalactivity import get_mount_point
++ current_mount_point = get_mount_point()
++
++ if is_mount_point_for_locally_mounted_remote_share(current_mount_point):
++ transfer_ownership = True
++
+ metadata = get(metadata['uid'])
++
+ if mount_point == '/' and metadata['icon-color'] == '#000000,#ffffff':
+ client = gconf.client_get_default()
+ metadata['icon-color'] = client.get_string('/desktop/sugar/user/color')
+@@ -823,7 +863,7 @@ def copy(metadata, mount_point):
+ metadata['mountpoint'] = mount_point
+ del metadata['uid']
+
+- return write(metadata, file_path, transfer_ownership=False)
++ return write(metadata, file_path, transfer_ownership=transfer_ownership)
+
+
+ def write(metadata, file_path='', update_mtime=True, transfer_ownership=True):
+@@ -845,27 +885,105 @@ def write(metadata, file_path='', update_mtime=True, transfer_ownership=True):
+ object_id = _get_datastore().create(dbus.Dictionary(metadata),
+ file_path,
+ transfer_ownership)
+- else:
+- # HACK: For documents: modify the mount-point
+- from jarabe.journal.journalactivity import get_mount_point
+- if get_mount_point() == get_documents_path():
+- metadata['mountpoint'] = get_documents_path()
++ elif metadata.get('mountpoint', '/') == (WEBDAV_MOUNT_POINT + SCHOOL_SERVER_IP_ADDRESS_OR_DNS_NAME):
++ filename = metadata['title']
++
++ ip_address_or_dns_name = SCHOOL_SERVER_IP_ADDRESS_OR_DNS_NAME
++ webdavmanager.get_remote_webdav_share_metadata(ip_address_or_dns_name)
++
++ data_webdav_manager = \
++ webdavmanager.get_data_webdav_manager(ip_address_or_dns_name)
++ metadata_webdav_manager = \
++ webdavmanager.get_metadata_webdav_manager(ip_address_or_dns_name)
++
++
++ # If we get a resource by this name, there is already an entry
++ # on the server with this name; we do not want to do any
++ # overwrites.
++ data_resource = webdavmanager.get_resource_by_resource_key(data_webdav_manager,
++ '/webdav/' + filename)
++ metadata_resource = webdavmanager.get_resource_by_resource_key(metadata_webdav_manager,
++ '/webdav/.Sugar-Metadata/' + filename + '.metadata')
++ if (data_resource is not None) or (metadata_resource is not None):
++ raise Exception(_('Entry already present on the server with '
++ 'this name. Try again after renaming.'))
++
++ # No entry for this name present.
++ # So, first write the metadata- and preview-file to temporary
++ # locations.
++ metadata_file_path, preview_file_path = \
++ _write_metadata_and_preview_files_and_return_file_paths(metadata,
++ filename)
++
++ # Finally,
++ # Upload the data file.
++ webdavmanager.add_resource_by_resource_key(data_webdav_manager,
++ filename,
++ file_path)
++
++ # Upload the preview file.
++ if preview_file_path is not None:
++ webdavmanager.add_resource_by_resource_key(metadata_webdav_manager,
++ filename + '.preview',
++ preview_file_path)
++
++ # Upload the metadata file.
++ #
++ # Note that this needs to be the last step. If there was any
++ # error uploading the data- or the preview-file, control would
++ # not reach here.
++ #
++ # In other words, the control reaches here only if the data-
++ # and the preview- files have been uploaded. Finally, IF this
++ # file is successfully uploaded, we have the guarantee that all
++ # files for a particular journal entry are in place.
++ webdavmanager.add_resource_by_resource_key(metadata_webdav_manager,
++ filename + '.metadata',
++ metadata_file_path)
++
++
++ object_id = 'doesn\'t matter'
+
+- object_id = _write_entry_on_external_device(metadata, file_path)
++ else:
++ object_id = _write_entry_on_external_device(metadata,
++ file_path,
++ transfer_ownership)
+
+ return object_id
+
+
+-def _rename_entry_on_external_device(file_path, destination_path,
+- metadata_dir_path):
++def make_file_fully_permissible(file_path):
++ fd = os.open(file_path, os.O_RDONLY)
++ os.fchmod(fd, stat.S_IRWXU | stat.S_IRWXG |stat.S_IRWXO)
++ os.close(fd)
++
++
++def _rename_entry_on_external_device(file_path, destination_path):
+ """Rename an entry with the associated metadata on an external device."""
+ old_file_path = file_path
+ if old_file_path != destination_path:
+- os.rename(file_path, destination_path)
++ # Strangely, "os.rename" works fine on sugar-jhbuild, but fails
++ # on XOs, wih the OSError 13 ("invalid cross-device link"). So,
++ # using the system call "mv".
++ os.system('mv "%s" "%s"' % (file_path, destination_path))
++ make_file_fully_permissible(destination_path)
++
++
++ # In renaming, we want to delete the metadata-, and preview-
++ # files of the current mount-point, and not the destination
++ # mount-point.
++ # But we also need to ensure that the directory of
++ # 'old_file_path' and 'destination_path' are not same.
++ if os.path.dirname(old_file_path) == os.path.dirname(destination_path):
++ return
++
++ from jarabe.journal.journalactivity import get_mount_point
++ source_metadata_dir_path = get_mount_point() + '/.Sugar-Metadata'
++
+ old_fname = os.path.basename(file_path)
+- old_files = [os.path.join(metadata_dir_path,
++ old_files = [os.path.join(source_metadata_dir_path,
+ old_fname + '.metadata'),
+- os.path.join(metadata_dir_path,
++ os.path.join(source_metadata_dir_path,
+ old_fname + '.preview')]
+ for ofile in old_files:
+ if os.path.exists(ofile):
+@@ -876,41 +994,32 @@ def _rename_entry_on_external_device(file_path, destination_path,
+ 'for file=%s', ofile, old_fname)
+
+
+-def _write_entry_on_external_device(metadata, file_path):
+- """Create and update an entry copied from the
+- DS to an external storage device.
+-
+- Besides copying the associated file a file for the preview
+- and one for the metadata are stored in the hidden directory
+- .Sugar-Metadata.
+-
+- This function handles renames of an entry on the
+- external device and avoids name collisions. Renames are
+- handled failsafe.
+-
+- """
+- if 'uid' in metadata and os.path.exists(metadata['uid']):
+- file_path = metadata['uid']
++def _write_metadata_and_preview_files_and_return_file_paths(metadata,
++ file_name):
++ metadata_copy = metadata.copy()
++ metadata_copy.pop('mountpoint', None)
++ metadata_copy.pop('uid', None)
+
+- if not file_path or not os.path.exists(file_path):
+- raise ValueError('Entries without a file cannot be copied to '
+- 'removable devices')
+
+- if not metadata.get('title'):
+- metadata['title'] = _('Untitled')
+- file_name = get_file_name(metadata['title'], metadata['mime_type'])
++ # For copying to School-Server, we need to retain this property.
++ # Else wise, I have no idea why this property is being removed !!
++ if (metadata.get('mountpoint', '/') != (WEBDAV_MOUNT_POINT + SCHOOL_SERVER_IP_ADDRESS_OR_DNS_NAME)) and \
++ (metadata.get('mountpoint', '/') != LOCAL_SHARES_MOUNT_POINT):
++ metadata_copy.pop('filesize', None)
+
+- destination_path = os.path.join(metadata['mountpoint'], file_name)
+- if destination_path != file_path:
+- file_name = get_unique_file_name(metadata['mountpoint'], file_name)
+- destination_path = os.path.join(metadata['mountpoint'], file_name)
+- clean_name, extension_ = os.path.splitext(file_name)
+- metadata['title'] = clean_name
++ # For journal case, there is the special treatment.
++ if metadata.get('mountpoint', '/') == '/':
++ if metadata.get('uid', ''):
++ object_id = _get_datastore().update(metadata['uid'],
++ dbus.Dictionary(metadata),
++ '',
++ False)
++ else:
++ object_id = _get_datastore().create(dbus.Dictionary(metadata),
++ '',
++ False)
++ return
+
+- metadata_copy = metadata.copy()
+- metadata_copy.pop('mountpoint', None)
+- metadata_copy.pop('uid', None)
+- metadata_copy.pop('filesize', None)
+
+ metadata_dir_path = os.path.join(metadata['mountpoint'],
+ JOURNAL_METADATA_DIR)
+@@ -939,25 +1048,64 @@ def _write_entry_on_external_device(metadata, file_path):
+ os.close(fh)
+ os.rename(fn, os.path.join(metadata_dir_path, preview_fname))
+
+- if not os.path.dirname(destination_path) == os.path.dirname(file_path):
+- shutil.copy(file_path, destination_path)
++ metadata_destination_path = os.path.join(metadata_dir_path, file_name + '.metadata')
++ make_file_fully_permissible(metadata_destination_path)
++ if preview:
++ preview_destination_path = os.path.join(metadata_dir_path, preview_fname)
++ make_file_fully_permissible(preview_destination_path)
+ else:
+- _rename_entry_on_external_device(file_path, destination_path,
+- metadata_dir_path)
++ preview_destination_path = None
++
++ return (metadata_destination_path, preview_destination_path)
++
++
++def update_only_metadata_and_preview_files_and_return_file_paths(metadata):
++ file_name = get_file_name(metadata['title'], metadata['mime_type'])
++ _write_metadata_and_preview_files_and_return_file_paths(metadata,
++ file_name)
++
++
++def _write_entry_on_external_device(metadata, file_path,
++ transfer_ownership):
++ """Create and update an entry copied from the
++ DS to an external storage device.
++
++ Besides copying the associated file a file for the preview
++ and one for the metadata are stored in the hidden directory
++ .Sugar-Metadata.
++
++ This function handles renames of an entry on the
++ external device and avoids name collisions. Renames are
++ handled failsafe.
++
++ """
++ if 'uid' in metadata and os.path.exists(metadata['uid']):
++ file_path = metadata['uid']
++
++ if not file_path or not os.path.exists(file_path):
++ raise ValueError('Entries without a file cannot be copied to '
++ 'removable devices')
++
++ if not metadata.get('title'):
++ metadata['title'] = _('Untitled')
++ file_name = get_file_name(metadata['title'], metadata['mime_type'])
+
+- # 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)
++ destination_path = os.path.join(metadata['mountpoint'], file_name)
++ if destination_path != file_path:
++ file_name = get_unique_file_name(metadata['mountpoint'], file_name)
++ destination_path = os.path.join(metadata['mountpoint'], file_name)
++ clean_name, extension_ = os.path.splitext(file_name)
++ metadata['title'] = clean_name
+
+- 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)
++ _write_metadata_and_preview_files_and_return_file_paths(metadata,
++ file_name)
+
++ if (os.path.dirname(destination_path) == os.path.dirname(file_path)) or \
++ (transfer_ownership == True):
++ _rename_entry_on_external_device(file_path, destination_path)
++ else:
++ shutil.copy(file_path, destination_path)
++ make_file_fully_permissible(destination_path)
+
+ object_id = destination_path
+ created.send(None, object_id=object_id)
+@@ -1013,7 +1161,11 @@ def is_editable(metadata):
+ # called, upon an entry in the context of a singular
+ # mount-point.
+ from jarabe.journal.journalactivity import get_mount_point
+- return os.access(get_mount_point(), os.W_OK)
++ mount_point = get_mount_point()
++
++ if is_mount_point_for_locally_mounted_remote_share(mount_point):
++ return False
++ return os.access(mount_point, os.W_OK)
+
+
+ def get_documents_path():
+diff --git a/src/jarabe/journal/palettes.py b/src/jarabe/journal/palettes.py
+index 66dcadc..94922ec 100644
+--- a/src/jarabe/journal/palettes.py
++++ b/src/jarabe/journal/palettes.py
+@@ -30,6 +30,7 @@ import gio
+ import glib
+ import time
+ import socket
++import dbus
+
+ from sugar import _sugarext
+
+@@ -44,14 +45,13 @@ from jarabe.model import friends
+ from jarabe.model import filetransfer
+ from jarabe.model import mimeregistry
+ from jarabe.journal import misc
+-from jarabe.journal import model
++from jarabe.journal import model, webdavmanager
+ from jarabe.journal.journalwindow import freeze_ui, \
+ unfreeze_ui, \
+ show_normal_cursor, \
+ show_waiting_cursor
+
+ from webdav.Connection import WebdavError
+-from jarabe.journal.webdavmanager import get_resource_by_ip_address_and_resource_key
+
+
+ friends_model = friends.get_model()
+@@ -59,6 +59,49 @@ friends_model = friends.get_model()
+ _copy_menu_helper = None
+ _current_action_item = None
+
++
++class PassphraseDialog(gtk.Dialog):
++ def __init__(self, callback, metadata):
++ gtk.Dialog.__init__(self, flags=gtk.DIALOG_MODAL)
++ self._callback = callback
++ self._metadata = metadata
++ self.set_title(_('Passphrase required'))
++ self.set_has_separator(False)
++
++ # TRANS: Please do not translate the '%s'.
++ label_text = _('Please enter the passphrase for %s' % metadata['title'])
++ label = gtk.Label(label_text)
++ self.vbox.pack_start(label)
++
++ self.add_buttons(gtk.STOCK_OK, gtk.RESPONSE_OK)
++ self.set_default_response(gtk.RESPONSE_OK)
++ self.set_has_separator(True)
++ self.add_key_entry()
++
++ self.connect('response', self._key_dialog_response_cb)
++ self.show_all()
++
++ def add_key_entry(self):
++ self._entry = gtk.Entry()
++ self._entry.connect('activate', self._entry_activate_cb)
++ self.vbox.pack_start(self._entry)
++ self.vbox.set_spacing(6)
++ self.vbox.show_all()
++
++ self._entry.grab_focus()
++
++ def _entry_activate_cb(self, entry):
++ self.response(gtk.RESPONSE_OK)
++
++ def get_response_object(self):
++ return self._response
++
++ def _key_dialog_response_cb(self, widget, response_id):
++ self.hide()
++ gobject.idle_add(self._callback, self._metadata,
++ self._entry.get_text())
++
++
+ class ObjectPalette(Palette):
+
+ __gtype_name__ = 'ObjectPalette'
+@@ -528,6 +571,7 @@ class ActionItem(gobject.GObject):
+ This is the stage, just after EVERY metadata has been
+ processed.
+ """
++ self._hide_info_widget_for_single_mode()
+
+ # Toggle the corresponding checkbox - but only for batch-mode.
+ if self._batch_mode and self._auto_deselect_source_entries:
+@@ -573,6 +617,25 @@ class ActionItem(gobject.GObject):
+ from jarabe.journal.journalactivity import get_journal
+ get_journal().get_list_view().refresh()
+
++ def _handle_single_mode_notification(self, message, severity):
++ from jarabe.journal.journalactivity import get_journal
++ journal = get_journal()
++
++ journal._show_alert(message, severity)
++ self._hide_info_widget_for_single_mode()
++
++ def _hide_info_widget_for_single_mode(self):
++ if (not self._batch_mode):
++ from jarabe.journal.journalactivity import get_journal
++ journal = get_journal()
++
++ journal.get_toolbar_box().hide_info_widget()
++
++ def _unhide_info_widget_for_single_mode(self):
++ if not self._batch_mode:
++ from jarabe.journal.journalactivity import get_journal
++ get_journal().update_progress(0)
++
+ def _handle_error_alert(self, error_message, metadata):
+ """
+ This handles any error scenarios. Examples are of entries that
+@@ -593,19 +656,11 @@ class ActionItem(gobject.GObject):
+
+ current_len = len(self._metadata_list)
+
+- # TRANS: Do not translate the two %d, and the three %s.
+- info_alert_message = _('( %d / %d ) Error while %s %s : %s') % (
+- self._metadata_list_initial_len - current_len,
+- self._metadata_list_initial_len,
+- self._get_info_alert_title(),
+- metadata['title'],
+- error_message)
+-
+ # Only show the alert, if allowed to.
+ if self._show_not_completed_ops_info:
+ from jarabe.journal.journalactivity import get_journal
+- get_journal().update_confirmation_alert(self._get_info_alert_title() + ' ...',
+- info_alert_message,
++ get_journal().update_confirmation_alert(_('Error'),
++ error_message,
+ self._process_error_skipping,
+ metadata)
+ else:
+@@ -639,19 +694,55 @@ class ActionItem(gobject.GObject):
+ 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
++ ip_address_or_dns_name = \
++ model.extract_ip_address_or_dns_name_from_locally_mounted_remote_share_path(file_path)
++
++ data_webdav_manager = \
++ webdavmanager.get_data_webdav_manager(ip_address_or_dns_name)
++ metadata_webdav_manager = \
++ webdavmanager.get_metadata_webdav_manager(ip_address_or_dns_name)
++
++ # Download the preview file, if it exists.
++ preview_resource = \
++ webdavmanager.get_resource_by_resource_key(metadata_webdav_manager,
++ '/webdav/.Sugar-Metadata/' + filename + '.preview')
++ preview_path = os.path.dirname(file_path) + '/.Sugar-Metadata/'+ filename + '.preview'
++
++ if preview_resource is not None:
++ try:
++ preview_resource.downloadFile(preview_path,
++ show_progress=False,
++ filesize=0)
++ 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._handle_single_mode_notification(error_message,
++ _('Error'))
++ return False
++
++ # If we manage to reach here, download the data file.
++ data_resource = \
++ webdavmanager.get_resource_by_resource_key(data_webdav_manager,
++ '/webdav/'+ filename)
+ try:
+- resource.downloadFile(download_file_path)
++ data_resource.downloadFile(metadata['uid'],
++ show_progress=True,
++ filesize=int(metadata['filesize']))
+ return True
+ except (WebdavError, socket.error), e:
++ # Delete the downloaded preview file, if it exists.
++ if os.path.exists(preview_path):
++ os.unlink(preview_path)
++
+ 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,
++ self._handle_single_mode_notification(error_message,
+ _('Error'))
+ return False
+
+@@ -662,7 +753,7 @@ class ActionItem(gobject.GObject):
+ if self._batch_mode:
+ self._handle_error_alert(error_message, metadata)
+ else:
+- self.emit('volume-error', error_message, _('Warning'))
++ self._handle_single_mode_notification(error_message, _('Warning'))
+ return False
+ else:
+ return True
+@@ -674,12 +765,12 @@ class ActionItem(gobject.GObject):
+ model.copy(metadata, mount_point)
+ return True
+ except Exception, e:
+- logging.exception('Error while copying the entry. %s', e)
++ logging.exception(e)
+ error_message = _('Error while copying the entry. %s') % e
+ if self._batch_mode:
+ self._handle_error_alert(error_message, metadata)
+ else:
+- self.emit('volume-error', error_message, _('Error'))
++ self._handle_single_mode_notification(error_message, _('Error'))
+ return False
+ finally:
+ self._set_bundle_installation_allowed(True)
+@@ -689,7 +780,7 @@ class ActionItem(gobject.GObject):
+ self._set_bundle_installation_allowed(False)
+
+ try:
+- model.write(metadata, update_mtime=False)
++ model.update_only_metadata_and_preview_files_and_return_file_paths(metadata)
+ return True
+ except Exception, e:
+ logging.exception('Error while writing the metadata. %s', e)
+@@ -698,7 +789,7 @@ class ActionItem(gobject.GObject):
+ if self._batch_mode:
+ self._handle_error_alert(error_message, metadata)
+ else:
+- self.emit('volume-error', error_message, _('Error'))
++ self._handle_single_mode_notification(error_message, _('Error'))
+ return False
+ finally:
+ self._set_bundle_installation_allowed(True)
+@@ -725,7 +816,7 @@ class BaseCopyMenuItem(MenuItem, ActionItem):
+ }
+
+ def __init__(self, metadata_list, label, show_editing_alert,
+- show_progress_info_alert, batch_mode):
++ show_progress_info_alert, batch_mode, mount_point):
+ MenuItem.__init__(self, label)
+ ActionItem.__init__(self, label, metadata_list, show_editing_alert,
+ show_progress_info_alert, batch_mode,
+@@ -733,6 +824,10 @@ class BaseCopyMenuItem(MenuItem, ActionItem):
+ need_to_popup_options=False,
+ operate_on_deselected_entries=False,
+ show_not_completed_ops_info=True)
++ self._mount_point = mount_point
++
++ def get_mount_point(self):
++ return self._mount_point
+
+ def _get_actionable_signal(self):
+ return 'activate'
+@@ -751,6 +846,30 @@ class BaseCopyMenuItem(MenuItem, ActionItem):
+ def _get_info_alert_title(self):
+ return _('Copying')
+
++ def _operate(self, metadata):
++ from jarabe.journal.journalactivity import get_mount_point
++ if(model.is_mount_point_for_locally_mounted_remote_share(get_mount_point())) \
++ and (model.is_mount_point_for_school_server(get_mount_point()) == True):
++ PassphraseDialog(self._proceed_after_receiving_passphrase, metadata)
++ else:
++ self._proceed_with_copy(metadata)
++
++ def _proceed_after_receiving_passphrase(self, metadata, passphrase):
++ if metadata['passphrase'] != passphrase:
++ error_message = _('Passphrase does not match.')
++ if self._batch_mode:
++ self._handle_error_alert(error_message, metadata)
++ else:
++ self._handle_single_mode_notification(error_message, _('Error'))
++ return False
++ else:
++ self._unhide_info_widget_for_single_mode()
++ gobject.idle_add(self._proceed_with_copy, metadata)
++
++ def _proceed_with_copy(self, metadata):
++ return NotImplementedError
++
++
+
+ class VolumeMenu(BaseCopyMenuItem):
+ def __init__(self, metadata_list, label, mount_point,
+@@ -758,12 +877,13 @@ class VolumeMenu(BaseCopyMenuItem):
+ batch_mode):
+ BaseCopyMenuItem.__init__(self, metadata_list, label,
+ show_editing_alert,
+- show_progress_info_alert, batch_mode)
+- self._mount_point = mount_point
++ show_progress_info_alert, batch_mode,
++ mount_point)
+
+- def _operate(self, metadata):
++ def _proceed_with_copy(self, metadata):
+ if not self._file_path_valid(metadata):
+ return False
++
+ if not self._metadata_copy_valid(metadata, self._mount_point):
+ return False
+
+@@ -777,10 +897,10 @@ class ClipboardMenu(BaseCopyMenuItem):
+ BaseCopyMenuItem.__init__(self, metadata_list, _('Clipboard'),
+ show_editing_alert,
+ show_progress_info_alert,
+- batch_mode)
++ batch_mode, None)
+ self._temp_file_path_list = []
+
+- def _operate(self, metadata):
++ def _proceed_with_copy(self, metadata):
+ if not self._file_path_valid(metadata):
+ return False
+
+@@ -811,11 +931,13 @@ class DocumentsMenu(BaseCopyMenuItem):
+ BaseCopyMenuItem.__init__(self, metadata_list, _('Documents'),
+ show_editing_alert,
+ show_progress_info_alert,
+- batch_mode)
++ batch_mode,
++ model.get_documents_path())
+
+- def _operate(self, metadata):
++ def _proceed_with_copy(self, metadata):
+ if not self._file_path_valid(metadata):
+ return False
++
+ if not self._metadata_copy_valid(metadata,
+ model.get_documents_path()):
+ return False
+@@ -824,24 +946,119 @@ class DocumentsMenu(BaseCopyMenuItem):
+ self._post_operate_per_metadata_per_action(metadata)
+
+
+-class SharesMenu(BaseCopyMenuItem):
++class LocalSharesMenu(BaseCopyMenuItem):
++ def __init__(self, metadata_list, show_editing_alert,
++ show_progress_info_alert, batch_mode):
++ BaseCopyMenuItem.__init__(self, metadata_list, _('Local Shares'),
++ show_editing_alert,
++ show_progress_info_alert,
++ batch_mode,
++ model.LOCAL_SHARES_MOUNT_POINT)
++
++ def _proceed_with_copy(self, metadata):
++ if not self._file_path_valid(metadata):
++ return False
++
++ # Attach the filesize.
++ file_path = model.get_file(metadata['uid'])
++ metadata['filesize'] = os.stat(file_path).st_size
++
++ # Attach the current mount-point.
++ from jarabe.journal.journalactivity import get_mount_point
++ metadata['mountpoint'] = get_mount_point()
++
++ if not self._metadata_write_valid(metadata):
++ return False
++
++ if not self._metadata_copy_valid(metadata,
++ model.LOCAL_SHARES_MOUNT_POINT):
++ return False
++
++ # This is sync-operation. Call the post-operation now.
++ self._post_operate_per_metadata_per_action(metadata)
++
++
++class SchoolServerMenu(BaseCopyMenuItem):
+ def __init__(self, metadata_list, show_editing_alert,
+ show_progress_info_alert, batch_mode):
+- BaseCopyMenuItem.__init__(self, metadata_list, _('Shares'),
++ BaseCopyMenuItem.__init__(self, metadata_list, _('School Server'),
+ show_editing_alert,
+ show_progress_info_alert,
+- batch_mode)
++ batch_mode,
++ model.WEBDAV_MOUNT_POINT + model.SCHOOL_SERVER_IP_ADDRESS_OR_DNS_NAME)
+
+ def _operate(self, metadata):
+ if not self._file_path_valid(metadata):
+ return False
++
++ # If the entry is copyable, proceed with asking the
++ # upload-passphrase.
++ PassphraseDialog(self._proceed_after_receiving_passphrase, metadata)
++
++ def _proceed_after_receiving_passphrase(self, metadata, passphrase):
++ self._unhide_info_widget_for_single_mode()
++ gobject.idle_add(self._proceed_with_uploading, metadata,
++ passphrase)
++
++ def _proceed_with_uploading(self, metadata, passphrase):
++ #
++ # Attach the passphrase.
++ metadata['passphrase'] = passphrase
++
++ # Attach the filesize.
++ file_path = model.get_file(metadata['uid'])
++ metadata['filesize'] = os.stat(file_path).st_size
++
++ # Attach the current mount-point.
++ from jarabe.journal.journalactivity import get_mount_point, \
++ get_journal
++ metadata['mountpoint'] = get_mount_point()
++
++ # Attach the info of the uploader.
++ from jarabe.model.buddy import get_owner_instance
++ metadata['uploader-nick'] = get_owner_instance().props.nick
++ metadata['uploader-serial'] = self.__get_serial_number()
++
++ if not self._metadata_write_valid(metadata):
++ return False
++
+ if not self._metadata_copy_valid(metadata,
+- '/var/www/web1/web'):
++ model.WEBDAV_MOUNT_POINT + model.SCHOOL_SERVER_IP_ADDRESS_OR_DNS_NAME):
+ return False
+
+ # This is sync-operation. Call the post-operation now.
+ self._post_operate_per_metadata_per_action(metadata)
+
++ def __get_serial_number(self):
++ _OFW_TREE = '/ofw'
++ _PROC_TREE = '/proc/device-tree'
++ _SN = 'serial-number'
++ _not_available = _('Not available')
++
++ serial_no = None
++ if os.path.exists(os.path.join(_OFW_TREE, _SN)):
++ serial_no = self.__read_file(os.path.join(_OFW_TREE, _SN))
++ elif os.path.exists(os.path.join(_PROC_TREE, _SN)):
++ serial_no = self.__read_file(os.path.join(_PROC_TREE, _SN))
++
++ if serial_no is None:
++ serial_no = _not_available
++ return serial_no
++
++ def __read_file(self, path):
++ if os.access(path, os.R_OK) == 0:
++ return None
++
++ fd = open(path, 'r')
++ value = fd.read()
++ fd.close()
++ if value:
++ value = value.strip('\n')
++ return value
++ else:
++ logging.debug('No information in file or directory: %s', path)
++ return None
++
+
+ class FriendsMenu(gtk.Menu):
+ __gtype_name__ = 'JournalFriendsMenu'
+@@ -970,12 +1187,24 @@ class CopyMenuHelper(gtk.Menu):
+ menu.append(documents_menu)
+ documents_menu.show()
+
+- if get_mount_point() != '/var/www/web1/web':
+- documents_menu = SharesMenu(metadata_list,
++ if get_mount_point() != model.LOCAL_SHARES_MOUNT_POINT:
++ local_shares_menu = LocalSharesMenu(metadata_list,
++ show_editing_alert,
++ show_progress_info_alert,
++ batch_mode)
++ local_shares_menu.set_image(Icon(icon_name='emblem-neighborhood-shared',
++ icon_size=gtk.ICON_SIZE_MENU))
++ local_shares_menu.connect('volume-error', self.__volume_error_cb)
++ menu.append(local_shares_menu)
++ local_shares_menu.show()
++
++ if (model.is_school_server_present()) and \
++ (not model.is_mount_point_for_locally_mounted_remote_share(get_mount_point())):
++ documents_menu = SchoolServerMenu(metadata_list,
+ show_editing_alert,
+ show_progress_info_alert,
+ batch_mode)
+- documents_menu.set_image(Icon(icon_name='emblem-neighborhood-shared',
++ documents_menu.set_image(Icon(icon_name='school-server',
+ icon_size=gtk.ICON_SIZE_MENU))
+ documents_menu.connect('volume-error', self.__volume_error_cb)
+ menu.append(documents_menu)
+diff --git a/src/jarabe/journal/volumestoolbar.py b/src/jarabe/journal/volumestoolbar.py
+index c24475d..29a8619 100644
+--- a/src/jarabe/journal/volumestoolbar.py
++++ b/src/jarabe/journal/volumestoolbar.py
+@@ -37,7 +37,6 @@ 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, RemoteSharePalette
+ import jarabe.frame
+@@ -45,6 +44,9 @@ import jarabe.frame
+
+ _JOURNAL_0_METADATA_DIR = '.olpc.store'
+
++SHARE_TYPE_PEER = 1
++SHARE_TYPE_SCHOOL_SERVER = 2
++
+
+ def _get_id(document):
+ """Get the ID for the document in the xapian database."""
+@@ -211,7 +213,15 @@ class VolumesToolbar(gtk.Toolbar):
+
+ def _set_up_volumes(self):
+ self._set_up_documents_button()
+- self._set_up_shares_button()
++ self._set_up_local_shares_button()
++
++ client = gconf.client_get_default()
++ color = XoColor(client.get_string('/desktop/sugar/user/color'))
++
++ if model.is_school_server_present():
++ self._add_remote_share_button(_('School-Server Shares'),
++ model.SCHOOL_SERVER_IP_ADDRESS_OR_DNS_NAME,
++ color, SHARE_TYPE_SCHOOL_SERVER)
+
+ volume_monitor = gio.volume_monitor_get()
+ self._mount_added_hid = volume_monitor.connect('mount-added',
+@@ -242,18 +252,28 @@ class VolumesToolbar(gtk.Toolbar):
+ 'user-documents',
+ _('Documents'))
+
+- def _set_up_shares_button(self):
+- shares_dir_path = '/var/www/web1/web'
+- self._set_up_directory_button(shares_dir_path,
++ def _set_up_local_shares_button(self):
++ local_shares_path = model.LOCAL_SHARES_MOUNT_POINT
++ self._set_up_directory_button(local_shares_path,
+ 'emblem-neighborhood-shared',
+- _('Shares'))
+-
+- def _add_remote_share_button(self, buddy):
+- button = RemoteSharesButton(buddy)
++ _('Local Shares'))
++
++ def _add_remote_share_button(self, primary_text,
++ ip_address_or_dns_name, color,
++ share_type):
++ button = RemoteSharesButton(primary_text, ip_address_or_dns_name,
++ color, share_type)
++ button._share_type = share_type
+ button.props.group = self._volume_buttons[0]
+- label = glib.markup_escape_text(_('%s\'s share') % \
+- buddy.props.nick)
+- button.set_palette(RemoteSharePalette(buddy, button))
++
++ show_unmount_option = None
++ if share_type == SHARE_TYPE_PEER:
++ show_unmount_option = True
++ else:
++ show_unmount_option = False
++ button.set_palette(RemoteSharePalette(primary_text,
++ ip_address_or_dns_name, button,
++ show_unmount_option))
+ button.connect('toggled', self._button_toggled_cb)
+ button.show()
+
+@@ -262,12 +282,7 @@ class VolumesToolbar(gtk.Toolbar):
+ 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)
++ return button
+
+ def __mount_added_cb(self, volume_monitor, mount):
+ self._add_button(mount)
+@@ -302,13 +317,13 @@ class VolumesToolbar(gtk.Toolbar):
+
+ def _button_toggled_cb(self, button, force_toggle=False):
+ if button.props.active or force_toggle:
++ button.set_active(True)
+ from jarabe.journal.journalactivity import get_journal
+ journal = get_journal()
+
+ journal.get_list_view()._selected_entries = 0
+ journal.switch_to_editing_mode(False)
+ journal.get_list_view().inhibit_refresh(False)
+- journal.get_list_view().refresh()
+
+ self.emit('volume-changed', button.mount_point)
+
+@@ -328,6 +343,14 @@ class VolumesToolbar(gtk.Toolbar):
+ logging.error('Couldnt find button with mount_point %r', mount_point)
+ return None
+
++ def _get_button_for_mount_point(self, mount_point):
++ for button in self.get_children():
++ if button.mount_point == mount_point:
++ return button
++ logging.error('Couldnt find button with mount_point %r', mount_point)
++ return None
++
++
+ def _remove_button(self, mount):
+ button = self._get_button_for_mount(mount)
+ self._volume_buttons.remove(button)
+@@ -337,16 +360,20 @@ 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.
++ def _remove_remote_share_button(self, ip_address_or_dns_name):
+ for button in self.get_children():
+ if type(button) == RemoteSharesButton and \
+- button.mount_point == mount_point:
++ button.mount_point == (model.WEBDAV_MOUNT_POINT + ip_address_or_dns_name):
+ self._volume_buttons.remove(button)
+ self.remove(button)
++
++ from jarabe.journal.webdavmanager import \
++ unmount_share_from_backend
++ unmount_share_from_backend(ip_address_or_dns_name)
++
+ self.get_children()[0].props.active = True
+
+- if len(sel.get_children()) < 2:
++ if len(self.get_children()) < 2:
+ self.hide()
+ break;
+
+@@ -379,23 +406,36 @@ class BaseButton(RadioToolButton):
+
+ def _drag_data_received_cb(self, widget, drag_context, x, y,
+ selection_data, info, timestamp):
++ # Disallow copying to mounted-shares for peers.
++ if (model.is_mount_point_for_locally_mounted_remote_share(self.mount_point)) and \
++ (model.is_mount_point_for_peer_share(self.mount_point)):
++ from jarabe.journal.journalactivity import get_journal
++
++ journal = get_journal()
++ journal._show_alert(_('Entries cannot be copied to Peer-Shares.'), _('Error'))
++ return
++
+ object_id = selection_data.data
+ metadata = model.get(object_id)
+- 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.')
+- self.emit('volume-error',
+- _('Entries without a file cannot be copied.'),
+- _('Warning'))
+- return
+
+- try:
+- model.copy(metadata, self.mount_point)
+- except IOError, e:
+- logging.exception('Error while copying the entry. %s', e.strerror)
+- self.emit('volume-error',
+- _('Error while copying the entry. %s') % e.strerror,
+- _('Error'))
++ from jarabe.journal.palettes import CopyMenu, get_copy_menu_helper
++ copy_menu_helper = get_copy_menu_helper()
++
++ dummy_copy_menu = CopyMenu()
++ copy_menu_helper.insert_copy_to_menu_items(dummy_copy_menu,
++ [metadata],
++ False,
++ False,
++ False)
++
++ # Now, activate the menuitem, whose mount-point matches the
++ # mount-point of the button, upon whom the item has been
++ # dragged.
++ children_menu_items = dummy_copy_menu.get_children()
++ for child in children_menu_items:
++ if child.get_mount_point() == self.mount_point:
++ child.activate()
++ return
+
+
+ class VolumeButton(BaseButton):
+@@ -478,6 +518,7 @@ class DirectoryButton(BaseButton):
+
+ def __init__(self, dir_path, icon_name):
+ BaseButton.__init__(self, mount_point=dir_path)
++ self._mount = dir_path
+
+ self.props.named_icon = icon_name
+
+@@ -488,16 +529,22 @@ class DirectoryButton(BaseButton):
+
+ class RemoteSharesButton(BaseButton):
+
+- def __init__(self, buddy):
+- BaseButton.__init__(self, mount_point=buddy.props.ip_address)
++ def __init__(self, primary_text, ip_address_or_dns_name, color,
++ share_type):
++ BaseButton.__init__(self, mount_point=(model.WEBDAV_MOUNT_POINT + ip_address_or_dns_name))
++
++ self._primary_text = primary_text
++ self._ip_address_or_dns_name = ip_address_or_dns_name
+
+- 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
++ if share_type == SHARE_TYPE_PEER:
++ self.props.named_icon = 'emblem-neighborhood-shared'
++ elif share_type == SHARE_TYPE_SCHOOL_SERVER:
++ self.props.named_icon = 'school-server'
++ self.props.xo_color = color
+
+ def create_palette(self):
+- palette = RemoteSharePalette(self._buddy)
++ palette = RemoteSharePalette(self._primary_text, self._ip_address_or_dns_name,
++ self, True)
+ return palette
+
+
+diff --git a/src/jarabe/journal/webdavmanager.py b/src/jarabe/journal/webdavmanager.py
+index 6cd0713..3ff9990 100644
+--- a/src/jarabe/journal/webdavmanager.py
++++ b/src/jarabe/journal/webdavmanager.py
+@@ -1,5 +1,6 @@
+ from gettext import gettext as _
+
++import logging
+ import os
+ import sys
+
+@@ -13,11 +14,6 @@ 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.
+@@ -50,6 +46,9 @@ class WebDavUrlManager(gobject.GObject):
+ def _get_number_of_collections(self):
+ return len(self._remote_webdav_share_collections)
+
++ def _get_root(self):
++ return self._root
++
+ def _get_resources_dict(self):
+ return self._remote_webdav_share_resources
+
+@@ -57,7 +56,13 @@ class WebDavUrlManager(gobject.GObject):
+ return self._remote_webdav_share_collections
+
+ def _get_resource_by_key(self, key):
+- return self._remote_webdav_share_resources[key]['resource']
++ if key in self._remote_webdav_share_resources.keys():
++ return self._remote_webdav_share_resources[key]['resource']
++ return None
++
++ def _add_or_replace_resource_by_key(self, key, resource):
++ self._remote_webdav_share_resources[key] = {}
++ self._remote_webdav_share_resources[key]['resource'] = resource
+
+ def _get_metadata_list(self):
+ metadata_list = []
+@@ -69,11 +74,9 @@ class WebDavUrlManager(gobject.GObject):
+ 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)
++ self._root = webdavConnection
+
+ authFailures = 0
+ while authFailures < 2:
+@@ -111,8 +114,13 @@ class WebDavUrlManager(gobject.GObject):
+ error_message = e
+ get_journal()._volume_error_cb(None, error_message,_('Error'))
+
+- # Simply return, in case of connection-not-available.
+- return False
++ # Re-raise this error.
++ # Note that since this is not an
++ # "AuthorizationError", this will not be caught
++ # by the outer except-block. Instead, it will
++ # navigate all the way back up, and will report
++ # the error in the enclosing except block.
++ raise e
+
+ else:
+ # If this indeed is an Authorization Error,
+@@ -138,25 +146,54 @@ class WebDavUrlManager(gobject.GObject):
+
+ webdav_manager = {}
+
++def get_data_webdav_manager(ip_address_or_dns_name):
++ return webdav_manager[ip_address_or_dns_name]['data']
++
++
++def get_metadata_webdav_manager(ip_address_or_dns_name):
++ return webdav_manager[ip_address_or_dns_name]['metadata']
+
+-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()
++def get_resource_by_resource_key(root_webdav, key):
++ resources_dict = root_webdav._get_resources_dict()
++ if key in resources_dict.keys():
+ resource_dict = resources_dict[key]
+ resource = resource_dict['resource']
+-
+ return resource
++ return None
+
+
+-def get_remote_webdav_share_metadata(ip_address):
+- protocol = 'dav://'
++def add_resource_by_resource_key(root_webdav, key,
++ content_file_path):
++ root = root_webdav._get_root()
+
+- root_webdav_url = '/webdav'
++ resource = root.addResource(key)
++
++ # Procure the resource-lock.
++ lockToken = resource.lock('olpc')
++
++ input_stream = open(content_file_path)
+
+- complete_root_url = protocol + ip_address + root_webdav_url
++ # Now, upload the data; but it's necessary to enclose this in a
++ # try-except-finally block here, since we need to close the
++ # input-stream, whatever may happen.
++ try:
++ resource.uploadFile(input_stream, lockToken)
++ root_webdav._add_or_replace_resource_by_key(key, resource)
++ except Exception, e:
++ logging.exception(e)
++ resource.delete(lockToken)
++ raise e
++ else:
++ resource.unlock(lockToken)
++ finally:
++ input_stream.close()
++
++
++def get_remote_webdav_share_metadata(ip_address_or_dns_name):
++ protocol = 'davs://'
++ root_webdav_url = '/webdav'
++ complete_root_url = protocol + ip_address_or_dns_name + root_webdav_url
+
+ root_webdav = WebDavUrlManager(complete_root_url, 'test', 'olpc')
+ if root_webdav._fetch_resources_and_collections() is False:
+@@ -165,7 +202,8 @@ def get_remote_webdav_share_metadata(ip_address):
+
+ # Keep reference to the "WebDavUrlManager", keyed by IP-Address.
+ global webdav_manager
+- webdav_manager[ip_address] = root_webdav
++ webdav_manager[ip_address_or_dns_name] = {}
++ webdav_manager[ip_address_or_dns_name]['data'] = root_webdav
+
+
+ # Assert that the number of collections is only one at this url
+@@ -174,12 +212,14 @@ def get_remote_webdav_share_metadata(ip_address):
+
+ root_sugar_metadata_url = root_webdav_url + '/.Sugar-Metadata'
+
+- complete_root_sugar_metadata_url = protocol + ip_address + root_sugar_metadata_url
++ complete_root_sugar_metadata_url = protocol + ip_address_or_dns_name + 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 []
+
++ webdav_manager[ip_address_or_dns_name]['metadata'] = root_webdav_sugar_metadata
++
+ # assert that the number of collections is zero at this url.
+ assert root_webdav_sugar_metadata._get_number_of_collections() == 0
+
+@@ -189,68 +229,82 @@ def get_remote_webdav_share_metadata(ip_address):
+ 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_data_root_dir = '/tmp/' + ip_address_or_dns_name
+ 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 ::
++ metadata_list = []
+
+- /webdav/a.txt
++ # Note that the presence of a resource in the metadata directory,
++ # is the only assurance of the entry (and its constituents) being
++ # present in entirety. Thus, always proceed taking the metadata as
++ # the "key".
++ for root_webdav_sugar_metadata_resource_name in root_webdav_sugar_metadata_resources.keys():
+ """
+- split_tokens_array = root_webdav_resource_name.split('/')
++ root_webdav_sugar_metadata_resource_name is of the type ::
+
+- # This will provide us with "a.txt"
+- basename = split_tokens_array[len(split_tokens_array) - 1]
++ /webdav/.Sugar-Metadata/a.txt.metadata, OR
++ /webdav/.Sugar-Metadata/a.txt.preview
++ """
+
+- # This will provide us with "a.txt.metadata"
+- sugar_metadata_basename = basename + '.metadata'
++ # If this is a "preview" resource, continue forward, as we only
++ # want the metadata list. The "preview" resources are anyways
++ # already present in the manager DS.
++ if root_webdav_sugar_metadata_resource_name.endswith('.preview'):
++ continue
+
+- # Thus will provide us with "/webdav/.Sugar-Metadata/a.txt.metadata"
+- sugar_metadata_url = root_sugar_metadata_url + '/' + sugar_metadata_basename
++ split_tokens_array = root_webdav_sugar_metadata_resource_name.split('/')
+
+- # 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()
++ # This will provide us with "a.txt.metadata"
++ sugar_metadata_basename = split_tokens_array[len(split_tokens_array) - 1]
+
+- # 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".
++ # This will provide us with "a.txt"
++ basename = sugar_metadata_basename[0:sugar_metadata_basename.index('.metadata')]
+
+ 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 = \
++ root_webdav_sugar_metadata._get_resource_by_key(root_webdav_sugar_metadata_resource_name)
+ metadata_resource.downloadFile(downloaded_metadata_file_path)
+
++
++ # We need to download the preview-file as well at this stage,
++ # so that it can be shown in the expanded entry.
++ downloaded_preview_file_path = downloaded_metadata_file_dir + \
++ '/' + basename + '.preview'
++ root_webdav_sugar_preview_resource_name = \
++ root_webdav_sugar_metadata_resource_name[0:root_webdav_sugar_metadata_resource_name.index('.metadata')] + \
++ '.preview'
++ preview_resource = \
++ root_webdav_sugar_metadata._get_resource_by_key(root_webdav_sugar_preview_resource_name)
++ if preview_resource is not None:
++ preview_resource.downloadFile(downloaded_preview_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.
++ # Fill in the missing metadata properties.
++ # 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()
++ metadata['creation_time'] = metadata['timestamp']
+
+ # 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)
++ metadata_list.append(metadata)
++
++ return metadata_list
++
++
++def is_remote_webdav_loaded(ip_address_or_dns_name):
++ return ip_address_or_dns_name in webdav_manager.keys()
++
+
+- return root_webdav._get_metadata_list()
++def unmount_share_from_backend(ip_address_or_dns_name):
++ del webdav_manager[ip_address_or_dns_name]
+diff --git a/src/jarabe/view/buddymenu.py b/src/jarabe/view/buddymenu.py
+index dfbcfa3..f6af1b5 100644
+--- a/src/jarabe/view/buddymenu.py
++++ b/src/jarabe/view/buddymenu.py
+@@ -27,6 +27,7 @@ from sugar.graphics.palette import Palette
+ from sugar.graphics.menuitem import MenuItem
+ from sugar.graphics.icon import Icon
+
++from jarabe.frame.notification import NotificationIcon
+ from jarabe.model import shell
+ from jarabe.model import friends
+ from jarabe.model.session import get_session_manager
+@@ -73,11 +74,17 @@ class BuddyMenu(Palette):
+ self.menu.append(menu_item)
+ menu_item.show()
+
+- access_buddy_remote_share_menu_item = MenuItem(_('Access Share'), 'list-add')
+- access_buddy_remote_share_menu_item.connect('activate',
+- self._access_share_cb)
+- self.menu.append(access_buddy_remote_share_menu_item)
+- access_buddy_remote_share_menu_item.show()
++ remote_share_menu_item = None
++ from jarabe.journal import webdavmanager
++ if not webdavmanager.is_remote_webdav_loaded(self._buddy.props.ip_address):
++ remote_share_menu_item = MenuItem(_('Access Share'), 'list-add')
++ remote_share_menu_item.connect('activate', self._access_share_cb)
++ else:
++ remote_share_menu_item = MenuItem(_('Unmount Share'), 'list-remove')
++ remote_share_menu_item.connect('activate', self.__unmount_cb)
++
++ self.menu.append(remote_share_menu_item)
++ remote_share_menu_item.show()
+
+ self._invite_menu = MenuItem('')
+ self._invite_menu.connect('activate', self._invite_friend_cb)
+@@ -89,10 +96,36 @@ class BuddyMenu(Palette):
+ activity = home_model.get_active_activity()
+ self._update_invite_menu(activity)
+
++ def __unmount_cb(self, menuitem):
++ from jarabe.journal.journalactivity import get_journal
++ singleton_volumes_toolbar = get_journal().get_volumes_toolbar()
++ singleton_volumes_toolbar._remove_remote_share_button(self._buddy.props.ip_address)
++
+ def _access_share_cb(self, menuitem):
+ from jarabe.journal.journalactivity import get_journal
+ volumes_toolbar = get_journal().get_volumes_toolbar()
+- volumes_toolbar._add_remote_share_button(self._buddy)
++
++ # TRANS: Do not translate the """%s""".
++ primary_text = _('%s\'s Shares') % self._buddy.props.nick
++ button = volumes_toolbar._add_remote_share_button(primary_text,
++ self._buddy.props.ip_address,
++ self._buddy.props.color,
++ jarabe.journal.volumestoolbar.SHARE_TYPE_PEER)
++
++ # Now, make three levels of transitions, from the
++ # Neighborhood-view.
++
++ # 1. Switch to the home-view.
++ from jarabe.model import shell
++ from jarabe.model.shell import ShellModel
++ shell.get_model().set_zoom_level(ShellModel.ZOOM_HOME)
++
++ # 2. Switch to the journal-activity-view.
++ top_frame = jarabe.frame.get_view()
++ top_frame.switch_to_journal_activity()
++
++ # 3. Switch to the newly mounted-view.
++ volumes_toolbar._button_toggled_cb(button, force_toggle=True)
+
+ def _add_my_items(self):
+ item = MenuItem(_('Shutdown'), 'system-shutdown')
+diff --git a/src/jarabe/view/palettes.py b/src/jarabe/view/palettes.py
+index 3b26faf..34cce63 100644
+--- a/src/jarabe/view/palettes.py
++++ b/src/jarabe/view/palettes.py
+@@ -339,12 +339,14 @@ class JournalXSPalette(Palette):
+
+
+ class RemoteSharePalette(Palette):
+- def __init__(self, buddy, button):
+- Palette.__init__(self, label=('%s\'s share' % buddy.props.nick))
+- self._buddy = buddy
++ def __init__(self, primary_text, ip_address_or_dns_name, button,
++ show_unmount_option):
++ Palette.__init__(self, label=primary_text)
+ self._button = button
++ self._ip_address_or_dns_name = ip_address_or_dns_name
+
+- self.props.secondary_text = glib.markup_escape_text(buddy.props.ip_address)
++ self.props.secondary_text = \
++ glib.markup_escape_text(self._ip_address_or_dns_name)
+
+ vbox = gtk.VBox()
+ self.set_content(vbox)
+@@ -352,20 +354,35 @@ class RemoteSharePalette(Palette):
+
+ self.connect('popup', self.__popup_cb)
+
+- menu_item = MenuItem(pgettext('Share', 'Unmount'))
+-
+- icon = Icon(icon_name='media-eject', icon_size=gtk.ICON_SIZE_MENU)
++ menu_item = MenuItem(pgettext('Share', _('Reload')))
++ icon = Icon(icon_name='system-restart', icon_size=gtk.ICON_SIZE_MENU)
+ menu_item.set_image(icon)
+ icon.show()
+
+- menu_item.connect('activate', self.__unmount_activate_cb)
++ menu_item.connect('activate', self.__reload_remote_share)
+ self.menu.append(menu_item)
+ menu_item.show()
+
++
++ if show_unmount_option == True:
++ menu_item = MenuItem(pgettext('Share', 'Unmount'))
++ icon = Icon(icon_name='media-eject', icon_size=gtk.ICON_SIZE_MENU)
++ menu_item.set_image(icon)
++ icon.show()
++
++ menu_item.connect('activate', self.__unmount_activate_cb)
++ self.menu.append(menu_item)
++ menu_item.show()
++
++ def __reload_remote_share(self, menu_item):
++ from jarabe.journal.journalactivity import get_journal
++ get_journal().get_list_view().refresh()
++
+ def __unmount_activate_cb(self, menu_item):
+ from jarabe.journal.journalactivity import get_journal
++
+ singleton_volumes_toolbar = get_journal().get_volumes_toolbar()
+- singleton_volumes_toolbar._remove_remote_share_button(self._buddy.props.ip_address)
++ singleton_volumes_toolbar._remove_remote_share_button(self._ip_address_or_dns_name)
+
+ def __popup_cb(self, palette):
+ pass
+diff --git a/src/webdav/Connection.py b/src/webdav/Connection.py
+index 66f7833..33719f9 100644
+--- a/src/webdav/Connection.py
++++ b/src/webdav/Connection.py
+@@ -197,7 +197,9 @@ class Connection(DAV):
+ path = _urlEncode(path)
+ try:
+ HTTPConnection.request(self, 'PUT', path, "", header)
+- self._blockCopySocket(srcfile, self, Connection.blockSize)
++ filesize = os.path.getsize(srcfile.name)
++ self._blockCopySocket(srcfile, self,
++ Connection.blockSize,filesize)
+ srcfile.close()
+ response = self.getresponse()
+ except (CannotSendRequest, socket.error, BadStatusLine, ResponseNotReady), exc:
+@@ -215,14 +217,15 @@ class Connection(DAV):
+ finally:
+ self._lock.release()
+
+- def _blockCopySocket(self, source, toSocket, blockSize):
++ def _blockCopySocket(self, source, toSocket, blockSize, filesize):
+ transferedBytes = 0
+ block = source.read(blockSize)
+- #while source.readinto(block, blockSize):
+ while len(block):
+- toSocket.send(block)
+ self.logger.debug("Wrote %d bytes." % len(block))
+ transferedBytes += len(block)
++ toSocket.send(block)
++ from jarabe.journal.journalactivity import get_journal
++ get_journal().update_progress(transferedBytes/(filesize*1.0))
+ block = source.read(blockSize)
+ self.logger.info("Transfered %d bytes." % transferedBytes)
+
+diff --git a/src/webdav/WebdavClient.py b/src/webdav/WebdavClient.py
+index ab5cec3..8ce5c77 100644
+--- a/src/webdav/WebdavClient.py
++++ b/src/webdav/WebdavClient.py
+@@ -336,6 +336,9 @@ class ResourceStorer(object):
+ else:
+ header["Content-length"] = 0
+
++ # We need to change the header["Content-length"] to a string.
++ header["Content-length"] = str(header["Content-length"])
++
+ try:
+ response = self.connection.put(self.path, content, extra_hdrs=header)
+ finally:
+@@ -367,7 +370,8 @@ class ResourceStorer(object):
+ # TODO: Other interface ? return self.connection.getfile()
+ return response
+
+- def downloadFile(self, localFileName):
++ def downloadFile(self, localFileName, show_progress=False,
++ filesize=0):
+ """
+ Copy binary data from permanent storage to a local file.
+
+@@ -377,7 +381,8 @@ class ResourceStorer(object):
+ remoteFile = self.downloadContent()
+ try:
+ socket.setdefaulttimeout(SOCKET_DEFAULT_TIMEOUT)
+- _blockCopyFile(remoteFile, localFile, Connection.blockSize)
++ _blockCopyFile(remoteFile, localFile, Connection.blockSize,
++ show_progress, filesize)
+ except socket.error, e:
+ raise e
+ remoteFile.close()
+@@ -807,7 +812,7 @@ class LockToken(object):
+ return self.value()
+
+
+-def _blockCopyFile(source, dest, blockSize):
++def _blockCopyFile(source, dest, blockSize, show_progress, filesize):
+ """
+ Copies a file in chunks of C{blockSize}.
+
+@@ -821,8 +826,11 @@ def _blockCopyFile(source, dest, blockSize):
+ transferedBytes = 0
+ block = source.read(blockSize)
+ while len(block):
+- dest.write(block)
+ transferedBytes += len(block);
++ dest.write(block)
++ if show_progress:
++ from jarabe.journal.journalactivity import get_journal
++ get_journal().update_progress(transferedBytes/(filesize * 1.0))
+ block = source.read(blockSize)
+
+ def _checkUrl(url):
+diff --git a/src/webdav/davlib.py b/src/webdav/davlib.py
+index f4dac91..a611e51 100644
+--- a/src/webdav/davlib.py
++++ b/src/webdav/davlib.py
+@@ -31,7 +31,7 @@ BLOCKSIZE = 16384
+ class HTTPProtocolChooser(httplib.HTTPSConnection):
+ def __init__(self, *args, **kw):
+ self.protocol = kw.pop('protocol')
+- if self.protocol == "https":
++ if self.__is_secure_protocol():
+ self.default_port = 443
+ else:
+ self.default_port = 80
+@@ -39,11 +39,14 @@ class HTTPProtocolChooser(httplib.HTTPSConnection):
+ apply(httplib.HTTPSConnection.__init__, (self,) + args, kw)
+
+ def connect(self):
+- if self.protocol == "https":
++ if self.__is_secure_protocol():
+ httplib.HTTPSConnection.connect(self)
+ else:
+ httplib.HTTPConnection.connect(self)
+
++ def __is_secure_protocol(self):
++ return (self.protocol == 'https') or (self.protocol == 'davs')
++
+
+ class HTTPConnectionAuth(HTTPProtocolChooser):
+ def __init__(self, *args, **kw):
+@@ -333,4 +336,4 @@ class DAV(HTTPConnectionAuth):
+ response = self.lock(url, owner, timeout, depth)
+ response.parse_lock_response()
+ return response.locktoken
+-
+\ No newline at end of file
++
+--
+1.7.4.4
+
diff --git a/rpms/sugar/0134-translation-fix-closes-sdxo-2218.patch b/rpms/sugar/0134-translation-fix-closes-sdxo-2218.patch
index 891dc3b..f2f516a 100644
--- a/rpms/sugar/0134-translation-fix-closes-sdxo-2218.patch
+++ b/rpms/sugar/0134-translation-fix-closes-sdxo-2218.patch
@@ -19,8 +19,8 @@ index a1cbd6d..32e7163 100644
-"El servidor es equivalente al cuarto en el cual se esta; la gente en el "
-"mismo servidor podrá verse entre ellos, aun cuando no esten en la misma red."
+"El servidor es equivalente al cuarto en el cual se está; la gente en el "
-+"mismo servidor podrá verse entre si, aun cuando no esten en la misma red."
-
++"mismo servidor podrá verse entre sí, aun cuando no estén en la misma red."
+
#: ../extensions/cpsection/network/view.py:140
msgid "Server:"
--
diff --git a/rpms/sugar/0138-sdxo-2338-Hide-the-alert-if-any-automatically-when-s.patch b/rpms/sugar/0138-sdxo-2338-Hide-the-alert-if-any-automatically-when-s.patch
new file mode 100644
index 0000000..7860416
--- /dev/null
+++ b/rpms/sugar/0138-sdxo-2338-Hide-the-alert-if-any-automatically-when-s.patch
@@ -0,0 +1,26 @@
+From 8d455fc8e8e7d5244d3092b7996ea3a569394bc6 Mon Sep 17 00:00:00 2001
+From: Ajay Garg <ajay@activitycentral.com>
+Date: Tue, 18 Sep 2012 16:35:35 +0530
+Subject: [PATCH] sdxo#2338: Hide the alert (if any) automatically, when
+ switching from one mount-point to another.
+Organization: Sugar Labs Foundation
+Signed-off-by: Ajay Garg <ajay@activitycentral.com>
+---
+ src/jarabe/journal/volumestoolbar.py | 1 +
+ 1 files changed, 1 insertions(+), 0 deletions(-)
+
+diff --git a/src/jarabe/journal/volumestoolbar.py b/src/jarabe/journal/volumestoolbar.py
+index 29a8619..3b2c018 100644
+--- a/src/jarabe/journal/volumestoolbar.py
++++ b/src/jarabe/journal/volumestoolbar.py
+@@ -321,6 +321,7 @@ class VolumesToolbar(gtk.Toolbar):
+ from jarabe.journal.journalactivity import get_journal
+ journal = get_journal()
+
++ journal.hide_alert()
+ journal.get_list_view()._selected_entries = 0
+ journal.switch_to_editing_mode(False)
+ journal.get_list_view().inhibit_refresh(False)
+--
+1.7.4.4
+
diff --git a/rpms/sugar/0139-sdxo-2341-Now-renaming-journal-entries-work-in-Local.patch b/rpms/sugar/0139-sdxo-2341-Now-renaming-journal-entries-work-in-Local.patch
new file mode 100644
index 0000000..c6e0701
--- /dev/null
+++ b/rpms/sugar/0139-sdxo-2341-Now-renaming-journal-entries-work-in-Local.patch
@@ -0,0 +1,35 @@
+From ddde0e9f70e176b76725abc994981626add2bbb9 Mon Sep 17 00:00:00 2001
+From: Ajay Garg <ajay@activitycentral.com>
+Date: Tue, 18 Sep 2012 20:44:05 +0530
+Subject: [PATCH] sdxo#2341: Now renaming journal entries work in
+ Local-Shares/Documents.
+Organization: Sugar Labs Foundation
+Signed-off-by: Ajay Garg <ajay@activitycentral.com>
+---
+ src/jarabe/journal/listview.py | 4 +++-
+ 1 files changed, 3 insertions(+), 1 deletions(-)
+
+diff --git a/src/jarabe/journal/listview.py b/src/jarabe/journal/listview.py
+index 93ca98d..a4b4d8e 100644
+--- a/src/jarabe/journal/listview.py
++++ b/src/jarabe/journal/listview.py
+@@ -749,13 +749,15 @@ class ListView(BaseListView):
+ misc.resume(metadata)
+
+ def __cell_title_edited_cb(self, cell, path, new_text):
+- from jarabe.journal.journalactivity import get_journal
++ from jarabe.journal.journalactivity import get_journal, \
++ get_mount_point
+ if get_journal().is_editing_mode_present():
+ return
+
+ row = self._model[path]
+ metadata = model.get(row[ListModel.COLUMN_UID])
+ metadata['title'] = new_text
++ metadata['mountpoint'] = get_mount_point()
+ model.write(metadata, update_mtime=False)
+ self.cell_title.props.editable = False
+
+--
+1.7.4.4
+
diff --git a/rpms/sugar/0140-sdxo-2342-Enable-View-Details-for-remote-shares.patch b/rpms/sugar/0140-sdxo-2342-Enable-View-Details-for-remote-shares.patch
new file mode 100644
index 0000000..6c59c1f
--- /dev/null
+++ b/rpms/sugar/0140-sdxo-2342-Enable-View-Details-for-remote-shares.patch
@@ -0,0 +1,26 @@
+From 4c6534f804671b2c5782f62dcdda7f428c6fc942 Mon Sep 17 00:00:00 2001
+From: Ajay Garg <ajay@activitycentral.com>
+Date: Tue, 18 Sep 2012 21:34:32 +0530
+Subject: [PATCH] sdxo#2342: Enable View-Details for remote-shares.
+Organization: Sugar Labs Foundation
+Signed-off-by: Ajay Garg <ajay@activitycentral.com>
+---
+ src/jarabe/journal/palettes.py | 2 --
+ 1 files changed, 0 insertions(+), 2 deletions(-)
+
+diff --git a/src/jarabe/journal/palettes.py b/src/jarabe/journal/palettes.py
+index 94922ec..facfd0d 100644
+--- a/src/jarabe/journal/palettes.py
++++ b/src/jarabe/journal/palettes.py
+@@ -207,8 +207,6 @@ 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()
+
+--
+1.7.4.4
+
diff --git a/rpms/sugar/0141-sdxo-2343-sdxo-2344-Disable-Start-and-Erase-buttons-.patch b/rpms/sugar/0141-sdxo-2343-sdxo-2344-Disable-Start-and-Erase-buttons-.patch
new file mode 100644
index 0000000..985efe4
--- /dev/null
+++ b/rpms/sugar/0141-sdxo-2343-sdxo-2344-Disable-Start-and-Erase-buttons-.patch
@@ -0,0 +1,100 @@
+From 821eb2dcd3f93e78da1ca07cd8978ae9bddebe42 Mon Sep 17 00:00:00 2001
+From: Ajay Garg <ajay@activitycentral.com>
+Date: Tue, 18 Sep 2012 23:18:18 +0530
+Subject: [PATCH] sdxo#2343, sdxo#2344: Disable "Start" and "Erase" buttons
+ for DetailView of remote-shares.
+Organization: Sugar Labs Foundation
+Signed-off-by: Ajay Garg <ajay@activitycentral.com>
+---
+ src/jarabe/journal/journalactivity.py | 4 ++++
+ src/jarabe/journal/journaltoolbox.py | 33 ++++++++++++++++++++++++++-------
+ 2 files changed, 30 insertions(+), 7 deletions(-)
+
+diff --git a/src/jarabe/journal/journalactivity.py b/src/jarabe/journal/journalactivity.py
+index f902027..3ecb97c 100644
+--- a/src/jarabe/journal/journalactivity.py
++++ b/src/jarabe/journal/journalactivity.py
+@@ -303,6 +303,10 @@ class JournalActivity(JournalWindow):
+ logging.debug('Selected volume: %r.', mount_point)
+ self._main_toolbox.search_toolbar.set_mount_point(mount_point)
+ set_mount_point(mount_point)
++
++ # Also, need to update the mount-point for Detail-View.
++ self._detail_toolbox.set_mount_point(mount_point)
++
+ self._main_toolbox.set_current_toolbar(0)
+
+ def __model_created_cb(self, sender, **kwargs):
+diff --git a/src/jarabe/journal/journaltoolbox.py b/src/jarabe/journal/journaltoolbox.py
+index b1c0cac..663497d 100644
+--- a/src/jarabe/journal/journaltoolbox.py
++++ b/src/jarabe/journal/journaltoolbox.py
+@@ -381,10 +381,17 @@ class DetailToolbox(Toolbox):
+ def __init__(self):
+ Toolbox.__init__(self)
+
+- self.entry_toolbar = EntryToolbar()
++ self.entry_toolbar = EntryToolbar(self)
+ self.add_toolbar('', self.entry_toolbar)
+ self.entry_toolbar.show()
+
++ def set_mount_point(self, mount_point):
++ self._mount_point = mount_point
++ self.entry_toolbar.set_sensitivity_of_icons()
++
++ def get_mount_point(self):
++ return self._mount_point
++
+
+ class EntryToolbar(gtk.Toolbar):
+ __gsignals__ = {
+@@ -392,9 +399,10 @@ class EntryToolbar(gtk.Toolbar):
+ ([str, str])),
+ }
+
+- def __init__(self):
++ def __init__(self, detail_toolbox):
+ gtk.Toolbar.__init__(self)
+
++ self._detail_toolbox = detail_toolbox
+ self._metadata = None
+ self._temp_file_path = None
+
+@@ -425,11 +433,11 @@ class EntryToolbar(gtk.Toolbar):
+ self.add(separator)
+ separator.show()
+
+- erase_button = ToolButton('list-remove')
+- erase_button.set_tooltip(_('Erase'))
+- erase_button.connect('clicked', self._erase_button_clicked_cb)
+- self.add(erase_button)
+- erase_button.show()
++ self._erase_button = ToolButton('list-remove')
++ self._erase_button.set_tooltip(_('Erase'))
++ self._erase_button.connect('clicked', self._erase_button_clicked_cb)
++ self.add(self._erase_button)
++ self._erase_button.show()
+
+ def set_metadata(self, metadata):
+ self._metadata = metadata
+@@ -437,6 +445,17 @@ class EntryToolbar(gtk.Toolbar):
+ self._refresh_duplicate_palette()
+ self._refresh_resume_palette()
+
++ def set_sensitivity_of_icons(self):
++ mount_point = self._detail_toolbox.get_mount_point()
++ if model.is_mount_point_for_locally_mounted_remote_share(mount_point):
++ sensitivity = False
++ else:
++ sensitivity = True
++
++ self._resume.set_sensitive(sensitivity)
++ self._duplicate.set_sensitive(sensitivity)
++ self._erase_button.set_sensitive(sensitivity)
++
+ def _resume_clicked_cb(self, button):
+ misc.resume(self._metadata)
+
+--
+1.7.4.4
+
diff --git a/rpms/sugar/0142-sdxo-2345-Now-favorite-buttons-work-as-desired.patch b/rpms/sugar/0142-sdxo-2345-Now-favorite-buttons-work-as-desired.patch
new file mode 100644
index 0000000..039543a
--- /dev/null
+++ b/rpms/sugar/0142-sdxo-2345-Now-favorite-buttons-work-as-desired.patch
@@ -0,0 +1,77 @@
+From e2f880c64ceeb7620b436f0bed69c388e81ec804 Mon Sep 17 00:00:00 2001
+From: Ajay Garg <ajay@activitycentral.com>
+Date: Wed, 19 Sep 2012 00:32:54 +0530
+Subject: [PATCH] sdxo#2345: Now, favorite-buttons work as desired.
+Organization: Sugar Labs Foundation
+Signed-off-by: Ajay Garg <ajay@activitycentral.com>
+---
+ src/jarabe/journal/expandedentry.py | 13 +++++--------
+ src/jarabe/journal/journalactivity.py | 3 +++
+ src/jarabe/journal/listview.py | 6 +++++-
+ 3 files changed, 13 insertions(+), 9 deletions(-)
+
+diff --git a/src/jarabe/journal/expandedentry.py b/src/jarabe/journal/expandedentry.py
+index 1e857ba..10e18ae 100644
+--- a/src/jarabe/journal/expandedentry.py
++++ b/src/jarabe/journal/expandedentry.py
+@@ -429,14 +429,11 @@ class ExpandedEntry(hippo.CanvasBox):
+ needs_update = True
+
+ if needs_update:
+- if self._metadata.get('mountpoint', '/') == '/':
+- model.write(self._metadata, update_mtime=False)
+- else:
+- old_file_path = os.path.join(self._metadata['mountpoint'],
+- model.get_file_name(old_title,
+- self._metadata['mime_type']))
+- model.write(self._metadata, file_path=old_file_path,
+- update_mtime=False)
++ from jarabe.journal.journalactivity import get_journal
++ self._metadata['mountpoint'] = \
++ get_journal().get_detail_toolbox().get_mount_point()
++
++ model.update_only_metadata_and_preview_files_and_return_file_paths(self._metadata)
+
+ self._update_title_sid = None
+
+diff --git a/src/jarabe/journal/journalactivity.py b/src/jarabe/journal/journalactivity.py
+index 3ecb97c..05b2e67 100644
+--- a/src/jarabe/journal/journalactivity.py
++++ b/src/jarabe/journal/journalactivity.py
+@@ -518,6 +518,9 @@ class JournalActivity(JournalWindow):
+ def get_toolbar_box(self):
+ return self._toolbox
+
++ def get_detail_toolbox(self):
++ return self._detail_toolbox
++
+
+ def get_journal():
+ global _journal
+diff --git a/src/jarabe/journal/listview.py b/src/jarabe/journal/listview.py
+index a4b4d8e..780ba0a 100644
+--- a/src/jarabe/journal/listview.py
++++ b/src/jarabe/journal/listview.py
+@@ -330,7 +330,7 @@ class BaseListView(gtk.Bin):
+
+ favorite = None
+ if 'keep' in metadata.keys():
+- favorite = metadata['keep']
++ favorite = str(metadata['keep'])
+
+ if favorite == '1':
+ client = gconf.client_get_default()
+@@ -348,6 +348,10 @@ class BaseListView(gtk.Bin):
+ metadata['keep'] = '0'
+ else:
+ metadata['keep'] = '1'
++
++ from jarabe.journal.journalactivity import get_mount_point
++ metadata['mountpoint'] = get_mount_point()
++
+ model.write(metadata, update_mtime=False)
+
+ self.__redraw_view_if_necessary()
+--
+1.7.4.4
+
diff --git a/rpms/sugar/0143-sdxo-2337-Remove-the-trailing-x00-character-from-ser.patch b/rpms/sugar/0143-sdxo-2337-Remove-the-trailing-x00-character-from-ser.patch
new file mode 100644
index 0000000..1d897a7
--- /dev/null
+++ b/rpms/sugar/0143-sdxo-2337-Remove-the-trailing-x00-character-from-ser.patch
@@ -0,0 +1,30 @@
+From af343445e645180e2b53ca3ef2ed7a9a1f0f6969 Mon Sep 17 00:00:00 2001
+From: Ajay Garg <ajay@activitycentral.com>
+Date: Wed, 19 Sep 2012 18:29:51 +0530
+Subject: [PATCH] sdxo#2337: Remove the trailing '\x00' character from
+ serial-number; else when it is used as a metadata in
+ copying to SS, DBUS will crash.
+Organization: Sugar Labs Foundation
+Signed-off-by: Ajay Garg <ajay@activitycentral.com>
+---
+ src/jarabe/journal/palettes.py | 4 +++-
+ 1 files changed, 3 insertions(+), 1 deletions(-)
+
+diff --git a/src/jarabe/journal/palettes.py b/src/jarabe/journal/palettes.py
+index facfd0d..72a06b1 100644
+--- a/src/jarabe/journal/palettes.py
++++ b/src/jarabe/journal/palettes.py
+@@ -1041,7 +1041,9 @@ class SchoolServerMenu(BaseCopyMenuItem):
+
+ if serial_no is None:
+ serial_no = _not_available
+- return serial_no
++
++ # Remove the trailing binary character, else DBUS will crash.
++ return serial_no.rstrip('\x00')
+
+ def __read_file(self, path):
+ if os.access(path, os.R_OK) == 0:
+--
+1.7.4.4
+
diff --git a/rpms/sugar/0144-sdxo-2339-Metadata-and-Preview-files-must-never-be-d.patch b/rpms/sugar/0144-sdxo-2339-Metadata-and-Preview-files-must-never-be-d.patch
new file mode 100644
index 0000000..112bbca
--- /dev/null
+++ b/rpms/sugar/0144-sdxo-2339-Metadata-and-Preview-files-must-never-be-d.patch
@@ -0,0 +1,41 @@
+From bb19c8b97bfbe0d4af22724714da46e4f2b22319 Mon Sep 17 00:00:00 2001
+From: Ajay Garg <ajay@activitycentral.com>
+Date: Thu, 20 Sep 2012 13:19:56 +0530
+Subject: [PATCH] sdxo#2339: Metadata-and-Preview files must never be deleted for remote-shares (when moving the data-file).
+Organization: Sugar Labs Foundation
+Signed-off-by: Ajay Garg <ajay@activitycentral.com>
+---
+ src/jarabe/journal/model.py | 10 +++++++++-
+ 1 files changed, 9 insertions(+), 1 deletions(-)
+
+diff --git a/src/jarabe/journal/model.py b/src/jarabe/journal/model.py
+index 1a47556..e0c0620 100644
+--- a/src/jarabe/journal/model.py
++++ b/src/jarabe/journal/model.py
+@@ -978,6 +978,14 @@ def _rename_entry_on_external_device(file_path, destination_path):
+ return
+
+ from jarabe.journal.journalactivity import get_mount_point
++
++ # Also, as a special case, the metadata- and preview-files of
++ # the remote-shares must never be deleted. For them, only the
++ # data-file needs to be moved.
++ if is_mount_point_for_locally_mounted_remote_share(get_mount_point()):
++ return
++
++
+ source_metadata_dir_path = get_mount_point() + '/.Sugar-Metadata'
+
+ old_fname = os.path.basename(file_path)
+@@ -1003,7 +1011,7 @@ def _write_metadata_and_preview_files_and_return_file_paths(metadata,
+
+ # For copying to School-Server, we need to retain this property.
+ # Else wise, I have no idea why this property is being removed !!
+- if (metadata.get('mountpoint', '/') != (WEBDAV_MOUNT_POINT + SCHOOL_SERVER_IP_ADDRESS_OR_DNS_NAME)) and \
++ if (is_mount_point_for_locally_mounted_remote_share(metadata.get('mountpoint', '/')) == False) and \
+ (metadata.get('mountpoint', '/') != LOCAL_SHARES_MOUNT_POINT):
+ metadata_copy.pop('filesize', None)
+
+--
+1.7.4.4
+
diff --git a/rpms/sugar/0146-sdxo-2361-Minor-fix-for-major-bug.patch b/rpms/sugar/0146-sdxo-2361-Minor-fix-for-major-bug.patch
new file mode 100644
index 0000000..ac69860
--- /dev/null
+++ b/rpms/sugar/0146-sdxo-2361-Minor-fix-for-major-bug.patch
@@ -0,0 +1,31 @@
+From 69edc342a56d8cbceda66baf9cdf35188e2aa58e Mon Sep 17 00:00:00 2001
+From: Ajay Garg <ajay@activitycentral.com>
+Date: Sat, 22 Sep 2012 17:13:25 +0530
+Subject: [PATCH] sdxo#2361: Minor fix, for major bug :)
+Organization: Sugar Labs Foundation
+Signed-off-by: Ajay Garg <ajay@activitycentral.com>
+---
+ src/jarabe/journal/model.py | 4 ++--
+ 1 files changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/src/jarabe/journal/model.py b/src/jarabe/journal/model.py
+index e0c0620..0192541 100644
+--- a/src/jarabe/journal/model.py
++++ b/src/jarabe/journal/model.py
+@@ -73,11 +73,11 @@ SCHOOL_SERVER_IP_ADDRESS_OR_DNS_NAME_PATH = \
+ '/desktop/sugar/network/school_server_ip_address_or_dns_name'
+
+ client = gconf.client_get_default()
+-SCHOOL_SERVER_IP_ADDRESS_OR_DNS_NAME = client.get_string(SCHOOL_SERVER_IP_ADDRESS_OR_DNS_NAME_PATH)
++SCHOOL_SERVER_IP_ADDRESS_OR_DNS_NAME = client.get_string(SCHOOL_SERVER_IP_ADDRESS_OR_DNS_NAME_PATH) or ''
+
+
+ def is_school_server_present():
+- return not (SCHOOL_SERVER_IP_ADDRESS_OR_DNS_NAME is None)
++ return not (SCHOOL_SERVER_IP_ADDRESS_OR_DNS_NAME is '')
+
+
+ def _get_mount_point(path):
+--
+1.7.4.4
+
diff --git a/rpms/sugar/0147-sdxo-2364-Do-not-show-checkboxes-in-ObjectChooser.patch b/rpms/sugar/0147-sdxo-2364-Do-not-show-checkboxes-in-ObjectChooser.patch
new file mode 100644
index 0000000..c263dac
--- /dev/null
+++ b/rpms/sugar/0147-sdxo-2364-Do-not-show-checkboxes-in-ObjectChooser.patch
@@ -0,0 +1,77 @@
+From ba06d75978a91acc7609c1b513dd3cb4a2c8a55f Mon Sep 17 00:00:00 2001
+From: Ajay Garg <ajay@activitycentral.com>
+Date: Wed, 26 Sep 2012 00:28:04 +0530
+Subject: [PATCH] sdxo#2364: Do not show checkboxes in ObjectChooser.
+Organization: Sugar Labs Foundation
+Signed-off-by: Ajay Garg <ajay@activitycentral.com>
+---
+ src/jarabe/journal/listview.py | 24 +++++++++++++-----------
+ src/jarabe/journal/objectchooser.py | 2 +-
+ 2 files changed, 14 insertions(+), 12 deletions(-)
+
+diff --git a/src/jarabe/journal/listview.py b/src/jarabe/journal/listview.py
+index 780ba0a..7e04d47 100644
+--- a/src/jarabe/journal/listview.py
++++ b/src/jarabe/journal/listview.py
+@@ -70,7 +70,8 @@ class BaseListView(gtk.Bin):
+ 'clear-clicked': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])),
+ }
+
+- def __init__(self):
++ def __init__(self, is_object_chooser):
++ self._is_object_chooser = is_object_chooser
+ self._query = {}
+ self._model = None
+ self._progress_bar = None
+@@ -148,15 +149,16 @@ class BaseListView(gtk.Bin):
+ return object_id.startswith(self._query['mountpoints'][0])
+
+ def _add_columns(self):
+- cell_select = CellRendererToggle(self.tree_view)
+- cell_select.connect('clicked', self.__cell_select_clicked_cb)
++ if not self._is_object_chooser:
++ cell_select = CellRendererToggle(self.tree_view)
++ cell_select.connect('clicked', self.__cell_select_clicked_cb)
+
+- column = gtk.TreeViewColumn()
+- column.props.sizing = gtk.TREE_VIEW_COLUMN_FIXED
+- column.props.fixed_width = cell_select.props.width
+- column.pack_start(cell_select)
+- column.set_cell_data_func(cell_select, self.__select_set_data_cb)
+- self.tree_view.append_column(column)
++ column = gtk.TreeViewColumn()
++ column.props.sizing = gtk.TREE_VIEW_COLUMN_FIXED
++ column.props.fixed_width = cell_select.props.width
++ column.pack_start(cell_select)
++ column.set_cell_data_func(cell_select, self.__select_set_data_cb)
++ self.tree_view.append_column(column)
+
+ cell_favorite = CellRendererFavorite(self.tree_view)
+ cell_favorite.connect('clicked', self.__favorite_clicked_cb)
+@@ -680,8 +682,8 @@ class ListView(BaseListView):
+ ([str, str])),
+ }
+
+- def __init__(self):
+- BaseListView.__init__(self)
++ def __init__(self, is_object_chooser=False):
++ BaseListView.__init__(self, is_object_chooser)
+ self._is_dragging = False
+
+ self.tree_view.connect('drag-begin', self.__drag_begin_cb)
+diff --git a/src/jarabe/journal/objectchooser.py b/src/jarabe/journal/objectchooser.py
+index 59df14b..f69ce3a 100644
+--- a/src/jarabe/journal/objectchooser.py
++++ b/src/jarabe/journal/objectchooser.py
+@@ -192,7 +192,7 @@ class ChooserListView(BaseListView):
+ }
+
+ def __init__(self):
+- BaseListView.__init__(self)
++ BaseListView.__init__(self, is_object_chooser=True)
+
+ self.cell_icon.props.show_palette = False
+ self.tree_view.props.hover_selection = True
+--
+1.7.4.4
+
diff --git a/rpms/sugar/0148-sdxo-2370-Hide-the-alert-if-any-when-remote-share-is.patch b/rpms/sugar/0148-sdxo-2370-Hide-the-alert-if-any-when-remote-share-is.patch
new file mode 100644
index 0000000..18fdc26
--- /dev/null
+++ b/rpms/sugar/0148-sdxo-2370-Hide-the-alert-if-any-when-remote-share-is.patch
@@ -0,0 +1,27 @@
+From 9a831194ac0a1ee6b355f9a59e7294df5f2cc7f8 Mon Sep 17 00:00:00 2001
+From: Ajay Garg <ajay@activitycentral.com>
+Date: Thu, 27 Sep 2012 03:27:14 +0530
+Subject: [PATCH] sdxo#2370: Hide the alert (if any), when remote-share is re-loaded.
+Organization: Sugar Labs Foundation
+
+
+Signed-off-by: Ajay Garg <ajay@activitycentral.com>
+---
+ src/jarabe/view/palettes.py | 1 +
+ 1 files changed, 1 insertions(+), 0 deletions(-)
+
+diff --git a/src/jarabe/view/palettes.py b/src/jarabe/view/palettes.py
+index 34cce63..8aaf1af 100644
+--- a/src/jarabe/view/palettes.py
++++ b/src/jarabe/view/palettes.py
+@@ -376,6 +376,7 @@ class RemoteSharePalette(Palette):
+
+ def __reload_remote_share(self, menu_item):
+ from jarabe.journal.journalactivity import get_journal
++ get_journal().hide_alert()
+ get_journal().get_list_view().refresh()
+
+ def __unmount_activate_cb(self, menu_item):
+--
+1.7.4.4
+
diff --git a/rpms/sugar/0149-sdxo-2379-Fix-the-logic-of-computing-XO-Serial-Numbe.patch b/rpms/sugar/0149-sdxo-2379-Fix-the-logic-of-computing-XO-Serial-Numbe.patch
new file mode 100644
index 0000000..2c7294b
--- /dev/null
+++ b/rpms/sugar/0149-sdxo-2379-Fix-the-logic-of-computing-XO-Serial-Numbe.patch
@@ -0,0 +1,62 @@
+From c7c1696ae3dd3cd2f2dff6c4ab752f0778499bf3 Mon Sep 17 00:00:00 2001
+From: Ajay Garg <ajay@activitycentral.com>
+Date: Fri, 28 Sep 2012 00:14:41 +0530
+Subject: [PATCH] sdxo#2379: Fix the logic of computing XO-Serial Number.
+Organization: Sugar Labs Foundation
+Signed-off-by: Ajay Garg <ajay@activitycentral.com>
+---
+ src/jarabe/journal/misc.py | 33 +++++++++++++++++++++++++--------
+ 1 files changed, 25 insertions(+), 8 deletions(-)
+
+diff --git a/src/jarabe/journal/misc.py b/src/jarabe/journal/misc.py
+index 2c5d39a..e5b135d 100644
+--- a/src/jarabe/journal/misc.py
++++ b/src/jarabe/journal/misc.py
+@@ -323,19 +323,36 @@ def get_backup_identifier():
+ return serial_number
+
+ def get_xo_serial():
+- path = '/ofw/serial-number'
++ _OFW_TREE = '/ofw'
++ _PROC_TREE = '/proc/device-tree'
++ _SN = 'serial-number'
++ _not_available = _('Not available')
+
++ serial_no = None
++ if os.path.exists(os.path.join(_OFW_TREE, _SN)):
++ serial_no = read_file(os.path.join(_OFW_TREE, _SN))
++ elif os.path.exists(os.path.join(_PROC_TREE, _SN)):
++ serial_no = read_file(os.path.join(_PROC_TREE, _SN))
++
++ if serial_no is None:
++ serial_no = _not_available
++
++ # Remove the trailing binary character, else DBUS will crash.
++ return serial_no.rstrip('\x00')
++
++
++def read_file(path):
+ if os.access(path, os.R_OK) == 0:
+ return None
+
+- file_descriptor = open(path, 'r')
+- content = file_descriptor.read()
+- file_descriptor.close()
+-
+- if content:
+- return content.strip()
++ fd = open(path, 'r')
++ value = fd.read()
++ fd.close()
++ if value:
++ value = value.strip('\n')
++ return value
+ else:
+- logging.error('No serial number at %s', path)
++ logging.debug('No information in file or directory: %s', path)
+ return None
+
+ def get_nick():
+--
+1.7.4.4
+
diff --git a/rpms/sugar/0150-Removing-duplicate-code-instead-using-the-helper-mis.patch b/rpms/sugar/0150-Removing-duplicate-code-instead-using-the-helper-mis.patch
new file mode 100644
index 0000000..ce297be
--- /dev/null
+++ b/rpms/sugar/0150-Removing-duplicate-code-instead-using-the-helper-mis.patch
@@ -0,0 +1,65 @@
+From d205e48a0126b243e535dc2e4be605d5b5acc149 Mon Sep 17 00:00:00 2001
+From: Ajay Garg <ajay@activitycentral.com>
+Date: Fri, 28 Sep 2012 00:24:31 +0530
+Subject: [PATCH] Removing duplicate code; instead using the "helper" misc.py class.
+Organization: Sugar Labs Foundation
+Signed-off-by: Ajay Garg <ajay@activitycentral.com>
+---
+ src/jarabe/journal/palettes.py | 34 +---------------------------------
+ 1 files changed, 1 insertions(+), 33 deletions(-)
+
+diff --git a/src/jarabe/journal/palettes.py b/src/jarabe/journal/palettes.py
+index 72a06b1..0140edf 100644
+--- a/src/jarabe/journal/palettes.py
++++ b/src/jarabe/journal/palettes.py
+@@ -1015,7 +1015,7 @@ class SchoolServerMenu(BaseCopyMenuItem):
+ # Attach the info of the uploader.
+ from jarabe.model.buddy import get_owner_instance
+ metadata['uploader-nick'] = get_owner_instance().props.nick
+- metadata['uploader-serial'] = self.__get_serial_number()
++ metadata['uploader-serial'] = misc.get_xo_serial()
+
+ if not self._metadata_write_valid(metadata):
+ return False
+@@ -1027,38 +1027,6 @@ class SchoolServerMenu(BaseCopyMenuItem):
+ # This is sync-operation. Call the post-operation now.
+ self._post_operate_per_metadata_per_action(metadata)
+
+- def __get_serial_number(self):
+- _OFW_TREE = '/ofw'
+- _PROC_TREE = '/proc/device-tree'
+- _SN = 'serial-number'
+- _not_available = _('Not available')
+-
+- serial_no = None
+- if os.path.exists(os.path.join(_OFW_TREE, _SN)):
+- serial_no = self.__read_file(os.path.join(_OFW_TREE, _SN))
+- elif os.path.exists(os.path.join(_PROC_TREE, _SN)):
+- serial_no = self.__read_file(os.path.join(_PROC_TREE, _SN))
+-
+- if serial_no is None:
+- serial_no = _not_available
+-
+- # Remove the trailing binary character, else DBUS will crash.
+- return serial_no.rstrip('\x00')
+-
+- def __read_file(self, path):
+- if os.access(path, os.R_OK) == 0:
+- return None
+-
+- fd = open(path, 'r')
+- value = fd.read()
+- fd.close()
+- if value:
+- value = value.strip('\n')
+- return value
+- else:
+- logging.debug('No information in file or directory: %s', path)
+- return None
+-
+
+ class FriendsMenu(gtk.Menu):
+ __gtype_name__ = 'JournalFriendsMenu'
+--
+1.7.4.4
+
diff --git a/rpms/sugar/0151-sdxo-2346-Now-hovering-clicking-of-favorite-icons-in.patch b/rpms/sugar/0151-sdxo-2346-Now-hovering-clicking-of-favorite-icons-in.patch
new file mode 100644
index 0000000..eb0e2fa
--- /dev/null
+++ b/rpms/sugar/0151-sdxo-2346-Now-hovering-clicking-of-favorite-icons-in.patch
@@ -0,0 +1,132 @@
+From fca5be9052b0d9c20eb86e13f97a64121fd1162b Mon Sep 17 00:00:00 2001
+From: Ajay Garg <ajay@activitycentral.com>
+Date: Fri, 28 Sep 2012 09:51:50 +0530
+Subject: [PATCH] sdxo#2346: Now, hovering/clicking of favorite-icons in remote-shares has no effect.
+Organization: Sugar Labs Foundation
+Signed-off-by: Ajay Garg <ajay@activitycentral.com>
+---
+ src/jarabe/journal/expandedentry.py | 5 +++++
+ src/jarabe/journal/journalactivity.py | 1 +
+ src/jarabe/journal/keepicon.py | 7 +++++++
+ src/jarabe/journal/listview.py | 11 +++++++++++
+ src/jarabe/journal/model.py | 15 +++++++++++++++
+ 5 files changed, 39 insertions(+), 0 deletions(-)
+
+diff --git a/src/jarabe/journal/expandedentry.py b/src/jarabe/journal/expandedentry.py
+index 10e18ae..0e69b9a 100644
+--- a/src/jarabe/journal/expandedentry.py
++++ b/src/jarabe/journal/expandedentry.py
+@@ -441,6 +441,11 @@ class ExpandedEntry(hippo.CanvasBox):
+ return (str(self._metadata.get('keep', 0)) == '1')
+
+ def _keep_icon_activated_cb(self, keep_icon):
++ # If it is a locally-mounted rmote-share, return without doing
++ # any processing.
++ if model.is_current_mount_point_for_remote_share(model.DETAIL_VIEW):
++ return
++
+ if self.get_keep():
+ self._metadata['keep'] = 0
+ else:
+diff --git a/src/jarabe/journal/journalactivity.py b/src/jarabe/journal/journalactivity.py
+index 05b2e67..ac69d42 100644
+--- a/src/jarabe/journal/journalactivity.py
++++ b/src/jarabe/journal/journalactivity.py
+@@ -231,6 +231,7 @@ class JournalActivity(JournalWindow):
+ self._secondary_view = gtk.VBox()
+
+ self._detail_toolbox = DetailToolbox()
++ self._detail_toolbox.set_mount_point('/')
+ self._detail_toolbox.entry_toolbar.connect('volume-error',
+ self.__volume_error_cb)
+
+diff --git a/src/jarabe/journal/keepicon.py b/src/jarabe/journal/keepicon.py
+index 5bc299b..8ec3f96 100644
+--- a/src/jarabe/journal/keepicon.py
++++ b/src/jarabe/journal/keepicon.py
+@@ -22,6 +22,8 @@ from sugar.graphics.icon import CanvasIcon
+ from sugar.graphics import style
+ from sugar.graphics.xocolor import XoColor
+
++from jarabe.journal import model
++
+
+ class KeepIcon(CanvasIcon):
+ def __init__(self, keep):
+@@ -53,6 +55,11 @@ class KeepIcon(CanvasIcon):
+ setter=set_keep)
+
+ def __motion_notify_event_cb(self, icon, event):
++ # If it is a locally-mounted rmote-share, return without doing
++ # any processing.
++ if model.is_current_mount_point_for_remote_share(model.DETAIL_VIEW):
++ return
++
+ if not self._keep:
+ if event.detail == hippo.MOTION_DETAIL_ENTER:
+ client = gconf.client_get_default()
+diff --git a/src/jarabe/journal/listview.py b/src/jarabe/journal/listview.py
+index 7e04d47..31bf97e 100644
+--- a/src/jarabe/journal/listview.py
++++ b/src/jarabe/journal/listview.py
+@@ -342,6 +342,11 @@ class BaseListView(gtk.Bin):
+ cell.props.xo_color = None
+
+ def __favorite_clicked_cb(self, cell, path):
++ # If this is a remote-share, return without doing any
++ # processing.
++ if model.is_current_mount_point_for_remote_share(model.LIST_VIEW):
++ return
++
+ row = self._model[path]
+ metadata = model.get(row[ListModel.COLUMN_UID])
+ if not model.is_editable(metadata):
+@@ -798,6 +803,12 @@ class CellRendererFavorite(CellRendererIcon):
+ self.props.prelit_stroke_color = prelit_color.get_stroke_color()
+ self.props.prelit_fill_color = prelit_color.get_fill_color()
+
++ def on_render(self, window, widget, background_area, cell_area, expose_area, flags):
++ # If this is a remote-share, mask the "PRELIT" flag.
++ if model.is_current_mount_point_for_remote_share(model.LIST_VIEW):
++ flags = flags & (~(gtk.CELL_RENDERER_PRELIT))
++
++ CellRendererIcon.on_render(self, window, widget, background_area, cell_area, expose_area, flags)
+
+ class CellRendererDetail(CellRendererIcon):
+ __gtype_name__ = 'JournalCellRendererDetail'
+diff --git a/src/jarabe/journal/model.py b/src/jarabe/journal/model.py
+index 0192541..97ec85c 100644
+--- a/src/jarabe/journal/model.py
++++ b/src/jarabe/journal/model.py
+@@ -63,6 +63,9 @@ LOCAL_SHARES_MOUNT_POINT = '/var/www/web1/web/'
+
+ JOURNAL_METADATA_DIR = '.Sugar-Metadata'
+
++LIST_VIEW = 1
++DETAIL_VIEW = 2
++
+ _datastore = None
+ created = dispatch.Signal()
+ updated = dispatch.Signal()
+@@ -109,6 +112,18 @@ def is_mount_point_for_peer_share(mount_point):
+ return _check_remote_sharing_mount_point(mount_point, SHARE_TYPE_PEER)
+
+
++def is_current_mount_point_for_remote_share(view_type):
++ from jarabe.journal.journalactivity import get_journal, get_mount_point
++ if view_type == LIST_VIEW:
++ current_mount_point = get_mount_point()
++ elif view_type == DETAIL_VIEW:
++ current_mount_point = get_journal().get_detail_toolbox().get_mount_point()
++
++ if is_mount_point_for_locally_mounted_remote_share(current_mount_point):
++ return True
++ return False
++
++
+ def extract_ip_address_or_dns_name_from_locally_mounted_remote_share_path(path):
+ """
+ Path is of type ::
+--
+1.7.4.4
+
diff --git a/rpms/sugar/0152-sdxo-2336-Now-copy-to-options-are-shown-even-without.patch b/rpms/sugar/0152-sdxo-2336-Now-copy-to-options-are-shown-even-without.patch
new file mode 100644
index 0000000..e2fb5d7
--- /dev/null
+++ b/rpms/sugar/0152-sdxo-2336-Now-copy-to-options-are-shown-even-without.patch
@@ -0,0 +1,35 @@
+From 5a4699ee6d78bd350e3fc6b40bea1e2b5d1a5a7c Mon Sep 17 00:00:00 2001
+From: Ajay Garg <ajay@activitycentral.com>
+Date: Fri, 28 Sep 2012 11:37:51 +0530
+Subject: [PATCH] sdxo#2336: Now, copy-to options are shown, even without needing an explicit click for the first time.
+Organization: Sugar Labs Foundation
+Signed-off-by: Ajay Garg <ajay@activitycentral.com>
+---
+ src/jarabe/journal/journaltoolbox.py | 4 +++-
+ 1 files changed, 3 insertions(+), 1 deletions(-)
+
+diff --git a/src/jarabe/journal/journaltoolbox.py b/src/jarabe/journal/journaltoolbox.py
+index 663497d..c67b6c1 100644
+--- a/src/jarabe/journal/journaltoolbox.py
++++ b/src/jarabe/journal/journaltoolbox.py
+@@ -718,6 +718,7 @@ class BatchCopyButton(ToolButton, palettes.ActionItem):
+ self.props.tooltip = _('Copy')
+
+ self._metadata_list = None
++ self._fill_and_pop_up_options(None)
+
+ def _get_actionable_signal(self):
+ return 'clicked'
+@@ -731,7 +732,8 @@ class BatchCopyButton(ToolButton, palettes.ActionItem):
+ show_editing_alert=True,
+ show_progress_info_alert=True,
+ batch_mode=True)
+- self.props.palette.popup(immediate=True, state=1)
++ if widget_clicked is not None:
++ self.props.palette.popup(immediate=True, state=1)
+
+
+ class MultiSelectEntriesInfoWidget(gtk.ToolItem):
+--
+1.7.4.4
+
diff --git a/rpms/sugar/0153-Disable-1-to-N-feature.patch b/rpms/sugar/0153-Disable-1-to-N-feature.patch
new file mode 100644
index 0000000..ec97874
--- /dev/null
+++ b/rpms/sugar/0153-Disable-1-to-N-feature.patch
@@ -0,0 +1,86 @@
+From eb5aabfb30cae0725bf287a7239427287f5331e4 Mon Sep 17 00:00:00 2001
+From: Ajay Garg <ajay@activitycentral.com>
+Date: Tue, 2 Oct 2012 19:09:27 +0530
+Subject: [PATCH] Disable 1-to-N feature.
+Organization: Sugar Labs Foundation
+Signed-off-by: Ajay Garg <ajay@activitycentral.com>
+---
+ src/jarabe/journal/model.py | 2 +-
+ src/jarabe/journal/palettes.py | 11 -----------
+ src/jarabe/journal/volumestoolbar.py | 1 -
+ src/jarabe/view/buddymenu.py | 12 ------------
+ 4 files changed, 1 insertions(+), 25 deletions(-)
+
+diff --git a/src/jarabe/journal/model.py b/src/jarabe/journal/model.py
+index 97ec85c..a933a7a 100644
+--- a/src/jarabe/journal/model.py
++++ b/src/jarabe/journal/model.py
+@@ -80,7 +80,7 @@ SCHOOL_SERVER_IP_ADDRESS_OR_DNS_NAME = client.get_string(SCHOOL_SERVER_IP_ADDRES
+
+
+ def is_school_server_present():
+- return not (SCHOOL_SERVER_IP_ADDRESS_OR_DNS_NAME is '')
++ return False
+
+
+ def _get_mount_point(path):
+diff --git a/src/jarabe/journal/palettes.py b/src/jarabe/journal/palettes.py
+index 0140edf..b62d068 100644
+--- a/src/jarabe/journal/palettes.py
++++ b/src/jarabe/journal/palettes.py
+@@ -1155,17 +1155,6 @@ class CopyMenuHelper(gtk.Menu):
+ menu.append(documents_menu)
+ documents_menu.show()
+
+- if get_mount_point() != model.LOCAL_SHARES_MOUNT_POINT:
+- local_shares_menu = LocalSharesMenu(metadata_list,
+- show_editing_alert,
+- show_progress_info_alert,
+- batch_mode)
+- local_shares_menu.set_image(Icon(icon_name='emblem-neighborhood-shared',
+- icon_size=gtk.ICON_SIZE_MENU))
+- local_shares_menu.connect('volume-error', self.__volume_error_cb)
+- menu.append(local_shares_menu)
+- local_shares_menu.show()
+-
+ if (model.is_school_server_present()) and \
+ (not model.is_mount_point_for_locally_mounted_remote_share(get_mount_point())):
+ documents_menu = SchoolServerMenu(metadata_list,
+diff --git a/src/jarabe/journal/volumestoolbar.py b/src/jarabe/journal/volumestoolbar.py
+index 3b2c018..3b62e9f 100644
+--- a/src/jarabe/journal/volumestoolbar.py
++++ b/src/jarabe/journal/volumestoolbar.py
+@@ -213,7 +213,6 @@ class VolumesToolbar(gtk.Toolbar):
+
+ def _set_up_volumes(self):
+ self._set_up_documents_button()
+- self._set_up_local_shares_button()
+
+ client = gconf.client_get_default()
+ color = XoColor(client.get_string('/desktop/sugar/user/color'))
+diff --git a/src/jarabe/view/buddymenu.py b/src/jarabe/view/buddymenu.py
+index f6af1b5..0477f13 100644
+--- a/src/jarabe/view/buddymenu.py
++++ b/src/jarabe/view/buddymenu.py
+@@ -74,18 +74,6 @@ class BuddyMenu(Palette):
+ self.menu.append(menu_item)
+ menu_item.show()
+
+- remote_share_menu_item = None
+- from jarabe.journal import webdavmanager
+- if not webdavmanager.is_remote_webdav_loaded(self._buddy.props.ip_address):
+- remote_share_menu_item = MenuItem(_('Access Share'), 'list-add')
+- remote_share_menu_item.connect('activate', self._access_share_cb)
+- else:
+- remote_share_menu_item = MenuItem(_('Unmount Share'), 'list-remove')
+- remote_share_menu_item.connect('activate', self.__unmount_cb)
+-
+- self.menu.append(remote_share_menu_item)
+- remote_share_menu_item.show()
+-
+ self._invite_menu = MenuItem('')
+ self._invite_menu.connect('activate', self._invite_friend_cb)
+ self.menu.append(self._invite_menu)
+--
+1.7.4.4
+
diff --git a/rpms/sugar/0154-sdxo-1592-PART-1-Bringing-no-hippo-intro-pages-into-.patch b/rpms/sugar/0154-sdxo-1592-PART-1-Bringing-no-hippo-intro-pages-into-.patch
new file mode 100644
index 0000000..f493878
--- /dev/null
+++ b/rpms/sugar/0154-sdxo-1592-PART-1-Bringing-no-hippo-intro-pages-into-.patch
@@ -0,0 +1,352 @@
+From 233642096d746269b3b7cf919fde4c2a1ff739f8 Mon Sep 17 00:00:00 2001
+From: Ajay Garg <ajay@activitycentral.com>
+Date: Thu, 20 Sep 2012 00:20:50 +0530
+Subject: [PATCH 1/2] sdxo#1592 [PART-1]: Bringing "no-hippo" intro-pages into
+ dx3. This is required, since there is no correspondent
+ to "gtk.SpinButton" in Hippo.
+Organization: Sugar Labs Foundation
+
+
+Signed-off-by: Ajay Garg <ajay@activitycentral.com>
+---
+ src/jarabe/intro/colorpicker.py | 24 +++---
+ src/jarabe/intro/window.py | 149 ++++++++++++++++++++-------------------
+ 2 files changed, 89 insertions(+), 84 deletions(-)
+
+diff --git a/src/jarabe/intro/colorpicker.py b/src/jarabe/intro/colorpicker.py
+index 997199b..75c15c1 100644
+--- a/src/jarabe/intro/colorpicker.py
++++ b/src/jarabe/intro/colorpicker.py
+@@ -14,27 +14,27 @@
+ # along with this program; if not, write to the Free Software
+ # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+-import hippo
++import gtk
+
+-from sugar.graphics.icon import CanvasIcon
++from sugar.graphics.icon import Icon
+ from sugar.graphics import style
+ from sugar.graphics.xocolor import XoColor
+
+
+-class ColorPicker(hippo.CanvasBox, hippo.CanvasItem):
+- def __init__(self, **kwargs):
+- hippo.CanvasBox.__init__(self, **kwargs)
+- self.props.orientation = hippo.ORIENTATION_HORIZONTAL
++class ColorPicker(gtk.EventBox):
++ def __init__(self):
++ gtk.EventBox.__init__(self)
+ self._xo_color = None
+
+- self._xo = CanvasIcon(size=style.XLARGE_ICON_SIZE,
+- icon_name='computer-xo')
++ self._xo = Icon(pixel_size=style.XLARGE_ICON_SIZE,
++ icon_name='computer-xo')
+ self._set_random_colors()
+- self._xo.connect('activated', self._xo_activated_cb)
+- self.append(self._xo)
++ self.connect('button-press-event', self._button_press_cb)
++ self.add(self._xo)
+
+- def _xo_activated_cb(self, item):
+- self._set_random_colors()
++ def _button_press_cb(self, widget, event):
++ if event.button == 1 and event.type == gtk.gdk.BUTTON_PRESS:
++ self._set_random_colors()
+
+ def get_color(self):
+ return self._xo_color
+diff --git a/src/jarabe/intro/window.py b/src/jarabe/intro/window.py
+index df19fbf..a6a2a29 100644
+--- a/src/jarabe/intro/window.py
++++ b/src/jarabe/intro/window.py
+@@ -15,6 +15,7 @@
+ # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+ import os
++import os.path
+ import logging
+ from gettext import gettext as _
+ import gconf
+@@ -22,12 +23,11 @@ import pwd
+
+ import gtk
+ import gobject
+-import hippo
+
+ from sugar import env
++from sugar import profile
+ from sugar.graphics import style
+ from sugar.graphics.icon import Icon
+-from sugar.graphics.entry import CanvasEntry
+ from sugar.graphics.xocolor import XoColor
+
+ from jarabe.intro import colorpicker
+@@ -45,25 +45,36 @@ def create_profile(name, color=None):
+ client.set_string('/desktop/sugar/user/color', color.to_string())
+ client.suggest_sync()
+
++ if profile.get_pubkey() and profile.get_profile().privkey_hash:
++ logging.info('Valid key pair found, skipping generation.')
++ return
++
+ # Generate keypair
+ import commands
+ keypath = os.path.join(env.get_profile_path(), 'owner.key')
+- if not os.path.isfile(keypath):
+- cmd = "ssh-keygen -q -t dsa -f %s -C '' -N ''" % keypath
+- (s, o) = commands.getstatusoutput(cmd)
+- if s != 0:
+- logging.error('Could not generate key pair: %d %s', s, o)
+- else:
+- logging.error('Keypair exists, skip generation.')
++ if os.path.exists(keypath):
++ os.rename(keypath, keypath + '.broken')
++ logging.warning('Existing private key %s moved to %s.broken',
++ keypath, keypath)
++
++ if os.path.exists(keypath + '.pub'):
++ os.rename(keypath + '.pub', keypath + '.pub.broken')
++ logging.warning('Existing public key %s.pub moved to %s.pub.broken',
++ keypath, keypath)
+
++ cmd = "ssh-keygen -q -t dsa -f %s -C '' -N ''" % (keypath, )
++ (s, o) = commands.getstatusoutput(cmd)
++ if s != 0:
++ logging.error('Could not generate key pair: %d %s', s, o)
+
+-class _Page(hippo.CanvasBox):
++
++class _Page(gtk.VBox):
+ __gproperties__ = {
+ 'valid': (bool, None, None, False, gobject.PARAM_READABLE),
+ }
+
+- def __init__(self, **kwargs):
+- hippo.CanvasBox.__init__(self, **kwargs)
++ def __init__(self):
++ gtk.VBox.__init__(self)
+ self.valid = False
+
+ def set_valid(self, valid):
+@@ -80,27 +91,23 @@ class _Page(hippo.CanvasBox):
+
+ class _NamePage(_Page):
+ def __init__(self, intro):
+- _Page.__init__(self, xalign=hippo.ALIGNMENT_CENTER,
+- background_color=_BACKGROUND_COLOR.get_int(),
+- spacing=style.DEFAULT_SPACING,
+- orientation=hippo.ORIENTATION_HORIZONTAL,)
+-
++ _Page.__init__(self)
+ self._intro = intro
+
+- label = hippo.CanvasText(text=_('Name:'))
+- self.append(label)
+-
+- self._entry = CanvasEntry(box_width=style.zoom(300))
+- self._entry.set_background(_BACKGROUND_COLOR.get_html())
+- self._entry.connect('notify::text', self._text_changed_cb)
++ alignment = gtk.Alignment(0.5, 0.5, 0, 0)
++ self.pack_start(alignment, expand=True, fill=True)
+
+- widget = self._entry.props.widget
+- widget.set_max_length(45)
++ hbox = gtk.HBox(spacing=style.DEFAULT_SPACING)
++ alignment.add(hbox)
+
+- self.append(self._entry)
++ label = gtk.Label(_('Name:'))
++ hbox.pack_start(label, expand=False)
+
+- if gtk.widget_get_default_direction() == gtk.TEXT_DIR_RTL:
+- self.reverse()
++ self._entry = gtk.Entry()
++ self._entry.connect('notify::text', self._text_changed_cb)
++ self._entry.set_size_request(style.zoom(300), -1)
++ self._entry.set_max_length(45)
++ hbox.pack_start(self._entry, expand=False)
+
+ def _text_changed_cb(self, entry, pspec):
+ valid = len(entry.props.text.strip()) > 0
+@@ -113,22 +120,21 @@ class _NamePage(_Page):
+ self._entry.props.text = new_name
+
+ def activate(self):
+- self._entry.props.widget.grab_focus()
++ self._entry.grab_focus()
+
+
+ class _ColorPage(_Page):
+- def __init__(self, **kwargs):
+- _Page.__init__(self, xalign=hippo.ALIGNMENT_CENTER,
+- background_color=_BACKGROUND_COLOR.get_int(),
+- spacing=style.DEFAULT_SPACING,
+- yalign=hippo.ALIGNMENT_CENTER, **kwargs)
++ def __init__(self):
++ _Page.__init__(self)
+
+- self._label = hippo.CanvasText(text=_('Click to change color:'),
+- xalign=hippo.ALIGNMENT_CENTER)
+- self.append(self._label)
++ vbox = gtk.VBox(spacing=style.DEFAULT_SPACING)
++ self.pack_start(vbox, expand=True, fill=False)
+
+- self._cp = colorpicker.ColorPicker(xalign=hippo.ALIGNMENT_CENTER)
+- self.append(self._cp)
++ self._label = gtk.Label(_('Click to change color:'))
++ vbox.pack_start(self._label)
++
++ self._cp = colorpicker.ColorPicker()
++ vbox.pack_start(self._cp)
+
+ self._color = self._cp.get_color()
+ self.set_valid(True)
+@@ -137,7 +143,7 @@ class _ColorPage(_Page):
+ return self._cp.get_color()
+
+
+-class _IntroBox(hippo.CanvasBox):
++class _IntroBox(gtk.VBox):
+ __gsignals__ = {
+ 'done': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT])),
+@@ -150,8 +156,8 @@ class _IntroBox(hippo.CanvasBox):
+ PAGE_LAST = PAGE_COLOR
+
+ def __init__(self):
+- hippo.CanvasBox.__init__(self, padding=style.zoom(30),
+- background_color=_BACKGROUND_COLOR.get_int())
++ gtk.VBox.__init__(self)
++ self.set_border_width(style.zoom(30))
+
+ self._page = self.PAGE_NAME
+ self._name_page = _NamePage(self)
+@@ -172,58 +178,57 @@ class _IntroBox(hippo.CanvasBox):
+ self._setup_page()
+
+ def _setup_page(self):
+- self.remove_all()
++ for child in self.get_children():
++ self.remove(child)
+
+ if self._page == self.PAGE_NAME:
+ self._current_page = self._name_page
+ elif self._page == self.PAGE_COLOR:
+ self._current_page = self._color_page
+
+- self.append(self._current_page, hippo.PACK_EXPAND)
++ self.pack_start(self._current_page, expand=True)
+
+- button_box = hippo.CanvasBox(orientation=hippo.ORIENTATION_HORIZONTAL)
++ button_box = gtk.HButtonBox()
+
+- if self._page != self.PAGE_FIRST:
+- back_button = hippo.CanvasButton(text=_('Back'))
++ if self._page == self.PAGE_FIRST:
++ button_box.set_layout(gtk.BUTTONBOX_END)
++ else:
++ button_box.set_layout(gtk.BUTTONBOX_EDGE)
++ back_button = gtk.Button(_('Back'))
+ image = Icon(icon_name='go-left')
+- back_button.props.widget.set_image(image)
+- back_button.connect('activated', self._back_activated_cb)
+- button_box.append(back_button)
+-
+- spacer = hippo.CanvasBox()
+- button_box.append(spacer, hippo.PACK_EXPAND)
++ back_button.set_image(image)
++ back_button.connect('clicked', self._back_activated_cb)
++ button_box.pack_start(back_button)
+
+- self._next_button = hippo.CanvasButton()
++ self._next_button = gtk.Button()
+ image = Icon(icon_name='go-right')
+- self._next_button.props.widget.set_image(image)
++ self._next_button.set_image(image)
+
+ if self._page == self.PAGE_LAST:
+- self._next_button.props.text = _('Done')
+- self._next_button.connect('activated', self._done_activated_cb)
++ self._next_button.set_label(_('Done'))
++ self._next_button.connect('clicked', self._done_activated_cb)
+ else:
+- self._next_button.props.text = _('Next')
+- self._next_button.connect('activated', self._next_activated_cb)
++ self._next_button.set_label(_('Next'))
++ self._next_button.connect('clicked', self._next_activated_cb)
+
+ self._current_page.activate()
+
+ self._update_next_button()
+- button_box.append(self._next_button)
++ button_box.pack_start(self._next_button)
+
+ self._current_page.connect('notify::valid',
+ self._page_valid_changed_cb)
+- self.append(button_box)
+
+- if gtk.widget_get_default_direction() == gtk.TEXT_DIR_RTL:
+- button_box.reverse()
++ self.pack_start(button_box, expand=False)
++ self.show_all()
+
+ def _update_next_button(self):
+- widget = self._next_button.props.widget
+- widget.props.sensitive = self._current_page.props.valid
++ self._next_button.set_sensitive(self._current_page.props.valid)
+
+ def _page_valid_changed_cb(self, page, pspec):
+ self._update_next_button()
+
+- def _back_activated_cb(self, item):
++ def _back_activated_cb(self, widget):
+ self.back()
+
+ def back(self):
+@@ -231,7 +236,7 @@ class _IntroBox(hippo.CanvasBox):
+ self._page -= 1
+ self._setup_page()
+
+- def _next_activated_cb(self, item):
++ def _next_activated_cb(self, widget):
+ self.next()
+
+ def next(self):
+@@ -241,7 +246,7 @@ class _IntroBox(hippo.CanvasBox):
+ self._page += 1
+ self._setup_page()
+
+- def _done_activated_cb(self, item):
++ def _done_activated_cb(self, widget):
+ self.done()
+
+ def done(self):
+@@ -252,19 +257,19 @@ class _IntroBox(hippo.CanvasBox):
+
+
+ class IntroWindow(gtk.Window):
++ __gtype_name__ = 'SugarIntroWindow'
++
+ def __init__(self):
+ gtk.Window.__init__(self)
+
+ self.props.decorated = False
+ self.maximize()
+
+- self._canvas = hippo.Canvas()
+ self._intro_box = _IntroBox()
+ self._intro_box.connect('done', self._done_cb)
+- self._canvas.set_root(self._intro_box)
+
+- self.add(self._canvas)
+- self._canvas.show()
++ self.add(self._intro_box)
++ self._intro_box.show()
+ self.connect('key-press-event', self.__key_press_cb)
+
+ def _done_cb(self, box, name, color):
+--
+1.7.4.4
+
diff --git a/rpms/sugar/0155-sdxo-1592-PART-2-Incorporating-Age-Page-at-boot-up.patch b/rpms/sugar/0155-sdxo-1592-PART-2-Incorporating-Age-Page-at-boot-up.patch
new file mode 100644
index 0000000..44d58d5
--- /dev/null
+++ b/rpms/sugar/0155-sdxo-1592-PART-2-Incorporating-Age-Page-at-boot-up.patch
@@ -0,0 +1,155 @@
+From bc0718f0b8e089bf344e98f789e6d7a68c39e97a Mon Sep 17 00:00:00 2001
+From: Ajay Garg <ajay@activitycentral.com>
+Date: Thu, 20 Sep 2012 00:56:53 +0530
+Subject: [PATCH 2/2] sdxo#1592 [PART-2]: Incorporating "Age"-Page-at-boot-up.
+Organization: Sugar Labs Foundation
+Signed-off-by: Ajay Garg <ajay@activitycentral.com>
+---
+ src/jarabe/intro/window.py | 75 +++++++++++++++++++++++++++++++++++++++-----
+ 1 files changed, 67 insertions(+), 8 deletions(-)
+
+diff --git a/src/jarabe/intro/window.py b/src/jarabe/intro/window.py
+index a6a2a29..daa0c72 100644
+--- a/src/jarabe/intro/window.py
++++ b/src/jarabe/intro/window.py
+@@ -36,12 +36,29 @@ from jarabe.intro import colorpicker
+ _BACKGROUND_COLOR = style.COLOR_WHITE
+
+
+-def create_profile(name, color=None):
++def create_profile(name, age, color=None):
+ if not color:
+ color = XoColor()
+
+ client = gconf.client_get_default()
+ client.set_string('/desktop/sugar/user/nick', name)
++
++ # Algorithm to generate the timestamp of the birthday of the
++ # XO-user ::
++ #
++ # timestamp = current_timestamp - [age * (365 * 24 * 60 * 60)]
++ #
++ # Note that, this timestamp may actually (in worst-case) be
++ # off-target by 1 year, but that is ok, since we want an
++ # "approximate" age of the XO-user (for statistics-collection).
++ import time
++ current_timestamp = time.time()
++ xo_user_age_as_timestamp = int(age) * 365 * 24 * 60 * 60
++
++ approx_timestamp_at_user_birthday = current_timestamp - xo_user_age_as_timestamp
++ client.set_int('/desktop/sugar/user/birth_timestamp', int(approx_timestamp_at_user_birthday))
++ # Done.
++
+ client.set_string('/desktop/sugar/user/color', color.to_string())
+ client.suggest_sync()
+
+@@ -89,6 +106,42 @@ class _Page(gtk.VBox):
+ pass
+
+
++class _AgePage(_Page):
++ def __init__(self, intro):
++ _Page.__init__(self)
++ self._intro = intro
++
++ alignment = gtk.Alignment(0.5, 0.5, 0, 0)
++ self.pack_start(alignment, expand=True, fill=True)
++
++ hbox = gtk.HBox(spacing=style.DEFAULT_SPACING)
++ alignment.add(hbox)
++
++ label = gtk.Label(_('Age:'))
++ hbox.pack_start(label, expand=False)
++
++ adjustment = gtk.Adjustment(0, 0, 1000, 1, 0, 0)
++ self._entry = gtk.SpinButton(adjustment)
++ self._entry.props.editable = False
++ self._entry.connect('notify::text', self._text_changed_cb)
++ self._entry.set_max_length(15)
++ hbox.pack_start(self._entry, expand=False)
++
++ label = gtk.Label(_('years'))
++ hbox.pack_start(label, expand=False)
++
++
++ def _text_changed_cb(self, entry, pspec):
++ valid = int(entry.props.text) > 0
++ self.set_valid(valid)
++
++ def get_age(self):
++ return self._entry.props.text
++
++ def activate(self):
++ self._entry.grab_focus()
++
++
+ class _NamePage(_Page):
+ def __init__(self, intro):
+ _Page.__init__(self)
+@@ -146,13 +199,15 @@ class _ColorPage(_Page):
+ class _IntroBox(gtk.VBox):
+ __gsignals__ = {
+ 'done': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+- ([gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT])),
++ ([gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT])),
+ }
+
+ PAGE_NAME = 0
+- PAGE_COLOR = 1
++ PAGE_AGE = 1
++ PAGE_COLOR = 2
+
+ PAGE_FIRST = PAGE_NAME
++ PAGE_SECOND = PAGE_AGE
+ PAGE_LAST = PAGE_COLOR
+
+ def __init__(self):
+@@ -161,6 +216,7 @@ class _IntroBox(gtk.VBox):
+
+ self._page = self.PAGE_NAME
+ self._name_page = _NamePage(self)
++ self._age_page = _AgePage(self)
+ self._color_page = _ColorPage()
+ self._current_page = None
+ self._next_button = None
+@@ -183,6 +239,8 @@ class _IntroBox(gtk.VBox):
+
+ if self._page == self.PAGE_NAME:
+ self._current_page = self._name_page
++ if self._page == self.PAGE_AGE:
++ self._current_page = self._age_page
+ elif self._page == self.PAGE_COLOR:
+ self._current_page = self._color_page
+
+@@ -251,9 +309,10 @@ class _IntroBox(gtk.VBox):
+
+ def done(self):
+ name = self._name_page.get_name()
++ age = self._age_page.get_age()
+ color = self._color_page.get_color()
+
+- self.emit('done', name, color)
++ self.emit('done', name, age, color)
+
+
+ class IntroWindow(gtk.Window):
+@@ -272,12 +331,12 @@ class IntroWindow(gtk.Window):
+ self._intro_box.show()
+ self.connect('key-press-event', self.__key_press_cb)
+
+- def _done_cb(self, box, name, color):
++ def _done_cb(self, box, name, age, color):
+ self.hide()
+- gobject.idle_add(self._create_profile_cb, name, color)
++ gobject.idle_add(self._create_profile_cb, name, age, color)
+
+- def _create_profile_cb(self, name, color):
+- create_profile(name, color)
++ def _create_profile_cb(self, name, age, color):
++ create_profile(name, age, color)
+ gtk.main_quit()
+
+ return False
+--
+1.7.4.4
+