summaryrefslogtreecommitdiff
path: root/script/filewatch.lua
blob: 1ccb66ce7bceff058df4d279b658da2c1c29fcb5 (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
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
local fw    = require 'bee.filewatch'
local fs    = require 'bee.filesystem'
local plat  = require 'bee.platform'
local await = require 'await'

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? async 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()
        watch:add(path)
        log.debug('Watch add:', path)
        if recursive then
            local count = 1
            ---@async
            local function scanDirctory(dir)
                count = count + 1
                if count % 10 == 0 then
                    await.delay()
                    if count % 100 == 0 then
                        log.warn('Watching so many dirs:', count, dir:string())
                    end
                end
                for fullpath, status in fs.pairs(dir) do
                    local st = status:type()
                    if st == 'directory'
                    or st == 'symlink'
                    or st == 'junction' then
                        if not filter or filter(fullpath:string()) then
                            watch:add(fullpath:string())
                            log.trace('Watch add:', fullpath:string())
                            xpcall(scanDirctory, log.error, fullpath)
                        end
                    end
                end
            end

            xpcall(scanDirctory, log.error, fs.path(path))
        end
        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
            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