file_monitoring 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+