| 1 |
module fgof_watch |
| 2 |
use, intrinsic :: iso_c_binding, only : c_associated, c_char, c_f_pointer, c_int, c_null_char, c_null_ptr, c_ptr, c_size_t |
| 3 |
use fgof_watch_types, only : & |
| 4 |
FGOF_WATCH_ERR_NONE, & |
| 5 |
FGOF_WATCH_ERR_SNAPSHOT_FAILED, & |
| 6 |
FGOF_WATCH_EVT_CREATED, & |
| 7 |
FGOF_WATCH_EVT_MODIFIED, & |
| 8 |
FGOF_WATCH_EVT_MOVED, & |
| 9 |
FGOF_WATCH_EVT_NONE, & |
| 10 |
FGOF_WATCH_EVT_REMOVED, & |
| 11 |
watch_entry, & |
| 12 |
watch_event, & |
| 13 |
watch_options, & |
| 14 |
watch_session |
| 15 |
implicit none |
| 16 |
private |
| 17 |
|
| 18 |
public :: init_watch |
| 19 |
public :: poll_watch |
| 20 |
public :: reset_watch |
| 21 |
public :: clear_ignore_prefixes |
| 22 |
public :: set_ignore_prefixes |
| 23 |
|
| 24 |
interface |
| 25 |
integer(c_int) function fgof_watch_collect_snapshot_c(root, recursive, ignore_hidden, prefix_count, prefix_stride, prefixes, buffer, buffer_len) bind(C, name="fgof_watch_collect_snapshot") |
| 26 |
import :: c_char, c_int, c_ptr, c_size_t |
| 27 |
character(kind=c_char), intent(in) :: root(*) |
| 28 |
integer(c_int), value :: recursive |
| 29 |
integer(c_int), value :: ignore_hidden |
| 30 |
integer(c_int), value :: prefix_count |
| 31 |
integer(c_int), value :: prefix_stride |
| 32 |
character(kind=c_char), intent(in) :: prefixes(*) |
| 33 |
type(c_ptr), intent(out) :: buffer |
| 34 |
integer(c_size_t), intent(out) :: buffer_len |
| 35 |
end function fgof_watch_collect_snapshot_c |
| 36 |
|
| 37 |
subroutine fgof_watch_free_buffer_c(buffer) bind(C, name="fgof_watch_free_buffer") |
| 38 |
import :: c_ptr |
| 39 |
type(c_ptr), value :: buffer |
| 40 |
end subroutine fgof_watch_free_buffer_c |
| 41 |
end interface |
| 42 |
|
| 43 |
contains |
| 44 |
|
| 45 |
subroutine init_watch(session, root, options) |
| 46 |
type(watch_session), intent(out) :: session |
| 47 |
character(len=*), intent(in) :: root |
| 48 |
type(watch_options), intent(in), optional :: options |
| 49 |
integer :: snapshot_status |
| 50 |
character(len=:), allocatable :: snapshot_message |
| 51 |
|
| 52 |
if (present(options)) then |
| 53 |
session%options = options |
| 54 |
end if |
| 55 |
|
| 56 |
session%root = root |
| 57 |
session%active = (len(root) > 0) |
| 58 |
|
| 59 |
if (.not. session%active) then |
| 60 |
allocate(session%entries(0)) |
| 61 |
allocate(session%pending_events(0)) |
| 62 |
allocate(session%pending_remaining(0)) |
| 63 |
call clear_watch_error(session) |
| 64 |
return |
| 65 |
end if |
| 66 |
|
| 67 |
call collect_snapshot(root, session%options, session%entries, snapshot_status, snapshot_message) |
| 68 |
allocate(session%pending_events(0)) |
| 69 |
allocate(session%pending_remaining(0)) |
| 70 |
if (snapshot_status /= 0) then |
| 71 |
call set_watch_error(session, FGOF_WATCH_ERR_SNAPSHOT_FAILED, snapshot_message) |
| 72 |
deallocate(session%entries) |
| 73 |
allocate(session%entries(0)) |
| 74 |
else |
| 75 |
call clear_watch_error(session) |
| 76 |
end if |
| 77 |
end subroutine init_watch |
| 78 |
|
| 79 |
function poll_watch(session) result(events) |
| 80 |
type(watch_session), intent(inout) :: session |
| 81 |
type(watch_event), allocatable :: events(:) |
| 82 |
type(watch_entry), allocatable :: current_entries(:) |
| 83 |
integer :: snapshot_status |
| 84 |
character(len=:), allocatable :: snapshot_message |
| 85 |
|
| 86 |
if (.not. session%active) then |
| 87 |
allocate(events(0)) |
| 88 |
return |
| 89 |
end if |
| 90 |
|
| 91 |
call collect_snapshot(session%root, session%options, current_entries, snapshot_status, snapshot_message) |
| 92 |
if (snapshot_status /= 0) then |
| 93 |
call set_watch_error(session, FGOF_WATCH_ERR_SNAPSHOT_FAILED, snapshot_message) |
| 94 |
allocate(events(0)) |
| 95 |
return |
| 96 |
end if |
| 97 |
|
| 98 |
call clear_watch_error(session) |
| 99 |
events = diff_snapshots(session%entries, current_entries, session%options) |
| 100 |
if (session%options%debounce_polls > 0) then |
| 101 |
events = debounce_event_batch(session, events) |
| 102 |
end if |
| 103 |
call move_alloc(current_entries, session%entries) |
| 104 |
end function poll_watch |
| 105 |
|
| 106 |
subroutine reset_watch(session) |
| 107 |
type(watch_session), intent(inout) :: session |
| 108 |
|
| 109 |
if (allocated(session%root)) then |
| 110 |
deallocate(session%root) |
| 111 |
end if |
| 112 |
|
| 113 |
if (allocated(session%entries)) then |
| 114 |
deallocate(session%entries) |
| 115 |
end if |
| 116 |
|
| 117 |
if (allocated(session%pending_events)) then |
| 118 |
deallocate(session%pending_events) |
| 119 |
end if |
| 120 |
|
| 121 |
if (allocated(session%pending_remaining)) then |
| 122 |
deallocate(session%pending_remaining) |
| 123 |
end if |
| 124 |
|
| 125 |
session%options = watch_options() |
| 126 |
session%active = .false. |
| 127 |
call clear_watch_error(session) |
| 128 |
end subroutine reset_watch |
| 129 |
|
| 130 |
subroutine clear_watch_error(session) |
| 131 |
type(watch_session), intent(inout) :: session |
| 132 |
|
| 133 |
session%last_error_code = FGOF_WATCH_ERR_NONE |
| 134 |
if (allocated(session%last_error_message)) then |
| 135 |
deallocate(session%last_error_message) |
| 136 |
end if |
| 137 |
session%last_error_message = "" |
| 138 |
end subroutine clear_watch_error |
| 139 |
|
| 140 |
subroutine set_watch_error(session, code, message) |
| 141 |
type(watch_session), intent(inout) :: session |
| 142 |
integer, intent(in) :: code |
| 143 |
character(len=*), intent(in) :: message |
| 144 |
|
| 145 |
session%last_error_code = code |
| 146 |
if (allocated(session%last_error_message)) then |
| 147 |
deallocate(session%last_error_message) |
| 148 |
end if |
| 149 |
session%last_error_message = trim(message) |
| 150 |
end subroutine set_watch_error |
| 151 |
|
| 152 |
subroutine set_ignore_prefixes(options, prefixes) |
| 153 |
type(watch_options), intent(inout) :: options |
| 154 |
character(len=*), intent(in) :: prefixes(:) |
| 155 |
integer :: i |
| 156 |
integer :: width |
| 157 |
|
| 158 |
call clear_ignore_prefixes(options) |
| 159 |
if (size(prefixes) == 0) return |
| 160 |
|
| 161 |
width = max(1, max_string_length(prefixes)) |
| 162 |
allocate(character(len=width) :: options%ignore_prefixes(size(prefixes))) |
| 163 |
allocate(options%ignore_prefix_lengths(size(prefixes))) |
| 164 |
do i = 1, size(prefixes) |
| 165 |
options%ignore_prefixes(i) = prefixes(i) |
| 166 |
options%ignore_prefix_lengths(i) = len(prefixes(i)) |
| 167 |
end do |
| 168 |
end subroutine set_ignore_prefixes |
| 169 |
|
| 170 |
subroutine clear_ignore_prefixes(options) |
| 171 |
type(watch_options), intent(inout) :: options |
| 172 |
|
| 173 |
if (allocated(options%ignore_prefixes)) then |
| 174 |
deallocate(options%ignore_prefixes) |
| 175 |
end if |
| 176 |
if (allocated(options%ignore_prefix_lengths)) then |
| 177 |
deallocate(options%ignore_prefix_lengths) |
| 178 |
end if |
| 179 |
end subroutine clear_ignore_prefixes |
| 180 |
|
| 181 |
subroutine collect_snapshot(root, options, entries, status_code, status_message) |
| 182 |
character(len=*), intent(in) :: root |
| 183 |
type(watch_options), intent(in) :: options |
| 184 |
type(watch_entry), allocatable, intent(out) :: entries(:) |
| 185 |
integer, intent(out) :: status_code |
| 186 |
character(len=:), allocatable, intent(out) :: status_message |
| 187 |
type(c_ptr) :: raw_ptr |
| 188 |
integer(c_int) :: status |
| 189 |
integer(c_size_t) :: raw_len |
| 190 |
character(kind=c_char), allocatable :: c_root(:) |
| 191 |
character(kind=c_char), allocatable :: c_prefixes(:) |
| 192 |
integer(c_int) :: prefix_count |
| 193 |
integer(c_int) :: prefix_stride |
| 194 |
character(kind=c_char), pointer :: raw_chars(:) |
| 195 |
|
| 196 |
c_root = to_c_string(root) |
| 197 |
call pack_ignore_prefixes(options, prefix_count, prefix_stride, c_prefixes) |
| 198 |
raw_ptr = c_null_ptr |
| 199 |
raw_len = 0_c_size_t |
| 200 |
status_code = 0 |
| 201 |
status_message = "" |
| 202 |
|
| 203 |
status = fgof_watch_collect_snapshot_c( & |
| 204 |
c_root, & |
| 205 |
merge(1_c_int, 0_c_int, options%recursive), & |
| 206 |
merge(1_c_int, 0_c_int, options%ignore_hidden), & |
| 207 |
prefix_count, & |
| 208 |
prefix_stride, & |
| 209 |
c_prefixes, & |
| 210 |
raw_ptr, & |
| 211 |
raw_len & |
| 212 |
) |
| 213 |
if (status /= 0_c_int) then |
| 214 |
allocate(entries(0)) |
| 215 |
if (c_associated(raw_ptr)) call fgof_watch_free_buffer_c(raw_ptr) |
| 216 |
status_code = int(status) |
| 217 |
status_message = errno_message("snapshot collection failed", int(status)) |
| 218 |
return |
| 219 |
end if |
| 220 |
|
| 221 |
if (.not. c_associated(raw_ptr) .or. raw_len == 0_c_size_t) then |
| 222 |
allocate(entries(0)) |
| 223 |
if (c_associated(raw_ptr)) call fgof_watch_free_buffer_c(raw_ptr) |
| 224 |
return |
| 225 |
end if |
| 226 |
|
| 227 |
call c_f_pointer(raw_ptr, raw_chars, [int(raw_len)]) |
| 228 |
call parse_snapshot_buffer(raw_chars, int(raw_len), entries) |
| 229 |
call fgof_watch_free_buffer_c(raw_ptr) |
| 230 |
|
| 231 |
call filter_entries(root, options, entries) |
| 232 |
call sort_entries(entries) |
| 233 |
end subroutine collect_snapshot |
| 234 |
|
| 235 |
subroutine pack_ignore_prefixes(options, count, stride, buffer) |
| 236 |
type(watch_options), intent(in) :: options |
| 237 |
integer(c_int), intent(out) :: count |
| 238 |
integer(c_int), intent(out) :: stride |
| 239 |
character(kind=c_char), allocatable, intent(out) :: buffer(:) |
| 240 |
integer :: i |
| 241 |
integer :: j |
| 242 |
integer :: width |
| 243 |
integer :: offset |
| 244 |
|
| 245 |
if (.not. allocated(options%ignore_prefixes)) then |
| 246 |
count = 0_c_int |
| 247 |
stride = 0_c_int |
| 248 |
buffer = empty_c_string() |
| 249 |
return |
| 250 |
end if |
| 251 |
|
| 252 |
if (size(options%ignore_prefixes) == 0) then |
| 253 |
count = 0_c_int |
| 254 |
stride = 0_c_int |
| 255 |
buffer = empty_c_string() |
| 256 |
return |
| 257 |
end if |
| 258 |
|
| 259 |
width = max_string_length(options%ignore_prefixes) + 1 |
| 260 |
count = int(size(options%ignore_prefixes), c_int) |
| 261 |
stride = int(width, c_int) |
| 262 |
allocate(buffer(size(options%ignore_prefixes) * width)) |
| 263 |
buffer = c_null_char |
| 264 |
|
| 265 |
do i = 1, size(options%ignore_prefixes) |
| 266 |
offset = (i - 1) * width |
| 267 |
do j = 1, ignore_prefix_length(options, i) |
| 268 |
buffer(offset + j) = options%ignore_prefixes(i)(j:j) |
| 269 |
end do |
| 270 |
buffer(offset + ignore_prefix_length(options, i) + 1) = c_null_char |
| 271 |
end do |
| 272 |
end subroutine pack_ignore_prefixes |
| 273 |
|
| 274 |
subroutine filter_entries(root, options, entries) |
| 275 |
character(len=*), intent(in) :: root |
| 276 |
type(watch_options), intent(in) :: options |
| 277 |
type(watch_entry), allocatable, intent(inout) :: entries(:) |
| 278 |
type(watch_entry), allocatable :: filtered(:) |
| 279 |
integer :: i |
| 280 |
|
| 281 |
allocate(filtered(0)) |
| 282 |
do i = 1, size(entries) |
| 283 |
if (entry_is_ignored(root, options, entries(i))) cycle |
| 284 |
call append_entry(filtered, entries(i)) |
| 285 |
end do |
| 286 |
call move_alloc(filtered, entries) |
| 287 |
end subroutine filter_entries |
| 288 |
|
| 289 |
logical function entry_is_ignored(root, options, entry) result(ignored) |
| 290 |
character(len=*), intent(in) :: root |
| 291 |
type(watch_options), intent(in) :: options |
| 292 |
type(watch_entry), intent(in) :: entry |
| 293 |
|
| 294 |
ignored = .false. |
| 295 |
|
| 296 |
if (options%ignore_hidden) then |
| 297 |
if (contains_hidden_segment(path_after_root(root, entry%path))) then |
| 298 |
ignored = .true. |
| 299 |
return |
| 300 |
end if |
| 301 |
end if |
| 302 |
|
| 303 |
if (path_matches_ignore_prefix(options, entry%path)) then |
| 304 |
ignored = .true. |
| 305 |
end if |
| 306 |
end function entry_is_ignored |
| 307 |
|
| 308 |
logical function path_matches_ignore_prefix(options, path) result(matches) |
| 309 |
type(watch_options), intent(in) :: options |
| 310 |
character(len=*), intent(in) :: path |
| 311 |
integer :: i |
| 312 |
integer :: prefix_len |
| 313 |
|
| 314 |
matches = .false. |
| 315 |
if (.not. allocated(options%ignore_prefixes)) return |
| 316 |
|
| 317 |
do i = 1, size(options%ignore_prefixes) |
| 318 |
prefix_len = ignore_prefix_length(options, i) |
| 319 |
if (prefix_len == 0) cycle |
| 320 |
if (len(path) == prefix_len) then |
| 321 |
if (path == options%ignore_prefixes(i)(1:prefix_len)) then |
| 322 |
matches = .true. |
| 323 |
return |
| 324 |
end if |
| 325 |
end if |
| 326 |
if (len(path) > prefix_len) then |
| 327 |
if (path(1:prefix_len) == options%ignore_prefixes(i)(1:prefix_len) .and. path(prefix_len + 1:prefix_len + 1) == "/") then |
| 328 |
matches = .true. |
| 329 |
return |
| 330 |
end if |
| 331 |
end if |
| 332 |
end do |
| 333 |
end function path_matches_ignore_prefix |
| 334 |
|
| 335 |
integer function ignore_prefix_length(options, index_value) result(length_value) |
| 336 |
type(watch_options), intent(in) :: options |
| 337 |
integer, intent(in) :: index_value |
| 338 |
|
| 339 |
if (allocated(options%ignore_prefix_lengths)) then |
| 340 |
length_value = options%ignore_prefix_lengths(index_value) |
| 341 |
else |
| 342 |
length_value = len_trim(options%ignore_prefixes(index_value)) |
| 343 |
end if |
| 344 |
end function ignore_prefix_length |
| 345 |
|
| 346 |
function path_after_root(root, path) result(relative) |
| 347 |
character(len=*), intent(in) :: root |
| 348 |
character(len=*), intent(in) :: path |
| 349 |
character(len=:), allocatable :: relative |
| 350 |
|
| 351 |
if (path == root) then |
| 352 |
relative = basename_text(root) |
| 353 |
return |
| 354 |
end if |
| 355 |
|
| 356 |
if (len(path) > len(root)) then |
| 357 |
if (path(1:len(root)) == root .and. path(len(root) + 1:len(root) + 1) == "/") then |
| 358 |
relative = path(len(root) + 2:) |
| 359 |
return |
| 360 |
end if |
| 361 |
end if |
| 362 |
|
| 363 |
relative = path |
| 364 |
end function path_after_root |
| 365 |
|
| 366 |
logical function contains_hidden_segment(path) result(has_hidden) |
| 367 |
character(len=*), intent(in) :: path |
| 368 |
integer :: i |
| 369 |
integer :: start |
| 370 |
integer :: n |
| 371 |
|
| 372 |
has_hidden = .false. |
| 373 |
n = len(path) |
| 374 |
if (n == 0) return |
| 375 |
|
| 376 |
start = 1 |
| 377 |
do i = 1, n + 1 |
| 378 |
if (i <= n .and. path(i:i) /= "/") cycle |
| 379 |
if (i > start) then |
| 380 |
if (path(start:start) == ".") then |
| 381 |
has_hidden = .true. |
| 382 |
return |
| 383 |
end if |
| 384 |
end if |
| 385 |
start = i + 1 |
| 386 |
end do |
| 387 |
end function contains_hidden_segment |
| 388 |
|
| 389 |
function basename_text(path) result(name) |
| 390 |
character(len=*), intent(in) :: path |
| 391 |
character(len=:), allocatable :: name |
| 392 |
integer :: i |
| 393 |
|
| 394 |
do i = len(path), 1, -1 |
| 395 |
if (path(i:i) == "/") then |
| 396 |
name = path(i + 1:) |
| 397 |
return |
| 398 |
end if |
| 399 |
end do |
| 400 |
|
| 401 |
name = path |
| 402 |
end function basename_text |
| 403 |
|
| 404 |
function diff_snapshots(previous_entries, current_entries, options) result(events) |
| 405 |
type(watch_entry), intent(in) :: previous_entries(:) |
| 406 |
type(watch_entry), intent(in) :: current_entries(:) |
| 407 |
type(watch_options), intent(in) :: options |
| 408 |
type(watch_event), allocatable :: events(:) |
| 409 |
type(watch_entry), allocatable :: created(:) |
| 410 |
type(watch_entry), allocatable :: modified(:) |
| 411 |
type(watch_entry), allocatable :: removed(:) |
| 412 |
integer :: i |
| 413 |
integer :: j |
| 414 |
|
| 415 |
allocate(created(0)) |
| 416 |
allocate(modified(0)) |
| 417 |
allocate(removed(0)) |
| 418 |
|
| 419 |
i = 1 |
| 420 |
j = 1 |
| 421 |
do while (i <= size(previous_entries) .or. j <= size(current_entries)) |
| 422 |
if (i > size(previous_entries)) then |
| 423 |
call append_entry(created, current_entries(j)) |
| 424 |
j = j + 1 |
| 425 |
else if (j > size(current_entries)) then |
| 426 |
call append_entry(removed, previous_entries(i)) |
| 427 |
i = i + 1 |
| 428 |
else if (previous_entries(i)%path == current_entries(j)%path) then |
| 429 |
if (entry_changed(previous_entries(i), current_entries(j))) then |
| 430 |
if (.not. current_entries(j)%is_directory) then |
| 431 |
call append_entry(modified, current_entries(j)) |
| 432 |
end if |
| 433 |
end if |
| 434 |
i = i + 1 |
| 435 |
j = j + 1 |
| 436 |
else if (entry_less(previous_entries(i)%path, current_entries(j)%path)) then |
| 437 |
call append_entry(removed, previous_entries(i)) |
| 438 |
i = i + 1 |
| 439 |
else |
| 440 |
call append_entry(created, current_entries(j)) |
| 441 |
j = j + 1 |
| 442 |
end if |
| 443 |
end do |
| 444 |
|
| 445 |
events = build_event_batch(created, modified, removed, options) |
| 446 |
call sort_events(events) |
| 447 |
end function diff_snapshots |
| 448 |
|
| 449 |
function build_event_batch(created, modified, removed, options) result(events) |
| 450 |
type(watch_entry), intent(in) :: created(:) |
| 451 |
type(watch_entry), intent(in) :: modified(:) |
| 452 |
type(watch_entry), intent(in) :: removed(:) |
| 453 |
type(watch_options), intent(in) :: options |
| 454 |
type(watch_event), allocatable :: events(:) |
| 455 |
logical, allocatable :: created_used(:) |
| 456 |
logical, allocatable :: removed_used(:) |
| 457 |
integer :: i |
| 458 |
integer :: j |
| 459 |
|
| 460 |
allocate(events(0)) |
| 461 |
allocate(created_used(size(created))) |
| 462 |
allocate(removed_used(size(removed))) |
| 463 |
created_used = .false. |
| 464 |
removed_used = .false. |
| 465 |
|
| 466 |
do i = 1, size(removed) |
| 467 |
do j = 1, size(created) |
| 468 |
if (created_used(j)) cycle |
| 469 |
if (removed(i)%inode <= 0) cycle |
| 470 |
if (removed(i)%inode /= created(j)%inode) cycle |
| 471 |
if (removed(i)%is_directory .neqv. created(j)%is_directory) cycle |
| 472 |
if (created(j)%is_directory .and. .not. options%emit_directory_events) then |
| 473 |
created_used(j) = .true. |
| 474 |
removed_used(i) = .true. |
| 475 |
exit |
| 476 |
end if |
| 477 |
call append_event(events, FGOF_WATCH_EVT_MOVED, created(j)%path, removed(i)%path, created(j)%is_directory) |
| 478 |
created_used(j) = .true. |
| 479 |
removed_used(i) = .true. |
| 480 |
exit |
| 481 |
end do |
| 482 |
end do |
| 483 |
|
| 484 |
do i = 1, size(created) |
| 485 |
if (created_used(i)) cycle |
| 486 |
if (created(i)%is_directory .and. .not. options%emit_directory_events) cycle |
| 487 |
call append_event(events, FGOF_WATCH_EVT_CREATED, created(i)%path, "", created(i)%is_directory) |
| 488 |
end do |
| 489 |
|
| 490 |
do i = 1, size(modified) |
| 491 |
if (modified(i)%is_directory .and. .not. options%emit_directory_events) cycle |
| 492 |
call append_event(events, FGOF_WATCH_EVT_MODIFIED, modified(i)%path, "", modified(i)%is_directory) |
| 493 |
end do |
| 494 |
|
| 495 |
do i = 1, size(removed) |
| 496 |
if (removed_used(i)) cycle |
| 497 |
if (removed(i)%is_directory .and. .not. options%emit_directory_events) cycle |
| 498 |
call append_event(events, FGOF_WATCH_EVT_REMOVED, removed(i)%path, "", removed(i)%is_directory) |
| 499 |
end do |
| 500 |
end function build_event_batch |
| 501 |
|
| 502 |
function debounce_event_batch(session, raw_events) result(events) |
| 503 |
type(watch_session), intent(inout) :: session |
| 504 |
type(watch_event), intent(in) :: raw_events(:) |
| 505 |
type(watch_event), allocatable :: events(:) |
| 506 |
logical, allocatable :: touched(:) |
| 507 |
integer :: i |
| 508 |
integer :: index |
| 509 |
|
| 510 |
if (.not. allocated(session%pending_events)) allocate(session%pending_events(0)) |
| 511 |
if (.not. allocated(session%pending_remaining)) allocate(session%pending_remaining(0)) |
| 512 |
|
| 513 |
allocate(touched(size(session%pending_events))) |
| 514 |
touched = .false. |
| 515 |
|
| 516 |
do i = 1, size(raw_events) |
| 517 |
index = find_related_pending_event(session%pending_events, raw_events(i)) |
| 518 |
if (index > 0) then |
| 519 |
call merge_pending_event(session, index, raw_events(i)) |
| 520 |
if (index <= size(session%pending_events)) then |
| 521 |
touched = resize_logical_flags(touched, size(session%pending_events)) |
| 522 |
touched(index) = .true. |
| 523 |
end if |
| 524 |
else |
| 525 |
call append_pending_event(session, raw_events(i), session%options%debounce_polls) |
| 526 |
touched = resize_logical_flags(touched, size(session%pending_events)) |
| 527 |
touched(size(touched)) = .true. |
| 528 |
end if |
| 529 |
end do |
| 530 |
|
| 531 |
do i = 1, size(session%pending_remaining) |
| 532 |
if (touched(i)) cycle |
| 533 |
session%pending_remaining(i) = session%pending_remaining(i) - 1 |
| 534 |
end do |
| 535 |
|
| 536 |
call emit_ready_events(session, events) |
| 537 |
end function debounce_event_batch |
| 538 |
|
| 539 |
subroutine merge_pending_event(session, index, incoming) |
| 540 |
type(watch_session), intent(inout) :: session |
| 541 |
integer, intent(in) :: index |
| 542 |
type(watch_event), intent(in) :: incoming |
| 543 |
type(watch_event) :: merged |
| 544 |
logical :: drop_pending |
| 545 |
|
| 546 |
call merge_event_pair(session%pending_events(index), incoming, merged, drop_pending) |
| 547 |
if (drop_pending) then |
| 548 |
call remove_pending_event(session, index) |
| 549 |
return |
| 550 |
end if |
| 551 |
|
| 552 |
session%pending_events(index) = merged |
| 553 |
session%pending_remaining(index) = session%options%debounce_polls |
| 554 |
end subroutine merge_pending_event |
| 555 |
|
| 556 |
subroutine merge_event_pair(existing, incoming, merged, drop_pending) |
| 557 |
type(watch_event), intent(in) :: existing |
| 558 |
type(watch_event), intent(in) :: incoming |
| 559 |
type(watch_event), intent(out) :: merged |
| 560 |
logical, intent(out) :: drop_pending |
| 561 |
|
| 562 |
drop_pending = .false. |
| 563 |
merged = incoming |
| 564 |
|
| 565 |
select case (existing%kind) |
| 566 |
case (FGOF_WATCH_EVT_CREATED) |
| 567 |
select case (incoming%kind) |
| 568 |
case (FGOF_WATCH_EVT_CREATED) |
| 569 |
merged = incoming |
| 570 |
case (FGOF_WATCH_EVT_MODIFIED) |
| 571 |
merged = existing |
| 572 |
case (FGOF_WATCH_EVT_REMOVED) |
| 573 |
if (incoming%path == existing%path) then |
| 574 |
drop_pending = .true. |
| 575 |
else |
| 576 |
merged = incoming |
| 577 |
end if |
| 578 |
case (FGOF_WATCH_EVT_MOVED) |
| 579 |
if (incoming%previous_path == existing%path) then |
| 580 |
merged = existing |
| 581 |
merged%path = incoming%path |
| 582 |
else |
| 583 |
merged = incoming |
| 584 |
end if |
| 585 |
end select |
| 586 |
|
| 587 |
case (FGOF_WATCH_EVT_MODIFIED) |
| 588 |
select case (incoming%kind) |
| 589 |
case (FGOF_WATCH_EVT_CREATED) |
| 590 |
merged = incoming |
| 591 |
case (FGOF_WATCH_EVT_MODIFIED) |
| 592 |
merged = incoming |
| 593 |
case (FGOF_WATCH_EVT_REMOVED) |
| 594 |
merged = incoming |
| 595 |
case (FGOF_WATCH_EVT_MOVED) |
| 596 |
merged = incoming |
| 597 |
end select |
| 598 |
|
| 599 |
case (FGOF_WATCH_EVT_REMOVED) |
| 600 |
select case (incoming%kind) |
| 601 |
case (FGOF_WATCH_EVT_CREATED) |
| 602 |
if (incoming%path == existing%path) then |
| 603 |
merged%kind = FGOF_WATCH_EVT_MODIFIED |
| 604 |
merged%path = incoming%path |
| 605 |
merged%previous_path = "" |
| 606 |
merged%is_directory = incoming%is_directory |
| 607 |
else |
| 608 |
merged = incoming |
| 609 |
end if |
| 610 |
case default |
| 611 |
merged = incoming |
| 612 |
end select |
| 613 |
|
| 614 |
case (FGOF_WATCH_EVT_MOVED) |
| 615 |
select case (incoming%kind) |
| 616 |
case (FGOF_WATCH_EVT_MODIFIED) |
| 617 |
if (incoming%path == existing%path) then |
| 618 |
merged = existing |
| 619 |
else |
| 620 |
merged = incoming |
| 621 |
end if |
| 622 |
case (FGOF_WATCH_EVT_REMOVED) |
| 623 |
if (incoming%path == existing%path) then |
| 624 |
merged = incoming |
| 625 |
else |
| 626 |
merged = incoming |
| 627 |
end if |
| 628 |
case (FGOF_WATCH_EVT_MOVED) |
| 629 |
if (incoming%previous_path == existing%path) then |
| 630 |
merged = existing |
| 631 |
merged%path = incoming%path |
| 632 |
else |
| 633 |
merged = incoming |
| 634 |
end if |
| 635 |
case (FGOF_WATCH_EVT_CREATED) |
| 636 |
merged = incoming |
| 637 |
end select |
| 638 |
end select |
| 639 |
end subroutine merge_event_pair |
| 640 |
|
| 641 |
subroutine emit_ready_events(session, events) |
| 642 |
type(watch_session), intent(inout) :: session |
| 643 |
type(watch_event), allocatable, intent(out) :: events(:) |
| 644 |
type(watch_event), allocatable :: ready(:) |
| 645 |
integer :: i |
| 646 |
|
| 647 |
allocate(ready(0)) |
| 648 |
i = 1 |
| 649 |
do while (i <= size(session%pending_events)) |
| 650 |
if (session%pending_remaining(i) > 0) then |
| 651 |
i = i + 1 |
| 652 |
cycle |
| 653 |
end if |
| 654 |
|
| 655 |
call append_event_object(ready, session%pending_events(i)) |
| 656 |
call remove_pending_event(session, i) |
| 657 |
end do |
| 658 |
|
| 659 |
call move_alloc(ready, events) |
| 660 |
end subroutine emit_ready_events |
| 661 |
|
| 662 |
subroutine append_pending_event(session, event, remaining) |
| 663 |
type(watch_session), intent(inout) :: session |
| 664 |
type(watch_event), intent(in) :: event |
| 665 |
integer, intent(in) :: remaining |
| 666 |
type(watch_event), allocatable :: grown_events(:) |
| 667 |
integer, allocatable :: grown_remaining(:) |
| 668 |
integer :: n |
| 669 |
|
| 670 |
n = size(session%pending_events) |
| 671 |
allocate(grown_events(n + 1)) |
| 672 |
allocate(grown_remaining(n + 1)) |
| 673 |
|
| 674 |
if (n > 0) then |
| 675 |
grown_events(1:n) = session%pending_events |
| 676 |
grown_remaining(1:n) = session%pending_remaining |
| 677 |
end if |
| 678 |
|
| 679 |
grown_events(n + 1) = event |
| 680 |
grown_remaining(n + 1) = remaining |
| 681 |
|
| 682 |
call move_alloc(grown_events, session%pending_events) |
| 683 |
call move_alloc(grown_remaining, session%pending_remaining) |
| 684 |
end subroutine append_pending_event |
| 685 |
|
| 686 |
subroutine remove_pending_event(session, index) |
| 687 |
type(watch_session), intent(inout) :: session |
| 688 |
integer, intent(in) :: index |
| 689 |
type(watch_event), allocatable :: kept_events(:) |
| 690 |
integer, allocatable :: kept_remaining(:) |
| 691 |
integer :: n |
| 692 |
|
| 693 |
n = size(session%pending_events) |
| 694 |
if (index < 1 .or. index > n) return |
| 695 |
|
| 696 |
allocate(kept_events(n - 1)) |
| 697 |
allocate(kept_remaining(n - 1)) |
| 698 |
|
| 699 |
if (index > 1) then |
| 700 |
kept_events(1:index - 1) = session%pending_events(1:index - 1) |
| 701 |
kept_remaining(1:index - 1) = session%pending_remaining(1:index - 1) |
| 702 |
end if |
| 703 |
|
| 704 |
if (index < n) then |
| 705 |
kept_events(index:n - 1) = session%pending_events(index + 1:n) |
| 706 |
kept_remaining(index:n - 1) = session%pending_remaining(index + 1:n) |
| 707 |
end if |
| 708 |
|
| 709 |
call move_alloc(kept_events, session%pending_events) |
| 710 |
call move_alloc(kept_remaining, session%pending_remaining) |
| 711 |
end subroutine remove_pending_event |
| 712 |
|
| 713 |
integer function find_related_pending_event(pending_events, incoming) result(index_found) |
| 714 |
type(watch_event), intent(in) :: pending_events(:) |
| 715 |
type(watch_event), intent(in) :: incoming |
| 716 |
integer :: i |
| 717 |
|
| 718 |
index_found = 0 |
| 719 |
do i = 1, size(pending_events) |
| 720 |
if (events_related(pending_events(i), incoming)) then |
| 721 |
index_found = i |
| 722 |
return |
| 723 |
end if |
| 724 |
end do |
| 725 |
end function find_related_pending_event |
| 726 |
|
| 727 |
logical function events_related(left, right) result(related) |
| 728 |
type(watch_event), intent(in) :: left |
| 729 |
type(watch_event), intent(in) :: right |
| 730 |
|
| 731 |
related = .false. |
| 732 |
if (same_nonempty_text(left%path, right%path)) related = .true. |
| 733 |
if (same_nonempty_text(left%path, right%previous_path)) related = .true. |
| 734 |
if (same_nonempty_text(left%previous_path, right%path)) related = .true. |
| 735 |
if (same_nonempty_text(left%previous_path, right%previous_path)) related = .true. |
| 736 |
end function events_related |
| 737 |
|
| 738 |
logical function same_nonempty_text(left, right) result(matches) |
| 739 |
character(len=*), intent(in) :: left |
| 740 |
character(len=*), intent(in) :: right |
| 741 |
|
| 742 |
matches = .false. |
| 743 |
if (len(left) == 0 .or. len(right) == 0) return |
| 744 |
matches = (left == right) |
| 745 |
end function same_nonempty_text |
| 746 |
|
| 747 |
function resize_logical_flags(flags, new_size) result(resized) |
| 748 |
logical, intent(in) :: flags(:) |
| 749 |
integer, intent(in) :: new_size |
| 750 |
logical, allocatable :: resized(:) |
| 751 |
integer :: copy_count |
| 752 |
|
| 753 |
allocate(resized(new_size)) |
| 754 |
resized = .false. |
| 755 |
copy_count = min(size(flags), new_size) |
| 756 |
if (copy_count > 0) resized(1:copy_count) = flags(1:copy_count) |
| 757 |
end function resize_logical_flags |
| 758 |
|
| 759 |
logical function entry_changed(previous_entry, current_entry) result(changed) |
| 760 |
type(watch_entry), intent(in) :: previous_entry |
| 761 |
type(watch_entry), intent(in) :: current_entry |
| 762 |
|
| 763 |
changed = .false. |
| 764 |
if (previous_entry%inode /= current_entry%inode) changed = .true. |
| 765 |
if (previous_entry%size /= current_entry%size) changed = .true. |
| 766 |
if (previous_entry%mtime_sec /= current_entry%mtime_sec) changed = .true. |
| 767 |
if (previous_entry%mtime_nsec /= current_entry%mtime_nsec) changed = .true. |
| 768 |
if (previous_entry%is_directory .neqv. current_entry%is_directory) changed = .true. |
| 769 |
end function entry_changed |
| 770 |
|
| 771 |
subroutine parse_snapshot_buffer(buffer, count, entries) |
| 772 |
character(kind=c_char), intent(in) :: buffer(:) |
| 773 |
integer, intent(in) :: count |
| 774 |
type(watch_entry), allocatable, intent(out) :: entries(:) |
| 775 |
integer :: i |
| 776 |
integer :: field_count |
| 777 |
integer :: record_count |
| 778 |
integer :: start |
| 779 |
integer :: terminator_index |
| 780 |
character(len=:), allocatable :: kind_text |
| 781 |
character(len=:), allocatable :: inode_text |
| 782 |
character(len=:), allocatable :: size_text |
| 783 |
character(len=:), allocatable :: mtime_sec_text |
| 784 |
character(len=:), allocatable :: mtime_nsec_text |
| 785 |
character(len=:), allocatable :: path_text |
| 786 |
|
| 787 |
if (count <= 0) then |
| 788 |
allocate(entries(0)) |
| 789 |
return |
| 790 |
end if |
| 791 |
|
| 792 |
field_count = 0 |
| 793 |
do i = 1, count |
| 794 |
if (buffer(i) == c_null_char) field_count = field_count + 1 |
| 795 |
end do |
| 796 |
if (field_count == 0 .or. mod(field_count, 6) /= 0) then |
| 797 |
allocate(entries(0)) |
| 798 |
return |
| 799 |
end if |
| 800 |
|
| 801 |
record_count = field_count / 6 |
| 802 |
allocate(entries(record_count)) |
| 803 |
start = 1 |
| 804 |
do i = 1, record_count |
| 805 |
call next_nul_field(buffer, count, start, kind_text, terminator_index) |
| 806 |
if (terminator_index == 0) exit |
| 807 |
call next_nul_field(buffer, count, start, inode_text, terminator_index) |
| 808 |
if (terminator_index == 0) exit |
| 809 |
call next_nul_field(buffer, count, start, size_text, terminator_index) |
| 810 |
if (terminator_index == 0) exit |
| 811 |
call next_nul_field(buffer, count, start, mtime_sec_text, terminator_index) |
| 812 |
if (terminator_index == 0) exit |
| 813 |
call next_nul_field(buffer, count, start, mtime_nsec_text, terminator_index) |
| 814 |
if (terminator_index == 0) exit |
| 815 |
call next_nul_field(buffer, count, start, path_text, terminator_index) |
| 816 |
if (terminator_index == 0) exit |
| 817 |
call parse_snapshot_fields(kind_text, inode_text, size_text, mtime_sec_text, mtime_nsec_text, path_text, entries(i)) |
| 818 |
end do |
| 819 |
end subroutine parse_snapshot_buffer |
| 820 |
|
| 821 |
subroutine parse_snapshot_fields(kind_text, inode_text, size_text, mtime_sec_text, mtime_nsec_text, path_text, entry) |
| 822 |
character(len=*), intent(in) :: kind_text |
| 823 |
character(len=*), intent(in) :: inode_text |
| 824 |
character(len=*), intent(in) :: size_text |
| 825 |
character(len=*), intent(in) :: mtime_sec_text |
| 826 |
character(len=*), intent(in) :: mtime_nsec_text |
| 827 |
character(len=*), intent(in) :: path_text |
| 828 |
type(watch_entry), intent(out) :: entry |
| 829 |
integer :: iostat_value |
| 830 |
|
| 831 |
entry = watch_entry() |
| 832 |
if (len(kind_text) == 0) then |
| 833 |
entry%path = "" |
| 834 |
return |
| 835 |
end if |
| 836 |
|
| 837 |
entry%is_directory = (kind_text(1:1) == "D") |
| 838 |
read(inode_text, *, iostat=iostat_value) entry%inode |
| 839 |
if (iostat_value /= 0) entry%inode = 0 |
| 840 |
read(size_text, *, iostat=iostat_value) entry%size |
| 841 |
if (iostat_value /= 0) entry%size = 0 |
| 842 |
read(mtime_sec_text, *, iostat=iostat_value) entry%mtime_sec |
| 843 |
if (iostat_value /= 0) entry%mtime_sec = 0 |
| 844 |
read(mtime_nsec_text, *, iostat=iostat_value) entry%mtime_nsec |
| 845 |
if (iostat_value /= 0) entry%mtime_nsec = 0 |
| 846 |
entry%path = path_text |
| 847 |
end subroutine parse_snapshot_fields |
| 848 |
|
| 849 |
subroutine next_nul_field(buffer, count, start_index, field, terminator_index) |
| 850 |
character(kind=c_char), intent(in) :: buffer(:) |
| 851 |
integer, intent(in) :: count |
| 852 |
integer, intent(inout) :: start_index |
| 853 |
character(len=:), allocatable, intent(out) :: field |
| 854 |
integer, intent(out) :: terminator_index |
| 855 |
integer :: i |
| 856 |
integer :: width |
| 857 |
|
| 858 |
if (start_index > count) then |
| 859 |
field = "" |
| 860 |
terminator_index = 0 |
| 861 |
return |
| 862 |
end if |
| 863 |
|
| 864 |
terminator_index = 0 |
| 865 |
do i = start_index, count |
| 866 |
if (buffer(i) == c_null_char) then |
| 867 |
terminator_index = i |
| 868 |
exit |
| 869 |
end if |
| 870 |
end do |
| 871 |
if (terminator_index == 0) then |
| 872 |
field = "" |
| 873 |
return |
| 874 |
end if |
| 875 |
|
| 876 |
width = terminator_index - start_index |
| 877 |
allocate(character(len=width) :: field) |
| 878 |
do i = 1, width |
| 879 |
field(i:i) = char(iachar(buffer(start_index + i - 1))) |
| 880 |
end do |
| 881 |
start_index = terminator_index + 1 |
| 882 |
end subroutine next_nul_field |
| 883 |
|
| 884 |
function errno_message(prefix, errnum) result(message) |
| 885 |
character(len=*), intent(in) :: prefix |
| 886 |
integer, intent(in) :: errnum |
| 887 |
character(len=:), allocatable :: message |
| 888 |
character(len=32) :: code_text |
| 889 |
|
| 890 |
write(code_text, '(I0)') errnum |
| 891 |
message = trim(prefix) // " (errno=" // trim(code_text) // ")" |
| 892 |
end function errno_message |
| 893 |
|
| 894 |
function to_c_string(str) result(buf) |
| 895 |
character(len=*), intent(in) :: str |
| 896 |
character(kind=c_char), allocatable :: buf(:) |
| 897 |
integer :: i |
| 898 |
integer :: n |
| 899 |
|
| 900 |
n = len(str) |
| 901 |
allocate(buf(n + 1)) |
| 902 |
do i = 1, n |
| 903 |
buf(i) = str(i:i) |
| 904 |
end do |
| 905 |
buf(n + 1) = c_null_char |
| 906 |
end function to_c_string |
| 907 |
|
| 908 |
function empty_c_string() result(buf) |
| 909 |
character(kind=c_char), allocatable :: buf(:) |
| 910 |
|
| 911 |
allocate(buf(1)) |
| 912 |
buf(1) = c_null_char |
| 913 |
end function empty_c_string |
| 914 |
|
| 915 |
subroutine append_entry(entries, entry) |
| 916 |
type(watch_entry), allocatable, intent(inout) :: entries(:) |
| 917 |
type(watch_entry), intent(in) :: entry |
| 918 |
type(watch_entry), allocatable :: grown(:) |
| 919 |
integer :: n |
| 920 |
|
| 921 |
n = size(entries) |
| 922 |
allocate(grown(n + 1)) |
| 923 |
if (n > 0) grown(1:n) = entries |
| 924 |
grown(n + 1) = entry |
| 925 |
call move_alloc(grown, entries) |
| 926 |
end subroutine append_entry |
| 927 |
|
| 928 |
subroutine append_event(events, kind, path, previous_path, is_directory) |
| 929 |
type(watch_event), allocatable, intent(inout) :: events(:) |
| 930 |
integer, intent(in) :: kind |
| 931 |
character(len=*), intent(in) :: path |
| 932 |
character(len=*), intent(in) :: previous_path |
| 933 |
logical, intent(in) :: is_directory |
| 934 |
type(watch_event), allocatable :: grown(:) |
| 935 |
integer :: n |
| 936 |
|
| 937 |
n = size(events) |
| 938 |
allocate(grown(n + 1)) |
| 939 |
if (n > 0) grown(1:n) = events |
| 940 |
grown(n + 1)%kind = kind |
| 941 |
grown(n + 1)%is_directory = is_directory |
| 942 |
grown(n + 1)%path = path |
| 943 |
if (len(previous_path) > 0) then |
| 944 |
grown(n + 1)%previous_path = previous_path |
| 945 |
else |
| 946 |
grown(n + 1)%previous_path = "" |
| 947 |
end if |
| 948 |
call move_alloc(grown, events) |
| 949 |
end subroutine append_event |
| 950 |
|
| 951 |
subroutine append_event_object(events, event) |
| 952 |
type(watch_event), allocatable, intent(inout) :: events(:) |
| 953 |
type(watch_event), intent(in) :: event |
| 954 |
type(watch_event), allocatable :: grown(:) |
| 955 |
integer :: n |
| 956 |
|
| 957 |
n = size(events) |
| 958 |
allocate(grown(n + 1)) |
| 959 |
if (n > 0) grown(1:n) = events |
| 960 |
grown(n + 1) = event |
| 961 |
call move_alloc(grown, events) |
| 962 |
end subroutine append_event_object |
| 963 |
|
| 964 |
subroutine sort_entries(entries) |
| 965 |
type(watch_entry), intent(inout) :: entries(:) |
| 966 |
type(watch_entry) :: temp |
| 967 |
integer :: i |
| 968 |
integer :: j |
| 969 |
|
| 970 |
do i = 1, size(entries) - 1 |
| 971 |
do j = i + 1, size(entries) |
| 972 |
if (entry_less(entries(j)%path, entries(i)%path)) then |
| 973 |
temp = entries(i) |
| 974 |
entries(i) = entries(j) |
| 975 |
entries(j) = temp |
| 976 |
end if |
| 977 |
end do |
| 978 |
end do |
| 979 |
end subroutine sort_entries |
| 980 |
|
| 981 |
subroutine sort_events(events) |
| 982 |
type(watch_event), intent(inout) :: events(:) |
| 983 |
type(watch_event) :: temp |
| 984 |
integer :: i |
| 985 |
integer :: j |
| 986 |
|
| 987 |
do i = 1, size(events) - 1 |
| 988 |
do j = i + 1, size(events) |
| 989 |
if (event_less(events(j), events(i))) then |
| 990 |
temp = events(i) |
| 991 |
events(i) = events(j) |
| 992 |
events(j) = temp |
| 993 |
end if |
| 994 |
end do |
| 995 |
end do |
| 996 |
end subroutine sort_events |
| 997 |
|
| 998 |
logical function entry_less(left, right) result(is_less) |
| 999 |
character(len=*), intent(in) :: left |
| 1000 |
character(len=*), intent(in) :: right |
| 1001 |
integer :: i |
| 1002 |
integer :: limit |
| 1003 |
|
| 1004 |
limit = min(len(left), len(right)) |
| 1005 |
do i = 1, limit |
| 1006 |
if (left(i:i) < right(i:i)) then |
| 1007 |
is_less = .true. |
| 1008 |
return |
| 1009 |
end if |
| 1010 |
if (left(i:i) > right(i:i)) then |
| 1011 |
is_less = .false. |
| 1012 |
return |
| 1013 |
end if |
| 1014 |
end do |
| 1015 |
|
| 1016 |
is_less = (len(left) < len(right)) |
| 1017 |
end function entry_less |
| 1018 |
|
| 1019 |
integer function max_string_length(values) result(max_len) |
| 1020 |
character(len=*), intent(in) :: values(:) |
| 1021 |
integer :: i |
| 1022 |
|
| 1023 |
max_len = 1 |
| 1024 |
do i = 1, size(values) |
| 1025 |
max_len = max(max_len, len(values(i))) |
| 1026 |
end do |
| 1027 |
end function max_string_length |
| 1028 |
|
| 1029 |
logical function event_less(left, right) result(is_less) |
| 1030 |
type(watch_event), intent(in) :: left |
| 1031 |
type(watch_event), intent(in) :: right |
| 1032 |
|
| 1033 |
if (left%path /= right%path) then |
| 1034 |
is_less = entry_less(left%path, right%path) |
| 1035 |
return |
| 1036 |
end if |
| 1037 |
|
| 1038 |
if (left%kind /= right%kind) then |
| 1039 |
is_less = (left%kind < right%kind) |
| 1040 |
return |
| 1041 |
end if |
| 1042 |
|
| 1043 |
is_less = entry_less(left%previous_path, right%previous_path) |
| 1044 |
end function event_less |
| 1045 |
|
| 1046 |
end module fgof_watch |
| 1047 |
|