Add myself to the DOAP for GLib
[glib.git] / gio / kqueue / dep-list.c
blobaf0010c7273d9a06fc5d1392e2e8c682dd2bf44d
1 /*******************************************************************************
2 Copyright (c) 2011, 2012 Dmitry Matveev <me@dmitrymatveev.co.uk>
4 Permission is hereby granted, free of charge, to any person obtaining a copy
5 of this software and associated documentation files (the "Software"), to deal
6 in the Software without restriction, including without limitation the rights
7 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 copies of the Software, and to permit persons to whom the Software is
9 furnished to do so, subject to the following conditions:
11 The above copyright notice and this permission notice shall be included in
12 all copies or substantial portions of the Software.
14 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 THE SOFTWARE.
21 *******************************************************************************/
23 #include <glib.h>
25 #include <stdlib.h> /* calloc */
26 #include <stdio.h> /* printf */
27 #include <dirent.h> /* opendir, readdir, closedir */
28 #include <string.h> /* strcmp */
29 #include <assert.h>
31 #include "dep-list.h"
33 static gboolean kdl_debug_enabled = FALSE;
34 #define perror_msg if (kdl_debug_enabled) g_warning
37 /**
38 * Print a list to stdout.
40 * @param[in] dl A pointer to a list.
41 **/
42 void
43 dl_print (const dep_list *dl)
45 while (dl != NULL) {
46 printf ("%lld:%s ", (long long int) dl->inode, dl->path);
47 dl = dl->next;
49 printf ("\n");
52 /**
53 * Create a new list item.
55 * Create a new list item and initialize its fields.
57 * @param[in] path A name of a file (the string is not copied!).
58 * @param[in] inode A file's inode number.
59 * @return A pointer to a new item or NULL in the case of error.
60 **/
61 dep_list* dl_create (char *path, ino_t inode)
63 dep_list *dl = calloc (1, sizeof (dep_list));
64 if (dl == NULL) {
65 perror_msg ("Failed to create a new dep-list item");
66 return NULL;
69 dl->path = path;
70 dl->inode = inode;
71 return dl;
74 /**
75 * Create a shallow copy of a list.
77 * A shallow copy is a copy of a structure, but not the copy of the
78 * contents. All data pointers ('path' in our case) of a list and its
79 * shallow copy will point to the same memory.
81 * @param[in] dl A pointer to list to make a copy. May be NULL.
82 * @return A shallow copy of the list.
83 **/
84 dep_list*
85 dl_shallow_copy (const dep_list *dl)
87 if (dl == NULL) {
88 return NULL;
91 dep_list *head = calloc (1, sizeof (dep_list));
92 if (head == NULL) {
93 perror_msg ("Failed to allocate head during shallow copy");
94 return NULL;
97 dep_list *cp = head;
98 const dep_list *it = dl;
100 while (it != NULL) {
101 cp->path = it->path;
102 cp->inode = it->inode;
103 if (it->next) {
104 cp->next = calloc (1, sizeof (dep_list));
105 if (cp->next == NULL) {
106 perror_msg ("Failed to allocate a new element during shallow copy");
107 dl_shallow_free (head);
108 return NULL;
110 cp = cp->next;
112 it = it->next;
115 return head;
119 * Free the memory allocated for shallow copy.
121 * This function will free the memory used by a list structure, but
122 * the list data will remain in the heap.
124 * @param[in] dl A pointer to a list. May be NULL.
126 void
127 dl_shallow_free (dep_list *dl)
129 while (dl != NULL) {
130 dep_list *ptr = dl;
131 dl = dl->next;
132 free (ptr);
137 * Free the memory allocated for a list.
139 * This function will free all the memory used by a list: both
140 * list structure and the list data.
142 * @param[in] dl A pointer to a list. May be NULL.
144 void
145 dl_free (dep_list *dl)
147 while (dl != NULL) {
148 dep_list *ptr = dl;
149 dl = dl->next;
151 free (ptr->path);
152 free (ptr);
157 * Create a directory listing and return it as a list.
159 * @param[in] path A path to a directory.
160 * @return A pointer to a list. May return NULL, check errno in this case.
162 dep_list*
163 dl_listing (const char *path)
165 assert (path != NULL);
167 dep_list *head = NULL;
168 dep_list *prev = NULL;
169 DIR *dir = opendir (path);
170 if (dir != NULL) {
171 struct dirent *ent;
173 while ((ent = readdir (dir)) != NULL) {
174 if (!strcmp (ent->d_name, ".") || !strcmp (ent->d_name, "..")) {
175 continue;
178 if (head == NULL) {
179 head = calloc (1, sizeof (dep_list));
180 if (head == NULL) {
181 perror_msg ("Failed to allocate head during listing");
182 goto error;
186 dep_list *iter = (prev == NULL) ? head : calloc (1, sizeof (dep_list));
187 if (iter == NULL) {
188 perror_msg ("Failed to allocate a new element during listing");
189 goto error;
192 iter->path = strdup (ent->d_name);
193 if (iter->path == NULL) {
194 perror_msg ("Failed to copy a string during listing");
195 goto error;
198 iter->inode = ent->d_ino;
199 iter->next = NULL;
200 if (prev) {
201 prev->next = iter;
203 prev = iter;
206 closedir (dir);
208 return head;
210 error:
211 if (dir != NULL) {
212 closedir (dir);
214 dl_free (head);
215 return NULL;
219 * Perform a diff on lists.
221 * This function performs something like a set intersection. The same items
222 * will be removed from the both lists. Items are comapred by a filename.
224 * @param[in,out] before A pointer to a pointer to a list. Will contain items
225 * which were not found in the 'after' list.
226 * @param[in,out] after A pointer to a pointer to a list. Will containt items
227 * which were not found in the 'before' list.
229 void
230 dl_diff (dep_list **before, dep_list **after)
232 assert (before != NULL);
233 assert (after != NULL);
235 if (*before == NULL || *after == NULL) {
236 return;
239 dep_list *before_iter = *before;
240 dep_list *before_prev = NULL;
242 while (before_iter != NULL) {
243 dep_list *after_iter = *after;
244 dep_list *after_prev = NULL;
246 int matched = 0;
247 while (after_iter != NULL) {
248 if (strcmp (before_iter->path, after_iter->path) == 0) {
249 matched = 1;
250 /* removing the entry from the both lists */
251 if (before_prev) {
252 before_prev->next = before_iter->next;
253 } else {
254 *before = before_iter->next;
257 if (after_prev) {
258 after_prev->next = after_iter->next;
259 } else {
260 *after = after_iter->next;
262 free (after_iter);
263 break;
265 after_prev = after_iter;
266 after_iter = after_iter->next;
269 dep_list *oldptr = before_iter;
270 before_iter = before_iter->next;
271 if (matched == 0) {
272 before_prev = oldptr;
273 } else {
274 free (oldptr);
281 * Traverses two lists. Compares items with a supplied expression
282 * and performs the passed code on a match. Removes the matched entries
283 * from the both lists.
285 #define EXCLUDE_SIMILAR(removed_list, added_list, match_expr, matched_code) \
286 assert (removed_list != NULL); \
287 assert (added_list != NULL); \
289 dep_list *removed_list##_iter = *removed_list; \
290 dep_list *removed_list##_prev = NULL; \
292 int productive = 0; \
294 while (removed_list##_iter != NULL) { \
295 dep_list *added_list##_iter = *added_list; \
296 dep_list *added_list##_prev = NULL; \
298 int matched = 0; \
299 while (added_list##_iter != NULL) { \
300 if (match_expr) { \
301 matched = 1; \
302 ++productive; \
303 matched_code; \
305 if (removed_list##_prev) { \
306 removed_list##_prev->next = removed_list##_iter->next; \
307 } else { \
308 *removed_list = removed_list##_iter->next; \
310 if (added_list##_prev) { \
311 added_list##_prev->next = added_list##_iter->next; \
312 } else { \
313 *added_list = added_list##_iter->next; \
315 free (added_list##_iter); \
316 break; \
318 added_list##_iter = added_list##_iter->next; \
320 dep_list *oldptr = removed_list##_iter; \
321 removed_list##_iter = removed_list##_iter->next; \
322 if (matched == 0) { \
323 removed_list##_prev = oldptr; \
324 } else { \
325 free (oldptr); \
328 return (productive > 0);
331 #define cb_invoke(cbs, name, udata, ...) \
332 do { \
333 if (cbs->name) { \
334 (cbs->name) (udata, ## __VA_ARGS__); \
336 } while (0)
339 * Detect and notify about moves in the watched directory.
341 * A move is what happens when you rename a file in a directory, and
342 * a new name is unique, i.e. you didnt overwrite any existing files
343 * with this one.
345 * @param[in,out] removed A list of the removed files in the directory.
346 * @param[in,out] added A list of the added files of the directory.
347 * @param[in] cbs A pointer to #traverse_cbs, an user-defined set of
348 * traverse callbacks.
349 * @param[in] udata A pointer to the user-defined data.
350 * @return 0 if no files were renamed, >0 otherwise.
352 static int
353 dl_detect_moves (dep_list **removed,
354 dep_list **added,
355 const traverse_cbs *cbs,
356 void *udata)
358 assert (cbs != NULL);
360 EXCLUDE_SIMILAR
361 (removed, added,
362 (removed_iter->inode == added_iter->inode),
364 cb_invoke (cbs, moved, udata,
365 removed_iter->path, removed_iter->inode,
366 added_iter->path, added_iter->inode);
371 * Detect and notify about replacements in the watched directory.
373 * Consider you are watching a directory foo with the folloing files
374 * insinde:
376 * foo/bar
377 * foo/baz
379 * A replacement in a watched directory is what happens when you invoke
381 * mv /foo/bar /foo/bar
383 * i.e. when you replace a file in a watched directory with another file
384 * from the same directory.
386 * @param[in,out] removed A list of the removed files in the directory.
387 * @param[in,out] current A list with the current contents of the directory.
388 * @param[in] cbs A pointer to #traverse_cbs, an user-defined set of
389 * traverse callbacks.
390 * @param[in] udata A pointer to the user-defined data.
391 * @return 0 if no files were renamed, >0 otherwise.
393 static int
394 dl_detect_replacements (dep_list **removed,
395 dep_list **current,
396 const traverse_cbs *cbs,
397 void *udata)
399 assert (cbs != NULL);
401 EXCLUDE_SIMILAR
402 (removed, current,
403 (removed_iter->inode == current_iter->inode),
405 cb_invoke (cbs, replaced, udata,
406 removed_iter->path, removed_iter->inode,
407 current_iter->path, current_iter->inode);
412 * Detect and notify about overwrites in the watched directory.
414 * Consider you are watching a directory foo with a file inside:
416 * foo/bar
418 * And you also have a directory tmp with a file 1:
420 * tmp/1
422 * You do not watching directory tmp.
424 * An overwrite in a watched directory is what happens when you invoke
426 * mv /tmp/1 /foo/bar
428 * i.e. when you overwrite a file in a watched directory with another file
429 * from the another directory.
431 * @param[in,out] previous A list with the previous contents of the directory.
432 * @param[in,out] current A list with the current contents of the directory.
433 * @param[in] cbs A pointer to #traverse_cbs, an user-defined set of
434 * traverse callbacks.
435 * @param[in] udata A pointer to the user-defined data.
436 * @return 0 if no files were renamed, >0 otherwise.
438 static int
439 dl_detect_overwrites (dep_list **previous,
440 dep_list **current,
441 const traverse_cbs *cbs,
442 void *udata)
444 assert (cbs != NULL);
446 EXCLUDE_SIMILAR
447 (previous, current,
448 (strcmp (previous_iter->path, current_iter->path) == 0
449 && previous_iter->inode != current_iter->inode),
451 cb_invoke (cbs, overwritten, udata, current_iter->path, current_iter->inode);
457 * Traverse a list and invoke a callback for each item.
459 * @param[in] list A #dep_list.
460 * @param[in] cb A #single_entry_cb callback function.
461 * @param[in] udata A pointer to the user-defined data.
463 static void
464 dl_emit_single_cb_on (dep_list *list,
465 single_entry_cb cb,
466 void *udata)
468 while (cb && list != NULL) {
469 (cb) (udata, list->path, list->inode);
470 list = list->next;
476 * Recognize all the changes in the directory, invoke the appropriate callbacks.
478 * This is the core function of directory diffing submodule.
480 * @param[in] before The previous contents of the directory.
481 * @param[in] after The current contents of the directory.
482 * @param[in] cbs A pointer to user callbacks (#traverse_callbacks).
483 * @param[in] udata A pointer to user data.
485 void
486 dl_calculate (dep_list *before,
487 dep_list *after,
488 const traverse_cbs *cbs,
489 void *udata)
491 assert (cbs != NULL);
493 int need_update = 0;
495 dep_list *was = dl_shallow_copy (before);
496 dep_list *pre = dl_shallow_copy (before);
497 dep_list *now = dl_shallow_copy (after);
498 dep_list *lst = dl_shallow_copy (after);
500 dl_diff (&was, &now);
502 need_update += dl_detect_moves (&was, &now, cbs, udata);
503 need_update += dl_detect_replacements (&was, &lst, cbs, udata);
504 dl_detect_overwrites (&pre, &lst, cbs, udata);
506 if (need_update) {
507 cb_invoke (cbs, names_updated, udata);
510 dl_emit_single_cb_on (was, cbs->removed, udata);
511 dl_emit_single_cb_on (now, cbs->added, udata);
513 cb_invoke (cbs, many_added, udata, now);
514 cb_invoke (cbs, many_removed, udata, was);
516 dl_shallow_free (lst);
517 dl_shallow_free (now);
518 dl_shallow_free (pre);
519 dl_shallow_free (was);