LCOV - code coverage report
Current view: top level - egg - egg-file-tracker.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 89.8 % 108 97
Test Date: 2024-12-15 20:37:51 Functions: 100.0 % 13 13

            Line data    Source code
       1              : /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
       2              : /* egg-file-tracker.c - Watch for changes in a directory
       3              : 
       4              :    Copyright (C) 2008 Stefan Walter
       5              : 
       6              :    The Gnome Keyring Library is free software; you can redistribute it and/or
       7              :    modify it under the terms of the GNU Library General Public License as
       8              :    published by the Free Software Foundation; either version 2 of the
       9              :    License, or (at your option) any later version.
      10              : 
      11              :    The Gnome Keyring Library is distributed in the hope that it will be useful,
      12              :    but WITHOUT ANY WARRANTY; without even the implied warranty of
      13              :    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
      14              :    Library General Public License for more details.
      15              : 
      16              :    You should have received a copy of the GNU Library General Public
      17              :    License along with the Gnome Library; see the file COPYING.LIB.  If not,
      18              :    <http://www.gnu.org/licenses/>.
      19              : 
      20              :    Author: Stef Walter <stef@memberwebs.com>
      21              : */
      22              : 
      23              : #include "config.h"
      24              : 
      25              : #include "egg-file-tracker.h"
      26              : 
      27              : #include "egg/egg-error.h"
      28              : 
      29              : #include <glib.h>
      30              : #include <glib/gstdio.h>
      31              : 
      32              : #include <sys/stat.h>
      33              : #include <errno.h>
      34              : #include <unistd.h>
      35              : 
      36              : typedef struct _UpdateDescendants {
      37              :         EggFileTracker *tracker;
      38              :         GHashTable *checks;
      39              : } UpdateDescendants;
      40              : 
      41              : struct _EggFileTracker {
      42              :         GObject parent;
      43              : 
      44              :         /* Specification */
      45              :         GPatternSpec *include;
      46              :         GPatternSpec *exclude;
      47              :         gchar *directory_path;
      48              :         time_t directory_mtime;
      49              : 
      50              :         /* Matched files */
      51              :         GHashTable *files;
      52              : };
      53              : 
      54              : enum {
      55              :         FILE_ADDED,
      56              :         FILE_REMOVED,
      57              :         FILE_CHANGED,
      58              :         LAST_SIGNAL
      59              : };
      60              : 
      61              : static guint signals[LAST_SIGNAL] = { 0 };
      62              : 
      63         1088 : G_DEFINE_TYPE (EggFileTracker, egg_file_tracker, G_TYPE_OBJECT);
      64              : 
      65              : /* -----------------------------------------------------------------------------
      66              :  * HELPERS
      67              :  */
      68              : 
      69              : static void
      70          192 : copy_key_string (gpointer key, gpointer value, gpointer data)
      71              : {
      72          192 :         GHashTable *dest = (GHashTable*)data;
      73          192 :         g_hash_table_replace (dest, g_strdup (key), value);
      74          192 : }
      75              : 
      76              : static void
      77            4 : remove_files (gpointer key, gpointer value, gpointer data)
      78              : {
      79            4 :         EggFileTracker *self = EGG_FILE_TRACKER (data);
      80              : 
      81            4 :         g_hash_table_remove (self->files, key);
      82            4 :         g_signal_emit (self, signals[FILE_REMOVED], 0, key);
      83            4 : }
      84              : 
      85              : static gboolean
      86          192 : update_file (EggFileTracker *self, gboolean force_all, const gchar *path)
      87              : {
      88              :         time_t old_mtime;
      89              :         struct stat sb;
      90              : 
      91          192 :         if (stat (path, &sb) < 0) {
      92            4 :                 if (errno != ENOENT && errno != ENOTDIR && errno != EPERM)
      93            0 :                         g_warning ("couldn't stat file: %s: %s", path, g_strerror (errno));
      94            4 :                 return FALSE;
      95              :         }
      96              : 
      97          188 :         old_mtime = GPOINTER_TO_UINT (g_hash_table_lookup (self->files, path));
      98          188 :         g_assert (old_mtime);
      99              : 
     100              :         /* See if it has actually changed */
     101          188 :         if (force_all || old_mtime != sb.st_mtime) {
     102            4 :                 g_assert (g_hash_table_lookup (self->files, path));
     103            8 :                 g_hash_table_insert (self->files, g_strdup (path), GUINT_TO_POINTER (sb.st_mtime));
     104            4 :                 g_signal_emit (self, signals[FILE_CHANGED], 0, path);
     105              :         }
     106              : 
     107          188 :         return TRUE;
     108              : }
     109              : 
     110              : static void
     111          190 : update_each_file (gpointer key, gpointer unused, gpointer data)
     112              : {
     113          190 :         UpdateDescendants *ctx = (UpdateDescendants*)data;
     114          190 :         if (update_file (ctx->tracker, FALSE, key))
     115          186 :                 g_hash_table_remove (ctx->checks, key);
     116          190 : }
     117              : 
     118              : static void
     119          244 : update_directory (EggFileTracker *self, gboolean force_all, GHashTable *checks)
     120              : {
     121              :         UpdateDescendants uctx;
     122              :         struct stat sb;
     123          244 :         GError *err = NULL;
     124              :         const char *filename;
     125              :         gchar *file;
     126              :         GDir *dir;
     127              :         int ret, lasterr;
     128              : 
     129          244 :         g_assert (checks);
     130          244 :         g_assert (EGG_IS_FILE_TRACKER (self));
     131              : 
     132          244 :         if (!self->directory_path)
     133          179 :                 return;
     134              : 
     135          244 :         if (stat (self->directory_path, &sb) < 0) {
     136            1 :                 if (errno != ENOENT && errno != ENOTDIR && errno != EPERM)
     137            0 :                         g_message ("couldn't stat directory: %s: %s",
     138              :                                    self->directory_path, g_strerror (errno));
     139            1 :                 return;
     140              :         }
     141              : 
     142              :         /* See if it was updated since last seen or not */
     143          243 :         if (!force_all && self->directory_mtime == sb.st_mtime) {
     144              : 
     145          178 :                 uctx.checks = checks;
     146          178 :                 uctx.tracker = self;
     147              : 
     148              :                 /* Still need to check for individual file updates */
     149          178 :                 g_hash_table_foreach (self->files, update_each_file, &uctx);
     150          178 :                 return;
     151              :         }
     152              : 
     153           65 :         self->directory_mtime = sb.st_mtime;
     154              : 
     155              :         /* Actually list the directory */
     156           65 :         dir = g_dir_open (self->directory_path, 0, &err);
     157           65 :         if (dir == NULL) {
     158            0 :                 if (errno != ENOENT && errno != ENOTDIR && errno != EPERM)
     159            0 :                         g_message ("couldn't list keyrings at: %s: %s", self->directory_path,
     160              :                                    egg_error_message (err));
     161            0 :                 g_error_free (err);
     162            0 :                 return;
     163              :         }
     164              : 
     165          203 :         while ((filename = g_dir_read_name (dir)) != NULL) {
     166          138 :                 if (filename[0] == '.')
     167            0 :                         continue;
     168          138 :                 if (self->include && !g_pattern_match_string (self->include, filename))
     169           24 :                         continue;
     170          114 :                 if (self->exclude && g_pattern_match_string (self->exclude, filename))
     171            0 :                         continue;
     172              : 
     173          114 :                 file = g_build_filename (self->directory_path, filename, NULL);
     174              : 
     175              :                 /* If we hadn't yet seen this, then add it */
     176          114 :                 if (!g_hash_table_remove (checks, file)) {
     177              : 
     178              :                         /* Get the last modified time for this one */
     179          112 :                         ret = g_stat (file, &sb);
     180          112 :                         lasterr = errno;
     181              : 
     182              :                         /* Couldn't access the file */
     183          112 :                         if (ret < 0) {
     184            0 :                                 g_message ("couldn't stat file: %s: %s", file, g_strerror (lasterr));
     185              : 
     186              :                         } else {
     187              : 
     188              :                                 /* We don't do directories */
     189          112 :                                 if (!(sb.st_mode & S_IFDIR)) {
     190          224 :                                         g_hash_table_replace (self->files, g_strdup (file), GINT_TO_POINTER (sb.st_mtime));
     191          112 :                                         g_signal_emit (self, signals[FILE_ADDED], 0, file);
     192              :                                 }
     193              :                         }
     194              : 
     195              :                 /* Otherwise we already had it, see if it needs updating */
     196              :                 } else {
     197            2 :                         update_file (self, force_all, file);
     198              :                 }
     199              : 
     200          114 :                 g_free (file);
     201              :         }
     202              : 
     203           65 :         g_dir_close (dir);
     204              : }
     205              : 
     206              : /* -----------------------------------------------------------------------------
     207              :  * OBJECT
     208              :  */
     209              : 
     210              : static void
     211          203 : egg_file_tracker_init (EggFileTracker *self)
     212              : {
     213          203 :         self->files = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
     214          203 : }
     215              : 
     216              : static void
     217          203 : egg_file_tracker_finalize (GObject *obj)
     218              : {
     219          203 :         EggFileTracker *self = EGG_FILE_TRACKER (obj);
     220              : 
     221          203 :         if (self->include)
     222          203 :                 g_pattern_spec_free (self->include);
     223          203 :         if (self->exclude)
     224            0 :                 g_pattern_spec_free (self->exclude);
     225          203 :         g_free (self->directory_path);
     226              : 
     227          203 :         g_hash_table_destroy (self->files);
     228              : 
     229          203 :         G_OBJECT_CLASS (egg_file_tracker_parent_class)->finalize (obj);
     230          203 : }
     231              : 
     232              : static void
     233           38 : egg_file_tracker_class_init (EggFileTrackerClass *klass)
     234              : {
     235              :         GObjectClass *gobject_class;
     236           38 :         gobject_class = (GObjectClass*) klass;
     237              : 
     238           38 :         egg_file_tracker_parent_class = g_type_class_peek_parent (klass);
     239           38 :         gobject_class->finalize = egg_file_tracker_finalize;
     240              : 
     241           38 :         signals[FILE_ADDED] = g_signal_new ("file-added", EGG_TYPE_FILE_TRACKER,
     242              :                         G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (EggFileTrackerClass, file_added),
     243              :                         NULL, NULL, g_cclosure_marshal_VOID__STRING,
     244              :                         G_TYPE_NONE, 1, G_TYPE_STRING);
     245              : 
     246           38 :         signals[FILE_CHANGED] = g_signal_new ("file-changed", EGG_TYPE_FILE_TRACKER,
     247              :                         G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (EggFileTrackerClass, file_changed),
     248              :                         NULL, NULL, g_cclosure_marshal_VOID__STRING,
     249              :                         G_TYPE_NONE, 1, G_TYPE_STRING);
     250              : 
     251           38 :         signals[FILE_REMOVED] = g_signal_new ("file-removed", EGG_TYPE_FILE_TRACKER,
     252              :                         G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (EggFileTrackerClass, file_removed),
     253              :                         NULL, NULL, g_cclosure_marshal_VOID__STRING,
     254              :                         G_TYPE_NONE, 1, G_TYPE_STRING);
     255           38 : }
     256              : 
     257              : EggFileTracker*
     258          203 : egg_file_tracker_new (const gchar *directory, const gchar *include, const gchar *exclude)
     259              : {
     260              :         EggFileTracker *self;
     261              :         const gchar *homedir;
     262              : 
     263          203 :         g_return_val_if_fail (directory, NULL);
     264              : 
     265          203 :         self = g_object_new (EGG_TYPE_FILE_TRACKER, NULL);
     266              : 
     267              :         /* TODO: Use properties */
     268              : 
     269          203 :         if (directory[0] == '~' && directory[1] == '/') {
     270            5 :                 homedir = g_getenv ("HOME");
     271            5 :                 if (!homedir)
     272            0 :                         homedir = g_get_home_dir ();
     273            5 :                 self->directory_path = g_build_filename (homedir, directory + 2, NULL);
     274              : 
     275              :         /* A relative or absolute path */
     276              :         } else {
     277          198 :                 self->directory_path = g_strdup (directory);
     278              :         }
     279              : 
     280          203 :         self->include = include ? g_pattern_spec_new (include) : NULL;
     281          203 :         self->exclude = exclude ? g_pattern_spec_new (exclude) : NULL;
     282              : 
     283          203 :         return self;
     284              : }
     285              : 
     286              : void
     287          244 : egg_file_tracker_refresh (EggFileTracker *self, gboolean force_all)
     288              : {
     289              :         GHashTable *checks;
     290              : 
     291          244 :         g_return_if_fail (EGG_IS_FILE_TRACKER (self));
     292              : 
     293              :         /* Copy into our check set */
     294          244 :         checks = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
     295          244 :         g_hash_table_foreach (self->files, copy_key_string, checks);
     296              : 
     297              :         /* If only one volume, then just try and access it directly */
     298          244 :         update_directory (self, force_all, checks);
     299              : 
     300              :         /* Find any keyrings whose paths we didn't see */
     301          244 :         g_hash_table_foreach (checks, remove_files, self);
     302          244 :         g_hash_table_destroy (checks);
     303              : }
        

Generated by: LCOV version 2.0-1