summaryrefslogtreecommitdiff
path: root/share/caja-share.c
diff options
context:
space:
mode:
Diffstat (limited to 'share/caja-share.c')
-rw-r--r--share/caja-share.c1282
1 files changed, 1282 insertions, 0 deletions
diff --git a/share/caja-share.c b/share/caja-share.c
new file mode 100644
index 0000000..61be2d6
--- /dev/null
+++ b/share/caja-share.c
@@ -0,0 +1,1282 @@
+/* caja-share -- Caja File Sharing Extension
+ *
+ * Sebastien Estienne <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * (C) Copyright 2005 Ethium, Inc.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <libcaja-extension/caja-extension-types.h>
+#include <libcaja-extension/caja-column-provider.h>
+#include <libcaja-extension/caja-extension-types.h>
+#include <libcaja-extension/caja-file-info.h>
+#include <libcaja-extension/caja-info-provider.h>
+#include <libcaja-extension/caja-menu-provider.h>
+#include <libcaja-extension/caja-property-page-provider.h>
+
+#include "caja-share.h"
+
+#include <glib/gi18n-lib.h>
+
+#include <gio/gio.h>
+
+#include <gtk/gtk.h>
+
+#include <string.h>
+#include <time.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <stdlib.h>
+
+#include "shares.h"
+
+
+#define NEED_IF_GUESTOK_MASK (S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) /* need go+rx for guest enabled usershares */
+#define NEED_IF_WRITABLE_MASK (S_IWGRP | S_IWOTH) /* writable usershares need go+w additionally*/
+#define NEED_ALL_MASK (NEED_IF_GUESTOK_MASK | NEED_IF_WRITABLE_MASK)
+
+static GObjectClass *parent_class;
+
+/* Structure to hold all the information for a share's property page. If
+ * you add stuff to this, add it to free_property_page_cb() as well.
+ */
+typedef struct {
+ char *path; /* Full path which is being shared */
+ CajaFileInfo *fileinfo; /* Caja file to which this page refers */
+
+ GtkBuilder *xml;
+
+ GtkWidget *main; /* Widget that holds all the rest. Its "PropertyPage" GObject-data points to this PropertyPage structure */
+
+ GtkWidget *checkbutton_share_folder;
+ GtkWidget *hbox_share_name;
+ GtkWidget *hbox_share_comment;
+ GtkWidget *entry_share_name;
+ GtkWidget *checkbutton_share_rw_ro;
+ GtkWidget *checkbutton_share_guest_ok;
+ GtkWidget *entry_share_comment;
+ GtkWidget *label_status;
+ GtkWidget *button_cancel;
+ GtkWidget *button_apply;
+
+ GtkWidget *standalone_window;
+
+ gboolean was_initially_shared;
+ gboolean was_writable;
+ gboolean is_dirty;
+} PropertyPage;
+
+static void property_page_set_warning (PropertyPage *page);
+static void property_page_set_error (PropertyPage *page, const char *message);
+static void property_page_set_normal (PropertyPage *page);
+
+static void
+property_page_validate_fields (PropertyPage *page)
+{
+ const char *name;
+
+ name = gtk_entry_get_text (GTK_ENTRY (page->entry_share_name));
+
+ if (g_utf8_strlen (name, -1) <= 12)
+ property_page_set_normal (page);
+ else
+ property_page_set_warning (page);
+}
+
+static gboolean
+message_confirm_missing_permissions (GtkWidget *widget, const char *path, mode_t need_mask)
+{
+ GtkWidget *toplevel;
+ GtkWidget *dialog;
+ char *display_name;
+ gboolean result;
+
+ toplevel = gtk_widget_get_toplevel (widget);
+ if (!GTK_IS_WINDOW (toplevel))
+ toplevel = NULL;
+
+ display_name = g_filename_display_basename (path);
+
+ dialog = gtk_message_dialog_new (toplevel ? GTK_WINDOW (toplevel) : NULL,
+ 0,
+ GTK_MESSAGE_QUESTION,
+ GTK_BUTTONS_NONE,
+ _("Caja needs to add some permissions to your folder \"%s\" in order to share it"),
+ display_name);
+
+ /* FIXME: the following message only mentions "permission by others". We
+ * should probably be more explicit and mention group/other permissions.
+ * We'll be able to do that after the period of string freeze.
+ */
+ gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
+ _("The folder \"%s\" needs the following extra permissions for sharing to work:\n"
+ "%s%s%s"
+ "Do you want Caja to add these permissions to the folder automatically?"),
+ display_name,
+ (need_mask & (S_IRGRP | S_IROTH)) ? _(" - read permission by others\n") : "",
+ (need_mask & (S_IWGRP | S_IWOTH)) ? _(" - write permission by others\n") : "",
+ (need_mask & (S_IXGRP | S_IXOTH)) ? _(" - execute permission by others\n") : "");
+ g_free (display_name);
+
+ gtk_dialog_add_button (GTK_DIALOG (dialog), GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL);
+ gtk_dialog_add_button (GTK_DIALOG (dialog), _("Add the permissions automatically"), GTK_RESPONSE_ACCEPT);
+ gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_ACCEPT);
+
+ result = gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT;
+ gtk_widget_destroy (dialog);
+
+ return result;
+}
+
+static void
+error_when_changing_permissions (GtkWidget *widget, const char *path)
+{
+ GtkWidget *toplevel;
+ GtkWidget *dialog;
+ char *display_name;
+
+ toplevel = gtk_widget_get_toplevel (widget);
+ if (!GTK_IS_WINDOW (toplevel))
+ toplevel = NULL;
+
+ display_name = g_filename_display_basename (path);
+
+ dialog = gtk_message_dialog_new (toplevel ? GTK_WINDOW (toplevel) : NULL,
+ 0,
+ GTK_MESSAGE_ERROR,
+ GTK_BUTTONS_OK,
+ _("Could not change the permissions of folder \"%s\""),
+ display_name);
+ g_free (display_name);
+
+ gtk_dialog_run (GTK_DIALOG (dialog));
+ gtk_widget_destroy (dialog);
+}
+
+static char *
+get_key_file_path (void)
+{
+ return g_build_filename (g_get_home_dir (), ".mate2", "mate-file-manager-share-modified-permissions", NULL);
+}
+
+static void
+save_key_file (const char *filename, GKeyFile *key_file)
+{
+ char *contents;
+ gsize length;
+
+ /* NULL GError */
+ contents = g_key_file_to_data (key_file, &length, NULL);
+ if (!contents)
+ return;
+
+ /* NULL GError */
+ g_file_set_contents (filename, contents, length, NULL);
+
+ g_free (contents);
+}
+
+static void
+save_changed_permissions (const char *path, mode_t need_mask)
+{
+ GKeyFile *key_file;
+ char *key_file_path;
+ char str[50];
+
+ key_file = g_key_file_new ();
+ key_file_path = get_key_file_path ();
+
+ /* NULL GError
+ *
+ * We don't check the return value of this. If the file doesn't exist, we'll
+ * simply want to create it.
+ */
+ g_key_file_load_from_file (key_file, key_file_path, 0, NULL);
+
+ g_snprintf (str, sizeof (str), "%o", (guint) need_mask); /* octal, baby */
+ g_key_file_set_string (key_file, path, "need_mask", str);
+
+ save_key_file (key_file_path, key_file);
+
+ g_key_file_free (key_file);
+ g_free (key_file_path);
+}
+
+static void
+remove_permissions (const char *path, mode_t need_mask)
+{
+ struct stat st;
+ mode_t new_mode;
+
+ if (need_mask == 0)
+ return;
+
+ if (stat (path, &st) != 0)
+ return;
+
+ new_mode = st.st_mode & ~need_mask;
+
+ /* Bleah, no error checking */
+ chmod (path, new_mode);
+}
+
+static void
+remove_from_saved_permissions (const char *path, mode_t remove_mask)
+{
+ GKeyFile *key_file;
+ char *key_file_path;
+
+ if (remove_mask == 0)
+ return;
+
+ key_file = g_key_file_new ();
+ key_file_path = get_key_file_path ();
+
+ if (g_key_file_load_from_file (key_file, key_file_path, 0, NULL))
+ {
+ mode_t need_mask;
+ mode_t remove_from_current_mask;
+ char *str;
+
+ need_mask = 0;
+
+ /* NULL GError */
+ str = g_key_file_get_string (key_file, path, "need_mask", NULL);
+
+ if (str)
+ {
+ guint i;
+
+ if (sscanf (str, "%o", &i) == 1) /* octal */
+ need_mask = i;
+
+ g_free (str);
+ }
+
+ remove_from_current_mask = need_mask & remove_mask;
+ remove_permissions (path, remove_from_current_mask);
+
+ need_mask &= ~remove_mask;
+
+ if (need_mask == 0)
+ {
+ /* NULL GError */
+ g_key_file_remove_group (key_file, path, NULL);
+ }
+ else
+ {
+ char buf[50];
+
+ g_snprintf (buf, sizeof (buf), "%o", (guint) need_mask); /* octal */
+ g_key_file_set_string (key_file, path, "need_mask", buf);
+ }
+
+ save_key_file (key_file_path, key_file);
+ }
+
+ g_key_file_free (key_file);
+ g_free (key_file_path);
+}
+
+static void
+restore_saved_permissions (const char *path)
+{
+ remove_from_saved_permissions (path, NEED_ALL_MASK);
+}
+
+static void
+restore_write_permissions (const char *path)
+{
+ remove_from_saved_permissions (path, NEED_IF_WRITABLE_MASK);
+}
+
+typedef enum {
+ CONFIRM_CANCEL_OR_ERROR,
+ CONFIRM_NO_MODIFICATIONS,
+ CONFIRM_MODIFIED
+} ConfirmPermissionsStatus;
+
+static ConfirmPermissionsStatus
+confirm_sharing_permissions (GtkWidget *widget, const char *path, gboolean is_shared, gboolean guest_ok, gboolean is_writable)
+{
+ struct stat st;
+ mode_t mode, new_mode, need_mask;
+
+ if (!is_shared)
+ return CONFIRM_NO_MODIFICATIONS;
+
+ if (stat (path, &st) != 0)
+ return CONFIRM_NO_MODIFICATIONS; /* We'll just let "net usershare" give back an error if the file disappears */
+
+ new_mode = mode = st.st_mode;
+
+ if (guest_ok)
+ new_mode |= NEED_IF_GUESTOK_MASK;
+ if (is_writable)
+ new_mode |= NEED_IF_WRITABLE_MASK;
+
+ need_mask = new_mode & ~mode;
+
+ if (need_mask != 0)
+ {
+ g_assert (mode != new_mode);
+
+ if (!message_confirm_missing_permissions (widget, path, need_mask))
+ return CONFIRM_CANCEL_OR_ERROR;
+
+ if (chmod (path, new_mode) != 0)
+ {
+ error_when_changing_permissions (widget, path);
+ return CONFIRM_CANCEL_OR_ERROR;
+ }
+
+ save_changed_permissions (path, need_mask);
+
+ return CONFIRM_MODIFIED;
+ }
+ else
+ {
+ g_assert (mode == new_mode);
+ return CONFIRM_NO_MODIFICATIONS;
+ }
+
+ g_assert_not_reached ();
+ return CONFIRM_CANCEL_OR_ERROR;
+}
+
+static gboolean
+property_page_commit (PropertyPage *page)
+{
+ gboolean is_shared;
+ ShareInfo share_info;
+ ConfirmPermissionsStatus status;
+ GError *error;
+ gboolean retval;
+
+ is_shared = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (page->checkbutton_share_folder));
+
+ share_info.path = page->path;
+ share_info.share_name = (char *) gtk_entry_get_text (GTK_ENTRY (page->entry_share_name));
+ share_info.comment = (char *) gtk_entry_get_text (GTK_ENTRY (page->entry_share_comment));
+ share_info.is_writable = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (page->checkbutton_share_rw_ro));
+ share_info.guest_ok = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (page->checkbutton_share_guest_ok));
+
+ /* Do we need to unset the write permissions that we added in the past? */
+ if (is_shared && page->was_writable && !share_info.is_writable)
+ restore_write_permissions (page->path);
+
+ status = confirm_sharing_permissions (page->main, page->path, is_shared, share_info.guest_ok, share_info.is_writable);
+ if (status == CONFIRM_CANCEL_OR_ERROR)
+ return FALSE; /* the user didn't want us to change his folder's permissions */
+
+ error = NULL;
+ retval = shares_modify_share (share_info.path, is_shared ? &share_info : NULL, &error);
+
+ if (!retval)
+ {
+ property_page_set_error (page, error->message);
+ g_error_free (error);
+
+ /* Since the operation failed, we restore things to the way they were */
+ if (status == CONFIRM_MODIFIED)
+ restore_saved_permissions (page->path);
+ }
+ else
+ {
+ property_page_validate_fields (page);
+ caja_file_info_invalidate_extension_info (page->fileinfo);
+ }
+
+ if (!is_shared)
+ restore_saved_permissions (page->path);
+
+ /* update initially shared state, so that we may undo later on */
+ if (retval)
+ {
+ page->was_initially_shared = is_shared;
+ page->is_dirty = FALSE;
+ }
+
+ return retval;
+}
+
+/*--------------------------------------------------------------------------*/
+static gchar *
+get_fullpath_from_fileinfo(CajaFileInfo *fileinfo)
+{
+ GFile *file;
+ gchar *fullpath;
+
+ g_assert (fileinfo != NULL);
+
+ file = caja_file_info_get_location(fileinfo);
+ fullpath = g_file_get_path(file);
+ g_assert (fullpath != NULL && g_file_is_native(file)); /* In the beginning we checked that this was a local URI */
+ g_object_unref(file);
+
+ return(fullpath);
+}
+
+
+/*--------------------------------------------------------------------------*/
+static void
+property_page_set_warning (PropertyPage *page)
+{
+ GdkColor colorYellow;
+
+ gtk_label_set_text (GTK_LABEL (page->label_status), _("Share name is too long"));
+
+ gdk_color_parse ("#ECDF62", &colorYellow);
+ gtk_widget_modify_base (page->entry_share_name, GTK_STATE_NORMAL, &colorYellow);
+}
+
+
+static void
+property_page_set_error (PropertyPage *page, const char *message)
+{
+ GdkColor colorRed;
+
+ gtk_label_set_text (GTK_LABEL (page->label_status), message);
+
+ gdk_color_parse ("#C1665A", &colorRed);
+ gtk_widget_modify_base (page->entry_share_name, GTK_STATE_NORMAL, &colorRed);
+}
+
+static void
+property_page_set_normal (PropertyPage *page)
+{
+ gtk_label_set_text (GTK_LABEL (page->label_status), "");
+ gtk_widget_modify_base (page->entry_share_name, GTK_STATE_NORMAL, NULL);
+}
+
+static gboolean
+property_page_share_name_is_valid (PropertyPage *page)
+{
+ const char *newname;
+
+ newname = gtk_entry_get_text (GTK_ENTRY (page->entry_share_name));
+
+ if (strlen (newname) == 0)
+ {
+ property_page_set_error (page, _("The share name cannot be empty"));
+ return FALSE;
+ }
+ else
+ {
+ GError *error;
+ gboolean exists;
+
+ error = NULL;
+ if (!shares_get_share_name_exists (newname, &exists, &error))
+ {
+ char *str;
+
+ str = g_strdup_printf (_("Error while getting share information: %s"), error->message);
+ property_page_set_error (page, str);
+ g_free (str);
+ g_error_free (error);
+
+ return FALSE;
+ }
+
+ if (exists)
+ {
+ property_page_set_error (page, _("Another share has the same name"));
+ return FALSE;
+ }
+ else
+ {
+ property_page_set_normal (page);
+ return TRUE;
+ }
+ }
+}
+
+static void
+property_page_set_controls_sensitivity (PropertyPage *page,
+ gboolean sensitive)
+{
+ gtk_widget_set_sensitive (page->entry_share_name, sensitive);
+ gtk_widget_set_sensitive (page->entry_share_comment, sensitive);
+ gtk_widget_set_sensitive (page->hbox_share_comment, sensitive);
+ gtk_widget_set_sensitive (page->hbox_share_name, sensitive);
+ gtk_widget_set_sensitive (page->checkbutton_share_rw_ro, sensitive);
+
+ if (sensitive)
+ {
+ gboolean guest_ok_allowed;
+ shares_supports_guest_ok (&guest_ok_allowed, NULL);
+ gtk_widget_set_sensitive (page->checkbutton_share_guest_ok, guest_ok_allowed);
+ }
+ else
+ gtk_widget_set_sensitive (page->checkbutton_share_guest_ok, FALSE);
+}
+
+static void
+property_page_check_sensitivity (PropertyPage *page)
+{
+ gboolean enabled;
+ gboolean apply_is_sensitive;
+
+ enabled = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (page->checkbutton_share_folder));
+ property_page_set_controls_sensitivity (page, enabled);
+
+ if (enabled)
+ apply_is_sensitive = page->is_dirty || !page->was_initially_shared;
+ else
+ apply_is_sensitive = page->was_initially_shared;
+
+ gtk_widget_set_sensitive (page->button_apply, apply_is_sensitive);
+ gtk_button_set_label (GTK_BUTTON(page->button_apply),
+ page->was_initially_shared ? _("Modify _Share") : _("Create _Share"));
+}
+
+static void
+modify_share_name_text_entry (GtkEditable *editable,
+ gpointer user_data)
+{
+ PropertyPage *page;
+
+ page = user_data;
+
+ page->is_dirty = TRUE;
+
+ /* This function does simple validation on the share name and sets the error
+ * label; just let it run and ignore the result value.
+ */
+ property_page_share_name_is_valid (page);
+
+ property_page_check_sensitivity (page);
+}
+
+static void
+modify_share_comment_text_entry (GtkEditable *editable,
+ gpointer user_data)
+{
+ PropertyPage *page;
+
+ page = user_data;
+
+ page->is_dirty = TRUE;
+ property_page_check_sensitivity (page);
+}
+
+/*--------------------------------------------------------------------------*/
+static void
+on_checkbutton_share_folder_toggled (GtkToggleButton *togglebutton,
+ gpointer user_data)
+{
+ PropertyPage *page;
+
+ page = user_data;
+
+ property_page_check_sensitivity (page);
+}
+
+static void
+on_checkbutton_rw_ro_toggled (GtkToggleButton *togglebutton,
+ gpointer user_data)
+{
+ PropertyPage *page;
+
+ page = user_data;
+
+ page->is_dirty = TRUE;
+
+ property_page_check_sensitivity (page);
+}
+
+static void
+on_checkbutton_guest_ok_toggled (GtkToggleButton *togglebutton,
+ gpointer user_data)
+{
+ PropertyPage *page;
+
+ page = user_data;
+
+ page->is_dirty = TRUE;
+
+ property_page_check_sensitivity (page);
+}
+
+static void
+free_property_page_cb (gpointer data)
+{
+ PropertyPage *page;
+
+ page = data;
+
+ g_free (page->path);
+ g_object_unref (page->fileinfo);
+ g_object_unref (page->xml);
+
+ g_free (page);
+}
+
+static void
+button_apply_clicked_cb (GtkButton *button,
+ gpointer data)
+{
+ PropertyPage *page;
+
+ page = data;
+
+ if (property_page_commit (page))
+ {
+ if (page->standalone_window)
+ gtk_widget_destroy (page->standalone_window);
+ else
+ property_page_check_sensitivity (page);
+ }
+}
+
+/*--------------------------------------------------------------------------*/
+static PropertyPage *
+create_property_page (CajaFileInfo *fileinfo)
+{
+ PropertyPage *page;
+ GError *error;
+ ShareInfo *share_info;
+ char *share_name;
+ gboolean free_share_name;
+ const char *comment;
+ char *apply_button_label;
+
+ page = g_new0 (PropertyPage, 1);
+
+ page->path = get_fullpath_from_fileinfo(fileinfo);
+ page->fileinfo = g_object_ref (fileinfo);
+
+ error = NULL;
+ if (!shares_get_share_info_for_path (page->path, &share_info, &error))
+ {
+ /* We'll assume that there is no share for that path, but we'll still
+ * bring up an error dialog.
+ */
+ GtkWidget *message;
+
+ message = gtk_message_dialog_new (NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
+ _("There was an error while getting the sharing information"));
+ gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (message), "%s", error->message);
+ gtk_widget_show (message);
+
+ share_info = NULL;
+ g_error_free (error);
+ error = NULL;
+ }
+
+
+ page->xml = gtk_builder_new ();
+ gtk_builder_set_translation_domain (page->xml, GETTEXT_PACKAGE);
+ g_assert (gtk_builder_add_from_file (page->xml,
+ INTERFACES_DIR"/share-dialog.ui", &error));
+
+ page->main = GTK_WIDGET (gtk_builder_get_object (page->xml, "vbox1"));
+ g_assert (page->main != NULL);
+
+ g_object_set_data_full (G_OBJECT (page->main),
+ "PropertyPage",
+ page,
+ free_property_page_cb);
+
+ page->checkbutton_share_folder = GTK_WIDGET (gtk_builder_get_object (page->xml,"checkbutton_share_folder"));
+ page->hbox_share_comment = GTK_WIDGET (gtk_builder_get_object (page->xml,"hbox_share_comment"));
+ page->hbox_share_name = GTK_WIDGET (gtk_builder_get_object (page->xml,"hbox_share_name"));
+ page->checkbutton_share_rw_ro = GTK_WIDGET (gtk_builder_get_object (page->xml,"checkbutton_share_rw_ro"));
+ page->checkbutton_share_guest_ok = GTK_WIDGET (gtk_builder_get_object (page->xml,"checkbutton_share_guest_ok"));
+ page->entry_share_name = GTK_WIDGET (gtk_builder_get_object (page->xml,"entry_share_name"));
+ page->entry_share_comment = GTK_WIDGET (gtk_builder_get_object (page->xml,"entry_share_comment"));
+ page->label_status = GTK_WIDGET (gtk_builder_get_object (page->xml,"label_status"));
+ page->button_cancel = GTK_WIDGET (gtk_builder_get_object (page->xml,"button_cancel"));
+ page->button_apply = GTK_WIDGET (gtk_builder_get_object (page->xml,"button_apply"));
+
+ /* Sanity check so that we don't screw up the Glade file */
+ g_assert (page->checkbutton_share_folder != NULL
+ && page->hbox_share_comment != NULL
+ && page->hbox_share_name != NULL
+ && page->checkbutton_share_rw_ro != NULL
+ && page->checkbutton_share_guest_ok != NULL
+ && page->entry_share_name != NULL
+ && page->entry_share_comment != NULL
+ && page->label_status != NULL
+ && page->button_cancel != NULL
+ && page->button_apply != NULL);
+
+ if (share_info)
+ {
+ page->was_initially_shared = TRUE;
+ page->was_writable = share_info->is_writable;
+ }
+
+ /* Share name */
+
+ if (share_info)
+ {
+ share_name = share_info->share_name;
+ free_share_name = FALSE;
+ }
+ else
+ {
+ share_name = g_filename_display_basename (page->path);
+ free_share_name = TRUE;
+ }
+
+ gtk_entry_set_text (GTK_ENTRY (page->entry_share_name), share_name);
+
+ if (free_share_name)
+ g_free (share_name);
+
+ /* Comment */
+
+ if (share_info == NULL || share_info->comment == NULL)
+ comment = "";
+ else
+ comment = share_info->comment;
+
+ gtk_entry_set_text (GTK_ENTRY (page->entry_share_comment), comment);
+
+ /* Share toggle */
+
+ if (share_info)
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (page->checkbutton_share_folder), TRUE);
+ else
+ {
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (page->checkbutton_share_folder), FALSE);
+ }
+
+ /* Share name */
+
+ if (g_utf8_strlen(gtk_entry_get_text (GTK_ENTRY (page->entry_share_name)), -1) > 12)
+ property_page_set_warning (page);
+
+ /* Permissions */
+ if (share_info != NULL && share_info->is_writable)
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (page->checkbutton_share_rw_ro), TRUE);
+ else
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (page->checkbutton_share_rw_ro), FALSE);
+
+ /* Guest access */
+ if (share_info != NULL && share_info->guest_ok)
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (page->checkbutton_share_guest_ok), TRUE);
+ else
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (page->checkbutton_share_guest_ok), FALSE);
+
+ /* Apply button */
+
+ if (share_info)
+ apply_button_label = _("Modify _Share");
+ else
+ apply_button_label = _("Create _Share");
+
+ gtk_button_set_label (GTK_BUTTON (page->button_apply), apply_button_label);
+ gtk_button_set_use_underline (GTK_BUTTON (page->button_apply), TRUE);
+ gtk_button_set_image (GTK_BUTTON (page->button_apply), gtk_image_new_from_stock (GTK_STOCK_SAVE, GTK_ICON_SIZE_BUTTON));
+
+ gtk_widget_set_sensitive (page->button_apply, FALSE);
+
+ /* Sensitivity */
+
+ property_page_check_sensitivity (page);
+
+ /* Signal handlers */
+
+ g_signal_connect (page->checkbutton_share_folder, "toggled",
+ G_CALLBACK (on_checkbutton_share_folder_toggled),
+ page);
+
+ g_signal_connect (page->checkbutton_share_rw_ro, "toggled",
+ G_CALLBACK (on_checkbutton_rw_ro_toggled),
+ page);
+
+ g_signal_connect (page->checkbutton_share_guest_ok, "toggled",
+ G_CALLBACK (on_checkbutton_guest_ok_toggled),
+ page);
+
+ g_signal_connect (page->entry_share_name, "changed",
+ G_CALLBACK (modify_share_name_text_entry),
+ page);
+
+ g_signal_connect (page->entry_share_comment, "changed",
+ G_CALLBACK (modify_share_comment_text_entry),
+ page);
+
+ g_signal_connect (page->button_apply, "clicked",
+ G_CALLBACK (button_apply_clicked_cb), page);
+
+ if (share_info != NULL)
+ shares_free_share_info (share_info);
+
+ return page;
+}
+
+/* Implementation of the CajaInfoProvider interface */
+
+/* caja_info_provider_update_file_info
+ * This function is called by Caja when it wants the extension to
+ * fill in data about the file. It passes a CajaFileInfo object,
+ * which the extension can use to read data from the file, and which
+ * the extension should add data to.
+ *
+ * If the data can be added immediately (without doing blocking IO),
+ * the extension can do so, and return CAJA_OPERATION_COMPLETE.
+ * In this case the 'update_complete' and 'handle' parameters can be
+ * ignored.
+ *
+ * If waiting for the deata would block the UI, the extension should
+ * perform the task asynchronously, and return
+ * CAJA_OPERATION_IN_PROGRESS. The function must also set the
+ * 'handle' pointer to a value unique to the object, and invoke the
+ * 'update_complete' closure when the update is done.
+ *
+ * If the extension encounters an error, it should return
+ * CAJA_OPERATION_FAILED.
+ */
+typedef struct {
+ gboolean cancelled;
+ CajaInfoProvider *provider;
+ CajaFileInfo *file;
+ GClosure *update_complete;
+} CajaShareHandle;
+
+static CajaShareStatus
+get_share_status_and_free_share_info (ShareInfo *share_info)
+{
+ CajaShareStatus result;
+
+ if (!share_info)
+ result = CAJA_SHARE_NOT_SHARED;
+ else
+ {
+ if (share_info->is_writable)
+ result = CAJA_SHARE_SHARED_RW;
+ else
+ result = CAJA_SHARE_SHARED_RO;
+
+ shares_free_share_info (share_info);
+ }
+
+ return result;
+}
+
+
+/*--------------------------------------------------------------------------*/
+static void
+get_share_info_for_file_info (CajaFileInfo *file, ShareInfo **share_info, gboolean *is_shareable)
+{
+ char *uri;
+ char *local_path = NULL;
+ GFile *f;
+
+ *share_info = NULL;
+ *is_shareable = FALSE;
+
+ uri = caja_file_info_get_uri (file);
+ f = caja_file_info_get_location(file);
+ if (!uri)
+ goto out;
+
+#define NETWORK_SHARE_PREFIX "network:///share-"
+
+ if (g_str_has_prefix (uri, NETWORK_SHARE_PREFIX))
+ {
+ const char *share_name;
+
+ share_name = uri + strlen (NETWORK_SHARE_PREFIX);
+
+ /* FIXME: NULL GError */
+ if (!shares_get_share_info_for_share_name (share_name, share_info, NULL))
+ {
+ *share_info = NULL;
+ *is_shareable = TRUE; /* it *has* the prefix, anyway... we are just unsynchronized with what mate-vfs thinks */
+ }
+ else
+ {
+ *is_shareable = TRUE;
+ }
+
+ goto out;
+ }
+
+ if (!caja_file_info_is_directory(file))
+ goto out;
+
+ local_path = g_file_get_path(f);
+ if (!local_path || !g_file_is_native(f))
+ goto out;
+
+ /* FIXME: NULL GError */
+ if (!shares_get_share_info_for_path (local_path, share_info, NULL))
+ goto out;
+
+ *is_shareable = TRUE;
+
+ out:
+
+ g_object_unref(f);
+ g_free (uri);
+ g_free (local_path);
+}
+
+/*--------------------------------------------------------------------------*/
+static CajaShareStatus
+file_get_share_status_file(CajaFileInfo *file)
+{
+ ShareInfo *share_info;
+ gboolean is_shareable;
+
+ get_share_info_for_file_info (file, &share_info, &is_shareable);
+
+ if (!is_shareable)
+ return CAJA_SHARE_NOT_SHARED;
+
+ return get_share_status_and_free_share_info (share_info);
+}
+
+static CajaOperationResult
+caja_share_update_file_info (CajaInfoProvider *provider,
+ CajaFileInfo *file,
+ GClosure *update_complete,
+ CajaOperationHandle **handle)
+{
+/* gchar *share_status = NULL; */
+
+ switch (file_get_share_status_file (file)) {
+
+ case CAJA_SHARE_SHARED_RO:
+ caja_file_info_add_emblem (file, "shared");
+/* share_status = _("shared (read only)"); */
+ break;
+
+ case CAJA_SHARE_SHARED_RW:
+ caja_file_info_add_emblem (file, "shared");
+/* share_status = _("shared (read and write)"); */
+ break;
+
+ case CAJA_SHARE_NOT_SHARED:
+/* share_status = _("not shared"); */
+ break;
+
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+
+/* caja_file_info_add_string_attribute (file, */
+/* "CajaShare::share_status", */
+/* share_status); */
+ return CAJA_OPERATION_COMPLETE;
+}
+
+
+static void
+caja_share_cancel_update (CajaInfoProvider *provider,
+ CajaOperationHandle *handle)
+{
+ CajaShareHandle *share_handle;
+
+ share_handle = (CajaShareHandle*)handle;
+ share_handle->cancelled = TRUE;
+}
+
+static void
+caja_share_info_provider_iface_init (CajaInfoProviderIface *iface)
+{
+ iface->update_file_info = caja_share_update_file_info;
+ iface->cancel_update = caja_share_cancel_update;
+}
+
+/*--------------------------------------------------------------------------*/
+/* caja_property_page_provider_get_pages
+ *
+ * This function is called by Caja when it wants property page
+ * items from the extension.
+ *
+ * This function is called in the main thread before a property page
+ * is shown, so it should return quickly.
+ *
+ * The function should return a GList of allocated CajaPropertyPage
+ * items.
+ */
+static GList *
+caja_share_get_property_pages (CajaPropertyPageProvider *provider,
+ GList *files)
+{
+ PropertyPage *page;
+ GList *pages;
+ CajaPropertyPage *np_page;
+ CajaFileInfo *fileinfo;
+ ShareInfo *share_info;
+ gboolean is_shareable;
+
+ /* Only show the property page if 1 file is selected */
+ if (!files || files->next != NULL) {
+ return NULL;
+ }
+
+ fileinfo = CAJA_FILE_INFO (files->data);
+
+ get_share_info_for_file_info (fileinfo, &share_info, &is_shareable);
+ if (!is_shareable)
+ return NULL;
+
+ page = create_property_page (fileinfo);
+ gtk_widget_hide (page->button_cancel);
+
+ if (share_info)
+ shares_free_share_info (share_info);
+
+ pages = NULL;
+ np_page = caja_property_page_new
+ ("CajaShare::property_page",
+ gtk_label_new (_("Share")),
+ page->main);
+ pages = g_list_append (pages, np_page);
+
+ return pages;
+}
+
+/*--------------------------------------------------------------------------*/
+static void
+caja_share_property_page_provider_iface_init (CajaPropertyPageProviderIface *iface)
+{
+ iface->get_pages = caja_share_get_property_pages;
+}
+
+/*--------------------------------------------------------------------------*/
+static void
+caja_share_instance_init (CajaShare *share)
+{
+}
+
+/*--------------------------------------------------------------------------*/
+static void
+caja_share_class_init (CajaShareClass *class)
+{
+ parent_class = g_type_class_peek_parent (class);
+}
+
+/* caja_menu_provider_get_file_items
+ *
+ * This function is called by Caja when it wants context menu
+ * items from the extension.
+ *
+ * This function is called in the main thread before a context menu
+ * is shown, so it should return quickly.
+ *
+ * The function should return a GList of allocated CajaMenuItem
+ * items.
+ */
+
+static void
+button_cancel_clicked_cb (GtkButton *button, gpointer data)
+{
+ GtkWidget *window;
+
+ window = GTK_WIDGET (data);
+ gtk_widget_destroy (window);
+}
+
+static void
+share_this_folder_callback (CajaMenuItem *item,
+ gpointer user_data)
+{
+ CajaFileInfo *fileinfo;
+ PropertyPage *page;
+ GtkWidget * window;
+
+ fileinfo = CAJA_FILE_INFO (user_data);
+ g_assert (fileinfo != NULL);
+
+ window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+ gtk_window_set_title (GTK_WINDOW (window), _("Folder Sharing"));
+ page = create_property_page (fileinfo);
+ page->standalone_window = window;
+ g_signal_connect (page->button_cancel, "clicked",
+ G_CALLBACK (button_cancel_clicked_cb), window);
+
+ gtk_container_add (GTK_CONTAINER (window), page->main);
+ gtk_widget_show (window);
+}
+
+static GList *
+caja_share_get_file_items (CajaMenuProvider *provider,
+ GtkWidget *window,
+ GList *files)
+{
+ GList *items;
+ CajaMenuItem *item;
+ CajaFileInfo *fileinfo;
+ ShareInfo *share_info;
+ gboolean is_shareable;
+
+ /* Only show the property page if 1 file is selected */
+ if (!files || files->next != NULL) {
+ return NULL;
+ }
+
+ fileinfo = CAJA_FILE_INFO (files->data);
+
+ get_share_info_for_file_info (fileinfo, &share_info, &is_shareable);
+
+ if (!is_shareable)
+ return NULL;
+
+ if (share_info)
+ shares_free_share_info (share_info);
+
+ /* We don't own a reference to the file info to keep it around, so acquire one */
+ g_object_ref (fileinfo);
+
+ /* FMQ: change the label to "Share with Windows users"? */
+ item = caja_menu_item_new ("CajaShare::share",
+ _("Sharing Options"),
+ _("Share this Folder"),
+ "folder-remote");
+ g_signal_connect (item, "activate",
+ G_CALLBACK (share_this_folder_callback),
+ fileinfo);
+ g_object_set_data_full (G_OBJECT (item),
+ "files",
+ fileinfo,
+ g_object_unref); /* Release our reference when the menu item goes away */
+
+ items = g_list_append (NULL, item);
+ return items;
+}
+
+/*--------------------------------------------------------------------------*/
+static void
+caja_share_menu_provider_iface_init (CajaMenuProviderIface *iface)
+{
+ iface->get_file_items = caja_share_get_file_items;
+}
+
+/*--------------------------------------------------------------------------*/
+/* Type registration. Because this type is implemented in a module
+ * that can be unloaded, we separate type registration from get_type().
+ * the type_register() function will be called by the module's
+ * initialization function. */
+static GType share_type = 0;
+
+#define CAJA_TYPE_SHARE (caja_share_get_type ())
+
+static GType
+caja_share_get_type (void)
+{
+ return share_type;
+}
+
+static void
+caja_share_register_type (GTypeModule *module)
+{
+ static const GTypeInfo info = {
+ sizeof (CajaShareClass),
+ (GBaseInitFunc) NULL,
+ (GBaseFinalizeFunc) NULL,
+ (GClassInitFunc) caja_share_class_init,
+ NULL,
+ NULL,
+ sizeof (CajaShare),
+ 0,
+ (GInstanceInitFunc) caja_share_instance_init,
+ };
+
+ share_type = g_type_module_register_type (module,
+ G_TYPE_OBJECT,
+ "CajaShare",
+ &info, 0);
+
+ static const GInterfaceInfo property_page_provider_iface_info = {
+ (GInterfaceInitFunc) caja_share_property_page_provider_iface_init,
+ NULL,
+ NULL
+ };
+
+ g_type_module_add_interface (module,
+ share_type,
+ CAJA_TYPE_PROPERTY_PAGE_PROVIDER,
+ &property_page_provider_iface_info);
+
+
+ static const GInterfaceInfo info_provider_iface_info = {
+ (GInterfaceInitFunc) caja_share_info_provider_iface_init,
+ NULL,
+ NULL
+ };
+
+ g_type_module_add_interface (module,
+ share_type,
+ CAJA_TYPE_INFO_PROVIDER,
+ &info_provider_iface_info);
+
+ /* Menu right clik */
+ static const GInterfaceInfo menu_provider_iface_info = {
+ (GInterfaceInitFunc) caja_share_menu_provider_iface_init,
+ NULL,
+ NULL
+ };
+
+ g_type_module_add_interface (module,
+ share_type,
+ CAJA_TYPE_MENU_PROVIDER,
+ &menu_provider_iface_info);
+
+}
+
+/* Extension module functions. These functions are defined in
+ * caja-extensions-types.h, and must be implemented by all
+ * extensions. */
+
+/* Initialization function. In addition to any module-specific
+ * initialization, any types implemented by the module should
+ * be registered here. */
+void
+caja_module_initialize (GTypeModule *module)
+{
+ /*g_print ("Initializing caja-share extension\n");*/
+
+ bindtextdomain(GETTEXT_PACKAGE, MATELOCALEDIR);
+ bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
+
+ caja_share_register_type (module);
+}
+
+/* Perform module-specific shutdown. */
+void
+caja_module_shutdown (void)
+{
+ /*g_print ("Shutting down caja-share extension\n");*/
+ /* FIXME freeing */
+}
+
+/* List all the extension types. */
+void
+caja_module_list_types (const GType **types,
+ int *num_types)
+{
+ static GType type_list[1];
+
+ type_list[0] = CAJA_TYPE_SHARE;
+
+ *types = type_list;
+ *num_types = 1;
+}