
#include <stdlib.h>
#include <glib.h>
#include <gio/gio.h>

static void mount_done_cb (GObject *object, GAsyncResult *res, gpointer user_data)
{
	GMainLoop *main_loop = user_data;
	GError *error = NULL;
	gboolean succeeded = g_file_mount_enclosing_volume_finish (G_FILE (object), res, &error);
	if(!succeeded) {
		g_print ("Mount error: %s\n", error->message);
		exit (-1);
	}
	g_main_loop_quit (main_loop);
}

static void unmount_done_cb (GObject *object, GAsyncResult *res, gpointer user_data)
{
	GMainLoop *main_loop = user_data;
	GError *error = NULL;
	gboolean succeeded = g_mount_unmount_with_operation_finish (G_MOUNT (object), res, &error);
	if (!succeeded) {
		g_print ("Unmount error: %s\n", error->message);
		exit (-1);
	}
	g_main_loop_quit (main_loop);
}

static void no_password_cb (GMountOperation *op, const gchar *message, const gchar *default_user, const gchar *default_domain, GAskPasswordFlags flags)
{
	g_mount_operation_reply (op, G_MOUNT_OPERATION_UNHANDLED);
}

static void ask_password_cb (GMountOperation *op, const gchar *message, const gchar *default_user, const gchar *default_domain, GAskPasswordFlags flags)
{
	g_print ("%s\n", message);

	if (flags & G_ASK_PASSWORD_NEED_USERNAME) {
		g_print ("Need username!\n");
	}
	if (flags & G_ASK_PASSWORD_NEED_DOMAIN) {
		g_print ("Need domain!\n");
		g_mount_operation_set_domain (op, default_domain);
	}
	if (flags & G_ASK_PASSWORD_NEED_PASSWORD) {
		g_print ("Need password!\n");
	}

	/* Disconnect the signal handler, if we don't have the
	 * password now we're never going to no matter how many times
	 * it asks
	 */
	g_signal_handlers_disconnect_matched (op, G_SIGNAL_MATCH_FUNC, 0, 0, NULL, ask_password_cb, NULL);
	g_signal_connect (op, "ask_password", G_CALLBACK (no_password_cb), NULL);	
	
	g_mount_operation_reply (op, G_MOUNT_OPERATION_HANDLED);
}

static gboolean mount (gchar *server, gchar *share, gchar *user, gchar *password)
{
	GMainContext *context;
	GFile *file;
	GMountOperation *op;
	GError *error = NULL;
	gchar *uncpath = g_strdup_printf ("smb://%s/%s", server, share);
	GMainLoop *main_loop;

	context = g_main_context_new ();
	g_main_context_push_thread_default (context);
	main_loop = g_main_loop_new (context, FALSE);

	op = g_mount_operation_new ();
	g_mount_operation_set_username (op, user);
	g_mount_operation_set_password (op, password);
	g_signal_connect (op, "ask_password", G_CALLBACK (ask_password_cb), NULL);

	file = g_file_new_for_uri (uncpath);
	g_file_mount_enclosing_volume (file, 0, op, NULL, mount_done_cb, main_loop);
	g_main_loop_run (main_loop);
	
	g_main_context_pop_thread_default (context);

	g_object_unref (op);
	g_object_unref (file);
	g_main_loop_unref (main_loop);
	g_main_context_unref (context);
	g_free (uncpath);

	if (error != NULL) {
		g_print ("mount error is %s\n", error->message);
		return FALSE;
	} else {
		return TRUE;
	}
}

static gboolean unmount (gchar *server, gchar *share)
{
	GMainContext *context;
	GFile *file;
	GMount *mount;
	GMountOperation *op;
	GError *error = NULL;
	gchar *uncpath = g_strdup_printf ("smb://%s/%s", server, share);
	GMainLoop *main_loop;
	
	context = g_main_context_new ();
	g_main_context_push_thread_default (context);
	main_loop = g_main_loop_new (context, FALSE);

	op = g_mount_operation_new ();

	file = g_file_new_for_uri (uncpath);
	mount = g_file_find_enclosing_mount (file, NULL, &error);
	
	if (mount != NULL) {
		g_mount_unmount_with_operation (mount, 0, op, NULL, unmount_done_cb, main_loop);
	
		g_main_loop_run (main_loop);
	}

	g_main_context_pop_thread_default (context);

	g_object_unref (op);
	g_object_unref (mount);
	g_object_unref (file);
	g_main_loop_unref (main_loop);
	g_main_context_unref (context);
	g_free (uncpath);

	if (error != NULL) {
		g_print ("unmount error is %s\n", error->message);
		return FALSE;
	} else {
		return TRUE;
	}
}

static gchar * get_mount_point(gchar *server, gchar *share)
{
	GDBusConnection *dbus;
	GVariant *reply;
	GError *error = NULL;
	gchar *ret = NULL;

	/* Not using LookupMount as that kept crashing gvfsd on Ubuntu 14.04 */
	dbus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
	reply = g_dbus_connection_call_sync (dbus,
		"org.gtk.vfs.Daemon",
		"/org/gtk/vfs/mounttracker",
		"org.gtk.vfs.MountTracker",
		"ListMounts",
		g_variant_new ("()", NULL),
		G_VARIANT_TYPE ("(a(sossssssbay(aya{sv})ay))"),
		G_DBUS_CALL_FLAGS_NONE,
		-1, NULL, &error);

	if (reply != NULL) {
		GVariantIter iter;
		GVariant *outer_item, *inner_item;

		outer_item = g_variant_get_child_value (reply, 0);

		g_variant_iter_init (&iter, outer_item);
		while ((inner_item = g_variant_iter_next_value (&iter))) {
			GVariant *mount_spec, *mount_dictv;
			GVariantDict mount_dict;
			const gchar *found_type, *found_server, *found_share;
			gsize child_count = g_variant_n_children (inner_item);

			/* Sanity check */
			if (child_count < 11) {
				g_variant_unref (inner_item);
				continue;
			}

			/* The mount spec */
			mount_spec = g_variant_get_child_value (inner_item, 10);
			g_variant_get (mount_spec, "(ay@a{sv})", NULL, &mount_dictv);
			g_variant_dict_init (&mount_dict, mount_dictv);

			if (!g_variant_dict_lookup (&mount_dict, "type", "^&ay", &found_type) ||
			    !g_variant_dict_lookup (&mount_dict, "server", "^&ay", &found_server) ||
			    !g_variant_dict_lookup (&mount_dict, "share", "^&ay", &found_share) ||
			    !g_str_equal (found_type, "smb-share") ||
			    !g_str_equal (found_server, server) ||
			    !g_str_equal (found_share, share)) {
			    	g_variant_unref (g_variant_dict_end (&mount_dict));
				g_variant_unref (mount_dictv);
				g_variant_unref (mount_spec);
				g_variant_unref (inner_item);
				continue;
			}
			g_variant_unref (g_variant_dict_end (&mount_dict));

			/* The fuse mount point */
			g_variant_get_child (inner_item, 9, "^ay", &ret);

			g_variant_unref (mount_dictv);
			g_variant_unref (mount_spec);
			g_variant_unref (inner_item);

			break;
		}

		g_variant_unref (outer_item);
		g_clear_pointer (&reply, g_variant_unref);
	} else {
		g_warning("DBus error: %s", error->message);
	}

	g_clear_error (&error);
	g_object_unref (dbus);
	
	return ret;
}

int main (int argc, char **argv)
{
	gboolean ret = TRUE;
	
	if (argc < 5) {
		g_print ("Usage: %s server share user pass\n", argv[0]);
		exit (-1);
	}
	
	while(ret) {
		ret = mount (argv[1], argv[2], argv[3], argv[4]);
		if (ret == FALSE) {
			g_print ("Mount failed.\n");
		} else {
			g_print ("Mounted at: %s\n", get_mount_point (argv[1], argv[2]));
		
			ret = unmount (argv[1], argv[2]);
			if (ret == FALSE) {
				g_print ("Unmount failed.\n");
			} else {
				g_print ("Unmounted.\n");
			}
		}
	}

	exit (0);
}
