diff --git a/ChangeLog b/ChangeLog index 650abceb5..95344a13c 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,18 @@ +2008-05-11 Dan Williams + + * configure.in + Makefile.am + gfilemonitor/* + - Add a private copy of the GIO GFileMonitor code, with a custom GFile + implementation, so that the same change monitoring code can be used + on systems without glib-2.14 (like Fedora 8) + + * system-settings/plugins/keyfile/Makefile.am + system-settings/plugins/keyfile/plugin.c + system-settings/plugins/ifcfg-suse/Makefile.am + system-settings/plugins/ifcfg-suse/plugin.c + - Use private gfilemonitor code if GIO is not present + 2008-05-09 Tambet Ingo * system-settings/plugins/ifcfg-suse/nm-suse-connection.c: Implement diff --git a/Makefile.am b/Makefile.am index 89f24b7c8..ccc9c92b7 100644 --- a/Makefile.am +++ b/Makefile.am @@ -21,8 +21,13 @@ clean: endif -SUBDIRS = \ - marshallers \ +SUBDIRS = marshallers + +if NO_GIO +SUBDIRS += gfilemonitor +endif + +SUBDIRS += \ libnm-util \ libnm-glib \ src \ diff --git a/configure.in b/configure.in index f04228dee..537435e51 100644 --- a/configure.in +++ b/configure.in @@ -206,10 +206,16 @@ PKG_CHECK_MODULES(GOBJECT, gobject-2.0) AC_SUBST(GOBJECT_CFLAGS) AC_SUBST(GOBJECT_LIBS) -# This is optional, at least for now. -PKG_CHECK_MODULES(GIO, gio-2.0,,true) -AC_SUBST(GIO_CFLAGS) -AC_SUBST(GIO_LIBS) +PKG_CHECK_EXISTS(gio-2.0,[have_gio=yes],[have_gio=no]) +if test x"$have_gio" = "xno"; then + AC_DEFINE([NO_GIO],[1],[Define if you don't have GIO]) + echo "GIO not found; using private GFileMonitor library" +else + PKG_CHECK_MODULES(GIO, gio-2.0) + AC_SUBST(GIO_CFLAGS) + AC_SUBST(GIO_LIBS) +fi +AM_CONDITIONAL(NO_GIO, test x"$have_gio" == "xno") PKG_CHECK_MODULES(HAL, hal >= 0.5.0) AC_SUBST(HAL_CFLAGS) @@ -305,6 +311,7 @@ libnm-util/libnm-util.pc libnm-util/Makefile libnm-glib/libnm_glib.pc libnm-glib/Makefile +gfilemonitor/Makefile callouts/Makefile tools/Makefile system-settings/Makefile diff --git a/gfilemonitor/COPYING b/gfilemonitor/COPYING new file mode 100644 index 000000000..bf50f20de --- /dev/null +++ b/gfilemonitor/COPYING @@ -0,0 +1,482 @@ + GNU LIBRARY GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the library GPL. It is + numbered 2 because it goes with version 2 of the ordinary GPL.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Library General Public License, applies to some +specially designated Free Software Foundation software, and to any +other libraries whose authors decide to use it. You can use it for +your libraries, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if +you distribute copies of the library, or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link a program with the library, you must provide +complete object files to the recipients so that they can relink them +with the library, after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + Our method of protecting your rights has two steps: (1) copyright +the library, and (2) offer you this license which gives you legal +permission to copy, distribute and/or modify the library. + + Also, for each distributor's protection, we want to make certain +that everyone understands that there is no warranty for this free +library. If the library is modified by someone else and passed on, we +want its recipients to know that what they have is not the original +version, so that any problems introduced by others will not reflect on +the original authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that companies distributing free +software will individually obtain patent licenses, thus in effect +transforming the program into proprietary software. To prevent this, +we have made it clear that any patent must be licensed for everyone's +free use or not licensed at all. + + Most GNU software, including some libraries, is covered by the ordinary +GNU General Public License, which was designed for utility programs. This +license, the GNU Library General Public License, applies to certain +designated libraries. This license is quite different from the ordinary +one; be sure to read it in full, and don't assume that anything in it is +the same as in the ordinary license. + + The reason we have a separate public license for some libraries is that +they blur the distinction we usually make between modifying or adding to a +program and simply using it. Linking a program with a library, without +changing the library, is in some sense simply using the library, and is +analogous to running a utility program or application program. However, in +a textual and legal sense, the linked executable is a combined work, a +derivative of the original library, and the ordinary General Public License +treats it as such. + + Because of this blurred distinction, using the ordinary General +Public License for libraries did not effectively promote software +sharing, because most developers did not use the libraries. We +concluded that weaker conditions might promote sharing better. + + However, unrestricted linking of non-free programs would deprive the +users of those programs of all benefit from the free status of the +libraries themselves. This Library General Public License is intended to +permit developers of non-free programs to use free libraries, while +preserving your freedom as a user of such programs to change the free +libraries that are incorporated in them. (We have not seen how to achieve +this as regards changes in header files, but we have achieved it as regards +changes in the actual functions of the Library.) The hope is that this +will lead to faster development of free libraries. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, while the latter only +works together with the library. + + Note that it is possible for a library to be covered by the ordinary +General Public License rather than by this special one. + + GNU LIBRARY GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library which +contains a notice placed by the copyright holder or other authorized +party saying it may be distributed under the terms of this Library +General Public License (also called "this License"). Each licensee is +addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also compile or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + c) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + d) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the source code distributed need not include anything that is normally +distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Library General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This 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. + + 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library 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. + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! diff --git a/gfilemonitor/Makefile.am b/gfilemonitor/Makefile.am new file mode 100644 index 000000000..5030b2ae7 --- /dev/null +++ b/gfilemonitor/Makefile.am @@ -0,0 +1,29 @@ +INCLUDES = -I${top_srcdir}/include -I${top_builddir}/marshallers + +noinst_LTLIBRARIES = libgfilemonitor.la + +libgfilemonitor_la_SOURCES = \ + gfile.c \ + gfile.h \ + gfilemonitor.c \ + gfilemonitor.h \ + glocalfilemonitor.c \ + glocalfilemonitor.h \ + glocaldirectorymonitor.c \ + glocaldirectorymonitor.h \ + inotify-diag.c \ + inotify-diag.h \ + inotify-helper.c \ + inotify-helper.h \ + inotify-kernel.c \ + inotify-kernel.h \ + inotify-missing.c \ + inotify-missing.h \ + inotify-path.c \ + inotify-path.h \ + inotify-sub.c \ + inotify-sub.h + +libgfilemonitor_la_CPPFLAGS = $(GTHREAD_CFLAGS) + +libgfilemonitor_la_LIBADD = $(GTHREAD_LIBS) diff --git a/gfilemonitor/README b/gfilemonitor/README new file mode 100644 index 000000000..84662816a --- /dev/null +++ b/gfilemonitor/README @@ -0,0 +1,4 @@ +A local copy of the Linux glib-2.14 GFileMonitor functionality, with a +re-implementation of GFile that doesn't depend on GIO. Allows NM source code to +use the same GFileMonitor APIs regardless of glib version. + diff --git a/gfilemonitor/gfile.c b/gfilemonitor/gfile.c new file mode 100644 index 000000000..b69107a22 --- /dev/null +++ b/gfilemonitor/gfile.c @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2008 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: Dan Williams + */ + +#include +#include + +#include "gfile.h" +#include "glocalfilemonitor.h" +#include "glocaldirectorymonitor.h" + +typedef struct { + char *path; +} GFilePrivate; + +#define G_FILE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), G_TYPE_FILE, GFilePrivate)) + +G_DEFINE_TYPE (GFile, g_file, G_TYPE_OBJECT) + +char * +g_file_get_basename (GFile *file) +{ + g_return_val_if_fail (G_IS_FILE (file), NULL); + + return g_path_get_basename (G_FILE_GET_PRIVATE (file)->path); +} + +char * +g_file_get_path (GFile *file) +{ + g_return_val_if_fail (G_IS_FILE (file), NULL); + + return g_strdup (G_FILE_GET_PRIVATE (file)->path); +} + +const char * +g_file_get_const_path (GFile *file) +{ + g_return_val_if_fail (G_IS_FILE (file), NULL); + + return G_FILE_GET_PRIVATE (file)->path; +} + +GFileMonitor * +g_file_monitor_directory (GFile *file, + GFileMonitorFlags flags, + void *unused, + GError **error) +{ + g_return_val_if_fail (G_IS_FILE (file), NULL); + + return _g_local_directory_monitor_new (G_FILE_GET_PRIVATE (file)->path, flags, error); +} + +GFileMonitor * +g_file_monitor_file (GFile *file, + GFileMonitorFlags flags, + void *unused, + GError **error) +{ + g_return_val_if_fail (G_IS_FILE (file), NULL); + + return _g_local_file_monitor_new (G_FILE_GET_PRIVATE (file)->path, flags, error); +} + +GFile * +g_file_new_for_path (const char *path) +{ + GFile *file; + GFilePrivate *priv; + + file = (GFile *) g_object_new (G_TYPE_FILE, NULL); + if (!file) + return NULL; + + priv = G_FILE_GET_PRIVATE (file); + priv->path = g_strdup (path); + + return file; +} + +static void +g_file_init (GFile *file) +{ +} + +static void +finalize (GObject *object) +{ + GFile *file = G_FILE (object); + GFilePrivate *priv = G_FILE_GET_PRIVATE (file); + + g_free (priv->path); + priv->path = NULL; + + G_OBJECT_CLASS (g_file_parent_class)->finalize (object); +} + +static void +g_file_class_init (GFileClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + g_type_class_add_private (klass, sizeof (GFilePrivate)); + + /* virtual methods */ + object_class->finalize = finalize; +} + diff --git a/gfilemonitor/gfile.h b/gfilemonitor/gfile.h new file mode 100644 index 000000000..4386b7ec9 --- /dev/null +++ b/gfilemonitor/gfile.h @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2008 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: Dan Williams + */ + +#ifndef __G_FILE_H__ +#define __G_FILE_H__ + +#include + +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_FILE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), G_TYPE_FILE, GFileClass)) +#define G_IS_FILE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), G_TYPE_FILE)) +#define G_IS_FILE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), G_TYPE_FILE)) +#define G_FILE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), G_TYPE_FILE, GFileClass)) + +typedef enum { + G_FILE_MONITOR_NONE = 0, +} GFileMonitorFlags; + +typedef struct _GFileMonitor GFileMonitor; + +typedef struct { + GObject parent; +} GFile; + +typedef struct { + GObjectClass parent; +} GFileClass; + +GType g_file_get_type (void) G_GNUC_CONST; + +GFile * g_file_new_for_path (const char *path); + +char * g_file_get_basename (GFile *file); + +char * g_file_get_path (GFile *file); + +const char * g_file_get_const_path (GFile *file); + +GFileMonitor * g_file_monitor_directory (GFile *file, + GFileMonitorFlags flags, + void *unused, + GError **error); + +GFileMonitor * g_file_monitor_file (GFile *file, + GFileMonitorFlags flags, + void *unused, + GError **error); + +G_END_DECLS + +#endif /* __G_FILE_H__ */ diff --git a/gfilemonitor/gfilemonitor.c b/gfilemonitor/gfilemonitor.c new file mode 100644 index 000000000..c5b18741f --- /dev/null +++ b/gfilemonitor/gfilemonitor.c @@ -0,0 +1,696 @@ +/* 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 + */ + +#include +#include + +#include "gfilemonitor.h" +#include "nm-marshal.h" + +/** + * SECTION:gfilemonitor + * @short_description: File Monitor + * @include: gio/gio.h + * + * Monitors a file or directory for changes. + * + * To obtain a #GFileMonitor for a file or directory, use + * g_file_monitor_file() or g_file_monitor_directory(). + * + * To get informed about changes to the file or directory you + * are monitoring, connect to the #GFileMonitor::changed signal. + **/ + +G_LOCK_DEFINE_STATIC(cancelled); + +enum { + CHANGED, + LAST_SIGNAL +}; + +G_DEFINE_ABSTRACT_TYPE (GFileMonitor, g_file_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 _GFileMonitorPrivate { + gboolean cancelled; + int rate_limit_msec; + + /* Rate limiting change events */ + GHashTable *rate_limiter; + + GSource *timeout; + guint32 timeout_fires_at; +}; + +enum { + PROP_0, + PROP_RATE_LIMIT, + PROP_CANCELLED +}; + +static void +g_file_monitor_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GFileMonitor *monitor; + + monitor = G_FILE_MONITOR (object); + + switch (prop_id) + { + case PROP_RATE_LIMIT: + g_file_monitor_set_rate_limit (monitor, g_value_get_int (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +g_file_monitor_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GFileMonitor *monitor; + GFileMonitorPrivate *priv; + + monitor = G_FILE_MONITOR (object); + priv = monitor->priv; + + switch (prop_id) + { + case PROP_RATE_LIMIT: + g_value_set_int (value, priv->rate_limit_msec); + break; + + case PROP_CANCELLED: + G_LOCK (cancelled); + g_value_set_boolean (value, priv->cancelled); + G_UNLOCK (cancelled); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +#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_slice_free (RateLimiter, limiter); +} + +static void +g_file_monitor_finalize (GObject *object) +{ + GFileMonitor *monitor; + + monitor = G_FILE_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_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 */ + 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 *object_class; + + g_type_class_add_private (klass, sizeof (GFileMonitorPrivate)); + + object_class = G_OBJECT_CLASS (klass); + object_class->finalize = g_file_monitor_finalize; + object_class->dispose = g_file_monitor_dispose; + object_class->get_property = g_file_monitor_get_property; + object_class->set_property = g_file_monitor_set_property; + + /** + * GFileMonitor::changed: + * @monitor: a #GFileMonitor. + * @file: a #GFile. + * @other_file: a #GFile. + * @event_type: a #GFileMonitorEvent. + * + * Emitted when a file has been changed. + **/ + signals[CHANGED] = + g_signal_new ("changed", + G_TYPE_FILE_MONITOR, + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GFileMonitorClass, changed), + NULL, NULL, + nm_marshal_VOID__OBJECT_OBJECT_ENUM, + G_TYPE_NONE, 3, + G_TYPE_FILE, G_TYPE_FILE, G_TYPE_FILE_MONITOR_EVENT); + + g_object_class_install_property (object_class, + PROP_RATE_LIMIT, + g_param_spec_int ("rate-limit", + "Rate limit", + "The limit of the monitor to watch for changes, in milliseconds", + 0, G_MAXINT, + DEFAULT_RATE_LIMIT_MSECS, + G_PARAM_READWRITE| + G_PARAM_STATIC_NAME|G_PARAM_STATIC_NICK|G_PARAM_STATIC_BLURB)); + + g_object_class_install_property (object_class, + PROP_CANCELLED, + g_param_spec_boolean ("cancelled", + "Cancelled", + "Whether the monitor has been cancelled", + FALSE, + G_PARAM_READABLE| + G_PARAM_STATIC_NAME|G_PARAM_STATIC_NICK|G_PARAM_STATIC_BLURB)); +} + +static guint +g_file_hash (GFile *file) +{ + return g_str_hash (g_file_get_const_path (file)); +} + +static gboolean +g_file_equal (GFile *file1, GFile *file2) +{ + return g_str_equal (g_file_get_const_path (file1), g_file_get_const_path (file2)); +} + +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; + monitor->priv->rate_limiter = g_hash_table_new_full ((GHashFunc) g_file_hash, (GEqualFunc) g_file_equal, + NULL, (GDestroyNotify) rate_limiter_free); +} + +/** + * g_file_monitor_is_cancelled: + * @monitor: a #GFileMonitor + * + * Returns whether the monitor is canceled. + * + * Returns: %TRUE if monitor is canceled. %FALSE otherwise. + **/ +gboolean +g_file_monitor_is_cancelled (GFileMonitor *monitor) +{ + gboolean res; + + g_return_val_if_fail (G_IS_FILE_MONITOR (monitor), FALSE); + + G_LOCK (cancelled); + res = monitor->priv->cancelled; + G_UNLOCK (cancelled); + + return res; +} + +/** + * g_file_monitor_cancel: + * @monitor: a #GFileMonitor. + * + * Cancels a file 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); + + G_LOCK (cancelled); + if (monitor->priv->cancelled) + { + G_UNLOCK (cancelled); + return TRUE; + } + + monitor->priv->cancelled = TRUE; + G_UNLOCK (cancelled); + + g_object_notify (G_OBJECT (monitor), "cancelled"); + + 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 report + * consecutive change events to the same file. + * + **/ +void +g_file_monitor_set_rate_limit (GFileMonitor *monitor, + int limit_msecs) +{ + GFileMonitorPrivate *priv; + + g_return_if_fail (G_IS_FILE_MONITOR (monitor)); + + priv = monitor->priv; + if (priv->rate_limit_msec != limit_msecs) + { + monitor->priv->rate_limit_msec = limit_msecs; + g_object_notify (G_OBJECT (monitor), "rate-limit"); + } +} + +typedef struct { + GFileMonitor *monitor; + GFile *child; + GFile *other_file; + GFileMonitorEvent event_type; +} FileChange; + +static gboolean +emit_cb (gpointer data) +{ + FileChange *change = data; + g_signal_emit (change->monitor, signals[CHANGED], 0, + change->child, change->other_file, change->event_type); + return FALSE; +} + +static void +file_change_free (FileChange *change) +{ + g_object_unref (change->monitor); + g_object_unref (change->child); + if (change->other_file) + g_object_unref (change->other_file); + + g_slice_free (FileChange, change); +} + +static void +emit_in_idle (GFileMonitor *monitor, + GFile *child, + GFile *other_file, + GFileMonitorEvent event_type) +{ + GSource *source; + FileChange *change; + + change = g_slice_new (FileChange); + + change->monitor = g_object_ref (monitor); + change->child = g_object_ref (child); + if (other_file) + change->other_file = g_object_ref (other_file); + else + change->other_file = NULL; + change->event_type = event_type; + + source = g_idle_source_new (); + g_source_set_priority (source, 0); + + g_source_set_callback (source, emit_cb, change, (GDestroyNotify)file_change_free); + g_source_attach (source, NULL); + g_source_unref (source); +} + +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 RateLimiter * +new_limiter (GFileMonitor *monitor, + GFile *file) +{ + RateLimiter *limiter; + + limiter = g_slice_new0 (RateLimiter); + 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 (GFileMonitor *monitor, + RateLimiter *limiter) +{ + if (limiter->send_virtual_changes_done_at != 0) + { + emit_in_idle (monitor, 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 (GFileMonitor *monitor, + RateLimiter *limiter, + guint32 time_now) +{ + if (limiter->send_delayed_change_at != 0) + { + emit_in_idle (monitor, + 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; + GFileMonitor *monitor; +} ForEachData; + +static gboolean +calc_min_time (GFileMonitor *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) +{ + GFileMonitor *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 (GFileMonitor *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_file_monitor_emit_event: + * @monitor: a #GFileMonitor. + * @child: a #GFile. + * @other_file: a #GFile. + * @event_type: a set of #GFileMonitorEvent flags. + * + * Emits the #GFileMonitor::changed signal if a change + * has taken place. Should be called from file monitor + * implementations only. + * + * The signal will be emitted from an idle handler. + **/ +void +g_file_monitor_emit_event (GFileMonitor *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_FILE_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); + } + emit_in_idle (monitor, 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) + { + emit_in_idle (monitor, 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); + } +} + + +GType +g_file_monitor_event_get_type (void) +{ + static GType etype = 0; + + if (etype == 0) { + static const GEnumValue values[] = { + { G_FILE_MONITOR_EVENT_CHANGED, "G_FILE_MONITOR_EVENT_CHANGED", "changed" }, + { G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT, "G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT", "changes-done-hint" }, + { G_FILE_MONITOR_EVENT_DELETED, "G_FILE_MONITOR_EVENT_DELETED", "deleted" }, + { G_FILE_MONITOR_EVENT_CREATED, "G_FILE_MONITOR_EVENT_CREATED", "created" }, + { G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED, "G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED", "attribute-changed" }, + { G_FILE_MONITOR_EVENT_PRE_UNMOUNT, "G_FILE_MONITOR_EVENT_PRE_UNMOUNT", "pre-unmount" }, + { G_FILE_MONITOR_EVENT_UNMOUNTED, "G_FILE_MONITOR_EVENT_UNMOUNTED", "unmounted" }, + { 0, NULL, NULL } + }; + etype = g_enum_register_static ("GFileMonitorEvent", values); + } + + return etype; +} + diff --git a/gfilemonitor/gfilemonitor.h b/gfilemonitor/gfilemonitor.h new file mode 100644 index 000000000..53a923b68 --- /dev/null +++ b/gfilemonitor/gfilemonitor.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 + */ + +#ifndef __G_FILE_MONITOR_H__ +#define __G_FILE_MONITOR_H__ + +#include +#include "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)) + +/** + * GFileMonitorEvent: + * @G_FILE_MONITOR_EVENT_CHANGED: a file changed. + * @G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT: a hint that this was probably the last change in a set of changes. + * @G_FILE_MONITOR_EVENT_DELETED: a file was deleted. + * @G_FILE_MONITOR_EVENT_CREATED: a file was created. + * @G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED: a file attribute was changed. + * @G_FILE_MONITOR_EVENT_PRE_UNMOUNT: the file location will soon be unmounted. + * @G_FILE_MONITOR_EVENT_UNMOUNTED: the file location was unmounted. + * + * Specifies what type of event a monitor event is. + **/ +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; + +GType g_file_monitor_event_get_type (void) G_GNUC_CONST; +#define G_TYPE_FILE_MONITOR_EVENT (g_file_monitor_event_get_type ()) + +typedef struct _GFileMonitorClass GFileMonitorClass; +typedef struct _GFileMonitorPrivate GFileMonitorPrivate; + +/** + * GFileMonitor: + * + * Watches for changes to a file. + **/ +struct _GFileMonitor +{ + GObject parent_instance; + + /*< 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); + + /*< private >*/ + /* 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 *child, + GFile *other_file, + GFileMonitorEvent event_type); + +G_END_DECLS + +#endif /* __G_FILE_MONITOR_H__ */ diff --git a/gfilemonitor/glocaldirectorymonitor.c b/gfilemonitor/glocaldirectorymonitor.c new file mode 100644 index 000000000..0f605caab --- /dev/null +++ b/gfilemonitor/glocaldirectorymonitor.c @@ -0,0 +1,244 @@ +/* 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 + */ + +#include +#include + +#include "glocaldirectorymonitor.h" + +enum +{ + PROP_0, + PROP_DIRNAME +}; + +#if 0 +static gboolean g_local_directory_monitor_cancel (GFileMonitor *monitor); +static void mounts_changed (GUnixMountMonitor *mount_monitor, + gpointer user_data); +#endif + +G_DEFINE_ABSTRACT_TYPE (GLocalDirectoryMonitor, g_local_directory_monitor, G_TYPE_FILE_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 0 + 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; + } +#endif + + 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_warn_if_fail (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 0 + if (!klass->mount_notify) + { +#ifdef G_OS_WIN32 + g_warning ("G_OS_WIN32: no mount emulation"); +#else + GUnixMountEntry *mount; + + /* Emulate unmount detection */ + + mount = g_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_object (local_monitor->mount_monitor, "mounts_changed", + G_CALLBACK (mounts_changed), local_monitor, 0); +#endif + } +#endif + + return obj; +} + +static void +g_local_directory_monitor_class_init (GLocalDirectoryMonitorClass* klass) +{ + GObjectClass* gobject_class = G_OBJECT_CLASS (klass); +#if 0 + GFileMonitorClass *file_monitor_class = G_FILE_MONITOR_CLASS (klass); +#endif + + 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; + +#if 0 + file_monitor_class->cancel = g_local_directory_monitor_cancel; +#endif + + 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| + G_PARAM_STATIC_NAME|G_PARAM_STATIC_NICK|G_PARAM_STATIC_BLURB)); + +#if 0 + klass->mount_notify = FALSE; +#endif +} + +static void +g_local_directory_monitor_init (GLocalDirectoryMonitor *local_monitor) +{ +} + +#if 0 +static void +mounts_changed (GUnixMountMonitor *mount_monitor, + gpointer user_data) +{ + GLocalDirectoryMonitor *local_monitor = user_data; + GUnixMountEntry *mount; + gboolean is_mounted; + GFile *file; + + /* Emulate unmount detection */ +#ifdef G_OS_WIN32 + mount = NULL; + g_warning ("G_OS_WIN32: no mount emulation"); +#else + mount = g_unix_mount_at (local_monitor->dirname, NULL); + + is_mounted = mount != NULL; + + if (mount) + g_unix_mount_free (mount); +#endif + + if (local_monitor->was_mounted != is_mounted) + { + if (local_monitor->was_mounted && !is_mounted) + { + file = g_file_new_for_path (local_monitor->dirname); + g_file_monitor_emit_event (G_FILE_MONITOR (local_monitor), + file, NULL, + G_FILE_MONITOR_EVENT_UNMOUNTED); + g_object_unref (file); + } + local_monitor->was_mounted = is_mounted; + } +} +#endif + +/** + * _g_local_directory_monitor_new: + * @dirname: filename of the directory to monitor. + * @flags: #GFileMonitorFlags. + * + * Returns: new #GFileMonitor for the given @dirname. + **/ +GFileMonitor* +_g_local_directory_monitor_new (const char *dirname, + GFileMonitorFlags flags, + GError **error) +{ + return G_FILE_MONITOR (g_object_new (G_TYPE_LOCAL_DIRECTORY_MONITOR, "dirname", dirname, NULL)); +} + +#if 0 +static gboolean +g_local_directory_monitor_cancel (GFileMonitor *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; +} +#endif + diff --git a/gfilemonitor/glocaldirectorymonitor.h b/gfilemonitor/glocaldirectorymonitor.h new file mode 100644 index 000000000..a82f1f6b8 --- /dev/null +++ b/gfilemonitor/glocaldirectorymonitor.h @@ -0,0 +1,69 @@ +/* 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 + */ + +#ifndef __G_LOCAL_DIRECTORY_MONITOR_H__ +#define __G_LOCAL_DIRECTORY_MONITOR_H__ + +#include +#include "gfilemonitor.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)) + +#define G_LOCAL_DIRECTORY_MONITOR_EXTENSION_POINT_NAME "gio-local-directory-monitor" + +typedef struct _GLocalDirectoryMonitor GLocalDirectoryMonitor; +typedef struct _GLocalDirectoryMonitorClass GLocalDirectoryMonitorClass; + +struct _GLocalDirectoryMonitor +{ + GFileMonitor parent_instance; + gchar *dirname; +#if 0 + /* For mount emulation */ + GUnixMountMonitor *mount_monitor; + gboolean was_mounted; +#endif +}; + +struct _GLocalDirectoryMonitorClass { + GFileMonitorClass parent_class; +#if 0 + gboolean mount_notify; +#endif + gboolean (*is_supported) (void); +}; + +GType g_local_directory_monitor_get_type (void) G_GNUC_CONST; + +GFileMonitor* _g_local_directory_monitor_new (const char *dirname, + GFileMonitorFlags flags, + GError **error); + +G_END_DECLS + +#endif /* __G_LOCAL_DIRECTORY_MONITOR_H__ */ diff --git a/gfilemonitor/glocalfilemonitor.c b/gfilemonitor/glocalfilemonitor.c new file mode 100644 index 000000000..5eb5223fb --- /dev/null +++ b/gfilemonitor/glocalfilemonitor.c @@ -0,0 +1,141 @@ +/* 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 + */ + +#include +#include + +#include "glocalfilemonitor.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_warn_if_fail (G_VALUE_HOLDS_STRING (construct_properties[i].value)); + filename = g_value_get_string (construct_properties[i].value); + break; + } + } + + g_warn_if_fail (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| + G_PARAM_STATIC_NAME|G_PARAM_STATIC_NICK|G_PARAM_STATIC_BLURB)); +} + +/** + * 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, + GError **error) +{ + return G_FILE_MONITOR (g_object_new (G_TYPE_LOCAL_FILE_MONITOR, "filename", pathname, NULL)); +} + diff --git a/gfilemonitor/glocalfilemonitor.h b/gfilemonitor/glocalfilemonitor.h new file mode 100644 index 000000000..0bbe8deb9 --- /dev/null +++ b/gfilemonitor/glocalfilemonitor.h @@ -0,0 +1,61 @@ +/* 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 + */ + +#ifndef __G_LOCAL_FILE_MONITOR_H__ +#define __G_LOCAL_FILE_MONITOR_H__ + +#include +#include "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)) + +#define G_LOCAL_FILE_MONITOR_EXTENSION_POINT_NAME "gio-local-file-monitor" + +typedef struct _GLocalFileMonitor GLocalFileMonitor; +typedef struct _GLocalFileMonitorClass GLocalFileMonitorClass; + +struct _GLocalFileMonitor +{ + GFileMonitor parent_instance; + gchar *filename; +}; + +struct _GLocalFileMonitorClass { + GFileMonitorClass parent_class; + 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, + GError **error); + +G_END_DECLS + +#endif /* __G_LOCAL_FILE_MONITOR_H__ */ diff --git a/gfilemonitor/inotify-diag.c b/gfilemonitor/inotify-diag.c new file mode 100644 index 000000000..937ebd702 --- /dev/null +++ b/gfilemonitor/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 +*/ + +#include "config.h" +#include +#include +#include +#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/gfilemonitor/inotify-diag.h b/gfilemonitor/inotify-diag.h new file mode 100644 index 000000000..f818f1616 --- /dev/null +++ b/gfilemonitor/inotify-diag.h @@ -0,0 +1,29 @@ +/* inotify-helper.h - GNOME VFS 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 +*/ + + +#ifndef __INOTIFY_DIAG_H +#define __INOTIFY_DIAG_H + +void _id_startup (void); + +#endif /* __INOTIFY_DIAG_H */ diff --git a/gfilemonitor/inotify-helper.c b/gfilemonitor/inotify-helper.c new file mode 100644 index 000000000..ac53a8886 --- /dev/null +++ b/gfilemonitor/inotify-helper.c @@ -0,0 +1,234 @@ +/* -*- 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 +*/ + +#include "config.h" +#include +#include +#include +#include +/* Just include the local header to stop all the pain */ +#include +#include "gfilemonitor.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_GNUC_INTERNAL 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); + + g_file_monitor_emit_event (G_FILE_MONITOR (sub->user_data), + 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); + + g_file_monitor_emit_event (G_FILE_MONITOR (sub->user_data), + 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/gfilemonitor/inotify-helper.h b/gfilemonitor/inotify-helper.h new file mode 100644 index 000000000..1fd9701d4 --- /dev/null +++ b/gfilemonitor/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 +*/ + + +#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/gfilemonitor/inotify-kernel.c b/gfilemonitor/inotify-kernel.c new file mode 100644 index 000000000..f9861a0bd --- /dev/null +++ b/gfilemonitor/inotify-kernel.c @@ -0,0 +1,673 @@ +/* + 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 +*/ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include "inotify-kernel.h" +#include + +/* 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/gfilemonitor/inotify-kernel.h b/gfilemonitor/inotify-kernel.h new file mode 100644 index 000000000..b406d71d5 --- /dev/null +++ b/gfilemonitor/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 +*/ + +#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/gfilemonitor/inotify-missing.c b/gfilemonitor/inotify-missing.c new file mode 100644 index 000000000..d19332acf --- /dev/null +++ b/gfilemonitor/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 +*/ + +#include "config.h" +#include +#include "inotify-missing.h" +#include "inotify-path.h" + +#define SCAN_MISSING_TIME 4 /* 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_seconds (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/gfilemonitor/inotify-missing.h b/gfilemonitor/inotify-missing.h new file mode 100644 index 000000000..b67b59525 --- /dev/null +++ b/gfilemonitor/inotify-missing.h @@ -0,0 +1,35 @@ +/* inotify-helper.h - GNOME VFS 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 +*/ + + +#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/gfilemonitor/inotify-path.c b/gfilemonitor/inotify-path.c new file mode 100644 index 000000000..33275989c --- /dev/null +++ b/gfilemonitor/inotify-path.c @@ -0,0 +1,422 @@ +/* -*- 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 +*/ + +#include "config.h" + +/* Don't put conflicting kernel types in the global namespace: */ +#define __KERNEL_STRICT_NAMES + +#include +#include +#include +#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) + +/* Older libcs don't have this */ +#ifndef IN_ONLYDIR +#define IN_ONLYDIR 0 +#endif + +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/gfilemonitor/inotify-path.h b/gfilemonitor/inotify-path.h new file mode 100644 index 000000000..c613b9f82 --- /dev/null +++ b/gfilemonitor/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 +*/ + +#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/gfilemonitor/inotify-sub.c b/gfilemonitor/inotify-sub.c new file mode 100644 index 000000000..404dc208e --- /dev/null +++ b/gfilemonitor/inotify-sub.c @@ -0,0 +1,70 @@ +/* -*- 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 +*/ + +#include "config.h" +#include +#include + +#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/gfilemonitor/inotify-sub.h b/gfilemonitor/inotify-sub.h new file mode 100644 index 000000000..36561e74c --- /dev/null +++ b/gfilemonitor/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 +*/ + + +#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/system-settings/plugins/ifcfg-suse/Makefile.am b/system-settings/plugins/ifcfg-suse/Makefile.am index cafab8699..7f1043f64 100644 --- a/system-settings/plugins/ifcfg-suse/Makefile.am +++ b/system-settings/plugins/ifcfg-suse/Makefile.am @@ -28,7 +28,14 @@ libnm_settings_plugin_ifcfg_suse_la_LIBADD = \ $(GLIB_LIBS) \ $(GMODULE_LIBS) \ $(POLKIT_LIBS) \ - $(GIO_LIBS) \ $(top_builddir)/libnm-util/libnm-util.la \ $(top_builddir)/libnm-glib/libnm_glib.la +if NO_GIO +libnm_settings_plugin_ifcfg_suse_la_LIBADD += \ + $(top_builddir)/gfilemonitor/libgfilemonitor.la +else +libnm_settings_plugin_ifcfg_suse_la_LIBADD += \ + $(GIO_LIBS) +endif + diff --git a/system-settings/plugins/ifcfg-suse/plugin.c b/system-settings/plugins/ifcfg-suse/plugin.c index 86fe71944..22fc72d12 100644 --- a/system-settings/plugins/ifcfg-suse/plugin.c +++ b/system-settings/plugins/ifcfg-suse/plugin.c @@ -21,13 +21,19 @@ * (C) Copyright 2007 Red Hat, Inc. */ +#include #include #include #include #include #include #include + +#ifndef NO_GIO #include +#else +#include +#endif #include #include diff --git a/system-settings/plugins/keyfile/Makefile.am b/system-settings/plugins/keyfile/Makefile.am index d4c7e48f5..d4e4a4a40 100644 --- a/system-settings/plugins/keyfile/Makefile.am +++ b/system-settings/plugins/keyfile/Makefile.am @@ -27,7 +27,13 @@ libnm_settings_plugin_keyfile_la_LIBADD = \ $(GLIB_LIBS) \ $(GMODULE_LIBS) \ $(DBUS_LIBS) \ - $(GIO_LIBS) \ $(top_builddir)/libnm-util/libnm-util.la \ $(top_builddir)/libnm-glib/libnm_glib.la +if NO_GIO +libnm_settings_plugin_keyfile_la_LIBADD += \ + $(top_builddir)/gfilemonitor/libgfilemonitor.la +else +libnm_settings_plugin_keyfile_la_LIBADD += \ + $(GIO_LIBS) +endif diff --git a/system-settings/plugins/keyfile/plugin.c b/system-settings/plugins/keyfile/plugin.c index 5320fb22b..83ab1173c 100644 --- a/system-settings/plugins/keyfile/plugin.c +++ b/system-settings/plugins/keyfile/plugin.c @@ -1,16 +1,22 @@ /* -*- Mode: C; tab-width: 5; indent-tabs-mode: t; c-basic-offset: 5 -*- */ +#include #include #include #include #include #include #include -#include #include #include #include +#ifndef NO_GIO +#include +#else +#include +#endif + #include "plugin.h" #include "nm-system-config-interface.h" #include "nm-keyfile-connection.h"