1 /* GIO - GLib Input, Output and Streaming Library
3 * Copyright (C) 2006-2007 Red Hat, Inc.
4 * Copyright (C) 2015 Chun-wei Fan
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
11 * This 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 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General
17 * Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
19 * Author: Vlad Grecescu <b100dian@gmail.com>
20 * Author: Chun-wei Fan <fanc999@yahoo.com.tw>
26 #include "gwin32fsmonitorutils.h"
27 #include "gio/gfile.h"
31 #define MAX_PATH_LONG 32767 /* Support Paths longer than MAX_PATH (260) characters */
34 g_win32_fs_monitor_handle_event (GWin32FSMonitorPrivate
*monitor
,
35 const gchar
*filename
,
36 PFILE_NOTIFY_INFORMATION pfni
)
38 GFileMonitorEvent fme
;
39 PFILE_NOTIFY_INFORMATION pfni_next
;
40 WIN32_FILE_ATTRIBUTE_DATA attrib_data
= {0, };
41 gchar
*renamed_file
= NULL
;
45 case FILE_ACTION_ADDED
:
46 fme
= G_FILE_MONITOR_EVENT_CREATED
;
49 case FILE_ACTION_REMOVED
:
50 fme
= G_FILE_MONITOR_EVENT_DELETED
;
53 case FILE_ACTION_MODIFIED
:
55 gboolean success_attribs
= GetFileAttributesExW (monitor
->wfullpath_with_long_prefix
,
56 GetFileExInfoStandard
,
59 if (monitor
->file_attribs
!= INVALID_FILE_ATTRIBUTES
&&
61 attrib_data
.dwFileAttributes
!= monitor
->file_attribs
)
62 fme
= G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED
;
64 fme
= G_FILE_MONITOR_EVENT_CHANGED
;
66 monitor
->file_attribs
= attrib_data
.dwFileAttributes
;
70 case FILE_ACTION_RENAMED_OLD_NAME
:
71 if (pfni
->NextEntryOffset
!= 0)
73 /* If the file was renamed in the same directory, we would get a
74 * FILE_ACTION_RENAMED_NEW_NAME action in the next FILE_NOTIFY_INFORMATION
77 glong file_name_len
= 0;
79 pfni_next
= (PFILE_NOTIFY_INFORMATION
) ((BYTE
*)pfni
+ pfni
->NextEntryOffset
);
80 renamed_file
= g_utf16_to_utf8 (pfni_next
->FileName
, pfni_next
->FileNameLength
/ sizeof(WCHAR
), NULL
, &file_name_len
, NULL
);
81 if (pfni_next
->Action
== FILE_ACTION_RENAMED_NEW_NAME
)
82 fme
= G_FILE_MONITOR_EVENT_RENAMED
;
84 fme
= G_FILE_MONITOR_EVENT_MOVED_OUT
;
87 fme
= G_FILE_MONITOR_EVENT_MOVED_OUT
;
90 case FILE_ACTION_RENAMED_NEW_NAME
:
91 if (monitor
->pfni_prev
!= NULL
&&
92 monitor
->pfni_prev
->Action
== FILE_ACTION_RENAMED_OLD_NAME
)
94 /* don't bother sending events, was already sent (rename) */
98 fme
= G_FILE_MONITOR_EVENT_MOVED_IN
;
102 /* The possible Windows actions are all above, so shouldn't get here */
103 g_assert_not_reached ();
108 return g_file_monitor_source_handle_event (monitor
->fms
,
113 g_get_monotonic_time ());
120 g_win32_fs_monitor_callback (DWORD error
,
122 LPOVERLAPPED lpOverlapped
)
125 PFILE_NOTIFY_INFORMATION pfile_notify_walker
;
126 GWin32FSMonitorPrivate
*monitor
= (GWin32FSMonitorPrivate
*) lpOverlapped
;
128 DWORD notify_filter
= monitor
->isfile
?
129 (FILE_NOTIFY_CHANGE_FILE_NAME
|
130 FILE_NOTIFY_CHANGE_ATTRIBUTES
|
131 FILE_NOTIFY_CHANGE_SIZE
) :
132 (FILE_NOTIFY_CHANGE_FILE_NAME
|
133 FILE_NOTIFY_CHANGE_DIR_NAME
|
134 FILE_NOTIFY_CHANGE_ATTRIBUTES
|
135 FILE_NOTIFY_CHANGE_SIZE
);
137 /* If monitor->self is NULL the GWin32FileMonitor object has been destroyed. */
138 if (monitor
->self
== NULL
||
139 g_file_monitor_is_cancelled (monitor
->self
) ||
140 monitor
->file_notify_buffer
== NULL
)
142 g_free (monitor
->file_notify_buffer
);
151 pfile_notify_walker
= (PFILE_NOTIFY_INFORMATION
)((BYTE
*)monitor
->file_notify_buffer
+ offset
);
152 if (pfile_notify_walker
->Action
> 0)
157 changed_file
= g_utf16_to_utf8 (pfile_notify_walker
->FileName
,
158 pfile_notify_walker
->FileNameLength
/ sizeof(WCHAR
),
159 NULL
, &file_name_len
, NULL
);
163 gint long_filename_length
= wcslen (monitor
->wfilename_long
);
164 gint short_filename_length
= wcslen (monitor
->wfilename_short
);
165 enum GWin32FileMonitorFileAlias alias_state
;
167 /* If monitoring a file, check that the changed file
168 * in the directory matches the file that is to be monitored
169 * We need to check both the long and short file names for the same file.
171 * We need to send in the name of the monitored file, not its long (or short) variant,
175 if (_wcsnicmp (pfile_notify_walker
->FileName
,
176 monitor
->wfilename_long
,
177 long_filename_length
) == 0)
179 if (_wcsnicmp (pfile_notify_walker
->FileName
,
180 monitor
->wfilename_short
,
181 short_filename_length
) == 0)
183 alias_state
= G_WIN32_FILE_MONITOR_NO_ALIAS
;
186 alias_state
= G_WIN32_FILE_MONITOR_LONG_FILENAME
;
188 else if (_wcsnicmp (pfile_notify_walker
->FileName
,
189 monitor
->wfilename_short
,
190 short_filename_length
) == 0)
192 alias_state
= G_WIN32_FILE_MONITOR_SHORT_FILENAME
;
195 alias_state
= G_WIN32_FILE_MONITOR_NO_MATCH_FOUND
;
197 if (alias_state
!= G_WIN32_FILE_MONITOR_NO_MATCH_FOUND
)
199 wchar_t *monitored_file_w
;
200 gchar
*monitored_file
;
204 case G_WIN32_FILE_MONITOR_NO_ALIAS
:
205 monitored_file
= g_strdup (changed_file
);
207 case G_WIN32_FILE_MONITOR_LONG_FILENAME
:
208 case G_WIN32_FILE_MONITOR_SHORT_FILENAME
:
209 monitored_file_w
= wcsrchr (monitor
->wfullpath_with_long_prefix
, L
'\\');
210 monitored_file
= g_utf16_to_utf8 (monitored_file_w
+ 1, -1, NULL
, NULL
, NULL
);
213 g_assert_not_reached ();
217 g_win32_fs_monitor_handle_event (monitor
, monitored_file
, pfile_notify_walker
);
218 g_free (monitored_file
);
222 g_win32_fs_monitor_handle_event (monitor
, changed_file
, pfile_notify_walker
);
224 g_free (changed_file
);
227 monitor
->pfni_prev
= pfile_notify_walker
;
228 offset
+= pfile_notify_walker
->NextEntryOffset
;
230 while (pfile_notify_walker
->NextEntryOffset
);
232 ReadDirectoryChangesW (monitor
->hDirectory
,
233 monitor
->file_notify_buffer
,
234 monitor
->buffer_allocated_bytes
,
237 &monitor
->buffer_filled_bytes
,
238 &monitor
->overlapped
,
239 g_win32_fs_monitor_callback
);
243 g_win32_fs_monitor_init (GWin32FSMonitorPrivate
*monitor
,
244 const gchar
*dirname
,
245 const gchar
*filename
,
248 wchar_t *wdirname_with_long_prefix
= NULL
;
249 const gchar LONGPFX
[] = "\\\\?\\";
250 gchar
*fullpath_with_long_prefix
, *dirname_with_long_prefix
;
251 DWORD notify_filter
= isfile
?
252 (FILE_NOTIFY_CHANGE_FILE_NAME
|
253 FILE_NOTIFY_CHANGE_ATTRIBUTES
|
254 FILE_NOTIFY_CHANGE_SIZE
) :
255 (FILE_NOTIFY_CHANGE_FILE_NAME
|
256 FILE_NOTIFY_CHANGE_DIR_NAME
|
257 FILE_NOTIFY_CHANGE_ATTRIBUTES
|
258 FILE_NOTIFY_CHANGE_SIZE
);
260 gboolean success_attribs
;
261 WIN32_FILE_ATTRIBUTE_DATA attrib_data
= {0, };
266 dirname_with_long_prefix
= g_strconcat (LONGPFX
, dirname
, NULL
);
267 wdirname_with_long_prefix
= g_utf8_to_utf16 (dirname_with_long_prefix
, -1, NULL
, NULL
, NULL
);
272 wchar_t wlongname
[MAX_PATH_LONG
];
273 wchar_t wshortname
[MAX_PATH_LONG
];
274 wchar_t *wfullpath
, *wbasename_long
, *wbasename_short
;
276 fullpath
= g_build_filename (dirname
, filename
, NULL
);
277 fullpath_with_long_prefix
= g_strconcat (LONGPFX
, fullpath
, NULL
);
279 wfullpath
= g_utf8_to_utf16 (fullpath
, -1, NULL
, NULL
, NULL
);
281 monitor
->wfullpath_with_long_prefix
=
282 g_utf8_to_utf16 (fullpath_with_long_prefix
, -1, NULL
, NULL
, NULL
);
284 /* ReadDirectoryChangesW() can return the normal filename or the
285 * "8.3" format filename, so we need to keep track of both these names
286 * so that we can check against them later when it returns
288 if (GetLongPathNameW (monitor
->wfullpath_with_long_prefix
, wlongname
, MAX_PATH_LONG
) == 0)
290 wbasename_long
= wcsrchr (monitor
->wfullpath_with_long_prefix
, L
'\\');
291 monitor
->wfilename_long
= wbasename_long
!= NULL
?
292 wcsdup (wbasename_long
+ 1) :
297 wbasename_long
= wcsrchr (wlongname
, L
'\\');
298 monitor
->wfilename_long
= wbasename_long
!= NULL
?
299 wcsdup (wbasename_long
+ 1) :
304 if (GetShortPathNameW (monitor
->wfullpath_with_long_prefix
, wshortname
, MAX_PATH_LONG
) == 0)
306 wbasename_short
= wcsrchr (monitor
->wfullpath_with_long_prefix
, L
'\\');
307 monitor
->wfilename_short
= wbasename_short
!= NULL
?
308 wcsdup (wbasename_short
+ 1) :
313 wbasename_short
= wcsrchr (wshortname
, L
'\\');
314 monitor
->wfilename_short
= wbasename_short
!= NULL
?
315 wcsdup (wbasename_short
+ 1) :
323 monitor
->wfilename_short
= NULL
;
324 monitor
->wfilename_long
= NULL
;
325 monitor
->wfullpath_with_long_prefix
= g_utf8_to_utf16 (dirname_with_long_prefix
, -1, NULL
, NULL
, NULL
);
328 monitor
->isfile
= isfile
;
332 dirname_with_long_prefix
= g_strconcat (LONGPFX
, filename
, NULL
);
333 monitor
->wfullpath_with_long_prefix
= g_utf8_to_utf16 (dirname_with_long_prefix
, -1, NULL
, NULL
, NULL
);
334 monitor
->wfilename_long
= NULL
;
335 monitor
->wfilename_short
= NULL
;
336 monitor
->isfile
= FALSE
;
339 success_attribs
= GetFileAttributesExW (monitor
->wfullpath_with_long_prefix
,
340 GetFileExInfoStandard
,
343 monitor
->file_attribs
= attrib_data
.dwFileAttributes
; /* Store up original attributes */
345 monitor
->file_attribs
= INVALID_FILE_ATTRIBUTES
;
346 monitor
->pfni_prev
= NULL
;
347 monitor
->hDirectory
= CreateFileW (wdirname_with_long_prefix
!= NULL
? wdirname_with_long_prefix
: monitor
->wfullpath_with_long_prefix
,
348 FILE_GENERIC_READ
| FILE_GENERIC_WRITE
,
349 FILE_SHARE_DELETE
| FILE_SHARE_READ
| FILE_SHARE_WRITE
,
352 FILE_FLAG_BACKUP_SEMANTICS
| FILE_FLAG_OVERLAPPED
,
355 g_free (wdirname_with_long_prefix
);
356 g_free (dirname_with_long_prefix
);
358 if (monitor
->hDirectory
!= INVALID_HANDLE_VALUE
)
360 ReadDirectoryChangesW (monitor
->hDirectory
,
361 monitor
->file_notify_buffer
,
362 monitor
->buffer_allocated_bytes
,
365 &monitor
->buffer_filled_bytes
,
366 &monitor
->overlapped
,
367 g_win32_fs_monitor_callback
);
371 GWin32FSMonitorPrivate
*
372 g_win32_fs_monitor_create (gboolean isfile
)
374 GWin32FSMonitorPrivate
*monitor
= g_new0 (GWin32FSMonitorPrivate
, 1);
376 monitor
->buffer_allocated_bytes
= 32784;
377 monitor
->file_notify_buffer
= g_new0 (FILE_NOTIFY_INFORMATION
, monitor
->buffer_allocated_bytes
);
383 g_win32_fs_monitor_finalize (GWin32FSMonitorPrivate
*monitor
)
385 g_free (monitor
->wfullpath_with_long_prefix
);
386 g_free (monitor
->wfilename_long
);
387 g_free (monitor
->wfilename_short
);
389 if (monitor
->hDirectory
== INVALID_HANDLE_VALUE
)
391 /* If we don't have a directory handle we can free
392 * monitor->file_notify_buffer and monitor here. The
393 * callback won't be called obviously any more (and presumably
394 * never has been called).
396 g_free (monitor
->file_notify_buffer
);
397 monitor
->file_notify_buffer
= NULL
;
402 /* If we have a directory handle, the OVERLAPPED struct is
403 * passed once more to the callback as a result of the
404 * CloseHandle() done in the cancel method, so monitor has to
405 * be kept around. The GWin32DirectoryMonitor object is
406 * disappearing, so can't leave a pointer to it in
409 monitor
->self
= NULL
;
414 g_win32_fs_monitor_close_handle (GWin32FSMonitorPrivate
*monitor
)
416 /* This triggers a last callback() with nBytes==0. */
418 /* Actually I am not so sure about that, it seems to trigger a last
419 * callback allright, but the way to recognize that it is the final
420 * one is not to check for nBytes==0, I think that was a
423 if (monitor
->hDirectory
!= INVALID_HANDLE_VALUE
)
424 CloseHandle (monitor
->hDirectory
);