From 41924f8c75565038db2f52860c52650cd755ebba Mon Sep 17 00:00:00 2001 From: Marek Kasik Date: Thu, 14 Feb 2013 15:54:20 +0100 Subject: [PATCH] printers: Add class for searching for samba printers Add class PpSamba which contains asynchronous function for searching for samba-shared printers. It can search for printers on local network or on a specific host if needed. https://bugzilla.gnome.org/show_bug.cgi?id=683229 --- configure.ac | 3 +- panels/printers/Makefile.am | 2 + panels/printers/pp-host.h | 4 - panels/printers/pp-samba.c | 507 ++++++++++++++++++++++++++++++++++++ panels/printers/pp-samba.h | 67 +++++ panels/printers/pp-utils.h | 9 +- 6 files changed, 586 insertions(+), 6 deletions(-) create mode 100644 panels/printers/pp-samba.c create mode 100644 panels/printers/pp-samba.h diff --git a/configure.ac b/configure.ac index a3d3fc7e5..dfe82d239 100644 --- a/configure.ac +++ b/configure.ac @@ -152,7 +152,8 @@ PKG_CHECK_MODULES(COLOR_PANEL, $COMMON_MODULES colord-gtk >= $COLORD_GTK_REQUIRED_VERSION gnome-desktop-3.0 >= $GNOME_DESKTOP_REQUIRED_VERSION) PKG_CHECK_MODULES(PRINTERS_PANEL, $COMMON_MODULES - polkit-gobject-1 >= $POLKIT_REQUIRED_VERSION) + polkit-gobject-1 >= $POLKIT_REQUIRED_VERSION + smbclient) PKG_CHECK_MODULES(PRIVACY_PANEL, $COMMON_MODULES) PKG_CHECK_MODULES(REGION_PANEL, $COMMON_MODULES polkit-gobject-1 >= $POLKIT_REQUIRED_VERSION diff --git a/panels/printers/Makefile.am b/panels/printers/Makefile.am index b276546ef..6b05acceb 100644 --- a/panels/printers/Makefile.am +++ b/panels/printers/Makefile.am @@ -39,6 +39,8 @@ libprinters_la_SOURCES = \ pp-jobs-dialog.h \ pp-authentication-dialog.c \ pp-authentication-dialog.h \ + pp-samba.c \ + pp-samba.h \ cc-printers-panel.c \ cc-printers-panel.h diff --git a/panels/printers/pp-host.h b/panels/printers/pp-host.h index b3c2b5b35..f5bfb7e59 100644 --- a/panels/printers/pp-host.h +++ b/panels/printers/pp-host.h @@ -35,10 +35,6 @@ G_BEGIN_DECLS #define PP_IS_HOST_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), PP_TYPE_HOST)) #define PP_HOST_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), PP_TYPE_HOST, PpHostClass)) -typedef struct{ - GList *devices; -} PpDevicesList; - typedef struct _PpHost PpHost; typedef struct _PpHostClass PpHostClass; typedef struct _PpHostPrivate PpHostPrivate; diff --git a/panels/printers/pp-samba.c b/panels/printers/pp-samba.c new file mode 100644 index 000000000..299c551ae --- /dev/null +++ b/panels/printers/pp-samba.c @@ -0,0 +1,507 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright 2012 - 2013 Red Hat, Inc, + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * Author: Marek Kasik + */ + +#include "pp-samba.h" + +#include "config.h" + +#include +#include +#include + +#include "pp-authentication-dialog.h" + +#define POLL_DELAY 100000 + +struct _PpSambaPrivate +{ + GtkWindow *parent; + + gchar *hostname; +}; + +G_DEFINE_TYPE (PpSamba, pp_samba, G_TYPE_OBJECT); + +enum +{ + PROP_0 = 0, + PROP_HOSTNAME, + PROP_PARENT, +}; + +static void +pp_samba_finalize (GObject *object) +{ + PpSambaPrivate *priv; + + priv = PP_SAMBA (object)->priv; + + g_free (priv->hostname); + g_object_unref (priv->parent); + + G_OBJECT_CLASS (pp_samba_parent_class)->finalize (object); +} + +static void +pp_samba_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *param_spec) +{ + PpSamba *self = PP_SAMBA (object); + + switch (prop_id) + { + case PROP_HOSTNAME: + g_value_set_string (value, self->priv->hostname); + break; + case PROP_PARENT: + g_value_set_pointer (value, self->priv->parent); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, + prop_id, + param_spec); + break; + } +} + +static void +pp_samba_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *param_spec) +{ + PpSamba *self = PP_SAMBA (object); + + switch (prop_id) + { + case PROP_HOSTNAME: + g_free (self->priv->hostname); + self->priv->hostname = g_value_dup_string (value); + break; + case PROP_PARENT: + if (self->priv->parent) + g_object_unref (self->priv->parent); + self->priv->parent = g_object_ref (G_OBJECT (g_value_get_pointer (value))); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, + prop_id, + param_spec); + break; + } +} + +static void +pp_samba_class_init (PpSambaClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + g_type_class_add_private (klass, sizeof (PpSambaPrivate)); + + gobject_class->set_property = pp_samba_set_property; + gobject_class->get_property = pp_samba_get_property; + gobject_class->finalize = pp_samba_finalize; + + /* + * Used for searching on specific host. + */ + g_object_class_install_property (gobject_class, PROP_HOSTNAME, + g_param_spec_string ("hostname", + "Hostname", + "The hostname to search", + NULL, + G_PARAM_READWRITE)); + + /* + * Used for authentication dialog. + */ + g_object_class_install_property (gobject_class, PROP_PARENT, + g_param_spec_pointer ("parent", + "Parent", + "Parent window", + G_PARAM_READWRITE)); +} + +static void +pp_samba_init (PpSamba *samba) +{ + samba->priv = G_TYPE_INSTANCE_GET_PRIVATE (samba, + PP_TYPE_SAMBA, + PpSambaPrivate); +} + +PpSamba * +pp_samba_new (GtkWindow *parent, + const gchar *hostname) +{ + return g_object_new (PP_TYPE_SAMBA, + "parent", parent, + "hostname", hostname, + NULL); +} + +typedef struct +{ + gchar *server; + gchar *share; + gchar *workgroup; + gchar *username; + gchar *password; +} SMBAuthInfo; + +static void +smb_auth_info_free (SMBAuthInfo *auth_info) +{ + if (auth_info) + { + g_free (auth_info->server); + g_free (auth_info->share); + g_free (auth_info->workgroup); + g_free (auth_info->username); + g_free (auth_info->password); + g_free (auth_info); + } +} + +typedef struct +{ + PpDevicesList *devices; + GMainContext *context; + gboolean waiting; + GtkWindow *parent; + SMBAuthInfo *auth_info; + gboolean hostname_set; +} SMBData; + +static void +smb_data_free (SMBData *data) +{ + if (data) + { + if (data->devices) + { + g_list_free_full (data->devices->devices, (GDestroyNotify) pp_print_device_free); + g_free (data->devices); + } + + smb_auth_info_free (data->auth_info); + g_object_unref (data->parent); + + g_free (data); + } +} + +static void +auth_cb (PpAuthenticationDialog *auth_dialog, + const gchar *username, + const gchar *password, + gpointer user_data) +{ + SMBData *data = (SMBData *) user_data; + + if (username && username[0] != '\0') + { + g_free (data->auth_info->username); + data->auth_info->username = g_strdup (username); + } + + if (password && password[0] != '\0') + { + g_free (data->auth_info->password); + data->auth_info->password = g_strdup (password); + } + + g_object_unref (auth_dialog); + + data->waiting = FALSE; +} + +static gboolean +get_auth_info (gpointer user_data) +{ + PpAuthenticationDialog *auth_dialog; + SMBData *data = (SMBData *) user_data; + gchar *text; + + /* Translators: Samba server needs authentication of the user to show list of its printers. */ + text = g_strdup_printf (_("Enter your username and password to view printers available on %s."), + data->auth_info->server); + auth_dialog = pp_authentication_dialog_new (data->parent, + text, + data->auth_info->username); + g_signal_connect (auth_dialog, "response", G_CALLBACK (auth_cb), user_data); + + g_free (text); + + return FALSE; +} + +static void +auth_fn (SMBCCTX *smb_context, + const char *server, + const char *share, + char *workgroup, + int wgmaxlen, + char *username, + int unmaxlen, + char *password, + int pwmaxlen) +{ + GSource *source; + SMBData *data; + + data = (SMBData *) smbc_getOptionUserData (smb_context); + + data->auth_info = g_new (SMBAuthInfo, 1); + data->auth_info->server = g_strdup (server); + data->auth_info->share = g_strdup (share); + data->auth_info->workgroup = g_strdup (workgroup); + data->auth_info->username = g_strdup (username); + data->auth_info->password = g_strdup (password); + + data->waiting = TRUE; + + source = g_idle_source_new (); + g_source_set_callback (source, + get_auth_info, + data, + NULL); + g_source_attach (source, data->context); + g_source_unref (source); + + /* + * smbclient needs to get authentication data + * from this synchronous callback so we are blocking + * until we get them + */ + while (data->waiting) + { + g_usleep (POLL_DELAY); + } + + if (g_strcmp0 (username, data->auth_info->username) != 0) + g_strlcpy (username, data->auth_info->username, unmaxlen); + + if (g_strcmp0 (password, data->auth_info->password) != 0) + g_strlcpy (password, data->auth_info->password, pwmaxlen); + + smb_auth_info_free (data->auth_info); + data->auth_info = NULL; +} + +static void +anonymous_auth_fn (SMBCCTX *smb_context, + const char *server, + const char *share, + char *workgroup, + int wgmaxlen, + char *username, + int unmaxlen, + char *password, + int pwmaxlen) +{ + username[0] = '\0'; + password[0] = '\0'; +} + +static void +list_dir (SMBCCTX *smb_context, + const gchar *dirname, + const gchar *path, + GCancellable *cancellable, + SMBData *data) +{ + struct smbc_dirent *dirent; + smbc_closedir_fn smbclient_closedir; + smbc_readdir_fn smbclient_readdir; + smbc_opendir_fn smbclient_opendir; + PpPrintDevice *device; + SMBCFILE *dir; + + if (!g_cancellable_is_cancelled (cancellable)) + { + smbclient_closedir = smbc_getFunctionClosedir (smb_context); + smbclient_readdir = smbc_getFunctionReaddir (smb_context); + smbclient_opendir = smbc_getFunctionOpendir (smb_context); + + dir = smbclient_opendir (smb_context, dirname); + if (!dir) + { + smbc_setFunctionAuthDataWithContext (smb_context, auth_fn); + dir = smbclient_opendir (smb_context, dirname); + smbc_setFunctionAuthDataWithContext (smb_context, anonymous_auth_fn); + } + + while (dir && (dirent = smbclient_readdir (smb_context, dir))) + { + gchar *subdirname = NULL; + gchar *subpath = NULL; + + if (dirent->smbc_type == SMBC_WORKGROUP) + { + subdirname = g_strdup_printf ("%s%s", dirname, dirent->name); + subpath = g_strdup_printf ("%s%s", path, dirent->name); + } + + if (dirent->smbc_type == SMBC_SERVER) + { + subdirname = g_strdup_printf ("smb://%s", dirent->name); + subpath = g_strdup_printf ("%s//%s", path, dirent->name); + } + + if (dirent->smbc_type == SMBC_PRINTER_SHARE) + { + device = g_new0 (PpPrintDevice, 1); + + device->device_uri = g_strdup_printf ("%s/%s", + dirname, + dirent->name); + + device->device_class = g_strdup ("network"); + device->device_info = g_strdup (dirent->comment); + device->device_name = g_strdup (dirent->name); + device->device_name = + g_strcanon (device->device_name, ALLOWED_CHARACTERS, '-'); + if (data->hostname_set) + device->acquisition_method = ACQUISITION_METHOD_SAMBA_HOST; + else + device->acquisition_method = ACQUISITION_METHOD_SAMBA; + device->device_location = g_strdup (path); + device->host_name = g_strdup (dirname); + + data->devices->devices = g_list_append (data->devices->devices, device); + } + + if (subdirname) + { + list_dir (smb_context, + subdirname, + subpath, + cancellable, + data); + g_free (subdirname); + g_free (subpath); + } + } + + if (dir) + smbclient_closedir (smb_context, dir); + } +} + +static void +_pp_samba_get_devices_thread (GSimpleAsyncResult *res, + GObject *object, + GCancellable *cancellable) +{ + PpSambaPrivate *priv; + static GMutex mutex; + PpSamba *samba = PP_SAMBA (object); + SMBData *data; + SMBCCTX *smb_context; + gchar *dirname; + gchar *path; + + priv = samba->priv; + + data = g_simple_async_result_get_op_res_gpointer (res); + data->devices = g_new0 (PpDevicesList, 1); + data->devices->devices = NULL; + + g_mutex_lock (&mutex); + + smb_context = smbc_new_context (); + if (smb_context) + { + if (smbc_init_context (smb_context)) + { + smbc_setOptionUserData (smb_context, data); + + if (priv->hostname) + { + dirname = g_strdup_printf ("smb://%s", priv->hostname); + path = g_strdup_printf ("//%s", priv->hostname); + } + else + { + dirname = g_strdup_printf ("smb://"); + path = g_strdup_printf ("//"); + } + + smbc_setFunctionAuthDataWithContext (smb_context, anonymous_auth_fn); + list_dir (smb_context, dirname, path, cancellable, data); + + g_free (dirname); + g_free (path); + } + + smbc_free_context (smb_context, 1); + } + + g_mutex_unlock (&mutex); +} + +void +pp_samba_get_devices_async (PpSamba *samba, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GSimpleAsyncResult *res; + PpSambaPrivate *priv = samba->priv; + SMBData *data; + + res = g_simple_async_result_new (G_OBJECT (samba), callback, user_data, pp_samba_get_devices_async); + data = g_new0 (SMBData, 1); + data->devices = NULL; + data->context = g_main_context_default (); + data->hostname_set = priv->hostname != NULL; + data->parent = g_object_ref (priv->parent); + + g_simple_async_result_set_check_cancellable (res, cancellable); + g_simple_async_result_set_op_res_gpointer (res, data, (GDestroyNotify) smb_data_free); + g_simple_async_result_run_in_thread (res, _pp_samba_get_devices_thread, 0, cancellable); + + g_object_unref (res); +} + +PpDevicesList * +pp_samba_get_devices_finish (PpSamba *samba, + GAsyncResult *res, + GError **error) +{ + GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (res); + SMBData *data; + PpDevicesList *result; + + g_warn_if_fail (g_simple_async_result_get_source_tag (simple) == pp_samba_get_devices_async); + + if (g_simple_async_result_propagate_error (simple, error)) + return NULL; + + data = g_simple_async_result_get_op_res_gpointer (simple); + result = data->devices; + data->devices = NULL; + + return result; +} diff --git a/panels/printers/pp-samba.h b/panels/printers/pp-samba.h new file mode 100644 index 000000000..64d505fed --- /dev/null +++ b/panels/printers/pp-samba.h @@ -0,0 +1,67 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright 2012 - 2013 Red Hat, Inc, + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * Author: Marek Kasik + */ + +#ifndef __PP_SAMBA_H__ +#define __PP_SAMBA_H__ + +#include "pp-utils.h" + +G_BEGIN_DECLS + +#define PP_TYPE_SAMBA (pp_samba_get_type ()) +#define PP_SAMBA(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), PP_TYPE_SAMBA, PpSamba)) +#define PP_SAMBA_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), PP_TYPE_SAMBA, PpSambaClass)) +#define PP_IS_SAMBA(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), PP_TYPE_SAMBA)) +#define PP_IS_SAMBA_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), PP_TYPE_SAMBA)) +#define PP_SAMBA_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), PP_TYPE_SAMBA, PpSambaClass)) + +typedef struct _PpSamba PpSamba; +typedef struct _PpSambaClass PpSambaClass; +typedef struct _PpSambaPrivate PpSambaPrivate; + +struct _PpSamba +{ + GObject parent_instance; + PpSambaPrivate *priv; +}; + +struct _PpSambaClass +{ + GObjectClass parent_class; +}; + +GType pp_samba_get_type (void) G_GNUC_CONST; + +PpSamba *pp_samba_new (GtkWindow *parent, + const gchar *hostname); + +void pp_samba_get_devices_async (PpSamba *samba, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + +PpDevicesList *pp_samba_get_devices_finish (PpSamba *samba, + GAsyncResult *result, + GError **error); + +G_END_DECLS + +#endif /* __PP_SAMBA_H__ */ diff --git a/panels/printers/pp-utils.h b/panels/printers/pp-utils.h index 7c6cc2bf7..2514f768f 100644 --- a/panels/printers/pp-utils.h +++ b/panels/printers/pp-utils.h @@ -52,7 +52,9 @@ enum { ACQUISITION_METHOD_DEFAULT_CUPS_SERVER = 0, ACQUISITION_METHOD_REMOTE_CUPS_SERVER, - ACQUISITION_METHOD_SNMP + ACQUISITION_METHOD_SNMP, + ACQUISITION_METHOD_SAMBA, + ACQUISITION_METHOD_SAMBA_HOST }; typedef struct @@ -76,6 +78,11 @@ typedef struct gsize num_of_manufacturers; } PPDList; +typedef struct +{ + GList *devices; +} PpDevicesList; + gchar *get_tag_value (const gchar *tag_string, const gchar *tag_name);