summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStefano Karapetsas <[email protected]>2013-10-18 15:12:48 +0200
committerStefano Karapetsas <[email protected]>2013-10-18 15:12:48 +0200
commitfb045fd28691358757a021f81064480885939ab5 (patch)
tree171ae2c90b83a618f3e4955f9a0029547739a4e0
parent1d56fce29aa71f3d6c1023e7a635b7d5264dbbf7 (diff)
downloadcaja-extensions-fb045fd28691358757a021f81064480885939ab5.tar.bz2
caja-extensions-fb045fd28691358757a021f81064480885939ab5.tar.xz
Add share extension
-rw-r--r--AUTHORS11
-rw-r--r--Makefile.am3
-rw-r--r--configure.ac7
-rw-r--r--po/POTFILES.in3
-rw-r--r--share/Makefile.am28
-rw-r--r--share/caja-share.c1284
-rw-r--r--share/caja-share.h65
-rw-r--r--share/mate-file-manager-share.c1284
-rw-r--r--share/mate-file-manager-share.h65
-rw-r--r--share/share-dialog.ui301
-rw-r--r--share/shares.c1023
-rw-r--r--share/shares.h47
12 files changed, 4120 insertions, 1 deletions
diff --git a/AUTHORS b/AUTHORS
index 7b33447..d9fb08b 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -22,3 +22,14 @@ Roberto Majadas <[email protected]>
This module it's highly influenced by file-roller module
Thanks Paolo Bacchilega :)
Bluetooth plugin: Bastien Nocera <[email protected]>
+
+nautilus-share:
+Sebastien Estienne <[email protected]>
+Federico Mena Quintero <[email protected]>
+Ed Catmur <[email protected]> : dbus 0.3x API
+VoJcEK <[email protected]> : polish translation
+Michael Kanis <[email protected]> : german translation
+WareKala <[email protected]> : finnish translation
+Mirko Maischberger <[email protected]> : italian translation
+Aaron Kurtz <[email protected]> : Fedora Core 4 package
+Lucius Curado <[email protected]> : brazilian translation
diff --git a/Makefile.am b/Makefile.am
index f7defe6..c0a4f55 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -4,7 +4,8 @@ SUBDIRS = \
gksu \
image-converter \
open-terminal \
- sendto
+ sendto \
+ share
EXTRA_DIST = \
intltool-extract.in \
diff --git a/configure.ac b/configure.ac
index 41141cb..66da57a 100644
--- a/configure.ac
+++ b/configure.ac
@@ -71,6 +71,12 @@ AC_SUBST(SENDTO_CFLAGS)
AC_SUBST(SENDTO_LIBS)
AC_DEFINE_UNQUOTED(GAJIM_SHARE_DIR, "$prefix/share/gajim", [path to gajim share dir])
+# share extension
+PKG_CHECK_MODULES(SHARE,
+ glib-2.0 >= $GLIB_REQUIRED)
+AC_SUBST(SHARE_CFLAGS)
+AC_SUBST(SHARE_LIBS)
+
# Get caja extensions directory
CAJA_EXTENSION_DIR=`$PKG_CONFIG --variable=extensiondir libcaja-extension`
AC_SUBST(CAJA_EXTENSION_DIR)
@@ -98,6 +104,7 @@ AC_OUTPUT([
sendto/plugins/pidgin/Makefile
sendto/plugins/removable-devices/Makefile
sendto/plugins/upnp/Makefile
+ share/Makefile
po/Makefile.in
])
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 102d299..40d92aa 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -20,3 +20,6 @@ sendto/plugins/caja-burn/caja-burn.c
sendto/plugins/pidgin/pidgin.c
sendto/plugins/removable-devices/removable-devices.c
sendto/plugins/upnp/upnp.c
+share/mate-file-manager-share.c
+[type: gettext/glade]share/share-dialog.ui
+share/shares.c
diff --git a/share/Makefile.am b/share/Makefile.am
new file mode 100644
index 0000000..3a9e28c
--- /dev/null
+++ b/share/Makefile.am
@@ -0,0 +1,28 @@
+interfacesdir = $(datadir)/caja-share
+interfaces_DATA = share-dialog.ui
+EXTRA_DIST = share-dialog.ui
+
+libcaja_share_la_CFLAGS = \
+ -Wall \
+ -DG_LOG_DOMAIN=\"Caja-Share\" \
+ -DINTERFACES_DIR=\"$(interfacesdir)\" \
+ -DDATADIR=\"$(datadir)\" \
+ -DMATELOCALEDIR=\""$(datadir)/locale"\" \
+ -I$(top_srcdir) \
+ -I$(top_builddir) \
+ $(WARN_CFLAGS) \
+ $(DISABLE_DEPRECATED_CFLAGS) \
+ $(CAJA_CFLAGS) \
+ $(SHARE_CFLAGS)
+
+caja_extensiondir=$(CAJA_EXTENSION_DIR)
+caja_extension_LTLIBRARIES=libcaja-share.la
+
+libcaja_share_la_SOURCES = \
+ mate-file-manager-share.c \
+ mate-file-manager-share.h \
+ shares.c \
+ shares.h
+
+libcaja_share_la_LDFLAGS = -module -avoid-version
+libcaja_share_la_LIBADD = $(SHARE_LIBS) $(CAJA_LIBS)
diff --git a/share/caja-share.c b/share/caja-share.c
new file mode 100644
index 0000000..986f48d
--- /dev/null
+++ b/share/caja-share.c
@@ -0,0 +1,1284 @@
+/* mate-file-manager-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 "mate-file-manager-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, "mate-file-manager-share");
+ 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);
+
+ /* onglet share propri�t� */
+ 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);
+
+
+ /* premier page propri�t� ? */
+ 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 mate-file-manager-share extension\n");*/
+
+ bindtextdomain("mate-file-manager-share", CAJA_SHARE_LOCALEDIR);
+ bind_textdomain_codeset("mate-file-manager-share", "UTF-8");
+
+ caja_share_register_type (module);
+}
+
+/* Perform module-specific shutdown. */
+void
+caja_module_shutdown (void)
+{
+ /*g_print ("Shutting down mate-file-manager-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;
+}
diff --git a/share/caja-share.h b/share/caja-share.h
new file mode 100644
index 0000000..9907ac2
--- /dev/null
+++ b/share/caja-share.h
@@ -0,0 +1,65 @@
+/* mate-file-manager-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.
+ */
+
+#ifndef CAJA_SHARE_H
+#define CAJA_SHARE_H
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+/* Declarations for the Share extension object. This object will be
+ * instantiated by caja. It implements the GInterfaces
+ * exported by libcaja. */
+
+
+typedef struct _CajaShare CajaShare;
+typedef struct _CajaShareClass CajaShareClass;
+
+struct _CajaShare {
+ GObject parent_slot;
+};
+
+struct _CajaShareClass {
+ GObjectClass parent_slot;
+
+ /* No extra class members */
+};
+
+
+typedef struct _CajaShareData CajaShareData;
+
+struct _CajaShareData {
+ gchar *fullpath;
+ gchar *section;
+ CajaFileInfo *fileinfo;
+};
+
+G_END_DECLS
+
+typedef enum {
+ CAJA_SHARE_NOT_SHARED,
+ CAJA_SHARE_SHARED_RO,
+ CAJA_SHARE_SHARED_RW
+} CajaShareStatus;
+
+#endif
+
diff --git a/share/mate-file-manager-share.c b/share/mate-file-manager-share.c
new file mode 100644
index 0000000..e71e414
--- /dev/null
+++ b/share/mate-file-manager-share.c
@@ -0,0 +1,1284 @@
+/* mate-file-manager-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 "mate-file-manager-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, "mate-file-manager-share");
+ 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);
+
+ /* onglet share propri�t� */
+ 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);
+
+
+ /* premier page propri�t� ? */
+ 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 mate-file-manager-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 mate-file-manager-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;
+}
diff --git a/share/mate-file-manager-share.h b/share/mate-file-manager-share.h
new file mode 100644
index 0000000..9907ac2
--- /dev/null
+++ b/share/mate-file-manager-share.h
@@ -0,0 +1,65 @@
+/* mate-file-manager-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.
+ */
+
+#ifndef CAJA_SHARE_H
+#define CAJA_SHARE_H
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+/* Declarations for the Share extension object. This object will be
+ * instantiated by caja. It implements the GInterfaces
+ * exported by libcaja. */
+
+
+typedef struct _CajaShare CajaShare;
+typedef struct _CajaShareClass CajaShareClass;
+
+struct _CajaShare {
+ GObject parent_slot;
+};
+
+struct _CajaShareClass {
+ GObjectClass parent_slot;
+
+ /* No extra class members */
+};
+
+
+typedef struct _CajaShareData CajaShareData;
+
+struct _CajaShareData {
+ gchar *fullpath;
+ gchar *section;
+ CajaFileInfo *fileinfo;
+};
+
+G_END_DECLS
+
+typedef enum {
+ CAJA_SHARE_NOT_SHARED,
+ CAJA_SHARE_SHARED_RO,
+ CAJA_SHARE_SHARED_RW
+} CajaShareStatus;
+
+#endif
+
diff --git a/share/share-dialog.ui b/share/share-dialog.ui
new file mode 100644
index 0000000..caf96d3
--- /dev/null
+++ b/share/share-dialog.ui
@@ -0,0 +1,301 @@
+<?xml version="1.0"?>
+<interface>
+ <!-- interface-requires gtk+ 2.12 -->
+ <!-- interface-naming-policy toplevel-contextual -->
+ <object class="GtkVBox" id="vbox1">
+ <property name="visible">True</property>
+ <property name="border_width">12</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkHBox" id="hbox6">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkImage" id="logo_image">
+ <property name="visible">True</property>
+ <property name="pixel_size">64</property>
+ <property name="icon_name">folder-remote</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="logo_label">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">&lt;big&gt;&lt;b&gt;Folder Sharing&lt;/b&gt;&lt;/big&gt;</property>
+ <property name="use_markup">True</property>
+ <property name="justify">center</property>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHSeparator" id="hseparator1">
+ <property name="visible">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkTable" id="table1">
+ <property name="visible">True</property>
+ <property name="n_rows">5</property>
+ <property name="n_columns">2</property>
+ <property name="column_spacing">12</property>
+ <property name="row_spacing">6</property>
+ <child>
+ <object class="GtkHBox" id="hbox_share_name">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkLabel" id="label1">
+ <property name="width_request">20</property>
+ <property name="visible">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label_share_name">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Share _name:</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">entry_share_name</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="x_options">GTK_FILL</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="entry_share_name">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">&#x25CF;</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="checkbutton_share_folder">
+ <property name="label" translatable="yes">Share this _folder</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="right_attach">2</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox_share_comment">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkLabel" id="label2">
+ <property name="width_request">20</property>
+ <property name="visible">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label_share_comment">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Co_mment:</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">entry_share_comment</property>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ <property name="x_options">GTK_FILL</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="entry_share_comment">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">&#x25CF;</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="checkbutton_share_rw_ro">
+ <property name="label" translatable="yes">_Allow others to create and delete files in this folder</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="right_attach">2</property>
+ <property name="top_attach">3</property>
+ <property name="bottom_attach">4</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="checkbutton_share_guest_ok">
+ <property name="label" translatable="yes">_Guest access (for people without a user account)</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="right_attach">2</property>
+ <property name="top_attach">4</property>
+ <property name="bottom_attach">5</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label_status">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="wrap">True</property>
+ <property name="selectable">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHSeparator" id="hseparator2"/>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">4</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkScrolledWindow" id="scrolledwindow1">
+ <property name="sensitive">False</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">automatic</property>
+ <property name="vscrollbar_policy">automatic</property>
+ <property name="shadow_type">in</property>
+ <child>
+ <object class="GtkTreeView" id="treeview_share_list">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">1</property>
+ <property name="position">5</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHSeparator" id="hseparator3">
+ <property name="sensitive">False</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">6</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHButtonBox" id="hbuttonbox1">
+ <property name="visible">True</property>
+ <property name="spacing">5</property>
+ <property name="layout_style">end</property>
+ <child>
+ <object class="GtkButton" id="button_cancel">
+ <property name="label">gtk-cancel</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_stock">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button_apply">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="receives_default">True</property>
+ <child>
+ <object class="GtkImage" id="image1">
+ <property name="visible">True</property>
+ <property name="stock">gtk-save</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">7</property>
+ </packing>
+ </child>
+ </object>
+</interface>
diff --git a/share/shares.c b/share/shares.c
new file mode 100644
index 0000000..cd609f3
--- /dev/null
+++ b/share/shares.c
@@ -0,0 +1,1023 @@
+#include <config.h>
+#include <string.h>
+#include <time.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <glib/gi18n-lib.h>
+#include "shares.h"
+
+#undef DEBUG_SHARES
+#ifdef DEBUG_SHARES
+# define NET_USERSHARE_ARGV0 "debug-net-usershare"
+#else
+# define NET_USERSHARE_ARGV0 "net"
+#endif
+
+static GHashTable *path_share_info_hash;
+static GHashTable *share_name_share_info_hash;
+
+#define NUM_CALLS_BETWEEN_TIMESTAMP_UPDATES 100
+#define TIMESTAMP_THRESHOLD 10 /* seconds */
+static int refresh_timestamp_update_counter;
+static time_t refresh_timestamp;
+
+#define KEY_PATH "path"
+#define KEY_COMMENT "comment"
+#define KEY_ACL "usershare_acl"
+#define KEY_GUEST_OK "guest_ok"
+#define GROUP_ALLOW_GUESTS "global"
+#define KEY_ALLOW_GUESTS "usershare allow guests"
+
+/* Debugging flags */
+static gboolean throw_error_on_refresh;
+static gboolean throw_error_on_add;
+static gboolean throw_error_on_modify;
+static gboolean throw_error_on_remove;
+
+
+
+/* Interface to "net usershare" */
+
+static gboolean
+net_usershare_run (int argc, char **argv, GKeyFile **ret_key_file, GError **error)
+{
+ int real_argc;
+ int i;
+ char **real_argv;
+ gboolean retval;
+ char *stdout_contents;
+ char *stderr_contents;
+ int exit_status;
+ int exit_code;
+ GKeyFile *key_file;
+ GError *real_error;
+
+ g_assert (argc > 0);
+ g_assert (argv != NULL);
+ g_assert (error == NULL || *error == NULL);
+
+ if (ret_key_file)
+ *ret_key_file = NULL;
+
+ /* Build command line */
+
+ real_argc = 2 + argc + 1; /* "net" "usershare" [argv] NULL */
+ real_argv = g_new (char *, real_argc);
+
+ real_argv[0] = NET_USERSHARE_ARGV0;
+ real_argv[1] = "usershare";
+
+ for (i = 0; i < argc; i++) {
+ g_assert (argv[i] != NULL);
+ real_argv[i + 2] = argv[i];
+ }
+
+ real_argv[real_argc - 1] = NULL;
+
+ /* Launch */
+
+ stdout_contents = NULL;
+ stderr_contents = NULL;
+ /*
+ {
+ char **p;
+
+ g_message ("------------------------------------------");
+
+ for (p = real_argv; *p; p++)
+ g_message ("spawn arg \"%s\"", *p);
+
+ g_message ("end of spawn args; SPAWNING\n");
+ }
+ */
+ real_error = NULL;
+ retval = g_spawn_sync (NULL, /* cwd */
+ real_argv,
+ NULL, /* envp */
+ G_SPAWN_SEARCH_PATH,
+ NULL, /* GSpawnChildSetupFunc */
+ NULL, /* user_data */
+ &stdout_contents,
+ &stderr_contents,
+ &exit_status,
+ &real_error);
+
+ /* g_message ("returned from spawn: %s: %s", retval ? "SUCCESS" : "FAIL", retval ? "" : real_error->message); */
+
+ if (!retval) {
+ g_propagate_error (error, real_error);
+ goto out;
+ }
+
+ if (!WIFEXITED (exit_status)) {
+ g_message ("WIFEXITED(%d) was false!", exit_status);
+ retval = FALSE;
+
+ if (WIFSIGNALED (exit_status)) {
+ int signal_num;
+
+ signal_num = WTERMSIG (exit_status);
+ g_message ("Child got signal %d", signal_num);
+
+ g_set_error (error,
+ SHARES_ERROR,
+ SHARES_ERROR_FAILED,
+ _("%s %s %s returned with signal %d"),
+ real_argv[0],
+ real_argv[1],
+ real_argv[2],
+ signal_num);
+ } else
+ g_set_error (error,
+ SHARES_ERROR,
+ SHARES_ERROR_FAILED,
+ _("%s %s %s failed for an unknown reason"),
+ real_argv[0],
+ real_argv[1],
+ real_argv[2]);
+
+ goto out;
+ }
+
+ exit_code = WEXITSTATUS (exit_status);
+
+ /* g_message ("exit code %d", exit_code); */
+ if (exit_code != 0) {
+ char *str;
+ char *message;
+
+ /* stderr_contents is in the system locale encoding, not UTF-8 */
+
+ str = g_locale_to_utf8 (stderr_contents, -1, NULL, NULL, NULL);
+
+ if (str && str[0])
+ message = g_strdup_printf (_("'net usershare' returned error %d: %s"), exit_code, str);
+ else
+ message = g_strdup_printf (_("'net usershare' returned error %d"), exit_code);
+
+ g_free (str);
+
+ g_set_error (error,
+ G_SPAWN_ERROR,
+ G_SPAWN_ERROR_FAILED,
+ "%s",
+ message);
+
+ g_free (message);
+
+ retval = FALSE;
+ goto out;
+ }
+
+ if (ret_key_file) {
+ /* g_message ("caller wants GKeyFile"); */
+
+ *ret_key_file = NULL;
+
+ /* FIXME: [email protected] says the output of "net usershare" is nearly always
+ * in UTF-8, but that it can be configured in the master smb.conf. We assume
+ * UTF-8 for now.
+ */
+
+ if (!g_utf8_validate (stdout_contents, -1, NULL)) {
+ g_message ("stdout of net usershare was not in valid UTF-8");
+ g_set_error (error,
+ G_SPAWN_ERROR,
+ G_SPAWN_ERROR_FAILED,
+ _("the output of 'net usershare' is not in valid UTF-8 encoding"));
+ retval = FALSE;
+ goto out;
+ }
+
+ key_file = g_key_file_new ();
+
+ real_error = NULL;
+ if (!g_key_file_load_from_data (key_file, stdout_contents, -1, 0, &real_error)) {
+ g_message ("Error when parsing key file {\n%s\n}: %s", stdout_contents, real_error->message);
+ g_propagate_error (error, real_error);
+ g_key_file_free (key_file);
+ retval = FALSE;
+ goto out;
+ }
+
+ retval = TRUE;
+ *ret_key_file = key_file;
+ } else
+ retval = TRUE;
+
+ /* g_message ("success from calling net usershare and parsing its output"); */
+
+ out:
+ g_free (real_argv);
+ g_free (stdout_contents);
+ g_free (stderr_contents);
+
+ /* g_message ("------------------------------------------"); */
+
+ return retval;
+}
+
+
+
+/* Internals */
+
+static void
+ensure_hashes (void)
+{
+ if (path_share_info_hash == NULL) {
+ g_assert (share_name_share_info_hash == NULL);
+
+ path_share_info_hash = g_hash_table_new (g_str_hash, g_str_equal);
+ share_name_share_info_hash = g_hash_table_new (g_str_hash, g_str_equal);
+ } else
+ g_assert (share_name_share_info_hash != NULL);
+}
+
+static ShareInfo *
+lookup_share_by_path (const char *path)
+{
+ ensure_hashes ();
+ return g_hash_table_lookup (path_share_info_hash, path);
+}
+
+static ShareInfo *
+lookup_share_by_share_name (const char *share_name)
+{
+ ensure_hashes ();
+ return g_hash_table_lookup (share_name_share_info_hash, share_name);
+}
+
+static void
+add_share_info_to_hashes (ShareInfo *info)
+{
+ ensure_hashes ();
+ g_hash_table_insert (path_share_info_hash, info->path, info);
+ g_hash_table_insert (share_name_share_info_hash, info->share_name, info);
+}
+
+static void
+remove_share_info_from_hashes (ShareInfo *info)
+{
+ ensure_hashes ();
+ g_hash_table_remove (path_share_info_hash, info->path);
+ g_hash_table_remove (share_name_share_info_hash, info->share_name);
+}
+
+static gboolean
+remove_from_path_hash_cb (gpointer key,
+ gpointer value,
+ gpointer data)
+{
+ ShareInfo *info;
+
+ info = value;
+ shares_free_share_info (info);
+
+ return TRUE;
+}
+
+static gboolean
+remove_from_share_name_hash_cb (gpointer key,
+ gpointer value,
+ gpointer data)
+{
+ /* The ShareInfo was already freed in remove_from_path_hash_cb() */
+ return TRUE;
+}
+
+static void
+free_all_shares (void)
+{
+ ensure_hashes ();
+ g_hash_table_foreach_remove (path_share_info_hash, remove_from_path_hash_cb, NULL);
+ g_hash_table_foreach_remove (share_name_share_info_hash, remove_from_share_name_hash_cb, NULL);
+}
+
+static char *
+get_string_from_key_file (GKeyFile *key_file, const char *group, const char *key)
+{
+ GError *error;
+ char *str;
+
+ error = NULL;
+ str = NULL;
+
+ if (g_key_file_has_key (key_file, group, key, &error)) {
+ str = g_key_file_get_string (key_file, group, key, &error);
+ if (!str) {
+ g_assert (!g_error_matches (error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_NOT_FOUND)
+ && !g_error_matches (error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_GROUP_NOT_FOUND));
+
+ g_error_free (error);
+ }
+ } else {
+ g_assert (!g_error_matches (error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_GROUP_NOT_FOUND));
+ g_error_free (error);
+ }
+
+ return str;
+}
+
+static void
+add_key_group_to_hashes (GKeyFile *key_file, const char *group)
+{
+ char *path;
+ char *comment;
+ char *acl;
+ gboolean is_writable;
+ char *guest_ok_str;
+ gboolean guest_ok;
+ ShareInfo *info;
+ ShareInfo *old_info;
+
+ /* Remove the old share based on the name */
+
+ old_info = lookup_share_by_share_name (group);
+ if (old_info) {
+ remove_share_info_from_hashes (old_info);
+ shares_free_share_info (old_info);
+ }
+
+ /* Start parsing, and remove the old share based on the path */
+
+ path = get_string_from_key_file (key_file, group, KEY_PATH);
+ if (!path) {
+ g_message ("group '%s' doesn't have a '%s' key! Ignoring group.", group, KEY_PATH);
+ return;
+ }
+
+ old_info = lookup_share_by_path (path);
+ if (old_info) {
+ remove_share_info_from_hashes (old_info);
+ shares_free_share_info (old_info);
+ }
+
+ /* Finish parsing */
+
+ comment = get_string_from_key_file (key_file, group, KEY_COMMENT);
+
+ acl = get_string_from_key_file (key_file, group, KEY_ACL);
+ if (acl) {
+ if (strstr (acl, "Everyone:R"))
+ is_writable = FALSE;
+ else if (strstr (acl, "Everyone:F"))
+ is_writable = TRUE;
+ else {
+ g_message ("unknown format for key '%s/%s' as it contains '%s'. Assuming that the share is read-only",
+ group, KEY_ACL, acl);
+ is_writable = FALSE;
+ }
+
+ g_free (acl);
+ } else {
+ g_message ("group '%s' doesn't have a '%s' key! Assuming that the share is read-only.", group, KEY_ACL);
+ is_writable = FALSE;
+ }
+
+ guest_ok_str = get_string_from_key_file (key_file, group, KEY_GUEST_OK);
+ if (guest_ok_str) {
+ if (strcmp (guest_ok_str, "n") == 0)
+ guest_ok = FALSE;
+ else if (strcmp (guest_ok_str, "y") == 0)
+ guest_ok = TRUE;
+ else {
+ g_message ("unknown format for key '%s/%s' as it contains '%s'. Assuming that the share is not guest accessible.",
+ group, KEY_GUEST_OK, guest_ok_str);
+ guest_ok = FALSE;
+ }
+
+ g_free (guest_ok_str);
+ } else {
+ g_message ("group '%s' doesn't have a '%s' key! Assuming that the share is not guest accessible.", group, KEY_GUEST_OK);
+ guest_ok = FALSE;
+ }
+
+ g_assert (path != NULL);
+ g_assert (group != NULL);
+
+ info = g_new (ShareInfo, 1);
+ info->path = path;
+ info->share_name = g_strdup (group);
+ info->comment = comment;
+ info->is_writable = is_writable;
+ info->guest_ok = guest_ok;
+
+ add_share_info_to_hashes (info);
+}
+
+static void
+replace_shares_from_key_file (GKeyFile *key_file)
+{
+ gsize num_groups;
+ char **group_names;
+ gsize i;
+
+ group_names = g_key_file_get_groups (key_file, &num_groups);
+
+ /* FIXME: In add_key_group_to_hashes(), we simply ignore key groups
+ * which have invalid data (i.e. no path). We could probably accumulate a
+ * GError with the list of invalid groups and propagate it upwards.
+ */
+ for (i = 0; i < num_groups; i++) {
+ g_assert (group_names[i] != NULL);
+ add_key_group_to_hashes (key_file, group_names[i]);
+ }
+
+ g_strfreev (group_names);
+}
+
+static gboolean
+refresh_shares (GError **error)
+{
+ GKeyFile *key_file;
+ char *argv[1];
+ GError *real_error;
+
+ free_all_shares ();
+
+ if (throw_error_on_refresh) {
+ g_set_error (error,
+ SHARES_ERROR,
+ SHARES_ERROR_FAILED,
+ _("Failed"));
+ return FALSE;
+ }
+
+ argv[0] = "info";
+
+ real_error = NULL;
+ if (!net_usershare_run (G_N_ELEMENTS (argv), argv, &key_file, &real_error)) {
+ g_message ("Called \"net usershare info\" but it failed: %s", real_error->message);
+ g_propagate_error (error, real_error);
+ return FALSE;
+ }
+
+ g_assert (key_file != NULL);
+
+ replace_shares_from_key_file (key_file);
+ g_key_file_free (key_file);
+
+ return TRUE;
+}
+
+static gboolean
+refresh_if_needed (GError **error)
+{
+ gboolean retval;
+
+ if (refresh_timestamp_update_counter == 0) {
+ time_t new_timestamp;
+
+ refresh_timestamp_update_counter = NUM_CALLS_BETWEEN_TIMESTAMP_UPDATES;
+
+ new_timestamp = time (NULL);
+ if (new_timestamp - refresh_timestamp > TIMESTAMP_THRESHOLD) {
+ /* g_message ("REFRESHING SHARES"); */
+ retval = refresh_shares (error);
+ } else
+ retval = TRUE;
+
+ refresh_timestamp = new_timestamp;
+ } else {
+ refresh_timestamp_update_counter--;
+ retval = TRUE;
+ }
+
+ return retval;
+}
+
+static ShareInfo *
+copy_share_info (ShareInfo *info)
+{
+ ShareInfo *copy;
+
+ if (!info)
+ return NULL;
+
+ copy = g_new (ShareInfo, 1);
+ copy->path = g_strdup (info->path);
+ copy->share_name = g_strdup (info->share_name);
+ copy->comment = g_strdup (info->comment);
+ copy->is_writable = info->is_writable;
+ copy->guest_ok = info->guest_ok;
+
+ return copy;
+}
+
+/**
+ * shares_supports_guest_ok:
+ * @supports_guest_ok_ret: Location to store whether "usershare allow guests"
+ * is enabled.
+ * @error: Location to store error, or #NULL.
+ *
+ * Determines whether the option "usershare allow guests" is enabled in samba
+ * config as shown by testparm.
+ *
+ * Return value: #TRUE if if the info could be queried successfully, #FALSE
+ * otherwise. If this function returns #FALSE, an error code will be returned
+ * in the @error argument, and *@ret_info_list will be set to #FALSE.
+ **/
+gboolean
+shares_supports_guest_ok (gboolean *supports_guest_ok_ret, GError **error)
+{
+ gboolean retval;
+ gboolean result;
+ char *stdout_contents;
+ char *stderr_contents;
+ int exit_status;
+ int exit_code;
+
+ *supports_guest_ok_ret = FALSE;
+
+ result = g_spawn_command_line_sync ("testparm -s --parameter-name='usershare allow guests'",
+ &stdout_contents,
+ &stderr_contents,
+ &exit_status,
+ error);
+ if (!result)
+ return FALSE;
+
+ retval = FALSE;
+
+ if (!WIFEXITED (exit_status)) {
+ if (WIFSIGNALED (exit_status)) {
+ int signal_num;
+
+ signal_num = WTERMSIG (exit_status);
+ g_set_error (error,
+ SHARES_ERROR,
+ SHARES_ERROR_FAILED,
+ _("Samba's testparm returned with signal %d"),
+ signal_num);
+ } else
+ g_set_error (error,
+ SHARES_ERROR,
+ SHARES_ERROR_FAILED,
+ _("Samba's testparm failed for an unknown reason"));
+
+ goto out;
+ }
+
+ exit_code = WEXITSTATUS (exit_status);
+ if (exit_code != 0) {
+ char *str;
+ char *message;
+
+ /* stderr_contents is in the system locale encoding, not UTF-8 */
+
+ str = g_locale_to_utf8 (stderr_contents, -1, NULL, NULL, NULL);
+
+ if (str && str[0])
+ message = g_strdup_printf (_("Samba's testparm returned error %d: %s"), exit_code, str);
+ else
+ message = g_strdup_printf (_("Samba's testparm returned error %d"), exit_code);
+
+ g_free (str);
+
+ g_set_error (error,
+ G_SPAWN_ERROR,
+ G_SPAWN_ERROR_FAILED,
+ "%s",
+ message);
+
+ g_free (message);
+
+ goto out;
+ }
+
+ retval = TRUE;
+ *supports_guest_ok_ret = (g_ascii_strncasecmp (stdout_contents, "Yes", 3) == 0);
+
+ out:
+ g_free (stdout_contents);
+ g_free (stderr_contents);
+
+ return retval;
+}
+
+static gboolean
+add_share (ShareInfo *info, GError **error)
+{
+ char *argv[7];
+ int argc;
+ ShareInfo *copy;
+ GKeyFile *key_file;
+ GError *real_error;
+ gboolean supports_success;
+ gboolean supports_guest_ok;
+ gboolean net_usershare_success;
+
+ /* g_message ("add_share() start"); */
+
+ if (throw_error_on_add) {
+ g_set_error (error,
+ SHARES_ERROR,
+ SHARES_ERROR_FAILED,
+ _("Failed"));
+ g_message ("add_share() end FAIL");
+ return FALSE;
+ }
+
+ supports_success = shares_supports_guest_ok (&supports_guest_ok, error);
+ if (!supports_success)
+ return FALSE;
+
+ argv[0] = "add";
+ argv[1] = "-l";
+ argv[2] = info->share_name;
+ argv[3] = info->path;
+ argv[4] = info->comment;
+ argv[5] = info->is_writable ? "Everyone:F" : g_strdup_printf ("Everyone:R,%s:F", g_get_user_name ());
+
+ if (supports_guest_ok) {
+ argv[6] = info->guest_ok ? "guest_ok=y" : "guest_ok=n";
+ argc = 7;
+ } else
+ argc = 6;
+
+ real_error = NULL;
+ net_usershare_success = net_usershare_run (argc, argv, &key_file, &real_error);
+ if (!info->is_writable) g_free (argv[5]);
+
+ if (!net_usershare_success) {
+ g_message ("Called \"net usershare add\" but it failed: %s", real_error->message);
+ g_propagate_error (error, real_error);
+ return FALSE;
+ }
+
+ replace_shares_from_key_file (key_file);
+
+ copy = copy_share_info (info);
+ add_share_info_to_hashes (copy);
+
+ /* g_message ("add_share() end SUCCESS"); */
+
+ return TRUE;
+}
+
+static gboolean
+remove_share (const char *path, GError **error)
+{
+ ShareInfo *old_info;
+ char *argv[2];
+ GError *real_error;
+
+ /* g_message ("remove_share() start"); */
+
+ if (throw_error_on_remove) {
+ g_set_error (error,
+ SHARES_ERROR,
+ SHARES_ERROR_FAILED,
+ "Failed");
+ g_message ("remove_share() end FAIL");
+ return FALSE;
+ }
+
+ old_info = lookup_share_by_path (path);
+ if (!old_info) {
+ char *display_name;
+
+ display_name = g_filename_display_name (path);
+ g_set_error (error,
+ SHARES_ERROR,
+ SHARES_ERROR_NONEXISTENT,
+ _("Cannot remove the share for path %s: that path is not shared"),
+ display_name);
+ g_free (display_name);
+
+ g_message ("remove_share() end FAIL: path %s was not in our hashes", path);
+ return FALSE;
+ }
+
+ argv[0] = "delete";
+ argv[1] = old_info->share_name;
+
+ real_error = NULL;
+ if (!net_usershare_run (G_N_ELEMENTS (argv), argv, NULL, &real_error)) {
+ g_message ("Called \"net usershare delete\" but it failed: %s", real_error->message);
+ g_propagate_error (error, real_error);
+ g_message ("remove_share() end FAIL");
+ return FALSE;
+ }
+
+ remove_share_info_from_hashes (old_info);
+ shares_free_share_info (old_info);
+
+ /* g_message ("remove_share() end SUCCESS"); */
+
+ return TRUE;
+}
+
+static gboolean
+modify_share (const char *old_path, ShareInfo *info, GError **error)
+{
+ ShareInfo *old_info;
+
+ /* g_message ("modify_share() start"); */
+
+ old_info = lookup_share_by_path (old_path);
+ if (old_info == NULL) {
+ /*g_message ("modify_share() end; calling add_share() instead");*/
+ return add_share (info, error);
+ }
+
+ g_assert (old_info != NULL);
+
+ if (strcmp (info->path, old_info->path) != 0) {
+ g_set_error (error,
+ SHARES_ERROR,
+ SHARES_ERROR_FAILED,
+ _("Cannot change the path of an existing share; please remove the old share first and add a new one"));
+ g_message ("modify_share() end FAIL: tried to change the path in a share!");
+ return FALSE;
+ }
+
+ if (throw_error_on_modify) {
+ g_set_error (error,
+ SHARES_ERROR,
+ SHARES_ERROR_FAILED,
+ "Failed");
+ g_message ("modify_share() end FAIL");
+ return FALSE;
+ }
+
+ /* Although "net usershare add" will modify an existing share if it has the same share name
+ * as the one that gets passed in, our semantics are different. We have a one-to-one mapping
+ * between paths and share names; "net usershare" supports a one-to-many mapping from paths
+ * to share names. So, we must first remove the old share and then add the new/modified one.
+ */
+
+ if (!remove_share (old_path, error)) {
+ g_message ("modify_share() end FAIL: error when removing old share");
+ return FALSE;
+ }
+
+ /* g_message ("modify_share() end: will call add_share() with the new share info"); */
+ return add_share (info, error);
+}
+
+
+
+/* Public API */
+
+GQuark
+shares_error_quark (void)
+{
+ static GQuark quark;
+
+ if (quark == 0)
+ quark = g_quark_from_string ("mate-file-manager-shares-error-quark"); /* not from_static_string since we are a module */
+
+ return quark;
+}
+
+/**
+ * shares_free_share_info:
+ * @info: A #ShareInfo structure.
+ *
+ * Frees a #ShareInfo structure.
+ **/
+void
+shares_free_share_info (ShareInfo *info)
+{
+ g_assert (info != NULL);
+
+ g_free (info->path);
+ g_free (info->share_name);
+ g_free (info->comment);
+ g_free (info);
+}
+
+/**
+ * shares_get_path_is_shared:
+ * @path: A full path name ("/foo/bar/baz") in file system encoding.
+ * @ret_is_shared: Location to store result value (#TRUE if the path is shared, #FALSE otherwise)
+ * @error: Location to store error, or #NULL.
+ *
+ * Checks whether a path is shared through Samba.
+ *
+ * Return value: #TRUE if the info could be queried successfully, #FALSE
+ * otherwise. If this function returns #FALSE, an error code will be returned in the
+ * @error argument, and *@ret_is_shared will be set to #FALSE.
+ **/
+gboolean
+shares_get_path_is_shared (const char *path, gboolean *ret_is_shared, GError **error)
+{
+ g_assert (ret_is_shared != NULL);
+ g_assert (error == NULL || *error == NULL);
+
+ if (!refresh_if_needed (error)) {
+ *ret_is_shared = FALSE;
+ return FALSE;
+ }
+
+ *ret_is_shared = (lookup_share_by_path (path) != NULL);
+
+ return TRUE;
+}
+
+/**
+ * shares_get_share_info_for_path:
+ * @path: A full path name ("/foo/bar/baz") in file system encoding.
+ * @ret_share_info: Location to store result with the share's info - on return,
+ * will be non-NULL if the path is indeed shared, or #NULL if the path is not
+ * shared. You must free the non-NULL value with shares_free_share_info().
+ * @error: Location to store error, or #NULL.
+ *
+ * Queries the information for a shared path: its share name, its read-only status, etc.
+ *
+ * Return value: #TRUE if the info could be queried successfully, #FALSE
+ * otherwise. If this function returns #FALSE, an error code will be returned in the
+ * @error argument, and *@ret_share_info will be set to #NULL.
+ **/
+gboolean
+shares_get_share_info_for_path (const char *path, ShareInfo **ret_share_info, GError **error)
+{
+ ShareInfo *info;
+
+ g_assert (path != NULL);
+ g_assert (ret_share_info != NULL);
+ g_assert (error == NULL || *error == NULL);
+
+ if (!refresh_if_needed (error)) {
+ *ret_share_info = NULL;
+ return FALSE;
+ }
+
+ info = lookup_share_by_path (path);
+ *ret_share_info = copy_share_info (info);
+
+ return TRUE;
+}
+
+/**
+ * shares_get_share_name_exists:
+ * @share_name: Name of a share.
+ * @ret_exists: Location to store return value; #TRUE if the share name exists, #FALSE otherwise.
+ *
+ * Queries whether a share name already exists in the user's list of shares.
+ *
+ * Return value: #TRUE if the info could be queried successfully, #FALSE
+ * otherwise. If this function returns #FALSE, an error code will be returned in the
+ * @error argument, and *@ret_exists will be set to #FALSE.
+ **/
+gboolean
+shares_get_share_name_exists (const char *share_name, gboolean *ret_exists, GError **error)
+{
+ g_assert (share_name != NULL);
+ g_assert (ret_exists != NULL);
+ g_assert (error == NULL || *error == NULL);
+
+ if (!refresh_if_needed (error)) {
+ *ret_exists = FALSE;
+ return FALSE;
+ }
+
+ *ret_exists = (lookup_share_by_share_name (share_name) != NULL);
+
+ return TRUE;
+}
+
+/**
+ * shares_get_share_info_for_share_name:
+ * @share_name: Name of a share.
+ * @ret_share_info: Location to store result with the share's info - on return,
+ * will be non-NULL if there is a share for the specified name, or #NULL if no
+ * share has such name. You must free the non-NULL value with
+ * shares_free_share_info().
+ * @error: Location to store error, or #NULL.
+ *
+ * Queries the information for the share which has a specific name.
+ *
+ * Return value: #TRUE if the info could be queried successfully, #FALSE
+ * otherwise. If this function returns #FALSE, an error code will be returned in the
+ * @error argument, and *@ret_share_info will be set to #NULL.
+ **/
+gboolean
+shares_get_share_info_for_share_name (const char *share_name, ShareInfo **ret_share_info, GError **error)
+{
+ ShareInfo *info;
+
+ g_assert (share_name != NULL);
+ g_assert (ret_share_info != NULL);
+ g_assert (error == NULL || *error == NULL);
+
+ if (!refresh_if_needed (error)) {
+ *ret_share_info = NULL;
+ return FALSE;
+ }
+
+ info = lookup_share_by_share_name (share_name);
+ *ret_share_info = copy_share_info (info);
+
+ return TRUE;
+}
+
+/**
+ * shares_modify_share:
+ * @old_path: Path of the share to modify, or %NULL.
+ * @info: Info of the share to modify/add, or %NULL to delete a share.
+ * @error: Location to store error, or #NULL.
+ *
+ * Can add, modify, or delete shares. To add a share, pass %NULL for @old_path,
+ * and a non-null @info. To modify a share, pass a non-null @old_path and
+ * non-null @info; in this case, @info->path must have the same contents as
+ * @old_path. To remove a share, pass a non-NULL @old_path and a %NULL @info.
+ *
+ * Return value: TRUE if the share could be modified, FALSE otherwise. If this returns
+ * FALSE, then the error information will be placed in @error.
+ **/
+gboolean
+shares_modify_share (const char *old_path, ShareInfo *info, GError **error)
+{
+ g_assert ((old_path == NULL && info != NULL)
+ || (old_path != NULL && info == NULL)
+ || (old_path != NULL && info != NULL));
+ g_assert (error == NULL || *error == NULL);
+
+ if (!refresh_if_needed (error))
+ return FALSE;
+
+ if (old_path == NULL)
+ return add_share (info, error);
+ else if (info == NULL)
+ return remove_share (old_path, error);
+ else
+ return modify_share (old_path, info, error);
+}
+
+static void
+copy_to_slist_cb (gpointer key, gpointer value, gpointer data)
+{
+ ShareInfo *info;
+ ShareInfo *copy;
+ GSList **list;
+
+ info = value;
+ list = data;
+
+ copy = copy_share_info (info);
+ *list = g_slist_prepend (*list, copy);
+}
+
+/**
+ * shares_get_share_info_list:
+ * @ret_info_list: Location to store the return value, which is a list
+ * of #ShareInfo structures. Free this with shares_free_share_info_list().
+ * @error: Location to store error, or #NULL.
+ *
+ * Gets the list of shared folders and their information.
+ *
+ * Return value: #TRUE if the info could be queried successfully, #FALSE
+ * otherwise. If this function returns #FALSE, an error code will be returned in the
+ * @error argument, and *@ret_info_list will be set to #NULL.
+ **/
+gboolean
+shares_get_share_info_list (GSList **ret_info_list, GError **error)
+{
+ g_assert (ret_info_list != NULL);
+ g_assert (error == NULL || *error == NULL);
+
+ if (!refresh_if_needed (error)) {
+ *ret_info_list = NULL;
+ return FALSE;
+ }
+
+ *ret_info_list = NULL;
+ g_hash_table_foreach (path_share_info_hash, copy_to_slist_cb, ret_info_list);
+
+ return TRUE;
+}
+
+/**
+ * shares_free_share_info_list:
+ * @list: List of #ShareInfo structures, or %NULL.
+ *
+ * Frees a list of #ShareInfo structures as returned by shares_get_share_info_list().
+ **/
+void
+shares_free_share_info_list (GSList *list)
+{
+ GSList *l;
+
+ for (l = list; l; l = l->next) {
+ ShareInfo *info;
+
+ info = l->data;
+ shares_free_share_info (l->data);
+ }
+
+ g_slist_free (list);
+}
+
+void
+shares_set_debug (gboolean error_on_refresh,
+ gboolean error_on_add,
+ gboolean error_on_modify,
+ gboolean error_on_remove)
+{
+ throw_error_on_refresh = error_on_refresh;
+ throw_error_on_add = error_on_add;
+ throw_error_on_modify = error_on_modify;
+ throw_error_on_remove = error_on_remove;
+}
diff --git a/share/shares.h b/share/shares.h
new file mode 100644
index 0000000..beec95f
--- /dev/null
+++ b/share/shares.h
@@ -0,0 +1,47 @@
+#ifndef SHARES_H
+#define SHARES_H
+
+#include <glib.h>
+
+typedef struct {
+ char *path;
+ char *share_name;
+ char *comment;
+ gboolean is_writable;
+ gboolean guest_ok;
+} ShareInfo;
+
+#define SHARES_ERROR (shares_error_quark ())
+
+typedef enum {
+ SHARES_ERROR_FAILED,
+ SHARES_ERROR_NONEXISTENT
+} SharesError;
+
+GQuark shares_error_quark (void);
+
+void shares_free_share_info (ShareInfo *info);
+
+gboolean shares_get_path_is_shared (const char *path, gboolean *ret_is_shared, GError **error);
+
+gboolean shares_get_share_info_for_path (const char *path, ShareInfo **ret_share_info, GError **error);
+
+gboolean shares_get_share_name_exists (const char *share_name, gboolean *ret_exists, GError **error);
+
+gboolean shares_get_share_info_for_share_name (const char *share_name, ShareInfo **ret_share_info, GError **error);
+
+gboolean shares_modify_share (const char *old_path, ShareInfo *info, GError **error);
+
+gboolean shares_get_share_info_list (GSList **ret_info_list, GError **error);
+
+void shares_free_share_info_list (GSList *list);
+
+gboolean shares_supports_guest_ok (gboolean *supports_guest_ok_ret,
+ GError **error);
+
+void shares_set_debug (gboolean error_on_refresh,
+ gboolean error_on_add,
+ gboolean error_on_modify,
+ gboolean error_on_remove);
+
+#endif