diff options
author | Alexander Larsson <alexl@redhat.com> | 2007-11-26 16:13:05 +0000 |
---|---|---|
committer | Alexander Larsson <alexl@src.gnome.org> | 2007-11-26 16:13:05 +0000 |
commit | 3781343738de4abddf56982325a77bd70a98cd26 (patch) | |
tree | 7edf4bfd446dc1465fdb8595641bf5307955940a /gio | |
parent | 8bdbcb92135c8831d045874d5913a13fe30c3af4 (diff) |
gio/ docs/reference/gio Merged gio-standalone into glib.
2007-11-26 Alexander Larsson <alexl@redhat.com>
* Makefile.am:
* configure.in:
* gio-2.0-uninstalled.pc.in:
* gio-2.0.pc.in:
* gio-unix-2.0-uninstalled.pc.in:
* gio-unix-2.0.pc.in:
* gio/
* docs/reference/gio
Merged gio-standalone into glib.
* glib/glibintl.h:
* glib/gutils.c:
Export glib_gettext so that gio can use it
Add P_ (using same domain for now)
Add I_ as g_intern_static_string
svn path=/trunk/; revision=5941
Diffstat (limited to 'gio')
167 files changed, 50995 insertions, 0 deletions
diff --git a/gio/Makefile.am b/gio/Makefile.am new file mode 100644 index 000000000..31b3e5c7b --- /dev/null +++ b/gio/Makefile.am @@ -0,0 +1,225 @@ +NULL = + +SUBDIRS= + +if OS_UNIX +SUBDIRS += xdgmime +endif + +AM_CPPFLAGS = \ + -DG_LOG_DOMAIN=\"GLib-GIO\" \ + -I$(top_builddir) \ + -I$(top_srcdir) \ + -I$(top_srcdir)/glib \ + -I$(top_srcdir)/gmodule \ + $(GLIB_DEBUG_FLAGS) \ + -DG_DISABLE_DEPRECATED \ + -DGIO_MODULE_DIR=\"$(libdir)/gio/modules\" + +lib_LTLIBRARIES = libgio-2.0.la + +marshal_sources = \ + gio-marshal.h \ + gio-marshal.c \ + $(NULL) + +if CROSS_COMPILING + glib_genmarshal=$(GLIB_GENMARSHAL) +else + glib_genmarshal=../gobject/glib-genmarshal +endif + +gio-marshal.h: gio-marshal.list + $(glib_genmarshal) --prefix=_gio_marshal $(srcdir)/gio-marshal.list --header > $@ + +gio-marshal.c: gio-marshal.h gio-marshal.list + (echo "#include \"gio-marshal.h\""; \ + $(glib_genmarshal) --prefix=_gio_marshal $(srcdir)/gio-marshal.list --body) > $@ + +local_sources = \ + glocaldirectorymonitor.c \ + glocaldirectorymonitor.h \ + glocalfile.c \ + glocalfile.h \ + glocalfileenumerator.c \ + glocalfileenumerator.h \ + glocalfileinfo.c \ + glocalfileinfo.h \ + glocalfileinputstream.c \ + glocalfileinputstream.h \ + glocalfilemonitor.c \ + glocalfilemonitor.h \ + glocalfileoutputstream.c \ + glocalfileoutputstream.h \ + glocalvfs.c \ + glocalvfs.h \ + $(NULL) + +platform_libadd = +appinfo_sources = + +if HAVE_INOTIFY +SUBDIRS += inotify +platform_libadd += inotify/libinotify.la +endif + +SUBDIRS += . + +if HAVE_FAM +SUBDIRS += fam +endif + +if OS_UNIX +appinfo_sources += gdesktopappinfo.c gdesktopappinfo.h +platform_libadd += xdgmime/libxdgmime.la +unix_sources = \ + gunixdrive.c \ + gunixdrive.h \ + gunixmounts.c \ + gunixmounts.h \ + gunixvolume.c \ + gunixvolume.h \ + gunixvolumemonitor.c \ + gunixvolumemonitor.h \ + $(NULL) + +giounixincludedir=$(includedir)/gio-unix-2.0/gio +giounixinclude_HEADERS = \ + gunixmounts.h \ + $(NULL) +endif + +if OS_WIN32 +appinfo_sources += gwin32appinfo.c gwin32appinfo.h +platform_libadd += -lshlwapi +endif + +libgio_2_0_la_SOURCES = \ + gappinfo.c \ + gasynchelper.c \ + gasynchelper.h \ + gasyncresult.c \ + gbufferedinputstream.c \ + gbufferedoutputstream.c \ + gcancellable.c \ + gcontenttype.c \ + gcontenttypeprivate.h \ + gdatainputstream.c \ + gdataoutputstream.c \ + gdirectorymonitor.c \ + gdrive.c \ + gdriveprivate.h \ + gdummyfile.c \ + gfile.c \ + gfileattribute.c \ + gfileenumerator.c \ + gfileicon.c \ + gfileinfo.c \ + gfileinputstream.c \ + gfilemonitor.c \ + gfilenamecompleter.c \ + gfileoutputstream.c \ + gfilterinputstream.c \ + gfilteroutputstream.c \ + gicon.c \ + ginputstream.c \ + gioerror.c \ + giomodule.c \ + gioscheduler.c \ + gloadableicon.c \ + gmemoryinputstream.c \ + gmemoryoutputstream.c \ + gmountoperation.c \ + gnativevolumemonitor.c \ + gnativevolumemonitor.h \ + goutputstream.c \ + gpollfilemonitor.c \ + gpollfilemonitor.h \ + gseekable.c \ + gsimpleasyncresult.c \ + gsocketinputstream.c \ + gsocketoutputstream.c \ + gthemedicon.c \ + gunionvolumemonitor.c \ + gunionvolumemonitor.h \ + gurifuncs.c \ + gvfs.c \ + gvolume.c \ + gvolumemonitor.c \ + gvolumeprivate.h \ + $(appinfo_sources) \ + $(unix_sources) \ + $(local_sources) \ + $(marshal_sources) \ + $(NULL) + +$(libgio_2_0_la_OBJECTS): $(marshal_sources) + +libgio_2_0_la_LIBADD = \ + $(top_builddir)/glib/libglib-2.0.la \ + $(top_builddir)/gobject/libgobject-2.0.la \ + $(top_builddir)/gmodule/libgmodule-2.0.la \ + $(platform_libadd) \ + $(SELINUX_LIBS) \ + $(GLIB_LIBS) \ + $(XATTR_LIBS) \ + $(NULL) + +if OS_WIN32 +no_undefined = -no-undefined +endif + +libgio_2_0_la_LDFLAGS= -export-dynamic $(no_undefined) -export-symbols-regex '^g_.*' + +gioincludedir=$(includedir)/glib-2.0/gio/ +gioinclude_HEADERS = \ + gappinfo.h \ + gasyncresult.h \ + gbufferedinputstream.h \ + gbufferedoutputstream.h \ + gcancellable.h \ + gcontenttype.h \ + gdatainputstream.h \ + gdataoutputstream.h \ + gdirectorymonitor.h \ + gdrive.h \ + gdummyfile.h \ + gfile.h \ + gfileattribute.h \ + gfileenumerator.h \ + gfileicon.h \ + gfileinfo.h \ + gfileinputstream.h \ + gfilemonitor.h \ + gfilenamecompleter.h \ + gfileoutputstream.h \ + gfilterinputstream.h \ + gfilteroutputstream.h \ + gicon.h \ + ginputstream.h \ + gioerror.h \ + giomodule.h \ + gioscheduler.h \ + gloadableicon.h \ + gmemoryinputstream.h \ + gmemoryoutputstream.h \ + gmountoperation.h \ + goutputstream.h \ + gseekable.h \ + gsimpleasyncresult.h \ + gsocketinputstream.h \ + gsocketoutputstream.h \ + gthemedicon.h \ + gurifuncs.h \ + gvfs.h \ + gvolume.h \ + gvolumemonitor.h \ + $(NULL) + +EXTRA_DIST = \ + gio-marshal.list \ + $(NULL) + +CLEANFILES = \ + $(marshal_sources) \ + $(NULL) diff --git a/gio/fam/Makefile.am b/gio/fam/Makefile.am new file mode 100644 index 000000000..0e8b79969 --- /dev/null +++ b/gio/fam/Makefile.am @@ -0,0 +1,33 @@ +NULL = + +module_flags = -export_dynamic -avoid-version -module -no-undefined -export-symbols-regex '^g_io_module_(load|unload)' + +giomodule_LTLIBRARIES = libgiofam.la +giomoduledir = $(libdir)/gio/modules + +libgiofam_la_SOURCES = \ + fam-helper.c \ + fam-helper.h \ + fam-module.c \ + gfamdirectorymonitor.c \ + gfamdirectorymonitor.h \ + gfamfilemonitor.c \ + gfamfilemonitor.h \ + $(NULL) + +libgiofam_la_CFLAGS = \ + -DG_LOG_DOMAIN=\"GLib-GIO\" \ + -I$(top_srcdir) \ + -I$(top_srcdir)/glib \ + -I$(top_srcdir)/gmodule \ + -I$(top_srcdir)/gio \ + -DGIO_MODULE_DIR=\"$(libdir)/gio/modules\" \ + -DG_DISABLE_DEPRECATED + +libgiofam_la_LDFLAGS = $(module_flags) +libgiofam_la_LIBADD = \ + $(top_builddir)/gio/libgio-2.0.la \ + $(GLIB_LIBS) \ + $(FAM_LIBS) \ + $(NULL) + diff --git a/gio/fam/fam-helper.c b/gio/fam/fam-helper.c new file mode 100644 index 000000000..40c2c6ac3 --- /dev/null +++ b/gio/fam/fam-helper.c @@ -0,0 +1,245 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Authors: Alexander Larsson <alexl@redhat.com> + * John McCutchan <john@johnmccutchan.com> + * Sebastian Dröge <slomo@circular-chaos.org> + */ + +#include "config.h" +#include <fam.h> +#include <gio/gfilemonitor.h> +#include <gio/gdirectorymonitor.h> + +#include "fam-helper.h" + +static FAMConnection* fam_connection = NULL; +static gint fam_watch_id = 0; +G_LOCK_DEFINE_STATIC(fam_connection); + +struct _fam_sub +{ + gchar *pathname; + gboolean directory; + gpointer user_data; + gboolean cancelled; + FAMRequest request; +}; + +static GFileMonitorEvent +fam_event_to_file_monitor_event (enum FAMCodes code) +{ + switch (code) + { + case FAMChanged: + return G_FILE_MONITOR_EVENT_CHANGED; + break; + case FAMDeleted: + return G_FILE_MONITOR_EVENT_DELETED; + break; + case FAMCreated: + return G_FILE_MONITOR_EVENT_CREATED; + break; + default: + return -1; + break; + } +} + +static gboolean +fam_do_iter_unlocked (void) +{ + while (fam_connection != NULL && FAMPending (fam_connection)) { + FAMEvent ev; + fam_sub* sub = NULL; + gboolean cancelled; + + if (FAMNextEvent (fam_connection, &ev) != 1) { + FAMClose (fam_connection); + g_free (fam_connection); + g_source_remove (fam_watch_id); + fam_watch_id = 0; + fam_connection = NULL; + return FALSE; + } + + sub = (fam_sub*)ev.userdata; + cancelled = sub->cancelled; + if (ev.code == FAMAcknowledge && cancelled) + { + g_free (sub); + continue; + } + + if (cancelled) + continue; + + if (sub->directory) + { + GDirectoryMonitor* monitor = G_DIRECTORY_MONITOR (sub->user_data); + GFileMonitorEvent eflags = fam_event_to_file_monitor_event (ev.code); + gchar* path = NULL; + GFile *child, *parent; + + /* unsupported event */ + if (eflags == -1) + continue; + + if (ev.filename[0] == '/') + path = g_strdup (ev.filename); + else + path = g_strdup_printf ("%s/%s", sub->pathname, ev.filename); + + child = g_file_new_for_path (path); + parent = g_file_get_parent (child); + g_directory_monitor_emit_event (monitor, child, NULL, eflags); + g_free (path); + g_object_unref (child); + g_object_unref (parent); + } else { + GFile *child; + GFileMonitor* monitor = G_FILE_MONITOR (sub->user_data); + GFileMonitorEvent eflags = fam_event_to_file_monitor_event (ev.code); + gchar* path = NULL; + + if (eflags == -1) + continue; + path = g_strdup (ev.filename); + child = g_file_new_for_path (path); + g_file_monitor_emit_event (monitor, child, NULL, eflags); + g_free (path); + g_object_unref (child); + } + } + + return TRUE; +} + +static gboolean +fam_callback (GIOChannel *source, + GIOCondition condition, + gpointer data) +{ + gboolean res; + G_LOCK (fam_connection); + + res = fam_do_iter_unlocked (); + + G_UNLOCK (fam_connection); + return res; +} + +gboolean +_fam_sub_startup (void) +{ + GIOChannel *ioc; + + G_LOCK (fam_connection); + + if (fam_connection == NULL) { + fam_connection = g_new0 (FAMConnection, 1); + if (FAMOpen2 (fam_connection, "gvfs user") != 0) { + g_warning ("FAMOpen failed, FAMErrno=%d\n", FAMErrno); + g_free (fam_connection); + fam_connection = NULL; + G_UNLOCK (fam_connection); + return FALSE; + } +#ifdef HAVE_FAM_NO_EXISTS + /* This is a gamin extension that avoids sending all the Exists event for dir monitors */ + FAMNoExists (fam_connection); +#endif + ioc = g_io_channel_unix_new (FAMCONNECTION_GETFD(fam_connection)); + fam_watch_id = g_io_add_watch (ioc, + G_IO_IN | G_IO_HUP | G_IO_ERR, + fam_callback, fam_connection); + g_io_channel_unref (ioc); + } + + G_UNLOCK (fam_connection); + + return TRUE; +} + +fam_sub* +_fam_sub_add (const gchar* pathname, + gboolean directory, + gpointer user_data) +{ + fam_sub *sub; + + if (!_fam_sub_startup ()) + return NULL; + + sub = g_new0 (fam_sub, 1); + sub->pathname = g_strdup (pathname); + sub->directory = directory; + sub->user_data = user_data; + + G_LOCK (fam_connection); + /* We need to queue up incoming messages to avoid blocking on write + * if there are many monitors being canceled */ + fam_do_iter_unlocked (); + + if (fam_connection == NULL) { + G_UNLOCK (fam_connection); + return NULL; + } + + if (directory) + FAMMonitorDirectory (fam_connection, pathname, &sub->request, sub); + else + FAMMonitorFile (fam_connection, pathname, &sub->request, sub); + + G_UNLOCK (fam_connection); + return sub; +} + +gboolean +_fam_sub_cancel (fam_sub* sub) +{ + if (sub->cancelled) + return TRUE; + + sub->cancelled = TRUE; + + G_LOCK (fam_connection); + /* We need to queue up incoming messages to avoid blocking on write + * if there are many monitors being canceled */ + fam_do_iter_unlocked (); + + if (fam_connection == NULL) { + G_UNLOCK (fam_connection); + return FALSE; + } + + FAMCancelMonitor (fam_connection, &sub->request); + + G_UNLOCK (fam_connection); + + return TRUE; +} + +void +_fam_sub_free (fam_sub* sub) +{ + g_free (sub->pathname); + g_free (sub); +} + diff --git a/gio/fam/fam-helper.h b/gio/fam/fam-helper.h new file mode 100644 index 000000000..e8acbfeff --- /dev/null +++ b/gio/fam/fam-helper.h @@ -0,0 +1,37 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Authors: Alexander Larsson <alexl@redhat.com> + * John McCutchan <john@johnmccutchan.com> + * Sebastian Dröge <slomo@circular-chaos.org> + */ + +#ifndef __FAM_HELPER_H__ +#define __FAM_HELPER_H__ + +typedef struct _fam_sub fam_sub; + +gboolean _fam_sub_startup (void); +fam_sub* _fam_sub_add (const gchar* pathname, + gboolean directory, + gpointer user_data); +gboolean _fam_sub_cancel (fam_sub* sub); +void _fam_sub_free (fam_sub* sub); + +#endif /* __FAM_HELPER_H__ */ diff --git a/gio/fam/fam-module.c b/gio/fam/fam-module.c new file mode 100644 index 000000000..d7b9cfcae --- /dev/null +++ b/gio/fam/fam-module.c @@ -0,0 +1,41 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * Copyright (C) 2007 Sebastian Dröge. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Authors: Alexander Larsson <alexl@redhat.com> + * John McCutchan <john@johnmccutchan.com> + * Sebastian Dröge <slomo@circular-chaos.org> + */ + +#include "giomodule.h" +#include "gfamdirectorymonitor.h" +#include "gfamfilemonitor.h" + +void +g_io_module_load (GIOModule *module) +{ + g_fam_file_monitor_register (module); + g_fam_directory_monitor_register (module); +} + +void +g_io_module_unload (GIOModule *module) +{ +} + diff --git a/gio/fam/gfamdirectorymonitor.c b/gio/fam/gfamdirectorymonitor.c new file mode 100644 index 000000000..89298301b --- /dev/null +++ b/gio/fam/gfamdirectorymonitor.c @@ -0,0 +1,152 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * Copyright (C) 2007 Sebastian Dröge. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Authors: Alexander Larsson <alexl@redhat.com> + * John McCutchan <john@johnmccutchan.com> + * Sebastian Dröge <slomo@circular-chaos.org> + */ + +#include <config.h> + +#include "gfamdirectorymonitor.h" +#include "giomodule.h" + +#include "fam-helper.h" + +struct _GFamDirectoryMonitor +{ + GLocalDirectoryMonitor parent_instance; + fam_sub *sub; +}; + +static gboolean g_fam_directory_monitor_cancel (GDirectoryMonitor* monitor); + +G_DEFINE_DYNAMIC_TYPE (GFamDirectoryMonitor, g_fam_directory_monitor, G_TYPE_LOCAL_DIRECTORY_MONITOR) + +static void +g_fam_directory_monitor_finalize (GObject *object) +{ + GFamDirectoryMonitor *fam_monitor = G_FAM_DIRECTORY_MONITOR (object); + fam_sub *sub = fam_monitor->sub; + + if (sub) { + if (!_fam_sub_cancel (sub)) + g_warning ("Unexpected error cancelling fam monitor"); + + _fam_sub_free (sub); + fam_monitor->sub = NULL; + } + + if (G_OBJECT_CLASS (g_fam_directory_monitor_parent_class)->finalize) + (*G_OBJECT_CLASS (g_fam_directory_monitor_parent_class)->finalize) (object); +} + +static GObject * +g_fam_directory_monitor_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_properties) +{ + GObject *obj; + GFamDirectoryMonitorClass *klass; + GObjectClass *parent_class; + GFamDirectoryMonitor *fam_monitor; + const gchar *dirname = NULL; + fam_sub *sub = NULL; + + klass = G_FAM_DIRECTORY_MONITOR_CLASS (g_type_class_peek (G_TYPE_FAM_DIRECTORY_MONITOR)); + parent_class = G_OBJECT_CLASS (g_type_class_peek_parent (klass)); + obj = parent_class->constructor (type, + n_construct_properties, + construct_properties); + + fam_monitor = G_FAM_DIRECTORY_MONITOR (obj); + + dirname = G_LOCAL_DIRECTORY_MONITOR (obj)->dirname; + g_assert (dirname != NULL); + + sub = _fam_sub_add (dirname, TRUE, fam_monitor); + /* FIXME: what to do about errors here? we can't return NULL or another + * kind of error and an assertion is probably too hard */ + g_assert (sub != NULL); + + fam_monitor->sub = sub; + + return obj; +} + +static void +g_fam_directory_monitor_class_finalize (GFamDirectoryMonitorClass *klass) +{ +} + +static gboolean +g_fam_directory_monitor_is_supported (void) +{ + return _fam_sub_startup (); +} + +static void +g_fam_directory_monitor_class_init (GFamDirectoryMonitorClass* klass) +{ + GObjectClass* gobject_class = G_OBJECT_CLASS (klass); + GDirectoryMonitorClass *directory_monitor_class = G_DIRECTORY_MONITOR_CLASS (klass); + GLocalDirectoryMonitorClass *local_directory_monitor_class = G_LOCAL_DIRECTORY_MONITOR_CLASS (klass); + + gobject_class->finalize = g_fam_directory_monitor_finalize; + gobject_class->constructor = g_fam_directory_monitor_constructor; + directory_monitor_class->cancel = g_fam_directory_monitor_cancel; + + local_directory_monitor_class->prio = 10; + local_directory_monitor_class->mount_notify = FALSE; + local_directory_monitor_class->is_supported = g_fam_directory_monitor_is_supported; +} + +static void +g_fam_directory_monitor_init (GFamDirectoryMonitor* monitor) +{ + +} + +static gboolean +g_fam_directory_monitor_cancel (GDirectoryMonitor* monitor) +{ + GFamDirectoryMonitor *fam_monitor = G_FAM_DIRECTORY_MONITOR (monitor); + fam_sub *sub = fam_monitor->sub; + + if (sub) { + if (!_fam_sub_cancel (sub)) + g_warning ("Unexpected error cancelling fam monitor"); + + _fam_sub_free (sub); + fam_monitor->sub = NULL; + } + + if (G_DIRECTORY_MONITOR_CLASS (g_fam_directory_monitor_parent_class)->cancel) + (*G_DIRECTORY_MONITOR_CLASS (g_fam_directory_monitor_parent_class)->cancel) (monitor); + + return TRUE; +} + +void +g_fam_directory_monitor_register (GIOModule *module) +{ + g_fam_directory_monitor_register_type (G_TYPE_MODULE (module)); +} + diff --git a/gio/fam/gfamdirectorymonitor.h b/gio/fam/gfamdirectorymonitor.h new file mode 100644 index 000000000..eefda883d --- /dev/null +++ b/gio/fam/gfamdirectorymonitor.h @@ -0,0 +1,55 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * Copyright (C) 2007 Sebastian Dröge. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Authors: Alexander Larsson <alexl@redhat.com> + * John McCutchan <john@johnmccutchan.com> + * Sebastian Dröge <slomo@circular-chaos.org> + */ + +#ifndef __G_FAM_DIRECTORY_MONITOR_H__ +#define __G_FAM_DIRECTORY_MONITOR_H__ + +#include <glib-object.h> +#include <string.h> +#include <gio/gdirectorymonitor.h> +#include "glocaldirectorymonitor.h" +#include "giomodule.h" + +G_BEGIN_DECLS + +#define G_TYPE_FAM_DIRECTORY_MONITOR (g_fam_directory_monitor_get_type ()) +#define G_FAM_DIRECTORY_MONITOR(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_FAM_DIRECTORY_MONITOR, GFamDirectoryMonitor)) +#define G_FAM_DIRECTORY_MONITOR_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), G_TYPE_FAM_DIRECTORY_MONITOR, GFamDirectoryMonitorClass)) +#define G_IS_FAM_DIRECTORY_MONITOR(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_FAM_DIRECTORY_MONITOR)) +#define G_IS_FAM_DIRECTORY_MONITOR_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_FAM_DIRECTORY_MONITOR)) + +typedef struct _GFamDirectoryMonitor GFamDirectoryMonitor; +typedef struct _GFamDirectoryMonitorClass GFamDirectoryMonitorClass; + +struct _GFamDirectoryMonitorClass { + GLocalDirectoryMonitorClass parent_class; +}; + +GType g_fam_directory_monitor_get_type (void); +void g_fam_directory_monitor_register (GIOModule *module); + +G_END_DECLS + +#endif /* __G_FAM_DIRECTORY_MONITOR_H__ */ diff --git a/gio/fam/gfamfilemonitor.c b/gio/fam/gfamfilemonitor.c new file mode 100644 index 000000000..be3631806 --- /dev/null +++ b/gio/fam/gfamfilemonitor.c @@ -0,0 +1,150 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * Copyright (C) 2007 Sebastian Dröge. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Authors: Alexander Larsson <alexl@redhat.com> + * John McCutchan <john@johnmccutchan.com> + * Sebastian Dröge <slomo@circular-chaos.org> + */ + +#include <config.h> + +#include "gfamfilemonitor.h" +#include "giomodule.h" + +#include "fam-helper.h" + +struct _GFamFileMonitor +{ + GLocalFileMonitor parent_instance; + fam_sub *sub; +}; + +static gboolean g_fam_file_monitor_cancel (GFileMonitor* monitor); + +G_DEFINE_DYNAMIC_TYPE (GFamFileMonitor, g_fam_file_monitor, G_TYPE_LOCAL_FILE_MONITOR) + +static void +g_fam_file_monitor_finalize (GObject *object) +{ + GFamFileMonitor *fam_monitor = G_FAM_FILE_MONITOR (object); + fam_sub *sub = fam_monitor->sub; + + if (sub) { + if (!_fam_sub_cancel (sub)) + g_warning ("Unexpected error cancelling fam monitor"); + _fam_sub_free (sub); + fam_monitor->sub = NULL; + } + + if (G_OBJECT_CLASS (g_fam_file_monitor_parent_class)->finalize) + (*G_OBJECT_CLASS (g_fam_file_monitor_parent_class)->finalize) (object); +} + +static GObject * +g_fam_file_monitor_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_properties) +{ + GObject *obj; + GFamFileMonitorClass *klass; + GObjectClass *parent_class; + GFamFileMonitor *fam_monitor; + const gchar *filename = NULL; + fam_sub *sub = NULL; + + klass = G_FAM_FILE_MONITOR_CLASS (g_type_class_peek (G_TYPE_FAM_FILE_MONITOR)); + parent_class = G_OBJECT_CLASS (g_type_class_peek_parent (klass)); + obj = parent_class->constructor (type, + n_construct_properties, + construct_properties); + + fam_monitor = G_FAM_FILE_MONITOR (obj); + + filename = G_LOCAL_FILE_MONITOR (obj)->filename; + + g_assert (filename != NULL); + + sub = _fam_sub_add (filename, FALSE, fam_monitor); + /* FIXME: what to do about errors here? we can't return NULL or another + * kind of error and an assertion is probably too hard */ + g_assert (sub != NULL); + + fam_monitor->sub = sub; + + return obj; +} + +static void +g_fam_file_monitor_class_finalize (GFamFileMonitorClass *klass) +{ +} + +static gboolean +g_fam_file_monitor_is_supported (void) +{ + return _fam_sub_startup (); +} + +static void +g_fam_file_monitor_class_init (GFamFileMonitorClass* klass) +{ + GObjectClass* gobject_class = G_OBJECT_CLASS (klass); + GFileMonitorClass *file_monitor_class = G_FILE_MONITOR_CLASS (klass); + GLocalFileMonitorClass *local_file_monitor_class = G_LOCAL_FILE_MONITOR_CLASS (klass); + + gobject_class->finalize = g_fam_file_monitor_finalize; + gobject_class->constructor = g_fam_file_monitor_constructor; + file_monitor_class->cancel = g_fam_file_monitor_cancel; + + local_file_monitor_class->prio = 10; + local_file_monitor_class->is_supported = g_fam_file_monitor_is_supported; +} + +static void +g_fam_file_monitor_init (GFamFileMonitor* monitor) +{ + +} + +static gboolean +g_fam_file_monitor_cancel (GFileMonitor* monitor) +{ + GFamFileMonitor *fam_monitor = G_FAM_FILE_MONITOR (monitor); + fam_sub *sub = fam_monitor->sub; + + if (sub) { + if (!_fam_sub_cancel (sub)) + g_warning ("Unexpected error cancelling fam monitor"); + _fam_sub_free (sub); + fam_monitor->sub = NULL; + } + + if (G_FILE_MONITOR_CLASS (g_fam_file_monitor_parent_class)->cancel) + (*G_FILE_MONITOR_CLASS (g_fam_file_monitor_parent_class)->cancel) (monitor); + + return TRUE; +} + +void +g_fam_file_monitor_register (GIOModule *module) +{ + g_fam_file_monitor_register_type (G_TYPE_MODULE (module)); +} + diff --git a/gio/fam/gfamfilemonitor.h b/gio/fam/gfamfilemonitor.h new file mode 100644 index 000000000..559384eb4 --- /dev/null +++ b/gio/fam/gfamfilemonitor.h @@ -0,0 +1,55 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * Copyright (C) 2007 Sebastian Dröge. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Authors: Alexander Larsson <alexl@redhat.com> + * John McCutchan <john@johnmccutchan.com> + * Sebastian Dröge <slomo@circular-chaos.org> + */ + +#ifndef __G_FAM_FILE_MONITOR_H__ +#define __G_FAM_FILE_MONITOR_H__ + +#include <glib-object.h> +#include <string.h> +#include <gio/gfilemonitor.h> +#include "glocalfilemonitor.h" +#include "giomodule.h" + +G_BEGIN_DECLS + +#define G_TYPE_FAM_FILE_MONITOR (g_fam_file_monitor_get_type ()) +#define G_FAM_FILE_MONITOR(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_FAM_FILE_MONITOR, GFamFileMonitor)) +#define G_FAM_FILE_MONITOR_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), G_TYPE_FAM_FILE_MONITOR, GFamFileMonitorClass)) +#define G_IS_FAM_FILE_MONITOR(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_FAM_FILE_MONITOR)) +#define G_IS_FAM_FILE_MONITOR_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_FAM_FILE_MONITOR)) + +typedef struct _GFamFileMonitor GFamFileMonitor; +typedef struct _GFamFileMonitorClass GFamFileMonitorClass; + +struct _GFamFileMonitorClass { + GLocalFileMonitorClass parent_class; +}; + +GType g_fam_file_monitor_get_type (void); +void g_fam_file_monitor_register (GIOModule *module); + +G_END_DECLS + +#endif /* __G_FAM_FILE_MONITOR_H__ */ diff --git a/gio/gappinfo.c b/gio/gappinfo.c new file mode 100644 index 000000000..922d70072 --- /dev/null +++ b/gio/gappinfo.c @@ -0,0 +1,535 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#include <config.h> +#include "gappinfo.h" +#include "glibintl.h" +#include <gioerror.h> + + +static void g_app_info_base_init (gpointer g_class); +static void g_app_info_class_init (gpointer g_class, + gpointer class_data); + + +GType +g_app_info_get_type (void) +{ + static GType app_info_type = 0; + + if (! app_info_type) + { + static const GTypeInfo app_info_info = + { + sizeof (GAppInfoIface), /* class_size */ + g_app_info_base_init, /* base_init */ + NULL, /* base_finalize */ + g_app_info_class_init, + NULL, /* class_finalize */ + NULL, /* class_data */ + 0, + 0, /* n_preallocs */ + NULL + }; + + app_info_type = + g_type_register_static (G_TYPE_INTERFACE, I_("GAppInfo"), + &app_info_info, 0); + + g_type_interface_add_prerequisite (app_info_type, G_TYPE_OBJECT); + } + + return app_info_type; +} + +static void +g_app_info_class_init (gpointer g_class, + gpointer class_data) +{ +} + +static void +g_app_info_base_init (gpointer g_class) +{ +} + + +/** + * g_app_info_dup: + * @appinfo: a #GAppInfo. + * + * Returns: a duplicate of @appinfo. + **/ +GAppInfo * +g_app_info_dup (GAppInfo *appinfo) +{ + GAppInfoIface *iface; + + g_return_val_if_fail (G_IS_APP_INFO (appinfo), NULL); + + iface = G_APP_INFO_GET_IFACE (appinfo); + + return (* iface->dup) (appinfo); +} + +/** + * g_app_info_equal: + * @appinfo1: the first #GAppInfo. + * @appinfo2: the second #GAppInfo. + * + * Returns: %TRUE if @appinfo1 is equal to @appinfo2. %FALSE otherwise. + * + **/ +gboolean +g_app_info_equal (GAppInfo *appinfo1, + GAppInfo *appinfo2) +{ + GAppInfoIface *iface; + + g_return_val_if_fail (G_IS_APP_INFO (appinfo1), FALSE); + g_return_val_if_fail (G_IS_APP_INFO (appinfo2), FALSE); + + if (G_TYPE_FROM_INSTANCE (appinfo1) != G_TYPE_FROM_INSTANCE (appinfo2)) + return FALSE; + + iface = G_APP_INFO_GET_IFACE (appinfo1); + + return (* iface->equal) (appinfo1, appinfo2); +} + +/** + * g_app_info_get_id: + * @appinfo: a #GAppInfo. + * + * Returns: + **/ +const char * +g_app_info_get_id (GAppInfo *appinfo) +{ + GAppInfoIface *iface; + + g_return_val_if_fail (G_IS_APP_INFO (appinfo), NULL); + + iface = G_APP_INFO_GET_IFACE (appinfo); + + return (* iface->get_id) (appinfo); +} + +/** + * g_app_info_get_name: + * @appinfo: a #GAppInfo. + * + * Returns: the name of the application for @appinfo. + **/ +const char * +g_app_info_get_name (GAppInfo *appinfo) +{ + GAppInfoIface *iface; + + g_return_val_if_fail (G_IS_APP_INFO (appinfo), NULL); + + iface = G_APP_INFO_GET_IFACE (appinfo); + + return (* iface->get_name) (appinfo); +} + +/** + * g_app_info_get_description: + * @appinfo: a #GAppInfo. + * + * Returns: a string containing a description of the + * application @appinfo. + * The returned string should be not freed when no longer needed. + **/ +const char * +g_app_info_get_description (GAppInfo *appinfo) +{ + GAppInfoIface *iface; + + g_return_val_if_fail (G_IS_APP_INFO (appinfo), NULL); + + iface = G_APP_INFO_GET_IFACE (appinfo); + + return (* iface->get_description) (appinfo); +} + +/** + * g_app_info_get_executable: + * @appinfo: a #GAppInfo. + * + * Returns: a string containing the @appinfo's application + * binary's name. + **/ +const char * +g_app_info_get_executable (GAppInfo *appinfo) +{ + GAppInfoIface *iface; + + g_return_val_if_fail (G_IS_APP_INFO (appinfo), NULL); + + iface = G_APP_INFO_GET_IFACE (appinfo); + + return (* iface->get_executable) (appinfo); +} + + +/** + * g_app_info_set_as_default_for_type: + * @appinfo: a #GAppInfo. + * @content_type: the content type. + * @error: a #GError. + * + * Returns: %TRUE if the given @appinfo is the default + * for the given @content_type. %FALSE if not, + * or in case of an error. + **/ +gboolean +g_app_info_set_as_default_for_type (GAppInfo *appinfo, + const char *content_type, + GError **error) +{ + GAppInfoIface *iface; + + g_return_val_if_fail (G_IS_APP_INFO (appinfo), FALSE); + g_return_val_if_fail (content_type != NULL, FALSE); + + iface = G_APP_INFO_GET_IFACE (appinfo); + + return (* iface->set_as_default_for_type) (appinfo, content_type, error); +} + + +/** + * g_app_info_set_as_default_for_extension: + * @appinfo: a #GAppInfo. + * @extension: a string containing the file extension. + * @error: a #GError. + * + * Returns: %TRUE if the given @appinfo is the default + * for the given @extension. %FALSE if not, + * or in case of an error. + **/ +gboolean +g_app_info_set_as_default_for_extension (GAppInfo *appinfo, + const char *extension, + GError **error) +{ + GAppInfoIface *iface; + + g_return_val_if_fail (G_IS_APP_INFO (appinfo), FALSE); + g_return_val_if_fail (extension != NULL, FALSE); + + iface = G_APP_INFO_GET_IFACE (appinfo); + + if (iface->set_as_default_for_extension) + return (* iface->set_as_default_for_extension) (appinfo, extension, error); + + g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "g_app_info_set_as_default_for_extension not supported yet"); + return FALSE; +} + + +/** + * g_app_info_add_supports_type: + * @appinfo: a #GAppInfo. + * @content_type: a string. + * @error: a #GError. + * + * Returns: %TRUE if @appinfo supports @content_type. + * %FALSE if not, or in case of an error. + **/ +gboolean +g_app_info_add_supports_type (GAppInfo *appinfo, + const char *content_type, + GError **error) +{ + GAppInfoIface *iface; + + g_return_val_if_fail (G_IS_APP_INFO (appinfo), FALSE); + g_return_val_if_fail (content_type != NULL, FALSE); + + iface = G_APP_INFO_GET_IFACE (appinfo); + + if (iface->add_supports_type) + return (* iface->add_supports_type) (appinfo, content_type, error); + + g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "g_app_info_add_supports_type not supported yet"); + return FALSE; +} + + +/** + * g_app_info_can_remove_support_type: + * @appinfo: a #GAppInfo. + * + * Returns: %TRUE if it is possible to remove supported + * content types from a given @appinfo, %FALSE if not. + **/ +gboolean +g_app_info_can_remove_supports_type (GAppInfo *appinfo) +{ + GAppInfoIface *iface; + + g_return_val_if_fail (G_IS_APP_INFO (appinfo), FALSE); + + iface = G_APP_INFO_GET_IFACE (appinfo); + + if (iface->can_remove_supports_type) + return (* iface->can_remove_supports_type) (appinfo); + + return FALSE; +} + + +/** + * g_app_info_remove_supports_type: + * @appinfo: a #GAppInfo. + * @content_type: a string. + * @error: a #GError. + * + * Returns: %TRUE if @content_type support was removed + * from @appinfo. %FALSE if not. + **/ +gboolean +g_app_info_remove_supports_type (GAppInfo *appinfo, + const char *content_type, + GError **error) +{ + GAppInfoIface *iface; + + g_return_val_if_fail (G_IS_APP_INFO (appinfo), FALSE); + g_return_val_if_fail (content_type != NULL, FALSE); + + iface = G_APP_INFO_GET_IFACE (appinfo); + + if (iface->remove_supports_type) + return (* iface->remove_supports_type) (appinfo, content_type, error); + + g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "g_app_info_remove_supports_type not supported yet"); + return FALSE; +} + + +/** + * g_app_info_get_icon: + * @appinfo: a #GAppInfo. + * + * Returns: the default #GIcon for @appinfo. + **/ +GIcon * +g_app_info_get_icon (GAppInfo *appinfo) +{ + GAppInfoIface *iface; + + g_return_val_if_fail (G_IS_APP_INFO (appinfo), NULL); + + iface = G_APP_INFO_GET_IFACE (appinfo); + + return (* iface->get_icon) (appinfo); +} + + +/** + * g_app_info_launch: + * @appinfo: a #GAppInfo. + * @files: a #GList of #GFile objects. + * @launch_context: a #GAppLaunchContext. + * @error: a #GError. + * + * Returns: %TRUE on successful launch. + **/ +gboolean +g_app_info_launch (GAppInfo *appinfo, + GList *files, + GAppLaunchContext *launch_context, + GError **error) +{ + GAppInfoIface *iface; + + g_return_val_if_fail (G_IS_APP_INFO (appinfo), FALSE); + + iface = G_APP_INFO_GET_IFACE (appinfo); + + return (* iface->launch) (appinfo, files, launch_context, error); +} + + +/** + * g_app_info_supports_uris: + * @appinfo: a #GAppInfo. + * + * Returns: %TRUE if the @appinfo supports URIs. + **/ +gboolean +g_app_info_supports_uris (GAppInfo *appinfo) +{ + GAppInfoIface *iface; + + g_return_val_if_fail (G_IS_APP_INFO (appinfo), FALSE); + + iface = G_APP_INFO_GET_IFACE (appinfo); + + return (* iface->supports_uris) (appinfo); +} + + +/** + * g_app_info_launch_uris: + * @appinfo: a #GAppInfo. + * @uris: a #GList containing URIs to launch. + * @launch_context: a #GAppLaunchContext. + * @error: a #GError. + * + * Returns: %TRUE if the @appinfo was launched + * with the given @uris. + **/ +gboolean +g_app_info_launch_uris (GAppInfo *appinfo, + GList *uris, + GAppLaunchContext *launch_context, + GError **error) +{ + GAppInfoIface *iface; + + g_return_val_if_fail (G_IS_APP_INFO (appinfo), FALSE); + + iface = G_APP_INFO_GET_IFACE (appinfo); + + return (* iface->launch) (appinfo, uris, launch_context, error); +} + + +/** + * g_app_info_should_show: + * @appinfo: a #GAppInfo. + * @desktop_env: a string. + * + * Returns: %TRUE if the @GAppInfo should be shown, + * %FALSE otherwise. + **/ +gboolean +g_app_info_should_show (GAppInfo *appinfo, + const char *desktop_env) +{ + GAppInfoIface *iface; + + g_return_val_if_fail (G_IS_APP_INFO (appinfo), FALSE); + + iface = G_APP_INFO_GET_IFACE (appinfo); + + return (* iface->should_show) (appinfo, desktop_env); +} + +G_DEFINE_TYPE (GAppLaunchContext, g_app_launch_context, G_TYPE_OBJECT); + +/** + * g_app_launch_context_new: + * + * Returns: A new #GAppLaunchContext. + **/ +GAppLaunchContext * +g_app_launch_context_new (void) +{ + return g_object_new (G_TYPE_APP_LAUNCH_CONTEXT, NULL); +} + +static void +g_app_launch_context_class_init (GAppLaunchContextClass *klass) +{ +} + +static void +g_app_launch_context_init (GAppLaunchContext *launch_context) +{ +} + +/** + * g_app_launch_context_get_display: + * @context: a #GAppLaunchContext. + * @info: a #GAppInfo. + * @files: a #GList of files. + * + **/ +char * +g_app_launch_context_get_display (GAppLaunchContext *context, + GAppInfo *info, + GList *files) +{ + GAppLaunchContextClass *class; + + g_return_val_if_fail (G_IS_APP_LAUNCH_CONTEXT (context), NULL); + g_return_val_if_fail (G_IS_APP_INFO (info), NULL); + + class = G_APP_LAUNCH_CONTEXT_GET_CLASS (context); + + if (class->get_display == NULL) + return NULL; + + return class->get_display (context, info, files); +} + +/** + * g_app_launch_context_get_startup_notify_id: + * @context: a #GAppLaunchContext. + * @info: a #GAppInfo. + * @files: a #GList of files. + * + **/ +char * +g_app_launch_context_get_startup_notify_id (GAppLaunchContext *context, + GAppInfo *info, + GList *files) +{ + GAppLaunchContextClass *class; + + g_return_val_if_fail (G_IS_APP_LAUNCH_CONTEXT (context), NULL); + g_return_val_if_fail (G_IS_APP_INFO (info), NULL); + + class = G_APP_LAUNCH_CONTEXT_GET_CLASS (context); + + if (class->get_startup_notify_id == NULL) + return NULL; + + return class->get_startup_notify_id (context, info, files); +} + + +/** + * g_app_launch_context_get_startup_notify_id: + * @context: a #GAppLaunchContext. + * @startup_notify_id: a string containing the startup ID of the application. + * + **/ +void +g_app_launch_context_launch_failed (GAppLaunchContext *context, + const char *startup_notify_id) +{ + GAppLaunchContextClass *class; + + g_return_if_fail (G_IS_APP_LAUNCH_CONTEXT (context)); + g_return_if_fail (startup_notify_id != NULL); + + class = G_APP_LAUNCH_CONTEXT_GET_CLASS (context); + + if (class->launch_failed != NULL) + class->launch_failed (context, startup_notify_id); +} diff --git a/gio/gappinfo.h b/gio/gappinfo.h new file mode 100644 index 000000000..0a88349d4 --- /dev/null +++ b/gio/gappinfo.h @@ -0,0 +1,206 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#ifndef __G_APP_INFO_H__ +#define __G_APP_INFO_H__ + +#include <glib-object.h> +#include <gio/gicon.h> + +G_BEGIN_DECLS + +#define G_TYPE_APP_INFO (g_app_info_get_type ()) +#define G_APP_INFO(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), G_TYPE_APP_INFO, GAppInfo)) +#define G_IS_APP_INFO(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), G_TYPE_APP_INFO)) +#define G_APP_INFO_GET_IFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), G_TYPE_APP_INFO, GAppInfoIface)) + +#define G_TYPE_APP_LAUNCH_CONTEXT (g_app_launch_context_get_type ()) +#define G_APP_LAUNCH_CONTEXT(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_APP_LAUNCH_CONTEXT, GAppLaunchContext)) +#define G_APP_LAUNCH_CONTEXT_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), G_TYPE_APP_LAUNCH_CONTEXT, GAppLaunchContextClass)) +#define G_IS_APP_LAUNCH_CONTEXT(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_APP_LAUNCH_CONTEXT)) +#define G_IS_APP_LAUNCH_CONTEXT_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_APP_LAUNCH_CONTEXT)) +#define G_APP_LAUNCH_CONTEXT_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_TYPE_APP_LAUNCH_CONTEXT, GAppLaunchContextClass)) + +typedef enum { + G_APP_INFO_CREATE_FLAGS_NONE = 0, + G_APP_INFO_CREATE_NEEDS_TERMINAL = (1<<0) +} GAppInfoCreateFlags; + +typedef struct _GAppLaunchContext GAppLaunchContext; +typedef struct _GAppLaunchContextClass GAppLaunchContextClass; +typedef struct _GAppLaunchContextPrivate GAppLaunchContextPrivate; + +typedef struct _GAppInfo GAppInfo; /* Dummy typedef */ +typedef struct _GAppInfoIface GAppInfoIface; + +struct _GAppInfoIface +{ + GTypeInterface g_iface; + + /* Virtual Table */ + + GAppInfo * (*dup) (GAppInfo *appinfo); + gboolean (*equal) (GAppInfo *appinfo1, + GAppInfo *appinfo2); + const char * (*get_id) (GAppInfo *appinfo); + const char * (*get_name) (GAppInfo *appinfo); + const char * (*get_description) (GAppInfo *appinfo); + const char * (*get_executable) (GAppInfo *appinfo); + GIcon * (*get_icon) (GAppInfo *appinfo); + gboolean (*launch) (GAppInfo *appinfo, + GList *filenames, + GAppLaunchContext *launch_context, + GError **error); + gboolean (*supports_uris) (GAppInfo *appinfo); + gboolean (*launch_uris) (GAppInfo *appinfo, + GList *uris, + GAppLaunchContext *launch_context, + GError **error); + gboolean (*should_show) (GAppInfo *appinfo, + const char *desktop_env); + gboolean (*supports_xdg_startup_notify) (GAppInfo *appinfo); + + + /* For changing associations */ + gboolean (*set_as_default_for_type) (GAppInfo *appinfo, + const char *content_type, + GError **error); + gboolean (*set_as_default_for_extension) (GAppInfo *appinfo, + const char *extension, + GError **error); + gboolean (*add_supports_type) (GAppInfo *appinfo, + const char *content_type, + GError **error); + gboolean (*can_remove_supports_type) (GAppInfo *appinfo); + gboolean (*remove_supports_type) (GAppInfo *appinfo, + const char *content_type, + GError **error); + + /* Padding for future expansion */ + void (*_g_reserved1) (void); + void (*_g_reserved2) (void); + void (*_g_reserved3) (void); + void (*_g_reserved4) (void); + void (*_g_reserved5) (void); + void (*_g_reserved6) (void); + void (*_g_reserved7) (void); + void (*_g_reserved8) (void); + void (*_g_reserved9) (void); + void (*_g_reserved10) (void); +}; + +GType g_app_info_get_type (void) G_GNUC_CONST; +GType g_app_launch_context_get_type (void) G_GNUC_CONST; + +GAppInfo * g_app_info_create_from_commandline (const char *commandline, + const char *application_name, + GAppInfoCreateFlags flags, + GError **error); +GAppInfo * g_app_info_dup (GAppInfo *appinfo); +gboolean g_app_info_equal (GAppInfo *appinfo1, + GAppInfo *appinfo2); +const char *g_app_info_get_id (GAppInfo *appinfo); +const char *g_app_info_get_name (GAppInfo *appinfo); +const char *g_app_info_get_description (GAppInfo *appinfo); +const char *g_app_info_get_executable (GAppInfo *appinfo); +GIcon * g_app_info_get_icon (GAppInfo *appinfo); +gboolean g_app_info_launch (GAppInfo *appinfo, + GList *files, + GAppLaunchContext *launch_context, + GError **error); +gboolean g_app_info_supports_uris (GAppInfo *appinfo); +gboolean g_app_info_launch_uris (GAppInfo *appinfo, + GList *uris, + GAppLaunchContext *launch_context, + GError **error); +gboolean g_app_info_should_show (GAppInfo *appinfo, + const char *desktop_env); + +gboolean g_app_info_set_as_default_for_type (GAppInfo *appinfo, + const char *content_type, + GError **error); +gboolean g_app_info_set_as_default_for_extension (GAppInfo *appinfo, + const char *extension, + GError **error); +gboolean g_app_info_add_supports_type (GAppInfo *appinfo, + const char *content_type, + GError **error); +gboolean g_app_info_can_remove_supports_type (GAppInfo *appinfo); +gboolean g_app_info_remove_supports_type (GAppInfo *appinfo, + const char *content_type, + GError **error); + +GList * g_app_info_get_all (void); +GList * g_app_info_get_all_for_type (const char *content_type); +GAppInfo *g_app_info_get_default_for_type (const char *content_type, + gboolean must_support_uris); +GAppInfo *g_app_info_get_default_for_uri_scheme (const char *uri_scheme); + +/* TODO: missing operations: + add app as supporting a content type, but don't make it default + implement set_as_default_for_extension + + can_remove, remove (as in, don't support a specific mimetype) +*/ + + +struct _GAppLaunchContext +{ + GObject parent_instance; + + GAppLaunchContextPrivate *priv; +}; + +struct _GAppLaunchContextClass +{ + GObjectClass parent_class; + + char * (*get_display) (GAppLaunchContext *context, + GAppInfo *info, + GList *files); + char * (*get_startup_notify_id) (GAppLaunchContext *context, + GAppInfo *info, + GList *files); + void (*launch_failed) (GAppLaunchContext *context, + const char *startup_notify_id); + + /* Padding for future expansion */ + void (*_g_reserved1) (void); + void (*_g_reserved2) (void); + void (*_g_reserved3) (void); + void (*_g_reserved4) (void); + void (*_g_reserved5) (void); +}; + +GAppLaunchContext *g_app_launch_context_new (void); +char * g_app_launch_context_get_display (GAppLaunchContext *context, + GAppInfo *info, + GList *files); +char * g_app_launch_context_get_startup_notify_id (GAppLaunchContext *context, + GAppInfo *info, + GList *files); +void g_app_launch_context_launch_failed (GAppLaunchContext *context, + const char * startup_notify_id); + +G_END_DECLS + +#endif /* __G_APP_INFO_H__ */ diff --git a/gio/gasynchelper.c b/gio/gasynchelper.c new file mode 100644 index 000000000..b6a9352c7 --- /dev/null +++ b/gio/gasynchelper.c @@ -0,0 +1,163 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#include <config.h> + +#include "gasynchelper.h" + +static void +async_result_free (gpointer data) +{ + GAsyncResultData *res = data; + + if (res->error) + g_error_free (res->error); + + g_object_unref (res->async_object); + + g_free (res); +} + +void +_g_queue_async_result (GAsyncResultData *result, + gpointer async_object, + GError *error, + gpointer user_data, + GSourceFunc source_func) +{ + GSource *source; + + g_return_if_fail (G_IS_OBJECT (async_object)); + + result->async_object = g_object_ref (async_object); + result->user_data = user_data; + result->error = error; + + source = g_idle_source_new (); + g_source_set_priority (source, G_PRIORITY_DEFAULT); + g_source_set_callback (source, source_func, result, async_result_free); + g_source_attach (source, NULL); + g_source_unref (source); +} + +/************************************************************************* + * fd source * + ************************************************************************/ + +typedef struct +{ + GSource source; + GPollFD pollfd; + GCancellable *cancellable; + gulong cancelled_tag; +} FDSource; + +static gboolean +fd_source_prepare (GSource *source, + gint *timeout) +{ + FDSource *fd_source = (FDSource *)source; + *timeout = -1; + + return g_cancellable_is_cancelled (fd_source->cancellable); +} + +static gboolean +fd_source_check (GSource *source) +{ + FDSource *fd_source = (FDSource *)source; + + return + g_cancellable_is_cancelled (fd_source->cancellable) || + fd_source->pollfd.revents != 0; +} + +static gboolean +fd_source_dispatch (GSource *source, + GSourceFunc callback, + gpointer user_data) + +{ + GFDSourceFunc func = (GFDSourceFunc)callback; + FDSource *fd_source = (FDSource *)source; + + g_assert (func != NULL); + + return (*func) (user_data, fd_source->pollfd.revents, fd_source->pollfd.fd); +} + +static void +fd_source_finalize (GSource *source) +{ + FDSource *fd_source = (FDSource *)source; + + if (fd_source->cancelled_tag) + g_signal_handler_disconnect (fd_source->cancellable, + fd_source->cancelled_tag); + + if (fd_source->cancellable) + g_object_unref (fd_source->cancellable); +} + +static GSourceFuncs fd_source_funcs = { + fd_source_prepare, + fd_source_check, + fd_source_dispatch, + fd_source_finalize +}; + +/* Might be called on another thread */ +static void +fd_source_cancelled_cb (GCancellable *cancellable, + gpointer data) +{ + /* Wake up the mainloop in case we're waiting on async calls with FDSource */ + g_main_context_wakeup (NULL); +} + +GSource * +_g_fd_source_new (int fd, + gushort events, + GCancellable *cancellable) +{ + GSource *source; + FDSource *fd_source; + + source = g_source_new (&fd_source_funcs, sizeof (FDSource)); + fd_source = (FDSource *)source; + + if (cancellable) + fd_source->cancellable = g_object_ref (cancellable); + + fd_source->pollfd.fd = fd; + fd_source->pollfd.events = events; + g_source_add_poll (source, &fd_source->pollfd); + + if (cancellable) + fd_source->cancelled_tag = + g_signal_connect_data (cancellable, "cancelled", + (GCallback)fd_source_cancelled_cb, + NULL, NULL, + 0); + + return source; +} diff --git a/gio/gasynchelper.h b/gio/gasynchelper.h new file mode 100644 index 000000000..0f57a4093 --- /dev/null +++ b/gio/gasynchelper.h @@ -0,0 +1,53 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#ifndef __G_ASYNC_HELPER_H__ +#define __G_ASYNC_HELPER_H__ + +#include <glib-object.h> +#include "gcancellable.h" + +G_BEGIN_DECLS + +typedef struct { + gpointer async_object; + GError * error; + gpointer user_data; +} GAsyncResultData; + +typedef gboolean (*GFDSourceFunc) (gpointer user_data, + GIOCondition condition, + int fd); + +void _g_queue_async_result (GAsyncResultData *result, + gpointer async_object, + GError *error, + gpointer user_data, + GSourceFunc source_func); + +GSource *_g_fd_source_new (int fd, + gushort events, + GCancellable *cancellable); + +G_END_DECLS + +#endif /* __G_ASYNC_HELPER_H__ */ diff --git a/gio/gasyncresult.c b/gio/gasyncresult.c new file mode 100644 index 000000000..840931976 --- /dev/null +++ b/gio/gasyncresult.c @@ -0,0 +1,107 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#include <config.h> +#include "gasyncresult.h" +#include "glibintl.h" + +static void g_async_result_base_init (gpointer g_class); +static void g_async_result_class_init (gpointer g_class, + gpointer class_data); + +GType +g_async_result_get_type (void) +{ + static GType async_result_type = 0; + + if (! async_result_type) + { + static const GTypeInfo async_result_info = + { + sizeof (GAsyncResultIface), /* class_size */ + g_async_result_base_init, /* base_init */ + NULL, /* base_finalize */ + g_async_result_class_init, + NULL, /* class_finalize */ + NULL, /* class_data */ + 0, + 0, /* n_preallocs */ + NULL + }; + + async_result_type = + g_type_register_static (G_TYPE_INTERFACE, I_("GAsyncResult"), + &async_result_info, 0); + + g_type_interface_add_prerequisite (async_result_type, G_TYPE_OBJECT); + } + + return async_result_type; +} + +static void +g_async_result_class_init (gpointer g_class, + gpointer class_data) +{ +} + +static void +g_async_result_base_init (gpointer g_class) +{ +} + +/** + * g_async_result_get_user_data: + * @res: a #GAsyncResult. + * + * Returns: the user data for the given @res, or + * %NULL on failure. + **/ +gpointer +g_async_result_get_user_data (GAsyncResult *res) +{ + GAsyncResultIface *iface; + + g_return_val_if_fail (G_IS_ASYNC_RESULT (res), NULL); + + iface = G_ASYNC_RESULT_GET_IFACE (res); + + return (* iface->get_user_data) (res); +} + +/** + * g_async_result_get_source_object: + * @res: a #GAsyncResult. + * + * Returns: the source object for the @res. + **/ +GObject * +g_async_result_get_source_object (GAsyncResult *res) +{ + GAsyncResultIface *iface; + + g_return_val_if_fail (G_IS_ASYNC_RESULT (res), NULL); + + iface = G_ASYNC_RESULT_GET_IFACE (res); + + return (* iface->get_source_object) (res); +} diff --git a/gio/gasyncresult.h b/gio/gasyncresult.h new file mode 100644 index 000000000..26f867e02 --- /dev/null +++ b/gio/gasyncresult.h @@ -0,0 +1,59 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#ifndef __G_ASYNC_RESULT_H__ +#define __G_ASYNC_RESULT_H__ + +#include <glib-object.h> + +G_BEGIN_DECLS + +#define G_TYPE_ASYNC_RESULT (g_async_result_get_type ()) +#define G_ASYNC_RESULT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), G_TYPE_ASYNC_RESULT, GAsyncResult)) +#define G_IS_ASYNC_RESULT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), G_TYPE_ASYNC_RESULT)) +#define G_ASYNC_RESULT_GET_IFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), G_TYPE_ASYNC_RESULT, GAsyncResultIface)) + +typedef struct _GAsyncResult GAsyncResult; /* Dummy typedef */ +typedef struct _GAsyncResultIface GAsyncResultIface; + +typedef void (*GAsyncReadyCallback) (GObject *source_object, + GAsyncResult *res, + gpointer user_data); + +struct _GAsyncResultIface +{ + GTypeInterface g_iface; + + /* Virtual Table */ + + gpointer (*get_user_data) (GAsyncResult *async_result); + GObject * (*get_source_object) (GAsyncResult *async_result); +}; + +GType g_async_result_get_type (void) G_GNUC_CONST; + +gpointer g_async_result_get_user_data (GAsyncResult *res); +GObject *g_async_result_get_source_object (GAsyncResult *res); + +G_END_DECLS + +#endif /* __G_ASYNC_RESULT_H__ */ diff --git a/gio/gbufferedinputstream.c b/gio/gbufferedinputstream.c new file mode 100644 index 000000000..18469a041 --- /dev/null +++ b/gio/gbufferedinputstream.c @@ -0,0 +1,1230 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Christian Kellner <gicmo@gnome.org> + */ + +#include <config.h> + +#include "gbufferedinputstream.h" +#include "ginputstream.h" +#include "gsimpleasyncresult.h" +#include <string.h> + +#include "glibintl.h" + +#define DEFAULT_BUFFER_SIZE 4096 + +struct _GBufferedInputStreamPrivate { + guint8 *buffer; + gsize len; + gsize pos; + gsize end; + GAsyncReadyCallback outstanding_callback; +}; + +enum { + PROP_0, + PROP_BUFSIZE +}; + +static void g_buffered_input_stream_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec); + +static void g_buffered_input_stream_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec); +static void g_buffered_input_stream_finalize (GObject *object); + + +static gssize g_buffered_input_stream_skip (GInputStream *stream, + gsize count, + GCancellable *cancellable, + GError **error); +static void g_buffered_input_stream_skip_async (GInputStream *stream, + gsize count, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +static gssize g_buffered_input_stream_skip_finish (GInputStream *stream, + GAsyncResult *result, + GError **error); +static gssize g_buffered_input_stream_read (GInputStream *stream, + void *buffer, + gsize count, + GCancellable *cancellable, + GError **error); +static void g_buffered_input_stream_read_async (GInputStream *stream, + void *buffer, + gsize count, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +static gssize g_buffered_input_stream_read_finish (GInputStream *stream, + GAsyncResult *result, + GError **error); +static gssize g_buffered_input_stream_real_fill (GBufferedInputStream *stream, + gssize count, + GCancellable *cancellable, + GError **error); +static void g_buffered_input_stream_real_fill_async (GBufferedInputStream *stream, + gssize count, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +static gssize g_buffered_input_stream_real_fill_finish (GBufferedInputStream *stream, + GAsyncResult *result, + GError **error); + +static void compact_buffer (GBufferedInputStream *stream); + +G_DEFINE_TYPE (GBufferedInputStream, + g_buffered_input_stream, + G_TYPE_FILTER_INPUT_STREAM) + + +static void +g_buffered_input_stream_class_init (GBufferedInputStreamClass *klass) +{ + GObjectClass *object_class; + GInputStreamClass *istream_class; + GBufferedInputStreamClass *bstream_class; + + g_type_class_add_private (klass, sizeof (GBufferedInputStreamPrivate)); + + object_class = G_OBJECT_CLASS (klass); + object_class->get_property = g_buffered_input_stream_get_property; + object_class->set_property = g_buffered_input_stream_set_property; + object_class->finalize = g_buffered_input_stream_finalize; + + istream_class = G_INPUT_STREAM_CLASS (klass); + istream_class->skip = g_buffered_input_stream_skip; + istream_class->skip_async = g_buffered_input_stream_skip_async; + istream_class->skip_finish = g_buffered_input_stream_skip_finish; + istream_class->read = g_buffered_input_stream_read; + istream_class->read_async = g_buffered_input_stream_read_async; + istream_class->read_finish = g_buffered_input_stream_read_finish; + + bstream_class = G_BUFFERED_INPUT_STREAM_CLASS (klass); + bstream_class->fill = g_buffered_input_stream_real_fill; + bstream_class->fill_async = g_buffered_input_stream_real_fill_async; + bstream_class->fill_finish = g_buffered_input_stream_real_fill_finish; + + g_object_class_install_property (object_class, + PROP_BUFSIZE, + g_param_spec_uint ("buffer-size", + P_("Buffer Size"), + P_("The size of the backend buffer"), + 1, + G_MAXUINT, + DEFAULT_BUFFER_SIZE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | + G_PARAM_STATIC_NAME|G_PARAM_STATIC_NICK|G_PARAM_STATIC_BLURB)); + + +} + +/** + * g_buffered_input_stream_get_buffer_size: + * @stream: #GBufferedInputStream. + * + * Returns: the current buffer size, or -1 on error. + **/ +gsize +g_buffered_input_stream_get_buffer_size (GBufferedInputStream *stream) +{ + g_return_val_if_fail (G_IS_BUFFERED_INPUT_STREAM (stream), -1); + + return stream->priv->len; +} + +/** + * g_buffered_input_stream_set_buffer_size: + * @stream: #GBufferedInputStream. + * @size: a #gsize. + * + * Sets the size of the internal buffer of @stream to @size, or to the + * size of the contents of the buffer. The buffer can never be resized + * smaller than its current contents. + **/ +void +g_buffered_input_stream_set_buffer_size (GBufferedInputStream *stream, + gsize size) +{ + GBufferedInputStreamPrivate *priv; + gsize in_buffer; + guint8 *buffer; + + g_return_if_fail (G_IS_BUFFERED_INPUT_STREAM (stream)); + + priv = stream->priv; + + if (priv->buffer) + { + in_buffer = priv->end - priv->pos; + + /* Never resize smaller than current buffer contents */ + size = MAX (size, in_buffer); + + buffer = g_malloc (size); + memcpy (buffer, priv->buffer + priv->pos, in_buffer); + priv->len = size; + priv->pos = 0; + priv->end = in_buffer; + g_free (priv->buffer); + priv->buffer = buffer; + } + else + { + priv->len = size; + priv->pos = 0; + priv->end = 0; + priv->buffer = g_malloc (size); + } +} + +static void +g_buffered_input_stream_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GBufferedInputStreamPrivate *priv; + GBufferedInputStream *bstream; + + bstream = G_BUFFERED_INPUT_STREAM (object); + priv = bstream->priv; + + switch (prop_id) + { + case PROP_BUFSIZE: + g_buffered_input_stream_set_buffer_size (bstream, g_value_get_uint (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } + +} + +static void +g_buffered_input_stream_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GBufferedInputStreamPrivate *priv; + GBufferedInputStream *bstream; + + bstream = G_BUFFERED_INPUT_STREAM (object); + priv = bstream->priv; + + switch (prop_id) + { + + case PROP_BUFSIZE: + g_value_set_uint (value, priv->len); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } + +} + +static void +g_buffered_input_stream_finalize (GObject *object) +{ + GBufferedInputStreamPrivate *priv; + GBufferedInputStream *stream; + + stream = G_BUFFERED_INPUT_STREAM (object); + priv = stream->priv; + + g_free (priv->buffer); + + if (G_OBJECT_CLASS (g_buffered_input_stream_parent_class)->finalize) + (*G_OBJECT_CLASS (g_buffered_input_stream_parent_class)->finalize) (object); +} + +static void +g_buffered_input_stream_init (GBufferedInputStream *stream) +{ + stream->priv = G_TYPE_INSTANCE_GET_PRIVATE (stream, + G_TYPE_BUFFERED_INPUT_STREAM, + GBufferedInputStreamPrivate); +} + + +/** + * g_buffered_input_stream_new: + * @base_stream: a #GInputStream. + * + * Creates a new #GInputStream from the given @base_stream, with + * a buffer set to the default size (4 kilobytes). + * + * Returns: a #GInputStream for the given @base_stream. + **/ +GInputStream * +g_buffered_input_stream_new (GInputStream *base_stream) +{ + GInputStream *stream; + + g_return_val_if_fail (G_IS_INPUT_STREAM (base_stream), NULL); + + stream = g_object_new (G_TYPE_BUFFERED_INPUT_STREAM, + "base-stream", base_stream, + NULL); + + return stream; +} + +/** + * g_buffered_input_stream_new_sized: + * @base_stream: a #GOutputStream. + * @size: a #gsize. + * + * Creates a new #GBufferedInputStream from the given @base_stream, with + * a buffer set to @size. + * + * Returns: a #GInputStream. + **/ +GInputStream * +g_buffered_input_stream_new_sized (GInputStream *base_stream, + gsize size) +{ + GInputStream *stream; + + g_return_val_if_fail (G_IS_INPUT_STREAM (base_stream), NULL); + + stream = g_object_new (G_TYPE_BUFFERED_INPUT_STREAM, + "base-stream", base_stream, + "buffer-size", (guint)size, + NULL); + + return stream; +} + +/** + * g_buffered_input_stream_fill: + * @stream: #GBufferedInputStream. + * @count: the number of bytes that will be read from the stream. + * @cancellable: optional #GCancellable object, %NULL to ignore. + * @error: location to store the error occuring, or %NULL to ignore. + * + * Tries to read @count bytes from the stream into the buffer. + * Will block during this read. + * + * If @count is zero, returns zero and does nothing. A value of @count + * larger than %G_MAXSSIZE will cause a %G_IO_ERROR_INVALID_ARGUMENT error. + * + * On success, the number of bytes read into the buffer is returned. + * It is not an error if this is not the same as the requested size, as it + * can happen e.g. near the end of a file. Zero is returned on end of file + * (or if @count is zero), but never otherwise. + * + * If @cancellable is not %NULL, then the operation can be cancelled by + * triggering the cancellable object from another thread. If the operation + * was cancelled, the error %G_IO_ERROR_CANCELLED will be returned. If an + * operation was partially finished when the operation was cancelled the + * partial result will be returned, without an error. + * + * On error -1 is returned and @error is set accordingly. + * + * For the asynchronous, non-blocking, version of this function, see + * g_buffered_input_stream_fill_async(). + * + * Returns: the number of bytes read into @stream's buffer, up to @count, + * or -1 on error. + **/ +gssize +g_buffered_input_stream_fill (GBufferedInputStream *stream, + gssize count, + GCancellable *cancellable, + GError **error) +{ + GBufferedInputStreamClass *class; + GInputStream *input_stream; + gssize res; + + g_return_val_if_fail (G_IS_BUFFERED_INPUT_STREAM (stream), -1); + + input_stream = G_INPUT_STREAM (stream); + + if (g_input_stream_is_closed (input_stream)) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_CLOSED, + _("Stream is already closed")); + return -1; + } + + if (g_input_stream_has_pending (input_stream)) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_PENDING, + _("Stream has outstanding operation")); + return -1; + } + + g_input_stream_set_pending (input_stream, TRUE); + + if (cancellable) + g_push_current_cancellable (cancellable); + + class = G_BUFFERED_INPUT_STREAM_GET_CLASS (stream); + res = class->fill (stream, count, cancellable, error); + + if (cancellable) + g_pop_current_cancellable (cancellable); + + g_input_stream_set_pending (input_stream, FALSE); + + return res; +} + +static void +async_fill_callback_wrapper (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + GBufferedInputStream *stream = G_BUFFERED_INPUT_STREAM (source_object); + + g_input_stream_set_pending (G_INPUT_STREAM (stream), FALSE); + (*stream->priv->outstanding_callback) (source_object, res, user_data); + g_object_unref (stream); +} + +/** + * g_buffered_input_stream_fill_async: + * @stream: #GBufferedInputStream. + * @count: a #gssize. + * @io_priority: the io priority of the request. the io priority of the request. + * @cancellable: optional #GCancellable object + * @callback: a #GAsyncReadyCallback. + * @user_data: a #gpointer. + * + * Reads data into @stream's buffer asynchronously, up to @count size. + * @io_priority can be used to prioritize reads. For the synchronous + * version of this function, see g_buffered_input_stream_fill(). + * + **/ + +void +g_buffered_input_stream_fill_async (GBufferedInputStream *stream, + gssize count, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GBufferedInputStreamClass *class; + GSimpleAsyncResult *simple; + + g_return_if_fail (G_IS_BUFFERED_INPUT_STREAM (stream)); + + if (count == 0) + { + simple = g_simple_async_result_new (G_OBJECT (stream), + callback, + user_data, + g_buffered_input_stream_fill_async); + g_simple_async_result_complete_in_idle (simple); + g_object_unref (simple); + return; + } + + if (((gssize) count) < 0) + { + g_simple_async_report_error_in_idle (G_OBJECT (stream), + callback, + user_data, + G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, + _("Too large count value passed to g_input_stream_read_async")); + return; + } + + if (g_input_stream_is_closed (G_INPUT_STREAM (stream))) + { + g_simple_async_report_error_in_idle (G_OBJECT (stream), + callback, + user_data, + G_IO_ERROR, G_IO_ERROR_CLOSED, + _("Stream is already closed")); + return; + } + + if (g_input_stream_has_pending (G_INPUT_STREAM (stream))) + { + g_simple_async_report_error_in_idle (G_OBJECT (stream), + callback, + user_data, + G_IO_ERROR, G_IO_ERROR_PENDING, + _("Stream has outstanding operation")); + return; + } + + class = G_BUFFERED_INPUT_STREAM_GET_CLASS (stream); + + g_input_stream_set_pending (G_INPUT_STREAM (stream), TRUE); + stream->priv->outstanding_callback = callback; + g_object_ref (stream); + class->fill_async (stream, count, io_priority, cancellable, + async_fill_callback_wrapper, user_data); +} + +/** + * g_buffered_input_stream_fill_finished: + * @stream: a #GBufferedInputStream. + * @result: a #GAsyncResult. + * @error: a #GError. + * + * Finishes an asynchronous read. + * + * Returns: a #gssize of the read stream, or -1 on an error. + **/ +gssize +g_buffered_input_stream_fill_finish (GBufferedInputStream *stream, + GAsyncResult *result, + GError **error) +{ + GSimpleAsyncResult *simple; + GBufferedInputStreamClass *class; + + g_return_val_if_fail (G_IS_BUFFERED_INPUT_STREAM (stream), -1); + g_return_val_if_fail (G_IS_ASYNC_RESULT (result), -1); + + if (G_IS_SIMPLE_ASYNC_RESULT (result)) + { + simple = G_SIMPLE_ASYNC_RESULT (result); + if (g_simple_async_result_propagate_error (simple, error)) + return -1; + + /* Special case read of 0 bytes */ + if (g_simple_async_result_get_source_tag (simple) == g_buffered_input_stream_fill_async) + return 0; + } + + class = G_BUFFERED_INPUT_STREAM_GET_CLASS (stream); + return class->fill_finish (stream, result, error); +} + +/** + * g_buffered_input_stream_get_available: + * @stream: #GBufferedInputStream. + * + * Returns: size of the available stream. + **/ +gsize +g_buffered_input_stream_get_available (GBufferedInputStream *stream) +{ + g_return_val_if_fail (G_IS_BUFFERED_INPUT_STREAM (stream), -1); + + return stream->priv->end - stream->priv->pos; +} + +/** + * g_buffered_input_stream_peek: + * @stream: a #GBufferedInputStream. + * @buffer: a pointer to an allocated chunk of memory. + * @offset: a #gsize. + * @count: a #gsize. + * + * Returns: + **/ +gsize +g_buffered_input_stream_peek (GBufferedInputStream *stream, + void *buffer, + gsize offset, + gsize count) +{ + gsize available; + gsize end; + + g_return_val_if_fail (G_IS_BUFFERED_INPUT_STREAM (stream), -1); + g_return_val_if_fail (buffer != NULL, -1); + + available = g_buffered_input_stream_get_available (stream); + + if (offset > available) + return 0; + + end = MIN (offset + count, available); + count = end - offset; + + memcpy (buffer, stream->priv->buffer + stream->priv->pos + offset, count); + return count; +} + +static void +compact_buffer (GBufferedInputStream *stream) +{ + GBufferedInputStreamPrivate *priv; + gsize current_size; + + priv = stream->priv; + + current_size = priv->end - priv->pos; + + g_memmove (priv->buffer, + priv->buffer + priv->pos, + current_size); + + priv->pos = 0; + priv->end = current_size; +} + +static gssize +g_buffered_input_stream_real_fill (GBufferedInputStream *stream, + gssize count, + GCancellable *cancellable, + GError **error) +{ + GBufferedInputStreamPrivate *priv; + GInputStream *base_stream; + gssize nread; + gsize in_buffer; + + priv = stream->priv; + + if (count == -1) + count = priv->len; + + in_buffer = priv->end - priv->pos; + + /* Never fill more than can fit in the buffer */ + count = MIN (count, priv->len - in_buffer); + + /* If requested length does not fit at end, compact */ + if (priv->len - priv->end < count) + compact_buffer (stream); + + base_stream = G_FILTER_INPUT_STREAM (stream)->base_stream; + nread = g_input_stream_read (base_stream, + priv->buffer + priv->end, + count, + cancellable, + error); + + if (nread > 0) + priv->end += nread; + + return nread; +} + +static gssize +g_buffered_input_stream_skip (GInputStream *stream, + gsize count, + GCancellable *cancellable, + GError **error) +{ + GBufferedInputStream *bstream; + GBufferedInputStreamPrivate *priv; + GInputStream *base_stream; + gsize available, bytes_skipped; + gssize nread; + + bstream = G_BUFFERED_INPUT_STREAM (stream); + priv = bstream->priv; + + available = priv->end - priv->pos; + + if (count <= available) + { + priv->pos += count; + return count; + } + + /* Full request not available, skip all currently availbile and request refill for more */ + + priv->pos = 0; + priv->end = 0; + bytes_skipped = available; + count -= available; + + if (bytes_skipped > 0) + error = NULL; /* Ignore further errors if we already read some data */ + + if (count > priv->len) + { + /* Large request, shortcut buffer */ + + base_stream = G_FILTER_INPUT_STREAM (stream)->base_stream; + + nread = g_input_stream_skip (base_stream, + count, + cancellable, + error); + + if (nread < 0 && bytes_skipped == 0) + return -1; + + if (nread > 0) + bytes_skipped += nread; + + return bytes_skipped; + } + + g_input_stream_set_pending (stream, FALSE); /* to avoid already pending error */ + nread = g_buffered_input_stream_fill (bstream, priv->len, cancellable, error); + g_input_stream_set_pending (stream, TRUE); /* enable again */ + + if (nread < 0) + { + if (bytes_skipped == 0) + return -1; + else + return bytes_skipped; + } + + available = priv->end - priv->pos; + count = MIN (count, available); + + bytes_skipped += count; + priv->pos += count; + + return bytes_skipped; +} + +static gssize +g_buffered_input_stream_read (GInputStream *stream, + void *buffer, + gsize count, + GCancellable *cancellable, + GError **error) +{ + GBufferedInputStream *bstream; + GBufferedInputStreamPrivate *priv; + GInputStream *base_stream; + gsize available, bytes_read; + gssize nread; + + bstream = G_BUFFERED_INPUT_STREAM (stream); + priv = bstream->priv; + + available = priv->end - priv->pos; + + if (count <= available) + { + memcpy (buffer, priv->buffer + priv->pos, count); + priv->pos += count; + return count; + } + + /* Full request not available, read all currently availbile and request refill for more */ + + memcpy (buffer, priv->buffer + priv->pos, available); + priv->pos = 0; + priv->end = 0; + bytes_read = available; + count -= available; + + if (bytes_read > 0) + error = NULL; /* Ignore further errors if we already read some data */ + + if (count > priv->len) + { + /* Large request, shortcut buffer */ + + base_stream = G_FILTER_INPUT_STREAM (stream)->base_stream; + + nread = g_input_stream_read (base_stream, + (char *)buffer + bytes_read, + count, + cancellable, + error); + + if (nread < 0 && bytes_read == 0) + return -1; + + if (nread > 0) + bytes_read += nread; + + return bytes_read; + } + + g_input_stream_set_pending (stream, FALSE); /* to avoid already pending error */ + nread = g_buffered_input_stream_fill (bstream, priv->len, cancellable, error); + g_input_stream_set_pending (stream, TRUE); /* enable again */ + if (nread < 0) + { + if (bytes_read == 0) + return -1; + else + return bytes_read; + } + + available = priv->end - priv->pos; + count = MIN (count, available); + + memcpy ((char *)buffer + bytes_read, (char *)priv->buffer + priv->pos, count); + bytes_read += count; + priv->pos += count; + + return bytes_read; +} + +/* ************************** */ +/* Async stuff implementation */ +/* ************************** */ + +static void +fill_async_callback (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + GError *error; + gssize res; + GSimpleAsyncResult *simple; + + simple = user_data; + + error = NULL; + res = g_input_stream_read_finish (G_INPUT_STREAM (source_object), + result, &error); + + g_simple_async_result_set_op_res_gssize (simple, res); + if (res == -1) + { + g_simple_async_result_set_from_error (simple, error); + g_error_free (error); + } + + /* Complete immediately, not in idle, since we're already in a mainloop callout */ + g_simple_async_result_complete (simple); + g_object_unref (simple); +} + +static void +g_buffered_input_stream_real_fill_async (GBufferedInputStream *stream, + gssize count, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GBufferedInputStreamPrivate *priv; + GInputStream *base_stream; + GSimpleAsyncResult *simple; + gsize in_buffer; + + priv = stream->priv; + + if (count == -1) + count = priv->len; + + in_buffer = priv->end - priv->pos; + + /* Never fill more than can fit in the buffer */ + count = MIN (count, priv->len - in_buffer); + + /* If requested length does not fit at end, compact */ + if (priv->len - priv->end < count) + compact_buffer (stream); + + simple = g_simple_async_result_new (G_OBJECT (stream), + callback, user_data, + g_buffered_input_stream_real_fill_async); + + base_stream = G_FILTER_INPUT_STREAM (stream)->base_stream; + g_input_stream_read_async (base_stream, + priv->buffer + priv->end, + count, + io_priority, + cancellable, + fill_async_callback, + simple); +} + +static gssize +g_buffered_input_stream_real_fill_finish (GBufferedInputStream *stream, + GAsyncResult *result, + GError **error) +{ + GSimpleAsyncResult *simple; + gssize nread; + + simple = G_SIMPLE_ASYNC_RESULT (result); + g_assert (g_simple_async_result_get_source_tag (simple) == g_buffered_input_stream_real_fill_async); + + nread = g_simple_async_result_get_op_res_gssize (simple); + return nread; +} + +typedef struct { + gssize bytes_read; + gssize count; + void *buffer; +} ReadAsyncData; + +static void +free_read_async_data (gpointer _data) +{ + ReadAsyncData *data = _data; + g_slice_free (ReadAsyncData, data); +} + +static void +large_read_callback (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (user_data); + ReadAsyncData *data; + GError *error; + gssize nread; + + data = g_simple_async_result_get_op_res_gpointer (simple); + + error = NULL; + nread = g_input_stream_read_finish (G_INPUT_STREAM (source_object), + result, &error); + + /* Only report the error if we've not already read some data */ + if (nread < 0 && data->bytes_read == 0) + g_simple_async_result_set_from_error (simple, error); + + if (nread > 0) + data->bytes_read += nread; + + if (error) + g_error_free (error); + + /* Complete immediately, not in idle, since we're already in a mainloop callout */ + g_simple_async_result_complete (simple); + g_object_unref (simple); +} + +static void +read_fill_buffer_callback (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (user_data); + GBufferedInputStream *bstream; + GBufferedInputStreamPrivate *priv; + ReadAsyncData *data; + GError *error; + gssize nread; + gsize available; + + bstream = G_BUFFERED_INPUT_STREAM (source_object); + priv = bstream->priv; + + g_input_stream_set_pending (G_INPUT_STREAM (bstream), TRUE); /* enable again */ + + data = g_simple_async_result_get_op_res_gpointer (simple); + + error = NULL; + nread = g_buffered_input_stream_fill_finish (bstream, + result, &error); + + if (nread < 0 && data->bytes_read == 0) + g_simple_async_result_set_from_error (simple, error); + + + if (nread > 0) + { + available = priv->end - priv->pos; + data->count = MIN (data->count, available); + + memcpy ((char *)data->buffer + data->bytes_read, (char *)priv->buffer + priv->pos, data->count); + data->bytes_read += data->count; + priv->pos += data->count; + } + + if (error) + g_error_free (error); + + /* Complete immediately, not in idle, since we're already in a mainloop callout */ + g_simple_async_result_complete (simple); + g_object_unref (simple); +} + +static void +g_buffered_input_stream_read_async (GInputStream *stream, + void *buffer, + gsize count, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GBufferedInputStream *bstream; + GBufferedInputStreamPrivate *priv; + GInputStream *base_stream; + gsize available; + GSimpleAsyncResult *simple; + ReadAsyncData *data; + + bstream = G_BUFFERED_INPUT_STREAM (stream); + priv = bstream->priv; + + data = g_slice_new (ReadAsyncData); + data->buffer = buffer; + data->bytes_read = 0; + simple = g_simple_async_result_new (G_OBJECT (stream), + callback, user_data, + g_buffered_input_stream_read_async); + g_simple_async_result_set_op_res_gpointer (simple, data, free_read_async_data); + + available = priv->end - priv->pos; + + if (count <= available) + { + memcpy (buffer, priv->buffer + priv->pos, count); + priv->pos += count; + data->bytes_read = count; + + g_simple_async_result_complete_in_idle (simple); + g_object_unref (simple); + return; + } + + + /* Full request not available, read all currently availbile and request refill for more */ + + memcpy (buffer, priv->buffer + priv->pos, available); + priv->pos = 0; + priv->end = 0; + + count -= available; + + data->bytes_read = available; + data->count = count; + + if (count > priv->len) + { + /* Large request, shortcut buffer */ + + base_stream = G_FILTER_INPUT_STREAM (stream)->base_stream; + + g_input_stream_read_async (base_stream, + (char *)buffer + data->bytes_read, + count, + io_priority, cancellable, + large_read_callback, + simple); + } + else + { + g_input_stream_set_pending (stream, FALSE); /* to avoid already pending error */ + g_buffered_input_stream_fill_async (bstream, priv->len, + io_priority, cancellable, + read_fill_buffer_callback, simple); + } +} + +static gssize +g_buffered_input_stream_read_finish (GInputStream *stream, + GAsyncResult *result, + GError **error) +{ + GSimpleAsyncResult *simple; + ReadAsyncData *data; + + simple = G_SIMPLE_ASYNC_RESULT (result); + + g_assert (g_simple_async_result_get_source_tag (simple) == g_buffered_input_stream_read_async); + + data = g_simple_async_result_get_op_res_gpointer (simple); + + return data->bytes_read; +} + +typedef struct { + gssize bytes_skipped; + gssize count; +} SkipAsyncData; + +static void +free_skip_async_data (gpointer _data) +{ + SkipAsyncData *data = _data; + g_slice_free (SkipAsyncData, data); +} + +static void +large_skip_callback (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (user_data); + SkipAsyncData *data; + GError *error; + gssize nread; + + data = g_simple_async_result_get_op_res_gpointer (simple); + + error = NULL; + nread = g_input_stream_skip_finish (G_INPUT_STREAM (source_object), + result, &error); + + /* Only report the error if we've not already read some data */ + if (nread < 0 && data->bytes_skipped == 0) + g_simple_async_result_set_from_error (simple, error); + + if (nread > 0) + data->bytes_skipped += nread; + + if (error) + g_error_free (error); + + /* Complete immediately, not in idle, since we're already in a mainloop callout */ + g_simple_async_result_complete (simple); + g_object_unref (simple); +} + +static void +skip_fill_buffer_callback (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (user_data); + GBufferedInputStream *bstream; + GBufferedInputStreamPrivate *priv; + SkipAsyncData *data; + GError *error; + gssize nread; + gsize available; + + bstream = G_BUFFERED_INPUT_STREAM (source_object); + priv = bstream->priv; + + g_input_stream_set_pending (G_INPUT_STREAM (bstream), TRUE); /* enable again */ + + data = g_simple_async_result_get_op_res_gpointer (simple); + + error = NULL; + nread = g_buffered_input_stream_fill_finish (bstream, + result, &error); + + if (nread < 0 && data->bytes_skipped == 0) + g_simple_async_result_set_from_error (simple, error); + + + if (nread > 0) + { + available = priv->end - priv->pos; + data->count = MIN (data->count, available); + + data->bytes_skipped += data->count; + priv->pos += data->count; + } + + if (error) + g_error_free (error); + + /* Complete immediately, not in idle, since we're already in a mainloop callout */ + g_simple_async_result_complete (simple); + g_object_unref (simple); +} + +static void +g_buffered_input_stream_skip_async (GInputStream *stream, + gsize count, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GBufferedInputStream *bstream; + GBufferedInputStreamPrivate *priv; + GInputStream *base_stream; + gsize available; + GSimpleAsyncResult *simple; + SkipAsyncData *data; + + bstream = G_BUFFERED_INPUT_STREAM (stream); + priv = bstream->priv; + + data = g_slice_new (SkipAsyncData); + data->bytes_skipped = 0; + simple = g_simple_async_result_new (G_OBJECT (stream), + callback, user_data, + g_buffered_input_stream_skip_async); + g_simple_async_result_set_op_res_gpointer (simple, data, free_skip_async_data); + + available = priv->end - priv->pos; + + if (count <= available) + { + priv->pos += count; + data->bytes_skipped = count; + + g_simple_async_result_complete_in_idle (simple); + g_object_unref (simple); + return; + } + + + /* Full request not available, skip all currently availbile and request refill for more */ + + priv->pos = 0; + priv->end = 0; + + count -= available; + + data->bytes_skipped = available; + data->count = count; + + if (count > priv->len) + { + /* Large request, shortcut buffer */ + + base_stream = G_FILTER_INPUT_STREAM (stream)->base_stream; + + g_input_stream_skip_async (base_stream, + count, + io_priority, cancellable, + large_skip_callback, + simple); + } + else + { + g_input_stream_set_pending (stream, FALSE); /* to avoid already pending error */ + g_buffered_input_stream_fill_async (bstream, priv->len, + io_priority, cancellable, + skip_fill_buffer_callback, simple); + } +} + +static gssize +g_buffered_input_stream_skip_finish (GInputStream *stream, + GAsyncResult *result, + GError **error) +{ + GSimpleAsyncResult *simple; + SkipAsyncData *data; + + simple = G_SIMPLE_ASYNC_RESULT (result); + + g_assert (g_simple_async_result_get_source_tag (simple) == g_buffered_input_stream_skip_async); + + data = g_simple_async_result_get_op_res_gpointer (simple); + + return data->bytes_skipped; +} + +/* vim: ts=2 sw=2 et */ diff --git a/gio/gbufferedinputstream.h b/gio/gbufferedinputstream.h new file mode 100644 index 000000000..eab35b5b5 --- /dev/null +++ b/gio/gbufferedinputstream.h @@ -0,0 +1,110 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Christian Kellner <gicmo@gnome.org> + */ + +#ifndef __G_BUFFERED_INPUT_STREAM_H__ +#define __G_BUFFERED_INPUT_STREAM_H__ + +#include <glib-object.h> +#include <gio/gfilterinputstream.h> + +G_BEGIN_DECLS + +#define G_TYPE_BUFFERED_INPUT_STREAM (g_buffered_input_stream_get_type ()) +#define G_BUFFERED_INPUT_STREAM(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_BUFFERED_INPUT_STREAM, GBufferedInputStream)) +#define G_BUFFERED_INPUT_STREAM_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), G_TYPE_BUFFERED_INPUT_STREAM, GBufferedInputStreamClass)) +#define G_IS_BUFFERED_INPUT_STREAM(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_BUFFERED_INPUT_STREAM)) +#define G_IS_BUFFERED_INPUT_STREAM_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_BUFFERED_INPUT_STREAM)) +#define G_BUFFERED_INPUT_STREAM_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_TYPE_BUFFERED_INPUT_STREAM, GBufferedInputStreamClass)) + +typedef struct _GBufferedInputStream GBufferedInputStream; +typedef struct _GBufferedInputStreamClass GBufferedInputStreamClass; +typedef struct _GBufferedInputStreamPrivate GBufferedInputStreamPrivate; + +struct _GBufferedInputStream +{ + GFilterInputStream parent; + + /*< private >*/ + GBufferedInputStreamPrivate *priv; +}; + +struct _GBufferedInputStreamClass +{ + GFilterInputStreamClass parent_class; + + gssize (* fill) (GBufferedInputStream *stream, + gssize count, + GCancellable *cancellable, + GError **error); + + /* Async ops: (optional in derived classes) */ + void (* fill_async) (GBufferedInputStream *stream, + gssize count, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + gssize (* fill_finish) (GBufferedInputStream *stream, + GAsyncResult *result, + GError **error); + + /* Padding for future expansion */ + void (*_g_reserved1) (void); + void (*_g_reserved2) (void); + void (*_g_reserved3) (void); + void (*_g_reserved4) (void); + void (*_g_reserved5) (void); +}; + + +GType g_buffered_input_stream_get_type (void) G_GNUC_CONST; +GInputStream* g_buffered_input_stream_new (GInputStream *base_stream); +GInputStream* g_buffered_input_stream_new_sized (GInputStream *base_stream, + gsize size); + +gsize g_buffered_input_stream_get_buffer_size (GBufferedInputStream *stream); +void g_buffered_input_stream_set_buffer_size (GBufferedInputStream *stream, + gsize size); +gsize g_buffered_input_stream_get_available (GBufferedInputStream *stream); +gsize g_buffered_input_stream_peek (GBufferedInputStream *stream, + void *buffer, + gsize offset, + gsize count); + +gssize g_buffered_input_stream_fill (GBufferedInputStream *stream, + gssize count, + GCancellable *cancellable, + GError **error); +void g_buffered_input_stream_fill_async (GBufferedInputStream *stream, + gssize count, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +gssize g_buffered_input_stream_fill_finish (GBufferedInputStream *stream, + GAsyncResult *result, + GError **error); + + +G_END_DECLS + +#endif /* __G_BUFFERED_INPUT_STREAM_H__ */ diff --git a/gio/gbufferedoutputstream.c b/gio/gbufferedoutputstream.c new file mode 100644 index 000000000..210ccfb0d --- /dev/null +++ b/gio/gbufferedoutputstream.c @@ -0,0 +1,734 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Christian Kellner <gicmo@gnome.org> + */ + +#include <config.h> + +#include "gbufferedoutputstream.h" +#include "goutputstream.h" +#include "gsimpleasyncresult.h" +#include "string.h" + +#include "glibintl.h" + +#define DEFAULT_BUFFER_SIZE 4096 + +struct _GBufferedOutputStreamPrivate { + guint8 *buffer; + gsize len; + goffset pos; + gboolean auto_grow; +}; + +enum { + PROP_0, + PROP_BUFSIZE +}; + +static void g_buffered_output_stream_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec); + +static void g_buffered_output_stream_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec); +static void g_buffered_output_stream_finalize (GObject *object); + + +static gssize g_buffered_output_stream_write (GOutputStream *stream, + const void *buffer, + gsize count, + GCancellable *cancellable, + GError **error); +static gboolean g_buffered_output_stream_flush (GOutputStream *stream, + GCancellable *cancellable, + GError **error); +static gboolean g_buffered_output_stream_close (GOutputStream *stream, + GCancellable *cancellable, + GError **error); + +static void g_buffered_output_stream_write_async (GOutputStream *stream, + const void *buffer, + gsize count, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer data); +static gssize g_buffered_output_stream_write_finish (GOutputStream *stream, + GAsyncResult *result, + GError **error); +static void g_buffered_output_stream_flush_async (GOutputStream *stream, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer data); +static gboolean g_buffered_output_stream_flush_finish (GOutputStream *stream, + GAsyncResult *result, + GError **error); +static void g_buffered_output_stream_close_async (GOutputStream *stream, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer data); +static gboolean g_buffered_output_stream_close_finish (GOutputStream *stream, + GAsyncResult *result, + GError **error); + +G_DEFINE_TYPE (GBufferedOutputStream, + g_buffered_output_stream, + G_TYPE_FILTER_OUTPUT_STREAM) + + +static void +g_buffered_output_stream_class_init (GBufferedOutputStreamClass *klass) +{ + GObjectClass *object_class; + GOutputStreamClass *ostream_class; + + g_type_class_add_private (klass, sizeof (GBufferedOutputStreamPrivate)); + + object_class = G_OBJECT_CLASS (klass); + object_class->get_property = g_buffered_output_stream_get_property; + object_class->set_property = g_buffered_output_stream_set_property; + object_class->finalize = g_buffered_output_stream_finalize; + + ostream_class = G_OUTPUT_STREAM_CLASS (klass); + ostream_class->write = g_buffered_output_stream_write; + ostream_class->flush = g_buffered_output_stream_flush; + ostream_class->close = g_buffered_output_stream_close; + ostream_class->write_async = g_buffered_output_stream_write_async; + ostream_class->write_finish = g_buffered_output_stream_write_finish; + ostream_class->flush_async = g_buffered_output_stream_flush_async; + ostream_class->flush_finish = g_buffered_output_stream_flush_finish; + ostream_class->close_async = g_buffered_output_stream_close_async; + ostream_class->close_finish = g_buffered_output_stream_close_finish; + + g_object_class_install_property (object_class, + PROP_BUFSIZE, + g_param_spec_uint ("buffer-size", + P_("Buffer Size"), + P_("The size of the backend buffer"), + 1, + G_MAXUINT, + DEFAULT_BUFFER_SIZE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_NAME|G_PARAM_STATIC_NICK|G_PARAM_STATIC_BLURB)); + +} + +/** + * g_buffered_output_stream_get_buffer_size: + * @stream: a #GBufferedOutputStream. + * + * Returns: the current size of the buffer. + **/ +gsize +g_buffered_output_stream_get_buffer_size (GBufferedOutputStream *stream) +{ + g_return_val_if_fail (G_IS_BUFFERED_OUTPUT_STREAM (stream), -1); + + return stream->priv->len; +} + +/** + * g_buffered_output_stream_set_buffer_size: + * @stream: a #GBufferedOutputStream. + * @size: a #gsize. + * + * Sets the size of the internal buffer to @size. + * + **/ +void +g_buffered_output_stream_set_buffer_size (GBufferedOutputStream *stream, + gsize size) +{ + GBufferedOutputStreamPrivate *priv; + guint8 *buffer; + + g_return_if_fail (G_IS_BUFFERED_OUTPUT_STREAM (stream)); + + priv = stream->priv; + + if (priv->buffer) + { + size = MAX (size, priv->pos); + + buffer = g_malloc (size); + memcpy (buffer, priv->buffer, priv->pos); + g_free (priv->buffer); + priv->buffer = buffer; + priv->len = size; + /* Keep old pos */ + } + else + { + priv->buffer = g_malloc (size); + priv->len = size; + priv->pos = 0; + } +} + +/** + * g_buffered_output_stream_get_auto_grow: + * @stream: a #GBufferedOutputStream. + * + * Returns: %TRUE if the @stream's buffer automatically grows, + * %FALSE otherwise. + **/ +gboolean +g_buffered_output_stream_get_auto_grow (GBufferedOutputStream *stream) +{ + g_return_val_if_fail (G_IS_BUFFERED_OUTPUT_STREAM (stream), FALSE); + + return stream->priv->auto_grow; +} + +/** + * g_buffered_output_stream_set_auto_grow: + * @stream: a #GBufferedOutputStream. + * @auto_grow: a boolean. + * + * Sets whether or not the @stream's buffer should automatically grow. + * + **/ +void +g_buffered_output_stream_set_auto_grow (GBufferedOutputStream *stream, + gboolean auto_grow) +{ + g_return_if_fail (G_IS_BUFFERED_OUTPUT_STREAM (stream)); + + stream->priv->auto_grow = auto_grow; +} + +static void +g_buffered_output_stream_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GBufferedOutputStream *buffered_stream; + GBufferedOutputStreamPrivate *priv; + + buffered_stream = G_BUFFERED_OUTPUT_STREAM (object); + priv = buffered_stream->priv; + + switch (prop_id) + { + + case PROP_BUFSIZE: + g_buffered_output_stream_set_buffer_size (buffered_stream, g_value_get_uint (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } + +} + +static void +g_buffered_output_stream_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GBufferedOutputStream *buffered_stream; + GBufferedOutputStreamPrivate *priv; + + buffered_stream = G_BUFFERED_OUTPUT_STREAM (object); + priv = buffered_stream->priv; + + switch (prop_id) + { + + case PROP_BUFSIZE: + g_value_set_uint (value, priv->len); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } + +} + +static void +g_buffered_output_stream_finalize (GObject *object) +{ + GBufferedOutputStream *stream; + GBufferedOutputStreamPrivate *priv; + + stream = G_BUFFERED_OUTPUT_STREAM (object); + priv = stream->priv; + + g_free (priv->buffer); + + if (G_OBJECT_CLASS (g_buffered_output_stream_parent_class)->finalize) + (*G_OBJECT_CLASS (g_buffered_output_stream_parent_class)->finalize) (object); +} + +static void +g_buffered_output_stream_init (GBufferedOutputStream *stream) +{ + stream->priv = G_TYPE_INSTANCE_GET_PRIVATE (stream, + G_TYPE_BUFFERED_OUTPUT_STREAM, + GBufferedOutputStreamPrivate); + +} + +/** + * g_buffered_output_stream_new: + * @base_stream: a #GOutputStream. + * + * Returns: a #GOutputStream for the given @base_stream. + **/ +GOutputStream * +g_buffered_output_stream_new (GOutputStream *base_stream) +{ + GOutputStream *stream; + + g_return_val_if_fail (G_IS_OUTPUT_STREAM (base_stream), NULL); + + stream = g_object_new (G_TYPE_BUFFERED_OUTPUT_STREAM, + "base-stream", base_stream, + NULL); + + return stream; +} + +/** + * g_buffered_output_stream_new_sized: + * @base_stream: a #GOutputStream. + * @size: a #gsize. + * + * Returns: a #GOutputStream with an internal buffer set to @size. + **/ +GOutputStream * +g_buffered_output_stream_new_sized (GOutputStream *base_stream, + guint size) +{ + GOutputStream *stream; + + g_return_val_if_fail (G_IS_OUTPUT_STREAM (base_stream), NULL); + + stream = g_object_new (G_TYPE_BUFFERED_OUTPUT_STREAM, + "base-stream", base_stream, + "buffer-size", size, + NULL); + + return stream; +} + +static gboolean +flush_buffer (GBufferedOutputStream *stream, + GCancellable *cancellable, + GError **error) +{ + GBufferedOutputStreamPrivate *priv; + GOutputStream *base_stream; + gboolean res; + gsize bytes_written; + gsize count; + + priv = stream->priv; + bytes_written = 0; + base_stream = G_FILTER_OUTPUT_STREAM (stream)->base_stream; + + g_return_val_if_fail (G_IS_OUTPUT_STREAM (base_stream), FALSE); + + res = g_output_stream_write_all (base_stream, + priv->buffer, + priv->pos, + &bytes_written, + cancellable, + error); + + count = priv->pos - bytes_written; + + if (count > 0) + g_memmove (priv->buffer, priv->buffer + bytes_written, count); + + priv->pos -= bytes_written; + + return res; +} + +static gssize +g_buffered_output_stream_write (GOutputStream *stream, + const void *buffer, + gsize count, + GCancellable *cancellable, + GError **error) +{ + GBufferedOutputStream *bstream; + GBufferedOutputStreamPrivate *priv; + gboolean res; + gsize n; + gsize new_size; + + bstream = G_BUFFERED_OUTPUT_STREAM (stream); + priv = bstream->priv; + + n = priv->len - priv->pos; + + if (priv->auto_grow && n < count) + { + new_size = MAX (priv->len * 2, priv->len + count); + g_buffered_output_stream_set_buffer_size (bstream, new_size); + } + else if (n == 0) + { + res = flush_buffer (bstream, cancellable, error); + + if (res == FALSE) + return -1; + } + + n = priv->len - priv->pos; + + count = MIN (count, n); + memcpy (priv->buffer + priv->pos, buffer, count); + priv->pos += count; + + return count; +} + +static gboolean +g_buffered_output_stream_flush (GOutputStream *stream, + GCancellable *cancellable, + GError **error) +{ + GBufferedOutputStream *bstream; + GBufferedOutputStreamPrivate *priv; + GOutputStream *base_stream; + gboolean res; + + bstream = G_BUFFERED_OUTPUT_STREAM (stream); + priv = bstream->priv; + base_stream = G_FILTER_OUTPUT_STREAM (stream)->base_stream; + + res = flush_buffer (bstream, cancellable, error); + + if (res == FALSE) { + return FALSE; + } + + res = g_output_stream_flush (base_stream, + cancellable, + error); + return res; +} + +static gboolean +g_buffered_output_stream_close (GOutputStream *stream, + GCancellable *cancellable, + GError **error) +{ + GBufferedOutputStream *bstream; + GBufferedOutputStreamPrivate *priv; + GOutputStream *base_stream; + gboolean res; + + bstream = G_BUFFERED_OUTPUT_STREAM (stream); + priv = bstream->priv; + base_stream = G_FILTER_OUTPUT_STREAM (bstream)->base_stream; + + res = flush_buffer (bstream, cancellable, error); + + /* report the first error but still close the stream */ + if (res) + { + res = g_output_stream_close (base_stream, + cancellable, + error); + } + else + { + g_output_stream_close (base_stream, + cancellable, + NULL); + } + + return res; +} + +/* ************************** */ +/* Async stuff implementation */ +/* ************************** */ + +/* TODO: This should be using the base class async ops, not threads */ + +typedef struct { + + guint flush_stream : 1; + guint close_stream : 1; + +} FlushData; + +static void +free_flush_data (gpointer data) +{ + g_slice_free (FlushData, data); +} + +/* This function is used by all three (i.e. + * _write, _flush, _close) functions since + * all of them will need to flush the buffer + * and so closing and writing is just a special + * case of flushing + some addition stuff */ +static void +flush_buffer_thread (GSimpleAsyncResult *result, + GObject *object, + GCancellable *cancellable) +{ + GBufferedOutputStream *stream; + GOutputStream *base_stream; + FlushData *fdata; + gboolean res; + GError *error = NULL; + + stream = G_BUFFERED_OUTPUT_STREAM (object); + fdata = g_simple_async_result_get_op_res_gpointer (result); + base_stream = G_FILTER_OUTPUT_STREAM (stream)->base_stream; + + res = flush_buffer (stream, cancellable, &error); + + /* if flushing the buffer didn't work don't even bother + * to flush the stream but just report that error */ + if (res && fdata->flush_stream) + { + res = g_output_stream_flush (base_stream, + cancellable, + &error); + } + + if (fdata->close_stream) + { + + /* if flushing the buffer or the stream returned + * an error report that first error but still try + * close the stream */ + if (res == FALSE) + { + g_output_stream_close (base_stream, + cancellable, + NULL); + } + else + { + res = g_output_stream_close (base_stream, + cancellable, + &error); + } + + } + + if (res == FALSE) + { + g_simple_async_result_set_from_error (result, error); + g_error_free (error); + } +} + +typedef struct { + + FlushData fdata; + + gsize count; + const void *buffer; + +} WriteData; + +static void +free_write_data (gpointer data) +{ + g_slice_free (WriteData, data); +} + +static void +g_buffered_output_stream_write_async (GOutputStream *stream, + const void *buffer, + gsize count, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer data) +{ + GBufferedOutputStream *buffered_stream; + GBufferedOutputStreamPrivate *priv; + GSimpleAsyncResult *res; + WriteData *wdata; + + buffered_stream = G_BUFFERED_OUTPUT_STREAM (stream); + priv = buffered_stream->priv; + + wdata = g_slice_new (WriteData); + wdata->count = count; + wdata->buffer = buffer; + + res = g_simple_async_result_new (G_OBJECT (stream), + callback, + data, + g_buffered_output_stream_write_async); + + g_simple_async_result_set_op_res_gpointer (res, wdata, free_write_data); + + /* if we have space left directly call the + * callback (from idle) otherwise schedule a buffer + * flush in the thread. In both cases the actual + * copying of the data to the buffer will be done in + * the write_finish () func since that should + * be fast enough */ + if (priv->len - priv->pos > 0) + { + g_simple_async_result_complete_in_idle (res); + } + else + { + wdata->fdata.flush_stream = FALSE; + wdata->fdata.close_stream = FALSE; + g_simple_async_result_run_in_thread (res, + flush_buffer_thread, + io_priority, + cancellable); + g_object_unref (res); + } +} + +static gssize +g_buffered_output_stream_write_finish (GOutputStream *stream, + GAsyncResult *result, + GError **error) +{ + GBufferedOutputStreamPrivate *priv; + GBufferedOutputStream *buffered_stream; + GSimpleAsyncResult *simple; + WriteData *wdata; + gssize count; + + simple = G_SIMPLE_ASYNC_RESULT (result); + buffered_stream = G_BUFFERED_OUTPUT_STREAM (stream); + priv = buffered_stream->priv; + + g_assert (g_simple_async_result_get_source_tag (simple) == + g_buffered_output_stream_write_async); + + wdata = g_simple_async_result_get_op_res_gpointer (simple); + + /* Now do the real copying of data to the buffer */ + count = priv->len - priv->pos; + count = MIN (wdata->count, count); + + memcpy (priv->buffer + priv->pos, wdata->buffer, count); + + priv->pos += count; + + return count; +} + +static void +g_buffered_output_stream_flush_async (GOutputStream *stream, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer data) +{ + GSimpleAsyncResult *res; + FlushData *fdata; + + fdata = g_slice_new (FlushData); + fdata->flush_stream = TRUE; + fdata->close_stream = FALSE; + + res = g_simple_async_result_new (G_OBJECT (stream), + callback, + data, + g_buffered_output_stream_flush_async); + + g_simple_async_result_set_op_res_gpointer (res, fdata, free_flush_data); + + g_simple_async_result_run_in_thread (res, + flush_buffer_thread, + io_priority, + cancellable); + g_object_unref (res); +} + +static gboolean +g_buffered_output_stream_flush_finish (GOutputStream *stream, + GAsyncResult *result, + GError **error) +{ + GSimpleAsyncResult *simple; + + simple = G_SIMPLE_ASYNC_RESULT (result); + + g_assert (g_simple_async_result_get_source_tag (simple) == + g_buffered_output_stream_flush_async); + + return TRUE; +} + +static void +g_buffered_output_stream_close_async (GOutputStream *stream, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer data) +{ + GSimpleAsyncResult *res; + FlushData *fdata; + + fdata = g_slice_new (FlushData); + fdata->close_stream = TRUE; + + res = g_simple_async_result_new (G_OBJECT (stream), + callback, + data, + g_buffered_output_stream_close_async); + + g_simple_async_result_set_op_res_gpointer (res, fdata, free_flush_data); + + g_simple_async_result_run_in_thread (res, + flush_buffer_thread, + io_priority, + cancellable); + g_object_unref (res); +} + +static gboolean +g_buffered_output_stream_close_finish (GOutputStream *stream, + GAsyncResult *result, + GError **error) +{ + GSimpleAsyncResult *simple; + + simple = G_SIMPLE_ASYNC_RESULT (result); + + g_assert (g_simple_async_result_get_source_tag (simple) == + g_buffered_output_stream_flush_async); + + return TRUE; +} + +/* vim: ts=2 sw=2 et */ diff --git a/gio/gbufferedoutputstream.h b/gio/gbufferedoutputstream.h new file mode 100644 index 000000000..54086448c --- /dev/null +++ b/gio/gbufferedoutputstream.h @@ -0,0 +1,76 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Christian Kellner <gicmo@gnome.org> + */ + +#ifndef __G_BUFFERED_OUTPUT_STREAM_H__ +#define __G_BUFFERED_OUTPUT_STREAM_H__ + +#include <glib-object.h> +#include <gio/gfilteroutputstream.h> + +G_BEGIN_DECLS + +#define G_TYPE_BUFFERED_OUTPUT_STREAM (g_buffered_output_stream_get_type ()) +#define G_BUFFERED_OUTPUT_STREAM(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_BUFFERED_OUTPUT_STREAM, GBufferedOutputStream)) +#define G_BUFFERED_OUTPUT_STREAM_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), G_TYPE_BUFFERED_OUTPUT_STREAM, GBufferedOutputStreamClass)) +#define G_IS_BUFFERED_OUTPUT_STREAM(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_BUFFERED_OUTPUT_STREAM)) +#define G_IS_BUFFERED_OUTPUT_STREAM_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_BUFFERED_OUTPUT_STREAM)) +#define G_BUFFERED_OUTPUT_STREAM_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_TYPE_BUFFERED_OUTPUT_STREAM, GBufferedOutputStreamClass)) + +typedef struct _GBufferedOutputStream GBufferedOutputStream; +typedef struct _GBufferedOutputStreamClass GBufferedOutputStreamClass; +typedef struct _GBufferedOutputStreamPrivate GBufferedOutputStreamPrivate; + +struct _GBufferedOutputStream +{ + GFilterOutputStream parent; + + /*< protected >*/ + GBufferedOutputStreamPrivate *priv; +}; + +struct _GBufferedOutputStreamClass +{ + GOutputStreamClass parent_class; + + /* Padding for future expansion */ + void (*_g_reserved1) (void); + void (*_g_reserved2) (void); + void (*_g_reserved3) (void); + void (*_g_reserved4) (void); + void (*_g_reserved5) (void); +}; + + +GType g_buffered_output_stream_get_type (void) G_GNUC_CONST; +GOutputStream* g_buffered_output_stream_new (GOutputStream *base_stream); +GOutputStream* g_buffered_output_stream_new_sized (GOutputStream *base_stream, + guint size); +gsize g_buffered_output_stream_get_buffer_size (GBufferedOutputStream *stream); +void g_buffered_output_stream_set_buffer_size (GBufferedOutputStream *stream, + gsize size); +gboolean g_buffered_output_stream_get_auto_grow (GBufferedOutputStream *stream); +void g_buffered_output_stream_set_auto_grow (GBufferedOutputStream *stream, + gboolean auto_grow); + +G_END_DECLS + +#endif /* __G_BUFFERED_OUTPUT_STREAM_H__ */ diff --git a/gio/gcancellable.c b/gio/gcancellable.c new file mode 100644 index 000000000..c55737a32 --- /dev/null +++ b/gio/gcancellable.c @@ -0,0 +1,322 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#include <config.h> +#include <unistd.h> +#include <fcntl.h> +#include <gioerror.h> +#include "gcancellable.h" + +#include "glibintl.h" + + +/* + * GCancellable is a thread-safe operation cancellation stack used + * throughout GIO to allow for cancellation of asynchronous operations. + */ + +enum { + CANCELLED, + LAST_SIGNAL +}; + +struct _GCancellable +{ + GObject parent_instance; + + guint cancelled : 1; + guint allocated_pipe : 1; + int cancel_pipe[2]; +}; + +static guint signals[LAST_SIGNAL] = { 0 }; + +G_DEFINE_TYPE (GCancellable, g_cancellable, G_TYPE_OBJECT); + +static GStaticPrivate current_cancellable = G_STATIC_PRIVATE_INIT; +G_LOCK_DEFINE_STATIC(cancellable); + +static void +g_cancellable_finalize (GObject *object) +{ + GCancellable *cancellable = G_CANCELLABLE (object); + + if (cancellable->cancel_pipe[0] != -1) + close (cancellable->cancel_pipe[0]); + + if (cancellable->cancel_pipe[1] != -1) + close (cancellable->cancel_pipe[1]); + + if (G_OBJECT_CLASS (g_cancellable_parent_class)->finalize) + (*G_OBJECT_CLASS (g_cancellable_parent_class)->finalize) (object); +} + +static void +g_cancellable_class_init (GCancellableClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->finalize = g_cancellable_finalize; + + signals[CANCELLED] = + g_signal_new (I_("cancelled"), + G_TYPE_FROM_CLASS (gobject_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GCancellableClass, cancelled), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + +} + +static void +set_fd_nonblocking (int fd) +{ +#ifdef F_GETFL + glong fcntl_flags; + fcntl_flags = fcntl (fd, F_GETFL); + +#ifdef O_NONBLOCK + fcntl_flags |= O_NONBLOCK; +#else + fcntl_flags |= O_NDELAY; +#endif + + fcntl (fd, F_SETFL, fcntl_flags); +#endif +} + +static void +g_cancellable_open_pipe (GCancellable *cancellable) +{ + if (pipe (cancellable->cancel_pipe) == 0) + { + /* Make them nonblocking, just to be sure we don't block + * on errors and stuff + */ + set_fd_nonblocking (cancellable->cancel_pipe[0]); + set_fd_nonblocking (cancellable->cancel_pipe[1]); + } + else + g_warning ("Failed to create pipe for GCancellable. Out of file descriptors?"); +} + +static void +g_cancellable_init (GCancellable *cancellable) +{ + cancellable->cancel_pipe[0] = -1; + cancellable->cancel_pipe[1] = -1; +} + +/** + * g_cancellable_new: + * + * Returns: a new #GCancellable object. + **/ +GCancellable * +g_cancellable_new (void) +{ + return g_object_new (G_TYPE_CANCELLABLE, NULL); +} + +/** + * g_push_current_cancellable: + * @cancellable: optional #GCancellable object, %NULL to ignore. + * + * Pushes @cancellable onto the cancellable stack. + **/ +void +g_push_current_cancellable (GCancellable *cancellable) +{ + GSList *l; + + g_assert (cancellable != NULL); + + l = g_static_private_get (¤t_cancellable); + l = g_slist_prepend (l, cancellable); + g_static_private_set (¤t_cancellable, l, NULL); +} + +/** + * g_pop_current_cancellable: + * @cancellable: optional #GCancellable object, %NULL to ignore. + * + * Pops @cancellable off the cancellable stack if @cancellable + * is on the top of the stack. + **/ +void +g_pop_current_cancellable (GCancellable *cancellable) +{ + GSList *l; + + l = g_static_private_get (¤t_cancellable); + + g_assert (l != NULL); + g_assert (l->data == cancellable); + + l = g_slist_delete_link (l, l); + g_static_private_set (¤t_cancellable, l, NULL); +} + +/** + * g_cancellable_get_current: + * + * Returns: a #GCancellable from the top of the stack, or %NULL + * if the stack is empty. + **/ +GCancellable * +g_cancellable_get_current (void) +{ + GSList *l; + + l = g_static_private_get (¤t_cancellable); + if (l == NULL) + return NULL; + + return G_CANCELLABLE (l->data); +} + +/** + * g_cancellable_reset: + * @cancellable: a #GCancellable object. + * + * Resets @cancellable to its uncancelled state. + * + **/ +void +g_cancellable_reset (GCancellable *cancellable) +{ + g_return_if_fail (G_IS_CANCELLABLE (cancellable)); + + G_LOCK(cancellable); + /* Make sure we're not leaving old cancel state around */ + if (cancellable->cancelled) + { + char ch; + if (cancellable->cancel_pipe[0] != -1) + read (cancellable->cancel_pipe[0], &ch, 1); + cancellable->cancelled = FALSE; + } + G_UNLOCK(cancellable); +} + +/** + * g_cancellable_is_cancelled: + * @cancellable: a #GCancellable or NULL. + * + * Returns: %TRUE if @cancellable is is cancelled, + * FALSE if called with %NULL or if item is not cancelled. + **/ +gboolean +g_cancellable_is_cancelled (GCancellable *cancellable) +{ + return cancellable != NULL && cancellable->cancelled; +} + +/** + * g_cancellable_set_error_if_cancelled: + * @cancellable: a #GCancellable object. + * @error: #GError to append error state to. + * + * Sets the current error to notify that the operation was cancelled. + * + * Returns: %TRUE if @cancellable was cancelled, %FALSE if it was not. + **/ +gboolean +g_cancellable_set_error_if_cancelled (GCancellable *cancellable, + GError **error) +{ + if (g_cancellable_is_cancelled (cancellable)) + { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_CANCELLED, + _("Operation was cancelled")); + return TRUE; + } + + return FALSE; +} + +/** + * g_cancellable_get_fd: + * @cancellable: a #GCancellable. + * + * Returns: A valid file descriptor. -1 if the file descriptor + * is not supported, or on errors. + **/ +int +g_cancellable_get_fd (GCancellable *cancellable) +{ + int fd; + if (cancellable == NULL) + return -1; + + G_LOCK(cancellable); + if (!cancellable->allocated_pipe) + { + cancellable->allocated_pipe = TRUE; + g_cancellable_open_pipe (cancellable); + } + + fd = cancellable->cancel_pipe[0]; + G_UNLOCK(cancellable); + + return fd; +} + +/** + * g_cancellable_cancel: + * @cancellable: a #GCancellable object. + * + * Will set @cancellable to cancelled, and will emit the CANCELLED + * signal. This function is thread-safe. + * + **/ +void +g_cancellable_cancel (GCancellable *cancellable) +{ + gboolean cancel; + + cancel = FALSE; + + G_LOCK(cancellable); + if (cancellable != NULL && + !cancellable->cancelled) + { + char ch = 'x'; + cancel = TRUE; + cancellable->cancelled = TRUE; + if (cancellable->cancel_pipe[1] != -1) + write (cancellable->cancel_pipe[1], &ch, 1); + } + G_UNLOCK(cancellable); + + if (cancel) + { + g_object_ref (cancellable); + g_signal_emit (cancellable, signals[CANCELLED], 0); + g_object_unref (cancellable); + } +} + + diff --git a/gio/gcancellable.h b/gio/gcancellable.h new file mode 100644 index 000000000..a6229da4f --- /dev/null +++ b/gio/gcancellable.h @@ -0,0 +1,74 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#ifndef __G_CANCELLABLE_H__ +#define __G_CANCELLABLE_H__ + +#include <glib-object.h> + +G_BEGIN_DECLS + +#define G_TYPE_CANCELLABLE (g_cancellable_get_type ()) +#define G_CANCELLABLE(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_CANCELLABLE, GCancellable)) +#define G_CANCELLABLE_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), G_TYPE_CANCELLABLE, GCancellableClass)) +#define G_IS_CANCELLABLE(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_CANCELLABLE)) +#define G_IS_CANCELLABLE_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_CANCELLABLE)) +#define G_CANCELLABLE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_TYPE_CANCELLABLE, GCancellableClass)) + +typedef struct _GCancellable GCancellable; +typedef struct _GCancellableClass GCancellableClass; + +struct _GCancellableClass +{ + GObjectClass parent_class; + + void (* cancelled) (GCancellable *cancellable); + + /* Padding for future expansion */ + void (*_g_reserved1) (void); + void (*_g_reserved2) (void); + void (*_g_reserved3) (void); + void (*_g_reserved4) (void); + void (*_g_reserved5) (void); +}; + +GType g_cancellable_get_type (void) G_GNUC_CONST; + +GCancellable *g_cancellable_new (void); + +/* These are only safe to call inside a cancellable op */ +gboolean g_cancellable_is_cancelled (GCancellable *cancellable); +gboolean g_cancellable_set_error_if_cancelled (GCancellable *cancellable, + GError **error); +int g_cancellable_get_fd (GCancellable *cancellable); +GCancellable *g_cancellable_get_current (void); +void g_push_current_cancellable (GCancellable *cancellable); +void g_pop_current_cancellable (GCancellable *cancellable); +void g_cancellable_reset (GCancellable *cancellable); + + +/* This is safe to call from another thread */ +void g_cancellable_cancel (GCancellable *cancellable); + +G_END_DECLS + +#endif /* __G_CANCELLABLE_H__ */ diff --git a/gio/gcontenttype.c b/gio/gcontenttype.c new file mode 100644 index 000000000..5952cacdb --- /dev/null +++ b/gio/gcontenttype.c @@ -0,0 +1,861 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#include <config.h> + +#include <sys/types.h> +#include <dirent.h> +#include <string.h> +#include <stdio.h> + +#include "gcontenttypeprivate.h" +#include "glibintl.h" + +/* A content type is a platform specific string that defines the type + of a file. On unix it is a mime type, on win32 it is an extension string + like ".doc", ".txt" or a percieved string like "audio". Such strings + can be looked up in the registry at HKEY_CLASSES_ROOT. +*/ + +#ifdef G_OS_WIN32 + +#include <windows.h> + +static char * +get_registry_classes_key (const char *subdir, + const wchar_t *key_name) +{ + wchar_t *wc_key; + HKEY reg_key = NULL; + DWORD key_type; + DWORD nbytes; + char *value_utf8; + + value_utf8 = NULL; + + nbytes = 0; + wc_key = g_utf8_to_utf16 (subdir, -1, NULL, NULL, NULL); + if (RegOpenKeyExW (HKEY_CLASSES_ROOT, wc_key, 0, + KEY_QUERY_VALUE, ®_key) == ERROR_SUCCESS && + RegQueryValueExW (reg_key, key_name, 0, + &key_type, NULL, &nbytes) == ERROR_SUCCESS && + key_type == REG_SZ) + { + wchar_t *wc_temp = g_new (wchar_t, (nbytes+1)/2 + 1); + RegQueryValueExW (reg_key, key_name, 0, + &key_type, (LPBYTE) wc_temp, &nbytes); + wc_temp[nbytes/2] = '\0'; + value_utf8 = g_utf16_to_utf8 (wc_temp, -1, NULL, NULL, NULL); + g_free (wc_temp); + } + g_free (wc_key); + + if (reg_key != NULL) + RegCloseKey (reg_key); + + return value_utf8; +} + +/** + * g_content_type_equals: + * @type1: a content type string. + * @type2: a content type string. + * + * Compares two content types for equality. + * + * Returns: %TRUE if the two strings are identical or equivalent, + * %FALSE otherwise. + **/ +gboolean +g_content_type_equals (const char *type1, + const char *type2) +{ + char *progid1, *progid2; + gboolean res; + + g_return_val_if_fail (type1 != NULL, FALSE); + g_return_val_if_fail (type2 != NULL, FALSE); + + if (g_ascii_strcasecmp (type1, type2) == 0) + return TRUE; + + res = FALSE; + progid1 = get_registry_classes_key (type1, NULL); + progid2 = get_registry_classes_key (type2, NULL); + if (progid1 != NULL && progid2 != NULL && + strcmp (progid1, progid2) == 0) + res = TRUE; + g_free (progid1); + g_free (progid2); + + return res; +} + +/** + * g_content_type_is_a: + * @type: a content type string. a content type string. + * @supertype: a string. + * + * Determines if @type is a subset of @supertype. + * + * Returns: %TRUE if @type is a kind of @supertype, + * %FALSE otherwise. + **/ +gboolean +g_content_type_is_a (const char *type, + const char *supertype) +{ + gboolean res; + char *value_utf8; + + g_return_val_if_fail (type != NULL, FALSE); + g_return_val_if_fail (supertype != NULL, FALSE); + + if (g_content_type_equals (type, supertype)) + return TRUE; + + res = FALSE; + value_utf8 = get_registry_classes_key (type, L"PerceivedType"); + if (value_utf8 && strcmp (value_utf8, supertype) == 0) + res = TRUE; + g_free (value_utf8); + + return res; +} + +/** + * g_content_type_is_unknown: + * @type: a content type string. a content type string. + * + * Returns: + **/ +gboolean +g_content_type_is_unknown (const char *type) +{ + g_return_val_if_fail (type != NULL, FALSE); + + return strcmp ("*", type) == 0; +} + +/** + * g_content_type_get_description: + * @type: a content type string. a content type string. + * + * Returns: a short description of the content type @type. + **/ +char * +g_content_type_get_description (const char *type) +{ + char *progid; + char *description; + + g_return_val_if_fail (type != NULL, NULL); + + progid = get_registry_classes_key (type, NULL); + if (progid) + { + description = get_registry_classes_key (progid, NULL); + g_free (progid); + + if (description) + return description; + } + + if (g_content_type_is_unknown (type)) + return g_strdup (_("Unknown type")); + return g_strdup_printf (_("%s filetype"), type); +} + +/** + * g_content_type_get_mime_type: + * @type: a content type string. a content type string. + * + * Returns: the registered mime-type for the given @type. + **/ +char * +g_content_type_get_mime_type (const char *type) +{ + char *mime; + + g_return_val_if_fail (type != NULL, NULL); + + mime = get_registry_classes_key (type, L"Content Type"); + if (mime) + return mime; + else if (g_content_type_is_unknown (type)) + return g_strdup ("application/octet-stream"); + else if (*type == '.') + return g_strdup_printf ("application/x-ext-%s", type+1); + /* TODO: Map "image" to "image/ *", etc? */ + + return g_strdup ("application/octet-stream"); +} + +/** + * g_content_type_get_icon: + * @type: a content type string. a content type string. + * + * Returns: #GIcon corresponding to the content type. + **/ +GIcon * +g_content_type_get_icon (const char *type) +{ + g_return_val_if_fail (type != NULL, NULL); + + /* TODO: How do we represent icons??? + In the registry they are the default value of + HKEY_CLASSES_ROOT\<progid>\DefaultIcon with typical values like: + <type>: <value> + REG_EXPAND_SZ: %SystemRoot%\System32\Wscript.exe,3 + REG_SZ: shimgvw.dll,3 + */ + return NULL; +} + +/** + * g_content_type_can_be_executable: + * @type: a content type string. + * + * Returns: %TRUE if the file type corresponds to something that + * can be executable, %FALSE otherwise. Note that for instance + * things like textfiles can be executables (i.e. scripts) + **/ +gboolean +g_content_type_can_be_executable (const char *type) +{ + g_return_val_if_fail (type != NULL, FALSE); + + if (strcmp (type, ".exe") == 0 || + strcmp (type, ".com") == 0 || + strcmp (type, ".bat") == 0) + return TRUE; + return FALSE; +} + +static gboolean +looks_like_text (const guchar *data, gsize data_size) +{ + gsize i; + guchar c; + for (i = 0; i < data_size; i++) + { + c = data[i]; + if (g_ascii_iscntrl (c) && !g_ascii_isspace (c)) + return FALSE; + } + return TRUE; +} + +/** + * g_content_type_guess: + * @filename: a string. + * @data: a stream of data. + * @data_size: the size of @data. + * @result_uncertain: a flag indicating the certainty of the + * result. + * + * Returns: a string indicating a guessed content type for the + * given data. If the function is uncertain, @result_uncertain + * will be set to %TRUE. + **/ +char * +g_content_type_guess (const char *filename, + const guchar *data, + gsize data_size, + gboolean *result_uncertain) +{ + char *basename; + char *type; + char *dot; + + type = NULL; + + if (filename) + { + basename = g_path_get_basename (filename); + dot = strrchr (basename, '.'); + if (dot) + type = g_strdup (dot); + g_free (basename); + } + + if (type) + return type; + + if (data && looks_like_text (data, data_size)) + return g_strdup (".txt"); + + return g_strdup ("*"); +} + +/** + * g_content_types_get_registered: + * + * Returns: #GList of the registered content types. + **/ +GList * +g_content_types_get_registered (void) +{ + DWORD index; + wchar_t keyname[256]; + DWORD key_len; + char *key_utf8; + GList *types; + + types = NULL; + index = 0; + key_len = 256; + while (RegEnumKeyExW(HKEY_CLASSES_ROOT, + index, + keyname, + &key_len, + NULL, + NULL, + NULL, + NULL) == ERROR_SUCCESS) + { + key_utf8 = g_utf16_to_utf8 (keyname, -1, NULL, NULL, NULL); + if (key_utf8) + { + if (*key_utf8 == '.') + types = g_list_prepend (types, key_utf8); + else + g_free (key_utf8); + } + index++; + key_len = 256; + } + + return g_list_reverse (types); +} + +#else /* !G_OS_WIN32 - Unix specific version */ + +#define XDG_PREFIX _gio_xdg +#include "xdgmime/xdgmime.h" + +/* We lock this mutex whenever we modify global state in this module. */ +G_LOCK_DEFINE_STATIC (gio_xdgmime); + +gsize +_g_unix_content_type_get_sniff_len (void) +{ + gsize size; + + G_LOCK (gio_xdgmime); + size = xdg_mime_get_max_buffer_extents (); + G_UNLOCK (gio_xdgmime); + + return size; +} + +char * +_g_unix_content_type_unalias (const char *type) +{ + char *res; + + G_LOCK (gio_xdgmime); + res = g_strdup (xdg_mime_unalias_mime_type (type)); + G_UNLOCK (gio_xdgmime); + + return res; +} + +char ** +_g_unix_content_type_get_parents (const char *type) +{ + const char *umime; + const char **parents; + GPtrArray *array; + int i; + + array = g_ptr_array_new (); + + G_LOCK (gio_xdgmime); + + umime = xdg_mime_unalias_mime_type (type); + g_ptr_array_add (array, g_strdup (umime)); + + parents = xdg_mime_get_mime_parents (umime); + for (i = 0; parents && parents[i] != NULL; i++) + g_ptr_array_add (array, g_strdup (parents[i])); + + G_UNLOCK (gio_xdgmime); + + g_ptr_array_add (array, NULL); + + return (char **)g_ptr_array_free (array, FALSE); +} + +gboolean +g_content_type_equals (const char *type1, + const char *type2) +{ + gboolean res; + + g_return_val_if_fail (type1 != NULL, FALSE); + g_return_val_if_fail (type2 != NULL, FALSE); + + G_LOCK (gio_xdgmime); + res = xdg_mime_mime_type_equal (type1, type2); + G_UNLOCK (gio_xdgmime); + + return res; +} + +gboolean +g_content_type_is_a (const char *type, + const char *supertype) +{ + gboolean res; + + g_return_val_if_fail (type != NULL, FALSE); + g_return_val_if_fail (supertype != NULL, FALSE); + + G_LOCK (gio_xdgmime); + res = xdg_mime_mime_type_subclass (type, supertype); + G_UNLOCK (gio_xdgmime); + + return res; +} + +gboolean +g_content_type_is_unknown (const char *type) +{ + g_return_val_if_fail (type != NULL, FALSE); + + return strcmp (XDG_MIME_TYPE_UNKNOWN, type) == 0; +} + + +typedef enum { + MIME_TAG_TYPE_OTHER, + MIME_TAG_TYPE_COMMENT +} MimeTagType; + +typedef struct { + int current_type; + int current_lang_level; + int comment_lang_level; + char *comment; +} MimeParser; + + +static int +language_level (const char *lang) +{ + const char * const *lang_list; + int i; + + /* The returned list is sorted from most desirable to least + desirable and always contains the default locale "C". */ + lang_list = g_get_language_names (); + + for (i = 0; lang_list[i]; i++) + if (strcmp (lang_list[i], lang) == 0) + return 1000-i; + + return 0; +} + +static void +mime_info_start_element (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + gpointer user_data, + GError **error) +{ + int i; + const char *lang; + MimeParser *parser = user_data; + + if (strcmp (element_name, "comment") == 0) + { + lang = "C"; + for (i = 0; attribute_names[i]; i++) + if (strcmp (attribute_names[i], "xml:lang") == 0) + { + lang = attribute_values[i]; + break; + } + + parser->current_lang_level = language_level (lang); + parser->current_type = MIME_TAG_TYPE_COMMENT; + } + else + parser->current_type = MIME_TAG_TYPE_OTHER; + +} + +static void +mime_info_end_element (GMarkupParseContext *context, + const gchar *element_name, + gpointer user_data, + GError **error) +{ + MimeParser *parser = user_data; + + parser->current_type = MIME_TAG_TYPE_OTHER; +} + +static void +mime_info_text (GMarkupParseContext *context, + const gchar *text, + gsize text_len, + gpointer user_data, + GError **error) +{ + MimeParser *parser = user_data; + + if (parser->current_type == MIME_TAG_TYPE_COMMENT && + parser->current_lang_level > parser->comment_lang_level) + { + g_free (parser->comment); + parser->comment = g_strndup (text, text_len); + parser->comment_lang_level = parser->current_lang_level; + } +} + +static char * +load_comment_for_mime_helper (const char *dir, const char *basename) +{ + GMarkupParseContext *context; + char *filename, *data; + gsize len; + gboolean res; + MimeParser parse_data = {0}; + GMarkupParser parser = { + mime_info_start_element, + mime_info_end_element, + mime_info_text + }; + + filename = g_build_filename (dir, "mime", basename, NULL); + + res = g_file_get_contents (filename, &data, &len, NULL); + g_free (filename); + if (!res) + return NULL; + + context = g_markup_parse_context_new (&parser, 0, &parse_data, NULL); + res = g_markup_parse_context_parse (context, data, len, NULL); + g_free (data); + g_markup_parse_context_free (context); + + if (!res) + return NULL; + + return parse_data.comment; +} + + +static char * +load_comment_for_mime (const char *mimetype) +{ + const char * const* dirs; + char *basename; + char *comment; + int i; + + basename = g_strdup_printf ("%s.xml", mimetype); + + comment = load_comment_for_mime_helper (g_get_user_data_dir (), basename); + if (comment) + { + g_free (basename); + return comment; + } + + dirs = g_get_system_data_dirs (); + + for (i = 0; dirs[i] != NULL; i++) + { + comment = load_comment_for_mime_helper (dirs[i], basename); + if (comment) + { + g_free (basename); + return comment; + } + } + g_free (basename); + + return g_strdup_printf (_("%s type"), mimetype); +} + +char * +g_content_type_get_description (const char *type) +{ + static GHashTable *type_comment_cache = NULL; + char *comment; + + g_return_val_if_fail (type != NULL, NULL); + + G_LOCK (gio_xdgmime); + if (type_comment_cache == NULL) + type_comment_cache = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); + + comment = g_hash_table_lookup (type_comment_cache, type); + comment = g_strdup (comment); + G_UNLOCK (gio_xdgmime); + + if (comment != NULL) + return comment; + + comment = load_comment_for_mime (type); + + G_LOCK (gio_xdgmime); + g_hash_table_insert (type_comment_cache, + g_strdup (type), + g_strdup (comment)); + G_UNLOCK (gio_xdgmime); + + return comment; +} + +char * +g_content_type_get_mime_type (const char *type) +{ + g_return_val_if_fail (type != NULL, NULL); + + return g_strdup (type); +} + +GIcon * +g_content_type_get_icon (const char *type) +{ + g_return_val_if_fail (type != NULL, NULL); + + /* TODO: Implement */ + return NULL; +} + +/** + * g_content_type_can_be_executable: + * @type: a content type string. + * + * Returns: %TRUE if the file type corresponds to something that + * can be executable, %FALSE otherwise. Note that for instance + * things like textfiles can be executables (i.e. scripts) + **/ +gboolean +g_content_type_can_be_executable (const char *type) +{ + g_return_val_if_fail (type != NULL, FALSE); + + if (g_content_type_is_a (type, "application/x-executable") || + g_content_type_is_a (type, "text/plain")) + return TRUE; + + return FALSE; +} + +static gboolean +looks_like_text (const guchar *data, gsize data_size) +{ + gsize i; + for (i = 0; i < data_size; i++) + { + if g_ascii_iscntrl (data[i]) + return FALSE; + } + return TRUE; +} + +char * +g_content_type_guess (const char *filename, + const guchar *data, + gsize data_size, + gboolean *result_uncertain) +{ + char *basename; + const char *name_mimetypes[10], *sniffed_mimetype; + char *mimetype; + int i; + int n_name_mimetypes; + int sniffed_prio; + + sniffed_prio = 0; + n_name_mimetypes = 0; + sniffed_mimetype = XDG_MIME_TYPE_UNKNOWN; + + if (result_uncertain) + *result_uncertain = FALSE; + + G_LOCK (gio_xdgmime); + + if (filename) + { + basename = g_path_get_basename (filename); + n_name_mimetypes = xdg_mime_get_mime_types_from_file_name (basename, name_mimetypes, 10); + g_free (basename); + } + + /* Got an extension match, and no conflicts. This is it. */ + if (n_name_mimetypes == 1) + { + G_UNLOCK (gio_xdgmime); + return g_strdup (name_mimetypes[0]); + } + + if (data) + { + sniffed_mimetype = xdg_mime_get_mime_type_for_data (data, data_size, &sniffed_prio); + if (sniffed_mimetype == XDG_MIME_TYPE_UNKNOWN && + data && + looks_like_text (data, data_size)) + sniffed_mimetype = "text/plain"; + } + + if (n_name_mimetypes == 0) + { + if (sniffed_mimetype == XDG_MIME_TYPE_UNKNOWN && + result_uncertain) + *result_uncertain = TRUE; + + mimetype = g_strdup (sniffed_mimetype); + } + else + { + mimetype = NULL; + if (sniffed_mimetype != XDG_MIME_TYPE_UNKNOWN) + { + if (sniffed_prio >= 80) /* High priority sniffing match, use that */ + mimetype = g_strdup (sniffed_mimetype); + else + { + /* There are conflicts between the name matches and we have a sniffed + type, use that as a tie breaker. */ + + for (i = 0; i < n_name_mimetypes; i++) + { + if ( xdg_mime_mime_type_subclass (name_mimetypes[i], sniffed_mimetype)) + { + /* This nametype match is derived from (or the same as) the sniffed type). + This is probably it. */ + mimetype = g_strdup (name_mimetypes[i]); + break; + } + } + } + } + + if (mimetype == NULL) + { + /* Conflicts, and sniffed type was no help or not there. guess on the first one */ + mimetype = g_strdup (name_mimetypes[0]); + if (result_uncertain) + *result_uncertain = TRUE; + } + } + + G_UNLOCK (gio_xdgmime); + + return mimetype; +} + +static gboolean +foreach_mimetype (gpointer key, + gpointer value, + gpointer user_data) +{ + GList **l = user_data; + + *l = g_list_prepend (*l, (char *)key); + return TRUE; +} + +static void +enumerate_mimetypes_subdir (const char *dir, const char *prefix, GHashTable *mimetypes) +{ + DIR *d; + struct dirent *ent; + char *mimetype; + + d = opendir (dir); + if (d) + { + while ((ent = readdir (d)) != NULL) + { + if (g_str_has_suffix (ent->d_name, ".xml")) + { + mimetype = g_strdup_printf ("%s/%.*s", prefix, (int) strlen (ent->d_name) - 4, ent->d_name); + g_hash_table_insert (mimetypes, mimetype, NULL); + } + } + closedir (d); + } +} + +static void +enumerate_mimetypes_dir (const char *dir, GHashTable *mimetypes) +{ + DIR *d; + struct dirent *ent; + char *mimedir; + char *name; + + mimedir = g_build_filename (dir, "mime", NULL); + + d = opendir (mimedir); + if (d) + { + while ((ent = readdir (d)) != NULL) + { + if (strcmp (ent->d_name, "packages") != 0) + { + name = g_build_filename (mimedir, ent->d_name, NULL); + if (g_file_test (name, G_FILE_TEST_IS_DIR)) + enumerate_mimetypes_subdir (name, ent->d_name, mimetypes); + g_free (name); + } + } + closedir (d); + } + + g_free (mimedir); +} + +GList * +g_content_types_get_registered (void) +{ + const char * const* dirs; + GHashTable *mimetypes; + int i; + GList *l; + + mimetypes = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); + + enumerate_mimetypes_dir (g_get_user_data_dir (), mimetypes); + dirs = g_get_system_data_dirs (); + + for (i = 0; dirs[i] != NULL; i++) + enumerate_mimetypes_dir (dirs[i], mimetypes); + + l = NULL; + g_hash_table_foreach_steal (mimetypes, foreach_mimetype, &l); + g_hash_table_destroy (mimetypes); + + return l; +} + +#endif /* Unix version */ diff --git a/gio/gcontenttype.h b/gio/gcontenttype.h new file mode 100644 index 000000000..e8bbf5780 --- /dev/null +++ b/gio/gcontenttype.h @@ -0,0 +1,50 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#ifndef __G_CONTENT_TYPE_H__ +#define __G_CONTENT_TYPE_H__ + +#include <glib.h> +#include <gio/gicon.h> + +G_BEGIN_DECLS + +gboolean g_content_type_equals (const char *type1, + const char *type2); +gboolean g_content_type_is_a (const char *type, + const char *supertype); +gboolean g_content_type_is_unknown (const char *type); +char * g_content_type_get_description (const char *type); +char * g_content_type_get_mime_type (const char *type); +GIcon * g_content_type_get_icon (const char *type); +gboolean g_content_type_can_be_executable (const char *type); + +char * g_content_type_guess (const char *filename, + const guchar *data, + gsize data_size, + gboolean *result_uncertain ); + +GList * g_content_types_get_registered (void); + +G_END_DECLS + +#endif /* __G_CONTENT_TYPE_H__ */ diff --git a/gio/gcontenttypeprivate.h b/gio/gcontenttypeprivate.h new file mode 100644 index 000000000..a94d13895 --- /dev/null +++ b/gio/gcontenttypeprivate.h @@ -0,0 +1,36 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#ifndef __G_CONTENT_TYPE_PRIVATE_H__ +#define __G_CONTENT_TYPE_PRIVATE_H__ + +#include "gcontenttype.h" + +G_BEGIN_DECLS + +gsize _g_unix_content_type_get_sniff_len (void); +char * _g_unix_content_type_unalias (const char *type); +char **_g_unix_content_type_get_parents (const char *type); + +G_END_DECLS + +#endif /* __G_CONTENT_TYPE_PRIVATE_H__ */ diff --git a/gio/gdatainputstream.c b/gio/gdatainputstream.c new file mode 100644 index 000000000..54a46784f --- /dev/null +++ b/gio/gdatainputstream.c @@ -0,0 +1,718 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#include <config.h> +#include "gdatainputstream.h" +#include "glibintl.h" + +struct _GDataInputStreamPrivate { + GDataStreamByteOrder byte_order; + GDataStreamNewlineType newline_type; +}; + +enum { + PROP_0 +}; + +static void g_data_input_stream_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec); +static void g_data_input_stream_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec); + +G_DEFINE_TYPE (GDataInputStream, + g_data_input_stream, + G_TYPE_BUFFERED_INPUT_STREAM) + + +static void +g_data_input_stream_class_init (GDataInputStreamClass *klass) +{ + GObjectClass *object_class; + + g_type_class_add_private (klass, sizeof (GDataInputStreamPrivate)); + + object_class = G_OBJECT_CLASS (klass); + object_class->get_property = g_data_input_stream_get_property; + object_class->set_property = g_data_input_stream_set_property; +} + +static void +g_data_input_stream_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GDataInputStreamPrivate *priv; + GDataInputStream *dstream; + + dstream = G_DATA_INPUT_STREAM (object); + priv = dstream->priv; + + switch (prop_id) + { + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } + +} + +static void +g_data_input_stream_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GDataInputStreamPrivate *priv; + GDataInputStream *dstream; + + dstream = G_DATA_INPUT_STREAM (object); + priv = dstream->priv; + + switch (prop_id) + { + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } + +} +static void +g_data_input_stream_init (GDataInputStream *stream) +{ + stream->priv = G_TYPE_INSTANCE_GET_PRIVATE (stream, + G_TYPE_DATA_INPUT_STREAM, + GDataInputStreamPrivate); + + stream->priv->byte_order = G_DATA_STREAM_BYTE_ORDER_BIG_ENDIAN; + stream->priv->newline_type = G_DATA_STREAM_NEWLINE_TYPE_LF; +} + +/** + * g_data_input_stream_new: + * @base_stream: a given #GInputStream. + * + * Returns: a new #GDataInputStream. + **/ +GDataInputStream * +g_data_input_stream_new (GInputStream *base_stream) +{ + GDataInputStream *stream; + + g_return_val_if_fail (G_IS_INPUT_STREAM (base_stream), NULL); + + stream = g_object_new (G_TYPE_DATA_INPUT_STREAM, + "base-stream", base_stream, + NULL); + + return stream; +} + +/** + * g_data_input_stream_set_byte_order: + * @stream: a given #GDataInputStream. + * @order: a #GDataStreamByteOrder to set. + * + * This function sets the byte order for the given @stream. All subsequent + * reads from the @stream will be read in the given @order. + * + **/ +void +g_data_input_stream_set_byte_order (GDataInputStream *stream, + GDataStreamByteOrder order) +{ + g_return_if_fail (G_IS_DATA_INPUT_STREAM (stream)); + + stream->priv->byte_order = order; +} + +/** + * g_data_input_stream_get_byte_order: + * @stream: a given #GDataInputStream. + * + * Returns the @stream's current #GDataStreamByteOrder. + **/ +GDataStreamByteOrder +g_data_input_stream_get_byte_order (GDataInputStream *stream) +{ + g_return_val_if_fail (G_IS_DATA_INPUT_STREAM (stream), G_DATA_STREAM_BYTE_ORDER_HOST_ENDIAN); + + return stream->priv->byte_order; +} + +/** + * g_data_input_stream_set_newline_type: + * @stream: a given #GDataInputStream. + * @type: the type of new line return as #GDataStreamNewlineType. + * + * Sets the newline type for the @stream. + * + * TODO: is it valid to set this to G_DATA_STREAM_NEWLINE_TYPE_ANY, or + * should it always be set to {_LF, _CR, _CR_LF} + * + **/ +void +g_data_input_stream_set_newline_type (GDataInputStream *stream, + GDataStreamNewlineType type) +{ + g_return_if_fail (G_IS_DATA_INPUT_STREAM (stream)); + + stream->priv->newline_type = type; +} + +/** + * g_data_input_stream_get_newline_type: + * @stream: a given #GDataInputStream. + * + * Gets the current newline type for the @stream. + * + * Returns: #GDataStreamNewlineType for the given @stream. + **/ +GDataStreamNewlineType +g_data_input_stream_get_newline_type (GDataInputStream *stream) +{ + g_return_val_if_fail (G_IS_DATA_INPUT_STREAM (stream), G_DATA_STREAM_NEWLINE_TYPE_ANY); + + return stream->priv->newline_type; +} + +static gboolean +read_data (GDataInputStream *stream, + void *buffer, + gsize size, + GCancellable *cancellable, + GError **error) +{ + gsize available; + gssize res; + + while ((available = g_buffered_input_stream_get_available (G_BUFFERED_INPUT_STREAM (stream))) < size) + { + res = g_buffered_input_stream_fill (G_BUFFERED_INPUT_STREAM (stream), + size - available, + cancellable, error); + if (res < 0) + return FALSE; + if (res == 0) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + _("Unexpected early end-of-stream")); + return FALSE; + } + } + + /* This should always succeed, since its in the buffer */ + res = g_input_stream_read (G_INPUT_STREAM (stream), + buffer, size, + NULL, NULL); + g_assert (res == size); + return TRUE; +} + + +/** + * g_data_input_stream_read_byte: + * @stream: a given #GDataInputStream. + * @cancellable: optional #GCancellable object, %NULL to ignore. + * @error: #GError for error reporting. + * + * In order to get the correct byte order for this read operation, + * see g_data_stream_get_byte_order() and g_data_stream_set_byte_order(). + * + * Returns: an unsigned 8-bit/1-byte value read from the @stream or %0 + * if an error occured. + **/ + +guchar +g_data_input_stream_read_byte (GDataInputStream *stream, + GCancellable *cancellable, + GError **error) +{ + guchar c; + + g_return_val_if_fail (G_IS_DATA_INPUT_STREAM (stream), '\0'); + + if (read_data (stream, &c, 1, cancellable, error)) + return c; + + return 0; +} + + +/** + * g_data_input_stream_read_int16: + * @stream: a given #GDataInputStream. + * @cancellable: optional #GCancellable object, %NULL to ignore. + * @error: #GError for error reporting. + * + * In order to get the correct byte order for this read operation, + * see g_data_stream_get_byte_order() and g_data_stream_set_byte_order(). + * + * Returns a signed 16-bit/2-byte value read from @stream or %0 if + * an error occured. + **/ +gint16 +g_data_input_stream_read_int16 (GDataInputStream *stream, + GCancellable *cancellable, + GError **error) +{ + gint16 v; + + g_return_val_if_fail (G_IS_DATA_INPUT_STREAM (stream), 0); + + if (read_data (stream, &v, 2, cancellable, error)) + { + switch (stream->priv->byte_order) + { + case G_DATA_STREAM_BYTE_ORDER_BIG_ENDIAN: + v = GINT16_FROM_BE (v); + break; + case G_DATA_STREAM_BYTE_ORDER_LITTLE_ENDIAN: + v = GINT16_FROM_LE (v); + break; + case G_DATA_STREAM_BYTE_ORDER_HOST_ENDIAN: + default: + break; + } + return v; + } + + return 0; +} + + +/** + * g_data_input_stream_read_uint16: + * @stream: a given #GDataInputStream. + * @cancellable: optional #GCancellable object, %NULL to ignore. + * @error: #GError for error reporting. + * + * In order to get the correct byte order for this read operation, + * see g_data_stream_get_byte_order() and g_data_stream_set_byte_order(). + * + * Returns an unsigned 16-bit/2-byte value read from the @stream or %0 if + * an error occured. + **/ +guint16 +g_data_input_stream_read_uint16 (GDataInputStream *stream, + GCancellable *cancellable, + GError **error) +{ + guint16 v; + + g_return_val_if_fail (G_IS_DATA_INPUT_STREAM (stream), 0); + + if (read_data (stream, &v, 2, cancellable, error)) + { + switch (stream->priv->byte_order) + { + case G_DATA_STREAM_BYTE_ORDER_BIG_ENDIAN: + v = GUINT16_FROM_BE (v); + break; + case G_DATA_STREAM_BYTE_ORDER_LITTLE_ENDIAN: + v = GUINT16_FROM_LE (v); + break; + case G_DATA_STREAM_BYTE_ORDER_HOST_ENDIAN: + default: + break; + } + return v; + } + + return 0; +} + + +/** + * g_data_input_stream_read_int32: + * @stream: a given #GDataInputStream. + * @cancellable: optional #GCancellable object, %NULL to ignore. + * @error: #GError for error reporting. + * + * In order to get the correct byte order for this read operation, + * see g_data_stream_get_byte_order() and g_data_stream_set_byte_order(). + * + * Returns a signed 32-bit/4-byte value read from the @stream or %0 if + * an error occured. + **/ +gint32 +g_data_input_stream_read_int32 (GDataInputStream *stream, + GCancellable *cancellable, + GError **error) +{ + gint32 v; + + g_return_val_if_fail (G_IS_DATA_INPUT_STREAM (stream), 0); + + if (read_data (stream, &v, 4, cancellable, error)) + { + switch (stream->priv->byte_order) + { + case G_DATA_STREAM_BYTE_ORDER_BIG_ENDIAN: + v = GINT32_FROM_BE (v); + break; + case G_DATA_STREAM_BYTE_ORDER_LITTLE_ENDIAN: + v = GINT32_FROM_LE (v); + break; + case G_DATA_STREAM_BYTE_ORDER_HOST_ENDIAN: + default: + break; + } + return v; + } + + return 0; +} + + +/** + * g_data_input_stream_read_uint32: + * @stream: a given #GDataInputStream. + * @cancellable: optional #GCancellable object, %NULL to ignore. + * @error: #GError for error reporting. + * + * In order to get the correct byte order for this read operation, + * see g_data_stream_get_byte_order() and g_data_stream_set_byte_order(). + * + * Returns an unsigned 32-bit/4-byte value read from the @stream or %0 if + * an error occured. + **/ +guint32 +g_data_input_stream_read_uint32 (GDataInputStream *stream, + GCancellable *cancellable, + GError **error) +{ + guint32 v; + + g_return_val_if_fail (G_IS_DATA_INPUT_STREAM (stream), 0); + + if (read_data (stream, &v, 4, cancellable, error)) + { + switch (stream->priv->byte_order) + { + case G_DATA_STREAM_BYTE_ORDER_BIG_ENDIAN: + v = GUINT32_FROM_BE (v); + break; + case G_DATA_STREAM_BYTE_ORDER_LITTLE_ENDIAN: + v = GUINT32_FROM_LE (v); + break; + case G_DATA_STREAM_BYTE_ORDER_HOST_ENDIAN: + default: + break; + } + return v; + } + + return 0; +} + + +/** + * g_data_input_stream_read_int64: + * @stream: a given #GDataInputStream. + * @cancellable: optional #GCancellable object, %NULL to ignore. + * @error: #GError for error reporting. + * + * In order to get the correct byte order for this read operation, + * see g_data_stream_get_byte_order() and g_data_stream_set_byte_order(). + * + * Returns a signed 64-bit/8-byte value read from @stream or %0 if + * an error occured. + **/ +gint64 +g_data_input_stream_read_int64 (GDataInputStream *stream, + GCancellable *cancellable, + GError **error) +{ + gint64 v; + + g_return_val_if_fail (G_IS_DATA_INPUT_STREAM (stream), 0); + + if (read_data (stream, &v, 8, cancellable, error)) + { + switch (stream->priv->byte_order) + { + case G_DATA_STREAM_BYTE_ORDER_BIG_ENDIAN: + v = GINT64_FROM_BE (v); + break; + case G_DATA_STREAM_BYTE_ORDER_LITTLE_ENDIAN: + v = GINT64_FROM_LE (v); + break; + case G_DATA_STREAM_BYTE_ORDER_HOST_ENDIAN: + default: + break; + } + return v; + } + + return 0; +} + + +/** + * g_data_input_stream_read_uint64: + * @stream: a given #GDataInputStream. + * @cancellable: optional #GCancellable object, %NULL to ignore. + * @error: #GError for error reporting. + * + * In order to get the correct byte order for this read operation, + * see g_data_stream_get_byte_order(). + * + * Returns an unsigned 64-bit/8-byte read from @stream or %0 if + * an error occured. + **/ +guint64 +g_data_input_stream_read_uint64 (GDataInputStream *stream, + GCancellable *cancellable, + GError **error) +{ + guint64 v; + + g_return_val_if_fail (G_IS_DATA_INPUT_STREAM (stream), 0); + + if (read_data (stream, &v, 8, cancellable, error)) + { + switch (stream->priv->byte_order) + { + case G_DATA_STREAM_BYTE_ORDER_BIG_ENDIAN: + v = GUINT64_FROM_BE (v); + break; + case G_DATA_STREAM_BYTE_ORDER_LITTLE_ENDIAN: + v = GUINT64_FROM_LE (v); + break; + case G_DATA_STREAM_BYTE_ORDER_HOST_ENDIAN: + default: + break; + } + return v; + } + + return 0; +} + +static gssize +scan_for_newline (GDataInputStream *stream, + gsize *checked_out, + gboolean *last_saw_cr_out, + int *newline_len_out) +{ + GBufferedInputStream *bstream; + GDataInputStreamPrivate *priv; + char buffer[100]; + gsize start, end, peeked; + int i; + gssize found_pos; + int newline_len; + gsize available, checked; + gboolean last_saw_cr; + + priv = stream->priv; + + bstream = G_BUFFERED_INPUT_STREAM (stream); + + available = g_buffered_input_stream_get_available (bstream); + + checked = *checked_out; + last_saw_cr = *last_saw_cr_out; + found_pos = -1; + newline_len = 0; + + while (checked < available) + { + start = checked; + end = MIN (start + sizeof(buffer), available); + peeked = g_buffered_input_stream_peek (bstream, buffer, start, end - start); + end = start + peeked; + + for (i = 0; i < peeked; i++) + { + switch (priv->newline_type) + { + case G_DATA_STREAM_NEWLINE_TYPE_LF: + if (buffer[i] == 10) + { + found_pos = start + i; + newline_len = 1; + } + break; + case G_DATA_STREAM_NEWLINE_TYPE_CR: + if (buffer[i] == 13) + { + found_pos = start + i; + newline_len = 1; + } + break; + case G_DATA_STREAM_NEWLINE_TYPE_CR_LF: + if (last_saw_cr && buffer[i] == 10) + { + found_pos = start + i - 1; + newline_len = 2; + } + break; + default: + case G_DATA_STREAM_NEWLINE_TYPE_ANY: + if (buffer[i] == 10) /* LF */ + { + if (last_saw_cr) + { + /* CR LF */ + found_pos = start + i - 1; + newline_len = 2; + } + else + { + /* LF */ + found_pos = start + i; + newline_len = 1; + } + } + else if (last_saw_cr) + { + /* Last was cr, this is not LF, end is CR */ + found_pos = start + i - 1; + newline_len = 1; + } + /* Don't check for CR here, instead look at last_saw_cr on next byte */ + break; + } + + last_saw_cr = (buffer[i] == 13); + + if (found_pos != -1) + { + *newline_len_out = newline_len; + return found_pos; + } + } + checked = end; + } + + *checked_out = checked; + *last_saw_cr_out = last_saw_cr; + return -1; +} + + +/** + * g_data_input_stream_read_line: + * @stream: a given #GDataInputStream. + * @length: a #gsize to get the length of the data read in. + * @cancellable: optional #GCancellable object, %NULL to ignore. + * @error: #GError for error reporting. + * + * Returns a string with the line that was read in. Set @length to + * a #gsize to get the length of the read line. This function will + * return %NULL on an error. + **/ +char * +g_data_input_stream_read_line (GDataInputStream *stream, + gsize *length, + GCancellable *cancellable, + GError **error) +{ + GBufferedInputStream *bstream; + gsize checked; + gboolean last_saw_cr; + gssize found_pos; + gssize res; + int newline_len; + char *line; + + g_return_val_if_fail (G_IS_DATA_INPUT_STREAM (stream), NULL); + + bstream = G_BUFFERED_INPUT_STREAM (stream); + + newline_len = 0; + checked = 0; + last_saw_cr = FALSE; + + while ((found_pos = scan_for_newline (stream, &checked, &last_saw_cr, &newline_len)) == -1) + { + if (g_buffered_input_stream_get_available (bstream) == + g_buffered_input_stream_get_buffer_size (bstream)) + g_buffered_input_stream_set_buffer_size (bstream, + 2 * g_buffered_input_stream_get_buffer_size (bstream)); + + res = g_buffered_input_stream_fill (bstream, -1, cancellable, error); + if (res < 0) + return NULL; + if (res == 0) + { + /* End of stream */ + if (g_buffered_input_stream_get_available (bstream) == 0) + { + if (length) + *length = 0; + return NULL; + } + else + { + found_pos = checked; + newline_len = 0; + break; + } + } + } + + line = g_malloc (found_pos + newline_len + 1); + + res = g_input_stream_read (G_INPUT_STREAM (stream), + line, + found_pos + newline_len, + NULL, NULL); + if (length) + *length = (gsize)found_pos; + g_assert (res == found_pos + newline_len); + line[found_pos] = 0; + + return line; +} + + +/** + * g_data_input_stream_read_until: + * @stream: a given #GDataInputStream. + * @stop_char: character to terminate the read. + * @length: a #gsize to get the length of the data read in. + * @cancellable: optional #GCancellable object, %NULL to ignore. + * @error: #GError for error reporting. + * + * NOTE: not supported for #GDataInputStream. + * Returns %NULL. + **/ +char * +g_data_input_stream_read_until (GDataInputStream *stream, + gchar stop_char, + gsize *length, + GCancellable *cancellable, + GError **error) +{ + /* TODO: should be implemented */ + g_assert_not_reached (); + return NULL; +} diff --git a/gio/gdatainputstream.h b/gio/gdatainputstream.h new file mode 100644 index 000000000..af58dbfbe --- /dev/null +++ b/gio/gdatainputstream.h @@ -0,0 +1,117 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#ifndef __G_DATA_INPUT_STREAM_H__ +#define __G_DATA_INPUT_STREAM_H__ + +#include <glib-object.h> +#include <gio/gbufferedinputstream.h> + +G_BEGIN_DECLS + +#define G_TYPE_DATA_INPUT_STREAM (g_data_input_stream_get_type ()) +#define G_DATA_INPUT_STREAM(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_DATA_INPUT_STREAM, GDataInputStream)) +#define G_DATA_INPUT_STREAM_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), G_TYPE_DATA_INPUT_STREAM, GDataInputStreamClass)) +#define G_IS_DATA_INPUT_STREAM(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_DATA_INPUT_STREAM)) +#define G_IS_DATA_INPUT_STREAM_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_DATA_INPUT_STREAM)) +#define G_DATA_INPUT_STREAM_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_TYPE_DATA_INPUT_STREAM, GDataInputStreamClass)) + +typedef struct _GDataInputStream GDataInputStream; +typedef struct _GDataInputStreamClass GDataInputStreamClass; +typedef struct _GDataInputStreamPrivate GDataInputStreamPrivate; + +struct _GDataInputStream +{ + GBufferedInputStream parent; + + /*< private >*/ + GDataInputStreamPrivate *priv; +}; + +struct _GDataInputStreamClass +{ + GBufferedInputStreamClass parent_class; + + /* Padding for future expansion */ + void (*_g_reserved1) (void); + void (*_g_reserved2) (void); + void (*_g_reserved3) (void); + void (*_g_reserved4) (void); + void (*_g_reserved5) (void); +}; + +typedef enum { + G_DATA_STREAM_BYTE_ORDER_BIG_ENDIAN, + G_DATA_STREAM_BYTE_ORDER_LITTLE_ENDIAN, + G_DATA_STREAM_BYTE_ORDER_HOST_ENDIAN +} GDataStreamByteOrder; + +typedef enum { + G_DATA_STREAM_NEWLINE_TYPE_LF, + G_DATA_STREAM_NEWLINE_TYPE_CR, + G_DATA_STREAM_NEWLINE_TYPE_CR_LF, + G_DATA_STREAM_NEWLINE_TYPE_ANY +} GDataStreamNewlineType; + +GType g_data_input_stream_get_type (void) G_GNUC_CONST; +GDataInputStream* g_data_input_stream_new (GInputStream *base_stream); + +void g_data_input_stream_set_byte_order (GDataInputStream *stream, + GDataStreamByteOrder order); +GDataStreamByteOrder g_data_input_stream_get_byte_order (GDataInputStream *stream); +void g_data_input_stream_set_newline_type (GDataInputStream *data_stream, + GDataStreamNewlineType type); +GDataStreamNewlineType g_data_input_stream_get_newline_type (GDataInputStream *stream); +guchar g_data_input_stream_read_byte (GDataInputStream *stream, + GCancellable *cancellable, + GError **error); +gint16 g_data_input_stream_read_int16 (GDataInputStream *stream, + GCancellable *cancellable, + GError **error); +guint16 g_data_input_stream_read_uint16 (GDataInputStream *stream, + GCancellable *cancellable, + GError **error); +gint32 g_data_input_stream_read_int32 (GDataInputStream *stream, + GCancellable *cancellable, + GError **error); +guint32 g_data_input_stream_read_uint32 (GDataInputStream *stream, + GCancellable *cancellable, + GError **error); +gint64 g_data_input_stream_read_int64 (GDataInputStream *stream, + GCancellable *cancellable, + GError **error); +guint64 g_data_input_stream_read_uint64 (GDataInputStream *stream, + GCancellable *cancellable, + GError **error); +char * g_data_input_stream_read_line (GDataInputStream *stream, + gsize *length, + GCancellable *cancellable, + GError **error); +char * g_data_input_stream_read_until (GDataInputStream *stream, + gchar stop_char, + gsize *length, + GCancellable *cancellable, + GError **error); + +G_END_DECLS + +#endif /* __G_DATA_INPUT_STREAM_H__ */ diff --git a/gio/gdataoutputstream.c b/gio/gdataoutputstream.c new file mode 100644 index 000000000..6393d7c82 --- /dev/null +++ b/gio/gdataoutputstream.c @@ -0,0 +1,441 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#include <config.h> +#include <string.h> +#include "gdataoutputstream.h" +#include "glibintl.h" + +struct _GDataOutputStreamPrivate { + GDataStreamByteOrder byte_order; +}; + +enum { + PROP_0 +}; + +static void g_data_output_stream_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec); +static void g_data_output_stream_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec); + +G_DEFINE_TYPE (GDataOutputStream, + g_data_output_stream, + G_TYPE_FILTER_OUTPUT_STREAM) + + +static void +g_data_output_stream_class_init (GDataOutputStreamClass *klass) +{ + GObjectClass *object_class; + + g_type_class_add_private (klass, sizeof (GDataOutputStreamPrivate)); + + object_class = G_OBJECT_CLASS (klass); + object_class->get_property = g_data_output_stream_get_property; + object_class->set_property = g_data_output_stream_set_property; +} + +static void +g_data_output_stream_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GDataOutputStreamPrivate *priv; + GDataOutputStream *dstream; + + dstream = G_DATA_OUTPUT_STREAM (object); + priv = dstream->priv; + + switch (prop_id) + { + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } + +} + +static void +g_data_output_stream_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GDataOutputStreamPrivate *priv; + GDataOutputStream *dstream; + + dstream = G_DATA_OUTPUT_STREAM (object); + priv = dstream->priv; + + switch (prop_id) + { + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } + +} +static void +g_data_output_stream_init (GDataOutputStream *stream) +{ + stream->priv = G_TYPE_INSTANCE_GET_PRIVATE (stream, + G_TYPE_DATA_OUTPUT_STREAM, + GDataOutputStreamPrivate); + + stream->priv->byte_order = G_DATA_STREAM_BYTE_ORDER_BIG_ENDIAN; +} + +/** + * g_data_output_stream_new: + * @base_stream: a #GOutputStream. + * + * Returns: #GDataOutputStream. + **/ +GDataOutputStream * +g_data_output_stream_new (GOutputStream *base_stream) +{ + GDataOutputStream *stream; + + g_return_val_if_fail (G_IS_OUTPUT_STREAM (base_stream), NULL); + + stream = g_object_new (G_TYPE_DATA_OUTPUT_STREAM, + "base-stream", base_stream, + NULL); + + return stream; +} + +/** + * g_data_output_stream_set_byte_order: + * @stream: a #GDataOutputStream. a #GDataOutputStream. + * @order: a %GDataStreamByteOrder. + * + **/ +void +g_data_output_stream_set_byte_order (GDataOutputStream *stream, + GDataStreamByteOrder order) +{ + g_return_if_fail (G_IS_DATA_OUTPUT_STREAM (stream)); + + stream->priv->byte_order = order; +} + +/** + * g_data_output_stream_get_byte_order: + * @stream: a #GDataOutputStream. a #GDataOutputStream. + * + * Returns: the %GDataStreamByteOrder for the @stream. + **/ +GDataStreamByteOrder +g_data_output_stream_get_byte_order (GDataOutputStream *stream) +{ + g_return_val_if_fail (G_IS_DATA_OUTPUT_STREAM (stream), G_DATA_STREAM_BYTE_ORDER_HOST_ENDIAN); + + return stream->priv->byte_order; +} + +/** + * g_data_output_stream_put_byte: + * @data_stream: a #GDataOutputStream. + * @data: a #guchar. + * @cancellable: optional #GCancellable object, %NULL to ignore. + * @error: a #GError. + * + * Returns: %TRUE if @data was successfully added to the @stream. + **/ +gboolean +g_data_output_stream_put_byte (GDataOutputStream *data_stream, + guchar data, + GCancellable *cancellable, + GError **error) +{ + gsize bytes_written; + + g_return_val_if_fail (G_IS_DATA_OUTPUT_STREAM (data_stream), FALSE); + + return g_output_stream_write_all (G_OUTPUT_STREAM (data_stream), + &data, 1, + &bytes_written, + cancellable, error); +} + +/** + * g_data_output_stream_put_int16: + * @stream: a #GDataOutputStream. a #GDataOutputStream. + * @data: a #gint16. + * @cancellable: optional #GCancellable object, %NULL to ignore. + * @error: a #GError. + * + * Returns: %TRUE if @data was successfully added to the @stream. + **/ +gboolean +g_data_output_stream_put_int16 (GDataOutputStream *stream, + gint16 data, + GCancellable *cancellable, + GError **error) +{ + gsize bytes_written; + + g_return_val_if_fail (G_IS_DATA_OUTPUT_STREAM (stream), FALSE); + + switch (stream->priv->byte_order) + { + case G_DATA_STREAM_BYTE_ORDER_BIG_ENDIAN: + data = GINT16_TO_BE (data); + break; + case G_DATA_STREAM_BYTE_ORDER_LITTLE_ENDIAN: + data = GINT16_TO_LE (data); + break; + case G_DATA_STREAM_BYTE_ORDER_HOST_ENDIAN: + default: + break; + } + + return g_output_stream_write_all (G_OUTPUT_STREAM (stream), + &data, 2, + &bytes_written, + cancellable, error); +} + +/** + * g_data_output_stream_put_uint16: + * @stream: a #GDataOutputStream. a #GDataOutputStream. + * @data: a #guint16. + * @cancellable: optional #GCancellable object, %NULL to ignore. + * @error: a #GError. + * + * Returns: %TRUE if @data was successfully added to the @stream. + **/ +gboolean +g_data_output_stream_put_uint16 (GDataOutputStream *stream, + guint16 data, + GCancellable *cancellable, + GError **error) +{ + gsize bytes_written; + + g_return_val_if_fail (G_IS_DATA_OUTPUT_STREAM (stream), FALSE); + + switch (stream->priv->byte_order) + { + case G_DATA_STREAM_BYTE_ORDER_BIG_ENDIAN: + data = GUINT16_TO_BE (data); + break; + case G_DATA_STREAM_BYTE_ORDER_LITTLE_ENDIAN: + data = GUINT16_TO_LE (data); + break; + case G_DATA_STREAM_BYTE_ORDER_HOST_ENDIAN: + default: + break; + } + + return g_output_stream_write_all (G_OUTPUT_STREAM (stream), + &data, 2, + &bytes_written, + cancellable, error); +} + +/** + * g_data_output_stream_put_int32: + * @stream: a #GDataOutputStream. a #GDataOutputStream. + * @data: a #gint32. + * @cancellable: optional #GCancellable object, %NULL to ignore. + * @error: a #GError. + * + * Returns: %TRUE if @data was successfully added to the @stream. + **/ +gboolean +g_data_output_stream_put_int32 (GDataOutputStream *stream, + gint32 data, + GCancellable *cancellable, + GError **error) +{ + gsize bytes_written; + + g_return_val_if_fail (G_IS_DATA_OUTPUT_STREAM (stream), FALSE); + + switch (stream->priv->byte_order) + { + case G_DATA_STREAM_BYTE_ORDER_BIG_ENDIAN: + data = GINT32_TO_BE (data); + break; + case G_DATA_STREAM_BYTE_ORDER_LITTLE_ENDIAN: + data = GINT32_TO_LE (data); + break; + case G_DATA_STREAM_BYTE_ORDER_HOST_ENDIAN: + default: + break; + } + + return g_output_stream_write_all (G_OUTPUT_STREAM (stream), + &data, 4, + &bytes_written, + cancellable, error); +} + +/** + * g_data_output_stream_put_uint32: + * @stream: a #GDataOutputStream. a #GDataOutputStream. + * @data: a #guint32. + * @cancellable: optional #GCancellable object, %NULL to ignore. + * @error: a #GError. + * + * Returns: %TRUE if @data was successfully added to the @stream. + **/ +gboolean +g_data_output_stream_put_uint32 (GDataOutputStream *stream, + guint32 data, + GCancellable *cancellable, + GError **error) +{ + gsize bytes_written; + + g_return_val_if_fail (G_IS_DATA_OUTPUT_STREAM (stream), FALSE); + + switch (stream->priv->byte_order) + { + case G_DATA_STREAM_BYTE_ORDER_BIG_ENDIAN: + data = GUINT32_TO_BE (data); + break; + case G_DATA_STREAM_BYTE_ORDER_LITTLE_ENDIAN: + data = GUINT32_TO_LE (data); + break; + case G_DATA_STREAM_BYTE_ORDER_HOST_ENDIAN: + default: + break; + } + + return g_output_stream_write_all (G_OUTPUT_STREAM (stream), + &data, 4, + &bytes_written, + cancellable, error); +} + +/** + * g_data_output_stream_put_int64: + * @stream: a #GDataOutputStream. a #GDataOutputStream. + * @data: a #gint64. + * @cancellable: optional #GCancellable object, %NULL to ignore. + * @error: a #GError. + * + * Returns: %TRUE if @data was successfully added to the @stream. + **/ +gboolean +g_data_output_stream_put_int64 (GDataOutputStream *stream, + gint64 data, + GCancellable *cancellable, + GError **error) +{ + gsize bytes_written; + + g_return_val_if_fail (G_IS_DATA_OUTPUT_STREAM (stream), FALSE); + + switch (stream->priv->byte_order) + { + case G_DATA_STREAM_BYTE_ORDER_BIG_ENDIAN: + data = GINT64_TO_BE (data); + break; + case G_DATA_STREAM_BYTE_ORDER_LITTLE_ENDIAN: + data = GINT64_TO_LE (data); + break; + case G_DATA_STREAM_BYTE_ORDER_HOST_ENDIAN: + default: + break; + } + + return g_output_stream_write_all (G_OUTPUT_STREAM (stream), + &data, 8, + &bytes_written, + cancellable, error); +} + +/** + * g_data_output_stream_put_uint64: + * @stream: a #GDataOutputStream. a #GDataOutputStream. + * @data: a #guint64. + * @cancellable: optional #GCancellable object, %NULL to ignore. + * @error: a #GError. + * + * Returns: %TRUE if @data was successfully added to the @stream. + **/ +gboolean +g_data_output_stream_put_uint64 (GDataOutputStream *stream, + guint64 data, + GCancellable *cancellable, + GError **error) +{ + gsize bytes_written; + + g_return_val_if_fail (G_IS_DATA_OUTPUT_STREAM (stream), FALSE); + + switch (stream->priv->byte_order) + { + case G_DATA_STREAM_BYTE_ORDER_BIG_ENDIAN: + data = GUINT64_TO_BE (data); + break; + case G_DATA_STREAM_BYTE_ORDER_LITTLE_ENDIAN: + data = GUINT64_TO_LE (data); + break; + case G_DATA_STREAM_BYTE_ORDER_HOST_ENDIAN: + default: + break; + } + + return g_output_stream_write_all (G_OUTPUT_STREAM (stream), + &data, 8, + &bytes_written, + cancellable, error); +} + +/** + * g_data_output_stream_put_string: + * @stream: a #GDataOutputStream. + * @str: a string. + * @cancellable: optional #GCancellable object, %NULL to ignore. + * @error: a #GError. + * + * Returns: %TRUE if @data was successfully added to the @stream. + **/ +gboolean +g_data_output_stream_put_string (GDataOutputStream *stream, + const char *str, + GCancellable *cancellable, + GError **error) +{ + gsize bytes_written; + + g_return_val_if_fail (G_IS_DATA_OUTPUT_STREAM (stream), FALSE); + g_return_val_if_fail (str != NULL, FALSE); + + return g_output_stream_write_all (G_OUTPUT_STREAM (stream), + str, strlen (str), + &bytes_written, + cancellable, error); +} diff --git a/gio/gdataoutputstream.h b/gio/gdataoutputstream.h new file mode 100644 index 000000000..94bdfa5c2 --- /dev/null +++ b/gio/gdataoutputstream.h @@ -0,0 +1,108 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#ifndef __G_DATA_OUTPUT_STREAM_H__ +#define __G_DATA_OUTPUT_STREAM_H__ + +#include <glib-object.h> +#include <gio/gfilteroutputstream.h> +#include <gio/gdatainputstream.h> + +G_BEGIN_DECLS + +#define G_TYPE_DATA_OUTPUT_STREAM (g_data_output_stream_get_type ()) +#define G_DATA_OUTPUT_STREAM(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_DATA_OUTPUT_STREAM, GDataOutputStream)) +#define G_DATA_OUTPUT_STREAM_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), G_TYPE_DATA_OUTPUT_STREAM, GDataOutputStreamClass)) +#define G_IS_DATA_OUTPUT_STREAM(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_DATA_OUTPUT_STREAM)) +#define G_IS_DATA_OUTPUT_STREAM_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_DATA_OUTPUT_STREAM)) +#define G_DATA_OUTPUT_STREAM_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_TYPE_DATA_OUTPUT_STREAM, GDataOutputStreamClass)) + +typedef struct _GDataOutputStream GDataOutputStream; +typedef struct _GDataOutputStreamClass GDataOutputStreamClass; +typedef struct _GDataOutputStreamPrivate GDataOutputStreamPrivate; + +struct _GDataOutputStream +{ + GFilterOutputStream parent; + + /*< private >*/ + GDataOutputStreamPrivate *priv; +}; + +struct _GDataOutputStreamClass +{ + GFilterOutputStreamClass parent_class; + + /* Padding for future expansion */ + void (*_g_reserved1) (void); + void (*_g_reserved2) (void); + void (*_g_reserved3) (void); + void (*_g_reserved4) (void); + void (*_g_reserved5) (void); +}; + + +GType g_data_output_stream_get_type (void) G_GNUC_CONST; +GDataOutputStream* g_data_output_stream_new (GOutputStream *base_stream); + +void g_data_output_stream_set_byte_order (GDataOutputStream *data_stream, + GDataStreamByteOrder order); +GDataStreamByteOrder g_data_output_stream_get_byte_order (GDataOutputStream *stream); +void g_data_output_stream_set_expand_buffer (GDataOutputStream *data_stream, + gboolean expand_buffer); + +gboolean g_data_output_stream_put_byte (GDataOutputStream *data_stream, + guchar data, + GCancellable *cancellable, + GError **error); +gboolean g_data_output_stream_put_int16 (GDataOutputStream *stream, + gint16 data, + GCancellable *cancellable, + GError **error); +gboolean g_data_output_stream_put_uint16 (GDataOutputStream *stream, + guint16 data, + GCancellable *cancellable, + GError **error); +gboolean g_data_output_stream_put_int32 (GDataOutputStream *stream, + gint32 data, + GCancellable *cancellable, + GError **error); +gboolean g_data_output_stream_put_uint32 (GDataOutputStream *stream, + guint32 data, + GCancellable *cancellable, + GError **error); +gboolean g_data_output_stream_put_int64 (GDataOutputStream *stream, + gint64 data, + GCancellable *cancellable, + GError **error); +gboolean g_data_output_stream_put_uint64 (GDataOutputStream *stream, + guint64 data, + GCancellable *cancellable, + GError **error); +gboolean g_data_output_stream_put_string (GDataOutputStream *stream, + const char *str, + GCancellable *cancellable, + GError **error); + +G_END_DECLS + +#endif /* __G_DATA_OUTPUT_STREAM_H__ */ diff --git a/gio/gdesktopappinfo.c b/gio/gdesktopappinfo.c new file mode 100644 index 000000000..e8e77cf00 --- /dev/null +++ b/gio/gdesktopappinfo.c @@ -0,0 +1,2185 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * Copyright © 2007 Ryan Lortie + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#include <config.h> + +#include <errno.h> +#include <string.h> +#include <unistd.h> +#include <sys/wait.h> + +#include "gcontenttypeprivate.h" +#include "gdesktopappinfo.h" +#include "gioerror.h" +#include "gthemedicon.h" +#include "gfileicon.h" +#include <glib/gstdio.h> +#include "glibintl.h" + +#define DEFAULT_APPLICATIONS_GROUP "Default Applications" +#define MIME_CACHE_GROUP "MIME Cache" + +static void g_desktop_app_info_iface_init (GAppInfoIface *iface); + +static GList *get_all_desktop_entries_for_mime_type (const char *base_mime_type); +static void mime_info_cache_reload (const char *dir); + +struct _GDesktopAppInfo +{ + GObject parent_instance; + + char *desktop_id; + char *filename; + + char *name; + /* FIXME: what about GenericName ? */ + char *comment; + char *icon_name; + GIcon *icon; + char **only_show_in; + char **not_show_in; + char *try_exec; + char *exec; + char *binary; + char *path; + + guint nodisplay : 1; + guint hidden : 1; + guint terminal : 1; + guint startup_notify : 1; + /* FIXME: what about StartupWMClass ? */ +}; + +G_DEFINE_TYPE_WITH_CODE (GDesktopAppInfo, g_desktop_app_info, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (G_TYPE_APP_INFO, + g_desktop_app_info_iface_init)) + +static gpointer +search_path_init (gpointer data) +{ + char **args = NULL; + const char * const *data_dirs; + const char *user_data_dir; + int i, length, j; + + data_dirs = g_get_system_data_dirs (); + length = g_strv_length ((char **) data_dirs); + + args = g_new (char *, length + 2); + + j = 0; + user_data_dir = g_get_user_data_dir (); + args[j++] = g_build_filename (user_data_dir, "applications", NULL); + for (i = 0; i < length; i++) + args[j++] = g_build_filename (data_dirs[i], + "applications", NULL); + args[j++] = NULL; + + return args; +} + +static const char * const * +get_applications_search_path (void) +{ + static GOnce once_init = G_ONCE_INIT; + return g_once (&once_init, search_path_init, NULL); +} + +static void +g_desktop_app_info_finalize (GObject *object) +{ + GDesktopAppInfo *info; + + info = G_DESKTOP_APP_INFO (object); + + g_free (info->desktop_id); + g_free (info->filename); + g_free (info->name); + g_free (info->comment); + g_free (info->icon_name); + if (info->icon) + g_object_unref (info->icon); + g_strfreev (info->only_show_in); + g_strfreev (info->not_show_in); + g_free (info->try_exec); + g_free (info->exec); + g_free (info->binary); + g_free (info->path); + + G_OBJECT_CLASS (g_desktop_app_info_parent_class)->finalize (object); +} + +static void +g_desktop_app_info_class_init (GDesktopAppInfoClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->finalize = g_desktop_app_info_finalize; +} + +static void +g_desktop_app_info_init (GDesktopAppInfo *local) +{ +} + +/** + * g_desktop_app_info_new_from_filename: + * @filename: a string containing a file name. + * + * Returns: a new #GDesktopAppInfo or %NULL on error. + **/ +GDesktopAppInfo * +g_desktop_app_info_new_from_filename (const char *filename) +{ + GDesktopAppInfo *info; + GKeyFile *key_file; + char *start_group; + char *type; + char *try_exec; + + key_file = g_key_file_new (); + + if (!g_key_file_load_from_file (key_file, + filename, + G_KEY_FILE_NONE, + NULL)) + return NULL; + + start_group = g_key_file_get_start_group (key_file); + if (start_group == NULL || strcmp (start_group, G_KEY_FILE_DESKTOP_GROUP) != 0) + { + g_free (start_group); + return NULL; + } + g_free (start_group); + + type = g_key_file_get_string (key_file, + G_KEY_FILE_DESKTOP_GROUP, + G_KEY_FILE_DESKTOP_KEY_TYPE, + NULL); + if (type == NULL || strcmp (type, G_KEY_FILE_DESKTOP_TYPE_APPLICATION) != 0) + { + g_free (type); + return NULL; + } + g_free (type); + + try_exec = g_key_file_get_string (key_file, + G_KEY_FILE_DESKTOP_GROUP, + G_KEY_FILE_DESKTOP_KEY_TRY_EXEC, + NULL); + if (try_exec) + { + char *t; + t = g_find_program_in_path (try_exec); + if (t == NULL) + { + g_free (try_exec); + return NULL; + } + g_free (t); + } + + info = g_object_new (G_TYPE_DESKTOP_APP_INFO, NULL); + info->filename = g_strdup (filename); + + info->name = g_key_file_get_locale_string (key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_NAME, NULL, NULL); + info->comment = g_key_file_get_locale_string (key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_COMMENT, NULL, NULL); + info->nodisplay = g_key_file_get_boolean (key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_NO_DISPLAY, NULL) != FALSE; + info->icon_name = g_key_file_get_locale_string (key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_ICON, NULL, NULL); + info->only_show_in = g_key_file_get_string_list (key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_ONLY_SHOW_IN, NULL, NULL); + info->not_show_in = g_key_file_get_string_list (key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_NOT_SHOW_IN, NULL, NULL); + info->try_exec = try_exec; + info->exec = g_key_file_get_string (key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_EXEC, NULL); + info->path = g_key_file_get_string (key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_PATH, NULL); + info->terminal = g_key_file_get_boolean (key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_TERMINAL, NULL) != FALSE; + info->startup_notify = g_key_file_get_boolean (key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_STARTUP_NOTIFY, NULL) != FALSE; + info->hidden = g_key_file_get_boolean (key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_HIDDEN, NULL) != FALSE; + + info->icon = NULL; + if (info->icon_name) + { + if (g_path_is_absolute (info->icon_name)) + { + GFile *file; + + file = g_file_new_for_path (info->icon_name); + info->icon = g_file_icon_new (file); + g_object_unref (file); + } + else + info->icon = g_themed_icon_new (info->icon_name); + } + + if (info->exec) + { + char *p, *start; + + p = info->exec; + while (*p == ' ') + p++; + start = p; + while (*p != ' ' && *p != 0) + p++; + + info->binary = g_strndup (start, p - start); + } + + return info; +} + +/** + * g_desktop_app_info_new: + * @desktop_id: + * + * Returns: a new #GDesktopAppInfo. + **/ +GDesktopAppInfo * +g_desktop_app_info_new (const char *desktop_id) +{ + GDesktopAppInfo *appinfo; + const char * const *dirs; + int i; + + dirs = get_applications_search_path (); + + for (i = 0; dirs[i] != NULL; i++) + { + char *basename; + char *filename; + char *p; + + filename = g_build_filename (dirs[i], desktop_id, NULL); + appinfo = g_desktop_app_info_new_from_filename (filename); + g_free (filename); + if (appinfo != NULL) + { + goto found; + } + + basename = g_strdup (desktop_id); + p = basename; + while ((p = strchr (p, '-')) != NULL) + { + *p = '/'; + + filename = g_build_filename (dirs[i], basename, NULL); + appinfo = g_desktop_app_info_new_from_filename (filename); + g_free (filename); + if (appinfo != NULL) + { + g_free (basename); + goto found; + } + *p = '-'; + p++; + } + } + + return NULL; + + found: + appinfo->desktop_id = g_strdup (desktop_id); + + if (g_desktop_app_info_get_is_hidden (appinfo)) + { + g_object_unref (appinfo); + appinfo = NULL; + } + + return appinfo; +} + +static GAppInfo * +g_desktop_app_info_dup (GAppInfo *appinfo) +{ + GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo); + GDesktopAppInfo *new_info; + + new_info = g_object_new (G_TYPE_DESKTOP_APP_INFO, NULL); + + new_info->filename = g_strdup (info->filename); + new_info->desktop_id = g_strdup (info->desktop_id); + + new_info->name = g_strdup (info->name); + new_info->comment = g_strdup (info->comment); + new_info->nodisplay = info->nodisplay; + new_info->icon_name = g_strdup (info->icon_name); + new_info->icon = g_object_ref (info->icon); + new_info->only_show_in = g_strdupv (info->only_show_in); + new_info->not_show_in = g_strdupv (info->not_show_in); + new_info->try_exec = g_strdup (info->try_exec); + new_info->exec = g_strdup (info->exec); + new_info->binary = g_strdup (info->binary); + new_info->path = g_strdup (info->path); + new_info->hidden = info->hidden; + new_info->terminal = info->terminal; + new_info->startup_notify = info->startup_notify; + + return G_APP_INFO (new_info); +} + +static gboolean +g_desktop_app_info_equal (GAppInfo *appinfo1, + GAppInfo *appinfo2) +{ + GDesktopAppInfo *info1 = G_DESKTOP_APP_INFO (appinfo1); + GDesktopAppInfo *info2 = G_DESKTOP_APP_INFO (appinfo2); + + if (info1->desktop_id == NULL || + info2->desktop_id == NULL) + return FALSE; + + return strcmp (info1->desktop_id, info2->desktop_id) == 0; +} + +static const char * +g_desktop_app_info_get_id (GAppInfo *appinfo) +{ + GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo); + + return info->desktop_id; +} + +static const char * +g_desktop_app_info_get_name (GAppInfo *appinfo) +{ + GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo); + + if (info->name == NULL) + return _("Unnamed"); + return info->name; +} + +/** + * g_desktop_app_info_get_is_hidden: + * @info: + * + * Returns: %TRUE if hidden, %FALSE otherwise. + **/ +gboolean +g_desktop_app_info_get_is_hidden (GDesktopAppInfo *info) +{ + return info->hidden; +} + +static const char * +g_desktop_app_info_get_description (GAppInfo *appinfo) +{ + GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo); + + return info->comment; +} + +static const char * +g_desktop_app_info_get_executable (GAppInfo *appinfo) +{ + GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo); + + return info->binary; +} + +static GIcon * +g_desktop_app_info_get_icon (GAppInfo *appinfo) +{ + GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo); + + return info->icon; +} + +static char * +expand_macro_single (char macro, GFile *file) +{ + char *result = NULL; + char *uri, *path; + + path = g_file_get_path (file); + uri = g_file_get_uri (file); + + switch (macro) + { + case 'u': + case 'U': + result = g_shell_quote (uri); + break; + case 'f': + case 'F': + if (path) + result = g_shell_quote (path); + break; + case 'd': + case 'D': + if (path) + result = g_shell_quote (g_path_get_dirname (path)); + break; + case 'n': + case 'N': + if (path) + result = g_shell_quote (g_path_get_basename (path)); + break; + } + + g_free (path); + g_free (uri); + + return result; +} + +static void +expand_macro (char macro, GString *exec, GDesktopAppInfo *info, GList **file_list) +{ + GList *files = *file_list; + char *expanded; + + g_return_if_fail (exec != NULL); + + switch (macro) + { + case 'u': + case 'f': + case 'd': + case 'n': + if (files) + { + expanded = expand_macro_single (macro, files->data); + if (expanded) + { + g_string_append (exec, expanded); + g_free (expanded); + } + files = files->next; + } + + break; + + case 'U': + case 'F': + case 'D': + case 'N': + while (files) + { + expanded = expand_macro_single (macro, files->data); + if (expanded) + { + g_string_append (exec, expanded); + g_free (expanded); + } + + files = files->next; + + if (files != NULL && expanded) + g_string_append_c (exec, ' '); + } + + break; + + case 'i': + if (info->icon_name) + { + g_string_append (exec, "--icon "); + g_string_append (exec, info->icon_name); + } + break; + + case 'c': + if (info->name) + g_string_append (exec, info->name); + break; + + case 'k': + if (info->filename) + g_string_append (exec, info->filename); + break; + + case 'm': /* deprecated */ + break; + + case '%': + g_string_append_c (exec, '%'); + break; + } + + *file_list = files; +} + +static gboolean +expand_application_parameters (GDesktopAppInfo *info, + GList **files, + int *argc, + char ***argv, + GError **error) +{ + GList *file_list = *files; + const char *p = info->exec; + GString *expanded_exec = g_string_new (NULL); + gboolean res; + + if (info->exec == NULL) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + _("Desktop file didn't specify Exec field")); + return FALSE; + } + + while (*p) + { + if (p[0] == '%' && p[1] != '\0') + { + expand_macro (p[1], expanded_exec, info, files); + p++; + } + else + g_string_append_c (expanded_exec, *p); + + p++; + } + + /* No file substitutions */ + if (file_list == *files && file_list != NULL) + { + /* If there is no macro default to %f. This is also what KDE does */ + g_string_append_c (expanded_exec, ' '); + expand_macro ('f', expanded_exec, info, files); + } + + res = g_shell_parse_argv (expanded_exec->str, argc, argv, error); + g_string_free (expanded_exec, TRUE); + return res; +} + +static gboolean +prepend_terminal_to_vector (int *argc, + char ***argv) +{ +#ifndef G_OS_WIN32 + char **real_argv; + int real_argc; + int i, j; + char **term_argv = NULL; + int term_argc = 0; + char *check; + char **the_argv; + + g_return_val_if_fail (argc != NULL, FALSE); + g_return_val_if_fail (argv != NULL, FALSE); + + /* sanity */ + if(*argv == NULL) + *argc = 0; + + the_argv = *argv; + + /* compute size if not given */ + if (*argc < 0) + { + for (i = 0; the_argv[i] != NULL; i++) + ; + *argc = i; + } + + term_argc = 2; + term_argv = g_new0 (char *, 3); + + check = g_find_program_in_path ("gnome-terminal"); + if (check != NULL) + { + term_argv[0] = check; + /* Note that gnome-terminal takes -x and + * as -e in gnome-terminal is broken we use that. */ + term_argv[1] = g_strdup ("-x"); + } + else + { + if (check == NULL) + check = g_find_program_in_path ("nxterm"); + if (check == NULL) + check = g_find_program_in_path ("color-xterm"); + if (check == NULL) + check = g_find_program_in_path ("rxvt"); + if (check == NULL) + check = g_find_program_in_path ("xterm"); + if (check == NULL) + check = g_find_program_in_path ("dtterm"); + if (check == NULL) + { + check = g_strdup ("xterm"); + g_warning ("couldn't find a terminal, falling back to xterm"); + } + term_argv[0] = check; + term_argv[1] = g_strdup ("-e"); + } + + real_argc = term_argc + *argc; + real_argv = g_new (char *, real_argc + 1); + + for (i = 0; i < term_argc; i++) + real_argv[i] = term_argv[i]; + + for (j = 0; j < *argc; j++, i++) + real_argv[i] = (char *)the_argv[j]; + + real_argv[i] = NULL; + + g_free (*argv); + *argv = real_argv; + *argc = real_argc; + + /* we use g_free here as we sucked all the inner strings + * out from it into real_argv */ + g_free (term_argv); + return TRUE; +#else + return FALSE; +#endif /* G_OS_WIN32 */ +} + +/* '=' is the new '\0'. + * DO NOT CALL unless at least one string ends with '=' + */ +static gboolean +is_env (const char *a, + const char *b) +{ + while (*a == *b) + { + if (*a == 0 || *b == 0) + return FALSE; + + if (*a == '=') + return TRUE; + + a++; + b++; + } + + return FALSE; +} + +/* free with g_strfreev */ +static char ** +replace_env_var (char **old_environ, + const char *env_var, + const char *new_value) +{ + int length, new_length; + int index, new_index; + char **new_environ; + int i, new_i; + + /* do two things at once: + * - discover the length of the environment ('length') + * - find the location (if any) of the env var ('index') + */ + index = -1; + for (length = 0; old_environ[length]; length++) + { + /* if we already have it in our environment, replace */ + if (is_env (old_environ[length], env_var)) + index = length; + } + + + /* no current env var, no desired env value. + * this is easy :) + */ + if (new_value == NULL && index == -1) + return old_environ; + + /* in all cases now, we will be using a modified environment. + * determine its length and allocated it. + * + * after this block: + * new_index = location to insert, if any + * new_length = length of the new array + * new_environ = the pointer array for the new environment + */ + + if (new_value == NULL && index >= 0) + { + /* in this case, we will be removing an entry */ + new_length = length - 1; + new_index = -1; + } + else if (new_value != NULL && index < 0) + { + /* in this case, we will be adding an entry to the end */ + new_length = length + 1; + new_index = length; + } + else + /* in this case, we will be replacing the existing entry */ + { + new_length = length; + new_index = index; + } + + new_environ = g_malloc (sizeof (char *) * (new_length + 1)); + new_environ[new_length] = NULL; + + /* now we do the copying. + * for each entry in the new environment, we decide what to do + */ + + i = 0; + for (new_i = 0; new_i < new_length; new_i++) + { + if (new_i == new_index) + { + /* insert our new item */ + new_environ[new_i] = g_strconcat (env_var, + "=", + new_value, + NULL); + + /* if we had an old entry, skip it now */ + if (index >= 0) + i++; + } + else + { + /* if this is the old DESKTOP_STARTUP_ID, skip it */ + if (i == index) + i++; + + /* copy an old item */ + new_environ[new_i] = g_strdup (old_environ[i]); + i++; + } + } + + g_strfreev (old_environ); + + return new_environ; +} + +static GList * +dup_list_segment (GList *start, + GList *end) +{ + GList *res; + + res = NULL; + while (start != NULL && start != end) + { + res = g_list_prepend (res, start->data); + start = start->next; + } + + return g_list_reverse (res); +} + +static gboolean +g_desktop_app_info_launch (GAppInfo *appinfo, + GList *files, + GAppLaunchContext *launch_context, + GError **error) +{ + GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo); + gboolean completed = FALSE; + GList *old_files; + GList *launched_files; + char **envp; + char **argv; + int argc; + char *display; + char *sn_id; + + g_return_val_if_fail (appinfo != NULL, FALSE); + + argv = NULL; + envp = NULL; + + do + { + old_files = files; + if (!expand_application_parameters (info, &files, + &argc, &argv, error)) + goto out; + + if (info->terminal && !prepend_terminal_to_vector (&argc, &argv)) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + _("Unable to find terminal required for application")); + goto out; + } + + sn_id = NULL; + if (launch_context) + { + launched_files = dup_list_segment (old_files, files); + + display = g_app_launch_context_get_display (launch_context, + appinfo, + launched_files); + + sn_id = NULL; + if (info->startup_notify) + sn_id = g_app_launch_context_get_startup_notify_id (launch_context, + appinfo, + launched_files); + + if (display || sn_id) + { + envp = g_listenv (); + + if (display) + envp = replace_env_var (envp, + "DISPLAY", + display); + + if (sn_id) + envp = replace_env_var (envp, + "DESKTOP_STARTUP_ID", + sn_id); + } + + g_free (display); + + g_list_free (launched_files); + } + + if (!g_spawn_async (info->path, /* working directory */ + argv, + envp, + G_SPAWN_SEARCH_PATH /* flags */, + NULL /* child_setup */, + NULL /* data */, + NULL /* child_pid */, + error)) + { + if (sn_id) + { + g_app_launch_context_launch_failed (launch_context, sn_id); + g_free (sn_id); + } + goto out; + } + + + g_free (sn_id); + + g_strfreev (envp); + g_strfreev (argv); + envp = NULL; + argv = NULL; + } + while (files != NULL); + + completed = TRUE; + + out: + g_strfreev (argv); + g_strfreev (envp); + + return completed; +} + +static gboolean +g_desktop_app_info_supports_uris (GAppInfo *appinfo) +{ + GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo); + + return + (strstr (info->exec, "%u") != NULL) || + (strstr (info->exec, "%U") != NULL); +} + +static gboolean +g_desktop_app_info_supports_xdg_startup_notify (GAppInfo *appinfo) +{ + GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo); + + return info->startup_notify; +} + +static gboolean +g_desktop_app_info_launch_uris (GAppInfo *appinfo, + GList *uris, + GAppLaunchContext *launch_context, + GError **error) +{ + GList *files; + GFile *file; + gboolean res; + + files = NULL; + while (uris) + { + file = g_file_new_for_uri (uris->data); + if (file == NULL) + g_warning ("Invalid uri passed to g_desktop_app_info_launch_uris"); + + if (file) + files = g_list_prepend (files, file); + } + + files = g_list_reverse (files); + + res = g_desktop_app_info_launch (appinfo, files, launch_context, error); + + g_list_foreach (files, (GFunc)g_object_unref, NULL); + g_list_free (files); + + return res; +} + +static gboolean +g_desktop_app_info_should_show (GAppInfo *appinfo, + const char *desktop_env) +{ + GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo); + gboolean found; + int i; + + if (info->nodisplay) + return FALSE; + + if (info->only_show_in) + { + if (desktop_env == NULL) + return FALSE; + + found = FALSE; + for (i = 0; info->only_show_in[i] != NULL; i++) + { + if (strcmp (info->only_show_in[i], desktop_env) == 0) + { + found = TRUE; + break; + } + } + if (!found) + return FALSE; + } + + if (info->not_show_in && desktop_env) + { + for (i = 0; info->not_show_in[i] != NULL; i++) + { + if (strcmp (info->not_show_in[i], desktop_env) == 0) + return FALSE; + } + } + + return TRUE; +} + +typedef enum { + APP_DIR, + MIMETYPE_DIR +} DirType; + +static char * +ensure_dir (DirType type, + GError **error) +{ + char *path, *display_name; + int err; + + if (type == APP_DIR) + { + path = g_build_filename (g_get_user_data_dir (), "applications", NULL); + } + else + { + path = g_build_filename (g_get_user_data_dir (), "mime", "packages", NULL); + } + + errno = 0; + if (g_mkdir_with_parents (path, 0700) == 0) + return path; + + err = errno; + display_name = g_filename_display_name (path); + if (type == APP_DIR) + { + g_set_error (error, G_IO_ERROR, g_io_error_from_errno (err), + _("Can't create user application configuration folder %s: %s"), + display_name, g_strerror (err)); + } + else + { + g_set_error (error, G_IO_ERROR, g_io_error_from_errno (err), + _("Can't create user mime configuration folder %s: %s"), + display_name, g_strerror (err)); + } + + g_free (display_name); + g_free (path); + + return NULL; +} + +static gboolean +update_default_list (const char *desktop_id, const char *content_type, gboolean add, GError **error) +{ + char *dirname, *filename; + GKeyFile *key_file; + gboolean load_succeeded, res; + char **old_list; + char **list; + gsize length, data_size; + char *data; + int i, j; + + dirname = ensure_dir (APP_DIR, error); + if (!dirname) + return FALSE; + + filename = g_build_filename (dirname, "defaults.list", NULL); + g_free (dirname); + + key_file = g_key_file_new (); + load_succeeded = g_key_file_load_from_file (key_file, filename, G_KEY_FILE_NONE, NULL); + if (!load_succeeded || !g_key_file_has_group (key_file, DEFAULT_APPLICATIONS_GROUP)) + { + g_key_file_free (key_file); + key_file = g_key_file_new (); + } + + length = 0; + old_list = g_key_file_get_string_list (key_file, DEFAULT_APPLICATIONS_GROUP, + content_type, &length, NULL); + + list = g_new (char *, 1 + length + 1); + + i = 0; + if (add) + list[i++] = g_strdup (desktop_id); + if (old_list) + { + for (j = 0; old_list[j] != NULL; j++) + { + if (strcmp (old_list[j], desktop_id) != 0) + list[i++] = g_strdup (old_list[j]); + } + } + list[i] = NULL; + + g_strfreev (old_list); + + g_key_file_set_string_list (key_file, + DEFAULT_APPLICATIONS_GROUP, + content_type, + (const char * const *)list, i); + + g_strfreev (list); + + data = g_key_file_to_data (key_file, &data_size, error); + g_key_file_free (key_file); + + res = g_file_set_contents (filename, data, data_size, error); + + mime_info_cache_reload (NULL); + + g_free (filename); + g_free (data); + + return res; +} + +static gboolean +g_desktop_app_info_set_as_default_for_type (GAppInfo *appinfo, + const char *content_type, + GError **error) +{ + GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo); + + if (!g_app_info_add_supports_type (appinfo, content_type, error)) + return FALSE; + + return update_default_list (info->desktop_id, content_type, TRUE, error); +} + +static void +update_program_done (GPid pid, + gint status, + gpointer data) +{ + /* Did the application exit correctly */ + if (WIFEXITED (status) && + WEXITSTATUS (status) == 0) + { + /* Here we could clean out any caches in use */ + } +} + +static void +run_update_command (char *command, + char *subdir) +{ + char *argv[3] = { + NULL, + NULL, + NULL, + }; + GPid pid = 0; + GError *error = NULL; + + argv[0] = command; + argv[1] = g_build_filename (g_get_user_data_dir (), subdir, NULL); + + if (g_spawn_async ("/", argv, + NULL, /* envp */ + G_SPAWN_SEARCH_PATH | + G_SPAWN_STDOUT_TO_DEV_NULL | + G_SPAWN_STDERR_TO_DEV_NULL | + G_SPAWN_DO_NOT_REAP_CHILD, + NULL, NULL, /* No setup function */ + &pid, + NULL)) + g_child_watch_add (pid, update_program_done, NULL); + else + { + /* If we get an error at this point, it's quite likely the user doesn't + * have an installed copy of either 'update-mime-database' or + * 'update-desktop-database'. I don't think we want to popup an error + * dialog at this point, so we just do a g_warning to give the user a + * chance of debugging it. + */ + g_warning ("%s", error->message); + } + + g_free (argv[1]); +} + +static gboolean +g_desktop_app_info_set_as_default_for_extension (GAppInfo *appinfo, + const char *extension, + GError **error) +{ + char *filename, *basename, *mimetype; + char *dirname; + gboolean res; + + dirname = ensure_dir (MIMETYPE_DIR, error); + if (!dirname) + return FALSE; + + basename = g_strdup_printf ("user-extension-%s.xml", extension); + filename = g_build_filename (dirname, basename, NULL); + g_free (basename); + g_free (dirname); + + mimetype = g_strdup_printf ("application/x-extension-%s", extension); + + if (!g_file_test (filename, G_FILE_TEST_EXISTS)) { + char *contents; + + contents = + g_strdup_printf ("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + "<mime-info xmlns=\"http://www.freedesktop.org/standards/shared-mime-info\">\n" + " <mime-type type=\"%s\">\n" + " <comment>%s document</comment>\n" + " <glob pattern=\"*.%s\"/>\n" + " </mime-type>\n" + "</mime-info>\n", mimetype, extension, extension); + + g_file_set_contents (filename, contents, -1, NULL); + g_free (contents); + + run_update_command ("update-mime-database", "mime"); + } + g_free (filename); + + res = g_desktop_app_info_set_as_default_for_type (appinfo, + mimetype, + error); + + g_free (mimetype); + + return res; +} + +static gboolean +g_desktop_app_info_add_supports_type (GAppInfo *appinfo, + const char *content_type, + GError **error) +{ + GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo); + GKeyFile *keyfile; + char *new_mimetypes, *old_mimetypes, *content; + char *dirname; + char *filename; + + keyfile = g_key_file_new (); + if (!g_key_file_load_from_file (keyfile, info->filename, + G_KEY_FILE_KEEP_COMMENTS | + G_KEY_FILE_KEEP_TRANSLATIONS, error)) + { + g_key_file_free (keyfile); + return FALSE; + } + + old_mimetypes = g_key_file_get_string (keyfile, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_MIME_TYPE, NULL); + new_mimetypes = g_strconcat (content_type, ";", old_mimetypes, NULL); + g_key_file_set_string (keyfile, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_MIME_TYPE, new_mimetypes); + g_free (old_mimetypes); + g_free (new_mimetypes); + + content = g_key_file_to_data (keyfile, NULL, NULL); + g_key_file_free (keyfile); + + dirname = ensure_dir (APP_DIR, error); + if (!dirname) + { + g_free (content); + return FALSE; + } + + filename = g_build_filename (dirname, info->desktop_id, NULL); + g_free (dirname); + + if (!g_file_set_contents (filename, content, -1, error)) + { + g_free (filename); + g_free (content); + return FALSE; + } + g_free (filename); + g_free (content); + + run_update_command ("update-desktop-database", "applications"); + return TRUE; +} + +static gboolean +g_desktop_app_info_can_remove_supports_type (GAppInfo *appinfo) +{ + GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo); + char *user_dirname; + + user_dirname = g_build_filename (g_get_user_data_dir (), "applications", NULL); + return g_str_has_prefix (info->filename, user_dirname); +} + +static gboolean +g_desktop_app_info_remove_supports_type (GAppInfo *appinfo, + const char *content_type, + GError **error) +{ + GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo); + GKeyFile *keyfile; + char *new_mimetypes, *old_mimetypes, *content; + char *found; + char *filename; + char *dirname; + + keyfile = g_key_file_new (); + if (!g_key_file_load_from_file (keyfile, info->filename, + G_KEY_FILE_KEEP_COMMENTS | + G_KEY_FILE_KEEP_TRANSLATIONS, error)) + { + g_key_file_free (keyfile); + return FALSE; + } + + old_mimetypes = g_key_file_get_string (keyfile, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_MIME_TYPE, NULL); + new_mimetypes = g_strdup (old_mimetypes); + found = NULL; + if (new_mimetypes) + found = strstr (new_mimetypes, content_type); + if (found && *(found + strlen (content_type)) == ';') + { + char *rest = found + strlen (content_type) + 1; + memmove (found, rest, strlen (rest) + 1); + } + g_key_file_set_string (keyfile, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_MIME_TYPE, new_mimetypes); + g_free (old_mimetypes); + g_free (new_mimetypes); + + content = g_key_file_to_data (keyfile, NULL, NULL); + g_key_file_free (keyfile); + + dirname = ensure_dir (APP_DIR, error); + if (!dirname) + { + g_free (content); + return FALSE; + } + + filename = g_build_filename (dirname, info->desktop_id, NULL); + g_free (dirname); + if (!g_file_set_contents (filename, content, -1, error)) + { + g_free (filename); + g_free (content); + return FALSE; + } + g_free (filename); + g_free (content); + + run_update_command ("update-desktop-database", "applications"); + + return update_default_list (info->desktop_id, content_type, FALSE, error); +} + +/** + * g_app_info_create_from_commandline: + * @commandline: + * @application_name: + * @flags: + * @error: a #GError location to store the error occuring, or %NULL to + * ignore. + * Returns: new #GAppInfo for given command. + **/ +GAppInfo * +g_app_info_create_from_commandline (const char *commandline, + const char *application_name, + GAppInfoCreateFlags flags, + GError **error) +{ + GKeyFile *key_file; + char *dirname; + char **split; + char *basename, *exec, *filename, *comment; + char *data, *desktop_id; + gsize data_size; + int fd; + GDesktopAppInfo *info; + gboolean res; + + dirname = ensure_dir (APP_DIR, error); + if (!dirname) + return NULL; + + key_file = g_key_file_new (); + + g_key_file_set_string (key_file, G_KEY_FILE_DESKTOP_GROUP, + "Encoding", "UTF-8"); + g_key_file_set_string (key_file, G_KEY_FILE_DESKTOP_GROUP, + G_KEY_FILE_DESKTOP_KEY_VERSION, "1.0"); + g_key_file_set_string (key_file, G_KEY_FILE_DESKTOP_GROUP, + G_KEY_FILE_DESKTOP_KEY_TYPE, + G_KEY_FILE_DESKTOP_TYPE_APPLICATION); + if (flags & G_APP_INFO_CREATE_NEEDS_TERMINAL) + g_key_file_set_boolean (key_file, G_KEY_FILE_DESKTOP_GROUP, + G_KEY_FILE_DESKTOP_KEY_TERMINAL, TRUE); + + exec = g_strconcat (commandline, " %f", NULL); + g_key_file_set_string (key_file, G_KEY_FILE_DESKTOP_GROUP, + G_KEY_FILE_DESKTOP_KEY_EXEC, exec); + g_free (exec); + + /* FIXME: this should be more robust. Maybe g_shell_parse_argv and use argv[0] */ + split = g_strsplit (commandline, " ", 2); + basename = g_path_get_basename (split[0]); + g_strfreev (split); + g_key_file_set_string (key_file, G_KEY_FILE_DESKTOP_GROUP, + G_KEY_FILE_DESKTOP_KEY_NAME, application_name?application_name:basename); + + comment = g_strdup_printf (_("Custom definition for %s"), basename); + g_key_file_set_string (key_file, G_KEY_FILE_DESKTOP_GROUP, + G_KEY_FILE_DESKTOP_KEY_COMMENT, comment); + g_free (comment); + + g_key_file_set_boolean (key_file, G_KEY_FILE_DESKTOP_GROUP, + G_KEY_FILE_DESKTOP_KEY_NO_DISPLAY, TRUE); + + data = g_key_file_to_data (key_file, &data_size, NULL); + g_key_file_free (key_file); + + desktop_id = g_strdup_printf ("userapp-%s-XXXXXX.desktop", basename); + g_free (basename); + filename = g_build_filename (dirname, desktop_id, NULL); + g_free (desktop_id); + g_free (dirname); + + fd = g_mkstemp (filename); + if (fd == -1) + { + char *display_name; + + display_name = g_filename_display_name (filename); + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + _("Can't create user desktop file %s"), display_name); + g_free (display_name); + g_free (filename); + g_free (data); + return NULL; + } + + desktop_id = g_path_get_basename (filename); + + close (fd); + + res = g_file_set_contents (filename, data, data_size, error); + if (!res) + { + g_free (desktop_id); + g_free (filename); + return NULL; + } + + run_update_command ("update-desktop-database", "applications"); + + info = g_desktop_app_info_new_from_filename (filename); + g_free (filename); + if (info == NULL) + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + _("Can't load just created desktop file")); + else + info->desktop_id = g_strdup (desktop_id); + + g_free (desktop_id); + + return G_APP_INFO (info); +} + + +static void +g_desktop_app_info_iface_init (GAppInfoIface *iface) +{ + iface->dup = g_desktop_app_info_dup; + iface->equal = g_desktop_app_info_equal; + iface->get_id = g_desktop_app_info_get_id; + iface->get_name = g_desktop_app_info_get_name; + iface->get_description = g_desktop_app_info_get_description; + iface->get_executable = g_desktop_app_info_get_executable; + iface->get_icon = g_desktop_app_info_get_icon; + iface->launch = g_desktop_app_info_launch; + iface->supports_uris = g_desktop_app_info_supports_uris; + iface->supports_xdg_startup_notify = g_desktop_app_info_supports_xdg_startup_notify; + iface->launch_uris = g_desktop_app_info_launch_uris; + iface->should_show = g_desktop_app_info_should_show; + iface->set_as_default_for_type = g_desktop_app_info_set_as_default_for_type; + iface->set_as_default_for_extension = g_desktop_app_info_set_as_default_for_extension; + iface->add_supports_type = g_desktop_app_info_add_supports_type; + iface->can_remove_supports_type = g_desktop_app_info_can_remove_supports_type; + iface->remove_supports_type = g_desktop_app_info_remove_supports_type; +} + +static gboolean +app_info_in_list (GAppInfo *info, GList *l) +{ + while (l != NULL) + { + if (g_app_info_equal (info, l->data)) + return TRUE; + l = l->next; + } + return FALSE; +} + + +/** + * g_app_info_get_all_for_type: + * @content_type: + * + * Returns: #GList of #GAppInfo s for given @content_type. + **/ +GList * +g_app_info_get_all_for_type (const char *content_type) +{ + GList *desktop_entries, *l; + GList *infos; + GDesktopAppInfo *info; + + desktop_entries = get_all_desktop_entries_for_mime_type (content_type); + + infos = NULL; + for (l = desktop_entries; l != NULL; l = l->next) + { + char *desktop_entry = l->data; + + info = g_desktop_app_info_new (desktop_entry); + if (info) + { + if (app_info_in_list (G_APP_INFO (info), infos)) + g_object_unref (info); + else + infos = g_list_prepend (infos, info); + } + g_free (desktop_entry); + } + + g_list_free (desktop_entries); + + return g_list_reverse (infos); +} + + +/** + * g_app_info-get_default_for_type: + * @content_type: + * @must_support_uris: + * + * Returns: #GAppInfo for given @content_type. + **/ +GAppInfo * +g_app_info_get_default_for_type (const char *content_type, + gboolean must_support_uris) +{ + GList *desktop_entries, *l; + GAppInfo *info; + + desktop_entries = get_all_desktop_entries_for_mime_type (content_type); + + info = NULL; + for (l = desktop_entries; l != NULL; l = l->next) + { + char *desktop_entry = l->data; + + info = (GAppInfo *)g_desktop_app_info_new (desktop_entry); + if (info) + { + if (must_support_uris && !g_app_info_supports_uris (info)) + { + g_object_unref (info); + info = NULL; + } + else + break; + } + } + + g_list_foreach (desktop_entries, (GFunc)g_free, NULL); + g_list_free (desktop_entries); + + return info; +} + + +/** + * g_app_info_get_default_for_uri_scheme: + * @uri_scheme: + * + * Returns: #GAppInfo + **/ +GAppInfo * +g_app_info_get_default_for_uri_scheme (const char *uri_scheme) +{ + /* TODO: Implement this using giomodules, reading the gconf settings + * in /desktop/gnome/url-handlers + */ + return NULL; +} + + +static void +get_apps_from_dir (GHashTable *apps, const char *dirname, const char *prefix) +{ + GDir *dir; + const char *basename; + char *filename, *subprefix, *desktop_id; + gboolean hidden; + GDesktopAppInfo *appinfo; + + dir = g_dir_open (dirname, 0, NULL); + if (dir) + { + while ((basename = g_dir_read_name (dir)) != NULL) + { + filename = g_build_filename (dirname, basename, NULL); + if (g_str_has_suffix (basename, ".desktop")) + { + desktop_id = g_strconcat (prefix, basename, NULL); + + /* Use _extended so we catch NULLs too (hidden) */ + if (!g_hash_table_lookup_extended (apps, desktop_id, NULL, NULL)) + { + appinfo = g_desktop_app_info_new_from_filename (filename); + + /* Don't return apps that don't take arguments */ + if (appinfo && + g_desktop_app_info_get_is_hidden (appinfo) && + strstr (appinfo->exec,"%U") == NULL && + strstr (appinfo->exec,"%u") == NULL && + strstr (appinfo->exec,"%f") == NULL && + strstr (appinfo->exec,"%F") == NULL) + { + g_object_unref (appinfo); + appinfo = NULL; + hidden = TRUE; + } + + if (appinfo != NULL || hidden) + { + g_hash_table_insert (apps, g_strdup (desktop_id), appinfo); + + if (appinfo) + { + /* Reuse instead of strdup here */ + appinfo->desktop_id = desktop_id; + desktop_id = NULL; + } + } + } + g_free (desktop_id); + } + else + { + if (g_file_test (filename, G_FILE_TEST_IS_DIR)) + { + subprefix = g_strconcat (prefix, basename, "-", NULL); + get_apps_from_dir (apps, filename, subprefix); + g_free (subprefix); + } + } + g_free (filename); + } + g_dir_close (dir); + } +} + +static void +collect_apps (gpointer key, + gpointer value, + gpointer user_data) +{ + GList **infos = user_data; + + if (value) + *infos = g_list_prepend (*infos, value); +} + + +/** + * g_app_info_get_all: + * + * Returns: a newly allocated #GList of references to #GAppInfo s. + **/ +GList * +g_app_info_get_all (void) +{ + const char * const *dirs; + GHashTable *apps; + int i; + GList *infos; + + dirs = get_applications_search_path (); + + apps = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, NULL); + + + for (i = 0; dirs[i] != NULL; i++) + get_apps_from_dir (apps, dirs[i], ""); + + + infos = NULL; + g_hash_table_foreach (apps, + collect_apps, + &infos); + + g_hash_table_destroy (apps); + + return g_list_reverse (infos); +} + +/* Cacheing of mimeinfo.cache and defaults.list files */ + +typedef struct { + char *path; + GHashTable *mime_info_cache_map; + GHashTable *defaults_list_map; + time_t mime_info_cache_timestamp; + time_t defaults_list_timestamp; +} MimeInfoCacheDir; + +typedef struct { + GList *dirs; /* mimeinfo.cache and defaults.list */ + GHashTable *global_defaults_cache; /* global results of defaults.list lookup and validation */ + time_t last_stat_time; + guint should_ping_mime_monitor : 1; +} MimeInfoCache; + +static MimeInfoCache *mime_info_cache = NULL; +G_LOCK_DEFINE_STATIC (mime_info_cache); + +static void mime_info_cache_dir_add_desktop_entries (MimeInfoCacheDir *dir, + const char *mime_type, + char **new_desktop_file_ids); + +static MimeInfoCache * mime_info_cache_new (void); + +static void +destroy_info_cache_value (gpointer key, GList *value, gpointer data) +{ + g_list_foreach (value, (GFunc)g_free, NULL); + g_list_free (value); +} + +static void +destroy_info_cache_map (GHashTable *info_cache_map) +{ + g_hash_table_foreach (info_cache_map, (GHFunc)destroy_info_cache_value, NULL); + g_hash_table_destroy (info_cache_map); +} + +static gboolean +mime_info_cache_dir_out_of_date (MimeInfoCacheDir *dir, + const char *cache_file, + time_t *timestamp) +{ + struct stat buf; + char *filename; + + filename = g_build_filename (dir->path, cache_file, NULL); + + if (g_stat (filename, &buf) < 0) + { + g_free (filename); + return TRUE; + } + g_free (filename); + + if (buf.st_mtime != *timestamp) + return TRUE; + + return FALSE; +} + +/* Call with lock held */ +static gboolean +remove_all (gpointer key, + gpointer value, + gpointer user_data) +{ + return TRUE; +} + + +static void +mime_info_cache_blow_global_cache (void) +{ + g_hash_table_foreach_remove (mime_info_cache->global_defaults_cache, + remove_all, NULL); +} + +static void +mime_info_cache_dir_init (MimeInfoCacheDir *dir) +{ + GError *load_error; + GKeyFile *key_file; + gchar *filename, **mime_types; + int i; + struct stat buf; + + load_error = NULL; + mime_types = NULL; + + if (dir->mime_info_cache_map != NULL && + !mime_info_cache_dir_out_of_date (dir, "mimeinfo.cache", + &dir->mime_info_cache_timestamp)) + return; + + if (dir->mime_info_cache_map != NULL) + destroy_info_cache_map (dir->mime_info_cache_map); + + dir->mime_info_cache_map = g_hash_table_new_full (g_str_hash, g_str_equal, + (GDestroyNotify) g_free, + NULL); + + key_file = g_key_file_new (); + + filename = g_build_filename (dir->path, "mimeinfo.cache", NULL); + + if (g_stat (filename, &buf) < 0) + goto error; + + if (dir->mime_info_cache_timestamp > 0) + mime_info_cache->should_ping_mime_monitor = TRUE; + + dir->mime_info_cache_timestamp = buf.st_mtime; + + g_key_file_load_from_file (key_file, filename, G_KEY_FILE_NONE, &load_error); + + g_free (filename); + filename = NULL; + + if (load_error != NULL) + goto error; + + mime_types = g_key_file_get_keys (key_file, MIME_CACHE_GROUP, + NULL, &load_error); + + if (load_error != NULL) + goto error; + + for (i = 0; mime_types[i] != NULL; i++) + { + gchar **desktop_file_ids; + char *unaliased_type; + desktop_file_ids = g_key_file_get_string_list (key_file, + MIME_CACHE_GROUP, + mime_types[i], + NULL, + NULL); + + if (desktop_file_ids == NULL) + continue; + + unaliased_type = _g_unix_content_type_unalias (mime_types[i]); + mime_info_cache_dir_add_desktop_entries (dir, + unaliased_type, + desktop_file_ids); + g_free (unaliased_type); + + g_strfreev (desktop_file_ids); + } + + g_strfreev (mime_types); + g_key_file_free (key_file); + + return; + error: + g_free (filename); + g_key_file_free (key_file); + + if (mime_types != NULL) + g_strfreev (mime_types); + + if (load_error) + g_error_free (load_error); +} + +static void +mime_info_cache_dir_init_defaults_list (MimeInfoCacheDir *dir) +{ + GKeyFile *key_file; + GError *load_error; + gchar *filename, **mime_types; + char *unaliased_type; + char **desktop_file_ids; + int i; + struct stat buf; + + load_error = NULL; + mime_types = NULL; + + if (dir->defaults_list_map != NULL && + !mime_info_cache_dir_out_of_date (dir, "defaults.list", + &dir->defaults_list_timestamp)) + return; + + if (dir->defaults_list_map != NULL) + g_hash_table_destroy (dir->defaults_list_map); + + dir->defaults_list_map = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, (GDestroyNotify)g_strfreev); + + key_file = g_key_file_new (); + + filename = g_build_filename (dir->path, "defaults.list", NULL); + if (g_stat (filename, &buf) < 0) + goto error; + + if (dir->defaults_list_timestamp > 0) + mime_info_cache->should_ping_mime_monitor = TRUE; + + dir->defaults_list_timestamp = buf.st_mtime; + + g_key_file_load_from_file (key_file, filename, G_KEY_FILE_NONE, &load_error); + g_free (filename); + filename = NULL; + + if (load_error != NULL) + goto error; + + mime_types = g_key_file_get_keys (key_file, DEFAULT_APPLICATIONS_GROUP, + NULL, &load_error); + + if (load_error != NULL) + goto error; + + for (i = 0; mime_types[i] != NULL; i++) + { + desktop_file_ids = g_key_file_get_string_list (key_file, + DEFAULT_APPLICATIONS_GROUP, + mime_types[i], + NULL, + NULL); + if (desktop_file_ids == NULL) + continue; + + unaliased_type = _g_unix_content_type_unalias (mime_types[i]); + g_hash_table_replace (dir->defaults_list_map, + unaliased_type, + desktop_file_ids); + } + + g_strfreev (mime_types); + g_key_file_free (key_file); + + return; + error: + g_free (filename); + g_key_file_free (key_file); + + if (mime_types != NULL) + g_strfreev (mime_types); + + if (load_error) + g_error_free (load_error); +} + +static MimeInfoCacheDir * +mime_info_cache_dir_new (const char *path) +{ + MimeInfoCacheDir *dir; + + dir = g_new0 (MimeInfoCacheDir, 1); + dir->path = g_strdup (path); + + return dir; +} + +static void +mime_info_cache_dir_free (MimeInfoCacheDir *dir) +{ + if (dir == NULL) + return; + + if (dir->mime_info_cache_map != NULL) + { + destroy_info_cache_map (dir->mime_info_cache_map); + dir->mime_info_cache_map = NULL; + + } + + if (dir->defaults_list_map != NULL) + { + g_hash_table_destroy (dir->defaults_list_map); + dir->defaults_list_map = NULL; + } + + g_free (dir); +} + +static void +mime_info_cache_dir_add_desktop_entries (MimeInfoCacheDir *dir, + const char *mime_type, + char **new_desktop_file_ids) +{ + GList *desktop_file_ids; + int i; + + desktop_file_ids = g_hash_table_lookup (dir->mime_info_cache_map, + mime_type); + + for (i = 0; new_desktop_file_ids[i] != NULL; i++) + { + if (!g_list_find (desktop_file_ids, new_desktop_file_ids[i])) + desktop_file_ids = g_list_append (desktop_file_ids, + g_strdup (new_desktop_file_ids[i])); + } + + g_hash_table_insert (dir->mime_info_cache_map, g_strdup (mime_type), desktop_file_ids); +} + +static void +mime_info_cache_init_dir_lists (void) +{ + const char * const *dirs; + int i; + + mime_info_cache = mime_info_cache_new (); + + dirs = get_applications_search_path (); + + for (i = 0; dirs[i] != NULL; i++) + { + MimeInfoCacheDir *dir; + + dir = mime_info_cache_dir_new (dirs[i]); + + if (dir != NULL) + { + mime_info_cache_dir_init (dir); + mime_info_cache_dir_init_defaults_list (dir); + + mime_info_cache->dirs = g_list_append (mime_info_cache->dirs, dir); + } + } +} + +static void +mime_info_cache_update_dir_lists (void) +{ + GList *tmp; + + tmp = mime_info_cache->dirs; + + while (tmp != NULL) + { + MimeInfoCacheDir *dir = (MimeInfoCacheDir *) tmp->data; + + /* No need to do this if we had file monitors... */ + mime_info_cache_blow_global_cache (); + mime_info_cache_dir_init (dir); + mime_info_cache_dir_init_defaults_list (dir); + + tmp = tmp->next; + } +} + +static void +mime_info_cache_init (void) +{ + G_LOCK (mime_info_cache); + if (mime_info_cache == NULL) + mime_info_cache_init_dir_lists (); + else + { + time_t now; + + time (&now); + if (now >= mime_info_cache->last_stat_time + 10) + { + mime_info_cache_update_dir_lists (); + mime_info_cache->last_stat_time = now; + } + } + + if (mime_info_cache->should_ping_mime_monitor) + { + /* g_idle_add (emit_mime_changed, NULL); */ + mime_info_cache->should_ping_mime_monitor = FALSE; + } + + G_UNLOCK (mime_info_cache); +} + +static MimeInfoCache * +mime_info_cache_new (void) +{ + MimeInfoCache *cache; + + cache = g_new0 (MimeInfoCache, 1); + + cache->global_defaults_cache = g_hash_table_new_full (g_str_hash, g_str_equal, + (GDestroyNotify) g_free, + (GDestroyNotify) g_free); + return cache; +} + +static void +mime_info_cache_free (MimeInfoCache *cache) +{ + if (cache == NULL) + return; + + g_list_foreach (cache->dirs, + (GFunc) mime_info_cache_dir_free, + NULL); + g_list_free (cache->dirs); + g_hash_table_destroy (cache->global_defaults_cache); + g_free (cache); +} + +/** + * mime_info_cache_reload: + * @dir: directory path which needs reloading. + * + * Reload the mime information for the @dir. + */ +static void +mime_info_cache_reload (const char *dir) +{ + /* FIXME: just reload the dir that needs reloading, + * don't blow the whole cache + */ + if (mime_info_cache != NULL) + { + G_LOCK (mime_info_cache); + mime_info_cache_free (mime_info_cache); + mime_info_cache = NULL; + G_UNLOCK (mime_info_cache); + } +} + +static GList * +append_desktop_entry (GList *list, const char *desktop_entry) +{ + /* Add if not already in list, and valid */ + if (!g_list_find_custom (list, desktop_entry, (GCompareFunc) strcmp)) + list = g_list_prepend (list, g_strdup (desktop_entry)); + + return list; +} + +/** + * get_all_desktop_entries_for_mime_type: + * @mime_type: a mime type. + * + * Returns all the desktop filenames for @mime_type. The desktop files + * are listed in an order so that default applications are listed before + * non-default ones, and handlers for inherited mimetypes are listed + * after the base ones. + * + * Return value: a #GList containing the desktop filenames containing the + * @mime_type. + */ +static GList * +get_all_desktop_entries_for_mime_type (const char *base_mime_type) +{ + GList *desktop_entries, *list, *dir_list, *tmp; + MimeInfoCacheDir *dir; + char *mime_type; + char **mime_types; + char **default_entries; + int i,j; + + mime_info_cache_init (); + + mime_types = _g_unix_content_type_get_parents (base_mime_type); + G_LOCK (mime_info_cache); + + desktop_entries = NULL; + for (i = 0; mime_types[i] != NULL; i++) + { + mime_type = mime_types[i]; + + /* Go through all apps listed as defaults */ + for (dir_list = mime_info_cache->dirs; + dir_list != NULL; + dir_list = dir_list->next) + { + dir = dir_list->data; + default_entries = g_hash_table_lookup (dir->defaults_list_map, mime_type); + for (j = 0; default_entries != NULL && default_entries[j] != NULL; j++) + desktop_entries = append_desktop_entry (desktop_entries, default_entries[j]); + } + + /* Go through all entries that support the mimetype */ + for (dir_list = mime_info_cache->dirs; + dir_list != NULL; + dir_list = dir_list->next) { + dir = dir_list->data; + + list = g_hash_table_lookup (dir->mime_info_cache_map, mime_type); + for (tmp = list; tmp != NULL; tmp = tmp->next) { + desktop_entries = append_desktop_entry (desktop_entries, tmp->data); + } + } + } + + G_UNLOCK (mime_info_cache); + + g_strfreev (mime_types); + + desktop_entries = g_list_reverse (desktop_entries); + + return desktop_entries; +} diff --git a/gio/gdesktopappinfo.h b/gio/gdesktopappinfo.h new file mode 100644 index 000000000..0f667dca3 --- /dev/null +++ b/gio/gdesktopappinfo.h @@ -0,0 +1,54 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#ifndef __G_DESKTOP_APP_INFO_H__ +#define __G_DESKTOP_APP_INFO_H__ + +#include <gio/gappinfo.h> + +G_BEGIN_DECLS + +#define G_TYPE_DESKTOP_APP_INFO (g_desktop_app_info_get_type ()) +#define G_DESKTOP_APP_INFO(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_DESKTOP_APP_INFO, GDesktopAppInfo)) +#define G_DESKTOP_APP_INFO_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), G_TYPE_DESKTOP_APP_INFO, GDesktopAppInfoClass)) +#define G_IS_DESKTOP_APP_INFO(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_DESKTOP_APP_INFO)) +#define G_IS_DESKTOP_APP_INFO_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_DESKTOP_APP_INFO)) +#define G_DESKTOP_APP_INFO_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_TYPE_DESKTOP_APP_INFO, GDesktopAppInfoClass)) + +typedef struct _GDesktopAppInfo GDesktopAppInfo; +typedef struct _GDesktopAppInfoClass GDesktopAppInfoClass; + +struct _GDesktopAppInfoClass +{ + GObjectClass parent_class; +}; + +GType g_desktop_app_info_get_type (void) G_GNUC_CONST; + +GDesktopAppInfo *g_desktop_app_info_new_from_filename (const char *filename); +GDesktopAppInfo *g_desktop_app_info_new (const char *desktop_id); +gboolean g_desktop_app_info_get_is_hidden (GDesktopAppInfo *info); + +G_END_DECLS + + +#endif /* __G_DESKTOP_APP_INFO_H__ */ diff --git a/gio/gdirectorymonitor.c b/gio/gdirectorymonitor.c new file mode 100644 index 000000000..eb50a958c --- /dev/null +++ b/gio/gdirectorymonitor.c @@ -0,0 +1,472 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#include <config.h> +#include <string.h> + +#include "gdirectorymonitor.h" +#include "gio-marshal.h" +#include "gfile.h" +#include "gvfs.h" +#include "glibintl.h" + +enum { + CHANGED, + LAST_SIGNAL +}; + +G_DEFINE_ABSTRACT_TYPE (GDirectoryMonitor, g_directory_monitor, G_TYPE_OBJECT); + +typedef struct { + GFile *file; + guint32 last_sent_change_time; /* 0 == not sent */ + guint32 send_delayed_change_at; /* 0 == never */ + guint32 send_virtual_changes_done_at; /* 0 == never */ +} RateLimiter; + +struct _GDirectoryMonitorPrivate { + gboolean cancelled; + int rate_limit_msec; + + GHashTable *rate_limiter; + + GSource *timeout; + guint32 timeout_fires_at; +}; + +#define DEFAULT_RATE_LIMIT_MSECS 800 +#define DEFAULT_VIRTUAL_CHANGES_DONE_DELAY_SECS 2 + +static guint signals[LAST_SIGNAL] = { 0 }; + +static void +rate_limiter_free (RateLimiter *limiter) +{ + g_object_unref (limiter->file); + g_free (limiter); +} + +static void +g_directory_monitor_finalize (GObject *object) +{ + GDirectoryMonitor *monitor; + + monitor = G_DIRECTORY_MONITOR (object); + + if (monitor->priv->timeout) + { + g_source_destroy (monitor->priv->timeout); + g_source_unref (monitor->priv->timeout); + } + + g_hash_table_destroy (monitor->priv->rate_limiter); + + if (G_OBJECT_CLASS (g_directory_monitor_parent_class)->finalize) + (*G_OBJECT_CLASS (g_directory_monitor_parent_class)->finalize) (object); +} + +static void +g_directory_monitor_dispose (GObject *object) +{ + GDirectoryMonitor *monitor; + + monitor = G_DIRECTORY_MONITOR (object); + + /* Make sure we cancel on last unref */ + if (!monitor->priv->cancelled) + g_directory_monitor_cancel (monitor); + + if (G_OBJECT_CLASS (g_directory_monitor_parent_class)->dispose) + (*G_OBJECT_CLASS (g_directory_monitor_parent_class)->dispose) (object); +} + +static void +g_directory_monitor_class_init (GDirectoryMonitorClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + g_type_class_add_private (klass, sizeof (GDirectoryMonitorPrivate)); + + gobject_class->finalize = g_directory_monitor_finalize; + gobject_class->dispose = g_directory_monitor_dispose; + + /** + * GDirectoryMonitor::changed: + * @monitor: the #GDirectoryMonitor + * @child: the #GFile which changed + * @other_file: the other #GFile which changed + * @event_type: a #GFileMonitorEvent indicating what the event was + * + * Emitted when a child file changes. + */ + signals[CHANGED] = + g_signal_new (I_("changed"), + G_TYPE_DIRECTORY_MONITOR, + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GDirectoryMonitorClass, changed), + NULL, NULL, + _gio_marshal_VOID__OBJECT_OBJECT_INT, + G_TYPE_NONE,3, + G_TYPE_FILE, + G_TYPE_FILE, + G_TYPE_INT); +} + +static void +g_directory_monitor_init (GDirectoryMonitor *monitor) +{ + monitor->priv = G_TYPE_INSTANCE_GET_PRIVATE (monitor, + G_TYPE_DIRECTORY_MONITOR, + GDirectoryMonitorPrivate); + + monitor->priv->rate_limit_msec = DEFAULT_RATE_LIMIT_MSECS; + monitor->priv->rate_limiter = g_hash_table_new_full (g_file_hash, (GEqualFunc)g_file_equal, + NULL, (GDestroyNotify) rate_limiter_free); +} + + +/** + * g_directory_monitor_cancel: + * @monitor: + * + * Returns: + **/ +gboolean +g_directory_monitor_cancel (GDirectoryMonitor* monitor) +{ + GDirectoryMonitorClass *class; + + g_return_val_if_fail (G_IS_DIRECTORY_MONITOR (monitor), FALSE); + + if (monitor->priv->cancelled) + return TRUE; + + monitor->priv->cancelled = TRUE; + + class = G_DIRECTORY_MONITOR_GET_CLASS (monitor); + return (* class->cancel) (monitor); +} + +/** + * g_directory_monitor_set_rate_limit: + * @monitor: + * @limit_msecs: + * + **/ +void +g_directory_monitor_set_rate_limit (GDirectoryMonitor *monitor, + int limit_msecs) +{ + g_return_if_fail (G_IS_DIRECTORY_MONITOR (monitor)); + + monitor->priv->rate_limit_msec = limit_msecs; +} + +/** + * g_directory_monitor_is_cancelled: + * @monitor: + * + * Returns: + **/ +gboolean +g_directory_monitor_is_cancelled (GDirectoryMonitor *monitor) +{ + g_return_val_if_fail (G_IS_DIRECTORY_MONITOR (monitor), FALSE); + + return monitor->priv->cancelled; +} + +static guint32 +get_time_msecs (void) +{ + return g_thread_gettime() / (1000 * 1000); +} + +static guint32 +time_difference (guint32 from, guint32 to) +{ + if (from > to) + return 0; + return to - from; +} + +static RateLimiter * +new_limiter (GDirectoryMonitor *monitor, + GFile *file) +{ + RateLimiter *limiter; + + limiter = g_new0 (RateLimiter, 1); + limiter->file = g_object_ref (file); + g_hash_table_insert (monitor->priv->rate_limiter, file, limiter); + + return limiter; +} + +static void +rate_limiter_send_virtual_changes_done_now (GDirectoryMonitor *monitor, RateLimiter *limiter) +{ + if (limiter->send_virtual_changes_done_at != 0) + { + g_signal_emit (monitor, signals[CHANGED], 0, limiter->file, NULL, G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT); + limiter->send_virtual_changes_done_at = 0; + } +} + +static void +rate_limiter_send_delayed_change_now (GDirectoryMonitor *monitor, RateLimiter *limiter, guint32 time_now) +{ + if (limiter->send_delayed_change_at != 0) + { + g_signal_emit (monitor, signals[CHANGED], 0, limiter->file, NULL, G_FILE_MONITOR_EVENT_CHANGED); + limiter->send_delayed_change_at = 0; + limiter->last_sent_change_time = time_now; + } +} + +typedef struct { + guint32 min_time; + guint32 time_now; + GDirectoryMonitor *monitor; +} ForEachData; + +static gboolean +calc_min_time (GDirectoryMonitor *monitor, RateLimiter *limiter, guint32 time_now, guint32 *min_time) +{ + gboolean delete_me; + guint32 expire_at; + + delete_me = TRUE; + + if (limiter->last_sent_change_time != 0) + { + /* Set a timeout at 2*rate limit so that we can clear out the change from the hash eventualy */ + expire_at = limiter->last_sent_change_time + 2 * monitor->priv->rate_limit_msec; + + if (time_difference (time_now, expire_at) > 0) + { + delete_me = FALSE; + *min_time = MIN (*min_time, + time_difference (time_now, expire_at)); + } + } + + if (limiter->send_delayed_change_at != 0) + { + delete_me = FALSE; + *min_time = MIN (*min_time, + time_difference (time_now, limiter->send_delayed_change_at)); + } + + if (limiter->send_virtual_changes_done_at != 0) + { + delete_me = FALSE; + *min_time = MIN (*min_time, + time_difference (time_now, limiter->send_virtual_changes_done_at)); + } + + return delete_me; +} + +static gboolean +foreach_rate_limiter_fire (gpointer key, + gpointer value, + gpointer user_data) +{ + RateLimiter *limiter = value; + ForEachData *data = user_data; + + if (limiter->send_delayed_change_at != 0 && + time_difference (data->time_now, limiter->send_delayed_change_at) == 0) + rate_limiter_send_delayed_change_now (data->monitor, limiter, data->time_now); + + if (limiter->send_virtual_changes_done_at != 0 && + time_difference (data->time_now, limiter->send_virtual_changes_done_at) == 0) + rate_limiter_send_virtual_changes_done_now (data->monitor, limiter); + + return calc_min_time (data->monitor, limiter, data->time_now, &data->min_time); +} + +static gboolean +rate_limiter_timeout (gpointer timeout_data) +{ + GDirectoryMonitor *monitor = timeout_data; + ForEachData data; + GSource *source; + + data.min_time = G_MAXUINT32; + data.monitor = monitor; + data.time_now = get_time_msecs (); + g_hash_table_foreach_remove (monitor->priv->rate_limiter, + foreach_rate_limiter_fire, + &data); + + /* Remove old timeout */ + if (monitor->priv->timeout) + { + g_source_destroy (monitor->priv->timeout); + g_source_unref (monitor->priv->timeout); + monitor->priv->timeout = NULL; + monitor->priv->timeout_fires_at = 0; + } + + /* Set up new timeout */ + if (data.min_time != G_MAXUINT32) + { + source = g_timeout_source_new (data.min_time + 1); /* + 1 to make sure we've really passed the time */ + g_source_set_callback (source, rate_limiter_timeout, monitor, NULL); + g_source_attach (source, NULL); + + monitor->priv->timeout = source; + monitor->priv->timeout_fires_at = data.time_now + data.min_time; + } + + return FALSE; +} + +static gboolean +foreach_rate_limiter_update (gpointer key, + gpointer value, + gpointer user_data) +{ + RateLimiter *limiter = value; + ForEachData *data = user_data; + + return calc_min_time (data->monitor, limiter, data->time_now, &data->min_time); +} + +static void +update_rate_limiter_timeout (GDirectoryMonitor *monitor, guint new_time) +{ + ForEachData data; + GSource *source; + + if (monitor->priv->timeout_fires_at != 0 && new_time != 0 && + time_difference (new_time, monitor->priv->timeout_fires_at) == 0) + return; /* Nothing to do, we already fire earlier than that */ + + data.min_time = G_MAXUINT32; + data.monitor = monitor; + data.time_now = get_time_msecs (); + g_hash_table_foreach_remove (monitor->priv->rate_limiter, + foreach_rate_limiter_update, + &data); + + /* Remove old timeout */ + if (monitor->priv->timeout) + { + g_source_destroy (monitor->priv->timeout); + g_source_unref (monitor->priv->timeout); + monitor->priv->timeout_fires_at = 0; + monitor->priv->timeout = NULL; + } + + /* Set up new timeout */ + if (data.min_time != G_MAXUINT32) + { + source = g_timeout_source_new (data.min_time + 1); /* + 1 to make sure we've really passed the time */ + g_source_set_callback (source, rate_limiter_timeout, monitor, NULL); + g_source_attach (source, NULL); + + monitor->priv->timeout = source; + monitor->priv->timeout_fires_at = data.time_now + data.min_time; + } +} + +/** + * g_directory_monitor_emit_event: + * @monitor: + * @child: + * @other_file: + * @event_type: + * + **/ +void +g_directory_monitor_emit_event (GDirectoryMonitor *monitor, + GFile *child, + GFile *other_file, + GFileMonitorEvent event_type) +{ + guint32 time_now, since_last; + gboolean emit_now; + RateLimiter *limiter; + + g_return_if_fail (G_IS_DIRECTORY_MONITOR (monitor)); + g_return_if_fail (G_IS_FILE (child)); + + limiter = g_hash_table_lookup (monitor->priv->rate_limiter, child); + + if (event_type != G_FILE_MONITOR_EVENT_CHANGED) + { + if (limiter) + { + rate_limiter_send_delayed_change_now (monitor, limiter, get_time_msecs ()); + if (event_type == G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT) + limiter->send_virtual_changes_done_at = 0; + else + rate_limiter_send_virtual_changes_done_now (monitor, limiter); + update_rate_limiter_timeout (monitor, 0); + } + g_signal_emit (monitor, signals[CHANGED], 0, child, other_file, event_type); + } + else + { + /* Changed event, rate limit */ + time_now = get_time_msecs (); + emit_now = TRUE; + + if (limiter) + { + since_last = time_difference (limiter->last_sent_change_time, time_now); + if (since_last < monitor->priv->rate_limit_msec) + { + /* We ignore this change, but arm a timer so that we can fire it later if we + don't get any other events (that kill this timeout) */ + emit_now = FALSE; + if (limiter->send_delayed_change_at == 0) + { + limiter->send_delayed_change_at = time_now + monitor->priv->rate_limit_msec; + update_rate_limiter_timeout (monitor, limiter->send_delayed_change_at); + } + } + } + + if (limiter == NULL) + limiter = new_limiter (monitor, child); + + if (emit_now) + { + g_signal_emit (monitor, signals[CHANGED], 0, child, other_file, event_type); + + limiter->last_sent_change_time = time_now; + limiter->send_delayed_change_at = 0; + /* Set a timeout of 2*rate limit so that we can clear out the change from the hash eventualy */ + update_rate_limiter_timeout (monitor, time_now + 2 * monitor->priv->rate_limit_msec); + } + + /* Schedule a virtual change done. This is removed if we get a real one, and + postponed if we get more change events. */ + + limiter->send_virtual_changes_done_at = time_now + DEFAULT_VIRTUAL_CHANGES_DONE_DELAY_SECS * 1000; + update_rate_limiter_timeout (monitor, limiter->send_virtual_changes_done_at); + } +} diff --git a/gio/gdirectorymonitor.h b/gio/gdirectorymonitor.h new file mode 100644 index 000000000..d4e357307 --- /dev/null +++ b/gio/gdirectorymonitor.h @@ -0,0 +1,86 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#ifndef __G_DIRECTORY_MONITOR_H__ +#define __G_DIRECTORY_MONITOR_H__ + +#include <glib-object.h> +#include <gio/gfile.h> +#include <gio/gfilemonitor.h> + +G_BEGIN_DECLS + +#define G_TYPE_DIRECTORY_MONITOR (g_directory_monitor_get_type ()) +#define G_DIRECTORY_MONITOR(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_DIRECTORY_MONITOR, GDirectoryMonitor)) +#define G_DIRECTORY_MONITOR_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), G_TYPE_DIRECTORY_MONITOR, GDirectoryMonitorClass)) +#define G_IS_DIRECTORY_MONITOR(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_DIRECTORY_MONITOR)) +#define G_IS_DIRECTORY_MONITOR_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_DIRECTORY_MONITOR)) +#define G_DIRECTORY_MONITOR_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_TYPE_DIRECTORY_MONITOR, GDirectoryMonitorClass)) + +typedef struct _GDirectoryMonitorClass GDirectoryMonitorClass; +typedef struct _GDirectoryMonitorPrivate GDirectoryMonitorPrivate; + +struct _GDirectoryMonitor +{ + GObject parent; + + /*< private >*/ + GDirectoryMonitorPrivate *priv; +}; + +struct _GDirectoryMonitorClass +{ + GObjectClass parent_class; + + /* Signals */ + void (* changed) (GDirectoryMonitor* monitor, + GFile *child, + GFile *other_file, + GFileMonitorEvent event_type); + + /* Virtual Table */ + gboolean (*cancel)(GDirectoryMonitor* monitor); + + /* Padding for future expansion */ + void (*_g_reserved1) (void); + void (*_g_reserved2) (void); + void (*_g_reserved3) (void); + void (*_g_reserved4) (void); + void (*_g_reserved5) (void); +}; + +GType g_directory_monitor_get_type (void) G_GNUC_CONST; + +gboolean g_directory_monitor_cancel (GDirectoryMonitor *monitor); +gboolean g_directory_monitor_is_cancelled (GDirectoryMonitor *monitor); +void g_directory_monitor_set_rate_limit (GDirectoryMonitor *monitor, + int limit_msecs); + +/* For implementations */ +void g_directory_monitor_emit_event (GDirectoryMonitor *monitor, + GFile *child, + GFile *other_file, + GFileMonitorEvent event_type); + +G_END_DECLS + +#endif /* __G_DIRECTORY_MONITOR_H__ */ diff --git a/gio/gdrive.c b/gio/gdrive.c new file mode 100644 index 000000000..b953c2c88 --- /dev/null +++ b/gio/gdrive.c @@ -0,0 +1,348 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#include <config.h> +#include "gdrive.h" +#include "gsimpleasyncresult.h" +#include "glibintl.h" + +static void g_drive_base_init (gpointer g_class); +static void g_drive_class_init (gpointer g_class, + gpointer class_data); + +GType +g_drive_get_type (void) +{ + static GType drive_type = 0; + + if (! drive_type) + { + static const GTypeInfo drive_info = + { + sizeof (GDriveIface), /* class_size */ + g_drive_base_init, /* base_init */ + NULL, /* base_finalize */ + g_drive_class_init, + NULL, /* class_finalize */ + NULL, /* class_data */ + 0, + 0, /* n_preallocs */ + NULL + }; + + drive_type = + g_type_register_static (G_TYPE_INTERFACE, I_("GDrive"), + &drive_info, 0); + + g_type_interface_add_prerequisite (drive_type, G_TYPE_OBJECT); + } + + return drive_type; +} + +static void +g_drive_class_init (gpointer g_class, + gpointer class_data) +{ +} + +static void +g_drive_base_init (gpointer g_class) +{ + static gboolean initialized = FALSE; + + if (! initialized) + { + g_signal_new (I_("changed"), + G_TYPE_DRIVE, + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GDriveIface, changed), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + initialized = TRUE; + } +} + +/** + * g_drive_get_name: + * @drive: a #GDrive. + * + * Returns: string containing @drive's name. + * + * The returned string should be freed when no longer needed + **/ +char * +g_drive_get_name (GDrive *drive) +{ + GDriveIface *iface; + + g_return_val_if_fail (G_IS_DRIVE (drive), NULL); + + iface = G_DRIVE_GET_IFACE (drive); + + return (* iface->get_name) (drive); +} + +/** + * g_drive_get_icon: + * @drive: a #GDrive. + * + * Gets the icon for @drive. + * + * Returns: #GIcon for the @drive. + **/ +GIcon * +g_drive_get_icon (GDrive *drive) +{ + GDriveIface *iface; + + g_return_val_if_fail (G_IS_DRIVE (drive), NULL); + + iface = G_DRIVE_GET_IFACE (drive); + + return (* iface->get_icon) (drive); +} + +/** + * g_drive_has_volumes: + * @drive: a #GDrive. + * + * Returns: %TRUE if @drive contains volumes, %FALSE otherwise. + **/ +gboolean +g_drive_has_volumes (GDrive *drive) +{ + GDriveIface *iface; + + g_return_val_if_fail (G_IS_DRIVE (drive), FALSE); + + iface = G_DRIVE_GET_IFACE (drive); + + return (* iface->has_volumes) (drive); +} + +/** + * g_drive_get_volumes: + * @drive: a #GDrive. + * + * Returns: #GList containing any #GVolume s on the given @drive. + * NOTE: Fact-check this. + **/ +GList * +g_drive_get_volumes (GDrive *drive) +{ + GDriveIface *iface; + + g_return_val_if_fail (G_IS_DRIVE (drive), NULL); + + iface = G_DRIVE_GET_IFACE (drive); + + return (* iface->get_volumes) (drive); +} + +/** + * g_drive_is_automounted: + * @drive: a #GDrive. + * + * Returns: %TRUE if the drive was automounted. %FALSE otherwise. + **/ +gboolean +g_drive_is_automounted (GDrive *drive) +{ + GDriveIface *iface; + + g_return_val_if_fail (G_IS_DRIVE (drive), FALSE); + + iface = G_DRIVE_GET_IFACE (drive); + + return (* iface->is_automounted) (drive); +} + +/** + * g_drive_can_mount: + * @drive: a #GDrive. + * + * Returns: %TRUE if the @drive can be mounted. %FALSE otherwise. + **/ +gboolean +g_drive_can_mount (GDrive *drive) +{ + GDriveIface *iface; + + g_return_val_if_fail (G_IS_DRIVE (drive), FALSE); + + iface = G_DRIVE_GET_IFACE (drive); + + if (iface->can_mount == NULL) + return FALSE; + + return (* iface->can_mount) (drive); +} + +/** + * g_drive_can_eject: + * @drive: pointer to a #GDrive. + * + * Returns: %TRUE if the @drive can be ejected. %FALSE otherwise. + **/ +gboolean +g_drive_can_eject (GDrive *drive) +{ + GDriveIface *iface; + + g_return_val_if_fail (G_IS_DRIVE (drive), FALSE); + + iface = G_DRIVE_GET_IFACE (drive); + + if (iface->can_eject == NULL) + return FALSE; + + return (* iface->can_eject) (drive); +} + +/** + * g_drive_mount: + * @drive: a #GDrive. + * @mount_operation: a #GMountOperation. + * @callback: a #GAsyncReadyCallback. + * @user_data: a #gpointer. + * + * + **/ +void +g_drive_mount (GDrive *drive, + GMountOperation *mount_operation, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GDriveIface *iface; + + g_return_if_fail (G_IS_DRIVE (drive)); + g_return_if_fail (G_IS_MOUNT_OPERATION (mount_operation)); + + iface = G_DRIVE_GET_IFACE (drive); + + if (iface->mount == NULL) + { + g_simple_async_report_error_in_idle (G_OBJECT (drive), callback, user_data, + G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, + _("drive doesn't implement mount")); + + return; + } + + (* iface->mount) (drive, mount_operation, cancellable, callback, user_data); +} + +/** + * g_drive_mount_finish: + * @drive: pointer to a #GDrive. + * @result: a #GAsyncResult. + * @error: a #GError. + * + * Returns: %TRUE, %FALSE if operation failed. + **/ +gboolean +g_drive_mount_finish (GDrive *drive, + GAsyncResult *result, + GError **error) +{ + GDriveIface *iface; + + g_return_val_if_fail (G_IS_DRIVE (drive), FALSE); + g_return_val_if_fail (G_IS_ASYNC_RESULT (result), FALSE); + + if (G_IS_SIMPLE_ASYNC_RESULT (result)) + { + GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (result); + if (g_simple_async_result_propagate_error (simple, error)) + return FALSE; + } + + iface = G_DRIVE_GET_IFACE (drive); + return (* iface->mount_finish) (drive, result, error); +} + +/** + * g_drive_eject: + * @drive: a #GDrive. + * @callback: a #GAsyncReadyCallback. + * @user_data: a #gpointer. + * + **/ +void +g_drive_eject (GDrive *drive, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GDriveIface *iface; + + g_return_if_fail (G_IS_DRIVE (drive)); + + iface = G_DRIVE_GET_IFACE (drive); + + if (iface->eject == NULL) + { + g_simple_async_report_error_in_idle (G_OBJECT (drive), callback, user_data, + G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, + _("drive doesn't implement eject")); + + return; + } + + (* iface->eject) (drive, cancellable, callback, user_data); +} + +/** + * g_drive_eject_finish + * @drive: a #GDrive. + * @result: a #GAsyncResult. + * @error: a #GError. + * + * Returns: %TRUE if the drive has been ejected successfully, + * %FALSE otherwise. + **/ +gboolean +g_drive_eject_finish (GDrive *drive, + GAsyncResult *result, + GError **error) +{ + GDriveIface *iface; + + g_return_val_if_fail (G_IS_DRIVE (drive), FALSE); + g_return_val_if_fail (G_IS_ASYNC_RESULT (result), FALSE); + + if (G_IS_SIMPLE_ASYNC_RESULT (result)) + { + GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (result); + if (g_simple_async_result_propagate_error (simple, error)) + return FALSE; + } + + iface = G_DRIVE_GET_IFACE (drive); + + return (* iface->mount_finish) (drive, result, error); +} diff --git a/gio/gdrive.h b/gio/gdrive.h new file mode 100644 index 000000000..7a0413ea9 --- /dev/null +++ b/gio/gdrive.h @@ -0,0 +1,99 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#ifndef __G_DRIVE_H__ +#define __G_DRIVE_H__ + +#include <glib-object.h> +#include <gio/gvolume.h> +#include <gio/gmountoperation.h> + +G_BEGIN_DECLS + +#define G_TYPE_DRIVE (g_drive_get_type ()) +#define G_DRIVE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), G_TYPE_DRIVE, GDrive)) +#define G_IS_DRIVE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), G_TYPE_DRIVE)) +#define G_DRIVE_GET_IFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), G_TYPE_DRIVE, GDriveIface)) + +typedef struct _GDriveIface GDriveIface; + +struct _GDriveIface +{ + GTypeInterface g_iface; + + /* signals */ + void (*changed) (GVolume *volume); + + /* Virtual Table */ + + char * (*get_name) (GDrive *drive); + GIcon * (*get_icon) (GDrive *drive); + gboolean (*has_volumes) (GDrive *drive); + GList * (*get_volumes) (GDrive *drive); + gboolean (*is_automounted)(GDrive *drive); + gboolean (*can_mount) (GDrive *drive); + gboolean (*can_eject) (GDrive *drive); + void (*mount) (GDrive *drive, + GMountOperation *mount_operation, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + gboolean (*mount_finish)(GDrive *drive, + GAsyncResult *result, + GError **error); + void (*eject) (GDrive *drive, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + gboolean (*eject_finish)(GDrive *drive, + GAsyncResult *result, + GError **error); +}; + +GType g_drive_get_type (void) G_GNUC_CONST; + +char * g_drive_get_name (GDrive *drive); +GIcon * g_drive_get_icon (GDrive *drive); +gboolean g_drive_has_volumes (GDrive *drive); +GList * g_drive_get_volumes (GDrive *drive); +gboolean g_drive_is_automounted (GDrive *drive); +gboolean g_drive_can_mount (GDrive *drive); +gboolean g_drive_can_eject (GDrive *drive); +void g_drive_mount (GDrive *drive, + GMountOperation *mount_operation, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean g_drive_mount_finish (GDrive *drive, + GAsyncResult *result, + GError **error); +void g_drive_eject (GDrive *drive, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean g_drive_eject_finish (GDrive *drive, + GAsyncResult *result, + GError **error); + +G_END_DECLS + +#endif /* __G_DRIVE_H__ */ diff --git a/gio/gdriveprivate.h b/gio/gdriveprivate.h new file mode 100644 index 000000000..bd53b2a20 --- /dev/null +++ b/gio/gdriveprivate.h @@ -0,0 +1,32 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#ifndef __G_DRIVEPRIV_H__ +#define __G_DRIVEPRIV_H__ + +#include <gio/gdrive.h> + +G_BEGIN_DECLS + +G_END_DECLS + +#endif /* __G_DRIVEPRIV_H__ */ diff --git a/gio/gdummyfile.c b/gio/gdummyfile.c new file mode 100644 index 000000000..40ac3b166 --- /dev/null +++ b/gio/gdummyfile.c @@ -0,0 +1,752 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#include <config.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <string.h> +#include <errno.h> +#include <fcntl.h> +#include <unistd.h> +#include <stdlib.h> + +#include "gdummyfile.h" + +static void g_dummy_file_file_iface_init (GFileIface *iface); + +typedef struct { + char *scheme; + char *userinfo; + char *host; + int port; /* -1 => not in uri */ + char *path; + char *query; + char *fragment; +} GDecodedUri; + +struct _GDummyFile +{ + GObject parent_instance; + + GDecodedUri *decoded_uri; + char *text_uri; +}; + +G_DEFINE_TYPE_WITH_CODE (GDummyFile, g_dummy_file, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (G_TYPE_FILE, + g_dummy_file_file_iface_init)) + +#define SUB_DELIM_CHARS "!$&'()*+,;=" + +static char * _g_encode_uri (GDecodedUri *decoded); +static void _g_decoded_uri_free (GDecodedUri *decoded); +static GDecodedUri *_g_decode_uri (const char *uri); +static GDecodedUri *_g_decoded_uri_new (void); + +static char * unescape_string (const gchar *escaped_string, + const gchar *escaped_string_end, + const gchar *illegal_characters); + +static void g_string_append_encoded (GString *string, const char *encoded, + const char *reserved_chars_allowed); + +static void +g_dummy_file_finalize (GObject *object) +{ + GDummyFile *dummy; + + dummy = G_DUMMY_FILE (object); + + if (dummy->decoded_uri) + _g_decoded_uri_free (dummy->decoded_uri); + + g_free (dummy->text_uri); + + if (G_OBJECT_CLASS (g_dummy_file_parent_class)->finalize) + (*G_OBJECT_CLASS (g_dummy_file_parent_class)->finalize) (object); +} + +static void +g_dummy_file_class_init (GDummyFileClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->finalize = g_dummy_file_finalize; +} + +static void +g_dummy_file_init (GDummyFile *dummy) +{ +} + +/** + * g_dummy_file_new: + * @uri: Universal Resource Identifier for the dummy file object. + * + * Returns: a new #GFile. + **/ +GFile * +g_dummy_file_new (const char *uri) +{ + GDummyFile *dummy; + + g_return_val_if_fail (uri != NULL, NULL); + + dummy = g_object_new (G_TYPE_DUMMY_FILE, NULL); + dummy->text_uri = g_strdup (uri); + dummy->decoded_uri = _g_decode_uri (uri); + + return G_FILE (dummy); +} + +static gboolean +g_dummy_file_is_native (GFile *file) +{ + return FALSE; +} + +static char * +g_dummy_file_get_basename (GFile *file) +{ + GDummyFile *dummy = G_DUMMY_FILE (file); + + if (dummy->decoded_uri) + return g_path_get_basename (dummy->decoded_uri->path); + return g_strdup (dummy->text_uri); +} + +static char * +g_dummy_file_get_path (GFile *file) +{ + GDummyFile *dummy = G_DUMMY_FILE (file); + + if (dummy->decoded_uri) + return g_strdup (dummy->decoded_uri->path); + return NULL; +} + +static char * +g_dummy_file_get_uri (GFile *file) +{ + return g_strdup (G_DUMMY_FILE (file)->text_uri); +} + +static char * +g_dummy_file_get_parse_name (GFile *file) +{ + return g_strdup (G_DUMMY_FILE (file)->text_uri); +} + +static GFile * +g_dummy_file_get_parent (GFile *file) +{ + GDummyFile *dummy = G_DUMMY_FILE (file); + GFile *parent; + char *dirname; + char *uri; + GDecodedUri new_decoded_uri; + + if (dummy->decoded_uri == NULL) + return NULL; + + dirname = g_path_get_dirname (dummy->decoded_uri->path); + + if (strcmp (dirname, ".") == 0) + { + g_free (dirname); + return NULL; + } + + new_decoded_uri = *dummy->decoded_uri; + new_decoded_uri.path = dirname; + uri = _g_encode_uri (&new_decoded_uri); + g_free (dirname); + + parent = g_dummy_file_new (uri); + g_free (uri); + + return parent; +} + +static GFile * +g_dummy_file_dup (GFile *file) +{ + GDummyFile *dummy = G_DUMMY_FILE (file); + + return g_dummy_file_new (dummy->text_uri); +} + +static guint +g_dummy_file_hash (GFile *file) +{ + GDummyFile *dummy = G_DUMMY_FILE (file); + + return g_str_hash (dummy->text_uri); +} + +static gboolean +g_dummy_file_equal (GFile *file1, + GFile *file2) +{ + GDummyFile *dummy1 = G_DUMMY_FILE (file1); + GDummyFile *dummy2 = G_DUMMY_FILE (file2); + + return g_str_equal (dummy1->text_uri, dummy2->text_uri); +} + +static int +safe_strcmp (const char *a, const char *b) +{ + if (a == NULL) + a = ""; + if (b == NULL) + b = ""; + + return strcmp (a, b); +} + +static gboolean +uri_same_except_path (GDecodedUri *a, + GDecodedUri *b) +{ + if (safe_strcmp (a->scheme, b->scheme) != 0) + return FALSE; + if (safe_strcmp (a->userinfo, b->userinfo) != 0) + return FALSE; + if (safe_strcmp (a->host, b->host) != 0) + return FALSE; + if (a->port != b->port) + return FALSE; + + return TRUE; +} + +static const char * +match_prefix (const char *path, const char *prefix) +{ + int prefix_len; + + prefix_len = strlen (prefix); + if (strncmp (path, prefix, prefix_len) != 0) + return NULL; + return path + prefix_len; +} + +static gboolean +g_dummy_file_contains_file (GFile *parent, + GFile *descendant) +{ + GDummyFile *parent_dummy = G_DUMMY_FILE (parent); + GDummyFile *descendant_dummy = G_DUMMY_FILE (descendant); + const char *remainder; + + if (parent_dummy->decoded_uri != NULL && + descendant_dummy->decoded_uri != NULL) + { + if (uri_same_except_path (parent_dummy->decoded_uri, + descendant_dummy->decoded_uri)) { + remainder = match_prefix (descendant_dummy->decoded_uri->path, + parent_dummy->decoded_uri->path); + if (remainder != NULL && *remainder == '/') + { + while (*remainder == '/') + remainder++; + if (*remainder != 0) + return TRUE; + } + } + } + else + { + remainder = match_prefix (descendant_dummy->text_uri, + parent_dummy->text_uri); + if (remainder != NULL && *remainder == '/') + { + while (*remainder == '/') + remainder++; + if (*remainder != 0) + return TRUE; + } + } + + return FALSE; +} + +static char * +g_dummy_file_get_relative_path (GFile *parent, + GFile *descendant) +{ + GDummyFile *parent_dummy = G_DUMMY_FILE (parent); + GDummyFile *descendant_dummy = G_DUMMY_FILE (descendant); + const char *remainder; + + if (parent_dummy->decoded_uri != NULL && + descendant_dummy->decoded_uri != NULL) + { + if (uri_same_except_path (parent_dummy->decoded_uri, + descendant_dummy->decoded_uri)) { + remainder = match_prefix (descendant_dummy->decoded_uri->path, + parent_dummy->decoded_uri->path); + if (remainder != NULL && *remainder == '/') + { + while (*remainder == '/') + remainder++; + if (*remainder != 0) + return g_strdup (remainder); + } + } + } + else + { + remainder = match_prefix (descendant_dummy->text_uri, + parent_dummy->text_uri); + if (remainder != NULL && *remainder == '/') + { + while (*remainder == '/') + remainder++; + if (*remainder != 0) + return unescape_string (remainder, NULL, "/"); + } + } + + return NULL; +} + + +static GFile * +g_dummy_file_resolve_relative_path (GFile *file, + const char *relative_path) +{ + GDummyFile *dummy = G_DUMMY_FILE (file); + GFile *child; + char *uri; + GDecodedUri new_decoded_uri; + GString *str; + + if (dummy->decoded_uri == NULL) + { + str = g_string_new (dummy->text_uri); + g_string_append (str, "/"); + g_string_append_encoded (str, relative_path, SUB_DELIM_CHARS ":@/"); + child = g_dummy_file_new (str->str); + g_string_free (str, TRUE); + } + else + { + new_decoded_uri = *dummy->decoded_uri; + + if (g_path_is_absolute (relative_path)) + new_decoded_uri.path = g_strdup (relative_path); + else + new_decoded_uri.path = g_build_filename (new_decoded_uri.path, relative_path, NULL); + + uri = _g_encode_uri (&new_decoded_uri); + g_free (new_decoded_uri.path); + + child = g_dummy_file_new (uri); + g_free (uri); + } + + return child; +} + +static GFile * +g_dummy_file_get_child_for_display_name (GFile *file, + const char *display_name, + GError **error) +{ + return g_file_get_child (file, display_name); +} + +static gboolean +g_dummy_file_has_uri_scheme (GFile *file, + const char *uri_scheme) +{ + GDummyFile *dummy = G_DUMMY_FILE (file); + + if (dummy->decoded_uri) + return g_ascii_strcasecmp (uri_scheme, dummy->decoded_uri->scheme) == 0; + return FALSE; +} + +static char * +g_dummy_file_get_uri_scheme (GFile *file) +{ + GDummyFile *dummy = G_DUMMY_FILE (file); + + if (dummy->decoded_uri) + return g_strdup (dummy->decoded_uri->scheme); + + return NULL; +} + + +static void +g_dummy_file_file_iface_init (GFileIface *iface) +{ + iface->dup = g_dummy_file_dup; + iface->hash = g_dummy_file_hash; + iface->equal = g_dummy_file_equal; + iface->is_native = g_dummy_file_is_native; + iface->has_uri_scheme = g_dummy_file_has_uri_scheme; + iface->get_uri_scheme = g_dummy_file_get_uri_scheme; + iface->get_basename = g_dummy_file_get_basename; + iface->get_path = g_dummy_file_get_path; + iface->get_uri = g_dummy_file_get_uri; + iface->get_parse_name = g_dummy_file_get_parse_name; + iface->get_parent = g_dummy_file_get_parent; + iface->contains_file = g_dummy_file_contains_file; + iface->get_relative_path = g_dummy_file_get_relative_path; + iface->resolve_relative_path = g_dummy_file_resolve_relative_path; + iface->get_child_for_display_name = g_dummy_file_get_child_for_display_name; +} + +/* Uri handling helper functions: */ + +static int +unescape_character (const char *scanner) +{ + int first_digit; + int second_digit; + + first_digit = g_ascii_xdigit_value (*scanner++); + if (first_digit < 0) + return -1; + + second_digit = g_ascii_xdigit_value (*scanner++); + if (second_digit < 0) + return -1; + + return (first_digit << 4) | second_digit; +} + +static char * +unescape_string (const gchar *escaped_string, + const gchar *escaped_string_end, + const gchar *illegal_characters) +{ + const gchar *in; + gchar *out, *result; + gint character; + + if (escaped_string == NULL) + return NULL; + + if (escaped_string_end == NULL) + escaped_string_end = escaped_string + strlen (escaped_string); + + result = g_malloc (escaped_string_end - escaped_string + 1); + + out = result; + for (in = escaped_string; in < escaped_string_end; in++) { + character = *in; + if (*in == '%') { + in++; + if (escaped_string_end - in < 2) + { + g_free (result); + return NULL; + } + + character = unescape_character (in); + + /* Check for an illegal character. We consider '\0' illegal here. */ + if (character <= 0 || + (illegal_characters != NULL && + strchr (illegal_characters, (char)character) != NULL)) + { + g_free (result); + return NULL; + } + in++; /* The other char will be eaten in the loop header */ + } + *out++ = (char)character; + } + + *out = '\0'; + g_assert (out - result <= strlen (escaped_string)); + return result; +} + +void +_g_decoded_uri_free (GDecodedUri *decoded) +{ + if (decoded == NULL) + return; + + g_free (decoded->scheme); + g_free (decoded->query); + g_free (decoded->fragment); + g_free (decoded->userinfo); + g_free (decoded->host); + g_free (decoded->path); + g_free (decoded); +} + +GDecodedUri * +_g_decoded_uri_new (void) +{ + GDecodedUri *uri; + + uri = g_new0 (GDecodedUri, 1); + uri->port = -1; + + return uri; +} + +GDecodedUri * +_g_decode_uri (const char *uri) +{ + GDecodedUri *decoded; + const char *p, *in, *hier_part_start, *hier_part_end, *query_start, *fragment_start; + char *out; + char c; + + /* From RFC 3986 Decodes: + * URI = scheme ":" hier-part [ "?" query ] [ "#" fragment ] + */ + + p = uri; + + /* Decode scheme: + scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) + */ + + if (!g_ascii_isalpha (*p)) + return NULL; + + while (1) + { + c = *p++; + + if (c == ':') + break; + + if (!(g_ascii_isalnum(c) || + c == '+' || + c == '-' || + c == '.')) + return NULL; + } + + decoded = _g_decoded_uri_new (); + + decoded->scheme = g_malloc (p - uri); + out = decoded->scheme; + for (in = uri; in < p - 1; in++) + *out++ = g_ascii_tolower (*in); + *out = 0; + + hier_part_start = p; + + query_start = strchr (p, '?'); + if (query_start) + { + hier_part_end = query_start++; + fragment_start = strchr (query_start, '#'); + if (fragment_start) + { + decoded->query = g_strndup (query_start, fragment_start - query_start); + decoded->fragment = g_strdup (fragment_start+1); + } + else + { + decoded->query = g_strdup (query_start); + decoded->fragment = NULL; + } + } + else + { + /* No query */ + decoded->query = NULL; + fragment_start = strchr (p, '#'); + if (fragment_start) + { + hier_part_end = fragment_start++; + decoded->fragment = g_strdup (fragment_start); + } + else + { + hier_part_end = p + strlen (p); + decoded->fragment = NULL; + } + } + + /* 3: + hier-part = "//" authority path-abempty + / path-absolute + / path-rootless + / path-empty + + */ + + if (hier_part_start[0] == '/' && + hier_part_start[1] == '/') + { + const char *authority_start, *authority_end; + const char *userinfo_start, *userinfo_end; + const char *host_start, *host_end; + const char *port_start; + + authority_start = hier_part_start + 2; + /* authority is always followed by / or nothing */ + authority_end = memchr (authority_start, '/', hier_part_end - authority_start); + if (authority_end == NULL) + authority_end = hier_part_end; + + /* 3.2: + authority = [ userinfo "@" ] host [ ":" port ] + */ + + userinfo_end = memchr (authority_start, '@', authority_end - authority_start); + if (userinfo_end) + { + userinfo_start = authority_start; + decoded->userinfo = unescape_string (userinfo_start, userinfo_end, NULL); + if (decoded->userinfo == NULL) + { + _g_decoded_uri_free (decoded); + return NULL; + } + host_start = userinfo_end + 1; + } + else + host_start = authority_start; + + port_start = memchr (host_start, ':', authority_end - host_start); + if (port_start) + { + host_end = port_start++; + + decoded->port = atoi(port_start); + } + else + { + host_end = authority_end; + decoded->port = -1; + } + + decoded->host = g_strndup (host_start, host_end - host_start); + + hier_part_start = authority_end; + } + + decoded->path = unescape_string (hier_part_start, hier_part_end, "/"); + + if (decoded->path == NULL) + { + _g_decoded_uri_free (decoded); + return NULL; + } + + return decoded; +} + +static gboolean +is_valid (char c, const char *reserved_chars_allowed) +{ + if (g_ascii_isalnum (c) || + c == '-' || + c == '.' || + c == '_' || + c == '~') + return TRUE; + + if (reserved_chars_allowed && + strchr (reserved_chars_allowed, c) != NULL) + return TRUE; + + return FALSE; +} + +static void +g_string_append_encoded (GString *string, const char *encoded, + const char *reserved_chars_allowed) +{ + unsigned char c; + const char *end; + static const gchar hex[16] = "0123456789ABCDEF"; + + end = encoded + strlen (encoded); + + while ((c = *encoded) != 0) + { + if (is_valid (c, reserved_chars_allowed)) + { + g_string_append_c (string, c); + encoded++; + } + else + { + g_string_append_c (string, '%'); + g_string_append_c (string, hex[((guchar)c) >> 4]); + g_string_append_c (string, hex[((guchar)c) & 0xf]); + encoded++; + } + } +} + +static char * +_g_encode_uri (GDecodedUri *decoded) +{ + GString *uri; + + uri = g_string_new (NULL); + + g_string_append (uri, decoded->scheme); + g_string_append (uri, "://"); + + if (decoded->host != NULL) + { + if (decoded->userinfo) + { + /* userinfo = *( unreserved / pct-encoded / sub-delims / ":" ) */ + g_string_append_encoded (uri, decoded->userinfo, SUB_DELIM_CHARS ":"); + g_string_append_c (uri, '@'); + } + + g_string_append (uri, decoded->host); + + if (decoded->port != -1) + { + g_string_append_c (uri, ':'); + g_string_append_printf (uri, "%d", decoded->port); + } + } + + g_string_append_encoded (uri, decoded->path, SUB_DELIM_CHARS ":@/"); + + if (decoded->query) + { + g_string_append_c (uri, '?'); + g_string_append (uri, decoded->query); + } + + if (decoded->fragment) + { + g_string_append_c (uri, '#'); + g_string_append (uri, decoded->fragment); + } + + return g_string_free (uri, FALSE); +} diff --git a/gio/gdummyfile.h b/gio/gdummyfile.h new file mode 100644 index 000000000..43e45177f --- /dev/null +++ b/gio/gdummyfile.h @@ -0,0 +1,51 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#ifndef __G_DUMMY_FILE_H__ +#define __G_DUMMY_FILE_H__ + +#include <gio/gfile.h> + +G_BEGIN_DECLS + +#define G_TYPE_DUMMY_FILE (g_dummy_file_get_type ()) +#define G_DUMMY_FILE(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_DUMMY_FILE, GDummyFile)) +#define G_DUMMY_FILE_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), G_TYPE_DUMMY_FILE, GDummyFileClass)) +#define G_IS_DUMMY_FILE(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_DUMMY_FILE)) +#define G_IS_DUMMY_FILE_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_DUMMY_FILE)) +#define G_DUMMY_FILE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_TYPE_DUMMY_FILE, GDummyFileClass)) + +typedef struct _GDummyFile GDummyFile; +typedef struct _GDummyFileClass GDummyFileClass; + +struct _GDummyFileClass +{ + GObjectClass parent_class; +}; + +GType g_dummy_file_get_type (void) G_GNUC_CONST; + +GFile * g_dummy_file_new (const char *uri); + +G_END_DECLS + +#endif /* __G_DUMMY_FILE_H__ */ diff --git a/gio/gfile.c b/gio/gfile.c new file mode 100644 index 000000000..617422f17 --- /dev/null +++ b/gio/gfile.c @@ -0,0 +1,4345 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#include <config.h> +#include <string.h> +#include <sys/types.h> +#ifdef HAVE_PWD_H +#include <pwd.h> +#endif +#include "gfile.h" +#include "gvfs.h" +#include "gioscheduler.h" +#include <glocalfile.h> +#include "gsimpleasyncresult.h" +#include "gpollfilemonitor.h" +#include "glibintl.h" + +static void g_file_base_init (gpointer g_class); +static void g_file_class_init (gpointer g_class, + gpointer class_data); + +static void g_file_real_query_info_async (GFile *file, + const char *attributes, + GFileQueryInfoFlags flags, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +static GFileInfo * g_file_real_query_info_finish (GFile *file, + GAsyncResult *res, + GError **error); +static void g_file_real_enumerate_children_async (GFile *file, + const char *attributes, + GFileQueryInfoFlags flags, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +static GFileEnumerator * g_file_real_enumerate_children_finish (GFile *file, + GAsyncResult *res, + GError **error); +static void g_file_real_read_async (GFile *file, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +static GFileInputStream * g_file_real_read_finish (GFile *file, + GAsyncResult *res, + GError **error); +static void g_file_real_append_to_async (GFile *file, + GFileCreateFlags flags, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +static GFileOutputStream *g_file_real_append_to_finish (GFile *file, + GAsyncResult *res, + GError **error); +static void g_file_real_create_async (GFile *file, + GFileCreateFlags flags, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +static GFileOutputStream *g_file_real_create_finish (GFile *file, + GAsyncResult *res, + GError **error); +static void g_file_real_replace_async (GFile *file, + const char *etag, + gboolean make_backup, + GFileCreateFlags flags, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +static GFileOutputStream *g_file_real_replace_finish (GFile *file, + GAsyncResult *res, + GError **error); +static gboolean g_file_real_set_attributes_from_info (GFile *file, + GFileInfo *info, + GFileQueryInfoFlags flags, + GCancellable *cancellable, + GError **error); +static void g_file_real_set_display_name_async (GFile *file, + const char *display_name, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +static GFile * g_file_real_set_display_name_finish (GFile *file, + GAsyncResult *res, + GError **error); +static void g_file_real_set_attributes_async (GFile *file, + GFileInfo *info, + GFileQueryInfoFlags flags, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +static gboolean g_file_real_set_attributes_finish (GFile *file, + GAsyncResult *res, + GFileInfo **info, + GError **error); + +GType +g_file_get_type (void) +{ + static GType file_type = 0; + + if (! file_type) + { + static const GTypeInfo file_info = + { + sizeof (GFileIface), /* class_size */ + g_file_base_init, /* base_init */ + NULL, /* base_finalize */ + g_file_class_init, + NULL, /* class_finalize */ + NULL, /* class_data */ + 0, + 0, /* n_preallocs */ + NULL + }; + + file_type = + g_type_register_static (G_TYPE_INTERFACE, I_("GFile"), + &file_info, 0); + + g_type_interface_add_prerequisite (file_type, G_TYPE_OBJECT); + } + + return file_type; +} + +static void +g_file_class_init (gpointer g_class, + gpointer class_data) +{ + GFileIface *iface = g_class; + + iface->enumerate_children_async = g_file_real_enumerate_children_async; + iface->enumerate_children_finish = g_file_real_enumerate_children_finish; + iface->set_display_name_async = g_file_real_set_display_name_async; + iface->set_display_name_finish = g_file_real_set_display_name_finish; + iface->query_info_async = g_file_real_query_info_async; + iface->query_info_finish = g_file_real_query_info_finish; + iface->set_attributes_async = g_file_real_set_attributes_async; + iface->set_attributes_finish = g_file_real_set_attributes_finish; + iface->read_async = g_file_real_read_async; + iface->read_finish = g_file_real_read_finish; + iface->append_to_async = g_file_real_append_to_async; + iface->append_to_finish = g_file_real_append_to_finish; + iface->create_async = g_file_real_create_async; + iface->create_finish = g_file_real_create_finish; + iface->replace_async = g_file_real_replace_async; + iface->replace_finish = g_file_real_replace_finish; + iface->set_attributes_from_info = g_file_real_set_attributes_from_info; +} + +static void +g_file_base_init (gpointer g_class) +{ +} + + +/** + * g_file_is_native: + * @file: input #GFile. + * + * Returns: %TRUE if file is native. + * TODO: Explain what "native" means. + **/ +gboolean +g_file_is_native (GFile *file) +{ + GFileIface *iface; + + g_return_val_if_fail (G_IS_FILE (file), FALSE); + + iface = G_FILE_GET_IFACE (file); + + return (* iface->is_native) (file); +} + + +/** + * g_file_has_uri_scheme: + * @file: input #GFile. + * @uri_scheme: a string containing a URI scheme. + * + * Returns: %TRUE if #GFile's backend supports the + * given URI scheme, FALSE if URI scheme is %NULL, + * not supported, or #GFile is invalid. + **/ +gboolean +g_file_has_uri_scheme (GFile *file, + const char *uri_scheme) +{ + GFileIface *iface; + + g_return_val_if_fail (G_IS_FILE (file), FALSE); + g_return_val_if_fail (uri_scheme != NULL, FALSE); + + iface = G_FILE_GET_IFACE (file); + + return (* iface->has_uri_scheme) (file, uri_scheme); +} + + +/** + * g_file_get_uri_scheme: + * @file: input #GFile. + * + * Returns: string to the URI scheme for the given #GFile. + * RFC 3986 decodes the scheme as: + * URI = scheme ":" hier-part [ "?" query ] [ "#" fragment ] + * Popular schemes include "file", "http", "svn", etc. + * + * The returned string should be freed when no longer needed. + **/ +char * +g_file_get_uri_scheme (GFile *file) +{ + GFileIface *iface; + + g_return_val_if_fail (G_IS_FILE (file), NULL); + + iface = G_FILE_GET_IFACE (file); + + return (* iface->get_uri_scheme) (file); +} + + +/** + * g_file_get_basename: + * @file: input #GFile. + * + * Returns: string containing the #GFile's base name, or %NULL if given + * #GFile is invalid. + * + * The returned string should be freed when no longer needed + **/ +char * +g_file_get_basename (GFile *file) +{ + GFileIface *iface; + + g_return_val_if_fail (G_IS_FILE (file), NULL); + + iface = G_FILE_GET_IFACE (file); + + return (* iface->get_basename) (file); +} + +/** + * g_file_get_path: + * @file: input #GFile. + * + * Returns: string containing the #GFile's path, or %NULL if given + * #GFile is invalid. + * + * The returned string should be freed when no longer needed + **/ +char * +g_file_get_path (GFile *file) +{ + GFileIface *iface; + + g_return_val_if_fail (G_IS_FILE (file), NULL); + + iface = G_FILE_GET_IFACE (file); + + return (* iface->get_path) (file); +} + +/** + * g_file_get_uri: + * @file: input #GFile. + * + * Returns: string to the #GFile's Universal Resource Identifier (URI), + * or %NULL if given #GFile is invalid. + * + * The returned string should be freed when no longer needed + **/ +char * +g_file_get_uri (GFile *file) +{ + GFileIface *iface; + + g_return_val_if_fail (G_IS_FILE (file), NULL); + + iface = G_FILE_GET_IFACE (file); + + return (* iface->get_uri) (file); +} + +/** + * g_file_parse_name: + * @file: input #GFile. + * + * Returns: string to the GFile's parsed name, or %NULL if given + * GFile is invalid. + * + * The returned string should be freed when no longer needed + **/ +char * +g_file_get_parse_name (GFile *file) +{ + GFileIface *iface; + + g_return_val_if_fail (G_IS_FILE (file), NULL); + + iface = G_FILE_GET_IFACE (file); + + return (* iface->get_parse_name) (file); +} + +/** + * g_file_dup: + * @file: input #GFile. + * + * Returns: #GFile that is a duplicate of the given #GFile, + * or %NULL if given #GFile is invalid. + **/ +GFile * +g_file_dup (GFile *file) +{ + GFileIface *iface; + + g_return_val_if_fail (G_IS_FILE (file), NULL); + + iface = G_FILE_GET_IFACE (file); + + return (* iface->dup) (file); +} + +/** + * g_file_hash: + * @file: #gconstpointer to a #GFile. + * + * Returns: 0 if @file is not a #GFile, otherwise a + * guint containing a hash of the #GFile. This function + * is intended for easily hashing a #GFile to add to a + * #GHashTable or similar data structure. + * + **/ +guint +g_file_hash (gconstpointer file) +{ + GFileIface *iface; + + g_return_val_if_fail (G_IS_FILE (file), 0); + + iface = G_FILE_GET_IFACE (file); + + return (* iface->hash) ((GFile *)file); +} + +/** + * g_file_equal: + * @file1: the first #GFile. + * @file2: the second #GFile. + * + * Returns: %TRUE if @file1 and @file2 are equal. + * %FALSE if either is not a #GFile. + **/ +gboolean +g_file_equal (GFile *file1, + GFile *file2) +{ + GFileIface *iface; + + g_return_val_if_fail (G_IS_FILE (file1), FALSE); + g_return_val_if_fail (G_IS_FILE (file2), FALSE); + + if (G_TYPE_FROM_INSTANCE (file1) != G_TYPE_FROM_INSTANCE (file2)) + return FALSE; + + iface = G_FILE_GET_IFACE (file1); + + return (* iface->equal) (file1, file2); +} + + +/** + * g_file_get_parent: + * @file: input #GFile. + * + * Returns: a #GFile structure to the parent of the given + * #GFile. + * + * This function should return the parent directory of the given + * @file. If the @file represents the root directory of the + * file system, then %NULL will be returned. + **/ +GFile * +g_file_get_parent (GFile *file) +{ + GFileIface *iface; + + g_return_val_if_fail (G_IS_FILE (file), NULL); + + iface = G_FILE_GET_IFACE (file); + + return (* iface->get_parent) (file); +} + +/** + * g_file_get_child: + * @file: input #GFile. + * @name: string containing the child's name. + * + * Returns: a #GFile to a child specified by + * @name or %NULL if @name is %NULL, or the specified + * child doesn't exist. + **/ +GFile * +g_file_get_child (GFile *file, + const char *name) +{ + g_return_val_if_fail (G_IS_FILE (file), NULL); + g_return_val_if_fail (name != NULL, NULL); + + return g_file_resolve_relative_path (file, name); +} + +/** + * g_file_get_child_for_display_name: + * @file: input #GFile. + * @display_name: string to a possible child. + * @error: #GError. + * + * Returns: %NULL if @display_name is %NULL, #GFile to + * the specified child. + **/ +GFile * +g_file_get_child_for_display_name (GFile *file, + const char *display_name, + GError **error) +{ + GFileIface *iface; + + g_return_val_if_fail (G_IS_FILE (file), NULL); + g_return_val_if_fail (display_name != NULL, NULL); + + iface = G_FILE_GET_IFACE (file); + + return (* iface->get_child_for_display_name) (file, display_name, error); +} + +/** + * g_file_contains_file: + * @parent: input #GFile. + * @descendant: input #GFile. + * + * Returns: %FALSE if either the @parent or @descendant + * is invalid. %TRUE if the @descendent's parent is @parent. + **/ +gboolean +g_file_contains_file (GFile *parent, + GFile *descendant) +{ + GFileIface *iface; + + g_return_val_if_fail (G_IS_FILE (parent), FALSE); + g_return_val_if_fail (G_IS_FILE (descendant), FALSE); + + if (G_TYPE_FROM_INSTANCE (parent) != G_TYPE_FROM_INSTANCE (descendant)) + return FALSE; + + iface = G_FILE_GET_IFACE (parent); + + return (* iface->contains_file) (parent, descendant); +} + +/** + * g_file_get_relative_path: + * @parent: input #GFile. + * @descendant: input #GFile. + * + * Returns: string with the relative path from + * @descendant to @parent. + * + * The returned string should be freed when no longer needed + **/ +char * +g_file_get_relative_path (GFile *parent, + GFile *descendant) +{ + GFileIface *iface; + + g_return_val_if_fail (G_IS_FILE (parent), NULL); + g_return_val_if_fail (G_IS_FILE (descendant), NULL); + + if (G_TYPE_FROM_INSTANCE (parent) != G_TYPE_FROM_INSTANCE (descendant)) + return NULL; + + iface = G_FILE_GET_IFACE (parent); + + return (* iface->get_relative_path) (parent, descendant); +} + +/** + * g_file_resolve_relative_path: + * @file: input #GFile. + * @relative_path: a given relative path string. + * + * Returns: #GFile to the resolved path. %NULL if @relative_path is NULL. + * or if @file is invalid. + **/ +GFile * +g_file_resolve_relative_path (GFile *file, + const char *relative_path) +{ + GFileIface *iface; + + g_return_val_if_fail (G_IS_FILE (file), NULL); + g_return_val_if_fail (relative_path != NULL, NULL); + + iface = G_FILE_GET_IFACE (file); + + return (* iface->resolve_relative_path) (file, relative_path); +} + +/** + * g_file_enumerate_children: + * @file: input #GFile. + * @attributes: a string. + * @flags: #GFileQueryInfoFlags field. + * @cancellable: optional #GCancellable object, %NULL to ignore. + * @error: #GError for error reporting. + * + * If @cancellable is not %NULL, then the operation can be cancelled by + * triggering the cancellable object from another thread. If the operation + * was cancelled, the error %G_IO_ERROR_CANCELLED will be returned. If an + * operation was partially finished when the operation was cancelled the + * partial result will be returned, without an error. + * + * Returns: %NULL if cancelled or if #GFile's backend doesn't + * support #GFileEnumerator. Returns a #GFileEnumerator if successful. + **/ +GFileEnumerator * +g_file_enumerate_children (GFile *file, + const char *attributes, + GFileQueryInfoFlags flags, + GCancellable *cancellable, + GError **error) + +{ + GFileIface *iface; + + g_return_val_if_fail (G_IS_FILE (file), NULL); + + if (g_cancellable_set_error_if_cancelled (cancellable, error)) + return NULL; + + iface = G_FILE_GET_IFACE (file); + + if (iface->enumerate_children == NULL) + { + g_set_error (error, G_IO_ERROR, + G_IO_ERROR_NOT_SUPPORTED, + _("Operation not supported")); + return NULL; + } + + return (* iface->enumerate_children) (file, attributes, flags, + cancellable, error); +} + +/** + * g_file_enumerate_children_async: + * @file: input #GFile. + * @attributes: a string. + * @flags: a set of #GFileQueryInfoFlags. + * @io_priority: the io priority of the request. the io priority of the request. + * @cancellable: optional #GCancellable object, %NULL to ignore. + * @callback: a #GAsyncReadyCallback. + * @user_data: a #gpointer. + * + * Asynchronously enumerates the children of the given @file. + * + * If @cancellable is not %NULL, then the operation can be cancelled by + * triggering the cancellable object from another thread. If the operation + * was cancelled, the error %G_IO_ERROR_CANCELLED will be returned. If an + * operation was partially finished when the operation was cancelled the + * partial result will be returned, without an error. + * + **/ +void +g_file_enumerate_children_async (GFile *file, + const char *attributes, + GFileQueryInfoFlags flags, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GFileIface *iface; + + g_return_if_fail (G_IS_FILE (file)); + + iface = G_FILE_GET_IFACE (file); + (* iface->enumerate_children_async) (file, + attributes, + flags, + io_priority, + cancellable, + callback, + user_data); +} + +/** + * g_file_enumerate_children_finish: + * @file: input #GFile. + * @res: a #GAsyncResult. + * @error: a #GError. + * + * Returns: a #GFileEnumerator or %NULL if an error occurred. + **/ +GFileEnumerator * +g_file_enumerate_children_finish (GFile *file, + GAsyncResult *res, + GError **error) +{ + GFileIface *iface; + + g_return_val_if_fail (G_IS_FILE (file), NULL); + g_return_val_if_fail (G_IS_ASYNC_RESULT (res), NULL); + + if (G_IS_SIMPLE_ASYNC_RESULT (res)) + { + GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (res); + if (g_simple_async_result_propagate_error (simple, error)) + return NULL; + } + + iface = G_FILE_GET_IFACE (file); + return (* iface->enumerate_children_finish) (file, res, error); +} + + +/** + * g_file_query_info: + * @file: input #GFile. + * @attributes: a string. + * @flags: a set of #GFileQueryInfoFlags. + * @cancellable: optional #GCancellable object, %NULL to ignore. + * @error: a #GError. + * + * If @cancellable is not %NULL, then the operation can be cancelled by + * triggering the cancellable object from another thread. If the operation + * was cancelled, the error %G_IO_ERROR_CANCELLED will be returned. If an + * operation was partially finished when the operation was cancelled the + * partial result will be returned, without an error. + * + * Returns: a #GFileInfo for the given @file, or %NULL on error. + **/ +GFileInfo * +g_file_query_info (GFile *file, + const char *attributes, + GFileQueryInfoFlags flags, + GCancellable *cancellable, + GError **error) +{ + GFileIface *iface; + + g_return_val_if_fail (G_IS_FILE (file), NULL); + + if (g_cancellable_set_error_if_cancelled (cancellable, error)) + return NULL; + + iface = G_FILE_GET_IFACE (file); + + if (iface->query_info == NULL) + { + g_set_error (error, G_IO_ERROR, + G_IO_ERROR_NOT_SUPPORTED, + _("Operation not supported")); + return NULL; + } + + return (* iface->query_info) (file, attributes, flags, cancellable, error); +} + +/** + * g_file_query_info_async: + * @file: input #GFile. + * @attributes: a string. + * @flags: a set of #GFileQueryInfoFlags. + * @io_priority: the io priority of the request. the io priority of the request. + * @cancellable: optional #GCancellable object, %NULL to ignore. + * @callback: a #GAsyncReadyCallback. + * @user_data: a #gpointer. + * + * If @cancellable is not %NULL, then the operation can be cancelled by + * triggering the cancellable object from another thread. If the operation + * was cancelled, the error %G_IO_ERROR_CANCELLED will be returned. If an + * operation was partially finished when the operation was cancelled the + * partial result will be returned, without an error. + * + **/ +void +g_file_query_info_async (GFile *file, + const char *attributes, + GFileQueryInfoFlags flags, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GFileIface *iface; + + g_return_if_fail (G_IS_FILE (file)); + + iface = G_FILE_GET_IFACE (file); + (* iface->query_info_async) (file, + attributes, + flags, + io_priority, + cancellable, + callback, + user_data); +} + +/** + * g_file_query_info_finish: + * @file: input #GFile. + * @error: a #GAsyncResult. + * @error: a #GError. + * + * Finishes an asynchronous file info query. If the operation failed, + * returns %NULL and set @error appropriately if present. + * + * Returns: #GFileInfo for given @file or %NULL. + **/ +GFileInfo * +g_file_query_info_finish (GFile *file, + GAsyncResult *res, + GError **error) +{ + GFileIface *iface; + + g_return_val_if_fail (G_IS_FILE (file), NULL); + g_return_val_if_fail (G_IS_ASYNC_RESULT (res), NULL); + + if (G_IS_SIMPLE_ASYNC_RESULT (res)) + { + GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (res); + if (g_simple_async_result_propagate_error (simple, error)) + return NULL; + } + + iface = G_FILE_GET_IFACE (file); + return (* iface->query_info_finish) (file, res, error); +} + +/** + * g_file_query_filesystem_info: + * @file: input #GFile. + * @attributes: a string. + * @cancellable: optional #GCancellable object, %NULL to ignore. + * @error: a #GError. + * + * If @cancellable is not %NULL, then the operation can be cancelled by + * triggering the cancellable object from another thread. If the operation + * was cancelled, the error %G_IO_ERROR_CANCELLED will be returned. If an + * operation was partially finished when the operation was cancelled the + * partial result will be returned, without an error. + * + * Returns: a #GFileInfo or %NULl if there was an error. + **/ +GFileInfo * +g_file_query_filesystem_info (GFile *file, + const char *attributes, + GCancellable *cancellable, + GError **error) +{ + GFileIface *iface; + + g_return_val_if_fail (G_IS_FILE (file), NULL); + + if (g_cancellable_set_error_if_cancelled (cancellable, error)) + return NULL; + + iface = G_FILE_GET_IFACE (file); + + if (iface->query_filesystem_info == NULL) + { + g_set_error (error, G_IO_ERROR, + G_IO_ERROR_NOT_SUPPORTED, + _("Operation not supported")); + return NULL; + } + + return (* iface->query_filesystem_info) (file, attributes, cancellable, error); +} + +/** + * g_file_find_enclosing_volume: + * @file: input #GFile. + * @cancellable: optional #GCancellable object, %NULL to ignore. + * @error: a #GError. + * + * If @cancellable is not %NULL, then the operation can be cancelled by + * triggering the cancellable object from another thread. If the operation + * was cancelled, the error %G_IO_ERROR_CANCELLED will be returned. If an + * operation was partially finished when the operation was cancelled the + * partial result will be returned, without an error. + * + * Returns: a #GVolume where the @file is located. + **/ +GVolume * +g_file_find_enclosing_volume (GFile *file, + GCancellable *cancellable, + GError **error) +{ + GFileIface *iface; + + g_return_val_if_fail (G_IS_FILE (file), NULL); + + if (g_cancellable_set_error_if_cancelled (cancellable, error)) + return NULL; + + iface = G_FILE_GET_IFACE (file); + if (iface->find_enclosing_volume == NULL) + { + g_set_error (error, G_IO_ERROR, + G_IO_ERROR_NOT_FOUND, + _("Containing volume does not exist")); + return NULL; + } + + return (* iface->find_enclosing_volume) (file, cancellable, error); +} + +/** + * g_file_read: + * @file: #GFile to read. + * @cancellable: a #GCancellable + * @error: a #GError. + * + * Reads a whole file into a #GFileInputStream. Fails returning %NULL if + * given #GFile points to a directory. + * + * If @cancellable is not %NULL, then the operation can be cancelled by + * triggering the cancellable object from another thread. If the operation + * was cancelled, the error %G_IO_ERROR_CANCELLED will be returned. If an + * operation was partially finished when the operation was cancelled the + * partial result will be returned, without an error. + * + * Returns: #GFileInputStream or %NULL. + **/ +GFileInputStream * +g_file_read (GFile *file, + GCancellable *cancellable, + GError **error) +{ + GFileIface *iface; + + g_return_val_if_fail (G_IS_FILE (file), NULL); + + if (g_cancellable_set_error_if_cancelled (cancellable, error)) + return NULL; + + iface = G_FILE_GET_IFACE (file); + + if (iface->read == NULL) + { + g_set_error (error, G_IO_ERROR, + G_IO_ERROR_NOT_SUPPORTED, + _("Operation not supported")); + return NULL; + } + + return (* iface->read) (file, cancellable, error); +} + +/** + * g_file_append_to: + * @file: input #GFile. + * @flags: a set of #GFileCreateFlags. + * @cancellable: optional #GCancellable object, %NULL to ignore. + * + * If @cancellable is not %NULL, then the operation can be cancelled by + * triggering the cancellable object from another thread. If the operation + * was cancelled, the error %G_IO_ERROR_CANCELLED will be returned. If an + * operation was partially finished when the operation was cancelled the + * partial result will be returned, without an error. + * + * Returns: a #GFileOutputStream. + **/ +GFileOutputStream * +g_file_append_to (GFile *file, + GFileCreateFlags flags, + GCancellable *cancellable, + GError **error) +{ + GFileIface *iface; + + g_return_val_if_fail (G_IS_FILE (file), NULL); + + if (g_cancellable_set_error_if_cancelled (cancellable, error)) + return NULL; + + iface = G_FILE_GET_IFACE (file); + + if (iface->append_to == NULL) + { + g_set_error (error, G_IO_ERROR, + G_IO_ERROR_NOT_SUPPORTED, + _("Operation not supported")); + return NULL; + } + + return (* iface->append_to) (file, flags, cancellable, error); +} + +/** + * g_file_create: + * @file: input #GFile. + * @flags: a set of #GFileCreateFlags. + * @cancellable: optional #GCancellable object, %NULL to ignore. + * @error: a #GError. + * + * If @cancellable is not %NULL, then the operation can be cancelled by + * triggering the cancellable object from another thread. If the operation + * was cancelled, the error %G_IO_ERROR_CANCELLED will be returned. If an + * operation was partially finished when the operation was cancelled the + * partial result will be returned, without an error. + * + * Returns: a #GFileOutputStream for the newly created file, or + * %NULL on error. + **/ +GFileOutputStream * +g_file_create (GFile *file, + GFileCreateFlags flags, + GCancellable *cancellable, + GError **error) +{ + GFileIface *iface; + + g_return_val_if_fail (G_IS_FILE (file), NULL); + + if (g_cancellable_set_error_if_cancelled (cancellable, error)) + return NULL; + + iface = G_FILE_GET_IFACE (file); + + if (iface->create == NULL) + { + g_set_error (error, G_IO_ERROR, + G_IO_ERROR_NOT_SUPPORTED, + _("Operation not supported")); + return NULL; + } + + return (* iface->create) (file, flags, cancellable, error); +} + +/** + * g_file_replace: + * @file: input #GFile. + * @etag: an Entity Tag for the current #GFile. + * @make_backup: a #gboolean. + * @flags: a set of #GFileCreateFlags. + * @cancellable: optional #GCancellable object, %NULL to ignore. + * + * If @cancellable is not %NULL, then the operation can be cancelled by + * triggering the cancellable object from another thread. If the operation + * was cancelled, the error %G_IO_ERROR_CANCELLED will be returned. If an + * operation was partially finished when the operation was cancelled the + * partial result will be returned, without an error. + * + * Returns: a #GFileOutputStream or %NULL on error. If @make_backup is %TRUE, + * this function will attempt to make a backup of the current file. + **/ +GFileOutputStream * +g_file_replace (GFile *file, + const char *etag, + gboolean make_backup, + GFileCreateFlags flags, + GCancellable *cancellable, + GError **error) +{ + GFileIface *iface; + + g_return_val_if_fail (G_IS_FILE (file), NULL); + + if (g_cancellable_set_error_if_cancelled (cancellable, error)) + return NULL; + + iface = G_FILE_GET_IFACE (file); + + if (iface->replace == NULL) + { + g_set_error (error, G_IO_ERROR, + G_IO_ERROR_NOT_SUPPORTED, + _("Operation not supported")); + return NULL; + } + + + /* Handle empty tag string as NULL in consistent way. */ + if (etag && *etag == 0) + etag = NULL; + + return (* iface->replace) (file, etag, make_backup, flags, cancellable, error); +} + +/** + * g_file_read_async: + * @file: input #GFile. + * @io_priority: the io priority of the request. the io priority of the request. + * @cancellable: optional #GCancellable object, %NULL to ignore. + * @callback: a #GAsyncReadyCallback. + * @user_data: a #gpointer. + * + * If @cancellable is not %NULL, then the operation can be cancelled by + * triggering the cancellable object from another thread. If the operation + * was cancelled, the error %G_IO_ERROR_CANCELLED will be returned. If an + * operation was partially finished when the operation was cancelled the + * partial result will be returned, without an error. + * + * Asynchronously reads @file. For the synchronous version of this function, + * see g_file_read(). + **/ +void +g_file_read_async (GFile *file, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GFileIface *iface; + + g_return_if_fail (G_IS_FILE (file)); + + iface = G_FILE_GET_IFACE (file); + (* iface->read_async) (file, + io_priority, + cancellable, + callback, + user_data); +} + +/** + * g_file_read_finish: + * @file: input #GFile. + * @error: a #GAsyncResult. + * @error: a #GError. + * + * Returns: a #GFileInputStream or %NULL if there was an error. + **/ +GFileInputStream * +g_file_read_finish (GFile *file, + GAsyncResult *res, + GError **error) +{ + GFileIface *iface; + + g_return_val_if_fail (G_IS_FILE (file), NULL); + g_return_val_if_fail (G_IS_ASYNC_RESULT (res), NULL); + + if (G_IS_SIMPLE_ASYNC_RESULT (res)) + { + GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (res); + if (g_simple_async_result_propagate_error (simple, error)) + return NULL; + } + + iface = G_FILE_GET_IFACE (file); + return (* iface->read_finish) (file, res, error); +} + +/** + * g_file_append_to_async: + * @file: input #GFile. + * @flags: a set of #GFileCreateFlags. + * @io_priority: the io priority of the request. the io priority of the request. + * @cancellable: optional #GCancellable object, %NULL to ignore. + * @callback: a #GAsyncReadyCallback. + * @user_data: a #gpointer. + * + * Readies a file for appending data asynchronously. + * For the synchronous version of this function, see + * g_file_append_to(). + * + * If @cancellable is not %NULL, then the operation can be cancelled by + * triggering the cancellable object from another thread. If the operation + * was cancelled, the error %G_IO_ERROR_CANCELLED will be returned. If an + * operation was partially finished when the operation was cancelled the + * partial result will be returned, without an error. + * + **/ +void +g_file_append_to_async (GFile *file, + GFileCreateFlags flags, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GFileIface *iface; + + g_return_if_fail (G_IS_FILE (file)); + + iface = G_FILE_GET_IFACE (file); + (* iface->append_to_async) (file, + flags, + io_priority, + cancellable, + callback, + user_data); +} + +/** + * g_file_append_to_finish: + * @file: input #GFile. + * @res: #GAsyncResult + * @error: a #GError. + * + * Returns: a valid #GFileOutputStream or %NULL upon error. + **/ +GFileOutputStream * +g_file_append_to_finish (GFile *file, + GAsyncResult *res, + GError **error) +{ + GFileIface *iface; + + g_return_val_if_fail (G_IS_FILE (file), NULL); + g_return_val_if_fail (G_IS_ASYNC_RESULT (res), NULL); + + if (G_IS_SIMPLE_ASYNC_RESULT (res)) + { + GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (res); + if (g_simple_async_result_propagate_error (simple, error)) + return NULL; + } + + iface = G_FILE_GET_IFACE (file); + return (* iface->append_to_finish) (file, res, error); +} + +/** + * g_file_create_async: + * @file: input #GFile. + * @flags: a set of #GFileCreateFlags. + * @io_priority: the io priority of the request. the io priority of the request. + * @cancellable: optional #GCancellable object, %NULL to ignore. + * @callback: a #GAsyncReadyCallback. + * @user_data: a #gpointer. + * + * Creates a new file asynchronously. For the synchronous version of + * this function, see g_file_create(). + * + * If @cancellable is not %NULL, then the operation can be cancelled by + * triggering the cancellable object from another thread. If the operation + * was cancelled, the error %G_IO_ERROR_CANCELLED will be returned. If an + * operation was partially finished when the operation was cancelled the + * partial result will be returned, without an error. + * + **/ +void +g_file_create_async (GFile *file, + GFileCreateFlags flags, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GFileIface *iface; + + g_return_if_fail (G_IS_FILE (file)); + + iface = G_FILE_GET_IFACE (file); + (* iface->create_async) (file, + flags, + io_priority, + cancellable, + callback, + user_data); +} + +/** + * g_file_create_finish: + * @file: input #GFile. + * @error: a #GAsyncResult. + * @error: a #GError. + * + * Returns: a #GFileOutputStream. + **/ +GFileOutputStream * +g_file_create_finish (GFile *file, + GAsyncResult *res, + GError **error) +{ + GFileIface *iface; + + g_return_val_if_fail (G_IS_FILE (file), NULL); + g_return_val_if_fail (G_IS_ASYNC_RESULT (res), NULL); + + if (G_IS_SIMPLE_ASYNC_RESULT (res)) + { + GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (res); + if (g_simple_async_result_propagate_error (simple, error)) + return NULL; + } + + iface = G_FILE_GET_IFACE (file); + return (* iface->create_finish) (file, res, error); +} + +/** + * g_file_replace_async: + * @file: input #GFile. + * @etag: an Entity Tag for the current #GFile. + * @make_backup: a #gboolean. + * @flags: a set of #GFileCreateFlags. + * @io_priority: the io priority of the request. the io priority of the request. + * @cancellable: optional #GCancellable object, %NULL to ignore. + * @callback: a #GAsyncReadyCallback. + * @user_data: a #gpointer. + * + * Replaces a file's contents. For the synchronous version of this + * function, see g_file_replace(). If @make_backup is %TRUE, this function + * will attempt to make a backup of the current file. + * + * If @cancellable is not %NULL, then the operation can be cancelled by + * triggering the cancellable object from another thread. If the operation + * was cancelled, the error %G_IO_ERROR_CANCELLED will be returned. If an + * operation was partially finished when the operation was cancelled the + * partial result will be returned, without an error. + * + **/ +void +g_file_replace_async (GFile *file, + const char *etag, + gboolean make_backup, + GFileCreateFlags flags, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GFileIface *iface; + + g_return_if_fail (G_IS_FILE (file)); + + iface = G_FILE_GET_IFACE (file); + (* iface->replace_async) (file, + etag, + make_backup, + flags, + io_priority, + cancellable, + callback, + user_data); +} + +/** + * g_file_replace_finish: + * @file: input #GFile. + * @error: a #GAsyncResult. + * @error: a #GError. + * + * Finishes replacing the contents of the file started by + * g_file_replace_async(). + * + * Returns: a #GFileOutputStream, or %NULL if an error has occured. + **/ +GFileOutputStream * +g_file_replace_finish (GFile *file, + GAsyncResult *res, + GError **error) +{ + GFileIface *iface; + + g_return_val_if_fail (G_IS_FILE (file), NULL); + g_return_val_if_fail (G_IS_ASYNC_RESULT (res), NULL); + + if (G_IS_SIMPLE_ASYNC_RESULT (res)) + { + GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (res); + if (g_simple_async_result_propagate_error (simple, error)) + return NULL; + } + + iface = G_FILE_GET_IFACE (file); + return (* iface->replace_finish) (file, res, error); +} + +static gboolean +copy_symlink (GFile *destination, + GFileCopyFlags flags, + GCancellable *cancellable, + const char *target, + GError **error) +{ + GError *my_error; + gboolean tried_delete; + GFileInfo *info; + GFileType file_type; + + tried_delete = FALSE; + + retry: + my_error = NULL; + if (!g_file_make_symbolic_link (destination, target, cancellable, &my_error)) + { + /* Maybe it already existed, and we want to overwrite? */ + if (!tried_delete && (flags & G_FILE_COPY_OVERWRITE) && + my_error->domain == G_IO_ERROR && my_error->code == G_IO_ERROR_EXISTS) + { + g_error_free (my_error); + + + /* Don't overwrite if the destination is a directory */ + info = g_file_query_info (destination, G_FILE_ATTRIBUTE_STD_TYPE, + G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, + cancellable, &my_error); + if (info != NULL) + { + file_type = g_file_info_get_file_type (info); + g_object_unref (info); + + if (file_type == G_FILE_TYPE_DIRECTORY) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_IS_DIRECTORY, + _("Can't copy over directory")); + return FALSE; + } + } + + if (!g_file_delete (destination, cancellable, error)) + return FALSE; + + tried_delete = TRUE; + goto retry; + } + /* Nah, fail */ + g_propagate_error (error, my_error); + return FALSE; + } + + return TRUE; +} + +static GInputStream * +open_source_for_copy (GFile *source, + GFile *destination, + GFileCopyFlags flags, + GCancellable *cancellable, + GError **error) +{ + GError *my_error; + GInputStream *in; + GFileInfo *info; + GFileType file_type; + + my_error = NULL; + in = (GInputStream *)g_file_read (source, cancellable, &my_error); + if (in != NULL) + return in; + + /* There was an error opening the source, try to set a good error for it: */ + + if (my_error->domain == G_IO_ERROR && my_error->code == G_IO_ERROR_IS_DIRECTORY) + { + /* The source is a directory, don't fail with WOULD_RECURSE immediately, as + that is less useful to the app. Better check for errors on the target instead. */ + + g_error_free (my_error); + my_error = NULL; + + info = g_file_query_info (destination, G_FILE_ATTRIBUTE_STD_TYPE, + G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, + cancellable, &my_error); + if (info != NULL) + { + file_type = g_file_info_get_file_type (info); + g_object_unref (info); + + if (flags & G_FILE_COPY_OVERWRITE) + { + if (file_type == G_FILE_TYPE_DIRECTORY) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_WOULD_MERGE, + _("Can't copy directory over directory")); + return NULL; + } + /* continue to would_recurse error */ + } + else + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_EXISTS, + _("Target file exists")); + return NULL; + } + } + else + { + /* Error getting info from target, return that error (except for NOT_FOUND, which is no error here) */ + if (my_error->domain != G_IO_ERROR && my_error->code != G_IO_ERROR_NOT_FOUND) + { + g_propagate_error (error, my_error); + return NULL; + } + g_error_free (my_error); + } + + g_set_error (error, G_IO_ERROR, G_IO_ERROR_WOULD_RECURSE, + _("Can't recursively copy directory")); + return NULL; + } + + g_propagate_error (error, my_error); + return NULL; +} + +static gboolean +should_copy (GFileAttributeInfo *info, gboolean as_move) +{ + if (as_move) + return info->flags & G_FILE_ATTRIBUTE_FLAGS_COPY_WHEN_MOVED; + return info->flags & G_FILE_ATTRIBUTE_FLAGS_COPY_WITH_FILE; +} + +static char * +build_attribute_list_for_copy (GFileAttributeInfoList *attributes, + GFileAttributeInfoList *namespaces, + gboolean as_move) +{ + GString *s; + gboolean first; + int i; + + first = TRUE; + s = g_string_new (""); + + if (attributes) + { + for (i = 0; i < attributes->n_infos; i++) + { + if (should_copy (&attributes->infos[i], as_move)) + { + if (first) + first = FALSE; + else + g_string_append_c (s, ','); + + g_string_append (s, attributes->infos[i].name); + } + } + } + + if (namespaces) + { + for (i = 0; i < namespaces->n_infos; i++) + { + if (should_copy (&namespaces->infos[i], as_move)) + { + if (first) + first = FALSE; + else + g_string_append_c (s, ','); + + g_string_append (s, namespaces->infos[i].name); + g_string_append (s, ":*"); + } + } + } + + return g_string_free (s, FALSE); + +} + +gboolean +g_file_copy_attributes (GFile *source, + GFile *destination, + GFileCopyFlags flags, + GCancellable *cancellable, + GError **error) +{ + GFileAttributeInfoList *attributes, *namespaces; + char *attrs_to_read; + gboolean res; + GFileInfo *info; + gboolean as_move; + gboolean source_nofollow_symlinks; + + as_move = flags & G_FILE_COPY_ALL_METADATA; + source_nofollow_symlinks = flags & G_FILE_COPY_NOFOLLOW_SYMLINKS; + + /* Ignore errors here, if the target supports no attributes there is nothing to copy */ + attributes = g_file_query_settable_attributes (destination, cancellable, NULL); + namespaces = g_file_query_writable_namespaces (destination, cancellable, NULL); + + if (attributes == NULL && namespaces == NULL) + return TRUE; + + attrs_to_read = build_attribute_list_for_copy (attributes, namespaces, as_move); + + /* Ignore errors here, if we can't read some info (e.g. if it doesn't exist) + we just don't copy it. */ + info = g_file_query_info (source, attrs_to_read, + source_nofollow_symlinks ? G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS:0, + cancellable, + NULL); + + g_free (attrs_to_read); + + res = TRUE; + if (info) + { + res = g_file_set_attributes_from_info (destination, + info, 0, + cancellable, + error); + g_object_unref (info); + } + + g_file_attribute_info_list_unref (attributes); + g_file_attribute_info_list_unref (namespaces); + + return res; +} + +/* Closes the streams */ +static gboolean +copy_stream_with_progress (GInputStream *in, + GOutputStream *out, + GCancellable *cancellable, + GFileProgressCallback progress_callback, + gpointer progress_callback_data, + GError **error) +{ + gssize n_read, n_written; + goffset current_size; + char buffer[8192], *p; + gboolean res; + goffset total_size; + GFileInfo *info; + + total_size = 0; + info = g_file_input_stream_query_info (G_FILE_INPUT_STREAM (in), + G_FILE_ATTRIBUTE_STD_SIZE, + cancellable, NULL); + if (info) + { + total_size = g_file_info_get_size (info); + g_object_unref (info); + } + + current_size = 0; + res = TRUE; + while (TRUE) + { + n_read = g_input_stream_read (in, buffer, sizeof (buffer), cancellable, error); + if (n_read == -1) + { + res = FALSE; + break; + } + + if (n_read == 0) + break; + + current_size += n_read; + + p = buffer; + while (n_read > 0) + { + n_written = g_output_stream_write (out, p, n_read, cancellable, error); + if (n_written == -1) + { + res = FALSE; + break; + } + + p += n_written; + n_read -= n_written; + } + + if (!res) + break; + + if (progress_callback) + progress_callback (current_size, total_size, progress_callback_data); + } + + if (!res) + error = NULL; /* Ignore further errors */ + + /* Make sure we send full copied size */ + if (progress_callback) + progress_callback (current_size, total_size, progress_callback_data); + + + /* Don't care about errors in source here */ + g_input_stream_close (in, cancellable, NULL); + + /* But write errors on close are bad! */ + if (!g_output_stream_close (out, cancellable, error)) + res = FALSE; + + g_object_unref (in); + g_object_unref (out); + + return res; +} + +static gboolean +file_copy_fallback (GFile *source, + GFile *destination, + GFileCopyFlags flags, + GCancellable *cancellable, + GFileProgressCallback progress_callback, + gpointer progress_callback_data, + GError **error) +{ + GInputStream *in; + GOutputStream *out; + GFileInfo *info; + const char *target; + + /* Maybe copy the symlink? */ + if (flags & G_FILE_COPY_NOFOLLOW_SYMLINKS) + { + info = g_file_query_info (source, + G_FILE_ATTRIBUTE_STD_TYPE "," G_FILE_ATTRIBUTE_STD_SYMLINK_TARGET, + G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, + cancellable, + error); + if (info == NULL) + return FALSE; + + if (g_file_info_get_file_type (info) == G_FILE_TYPE_SYMBOLIC_LINK && + (target = g_file_info_get_symlink_target (info)) != NULL) + { + if (!copy_symlink (destination, flags, cancellable, target, error)) + { + g_object_unref (info); + return FALSE; + } + + g_object_unref (info); + goto copied_file; + } + + g_object_unref (info); + } + + in = open_source_for_copy (source, destination, flags, cancellable, error); + if (in == NULL) + return FALSE; + + if (flags & G_FILE_COPY_OVERWRITE) + { + out = (GOutputStream *)g_file_replace (destination, + NULL, 0, + flags & G_FILE_COPY_BACKUP, + cancellable, error); + } + else + { + out = (GOutputStream *)g_file_create (destination, 0, cancellable, error); + } + + if (out == NULL) + { + g_object_unref (in); + return FALSE; + } + + if (!copy_stream_with_progress (in, out, cancellable, + progress_callback, progress_callback_data, + error)) + return FALSE; + + copied_file: + + /* Ignore errors here. Failure to copy metadata is not a hard error */ + g_file_copy_attributes (source, destination, + flags, cancellable, NULL); + + return TRUE; +} + +/** + * g_file_copy: + * @source: input #GFile. + * @destination: destination #GFile + * @flags: set of #GFileCopyFlags + * @cancellable: optional #GCancellable object, %NULL to ignore. + * @progress_callback: function to callback with progress information + * @progress_callback_data: userdata to pass to @progress_callback + * @error: #GError to set on error + * + * List of possible errors resulting from g_file_copy(): + * source dest flags res + * - * * G_IO_ERROR_NOT_FOUND + * file - * ok + * file * 0 G_IO_ERROR_EXISTS + * file file overwr ok + * file dir overwr G_IO_ERROR_IS_DIRECTORY + * + * dir - * G_IO_ERROR_WOULD_RECURSE + * dir * 0 G_IO_ERROR_EXISTS + * dir dir overwr G_IO_ERROR_WOULD_MERGE + * dir file overwr G_IO_ERROR_WOULD_RECURSE + * + * Returns: %TRUE on success, %FALSE otherwise. + **/ +gboolean +g_file_copy (GFile *source, + GFile *destination, + GFileCopyFlags flags, + GCancellable *cancellable, + GFileProgressCallback progress_callback, + gpointer progress_callback_data, + GError **error) +{ + GFileIface *iface; + GError *my_error; + gboolean res; + + g_return_val_if_fail (G_IS_FILE (source), FALSE); + g_return_val_if_fail (G_IS_FILE (destination), FALSE); + + if (g_cancellable_set_error_if_cancelled (cancellable, error)) + return FALSE; + + if (G_OBJECT_TYPE (source) == G_OBJECT_TYPE (destination)) + { + iface = G_FILE_GET_IFACE (source); + + if (iface->copy) + { + my_error = NULL; + res = (* iface->copy) (source, destination, flags, cancellable, progress_callback, progress_callback_data, &my_error); + + if (res) + return TRUE; + + if (my_error->domain != G_IO_ERROR || my_error->code != G_IO_ERROR_NOT_SUPPORTED) + { + g_propagate_error (error, my_error); + return FALSE; + } + } + } + + return file_copy_fallback (source, destination, flags, cancellable, + progress_callback, progress_callback_data, + error); +} + + +/** + * g_file_move: + * @source: GFile* pointing to the source location. + * @destination: GFile* pointing to the destination location. + * @flags: GFileCopyFlags enum. + * @cancellable: optional #GCancellable object, %NULL to ignore. + * @progress_callback: GFileProgressCallback function for updates. + * @progress_callback_data: gpointer to user data for the callback function. + * @error: GError for returning error conditions. + * + * List of possible returns from g_file_move() with given source, + * destination, and flags: + * + * source dest flags results in + * - * * G_IO_ERROR_NOT_FOUND + * file - * ok + * file * 0 G_IO_ERROR_EXISTS + * file file overwr ok + * file dir overwr G_IO_ERROR_IS_DIRECTORY + * + * dir - * ok || G_IO_ERROR_WOULD_RECURSE + * dir * 0 G_IO_ERROR_EXISTS + * dir dir overwr G_IO_ERROR_WOULD_MERGE + * dir file overwr ok || G_IO_ERROR_WOULD_RECURSE + * + * Returns: %TRUE on successful move, %FALSE otherwise. + **/ +gboolean +g_file_move (GFile *source, + GFile *destination, + GFileCopyFlags flags, + GCancellable *cancellable, + GFileProgressCallback progress_callback, + gpointer progress_callback_data, + GError **error) +{ + GFileIface *iface; + GError *my_error; + gboolean res; + + g_return_val_if_fail (G_IS_FILE (source), FALSE); + g_return_val_if_fail (G_IS_FILE (destination), FALSE); + + if (g_cancellable_set_error_if_cancelled (cancellable, error)) + return FALSE; + + if (G_OBJECT_TYPE (source) == G_OBJECT_TYPE (destination)) + { + iface = G_FILE_GET_IFACE (source); + + if (iface->move) + { + my_error = NULL; + res = (* iface->move) (source, destination, flags, cancellable, progress_callback, progress_callback_data, &my_error); + + if (res) + return TRUE; + + if (my_error->domain != G_IO_ERROR || my_error->code != G_IO_ERROR_NOT_SUPPORTED) + { + g_propagate_error (error, my_error); + return FALSE; + } + } + } + + flags |= G_FILE_COPY_ALL_METADATA; + if (!g_file_copy (source, destination, flags, cancellable, + progress_callback, progress_callback_data, + error)) + return FALSE; + + return g_file_delete (source, cancellable, error); +} + +/** + * g_file_make_directory + * @file: input #GFile. + * @cancellable: optional #GCancellable object, %NULL to ignore. + * @error: a #GError. + * + * Returns: %TRUE on successful creation, %FALSE otherwise. + **/ +gboolean +g_file_make_directory (GFile *file, + GCancellable *cancellable, + GError **error) +{ + GFileIface *iface; + + g_return_val_if_fail (G_IS_FILE (file), FALSE); + + if (g_cancellable_set_error_if_cancelled (cancellable, error)) + return FALSE; + + iface = G_FILE_GET_IFACE (file); + + if (iface->make_directory == NULL) + { + g_set_error (error, G_IO_ERROR, + G_IO_ERROR_NOT_SUPPORTED, + _("Operation not supported")); + return FALSE; + } + + return (* iface->make_directory) (file, cancellable, error); +} + +/** + * g_file_make_symbolic_link: + * @file: input #GFile. + * @symlink_value: a string with the name of the new symlink. + * @cancellable: optional #GCancellable object, %NULL to ignore. + * @error: a #GError. + * + * Returns: %TRUE on the creation of a new symlink, %FALSE otherwise. + **/ +gboolean +g_file_make_symbolic_link (GFile *file, + const char *symlink_value, + GCancellable *cancellable, + GError **error) +{ + GFileIface *iface; + + g_return_val_if_fail (G_IS_FILE (file), FALSE); + g_return_val_if_fail (symlink_value != NULL, FALSE); + + if (g_cancellable_set_error_if_cancelled (cancellable, error)) + return FALSE; + + if (*symlink_value == '\0') + { + g_set_error (error, G_IO_ERROR, + G_IO_ERROR_INVALID_ARGUMENT, + _("Invalid symlink value given")); + return FALSE; + } + + iface = G_FILE_GET_IFACE (file); + + if (iface->make_symbolic_link == NULL) + { + g_set_error (error, G_IO_ERROR, + G_IO_ERROR_NOT_SUPPORTED, + _("Operation not supported")); + return FALSE; + } + + return (* iface->make_symbolic_link) (file, symlink_value, cancellable, error); +} + +/** + * g_file_delete: + * @file: input #GFile. + * @cancellable: optional #GCancellable object, %NULL to ignore. + * @error: a #GError. + * + * Returns: %TRUE if the file was deleted. %FALSE otherwise. + **/ +gboolean +g_file_delete (GFile *file, + GCancellable *cancellable, + GError **error) +{ + GFileIface *iface; + + g_return_val_if_fail (G_IS_FILE (file), FALSE); + + if (g_cancellable_set_error_if_cancelled (cancellable, error)) + return FALSE; + + iface = G_FILE_GET_IFACE (file); + + if (iface->delete_file == NULL) + { + g_set_error (error, G_IO_ERROR, + G_IO_ERROR_NOT_SUPPORTED, + _("Operation not supported")); + return FALSE; + } + + return (* iface->delete_file) (file, cancellable, error); +} + +/** + * g_file_trash: + * @file: GFile to location to send to trash. + * @cancellable: optional #GCancellable object, %NULL to ignore. + * @error: GError for possible failures. + * + * This function sends the object to the virtual "Trash" location. If the + * GFile interface does not have a corresponding "Trash" location, this function + * returns %FALSE, and will set @error appropriately. + * + * Returns: %TRUE on successful trash, %FALSE otherwise. + **/ +gboolean +g_file_trash (GFile *file, + GCancellable *cancellable, + GError **error) +{ + GFileIface *iface; + + g_return_val_if_fail (G_IS_FILE (file), FALSE); + + if (g_cancellable_set_error_if_cancelled (cancellable, error)) + return FALSE; + + iface = G_FILE_GET_IFACE (file); + + if (iface->trash == NULL) + { + g_set_error (error, + G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, + _("Trash not supported")); + return FALSE; + } + + return (* iface->trash) (file, cancellable, error); +} + +/** + * g_file_set_display_name: + * @file: input #GFile. + * @display_name: a string. + * @cancellable: optional #GCancellable object, %NULL to ignore. + * @error: a #GError. + * + * Returns: a #GFile, or %NULL if there was an error. + * For the asynchronous version of this function, see + * g_file_set_display_name_async(). + **/ +GFile * +g_file_set_display_name (GFile *file, + const char *display_name, + GCancellable *cancellable, + GError **error) +{ + GFileIface *iface; + + g_return_val_if_fail (G_IS_FILE (file), NULL); + g_return_val_if_fail (display_name != NULL, NULL); + + if (strchr (display_name, '/') != NULL) + { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_INVALID_ARGUMENT, + _("File names cannot contain '/'")); + return NULL; + } + + if (g_cancellable_set_error_if_cancelled (cancellable, error)) + return NULL; + + iface = G_FILE_GET_IFACE (file); + + return (* iface->set_display_name) (file, display_name, cancellable, error); +} + +/** + * g_file_set_display_name_async: + * @file: input #GFile. + * @display_name: a string. + * @io_priority: the io priority of the request. the io priority of the request. + * @cancellable: optional #GCancellable object, %NULL to ignore. + * @callback: a #GAsyncReadyCallback. + * @user_data: a #gpointer. + * + * Asynchronously sets the display name for a given #GFile. + * For the synchronous version of this function, see + * g_file_set_display_name(). + * + * If @cancellable is not %NULL, then the operation can be cancelled by + * triggering the cancellable object from another thread. If the operation + * was cancelled, the error %G_IO_ERROR_CANCELLED will be returned. If an + * operation was partially finished when the operation was cancelled the + * partial result will be returned, without an error. + * + **/ +void +g_file_set_display_name_async (GFile *file, + const char *display_name, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GFileIface *iface; + + g_return_if_fail (G_IS_FILE (file)); + g_return_if_fail (display_name != NULL); + + iface = G_FILE_GET_IFACE (file); + (* iface->set_display_name_async) (file, + display_name, + io_priority, + cancellable, + callback, + user_data); +} + +/** + * g_file_set_display_name_finish: + * @file: input #GFile. + * @error: a #GAsyncResult. + * @error: a #GError. + * + * Returns: a #GFile or %NULL on error. + **/ +GFile * +g_file_set_display_name_finish (GFile *file, + GAsyncResult *res, + GError **error) +{ + GFileIface *iface; + + g_return_val_if_fail (G_IS_FILE (file), NULL); + g_return_val_if_fail (G_IS_ASYNC_RESULT (res), NULL); + + if (G_IS_SIMPLE_ASYNC_RESULT (res)) + { + GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (res); + if (g_simple_async_result_propagate_error (simple, error)) + return NULL; + } + + iface = G_FILE_GET_IFACE (file); + return (* iface->set_display_name_finish) (file, res, error); +} + +/** + * g_file_query_settable_attributes: + * @file: input #GFile. + * @cancellable: optional #GCancellable object, %NULL to ignore. + * @error: a #GError. + * + * Returns: the type and full attribute name of all the attributes that + * the file can set. This doesn't mean setting it will always succeed though, + * you might get an access failure, or some specific file may not support a + * specific attribute. + **/ +GFileAttributeInfoList * +g_file_query_settable_attributes (GFile *file, + GCancellable *cancellable, + GError **error) +{ + GFileIface *iface; + GError *my_error; + GFileAttributeInfoList *list; + + g_return_val_if_fail (G_IS_FILE (file), NULL); + + if (g_cancellable_set_error_if_cancelled (cancellable, error)) + return NULL; + + iface = G_FILE_GET_IFACE (file); + + if (iface->query_settable_attributes == NULL) + return g_file_attribute_info_list_new (); + + my_error = NULL; + list = (* iface->query_settable_attributes) (file, cancellable, &my_error); + + if (list == NULL) + { + if (my_error->domain == G_IO_ERROR && my_error->code == G_IO_ERROR_NOT_SUPPORTED) + { + list = g_file_attribute_info_list_new (); + g_error_free (my_error); + } + else + g_propagate_error (error, my_error); + } + + return list; +} + +/** + * g_file_query_writable_namespaces: + * @file: input #GFile. + * @cancellable: optional #GCancellable object, %NULL to ignore. + * @error: a #GError. + * + * Returns: a #GFileAttributeInfoList of attribute namespaces + * where the user can create their own attribute names, such + * as extended attributes. + **/ +GFileAttributeInfoList * +g_file_query_writable_namespaces (GFile *file, + GCancellable *cancellable, + GError **error) +{ + GFileIface *iface; + GError *my_error; + GFileAttributeInfoList *list; + + g_return_val_if_fail (G_IS_FILE (file), NULL); + + if (g_cancellable_set_error_if_cancelled (cancellable, error)) + return NULL; + + iface = G_FILE_GET_IFACE (file); + + if (iface->query_writable_namespaces == NULL) + return g_file_attribute_info_list_new (); + + my_error = NULL; + list = (* iface->query_writable_namespaces) (file, cancellable, &my_error); + + if (list == NULL) + { + if (my_error->domain == G_IO_ERROR && my_error->code == G_IO_ERROR_NOT_SUPPORTED) + { + list = g_file_attribute_info_list_new (); + g_error_free (my_error); + } + else + g_propagate_error (error, my_error); + } + + return list; +} + +/** + * g_file_set_attribute: + * @file: input #GFile. + * @attribute: a string containing the attribute's name. + * @value: #GFileAttributeValue + * @flags: #GFileQueryInfoFlags + * @cancellable: optional #GCancellable object, %NULL to ignore. + * @error: a #GError. + * + * Returns: %TRUE if the attribute was correctly set, %FALSE + * otherwise. + **/ +gboolean +g_file_set_attribute (GFile *file, + const char *attribute, + const GFileAttributeValue *value, + GFileQueryInfoFlags flags, + GCancellable *cancellable, + GError **error) +{ + GFileIface *iface; + + g_return_val_if_fail (G_IS_FILE (file), FALSE); + g_return_val_if_fail (attribute != NULL && *attribute != '\0', FALSE); + + if (g_cancellable_set_error_if_cancelled (cancellable, error)) + return FALSE; + + iface = G_FILE_GET_IFACE (file); + + if (iface->set_attribute == NULL) + { + g_set_error (error, G_IO_ERROR, + G_IO_ERROR_NOT_SUPPORTED, + _("Operation not supported")); + return FALSE; + } + + return (* iface->set_attribute) (file, attribute, value, flags, cancellable, error); +} + +/** + * g_file_set_attributes_from_info: + * @file: input #GFile. + * @info: a #GFileInfo. + * @flags: #GFileQueryInfoFlags + * @cancellable: optional #GCancellable object, %NULL to ignore. + * @error: a #GError. + * + * Tries to set all attributes in the GFileInfo on the target values, not stopping + * on the first error. + * + * Returns: %TRUE if there was any error, and @error will be set to + * the first error. Error on particular fields are flagged by setting the + * "status" field in the attribute value to %G_FILE_ATTRIBUTE_STATUS_ERROR_SETTING. + **/ +gboolean +g_file_set_attributes_from_info (GFile *file, + GFileInfo *info, + GFileQueryInfoFlags flags, + GCancellable *cancellable, + GError **error) +{ + GFileIface *iface; + + g_return_val_if_fail (G_IS_FILE (file), FALSE); + g_return_val_if_fail (G_IS_FILE_INFO (info), FALSE); + + if (g_cancellable_set_error_if_cancelled (cancellable, error)) + return FALSE; + + g_file_info_clear_status (info); + + iface = G_FILE_GET_IFACE (file); + + return (* iface->set_attributes_from_info) (file, info, flags, cancellable, error); +} + + +static gboolean +g_file_real_set_attributes_from_info (GFile *file, + GFileInfo *info, + GFileQueryInfoFlags flags, + GCancellable *cancellable, + GError **error) +{ + char **attributes; + int i; + gboolean res; + GFileAttributeValue *value; + + res = TRUE; + + attributes = g_file_info_list_attributes (info, NULL); + + for (i = 0; attributes[i] != NULL; i++) + { + value = (GFileAttributeValue *)g_file_info_get_attribute (info, attributes[i]); + + if (value->status != G_FILE_ATTRIBUTE_STATUS_UNSET) + continue; + + if (!g_file_set_attribute (file, attributes[i], value, flags, cancellable, error)) + { + value->status = G_FILE_ATTRIBUTE_STATUS_ERROR_SETTING; + res = FALSE; + /* Don't set error multiple times */ + error = NULL; + } + else + value->status = G_FILE_ATTRIBUTE_STATUS_SET; + } + + g_strfreev (attributes); + + return res; +} + +/** + * g_file_set_attributes_async: + * @file: input #GFile. + * @info: a #GFileInfo. + * @flags: a #GFileQueryInfoFlags. + * @io_priority: the io priority of the request. the io priority of the request. + * @cancellable: optional #GCancellable object, %NULL to ignore. + * @callback: a #GAsyncReadyCallback. + * @user_data: a #gpointer. + * + * Asynchronously sets the attributes of @file with @info and @flags. + * For the synchronous version of this function, see g_file_set_attributes(). + * + * If @cancellable is not %NULL, then the operation can be cancelled by + * triggering the cancellable object from another thread. If the operation + * was cancelled, the error %G_IO_ERROR_CANCELLED will be returned. If an + * operation was partially finished when the operation was cancelled the + * partial result will be returned, without an error. + * + **/ +void +g_file_set_attributes_async (GFile *file, + GFileInfo *info, + GFileQueryInfoFlags flags, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GFileIface *iface; + + g_return_if_fail (G_IS_FILE (file)); + g_return_if_fail (G_IS_FILE_INFO (info)); + + iface = G_FILE_GET_IFACE (file); + (* iface->set_attributes_async) (file, info, flags, io_priority, cancellable, callback, user_data); + +} + +/** + * g_file_set_attributes_finish: + * @file: input #GFile. + * @result: a #GAsyncResult. + * @info: a #GFileInfo. + * @error: a #GError. + * + * Returns: %TRUE if the attributes were set correctly, %FALSE otherwise. + **/ +gboolean +g_file_set_attributes_finish (GFile *file, + GAsyncResult *result, + GFileInfo **info, + GError **error) +{ + GFileIface *iface; + + g_return_val_if_fail (G_IS_FILE (file), FALSE); + g_return_val_if_fail (G_IS_ASYNC_RESULT (result), FALSE); + + /* No standard handling of errors here, as we must set info even + on errors */ + + iface = G_FILE_GET_IFACE (file); + return (* iface->set_attributes_finish) (file, result, info, error); +} + +/** + * g_file_set_attribute_string: + * @file: input #GFile. + * @attribute: a string containing the attribute's name. + * @value: a string containing the attribute's value. + * @flags: #GFileQueryInfoFlags. + * @cancellable: optional #GCancellable object, %NULL to ignore. + * @error: a #GError. + * + * Returns: %TRUE if the @attribute was successfully set to @value + * in the @file, %FALSE otherwise. + **/ +gboolean +g_file_set_attribute_string (GFile *file, + const char *attribute, + const char *value, + GFileQueryInfoFlags flags, + GCancellable *cancellable, + GError **error) +{ + GFileAttributeValue v; + + v.type = G_FILE_ATTRIBUTE_TYPE_STRING; + v.u.string = (char *)value; + return g_file_set_attribute (file, attribute, &v, flags, cancellable, error); +} + +/** + * g_file_attribute_byte_string: + * @file: input #GFile. + * @attribute: a string containing the attribute's name. + * @value: a string containing the attribute's new value. + * @flags: a #GFileQueryInfoFlags. + * @cancellable: optional #GCancellable object, %NULL to ignore. + * @error: a #GError. + * + * Returns: %TRUE if the @attribute was successfully set to @value + * in the @file, %FALSE otherwise. + **/ +gboolean +g_file_set_attribute_byte_string (GFile *file, + const char *attribute, + const char *value, + GFileQueryInfoFlags flags, + GCancellable *cancellable, + GError **error) +{ + GFileAttributeValue v; + + v.type = G_FILE_ATTRIBUTE_TYPE_BYTE_STRING; + v.u.string = (char *)value; + return g_file_set_attribute (file, attribute, &v, flags, cancellable, error); +} + +/** + * g_file_set_attribute_uint32: + * @file: input #GFile. + * @attribute: a string containing the attribute's name. + * @value: a #guint32 containing the attribute's new value. + * @flags: a #GFileQueryInfoFlags. + * @cancellable: optional #GCancellable object, %NULL to ignore. + * @error: a #GError. + * + * Returns: %TRUE if the @attribute was successfully set to @value + * in the @file, %FALSE otherwise. + **/ +gboolean +g_file_set_attribute_uint32 (GFile *file, + const char *attribute, + guint32 value, + GFileQueryInfoFlags flags, + GCancellable *cancellable, + GError **error) +{ + GFileAttributeValue v; + + v.type = G_FILE_ATTRIBUTE_TYPE_UINT32; + v.u.uint32 = value; + return g_file_set_attribute (file, attribute, &v, flags, cancellable, error); +} + +/** + * g_file_set_attribute_int32: + * @file: input #GFile. + * @attribute: a string containing the attribute's name. + * @value: a #gint32 containing the attribute's new value. + * @flags: a #GFileQueryInfoFlags. + * @cancellable: optional #GCancellable object, %NULL to ignore. + * @error: a #GError. + * + * Returns: %TRUE if the @attribute was successfully set to @value + * in the @file, %FALSE otherwise. + **/ +gboolean +g_file_set_attribute_int32 (GFile *file, + const char *attribute, + gint32 value, + GFileQueryInfoFlags flags, + GCancellable *cancellable, + GError **error) +{ + GFileAttributeValue v; + + v.type = G_FILE_ATTRIBUTE_TYPE_INT32; + v.u.int32 = value; + return g_file_set_attribute (file, attribute, &v, flags, cancellable, error); +} + +/** + * g_file_set_attribute_uint64: + * @file: input #GFile. + * @attribute: a string containing the attribute's name. + * @value: a #guint64 containing the attribute's new value. + * @flags: a #GFileQueryInfoFlags. + * @cancellable: optional #GCancellable object, %NULL to ignore. + * @error: a #GError. + * + * Returns: %TRUE if the @attribute was successfully set to @value + * in the @file, %FALSE otherwise. + **/ +gboolean +g_file_set_attribute_uint64 (GFile *file, + const char *attribute, + guint64 value, + GFileQueryInfoFlags flags, + GCancellable *cancellable, + GError **error) + { + GFileAttributeValue v; + + v.type = G_FILE_ATTRIBUTE_TYPE_UINT64; + v.u.uint64 = value; + return g_file_set_attribute (file, attribute, &v, flags, cancellable, error); +} + +/** + * g_file_set_attribute_int64: + * @file: input #GFile. + * @attribute: a string containing the attribute's name. + * @value: a #guint64 containing the attribute's new value. + * @flags: a #GFileQueryInfoFlags. + * @cancellable: optional #GCancellable object, %NULL to ignore. + * @error: a #GError. + * + * Returns: %TRUE if the @attribute was successfully set to @value + * in the @file, %FALSE otherwise. + **/ +gboolean +g_file_set_attribute_int64 (GFile *file, + const char *attribute, + gint64 value, + GFileQueryInfoFlags flags, + GCancellable *cancellable, + GError **error) +{ + GFileAttributeValue v; + + v.type = G_FILE_ATTRIBUTE_TYPE_INT64; + v.u.int64 = value; + return g_file_set_attribute (file, attribute, &v, flags, cancellable, error); +} + +/** + * g_file_mount_mountable: + * @file: input #GFile. + * @mount_operation: a #GMountOperation. + * @cancellable: optional #GCancellable object, %NULL to ignore. + * @callback: a #GAsyncReadyCallback. + * @user_data: a #gpointer. + * + * Mounts a mountable file using @mount_operation, if possible. + * + **/ +void +g_file_mount_mountable (GFile *file, + GMountOperation *mount_operation, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GFileIface *iface; + + g_return_if_fail (G_IS_FILE (file)); + g_return_if_fail (G_IS_MOUNT_OPERATION (mount_operation)); + + iface = G_FILE_GET_IFACE (file); + + if (iface->mount_mountable == NULL) + { + g_simple_async_report_error_in_idle (G_OBJECT (file), + callback, + user_data, + G_IO_ERROR, + G_IO_ERROR_NOT_SUPPORTED, + _("Operation not supported")); + } + + (* iface->mount_mountable) (file, + mount_operation, + cancellable, + callback, + user_data); + +} + +/** + * g_file_mount_mountable_finish: + * @file: input #GFile. + * @result: a #GAsyncResult. + * @error: a #GError. + * + * Returns: a #GFile or %NULL on error. + **/ +GFile * +g_file_mount_mountable_finish (GFile *file, + GAsyncResult *result, + GError **error) +{ + GFileIface *iface; + + g_return_val_if_fail (G_IS_FILE (file), NULL); + g_return_val_if_fail (G_IS_ASYNC_RESULT (result), NULL); + + if (G_IS_SIMPLE_ASYNC_RESULT (result)) + { + GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (result); + if (g_simple_async_result_propagate_error (simple, error)) + return NULL; + } + + iface = G_FILE_GET_IFACE (file); + return (* iface->mount_mountable_finish) (file, result, error); +} + +/** + * g_file_unmount_mountable: + * @file: input #GFile. + * @cancellable: optional #GCancellable object, %NULL to ignore. + * @callback: a #GAsyncReadyCallback. + * @user_data: a #gpointer. + * + **/ +void +g_file_unmount_mountable (GFile *file, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GFileIface *iface; + + g_return_if_fail (G_IS_FILE (file)); + + iface = G_FILE_GET_IFACE (file); + + if (iface->unmount_mountable == NULL) + { + g_simple_async_report_error_in_idle (G_OBJECT (file), + callback, + user_data, + G_IO_ERROR, + G_IO_ERROR_NOT_SUPPORTED, + _("Operation not supported")); + } + + (* iface->unmount_mountable) (file, + cancellable, + callback, + user_data); +} + +/** + * g_file_unmount_mountable_finish: + * @file: input #GFile. + * @result: a #GAsyncResult. + * @error: a #GError. + * + * Returns: %TRUE if the operation finished successfully. %FALSE + * otherwise. + **/ +gboolean +g_file_unmount_mountable_finish (GFile *file, + GAsyncResult *result, + GError **error) +{ + GFileIface *iface; + + g_return_val_if_fail (G_IS_FILE (file), FALSE); + g_return_val_if_fail (G_IS_ASYNC_RESULT (result), FALSE); + + if (G_IS_SIMPLE_ASYNC_RESULT (result)) + { + GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (result); + if (g_simple_async_result_propagate_error (simple, error)) + return FALSE; + } + + iface = G_FILE_GET_IFACE (file); + return (* iface->unmount_mountable_finish) (file, result, error); +} + +/** + * g_file_eject_mountable: + * @file: input #GFile. + * @cancellable: optional #GCancellable object, %NULL to ignore. + * @callback: a #GAsyncReadyCallback. + * @user_data: a #gpointer. + * + **/ +void +g_file_eject_mountable (GFile *file, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GFileIface *iface; + + g_return_if_fail (G_IS_FILE (file)); + + iface = G_FILE_GET_IFACE (file); + + if (iface->eject_mountable == NULL) + { + g_simple_async_report_error_in_idle (G_OBJECT (file), + callback, + user_data, + G_IO_ERROR, + G_IO_ERROR_NOT_SUPPORTED, + _("Operation not supported")); + } + + (* iface->eject_mountable) (file, + cancellable, + callback, + user_data); +} + +/** + * g_file_eject_mountable_finish: + * @file: input #GFile. + * @result: a #GAsyncResult. + * @error: a #GError. + * + * Returns: %TRUE if the @file was ejected successfully. %FALSE + * otherwise. + **/ +gboolean +g_file_eject_mountable_finish (GFile *file, + GAsyncResult *result, + GError **error) +{ + GFileIface *iface; + + g_return_val_if_fail (G_IS_FILE (file), FALSE); + g_return_val_if_fail (G_IS_ASYNC_RESULT (result), FALSE); + + if (G_IS_SIMPLE_ASYNC_RESULT (result)) + { + GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (result); + if (g_simple_async_result_propagate_error (simple, error)) + return FALSE; + } + + iface = G_FILE_GET_IFACE (file); + return (* iface->eject_mountable_finish) (file, result, error); +} + +/** + * g_file_monitor_directory: + * @file: input #GFile. + * @flags: a set of #GFileMonitorFlags. + * @cancellable: optional #GCancellable object, %NULL to ignore. + * + * Returns: a #GDirectoryMonitor for the given @file, + * or %NULL on error. + **/ +GDirectoryMonitor* +g_file_monitor_directory (GFile *file, + GFileMonitorFlags flags, + GCancellable *cancellable) +{ + GFileIface *iface; + + g_return_val_if_fail (G_IS_FILE (file), NULL); + + iface = G_FILE_GET_IFACE (file); + + if (iface->monitor_dir == NULL) + return NULL; + + return (* iface->monitor_dir) (file, flags, cancellable); +} + +/** + * g_file_monitor_file: + * @file: input #GFile. + * @flags: a set of #GFileMonitorFlags. + * @cancellable: optional #GCancellable object, %NULL to ignore. + * + * Returns: a #GFileMonitor for the given @file, + * or %NULL on error. + **/ +GFileMonitor* +g_file_monitor_file (GFile *file, + GFileMonitorFlags flags, + GCancellable *cancellable) +{ + GFileIface *iface; + GFileMonitor *monitor; + + g_return_val_if_fail (G_IS_FILE (file), NULL); + + iface = G_FILE_GET_IFACE (file); + + monitor = NULL; + + if (iface->monitor_file) + monitor = (* iface->monitor_file) (file, flags, cancellable); + +/* Fallback to polling */ + if (monitor == NULL) + monitor = g_poll_file_monitor_new (file); + + return monitor; +} + +/******************************************** + * Default implementation of async ops * + ********************************************/ + +typedef struct { + char *attributes; + GFileQueryInfoFlags flags; + GFileInfo *info; +} QueryInfoAsyncData; + +static void +query_info_data_free (QueryInfoAsyncData *data) +{ + if (data->info) + g_object_unref (data->info); + g_free (data->attributes); + g_free (data); +} + +static void +query_info_async_thread (GSimpleAsyncResult *res, + GObject *object, + GCancellable *cancellable) +{ + GError *error = NULL; + QueryInfoAsyncData *data; + GFileInfo *info; + + data = g_simple_async_result_get_op_res_gpointer (res); + + info = g_file_query_info (G_FILE (object), data->attributes, data->flags, cancellable, &error); + + if (info == NULL) + { + g_simple_async_result_set_from_error (res, error); + g_error_free (error); + } + else + data->info = info; +} + +static void +g_file_real_query_info_async (GFile *file, + const char *attributes, + GFileQueryInfoFlags flags, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GSimpleAsyncResult *res; + QueryInfoAsyncData *data; + + data = g_new0 (QueryInfoAsyncData, 1); + data->attributes = g_strdup (attributes); + data->flags = flags; + + res = g_simple_async_result_new (G_OBJECT (file), callback, user_data, g_file_real_query_info_async); + g_simple_async_result_set_op_res_gpointer (res, data, (GDestroyNotify)query_info_data_free); + + g_simple_async_result_run_in_thread (res, query_info_async_thread, io_priority, cancellable); + g_object_unref (res); +} + +static GFileInfo * +g_file_real_query_info_finish (GFile *file, + GAsyncResult *res, + GError **error) +{ + GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (res); + QueryInfoAsyncData *data; + + g_assert (g_simple_async_result_get_source_tag (simple) == g_file_real_query_info_async); + + data = g_simple_async_result_get_op_res_gpointer (simple); + if (data->info) + return g_object_ref (data->info); + + return NULL; +} + +typedef struct { + char *attributes; + GFileQueryInfoFlags flags; + GFileEnumerator *enumerator; +} EnumerateChildrenAsyncData; + +static void +enumerate_children_data_free (EnumerateChildrenAsyncData *data) +{ + if (data->enumerator) + g_object_unref (data->enumerator); + g_free (data->attributes); + g_free (data); +} + +static void +enumerate_children_async_thread (GSimpleAsyncResult *res, + GObject *object, + GCancellable *cancellable) +{ + GError *error = NULL; + EnumerateChildrenAsyncData *data; + GFileEnumerator *enumerator; + + data = g_simple_async_result_get_op_res_gpointer (res); + + enumerator = g_file_enumerate_children (G_FILE (object), data->attributes, data->flags, cancellable, &error); + + if (enumerator == NULL) + { + g_simple_async_result_set_from_error (res, error); + g_error_free (error); + } + else + data->enumerator = enumerator; +} + +static void +g_file_real_enumerate_children_async (GFile *file, + const char *attributes, + GFileQueryInfoFlags flags, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GSimpleAsyncResult *res; + EnumerateChildrenAsyncData *data; + + data = g_new0 (EnumerateChildrenAsyncData, 1); + data->attributes = g_strdup (attributes); + data->flags = flags; + + res = g_simple_async_result_new (G_OBJECT (file), callback, user_data, g_file_real_enumerate_children_async); + g_simple_async_result_set_op_res_gpointer (res, data, (GDestroyNotify)enumerate_children_data_free); + + g_simple_async_result_run_in_thread (res, enumerate_children_async_thread, io_priority, cancellable); + g_object_unref (res); +} + +static GFileEnumerator * +g_file_real_enumerate_children_finish (GFile *file, + GAsyncResult *res, + GError **error) +{ + GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (res); + EnumerateChildrenAsyncData *data; + + g_assert (g_simple_async_result_get_source_tag (simple) == g_file_real_enumerate_children_async); + + data = g_simple_async_result_get_op_res_gpointer (simple); + if (data->enumerator) + return g_object_ref (data->enumerator); + + return NULL; +} + +static void +open_read_async_thread (GSimpleAsyncResult *res, + GObject *object, + GCancellable *cancellable) +{ + GFileIface *iface; + GFileInputStream *stream; + GError *error = NULL; + + iface = G_FILE_GET_IFACE (object); + + stream = iface->read (G_FILE (object), cancellable, &error); + + if (stream == NULL) + { + g_simple_async_result_set_from_error (res, error); + g_error_free (error); + } + else + g_simple_async_result_set_op_res_gpointer (res, stream, g_object_unref); +} + +static void +g_file_real_read_async (GFile *file, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GSimpleAsyncResult *res; + + res = g_simple_async_result_new (G_OBJECT (file), callback, user_data, g_file_real_read_async); + + g_simple_async_result_run_in_thread (res, open_read_async_thread, io_priority, cancellable); + g_object_unref (res); +} + +static GFileInputStream * +g_file_real_read_finish (GFile *file, + GAsyncResult *res, + GError **error) +{ + GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (res); + gpointer op; + + g_assert (g_simple_async_result_get_source_tag (simple) == g_file_real_read_async); + + op = g_simple_async_result_get_op_res_gpointer (simple); + if (op) + return g_object_ref (op); + + return NULL; +} + +static void +append_to_async_thread (GSimpleAsyncResult *res, + GObject *object, + GCancellable *cancellable) +{ + GFileIface *iface; + GFileCreateFlags *data; + GFileOutputStream *stream; + GError *error = NULL; + + iface = G_FILE_GET_IFACE (object); + + data = g_simple_async_result_get_op_res_gpointer (res); + + stream = iface->append_to (G_FILE (object), *data, cancellable, &error); + + if (stream == NULL) + { + g_simple_async_result_set_from_error (res, error); + g_error_free (error); + } + else + g_simple_async_result_set_op_res_gpointer (res, stream, g_object_unref); +} + +static void +g_file_real_append_to_async (GFile *file, + GFileCreateFlags flags, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GFileCreateFlags *data; + GSimpleAsyncResult *res; + + data = g_new0 (GFileCreateFlags, 1); + *data = flags; + + res = g_simple_async_result_new (G_OBJECT (file), callback, user_data, g_file_real_append_to_async); + g_simple_async_result_set_op_res_gpointer (res, data, (GDestroyNotify)g_free); + + g_simple_async_result_run_in_thread (res, append_to_async_thread, io_priority, cancellable); + g_object_unref (res); +} + +static GFileOutputStream * +g_file_real_append_to_finish (GFile *file, + GAsyncResult *res, + GError **error) +{ + GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (res); + gpointer op; + + g_assert (g_simple_async_result_get_source_tag (simple) == g_file_real_append_to_async); + + op = g_simple_async_result_get_op_res_gpointer (simple); + if (op) + return g_object_ref (op); + + return NULL; +} + +static void +create_async_thread (GSimpleAsyncResult *res, + GObject *object, + GCancellable *cancellable) +{ + GFileIface *iface; + GFileCreateFlags *data; + GFileOutputStream *stream; + GError *error = NULL; + + iface = G_FILE_GET_IFACE (object); + + data = g_simple_async_result_get_op_res_gpointer (res); + + stream = iface->create (G_FILE (object), *data, cancellable, &error); + + if (stream == NULL) + { + g_simple_async_result_set_from_error (res, error); + g_error_free (error); + } + else + g_simple_async_result_set_op_res_gpointer (res, stream, g_object_unref); +} + +static void +g_file_real_create_async (GFile *file, + GFileCreateFlags flags, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GFileCreateFlags *data; + GSimpleAsyncResult *res; + + data = g_new0 (GFileCreateFlags, 1); + *data = flags; + + res = g_simple_async_result_new (G_OBJECT (file), callback, user_data, g_file_real_create_async); + g_simple_async_result_set_op_res_gpointer (res, data, (GDestroyNotify)g_free); + + g_simple_async_result_run_in_thread (res, create_async_thread, io_priority, cancellable); + g_object_unref (res); +} + +static GFileOutputStream * +g_file_real_create_finish (GFile *file, + GAsyncResult *res, + GError **error) +{ + GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (res); + gpointer op; + + g_assert (g_simple_async_result_get_source_tag (simple) == g_file_real_create_async); + + op = g_simple_async_result_get_op_res_gpointer (simple); + if (op) + return g_object_ref (op); + + return NULL; +} + +typedef struct { + GFileOutputStream *stream; + char *etag; + gboolean make_backup; + GFileCreateFlags flags; +} ReplaceAsyncData; + +static void +replace_async_data_free (ReplaceAsyncData *data) +{ + if (data->stream) + g_object_unref (data->stream); + g_free (data->etag); + g_free (data); +} + +static void +replace_async_thread (GSimpleAsyncResult *res, + GObject *object, + GCancellable *cancellable) +{ + GFileIface *iface; + GFileOutputStream *stream; + GError *error = NULL; + ReplaceAsyncData *data; + + iface = G_FILE_GET_IFACE (object); + + data = g_simple_async_result_get_op_res_gpointer (res); + + stream = iface->replace (G_FILE (object), + data->etag, + data->make_backup, + data->flags, + cancellable, + &error); + + if (stream == NULL) + { + g_simple_async_result_set_from_error (res, error); + g_error_free (error); + } + else + data->stream = stream; +} + +static void +g_file_real_replace_async (GFile *file, + const char *etag, + gboolean make_backup, + GFileCreateFlags flags, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GSimpleAsyncResult *res; + ReplaceAsyncData *data; + + data = g_new0 (ReplaceAsyncData, 1); + data->etag = g_strdup (etag); + data->make_backup = make_backup; + data->flags = flags; + + res = g_simple_async_result_new (G_OBJECT (file), callback, user_data, g_file_real_replace_async); + g_simple_async_result_set_op_res_gpointer (res, data, (GDestroyNotify)replace_async_data_free); + + g_simple_async_result_run_in_thread (res, replace_async_thread, io_priority, cancellable); + g_object_unref (res); +} + +static GFileOutputStream * +g_file_real_replace_finish (GFile *file, + GAsyncResult *res, + GError **error) +{ + GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (res); + ReplaceAsyncData *data; + + g_assert (g_simple_async_result_get_source_tag (simple) == g_file_real_replace_async); + + data = g_simple_async_result_get_op_res_gpointer (simple); + if (data->stream) + return g_object_ref (data->stream); + + return NULL; +} + +typedef struct { + char *name; + GFile *file; +} SetDisplayNameAsyncData; + +static void +set_display_name_data_free (SetDisplayNameAsyncData *data) +{ + g_free (data->name); + if (data->file) + g_object_unref (data->file); + g_free (data); +} + +static void +set_display_name_async_thread (GSimpleAsyncResult *res, + GObject *object, + GCancellable *cancellable) +{ + GError *error = NULL; + SetDisplayNameAsyncData *data; + GFile *file; + + data = g_simple_async_result_get_op_res_gpointer (res); + + file = g_file_set_display_name (G_FILE (object), data->name, cancellable, &error); + + if (file == NULL) + { + g_simple_async_result_set_from_error (res, error); + g_error_free (error); + } + else + data->file = file; +} + +static void +g_file_real_set_display_name_async (GFile *file, + const char *display_name, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GSimpleAsyncResult *res; + SetDisplayNameAsyncData *data; + + data = g_new0 (SetDisplayNameAsyncData, 1); + data->name = g_strdup (display_name); + + res = g_simple_async_result_new (G_OBJECT (file), callback, user_data, g_file_real_set_display_name_async); + g_simple_async_result_set_op_res_gpointer (res, data, (GDestroyNotify)set_display_name_data_free); + + g_simple_async_result_run_in_thread (res, set_display_name_async_thread, io_priority, cancellable); + g_object_unref (res); +} + +static GFile * +g_file_real_set_display_name_finish (GFile *file, + GAsyncResult *res, + GError **error) +{ + GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (res); + SetDisplayNameAsyncData *data; + + g_assert (g_simple_async_result_get_source_tag (simple) == g_file_real_set_display_name_async); + + data = g_simple_async_result_get_op_res_gpointer (simple); + if (data->file) + return g_object_ref (data->file); + + return NULL; +} + +typedef struct { + GFileQueryInfoFlags flags; + GFileInfo *info; + gboolean res; + GError *error; +} SetInfoAsyncData; + +static void +set_info_data_free (SetInfoAsyncData *data) +{ + if (data->info) + g_object_unref (data->info); + if (data->error) + g_error_free (data->error); + g_free (data); +} + +static void +set_info_async_thread (GSimpleAsyncResult *res, + GObject *object, + GCancellable *cancellable) +{ + SetInfoAsyncData *data; + + data = g_simple_async_result_get_op_res_gpointer (res); + + data->error = NULL; + data->res = g_file_set_attributes_from_info (G_FILE (object), + data->info, + data->flags, + cancellable, + &data->error); +} + +static void +g_file_real_set_attributes_async (GFile *file, + GFileInfo *info, + GFileQueryInfoFlags flags, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GSimpleAsyncResult *res; + SetInfoAsyncData *data; + + data = g_new0 (SetInfoAsyncData, 1); + data->info = g_file_info_dup (info); + data->flags = flags; + + res = g_simple_async_result_new (G_OBJECT (file), callback, user_data, g_file_real_set_attributes_async); + g_simple_async_result_set_op_res_gpointer (res, data, (GDestroyNotify)set_info_data_free); + + g_simple_async_result_run_in_thread (res, set_info_async_thread, io_priority, cancellable); + g_object_unref (res); +} + +static gboolean +g_file_real_set_attributes_finish (GFile *file, + GAsyncResult *res, + GFileInfo **info, + GError **error) +{ + GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (res); + SetInfoAsyncData *data; + + g_assert (g_simple_async_result_get_source_tag (simple) == g_file_real_set_attributes_async); + + data = g_simple_async_result_get_op_res_gpointer (simple); + + if (info) + *info = g_object_ref (data->info); + + if (error != NULL && data->error) { + *error = g_error_copy (data->error); + } + + return data->res; +} + +/******************************************** + * Default VFS operations * + ********************************************/ + +/** + * g_file_new_for_path: + * @path: a string containing a relative or absolute path. + * + * Constructs a #GFile for given @path. This operation never + * fails, but the returned object might not support any I/O + * operation if the @path is malformed. + * + * Returns a new #GFile for the given @path. + **/ +GFile * +g_file_new_for_path (const char *path) +{ + g_return_val_if_fail (path != NULL, NULL); + + return g_vfs_get_file_for_path (g_vfs_get_default (), + path); +} + +/** + * g_file_new_for_uri: + * @uri: a string containing a URI. + * + * This operation never fails, but the returned object + * might not support any I/O operation if the @uri + * is malformed or if the uri type is not supported. + * + * Returns a #GFile for the given @uri. + **/ +GFile * +g_file_new_for_uri (const char *uri) +{ + g_return_val_if_fail (uri != NULL, NULL); + + return g_vfs_get_file_for_uri (g_vfs_get_default (), + uri); +} + +/** + * g_file_parse_name: + * @parse_name: a file name or path to be parsed. + * + * Constructs a #GFile with the given @parse_name, + * looked up by #GVfs. This operation never fails, + * but the returned object might not support any I/O + * operation if the @parse_name cannot be parsed by #GVfs. + * + * Returns a new #GFile. + **/ +GFile * +g_file_parse_name (const char *parse_name) +{ + g_return_val_if_fail (parse_name != NULL, NULL); + + return g_vfs_parse_name (g_vfs_get_default (), + parse_name); +} + +static gboolean +is_valid_scheme_character (char c) +{ + return g_ascii_isalnum (c) || c == '+' || c == '-' || c == '.'; +} + +static gboolean +has_valid_scheme (const char *uri) +{ + const char *p; + + p = uri; + + if (!is_valid_scheme_character (*p)) + return FALSE; + + do { + p++; + } while (is_valid_scheme_character (*p)); + + return *p == ':'; +} + +/** + * g_file_new_for_commandline_arg: + * @arg: a command line string. + * + * Attempts to generate a #GFile with the given line from + * the command line argument. + * + * Returns a new #GFile. + **/ +GFile * +g_file_new_for_commandline_arg (const char *arg) +{ + GFile *file; + char *filename; + char *current_dir; + + g_return_val_if_fail (arg != NULL, NULL); + + if (g_path_is_absolute (arg)) + return g_file_new_for_path (arg); + + if (has_valid_scheme (arg)) + return g_file_new_for_uri (arg); + + current_dir = g_get_current_dir (); + filename = g_build_filename (current_dir, arg, NULL); + g_free (current_dir); + + file = g_file_new_for_path (filename); + g_free (filename); + + return file; +} + +/** + * g_mount_for_location: + * @location: input #GFile. + * @mount_operation: a #GMountOperation. + * @cancellable: optional #GCancellable object, %NULL to ignore. + * @callback: a #GAsyncReadyCallback. + * @user_data: a #gpointer. + * + * Starts the @mount_operation, mounting the volume at @location. This + * operation is cancellable with @cancellable. When the operation has + * completed, @callback will be called with user data. To finish + * the operation, call g_mount_for_location_finish() with the + * #GAsyncResult returned by the @callback. + * + **/ +void +g_mount_for_location (GFile *location, + GMountOperation *mount_operation, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GFileIface *iface; + + g_return_if_fail (G_IS_FILE (location)); + g_return_if_fail (G_IS_MOUNT_OPERATION (mount_operation)); + + iface = G_FILE_GET_IFACE (location); + + if (iface->mount_for_location == NULL) + { + g_simple_async_report_error_in_idle (G_OBJECT (location), + callback, user_data, + G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, + _("volume doesn't implement mount")); + + return; + } + + (* iface->mount_for_location) (location, mount_operation, cancellable, callback, user_data); + +} + +/** + * g_mount_for_location_finish: + * @location: input #GFile. + * @result: a #GAsyncResult. + * @error: a #GError. + * + * Finishes an Asynchronous mount operation. + * + * Returns: %TRUE if the mount was finished successfully. If %FALSE and + * @error is present, it will be set appropriately. + **/ +gboolean +g_mount_for_location_finish (GFile *location, + GAsyncResult *result, + GError **error) +{ + GFileIface *iface; + + g_return_val_if_fail (G_IS_FILE (location), FALSE); + g_return_val_if_fail (G_IS_ASYNC_RESULT (result), FALSE); + + if (G_IS_SIMPLE_ASYNC_RESULT (result)) + { + GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (result); + if (g_simple_async_result_propagate_error (simple, error)) + return FALSE; + } + + iface = G_FILE_GET_IFACE (location); + + return (* iface->mount_for_location_finish) (location, result, error); +} + +/******************************************** + * Utility functions * + ********************************************/ + +#define GET_CONTENT_BLOCK_SIZE 8192 + +/** + * g_file_load_contents: + * @file: input #GFile. + * @cancellable: optional #GCancellable object, %NULL to ignore. + * @contents: + * @length: + * @etag_out: a pointer to the current entity tag for the document. + * @error: a #GError. + * + * Returns: %TRUE if the @file's contents were successfully loaded. + * %FALSE if there were errors. The length of the loaded data will be + * put into @length, the contents in @contents. + **/ +gboolean +g_file_load_contents (GFile *file, + GCancellable *cancellable, + char **contents, + gsize *length, + char **etag_out, + GError **error) +{ + GFileInputStream *in; + GByteArray *content; + gsize pos; + gssize res; + GFileInfo *info; + + g_return_val_if_fail (G_IS_FILE (file), FALSE); + g_return_val_if_fail (contents != NULL, FALSE); + + in = g_file_read (file, + cancellable, + error); + if (in == NULL) + return FALSE; + + content = g_byte_array_new (); + pos = 0; + + g_byte_array_set_size (content, pos + GET_CONTENT_BLOCK_SIZE + 1); + while ((res = g_input_stream_read (G_INPUT_STREAM (in), + content->data + pos, + GET_CONTENT_BLOCK_SIZE, + cancellable, error)) > 0) + { + pos += res; + g_byte_array_set_size (content, pos + GET_CONTENT_BLOCK_SIZE + 1); + } + + if (etag_out) + { + *etag_out = NULL; + + info = g_file_input_stream_query_info (in, + G_FILE_ATTRIBUTE_ETAG_VALUE, + cancellable, + NULL); + if (info) + { + *etag_out = g_strdup (g_file_info_get_etag (info)); + g_object_unref (info); + } + } + + /* Ignore errors on close */ + g_input_stream_close (G_INPUT_STREAM (in), cancellable, NULL); + g_object_unref (in); + + if (res < 0) + { + /* error is set already */ + g_byte_array_free (content, TRUE); + return FALSE; + } + + if (length) + *length = pos; + + /* Zero terminate (we got an extra byte allocated for this */ + content->data[pos] = 0; + + *contents = (char *)g_byte_array_free (content, FALSE); + + return TRUE; +} + +typedef struct { + GFile *file; + GError *error; + GCancellable *cancellable; + GFileReadMoreCallback read_more_callback; + GAsyncReadyCallback callback; + gpointer user_data; + GByteArray *content; + gsize pos; + char *etag; +} LoadContentsData; + + +static void +load_contents_data_free (LoadContentsData *data) +{ + if (data->error) + g_error_free (data->error); + if (data->cancellable) + g_object_unref (data->cancellable); + if (data->content) + g_byte_array_free (data->content, TRUE); + g_free (data->etag); + g_object_unref (data->file); + g_free (data); +} + +static void +load_contents_close_callback (GObject *obj, + GAsyncResult *close_res, + gpointer user_data) +{ + GInputStream *stream = G_INPUT_STREAM (obj); + LoadContentsData *data = user_data; + GSimpleAsyncResult *res; + + /* Ignore errors here, we're only reading anyway */ + g_input_stream_close_finish (stream, close_res, NULL); + g_object_unref (stream); + + res = g_simple_async_result_new (G_OBJECT (data->file), + data->callback, + data->user_data, + g_file_load_contents_async); + g_simple_async_result_set_op_res_gpointer (res, data, (GDestroyNotify)load_contents_data_free); + g_simple_async_result_complete (res); + g_object_unref (res); +} + +static void +load_contents_fstat_callback (GObject *obj, + GAsyncResult *stat_res, + gpointer user_data) +{ + GInputStream *stream = G_INPUT_STREAM (obj); + LoadContentsData *data = user_data; + GFileInfo *info; + + info = g_file_input_stream_query_info_finish (G_FILE_INPUT_STREAM (stream), + stat_res, NULL); + if (info) + { + data->etag = g_strdup (g_file_info_get_etag (info)); + g_object_unref (info); + } + + g_input_stream_close_async (stream, 0, + data->cancellable, + load_contents_close_callback, data); +} + +static void +load_contents_read_callback (GObject *obj, + GAsyncResult *read_res, + gpointer user_data) +{ + GInputStream *stream = G_INPUT_STREAM (obj); + LoadContentsData *data = user_data; + GError *error = NULL; + gssize read_size; + + read_size = g_input_stream_read_finish (stream, read_res, &error); + + if (read_size < 0) + { + /* Error or EOF, close the file */ + data->error = error; + g_input_stream_close_async (stream, 0, + data->cancellable, + load_contents_close_callback, data); + } + else if (read_size == 0) + { + g_file_input_stream_query_info_async (G_FILE_INPUT_STREAM (stream), + G_FILE_ATTRIBUTE_ETAG_VALUE, + 0, + data->cancellable, + load_contents_fstat_callback, + data); + } + else if (read_size > 0) + { + data->pos += read_size; + + g_byte_array_set_size (data->content, + data->pos + GET_CONTENT_BLOCK_SIZE); + + + if (data->read_more_callback && + !data->read_more_callback ((char *)data->content->data, data->pos, data->user_data)) + g_file_input_stream_query_info_async (G_FILE_INPUT_STREAM (stream), + G_FILE_ATTRIBUTE_ETAG_VALUE, + 0, + data->cancellable, + load_contents_fstat_callback, + data); + else + g_input_stream_read_async (stream, + data->content->data + data->pos, + GET_CONTENT_BLOCK_SIZE, + 0, + data->cancellable, + load_contents_read_callback, + data); + } +} + +static void +load_contents_open_callback (GObject *obj, + GAsyncResult *open_res, + gpointer user_data) +{ + GFile *file = G_FILE (obj); + GFileInputStream *stream; + LoadContentsData *data = user_data; + GError *error = NULL; + GSimpleAsyncResult *res; + + stream = g_file_read_finish (file, open_res, &error); + + if (stream) + { + g_byte_array_set_size (data->content, + data->pos + GET_CONTENT_BLOCK_SIZE); + g_input_stream_read_async (G_INPUT_STREAM (stream), + data->content->data + data->pos, + GET_CONTENT_BLOCK_SIZE, + 0, + data->cancellable, + load_contents_read_callback, + data); + + } + else + { + res = g_simple_async_result_new_from_error (G_OBJECT (data->file), + data->callback, + data->user_data, + error); + g_simple_async_result_complete (res); + g_error_free (error); + load_contents_data_free (data); + g_object_unref (res); + } +} + +/** + * g_file_load_partial_contents_async: + * @file: input #GFile. + * @cancellable: optional #GCancellable object, %NULL to ignore. + * @read_more_callback: a #GFileReadMoreCallback. + * @callback: a #GAsyncReadyCallback. + * @user_data: a #gpointer. + * + * If @cancellable is not %NULL, then the operation can be cancelled by + * triggering the cancellable object from another thread. If the operation + * was cancelled, the error %G_IO_ERROR_CANCELLED will be returned. If an + * operation was partially finished when the operation was cancelled the + * partial result will be returned, without an error. + * + **/ +void +g_file_load_partial_contents_async (GFile *file, + GCancellable *cancellable, + GFileReadMoreCallback read_more_callback, + GAsyncReadyCallback callback, + gpointer user_data) +{ + LoadContentsData *data; + + g_return_if_fail (G_IS_FILE (file)); + + data = g_new0 (LoadContentsData, 1); + + if (cancellable) + data->cancellable = g_object_ref (cancellable); + data->read_more_callback = read_more_callback; + data->callback = callback; + data->user_data = user_data; + data->content = g_byte_array_new (); + data->file = g_object_ref (file); + + g_file_read_async (file, + 0, + cancellable, + load_contents_open_callback, + data); +} + +/** + * g_file_load_partial_contents_finish: + * @file: input #GFile. + * @error: a #GAsyncResult. + * @contents: a pointer to where the contents of the file will be placed. + * @length: a pointer to the location where the content's length will be placed. + * @etag_out: a pointer to the current entity tag for the document. + * @error: a #GError. + * + * Returns: %TRUE if the load was successful. If %FALSE and @error is + * present, it will be set appropriately. + **/ +gboolean +g_file_load_partial_contents_finish (GFile *file, + GAsyncResult *res, + char **contents, + gsize *length, + char **etag_out, + GError **error) +{ + GSimpleAsyncResult *simple; + LoadContentsData *data; + + g_return_val_if_fail (G_IS_FILE (file), FALSE); + g_return_val_if_fail (G_IS_SIMPLE_ASYNC_RESULT (res), FALSE); + g_return_val_if_fail (contents != NULL, FALSE); + + simple = G_SIMPLE_ASYNC_RESULT (res); + + if (g_simple_async_result_propagate_error (simple, error)) + return FALSE; + + g_assert (g_simple_async_result_get_source_tag (simple) == g_file_load_contents_async); + + data = g_simple_async_result_get_op_res_gpointer (simple); + + if (data->error) + { + g_propagate_error (error, data->error); + data->error = NULL; + *contents = NULL; + if (length) + *length = 0; + return FALSE; + } + + if (length) + *length = data->pos; + + if (etag_out) + { + *etag_out = data->etag; + data->etag = NULL; + } + + /* Zero terminate */ + g_byte_array_set_size (data->content, + data->pos + 1); + data->content->data[data->pos] = 0; + + *contents = (char *)g_byte_array_free (data->content, FALSE); + data->content = NULL; + + return TRUE; +} + +/** + * g_file_load_contents_async: + * @file: input #GFile. + * @cancellable: optional #GCancellable object, %NULL to ignore. + * @callback: a #GAsyncReadyCallback. + * @user_data: a #gpointer. + * + * Starts an asynchronous load of the @file's contents. When the load operation + * has completed, @callback will be called with @userdata. To finish + * the operation, call g_file_load_contents_finish() with the + * #GAsyncResult returned by the @callback. + * + * If @cancellable is not %NULL, then the operation can be cancelled by + * triggering the cancellable object from another thread. If the operation + * was cancelled, the error %G_IO_ERROR_CANCELLED will be returned. If an + * operation was partially finished when the operation was cancelled the + * partial result will be returned, without an error. + * + **/ +void +g_file_load_contents_async (GFile *file, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_file_load_partial_contents_async (file, + cancellable, + NULL, + callback, user_data); + +} + +/** + * g_file_load_contents_finish: + * @file: input #GFile. + * @error: a #GAsyncResult. + * @contents: an array of strings. + * @length: a pointer to a #gsize. + * @etag_out: a pointer to a string to get the new entity tag. + * @error: a #GError. + * + * Finishes an asynchronous load of the @file's contents. The contents + * are placed in @contents, and @length is set to the size of the @contents + * string. If @etag_out is present, it will be set to the new entity + * tag for the @file. + * + * Returns: %TRUE if the load was successful. If %FALSE and @error is + * present, it will be set appropriately. + **/ +gboolean +g_file_load_contents_finish (GFile *file, + GAsyncResult *res, + char **contents, + gsize *length, + char **etag_out, + GError **error) +{ + return g_file_load_partial_contents_finish (file, + res, + contents, + length, + etag_out, + error); + +} + +/** + * g_file_replace_contents: + * @file: input #GFile. + * @length: a #gsize. + * @etag: the old entity tag for the document. + * @make_backup: a #gboolean. + * @flags: a set of #GFileCreateFlags. + * @new_etag: a new entity tag for the document. + * @cancellable: optional #GCancellable object, %NULL to ignore. + * @error: a #GError. + * + * Replaces the contents of @file with @contents of @length. The old + * @etag will be replaced with the @new_etag. If @make_backup is %TRUE, + * this function will attempt to make a backup of @file. + * + * If @cancellable is not %NULL, then the operation can be cancelled by + * triggering the cancellable object from another thread. If the operation + * was cancelled, the error %G_IO_ERROR_CANCELLED will be returned. If an + * operation was partially finished when the operation was cancelled the + * partial result will be returned, without an error. + * + * Returns: %TRUE if successful. If an error + * has occured, this function will return %FALSE and set @error + * appropriately if present. + **/ +gboolean +g_file_replace_contents (GFile *file, + const char *contents, + gsize length, + const char *etag, + gboolean make_backup, + GFileCreateFlags flags, + char **new_etag, + GCancellable *cancellable, + GError **error) +{ + GFileOutputStream *out; + gsize pos, remainder; + gssize res; + + g_return_val_if_fail (G_IS_FILE (file), FALSE); + g_return_val_if_fail (contents != NULL, FALSE); + + out = g_file_replace (file, + etag, + make_backup, + flags, + cancellable, + error); + if (out == NULL) + return FALSE; + + pos = 0; + remainder = length; + while (remainder > 0 && + (res = g_output_stream_write (G_OUTPUT_STREAM (out), + contents + pos, + MIN (remainder, GET_CONTENT_BLOCK_SIZE), + cancellable, + error)) > 0) + { + pos += res; + remainder -= res; + } + + if (remainder > 0 && res < 0) + { + /* Ignore errors on close */ + g_output_stream_close (G_OUTPUT_STREAM (out), cancellable, NULL); + + /* error is set already */ + return FALSE; + } + + if (!g_output_stream_close (G_OUTPUT_STREAM (out), cancellable, error)) + return FALSE; + + if (new_etag) + *new_etag = g_file_output_stream_get_etag (out); + + return TRUE; +} + +typedef struct { + GFile *file; + GError *error; + GCancellable *cancellable; + GAsyncReadyCallback callback; + gpointer user_data; + const char *content; + gsize length; + gsize pos; + char *etag; +} ReplaceContentsData; + +static void +replace_contents_data_free (ReplaceContentsData *data) +{ + if (data->error) + g_error_free (data->error); + if (data->cancellable) + g_object_unref (data->cancellable); + g_object_unref (data->file); + g_free (data->etag); + g_free (data); +} + +static void +replace_contents_close_callback (GObject *obj, + GAsyncResult *close_res, + gpointer user_data) +{ + GOutputStream *stream = G_OUTPUT_STREAM (obj); + ReplaceContentsData *data = user_data; + GSimpleAsyncResult *res; + + /* Ignore errors here, we're only reading anyway */ + g_output_stream_close_finish (stream, close_res, NULL); + g_object_unref (stream); + + data->etag = g_file_output_stream_get_etag (G_FILE_OUTPUT_STREAM (stream)); + + res = g_simple_async_result_new (G_OBJECT (data->file), + data->callback, + data->user_data, + g_file_replace_contents_async); + g_simple_async_result_set_op_res_gpointer (res, data, (GDestroyNotify)replace_contents_data_free); + g_simple_async_result_complete (res); + g_object_unref (res); +} + +static void +replace_contents_write_callback (GObject *obj, + GAsyncResult *read_res, + gpointer user_data) +{ + GOutputStream *stream = G_OUTPUT_STREAM (obj); + ReplaceContentsData *data = user_data; + GError *error = NULL; + gssize write_size; + + write_size = g_output_stream_write_finish (stream, read_res, &error); + + if (write_size <= 0) + { + /* Error or EOF, close the file */ + if (write_size < 0) + data->error = error; + g_output_stream_close_async (stream, 0, + data->cancellable, + replace_contents_close_callback, data); + } + else if (write_size > 0) + { + data->pos += write_size; + + if (data->pos >= data->length) + g_output_stream_close_async (stream, 0, + data->cancellable, + replace_contents_close_callback, data); + else + g_output_stream_write_async (stream, + data->content + data->pos, + data->length - data->pos, + 0, + data->cancellable, + replace_contents_write_callback, + data); + } +} + +static void +replace_contents_open_callback (GObject *obj, + GAsyncResult *open_res, + gpointer user_data) +{ + GFile *file = G_FILE (obj); + GFileOutputStream *stream; + ReplaceContentsData *data = user_data; + GError *error = NULL; + GSimpleAsyncResult *res; + + stream = g_file_replace_finish (file, open_res, &error); + + if (stream) + { + g_output_stream_write_async (G_OUTPUT_STREAM (stream), + data->content + data->pos, + data->length - data->pos, + 0, + data->cancellable, + replace_contents_write_callback, + data); + + } + else + { + res = g_simple_async_result_new_from_error (G_OBJECT (data->file), + data->callback, + data->user_data, + error); + g_simple_async_result_complete (res); + g_error_free (error); + replace_contents_data_free (data); + g_object_unref (res); + } +} + +/** + * g_file_replace_contents_async: + * @file: input #GFile. + * @contents: string of contents to replace the file with. + * @length: length of the @contents string. + * @etag: a new entity tag for the @file. + * @make_backup: a #gboolean. + * @flags: a set of #GFileCreateFlags. + * @cancellable: optional #GCancellable object, %NULL to ignore. + * @callback: a #GAsyncReadyCallback. + * @user_data: a #gpointer. + * + * Starts an asynchronous replacement of @file with the given + * @contents of @length bytes. @etag will replace the document's + * current entity tag. + * + * When this operation has completed, @callback will be called with + * @user_user data, and the operation can be finalized with + * g_file_replace_contents_finish(). + * + * If @cancellable is not %NULL, then the operation can be cancelled by + * triggering the cancellable object from another thread. If the operation + * was cancelled, the error %G_IO_ERROR_CANCELLED will be returned. If an + * operation was partially finished when the operation was cancelled the + * partial result will be returned, without an error. + * + * If @make_backup is %TRUE, this function will attempt to + * make a backup of @file. + * + **/ +void +g_file_replace_contents_async (GFile *file, + const char *contents, + gsize length, + const char *etag, + gboolean make_backup, + GFileCreateFlags flags, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + ReplaceContentsData *data; + + g_return_if_fail (G_IS_FILE (file)); + g_return_if_fail (contents != NULL); + + data = g_new0 (ReplaceContentsData, 1); + + if (cancellable) + data->cancellable = g_object_ref (cancellable); + data->callback = callback; + data->user_data = user_data; + data->content = contents; + data->length = length; + data->pos = 0; + data->file = g_object_ref (file); + + g_file_replace_async (file, + etag, + make_backup, + flags, + 0, + cancellable, + replace_contents_open_callback, + data); +} + +/** + * g_file_replace_contents_finish: + * @file: input #GFile. + * @error: a #GAsyncResult. + * @new_etag: a pointer to the new entity tag string for the contents of the file. + * @error: a #GError. + * + * Finishes an asynchronous replace of the given @file. + * This function will take ownership of the @new_etag, if present. + * + * Returns: %TRUE on success, %FALSE on failure. + **/ +gboolean +g_file_replace_contents_finish (GFile *file, + GAsyncResult *res, + char **new_etag, + GError **error) +{ + GSimpleAsyncResult *simple; + ReplaceContentsData *data; + + g_return_val_if_fail (G_IS_FILE (file), FALSE); + g_return_val_if_fail (G_IS_SIMPLE_ASYNC_RESULT (res), FALSE); + + simple = G_SIMPLE_ASYNC_RESULT (res); + + if (g_simple_async_result_propagate_error (simple, error)) + return FALSE; + + g_assert (g_simple_async_result_get_source_tag (simple) == g_file_replace_contents_async); + + data = g_simple_async_result_get_op_res_gpointer (simple); + + if (data->error) + { + g_propagate_error (error, data->error); + data->error = NULL; + return FALSE; + } + + + if (new_etag) + { + *new_etag = data->etag; + data->etag = NULL; /* Take ownership */ + } + + return TRUE; +} diff --git a/gio/gfile.h b/gio/gfile.h new file mode 100644 index 000000000..ac3f02f14 --- /dev/null +++ b/gio/gfile.h @@ -0,0 +1,676 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#ifndef __G_FILE_H__ +#define __G_FILE_H__ + +#include <glib-object.h> +#include <gio/gfileinfo.h> +#include <gio/gfileenumerator.h> +#include <gio/gfileinputstream.h> +#include <gio/gfileoutputstream.h> +#include <gio/gmountoperation.h> + +G_BEGIN_DECLS + +#define G_TYPE_FILE (g_file_get_type ()) +#define G_FILE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), G_TYPE_FILE, GFile)) +#define G_IS_FILE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), G_TYPE_FILE)) +#define G_FILE_GET_IFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), G_TYPE_FILE, GFileIface)) + +typedef enum { + G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS = (1<<0) +} GFileQueryInfoFlags; + +typedef enum { + G_FILE_CREATE_FLAGS_NONE = 0, + G_FILE_CREATE_FLAGS_PRIVATE = (1<<0) +} GFileCreateFlags; + +typedef enum { + G_FILE_COPY_OVERWRITE = (1<<0), + G_FILE_COPY_BACKUP = (1<<1), + G_FILE_COPY_NOFOLLOW_SYMLINKS = (1<<2), + G_FILE_COPY_ALL_METADATA = (1<<3) +} GFileCopyFlags; + +typedef enum { + G_FILE_MONITOR_FLAGS_NONE = 0, + G_FILE_MONITOR_FLAGS_MONITOR_MOUNTS = (1<<0) +} GFileMonitorFlags; + +typedef struct _GFile GFile; /* Dummy typedef */ +typedef struct _GFileIface GFileIface; +typedef struct _GDirectoryMonitor GDirectoryMonitor; +typedef struct _GFileMonitor GFileMonitor; +typedef struct _GVolume GVolume; /* Dummy typedef */ + +typedef void (*GFileProgressCallback) (goffset current_num_bytes, + goffset total_num_bytes, + gpointer user_data); +typedef gboolean (* GFileReadMoreCallback) (const char *file_contents, + goffset file_size, + gpointer callback_data); + + +struct _GFileIface +{ + GTypeInterface g_iface; + + /* Virtual Table */ + + GFile * (*dup) (GFile *file); + guint (*hash) (GFile *file); + gboolean (*equal) (GFile *file1, + GFile *file2); + gboolean (*is_native) (GFile *file); + gboolean (*has_uri_scheme) (GFile *file, + const char *uri_scheme); + char * (*get_uri_scheme) (GFile *file); + char * (*get_basename) (GFile *file); + char * (*get_path) (GFile *file); + char * (*get_uri) (GFile *file); + char * (*get_parse_name) (GFile *file); + GFile * (*get_parent) (GFile *file); + gboolean (*contains_file) (GFile *parent, + GFile *descendant); + char * (*get_relative_path) (GFile *parent, + GFile *descendant); + GFile * (*resolve_relative_path) (GFile *file, + const char *relative_path); + GFile * (*get_child_for_display_name) (GFile *file, + const char *display_name, + GError **error); + + GFileEnumerator * (*enumerate_children) (GFile *file, + const char *attributes, + GFileQueryInfoFlags flags, + GCancellable *cancellable, + GError **error); + void (*enumerate_children_async) (GFile *file, + const char *attributes, + GFileQueryInfoFlags flags, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + GFileEnumerator * (*enumerate_children_finish) (GFile *file, + GAsyncResult *res, + GError **error); + + GFileInfo * (*query_info) (GFile *file, + const char *attributes, + GFileQueryInfoFlags flags, + GCancellable *cancellable, + GError **error); + void (*query_info_async) (GFile *file, + const char *attributes, + GFileQueryInfoFlags flags, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + GFileInfo * (*query_info_finish) (GFile *file, + GAsyncResult *res, + GError **error); + + GFileInfo * (*query_filesystem_info)(GFile *file, + const char *attributes, + GCancellable *cancellable, + GError **error); + void (*_query_filesystem_info_async) (void); + void (*_query_filesystem_info_finish) (void); + + GVolume * (*find_enclosing_volume)(GFile *file, + GCancellable *cancellable, + GError **error); + void (*find_enclosing_volume_async)(GFile *file, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + GVolume * (*find_enclosing_volume_finish)(GFile *file, + GAsyncResult *res, + GError **error); + + GFile * (*set_display_name) (GFile *file, + const char *display_name, + GCancellable *cancellable, + GError **error); + void (*set_display_name_async) (GFile *file, + const char *display_name, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + GFile * (*set_display_name_finish) (GFile *file, + GAsyncResult *res, + GError **error); + + GFileAttributeInfoList * (*query_settable_attributes) (GFile *file, + GCancellable *cancellable, + GError **error); + void (*_query_settable_attributes_async) (void); + void (*_query_settable_attributes_finish) (void); + + GFileAttributeInfoList * (*query_writable_namespaces) (GFile *file, + GCancellable *cancellable, + GError **error); + void (*_query_writable_namespaces_async) (void); + void (*_query_writable_namespaces_finish) (void); + + gboolean (*set_attribute) (GFile *file, + const char *attribute, + const GFileAttributeValue *value, + GFileQueryInfoFlags flags, + GCancellable *cancellable, + GError **error); + gboolean (*set_attributes_from_info) (GFile *file, + GFileInfo *info, + GFileQueryInfoFlags flags, + GCancellable *cancellable, + GError **error); + void (*set_attributes_async) (GFile *file, + GFileInfo *info, + GFileQueryInfoFlags flags, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + gboolean (*set_attributes_finish) (GFile *file, + GAsyncResult *result, + GFileInfo **info, + GError **error); + + GFileInputStream * (*read) (GFile *file, + GCancellable *cancellable, + GError **error); + void (*read_async) (GFile *file, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + GFileInputStream * (*read_finish) (GFile *file, + GAsyncResult *res, + GError **error); + + GFileOutputStream * (*append_to) (GFile *file, + GFileCreateFlags flags, + GCancellable *cancellable, + GError **error); + void (*append_to_async) (GFile *file, + GFileCreateFlags flags, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + GFileOutputStream * (*append_to_finish) (GFile *file, + GAsyncResult *res, + GError **error); + + GFileOutputStream * (*create) (GFile *file, + GFileCreateFlags flags, + GCancellable *cancellable, + GError **error); + void (*create_async) (GFile *file, + GFileCreateFlags flags, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + GFileOutputStream * (*create_finish) (GFile *file, + GAsyncResult *res, + GError **error); + + GFileOutputStream * (*replace) (GFile *file, + const char *etag, + gboolean make_backup, + GFileCreateFlags flags, + GCancellable *cancellable, + GError **error); + void (*replace_async) (GFile *file, + const char *etag, + gboolean make_backup, + GFileCreateFlags flags, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + GFileOutputStream * (*replace_finish) (GFile *file, + GAsyncResult *res, + GError **error); + + gboolean (*delete_file) (GFile *file, + GCancellable *cancellable, + GError **error); + void (*_delete_file_async) (void); + void (*_delete_file_finish) (void); + + gboolean (*trash) (GFile *file, + GCancellable *cancellable, + GError **error); + void (*_trash_async) (void); + void (*_trash_finish) (void); + + gboolean (*make_directory) (GFile *file, + GCancellable *cancellable, + GError **error); + void (*_make_directory_async) (void); + void (*_make_directory_finish) (void); + + gboolean (*make_symbolic_link) (GFile *file, + const char *symlink_value, + GCancellable *cancellable, + GError **error); + void (*_make_symbolic_link_async) (void); + void (*_make_symbolic_link_finish) (void); + + gboolean (*copy) (GFile *source, + GFile *destination, + GFileCopyFlags flags, + GCancellable *cancellable, + GFileProgressCallback progress_callback, + gpointer progress_callback_data, + GError **error); + void (*_copy_async) (void); + void (*_copy_finish) (void); + + gboolean (*move) (GFile *source, + GFile *destination, + GFileCopyFlags flags, + GCancellable *cancellable, + GFileProgressCallback progress_callback, + gpointer progress_callback_data, + GError **error); + + void (*_move_async) (void); + void (*_move_finish) (void); + + + void (*mount_mountable) (GFile *file, + GMountOperation *mount_operation, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + GFile * (*mount_mountable_finish) (GFile *file, + GAsyncResult *result, + GError **error); + void (*unmount_mountable) (GFile *file, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + gboolean (*unmount_mountable_finish) (GFile *file, + GAsyncResult *result, + GError **error); + void (*eject_mountable) (GFile *file, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + gboolean (*eject_mountable_finish) (GFile *file, + GAsyncResult *result, + GError **error); + + + void (*mount_for_location) (GFile *location, + GMountOperation *mount_operation, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + gboolean (*mount_for_location_finish) (GFile *location, + GAsyncResult *result, + GError **error); + + GDirectoryMonitor* (*monitor_dir) (GFile *file, + GFileMonitorFlags flags, + GCancellable *cancellable); + + GFileMonitor* (*monitor_file) (GFile *file, + GFileMonitorFlags flags, + GCancellable *cancellable); +}; + +GType g_file_get_type (void) G_GNUC_CONST; + +GFile * g_file_new_for_path (const char *path); +GFile * g_file_new_for_uri (const char *uri); +GFile * g_file_new_for_commandline_arg (const char *arg); +GFile * g_file_parse_name (const char *parse_name); +GFile * g_file_dup (GFile *file); +guint g_file_hash (gconstpointer file); +gboolean g_file_equal (GFile *file1, + GFile *file2); +char * g_file_get_basename (GFile *file); +char * g_file_get_path (GFile *file); +char * g_file_get_uri (GFile *file); +char * g_file_get_parse_name (GFile *file); +GFile * g_file_get_parent (GFile *file); +GFile * g_file_get_child (GFile *file, + const char *name); +GFile * g_file_get_child_for_display_name (GFile *file, + const char *display_name, + GError **error); +gboolean g_file_contains_file (GFile *parent, + GFile *descendant); +char * g_file_get_relative_path (GFile *parent, + GFile *descendant); +GFile * g_file_resolve_relative_path (GFile *file, + const char *relative_path); +gboolean g_file_is_native (GFile *file); +gboolean g_file_has_uri_scheme (GFile *file, + const char *uri_scheme); +char * g_file_get_uri_scheme (GFile *file); +GFileInputStream * g_file_read (GFile *file, + GCancellable *cancellable, + GError **error); +void g_file_read_async (GFile *file, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +GFileInputStream * g_file_read_finish (GFile *file, + GAsyncResult *res, + GError **error); +GFileOutputStream * g_file_append_to (GFile *file, + GFileCreateFlags flags, + GCancellable *cancellable, + GError **error); +GFileOutputStream * g_file_create (GFile *file, + GFileCreateFlags flags, + GCancellable *cancellable, + GError **error); +GFileOutputStream * g_file_replace (GFile *file, + const char *etag, + gboolean make_backup, + GFileCreateFlags flags, + GCancellable *cancellable, + GError **error); +void g_file_append_to_async (GFile *file, + GFileCreateFlags flags, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +GFileOutputStream * g_file_append_to_finish (GFile *file, + GAsyncResult *res, + GError **error); +void g_file_create_async (GFile *file, + GFileCreateFlags flags, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +GFileOutputStream * g_file_create_finish (GFile *file, + GAsyncResult *res, + GError **error); +void g_file_replace_async (GFile *file, + const char *etag, + gboolean make_backup, + GFileCreateFlags flags, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +GFileOutputStream * g_file_replace_finish (GFile *file, + GAsyncResult *res, + GError **error); +GFileInfo * g_file_query_info (GFile *file, + const char *attributes, + GFileQueryInfoFlags flags, + GCancellable *cancellable, + GError **error); +void g_file_query_info_async (GFile *file, + const char *attributes, + GFileQueryInfoFlags flags, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +GFileInfo * g_file_query_info_finish (GFile *file, + GAsyncResult *res, + GError **error); +GFileInfo * g_file_query_filesystem_info (GFile *file, + const char *attributes, + GCancellable *cancellable, + GError **error); +GVolume * g_file_find_enclosing_volume (GFile *file, + GCancellable *cancellable, + GError **error); +GFileEnumerator * g_file_enumerate_children (GFile *file, + const char *attributes, + GFileQueryInfoFlags flags, + GCancellable *cancellable, + GError **error); +void g_file_enumerate_children_async (GFile *file, + const char *attributes, + GFileQueryInfoFlags flags, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +GFileEnumerator * g_file_enumerate_children_finish (GFile *file, + GAsyncResult *res, + GError **error); +GFile * g_file_set_display_name (GFile *file, + const char *display_name, + GCancellable *cancellable, + GError **error); +void g_file_set_display_name_async (GFile *file, + const char *display_name, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +GFile * g_file_set_display_name_finish (GFile *file, + GAsyncResult *res, + GError **error); +gboolean g_file_delete (GFile *file, + GCancellable *cancellable, + GError **error); +gboolean g_file_trash (GFile *file, + GCancellable *cancellable, + GError **error); +gboolean g_file_copy (GFile *source, + GFile *destination, + GFileCopyFlags flags, + GCancellable *cancellable, + GFileProgressCallback progress_callback, + gpointer progress_callback_data, + GError **error); +gboolean g_file_move (GFile *source, + GFile *destination, + GFileCopyFlags flags, + GCancellable *cancellable, + GFileProgressCallback progress_callback, + gpointer progress_callback_data, + GError **error); +gboolean g_file_make_directory (GFile *file, + GCancellable *cancellable, + GError **error); +gboolean g_file_make_symbolic_link (GFile *file, + const char *symlink_value, + GCancellable *cancellable, + GError **error); +GFileAttributeInfoList *g_file_query_settable_attributes (GFile *file, + GCancellable *cancellable, + GError **error); +GFileAttributeInfoList *g_file_query_writable_namespaces (GFile *file, + GCancellable *cancellable, + GError **error); +gboolean g_file_set_attribute (GFile *file, + const char *attribute, + const GFileAttributeValue *value, + GFileQueryInfoFlags flags, + GCancellable *cancellable, + GError **error); +gboolean g_file_set_attributes_from_info (GFile *file, + GFileInfo *info, + GFileQueryInfoFlags flags, + GCancellable *cancellable, + GError **error); +void g_file_set_attributes_async (GFile *file, + GFileInfo *info, + GFileQueryInfoFlags flags, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean g_file_set_attributes_finish (GFile *file, + GAsyncResult *result, + GFileInfo **info, + GError **error); +gboolean g_file_set_attribute_string (GFile *file, + const char *attribute, + const char *value, + GFileQueryInfoFlags flags, + GCancellable *cancellable, + GError **error); +gboolean g_file_set_attribute_byte_string (GFile *file, + const char *attribute, + const char *value, + GFileQueryInfoFlags flags, + GCancellable *cancellable, + GError **error); +gboolean g_file_set_attribute_uint32 (GFile *file, + const char *attribute, + guint32 value, + GFileQueryInfoFlags flags, + GCancellable *cancellable, + GError **error); +gboolean g_file_set_attribute_int32 (GFile *file, + const char *attribute, + gint32 value, + GFileQueryInfoFlags flags, + GCancellable *cancellable, + GError **error); +gboolean g_file_set_attribute_uint64 (GFile *file, + const char *attribute, + guint64 value, + GFileQueryInfoFlags flags, + GCancellable *cancellable, + GError **error); +gboolean g_file_set_attribute_int64 (GFile *file, + const char *attribute, + gint64 value, + GFileQueryInfoFlags flags, + GCancellable *cancellable, + GError **error); +void g_mount_for_location (GFile *location, + GMountOperation *mount_operation, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean g_mount_for_location_finish (GFile *location, + GAsyncResult *result, + GError **error); +void g_file_mount_mountable (GFile *file, + GMountOperation *mount_operation, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +GFile * g_file_mount_mountable_finish (GFile *file, + GAsyncResult *result, + GError **error); +void g_file_unmount_mountable (GFile *file, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean g_file_unmount_mountable_finish (GFile *file, + GAsyncResult *result, + GError **error); +void g_file_eject_mountable (GFile *file, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean g_file_eject_mountable_finish (GFile *file, + GAsyncResult *result, + GError **error); + +gboolean g_file_copy_attributes (GFile *source, + GFile *destination, + GFileCopyFlags flags, + GCancellable *cancellable, + GError **error); + + +GDirectoryMonitor* g_file_monitor_directory (GFile *file, + GFileMonitorFlags flags, + GCancellable *cancellable); +GFileMonitor* g_file_monitor_file (GFile *file, + GFileMonitorFlags flags, + GCancellable *cancellable); + + +/* Utilities */ + +gboolean g_file_load_contents (GFile *file, + GCancellable *cancellable, + char **contents, + gsize *length, + char **etag_out, + GError **error); +void g_file_load_contents_async (GFile *file, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean g_file_load_contents_finish (GFile *file, + GAsyncResult *res, + char **contents, + gsize *length, + char **etag_out, + GError **error); +void g_file_load_partial_contents_async (GFile *file, + GCancellable *cancellable, + GFileReadMoreCallback read_more_callback, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean g_file_load_partial_contents_finish (GFile *file, + GAsyncResult *res, + char **contents, + gsize *length, + char **etag_out, + GError **error); +gboolean g_file_replace_contents (GFile *file, + const char *contents, + gsize length, + const char *etag, + gboolean make_backup, + GFileCreateFlags flags, + char **new_etag, + GCancellable *cancellable, + GError **error); +void g_file_replace_contents_async (GFile *file, + const char *contents, + gsize length, + const char *etag, + gboolean make_backup, + GFileCreateFlags flags, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean g_file_replace_contents_finish (GFile *file, + GAsyncResult *res, + char **new_etag, + GError **error); + +G_END_DECLS + +#endif /* __G_FILE_H__ */ diff --git a/gio/gfileattribute.c b/gio/gfileattribute.c new file mode 100644 index 000000000..512500b3c --- /dev/null +++ b/gio/gfileattribute.c @@ -0,0 +1,704 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#include <config.h> + +#include <string.h> + +#include "gfileattribute.h" +#include <glib-object.h> +#include "glibintl.h" + +/** + * g_file_attribute_value_free: + * @attr: a #GFileAttributeValue. + * + * Frees the memory used by @attr. + * + **/ +void +g_file_attribute_value_free (GFileAttributeValue *attr) +{ + g_return_if_fail (attr != NULL); + + g_file_attribute_value_clear (attr); + g_free (attr); +} + +/** + * g_file_attribute_value_clear: + * @attr: a #GFileAttributeValue. + * + * Clears the value of @attr and sets its type to + * %G_FILE_ATTRIBUTE_TYPE_INVALID. + * + **/ +void +g_file_attribute_value_clear (GFileAttributeValue *attr) +{ + g_return_if_fail (attr != NULL); + + if (attr->type == G_FILE_ATTRIBUTE_TYPE_STRING || + attr->type == G_FILE_ATTRIBUTE_TYPE_BYTE_STRING) + g_free (attr->u.string); + + if (attr->type == G_FILE_ATTRIBUTE_TYPE_OBJECT && + attr->u.obj != NULL) + g_object_unref (attr->u.obj); + + attr->type = G_FILE_ATTRIBUTE_TYPE_INVALID; +} + +/** + * g_file_attribute_value_set: + * @attr: a #GFileAttributeValue. + * @new_value: + * + **/ +void +g_file_attribute_value_set (GFileAttributeValue *attr, + const GFileAttributeValue *new_value) +{ + g_return_if_fail (attr != NULL); + g_return_if_fail (new_value != NULL); + + g_file_attribute_value_clear (attr); + *attr = *new_value; + + if (attr->type == G_FILE_ATTRIBUTE_TYPE_STRING || + attr->type == G_FILE_ATTRIBUTE_TYPE_BYTE_STRING) + attr->u.string = g_strdup (attr->u.string); + + if (attr->type == G_FILE_ATTRIBUTE_TYPE_OBJECT && + attr->u.obj != NULL) + g_object_ref (attr->u.obj); +} + +/** + * g_file_attribute_value_new: + * + * Returns: a new #GFileAttributeValue. + **/ +GFileAttributeValue * +g_file_attribute_value_new (void) +{ + GFileAttributeValue *attr; + + attr = g_new (GFileAttributeValue, 1); + attr->type = G_FILE_ATTRIBUTE_TYPE_INVALID; + return attr; +} + + +/** + * g_file_attribute_value_dup: + * @other: a #GFileAttributeValue to duplicate. + * + * Returns: a duplicate of the @other. + **/ +GFileAttributeValue * +g_file_attribute_value_dup (const GFileAttributeValue *other) +{ + GFileAttributeValue *attr; + + g_return_val_if_fail (other != NULL, NULL); + + attr = g_new (GFileAttributeValue, 1); + attr->type = G_FILE_ATTRIBUTE_TYPE_INVALID; + g_file_attribute_value_set (attr, other); + return attr; +} + +static gboolean +valid_char (char c) +{ + return c >= 32 && c <= 126 && c != '\\'; +} + +static char * +escape_byte_string (const char *str) +{ + size_t len; + int num_invalid, i; + char *escaped_val, *p; + unsigned char c; + char *hex_digits = "0123456789abcdef"; + + len = strlen (str); + + num_invalid = 0; + for (i = 0; i < len; i++) + { + if (!valid_char (str[i])) + num_invalid++; + } + + if (num_invalid == 0) + return g_strdup (str); + else + { + escaped_val = g_malloc (len + num_invalid*3 + 1); + + p = escaped_val; + for (i = 0; i < len; i++) + { + c = str[i]; + if (valid_char (c)) + *p++ = c; + else + { + *p++ = '\\'; + *p++ = 'x'; + *p++ = hex_digits[(c >> 8) & 0xf]; + *p++ = hex_digits[c & 0xf]; + } + } + *p++ = 0; + return escaped_val; + } +} + +/** + * g_file_attribute_value_as_string: + * @attr: a #GFileAttributeValue. + * + * Converts a #GFileAttributeValue to a string for display. + * The returned string should be freed when no longer needed + * + * Returns: a string from the @attr, %NULL on error, or "<invalid>" if + * @attr is of type %G_FILE_ATTRIBUTE_TYPE_INVALID. + **/ +char * +g_file_attribute_value_as_string (const GFileAttributeValue *attr) +{ + char *str; + + g_return_val_if_fail (attr != NULL, NULL); + + switch (attr->type) + { + case G_FILE_ATTRIBUTE_TYPE_STRING: + str = g_strdup (attr->u.string); + break; + case G_FILE_ATTRIBUTE_TYPE_BYTE_STRING: + str = escape_byte_string (attr->u.string); + break; + case G_FILE_ATTRIBUTE_TYPE_BOOLEAN: + str = g_strdup_printf ("%s", attr->u.boolean?"TRUE":"FALSE"); + break; + case G_FILE_ATTRIBUTE_TYPE_UINT32: + str = g_strdup_printf ("%u", (unsigned int)attr->u.uint32); + break; + case G_FILE_ATTRIBUTE_TYPE_INT32: + str = g_strdup_printf ("%i", (int)attr->u.int32); + break; + case G_FILE_ATTRIBUTE_TYPE_UINT64: + str = g_strdup_printf ("%"G_GUINT64_FORMAT, attr->u.uint64); + break; + case G_FILE_ATTRIBUTE_TYPE_INT64: + str = g_strdup_printf ("%"G_GINT64_FORMAT, attr->u.int64); + break; + case G_FILE_ATTRIBUTE_TYPE_OBJECT: + str = g_strdup_printf ("%s:%p", g_type_name_from_instance + ((GTypeInstance *) attr->u.obj), + attr->u.obj); + break; + default: + g_warning ("Invalid type in GFileInfo attribute"); + str = g_strdup ("<invalid>"); + break; + } + + return str; +} + +/** + * g_file_attribute_value_get_string: + * @attr: a #GFileAttributeValue. + * + * Returns: + **/ +const char * +g_file_attribute_value_get_string (const GFileAttributeValue *attr) +{ + if (attr == NULL) + return NULL; + + g_return_val_if_fail (attr->type == G_FILE_ATTRIBUTE_TYPE_STRING, NULL); + + return attr->u.string; +} + +/** + * g_file_attribute_value_get_byte_string: + * @attr: a #GFileAttributeValue. + * + * Returns: + **/ +const char * +g_file_attribute_value_get_byte_string (const GFileAttributeValue *attr) +{ + if (attr == NULL) + return NULL; + + g_return_val_if_fail (attr->type == G_FILE_ATTRIBUTE_TYPE_BYTE_STRING, NULL); + + return attr->u.string; +} + +/** + * g_file_attribute_value_get_boolean: + * @attr: a #GFileAttributeValue. + * + * Returns: + **/ +gboolean +g_file_attribute_value_get_boolean (const GFileAttributeValue *attr) +{ + if (attr == NULL) + return FALSE; + + g_return_val_if_fail (attr->type == G_FILE_ATTRIBUTE_TYPE_BOOLEAN, FALSE); + + return attr->u.boolean; +} + +/** + * g_file_attribute_value_get_uint32: + * @attr: a #GFileAttributeValue. + * + * Returns: + **/ +guint32 +g_file_attribute_value_get_uint32 (const GFileAttributeValue *attr) +{ + if (attr == NULL) + return 0; + + g_return_val_if_fail (attr->type == G_FILE_ATTRIBUTE_TYPE_UINT32, 0); + + return attr->u.uint32; +} + +/** + * g_file_attribute_value_get_int32: + * @attr: a #GFileAttributeValue. + * + * Returns: + **/ +gint32 +g_file_attribute_value_get_int32 (const GFileAttributeValue *attr) +{ + if (attr == NULL) + return 0; + + g_return_val_if_fail (attr->type == G_FILE_ATTRIBUTE_TYPE_INT32, 0); + + return attr->u.int32; +} + +/** + * g_file_attribute_value_get_uint64: + * @attr: a #GFileAttributeValue. + * + * Returns: + **/ +guint64 +g_file_attribute_value_get_uint64 (const GFileAttributeValue *attr) +{ + if (attr == NULL) + return 0; + + g_return_val_if_fail (attr->type == G_FILE_ATTRIBUTE_TYPE_UINT64, 0); + + return attr->u.uint64; +} + +/** + * g_file_attribute_value_get_int64: + * @attr: a #GFileAttributeValue. + * + * Returns: + **/ +gint64 +g_file_attribute_value_get_int64 (const GFileAttributeValue *attr) +{ + if (attr == NULL) + return 0; + + g_return_val_if_fail (attr->type == G_FILE_ATTRIBUTE_TYPE_INT64, 0); + + return attr->u.int64; +} + +/** + * g_file_attribute_value_get_object: + * @attr: a #GFileAttributeValue. + * + * Returns: + **/ +GObject * +g_file_attribute_value_get_object (const GFileAttributeValue *attr) +{ + if (attr == NULL) + return NULL; + + g_return_val_if_fail (attr->type == G_FILE_ATTRIBUTE_TYPE_OBJECT, NULL); + + return attr->u.obj; +} + +/** + * g_file_attribute_value_set_string: + * @attr: a #GFileAttributeValue. + * @string: + * + **/ +void +g_file_attribute_value_set_string (GFileAttributeValue *attr, + const char *string) +{ + g_return_if_fail (attr != NULL); + g_return_if_fail (string != NULL); + + g_file_attribute_value_clear (attr); + attr->type = G_FILE_ATTRIBUTE_TYPE_STRING; + attr->u.string = g_strdup (string); +} + +/** + * g_file_attribute_value_set_byte_string: + * @attr: a #GFileAttributeValue. + * @string: + * + **/ +void +g_file_attribute_value_set_byte_string (GFileAttributeValue *attr, + const char *string) +{ + g_return_if_fail (attr != NULL); + g_return_if_fail (string != NULL); + + g_file_attribute_value_clear (attr); + attr->type = G_FILE_ATTRIBUTE_TYPE_BYTE_STRING; + attr->u.string = g_strdup (string); +} + +/** + * g_file_attribute_value_set_boolean: + * @attr: a #GFileAttributeValue. + * @value: + * + **/ +void +g_file_attribute_value_set_boolean (GFileAttributeValue *attr, + gboolean value) +{ + g_return_if_fail (attr != NULL); + + g_file_attribute_value_clear (attr); + attr->type = G_FILE_ATTRIBUTE_TYPE_BOOLEAN; + attr->u.boolean = !!value; +} + +/** + * g_file_attribute_value_set_uint32: + * @attr: a #GFileAttributeValue. + * @value: + * + **/ +void +g_file_attribute_value_set_uint32 (GFileAttributeValue *attr, + guint32 value) +{ + g_return_if_fail (attr != NULL); + + g_file_attribute_value_clear (attr); + attr->type = G_FILE_ATTRIBUTE_TYPE_UINT32; + attr->u.uint32 = value; +} + +/** + * g_file_attribute_value_set_int32: + * @attr: a #GFileAttributeValue. + * @value: + * + **/ +void +g_file_attribute_value_set_int32 (GFileAttributeValue *attr, + gint32 value) +{ + g_return_if_fail (attr != NULL); + + g_file_attribute_value_clear (attr); + attr->type = G_FILE_ATTRIBUTE_TYPE_INT32; + attr->u.int32 = value; +} + +/** + * g_file_attribute_value_set_uint64: + * @attr: a #GFileAttributeValue. + * @value: + * + **/ +void +g_file_attribute_value_set_uint64 (GFileAttributeValue *attr, + guint64 value) +{ + g_return_if_fail (attr != NULL); + + g_file_attribute_value_clear (attr); + attr->type = G_FILE_ATTRIBUTE_TYPE_UINT64; + attr->u.uint64 = value; +} + +/** + * g_file_attribute_value_set_int64: + * @attr: a #GFileAttributeValue. + * @value: a #gint64 to set the value to. + * + **/ +void +g_file_attribute_value_set_int64 (GFileAttributeValue *attr, + gint64 value) +{ + g_return_if_fail (attr != NULL); + + g_file_attribute_value_clear (attr); + attr->type = G_FILE_ATTRIBUTE_TYPE_INT64; + attr->u.int64 = value; +} + +/** + * g_file_attribute_value_set_object: + * @attr: a #GFileAttributeValue. + * @obj: a #GObject. + * + * Sets the file attribute @attr to contain the value @obj. + * The @attr references the object internally. + * + **/ +void +g_file_attribute_value_set_object (GFileAttributeValue *attr, + GObject *obj) +{ + g_return_if_fail (attr != NULL); + g_return_if_fail (obj != NULL); + + g_file_attribute_value_clear (attr); + attr->type = G_FILE_ATTRIBUTE_TYPE_OBJECT; + attr->u.obj = g_object_ref (obj); +} + +typedef struct { + GFileAttributeInfoList public; + GArray *array; + int ref_count; +} GFileAttributeInfoListPriv; + +static void +list_update_public (GFileAttributeInfoListPriv *priv) +{ + priv->public.infos = (GFileAttributeInfo *)priv->array->data; + priv->public.n_infos = priv->array->len; +} + +/** + * g_file_attribute_info_list_new: + * + * Returns a new #GFileAttributeInfoList. + **/ +GFileAttributeInfoList * +g_file_attribute_info_list_new (void) +{ + GFileAttributeInfoListPriv *priv; + + priv = g_new0 (GFileAttributeInfoListPriv, 1); + + priv->ref_count = 1; + priv->array = g_array_new (TRUE, FALSE, sizeof (GFileAttributeInfo)); + + list_update_public (priv); + + return (GFileAttributeInfoList *)priv; +} + +/** + * g_file_attribute_info_list_dup: + * @list: a #GFileAttributeInfoList to duplicate. + * + * Returns a duplicate of the given @list. + **/ +GFileAttributeInfoList * +g_file_attribute_info_list_dup (GFileAttributeInfoList *list) +{ + GFileAttributeInfoListPriv *new; + int i; + + g_return_val_if_fail (list != NULL, NULL); + + new = g_new0 (GFileAttributeInfoListPriv, 1); + new->ref_count = 1; + new->array = g_array_new (TRUE, FALSE, sizeof (GFileAttributeInfo)); + + g_array_set_size (new->array, list->n_infos); + list_update_public (new); + for (i = 0; i < list->n_infos; i++) + { + new->public.infos[i].name = g_strdup (list->infos[i].name); + new->public.infos[i].type = list->infos[i].type; + new->public.infos[i].flags = list->infos[i].flags; + } + + return (GFileAttributeInfoList *)new; +} + +/** + * g_file_attribute_info_list_ref: + * @list: a #GFileAttributeInfoList to reference. + * + * Returns: #GFileAttributeInfoList or %NULL on error. + **/ +GFileAttributeInfoList * +g_file_attribute_info_list_ref (GFileAttributeInfoList *list) +{ + GFileAttributeInfoListPriv *priv = (GFileAttributeInfoListPriv *)list; + + g_return_val_if_fail (list != NULL, NULL); + g_return_val_if_fail (priv->ref_count > 0, NULL); + + g_atomic_int_inc (&priv->ref_count); + + return list; +} + +/** + * g_file_attribute_info_list_unref: + * @list: The #GFileAttributeInfoList to unreference. + * + * Removes a reference from the given @list. If the reference count + * falls to zero, the @list is deleted. + **/ +void +g_file_attribute_info_list_unref (GFileAttributeInfoList *list) +{ + GFileAttributeInfoListPriv *priv = (GFileAttributeInfoListPriv *)list; + int i; + + g_return_if_fail (list != NULL); + g_return_if_fail (priv->ref_count > 0); + + if (g_atomic_int_dec_and_test (&priv->ref_count)) + { + for (i = 0; i < list->n_infos; i++) + g_free (list->infos[i].name); + g_array_free (priv->array, TRUE); + } +} + +static int +g_file_attribute_info_list_bsearch (GFileAttributeInfoList *list, + const char *name) +{ + int start, end, mid; + + start = 0; + end = list->n_infos; + + while (start != end) + { + mid = start + (end - start) / 2; + + if (strcmp (name, list->infos[mid].name) < 0) + end = mid; + else if (strcmp (name, list->infos[mid].name) > 0) + start = mid + 1; + else + return mid; + } + return start; +} + +/** + * g_file_attribute_info_list_lookup: + * @list: a #GFileAttributeInfoList. + * @name: the name of the attribute to lookup. + * + * Returns: a #GFileAttributeInfo for the @name, or %NULL if an + * attribute isn't found. + **/ +const GFileAttributeInfo * +g_file_attribute_info_list_lookup (GFileAttributeInfoList *list, + const char *name) +{ + int i; + + g_return_val_if_fail (list != NULL, NULL); + g_return_val_if_fail (name != NULL, NULL); + + i = g_file_attribute_info_list_bsearch (list, name); + + if (i < list->n_infos && strcmp (list->infos[i].name, name) == 0) + return &list->infos[i]; + + return NULL; +} + +/** + * g_file_attribute_info_list_add: + * @list: a #GFileAttributeInfoList. + * @name: the name of the attribute to add. + * @type: the #GFileAttributeType for the attribute. + * @flags: #GFileAttributeFlags for the attribute. + * + * Adds a new attribute with @name to the @list, setting + * its @type and @flags. + * + **/ +void +g_file_attribute_info_list_add (GFileAttributeInfoList *list, + const char *name, + GFileAttributeType type, + GFileAttributeFlags flags) +{ + GFileAttributeInfoListPriv *priv = (GFileAttributeInfoListPriv *)list; + GFileAttributeInfo info; + int i; + + g_return_if_fail (list != NULL); + g_return_if_fail (name != NULL); + + i = g_file_attribute_info_list_bsearch (list, name); + + if (i < list->n_infos && strcmp (list->infos[i].name, name) == 0) + { + list->infos[i].type = type; + return; + } + + info.name = g_strdup (name); + info.type = type; + info.flags = flags; + g_array_insert_vals (priv->array, i, &info, 1); + + list_update_public (priv); +} diff --git a/gio/gfileattribute.h b/gio/gfileattribute.h new file mode 100644 index 000000000..125a46d45 --- /dev/null +++ b/gio/gfileattribute.h @@ -0,0 +1,132 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#ifndef __G_FILE_ATTRIBUTE_H__ +#define __G_FILE_ATTRIBUTE_H__ + +#include <glib-object.h> + +G_BEGIN_DECLS + +typedef enum { + G_FILE_ATTRIBUTE_TYPE_INVALID = 0, + G_FILE_ATTRIBUTE_TYPE_STRING, + G_FILE_ATTRIBUTE_TYPE_BYTE_STRING, /* zero terminated string of non-zero bytes */ + G_FILE_ATTRIBUTE_TYPE_BOOLEAN, + G_FILE_ATTRIBUTE_TYPE_UINT32, + G_FILE_ATTRIBUTE_TYPE_INT32, + G_FILE_ATTRIBUTE_TYPE_UINT64, + G_FILE_ATTRIBUTE_TYPE_INT64, + G_FILE_ATTRIBUTE_TYPE_OBJECT +} GFileAttributeType; + +typedef enum { + G_FILE_ATTRIBUTE_FLAGS_NONE = 0, + G_FILE_ATTRIBUTE_FLAGS_COPY_WITH_FILE = 1 << 0, + G_FILE_ATTRIBUTE_FLAGS_COPY_WHEN_MOVED = 1 << 1 +} GFileAttributeFlags; + +/* Used by g_file_set_attributes_from_info */ +typedef enum { + G_FILE_ATTRIBUTE_STATUS_UNSET = 0, + G_FILE_ATTRIBUTE_STATUS_SET, + G_FILE_ATTRIBUTE_STATUS_ERROR_SETTING +} GFileAttributeStatus; + +#define G_FILE_ATTRIBUTE_VALUE_INIT {0} + +typedef struct { + GFileAttributeType type : 8; + GFileAttributeStatus status : 8; + union { + gboolean boolean; + gint32 int32; + guint32 uint32; + gint64 int64; + guint64 uint64; + char *string; + GQuark quark; + GObject *obj; + } u; +} GFileAttributeValue; + +typedef struct { + char *name; + GFileAttributeType type; + GFileAttributeFlags flags; +} GFileAttributeInfo; + +typedef struct { + GFileAttributeInfo *infos; + int n_infos; +} GFileAttributeInfoList; + +GFileAttributeValue *g_file_attribute_value_new (void); +void g_file_attribute_value_free (GFileAttributeValue *attr); +void g_file_attribute_value_clear (GFileAttributeValue *attr); +void g_file_attribute_value_set (GFileAttributeValue *attr, + const GFileAttributeValue *new_value); +GFileAttributeValue *g_file_attribute_value_dup (const GFileAttributeValue *other); + +char * g_file_attribute_value_as_string (const GFileAttributeValue *attr); + +const char * g_file_attribute_value_get_string (const GFileAttributeValue *attr); +const char * g_file_attribute_value_get_byte_string (const GFileAttributeValue *attr); +gboolean g_file_attribute_value_get_boolean (const GFileAttributeValue *attr); +guint32 g_file_attribute_value_get_uint32 (const GFileAttributeValue *attr); +gint32 g_file_attribute_value_get_int32 (const GFileAttributeValue *attr); +guint64 g_file_attribute_value_get_uint64 (const GFileAttributeValue *attr); +gint64 g_file_attribute_value_get_int64 (const GFileAttributeValue *attr); +GObject * g_file_attribute_value_get_object (const GFileAttributeValue *attr); + +void g_file_attribute_value_set_string (GFileAttributeValue *attr, + const char *string); +void g_file_attribute_value_set_byte_string (GFileAttributeValue *attr, + const char *string); +void g_file_attribute_value_set_boolean (GFileAttributeValue *attr, + gboolean value); +void g_file_attribute_value_set_uint32 (GFileAttributeValue *attr, + guint32 value); +void g_file_attribute_value_set_int32 (GFileAttributeValue *attr, + gint32 value); +void g_file_attribute_value_set_uint64 (GFileAttributeValue *attr, + guint64 value); +void g_file_attribute_value_set_int64 (GFileAttributeValue *attr, + gint64 value); +void g_file_attribute_value_set_object (GFileAttributeValue *attr, + GObject *obj); + +GFileAttributeInfoList * g_file_attribute_info_list_new (void); +GFileAttributeInfoList * g_file_attribute_info_list_ref (GFileAttributeInfoList *list); +void g_file_attribute_info_list_unref (GFileAttributeInfoList *list); +GFileAttributeInfoList * g_file_attribute_info_list_dup (GFileAttributeInfoList *list); +const GFileAttributeInfo *g_file_attribute_info_list_lookup (GFileAttributeInfoList *list, + const char *name); +void g_file_attribute_info_list_add (GFileAttributeInfoList *list, + const char *name, + GFileAttributeType type, + GFileAttributeFlags flags); + +G_END_DECLS + + +#endif /* __G_FILE_INFO_H__ */ diff --git a/gio/gfileenumerator.c b/gio/gfileenumerator.c new file mode 100644 index 000000000..300fff316 --- /dev/null +++ b/gio/gfileenumerator.c @@ -0,0 +1,617 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#include <config.h> +#include "gfileenumerator.h" +#include "gioscheduler.h" +#include "gasynchelper.h" +#include "gsimpleasyncresult.h" +#include "glibintl.h" + +G_DEFINE_TYPE (GFileEnumerator, g_file_enumerator, G_TYPE_OBJECT); + +struct _GFileEnumeratorPrivate { + /* TODO: Should be public for subclasses? */ + guint closed : 1; + guint pending : 1; + GAsyncReadyCallback outstanding_callback; + GError *outstanding_error; +}; + +static void g_file_enumerator_real_next_files_async (GFileEnumerator *enumerator, + int num_files, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +static GList * g_file_enumerator_real_next_files_finish (GFileEnumerator *enumerator, + GAsyncResult *res, + GError **error); +static void g_file_enumerator_real_close_async (GFileEnumerator *enumerator, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +static gboolean g_file_enumerator_real_close_finish (GFileEnumerator *enumerator, + GAsyncResult *res, + GError **error); + +static void +g_file_enumerator_finalize (GObject *object) +{ + GFileEnumerator *enumerator; + + enumerator = G_FILE_ENUMERATOR (object); + + if (!enumerator->priv->closed) + g_file_enumerator_close (enumerator, NULL, NULL); + + if (G_OBJECT_CLASS (g_file_enumerator_parent_class)->finalize) + (*G_OBJECT_CLASS (g_file_enumerator_parent_class)->finalize) (object); +} + +static void +g_file_enumerator_class_init (GFileEnumeratorClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + g_type_class_add_private (klass, sizeof (GFileEnumeratorPrivate)); + + gobject_class->finalize = g_file_enumerator_finalize; + + klass->next_files_async = g_file_enumerator_real_next_files_async; + klass->next_files_finish = g_file_enumerator_real_next_files_finish; + klass->close_async = g_file_enumerator_real_close_async; + klass->close_finish = g_file_enumerator_real_close_finish; +} + +static void +g_file_enumerator_init (GFileEnumerator *enumerator) +{ + enumerator->priv = G_TYPE_INSTANCE_GET_PRIVATE (enumerator, + G_TYPE_FILE_ENUMERATOR, + GFileEnumeratorPrivate); +} + +/** + * g_file_enumerator_next_file: + * @enumerator: a #GFileEnumerator. + * @cancellable: optional #GCancellable object, %NULL to ignore. + * @error: location to store the error occuring, or %NULL to ignore + * + * Returns information for the next file in the enumerated object. + * Will block until the information is available. + * + * On error, returns %NULL and sets @error to the error. If the + * enumerator is at the end, %NULL will be returned and @error will + * be unset. + * + * Return value: A #GFileInfo or %NULL on error or end of enumerator + **/ +GFileInfo * +g_file_enumerator_next_file (GFileEnumerator *enumerator, + GCancellable *cancellable, + GError **error) +{ + GFileEnumeratorClass *class; + GFileInfo *info; + + g_return_val_if_fail (G_IS_FILE_ENUMERATOR (enumerator), NULL); + g_return_val_if_fail (enumerator != NULL, NULL); + + if (enumerator->priv->closed) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_CLOSED, + _("Enumerator is closed")); + return NULL; + } + + if (enumerator->priv->pending) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_PENDING, + _("File enumerator has outstanding operation")); + return NULL; + } + + if (enumerator->priv->outstanding_error) + { + g_propagate_error (error, enumerator->priv->outstanding_error); + enumerator->priv->outstanding_error = NULL; + return NULL; + } + + class = G_FILE_ENUMERATOR_GET_CLASS (enumerator); + + if (cancellable) + g_push_current_cancellable (cancellable); + + enumerator->priv->pending = TRUE; + info = (* class->next_file) (enumerator, cancellable, error); + enumerator->priv->pending = FALSE; + + if (cancellable) + g_pop_current_cancellable (cancellable); + + return info; +} + +/** + * g_file_enumerator_close: + * @enumerator: a #GFileEnumerator. + * @cancellable: optional #GCancellable object, %NULL to ignore. + * @error: location to store the error occuring, or %NULL to ignore + * + * Releases all resources used by this enumerator, making the + * enumerator return %G_IO_ERROR_CLOSED on all calls. + * + * This will be automatically called when the last reference + * is dropped, but you might want to call make sure resources + * are released as early as possible. + * + * Return value: #TRUE on success or #FALSE on error. + **/ +gboolean +g_file_enumerator_close (GFileEnumerator *enumerator, + GCancellable *cancellable, + GError **error) +{ + GFileEnumeratorClass *class; + + g_return_val_if_fail (G_IS_FILE_ENUMERATOR (enumerator), FALSE); + g_return_val_if_fail (enumerator != NULL, FALSE); + + class = G_FILE_ENUMERATOR_GET_CLASS (enumerator); + + if (enumerator->priv->closed) + return TRUE; + + if (enumerator->priv->pending) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_PENDING, + _("File enumerator has outstanding operation")); + return FALSE; + } + + if (cancellable) + g_push_current_cancellable (cancellable); + + enumerator->priv->pending = TRUE; + (* class->close) (enumerator, cancellable, error); + enumerator->priv->pending = FALSE; + enumerator->priv->closed = TRUE; + + if (cancellable) + g_pop_current_cancellable (cancellable); + + return TRUE; +} + +static void +next_async_callback_wrapper (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + GFileEnumerator *enumerator = G_FILE_ENUMERATOR (source_object); + + enumerator->priv->pending = FALSE; + if (enumerator->priv->outstanding_callback) + (*enumerator->priv->outstanding_callback) (source_object, res, user_data); + g_object_unref (enumerator); +} + +/** + * g_file_enumerator_next_files_async: + * @enumerator: a #GFileEnumerator. + * @num_files: the number of file info objects to request + * @io_priority: the io priority of the request. the io priority of the request + * @cancellable: optional #GCancellable object, %NULL to ignore. + * @callback: callback to call when the request is satisfied + * @user_data: the user_data to pass to callback function + * + * Request information for a number of files from the enumerator asynchronously. + * When all i/o for the operation is finished the @callback will be called with + * the requested information. + * + * The callback can be called with less than @num_files files in case of error + * or at the end of the enumerator. In case of a partial error the callback will + * be called with any succeeding items and no error, and on the next request the + * error will be reported. If a request is cancelled the callback will be called + * with %G_IO_ERROR_CANCELLED. + * + * During an async request no other sync and async calls are allowed, and will + * result in %G_IO_ERROR_PENDING errors. + * + * Any outstanding i/o request with higher priority (lower numerical value) will + * be executed before an outstanding request with lower priority. Default + * priority is %G_PRIORITY_DEFAULT. + **/ +void +g_file_enumerator_next_files_async (GFileEnumerator *enumerator, + int num_files, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GFileEnumeratorClass *class; + GSimpleAsyncResult *simple; + + g_return_if_fail (G_IS_FILE_ENUMERATOR (enumerator)); + g_return_if_fail (enumerator != NULL); + g_return_if_fail (num_files >= 0); + + if (num_files == 0) + { + simple = g_simple_async_result_new (G_OBJECT (enumerator), + callback, + user_data, + g_file_enumerator_next_files_async); + g_simple_async_result_complete_in_idle (simple); + g_object_unref (simple); + return; + } + + if (enumerator->priv->closed) + { + g_simple_async_report_error_in_idle (G_OBJECT (enumerator), + callback, + user_data, + G_IO_ERROR, G_IO_ERROR_CLOSED, + _("File enumerator is already closed")); + return; + } + + if (enumerator->priv->pending) + { + g_simple_async_report_error_in_idle (G_OBJECT (enumerator), + callback, + user_data, + G_IO_ERROR, G_IO_ERROR_PENDING, + _("File enumerator has outstanding operation")); + return; + } + + class = G_FILE_ENUMERATOR_GET_CLASS (enumerator); + + enumerator->priv->pending = TRUE; + enumerator->priv->outstanding_callback = callback; + g_object_ref (enumerator); + (* class->next_files_async) (enumerator, num_files, io_priority, cancellable, + next_async_callback_wrapper, user_data); +} + +/** + * g_file_enumerator_next_files_finish: + * @enumerator: a #GFileEnumerator. + * @result: a #GAsyncResult. + * @error: a #GError location to store the error occuring, or %NULL to + * ignore. + * Returns: + **/ +GList * +g_file_enumerator_next_files_finish (GFileEnumerator *enumerator, + GAsyncResult *result, + GError **error) +{ + GFileEnumeratorClass *class; + GSimpleAsyncResult *simple; + + g_return_val_if_fail (G_IS_FILE_ENUMERATOR (enumerator), NULL); + g_return_val_if_fail (G_IS_ASYNC_RESULT (result), NULL); + + if (G_IS_SIMPLE_ASYNC_RESULT (result)) + { + simple = G_SIMPLE_ASYNC_RESULT (result); + if (g_simple_async_result_propagate_error (simple, error)) + return NULL; + + /* Special case read of 0 files */ + if (g_simple_async_result_get_source_tag (simple) == g_file_enumerator_next_files_async) + return NULL; + } + + class = G_FILE_ENUMERATOR_GET_CLASS (enumerator); + return class->next_files_finish (enumerator, result, error); +} + +static void +close_async_callback_wrapper (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + GFileEnumerator *enumerator = G_FILE_ENUMERATOR (source_object); + + enumerator->priv->pending = FALSE; + enumerator->priv->closed = TRUE; + if (enumerator->priv->outstanding_callback) + (*enumerator->priv->outstanding_callback) (source_object, res, user_data); + g_object_unref (enumerator); +} + +/** + * g_file_enumerator_close_async: + * @enumerator: a #GFileEnumerator. + * @io_priority: the io priority of the request. the io priority of the request + * @cancellable: optional #GCancellable object, %NULL to ignore. + * @callback: callback to call when the request is satisfied + * @user_data: the user_data to pass to callback function + * + **/ +void +g_file_enumerator_close_async (GFileEnumerator *enumerator, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GFileEnumeratorClass *class; + + g_return_if_fail (G_IS_FILE_ENUMERATOR (enumerator)); + + if (enumerator->priv->closed) + { + g_simple_async_report_error_in_idle (G_OBJECT (enumerator), + callback, + user_data, + G_IO_ERROR, G_IO_ERROR_CLOSED, + _("File enumerator is already closed")); + return; + } + + if (enumerator->priv->pending) + { + g_simple_async_report_error_in_idle (G_OBJECT (enumerator), + callback, + user_data, + G_IO_ERROR, G_IO_ERROR_PENDING, + _("File enumerator has outstanding operation")); + return; + } + + class = G_FILE_ENUMERATOR_GET_CLASS (enumerator); + + enumerator->priv->pending = TRUE; + enumerator->priv->outstanding_callback = callback; + g_object_ref (enumerator); + (* class->close_async) (enumerator, io_priority, cancellable, + close_async_callback_wrapper, user_data); +} + +/** + * g_file_enumerator_close_finish: + * @enumerator: a #GFileEnumerator. + * @result: a #GAsyncResult. + * @error: a #GError location to store the error occuring, or %NULL to + * ignore. + * Returns: %TRUE if the close operation has finished successfully. + **/ +gboolean +g_file_enumerator_close_finish (GFileEnumerator *enumerator, + GAsyncResult *result, + GError **error) +{ + GSimpleAsyncResult *simple; + GFileEnumeratorClass *class; + + g_return_val_if_fail (G_IS_FILE_ENUMERATOR (enumerator), FALSE); + g_return_val_if_fail (G_IS_ASYNC_RESULT (result), FALSE); + + if (G_IS_SIMPLE_ASYNC_RESULT (result)) + { + simple = G_SIMPLE_ASYNC_RESULT (result); + if (g_simple_async_result_propagate_error (simple, error)) + return FALSE; + } + + class = G_FILE_ENUMERATOR_GET_CLASS (enumerator); + return class->close_finish (enumerator, result, error); +} + +/** + * g_file_enumerator_is_closed: + * @enumerator: a #GFileEnumerator. + * + * Returns: %TRUE if the @enumerator is closed. + **/ +gboolean +g_file_enumerator_is_closed (GFileEnumerator *enumerator) +{ + g_return_val_if_fail (G_IS_FILE_ENUMERATOR (enumerator), TRUE); + + return enumerator->priv->closed; +} + +/** + * g_file_enumerator_has_pending: + * @enumerator: a #GFileEnumerator. + * + * Returns: %TRUE if the @enumerator has pending operations. + **/ +gboolean +g_file_enumerator_has_pending (GFileEnumerator *enumerator) +{ + g_return_val_if_fail (G_IS_FILE_ENUMERATOR (enumerator), TRUE); + + return enumerator->priv->pending; +} + +/** + * g_file_enumerator_set_pending: + * @enumerator: a #GFileEnumerator. + * @pending: a boolean value. + * + **/ +void +g_file_enumerator_set_pending (GFileEnumerator *enumerator, + gboolean pending) +{ + g_return_if_fail (G_IS_FILE_ENUMERATOR (enumerator)); + + enumerator->priv->pending = pending; +} + +typedef struct { + int num_files; + GList *files; +} NextAsyncOp; + +static void +next_files_thread (GSimpleAsyncResult *res, + GObject *object, + GCancellable *cancellable) +{ + NextAsyncOp *op; + GFileEnumeratorClass *class; + GError *error = NULL; + GFileInfo *info; + GFileEnumerator *enumerator; + int i; + + enumerator = G_FILE_ENUMERATOR (object); + op = g_simple_async_result_get_op_res_gpointer (res); + + class = G_FILE_ENUMERATOR_GET_CLASS (object); + + for (i = 0; i < op->num_files; i++) + { + if (g_cancellable_set_error_if_cancelled (cancellable, &error)) + info = NULL; + else + info = class->next_file (enumerator, cancellable, &error); + + if (info == NULL) + { + /* If we get an error after first file, return that on next operation */ + if (error != NULL && i > 0) + { + if (error->domain == G_IO_ERROR && + error->code == G_IO_ERROR_CANCELLED) + g_error_free (error); /* Never propagate cancel errors to other call */ + else + enumerator->priv->outstanding_error = error; + error = NULL; + } + + break; + } + else + op->files = g_list_prepend (op->files, info); + } +} + + +static void +g_file_enumerator_real_next_files_async (GFileEnumerator *enumerator, + int num_files, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GSimpleAsyncResult *res; + NextAsyncOp *op; + + op = g_new0 (NextAsyncOp, 1); + + op->num_files = num_files; + op->files = NULL; + + res = g_simple_async_result_new (G_OBJECT (enumerator), callback, user_data, g_file_enumerator_real_next_files_async); + g_simple_async_result_set_op_res_gpointer (res, op, g_free); + + g_simple_async_result_run_in_thread (res, next_files_thread, io_priority, cancellable); + g_object_unref (res); +} + +static GList * +g_file_enumerator_real_next_files_finish (GFileEnumerator *enumerator, + GAsyncResult *result, + GError **error) +{ + GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (result); + NextAsyncOp *op; + + g_assert (g_simple_async_result_get_source_tag (simple) == + g_file_enumerator_real_next_files_async); + + op = g_simple_async_result_get_op_res_gpointer (simple); + + return op->files; +} + +static void +close_async_thread (GSimpleAsyncResult *res, + GObject *object, + GCancellable *cancellable) +{ + GFileEnumeratorClass *class; + GError *error = NULL; + gboolean result; + + /* Auto handling of cancelation disabled, and ignore + cancellation, since we want to close things anyway, although + possibly in a quick-n-dirty way. At least we never want to leak + open handles */ + + class = G_FILE_ENUMERATOR_GET_CLASS (object); + result = class->close (G_FILE_ENUMERATOR (object), cancellable, &error); + if (!result) + { + g_simple_async_result_set_from_error (res, error); + g_error_free (error); + } +} + + +static void +g_file_enumerator_real_close_async (GFileEnumerator *enumerator, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GSimpleAsyncResult *res; + + res = g_simple_async_result_new (G_OBJECT (enumerator), + callback, + user_data, + g_file_enumerator_real_close_async); + + g_simple_async_result_set_handle_cancellation (res, FALSE); + + g_simple_async_result_run_in_thread (res, + close_async_thread, + io_priority, + cancellable); + g_object_unref (res); +} + +static gboolean +g_file_enumerator_real_close_finish (GFileEnumerator *enumerator, + GAsyncResult *result, + GError **error) +{ + GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (result); + g_assert (g_simple_async_result_get_source_tag (simple) == + g_file_enumerator_real_close_async); + return TRUE; +} diff --git a/gio/gfileenumerator.h b/gio/gfileenumerator.h new file mode 100644 index 000000000..1e83f8856 --- /dev/null +++ b/gio/gfileenumerator.h @@ -0,0 +1,130 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#ifndef __G_FILE_ENUMERATOR_H__ +#define __G_FILE_ENUMERATOR_H__ + +#include <glib-object.h> +#include <gio/gioerror.h> +#include <gio/gcancellable.h> +#include <gio/gfileinfo.h> +#include <gio/gasyncresult.h> + +G_BEGIN_DECLS + + +#define G_TYPE_FILE_ENUMERATOR (g_file_enumerator_get_type ()) +#define G_FILE_ENUMERATOR(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_FILE_ENUMERATOR, GFileEnumerator)) +#define G_FILE_ENUMERATOR_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), G_TYPE_FILE_ENUMERATOR, GFileEnumeratorClass)) +#define G_IS_FILE_ENUMERATOR(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_FILE_ENUMERATOR)) +#define G_IS_FILE_ENUMERATOR_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_FILE_ENUMERATOR)) +#define G_FILE_ENUMERATOR_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_TYPE_FILE_ENUMERATOR, GFileEnumeratorClass)) + + +typedef struct _GFileEnumerator GFileEnumerator; +typedef struct _GFileEnumeratorClass GFileEnumeratorClass; +typedef struct _GFileEnumeratorPrivate GFileEnumeratorPrivate; + + +struct _GFileEnumerator +{ + GObject parent; + + /*< private >*/ + GFileEnumeratorPrivate *priv; +}; + +struct _GFileEnumeratorClass +{ + GObjectClass parent_class; + + /* Virtual Table */ + + GFileInfo *(*next_file) (GFileEnumerator *enumerator, + GCancellable *cancellable, + GError **error); + gboolean (*close) (GFileEnumerator *enumerator, + GCancellable *cancellable, + GError **error); + + void (*next_files_async) (GFileEnumerator *enumerator, + int num_files, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + GList * (*next_files_finish) (GFileEnumerator *enumerator, + GAsyncResult *res, + GError **error); + void (*close_async) (GFileEnumerator *enumerator, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + gboolean (*close_finish) (GFileEnumerator *enumerator, + GAsyncResult *res, + GError **error); + + + /* Padding for future expansion */ + void (*_g_reserved1) (void); + void (*_g_reserved2) (void); + void (*_g_reserved3) (void); + void (*_g_reserved4) (void); + void (*_g_reserved5) (void); + void (*_g_reserved6) (void); + void (*_g_reserved7) (void); +}; + +GType g_file_enumerator_get_type (void) G_GNUC_CONST; + +GFileInfo *g_file_enumerator_next_file (GFileEnumerator *enumerator, + GCancellable *cancellable, + GError **error); +gboolean g_file_enumerator_close (GFileEnumerator *enumerator, + GCancellable *cancellable, + GError **error); +void g_file_enumerator_next_files_async (GFileEnumerator *enumerator, + int num_files, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +GList * g_file_enumerator_next_files_finish (GFileEnumerator *enumerator, + GAsyncResult *result, + GError **error); +void g_file_enumerator_close_async (GFileEnumerator *enumerator, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean g_file_enumerator_close_finish (GFileEnumerator *enumerator, + GAsyncResult *result, + GError **error); +gboolean g_file_enumerator_is_closed (GFileEnumerator *enumerator); +gboolean g_file_enumerator_has_pending (GFileEnumerator *enumerator); +void g_file_enumerator_set_pending (GFileEnumerator *enumerator, + gboolean pending); + +G_END_DECLS + +#endif /* __G_FILE_ENUMERATOR_H__ */ diff --git a/gio/gfileicon.c b/gio/gfileicon.c new file mode 100644 index 000000000..6e46e7f95 --- /dev/null +++ b/gio/gfileicon.c @@ -0,0 +1,257 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#include <config.h> + +#include "gfileicon.h" +#include "gsimpleasyncresult.h" + +static void g_file_icon_icon_iface_init (GIconIface *iface); +static void g_file_icon_loadable_icon_iface_init (GLoadableIconIface *iface); +static void g_file_icon_load_async (GLoadableIcon *icon, + int size, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + +struct _GFileIcon +{ + GObject parent_instance; + + GFile *file; +}; + +struct _GFileIconClass +{ + GObjectClass parent_class; +}; + +G_DEFINE_TYPE_WITH_CODE (GFileIcon, g_file_icon, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (G_TYPE_ICON, + g_file_icon_icon_iface_init); + G_IMPLEMENT_INTERFACE (G_TYPE_LOADABLE_ICON, + g_file_icon_loadable_icon_iface_init); + ) + +static void +g_file_icon_finalize (GObject *object) +{ + GFileIcon *icon; + + icon = G_FILE_ICON (object); + + g_object_unref (icon->file); + + if (G_OBJECT_CLASS (g_file_icon_parent_class)->finalize) + (*G_OBJECT_CLASS (g_file_icon_parent_class)->finalize) (object); +} + +static void +g_file_icon_class_init (GFileIconClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->finalize = g_file_icon_finalize; +} + +static void +g_file_icon_init (GFileIcon *file) +{ +} + +/** + * g_file_icon_new: + * @file: + * + * Returns: + **/ +GIcon * +g_file_icon_new (GFile *file) +{ + GFileIcon *icon; + + g_return_val_if_fail (G_IS_FILE (file), NULL); + + icon = g_object_new (G_TYPE_FILE_ICON, NULL); + icon->file = g_object_ref (file); + + return G_ICON (icon); +} + +/** + * g_file_icon_get_file: + * @icon: + * + * Returns: + **/ +GFile * +g_file_icon_get_file (GFileIcon *icon) +{ + g_return_val_if_fail (G_IS_FILE_ICON (icon), NULL); + + return icon->file; +} + +static guint +g_file_icon_hash (GIcon *icon) +{ + GFileIcon *file_icon = G_FILE_ICON (icon); + + return g_file_hash (file_icon->file); +} + +static gboolean +g_file_icon_equal (GIcon *icon1, + GIcon *icon2) +{ + GFileIcon *file1 = G_FILE_ICON (icon1); + GFileIcon *file2 = G_FILE_ICON (icon2); + + return g_file_equal (file1->file, file2->file); +} + + +static void +g_file_icon_icon_iface_init (GIconIface *iface) +{ + iface->hash = g_file_icon_hash; + iface->equal = g_file_icon_equal; +} + + +static GInputStream * +g_file_icon_load (GLoadableIcon *icon, + int size, + char **type, + GCancellable *cancellable, + GError **error) +{ + GFileInputStream *stream; + GFileIcon *file_icon = G_FILE_ICON (icon); + + stream = g_file_read (file_icon->file, + cancellable, + error); + + return G_INPUT_STREAM (stream); +} + +typedef struct { + GLoadableIcon *icon; + GAsyncReadyCallback callback; + gpointer user_data; +} LoadData; + +static void +load_data_free (LoadData *data) +{ + g_object_unref (data->icon); + g_free (data); +} + +static void +load_async_callback (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + GFileInputStream *stream; + GError *error = NULL; + GSimpleAsyncResult *simple; + LoadData *data = user_data; + + stream = g_file_read_finish (G_FILE (source_object), res, &error); + + if (stream == NULL) + { + simple = g_simple_async_result_new_from_error (G_OBJECT (data->icon), + data->callback, + data->user_data, + error); + g_error_free (error); + } + else + { + simple = g_simple_async_result_new (G_OBJECT (data->icon), + data->callback, + data->user_data, + g_file_icon_load_async); + + g_simple_async_result_set_op_res_gpointer (simple, + stream, + g_object_unref); + } + + + g_simple_async_result_complete (simple); + + load_data_free (data); +} + +static void +g_file_icon_load_async (GLoadableIcon *icon, + int size, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GFileIcon *file_icon = G_FILE_ICON (icon); + LoadData *data; + + data = g_new0 (LoadData, 1); + data->icon = g_object_ref (icon); + data->callback = callback; + data->user_data = user_data; + + g_file_read_async (file_icon->file, 0, + cancellable, + load_async_callback, data); + +} + +static GInputStream * +g_file_icon_load_finish (GLoadableIcon *icon, + GAsyncResult *res, + char **type, + GError **error) +{ + GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (res); + gpointer op; + + g_assert (g_simple_async_result_get_source_tag (simple) == g_file_icon_load_async); + + if (type) + *type = NULL; + + op = g_simple_async_result_get_op_res_gpointer (simple); + if (op) + return g_object_ref (op); + + return NULL; +} + +static void +g_file_icon_loadable_icon_iface_init (GLoadableIconIface *iface) +{ + iface->load = g_file_icon_load; + iface->load_async = g_file_icon_load_async; + iface->load_finish = g_file_icon_load_finish; +} diff --git a/gio/gfileicon.h b/gio/gfileicon.h new file mode 100644 index 000000000..1745e5fba --- /dev/null +++ b/gio/gfileicon.h @@ -0,0 +1,49 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#ifndef __G_FILE_ICON_H__ +#define __G_FILE_ICON_H__ + +#include <gio/gloadableicon.h> +#include <gio/gfile.h> + +G_BEGIN_DECLS + +#define G_TYPE_FILE_ICON (g_file_icon_get_type ()) +#define G_FILE_ICON(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_FILE_ICON, GFileIcon)) +#define G_FILE_ICON_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), G_TYPE_FILE_ICON, GFileIconClass)) +#define G_IS_FILE_ICON(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_FILE_ICON)) +#define G_IS_FILE_ICON_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_FILE_ICON)) +#define G_FILE_ICON_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_TYPE_FILE_ICON, GFileIconClass)) + +typedef struct _GFileIcon GFileIcon; +typedef struct _GFileIconClass GFileIconClass; + +GType g_file_icon_get_type (void) G_GNUC_CONST; + +GIcon *g_file_icon_new (GFile *file); + +GFile *g_file_icon_get_file (GFileIcon *icon); + +G_END_DECLS + +#endif /* __G_FILE_ICON_H__ */ diff --git a/gio/gfileinfo.c b/gio/gfileinfo.c new file mode 100644 index 000000000..07c8f4918 --- /dev/null +++ b/gio/gfileinfo.c @@ -0,0 +1,1924 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#include <config.h> + +#include <string.h> + +#include "gfileinfo.h" +#include "glibintl.h" + +/* We use this nasty thing, because NULL is a valid attribute matcher (matches nothing) */ +#define NO_ATTRIBUTE_MASK ((GFileAttributeMatcher *)1) + +typedef struct { + guint32 attribute; + GFileAttributeValue value; +} GFileAttribute; + +struct _GFileInfo +{ + GObject parent_instance; + + GArray *attributes; + GFileAttributeMatcher *mask; +}; + +struct _GFileInfoClass +{ + GObjectClass parent_class; +}; + +static gboolean g_file_attribute_matcher_matches_id (GFileAttributeMatcher *matcher, + guint32 id); + +G_DEFINE_TYPE (GFileInfo, g_file_info, G_TYPE_OBJECT); + +typedef struct { + guint32 id; + guint32 attribute_id_counter; +} NSInfo; + +G_LOCK_DEFINE_STATIC (attribute_hash); +static int namespace_id_counter = 0; +static GHashTable *ns_hash = NULL; +static GHashTable *attribute_hash = NULL; +static char ***attributes = NULL; + +/* Attribute ids are 32bit, we split it up like this: + * |------------|--------------------| + * 12 bit 20 bit + * namespace attribute id + * + * This way the attributes gets sorted in namespace order + */ + +#define NS_POS 20 +#define NS_MASK ((guint32)((1<<12) - 1)) +#define ID_POS 0 +#define ID_MASK ((guint32)((1<<20) - 1)) + +#define GET_NS(_attr_id) \ + (((guint32) (_attr_id) >> NS_POS) & NS_MASK) +#define GET_ID(_attr_id) \ + (((guint32)(_attr_id) >> ID_POS) & ID_MASK) + +#define MAKE_ATTR_ID(_ns, _id) \ + ( ((((guint32) _ns) & NS_MASK) << NS_POS) | \ + ((((guint32) _id) & ID_MASK) << ID_POS) ) + +static NSInfo * +_lookup_namespace (const char *namespace) +{ + NSInfo *ns_info; + + ns_info = g_hash_table_lookup (ns_hash, namespace); + if (ns_info == NULL) + { + ns_info = g_new0 (NSInfo, 1); + ns_info->id = ++namespace_id_counter; + g_hash_table_insert (ns_hash, g_strdup (namespace), ns_info); + attributes = g_realloc (attributes, (ns_info->id + 1) * sizeof (char **)); + attributes[ns_info->id] = NULL; + } + return ns_info; +} + +static guint32 +lookup_namespace (const char *namespace) +{ + NSInfo *ns_info; + guint32 id; + + G_LOCK (attribute_hash); + + if (attribute_hash == NULL) + { + ns_hash = g_hash_table_new (g_str_hash, g_str_equal); + attribute_hash = g_hash_table_new (g_str_hash, g_str_equal); + } + + ns_info = _lookup_namespace (namespace); + id = 0; + if (ns_info) + id = ns_info->id; + + G_UNLOCK (attribute_hash); + + return id; +} + +static char * +get_attribute_for_id (int attribute) +{ + char *s; + G_LOCK (attribute_hash); + s = attributes[GET_NS(attribute)][GET_ID(attribute)]; + G_UNLOCK (attribute_hash); + return s; +} + +static guint32 +lookup_attribute (const char *attribute) +{ + guint32 attr_id, id; + char *ns; + const char *colon; + NSInfo *ns_info; + + G_LOCK (attribute_hash); + if (attribute_hash == NULL) + { + ns_hash = g_hash_table_new (g_str_hash, g_str_equal); + attribute_hash = g_hash_table_new (g_str_hash, g_str_equal); + } + + attr_id = GPOINTER_TO_UINT (g_hash_table_lookup (attribute_hash, attribute)); + + if (attr_id != 0) + { + G_UNLOCK (attribute_hash); + return attr_id; + } + + colon = strchr (attribute, ':'); + if (colon) + ns = g_strndup (attribute, colon - attribute); + else + ns = g_strdup (""); + + ns_info = _lookup_namespace (ns); + g_free (ns); + + id = ++ns_info->attribute_id_counter; + attributes[ns_info->id] = g_realloc (attributes[ns_info->id], (id + 1) * sizeof (char *)); + attributes[ns_info->id][id] = g_strdup (attribute); + + attr_id = MAKE_ATTR_ID (ns_info->id, id); + + g_hash_table_insert (attribute_hash, attributes[ns_info->id][id], GUINT_TO_POINTER (attr_id)); + + G_UNLOCK (attribute_hash); + + return attr_id; +} + +static void +g_file_info_finalize (GObject *object) +{ + GFileInfo *info; + int i; + GFileAttribute *attrs; + + info = G_FILE_INFO (object); + + attrs = (GFileAttribute *)info->attributes->data; + for (i = 0; i < info->attributes->len; i++) + g_file_attribute_value_clear (&attrs[i].value); + g_array_free (info->attributes, TRUE); + + if (info->mask != NO_ATTRIBUTE_MASK) + g_file_attribute_matcher_unref (info->mask); + + if (G_OBJECT_CLASS (g_file_info_parent_class)->finalize) + (*G_OBJECT_CLASS (g_file_info_parent_class)->finalize) (object); +} + +static void +g_file_info_class_init (GFileInfoClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->finalize = g_file_info_finalize; +} + +static void +g_file_info_init (GFileInfo *info) +{ + info->mask = NO_ATTRIBUTE_MASK; + info->attributes = g_array_new (FALSE, FALSE, + sizeof (GFileAttribute)); +} + +/** + * g_file_info_new: + * + * Returns: a new #GFileInfo. + **/ +GFileInfo * +g_file_info_new (void) +{ + return g_object_new (G_TYPE_FILE_INFO, NULL); +} + +/** + * g_file_info_copy_into: + * @src_info: source to copy attributes from. + * @dest_info: destination to copy attributes to. + * + * Copies all of the attributes from @src_info to @dest_info. + **/ +void +g_file_info_copy_into (GFileInfo *src_info, GFileInfo *dest_info) +{ + GFileAttribute *source, *dest; + int i; + + g_return_if_fail (G_IS_FILE_INFO (src_info)); + g_return_if_fail (G_IS_FILE_INFO (dest_info)); + + dest = (GFileAttribute *)dest_info->attributes->data; + for (i = 0; i < dest_info->attributes->len; i++) + g_file_attribute_value_clear (&dest[i].value); + + g_array_set_size (dest_info->attributes, + src_info->attributes->len); + + source = (GFileAttribute *)src_info->attributes->data; + dest = (GFileAttribute *)dest_info->attributes->data; + + for (i = 0; i < src_info->attributes->len; i++) + { + dest[i].attribute = source[i].attribute; + dest[i].value.type = G_FILE_ATTRIBUTE_TYPE_INVALID; + g_file_attribute_value_set (&dest[i].value, &source[i].value); + } + + if (src_info->mask == NO_ATTRIBUTE_MASK) + dest_info->mask = NO_ATTRIBUTE_MASK; + else + dest_info->mask = g_file_attribute_matcher_ref (src_info->mask); +} + +/** + * g_file_info_dup: + * @other: a #GFileInfo. + * + * Returns: a duplicate #GFileInfo of @other. + **/ +GFileInfo * +g_file_info_dup (GFileInfo *other) +{ + GFileInfo *new; + + g_return_val_if_fail (G_IS_FILE_INFO (other), NULL); + + new = g_file_info_new (); + g_file_info_copy_into (other, new); + return new; +} + +/** + * g_file_info_set_attribute_mask: + * @info: a #GFileInfo. + * @mask: a #GFileAttributeMatcher. + * + * Sets @mask on @info to match specific attribute types. + * + **/ +void +g_file_info_set_attribute_mask (GFileInfo *info, + GFileAttributeMatcher *mask) +{ + GFileAttribute *attr; + int i; + + g_return_if_fail (G_IS_FILE_INFO (info)); + g_return_if_fail (mask != NULL); + + if (mask != info->mask) + { + if (info->mask != NO_ATTRIBUTE_MASK) + g_file_attribute_matcher_unref (info->mask); + info->mask = g_file_attribute_matcher_ref (mask); + + /* Remove non-matching attributes */ + for (i = 0; i < info->attributes->len; i++) + { + attr = &g_array_index (info->attributes, GFileAttribute, i); + if (!g_file_attribute_matcher_matches_id (mask, + attr->attribute)) + { + g_file_attribute_value_clear (&attr->value); + g_array_remove_index (info->attributes, i); + i--; + } + } + } +} + +/** + * g_file_info_unset_attribute_mask: + * @info: #GFileInfo. + * + **/ +void +g_file_info_unset_attribute_mask (GFileInfo *info) +{ + g_return_if_fail (G_IS_FILE_INFO (info)); + + if (info->mask != NO_ATTRIBUTE_MASK) + g_file_attribute_matcher_unref (info->mask); + info->mask = NO_ATTRIBUTE_MASK; +} + +/** + * g_file_info_clear_status: + * @info: a #GFileInfo. + * + * Clears the status information from @info. + * + **/ +void +g_file_info_clear_status (GFileInfo *info) +{ + GFileAttribute *attrs; + int i; + + g_return_if_fail (G_IS_FILE_INFO (info)); + + attrs = (GFileAttribute *)info->attributes->data; + for (i = 0; i < info->attributes->len; i++) + attrs[i].value.status = G_FILE_ATTRIBUTE_STATUS_UNSET; +} + +static int +g_file_info_find_place (GFileInfo *info, + guint32 attribute) +{ + int min, max, med; + GFileAttribute *attrs; + /* Binary search for the place where attribute would be, if its + in the array */ + + min = 0; + max = info->attributes->len; + + attrs = (GFileAttribute *)info->attributes->data; + + while (min < max) + { + med = min + (max - min) / 2; + if (attrs[med].attribute == attribute) + { + min = med; + break; + } + else if (attrs[med].attribute < attribute) + min = med + 1; + else /* attrs[med].attribute > attribute */ + max = med; + } + + return min; +} + +static GFileAttributeValue * +g_file_info_find_value (GFileInfo *info, + guint32 attr_id) +{ + GFileAttribute *attrs; + int i; + + i = g_file_info_find_place (info, attr_id); + attrs = (GFileAttribute *)info->attributes->data; + if (i < info->attributes->len && + attrs[i].attribute == attr_id) + return &attrs[i].value; + + return NULL; +} + +static GFileAttributeValue * +g_file_info_find_value_by_name (GFileInfo *info, + const char *attribute) +{ + guint32 attr_id; + + attr_id = lookup_attribute (attribute); + return g_file_info_find_value (info, attr_id); +} + +/** + * g_file_info_has_attribute: + * @info: a #GFileInfo. + * @attribute: a string. + * + * Returns: %TRUE if @GFileInfo has an attribute named @attribute, + * %FALSE otherwise. + **/ +gboolean +g_file_info_has_attribute (GFileInfo *info, + const char *attribute) +{ + GFileAttributeValue *value; + + g_return_val_if_fail (G_IS_FILE_INFO (info), FALSE); + g_return_val_if_fail (attribute != NULL && *attribute != '\0', FALSE); + + value = g_file_info_find_value_by_name (info, attribute); + return value != NULL; +} + +/** + * g_file_info_list_attributes: + * @info: a #GFileInfo. + * @name_space: a string. + * + * Returns: a null-terminated array of strings of all of the + * possible attribute types for the given @name_space, or + * %NULL on error. + **/ +char ** +g_file_info_list_attributes (GFileInfo *info, + const char *name_space) +{ + GPtrArray *names; + GFileAttribute *attrs; + guint32 attribute; + guint32 ns_id = (name_space) ? lookup_namespace (name_space) : 0; + int i; + + g_return_val_if_fail (G_IS_FILE_INFO (info), NULL); + + names = g_ptr_array_new (); + attrs = (GFileAttribute *)info->attributes->data; + for (i = 0; i < info->attributes->len; i++) + { + attribute = attrs[i].attribute; + if (ns_id == 0 || GET_NS (attribute) == ns_id) + g_ptr_array_add (names, g_strdup (get_attribute_for_id (attribute))); + } + + /* NULL terminate */ + g_ptr_array_add (names, NULL); + + return (char **)g_ptr_array_free (names, FALSE); +} + +/** + * g_file_info_get_attribute_type: + * @info: a #GFileInfo. + * @attribute: a string. + * + * Returns: a #GFileAttributeType for the given @attribute, or + * %G_FILE_ATTRIBUTE_TYPE_INVALID if one cannot be found. + **/ +GFileAttributeType +g_file_info_get_attribute_type (GFileInfo *info, + const char *attribute) +{ + GFileAttributeValue *value; + + g_return_val_if_fail (G_IS_FILE_INFO (info), G_FILE_ATTRIBUTE_TYPE_INVALID); + g_return_val_if_fail (attribute != NULL && *attribute != '\0', G_FILE_ATTRIBUTE_TYPE_INVALID); + + value = g_file_info_find_value_by_name (info, attribute); + if (value) + return value->type; + else + return G_FILE_ATTRIBUTE_TYPE_INVALID; +} + +/** + * g_file_info_remove_attribute: + * @info: a #GFileInfo. + * @attribute: a string. + * + * Removes @attribute from @info if it exists. + * + **/ +void +g_file_info_remove_attribute (GFileInfo *info, + const char *attribute) +{ + guint32 attr_id; + GFileAttribute *attrs; + int i; + + g_return_if_fail (G_IS_FILE_INFO (info)); + g_return_if_fail (attribute != NULL && *attribute != '\0'); + + attr_id = lookup_attribute (attribute); + + i = g_file_info_find_place (info, attr_id); + attrs = (GFileAttribute *)info->attributes->data; + if (i < info->attributes->len && + attrs[i].attribute == attr_id) + { + g_file_attribute_value_clear (&attrs[i].value); + g_array_remove_index (info->attributes, i); + } +} + +/** + * g_file_info_get_attribute: + * @info: a #GFileInfo. + * @attribute: a string. + * + * Returns: a #GFileAttributeValue for the given @attribute, or + * %NULL otherwise. + **/ +GFileAttributeValue * +g_file_info_get_attribute (GFileInfo *info, + const char *attribute) + +{ + g_return_val_if_fail (G_IS_FILE_INFO (info), NULL); + g_return_val_if_fail (attribute != NULL && *attribute != '\0', NULL); + + return g_file_info_find_value_by_name (info, attribute); +} + +/** + * g_file_info_get_attribute_object: + * @info: a #GFileInfo. + * @attribute: a string. + * + * Returns: a #GObject associated with the given @attribute, or + * %NULL otherwise. + **/ +GObject * +g_file_info_get_attribute_object (GFileInfo *info, + const char *attribute) +{ + GFileAttributeValue *value; + + g_return_val_if_fail (G_IS_FILE_INFO (info), NULL); + g_return_val_if_fail (attribute != NULL && *attribute != '\0', NULL); + + value = g_file_info_find_value_by_name (info, attribute); + return g_file_attribute_value_get_object (value); +} + +/** + * g_file_info_get_attribute_string: + * @info: a #GFileInfo. + * @attribute: a string. + * + * Returns: the contents of the @attribute value as a string, or + * %NULL otherwise. + **/ +const char * +g_file_info_get_attribute_string (GFileInfo *info, + const char *attribute) +{ + GFileAttributeValue *value; + + g_return_val_if_fail (G_IS_FILE_INFO (info), NULL); + g_return_val_if_fail (attribute != NULL && *attribute != '\0', NULL); + + value = g_file_info_find_value_by_name (info, attribute); + return g_file_attribute_value_get_string (value); +} + +/** + * g_file_info_get_attribute_byte_string: + * @info: a #GFileInfo. + * @attribute: a string. + * + * Returns: the contents of the @attribute value as a byte string, or + * %NULL otherwise. + **/ +const char * +g_file_info_get_attribute_byte_string (GFileInfo *info, + const char *attribute) +{ + GFileAttributeValue *value; + + g_return_val_if_fail (G_IS_FILE_INFO (info), NULL); + g_return_val_if_fail (attribute != NULL && *attribute != '\0', NULL); + + value = g_file_info_find_value_by_name (info, attribute); + return g_file_attribute_value_get_byte_string (value); +} + +/** + * g_file_info_get_attribute_boolean: + * @info: a #GFileInfo. + * @attribute: a string. + * + * Returns: + **/ +gboolean +g_file_info_get_attribute_boolean (GFileInfo *info, + const char *attribute) +{ + GFileAttributeValue *value; + + g_return_val_if_fail (G_IS_FILE_INFO (info), FALSE); + g_return_val_if_fail (attribute != NULL && *attribute != '\0', FALSE); + + value = g_file_info_find_value_by_name (info, attribute); + return g_file_attribute_value_get_boolean (value); +} + +/** + * g_file_info_get_attribute_uint32: + * @info: a #GFileInfo. + * @attribute: a string. + * + * Returns: + **/ +guint32 +g_file_info_get_attribute_uint32 (GFileInfo *info, + const char *attribute) +{ + GFileAttributeValue *value; + + g_return_val_if_fail (G_IS_FILE_INFO (info), 0); + g_return_val_if_fail (attribute != NULL && *attribute != '\0', 0); + + value = g_file_info_find_value_by_name (info, attribute); + return g_file_attribute_value_get_uint32 (value); +} + +/** + * g_file_info_get_attribute_int32: + * @info: + * @attribute: + * + * Returns: + **/ +gint32 +g_file_info_get_attribute_int32 (GFileInfo *info, + const char *attribute) +{ + GFileAttributeValue *value; + + g_return_val_if_fail (G_IS_FILE_INFO (info), 0); + g_return_val_if_fail (attribute != NULL && *attribute != '\0', 0); + + value = g_file_info_find_value_by_name (info, attribute); + return g_file_attribute_value_get_int32 (value); +} + +/** + * g_file_info_get_attribute_uint64: + * @info: + * @attribute: + * + * Returns: + **/ +guint64 +g_file_info_get_attribute_uint64 (GFileInfo *info, + const char *attribute) +{ + GFileAttributeValue *value; + + g_return_val_if_fail (G_IS_FILE_INFO (info), 0); + g_return_val_if_fail (attribute != NULL && *attribute != '\0', 0); + + value = g_file_info_find_value_by_name (info, attribute); + return g_file_attribute_value_get_uint64 (value); +} + +/** + * g_file_info_get_attribute_int64: + * @info: + * @attribute: + * + * Returns: + **/ +gint64 +g_file_info_get_attribute_int64 (GFileInfo *info, + const char *attribute) +{ + GFileAttributeValue *value; + + g_return_val_if_fail (G_IS_FILE_INFO (info), 0); + g_return_val_if_fail (attribute != NULL && *attribute != '\0', 0); + + value = g_file_info_find_value_by_name (info, attribute); + return g_file_attribute_value_get_int64 (value); +} + +static GFileAttributeValue * +g_file_info_create_value (GFileInfo *info, + guint32 attr_id) +{ + GFileAttribute *attrs; + GFileAttribute attr; + int i; + + if (info->mask != NO_ATTRIBUTE_MASK && + !g_file_attribute_matcher_matches_id (info->mask, attr_id)) + return NULL; + + i = g_file_info_find_place (info, attr_id); + + attrs = (GFileAttribute *)info->attributes->data; + if (i < info->attributes->len && + attrs[i].attribute == attr_id) + return &attrs[i].value; + else + { + attr.attribute = attr_id; + attr.value.type = G_FILE_ATTRIBUTE_TYPE_INVALID; + g_array_insert_val (info->attributes, i, attr); + + attrs = (GFileAttribute *)info->attributes->data; + return &attrs[i].value; + } +} + +static GFileAttributeValue * +g_file_info_create_value_by_name (GFileInfo *info, + const char *attribute) +{ + guint32 attr_id; + + attr_id = lookup_attribute (attribute); + + return g_file_info_create_value (info, attr_id); +} + +/** + * g_file_info_set_attribute: + * @info: + * @attribute: + * @attr_value: + * + **/ +void +g_file_info_set_attribute (GFileInfo *info, + const char *attribute, + const GFileAttributeValue *attr_value) +{ + GFileAttributeValue *value; + + g_return_if_fail (G_IS_FILE_INFO (info)); + g_return_if_fail (attribute != NULL && *attribute != '\0'); + g_return_if_fail (attr_value != NULL); + + value = g_file_info_create_value_by_name (info, attribute); + if (value) + g_file_attribute_value_set (value, attr_value); +} + +/** + * g_file_info_set_attribute_object: + * @info: + * @attribute: + * @attr_value: + * + **/ +void +g_file_info_set_attribute_object (GFileInfo *info, + const char *attribute, + GObject *attr_value) +{ + GFileAttributeValue *value; + + g_return_if_fail (G_IS_FILE_INFO (info)); + g_return_if_fail (attribute != NULL && *attribute != '\0'); + g_return_if_fail (G_IS_OBJECT (attr_value)); + + value = g_file_info_create_value_by_name (info, attribute); + if (value) + g_file_attribute_value_set_object (value, attr_value); +} + +/** + * g_file_info_set_attribute_string: + * @info: + * @attribute: + * @attr_value: + * + **/ +void +g_file_info_set_attribute_string (GFileInfo *info, + const char *attribute, + const char *attr_value) +{ + GFileAttributeValue *value; + + g_return_if_fail (G_IS_FILE_INFO (info)); + g_return_if_fail (attribute != NULL && *attribute != '\0'); + g_return_if_fail (attr_value != NULL); + + value = g_file_info_create_value_by_name (info, attribute); + if (value) + g_file_attribute_value_set_string (value, attr_value); +} + +/** + * g_file_info_set_attribute_byte_string: + * @info: + * @attribute: + * @attr_value: + * + **/ +void +g_file_info_set_attribute_byte_string (GFileInfo *info, + const char *attribute, + const char *attr_value) +{ + GFileAttributeValue *value; + + g_return_if_fail (G_IS_FILE_INFO (info)); + g_return_if_fail (attribute != NULL && *attribute != '\0'); + g_return_if_fail (attr_value != NULL); + + value = g_file_info_create_value_by_name (info, attribute); + if (value) + g_file_attribute_value_set_byte_string (value, attr_value); +} + +/** + * g_file_info_set_attribute_boolean: + * @info: + * @attribute: + * @attr_value: + * + **/ +void +g_file_info_set_attribute_boolean (GFileInfo *info, + const char *attribute, + gboolean attr_value) +{ + GFileAttributeValue *value; + + g_return_if_fail (G_IS_FILE_INFO (info)); + g_return_if_fail (attribute != NULL && *attribute != '\0'); + + value = g_file_info_create_value_by_name (info, attribute); + if (value) + g_file_attribute_value_set_boolean (value, attr_value); +} + +/** + * g_file_info_set_attribute_uint32: + * @info: + * @attribute: + * @attr_value: + * + **/ + +void +g_file_info_set_attribute_uint32 (GFileInfo *info, + const char *attribute, + guint32 attr_value) +{ + GFileAttributeValue *value; + + g_return_if_fail (G_IS_FILE_INFO (info)); + g_return_if_fail (attribute != NULL && *attribute != '\0'); + + value = g_file_info_create_value_by_name (info, attribute); + if (value) + g_file_attribute_value_set_uint32 (value, attr_value); +} + + +/** + * g_file_info_set_attribute_int32: + * @info: + * @attribute: + * @attr_value: + * + **/ +void +g_file_info_set_attribute_int32 (GFileInfo *info, + const char *attribute, + gint32 attr_value) +{ + GFileAttributeValue *value; + + g_return_if_fail (G_IS_FILE_INFO (info)); + g_return_if_fail (attribute != NULL && *attribute != '\0'); + + value = g_file_info_create_value_by_name (info, attribute); + if (value) + g_file_attribute_value_set_int32 (value, attr_value); +} + +/** + * g_file_info_set_attribute_uint64: + * @info: + * @attribute: + * @attr_value: + * + **/ +void +g_file_info_set_attribute_uint64 (GFileInfo *info, + const char *attribute, + guint64 attr_value) +{ + GFileAttributeValue *value; + + g_return_if_fail (G_IS_FILE_INFO (info)); + g_return_if_fail (attribute != NULL && *attribute != '\0'); + + value = g_file_info_create_value_by_name (info, attribute); + if (value) + g_file_attribute_value_set_uint64 (value, attr_value); +} + +/** + * g_file_info_set_attribute_int64: + * @info: + * @attribute: attribute name to set. + * @attr_value: int64 value to set attribute to. + * + **/ +void +g_file_info_set_attribute_int64 (GFileInfo *info, + const char *attribute, + gint64 attr_value) +{ + GFileAttributeValue *value; + + g_return_if_fail (G_IS_FILE_INFO (info)); + g_return_if_fail (attribute != NULL && *attribute != '\0'); + + value = g_file_info_create_value_by_name (info, attribute); + if (value) + g_file_attribute_value_set_int64 (value, attr_value); +} + +/* Helper getters */ +/** + * g_file_info_get_file_type: + * @info: a #GFileInfo. + * + * Returns: a #GFileType for the given file. + **/ +GFileType +g_file_info_get_file_type (GFileInfo *info) +{ + static guint32 attr = 0; + GFileAttributeValue *value; + + g_return_val_if_fail (G_IS_FILE_INFO (info), G_FILE_TYPE_UNKNOWN); + + if (attr == 0) + attr = lookup_attribute (G_FILE_ATTRIBUTE_STD_TYPE); + + value = g_file_info_find_value (info, attr); + return (GFileType)g_file_attribute_value_get_uint32 (value); +} + +/** + * g_file_info_get_is_hidden: + * @info: a #GFileInfo. + * + * Returns: %TRUE if the file is a hidden file, %FALSE otherwise. + **/ +gboolean +g_file_info_get_is_hidden (GFileInfo *info) +{ + static guint32 attr = 0; + GFileAttributeValue *value; + + g_return_val_if_fail (G_IS_FILE_INFO (info), FALSE); + + if (attr == 0) + attr = lookup_attribute (G_FILE_ATTRIBUTE_STD_IS_HIDDEN); + + value = g_file_info_find_value (info, attr); + return (GFileType)g_file_attribute_value_get_boolean (value); +} + +/** + * g_file_info_get_is_backup: + * @info: a #GFileInfo. + * + * Returns: %TRUE if file is a backup file (.*~), %FALSE otherwise. + **/ +gboolean +g_file_info_get_is_backup (GFileInfo *info) +{ + static guint32 attr = 0; + GFileAttributeValue *value; + + g_return_val_if_fail (G_IS_FILE_INFO (info), FALSE); + + if (attr == 0) + attr = lookup_attribute (G_FILE_ATTRIBUTE_STD_IS_BACKUP); + + value = g_file_info_find_value (info, attr); + return (GFileType)g_file_attribute_value_get_boolean (value); +} + +/** + * g_file_info_get_is_symlink: + * @info: a #GFileInfo. + * + * Returns: %TRUE if the given @info is a symlink. + **/ +gboolean +g_file_info_get_is_symlink (GFileInfo *info) +{ + static guint32 attr = 0; + GFileAttributeValue *value; + + g_return_val_if_fail (G_IS_FILE_INFO (info), FALSE); + + if (attr == 0) + attr = lookup_attribute (G_FILE_ATTRIBUTE_STD_IS_SYMLINK); + + value = g_file_info_find_value (info, attr); + return (GFileType)g_file_attribute_value_get_boolean (value); +} + +/** + * g_file_info_get_name: + * @info: a #GFileInfo. + * + * Returns: + **/ +const char * +g_file_info_get_name (GFileInfo *info) +{ + static guint32 attr = 0; + GFileAttributeValue *value; + + g_return_val_if_fail (G_IS_FILE_INFO (info), NULL); + + if (attr == 0) + attr = lookup_attribute (G_FILE_ATTRIBUTE_STD_NAME); + + value = g_file_info_find_value (info, attr); + return g_file_attribute_value_get_byte_string (value); +} + +/** + * g_file_info_get_display_name: + * @info: a #GFileInfo. + * + * Returns: + **/ +const char * +g_file_info_get_display_name (GFileInfo *info) +{ + static guint32 attr = 0; + GFileAttributeValue *value; + + g_return_val_if_fail (G_IS_FILE_INFO (info), NULL); + + if (attr == 0) + attr = lookup_attribute (G_FILE_ATTRIBUTE_STD_DISPLAY_NAME); + + value = g_file_info_find_value (info, attr); + return g_file_attribute_value_get_string (value); +} + +/** + * g_file_info_get_edit_name: + * @info: a #GFileInfo. + * + * Returns: + **/ +const char * +g_file_info_get_edit_name (GFileInfo *info) +{ + static guint32 attr = 0; + GFileAttributeValue *value; + + g_return_val_if_fail (G_IS_FILE_INFO (info), NULL); + + if (attr == 0) + attr = lookup_attribute (G_FILE_ATTRIBUTE_STD_EDIT_NAME); + + value = g_file_info_find_value (info, attr); + return g_file_attribute_value_get_string (value); +} + +/** + * g_file_info_get_icon: + * @info: a #GFileInfo. + * + * Returns: #GIcon for the given @info. + **/ +GIcon * +g_file_info_get_icon (GFileInfo *info) +{ + static guint32 attr = 0; + GFileAttributeValue *value; + GObject *obj; + + g_return_val_if_fail (G_IS_FILE_INFO (info), NULL); + + if (attr == 0) + attr = lookup_attribute (G_FILE_ATTRIBUTE_STD_ICON); + + value = g_file_info_find_value (info, attr); + obj = g_file_attribute_value_get_object (value); + if (obj != NULL && G_IS_ICON (obj)) + return G_ICON (obj); + return NULL; +} + +/** + * g_file_info_get_content_type: + * @info: a #GFileInfo. + * + * Returns: + **/ +const char * +g_file_info_get_content_type (GFileInfo *info) +{ + static guint32 attr = 0; + GFileAttributeValue *value; + + g_return_val_if_fail (G_IS_FILE_INFO (info), NULL); + + if (attr == 0) + attr = lookup_attribute (G_FILE_ATTRIBUTE_STD_CONTENT_TYPE); + + value = g_file_info_find_value (info, attr); + return g_file_attribute_value_get_string (value); +} + +/** + * g_file_info_get_size: + * @info: a #GFileInfo. + * + * Returns: goffset. + **/ +goffset +g_file_info_get_size (GFileInfo *info) +{ + static guint32 attr = 0; + GFileAttributeValue *value; + + g_return_val_if_fail (G_IS_FILE_INFO (info), (goffset) 0); + + if (attr == 0) + attr = lookup_attribute (G_FILE_ATTRIBUTE_STD_SIZE); + + value = g_file_info_find_value (info, attr); + return (goffset) g_file_attribute_value_get_uint64 (value); +} + +/** + * g_file_info_get_modification_time: + * @info: a #GFileInfo. + * @result: + * + **/ + +void +g_file_info_get_modification_time (GFileInfo *info, + GTimeVal *result) +{ + static guint32 attr_mtime = 0, attr_mtime_usec; + GFileAttributeValue *value; + + g_return_if_fail (G_IS_FILE_INFO (info)); + g_return_if_fail (result != NULL); + + if (attr_mtime == 0) + { + attr_mtime = lookup_attribute (G_FILE_ATTRIBUTE_TIME_MODIFIED); + attr_mtime_usec = lookup_attribute (G_FILE_ATTRIBUTE_TIME_MODIFIED_USEC); + } + + value = g_file_info_find_value (info, attr_mtime); + result->tv_sec = g_file_attribute_value_get_uint64 (value); + value = g_file_info_find_value (info, attr_mtime_usec); + result->tv_usec = g_file_attribute_value_get_uint32 (value); +} + +/** + * g_file_info_get_symlink_target: + * @info: a #GFileInfo. + * + * Returns: + **/ +const char * +g_file_info_get_symlink_target (GFileInfo *info) +{ + static guint32 attr = 0; + GFileAttributeValue *value; + + g_return_val_if_fail (G_IS_FILE_INFO (info), NULL); + + if (attr == 0) + attr = lookup_attribute (G_FILE_ATTRIBUTE_STD_SYMLINK_TARGET); + + value = g_file_info_find_value (info, attr); + return g_file_attribute_value_get_byte_string (value); +} + +/** + * g_file_info_get_etag: + * @info: a #GFileInfo. + * + * Returns: + **/ +const char * +g_file_info_get_etag (GFileInfo *info) +{ + static guint32 attr = 0; + GFileAttributeValue *value; + + g_return_val_if_fail (G_IS_FILE_INFO (info), NULL); + + if (attr == 0) + attr = lookup_attribute (G_FILE_ATTRIBUTE_ETAG_VALUE); + + value = g_file_info_find_value (info, attr); + return g_file_attribute_value_get_string (value); +} + +/** + * g_file_info_get_sort_order: + * @info: a #GFileInfo. + * + * Returns: + **/ +gint32 +g_file_info_get_sort_order (GFileInfo *info) +{ + static guint32 attr = 0; + GFileAttributeValue *value; + + g_return_val_if_fail (G_IS_FILE_INFO (info), 0); + + if (attr == 0) + attr = lookup_attribute (G_FILE_ATTRIBUTE_STD_SORT_ORDER); + + value = g_file_info_find_value (info, attr); + return g_file_attribute_value_get_int32 (value); +} + +/* Helper setters: */ +/** + * g_file_info_set_file_type: + * @info: a #GFileInfo. + * @type: + * + **/ +void +g_file_info_set_file_type (GFileInfo *info, + GFileType type) +{ + static guint32 attr = 0; + GFileAttributeValue *value; + + g_return_if_fail (G_IS_FILE_INFO (info)); + + if (attr == 0) + attr = lookup_attribute (G_FILE_ATTRIBUTE_STD_TYPE); + + value = g_file_info_create_value (info, attr); + if (value) + g_file_attribute_value_set_uint32 (value, type); +} + +/** + * g_file_info_set_is_hidden: + * @info: a #GFileInfo. + * @is_hidden: + * + **/ +void +g_file_info_set_is_hidden (GFileInfo *info, + gboolean is_hidden) +{ + static guint32 attr = 0; + GFileAttributeValue *value; + + g_return_if_fail (G_IS_FILE_INFO (info)); + + if (attr == 0) + attr = lookup_attribute (G_FILE_ATTRIBUTE_STD_IS_HIDDEN); + + value = g_file_info_create_value (info, attr); + if (value) + g_file_attribute_value_set_boolean (value, is_hidden); +} + +/** + * g_file_info_set_is_symlink: + * @info: a #GFileInfo. + * @is_symlink: + * + **/ +void +g_file_info_set_is_symlink (GFileInfo *info, + gboolean is_symlink) +{ + static guint32 attr = 0; + GFileAttributeValue *value; + + g_return_if_fail (G_IS_FILE_INFO (info)); + + if (attr == 0) + attr = lookup_attribute (G_FILE_ATTRIBUTE_STD_IS_SYMLINK); + + value = g_file_info_create_value (info, attr); + if (value) + g_file_attribute_value_set_boolean (value, is_symlink); +} + +/** + * g_file_info_set_name: + * @info: a #GFileInfo. + * @name: + * + **/ +void +g_file_info_set_name (GFileInfo *info, + const char *name) +{ + static guint32 attr = 0; + GFileAttributeValue *value; + + g_return_if_fail (G_IS_FILE_INFO (info)); + g_return_if_fail (name != NULL); + + if (attr == 0) + attr = lookup_attribute (G_FILE_ATTRIBUTE_STD_NAME); + + value = g_file_info_create_value (info, attr); + if (value) + g_file_attribute_value_set_byte_string (value, name); +} + +/** + * g_file_info_set_display_name: + * @info: a #GFileInfo. + * @display_name: + * + **/ +void +g_file_info_set_display_name (GFileInfo *info, + const char *display_name) +{ + static guint32 attr = 0; + GFileAttributeValue *value; + + g_return_if_fail (G_IS_FILE_INFO (info)); + g_return_if_fail (display_name != NULL); + + if (attr == 0) + attr = lookup_attribute (G_FILE_ATTRIBUTE_STD_DISPLAY_NAME); + + value = g_file_info_create_value (info, attr); + if (value) + g_file_attribute_value_set_string (value, display_name); +} + +/** + * g_file_info_set_edit_name: + * @info: a #GFileInfo. + * @edit_name: + * + **/ + +void +g_file_info_set_edit_name (GFileInfo *info, + const char *edit_name) +{ + static guint32 attr = 0; + GFileAttributeValue *value; + + g_return_if_fail (G_IS_FILE_INFO (info)); + g_return_if_fail (edit_name != NULL); + + if (attr == 0) + attr = lookup_attribute (G_FILE_ATTRIBUTE_STD_EDIT_NAME); + + value = g_file_info_create_value (info, attr); + if (value) + g_file_attribute_value_set_string (value, edit_name); +} + +/** + * g_file_info_set_icon: + * @info: a #GFileInfo. + * @icon: + * + **/ +void +g_file_info_set_icon (GFileInfo *info, + GIcon *icon) +{ + static guint32 attr = 0; + GFileAttributeValue *value; + + g_return_if_fail (G_IS_FILE_INFO (info)); + g_return_if_fail (G_IS_ICON (icon)); + + if (attr == 0) + attr = lookup_attribute (G_FILE_ATTRIBUTE_STD_ICON); + + value = g_file_info_create_value (info, attr); + if (value) + g_file_attribute_value_set_object (value, G_OBJECT (icon)); +} + +/** + * g_file_info_set_content_type: + * @info: a #GFileInfo. + * @content_type: + * + **/ +void +g_file_info_set_content_type (GFileInfo *info, + const char *content_type) +{ + static guint32 attr = 0; + GFileAttributeValue *value; + + g_return_if_fail (G_IS_FILE_INFO (info)); + g_return_if_fail (content_type != NULL); + + if (attr == 0) + attr = lookup_attribute (G_FILE_ATTRIBUTE_STD_CONTENT_TYPE); + + value = g_file_info_create_value (info, attr); + if (value) + g_file_attribute_value_set_string (value, content_type); +} + +/** + * g_file_info_set_size: + * @info: a #GFileInfo. + * @size: + * + **/ +void +g_file_info_set_size (GFileInfo *info, + goffset size) +{ + static guint32 attr = 0; + GFileAttributeValue *value; + + g_return_if_fail (G_IS_FILE_INFO (info)); + + if (attr == 0) + attr = lookup_attribute (G_FILE_ATTRIBUTE_STD_SIZE); + + value = g_file_info_create_value (info, attr); + if (value) + g_file_attribute_value_set_uint64 (value, size); +} + +/** + * g_file_info_set_modification_time + * @info: a #GFileInfo. + * @mtime: + * + **/ +void +g_file_info_set_modification_time (GFileInfo *info, + GTimeVal *mtime) +{ + static guint32 attr_mtime = 0, attr_mtime_usec; + GFileAttributeValue *value; + + g_return_if_fail (G_IS_FILE_INFO (info)); + g_return_if_fail (mtime != NULL); + + if (attr_mtime == 0) + { + attr_mtime = lookup_attribute (G_FILE_ATTRIBUTE_TIME_MODIFIED); + attr_mtime_usec = lookup_attribute (G_FILE_ATTRIBUTE_TIME_MODIFIED_USEC); + } + + value = g_file_info_create_value (info, attr_mtime); + if (value) + g_file_attribute_value_set_uint64 (value, mtime->tv_sec); + value = g_file_info_create_value (info, attr_mtime_usec); + if (value) + g_file_attribute_value_set_uint32 (value, mtime->tv_usec); +} + +/** + * g_file_info_set_symlink_target: + * @info: a #GFileInfo. + * @symlink_target: + * + **/ +void +g_file_info_set_symlink_target (GFileInfo *info, + const char *symlink_target) +{ + static guint32 attr = 0; + GFileAttributeValue *value; + + g_return_if_fail (G_IS_FILE_INFO (info)); + g_return_if_fail (symlink_target != NULL); + + if (attr == 0) + attr = lookup_attribute (G_FILE_ATTRIBUTE_STD_SYMLINK_TARGET); + + value = g_file_info_create_value (info, attr); + if (value) + g_file_attribute_value_set_byte_string (value, symlink_target); +} + +/** + * g_file_info_set_sort_order: + * @info: a #GFileInfo. + * @sort_order: + * + **/ +void +g_file_info_set_sort_order (GFileInfo *info, + gint32 sort_order) +{ + static guint32 attr = 0; + GFileAttributeValue *value; + + g_return_if_fail (G_IS_FILE_INFO (info)); + + if (attr == 0) + attr = lookup_attribute (G_FILE_ATTRIBUTE_STD_SORT_ORDER); + + value = g_file_info_create_value (info, attr); + if (value) + g_file_attribute_value_set_int32 (value, sort_order); +} + + +#define KILOBYTE_FACTOR 1024.0 +#define MEGABYTE_FACTOR (1024.0 * 1024.0) +#define GIGABYTE_FACTOR (1024.0 * 1024.0 * 1024.0) + +char * +g_format_file_size_for_display (goffset size) +{ + if (size < (goffset) KILOBYTE_FACTOR) + return g_strdup_printf (dngettext(GETTEXT_PACKAGE, "%u byte", "%u bytes",(guint) size), (guint) size); + else + { + gdouble displayed_size; + + if (size < (goffset) MEGABYTE_FACTOR) + { + displayed_size = (gdouble) size / KILOBYTE_FACTOR; + return g_strdup_printf (_("%.1f KB"), displayed_size); + } + else if (size < (goffset) GIGABYTE_FACTOR) + { + displayed_size = (gdouble) size / MEGABYTE_FACTOR; + return g_strdup_printf (_("%.1f MB"), displayed_size); + } + else + { + displayed_size = (gdouble) size / GIGABYTE_FACTOR; + return g_strdup_printf (_("%.1f GB"), displayed_size); + } + } +} + +#define ON_STACK_MATCHERS 5 + +typedef struct { + guint32 id; + guint32 mask; +} SubMatcher; + +struct _GFileAttributeMatcher { + gboolean all; + SubMatcher sub_matchers[ON_STACK_MATCHERS]; + GArray *more_sub_matchers; + + /* Interator */ + guint32 iterator_ns; + int iterator_pos; + int ref; +}; + +static void +matcher_add (GFileAttributeMatcher *matcher, + guint id, guint mask) +{ + SubMatcher *sub_matchers; + int i; + SubMatcher s; + + for (i = 0; i < ON_STACK_MATCHERS; i++) + { + /* First empty spot, not found, use this */ + if (matcher->sub_matchers[i].id == 0) + { + matcher->sub_matchers[i].id = id; + matcher->sub_matchers[i].mask = mask; + return; + } + + /* Already added */ + if (matcher->sub_matchers[i].id == id && + matcher->sub_matchers[i].mask == mask) + return; + } + + if (matcher->more_sub_matchers == NULL) + matcher->more_sub_matchers = g_array_new (FALSE, FALSE, sizeof (SubMatcher)); + + sub_matchers = (SubMatcher *)matcher->more_sub_matchers->data; + for (i = 0; i < matcher->more_sub_matchers->len; i++) + { + /* Already added */ + if (sub_matchers[i].id == id && + sub_matchers[i].mask == mask) + return; + } + + s.id = id; + s.mask = mask; + + g_array_append_val (matcher->more_sub_matchers, s); +} + +/** + * g_file_attribute_matcher_new + * @attributes: + * + * Returns: #GFileAttributeMatcher. + **/ +GFileAttributeMatcher * +g_file_attribute_matcher_new (const char *attributes) +{ + char **split; + char *colon; + int i; + GFileAttributeMatcher *matcher; + + if (attributes == NULL || *attributes == '\0') + return NULL; + + matcher = g_malloc0 (sizeof (GFileAttributeMatcher)); + matcher->ref = 1; + + split = g_strsplit (attributes, ",", -1); + + for (i = 0; split[i] != NULL; i++) + { + if (strcmp (split[i], "*") == 0) + matcher->all = TRUE; + else + { + guint32 id, mask; + + colon = strchr (split[i], ':'); + if (colon != NULL && + !(colon[1] == 0 || + (colon[1] == '*' && + colon[2] == 0))) + { + id = lookup_attribute (split[i]); + mask = 0xffffffff; + } + else + { + if (colon) + *colon = 0; + + id = lookup_namespace (split[i]) << NS_POS; + mask = NS_MASK << NS_POS; + } + + matcher_add (matcher, id, mask); + } + } + + g_strfreev (split); + + return matcher; +} + +/** + * g_file_attribute_matcher_ref: + * @matcher: a #GFileAttributeMatcher. + * + * Returns: a #GFileAttributeMatcher. + **/ +GFileAttributeMatcher * +g_file_attribute_matcher_ref (GFileAttributeMatcher *matcher) +{ + g_return_val_if_fail (matcher != NULL, NULL); + g_return_val_if_fail (matcher->ref > 0, NULL); + + g_atomic_int_inc (&matcher->ref); + + return matcher; +} + +/** + * g_file_attribute_matcher_unref: + * @matcher: a #GFileAttributeMatcher. + * + * Unreferences @matcher. If the reference count falls below 1, + * the @matcher is automatically freed. + * + **/ +void +g_file_attribute_matcher_unref (GFileAttributeMatcher *matcher) +{ + g_return_if_fail (matcher != NULL); + g_return_if_fail (matcher->ref > 0); + + if (g_atomic_int_dec_and_test (&matcher->ref)) + { + if (matcher->more_sub_matchers) + g_array_free (matcher->more_sub_matchers, TRUE); + + g_free (matcher); + } +} + +/** + * g_file_attribute_matcher_matches_only: + * @matcher: a #GFileAttributeMatcher. + * @attribute: + * + * Returns: + **/ +gboolean +g_file_attribute_matcher_matches_only (GFileAttributeMatcher *matcher, + const char *attribute) +{ + guint32 id; + + g_return_val_if_fail (matcher != NULL, FALSE); + g_return_val_if_fail (attribute != NULL && *attribute != '\0', FALSE); + + if (matcher->all) + return FALSE; + + id = lookup_attribute (attribute); + + if (matcher->sub_matchers[0].id != 0 && + matcher->sub_matchers[1].id == 0 && + matcher->sub_matchers[0].mask == 0xffffffff && + matcher->sub_matchers[0].id == id) + return TRUE; + + return FALSE; +} + +static gboolean +matcher_matches_id (GFileAttributeMatcher *matcher, + guint32 id) +{ + SubMatcher *sub_matchers; + int i; + + for (i = 0; i < ON_STACK_MATCHERS; i++) + { + if (matcher->sub_matchers[i].id == 0) + return FALSE; + + if (matcher->sub_matchers[i].id == (id & matcher->sub_matchers[i].mask)) + return TRUE; + } + + if (matcher->more_sub_matchers) + { + sub_matchers = (SubMatcher *)matcher->more_sub_matchers->data; + for (i = 0; i < matcher->more_sub_matchers->len; i++) + { + if (sub_matchers[i].id == (id & sub_matchers[i].mask)) + return TRUE; + } + } + + return FALSE; +} + +static gboolean +g_file_attribute_matcher_matches_id (GFileAttributeMatcher *matcher, + guint32 id) +{ + g_return_val_if_fail (matcher != NULL, FALSE); + + if (matcher->all) + return TRUE; + + return matcher_matches_id (matcher, id); +} + +/** + * g_file_attribute_matcher_matches: + * @matcher: a #GFileAttributeMatcher. + * @attribute: + * + * Returns: + **/ +gboolean +g_file_attribute_matcher_matches (GFileAttributeMatcher *matcher, + const char *attribute) +{ + g_return_val_if_fail (matcher != NULL, FALSE); + g_return_val_if_fail (attribute != NULL && *attribute != '\0', FALSE); + + if (matcher->all) + return TRUE; + + return matcher_matches_id (matcher, lookup_attribute (attribute)); +} + +/* return TRUE -> all */ +/** + * g_file_attribute_matcher_enumerate_namespace: + * @matcher: a #GFileAttributeMatcher. + * @ns: + * + * Returns: %TRUE, %FALSE. + **/ +gboolean +g_file_attribute_matcher_enumerate_namespace (GFileAttributeMatcher *matcher, + const char *ns) +{ + SubMatcher *sub_matchers; + int ns_id; + int i; + + g_return_val_if_fail (matcher != NULL, FALSE); + g_return_val_if_fail (ns != NULL && *ns != '\0', FALSE); + + if (matcher->all) + return TRUE; + + ns_id = lookup_namespace (ns) << NS_POS; + + for (i = 0; i < ON_STACK_MATCHERS; i++) + { + if (matcher->sub_matchers[i].id == ns_id) + return TRUE; + } + + if (matcher->more_sub_matchers) + { + sub_matchers = (SubMatcher *)matcher->more_sub_matchers->data; + for (i = 0; i < matcher->more_sub_matchers->len; i++) + { + if (sub_matchers[i].id == ns_id) + return TRUE; + } + } + + matcher->iterator_ns = ns_id; + matcher->iterator_pos = 0; + + return FALSE; +} + +/** + * g_file_attribute_matcher_enumerate_next: + * @matcher: a #GFileAttributeMatcher. + * + * Returns: + **/ +const char * +g_file_attribute_matcher_enumerate_next (GFileAttributeMatcher *matcher) +{ + int i; + SubMatcher *sub_matcher; + + g_return_val_if_fail (matcher != NULL, NULL); + + while (1) + { + i = matcher->iterator_pos++; + + if (i < ON_STACK_MATCHERS) + { + if (matcher->sub_matchers[i].id == 0) + return NULL; + + sub_matcher = &matcher->sub_matchers[i]; + } + else + { + if (matcher->more_sub_matchers == NULL) + return NULL; + + i -= ON_STACK_MATCHERS; + if (i < matcher->more_sub_matchers->len) + sub_matcher = &g_array_index (matcher->more_sub_matchers, SubMatcher, i); + else + return NULL; + } + + if (sub_matcher->mask == 0xffffffff && + (sub_matcher->id & (NS_MASK << NS_POS)) == matcher->iterator_ns) + return get_attribute_for_id (sub_matcher->id); + } +} diff --git a/gio/gfileinfo.h b/gio/gfileinfo.h new file mode 100644 index 000000000..480d6a6de --- /dev/null +++ b/gio/gfileinfo.h @@ -0,0 +1,280 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#ifndef __G_FILE_INFO_H__ +#define __G_FILE_INFO_H__ + +#include <glib-object.h> +#include <gio/gfileattribute.h> +#include <gio/gicon.h> + +G_BEGIN_DECLS + +#define G_TYPE_FILE_INFO (g_file_info_get_type ()) +#define G_FILE_INFO(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_FILE_INFO, GFileInfo)) +#define G_FILE_INFO_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), G_TYPE_FILE_INFO, GFileInfoClass)) +#define G_IS_FILE_INFO(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_FILE_INFO)) +#define G_IS_FILE_INFO_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_FILE_INFO)) +#define G_FILE_INFO_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_TYPE_FILE_INFO, GFileInfoClass)) + +typedef struct _GFileInfo GFileInfo; +typedef struct _GFileInfoClass GFileInfoClass; +typedef struct _GFileAttributeMatcher GFileAttributeMatcher; + +typedef enum { + G_FILE_TYPE_UNKNOWN = 0, + G_FILE_TYPE_REGULAR, + G_FILE_TYPE_DIRECTORY, + G_FILE_TYPE_SYMBOLIC_LINK, + G_FILE_TYPE_SPECIAL, /* socket, fifo, blockdev, chardev */ + G_FILE_TYPE_SHORTCUT, + G_FILE_TYPE_MOUNTABLE +} GFileType; + +/* Common Attributes: */ + +#define G_FILE_ATTRIBUTE_STD_TYPE "std:type" /* uint32 (GFileType) */ +#define G_FILE_ATTRIBUTE_STD_IS_HIDDEN "std:is_hidden" /* boolean */ +#define G_FILE_ATTRIBUTE_STD_IS_BACKUP "std:is_backup" /* boolean */ +#define G_FILE_ATTRIBUTE_STD_IS_SYMLINK "std:is_symlink" /* boolean */ +#define G_FILE_ATTRIBUTE_STD_IS_VIRTUAL "std:is_virtual" /* boolean */ +#define G_FILE_ATTRIBUTE_STD_NAME "std:name" /* byte string */ +#define G_FILE_ATTRIBUTE_STD_DISPLAY_NAME "std:display_name" /* string */ +#define G_FILE_ATTRIBUTE_STD_EDIT_NAME "std:edit_name" /* string */ +#define G_FILE_ATTRIBUTE_STD_COPY_NAME "std:copy_name" /* string */ +#define G_FILE_ATTRIBUTE_STD_ICON "std:icon" /* object (GIcon) */ +#define G_FILE_ATTRIBUTE_STD_CONTENT_TYPE "std:content_type" /* string */ +#define G_FILE_ATTRIBUTE_STD_FAST_CONTENT_TYPE "std:fast_content_type" /* string */ +#define G_FILE_ATTRIBUTE_STD_SIZE "std:size" /* uint64 */ +#define G_FILE_ATTRIBUTE_STD_SYMLINK_TARGET "std:symlink_target" /* byte string */ +#define G_FILE_ATTRIBUTE_STD_TARGET_URI "std:target_uri" /* string */ +#define G_FILE_ATTRIBUTE_STD_SORT_ORDER "std:sort_order" /* int32 */ + +/* Entity tags, used to avoid missing updates on save */ + +#define G_FILE_ATTRIBUTE_ETAG_VALUE "etag:value" /* string */ + +/* File identifier, for e.g. avoiding loops when doing recursive directory scanning */ + +#define G_FILE_ATTRIBUTE_ID_FILE "id:file" /* string */ +#define G_FILE_ATTRIBUTE_ID_FS "id:fs" /* string */ + +/* Calculated Access Rights for current user */ + +#define G_FILE_ATTRIBUTE_ACCESS_CAN_READ "access:can_read" /* boolean */ +#define G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE "access:can_write" /* boolean */ +#define G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE "access:can_execute" /* boolean */ +#define G_FILE_ATTRIBUTE_ACCESS_CAN_DELETE "access:can_delete" /* boolean */ +#define G_FILE_ATTRIBUTE_ACCESS_CAN_TRASH "access:can_trash" /* boolean */ +#define G_FILE_ATTRIBUTE_ACCESS_CAN_RENAME "access:can_rename" /* boolean */ +/* TODO: Should we have special version for directories? can_enumerate, etc */ + +/* Mountable attributes */ + +#define G_FILE_ATTRIBUTE_MOUNTABLE_CAN_MOUNT "mountable:can_mount" /* boolean */ +#define G_FILE_ATTRIBUTE_MOUNTABLE_CAN_UNMOUNT "mountable:can_unmount" /* boolean */ +#define G_FILE_ATTRIBUTE_MOUNTABLE_CAN_EJECT "mountable:can_eject" /* boolean */ +#define G_FILE_ATTRIBUTE_MOUNTABLE_UNIX_DEVICE "mountable:unix_device" /* uint32 */ +#define G_FILE_ATTRIBUTE_MOUNTABLE_HAL_UDI "mountable:hal_udi" /* string */ + +/* Time attributes */ + + /* The last time the file content or an attribute was modified */ +#define G_FILE_ATTRIBUTE_TIME_MODIFIED "time:modified" /* uint64 */ +#define G_FILE_ATTRIBUTE_TIME_MODIFIED_USEC "time:modified_usec" /* uint32 */ + /* The last time the file was read */ +#define G_FILE_ATTRIBUTE_TIME_ACCESS "time:access" /* uint64 */ +#define G_FILE_ATTRIBUTE_TIME_ACCESS_USEC "time:access_usec" /* uint32 */ + /* The last time a file attribute was changed (e.g. unix ctime) */ +#define G_FILE_ATTRIBUTE_TIME_CHANGED "time:changed" /* uint64 */ +#define G_FILE_ATTRIBUTE_TIME_CHANGED_USEC "time:changed_usec" /* uint32 */ + /* When the file was originally created (e.g. ntfs ctime) */ +#define G_FILE_ATTRIBUTE_TIME_CREATED "time:created" /* uint64 */ +#define G_FILE_ATTRIBUTE_TIME_CREATED_USEC "time:created_usec" /* uint32 */ + +/* Unix specific attributes */ + +#define G_FILE_ATTRIBUTE_UNIX_DEVICE "unix:device" /* uint32 */ +#define G_FILE_ATTRIBUTE_UNIX_INODE "unix:inode" /* uint64 */ +#define G_FILE_ATTRIBUTE_UNIX_MODE "unix:mode" /* uint32 */ +#define G_FILE_ATTRIBUTE_UNIX_NLINK "unix:nlink" /* uint32 */ +#define G_FILE_ATTRIBUTE_UNIX_UID "unix:uid" /* uint32 */ +#define G_FILE_ATTRIBUTE_UNIX_GID "unix:gid" /* uint32 */ +#define G_FILE_ATTRIBUTE_UNIX_RDEV "unix:rdev" /* uint32 */ +#define G_FILE_ATTRIBUTE_UNIX_BLOCK_SIZE "unix:block_size" /* uint32 */ +#define G_FILE_ATTRIBUTE_UNIX_BLOCKS "unix:blocks" /* uint64 */ +#define G_FILE_ATTRIBUTE_UNIX_IS_MOUNTPOINT "unix:is_mountpoint" /* boolean */ + +/* DOS specific attributes */ + +#define G_FILE_ATTRIBUTE_DOS_IS_ARCHIVE "dos:is_archive" /* boolean */ +#define G_FILE_ATTRIBUTE_DOS_IS_SYSTEM "dos:is_system" /* boolean */ + +/* Owner attributes */ + +#define G_FILE_ATTRIBUTE_OWNER_USER "owner:user" /* string */ +#define G_FILE_ATTRIBUTE_OWNER_USER_REAL "owner:user_real" /* string */ +#define G_FILE_ATTRIBUTE_OWNER_GROUP "owner:group" /* string */ + +/* Thumbnails */ + +#define G_FILE_ATTRIBUTE_THUMBNAIL_PATH "thumbnail:path" /* bytestring */ +#define G_FILE_ATTRIBUTE_THUMBNAILING_FAILED "thumbnail:failed" /* bytestring */ + +/* File system info (for g_file_get_filesystem_info) */ + +#define G_FILE_ATTRIBUTE_FS_SIZE "fs:size" /* uint64 */ +#define G_FILE_ATTRIBUTE_FS_FREE "fs:free" /* uint64 */ +#define G_FILE_ATTRIBUTE_FS_TYPE "fs:type" /* string */ +#define G_FILE_ATTRIBUTE_FS_READONLY "fs:readonly" /* boolean */ + +#define G_FILE_ATTRIBUTE_GVFS_BACKEND "gvfs:backend" /* string */ + +GType g_file_info_get_type (void) G_GNUC_CONST; + +GFileInfo * g_file_info_new (void); +GFileInfo * g_file_info_dup (GFileInfo *other); +void g_file_info_copy_into (GFileInfo *src_info, + GFileInfo *dest_info); +gboolean g_file_info_has_attribute (GFileInfo *info, + const char *attribute); +char ** g_file_info_list_attributes (GFileInfo *info, + const char *name_space); +GFileAttributeType g_file_info_get_attribute_type (GFileInfo *info, + const char *attribute); +void g_file_info_remove_attribute (GFileInfo *info, + const char *attribute); +GFileAttributeValue * g_file_info_get_attribute (GFileInfo *info, + const char *attribute); +const char * g_file_info_get_attribute_string (GFileInfo *info, + const char *attribute); +const char * g_file_info_get_attribute_byte_string (GFileInfo *info, + const char *attribute); +gboolean g_file_info_get_attribute_boolean (GFileInfo *info, + const char *attribute); +guint32 g_file_info_get_attribute_uint32 (GFileInfo *info, + const char *attribute); +gint32 g_file_info_get_attribute_int32 (GFileInfo *info, + const char *attribute); +guint64 g_file_info_get_attribute_uint64 (GFileInfo *info, + const char *attribute); +gint64 g_file_info_get_attribute_int64 (GFileInfo *info, + const char *attribute); +GObject * g_file_info_get_attribute_object (GFileInfo *info, + const char *attribute); + +void g_file_info_set_attribute (GFileInfo *info, + const char *attribute, + const GFileAttributeValue *attr_value); +void g_file_info_set_attribute_string (GFileInfo *info, + const char *attribute, + const char *attr_value); +void g_file_info_set_attribute_byte_string (GFileInfo *info, + const char *attribute, + const char *attr_value); +void g_file_info_set_attribute_boolean (GFileInfo *info, + const char *attribute, + gboolean attr_value); +void g_file_info_set_attribute_uint32 (GFileInfo *info, + const char *attribute, + guint32 attr_value); +void g_file_info_set_attribute_int32 (GFileInfo *info, + const char *attribute, + gint32 attr_value); +void g_file_info_set_attribute_uint64 (GFileInfo *info, + const char *attribute, + guint64 attr_value); +void g_file_info_set_attribute_int64 (GFileInfo *info, + const char *attribute, + gint64 attr_value); +void g_file_info_set_attribute_object (GFileInfo *info, + const char *attribute, + GObject *attr_value); + +void g_file_info_clear_status (GFileInfo *info); + +/* Helper getters: */ +GFileType g_file_info_get_file_type (GFileInfo *info); +gboolean g_file_info_get_is_hidden (GFileInfo *info); +gboolean g_file_info_get_is_backup (GFileInfo *info); +gboolean g_file_info_get_is_symlink (GFileInfo *info); +const char * g_file_info_get_name (GFileInfo *info); +const char * g_file_info_get_display_name (GFileInfo *info); +const char * g_file_info_get_edit_name (GFileInfo *info); +GIcon * g_file_info_get_icon (GFileInfo *info); +const char * g_file_info_get_content_type (GFileInfo *info); +goffset g_file_info_get_size (GFileInfo *info); +void g_file_info_get_modification_time (GFileInfo *info, + GTimeVal *result); +const char * g_file_info_get_symlink_target (GFileInfo *info); +const char * g_file_info_get_etag (GFileInfo *info); +gint32 g_file_info_get_sort_order (GFileInfo *info); + +void g_file_info_set_attribute_mask (GFileInfo *info, + GFileAttributeMatcher *mask); +void g_file_info_unset_attribute_mask (GFileInfo *info); + +/* Helper setters: */ +void g_file_info_set_file_type (GFileInfo *info, + GFileType type); +void g_file_info_set_is_hidden (GFileInfo *info, + gboolean is_hidden); +void g_file_info_set_is_symlink (GFileInfo *info, + gboolean is_symlink); +void g_file_info_set_name (GFileInfo *info, + const char *name); +void g_file_info_set_display_name (GFileInfo *info, + const char *display_name); +void g_file_info_set_edit_name (GFileInfo *info, + const char *edit_name); +void g_file_info_set_icon (GFileInfo *info, + GIcon *icon); +void g_file_info_set_content_type (GFileInfo *info, + const char *content_type); +void g_file_info_set_size (GFileInfo *info, + goffset size); +void g_file_info_set_modification_time (GFileInfo *info, + GTimeVal *mtime); +void g_file_info_set_symlink_target (GFileInfo *info, + const char *symlink_target); +void g_file_info_set_sort_order (GFileInfo *info, + gint32 sort_order); + +/* Helper functions for attributes: */ +/* TODO: Move this to glib when merging */ +char *g_format_file_size_for_display (goffset size); + +GFileAttributeMatcher *g_file_attribute_matcher_new (const char *attributes); +GFileAttributeMatcher *g_file_attribute_matcher_ref (GFileAttributeMatcher *matcher); +void g_file_attribute_matcher_unref (GFileAttributeMatcher *matcher); +gboolean g_file_attribute_matcher_matches (GFileAttributeMatcher *matcher, + const char *attribute); +gboolean g_file_attribute_matcher_matches_only (GFileAttributeMatcher *matcher, + const char *attribute); +gboolean g_file_attribute_matcher_enumerate_namespace (GFileAttributeMatcher *matcher, + const char *ns); +const char * g_file_attribute_matcher_enumerate_next (GFileAttributeMatcher *matcher); + +G_END_DECLS + + +#endif /* __G_FILE_INFO_H__ */ diff --git a/gio/gfileinputstream.c b/gio/gfileinputstream.c new file mode 100644 index 000000000..24c8a61fc --- /dev/null +++ b/gio/gfileinputstream.c @@ -0,0 +1,481 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#include <config.h> + +#include <glib.h> +#include <gfileinputstream.h> +#include <gseekable.h> +#include "gsimpleasyncresult.h" +#include "glibintl.h" + +static void g_file_input_stream_seekable_iface_init (GSeekableIface *iface); +static goffset g_file_input_stream_seekable_tell (GSeekable *seekable); +static gboolean g_file_input_stream_seekable_can_seek (GSeekable *seekable); +static gboolean g_file_input_stream_seekable_seek (GSeekable *seekable, + goffset offset, + GSeekType type, + GCancellable *cancellable, + GError **error); +static gboolean g_file_input_stream_seekable_can_truncate (GSeekable *seekable); +static gboolean g_file_input_stream_seekable_truncate (GSeekable *seekable, + goffset offset, + GCancellable *cancellable, + GError **error); +static void g_file_input_stream_real_query_info_async (GFileInputStream *stream, + char *attributes, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +static GFileInfo *g_file_input_stream_real_query_info_finish (GFileInputStream *stream, + GAsyncResult *result, + GError **error); + + +G_DEFINE_TYPE_WITH_CODE (GFileInputStream, g_file_input_stream, G_TYPE_INPUT_STREAM, + G_IMPLEMENT_INTERFACE (G_TYPE_SEEKABLE, + g_file_input_stream_seekable_iface_init)) + +struct _GFileInputStreamPrivate { + GAsyncReadyCallback outstanding_callback; +}; + +static void +g_file_input_stream_class_init (GFileInputStreamClass *klass) +{ + g_type_class_add_private (klass, sizeof (GFileInputStreamPrivate)); + + klass->query_info_async = g_file_input_stream_real_query_info_async; + klass->query_info_finish = g_file_input_stream_real_query_info_finish; +} + +static void +g_file_input_stream_seekable_iface_init (GSeekableIface *iface) +{ + iface->tell = g_file_input_stream_seekable_tell; + iface->can_seek = g_file_input_stream_seekable_can_seek; + iface->seek = g_file_input_stream_seekable_seek; + iface->can_truncate = g_file_input_stream_seekable_can_truncate; + iface->truncate = g_file_input_stream_seekable_truncate; +} + +static void +g_file_input_stream_init (GFileInputStream *stream) +{ + stream->priv = G_TYPE_INSTANCE_GET_PRIVATE (stream, + G_TYPE_FILE_INPUT_STREAM, + GFileInputStreamPrivate); +} + +/** + * g_file_input_stream_query_info: + * @stream: + * @attributes: + * @cancellable: optional #GCancellable object, %NULL to ignore. + * @error: a #GError location to store the error occuring, or %NULL to + * ignore. + * Returns: + **/ +GFileInfo * +g_file_input_stream_query_info (GFileInputStream *stream, + char *attributes, + GCancellable *cancellable, + GError **error) +{ + GFileInputStreamClass *class; + GInputStream *input_stream; + GFileInfo *info; + + g_return_val_if_fail (G_IS_FILE_INPUT_STREAM (stream), NULL); + + input_stream = G_INPUT_STREAM (stream); + + if (g_input_stream_is_closed (input_stream)) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_CLOSED, + _("Stream is already closed")); + return NULL; + } + + if (g_input_stream_has_pending (input_stream)) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_PENDING, + _("Stream has outstanding operation")); + return NULL; + } + + info = NULL; + + g_input_stream_set_pending (input_stream, TRUE); + + if (cancellable) + g_push_current_cancellable (cancellable); + + class = G_FILE_INPUT_STREAM_GET_CLASS (stream); + if (class->query_info) + info = class->query_info (stream, attributes, cancellable, error); + else + g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, + _("Stream doesn't support query_info")); + + if (cancellable) + g_pop_current_cancellable (cancellable); + + g_input_stream_set_pending (input_stream, FALSE); + + return info; +} + +static void +async_ready_callback_wrapper (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + GFileInputStream *stream = G_FILE_INPUT_STREAM (source_object); + + g_input_stream_set_pending (G_INPUT_STREAM (stream), FALSE); + if (stream->priv->outstanding_callback) + (*stream->priv->outstanding_callback) (source_object, res, user_data); + g_object_unref (stream); +} + +/** + * g_file_input_stream_query_info_async: + * @stream: + * @attributes: + * @io_priority: the io priority of the request. + * @cancellable: optional #GCancellable object, %NULL to ignore. @callback: + * @user_data: + * + **/ +void +g_file_input_stream_query_info_async (GFileInputStream *stream, + char *attributes, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GFileInputStreamClass *klass; + GInputStream *input_stream; + + g_return_if_fail (G_IS_FILE_INPUT_STREAM (stream)); + + input_stream = G_INPUT_STREAM (stream); + + if (g_input_stream_is_closed (input_stream)) + { + g_simple_async_report_error_in_idle (G_OBJECT (stream), + callback, + user_data, + G_IO_ERROR, G_IO_ERROR_CLOSED, + _("Stream is already closed")); + return; + } + + if (g_input_stream_has_pending (input_stream)) + { + g_simple_async_report_error_in_idle (G_OBJECT (stream), + callback, + user_data, + G_IO_ERROR, G_IO_ERROR_PENDING, + _("Stream has outstanding operation")); + return; + } + + klass = G_FILE_INPUT_STREAM_GET_CLASS (stream); + + g_input_stream_set_pending (input_stream, TRUE); + stream->priv->outstanding_callback = callback; + g_object_ref (stream); + klass->query_info_async (stream, attributes, io_priority, cancellable, + async_ready_callback_wrapper, user_data); +} + +/** + * g_file_input_stream_query_info_finish: + * @stream: + * @result: + * @error: a #GError location to store the error occuring, or %NULL to + * ignore. + * Returns: #GFileInfo. + **/ +GFileInfo * +g_file_input_stream_query_info_finish (GFileInputStream *stream, + GAsyncResult *result, + GError **error) +{ + GSimpleAsyncResult *simple; + GFileInputStreamClass *class; + + g_return_val_if_fail (G_IS_FILE_INPUT_STREAM (stream), NULL); + g_return_val_if_fail (G_IS_ASYNC_RESULT (result), NULL); + + if (G_IS_SIMPLE_ASYNC_RESULT (result)) + { + simple = G_SIMPLE_ASYNC_RESULT (result); + if (g_simple_async_result_propagate_error (simple, error)) + return NULL; + } + + class = G_FILE_INPUT_STREAM_GET_CLASS (stream); + return class->query_info_finish (stream, result, error); +} + +/** + * g_file_input_stream_tell: + * @stream: + * + * Returns: + **/ +goffset +g_file_input_stream_tell (GFileInputStream *stream) +{ + GFileInputStreamClass *class; + goffset offset; + + g_return_val_if_fail (G_IS_FILE_INPUT_STREAM (stream), 0); + + class = G_FILE_INPUT_STREAM_GET_CLASS (stream); + + offset = 0; + if (class->tell) + offset = class->tell (stream); + + return offset; +} + +static goffset +g_file_input_stream_seekable_tell (GSeekable *seekable) +{ + return g_file_input_stream_tell (G_FILE_INPUT_STREAM (seekable)); +} + +/** + * g_file_input_stream_can_seek: + * @stream: + * + * Returns: %TRUE if stream can be seeked. %FALSE otherwise. + **/ +gboolean +g_file_input_stream_can_seek (GFileInputStream *stream) +{ + GFileInputStreamClass *class; + gboolean can_seek; + + g_return_val_if_fail (G_IS_FILE_INPUT_STREAM (stream), FALSE); + + class = G_FILE_INPUT_STREAM_GET_CLASS (stream); + + can_seek = FALSE; + if (class->seek) + { + can_seek = TRUE; + if (class->can_seek) + can_seek = class->can_seek (stream); + } + + return can_seek; +} + +static gboolean +g_file_input_stream_seekable_can_seek (GSeekable *seekable) +{ + return g_file_input_stream_can_seek (G_FILE_INPUT_STREAM (seekable)); +} + +/** + * g_file_input_stream_seek: + * @stream: + * @offset: + * @type: + * @cancellable: optional #GCancellable object, %NULL to ignore. + * @error: a #GError location to store the error occuring, or %NULL to + * ignore. + * Returns: + **/ +gboolean +g_file_input_stream_seek (GFileInputStream *stream, + goffset offset, + GSeekType type, + GCancellable *cancellable, + GError **error) +{ + GFileInputStreamClass *class; + GInputStream *input_stream; + gboolean res; + + g_return_val_if_fail (G_IS_FILE_INPUT_STREAM (stream), FALSE); + + input_stream = G_INPUT_STREAM (stream); + class = G_FILE_INPUT_STREAM_GET_CLASS (stream); + + if (g_input_stream_is_closed (input_stream)) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_CLOSED, + _("Stream is already closed")); + return FALSE; + } + + if (g_input_stream_has_pending (input_stream)) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_PENDING, + _("Stream has outstanding operation")); + return FALSE; + } + + if (!class->seek) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, + _("Seek not supported on stream")); + return FALSE; + } + + g_input_stream_set_pending (input_stream, TRUE); + + if (cancellable) + g_push_current_cancellable (cancellable); + + res = class->seek (stream, offset, type, cancellable, error); + + if (cancellable) + g_pop_current_cancellable (cancellable); + + g_input_stream_set_pending (input_stream, FALSE); + + return res; +} + +static gboolean +g_file_input_stream_seekable_seek (GSeekable *seekable, + goffset offset, + GSeekType type, + GCancellable *cancellable, + GError **error) +{ + return g_file_input_stream_seek (G_FILE_INPUT_STREAM (seekable), + offset, type, cancellable, error); +} + +static gboolean +g_file_input_stream_seekable_can_truncate (GSeekable *seekable) +{ + return FALSE; +} + +static gboolean +g_file_input_stream_seekable_truncate (GSeekable *seekable, + goffset offset, + GCancellable *cancellable, + GError **error) +{ + g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, + _("Truncate not allowed on input stream")); + return FALSE; +} + +/******************************************** + * Default implementation of async ops * + ********************************************/ + +typedef struct { + char *attributes; + GFileInfo *info; +} QueryInfoAsyncData; + +static void +query_info_data_free (QueryInfoAsyncData *data) +{ + if (data->info) + g_object_unref (data->info); + g_free (data->attributes); + g_free (data); +} + +static void +query_info_async_thread (GSimpleAsyncResult *res, + GObject *object, + GCancellable *cancellable) +{ + GFileInputStreamClass *class; + GError *error = NULL; + QueryInfoAsyncData *data; + GFileInfo *info; + + data = g_simple_async_result_get_op_res_gpointer (res); + + info = NULL; + + class = G_FILE_INPUT_STREAM_GET_CLASS (object); + if (class->query_info) + info = class->query_info (G_FILE_INPUT_STREAM (object), data->attributes, cancellable, &error); + else + g_set_error (&error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, + _("Stream doesn't support query_info")); + + if (info == NULL) + { + g_simple_async_result_set_from_error (res, error); + g_error_free (error); + } + else + data->info = info; +} + +static void +g_file_input_stream_real_query_info_async (GFileInputStream *stream, + char *attributes, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GSimpleAsyncResult *res; + QueryInfoAsyncData *data; + + data = g_new0 (QueryInfoAsyncData, 1); + data->attributes = g_strdup (attributes); + + res = g_simple_async_result_new (G_OBJECT (stream), callback, user_data, g_file_input_stream_real_query_info_async); + g_simple_async_result_set_op_res_gpointer (res, data, (GDestroyNotify)query_info_data_free); + + g_simple_async_result_run_in_thread (res, query_info_async_thread, io_priority, cancellable); + g_object_unref (res); +} + +static GFileInfo * +g_file_input_stream_real_query_info_finish (GFileInputStream *stream, + GAsyncResult *res, + GError **error) +{ + GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (res); + QueryInfoAsyncData *data; + + g_assert (g_simple_async_result_get_source_tag (simple) == g_file_input_stream_real_query_info_async); + + data = g_simple_async_result_get_op_res_gpointer (simple); + if (data->info) + return g_object_ref (data->info); + + return NULL; +} diff --git a/gio/gfileinputstream.h b/gio/gfileinputstream.h new file mode 100644 index 000000000..ecf876634 --- /dev/null +++ b/gio/gfileinputstream.h @@ -0,0 +1,110 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#ifndef __G_FILE_INPUT_STREAM_H__ +#define __G_FILE_INPUT_STREAM_H__ + +#include <gio/ginputstream.h> +#include <gio/gfileinfo.h> + +G_BEGIN_DECLS + +#define G_TYPE_FILE_INPUT_STREAM (g_file_input_stream_get_type ()) +#define G_FILE_INPUT_STREAM(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_FILE_INPUT_STREAM, GFileInputStream)) +#define G_FILE_INPUT_STREAM_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), G_TYPE_FILE_INPUT_STREAM, GFileInputStreamClass)) +#define G_IS_FILE_INPUT_STREAM(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_FILE_INPUT_STREAM)) +#define G_IS_FILE_INPUT_STREAM_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_FILE_INPUT_STREAM)) +#define G_FILE_INPUT_STREAM_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_TYPE_FILE_INPUT_STREAM, GFileInputStreamClass)) + +typedef struct _GFileInputStream GFileInputStream; +typedef struct _GFileInputStreamClass GFileInputStreamClass; +typedef struct _GFileInputStreamPrivate GFileInputStreamPrivate; + +struct _GFileInputStream +{ + GInputStream parent; + + /*< private >*/ + GFileInputStreamPrivate *priv; +}; + +struct _GFileInputStreamClass +{ + GInputStreamClass parent_class; + + goffset (*tell) (GFileInputStream *stream); + gboolean (*can_seek) (GFileInputStream *stream); + gboolean (*seek) (GFileInputStream *stream, + goffset offset, + GSeekType type, + GCancellable *cancellable, + GError **error); + GFileInfo *(*query_info) (GFileInputStream *stream, + char *attributes, + GCancellable *cancellable, + GError **error); + void (*query_info_async) (GFileInputStream *stream, + char *attributes, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + GFileInfo *(*query_info_finish) (GFileInputStream *stream, + GAsyncResult *res, + GError **error); + + /* Padding for future expansion */ + void (*_g_reserved1) (void); + void (*_g_reserved2) (void); + void (*_g_reserved3) (void); + void (*_g_reserved4) (void); + void (*_g_reserved5) (void); +}; + +GType g_file_input_stream_get_type (void) G_GNUC_CONST; + +GFileInfo *g_file_input_stream_query_info (GFileInputStream *stream, + char *attributes, + GCancellable *cancellable, + GError **error); +void g_file_input_stream_query_info_async (GFileInputStream *stream, + char *attributes, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +GFileInfo *g_file_input_stream_query_info_finish (GFileInputStream *stream, + GAsyncResult *result, + GError **error); +goffset g_file_input_stream_tell (GFileInputStream *stream); +gboolean g_file_input_stream_can_seek (GFileInputStream *stream); +gboolean g_file_input_stream_seek (GFileInputStream *stream, + goffset offset, + GSeekType type, + GCancellable *cancellable, + GError **error); + + + +G_END_DECLS + +#endif /* __G_FILE_FILE_INPUT_STREAM_H__ */ diff --git a/gio/gfilemonitor.c b/gio/gfilemonitor.c new file mode 100644 index 000000000..ba9fa561b --- /dev/null +++ b/gio/gfilemonitor.c @@ -0,0 +1,380 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#include <config.h> +#include <string.h> + +#include "gfilemonitor.h" +#include "gio-marshal.h" +#include "gvfs.h" +#include "glibintl.h" + +enum { + CHANGED, + LAST_SIGNAL +}; + +G_DEFINE_ABSTRACT_TYPE (GFileMonitor, g_file_monitor, G_TYPE_OBJECT); + +struct _GFileMonitorPrivate { + gboolean cancelled; + int rate_limit_msec; + + /* Rate limiting change events */ + guint32 last_sent_change_time; /* Some monitonic clock in msecs */ + GFile *last_sent_change_file; + + guint send_delayed_change_timeout; + + /* Virtual CHANGES_DONE_HINT emission */ + GSource *virtual_changes_done_timeout; + GFile *virtual_changes_done_file; +}; + +#define DEFAULT_RATE_LIMIT_MSECS 800 +#define DEFAULT_VIRTUAL_CHANGES_DONE_DELAY_SECS 2 + +static guint signals[LAST_SIGNAL] = { 0 }; + +static void +g_file_monitor_finalize (GObject *object) +{ + GFileMonitor *monitor; + + monitor = G_FILE_MONITOR (object); + + if (monitor->priv->last_sent_change_file) + g_object_unref (monitor->priv->last_sent_change_file); + + if (monitor->priv->send_delayed_change_timeout != 0) + g_source_remove (monitor->priv->send_delayed_change_timeout); + + if (monitor->priv->virtual_changes_done_file) + g_object_unref (monitor->priv->virtual_changes_done_file); + + if (monitor->priv->virtual_changes_done_timeout) + g_source_destroy (monitor->priv->virtual_changes_done_timeout); + + if (G_OBJECT_CLASS (g_file_monitor_parent_class)->finalize) + (*G_OBJECT_CLASS (g_file_monitor_parent_class)->finalize) (object); +} + +static void +g_file_monitor_dispose (GObject *object) +{ + GFileMonitor *monitor; + + monitor = G_FILE_MONITOR (object); + + /* Make sure we cancel on last unref */ + if (!monitor->priv->cancelled) + g_file_monitor_cancel (monitor); + + if (G_OBJECT_CLASS (g_file_monitor_parent_class)->dispose) + (*G_OBJECT_CLASS (g_file_monitor_parent_class)->dispose) (object); +} + +static void +g_file_monitor_class_init (GFileMonitorClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + g_type_class_add_private (klass, sizeof (GFileMonitorPrivate)); + + gobject_class->finalize = g_file_monitor_finalize; + gobject_class->dispose = g_file_monitor_dispose; + + signals[CHANGED] = + g_signal_new (I_("changed"), + G_TYPE_FILE_MONITOR, + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GFileMonitorClass, changed), + NULL, NULL, + _gio_marshal_VOID__OBJECT_OBJECT_INT, + G_TYPE_NONE,3, + G_TYPE_FILE, + G_TYPE_FILE, + G_TYPE_INT); +} + +static void +g_file_monitor_init (GFileMonitor *monitor) +{ + monitor->priv = G_TYPE_INSTANCE_GET_PRIVATE (monitor, + G_TYPE_FILE_MONITOR, + GFileMonitorPrivate); + monitor->priv->rate_limit_msec = DEFAULT_RATE_LIMIT_MSECS; +} + +/** + * g_file_monitor_is_cancelled: + * @monitor: + * + * Returns: %TRUE if monitor is canceled. %FALSE otherwise. + **/ +gboolean +g_file_monitor_is_cancelled (GFileMonitor *monitor) +{ + g_return_val_if_fail (G_IS_FILE_MONITOR (monitor), FALSE); + + return monitor->priv->cancelled; +} + +/** + * g_file_monitor_cancel: + * @monitor: + * + * Returns: %TRUE if monitor was cancelled. + **/ +gboolean +g_file_monitor_cancel (GFileMonitor* monitor) +{ + GFileMonitorClass *klass; + + g_return_val_if_fail (G_IS_FILE_MONITOR (monitor), FALSE); + + if (monitor->priv->cancelled) + return TRUE; + + monitor->priv->cancelled = TRUE; + + klass = G_FILE_MONITOR_GET_CLASS (monitor); + return (* klass->cancel) (monitor); +} + +/** + * g_file_monitor_set_rate_limit: + * @monitor: a #GFileMonitor. + * @limit_msecs: a integer with the limit in milliseconds to + * poll for changes. + * + * Sets the rate limit to which the @monitor will poll for changes + * on the device. + * + **/ +void +g_file_monitor_set_rate_limit (GFileMonitor *monitor, + int limit_msecs) +{ + g_return_if_fail (G_IS_FILE_MONITOR (monitor)); + + monitor->priv->rate_limit_msec = limit_msecs; +} + +static guint32 +get_time_msecs (void) +{ + return g_thread_gettime() / (1000 * 1000); +} + +static guint32 +time_difference (guint32 from, guint32 to) +{ + if (from > to) + return 0; + return to - from; +} + +/* Change event rate limiting support: */ + +static void +update_last_sent_change (GFileMonitor *monitor, GFile *file, guint32 time_now) +{ + if (monitor->priv->last_sent_change_file != file) + { + if (monitor->priv->last_sent_change_file) + { + g_object_unref (monitor->priv->last_sent_change_file); + monitor->priv->last_sent_change_file = NULL; + } + if (file) + monitor->priv->last_sent_change_file = g_object_ref (file); + } + + monitor->priv->last_sent_change_time = time_now; +} + +static void +send_delayed_change_now (GFileMonitor *monitor) +{ + if (monitor->priv->send_delayed_change_timeout) + { + g_signal_emit (monitor, signals[CHANGED], 0, + monitor->priv->last_sent_change_file, NULL, + G_FILE_MONITOR_EVENT_CHANGED); + + g_source_remove (monitor->priv->send_delayed_change_timeout); + monitor->priv->send_delayed_change_timeout = 0; + + /* Same file, new last_sent time */ + monitor->priv->last_sent_change_time = get_time_msecs (); + } +} + +static gboolean +delayed_changed_event_timeout (gpointer data) +{ + GFileMonitor *monitor = data; + + send_delayed_change_now (monitor); + + return FALSE; +} + +static void +schedule_delayed_change (GFileMonitor *monitor, GFile *file, guint32 delay_msec) +{ + if (monitor->priv->send_delayed_change_timeout == 0) /* Only set the timeout once */ + { + monitor->priv->send_delayed_change_timeout = + g_timeout_add (delay_msec, delayed_changed_event_timeout, monitor); + } +} + +static void +cancel_delayed_change (GFileMonitor *monitor) +{ + if (monitor->priv->send_delayed_change_timeout != 0) + { + g_source_remove (monitor->priv->send_delayed_change_timeout); + monitor->priv->send_delayed_change_timeout = 0; + } +} + +/* Virtual changes_done_hint support: */ + +static void +send_virtual_changes_done_now (GFileMonitor *monitor) +{ + if (monitor->priv->virtual_changes_done_timeout) + { + g_signal_emit (monitor, signals[CHANGED], 0, + monitor->priv->virtual_changes_done_file, NULL, + G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT); + + g_source_destroy (monitor->priv->virtual_changes_done_timeout); + monitor->priv->virtual_changes_done_timeout = NULL; + + g_object_unref (monitor->priv->virtual_changes_done_file); + monitor->priv->virtual_changes_done_file = NULL; + } +} + +static gboolean +virtual_changes_done_timeout (gpointer data) +{ + GFileMonitor *monitor = data; + + send_virtual_changes_done_now (monitor); + + return FALSE; +} + +static void +schedule_virtual_change_done (GFileMonitor *monitor, GFile *file) +{ + GSource *source; + + source = g_timeout_source_new_seconds (DEFAULT_VIRTUAL_CHANGES_DONE_DELAY_SECS); + + g_source_set_callback (source, virtual_changes_done_timeout, monitor, NULL); + g_source_attach (source, NULL); + monitor->priv->virtual_changes_done_timeout = source; + monitor->priv->virtual_changes_done_file = g_object_ref (file); + g_source_unref (source); +} + +static void +cancel_virtual_changes_done (GFileMonitor *monitor) +{ + if (monitor->priv->virtual_changes_done_timeout) + { + g_source_destroy (monitor->priv->virtual_changes_done_timeout); + monitor->priv->virtual_changes_done_timeout = NULL; + + g_object_unref (monitor->priv->virtual_changes_done_file); + monitor->priv->virtual_changes_done_file = NULL; + } +} + +/** + * g_file_monitor_emit_event: + * @monitor: + * @file: + * @other_file: + * @event_type: + * + **/ +void +g_file_monitor_emit_event (GFileMonitor *monitor, + GFile *file, + GFile *other_file, + GFileMonitorEvent event_type) +{ + guint32 time_now, since_last; + gboolean emit_now; + + g_return_if_fail (G_IS_FILE_MONITOR (monitor)); + g_return_if_fail (G_IS_FILE (file)); + + if (event_type != G_FILE_MONITOR_EVENT_CHANGED) + { + send_delayed_change_now (monitor); + update_last_sent_change (monitor, NULL, 0); + if (event_type == G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT) + cancel_virtual_changes_done (monitor); + else + send_virtual_changes_done_now (monitor); + g_signal_emit (monitor, signals[CHANGED], 0, file, other_file, event_type); + } + else + { + time_now = get_time_msecs (); + emit_now = TRUE; + + if (monitor->priv->last_sent_change_file) + { + since_last = time_difference (monitor->priv->last_sent_change_time, time_now); + if (since_last < monitor->priv->rate_limit_msec) + { + /* We ignore this change, but arm a timer so that we can fire it later if we + don't get any other events (that kill this timeout) */ + emit_now = FALSE; + schedule_delayed_change (monitor, file, + monitor->priv->rate_limit_msec - since_last); + } + } + + if (emit_now) + { + g_signal_emit (monitor, signals[CHANGED], 0, file, other_file, event_type); + + cancel_delayed_change (monitor); + update_last_sent_change (monitor, file, time_now); + } + + /* Schedule a virtual change done. This is removed if we get a real one, and + postponed if we get more change events. */ + cancel_virtual_changes_done (monitor); + schedule_virtual_change_done (monitor, file); + } +} diff --git a/gio/gfilemonitor.h b/gio/gfilemonitor.h new file mode 100644 index 000000000..edb9f64be --- /dev/null +++ b/gio/gfilemonitor.h @@ -0,0 +1,97 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#ifndef __G_FILE_MONITOR_H__ +#define __G_FILE_MONITOR_H__ + +#include <glib-object.h> +#include <gio/gfile.h> + +G_BEGIN_DECLS + +#define G_TYPE_FILE_MONITOR (g_file_monitor_get_type ()) +#define G_FILE_MONITOR(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_FILE_MONITOR, GFileMonitor)) +#define G_FILE_MONITOR_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), G_TYPE_FILE_MONITOR, GFileMonitorClass)) +#define G_IS_FILE_MONITOR(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_FILE_MONITOR)) +#define G_IS_FILE_MONITOR_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_FILE_MONITOR)) +#define G_FILE_MONITOR_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_TYPE_FILE_MONITOR, GFileMonitorClass)) + +typedef enum { + G_FILE_MONITOR_EVENT_CHANGED, + G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT, + G_FILE_MONITOR_EVENT_DELETED, + G_FILE_MONITOR_EVENT_CREATED, + G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED, + G_FILE_MONITOR_EVENT_PRE_UNMOUNT, + G_FILE_MONITOR_EVENT_UNMOUNTED +} GFileMonitorEvent; + +typedef struct _GFileMonitorClass GFileMonitorClass; +typedef struct _GFileMonitorPrivate GFileMonitorPrivate; + +struct _GFileMonitor +{ + GObject parent; + + /*< private >*/ + GFileMonitorPrivate *priv; +}; + +struct _GFileMonitorClass +{ + GObjectClass parent_class; + + /* Signals */ + void (* changed) (GFileMonitor* monitor, + GFile* file, + GFile* other_file, + GFileMonitorEvent event_type); + + /* Virtual Table */ + gboolean (*cancel)(GFileMonitor* monitor); + + + /* Padding for future expansion */ + void (*_g_reserved1) (void); + void (*_g_reserved2) (void); + void (*_g_reserved3) (void); + void (*_g_reserved4) (void); + void (*_g_reserved5) (void); +}; + +GType g_file_monitor_get_type (void) G_GNUC_CONST; + +gboolean g_file_monitor_cancel (GFileMonitor *monitor); +gboolean g_file_monitor_is_cancelled (GFileMonitor *monitor); +void g_file_monitor_set_rate_limit (GFileMonitor *monitor, + int limit_msecs); + + +/* For implementations */ +void g_file_monitor_emit_event (GFileMonitor *monitor, + GFile *file, + GFile *other_file, + GFileMonitorEvent event_type); + +G_END_DECLS + +#endif /* __G_FILE_MONITOR_H__ */ diff --git a/gio/gfilenamecompleter.c b/gio/gfilenamecompleter.c new file mode 100644 index 000000000..c8d85d817 --- /dev/null +++ b/gio/gfilenamecompleter.c @@ -0,0 +1,489 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#include <config.h> +#include "gfilenamecompleter.h" +#include "gurifuncs.h" +#include "gfile.h" +#include <string.h> +#include "glibintl.h" + +enum { + GOT_COMPLETION_DATA, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL] = { 0 }; + +typedef struct { + GFilenameCompleter *completer; + GFileEnumerator *enumerator; + GCancellable *cancellable; + gboolean should_escape; + GFile *dir; + GList *basenames; + gboolean dirs_only; +} LoadBasenamesData; + +struct _GFilenameCompleter { + GObject parent; + + GFile *basenames_dir; + gboolean basenames_are_escaped; + GList *basenames; + gboolean dirs_only; + + LoadBasenamesData *basename_loader; +}; + +G_DEFINE_TYPE (GFilenameCompleter, g_filename_completer, G_TYPE_OBJECT); + +static void cancel_load_basenames (GFilenameCompleter *completer); + +static void +g_filename_completer_finalize (GObject *object) +{ + GFilenameCompleter *completer; + + completer = G_FILENAME_COMPLETER (object); + + cancel_load_basenames (completer); + + if (completer->basenames_dir) + g_object_unref (completer->basenames_dir); + + g_list_foreach (completer->basenames, (GFunc)g_free, NULL); + g_list_free (completer->basenames); + + if (G_OBJECT_CLASS (g_filename_completer_parent_class)->finalize) + (*G_OBJECT_CLASS (g_filename_completer_parent_class)->finalize) (object); +} + +static void +g_filename_completer_class_init (GFilenameCompleterClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->finalize = g_filename_completer_finalize; + + signals[GOT_COMPLETION_DATA] = g_signal_new (I_("got_completion_data"), + G_TYPE_FILENAME_COMPLETER, + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GFilenameCompleterClass, got_completion_data), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); +} + +static void +g_filename_completer_init (GFilenameCompleter *completer) +{ +} + +/** + * g_filename_completer_new: + * + * Returns: a new #GFilenameCompleter. + **/ +GFilenameCompleter * +g_filename_completer_new (void) +{ + return g_object_new (G_TYPE_FILENAME_COMPLETER, NULL); +} + +static char * +longest_common_prefix (char *a, char *b) +{ + char *start; + + start = a; + + while (g_utf8_get_char (a) == g_utf8_get_char (b)) + { + a = g_utf8_next_char (a); + b = g_utf8_next_char (b); + } + + return g_strndup (start, a - start); +} + +static void +load_basenames_data_free (LoadBasenamesData *data) +{ + if (data->enumerator) + g_object_unref (data->enumerator); + + g_object_unref (data->cancellable); + g_object_unref (data->dir); + + g_list_foreach (data->basenames, (GFunc)g_free, NULL); + g_list_free (data->basenames); + + g_free (data); +} + +static void +got_more_files (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + LoadBasenamesData *data = user_data; + GList *infos, *l; + GFileInfo *info; + const char *name; + gboolean append_slash; + char *t; + char *basename; + + if (data->completer == NULL) + { + /* Was cancelled */ + load_basenames_data_free (data); + return; + } + + infos = g_file_enumerator_next_files_finish (data->enumerator, res, NULL); + + for (l = infos; l != NULL; l = l->next) + { + info = l->data; + + if (data->dirs_only && + g_file_info_get_file_type (info) != G_FILE_TYPE_DIRECTORY) + { + g_object_unref (info); + continue; + } + + append_slash = g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY; + name = g_file_info_get_name (info); + if (name == NULL) + { + g_object_unref (info); + continue; + } + + + if (data->should_escape) + basename = g_uri_escape_string (name, + G_URI_RESERVED_CHARS_ALLOWED_IN_PATH, + TRUE); + else + /* If not should_escape, must be a local filename, convert to utf8 */ + basename = g_filename_to_utf8 (name, -1, NULL, NULL, NULL); + + if (basename) + { + if (append_slash) + { + t = basename; + basename = g_strconcat (basename, "/", NULL); + g_free (t); + } + + data->basenames = g_list_prepend (data->basenames, basename); + } + + g_object_unref (info); + } + + g_list_free (infos); + + if (infos) + { + /* Not last, get more files */ + g_file_enumerator_next_files_async (data->enumerator, + 100, + 0, + data->cancellable, + got_more_files, data); + } + else + { + data->completer->basename_loader = NULL; + + if (data->completer->basenames_dir) + g_object_unref (data->completer->basenames_dir); + g_list_foreach (data->completer->basenames, (GFunc)g_free, NULL); + g_list_free (data->completer->basenames); + + data->completer->basenames_dir = g_object_ref (data->dir); + data->completer->basenames = data->basenames; + data->completer->basenames_are_escaped = data->should_escape; + data->basenames = NULL; + + g_file_enumerator_close_async (data->enumerator, 0, NULL, NULL, NULL); + + g_signal_emit (data->completer, signals[GOT_COMPLETION_DATA], 0); + load_basenames_data_free (data); + } +} + + +static void +got_enum (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + LoadBasenamesData *data = user_data; + + if (data->completer == NULL) + { + /* Was cancelled */ + load_basenames_data_free (data); + return; + } + + data->enumerator = g_file_enumerate_children_finish (G_FILE (source_object), res, NULL); + + if (data->enumerator == NULL) + { + data->completer->basename_loader = NULL; + + if (data->completer->basenames_dir) + g_object_unref (data->completer->basenames_dir); + g_list_foreach (data->completer->basenames, (GFunc)g_free, NULL); + g_list_free (data->completer->basenames); + + /* Mark uptodate with no basenames */ + data->completer->basenames_dir = g_object_ref (data->dir); + data->completer->basenames = NULL; + data->completer->basenames_are_escaped = data->should_escape; + + load_basenames_data_free (data); + return; + } + + g_file_enumerator_next_files_async (data->enumerator, + 100, + 0, + data->cancellable, + got_more_files, data); +} + +static void +schedule_load_basenames (GFilenameCompleter *completer, + GFile *dir, + gboolean should_escape) +{ + LoadBasenamesData *data; + + cancel_load_basenames (completer); + + data = g_new0 (LoadBasenamesData, 1); + data->completer = completer; + data->cancellable = g_cancellable_new (); + data->dir = g_object_ref (dir); + data->should_escape = should_escape; + data->dirs_only = completer->dirs_only; + + completer->basename_loader = data; + + g_file_enumerate_children_async (dir, + G_FILE_ATTRIBUTE_STD_NAME "," G_FILE_ATTRIBUTE_STD_TYPE, + 0, 0, + data->cancellable, + got_enum, data); +} + +static void +cancel_load_basenames (GFilenameCompleter *completer) +{ + LoadBasenamesData *loader; + + if (completer->basename_loader) + { + loader = completer->basename_loader; + loader->completer = NULL; + + g_cancellable_cancel (loader->cancellable); + + completer->basename_loader = NULL; + } +} + + +/* Returns a list of possible matches and the basename to use for it */ +static GList * +init_completion (GFilenameCompleter *completer, + const char *initial_text, + char **basename_out) +{ + gboolean should_escape; + GFile *file, *parent; + char *basename; + char *t; + int len; + + *basename_out = NULL; + + should_escape = ! (g_path_is_absolute (initial_text) || *initial_text == '~'); + + len = strlen (initial_text); + + if (len > 0 && + initial_text[len - 1] == '/') + return NULL; + + file = g_file_parse_name (initial_text); + parent = g_file_get_parent (file); + if (parent == NULL) + { + g_object_unref (file); + return NULL; + } + + if (completer->basenames_dir == NULL || + completer->basenames_are_escaped != should_escape || + !g_file_equal (parent, completer->basenames_dir)) + { + schedule_load_basenames (completer, parent, should_escape); + g_object_unref (file); + return NULL; + } + + basename = g_file_get_basename (file); + if (should_escape) + { + t = basename; + basename = g_uri_escape_string (basename, G_URI_RESERVED_CHARS_ALLOWED_IN_PATH, TRUE); + g_free (t); + } + else + { + t = basename; + basename = g_filename_to_utf8 (basename, -1, NULL, NULL, NULL); + g_free (t); + + if (basename == NULL) + return NULL; + } + + *basename_out = basename; + + return completer->basenames; +} + +/** + * g_filename_completer_get_completion_suffix: + * @completer: the filename completer. + * @initial_text: text to be completed. + * + * Returns: a completed string. This string is not owned by GIO, so + * remember to g_free() it when finished. + **/ +char * +g_filename_completer_get_completion_suffix (GFilenameCompleter *completer, + const char *initial_text) +{ + GList *possible_matches, *l; + char *prefix; + char *suffix; + char *possible_match; + char *lcp; + + g_return_val_if_fail (G_IS_FILENAME_COMPLETER (completer), NULL); + g_return_val_if_fail (initial_text != NULL, NULL); + + possible_matches = init_completion (completer, initial_text, &prefix); + + suffix = NULL; + + for (l = possible_matches; l != NULL; l = l->next) + { + possible_match = l->data; + + if (g_str_has_prefix (possible_match, prefix)) + { + if (suffix == NULL) + suffix = g_strdup (possible_match + strlen (prefix)); + else + { + lcp = longest_common_prefix (suffix, + possible_match + strlen (prefix)); + g_free (suffix); + suffix = lcp; + + if (*suffix == 0) + break; + } + } + } + + g_free (prefix); + + return suffix; +} + +/** + * g_filename_completer_get_completions: + * @completer: the filename completer. + * @initial_text: text to be completed. + * + * Returns: array of strings with possible completions for @initial_text. + * This array must be freed by g_strfreev() when finished. + **/ +char ** +g_filename_completer_get_completions (GFilenameCompleter *completer, + const char *initial_text) +{ + GList *possible_matches, *l; + char *prefix; + char *possible_match; + GPtrArray *res; + + g_return_val_if_fail (G_IS_FILENAME_COMPLETER (completer), NULL); + g_return_val_if_fail (initial_text != NULL, NULL); + + possible_matches = init_completion (completer, initial_text, &prefix); + + res = g_ptr_array_new (); + for (l = possible_matches; l != NULL; l = l->next) + { + possible_match = l->data; + + if (g_str_has_prefix (possible_match, prefix)) + g_ptr_array_add (res, + g_strconcat (initial_text, possible_match + strlen (prefix), NULL)); + } + + g_free (prefix); + + return (char**)g_ptr_array_free (res, FALSE); +} + +/** + * g_filename_completer_set_dirs_only: + * @completer: the filename completer. + * @dirs_only: + * + * If @dirs_only is %TRUE, @completer will only + * complete directory names, and not file names. + **/ +void +g_filename_completer_set_dirs_only (GFilenameCompleter *completer, + gboolean dirs_only) +{ + g_return_if_fail (G_IS_FILENAME_COMPLETER (completer)); + + completer->dirs_only = dirs_only; +} diff --git a/gio/gfilenamecompleter.h b/gio/gfilenamecompleter.h new file mode 100644 index 000000000..cf4cae198 --- /dev/null +++ b/gio/gfilenamecompleter.h @@ -0,0 +1,66 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#ifndef __G_FILENAME_COMPLETER_H__ +#define __G_FILENAME_COMPLETER_H__ + +#include <glib-object.h> + +G_BEGIN_DECLS + +#define G_TYPE_FILENAME_COMPLETER (g_filename_completer_get_type ()) +#define G_FILENAME_COMPLETER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_FILENAME_COMPLETER, GFilenameCompleter)) +#define G_FILENAME_COMPLETER_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), G_TYPE_FILENAME_COMPLETER, GFilenameCompleterClass)) +#define G_FILENAME_COMPLETER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_TYPE_FILENAME_COMPLETER, GFilenameCompleterClass)) +#define G_IS_FILENAME_COMPLETER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_FILENAME_COMPLETER)) +#define G_IS_FILENAME_COMPLETER_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_FILENAME_COMPLETER)) + +typedef struct _GFilenameCompleter GFilenameCompleter; +typedef struct _GFilenameCompleterClass GFilenameCompleterClass; + +struct _GFilenameCompleterClass { + GObjectClass parent_class; + + /*< public >*/ + /* signals */ + void (* got_completion_data) (GFilenameCompleter *filename_completer); + + /* Padding for future expansion */ + void (*_g_reserved1) (void); + void (*_g_reserved2) (void); + void (*_g_reserved3) (void); +}; + +GType g_filename_completer_get_type (void) G_GNUC_CONST; + +GFilenameCompleter *g_filename_completer_new (void); + +char * g_filename_completer_get_completion_suffix (GFilenameCompleter *completer, + const char *initial_text); +char ** g_filename_completer_get_completions (GFilenameCompleter *completer, + const char *initial_text); +void g_filename_completer_set_dirs_only (GFilenameCompleter *completer, + gboolean dirs_only); + +G_END_DECLS + +#endif /* __G_FILENAME_COMPLETER_H__ */ diff --git a/gio/gfileoutputstream.c b/gio/gfileoutputstream.c new file mode 100644 index 000000000..40914d671 --- /dev/null +++ b/gio/gfileoutputstream.c @@ -0,0 +1,613 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#include <config.h> + +#include <glib.h> +#include <gfileoutputstream.h> +#include <gseekable.h> +#include "gsimpleasyncresult.h" +#include "glibintl.h" + +static void g_file_output_stream_seekable_iface_init (GSeekableIface *iface); +static goffset g_file_output_stream_seekable_tell (GSeekable *seekable); +static gboolean g_file_output_stream_seekable_can_seek (GSeekable *seekable); +static gboolean g_file_output_stream_seekable_seek (GSeekable *seekable, + goffset offset, + GSeekType type, + GCancellable *cancellable, + GError **error); +static gboolean g_file_output_stream_seekable_can_truncate (GSeekable *seekable); +static gboolean g_file_output_stream_seekable_truncate (GSeekable *seekable, + goffset offset, + GCancellable *cancellable, + GError **error); +static void g_file_output_stream_real_query_info_async (GFileOutputStream *stream, + char *attributes, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +static GFileInfo *g_file_output_stream_real_query_info_finish (GFileOutputStream *stream, + GAsyncResult *result, + GError **error); + +G_DEFINE_TYPE_WITH_CODE (GFileOutputStream, g_file_output_stream, G_TYPE_OUTPUT_STREAM, + G_IMPLEMENT_INTERFACE (G_TYPE_SEEKABLE, + g_file_output_stream_seekable_iface_init)); + +struct _GFileOutputStreamPrivate { + GAsyncReadyCallback outstanding_callback; +}; + +static void +g_file_output_stream_class_init (GFileOutputStreamClass *klass) +{ + g_type_class_add_private (klass, sizeof (GFileOutputStreamPrivate)); + + klass->query_info_async = g_file_output_stream_real_query_info_async; + klass->query_info_finish = g_file_output_stream_real_query_info_finish; +} + +static void +g_file_output_stream_seekable_iface_init (GSeekableIface *iface) +{ + iface->tell = g_file_output_stream_seekable_tell; + iface->can_seek = g_file_output_stream_seekable_can_seek; + iface->seek = g_file_output_stream_seekable_seek; + iface->can_truncate = g_file_output_stream_seekable_can_truncate; + iface->truncate = g_file_output_stream_seekable_truncate; +} + +static void +g_file_output_stream_init (GFileOutputStream *stream) +{ + stream->priv = G_TYPE_INSTANCE_GET_PRIVATE (stream, + G_TYPE_FILE_OUTPUT_STREAM, + GFileOutputStreamPrivate); +} + +/** + * g_file_output_stream_query_info: + * @stream: a #GFileOutputStream. + * @attributes: + * @cancellable: optional #GCancellable object, %NULL to ignore. + * @error: a #GError location to store the error occuring, or %NULL to + * ignore. + * + * * Returns: %NULL or a #GFileInfo for the @stream. + * + * For the asynchronous version of this function, see + * g_file_output_stream_query_info_async(). + **/ +GFileInfo * +g_file_output_stream_query_info (GFileOutputStream *stream, + char *attributes, + GCancellable *cancellable, + GError **error) +{ + GFileOutputStreamClass *class; + GOutputStream *output_stream; + GFileInfo *info; + + g_return_val_if_fail (G_IS_FILE_OUTPUT_STREAM (stream), NULL); + + output_stream = G_OUTPUT_STREAM (stream); + + if (g_output_stream_is_closed (output_stream)) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_CLOSED, + _("Stream is already closed")); + return NULL; + } + + if (g_output_stream_has_pending (output_stream)) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_PENDING, + _("Stream has outstanding operation")); + return NULL; + } + + info = NULL; + + g_output_stream_set_pending (output_stream, TRUE); + + if (cancellable) + g_push_current_cancellable (cancellable); + + class = G_FILE_OUTPUT_STREAM_GET_CLASS (stream); + if (class->query_info) + info = class->query_info (stream, attributes, cancellable, error); + else + g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, + _("Stream doesn't support query_info")); + + if (cancellable) + g_pop_current_cancellable (cancellable); + + g_output_stream_set_pending (output_stream, FALSE); + + return info; +} + +static void +async_ready_callback_wrapper (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + GFileOutputStream *stream = G_FILE_OUTPUT_STREAM (source_object); + + g_output_stream_set_pending (G_OUTPUT_STREAM (stream), FALSE); + if (stream->priv->outstanding_callback) + (*stream->priv->outstanding_callback) (source_object, res, user_data); + g_object_unref (stream); +} + +/** + * g_file_output_stream_query_info_async: + * @stream: a #GFileOutputStream. + * @attributes: + * @io_priority: the io priority of the request. + * @cancellable: optional #GCancellable object, %NULL to ignore. + * @callback: a #GAsyncReadyCallback. + * @user_data: user data for @callback. + * + * Asynchronously queries the @stream for a #GFileInfo. When completed, + * @callback will be called with a #GAsyncResult which can be used to + * finish the operation with g_file_output_stream_query_info_finish(). + * + * For the synchronous version of this function, see + * g_file_output_stream_query_info(). + * + **/ +void +g_file_output_stream_query_info_async (GFileOutputStream *stream, + char *attributes, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GFileOutputStreamClass *klass; + GOutputStream *output_stream; + + g_return_if_fail (G_IS_FILE_OUTPUT_STREAM (stream)); + + output_stream = G_OUTPUT_STREAM (stream); + + if (g_output_stream_is_closed (output_stream)) + { + g_simple_async_report_error_in_idle (G_OBJECT (stream), + callback, + user_data, + G_IO_ERROR, G_IO_ERROR_CLOSED, + _("Stream is already closed")); + return; + } + + if (g_output_stream_has_pending (output_stream)) + { + g_simple_async_report_error_in_idle (G_OBJECT (stream), + callback, + user_data, + G_IO_ERROR, G_IO_ERROR_PENDING, + _("Stream has outstanding operation")); + return; + } + + klass = G_FILE_OUTPUT_STREAM_GET_CLASS (stream); + + g_output_stream_set_pending (output_stream, TRUE); + stream->priv->outstanding_callback = callback; + g_object_ref (stream); + klass->query_info_async (stream, attributes, io_priority, cancellable, + async_ready_callback_wrapper, user_data); +} + +/** + * g_file_output_stream_query_info_finish: + * @stream: a #GFileOutputStream. + * @result: a #GAsyncResult. + * @error: a #GError location to store the error occuring, or %NULL to + * ignore. + * + * Finalizes the asynchronous query started + * by g_file_output_stream_query_info_async(). + * + * Returns: A #GFileInfo for the finished query. + **/ +GFileInfo * +g_file_output_stream_query_info_finish (GFileOutputStream *stream, + GAsyncResult *result, + GError **error) +{ + GSimpleAsyncResult *simple; + GFileOutputStreamClass *class; + + g_return_val_if_fail (G_IS_FILE_OUTPUT_STREAM (stream), NULL); + g_return_val_if_fail (G_IS_ASYNC_RESULT (result), NULL); + + if (G_IS_SIMPLE_ASYNC_RESULT (result)) + { + simple = G_SIMPLE_ASYNC_RESULT (result); + if (g_simple_async_result_propagate_error (simple, error)) + return NULL; + } + + class = G_FILE_OUTPUT_STREAM_GET_CLASS (stream); + return class->query_info_finish (stream, result, error); +} + +/** + * g_file_output_stream_get_etag: + * @stream: a #GFileOutputString. + * + * Returns: + **/ +char * +g_file_output_stream_get_etag (GFileOutputStream *stream) +{ + GFileOutputStreamClass *class; + GOutputStream *output_stream; + char *etag; + + g_return_val_if_fail (G_IS_FILE_OUTPUT_STREAM (stream), NULL); + + output_stream = G_OUTPUT_STREAM (stream); + + if (!g_output_stream_is_closed (output_stream)) + { + g_warning ("stream is not closed yet, can't get etag"); + return NULL; + } + + etag = NULL; + + class = G_FILE_OUTPUT_STREAM_GET_CLASS (stream); + if (class->get_etag) + etag = class->get_etag (stream); + + return etag; +} + +/** + * g_file_output_stream_tell: + * @stream: + * + * Returns: + **/ +goffset +g_file_output_stream_tell (GFileOutputStream *stream) +{ + GFileOutputStreamClass *class; + goffset offset; + + g_return_val_if_fail (G_IS_FILE_OUTPUT_STREAM (stream), 0); + + class = G_FILE_OUTPUT_STREAM_GET_CLASS (stream); + + offset = 0; + if (class->tell) + offset = class->tell (stream); + + return offset; +} + +static goffset +g_file_output_stream_seekable_tell (GSeekable *seekable) +{ + return g_file_output_stream_tell (G_FILE_OUTPUT_STREAM (seekable)); +} + +/** + * g_file_output_stream_can_seek: + * @stream: + * + * Returns: + **/ +gboolean +g_file_output_stream_can_seek (GFileOutputStream *stream) +{ + GFileOutputStreamClass *class; + gboolean can_seek; + + g_return_val_if_fail (G_IS_FILE_OUTPUT_STREAM (stream), FALSE); + + class = G_FILE_OUTPUT_STREAM_GET_CLASS (stream); + + can_seek = FALSE; + if (class->seek) + { + can_seek = TRUE; + if (class->can_seek) + can_seek = class->can_seek (stream); + } + + return can_seek; +} + +static gboolean +g_file_output_stream_seekable_can_seek (GSeekable *seekable) +{ + return g_file_output_stream_can_seek (G_FILE_OUTPUT_STREAM (seekable)); +} + +/** + * g_file_output_stream_seek: + * @stream: + * @offset: + * @type: + * @cancellable: optional #GCancellable object, %NULL to ignore. + * @error: a #GError location to store the error occuring, or %NULL to + * ignore. + * Returns: + **/ +gboolean +g_file_output_stream_seek (GFileOutputStream *stream, + goffset offset, + GSeekType type, + GCancellable *cancellable, + GError **error) +{ + GFileOutputStreamClass *class; + GOutputStream *output_stream; + gboolean res; + + g_return_val_if_fail (G_IS_FILE_OUTPUT_STREAM (stream), FALSE); + + output_stream = G_OUTPUT_STREAM (stream); + class = G_FILE_OUTPUT_STREAM_GET_CLASS (stream); + + if (g_output_stream_is_closed (output_stream)) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_CLOSED, + _("Stream is already closed")); + return FALSE; + } + + if (g_output_stream_has_pending (output_stream)) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_PENDING, + _("Stream has outstanding operation")); + return FALSE; + } + + if (!class->seek) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, + _("Seek not supported on stream")); + return FALSE; + } + + g_output_stream_set_pending (output_stream, TRUE); + + if (cancellable) + g_push_current_cancellable (cancellable); + + res = class->seek (stream, offset, type, cancellable, error); + + if (cancellable) + g_pop_current_cancellable (cancellable); + + g_output_stream_set_pending (output_stream, FALSE); + + return res; +} + +static gboolean +g_file_output_stream_seekable_seek (GSeekable *seekable, + goffset offset, + GSeekType type, + GCancellable *cancellable, + GError **error) +{ + return g_file_output_stream_seek (G_FILE_OUTPUT_STREAM (seekable), + offset, type, cancellable, error); +} + +/** + * g_file_output_stream_can_truncate: + * @stream: + * + * Returns: %TRUE if stream can be truncated. + **/ +gboolean +g_file_output_stream_can_truncate (GFileOutputStream *stream) +{ + GFileOutputStreamClass *class; + gboolean can_truncate; + + g_return_val_if_fail (G_IS_FILE_OUTPUT_STREAM (stream), FALSE); + + class = G_FILE_OUTPUT_STREAM_GET_CLASS (stream); + + can_truncate = FALSE; + if (class->truncate) + { + can_truncate = TRUE; + if (class->can_truncate) + can_truncate = class->can_truncate (stream); + } + + return can_truncate; +} + +static gboolean +g_file_output_stream_seekable_can_truncate (GSeekable *seekable) +{ + return g_file_output_stream_can_truncate (G_FILE_OUTPUT_STREAM (seekable)); +} + +/** + * g_file_output_stream_truncate: + * @stream: + * @size: + * @cancellable: optional #GCancellable object, %NULL to ignore. + * @error: a #GError location to store the error occuring, or %NULL to + * ignore. + * Returns: %TRUE if @stream is truncated. + **/ +gboolean +g_file_output_stream_truncate (GFileOutputStream *stream, + goffset size, + GCancellable *cancellable, + GError **error) +{ + GFileOutputStreamClass *class; + GOutputStream *output_stream; + gboolean res; + + g_return_val_if_fail (G_IS_FILE_OUTPUT_STREAM (stream), FALSE); + + output_stream = G_OUTPUT_STREAM (stream); + class = G_FILE_OUTPUT_STREAM_GET_CLASS (stream); + + if (g_output_stream_is_closed (output_stream)) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_CLOSED, + _("Stream is already closed")); + return FALSE; + } + + if (g_output_stream_has_pending (output_stream)) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_PENDING, + _("Stream has outstanding operation")); + return FALSE; + } + + if (!class->truncate) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, + _("Truncate not supported on stream")); + return FALSE; + } + + g_output_stream_set_pending (output_stream, TRUE); + + if (cancellable) + g_push_current_cancellable (cancellable); + + res = class->truncate (stream, size, cancellable, error); + + if (cancellable) + g_pop_current_cancellable (cancellable); + + g_output_stream_set_pending (output_stream, FALSE); + + return res; +} + +static gboolean +g_file_output_stream_seekable_truncate (GSeekable *seekable, + goffset size, + GCancellable *cancellable, + GError **error) +{ + return g_file_output_stream_truncate (G_FILE_OUTPUT_STREAM (seekable), + size, cancellable, error); +} +/******************************************** + * Default implementation of async ops * + ********************************************/ + +typedef struct { + char *attributes; + GFileInfo *info; +} QueryInfoAsyncData; + +static void +query_info_data_free (QueryInfoAsyncData *data) +{ + if (data->info) + g_object_unref (data->info); + g_free (data->attributes); + g_free (data); +} + +static void +query_info_async_thread (GSimpleAsyncResult *res, + GObject *object, + GCancellable *cancellable) +{ + GFileOutputStreamClass *class; + GError *error = NULL; + QueryInfoAsyncData *data; + GFileInfo *info; + + data = g_simple_async_result_get_op_res_gpointer (res); + + info = NULL; + + class = G_FILE_OUTPUT_STREAM_GET_CLASS (object); + if (class->query_info) + info = class->query_info (G_FILE_OUTPUT_STREAM (object), data->attributes, cancellable, &error); + else + g_set_error (&error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, + _("Stream doesn't support query_info")); + + if (info == NULL) + { + g_simple_async_result_set_from_error (res, error); + g_error_free (error); + } + else + data->info = info; +} + +static void +g_file_output_stream_real_query_info_async (GFileOutputStream *stream, + char *attributes, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GSimpleAsyncResult *res; + QueryInfoAsyncData *data; + + data = g_new0 (QueryInfoAsyncData, 1); + data->attributes = g_strdup (attributes); + + res = g_simple_async_result_new (G_OBJECT (stream), callback, user_data, g_file_output_stream_real_query_info_async); + g_simple_async_result_set_op_res_gpointer (res, data, (GDestroyNotify)query_info_data_free); + + g_simple_async_result_run_in_thread (res, query_info_async_thread, io_priority, cancellable); + g_object_unref (res); +} + +static GFileInfo * +g_file_output_stream_real_query_info_finish (GFileOutputStream *stream, + GAsyncResult *res, + GError **error) +{ + GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (res); + QueryInfoAsyncData *data; + + g_assert (g_simple_async_result_get_source_tag (simple) == g_file_output_stream_real_query_info_async); + + data = g_simple_async_result_get_op_res_gpointer (simple); + if (data->info) + return g_object_ref (data->info); + + return NULL; +} diff --git a/gio/gfileoutputstream.h b/gio/gfileoutputstream.h new file mode 100644 index 000000000..6525754e1 --- /dev/null +++ b/gio/gfileoutputstream.h @@ -0,0 +1,121 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#ifndef __G_FILE_OUTPUT_STREAM_H__ +#define __G_FILE_OUTPUT_STREAM_H__ + +#include <gio/goutputstream.h> +#include <gio/gfileinfo.h> + +G_BEGIN_DECLS + +#define G_TYPE_FILE_OUTPUT_STREAM (g_file_output_stream_get_type ()) +#define G_FILE_OUTPUT_STREAM(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_FILE_OUTPUT_STREAM, GFileOutputStream)) +#define G_FILE_OUTPUT_STREAM_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), G_TYPE_FILE_OUTPUT_STREAM, GFileOutputStreamClass)) +#define G_IS_FILE_OUTPUT_STREAM(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_FILE_OUTPUT_STREAM)) +#define G_IS_FILE_OUTPUT_STREAM_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_FILE_OUTPUT_STREAM)) +#define G_FILE_OUTPUT_STREAM_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_TYPE_FILE_OUTPUT_STREAM, GFileOutputStreamClass)) + +typedef struct _GFileOutputStream GFileOutputStream; +typedef struct _GFileOutputStreamClass GFileOutputStreamClass; +typedef struct _GFileOutputStreamPrivate GFileOutputStreamPrivate; + +struct _GFileOutputStream +{ + GOutputStream parent; + + /*< private >*/ + GFileOutputStreamPrivate *priv; +}; + +struct _GFileOutputStreamClass +{ + GOutputStreamClass parent_class; + + goffset (*tell) (GFileOutputStream *stream); + gboolean (*can_seek) (GFileOutputStream *stream); + gboolean (*seek) (GFileOutputStream *stream, + goffset offset, + GSeekType type, + GCancellable *cancellable, + GError **error); + gboolean (*can_truncate) (GFileOutputStream *stream); + gboolean (*truncate) (GFileOutputStream *stream, + goffset size, + GCancellable *cancellable, + GError **error); + GFileInfo *(*query_info) (GFileOutputStream *stream, + char *attributes, + GCancellable *cancellable, + GError **error); + void (*query_info_async) (GFileOutputStream *stream, + char *attributes, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + GFileInfo *(*query_info_finish) (GFileOutputStream *stream, + GAsyncResult *res, + GError **error); + char *(*get_etag) (GFileOutputStream *stream); + + /* Padding for future expansion */ + void (*_g_reserved1) (void); + void (*_g_reserved2) (void); + void (*_g_reserved3) (void); + void (*_g_reserved4) (void); + void (*_g_reserved5) (void); +}; + +GType g_file_output_stream_get_type (void) G_GNUC_CONST; + + +GFileInfo *g_file_output_stream_query_info (GFileOutputStream *stream, + char *attributes, + GCancellable *cancellable, + GError **error); +void g_file_output_stream_query_info_async (GFileOutputStream *stream, + char *attributes, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +GFileInfo *g_file_output_stream_query_info_finish (GFileOutputStream *stream, + GAsyncResult *result, + GError **error); +char * g_file_output_stream_get_etag (GFileOutputStream *stream); +goffset g_file_output_stream_tell (GFileOutputStream *stream); +gboolean g_file_output_stream_can_seek (GFileOutputStream *stream); +gboolean g_file_output_stream_seek (GFileOutputStream *stream, + goffset offset, + GSeekType type, + GCancellable *cancellable, + GError **error); +gboolean g_file_output_stream_can_truncate (GFileOutputStream *stream); +gboolean g_file_output_stream_truncate (GFileOutputStream *stream, + goffset size, + GCancellable *cancellable, + GError **error); + +G_END_DECLS + +#endif /* __G_FILE_FILE_OUTPUT_STREAM_H__ */ diff --git a/gio/gfilterinputstream.c b/gio/gfilterinputstream.c new file mode 100644 index 000000000..6d2f99a33 --- /dev/null +++ b/gio/gfilterinputstream.c @@ -0,0 +1,391 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Christian Kellner <gicmo@gnome.org> + */ + +#include <config.h> +#include "gfilterinputstream.h" +#include "ginputstream.h" +#include "glibintl.h" + +enum { + PROP_0, + PROP_BASE_STREAM +}; + +static void g_filter_input_stream_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec); + +static void g_filter_input_stream_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec); +static void g_filter_input_stream_finalize (GObject *object); + + +static gssize g_filter_input_stream_read (GInputStream *stream, + void *buffer, + gsize count, + GCancellable *cancellable, + GError **error); +static gssize g_filter_input_stream_skip (GInputStream *stream, + gsize count, + GCancellable *cancellable, + GError **error); +static gboolean g_filter_input_stream_close (GInputStream *stream, + GCancellable *cancellable, + GError **error); +static void g_filter_input_stream_read_async (GInputStream *stream, + void *buffer, + gsize count, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +static gssize g_filter_input_stream_read_finish (GInputStream *stream, + GAsyncResult *result, + GError **error); +static void g_filter_input_stream_skip_async (GInputStream *stream, + gsize count, + int io_priority, + GCancellable *cancellabl, + GAsyncReadyCallback callback, + gpointer datae); +static gssize g_filter_input_stream_skip_finish (GInputStream *stream, + GAsyncResult *result, + GError **error); +static void g_filter_input_stream_close_async (GInputStream *stream, + int io_priority, + GCancellable *cancellabl, + GAsyncReadyCallback callback, + gpointer data); +static gboolean g_filter_input_stream_close_finish (GInputStream *stream, + GAsyncResult *result, + GError **error); + +G_DEFINE_TYPE (GFilterInputStream, g_filter_input_stream, G_TYPE_INPUT_STREAM) + + +static void +g_filter_input_stream_class_init (GFilterInputStreamClass *klass) +{ + GObjectClass *object_class; + GInputStreamClass *istream_class; + + object_class = G_OBJECT_CLASS (klass); + object_class->get_property = g_filter_input_stream_get_property; + object_class->set_property = g_filter_input_stream_set_property; + object_class->finalize = g_filter_input_stream_finalize; + + istream_class = G_INPUT_STREAM_CLASS (klass); + istream_class->read = g_filter_input_stream_read; + istream_class->skip = g_filter_input_stream_skip; + istream_class->close = g_filter_input_stream_close; + + istream_class->read_async = g_filter_input_stream_read_async; + istream_class->read_finish = g_filter_input_stream_read_finish; + istream_class->skip_async = g_filter_input_stream_skip_async; + istream_class->skip_finish = g_filter_input_stream_skip_finish; + istream_class->close_async = g_filter_input_stream_close_async; + istream_class->close_finish = g_filter_input_stream_close_finish; + + g_object_class_install_property (object_class, + PROP_BASE_STREAM, + g_param_spec_object ("base-stream", + P_("The Filter Base Stream"), + P_("The underlying base stream the io ops will be done on"), + G_TYPE_INPUT_STREAM, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_NAME|G_PARAM_STATIC_NICK|G_PARAM_STATIC_BLURB)); + +} + +static void +g_filter_input_stream_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GFilterInputStream *filter_stream; + GObject *obj; + + filter_stream = G_FILTER_INPUT_STREAM (object); + + switch (prop_id) + { + case PROP_BASE_STREAM: + obj = g_value_dup_object (value); + filter_stream->base_stream = G_INPUT_STREAM (obj); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } + +} + +static void +g_filter_input_stream_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GFilterInputStream *filter_stream; + + filter_stream = G_FILTER_INPUT_STREAM (object); + + switch (prop_id) + { + case PROP_BASE_STREAM: + g_value_set_object (value, filter_stream->base_stream); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } + +} + +static void +g_filter_input_stream_finalize (GObject *object) +{ + GFilterInputStream *stream; + + stream = G_FILTER_INPUT_STREAM (object); + + g_object_unref (stream->base_stream); + + if (G_OBJECT_CLASS (g_filter_input_stream_parent_class)->finalize) + (*G_OBJECT_CLASS (g_filter_input_stream_parent_class)->finalize) (object); +} + +static void +g_filter_input_stream_init (GFilterInputStream *stream) +{ + +} + +/** + * g_filter_input_stream_get_base_stream: + * @stream: a #GFilterInputStream. + * + * Returns: a #GInputStream. + **/ +GInputStream * +g_filter_input_stream_get_base_stream (GFilterInputStream *stream) +{ + g_return_val_if_fail (G_IS_FILTER_INPUT_STREAM (stream), NULL); + + return stream->base_stream; +} + +static gssize +g_filter_input_stream_read (GInputStream *stream, + void *buffer, + gsize count, + GCancellable *cancellable, + GError **error) +{ + GFilterInputStream *filter_stream; + GInputStream *base_stream; + gssize nread; + + filter_stream = G_FILTER_INPUT_STREAM (stream); + base_stream = filter_stream->base_stream; + + nread = g_input_stream_read (base_stream, + buffer, + count, + cancellable, + error); + + return nread; +} + +static gssize +g_filter_input_stream_skip (GInputStream *stream, + gsize count, + GCancellable *cancellable, + GError **error) +{ + GFilterInputStream *filter_stream; + GInputStream *base_stream; + gssize nskipped; + + filter_stream = G_FILTER_INPUT_STREAM (stream); + base_stream = filter_stream->base_stream; + + nskipped = g_input_stream_skip (base_stream, + count, + cancellable, + error); + return nskipped; +} + +static gboolean +g_filter_input_stream_close (GInputStream *stream, + GCancellable *cancellable, + GError **error) +{ + GFilterInputStream *filter_stream; + GInputStream *base_stream; + gboolean res; + + filter_stream = G_FILTER_INPUT_STREAM (stream); + base_stream = filter_stream->base_stream; + + res = g_input_stream_close (base_stream, + cancellable, + error); + + return res; +} + +static void +g_filter_input_stream_read_async (GInputStream *stream, + void *buffer, + gsize count, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GFilterInputStream *filter_stream; + GInputStream *base_stream; + + filter_stream = G_FILTER_INPUT_STREAM (stream); + base_stream = filter_stream->base_stream; + + g_input_stream_read_async (base_stream, + buffer, + count, + io_priority, + cancellable, + callback, + user_data); +} + +static gssize +g_filter_input_stream_read_finish (GInputStream *stream, + GAsyncResult *result, + GError **error) +{ + GFilterInputStream *filter_stream; + GInputStream *base_stream; + gssize nread; + + filter_stream = G_FILTER_INPUT_STREAM (stream); + base_stream = filter_stream->base_stream; + + nread = g_input_stream_read_finish (base_stream, + result, + error); + + return nread; +} + +static void +g_filter_input_stream_skip_async (GInputStream *stream, + gsize count, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GFilterInputStream *filter_stream; + GInputStream *base_stream; + + filter_stream = G_FILTER_INPUT_STREAM (stream); + base_stream = filter_stream->base_stream; + + g_input_stream_skip_async (base_stream, + count, + io_priority, + cancellable, + callback, + user_data); + +} + +static gssize +g_filter_input_stream_skip_finish (GInputStream *stream, + GAsyncResult *result, + GError **error) +{ + GFilterInputStream *filter_stream; + GInputStream *base_stream; + gssize nskipped; + + filter_stream = G_FILTER_INPUT_STREAM (stream); + base_stream = filter_stream->base_stream; + + nskipped = g_input_stream_skip_finish (base_stream, + result, + error); + + return nskipped; +} + +static void +g_filter_input_stream_close_async (GInputStream *stream, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GFilterInputStream *filter_stream; + GInputStream *base_stream; + + filter_stream = G_FILTER_INPUT_STREAM (stream); + base_stream = filter_stream->base_stream; + + g_input_stream_close_async (base_stream, + io_priority, + cancellable, + callback, + user_data); + + +} + +static gboolean +g_filter_input_stream_close_finish (GInputStream *stream, + GAsyncResult *result, + GError **error) +{ + GFilterInputStream *filter_stream; + GInputStream *base_stream; + gboolean res; + + filter_stream = G_FILTER_INPUT_STREAM (stream); + base_stream = filter_stream->base_stream; + + res = g_input_stream_close_finish (stream, + result, + error); + + return res; +} + +/* vim: ts=2 sw=2 et */ diff --git a/gio/gfilterinputstream.h b/gio/gfilterinputstream.h new file mode 100644 index 000000000..331f9be68 --- /dev/null +++ b/gio/gfilterinputstream.h @@ -0,0 +1,65 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Christian Kellner <gicmo@gnome.org> + */ + +#ifndef __G_FILTER_INPUT_STREAM_H__ +#define __G_FILTER_INPUT_STREAM_H__ + +#include <glib-object.h> +#include <gio/ginputstream.h> + +G_BEGIN_DECLS + +#define G_TYPE_FILTER_INPUT_STREAM (g_filter_input_stream_get_type ()) +#define G_FILTER_INPUT_STREAM(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_FILTER_INPUT_STREAM, GFilterInputStream)) +#define G_FILTER_INPUT_STREAM_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), G_TYPE_FILTER_INPUT_STREAM, GFilterInputStreamClass)) +#define G_IS_FILTER_INPUT_STREAM(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_FILTER_INPUT_STREAM)) +#define G_IS_FILTER_INPUT_STREAM_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_FILTER_INPUT_STREAM)) +#define G_FILTER_INPUT_STREAM_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_TYPE_FILTER_INPUT_STREAM, GFilterInputStreamClass)) + +typedef struct _GFilterInputStream GFilterInputStream; +typedef struct _GFilterInputStreamClass GFilterInputStreamClass; +typedef struct _GFilterInputStreamPrivate GFilterInputStreamPrivate; + +struct _GFilterInputStream +{ + GInputStream parent; + + /*<protected >*/ + GInputStream *base_stream; +}; + +struct _GFilterInputStreamClass +{ + GInputStreamClass parent_class; + + /* Padding for future expansion */ + void (*_g_reserved1) (void); + void (*_g_reserved2) (void); + void (*_g_reserved3) (void); +}; + + +GType g_filter_input_stream_get_type (void) G_GNUC_CONST; +GInputStream *g_filter_input_stream_get_base_stream (GFilterInputStream *stream); +G_END_DECLS + +#endif /* __G_FILTER_INPUT_STREAM_H__ */ diff --git a/gio/gfilteroutputstream.c b/gio/gfilteroutputstream.c new file mode 100644 index 000000000..ca41be5f9 --- /dev/null +++ b/gio/gfilteroutputstream.c @@ -0,0 +1,366 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Christian Kellner <gicmo@gnome.org> + */ + +#include <config.h> +#include "gfilteroutputstream.h" +#include "goutputstream.h" +#include "glibintl.h" + +enum { + PROP_0, + PROP_BASE_STREAM +}; + +static void g_filter_output_stream_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec); + +static void g_filter_output_stream_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec); +static void g_filter_output_stream_dispose (GObject *object); + + +static gssize g_filter_output_stream_write (GOutputStream *stream, + const void *buffer, + gsize count, + GCancellable *cancellable, + GError **error); +static gboolean g_filter_output_stream_flush (GOutputStream *stream, + GCancellable *cancellable, + GError **error); +static gboolean g_filter_output_stream_close (GOutputStream *stream, + GCancellable *cancellable, + GError **error); +static void g_filter_output_stream_write_async (GOutputStream *stream, + const void *buffer, + gsize count, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer data); +static gssize g_filter_output_stream_write_finish (GOutputStream *stream, + GAsyncResult *result, + GError **error); +static void g_filter_output_stream_flush_async (GOutputStream *stream, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer data); +static gboolean g_filter_output_stream_flush_finish (GOutputStream *stream, + GAsyncResult *result, + GError **error); +static void g_filter_output_stream_close_async (GOutputStream *stream, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer data); +static gboolean g_filter_output_stream_close_finish (GOutputStream *stream, + GAsyncResult *result, + GError **error); + + + +G_DEFINE_TYPE (GFilterOutputStream, g_filter_output_stream, G_TYPE_OUTPUT_STREAM) + + + +static void +g_filter_output_stream_class_init (GFilterOutputStreamClass *klass) +{ + GObjectClass *object_class; + GOutputStreamClass *ostream_class; + + object_class = G_OBJECT_CLASS (klass); + object_class->get_property = g_filter_output_stream_get_property; + object_class->set_property = g_filter_output_stream_set_property; + object_class->dispose = g_filter_output_stream_dispose; + + ostream_class = G_OUTPUT_STREAM_CLASS (klass); + ostream_class->write = g_filter_output_stream_write; + ostream_class->flush = g_filter_output_stream_flush; + ostream_class->close = g_filter_output_stream_close; + ostream_class->write_async = g_filter_output_stream_write_async; + ostream_class->write_finish = g_filter_output_stream_write_finish; + ostream_class->flush_async = g_filter_output_stream_flush_async; + ostream_class->flush_finish = g_filter_output_stream_flush_finish; + ostream_class->close_async = g_filter_output_stream_close_async; + ostream_class->close_finish = g_filter_output_stream_close_finish; + + g_object_class_install_property (object_class, + PROP_BASE_STREAM, + g_param_spec_object ("base-stream", + P_("The Filter Base Stream"), + P_("The underlying base stream the io ops will be done on"), + G_TYPE_OUTPUT_STREAM, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_NAME|G_PARAM_STATIC_NICK|G_PARAM_STATIC_BLURB)); + +} + +static void +g_filter_output_stream_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GFilterOutputStream *filter_stream; + GObject *obj; + + filter_stream = G_FILTER_OUTPUT_STREAM (object); + + switch (prop_id) + { + case PROP_BASE_STREAM: + obj = g_value_dup_object (value); + filter_stream->base_stream = G_OUTPUT_STREAM (obj); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } + +} + +static void +g_filter_output_stream_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GFilterOutputStream *filter_stream; + + filter_stream = G_FILTER_OUTPUT_STREAM (object); + + switch (prop_id) + { + case PROP_BASE_STREAM: + g_value_set_object (value, filter_stream->base_stream); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } + +} + +static void +g_filter_output_stream_dispose (GObject *object) +{ + GFilterOutputStream *stream; + + stream = G_FILTER_OUTPUT_STREAM (object); + + G_OBJECT_CLASS (g_filter_output_stream_parent_class)->dispose (object); + + if (stream->base_stream) + { + g_object_unref (stream->base_stream); + stream->base_stream = NULL; + } +} + + +static void +g_filter_output_stream_init (GFilterOutputStream *stream) +{ +} + +GOutputStream * +g_filter_output_stream_get_base_stream (GFilterOutputStream *stream) +{ + g_return_val_if_fail (G_IS_FILTER_OUTPUT_STREAM (stream), NULL); + + return stream->base_stream; +} + +static gssize +g_filter_output_stream_write (GOutputStream *stream, + const void *buffer, + gsize count, + GCancellable *cancellable, + GError **error) +{ + GFilterOutputStream *filter_stream; + gssize nwritten; + + filter_stream = G_FILTER_OUTPUT_STREAM (stream); + + nwritten = g_output_stream_write (filter_stream->base_stream, + buffer, + count, + cancellable, + error); + + return nwritten; +} + +static gboolean +g_filter_output_stream_flush (GOutputStream *stream, + GCancellable *cancellable, + GError **error) +{ + GFilterOutputStream *filter_stream; + gboolean res; + + filter_stream = G_FILTER_OUTPUT_STREAM (stream); + + res = g_output_stream_flush (filter_stream->base_stream, + cancellable, + error); + + return res; +} + +static gboolean +g_filter_output_stream_close (GOutputStream *stream, + GCancellable *cancellable, + GError **error) +{ + GFilterOutputStream *filter_stream; + gboolean res; + + filter_stream = G_FILTER_OUTPUT_STREAM (stream); + + res = g_output_stream_close (filter_stream->base_stream, + cancellable, + error); + + return res; +} + +static void +g_filter_output_stream_write_async (GOutputStream *stream, + const void *buffer, + gsize count, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer data) +{ + GFilterOutputStream *filter_stream; + + filter_stream = G_FILTER_OUTPUT_STREAM (stream); + + g_output_stream_write_async (filter_stream->base_stream, + buffer, + count, + io_priority, + cancellable, + callback, + data); + +} + +static gssize +g_filter_output_stream_write_finish (GOutputStream *stream, + GAsyncResult *result, + GError **error) +{ + GFilterOutputStream *filter_stream; + gssize nwritten; + + filter_stream = G_FILTER_OUTPUT_STREAM (stream); + + nwritten = g_output_stream_write_finish (filter_stream->base_stream, + result, + error); + + return nwritten; +} + +static void +g_filter_output_stream_flush_async (GOutputStream *stream, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer data) +{ + GFilterOutputStream *filter_stream; + + filter_stream = G_FILTER_OUTPUT_STREAM (stream); + + g_output_stream_flush_async (filter_stream->base_stream, + io_priority, + cancellable, + callback, + data); +} + +static gboolean +g_filter_output_stream_flush_finish (GOutputStream *stream, + GAsyncResult *result, + GError **error) +{ + GFilterOutputStream *filter_stream; + gboolean res; + + filter_stream = G_FILTER_OUTPUT_STREAM (stream); + + res = g_output_stream_flush_finish (filter_stream->base_stream, + result, + error); + + return res; +} + +static void +g_filter_output_stream_close_async (GOutputStream *stream, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer data) +{ + GFilterOutputStream *filter_stream; + + filter_stream = G_FILTER_OUTPUT_STREAM (stream); + + g_output_stream_close_async (filter_stream->base_stream, + io_priority, + cancellable, + callback, + data); +} + +static gboolean +g_filter_output_stream_close_finish (GOutputStream *stream, + GAsyncResult *result, + GError **error) +{ + GFilterOutputStream *filter_stream; + gboolean res; + + filter_stream = G_FILTER_OUTPUT_STREAM (stream); + + res = g_output_stream_close_finish (filter_stream->base_stream, + result, + error); + + return res; +} + + +/* vim: ts=2 sw=2 et */ diff --git a/gio/gfilteroutputstream.h b/gio/gfilteroutputstream.h new file mode 100644 index 000000000..604f9283d --- /dev/null +++ b/gio/gfilteroutputstream.h @@ -0,0 +1,65 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Christian Kellner <gicmo@gnome.org> + */ + +#ifndef __G_FILTER_OUTPUT_STREAM_H__ +#define __G_FILTER_OUTPUT_STREAM_H__ + +#include <glib-object.h> +#include <gio/goutputstream.h> + +G_BEGIN_DECLS + +#define G_TYPE_FILTER_OUTPUT_STREAM (g_filter_output_stream_get_type ()) +#define G_FILTER_OUTPUT_STREAM(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_FILTER_OUTPUT_STREAM, GFilterOutputStream)) +#define G_FILTER_OUTPUT_STREAM_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), G_TYPE_FILTER_OUTPUT_STREAM, GFilterOutputStreamClass)) +#define G_IS_FILTER_OUTPUT_STREAM(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_FILTER_OUTPUT_STREAM)) +#define G_IS_FILTER_OUTPUT_STREAM_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_FILTER_OUTPUT_STREAM)) +#define G_FILTER_OUTPUT_STREAM_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_TYPE_FILTER_OUTPUT_STREAM, GFilterOutputStreamClass)) + +typedef struct _GFilterOutputStream GFilterOutputStream; +typedef struct _GFilterOutputStreamClass GFilterOutputStreamClass; +typedef struct _GFilterOutputStreamPrivate GFilterOutputStreamPrivate; + +struct _GFilterOutputStream +{ + GOutputStream parent; + + /*< protected >*/ + GOutputStream *base_stream; +}; + +struct _GFilterOutputStreamClass +{ + GOutputStreamClass parent_class; + + /* Padding for future expansion */ + void (*_g_reserved1) (void); + void (*_g_reserved2) (void); + void (*_g_reserved3) (void); +}; + + +GType g_filter_output_stream_get_type (void) G_GNUC_CONST; +GOutputStream *g_filter_output_stream_get_base_stream (GFilterOutputStream *stream); +G_END_DECLS + +#endif /* __G_FILTER_OUTPUT_STREAM_H__ */ diff --git a/gio/gicon.c b/gio/gicon.c new file mode 100644 index 000000000..5913ac3ad --- /dev/null +++ b/gio/gicon.c @@ -0,0 +1,118 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#include <config.h> +#include "gicon.h" + +#include "glibintl.h" + +static void g_icon_base_init (gpointer g_class); +static void g_icon_class_init (gpointer g_class, + gpointer class_data); + +GType +g_icon_get_type (void) +{ + static GType icon_type = 0; + + if (! icon_type) + { + static const GTypeInfo icon_info = + { + sizeof (GIconIface), /* class_size */ + g_icon_base_init, /* base_init */ + NULL, /* base_finalize */ + g_icon_class_init, + NULL, /* class_finalize */ + NULL, /* class_data */ + 0, + 0, /* n_preallocs */ + NULL + }; + + icon_type = + g_type_register_static (G_TYPE_INTERFACE, I_("GIcon"), + &icon_info, 0); + + g_type_interface_add_prerequisite (icon_type, G_TYPE_OBJECT); + } + + return icon_type; +} + +static void +g_icon_class_init (gpointer g_class, + gpointer class_data) +{ +} + +static void +g_icon_base_init (gpointer g_class) +{ +} + +/** + * g_icon_hash: + * @icon: #gconstpointer to an icon object. + * + * Returns: a #guint containing a hash for the @icon, suitable for + * use in a #GHashTable or similar data structure. + **/ +guint +g_icon_hash (gconstpointer icon) +{ + GIconIface *iface; + + g_return_val_if_fail (G_IS_ICON (icon), 0); + + iface = G_ICON_GET_IFACE (icon); + + return (* iface->hash) ((GIcon *)icon); +} + +/** + * g_icon_equal: + * @icon1: pointer to the first #GIcon. + * @icon2: pointer to the second #GIcon. + * + * Returns: %TRUE if @icon1 is equal to @icon2. %FALSE otherwise. + **/ +gboolean +g_icon_equal (GIcon *icon1, + GIcon *icon2) +{ + GIconIface *iface; + + if (icon1 == NULL && icon2 == NULL) + return TRUE; + + if (icon1 == NULL || icon2 == NULL) + return FALSE; + + if (G_TYPE_FROM_INSTANCE (icon1) != G_TYPE_FROM_INSTANCE (icon2)) + return FALSE; + + iface = G_ICON_GET_IFACE (icon1); + + return (* iface->equal) (icon1, icon2); +} + diff --git a/gio/gicon.h b/gio/gicon.h new file mode 100644 index 000000000..6c0dbc0de --- /dev/null +++ b/gio/gicon.h @@ -0,0 +1,58 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#ifndef __G_ICON_H__ +#define __G_ICON_H__ + +#include <glib-object.h> + +G_BEGIN_DECLS + +#define G_TYPE_ICON (g_icon_get_type ()) +#define G_ICON(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), G_TYPE_ICON, GIcon)) +#define G_IS_ICON(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), G_TYPE_ICON)) +#define G_ICON_GET_IFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), G_TYPE_ICON, GIconIface)) + +typedef struct _GIcon GIcon; /* Dummy typedef */ +typedef struct _GIconIface GIconIface; + + +struct _GIconIface +{ + GTypeInterface g_iface; + + /* Virtual Table */ + + guint (*hash) (GIcon *icon); + gboolean (*equal) (GIcon *icon1, + GIcon *icon2); +}; + +GType g_icon_get_type (void) G_GNUC_CONST; + +guint g_icon_hash (gconstpointer icon); +gboolean g_icon_equal (GIcon *icon1, + GIcon *icon2); + +G_END_DECLS + +#endif /* __G_ICON_H__ */ diff --git a/gio/ginputstream.c b/gio/ginputstream.c new file mode 100644 index 000000000..127f5200e --- /dev/null +++ b/gio/ginputstream.c @@ -0,0 +1,1184 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#include <config.h> +#include <glib.h> +#include "glibintl.h" + +#include "ginputstream.h" +#include "gseekable.h" +#include "gsimpleasyncresult.h" + +G_DEFINE_TYPE (GInputStream, g_input_stream, G_TYPE_OBJECT); + +struct _GInputStreamPrivate { + guint closed : 1; + guint pending : 1; + GAsyncReadyCallback outstanding_callback; +}; + +static gssize g_input_stream_real_skip (GInputStream *stream, + gsize count, + GCancellable *cancellable, + GError **error); +static void g_input_stream_real_read_async (GInputStream *stream, + void *buffer, + gsize count, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +static gssize g_input_stream_real_read_finish (GInputStream *stream, + GAsyncResult *result, + GError **error); +static void g_input_stream_real_skip_async (GInputStream *stream, + gsize count, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer data); +static gssize g_input_stream_real_skip_finish (GInputStream *stream, + GAsyncResult *result, + GError **error); +static void g_input_stream_real_close_async (GInputStream *stream, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer data); +static gboolean g_input_stream_real_close_finish (GInputStream *stream, + GAsyncResult *result, + GError **error); + +static void +g_input_stream_finalize (GObject *object) +{ + GInputStream *stream; + + stream = G_INPUT_STREAM (object); + + if (!stream->priv->closed) + g_input_stream_close (stream, NULL, NULL); + + if (G_OBJECT_CLASS (g_input_stream_parent_class)->finalize) + (*G_OBJECT_CLASS (g_input_stream_parent_class)->finalize) (object); +} + +static void +g_input_stream_dispose (GObject *object) +{ + GInputStream *stream; + + stream = G_INPUT_STREAM (object); + + if (!stream->priv->closed) + g_input_stream_close (stream, NULL, NULL); + + if (G_OBJECT_CLASS (g_input_stream_parent_class)->dispose) + (*G_OBJECT_CLASS (g_input_stream_parent_class)->dispose) (object); +} + + +static void +g_input_stream_class_init (GInputStreamClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + g_type_class_add_private (klass, sizeof (GInputStreamPrivate)); + + gobject_class->finalize = g_input_stream_finalize; + gobject_class->dispose = g_input_stream_dispose; + + klass->skip = g_input_stream_real_skip; + klass->read_async = g_input_stream_real_read_async; + klass->read_finish = g_input_stream_real_read_finish; + klass->skip_async = g_input_stream_real_skip_async; + klass->skip_finish = g_input_stream_real_skip_finish; + klass->close_async = g_input_stream_real_close_async; + klass->close_finish = g_input_stream_real_close_finish; +} + +static void +g_input_stream_init (GInputStream *stream) +{ + stream->priv = G_TYPE_INSTANCE_GET_PRIVATE (stream, + G_TYPE_INPUT_STREAM, + GInputStreamPrivate); +} + +/** + * g_input_stream_read: + * @stream: a #GInputStream. + * @buffer: a buffer to read data into (which should be at least count bytes long). + * @count: the number of bytes that will be read from the stream + * @cancellable: optional #GCancellable object, %NULL to ignore. + * @error: location to store the error occuring, or %NULL to ignore + * + * Tries to read @count bytes from the stream into the buffer starting at + * @buffer. Will block during this read. + * + * If count is zero returns zero and does nothing. A value of @count + * larger than %G_MAXSSIZE will cause a %G_IO_ERROR_INVALID_ARGUMENT error. + * + * On success, the number of bytes read into the buffer is returned. + * It is not an error if this is not the same as the requested size, as it + * can happen e.g. near the end of a file. Zero is returned on end of file + * (or if @count is zero), but never otherwise. + * + * If @cancellable is not NULL, then the operation can be cancelled by + * triggering the cancellable object from another thread. If the operation + * was cancelled, the error G_IO_ERROR_CANCELLED will be returned. If an + * operation was partially finished when the operation was cancelled the + * partial result will be returned, without an error. + * + * On error -1 is returned and @error is set accordingly. + * + * Return value: Number of bytes read, or -1 on error + **/ +gssize +g_input_stream_read (GInputStream *stream, + void *buffer, + gsize count, + GCancellable *cancellable, + GError **error) +{ + GInputStreamClass *class; + gssize res; + + g_return_val_if_fail (G_IS_INPUT_STREAM (stream), -1); + g_return_val_if_fail (buffer != NULL, 0); + + if (count == 0) + return 0; + + if (((gssize) count) < 0) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, + _("Too large count value passed to g_input_stream_read")); + return -1; + } + + if (stream->priv->closed) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_CLOSED, + _("Stream is already closed")); + return -1; + } + + if (stream->priv->pending) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_PENDING, + _("Stream has outstanding operation")); + return -1; + } + + class = G_INPUT_STREAM_GET_CLASS (stream); + + if (class->read == NULL) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, + _("Input stream doesn't implement read")); + return -1; + } + + if (cancellable) + g_push_current_cancellable (cancellable); + + stream->priv->pending = TRUE; + res = class->read (stream, buffer, count, cancellable, error); + stream->priv->pending = FALSE; + + if (cancellable) + g_pop_current_cancellable (cancellable); + + return res; +} + +/** + * g_input_stream_read_all: + * @stream: a #GInputStream. + * @buffer: a buffer to read data into (which should be at least count bytes long). + * @count: the number of bytes that will be read from the stream + * @bytes_read: location to store the number of bytes that was read from the stream + * @cancellable: optional #GCancellable object, %NULL to ignore. + * @error: location to store the error occuring, or %NULL to ignore + * + * Tries to read @count bytes from the stream into the buffer starting at + * @buffer. Will block during this read. + * + * This function is similar to g_input_stream_read(), except it tries to + * read as many bytes as requested, only stopping on an error or end of stream. + * + * On a successful read of @count bytes, or if we reached the end of the + * stream, TRUE is returned, and @bytes_read is set to the number of bytes + * read into @buffer. + * + * If there is an error during the operation FALSE is returned and @error + * is set to indicate the error status, @bytes_read is updated to contain + * the number of bytes read into @buffer before the error occured. + * + * Return value: TRUE on success, FALSE if there was an error + **/ +gboolean +g_input_stream_read_all (GInputStream *stream, + void *buffer, + gsize count, + gsize *bytes_read, + GCancellable *cancellable, + GError **error) +{ + gsize _bytes_read; + gssize res; + + g_return_val_if_fail (G_IS_INPUT_STREAM (stream), FALSE); + g_return_val_if_fail (buffer != NULL, FALSE); + + _bytes_read = 0; + while (_bytes_read < count) + { + res = g_input_stream_read (stream, (char *)buffer + _bytes_read, count - _bytes_read, + cancellable, error); + if (res == -1) + { + if (bytes_read) + *bytes_read = _bytes_read; + return FALSE; + } + + if (res == 0) + break; + + _bytes_read += res; + } + + if (bytes_read) + *bytes_read = _bytes_read; + return TRUE; +} + +/** + * g_input_stream_skip: + * @stream: a #GInputStream. + * @count: the number of bytes that will be skipped from the stream + * @cancellable: optional #GCancellable object, %NULL to ignore. + * @error: location to store the error occuring, or %NULL to ignore + * + * Tries to skip @count bytes from the stream. Will block during the operation. + * + * This is identical to g_input_stream_read(), from a behaviour standpoint, + * but the bytes that are skipped are not returned to the user. Some + * streams have an implementation that is more efficient than reading the data. + * + * This function is optional for inherited classes, as the default implementation + * emulates it using read. + * + * If @cancellable is not %NULL, then the operation can be cancelled by + * triggering the cancellable object from another thread. If the operation + * was cancelled, the error %G_IO_ERROR_CANCELLED will be returned. If an + * operation was partially finished when the operation was cancelled the + * partial result will be returned, without an error. + * + * Return value: Number of bytes skipped, or -1 on error + **/ +gssize +g_input_stream_skip (GInputStream *stream, + gsize count, + GCancellable *cancellable, + GError **error) +{ + GInputStreamClass *class; + gssize res; + + g_return_val_if_fail (G_IS_INPUT_STREAM (stream), -1); + + if (count == 0) + return 0; + + if (((gssize) count) < 0) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, + _("Too large count value passed to g_input_stream_skip")); + return -1; + } + + if (stream->priv->closed) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_CLOSED, + _("Stream is already closed")); + return -1; + } + + if (stream->priv->pending) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_PENDING, + _("Stream has outstanding operation")); + return -1; + } + + class = G_INPUT_STREAM_GET_CLASS (stream); + + if (cancellable) + g_push_current_cancellable (cancellable); + + stream->priv->pending = TRUE; + res = class->skip (stream, count, cancellable, error); + stream->priv->pending = FALSE; + + if (cancellable) + g_pop_current_cancellable (cancellable); + + return res; +} + +static gssize +g_input_stream_real_skip (GInputStream *stream, + gsize count, + GCancellable *cancellable, + GError **error) +{ + GInputStreamClass *class; + gssize ret, read_bytes; + char buffer[8192]; + GError *my_error; + + class = G_INPUT_STREAM_GET_CLASS (stream); + + if (G_IS_SEEKABLE (stream) && g_seekable_can_seek (G_SEEKABLE (stream))) + { + if (g_seekable_seek (G_SEEKABLE (stream), + count, + G_SEEK_CUR, + cancellable, + NULL)) + return count; + } + + /* If not seekable, or seek failed, fall back to reading data: */ + + class = G_INPUT_STREAM_GET_CLASS (stream); + + read_bytes = 0; + while (1) + { + my_error = NULL; + + ret = class->read (stream, buffer, MIN (sizeof (buffer), count), + cancellable, &my_error); + if (ret == -1) + { + if (read_bytes > 0 && + my_error->domain == G_IO_ERROR && + my_error->code == G_IO_ERROR_CANCELLED) + { + g_error_free (my_error); + return read_bytes; + } + + g_propagate_error (error, my_error); + return -1; + } + + count -= ret; + read_bytes += ret; + + if (ret == 0 || count == 0) + return read_bytes; + } +} + +/** + * g_input_stream_close: + * @stream: A #GInputStream. + * @error: location to store the error occuring, or %NULL to ignore + * + * Closes the stream, releasing resources related to it. + * + * Once the stream is closed, all other operations will return %G_IO_ERROR_CLOSED. + * Closing a stream multiple times will not return an error. + * + * Streams will be automatically closed when the last reference + * is dropped, but you might want to call make sure resources + * are released as early as possible. + * + * Some streams might keep the backing store of the stream (e.g. a file descriptor) + * open after the stream is closed. See the documentation for the individual + * stream for details. + * + * On failure the first error that happened will be reported, but the close + * operation will finish as much as possible. A stream that failed to + * close will still return %G_IO_ERROR_CLOSED all operations. Still, it + * is important to check and report the error to the user. + * + * If @cancellable is not NULL, then the operation can be cancelled by + * triggering the cancellable object from another thread. If the operation + * was cancelled, the error %G_IO_ERROR_CANCELLED will be returned. + * Cancelling a close will still leave the stream closed, but some streams + * can use a faster close that doesn't block to e.g. check errors. + * + * Return value: %TRUE on success, %FALSE on failure + **/ +gboolean +g_input_stream_close (GInputStream *stream, + GCancellable *cancellable, + GError **error) +{ + GInputStreamClass *class; + gboolean res; + + g_return_val_if_fail (G_IS_INPUT_STREAM (stream), FALSE); + + class = G_INPUT_STREAM_GET_CLASS (stream); + + if (stream->priv->closed) + return TRUE; + + if (stream->priv->pending) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_PENDING, + _("Stream has outstanding operation")); + return FALSE; + } + + res = TRUE; + + stream->priv->pending = TRUE; + + if (cancellable) + g_push_current_cancellable (cancellable); + + if (class->close) + res = class->close (stream, cancellable, error); + + if (cancellable) + g_pop_current_cancellable (cancellable); + + stream->priv->closed = TRUE; + + stream->priv->pending = FALSE; + + return res; +} + +static void +async_ready_callback_wrapper (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + GInputStream *stream = G_INPUT_STREAM (source_object); + + stream->priv->pending = FALSE; + if (stream->priv->outstanding_callback) + (*stream->priv->outstanding_callback) (source_object, res, user_data); + g_object_unref (stream); +} + +static void +async_ready_close_callback_wrapper (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + GInputStream *stream = G_INPUT_STREAM (source_object); + + stream->priv->pending = FALSE; + stream->priv->closed = TRUE; + if (stream->priv->outstanding_callback) + (*stream->priv->outstanding_callback) (source_object, res, user_data); + g_object_unref (stream); +} + +/** + * g_input_stream_read_async: + * @stream: A #GInputStream. + * @buffer: a buffer to read data into (which should be at least count bytes long). + * @count: the number of bytes that will be read from the stream + * @io_priority: the io priority of the request. the io priority of the request + * @cancellable: optional #GCancellable object, %NULL to ignore. + * @callback: callback to call when the request is satisfied + * @user_data: the data to pass to callback function + * + * Request an asynchronous read of @count bytes from the stream into the buffer + * starting at @buffer. When the operation is finished @callback will be called, + * giving the results. + * + * During an async request no other sync and async calls are allowed, and will + * result in %G_IO_ERROR_PENDING errors. + * + * A value of @count larger than %G_MAXSSIZE will cause a %G_IO_ERROR_INVALID_ARGUMENT error. + * + * On success, the number of bytes read into the buffer will be passed to the + * callback. It is not an error if this is not the same as the requested size, as it + * can happen e.g. near the end of a file, but generally we try to read + * as many bytes as requested. Zero is returned on end of file + * (or if @count is zero), but never otherwise. + * + * Any outstanding i/o request with higher priority (lower numerical value) will + * be executed before an outstanding request with lower priority. Default + * priority is %G_PRIORITY_DEFAULT. + * + * The asyncronous methods have a default fallback that uses threads to implement + * asynchronicity, so they are optional for inheriting classes. However, if you + * override one you must override all. + **/ +void +g_input_stream_read_async (GInputStream *stream, + void *buffer, + gsize count, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GInputStreamClass *class; + GSimpleAsyncResult *simple; + + g_return_if_fail (G_IS_INPUT_STREAM (stream)); + g_return_if_fail (buffer != NULL); + + if (count == 0) + { + simple = g_simple_async_result_new (G_OBJECT (stream), + callback, + user_data, + g_input_stream_read_async); + g_simple_async_result_complete_in_idle (simple); + g_object_unref (simple); + return; + } + + if (((gssize) count) < 0) + { + g_simple_async_report_error_in_idle (G_OBJECT (stream), + callback, + user_data, + G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, + _("Too large count value passed to g_input_stream_read_async")); + return; + } + + if (stream->priv->closed) + { + g_simple_async_report_error_in_idle (G_OBJECT (stream), + callback, + user_data, + G_IO_ERROR, G_IO_ERROR_CLOSED, + _("Stream is already closed")); + return; + } + + if (stream->priv->pending) + { + g_simple_async_report_error_in_idle (G_OBJECT (stream), + callback, + user_data, + G_IO_ERROR, G_IO_ERROR_PENDING, + _("Stream has outstanding operation")); + return; + } + + class = G_INPUT_STREAM_GET_CLASS (stream); + + stream->priv->pending = TRUE; + stream->priv->outstanding_callback = callback; + g_object_ref (stream); + class->read_async (stream, buffer, count, io_priority, cancellable, + async_ready_callback_wrapper, user_data); +} + +/** + * g_input_stream_read_finish: + * @stream: + * @result: + * @error: a #GError location to store the error occuring, or %NULL to + * ignore. + * Returns: + **/ +gssize +g_input_stream_read_finish (GInputStream *stream, + GAsyncResult *result, + GError **error) +{ + GSimpleAsyncResult *simple; + GInputStreamClass *class; + + g_return_val_if_fail (G_IS_INPUT_STREAM (stream), -1); + g_return_val_if_fail (G_IS_ASYNC_RESULT (result), -1); + + if (G_IS_SIMPLE_ASYNC_RESULT (result)) + { + simple = G_SIMPLE_ASYNC_RESULT (result); + if (g_simple_async_result_propagate_error (simple, error)) + return -1; + + /* Special case read of 0 bytes */ + if (g_simple_async_result_get_source_tag (simple) == g_input_stream_read_async) + return 0; + } + + class = G_INPUT_STREAM_GET_CLASS (stream); + return class->read_finish (stream, result, error); +} + +/** + * g_input_stream_skip_async: + * @stream: A #GInputStream. + * @count: the number of bytes that will be skipped from the stream + * @io_priority: the io priority of the request. the io priority of the request + * @cancellable: optional #GCancellable object, %NULL to ignore. + * @callback: callback to call when the request is satisfied + * @user_data: the data to pass to callback function + * + * Request an asynchronous skip of @count bytes from the stream into the buffer + * starting at @buffer. When the operation is finished @callback will be called, + * giving the results. + * + * During an async request no other sync and async calls are allowed, and will + * result in %G_IO_ERROR_PENDING errors. + * + * A value of @count larger than %G_MAXSSIZE will cause a %G_IO_ERROR_INVALID_ARGUMENT error. + * + * On success, the number of bytes skipped will be passed to the + * callback. It is not an error if this is not the same as the requested size, as it + * can happen e.g. near the end of a file, but generally we try to skip + * as many bytes as requested. Zero is returned on end of file + * (or if @count is zero), but never otherwise. + * + * Any outstanding i/o request with higher priority (lower numerical value) will + * be executed before an outstanding request with lower priority. Default + * priority is %G_PRIORITY_DEFAULT. + * + * The asyncronous methods have a default fallback that uses threads to implement + * asynchronicity, so they are optional for inheriting classes. However, if you + * override one you must override all. + **/ +void +g_input_stream_skip_async (GInputStream *stream, + gsize count, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GInputStreamClass *class; + GSimpleAsyncResult *simple; + + g_return_if_fail (G_IS_INPUT_STREAM (stream)); + + if (count == 0) + { + simple = g_simple_async_result_new (G_OBJECT (stream), + callback, + user_data, + g_input_stream_skip_async); + + g_simple_async_result_complete_in_idle (simple); + g_object_unref (simple); + return; + } + + if (((gssize) count) < 0) + { + g_simple_async_report_error_in_idle (G_OBJECT (stream), + callback, + user_data, + G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, + _("Too large count value passed to g_input_stream_skip_async")); + return; + } + + if (stream->priv->closed) + { + g_simple_async_report_error_in_idle (G_OBJECT (stream), + callback, + user_data, + G_IO_ERROR, G_IO_ERROR_CLOSED, + _("Stream is already closed")); + return; + } + + if (stream->priv->pending) + { + g_simple_async_report_error_in_idle (G_OBJECT (stream), + callback, + user_data, + G_IO_ERROR, G_IO_ERROR_PENDING, + _("Stream has outstanding operation")); + return; + } + + class = G_INPUT_STREAM_GET_CLASS (stream); + stream->priv->pending = TRUE; + stream->priv->outstanding_callback = callback; + g_object_ref (stream); + class->skip_async (stream, count, io_priority, cancellable, + async_ready_callback_wrapper, user_data); +} + +/** + * g_input_stream_skip_finish: + * @stream: + * @result: + * @error: a #GError location to store the error occuring, or %NULL to + * ignore. + * Returns: + **/ +gssize +g_input_stream_skip_finish (GInputStream *stream, + GAsyncResult *result, + GError **error) +{ + GSimpleAsyncResult *simple; + GInputStreamClass *class; + + g_return_val_if_fail (G_IS_INPUT_STREAM (stream), -1); + g_return_val_if_fail (G_IS_ASYNC_RESULT (result), -1); + + if (G_IS_SIMPLE_ASYNC_RESULT (result)) + { + simple = G_SIMPLE_ASYNC_RESULT (result); + if (g_simple_async_result_propagate_error (simple, error)) + return -1; + + /* Special case skip of 0 bytes */ + if (g_simple_async_result_get_source_tag (simple) == g_input_stream_skip_async) + return 0; + } + + class = G_INPUT_STREAM_GET_CLASS (stream); + return class->skip_finish (stream, result, error); +} + +/** + * g_input_stream_close_async: + * @stream: A #GInputStream. + * @io_priority: the io priority of the request. the io priority of the request + * @cancellable: optional cancellable object + * @callback: callback to call when the request is satisfied + * @user_data: the data to pass to callback function + * + * Requests an asynchronous closes of the stream, releasing resources related to it. + * When the operation is finished @callback will be called, giving the results. + * + * For behaviour details see g_input_stream_close(). + * + * The asyncronous methods have a default fallback that uses threads to implement + * asynchronicity, so they are optional for inheriting classes. However, if you + * override one you must override all. + **/ +void +g_input_stream_close_async (GInputStream *stream, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GInputStreamClass *class; + GSimpleAsyncResult *simple; + + g_return_if_fail (G_IS_INPUT_STREAM (stream)); + + if (stream->priv->closed) + { + simple = g_simple_async_result_new (G_OBJECT (stream), + callback, + user_data, + g_input_stream_close_async); + + g_simple_async_result_complete_in_idle (simple); + g_object_unref (simple); + return; + } + + if (stream->priv->pending) + { + g_simple_async_report_error_in_idle (G_OBJECT (stream), + callback, + user_data, + G_IO_ERROR, G_IO_ERROR_PENDING, + _("Stream has outstanding operation")); + return; + } + + class = G_INPUT_STREAM_GET_CLASS (stream); + stream->priv->pending = TRUE; + stream->priv->outstanding_callback = callback; + g_object_ref (stream); + class->close_async (stream, io_priority, cancellable, + async_ready_close_callback_wrapper, user_data); +} + +/** + * g_input_stream_close_finish: + * @stream: + * @result: + * @error: a #GError location to store the error occuring, or %NULL to + * ignore. + * Returns: + **/ +gboolean +g_input_stream_close_finish (GInputStream *stream, + GAsyncResult *result, + GError **error) +{ + GSimpleAsyncResult *simple; + GInputStreamClass *class; + + g_return_val_if_fail (G_IS_INPUT_STREAM (stream), FALSE); + g_return_val_if_fail (G_IS_ASYNC_RESULT (result), FALSE); + + if (G_IS_SIMPLE_ASYNC_RESULT (result)) + { + simple = G_SIMPLE_ASYNC_RESULT (result); + if (g_simple_async_result_propagate_error (simple, error)) + return FALSE; + + /* Special case already closed */ + if (g_simple_async_result_get_source_tag (simple) == g_input_stream_close_async) + return TRUE; + } + + class = G_INPUT_STREAM_GET_CLASS (stream); + return class->close_finish (stream, result, error); +} + +/** + * g_input_stream_is_closed: + * @stream: input stream. + * + * Returns: %TRUE if the stream is closed. + **/ +gboolean +g_input_stream_is_closed (GInputStream *stream) +{ + g_return_val_if_fail (G_IS_INPUT_STREAM (stream), TRUE); + + return stream->priv->closed; +} + +/** + * g_input_stream_has_pending: + * @stream: input stream. + * + * Returns: %TRUE if @stream has pending actions. + **/ +gboolean +g_input_stream_has_pending (GInputStream *stream) +{ + g_return_val_if_fail (G_IS_INPUT_STREAM (stream), TRUE); + + return stream->priv->pending; +} + +/** + * g_input_stream_set_pending: + * @stream: input stream + * @pending: boolean. + * + * Sets @stream has actions pending. + **/ +void +g_input_stream_set_pending (GInputStream *stream, + gboolean pending) +{ + g_return_if_fail (G_IS_INPUT_STREAM (stream)); + + stream->priv->pending = pending; +} + +/******************************************** + * Default implementation of async ops * + ********************************************/ + +typedef struct { + void *buffer; + gsize count_requested; + gssize count_read; +} ReadData; + +static void +read_async_thread (GSimpleAsyncResult *res, + GObject *object, + GCancellable *cancellable) +{ + ReadData *op; + GInputStreamClass *class; + GError *error = NULL; + + op = g_simple_async_result_get_op_res_gpointer (res); + + class = G_INPUT_STREAM_GET_CLASS (object); + + op->count_read = class->read (G_INPUT_STREAM (object), + op->buffer, op->count_requested, + cancellable, &error); + if (op->count_read == -1) + { + g_simple_async_result_set_from_error (res, error); + g_error_free (error); + } +} + +static void +g_input_stream_real_read_async (GInputStream *stream, + void *buffer, + gsize count, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GSimpleAsyncResult *res; + ReadData *op; + + op = g_new (ReadData, 1); + res = g_simple_async_result_new (G_OBJECT (stream), callback, user_data, g_input_stream_real_read_async); + g_simple_async_result_set_op_res_gpointer (res, op, g_free); + op->buffer = buffer; + op->count_requested = count; + + g_simple_async_result_run_in_thread (res, read_async_thread, io_priority, cancellable); + g_object_unref (res); +} + +static gssize +g_input_stream_real_read_finish (GInputStream *stream, + GAsyncResult *result, + GError **error) +{ + GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (result); + ReadData *op; + + g_assert (g_simple_async_result_get_source_tag (simple) == + g_input_stream_real_read_async); + + op = g_simple_async_result_get_op_res_gpointer (simple); + + return op->count_read; +} + +typedef struct { + gsize count_requested; + gssize count_skipped; +} SkipData; + + +static void +skip_async_thread (GSimpleAsyncResult *res, + GObject *object, + GCancellable *cancellable) +{ + SkipData *op; + GInputStreamClass *class; + GError *error = NULL; + + class = G_INPUT_STREAM_GET_CLASS (object); + op = g_simple_async_result_get_op_res_gpointer (res); + op->count_skipped = class->skip (G_INPUT_STREAM (object), + op->count_requested, + cancellable, &error); + if (op->count_skipped == -1) + { + g_simple_async_result_set_from_error (res, error); + g_error_free (error); + } +} + +typedef struct { + char buffer[8192]; + gsize count; + gsize count_skipped; + int io_prio; + GCancellable *cancellable; + gpointer user_data; + GAsyncReadyCallback callback; +} SkipFallbackAsyncData; + +static void +skip_callback_wrapper (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + GInputStreamClass *class; + SkipFallbackAsyncData *data = user_data; + SkipData *op; + GSimpleAsyncResult *simple; + GError *error = NULL; + gssize ret; + + ret = g_input_stream_read_finish (G_INPUT_STREAM (source_object), res, &error); + + if (ret > 0) + { + data->count -= ret; + data->count_skipped += ret; + + if (data->count > 0) + { + class = G_INPUT_STREAM_GET_CLASS (source_object); + class->read_async (G_INPUT_STREAM (source_object), data->buffer, MIN (8192, data->count), data->io_prio, data->cancellable, + skip_callback_wrapper, data); + return; + } + } + + op = g_new0 (SkipData, 1); + op->count_skipped = data->count_skipped; + simple = g_simple_async_result_new (source_object, + data->callback, data->user_data, + g_input_stream_real_skip_async); + + g_simple_async_result_set_op_res_gpointer (simple, op, g_free); + + if (ret == -1) + { + if (data->count_skipped && + error->domain == G_IO_ERROR && + error->code == G_IO_ERROR_CANCELLED) + { /* No error, return partial read */ } + else + g_simple_async_result_set_from_error (simple, error); + g_error_free (error); + } + + /* Complete immediately, not in idle, since we're already in a mainloop callout */ + g_simple_async_result_complete (simple); + g_object_unref (simple); + + g_free (data); + } + +static void +g_input_stream_real_skip_async (GInputStream *stream, + gsize count, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GInputStreamClass *class; + SkipData *op; + SkipFallbackAsyncData *data; + GSimpleAsyncResult *res; + + class = G_INPUT_STREAM_GET_CLASS (stream); + + if (class->read_async == g_input_stream_real_read_async) + { + /* Read is thread-using async fallback. + * Make skip use threads too, so that we can use a possible sync skip + * implementation. */ + op = g_new0 (SkipData, 1); + + res = g_simple_async_result_new (G_OBJECT (stream), callback, user_data, + g_input_stream_real_skip_async); + + g_simple_async_result_set_op_res_gpointer (res, op, g_free); + + op->count_requested = count; + + g_simple_async_result_run_in_thread (res, skip_async_thread, io_priority, cancellable); + g_object_unref (res); + } + else + { + /* TODO: Skip fallback uses too much memory, should do multiple read calls */ + + /* There is a custom async read function, lets use that. */ + data = g_new (SkipFallbackAsyncData, 1); + data->count = count; + data->count_skipped = 0; + data->io_prio = io_priority; + data->cancellable = cancellable; + data->callback = callback; + data->user_data = user_data; + class->read_async (stream, data->buffer, MIN (8192, count), io_priority, cancellable, + skip_callback_wrapper, data); + } + +} + +static gssize +g_input_stream_real_skip_finish (GInputStream *stream, + GAsyncResult *result, + GError **error) +{ + GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (result); + SkipData *op; + + g_assert (g_simple_async_result_get_source_tag (simple) == g_input_stream_real_skip_async); + op = g_simple_async_result_get_op_res_gpointer (simple); + return op->count_skipped; +} + +static void +close_async_thread (GSimpleAsyncResult *res, + GObject *object, + GCancellable *cancellable) +{ + GInputStreamClass *class; + GError *error = NULL; + gboolean result; + + /* Auto handling of cancelation disabled, and ignore + cancellation, since we want to close things anyway, although + possibly in a quick-n-dirty way. At least we never want to leak + open handles */ + + class = G_INPUT_STREAM_GET_CLASS (object); + result = class->close (G_INPUT_STREAM (object), cancellable, &error); + if (!result) + { + g_simple_async_result_set_from_error (res, error); + g_error_free (error); + } +} + +static void +g_input_stream_real_close_async (GInputStream *stream, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GSimpleAsyncResult *res; + + res = g_simple_async_result_new (G_OBJECT (stream), + callback, + user_data, + g_input_stream_real_close_async); + + g_simple_async_result_set_handle_cancellation (res, FALSE); + + g_simple_async_result_run_in_thread (res, + close_async_thread, + io_priority, + cancellable); + g_object_unref (res); +} + +static gboolean +g_input_stream_real_close_finish (GInputStream *stream, + GAsyncResult *result, + GError **error) +{ + GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (result); + g_assert (g_simple_async_result_get_source_tag (simple) == g_input_stream_real_close_async); + return TRUE; +} diff --git a/gio/ginputstream.h b/gio/ginputstream.h new file mode 100644 index 000000000..7df266298 --- /dev/null +++ b/gio/ginputstream.h @@ -0,0 +1,165 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#ifndef __G_INPUT_STREAM_H__ +#define __G_INPUT_STREAM_H__ + +#include <glib-object.h> +#include <gio/gioerror.h> +#include <gio/gcancellable.h> +#include <gio/gasyncresult.h> + +G_BEGIN_DECLS + +#define G_TYPE_INPUT_STREAM (g_input_stream_get_type ()) +#define G_INPUT_STREAM(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_INPUT_STREAM, GInputStream)) +#define G_INPUT_STREAM_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), G_TYPE_INPUT_STREAM, GInputStreamClass)) +#define G_IS_INPUT_STREAM(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_INPUT_STREAM)) +#define G_IS_INPUT_STREAM_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_INPUT_STREAM)) +#define G_INPUT_STREAM_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_TYPE_INPUT_STREAM, GInputStreamClass)) + +typedef struct _GInputStream GInputStream; +typedef struct _GInputStreamClass GInputStreamClass; +typedef struct _GInputStreamPrivate GInputStreamPrivate; + +struct _GInputStream +{ + GObject parent; + + /*< private >*/ + GInputStreamPrivate *priv; +}; + +struct _GInputStreamClass +{ + GObjectClass parent_class; + + /* Sync ops: */ + + gssize (* read) (GInputStream *stream, + void *buffer, + gsize count, + GCancellable *cancellable, + GError **error); + gssize (* skip) (GInputStream *stream, + gsize count, + GCancellable *cancellable, + GError **error); + gboolean (* close) (GInputStream *stream, + GCancellable *cancellable, + GError **error); + + /* Async ops: (optional in derived classes) */ + void (* read_async) (GInputStream *stream, + void *buffer, + gsize count, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + gssize (* read_finish) (GInputStream *stream, + GAsyncResult *result, + GError **error); + void (* skip_async) (GInputStream *stream, + gsize count, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + gssize (* skip_finish) (GInputStream *stream, + GAsyncResult *result, + GError **error); + void (* close_async) (GInputStream *stream, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + gboolean (* close_finish)(GInputStream *stream, + GAsyncResult *result, + GError **error); + + /* Padding for future expansion */ + void (*_g_reserved1) (void); + void (*_g_reserved2) (void); + void (*_g_reserved3) (void); + void (*_g_reserved4) (void); + void (*_g_reserved5) (void); +}; + +GType g_input_stream_get_type (void) G_GNUC_CONST; + +gssize g_input_stream_read (GInputStream *stream, + void *buffer, + gsize count, + GCancellable *cancellable, + GError **error); +gboolean g_input_stream_read_all (GInputStream *stream, + void *buffer, + gsize count, + gsize *bytes_read, + GCancellable *cancellable, + GError **error); +gssize g_input_stream_skip (GInputStream *stream, + gsize count, + GCancellable *cancellable, + GError **error); +gboolean g_input_stream_close (GInputStream *stream, + GCancellable *cancellable, + GError **error); +void g_input_stream_read_async (GInputStream *stream, + void *buffer, + gsize count, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +gssize g_input_stream_read_finish (GInputStream *stream, + GAsyncResult *result, + GError **error); +void g_input_stream_skip_async (GInputStream *stream, + gsize count, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +gssize g_input_stream_skip_finish (GInputStream *stream, + GAsyncResult *result, + GError **error); +void g_input_stream_close_async (GInputStream *stream, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean g_input_stream_close_finish (GInputStream *stream, + GAsyncResult *result, + GError **error); + +/* For implementations: */ + +gboolean g_input_stream_is_closed (GInputStream *stream); +gboolean g_input_stream_has_pending (GInputStream *stream); +void g_input_stream_set_pending (GInputStream *stream, + gboolean pending); + +G_END_DECLS + +#endif /* __G_INPUT_STREAM_H__ */ diff --git a/gio/gio-marshal.list b/gio/gio-marshal.list new file mode 100644 index 000000000..571666316 --- /dev/null +++ b/gio/gio-marshal.list @@ -0,0 +1,4 @@ +BOOLEAN:STRING,STRING,STRING,INT +BOOLEAN:STRING,POINTER +VOID:BOOLEAN,POINTER +VOID:OBJECT,OBJECT,INT diff --git a/gio/gioerror.c b/gio/gioerror.c new file mode 100644 index 000000000..9b1553d54 --- /dev/null +++ b/gio/gioerror.c @@ -0,0 +1,155 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#include <config.h> +#include <errno.h> +#include "gioerror.h" + +/** + * g_io_error_quark: + * + * Return value: The quark used as %G_IO_ERROR + **/ +GQuark +g_io_error_quark (void) +{ + return g_quark_from_static_string ("g-io-error-quark"); +} + +GIOErrorEnum +g_io_error_from_errno (gint err_no) +{ + switch (err_no) + { +#ifdef EEXIST + case EEXIST: + return G_IO_ERROR_EXISTS; + break; +#endif + +#ifdef EISDIR + case EISDIR: + return G_IO_ERROR_IS_DIRECTORY; + break; +#endif + +#ifdef EACCES + case EACCES: + return G_IO_ERROR_PERMISSION_DENIED; + break; +#endif + +#ifdef ENAMETOOLONG + case ENAMETOOLONG: + return G_IO_ERROR_FILENAME_TOO_LONG; + break; +#endif + +#ifdef ENOENT + case ENOENT: + return G_IO_ERROR_NOT_FOUND; + break; +#endif + +#ifdef ENOTDIR + case ENOTDIR: + return G_IO_ERROR_NOT_DIRECTORY; + break; +#endif + +#ifdef EROFS + case EROFS: + return G_IO_ERROR_READ_ONLY; + break; +#endif + +#ifdef ELOOP + case ELOOP: + return G_IO_ERROR_TOO_MANY_LINKS; + break; +#endif + +#ifdef ENOSPC + case ENOSPC: + return G_IO_ERROR_NO_SPACE; + break; +#endif + +#ifdef ENOMEM + case ENOMEM: + return G_IO_ERROR_NO_SPACE; + break; +#endif + +#ifdef EINVAL + case EINVAL: + return G_IO_ERROR_INVALID_ARGUMENT; + break; +#endif + +#ifdef EPERM + case EPERM: + return G_IO_ERROR_PERMISSION_DENIED; + break; +#endif + +#ifdef ECANCELED + case ECANCELED: + return G_IO_ERROR_CANCELLED; + break; +#endif + +#ifdef ENOTEMPTY + case ENOTEMPTY: + return G_IO_ERROR_NOT_EMPTY; + break; +#endif + +#ifdef ENOTSUP + case ENOTSUP: + return G_IO_ERROR_NOT_SUPPORTED; + break; +#endif + +#ifdef ETIMEDOUT + case ETIMEDOUT: + return G_IO_ERROR_TIMED_OUT; + break; +#endif + +#ifdef EBUSY + case EBUSY: + return G_IO_ERROR_BUSY; + break; +#endif + +#ifdef EWOULDBLOCK + case EWOULDBLOCK: + return G_IO_ERROR_WOULD_BLOCK; + break; +#endif + + default: + return G_IO_ERROR_FAILED; + break; + } +} diff --git a/gio/gioerror.h b/gio/gioerror.h new file mode 100644 index 000000000..c4606fcec --- /dev/null +++ b/gio/gioerror.h @@ -0,0 +1,78 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#ifndef __G_IO_ERROR_H__ +#define __G_IO_ERROR_H__ + +#include <glib/gerror.h> + +G_BEGIN_DECLS + +GQuark g_io_error_quark (void); + +#define G_IO_ERROR g_io_error_quark() + +/* This enumeration conflicts with GIOError in giochannel.h. However, + * that is only used as a return value in some deprecated functions. + * So, we reuse the same prefix for the enumeration values, but call + * the actual enumeration (which is rarely used) GIOErrorEnum. + */ + +typedef enum +{ + G_IO_ERROR_FAILED, + G_IO_ERROR_NOT_FOUND, + G_IO_ERROR_EXISTS, + G_IO_ERROR_IS_DIRECTORY, + G_IO_ERROR_NOT_DIRECTORY, + G_IO_ERROR_NOT_EMPTY, + G_IO_ERROR_NOT_REGULAR_FILE, + G_IO_ERROR_NOT_SYMBOLIC_LINK, + G_IO_ERROR_NOT_MOUNTABLE_FILE, + G_IO_ERROR_FILENAME_TOO_LONG, + G_IO_ERROR_INVALID_FILENAME, + G_IO_ERROR_TOO_MANY_LINKS, + G_IO_ERROR_NO_SPACE, + G_IO_ERROR_INVALID_ARGUMENT, + G_IO_ERROR_PERMISSION_DENIED, + G_IO_ERROR_NOT_SUPPORTED, + G_IO_ERROR_NOT_MOUNTED, + G_IO_ERROR_ALREADY_MOUNTED, + G_IO_ERROR_CLOSED, + G_IO_ERROR_CANCELLED, + G_IO_ERROR_PENDING, + G_IO_ERROR_READ_ONLY, + G_IO_ERROR_CANT_CREATE_BACKUP, + G_IO_ERROR_WRONG_ETAG, + G_IO_ERROR_TIMED_OUT, + G_IO_ERROR_WOULD_RECURSE, + G_IO_ERROR_BUSY, + G_IO_ERROR_WOULD_BLOCK, + G_IO_ERROR_HOST_NOT_FOUND, + G_IO_ERROR_WOULD_MERGE +} GIOErrorEnum; + +GIOErrorEnum g_io_error_from_errno (gint err_no); + +G_END_DECLS + +#endif /* __G_IO_ERROR_H__ */ diff --git a/gio/giomodule.c b/gio/giomodule.c new file mode 100644 index 000000000..ab7c29d14 --- /dev/null +++ b/gio/giomodule.c @@ -0,0 +1,237 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#include <config.h> + +#include "giomodule.h" + +struct _GIOModule { + GTypeModule parent_instance; + + gchar *filename; + GModule *library; + + void (* load) (GIOModule *module); + void (* unload) (GIOModule *module); +}; + +struct _GIOModuleClass +{ + GTypeModuleClass parent_class; + +}; + +static void g_io_module_finalize (GObject *object); +static gboolean g_io_module_load_module (GTypeModule *gmodule); +static void g_io_module_unload_module (GTypeModule *gmodule); + +G_DEFINE_TYPE (GIOModule, g_io_module, G_TYPE_TYPE_MODULE); + +static void +g_io_module_class_init (GIOModuleClass *class) +{ + GObjectClass *object_class = G_OBJECT_CLASS (class); + GTypeModuleClass *type_module_class = G_TYPE_MODULE_CLASS (class); + + object_class->finalize = g_io_module_finalize; + + type_module_class->load = g_io_module_load_module; + type_module_class->unload = g_io_module_unload_module; +} + +static void +g_io_module_init (GIOModule *module) +{ +} + +static void +g_io_module_finalize (GObject *object) +{ + GIOModule *module = G_IO_MODULE (object); + + g_free (module->filename); + + G_OBJECT_CLASS (g_io_module_parent_class)->finalize (object); +} + +static gboolean +g_io_module_load_module (GTypeModule *gmodule) +{ + GIOModule *module = G_IO_MODULE (gmodule); + + if (!module->filename) + { + g_warning ("GIOModule path not set"); + return FALSE; + } + + module->library = g_module_open (module->filename, G_MODULE_BIND_LAZY | G_MODULE_BIND_LOCAL); + + if (!module->library) + { + g_printerr ("%s\n", g_module_error ()); + return FALSE; + } + + /* Make sure that the loaded library contains the required methods */ + if (! g_module_symbol (module->library, + "g_io_module_load", + (gpointer *) &module->load) || + ! g_module_symbol (module->library, + "g_io_module_unload", + (gpointer *) &module->unload)) + { + g_printerr ("%s\n", g_module_error ()); + g_module_close (module->library); + + return FALSE; + } + + /* Initialize the loaded module */ + module->load (module); + + return TRUE; +} + +static void +g_io_module_unload_module (GTypeModule *gmodule) +{ + GIOModule *module = G_IO_MODULE (gmodule); + + module->unload (module); + + g_module_close (module->library); + module->library = NULL; + + module->load = NULL; + module->unload = NULL; +} + +/** + * g_io_module_new: + * @filename: filename of the module to load. + * + * Returns: a new #GIOModule from given @filename, + * or %NULL on error. + **/ +GIOModule * +g_io_module_new (const gchar *filename) +{ + GIOModule *module; + + g_return_val_if_fail (filename != NULL, NULL); + + module = g_object_new (G_IO_TYPE_MODULE, NULL); + module->filename = g_strdup (filename); + + return module; +} + +static gboolean +is_valid_module_name (const gchar *basename) +{ +#if !defined(G_OS_WIN32) && !defined(G_WITH_CYGWIN) + return + g_str_has_prefix (basename, "lib") && + g_str_has_suffix (basename, ".so"); +#else + return g_str_has_suffix (basename, ".dll"); +#endif +} + +static GList * +load_modules (const char *dirname) +{ + const gchar *name; + GDir *dir; + GList *modules; + + if (!g_module_supported ()) + return NULL; + + dir = g_dir_open (dirname, 0, NULL); + if (!dir) + return NULL; + + modules = NULL; + while ((name = g_dir_read_name (dir))) + { + if (is_valid_module_name (name)) + { + GIOModule *module; + gchar *path; + + path = g_build_filename (dirname, name, NULL); + module = g_io_module_new (path); + + if (!g_type_module_use (G_TYPE_MODULE (module))) + { + g_printerr ("Failed to load module: %s\n", path); + g_object_unref (module); + g_free (path); + continue; + } + + g_free (path); + + g_type_module_unuse (G_TYPE_MODULE (module)); + + modules = g_list_prepend (modules, module); + } + } + + g_dir_close (dir); + + return modules; +} + +G_LOCK_DEFINE_STATIC (loaded_dirs); +static GHashTable *loaded_dirs = NULL; + +/** + * g_io_module_ensure_loaded: + * @directory: directory to ensure is loaded. + * + **/ +void +g_io_modules_ensure_loaded (const char *directory) +{ + GList *modules; + + g_return_if_fail (directory != NULL); + + G_LOCK (loaded_dirs); + + if (loaded_dirs == NULL) + loaded_dirs = g_hash_table_new (g_str_hash, g_str_equal); + + if (!g_hash_table_lookup_extended (loaded_dirs, directory, + NULL, NULL)) + { + modules = load_modules (directory); + g_hash_table_insert (loaded_dirs, + g_strdup (directory), + modules); + } + + G_UNLOCK (loaded_dirs); +} diff --git a/gio/giomodule.h b/gio/giomodule.h new file mode 100644 index 000000000..44ed052f1 --- /dev/null +++ b/gio/giomodule.h @@ -0,0 +1,52 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#ifndef __G_IO_MODULE_H__ +#define __G_IO_MODULE_H__ + +#include <glib-object.h> +#include <gmodule.h> + +G_BEGIN_DECLS + +#define G_IO_TYPE_MODULE (g_io_module_get_type ()) +#define G_IO_MODULE(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_IO_TYPE_MODULE, GIOModule)) +#define G_IO_MODULE_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), G_IO_TYPE_MODULE, GIOModuleClass)) +#define G_IO_IS_MODULE(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_IO_TYPE_MODULE)) +#define G_IO_IS_MODULE_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), G_IO_TYPE_MODULE)) +#define G_IO_MODULE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_IO_TYPE_MODULE, GIOModuleClass)) + +typedef struct _GIOModule GIOModule; +typedef struct _GIOModuleClass GIOModuleClass; + +GType g_io_module_get_type (void) G_GNUC_CONST; +GIOModule *g_io_module_new (const gchar *filename); + +void g_io_modules_ensure_loaded (const char *directory); + +/* API for the modules to implement */ +void g_io_module_load (GIOModule *module); +void g_io_module_unload (GIOModule *module); + +G_END_DECLS + +#endif /* __G_IO_MODULE_H__ */ diff --git a/gio/gioscheduler.c b/gio/gioscheduler.c new file mode 100644 index 000000000..1c5f5485d --- /dev/null +++ b/gio/gioscheduler.c @@ -0,0 +1,372 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#include <config.h> + +#include "gioscheduler.h" + +struct _GIOJob { + GSList *active_link; + GIOJobFunc job_func; + GIODataFunc cancel_func; /* Runs under job map lock */ + gpointer data; + GDestroyNotify destroy_notify; + + gint io_priority; + GCancellable *cancellable; + + guint idle_tag; +}; + +G_LOCK_DEFINE_STATIC(active_jobs); +static GSList *active_jobs = NULL; + +static GThreadPool *job_thread_pool = NULL; + +static void io_job_thread (gpointer data, + gpointer user_data); + +static void +g_io_job_free (GIOJob *job) +{ + if (job->cancellable) + g_object_unref (job->cancellable); + g_free (job); +} + +static gint +g_io_job_compare (gconstpointer a, + gconstpointer b, + gpointer user_data) +{ + const GIOJob *aa = a; + const GIOJob *bb = b; + + /* Cancelled jobs are set prio == -1, so that + they are executed as quickly as possible */ + + /* Lower value => higher priority */ + if (aa->io_priority < bb->io_priority) + return -1; + if (aa->io_priority == bb->io_priority) + return 0; + return 1; +} + +static gpointer +init_scheduler (gpointer arg) +{ + if (job_thread_pool == NULL) + { + /* TODO: thread_pool_new can fail */ + job_thread_pool = g_thread_pool_new (io_job_thread, + NULL, + 10, + FALSE, + NULL); + if (job_thread_pool != NULL) + { + g_thread_pool_set_sort_function (job_thread_pool, + g_io_job_compare, + NULL); + /* Its kinda weird that this is a global setting + * instead of per threadpool. However, we really + * want to cache some threads, but not keep around + * those threads forever. */ + g_thread_pool_set_max_idle_time (15 * 1000); + g_thread_pool_set_max_unused_threads (2); + } + } + return NULL; +} + +static void +remove_active_job (GIOJob *job) +{ + GIOJob *other_job; + GSList *l; + gboolean resort_jobs; + + G_LOCK (active_jobs); + active_jobs = g_slist_delete_link (active_jobs, job->active_link); + + resort_jobs = FALSE; + for (l = active_jobs; l != NULL; l = l->next) + { + other_job = l->data; + if (other_job->io_priority >= 0 && + g_cancellable_is_cancelled (other_job->cancellable)) + { + other_job->io_priority = -1; + resort_jobs = TRUE; + } + } + G_UNLOCK (active_jobs); + + if (resort_jobs && + job_thread_pool != NULL) + g_thread_pool_set_sort_function (job_thread_pool, + g_io_job_compare, + NULL); + +} + +static void +io_job_thread (gpointer data, + gpointer user_data) +{ + GIOJob *job = data; + + if (job->cancellable) + g_push_current_cancellable (job->cancellable); + job->job_func (job, job->cancellable, job->data); + if (job->cancellable) + g_pop_current_cancellable (job->cancellable); + + if (job->destroy_notify) + job->destroy_notify (job->data); + + remove_active_job (job); + g_io_job_free (job); + +} + +static gboolean +run_job_at_idle (gpointer data) +{ + GIOJob *job = data; + + if (job->cancellable) + g_push_current_cancellable (job->cancellable); + + job->job_func (job, job->cancellable, job->data); + + if (job->cancellable) + g_pop_current_cancellable (job->cancellable); + + if (job->destroy_notify) + job->destroy_notify (job->data); + + remove_active_job (job); + g_io_job_free (job); + + return FALSE; +} + +/** + * g_schedule_io_job: + * @job_func: a #GIOJobFunc. + * @user_data: a #gpointer. + * @notify: a #GDestroyNotify. + * @io_priority: the io priority of the request. a #gint. + * @cancellable: optional #GCancellable object, %NULL to ignore. + * + * Schedules the @job_func. + * + **/ +void +g_schedule_io_job (GIOJobFunc job_func, + gpointer user_data, + GDestroyNotify notify, + gint io_priority, + GCancellable *cancellable) +{ + static GOnce once_init = G_ONCE_INIT; + GIOJob *job; + + g_return_if_fail (job_func != NULL); + + job = g_new0 (GIOJob, 1); + job->job_func = job_func; + job->data = user_data; + job->destroy_notify = notify; + job->io_priority = io_priority; + + if (cancellable) + job->cancellable = g_object_ref (cancellable); + + G_LOCK (active_jobs); + active_jobs = g_slist_prepend (active_jobs, job); + job->active_link = active_jobs; + G_UNLOCK (active_jobs); + + if (g_thread_supported()) + { + g_once (&once_init, init_scheduler, NULL); + g_thread_pool_push (job_thread_pool, job, NULL); + } + else + { + /* Threads not available, instead do the i/o sync inside a + * low prio idle handler + */ + job->idle_tag = g_idle_add_full (G_PRIORITY_DEFAULT_IDLE + 1 + io_priority / 10, + run_job_at_idle, + job, NULL); + } +} + +/** + * g_cancel_all_io_jobs: + * + * Cancels all cancellable I/O Jobs. + **/ +void +g_cancel_all_io_jobs (void) +{ + GSList *cancellable_list, *l; + + G_LOCK (active_jobs); + cancellable_list = NULL; + for (l = active_jobs; l != NULL; l = l->next) + { + GIOJob *job = l->data; + if (job->cancellable) + cancellable_list = g_slist_prepend (cancellable_list, + g_object_ref (job->cancellable)); + } + G_UNLOCK (active_jobs); + + for (l = cancellable_list; l != NULL; l = l->next) + { + GCancellable *c = l->data; + g_cancellable_cancel (c); + g_object_unref (c); + } + g_slist_free (cancellable_list); +} + +typedef struct { + GIODataFunc func; + gpointer data; + GDestroyNotify notify; + + GMutex *ack_lock; + GCond *ack_condition; +} MainLoopProxy; + +static gboolean +mainloop_proxy_func (gpointer data) +{ + MainLoopProxy *proxy = data; + + proxy->func (proxy->data); + + if (proxy->ack_lock) + { + g_mutex_lock (proxy->ack_lock); + g_cond_signal (proxy->ack_condition); + g_mutex_unlock (proxy->ack_lock); + } + + return FALSE; +} + +static void +mainloop_proxy_free (MainLoopProxy *proxy) +{ + if (proxy->ack_lock) + { + g_mutex_free (proxy->ack_lock); + g_cond_free (proxy->ack_condition); + } + + g_free (proxy); +} + +static void +mainloop_proxy_notify (gpointer data) +{ + MainLoopProxy *proxy = data; + + if (proxy->notify) + proxy->notify (proxy->data); + + /* If nonblocking we free here, otherwise we free in io thread */ + if (proxy->ack_lock == NULL) + mainloop_proxy_free (proxy); +} + +/** + * g_io_job_send_to_mainloop: + * @job: a #GIOJob. + * @func: a #GIODataFunc. + * @user_data: a #gpointer. + * @notify: a #GDestroyNotify. + * @block: boolean flag indicating whether or not this job should block. + * + * + **/ +void +g_io_job_send_to_mainloop (GIOJob *job, + GIODataFunc func, + gpointer user_data, + GDestroyNotify notify, + gboolean block) +{ + GSource *source; + MainLoopProxy *proxy; + guint id; + + g_return_if_fail (job != NULL); + g_return_if_fail (func != NULL); + + if (job->idle_tag) + { + /* We just immediately re-enter in the case of idles (non-threads) + * Anything else would just deadlock. If you can't handle this, enable threads. + */ + func (user_data); + return; + } + + proxy = g_new0 (MainLoopProxy, 1); + proxy->func = func; + proxy->data = user_data; + proxy->notify = notify; + + if (block) + { + proxy->ack_lock = g_mutex_new (); + proxy->ack_condition = g_cond_new (); + } + + source = g_idle_source_new (); + g_source_set_priority (source, G_PRIORITY_DEFAULT); + + g_source_set_callback (source, mainloop_proxy_func, proxy, mainloop_proxy_notify); + + if (block) + g_mutex_lock (proxy->ack_lock); + + id = g_source_attach (source, NULL); + g_source_unref (source); + + if (block) + { + g_cond_wait (proxy->ack_condition, proxy->ack_lock); + g_mutex_unlock (proxy->ack_lock); + + /* destroy notify didn't free proxy */ + mainloop_proxy_free (proxy); + } +} diff --git a/gio/gioscheduler.h b/gio/gioscheduler.h new file mode 100644 index 000000000..73f2684e5 --- /dev/null +++ b/gio/gioscheduler.h @@ -0,0 +1,55 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#ifndef __G_IO_SCHEDULER_H__ +#define __G_IO_SCHEDULER_H__ + +#include <glib.h> +#include <gio/gcancellable.h> + +G_BEGIN_DECLS + +typedef struct _GIOJob GIOJob; + +typedef void (*GIOJobFunc) (GIOJob *job, + GCancellable *cancellable, + gpointer user_data); + +typedef void (*GIODataFunc) (gpointer user_data); + +void g_schedule_io_job (GIOJobFunc job_func, + gpointer user_data, + GDestroyNotify notify, + gint io_priority, + GCancellable *cancellable); +void g_cancel_all_io_jobs (void); + +void g_io_job_send_to_mainloop (GIOJob *job, + GIODataFunc func, + gpointer user_data, + GDestroyNotify notify, + gboolean block); + + +G_END_DECLS + +#endif /* __G_IO_SCHEDULER_H__ */ diff --git a/gio/gloadableicon.c b/gio/gloadableicon.c new file mode 100644 index 000000000..569065e94 --- /dev/null +++ b/gio/gloadableicon.c @@ -0,0 +1,260 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#include <config.h> + +#include "gsimpleasyncresult.h" +#include "gloadableicon.h" +#include "glibintl.h" + +static void g_loadable_icon_real_load_async (GLoadableIcon *icon, + int size, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +static GInputStream *g_loadable_icon_real_load_finish (GLoadableIcon *icon, + GAsyncResult *res, + char **type, + GError **error); +static void g_loadable_icon_base_init (gpointer g_class); +static void g_loadable_icon_class_init (gpointer g_class, + gpointer class_data); + +GType +g_loadable_icon_get_type (void) +{ + static GType loadable_icon_type = 0; + + if (! loadable_icon_type) + { + static const GTypeInfo loadable_icon_info = + { + sizeof (GLoadableIconIface), /* class_size */ + g_loadable_icon_base_init, /* base_init */ + NULL, /* base_finalize */ + g_loadable_icon_class_init, + NULL, /* class_finalize */ + NULL, /* class_data */ + 0, + 0, /* n_preallocs */ + NULL + }; + + loadable_icon_type = + g_type_register_static (G_TYPE_INTERFACE, I_("GLoadableIcon"), + &loadable_icon_info, 0); + + g_type_interface_add_prerequisite (loadable_icon_type, G_TYPE_ICON); + } + + return loadable_icon_type; +} + +static void +g_loadable_icon_class_init (gpointer g_class, + gpointer class_data) +{ + GLoadableIconIface *iface = g_class; + + iface->load_async = g_loadable_icon_real_load_async; + iface->load_finish = g_loadable_icon_real_load_finish; +} + +static void +g_loadable_icon_base_init (gpointer g_class) +{ +} + +/** + * g_loadable_icon_load: + * @icon: + * @size: + * @type: + * @cancellable: optional #GCancellable object, %NULL to ignore. + * @error: a #GError location to store the error occuring, or %NULL to + * ignore. + * Returns: + **/ +GInputStream * +g_loadable_icon_load (GLoadableIcon *icon, + int size, + char **type, + GCancellable *cancellable, + GError **error) +{ + GLoadableIconIface *iface; + + g_return_val_if_fail (G_IS_LOADABLE_ICON (icon), NULL); + + iface = G_LOADABLE_ICON_GET_IFACE (icon); + + return (* iface->load) (icon, size, type, cancellable, error); + +} + +/** + * g_loadable_icon_load_async: + * @icon: + * @size: + * @cancellable: optional #GCancellable object, %NULL to ignore. @callback: + * @user_data: + * + * Loads an icon asynchronously. + * + **/ +void +g_loadable_icon_load_async (GLoadableIcon *icon, + int size, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GLoadableIconIface *iface; + + g_return_if_fail (G_IS_LOADABLE_ICON (icon)); + + iface = G_LOADABLE_ICON_GET_IFACE (icon); + + (* iface->load_async) (icon, size, cancellable, callback, user_data); + +} + +/** + * g_loadable_icon_load_finish: + * @icon: + * @res: + * @type: + * @error: a #GError location to store the error occuring, or %NULL to + * ignore. + * Returns: + **/ +GInputStream * +g_loadable_icon_load_finish (GLoadableIcon *icon, + GAsyncResult *res, + char **type, + GError **error) +{ + GLoadableIconIface *iface; + + g_return_val_if_fail (G_IS_LOADABLE_ICON (icon), NULL); + g_return_val_if_fail (G_IS_ASYNC_RESULT (res), NULL); + + if (G_IS_SIMPLE_ASYNC_RESULT (res)) + { + GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (res); + if (g_simple_async_result_propagate_error (simple, error)) + return NULL; + } + + iface = G_LOADABLE_ICON_GET_IFACE (icon); + + return (* iface->load_finish) (icon, res, type, error); + +} + +/******************************************** + * Default implementation of async load * + ********************************************/ + +typedef struct { + int size; + char *type; + GInputStream *stream; +} LoadData; + +static void +load_data_free (LoadData *data) +{ + if (data->stream) + g_object_unref (data->stream); + g_free (data->type); + g_free (data); +} + +static void +load_async_thread (GSimpleAsyncResult *res, + GObject *object, + GCancellable *cancellable) +{ + GLoadableIconIface *iface; + GInputStream *stream; + LoadData *data; + GError *error = NULL; + char *type = NULL; + + data = g_simple_async_result_get_op_res_gpointer (res); + + iface = G_LOADABLE_ICON_GET_IFACE (object); + stream = iface->load (G_LOADABLE_ICON (object), data->size, &type, cancellable, &error); + + if (stream == NULL) + { + g_simple_async_result_set_from_error (res, error); + g_error_free (error); + } + else + { + data->stream = stream; + data->type = type; + } +} + + + +static void +g_loadable_icon_real_load_async (GLoadableIcon *icon, + int size, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GSimpleAsyncResult *res; + LoadData *data; + + res = g_simple_async_result_new (G_OBJECT (icon), callback, user_data, g_loadable_icon_real_load_async); + data = g_new0 (LoadData, 1); + g_simple_async_result_set_op_res_gpointer (res, data, (GDestroyNotify) load_data_free); + g_simple_async_result_run_in_thread (res, load_async_thread, 0, cancellable); + g_object_unref (res); +} + +static GInputStream * +g_loadable_icon_real_load_finish (GLoadableIcon *icon, + GAsyncResult *res, + char **type, + GError **error) +{ + GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (res); + LoadData *data; + + g_assert (g_simple_async_result_get_source_tag (simple) == g_loadable_icon_real_load_async); + + data = g_simple_async_result_get_op_res_gpointer (simple); + + if (type) + { + *type = data->type; + data->type = NULL; + } + + return g_object_ref (data->stream); +} diff --git a/gio/gloadableicon.h b/gio/gloadableicon.h new file mode 100644 index 000000000..ef203d709 --- /dev/null +++ b/gio/gloadableicon.h @@ -0,0 +1,83 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#ifndef __G_LOADABLE_ICON_H__ +#define __G_LOADABLE_ICON_H__ + +#include <glib-object.h> +#include <gio/gicon.h> +#include <gio/ginputstream.h> + +G_BEGIN_DECLS + +#define G_TYPE_LOADABLE_ICON (g_loadable_icon_get_type ()) +#define G_LOADABLE_ICON(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), G_TYPE_LOADABLE_ICON, GLoadableIcon)) +#define G_IS_LOADABLE_ICON(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), G_TYPE_LOADABLE_ICON)) +#define G_LOADABLE_ICON_GET_IFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), G_TYPE_LOADABLE_ICON, GLoadableIconIface)) + +typedef struct _GLoadableIcon GLoadableIcon; /* Dummy typedef */ +typedef struct _GLoadableIconIface GLoadableIconIface; + + +struct _GLoadableIconIface +{ + GTypeInterface g_iface; + + /* Virtual Table */ + + GInputStream * (*load) (GLoadableIcon *icon, + int size, + char **type, + GCancellable *cancellable, + GError **error); + void (*load_async) (GLoadableIcon *icon, + int size, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + GInputStream * (*load_finish) (GLoadableIcon *icon, + GAsyncResult *res, + char **type, + GError **error); +}; + +GType g_loadable_icon_get_type (void) G_GNUC_CONST; + + +GInputStream *g_loadable_icon_load (GLoadableIcon *icon, + int size, + char **type, + GCancellable *cancellable, + GError **error); +void g_loadable_icon_load_async (GLoadableIcon *icon, + int size, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +GInputStream *g_loadable_icon_load_finish (GLoadableIcon *icon, + GAsyncResult *res, + char **type, + GError **error); + +G_END_DECLS + +#endif /* __G_LOADABLE_ICON_H__ */ diff --git a/gio/glocaldirectorymonitor.c b/gio/glocaldirectorymonitor.c new file mode 100644 index 000000000..0cdcf8aa4 --- /dev/null +++ b/gio/glocaldirectorymonitor.c @@ -0,0 +1,290 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#include <config.h> + +#include "glocaldirectorymonitor.h" +#include "gunixmounts.h" +#include "gdirectorymonitor.h" +#include "giomodule.h" + +#include <string.h> + +enum +{ + PROP_0, + PROP_DIRNAME +}; + +static gboolean g_local_directory_monitor_cancel (GDirectoryMonitor* monitor); +static void mounts_changed (GUnixMountMonitor *mount_monitor, gpointer user_data); + +G_DEFINE_ABSTRACT_TYPE (GLocalDirectoryMonitor, g_local_directory_monitor, G_TYPE_DIRECTORY_MONITOR) + +static void +g_local_directory_monitor_finalize (GObject* object) +{ + GLocalDirectoryMonitor* local_monitor; + local_monitor = G_LOCAL_DIRECTORY_MONITOR (object); + + g_free (local_monitor->dirname); + + if (local_monitor->mount_monitor) + { + g_signal_handlers_disconnect_by_func (local_monitor->mount_monitor, mounts_changed, local_monitor); + g_object_unref (local_monitor->mount_monitor); + local_monitor->mount_monitor = NULL; + } + + if (G_OBJECT_CLASS (g_local_directory_monitor_parent_class)->finalize) + (*G_OBJECT_CLASS (g_local_directory_monitor_parent_class)->finalize) (object); +} + +static void +g_local_directory_monitor_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + switch (property_id) + { + case PROP_DIRNAME: + /* Do nothing */ + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static GObject * +g_local_directory_monitor_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_properties) +{ + GObject *obj; + GLocalDirectoryMonitorClass *klass; + GObjectClass *parent_class; + GLocalDirectoryMonitor *local_monitor; + const gchar *dirname = NULL; + gint i; + + klass = G_LOCAL_DIRECTORY_MONITOR_CLASS (g_type_class_peek (G_TYPE_LOCAL_DIRECTORY_MONITOR)); + parent_class = G_OBJECT_CLASS (g_type_class_peek_parent (klass)); + obj = parent_class->constructor (type, + n_construct_properties, + construct_properties); + + local_monitor = G_LOCAL_DIRECTORY_MONITOR (obj); + + for (i = 0; i < n_construct_properties; i++) + { + if (strcmp ("dirname", g_param_spec_get_name (construct_properties[i].pspec)) == 0) + { + g_assert (G_VALUE_HOLDS_STRING (construct_properties[i].value)); + dirname = g_value_get_string (construct_properties[i].value); + break; + } + } + + local_monitor->dirname = g_strdup (dirname); + + if (!klass->mount_notify) + { + GUnixMount *mount; + + /* Emulate unmount detection */ + + mount = g_get_unix_mount_at (local_monitor->dirname, NULL); + + local_monitor->was_mounted = mount != NULL; + + if (mount) + g_unix_mount_free (mount); + + local_monitor->mount_monitor = g_unix_mount_monitor_new (); + g_signal_connect (local_monitor->mount_monitor, "mounts_changed", + G_CALLBACK (mounts_changed), local_monitor); + } + + return obj; +} + +static void +g_local_directory_monitor_class_init (GLocalDirectoryMonitorClass* klass) +{ + GObjectClass* gobject_class = G_OBJECT_CLASS (klass); + GDirectoryMonitorClass *dir_monitor_class = G_DIRECTORY_MONITOR_CLASS (klass); + + gobject_class->finalize = g_local_directory_monitor_finalize; + gobject_class->set_property = g_local_directory_monitor_set_property; + gobject_class->constructor = g_local_directory_monitor_constructor; + + dir_monitor_class->cancel = g_local_directory_monitor_cancel; + + g_object_class_install_property (gobject_class, PROP_DIRNAME, + g_param_spec_string ("dirname", "Directory name", "Directory to monitor", + NULL, G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE)); + + klass->mount_notify = FALSE; +} + +static void +g_local_directory_monitor_init (GLocalDirectoryMonitor* local_monitor) +{ +} + +static void +mounts_changed (GUnixMountMonitor *mount_monitor, + gpointer user_data) +{ + GLocalDirectoryMonitor *local_monitor = user_data; + GUnixMount *mount; + gboolean is_mounted; + GFile *file; + + /* Emulate unmount detection */ + + mount = g_get_unix_mount_at (local_monitor->dirname, NULL); + + is_mounted = mount != NULL; + + if (mount) + g_unix_mount_free (mount); + + if (local_monitor->was_mounted != is_mounted) + { + if (local_monitor->was_mounted && !is_mounted) + { + file = g_file_new_for_path (local_monitor->dirname); + g_directory_monitor_emit_event (G_DIRECTORY_MONITOR (local_monitor), + file, NULL, + G_FILE_MONITOR_EVENT_UNMOUNTED); + g_object_unref (file); + } + local_monitor->was_mounted = is_mounted; + } +} + +static gint +_compare_monitor_class_by_prio (gconstpointer a, + gconstpointer b, + gpointer user_data) +{ + GType *type1 = (GType *) a, *type2 = (GType *) b; + GLocalDirectoryMonitorClass *klass1, *klass2; + gint ret; + + klass1 = G_LOCAL_DIRECTORY_MONITOR_CLASS (g_type_class_ref (*type1)); + klass2 = G_LOCAL_DIRECTORY_MONITOR_CLASS (g_type_class_ref (*type2)); + + ret = klass1->prio - klass2->prio; + + g_type_class_unref (klass1); + g_type_class_unref (klass2); + + return ret; +} + +extern GType g_inotify_directory_monitor_get_type (void); + +static gpointer +get_default_local_directory_monitor (gpointer data) +{ + GType *monitor_impls, chosen_type; + guint n_monitor_impls; + GType *ret = (GType *) data; + gint i; + +#if defined(HAVE_SYS_INOTIFY_H) || defined(HAVE_LINUX_INOTIFY_H) + /* Register Inotify monitor */ + g_inotify_directory_monitor_get_type (); +#endif + + g_io_modules_ensure_loaded (GIO_MODULE_DIR); + + monitor_impls = g_type_children (G_TYPE_LOCAL_DIRECTORY_MONITOR, + &n_monitor_impls); + + chosen_type = G_TYPE_INVALID; + + g_qsort_with_data (monitor_impls, + n_monitor_impls, + sizeof (GType), + _compare_monitor_class_by_prio, + NULL); + + for (i = n_monitor_impls - 1; i >= 0 && chosen_type == G_TYPE_INVALID; i--) + { + GLocalDirectoryMonitorClass *klass; + + klass = G_LOCAL_DIRECTORY_MONITOR_CLASS (g_type_class_ref (monitor_impls[i])); + + if (klass->is_supported()) + chosen_type = monitor_impls[i]; + + g_type_class_unref (klass); + } + + g_free (monitor_impls); + *ret = chosen_type; + + return NULL; +} + +/** + * g_local_directory_monitor_new: + * @dirname: filename of the directory to monitor. + * @flags: #GFileMonitorFlags. + * + * Returns: new #GDirectoryMonitor for the given @dirname. + **/ +GDirectoryMonitor* +g_local_directory_monitor_new (const char* dirname, + GFileMonitorFlags flags) +{ + static GOnce once_init = G_ONCE_INIT; + static GType monitor_type = G_TYPE_INVALID; + + g_once (&once_init, get_default_local_directory_monitor, &monitor_type); + + if (monitor_type != G_TYPE_INVALID) + return G_DIRECTORY_MONITOR (g_object_new (monitor_type, "dirname", dirname, NULL)); + + return NULL; +} + +static gboolean +g_local_directory_monitor_cancel (GDirectoryMonitor* monitor) +{ + GLocalDirectoryMonitor *local_monitor = G_LOCAL_DIRECTORY_MONITOR (monitor); + + if (local_monitor->mount_monitor) + { + g_signal_handlers_disconnect_by_func (local_monitor->mount_monitor, mounts_changed, local_monitor); + g_object_unref (local_monitor->mount_monitor); + local_monitor->mount_monitor = NULL; + } + + return TRUE; +} + diff --git a/gio/glocaldirectorymonitor.h b/gio/glocaldirectorymonitor.h new file mode 100644 index 000000000..9b8f9ed4d --- /dev/null +++ b/gio/glocaldirectorymonitor.h @@ -0,0 +1,65 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#ifndef __G_LOCAL_DIRECTORY_MONITOR_H__ +#define __G_LOCAL_DIRECTORY_MONITOR_H__ + +#include <glib-object.h> +#include <gio/gdirectorymonitor.h> + +#include "gunixmounts.h" + +G_BEGIN_DECLS + +#define G_TYPE_LOCAL_DIRECTORY_MONITOR (g_local_directory_monitor_get_type ()) +#define G_LOCAL_DIRECTORY_MONITOR(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_LOCAL_DIRECTORY_MONITOR, GLocalDirectoryMonitor)) +#define G_LOCAL_DIRECTORY_MONITOR_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), G_TYPE_LOCAL_DIRECTORY_MONITOR, GLocalDirectoryMonitorClass)) +#define G_IS_LOCAL_DIRECTORY_MONITOR(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_LOCAL_DIRECTORY_MONITOR)) +#define G_IS_LOCAL_DIRECTORY_MONITOR_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_LOCAL_DIRECTORY_MONITOR)) + +typedef struct _GLocalDirectoryMonitor GLocalDirectoryMonitor; +typedef struct _GLocalDirectoryMonitorClass GLocalDirectoryMonitorClass; + +struct _GLocalDirectoryMonitor +{ + GDirectoryMonitor parent_instance; + gchar *dirname; + /* For mount emulation */ + GUnixMountMonitor *mount_monitor; + gboolean was_mounted; +}; + +struct _GLocalDirectoryMonitorClass { + GDirectoryMonitorClass parent_class; + gint prio; + gboolean mount_notify; + gboolean (*is_supported) (void); +}; + +GType g_local_directory_monitor_get_type (void) G_GNUC_CONST; + +GDirectoryMonitor* g_local_directory_monitor_new (const char* dirname, + GFileMonitorFlags flags); + +G_END_DECLS + +#endif /* __G_LOCAL_DIRECTORY_MONITOR_H__ */ diff --git a/gio/glocalfile.c b/gio/glocalfile.c new file mode 100644 index 000000000..9d5b92606 --- /dev/null +++ b/gio/glocalfile.c @@ -0,0 +1,1826 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#include <config.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <string.h> +#include <errno.h> +#include <fcntl.h> +#include <unistd.h> + +#if HAVE_SYS_STATFS_H +#include <sys/statfs.h> +#endif +#if HAVE_SYS_STATVFS_H +#include <sys/statvfs.h> +#endif +#if HAVE_SYS_VFS_H +#include <sys/vfs.h> +#elif HAVE_SYS_MOUNT_H +#if HAVE_SYS_PARAM_H +#include <sys/param.h> +#endif +#include <sys/mount.h> +#endif + +#if defined(HAVE_STATFS) && defined(HAVE_STATVFS) +/* Some systems have both statfs and statvfs, pick the + most "native" for these */ +# if defined(sun) && defined(__SVR4) + /* on solaris, statfs doesn't even have the + f_bavail field */ +# define USE_STATVFS +# else + /* at least on linux, statfs is the actual syscall */ +# define USE_STATFS +# endif + +#elif defined(HAVE_STATFS) + +# define USE_STATFS + +#elif defined(HAVE_STATVFS) + +# define USE_STATVFS + +#endif + +#include "glocalfile.h" +#include "glocalfileinfo.h" +#include "glocalfileenumerator.h" +#include "glocalfileinputstream.h" +#include "glocalfileoutputstream.h" +#include "glocaldirectorymonitor.h" +#include "glocalfilemonitor.h" +#include "gvolumeprivate.h" +#include <glib/gstdio.h> +#include "glibintl.h" + +static void g_local_file_file_iface_init (GFileIface *iface); + +static GFileAttributeInfoList *local_writable_attributes = NULL; +static GFileAttributeInfoList *local_writable_namespaces = NULL; + +struct _GLocalFile +{ + GObject parent_instance; + + char *filename; +}; + +G_DEFINE_TYPE_WITH_CODE (GLocalFile, g_local_file, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (G_TYPE_FILE, + g_local_file_file_iface_init)) + +static char * find_mountpoint_for (const char *file, dev_t dev); + +static void +g_local_file_finalize (GObject *object) +{ + GLocalFile *local; + + local = G_LOCAL_FILE (object); + + g_free (local->filename); + + if (G_OBJECT_CLASS (g_local_file_parent_class)->finalize) + (*G_OBJECT_CLASS (g_local_file_parent_class)->finalize) (object); +} + +static void +g_local_file_class_init (GLocalFileClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GFileAttributeInfoList *list; + + gobject_class->finalize = g_local_file_finalize; + + /* Set up attribute lists */ + + /* Writable attributes: */ + + list = g_file_attribute_info_list_new (); + + g_file_attribute_info_list_add (list, + G_FILE_ATTRIBUTE_UNIX_MODE, + G_FILE_ATTRIBUTE_TYPE_UINT32, + G_FILE_ATTRIBUTE_FLAGS_COPY_WHEN_MOVED); + +#ifdef HAVE_CHOWN + g_file_attribute_info_list_add (list, + G_FILE_ATTRIBUTE_UNIX_UID, + G_FILE_ATTRIBUTE_TYPE_UINT32, + G_FILE_ATTRIBUTE_FLAGS_COPY_WHEN_MOVED); + g_file_attribute_info_list_add (list, + G_FILE_ATTRIBUTE_UNIX_GID, + G_FILE_ATTRIBUTE_TYPE_UINT32, + G_FILE_ATTRIBUTE_FLAGS_COPY_WHEN_MOVED); +#endif + +#ifdef HAVE_SYMLINK + g_file_attribute_info_list_add (list, + G_FILE_ATTRIBUTE_STD_SYMLINK_TARGET, + G_FILE_ATTRIBUTE_TYPE_BYTE_STRING, + 0); +#endif + +#ifdef HAVE_UTIMES + g_file_attribute_info_list_add (list, + G_FILE_ATTRIBUTE_TIME_MODIFIED, + G_FILE_ATTRIBUTE_TYPE_UINT64, + G_FILE_ATTRIBUTE_FLAGS_COPY_WHEN_MOVED); + g_file_attribute_info_list_add (list, + G_FILE_ATTRIBUTE_TIME_MODIFIED_USEC, + G_FILE_ATTRIBUTE_TYPE_UINT32, + G_FILE_ATTRIBUTE_FLAGS_COPY_WHEN_MOVED); + g_file_attribute_info_list_add (list, + G_FILE_ATTRIBUTE_TIME_ACCESS, + G_FILE_ATTRIBUTE_TYPE_UINT64, + G_FILE_ATTRIBUTE_FLAGS_COPY_WHEN_MOVED); + g_file_attribute_info_list_add (list, + G_FILE_ATTRIBUTE_TIME_ACCESS_USEC, + G_FILE_ATTRIBUTE_TYPE_UINT32, + G_FILE_ATTRIBUTE_FLAGS_COPY_WHEN_MOVED); +#endif + + local_writable_attributes = list; + + /* Writable namespaces: */ + + list = g_file_attribute_info_list_new (); + +#ifdef HAVE_XATTR + g_file_attribute_info_list_add (list, + "xattr", + G_FILE_ATTRIBUTE_TYPE_STRING, + G_FILE_ATTRIBUTE_FLAGS_COPY_WITH_FILE | + G_FILE_ATTRIBUTE_FLAGS_COPY_WHEN_MOVED); + g_file_attribute_info_list_add (list, + "xattr_sys", + G_FILE_ATTRIBUTE_TYPE_STRING, + G_FILE_ATTRIBUTE_FLAGS_COPY_WHEN_MOVED); +#endif + + local_writable_namespaces = list; +} + +static void +g_local_file_init (GLocalFile *local) +{ +} + + +static char * +canonicalize_filename (const char *filename) +{ + char *canon, *start, *p, *q; + char *cwd; + + if (!g_path_is_absolute (filename)) + { + cwd = g_get_current_dir (); + canon = g_build_filename (cwd, filename, NULL); + g_free (cwd); + } + else + canon = g_strdup (filename); + + start = (char *)g_path_skip_root (canon); + + p = start; + while (*p != 0) + { + if (p[0] == '.' && (p[1] == 0 || G_IS_DIR_SEPARATOR (p[1]))) + { + memmove (p, p+1, strlen (p+1)+1); + } + else if (p[0] == '.' && p[1] == '.' && (p[2] == 0 || G_IS_DIR_SEPARATOR (p[2]))) + { + q = p + 2; + /* Skip previous separator */ + p = p - 2; + if (p < start) + p = start; + while (p > start && !G_IS_DIR_SEPARATOR (*p)) + p--; + if (G_IS_DIR_SEPARATOR (*p)) + *p++ = G_DIR_SEPARATOR; + memmove (p, q, strlen (q)+1); + } + else + { + /* Skip until next separator */ + while (*p != 0 && !G_IS_DIR_SEPARATOR (*p)) + p++; + + if (*p != 0) + { + /* Canonicalize one separator */ + *p++ = G_DIR_SEPARATOR; + } + } + + /* Remove additional separators */ + q = p; + while (*q && G_IS_DIR_SEPARATOR (*q)) + q++; + + if (p != q) + memmove (p, q, strlen (q)+1); + } + + /* Remove trailing slashes */ + if (p > start && G_IS_DIR_SEPARATOR (*(p-1))) + *(p-1) = 0; + + return canon; +} + +/** + * g_local_file_new: + * @filename: filename of the file to create. + * + * Returns: new local #GFile. + **/ +GFile * +g_local_file_new (const char *filename) +{ + GLocalFile *local; + + local = g_object_new (G_TYPE_LOCAL_FILE, NULL); + local->filename = canonicalize_filename (filename); + + return G_FILE (local); +} + +static gboolean +g_local_file_is_native (GFile *file) +{ + return TRUE; +} + +static gboolean +g_local_file_has_uri_scheme (GFile *file, + const char *uri_scheme) +{ + return g_ascii_strcasecmp (uri_scheme, "file") == 0; +} + +static char * +g_local_file_get_uri_scheme (GFile *file) +{ + return g_strdup ("file"); +} + +static char * +g_local_file_get_basename (GFile *file) +{ + return g_path_get_basename (G_LOCAL_FILE (file)->filename); +} + +static char * +g_local_file_get_path (GFile *file) +{ + return g_strdup (G_LOCAL_FILE (file)->filename); +} + +static char * +g_local_file_get_uri (GFile *file) +{ + return g_filename_to_uri (G_LOCAL_FILE (file)->filename, NULL, NULL); +} + +static gboolean +get_filename_charset (const gchar **filename_charset) +{ + const gchar **charsets; + gboolean is_utf8; + + is_utf8 = g_get_filename_charsets (&charsets); + + if (filename_charset) + *filename_charset = charsets[0]; + + return is_utf8; +} + +static gboolean +name_is_valid_for_display (const char *string, + gboolean is_valid_utf8) +{ + char c; + + if (!is_valid_utf8 && + !g_utf8_validate (string, -1, NULL)) + return FALSE; + + while ((c = *string++) != 0) + { + if (g_ascii_iscntrl(c)) + return FALSE; + } + + return TRUE; +} + +static char * +g_local_file_get_parse_name (GFile *file) +{ + const char *filename; + char *parse_name; + const gchar *charset; + char *utf8_filename; + char *roundtripped_filename; + gboolean free_utf8_filename; + gboolean is_valid_utf8; + + filename = G_LOCAL_FILE (file)->filename; + if (get_filename_charset (&charset)) + { + utf8_filename = (char *)filename; + free_utf8_filename = FALSE; + is_valid_utf8 = FALSE; /* Can't guarantee this */ + } + else + { + utf8_filename = g_convert (filename, -1, + "UTF-8", charset, NULL, NULL, NULL); + free_utf8_filename = TRUE; + is_valid_utf8 = TRUE; + + if (utf8_filename != NULL) + { + /* Make sure we can roundtrip: */ + roundtripped_filename = g_convert (utf8_filename, -1, + charset, "UTF-8", NULL, NULL, NULL); + + if (roundtripped_filename == NULL || + strcmp (utf8_filename, roundtripped_filename) != 0) + { + g_free (utf8_filename); + utf8_filename = NULL; + } + } + } + + + if (utf8_filename != NULL && + name_is_valid_for_display (utf8_filename, is_valid_utf8)) + { + if (free_utf8_filename) + parse_name = utf8_filename; + else + parse_name = g_strdup (utf8_filename); + } + else + { + parse_name = g_filename_to_uri (filename, NULL, NULL); + if (free_utf8_filename) + g_free (utf8_filename); + } + + return parse_name; +} + +static GFile * +g_local_file_get_parent (GFile *file) +{ + GLocalFile *local = G_LOCAL_FILE (file); + const char *non_root; + char *dirname; + GFile *parent; + + /* Check for root */ + non_root = g_path_skip_root (local->filename); + if (*non_root == 0) + return NULL; + + dirname = g_path_get_dirname (local->filename); + parent = g_local_file_new (dirname); + g_free (dirname); + return parent; +} + +static GFile * +g_local_file_dup (GFile *file) +{ + GLocalFile *local = G_LOCAL_FILE (file); + + return g_local_file_new (local->filename); +} + +static guint +g_local_file_hash (GFile *file) +{ + GLocalFile *local = G_LOCAL_FILE (file); + + return g_str_hash (local->filename); +} + +static gboolean +g_local_file_equal (GFile *file1, + GFile *file2) +{ + GLocalFile *local1 = G_LOCAL_FILE (file1); + GLocalFile *local2 = G_LOCAL_FILE (file2); + + return g_str_equal (local1->filename, local2->filename); +} + +static const char * +match_prefix (const char *path, const char *prefix) +{ + int prefix_len; + + prefix_len = strlen (prefix); + if (strncmp (path, prefix, prefix_len) != 0) + return NULL; + return path + prefix_len; +} + +static gboolean +g_local_file_contains_file (GFile *parent, + GFile *descendant) +{ + GLocalFile *parent_local = G_LOCAL_FILE (parent); + GLocalFile *descendant_local = G_LOCAL_FILE (descendant); + const char *remainder; + + remainder = match_prefix (descendant_local->filename, parent_local->filename); + if (remainder != NULL && G_IS_DIR_SEPARATOR (*remainder)) + return TRUE; + return FALSE; +} + +static char * +g_local_file_get_relative_path (GFile *parent, + GFile *descendant) +{ + GLocalFile *parent_local = G_LOCAL_FILE (parent); + GLocalFile *descendant_local = G_LOCAL_FILE (descendant); + const char *remainder; + + remainder = match_prefix (descendant_local->filename, parent_local->filename); + + if (remainder != NULL && G_IS_DIR_SEPARATOR (*remainder)) + return g_strdup (remainder + 1); + return NULL; +} + +static GFile * +g_local_file_resolve_relative_path (GFile *file, + const char *relative_path) +{ + GLocalFile *local = G_LOCAL_FILE (file); + char *filename; + GFile *child; + + if (g_path_is_absolute (relative_path)) + return g_local_file_new (relative_path); + + filename = g_build_filename (local->filename, relative_path, NULL); + child = g_local_file_new (filename); + g_free (filename); + + return child; +} + +static GFileEnumerator * +g_local_file_enumerate_children (GFile *file, + const char *attributes, + GFileQueryInfoFlags flags, + GCancellable *cancellable, + GError **error) +{ + GLocalFile *local = G_LOCAL_FILE (file); + return g_local_file_enumerator_new (local->filename, + attributes, flags, + cancellable, error); +} + +static GFile * +g_local_file_get_child_for_display_name (GFile *file, + const char *display_name, + GError **error) +{ + GFile *parent, *new_file; + char *basename; + + basename = g_filename_from_utf8 (display_name, -1, NULL, NULL, NULL); + if (basename == NULL) + { + g_set_error (error, G_IO_ERROR, + G_IO_ERROR_INVALID_FILENAME, + _("Invalid filename %s"), display_name); + return NULL; + } + + parent = g_file_get_parent (file); + new_file = g_file_get_child (file, basename); + g_object_unref (parent); + g_free (basename); + + return new_file; +} + +#ifdef USE_STATFS +static const char * +get_fs_type (long f_type) +{ + + /* filesystem ids taken from linux manpage */ + switch (f_type) { + case 0xadf5: + return "adfs"; + case 0xADFF: + return "affs"; + case 0x42465331: + return "befs"; + case 0x1BADFACE: + return "bfs"; + case 0xFF534D42: + return "cifs"; + case 0x73757245: + return "coda"; + case 0x012FF7B7: + return "coh"; + case 0x28cd3d45: + return "cramfs"; + case 0x1373: + return "devfs"; + case 0x00414A53: + return "efs"; + case 0x137D: + return "ext"; + case 0xEF51: + return "ext2"; + case 0xEF53: + return "ext3"; + case 0x4244: + return "hfs"; + case 0xF995E849: + return "hpfs"; + case 0x958458f6: + return "hugetlbfs"; + case 0x9660: + return "isofs"; + case 0x72b6: + return "jffs2"; + case 0x3153464a: + return "jfs"; + case 0x137F: + return "minix"; + case 0x138F: + return "minix2"; + case 0x2468: + return "minix2"; + case 0x2478: + return "minix22"; + case 0x4d44: + return "msdos"; + case 0x564c: + return "ncp"; + case 0x6969: + return "nfs"; + case 0x5346544e: + return "ntfs"; + case 0x9fa1: + return "openprom"; + case 0x9fa0: + return "proc"; + case 0x002f: + return "qnx4"; + case 0x52654973: + return "reiserfs"; + case 0x7275: + return "romfs"; + case 0x517B: + return "smb"; + case 0x012FF7B6: + return "sysv2"; + case 0x012FF7B5: + return "sysv4"; + case 0x01021994: + return "tmpfs"; + case 0x15013346: + return "udf"; + case 0x00011954: + return "ufs"; + case 0x9fa2: + return "usbdevice"; + case 0xa501FCF5: + return "vxfs"; + case 0x012FF7B4: + return "xenix"; + case 0x58465342: + return "xfs"; + case 0x012FD16D: + return "xiafs"; + default: + return NULL; + } +} +#endif + +G_LOCK_DEFINE_STATIC(mount_info_hash); +static GHashTable *mount_info_hash = NULL; +guint64 mount_info_hash_cache_time = 0; + +typedef enum { + MOUNT_INFO_READONLY = 1<<0 +} MountInfo; + +static gboolean +device_equal (gconstpointer v1, + gconstpointer v2) +{ + return *(dev_t *)v1 == * (dev_t *)v2; +} + +static guint +device_hash (gconstpointer v) +{ + return (guint) *(dev_t *)v; +} + +static void +get_mount_info (GFileInfo *fs_info, + const char *path, + GFileAttributeMatcher *matcher) +{ + struct stat buf; + gboolean got_info; + gpointer info_as_ptr; + guint mount_info; + char *mountpoint; + dev_t *dev; + GUnixMount *mount; + guint64 cache_time; + + if (lstat (path, &buf) != 0) + return; + + G_LOCK (mount_info_hash); + + if (mount_info_hash == NULL) + mount_info_hash = g_hash_table_new_full (device_hash, device_equal, + g_free, NULL); + + + if (g_unix_mounts_changed_since (mount_info_hash_cache_time)) + g_hash_table_remove_all (mount_info_hash); + + got_info = g_hash_table_lookup_extended (mount_info_hash, + &buf.st_dev, + NULL, + &info_as_ptr); + + G_UNLOCK (mount_info_hash); + + mount_info = GPOINTER_TO_UINT (info_as_ptr); + + if (!got_info) + { + mount_info = 0; + + mountpoint = find_mountpoint_for (path, buf.st_dev); + if (mountpoint == NULL) + mountpoint = "/"; + + mount = g_get_unix_mount_at (mountpoint, &cache_time); + if (mount) + { + if (g_unix_mount_is_readonly (mount)) + mount_info |= MOUNT_INFO_READONLY; + + g_unix_mount_free (mount); + } + + dev = g_new0 (dev_t, 1); + *dev = buf.st_dev; + + G_LOCK (mount_info_hash); + mount_info_hash_cache_time = cache_time; + g_hash_table_insert (mount_info_hash, dev, GUINT_TO_POINTER (mount_info)); + G_UNLOCK (mount_info_hash); + } + + if (mount_info & MOUNT_INFO_READONLY) + g_file_info_set_attribute_boolean (fs_info, G_FILE_ATTRIBUTE_FS_READONLY, TRUE); +} + +static GFileInfo * +g_local_file_query_filesystem_info (GFile *file, + const char *attributes, + GCancellable *cancellable, + GError **error) +{ + GLocalFile *local = G_LOCAL_FILE (file); + GFileInfo *info; + int statfs_result; + gboolean no_size; + guint64 block_size; +#ifdef USE_STATFS + struct statfs statfs_buffer; + const char *fstype; +#elif defined(USE_STATVFS) + struct statvfs statfs_buffer; +#endif + GFileAttributeMatcher *attribute_matcher; + + no_size = FALSE; + +#ifdef USE_STATFS + +#if STATFS_ARGS == 2 + statfs_result = statfs (local->filename, &statfs_buffer); +#elif STATFS_ARGS == 4 + statfs_result = statfs (local->filename, &statfs_buffer, + sizeof (statfs_buffer), 0); +#endif + block_size = statfs_buffer.f_bsize; + +#if defined(__linux__) + /* ncpfs does not know the amount of available and free space * + * assuming ncpfs is linux specific, if you are on a non-linux platform + * where ncpfs is available, please file a bug about it on bugzilla.gnome.org + */ + if (statfs_buffer.f_bavail == 0 && statfs_buffer.f_bfree == 0 && + /* linux/ncp_fs.h: NCP_SUPER_MAGIC == 0x564c */ + statfs_buffer.f_type == 0x564c) + no_size = TRUE; +#endif + +#elif defined(USE_STATVFS) + statfs_result = statvfs (local->filename, &statfs_buffer); + block_size = statfs_buffer.f_frsize; +#endif + + if (statfs_result == -1) + { + g_set_error (error, G_IO_ERROR, + g_io_error_from_errno (errno), + _("Error getting filesystem info: %s"), + g_strerror (errno)); + return NULL; + } + + info = g_file_info_new (); + + attribute_matcher = g_file_attribute_matcher_new (attributes); + + if (g_file_attribute_matcher_matches (attribute_matcher, + G_FILE_ATTRIBUTE_FS_FREE)) + g_file_info_set_attribute_uint64 (info, G_FILE_ATTRIBUTE_FS_FREE, block_size * statfs_buffer.f_bavail); + if (g_file_attribute_matcher_matches (attribute_matcher, + G_FILE_ATTRIBUTE_FS_SIZE)) + g_file_info_set_attribute_uint64 (info, G_FILE_ATTRIBUTE_FS_SIZE, block_size * statfs_buffer.f_blocks); + +#ifdef USE_STATFS + fstype = get_fs_type (statfs_buffer.f_type); + if (fstype && + g_file_attribute_matcher_matches (attribute_matcher, + G_FILE_ATTRIBUTE_FS_TYPE)) + g_file_info_set_attribute_string (info, G_FILE_ATTRIBUTE_FS_TYPE, fstype); +#endif + + if (g_file_attribute_matcher_matches (attribute_matcher, + G_FILE_ATTRIBUTE_FS_READONLY)) + { + get_mount_info (info, local->filename, attribute_matcher); + } + + g_file_attribute_matcher_unref (attribute_matcher); + + return info; +} + +static GVolume * +g_local_file_find_enclosing_volume (GFile *file, + GCancellable *cancellable, + GError **error) +{ + GLocalFile *local = G_LOCAL_FILE (file); + struct stat buf; + char *mountpoint; + GVolume *volume; + + if (lstat (local->filename, &buf) != 0) + { + g_set_error (error, G_IO_ERROR, + G_IO_ERROR_NOT_FOUND, + _("Containing volume does not exist")); + return NULL; + } + + mountpoint = find_mountpoint_for (local->filename, buf.st_dev); + if (mountpoint == NULL) + { + g_set_error (error, G_IO_ERROR, + G_IO_ERROR_NOT_FOUND, + _("Containing volume does not exist")); + return NULL; + } + + volume = g_volume_get_for_mount_path (mountpoint); + g_free (mountpoint); + if (volume) + return volume; + + g_set_error (error, G_IO_ERROR, + G_IO_ERROR_NOT_FOUND, + _("Containing volume does not exist")); + return NULL; +} + +static GFile * +g_local_file_set_display_name (GFile *file, + const char *display_name, + GCancellable *cancellable, + GError **error) +{ + GLocalFile *local, *new_local; + GFile *new_file, *parent; + struct stat statbuf; + int errsv; + + parent = g_file_get_parent (file); + if (parent == NULL) + { + g_set_error (error, G_IO_ERROR, + G_IO_ERROR_FAILED, + _("Can't rename root directory")); + return NULL; + } + + new_file = g_file_get_child_for_display_name (parent, display_name, error); + g_object_unref (parent); + + if (new_file == NULL) + return NULL; + + local = G_LOCAL_FILE (file); + new_local = G_LOCAL_FILE (new_file); + + if (!(g_lstat (new_local->filename, &statbuf) == -1 && + errno == ENOENT)) + { + g_set_error (error, G_IO_ERROR, + G_IO_ERROR_EXISTS, + _("Can't rename file, filename already exist")); + return NULL; + } + + if (rename (local->filename, new_local->filename) == -1) + { + errsv = errno; + + if (errsv == EINVAL) + /* We can't get a rename file into itself error herer, + so this must be an invalid filename, on e.g. FAT */ + g_set_error (error, G_IO_ERROR, + G_IO_ERROR_INVALID_FILENAME, + _("Invalid filename")); + else + g_set_error (error, G_IO_ERROR, + g_io_error_from_errno (errsv), + _("Error renaming file: %s"), + g_strerror (errsv)); + g_object_unref (new_file); + return NULL; + } + + return new_file; +} + +static GFileInfo * +g_local_file_query_info (GFile *file, + const char *attributes, + GFileQueryInfoFlags flags, + GCancellable *cancellable, + GError **error) +{ + GLocalFile *local = G_LOCAL_FILE (file); + GFileInfo *info; + GFileAttributeMatcher *matcher; + char *basename, *dirname; + GLocalParentFileInfo parent_info; + + matcher = g_file_attribute_matcher_new (attributes); + + basename = g_path_get_basename (local->filename); + + dirname = g_path_get_dirname (local->filename); + _g_local_file_info_get_parent_info (dirname, matcher, &parent_info); + g_free (dirname); + + info = _g_local_file_info_get (basename, local->filename, + matcher, flags, &parent_info, + error); + + g_free (basename); + + g_file_attribute_matcher_unref (matcher); + + return info; +} + +static GFileAttributeInfoList * +g_local_file_query_settable_attributes (GFile *file, + GCancellable *cancellable, + GError **error) +{ + return g_file_attribute_info_list_ref (local_writable_attributes); +} + +static GFileAttributeInfoList * +g_local_file_query_writable_namespaces (GFile *file, + GCancellable *cancellable, + GError **error) +{ + return g_file_attribute_info_list_ref (local_writable_namespaces); +} + +static gboolean +g_local_file_set_attribute (GFile *file, + const char *attribute, + const GFileAttributeValue *value, + GFileQueryInfoFlags flags, + GCancellable *cancellable, + GError **error) +{ + GLocalFile *local = G_LOCAL_FILE (file); + + return _g_local_file_info_set_attribute (local->filename, + attribute, + value, + flags, + cancellable, + error); +} + +static gboolean +g_local_file_set_attributes_from_info (GFile *file, + GFileInfo *info, + GFileQueryInfoFlags flags, + GCancellable *cancellable, + GError **error) +{ + GLocalFile *local = G_LOCAL_FILE (file); + int res, chained_res; + GFileIface* default_iface; + + res = _g_local_file_info_set_attributes (local->filename, + info, flags, + cancellable, + error); + + if (!res) + error = NULL; /* Don't write over error if further errors */ + + default_iface = g_type_default_interface_peek (G_TYPE_FILE); + + chained_res = (default_iface->set_attributes_from_info) (file, info, flags, cancellable, error); + + return res && chained_res; +} + +static GFileInputStream * +g_local_file_read (GFile *file, + GCancellable *cancellable, + GError **error) +{ + GLocalFile *local = G_LOCAL_FILE (file); + int fd; + struct stat buf; + + fd = g_open (local->filename, O_RDONLY, 0); + if (fd == -1) + { + g_set_error (error, G_IO_ERROR, + g_io_error_from_errno (errno), + _("Error opening file: %s"), + g_strerror (errno)); + return NULL; + } + + if (fstat(fd, &buf) == 0 && S_ISDIR (buf.st_mode)) + { + close (fd); + g_set_error (error, G_IO_ERROR, + G_IO_ERROR_IS_DIRECTORY, + _("Can't open directory")); + return NULL; + } + + return g_local_file_input_stream_new (fd); +} + +static GFileOutputStream * +g_local_file_append_to (GFile *file, + GFileCreateFlags flags, + GCancellable *cancellable, + GError **error) +{ + return g_local_file_output_stream_append (G_LOCAL_FILE (file)->filename, + flags, cancellable, error); +} + +static GFileOutputStream * +g_local_file_create (GFile *file, + GFileCreateFlags flags, + GCancellable *cancellable, + GError **error) +{ + return g_local_file_output_stream_create (G_LOCAL_FILE (file)->filename, + flags, cancellable, error); +} + +static GFileOutputStream * +g_local_file_replace (GFile *file, + const char *etag, + gboolean make_backup, + GFileCreateFlags flags, + GCancellable *cancellable, + GError **error) +{ + return g_local_file_output_stream_replace (G_LOCAL_FILE (file)->filename, + etag, make_backup, flags, + cancellable, error); +} + + +static gboolean +g_local_file_delete (GFile *file, + GCancellable *cancellable, + GError **error) +{ + GLocalFile *local = G_LOCAL_FILE (file); + + if (g_remove (local->filename) == -1) + { + g_set_error (error, G_IO_ERROR, + g_io_error_from_errno (errno), + _("Error removing file: %s"), + g_strerror (errno)); + return FALSE; + } + + return TRUE; +} + +static char * +strip_trailing_slashes (const char *path) +{ + char *path_copy; + int len; + + path_copy = g_strdup (path); + len = strlen (path_copy); + while (len > 1 && path_copy[len-1] == '/') + path_copy[--len] = 0; + + return path_copy; + } + +static char * +expand_symlink (const char *link) +{ + char *resolved, *canonical, *parent, *link2; + char symlink_value[4096]; + ssize_t res; + + res = readlink (link, symlink_value, sizeof (symlink_value) - 1); + if (res == -1) + return g_strdup (link); + symlink_value[res] = 0; + + if (g_path_is_absolute (symlink_value)) + return canonicalize_filename (symlink_value); + else + { + link2 = strip_trailing_slashes (link); + parent = g_path_get_dirname (link2); + g_free (link2); + + resolved = g_build_filename (parent, symlink_value, NULL); + g_free (parent); + + canonical = canonicalize_filename (resolved); + + g_free (resolved); + + return canonical; + } +} + +static char * +get_parent (const char *path, dev_t *parent_dev) +{ + char *parent, *tmp; + struct stat parent_stat; + int num_recursions; + char *path_copy; + + path_copy = strip_trailing_slashes (path); + + parent = g_path_get_dirname (path_copy); + if (strcmp (parent, ".") == 0 || + strcmp (parent, path_copy) == 0) + { + g_free (path_copy); + return NULL; + } + g_free (path_copy); + + num_recursions = 0; + do { + if (g_lstat (parent, &parent_stat) != 0) + { + g_free (parent); + return NULL; + } + + if (S_ISLNK (parent_stat.st_mode)) + { + tmp = parent; + parent = expand_symlink (parent); + g_free (tmp); + } + + num_recursions++; + if (num_recursions > 12) + { + g_free (parent); + return NULL; + } + } while (S_ISLNK (parent_stat.st_mode)); + + *parent_dev = parent_stat.st_dev; + + return parent; +} + +static char * +expand_all_symlinks (const char *path) +{ + char *parent, *parent_expanded; + char *basename, *res; + dev_t parent_dev; + + parent = get_parent (path, &parent_dev); + if (parent) + { + parent_expanded = expand_all_symlinks (parent); + g_free (parent); + basename = g_path_get_basename (path); + res = g_build_filename (parent_expanded, basename, NULL); + g_free (basename); + g_free (parent_expanded); + } + else + res = g_strdup (path); + + return res; +} + +static char * +find_mountpoint_for (const char *file, dev_t dev) +{ + char *dir, *parent; + dev_t dir_dev, parent_dev; + + dir = g_strdup (file); + dir_dev = dev; + + while (1) { + parent = get_parent (dir, &parent_dev); + if (parent == NULL) + return dir; + + if (parent_dev != dir_dev) + { + g_free (parent); + return dir; + } + + g_free (dir); + dir = parent; + } +} + +static char * +find_topdir_for (const char *file) +{ + char *dir; + dev_t dir_dev; + + dir = get_parent (file, &dir_dev); + if (dir == NULL) + return NULL; + + return find_mountpoint_for (dir, dir_dev); +} + +static char * +get_unique_filename (const char *basename, int id) +{ + const char *dot; + + if (id == 1) + return g_strdup (basename); + + dot = strchr (basename, '.'); + if (dot) + return g_strdup_printf ("%.*s.%d%s", dot - basename, basename, id, dot); + else + return g_strdup_printf ("%s.%d", basename, id); +} + +static gboolean +path_has_prefix (const char *path, const char *prefix) +{ + int prefix_len; + + if (prefix == NULL) + return TRUE; + + prefix_len = strlen (prefix); + + if (strncmp (path, prefix, prefix_len) == 0 && + (prefix_len == 0 || /* empty prefix always matches */ + prefix[prefix_len - 1] == '/' || /* last char in prefix was a /, so it must be in path too */ + path[prefix_len] == 0 || + path[prefix_len] == '/')) + return TRUE; + + return FALSE; +} + +static char * +try_make_relative (const char *path, const char *base) +{ + char *path2, *base2; + char *relative; + + path2 = expand_all_symlinks (path); + base2 = expand_all_symlinks (base); + + relative = NULL; + if (path_has_prefix (path2, base2)) + { + relative = path2 + strlen (base2); + while (*relative == '/') + relative ++; + relative = g_strdup (relative); + } + g_free (path2); + g_free (base2); + + if (relative) + return relative; + + /* Failed, use abs path */ + return g_strdup (path); +} + +static char * +escape_trash_name (char *name) +{ + GString *str; + const gchar hex[16] = "0123456789ABCDEF"; + + str = g_string_new (""); + + while (*name != 0) + { + char c; + + c = *name++; + + if (g_ascii_isprint (c)) + g_string_append_c (str, c); + else + { + g_string_append_c (str, '%'); + g_string_append_c (str, hex[((guchar)c) >> 4]); + g_string_append_c (str, hex[((guchar)c) & 0xf]); + } + } + + return g_string_free (str, FALSE); +} + +static gboolean +g_local_file_trash (GFile *file, + GCancellable *cancellable, + GError **error) +{ + GLocalFile *local = G_LOCAL_FILE (file); + struct stat file_stat, home_stat, trash_stat, global_stat; + const char *homedir; + char *dirname; + char *trashdir, *globaldir, *topdir, *infodir, *filesdir; + char *basename, *trashname, *trashfile, *infoname, *infofile; + char *original_name, *original_name_escaped; + uid_t uid; + char uid_str[32]; + int i; + char *data; + gboolean is_homedir_trash; + time_t t; + struct tm now; + char delete_time[32]; + int fd; + + if (g_lstat (local->filename, &file_stat) != 0) + { + g_set_error (error, G_IO_ERROR, + g_io_error_from_errno (errno), + _("Error trashing file: %s"), + g_strerror (errno)); + return FALSE; + } + + homedir = g_get_home_dir (); + g_stat (homedir, &home_stat); + + is_homedir_trash = FALSE; + trashdir = NULL; + if (file_stat.st_dev == home_stat.st_dev) + { + is_homedir_trash = TRUE; + errno = 0; + trashdir = g_build_filename (g_get_user_data_dir (), "Trash", NULL); + if (g_mkdir_with_parents (trashdir, 0700) < 0) + { + char *display_name; + int err; + + err = errno; + display_name = g_filename_display_name (trashdir); + g_set_error (error, G_IO_ERROR, + g_io_error_from_errno (err), + _("Unable to create trash dir %s: %s"), + display_name, g_strerror (err)); + g_free (display_name); + g_free (trashdir); + return FALSE; + } + topdir = g_strdup (g_get_user_data_dir ()); + } + else + { + uid = geteuid (); + g_snprintf (uid_str, sizeof (uid_str), "%lu", (unsigned long)uid); + + topdir = find_topdir_for (local->filename); + if (topdir == NULL) + { + g_set_error (error, G_IO_ERROR, + G_IO_ERROR_NOT_SUPPORTED, + _("Unable to find toplevel directory for trash")); + return FALSE; + } + + /* Try looking for global trash dir $topdir/.Trash/$uid */ + globaldir = g_build_filename (topdir, ".Trash", NULL); + if (g_lstat (globaldir, &global_stat) == 0 && + S_ISDIR (global_stat.st_mode) && + (global_stat.st_mode & S_ISVTX) != 0) + { + trashdir = g_build_filename (globaldir, uid_str, NULL); + + if (g_lstat (trashdir, &trash_stat) == 0) + { + if (!S_ISDIR (trash_stat.st_mode) || + trash_stat.st_uid != uid) + { + /* Not a directory or not owned by user, ignore */ + g_free (trashdir); + trashdir = NULL; + } + } + else if (g_mkdir (trashdir, 0700) == -1) + { + g_free (trashdir); + trashdir = NULL; + } + } + g_free (globaldir); + + if (trashdir == NULL) + { + /* No global trash dir, or it failed the tests, fall back to $topdir/.Trash-$uid */ + dirname = g_strdup_printf (".Trash-%s", uid_str); + trashdir = g_build_filename (topdir, dirname, NULL); + g_free (dirname); + + if (g_lstat (trashdir, &trash_stat) == 0) + { + if (!S_ISDIR (trash_stat.st_mode) || + trash_stat.st_uid != uid) + { + /* Not a directory or not owned by user, ignore */ + g_free (trashdir); + trashdir = NULL; + } + } + else if (g_mkdir (trashdir, 0700) == -1) + { + g_free (trashdir); + trashdir = NULL; + } + } + + if (trashdir == NULL) + { + g_free (topdir); + g_set_error (error, G_IO_ERROR, + G_IO_ERROR_NOT_SUPPORTED, + _("Unable to find or create trash directory")); + return FALSE; + } + } + + /* Trashdir points to the trash dir with the "info" and "files" subdirectories */ + + infodir = g_build_filename (trashdir, "info", NULL); + filesdir = g_build_filename (trashdir, "files", NULL); + g_free (trashdir); + + /* Make sure we have the subdirectories */ + if ((g_mkdir (infodir, 0700) == -1 && errno != EEXIST) || + (g_mkdir (filesdir, 0700) == -1 && errno != EEXIST)) + { + g_free (topdir); + g_free (infodir); + g_free (filesdir); + g_set_error (error, G_IO_ERROR, + G_IO_ERROR_NOT_SUPPORTED, + _("Unable to find or create trash directory")); + return FALSE; + } + + basename = g_path_get_basename (local->filename); + i = 1; + trashname = NULL; + infofile = NULL; + do { + g_free (trashname); + g_free (infofile); + + trashname = get_unique_filename (basename, i++); + infoname = g_strconcat (trashname, ".trashinfo", NULL); + infofile = g_build_filename (infodir, infoname, NULL); + g_free (infoname); + + fd = open (infofile, O_CREAT | O_EXCL, 0666); + } while (fd == -1 && errno == EEXIST); + + g_free (basename); + g_free (infodir); + + if (fd == -1) + { + g_free (filesdir); + g_free (topdir); + g_free (trashname); + g_free (infofile); + + g_set_error (error, G_IO_ERROR, + g_io_error_from_errno (errno), + _("Unable to create trashed file: %s"), + g_strerror (errno)); + return FALSE; + } + + close (fd); + + /* TODO: Maybe we should verify that you can delete the file from the trash + before moving it? OTOH, that is hard, as it needs a recursive scan */ + + trashfile = g_build_filename (filesdir, trashname, NULL); + + g_free (filesdir); + + if (g_rename (local->filename, trashfile) == -1) + { + g_free (topdir); + g_free (trashname); + g_free (infofile); + g_free (trashfile); + + g_set_error (error, G_IO_ERROR, + g_io_error_from_errno (errno), + _("Unable to trash file: %s"), + g_strerror (errno)); + return FALSE; + } + + g_free (trashfile); + + /* TODO: Do we need to update mtime/atime here after the move? */ + + /* Use absolute names for homedir */ + if (is_homedir_trash) + original_name = g_strdup (local->filename); + else + original_name = try_make_relative (local->filename, topdir); + original_name_escaped = escape_trash_name (original_name); + + g_free (original_name); + g_free (topdir); + + t = time (NULL); + localtime_r (&t, &now); + delete_time[0] = 0; + strftime(delete_time, sizeof (delete_time), "%Y-%m-%dT%H:%M:%S", &now); + + data = g_strdup_printf ("[Trash Info]\nPath=%s\nDeletionDate=%s\n", + original_name_escaped, delete_time); + + g_file_set_contents (infofile, data, -1, NULL); + g_free (infofile); + g_free (data); + + g_free (original_name_escaped); + g_free (trashname); + + return TRUE; +} + +static gboolean +g_local_file_make_directory (GFile *file, + GCancellable *cancellable, + GError **error) +{ + GLocalFile *local = G_LOCAL_FILE (file); + + if (g_mkdir (local->filename, 0755) == -1) + { + int errsv = errno; + + if (errsv == EINVAL) + /* This must be an invalid filename, on e.g. FAT */ + g_set_error (error, G_IO_ERROR, + G_IO_ERROR_INVALID_FILENAME, + _("Invalid filename")); + else + g_set_error (error, G_IO_ERROR, + g_io_error_from_errno (errsv), + _("Error removing file: %s"), + g_strerror (errsv)); + return FALSE; + } + + return TRUE; +} + +static gboolean +g_local_file_make_symbolic_link (GFile *file, + const char *symlink_value, + GCancellable *cancellable, + GError **error) +{ +#ifdef HAVE_SYMLINK + GLocalFile *local = G_LOCAL_FILE (file); + + if (symlink (symlink_value, local->filename) == -1) + { + int errsv = errno; + + if (errsv == EINVAL) + /* This must be an invalid filename, on e.g. FAT */ + g_set_error (error, G_IO_ERROR, + G_IO_ERROR_INVALID_FILENAME, + _("Invalid filename")); + else + g_set_error (error, G_IO_ERROR, + g_io_error_from_errno (errsv), + _("Error making symbolic link: %s"), + g_strerror (errsv)); + return FALSE; + } + return TRUE; +#else + g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "Symlinks not supported"); + return FALSE; +#endif +} + + +static gboolean +g_local_file_copy (GFile *source, + GFile *destination, + GFileCopyFlags flags, + GCancellable *cancellable, + GFileProgressCallback progress_callback, + gpointer progress_callback_data, + GError **error) +{ + /* Fall back to default copy */ + g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "Copy not supported"); + return FALSE; +} + +static gboolean +g_local_file_move (GFile *source, + GFile *destination, + GFileCopyFlags flags, + GCancellable *cancellable, + GFileProgressCallback progress_callback, + gpointer progress_callback_data, + GError **error) +{ + GLocalFile *local_source = G_LOCAL_FILE (source); + GLocalFile *local_destination = G_LOCAL_FILE (destination); + struct stat statbuf; + gboolean destination_exist, source_is_dir; + char *backup_name; + int res; + + res = g_lstat (local_source->filename, &statbuf); + if (res == -1) + { + g_set_error (error, G_IO_ERROR, + g_io_error_from_errno (errno), + _("Error moving file: %s"), + g_strerror (errno)); + return FALSE; + } + else + source_is_dir = S_ISDIR (statbuf.st_mode); + + destination_exist = FALSE; + res = g_lstat (local_destination->filename, &statbuf); + if (res == 0) + { + destination_exist = TRUE; /* Target file exists */ + + if (flags & G_FILE_COPY_OVERWRITE) + { + /* Always fail on dirs, even with overwrite */ + if (S_ISDIR (statbuf.st_mode)) + { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_WOULD_MERGE, + _("Can't move directory over directory")); + return FALSE; + } + } + else + { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_EXISTS, + _("Target file already exists")); + return FALSE; + } + } + + if (flags & G_FILE_COPY_BACKUP && destination_exist) + { + backup_name = g_strconcat (local_destination->filename, "~", NULL); + if (rename (local_destination->filename, backup_name) == -1) + { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_CANT_CREATE_BACKUP, + _("Backup file creation failed")); + g_free (backup_name); + return FALSE; + } + g_free (backup_name); + destination_exist = FALSE; /* It did, but no more */ + } + + if (source_is_dir && destination_exist && (flags & G_FILE_COPY_OVERWRITE)) + { + /* Source is a dir, destination exists (and is not a dir, because that would have failed + earlier), and we're overwriting. Manually remove the target so we can do the rename. */ + res = unlink (local_destination->filename); + if (res == -1) + { + g_set_error (error, G_IO_ERROR, + g_io_error_from_errno (errno), + _("Error removing target file: %s"), + g_strerror (errno)); + return FALSE; + } + } + + if (rename (local_source->filename, local_destination->filename) == -1) + { + int errsv = errno; + if (errsv == EXDEV) + goto fallback; + + if (errsv == EINVAL) + /* This must be an invalid filename, on e.g. FAT, or + we're trying to move the file into itself... + We return invalid filename for both... */ + g_set_error (error, G_IO_ERROR, + G_IO_ERROR_INVALID_FILENAME, + _("Invalid filename")); + else + g_set_error (error, G_IO_ERROR, + g_io_error_from_errno (errsv), + _("Error moving file: %s"), + g_strerror (errsv)); + return FALSE; + + } + return TRUE; + + fallback: + + if (!g_file_copy (source, destination, G_FILE_COPY_OVERWRITE | G_FILE_COPY_ALL_METADATA, cancellable, + progress_callback, progress_callback_data, + error)) + return FALSE; + + return g_file_delete (source, cancellable, error); +} + + +static GDirectoryMonitor* +g_local_file_monitor_dir (GFile* file, + GFileMonitorFlags flags, + GCancellable *cancellable) +{ + GLocalFile* local_file = G_LOCAL_FILE(file); + return g_local_directory_monitor_new (local_file->filename, flags); +} + +static GFileMonitor* +g_local_file_monitor_file (GFile* file, + GFileMonitorFlags flags, + GCancellable *cancellable) +{ + GLocalFile* local_file = G_LOCAL_FILE(file); + return g_local_file_monitor_new (local_file->filename, flags); +} + +static void +g_local_file_file_iface_init (GFileIface *iface) +{ + iface->dup = g_local_file_dup; + iface->hash = g_local_file_hash; + iface->equal = g_local_file_equal; + iface->is_native = g_local_file_is_native; + iface->has_uri_scheme = g_local_file_has_uri_scheme; + iface->get_uri_scheme = g_local_file_get_uri_scheme; + iface->get_basename = g_local_file_get_basename; + iface->get_path = g_local_file_get_path; + iface->get_uri = g_local_file_get_uri; + iface->get_parse_name = g_local_file_get_parse_name; + iface->get_parent = g_local_file_get_parent; + iface->contains_file = g_local_file_contains_file; + iface->get_relative_path = g_local_file_get_relative_path; + iface->resolve_relative_path = g_local_file_resolve_relative_path; + iface->get_child_for_display_name = g_local_file_get_child_for_display_name; + iface->set_display_name = g_local_file_set_display_name; + iface->enumerate_children = g_local_file_enumerate_children; + iface->query_info = g_local_file_query_info; + iface->query_filesystem_info = g_local_file_query_filesystem_info; + iface->find_enclosing_volume = g_local_file_find_enclosing_volume; + iface->query_settable_attributes = g_local_file_query_settable_attributes; + iface->query_writable_namespaces = g_local_file_query_writable_namespaces; + iface->set_attribute = g_local_file_set_attribute; + iface->set_attributes_from_info = g_local_file_set_attributes_from_info; + iface->read = g_local_file_read; + iface->append_to = g_local_file_append_to; + iface->create = g_local_file_create; + iface->replace = g_local_file_replace; + iface->delete_file = g_local_file_delete; + iface->trash = g_local_file_trash; + iface->make_directory = g_local_file_make_directory; + iface->make_symbolic_link = g_local_file_make_symbolic_link; + iface->copy = g_local_file_copy; + iface->move = g_local_file_move; + iface->monitor_dir = g_local_file_monitor_dir; + iface->monitor_file = g_local_file_monitor_file; +} diff --git a/gio/glocalfile.h b/gio/glocalfile.h new file mode 100644 index 000000000..62e22f648 --- /dev/null +++ b/gio/glocalfile.h @@ -0,0 +1,51 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#ifndef __G_LOCAL_FILE_H__ +#define __G_LOCAL_FILE_H__ + +#include <gio/gfile.h> + +G_BEGIN_DECLS + +#define G_TYPE_LOCAL_FILE (g_local_file_get_type ()) +#define G_LOCAL_FILE(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_LOCAL_FILE, GLocalFile)) +#define G_LOCAL_FILE_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), G_TYPE_LOCAL_FILE, GLocalFileClass)) +#define G_IS_LOCAL_FILE(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_LOCAL_FILE)) +#define G_IS_LOCAL_FILE_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_LOCAL_FILE)) +#define G_LOCAL_FILE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_TYPE_LOCAL_FILE, GLocalFileClass)) + +typedef struct _GLocalFile GLocalFile; +typedef struct _GLocalFileClass GLocalFileClass; + +struct _GLocalFileClass +{ + GObjectClass parent_class; +}; + +GType g_local_file_get_type (void) G_GNUC_CONST; + +GFile * g_local_file_new (const char *filename); + +G_END_DECLS + +#endif /* __G_LOCAL_FILE_H__ */ diff --git a/gio/glocalfileenumerator.c b/gio/glocalfileenumerator.c new file mode 100644 index 000000000..8548fcfad --- /dev/null +++ b/gio/glocalfileenumerator.c @@ -0,0 +1,228 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#include <config.h> + +#include <glib.h> +#include <glocalfileenumerator.h> +#include <glocalfileinfo.h> +#include "glibintl.h" + + /* TODO: + * It would be nice to use the dirent->d_type to check file type without + * needing to stat each files on linux and other systems that support it. + * (question: does that following symlink or not?) + */ + + +struct _GLocalFileEnumerator +{ + GFileEnumerator parent; + + GFileAttributeMatcher *matcher; + GDir *dir; + char *filename; + char *attributes; + GFileQueryInfoFlags flags; + + gboolean got_parent_info; + GLocalParentFileInfo parent_info; + + gboolean follow_symlinks; +}; + +G_DEFINE_TYPE (GLocalFileEnumerator, g_local_file_enumerator, G_TYPE_FILE_ENUMERATOR); + +static GFileInfo *g_local_file_enumerator_next_file (GFileEnumerator *enumerator, + GCancellable *cancellable, + GError **error); +static gboolean g_local_file_enumerator_close (GFileEnumerator *enumerator, + GCancellable *cancellable, + GError **error); + + +static void +g_local_file_enumerator_finalize (GObject *object) +{ + GLocalFileEnumerator *local; + + local = G_LOCAL_FILE_ENUMERATOR (object); + + g_free (local->filename); + g_file_attribute_matcher_unref (local->matcher); + if (local->dir) + { + g_dir_close (local->dir); + local->dir = NULL; + } + + if (G_OBJECT_CLASS (g_local_file_enumerator_parent_class)->finalize) + (*G_OBJECT_CLASS (g_local_file_enumerator_parent_class)->finalize) (object); +} + + +static void +g_local_file_enumerator_class_init (GLocalFileEnumeratorClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GFileEnumeratorClass *enumerator_class = G_FILE_ENUMERATOR_CLASS (klass); + + gobject_class->finalize = g_local_file_enumerator_finalize; + + enumerator_class->next_file = g_local_file_enumerator_next_file; + enumerator_class->close = g_local_file_enumerator_close; +} + +static void +g_local_file_enumerator_init (GLocalFileEnumerator *local) +{ +} + +static void +convert_file_to_io_error (GError **error, + GError *file_error) +{ + int new_code; + + if (file_error == NULL) + return; + + new_code = G_IO_ERROR_FAILED; + + if (file_error->domain == G_FILE_ERROR) { + switch (file_error->code) { + case G_FILE_ERROR_NOENT: + new_code = G_IO_ERROR_NOT_FOUND; + break; + case G_FILE_ERROR_ACCES: + new_code = G_IO_ERROR_PERMISSION_DENIED; + break; + case G_FILE_ERROR_NOTDIR: + new_code = G_IO_ERROR_NOT_DIRECTORY; + break; + default: + break; + } + } + + g_set_error (error, G_IO_ERROR, + new_code, + "%s", file_error->message); +} + +GFileEnumerator * +g_local_file_enumerator_new (const char *filename, + const char *attributes, + GFileQueryInfoFlags flags, + GCancellable *cancellable, + GError **error) +{ + GLocalFileEnumerator *local; + GDir *dir; + GError *dir_error; + int new_code; + + dir_error = NULL; + dir = g_dir_open (filename, 0, error != NULL ? &dir_error : NULL); + if (dir == NULL) { + convert_file_to_io_error (error, dir_error); + g_error_free (dir_error); + return NULL; + } + + local = g_object_new (G_TYPE_LOCAL_FILE_ENUMERATOR, NULL); + + local->dir = dir; + local->filename = g_strdup (filename); + local->matcher = g_file_attribute_matcher_new (attributes); + local->flags = flags; + + return G_FILE_ENUMERATOR (local); +} + +static GFileInfo * +g_local_file_enumerator_next_file (GFileEnumerator *enumerator, + GCancellable *cancellable, + GError **error) +{ + GLocalFileEnumerator *local = G_LOCAL_FILE_ENUMERATOR (enumerator); + const char *filename; + char *path; + GFileInfo *info; + GError *my_error = NULL; + + if (!local->got_parent_info) + { + _g_local_file_info_get_parent_info (local->filename, local->matcher, &local->parent_info); + local->got_parent_info = TRUE; + } + + next_file: + + filename = g_dir_read_name (local->dir); + if (filename == NULL) + return NULL; + + path = g_build_filename (local->filename, filename, NULL); + info = _g_local_file_info_get (filename, path, + local->matcher, + local->flags, + &local->parent_info, + &my_error); + g_free (path); + + if (info == NULL) + { + /* Failed to get info */ + /* If the file does not exist there might have been a race where + * the file was removed between the readdir and the stat, so we + * ignore the file. */ + if (my_error->domain == G_IO_ERROR && + my_error->code == G_IO_ERROR_NOT_FOUND) + { + g_error_free (my_error); + goto next_file; + } + else + g_propagate_error (error, my_error); + } + + return info; +} + +static gboolean +g_local_file_enumerator_close (GFileEnumerator *enumerator, + GCancellable *cancellable, + GError **error) +{ + GLocalFileEnumerator *local = G_LOCAL_FILE_ENUMERATOR (enumerator); + + if (local->dir) + { + g_dir_close (local->dir); + local->dir = NULL; + } + + return TRUE; +} + + diff --git a/gio/glocalfileenumerator.h b/gio/glocalfileenumerator.h new file mode 100644 index 000000000..67b49ed69 --- /dev/null +++ b/gio/glocalfileenumerator.h @@ -0,0 +1,60 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#ifndef __G_LOCAL_FILE_ENUMERATOR_H__ +#define __G_LOCAL_FILE_ENUMERATOR_H__ + +#include <gfileenumerator.h> +#include <gfileinfo.h> +#include <gfile.h> + +G_BEGIN_DECLS + +#define G_TYPE_LOCAL_FILE_ENUMERATOR (g_local_file_enumerator_get_type ()) +#define G_LOCAL_FILE_ENUMERATOR(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_LOCAL_FILE_ENUMERATOR, GLocalFileEnumerator)) +#define G_LOCAL_FILE_ENUMERATOR_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), G_TYPE_LOCAL_FILE_ENUMERATOR, GLocalFileEnumeratorClass)) +#define G_IS_LOCAL_FILE_ENUMERATOR(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_LOCAL_FILE_ENUMERATOR)) +#define G_IS_LOCAL_FILE_ENUMERATOR_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_LOCAL_FILE_ENUMERATOR)) +#define G_LOCAL_FILE_ENUMERATOR_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_TYPE_LOCAL_FILE_ENUMERATOR, GLocalFileEnumeratorClass)) + +typedef struct _GLocalFileEnumerator GLocalFileEnumerator; +typedef struct _GLocalFileEnumeratorClass GLocalFileEnumeratorClass; +typedef struct _GLocalFileEnumeratorPrivate GLocalFileEnumeratorPrivate; + + +struct _GLocalFileEnumeratorClass +{ + GFileEnumeratorClass parent_class; + +}; + +GType g_local_file_enumerator_get_type (void) G_GNUC_CONST; + +GFileEnumerator *g_local_file_enumerator_new (const char *filename, + const char *attributes, + GFileQueryInfoFlags flags, + GCancellable *cancellable, + GError **error); + +G_END_DECLS + +#endif /* __G_FILE_LOCAL_FILE_ENUMERATOR_H__ */ diff --git a/gio/glocalfileinfo.c b/gio/glocalfileinfo.c new file mode 100644 index 000000000..5f6176be5 --- /dev/null +++ b/gio/glocalfileinfo.c @@ -0,0 +1,2216 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#include <config.h> + +#include <sys/time.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <string.h> +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> +#ifdef HAVE_GRP_H +#include <grp.h> +#endif +#ifdef HAVE_PWD_H +#include <pwd.h> +#endif +#ifdef HAVE_SELINUX +#include <selinux/selinux.h> +#endif + +#ifdef HAVE_XATTR + +#if defined HAVE_SYS_XATTR_H + #include <sys/xattr.h> +#elif defined HAVE_ATTR_XATTR_H + #include <attr/xattr.h> +#else + #error "Neither <sys/xattr.h> nor <attr/xattr.h> is present but extended attribute support is enabled." +#endif /* defined HAVE_SYS_XATTR_H || HAVE_ATTR_XATTR_H */ + +#endif /* HAVE_XATTR */ + +#include <glib/gstdio.h> +#include "glibintl.h" + +#include "glocalfileinfo.h" +#include "gioerror.h" +#include "gthemedicon.h" +#include "gcontenttype.h" +#include "gcontenttypeprivate.h" + +struct ThumbMD5Context { + guint32 buf[4]; + guint32 bits[2]; + unsigned char in[64]; +}; + +typedef struct { + char *user_name; + char *real_name; +} UidData; + +G_LOCK_DEFINE_STATIC (uid_cache); +static GHashTable *uid_cache = NULL; + +G_LOCK_DEFINE_STATIC (gid_cache); +static GHashTable *gid_cache = NULL; + +static void thumb_md5 (const char *string, unsigned char digest[16]); + +char * +_g_local_file_info_create_etag (struct stat *statbuf) +{ + GTimeVal tv; + + tv.tv_sec = statbuf->st_mtime; +#if defined (HAVE_STRUCT_STAT_ST_MTIMENSEC) + tv.tv_usec = statbuf->st_mtimensec / 1000; +#elif defined (HAVE_STRUCT_STAT_ST_MTIM_TV_NSEC) + tv.tv_usec = statbuf->st_mtim.tv_nsec / 1000; +#else + tv.tv_usec = 0; +#endif + + return g_strdup_printf ("%lu:%lu", tv.tv_sec, tv.tv_usec); +} + +static char * +_g_local_file_info_create_file_id (struct stat *statbuf) +{ + return g_strdup_printf ("l%" G_GUINT64_FORMAT ":%" G_GUINT64_FORMAT, + (guint64) statbuf->st_dev, + (guint64) statbuf->st_ino); +} + +static char * +_g_local_file_info_create_fs_id (struct stat *statbuf) +{ + return g_strdup_printf ("l%" G_GUINT64_FORMAT, + (guint64) statbuf->st_dev); +} + + +static gchar * +read_link (const gchar *full_name) +{ +#ifdef HAVE_READLINK + gchar *buffer; + guint size; + + size = 256; + buffer = g_malloc (size); + + while (1) + { + int read_size; + + read_size = readlink (full_name, buffer, size); + if (read_size < 0) + { + g_free (buffer); + return NULL; + } + if (read_size < size) + { + buffer[read_size] = 0; + return buffer; + } + size *= 2; + buffer = g_realloc (buffer, size); + } +#else + return NULL; +#endif +} + +/* Get the SELinux security context */ +static void +get_selinux_context (const char *path, + GFileInfo *info, + GFileAttributeMatcher *attribute_matcher, + gboolean follow_symlinks) +{ +#ifdef HAVE_SELINUX + char *context; + + if (!g_file_attribute_matcher_matches (attribute_matcher, "selinux:context")) + return; + + if (is_selinux_enabled ()) + { + if (follow_symlinks) + { + if (lgetfilecon_raw (path, &context) < 0) + return; + } + else + { + if (getfilecon_raw (path, &context) < 0) + return; + } + + if (context) + { + g_file_info_set_attribute_string (info, "selinux:context", context); + freecon(context); + } + } +#endif +} + +#ifdef HAVE_XATTR + +static gboolean +valid_char (char c) +{ + return c >= 32 && c <= 126 && c != '\\'; +} + +static gboolean +name_is_valid (const char *str) +{ + while (*str) + { + if (!valid_char (*str++)) + return FALSE; + } + return TRUE; +} + +static char * +hex_escape_string (const char *str, gboolean *free_return) +{ + int num_invalid, i; + char *escaped_str, *p; + unsigned char c; + static char *hex_digits = "0123456789abcdef"; + int len; + + len = strlen (str); + + num_invalid = 0; + for (i = 0; i < len; i++) + { + if (!valid_char (str[i])) + num_invalid++; + } + + if (num_invalid == 0) + { + *free_return = FALSE; + return (char *)str; + } + + escaped_str = g_malloc (len + num_invalid*3 + 1); + + p = escaped_str; + for (i = 0; i < len; i++) + { + if (valid_char (str[i])) + *p++ = str[i]; + else + { + c = str[i]; + *p++ = '\\'; + *p++ = 'x'; + *p++ = hex_digits[(c >> 4) & 0xf]; + *p++ = hex_digits[c & 0xf]; + } + } + *p++ = 0; + + *free_return = TRUE; + return escaped_str; +} + +static char * +hex_unescape_string (const char *str, int *out_len, gboolean *free_return) +{ + int i; + char *unescaped_str, *p; + unsigned char c; + int len; + + len = strlen (str); + + if (strchr (str, '\\') == NULL) + { + if (out_len) + *out_len = len; + *free_return = FALSE; + return (char *)str; + } + + unescaped_str = g_malloc (len + 1); + + p = unescaped_str; + for (i = 0; i < len; i++) + { + if (str[i] == '\\' && + str[i+1] == 'x' && + len - i >= 4) + { + c = + (g_ascii_xdigit_value (str[i+2]) << 4) | + g_ascii_xdigit_value (str[i+3]); + *p++ = c; + i += 3; + } + else + *p++ = str[i]; + } + *p++ = 0; + + if (out_len) + *out_len = p - unescaped_str; + *free_return = TRUE; + return unescaped_str; +} + +static void +escape_xattr (GFileInfo *info, + const char *gio_attr, /* gio attribute name */ + const char *value, /* Is zero terminated */ + size_t len /* not including zero termination */) +{ + char *escaped_val; + gboolean free_escaped_val; + + escaped_val = hex_escape_string (value, &free_escaped_val); + + g_file_info_set_attribute_string (info, gio_attr, escaped_val); + + if (free_escaped_val) + g_free (escaped_val); +} + +static void +get_one_xattr (const char *path, + GFileInfo *info, + const char *gio_attr, + const char *xattr, + gboolean follow_symlinks) +{ + char value[64]; + char *value_p; + ssize_t len; + + if (follow_symlinks) + len = getxattr (path, xattr, value, sizeof (value)-1); + else + len = lgetxattr (path, xattr,value, sizeof (value)-1); + + value_p = NULL; + if (len >= 0) + value_p = value; + else if (len == -1 && errno == ERANGE) + { + if (follow_symlinks) + len = getxattr (path, xattr, NULL, 0); + else + len = lgetxattr (path, xattr, NULL, 0); + + if (len < 0) + return; + + value_p = g_malloc (len+1); + + if (follow_symlinks) + len = getxattr (path, xattr, value_p, len); + else + len = lgetxattr (path, xattr, value_p, len); + + if (len < 0) + { + g_free (value_p); + return; + } + } + else + return; + + /* Null terminate */ + value_p[len] = 0; + + escape_xattr (info, gio_attr, value_p, len); + + if (value_p != value) + g_free (value_p); +} + +#endif /* defined HAVE_XATTR */ + +static void +get_xattrs (const char *path, + gboolean user, + GFileInfo *info, + GFileAttributeMatcher *matcher, + gboolean follow_symlinks) +{ +#ifdef HAVE_XATTR + gboolean all; + gsize list_size; + ssize_t list_res_size; + size_t len; + char *list; + const char *attr, *attr2; + + if (user) + all = g_file_attribute_matcher_enumerate_namespace (matcher, "xattr"); + else + all = g_file_attribute_matcher_enumerate_namespace (matcher, "xattr_sys"); + + if (all) + { + if (follow_symlinks) + list_res_size = listxattr (path, NULL, 0); + else + list_res_size = llistxattr (path, NULL, 0); + + if (list_res_size == -1 || + list_res_size == 0) + return; + + list_size = list_res_size; + list = g_malloc (list_size); + + retry: + + if (follow_symlinks) + list_res_size = listxattr (path, list, list_size); + else + list_res_size = llistxattr (path, list, list_size); + + if (list_res_size == -1 && errno == ERANGE) + { + list_size = list_size * 2; + list = g_realloc (list, list_size); + goto retry; + } + + if (list_res_size == -1) + return; + + attr = list; + while (list_res_size > 0) + { + if ((user && g_str_has_prefix (attr, "user.")) || + (!user && !g_str_has_prefix (attr, "user."))) + { + char *escaped_attr, *gio_attr; + gboolean free_escaped_attr; + + if (user) + { + escaped_attr = hex_escape_string (attr + 5, &free_escaped_attr); + gio_attr = g_strconcat ("xattr:", escaped_attr, NULL); + } + else + { + escaped_attr = hex_escape_string (attr, &free_escaped_attr); + gio_attr = g_strconcat ("xattr_sys:", escaped_attr, NULL); + } + + if (free_escaped_attr) + g_free (escaped_attr); + + get_one_xattr (path, info, gio_attr, attr, follow_symlinks); + } + + len = strlen (attr) + 1; + attr += len; + list_res_size -= len; + } + + g_free (list); + } + else + { + while ((attr = g_file_attribute_matcher_enumerate_next (matcher)) != NULL) + { + char *unescaped_attribute, *a; + gboolean free_unescaped_attribute; + + attr2 = strchr (attr, ':'); + if (attr2) + { + attr2++; /* Skip ':' */ + unescaped_attribute = hex_unescape_string (attr2, NULL, &free_unescaped_attribute); + if (user) + a = g_strconcat ("user.", unescaped_attribute, NULL); + else + a = unescaped_attribute; + + get_one_xattr (path, info, attr, a, follow_symlinks); + + if (user) + g_free (a); + + if (free_unescaped_attribute) + g_free (unescaped_attribute); + } + } + } +#endif /* defined HAVE_XATTR */ +} + +#ifdef HAVE_XATTR +static void +get_one_xattr_from_fd (int fd, + GFileInfo *info, + const char *gio_attr, + const char *xattr) +{ + char value[64]; + char *value_p; + ssize_t len; + + len = fgetxattr (fd, xattr, value, sizeof (value)-1); + + value_p = NULL; + if (len >= 0) + value_p = value; + else if (len == -1 && errno == ERANGE) + { + len = fgetxattr (fd, xattr, NULL, 0); + + if (len < 0) + return; + + value_p = g_malloc (len+1); + + len = fgetxattr (fd, xattr, value_p, len); + + if (len < 0) + { + g_free (value_p); + return; + } + } + else + return; + + /* Null terminate */ + value_p[len] = 0; + + escape_xattr (info, gio_attr, value_p, len); + + if (value_p != value) + g_free (value_p); +} +#endif /* defined HAVE_XATTR */ + +static void +get_xattrs_from_fd (int fd, + gboolean user, + GFileInfo *info, + GFileAttributeMatcher *matcher) +{ +#ifdef HAVE_XATTR + gboolean all; + gsize list_size; + ssize_t list_res_size; + size_t len; + char *list; + const char *attr, *attr2; + + if (user) + all = g_file_attribute_matcher_enumerate_namespace (matcher, "xattr"); + else + all = g_file_attribute_matcher_enumerate_namespace (matcher, "xattr_sys"); + + if (all) + { + list_res_size = flistxattr (fd, NULL, 0); + + if (list_res_size == -1 || + list_res_size == 0) + return; + + list_size = list_res_size; + list = g_malloc (list_size); + + retry: + + list_res_size = flistxattr (fd, list, list_size); + + if (list_res_size == -1 && errno == ERANGE) + { + list_size = list_size * 2; + list = g_realloc (list, list_size); + goto retry; + } + + if (list_res_size == -1) + return; + + attr = list; + while (list_res_size > 0) + { + if ((user && g_str_has_prefix (attr, "user.")) || + (!user && !g_str_has_prefix (attr, "user."))) + { + char *escaped_attr, *gio_attr; + gboolean free_escaped_attr; + + if (user) + { + escaped_attr = hex_escape_string (attr + 5, &free_escaped_attr); + gio_attr = g_strconcat ("xattr:", escaped_attr, NULL); + } + else + { + escaped_attr = hex_escape_string (attr, &free_escaped_attr); + gio_attr = g_strconcat ("xattr_sys:", escaped_attr, NULL); + } + + if (free_escaped_attr) + g_free (escaped_attr); + + get_one_xattr_from_fd (fd, info, gio_attr, attr); + } + + len = strlen (attr) + 1; + attr += len; + list_res_size -= len; + } + + g_free (list); + } + else + { + while ((attr = g_file_attribute_matcher_enumerate_next (matcher)) != NULL) + { + char *unescaped_attribute, *a; + gboolean free_unescaped_attribute; + + attr2 = strchr (attr, ':'); + if (attr2) + { + attr2++; /* Skip ':' */ + unescaped_attribute = hex_unescape_string (attr2, NULL, &free_unescaped_attribute); + if (user) + a = g_strconcat ("user.", unescaped_attribute, NULL); + else + a = unescaped_attribute; + + get_one_xattr_from_fd (fd, info, attr, a); + + if (user) + g_free (a); + + if (free_unescaped_attribute) + g_free (unescaped_attribute); + } + } + } +#endif /* defined HAVE_XATTR */ +} + +#ifdef HAVE_XATTR +static gboolean +set_xattr (char *filename, + const char *escaped_attribute, + const GFileAttributeValue *attr_value, + GError **error) +{ + char *attribute, *value; + gboolean free_attribute, free_value; + int val_len, res, errsv; + gboolean is_user; + char *a; + + if (attr_value == NULL) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, + _("Attribute value must be non-NULL")); + return FALSE; + } + + if (attr_value->type != G_FILE_ATTRIBUTE_TYPE_STRING) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, + _("Invalid attribute type (string expected)")); + return FALSE; + } + + if (!name_is_valid (escaped_attribute)) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, + _("Invalid extended attribute name")); + return FALSE; + } + + if (g_str_has_prefix (escaped_attribute, "xattr:")) + { + escaped_attribute += 6; + is_user = TRUE; + } + else + { + g_assert (g_str_has_prefix (escaped_attribute, "xattr_sys:")); + escaped_attribute += 10; + is_user = FALSE; + } + + attribute = hex_unescape_string (escaped_attribute, NULL, &free_attribute); + value = hex_unescape_string (attr_value->u.string, &val_len, &free_value); + + + if (is_user) + a = g_strconcat ("user.", attribute, NULL); + else + a = attribute; + + res = setxattr (filename, a, value, val_len, 0); + errsv = errno; + + if (is_user) + g_free (a); + + if (free_attribute) + g_free (attribute); + + if (free_value) + g_free (value); + + if (res == -1) + { + g_set_error (error, G_IO_ERROR, + g_io_error_from_errno (errsv), + _("Error setting extended attribute '%s': %s"), + escaped_attribute, g_strerror (errno)); + return FALSE; + } + + return TRUE; +} + +#endif + + +void +_g_local_file_info_get_parent_info (const char *dir, + GFileAttributeMatcher *attribute_matcher, + GLocalParentFileInfo *parent_info) +{ + struct stat statbuf; + int res; + + parent_info->writable = FALSE; + parent_info->is_sticky = FALSE; + parent_info->device = 0; + + if (g_file_attribute_matcher_matches (attribute_matcher, G_FILE_ATTRIBUTE_ACCESS_CAN_RENAME) || + g_file_attribute_matcher_matches (attribute_matcher, G_FILE_ATTRIBUTE_ACCESS_CAN_DELETE) || + g_file_attribute_matcher_matches (attribute_matcher, G_FILE_ATTRIBUTE_ACCESS_CAN_TRASH) || + g_file_attribute_matcher_matches (attribute_matcher, G_FILE_ATTRIBUTE_UNIX_IS_MOUNTPOINT)) + { + parent_info->writable = (g_access (dir, W_OK) == 0); + + res = g_stat (dir, &statbuf); + + /* + * The sticky bit (S_ISVTX) on a directory means that a file in that directory can be + * renamed or deleted only by the owner of the file, by the owner of the directory, and + * by a privileged process. + */ + if (res == 0) + { + parent_info->is_sticky = (statbuf.st_mode & S_ISVTX) != 0; + parent_info->owner = statbuf.st_uid; + parent_info->device = statbuf.st_dev; + } + } +} + +static void +get_access_rights (GFileAttributeMatcher *attribute_matcher, + GFileInfo *info, + const gchar *path, + struct stat *statbuf, + GLocalParentFileInfo *parent_info) +{ + if (g_file_attribute_matcher_matches (attribute_matcher, + G_FILE_ATTRIBUTE_ACCESS_CAN_READ)) + g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_READ, + g_access (path, R_OK) == 0); + + if (g_file_attribute_matcher_matches (attribute_matcher, + G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE)) + g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE, + g_access (path, W_OK) == 0); + + if (g_file_attribute_matcher_matches (attribute_matcher, + G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE)) + g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE, + g_access (path, X_OK) == 0); + + + if (parent_info) + { + gboolean writable; + + writable = FALSE; + if (parent_info->writable) + { + if (parent_info->is_sticky) + { + uid_t uid = geteuid (); + + if (uid == statbuf->st_uid || + uid == parent_info->owner || + uid == 0) + writable = TRUE; + } + else + writable = TRUE; + } + + if (g_file_attribute_matcher_matches (attribute_matcher, G_FILE_ATTRIBUTE_ACCESS_CAN_RENAME)) + g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_RENAME, + writable); + + if (g_file_attribute_matcher_matches (attribute_matcher, G_FILE_ATTRIBUTE_ACCESS_CAN_DELETE)) + g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_DELETE, + writable); + + /* TODO: This means we can move it, but we should also look for a trash dir */ + if (g_file_attribute_matcher_matches (attribute_matcher, G_FILE_ATTRIBUTE_ACCESS_CAN_TRASH)) + g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_TRASH, + writable); + } +} + +static void +set_info_from_stat (GFileInfo *info, struct stat *statbuf, + GFileAttributeMatcher *attribute_matcher) +{ + GFileType file_type; + + file_type = G_FILE_TYPE_UNKNOWN; + + if (S_ISREG (statbuf->st_mode)) + file_type = G_FILE_TYPE_REGULAR; + else if (S_ISDIR (statbuf->st_mode)) + file_type = G_FILE_TYPE_DIRECTORY; + else if (S_ISCHR (statbuf->st_mode) || + S_ISBLK (statbuf->st_mode) || + S_ISFIFO (statbuf->st_mode) +#ifdef S_ISSOCK + || S_ISSOCK (statbuf->st_mode) +#endif + ) + file_type = G_FILE_TYPE_SPECIAL; +#ifdef S_ISLNK + else if (S_ISLNK (statbuf->st_mode)) + file_type = G_FILE_TYPE_SYMBOLIC_LINK; +#endif + + g_file_info_set_file_type (info, file_type); + g_file_info_set_size (info, statbuf->st_size); + + g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_DEVICE, statbuf->st_dev); + g_file_info_set_attribute_uint64 (info, G_FILE_ATTRIBUTE_UNIX_INODE, statbuf->st_ino); + g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_MODE, statbuf->st_mode); + g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_NLINK, statbuf->st_nlink); + g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_UID, statbuf->st_uid); + g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_GID, statbuf->st_gid); + g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_RDEV, statbuf->st_rdev); +#if defined (HAVE_STRUCT_STAT_BLKSIZE) + g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_BLOCK_SIZE, statbuf->st_blksize); +#endif +#if defined (HAVE_STRUCT_STAT_BLOCKS) + g_file_info_set_attribute_uint64 (info, G_FILE_ATTRIBUTE_UNIX_BLOCKS, statbuf->st_blocks); +#endif + + g_file_info_set_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_MODIFIED, statbuf->st_mtime); +#if defined (HAVE_STRUCT_STAT_ST_MTIMENSEC) + g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_TIME_MODIFIED_USEC, statbuf->st_mtimensec / 1000); +#elif defined (HAVE_STRUCT_STAT_ST_MTIM_TV_NSEC) + g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_TIME_MODIFIED_USEC, statbuf->st_mtim.tv_nsec / 1000); +#endif + + g_file_info_set_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_ACCESS, statbuf->st_atime); +#if defined (HAVE_STRUCT_STAT_ST_ATIMENSEC) + g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_TIME_ACCESS_USEC, statbuf->st_atimensec / 1000); +#elif defined (HAVE_STRUCT_STAT_ST_ATIM_TV_NSEC) + g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_TIME_ACCESS_USEC, statbuf->st_atim.tv_nsec / 1000); +#endif + + g_file_info_set_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_CHANGED, statbuf->st_ctime); +#if defined (HAVE_STRUCT_STAT_ST_CTIMENSEC) + g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_TIME_CHANGED_USEC, statbuf->st_ctimensec / 1000); +#elif defined (HAVE_STRUCT_STAT_ST_CTIM_TV_NSEC) + g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_TIME_CHANGED_USEC, statbuf->st_ctim.tv_nsec / 1000); +#endif + + if (g_file_attribute_matcher_matches (attribute_matcher, + G_FILE_ATTRIBUTE_ETAG_VALUE)) + { + char *etag = _g_local_file_info_create_etag (statbuf); + g_file_info_set_attribute_string (info, G_FILE_ATTRIBUTE_ETAG_VALUE, etag); + g_free (etag); + } + + if (g_file_attribute_matcher_matches (attribute_matcher, + G_FILE_ATTRIBUTE_ID_FILE)) + { + char *id = _g_local_file_info_create_file_id (statbuf); + g_file_info_set_attribute_string (info, G_FILE_ATTRIBUTE_ID_FILE, id); + g_free (id); + } + + if (g_file_attribute_matcher_matches (attribute_matcher, + G_FILE_ATTRIBUTE_ID_FS)) + { + char *id = _g_local_file_info_create_fs_id (statbuf); + g_file_info_set_attribute_string (info, G_FILE_ATTRIBUTE_ID_FS, id); + g_free (id); + } +} + +static char * +make_valid_utf8 (const char *name) +{ + GString *string; + const gchar *remainder, *invalid; + gint remaining_bytes, valid_bytes; + + string = NULL; + remainder = name; + remaining_bytes = strlen (name); + + while (remaining_bytes != 0) + { + if (g_utf8_validate (remainder, remaining_bytes, &invalid)) + break; + valid_bytes = invalid - remainder; + + if (string == NULL) + string = g_string_sized_new (remaining_bytes); + + g_string_append_len (string, remainder, valid_bytes); + /* append U+FFFD REPLACEMENT CHARACTER */ + g_string_append (string, "\357\277\275"); + + remaining_bytes -= valid_bytes + 1; + remainder = invalid + 1; + } + + if (string == NULL) + return g_strdup (name); + + g_string_append (string, remainder); + + g_assert (g_utf8_validate (string->str, -1, NULL)); + + return g_string_free (string, FALSE); +} + +static char * +convert_pwd_string_to_utf8 (char *pwd_str) +{ + char *utf8_string; + + if (!g_utf8_validate (pwd_str, -1, NULL)) + { + utf8_string = g_locale_to_utf8 (pwd_str, -1, NULL, NULL, NULL); + if (utf8_string == NULL) + utf8_string = make_valid_utf8 (pwd_str); + } + else + utf8_string = g_strdup (pwd_str); + + return utf8_string; +} + +static void +uid_data_free (UidData *data) +{ + g_free (data->user_name); + g_free (data->real_name); + g_free (data); +} + +/* called with lock held */ +static UidData * +lookup_uid_data (uid_t uid) +{ + UidData *data; + char buffer[4096]; + struct passwd pwbuf; + struct passwd *pwbufp; + char *gecos, *comma; + + if (uid_cache == NULL) + uid_cache = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)uid_data_free); + + data = g_hash_table_lookup (uid_cache, GINT_TO_POINTER (uid)); + + if (data) + return data; + + data = g_new0 (UidData, 1); + +#if defined(HAVE_POSIX_GETPWUID_R) + getpwuid_r (uid, &pwbuf, buffer, sizeof(buffer), &pwbufp); +#elif defined(HAVE_NONPOSIX_GETPWUID_R) + pwbufp = getpwuid_r (uid, &pwbuf, buffer, sizeof(buffer)); +#else + pwbufp = getpwuid (uid); +#endif + + if (pwbufp != NULL) + { + if (pwbufp->pw_name != NULL && pwbufp->pw_name[0] != 0) + data->user_name = convert_pwd_string_to_utf8 (pwbufp->pw_name); + + gecos = pwbufp->pw_gecos; + + if (gecos) + { + comma = strchr (gecos, ','); + if (comma) + *comma = 0; + data->real_name = convert_pwd_string_to_utf8 (gecos); + } + } + + /* Default fallbacks */ + if (data->real_name == NULL) + { + if (data->user_name != NULL) + data->real_name = g_strdup (data->user_name); + else + data->real_name = g_strdup_printf("user #%d", (int)uid); + } + + if (data->user_name == NULL) + data->user_name = g_strdup_printf("%d", (int)uid); + + g_hash_table_replace (uid_cache, GINT_TO_POINTER (uid), data); + + return data; +} + +static char * +get_username_from_uid (uid_t uid) +{ + char *res; + UidData *data; + + G_LOCK (uid_cache); + data = lookup_uid_data (uid); + res = g_strdup (data->user_name); + G_UNLOCK (uid_cache); + + return res; +} + +static char * +get_realname_from_uid (uid_t uid) +{ + char *res; + UidData *data; + + G_LOCK (uid_cache); + data = lookup_uid_data (uid); + res = g_strdup (data->real_name); + G_UNLOCK (uid_cache); + + return res; +} + + +/* called with lock held */ +static char * +lookup_gid_name (gid_t gid) +{ + char *name; + char buffer[4096]; + struct group gbuf; + struct group *gbufp; + + if (gid_cache == NULL) + gid_cache = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)g_free); + + name = g_hash_table_lookup (gid_cache, GINT_TO_POINTER (gid)); + + if (name) + return name; + +#if defined (HAVE_POSIX_GETGRGID_R) + getgrgid_r (gid, &gbuf, buffer, sizeof(buffer), &gbufp); +#elif defined (HAVE_NONPOSIX_GETGRGID_R) + gbufp = getgrgid_r (gid, &gbuf, buffer, sizeof(buffer)); +#else + gbufp = getgrgid (gid); +#endif + + if (gbufp != NULL && + gbufp->gr_name != NULL && + gbufp->gr_name[0] != 0) + name = convert_pwd_string_to_utf8 (gbufp->gr_name); + else + name = g_strdup_printf("%d", (int)gid); + + g_hash_table_replace (gid_cache, GINT_TO_POINTER (gid), name); + + return name; +} + +static char * +get_groupname_from_gid (gid_t gid) +{ + char *res; + char *name; + + G_LOCK (gid_cache); + name = lookup_gid_name (gid); + res = g_strdup (name); + G_UNLOCK (gid_cache); + return res; +} + +static char * +get_content_type (const char *basename, + const char *path, + struct stat *statbuf, + gboolean is_symlink, + gboolean symlink_broken, + GFileQueryInfoFlags flags, + gboolean fast) +{ + if (is_symlink && + (symlink_broken || (flags & G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS))) + return g_strdup ("inode/symlink"); + else if (S_ISDIR(statbuf->st_mode)) + return g_strdup ("inode/directory"); + else if (S_ISCHR(statbuf->st_mode)) + return g_strdup ("inode/chardevice"); + else if (S_ISBLK(statbuf->st_mode)) + return g_strdup ("inode/blockdevice"); + else if (S_ISFIFO(statbuf->st_mode)) + return g_strdup ("inode/fifo"); +#ifdef S_ISSOCK + else if (S_ISSOCK(statbuf->st_mode)) + return g_strdup ("inode/socket"); +#endif + else + { + char *content_type; + gboolean result_uncertain; + + content_type = g_content_type_guess (basename, NULL, 0, &result_uncertain); + +#ifndef G_OS_WIN32 + if (!fast && result_uncertain && path != NULL) + { + guchar sniff_buffer[4096]; + gsize sniff_length; + int fd; + + sniff_length = _g_unix_content_type_get_sniff_len (); + if (sniff_length > 4096) + sniff_length = 4096; + + fd = open (path, O_RDONLY); + if (fd != -1) + { + ssize_t res; + + res = read (fd, sniff_buffer, sniff_length); + close (fd); + if (res > 0) + { + g_free (content_type); + content_type = g_content_type_guess (basename, sniff_buffer, res, NULL); + } + } + } +#endif + + return content_type; + } + +} + +static char * +thumb_digest_to_ascii (unsigned char digest[16], const char *suffix) +{ + static const char hex_digits[] = "0123456789abcdef"; + char *res; + int i; + + res = g_malloc (33 + strlen (suffix)); + + for (i = 0; i < 16; i++) { + res[2*i] = hex_digits[digest[i] >> 4]; + res[2*i+1] = hex_digits[digest[i] & 0xf]; + } + + res[32] = 0; + + strcat (res, suffix); + + return res; +} + + +static void +get_thumbnail_attributes (const char *path, + GFileInfo *info) +{ + char *uri; + unsigned char digest[16]; + char *filename; + char *basename; + + uri = g_filename_to_uri (path, NULL, NULL); + thumb_md5 (uri, digest); + g_free (uri); + + basename = thumb_digest_to_ascii (digest, ".png"); + + filename = g_build_filename (g_get_home_dir(), ".thumbnails", "normal", basename, NULL); + + if (g_file_test (filename, G_FILE_TEST_IS_REGULAR)) + g_file_info_set_attribute_byte_string (info, G_FILE_ATTRIBUTE_THUMBNAIL_PATH, filename); + else + { + g_free (filename); + filename = g_build_filename (g_get_home_dir(), ".thumbnails", "fail", "gnome-thumbnail-factory", basename, NULL); + + if (g_file_test (filename, G_FILE_TEST_IS_REGULAR)) + g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_THUMBNAILING_FAILED, TRUE); + } + g_free (basename); + g_free (filename); +} + + +GFileInfo * +_g_local_file_info_get (const char *basename, + const char *path, + GFileAttributeMatcher *attribute_matcher, + GFileQueryInfoFlags flags, + GLocalParentFileInfo *parent_info, + GError **error) +{ + GFileInfo *info; + struct stat statbuf; + struct stat statbuf2; + int res; + gboolean is_symlink, symlink_broken; + + info = g_file_info_new (); + + /* Make sure we don't set any unwanted attributes */ + g_file_info_set_attribute_mask (info, attribute_matcher); + + g_file_info_set_name (info, basename); + + /* Avoid stat in trivial case */ + if (attribute_matcher == NULL) + return info; + + res = g_lstat (path, &statbuf); + if (res == -1) + { + g_object_unref (info); + g_set_error (error, G_IO_ERROR, + g_io_error_from_errno (errno), + _("Error stating file '%s': %s"), + path, g_strerror (errno)); + return NULL; + } + +#ifdef S_ISLNK + is_symlink = S_ISLNK (statbuf.st_mode); +#else + is_symlink = FALSE; +#endif + symlink_broken = FALSE; + + if (is_symlink) + { + g_file_info_set_is_symlink (info, TRUE); + + /* Unless NOFOLLOW was set we default to following symlinks */ + if (!(flags & G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS)) + { + res = stat (path, &statbuf2); + + /* Report broken links as symlinks */ + if (res != -1) + statbuf = statbuf2; + else + symlink_broken = TRUE; + } + } + + set_info_from_stat (info, &statbuf, attribute_matcher); + + if (basename != NULL && basename[0] == '.') + g_file_info_set_is_hidden (info, TRUE); + + if (basename != NULL && basename[strlen (basename) -1] == '~') + g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_STD_IS_BACKUP, TRUE); + + if (is_symlink && + g_file_attribute_matcher_matches (attribute_matcher, + G_FILE_ATTRIBUTE_STD_SYMLINK_TARGET)) + { + char *link = read_link (path); + g_file_info_set_symlink_target (info, link); + g_free (link); + } + + if (g_file_attribute_matcher_matches (attribute_matcher, + G_FILE_ATTRIBUTE_STD_DISPLAY_NAME)) + { + char *display_name = g_filename_display_basename (path); + + if (strstr (display_name, "\357\277\275") != NULL) + { + char *p = display_name; + display_name = g_strconcat (display_name, _(" (invalid encoding)"), NULL); + g_free (p); + } + g_file_info_set_display_name (info, display_name); + g_free (display_name); + } + + if (g_file_attribute_matcher_matches (attribute_matcher, + G_FILE_ATTRIBUTE_STD_EDIT_NAME)) + { + char *edit_name = g_filename_display_basename (path); + g_file_info_set_edit_name (info, edit_name); + g_free (edit_name); + } + + + if (g_file_attribute_matcher_matches (attribute_matcher, + G_FILE_ATTRIBUTE_STD_COPY_NAME)) + { + char *copy_name = g_filename_to_utf8 (basename, -1, NULL, NULL, NULL); + if (copy_name) + g_file_info_set_attribute_string (info, G_FILE_ATTRIBUTE_STD_COPY_NAME, copy_name); + g_free (copy_name); + } + + if (g_file_attribute_matcher_matches (attribute_matcher, + G_FILE_ATTRIBUTE_STD_CONTENT_TYPE) || + g_file_attribute_matcher_matches (attribute_matcher, + G_FILE_ATTRIBUTE_STD_ICON)) + { + char *content_type = get_content_type (basename, path, &statbuf, is_symlink, symlink_broken, flags, FALSE); + + if (content_type) + { + g_file_info_set_content_type (info, content_type); + + if (g_file_attribute_matcher_matches (attribute_matcher, + G_FILE_ATTRIBUTE_STD_ICON)) + { + char *mimetype_icon, *generic_mimetype_icon, *type_icon, *p; + char *icon_names[3]; + GIcon *icon; + int i; + + mimetype_icon = g_strdup (content_type); + + while ((p = strchr(mimetype_icon, '/')) != NULL) + *p = '-'; + + p = strchr (content_type, '/'); + if (p == NULL) + p = content_type + strlen (content_type); + + generic_mimetype_icon = g_malloc (p - content_type + strlen ("-x-generic") + 1); + memcpy (generic_mimetype_icon, content_type, p - content_type); + memcpy (generic_mimetype_icon + (p - content_type), "-x-generic", strlen ("-x-generic")); + generic_mimetype_icon[(p - content_type) + strlen ("-x-generic")] = 0; + + /* TODO: Special case desktop dir? That could be expensive with xdg dirs... */ + if (strcmp (path, g_get_home_dir ()) == 0) + type_icon = "user-home"; + else if (S_ISDIR (statbuf.st_mode)) + type_icon = "folder"; + else if (statbuf.st_mode & S_IXUSR) + type_icon = "application-x-executable"; + else + type_icon = "text-x-generic"; + + i = 0; + icon_names[i++] = mimetype_icon; + icon_names[i++] = generic_mimetype_icon; + if (strcmp (generic_mimetype_icon, type_icon) != 0 && + strcmp (mimetype_icon, type_icon) != 0) + icon_names[i++] = type_icon; + + icon = g_themed_icon_new_from_names (icon_names, i); + g_file_info_set_icon (info, icon); + + g_object_unref (icon); + g_free (mimetype_icon); + g_free (generic_mimetype_icon); + } + + g_free (content_type); + } + } + + if (g_file_attribute_matcher_matches (attribute_matcher, + G_FILE_ATTRIBUTE_STD_FAST_CONTENT_TYPE)) + { + char *content_type = get_content_type (basename, path, &statbuf, is_symlink, symlink_broken, flags, TRUE); + + if (content_type) + { + g_file_info_set_attribute_string (info, G_FILE_ATTRIBUTE_STD_FAST_CONTENT_TYPE, content_type); + g_free (content_type); + } + } + + if (g_file_attribute_matcher_matches (attribute_matcher, + G_FILE_ATTRIBUTE_OWNER_USER)) + { + char *name; + + name = get_username_from_uid (statbuf.st_uid); + if (name) + g_file_info_set_attribute_string (info, G_FILE_ATTRIBUTE_OWNER_USER, name); + g_free (name); + } + + if (g_file_attribute_matcher_matches (attribute_matcher, + G_FILE_ATTRIBUTE_OWNER_USER_REAL)) + { + char *name; + + name = get_realname_from_uid (statbuf.st_uid); + if (name) + g_file_info_set_attribute_string (info, G_FILE_ATTRIBUTE_OWNER_USER_REAL, name); + g_free (name); + } + + if (g_file_attribute_matcher_matches (attribute_matcher, + G_FILE_ATTRIBUTE_OWNER_GROUP)) + { + char *name; + + name = get_groupname_from_gid (statbuf.st_gid); + if (name) + g_file_info_set_attribute_string (info, G_FILE_ATTRIBUTE_OWNER_GROUP, name); + g_free (name); + } + + if (parent_info && parent_info->device != 0 && + g_file_attribute_matcher_matches (attribute_matcher, G_FILE_ATTRIBUTE_UNIX_IS_MOUNTPOINT) && + statbuf.st_dev != parent_info->device) + g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_UNIX_IS_MOUNTPOINT, TRUE); + + get_access_rights (attribute_matcher, info, path, &statbuf, parent_info); + + get_selinux_context (path, info, attribute_matcher, (flags & G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS) == 0); + get_xattrs (path, TRUE, info, attribute_matcher, (flags & G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS) == 0); + get_xattrs (path, FALSE, info, attribute_matcher, (flags & G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS) == 0); + + if (g_file_attribute_matcher_matches (attribute_matcher, + G_FILE_ATTRIBUTE_THUMBNAIL_PATH)) + get_thumbnail_attributes (path, info); + + g_file_info_unset_attribute_mask (info); + + return info; +} + +GFileInfo * +_g_local_file_info_get_from_fd (int fd, + char *attributes, + GError **error) +{ + struct stat stat_buf; + GFileAttributeMatcher *matcher; + GFileInfo *info; + + if (fstat (fd, &stat_buf) == -1) + { + g_set_error (error, G_IO_ERROR, + g_io_error_from_errno (errno), + _("Error stating file descriptor: %s"), + g_strerror (errno)); + return NULL; + } + + info = g_file_info_new (); + + matcher = g_file_attribute_matcher_new (attributes); + + /* Make sure we don't set any unwanted attributes */ + g_file_info_set_attribute_mask (info, matcher); + + set_info_from_stat (info, &stat_buf, matcher); + +#ifdef HAVE_SELINUX + if (g_file_attribute_matcher_matches (matcher, "selinux:context") && + is_selinux_enabled ()) + { + char *context; + if (fgetfilecon_raw (fd, &context) >= 0) + { + g_file_info_set_attribute_string (info, "selinux:context", context); + freecon(context); + } + } +#endif + + get_xattrs_from_fd (fd, TRUE, info, matcher); + get_xattrs_from_fd (fd, FALSE, info, matcher); + + g_file_attribute_matcher_unref (matcher); + + g_file_info_unset_attribute_mask (info); + + return info; +} + +static gboolean +get_uint32 (const GFileAttributeValue *value, + guint32 *val_out, + GError **error) +{ + if (value->type != G_FILE_ATTRIBUTE_TYPE_UINT32) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, + _("Invalid attribute type (uint32 expected)")); + return FALSE; + } + + *val_out = value->u.uint32; + + return TRUE; +} + +static gboolean +get_uint64 (const GFileAttributeValue *value, + guint64 *val_out, + GError **error) +{ + if (value->type != G_FILE_ATTRIBUTE_TYPE_UINT64) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, + _("Invalid attribute type (uint64 expected)")); + return FALSE; + } + + *val_out = value->u.uint64; + + return TRUE; +} + +#if defined(HAVE_SYMLINK) +static gboolean +get_byte_string (const GFileAttributeValue *value, + const char **val_out, + GError **error) +{ + if (value->type != G_FILE_ATTRIBUTE_TYPE_BYTE_STRING) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, + _("Invalid attribute type (byte string expected)")); + return FALSE; + } + + *val_out = value->u.string; + + return TRUE; +} +#endif + +static gboolean +set_unix_mode (char *filename, + const GFileAttributeValue *value, + GError **error) +{ + guint32 val; + + if (!get_uint32 (value, &val, error)) + return FALSE; + + if (g_chmod (filename, val) == -1) + { + g_set_error (error, G_IO_ERROR, + g_io_error_from_errno (errno), + _("Error setting permissions: %s"), + g_strerror (errno)); + return FALSE; + } + return TRUE; +} + +#ifdef HAVE_CHOWN +static gboolean +set_unix_uid_gid (char *filename, + const GFileAttributeValue *uid_value, + const GFileAttributeValue *gid_value, + GFileQueryInfoFlags flags, + GError **error) +{ + int res; + guint32 val; + uid_t uid; + gid_t gid; + + if (uid_value) + { + if (!get_uint32 (uid_value, &val, error)) + return FALSE; + uid = val; + } + else + uid = -1; + + if (gid_value) + { + if (!get_uint32 (gid_value, &val, error)) + return FALSE; + gid = val; + } + else + gid = -1; + + if (flags & G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS) + res = lchown (filename, uid, gid); + else + res = chown (filename, uid, gid); + + if (res == -1) + { + g_set_error (error, G_IO_ERROR, + g_io_error_from_errno (errno), + _("Error setting owner: %s"), + g_strerror (errno)); + return FALSE; + } + return TRUE; +} +#endif + +#ifdef HAVE_SYMLINK +static gboolean +set_symlink (char *filename, + const GFileAttributeValue *value, + GError **error) +{ + const char *val; + struct stat statbuf; + + if (!get_byte_string (value, &val, error)) + return FALSE; + + if (val == NULL) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, + _("symlink must be non-NULL")); + return FALSE; + } + + if (g_lstat (filename, &statbuf)) + { + g_set_error (error, G_IO_ERROR, + g_io_error_from_errno (errno), + _("Error setting symlink: %s"), + g_strerror (errno)); + return FALSE; + } + + if (!S_ISLNK (statbuf.st_mode)) + { + g_set_error (error, G_IO_ERROR, + G_IO_ERROR_NOT_SYMBOLIC_LINK, + _("Error setting symlink: file is not a symlink")); + return FALSE; + } + + if (g_unlink (filename)) + { + g_set_error (error, G_IO_ERROR, + g_io_error_from_errno (errno), + _("Error setting symlink: %s"), + g_strerror (errno)); + return FALSE; + } + + if (symlink (filename, val) != 0) + { + g_set_error (error, G_IO_ERROR, + g_io_error_from_errno (errno), + _("Error setting symlink: %s"), + g_strerror (errno)); + return FALSE; + } + + return TRUE; +} +#endif + +static int +lazy_stat (char *filename, struct stat *statbuf, gboolean *called_stat) +{ + int res; + + if (*called_stat) + return 0; + + res = g_stat (filename, statbuf); + + if (res == 0) + *called_stat = TRUE; + + return res; +} + + +#ifdef HAVE_UTIMES +static gboolean +set_mtime_atime (char *filename, + const GFileAttributeValue *mtime_value, + const GFileAttributeValue *mtime_usec_value, + const GFileAttributeValue *atime_value, + const GFileAttributeValue *atime_usec_value, + GError **error) +{ + int res; + guint64 val; + guint32 val_usec; + struct stat statbuf; + gboolean got_stat = FALSE; + struct timeval times[2] = { {0, 0}, {0, 0} }; + + /* ATIME */ + if (atime_value) + { + if (!get_uint64 (atime_value, &val, error)) + return FALSE; + times[0].tv_sec = val; + } + else + { + if (lazy_stat (filename, &statbuf, &got_stat) == 0) + { + times[0].tv_sec = statbuf.st_mtime; +#if defined (HAVE_STRUCT_STAT_ST_ATIMENSEC) + times[0].tv_usec = statbuf.st_atimensec / 1000; +#elif defined (HAVE_STRUCT_STAT_ST_ATIM_TV_NSEC) + times[0].tv_usec = statbuf.st_atim.tv_nsec / 1000; +#endif + } + } + + if (atime_usec_value) + { + if (!get_uint32 (atime_usec_value, &val_usec, error)) + return FALSE; + times[0].tv_usec = val_usec; + } + + /* MTIME */ + if (mtime_value) + { + if (!get_uint64 (mtime_value, &val, error)) + return FALSE; + times[1].tv_sec = val; + } + else + { + if (lazy_stat (filename, &statbuf, &got_stat) == 0) + { + times[1].tv_sec = statbuf.st_mtime; +#if defined (HAVE_STRUCT_STAT_ST_MTIMENSEC) + times[1].tv_usec = statbuf.st_mtimensec / 1000; +#elif defined (HAVE_STRUCT_STAT_ST_MTIM_TV_NSEC) + times[1].tv_usec = statbuf.st_mtim.tv_nsec / 1000; +#endif + } + } + + if (mtime_usec_value) + { + if (!get_uint32 (mtime_usec_value, &val_usec, error)) + return FALSE; + times[1].tv_usec = val_usec; + } + + res = utimes(filename, times); + if (res == -1) + { + g_set_error (error, G_IO_ERROR, + g_io_error_from_errno (errno), + _("Error setting owner: %s"), + g_strerror (errno)); + return FALSE; + } + return TRUE; +} +#endif + +gboolean +_g_local_file_info_set_attribute (char *filename, + const char *attribute, + const GFileAttributeValue *value, + GFileQueryInfoFlags flags, + GCancellable *cancellable, + GError **error) +{ + if (strcmp (attribute, G_FILE_ATTRIBUTE_UNIX_MODE) == 0) + return set_unix_mode (filename, value, error); + +#ifdef HAVE_CHOWN + else if (strcmp (attribute, G_FILE_ATTRIBUTE_UNIX_UID) == 0) + return set_unix_uid_gid (filename, value, NULL, flags, error); + else if (strcmp (attribute, G_FILE_ATTRIBUTE_UNIX_GID) == 0) + return set_unix_uid_gid (filename, NULL, value, flags, error); +#endif + +#ifdef HAVE_SYMLINK + else if (strcmp (attribute, G_FILE_ATTRIBUTE_STD_SYMLINK_TARGET) == 0) + return set_symlink (filename, value, error); +#endif + +#ifdef HAVE_UTIMES + else if (strcmp (attribute, G_FILE_ATTRIBUTE_TIME_MODIFIED) == 0) + return set_mtime_atime (filename, value, NULL, NULL, NULL, error); + else if (strcmp (attribute, G_FILE_ATTRIBUTE_TIME_MODIFIED_USEC) == 0) + return set_mtime_atime (filename, NULL, value, NULL, NULL, error); + else if (strcmp (attribute, G_FILE_ATTRIBUTE_TIME_ACCESS) == 0) + return set_mtime_atime (filename, NULL, NULL, value, NULL, error); + else if (strcmp (attribute, G_FILE_ATTRIBUTE_TIME_ACCESS_USEC) == 0) + return set_mtime_atime (filename, NULL, NULL, NULL, value, error); +#endif + +#ifdef HAVE_XATTR + else if (g_str_has_prefix (attribute, "xattr:")) + return set_xattr (filename, attribute, value, error); + else if (g_str_has_prefix (attribute, "xattr_sys:")) + return set_xattr (filename, attribute, value, error); +#endif + + g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, + _("Setting attribute %s not supported"), attribute); + return FALSE; +} + +gboolean +_g_local_file_info_set_attributes (char *filename, + GFileInfo *info, + GFileQueryInfoFlags flags, + GCancellable *cancellable, + GError **error) +{ + GFileAttributeValue *value, *uid, *gid; + GFileAttributeValue *mtime, *mtime_usec, *atime, *atime_usec; + GFileAttributeStatus status; + gboolean res; + + /* Handles setting multiple specified data in a single set, and takes care + of ordering restrictions when setting attributes */ + + res = TRUE; + + /* Set symlink first, since this recreates the file */ +#ifdef HAVE_SYMLINK + value = g_file_info_get_attribute (info, G_FILE_ATTRIBUTE_STD_SYMLINK_TARGET); + if (value) + { + if (!set_symlink (filename, value, error)) + { + value->status = G_FILE_ATTRIBUTE_STATUS_ERROR_SETTING; + res = FALSE; + /* Don't set error multiple times */ + error = NULL; + } + else + value->status = G_FILE_ATTRIBUTE_STATUS_SET; + + } +#endif + +#ifdef HAVE_CHOWN + /* Group uid and gid setting into one call + * Change ownership before permissions, since ownership changes can + change permissions (e.g. setuid) + */ + uid = g_file_info_get_attribute (info, G_FILE_ATTRIBUTE_UNIX_UID); + gid = g_file_info_get_attribute (info, G_FILE_ATTRIBUTE_UNIX_GID); + + if (uid || gid) + { + if (!set_unix_uid_gid (filename, uid, gid, flags, error)) + { + status = G_FILE_ATTRIBUTE_STATUS_ERROR_SETTING; + res = FALSE; + /* Don't set error multiple times */ + error = NULL; + } + else + status = G_FILE_ATTRIBUTE_STATUS_SET; + if (uid) + uid->status = status; + if (gid) + gid->status = status; + } +#endif + + value = g_file_info_get_attribute (info, G_FILE_ATTRIBUTE_UNIX_MODE); + if (value) + { + if (!set_unix_mode (filename, value, error)) + { + value->status = G_FILE_ATTRIBUTE_STATUS_ERROR_SETTING; + res = FALSE; + /* Don't set error multiple times */ + error = NULL; + } + else + value->status = G_FILE_ATTRIBUTE_STATUS_SET; + + } + +#ifdef HAVE_UTIMES + /* Group all time settings into one call + * Change times as the last thing to avoid it changing due to metadata changes + */ + + mtime = g_file_info_get_attribute (info, G_FILE_ATTRIBUTE_TIME_MODIFIED); + mtime_usec = g_file_info_get_attribute (info, G_FILE_ATTRIBUTE_TIME_MODIFIED_USEC); + atime = g_file_info_get_attribute (info, G_FILE_ATTRIBUTE_TIME_ACCESS); + atime_usec = g_file_info_get_attribute (info, G_FILE_ATTRIBUTE_TIME_ACCESS_USEC); + + if (mtime || mtime_usec || atime || atime_usec) + { + if (!set_mtime_atime (filename, mtime, mtime_usec, atime, atime_usec, error)) + { + status = G_FILE_ATTRIBUTE_STATUS_ERROR_SETTING; + res = FALSE; + /* Don't set error multiple times */ + error = NULL; + } + else + status = G_FILE_ATTRIBUTE_STATUS_SET; + + if (mtime) + mtime->status = status; + if (mtime_usec) + mtime_usec->status = status; + if (atime) + atime->status = status; + if (atime_usec) + atime_usec->status = status; + } +#endif + + /* xattrs are handled by default callback */ + + return res; +} + + +/* + * This code implements the MD5 message-digest algorithm. + * The algorithm is due to Ron Rivest. This code was + * written by Colin Plumb in 1993, no copyright is claimed. + * This code is in the public domain; do with it what you wish. + * + * Equivalent code is available from RSA Data Security, Inc. + * This code has been tested against that, and is equivalent, + * except that you don't need to include two pages of legalese + * with every copy. + * + * To compute the message digest of a chunk of bytes, declare an + * ThumbMD5Context structure, pass it to thumb_md5_init, call + * thumb_md5_update as needed on buffers full of bytes, and then call + * thumb_md5_final, which will fill a supplied 32-byte array with the + * digest in ascii form. + * + */ + +static void thumb_md5_init (struct ThumbMD5Context *context); +static void thumb_md5_update (struct ThumbMD5Context *context, + unsigned char const *buf, + unsigned len); +static void thumb_md5_final (unsigned char digest[16], + struct ThumbMD5Context *context); +static void thumb_md5_transform (guint32 buf[4], + guint32 const in[16]); + + +static void +thumb_md5 (const char *string, unsigned char digest[16]) +{ + struct ThumbMD5Context md5_context; + + thumb_md5_init (&md5_context); + thumb_md5_update (&md5_context, (unsigned char *)string, strlen (string)); + thumb_md5_final (digest, &md5_context); +} + +#if G_BYTE_ORDER == G_LITTLE_ENDIAN +#define byteReverse(buf, len) /* Nothing */ +#else + +/* + * Note: this code is harmless on little-endian machines. + */ +static void +byteReverse(unsigned char *buf, unsigned longs) +{ + guint32 t; + do { + t = (guint32) ((unsigned) buf[3] << 8 | buf[2]) << 16 | + ((unsigned) buf[1] << 8 | buf[0]); + *(guint32 *) buf = t; + buf += 4; + } while (--longs); +} + +#endif + +/* + * Start MD5 accumulation. Set bit count to 0 and buffer to mysterious + * initialization constants. + */ +static void +thumb_md5_init (struct ThumbMD5Context *ctx) +{ + ctx->buf[0] = 0x67452301; + ctx->buf[1] = 0xefcdab89; + ctx->buf[2] = 0x98badcfe; + ctx->buf[3] = 0x10325476; + + ctx->bits[0] = 0; + ctx->bits[1] = 0; +} + +/* + * Update context to reflect the concatenation of another buffer full + * of bytes. + */ +static void +thumb_md5_update (struct ThumbMD5Context *ctx, + unsigned char const *buf, + unsigned len) +{ + guint32 t; + + /* Update bitcount */ + + t = ctx->bits[0]; + if ((ctx->bits[0] = t + ((guint32) len << 3)) < t) + ctx->bits[1]++; /* Carry from low to high */ + ctx->bits[1] += len >> 29; + + t = (t >> 3) & 0x3f; /* Bytes already in shsInfo->data */ + + /* Handle any leading odd-sized chunks */ + + if (t) { + unsigned char *p = (unsigned char *) ctx->in + t; + + t = 64 - t; + if (len < t) { + memcpy (p, buf, len); + return; + } + memcpy (p, buf, t); + byteReverse (ctx->in, 16); + thumb_md5_transform (ctx->buf, (guint32 *) ctx->in); + buf += t; + len -= t; + } + + /* Process data in 64-byte chunks */ + + while (len >= 64) { + memcpy (ctx->in, buf, 64); + byteReverse (ctx->in, 16); + thumb_md5_transform (ctx->buf, (guint32 *) ctx->in); + buf += 64; + len -= 64; + } + + /* Handle any remaining bytes of data. */ + + memcpy(ctx->in, buf, len); +} + +/* + * Final wrapup - pad to 64-byte boundary with the bit pattern + * 1 0* (64-bit count of bits processed, MSB-first) + */ +static void +thumb_md5_final (unsigned char digest[16], struct ThumbMD5Context *ctx) +{ + unsigned count; + unsigned char *p; + + /* Compute number of bytes mod 64 */ + count = (ctx->bits[0] >> 3) & 0x3F; + + /* Set the first char of padding to 0x80. This is safe since there is + always at least one byte free */ + p = ctx->in + count; + *p++ = 0x80; + + /* Bytes of padding needed to make 64 bytes */ + count = 64 - 1 - count; + + /* Pad out to 56 mod 64 */ + if (count < 8) { + /* Two lots of padding: Pad the first block to 64 bytes */ + memset (p, 0, count); + byteReverse (ctx->in, 16); + thumb_md5_transform (ctx->buf, (guint32 *) ctx->in); + + /* Now fill the next block with 56 bytes */ + memset(ctx->in, 0, 56); + } else { + /* Pad block to 56 bytes */ + memset(p, 0, count - 8); + } + byteReverse(ctx->in, 14); + + /* Append length in bits and transform */ + ((guint32 *) ctx->in)[14] = ctx->bits[0]; + ((guint32 *) ctx->in)[15] = ctx->bits[1]; + + thumb_md5_transform (ctx->buf, (guint32 *) ctx->in); + byteReverse ((unsigned char *) ctx->buf, 4); + memcpy (digest, ctx->buf, 16); + memset (ctx, 0, sizeof(ctx)); /* In case it's sensitive */ +} + + +/* The four core functions - F1 is optimized somewhat */ + +#define F1(x, y, z) (z ^ (x & (y ^ z))) +#define F2(x, y, z) F1 (z, x, y) +#define F3(x, y, z) (x ^ y ^ z) +#define F4(x, y, z) (y ^ (x | ~z)) + +/* This is the central step in the MD5 algorithm. */ +#define thumb_md5_step(f, w, x, y, z, data, s) \ + ( w += f(x, y, z) + data, w = w<<s | w>>(32-s), w += x ) + +/* + * The core of the MD5 algorithm, this alters an existing MD5 hash to + * reflect the addition of 16 longwords of new data. ThumbMD5Update blocks + * the data and converts bytes into longwords for this routine. + */ +static void +thumb_md5_transform (guint32 buf[4], guint32 const in[16]) +{ + register guint32 a, b, c, d; + + a = buf[0]; + b = buf[1]; + c = buf[2]; + d = buf[3]; + + thumb_md5_step(F1, a, b, c, d, in[0] + 0xd76aa478, 7); + thumb_md5_step(F1, d, a, b, c, in[1] + 0xe8c7b756, 12); + thumb_md5_step(F1, c, d, a, b, in[2] + 0x242070db, 17); + thumb_md5_step(F1, b, c, d, a, in[3] + 0xc1bdceee, 22); + thumb_md5_step(F1, a, b, c, d, in[4] + 0xf57c0faf, 7); + thumb_md5_step(F1, d, a, b, c, in[5] + 0x4787c62a, 12); + thumb_md5_step(F1, c, d, a, b, in[6] + 0xa8304613, 17); + thumb_md5_step(F1, b, c, d, a, in[7] + 0xfd469501, 22); + thumb_md5_step(F1, a, b, c, d, in[8] + 0x698098d8, 7); + thumb_md5_step(F1, d, a, b, c, in[9] + 0x8b44f7af, 12); + thumb_md5_step(F1, c, d, a, b, in[10] + 0xffff5bb1, 17); + thumb_md5_step(F1, b, c, d, a, in[11] + 0x895cd7be, 22); + thumb_md5_step(F1, a, b, c, d, in[12] + 0x6b901122, 7); + thumb_md5_step(F1, d, a, b, c, in[13] + 0xfd987193, 12); + thumb_md5_step(F1, c, d, a, b, in[14] + 0xa679438e, 17); + thumb_md5_step(F1, b, c, d, a, in[15] + 0x49b40821, 22); + + thumb_md5_step(F2, a, b, c, d, in[1] + 0xf61e2562, 5); + thumb_md5_step(F2, d, a, b, c, in[6] + 0xc040b340, 9); + thumb_md5_step(F2, c, d, a, b, in[11] + 0x265e5a51, 14); + thumb_md5_step(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20); + thumb_md5_step(F2, a, b, c, d, in[5] + 0xd62f105d, 5); + thumb_md5_step(F2, d, a, b, c, in[10] + 0x02441453, 9); + thumb_md5_step(F2, c, d, a, b, in[15] + 0xd8a1e681, 14); + thumb_md5_step(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20); + thumb_md5_step(F2, a, b, c, d, in[9] + 0x21e1cde6, 5); + thumb_md5_step(F2, d, a, b, c, in[14] + 0xc33707d6, 9); + thumb_md5_step(F2, c, d, a, b, in[3] + 0xf4d50d87, 14); + thumb_md5_step(F2, b, c, d, a, in[8] + 0x455a14ed, 20); + thumb_md5_step(F2, a, b, c, d, in[13] + 0xa9e3e905, 5); + thumb_md5_step(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9); + thumb_md5_step(F2, c, d, a, b, in[7] + 0x676f02d9, 14); + thumb_md5_step(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20); + + thumb_md5_step(F3, a, b, c, d, in[5] + 0xfffa3942, 4); + thumb_md5_step(F3, d, a, b, c, in[8] + 0x8771f681, 11); + thumb_md5_step(F3, c, d, a, b, in[11] + 0x6d9d6122, 16); + thumb_md5_step(F3, b, c, d, a, in[14] + 0xfde5380c, 23); + thumb_md5_step(F3, a, b, c, d, in[1] + 0xa4beea44, 4); + thumb_md5_step(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11); + thumb_md5_step(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16); + thumb_md5_step(F3, b, c, d, a, in[10] + 0xbebfbc70, 23); + thumb_md5_step(F3, a, b, c, d, in[13] + 0x289b7ec6, 4); + thumb_md5_step(F3, d, a, b, c, in[0] + 0xeaa127fa, 11); + thumb_md5_step(F3, c, d, a, b, in[3] + 0xd4ef3085, 16); + thumb_md5_step(F3, b, c, d, a, in[6] + 0x04881d05, 23); + thumb_md5_step(F3, a, b, c, d, in[9] + 0xd9d4d039, 4); + thumb_md5_step(F3, d, a, b, c, in[12] + 0xe6db99e5, 11); + thumb_md5_step(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16); + thumb_md5_step(F3, b, c, d, a, in[2] + 0xc4ac5665, 23); + + thumb_md5_step(F4, a, b, c, d, in[0] + 0xf4292244, 6); + thumb_md5_step(F4, d, a, b, c, in[7] + 0x432aff97, 10); + thumb_md5_step(F4, c, d, a, b, in[14] + 0xab9423a7, 15); + thumb_md5_step(F4, b, c, d, a, in[5] + 0xfc93a039, 21); + thumb_md5_step(F4, a, b, c, d, in[12] + 0x655b59c3, 6); + thumb_md5_step(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10); + thumb_md5_step(F4, c, d, a, b, in[10] + 0xffeff47d, 15); + thumb_md5_step(F4, b, c, d, a, in[1] + 0x85845dd1, 21); + thumb_md5_step(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6); + thumb_md5_step(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10); + thumb_md5_step(F4, c, d, a, b, in[6] + 0xa3014314, 15); + thumb_md5_step(F4, b, c, d, a, in[13] + 0x4e0811a1, 21); + thumb_md5_step(F4, a, b, c, d, in[4] + 0xf7537e82, 6); + thumb_md5_step(F4, d, a, b, c, in[11] + 0xbd3af235, 10); + thumb_md5_step(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15); + thumb_md5_step(F4, b, c, d, a, in[9] + 0xeb86d391, 21); + + buf[0] += a; + buf[1] += b; + buf[2] += c; + buf[3] += d; +} diff --git a/gio/glocalfileinfo.h b/gio/glocalfileinfo.h new file mode 100644 index 000000000..888d5ba38 --- /dev/null +++ b/gio/glocalfileinfo.h @@ -0,0 +1,68 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#ifndef __G_LOCAL_FILE_INFO_H__ +#define __G_LOCAL_FILE_INFO_H__ + +#include <gio/gfileinfo.h> +#include <gio/gfile.h> +#include <sys/stat.h> + +G_BEGIN_DECLS + +typedef struct { + gboolean writable; + gboolean is_sticky; + int owner; + dev_t device; +} GLocalParentFileInfo; + +void _g_local_file_info_get_parent_info (const char *dir, + GFileAttributeMatcher *attribute_matcher, + GLocalParentFileInfo *parent_info); +GFileInfo *_g_local_file_info_get (const char *basename, + const char *path, + GFileAttributeMatcher *attribute_matcher, + GFileQueryInfoFlags flags, + GLocalParentFileInfo *parent_info, + GError **error); +GFileInfo *_g_local_file_info_get_from_fd (int fd, + char *attributes, + GError **error); +char * _g_local_file_info_create_etag (struct stat *statbuf); +gboolean _g_local_file_info_set_attribute (char *filename, + const char *attribute, + const GFileAttributeValue *value, + GFileQueryInfoFlags flags, + GCancellable *cancellable, + GError **error); +gboolean _g_local_file_info_set_attributes (char *filename, + GFileInfo *info, + GFileQueryInfoFlags flags, + GCancellable *cancellable, + GError **error); + +G_END_DECLS + +#endif /* __G_FILE_LOCAL_FILE_INFO_H__ */ + + diff --git a/gio/glocalfileinputstream.c b/gio/glocalfileinputstream.c new file mode 100644 index 000000000..3f90720c2 --- /dev/null +++ b/gio/glocalfileinputstream.c @@ -0,0 +1,319 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#include <config.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> +#include <errno.h> + +#include <glib.h> +#include <glib/gstdio.h> +#include "gioerror.h" +#include "glocalfileinputstream.h" +#include "glocalfileinfo.h" +#include "glibintl.h" + + +G_DEFINE_TYPE (GLocalFileInputStream, g_local_file_input_stream, G_TYPE_FILE_INPUT_STREAM); + +struct _GLocalFileInputStreamPrivate { + int fd; +}; + +static gssize g_local_file_input_stream_read (GInputStream *stream, + void *buffer, + gsize count, + GCancellable *cancellable, + GError **error); +static gssize g_local_file_input_stream_skip (GInputStream *stream, + gsize count, + GCancellable *cancellable, + GError **error); +static gboolean g_local_file_input_stream_close (GInputStream *stream, + GCancellable *cancellable, + GError **error); +static goffset g_local_file_input_stream_tell (GFileInputStream *stream); +static gboolean g_local_file_input_stream_can_seek (GFileInputStream *stream); +static gboolean g_local_file_input_stream_seek (GFileInputStream *stream, + goffset offset, + GSeekType type, + GCancellable *cancellable, + GError **error); +static GFileInfo *g_local_file_input_stream_query_info (GFileInputStream *stream, + char *attributes, + GCancellable *cancellable, + GError **error); + +static void +g_local_file_input_stream_finalize (GObject *object) +{ + GLocalFileInputStream *file; + + file = G_LOCAL_FILE_INPUT_STREAM (object); + + if (G_OBJECT_CLASS (g_local_file_input_stream_parent_class)->finalize) + (*G_OBJECT_CLASS (g_local_file_input_stream_parent_class)->finalize) (object); +} + +static void +g_local_file_input_stream_class_init (GLocalFileInputStreamClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GInputStreamClass *stream_class = G_INPUT_STREAM_CLASS (klass); + GFileInputStreamClass *file_stream_class = G_FILE_INPUT_STREAM_CLASS (klass); + + g_type_class_add_private (klass, sizeof (GLocalFileInputStreamPrivate)); + + gobject_class->finalize = g_local_file_input_stream_finalize; + + stream_class->read = g_local_file_input_stream_read; + stream_class->skip = g_local_file_input_stream_skip; + stream_class->close = g_local_file_input_stream_close; + file_stream_class->tell = g_local_file_input_stream_tell; + file_stream_class->can_seek = g_local_file_input_stream_can_seek; + file_stream_class->seek = g_local_file_input_stream_seek; + file_stream_class->query_info = g_local_file_input_stream_query_info; +} + +static void +g_local_file_input_stream_init (GLocalFileInputStream *info) +{ + info->priv = G_TYPE_INSTANCE_GET_PRIVATE (info, + G_TYPE_LOCAL_FILE_INPUT_STREAM, + GLocalFileInputStreamPrivate); +} + +/** + * g_local_file_input_stream_new: + * @fd: File Descriptor. + * + * Returns: #GFileInputStream for the given file descriptor. + **/ +GFileInputStream * +g_local_file_input_stream_new (int fd) +{ + GLocalFileInputStream *stream; + + stream = g_object_new (G_TYPE_LOCAL_FILE_INPUT_STREAM, NULL); + stream->priv->fd = fd; + + return G_FILE_INPUT_STREAM (stream); +} + +static gssize +g_local_file_input_stream_read (GInputStream *stream, + void *buffer, + gsize count, + GCancellable *cancellable, + GError **error) +{ + GLocalFileInputStream *file; + gssize res; + + file = G_LOCAL_FILE_INPUT_STREAM (stream); + + res = -1; + while (1) + { + if (g_cancellable_set_error_if_cancelled (cancellable, error)) + break; + res = read (file->priv->fd, buffer, count); + if (res == -1) + { + if (errno == EINTR) + continue; + + g_set_error (error, G_IO_ERROR, + g_io_error_from_errno (errno), + _("Error reading from file: %s"), + g_strerror (errno)); + } + + break; + } + + return res; +} + +static gssize +g_local_file_input_stream_skip (GInputStream *stream, + gsize count, + GCancellable *cancellable, + GError **error) +{ + off_t res, start; + GLocalFileInputStream *file; + + file = G_LOCAL_FILE_INPUT_STREAM (stream); + + if (g_cancellable_set_error_if_cancelled (cancellable, error)) + return -1; + + start = lseek (file->priv->fd, 0, SEEK_CUR); + if (start == -1) + { + g_set_error (error, G_IO_ERROR, + g_io_error_from_errno (errno), + _("Error seeking in file: %s"), + g_strerror (errno)); + return -1; + } + + res = lseek (file->priv->fd, count, SEEK_CUR); + if (res == -1) + { + g_set_error (error, G_IO_ERROR, + g_io_error_from_errno (errno), + _("Error seeking in file: %s"), + g_strerror (errno)); + return -1; + } + + return res - start; +} + +static gboolean +g_local_file_input_stream_close (GInputStream *stream, + GCancellable *cancellable, + GError **error) +{ + GLocalFileInputStream *file; + int res; + + file = G_LOCAL_FILE_INPUT_STREAM (stream); + + if (file->priv->fd == -1) + return TRUE; + + while (1) + { + res = close (file->priv->fd); + if (res == -1) + { + g_set_error (error, G_IO_ERROR, + g_io_error_from_errno (errno), + _("Error closing file: %s"), + g_strerror (errno)); + } + break; + } + + return res != -1; +} + + +static goffset +g_local_file_input_stream_tell (GFileInputStream *stream) +{ + GLocalFileInputStream *file; + off_t pos; + + file = G_LOCAL_FILE_INPUT_STREAM (stream); + + pos = lseek (file->priv->fd, 0, SEEK_CUR); + + if (pos == (off_t)-1) + return 0; + + return pos; +} + +static gboolean +g_local_file_input_stream_can_seek (GFileInputStream *stream) +{ + GLocalFileInputStream *file; + off_t pos; + + file = G_LOCAL_FILE_INPUT_STREAM (stream); + + pos = lseek (file->priv->fd, 0, SEEK_CUR); + + if (pos == (off_t)-1 && + errno == ESPIPE) + return FALSE; + + return TRUE; +} + +static int +seek_type_to_lseek (GSeekType type) +{ + switch (type) + { + default: + case G_SEEK_CUR: + return SEEK_CUR; + + case G_SEEK_SET: + return SEEK_SET; + + case G_SEEK_END: + return SEEK_END; + } +} + +static gboolean +g_local_file_input_stream_seek (GFileInputStream *stream, + goffset offset, + GSeekType type, + GCancellable *cancellable, + GError **error) +{ + GLocalFileInputStream *file; + off_t pos; + + file = G_LOCAL_FILE_INPUT_STREAM (stream); + + pos = lseek (file->priv->fd, offset, seek_type_to_lseek (type)); + + if (pos == (off_t)-1) + { + g_set_error (error, G_IO_ERROR, + g_io_error_from_errno (errno), + _("Error seeking in file: %s"), + g_strerror (errno)); + return FALSE; + } + + return TRUE; +} + +static GFileInfo * +g_local_file_input_stream_query_info (GFileInputStream *stream, + char *attributes, + GCancellable *cancellable, + GError **error) +{ + GLocalFileInputStream *file; + + file = G_LOCAL_FILE_INPUT_STREAM (stream); + + if (g_cancellable_set_error_if_cancelled (cancellable, error)) + return NULL; + + return _g_local_file_info_get_from_fd (file->priv->fd, + attributes, + error); +} diff --git a/gio/glocalfileinputstream.h b/gio/glocalfileinputstream.h new file mode 100644 index 000000000..259ed8eb7 --- /dev/null +++ b/gio/glocalfileinputstream.h @@ -0,0 +1,60 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#ifndef __G_LOCAL_FILE_INPUT_STREAM_H__ +#define __G_LOCAL_FILE_INPUT_STREAM_H__ + +#include <gio/gfileinputstream.h> + +G_BEGIN_DECLS + +#define G_TYPE_LOCAL_FILE_INPUT_STREAM (g_local_file_input_stream_get_type ()) +#define G_LOCAL_FILE_INPUT_STREAM(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_LOCAL_FILE_INPUT_STREAM, GLocalFileInputStream)) +#define G_LOCAL_FILE_INPUT_STREAM_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), G_TYPE_LOCAL_FILE_INPUT_STREAM, GLocalFileInputStreamClass)) +#define G_IS_LOCAL_FILE_INPUT_STREAM(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_LOCAL_FILE_INPUT_STREAM)) +#define G_IS_LOCAL_FILE_INPUT_STREAM_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_LOCAL_FILE_INPUT_STREAM)) +#define G_LOCAL_FILE_INPUT_STREAM_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_TYPE_LOCAL_FILE_INPUT_STREAM, GLocalFileInputStreamClass)) + +typedef struct _GLocalFileInputStream GLocalFileInputStream; +typedef struct _GLocalFileInputStreamClass GLocalFileInputStreamClass; +typedef struct _GLocalFileInputStreamPrivate GLocalFileInputStreamPrivate; + +struct _GLocalFileInputStream +{ + GFileInputStream parent; + + /*< private >*/ + GLocalFileInputStreamPrivate *priv; +}; + +struct _GLocalFileInputStreamClass +{ + GFileInputStreamClass parent_class; +}; + +GType g_local_file_input_stream_get_type (void) G_GNUC_CONST; + +GFileInputStream *g_local_file_input_stream_new (int fd); + +G_END_DECLS + +#endif /* __G_LOCAL_FILE_INPUT_STREAM_H__ */ diff --git a/gio/glocalfilemonitor.c b/gio/glocalfilemonitor.c new file mode 100644 index 000000000..94ebf7b61 --- /dev/null +++ b/gio/glocalfilemonitor.c @@ -0,0 +1,211 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#include <config.h> + +#include "glocalfilemonitor.h" +#include "giomodule.h" + +#include <string.h> + +enum +{ + PROP_0, + PROP_FILENAME +}; + +G_DEFINE_ABSTRACT_TYPE (GLocalFileMonitor, g_local_file_monitor, G_TYPE_FILE_MONITOR) + +static void +g_local_file_monitor_init (GLocalFileMonitor* local_monitor) +{ +} + +static void +g_local_file_monitor_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + switch (property_id) + { + case PROP_FILENAME: + /* Do nothing */ + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static GObject * +g_local_file_monitor_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_properties) +{ + GObject *obj; + GLocalFileMonitorClass *klass; + GObjectClass *parent_class; + GLocalFileMonitor *local_monitor; + const gchar *filename = NULL; + gint i; + + klass = G_LOCAL_FILE_MONITOR_CLASS (g_type_class_peek (G_TYPE_LOCAL_FILE_MONITOR)); + parent_class = G_OBJECT_CLASS (g_type_class_peek_parent (klass)); + obj = parent_class->constructor (type, + n_construct_properties, + construct_properties); + + local_monitor = G_LOCAL_FILE_MONITOR (obj); + + for (i = 0; i < n_construct_properties; i++) + { + if (strcmp ("filename", g_param_spec_get_name (construct_properties[i].pspec)) == 0) + { + g_assert (G_VALUE_HOLDS_STRING (construct_properties[i].value)); + filename = g_value_get_string (construct_properties[i].value); + break; + } + } + + g_assert (filename != NULL); + + local_monitor->filename = g_strdup (filename); + return obj; +} + +static void +g_local_file_monitor_finalize (GObject *object) +{ + GLocalFileMonitor *local_monitor = G_LOCAL_FILE_MONITOR (object); + if (local_monitor->filename) + { + g_free (local_monitor->filename); + local_monitor->filename = NULL; + } + + if (G_OBJECT_CLASS (g_local_file_monitor_parent_class)->finalize) + (*G_OBJECT_CLASS (g_local_file_monitor_parent_class)->finalize) (object); +} + +static void +g_local_file_monitor_class_init (GLocalFileMonitorClass* klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->set_property = g_local_file_monitor_set_property; + gobject_class->finalize = g_local_file_monitor_finalize; + gobject_class->constructor = g_local_file_monitor_constructor; + + g_object_class_install_property (gobject_class, PROP_FILENAME, + g_param_spec_string ("filename", "File name", "File name to monitor", + NULL, G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE)); +} + +static gint +_compare_monitor_class_by_prio (gconstpointer a, + gconstpointer b, + gpointer user_data) +{ + GType *type1 = (GType *) a, *type2 = (GType *) b; + GLocalFileMonitorClass *klass1, *klass2; + gint ret; + + klass1 = G_LOCAL_FILE_MONITOR_CLASS (g_type_class_ref (*type1)); + klass2 = G_LOCAL_FILE_MONITOR_CLASS (g_type_class_ref (*type2)); + + ret = klass1->prio - klass2->prio; + + g_type_class_unref (klass1); + g_type_class_unref (klass2); + + return ret; +} + +extern GType g_inotify_file_monitor_get_type (void); + +static gpointer +get_default_local_file_monitor (gpointer data) +{ + GType *monitor_impls, chosen_type; + guint n_monitor_impls; + gint i; + GType *ret = (GType *) data; + +#if defined(HAVE_SYS_INOTIFY_H) || defined(HAVE_LINUX_INOTIFY_H) + /* Register Inotify monitor */ + g_inotify_file_monitor_get_type (); +#endif + + g_io_modules_ensure_loaded (GIO_MODULE_DIR); + + monitor_impls = g_type_children (G_TYPE_LOCAL_FILE_MONITOR, + &n_monitor_impls); + + chosen_type = G_TYPE_INVALID; + + g_qsort_with_data (monitor_impls, + n_monitor_impls, + sizeof (GType), + _compare_monitor_class_by_prio, + NULL); + + for (i = n_monitor_impls - 1; i >= 0 && chosen_type == G_TYPE_INVALID; i--) + { + GLocalFileMonitorClass *klass; + + klass = G_LOCAL_FILE_MONITOR_CLASS (g_type_class_ref (monitor_impls[i])); + + if (klass->is_supported()) + chosen_type = monitor_impls[i]; + + g_type_class_unref (klass); + } + + g_free (monitor_impls); + + *ret = chosen_type; + + return NULL; +} + +/** + * g_local_file_monitor_new: + * @pathname: path name to monitor. + * @flags: #GFileMonitorFlags. + * + * Returns: a new #GFileMonitor for the given @pathname. + **/ +GFileMonitor* +g_local_file_monitor_new (const char* pathname, + GFileMonitorFlags flags) +{ + static GOnce once_init = G_ONCE_INIT; + static GType monitor_type = G_TYPE_INVALID; + + g_once (&once_init, get_default_local_file_monitor, &monitor_type); + + if (monitor_type != G_TYPE_INVALID) + return G_FILE_MONITOR (g_object_new (monitor_type, "filename", pathname, NULL)); + + return NULL; +} diff --git a/gio/glocalfilemonitor.h b/gio/glocalfilemonitor.h new file mode 100644 index 000000000..b11ce8575 --- /dev/null +++ b/gio/glocalfilemonitor.h @@ -0,0 +1,59 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#ifndef __G_LOCAL_FILE_MONITOR_H__ +#define __G_LOCAL_FILE_MONITOR_H__ + +#include <glib-object.h> +#include <gio/gfilemonitor.h> + +G_BEGIN_DECLS + +#define G_TYPE_LOCAL_FILE_MONITOR (g_local_file_monitor_get_type ()) +#define G_LOCAL_FILE_MONITOR(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_LOCAL_FILE_MONITOR, GLocalFileMonitor)) +#define G_LOCAL_FILE_MONITOR_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), G_TYPE_LOCAL_FILE_MONITOR, GLocalFileMonitorClass)) +#define G_IS_LOCAL_FILE_MONITOR(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_LOCAL_FILE_MONITOR)) +#define G_IS_LOCAL_FILE_MONITOR_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_LOCAL_FILE_MONITOR)) + +typedef struct _GLocalFileMonitor GLocalFileMonitor; +typedef struct _GLocalFileMonitorClass GLocalFileMonitorClass; + +struct _GLocalFileMonitor +{ + GFileMonitor parent_instance; + gchar *filename; +}; + +struct _GLocalFileMonitorClass { + GFileMonitorClass parent_class; + gint prio; + gboolean (*is_supported) (void); +}; + +GType g_local_file_monitor_get_type (void) G_GNUC_CONST; + +GFileMonitor* g_local_file_monitor_new (const char* pathname, + GFileMonitorFlags flags); + +G_END_DECLS + +#endif /* __G_LOCAL_FILE_MONITOR_H__ */ diff --git a/gio/glocalfileoutputstream.c b/gio/glocalfileoutputstream.c new file mode 100644 index 000000000..c8b58c9da --- /dev/null +++ b/gio/glocalfileoutputstream.c @@ -0,0 +1,910 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#include <config.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> +#include <errno.h> +#include <string.h> + +#include <glib.h> +#include <glib/gstdio.h> +#include "glibintl.h" +#include "gioerror.h" +#include "glocalfileoutputstream.h" +#include "glocalfileinfo.h" + +G_DEFINE_TYPE (GLocalFileOutputStream, g_local_file_output_stream, G_TYPE_FILE_OUTPUT_STREAM); + +/* Some of the file replacement code was based on the code from gedit, + * relicenced to LGPL with permissions from the authors. + */ + +#define BACKUP_EXTENSION "~" + +struct _GLocalFileOutputStreamPrivate { + char *tmp_filename; + char *original_filename; + char *backup_filename; + char *etag; + int fd; +}; + +static gssize g_local_file_output_stream_write (GOutputStream *stream, + const void *buffer, + gsize count, + GCancellable *cancellable, + GError **error); +static gboolean g_local_file_output_stream_close (GOutputStream *stream, + GCancellable *cancellable, + GError **error); +static GFileInfo *g_local_file_output_stream_query_info (GFileOutputStream *stream, + char *attributes, + GCancellable *cancellable, + GError **error); +static char * g_local_file_output_stream_get_etag (GFileOutputStream *stream); +static goffset g_local_file_output_stream_tell (GFileOutputStream *stream); +static gboolean g_local_file_output_stream_can_seek (GFileOutputStream *stream); +static gboolean g_local_file_output_stream_seek (GFileOutputStream *stream, + goffset offset, + GSeekType type, + GCancellable *cancellable, + GError **error); +static gboolean g_local_file_output_stream_can_truncate (GFileOutputStream *stream); +static gboolean g_local_file_output_stream_truncate (GFileOutputStream *stream, + goffset size, + GCancellable *cancellable, + GError **error); + +static void +g_local_file_output_stream_finalize (GObject *object) +{ + GLocalFileOutputStream *file; + + file = G_LOCAL_FILE_OUTPUT_STREAM (object); + + g_free (file->priv->tmp_filename); + g_free (file->priv->original_filename); + g_free (file->priv->backup_filename); + g_free (file->priv->etag); + + if (G_OBJECT_CLASS (g_local_file_output_stream_parent_class)->finalize) + (*G_OBJECT_CLASS (g_local_file_output_stream_parent_class)->finalize) (object); +} + +static void +g_local_file_output_stream_class_init (GLocalFileOutputStreamClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GOutputStreamClass *stream_class = G_OUTPUT_STREAM_CLASS (klass); + GFileOutputStreamClass *file_stream_class = G_FILE_OUTPUT_STREAM_CLASS (klass); + + g_type_class_add_private (klass, sizeof (GLocalFileOutputStreamPrivate)); + + gobject_class->finalize = g_local_file_output_stream_finalize; + + stream_class->write = g_local_file_output_stream_write; + stream_class->close = g_local_file_output_stream_close; + file_stream_class->query_info = g_local_file_output_stream_query_info; + file_stream_class->get_etag = g_local_file_output_stream_get_etag; + file_stream_class->tell = g_local_file_output_stream_tell; + file_stream_class->can_seek = g_local_file_output_stream_can_seek; + file_stream_class->seek = g_local_file_output_stream_seek; + file_stream_class->can_truncate = g_local_file_output_stream_can_truncate; + file_stream_class->truncate = g_local_file_output_stream_truncate; +} + +static void +g_local_file_output_stream_init (GLocalFileOutputStream *stream) +{ + stream->priv = G_TYPE_INSTANCE_GET_PRIVATE (stream, + G_TYPE_LOCAL_FILE_OUTPUT_STREAM, + GLocalFileOutputStreamPrivate); +} + +static gssize +g_local_file_output_stream_write (GOutputStream *stream, + const void *buffer, + gsize count, + GCancellable *cancellable, + GError **error) +{ + GLocalFileOutputStream *file; + gssize res; + + file = G_LOCAL_FILE_OUTPUT_STREAM (stream); + + while (1) + { + if (g_cancellable_set_error_if_cancelled (cancellable, error)) + return -1; + res = write (file->priv->fd, buffer, count); + if (res == -1) + { + if (errno == EINTR) + continue; + + g_set_error (error, G_IO_ERROR, + g_io_error_from_errno (errno), + _("Error writing to file: %s"), + g_strerror (errno)); + } + + break; + } + + return res; +} + +static gboolean +g_local_file_output_stream_close (GOutputStream *stream, + GCancellable *cancellable, + GError **error) +{ + GLocalFileOutputStream *file; + struct stat final_stat; + int res; + + file = G_LOCAL_FILE_OUTPUT_STREAM (stream); + + if (file->priv->tmp_filename) + { + /* We need to move the temp file to its final place, + * and possibly create the backup file + */ + + if (file->priv->backup_filename) + { + if (g_cancellable_set_error_if_cancelled (cancellable, error)) + goto err_out; + +#ifdef HAVE_LINK + /* create original -> backup link, the original is then renamed over */ + if (unlink (file->priv->backup_filename) != 0 && + errno != ENOENT) + { + g_set_error (error, G_IO_ERROR, + G_IO_ERROR_CANT_CREATE_BACKUP, + _("Error removing old backup link: %s"), + g_strerror (errno)); + goto err_out; + } + + if (link (file->priv->original_filename, file->priv->backup_filename) != 0) + { + g_set_error (error, G_IO_ERROR, + G_IO_ERROR_CANT_CREATE_BACKUP, + _("Error creating backup link: %s"), + g_strerror (errno)); + goto err_out; + } +#else + /* If link not supported, just rename... */ + if (!rename (file->priv->original_filename, file->priv->backup_filename) != 0) + { + g_set_error (error, G_IO_ERROR, + G_IO_ERROR_CANT_CREATE_BACKUP, + _("Error creating backup copy: %s"), + g_strerror (errno)); + goto err_out; + } +#endif + } + + + if (g_cancellable_set_error_if_cancelled (cancellable, error)) + goto err_out; + + /* tmp -> original */ + if (rename (file->priv->tmp_filename, file->priv->original_filename) != 0) + { + g_set_error (error, G_IO_ERROR, + g_io_error_from_errno (errno), + _("Error renamining temporary file: %s"), + g_strerror (errno)); + goto err_out; + } + } + + if (g_cancellable_set_error_if_cancelled (cancellable, error)) + goto err_out; + + if (fstat (file->priv->fd, &final_stat) == 0) + file->priv->etag = _g_local_file_info_create_etag (&final_stat); + + while (1) + { + res = close (file->priv->fd); + if (res == -1) + { + g_set_error (error, G_IO_ERROR, + g_io_error_from_errno (errno), + _("Error closing file: %s"), + g_strerror (errno)); + } + break; + } + + return res != -1; + + err_out: + /* A simple try to close the fd in case we fail before the actual close */ + close (file->priv->fd); + return FALSE; +} + +static char * +g_local_file_output_stream_get_etag (GFileOutputStream *stream) +{ + GLocalFileOutputStream *file; + + file = G_LOCAL_FILE_OUTPUT_STREAM (stream); + + return g_strdup (file->priv->etag); +} + +static goffset +g_local_file_output_stream_tell (GFileOutputStream *stream) +{ + GLocalFileOutputStream *file; + off_t pos; + + file = G_LOCAL_FILE_OUTPUT_STREAM (stream); + + pos = lseek (file->priv->fd, 0, SEEK_CUR); + + if (pos == (off_t)-1) + return 0; + + return pos; +} + +static gboolean +g_local_file_output_stream_can_seek (GFileOutputStream *stream) +{ + GLocalFileOutputStream *file; + off_t pos; + + file = G_LOCAL_FILE_OUTPUT_STREAM (stream); + + pos = lseek (file->priv->fd, 0, SEEK_CUR); + + if (pos == (off_t)-1 && + errno == ESPIPE) + return FALSE; + + return TRUE; +} + +static int +seek_type_to_lseek (GSeekType type) +{ + switch (type) + { + default: + case G_SEEK_CUR: + return SEEK_CUR; + + case G_SEEK_SET: + return SEEK_SET; + + case G_SEEK_END: + return SEEK_END; + } +} + +static gboolean +g_local_file_output_stream_seek (GFileOutputStream *stream, + goffset offset, + GSeekType type, + GCancellable *cancellable, + GError **error) +{ + GLocalFileOutputStream *file; + off_t pos; + + file = G_LOCAL_FILE_OUTPUT_STREAM (stream); + + pos = lseek (file->priv->fd, offset, seek_type_to_lseek (type)); + + if (pos == (off_t)-1) + { + g_set_error (error, G_IO_ERROR, + g_io_error_from_errno (errno), + _("Error seeking in file: %s"), + g_strerror (errno)); + return FALSE; + } + + return TRUE; +} + +static gboolean +g_local_file_output_stream_can_truncate (GFileOutputStream *stream) +{ + /* We can't truncate pipes and stuff where we can't seek */ + return g_local_file_output_stream_can_seek (stream); +} + +static gboolean +g_local_file_output_stream_truncate (GFileOutputStream *stream, + goffset size, + GCancellable *cancellable, + GError **error) +{ + GLocalFileOutputStream *file; + int res; + + file = G_LOCAL_FILE_OUTPUT_STREAM (stream); + + restart: + res = ftruncate(file->priv->fd, size); + + if (res == -1) + { + if (errno == EINTR) + { + if (g_cancellable_set_error_if_cancelled (cancellable, error)) + return FALSE; + goto restart; + } + + g_set_error (error, G_IO_ERROR, + g_io_error_from_errno (errno), + _("Error truncating file: %s"), + g_strerror (errno)); + return FALSE; + } + + return TRUE; +} + + +static GFileInfo * +g_local_file_output_stream_query_info (GFileOutputStream *stream, + char *attributes, + GCancellable *cancellable, + GError **error) +{ + GLocalFileOutputStream *file; + + file = G_LOCAL_FILE_OUTPUT_STREAM (stream); + + if (g_cancellable_set_error_if_cancelled (cancellable, error)) + return NULL; + + return _g_local_file_info_get_from_fd (file->priv->fd, + attributes, + error); +} + +/** + * g_local_file_output_stream_create: + * @filename: + * @flags: + * @cancellable: optional #GCancellable object, %NULL to ignore. + * @error: a #GError location to store the error occuring, or %NULL to + * ignore. + * Returns: #GFileOutputStream. + **/ +GFileOutputStream * +g_local_file_output_stream_create (const char *filename, + GFileCreateFlags flags, + GCancellable *cancellable, + GError **error) +{ + GLocalFileOutputStream *stream; + int mode; + int fd; + + if (g_cancellable_set_error_if_cancelled (cancellable, error)) + return NULL; + + if (flags & G_FILE_CREATE_FLAGS_PRIVATE) + mode = 0600; + else + mode = 0666; + + fd = g_open (filename, + O_CREAT | O_EXCL | O_WRONLY, + 0666); + if (fd == -1) + { + int errsv = errno; + + if (errsv == EINVAL) + /* This must be an invalid filename, on e.g. FAT */ + g_set_error (error, G_IO_ERROR, + G_IO_ERROR_INVALID_FILENAME, + _("Invalid filename")); + else + g_set_error (error, G_IO_ERROR, + g_io_error_from_errno (errsv), + _("Error opening file '%s': %s"), + filename, g_strerror (errsv)); + return NULL; + } + + stream = g_object_new (G_TYPE_LOCAL_FILE_OUTPUT_STREAM, NULL); + stream->priv->fd = fd; + return G_FILE_OUTPUT_STREAM (stream); +} + +/** + * g_local_file_output_stream_append: + * @filename: + * @flags: + * @cancellable: optional #GCancellable object, %NULL to ignore. + * @error: a #GError location to store the error occuring, or %NULL to + * ignore. + * Returns: + **/ +GFileOutputStream * +g_local_file_output_stream_append (const char *filename, + GFileCreateFlags flags, + GCancellable *cancellable, + GError **error) +{ + GLocalFileOutputStream *stream; + int mode; + int fd; + + if (g_cancellable_set_error_if_cancelled (cancellable, error)) + return NULL; + + if (flags & G_FILE_CREATE_FLAGS_PRIVATE) + mode = 0600; + else + mode = 0666; + + fd = g_open (filename, + O_CREAT | O_APPEND | O_WRONLY, + mode); + if (fd == -1) + { + int errsv = errno; + + if (errsv == EINVAL) + /* This must be an invalid filename, on e.g. FAT */ + g_set_error (error, G_IO_ERROR, + G_IO_ERROR_INVALID_FILENAME, + _("Invalid filename")); + else + g_set_error (error, G_IO_ERROR, + g_io_error_from_errno (errsv), + _("Error opening file '%s': %s"), + filename, g_strerror (errsv)); + return NULL; + } + + stream = g_object_new (G_TYPE_LOCAL_FILE_OUTPUT_STREAM, NULL); + stream->priv->fd = fd; + + return G_FILE_OUTPUT_STREAM (stream); +} + +static char * +create_backup_filename (const char *filename) +{ + return g_strconcat (filename, BACKUP_EXTENSION, NULL); +} + +#define BUFSIZE 8192 /* size of normal write buffer */ + +static gboolean +copy_file_data (gint sfd, + gint dfd, + GError **error) +{ + gboolean ret = TRUE; + gpointer buffer; + const gchar *write_buffer; + ssize_t bytes_read; + ssize_t bytes_to_write; + ssize_t bytes_written; + + buffer = g_malloc (BUFSIZE); + + do + { + bytes_read = read (sfd, buffer, BUFSIZE); + if (bytes_read == -1) + { + if (errno == EINTR) + continue; + + g_set_error (error, G_IO_ERROR, + g_io_error_from_errno (errno), + _("Error reading from file: %s"), + g_strerror (errno)); + ret = FALSE; + break; + } + + bytes_to_write = bytes_read; + write_buffer = buffer; + + do + { + bytes_written = write (dfd, write_buffer, bytes_to_write); + if (bytes_written == -1) + { + if (errno == EINTR) + continue; + + g_set_error (error, G_IO_ERROR, + g_io_error_from_errno (errno), + _("Error writing to file: %s"), + g_strerror (errno)); + ret = FALSE; + break; + } + + bytes_to_write -= bytes_written; + write_buffer += bytes_written; + } + while (bytes_to_write > 0); + + } while ((bytes_read != 0) && (ret == TRUE)); + + g_free (buffer); + + return ret; +} + +static int +handle_overwrite_open (const char *filename, + const char *etag, + gboolean create_backup, + char **temp_filename, + GCancellable *cancellable, + GError **error) +{ + int fd = -1; + struct stat original_stat; + char *current_etag; + gboolean is_symlink; + int open_flags; + + /* We only need read access to the original file if we are creating a backup. + * We also add O_CREATE to avoid a race if the file was just removed */ + if (create_backup) + open_flags = O_RDWR | O_CREAT; + else + open_flags = O_WRONLY | O_CREAT; + + /* Some systems have O_NOFOLLOW, which lets us avoid some races + * when finding out if the file we opened was a symlink */ +#ifdef O_NOFOLLOW + is_symlink = FALSE; + fd = g_open (filename, open_flags | O_NOFOLLOW, 0666); + if (fd == -1 && errno == ELOOP) + { + /* Could be a symlink, or it could be a regular ELOOP error, + * but then the next open will fail too. */ + is_symlink = TRUE; + fd = g_open (filename, open_flags, 0666); + } +#else + fd = g_open (filename, open_flags, 0666); + /* This is racy, but we do it as soon as possible to minimize the race */ + is_symlink = g_file_test (filename, G_FILE_TEST_IS_SYMLINK); +#endif + + if (fd == -1) + { + g_set_error (error, G_IO_ERROR, + g_io_error_from_errno (errno), + _("Error opening file '%s': %s"), + filename, g_strerror (errno)); + return -1; + } + + if (fstat (fd, &original_stat) != 0) + { + g_set_error (error, G_IO_ERROR, + g_io_error_from_errno (errno), + _("Error stating file '%s': %s"), + filename, g_strerror (errno)); + goto err_out; + } + + /* not a regular file */ + if (!S_ISREG (original_stat.st_mode)) + { + if (S_ISDIR (original_stat.st_mode)) + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_IS_DIRECTORY, + _("Target file is a directory")); + else + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_NOT_REGULAR_FILE, + _("Target file is not a regular file")); + goto err_out; + } + + if (etag != NULL) + { + current_etag = _g_local_file_info_create_etag (&original_stat); + if (strcmp (etag, current_etag) != 0) + { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_WRONG_ETAG, + _("The file was externally modified")); + g_free (current_etag); + goto err_out; + } + g_free (current_etag); + } + + /* We use two backup strategies. + * The first one (which is faster) consist in saving to a + * tmp file then rename the original file to the backup and the + * tmp file to the original name. This is fast but doesn't work + * when the file is a link (hard or symbolic) or when we can't + * write to the current dir or can't set the permissions on the + * new file. + * The second strategy consist simply in copying the old file + * to a backup file and rewrite the contents of the file. + */ + + if (!(original_stat.st_nlink > 1) && !is_symlink) + { + char *dirname, *tmp_filename; + int tmpfd; + + dirname = g_path_get_dirname (filename); + tmp_filename = g_build_filename (dirname, ".goutputstream-XXXXXX", NULL); + g_free (dirname); + + tmpfd = g_mkstemp (tmp_filename); + if (tmpfd == -1) + { + g_free (tmp_filename); + goto fallback_strategy; + } + + /* try to keep permissions */ + + if ( +#ifdef F_CHOWN + fchown (tmpfd, original_stat.st_uid, original_stat.st_gid) == -1 || +#endif +#ifdef F_CHMOD + fchmod (tmpfd, original_stat.st_mode) == -1 || +#endif + 0 + ) + { + struct stat tmp_statbuf; + + /* Check that we really needed to change something */ + if (fstat (tmpfd, &tmp_statbuf) != 0 || + original_stat.st_uid != tmp_statbuf.st_uid || + original_stat.st_gid != tmp_statbuf.st_gid || + original_stat.st_mode != tmp_statbuf.st_mode) + { + close (tmpfd); + unlink (tmp_filename); + g_free (tmp_filename); + goto fallback_strategy; + } + } + + close (fd); + *temp_filename = tmp_filename; + return tmpfd; + } + + fallback_strategy: + + if (create_backup) + { + struct stat tmp_statbuf; + char *backup_filename; + int bfd; + + backup_filename = create_backup_filename (filename); + + if (unlink (backup_filename) == -1 && errno != ENOENT) + { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_CANT_CREATE_BACKUP, + _("Backup file creation failed")); + g_free (backup_filename); + goto err_out; + } + + bfd = g_open (backup_filename, + O_WRONLY | O_CREAT | O_EXCL, + original_stat.st_mode & 0777); + + if (bfd == -1) + { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_CANT_CREATE_BACKUP, + _("Backup file creation failed")); + g_free (backup_filename); + goto err_out; + } + + /* If needed, Try to set the group of the backup same as the + * original file. If this fails, set the protection + * bits for the group same as the protection bits for + * others. */ +#ifdef HAVE_FCHOWN + if (fstat (bfd, &tmp_statbuf) != 0) + { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_CANT_CREATE_BACKUP, + _("Backup file creation failed")); + unlink (backup_filename); + g_free (backup_filename); + goto err_out; + } + + if ((original_stat.st_gid != tmp_statbuf.st_gid) && + fchown (bfd, (uid_t) -1, original_stat.st_gid) != 0) + { + if (fchmod (bfd, + (original_stat.st_mode & 0707) | + ((original_stat.st_mode & 07) << 3)) != 0) + { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_CANT_CREATE_BACKUP, + _("Backup file creation failed")); + unlink (backup_filename); + close (bfd); + g_free (backup_filename); + goto err_out; + } + } +#endif + + if (!copy_file_data (fd, bfd, NULL)) + { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_CANT_CREATE_BACKUP, + _("Backup file creation failed")); + unlink (backup_filename); + close (bfd); + g_free (backup_filename); + + goto err_out; + } + + close (bfd); + g_free (backup_filename); + + /* Seek back to the start of the file after the backup copy */ + if (lseek (fd, 0, SEEK_SET) == -1) + { + g_set_error (error, G_IO_ERROR, + g_io_error_from_errno (errno), + _("Error seeking in file: %s"), + g_strerror (errno)); + goto err_out; + } + } + + /* Truncate the file at the start */ + if (ftruncate (fd, 0) == -1) + { + g_set_error (error, G_IO_ERROR, + g_io_error_from_errno (errno), + _("Error truncating file: %s"), + g_strerror (errno)); + goto err_out; + } + + return fd; + + err_out: + close (fd); + return -1; +} + +/** + * g_local_file_output_stream_replace: + * @filename: the file name. + * @etag: + * @create_backup: if set, create a backup of the file. + * @flags: + * @cancellable: optional #GCancellable object, %NULL to ignore. + * @error: a #GError location to store the error occuring, or %NULL to + * ignore. + * Returns: #GFileOutputStream + **/ +GFileOutputStream * +g_local_file_output_stream_replace (const char *filename, + const char *etag, + gboolean create_backup, + GFileCreateFlags flags, + GCancellable *cancellable, + GError **error) +{ + GLocalFileOutputStream *stream; + int mode; + int fd; + char *temp_file; + + if (g_cancellable_set_error_if_cancelled (cancellable, error)) + return NULL; + + temp_file = NULL; + + if (flags & G_FILE_CREATE_FLAGS_PRIVATE) + mode = 0600; + else + mode = 0666; + + /* If the file doesn't exist, create it */ + fd = g_open (filename, + O_CREAT | O_EXCL | O_WRONLY, + mode); + + if (fd == -1 && errno == EEXIST) + { + /* The file already exists */ + fd = handle_overwrite_open (filename, etag, create_backup, &temp_file, + cancellable, error); + if (fd == -1) + return NULL; + } + else if (fd == -1) + { + int errsv = errno; + + if (errsv == EINVAL) + /* This must be an invalid filename, on e.g. FAT */ + g_set_error (error, G_IO_ERROR, + G_IO_ERROR_INVALID_FILENAME, + _("Invalid filename")); + else + g_set_error (error, G_IO_ERROR, + g_io_error_from_errno (errsv), + _("Error opening file '%s': %s"), + filename, g_strerror (errsv)); + return NULL; + } + + + stream = g_object_new (G_TYPE_LOCAL_FILE_OUTPUT_STREAM, NULL); + stream->priv->fd = fd; + stream->priv->tmp_filename = temp_file; + if (create_backup) + stream->priv->backup_filename = create_backup_filename (filename); + stream->priv->original_filename = g_strdup (filename); + + return G_FILE_OUTPUT_STREAM (stream); +} diff --git a/gio/glocalfileoutputstream.h b/gio/glocalfileoutputstream.h new file mode 100644 index 000000000..fc8237d85 --- /dev/null +++ b/gio/glocalfileoutputstream.h @@ -0,0 +1,73 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#ifndef __G_LOCAL_FILE_OUTPUT_STREAM_H__ +#define __G_LOCAL_FILE_OUTPUT_STREAM_H__ + +#include <gio/gfileoutputstream.h> +#include <gio/gfile.h> + +G_BEGIN_DECLS + +#define G_TYPE_LOCAL_FILE_OUTPUT_STREAM (g_local_file_output_stream_get_type ()) +#define G_LOCAL_FILE_OUTPUT_STREAM(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_LOCAL_FILE_OUTPUT_STREAM, GLocalFileOutputStream)) +#define G_LOCAL_FILE_OUTPUT_STREAM_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), G_TYPE_LOCAL_FILE_OUTPUT_STREAM, GLocalFileOutputStreamClass)) +#define G_IS_LOCAL_FILE_OUTPUT_STREAM(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_LOCAL_FILE_OUTPUT_STREAM)) +#define G_IS_LOCAL_FILE_OUTPUT_STREAM_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_LOCAL_FILE_OUTPUT_STREAM)) +#define G_LOCAL_FILE_OUTPUT_STREAM_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_TYPE_LOCAL_FILE_OUTPUT_STREAM, GLocalFileOutputStreamClass)) + +typedef struct _GLocalFileOutputStream GLocalFileOutputStream; +typedef struct _GLocalFileOutputStreamClass GLocalFileOutputStreamClass; +typedef struct _GLocalFileOutputStreamPrivate GLocalFileOutputStreamPrivate; + +struct _GLocalFileOutputStream +{ + GFileOutputStream parent; + + /*< private >*/ + GLocalFileOutputStreamPrivate *priv; +}; + +struct _GLocalFileOutputStreamClass +{ + GFileOutputStreamClass parent_class; +}; + +GType g_local_file_output_stream_get_type (void) G_GNUC_CONST; +GFileOutputStream *g_local_file_output_stream_create (const char *filename, + GFileCreateFlags flags, + GCancellable *cancellable, + GError **error); +GFileOutputStream *g_local_file_output_stream_append (const char *filename, + GFileCreateFlags flags, + GCancellable *cancellable, + GError **error); +GFileOutputStream *g_local_file_output_stream_replace (const char *filename, + const char *etag, + gboolean create_backup, + GFileCreateFlags flags, + GCancellable *cancellable, + GError **error); + +G_END_DECLS + +#endif /* __G_LOCAL_FILE_OUTPUT_STREAM_H__ */ diff --git a/gio/glocalvfs.c b/gio/glocalvfs.c new file mode 100644 index 000000000..8a39f9866 --- /dev/null +++ b/gio/glocalvfs.c @@ -0,0 +1,191 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#include <config.h> +#include "glocalvfs.h" +#include "glocalfile.h" +#include <gio/gdummyfile.h> +#include <sys/types.h> +#include <pwd.h> + +struct _GLocalVfs +{ + GVfs parent; +}; + +struct _GLocalVfsClass +{ + GVfsClass parent_class; + +}; + +G_DEFINE_TYPE (GLocalVfs, g_local_vfs, G_TYPE_VFS) + +static void +g_local_vfs_finalize (GObject *object) +{ + /* must chain up */ + G_OBJECT_CLASS (g_local_vfs_parent_class)->finalize (object); +} + +static void +g_local_vfs_init (GLocalVfs *vfs) +{ +} + +/** + * g_local_vfs_new: + * + * Returns a new #GVfs handle. + **/ +GVfs * +g_local_vfs_new (void) +{ + return g_object_new (G_TYPE_LOCAL_VFS, NULL); +} + +static GFile * +g_local_vfs_get_file_for_path (GVfs *vfs, + const char *path) +{ + return g_local_file_new (path); +} + +static GFile * +g_local_vfs_get_file_for_uri (GVfs *vfs, + const char *uri) +{ + char *path; + GFile *file; + + path = g_filename_from_uri (uri, NULL, NULL); + + if (path != NULL) + file = g_local_file_new (path); + else + file = g_dummy_file_new (uri); + + g_free (path); + + return file; +} + +static const gchar * const * +g_local_vfs_get_supported_uri_schemes (GVfs *vfs) +{ + static const gchar * uri_schemes[] = { "file", NULL }; + + return uri_schemes; +} + +static GFile * +g_local_vfs_parse_name (GVfs *vfs, + const char *parse_name) +{ + GFile *file; + char *filename; + char *user_name; + char *user_prefix; + const char *user_start, *user_end; + char *rest; + struct passwd *passwd_file_entry; + + g_return_val_if_fail (G_IS_VFS (vfs), NULL); + g_return_val_if_fail (parse_name != NULL, NULL); + + if (g_ascii_strncasecmp ("file:", parse_name, 5) == 0) + filename = g_filename_from_uri (parse_name, NULL, NULL); + else + { + if (*parse_name == '~') + { + parse_name ++; + user_start = parse_name; + + while (*parse_name != 0 && *parse_name != '/') + parse_name++; + + user_end = parse_name; + + if (user_end == user_start) + user_prefix = g_strdup (g_get_home_dir()); + else + { + user_name = g_strndup (user_start, user_end - user_start); + passwd_file_entry = getpwnam (user_name); + g_free (user_name); + + if (passwd_file_entry != NULL && + passwd_file_entry->pw_dir != NULL) + user_prefix = g_strdup (passwd_file_entry->pw_dir); + else + user_prefix = g_strdup (g_get_home_dir ()); + } + + rest = NULL; + if (*user_end != 0) + rest = g_filename_from_utf8 (user_end, -1, NULL, NULL, NULL); + + filename = g_build_filename (user_prefix, rest, NULL); + g_free (rest); + g_free (user_prefix); + } + else + filename = g_filename_from_utf8 (parse_name, -1, NULL, NULL, NULL); + } + + if (filename == NULL) + filename = g_strdup (parse_name); + + file = g_local_file_new (filename); + g_free (filename); + + return file; +} + +static gboolean +g_local_vfs_is_active (GVfs *vfs) +{ + return TRUE; +} + +static void +g_local_vfs_class_init (GLocalVfsClass *class) +{ + GObjectClass *object_class; + GVfsClass *vfs_class; + + object_class = (GObjectClass *) class; + + object_class->finalize = g_local_vfs_finalize; + + vfs_class = G_VFS_CLASS (class); + + vfs_class->name = "local"; + vfs_class->priority = 0; + + vfs_class->is_active = g_local_vfs_is_active; + vfs_class->get_file_for_path = g_local_vfs_get_file_for_path; + vfs_class->get_file_for_uri = g_local_vfs_get_file_for_uri; + vfs_class->get_supported_uri_schemes = g_local_vfs_get_supported_uri_schemes; + vfs_class->parse_name = g_local_vfs_parse_name; +} diff --git a/gio/glocalvfs.h b/gio/glocalvfs.h new file mode 100644 index 000000000..dee124c3e --- /dev/null +++ b/gio/glocalvfs.h @@ -0,0 +1,47 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#ifndef __G_LOCAL_VFS_H__ +#define __G_LOCAL_VFS_H__ + +#include <gio/gvfs.h> + +G_BEGIN_DECLS + +#define G_TYPE_LOCAL_VFS (g_local_vfs_get_type ()) +#define G_LOCAL_VFS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), G_TYPE_LOCAL_VFS, GLocalVfs)) +#define G_LOCAL_VFS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), G_TYPE_LOCAL_VFS, GLocalVfsClass)) +#define G_IS_LOCAL_VFS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), G_TYPE_LOCAL_VFS)) +#define G_IS_LOCAL_VFS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), G_TYPE_LOCAL_VFS)) +#define G_LOCAL_VFS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), G_TYPE_LOCAL_VFS, GLocalVfsClass)) + +typedef struct _GLocalVfs GLocalVfs; +typedef struct _GLocalVfsClass GLocalVfsClass; + +GType g_local_vfs_get_type (void) G_GNUC_CONST; + +GVfs *g_local_vfs_new (void); + + +G_END_DECLS + +#endif /* __G_LOCAL_VFS_H__ */ diff --git a/gio/gmemoryinputstream.c b/gio/gmemoryinputstream.c new file mode 100644 index 000000000..fea27a4d7 --- /dev/null +++ b/gio/gmemoryinputstream.c @@ -0,0 +1,461 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Christian Kellner <gicmo@gnome.org> + */ + +#include <config.h> + +#include "gmemoryinputstream.h" +#include "ginputstream.h" +#include "gseekable.h" +#include "string.h" +#include "gsimpleasyncresult.h" + +#include "glibintl.h" + +struct _GMemoryInputStreamPrivate { + guint8 *buffer; + gsize pos; + gsize len; + gboolean free_data; +}; + +static gssize g_memory_input_stream_read (GInputStream *stream, + void *buffer, + gsize count, + GCancellable *cancellable, + GError **error); +static gssize g_memory_input_stream_skip (GInputStream *stream, + gsize count, + GCancellable *cancellable, + GError **error); +static gboolean g_memory_input_stream_close (GInputStream *stream, + GCancellable *cancellable, + GError **error); +static void g_memory_input_stream_read_async (GInputStream *stream, + void *buffer, + gsize count, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +static gssize g_memory_input_stream_read_finish (GInputStream *stream, + GAsyncResult *result, + GError **error); +static void g_memory_input_stream_skip_async (GInputStream *stream, + gsize count, + int io_priority, + GCancellable *cancellabl, + GAsyncReadyCallback callback, + gpointer datae); +static gssize g_memory_input_stream_skip_finish (GInputStream *stream, + GAsyncResult *result, + GError **error); +static void g_memory_input_stream_close_async (GInputStream *stream, + int io_priority, + GCancellable *cancellabl, + GAsyncReadyCallback callback, + gpointer data); +static gboolean g_memory_input_stream_close_finish (GInputStream *stream, + GAsyncResult *result, + GError **error); + +static void g_memory_input_stream_seekable_iface_init (GSeekableIface *iface); +static goffset g_memory_input_stream_tell (GSeekable *seekable); +static gboolean g_memory_input_stream_can_seek (GSeekable *seekable); +static gboolean g_memory_input_stream_seek (GSeekable *seekable, + goffset offset, + GSeekType type, + GCancellable *cancellable, + GError **error); +static gboolean g_memory_input_stream_can_truncate (GSeekable *seekable); +static gboolean g_memory_input_stream_truncate (GSeekable *seekable, + goffset offset, + GCancellable *cancellable, + GError **error); +static void g_memory_input_stream_finalize (GObject *object); + +G_DEFINE_TYPE_WITH_CODE (GMemoryInputStream, g_memory_input_stream, G_TYPE_INPUT_STREAM, + G_IMPLEMENT_INTERFACE (G_TYPE_SEEKABLE, + g_memory_input_stream_seekable_iface_init)) + + +static void +g_memory_input_stream_class_init (GMemoryInputStreamClass *klass) +{ + GObjectClass *object_class; + GInputStreamClass *istream_class; + + g_type_class_add_private (klass, sizeof (GMemoryInputStreamPrivate)); + + object_class = G_OBJECT_CLASS (klass); + object_class->finalize = g_memory_input_stream_finalize; + + istream_class = G_INPUT_STREAM_CLASS (klass); + istream_class->read = g_memory_input_stream_read; + istream_class->skip = g_memory_input_stream_skip; + istream_class->close = g_memory_input_stream_close; + + istream_class->read_async = g_memory_input_stream_read_async; + istream_class->read_finish = g_memory_input_stream_read_finish; + istream_class->skip_async = g_memory_input_stream_skip_async; + istream_class->skip_finish = g_memory_input_stream_skip_finish; + istream_class->close_async = g_memory_input_stream_close_async; + istream_class->close_finish = g_memory_input_stream_close_finish; +} + +static void +g_memory_input_stream_finalize (GObject *object) +{ + GMemoryInputStream *stream; + + stream = G_MEMORY_INPUT_STREAM (object); + + if (stream->priv->free_data) + g_free (stream->priv->buffer); + + if (G_OBJECT_CLASS (g_memory_input_stream_parent_class)->finalize) + (*G_OBJECT_CLASS (g_memory_input_stream_parent_class)->finalize) (object); +} + +static void +g_memory_input_stream_seekable_iface_init (GSeekableIface *iface) +{ + iface->tell = g_memory_input_stream_tell; + iface->can_seek = g_memory_input_stream_can_seek; + iface->seek = g_memory_input_stream_seek; + iface->can_truncate = g_memory_input_stream_can_truncate; + iface->truncate = g_memory_input_stream_truncate; +} + +static void +g_memory_input_stream_init (GMemoryInputStream *stream) +{ + stream->priv = G_TYPE_INSTANCE_GET_PRIVATE (stream, + G_TYPE_MEMORY_INPUT_STREAM, + GMemoryInputStreamPrivate); +} + +/** + * g_memory_input_stream_set_free_data: + * @stream: + * @free_data: + * + **/ +void +g_memory_input_stream_set_free_data (GMemoryInputStream *stream, + gboolean free_data) +{ + g_return_if_fail (G_IS_MEMORY_INPUT_STREAM (stream)); + + stream->priv->free_data = free_data; +} + +/** + * g_memory_input_stream_from_data: + * @data: input data. + * @len: length of the data. + * + * Returns: new #GInputStream read from @data of @len bytes. + **/ +GInputStream * +g_memory_input_stream_from_data (const void *data, gssize len) +{ + GInputStream *stream; + GMemoryInputStream *memory_stream; + + g_return_val_if_fail (data != NULL, NULL); + + stream = g_object_new (G_TYPE_MEMORY_INPUT_STREAM, NULL); + memory_stream = G_MEMORY_INPUT_STREAM (stream); + + if (len == -1) + len = strlen (data); + + memory_stream->priv->buffer = (guint8 *)data; + memory_stream->priv->len = len; + + return stream; +} + +static gssize +g_memory_input_stream_read (GInputStream *stream, + void *buffer, + gsize count, + GCancellable *cancellable, + GError **error) +{ + GMemoryInputStream *memory_stream; + GMemoryInputStreamPrivate * priv; + + memory_stream = G_MEMORY_INPUT_STREAM (stream); + priv = memory_stream->priv; + + count = MIN (count, priv->len - priv->pos); + memcpy (buffer, priv->buffer + priv->pos, count); + priv->pos += count; + + return count; +} + +/** + * g_memory_input_stream_get_data: + * @stream: + * + * Returns: + **/ +const void * +g_memory_input_stream_get_data (GMemoryInputStream *stream) +{ + g_return_val_if_fail (G_IS_MEMORY_INPUT_STREAM (stream), NULL); + + return stream->priv->buffer; +} + +/** + * g_memory_input_stream_get_data_size: + * @stream: + * + * Returns: + **/ +gsize +g_memory_input_stream_get_data_size (GMemoryInputStream *stream) +{ + g_return_val_if_fail (G_IS_MEMORY_INPUT_STREAM (stream), -1); + + return stream->priv->len; +} + +static gssize +g_memory_input_stream_skip (GInputStream *stream, + gsize count, + GCancellable *cancellable, + GError **error) +{ + GMemoryInputStream *memory_stream; + GMemoryInputStreamPrivate *priv; + + memory_stream = G_MEMORY_INPUT_STREAM (stream); + priv = memory_stream->priv; + + count = MIN (count, priv->len - priv->pos); + priv->pos += count; + + return count; + + +} + +static gboolean +g_memory_input_stream_close (GInputStream *stream, + GCancellable *cancellable, + GError **error) +{ + return TRUE; +} + +static void +g_memory_input_stream_read_async (GInputStream *stream, + void *buffer, + gsize count, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GSimpleAsyncResult *simple; + gssize nread; + + nread = g_memory_input_stream_read (stream, buffer, count, cancellable, NULL); + simple = g_simple_async_result_new (G_OBJECT (stream), + callback, + user_data, + g_memory_input_stream_read_async); + g_simple_async_result_set_op_res_gssize (simple, nread); + g_simple_async_result_complete_in_idle (simple); + g_object_unref (simple); +} + +static gssize +g_memory_input_stream_read_finish (GInputStream *stream, + GAsyncResult *result, + GError **error) +{ + GSimpleAsyncResult *simple; + gssize nread; + + simple = G_SIMPLE_ASYNC_RESULT (result); + g_assert (g_simple_async_result_get_source_tag (simple) == g_memory_input_stream_read_async); + + nread = g_simple_async_result_get_op_res_gssize (simple); + return nread; +} + +static void +g_memory_input_stream_skip_async (GInputStream *stream, + gsize count, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GSimpleAsyncResult *simple; + gssize nskipped; + + nskipped = g_memory_input_stream_skip (stream, count, cancellable, NULL); + simple = g_simple_async_result_new (G_OBJECT (stream), + callback, + user_data, + g_memory_input_stream_skip_async); + g_simple_async_result_set_op_res_gssize (simple, nskipped); + g_simple_async_result_complete_in_idle (simple); + g_object_unref (simple); +} + +static gssize +g_memory_input_stream_skip_finish (GInputStream *stream, + GAsyncResult *result, + GError **error) +{ + GSimpleAsyncResult *simple; + gssize nskipped; + + simple = G_SIMPLE_ASYNC_RESULT (result); + g_assert (g_simple_async_result_get_source_tag (simple) == g_memory_input_stream_skip_async); + + nskipped = g_simple_async_result_get_op_res_gssize (simple); + return nskipped; +} + +static void +g_memory_input_stream_close_async (GInputStream *stream, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GSimpleAsyncResult *simple; + + simple = g_simple_async_result_new (G_OBJECT (stream), + callback, + user_data, + g_memory_input_stream_close_async); + g_simple_async_result_complete_in_idle (simple); + g_object_unref (simple); +} + +static gboolean +g_memory_input_stream_close_finish (GInputStream *stream, + GAsyncResult *result, + GError **error) +{ + return TRUE; +} + +static goffset +g_memory_input_stream_tell (GSeekable *seekable) +{ + GMemoryInputStream *memory_stream; + GMemoryInputStreamPrivate * priv; + + memory_stream = G_MEMORY_INPUT_STREAM (seekable); + priv = memory_stream->priv; + + return priv->pos; +} + +static +gboolean g_memory_input_stream_can_seek (GSeekable *seekable) +{ + return TRUE; +} + +static gboolean +g_memory_input_stream_seek (GSeekable *seekable, + goffset offset, + GSeekType type, + GCancellable *cancellable, + GError **error) +{ + GMemoryInputStream *memory_stream; + GMemoryInputStreamPrivate * priv; + goffset absolute; + + memory_stream = G_MEMORY_INPUT_STREAM (seekable); + priv = memory_stream->priv; + + switch (type) { + + case G_SEEK_CUR: + absolute = priv->pos + offset; + break; + + case G_SEEK_SET: + absolute = offset; + break; + + case G_SEEK_END: + absolute = priv->len + offset; + break; + + default: + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_INVALID_ARGUMENT, + "Invalid GSeekType supplied"); + + return FALSE; + } + + if (absolute < 0 || absolute > priv->len) + { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_INVALID_ARGUMENT, + "Invalid seek request"); + return FALSE; + } + + priv->pos = absolute; + + return TRUE; +} + +static gboolean +g_memory_input_stream_can_truncate (GSeekable *seekable) +{ + return FALSE; +} + +static gboolean +g_memory_input_stream_truncate (GSeekable *seekable, + goffset offset, + GCancellable *cancellable, + GError **error) +{ + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_NOT_SUPPORTED, + "Cannot seek on GMemoryInputStream"); + return FALSE; +} + +/* vim: ts=2 sw=2 et */ + diff --git a/gio/gmemoryinputstream.h b/gio/gmemoryinputstream.h new file mode 100644 index 000000000..d62cb7fb2 --- /dev/null +++ b/gio/gmemoryinputstream.h @@ -0,0 +1,73 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Christian Kellner <gicmo@gnome.org> + */ + +#ifndef __G_MEMORY_INPUT_STREAM_H__ +#define __G_MEMORY_INPUT_STREAM_H__ + +#include <glib-object.h> +#include <gio/ginputstream.h> + +G_BEGIN_DECLS + +#define G_TYPE_MEMORY_INPUT_STREAM (g_memory_input_stream_get_type ()) +#define G_MEMORY_INPUT_STREAM(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_MEMORY_INPUT_STREAM, GMemoryInputStream)) +#define G_MEMORY_INPUT_STREAM_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), G_TYPE_MEMORY_INPUT_STREAM, GMemoryInputStreamClass)) +#define G_IS_MEMORY_INPUT_STREAM(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_MEMORY_INPUT_STREAM)) +#define G_IS_MEMORY_INPUT_STREAM_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_MEMORY_INPUT_STREAM)) +#define G_MEMORY_INPUT_STREAM_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_TYPE_MEMORY_INPUT_STREAM, GMemoryInputStreamClass)) + +typedef struct _GMemoryInputStream GMemoryInputStream; +typedef struct _GMemoryInputStreamClass GMemoryInputStreamClass; +typedef struct _GMemoryInputStreamPrivate GMemoryInputStreamPrivate; + +struct _GMemoryInputStream +{ + GInputStream parent; + + /*< private >*/ + GMemoryInputStreamPrivate *priv; +}; + +struct _GMemoryInputStreamClass +{ + GInputStreamClass parent_class; + + /* Padding for future expansion */ + void (*_g_reserved1) (void); + void (*_g_reserved2) (void); + void (*_g_reserved3) (void); + void (*_g_reserved4) (void); + void (*_g_reserved5) (void); +}; + + +GType g_memory_input_stream_get_type (void) G_GNUC_CONST; +GInputStream * g_memory_input_stream_from_data (const void *data, + gssize len); +void g_memory_input_stream_set_free_data (GMemoryInputStream *stream, + gboolean free_data); +const void *g_memory_input_stream_get_data (GMemoryInputStream *stream); +gsize g_memory_input_stream_get_data_size (GMemoryInputStream *stream); + +G_END_DECLS + +#endif /* __G_MEMORY_INPUT_STREAM_H__ */ diff --git a/gio/gmemoryoutputstream.c b/gio/gmemoryoutputstream.c new file mode 100644 index 000000000..966f34be0 --- /dev/null +++ b/gio/gmemoryoutputstream.c @@ -0,0 +1,678 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Christian Kellner <gicmo@gnome.org> + */ + +#include <config.h> +#include "gmemoryoutputstream.h" +#include "goutputstream.h" +#include "gseekable.h" +#include "gsimpleasyncresult.h" +#include "string.h" +#include "glibintl.h" + +struct _GMemoryOutputStreamPrivate { + + GByteArray *data; + goffset pos; + + guint max_size; + guint free_data : 1; +}; + +enum { + PROP_0, + PROP_DATA, + PROP_FREE_ARRAY, + PROP_SIZE_LIMIT +}; + +static void g_memory_output_stream_finalize (GObject *object); + +static void g_memory_output_stream_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec); + +static void g_memory_output_stream_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec); + +static gssize g_memory_output_stream_write (GOutputStream *stream, + const void *buffer, + gsize count, + GCancellable *cancellable, + GError **error); + +static gboolean g_memory_output_stream_close (GOutputStream *stream, + GCancellable *cancellable, + GError **error); + +static void g_memory_output_stream_write_async (GOutputStream *stream, + const void *buffer, + gsize count, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer data); +static gssize g_memory_output_stream_write_finish (GOutputStream *stream, + GAsyncResult *result, + GError **error); +static void g_memory_output_stream_close_async (GOutputStream *stream, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer data); +static gboolean g_memory_output_stream_close_finish (GOutputStream *stream, + GAsyncResult *result, + GError **error); + +static void g_memory_output_stream_seekable_iface_init (GSeekableIface *iface); +static goffset g_memory_output_stream_tell (GSeekable *seekable); +static gboolean g_memory_output_stream_can_seek (GSeekable *seekable); +static gboolean g_memory_output_stream_seek (GSeekable *seekable, + goffset offset, + GSeekType type, + GCancellable *cancellable, + GError **error); +static gboolean g_memory_output_stream_can_truncate (GSeekable *seekable); +static gboolean g_memory_output_stream_truncate (GSeekable *seekable, + goffset offset, + GCancellable *cancellable, + GError **error); + +G_DEFINE_TYPE_WITH_CODE (GMemoryOutputStream, g_memory_output_stream, G_TYPE_OUTPUT_STREAM, + G_IMPLEMENT_INTERFACE (G_TYPE_SEEKABLE, + g_memory_output_stream_seekable_iface_init)) + + +static void +g_memory_output_stream_class_init (GMemoryOutputStreamClass *klass) +{ + GOutputStreamClass *ostream_class; + GObjectClass *gobject_class; + + g_type_class_add_private (klass, sizeof (GMemoryOutputStreamPrivate)); + + gobject_class = G_OBJECT_CLASS (klass); + gobject_class->finalize = g_memory_output_stream_finalize; + gobject_class->get_property = g_memory_output_stream_get_property; + gobject_class->set_property = g_memory_output_stream_set_property; + + ostream_class = G_OUTPUT_STREAM_CLASS (klass); + + ostream_class->write = g_memory_output_stream_write; + ostream_class->close = g_memory_output_stream_close; + ostream_class->write_async = g_memory_output_stream_write_async; + ostream_class->write_finish = g_memory_output_stream_write_finish; + ostream_class->close_async = g_memory_output_stream_close_async; + ostream_class->close_finish = g_memory_output_stream_close_finish; + + g_object_class_install_property (gobject_class, + PROP_DATA, + g_param_spec_pointer ("data", + P_("Data byte array"), + P_("The byte array used as internal storage."), + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | + G_PARAM_STATIC_NAME|G_PARAM_STATIC_NICK|G_PARAM_STATIC_BLURB)); + + g_object_class_install_property (gobject_class, + PROP_FREE_ARRAY, + g_param_spec_boolean ("free-array", + P_("Free array data"), + P_("Wether or not the interal array should be free on close."), + FALSE, + G_PARAM_READWRITE | + G_PARAM_STATIC_NAME|G_PARAM_STATIC_NICK|G_PARAM_STATIC_BLURB)); + g_object_class_install_property (gobject_class, + PROP_SIZE_LIMIT, + g_param_spec_uint ("size-limit", + P_("Limit"), + P_("Maximum amount of bytes that can be written to the stream."), + 0, + G_MAXUINT, + 0, + G_PARAM_READWRITE | + G_PARAM_STATIC_NAME|G_PARAM_STATIC_NICK|G_PARAM_STATIC_BLURB)); + + +} + +static void +g_memory_output_stream_finalize (GObject *object) +{ + GMemoryOutputStream *stream; + + stream = G_MEMORY_OUTPUT_STREAM (object); + + if (stream->priv->free_data) + g_byte_array_free (stream->priv->data, TRUE); + + if (G_OBJECT_CLASS (g_memory_output_stream_parent_class)->finalize) + (*G_OBJECT_CLASS (g_memory_output_stream_parent_class)->finalize) (object); +} + +static void +g_memory_output_stream_seekable_iface_init (GSeekableIface *iface) +{ + iface->tell = g_memory_output_stream_tell; + iface->can_seek = g_memory_output_stream_can_seek; + iface->seek = g_memory_output_stream_seek; + iface->can_truncate = g_memory_output_stream_can_truncate; + iface->truncate = g_memory_output_stream_truncate; +} + + +static void +g_memory_output_stream_init (GMemoryOutputStream *stream) +{ + GMemoryOutputStreamPrivate *priv; + + stream->priv = G_TYPE_INSTANCE_GET_PRIVATE (stream, + G_TYPE_MEMORY_OUTPUT_STREAM, + GMemoryOutputStreamPrivate); + + priv = stream->priv; + priv->data = NULL; +} + +/** + * g_memory_output_stream_new: + * @data: a #GByteArray. + * + * Creates a new #GMemoryOutputStream. If @data is non-%NULL it will use + * that for its internal storage otherwise it will create a new #GByteArray. + * In both cases the internal #GByteArray can later be accessed through the + * "data" property. + * + * Note: The new stream will not take ownership of the supplied + * @data so you have to free it yourself after use or explicitly + * ask for it be freed on close by setting the "free-array" + * property to $TRUE. + * + * Return value: A newly created #GMemoryOutputStream object. + **/ +GOutputStream * +g_memory_output_stream_new (GByteArray *data) +{ + GOutputStream *stream; + + if (data == NULL) { + stream = g_object_new (G_TYPE_MEMORY_OUTPUT_STREAM, NULL); + } else { + stream = g_object_new (G_TYPE_MEMORY_OUTPUT_STREAM, + "data", data, + NULL); + } + + return stream; +} + +/** + * g_memory_output_stream_set_free_data: + * @ostream: + * @free_data: + * + **/ +void +g_memory_output_stream_set_free_data (GMemoryOutputStream *ostream, + gboolean free_data) +{ + GMemoryOutputStreamPrivate *priv; + + g_return_if_fail (G_IS_MEMORY_OUTPUT_STREAM (ostream)); + + priv = ostream->priv; + + priv->free_data = free_data; +} + +/** + * g_memory_output_stream_set_max_size: + * @ostream: + * @max_size: + * + **/ +void +g_memory_output_stream_set_max_size (GMemoryOutputStream *ostream, + guint max_size) +{ + GMemoryOutputStreamPrivate *priv; + + g_return_if_fail (G_IS_MEMORY_OUTPUT_STREAM (ostream)); + + priv = ostream->priv; + + priv->max_size = max_size; + + if (priv->max_size > 0 && + priv->max_size < priv->data->len) { + + g_byte_array_set_size (priv->data, priv->max_size); + + if (priv->pos > priv->max_size) { + priv->pos = priv->max_size; + } + } + + g_object_notify (G_OBJECT (ostream), "size-limit"); +} + +static void +g_memory_output_stream_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GMemoryOutputStream *ostream; + GMemoryOutputStreamPrivate *priv; + GByteArray *data; + guint max_size; + + ostream = G_MEMORY_OUTPUT_STREAM (object); + priv = ostream->priv; + + switch (prop_id) + { + case PROP_DATA: + + if (priv->data && priv->free_data) { + g_byte_array_free (priv->data, TRUE); + } + + data = g_value_get_pointer (value); + + if (data == NULL) { + data = g_byte_array_new (); + priv->free_data = TRUE; + } else { + priv->free_data = FALSE; + } + + priv->data = data; + priv->pos = 0; + g_object_notify (G_OBJECT (ostream), "data"); + break; + + case PROP_FREE_ARRAY: + priv->free_data = g_value_get_boolean (value); + break; + + case PROP_SIZE_LIMIT: + max_size = g_value_get_uint (value); + g_memory_output_stream_set_max_size (ostream, max_size); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +g_memory_output_stream_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GMemoryOutputStream *ostream; + GMemoryOutputStreamPrivate *priv; + + ostream = G_MEMORY_OUTPUT_STREAM (object); + priv = ostream->priv; + + switch (prop_id) + { + case PROP_DATA: + g_value_set_pointer (value, priv->data); + break; + + case PROP_FREE_ARRAY: + g_value_set_boolean (value, priv->free_data); + break; + + case PROP_SIZE_LIMIT: + g_value_set_uint (value, priv->max_size); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +/** + * g_memory_output_stream_get_data: + * @ostream: + * + * Returns: #GByteArray of the stream's data. + **/ +GByteArray * +g_memory_output_stream_get_data (GMemoryOutputStream *ostream) +{ + g_return_val_if_fail (G_IS_MEMORY_OUTPUT_STREAM (ostream), NULL); + + return ostream->priv->data; +} + + +static gboolean +array_check_boundary (GMemoryOutputStream *stream, + goffset size, + GError **error) +{ + GMemoryOutputStreamPrivate *priv; + + priv = stream->priv; + + if (! priv->max_size) { + return TRUE; + } + + if (priv->max_size < size || size > G_MAXUINT) { + + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_FAILED, + "Reached maximum data array limit"); + + return FALSE; + } + + return TRUE; +} + +static gssize +array_resize (GMemoryOutputStream *stream, + goffset size, + GError **error) +{ + GMemoryOutputStreamPrivate *priv; + guint old_len; + + priv = stream->priv; + + if (! array_check_boundary (stream, size, error)) + return -1; + + + if (priv->data->len == size) + return priv->data->len - priv->pos; + + + old_len = priv->data->len; + g_byte_array_set_size (priv->data, size); + + if (size > old_len && priv->pos > old_len) + memset (priv->data->data + priv->pos, 0, size - old_len); + + + return priv->data->len - priv->pos; +} + +static gssize +g_memory_output_stream_write (GOutputStream *stream, + const void *buffer, + gsize count, + GCancellable *cancellable, + GError **error) +{ + GMemoryOutputStream *ostream; + GMemoryOutputStreamPrivate *priv; + gsize new_size; + gssize n; + guint8 *dest; + + ostream = G_MEMORY_OUTPUT_STREAM (stream); + priv = ostream->priv; + + /* count < 0 is ensured by GOutputStream */ + + n = MIN (count, priv->data->len - priv->pos); + + if (n < 1) + { + new_size = priv->pos + count; + + if (priv->max_size > 0) + { + new_size = MIN (new_size, priv->max_size); + } + + n = array_resize (ostream, new_size, error); + + if (n == 0) + { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_FAILED, + "Reached maximum data array limit"); + return -1; + } + else if (n < 0) + { + return -1; + } + } + + dest = priv->data->data + priv->pos; + memcpy (dest, buffer, n); + priv->pos += n; + + return n; +} + +static gboolean +g_memory_output_stream_close (GOutputStream *stream, + GCancellable *cancellable, + GError **error) +{ + GMemoryOutputStream *ostream; + GMemoryOutputStreamPrivate *priv; + + ostream = G_MEMORY_OUTPUT_STREAM (stream); + priv = ostream->priv; + + return TRUE; +} + +static void +g_memory_output_stream_write_async (GOutputStream *stream, + const void *buffer, + gsize count, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer data) +{ + GSimpleAsyncResult *simple; + gssize nwritten; + + nwritten = g_memory_output_stream_write (stream, + buffer, + count, + cancellable, + NULL); + + + simple = g_simple_async_result_new (G_OBJECT (stream), + callback, + data, + g_memory_output_stream_write_async); + + g_simple_async_result_set_op_res_gssize (simple, nwritten); + g_simple_async_result_complete_in_idle (simple); + g_object_unref (simple); + +} + +static gssize +g_memory_output_stream_write_finish (GOutputStream *stream, + GAsyncResult *result, + GError **error) +{ + GSimpleAsyncResult *simple; + gssize nwritten; + + simple = G_SIMPLE_ASYNC_RESULT (result); + + g_assert (g_simple_async_result_get_source_tag (simple) == + g_memory_output_stream_write_async); + + nwritten = g_simple_async_result_get_op_res_gssize (simple); + return nwritten; +} + +static void +g_memory_output_stream_close_async (GOutputStream *stream, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer data) +{ + GSimpleAsyncResult *simple; + + simple = g_simple_async_result_new (G_OBJECT (stream), + callback, + data, + g_memory_output_stream_close_async); + + + /* will always return TRUE */ + g_memory_output_stream_close (stream, cancellable, NULL); + + g_simple_async_result_complete_in_idle (simple); + g_object_unref (simple); + +} + +static gboolean +g_memory_output_stream_close_finish (GOutputStream *stream, + GAsyncResult *result, + GError **error) +{ + GSimpleAsyncResult *simple; + + simple = G_SIMPLE_ASYNC_RESULT (result); + + g_assert (g_simple_async_result_get_source_tag (simple) == + g_memory_output_stream_close_async); + + + return TRUE; +} + +static goffset +g_memory_output_stream_tell (GSeekable *seekable) +{ + GMemoryOutputStream *stream; + GMemoryOutputStreamPrivate *priv; + + stream = G_MEMORY_OUTPUT_STREAM (seekable); + priv = stream->priv; + + return priv->pos; +} + +static gboolean +g_memory_output_stream_can_seek (GSeekable *seekable) +{ + return TRUE; +} + +static gboolean +g_memory_output_stream_seek (GSeekable *seekable, + goffset offset, + GSeekType type, + GCancellable *cancellable, + GError **error) +{ + GMemoryOutputStream *stream; + GMemoryOutputStreamPrivate *priv; + goffset absolute; + + stream = G_MEMORY_OUTPUT_STREAM (seekable); + priv = stream->priv; + + switch (type) { + + case G_SEEK_CUR: + absolute = priv->pos + offset; + break; + + case G_SEEK_SET: + absolute = offset; + break; + + case G_SEEK_END: + absolute = priv->data->len + offset; + break; + + default: + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_INVALID_ARGUMENT, + "Invalid GSeekType supplied"); + + return FALSE; + } + + if (absolute < 0) { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_INVALID_ARGUMENT, + "Invalid seek request"); + return FALSE; + } + + if (! array_check_boundary (stream, absolute, error)) + return FALSE; + + priv->pos = absolute; + + return TRUE; +} + +static gboolean +g_memory_output_stream_can_truncate (GSeekable *seekable) +{ + return TRUE; +} + +static gboolean +g_memory_output_stream_truncate (GSeekable *seekable, + goffset offset, + GCancellable *cancellable, + GError **error) +{ + GMemoryOutputStream *ostream; + GMemoryOutputStreamPrivate *priv; + + ostream = G_MEMORY_OUTPUT_STREAM (seekable); + priv = ostream->priv; + + if (array_resize (ostream, offset, error) < 0) + return FALSE; + + return TRUE; +} + +/* vim: ts=2 sw=2 et */ diff --git a/gio/gmemoryoutputstream.h b/gio/gmemoryoutputstream.h new file mode 100644 index 000000000..bcb3cc7de --- /dev/null +++ b/gio/gmemoryoutputstream.h @@ -0,0 +1,73 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Christian Kellner <gicmo@gnome.org> + */ + +#ifndef __G_MEMORY_OUTPUT_STREAM_H__ +#define __G_MEMORY_OUTPUT_STREAM_H__ + +#include <glib-object.h> +#include <gio/goutputstream.h> + +G_BEGIN_DECLS + +#define G_TYPE_MEMORY_OUTPUT_STREAM (g_memory_output_stream_get_type ()) +#define G_MEMORY_OUTPUT_STREAM(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_MEMORY_OUTPUT_STREAM, GMemoryOutputStream)) +#define G_MEMORY_OUTPUT_STREAM_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), G_TYPE_MEMORY_OUTPUT_STREAM, GMemoryOutputStreamClass)) +#define G_IS_MEMORY_OUTPUT_STREAM(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_MEMORY_OUTPUT_STREAM)) +#define G_IS_MEMORY_OUTPUT_STREAM_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_MEMORY_OUTPUT_STREAM)) +#define G_MEMORY_OUTPUT_STREAM_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_TYPE_MEMORY_OUTPUT_STREAM, GMemoryOutputStreamClass)) + +typedef struct _GMemoryOutputStream GMemoryOutputStream; +typedef struct _GMemoryOutputStreamClass GMemoryOutputStreamClass; +typedef struct _GMemoryOutputStreamPrivate GMemoryOutputStreamPrivate; + +struct _GMemoryOutputStream +{ + GOutputStream parent; + + /*< private >*/ + GMemoryOutputStreamPrivate *priv; +}; + +struct _GMemoryOutputStreamClass +{ + GOutputStreamClass parent_class; + + /* Padding for future expansion */ + void (*_g_reserved1) (void); + void (*_g_reserved2) (void); + void (*_g_reserved3) (void); + void (*_g_reserved4) (void); + void (*_g_reserved5) (void); +}; + + +GType g_memory_output_stream_get_type (void) G_GNUC_CONST; +GOutputStream *g_memory_output_stream_new (GByteArray *data); +void g_memory_output_stream_set_max_size (GMemoryOutputStream *ostream, + guint max_size); +GByteArray * g_memory_output_stream_get_data (GMemoryOutputStream *ostream); +void g_memory_output_stream_set_free_data (GMemoryOutputStream *ostream, + gboolean free_data); + +G_END_DECLS + +#endif /* __G_MEMORY_OUTPUT_STREAM_H__ */ diff --git a/gio/gmountoperation.c b/gio/gmountoperation.c new file mode 100644 index 000000000..034854bd3 --- /dev/null +++ b/gio/gmountoperation.c @@ -0,0 +1,348 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#include <config.h> + +#include <string.h> + +#include "gmountoperation.h" +#include "gio-marshal.h" +#include "glibintl.h" + +G_DEFINE_TYPE (GMountOperation, g_mount_operation, G_TYPE_OBJECT); + +enum { + ASK_PASSWORD, + ASK_QUESTION, + REPLY, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL] = { 0 }; + +struct _GMountOperationPrivate { + char *password; + char *user; + char *domain; + gboolean anonymous; + GPasswordSave password_save; + int choice; +}; + +static void +g_mount_operation_finalize (GObject *object) +{ + GMountOperation *operation; + GMountOperationPrivate *priv; + + operation = G_MOUNT_OPERATION (object); + + priv = operation->priv; + + g_free (priv->password); + g_free (priv->user); + g_free (priv->domain); + + if (G_OBJECT_CLASS (g_mount_operation_parent_class)->finalize) + (*G_OBJECT_CLASS (g_mount_operation_parent_class)->finalize) (object); +} + +static gboolean +boolean_handled_accumulator (GSignalInvocationHint *ihint, + GValue *return_accu, + const GValue *handler_return, + gpointer dummy) +{ + gboolean continue_emission; + gboolean signal_handled; + + signal_handled = g_value_get_boolean (handler_return); + g_value_set_boolean (return_accu, signal_handled); + continue_emission = !signal_handled; + + return continue_emission; +} + +static gboolean +ask_password (GMountOperation *op, + const char *message, + const char *default_user, + const char *default_domain, + GPasswordFlags flags) +{ + return FALSE; +} + +static gboolean +ask_question (GMountOperation *op, + const char *message, + const char *choices[]) +{ + return FALSE; +} + +static void +g_mount_operation_class_init (GMountOperationClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + g_type_class_add_private (klass, sizeof (GMountOperationPrivate)); + + gobject_class->finalize = g_mount_operation_finalize; + + klass->ask_password = ask_password; + klass->ask_question = ask_question; + + signals[ASK_PASSWORD] = + g_signal_new (I_("ask_password"), + G_TYPE_FROM_CLASS (gobject_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GMountOperationClass, ask_password), + boolean_handled_accumulator, NULL, + _gio_marshal_BOOLEAN__STRING_STRING_STRING_INT, + G_TYPE_BOOLEAN, 4, + G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_UINT); + + signals[ASK_QUESTION] = + g_signal_new (I_("ask_question"), + G_TYPE_FROM_CLASS (gobject_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GMountOperationClass, ask_question), + boolean_handled_accumulator, NULL, + _gio_marshal_BOOLEAN__STRING_POINTER, + G_TYPE_BOOLEAN, 2, + G_TYPE_STRING, G_TYPE_POINTER); + + signals[REPLY] = + g_signal_new (I_("reply"), + G_TYPE_FROM_CLASS (gobject_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GMountOperationClass, reply), + NULL, NULL, + g_cclosure_marshal_VOID__BOOLEAN, + G_TYPE_NONE, 1, + G_TYPE_BOOLEAN); +} + +static void +g_mount_operation_init (GMountOperation *operation) +{ + operation->priv = G_TYPE_INSTANCE_GET_PRIVATE (operation, + G_TYPE_MOUNT_OPERATION, + GMountOperationPrivate); +} + +/** + * g_mount_operation_new: + * + * Returns: a new #GMountOperation. + **/ +GMountOperation * +g_mount_operation_new (void) +{ + return g_object_new (G_TYPE_MOUNT_OPERATION, NULL); +} + +/** + * g_mount_operation_get_username + * @op: + * + * Returns: + **/ +const char * +g_mount_operation_get_username (GMountOperation *op) +{ + g_return_val_if_fail (G_IS_MOUNT_OPERATION (op), NULL); + return op->priv->user; +} + +/** + * g_mount_operation_set_username: + * @op: + * @username: input username. + * + **/ +void +g_mount_operation_set_username (GMountOperation *op, + const char *username) +{ + g_return_if_fail (G_IS_MOUNT_OPERATION (op)); + g_free (op->priv->user); + op->priv->user = g_strdup (username); +} + +/** + * g_mount_operation_get_password: + * @op: + * + * Returns: + **/ +const char * +g_mount_operation_get_password (GMountOperation *op) +{ + g_return_val_if_fail (G_IS_MOUNT_OPERATION (op), NULL); + return op->priv->password; +} + +/** + * g_mount_operation_set_password: + * @op: the given #GMountOperation. + * @password: password to set. + * + * Sets the mount operation's password to @password. + * + **/ +void +g_mount_operation_set_password (GMountOperation *op, + const char *password) +{ + g_return_if_fail (G_IS_MOUNT_OPERATION (op)); + g_free (op->priv->password); + op->priv->password = g_strdup (password); +} + +/** + * g_mount_operation_get_anonymous: + * @op: + * + * Returns: %TRUE if mount operation is anonymous. + **/ +gboolean +g_mount_operation_get_anonymous (GMountOperation *op) +{ + g_return_val_if_fail (G_IS_MOUNT_OPERATION (op), FALSE); + return op->priv->anonymous; +} + +/** + * g_mount_operation_set_anonymous: + * @op: the given #GMountOperation. + * @anonymous: boolean value. + * + **/ +void +g_mount_operation_set_anonymous (GMountOperation *op, + gboolean anonymous) +{ + g_return_if_fail (G_IS_MOUNT_OPERATION (op)); + op->priv->anonymous = anonymous; +} + +/** + * g_mount_operation_get_domain: + * @op: + * + * Returns: a const string set to the domain. + **/ +const char * +g_mount_operation_get_domain (GMountOperation *op) +{ + g_return_val_if_fail (G_IS_MOUNT_OPERATION (op), NULL); + return op->priv->domain; +} + +/** + * g_mount_operation_set_domain: + * @op: the given #GMountOperation. + * @domain: the domain to set. + * + * Sets the mount operation's domain. + **/ +void +g_mount_operation_set_domain (GMountOperation *op, + const char *domain) +{ + g_return_if_fail (G_IS_MOUNT_OPERATION (op)); + g_free (op->priv->domain); + op->priv->domain = g_strdup (domain); +} + +/** + * g_mount_operation_get_password_save: + * @op: the given #GMountOperation. + * + * Returns: #GPasswordSave. + **/ + +GPasswordSave +g_mount_operation_get_password_save (GMountOperation *op) +{ + g_return_val_if_fail (G_IS_MOUNT_OPERATION (op), G_PASSWORD_SAVE_NEVER); + return op->priv->password_save; +} + +/** + * g_mount_operation_set_password_save + * @op: + * @save: #GPasswordSave + * + **/ +void +g_mount_operation_set_password_save (GMountOperation *op, + GPasswordSave save) +{ + g_return_if_fail (G_IS_MOUNT_OPERATION (op)); + op->priv->password_save = save; +} + +/** + * g_mount_operation_get_choice: + * @op: + * + * Returns: + **/ +int +g_mount_operation_get_choice (GMountOperation *op) +{ + g_return_val_if_fail (G_IS_MOUNT_OPERATION (op), 0); + return op->priv->choice; +} + +/** + * g_mount_operation_set_choice: + * @op: + * @choice: + * + **/ +void +g_mount_operation_set_choice (GMountOperation *op, + int choice) +{ + g_return_if_fail (G_IS_MOUNT_OPERATION (op)); + op->priv->choice = choice; +} + +/** + * g_mount_operation_reply: + * @op: #GMountOperation. + * @abort: boolean. + * + * Emits the #GMountOperation::Reply signal with the abort flag set to + * @abort. + **/ +void +g_mount_operation_reply (GMountOperation *op, + gboolean abort) +{ + g_return_if_fail (G_IS_MOUNT_OPERATION (op)); + g_signal_emit (op, signals[REPLY], 0, abort); +} diff --git a/gio/gmountoperation.h b/gio/gmountoperation.h new file mode 100644 index 000000000..3b730b5ff --- /dev/null +++ b/gio/gmountoperation.h @@ -0,0 +1,126 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#ifndef __G_MOUNT_OPERATION_H__ +#define __G_MOUNT_OPERATION_H__ + +#include <sys/stat.h> + +#include <glib-object.h> + +G_BEGIN_DECLS + +#define G_TYPE_MOUNT_OPERATION (g_mount_operation_get_type ()) +#define G_MOUNT_OPERATION(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_MOUNT_OPERATION, GMountOperation)) +#define G_MOUNT_OPERATION_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), G_TYPE_MOUNT_OPERATION, GMountOperationClass)) +#define G_IS_MOUNT_OPERATION(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_MOUNT_OPERATION)) +#define G_IS_MOUNT_OPERATION_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_MOUNT_OPERATION)) +#define G_MOUNT_OPERATION_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_TYPE_MOUNT_OPERATION, GMountOperationClass)) + +typedef struct _GMountOperation GMountOperation; +typedef struct _GMountOperationClass GMountOperationClass; +typedef struct _GMountOperationPrivate GMountOperationPrivate; + +struct _GMountOperation +{ + GObject parent_instance; + + GMountOperationPrivate *priv; +}; + +typedef enum { + G_PASSWORD_FLAGS_NEED_PASSWORD = 1<<0, + G_PASSWORD_FLAGS_NEED_USERNAME = 1<<1, + G_PASSWORD_FLAGS_NEED_DOMAIN = 1<<2, + G_PASSWORD_FLAGS_SAVING_SUPPORTED = 1<<4, + G_PASSWORD_FLAGS_ANON_SUPPORTED = 1<<5 +} GPasswordFlags; + +typedef enum { + G_PASSWORD_SAVE_NEVER, + G_PASSWORD_SAVE_FOR_SESSION, + G_PASSWORD_SAVE_PERMANENTLY +} GPasswordSave; + +struct _GMountOperationClass +{ + GObjectClass parent_class; + + /* signals: */ + + gboolean (* ask_password) (GMountOperation *op, + const char *message, + const char *default_user, + const char *default_domain, + GPasswordFlags flags); + + gboolean (* ask_question) (GMountOperation *op, + const char *message, + const char *choices[]); + + void (* reply) (GMountOperation *op, + gboolean abort); + + + /* Padding for future expansion */ + void (*_g_reserved1) (void); + void (*_g_reserved2) (void); + void (*_g_reserved3) (void); + void (*_g_reserved4) (void); + void (*_g_reserved5) (void); + void (*_g_reserved6) (void); + void (*_g_reserved7) (void); + void (*_g_reserved8) (void); + void (*_g_reserved9) (void); + void (*_g_reserved10) (void); + void (*_g_reserved11) (void); + void (*_g_reserved12) (void); +}; + +GType g_mount_operation_get_type (void) G_GNUC_CONST; + +GMountOperation * g_mount_operation_new (void); + +const char * g_mount_operation_get_username (GMountOperation *op); +void g_mount_operation_set_username (GMountOperation *op, + const char *username); +const char * g_mount_operation_get_password (GMountOperation *op); +void g_mount_operation_set_password (GMountOperation *op, + const char *password); +gboolean g_mount_operation_get_anonymous (GMountOperation *op); +void g_mount_operation_set_anonymous (GMountOperation *op, + gboolean anonymous); +const char * g_mount_operation_get_domain (GMountOperation *op); +void g_mount_operation_set_domain (GMountOperation *op, + const char *domain); +GPasswordSave g_mount_operation_get_password_save (GMountOperation *op); +void g_mount_operation_set_password_save (GMountOperation *op, + GPasswordSave save); +int g_mount_operation_get_choice (GMountOperation *op); +void g_mount_operation_set_choice (GMountOperation *op, + int choice); +void g_mount_operation_reply (GMountOperation *op, + gboolean abort); + +G_END_DECLS + +#endif /* __G_MOUNT_OPERATION_H__ */ diff --git a/gio/gnativevolumemonitor.c b/gio/gnativevolumemonitor.c new file mode 100644 index 000000000..cc2d81a07 --- /dev/null +++ b/gio/gnativevolumemonitor.c @@ -0,0 +1,31 @@ +#include <config.h> + +#include <string.h> + +#include <glib.h> +#include "gnativevolumemonitor.h" + + +G_DEFINE_ABSTRACT_TYPE (GNativeVolumeMonitor, g_native_volume_monitor, G_TYPE_VOLUME_MONITOR); + +static void +g_native_volume_monitor_finalize (GObject *object) +{ + if (G_OBJECT_CLASS (g_native_volume_monitor_parent_class)->finalize) + (*G_OBJECT_CLASS (g_native_volume_monitor_parent_class)->finalize) (object); +} + + +static void +g_native_volume_monitor_class_init (GNativeVolumeMonitorClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->finalize = g_native_volume_monitor_finalize; +} + + +static void +g_native_volume_monitor_init (GNativeVolumeMonitor *native_monitor) +{ +} diff --git a/gio/gnativevolumemonitor.h b/gio/gnativevolumemonitor.h new file mode 100644 index 000000000..c3ccca2ee --- /dev/null +++ b/gio/gnativevolumemonitor.h @@ -0,0 +1,35 @@ +#ifndef __G_NATIVE_VOLUME_MONITOR_H__ +#define __G_NATIVE_VOLUME_MONITOR_H__ + +#include <glib-object.h> +#include <gio/gvolumemonitor.h> + +G_BEGIN_DECLS + +#define G_TYPE_NATIVE_VOLUME_MONITOR (g_native_volume_monitor_get_type ()) +#define G_NATIVE_VOLUME_MONITOR(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_NATIVE_VOLUME_MONITOR, GNativeVolumeMonitor)) +#define G_NATIVE_VOLUME_MONITOR_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), G_TYPE_NATIVE_VOLUME_MONITOR, GNativeVolumeMonitorClass)) +#define G_IS_NATIVE_VOLUME_MONITOR(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_NATIVE_VOLUME_MONITOR)) +#define G_IS_NATIVE_VOLUME_MONITOR_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_NATIVE_VOLUME_MONITOR)) + +typedef struct _GNativeVolumeMonitor GNativeVolumeMonitor; +typedef struct _GNativeVolumeMonitorClass GNativeVolumeMonitorClass; + +struct _GNativeVolumeMonitor { + GVolumeMonitor parent; +}; + +struct _GNativeVolumeMonitorClass { + GVolumeMonitorClass parent_class; + + GVolume * (*get_volume_for_mountpoint) (const char *mountpoint); + + int priority; +}; + +GType g_native_volume_monitor_get_type (void) G_GNUC_CONST; + + +G_END_DECLS + +#endif /* __G_NATIVE_VOLUME_MONITOR_H__ */ diff --git a/gio/goutputstream.c b/gio/goutputstream.c new file mode 100644 index 000000000..514ea87ba --- /dev/null +++ b/gio/goutputstream.c @@ -0,0 +1,1320 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#include <config.h> + +#include "goutputstream.h" +#include "gsimpleasyncresult.h" +#include "glibintl.h" + +G_DEFINE_TYPE (GOutputStream, g_output_stream, G_TYPE_OBJECT); + +struct _GOutputStreamPrivate { + guint closed : 1; + guint pending : 1; + guint cancelled : 1; + GAsyncReadyCallback outstanding_callback; +}; + +static gssize g_output_stream_real_splice (GOutputStream *stream, + GInputStream *source, + GOutputStreamSpliceFlags flags, + GCancellable *cancellable, + GError **error); +static void g_output_stream_real_write_async (GOutputStream *stream, + const void *buffer, + gsize count, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer data); +static gssize g_output_stream_real_write_finish (GOutputStream *stream, + GAsyncResult *result, + GError **error); +static void g_output_stream_real_splice_async (GOutputStream *stream, + GInputStream *source, + GOutputStreamSpliceFlags flags, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer data); +static gssize g_output_stream_real_splice_finish (GOutputStream *stream, + GAsyncResult *result, + GError **error); +static void g_output_stream_real_flush_async (GOutputStream *stream, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer data); +static gboolean g_output_stream_real_flush_finish (GOutputStream *stream, + GAsyncResult *result, + GError **error); +static void g_output_stream_real_close_async (GOutputStream *stream, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer data); +static gboolean g_output_stream_real_close_finish (GOutputStream *stream, + GAsyncResult *result, + GError **error); + +static void +g_output_stream_finalize (GObject *object) +{ + GOutputStream *stream; + + stream = G_OUTPUT_STREAM (object); + + if (G_OBJECT_CLASS (g_output_stream_parent_class)->finalize) + (*G_OBJECT_CLASS (g_output_stream_parent_class)->finalize) (object); +} + +static void +g_output_stream_dispose (GObject *object) +{ + GOutputStream *stream; + + stream = G_OUTPUT_STREAM (object); + + if (!stream->priv->closed) + g_output_stream_close (stream, NULL, NULL); + + if (G_OBJECT_CLASS (g_output_stream_parent_class)->dispose) + (*G_OBJECT_CLASS (g_output_stream_parent_class)->dispose) (object); +} + +static void +g_output_stream_class_init (GOutputStreamClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + g_type_class_add_private (klass, sizeof (GOutputStreamPrivate)); + + gobject_class->finalize = g_output_stream_finalize; + gobject_class->dispose = g_output_stream_dispose; + + klass->splice = g_output_stream_real_splice; + + klass->write_async = g_output_stream_real_write_async; + klass->write_finish = g_output_stream_real_write_finish; + klass->splice_async = g_output_stream_real_splice_async; + klass->splice_finish = g_output_stream_real_splice_finish; + klass->flush_async = g_output_stream_real_flush_async; + klass->flush_finish = g_output_stream_real_flush_finish; + klass->close_async = g_output_stream_real_close_async; + klass->close_finish = g_output_stream_real_close_finish; +} + +static void +g_output_stream_init (GOutputStream *stream) +{ + stream->priv = G_TYPE_INSTANCE_GET_PRIVATE (stream, + G_TYPE_OUTPUT_STREAM, + GOutputStreamPrivate); +} + +/** + * g_output_stream_write: + * @stream: a #GOutputStream. + * @buffer: the buffer containing the data to write. + * @count: the number of bytes to write + * @cancellable: optional cancellable object + * @error: location to store the error occuring, or %NULL to ignore + * + * Tries to write @count bytes from @buffer into the stream. Will block + * during the operation. + * + * If count is zero returns zero and does nothing. A value of @count + * larger than %G_MAXSSIZE will cause a %G_IO_ERROR_INVALID_ARGUMENT error. + * + * On success, the number of bytes written to the stream is returned. + * It is not an error if this is not the same as the requested size, as it + * can happen e.g. on a partial i/o error, or if the there is not enough + * storage in the stream. All writes either block until at least one byte + * is written, so zero is never returned (unless @count is zero). + * + * If @cancellable is not NULL, then the operation can be cancelled by + * triggering the cancellable object from another thread. If the operation + * was cancelled, the error G_IO_ERROR_CANCELLED will be returned. If an + * operation was partially finished when the operation was cancelled the + * partial result will be returned, without an error. + * + * On error -1 is returned and @error is set accordingly. + * + * Return value: Number of bytes written, or -1 on error + **/ +gssize +g_output_stream_write (GOutputStream *stream, + const void *buffer, + gsize count, + GCancellable *cancellable, + GError **error) +{ + GOutputStreamClass *class; + gssize res; + + g_return_val_if_fail (G_IS_OUTPUT_STREAM (stream), -1); + g_return_val_if_fail (buffer != NULL, 0); + + if (count == 0) + return 0; + + if (((gssize) count) < 0) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, + _("Too large count value passed to g_output_stream_write")); + return -1; + } + + if (stream->priv->closed) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_CLOSED, + _("Stream is already closed")); + return -1; + } + + if (stream->priv->pending) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_PENDING, + _("Stream has outstanding operation")); + return -1; + } + + class = G_OUTPUT_STREAM_GET_CLASS (stream); + + if (class->write == NULL) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, + _("Output stream doesn't implement write")); + return -1; + } + + if (cancellable) + g_push_current_cancellable (cancellable); + + stream->priv->pending = TRUE; + res = class->write (stream, buffer, count, cancellable, error); + stream->priv->pending = FALSE; + + if (cancellable) + g_pop_current_cancellable (cancellable); + + return res; +} + +/** + * g_output_stream_write_all: + * @stream: a #GOutputStream. + * @buffer: the buffer containing the data to write. + * @count: the number of bytes to write + * @bytes_written: location to store the number of bytes that was written to the stream + * @cancellable: optional #GCancellable object, %NULL to ignore. + * @error: location to store the error occuring, or %NULL to ignore + * + * Tries to write @count bytes from @buffer into the stream. Will block + * during the operation. + * + * This function is similar to g_output_stream_write(), except it tries to + * write as many bytes as requested, only stopping on an error. + * + * On a successful write of @count bytes, %TRUE is returned, and @bytes_written + * is set to @count. + * + * If there is an error during the operation FALSE is returned and @error + * is set to indicate the error status, @bytes_written is updated to contain + * the number of bytes written into the stream before the error occured. + * + * Return value: %TRUE on success, %FALSE if there was an error + **/ +gboolean +g_output_stream_write_all (GOutputStream *stream, + const void *buffer, + gsize count, + gsize *bytes_written, + GCancellable *cancellable, + GError **error) +{ + gsize _bytes_written; + gssize res; + + g_return_val_if_fail (G_IS_OUTPUT_STREAM (stream), FALSE); + g_return_val_if_fail (buffer != NULL, FALSE); + + _bytes_written = 0; + while (_bytes_written < count) + { + res = g_output_stream_write (stream, (char *)buffer + _bytes_written, count - _bytes_written, + cancellable, error); + if (res == -1) + { + if (bytes_written) + *bytes_written = _bytes_written; + return FALSE; + } + + if (res == 0) + g_warning ("Write returned zero without error"); + + _bytes_written += res; + } + + if (bytes_written) + *bytes_written = _bytes_written; + return TRUE; +} + +/** + * g_output_stream_flush: + * @stream: a #GOutputStream. + * @cancellable: optional cancellable object + * @error: location to store the error occuring, or %NULL to ignore + * + * Flushed any outstanding buffers in the stream. Will block during the operation. + * Closing the stream will implicitly cause a flush. + * + * This function is optional for inherited classes. + * + * If @cancellable is not NULL, then the operation can be cancelled by + * triggering the cancellable object from another thread. If the operation + * was cancelled, the error G_IO_ERROR_CANCELLED will be returned. + * + * Return value: TRUE on success, FALSE on error + **/ +gboolean +g_output_stream_flush (GOutputStream *stream, + GCancellable *cancellable, + GError **error) +{ + GOutputStreamClass *class; + gboolean res; + + g_return_val_if_fail (G_IS_OUTPUT_STREAM (stream), FALSE); + + if (stream->priv->closed) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_CLOSED, + _("Stream is already closed")); + return FALSE; + } + + if (stream->priv->pending) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_PENDING, + _("Stream has outstanding operation")); + return FALSE; + } + + class = G_OUTPUT_STREAM_GET_CLASS (stream); + + res = TRUE; + if (class->flush) + { + if (cancellable) + g_push_current_cancellable (cancellable); + + stream->priv->pending = TRUE; + res = class->flush (stream, cancellable, error); + stream->priv->pending = FALSE; + + if (cancellable) + g_pop_current_cancellable (cancellable); + } + + return res; +} + +/** + * g_output_stream_splice: + * @stream: + * @source: + * @flags: + * @cancellable: optional #GCancellable object, %NULL to ignore. + * @error: a #GError location to store the error occuring, or %NULL to + * ignore. + * + * + * Returns: + **/ +gssize +g_output_stream_splice (GOutputStream *stream, + GInputStream *source, + GOutputStreamSpliceFlags flags, + GCancellable *cancellable, + GError **error) +{ + GOutputStreamClass *class; + gboolean res; + + g_return_val_if_fail (G_IS_OUTPUT_STREAM (stream), -1); + g_return_val_if_fail (G_IS_INPUT_STREAM (source), -1); + + if (stream->priv->closed) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_CLOSED, + _("Target stream is already closed")); + return -1; + } + + if (g_input_stream_is_closed (source)) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_CLOSED, + _("Source stream is already closed")); + return -1; + } + + if (stream->priv->pending) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_PENDING, + _("Stream has outstanding operation")); + return -1; + } + + class = G_OUTPUT_STREAM_GET_CLASS (stream); + + res = TRUE; + if (cancellable) + g_push_current_cancellable (cancellable); + + stream->priv->pending = TRUE; + res = class->splice (stream, source, flags, cancellable, error); + stream->priv->pending = FALSE; + + if (cancellable) + g_pop_current_cancellable (cancellable); + + return res; +} + +static gssize +g_output_stream_real_splice (GOutputStream *stream, + GInputStream *source, + GOutputStreamSpliceFlags flags, + GCancellable *cancellable, + GError **error) +{ + gssize n_read, n_written; + gssize bytes_copied; + char buffer[8192], *p; + gboolean res; + + bytes_copied = 0; + res = TRUE; + do + { + n_read = g_input_stream_read (source, buffer, sizeof (buffer), cancellable, error); + if (n_read == -1) + { + res = FALSE; + break; + } + + if (n_read == 0) + break; + + p = buffer; + while (n_read > 0) + { + stream->priv->pending = FALSE; + n_written = g_output_stream_write (stream, p, n_read, cancellable, error); + stream->priv->pending = TRUE; + if (n_written == -1) + { + res = FALSE; + break; + } + + p += n_written; + n_read -= n_written; + bytes_copied += n_written; + } + } + while (res); + + if (!res) + error = NULL; /* Ignore further errors */ + + if (flags & G_OUTPUT_STREAM_SPLICE_FLAGS_CLOSE_SOURCE) + { + /* Don't care about errors in source here */ + g_input_stream_close (source, cancellable, NULL); + } + + if (flags & G_OUTPUT_STREAM_SPLICE_FLAGS_CLOSE_TARGET) + { + /* But write errors on close are bad! */ + stream->priv->pending = FALSE; + if (!g_output_stream_close (stream, cancellable, error)) + res = FALSE; + stream->priv->pending = TRUE; + } + + if (res) + return bytes_copied; + + return -1; +} + + +/** + * g_output_stream_close: + * @stream: A #GOutputStream. + * @cancellable: optional cancellable object + * @error: location to store the error occuring, or %NULL to ignore + * + * Closes the stream, releasing resources related to it. + * + * Once the stream is closed, all other operations will return %G_IO_ERROR_CLOSED. + * Closing a stream multiple times will not return an error. + * + * Closing a stream will automatically flush any outstanding buffers in the + * stream. + * + * Streams will be automatically closed when the last reference + * is dropped, but you might want to call make sure resources + * are released as early as possible. + * + * Some streams might keep the backing store of the stream (e.g. a file descriptor) + * open after the stream is closed. See the documentation for the individual + * stream for details. + * + * On failure the first error that happened will be reported, but the close + * operation will finish as much as possible. A stream that failed to + * close will still return %G_IO_ERROR_CLOSED all operations. Still, it + * is important to check and report the error to the user, otherwise + * there might be a loss of data as all data might not be written. + * + * If @cancellable is not NULL, then the operation can be cancelled by + * triggering the cancellable object from another thread. If the operation + * was cancelled, the error %G_IO_ERROR_CANCELLED will be returned. + * Cancelling a close will still leave the stream closed, but there some streams + * can use a faster close that doesn't block to e.g. check errors. On + * cancellation (as with any error) there is no guarantee that all written + * data will reach the target. + * + * Return value: %TRUE on success, %FALSE on failure + **/ +gboolean +g_output_stream_close (GOutputStream *stream, + GCancellable *cancellable, + GError **error) +{ + GOutputStreamClass *class; + gboolean res; + + g_return_val_if_fail (G_IS_OUTPUT_STREAM (stream), FALSE); + + class = G_OUTPUT_STREAM_GET_CLASS (stream); + + if (stream->priv->closed) + return TRUE; + + if (stream->priv->pending) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_PENDING, + _("Stream has outstanding operation")); + return FALSE; + } + + res = g_output_stream_flush (stream, cancellable, error); + + stream->priv->pending = TRUE; + + if (cancellable) + g_push_current_cancellable (cancellable); + + if (!res) + { + /* flushing caused the error that we want to return, + * but we still want to close the underlying stream if possible + */ + if (class->close) + class->close (stream, cancellable, NULL); + } + else + { + res = TRUE; + if (class->close) + res = class->close (stream, cancellable, error); + } + + if (cancellable) + g_pop_current_cancellable (cancellable); + + stream->priv->closed = TRUE; + stream->priv->pending = FALSE; + + return res; +} + +static void +async_ready_callback_wrapper (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + GOutputStream *stream = G_OUTPUT_STREAM (source_object); + + stream->priv->pending = FALSE; + if (stream->priv->outstanding_callback) + (*stream->priv->outstanding_callback) (source_object, res, user_data); + g_object_unref (stream); +} + +static void +async_ready_close_callback_wrapper (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + GOutputStream *stream = G_OUTPUT_STREAM (source_object); + + stream->priv->pending = FALSE; + stream->priv->closed = TRUE; + if (stream->priv->outstanding_callback) + (*stream->priv->outstanding_callback) (source_object, res, user_data); + g_object_unref (stream); +} + +/** + * g_output_stream_write_async: + * @stream: A #GOutputStream. + * @buffer: the buffer containing the data to write. + * @count: the number of bytes to write + * @io_priority: the io priority of the request. the io priority of the request + * @cancellable: optional #GCancellable object, %NULL to ignore. + * @callback: callback to call when the request is satisfied + * @user_data: the data to pass to callback function + * + * Request an asynchronous write of @count bytes from @buffer into the stream. + * When the operation is finished @callback will be called, giving the results. + * + * During an async request no other sync and async calls are allowed, and will + * result in %G_IO_ERROR_PENDING errors. + * + * A value of @count larger than %G_MAXSSIZE will cause a %G_IO_ERROR_INVALID_ARGUMENT error. + * + * On success, the number of bytes written will be passed to the + * @callback. It is not an error if this is not the same as the requested size, as it + * can happen e.g. on a partial i/o error, but generally we try to write + * as many bytes as requested. + * + * Any outstanding i/o request with higher priority (lower numerical value) will + * be executed before an outstanding request with lower priority. Default + * priority is %G_PRIORITY_DEFAULT. + * + * The asyncronous methods have a default fallback that uses threads to implement + * asynchronicity, so they are optional for inheriting classes. However, if you + * override one you must override all. + * + * For the synchronous, blocking version of this function, see g_output_stream_write(). + **/ +void +g_output_stream_write_async (GOutputStream *stream, + const void *buffer, + gsize count, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GOutputStreamClass *class; + GSimpleAsyncResult *simple; + + g_return_if_fail (G_IS_OUTPUT_STREAM (stream)); + g_return_if_fail (buffer != NULL); + + if (count == 0) + { + simple = g_simple_async_result_new (G_OBJECT (stream), + callback, + user_data, + g_output_stream_write_async); + g_simple_async_result_complete_in_idle (simple); + g_object_unref (simple); + return; + } + + if (((gssize) count) < 0) + { + g_simple_async_report_error_in_idle (G_OBJECT (stream), + callback, + user_data, + G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, + _("Too large count value passed to g_output_stream_write_async")); + return; + } + + if (stream->priv->closed) + { + g_simple_async_report_error_in_idle (G_OBJECT (stream), + callback, + user_data, + G_IO_ERROR, G_IO_ERROR_CLOSED, + _("Stream is already closed")); + return; + } + + if (stream->priv->pending) + { + g_simple_async_report_error_in_idle (G_OBJECT (stream), + callback, + user_data, + G_IO_ERROR, G_IO_ERROR_PENDING, + _("Stream has outstanding operation")); + return; + } + + class = G_OUTPUT_STREAM_GET_CLASS (stream); + + stream->priv->pending = TRUE; + stream->priv->outstanding_callback = callback; + g_object_ref (stream); + class->write_async (stream, buffer, count, io_priority, cancellable, + async_ready_callback_wrapper, user_data); +} + +/** + * g_output_stream_write_finish: + * @stream: a #GOutputStream. + * @result: a #GAsyncResult. + * @error: a #GError location to store the error occuring, or %NULL to + * ignore. + * + * + * Returns: + **/ +gssize +g_output_stream_write_finish (GOutputStream *stream, + GAsyncResult *result, + GError **error) +{ + GSimpleAsyncResult *simple; + GOutputStreamClass *class; + + g_return_val_if_fail (G_IS_OUTPUT_STREAM (stream), -1); + g_return_val_if_fail (G_IS_ASYNC_RESULT (result), -1); + + if (G_IS_SIMPLE_ASYNC_RESULT (result)) + { + simple = G_SIMPLE_ASYNC_RESULT (result); + if (g_simple_async_result_propagate_error (simple, error)) + return -1; + + /* Special case writes of 0 bytes */ + if (g_simple_async_result_get_source_tag (simple) == g_output_stream_write_async) + return 0; + } + + class = G_OUTPUT_STREAM_GET_CLASS (stream); + return class->write_finish (stream, result, error); +} + +typedef struct { + GInputStream *source; + gpointer user_data; + GAsyncReadyCallback callback; +} SpliceUserData; + +static void +async_ready_splice_callback_wrapper (GObject *source_object, + GAsyncResult *res, + gpointer _data) +{ + GOutputStream *stream = G_OUTPUT_STREAM (source_object); + SpliceUserData *data = _data; + + stream->priv->pending = FALSE; + + if (data->callback) + (*data->callback) (source_object, res, data->user_data); + + g_object_unref (stream); + g_object_unref (data->source); + g_free (data); +} + +/** + * g_output_stream_splice_async: + * @stream: + * @source: + * @flags: + * @io_priority: the io priority of the request. + * @cancellable: optional #GCancellable object, %NULL to ignore. + * @callback: + * @user_data: + * + **/ +void +g_output_stream_splice_async (GOutputStream *stream, + GInputStream *source, + GOutputStreamSpliceFlags flags, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GOutputStreamClass *class; + SpliceUserData *data; + + g_return_if_fail (G_IS_OUTPUT_STREAM (stream)); + g_return_if_fail (G_IS_INPUT_STREAM (source)); + + if (stream->priv->closed) + { + g_simple_async_report_error_in_idle (G_OBJECT (stream), + callback, + user_data, + G_IO_ERROR, G_IO_ERROR_CLOSED, + _("Target stream is already closed")); + return; + } + + if (g_input_stream_is_closed (source)) + { + g_simple_async_report_error_in_idle (G_OBJECT (stream), + callback, + user_data, + G_IO_ERROR, G_IO_ERROR_CLOSED, + _("Source stream is already closed")); + return; + } + + if (stream->priv->pending) + { + g_simple_async_report_error_in_idle (G_OBJECT (stream), + callback, + user_data, + G_IO_ERROR, G_IO_ERROR_PENDING, + _("Stream has outstanding operation")); + return; + } + + class = G_OUTPUT_STREAM_GET_CLASS (stream); + + stream->priv->pending = TRUE; + + data = g_new0 (SpliceUserData, 1); + data->callback = callback; + data->user_data = user_data; + data->source = g_object_ref (source); + + g_object_ref (stream); + class->splice_async (stream, source, flags, io_priority, cancellable, + async_ready_splice_callback_wrapper, data); +} + +/** + * g_output_stream_splice_finish: + * @stream: a #GOutputStream. + * @result: a #GAsyncResult. + * @error: a #GError location to store the error occuring, or %NULL to + * ignore. + * + * Returns: + **/ +gssize +g_output_stream_splice_finish (GOutputStream *stream, + GAsyncResult *result, + GError **error) +{ + GSimpleAsyncResult *simple; + GOutputStreamClass *class; + + g_return_val_if_fail (G_IS_OUTPUT_STREAM (stream), -1); + g_return_val_if_fail (G_IS_ASYNC_RESULT (result), -1); + + if (G_IS_SIMPLE_ASYNC_RESULT (result)) + { + simple = G_SIMPLE_ASYNC_RESULT (result); + if (g_simple_async_result_propagate_error (simple, error)) + return -1; + } + + class = G_OUTPUT_STREAM_GET_CLASS (stream); + return class->splice_finish (stream, result, error); +} + +/** + * g_output_stream_flush_async: + * @stream: + * @io_priority: the io priority of the request. + * @cancellable: optional #GCancellable object, %NULL to ignore. + * @callback: a #GAsyncReadyCallback. + * @user_data: + * + **/ +void +g_output_stream_flush_async (GOutputStream *stream, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GOutputStreamClass *class; + GSimpleAsyncResult *simple; + + g_return_if_fail (G_IS_OUTPUT_STREAM (stream)); + + if (stream->priv->closed) + { + g_simple_async_report_error_in_idle (G_OBJECT (stream), + callback, + user_data, + G_IO_ERROR, G_IO_ERROR_CLOSED, + _("Stream is already closed")); + return; + } + + if (stream->priv->pending) + { + g_simple_async_report_error_in_idle (G_OBJECT (stream), + callback, + user_data, + G_IO_ERROR, G_IO_ERROR_PENDING, + _("Stream has outstanding operation")); + return; + } + + class = G_OUTPUT_STREAM_GET_CLASS (stream); + + if (class->flush_async == NULL) + { + simple = g_simple_async_result_new (G_OBJECT (stream), + callback, + user_data, + g_output_stream_flush_async); + g_simple_async_result_complete_in_idle (simple); + g_object_unref (simple); + return; + } + + stream->priv->pending = TRUE; + stream->priv->outstanding_callback = callback; + g_object_ref (stream); + class->flush_async (stream, io_priority, cancellable, + async_ready_callback_wrapper, user_data); +} + +/** + * g_output_stream_flush_finish: + * @stream: a #GOutputStream. + * @result: a GAsyncResult. + * @error: a #GError location to store the error occuring, or %NULL to + * ignore. + * Returns: %TRUE if flush operation suceeded, %FALSE otherwise. + **/ +gboolean +g_output_stream_flush_finish (GOutputStream *stream, + GAsyncResult *result, + GError **error) +{ + GSimpleAsyncResult *simple; + GOutputStreamClass *klass; + + g_return_val_if_fail (G_IS_OUTPUT_STREAM (stream), FALSE); + g_return_val_if_fail (G_IS_ASYNC_RESULT (result), FALSE); + + if (G_IS_SIMPLE_ASYNC_RESULT (result)) + { + simple = G_SIMPLE_ASYNC_RESULT (result); + if (g_simple_async_result_propagate_error (simple, error)) + return FALSE; + + /* Special case default implementation */ + if (g_simple_async_result_get_source_tag (simple) == g_output_stream_flush_async) + return TRUE; + } + + klass = G_OUTPUT_STREAM_GET_CLASS (stream); + return klass->flush_finish (stream, result, error); +} + + +/** + * g_output_stream_close_async: + * @stream: A #GOutputStream. + * @callback: callback to call when the request is satisfied + * @user_data: the data to pass to callback function + * @cancellable: optional cancellable object + * + * Requests an asynchronous closes of the stream, releasing resources related to it. + * When the operation is finished @callback will be called, giving the results. + * + * For behaviour details see g_output_stream_close(). + * + * The asyncronous methods have a default fallback that uses threads to implement + * asynchronicity, so they are optional for inheriting classes. However, if you + * override one you must override all. + **/ +void +g_output_stream_close_async (GOutputStream *stream, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GOutputStreamClass *class; + GSimpleAsyncResult *simple; + + g_return_if_fail (G_IS_OUTPUT_STREAM (stream)); + + if (stream->priv->closed) + { + simple = g_simple_async_result_new (G_OBJECT (stream), + callback, + user_data, + g_output_stream_close_async); + g_simple_async_result_complete_in_idle (simple); + g_object_unref (simple); + return; + } + + if (stream->priv->pending) + { + g_simple_async_report_error_in_idle (G_OBJECT (stream), + callback, + user_data, + G_IO_ERROR, G_IO_ERROR_PENDING, + _("Stream has outstanding operation")); + return; + } + + class = G_OUTPUT_STREAM_GET_CLASS (stream); + stream->priv->pending = TRUE; + stream->priv->outstanding_callback = callback; + g_object_ref (stream); + class->close_async (stream, io_priority, cancellable, + async_ready_close_callback_wrapper, user_data); +} + +/** + * g_output_stream_close_finish: + * @stream: + * @result: + * @error: a #GError location to store the error occuring, or %NULL to + * ignore. + * Returns: %TRUE, %FALSE otherwise. + **/ +gboolean +g_output_stream_close_finish (GOutputStream *stream, + GAsyncResult *result, + GError **error) +{ + GSimpleAsyncResult *simple; + GOutputStreamClass *class; + + g_return_val_if_fail (G_IS_OUTPUT_STREAM (stream), FALSE); + g_return_val_if_fail (G_IS_ASYNC_RESULT (result), FALSE); + + if (G_IS_SIMPLE_ASYNC_RESULT (result)) + { + simple = G_SIMPLE_ASYNC_RESULT (result); + if (g_simple_async_result_propagate_error (simple, error)) + return FALSE; + + /* Special case already closed */ + if (g_simple_async_result_get_source_tag (simple) == g_output_stream_close_async) + return TRUE; + } + + class = G_OUTPUT_STREAM_GET_CLASS (stream); + return class->close_finish (stream, result, error); +} + +/** + * g_output_stream_is_closed: + * @stream: + * + * Returns: %TRUE if @stream is closed. %FALSE otherwise. + **/ +gboolean +g_output_stream_is_closed (GOutputStream *stream) +{ + g_return_val_if_fail (G_IS_OUTPUT_STREAM (stream), TRUE); + + return stream->priv->closed; +} + +/** + * g_output_stream_has_pending: + * @stream: + * + * Returns: %TRUE if @stream has pending actions. + **/ +gboolean +g_output_stream_has_pending (GOutputStream *stream) +{ + g_return_val_if_fail (G_IS_OUTPUT_STREAM (stream), FALSE); + + return stream->priv->pending; +} + +/** + * g_output_stream_set_pending: + * @stream: + * @pending: + * + * Sets the @stream as having pending actions. + * + **/ +void +g_output_stream_set_pending (GOutputStream *stream, + gboolean pending) +{ + g_return_if_fail (G_IS_OUTPUT_STREAM (stream)); + + stream->priv->pending = pending; +} + + +/******************************************** + * Default implementation of async ops * + ********************************************/ + +typedef struct { + const void *buffer; + gsize count_requested; + gssize count_written; +} WriteData; + +static void +write_async_thread (GSimpleAsyncResult *res, + GObject *object, + GCancellable *cancellable) +{ + WriteData *op; + GOutputStreamClass *class; + GError *error = NULL; + + class = G_OUTPUT_STREAM_GET_CLASS (object); + op = g_simple_async_result_get_op_res_gpointer (res); + op->count_written = class->write (G_OUTPUT_STREAM (object), op->buffer, op->count_requested, + cancellable, &error); + if (op->count_written == -1) + { + g_simple_async_result_set_from_error (res, error); + g_error_free (error); + } +} + +static void +g_output_stream_real_write_async (GOutputStream *stream, + const void *buffer, + gsize count, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GSimpleAsyncResult *res; + WriteData *op; + + op = g_new0 (WriteData, 1); + res = g_simple_async_result_new (G_OBJECT (stream), callback, user_data, g_output_stream_real_write_async); + g_simple_async_result_set_op_res_gpointer (res, op, g_free); + op->buffer = buffer; + op->count_requested = count; + + g_simple_async_result_run_in_thread (res, write_async_thread, io_priority, cancellable); + g_object_unref (res); +} + +static gssize +g_output_stream_real_write_finish (GOutputStream *stream, + GAsyncResult *result, + GError **error) +{ + GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (result); + WriteData *op; + + g_assert (g_simple_async_result_get_source_tag (simple) == g_output_stream_real_write_async); + op = g_simple_async_result_get_op_res_gpointer (simple); + return op->count_written; +} + +typedef struct { + GInputStream *source; + GOutputStreamSpliceFlags flags; + gssize bytes_copied; +} SpliceData; + +static void +splice_async_thread (GSimpleAsyncResult *result, + GObject *object, + GCancellable *cancellable) +{ + SpliceData *op; + GOutputStreamClass *class; + GError *error = NULL; + GOutputStream *stream; + + stream = G_OUTPUT_STREAM (object); + class = G_OUTPUT_STREAM_GET_CLASS (object); + op = g_simple_async_result_get_op_res_gpointer (result); + + stream->priv->pending = FALSE; + op->bytes_copied = + g_output_stream_splice (stream, + op->source, + op->flags, + cancellable, + &error); + stream->priv->pending = TRUE; + + if (op->bytes_copied == -1) + { + g_simple_async_result_set_from_error (result, error); + g_error_free (error); + } +} + +static void +g_output_stream_real_splice_async (GOutputStream *stream, + GInputStream *source, + GOutputStreamSpliceFlags flags, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GSimpleAsyncResult *res; + SpliceData *op; + + op = g_new0 (SpliceData, 1); + res = g_simple_async_result_new (G_OBJECT (stream), callback, user_data, g_output_stream_real_splice_async); + g_simple_async_result_set_op_res_gpointer (res, op, g_free); + op->flags = flags; + op->source = source; + + /* TODO: In the case where both source and destintion have + non-threadbased async calls we can use a true async copy here */ + + g_simple_async_result_run_in_thread (res, splice_async_thread, io_priority, cancellable); + g_object_unref (res); +} + +static gssize +g_output_stream_real_splice_finish (GOutputStream *stream, + GAsyncResult *result, + GError **error) +{ + GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (result); + SpliceData *op; + + g_assert (g_simple_async_result_get_source_tag (simple) == g_output_stream_real_splice_async); + op = g_simple_async_result_get_op_res_gpointer (simple); + return op->bytes_copied; +} + + + +static void +flush_async_thread (GSimpleAsyncResult *res, + GObject *object, + GCancellable *cancellable) +{ + GOutputStreamClass *class; + gboolean result; + GError *error = NULL; + + class = G_OUTPUT_STREAM_GET_CLASS (object); + result = TRUE; + if (class->flush) + result = class->flush (G_OUTPUT_STREAM (object), cancellable, &error); + + if (!result) + { + g_simple_async_result_set_from_error (res, error); + g_error_free (error); + } +} + +static void +g_output_stream_real_flush_async (GOutputStream *stream, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GSimpleAsyncResult *res; + + res = g_simple_async_result_new (G_OBJECT (stream), callback, user_data, g_output_stream_real_write_async); + + g_simple_async_result_run_in_thread (res, flush_async_thread, io_priority, cancellable); + g_object_unref (res); +} + +static gboolean +g_output_stream_real_flush_finish (GOutputStream *stream, + GAsyncResult *result, + GError **error) +{ + return TRUE; +} + +static void +close_async_thread (GSimpleAsyncResult *res, + GObject *object, + GCancellable *cancellable) +{ + GOutputStreamClass *class; + GError *error = NULL; + gboolean result; + + /* Auto handling of cancelation disabled, and ignore + cancellation, since we want to close things anyway, although + possibly in a quick-n-dirty way. At least we never want to leak + open handles */ + + class = G_OUTPUT_STREAM_GET_CLASS (object); + result = class->close (G_OUTPUT_STREAM (object), cancellable, &error); + if (!result) + { + g_simple_async_result_set_from_error (res, error); + g_error_free (error); + } +} + +static void +g_output_stream_real_close_async (GOutputStream *stream, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GSimpleAsyncResult *res; + + res = g_simple_async_result_new (G_OBJECT (stream), callback, user_data, g_output_stream_real_close_async); + + g_simple_async_result_set_handle_cancellation (res, FALSE); + + g_simple_async_result_run_in_thread (res, close_async_thread, io_priority, cancellable); + g_object_unref (res); +} + +static gboolean +g_output_stream_real_close_finish (GOutputStream *stream, + GAsyncResult *result, + GError **error) +{ + GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (result); + g_assert (g_simple_async_result_get_source_tag (simple) == g_output_stream_real_close_async); + return TRUE; +} diff --git a/gio/goutputstream.h b/gio/goutputstream.h new file mode 100644 index 000000000..4bfb621e2 --- /dev/null +++ b/gio/goutputstream.h @@ -0,0 +1,202 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#ifndef __G_OUTPUT_STREAM_H__ +#define __G_OUTPUT_STREAM_H__ + +#include <glib-object.h> +#include <gio/gioerror.h> +#include <gio/gasyncresult.h> +#include <gio/gcancellable.h> +#include <gio/ginputstream.h> + +G_BEGIN_DECLS + +#define G_TYPE_OUTPUT_STREAM (g_output_stream_get_type ()) +#define G_OUTPUT_STREAM(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_OUTPUT_STREAM, GOutputStream)) +#define G_OUTPUT_STREAM_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), G_TYPE_OUTPUT_STREAM, GOutputStreamClass)) +#define G_IS_OUTPUT_STREAM(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_OUTPUT_STREAM)) +#define G_IS_OUTPUT_STREAM_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_OUTPUT_STREAM)) +#define G_OUTPUT_STREAM_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_TYPE_OUTPUT_STREAM, GOutputStreamClass)) + +typedef enum { + G_OUTPUT_STREAM_SPLICE_FLAGS_NONE = 0, + G_OUTPUT_STREAM_SPLICE_FLAGS_CLOSE_SOURCE = 1 << 0, + G_OUTPUT_STREAM_SPLICE_FLAGS_CLOSE_TARGET = 1 << 1 +} GOutputStreamSpliceFlags; + +typedef struct _GOutputStream GOutputStream; +typedef struct _GOutputStreamClass GOutputStreamClass; +typedef struct _GOutputStreamPrivate GOutputStreamPrivate; + +struct _GOutputStream +{ + GObject parent; + + /*< private >*/ + GOutputStreamPrivate *priv; +}; + + +struct _GOutputStreamClass +{ + GObjectClass parent_class; + + /* Sync ops: */ + + gssize (* write) (GOutputStream *stream, + const void *buffer, + gsize count, + GCancellable *cancellable, + GError **error); + gssize (* splice) (GOutputStream *stream, + GInputStream *source, + GOutputStreamSpliceFlags flags, + GCancellable *cancellable, + GError **error); + gboolean (* flush) (GOutputStream *stream, + GCancellable *cancellable, + GError **error); + gboolean (* close) (GOutputStream *stream, + GCancellable *cancellable, + GError **error); + + /* Async ops: (optional in derived classes) */ + + void (* write_async) (GOutputStream *stream, + const void *buffer, + gsize count, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + gssize (* write_finish) (GOutputStream *stream, + GAsyncResult *result, + GError **error); + void (* splice_async) (GOutputStream *stream, + GInputStream *source, + GOutputStreamSpliceFlags flags, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer data); + gssize (* splice_finish)(GOutputStream *stream, + GAsyncResult *result, + GError **error); + void (* flush_async) (GOutputStream *stream, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + gboolean (* flush_finish) (GOutputStream *stream, + GAsyncResult *result, + GError **error); + void (* close_async) (GOutputStream *stream, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + gboolean (* close_finish) (GOutputStream *stream, + GAsyncResult *result, + GError **error); + + /* Padding for future expansion */ + void (*_g_reserved1) (void); + void (*_g_reserved2) (void); + void (*_g_reserved3) (void); + void (*_g_reserved4) (void); + void (*_g_reserved5) (void); + void (*_g_reserved6) (void); + void (*_g_reserved7) (void); + void (*_g_reserved8) (void); +}; + +GType g_output_stream_get_type (void) G_GNUC_CONST; + +gssize g_output_stream_write (GOutputStream *stream, + const void *buffer, + gsize count, + GCancellable *cancellable, + GError **error); +gboolean g_output_stream_write_all (GOutputStream *stream, + const void *buffer, + gsize count, + gsize *bytes_written, + GCancellable *cancellable, + GError **error); +gssize g_output_stream_splice (GOutputStream *stream, + GInputStream *source, + GOutputStreamSpliceFlags flags, + GCancellable *cancellable, + GError **error); +gboolean g_output_stream_flush (GOutputStream *stream, + GCancellable *cancellable, + GError **error); +gboolean g_output_stream_close (GOutputStream *stream, + GCancellable *cancellable, + GError **error); +void g_output_stream_write_async (GOutputStream *stream, + const void *buffer, + gsize count, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +gssize g_output_stream_write_finish (GOutputStream *stream, + GAsyncResult *result, + GError **error); +void g_output_stream_splice_async (GOutputStream *stream, + GInputStream *source, + GOutputStreamSpliceFlags flags, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +gssize g_output_stream_splice_finish (GOutputStream *stream, + GAsyncResult *result, + GError **error); +void g_output_stream_flush_async (GOutputStream *stream, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean g_output_stream_flush_finish (GOutputStream *stream, + GAsyncResult *result, + GError **error); +void g_output_stream_close_async (GOutputStream *stream, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean g_output_stream_close_finish (GOutputStream *stream, + GAsyncResult *result, + GError **error); + +gboolean g_output_stream_is_closed (GOutputStream *stream); +gboolean g_output_stream_has_pending (GOutputStream *stream); +void g_output_stream_set_pending (GOutputStream *stream, + gboolean pending); + + +G_END_DECLS + +#endif /* __G_OUTPUT_STREAM_H__ */ diff --git a/gio/gpollfilemonitor.c b/gio/gpollfilemonitor.c new file mode 100644 index 000000000..0b5a5e79d --- /dev/null +++ b/gio/gpollfilemonitor.c @@ -0,0 +1,226 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#include <config.h> +#include <string.h> + +#include "gpollfilemonitor.h" +#include "gfilemonitor.h" + +static gboolean g_poll_file_monitor_cancel (GFileMonitor* monitor); +static void schedule_poll_timeout (GPollFileMonitor* poll_monitor); + +struct _GPollFileMonitor +{ + GFileMonitor parent_instance; + GFile *file; + GFileInfo *last_info; + guint timeout; +}; + +#define POLL_TIME_SECS 5 + +G_DEFINE_TYPE (GPollFileMonitor, g_poll_file_monitor, G_TYPE_FILE_MONITOR) + +static void +g_poll_file_monitor_finalize (GObject* object) +{ + GPollFileMonitor* poll_monitor; + + poll_monitor = G_POLL_FILE_MONITOR (object); + + g_object_unref (poll_monitor->file); + + if (G_OBJECT_CLASS (g_poll_file_monitor_parent_class)->finalize) + (*G_OBJECT_CLASS (g_poll_file_monitor_parent_class)->finalize) (object); +} + + +static void +g_poll_file_monitor_class_init (GPollFileMonitorClass* klass) +{ + GObjectClass* gobject_class = G_OBJECT_CLASS (klass); + GFileMonitorClass *file_monitor_class = G_FILE_MONITOR_CLASS (klass); + + gobject_class->finalize = g_poll_file_monitor_finalize; + + file_monitor_class->cancel = g_poll_file_monitor_cancel; +} + +static void +g_poll_file_monitor_init (GPollFileMonitor* poll_monitor) +{ +} + +static int +safe_strcmp (const char *a, const char *b) +{ + if (a == NULL && b == NULL) + return 0; + if (a == NULL) + return -1; + if (b == NULL) + return 1; + + return strcmp (a, b); +} + +static int +calc_event_type (GFileInfo *last, + GFileInfo *new) +{ + if (last == NULL && new == NULL) + return -1; + + if (last == NULL && new != NULL) + return G_FILE_MONITOR_EVENT_CREATED; + + if (last != NULL && new == NULL) + return G_FILE_MONITOR_EVENT_DELETED; + + if (safe_strcmp (g_file_info_get_etag (last), + g_file_info_get_etag (new))) + return G_FILE_MONITOR_EVENT_CHANGED; + + if (g_file_info_get_size (last) != + g_file_info_get_size (new)) + return G_FILE_MONITOR_EVENT_CHANGED; + + return -1; +} + +static void +got_new_info (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + GPollFileMonitor* poll_monitor = user_data; + GFileInfo *info; + int event; + + info = g_file_query_info_finish (poll_monitor->file, res, NULL); + + if (!g_file_monitor_is_cancelled (G_FILE_MONITOR (poll_monitor))) + { + event = calc_event_type (poll_monitor->last_info, info); + + if (event != -1) + { + g_file_monitor_emit_event (G_FILE_MONITOR (poll_monitor), + poll_monitor->file, + NULL, event); + /* We're polling so slowly anyway, so always emit the done hint */ + if (event == G_FILE_MONITOR_EVENT_CHANGED) + g_file_monitor_emit_event (G_FILE_MONITOR (poll_monitor), + poll_monitor->file, + NULL, G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT); + } + + if (poll_monitor->last_info) + { + g_object_unref (poll_monitor->last_info); + poll_monitor->last_info = NULL; + } + + if (info) + poll_monitor->last_info = g_object_ref (info); + + schedule_poll_timeout (poll_monitor); + } + + if (info) + g_object_unref (info); + + g_object_unref (poll_monitor); +} + +static gboolean +poll_file_timeout (gpointer data) +{ + GPollFileMonitor* poll_monitor = data; + + poll_monitor->timeout = FALSE; + + g_file_query_info_async (poll_monitor->file, G_FILE_ATTRIBUTE_ETAG_VALUE "," G_FILE_ATTRIBUTE_STD_SIZE, + 0, 0, NULL, got_new_info, g_object_ref (poll_monitor)); + + return FALSE; +} + +static void +schedule_poll_timeout (GPollFileMonitor* poll_monitor) +{ + poll_monitor->timeout = g_timeout_add_seconds (POLL_TIME_SECS, poll_file_timeout, poll_monitor); + } + +static void +got_initial_info (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + GPollFileMonitor* poll_monitor = user_data; + GFileInfo *info; + + info = g_file_query_info_finish (poll_monitor->file, res, NULL); + + poll_monitor->last_info = info; + + if (!g_file_monitor_is_cancelled (G_FILE_MONITOR (poll_monitor))) + schedule_poll_timeout (poll_monitor); + + g_object_unref (poll_monitor); +} + +/** + * g_poll_file_monitor_new: + * @file: + * + * Returns a new #GFileMonitor for the given #GFile. + **/ +GFileMonitor* +g_poll_file_monitor_new (GFile *file) +{ + GPollFileMonitor* poll_monitor; + + poll_monitor = g_object_new (G_TYPE_POLL_FILE_MONITOR, NULL); + + poll_monitor->file = g_object_ref (file); + + g_file_query_info_async (file, G_FILE_ATTRIBUTE_ETAG_VALUE "," G_FILE_ATTRIBUTE_STD_SIZE, + 0, 0, NULL, got_initial_info, g_object_ref (poll_monitor)); + + return G_FILE_MONITOR (poll_monitor); +} + +static gboolean +g_poll_file_monitor_cancel (GFileMonitor* monitor) +{ + GPollFileMonitor *poll_monitor = G_POLL_FILE_MONITOR (monitor); + + if (poll_monitor->timeout) + { + g_source_remove (poll_monitor->timeout); + poll_monitor->timeout = 0; + } + + return TRUE; +} diff --git a/gio/gpollfilemonitor.h b/gio/gpollfilemonitor.h new file mode 100644 index 000000000..2d4e2a37a --- /dev/null +++ b/gio/gpollfilemonitor.h @@ -0,0 +1,50 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#ifndef __G_POLL_FILE_MONITOR_H__ +#define __G_POLL_FILE_MONITOR_H__ + +#include <glib-object.h> +#include <gio/gfilemonitor.h> + +G_BEGIN_DECLS + +#define G_TYPE_POLL_FILE_MONITOR (g_poll_file_monitor_get_type ()) +#define G_POLL_FILE_MONITOR(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_POLL_FILE_MONITOR, GPollFileMonitor)) +#define G_POLL_FILE_MONITOR_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), G_TYPE_POLL_FILE_MONITOR, GPollFileMonitorClass)) +#define G_IS_POLL_FILE_MONITOR(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_POLL_FILE_MONITOR)) +#define G_IS_POLL_FILE_MONITOR_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_POLL_FILE_MONITOR)) + +typedef struct _GPollFileMonitor GPollFileMonitor; +typedef struct _GPollFileMonitorClass GPollFileMonitorClass; + +struct _GPollFileMonitorClass { + GFileMonitorClass parent_class; +}; + +GType g_poll_file_monitor_get_type (void) G_GNUC_CONST; + +GFileMonitor* g_poll_file_monitor_new (GFile *file); + +G_END_DECLS + +#endif /* __G_POLL_FILE_MONITOR_H__ */ diff --git a/gio/gseekable.c b/gio/gseekable.c new file mode 100644 index 000000000..90f53da0a --- /dev/null +++ b/gio/gseekable.c @@ -0,0 +1,168 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#include <config.h> +#include "gseekable.h" +#include "glibintl.h" + +static void g_seekable_base_init (gpointer g_class); + + +GType +g_seekable_get_type (void) +{ + static GType seekable_type = 0; + + if (! seekable_type) + { + static const GTypeInfo seekable_info = + { + sizeof (GSeekableIface), /* class_size */ + g_seekable_base_init, /* base_init */ + NULL, /* base_finalize */ + NULL, + NULL, /* class_finalize */ + NULL, /* class_data */ + 0, + 0, /* n_preallocs */ + NULL + }; + + seekable_type = + g_type_register_static (G_TYPE_INTERFACE, I_("GSeekable"), + &seekable_info, 0); + + g_type_interface_add_prerequisite (seekable_type, G_TYPE_OBJECT); + } + + return seekable_type; +} + +static void +g_seekable_base_init (gpointer g_class) +{ +} + +/** + * g_seekable_tell: + * @seekable: + * + * Returns: a goffset. + **/ +goffset +g_seekable_tell (GSeekable *seekable) +{ + GSeekableIface *iface; + + g_return_val_if_fail (G_IS_SEEKABLE (seekable), 0); + + iface = G_SEEKABLE_GET_IFACE (seekable); + + return (* iface->tell) (seekable); +} + +/** + * g_seekable_can_seek: + * @seekable: + * + * Returns: %TRUE if @seekable can be seeked. %FALSE otherwise. + **/ +gboolean +g_seekable_can_seek (GSeekable *seekable) +{ + GSeekableIface *iface; + + g_return_val_if_fail (G_IS_SEEKABLE (seekable), FALSE); + + iface = G_SEEKABLE_GET_IFACE (seekable); + + return (* iface->can_seek) (seekable); +} + +/** + * g_seekable_seek: + * @seekable: + * @offset: + * @type: + * @cancellable: optional #GCancellable object, %NULL to ignore. + * @error: a #GError location to store the error occuring, or %NULL to + * ignore. + * Returns: %TRUE, %FALSE otherwise. + **/ +gboolean +g_seekable_seek (GSeekable *seekable, + goffset offset, + GSeekType type, + GCancellable *cancellable, + GError **error) +{ + GSeekableIface *iface; + + g_return_val_if_fail (G_IS_SEEKABLE (seekable), FALSE); + + iface = G_SEEKABLE_GET_IFACE (seekable); + + return (* iface->seek) (seekable, offset, type, cancellable, error); +} + +/** + * g_seekable_can_truncate: + * @seekable: + * + * Returns: + **/ +gboolean +g_seekable_can_truncate (GSeekable *seekable) +{ + GSeekableIface *iface; + + g_return_val_if_fail (G_IS_SEEKABLE (seekable), FALSE); + + iface = G_SEEKABLE_GET_IFACE (seekable); + + return (* iface->can_truncate) (seekable); +} + +/** + * g_seekable_truncate: + * @seekable: + * @offset: + * @cancellable: optional #GCancellable object, %NULL to ignore. + * @error: a #GError location to store the error occuring, or %NULL to + * ignore. + * Returns: + **/ +gboolean +g_seekable_truncate (GSeekable *seekable, + goffset offset, + GCancellable *cancellable, + GError **error) +{ + GSeekableIface *iface; + + g_return_val_if_fail (G_IS_SEEKABLE (seekable), FALSE); + + iface = G_SEEKABLE_GET_IFACE (seekable); + + return (* iface->truncate) (seekable, offset, cancellable, error); +} + diff --git a/gio/gseekable.h b/gio/gseekable.h new file mode 100644 index 000000000..33fc619b8 --- /dev/null +++ b/gio/gseekable.h @@ -0,0 +1,81 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#ifndef __G_SEEKABLE_H__ +#define __G_SEEKABLE_H__ + +#include <glib-object.h> +#include <gio/gcancellable.h> + +G_BEGIN_DECLS + +#define G_TYPE_SEEKABLE (g_seekable_get_type ()) +#define G_SEEKABLE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), G_TYPE_SEEKABLE, GSeekable)) +#define G_IS_SEEKABLE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), G_TYPE_SEEKABLE)) +#define G_SEEKABLE_GET_IFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), G_TYPE_SEEKABLE, GSeekableIface)) + +typedef struct _GSeekable GSeekable; +typedef struct _GSeekableIface GSeekableIface; + +struct _GSeekableIface +{ + GTypeInterface g_iface; + + /* Virtual Table */ + + goffset (* tell) (GSeekable *seekable); + + gboolean (* can_seek) (GSeekable *seekable); + gboolean (* seek) (GSeekable *seekable, + goffset offset, + GSeekType type, + GCancellable *cancellable, + GError **error); + + gboolean (* can_truncate) (GSeekable *seekable); + gboolean (* truncate) (GSeekable *seekable, + goffset offset, + GCancellable *cancellable, + GError **error); + + /* TODO: Async seek/truncate */ +}; + +GType g_seekable_get_type (void) G_GNUC_CONST; + +goffset g_seekable_tell (GSeekable *seekable); +gboolean g_seekable_can_seek (GSeekable *seekable); +gboolean g_seekable_seek (GSeekable *seekable, + goffset offset, + GSeekType type, + GCancellable *cancellable, + GError **error); +gboolean g_seekable_can_truncate (GSeekable *seekable); +gboolean g_seekable_truncate (GSeekable *seekable, + goffset offset, + GCancellable *cancellable, + GError **error); + +G_END_DECLS + + +#endif /* __G_SEEKABLE_H__ */ diff --git a/gio/gsimpleasyncresult.c b/gio/gsimpleasyncresult.c new file mode 100644 index 000000000..3c69f19d9 --- /dev/null +++ b/gio/gsimpleasyncresult.c @@ -0,0 +1,586 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#include <config.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <string.h> +#include <errno.h> +#include <fcntl.h> +#include <unistd.h> + +#include "gsimpleasyncresult.h" +#include "gioscheduler.h" +#include <gio/gioerror.h> +#include "glibintl.h" + +static void g_simple_async_result_async_result_iface_init (GAsyncResultIface *iface); + +struct _GSimpleAsyncResult +{ + GObject parent_instance; + + GObject *source_object; + GAsyncReadyCallback callback; + gpointer user_data; + GError *error; + gboolean failed; + gboolean handle_cancellation; + + gpointer source_tag; + + union { + gpointer v_pointer; + gboolean v_boolean; + gssize v_ssize; + } op_res; + + GDestroyNotify destroy_op_res; +}; + +struct _GSimpleAsyncResultClass +{ + GObjectClass parent_class; +}; + + +G_DEFINE_TYPE_WITH_CODE (GSimpleAsyncResult, g_simple_async_result, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_RESULT, + g_simple_async_result_async_result_iface_init)) + +static void +g_simple_async_result_finalize (GObject *object) +{ + GSimpleAsyncResult *simple; + + simple = G_SIMPLE_ASYNC_RESULT (object); + + if (simple->source_object) + g_object_unref (simple->source_object); + + if (simple->destroy_op_res) + simple->destroy_op_res (simple->op_res.v_pointer); + + if (simple->error) + g_error_free (simple->error); + + if (G_OBJECT_CLASS (g_simple_async_result_parent_class)->finalize) + (*G_OBJECT_CLASS (g_simple_async_result_parent_class)->finalize) (object); +} + +static void +g_simple_async_result_class_init (GSimpleAsyncResultClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->finalize = g_simple_async_result_finalize; +} + +static void +g_simple_async_result_init (GSimpleAsyncResult *simple) +{ + simple->handle_cancellation = TRUE; +} + +/** + * g_simple_async_result_new: + * @source_object: + * @callback: + * @user_data: + * @source_tag: + * + * Returns: #GSimpleAsyncResult + **/ +GSimpleAsyncResult * +g_simple_async_result_new (GObject *source_object, + GAsyncReadyCallback callback, + gpointer user_data, + gpointer source_tag) +{ + GSimpleAsyncResult *simple; + + g_return_val_if_fail (G_IS_OBJECT (source_object), NULL); + + simple = g_object_new (G_TYPE_SIMPLE_ASYNC_RESULT, NULL); + simple->callback = callback; + simple->source_object = g_object_ref (source_object); + simple->user_data = user_data; + simple->source_tag = source_tag; + + return simple; +} + +/** + * g_simple_async_result_new_from_error: + * @source_object: + * @callback: + * @user_data: + * @error: a #GError location to store the error occuring, or %NULL to + * ignore. + * Returns: #GSimpleAsyncResult + **/ +GSimpleAsyncResult * +g_simple_async_result_new_from_error (GObject *source_object, + GAsyncReadyCallback callback, + gpointer user_data, + GError *error) +{ + GSimpleAsyncResult *simple; + + g_return_val_if_fail (G_IS_OBJECT (source_object), NULL); + + simple = g_simple_async_result_new (source_object, + callback, + user_data, NULL); + g_simple_async_result_set_from_error (simple, error); + + return simple; +} + +/** + * g_simple_async_result_new_error: + * @source_object: + * @callback: + * @user_data: + * @domain: + * @code: + * @format: + * @... + * + * Returns: #GSimpleAsyncResult. + **/ +GSimpleAsyncResult * +g_simple_async_result_new_error (GObject *source_object, + GAsyncReadyCallback callback, + gpointer user_data, + GQuark domain, + gint code, + const char *format, + ...) +{ + GSimpleAsyncResult *simple; + va_list args; + + g_return_val_if_fail (G_IS_OBJECT (source_object), NULL); + g_return_val_if_fail (domain != 0, NULL); + g_return_val_if_fail (format != NULL, NULL); + + simple = g_simple_async_result_new (source_object, + callback, + user_data, NULL); + + va_start (args, format); + g_simple_async_result_set_error_va (simple, domain, code, format, args); + va_end (args); + + return simple; +} + + +static gpointer +g_simple_async_result_get_user_data (GAsyncResult *res) +{ + return G_SIMPLE_ASYNC_RESULT (res)->user_data; +} + +static GObject * +g_simple_async_result_get_source_object (GAsyncResult *res) +{ + if (G_SIMPLE_ASYNC_RESULT (res)->source_object) + return g_object_ref (G_SIMPLE_ASYNC_RESULT (res)->source_object); + return NULL; +} + +static void +g_simple_async_result_async_result_iface_init (GAsyncResultIface *iface) +{ + iface->get_user_data = g_simple_async_result_get_user_data; + iface->get_source_object = g_simple_async_result_get_source_object; +} + +/** + * g_simple_async_result_set_handle_cancellation: + * @simple: + * @handle_cancellation: + * + **/ +void +g_simple_async_result_set_handle_cancellation (GSimpleAsyncResult *simple, + gboolean handle_cancellation) +{ + g_return_if_fail (G_IS_SIMPLE_ASYNC_RESULT (simple)); + simple->handle_cancellation = handle_cancellation; +} + +/** + * g_simple_async_result_get_source_tag: + * @simple: + * + * Returns: + **/ +gpointer +g_simple_async_result_get_source_tag (GSimpleAsyncResult *simple) +{ + g_return_val_if_fail (G_IS_SIMPLE_ASYNC_RESULT (simple), NULL); + return simple->source_tag; +} + +/** + * g_simple_async_result_result_propagate_error: + * @simple: + * @dest: + * + * Returns: + **/ +gboolean +g_simple_async_result_propagate_error (GSimpleAsyncResult *simple, + GError **dest) +{ + g_return_val_if_fail (G_IS_SIMPLE_ASYNC_RESULT (simple), FALSE); + + if (simple->failed) + { + g_propagate_error (dest, simple->error); + simple->error = NULL; + return TRUE; + } + return FALSE; +} + +/** + * g_simple_async_result_set_op_res_gpointer: + * @simple: + * @op_res: + * @destroy_op_res: + * + **/ +void +g_simple_async_result_set_op_res_gpointer (GSimpleAsyncResult *simple, + gpointer op_res, + GDestroyNotify destroy_op_res) +{ + g_return_if_fail (G_IS_SIMPLE_ASYNC_RESULT (simple)); + + simple->op_res.v_pointer = op_res; + simple->destroy_op_res = destroy_op_res; +} + +/** + * g_simple_async_result_get_op_res_gpointer: + * @simple: + * + * Returns: gpointer. + **/ +gpointer +g_simple_async_result_get_op_res_gpointer (GSimpleAsyncResult *simple) +{ + g_return_val_if_fail (G_IS_SIMPLE_ASYNC_RESULT (simple), NULL); + return simple->op_res.v_pointer; +} + +/** + * g_simple_async_result_set_op_res_gssize: + * @simple: + * @op_res: + * + **/ +void +g_simple_async_result_set_op_res_gssize (GSimpleAsyncResult *simple, + gssize op_res) +{ + g_return_if_fail (G_IS_SIMPLE_ASYNC_RESULT (simple)); + simple->op_res.v_ssize = op_res; +} + +/** + * g_simple_async_result_get_op_res_gssize: + * @simple: + * + * Returns: + **/ +gssize +g_simple_async_result_get_op_res_gssize (GSimpleAsyncResult *simple) +{ + g_return_val_if_fail (G_IS_SIMPLE_ASYNC_RESULT (simple), 0); + return simple->op_res.v_ssize; +} + +/** + * g_simple_async_result_set_op_res_gboolean: + * @simple: + * @op_res: + * + **/ +void +g_simple_async_result_set_op_res_gboolean (GSimpleAsyncResult *simple, + gboolean op_res) +{ + g_return_if_fail (G_IS_SIMPLE_ASYNC_RESULT (simple)); + simple->op_res.v_boolean = !!op_res; +} + +/** + * g_simple_async_result_get_op_res_gboolean: + * @simple: + * + * Returns a #gboolean. + **/ +gboolean +g_simple_async_result_get_op_res_gboolean (GSimpleAsyncResult *simple) +{ + g_return_val_if_fail (G_IS_SIMPLE_ASYNC_RESULT (simple), FALSE); + return simple->op_res.v_boolean; +} + +/** + * g_simple_async_result_set_from_error: + * @simple: + * @error: #GError. + * + * Sets the result from given @error. + * + **/ +void +g_simple_async_result_set_from_error (GSimpleAsyncResult *simple, + GError *error) +{ + g_return_if_fail (G_IS_SIMPLE_ASYNC_RESULT (simple)); + g_return_if_fail (error != NULL); + + simple->error = g_error_copy (error); + simple->failed = TRUE; +} + +static GError* +_g_error_new_valist (GQuark domain, + gint code, + const char *format, + va_list args) +{ + GError *error; + char *message; + + message = g_strdup_vprintf (format, args); + + error = g_error_new_literal (domain, code, message); + g_free (message); + + return error; +} + +/** + * g_simple_async_result_set_error_va: + * @simple: + * @domain: + * @code: + * @format: + * @args: va_list of arguments. + * + * Sets error va_list, suitable for language bindings. + * + **/ +void +g_simple_async_result_set_error_va (GSimpleAsyncResult *simple, + GQuark domain, + gint code, + const char *format, + va_list args) +{ + g_return_if_fail (G_IS_SIMPLE_ASYNC_RESULT (simple)); + g_return_if_fail (domain != 0); + g_return_if_fail (format != NULL); + + simple->error = _g_error_new_valist (domain, code, format, args); + simple->failed = TRUE; +} + +/** + * g_simple_async_result_set_error: + * @simple: + * @domain: + * @code: + * @format: + * @... + * + **/ +void +g_simple_async_result_set_error (GSimpleAsyncResult *simple, + GQuark domain, + gint code, + const char *format, + ...) +{ + va_list args; + + g_return_if_fail (G_IS_SIMPLE_ASYNC_RESULT (simple)); + g_return_if_fail (domain != 0); + g_return_if_fail (format != NULL); + + va_start (args, format); + g_simple_async_result_set_error_va (simple, domain, code, format, args); + va_end (args); +} + +/** + * g_simple_async_result_complete: + * @simple: + * + **/ +void +g_simple_async_result_complete (GSimpleAsyncResult *simple) +{ + g_return_if_fail (G_IS_SIMPLE_ASYNC_RESULT (simple)); + + if (simple->callback) + simple->callback (simple->source_object, + G_ASYNC_RESULT (simple), + simple->user_data); +} + +static gboolean +complete_in_idle_cb (gpointer data) +{ + GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (data); + + g_simple_async_result_complete (simple); + + return FALSE; +} + +/** + * g_simple_async_result_complete_in_idle: + * @simple: + * + **/ +void +g_simple_async_result_complete_in_idle (GSimpleAsyncResult *simple) +{ + GSource *source; + guint id; + + g_return_if_fail (G_IS_SIMPLE_ASYNC_RESULT (simple)); + + g_object_ref (simple); + + source = g_idle_source_new (); + g_source_set_priority (source, G_PRIORITY_DEFAULT); + g_source_set_callback (source, complete_in_idle_cb, simple, g_object_unref); + + id = g_source_attach (source, NULL); + g_source_unref (source); +} + +typedef struct { + GSimpleAsyncResult *simple; + GSimpleAsyncThreadFunc func; +} RunInThreadData; + +static void +run_in_thread (GIOJob *job, + GCancellable *c, + gpointer _data) +{ + RunInThreadData *data = _data; + GSimpleAsyncResult *simple = data->simple; + + if (simple->handle_cancellation && + g_cancellable_is_cancelled (c)) + { + g_simple_async_result_set_error (simple, + G_IO_ERROR, + G_IO_ERROR_CANCELLED, + _("Operation was cancelled")); + } + else + { + data->func (simple, + simple->source_object, + c); + } + + g_simple_async_result_complete_in_idle (data->simple); + g_object_unref (data->simple); + g_free (data); +} + +/** + * g_simple_async_result_run_in_thread: + * @simple: + * @func: + * @io_priority: the io priority of the request. + * @cancellable: optional #GCancellable object, %NULL to ignore. + **/ +void +g_simple_async_result_run_in_thread (GSimpleAsyncResult *simple, + GSimpleAsyncThreadFunc func, + int io_priority, + GCancellable *cancellable) +{ + RunInThreadData *data; + + g_return_if_fail (G_IS_SIMPLE_ASYNC_RESULT (simple)); + g_return_if_fail (func != NULL); + + data = g_new (RunInThreadData, 1); + data->func = func; + data->simple = g_object_ref (simple); + g_schedule_io_job (run_in_thread, data, NULL, io_priority, cancellable); +} + +/** + * g_simple_async_report_error_in_idle: + * @object: + * @callback: + * @user_data: + * @domain: + * @code: + * @format: + * @... + * + **/ +void +g_simple_async_report_error_in_idle (GObject *object, + GAsyncReadyCallback callback, + gpointer user_data, + GQuark domain, + gint code, + const char *format, + ...) +{ + GSimpleAsyncResult *simple; + va_list args; + + g_return_if_fail (G_IS_OBJECT (object)); + g_return_if_fail (domain != 0); + g_return_if_fail (format != NULL); + + simple = g_simple_async_result_new (object, + callback, + user_data, NULL); + + va_start (args, format); + g_simple_async_result_set_error_va (simple, domain, code, format, args); + va_end (args); + g_simple_async_result_complete_in_idle (simple); + g_object_unref (simple); +} diff --git a/gio/gsimpleasyncresult.h b/gio/gsimpleasyncresult.h new file mode 100644 index 000000000..4c4ecf8e6 --- /dev/null +++ b/gio/gsimpleasyncresult.h @@ -0,0 +1,115 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#ifndef __G_SIMPLE_ASYNC_RESULT_H__ +#define __G_SIMPLE_ASYNC_RESULT_H__ + +#include <gio/gasyncresult.h> +#include <gio/gcancellable.h> + +G_BEGIN_DECLS + +#define G_TYPE_SIMPLE_ASYNC_RESULT (g_simple_async_result_get_type ()) +#define G_SIMPLE_ASYNC_RESULT(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_SIMPLE_ASYNC_RESULT, GSimpleAsyncResult)) +#define G_SIMPLE_ASYNC_RESULT_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), G_TYPE_SIMPLE_ASYNC_RESULT, GSimpleAsyncResultClass)) +#define G_IS_SIMPLE_ASYNC_RESULT(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_SIMPLE_ASYNC_RESULT)) +#define G_IS_SIMPLE_ASYNC_RESULT_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_SIMPLE_ASYNC_RESULT)) +#define G_SIMPLE_ASYNC_RESULT_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_TYPE_SIMPLE_ASYNC_RESULT, GSimpleAsyncResultClass)) + +typedef struct _GSimpleAsyncResult GSimpleAsyncResult; +typedef struct _GSimpleAsyncResultClass GSimpleAsyncResultClass; + +typedef void (*GSimpleAsyncThreadFunc) (GSimpleAsyncResult *res, + GObject *object, + GCancellable *cancellable); + + +GType g_simple_async_result_get_type (void) G_GNUC_CONST; + +GSimpleAsyncResult *g_simple_async_result_new (GObject *source_object, + GAsyncReadyCallback callback, + gpointer user_data, + gpointer source_tag); +GSimpleAsyncResult *g_simple_async_result_new_error (GObject *source_object, + GAsyncReadyCallback callback, + gpointer user_data, + GQuark domain, + gint code, + const char *format, + ...) G_GNUC_PRINTF (6, 7); +GSimpleAsyncResult *g_simple_async_result_new_from_error (GObject *source_object, + GAsyncReadyCallback callback, + gpointer user_data, + GError *error); + +void g_simple_async_result_set_op_res_gpointer (GSimpleAsyncResult *simple, + gpointer op_res, + GDestroyNotify destroy_op_res); +gpointer g_simple_async_result_get_op_res_gpointer (GSimpleAsyncResult *simple); + +void g_simple_async_result_set_op_res_gssize (GSimpleAsyncResult *simple, + gssize op_res); +gssize g_simple_async_result_get_op_res_gssize (GSimpleAsyncResult *simple); + +void g_simple_async_result_set_op_res_gboolean (GSimpleAsyncResult *simple, + gboolean op_res); +gboolean g_simple_async_result_get_op_res_gboolean (GSimpleAsyncResult *simple); + + + +gpointer g_simple_async_result_get_source_tag (GSimpleAsyncResult *simple); +void g_simple_async_result_set_handle_cancellation (GSimpleAsyncResult *simple, + gboolean handle_cancellation); +void g_simple_async_result_complete (GSimpleAsyncResult *simple); +void g_simple_async_result_complete_in_idle (GSimpleAsyncResult *simple); +void g_simple_async_result_run_in_thread (GSimpleAsyncResult *simple, + GSimpleAsyncThreadFunc func, + int io_priority, + GCancellable *cancellable); +void g_simple_async_result_set_from_error (GSimpleAsyncResult *simple, + GError *error); +gboolean g_simple_async_result_propagate_error (GSimpleAsyncResult *simple, + GError **dest); +void g_simple_async_result_set_error (GSimpleAsyncResult *simple, + GQuark domain, + gint code, + const char *format, + ...) G_GNUC_PRINTF (4, 5); +void g_simple_async_result_set_error_va (GSimpleAsyncResult *simple, + GQuark domain, + gint code, + const char *format, + va_list args); + +void g_simple_async_report_error_in_idle (GObject *object, + GAsyncReadyCallback callback, + gpointer user_data, + GQuark domain, + gint code, + const char *format, + ...); + +G_END_DECLS + + + +#endif /* __G_SIMPLE_ASYNC_RESULT_H__ */ diff --git a/gio/gsocketinputstream.c b/gio/gsocketinputstream.c new file mode 100644 index 000000000..890e8e1f8 --- /dev/null +++ b/gio/gsocketinputstream.c @@ -0,0 +1,469 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#include <config.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <errno.h> +#include <stdio.h> +#include <fcntl.h> +#include <poll.h> + +#include <glib.h> +#include <glib/gstdio.h> +#include "gioerror.h" +#include "gsimpleasyncresult.h" +#include "gsocketinputstream.h" +#include "gcancellable.h" +#include "gasynchelper.h" + +#include "glibintl.h" + +G_DEFINE_TYPE (GSocketInputStream, g_socket_input_stream, G_TYPE_INPUT_STREAM); + +struct _GSocketInputStreamPrivate { + int fd; + gboolean close_fd_at_close; +}; + +static gssize g_socket_input_stream_read (GInputStream *stream, + void *buffer, + gsize count, + GCancellable *cancellable, + GError **error); +static gboolean g_socket_input_stream_close (GInputStream *stream, + GCancellable *cancellable, + GError **error); +static void g_socket_input_stream_read_async (GInputStream *stream, + void *buffer, + gsize count, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer data); +static gssize g_socket_input_stream_read_finish (GInputStream *stream, + GAsyncResult *result, + GError **error); +static void g_socket_input_stream_skip_async (GInputStream *stream, + gsize count, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer data); +static gssize g_socket_input_stream_skip_finish (GInputStream *stream, + GAsyncResult *result, + GError **error); +static void g_socket_input_stream_close_async (GInputStream *stream, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer data); +static gboolean g_socket_input_stream_close_finish (GInputStream *stream, + GAsyncResult *result, + GError **error); + +static void +g_socket_input_stream_finalize (GObject *object) +{ + GSocketInputStream *stream; + + stream = G_SOCKET_INPUT_STREAM (object); + + if (G_OBJECT_CLASS (g_socket_input_stream_parent_class)->finalize) + (*G_OBJECT_CLASS (g_socket_input_stream_parent_class)->finalize) (object); +} + +static void +g_socket_input_stream_class_init (GSocketInputStreamClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GInputStreamClass *stream_class = G_INPUT_STREAM_CLASS (klass); + + g_type_class_add_private (klass, sizeof (GSocketInputStreamPrivate)); + + gobject_class->finalize = g_socket_input_stream_finalize; + + stream_class->read = g_socket_input_stream_read; + stream_class->close = g_socket_input_stream_close; + stream_class->read_async = g_socket_input_stream_read_async; + stream_class->read_finish = g_socket_input_stream_read_finish; + if (0) + { + /* TODO: Implement instead of using fallbacks */ + stream_class->skip_async = g_socket_input_stream_skip_async; + stream_class->skip_finish = g_socket_input_stream_skip_finish; + } + stream_class->close_async = g_socket_input_stream_close_async; + stream_class->close_finish = g_socket_input_stream_close_finish; +} + +static void +g_socket_input_stream_init (GSocketInputStream *socket) +{ + socket->priv = G_TYPE_INSTANCE_GET_PRIVATE (socket, + G_TYPE_SOCKET_INPUT_STREAM, + GSocketInputStreamPrivate); +} + +/** + * g_socket_input_stream_new: + * @fd: file descriptor. + * @close_fd_at_close: boolean value + * + * + * Returns: new #GInputStream. If @close_fd_at_close is %TRUE, + * @fd will be closed when the #GInputStream is closed. + **/ +GInputStream * +g_socket_input_stream_new (int fd, + gboolean close_fd_at_close) +{ + GSocketInputStream *stream; + + g_return_val_if_fail (fd != -1, NULL); + + stream = g_object_new (G_TYPE_SOCKET_INPUT_STREAM, NULL); + + stream->priv->fd = fd; + stream->priv->close_fd_at_close = close_fd_at_close; + + return G_INPUT_STREAM (stream); +} + +static gssize +g_socket_input_stream_read (GInputStream *stream, + void *buffer, + gsize count, + GCancellable *cancellable, + GError **error) +{ + GSocketInputStream *socket_stream; + gssize res; + struct pollfd poll_fds[2]; + int poll_ret; + int cancel_fd; + + socket_stream = G_SOCKET_INPUT_STREAM (stream); + + cancel_fd = g_cancellable_get_fd (cancellable); + if (cancel_fd != -1) + { + do + { + poll_fds[0].events = POLLIN; + poll_fds[0].fd = socket_stream->priv->fd; + poll_fds[1].events = POLLIN; + poll_fds[1].fd = cancel_fd; + poll_ret = poll (poll_fds, 2, -1); + } + while (poll_ret == -1 && errno == EINTR); + + if (poll_ret == -1) + { + g_set_error (error, G_IO_ERROR, + g_io_error_from_errno (errno), + _("Error reading from socket: %s"), + g_strerror (errno)); + return -1; + } + } + + while (1) + { + if (g_cancellable_set_error_if_cancelled (cancellable, error)) + break; + res = read (socket_stream->priv->fd, buffer, count); + if (res == -1) + { + if (errno == EINTR) + continue; + + g_set_error (error, G_IO_ERROR, + g_io_error_from_errno (errno), + _("Error reading from socket: %s"), + g_strerror (errno)); + } + + break; + } + + return res; +} + +static gboolean +g_socket_input_stream_close (GInputStream *stream, + GCancellable *cancellable, + GError **error) +{ + GSocketInputStream *socket_stream; + int res; + + socket_stream = G_SOCKET_INPUT_STREAM (stream); + + if (!socket_stream->priv->close_fd_at_close) + return TRUE; + + while (1) + { + /* This might block during the close. Doesn't seem to be a way to avoid it though. */ + res = close (socket_stream->priv->fd); + if (res == -1) + { + g_set_error (error, G_IO_ERROR, + g_io_error_from_errno (errno), + _("Error closing socket: %s"), + g_strerror (errno)); + } + break; + } + + return res != -1; +} + +typedef struct { + gsize count; + void *buffer; + GAsyncReadyCallback callback; + gpointer user_data; + GCancellable *cancellable; + GSocketInputStream *stream; +} ReadAsyncData; + +static gboolean +read_async_cb (ReadAsyncData *data, + GIOCondition condition, + int fd) +{ + GSimpleAsyncResult *simple; + GError *error = NULL; + gssize count_read; + + /* We know that we can read from fd once without blocking */ + while (1) + { + if (g_cancellable_set_error_if_cancelled (data->cancellable, &error)) + { + count_read = -1; + break; + } + count_read = read (data->stream->priv->fd, data->buffer, data->count); + if (count_read == -1) + { + if (errno == EINTR) + continue; + + g_set_error (&error, G_IO_ERROR, + g_io_error_from_errno (errno), + _("Error reading from socket: %s"), + g_strerror (errno)); + } + break; + } + + simple = g_simple_async_result_new (G_OBJECT (data->stream), + data->callback, + data->user_data, + g_socket_input_stream_read_async); + + g_simple_async_result_set_op_res_gssize (simple, count_read); + + if (count_read == -1) + { + g_simple_async_result_set_from_error (simple, error); + g_error_free (error); + } + + /* Complete immediately, not in idle, since we're already in a mainloop callout */ + g_simple_async_result_complete (simple); + g_object_unref (simple); + + return FALSE; +} + +static void +g_socket_input_stream_read_async (GInputStream *stream, + void *buffer, + gsize count, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GSource *source; + GSocketInputStream *socket_stream; + ReadAsyncData *data; + + socket_stream = G_SOCKET_INPUT_STREAM (stream); + + data = g_new0 (ReadAsyncData, 1); + data->count = count; + data->buffer = buffer; + data->callback = callback; + data->user_data = user_data; + data->cancellable = cancellable; + data->stream = socket_stream; + + source = _g_fd_source_new (socket_stream->priv->fd, + POLLIN, + cancellable); + + g_source_set_callback (source, (GSourceFunc)read_async_cb, data, g_free); + g_source_attach (source, NULL); + + g_source_unref (source); +} + +static gssize +g_socket_input_stream_read_finish (GInputStream *stream, + GAsyncResult *result, + GError **error) +{ + GSimpleAsyncResult *simple; + gssize nread; + + simple = G_SIMPLE_ASYNC_RESULT (result); + g_assert (g_simple_async_result_get_source_tag (simple) == g_socket_input_stream_read_async); + + nread = g_simple_async_result_get_op_res_gssize (simple); + return nread; +} + +static void +g_socket_input_stream_skip_async (GInputStream *stream, + gsize count, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer data) +{ + g_assert_not_reached (); + /* TODO: Not implemented */ +} + +static gssize +g_socket_input_stream_skip_finish (GInputStream *stream, + GAsyncResult *result, + GError **error) +{ + g_assert_not_reached (); + /* TODO: Not implemented */ +} + + +typedef struct { + GInputStream *stream; + GAsyncReadyCallback callback; + gpointer user_data; +} CloseAsyncData; + +static void +close_async_data_free (gpointer _data) +{ + CloseAsyncData *data = _data; + + g_free (data); +} + +static gboolean +close_async_cb (CloseAsyncData *data) +{ + GSocketInputStream *socket_stream; + GSimpleAsyncResult *simple; + GError *error = NULL; + gboolean result; + int res; + + socket_stream = G_SOCKET_INPUT_STREAM (data->stream); + + if (!socket_stream->priv->close_fd_at_close) + { + result = TRUE; + goto out; + } + + while (1) + { + res = close (socket_stream->priv->fd); + if (res == -1) + { + g_set_error (&error, G_IO_ERROR, + g_io_error_from_errno (errno), + _("Error closing socket: %s"), + g_strerror (errno)); + } + break; + } + + result = res != -1; + + out: + simple = g_simple_async_result_new (G_OBJECT (data->stream), + data->callback, + data->user_data, + g_socket_input_stream_close_async); + + if (!result) + { + g_simple_async_result_set_from_error (simple, error); + g_error_free (error); + } + + /* Complete immediately, not in idle, since we're already in a mainloop callout */ + g_simple_async_result_complete (simple); + g_object_unref (simple); + + return FALSE; +} + +static void +g_socket_input_stream_close_async (GInputStream *stream, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GSource *idle; + CloseAsyncData *data; + + data = g_new0 (CloseAsyncData, 1); + + data->stream = stream; + data->callback = callback; + data->user_data = user_data; + + idle = g_idle_source_new (); + g_source_set_callback (idle, (GSourceFunc)close_async_cb, data, close_async_data_free); + g_source_attach (idle, NULL); + g_source_unref (idle); +} + +static gboolean +g_socket_input_stream_close_finish (GInputStream *stream, + GAsyncResult *result, + GError **error) +{ + /* Failures handled in generic close_finish code */ + return TRUE; +} + diff --git a/gio/gsocketinputstream.h b/gio/gsocketinputstream.h new file mode 100644 index 000000000..31b79ce50 --- /dev/null +++ b/gio/gsocketinputstream.h @@ -0,0 +1,68 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#ifndef __G_SOCKET_INPUT_STREAM_H__ +#define __G_SOCKET_INPUT_STREAM_H__ + +#include <gio/ginputstream.h> + +G_BEGIN_DECLS + +#define G_TYPE_SOCKET_INPUT_STREAM (g_socket_input_stream_get_type ()) +#define G_SOCKET_INPUT_STREAM(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_SOCKET_INPUT_STREAM, GSocketInputStream)) +#define G_SOCKET_INPUT_STREAM_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), G_TYPE_SOCKET_INPUT_STREAM, GSocketInputStreamClass)) +#define G_IS_SOCKET_INPUT_STREAM(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_SOCKET_INPUT_STREAM)) +#define G_IS_SOCKET_INPUT_STREAM_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_SOCKET_INPUT_STREAM)) +#define G_SOCKET_INPUT_STREAM_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_TYPE_SOCKET_INPUT_STREAM, GSocketInputStreamClass)) + +typedef struct _GSocketInputStream GSocketInputStream; +typedef struct _GSocketInputStreamClass GSocketInputStreamClass; +typedef struct _GSocketInputStreamPrivate GSocketInputStreamPrivate; + +struct _GSocketInputStream +{ + GInputStream parent; + + /*< private >*/ + GSocketInputStreamPrivate *priv; +}; + +struct _GSocketInputStreamClass +{ + GInputStreamClass parent_class; + + /* Padding for future expansion */ + void (*_g_reserved1) (void); + void (*_g_reserved2) (void); + void (*_g_reserved3) (void); + void (*_g_reserved4) (void); + void (*_g_reserved5) (void); +}; + +GType g_socket_input_stream_get_type (void) G_GNUC_CONST; + +GInputStream *g_socket_input_stream_new (int fd, + gboolean close_fd_at_close); + +G_END_DECLS + +#endif /* __G_SOCKET_INPUT_STREAM_H__ */ diff --git a/gio/gsocketoutputstream.c b/gio/gsocketoutputstream.c new file mode 100644 index 000000000..54539f589 --- /dev/null +++ b/gio/gsocketoutputstream.c @@ -0,0 +1,426 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#include <config.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <errno.h> +#include <stdio.h> +#include <fcntl.h> +#include <poll.h> + +#include <glib.h> +#include <glib/gstdio.h> +#include "gioerror.h" +#include "gsocketoutputstream.h" +#include "gcancellable.h" +#include "gsimpleasyncresult.h" +#include "gasynchelper.h" + +#include "glibintl.h" + +G_DEFINE_TYPE (GSocketOutputStream, g_socket_output_stream, G_TYPE_OUTPUT_STREAM); + + +struct _GSocketOutputStreamPrivate { + int fd; + gboolean close_fd_at_close; +}; + +static gssize g_socket_output_stream_write (GOutputStream *stream, + const void *buffer, + gsize count, + GCancellable *cancellable, + GError **error); +static gboolean g_socket_output_stream_close (GOutputStream *stream, + GCancellable *cancellable, + GError **error); +static void g_socket_output_stream_write_async (GOutputStream *stream, + const void *buffer, + gsize count, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer data); +static gssize g_socket_output_stream_write_finish (GOutputStream *stream, + GAsyncResult *result, + GError **error); +static void g_socket_output_stream_close_async (GOutputStream *stream, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer data); +static gboolean g_socket_output_stream_close_finish (GOutputStream *stream, + GAsyncResult *result, + GError **error); + + +static void +g_socket_output_stream_finalize (GObject *object) +{ + GSocketOutputStream *stream; + + stream = G_SOCKET_OUTPUT_STREAM (object); + + if (G_OBJECT_CLASS (g_socket_output_stream_parent_class)->finalize) + (*G_OBJECT_CLASS (g_socket_output_stream_parent_class)->finalize) (object); +} + +static void +g_socket_output_stream_class_init (GSocketOutputStreamClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GOutputStreamClass *stream_class = G_OUTPUT_STREAM_CLASS (klass); + + g_type_class_add_private (klass, sizeof (GSocketOutputStreamPrivate)); + + gobject_class->finalize = g_socket_output_stream_finalize; + + stream_class->write = g_socket_output_stream_write; + stream_class->close = g_socket_output_stream_close; + stream_class->write_async = g_socket_output_stream_write_async; + stream_class->write_finish = g_socket_output_stream_write_finish; + stream_class->close_async = g_socket_output_stream_close_async; + stream_class->close_finish = g_socket_output_stream_close_finish; +} + +static void +g_socket_output_stream_init (GSocketOutputStream *socket) +{ + socket->priv = G_TYPE_INSTANCE_GET_PRIVATE (socket, + G_TYPE_SOCKET_OUTPUT_STREAM, + GSocketOutputStreamPrivate); +} + + +/** + * g_socket_output_stream_new: + * @fd: file descriptor. + * @close_fd_at_close: boolean value. + * + * Returns: #GOutputStream. If @close_fd_at_close is %TRUE, then + * @fd will be closed when the #GOutputStream is closed. + **/ +GOutputStream * +g_socket_output_stream_new (int fd, + gboolean close_fd_at_close) +{ + GSocketOutputStream *stream; + + g_return_val_if_fail (fd != -1, NULL); + + stream = g_object_new (G_TYPE_SOCKET_OUTPUT_STREAM, NULL); + + stream->priv->fd = fd; + stream->priv->close_fd_at_close = close_fd_at_close; + + return G_OUTPUT_STREAM (stream); +} + +static gssize +g_socket_output_stream_write (GOutputStream *stream, + const void *buffer, + gsize count, + GCancellable *cancellable, + GError **error) +{ + GSocketOutputStream *socket_stream; + gssize res; + struct pollfd poll_fds[2]; + int poll_ret; + int cancel_fd; + + socket_stream = G_SOCKET_OUTPUT_STREAM (stream); + + cancel_fd = g_cancellable_get_fd (cancellable); + if (cancel_fd != -1) + { + do + { + poll_fds[0].events = POLLOUT; + poll_fds[0].fd = socket_stream->priv->fd; + poll_fds[1].events = POLLIN; + poll_fds[1].fd = cancel_fd; + poll_ret = poll (poll_fds, 2, -1); + } + while (poll_ret == -1 && errno == EINTR); + + if (poll_ret == -1) + { + g_set_error (error, G_IO_ERROR, + g_io_error_from_errno (errno), + _("Error writing to socket: %s"), + g_strerror (errno)); + return -1; + } + } + + while (1) + { + if (g_cancellable_set_error_if_cancelled (cancellable, error)) + return -1; + + res = write (socket_stream->priv->fd, buffer, count); + if (res == -1) + { + if (errno == EINTR) + continue; + + g_set_error (error, G_IO_ERROR, + g_io_error_from_errno (errno), + _("Error writing to socket: %s"), + g_strerror (errno)); + } + + break; + } + + return res; +} + +static gboolean +g_socket_output_stream_close (GOutputStream *stream, + GCancellable *cancellable, + GError **error) +{ + GSocketOutputStream *socket_stream; + int res; + + socket_stream = G_SOCKET_OUTPUT_STREAM (stream); + + if (!socket_stream->priv->close_fd_at_close) + return TRUE; + + while (1) + { + /* This might block during the close. Doesn't seem to be a way to avoid it though. */ + res = close (socket_stream->priv->fd); + if (res == -1) + { + g_set_error (error, G_IO_ERROR, + g_io_error_from_errno (errno), + _("Error closing socket: %s"), + g_strerror (errno)); + } + break; + } + + return res != -1; +} + +typedef struct { + gsize count; + const void *buffer; + GAsyncReadyCallback callback; + gpointer user_data; + GCancellable *cancellable; + GSocketOutputStream *stream; +} WriteAsyncData; + +static gboolean +write_async_cb (WriteAsyncData *data, + GIOCondition condition, + int fd) +{ + GSimpleAsyncResult *simple; + GError *error = NULL; + gssize count_written; + + while (1) + { + if (g_cancellable_set_error_if_cancelled (data->cancellable, &error)) + { + count_written = -1; + break; + } + + count_written = write (data->stream->priv->fd, data->buffer, data->count); + if (count_written == -1) + { + if (errno == EINTR) + continue; + + g_set_error (&error, G_IO_ERROR, + g_io_error_from_errno (errno), + _("Error reading from socket: %s"), + g_strerror (errno)); + } + break; + } + + simple = g_simple_async_result_new (G_OBJECT (data->stream), + data->callback, + data->user_data, + g_socket_output_stream_write_async); + + g_simple_async_result_set_op_res_gssize (simple, count_written); + + if (count_written == -1) + { + g_simple_async_result_set_from_error (simple, error); + g_error_free (error); + } + + /* Complete immediately, not in idle, since we're already in a mainloop callout */ + g_simple_async_result_complete (simple); + g_object_unref (simple); + + return FALSE; +} + +static void +g_socket_output_stream_write_async (GOutputStream *stream, + const void *buffer, + gsize count, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GSource *source; + GSocketOutputStream *socket_stream; + WriteAsyncData *data; + + socket_stream = G_SOCKET_OUTPUT_STREAM (stream); + + data = g_new0 (WriteAsyncData, 1); + data->count = count; + data->buffer = buffer; + data->callback = callback; + data->user_data = user_data; + data->cancellable = cancellable; + data->stream = socket_stream; + + source = _g_fd_source_new (socket_stream->priv->fd, + POLLOUT, + cancellable); + + g_source_set_callback (source, (GSourceFunc)write_async_cb, data, g_free); + g_source_attach (source, NULL); + + g_source_unref (source); +} + +static gssize +g_socket_output_stream_write_finish (GOutputStream *stream, + GAsyncResult *result, + GError **error) +{ + GSimpleAsyncResult *simple; + gssize nwritten; + + simple = G_SIMPLE_ASYNC_RESULT (result); + g_assert (g_simple_async_result_get_source_tag (simple) == g_socket_output_stream_write_async); + + nwritten = g_simple_async_result_get_op_res_gssize (simple); + return nwritten; +} + +typedef struct { + GOutputStream *stream; + GAsyncReadyCallback callback; + gpointer user_data; +} CloseAsyncData; + +static gboolean +close_async_cb (CloseAsyncData *data) +{ + GSocketOutputStream *socket_stream; + GSimpleAsyncResult *simple; + GError *error = NULL; + gboolean result; + int res; + + socket_stream = G_SOCKET_OUTPUT_STREAM (data->stream); + + if (!socket_stream->priv->close_fd_at_close) + { + result = TRUE; + goto out; + } + + while (1) + { + res = close (socket_stream->priv->fd); + if (res == -1) + { + g_set_error (&error, G_IO_ERROR, + g_io_error_from_errno (errno), + _("Error closing socket: %s"), + g_strerror (errno)); + } + break; + } + + result = res != -1; + + out: + simple = g_simple_async_result_new (G_OBJECT (data->stream), + data->callback, + data->user_data, + g_socket_output_stream_close_async); + + if (!result) + { + g_simple_async_result_set_from_error (simple, error); + g_error_free (error); + } + + /* Complete immediately, not in idle, since we're already in a mainloop callout */ + g_simple_async_result_complete (simple); + g_object_unref (simple); + + return FALSE; +} + +static void +g_socket_output_stream_close_async (GOutputStream *stream, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GSource *idle; + CloseAsyncData *data; + + data = g_new0 (CloseAsyncData, 1); + + data->stream = stream; + data->callback = callback; + data->user_data = user_data; + + idle = g_idle_source_new (); + g_source_set_callback (idle, (GSourceFunc)close_async_cb, data, g_free); + g_source_attach (idle, NULL); + g_source_unref (idle); +} + +static gboolean +g_socket_output_stream_close_finish (GOutputStream *stream, + GAsyncResult *result, + GError **error) +{ + /* Failures handled in generic close_finish code */ + return TRUE; +} diff --git a/gio/gsocketoutputstream.h b/gio/gsocketoutputstream.h new file mode 100644 index 000000000..cff198654 --- /dev/null +++ b/gio/gsocketoutputstream.h @@ -0,0 +1,68 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#ifndef __G_SOCKET_OUTPUT_STREAM_H__ +#define __G_SOCKET_OUTPUT_STREAM_H__ + +#include <gio/goutputstream.h> + +G_BEGIN_DECLS + +#define G_TYPE_SOCKET_OUTPUT_STREAM (g_socket_output_stream_get_type ()) +#define G_SOCKET_OUTPUT_STREAM(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_SOCKET_OUTPUT_STREAM, GSocketOutputStream)) +#define G_SOCKET_OUTPUT_STREAM_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), G_TYPE_SOCKET_OUTPUT_STREAM, GSocketOutputStreamClass)) +#define G_IS_SOCKET_OUTPUT_STREAM(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_SOCKET_OUTPUT_STREAM)) +#define G_IS_SOCKET_OUTPUT_STREAM_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_SOCKET_OUTPUT_STREAM)) +#define G_SOCKET_OUTPUT_STREAM_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_TYPE_SOCKET_OUTPUT_STREAM, GSocketOutputStreamClass)) + +typedef struct _GSocketOutputStream GSocketOutputStream; +typedef struct _GSocketOutputStreamClass GSocketOutputStreamClass; +typedef struct _GSocketOutputStreamPrivate GSocketOutputStreamPrivate; + +struct _GSocketOutputStream +{ + GOutputStream parent; + + /*< private >*/ + GSocketOutputStreamPrivate *priv; +}; + +struct _GSocketOutputStreamClass +{ + GOutputStreamClass parent_class; + + /* Padding for future expansion */ + void (*_g_reserved1) (void); + void (*_g_reserved2) (void); + void (*_g_reserved3) (void); + void (*_g_reserved4) (void); + void (*_g_reserved5) (void); +}; + +GType g_socket_output_stream_get_type (void) G_GNUC_CONST; + +GOutputStream *g_socket_output_stream_new (int fd, + gboolean close_fd_at_close); + +G_END_DECLS + +#endif /* __G_SOCKET_OUTPUT_STREAM_H__ */ diff --git a/gio/gthemedicon.c b/gio/gthemedicon.c new file mode 100644 index 000000000..e27b03374 --- /dev/null +++ b/gio/gthemedicon.c @@ -0,0 +1,172 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#include <config.h> + +#include "gthemedicon.h" + +static void g_themed_icon_icon_iface_init (GIconIface *iface); + +struct _GThemedIcon +{ + GObject parent_instance; + + char **names; +}; + +struct _GThemedIconClass +{ + GObjectClass parent_class; +}; + +G_DEFINE_TYPE_WITH_CODE (GThemedIcon, g_themed_icon, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (G_TYPE_ICON, + g_themed_icon_icon_iface_init)) + +static void +g_themed_icon_finalize (GObject *object) +{ + GThemedIcon *themed; + + themed = G_THEMED_ICON (object); + + g_strfreev (themed->names); + + if (G_OBJECT_CLASS (g_themed_icon_parent_class)->finalize) + (*G_OBJECT_CLASS (g_themed_icon_parent_class)->finalize) (object); +} + +static void +g_themed_icon_class_init (GThemedIconClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->finalize = g_themed_icon_finalize; +} + +static void +g_themed_icon_init (GThemedIcon *themed) +{ +} + +/** + * g_themed_icon_new: + * @iconname: + * + * Returns: a new #GIcon. + **/ +GIcon * +g_themed_icon_new (const char *iconname) +{ + GThemedIcon *themed; + + g_return_val_if_fail (iconname != NULL, NULL); + + themed = g_object_new (G_TYPE_THEMED_ICON, NULL); + themed->names = g_new (char *, 2); + themed->names[0] = g_strdup (iconname); + themed->names[1] = NULL; + + return G_ICON (themed); +} + +/** + * g_themed_icon_new_from_names: + * @iconnames: + * @len: + * + * Returns: a new #GIcon. + **/ +GIcon * +g_themed_icon_new_from_names (char **iconnames, int len) +{ + GThemedIcon *themed; + int i; + + g_return_val_if_fail (iconnames != NULL, NULL); + + themed = g_object_new (G_TYPE_THEMED_ICON, NULL); + if (len == -1) + themed->names = g_strdupv (iconnames); + else + { + themed->names = g_new (char *, len + 1); + for (i = 0; i < len; i++) + themed->names[i] = g_strdup (iconnames[i]); + themed->names[i] = NULL; + } + + + return G_ICON (themed); +} + +/** + * g_themed_icon_get_names: + * @icon: + * + * Returns: + **/ +const char * const * +g_themed_icon_get_names (GThemedIcon *icon) +{ + g_return_val_if_fail (G_IS_THEMED_ICON (icon), NULL); + return (const char * const *)icon->names; +} + +static guint +g_themed_icon_hash (GIcon *icon) +{ + GThemedIcon *themed = G_THEMED_ICON (icon); + guint hash; + int i; + + hash = 0; + + for (i = 0; themed->names[i] != NULL; i++) + hash ^= g_str_hash (themed->names[i]); + + return hash; +} + +static gboolean +g_themed_icon_equal (GIcon *icon1, + GIcon *icon2) +{ + GThemedIcon *themed1 = G_THEMED_ICON (icon1); + GThemedIcon *themed2 = G_THEMED_ICON (icon2); + int i; + + for (i = 0; themed1->names[i] != NULL && themed2->names[i] != NULL; i++) + { + if (!g_str_equal (themed1->names[i], themed2->names[i])) + return FALSE; + } + + return themed1->names[i] == NULL && themed2->names[i] == NULL; +} + +static void +g_themed_icon_icon_iface_init (GIconIface *iface) +{ + iface->hash = g_themed_icon_hash; + iface->equal = g_themed_icon_equal; +} diff --git a/gio/gthemedicon.h b/gio/gthemedicon.h new file mode 100644 index 000000000..f560f1871 --- /dev/null +++ b/gio/gthemedicon.h @@ -0,0 +1,49 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#ifndef __G_THEMED_ICON_H__ +#define __G_THEMED_ICON_H__ + +#include <gio/gicon.h> + +G_BEGIN_DECLS + +#define G_TYPE_THEMED_ICON (g_themed_icon_get_type ()) +#define G_THEMED_ICON(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_THEMED_ICON, GThemedIcon)) +#define G_THEMED_ICON_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), G_TYPE_THEMED_ICON, GThemedIconClass)) +#define G_IS_THEMED_ICON(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_THEMED_ICON)) +#define G_IS_THEMED_ICON_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_THEMED_ICON)) +#define G_THEMED_ICON_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_TYPE_THEMED_ICON, GThemedIconClass)) + +typedef struct _GThemedIcon GThemedIcon; +typedef struct _GThemedIconClass GThemedIconClass; + +GType g_themed_icon_get_type (void) G_GNUC_CONST; + +GIcon *g_themed_icon_new (const char *iconname); +GIcon *g_themed_icon_new_from_names (char **iconnames, int len); + +const char * const *g_themed_icon_get_names (GThemedIcon *icon); + +G_END_DECLS + +#endif /* __G_THEMED_ICON_H__ */ diff --git a/gio/gunionvolumemonitor.c b/gio/gunionvolumemonitor.c new file mode 100644 index 000000000..bc46d4c64 --- /dev/null +++ b/gio/gunionvolumemonitor.c @@ -0,0 +1,392 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#include <config.h> + +#include <string.h> + +#include <glib.h> +#include "gunionvolumemonitor.h" +#include "gvolumeprivate.h" +#include "giomodule.h" +#ifdef G_OS_UNIX +#include "gunixvolumemonitor.h" +#endif +#include "gnativevolumemonitor.h" + +#include "glibintl.h" + +struct _GUnionVolumeMonitor { + GVolumeMonitor parent; + + GList *monitors; +}; + +static void g_union_volume_monitor_remove_monitor (GUnionVolumeMonitor *union_monitor, + GVolumeMonitor *child_monitor); + + +G_DEFINE_TYPE (GUnionVolumeMonitor, g_union_volume_monitor, G_TYPE_VOLUME_MONITOR); + + +G_LOCK_DEFINE_STATIC(the_volume_monitor); +static GUnionVolumeMonitor *the_volume_monitor = NULL; + +static void +g_union_volume_monitor_finalize (GObject *object) +{ + GUnionVolumeMonitor *monitor; + + monitor = G_UNION_VOLUME_MONITOR (object); + + while (monitor->monitors != NULL) + g_union_volume_monitor_remove_monitor (monitor, + monitor->monitors->data); + + if (G_OBJECT_CLASS (g_union_volume_monitor_parent_class)->finalize) + (*G_OBJECT_CLASS (g_union_volume_monitor_parent_class)->finalize) (object); +} + +static void +g_union_volume_monitor_dispose (GObject *object) +{ + GUnionVolumeMonitor *monitor; + + monitor = G_UNION_VOLUME_MONITOR (object); + + G_LOCK (the_volume_monitor); + the_volume_monitor = NULL; + G_UNLOCK (the_volume_monitor); + + if (G_OBJECT_CLASS (g_union_volume_monitor_parent_class)->dispose) + (*G_OBJECT_CLASS (g_union_volume_monitor_parent_class)->dispose) (object); +} + +static GList * +get_mounted_volumes (GVolumeMonitor *volume_monitor) +{ + GUnionVolumeMonitor *monitor; + GVolumeMonitor *child_monitor; + GList *res; + GList *l; + + monitor = G_UNION_VOLUME_MONITOR (volume_monitor); + + res = NULL; + + G_LOCK (the_volume_monitor); + + for (l = monitor->monitors; l != NULL; l = l->next) + { + child_monitor = l->data; + + res = g_list_concat (res, + g_volume_monitor_get_mounted_volumes (child_monitor)); + } + + G_UNLOCK (the_volume_monitor); + + return res; +} + +static GList * +get_connected_drives (GVolumeMonitor *volume_monitor) +{ + GUnionVolumeMonitor *monitor; + GVolumeMonitor *child_monitor; + GList *res; + GList *l; + + monitor = G_UNION_VOLUME_MONITOR (volume_monitor); + + res = NULL; + + G_LOCK (the_volume_monitor); + + for (l = monitor->monitors; l != NULL; l = l->next) + { + child_monitor = l->data; + + res = g_list_concat (res, + g_volume_monitor_get_connected_drives (child_monitor)); + } + + G_UNLOCK (the_volume_monitor); + + return res; +} + +static void +g_union_volume_monitor_class_init (GUnionVolumeMonitorClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GVolumeMonitorClass *monitor_class = G_VOLUME_MONITOR_CLASS (klass); + + gobject_class->finalize = g_union_volume_monitor_finalize; + gobject_class->dispose = g_union_volume_monitor_dispose; + + monitor_class->get_mounted_volumes = get_mounted_volumes; + monitor_class->get_connected_drives = get_connected_drives; +} + +static void +child_volume_mounted (GVolumeMonitor *child_monitor, + GVolume *child_volume, + GUnionVolumeMonitor *union_monitor) +{ + g_signal_emit_by_name (union_monitor, + "volume_mounted", + child_volume); +} + +static void +child_volume_pre_unmount (GVolumeMonitor *child_monitor, + GVolume *child_volume, + GUnionVolumeMonitor *union_monitor) +{ + g_signal_emit_by_name (union_monitor, + "volume_pre_unmount", + child_volume); +} + +static void +child_volume_unmounted (GVolumeMonitor *child_monitor, + GVolume *child_volume, + GUnionVolumeMonitor *union_monitor) +{ + g_signal_emit_by_name (union_monitor, + "volume_unmounted", + child_volume); +} + +static void +child_drive_connected (GVolumeMonitor *child_monitor, + GDrive *child_drive, + GUnionVolumeMonitor *union_monitor) +{ + g_signal_emit_by_name (union_monitor, + "drive_connected", + child_drive); +} + +static void +child_drive_disconnected (GVolumeMonitor *child_monitor, + GDrive *child_drive, + GUnionVolumeMonitor *union_monitor) +{ + g_signal_emit_by_name (union_monitor, + "drive_disconnected", + child_drive); +} + +static void +g_union_volume_monitor_add_monitor (GUnionVolumeMonitor *union_monitor, + GVolumeMonitor *volume_monitor) +{ + if (g_list_find (union_monitor->monitors, volume_monitor)) + return; + + union_monitor->monitors = + g_list_prepend (union_monitor->monitors, + g_object_ref (volume_monitor)); + + g_signal_connect (volume_monitor, "volume_mounted", (GCallback)child_volume_mounted, union_monitor); + g_signal_connect (volume_monitor, "volume_pre_unmount", (GCallback)child_volume_pre_unmount, union_monitor); + g_signal_connect (volume_monitor, "volume_unmounted", (GCallback)child_volume_unmounted, union_monitor); + g_signal_connect (volume_monitor, "drive_connected", (GCallback)child_drive_connected, union_monitor); + g_signal_connect (volume_monitor, "drive_disconnected", (GCallback)child_drive_disconnected, union_monitor); +} + +static void +g_union_volume_monitor_remove_monitor (GUnionVolumeMonitor *union_monitor, + GVolumeMonitor *child_monitor) +{ + GList *l; + + l = g_list_find (union_monitor->monitors, child_monitor); + if (l == NULL) + return; + + union_monitor->monitors = g_list_delete_link (union_monitor->monitors, l); + + g_signal_handlers_disconnect_by_func (child_monitor, child_volume_mounted, union_monitor); + g_signal_handlers_disconnect_by_func (child_monitor, child_volume_pre_unmount, union_monitor); + g_signal_handlers_disconnect_by_func (child_monitor, child_volume_unmounted, union_monitor); + g_signal_handlers_disconnect_by_func (child_monitor, child_drive_connected, union_monitor); + g_signal_handlers_disconnect_by_func (child_monitor, child_drive_disconnected, union_monitor); +} + +static gpointer +get_default_native_type (gpointer data) +{ + GNativeVolumeMonitorClass *klass; + GType *monitors; + guint n_monitors; + GType native_type; + GType *ret = (GType *) data; + int native_prio; + int i; + +#ifdef G_OS_UNIX + /* Ensure GUnixVolumeMonitor type is available */ + { + volatile GType unix_type; + /* volatile is required to avoid any G_GNUC_CONST optimizations */ + unix_type = g_unix_volume_monitor_get_type (); + } +#endif + + /* Ensure vfs in modules loaded */ + g_io_modules_ensure_loaded (GIO_MODULE_DIR); + + monitors = g_type_children (G_TYPE_NATIVE_VOLUME_MONITOR, &n_monitors); + native_type = 0; + native_prio = -1; + + for (i = 0; i < n_monitors; i++) + { + klass = G_NATIVE_VOLUME_MONITOR_CLASS (g_type_class_ref (monitors[i])); + if (klass->priority > native_prio) + { + native_prio = klass->priority; + native_type = monitors[i]; + } + + g_type_class_unref (klass); + } + + g_free (monitors); + + *ret = native_type; + + return NULL; +} + +static GType +get_native_type (void) +{ + static GOnce once_init = G_ONCE_INIT; + static GType type = G_TYPE_INVALID; + + g_once (&once_init, get_default_native_type, &type); + + return type; +} + +static void +g_union_volume_monitor_init (GUnionVolumeMonitor *union_monitor) +{ + GVolumeMonitor *monitor; + GType *monitors; + guint n_monitors; + GType native_type; + int i; + + native_type = get_native_type (); + + if (native_type != G_TYPE_INVALID) + { + monitor = g_object_new (native_type, NULL); + g_union_volume_monitor_add_monitor (union_monitor, monitor); + g_object_unref (monitor); + } + + monitors = g_type_children (G_TYPE_VOLUME_MONITOR, &n_monitors); + + for (i = 0; i < n_monitors; i++) + { + if (monitors[i] == G_TYPE_UNION_VOLUME_MONITOR || + g_type_is_a (monitors[i], G_TYPE_NATIVE_VOLUME_MONITOR)) + continue; + + monitor = g_object_new (monitors[i], NULL); + g_union_volume_monitor_add_monitor (union_monitor, monitor); + g_object_unref (monitor); + } + + g_free (monitors); +} + +static GUnionVolumeMonitor * +g_union_volume_monitor_new (void) +{ + GUnionVolumeMonitor *monitor; + + monitor = g_object_new (G_TYPE_UNION_VOLUME_MONITOR, NULL); + + return monitor; +} + + +/** + * g_volume_monitor_get: + * + * Returns: a #GVolumeMonitor. + **/ +GVolumeMonitor * +g_volume_monitor_get (void) +{ + GVolumeMonitor *vm; + + G_LOCK (the_volume_monitor); + + if (the_volume_monitor ) + vm = G_VOLUME_MONITOR (g_object_ref (the_volume_monitor)); + else + { + the_volume_monitor = g_union_volume_monitor_new (); + vm = G_VOLUME_MONITOR (the_volume_monitor); + } + + G_UNLOCK (the_volume_monitor); + + return vm; +} + +/** + * g_volume_get_for_mount_path: + * @mountpoint: a string. + * + * Returns: a #GVolume for given @mountpoint or %NULL. + **/ +GVolume * +g_volume_get_for_mount_path (const char *mountpoint) +{ + GType native_type; + GNativeVolumeMonitorClass *klass; + GVolume *volume; + + native_type = get_native_type (); + + if (native_type == G_TYPE_INVALID) + return NULL; + + volume = NULL; + + klass = G_NATIVE_VOLUME_MONITOR_CLASS (g_type_class_ref (native_type)); + if (klass->get_volume_for_mountpoint) + volume = klass->get_volume_for_mountpoint (mountpoint); + + g_type_class_unref (klass); + + return volume; +} diff --git a/gio/gunionvolumemonitor.h b/gio/gunionvolumemonitor.h new file mode 100644 index 000000000..8cb3ecb77 --- /dev/null +++ b/gio/gunionvolumemonitor.h @@ -0,0 +1,54 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#ifndef __G_UNION_VOLUME_MONITOR_H__ +#define __G_UNION_VOLUME_MONITOR_H__ + +#include <glib-object.h> +#include <gio/gvolumemonitor.h> + +G_BEGIN_DECLS + +#define G_TYPE_UNION_VOLUME_MONITOR (g_union_volume_monitor_get_type ()) +#define G_UNION_VOLUME_MONITOR(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_UNION_VOLUME_MONITOR, GUnionVolumeMonitor)) +#define G_UNION_VOLUME_MONITOR_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), G_TYPE_UNION_VOLUME_MONITOR, GUnionVolumeMonitorClass)) +#define G_IS_UNION_VOLUME_MONITOR(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_UNION_VOLUME_MONITOR)) +#define G_IS_UNION_VOLUME_MONITOR_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_UNION_VOLUME_MONITOR)) + +typedef struct _GUnionVolumeMonitor GUnionVolumeMonitor; +typedef struct _GUnionVolumeMonitorClass GUnionVolumeMonitorClass; + +struct _GUnionVolumeMonitorClass { + GVolumeMonitorClass parent_class; + +}; + +GType g_union_volume_monitor_get_type (void) G_GNUC_CONST; + +GList * g_union_volume_monitor_convert_volumes (GUnionVolumeMonitor *monitor, + GList *child_volumes); +GDrive *g_union_volume_monitor_convert_drive (GUnionVolumeMonitor *monitor, + GDrive *child_drive); + +G_END_DECLS + +#endif /* __G_UNION_VOLUME_MONITOR_H__ */ diff --git a/gio/gunixdrive.c b/gio/gunixdrive.c new file mode 100644 index 000000000..464399264 --- /dev/null +++ b/gio/gunixdrive.c @@ -0,0 +1,321 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#include <config.h> + +#include <string.h> + +#include <glib.h> +#include "gunixdrive.h" +#include "gunixvolume.h" +#include "gdriveprivate.h" +#include "gthemedicon.h" +#include "gvolumemonitor.h" + +#include "glibintl.h" + +struct _GUnixDrive { + GObject parent; + + GUnixVolume *volume; /* owned by volume monitor */ + char *name; + char *icon; + char *mountpoint; + GUnixMountType guessed_type; +}; + +static void g_unix_volume_drive_iface_init (GDriveIface *iface); + +G_DEFINE_TYPE_WITH_CODE (GUnixDrive, g_unix_drive, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (G_TYPE_DRIVE, + g_unix_volume_drive_iface_init)) + +static void +g_unix_drive_finalize (GObject *object) +{ + GUnixDrive *drive; + + drive = G_UNIX_DRIVE (object); + + if (drive->volume) + g_unix_volume_unset_drive (drive->volume, drive); + + g_free (drive->name); + g_free (drive->icon); + g_free (drive->mountpoint); + + if (G_OBJECT_CLASS (g_unix_drive_parent_class)->finalize) + (*G_OBJECT_CLASS (g_unix_drive_parent_class)->finalize) (object); +} + +static void +g_unix_drive_class_init (GUnixDriveClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->finalize = g_unix_drive_finalize; +} + +static void +g_unix_drive_init (GUnixDrive *unix_drive) +{ +} + +static char * +type_to_icon (GUnixMountType type) +{ + const char *icon_name = NULL; + + switch (type) + { + case G_UNIX_MOUNT_TYPE_HD: + icon_name = "drive-harddisk"; + break; + case G_UNIX_MOUNT_TYPE_FLOPPY: + case G_UNIX_MOUNT_TYPE_ZIP: + case G_UNIX_MOUNT_TYPE_JAZ: + case G_UNIX_MOUNT_TYPE_MEMSTICK: + icon_name = "drive-removable-media"; + break; + case G_UNIX_MOUNT_TYPE_CDROM: + icon_name = "drive-optical"; + break; + case G_UNIX_MOUNT_TYPE_NFS: + /* TODO: Would like a better icon here... */ + icon_name = "drive-removable-media"; + break; + case G_UNIX_MOUNT_TYPE_CAMERA: + icon_name = "camera-photo"; + break; + case G_UNIX_MOUNT_TYPE_IPOD: + icon_name = "multimedia-player"; + break; + case G_UNIX_MOUNT_TYPE_UNKNOWN: + default: + icon_name = "drive-removable-media"; + break; + } + return g_strdup (icon_name); +} + +/** + * g_unix_drive_new: + * @volume_monitor: a #GVolumeMonitor. + * @mountpoint: a #GUnixMountPoint. + * + * Returns: a #GUnixDrive for the given #GUnixMountPoint. + **/ +GUnixDrive * +g_unix_drive_new (GVolumeMonitor *volume_monitor, + GUnixMountPoint *mountpoint) +{ + GUnixDrive *drive; + + if (!(g_unix_mount_point_is_user_mountable (mountpoint) || + g_str_has_prefix (g_unix_mount_point_get_device_path (mountpoint), "/vol/")) || + g_unix_mount_point_is_loopback (mountpoint)) + return NULL; + + drive = g_object_new (G_TYPE_UNIX_DRIVE, NULL); + + drive->guessed_type = g_unix_mount_point_guess_type (mountpoint); + + /* TODO: */ + drive->mountpoint = g_strdup (g_unix_mount_point_get_mount_path (mountpoint)); + drive->icon = type_to_icon (drive->guessed_type); + drive->name = g_strdup (_("Unknown drive")); + + return drive; +} + +/** + * g_unix_drive_disconnected: + * @drive: + * + **/ +void +g_unix_drive_disconnected (GUnixDrive *drive) +{ + if (drive->volume) + { + g_unix_volume_unset_drive (drive->volume, drive); + drive->volume = NULL; + } +} + +/** + * g_unix_drive_set_volume: + * @drive: + * @volume: + * + **/ +void +g_unix_drive_set_volume (GUnixDrive *drive, + GUnixVolume *volume) +{ + if (drive->volume == volume) + return; + + if (drive->volume) + g_unix_volume_unset_drive (drive->volume, drive); + + drive->volume = volume; + + /* TODO: Emit changed in idle to avoid locking issues */ + g_signal_emit_by_name (drive, "changed"); +} + +/** + * g_unix_drive_unset_volume: + * @drive: + * @volume: + * + **/ +void +g_unix_drive_unset_volume (GUnixDrive *drive, + GUnixVolume *volume) +{ + if (drive->volume == volume) + { + drive->volume = NULL; + /* TODO: Emit changed in idle to avoid locking issues */ + g_signal_emit_by_name (drive, "changed"); + } +} + +static GIcon * +g_unix_drive_get_icon (GDrive *drive) +{ + GUnixDrive *unix_drive = G_UNIX_DRIVE (drive); + + return g_themed_icon_new (unix_drive->icon); +} + +static char * +g_unix_drive_get_name (GDrive *drive) +{ + GUnixDrive *unix_drive = G_UNIX_DRIVE (drive); + + return g_strdup (unix_drive->name); +} + +static gboolean +g_unix_drive_is_automounted (GDrive *drive) +{ + /* TODO */ + return FALSE; +} + +static gboolean +g_unix_drive_can_mount (GDrive *drive) +{ + /* TODO */ + return TRUE; +} + +static gboolean +g_unix_drive_can_eject (GDrive *drive) +{ + /* TODO */ + return FALSE; +} + +static GList * +g_unix_drive_get_volumes (GDrive *drive) +{ + GList *l; + GUnixDrive *unix_drive = G_UNIX_DRIVE (drive); + + l = NULL; + if (unix_drive->volume) + l = g_list_prepend (l, g_object_ref (unix_drive->volume)); + + return l; +} + +static gboolean +g_unix_drive_has_volumes (GDrive *drive) +{ + GUnixDrive *unix_drive = G_UNIX_DRIVE (drive); + + return unix_drive->volume != NULL; +} + + +gboolean +g_unix_drive_has_mountpoint (GUnixDrive *drive, + const char *mountpoint) +{ + return strcmp (drive->mountpoint, mountpoint) == 0; +} + +static void +g_unix_drive_mount (GDrive *drive, + GMountOperation *mount_operation, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + /* TODO */ +} + + +static gboolean +g_unix_drive_mount_finish (GDrive *drive, + GAsyncResult *result, + GError **error) +{ + return TRUE; +} + +static void +g_unix_drive_eject (GDrive *drive, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + /* TODO */ +} + +static gboolean +g_unix_drive_eject_finish (GDrive *drive, + GAsyncResult *result, + GError **error) +{ + return TRUE; +} + +static void +g_unix_volume_drive_iface_init (GDriveIface *iface) +{ + iface->get_name = g_unix_drive_get_name; + iface->get_icon = g_unix_drive_get_icon; + iface->has_volumes = g_unix_drive_has_volumes; + iface->get_volumes = g_unix_drive_get_volumes; + iface->is_automounted = g_unix_drive_is_automounted; + iface->can_mount = g_unix_drive_can_mount; + iface->can_eject = g_unix_drive_can_eject; + iface->mount = g_unix_drive_mount; + iface->mount_finish = g_unix_drive_mount_finish; + iface->eject = g_unix_drive_eject; + iface->eject_finish = g_unix_drive_eject_finish; +} diff --git a/gio/gunixdrive.h b/gio/gunixdrive.h new file mode 100644 index 000000000..869ed8451 --- /dev/null +++ b/gio/gunixdrive.h @@ -0,0 +1,59 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#ifndef __G_UNIX_DRIVE_H__ +#define __G_UNIX_DRIVE_H__ + +#include <glib-object.h> +#include <gio/gdrive.h> +#include <gio/gunixmounts.h> +#include <gio/gunixvolumemonitor.h> + +G_BEGIN_DECLS + +#define G_TYPE_UNIX_DRIVE (g_unix_drive_get_type ()) +#define G_UNIX_DRIVE(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_UNIX_DRIVE, GUnixDrive)) +#define G_UNIX_DRIVE_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), G_TYPE_UNIX_DRIVE, GUnixDriveClass)) +#define G_IS_UNIX_DRIVE(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_UNIX_DRIVE)) +#define G_IS_UNIX_DRIVE_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_UNIX_DRIVE)) + +typedef struct _GUnixDriveClass GUnixDriveClass; + +struct _GUnixDriveClass { + GObjectClass parent_class; +}; + +GType g_unix_drive_get_type (void) G_GNUC_CONST; + +GUnixDrive *g_unix_drive_new (GVolumeMonitor *volume_monitor, + GUnixMountPoint *mountpoint); +gboolean g_unix_drive_has_mountpoint (GUnixDrive *drive, + const char *mountpoint); +void g_unix_drive_set_volume (GUnixDrive *drive, + GUnixVolume *volume); +void g_unix_drive_unset_volume (GUnixDrive *drive, + GUnixVolume *volume); +void g_unix_drive_disconnected (GUnixDrive *drive); + +G_END_DECLS + +#endif /* __G_UNIX_DRIVE_H__ */ diff --git a/gio/gunixmounts.c b/gio/gunixmounts.c new file mode 100644 index 000000000..da23bd4c0 --- /dev/null +++ b/gio/gunixmounts.c @@ -0,0 +1,1515 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#include <config.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/wait.h> +#ifndef HAVE_SYSCTLBYNAME +#ifdef HAVE_SYS_PARAM_H +#include <sys/param.h> +#endif +#ifdef HAVE_SYS_POLL_H +#include <sys/poll.h> +#endif +#endif +#ifdef HAVE_POLL_H +#include <poll.h> +#endif +#include <stdio.h> +#include <unistd.h> +#include <sys/time.h> +#include <errno.h> +#include <string.h> +#include <signal.h> + +#include "gunixmounts.h" +#include "gfile.h" +#include "gfilemonitor.h" + +struct _GUnixMount { + char *mount_path; + char *device_path; + char *filesystem_type; + gboolean is_read_only; + gboolean is_system_internal; +}; + +struct _GUnixMountPoint { + char *mount_path; + char *device_path; + char *filesystem_type; + gboolean is_read_only; + gboolean is_user_mountable; + gboolean is_loopback; +}; + +enum { + MOUNTS_CHANGED, + MOUNTPOINTS_CHANGED, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL]; + +struct _GUnixMountMonitor { + GObject parent; + + GFileMonitor *fstab_monitor; + GFileMonitor *mtab_monitor; +}; + +struct _GUnixMountMonitorClass { + GObjectClass parent_class; +}; + +static GUnixMountMonitor *the_mount_monitor = NULL; + +static GList *_g_get_unix_mounts (void); +static GList *_g_get_unix_mount_points (void); + +G_DEFINE_TYPE (GUnixMountMonitor, g_unix_mount_monitor, G_TYPE_OBJECT); + +#define MOUNT_POLL_INTERVAL 4000 + +#ifdef HAVE_SYS_MNTTAB_H +#define MNTOPT_RO "ro" +#endif + +#ifdef HAVE_MNTENT_H +#include <mntent.h> +#elif defined (HAVE_SYS_MNTTAB_H) +#include <sys/mnttab.h> +#endif + +#ifdef HAVE_SYS_VFSTAB_H +#include <sys/vfstab.h> +#endif + +#if defined(HAVE_SYS_MNTCTL_H) && defined(HAVE_SYS_VMOUNT_H) && defined(HAVE_SYS_VFS_H) +#include <sys/mntctl.h> +#include <sys/vfs.h> +#include <sys/vmount.h> +#include <fshelp.h> +#endif + +#if defined(HAVE_GETMNTINFO) && defined(HAVE_FSTAB_H) && defined(HAVE_SYS_MOUNT_H) +#include <sys/ucred.h> +#include <sys/mount.h> +#include <fstab.h> +#ifdef HAVE_SYS_SYSCTL_H +#include <sys/sysctl.h> +#endif +#endif + +#ifndef HAVE_SETMNTENT +#define setmntent(f,m) fopen(f,m) +#endif +#ifndef HAVE_ENDMNTENT +#define endmntent(f) fclose(f) +#endif + +static gboolean +is_in (const char *value, const char *set[]) +{ + int i; + for (i = 0; set[i] != NULL; i++) + { + if (strcmp (set[i], value) == 0) + return TRUE; + } + return FALSE; +} + +static gboolean +guess_system_internal (const char *mountpoint, + const char *fs, + const char *device) +{ + const char *ignore_fs[] = { + "auto", + "autofs", + "devfs", + "devpts", + "kernfs", + "linprocfs", + "proc", + "procfs", + "ptyfs", + "rootfs", + "selinuxfs", + "sysfs", + "tmpfs", + "usbfs", + "nfsd", + NULL + }; + const char *ignore_devices[] = { + "none", + "sunrpc", + "devpts", + "nfsd", + "/dev/loop", + "/dev/vn", + NULL + }; + const char *ignore_mountpoints[] = { + /* Includes all FHS 2.3 toplevel dirs */ + "/bin", + "/boot", + "/dev", + "/etc", + "/home", + "/lib", + "/lib64", + "/media", + "/mnt", + "/opt", + "/root", + "/sbin", + "/srv", + "/tmp", + "/usr", + "/var", + "/proc", + "/sbin", + "/net", + NULL + }; + + if (is_in (fs, ignore_fs)) + return TRUE; + + if (is_in (device, ignore_devices)) + return TRUE; + + if (is_in (mountpoint, ignore_mountpoints)) + return TRUE; + + if (g_str_has_prefix (mountpoint, "/dev") || + g_str_has_prefix (mountpoint, "/proc") || + g_str_has_prefix (mountpoint, "/sys")) + return TRUE; + + if (strstr (mountpoint, "/.gvfs") != NULL) + return TRUE; + + return FALSE; +} + +#ifdef HAVE_MNTENT_H + +static char * +get_mtab_read_file (void) +{ +#ifdef _PATH_MOUNTED +# ifdef __linux__ + return "/proc/mounts"; +# else + return _PATH_MOUNTED; +# endif +#else + return "/etc/mtab"; +#endif +} + +static char * +get_mtab_monitor_file (void) +{ +#ifdef _PATH_MOUNTED + return _PATH_MOUNTED; +#else + return "/etc/mtab"; +#endif +} + +G_LOCK_DEFINE_STATIC(getmntent); + +static GList * +_g_get_unix_mounts () +{ + struct mntent *mntent; + FILE *file; + char *read_file; + GUnixMount *mount_entry; + GHashTable *mounts_hash; + GList *return_list; + + read_file = get_mtab_read_file (); + + file = setmntent (read_file, "r"); + if (file == NULL) + return NULL; + + return_list = NULL; + + mounts_hash = g_hash_table_new (g_str_hash, g_str_equal); + + G_LOCK (getmntent); + while ((mntent = getmntent (file)) != NULL) + { + /* ignore any mnt_fsname that is repeated and begins with a '/' + * + * We do this to avoid being fooled by --bind mounts, since + * these have the same device as the location they bind to. + * Its not an ideal solution to the problem, but its likely that + * the most important mountpoint is first and the --bind ones after + * that aren't as important. So it should work. + * + * The '/' is to handle procfs, tmpfs and other no device mounts. + */ + if (mntent->mnt_fsname != NULL && + mntent->mnt_fsname[0] == '/' && + g_hash_table_lookup (mounts_hash, mntent->mnt_fsname)) + continue; + + mount_entry = g_new0 (GUnixMount, 1); + mount_entry->mount_path = g_strdup (mntent->mnt_dir); + mount_entry->device_path = g_strdup (mntent->mnt_fsname); + mount_entry->filesystem_type = g_strdup (mntent->mnt_type); + +#if defined (HAVE_HASMNTOPT) + if (hasmntopt (mntent, MNTOPT_RO) != NULL) + mount_entry->is_read_only = TRUE; +#endif + + mount_entry->is_system_internal = + guess_system_internal (mount_entry->mount_path, + mount_entry->filesystem_type, + mount_entry->device_path); + + g_hash_table_insert (mounts_hash, + mount_entry->device_path, + mount_entry->device_path); + + return_list = g_list_prepend (return_list, mount_entry); + } + g_hash_table_destroy (mounts_hash); + + endmntent (file); + + G_UNLOCK (getmntent); + + return g_list_reverse (return_list); +} + +#elif defined (HAVE_SYS_MNTTAB_H) + +G_LOCK_DEFINE_STATIC(getmntent); + +static char * +get_mtab_read_file (void) +{ +#ifdef _PATH_MOUNTED + return _PATH_MOUNTED; +#else + return "/etc/mnttab"; +#endif +} + +static char * +get_mtab_monitor_file (void) +{ + return get_mtab_read_file (); +} + +static GList * +_g_get_unix_mounts (void) +{ + struct mnttab mntent; + FILE *file; + char *read_file; + GUnixMount *mount_entry; + GList *return_list; + + read_file = get_mtab_read_file (); + + file = setmntent (read_file, "r"); + if (file == NULL) + return NULL; + + return_list = NULL; + + G_LOCK (getmntent); + while (! getmntent (file, &mntent)) + { + mount_entry = g_new0 (GUnixMount, 1); + + mount_entry->mount_path = g_strdup (mntent.mnt_mountp); + mount_entry->device_path = g_strdup (mntent.mnt_special); + mount_entry->filesystem_type = g_strdup (mntent.mnt_fstype); + +#if defined (HAVE_HASMNTOPT) + if (hasmntopt (&mntent, MNTOPT_RO) != NULL) + mount_entry->is_read_only = TRUE; +#endif + + mount_entry->is_system_internal = + guess_system_internal (mount_entry->mount_path, + mount_entry->filesystem_type, + mount_entry->device_path); + + return_list = g_list_prepend (return_list, mount_entry); + } + + endmntent (file); + + G_UNLOCK (getmntent); + + return g_list_reverse (return_list); +} + +#elif defined(HAVE_SYS_MNTCTL_H) && defined(HAVE_SYS_VMOUNT_H) && defined(HAVE_SYS_VFS_H) + +static char * +get_mtab_monitor_file (void) +{ + return NULL; +} + +static GList * +_g_get_unix_mounts (void) +{ + struct vfs_ent *fs_info; + struct vmount *vmount_info; + int vmount_number; + unsigned int vmount_size; + int current; + GList *return_list; + + if (mntctl (MCTL_QUERY, sizeof (vmount_size), &vmount_size) != 0) + { + g_warning ("Unable to know the number of mounted volumes\n"); + + return NULL; + } + + vmount_info = (struct vmount*)g_malloc (vmount_size); + + vmount_number = mntctl (MCTL_QUERY, vmount_size, vmount_info); + + if (vmount_info->vmt_revision != VMT_REVISION) + g_warning ("Bad vmount structure revision number, want %d, got %d\n", VMT_REVISION, vmount_info->vmt_revision); + + if (vmount_number < 0) + { + g_warning ("Unable to recover mounted volumes information\n"); + + g_free (vmount_info); + return NULL; + } + + return_list = NULL; + while (vmount_number > 0) + { + mount_entry = g_new0 (GUnixMount, 1); + + mount_entry->device_path = g_strdup (vmt2dataptr (vmount_info, VMT_OBJECT)); + mount_entry->mount_path = g_strdup (vmt2dataptr (vmount_info, VMT_STUB)); + /* is_removable = (vmount_info->vmt_flags & MNT_REMOVABLE) ? 1 : 0; */ + mount_entry->is_read_only = (vmount_info->vmt_flags & MNT_READONLY) ? 1 : 0; + + fs_info = getvfsbytype (vmount_info->vmt_gfstype); + + if (fs_info == NULL) + mount_entry->filesystem_type = g_strdup ("unknown"); + else + mount_entry->filesystem_type = g_strdup (fs_info->vfsent_name); + + mount_entry->is_system_internal = + guess_system_internal (mount_entry->mount_path, + mount_entry->filesystem_type, + mount_entry->device_path); + + return_list = g_list_prepend (return_list, mount_entry); + + vmount_info = (struct vmount *)( (char*)vmount_info + + vmount_info->vmt_length); + vmount_number--; + } + + + g_free (vmount_info); + + return g_list_reverse (return_list); +} + +#elif defined(HAVE_GETMNTINFO) && defined(HAVE_FSTAB_H) && defined(HAVE_SYS_MOUNT_H) + +static char * +get_mtab_monitor_file (void) +{ + return NULL; +} + +static GList * +_g_get_unix_mounts (void) +{ + struct statfs *mntent = NULL; + int num_mounts, i; + GUnixMount *mount_entry; + GList *return_list; + + /* Pass MNT_NOWAIT to avoid blocking trying to update NFS mounts. */ + if ((num_mounts = getmntinfo (&mntent, MNT_NOWAIT)) == 0) + return NULL; + + return_list = NULL; + + for (i = 0; i < num_mounts; i++) + { + mount_entry = g_new0 (GUnixMount, 1); + + mount_entry->mount_path = g_strdup (mntent[i].f_mntonname); + mount_entry->device_path = g_strdup (mntent[i].f_mntfromname); + mount_entry->filesystem_type = g_strdup (mntent[i].f_fstypename); + if (mntent[i].f_flags & MNT_RDONLY) + mount_entry->is_read_only = TRUE; + + mount_entry->is_system_internal = + guess_system_internal (mount_entry->mount_path, + mount_entry->filesystem_type, + mount_entry->device_path); + + return_list = g_list_prepend (return_list, mount_entry); + } + + return g_list_reverse (return_list); +} +#else +#error No _g_get_unix_mounts() implementation for system +#endif + +/* _g_get_unix_mount_points(): + * read the fstab. + * don't return swap and ignore mounts. + */ + +static char * +get_fstab_file (void) +{ +#if defined(HAVE_SYS_MNTCTL_H) && defined(HAVE_SYS_VMOUNT_H) && defined(HAVE_SYS_VFS_H) + /* AIX */ + return "/etc/filesystems"; +#elif defined(_PATH_MNTTAB) + return _PATH_MNTTAB; +#elif defined(VFSTAB) + return VFSTAB; +#else + return "/etc/fstab"; +#endif +} + +#ifdef HAVE_MNTENT_H +static GList * +_g_get_unix_mount_points (void) +{ + struct mntent *mntent; + FILE *file; + char *read_file; + GUnixMountPoint *mount_entry; + GList *return_list; + + read_file = get_fstab_file (); + + file = setmntent (read_file, "r"); + if (file == NULL) + return NULL; + + return_list = NULL; + + G_LOCK (getmntent); + while ((mntent = getmntent (file)) != NULL) + { + if ((strcmp (mntent->mnt_dir, "ignore") == 0) || + (strcmp (mntent->mnt_dir, "swap") == 0)) + continue; + + mount_entry = g_new0 (GUnixMountPoint, 1); + mount_entry->mount_path = g_strdup (mntent->mnt_dir); + mount_entry->device_path = g_strdup (mntent->mnt_fsname); + mount_entry->filesystem_type = g_strdup (mntent->mnt_type); + +#ifdef HAVE_HASMNTOPT + if (hasmntopt (mntent, MNTOPT_RO) != NULL) + mount_entry->is_read_only = TRUE; + + if (hasmntopt (mntent, "loop") != NULL) + mount_entry->is_loopback = TRUE; + +#endif + + if ((mntent->mnt_type != NULL && strcmp ("supermount", mntent->mnt_type) == 0) +#ifdef HAVE_HASMNTOPT + || (hasmntopt (mntent, "user") != NULL + && hasmntopt (mntent, "user") != hasmntopt (mntent, "user_xattr")) + || hasmntopt (mntent, "pamconsole") != NULL + || hasmntopt (mntent, "users") != NULL + || hasmntopt (mntent, "owner") != NULL +#endif + ) + mount_entry->is_user_mountable = TRUE; + + return_list = g_list_prepend (return_list, mount_entry); + } + + endmntent (file); + G_UNLOCK (getmntent); + + return g_list_reverse (return_list); +} + +#elif defined (HAVE_SYS_MNTTAB_H) + +static GList * +_g_get_unix_mount_points (void) +{ + struct mnttab mntent; + FILE *file; + char *read_file; + GUnixMountPoint *mount_entry; + GList *return_list; + + read_file = get_fstab_file (); + + file = setmntent (read_file, "r"); + if (file == NULL) + return NULL; + + return_list = NULL; + + G_LOCK (getmntent); + while (! getmntent (file, &mntent)) + { + if ((strcmp (mntent.mnt_mountp, "ignore") == 0) || + (strcmp (mntent.mnt_mountp, "swap") == 0)) + continue; + + mount_entry = g_new0 (GUnixMountPoint, 1); + + mount_entry->mount_path = g_strdup (mntent.mnt_mountp); + mount_entry->device_path = g_strdup (mntent.mnt_special); + mount_entry->filesystem_type = g_strdup (mntent.mnt_fstype); + +#ifdef HAVE_HASMNTOPT + if (hasmntopt (&mntent, MNTOPT_RO) != NULL) + mount_entry->is_read_only = TRUE; + + if (hasmntopt (&mntent, "lofs") != NULL) + mount_entry->is_loopback = TRUE; +#endif + + if ((mntent.mnt_fstype != NULL) +#ifdef HAVE_HASMNTOPT + || (hasmntopt (&mntent, "user") != NULL + && hasmntopt (&mntent, "user") != hasmntopt (&mntent, "user_xattr")) + || hasmntopt (&mntent, "pamconsole") != NULL + || hasmntopt (&mntent, "users") != NULL + || hasmntopt (&mntent, "owner") != NULL +#endif + ) + mount_entry->is_user_mountable = TRUE; + + + return_list = g_list_prepend (return_list, mount_entry); + } + + endmntent (file); + G_UNLOCK (getmntent); + + return g_list_reverse (return_list); +} +#elif defined(HAVE_SYS_MNTCTL_H) && defined(HAVE_SYS_VMOUNT_H) && defined(HAVE_SYS_VFS_H) + +/* functions to parse /etc/filesystems on aix */ + +/* read character, ignoring comments (begin with '*', end with '\n' */ +static int +aix_fs_getc (FILE *fd) +{ + int c; + + while ((c = getc (fd)) == '*') + { + while (((c = getc (fd)) != '\n') && (c != EOF)) + ; + } +} + +/* eat all continuous spaces in a file */ +static int +aix_fs_ignorespace (FILE *fd) +{ + int c; + + while ((c = aix_fs_getc (fd)) != EOF) + { + if (!g_ascii_isspace (c)) + { + ungetc (c,fd); + return c; + } + } + + return EOF; +} + +/* read one word from file */ +static int +aix_fs_getword (FILE *fd, char *word) +{ + int c; + + aix_fs_ignorespace (fd); + + while (((c = aix_fs_getc (fd)) != EOF) && !g_ascii_isspace (c)) + { + if (c == '"') + { + while (((c = aix_fs_getc (fd)) != EOF) && (c != '"')) + *word++ = c; + else + *word++ = c; + } + } + *word = 0; + + return c; +} + +typedef struct { + char mnt_mount[PATH_MAX]; + char mnt_special[PATH_MAX]; + char mnt_fstype[16]; + char mnt_options[128]; +} AixMountTableEntry; + +/* read mount points properties */ +static int +aix_fs_get (FILE *fd, AixMountTableEntry *prop) +{ + static char word[PATH_MAX] = { 0 }; + char value[PATH_MAX]; + + /* read stanza */ + if (word[0] == 0) + { + if (aix_fs_getword (fd, word) == EOF) + return EOF; + } + + word[strlen(word) - 1] = 0; + strcpy (prop->mnt_mount, word); + + /* read attributes and value */ + + while (aix_fs_getword (fd, word) != EOF) + { + /* test if is attribute or new stanza */ + if (word[strlen(word) - 1] == ':') + return 0; + + /* read "=" */ + aix_fs_getword (fd, value); + + /* read value */ + aix_fs_getword (fd, value); + + if (strcmp (word, "dev") == 0) + strcpy (prop->mnt_special, value); + else if (strcmp (word, "vfs") == 0) + strcpy (prop->mnt_fstype, value); + else if (strcmp (word, "options") == 0) + strcpy(prop->mnt_options, value); + } + + return 0; +} + +static GList * +_g_get_unix_mount_points (void) +{ + struct mntent *mntent; + FILE *file; + char *read_file; + GUnixMountPoint *mount_entry; + AixMountTableEntry mntent; + GList *return_list; + + read_file = get_fstab_file (); + + file = setmntent (read_file, "r"); + if (file == NULL) + return NULL; + + return_list = NULL; + + while (!aix_fs_get (file, &mntent)) + { + if (strcmp ("cdrfs", mntent.mnt_fstype) == 0) + { + mount_entry = g_new0 (GUnixMountPoint, 1); + + + mount_entry->mount_path = g_strdup (mntent.mnt_mount); + mount_entry->device_path = g_strdup (mntent.mnt_special); + mount_entry->filesystem_type = g_strdup (mntent.mnt_fstype); + mount_entry->is_read_only = TRUE; + mount_entry->is_user_mountable = TRUE; + + return_list = g_list_prepend (return_list, mount_entry); + } + } + + endmntent (file); + + return g_list_reverse (return_list); +} + +#elif defined(HAVE_GETMNTINFO) && defined(HAVE_FSTAB_H) && defined(HAVE_SYS_MOUNT_H) + +static GList * +_g_get_unix_mount_points (void) +{ + struct fstab *fstab = NULL; + GUnixMountPoint *mount_entry; + GList *return_list; +#ifdef HAVE_SYS_SYSCTL_H + int usermnt = 0; + size_t len = sizeof(usermnt); + struct stat sb; +#endif + + if (!setfsent ()) + return NULL; + + return_list = NULL; + +#ifdef HAVE_SYS_SYSCTL_H +#if defined(HAVE_SYSCTLBYNAME) + sysctlbyname ("vfs.usermount", &usermnt, &len, NULL, 0); +#elif defined(CTL_VFS) && defined(VFS_USERMOUNT) + { + int mib[2]; + + mib[0] = CTL_VFS; + mib[1] = VFS_USERMOUNT; + sysctl (mib, 2, &usermnt, &len, NULL, 0); + } +#elif defined(CTL_KERN) && defined(KERN_USERMOUNT) + { + int mib[2]; + + mib[0] = CTL_KERN; + mib[1] = KERN_USERMOUNT; + sysctl (mib, 2, &usermnt, &len, NULL, 0); + } +#endif +#endif + + while ((fstab = getfsent ()) != NULL) + { + if (strcmp (fstab->fs_vfstype, "swap") == 0) + continue; + + mount_entry = g_new0 (GUnixMountPoint, 1); + + mount_entry->mount_path = g_strdup (fstab->fs_file); + mount_entry->device_path = g_strdup (fstab->fs_spec); + mount_entry->filesystem_type = g_strdup (fstab->fs_vfstype); + + if (strcmp (fstab->fs_type, "ro") == 0) + mount_entry->is_read_only = TRUE; + +#ifdef HAVE_SYS_SYSCTL_H + if (usermnt != 0) + { + uid_t uid = getuid (); + if (stat (fstab->fs_file, &sb) == 0) + { + if (uid == 0 || sb.st_uid == uid) + mount_entry->is_user_mountable = TRUE; + } + } +#endif + + return_list = g_list_prepend (return_list, mount_entry); + } + + endfsent (); + + return g_list_reverse (return_list); +} +#else +#error No g_get_mount_table() implementation for system +#endif + +static guint64 +get_mounts_timestamp (void) +{ + const char *monitor_file; + struct stat buf; + + monitor_file = get_mtab_monitor_file (); + if (monitor_file) + { + if (stat (monitor_file, &buf) == 0) + return (guint64)buf.st_mtime; + } + return 0; +} + +static guint64 +get_mount_points_timestamp (void) +{ + const char *monitor_file; + struct stat buf; + + monitor_file = get_fstab_file (); + if (monitor_file) + { + if (stat (monitor_file, &buf) == 0) + return (guint64)buf.st_mtime; + } + return 0; +} + +/** + * g_get_unix_mounts: + * @time_read: guint64 to contain a timestamp. + * + * Returns a #GList of the UNIX mounts. If @time_read + * is set, it will be filled with the mount timestamp. + **/ +GList * +g_get_unix_mounts (guint64 *time_read) +{ + if (time_read) + *time_read = get_mounts_timestamp (); + + return _g_get_unix_mounts (); +} + +/** + * g_get_unix_mount_at: + * @mount_path: path to mount. + * @time_read: guint64 to contain a timestamp. + * + * Returns a #GUnixMount. If @time_read + * is set, it will be filled with the mount timestamp. + **/ +GUnixMount * +g_get_unix_mount_at (const char *mount_path, + guint64 *time_read) +{ + GList *mounts, *l; + GUnixMount *mount_entry, *found; + + mounts = g_get_unix_mounts (time_read); + + found = NULL; + for (l = mounts; l != NULL; l = l->next) + { + mount_entry = l->data; + + if (strcmp (mount_path, mount_entry->mount_path) == 0) + found = mount_entry; + else + g_unix_mount_free (mount_entry); + + } + g_list_free (mounts); + + return found; +} + +/** + * g_get_unix_mount_points: + * @time_read: guint64 to contain a timestamp. + * + * Returns a #GList of the UNIX mountpoints. If @time_read + * is set, it will be filled with the mount timestamp. + **/ +GList * +g_get_unix_mount_points (guint64 *time_read) +{ + if (time_read) + *time_read = get_mount_points_timestamp (); + + return _g_get_unix_mount_points (); +} + +/** + * g_unix_mounts_change_since: + * @time: guint64 to contain a timestamp. + * + * Returns %TRUE if the mounts have changed since @time. + **/ +gboolean +g_unix_mounts_changed_since (guint64 time) +{ + return get_mounts_timestamp () != time; +} + +/** + * g_unix_mount_points_change_since: + * @time: guint64 to contain a timestamp. + * + * Returns %TRUE if the mount points have changed since @time. + **/ +gboolean +g_unix_mount_points_changed_since (guint64 time) +{ + return get_mount_points_timestamp () != time; +} + +static void +g_unix_mount_monitor_finalize (GObject *object) +{ + GUnixMountMonitor *monitor; + + monitor = G_UNIX_MOUNT_MONITOR (object); + + if (monitor->fstab_monitor) + { + g_file_monitor_cancel (monitor->fstab_monitor); + g_object_unref (monitor->fstab_monitor); + } + + if (monitor->mtab_monitor) + { + g_file_monitor_cancel (monitor->mtab_monitor); + g_object_unref (monitor->mtab_monitor); + } + + the_mount_monitor = NULL; + + if (G_OBJECT_CLASS (g_unix_mount_monitor_parent_class)->finalize) + (*G_OBJECT_CLASS (g_unix_mount_monitor_parent_class)->finalize) (object); +} + + +static void +g_unix_mount_monitor_class_init (GUnixMountMonitorClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->finalize = g_unix_mount_monitor_finalize; + + signals[MOUNTS_CHANGED] = + g_signal_new ("mounts_changed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + signals[MOUNTPOINTS_CHANGED] = + g_signal_new ("mountpoints_changed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); +} + +static void +fstab_file_changed (GFileMonitor* monitor, + GFile* file, + GFile* other_file, + GFileMonitorEvent event_type, + gpointer user_data) +{ + GUnixMountMonitor *mount_monitor; + + if (event_type != G_FILE_MONITOR_EVENT_CHANGED && + event_type != G_FILE_MONITOR_EVENT_CREATED && + event_type != G_FILE_MONITOR_EVENT_DELETED) + return; + + mount_monitor = user_data; + g_signal_emit (mount_monitor, signals[MOUNTPOINTS_CHANGED], 0); +} + +static void +mtab_file_changed (GFileMonitor* monitor, + GFile* file, + GFile* other_file, + GFileMonitorEvent event_type, + gpointer user_data) +{ + GUnixMountMonitor *mount_monitor; + + if (event_type != G_FILE_MONITOR_EVENT_CHANGED && + event_type != G_FILE_MONITOR_EVENT_CREATED && + event_type != G_FILE_MONITOR_EVENT_DELETED) + return; + + mount_monitor = user_data; + g_signal_emit (mount_monitor, signals[MOUNTS_CHANGED], 0); +} + +static void +g_unix_mount_monitor_init (GUnixMountMonitor *monitor) +{ + GFile *file; + + if (get_fstab_file () != NULL) + { + file = g_file_new_for_path (get_fstab_file ()); + monitor->fstab_monitor = g_file_monitor_file (file, 0, NULL); + g_object_unref (file); + + g_signal_connect (monitor->fstab_monitor, "changed", (GCallback)fstab_file_changed, monitor); + } + + if (get_mtab_monitor_file () != NULL) + { + file = g_file_new_for_path (get_mtab_monitor_file ()); + monitor->mtab_monitor = g_file_monitor_file (file, 0, NULL); + g_object_unref (file); + + g_signal_connect (monitor->mtab_monitor, "changed", (GCallback)mtab_file_changed, monitor); + } +} + +/** + * g_unix_mount_monitor_new: + * + * Returns a new #GUnixMountMonitor. + **/ +GUnixMountMonitor * +g_unix_mount_monitor_new (void) +{ + if (the_mount_monitor == NULL) + { + the_mount_monitor = g_object_new (G_TYPE_UNIX_MOUNT_MONITOR, NULL); + return the_mount_monitor; + } + + return g_object_ref (the_mount_monitor); +} + +/** + * g_unix_mount_free: + * @mount_entry: a #GUnixMount. + * + **/ +void +g_unix_mount_free (GUnixMount *mount_entry) +{ + g_return_if_fail (mount_entry != NULL); + + g_free (mount_entry->mount_path); + g_free (mount_entry->device_path); + g_free (mount_entry->filesystem_type); + g_free (mount_entry); +} + +/** + * g_unix_mount_point_free: + * @mount_point: + * + **/ +void +g_unix_mount_point_free (GUnixMountPoint *mount_point) +{ + g_return_if_fail (mount_point != NULL); + + g_free (mount_point->mount_path); + g_free (mount_point->device_path); + g_free (mount_point->filesystem_type); + g_free (mount_point); +} + +static int +strcmp_null (const char *str1, + const char *str2) +{ + if (str1 == str2) + return 0; + if (str1 == NULL && str2 != NULL) + return -1; + if (str1 != NULL && str2 == NULL) + return 1; + return strcmp (str1, str2); +} + +/** + * g_unix_mount_compare: + * @mount1: first #GUnixMount to compare. + * @mount2: second #GUnixMount to compare. + * + * Returns 1, 0 or -1 if @mount1 is greater than, equal to, + * or less than @mount2, respectively. + **/ +gint +g_unix_mount_compare (GUnixMount *mount1, + GUnixMount *mount2) +{ + int res; + + g_return_val_if_fail (mount1 != NULL && mount2 != NULL, 0); + + res = strcmp_null (mount1->mount_path, mount2->mount_path); + if (res != 0) + return res; + + res = strcmp_null (mount1->device_path, mount2->device_path); + if (res != 0) + return res; + + res = strcmp_null (mount1->filesystem_type, mount2->filesystem_type); + if (res != 0) + return res; + + res = mount1->is_read_only - mount2->is_read_only; + if (res != 0) + return res; + + return 0; +} + +/** + * g_unix_mount_get_mount_path: + * @mount_entry: input #GUnixMount to get the mount path for. + * + * Returns the mount path for @mount_entry. + * + **/ +const char * +g_unix_mount_get_mount_path (GUnixMount *mount_entry) +{ + g_return_val_if_fail (mount_entry != NULL, NULL); + + return mount_entry->mount_path; +} + +/** + * g_unix_mount_get_device_path: + * @mount_entry: a #GUnixMount. + * + **/ +const char * +g_unix_mount_get_device_path (GUnixMount *mount_entry) +{ + g_return_val_if_fail (mount_entry != NULL, NULL); + + return mount_entry->device_path; +} + +/** + * g_unix_mount_get_fs_type: + * @mount_entry: a #GUnixMount. + * + **/ +const char * +g_unix_mount_get_fs_type (GUnixMount *mount_entry) +{ + g_return_val_if_fail (mount_entry != NULL, NULL); + + return mount_entry->filesystem_type; +} + +/** + * g_unix_mount_is_readonly: + * @mount_entry: a #GUnixMount. + * + * Returns %TRUE if @mount_entry is read only. + * + **/ +gboolean +g_unix_mount_is_readonly (GUnixMount *mount_entry) +{ + g_return_val_if_fail (mount_entry != NULL, FALSE); + + return mount_entry->is_read_only; +} + +/** + * g_unix_mount_is_system_internal: + * @mount_entry: a #GUnixMount. + * + **/ +gboolean +g_unix_mount_is_system_internal (GUnixMount *mount_entry) +{ + g_return_val_if_fail (mount_entry != NULL, FALSE); + + return mount_entry->is_system_internal; +} + +/** + * g_unix_mount_point_compare: + * @mount1: a #GUnixMount. + * @mount2: a #GUnixMount. + * + * Returns 1, 0 or -1 if @mount1 is greater than, equal to, + * or less than @mount2, respectively. + **/ +gint +g_unix_mount_point_compare (GUnixMountPoint *mount1, + GUnixMountPoint *mount2) +{ + int res; + + g_return_val_if_fail (mount1 != NULL && mount2 != NULL, 0); + + res = strcmp_null (mount1->mount_path, mount2->mount_path); + if (res != 0) + return res; + + res = strcmp_null (mount1->device_path, mount2->device_path); + if (res != 0) + return res; + + res = strcmp_null (mount1->filesystem_type, mount2->filesystem_type); + if (res != 0) + return res; + + res = mount1->is_read_only - mount2->is_read_only; + if (res != 0) + return res; + + res = mount1->is_user_mountable - mount2->is_user_mountable; + if (res != 0) + return res; + + res = mount1->is_loopback - mount2->is_loopback; + if (res != 0) + return res; + + return 0; +} + +/** + * g_unix_mount_point_get_mount_path: + * @mount_point: a #GUnixMount. + * + **/ +const char * +g_unix_mount_point_get_mount_path (GUnixMountPoint *mount_point) +{ + g_return_val_if_fail (mount_point != NULL, NULL); + + return mount_point->mount_path; +} + +/** + * g_unix_mount_point_get_device_path: + * @mount_point: a #GUnixMount. + * + **/ +const char * +g_unix_mount_point_get_device_path (GUnixMountPoint *mount_point) +{ + g_return_val_if_fail (mount_point != NULL, NULL); + + return mount_point->device_path; +} + +/** + * g_unix_mount_point_get_fs_type: + * @mount_point: a #GUnixMount. + * + **/ +const char * +g_unix_mount_point_get_fs_type (GUnixMountPoint *mount_point) +{ + g_return_val_if_fail (mount_point != NULL, NULL); + + return mount_point->filesystem_type; +} + +/** + * g_unix_mount_point_is_readonly: + * @mount_point: a #GUnixMount. + * + **/ +gboolean +g_unix_mount_point_is_readonly (GUnixMountPoint *mount_point) +{ + g_return_val_if_fail (mount_point != NULL, FALSE); + + return mount_point->is_read_only; +} + +/** + * g_unix_mount_point_is_user_mountable: + * @mount_point: a #GUnixMount. + * + **/ +gboolean +g_unix_mount_point_is_user_mountable (GUnixMountPoint *mount_point) +{ + g_return_val_if_fail (mount_point != NULL, FALSE); + + return mount_point->is_user_mountable; +} + +/** + * g_unix_mount_point_is_loopback: + * @mount_point: a #GUnixMount. + * + **/ +gboolean +g_unix_mount_point_is_loopback (GUnixMountPoint *mount_point) +{ + g_return_val_if_fail (mount_point != NULL, FALSE); + + return mount_point->is_loopback; +} + +static GUnixMountType +guess_mount_type (const char *mount_path, + const char *device_path, + const char *filesystem_type) +{ + GUnixMountType type; + char *basename; + + type = G_UNIX_MOUNT_TYPE_UNKNOWN; + + if ((strcmp (filesystem_type, "udf") == 0) || + (strcmp (filesystem_type, "iso9660") == 0) || + (strcmp (filesystem_type, "cd9660") == 0)) + type = G_UNIX_MOUNT_TYPE_CDROM; + else if (strcmp (filesystem_type, "nfs") == 0) + type = G_UNIX_MOUNT_TYPE_NFS; + else if (g_str_has_prefix (device_path, "/vol/dev/diskette/") || + g_str_has_prefix (device_path, "/dev/fd") || + g_str_has_prefix (device_path, "/dev/floppy")) + type = G_UNIX_MOUNT_TYPE_FLOPPY; + else if (g_str_has_prefix (device_path, "/dev/cdrom") || + g_str_has_prefix (device_path, "/dev/acd") || + g_str_has_prefix (device_path, "/dev/cd")) + type = G_UNIX_MOUNT_TYPE_CDROM; + else if (g_str_has_prefix (device_path, "/vol/")) + { + const char *name = mount_path + strlen ("/"); + + if (g_str_has_prefix (name, "cdrom")) + type = G_UNIX_MOUNT_TYPE_CDROM; + else if (g_str_has_prefix (name, "floppy") || + g_str_has_prefix (device_path, "/vol/dev/diskette/")) + type = G_UNIX_MOUNT_TYPE_FLOPPY; + else if (g_str_has_prefix (name, "rmdisk")) + type = G_UNIX_MOUNT_TYPE_ZIP; + else if (g_str_has_prefix (name, "jaz")) + type = G_UNIX_MOUNT_TYPE_JAZ; + else if (g_str_has_prefix (name, "memstick")) + type = G_UNIX_MOUNT_TYPE_MEMSTICK; + } + else + { + basename = g_path_get_basename (mount_path); + + if (g_str_has_prefix (basename, "cdrom") || + g_str_has_prefix (basename, "cdwriter") || + g_str_has_prefix (basename, "burn") || + g_str_has_prefix (basename, "cdr") || + g_str_has_prefix (basename, "cdrw") || + g_str_has_prefix (basename, "dvdrom") || + g_str_has_prefix (basename, "dvdram") || + g_str_has_prefix (basename, "dvdr") || + g_str_has_prefix (basename, "dvdrw") || + g_str_has_prefix (basename, "cdrom_dvdrom") || + g_str_has_prefix (basename, "cdrom_dvdram") || + g_str_has_prefix (basename, "cdrom_dvdr") || + g_str_has_prefix (basename, "cdrom_dvdrw") || + g_str_has_prefix (basename, "cdr_dvdrom") || + g_str_has_prefix (basename, "cdr_dvdram") || + g_str_has_prefix (basename, "cdr_dvdr") || + g_str_has_prefix (basename, "cdr_dvdrw") || + g_str_has_prefix (basename, "cdrw_dvdrom") || + g_str_has_prefix (basename, "cdrw_dvdram") || + g_str_has_prefix (basename, "cdrw_dvdr") || + g_str_has_prefix (basename, "cdrw_dvdrw")) + type = G_UNIX_MOUNT_TYPE_CDROM; + else if (g_str_has_prefix (basename, "floppy")) + type = G_UNIX_MOUNT_TYPE_FLOPPY; + else if (g_str_has_prefix (basename, "zip")) + type = G_UNIX_MOUNT_TYPE_ZIP; + else if (g_str_has_prefix (basename, "jaz")) + type = G_UNIX_MOUNT_TYPE_JAZ; + else if (g_str_has_prefix (basename, "camera")) + type = G_UNIX_MOUNT_TYPE_CAMERA; + else if (g_str_has_prefix (basename, "memstick") || + g_str_has_prefix (basename, "memory_stick") || + g_str_has_prefix (basename, "ram")) + type = G_UNIX_MOUNT_TYPE_MEMSTICK; + else if (g_str_has_prefix (basename, "compact_flash")) + type = G_UNIX_MOUNT_TYPE_CF; + else if (g_str_has_prefix (basename, "smart_media")) + type = G_UNIX_MOUNT_TYPE_SM; + else if (g_str_has_prefix (basename, "sd_mmc")) + type = G_UNIX_MOUNT_TYPE_SDMMC; + else if (g_str_has_prefix (basename, "ipod")) + type = G_UNIX_MOUNT_TYPE_IPOD; + + g_free (basename); + } + + if (type == G_UNIX_MOUNT_TYPE_UNKNOWN) + type = G_UNIX_MOUNT_TYPE_HD; + + return type; +} + +/** + * g_unix_mount_guess_type: + * @mount_entry: a #GUnixMount. + * + **/ +GUnixMountType +g_unix_mount_guess_type (GUnixMount *mount_entry) +{ + g_return_val_if_fail (mount_entry != NULL, G_UNIX_MOUNT_TYPE_UNKNOWN); + g_return_val_if_fail (mount_entry->mount_path != NULL, G_UNIX_MOUNT_TYPE_UNKNOWN); + g_return_val_if_fail (mount_entry->device_path != NULL, G_UNIX_MOUNT_TYPE_UNKNOWN); + g_return_val_if_fail (mount_entry->filesystem_type != NULL, G_UNIX_MOUNT_TYPE_UNKNOWN); + + return guess_mount_type (mount_entry->mount_path, + mount_entry->device_path, + mount_entry->filesystem_type); +} + +/** + * g_unix_mount_point_guess_type: + * @mount_point: a #GUnixMount. + * + **/ +GUnixMountType +g_unix_mount_point_guess_type (GUnixMountPoint *mount_point) +{ + g_return_val_if_fail (mount_point != NULL, G_UNIX_MOUNT_TYPE_UNKNOWN); + g_return_val_if_fail (mount_point->mount_path != NULL, G_UNIX_MOUNT_TYPE_UNKNOWN); + g_return_val_if_fail (mount_point->device_path != NULL, G_UNIX_MOUNT_TYPE_UNKNOWN); + g_return_val_if_fail (mount_point->filesystem_type != NULL, G_UNIX_MOUNT_TYPE_UNKNOWN); + + return guess_mount_type (mount_point->mount_path, + mount_point->device_path, + mount_point->filesystem_type); +} diff --git a/gio/gunixmounts.h b/gio/gunixmounts.h new file mode 100644 index 000000000..7e9c03b11 --- /dev/null +++ b/gio/gunixmounts.h @@ -0,0 +1,92 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#ifndef __G_UNIX_MOUNTS_H__ +#define __G_UNIX_MOUNTS_H__ + +#include <glib.h> +#include <glib-object.h> + +G_BEGIN_DECLS + +typedef struct _GUnixMount GUnixMount; +typedef struct _GUnixMountPoint GUnixMountPoint; + +typedef enum { + G_UNIX_MOUNT_TYPE_UNKNOWN, + G_UNIX_MOUNT_TYPE_FLOPPY, + G_UNIX_MOUNT_TYPE_CDROM, + G_UNIX_MOUNT_TYPE_NFS, + G_UNIX_MOUNT_TYPE_ZIP, + G_UNIX_MOUNT_TYPE_JAZ, + G_UNIX_MOUNT_TYPE_MEMSTICK, + G_UNIX_MOUNT_TYPE_CF, + G_UNIX_MOUNT_TYPE_SM, + G_UNIX_MOUNT_TYPE_SDMMC, + G_UNIX_MOUNT_TYPE_IPOD, + G_UNIX_MOUNT_TYPE_CAMERA, + G_UNIX_MOUNT_TYPE_HD +} GUnixMountType; + +typedef struct _GUnixMountMonitor GUnixMountMonitor; +typedef struct _GUnixMountMonitorClass GUnixMountMonitorClass; + +#define G_TYPE_UNIX_MOUNT_MONITOR (g_unix_mount_monitor_get_type ()) +#define G_UNIX_MOUNT_MONITOR(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_UNIX_MOUNT_MONITOR, GUnixMountMonitor)) +#define G_UNIX_MOUNT_MONITOR_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), G_TYPE_UNIX_MOUNT_MONITOR, GUnixMountMonitorClass)) +#define G_IS_UNIX_MOUNT_MONITOR(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_UNIX_MOUNT_MONITOR)) +#define G_IS_UNIX_MOUNT_MONITOR_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_UNIX_MOUNT_MONITOR)) + +void g_unix_mount_free (GUnixMount *mount_entry); +void g_unix_mount_point_free (GUnixMountPoint *mount_point); +gint g_unix_mount_compare (GUnixMount *mount1, + GUnixMount *mount2); +const char * g_unix_mount_get_mount_path (GUnixMount *mount_entry); +const char * g_unix_mount_get_device_path (GUnixMount *mount_entry); +const char * g_unix_mount_get_fs_type (GUnixMount *mount_entry); +gboolean g_unix_mount_is_readonly (GUnixMount *mount_entry); +gboolean g_unix_mount_is_system_internal (GUnixMount *mount_entry); +GUnixMountType g_unix_mount_guess_type (GUnixMount *mount_entry); + +gint g_unix_mount_point_compare (GUnixMountPoint *mount1, + GUnixMountPoint *mount2); +const char * g_unix_mount_point_get_mount_path (GUnixMountPoint *mount_point); +const char * g_unix_mount_point_get_device_path (GUnixMountPoint *mount_point); +const char * g_unix_mount_point_get_fs_type (GUnixMountPoint *mount_point); +gboolean g_unix_mount_point_is_readonly (GUnixMountPoint *mount_point); +gboolean g_unix_mount_point_is_user_mountable (GUnixMountPoint *mount_point); +gboolean g_unix_mount_point_is_loopback (GUnixMountPoint *mount_point); +GUnixMountType g_unix_mount_point_guess_type (GUnixMountPoint *mount_point); + +GList * g_get_unix_mount_points (guint64 *time_read); +GList * g_get_unix_mounts (guint64 *time_read); +GUnixMount * g_get_unix_mount_at (const char *mount_path, + guint64 *time_read); +gboolean g_unix_mounts_changed_since (guint64 time); +gboolean g_unix_mount_points_changed_since (guint64 time); + +GType g_unix_mount_monitor_get_type (void) G_GNUC_CONST; +GUnixMountMonitor *g_unix_mount_monitor_new (void); + +G_END_DECLS + +#endif /* __G_UNIX_MOUNTS_H__ */ diff --git a/gio/gunixvolume.c b/gio/gunixvolume.c new file mode 100644 index 000000000..6ce1260c7 --- /dev/null +++ b/gio/gunixvolume.c @@ -0,0 +1,332 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#include <config.h> + +#include <string.h> + +#include <glib.h> +#include "gunixvolumemonitor.h" +#include "gunixvolume.h" +#include "gunixdrive.h" +#include "gvolumeprivate.h" +#include "gvolumemonitor.h" +#include "gthemedicon.h" + +#include "glibintl.h" + +struct _GUnixVolume { + GObject parent; + + GUnixDrive *drive; /* owned by volume monitor */ + char *name; + char *icon; + char *mountpoint; +}; + +static void g_unix_volume_volume_iface_init (GVolumeIface *iface); + +G_DEFINE_TYPE_WITH_CODE (GUnixVolume, g_unix_volume, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (G_TYPE_VOLUME, + g_unix_volume_volume_iface_init)) + + +static void +g_unix_volume_finalize (GObject *object) +{ + GUnixVolume *volume; + + volume = G_UNIX_VOLUME (object); + + if (volume->drive) + g_unix_drive_unset_volume (volume->drive, volume); + + g_assert (volume->drive == NULL); + g_free (volume->name); + g_free (volume->icon); + g_free (volume->mountpoint); + + if (G_OBJECT_CLASS (g_unix_volume_parent_class)->finalize) + (*G_OBJECT_CLASS (g_unix_volume_parent_class)->finalize) (object); +} + +static void +g_unix_volume_class_init (GUnixVolumeClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->finalize = g_unix_volume_finalize; +} + +static void +g_unix_volume_init (GUnixVolume *unix_volume) +{ +} + +static char * +get_filesystem_volume_name (const char *fs_type) +{ + /* TODO: add translation table from gnome-vfs */ + return g_strdup_printf (_("%s volume"), fs_type); +} + +static char * +type_to_icon (GUnixMountType type) +{ + const char *icon_name = NULL; + + switch (type) + { + case G_UNIX_MOUNT_TYPE_HD: + icon_name = "drive-harddisk"; + break; + case G_UNIX_MOUNT_TYPE_FLOPPY: + case G_UNIX_MOUNT_TYPE_ZIP: + case G_UNIX_MOUNT_TYPE_JAZ: + icon_name = "media-floppy"; + break; + case G_UNIX_MOUNT_TYPE_CDROM: + icon_name = "media-optical"; + break; + case G_UNIX_MOUNT_TYPE_NFS: + /* TODO: Would like a better icon here... */ + icon_name = "drive-harddisk"; + break; + case G_UNIX_MOUNT_TYPE_MEMSTICK: + icon_name = "media-flash"; + break; + case G_UNIX_MOUNT_TYPE_CAMERA: + icon_name = "camera-photo"; + break; + case G_UNIX_MOUNT_TYPE_IPOD: + icon_name = "multimedia-player"; + break; + case G_UNIX_MOUNT_TYPE_UNKNOWN: + default: + icon_name = "drive-harddisk"; + break; + } + return g_strdup (icon_name); +} + +/** + * g_unix_volume_new: + * @mount: + * @drive: + * + * Returns: a #GUnixVolume. + * + **/ +GUnixVolume * +g_unix_volume_new (GUnixMount *mount, + GUnixDrive *drive) +{ + GUnixVolume *volume; + GUnixMountType type; + const char *mount_path; + char *volume_name; + + mount_path = g_unix_mount_get_mount_path (mount); + + /* No drive for volume. Ignore internal things */ + if (drive == NULL && g_unix_mount_is_system_internal (mount)) + return NULL; + + volume = g_object_new (G_TYPE_UNIX_VOLUME, NULL); + volume->drive = drive; + if (drive) + g_unix_drive_set_volume (drive, volume); + volume->mountpoint = g_strdup (mount_path); + + type = g_unix_mount_guess_type (mount); + + volume->icon = type_to_icon (type); + + volume_name = NULL; + if (mount_path) + { + if (strcmp (mount_path, "/") == 0) + volume_name = g_strdup (_("Filesystem root")); + else + volume_name = g_filename_display_basename (mount_path); + } + + if (volume_name == NULL) + { + if (g_unix_mount_get_fs_type (mount) != NULL) + volume_name = g_strdup (get_filesystem_volume_name (g_unix_mount_get_fs_type (mount))); + } + + if (volume_name == NULL) + { + /* TODO: Use volume size as name? */ + volume_name = g_strdup (_("Unknown volume")); + } + + volume->name = volume_name; + + return volume; +} + +/** + * g_unix_volume_unmounted: + * @volume: + * + **/ +void +g_unix_volume_unmounted (GUnixVolume *volume) +{ + if (volume->drive) + { + g_unix_drive_unset_volume (volume->drive, volume); + volume->drive = NULL; + g_signal_emit_by_name (volume, "changed"); + } +} + +/** + * g_unix_volume_unset_drive: + * @volume: + * @drive: + * + **/ +void +g_unix_volume_unset_drive (GUnixVolume *volume, + GUnixDrive *drive) +{ + if (volume->drive == drive) + { + volume->drive = NULL; + /* TODO: Emit changed in idle to avoid locking issues */ + g_signal_emit_by_name (volume, "changed"); + } +} + +static GFile * +g_unix_volume_get_root (GVolume *volume) +{ + GUnixVolume *unix_volume = G_UNIX_VOLUME (volume); + + return g_file_new_for_path (unix_volume->mountpoint); +} + +static GIcon * +g_unix_volume_get_icon (GVolume *volume) +{ + GUnixVolume *unix_volume = G_UNIX_VOLUME (volume); + + return g_themed_icon_new (unix_volume->icon); +} + +static char * +g_unix_volume_get_name (GVolume *volume) +{ + GUnixVolume *unix_volume = G_UNIX_VOLUME (volume); + + return g_strdup (unix_volume->name); +} + +/** + * g_unix_volume_has_mountpoint: + * @volume: + * @mountpoint: + * + * Returns: + **/ +gboolean +g_unix_volume_has_mountpoint (GUnixVolume *volume, + const char *mountpoint) +{ + return strcmp (volume->mountpoint, mountpoint) == 0; +} + +static GDrive * +g_unix_volume_get_drive (GVolume *volume) +{ + GUnixVolume *unix_volume = G_UNIX_VOLUME (volume); + + if (unix_volume->drive) + return G_DRIVE (g_object_ref (unix_volume->drive)); + + return NULL; +} + +static gboolean +g_unix_volume_can_unmount (GVolume *volume) +{ + /* TODO */ + return FALSE; +} + +static gboolean +g_unix_volume_can_eject (GVolume *volume) +{ + /* TODO */ + return FALSE; +} + +static void +g_unix_volume_unmount (GVolume *volume, + GAsyncReadyCallback callback, + gpointer user_data) +{ + /* TODO */ +} + +static gboolean +g_unix_volume_unmount_finish (GVolume *volume, + GAsyncResult *result, + GError **error) +{ + return TRUE; +} + +static void +g_unix_volume_eject (GVolume *volume, + GAsyncReadyCallback callback, + gpointer user_data) +{ + /* TODO */ +} + +static gboolean +g_unix_volume_eject_finish (GVolume *volume, + GAsyncResult *result, + GError **error) +{ + return TRUE; +} + +static void +g_unix_volume_volume_iface_init (GVolumeIface *iface) +{ + iface->get_root = g_unix_volume_get_root; + iface->get_name = g_unix_volume_get_name; + iface->get_icon = g_unix_volume_get_icon; + iface->get_drive = g_unix_volume_get_drive; + iface->can_unmount = g_unix_volume_can_unmount; + iface->can_eject = g_unix_volume_can_eject; + iface->unmount = g_unix_volume_unmount; + iface->unmount_finish = g_unix_volume_unmount_finish; + iface->eject = g_unix_volume_eject; + iface->eject_finish = g_unix_volume_eject_finish; +} diff --git a/gio/gunixvolume.h b/gio/gunixvolume.h new file mode 100644 index 000000000..b0cb40600 --- /dev/null +++ b/gio/gunixvolume.h @@ -0,0 +1,57 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#ifndef __G_UNIX_VOLUME_H__ +#define __G_UNIX_VOLUME_H__ + +#include <glib-object.h> +#include <gio/gvolume.h> +#include <gio/gunixmounts.h> +#include <gio/gunixvolumemonitor.h> + +G_BEGIN_DECLS + +#define G_TYPE_UNIX_VOLUME (g_unix_volume_get_type ()) +#define G_UNIX_VOLUME(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_UNIX_VOLUME, GUnixVolume)) +#define G_UNIX_VOLUME_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), G_TYPE_UNIX_VOLUME, GUnixVolumeClass)) +#define G_IS_UNIX_VOLUME(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_UNIX_VOLUME)) +#define G_IS_UNIX_VOLUME_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_UNIX_VOLUME)) + +typedef struct _GUnixVolumeClass GUnixVolumeClass; + +struct _GUnixVolumeClass { + GObjectClass parent_class; +}; + +GType g_unix_volume_get_type (void) G_GNUC_CONST; + +GUnixVolume *g_unix_volume_new (GUnixMount *mount, + GUnixDrive *drive); +gboolean g_unix_volume_has_mountpoint (GUnixVolume *volume, + const char *mountpoint); +void g_unix_volume_unset_drive (GUnixVolume *volume, + GUnixDrive *drive); +void g_unix_volume_unmounted (GUnixVolume *volume); + +G_END_DECLS + +#endif /* __G_UNIX_VOLUME_H__ */ diff --git a/gio/gunixvolumemonitor.c b/gio/gunixvolumemonitor.c new file mode 100644 index 000000000..2430e4bcc --- /dev/null +++ b/gio/gunixvolumemonitor.c @@ -0,0 +1,382 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#include <config.h> + +#include <string.h> + +#include <glib.h> +#include "gunixvolumemonitor.h" +#include "gunixmounts.h" +#include "gunixvolume.h" +#include "gunixdrive.h" +#include "gvolumeprivate.h" + +#include "glibintl.h" + +struct _GUnixVolumeMonitor { + GNativeVolumeMonitor parent; + + GUnixMountMonitor *mount_monitor; + + GList *last_mountpoints; + GList *last_mounts; + + GList *drives; + GList *volumes; +}; + +static void mountpoints_changed (GUnixMountMonitor *mount_monitor, + gpointer user_data); +static void mounts_changed (GUnixMountMonitor *mount_monitor, + gpointer user_data); +static void update_drives (GUnixVolumeMonitor *monitor); +static void update_volumes (GUnixVolumeMonitor *monitor); + +G_DEFINE_TYPE (GUnixVolumeMonitor, g_unix_volume_monitor, G_TYPE_NATIVE_VOLUME_MONITOR); + +static void +g_unix_volume_monitor_finalize (GObject *object) +{ + GUnixVolumeMonitor *monitor; + + monitor = G_UNIX_VOLUME_MONITOR (object); + + g_signal_handlers_disconnect_by_func (monitor->mount_monitor, mountpoints_changed, monitor); + g_signal_handlers_disconnect_by_func (monitor->mount_monitor, mounts_changed, monitor); + + g_object_unref (monitor->mount_monitor); + + g_list_foreach (monitor->last_mounts, (GFunc)g_unix_mount_free, NULL); + g_list_free (monitor->last_mounts); + + g_list_foreach (monitor->volumes, (GFunc)g_object_unref, NULL); + g_list_free (monitor->volumes); + g_list_foreach (monitor->drives, (GFunc)g_object_unref, NULL); + g_list_free (monitor->drives); + + if (G_OBJECT_CLASS (g_unix_volume_monitor_parent_class)->finalize) + (*G_OBJECT_CLASS (g_unix_volume_monitor_parent_class)->finalize) (object); +} + +static GList * +get_mounted_volumes (GVolumeMonitor *volume_monitor) +{ + GUnixVolumeMonitor *monitor; + GList *l; + + monitor = G_UNIX_VOLUME_MONITOR (volume_monitor); + + l = g_list_copy (monitor->volumes); + g_list_foreach (l, (GFunc)g_object_ref, NULL); + + return l; +} + +static GList * +get_connected_drives (GVolumeMonitor *volume_monitor) +{ + GUnixVolumeMonitor *monitor; + GList *l; + + monitor = G_UNIX_VOLUME_MONITOR (volume_monitor); + + l = g_list_copy (monitor->drives); + g_list_foreach (l, (GFunc)g_object_ref, NULL); + + return l; +} + +static GVolume * +get_volume_for_mountpoint (const char *mountpoint) +{ + GUnixMount *mount; + GUnixVolume *volume; + + mount = g_get_unix_mount_at (mountpoint, NULL); + + /* TODO: Set drive? */ + volume = g_unix_volume_new (mount, NULL); + + return G_VOLUME (volume); +} + +static void +g_unix_volume_monitor_class_init (GUnixVolumeMonitorClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GVolumeMonitorClass *monitor_class = G_VOLUME_MONITOR_CLASS (klass); + GNativeVolumeMonitorClass *native_class = G_NATIVE_VOLUME_MONITOR_CLASS (klass); + + gobject_class->finalize = g_unix_volume_monitor_finalize; + + monitor_class->get_mounted_volumes = get_mounted_volumes; + monitor_class->get_connected_drives = get_connected_drives; + + native_class->priority = 0; + native_class->get_volume_for_mountpoint = get_volume_for_mountpoint; +} + +static void +mountpoints_changed (GUnixMountMonitor *mount_monitor, + gpointer user_data) +{ + GUnixVolumeMonitor *unix_monitor = user_data; + + /* Update both to make sure drives are created before volumes */ + update_drives (unix_monitor); + update_volumes (unix_monitor); +} + +static void +mounts_changed (GUnixMountMonitor *mount_monitor, + gpointer user_data) +{ + GUnixVolumeMonitor *unix_monitor = user_data; + + /* Update both to make sure drives are created before volumes */ + update_drives (unix_monitor); + update_volumes (unix_monitor); +} + +static void +g_unix_volume_monitor_init (GUnixVolumeMonitor *unix_monitor) +{ + + unix_monitor->mount_monitor = g_unix_mount_monitor_new (); + + g_signal_connect (unix_monitor->mount_monitor, + "mounts_changed", G_CALLBACK (mounts_changed), + unix_monitor); + + g_signal_connect (unix_monitor->mount_monitor, + "mountpoints_changed", G_CALLBACK (mountpoints_changed), + unix_monitor); + + update_drives (unix_monitor); + update_volumes (unix_monitor); + +} + +/** + * g_unix_volume_monitor_new: + * + * Returns: a new #GVolumeMonitor. + **/ +GVolumeMonitor * +g_unix_volume_monitor_new (void) +{ + GUnixVolumeMonitor *monitor; + + monitor = g_object_new (G_TYPE_UNIX_VOLUME_MONITOR, NULL); + + return G_VOLUME_MONITOR (monitor); +} + +static void +diff_sorted_lists (GList *list1, GList *list2, GCompareFunc compare, + GList **added, GList **removed) +{ + int order; + + *added = *removed = NULL; + + while (list1 != NULL && + list2 != NULL) + { + order = (*compare) (list1->data, list2->data); + if (order < 0) + { + *removed = g_list_prepend (*removed, list1->data); + list1 = list1->next; + } + else if (order > 0) + { + *added = g_list_prepend (*added, list2->data); + list2 = list2->next; + } + else + { /* same item */ + list1 = list1->next; + list2 = list2->next; + } + } + + while (list1 != NULL) + { + *removed = g_list_prepend (*removed, list1->data); + list1 = list1->next; + } + while (list2 != NULL) + { + *added = g_list_prepend (*added, list2->data); + list2 = list2->next; + } +} + +/** + * g_unix_volume_lookup_drive_for_mountpoint: + * @monitor: + * @mountpoint: + * + * Returns: #GUnixDrive for the given @mountpoint. + **/ +GUnixDrive * +g_unix_volume_monitor_lookup_drive_for_mountpoint (GUnixVolumeMonitor *monitor, + const char *mountpoint) +{ + GList *l; + + for (l = monitor->drives; l != NULL; l = l->next) + { + GUnixDrive *drive = l->data; + + if (g_unix_drive_has_mountpoint (drive, mountpoint)) + return drive; + } + + return NULL; +} + +static GUnixVolume * +find_volume_by_mountpoint (GUnixVolumeMonitor *monitor, + const char *mountpoint) +{ + GList *l; + + for (l = monitor->volumes; l != NULL; l = l->next) + { + GUnixVolume *volume = l->data; + + if (g_unix_volume_has_mountpoint (volume, mountpoint)) + return volume; + } + + return NULL; +} + +static void +update_drives (GUnixVolumeMonitor *monitor) +{ + GList *new_mountpoints; + GList *removed, *added; + GList *l; + GUnixDrive *drive; + + new_mountpoints = g_get_unix_mount_points (NULL); + + new_mountpoints = g_list_sort (new_mountpoints, (GCompareFunc) g_unix_mount_point_compare); + + diff_sorted_lists (monitor->last_mountpoints, + new_mountpoints, (GCompareFunc) g_unix_mount_point_compare, + &added, &removed); + + for (l = removed; l != NULL; l = l->next) + { + GUnixMountPoint *mountpoint = l->data; + + drive = g_unix_volume_monitor_lookup_drive_for_mountpoint (monitor, + g_unix_mount_point_get_mount_path (mountpoint)); + if (drive) + { + g_unix_drive_disconnected (drive); + monitor->drives = g_list_remove (monitor->drives, drive); + g_signal_emit_by_name (monitor, "drive_disconnected", drive); + g_object_unref (drive); + } + } + + for (l = added; l != NULL; l = l->next) + { + GUnixMountPoint *mountpoint = l->data; + + drive = g_unix_drive_new (G_VOLUME_MONITOR (monitor), mountpoint); + if (drive) + { + monitor->drives = g_list_prepend (monitor->drives, drive); + g_signal_emit_by_name (monitor, "drive_connected", drive); + } + } + + g_list_free (added); + g_list_free (removed); + g_list_foreach (monitor->last_mountpoints, + (GFunc)g_unix_mount_point_free, NULL); + g_list_free (monitor->last_mountpoints); + monitor->last_mountpoints = new_mountpoints; +} + +static void +update_volumes (GUnixVolumeMonitor *monitor) +{ + GList *new_mounts; + GList *removed, *added; + GList *l; + GUnixVolume *volume; + GUnixDrive *drive; + const char *mount_path; + + new_mounts = g_get_unix_mounts (NULL); + + new_mounts = g_list_sort (new_mounts, (GCompareFunc) g_unix_mount_compare); + + diff_sorted_lists (monitor->last_mounts, + new_mounts, (GCompareFunc) g_unix_mount_compare, + &added, &removed); + + for (l = removed; l != NULL; l = l->next) + { + GUnixMount *mount = l->data; + + volume = find_volume_by_mountpoint (monitor, g_unix_mount_get_mount_path (mount)); + if (volume) + { + g_unix_volume_unmounted (volume); + monitor->volumes = g_list_remove (monitor->volumes, volume); + g_signal_emit_by_name (monitor, "volume_unmounted", volume); + g_object_unref (volume); + } + } + + for (l = added; l != NULL; l = l->next) + { + GUnixMount *mount = l->data; + + mount_path = g_unix_mount_get_mount_path (mount); + + drive = g_unix_volume_monitor_lookup_drive_for_mountpoint (monitor, + mount_path); + volume = g_unix_volume_new (mount, drive); + if (volume) + { + monitor->volumes = g_list_prepend (monitor->volumes, volume); + g_signal_emit_by_name (monitor, "volume_mounted", volume); + } + } + + g_list_free (added); + g_list_free (removed); + g_list_foreach (monitor->last_mounts, + (GFunc)g_unix_mount_free, NULL); + g_list_free (monitor->last_mounts); + monitor->last_mounts = new_mounts; +} diff --git a/gio/gunixvolumemonitor.h b/gio/gunixvolumemonitor.h new file mode 100644 index 000000000..feb940943 --- /dev/null +++ b/gio/gunixvolumemonitor.h @@ -0,0 +1,57 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#ifndef __G_UNIX_VOLUME_MONITOR_H__ +#define __G_UNIX_VOLUME_MONITOR_H__ + +#include <glib-object.h> +#include <gio/gnativevolumemonitor.h> + +G_BEGIN_DECLS + +#define G_TYPE_UNIX_VOLUME_MONITOR (g_unix_volume_monitor_get_type ()) +#define G_UNIX_VOLUME_MONITOR(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_UNIX_VOLUME_MONITOR, GUnixVolumeMonitor)) +#define G_UNIX_VOLUME_MONITOR_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), G_TYPE_UNIX_VOLUME_MONITOR, GUnixVolumeMonitorClass)) +#define G_IS_UNIX_VOLUME_MONITOR(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_UNIX_VOLUME_MONITOR)) +#define G_IS_UNIX_VOLUME_MONITOR_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_UNIX_VOLUME_MONITOR)) + +typedef struct _GUnixVolumeMonitor GUnixVolumeMonitor; +typedef struct _GUnixVolumeMonitorClass GUnixVolumeMonitorClass; + +/* Forward definitions */ +typedef struct _GUnixVolume GUnixVolume; +typedef struct _GUnixDrive GUnixDrive; + +struct _GUnixVolumeMonitorClass { + GNativeVolumeMonitorClass parent_class; + +}; + +GType g_unix_volume_monitor_get_type (void) G_GNUC_CONST; + +GVolumeMonitor *g_unix_volume_monitor_new (void); +GUnixDrive * g_unix_volume_monitor_lookup_drive_for_mountpoint (GUnixVolumeMonitor *monitor, + const char *mountpoint); + +G_END_DECLS + +#endif /* __G_UNIX_VOLUME_MONITOR_H__ */ diff --git a/gio/gurifuncs.c b/gio/gurifuncs.c new file mode 100644 index 000000000..2e1072319 --- /dev/null +++ b/gio/gurifuncs.c @@ -0,0 +1,276 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#include <config.h> +#include "gurifuncs.h" +#include "string.h" + +static int +unescape_character (const char *scanner) +{ + int first_digit; + int second_digit; + + first_digit = g_ascii_xdigit_value (*scanner++); + if (first_digit < 0) + return -1; + + second_digit = g_ascii_xdigit_value (*scanner++); + if (second_digit < 0) + return -1; + + return (first_digit << 4) | second_digit; +} + +/** + * g_uri_unescape_segment: + * @escaped_string: a string. + * @escaped_string_end: a string. + * @illegal_characters: a string of illegal characters not to be allowed. + * + * Returns: an unescaped version of @escaped_string or %NULL on error. + * The returned string should be freed when no longer needed. + **/ +char * +g_uri_unescape_segment (const char *escaped_string, + const char *escaped_string_end, + const char *illegal_characters) +{ + const char *in; + char *out, *result; + gint character; + + if (escaped_string == NULL) + return NULL; + + if (escaped_string_end == NULL) + escaped_string_end = escaped_string + strlen (escaped_string); + + result = g_malloc (escaped_string_end - escaped_string + 1); + + out = result; + for (in = escaped_string; in < escaped_string_end; in++) + { + character = *in; + + if (*in == '%') + { + in++; + + if (escaped_string_end - in < 2) + { + /* Invalid escaped char (to short) */ + g_free (result); + return NULL; + } + + character = unescape_character (in); + + /* Check for an illegal character. We consider '\0' illegal here. */ + if (character <= 0 || + (illegal_characters != NULL && + strchr (illegal_characters, (char)character) != NULL)) + { + g_free (result); + return NULL; + } + + in++; /* The other char will be eaten in the loop header */ + } + *out++ = (char)character; + } + + *out = '\0'; + + return result; +} + +/** + * g_uri_unescape_string: + * @escaped_string: an escaped string to be unescaped. + * @illegal_characters: a string of illegal characters not to be allowed. + * + * Returns: an unescaped version of @escaped_string. + * + * The returned string should be freed when no longer needed + * + **/ +char * +g_uri_unescape_string (const char *escaped_string, + const char *illegal_characters) +{ + return g_uri_unescape_segment (escaped_string, NULL, illegal_characters); +} + +/** + * g_uri_get_scheme: + * @uri: a valid URI. + * + * Returns: The "Scheme" component of the URI, or %NULL on error. + * RFC 3986 decodes the scheme as: + * URI = scheme ":" hier-part [ "?" query ] [ "#" fragment ] + * Popular schemes include "file", "http", "svn", etc. + * + * The returned string should be freed when no longer needed. + * + **/ +char * +g_uri_get_scheme (const char *uri) +{ + const char *p; + char c; + + g_return_val_if_fail (uri != NULL, NULL); + + /* From RFC 3986 Decodes: + * URI = scheme ":" hier-part [ "?" query ] [ "#" fragment ] + */ + + p = uri; + + /* Decode scheme: + scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) + */ + + if (!g_ascii_isalpha (*p)) + return NULL; + + while (1) + { + c = *p++; + + if (c == ':') + break; + + if (!(g_ascii_isalnum(c) || + c == '+' || + c == '-' || + c == '.')) + return NULL; + } + + return g_strndup (uri, p - uri - 1); +} + +#define SUB_DELIM_CHARS "!$&'()*+,;=" + +static gboolean +is_valid (char c, const char *reserved_chars_allowed) +{ + if (g_ascii_isalnum (c) || + c == '-' || + c == '.' || + c == '_' || + c == '~') + return TRUE; + + if (reserved_chars_allowed && + strchr (reserved_chars_allowed, c) != NULL) + return TRUE; + + return FALSE; +} + +static gboolean +gunichar_ok (gunichar c) +{ + return + (c != (gunichar) -2) && + (c != (gunichar) -1); +} + +/** + * g_string_append_uri_escaped: + * @string: a #GString to append to. + * @unescaped: the input C string of unescaped URI data. + * @reserved_chars_allowed: a string of reserve characters allowed to be used. + * @allow_utf8: set %TRUE if the return value may include UTF8 characters. + * + * Returns a #GString with the escaped URI appended. + * + **/ +GString * +g_string_append_uri_escaped (GString *string, + const char *unescaped, + const char *reserved_chars_allowed, + gboolean allow_utf8) +{ + unsigned char c; + const char *end; + static const gchar hex[16] = "0123456789ABCDEF"; + + g_return_val_if_fail (string != NULL, NULL); + g_return_val_if_fail (unescaped != NULL, NULL); + + end = unescaped + strlen (unescaped); + + while ((c = *unescaped) != 0) + { + if (c >= 0x80 && allow_utf8 && + gunichar_ok (g_utf8_get_char_validated (unescaped, end - unescaped))) + { + int len = g_utf8_skip [c]; + g_string_append_len (string, unescaped, len); + unescaped += len; + } + else if (is_valid (c, reserved_chars_allowed)) + { + g_string_append_c (string, c); + unescaped++; + } + else + { + g_string_append_c (string, '%'); + g_string_append_c (string, hex[((guchar)c) >> 4]); + g_string_append_c (string, hex[((guchar)c) & 0xf]); + unescaped++; + } + } + + return string; +} + +/** + * g_uri_escape_string: + * @unescaped: the unescaped input string. + * @reserved_chars_allowed: a string of reserve characters allowed to be used. + * @allow_utf8: set to %TRUE if string can include UTF8 characters. + * + * Returns an escaped version of @unescaped. + * + * The returned string should be freed when no longer needed. + **/ +char * +g_uri_escape_string (const char *unescaped, + const char *reserved_chars_allowed, + gboolean allow_utf8) +{ + GString *s; + + g_return_val_if_fail (unescaped != NULL, NULL); + + s = g_string_sized_new (strlen (unescaped) + 10); + + g_string_append_uri_escaped (s, unescaped, reserved_chars_allowed, allow_utf8); + + return g_string_free (s, FALSE); +} diff --git a/gio/gurifuncs.h b/gio/gurifuncs.h new file mode 100644 index 000000000..cace2d90e --- /dev/null +++ b/gio/gurifuncs.h @@ -0,0 +1,55 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#ifndef __G_URI_FUNCS_H__ +#define __G_URI_FUNCS_H__ + +#include <glib.h> + +G_BEGIN_DECLS + +#define G_URI_RESERVED_CHARS_GENERIC_DELIMITERS ":/?#[]@" +#define G_URI_RESERVED_CHARS_SUBCOMPONENT_DELIMITERS "!$&'()*+,;=" +#define G_URI_RESERVED_CHARS_ALLOWED_IN_PATH_ELEMENT G_URI_RESERVED_CHARS_SUBCOMPONENT_DELIMITERS ":@" +#define G_URI_RESERVED_CHARS_ALLOWED_IN_PATH G_URI_RESERVED_CHARS_ALLOWED_IN_PATH_ELEMENT "/" +#define G_URI_RESERVED_CHARS_ALLOWED_IN_USERINFO G_URI_RESERVED_CHARS_SUBCOMPONENT_DELIMITERS ":" + +char * g_uri_unescape_string (const char *escaped_string, + const char *illegal_characters); +char * g_uri_unescape_segment (const char *escaped_string, + const char *escaped_string_end, + const char *illegal_characters); +char * g_uri_get_scheme (const char *uri); +char * g_uri_escape_string (const char *unescaped, + const char *reserved_chars_allowed, + gboolean allow_utf8); +GString *g_string_append_uri_escaped (GString *string, + const char *unescaped, + const char *reserved_chars_allowed, + gboolean allow_utf8); + + + + +G_END_DECLS + +#endif /* __G_URI_FUNCS_H__ */ diff --git a/gio/gvfs.c b/gio/gvfs.c new file mode 100644 index 000000000..4dc1f256b --- /dev/null +++ b/gio/gvfs.c @@ -0,0 +1,258 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#include <config.h> +#include <string.h> +#include "gvfs.h" +#include "glocalvfs.h" +#include "giomodule.h" +#include "glibintl.h" + +G_DEFINE_TYPE (GVfs, g_vfs, G_TYPE_OBJECT); + +static void +g_vfs_class_init (GVfsClass *klass) +{ +} + +static void +g_vfs_init (GVfs *vfs) +{ +} + +/** + * g_vfs_is_active: + * @vfs: an #GVfs. + * + * Returns TRUE if construction of the @vfs was successful and its now active. + **/ +gboolean +g_vfs_is_active (GVfs *vfs) +{ + GVfsClass *class; + + g_return_val_if_fail (G_IS_VFS (vfs), FALSE); + + class = G_VFS_GET_CLASS (vfs); + + return (* class->is_active) (vfs); +} + + +/** + * g_vfs_get_file_for_path: + * @vfs: an input #GVfs. + * @path: a string containing a VFS path. + * + * Returns a #GFile for the given @path. + * + **/ +GFile * +g_vfs_get_file_for_path (GVfs *vfs, + const char *path) +{ + GVfsClass *class; + + g_return_val_if_fail (G_IS_VFS (vfs), NULL); + g_return_val_if_fail (path != NULL, NULL); + + class = G_VFS_GET_CLASS (vfs); + + return (* class->get_file_for_path) (vfs, path); +} + +/** + * g_vfs_get_file_for_uri: + * @vfs: an input #GVfs. + * @uri: an input string containing a URI path. + * + * This operation never fails, but the returned object + * might not support any I/O operation if the uri + * is malformed or if the uri type is not supported. + * + * Returns a #GFile for the given @uri. + * + **/ +GFile * +g_vfs_get_file_for_uri (GVfs *vfs, + const char *uri) +{ + GVfsClass *class; + + g_return_val_if_fail (G_IS_VFS (vfs), NULL); + g_return_val_if_fail (uri != NULL, NULL); + + class = G_VFS_GET_CLASS (vfs); + + return (* class->get_file_for_uri) (vfs, uri); +} + +/** + * g_vfs_get_supported_uri_schemes: + * @vfs: an input #GVfs. + * + * Returns: + * + **/ +const gchar * const * +g_vfs_get_supported_uri_schemes (GVfs *vfs) +{ + GVfsClass *class; + + g_return_val_if_fail (G_IS_VFS (vfs), NULL); + + class = G_VFS_GET_CLASS (vfs); + + return (* class->get_supported_uri_schemes) (vfs); +} + +/** + * g_vfs_parse_name: + * @vfs: an input #GVfs. + * @parse_name: a string to be parsed by the VFS module. + * + * This operation never fails, but the returned object might + * not support any I/O operations if the @parse_name cannot + * be parsed by the #GVfs module. + * + * Returns a #GFile for the given @parse_name. + * + **/ +GFile * +g_vfs_parse_name (GVfs *vfs, + const char *parse_name) +{ + GVfsClass *class; + + g_return_val_if_fail (G_IS_VFS (vfs), NULL); + g_return_val_if_fail (parse_name != NULL, NULL); + + class = G_VFS_GET_CLASS (vfs); + + return (* class->parse_name) (vfs, parse_name); +} + +/* Note: This compares in reverse order. + Higher prio -> sort first + */ +static gint +compare_vfs_type (gconstpointer a, + gconstpointer b, + gpointer user_data) +{ + GVfsClass *class_a, *class_b; + gint res; + const char *use_this_vfs; + + class_a = g_type_class_ref (*(GType *)a); + class_b = g_type_class_ref (*(GType *)b); + use_this_vfs = user_data; + + if (class_a == class_b) + res = 0; + else if (use_this_vfs != NULL && + strcmp (class_a->name, use_this_vfs) == 0) + res = -1; + else if (use_this_vfs != NULL && + strcmp (class_b->name, use_this_vfs) == 0) + res = 1; + else + res = class_b->priority - class_a->priority; + + g_type_class_unref (class_a); + g_type_class_unref (class_b); + + return res; +} + + +static gpointer +get_default_vfs (gpointer arg) +{ + volatile GType local_type; + GType *vfs_impls; + int i; + guint n_vfs_impls; + const char *use_this; + GVfs *vfs; + GType (*casted_get_type)(void); + + use_this = g_getenv ("GIO_USE_VFS"); + + /* Ensure GLocalVfs type is available + the cast is required to avoid any G_GNUC_CONST optimizations */ + casted_get_type = g_local_vfs_get_type; + local_type = casted_get_type (); + + /* Ensure vfs in modules loaded */ + g_io_modules_ensure_loaded (GIO_MODULE_DIR); + + vfs_impls = g_type_children (G_TYPE_VFS, &n_vfs_impls); + + g_qsort_with_data (vfs_impls, n_vfs_impls, sizeof (GType), + compare_vfs_type, (gpointer)use_this); + + for (i = 0; i < n_vfs_impls; i++) + { + vfs = g_object_new (vfs_impls[i], NULL); + + if (g_vfs_is_active (vfs)) + break; + + g_object_unref (vfs); + vfs = NULL; + } + + g_free (vfs_impls); + + return vfs; +} + +/** + * g_vfs_get_default: + * + * Returns the default #GVfs for the system. + **/ +GVfs * +g_vfs_get_default (void) +{ + static GOnce once_init = G_ONCE_INIT; + + return g_once (&once_init, get_default_vfs, NULL); +} + +/** + * g_vfs_get_local: + * + * Returns the local #GVfs for the system. + **/ +GVfs * +g_vfs_get_local (void) +{ + static gsize vfs = 0; + + if (g_once_init_enter (&vfs)) + g_once_init_leave (&vfs, (gsize)g_local_vfs_new ()); + + return G_VFS (vfs); +} + diff --git a/gio/gvfs.h b/gio/gvfs.h new file mode 100644 index 000000000..ebc82bded --- /dev/null +++ b/gio/gvfs.h @@ -0,0 +1,98 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#ifndef __G_VFS_IMPLEMENTATION_H__ +#define __G_VFS_IMPLEMENTATION_H__ + +#include <glib-object.h> +#include <gio/gfile.h> + +G_BEGIN_DECLS + +#define G_TYPE_VFS (g_vfs_get_type ()) +#define G_VFS(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_VFS, GVfs)) +#define G_VFS_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), G_TYPE_VFS, GVfsClass)) +#define G_VFS_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_TYPE_VFS, GVfsClass)) +#define G_IS_VFS(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_VFS)) +#define G_IS_VFS_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_VFS)) + +typedef struct _GVfs GVfs; /* Dummy typedef */ +typedef struct _GVfsClass GVfsClass; + +struct _GVfs { + GObject parent; +}; + +struct _GVfsClass +{ + GObjectClass parent_class; + + const char *name; + int priority; + + /* Virtual Table */ + + gboolean (*is_active) (GVfs *vfs); + GFile *(*get_file_for_path) (GVfs *vfs, + const char *path); + GFile *(*get_file_for_uri) (GVfs *vfs, + const char *uri); + const gchar * const *(*get_supported_uri_schemes) (GVfs *vfs); + GFile *(*parse_name) (GVfs *vfs, + const char *parse_name); + + + + /* Padding for future expansion */ + void (*_g_reserved1) (void); + void (*_g_reserved2) (void); + void (*_g_reserved3) (void); + void (*_g_reserved4) (void); + void (*_g_reserved5) (void); + void (*_g_reserved6) (void); + void (*_g_reserved7) (void); + void (*_g_reserved8) (void); + void (*_g_reserved9) (void); + void (*_g_reserved10) (void); + void (*_g_reserved11) (void); + void (*_g_reserved12) (void); + +}; + +GType g_vfs_get_type (void) G_GNUC_CONST; + +gboolean g_vfs_is_active (GVfs *vfs); +GFile * g_vfs_get_file_for_path (GVfs *vfs, + const char *path); +GFile * g_vfs_get_file_for_uri (GVfs *vfs, + const char *uri); +const gchar * const * g_vfs_get_supported_uri_schemes (GVfs *vfs); + +GFile * g_vfs_parse_name (GVfs *vfs, + const char *parse_name); + +GVfs * g_vfs_get_default (void); +GVfs * g_vfs_get_local (void); + +G_END_DECLS + +#endif /* __G_VFS_H__ */ diff --git a/gio/gvolume.c b/gio/gvolume.c new file mode 100644 index 000000000..280f46c26 --- /dev/null +++ b/gio/gvolume.c @@ -0,0 +1,326 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#include <config.h> +#include "gvolume.h" +#include "gvolumeprivate.h" +#include "gsimpleasyncresult.h" +#include "glibintl.h" + +static void g_volume_base_init (gpointer g_class); +static void g_volume_class_init (gpointer g_class, + gpointer class_data); + +GType +g_volume_get_type (void) +{ + static GType volume_type = 0; + + if (! volume_type) + { + static const GTypeInfo volume_info = + { + sizeof (GVolumeIface), /* class_size */ + g_volume_base_init, /* base_init */ + NULL, /* base_finalize */ + g_volume_class_init, + NULL, /* class_finalize */ + NULL, /* class_data */ + 0, + 0, /* n_preallocs */ + NULL + }; + + volume_type = + g_type_register_static (G_TYPE_INTERFACE, I_("GVolume"), + &volume_info, 0); + + g_type_interface_add_prerequisite (volume_type, G_TYPE_OBJECT); + } + + return volume_type; +} + +static void +g_volume_class_init (gpointer g_class, + gpointer class_data) +{ +} + +static void +g_volume_base_init (gpointer g_class) +{ + static gboolean initialized = FALSE; + + if (! initialized) + { + g_signal_new (I_("changed"), + G_TYPE_VOLUME, + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GVolumeIface, changed), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + initialized = TRUE; + } +} + +/** + * g_volume_get_root: + * @volume: a #GVolume. + * + * Returns a #GFile. + * + **/ +GFile * +g_volume_get_root (GVolume *volume) +{ + GVolumeIface *iface; + + g_return_val_if_fail (G_IS_VOLUME (volume), NULL); + + iface = G_VOLUME_GET_IFACE (volume); + + return (* iface->get_root) (volume); +} + +/** + * g_volume_get_name: + * @volume: a #GVolume. + * + * Returns the name for the given @volume. + * + * The returned string should be freed when no longer needed. + * + **/ +char * +g_volume_get_name (GVolume *volume) +{ + GVolumeIface *iface; + + g_return_val_if_fail (G_IS_VOLUME (volume), NULL); + + iface = G_VOLUME_GET_IFACE (volume); + + return (* iface->get_name) (volume); +} + +/** + * g_volume_get_icon: + * @volume: + * + * Returns the #GIcon for the given @volume. + * + **/ +GIcon * +g_volume_get_icon (GVolume *volume) +{ + GVolumeIface *iface; + + g_return_val_if_fail (G_IS_VOLUME (volume), NULL); + + iface = G_VOLUME_GET_IFACE (volume); + + return (* iface->get_icon) (volume); +} + +/** + * g_volume_get_drive: + * @volume: + * + * Returns the #GDrive for the given @volume. + * + **/ +GDrive * +g_volume_get_drive (GVolume *volume) +{ + GVolumeIface *iface; + + g_return_val_if_fail (G_IS_VOLUME (volume), NULL); + + iface = G_VOLUME_GET_IFACE (volume); + + return (* iface->get_drive) (volume); +} + +/** + * g_volume_can_unmount: + * @volume: + * + * Returns %TRUE if the @volume can be unmounted. + **/ +gboolean +g_volume_can_unmount (GVolume *volume) +{ + GVolumeIface *iface; + + g_return_val_if_fail (G_IS_VOLUME (volume), FALSE); + + iface = G_VOLUME_GET_IFACE (volume); + + return (* iface->can_unmount) (volume); +} + +/** + * g_volume_can_eject: + * @volume: + * + * Returns %TRUE if the @volume can be ejected. + * + **/ +gboolean +g_volume_can_eject (GVolume *volume) +{ + GVolumeIface *iface; + + g_return_val_if_fail (G_IS_VOLUME (volume), FALSE); + + iface = G_VOLUME_GET_IFACE (volume); + + return (* iface->can_eject) (volume); +} + +/** + * g_volume_unmount: + * @volume: + * @callback: + * @user_data: + * + * + **/ +void +g_volume_unmount (GVolume *volume, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GVolumeIface *iface; + + g_return_if_fail (G_IS_VOLUME (volume)); + + iface = G_VOLUME_GET_IFACE (volume); + + if (iface->unmount == NULL) + { + g_simple_async_report_error_in_idle (G_OBJECT (volume), + callback, user_data, + G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, + _("volume doesn't implement unmount")); + + return; + } + + (* iface->unmount) (volume, cancellable, callback, user_data); +} + +/** + * g_volume_unmount_finish: + * @volume: + * @result: + * @error: a #GError location to store the error occuring, or %NULL to + * ignore. + * Return: + * + **/ +gboolean +g_volume_unmount_finish (GVolume *volume, + GAsyncResult *result, + GError **error) +{ + GVolumeIface *iface; + + g_return_val_if_fail (G_IS_VOLUME (volume), FALSE); + g_return_val_if_fail (G_IS_ASYNC_RESULT (result), FALSE); + + if (G_IS_SIMPLE_ASYNC_RESULT (result)) + { + GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (result); + if (g_simple_async_result_propagate_error (simple, error)) + return FALSE; + } + + iface = G_VOLUME_GET_IFACE (volume); + return (* iface->unmount_finish) (volume, result, error); +} + +/** + * g_volume_eject: + * @volume: + * @callback: + * @user_data: + * + **/ +void +g_volume_eject (GVolume *volume, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GVolumeIface *iface; + + g_return_if_fail (G_IS_VOLUME (volume)); + + iface = G_VOLUME_GET_IFACE (volume); + + if (iface->eject == NULL) + { + g_simple_async_report_error_in_idle (G_OBJECT (volume), + callback, user_data, + G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, + _("volume doesn't implement eject")); + + return; + } + + (* iface->eject) (volume, cancellable, callback, user_data); +} + +/** + * g_volume_eject_finish: + * @volume: + * @result: + * @error: a #GError location to store the error occuring, or %NULL to + * ignore. + * Returns: + * + **/ +gboolean +g_volume_eject_finish (GVolume *volume, + GAsyncResult *result, + GError **error) +{ + GVolumeIface *iface; + + g_return_val_if_fail (G_IS_VOLUME (volume), FALSE); + g_return_val_if_fail (G_IS_ASYNC_RESULT (result), FALSE); + + if (G_IS_SIMPLE_ASYNC_RESULT (result)) + { + GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (result); + if (g_simple_async_result_propagate_error (simple, error)) + return FALSE; + } + + iface = G_VOLUME_GET_IFACE (volume); + return (* iface->eject_finish) (volume, result, error); +} diff --git a/gio/gvolume.h b/gio/gvolume.h new file mode 100644 index 000000000..38addbe19 --- /dev/null +++ b/gio/gvolume.h @@ -0,0 +1,97 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#ifndef __G_VOLUME_H__ +#define __G_VOLUME_H__ + +#include <glib-object.h> +#include <gio/gfile.h> + +G_BEGIN_DECLS + +#define G_TYPE_VOLUME (g_volume_get_type ()) +#define G_VOLUME(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), G_TYPE_VOLUME, GVolume)) +#define G_IS_VOLUME(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), G_TYPE_VOLUME)) +#define G_VOLUME_GET_IFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), G_TYPE_VOLUME, GVolumeIface)) + +/* GVolume typedef is in gfile.h due to include order issues */ +typedef struct _GDrive GDrive; /* Dummy typedef */ +typedef struct _GVolumeIface GVolumeIface; + +struct _GVolumeIface +{ + GTypeInterface g_iface; + + /* signals */ + + void (*changed) (GVolume *volume); + + /* Virtual Table */ + + GFile * (*get_root) (GVolume *volume); + char * (*get_name) (GVolume *volume); + GIcon * (*get_icon) (GVolume *volume); + GDrive * (*get_drive) (GVolume *volume); + gboolean (*can_unmount) (GVolume *volume); + gboolean (*can_eject) (GVolume *volume); + void (*unmount) (GVolume *volume, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + gboolean (*unmount_finish) (GVolume *volume, + GAsyncResult *result, + GError **error); + void (*eject) (GVolume *volume, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + gboolean (*eject_finish) (GVolume *volume, + GAsyncResult *result, + GError **error); +}; + +GType g_volume_get_type (void) G_GNUC_CONST; + +GFile *g_volume_get_root (GVolume *volume); +char * g_volume_get_name (GVolume *volume); +GIcon * g_volume_get_icon (GVolume *volume); +GDrive * g_volume_get_drive (GVolume *volume); +gboolean g_volume_can_unmount (GVolume *volume); +gboolean g_volume_can_eject (GVolume *volume); +void g_volume_unmount (GVolume *volume, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean g_volume_unmount_finish (GVolume *volume, + GAsyncResult *result, + GError **error); +void g_volume_eject (GVolume *volume, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean g_volume_eject_finish (GVolume *volume, + GAsyncResult *result, + GError **error); + +G_END_DECLS + +#endif /* __G_VOLUME_H__ */ diff --git a/gio/gvolumemonitor.c b/gio/gvolumemonitor.c new file mode 100644 index 000000000..a1b7c9b82 --- /dev/null +++ b/gio/gvolumemonitor.c @@ -0,0 +1,143 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#include <config.h> +#include "gvolumemonitor.h" +#include "glibintl.h" + +G_DEFINE_TYPE (GVolumeMonitor, g_volume_monitor, G_TYPE_OBJECT); + +enum { + VOLUME_MOUNTED, + VOLUME_PRE_UNMOUNT, + VOLUME_UNMOUNTED, + DRIVE_CONNECTED, + DRIVE_DISCONNECTED, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL] = { 0 }; + + +static void +g_volume_monitor_finalize (GObject *object) +{ + GVolumeMonitor *monitor; + + monitor = G_VOLUME_MONITOR (object); + + if (G_OBJECT_CLASS (g_volume_monitor_parent_class)->finalize) + (*G_OBJECT_CLASS (g_volume_monitor_parent_class)->finalize) (object); +} + +static void +g_volume_monitor_class_init (GVolumeMonitorClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->finalize = g_volume_monitor_finalize; + + signals[VOLUME_MOUNTED] = g_signal_new (I_("volume_mounted"), + G_TYPE_VOLUME_MONITOR, + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GVolumeMonitorClass, volume_mounted), + NULL, NULL, + g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, 1, G_TYPE_VOLUME); + + signals[VOLUME_PRE_UNMOUNT] = g_signal_new (I_("volume_pre_unmount"), + G_TYPE_VOLUME_MONITOR, + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GVolumeMonitorClass, volume_pre_unmount), + NULL, NULL, + g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, 1, G_TYPE_VOLUME); + + signals[VOLUME_UNMOUNTED] = g_signal_new (I_("volume_unmounted"), + G_TYPE_VOLUME_MONITOR, + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GVolumeMonitorClass, volume_unmounted), + NULL, NULL, + g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, 1, G_TYPE_VOLUME); + + signals[DRIVE_CONNECTED] = g_signal_new (I_("drive_connected"), + G_TYPE_VOLUME_MONITOR, + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GVolumeMonitorClass, drive_connected), + NULL, NULL, + g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, 1, G_TYPE_DRIVE); + + + signals[DRIVE_DISCONNECTED] = g_signal_new (I_("drive_disconnected"), + G_TYPE_VOLUME_MONITOR, + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GVolumeMonitorClass, drive_disconnected), + NULL, NULL, + g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, 1, G_TYPE_DRIVE); +} + +static void +g_volume_monitor_init (GVolumeMonitor *monitor) +{ +} + +/** + * g_volume_monitor_get_mounted_volumes: + * @volume_monitor: a #GVolumeMonitor. + * + * Returns a #GList of mounted #GVolumes. + * + **/ +GList * +g_volume_monitor_get_mounted_volumes (GVolumeMonitor *volume_monitor) +{ + GVolumeMonitorClass *class; + + g_return_val_if_fail (G_IS_VOLUME_MONITOR (volume_monitor), NULL); + + class = G_VOLUME_MONITOR_GET_CLASS (volume_monitor); + + return class->get_mounted_volumes (volume_monitor); +} + +/** + * g_volume_monitor_get_connected_drives: + * @volume_monitor: a #GVolumeMonitor. + * + * Returns a #GList of connected #GDrives. + * + **/ +GList * +g_volume_monitor_get_connected_drives (GVolumeMonitor *volume_monitor) +{ + GVolumeMonitorClass *class; + + g_return_val_if_fail (G_IS_VOLUME_MONITOR (volume_monitor), NULL); + + class = G_VOLUME_MONITOR_GET_CLASS (volume_monitor); + + return class->get_connected_drives (volume_monitor); +} + diff --git a/gio/gvolumemonitor.h b/gio/gvolumemonitor.h new file mode 100644 index 000000000..ef990a1c4 --- /dev/null +++ b/gio/gvolumemonitor.h @@ -0,0 +1,88 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#ifndef __G_VOLUME_MONITOR_H__ +#define __G_VOLUME_MONITOR_H__ + +#include <glib-object.h> +#include <gio/gvolume.h> +#include <gio/gdrive.h> + +G_BEGIN_DECLS + +#define G_TYPE_VOLUME_MONITOR (g_volume_monitor_get_type ()) +#define G_VOLUME_MONITOR(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_VOLUME_MONITOR, GVolumeMonitor)) +#define G_VOLUME_MONITOR_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), G_TYPE_VOLUME_MONITOR, GVolumeMonitorClass)) +#define G_VOLUME_MONITOR_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_TYPE_VOLUME_MONITOR, GVolumeMonitorClass)) +#define G_IS_VOLUME_MONITOR(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_VOLUME_MONITOR)) +#define G_IS_VOLUME_MONITOR_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_VOLUME_MONITOR)) + +typedef struct _GVolumeMonitor GVolumeMonitor; +typedef struct _GVolumeMonitorClass GVolumeMonitorClass; + +struct _GVolumeMonitor { + GObject parent; + gpointer priv; +}; + +struct _GVolumeMonitorClass { + GObjectClass parent_class; + + /*< public >*/ + /* signals */ + void (* volume_mounted) (GVolumeMonitor *volume_monitor, + GVolume *volume); + void (* volume_pre_unmount) (GVolumeMonitor *volume_monitor, + GVolume *volume); + void (* volume_unmounted) (GVolumeMonitor *volume_monitor, + GVolume *volume); + void (* drive_connected) (GVolumeMonitor *volume_monitor, + GDrive *drive); + void (* drive_disconnected) (GVolumeMonitor *volume_monitor, + GDrive *drive); + + /* Vtable */ + + GList * (*get_mounted_volumes) (GVolumeMonitor *volume_monitor); + GList * (*get_connected_drives) (GVolumeMonitor *volume_monitor); + + + /* Padding for future expansion */ + void (*_g_reserved1) (void); + void (*_g_reserved2) (void); + void (*_g_reserved3) (void); + void (*_g_reserved4) (void); + void (*_g_reserved5) (void); + void (*_g_reserved6) (void); + void (*_g_reserved7) (void); + void (*_g_reserved8) (void); +}; + +GType g_volume_monitor_get_type (void) G_GNUC_CONST; + +GVolumeMonitor *g_volume_monitor_get (void); +GList * g_volume_monitor_get_mounted_volumes (GVolumeMonitor *volume_monitor); +GList * g_volume_monitor_get_connected_drives (GVolumeMonitor *volume_monitor); + +G_END_DECLS + +#endif /* __G_VOLUME_MONITOR_H__ */ diff --git a/gio/gvolumeprivate.h b/gio/gvolumeprivate.h new file mode 100644 index 000000000..ce959de82 --- /dev/null +++ b/gio/gvolumeprivate.h @@ -0,0 +1,34 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#ifndef __G_VOLUMEPRIV_H__ +#define __G_VOLUMEPRIV_H__ + +#include <gio/gvolume.h> + +G_BEGIN_DECLS + +GVolume *g_volume_get_for_mount_path (const char *mountpoint); + +G_END_DECLS + +#endif /* __G_VOLUMEPRIV_H__ */ diff --git a/gio/gwin32appinfo.c b/gio/gwin32appinfo.c new file mode 100644 index 000000000..946b55943 --- /dev/null +++ b/gio/gwin32appinfo.c @@ -0,0 +1,672 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#include <config.h> + +#include <string.h> + +#include "gcontenttypeprivate.h" +#include "gwin32appinfo.h" +#include "gioerror.h" +#include <glib/gstdio.h> +#include "glibintl.h" + +#include <windows.h> +#include <shlwapi.h> + +#ifndef ASSOCF_INIT_BYEXENAME +#define ASSOCF_INIT_BYEXENAME 0x00000002 +#endif + +/* These were wrong in MingW */ +#define REAL_ASSOCSTR_COMMAND 1 +#define REAL_ASSOCSTR_EXECUTABLE 2 +#define REAL_ASSOCSTR_FRIENDLYDOCNAME 3 +#define REAL_ASSOCSTR_FRIENDLYAPPNAME 4 + + +static void g_win32_app_info_iface_init (GAppInfoIface *iface); + +struct _GWin32AppInfo +{ + GObject parent_instance; + wchar_t *id; + char *id_utf8; + gboolean id_is_exename; + char *executable; + char *name; + gboolean no_open_with; +}; + +G_DEFINE_TYPE_WITH_CODE (GWin32AppInfo, g_win32_app_info, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (G_TYPE_APP_INFO, + g_win32_app_info_iface_init)) + + +static void +g_win32_app_info_finalize (GObject *object) +{ + GWin32AppInfo *info; + + info = G_WIN32_APP_INFO (object); + + g_free (info->id); + g_free (info->id_utf8); + g_free (info->name); + g_free (info->executable); + + if (G_OBJECT_CLASS (g_win32_app_info_parent_class)->finalize) + (*G_OBJECT_CLASS (g_win32_app_info_parent_class)->finalize) (object); +} + +static void +g_win32_app_info_class_init (GWin32AppInfoClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->finalize = g_win32_app_info_finalize; +} + +static void +g_win32_app_info_init (GWin32AppInfo *local) +{ +} + +static GAppInfo * +g_desktop_app_info_new_from_id (wchar_t *id /* takes ownership */, + gboolean id_is_exename) +{ + ASSOCF flags; + wchar_t buffer[1024]; + DWORD buffer_size; + GWin32AppInfo *info; + HKEY app_key; + + info = g_object_new (G_TYPE_WIN32_APP_INFO, NULL); + info->id = id; /* Takes ownership */ + info->id_utf8 = g_utf16_to_utf8 (id, -1, NULL, NULL, NULL); + info->id_is_exename = id_is_exename; + + flags = 0; + if (id_is_exename) + flags |= ASSOCF_INIT_BYEXENAME; + + buffer_size = 1024; + if (AssocQueryStringW(flags, + REAL_ASSOCSTR_EXECUTABLE, + id, + NULL, + buffer, + &buffer_size) == S_OK) + info->executable = g_utf16_to_utf8 (buffer, -1, NULL, NULL, NULL); + + buffer_size = 1024; + if (AssocQueryStringW(flags, + REAL_ASSOCSTR_FRIENDLYAPPNAME, + id, + NULL, + buffer, + &buffer_size) == S_OK) + info->name = g_utf16_to_utf8 (buffer, -1, NULL, NULL, NULL); + + if (info->name == NULL) + { + /* TODO: Should look up name from executable resources */ + if (info->executable) + info->name = g_path_get_basename (info->executable); + else + info->name = g_strdup (info->id_utf8); + } + + if (AssocQueryKeyW(flags, + ASSOCKEY_APP, + info->id, + NULL, + &app_key) == S_OK) + { + if (RegQueryValueExW (app_key, L"NoOpenWith", 0, + NULL, NULL, NULL) == ERROR_SUCCESS) + info->no_open_with = TRUE; + RegCloseKey (app_key); + } + + return G_APP_INFO (info); +} + +static wchar_t * +dup_wstring (wchar_t *str) +{ + gsize len; + for (len = 0; str[len] != 0; len++) + ; + return (wchar_t *)g_memdup (str, (len + 1) * 2); +} + +static GAppInfo * +g_win32_app_info_dup (GAppInfo *appinfo) +{ + GWin32AppInfo *info = G_WIN32_APP_INFO (appinfo); + GWin32AppInfo *new_info; + + new_info = g_object_new (G_TYPE_WIN32_APP_INFO, NULL); + + new_info->id = dup_wstring (info->id); + new_info->id_utf8 = g_strdup (info->id_utf8); + new_info->id_is_exename = info->id_is_exename; + new_info->name = g_strdup (info->name); + new_info->executable = g_strdup (info->executable); + new_info->no_open_with = info->no_open_with; + + return G_APP_INFO (new_info); +} + +static gboolean +g_win32_app_info_equal (GAppInfo *appinfo1, + GAppInfo *appinfo2) +{ + GWin32AppInfo *info1 = G_WIN32_APP_INFO (appinfo1); + GWin32AppInfo *info2 = G_WIN32_APP_INFO (appinfo2); + + if (info1->executable == NULL || + info2->executable == NULL) + return FALSE; + + return strcmp (info1->executable, info2->executable) == 0; +} + +static const char * +g_win32_app_info_get_id (GAppInfo *appinfo) +{ + GWin32AppInfo *info = G_WIN32_APP_INFO (appinfo); + + return info->id_utf8; +} + +static const char * +g_win32_app_info_get_name (GAppInfo *appinfo) +{ + GWin32AppInfo *info = G_WIN32_APP_INFO (appinfo); + + if (info->name == NULL) + return _("Unnamed"); + + return info->name; +} + +static const char * +g_win32_app_info_get_description (GAppInfo *appinfo) +{ + /* Win32 has no app descriptions */ + return NULL; +} + +static const char * +g_win32_app_info_get_executable (GAppInfo *appinfo) +{ + GWin32AppInfo *info = G_WIN32_APP_INFO (appinfo); + + return info->executable; +} + +static const char * +g_win32_app_info_get_icon (GAppInfo *appinfo) +{ + /* GWin32AppInfo *info = G_WIN32_APP_INFO (appinfo); */ + + /* TODO: How to handle icons */ + return NULL; +} + +static gboolean +g_win32_app_info_launch (GAppInfo *appinfo, + GList *files, + GAppLaunchContext *launch_context, + GError **error) +{ + GWin32AppInfo *info = G_WIN32_APP_INFO (appinfo); + ASSOCF flags; + HKEY class_key; + SHELLEXECUTEINFOW exec_info = {0}; + GList *l; + + /* TODO: What might startup_id mean on win32? */ + + flags = 0; + if (info->id_is_exename) + flags |= ASSOCF_INIT_BYEXENAME; + + if (AssocQueryKeyW(flags, + ASSOCKEY_SHELLEXECCLASS, + info->id, + NULL, + &class_key) != S_OK) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, _("Can't find application")); + return FALSE; + } + + for (l = file; l != NULL; l = l->next) + { + char *path = g_file_get_path (l->data); + wchar_t *wfilename = g_utf8_to_utf16 (path, -1, NULL, NULL, NULL); + + g_free (path); + + memset (&exec_info, 0, sizeof (exec_info)); + exec_info.cbSize = sizeof (exec_info); + exec_info.fMask = SEE_MASK_FLAG_DDEWAIT | SEE_MASK_CLASSKEY; + exec_info.lpFile = wfilename; + exec_info.nShow = SW_SHOWNORMAL; + exec_info.hkeyClass = class_key; + + if (!ShellExecuteExW(&exec_info)) + { + DWORD last_error; + LPVOID message; + char *message_utf8; + + last_error = GetLastError (); + FormatMessage (FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM, + NULL, + last_error, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPTSTR) &message, + 0, NULL ); + + message_utf8 = g_utf16_to_utf8 (message, -1, NULL, NULL, NULL); + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, _("Error launching application: %s"), message_utf8); + g_free (message_utf8); + LocalFree (message); + + g_free (wfilename); + RegCloseKey (class_key); + return FALSE; + } + + g_free (wfilename); + } + + RegCloseKey (class_key); + + return TRUE; +} + +static gboolean +g_win32_app_info_supports_uris (GAppInfo *appinfo) +{ + return FALSE; +} + +static gboolean +g_win32_app_info_launch_uris (GAppInfo *appinfo, + GList *uris, + GAppLaunchContext *launch_context, + GError **error) +{ + g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, _("URIs not supported")); + return FALSE; +} + +static gboolean +g_win32_app_info_should_show (GAppInfo *appinfo, + const char *win32_env) +{ + GWin32AppInfo *info = G_WIN32_APP_INFO (appinfo); + + if (info->no_open_with) + return FALSE; + + return TRUE; +} + +static gboolean +g_win32_app_info_set_as_default_for_type (GAppInfo *appinfo, + const char *content_type, + GError **error) +{ + g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, _("association changes not supported on win32")); + return FALSE; +} + +GAppInfo * +g_app_info_create_from_commandline (const char *commandline, + const char *application_name, + GAppInfoCreateFlags flags, + GError **error) +{ + g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, _("Association creation not supported on win32")); + return NULL; +} + + +static void +g_win32_app_info_iface_init (GAppInfoIface *iface) +{ + iface->dup = g_win32_app_info_dup; + iface->equal = g_win32_app_info_equal; + iface->get_id = g_win32_app_info_get_id; + iface->get_name = g_win32_app_info_get_name; + iface->get_description = g_win32_app_info_get_description; + iface->get_executable = g_win32_app_info_get_executable; + iface->get_icon = g_win32_app_info_get_icon; + iface->launch = g_win32_app_info_launch; + iface->supports_uris = g_win32_app_info_supports_uris; + iface->launch_uris = g_win32_app_info_launch_uris; + iface->should_show = g_win32_app_info_should_show; + iface->set_as_default_for_type = g_win32_app_info_set_as_default_for_type; +} + +static void +enumerate_open_with_list (HKEY dir_key, + GList **prognames) +{ + DWORD index; + wchar_t name[256]; + DWORD name_len, nbytes; + wchar_t data[256]; + wchar_t *data_alloc; + DWORD type; + + /* Must also look inside for a,b,c, + MRUList */ + index = 0; + name_len = 256; + nbytes = sizeof (data) - 2; + while (RegEnumValueW(dir_key, + index, + name, + &name_len, + 0, + &type, + (LPBYTE)data, + &nbytes) == ERROR_SUCCESS) + { + data[nbytes/2] = '\0'; + if (type == REG_SZ && + /* Ignore things like MRUList, just look at 'a', 'b', 'c', etc */ + name_len == 1) + { + data_alloc = (wchar_t *)g_memdup (data, nbytes + 2); + data_alloc[nbytes/2] = 0; + *prognames = g_list_prepend (*prognames, data_alloc); + } + index++; + name_len = 256; + nbytes = sizeof (data) - 2; + } + + index = 0; + name_len = 256; + while (RegEnumKeyExW(dir_key, + index, + name, + &name_len, + NULL, + NULL, + NULL, + NULL) == ERROR_SUCCESS) + { + *prognames = g_list_prepend (*prognames, g_memdup (name, (name_len + 1) * 2)); + index++; + name_len = 256; + } +} + +static void +enumerate_open_with_progids (HKEY dir_key, + GList **progids) +{ + DWORD index; + wchar_t name[256]; + DWORD name_len, type; + + index = 0; + name_len = 256; + while (RegEnumValueW(dir_key, + index, + name, + &name_len, + 0, + &type, + NULL, + 0) == ERROR_SUCCESS) + { + *progids = g_list_prepend (*progids, g_memdup (name, (name_len + 1) * 2)); + index++; + name_len = 256; + } +} + +static void +enumerate_open_with_root (HKEY dir_key, + GList **progids, + GList **prognames) +{ + HKEY reg_key = NULL; + + if (RegOpenKeyExW (dir_key, L"OpenWithList", 0, + KEY_READ, ®_key) == ERROR_SUCCESS) + { + enumerate_open_with_list (reg_key, prognames); + RegCloseKey (reg_key); + } + + if (RegOpenKeyExW (dir_key, L"OpenWithProgids", 0, + KEY_QUERY_VALUE, ®_key) == ERROR_SUCCESS) + { + enumerate_open_with_progids (reg_key, progids); + RegCloseKey (reg_key); + } +} + +static gboolean +app_info_in_list (GAppInfo *info, GList *l) +{ + while (l != NULL) + { + if (g_app_info_equal (info, l->data)) + return TRUE; + l = l->next; + } + return FALSE; +} + +/** + * g_app_info_get_all_for_type: + * @content_type: + * + * Returns a #GList of #GAppInfo for a given @content_type. + * + **/ +GList * +g_app_info_get_all_for_type (const char *content_type) +{ + GList *progids = NULL; + GList *prognames = NULL; + HKEY reg_key, sys_file_assoc_key, reg_key2; + wchar_t percieved_type[128]; + DWORD nchars, key_type; + wchar_t *wc_key; + GList *l; + GList *infos; + + wc_key = g_utf8_to_utf16 (content_type, -1, NULL, NULL, NULL); + if (RegOpenKeyExW (HKEY_CLASSES_ROOT, wc_key, 0, + KEY_QUERY_VALUE, ®_key) == ERROR_SUCCESS) + { + enumerate_open_with_root (reg_key, &progids, &prognames); + + nchars = sizeof (percieved_type) / sizeof(wchar_t); + if (RegQueryValueExW (reg_key, L"PerceivedType", 0, + &key_type, (LPBYTE) percieved_type, &nchars) == ERROR_SUCCESS) + { + if (key_type == REG_SZ && + RegOpenKeyExW (HKEY_CLASSES_ROOT, L"SystemFileAssociations", 0, + KEY_QUERY_VALUE, &sys_file_assoc_key) == ERROR_SUCCESS) + { + if (RegOpenKeyExW (sys_file_assoc_key, percieved_type, 0, + KEY_QUERY_VALUE, ®_key2) == ERROR_SUCCESS) + { + enumerate_open_with_root (reg_key2, &progids, &prognames); + RegCloseKey (reg_key2); + } + + RegCloseKey (sys_file_assoc_key); + } + } + RegCloseKey (reg_key); + } + + if (RegOpenKeyExW (HKEY_CURRENT_USER, L"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\FileExts", 0, + KEY_QUERY_VALUE, ®_key) == ERROR_SUCCESS) + { + if (RegOpenKeyExW (reg_key, wc_key, 0, + KEY_QUERY_VALUE, ®_key2) == ERROR_SUCCESS) + { + enumerate_open_with_root (reg_key2, &progids, &prognames); + RegCloseKey (reg_key2); + } + + RegCloseKey (reg_key); + } + + infos = NULL; + for (l = prognames; l != NULL; l = l->next) + { + GAppInfo *info; + + /* l->data ownership is taken */ + info = g_desktop_app_info_new_from_id ((wchar_t *)l->data, TRUE); + if (app_info_in_list (info, infos)) + g_object_unref (info); + else + infos = g_list_prepend (infos, info); + } + g_list_free (prognames); + + for (l = progids; l != NULL; l = l->next) + { + GAppInfo *info; + + /* l->data ownership is taken */ + info = g_desktop_app_info_new_from_id ((wchar_t *)l->data, FALSE); + if (app_info_in_list (info, infos)) + g_object_unref (info); + else + infos = g_list_prepend (infos, info); + } + g_list_free (progids); + + g_free (wc_key); + return g_list_reverse (infos); +} + +/** + * g_app_info_get_default_for_type: + * @content_type: + * @must_support_uris: + * + * Returns the default #GAppInfo for the given @content_type. If + * @must_support_uris is true, the #GAppInfo is expected to support + * URIs. + * + **/ +GAppInfo * +g_app_info_get_default_for_type (const char *content_type, + gboolean must_support_uris) +{ + wchar_t *wtype; + wchar_t buffer[1024]; + DWORD buffer_size; + + wtype = g_utf8_to_utf16 (content_type, -1, NULL, NULL, NULL); + + /* Verify that we have some sort of app registered for this type */ + buffer_size = 1024; + if (AssocQueryStringW(0, + REAL_ASSOCSTR_COMMAND, + wtype, + NULL, + buffer, + &buffer_size) == S_OK) + /* Takes ownership of wtype */ + return g_desktop_app_info_new_from_id (wtype, FALSE); + + g_free (wtype); + return NULL; +} + +/** + * g_app_info_get_default_for_uri_scheme: + * @uri_scheme: + * + **/ +GAppInfo * +g_app_info_get_default_for_uri_scheme (const char *uri_scheme) +{ + /* TODO: Implement */ + return NULL; +} + +/** + * g_app_info_get_all: + * + **/ +GList * +g_app_info_get_all (void) +{ + DWORD index; + wchar_t name[256]; + DWORD name_len; + HKEY reg_key; + GList *infos; + GAppInfo *info; + + if (RegOpenKeyExW (HKEY_CLASSES_ROOT, L"Applications", 0, + KEY_READ, ®_key) != ERROR_SUCCESS) + return NULL; + + infos = NULL; + index = 0; + name_len = 256; + while (RegEnumKeyExW(reg_key, + index, + name, + &name_len, + NULL, + NULL, + NULL, + NULL) == ERROR_SUCCESS) + { + wchar_t *name_dup = g_memdup (name, (name_len+1)*2); + /* name_dup ownership is taken */ + info = g_desktop_app_info_new_from_id (name_dup, TRUE); + infos = g_list_prepend (infos, info); + + index++; + name_len = 256; + } + + RegCloseKey (reg_key); + + return g_list_reverse (infos); +} diff --git a/gio/gwin32appinfo.h b/gio/gwin32appinfo.h new file mode 100644 index 000000000..cfec62f1e --- /dev/null +++ b/gio/gwin32appinfo.h @@ -0,0 +1,50 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#ifndef __G_WIN32_APP_INFO_H__ +#define __G_WIN32_APP_INFO_H__ + +#include <gio/gappinfo.h> + +G_BEGIN_DECLS + +#define G_TYPE_WIN32_APP_INFO (g_win32_app_info_get_type ()) +#define G_WIN32_APP_INFO(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_WIN32_APP_INFO, GWin32AppInfo)) +#define G_WIN32_APP_INFO_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), G_TYPE_WIN32_APP_INFO, GWin32AppInfoClass)) +#define G_IS_WIN32_APP_INFO(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_WIN32_APP_INFO)) +#define G_IS_WIN32_APP_INFO_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_WIN32_APP_INFO)) +#define G_WIN32_APP_INFO_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_TYPE_WIN32_APP_INFO, GWin32AppInfoClass)) + +typedef struct _GWin32AppInfo GWin32AppInfo; +typedef struct _GWin32AppInfoClass GWin32AppInfoClass; + +struct _GWin32AppInfoClass +{ + GObjectClass parent_class; +}; + +GType g_win32_app_info_get_type (void) G_GNUC_CONST; + +G_END_DECLS + + +#endif /* __G_WIN32_APP_INFO_H__ */ diff --git a/gio/inotify/Makefile.am b/gio/inotify/Makefile.am new file mode 100644 index 000000000..191f27dd2 --- /dev/null +++ b/gio/inotify/Makefile.am @@ -0,0 +1,34 @@ +NULL = + +noinst_LTLIBRARIES = libinotify.la + +libinotify_la_SOURCES = \ + inotify-kernel.c \ + inotify-sub.c \ + inotify-path.c \ + inotify-missing.c \ + inotify-helper.c \ + inotify-diag.c \ + inotify-diag.h \ + inotify-kernel.h \ + inotify-missing.h \ + inotify-path.h \ + inotify-sub.h \ + inotify-helper.h \ + local_inotify.h \ + local_inotify_syscalls.h \ + ginotifyfilemonitor.c \ + ginotifyfilemonitor.h \ + ginotifydirectorymonitor.c \ + ginotifydirectorymonitor.h \ + $(NULL) + +libinotify_la_CFLAGS = \ + -DG_LOG_DOMAIN=\"GLib-GIO\" \ + -I$(top_srcdir) \ + -I$(top_srcdir)/glib \ + -I$(top_srcdir)/gmodule \ + -I$(top_srcdir)/gio \ + -DGIO_MODULE_DIR=\"$(libdir)/gio/modules\" \ + -DG_DISABLE_DEPRECATED + diff --git a/gio/inotify/ginotifydirectorymonitor.c b/gio/inotify/ginotifydirectorymonitor.c new file mode 100644 index 000000000..bec36093c --- /dev/null +++ b/gio/inotify/ginotifydirectorymonitor.c @@ -0,0 +1,144 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * Copyright (C) 2007 Sebastian Dröge. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Authors: Alexander Larsson <alexl@redhat.com> + * John McCutchan <john@johnmccutchan.com> + * Sebastian Dröge <slomo@circular-chaos.org> + */ + +#include <config.h> + +#include "ginotifydirectorymonitor.h" +#include "giomodule.h" + +#define USE_INOTIFY 1 +#include "inotify-helper.h" + +struct _GInotifyDirectoryMonitor +{ + GLocalDirectoryMonitor parent_instance; + inotify_sub *sub; +}; + +static gboolean g_inotify_directory_monitor_cancel (GDirectoryMonitor* monitor); + +G_DEFINE_TYPE (GInotifyDirectoryMonitor, g_inotify_directory_monitor, G_TYPE_LOCAL_DIRECTORY_MONITOR) + +static void +g_inotify_directory_monitor_finalize (GObject *object) +{ + GInotifyDirectoryMonitor *inotify_monitor = G_INOTIFY_DIRECTORY_MONITOR (object); + inotify_sub *sub = inotify_monitor->sub; + + if (inotify_monitor->sub) + { + _ih_sub_cancel (sub); + _ih_sub_free (sub); + inotify_monitor->sub = NULL; + } + + if (G_OBJECT_CLASS (g_inotify_directory_monitor_parent_class)->finalize) + (*G_OBJECT_CLASS (g_inotify_directory_monitor_parent_class)->finalize) (object); +} + +static GObject * +g_inotify_directory_monitor_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_properties) +{ + GObject *obj; + GInotifyDirectoryMonitorClass *klass; + GObjectClass *parent_class; + GInotifyDirectoryMonitor *inotify_monitor; + const gchar *dirname = NULL; + inotify_sub *sub = NULL; + + klass = G_INOTIFY_DIRECTORY_MONITOR_CLASS (g_type_class_peek (G_TYPE_INOTIFY_DIRECTORY_MONITOR)); + parent_class = G_OBJECT_CLASS (g_type_class_peek_parent (klass)); + obj = parent_class->constructor (type, + n_construct_properties, + construct_properties); + + inotify_monitor = G_INOTIFY_DIRECTORY_MONITOR (obj); + + dirname = G_LOCAL_DIRECTORY_MONITOR (obj)->dirname; + g_assert (dirname != NULL); + + /* Will never fail as is_supported() should be called before instanciating + * anyway */ + g_assert (_ih_startup ()); + + sub = _ih_sub_new (dirname, NULL, inotify_monitor); + /* FIXME: what to do about errors here? we can't return NULL or another + * kind of error and an assertion is probably too hard */ + g_assert (sub != NULL); + g_assert (_ih_sub_add (sub)); + + inotify_monitor->sub = sub; + + return obj; +} + +static gboolean +g_inotify_directory_monitor_is_supported (void) +{ + return _ih_startup (); +} + +static void +g_inotify_directory_monitor_class_init (GInotifyDirectoryMonitorClass* klass) +{ + GObjectClass* gobject_class = G_OBJECT_CLASS (klass); + GDirectoryMonitorClass *directory_monitor_class = G_DIRECTORY_MONITOR_CLASS (klass); + GLocalDirectoryMonitorClass *local_directory_monitor_class = G_LOCAL_DIRECTORY_MONITOR_CLASS (klass); + + gobject_class->finalize = g_inotify_directory_monitor_finalize; + gobject_class->constructor = g_inotify_directory_monitor_constructor; + directory_monitor_class->cancel = g_inotify_directory_monitor_cancel; + + local_directory_monitor_class->prio = 20; + local_directory_monitor_class->mount_notify = TRUE; + local_directory_monitor_class->is_supported = g_inotify_directory_monitor_is_supported; +} + +static void +g_inotify_directory_monitor_init (GInotifyDirectoryMonitor* monitor) +{ + +} + +static gboolean +g_inotify_directory_monitor_cancel (GDirectoryMonitor* monitor) +{ + GInotifyDirectoryMonitor *inotify_monitor = G_INOTIFY_DIRECTORY_MONITOR (monitor); + inotify_sub *sub = inotify_monitor->sub; + + if (sub) { + _ih_sub_cancel (sub); + _ih_sub_free (sub); + inotify_monitor->sub = NULL; + } + + if (G_DIRECTORY_MONITOR_CLASS (g_inotify_directory_monitor_parent_class)->cancel) + (*G_DIRECTORY_MONITOR_CLASS (g_inotify_directory_monitor_parent_class)->cancel) (monitor); + + return TRUE; +} + diff --git a/gio/inotify/ginotifydirectorymonitor.h b/gio/inotify/ginotifydirectorymonitor.h new file mode 100644 index 000000000..bc170983a --- /dev/null +++ b/gio/inotify/ginotifydirectorymonitor.h @@ -0,0 +1,54 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * Copyright (C) 2007 Sebastian Dröge. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Authors: Alexander Larsson <alexl@redhat.com> + * John McCutchan <john@johnmccutchan.com> + * Sebastian Dröge <slomo@circular-chaos.org> + */ + +#ifndef __G_INOTIFY_DIRECTORY_MONITOR_H__ +#define __G_INOTIFY_DIRECTORY_MONITOR_H__ + +#include <glib-object.h> +#include <string.h> +#include <gio/gdirectorymonitor.h> +#include "glocaldirectorymonitor.h" +#include "giomodule.h" + +G_BEGIN_DECLS + +#define G_TYPE_INOTIFY_DIRECTORY_MONITOR (g_inotify_directory_monitor_get_type ()) +#define G_INOTIFY_DIRECTORY_MONITOR(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_INOTIFY_DIRECTORY_MONITOR, GInotifyDirectoryMonitor)) +#define G_INOTIFY_DIRECTORY_MONITOR_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), G_TYPE_INOTIFY_DIRECTORY_MONITOR, GInotifyDirectoryMonitorClass)) +#define G_IS_INOTIFY_DIRECTORY_MONITOR(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_INOTIFY_DIRECTORY_MONITOR)) +#define G_IS_INOTIFY_DIRECTORY_MONITOR_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_INOTIFY_DIRECTORY_MONITOR)) + +typedef struct _GInotifyDirectoryMonitor GInotifyDirectoryMonitor; +typedef struct _GInotifyDirectoryMonitorClass GInotifyDirectoryMonitorClass; + +struct _GInotifyDirectoryMonitorClass { + GLocalDirectoryMonitorClass parent_class; +}; + +GType g_inotify_directory_monitor_get_type (void); + +G_END_DECLS + +#endif /* __G_INOTIFY_DIRECTORY_MONITOR_H__ */ diff --git a/gio/inotify/ginotifyfilemonitor.c b/gio/inotify/ginotifyfilemonitor.c new file mode 100644 index 000000000..4132116a0 --- /dev/null +++ b/gio/inotify/ginotifyfilemonitor.c @@ -0,0 +1,162 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * Copyright (C) 2007 Sebastian Dröge. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Authors: Alexander Larsson <alexl@redhat.com> + * John McCutchan <john@johnmccutchan.com> + * Sebastian Dröge <slomo@circular-chaos.org> + */ + +#include <config.h> + +#include "ginotifyfilemonitor.h" +#include <gio/giomodule.h> + +#define USE_INOTIFY 1 +#include "inotify-helper.h" + +struct _GInotifyFileMonitor +{ + GLocalFileMonitor parent_instance; + gchar *filename; + gchar *dirname; + inotify_sub *sub; +}; + +static gboolean g_inotify_file_monitor_cancel (GFileMonitor* monitor); + +G_DEFINE_TYPE (GInotifyFileMonitor, g_inotify_file_monitor, G_TYPE_LOCAL_FILE_MONITOR) + +static void +g_inotify_file_monitor_finalize (GObject *object) +{ + GInotifyFileMonitor *inotify_monitor = G_INOTIFY_FILE_MONITOR (object); + inotify_sub *sub = inotify_monitor->sub; + + if (inotify_monitor->sub) + { + _ih_sub_cancel (sub); + _ih_sub_free (sub); + inotify_monitor->sub = NULL; + } + + if (inotify_monitor->filename) + { + g_free (inotify_monitor->filename); + inotify_monitor->filename = NULL; + } + + if (inotify_monitor->dirname) + { + g_free (inotify_monitor->dirname); + inotify_monitor->dirname = NULL; + } + + if (G_OBJECT_CLASS (g_inotify_file_monitor_parent_class)->finalize) + (*G_OBJECT_CLASS (g_inotify_file_monitor_parent_class)->finalize) (object); +} + +static GObject * +g_inotify_file_monitor_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_properties) +{ + GObject *obj; + GInotifyFileMonitorClass *klass; + GObjectClass *parent_class; + GInotifyFileMonitor *inotify_monitor; + const gchar *filename = NULL; + inotify_sub *sub = NULL; + + klass = G_INOTIFY_FILE_MONITOR_CLASS (g_type_class_peek (G_TYPE_INOTIFY_FILE_MONITOR)); + parent_class = G_OBJECT_CLASS (g_type_class_peek_parent (klass)); + obj = parent_class->constructor (type, + n_construct_properties, + construct_properties); + + inotify_monitor = G_INOTIFY_FILE_MONITOR (obj); + + filename = G_LOCAL_FILE_MONITOR (obj)->filename; + + g_assert (filename != NULL); + + inotify_monitor->filename = g_path_get_basename (filename); + inotify_monitor->dirname = g_path_get_dirname (filename); + + /* Will never fail as is_supported() should be called before instanciating + * anyway */ + g_assert (_ih_startup ()); + + sub = _ih_sub_new (inotify_monitor->dirname, inotify_monitor->filename, inotify_monitor); + + /* FIXME: what to do about errors here? we can't return NULL or another + * kind of error and an assertion is probably too hard */ + g_assert (sub != NULL); + g_assert (_ih_sub_add (sub)); + + inotify_monitor->sub = sub; + + return obj; +} + +static gboolean +g_inotify_file_monitor_is_supported (void) +{ + return _ih_startup (); +} + +static void +g_inotify_file_monitor_class_init (GInotifyFileMonitorClass* klass) +{ + GObjectClass* gobject_class = G_OBJECT_CLASS (klass); + GFileMonitorClass *file_monitor_class = G_FILE_MONITOR_CLASS (klass); + GLocalFileMonitorClass *local_file_monitor_class = G_LOCAL_FILE_MONITOR_CLASS (klass); + + gobject_class->finalize = g_inotify_file_monitor_finalize; + gobject_class->constructor = g_inotify_file_monitor_constructor; + file_monitor_class->cancel = g_inotify_file_monitor_cancel; + + local_file_monitor_class->prio = 20; + local_file_monitor_class->is_supported = g_inotify_file_monitor_is_supported; +} + +static void +g_inotify_file_monitor_init (GInotifyFileMonitor* monitor) +{ + +} + +static gboolean +g_inotify_file_monitor_cancel (GFileMonitor* monitor) +{ + GInotifyFileMonitor *inotify_monitor = G_INOTIFY_FILE_MONITOR (monitor); + inotify_sub *sub = inotify_monitor->sub; + + if (sub) { + _ih_sub_cancel (sub); + _ih_sub_free (sub); + inotify_monitor->sub = NULL; + } + + if (G_FILE_MONITOR_CLASS (g_inotify_file_monitor_parent_class)->cancel) + (*G_FILE_MONITOR_CLASS (g_inotify_file_monitor_parent_class)->cancel) (monitor); + + return TRUE; +} + diff --git a/gio/inotify/ginotifyfilemonitor.h b/gio/inotify/ginotifyfilemonitor.h new file mode 100644 index 000000000..ae877bc7b --- /dev/null +++ b/gio/inotify/ginotifyfilemonitor.h @@ -0,0 +1,54 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * Copyright (C) 2007 Sebastian Dröge. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Authors: Alexander Larsson <alexl@redhat.com> + * John McCutchan <john@johnmccutchan.com> + * Sebastian Dröge <slomo@circular-chaos.org> + */ + +#ifndef __G_INOTIFY_FILE_MONITOR_H__ +#define __G_INOTIFY_FILE_MONITOR_H__ + +#include <glib-object.h> +#include <string.h> +#include <gio/gfilemonitor.h> +#include <gio/glocalfilemonitor.h> +#include <gio/giomodule.h> + +G_BEGIN_DECLS + +#define G_TYPE_INOTIFY_FILE_MONITOR (g_inotify_file_monitor_get_type ()) +#define G_INOTIFY_FILE_MONITOR(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_INOTIFY_FILE_MONITOR, GInotifyFileMonitor)) +#define G_INOTIFY_FILE_MONITOR_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), G_TYPE_INOTIFY_FILE_MONITOR, GInotifyFileMonitorClass)) +#define G_IS_INOTIFY_FILE_MONITOR(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_INOTIFY_FILE_MONITOR)) +#define G_IS_INOTIFY_FILE_MONITOR_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_INOTIFY_FILE_MONITOR)) + +typedef struct _GInotifyFileMonitor GInotifyFileMonitor; +typedef struct _GInotifyFileMonitorClass GInotifyFileMonitorClass; + +struct _GInotifyFileMonitorClass { + GLocalFileMonitorClass parent_class; +}; + +GType g_inotify_file_monitor_get_type (void); + +G_END_DECLS + +#endif /* __G_INOTIFY_FILE_MONITOR_H__ */ diff --git a/gio/inotify/inotify-diag.c b/gio/inotify/inotify-diag.c new file mode 100644 index 000000000..937ebd702 --- /dev/null +++ b/gio/inotify/inotify-diag.c @@ -0,0 +1,74 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 2; tab-width: 8 -*- */ + +/* inotify-helper.c - Gnome VFS Monitor based on inotify. + + Copyright (C) 2005 John McCutchan + + The Gnome Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The Gnome Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Gnome Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + Authors: + John McCutchan <john@johnmccutchan.com> +*/ + +#include "config.h" +#include <glib.h> +#include <sys/types.h> +#include <unistd.h> +#include "inotify-missing.h" +#include "inotify-path.h" +#include "inotify-diag.h" + +#define DIAG_DUMP_TIME 20000 /* 20 seconds */ + +G_LOCK_EXTERN (inotify_lock); + +static gboolean +id_dump (gpointer userdata) +{ + GIOChannel *ioc; + pid_t pid; + char *fname; + G_LOCK (inotify_lock); + ioc = NULL; + pid = getpid (); + + fname = g_strdup_printf ("/tmp/gvfsid.%d", pid); + ioc = g_io_channel_new_file (fname, "w", NULL); + g_free (fname); + + if (!ioc) + { + G_UNLOCK (inotify_lock); + return TRUE; + } + + _im_diag_dump (ioc); + + g_io_channel_shutdown (ioc, TRUE, NULL); + g_io_channel_unref (ioc); + + G_UNLOCK (inotify_lock); + return TRUE; +} + +void +_id_startup (void) +{ + if (!g_getenv ("GVFS_INOTIFY_DIAG")) + return; + + g_timeout_add (DIAG_DUMP_TIME, id_dump, NULL); +} diff --git a/gio/inotify/inotify-diag.h b/gio/inotify/inotify-diag.h new file mode 100644 index 000000000..f818f1616 --- /dev/null +++ b/gio/inotify/inotify-diag.h @@ -0,0 +1,29 @@ +/* inotify-helper.h - GNOME VFS Monitor using inotify + + Copyright (C) 2006 John McCutchan <john@johnmccutchan.com> + + The Gnome Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The Gnome Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Gnome Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + Author: John McCutchan <john@johnmccutchan.com> +*/ + + +#ifndef __INOTIFY_DIAG_H +#define __INOTIFY_DIAG_H + +void _id_startup (void); + +#endif /* __INOTIFY_DIAG_H */ diff --git a/gio/inotify/inotify-helper.c b/gio/inotify/inotify-helper.c new file mode 100644 index 000000000..82a68de2e --- /dev/null +++ b/gio/inotify/inotify-helper.c @@ -0,0 +1,264 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 2; tab-width: 8 -*- */ + +/* inotify-helper.c - GVFS Monitor based on inotify. + + Copyright (C) 2007 John McCutchan + + The Gnome Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The Gnome Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Gnome Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + Authors: + John McCutchan <john@johnmccutchan.com> +*/ + +#include "config.h" +#include <errno.h> +#include <time.h> +#include <string.h> +#include <sys/ioctl.h> +/* Just include the local header to stop all the pain */ +#include "local_inotify.h" +#if 0 +#ifdef HAVE_SYS_INOTIFY_H +/* We don't actually include the libc header, because there has been + * problems with libc versions that was built without inotify support. + * Instead we use the local version. + */ +#include "local_inotify.h" +#elif defined (HAVE_LINUX_INOTIFY_H) +#include <linux/inotify.h> +#endif +#endif +#include <gio/glocalfile.h> +#include <gio/gfilemonitor.h> +#include <gio/gdirectorymonitor.h> +#include "inotify-helper.h" +#include "inotify-missing.h" +#include "inotify-path.h" +#include "inotify-diag.h" + +static gboolean ih_debug_enabled = FALSE; +#define IH_W if (ih_debug_enabled) g_warning + +static void ih_event_callback (ik_event_t *event, inotify_sub *sub); +static void ih_not_missing_callback (inotify_sub *sub); + +/* We share this lock with inotify-kernel.c and inotify-missing.c + * + * inotify-kernel.c takes the lock when it reads events from + * the kernel and when it processes those events + * + * inotify-missing.c takes the lock when it is scanning the missing + * list. + * + * We take the lock in all public functions + */ +G_LOCK_DEFINE (inotify_lock); + +static GFileMonitorEvent ih_mask_to_EventFlags (guint32 mask); + +/** + * _ih_startup: + * + * Initializes the inotify backend. This must be called before + * any other functions in this module. + * + * Return value: #TRUE if initialization succeeded, #FALSE otherwise + */ +gboolean +_ih_startup (void) +{ + static gboolean initialized = FALSE; + static gboolean result = FALSE; + + G_LOCK (inotify_lock); + + if (initialized == TRUE) + { + G_UNLOCK (inotify_lock); + return result; + } + + result = _ip_startup (ih_event_callback); + if (!result) + { + g_warning ("Could not initialize inotify\n"); + G_UNLOCK (inotify_lock); + return FALSE; + } + _im_startup (ih_not_missing_callback); + _id_startup (); + + IH_W ("started gvfs inotify backend\n"); + + initialized = TRUE; + + G_UNLOCK (inotify_lock); + + return TRUE; +} + +/** + * Adds a subscription to be monitored. + */ +gboolean +_ih_sub_add (inotify_sub * sub) +{ + G_LOCK (inotify_lock); + + if (!_ip_start_watching (sub)) + _im_add (sub); + + G_UNLOCK (inotify_lock); + return TRUE; +} + +/** + * Cancels a subscription which was being monitored. + */ +gboolean +_ih_sub_cancel (inotify_sub * sub) +{ + G_LOCK (inotify_lock); + + if (!sub->cancelled) + { + IH_W ("cancelling %s\n", sub->dirname); + sub->cancelled = TRUE; + _im_rm (sub); + _ip_stop_watching (sub); + } + + G_UNLOCK (inotify_lock); + + return TRUE; +} + + +static void +ih_event_callback (ik_event_t *event, inotify_sub *sub) +{ + gchar *fullpath; + GFileMonitorEvent eflags; + GFile* parent; + GFile* child; + + eflags = ih_mask_to_EventFlags (event->mask); + parent = g_file_new_for_path (sub->dirname); + if (event->name) + fullpath = g_strdup_printf ("%s/%s", sub->dirname, event->name); + else + fullpath = g_strdup_printf ("%s/", sub->dirname); + + child = g_file_new_for_path (fullpath); + g_free (fullpath); + + if (G_IS_DIRECTORY_MONITOR (sub->user_data)) + { + GDirectoryMonitor* monitor = G_DIRECTORY_MONITOR (sub->user_data); + g_directory_monitor_emit_event (monitor, + child, NULL, eflags); + } + else if (G_IS_FILE_MONITOR (sub->user_data)) + { + GFileMonitor* monitor = G_FILE_MONITOR (sub->user_data); + g_file_monitor_emit_event (monitor, + child, NULL, eflags); + } + + g_object_unref (child); + g_object_unref (parent); +} + +static void +ih_not_missing_callback (inotify_sub *sub) +{ + gchar *fullpath; + GFileMonitorEvent eflags; + guint32 mask; + GFile* parent; + GFile* child; + + parent = g_file_new_for_path (sub->dirname); + + if (sub->filename) + { + fullpath = g_strdup_printf ("%s/%s", sub->dirname, sub->filename); + g_warning ("Missing callback called fullpath = %s\n", fullpath); + if (!g_file_test (fullpath, G_FILE_TEST_EXISTS)) + { + g_free (fullpath); + return; + } + mask = IN_CREATE; + } + else + { + fullpath = g_strdup_printf ("%s", sub->dirname); + mask = IN_CREATE|IN_ISDIR; + } + + eflags = ih_mask_to_EventFlags (mask); + child = g_file_new_for_path (fullpath); + g_free (fullpath); + + if (G_IS_DIRECTORY_MONITOR (sub->user_data)) + { + GDirectoryMonitor* monitor = G_DIRECTORY_MONITOR (sub->user_data); + g_directory_monitor_emit_event (monitor, child, NULL, eflags); + } + else if (G_IS_FILE_MONITOR (sub->user_data)) + { + GFileMonitor* monitor = G_FILE_MONITOR (sub->user_data); + g_file_monitor_emit_event (monitor, + child, NULL, eflags); + } + + g_object_unref (child); + g_object_unref (parent); +} + +/* Transforms a inotify event to a GVFS event. */ +static GFileMonitorEvent +ih_mask_to_EventFlags (guint32 mask) +{ + mask &= ~IN_ISDIR; + switch (mask) + { + case IN_MODIFY: + return G_FILE_MONITOR_EVENT_CHANGED; + case IN_CLOSE_WRITE: + return G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT; + case IN_ATTRIB: + return G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED; + case IN_MOVE_SELF: + case IN_MOVED_FROM: + case IN_DELETE: + case IN_DELETE_SELF: + return G_FILE_MONITOR_EVENT_DELETED; + case IN_CREATE: + case IN_MOVED_TO: + return G_FILE_MONITOR_EVENT_CREATED; + case IN_UNMOUNT: + return G_FILE_MONITOR_EVENT_UNMOUNTED; + case IN_Q_OVERFLOW: + case IN_OPEN: + case IN_CLOSE_NOWRITE: + case IN_ACCESS: + case IN_IGNORED: + default: + return -1; + } +} diff --git a/gio/inotify/inotify-helper.h b/gio/inotify/inotify-helper.h new file mode 100644 index 000000000..1fd9701d4 --- /dev/null +++ b/gio/inotify/inotify-helper.h @@ -0,0 +1,33 @@ +/* inotify-helper.h - GVFS Directory Monitor using inotify + + Copyright (C) 2007 John McCutchan + + The Gnome Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The Gnome Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Gnome Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + Author: John McCutchan <john@johnmccutchan.com> +*/ + + +#ifndef __INOTIFY_HELPER_H +#define __INOTIFY_HELPER_H + +#include "inotify-sub.h" + +gboolean _ih_startup (void); +gboolean _ih_sub_add (inotify_sub *sub); +gboolean _ih_sub_cancel (inotify_sub *sub); + +#endif /* __INOTIFY_HELPER_H */ diff --git a/gio/inotify/inotify-kernel.c b/gio/inotify/inotify-kernel.c new file mode 100644 index 000000000..6735c455e --- /dev/null +++ b/gio/inotify/inotify-kernel.c @@ -0,0 +1,676 @@ +/* + Copyright (C) 2005 John McCutchan + + The Gnome Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The Gnome Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Gnome Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + Authors:. + John McCutchan <john@johnmccutchan.com> +*/ + +#include "config.h" + +#include <stdio.h> +#include <sys/ioctl.h> +#include <unistd.h> +#include <errno.h> +#include <string.h> +#include <glib.h> +#include "inotify-kernel.h" + +/* Just include the local headers to stop all the pain */ +#include "local_inotify.h" +#include "local_inotify_syscalls.h" +#if 0 +#ifdef HAVE_SYS_INOTIFY_H +/* We don't actually include the libc header, because there has been + * problems with libc versions that was built without inotify support. + * Instead we use the local version. + */ +#include "local_inotify.h" +#include "local_inotify_syscalls.h" +#elif defined (HAVE_LINUX_INOTIFY_H) +#include <linux/inotify.h> +#include "local_inotify_syscalls.h" +#endif +#endif + +/* Timings for pairing MOVED_TO / MOVED_FROM events */ +#define PROCESS_EVENTS_TIME 1000 /* milliseconds (1 hz) */ +#define DEFAULT_HOLD_UNTIL_TIME 0 /* 0 millisecond */ +#define MOVE_HOLD_UNTIL_TIME 0 /* 0 milliseconds */ + +static int inotify_instance_fd = -1; +static GQueue *events_to_process = NULL; +static GQueue *event_queue = NULL; +static GHashTable * cookie_hash = NULL; +static GIOChannel *inotify_read_ioc; +static GPollFD ik_poll_fd; +static gboolean ik_poll_fd_enabled = TRUE; +static void (*user_cb)(ik_event_t *event); + +static gboolean ik_read_callback (gpointer user_data); +static gboolean ik_process_eq_callback (gpointer user_data); + +static guint32 ik_move_matches = 0; +static guint32 ik_move_misses = 0; + +static gboolean process_eq_running = FALSE; + +/* We use the lock from inotify-helper.c + * + * There are two places that we take this lock + * + * 1) In ik_read_callback + * + * 2) ik_process_eq_callback. + * + * + * The rest of locking is taken care of in inotify-helper.c + */ +G_LOCK_EXTERN (inotify_lock); + +typedef struct ik_event_internal { + ik_event_t *event; + gboolean seen; + gboolean sent; + GTimeVal hold_until; + struct ik_event_internal *pair; +} ik_event_internal_t; + +/* In order to perform non-sleeping inotify event chunking we need + * a custom GSource + */ +static gboolean +ik_source_prepare (GSource *source, + gint *timeout) +{ + return FALSE; +} + +static gboolean +ik_source_timeout (gpointer data) +{ + GSource *source = (GSource *)data; + + /* Re-active the PollFD */ + g_source_add_poll (source, &ik_poll_fd); + g_source_unref (source); + ik_poll_fd_enabled = TRUE; + + return FALSE; +} + +#define MAX_PENDING_COUNT 2 +#define PENDING_THRESHOLD(qsize) ((qsize) >> 1) +#define PENDING_MARGINAL_COST(p) ((unsigned int)(1 << (p))) +#define MAX_QUEUED_EVENTS 2048 +#define AVERAGE_EVENT_SIZE sizeof (struct inotify_event) + 16 +#define TIMEOUT_MILLISECONDS 10 + +static gboolean +ik_source_check (GSource *source) +{ + static int prev_pending = 0, pending_count = 0; + + /* We already disabled the PollFD or + * nothing to be read from inotify */ + if (!ik_poll_fd_enabled || !(ik_poll_fd.revents & G_IO_IN)) + return FALSE; + + if (pending_count < MAX_PENDING_COUNT) + { + unsigned int pending; + + if (ioctl (inotify_instance_fd, FIONREAD, &pending) == -1) + goto do_read; + + pending /= AVERAGE_EVENT_SIZE; + + /* Don't wait if the number of pending events is too close + * to the maximum queue size. + */ + if (pending > PENDING_THRESHOLD (MAX_QUEUED_EVENTS)) + goto do_read; + + /* With each successive iteration, the minimum rate for + * further sleep doubles. */ + if (pending-prev_pending < PENDING_MARGINAL_COST (pending_count)) + goto do_read; + + prev_pending = pending; + pending_count++; + + /* We are going to wait to read the events: */ + + /* Remove the PollFD from the source */ + g_source_remove_poll (source, &ik_poll_fd); + /* To avoid threading issues we need to flag that we've done that */ + ik_poll_fd_enabled = FALSE; + /* Set a timeout to re-add the PollFD to the source */ + g_source_ref (source); + g_timeout_add (TIMEOUT_MILLISECONDS, ik_source_timeout, source); + + return FALSE; + } + +do_read: + /* We are ready to read events from inotify */ + + prev_pending = 0; + pending_count = 0; + + return TRUE; +} + +static gboolean +ik_source_dispatch (GSource *source, + GSourceFunc callback, + gpointer user_data) +{ + if (callback) + return callback (user_data); + return TRUE; +} + +static GSourceFuncs ik_source_funcs = +{ + ik_source_prepare, + ik_source_check, + ik_source_dispatch, + NULL +}; + +gboolean _ik_startup (void (*cb)(ik_event_t *event)) +{ + static gboolean initialized = FALSE; + GSource *source; + + user_cb = cb; + /* Ignore multi-calls */ + if (initialized) + return inotify_instance_fd >= 0; + + initialized = TRUE; + inotify_instance_fd = inotify_init (); + + if (inotify_instance_fd < 0) + return FALSE; + + inotify_read_ioc = g_io_channel_unix_new (inotify_instance_fd); + ik_poll_fd.fd = inotify_instance_fd; + ik_poll_fd.events = G_IO_IN | G_IO_HUP | G_IO_ERR; + g_io_channel_set_encoding (inotify_read_ioc, NULL, NULL); + g_io_channel_set_flags (inotify_read_ioc, G_IO_FLAG_NONBLOCK, NULL); + + source = g_source_new (&ik_source_funcs, sizeof(GSource)); + g_source_add_poll (source, &ik_poll_fd); + g_source_set_callback (source, ik_read_callback, NULL, NULL); + g_source_attach (source, NULL); + g_source_unref (source); + + cookie_hash = g_hash_table_new (g_direct_hash, g_direct_equal); + event_queue = g_queue_new (); + events_to_process = g_queue_new (); + + return TRUE; +} + +static ik_event_internal_t * +ik_event_internal_new (ik_event_t *event) +{ + ik_event_internal_t *internal_event = g_new0 (ik_event_internal_t, 1); + GTimeVal tv; + + g_assert (event); + + g_get_current_time (&tv); + g_time_val_add (&tv, DEFAULT_HOLD_UNTIL_TIME); + internal_event->event = event; + internal_event->hold_until = tv; + + return internal_event; +} + +static ik_event_t * +ik_event_new (char *buffer) +{ + struct inotify_event *kevent = (struct inotify_event *)buffer; + ik_event_t *event = g_new0(ik_event_t,1); + + g_assert (buffer); + + event->wd = kevent->wd; + event->mask = kevent->mask; + event->cookie = kevent->cookie; + event->len = kevent->len; + if (event->len) + event->name = g_strdup (kevent->name); + else + event->name = g_strdup (""); + + return event; +} + +ik_event_t * +_ik_event_new_dummy (const char *name, gint32 wd, guint32 mask) +{ + ik_event_t *event = g_new0 (ik_event_t,1); + event->wd = wd; + event->mask = mask; + event->cookie = 0; + if (name) + event->name = g_strdup (name); + else + event->name = g_strdup(""); + + event->len = strlen (event->name); + + return event; +} + +void +_ik_event_free (ik_event_t *event) +{ + if (event->pair) + _ik_event_free (event->pair); + g_free (event->name); + g_free (event); +} + +gint32 +_ik_watch (const char *path, guint32 mask, int *err) +{ + gint32 wd = -1; + + g_assert (path != NULL); + g_assert (inotify_instance_fd >= 0); + + wd = inotify_add_watch (inotify_instance_fd, path, mask); + + if (wd < 0) + { + int e = errno; + /* FIXME: debug msg failed to add watch */ + if (err) + *err = e; + return wd; + } + + g_assert (wd >= 0); + return wd; +} + +int +_ik_ignore(const char *path, gint32 wd) +{ + g_assert (wd >= 0); + g_assert (inotify_instance_fd >= 0); + + if (inotify_rm_watch (inotify_instance_fd, wd) < 0) + { + /* int e = errno; */ + /* failed to rm watch */ + return -1; + } + + return 0; +} + +void +_ik_move_stats (guint32 *matches, guint32 *misses) +{ + if (matches) + *matches = ik_move_matches; + + if (misses) + *misses = ik_move_misses; +} + +const char * +_ik_mask_to_string (guint32 mask) +{ + gboolean is_dir = mask & IN_ISDIR; + mask &= ~IN_ISDIR; + + if (is_dir) + { + switch (mask) + { + case IN_ACCESS: + return "ACCESS (dir)"; + case IN_MODIFY: + return "MODIFY (dir)"; + case IN_ATTRIB: + return "ATTRIB (dir)"; + case IN_CLOSE_WRITE: + return "CLOSE_WRITE (dir)"; + case IN_CLOSE_NOWRITE: + return "CLOSE_NOWRITE (dir)"; + case IN_OPEN: + return "OPEN (dir)"; + case IN_MOVED_FROM: + return "MOVED_FROM (dir)"; + case IN_MOVED_TO: + return "MOVED_TO (dir)"; + case IN_DELETE: + return "DELETE (dir)"; + case IN_CREATE: + return "CREATE (dir)"; + case IN_DELETE_SELF: + return "DELETE_SELF (dir)"; + case IN_UNMOUNT: + return "UNMOUNT (dir)"; + case IN_Q_OVERFLOW: + return "Q_OVERFLOW (dir)"; + case IN_IGNORED: + return "IGNORED (dir)"; + default: + return "UNKNOWN_EVENT (dir)"; + } + } + else + { + switch (mask) + { + case IN_ACCESS: + return "ACCESS"; + case IN_MODIFY: + return "MODIFY"; + case IN_ATTRIB: + return "ATTRIB"; + case IN_CLOSE_WRITE: + return "CLOSE_WRITE"; + case IN_CLOSE_NOWRITE: + return "CLOSE_NOWRITE"; + case IN_OPEN: + return "OPEN"; + case IN_MOVED_FROM: + return "MOVED_FROM"; + case IN_MOVED_TO: + return "MOVED_TO"; + case IN_DELETE: + return "DELETE"; + case IN_CREATE: + return "CREATE"; + case IN_DELETE_SELF: + return "DELETE_SELF"; + case IN_UNMOUNT: + return "UNMOUNT"; + case IN_Q_OVERFLOW: + return "Q_OVERFLOW"; + case IN_IGNORED: + return "IGNORED"; + default: + return "UNKNOWN_EVENT"; + } + } +} + + +static void +ik_read_events (gsize *buffer_size_out, gchar **buffer_out) +{ + static gchar *buffer = NULL; + static gsize buffer_size; + + /* Initialize the buffer on our first call */ + if (buffer == NULL) + { + buffer_size = AVERAGE_EVENT_SIZE; + buffer_size *= MAX_QUEUED_EVENTS; + buffer = g_malloc (buffer_size); + } + + *buffer_size_out = 0; + *buffer_out = NULL; + + memset (buffer, 0, buffer_size); + + if (g_io_channel_read_chars (inotify_read_ioc, (char *)buffer, buffer_size, buffer_size_out, NULL) != G_IO_STATUS_NORMAL) { + /* error reading */ + } + *buffer_out = buffer; +} + +static gboolean +ik_read_callback (gpointer user_data) +{ + gchar *buffer; + gsize buffer_size, buffer_i, events; + + G_LOCK (inotify_lock); + ik_read_events (&buffer_size, &buffer); + + buffer_i = 0; + events = 0; + while (buffer_i < buffer_size) + { + struct inotify_event *event; + gsize event_size; + event = (struct inotify_event *)&buffer[buffer_i]; + event_size = sizeof(struct inotify_event) + event->len; + g_queue_push_tail (events_to_process, ik_event_internal_new (ik_event_new (&buffer[buffer_i]))); + buffer_i += event_size; + events++; + } + + /* If the event process callback is off, turn it back on */ + if (!process_eq_running && events) + { + process_eq_running = TRUE; + g_timeout_add (PROCESS_EVENTS_TIME, ik_process_eq_callback, NULL); + } + + G_UNLOCK (inotify_lock); + + return TRUE; +} + +static gboolean +g_timeval_lt (GTimeVal *val1, GTimeVal *val2) +{ + if (val1->tv_sec < val2->tv_sec) + return TRUE; + + if (val1->tv_sec > val2->tv_sec) + return FALSE; + + /* val1->tv_sec == val2->tv_sec */ + if (val1->tv_usec < val2->tv_usec) + return TRUE; + + return FALSE; +} + +static gboolean +g_timeval_eq (GTimeVal *val1, GTimeVal *val2) +{ + return (val1->tv_sec == val2->tv_sec) && (val1->tv_usec == val2->tv_usec); +} + +static void +ik_pair_events (ik_event_internal_t *event1, ik_event_internal_t *event2) +{ + g_assert (event1 && event2); + /* We should only be pairing events that have the same cookie */ + g_assert (event1->event->cookie == event2->event->cookie); + /* We shouldn't pair an event that already is paired */ + g_assert (event1->pair == NULL && event2->pair == NULL); + + /* Pair the internal structures and the ik_event_t structures */ + event1->pair = event2; + event1->event->pair = event2->event; + + if (g_timeval_lt (&event1->hold_until, &event2->hold_until)) + event1->hold_until = event2->hold_until; + + event2->hold_until = event1->hold_until; +} + +static void +ik_event_add_microseconds (ik_event_internal_t *event, glong ms) +{ + g_assert (event); + g_time_val_add (&event->hold_until, ms); +} + +static gboolean +ik_event_ready (ik_event_internal_t *event) +{ + GTimeVal tv; + g_assert (event); + + g_get_current_time (&tv); + + /* An event is ready if, + * + * it has no cookie -- there is nothing to be gained by holding it + * or, it is already paired -- we don't need to hold it anymore + * or, we have held it long enough + */ + return + event->event->cookie == 0 || + event->pair != NULL || + g_timeval_lt (&event->hold_until, &tv) || + g_timeval_eq (&event->hold_until, &tv); +} + +static void +ik_pair_moves (gpointer data, gpointer user_data) +{ + ik_event_internal_t *event = (ik_event_internal_t *)data; + + if (event->seen == TRUE || event->sent == TRUE) + return; + + if (event->event->cookie != 0) + { + /* When we get a MOVED_FROM event we delay sending the event by + * MOVE_HOLD_UNTIL_TIME microseconds. We need to do this because a + * MOVED_TO pair _might_ be coming in the near future */ + if (event->event->mask & IN_MOVED_FROM) + { + g_hash_table_insert (cookie_hash, GINT_TO_POINTER (event->event->cookie), event); + /* because we don't deliver move events there is no point in waiting for the match right now. */ + ik_event_add_microseconds (event, MOVE_HOLD_UNTIL_TIME); + } + else if (event->event->mask & IN_MOVED_TO) + { + /* We need to check if we are waiting for this MOVED_TO events cookie to pair it with + * a MOVED_FROM */ + ik_event_internal_t *match = NULL; + match = g_hash_table_lookup (cookie_hash, GINT_TO_POINTER (event->event->cookie)); + if (match) + { + g_hash_table_remove (cookie_hash, GINT_TO_POINTER (event->event->cookie)); + ik_pair_events (match, event); + } + } + } + event->seen = TRUE; +} + +static void +ik_process_events (void) +{ + g_queue_foreach (events_to_process, ik_pair_moves, NULL); + + while (!g_queue_is_empty (events_to_process)) + { + ik_event_internal_t *event = g_queue_peek_head (events_to_process); + + /* This must have been sent as part of a MOVED_TO/MOVED_FROM */ + if (event->sent) + { + /* Pop event */ + g_queue_pop_head (events_to_process); + /* Free the internal event structure */ + g_free (event); + continue; + } + + /* The event isn't ready yet */ + if (!ik_event_ready (event)) + break; + + /* Pop it */ + event = g_queue_pop_head (events_to_process); + + /* Check if this is a MOVED_FROM that is also sitting in the cookie_hash */ + if (event->event->cookie && event->pair == NULL && + g_hash_table_lookup (cookie_hash, GINT_TO_POINTER (event->event->cookie))) + g_hash_table_remove (cookie_hash, GINT_TO_POINTER (event->event->cookie)); + + if (event->pair) + { + /* We send out paired MOVED_FROM/MOVED_TO events in the same event buffer */ + /* g_assert (event->event->mask == IN_MOVED_FROM && event->pair->event->mask == IN_MOVED_TO); */ + /* Copy the paired data */ + event->pair->sent = TRUE; + event->sent = TRUE; + ik_move_matches++; + } + else if (event->event->cookie) + { + /* If we couldn't pair a MOVED_FROM and MOVED_TO together, we change + * the event masks */ + /* Changeing MOVED_FROM to DELETE and MOVED_TO to create lets us make + * the gaurantee that you will never see a non-matched MOVE event */ + + if (event->event->mask & IN_MOVED_FROM) + { + event->event->mask = IN_DELETE|(event->event->mask & IN_ISDIR); + ik_move_misses++; /* not super accurate, if we aren't watching the destination it still counts as a miss */ + } + if (event->event->mask & IN_MOVED_TO) + event->event->mask = IN_CREATE|(event->event->mask & IN_ISDIR); + } + + /* Push the ik_event_t onto the event queue */ + g_queue_push_tail (event_queue, event->event); + /* Free the internal event structure */ + g_free (event); + } +} + +static gboolean +ik_process_eq_callback (gpointer user_data) +{ + gboolean res; + + /* Try and move as many events to the event queue */ + G_LOCK (inotify_lock); + ik_process_events (); + + while (!g_queue_is_empty (event_queue)) + { + ik_event_t *event = g_queue_pop_head (event_queue); + + user_cb (event); + } + + res = TRUE; + + if (g_queue_get_length (events_to_process) == 0) + { + process_eq_running = FALSE; + res = FALSE; + } + + G_UNLOCK (inotify_lock); + + return res; +} diff --git a/gio/inotify/inotify-kernel.h b/gio/inotify/inotify-kernel.h new file mode 100644 index 000000000..b406d71d5 --- /dev/null +++ b/gio/inotify/inotify-kernel.h @@ -0,0 +1,55 @@ +/* + Copyright (C) 2005 John McCutchan + + The Gnome Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The Gnome Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Gnome Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + Authors:. + John McCutchan <john@johnmccutchan.com> +*/ + +#ifndef __INOTIFY_KERNEL_H +#define __INOTIFY_KERNEL_H + +typedef struct ik_event_s { + gint32 wd; + guint32 mask; + guint32 cookie; + guint32 len; + char * name; + struct ik_event_s *pair; +} ik_event_t; + +gboolean _ik_startup (void (*cb) (ik_event_t *event)); + +ik_event_t *_ik_event_new_dummy (const char *name, + gint32 wd, + guint32 mask); +void _ik_event_free (ik_event_t *event); + +gint32 _ik_watch (const char *path, + guint32 mask, + int *err); +int _ik_ignore (const char *path, + gint32 wd); + + +/* The miss count will probably be enflated */ +void _ik_move_stats (guint32 *matches, + guint32 *misses); +const char *_ik_mask_to_string (guint32 mask); + + +#endif diff --git a/gio/inotify/inotify-missing.c b/gio/inotify/inotify-missing.c new file mode 100644 index 000000000..96126b3c8 --- /dev/null +++ b/gio/inotify/inotify-missing.c @@ -0,0 +1,167 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 2; tab-width: 8 -*- */ + +/* inotify-helper.c - Gnome VFS Monitor based on inotify. + + Copyright (C) 2005 John McCutchan + + The Gnome Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The Gnome Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Gnome Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + Authors: + John McCutchan <john@johnmccutchan.com> +*/ + +#include "config.h" +#include <glib.h> +#include "inotify-missing.h" +#include "inotify-path.h" + +#define SCAN_MISSING_TIME 4000 /* 1/4 Hz */ + +static gboolean im_debug_enabled = FALSE; +#define IM_W if (im_debug_enabled) g_warning + +/* We put inotify_sub's that are missing on this list */ +static GList *missing_sub_list = NULL; +static gboolean im_scan_missing (gpointer user_data); +static gboolean scan_missing_running = FALSE; +static void (*missing_cb)(inotify_sub *sub) = NULL; + +G_LOCK_EXTERN (inotify_lock); + +/* inotify_lock must be held before calling */ +void +_im_startup (void (*callback)(inotify_sub *sub)) +{ + static gboolean initialized = FALSE; + + if (!initialized) + { + missing_cb = callback; + initialized = TRUE; + } +} + +/* inotify_lock must be held before calling */ +void +_im_add (inotify_sub *sub) +{ + if (g_list_find (missing_sub_list, sub)) + { + IM_W ("asked to add %s to missing list but it's already on the list!\n", sub->dirname); + return; + } + + IM_W ("adding %s to missing list\n", sub->dirname); + missing_sub_list = g_list_prepend (missing_sub_list, sub); + + /* If the timeout is turned off, we turn it back on */ + if (!scan_missing_running) + { + scan_missing_running = TRUE; + g_timeout_add (SCAN_MISSING_TIME, im_scan_missing, NULL); + } +} + +/* inotify_lock must be held before calling */ +void +_im_rm (inotify_sub *sub) +{ + GList *link; + + link = g_list_find (missing_sub_list, sub); + + if (!link) + { + IM_W ("asked to remove %s from missing list but it isn't on the list!\n", sub->dirname); + return; + } + + IM_W ("removing %s from missing list\n", sub->dirname); + + missing_sub_list = g_list_remove_link (missing_sub_list, link); + g_list_free_1 (link); +} + +/* Scans the list of missing subscriptions checking if they + * are available yet. + */ +static gboolean +im_scan_missing (gpointer user_data) +{ + GList *nolonger_missing = NULL; + GList *l; + + G_LOCK (inotify_lock); + + IM_W ("scanning missing list with %d items\n", g_list_length (missing_sub_list)); + for (l = missing_sub_list; l; l = l->next) + { + inotify_sub *sub = l->data; + gboolean not_m = FALSE; + + IM_W ("checking %p\n", sub); + g_assert (sub); + g_assert (sub->dirname); + not_m = _ip_start_watching (sub); + + if (not_m) + { + missing_cb (sub); + IM_W ("removed %s from missing list\n", sub->dirname); + /* We have to build a list of list nodes to remove from the + * missing_sub_list. We do the removal outside of this loop. + */ + nolonger_missing = g_list_prepend (nolonger_missing, l); + } + } + + for (l = nolonger_missing; l ; l = l->next) + { + GList *llink = l->data; + missing_sub_list = g_list_remove_link (missing_sub_list, llink); + g_list_free_1 (llink); + } + + g_list_free (nolonger_missing); + + /* If the missing list is now empty, we disable the timeout */ + if (missing_sub_list == NULL) + { + scan_missing_running = FALSE; + G_UNLOCK (inotify_lock); + return FALSE; + } + else + { + G_UNLOCK (inotify_lock); + return TRUE; + } +} + + +/* inotify_lock must be held */ +void +_im_diag_dump (GIOChannel *ioc) +{ + GList *l; + g_io_channel_write_chars (ioc, "missing list:\n", -1, NULL, NULL); + for (l = missing_sub_list; l; l = l->next) + { + inotify_sub *sub = l->data; + g_io_channel_write_chars (ioc, sub->dirname, -1, NULL, NULL); + g_io_channel_write_chars (ioc, "\n", -1, NULL, NULL); + } +} diff --git a/gio/inotify/inotify-missing.h b/gio/inotify/inotify-missing.h new file mode 100644 index 000000000..b67b59525 --- /dev/null +++ b/gio/inotify/inotify-missing.h @@ -0,0 +1,35 @@ +/* inotify-helper.h - GNOME VFS Monitor using inotify + + Copyright (C) 2006 John McCutchan <john@johnmccutchan.com> + + The Gnome Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The Gnome Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Gnome Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + Author: John McCutchan <ttb@tentacle.dhs.org> +*/ + + +#ifndef __INOTIFY_MISSING_H +#define __INOTIFY_MISSING_H + +#include "inotify-sub.h" + +void _im_startup (void (*missing_cb)(inotify_sub *sub)); +void _im_add (inotify_sub *sub); +void _im_rm (inotify_sub *sub); +void _im_diag_dump (GIOChannel *ioc); + + +#endif /* __INOTIFY_MISSING_H */ diff --git a/gio/inotify/inotify-path.c b/gio/inotify/inotify-path.c new file mode 100644 index 000000000..76fc06d79 --- /dev/null +++ b/gio/inotify/inotify-path.c @@ -0,0 +1,417 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 2; tab-width: 8 -*- */ + +/* inotify-path.c - GVFS Directory Monitor based on inotify. + + Copyright (C) 2006 John McCutchan + + The Gnome Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The Gnome Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Gnome Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + Authors: + John McCutchan <john@johnmccutchan.com> +*/ + +#include "config.h" + +/* Don't put conflicting kernel types in the global namespace: */ +#define __KERNEL_STRICT_NAMES + +#include "local_inotify.h" +#if 0 +#ifdef HAVE_SYS_INOTIFY_H +/* We don't actually include the libc header, because there has been + * problems with libc versions that was built without inotify support. + * Instead we use the local version. + */ +#elif defined (HAVE_LINUX_INOTIFY_H) +#include <linux/inotify.h> +#endif +#endif +#include <string.h> +#include <glib.h> +#include "inotify-kernel.h" +#include "inotify-path.h" +#include "inotify-missing.h" + +#define IP_INOTIFY_MASK (IN_MODIFY|IN_ATTRIB|IN_MOVED_FROM|IN_MOVED_TO|IN_DELETE|IN_CREATE|IN_DELETE_SELF|IN_UNMOUNT|IN_MOVE_SELF|IN_CLOSE_WRITE) + +typedef struct ip_watched_dir_s { + char *path; + /* TODO: We need to maintain a tree of watched directories + * so that we can deliver move/delete events to sub folders. + * Or the application could do it... + */ + struct ip_watched_dir_s* parent; + GList* children; + + /* Inotify state */ + gint32 wd; + + /* List of inotify subscriptions */ + GList *subs; +} ip_watched_dir_t; + +static gboolean ip_debug_enabled = FALSE; +#define IP_W if (ip_debug_enabled) g_warning + +/* path -> ip_watched_dir */ +static GHashTable * path_dir_hash = NULL; +/* inotify_sub * -> ip_watched_dir * + * + * Each subscription is attached to a watched directory or it is on + * the missing list + */ +static GHashTable * sub_dir_hash = NULL; +/* This hash holds GLists of ip_watched_dir_t *'s + * We need to hold a list because symbolic links can share + * the same wd + */ +static GHashTable * wd_dir_hash = NULL; + +static ip_watched_dir_t *ip_watched_dir_new (const char *path, + int wd); +static void ip_watched_dir_free (ip_watched_dir_t *dir); +static void ip_event_callback (ik_event_t *event); + + +static void (*event_callback)(ik_event_t *event, inotify_sub *sub); + +gboolean +_ip_startup (void (*cb)(ik_event_t *event, inotify_sub *sub)) +{ + static gboolean initialized = FALSE; + static gboolean result = FALSE; + + if (initialized == TRUE) + return result; + + event_callback = cb; + result = _ik_startup (ip_event_callback); + + if (!result) + return FALSE; + + path_dir_hash = g_hash_table_new (g_str_hash, g_str_equal); + sub_dir_hash = g_hash_table_new (g_direct_hash, g_direct_equal); + wd_dir_hash = g_hash_table_new (g_direct_hash, g_direct_equal); + + initialized = TRUE; + return TRUE; +} + +static void +ip_map_path_dir (const char *path, ip_watched_dir_t *dir) +{ + g_assert (path && dir); + g_hash_table_insert (path_dir_hash, dir->path, dir); +} + +static void +ip_map_sub_dir (inotify_sub *sub, ip_watched_dir_t *dir) +{ + /* Associate subscription and directory */ + g_assert (dir && sub); + g_hash_table_insert (sub_dir_hash, sub, dir); + dir->subs = g_list_prepend (dir->subs, sub); +} + +static void +ip_map_wd_dir (gint32 wd, ip_watched_dir_t *dir) +{ + GList *dir_list; + + g_assert (wd >= 0 && dir); + dir_list = g_hash_table_lookup (wd_dir_hash, GINT_TO_POINTER (wd)); + dir_list = g_list_prepend (dir_list, dir); + g_hash_table_replace (wd_dir_hash, GINT_TO_POINTER (dir->wd), dir_list); +} + +gboolean +_ip_start_watching (inotify_sub *sub) +{ + gint32 wd; + int err; + ip_watched_dir_t *dir; + + g_assert (sub); + g_assert (!sub->cancelled); + g_assert (sub->dirname); + + IP_W ("Starting to watch %s\n", sub->dirname); + dir = g_hash_table_lookup (path_dir_hash, sub->dirname); + if (dir) + { + IP_W ("Already watching\n"); + goto out; + } + + IP_W ("Trying to add inotify watch "); + wd = _ik_watch (sub->dirname, IP_INOTIFY_MASK|IN_ONLYDIR, &err); + if (wd < 0) + { + IP_W("Failed\n"); + return FALSE; + } + else + { + /* Create new watched directory and associate it with the + * wd hash and path hash + */ + IP_W ("Success\n"); + dir = ip_watched_dir_new (sub->dirname, wd); + ip_map_wd_dir (wd, dir); + ip_map_path_dir (sub->dirname, dir); + } + + out: + ip_map_sub_dir (sub, dir); + + return TRUE; +} + +static void +ip_unmap_path_dir (const char *path, ip_watched_dir_t *dir) +{ + g_assert (path && dir); + g_hash_table_remove (path_dir_hash, dir->path); +} + +static void +ip_unmap_wd_dir (gint32 wd, ip_watched_dir_t *dir) +{ + GList *dir_list = g_hash_table_lookup (wd_dir_hash, GINT_TO_POINTER (wd)); + + if (!dir_list) + return; + + g_assert (wd >= 0 && dir); + dir_list = g_list_remove (dir_list, dir); + if (dir_list == NULL) + g_hash_table_remove (wd_dir_hash, GINT_TO_POINTER (dir->wd)); + else + g_hash_table_replace (wd_dir_hash, GINT_TO_POINTER (dir->wd), dir_list); +} + +static void +ip_unmap_wd (gint32 wd) +{ + GList *dir_list = g_hash_table_lookup (wd_dir_hash, GINT_TO_POINTER (wd)); + if (!dir_list) + return; + g_assert (wd >= 0); + g_hash_table_remove (wd_dir_hash, GINT_TO_POINTER (wd)); + g_list_free (dir_list); +} + +static void +ip_unmap_sub_dir (inotify_sub *sub, ip_watched_dir_t *dir) +{ + g_assert (sub && dir); + g_hash_table_remove (sub_dir_hash, sub); + dir->subs = g_list_remove (dir->subs, sub); +} + +static void +ip_unmap_all_subs (ip_watched_dir_t *dir) +{ + GList *l = NULL; + + for (l = dir->subs; l; l = l->next) + { + inotify_sub *sub = l->data; + g_hash_table_remove (sub_dir_hash, sub); + } + g_list_free (dir->subs); + dir->subs = NULL; +} + +gboolean +_ip_stop_watching (inotify_sub *sub) +{ + ip_watched_dir_t *dir = NULL; + + dir = g_hash_table_lookup (sub_dir_hash, sub); + if (!dir) + return TRUE; + + ip_unmap_sub_dir (sub, dir); + + /* No one is subscribing to this directory any more */ + if (dir->subs == NULL) + { + _ik_ignore (dir->path, dir->wd); + ip_unmap_wd_dir (dir->wd, dir); + ip_unmap_path_dir (dir->path, dir); + ip_watched_dir_free (dir); + } + + return TRUE; +} + + +static ip_watched_dir_t * +ip_watched_dir_new (const char *path, gint32 wd) +{ + ip_watched_dir_t *dir = g_new0 (ip_watched_dir_t, 1); + + dir->path = g_strdup (path); + dir->wd = wd; + + return dir; +} + +static void +ip_watched_dir_free (ip_watched_dir_t * dir) +{ + g_assert (dir->subs == NULL); + g_free (dir->path); + g_free (dir); +} + +static void +ip_wd_delete (gpointer data, gpointer user_data) +{ + ip_watched_dir_t *dir = data; + GList *l = NULL; + + for (l = dir->subs; l; l = l->next) + { + inotify_sub *sub = l->data; + /* Add subscription to missing list */ + _im_add (sub); + } + ip_unmap_all_subs (dir); + /* Unassociate the path and the directory */ + ip_unmap_path_dir (dir->path, dir); + ip_watched_dir_free (dir); +} + +static void +ip_event_dispatch (GList *dir_list, GList* pair_dir_list, ik_event_t *event) +{ + GList *dirl; + + if (!event) + return; + + for (dirl = dir_list; dirl; dirl = dirl->next) + { + GList *subl; + ip_watched_dir_t *dir = dirl->data; + + for (subl = dir->subs; subl; subl = subl->next) + { + inotify_sub *sub = subl->data; + + /* If the subscription and the event + * contain a filename and they don't + * match, we don't deliver this event. + */ + if (sub->filename && + event->name && + strcmp (sub->filename, event->name)) + continue; + + /* If the subscription has a filename + * but this event doesn't, we don't + * deliever this event. + */ + if (sub->filename && !event->name) + continue; + + /* FIXME: We might need to synthesize + * DELETE/UNMOUNT events when + * the filename doesn't match + */ + + event_callback (event, sub); + } + } + + if (!event->pair) + return; + + for (dirl = pair_dir_list; dirl; dirl = dirl->next) + { + GList *subl; + ip_watched_dir_t *dir = dirl->data; + + for (subl = dir->subs; subl; subl = subl->next) + { + inotify_sub *sub = subl->data; + + /* If the subscription and the event + * contain a filename and they don't + * match, we don't deliver this event. + */ + if (sub->filename && + event->pair->name && + strcmp (sub->filename, event->pair->name)) + continue; + + /* If the subscription has a filename + * but this event doesn't, we don't + * deliever this event. + */ + if (sub->filename && !event->pair->name) + continue; + + /* FIXME: We might need to synthesize + * DELETE/UNMOUNT events when + * the filename doesn't match + */ + + event_callback (event->pair, sub); + } + } +} + +static void +ip_event_callback (ik_event_t *event) +{ + GList* dir_list = NULL; + GList* pair_dir_list = NULL; + + dir_list = g_hash_table_lookup (wd_dir_hash, GINT_TO_POINTER (event->wd)); + + /* We can ignore the IGNORED events */ + if (event->mask & IN_IGNORED) + { + _ik_event_free (event); + return; + } + + if (event->pair) + pair_dir_list = g_hash_table_lookup (wd_dir_hash, GINT_TO_POINTER (event->pair->wd)); + + if (event->mask & IP_INOTIFY_MASK) + ip_event_dispatch (dir_list, pair_dir_list, event); + + /* We have to manage the missing list + * when we get an event that means the + * file has been deleted/moved/unmounted. + */ + if (event->mask & IN_DELETE_SELF || + event->mask & IN_MOVE_SELF || + event->mask & IN_UNMOUNT) + { + /* Add all subscriptions to missing list */ + g_list_foreach (dir_list, ip_wd_delete, NULL); + /* Unmap all directories attached to this wd */ + ip_unmap_wd (event->wd); + } + + _ik_event_free (event); +} diff --git a/gio/inotify/inotify-path.h b/gio/inotify/inotify-path.h new file mode 100644 index 000000000..c613b9f82 --- /dev/null +++ b/gio/inotify/inotify-path.h @@ -0,0 +1,33 @@ +/* + Copyright (C) 2005 John McCutchan + + The Gnome Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The Gnome Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Gnome Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + Authors:. + John McCutchan <john@johnmccutchan.com> +*/ + +#ifndef __INOTIFY_PATH_H +#define __INOTIFY_PATH_H + +#include "inotify-kernel.h" +#include "inotify-sub.h" + +gboolean _ip_startup (void (*event_cb)(ik_event_t *event, inotify_sub *sub)); +gboolean _ip_start_watching (inotify_sub *sub); +gboolean _ip_stop_watching (inotify_sub *sub); + +#endif diff --git a/gio/inotify/inotify-sub.c b/gio/inotify/inotify-sub.c new file mode 100644 index 000000000..f67a56318 --- /dev/null +++ b/gio/inotify/inotify-sub.c @@ -0,0 +1,68 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 2; tab-width: 8 -*- */ + +/* inotify-sub.c - GMonitor based on inotify. + + Copyright (C) 2006 John McCutchan + + The Gnome Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The Gnome Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Gnome Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + Authors: + John McCutchan <john@johnmccutchan.com> +*/ + +#include "config.h" +#include <string.h> +#include <glib.h> + +#include "inotify-sub.h" + +static gboolean is_debug_enabled = FALSE; +#define IS_W if (is_debug_enabled) g_warning + +static gchar* +dup_dirname(const gchar* dirname) +{ + gchar* d_dirname = g_strdup (dirname); + size_t len = strlen (d_dirname); + + if (d_dirname[len] == '/') + d_dirname[len] = '\0'; + + return d_dirname; +} + +inotify_sub* +_ih_sub_new (const gchar* dirname, const gchar* filename, gpointer user_data) +{ + inotify_sub* sub = NULL; + + sub = g_new0 (inotify_sub, 1); + sub->dirname = dup_dirname (dirname); + sub->filename = g_strdup (filename); + sub->user_data = user_data; + + IS_W ("new subscription for %s being setup\n", sub->dirname); + + return sub; +} + +void +_ih_sub_free (inotify_sub* sub) +{ + g_free (sub->dirname); + g_free (sub->filename); + g_free (sub); +} diff --git a/gio/inotify/inotify-sub.h b/gio/inotify/inotify-sub.h new file mode 100644 index 000000000..36561e74c --- /dev/null +++ b/gio/inotify/inotify-sub.h @@ -0,0 +1,38 @@ +/* inotify-sub.h - GVFS Directory Monitor using inotify + + Copyright (C) 2006 John McCutchan + + The Gnome Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The Gnome Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Gnome Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + Author: John McCutchan <john@johnmccutchan.com> +*/ + + +#ifndef __INOTIFY_SUB_H +#define __INOTIFY_SUB_H + +typedef struct +{ + gchar* dirname; + gchar* filename; + gboolean cancelled; + gpointer user_data; +} inotify_sub; + +inotify_sub* _ih_sub_new (const gchar* dirname, const gchar* filename, gpointer user_data); +void _ih_sub_free (inotify_sub* sub); + +#endif /* __INOTIFY_SUB_H */ diff --git a/gio/inotify/local_inotify.h b/gio/inotify/local_inotify.h new file mode 100644 index 000000000..267c88b5f --- /dev/null +++ b/gio/inotify/local_inotify.h @@ -0,0 +1,113 @@ +/* + * Inode based directory notification for Linux + * + * Copyright (C) 2005 John McCutchan + */ + +#ifndef _LINUX_INOTIFY_H +#define _LINUX_INOTIFY_H + +#include <linux/types.h> + +/* + * struct inotify_event - structure read from the inotify device for each event + * + * When you are watching a directory, you will receive the filename for events + * such as IN_CREATE, IN_DELETE, IN_OPEN, IN_CLOSE, ..., relative to the wd. + */ +struct inotify_event { + __s32 wd; /* watch descriptor */ + __u32 mask; /* watch mask */ + __u32 cookie; /* cookie to synchronize two events */ + __u32 len; /* length (including nulls) of name */ + char name[0]; /* stub for possible name */ +}; + +/* the following are legal, implemented events that user-space can watch for */ +#define IN_ACCESS 0x00000001 /* File was accessed */ +#define IN_MODIFY 0x00000002 /* File was modified */ +#define IN_ATTRIB 0x00000004 /* Metadata changed */ +#define IN_CLOSE_WRITE 0x00000008 /* Writtable file was closed */ +#define IN_CLOSE_NOWRITE 0x00000010 /* Unwrittable file closed */ +#define IN_OPEN 0x00000020 /* File was opened */ +#define IN_MOVED_FROM 0x00000040 /* File was moved from X */ +#define IN_MOVED_TO 0x00000080 /* File was moved to Y */ +#define IN_CREATE 0x00000100 /* Subfile was created */ +#define IN_DELETE 0x00000200 /* Subfile was deleted */ +#define IN_DELETE_SELF 0x00000400 /* Self was deleted */ +#define IN_MOVE_SELF 0x00000800 /* Self was moved */ + +/* the following are legal events. they are sent as needed to any watch */ +#define IN_UNMOUNT 0x00002000 /* Backing fs was unmounted */ +#define IN_Q_OVERFLOW 0x00004000 /* Event queued overflowed */ +#define IN_IGNORED 0x00008000 /* File was ignored */ + +/* helper events */ +#define IN_CLOSE (IN_CLOSE_WRITE | IN_CLOSE_NOWRITE) /* close */ +#define IN_MOVE (IN_MOVED_FROM | IN_MOVED_TO) /* moves */ + +/* special flags */ +#define IN_ONLYDIR 0x01000000 /* only watch the path if it is a directory */ +#define IN_DONT_FOLLOW 0x02000000 /* don't follow a sym link */ +#define IN_MASK_ADD 0x20000000 /* add to the mask of an already existing watch */ +#define IN_ISDIR 0x40000000 /* event occurred against dir */ +#define IN_ONESHOT 0x80000000 /* only send event once */ + +/* + * All of the events - we build the list by hand so that we can add flags in + * the future and not break backward compatibility. Apps will get only the + * events that they originally wanted. Be sure to add new events here! + */ +#define IN_ALL_EVENTS (IN_ACCESS | IN_MODIFY | IN_ATTRIB | IN_CLOSE_WRITE | \ + IN_CLOSE_NOWRITE | IN_OPEN | IN_MOVED_FROM | \ + IN_MOVED_TO | IN_DELETE | IN_CREATE | IN_DELETE_SELF | \ + IN_MOVE_SELF) + +#ifdef __KERNEL__ + +#include <linux/dcache.h> +#include <linux/fs.h> +#include <linux/config.h> + +#ifdef CONFIG_INOTIFY + +extern void inotify_inode_queue_event(struct inode *, __u32, __u32, + const char *); +extern void inotify_dentry_parent_queue_event(struct dentry *, __u32, __u32, + const char *); +extern void inotify_unmount_inodes(struct list_head *); +extern void inotify_inode_is_dead(struct inode *); +extern u32 inotify_get_cookie(void); + +#else + +static inline void inotify_inode_queue_event(struct inode *inode, + __u32 mask, __u32 cookie, + const char *filename) +{ +} + +static inline void inotify_dentry_parent_queue_event(struct dentry *dentry, + __u32 mask, __u32 cookie, + const char *filename) +{ +} + +static inline void inotify_unmount_inodes(struct list_head *list) +{ +} + +static inline void inotify_inode_is_dead(struct inode *inode) +{ +} + +static inline u32 inotify_get_cookie(void) +{ + return 0; +} + +#endif /* CONFIG_INOTIFY */ + +#endif /* __KERNEL __ */ + +#endif /* _LINUX_INOTIFY_H */ diff --git a/gio/inotify/local_inotify_syscalls.h b/gio/inotify/local_inotify_syscalls.h new file mode 100644 index 000000000..1821accef --- /dev/null +++ b/gio/inotify/local_inotify_syscalls.h @@ -0,0 +1,85 @@ +#ifndef _LINUX_INOTIFY_SYSCALLS_H +#define _LINUX_INOTIFY_SYSCALLS_H + +#include <asm/types.h> +#include <sys/syscall.h> +#include <unistd.h> + +#if defined(__i386__) +# define __NR_inotify_init 291 +# define __NR_inotify_add_watch 292 +# define __NR_inotify_rm_watch 293 +#elif defined(__x86_64__) +# define __NR_inotify_init 253 +# define __NR_inotify_add_watch 254 +# define __NR_inotify_rm_watch 255 +#elif defined(__alpha__) +# define __NR_inotify_init 444 +# define __NR_inotify_add_watch 445 +# define __NR_inotify_rm_watch 446 +#elif defined(__ppc__) || defined(__powerpc__) || defined(__powerpc64__) +# define __NR_inotify_init 275 +# define __NR_inotify_add_watch 276 +# define __NR_inotify_rm_watch 277 +#elif defined(__sparc__) || defined (__sparc64__) +# define __NR_inotify_init 151 +# define __NR_inotify_add_watch 152 +# define __NR_inotify_rm_watch 156 +#elif defined (__ia64__) +# define __NR_inotify_init 1277 +# define __NR_inotify_add_watch 1278 +# define __NR_inotify_rm_watch 1279 +#elif defined (__s390__) || defined (__s390x__) +# define __NR_inotify_init 284 +# define __NR_inotify_add_watch 285 +# define __NR_inotify_rm_watch 286 +#elif defined (__arm__) +# define __NR_inotify_init 316 +# define __NR_inotify_add_watch 317 +# define __NR_inotify_rm_watch 318 +#elif defined (__SH4__) +# define __NR_inotify_init 290 +# define __NR_inotify_add_watch 291 +# define __NR_inotify_rm_watch 292 +#elif defined (__SH5__) +# define __NR_inotify_init 318 +# define __NR_inotify_add_watch 319 +# define __NR_inotify_rm_watch 320 +#else +# warning "Unsupported architecture" +#endif + +#if defined(__i386__) || defined(__x86_64) || defined(__alpha__) || defined(__ppc__) || defined(__sparc__) || defined(__powerpc__) || defined(__powerpc64__) || defined(__ia64__) || defined(__s390__) +static inline int inotify_init (void) +{ + return syscall (__NR_inotify_init); +} + +static inline int inotify_add_watch (int fd, const char *name, __u32 mask) +{ + return syscall (__NR_inotify_add_watch, fd, name, mask); +} + +static inline int inotify_rm_watch (int fd, __u32 wd) +{ + return syscall (__NR_inotify_rm_watch, fd, wd); +} +#else +static inline int inotify_init (void) +{ + return -1; +} + +static inline int inotify_add_watch (int fd, const char *name, __u32 mask) +{ + return -1; +} + +static inline int inotify_rm_watch (int fd, __u32 wd) +{ + return -1; +} + +#endif + +#endif /* _LINUX_INOTIFY_SYSCALLS_H */ diff --git a/gio/xdgmime/.gitignore b/gio/xdgmime/.gitignore new file mode 100644 index 000000000..56e694593 --- /dev/null +++ b/gio/xdgmime/.gitignore @@ -0,0 +1 @@ +test-mime diff --git a/gio/xdgmime/Makefile.am b/gio/xdgmime/Makefile.am new file mode 100644 index 000000000..41f16a7ea --- /dev/null +++ b/gio/xdgmime/Makefile.am @@ -0,0 +1,24 @@ +AM_CPPFLAGS = -DXDG_PREFIX=_gio_xdg + +noinst_LTLIBRARIES = libxdgmime.la + +libxdgmime_la_SOURCES = \ + xdgmime.c \ + xdgmime.h \ + xdgmimealias.c \ + xdgmimealias.h \ + xdgmimecache.c \ + xdgmimecache.h \ + xdgmimeglob.c \ + xdgmimeglob.h \ + xdgmimeint.c \ + xdgmimeint.h \ + xdgmimemagic.c \ + xdgmimemagic.h \ + xdgmimeparent.c \ + xdgmimeparent.h + +noinst_PROGRAMS = test-mime + +test_mime_LDADD = libxdgmime.la +test_mime_SOURCES = test-mime.c diff --git a/gio/xdgmime/test-mime.c b/gio/xdgmime/test-mime.c new file mode 100644 index 000000000..7cff59b49 --- /dev/null +++ b/gio/xdgmime/test-mime.c @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2003,2004 Red Hat, Inc. + * Copyright (C) 2003,2004 Jonathan Blandford <jrb@alum.mit.edu> + * + * Licensed under the Academic Free License version 2.0 + * Or under the following terms: + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ +#include "xdgmime.h" +#include "xdgmimeglob.h" +#include <string.h> +#include <stdio.h> + + +static void +test_individual_glob (const char *glob, + XdgGlobType expected_type) +{ + XdgGlobType test_type; + + test_type = _xdg_glob_determine_type (glob); + if (test_type != expected_type) + { + printf ("Test Failed: %s is of type %s, but %s is expected\n", + glob, + ((test_type == XDG_GLOB_LITERAL)?"XDG_GLOB_LITERAL": + ((test_type == XDG_GLOB_SIMPLE)?"XDG_GLOB_SIMPLE":"XDG_GLOB_FULL")), + ((expected_type == XDG_GLOB_LITERAL)?"XDG_GLOB_LITERAL": + ((expected_type == XDG_GLOB_SIMPLE)?"XDG_GLOB_SIMPLE":"XDG_GLOB_COMPLEX"))); + } +} + +static void +test_glob_type (void) +{ + test_individual_glob ("*.gif", XDG_GLOB_SIMPLE); + test_individual_glob ("Foo*.gif", XDG_GLOB_FULL); + test_individual_glob ("*[4].gif", XDG_GLOB_FULL); + test_individual_glob ("Makefile", XDG_GLOB_LITERAL); + test_individual_glob ("sldkfjvlsdf\\\\slkdjf", XDG_GLOB_FULL); + test_individual_glob ("tree.[ch]", XDG_GLOB_FULL); +} + +static void +test_alias (const char *mime_a, + const char *mime_b, + int expected) +{ + int actual; + + actual = xdg_mime_mime_type_equal (mime_a, mime_b); + + if (actual != expected) + { + printf ("Test Failed: %s is %s to %s\n", + mime_a, actual ? "equal" : "not equal", mime_b); + } +} + +static void +test_aliasing (void) +{ + test_alias ("application/wordperfect", "application/vnd.wordperfect", 1); + test_alias ("application/x-gnome-app-info", "application/x-desktop", 1); + test_alias ("application/x-wordperfect", "application/vnd.wordperfect", 1); + test_alias ("application/x-wordperfect", "audio/x-midi", 0); + test_alias ("/", "vnd/vnd", 0); + test_alias ("application/octet-stream", "text/plain", 0); + test_alias ("text/plain", "text/*", 0); +} + +static void +test_subclass (const char *mime_a, + const char *mime_b, + int expected) +{ + int actual; + + actual = xdg_mime_mime_type_subclass (mime_a, mime_b); + + if (actual != expected) + { + printf ("Test Failed: %s is %s of %s\n", + mime_a, actual ? "subclass" : "not subclass", mime_b); + } +} + +static void +test_subclassing (void) +{ + test_subclass ("application/rtf", "text/plain", 1); + test_subclass ("message/news", "text/plain", 1); + test_subclass ("message/news", "message/*", 1); + test_subclass ("message/news", "text/*", 1); + test_subclass ("message/news", "application/octet-stream", 1); + test_subclass ("application/rtf", "application/octet-stream", 1); + test_subclass ("application/x-gnome-app-info", "text/plain", 1); + test_subclass ("image/x-djvu", "image/vnd.djvu", 1); + test_subclass ("image/vnd.djvu", "image/x-djvu", 1); + test_subclass ("image/vnd.djvu", "text/plain", 0); + test_subclass ("image/vnd.djvu", "text/*", 0); + test_subclass ("text/*", "text/plain", 0); +} + +int +main (int argc, char *argv[]) +{ + const char *result; + const char *file_name; + int i; + + test_glob_type (); + test_aliasing (); + test_subclassing (); + + for (i = 1; i < argc; i++) + { + file_name = argv[i]; + result = xdg_mime_get_mime_type_for_file (file_name, NULL); + printf ("File \"%s\" has a mime-type of %s\n", file_name, result); + } + +#if 0 + xdg_mime_dump (); +#endif + return 0; +} + diff --git a/gio/xdgmime/xdgmime.c b/gio/xdgmime/xdgmime.c new file mode 100644 index 000000000..0e11b070a --- /dev/null +++ b/gio/xdgmime/xdgmime.c @@ -0,0 +1,864 @@ +/* -*- mode: C; c-file-style: "gnu" -*- */ +/* xdgmime.c: XDG Mime Spec mime resolver. Based on version 0.11 of the spec. + * + * More info can be found at http://www.freedesktop.org/standards/ + * + * Copyright (C) 2003,2004 Red Hat, Inc. + * Copyright (C) 2003,2004 Jonathan Blandford <jrb@alum.mit.edu> + * + * Licensed under the Academic Free License version 2.0 + * Or under the following terms: + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "xdgmime.h" +#include "xdgmimeint.h" +#include "xdgmimeglob.h" +#include "xdgmimemagic.h" +#include "xdgmimealias.h" +#include "xdgmimeparent.h" +#include "xdgmimecache.h" +#include <stdio.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/time.h> +#include <unistd.h> +#include <assert.h> + +typedef struct XdgDirTimeList XdgDirTimeList; +typedef struct XdgCallbackList XdgCallbackList; + +static int need_reread = TRUE; +static time_t last_stat_time = 0; + +static XdgGlobHash *global_hash = NULL; +static XdgMimeMagic *global_magic = NULL; +static XdgAliasList *alias_list = NULL; +static XdgParentList *parent_list = NULL; +static XdgDirTimeList *dir_time_list = NULL; +static XdgCallbackList *callback_list = NULL; + +XdgMimeCache **_caches = NULL; +static int n_caches = 0; + +const char xdg_mime_type_unknown[] = "application/octet-stream"; + + +enum +{ + XDG_CHECKED_UNCHECKED, + XDG_CHECKED_VALID, + XDG_CHECKED_INVALID +}; + +struct XdgDirTimeList +{ + time_t mtime; + char *directory_name; + int checked; + XdgDirTimeList *next; +}; + +struct XdgCallbackList +{ + XdgCallbackList *next; + XdgCallbackList *prev; + int callback_id; + XdgMimeCallback callback; + void *data; + XdgMimeDestroy destroy; +}; + +/* Function called by xdg_run_command_on_dirs. If it returns TRUE, further + * directories aren't looked at */ +typedef int (*XdgDirectoryFunc) (const char *directory, + void *user_data); + +static XdgDirTimeList * +xdg_dir_time_list_new (void) +{ + XdgDirTimeList *retval; + + retval = calloc (1, sizeof (XdgDirTimeList)); + retval->checked = XDG_CHECKED_UNCHECKED; + + return retval; +} + +static void +xdg_dir_time_list_free (XdgDirTimeList *list) +{ + XdgDirTimeList *next; + + while (list) + { + next = list->next; + free (list->directory_name); + free (list); + list = next; + } +} + +static int +xdg_mime_init_from_directory (const char *directory) +{ + char *file_name; + struct stat st; + XdgDirTimeList *list; + + assert (directory != NULL); + + file_name = malloc (strlen (directory) + strlen ("/mime/mime.cache") + 1); + strcpy (file_name, directory); strcat (file_name, "/mime/mime.cache"); + if (stat (file_name, &st) == 0) + { + XdgMimeCache *cache = _xdg_mime_cache_new_from_file (file_name); + + if (cache != NULL) + { + list = xdg_dir_time_list_new (); + list->directory_name = file_name; + list->mtime = st.st_mtime; + list->next = dir_time_list; + dir_time_list = list; + + _caches = realloc (_caches, sizeof (XdgMimeCache *) * (n_caches + 2)); + _caches[n_caches] = cache; + _caches[n_caches + 1] = NULL; + n_caches++; + + return FALSE; + } + } + free (file_name); + + file_name = malloc (strlen (directory) + strlen ("/mime/globs") + 1); + strcpy (file_name, directory); strcat (file_name, "/mime/globs"); + if (stat (file_name, &st) == 0) + { + _xdg_mime_glob_read_from_file (global_hash, file_name); + + list = xdg_dir_time_list_new (); + list->directory_name = file_name; + list->mtime = st.st_mtime; + list->next = dir_time_list; + dir_time_list = list; + } + else + { + free (file_name); + } + + file_name = malloc (strlen (directory) + strlen ("/mime/magic") + 1); + strcpy (file_name, directory); strcat (file_name, "/mime/magic"); + if (stat (file_name, &st) == 0) + { + _xdg_mime_magic_read_from_file (global_magic, file_name); + + list = xdg_dir_time_list_new (); + list->directory_name = file_name; + list->mtime = st.st_mtime; + list->next = dir_time_list; + dir_time_list = list; + } + else + { + free (file_name); + } + + file_name = malloc (strlen (directory) + strlen ("/mime/aliases") + 1); + strcpy (file_name, directory); strcat (file_name, "/mime/aliases"); + _xdg_mime_alias_read_from_file (alias_list, file_name); + free (file_name); + + file_name = malloc (strlen (directory) + strlen ("/mime/subclasses") + 1); + strcpy (file_name, directory); strcat (file_name, "/mime/subclasses"); + _xdg_mime_parent_read_from_file (parent_list, file_name); + free (file_name); + + return FALSE; /* Keep processing */ +} + +/* Runs a command on all the directories in the search path */ +static void +xdg_run_command_on_dirs (XdgDirectoryFunc func, + void *user_data) +{ + const char *xdg_data_home; + const char *xdg_data_dirs; + const char *ptr; + + xdg_data_home = getenv ("XDG_DATA_HOME"); + if (xdg_data_home) + { + if ((func) (xdg_data_home, user_data)) + return; + } + else + { + const char *home; + + home = getenv ("HOME"); + if (home != NULL) + { + char *guessed_xdg_home; + int stop_processing; + + guessed_xdg_home = malloc (strlen (home) + strlen ("/.local/share/") + 1); + strcpy (guessed_xdg_home, home); + strcat (guessed_xdg_home, "/.local/share/"); + stop_processing = (func) (guessed_xdg_home, user_data); + free (guessed_xdg_home); + + if (stop_processing) + return; + } + } + + xdg_data_dirs = getenv ("XDG_DATA_DIRS"); + if (xdg_data_dirs == NULL) + xdg_data_dirs = "/usr/local/share/:/usr/share/"; + + ptr = xdg_data_dirs; + + while (*ptr != '\000') + { + const char *end_ptr; + char *dir; + int len; + int stop_processing; + + end_ptr = ptr; + while (*end_ptr != ':' && *end_ptr != '\000') + end_ptr ++; + + if (end_ptr == ptr) + { + ptr++; + continue; + } + + if (*end_ptr == ':') + len = end_ptr - ptr; + else + len = end_ptr - ptr + 1; + dir = malloc (len + 1); + strncpy (dir, ptr, len); + dir[len] = '\0'; + stop_processing = (func) (dir, user_data); + free (dir); + + if (stop_processing) + return; + + ptr = end_ptr; + } +} + +/* Checks file_path to make sure it has the same mtime as last time it was + * checked. If it has a different mtime, or if the file doesn't exist, it + * returns FALSE. + * + * FIXME: This doesn't protect against permission changes. + */ +static int +xdg_check_file (const char *file_path, + int *exists) +{ + struct stat st; + + /* If the file exists */ + if (stat (file_path, &st) == 0) + { + XdgDirTimeList *list; + + if (exists) + *exists = TRUE; + + for (list = dir_time_list; list; list = list->next) + { + if (! strcmp (list->directory_name, file_path) && + st.st_mtime == list->mtime) + { + if (list->checked == XDG_CHECKED_UNCHECKED) + list->checked = XDG_CHECKED_VALID; + else if (list->checked == XDG_CHECKED_VALID) + list->checked = XDG_CHECKED_INVALID; + + return (list->checked != XDG_CHECKED_VALID); + } + } + return TRUE; + } + + if (exists) + *exists = FALSE; + + return FALSE; +} + +static int +xdg_check_dir (const char *directory, + int *invalid_dir_list) +{ + int invalid, exists; + char *file_name; + + assert (directory != NULL); + + /* Check the mime.cache file */ + file_name = malloc (strlen (directory) + strlen ("/mime/mime.cache") + 1); + strcpy (file_name, directory); strcat (file_name, "/mime/mime.cache"); + invalid = xdg_check_file (file_name, &exists); + free (file_name); + if (invalid) + { + *invalid_dir_list = TRUE; + return TRUE; + } + else if (exists) + { + return FALSE; + } + + /* Check the globs file */ + file_name = malloc (strlen (directory) + strlen ("/mime/globs") + 1); + strcpy (file_name, directory); strcat (file_name, "/mime/globs"); + invalid = xdg_check_file (file_name, NULL); + free (file_name); + if (invalid) + { + *invalid_dir_list = TRUE; + return TRUE; + } + + /* Check the magic file */ + file_name = malloc (strlen (directory) + strlen ("/mime/magic") + 1); + strcpy (file_name, directory); strcat (file_name, "/mime/magic"); + invalid = xdg_check_file (file_name, NULL); + free (file_name); + if (invalid) + { + *invalid_dir_list = TRUE; + return TRUE; + } + + return FALSE; /* Keep processing */ +} + +/* Walks through all the mime files stat()ing them to see if they've changed. + * Returns TRUE if they have. */ +static int +xdg_check_dirs (void) +{ + XdgDirTimeList *list; + int invalid_dir_list = FALSE; + + for (list = dir_time_list; list; list = list->next) + list->checked = XDG_CHECKED_UNCHECKED; + + xdg_run_command_on_dirs ((XdgDirectoryFunc) xdg_check_dir, + &invalid_dir_list); + + if (invalid_dir_list) + return TRUE; + + for (list = dir_time_list; list; list = list->next) + { + if (list->checked != XDG_CHECKED_VALID) + return TRUE; + } + + return FALSE; +} + +/* We want to avoid stat()ing on every single mime call, so we only look for + * newer files every 5 seconds. This will return TRUE if we need to reread the + * mime data from disk. + */ +static int +xdg_check_time_and_dirs (void) +{ + struct timeval tv; + time_t current_time; + int retval = FALSE; + + gettimeofday (&tv, NULL); + current_time = tv.tv_sec; + + if (current_time >= last_stat_time + 5) + { + retval = xdg_check_dirs (); + last_stat_time = current_time; + } + + return retval; +} + +/* Called in every public function. It reloads the hash function if need be. + */ +static void +xdg_mime_init (void) +{ + if (xdg_check_time_and_dirs ()) + { + xdg_mime_shutdown (); + } + + if (need_reread) + { + global_hash = _xdg_glob_hash_new (); + global_magic = _xdg_mime_magic_new (); + alias_list = _xdg_mime_alias_list_new (); + parent_list = _xdg_mime_parent_list_new (); + + xdg_run_command_on_dirs ((XdgDirectoryFunc) xdg_mime_init_from_directory, + NULL); + + need_reread = FALSE; + } +} + +const char * +xdg_mime_get_mime_type_for_data (const void *data, + size_t len, + int *result_prio) +{ + const char *mime_type; + + xdg_mime_init (); + + if (_caches) + return _xdg_mime_cache_get_mime_type_for_data (data, len, result_prio); + + mime_type = _xdg_mime_magic_lookup_data (global_magic, data, len, result_prio, NULL, 0); + + if (mime_type) + return mime_type; + + return XDG_MIME_TYPE_UNKNOWN; +} + +const char * +xdg_mime_get_mime_type_for_file (const char *file_name, + struct stat *statbuf) +{ + const char *mime_type; + /* currently, only a few globs occur twice, and none + * more often, so 5 seems plenty. + */ + const char *mime_types[5]; + FILE *file; + unsigned char *data; + int max_extent; + int bytes_read; + struct stat buf; + const char *base_name; + int n; + + if (file_name == NULL) + return NULL; + if (! _xdg_utf8_validate (file_name)) + return NULL; + + xdg_mime_init (); + + if (_caches) + return _xdg_mime_cache_get_mime_type_for_file (file_name, statbuf); + + base_name = _xdg_get_base_name (file_name); + n = _xdg_glob_hash_lookup_file_name (global_hash, base_name, mime_types, 5); + + if (n == 1) + return mime_types[0]; + + if (!statbuf) + { + if (stat (file_name, &buf) != 0) + return XDG_MIME_TYPE_UNKNOWN; + + statbuf = &buf; + } + + if (!S_ISREG (statbuf->st_mode)) + return XDG_MIME_TYPE_UNKNOWN; + + /* FIXME: Need to make sure that max_extent isn't totally broken. This could + * be large and need getting from a stream instead of just reading it all + * in. */ + max_extent = _xdg_mime_magic_get_buffer_extents (global_magic); + data = malloc (max_extent); + if (data == NULL) + return XDG_MIME_TYPE_UNKNOWN; + + file = fopen (file_name, "r"); + if (file == NULL) + { + free (data); + return XDG_MIME_TYPE_UNKNOWN; + } + + bytes_read = fread (data, 1, max_extent, file); + if (ferror (file)) + { + free (data); + fclose (file); + return XDG_MIME_TYPE_UNKNOWN; + } + + mime_type = _xdg_mime_magic_lookup_data (global_magic, data, bytes_read, NULL, + mime_types, n); + + free (data); + fclose (file); + + if (mime_type) + return mime_type; + + return XDG_MIME_TYPE_UNKNOWN; +} + +const char * +xdg_mime_get_mime_type_from_file_name (const char *file_name) +{ + const char *mime_type; + + xdg_mime_init (); + + if (_caches) + return _xdg_mime_cache_get_mime_type_from_file_name (file_name); + + if (_xdg_glob_hash_lookup_file_name (global_hash, file_name, &mime_type, 1)) + return mime_type; + else + return XDG_MIME_TYPE_UNKNOWN; +} + +int +xdg_mime_get_mime_types_from_file_name (const char *file_name, + const char *mime_types[], + int n_mime_types) +{ + xdg_mime_init (); + + if (_caches) + return _xdg_mime_cache_get_mime_types_from_file_name (file_name, mime_types, n_mime_types); + + return _xdg_glob_hash_lookup_file_name (global_hash, file_name, mime_types, n_mime_types); +} + +int +xdg_mime_is_valid_mime_type (const char *mime_type) +{ + /* FIXME: We should make this a better test + */ + return _xdg_utf8_validate (mime_type); +} + +void +xdg_mime_shutdown (void) +{ + XdgCallbackList *list; + + /* FIXME: Need to make this (and the whole library) thread safe */ + if (dir_time_list) + { + xdg_dir_time_list_free (dir_time_list); + dir_time_list = NULL; + } + + if (global_hash) + { + _xdg_glob_hash_free (global_hash); + global_hash = NULL; + } + if (global_magic) + { + _xdg_mime_magic_free (global_magic); + global_magic = NULL; + } + + if (alias_list) + { + _xdg_mime_alias_list_free (alias_list); + alias_list = NULL; + } + + if (parent_list) + { + _xdg_mime_parent_list_free (parent_list); + parent_list = NULL; + } + + if (_caches) + { + int i; + + for (i = 0; i < n_caches; i++) + _xdg_mime_cache_unref (_caches[i]); + free (_caches); + _caches = NULL; + n_caches = 0; + } + + for (list = callback_list; list; list = list->next) + (list->callback) (list->data); + + need_reread = TRUE; +} + +int +xdg_mime_get_max_buffer_extents (void) +{ + xdg_mime_init (); + + if (_caches) + return _xdg_mime_cache_get_max_buffer_extents (); + + return _xdg_mime_magic_get_buffer_extents (global_magic); +} + +const char * +_xdg_mime_unalias_mime_type (const char *mime_type) +{ + const char *lookup; + + if (_caches) + return _xdg_mime_cache_unalias_mime_type (mime_type); + + if ((lookup = _xdg_mime_alias_list_lookup (alias_list, mime_type)) != NULL) + return lookup; + + return mime_type; +} + +const char * +xdg_mime_unalias_mime_type (const char *mime_type) +{ + xdg_mime_init (); + + return _xdg_mime_unalias_mime_type (mime_type); +} + +int +_xdg_mime_mime_type_equal (const char *mime_a, + const char *mime_b) +{ + const char *unalias_a, *unalias_b; + + unalias_a = _xdg_mime_unalias_mime_type (mime_a); + unalias_b = _xdg_mime_unalias_mime_type (mime_b); + + if (strcmp (unalias_a, unalias_b) == 0) + return 1; + + return 0; +} + +int +xdg_mime_mime_type_equal (const char *mime_a, + const char *mime_b) +{ + xdg_mime_init (); + + return _xdg_mime_mime_type_equal (mime_a, mime_b); +} + +int +xdg_mime_media_type_equal (const char *mime_a, + const char *mime_b) +{ + char *sep; + + xdg_mime_init (); + + sep = strchr (mime_a, '/'); + + if (sep && strncmp (mime_a, mime_b, sep - mime_a + 1) == 0) + return 1; + + return 0; +} + +#if 1 +static int +xdg_mime_is_super_type (const char *mime) +{ + int length; + const char *type; + + length = strlen (mime); + type = &(mime[length - 2]); + + if (strcmp (type, "/*") == 0) + return 1; + + return 0; +} +#endif + +int +_xdg_mime_mime_type_subclass (const char *mime, + const char *base) +{ + const char *umime, *ubase; + const char **parents; + + if (_caches) + return _xdg_mime_cache_mime_type_subclass (mime, base); + + umime = _xdg_mime_unalias_mime_type (mime); + ubase = _xdg_mime_unalias_mime_type (base); + + if (strcmp (umime, ubase) == 0) + return 1; + +#if 1 + /* Handle supertypes */ + if (xdg_mime_is_super_type (ubase) && + xdg_mime_media_type_equal (umime, ubase)) + return 1; +#endif + + /* Handle special cases text/plain and application/octet-stream */ + if (strcmp (ubase, "text/plain") == 0 && + strncmp (umime, "text/", 5) == 0) + return 1; + + if (strcmp (ubase, "application/octet-stream") == 0) + return 1; + + parents = _xdg_mime_parent_list_lookup (parent_list, umime); + for (; parents && *parents; parents++) + { + if (_xdg_mime_mime_type_subclass (*parents, ubase)) + return 1; + } + + return 0; +} + +int +xdg_mime_mime_type_subclass (const char *mime, + const char *base) +{ + xdg_mime_init (); + + return _xdg_mime_mime_type_subclass (mime, base); +} + +char ** +xdg_mime_list_mime_parents (const char *mime) +{ + const char **parents; + char **result; + int i, n; + + if (_caches) + return _xdg_mime_cache_list_mime_parents (mime); + + parents = xdg_mime_get_mime_parents (mime); + + if (!parents) + return NULL; + + for (i = 0; parents[i]; i++) ; + + n = (i + 1) * sizeof (char *); + result = (char **) malloc (n); + memcpy (result, parents, n); + + return result; +} + +const char ** +xdg_mime_get_mime_parents (const char *mime) +{ + const char *umime; + + xdg_mime_init (); + + umime = _xdg_mime_unalias_mime_type (mime); + + return _xdg_mime_parent_list_lookup (parent_list, umime); +} + +void +xdg_mime_dump (void) +{ + printf ("*** ALIASES ***\n\n"); + _xdg_mime_alias_list_dump (alias_list); + printf ("\n*** PARENTS ***\n\n"); + _xdg_mime_parent_list_dump (parent_list); +} + + +/* Registers a function to be called every time the mime database reloads its files + */ +int +xdg_mime_register_reload_callback (XdgMimeCallback callback, + void *data, + XdgMimeDestroy destroy) +{ + XdgCallbackList *list_el; + static int callback_id = 1; + + /* Make a new list element */ + list_el = calloc (1, sizeof (XdgCallbackList)); + list_el->callback_id = callback_id; + list_el->callback = callback; + list_el->data = data; + list_el->destroy = destroy; + list_el->next = callback_list; + if (list_el->next) + list_el->next->prev = list_el; + + callback_list = list_el; + callback_id ++; + + return callback_id - 1; +} + +void +xdg_mime_remove_callback (int callback_id) +{ + XdgCallbackList *list; + + for (list = callback_list; list; list = list->next) + { + if (list->callback_id == callback_id) + { + if (list->next) + list->next = list->prev; + + if (list->prev) + list->prev->next = list->next; + else + callback_list = list->next; + + /* invoke the destroy handler */ + (list->destroy) (list->data); + free (list); + return; + } + } +} diff --git a/gio/xdgmime/xdgmime.h b/gio/xdgmime/xdgmime.h new file mode 100644 index 000000000..b8fd2d50f --- /dev/null +++ b/gio/xdgmime/xdgmime.h @@ -0,0 +1,120 @@ +/* -*- mode: C; c-file-style: "gnu" -*- */ +/* xdgmime.h: XDG Mime Spec mime resolver. Based on version 0.11 of the spec. + * + * More info can be found at http://www.freedesktop.org/standards/ + * + * Copyright (C) 2003 Red Hat, Inc. + * Copyright (C) 2003 Jonathan Blandford <jrb@alum.mit.edu> + * + * Licensed under the Academic Free License version 2.0 + * Or under the following terms: + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + + +#ifndef __XDG_MIME_H__ +#define __XDG_MIME_H__ + +#include <stdlib.h> +#include <sys/stat.h> + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +#ifdef XDG_PREFIX +#define XDG_ENTRY(func) _XDG_ENTRY2(XDG_PREFIX,func) +#define _XDG_ENTRY2(prefix,func) _XDG_ENTRY3(prefix,func) +#define _XDG_ENTRY3(prefix,func) prefix##_##func +#endif + +typedef void (*XdgMimeCallback) (void *user_data); +typedef void (*XdgMimeDestroy) (void *user_data); + + +#ifdef XDG_PREFIX +#define xdg_mime_get_mime_type_for_data XDG_ENTRY(get_mime_type_for_data) +#define xdg_mime_get_mime_type_for_file XDG_ENTRY(get_mime_type_for_file) +#define xdg_mime_get_mime_type_from_file_name XDG_ENTRY(get_mime_type_from_file_name) +#define xdg_mime_get_mime_types_from_file_name XDG_ENTRY(get_mime_types_from_file_name) +#define xdg_mime_is_valid_mime_type XDG_ENTRY(is_valid_mime_type) +#define xdg_mime_mime_type_equal XDG_ENTRY(mime_type_equal) +#define _xdg_mime_mime_type_equal XDG_ENTRY(mime_type_equal_p) +#define xdg_mime_media_type_equal XDG_ENTRY(media_type_equal) +#define xdg_mime_mime_type_subclass XDG_ENTRY(mime_type_subclass) +#define _xdg_mime_mime_type_subclass XDG_ENTRY(mime_type_subclass_p) +#define xdg_mime_get_mime_parents XDG_ENTRY(get_mime_parents) +#define xdg_mime_list_mime_parents XDG_ENTRY(list_mime_parents) +#define xdg_mime_unalias_mime_type XDG_ENTRY(unalias_mime_type) +#define _xdg_mime_unalias_mime_type XDG_ENTRY(unalias_mime_type_p) +#define xdg_mime_get_max_buffer_extents XDG_ENTRY(get_max_buffer_extents) +#define xdg_mime_shutdown XDG_ENTRY(shutdown) +#define xdg_mime_dump XDG_ENTRY(dump) +#define xdg_mime_register_reload_callback XDG_ENTRY(register_reload_callback) +#define xdg_mime_remove_callback XDG_ENTRY(remove_callback) +#define xdg_mime_type_unknown XDG_ENTRY(type_unknown) +#endif + +extern const char xdg_mime_type_unknown[]; +#define XDG_MIME_TYPE_UNKNOWN xdg_mime_type_unknown + +const char *xdg_mime_get_mime_type_for_data (const void *data, + size_t len, + int *result_prio); +const char *xdg_mime_get_mime_type_for_file (const char *file_name, + struct stat *statbuf); +const char *xdg_mime_get_mime_type_from_file_name (const char *file_name); +int xdg_mime_get_mime_types_from_file_name(const char *file_name, + const char *mime_types[], + int n_mime_types); +int xdg_mime_is_valid_mime_type (const char *mime_type); +int xdg_mime_mime_type_equal (const char *mime_a, + const char *mime_b); +int xdg_mime_media_type_equal (const char *mime_a, + const char *mime_b); +int xdg_mime_mime_type_subclass (const char *mime_a, + const char *mime_b); + /* xdg_mime_get_mime_parents() is deprecated since it does + * not work correctly with caches. Use xdg_mime_list_parents() + * instead, but notice that that function expects you to free + * the array it returns. + */ +const char **xdg_mime_get_mime_parents (const char *mime); +char ** xdg_mime_list_mime_parents (const char *mime); +const char *xdg_mime_unalias_mime_type (const char *mime); +int xdg_mime_get_max_buffer_extents (void); +void xdg_mime_shutdown (void); +void xdg_mime_dump (void); +int xdg_mime_register_reload_callback (XdgMimeCallback callback, + void *data, + XdgMimeDestroy destroy); +void xdg_mime_remove_callback (int callback_id); + + /* Private versions of functions that don't call xdg_mime_init () */ +int _xdg_mime_mime_type_equal (const char *mime_a, + const char *mime_b); +int _xdg_mime_media_type_equal (const char *mime_a, + const char *mime_b); +int _xdg_mime_mime_type_subclass (const char *mime, + const char *base); +const char *_xdg_mime_unalias_mime_type (const char *mime); + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* __XDG_MIME_H__ */ diff --git a/gio/xdgmime/xdgmimealias.c b/gio/xdgmime/xdgmimealias.c new file mode 100644 index 000000000..07d89eb32 --- /dev/null +++ b/gio/xdgmime/xdgmimealias.c @@ -0,0 +1,184 @@ +/* -*- mode: C; c-file-style: "gnu" -*- */ +/* xdgmimealias.c: Private file. Datastructure for storing the aliases. + * + * More info can be found at http://www.freedesktop.org/standards/ + * + * Copyright (C) 2004 Red Hat, Inc. + * Copyright (C) 2004 Matthias Clasen <mclasen@redhat.com> + * + * Licensed under the Academic Free License version 2.0 + * Or under the following terms: + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "xdgmimealias.h" +#include "xdgmimeint.h" +#include <stdlib.h> +#include <stdio.h> +#include <assert.h> +#include <string.h> +#include <fnmatch.h> + +#ifndef FALSE +#define FALSE (0) +#endif + +#ifndef TRUE +#define TRUE (!FALSE) +#endif + +typedef struct XdgAlias XdgAlias; + +struct XdgAlias +{ + char *alias; + char *mime_type; +}; + +struct XdgAliasList +{ + struct XdgAlias *aliases; + int n_aliases; +}; + +XdgAliasList * +_xdg_mime_alias_list_new (void) +{ + XdgAliasList *list; + + list = malloc (sizeof (XdgAliasList)); + + list->aliases = NULL; + list->n_aliases = 0; + + return list; +} + +void +_xdg_mime_alias_list_free (XdgAliasList *list) +{ + int i; + + if (list->aliases) + { + for (i = 0; i < list->n_aliases; i++) + { + free (list->aliases[i].alias); + free (list->aliases[i].mime_type); + } + free (list->aliases); + } + free (list); +} + +static int +alias_entry_cmp (const void *v1, const void *v2) +{ + return strcmp (((XdgAlias *)v1)->alias, ((XdgAlias *)v2)->alias); +} + +const char * +_xdg_mime_alias_list_lookup (XdgAliasList *list, + const char *alias) +{ + XdgAlias *entry; + XdgAlias key; + + if (list->n_aliases > 0) + { + key.alias = (char *)alias; + key.mime_type = NULL; + + entry = bsearch (&key, list->aliases, list->n_aliases, + sizeof (XdgAlias), alias_entry_cmp); + if (entry) + return entry->mime_type; + } + + return NULL; +} + +void +_xdg_mime_alias_read_from_file (XdgAliasList *list, + const char *file_name) +{ + FILE *file; + char line[255]; + int alloc; + + file = fopen (file_name, "r"); + + if (file == NULL) + return; + + /* FIXME: Not UTF-8 safe. Doesn't work if lines are greater than 255 chars. + * Blah */ + alloc = list->n_aliases + 16; + list->aliases = realloc (list->aliases, alloc * sizeof (XdgAlias)); + while (fgets (line, 255, file) != NULL) + { + char *sep; + if (line[0] == '#') + continue; + + sep = strchr (line, ' '); + if (sep == NULL) + continue; + *(sep++) = '\000'; + sep[strlen (sep) -1] = '\000'; + if (list->n_aliases == alloc) + { + alloc <<= 1; + list->aliases = realloc (list->aliases, + alloc * sizeof (XdgAlias)); + } + list->aliases[list->n_aliases].alias = strdup (line); + list->aliases[list->n_aliases].mime_type = strdup (sep); + list->n_aliases++; + } + list->aliases = realloc (list->aliases, + list->n_aliases * sizeof (XdgAlias)); + + fclose (file); + + if (list->n_aliases > 1) + qsort (list->aliases, list->n_aliases, + sizeof (XdgAlias), alias_entry_cmp); +} + + +void +_xdg_mime_alias_list_dump (XdgAliasList *list) +{ + int i; + + if (list->aliases) + { + for (i = 0; i < list->n_aliases; i++) + { + printf ("%s %s\n", + list->aliases[i].alias, + list->aliases[i].mime_type); + } + } +} + + diff --git a/gio/xdgmime/xdgmimealias.h b/gio/xdgmime/xdgmimealias.h new file mode 100644 index 000000000..d0aaed052 --- /dev/null +++ b/gio/xdgmime/xdgmimealias.h @@ -0,0 +1,51 @@ +/* -*- mode: C; c-file-style: "gnu" -*- */ +/* xdgmimealias.h: Private file. Datastructure for storing the aliases. + * + * More info can be found at http://www.freedesktop.org/standards/ + * + * Copyright (C) 2004 Red Hat, Inc. + * Copyright (C) 200 Matthias Clasen <mclasen@redhat.com> + * + * Licensed under the Academic Free License version 2.0 + * Or under the following terms: + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __XDG_MIME_ALIAS_H__ +#define __XDG_MIME_ALIAS_H__ + +#include "xdgmime.h" + +typedef struct XdgAliasList XdgAliasList; + +#ifdef XDG_PREFIX +#define _xdg_mime_alias_read_from_file XDG_ENTRY(alias_read_from_file) +#define _xdg_mime_alias_list_new XDG_ENTRY(alias_list_new) +#define _xdg_mime_alias_list_free XDG_ENTRY(alias_list_free) +#define _xdg_mime_alias_list_lookup XDG_ENTRY(alias_list_lookup) +#define _xdg_mime_alias_list_dump XDG_ENTRY(alias_list_dump) +#endif + +void _xdg_mime_alias_read_from_file (XdgAliasList *list, + const char *file_name); +XdgAliasList *_xdg_mime_alias_list_new (void); +void _xdg_mime_alias_list_free (XdgAliasList *list); +const char *_xdg_mime_alias_list_lookup (XdgAliasList *list, + const char *alias); +void _xdg_mime_alias_list_dump (XdgAliasList *list); + +#endif /* __XDG_MIME_ALIAS_H__ */ diff --git a/gio/xdgmime/xdgmimecache.c b/gio/xdgmime/xdgmimecache.c new file mode 100644 index 000000000..f17ddf245 --- /dev/null +++ b/gio/xdgmime/xdgmimecache.c @@ -0,0 +1,909 @@ +/* -*- mode: C; c-file-style: "gnu" -*- */ +/* xdgmimealias.c: Private file. mmappable caches for mime data + * + * More info can be found at http://www.freedesktop.org/standards/ + * + * Copyright (C) 2005 Matthias Clasen <mclasen@redhat.com> + * + * Licensed under the Academic Free License version 2.0 + * Or under the following terms: + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <fcntl.h> +#include <unistd.h> +#include <fnmatch.h> +#include <assert.h> + +#include <netinet/in.h> /* for ntohl/ntohs */ + +#ifdef HAVE_MMAP +#include <sys/mman.h> +#endif + +#include <sys/stat.h> +#include <sys/types.h> + +#include "xdgmimecache.h" +#include "xdgmimeint.h" + +#ifndef MAX +#define MAX(a,b) ((a) > (b) ? (a) : (b)) +#endif + +#ifndef FALSE +#define FALSE (0) +#endif + +#ifndef TRUE +#define TRUE (!FALSE) +#endif + +#ifndef _O_BINARY +#define _O_BINARY 0 +#endif + +#ifndef MAP_FAILED +#define MAP_FAILED ((void *) -1) +#endif + +#define MAJOR_VERSION 1 +#define MINOR_VERSION 0 + +struct _XdgMimeCache +{ + int ref_count; + + size_t size; + char *buffer; +}; + +#define GET_UINT16(cache,offset) (ntohs(*(xdg_uint16_t*)((cache) + (offset)))) +#define GET_UINT32(cache,offset) (ntohl(*(xdg_uint32_t*)((cache) + (offset)))) + +XdgMimeCache * +_xdg_mime_cache_ref (XdgMimeCache *cache) +{ + cache->ref_count++; + return cache; +} + +void +_xdg_mime_cache_unref (XdgMimeCache *cache) +{ + cache->ref_count--; + + if (cache->ref_count == 0) + { +#ifdef HAVE_MMAP + munmap (cache->buffer, cache->size); +#endif + free (cache); + } +} + +XdgMimeCache * +_xdg_mime_cache_new_from_file (const char *file_name) +{ + XdgMimeCache *cache = NULL; + +#ifdef HAVE_MMAP + int fd = -1; + struct stat st; + char *buffer = NULL; + + /* Open the file and map it into memory */ + fd = open (file_name, O_RDONLY|_O_BINARY, 0); + + if (fd < 0) + return NULL; + + if (fstat (fd, &st) < 0 || st.st_size < 4) + goto done; + + buffer = (char *) mmap (NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0); + + if (buffer == MAP_FAILED) + goto done; + + /* Verify version */ + if (GET_UINT16 (buffer, 0) != MAJOR_VERSION || + GET_UINT16 (buffer, 2) != MINOR_VERSION) + { + munmap (buffer, st.st_size); + + goto done; + } + + cache = (XdgMimeCache *) malloc (sizeof (XdgMimeCache)); + cache->ref_count = 1; + cache->buffer = buffer; + cache->size = st.st_size; + + done: + if (fd != -1) + close (fd); + +#endif /* HAVE_MMAP */ + + return cache; +} + +static int +cache_magic_matchlet_compare_to_data (XdgMimeCache *cache, + xdg_uint32_t offset, + const void *data, + size_t len) +{ + xdg_uint32_t range_start = GET_UINT32 (cache->buffer, offset); + xdg_uint32_t range_length = GET_UINT32 (cache->buffer, offset + 4); + xdg_uint32_t data_length = GET_UINT32 (cache->buffer, offset + 12); + xdg_uint32_t data_offset = GET_UINT32 (cache->buffer, offset + 16); + xdg_uint32_t mask_offset = GET_UINT32 (cache->buffer, offset + 20); + + int i, j; + + for (i = range_start; i <= range_start + range_length; i++) + { + int valid_matchlet = TRUE; + + if (i + data_length > len) + return FALSE; + + if (mask_offset) + { + for (j = 0; j < data_length; j++) + { + if ((((unsigned char *)cache->buffer)[data_offset + j] & ((unsigned char *)cache->buffer)[mask_offset + j]) != + ((((unsigned char *) data)[j + i]) & ((unsigned char *)cache->buffer)[mask_offset + j])) + { + valid_matchlet = FALSE; + break; + } + } + } + else + { + for (j = 0; j < data_length; j++) + { + if (((unsigned char *)cache->buffer)[data_offset + j] != ((unsigned char *) data)[j + i]) + { + valid_matchlet = FALSE; + break; + } + } + } + + if (valid_matchlet) + return TRUE; + } + + return FALSE; +} + +static int +cache_magic_matchlet_compare (XdgMimeCache *cache, + xdg_uint32_t offset, + const void *data, + size_t len) +{ + xdg_uint32_t n_children = GET_UINT32 (cache->buffer, offset + 24); + xdg_uint32_t child_offset = GET_UINT32 (cache->buffer, offset + 28); + + int i; + + if (cache_magic_matchlet_compare_to_data (cache, offset, data, len)) + { + if (n_children == 0) + return TRUE; + + for (i = 0; i < n_children; i++) + { + if (cache_magic_matchlet_compare (cache, child_offset + 32 * i, + data, len)) + return TRUE; + } + } + + return FALSE; +} + +static const char * +cache_magic_compare_to_data (XdgMimeCache *cache, + xdg_uint32_t offset, + const void *data, + size_t len, + int *prio) +{ + xdg_uint32_t priority = GET_UINT32 (cache->buffer, offset); + xdg_uint32_t mimetype_offset = GET_UINT32 (cache->buffer, offset + 4); + xdg_uint32_t n_matchlets = GET_UINT32 (cache->buffer, offset + 8); + xdg_uint32_t matchlet_offset = GET_UINT32 (cache->buffer, offset + 12); + + int i; + + for (i = 0; i < n_matchlets; i++) + { + if (cache_magic_matchlet_compare (cache, matchlet_offset + i * 32, + data, len)) + { + *prio = priority; + + return cache->buffer + mimetype_offset; + } + } + + return NULL; +} + +static const char * +cache_magic_lookup_data (XdgMimeCache *cache, + const void *data, + size_t len, + int *prio, + const char *mime_types[], + int n_mime_types) +{ + xdg_uint32_t list_offset; + xdg_uint32_t n_entries; + xdg_uint32_t offset; + + int j, n; + + *prio = 0; + + list_offset = GET_UINT32 (cache->buffer, 24); + n_entries = GET_UINT32 (cache->buffer, list_offset); + offset = GET_UINT32 (cache->buffer, list_offset + 8); + + for (j = 0; j < n_entries; j++) + { + const char *match; + + match = cache_magic_compare_to_data (cache, offset + 16 * j, + data, len, prio); + if (match) + return match; + else + { + xdg_uint32_t mimetype_offset; + const char *non_match; + + mimetype_offset = GET_UINT32 (cache->buffer, offset + 16 * j + 4); + non_match = cache->buffer + mimetype_offset; + + for (n = 0; n < n_mime_types; n++) + { + if (mime_types[n] && + xdg_mime_mime_type_equal (mime_types[n], non_match)) + mime_types[n] = NULL; + } + } + } + + return NULL; +} + +static const char * +cache_alias_lookup (const char *alias) +{ + const char *ptr; + int i, min, max, mid, cmp; + + for (i = 0; _caches[i]; i++) + { + XdgMimeCache *cache = _caches[i]; + xdg_uint32_t list_offset = GET_UINT32 (cache->buffer, 4); + xdg_uint32_t n_entries = GET_UINT32 (cache->buffer, list_offset); + xdg_uint32_t offset; + + min = 0; + max = n_entries - 1; + while (max >= min) + { + mid = (min + max) / 2; + + offset = GET_UINT32 (cache->buffer, list_offset + 4 + 8 * mid); + ptr = cache->buffer + offset; + cmp = strcmp (ptr, alias); + + if (cmp < 0) + min = mid + 1; + else if (cmp > 0) + max = mid - 1; + else + { + offset = GET_UINT32 (cache->buffer, list_offset + 4 + 8 * mid + 4); + return cache->buffer + offset; + } + } + } + + return NULL; +} + +static int +cache_glob_lookup_literal (const char *file_name, + const char *mime_types[], + int n_mime_types) +{ + const char *ptr; + int i, min, max, mid, cmp; + + for (i = 0; _caches[i]; i++) + { + XdgMimeCache *cache = _caches[i]; + xdg_uint32_t list_offset = GET_UINT32 (cache->buffer, 12); + xdg_uint32_t n_entries = GET_UINT32 (cache->buffer, list_offset); + xdg_uint32_t offset; + + min = 0; + max = n_entries - 1; + while (max >= min) + { + mid = (min + max) / 2; + + offset = GET_UINT32 (cache->buffer, list_offset + 4 + 8 * mid); + ptr = cache->buffer + offset; + cmp = strcmp (ptr, file_name); + + if (cmp < 0) + min = mid + 1; + else if (cmp > 0) + max = mid - 1; + else + { + offset = GET_UINT32 (cache->buffer, list_offset + 4 + 8 * mid + 4); + mime_types[0] = (const char *)(cache->buffer + offset); + + return 1; + } + } + } + + return 0; +} + +static int +cache_glob_lookup_fnmatch (const char *file_name, + const char *mime_types[], + int n_mime_types) +{ + const char *mime_type; + const char *ptr; + + int i, j, n; + + n = 0; + for (i = 0; _caches[i]; i++) + { + XdgMimeCache *cache = _caches[i]; + + xdg_uint32_t list_offset = GET_UINT32 (cache->buffer, 20); + xdg_uint32_t n_entries = GET_UINT32 (cache->buffer, list_offset); + + for (j = 0; j < n_entries && n < n_mime_types; j++) + { + xdg_uint32_t offset = GET_UINT32 (cache->buffer, list_offset + 4 + 8 * j); + xdg_uint32_t mimetype_offset = GET_UINT32 (cache->buffer, list_offset + 4 + 8 * j + 4); + ptr = cache->buffer + offset; + mime_type = cache->buffer + mimetype_offset; + + /* FIXME: Not UTF-8 safe */ + if (fnmatch (ptr, file_name, 0) == 0) + mime_types[n++] = mime_type; + } + + if (n > 0) + return n; + } + + return 0; +} + +static int +cache_glob_node_lookup_suffix (XdgMimeCache *cache, + xdg_uint32_t n_entries, + xdg_uint32_t offset, + const char *suffix, + int ignore_case, + const char *mime_types[], + int n_mime_types) +{ + xdg_unichar_t character; + xdg_unichar_t match_char; + xdg_uint32_t mimetype_offset; + xdg_uint32_t n_children; + xdg_uint32_t child_offset; + + int min, max, mid, n, i; + + character = _xdg_utf8_to_ucs4 (suffix); + if (ignore_case) + character = _xdg_ucs4_to_lower (character); + + min = 0; + max = n_entries - 1; + while (max >= min) + { + mid = (min + max) / 2; + + match_char = GET_UINT32 (cache->buffer, offset + 16 * mid); + + if (match_char < character) + min = mid + 1; + else if (match_char > character) + max = mid - 1; + else + { + suffix = _xdg_utf8_next_char (suffix); + if (*suffix == '\0') + { + mimetype_offset = GET_UINT32 (cache->buffer, offset + 16 * mid + 4); + n = 0; + if (mimetype_offset) + mime_types[n++] = cache->buffer + mimetype_offset; + + n_children = GET_UINT32 (cache->buffer, offset + 16 * mid + 8); + child_offset = GET_UINT32 (cache->buffer, offset + 16 * mid + 12); + i = 0; + while (n < n_mime_types && i < n_children) + { + match_char = GET_UINT32 (cache->buffer, child_offset + 16 * i); + mimetype_offset = GET_UINT32 (cache->buffer, offset + 16 * i + 4); + if (match_char != 0) + break; + + mime_types[n++] = cache->buffer + mimetype_offset; + i++; + } + + return n; + } + else + { + n_children = GET_UINT32 (cache->buffer, offset + 16 * mid + 8); + child_offset = GET_UINT32 (cache->buffer, offset + 16 * mid + 12); + + return cache_glob_node_lookup_suffix (cache, + n_children, child_offset, + suffix, ignore_case, + mime_types, + n_mime_types); + } + } + } + + return 0; +} + +static int +cache_glob_lookup_suffix (const char *suffix, + int ignore_case, + const char *mime_types[], + int n_mime_types) +{ + int i, n; + + for (i = 0; _caches[i]; i++) + { + XdgMimeCache *cache = _caches[i]; + + xdg_uint32_t list_offset = GET_UINT32 (cache->buffer, 16); + xdg_uint32_t n_entries = GET_UINT32 (cache->buffer, list_offset); + xdg_uint32_t offset = GET_UINT32 (cache->buffer, list_offset + 4); + + n = cache_glob_node_lookup_suffix (cache, + n_entries, offset, + suffix, ignore_case, + mime_types, + n_mime_types); + if (n > 0) + return n; + } + + return 0; +} + +static void +find_stopchars (char *stopchars) +{ + int i, j, k, l; + + k = 0; + for (i = 0; _caches[i]; i++) + { + XdgMimeCache *cache = _caches[i]; + + xdg_uint32_t list_offset = GET_UINT32 (cache->buffer, 16); + xdg_uint32_t n_entries = GET_UINT32 (cache->buffer, list_offset); + xdg_uint32_t offset = GET_UINT32 (cache->buffer, list_offset + 4); + + for (j = 0; j < n_entries; j++) + { + xdg_uint32_t match_char = GET_UINT32 (cache->buffer, offset); + + if (match_char < 128) + { + for (l = 0; l < k; l++) + if (stopchars[l] == match_char) + break; + if (l == k) + { + stopchars[k] = (char) match_char; + k++; + } + } + + offset += 16; + } + } + + stopchars[k] = '\0'; +} + +static int +cache_glob_lookup_file_name (const char *file_name, + const char *mime_types[], + int n_mime_types) +{ + const char *ptr; + char stopchars[128]; + int n; + + assert (file_name != NULL); + + /* First, check the literals */ + n = cache_glob_lookup_literal (file_name, mime_types, n_mime_types); + if (n > 0) + return n; + + find_stopchars (stopchars); + + /* Next, check suffixes */ + ptr = strpbrk (file_name, stopchars); + while (ptr) + { + n = cache_glob_lookup_suffix (ptr, FALSE, mime_types, n_mime_types); + if (n > 0) + return n; + + n = cache_glob_lookup_suffix (ptr, TRUE, mime_types, n_mime_types); + if (n > 0) + return n; + + ptr = strpbrk (ptr + 1, stopchars); + } + + /* Last, try fnmatch */ + return cache_glob_lookup_fnmatch (file_name, mime_types, n_mime_types); +} + +int +_xdg_mime_cache_get_max_buffer_extents (void) +{ + xdg_uint32_t offset; + xdg_uint32_t max_extent; + int i; + + max_extent = 0; + for (i = 0; _caches[i]; i++) + { + XdgMimeCache *cache = _caches[i]; + + offset = GET_UINT32 (cache->buffer, 24); + max_extent = MAX (max_extent, GET_UINT32 (cache->buffer, offset + 4)); + } + + return max_extent; +} + +static const char * +cache_get_mime_type_for_data (const void *data, + size_t len, + int *result_prio, + const char *mime_types[], + int n_mime_types) +{ + const char *mime_type; + int i, n, priority; + + priority = 0; + mime_type = NULL; + for (i = 0; _caches[i]; i++) + { + XdgMimeCache *cache = _caches[i]; + + int prio; + const char *match; + + match = cache_magic_lookup_data (cache, data, len, &prio, + mime_types, n_mime_types); + if (prio > priority) + { + priority = prio; + mime_type = match; + } + } + + if (result_prio) + *result_prio = priority; + + if (priority > 0) + return mime_type; + + for (n = 0; n < n_mime_types; n++) + { + + if (mime_types[n]) + return mime_types[n]; + } + + return XDG_MIME_TYPE_UNKNOWN; +} + +const char * +_xdg_mime_cache_get_mime_type_for_data (const void *data, + size_t len, + int *result_prio) +{ + return cache_get_mime_type_for_data (data, len, result_prio, NULL, 0); +} + +const char * +_xdg_mime_cache_get_mime_type_for_file (const char *file_name, + struct stat *statbuf) +{ + const char *mime_type; + const char *mime_types[2]; + FILE *file; + unsigned char *data; + int max_extent; + int bytes_read; + struct stat buf; + const char *base_name; + int n; + + if (file_name == NULL) + return NULL; + + if (! _xdg_utf8_validate (file_name)) + return NULL; + + base_name = _xdg_get_base_name (file_name); + n = cache_glob_lookup_file_name (base_name, mime_types, 2); + + if (n == 1) + return mime_types[0]; + + if (!statbuf) + { + if (stat (file_name, &buf) != 0) + return XDG_MIME_TYPE_UNKNOWN; + + statbuf = &buf; + } + + if (!S_ISREG (statbuf->st_mode)) + return XDG_MIME_TYPE_UNKNOWN; + + /* FIXME: Need to make sure that max_extent isn't totally broken. This could + * be large and need getting from a stream instead of just reading it all + * in. */ + max_extent = _xdg_mime_cache_get_max_buffer_extents (); + data = malloc (max_extent); + if (data == NULL) + return XDG_MIME_TYPE_UNKNOWN; + + file = fopen (file_name, "r"); + if (file == NULL) + { + free (data); + return XDG_MIME_TYPE_UNKNOWN; + } + + bytes_read = fread (data, 1, max_extent, file); + if (ferror (file)) + { + free (data); + fclose (file); + return XDG_MIME_TYPE_UNKNOWN; + } + + mime_type = cache_get_mime_type_for_data (data, bytes_read, NULL, + mime_types, n); + + free (data); + fclose (file); + + return mime_type; +} + +const char * +_xdg_mime_cache_get_mime_type_from_file_name (const char *file_name) +{ + const char *mime_type; + + if (cache_glob_lookup_file_name (file_name, &mime_type, 1)) + return mime_type; + else + return XDG_MIME_TYPE_UNKNOWN; +} + +int +_xdg_mime_cache_get_mime_types_from_file_name (const char *file_name, + const char *mime_types[], + int n_mime_types) +{ + return cache_glob_lookup_file_name (file_name, mime_types, n_mime_types); +} + +#if 1 +static int +is_super_type (const char *mime) +{ + int length; + const char *type; + + length = strlen (mime); + type = &(mime[length - 2]); + + if (strcmp (type, "/*") == 0) + return 1; + + return 0; +} +#endif + +int +_xdg_mime_cache_mime_type_subclass (const char *mime, + const char *base) +{ + const char *umime, *ubase; + + int i, j, min, max, med, cmp; + + umime = _xdg_mime_cache_unalias_mime_type (mime); + ubase = _xdg_mime_cache_unalias_mime_type (base); + + if (strcmp (umime, ubase) == 0) + return 1; + + /* We really want to handle text/ * in GtkFileFilter, so we just + * turn on the supertype matching + */ +#if 1 + /* Handle supertypes */ + if (is_super_type (ubase) && + xdg_mime_media_type_equal (umime, ubase)) + return 1; +#endif + + /* Handle special cases text/plain and application/octet-stream */ + if (strcmp (ubase, "text/plain") == 0 && + strncmp (umime, "text/", 5) == 0) + return 1; + + if (strcmp (ubase, "application/octet-stream") == 0) + return 1; + + for (i = 0; _caches[i]; i++) + { + XdgMimeCache *cache = _caches[i]; + + xdg_uint32_t list_offset = GET_UINT32 (cache->buffer, 8); + xdg_uint32_t n_entries = GET_UINT32 (cache->buffer, list_offset); + xdg_uint32_t offset, n_parents, parent_offset; + + min = 0; + max = n_entries - 1; + while (max >= min) + { + med = (min + max)/2; + + offset = GET_UINT32 (cache->buffer, list_offset + 4 + 8 * med); + cmp = strcmp (cache->buffer + offset, umime); + if (cmp < 0) + min = med + 1; + else if (cmp > 0) + max = med - 1; + else + { + offset = GET_UINT32 (cache->buffer, list_offset + 4 + 8 * med + 4); + n_parents = GET_UINT32 (cache->buffer, offset); + + for (j = 0; j < n_parents; j++) + { + parent_offset = GET_UINT32 (cache->buffer, offset + 4 + 4 * j); + if (_xdg_mime_cache_mime_type_subclass (cache->buffer + parent_offset, ubase)) + return 1; + } + + break; + } + } + } + + return 0; +} + +const char * +_xdg_mime_cache_unalias_mime_type (const char *mime) +{ + const char *lookup; + + lookup = cache_alias_lookup (mime); + + if (lookup) + return lookup; + + return mime; +} + +char ** +_xdg_mime_cache_list_mime_parents (const char *mime) +{ + int i, j, k, p; + char *all_parents[128]; /* we'll stop at 128 */ + char **result; + + mime = xdg_mime_unalias_mime_type (mime); + + p = 0; + for (i = 0; _caches[i]; i++) + { + XdgMimeCache *cache = _caches[i]; + + xdg_uint32_t list_offset = GET_UINT32 (cache->buffer, 8); + xdg_uint32_t n_entries = GET_UINT32 (cache->buffer, list_offset); + + for (j = 0; j < n_entries; j++) + { + xdg_uint32_t mimetype_offset = GET_UINT32 (cache->buffer, list_offset + 4 + 8 * j); + xdg_uint32_t parents_offset = GET_UINT32 (cache->buffer, list_offset + 4 + 8 * j + 4); + + if (strcmp (cache->buffer + mimetype_offset, mime) == 0) + { + xdg_uint32_t parent_mime_offset; + xdg_uint32_t n_parents = GET_UINT32 (cache->buffer, parents_offset); + + for (k = 0; k < n_parents && p < 127; k++) + { + parent_mime_offset = GET_UINT32 (cache->buffer, parents_offset + 4 + 4 * k); + all_parents[p++] = cache->buffer + parent_mime_offset; + } + + break; + } + } + } + all_parents[p++] = 0; + + result = (char **) malloc (p * sizeof (char *)); + memcpy (result, all_parents, p * sizeof (char *)); + + return result; +} + diff --git a/gio/xdgmime/xdgmimecache.h b/gio/xdgmime/xdgmimecache.h new file mode 100644 index 000000000..1cd978fae --- /dev/null +++ b/gio/xdgmime/xdgmimecache.h @@ -0,0 +1,76 @@ +/* -*- mode: C; c-file-style: "gnu" -*- */ +/* xdgmimecache.h: Private file. Datastructure for mmapped caches. + * + * More info can be found at http://www.freedesktop.org/standards/ + * + * Copyright (C) 2005 Matthias Clasen <mclasen@redhat.com> + * + * Licensed under the Academic Free License version 2.0 + * Or under the following terms: + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __XDG_MIME_CACHE_H__ +#define __XDG_MIME_CACHE_H__ + +#include "xdgmime.h" + +typedef struct _XdgMimeCache XdgMimeCache; + +#ifdef XDG_PREFIX +#define _xdg_mime_cache_new_from_file XDG_ENTRY(cache_new_from_file) +#define _xdg_mime_cache_ref XDG_ENTRY(cache_ref) +#define _xdg_mime_cache_unref XDG_ENTRY(cache_unref) +#define _xdg_mime_cache_get_max_buffer_extents XDG_ENTRY(cache_get_max_buffer_extents) +#define _xdg_mime_cache_get_mime_type_for_data XDG_ENTRY(cache_get_mime_type_for_data) +#define _xdg_mime_cache_get_mime_type_for_file XDG_ENTRY(cache_get_mime_type_for_file) +#define _xdg_mime_cache_get_mime_type_from_file_name XDG_ENTRY(cache_get_mime_type_from_file_name) +#define _xdg_mime_cache_get_mime_types_from_file_name XDG_ENTRY(cache_get_mime_types_from_file_name) +#define _xdg_mime_cache_list_mime_parents XDG_ENTRY(cache_list_mime_parents) +#define _xdg_mime_cache_mime_type_subclass XDG_ENTRY(cache_mime_type_subclass) +#define _xdg_mime_cache_unalias_mime_type XDG_ENTRY(cache_unalias_mime_type) + +#endif + +extern XdgMimeCache **_caches; + +XdgMimeCache *_xdg_mime_cache_new_from_file (const char *file_name); +XdgMimeCache *_xdg_mime_cache_ref (XdgMimeCache *cache); +void _xdg_mime_cache_unref (XdgMimeCache *cache); + + +const char *_xdg_mime_cache_get_mime_type_for_data (const void *data, + size_t len, + int *result_prio); +const char *_xdg_mime_cache_get_mime_type_for_file (const char *file_name, + struct stat *statbuf); +int _xdg_mime_cache_get_mime_types_from_file_name (const char *file_name, + const char *mime_types[], + int n_mime_types); +const char *_xdg_mime_cache_get_mime_type_from_file_name (const char *file_name); +int _xdg_mime_cache_is_valid_mime_type (const char *mime_type); +int _xdg_mime_cache_mime_type_equal (const char *mime_a, + const char *mime_b); +int _xdg_mime_cache_media_type_equal (const char *mime_a, + const char *mime_b); +int _xdg_mime_cache_mime_type_subclass (const char *mime_a, + const char *mime_b); +char **_xdg_mime_cache_list_mime_parents (const char *mime); +const char *_xdg_mime_cache_unalias_mime_type (const char *mime); +int _xdg_mime_cache_get_max_buffer_extents (void); + +#endif /* __XDG_MIME_CACHE_H__ */ diff --git a/gio/xdgmime/xdgmimeglob.c b/gio/xdgmime/xdgmimeglob.c new file mode 100644 index 000000000..3aad6113c --- /dev/null +++ b/gio/xdgmime/xdgmimeglob.c @@ -0,0 +1,547 @@ +/* -*- mode: C; c-file-style: "gnu" -*- */ +/* xdgmimeglob.c: Private file. Datastructure for storing the globs. + * + * More info can be found at http://www.freedesktop.org/standards/ + * + * Copyright (C) 2003 Red Hat, Inc. + * Copyright (C) 2003 Jonathan Blandford <jrb@alum.mit.edu> + * + * Licensed under the Academic Free License version 2.0 + * Or under the following terms: + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "xdgmimeglob.h" +#include "xdgmimeint.h" +#include <stdlib.h> +#include <stdio.h> +#include <assert.h> +#include <string.h> +#include <fnmatch.h> + +#ifndef FALSE +#define FALSE (0) +#endif + +#ifndef TRUE +#define TRUE (!FALSE) +#endif + +typedef struct XdgGlobHashNode XdgGlobHashNode; +typedef struct XdgGlobList XdgGlobList; + +struct XdgGlobHashNode +{ + xdg_unichar_t character; + const char *mime_type; + XdgGlobHashNode *next; + XdgGlobHashNode *child; +}; +struct XdgGlobList +{ + const char *data; + const char *mime_type; + XdgGlobList *next; +}; + +struct XdgGlobHash +{ + XdgGlobList *literal_list; + XdgGlobHashNode *simple_node; + XdgGlobList *full_list; +}; + + +/* XdgGlobList + */ +static XdgGlobList * +_xdg_glob_list_new (void) +{ + XdgGlobList *new_element; + + new_element = calloc (1, sizeof (XdgGlobList)); + + return new_element; +} + +/* Frees glob_list and all of it's children */ +static void +_xdg_glob_list_free (XdgGlobList *glob_list) +{ + XdgGlobList *ptr, *next; + + ptr = glob_list; + + while (ptr != NULL) + { + next = ptr->next; + + if (ptr->data) + free ((void *) ptr->data); + if (ptr->mime_type) + free ((void *) ptr->mime_type); + free (ptr); + + ptr = next; + } +} + +static XdgGlobList * +_xdg_glob_list_append (XdgGlobList *glob_list, + void *data, + const char *mime_type) +{ + XdgGlobList *new_element; + XdgGlobList *tmp_element; + + new_element = _xdg_glob_list_new (); + new_element->data = data; + new_element->mime_type = mime_type; + if (glob_list == NULL) + return new_element; + + tmp_element = glob_list; + while (tmp_element->next != NULL) + tmp_element = tmp_element->next; + + tmp_element->next = new_element; + + return glob_list; +} + +#if 0 +static XdgGlobList * +_xdg_glob_list_prepend (XdgGlobList *glob_list, + void *data, + const char *mime_type) +{ + XdgGlobList *new_element; + + new_element = _xdg_glob_list_new (); + new_element->data = data; + new_element->next = glob_list; + new_element->mime_type = mime_type; + + return new_element; +} +#endif + +/* XdgGlobHashNode + */ + +static XdgGlobHashNode * +_xdg_glob_hash_node_new (void) +{ + XdgGlobHashNode *glob_hash_node; + + glob_hash_node = calloc (1, sizeof (XdgGlobHashNode)); + + return glob_hash_node; +} + +static void +_xdg_glob_hash_node_dump (XdgGlobHashNode *glob_hash_node, + int depth) +{ + int i; + for (i = 0; i < depth; i++) + printf (" "); + + printf ("%c", (char)glob_hash_node->character); + if (glob_hash_node->mime_type) + printf (" - %s\n", glob_hash_node->mime_type); + else + printf ("\n"); + if (glob_hash_node->child) + _xdg_glob_hash_node_dump (glob_hash_node->child, depth + 1); + if (glob_hash_node->next) + _xdg_glob_hash_node_dump (glob_hash_node->next, depth); +} + +static XdgGlobHashNode * +_xdg_glob_hash_insert_text (XdgGlobHashNode *glob_hash_node, + const char *text, + const char *mime_type) +{ + XdgGlobHashNode *node; + xdg_unichar_t character; + + character = _xdg_utf8_to_ucs4 (text); + + if ((glob_hash_node == NULL) || + (character < glob_hash_node->character)) + { + node = _xdg_glob_hash_node_new (); + node->character = character; + node->next = glob_hash_node; + glob_hash_node = node; + } + else if (character == glob_hash_node->character) + { + node = glob_hash_node; + } + else + { + XdgGlobHashNode *prev_node; + int found_node = FALSE; + + /* Look for the first character of text in glob_hash_node, and insert it if we + * have to.*/ + prev_node = glob_hash_node; + node = prev_node->next; + + while (node != NULL) + { + if (character < node->character) + { + node = _xdg_glob_hash_node_new (); + node->character = character; + node->next = prev_node->next; + prev_node->next = node; + + found_node = TRUE; + break; + } + else if (character == node->character) + { + found_node = TRUE; + break; + } + prev_node = node; + node = node->next; + } + + if (! found_node) + { + node = _xdg_glob_hash_node_new (); + node->character = character; + node->next = prev_node->next; + prev_node->next = node; + } + } + + text = _xdg_utf8_next_char (text); + if (*text == '\000') + { + if (node->mime_type) + { + if (strcmp (node->mime_type, mime_type)) + { + XdgGlobHashNode *child; + int found_node = FALSE; + + child = node->child; + while (child && child->character == '\0') + { + if (strcmp (child->mime_type, mime_type) == 0) + { + found_node = TRUE; + break; + } + child = child->next; + } + + if (!found_node) + { + child = _xdg_glob_hash_node_new (); + child->character = '\000'; + child->mime_type = strdup (mime_type); + child->child = NULL; + child->next = node->child; + node->child = child; + } + } + } + else + { + node->mime_type = strdup (mime_type); + } + } + else + { + node->child = _xdg_glob_hash_insert_text (node->child, text, mime_type); + } + return glob_hash_node; +} + +static int +_xdg_glob_hash_node_lookup_file_name (XdgGlobHashNode *glob_hash_node, + const char *file_name, + int ignore_case, + const char *mime_types[], + int n_mime_types) +{ + int n; + XdgGlobHashNode *node; + xdg_unichar_t character; + + if (glob_hash_node == NULL) + return 0; + + character = _xdg_utf8_to_ucs4 (file_name); + if (ignore_case) + character = _xdg_ucs4_to_lower(character); + + for (node = glob_hash_node; node && character >= node->character; node = node->next) + { + if (character == node->character) + { + file_name = _xdg_utf8_next_char (file_name); + if (*file_name == '\000') + { + n = 0; + if (node->mime_type) + mime_types[n++] = node->mime_type; + node = node->child; + while (n < n_mime_types && node && node->character == 0) + { + if (node->mime_type) + mime_types[n++] = node->mime_type; + node = node->next; + } + } + else + { + n = _xdg_glob_hash_node_lookup_file_name (node->child, + file_name, + ignore_case, + mime_types, + n_mime_types); + } + return n; + } + } + + return 0; +} + +int +_xdg_glob_hash_lookup_file_name (XdgGlobHash *glob_hash, + const char *file_name, + const char *mime_types[], + int n_mime_types) +{ + XdgGlobList *list; + const char *ptr; + char stopchars[128]; + int i, n; + XdgGlobHashNode *node; + + /* First, check the literals */ + + assert (file_name != NULL && n_mime_types > 0); + + for (list = glob_hash->literal_list; list; list = list->next) + { + if (strcmp ((const char *)list->data, file_name) == 0) + { + mime_types[0] = list->mime_type; + return 1; + } + } + + i = 0; + for (node = glob_hash->simple_node; node; node = node->next) + { + if (node->character < 128) + stopchars[i++] = (char)node->character; + } + stopchars[i] = '\0'; + + ptr = strpbrk (file_name, stopchars); + while (ptr) + { + n = _xdg_glob_hash_node_lookup_file_name (glob_hash->simple_node, ptr, FALSE, + mime_types, n_mime_types); + if (n > 0) + return n; + + n = _xdg_glob_hash_node_lookup_file_name (glob_hash->simple_node, ptr, TRUE, + mime_types, n_mime_types); + if (n > 0) + return n; + + ptr = strpbrk (ptr + 1, stopchars); + } + + /* FIXME: Not UTF-8 safe */ + n = 0; + for (list = glob_hash->full_list; list && n < n_mime_types; list = list->next) + { + if (fnmatch ((const char *)list->data, file_name, 0) == 0) + mime_types[n++] = list->mime_type; + } + + return n; +} + + + +/* XdgGlobHash + */ + +XdgGlobHash * +_xdg_glob_hash_new (void) +{ + XdgGlobHash *glob_hash; + + glob_hash = calloc (1, sizeof (XdgGlobHash)); + + return glob_hash; +} + + +static void +_xdg_glob_hash_free_nodes (XdgGlobHashNode *node) +{ + if (node) + { + if (node->child) + _xdg_glob_hash_free_nodes (node->child); + if (node->next) + _xdg_glob_hash_free_nodes (node->next); + if (node->mime_type) + free ((void *) node->mime_type); + free (node); + } +} + +void +_xdg_glob_hash_free (XdgGlobHash *glob_hash) +{ + _xdg_glob_list_free (glob_hash->literal_list); + _xdg_glob_list_free (glob_hash->full_list); + _xdg_glob_hash_free_nodes (glob_hash->simple_node); + free (glob_hash); +} + +XdgGlobType +_xdg_glob_determine_type (const char *glob) +{ + const char *ptr; + int maybe_in_simple_glob = FALSE; + int first_char = TRUE; + + ptr = glob; + + while (*ptr != '\000') + { + if (*ptr == '*' && first_char) + maybe_in_simple_glob = TRUE; + else if (*ptr == '\\' || *ptr == '[' || *ptr == '?' || *ptr == '*') + return XDG_GLOB_FULL; + + first_char = FALSE; + ptr = _xdg_utf8_next_char (ptr); + } + if (maybe_in_simple_glob) + return XDG_GLOB_SIMPLE; + else + return XDG_GLOB_LITERAL; +} + +/* glob must be valid UTF-8 */ +void +_xdg_glob_hash_append_glob (XdgGlobHash *glob_hash, + const char *glob, + const char *mime_type) +{ + XdgGlobType type; + + assert (glob_hash != NULL); + assert (glob != NULL); + + type = _xdg_glob_determine_type (glob); + + switch (type) + { + case XDG_GLOB_LITERAL: + glob_hash->literal_list = _xdg_glob_list_append (glob_hash->literal_list, strdup (glob), strdup (mime_type)); + break; + case XDG_GLOB_SIMPLE: + glob_hash->simple_node = _xdg_glob_hash_insert_text (glob_hash->simple_node, glob + 1, mime_type); + break; + case XDG_GLOB_FULL: + glob_hash->full_list = _xdg_glob_list_append (glob_hash->full_list, strdup (glob), strdup (mime_type)); + break; + } +} + +void +_xdg_glob_hash_dump (XdgGlobHash *glob_hash) +{ + XdgGlobList *list; + printf ("LITERAL STRINGS\n"); + if (glob_hash->literal_list == NULL) + { + printf (" None\n"); + } + else + { + for (list = glob_hash->literal_list; list; list = list->next) + printf (" %s - %s\n", (char *)list->data, list->mime_type); + } + printf ("\nSIMPLE GLOBS\n"); + _xdg_glob_hash_node_dump (glob_hash->simple_node, 4); + + printf ("\nFULL GLOBS\n"); + if (glob_hash->full_list == NULL) + { + printf (" None\n"); + } + else + { + for (list = glob_hash->full_list; list; list = list->next) + printf (" %s - %s\n", (char *)list->data, list->mime_type); + } +} + + +void +_xdg_mime_glob_read_from_file (XdgGlobHash *glob_hash, + const char *file_name) +{ + FILE *glob_file; + char line[255]; + + glob_file = fopen (file_name, "r"); + + if (glob_file == NULL) + return; + + /* FIXME: Not UTF-8 safe. Doesn't work if lines are greater than 255 chars. + * Blah */ + while (fgets (line, 255, glob_file) != NULL) + { + char *colon; + if (line[0] == '#') + continue; + + colon = strchr (line, ':'); + if (colon == NULL) + continue; + *(colon++) = '\000'; + colon[strlen (colon) -1] = '\000'; + _xdg_glob_hash_append_glob (glob_hash, colon, line); + } + + fclose (glob_file); +} diff --git a/gio/xdgmime/xdgmimeglob.h b/gio/xdgmime/xdgmimeglob.h new file mode 100644 index 000000000..25a1f20e5 --- /dev/null +++ b/gio/xdgmime/xdgmimeglob.h @@ -0,0 +1,67 @@ +/* -*- mode: C; c-file-style: "gnu" -*- */ +/* xdgmimeglob.h: Private file. Datastructure for storing the globs. + * + * More info can be found at http://www.freedesktop.org/standards/ + * + * Copyright (C) 2003 Red Hat, Inc. + * Copyright (C) 2003 Jonathan Blandford <jrb@alum.mit.edu> + * + * Licensed under the Academic Free License version 2.0 + * Or under the following terms: + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __XDG_MIME_GLOB_H__ +#define __XDG_MIME_GLOB_H__ + +#include "xdgmime.h" + +typedef struct XdgGlobHash XdgGlobHash; + +typedef enum +{ + XDG_GLOB_LITERAL, /* Makefile */ + XDG_GLOB_SIMPLE, /* *.gif */ + XDG_GLOB_FULL /* x*.[ch] */ +} XdgGlobType; + + +#ifdef XDG_PREFIX +#define _xdg_mime_glob_read_from_file XDG_ENTRY(glob_read_from_file) +#define _xdg_glob_hash_new XDG_ENTRY(hash_new) +#define _xdg_glob_hash_free XDG_ENTRY(hash_free) +#define _xdg_glob_hash_lookup_file_name XDG_ENTRY(hash_lookup_file_name) +#define _xdg_glob_hash_append_glob XDG_ENTRY(hash_append_glob) +#define _xdg_glob_determine_type XDG_ENTRY(determine_type) +#define _xdg_glob_hash_dump XDG_ENTRY(hash_dump) +#endif + +void _xdg_mime_glob_read_from_file (XdgGlobHash *glob_hash, + const char *file_name); +XdgGlobHash *_xdg_glob_hash_new (void); +void _xdg_glob_hash_free (XdgGlobHash *glob_hash); +int _xdg_glob_hash_lookup_file_name (XdgGlobHash *glob_hash, + const char *text, + const char *mime_types[], + int n_mime_types); +void _xdg_glob_hash_append_glob (XdgGlobHash *glob_hash, + const char *glob, + const char *mime_type); +XdgGlobType _xdg_glob_determine_type (const char *glob); +void _xdg_glob_hash_dump (XdgGlobHash *glob_hash); + +#endif /* __XDG_MIME_GLOB_H__ */ diff --git a/gio/xdgmime/xdgmimeint.c b/gio/xdgmime/xdgmimeint.c new file mode 100644 index 000000000..4a0ac4cc3 --- /dev/null +++ b/gio/xdgmime/xdgmimeint.c @@ -0,0 +1,154 @@ +/* -*- mode: C; c-file-style: "gnu" -*- */ +/* xdgmimeint.c: Internal defines and functions. + * + * More info can be found at http://www.freedesktop.org/standards/ + * + * Copyright (C) 2003 Red Hat, Inc. + * Copyright (C) 2003 Jonathan Blandford <jrb@alum.mit.edu> + * + * Licensed under the Academic Free License version 2.0 + * Or under the following terms: + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "xdgmimeint.h" +#include <ctype.h> +#include <string.h> + +#ifndef FALSE +#define FALSE (0) +#endif + +#ifndef TRUE +#define TRUE (!FALSE) +#endif + +static const char _xdg_utf8_skip_data[256] = { + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, + 3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,5,5,5,5,6,6,1,1 +}; + +const char * const _xdg_utf8_skip = _xdg_utf8_skip_data; + + + +/* Returns the number of unprocessed characters. */ +xdg_unichar_t +_xdg_utf8_to_ucs4(const char *source) +{ + xdg_unichar_t ucs32; + if( ! ( *source & 0x80 ) ) + { + ucs32 = *source; + } + else + { + int bytelength = 0; + xdg_unichar_t result; + if ( ! (*source & 0x40) ) + { + ucs32 = *source; + } + else + { + if ( ! (*source & 0x20) ) + { + result = *source++ & 0x1F; + bytelength = 2; + } + else if ( ! (*source & 0x10) ) + { + result = *source++ & 0x0F; + bytelength = 3; + } + else if ( ! (*source & 0x08) ) + { + result = *source++ & 0x07; + bytelength = 4; + } + else if ( ! (*source & 0x04) ) + { + result = *source++ & 0x03; + bytelength = 5; + } + else if ( ! (*source & 0x02) ) + { + result = *source++ & 0x01; + bytelength = 6; + } + else + { + result = *source++; + bytelength = 1; + } + + for ( bytelength --; bytelength > 0; bytelength -- ) + { + result <<= 6; + result |= *source++ & 0x3F; + } + ucs32 = result; + } + } + return ucs32; +} + + +/* hullo. this is great code. don't rewrite it */ + +xdg_unichar_t +_xdg_ucs4_to_lower (xdg_unichar_t source) +{ + /* FIXME: Do a real to_upper sometime */ + /* CaseFolding-3.2.0.txt has a table of rules. */ + if ((source & 0xFF) == source) + return (xdg_unichar_t) tolower ((unsigned char) source); + return source; +} + +int +_xdg_utf8_validate (const char *source) +{ + /* FIXME: actually write */ + return TRUE; +} + +const char * +_xdg_get_base_name (const char *file_name) +{ + const char *base_name; + + if (file_name == NULL) + return NULL; + + base_name = strrchr (file_name, '/'); + + if (base_name == NULL) + return file_name; + else + return base_name + 1; +} diff --git a/gio/xdgmime/xdgmimeint.h b/gio/xdgmime/xdgmimeint.h new file mode 100644 index 000000000..288148719 --- /dev/null +++ b/gio/xdgmime/xdgmimeint.h @@ -0,0 +1,73 @@ +/* -*- mode: C; c-file-style: "gnu" -*- */ +/* xdgmimeint.h: Internal defines and functions. + * + * More info can be found at http://www.freedesktop.org/standards/ + * + * Copyright (C) 2003 Red Hat, Inc. + * Copyright (C) 2003 Jonathan Blandford <jrb@alum.mit.edu> + * + * Licensed under the Academic Free License version 2.0 + * Or under the following terms: + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __XDG_MIME_INT_H__ +#define __XDG_MIME_INT_H__ + +#include "xdgmime.h" + + +#ifndef FALSE +#define FALSE (0) +#endif + +#ifndef TRUE +#define TRUE (!FALSE) +#endif + +/* FIXME: Needs to be configure check */ +typedef unsigned int xdg_unichar_t; +typedef unsigned char xdg_uchar8_t; +typedef unsigned short xdg_uint16_t; +typedef unsigned int xdg_uint32_t; + +#ifdef XDG_PREFIX +#define _xdg_utf8_skip XDG_ENTRY(utf8_skip) +#define _xdg_utf8_to_ucs4 XDG_ENTRY(utf8_to_ucs4) +#define _xdg_ucs4_to_lower XDG_ENTRY(ucs4_to_lower) +#define _xdg_utf8_validate XDG_ENTRY(utf8_validate) +#define _xdg_get_base_name XDG_ENTRY(get_ase_name) +#endif + +#define SWAP_BE16_TO_LE16(val) (xdg_uint16_t)(((xdg_uint16_t)(val) << 8)|((xdg_uint16_t)(val) >> 8)) + +#define SWAP_BE32_TO_LE32(val) (xdg_uint32_t)((((xdg_uint32_t)(val) & 0xFF000000U) >> 24) | \ + (((xdg_uint32_t)(val) & 0x00FF0000U) >> 8) | \ + (((xdg_uint32_t)(val) & 0x0000FF00U) << 8) | \ + (((xdg_uint32_t)(val) & 0x000000FFU) << 24)) +/* UTF-8 utils + */ +extern const char *const _xdg_utf8_skip; +#define _xdg_utf8_next_char(p) (char *)((p) + _xdg_utf8_skip[*(unsigned char *)(p)]) +#define _xdg_utf8_char_size(p) (int) (_xdg_utf8_skip[*(unsigned char *)(p)]) + +xdg_unichar_t _xdg_utf8_to_ucs4 (const char *source); +xdg_unichar_t _xdg_ucs4_to_lower (xdg_unichar_t source); +int _xdg_utf8_validate (const char *source); +const char *_xdg_get_base_name (const char *file_name); + +#endif /* __XDG_MIME_INT_H__ */ diff --git a/gio/xdgmime/xdgmimemagic.c b/gio/xdgmime/xdgmimemagic.c new file mode 100644 index 000000000..a2320f584 --- /dev/null +++ b/gio/xdgmime/xdgmimemagic.c @@ -0,0 +1,813 @@ +/* -*- mode: C; c-file-style: "gnu" -*- */ +/* xdgmimemagic.: Private file. Datastructure for storing magic files. + * + * More info can be found at http://www.freedesktop.org/standards/ + * + * Copyright (C) 2003 Red Hat, Inc. + * Copyright (C) 2003 Jonathan Blandford <jrb@alum.mit.edu> + * + * Licensed under the Academic Free License version 2.0 + * Or under the following terms: + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <assert.h> +#include "xdgmimemagic.h" +#include "xdgmimeint.h" +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include <errno.h> +#include <limits.h> + +#ifndef FALSE +#define FALSE (0) +#endif + +#ifndef TRUE +#define TRUE (!FALSE) +#endif + +#if !defined getc_unlocked && !defined HAVE_GETC_UNLOCKED +# define getc_unlocked(fp) getc (fp) +#endif + +typedef struct XdgMimeMagicMatch XdgMimeMagicMatch; +typedef struct XdgMimeMagicMatchlet XdgMimeMagicMatchlet; + +typedef enum +{ + XDG_MIME_MAGIC_SECTION, + XDG_MIME_MAGIC_MAGIC, + XDG_MIME_MAGIC_ERROR, + XDG_MIME_MAGIC_EOF +} XdgMimeMagicState; + +struct XdgMimeMagicMatch +{ + const char *mime_type; + int priority; + XdgMimeMagicMatchlet *matchlet; + XdgMimeMagicMatch *next; +}; + + +struct XdgMimeMagicMatchlet +{ + int indent; + int offset; + unsigned int value_length; + unsigned char *value; + unsigned char *mask; + unsigned int range_length; + unsigned int word_size; + XdgMimeMagicMatchlet *next; +}; + + +struct XdgMimeMagic +{ + XdgMimeMagicMatch *match_list; + int max_extent; +}; + +static XdgMimeMagicMatch * +_xdg_mime_magic_match_new (void) +{ + return calloc (1, sizeof (XdgMimeMagicMatch)); +} + + +static XdgMimeMagicMatchlet * +_xdg_mime_magic_matchlet_new (void) +{ + XdgMimeMagicMatchlet *matchlet; + + matchlet = malloc (sizeof (XdgMimeMagicMatchlet)); + + matchlet->indent = 0; + matchlet->offset = 0; + matchlet->value_length = 0; + matchlet->value = NULL; + matchlet->mask = NULL; + matchlet->range_length = 1; + matchlet->word_size = 1; + matchlet->next = NULL; + + return matchlet; +} + + +static void +_xdg_mime_magic_matchlet_free (XdgMimeMagicMatchlet *mime_magic_matchlet) +{ + if (mime_magic_matchlet) + { + if (mime_magic_matchlet->next) + _xdg_mime_magic_matchlet_free (mime_magic_matchlet->next); + if (mime_magic_matchlet->value) + free (mime_magic_matchlet->value); + if (mime_magic_matchlet->mask) + free (mime_magic_matchlet->mask); + free (mime_magic_matchlet); + } +} + + +/* Frees mime_magic_match and the remainder of its list + */ +static void +_xdg_mime_magic_match_free (XdgMimeMagicMatch *mime_magic_match) +{ + XdgMimeMagicMatch *ptr, *next; + + ptr = mime_magic_match; + while (ptr) + { + next = ptr->next; + + if (ptr->mime_type) + free ((void *) ptr->mime_type); + if (ptr->matchlet) + _xdg_mime_magic_matchlet_free (ptr->matchlet); + free (ptr); + + ptr = next; + } +} + +/* Reads in a hunk of data until a newline character or a '\000' is hit. The + * returned string is null terminated, and doesn't include the newline. + */ +static unsigned char * +_xdg_mime_magic_read_to_newline (FILE *magic_file, + int *end_of_file) +{ + unsigned char *retval; + int c; + int len, pos; + + len = 128; + pos = 0; + retval = malloc (len); + *end_of_file = FALSE; + + while (TRUE) + { + c = getc_unlocked (magic_file); + if (c == EOF) + { + *end_of_file = TRUE; + break; + } + if (c == '\n' || c == '\000') + break; + retval[pos++] = (unsigned char) c; + if (pos % 128 == 127) + { + len = len + 128; + retval = realloc (retval, len); + } + } + + retval[pos] = '\000'; + return retval; +} + +/* Returns the number read from the file, or -1 if no number could be read. + */ +static int +_xdg_mime_magic_read_a_number (FILE *magic_file, + int *end_of_file) +{ + /* LONG_MAX is about 20 characters on my system */ +#define MAX_NUMBER_SIZE 30 + char number_string[MAX_NUMBER_SIZE + 1]; + int pos = 0; + int c; + long retval = -1; + + while (TRUE) + { + c = getc_unlocked (magic_file); + + if (c == EOF) + { + *end_of_file = TRUE; + break; + } + if (! isdigit (c)) + { + ungetc (c, magic_file); + break; + } + number_string[pos] = (char) c; + pos++; + if (pos == MAX_NUMBER_SIZE) + break; + } + if (pos > 0) + { + number_string[pos] = '\000'; + errno = 0; + retval = strtol (number_string, NULL, 10); + + if ((retval < INT_MIN) || (retval > INT_MAX) || (errno != 0)) + return -1; + } + + return retval; +} + +/* Headers are of the format: + * [<priority>:<mime-type>] + */ +static XdgMimeMagicState +_xdg_mime_magic_parse_header (FILE *magic_file, XdgMimeMagicMatch *match) +{ + int c; + char *buffer; + char *end_ptr; + int end_of_file = 0; + + assert (magic_file != NULL); + assert (match != NULL); + + c = getc_unlocked (magic_file); + if (c == EOF) + return XDG_MIME_MAGIC_EOF; + if (c != '[') + return XDG_MIME_MAGIC_ERROR; + + match->priority = _xdg_mime_magic_read_a_number (magic_file, &end_of_file); + if (end_of_file) + return XDG_MIME_MAGIC_EOF; + if (match->priority == -1) + return XDG_MIME_MAGIC_ERROR; + + c = getc_unlocked (magic_file); + if (c == EOF) + return XDG_MIME_MAGIC_EOF; + if (c != ':') + return XDG_MIME_MAGIC_ERROR; + + buffer = (char *)_xdg_mime_magic_read_to_newline (magic_file, &end_of_file); + if (end_of_file) + return XDG_MIME_MAGIC_EOF; + + end_ptr = buffer; + while (*end_ptr != ']' && *end_ptr != '\000' && *end_ptr != '\n') + end_ptr++; + if (*end_ptr != ']') + { + free (buffer); + return XDG_MIME_MAGIC_ERROR; + } + *end_ptr = '\000'; + + match->mime_type = strdup (buffer); + free (buffer); + + return XDG_MIME_MAGIC_MAGIC; +} + +static XdgMimeMagicState +_xdg_mime_magic_parse_error (FILE *magic_file) +{ + int c; + + while (1) + { + c = getc_unlocked (magic_file); + if (c == EOF) + return XDG_MIME_MAGIC_EOF; + if (c == '\n') + return XDG_MIME_MAGIC_SECTION; + } +} + +/* Headers are of the format: + * [ indent ] ">" start-offset "=" value + * [ "&" mask ] [ "~" word-size ] [ "+" range-length ] "\n" + */ +static XdgMimeMagicState +_xdg_mime_magic_parse_magic_line (FILE *magic_file, + XdgMimeMagicMatch *match) +{ + XdgMimeMagicMatchlet *matchlet; + int c; + int end_of_file; + int indent = 0; + int bytes_read; + + assert (magic_file != NULL); + + /* Sniff the buffer to make sure it's a valid line */ + c = getc_unlocked (magic_file); + if (c == EOF) + return XDG_MIME_MAGIC_EOF; + else if (c == '[') + { + ungetc (c, magic_file); + return XDG_MIME_MAGIC_SECTION; + } + else if (c == '\n') + return XDG_MIME_MAGIC_MAGIC; + + /* At this point, it must be a digit or a '>' */ + end_of_file = FALSE; + if (isdigit (c)) + { + ungetc (c, magic_file); + indent = _xdg_mime_magic_read_a_number (magic_file, &end_of_file); + if (end_of_file) + return XDG_MIME_MAGIC_EOF; + if (indent == -1) + return XDG_MIME_MAGIC_ERROR; + c = getc_unlocked (magic_file); + if (c == EOF) + return XDG_MIME_MAGIC_EOF; + } + + if (c != '>') + return XDG_MIME_MAGIC_ERROR; + + matchlet = _xdg_mime_magic_matchlet_new (); + matchlet->indent = indent; + matchlet->offset = _xdg_mime_magic_read_a_number (magic_file, &end_of_file); + if (end_of_file) + { + _xdg_mime_magic_matchlet_free (matchlet); + return XDG_MIME_MAGIC_EOF; + } + if (matchlet->offset == -1) + { + _xdg_mime_magic_matchlet_free (matchlet); + return XDG_MIME_MAGIC_ERROR; + } + c = getc_unlocked (magic_file); + if (c == EOF) + { + _xdg_mime_magic_matchlet_free (matchlet); + return XDG_MIME_MAGIC_EOF; + } + else if (c != '=') + { + _xdg_mime_magic_matchlet_free (matchlet); + return XDG_MIME_MAGIC_ERROR; + } + + /* Next two bytes determine how long the value is */ + matchlet->value_length = 0; + c = getc_unlocked (magic_file); + if (c == EOF) + { + _xdg_mime_magic_matchlet_free (matchlet); + return XDG_MIME_MAGIC_EOF; + } + matchlet->value_length = c & 0xFF; + matchlet->value_length = matchlet->value_length << 8; + + c = getc_unlocked (magic_file); + if (c == EOF) + { + _xdg_mime_magic_matchlet_free (matchlet); + return XDG_MIME_MAGIC_EOF; + } + matchlet->value_length = matchlet->value_length + (c & 0xFF); + + matchlet->value = malloc (matchlet->value_length); + + /* OOM */ + if (matchlet->value == NULL) + { + _xdg_mime_magic_matchlet_free (matchlet); + return XDG_MIME_MAGIC_ERROR; + } + bytes_read = fread (matchlet->value, 1, matchlet->value_length, magic_file); + if (bytes_read != matchlet->value_length) + { + _xdg_mime_magic_matchlet_free (matchlet); + if (feof (magic_file)) + return XDG_MIME_MAGIC_EOF; + else + return XDG_MIME_MAGIC_ERROR; + } + + c = getc_unlocked (magic_file); + if (c == '&') + { + matchlet->mask = malloc (matchlet->value_length); + /* OOM */ + if (matchlet->mask == NULL) + { + _xdg_mime_magic_matchlet_free (matchlet); + return XDG_MIME_MAGIC_ERROR; + } + bytes_read = fread (matchlet->mask, 1, matchlet->value_length, magic_file); + if (bytes_read != matchlet->value_length) + { + _xdg_mime_magic_matchlet_free (matchlet); + if (feof (magic_file)) + return XDG_MIME_MAGIC_EOF; + else + return XDG_MIME_MAGIC_ERROR; + } + c = getc_unlocked (magic_file); + } + + if (c == '~') + { + matchlet->word_size = _xdg_mime_magic_read_a_number (magic_file, &end_of_file); + if (end_of_file) + { + _xdg_mime_magic_matchlet_free (matchlet); + return XDG_MIME_MAGIC_EOF; + } + if (matchlet->word_size != 0 && + matchlet->word_size != 1 && + matchlet->word_size != 2 && + matchlet->word_size != 4) + { + _xdg_mime_magic_matchlet_free (matchlet); + return XDG_MIME_MAGIC_ERROR; + } + c = getc_unlocked (magic_file); + } + + if (c == '+') + { + matchlet->range_length = _xdg_mime_magic_read_a_number (magic_file, &end_of_file); + if (end_of_file) + { + _xdg_mime_magic_matchlet_free (matchlet); + return XDG_MIME_MAGIC_EOF; + } + if (matchlet->range_length == -1) + { + _xdg_mime_magic_matchlet_free (matchlet); + return XDG_MIME_MAGIC_ERROR; + } + c = getc_unlocked (magic_file); + } + + + if (c == '\n') + { + /* We clean up the matchlet, byte swapping if needed */ + if (matchlet->word_size > 1) + { + int i; + if (matchlet->value_length % matchlet->word_size != 0) + { + _xdg_mime_magic_matchlet_free (matchlet); + return XDG_MIME_MAGIC_ERROR; + } + /* FIXME: need to get this defined in a <config.h> style file */ +#if LITTLE_ENDIAN + for (i = 0; i < matchlet->value_length; i = i + matchlet->word_size) + { + if (matchlet->word_size == 2) + *((xdg_uint16_t *) matchlet->value + i) = SWAP_BE16_TO_LE16 (*((xdg_uint16_t *) (matchlet->value + i))); + else if (matchlet->word_size == 4) + *((xdg_uint32_t *) matchlet->value + i) = SWAP_BE32_TO_LE32 (*((xdg_uint32_t *) (matchlet->value + i))); + if (matchlet->mask) + { + if (matchlet->word_size == 2) + *((xdg_uint16_t *) matchlet->mask + i) = SWAP_BE16_TO_LE16 (*((xdg_uint16_t *) (matchlet->mask + i))); + else if (matchlet->word_size == 4) + *((xdg_uint32_t *) matchlet->mask + i) = SWAP_BE32_TO_LE32 (*((xdg_uint32_t *) (matchlet->mask + i))); + + } + } +#endif + } + + matchlet->next = match->matchlet; + match->matchlet = matchlet; + + + return XDG_MIME_MAGIC_MAGIC; + } + + _xdg_mime_magic_matchlet_free (matchlet); + if (c == EOF) + return XDG_MIME_MAGIC_EOF; + + return XDG_MIME_MAGIC_ERROR; +} + +static int +_xdg_mime_magic_matchlet_compare_to_data (XdgMimeMagicMatchlet *matchlet, + const void *data, + size_t len) +{ + int i, j; + for (i = matchlet->offset; i < matchlet->offset + matchlet->range_length; i++) + { + int valid_matchlet = TRUE; + + if (i + matchlet->value_length > len) + return FALSE; + + if (matchlet->mask) + { + for (j = 0; j < matchlet->value_length; j++) + { + if ((matchlet->value[j] & matchlet->mask[j]) != + ((((unsigned char *) data)[j + i]) & matchlet->mask[j])) + { + valid_matchlet = FALSE; + break; + } + } + } + else + { + for (j = 0; j < matchlet->value_length; j++) + { + if (matchlet->value[j] != ((unsigned char *) data)[j + i]) + { + valid_matchlet = FALSE; + break; + } + } + } + if (valid_matchlet) + return TRUE; + } + return FALSE; +} + +static int +_xdg_mime_magic_matchlet_compare_level (XdgMimeMagicMatchlet *matchlet, + const void *data, + size_t len, + int indent) +{ + while ((matchlet != NULL) && (matchlet->indent == indent)) + { + if (_xdg_mime_magic_matchlet_compare_to_data (matchlet, data, len)) + { + if ((matchlet->next == NULL) || + (matchlet->next->indent <= indent)) + return TRUE; + + if (_xdg_mime_magic_matchlet_compare_level (matchlet->next, + data, + len, + indent + 1)) + return TRUE; + } + + do + { + matchlet = matchlet->next; + } + while (matchlet && matchlet->indent > indent); + } + + return FALSE; +} + +static int +_xdg_mime_magic_match_compare_to_data (XdgMimeMagicMatch *match, + const void *data, + size_t len) +{ + return _xdg_mime_magic_matchlet_compare_level (match->matchlet, data, len, 0); +} + +static void +_xdg_mime_magic_insert_match (XdgMimeMagic *mime_magic, + XdgMimeMagicMatch *match) +{ + XdgMimeMagicMatch *list; + + if (mime_magic->match_list == NULL) + { + mime_magic->match_list = match; + return; + } + + if (match->priority > mime_magic->match_list->priority) + { + match->next = mime_magic->match_list; + mime_magic->match_list = match; + return; + } + + list = mime_magic->match_list; + while (list->next != NULL) + { + if (list->next->priority < match->priority) + { + match->next = list->next; + list->next = match; + return; + } + list = list->next; + } + list->next = match; + match->next = NULL; +} + +XdgMimeMagic * +_xdg_mime_magic_new (void) +{ + return calloc (1, sizeof (XdgMimeMagic)); +} + +void +_xdg_mime_magic_free (XdgMimeMagic *mime_magic) +{ + if (mime_magic) { + _xdg_mime_magic_match_free (mime_magic->match_list); + free (mime_magic); + } +} + +int +_xdg_mime_magic_get_buffer_extents (XdgMimeMagic *mime_magic) +{ + return mime_magic->max_extent; +} + +const char * +_xdg_mime_magic_lookup_data (XdgMimeMagic *mime_magic, + const void *data, + size_t len, + int *result_prio, + const char *mime_types[], + int n_mime_types) +{ + XdgMimeMagicMatch *match; + const char *mime_type; + int n; + int prio; + + prio = 0; + mime_type = NULL; + for (match = mime_magic->match_list; match; match = match->next) + { + if (_xdg_mime_magic_match_compare_to_data (match, data, len)) + { + prio = match->priority; + mime_type = match->mime_type; + break; + } + else + { + for (n = 0; n < n_mime_types; n++) + { + if (mime_types[n] && + _xdg_mime_mime_type_equal (mime_types[n], match->mime_type)) + mime_types[n] = NULL; + } + } + } + + if (mime_type == NULL) + { + for (n = 0; n < n_mime_types; n++) + { + if (mime_types[n]) + mime_type = mime_types[n]; + } + } + + if (result_prio) + *result_prio = prio; + + return mime_type; +} + +static void +_xdg_mime_update_mime_magic_extents (XdgMimeMagic *mime_magic) +{ + XdgMimeMagicMatch *match; + int max_extent = 0; + + for (match = mime_magic->match_list; match; match = match->next) + { + XdgMimeMagicMatchlet *matchlet; + + for (matchlet = match->matchlet; matchlet; matchlet = matchlet->next) + { + int extent; + + extent = matchlet->value_length + matchlet->offset + matchlet->range_length; + if (max_extent < extent) + max_extent = extent; + } + } + + mime_magic->max_extent = max_extent; +} + +static XdgMimeMagicMatchlet * +_xdg_mime_magic_matchlet_mirror (XdgMimeMagicMatchlet *matchlets) +{ + XdgMimeMagicMatchlet *new_list; + XdgMimeMagicMatchlet *tmp; + + if ((matchlets == NULL) || (matchlets->next == NULL)) + return matchlets; + + new_list = NULL; + tmp = matchlets; + while (tmp != NULL) + { + XdgMimeMagicMatchlet *matchlet; + + matchlet = tmp; + tmp = tmp->next; + matchlet->next = new_list; + new_list = matchlet; + } + + return new_list; + +} + +static void +_xdg_mime_magic_read_magic_file (XdgMimeMagic *mime_magic, + FILE *magic_file) +{ + XdgMimeMagicState state; + XdgMimeMagicMatch *match = NULL; /* Quiet compiler */ + + state = XDG_MIME_MAGIC_SECTION; + + while (state != XDG_MIME_MAGIC_EOF) + { + switch (state) + { + case XDG_MIME_MAGIC_SECTION: + match = _xdg_mime_magic_match_new (); + state = _xdg_mime_magic_parse_header (magic_file, match); + if (state == XDG_MIME_MAGIC_EOF || state == XDG_MIME_MAGIC_ERROR) + _xdg_mime_magic_match_free (match); + break; + case XDG_MIME_MAGIC_MAGIC: + state = _xdg_mime_magic_parse_magic_line (magic_file, match); + if (state == XDG_MIME_MAGIC_SECTION || + (state == XDG_MIME_MAGIC_EOF && match->mime_type)) + { + match->matchlet = _xdg_mime_magic_matchlet_mirror (match->matchlet); + _xdg_mime_magic_insert_match (mime_magic, match); + } + else if (state == XDG_MIME_MAGIC_EOF || state == XDG_MIME_MAGIC_ERROR) + _xdg_mime_magic_match_free (match); + break; + case XDG_MIME_MAGIC_ERROR: + state = _xdg_mime_magic_parse_error (magic_file); + break; + case XDG_MIME_MAGIC_EOF: + default: + /* Make the compiler happy */ + assert (0); + } + } + _xdg_mime_update_mime_magic_extents (mime_magic); +} + +void +_xdg_mime_magic_read_from_file (XdgMimeMagic *mime_magic, + const char *file_name) +{ + FILE *magic_file; + char header[12]; + + magic_file = fopen (file_name, "r"); + + if (magic_file == NULL) + return; + + if (fread (header, 1, 12, magic_file) == 12) + { + if (memcmp ("MIME-Magic\0\n", header, 12) == 0) + _xdg_mime_magic_read_magic_file (mime_magic, magic_file); + } + + fclose (magic_file); +} diff --git a/gio/xdgmime/xdgmimemagic.h b/gio/xdgmime/xdgmimemagic.h new file mode 100644 index 000000000..8f113051e --- /dev/null +++ b/gio/xdgmime/xdgmimemagic.h @@ -0,0 +1,57 @@ +/* -*- mode: C; c-file-style: "gnu" -*- */ +/* xdgmimemagic.h: Private file. Datastructure for storing the magic files. + * + * More info can be found at http://www.freedesktop.org/standards/ + * + * Copyright (C) 2003 Red Hat, Inc. + * Copyright (C) 2003 Jonathan Blandford <jrb@alum.mit.edu> + * + * Licensed under the Academic Free License version 2.0 + * Or under the following terms: + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __XDG_MIME_MAGIC_H__ +#define __XDG_MIME_MAGIC_H__ + +#include <unistd.h> +#include "xdgmime.h" +typedef struct XdgMimeMagic XdgMimeMagic; + +#ifdef XDG_PREFIX +#define _xdg_mime_glob_read_from_file XDG_ENTRY(glob_read_from_file) +#define _xdg_mime_magic_new XDG_ENTRY(magic_new) +#define _xdg_mime_magic_read_from_file XDG_ENTRY(magic_read_from_file) +#define _xdg_mime_magic_free XDG_ENTRY(magic_free) +#define _xdg_mime_magic_get_buffer_extents XDG_ENTRY(magic_get_buffer_extents) +#define _xdg_mime_magic_lookup_data XDG_ENTRY(magic_lookup_data) +#endif + + +XdgMimeMagic *_xdg_mime_magic_new (void); +void _xdg_mime_magic_read_from_file (XdgMimeMagic *mime_magic, + const char *file_name); +void _xdg_mime_magic_free (XdgMimeMagic *mime_magic); +int _xdg_mime_magic_get_buffer_extents (XdgMimeMagic *mime_magic); +const char *_xdg_mime_magic_lookup_data (XdgMimeMagic *mime_magic, + const void *data, + size_t len, + int *result_prio, + const char *mime_types[], + int n_mime_types); + +#endif /* __XDG_MIME_MAGIC_H__ */ diff --git a/gio/xdgmime/xdgmimeparent.c b/gio/xdgmime/xdgmimeparent.c new file mode 100644 index 000000000..511bbacbc --- /dev/null +++ b/gio/xdgmime/xdgmimeparent.c @@ -0,0 +1,219 @@ +/* -*- mode: C; c-file-style: "gnu" -*- */ +/* xdgmimealias.c: Private file. Datastructure for storing the hierarchy. + * + * More info can be found at http://www.freedesktop.org/standards/ + * + * Copyright (C) 2004 Red Hat, Inc. + * Copyright (C) 2004 Matthias Clasen <mclasen@redhat.com> + * + * Licensed under the Academic Free License version 2.0 + * Or under the following terms: + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "xdgmimeparent.h" +#include "xdgmimeint.h" +#include <stdlib.h> +#include <stdio.h> +#include <assert.h> +#include <string.h> +#include <fnmatch.h> + +#ifndef FALSE +#define FALSE (0) +#endif + +#ifndef TRUE +#define TRUE (!FALSE) +#endif + +typedef struct XdgMimeParents XdgMimeParents; + +struct XdgMimeParents +{ + char *mime; + char **parents; + int n_parents; +}; + +struct XdgParentList +{ + struct XdgMimeParents *parents; + int n_mimes; +}; + +XdgParentList * +_xdg_mime_parent_list_new (void) +{ + XdgParentList *list; + + list = malloc (sizeof (XdgParentList)); + + list->parents = NULL; + list->n_mimes = 0; + + return list; +} + +void +_xdg_mime_parent_list_free (XdgParentList *list) +{ + int i; + char **p; + + if (list->parents) + { + for (i = 0; i < list->n_mimes; i++) + { + for (p = list->parents[i].parents; *p; p++) + free (*p); + + free (list->parents[i].parents); + free (list->parents[i].mime); + } + free (list->parents); + } + free (list); +} + +static int +parent_entry_cmp (const void *v1, const void *v2) +{ + return strcmp (((XdgMimeParents *)v1)->mime, ((XdgMimeParents *)v2)->mime); +} + +const char ** +_xdg_mime_parent_list_lookup (XdgParentList *list, + const char *mime) +{ + XdgMimeParents *entry; + XdgMimeParents key; + + if (list->n_mimes > 0) + { + key.mime = (char *)mime; + key.parents = NULL; + + entry = bsearch (&key, list->parents, list->n_mimes, + sizeof (XdgMimeParents), &parent_entry_cmp); + if (entry) + return (const char **)entry->parents; + } + + return NULL; +} + +void +_xdg_mime_parent_read_from_file (XdgParentList *list, + const char *file_name) +{ + FILE *file; + char line[255]; + int i, alloc; + XdgMimeParents *entry; + + file = fopen (file_name, "r"); + + if (file == NULL) + return; + + /* FIXME: Not UTF-8 safe. Doesn't work if lines are greater than 255 chars. + * Blah */ + alloc = list->n_mimes + 16; + list->parents = realloc (list->parents, alloc * sizeof (XdgMimeParents)); + while (fgets (line, 255, file) != NULL) + { + char *sep; + if (line[0] == '#') + continue; + + sep = strchr (line, ' '); + if (sep == NULL) + continue; + *(sep++) = '\000'; + sep[strlen (sep) -1] = '\000'; + entry = NULL; + for (i = 0; i < list->n_mimes; i++) + { + if (strcmp (list->parents[i].mime, line) == 0) + { + entry = &(list->parents[i]); + break; + } + } + + if (!entry) + { + if (list->n_mimes == alloc) + { + alloc <<= 1; + list->parents = realloc (list->parents, + alloc * sizeof (XdgMimeParents)); + } + list->parents[list->n_mimes].mime = strdup (line); + list->parents[list->n_mimes].parents = NULL; + entry = &(list->parents[list->n_mimes]); + list->n_mimes++; + } + + if (!entry->parents) + { + entry->n_parents = 1; + entry->parents = malloc ((entry->n_parents + 1) * sizeof (char *)); + } + else + { + entry->n_parents += 1; + entry->parents = realloc (entry->parents, + (entry->n_parents + 2) * sizeof (char *)); + } + entry->parents[entry->n_parents - 1] = strdup (sep); + entry->parents[entry->n_parents] = NULL; + } + + list->parents = realloc (list->parents, + list->n_mimes * sizeof (XdgMimeParents)); + + fclose (file); + + if (list->n_mimes > 1) + qsort (list->parents, list->n_mimes, + sizeof (XdgMimeParents), &parent_entry_cmp); +} + + +void +_xdg_mime_parent_list_dump (XdgParentList *list) +{ + int i; + char **p; + + if (list->parents) + { + for (i = 0; i < list->n_mimes; i++) + { + for (p = list->parents[i].parents; *p; p++) + printf ("%s %s\n", list->parents[i].mime, *p); + } + } +} + + diff --git a/gio/xdgmime/xdgmimeparent.h b/gio/xdgmime/xdgmimeparent.h new file mode 100644 index 000000000..257ea0497 --- /dev/null +++ b/gio/xdgmime/xdgmimeparent.h @@ -0,0 +1,51 @@ +/* -*- mode: C; c-file-style: "gnu" -*- */ +/* xdgmimeparent.h: Private file. Datastructure for storing the hierarchy. + * + * More info can be found at http://www.freedesktop.org/standards/ + * + * Copyright (C) 2004 Red Hat, Inc. + * Copyright (C) 200 Matthias Clasen <mclasen@redhat.com> + * + * Licensed under the Academic Free License version 2.0 + * Or under the following terms: + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __XDG_MIME_PARENT_H__ +#define __XDG_MIME_PARENT_H__ + +#include "xdgmime.h" + +typedef struct XdgParentList XdgParentList; + +#ifdef XDG_PREFIX +#define _xdg_mime_parent_read_from_file XDG_ENTRY(parent_read_from_file) +#define _xdg_mime_parent_list_new XDG_ENTRY(parent_list_new) +#define _xdg_mime_parent_list_free XDG_ENTRY(parent_list_free) +#define _xdg_mime_parent_list_lookup XDG_ENTRY(parent_list_lookup) +#define _xdg_mime_parent_list_dump XDG_ENTRY(parent_list_dump) +#endif + +void _xdg_mime_parent_read_from_file (XdgParentList *list, + const char *file_name); +XdgParentList *_xdg_mime_parent_list_new (void); +void _xdg_mime_parent_list_free (XdgParentList *list); +const char **_xdg_mime_parent_list_lookup (XdgParentList *list, + const char *mime); +void _xdg_mime_parent_list_dump (XdgParentList *list); + +#endif /* __XDG_MIME_PARENT_H__ */ |