-
Notifications
You must be signed in to change notification settings - Fork 242
/
record.rb
133 lines (113 loc) · 3.57 KB
/
record.rb
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
# frozen_string_literal: true
require 'thread'
require 'listen/record/entry'
require 'listen/record/symlink_detector'
module Listen
class Record
# TODO: one Record object per watched directory?
# TODO: deprecate
attr_reader :root
def initialize(directory)
@tree = _auto_hash
@root = directory.to_s
end
def add_dir(rel_path)
if ![nil, '', '.'].include?(rel_path)
@tree[rel_path] ||= {}
end
end
def update_file(rel_path, data)
dirname, basename = Pathname(rel_path).split.map(&:to_s)
_fast_update_file(dirname, basename, data)
end
def unset_path(rel_path)
dirname, basename = Pathname(rel_path).split.map(&:to_s)
_fast_unset_path(dirname, basename)
end
def file_data(rel_path)
dirname, basename = Pathname(rel_path).split.map(&:to_s)
if [nil, '', '.'].include? dirname
@tree[basename] ||= {}
@tree[basename].dup
else
@tree[dirname] ||= {}
@tree[dirname][basename] ||= {}
@tree[dirname][basename].dup
end
end
def dir_entries(rel_path)
subtree = if [nil, '', '.'].include? rel_path.to_s
@tree
else
_sub_tree(rel_path)
end
subtree.transform_values do |values|
# only get data for file entries
values.key?(:mtime) ? values : {}
end
end
# given a rel_path, finds all entries in the @tree at paths that are a child of the rel_path
def _sub_tree(rel_path)
@tree.each_with_object({}) do |(path, meta), result|
next unless path.start_with?(rel_path)
if path == rel_path
result.merge!(meta)
else
parts = path.split("/")
next unless parts.include?(rel_path)
sub_path = (parts - [rel_path]).join("/")
result[sub_path] = meta
end
end
end
def build
@tree = _auto_hash
# TODO: test with a file name given
# TODO: test other permissions
# TODO: test with mixed encoding
symlink_detector = SymlinkDetector.new
remaining = ::Queue.new
remaining << Entry.new(root, nil, nil)
_fast_build_dir(remaining, symlink_detector) until remaining.empty?
end
private
def _auto_hash
Hash.new { |h, k| h[k] = {} }
end
def _fast_update_file(dirname, basename, data)
if [nil, '', '.'].include?(dirname)
@tree[basename] = (@tree[basename] || {}).merge(data)
else
@tree[dirname] ||= {}
@tree[dirname][basename] = (@tree[dirname][basename] || {}).merge(data)
end
end
def _fast_unset_path(dirname, basename)
# this may need to be reworked to properly remove
# entries from a tree, without adding non-existing dirs to the record
if [nil, '', '.'].include?(dirname)
if @tree.key?(basename)
@tree.delete(basename)
end
elsif @tree.key?(dirname)
@tree[dirname].delete(basename)
end
end
def _fast_build_dir(remaining, symlink_detector)
entry = remaining.pop
children = entry.children # NOTE: children() implicitly tests if dir
symlink_detector.verify_unwatched!(entry)
children.each { |child| remaining << child }
add_dir(entry.record_dir_key)
rescue Errno::ENOTDIR
_fast_try_file(entry)
rescue SystemCallError, SymlinkDetector::Error
_fast_unset_path(entry.relative, entry.name)
end
def _fast_try_file(entry)
_fast_update_file(entry.relative, entry.name, entry.meta)
rescue SystemCallError
_fast_unset_path(entry.relative, entry.name)
end
end
end