file_monitoring 0.0.9 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/file_monitoring +0 -0
- data/lib/file_monitoring.rb +25 -8
- data/lib/file_monitoring/file_monitoring.rb +59 -57
- data/lib/file_monitoring/monitor_path.rb +240 -241
- data/lib/file_monitoring/version.rb +2 -4
- data/test/file_monitoring/monitor_path_test.rb +136 -140
- metadata +18 -19
- data/lib/file_monitoring/daemon_win32.rb +0 -29
data/bin/file_monitoring
CHANGED
File without changes
|
data/lib/file_monitoring.rb
CHANGED
@@ -1,12 +1,29 @@
|
|
1
1
|
require 'file_monitoring/file_monitoring'
|
2
2
|
|
3
|
-
#
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
3
|
+
# Daemon for monitoring directories for changes.
|
4
|
+
# Paths are checked for changes per user-defined period of time.
|
5
|
+
#
|
6
|
+
# Directory defined changed when:
|
7
|
+
# 1. Directory structure changed, i.e. sub-directories or files were added/removed
|
8
|
+
# 2. One of the files located in the directory or one of its sub-directories was changed
|
9
|
+
# 3. One of sub-directories changed (see 1. and 2. above)
|
10
|
+
#
|
11
|
+
# File monitoring controled by following configuration parameters:
|
12
|
+
# * <tt>default_monitoring_log_path</tt> - holds path of file monitoring log.
|
13
|
+
# This log containd track of changes found during monitoring
|
14
|
+
# * <tt>monitoring_paths</tt> - path and file monitoring configuration data
|
15
|
+
# regarding these paths.
|
16
|
+
|
17
|
+
module FileMonitoring
|
18
|
+
Params.path('default_monitoring_log_path', '~/.bbfs/log/file_monitoring.log',
|
19
|
+
'Default path for file monitoring log file. ' \
|
20
|
+
'This log containd track of changes found during monitoring')
|
21
|
+
Params.complex('monitoring_paths', nil, 'Array of Hashes with 3 fields: ' \
|
22
|
+
'path, scan_period and stable_state.')
|
23
|
+
|
24
|
+
# @see FileMonitoring#monitor_files
|
25
|
+
def monitor_files
|
26
|
+
fm = FileMonitoring.new
|
27
|
+
fm.monitor_files
|
11
28
|
end
|
12
29
|
end
|
@@ -2,71 +2,73 @@ require 'algorithms'
|
|
2
2
|
require 'fileutils'
|
3
3
|
require 'log'
|
4
4
|
require 'params'
|
5
|
-
require 'yaml'
|
6
5
|
|
7
6
|
require 'file_monitoring/monitor_path'
|
8
7
|
|
9
|
-
module
|
10
|
-
|
8
|
+
module FileMonitoring
|
9
|
+
# Manages file monitoring of number of file system locations
|
10
|
+
class FileMonitoring
|
11
11
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
def set_config_path(config_path)
|
18
|
-
@config_path = config_path
|
19
|
-
end
|
20
|
-
|
21
|
-
def set_event_queue(queue)
|
22
|
-
@event_queue = queue
|
23
|
-
end
|
24
|
-
|
25
|
-
def monitor_files
|
26
|
-
config_yml = YAML::load_file(@config_path)
|
27
|
-
|
28
|
-
conf_array = config_yml['paths']
|
29
|
-
|
30
|
-
pq = Containers::PriorityQueue.new
|
31
|
-
conf_array.each { |elem|
|
32
|
-
priority = (Time.now + elem['scan_period']).to_i
|
33
|
-
dir_stat = DirStat.new(elem['path'], elem['stable_state'])
|
34
|
-
dir_stat.set_event_queue(@event_queue) if @event_queue
|
35
|
-
Log.info [priority, elem, dir_stat]
|
36
|
-
pq.push([priority, elem, dir_stat], -priority)
|
37
|
-
}
|
38
|
-
|
39
|
-
log_path = Params['default_log_path']
|
40
|
-
if config_yml.key?('log_path')
|
41
|
-
log_path = File.expand_path(config_yml['log_path'])
|
42
|
-
end
|
43
|
-
|
44
|
-
Log.info 'Log path:' + log_path
|
45
|
-
FileUtils.mkdir_p(File.dirname(log_path))
|
46
|
-
log = File.open(log_path, 'w')
|
47
|
-
FileStat.set_log(log)
|
48
|
-
|
49
|
-
while true do
|
50
|
-
time, conf, dir_stat = pq.pop
|
51
|
-
#Log.info 'time:' + time.to_s()
|
52
|
-
#Log.info 'now:' + Time.now.to_i.to_s()
|
53
|
-
#Log.info conf
|
54
|
-
|
55
|
-
time_span = time - Time.now.to_i
|
56
|
-
if (time_span > 0)
|
57
|
-
sleep(time_span)
|
58
|
-
end
|
59
|
-
dir_stat.monitor
|
12
|
+
# Set event queue used for communication between different proceses.
|
13
|
+
# @param queue [Queue]
|
14
|
+
def set_event_queue(queue)
|
15
|
+
@event_queue = queue
|
16
|
+
end
|
60
17
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
18
|
+
# The main method. Loops on all paths, each time span and monitors them.
|
19
|
+
#
|
20
|
+
# =Algorithm:
|
21
|
+
# There is a loop that performs at every iteration:
|
22
|
+
# 1.Pull entry with a minimal time of check from queue
|
23
|
+
# 2.Recursively check path taken from entry for changes
|
24
|
+
# a.Notify subscribed processes on changes
|
25
|
+
# 3.Push entry to the queue with new time of next check
|
26
|
+
#
|
27
|
+
# This methods controlled by <tt>monitoring_paths</tt> configuration parameter,
|
28
|
+
# that provides path and file monitoring configuration data
|
29
|
+
def monitor_files
|
30
|
+
conf_array = Params['monitoring_paths']
|
31
|
+
|
32
|
+
# Directories states stored in the priority queue,
|
33
|
+
# where the key (priority) is a time when it should be checked next time.
|
34
|
+
# Priority queue means that all entries arranged by key (time to check) in increasing order.
|
35
|
+
pq = Containers::PriorityQueue.new
|
36
|
+
conf_array.each { |elem|
|
37
|
+
priority = (Time.now + elem['scan_period']).to_i
|
38
|
+
dir_stat = DirStat.new(File.expand_path(elem['path']), elem['stable_state'])
|
39
|
+
dir_stat.set_event_queue(@event_queue) if @event_queue
|
40
|
+
Log.info "File monitoring started for: #{elem}"
|
41
|
+
pq.push([priority, elem, dir_stat], -priority)
|
42
|
+
}
|
43
|
+
|
44
|
+
log_path = Params['default_monitoring_log_path']
|
45
|
+
|
46
|
+
Log.info 'File monitoring log: ' + log_path
|
47
|
+
log_dir = File.dirname(log_path)
|
48
|
+
FileUtils.mkdir_p(log_dir) unless File.exists?(log_dir)
|
49
|
+
log = File.open(log_path, 'w')
|
50
|
+
FileStat.set_log(log)
|
51
|
+
|
52
|
+
while true do
|
53
|
+
# pull entry that should be checked next,
|
54
|
+
# according to it's scan_period
|
55
|
+
time, conf, dir_stat = pq.pop
|
56
|
+
|
57
|
+
# time remains to wait before directory should be checked
|
58
|
+
time_span = time - Time.now.to_i
|
59
|
+
if (time_span > 0)
|
60
|
+
sleep(time_span)
|
65
61
|
end
|
62
|
+
dir_stat.monitor
|
66
63
|
|
67
|
-
|
64
|
+
# push entry with new a next time it should be checked as a priority key
|
65
|
+
priority = (Time.now + conf['scan_period']).to_i
|
66
|
+
pq.push([priority, conf, dir_stat], -priority)
|
68
67
|
end
|
69
|
-
end
|
70
68
|
|
69
|
+
log.close
|
70
|
+
end
|
71
71
|
end
|
72
|
+
|
72
73
|
end
|
74
|
+
|
@@ -1,292 +1,291 @@
|
|
1
1
|
require 'log'
|
2
2
|
require 'params'
|
3
3
|
|
4
|
-
module
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
end
|
4
|
+
module FileMonitoring
|
5
|
+
# Path monitoring.
|
6
|
+
|
7
|
+
# Enum-like structure that includes possible filesystem entities (files/directories) states:
|
8
|
+
# * <tt>NON_EXISTING</tt> - Entity that was treated during previous run, but absent currently
|
9
|
+
# * <tt>NEW</tt> - Entity that was found and added to control during this run
|
10
|
+
# * <tt>CHANGED</tt> - State was changed between two checks
|
11
|
+
# * <tt>UNCHANGED</tt> - Opposite to CHANGED
|
12
|
+
# * <tt>STABLE</tt> - Entity is in the UNCHANGED state for a defined (by user) number of iterations
|
13
|
+
class FileStatEnum
|
14
|
+
NON_EXISTING = "NON_EXISTING"
|
15
|
+
NEW = "NEW"
|
16
|
+
CHANGED = "CHANGED"
|
17
|
+
UNCHANGED = "UNCHANGED"
|
18
|
+
STABLE = "STABLE"
|
19
|
+
end
|
21
20
|
|
22
|
-
|
23
|
-
|
24
|
-
|
21
|
+
# This class holds current state of file and methods to control and report changes
|
22
|
+
class FileStat
|
23
|
+
attr_reader :cycles, :path, :stable_state, :state, :size, :modification_time
|
25
24
|
|
26
|
-
|
25
|
+
DEFAULT_STABLE_STATE = 10
|
27
26
|
|
28
|
-
|
27
|
+
@@log = nil
|
29
28
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
29
|
+
# Initializes new file monitoring object
|
30
|
+
# ==== Arguments:
|
31
|
+
#
|
32
|
+
# * <tt>path</tt> - File location
|
33
|
+
# * <tt>stable_state</tt> - Number of iterations to move unchanged file to stable state
|
34
|
+
def initialize(path, stable_state = DEFAULT_STABLE_STATE)
|
35
|
+
@path ||= path
|
36
|
+
@size = nil
|
37
|
+
@creation_time = nil
|
38
|
+
@modification_time = nil
|
39
|
+
@cycles = 0 # number of iterations from the last file modification
|
40
|
+
@state = FileStatEnum::NON_EXISTING
|
42
41
|
|
43
|
-
|
44
|
-
|
42
|
+
@stable_state = stable_state # number of iteration to move unchanged file to stable state
|
43
|
+
end
|
45
44
|
|
46
|
-
|
47
|
-
|
48
|
-
|
45
|
+
def set_output_queue(event_queue)
|
46
|
+
@event_queue = event_queue
|
47
|
+
end
|
49
48
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
49
|
+
# Sets a log file to report changes
|
50
|
+
# ==== Arguments:
|
51
|
+
#
|
52
|
+
# * <tt>log</tt> - already opened ruby File object
|
53
|
+
def self.set_log (log)
|
54
|
+
@@log = log
|
55
|
+
end
|
57
56
|
|
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
|
-
end
|
57
|
+
# Checks whether file was changed from the last iteration.
|
58
|
+
# For files, size and modification time are checked.
|
59
|
+
def monitor
|
60
|
+
file_stats = File.lstat(@path) rescue nil
|
61
|
+
new_state = nil
|
62
|
+
if file_stats == nil
|
63
|
+
new_state = FileStatEnum::NON_EXISTING
|
64
|
+
@size = nil
|
65
|
+
@creation_time = nil
|
66
|
+
@modification_time = nil
|
67
|
+
@cycles = 0
|
68
|
+
elsif @size == nil
|
69
|
+
new_state = FileStatEnum::NEW
|
70
|
+
@size = file_stats.size
|
71
|
+
@creation_time = file_stats.ctime.utc
|
72
|
+
@modification_time = file_stats.mtime.utc
|
73
|
+
@cycles = 0
|
74
|
+
elsif changed?(file_stats)
|
75
|
+
new_state = FileStatEnum::CHANGED
|
76
|
+
@size = file_stats.size
|
77
|
+
@creation_time = file_stats.ctime.utc
|
78
|
+
@modification_time = file_stats.mtime.utc
|
79
|
+
@cycles = 0
|
80
|
+
else
|
81
|
+
new_state = FileStatEnum::UNCHANGED
|
82
|
+
@cycles += 1
|
83
|
+
if @cycles >= @stable_state
|
84
|
+
new_state = FileStatEnum::STABLE
|
87
85
|
end
|
88
|
-
|
89
|
-
# The assignment
|
90
|
-
self.state= new_state
|
91
86
|
end
|
92
87
|
|
93
|
-
#
|
94
|
-
|
95
|
-
|
96
|
-
file_stats.ctime.utc == @creation_time.utc &&
|
97
|
-
file_stats.mtime.utc == @modification_time.utc)
|
98
|
-
end
|
88
|
+
# The assignment
|
89
|
+
self.state= new_state
|
90
|
+
end
|
99
91
|
|
100
|
-
|
101
|
-
|
102
|
-
|
92
|
+
# Checks that stored file attributes are the same as file attributes taken from file system.
|
93
|
+
def changed?(file_stats)
|
94
|
+
not (file_stats.size == @size &&
|
95
|
+
file_stats.ctime.utc == @creation_time.utc &&
|
96
|
+
file_stats.mtime.utc == @modification_time.utc)
|
97
|
+
end
|
103
98
|
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
99
|
+
def set_event_queue(queue)
|
100
|
+
@event_queue = queue
|
101
|
+
end
|
102
|
+
|
103
|
+
# Sets and writes to the log a new state.
|
104
|
+
def state= (new_state)
|
105
|
+
if (@state != new_state or @state == FileStatEnum::CHANGED)
|
106
|
+
@state = new_state
|
107
|
+
if (@@log)
|
108
|
+
@@log.puts(cur_stat)
|
109
|
+
@@log.flush #Ruby1.9.3: note that this is Ruby internal buffering only; the OS may buffer the data as well
|
110
|
+
end
|
111
|
+
if (!@event_queue.nil?)
|
112
|
+
Log.info "Writing to event queue [#{self.state}, #{self.path}]"
|
113
|
+
@event_queue.push([self.state, self.instance_of?(DirStat), self.path])
|
116
114
|
end
|
117
115
|
end
|
116
|
+
end
|
118
117
|
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
118
|
+
# Checks whether path and state are the same as of the argument
|
119
|
+
def == (other)
|
120
|
+
@path == other.path and @stable_state == other.stable_state
|
121
|
+
end
|
123
122
|
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
123
|
+
# Returns path and state of the file with indentation
|
124
|
+
def to_s (indent = 0)
|
125
|
+
(" " * indent) + path.to_s + " : " + state.to_s
|
126
|
+
end
|
128
127
|
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
end
|
128
|
+
# Reports current state with identification.
|
129
|
+
# NOTE This format used by log file.
|
130
|
+
def cur_stat
|
131
|
+
# TODO what output format have to be ?
|
132
|
+
Time.now.utc.to_s + " : " + self.state + " : " + self.path
|
135
133
|
end
|
134
|
+
end
|
136
135
|
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
136
|
+
# This class holds current state of directory and methods to control changes
|
137
|
+
class DirStat < FileStat
|
138
|
+
# Initializes new directory monitoring object
|
139
|
+
# ==== Arguments:
|
140
|
+
#
|
141
|
+
# * <tt>path</tt> - File location
|
142
|
+
# * <tt>stable_state</tt> - Number of iterations to move unchanged directory to stable state
|
143
|
+
def initialize(path, stable_state = DEFAULT_STABLE_STATE)
|
144
|
+
super
|
145
|
+
@dirs = nil
|
146
|
+
@files = nil
|
147
|
+
end
|
149
148
|
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
149
|
+
# Adds directory for monitoring.
|
150
|
+
def add_dir (dir)
|
151
|
+
@dirs[dir.path] = dir
|
152
|
+
end
|
154
153
|
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
154
|
+
# Adds file for monitoring.
|
155
|
+
def add_file (file)
|
156
|
+
@files[file.path] = file
|
157
|
+
end
|
159
158
|
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
159
|
+
# Removes directory from monitoring.
|
160
|
+
def rm_dir(dir)
|
161
|
+
@dirs.delete(dir.path)
|
162
|
+
end
|
164
163
|
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
164
|
+
# Removes file from monitoring.
|
165
|
+
def rm_file(file)
|
166
|
+
@files.delete(file.path)
|
167
|
+
end
|
169
168
|
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
169
|
+
# Checks that there is a sub-folder with a given path.
|
170
|
+
def has_dir?(path)
|
171
|
+
@dirs.has_key?(path)
|
172
|
+
end
|
174
173
|
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
174
|
+
# Checks that there is a file with a given path.
|
175
|
+
def has_file?(path)
|
176
|
+
@files.has_key?(path)
|
177
|
+
end
|
179
178
|
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
179
|
+
# Returns string which contains path and state of this directory as well as it's structure.
|
180
|
+
def to_s(indent = 0)
|
181
|
+
indent_increment = 2
|
182
|
+
child_indent = indent + indent_increment
|
183
|
+
res = super
|
184
|
+
@files.each_value do |file|
|
185
|
+
res += "\n" + file.to_s(child_ident)
|
186
|
+
end if @files
|
187
|
+
@dirs.each_value do |dir|
|
188
|
+
res += "\n" + dir.to_s(child_ident)
|
189
|
+
end if @dirs
|
190
|
+
res
|
191
|
+
end
|
193
192
|
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
end
|
193
|
+
# Checks that directory structure (i.e. files and directories located directly under this directory)
|
194
|
+
# wasn't changed since the last iteration.
|
195
|
+
def monitor
|
196
|
+
was_changed = false
|
197
|
+
new_state = nil
|
198
|
+
self_stat = File.lstat(@path) rescue nil
|
199
|
+
if self_stat == nil
|
200
|
+
new_state = FileStatEnum::NON_EXISTING
|
201
|
+
@files = nil
|
202
|
+
@dirs = nil
|
203
|
+
@cycles = 0
|
204
|
+
elsif @files == nil
|
205
|
+
new_state = FileStatEnum::NEW
|
206
|
+
@files = Hash.new
|
207
|
+
@dirs = Hash.new
|
208
|
+
@cycles = 0
|
209
|
+
update_dir
|
210
|
+
elsif update_dir
|
211
|
+
new_state = FileStatEnum::CHANGED
|
212
|
+
@cycles = 0
|
213
|
+
else
|
214
|
+
new_state = FileStatEnum::UNCHANGED
|
215
|
+
@cycles += 1
|
216
|
+
if @cycles >= @stable_state
|
217
|
+
new_state = FileStatEnum::STABLE
|
220
218
|
end
|
221
|
-
|
222
|
-
# The assignment
|
223
|
-
self.state= new_state
|
224
219
|
end
|
225
220
|
|
226
|
-
#
|
227
|
-
|
228
|
-
|
221
|
+
# The assignment
|
222
|
+
self.state= new_state
|
223
|
+
end
|
229
224
|
|
230
|
-
|
231
|
-
|
232
|
-
|
225
|
+
# Updates the files and directories hashes and globs the directory for changes.
|
226
|
+
def update_dir
|
227
|
+
was_changed = false
|
233
228
|
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
229
|
+
# monitor existing and absent files
|
230
|
+
@files.each_value do |file|
|
231
|
+
file.monitor
|
232
|
+
|
233
|
+
if file.state == FileStatEnum::NON_EXISTING
|
234
|
+
was_changed = true
|
235
|
+
rm_file(file)
|
238
236
|
end
|
237
|
+
end
|
239
238
|
|
240
|
-
|
241
|
-
|
239
|
+
@dirs.each_value do |dir|
|
240
|
+
dir.monitor
|
242
241
|
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
end
|
242
|
+
if dir.state == FileStatEnum::NON_EXISTING
|
243
|
+
was_changed = true
|
244
|
+
rm_dir(dir)
|
247
245
|
end
|
246
|
+
end
|
248
247
|
|
249
|
-
|
248
|
+
was_changed = was_changed || glob_me
|
250
249
|
|
251
|
-
|
252
|
-
|
250
|
+
return was_changed
|
251
|
+
end
|
253
252
|
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
end
|
253
|
+
# Globs the directory for new files and directories
|
254
|
+
def glob_me
|
255
|
+
was_changed = false
|
256
|
+
files = Dir.glob(path + "/*")
|
257
|
+
|
258
|
+
# add and monitor new files and directories
|
259
|
+
files.each do |file|
|
260
|
+
file_stat = File.lstat(file) rescue nil
|
261
|
+
if (file_stat.directory?)
|
262
|
+
unless (has_dir?(file)) # new directory
|
263
|
+
# change state only for existing directories
|
264
|
+
# newly added directories have to remain with NEW state
|
265
|
+
was_changed = true
|
266
|
+
ds = DirStat.new(file, self.stable_state)
|
267
|
+
ds.set_event_queue(@event_queue) unless @event_queue.nil?
|
268
|
+
ds.monitor
|
269
|
+
add_dir(ds)
|
270
|
+
end
|
271
|
+
else # it is a file
|
272
|
+
unless(has_file?(file)) # new file
|
273
|
+
# change state only for existing directories
|
274
|
+
# newly added directories have to remain with NEW state
|
275
|
+
was_changed = true
|
276
|
+
fs = FileStat.new(file, self.stable_state)
|
277
|
+
fs.set_event_queue(@event_queue) unless @event_queue.nil?
|
278
|
+
fs.monitor
|
279
|
+
add_file(fs)
|
282
280
|
end
|
283
281
|
end
|
284
|
-
|
285
|
-
return was_changed
|
286
282
|
end
|
287
283
|
|
288
|
-
|
284
|
+
return was_changed
|
289
285
|
end
|
290
286
|
|
287
|
+
protected :add_dir, :add_file, :rm_dir, :rm_file, :update_dir, :glob_me
|
291
288
|
end
|
289
|
+
|
292
290
|
end
|
291
|
+
|
@@ -2,156 +2,152 @@ require './lib/file_monitoring/monitor_path'
|
|
2
2
|
require 'test/unit'
|
3
3
|
require 'fileutils'
|
4
4
|
|
5
|
-
module
|
6
|
-
module
|
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
|
-
content.push(sprintf('%5d ', i))
|
44
|
-
end
|
45
|
-
file.puts(content)
|
46
|
-
end
|
47
|
-
|
48
|
-
@numb_of_copies.times do |i|
|
49
|
-
::FileUtils.cp(file_path, "#{file_path}.#{i}")
|
5
|
+
module FileMonitoring
|
6
|
+
module Test
|
7
|
+
class TestPathMonitor < ::Test::Unit::TestCase
|
8
|
+
# directory where tested files will be placed: __FILE__/time_modification_test
|
9
|
+
RESOURCES_DIR = File.expand_path(File.dirname(__FILE__) + '/path_monitor_test')
|
10
|
+
MOVE_DIR = '/dir1500' # directory that will be moved
|
11
|
+
MOVE_FILE = '/test_file.1000' # file that will be moved
|
12
|
+
MOVE_SRC_DIR = RESOURCES_DIR + '/dir1000' # directory where moved entities where placed
|
13
|
+
MOVE_DEST_DIR = RESOURCES_DIR # directory where moved entities will be placed
|
14
|
+
LOG_PATH = RESOURCES_DIR + '/../log.txt'
|
15
|
+
|
16
|
+
def setup
|
17
|
+
@sizes = [500, 1000, 1500]
|
18
|
+
@numb_of_copies = 2
|
19
|
+
@numb_entities = @sizes.size * (@numb_of_copies + 1) + @sizes.size
|
20
|
+
@test_file_name = 'test_file' # file name format: <test_file_name_prefix>.<size>[.serial_number_if_more_then_1]
|
21
|
+
@test_dir_name = 'dir'
|
22
|
+
::FileUtils.rm_rf(RESOURCES_DIR) if (File.exists?(RESOURCES_DIR))
|
23
|
+
|
24
|
+
# prepare files for testing
|
25
|
+
cur_dir = nil; # directory where currently files created in this iteration will be placed
|
26
|
+
|
27
|
+
@sizes.each do |size|
|
28
|
+
file_name = @test_file_name + '.' + size.to_s
|
29
|
+
|
30
|
+
if (cur_dir == nil)
|
31
|
+
cur_dir = String.new(RESOURCES_DIR)
|
32
|
+
else
|
33
|
+
cur_dir = cur_dir + "/#{@test_dir_name}" + size.to_s
|
34
|
+
end
|
35
|
+
Dir.mkdir(cur_dir) unless (File.exists?(cur_dir))
|
36
|
+
raise "Can't create writable working directory: #{cur_dir}" unless (File.exists?(cur_dir) and File.writable?(cur_dir))
|
37
|
+
|
38
|
+
file_path = cur_dir + '/' + file_name
|
39
|
+
File.open(file_path, 'w') do |file|
|
40
|
+
content = Array.new
|
41
|
+
size.times do |i|
|
42
|
+
content.push(sprintf('%5d ', i))
|
50
43
|
end
|
44
|
+
file.puts(content)
|
51
45
|
end
|
52
|
-
end
|
53
|
-
|
54
|
-
def test_monitor
|
55
|
-
log = File.open(LOG_PATH, 'w+')
|
56
|
-
FileStat.set_log(log)
|
57
|
-
test_dir = DirStat.new(RESOURCES_DIR)
|
58
|
-
|
59
|
-
# Initial run of monitoring -> initializing DirStat object
|
60
|
-
# all found object will be set to NEW state
|
61
|
-
# no output to the log
|
62
|
-
test_dir.monitor
|
63
46
|
|
64
|
-
|
65
|
-
|
66
|
-
log_prev_line = log.lineno
|
67
|
-
test_dir.monitor
|
68
|
-
sleep(1) # to be sure that data was indeed written
|
69
|
-
log.pos= log_prev_pos
|
70
|
-
log.each_line do |line|
|
71
|
-
assert_equal(true, line.include?(FileStatEnum::UNCHANGED))
|
47
|
+
@numb_of_copies.times do |i|
|
48
|
+
::FileUtils.cp(file_path, "#{file_path}.#{i}")
|
72
49
|
end
|
73
|
-
|
50
|
+
end
|
51
|
+
end
|
74
52
|
|
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
|
-
|
53
|
+
def test_monitor
|
54
|
+
log = File.open(LOG_PATH, 'w+')
|
55
|
+
FileStat.set_log(log)
|
56
|
+
test_dir = DirStat.new(RESOURCES_DIR)
|
57
|
+
|
58
|
+
# Initial run of monitoring -> initializing DirStat object
|
59
|
+
# all found object will be set to NEW state
|
60
|
+
# no output to the log
|
61
|
+
test_dir.monitor
|
62
|
+
|
63
|
+
# all files will be set to UNCHANGED
|
64
|
+
log_prev_pos = log.pos
|
65
|
+
log_prev_line = log.lineno
|
66
|
+
test_dir.monitor
|
67
|
+
sleep(1) # to be sure that data was indeed written
|
68
|
+
log.pos= log_prev_pos
|
69
|
+
log.each_line do |line|
|
70
|
+
assert_equal(true, line.include?(FileStatEnum::UNCHANGED))
|
71
|
+
end
|
72
|
+
assert_equal(@numb_entities, log.lineno - log_prev_line) # checking that all entities were monitored
|
73
|
+
|
74
|
+
# move (equivalent to delete and create new) directory with including files to new location
|
75
|
+
::FileUtils.mv MOVE_SRC_DIR + MOVE_DIR, MOVE_DEST_DIR + MOVE_DIR
|
76
|
+
log_prev_pos = log.pos
|
77
|
+
log_prev_line = log.lineno
|
78
|
+
test_dir.monitor
|
79
|
+
sleep(1)
|
80
|
+
log.pos= log_prev_pos
|
81
|
+
log.each_line do |line|
|
82
|
+
if (line.include?(MOVE_SRC_DIR + MOVE_DIR))
|
83
|
+
assert_equal(true, line.include?(FileStatEnum::NON_EXISTING))
|
84
|
+
elsif (line.include?(MOVE_DEST_DIR + MOVE_DIR))
|
85
|
+
assert_equal(true, line.include?(FileStatEnum::NEW))
|
86
|
+
elsif (line.include?(MOVE_SRC_DIR) or line.include?(MOVE_DEST_DIR))
|
87
|
+
assert_not_equal(true, line.include?(FileStatEnum::UNCHANGED))
|
88
|
+
assert_equal(true, line.include?(FileStatEnum::CHANGED))
|
107
89
|
end
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
90
|
+
end
|
91
|
+
# old and new containing directories: 2
|
92
|
+
# moved directory: 1
|
93
|
+
# deleted directory: 1
|
94
|
+
# moved files: @numb_of_copies + 1
|
95
|
+
assert_equal(5 + @numb_of_copies, log.lineno - log_prev_line) # checking that only MOVE_DIR_SRC, MOVE_DIR_DEST states were changed
|
96
|
+
|
97
|
+
# no changes:
|
98
|
+
# changed and new files moved to UNCHANGED
|
99
|
+
log_prev_pos = log.pos
|
100
|
+
log_prev_line = log.lineno
|
101
|
+
test_dir.monitor
|
102
|
+
sleep(1)
|
103
|
+
log.pos= log_prev_pos
|
104
|
+
log.each_line do |line|
|
105
|
+
assert_equal(true, line.include?(FileStatEnum::UNCHANGED))
|
106
|
+
end
|
107
|
+
# old and new containing directories: 2
|
108
|
+
# moved directory: 1
|
109
|
+
# moved files: @numb_of_copies + 1
|
110
|
+
assert_equal(4 + @numb_of_copies, log.lineno - log_prev_line)
|
111
|
+
|
112
|
+
# move (equivalent to delete and create new) file
|
113
|
+
log_prev_pos = log.pos
|
114
|
+
log_prev_line = log.lineno
|
115
|
+
::FileUtils.mv MOVE_SRC_DIR + MOVE_FILE, MOVE_DEST_DIR + MOVE_FILE
|
116
|
+
test_dir.monitor
|
117
|
+
sleep(1)
|
118
|
+
log.pos= log_prev_pos
|
119
|
+
log.each_line do |line|
|
120
|
+
if (line.include?MOVE_SRC_DIR + MOVE_FILE)
|
121
|
+
assert_equal(true, line.include?(FileStatEnum::NON_EXISTING))
|
122
|
+
elsif (line.include?MOVE_DEST_DIR + MOVE_FILE)
|
123
|
+
assert_equal(true, line.include?(FileStatEnum::NEW))
|
124
|
+
elsif (line.include?MOVE_SRC_DIR or line.include?MOVE_DEST_DIR)
|
125
|
+
assert_equal(true, line.include?(FileStatEnum::CHANGED))
|
128
126
|
end
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
127
|
+
end
|
128
|
+
# old and new containing directories: 2
|
129
|
+
# removed file: 1
|
130
|
+
# new file: 1
|
131
|
+
assert_equal(4, log.lineno - log_prev_line)
|
132
|
+
|
133
|
+
# all files were moved to UNCHANGED
|
134
|
+
test_dir.monitor
|
135
|
+
|
136
|
+
# check that all entities moved to stable
|
137
|
+
log_prev_pos = log.pos
|
138
|
+
log_prev_line = log.lineno
|
139
|
+
(test_dir.stable_state + 1).times do
|
135
140
|
test_dir.monitor
|
136
|
-
|
137
|
-
# check that all entities moved to stable
|
138
|
-
log_prev_pos = log.pos
|
139
|
-
log_prev_line = log.lineno
|
140
|
-
(test_dir.stable_state + 1).times do
|
141
|
-
test_dir.monitor
|
142
|
-
end
|
143
|
-
sleep(1)
|
144
|
-
log.pos= log_prev_pos
|
145
|
-
log.each_line do |line|
|
146
|
-
assert_equal(true, line.include?(FileStatEnum::STABLE))
|
147
|
-
end
|
148
|
-
assert_equal(@numb_entities, log.lineno - log_prev_line)
|
149
|
-
|
150
|
-
log.close
|
151
141
|
end
|
152
|
-
|
153
|
-
|
142
|
+
sleep(1)
|
143
|
+
log.pos= log_prev_pos
|
144
|
+
log.each_line do |line|
|
145
|
+
assert_equal(true, line.include?(FileStatEnum::STABLE))
|
146
|
+
end
|
147
|
+
assert_equal(@numb_entities, log.lineno - log_prev_line)
|
154
148
|
|
149
|
+
log.close
|
150
|
+
end
|
155
151
|
end
|
156
152
|
end
|
157
153
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: file_monitoring
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 1.0.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -76,33 +76,32 @@ dependencies:
|
|
76
76
|
- !ruby/object:Gem::Version
|
77
77
|
version: '0'
|
78
78
|
description: Deamon for monitoring file changes in set of patterns (blobs).
|
79
|
-
email:
|
79
|
+
email: bbfsdev@gmail.com
|
80
80
|
executables:
|
81
81
|
- file_monitoring
|
82
82
|
extensions: []
|
83
83
|
extra_rdoc_files: []
|
84
84
|
files:
|
85
85
|
- lib/file_monitoring.rb
|
86
|
-
- lib/file_monitoring/daemon_win32.rb
|
87
|
-
- lib/file_monitoring/file_monitoring.rb
|
88
86
|
- lib/file_monitoring/monitor_path.rb
|
89
87
|
- lib/file_monitoring/version.rb
|
88
|
+
- lib/file_monitoring/file_monitoring.rb
|
90
89
|
- test/file_monitoring/file_monitoring_test/conf.yml
|
91
|
-
- test/file_monitoring/file_monitoring_test/conf_win32.yml
|
92
90
|
- test/file_monitoring/file_monitoring_test/log
|
93
|
-
- test/file_monitoring/file_monitoring_test.
|
94
|
-
- test/file_monitoring/monitor_path_test/
|
95
|
-
- test/file_monitoring/monitor_path_test/dir1000/test_file.1000.0
|
91
|
+
- test/file_monitoring/file_monitoring_test/conf_win32.yml
|
92
|
+
- test/file_monitoring/monitor_path_test/test_file.500.0
|
96
93
|
- test/file_monitoring/monitor_path_test/dir1000/test_file.1000.1
|
94
|
+
- test/file_monitoring/monitor_path_test/dir1000/test_file.1000.0
|
95
|
+
- test/file_monitoring/monitor_path_test/dir1000/test_file.1000
|
96
|
+
- test/file_monitoring/monitor_path_test/test_file.500.1
|
97
|
+
- test/file_monitoring/monitor_path_test/test_file.500
|
97
98
|
- test/file_monitoring/monitor_path_test/dir1500/test_file.1500
|
98
99
|
- test/file_monitoring/monitor_path_test/dir1500/test_file.1500.0
|
99
100
|
- test/file_monitoring/monitor_path_test/dir1500/test_file.1500.1
|
100
|
-
- test/file_monitoring/
|
101
|
-
- test/file_monitoring/monitor_path_test/test_file.500.0
|
102
|
-
- test/file_monitoring/monitor_path_test/test_file.500.1
|
101
|
+
- test/file_monitoring/file_monitoring_test.rb
|
103
102
|
- test/file_monitoring/monitor_path_test.rb
|
104
103
|
- bin/file_monitoring
|
105
|
-
homepage: http://github.com/
|
104
|
+
homepage: http://github.com/bbfsdev/bbfs
|
106
105
|
licenses: []
|
107
106
|
post_install_message:
|
108
107
|
rdoc_options: []
|
@@ -128,16 +127,16 @@ specification_version: 3
|
|
128
127
|
summary: Deamon for monitoring file changes.
|
129
128
|
test_files:
|
130
129
|
- test/file_monitoring/file_monitoring_test/conf.yml
|
131
|
-
- test/file_monitoring/file_monitoring_test/conf_win32.yml
|
132
130
|
- test/file_monitoring/file_monitoring_test/log
|
133
|
-
- test/file_monitoring/file_monitoring_test.
|
134
|
-
- test/file_monitoring/monitor_path_test/
|
135
|
-
- test/file_monitoring/monitor_path_test/dir1000/test_file.1000.0
|
131
|
+
- test/file_monitoring/file_monitoring_test/conf_win32.yml
|
132
|
+
- test/file_monitoring/monitor_path_test/test_file.500.0
|
136
133
|
- test/file_monitoring/monitor_path_test/dir1000/test_file.1000.1
|
134
|
+
- test/file_monitoring/monitor_path_test/dir1000/test_file.1000.0
|
135
|
+
- test/file_monitoring/monitor_path_test/dir1000/test_file.1000
|
136
|
+
- test/file_monitoring/monitor_path_test/test_file.500.1
|
137
|
+
- test/file_monitoring/monitor_path_test/test_file.500
|
137
138
|
- test/file_monitoring/monitor_path_test/dir1500/test_file.1500
|
138
139
|
- test/file_monitoring/monitor_path_test/dir1500/test_file.1500.0
|
139
140
|
- test/file_monitoring/monitor_path_test/dir1500/test_file.1500.1
|
140
|
-
- test/file_monitoring/
|
141
|
-
- test/file_monitoring/monitor_path_test/test_file.500.0
|
142
|
-
- test/file_monitoring/monitor_path_test/test_file.500.1
|
141
|
+
- test/file_monitoring/file_monitoring_test.rb
|
143
142
|
- test/file_monitoring/monitor_path_test.rb
|
@@ -1,29 +0,0 @@
|
|
1
|
-
require 'win32/daemon'
|
2
|
-
include Win32
|
3
|
-
require 'rubygems'
|
4
|
-
|
5
|
-
begin
|
6
|
-
require "./file_monitoring/file_monitoring.rb"
|
7
|
-
|
8
|
-
conf_file_path = (ARGV.length > 0 ? "#{ARGV[0]}" : '~/.bbfs/etc/file_monitoring.yml')
|
9
|
-
conf_file_path = File.expand_path(conf_file_path)
|
10
|
-
|
11
|
-
CONFIG_FILE_PATH = "#{conf_file_path}"
|
12
|
-
|
13
|
-
class Daemon
|
14
|
-
def service_main
|
15
|
-
while running?
|
16
|
-
monitor_files(CONFIG_FILE_PATH)
|
17
|
-
end
|
18
|
-
end
|
19
|
-
|
20
|
-
def service_stop
|
21
|
-
exit!
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
|
-
Daemon.mainloop
|
26
|
-
|
27
|
-
rescue Exception => err
|
28
|
-
raise
|
29
|
-
end
|