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.
@@ -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
+