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
21 *******************************************************************************/
25 #include <stdlib.h> /* calloc */
26 #include <stdio.h> /* printf */
27 #include <dirent.h> /* opendir, readdir, closedir */
28 #include <string.h> /* strcmp */
33 static gboolean kdl_debug_enabled
= FALSE
;
34 #define perror_msg if (kdl_debug_enabled) g_warning
38 * Print a list to stdout.
40 * @param[in] dl A pointer to a list.
43 dl_print (const dep_list
*dl
)
46 printf ("%lld:%s ", (long long int) dl
->inode
, dl
->path
);
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.
61 dep_list
* dl_create (char *path
, ino_t inode
)
63 dep_list
*dl
= calloc (1, sizeof (dep_list
));
65 perror_msg ("Failed to create a new dep-list item");
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.
85 dl_shallow_copy (const dep_list
*dl
)
91 dep_list
*head
= calloc (1, sizeof (dep_list
));
93 perror_msg ("Failed to allocate head during shallow copy");
98 const dep_list
*it
= dl
;
102 cp
->inode
= it
->inode
;
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
);
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.
127 dl_shallow_free (dep_list
*dl
)
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.
145 dl_free (dep_list
*dl
)
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.
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
);
173 while ((ent
= readdir (dir
)) != NULL
) {
174 if (!strcmp (ent
->d_name
, ".") || !strcmp (ent
->d_name
, "..")) {
179 head
= calloc (1, sizeof (dep_list
));
181 perror_msg ("Failed to allocate head during listing");
186 dep_list
*iter
= (prev
== NULL
) ? head
: calloc (1, sizeof (dep_list
));
188 perror_msg ("Failed to allocate a new element during listing");
192 iter
->path
= strdup (ent
->d_name
);
193 if (iter
->path
== NULL
) {
194 perror_msg ("Failed to copy a string during listing");
198 iter
->inode
= ent
->d_ino
;
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.
230 dl_diff (dep_list
**before
, dep_list
**after
)
232 assert (before
!= NULL
);
233 assert (after
!= NULL
);
235 if (*before
== NULL
|| *after
== NULL
) {
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
;
247 while (after_iter
!= NULL
) {
248 if (strcmp (before_iter
->path
, after_iter
->path
) == 0) {
250 /* removing the entry from the both lists */
252 before_prev
->next
= before_iter
->next
;
254 *before
= before_iter
->next
;
258 after_prev
->next
= after_iter
->next
;
260 *after
= after_iter
->next
;
265 after_prev
= after_iter
;
266 after_iter
= after_iter
->next
;
269 dep_list
*oldptr
= before_iter
;
270 before_iter
= before_iter
->next
;
272 before_prev
= 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; \
299 while (added_list##_iter != NULL) { \
305 if (removed_list##_prev) { \
306 removed_list##_prev->next = removed_list##_iter->next; \
308 *removed_list = removed_list##_iter->next; \
310 if (added_list##_prev) { \
311 added_list##_prev->next = added_list##_iter->next; \
313 *added_list = added_list##_iter->next; \
315 free (added_list##_iter); \
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; \
328 return (productive > 0);
331 #define cb_invoke(cbs, name, udata, ...) \
334 (cbs->name) (udata, ## __VA_ARGS__); \
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
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.
353 dl_detect_moves (dep_list
**removed
,
355 const traverse_cbs
*cbs
,
358 assert (cbs
!= NULL
);
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
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.
394 dl_detect_replacements (dep_list
**removed
,
396 const traverse_cbs
*cbs
,
399 assert (cbs
!= NULL
);
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:
418 * And you also have a directory tmp with a file 1:
422 * You do not watching directory tmp.
424 * An overwrite in a watched directory is what happens when you invoke
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.
439 dl_detect_overwrites (dep_list
**previous
,
441 const traverse_cbs
*cbs
,
444 assert (cbs
!= NULL
);
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.
464 dl_emit_single_cb_on (dep_list
*list
,
468 while (cb
&& list
!= NULL
) {
469 (cb
) (udata
, list
->path
, list
->inode
);
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.
486 dl_calculate (dep_list
*before
,
488 const traverse_cbs
*cbs
,
491 assert (cbs
!= NULL
);
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
);
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
);