diff options
author | Ajay Garg <ajay@activitycentral.com> | 2012-10-02 14:59:50 (GMT) |
---|---|---|
committer | Ajay Garg <ajay@activitycentral.com> | 2012-10-02 14:59:50 (GMT) |
commit | 136ee50fb8588b3850cd5511c0958cb465417bf3 (patch) | |
tree | c5d54436650a1d8e82c384e2b151b5d0c98f6b2a | |
parent | 356d2706f016cbc223480036e800f5a01d0cde54 (diff) |
Unifying things as far as possible.
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 + |