summaryrefslogtreecommitdiff
path: root/script/filewatch.lua
blob: a8fa925f784514ef0fe2d36488a23ac6a4b3647e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
local fw    = require 'bee.filewatch'
local fs    = require 'bee.filesystem'
local plat  = require 'bee.platform'
local await = require 'await'
local files = require 'files'

local MODIFY = 1 << 0
local RENAME = 1 << 1

local function isExists(filename)
    local path = fs.path(filename)
    local suc, exists = pcall(fs.exists, path)
    if not suc or not exists then
        return false
    end
    if plat.OS ~= 'Windows' then
        return true
    end
    local suc, res = pcall(fs.fullpath, path)
    if not suc then
        return false
    end
    if res :string():gsub('^%w+:', string.lower)
    ~= path:string():gsub('^%w+:', string.lower) then
        return false
    end
    return true
end

---@class filewatch
local m = {}

m._eventList = {}
m._watchings = {}

---@async
---@param path string
---@param recursive boolean
---@param filter? fun(path: string):boolean
function m.watch(path, recursive, filter)
    if path == '' or not fs.is_directory(fs.path(path)) then
        return function () end
    end
    if m._watchings[path] then
        m._watchings[path].count = m._watchings[path].count + 1
    else
        local watch = fw.create()
        if recursive then
            watch:set_recursive(true)
            watch:set_follow_symlinks(true)
            watch:set_filter(filter)
        end
        log.debug('Watch add:', path)
        watch:add(path)
        m._watchings[path] = {
            count = 1,
            watch = watch,
        }
        log.debug('fw.add', path)
    end
    local removed
    return function ()
        if removed then
            return
        end
        removed = true
        m._watchings[path].count = m._watchings[path].count - 1
        if m._watchings[path].count == 0 then
            m._watchings[path] = nil
            log.debug('fw.remove', path)
        end
    end
end

---@param callback async fun(ev: string, path: string)
function m.event(callback)
    m._eventList[#m._eventList+1] = callback
end

function m._callEvent(ev, path)
    for _, callback in ipairs(m._eventList) do
        await.call(function ()
            callback(ev, path)
        end)
    end
end

function m.update()
    local collect
    for _, watching in pairs(m._watchings) do
        local watch = watching.watch
        for _ = 1, 10000 do
            local ev, path = watch:select()
            if not ev then
                break
            end
            path = files.normalize(path)
            log.debug('filewatch:', ev, path)
            if not collect then
                collect = {}
            end
            if ev == 'modify' then
                collect[path] = (collect[path] or 0) | MODIFY
            elseif ev == 'rename' then
                collect[path] = (collect[path] or 0) | RENAME
            end
        end
    end

    if not collect or not next(collect) then
        return
    end

    for path, flag in pairs(collect) do
        if flag & RENAME ~= 0 then
            if isExists(path) then
                m._callEvent('create', path)
            else
                m._callEvent('delete', path)
            end
        elseif flag & MODIFY ~= 0 then
            m._callEvent('change', path)
        end
    end

end

return m