mirror of
				https://gitlab.gnome.org/GNOME/glib.git
				synced 2025-10-31 00:12:19 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			537 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			537 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /*******************************************************************************
 | |
|   Copyright (c) 2011, 2012 Dmitry Matveev <me@dmitrymatveev.co.uk>
 | |
| 
 | |
|   Permission is hereby granted, free of charge, to any person obtaining a copy
 | |
|   of this software and associated documentation files (the "Software"), to deal
 | |
|   in the Software without restriction, including without limitation the rights
 | |
|   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | |
|   copies of the Software, and to permit persons to whom the Software is
 | |
|   furnished to do so, subject to the following conditions:
 | |
| 
 | |
|   The above copyright notice and this permission notice shall be included in
 | |
|   all copies or substantial portions of the Software.
 | |
| 
 | |
|   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | |
|   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | |
|   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | |
|   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | |
|   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | |
|   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 | |
|   THE SOFTWARE.
 | |
| *******************************************************************************/
 | |
| 
 | |
| #include <glib.h>
 | |
| 
 | |
| #include <stdlib.h>  /* calloc */
 | |
| #include <stdio.h>   /* printf */
 | |
| #include <dirent.h>  /* opendir, readdir, closedir */
 | |
| #include <string.h>  /* strcmp */
 | |
| #include <assert.h>
 | |
| 
 | |
| #include "dep-list.h"
 | |
| 
 | |
| static gboolean kdl_debug_enabled = FALSE;
 | |
| #define perror_msg if (kdl_debug_enabled) g_warning
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * Print a list to stdout.
 | |
|  *
 | |
|  * @param[in] dl A pointer to a list.
 | |
|  **/
 | |
| void
 | |
| dl_print (const dep_list *dl)
 | |
| {
 | |
|     while (dl != NULL) {
 | |
|         printf ("%lld:%s ", (long long int) dl->inode, dl->path);
 | |
|         dl = dl->next;
 | |
|     }
 | |
|     printf ("\n");
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Create a new list item.
 | |
|  *
 | |
|  * Create a new list item and initialize its fields.
 | |
|  *
 | |
|  * @param[in] path  A name of a file (the string is not copied!).
 | |
|  * @param[in] inode A file's inode number.
 | |
|  * @return A pointer to a new item or NULL in the case of error.
 | |
|  **/
 | |
| dep_list* dl_create (char *path, ino_t inode)
 | |
| {
 | |
|     dep_list *dl = calloc (1, sizeof (dep_list));
 | |
|     if (dl == NULL) {
 | |
|         perror_msg ("Failed to create a new dep-list item");
 | |
|         return NULL;
 | |
|     }
 | |
| 
 | |
|     dl->path = path;
 | |
|     dl->inode = inode;
 | |
|     return dl;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Create a shallow copy of a list.
 | |
|  *
 | |
|  * A shallow copy is a copy of a structure, but not the copy of the
 | |
|  * contents. All data pointers ('path' in our case) of a list and its
 | |
|  * shallow copy will point to the same memory.
 | |
|  *
 | |
|  * @param[in] dl A pointer to list to make a copy. May be NULL.
 | |
|  * @return A shallow copy of the list.
 | |
|  **/ 
 | |
| dep_list*
 | |
| dl_shallow_copy (const dep_list *dl)
 | |
| {
 | |
|     dep_list *head;
 | |
|     dep_list *cp;
 | |
|     const dep_list *it;
 | |
| 
 | |
|     if (dl == NULL) {
 | |
|         return NULL;
 | |
|     }
 | |
| 
 | |
|     head = calloc (1, sizeof (dep_list));
 | |
|     if (head == NULL) {
 | |
|         perror_msg ("Failed to allocate head during shallow copy");
 | |
|         return NULL;
 | |
|     }
 | |
| 
 | |
|     cp = head;
 | |
|     it = dl;
 | |
| 
 | |
|     while (it != NULL) {
 | |
|         cp->path = it->path;
 | |
|         cp->inode = it->inode;
 | |
|         if (it->next) {
 | |
|             cp->next = calloc (1, sizeof (dep_list));
 | |
|             if (cp->next == NULL) {
 | |
|                 perror_msg ("Failed to allocate a new element during shallow copy");
 | |
|                 dl_shallow_free (head);
 | |
|                 return NULL;
 | |
|             }
 | |
|             cp = cp->next;
 | |
|         }
 | |
|         it = it->next;
 | |
|     }
 | |
| 
 | |
|     return head;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Free the memory allocated for shallow copy.
 | |
|  *
 | |
|  * This function will free the memory used by a list structure, but
 | |
|  * the list data will remain in the heap.
 | |
|  *
 | |
|  * @param[in] dl A pointer to a list. May be NULL.
 | |
|  **/
 | |
| void
 | |
| dl_shallow_free (dep_list *dl)
 | |
| {
 | |
|     while (dl != NULL) {
 | |
|         dep_list *ptr = dl;
 | |
|         dl = dl->next;
 | |
|         free (ptr);
 | |
|     }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Free the memory allocated for a list.
 | |
|  *
 | |
|  * This function will free all the memory used by a list: both
 | |
|  * list structure and the list data.
 | |
|  *
 | |
|  * @param[in] dl A pointer to a list. May be NULL.
 | |
|  **/
 | |
| void
 | |
| dl_free (dep_list *dl)
 | |
| {
 | |
|     while (dl != NULL) {
 | |
|         dep_list *ptr = dl;
 | |
|         dl = dl->next;
 | |
| 
 | |
|         free (ptr->path);
 | |
|         free (ptr);
 | |
|     }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Create a directory listing and return it as a list.
 | |
|  *
 | |
|  * @param[in] path A path to a directory.
 | |
|  * @return A pointer to a list. May return NULL, check errno in this case.
 | |
|  **/
 | |
| dep_list*
 | |
| dl_listing (const char *path)
 | |
| {
 | |
|     dep_list *head = NULL;
 | |
|     dep_list *prev = NULL;
 | |
|     DIR *dir;
 | |
| 
 | |
|     assert (path != NULL);
 | |
| 
 | |
|     dir = opendir (path);
 | |
|     if (dir != NULL) {
 | |
|         struct dirent *ent;
 | |
| 
 | |
|         while ((ent = readdir (dir)) != NULL) {
 | |
|             dep_list *iter;
 | |
| 
 | |
|             if (!strcmp (ent->d_name, ".") || !strcmp (ent->d_name, "..")) {
 | |
|                 continue;
 | |
|             }
 | |
| 
 | |
|             if (head == NULL) {
 | |
|                 head = calloc (1, sizeof (dep_list));
 | |
|                 if (head == NULL) {
 | |
|                     perror_msg ("Failed to allocate head during listing");
 | |
|                     goto error;
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             iter = (prev == NULL) ? head : calloc (1, sizeof (dep_list));
 | |
|             if (iter == NULL) {
 | |
|                 perror_msg ("Failed to allocate a new element during listing");
 | |
|                 goto error;
 | |
|             }
 | |
| 
 | |
|             iter->path = strdup (ent->d_name);
 | |
|             if (iter->path == NULL) {
 | |
|                 perror_msg ("Failed to copy a string during listing");
 | |
|                 goto error;
 | |
|             }
 | |
| 
 | |
|             iter->inode = ent->d_ino;
 | |
|             iter->next = NULL;
 | |
|             if (prev) {
 | |
|                 prev->next = iter;
 | |
|             }
 | |
|             prev = iter;
 | |
|         }
 | |
| 
 | |
|         closedir (dir);
 | |
|     }
 | |
|     return head;
 | |
| 
 | |
| error:
 | |
|     if (dir != NULL) {
 | |
|         closedir (dir);
 | |
|     }
 | |
|     dl_free (head);
 | |
|     return NULL;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Perform a diff on lists.
 | |
|  *
 | |
|  * This function performs something like a set intersection. The same items
 | |
|  * will be removed from the both lists. Items are compared by a filename.
 | |
|  * 
 | |
|  * @param[in,out] before A pointer to a pointer to a list. Will contain items
 | |
|  *     which were not found in the 'after' list.
 | |
|  * @param[in,out] after  A pointer to a pointer to a list. Will contain items
 | |
|  *     which were not found in the 'before' list.
 | |
|  **/
 | |
| void
 | |
| dl_diff (dep_list **before, dep_list **after)
 | |
| {
 | |
|     dep_list *before_iter;
 | |
|     dep_list *before_prev;
 | |
| 
 | |
|     assert (before != NULL);
 | |
|     assert (after != NULL);
 | |
| 
 | |
|     if (*before == NULL || *after == NULL) {
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     before_iter = *before;
 | |
|     before_prev = NULL;
 | |
| 
 | |
|     while (before_iter != NULL) {
 | |
|         dep_list *after_iter = *after;
 | |
|         dep_list *after_prev = NULL;
 | |
|         dep_list *oldptr;
 | |
| 
 | |
|         int matched = 0;
 | |
|         while (after_iter != NULL) {
 | |
|             if (strcmp (before_iter->path, after_iter->path) == 0) {
 | |
|                 matched = 1;
 | |
|                 /* removing the entry from the both lists */
 | |
|                 if (before_prev) {
 | |
|                     before_prev->next = before_iter->next;
 | |
|                 } else {
 | |
|                     *before = before_iter->next;
 | |
|                 }
 | |
| 
 | |
|                 if (after_prev) {
 | |
|                     after_prev->next = after_iter->next;
 | |
|                 } else {
 | |
|                     *after = after_iter->next;
 | |
|                 }
 | |
|                 free (after_iter);
 | |
|                 break;
 | |
|             }
 | |
|             after_prev = after_iter;
 | |
|             after_iter = after_iter->next;
 | |
|         }
 | |
| 
 | |
|         oldptr = before_iter;
 | |
|         before_iter = before_iter->next;
 | |
|         if (matched == 0) {
 | |
|             before_prev = oldptr;
 | |
|         } else {
 | |
|             free (oldptr);
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * Traverses two lists. Compares items with a supplied expression
 | |
|  * and performs the passed code on a match. Removes the matched entries
 | |
|  * from the both lists.
 | |
|  **/
 | |
| #define EXCLUDE_SIMILAR(removed_list, added_list, match_expr, matched_code) \
 | |
| G_STMT_START {                                                          \
 | |
|     dep_list *removed_list##_iter;                                      \
 | |
|     dep_list *removed_list##_prev;                                      \
 | |
|     int productive = 0;                                                 \
 | |
|                                                                         \
 | |
|     assert (removed_list != NULL);                                      \
 | |
|     assert (added_list != NULL);                                        \
 | |
|                                                                         \
 | |
|     removed_list##_iter = *removed_list;                                \
 | |
|     removed_list##_prev = NULL;                                         \
 | |
|                                                                         \
 | |
|     while (removed_list##_iter != NULL) {                               \
 | |
|         dep_list *added_list##_iter = *added_list;                      \
 | |
|         dep_list *added_list##_prev = NULL;                             \
 | |
|         dep_list *oldptr;                                               \
 | |
|                                                                         \
 | |
|         int matched = 0;                                                \
 | |
|         while (added_list##_iter != NULL) {                             \
 | |
|             if (match_expr) {                                           \
 | |
|                 matched = 1;                                            \
 | |
|                 ++productive;                                           \
 | |
|                 matched_code;                                           \
 | |
|                                                                         \
 | |
|                 if (removed_list##_prev) {                              \
 | |
|                     removed_list##_prev->next = removed_list##_iter->next; \
 | |
|                 } else {                                                \
 | |
|                     *removed_list = removed_list##_iter->next;          \
 | |
|                 }                                                       \
 | |
|                 if (added_list##_prev) {                                \
 | |
|                     added_list##_prev->next = added_list##_iter->next;  \
 | |
|                 } else {                                                \
 | |
|                     *added_list = added_list##_iter->next;              \
 | |
|                 }                                                       \
 | |
|                 free (added_list##_iter);                               \
 | |
|                 break;                                                  \
 | |
|             }                                                           \
 | |
|             added_list##_iter = added_list##_iter->next;                \
 | |
|         }                                                               \
 | |
|         oldptr = removed_list##_iter;                         \
 | |
|         removed_list##_iter = removed_list##_iter->next;                \
 | |
|         if (matched == 0) {                                             \
 | |
|             removed_list##_prev = oldptr;                               \
 | |
|         } else {                                                        \
 | |
|             free (oldptr);                                              \
 | |
|         }                                                               \
 | |
|     }                                                                   \
 | |
|     return (productive > 0);                                            \
 | |
| } G_STMT_END
 | |
| 
 | |
| 
 | |
| #define cb_invoke(cbs, name, udata, ...) \
 | |
|     do { \
 | |
|         if (cbs->name) { \
 | |
|             (cbs->name) (udata, ## __VA_ARGS__); \
 | |
|         } \
 | |
|     } while (0)
 | |
| 
 | |
| /**
 | |
|  * Detect and notify about moves in the watched directory.
 | |
|  *
 | |
|  * A move is what happens when you rename a file in a directory, and
 | |
|  * a new name is unique, i.e. you didn't overwrite any existing files
 | |
|  * with this one.
 | |
|  *
 | |
|  * @param[in,out] removed  A list of the removed files in the directory.
 | |
|  * @param[in,out] added    A list of the added files of the directory.
 | |
|  * @param[in]     cbs      A pointer to #traverse_cbs, a user-defined set of 
 | |
|  *     traverse callbacks.
 | |
|  * @param[in]     udata    A pointer to the user-defined data.
 | |
|  * @return 0 if no files were renamed, >0 otherwise.
 | |
| **/
 | |
| static int
 | |
| dl_detect_moves (dep_list           **removed, 
 | |
|                  dep_list           **added, 
 | |
|                  const traverse_cbs  *cbs, 
 | |
|                  void                *udata)
 | |
| {
 | |
|     assert (cbs != NULL);
 | |
| 
 | |
|      EXCLUDE_SIMILAR
 | |
|         (removed, added,
 | |
|          (removed_iter->inode == added_iter->inode),
 | |
|          {
 | |
|              cb_invoke (cbs, moved, udata,
 | |
|                         removed_iter->path, removed_iter->inode,
 | |
|                         added_iter->path, added_iter->inode);
 | |
|          });
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Detect and notify about replacements in the watched directory.
 | |
|  *
 | |
|  * Consider you are watching a directory foo with the following files
 | |
|  * insinde:
 | |
|  *
 | |
|  *    foo/bar
 | |
|  *    foo/baz
 | |
|  *
 | |
|  * A replacement in a watched directory is what happens when you invoke
 | |
|  *
 | |
|  *    mv /foo/bar /foo/bar
 | |
|  *
 | |
|  * i.e. when you replace a file in a watched directory with another file
 | |
|  * from the same directory.
 | |
|  *
 | |
|  * @param[in,out] removed  A list of the removed files in the directory.
 | |
|  * @param[in,out] current  A list with the current contents of the directory.
 | |
|  * @param[in]     cbs      A pointer to #traverse_cbs, a user-defined set of 
 | |
|  *     traverse callbacks.
 | |
|  * @param[in]     udata    A pointer to the user-defined data.
 | |
|  * @return 0 if no files were renamed, >0 otherwise.
 | |
|  **/
 | |
| static int
 | |
| dl_detect_replacements (dep_list           **removed,
 | |
|                         dep_list           **current,
 | |
|                         const traverse_cbs  *cbs,
 | |
|                         void                *udata)
 | |
| {
 | |
|     assert (cbs != NULL);
 | |
| 
 | |
|     EXCLUDE_SIMILAR
 | |
|         (removed, current,
 | |
|          (removed_iter->inode == current_iter->inode),
 | |
|          {
 | |
|             cb_invoke (cbs, replaced, udata,
 | |
|                         removed_iter->path, removed_iter->inode,
 | |
|                         current_iter->path, current_iter->inode);
 | |
|          });
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Detect and notify about overwrites in the watched directory.
 | |
|  *
 | |
|  * Consider you are watching a directory foo with a file inside:
 | |
|  *
 | |
|  *    foo/bar
 | |
|  *
 | |
|  * And you also have a directory tmp with a file 1:
 | |
|  * 
 | |
|  *    tmp/1
 | |
|  *
 | |
|  * You do not watching directory tmp.
 | |
|  *
 | |
|  * An overwrite in a watched directory is what happens when you invoke
 | |
|  *
 | |
|  *    mv /tmp/1 /foo/bar
 | |
|  *
 | |
|  * i.e. when you overwrite a file in a watched directory with another file
 | |
|  * from the another directory.
 | |
|  *
 | |
|  * @param[in,out] previous A list with the previous contents of the directory.
 | |
|  * @param[in,out] current  A list with the current contents of the directory.
 | |
|  * @param[in]     cbs      A pointer to #traverse_cbs, a user-defined set of 
 | |
|  *     traverse callbacks.
 | |
|  * @param[in]     udata    A pointer to the user-defined data.
 | |
|  * @return 0 if no files were renamed, >0 otherwise.
 | |
|  **/
 | |
| static int
 | |
| dl_detect_overwrites (dep_list           **previous,
 | |
|                       dep_list           **current,
 | |
|                       const traverse_cbs  *cbs,
 | |
|                       void                *udata)
 | |
| {
 | |
|     assert (cbs != NULL);
 | |
| 
 | |
|     EXCLUDE_SIMILAR
 | |
|         (previous, current,
 | |
|          (strcmp (previous_iter->path, current_iter->path) == 0
 | |
|           && previous_iter->inode != current_iter->inode),
 | |
|          {
 | |
|              cb_invoke (cbs, overwritten, udata, current_iter->path, current_iter->inode);
 | |
|          });
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * Traverse a list and invoke a callback for each item.
 | |
|  * 
 | |
|  * @param[in] list  A #dep_list.
 | |
|  * @param[in] cb    A #single_entry_cb callback function.
 | |
|  * @param[in] udata A pointer to the user-defined data.
 | |
|  **/
 | |
| static void 
 | |
| dl_emit_single_cb_on (dep_list        *list,
 | |
|                       single_entry_cb  cb,
 | |
|                       void            *udata)
 | |
| {
 | |
|     while (cb && list != NULL) {
 | |
|         (cb) (udata, list->path, list->inode);
 | |
|         list = list->next;
 | |
|     }
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * Recognize all the changes in the directory, invoke the appropriate callbacks.
 | |
|  *
 | |
|  * This is the core function of directory diffing submodule.
 | |
|  *
 | |
|  * @param[in] before The previous contents of the directory.
 | |
|  * @param[in] after  The current contents of the directory.
 | |
|  * @param[in] cbs    A pointer to user callbacks (#traverse_callbacks).
 | |
|  * @param[in] udata  A pointer to user data.
 | |
|  **/
 | |
| void
 | |
| dl_calculate (dep_list           *before,
 | |
|               dep_list           *after,
 | |
|               const traverse_cbs *cbs,
 | |
|               void               *udata)
 | |
| {
 | |
|     int need_update = 0;
 | |
|     dep_list *was = dl_shallow_copy (before);
 | |
|     dep_list *pre = dl_shallow_copy (before);
 | |
|     dep_list *now = dl_shallow_copy (after);
 | |
|     dep_list *lst = dl_shallow_copy (after);
 | |
| 
 | |
|     assert (cbs != NULL);
 | |
| 
 | |
|     dl_diff (&was, &now); 
 | |
| 
 | |
|     need_update += dl_detect_moves (&was, &now, cbs, udata);
 | |
|     need_update += dl_detect_replacements (&was, &lst, cbs, udata);
 | |
|     dl_detect_overwrites (&pre, &lst, cbs, udata);
 | |
|  
 | |
|     if (need_update) {
 | |
|         cb_invoke (cbs, names_updated, udata);
 | |
|     }
 | |
| 
 | |
|     dl_emit_single_cb_on (was, cbs->removed, udata);
 | |
|     dl_emit_single_cb_on (now, cbs->added, udata);
 | |
| 
 | |
|     cb_invoke (cbs, many_added, udata, now);
 | |
|     cb_invoke (cbs, many_removed, udata, was);
 | |
|     
 | |
|     dl_shallow_free (lst);
 | |
|     dl_shallow_free (now);
 | |
|     dl_shallow_free (pre);
 | |
|     dl_shallow_free (was);
 | |
| }
 |