file_monitoring 0.0.9 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|