file_monitoring 0.0.2
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 +59 -0
- data/lib/file_monitoring/file_monitoring.rb +47 -0
- data/lib/file_monitoring/monitor_path.rb +265 -0
- metadata +91 -0
data/bin/file_monitoring
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# Monitors add/remove/update of files and directories.
|
4
|
+
# File monitoring checks file modification date and size.
|
5
|
+
# If one of the above changed, the file considered as changed.
|
6
|
+
# A log file is written with all incremental changes of files.
|
7
|
+
#
|
8
|
+
# Install:
|
9
|
+
# gem build file_monitoring.gemspec
|
10
|
+
# gem install file_monitoring
|
11
|
+
#
|
12
|
+
# Run: file_monitoring {conf_file} {app suffix}
|
13
|
+
# Examples:
|
14
|
+
# file_monitoring
|
15
|
+
# or
|
16
|
+
# file_monitoring conf.yml
|
17
|
+
# or
|
18
|
+
# file_monitoring conf.yml my_instance
|
19
|
+
#
|
20
|
+
# Note that scan period is in seconds.
|
21
|
+
# cong.yml structure:
|
22
|
+
# log_path: ~/my_monitor_log_dir/log
|
23
|
+
# paths:
|
24
|
+
# - path: /a_path/one
|
25
|
+
# scan_period: 100
|
26
|
+
# stable_state: 5
|
27
|
+
# - path: /a_path/two
|
28
|
+
# scan_period: 600
|
29
|
+
# stable_state: 3
|
30
|
+
|
31
|
+
require 'rubygems'
|
32
|
+
require 'daemons'
|
33
|
+
require 'file_monitoring/file_monitoring.rb'
|
34
|
+
|
35
|
+
conf_file_path = File.expand_path('~/.bbfs/etc/file_monitoring.yml')
|
36
|
+
if ARGV.length > 1
|
37
|
+
conf_file_path = File.expand_path(ARGV[1])
|
38
|
+
end
|
39
|
+
|
40
|
+
daemon_suffix = ''
|
41
|
+
if ARGV.length > 2
|
42
|
+
daemon_suffix = '_' + ARGV[2]
|
43
|
+
end
|
44
|
+
|
45
|
+
puts "Config taken from:" + conf_file_path
|
46
|
+
pid_dir = File.expand_path('~/.bbfs/run/' + daemon_suffix)
|
47
|
+
FileUtils.mkdir_p(pid_dir)
|
48
|
+
puts "pid dir:" + pid_dir
|
49
|
+
|
50
|
+
Daemons.run_proc(
|
51
|
+
'file_monitoring' + daemon_suffix, # name of daemon
|
52
|
+
:dir_mode => :normal,
|
53
|
+
:dir => pid_dir, # directory where pid file will be stored
|
54
|
+
# :backtrace => true,
|
55
|
+
# :monitor => true,
|
56
|
+
# :log_output => true
|
57
|
+
) do
|
58
|
+
monitor_files(conf_file_path)
|
59
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'file_monitoring/monitor_path.rb'
|
2
|
+
require 'algorithms'
|
3
|
+
require 'fileutils'
|
4
|
+
require 'yaml'
|
5
|
+
|
6
|
+
# The main method. Loops on all paths each time span and monitors them.
|
7
|
+
def monitor_files(config_path)
|
8
|
+
config_yml = YAML::load_file(config_path)
|
9
|
+
conf_array = config_yml["paths"]
|
10
|
+
|
11
|
+
pq = Containers::PriorityQueue.new
|
12
|
+
conf_array.each { |elem|
|
13
|
+
priority = (Time.now + elem["scan_period"]).to_i
|
14
|
+
pq.push([priority, elem, DirStat.new(elem["path"], elem["stable_state"])], -priority)
|
15
|
+
}
|
16
|
+
|
17
|
+
log_path = File.expand_path("~/.bbfs/log/file_monitoring.log")
|
18
|
+
if config_yml.key?("log_path")
|
19
|
+
log_path = File.expand_path(config_yml["log_path"])
|
20
|
+
end
|
21
|
+
|
22
|
+
puts "Log path:" + log_path
|
23
|
+
FileUtils.mkdir_p(File.dirname(log_path))
|
24
|
+
log = File.open(log_path, 'w')
|
25
|
+
FileStat.set_log(log)
|
26
|
+
|
27
|
+
while true do
|
28
|
+
time, conf, dir_stat = pq.pop
|
29
|
+
#puts "time:" + time.to_s()
|
30
|
+
#puts "now:" + Time.now.to_i.to_s()
|
31
|
+
#puts conf
|
32
|
+
|
33
|
+
time_span = time - Time.now.to_i
|
34
|
+
if (time_span > 0)
|
35
|
+
sleep(time_span)
|
36
|
+
end
|
37
|
+
|
38
|
+
dir_stat.monitor
|
39
|
+
|
40
|
+
#puts conf["path"]
|
41
|
+
#puts conf["scan_period"]
|
42
|
+
priority = (Time.now + conf["scan_period"]).to_i
|
43
|
+
pq.push([priority, conf, dir_stat], -priority)
|
44
|
+
end
|
45
|
+
|
46
|
+
log.close
|
47
|
+
end
|
@@ -0,0 +1,265 @@
|
|
1
|
+
# Path monitoring.
|
2
|
+
|
3
|
+
# Enum-like structure that includes possible filesystem entities (files/directories) states:
|
4
|
+
# * <tt>NON_EXISTING</tt> - Entity that was treated during previous run, but absent currently
|
5
|
+
# * <tt>NEW</tt> - Entity that was found and added to control during this run
|
6
|
+
# * <tt>CHANGED</tt> - State was changed between two checks
|
7
|
+
# * <tt>UNCHANGED</tt> - Opposite to CHANGED
|
8
|
+
# * <tt>STABLE</tt> - Entity is in the UNCHANGED state for a defined (by user) number of iterations
|
9
|
+
class FileStatEnum
|
10
|
+
NON_EXISTING = "NON_EXISTING"
|
11
|
+
NEW = "NEW"
|
12
|
+
CHANGED = "CHANGED"
|
13
|
+
UNCHANGED = "UNCHANGED"
|
14
|
+
STABLE = "STABLE"
|
15
|
+
end
|
16
|
+
|
17
|
+
# This class holds current state of file and methods to control and report changes
|
18
|
+
class FileStat
|
19
|
+
attr_reader :cycles, :path, :stable_state, :state, :size, :modification_time
|
20
|
+
|
21
|
+
DEFAULT_STABLE_STATE = 10
|
22
|
+
|
23
|
+
@@log = nil
|
24
|
+
|
25
|
+
# Initializes new file monitoring object
|
26
|
+
# ==== Arguments:
|
27
|
+
#
|
28
|
+
# * <tt>path</tt> - File location
|
29
|
+
# * <tt>stable_state</tt> - Number of iterations to move unchanged file to stable state
|
30
|
+
def initialize(path, stable_state = DEFAULT_STABLE_STATE)
|
31
|
+
@path ||= path
|
32
|
+
@size = nil
|
33
|
+
@modification_time = nil
|
34
|
+
@cycles = 0 # number of iterations from the last file modification
|
35
|
+
@state = FileStatEnum::NON_EXISTING
|
36
|
+
|
37
|
+
@stable_state = stable_state # number of iteration to move unchanged file to stable state
|
38
|
+
end
|
39
|
+
|
40
|
+
# Sets a log file to report changes
|
41
|
+
# ==== Arguments:
|
42
|
+
#
|
43
|
+
# * <tt>log</tt> - already opened ruby File object
|
44
|
+
def self.set_log (log)
|
45
|
+
@@log = log
|
46
|
+
end
|
47
|
+
|
48
|
+
# Checks whether file was changed from the last iteration.
|
49
|
+
# For files size and modification time are checked.
|
50
|
+
def monitor
|
51
|
+
file_stats = File.lstat(@path) rescue nil
|
52
|
+
new_state = nil
|
53
|
+
if file_stats == nil
|
54
|
+
new_state = FileStatEnum::NON_EXISTING
|
55
|
+
@size = nil
|
56
|
+
@modification_time = nil
|
57
|
+
@cycles = 0
|
58
|
+
elsif @size == nil
|
59
|
+
new_state = FileStatEnum::NEW
|
60
|
+
@size = file_stats.size
|
61
|
+
@modification_time = file_stats.mtime.utc
|
62
|
+
@cycles = 0
|
63
|
+
elsif changed?(file_stats)
|
64
|
+
new_state = FileStatEnum::CHANGED
|
65
|
+
@size = file_stats.size
|
66
|
+
@modification_time = file_stats.mtime.utc
|
67
|
+
@cycles = 0
|
68
|
+
else
|
69
|
+
new_state = FileStatEnum::UNCHANGED
|
70
|
+
@cycles += 1
|
71
|
+
if @cycles >= @stable_state
|
72
|
+
new_state = FileStatEnum::STABLE
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# The assignment
|
77
|
+
self.state= new_state
|
78
|
+
end
|
79
|
+
|
80
|
+
# Checks that stored file attributes are the same as file attributes taken from file system.
|
81
|
+
def changed?(file_stats)
|
82
|
+
not (file_stats.size == size and file_stats.mtime.utc == modification_time.utc)
|
83
|
+
end
|
84
|
+
|
85
|
+
# Sets and writes to the log a new state.
|
86
|
+
def state= (new_state)
|
87
|
+
if (@state != new_state or @state == FileStatEnum::CHANGED)
|
88
|
+
@state = new_state
|
89
|
+
if (@@log)
|
90
|
+
@@log.puts(cur_stat)
|
91
|
+
@@log.flush #Ruby1.9.3: note that this is Ruby internal buffering only; the OS may buffer the data as well
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
# Checks whether path and state are the same as of the argument
|
97
|
+
def == (other)
|
98
|
+
@path == other.path and @stable_state == other.stable_state
|
99
|
+
end
|
100
|
+
|
101
|
+
# Returns path and state of the file with indentation
|
102
|
+
def to_s (indent = 0)
|
103
|
+
(" " * indent) + path.to_s + " : " + state.to_s
|
104
|
+
end
|
105
|
+
|
106
|
+
# Reports current state with identification.
|
107
|
+
# # This format used by log file.
|
108
|
+
def cur_stat
|
109
|
+
# TODO what output format have to be ?
|
110
|
+
Time.now.utc.to_s + " : " + self.state + " : " + self.path
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
# This class holds current state of directory and methods to control changes
|
115
|
+
class DirStat < FileStat
|
116
|
+
# Initializes new directory monitoring object
|
117
|
+
# ==== Arguments:
|
118
|
+
#
|
119
|
+
# * <tt>path</tt> - File location
|
120
|
+
# * <tt>stable_state</tt> - Number of iterations to move unchanged directory to stable state
|
121
|
+
def initialize(path, stable_state = DEFAULT_STABLE_STATE)
|
122
|
+
super
|
123
|
+
@dirs = nil
|
124
|
+
@files = nil
|
125
|
+
end
|
126
|
+
|
127
|
+
# Adds directory for monitoring.
|
128
|
+
def add_dir (dir)
|
129
|
+
@dirs[dir.path] = dir
|
130
|
+
end
|
131
|
+
|
132
|
+
# Adds file for monitoring.
|
133
|
+
def add_file (file)
|
134
|
+
@files[file.path] = file
|
135
|
+
end
|
136
|
+
|
137
|
+
# Removes directory from monitoring.
|
138
|
+
def rm_dir(dir)
|
139
|
+
@dirs.delete(dir.path)
|
140
|
+
end
|
141
|
+
|
142
|
+
# Removes file from monitoring.
|
143
|
+
def rm_file(file)
|
144
|
+
@files.delete(file.path)
|
145
|
+
end
|
146
|
+
|
147
|
+
# Checks that there is a sub-folder with a given path.
|
148
|
+
def has_dir?(path)
|
149
|
+
@dirs.has_key?(path)
|
150
|
+
end
|
151
|
+
|
152
|
+
# Checks that there is a file with a given path.
|
153
|
+
def has_file?(path)
|
154
|
+
@files.has_key?(path)
|
155
|
+
end
|
156
|
+
|
157
|
+
# Returns string with contains path and state of this directory as well as it's structure.
|
158
|
+
def to_s(indent = 0)
|
159
|
+
indent_increment = 2
|
160
|
+
child_indent = indent + indent_increment
|
161
|
+
res = super
|
162
|
+
@files.each_value do |file|
|
163
|
+
res += "\n" + file.to_s(child_ident)
|
164
|
+
end
|
165
|
+
@dirs.each_value do |dir|
|
166
|
+
res += "\n" + dir.to_s(child_ident)
|
167
|
+
end
|
168
|
+
res
|
169
|
+
end
|
170
|
+
|
171
|
+
# Checks that directory structure (i.e. files and directories located directly under this directory)
|
172
|
+
# wasn't changed since the last iteration.
|
173
|
+
def monitor
|
174
|
+
was_changed = false
|
175
|
+
new_state = nil
|
176
|
+
self_stat = File.lstat(@path) rescue nil
|
177
|
+
if self_stat == nil
|
178
|
+
new_state = FileStatEnum::NON_EXISTING
|
179
|
+
@files = nil
|
180
|
+
@dirs = nil
|
181
|
+
@cycles = 0
|
182
|
+
elsif @files == nil
|
183
|
+
new_state = FileStatEnum::NEW
|
184
|
+
@files = Hash.new
|
185
|
+
@dirs = Hash.new
|
186
|
+
@cycles = 0
|
187
|
+
update_dir
|
188
|
+
elsif update_dir
|
189
|
+
new_state = FileStatEnum::CHANGED
|
190
|
+
@cycles = 0
|
191
|
+
else
|
192
|
+
new_state = FileStatEnum::UNCHANGED
|
193
|
+
@cycles += 1
|
194
|
+
if @cycles >= @stable_state
|
195
|
+
new_state = FileStatEnum::STABLE
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
# The assignment
|
200
|
+
self.state= new_state
|
201
|
+
end
|
202
|
+
|
203
|
+
# Updates the files and directories hashes and globs the directory for changes.
|
204
|
+
def update_dir
|
205
|
+
was_changed = false
|
206
|
+
|
207
|
+
# monitor existing and absent files
|
208
|
+
@files.each_value do |file|
|
209
|
+
file.monitor
|
210
|
+
|
211
|
+
if file.state == FileStatEnum::NON_EXISTING
|
212
|
+
was_changed = true
|
213
|
+
rm_file(file)
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
@dirs.each_value do |dir|
|
218
|
+
dir.monitor
|
219
|
+
|
220
|
+
if dir.state == FileStatEnum::NON_EXISTING
|
221
|
+
was_changed = true
|
222
|
+
rm_dir(dir)
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
was_changed = was_changed || glob_me
|
227
|
+
|
228
|
+
return was_changed
|
229
|
+
end
|
230
|
+
|
231
|
+
# Globs the directory for new files and directories
|
232
|
+
def glob_me
|
233
|
+
was_changed = false
|
234
|
+
files = Dir.glob(path + "/*")
|
235
|
+
|
236
|
+
# add and monitor new files and directories
|
237
|
+
files.each do |file|
|
238
|
+
file_stat = File.lstat(file) rescue nil
|
239
|
+
if (file_stat.directory?)
|
240
|
+
unless (has_dir?(file)) # new directory
|
241
|
+
# change state only for existing directories
|
242
|
+
# newly added directories have to remain with NEW state
|
243
|
+
was_changed = true
|
244
|
+
ds = DirStat.new(file, self.stable_state)
|
245
|
+
ds.monitor
|
246
|
+
add_dir(ds)
|
247
|
+
end
|
248
|
+
else # it is a file
|
249
|
+
unless(has_file?(file)) # new file
|
250
|
+
# change state only for existing directories
|
251
|
+
# newly added directories have to remain with NEW state
|
252
|
+
was_changed = true
|
253
|
+
fs = FileStat.new(file, self.stable_state)
|
254
|
+
fs.monitor
|
255
|
+
add_file(fs)
|
256
|
+
end
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
return was_changed
|
261
|
+
end
|
262
|
+
|
263
|
+
protected :add_dir, :add_file, :rm_dir, :rm_file, :update_dir, :glob_me
|
264
|
+
end
|
265
|
+
|
metadata
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: file_monitoring
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 0
|
8
|
+
- 2
|
9
|
+
version: 0.0.2
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Gena Petelko, Kolman Vornovitsky
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2012-01-01 00:00:00 +02:00
|
18
|
+
default_executable:
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: algorithms
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
segments:
|
29
|
+
- 0
|
30
|
+
version: "0"
|
31
|
+
type: :runtime
|
32
|
+
version_requirements: *id001
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: daemons
|
35
|
+
prerelease: false
|
36
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
37
|
+
none: false
|
38
|
+
requirements:
|
39
|
+
- - ">="
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
segments:
|
42
|
+
- 0
|
43
|
+
version: "0"
|
44
|
+
type: :runtime
|
45
|
+
version_requirements: *id002
|
46
|
+
description: Deamon for monitoring file changes.
|
47
|
+
email: kolmanv@gmail.com
|
48
|
+
executables:
|
49
|
+
- file_monitoring
|
50
|
+
extensions: []
|
51
|
+
|
52
|
+
extra_rdoc_files: []
|
53
|
+
|
54
|
+
files:
|
55
|
+
- lib/file_monitoring/file_monitoring.rb
|
56
|
+
- lib/file_monitoring/monitor_path.rb
|
57
|
+
- bin/file_monitoring
|
58
|
+
has_rdoc: true
|
59
|
+
homepage:
|
60
|
+
licenses: []
|
61
|
+
|
62
|
+
post_install_message:
|
63
|
+
rdoc_options: []
|
64
|
+
|
65
|
+
require_paths:
|
66
|
+
- lib
|
67
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
68
|
+
none: false
|
69
|
+
requirements:
|
70
|
+
- - ">="
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
segments:
|
73
|
+
- 0
|
74
|
+
version: "0"
|
75
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
76
|
+
none: false
|
77
|
+
requirements:
|
78
|
+
- - ">="
|
79
|
+
- !ruby/object:Gem::Version
|
80
|
+
segments:
|
81
|
+
- 0
|
82
|
+
version: "0"
|
83
|
+
requirements: []
|
84
|
+
|
85
|
+
rubyforge_project:
|
86
|
+
rubygems_version: 1.3.7
|
87
|
+
signing_key:
|
88
|
+
specification_version: 3
|
89
|
+
summary: Deamon for monitoring file changes.
|
90
|
+
test_files: []
|
91
|
+
|