Skip to content

Commit 648d7bb

Browse files
author
d4ryus
committed
fixes wildcards bug with pathnames
When a tracked file or folder contained a * ? or [ character a error was thrown. This is now fixed by wrapping the pathnames with the escape-wildcards function which escapes the characters. This is not a perfect solution and might still throw a error on other implementations besides sbcl. But its at least fixed on sbcl.
1 parent c346e81 commit 648d7bb

File tree

3 files changed

+122
-82
lines changed

3 files changed

+122
-82
lines changed

cl-fs-watcher.asd

+3-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
:author "d4ryus <[email protected]>"
66
:license "LLGPL"
77
:homepage "https://github.com/d4ryus/cl-fs-watcher"
8-
:depends-on (#:cl-async #:lparallel)
8+
:depends-on (#:cl-async
9+
#:lparallel
10+
#:uiop)
911
:serial t
1012
:components ((:file "package")
1113
(:file "cl-fs-watcher")))

cl-fs-watcher.lisp

+114-81
Original file line numberDiff line numberDiff line change
@@ -180,14 +180,59 @@ and to stop the Watcher and cleanup all its resources use:
180180
for more information. This callback also gets called if a
181181
error occures by calling hook.")))
182182

183+
(defun escape-wildcards (thing &optional (escape-char #\\))
184+
"Got the inspiration for that code from
185+
sb-impl::unparse-physical-piece, credits go to Xach for finding it.
186+
Thanks again for the helping me out"
187+
(let* ((srclen (length thing))
188+
(dstlen srclen))
189+
(dotimes (i srclen)
190+
(let ((char (char thing i)))
191+
(case char
192+
((#\* #\? #\[)
193+
(incf dstlen))
194+
(t (when (char= char escape-char)
195+
(incf dstlen))))))
196+
(let ((result (make-string dstlen))
197+
(dst 0))
198+
(dotimes (src srclen)
199+
(let ((char (char thing src)))
200+
(case char
201+
((#\* #\? #\[)
202+
(setf (char result dst) escape-char)
203+
(incf dst))
204+
(t (when (char= char escape-char)
205+
(setf (char result dst) escape-char)
206+
(incf dst))))
207+
(setf (char result dst) char)
208+
(incf dst)))
209+
result)))
210+
211+
(defun escaped-directory-exists-p (directory)
212+
(uiop:directory-exists-p
213+
(escape-wildcards directory)))
214+
215+
(defun escaped-file-exists-p (file)
216+
(uiop:file-exists-p
217+
(escape-wildcards file)))
218+
219+
(defun escaped-directory-files (directory &rest args)
220+
(apply #'uiop:directory-files
221+
(escape-wildcards directory)
222+
args))
223+
224+
(defun escaped-subdirectories (directory)
225+
(uiop:subdirectories
226+
(escape-wildcards directory)))
227+
183228
(defun get-event-type (filename renamed-p changed-p)
184-
"Will determine the Event-Type by using UIOP:DIRECTORY-EXISTS-P and
185-
UIOP:FILE-EXISTS-P. Will return one of the following types:
186-
:file-added, :file-removed, :file-changed, :directory-added.
187-
Since its not possible to determine :directory-removed and
188-
:on-delete a :file-removed will be returned instead."
189-
(let ((file-exists-p (uiop:file-exists-p filename))
190-
(directory-exists-p (uiop:directory-exists-p filename)))
229+
"Will determine the Event-Type by using ESCAPED-DIRECTORY-EXISTS-P
230+
and ESCAPED-FILE-EXISTS-P. Will return one of the following types:
231+
:file-added, :file-removed, :file-changed, :directory-added. Since
232+
its not possible to determine :directory-removed and :on-delete a
233+
:file-removed will be returned instead."
234+
(let ((file-exists-p (escaped-file-exists-p filename))
235+
(directory-exists-p (escaped-directory-exists-p filename)))
191236
(cond ((and renamed-p
192237
(not changed-p)
193238
file-exists-p
@@ -208,6 +253,14 @@ and to stop the Watcher and cleanup all its resources use:
208253
(not file-exists-p)
209254
directory-exists-p)
210255
:directory-added)
256+
;; i dont know what exactly it means to get a renamed and
257+
;; changed event on a directory, but it happens when 'touch'
258+
;; is run on a directory. Guess we could just ignore it.
259+
((and renamed-p
260+
changed-p
261+
(not file-exists-p)
262+
directory-exists-p)
263+
nil)
211264
;; if we get a change event but the file is already gone
212265
;; ignore it. It should be fine since a file-removed event
213266
;; will follow.
@@ -218,51 +271,49 @@ and to stop the Watcher and cleanup all its resources use:
218271
nil)
219272
(t
220273
(error (format nil
221-
(concatenate 'string
222-
"Could not determine event type in GET-EVENT-TYPE, file: ~a~%"
223-
"(file-exists-p: ~a, directory-exists-p: ~a, renamed-p: ~a, changed-p: ~a)")
274+
"Could not determine event type in GET-EVENT-TYPE, file: ~a~%~
275+
(file-exists-p: ~a, directory-exists-p: ~a, renamed-p: ~a, changed-p: ~a)"
224276
filename
225-
file-exists-p directory-exists-p renamed-p changed-p filename))))))
277+
file-exists-p directory-exists-p renamed-p changed-p))))))
226278

227279
(defun add-dir (watcher dir)
228280
"adds the specified dir to watcher, this function has to be called
229281
from the watcher-thread! See also: ADD-DIRECTORY-TO-WATCH."
230-
(let ((table (directory-handles watcher)))
231-
(multiple-value-bind (value present-p) (gethash dir table)
232-
(declare (ignore value))
233-
(unless present-p
234-
(let ((handle (if (or (recursive-p watcher)
235-
(string= (dir watcher) dir))
236-
;; add a fs-watch if either RECURSIVE-P is true
237-
;; or its the main directory
238-
(as:fs-watch dir
239-
(lambda (h f e s)
240-
(callback watcher h f e s)))
241-
nil)))
242-
(setf (gethash dir table) handle)
243-
;; this is _not_ nice... but there is no way to tell if we
244-
;; attached the handler fast enough. Since the OS could have
245-
;; already put some files inside the folder before we
246-
;; attached the handler. To (somewhat) fix that this will
247-
;; throw :file-created callbacks for each file, already
248-
;; inside the directory. The ugly part is that this will
249-
;; likely create duplicated :file-created events, since
250-
;; files could have been created while the handler was
251-
;; attached, but before this dolist finishes. But at least
252-
;; this will catch all files.
253-
(dolist (sub-file (uiop:directory-files dir))
254-
(callback watcher handle (subseq (format nil "~a" sub-file)
255-
(length (get-handle-path handle)))
256-
t
257-
nil))
258-
;; this makes sure that we dont miss any added directory
259-
;; events. In case ADD-DIR is called with a sub-directoy
260-
;; (from a filesystem event callback) which we already added
261-
;; by iterating over all sub-directories, ADD-DIR will
262-
;; return.
263-
(when (recursive-p watcher)
264-
(dolist (sub-dir (uiop:subdirectories dir))
265-
(add-dir watcher (format nil "~a" sub-dir)))))))))
282+
(when (or (recursive-p watcher)
283+
(string= (dir watcher) dir))
284+
(let ((table (directory-handles watcher)))
285+
(multiple-value-bind (value present-p) (gethash dir table)
286+
(declare (ignore value))
287+
(unless present-p
288+
(let ((handle (as:fs-watch dir
289+
(lambda (h f e s)
290+
(callback watcher h f e s)))))
291+
(setf (gethash dir table) handle)
292+
;; this is _not_ nice... but there is no way to tell if we
293+
;; attached the handler fast enough. Since the OS could have
294+
;; already put some files inside the folder before we
295+
;; attached the handler. To (somewhat) fix that this will
296+
;; throw :file-created callbacks for each file, already
297+
;; inside the directory. The ugly part is that this will
298+
;; likely create duplicated :file-created events, since
299+
;; files could have been created while the handler was
300+
;; attached, but before this dolist finishes. But at least
301+
;; this will catch all files.
302+
(dolist (sub-file (mapcar #'uiop:native-namestring
303+
(escaped-directory-files dir)))
304+
(callback watcher handle (subseq sub-file
305+
(length (get-handle-path handle)))
306+
t
307+
nil))
308+
;; this makes sure that we dont miss any added directory
309+
;; events. In case ADD-DIR is called with a sub-directoy
310+
;; (from a filesystem event callback) which we already added
311+
;; by iterating over all sub-directories, ADD-DIR will
312+
;; return.
313+
(when (recursive-p watcher)
314+
(dolist (sub-dir (mapcar #'uiop:native-namestring
315+
(escaped-subdirectories dir)))
316+
(add-dir watcher sub-dir)))))))))
266317

267318
(defun add-directory-to-watch (watcher dir)
268319
"adds dir to watcher, can be safetly called by any thread, will
@@ -368,7 +419,6 @@ and to stop the Watcher and cleanup all its resources use:
368419
(list fn watcher full-filename event-type)
369420
(slot-value watcher 'hook-queue))))
370421
(when (eql event-type :on-deleted)
371-
(remove-directory-from-watch watcher full-filename)
372422
(stop-watcher watcher))))
373423

374424
(defun watcher-event-loop (watcher)
@@ -377,38 +427,23 @@ and to stop the Watcher and cleanup all its resources use:
377427
only happen if STOP-WATCHER is called or the Main Directory gets
378428
deleted. This thread will get interrupted by
379429
add-directory-to-watch-dir if a new directory is added."
380-
(let ((initial-directories (list))
381-
(root-dir (dir watcher)))
382-
(if (recursive-p watcher)
383-
(uiop:collect-sub*directories (pathname root-dir)
384-
t
385-
t
386-
(lambda (dir) (push (format nil "~a" dir)
387-
initial-directories)))
388-
(progn
389-
(push root-dir initial-directories)
390-
(loop
391-
:for dir :in (uiop:subdirectories root-dir)
392-
:do (push (format nil "~a" dir) initial-directories))))
393-
(as:with-event-loop (:catch-app-errors (error-cb watcher))
394-
(loop
395-
:for dir :in initial-directories
396-
;; we can call add-dir directly here, since we are inside the
397-
;; event-loop thread
398-
:do (add-dir watcher dir))
399-
(setf (slot-value watcher 'alive-p) t))))
430+
(as:with-event-loop (:catch-app-errors (error-cb watcher))
431+
(add-dir watcher (dir watcher))
432+
(setf (slot-value watcher 'alive-p) t)))
400433

401434
;; overwrite constructor and set DIR to a absolute Path, also start
402435
;; the event-loop Thread
403436
(defmethod initialize-instance :after ((watcher watcher) &rest initargs)
404437
;; get fullpath as string and check if something went wrong
438+
(declare (ignorable initargs))
405439
(with-slots (dir) watcher
406-
(let ((fullpath (car (directory dir))))
440+
(let ((fullpath (car (directory (escape-wildcards dir)))))
407441
(if fullpath
408-
(setf dir (format nil "~a" fullpath))
409-
(error "TODO: ERROR: The given Directory does not exist (or is
410-
fishy, no read rights for example). calling DIRECTORY
411-
on it returned NIL.")))))
442+
(setf dir (uiop:native-namestring fullpath))
443+
(error "ERROR: The given Directory does not exist (or cannot~
444+
be opened, no read/execute rights for example). calling~
445+
(DIRECTORY ~a) returned NIL."
446+
dir)))))
412447

413448
(defun hook-thread-main-loop (watcher)
414449
"Main Function of hook-thread. If watcher gets started, the
@@ -480,14 +515,12 @@ and to stop the Watcher and cleanup all its resources use:
480515
(defun get-all-tracked-files (watcher)
481516
"returns all files (excluding directories) which are tracked by the
482517
given watcher"
483-
(mapcar
484-
(lambda (pathname) (format nil "~a" pathname))
485-
(apply #'append
486-
(loop
487-
:for key :being :the :hash-keys :of (directory-handles watcher)
488-
:using (hash-value value)
489-
:if value
490-
:collect (uiop:directory-files key)))))
518+
(apply #'append
519+
(loop
520+
:for key :being :the :hash-keys :of (directory-handles watcher)
521+
:using (hash-value value)
522+
:if value
523+
:collect key)))
491524

492525
(defun busy-p (watcher)
493526
"Returns t if Watcher is 'busy' and there are items on the

package.lisp

+5
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@
1111
#:recursive-p
1212

1313
;; functions
14+
#:escape-wildcards
15+
#:escaped-directory-exists-p
16+
#:escaped-file-exists-p
17+
#:escaped-directory-files
18+
#:escaped-subdirectories
1419
#:set-hook
1520
#:start-watcher
1621
#:stop-watcher

0 commit comments

Comments
 (0)