conrad_filer 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.project +11 -0
- data/README +0 -0
- data/bin/conrad_filer.rb +11 -0
- data/lib/conrad_filer/cli.rb +96 -0
- data/lib/conrad_filer/config.rb +98 -0
- data/lib/conrad_filer/db.rb +87 -0
- data/lib/conrad_filer/db_objects.rb +22 -0
- data/lib/conrad_filer/dir_polling_job.rb +48 -0
- data/lib/conrad_filer/id_type.rb +31 -0
- data/lib/conrad_filer/rule.rb +37 -0
- data/lib/conrad_filer/version.rb +3 -0
- data/lib/conrad_filer/watch_job.rb +72 -0
- data/lib/conrad_filer.rb +9 -0
- data/lib/inotify/bitmask.rb +96 -0
- data/lib/inotify/inotify_native.so +0 -0
- data/lib/inotify/thread.rb +77 -0
- data/lib/inotify.rb +3 -0
- data/lib/test_run.rb +4 -0
- metadata +85 -0
data/.project
ADDED
data/README
ADDED
File without changes
|
data/bin/conrad_filer.rb
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'conrad_filer/config'
|
3
|
+
require 'inotify'
|
4
|
+
|
5
|
+
module ConradFiler
|
6
|
+
|
7
|
+
Thread.abort_on_exception = true
|
8
|
+
|
9
|
+
class CLI
|
10
|
+
|
11
|
+
def initialize(argv)
|
12
|
+
@file_data = Hash.new
|
13
|
+
@file_changes = Hash.new
|
14
|
+
end
|
15
|
+
|
16
|
+
def run
|
17
|
+
@config = ConradFiler::Config.new
|
18
|
+
@watch_jobs = @config.get_watch_jobs
|
19
|
+
@id_types = @config.get_id_types
|
20
|
+
@rules = @config.get_rules
|
21
|
+
|
22
|
+
# Go through each of the rules and add them to the watch jobs they are configured for
|
23
|
+
# (note: any watch jobs that are not assigned rules will be disabled later)
|
24
|
+
@rules.each_value do |rule|
|
25
|
+
rule.get_watch_job_names.each do |watch_job_name|
|
26
|
+
rule.get_id_type_names.each do |id_type_name|
|
27
|
+
# puts "#{rule.name} #{watch_job_name} #{id_type_name}"
|
28
|
+
watch_job = @watch_jobs[watch_job_name]
|
29
|
+
watch_job.add_rule(@id_types[id_type_name], rule)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# Start up each watch job in a thread that will monitor directories. If a change is detected matching the bitmask associated
|
35
|
+
# with the watch job, the rule will be asked to perform for each id type that matches the file associated with the event
|
36
|
+
@watch_jobs.each_pair do |name, watch_job|
|
37
|
+
if watch_job.is_valid?
|
38
|
+
watch_job.start_inotify_thread
|
39
|
+
else
|
40
|
+
@config.disable_watch_job(name) # if watch job is enabled but nobody is referencing it, disable it so next time we don't bother including it
|
41
|
+
@watch_jobs.delete(name)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
# Now that we are monitoring live updates, its time to go hunt down changes since the last time the app was run and perform the rules
|
47
|
+
@watch_jobs.each_value do |watch_job|
|
48
|
+
# TODO: Tell the watch job to look for changes since last run
|
49
|
+
#watch_job.?????
|
50
|
+
end
|
51
|
+
|
52
|
+
# Scan the directories looking for changes since the app was last run
|
53
|
+
|
54
|
+
#DirMonitor.all_jobs(db) do |monitor_job|
|
55
|
+
# @threads << Thread.new(polling_job) do |job|
|
56
|
+
# cur_dir = job.directory
|
57
|
+
# #Dir[job.].each do |filename|
|
58
|
+
# fs = File::Stat.new(filename)
|
59
|
+
# end
|
60
|
+
#end
|
61
|
+
#@threads.each {|thread| thread.join}
|
62
|
+
|
63
|
+
# Create a special watch job for the configuration file and db looking for changes
|
64
|
+
# by not spawning the thread, the app will continue running
|
65
|
+
self.create_config_watch_job.start_loop
|
66
|
+
end
|
67
|
+
|
68
|
+
# create a watch job to detect if the database or config file have changed (call on_config_file_changed if this happens)
|
69
|
+
def create_config_watch_job
|
70
|
+
config_watch = Inotify::InotifyThread.new
|
71
|
+
config_watch_bitmask = Inotify::InotifyBitmask.all_events
|
72
|
+
config_watch_bitmask.set_flag(:in_move_self)
|
73
|
+
config_watch_bitmask.set_flag(:in_delete_self)
|
74
|
+
config_watch_bitmask.set_flag(:in_modify)
|
75
|
+
#puts @config.cfg_filename
|
76
|
+
#puts config_watch_bitmask.as_array_of_symbols
|
77
|
+
config_watch.add_watch(@config.cfg_filename, config_watch_bitmask.bitmask, false)
|
78
|
+
config_watch.add_watch(@config.db_filename, config_watch_bitmask.bitmask, false)
|
79
|
+
config_watch.register_event(config_watch_bitmask.bitmask, &(self.method(:on_config_file_changed)))
|
80
|
+
config_watch
|
81
|
+
end
|
82
|
+
|
83
|
+
# ConradFilerApp.on_config_file_changed
|
84
|
+
# This is the callback which is called when the configuration changes
|
85
|
+
def on_config_file_changed(notify_event, path)
|
86
|
+
filename = path
|
87
|
+
if filename == File.expand_path(@config.cfg_filename)
|
88
|
+
print "config_file_changed - "
|
89
|
+
elsif filename == File.expand_path(@config.db_filename)
|
90
|
+
print "database updated - "
|
91
|
+
end
|
92
|
+
puts Inotify::InotifyBitmask.new(notify_event.mask).as_array_of_symbols
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'parseconfig'
|
3
|
+
require 'conrad_filer/watch_job'
|
4
|
+
require 'conrad_filer/id_type'
|
5
|
+
require 'conrad_filer/rule'
|
6
|
+
require 'conrad_filer/db'
|
7
|
+
require 'inotify'
|
8
|
+
|
9
|
+
module ConradFiler
|
10
|
+
|
11
|
+
class Config
|
12
|
+
attr_reader :cfg_filename
|
13
|
+
attr_reader :db_filename
|
14
|
+
|
15
|
+
# Register all tables referenced in this class so the database can be created if needed
|
16
|
+
ConradFiler::DB.register_table_definition("CREATE TABLE WATCH_JOBS (NAME TEXT, PATHNAME TEXT, BITMASK INTEGER, RECURSIVE TEXT, ENABLED TEXT)")
|
17
|
+
ConradFiler::DB.register_table_definition("CREATE TABLE ID_TYPES (NAME TEXT, MATCH_REGEXP TEXT, IGNORE_CASE TEXT)")
|
18
|
+
ConradFiler::DB.register_table_definition("CREATE TABLE RULES (NAME TEXT, DIRECTORY TEXT, RECURSIVE TEXT)")
|
19
|
+
ConradFiler::DB.register_table_definition("CREATE TABLE RULE_WATCH_JOBS (RULE TEXT, WATCH_JOB TEXT)")
|
20
|
+
ConradFiler::DB.register_table_definition("CREATE TABLE RULE_ID_TYPES (RULE TEXT, ID_TYPE TEXT)")
|
21
|
+
|
22
|
+
|
23
|
+
## Register to create some data to play with when a new db is created
|
24
|
+
bitmask = Inotify::InotifyBitmask.all_events
|
25
|
+
bitmask.unset_flag(:in_close_nowrite)
|
26
|
+
bitmask.unset_flag(:in_open)
|
27
|
+
ConradFiler::DB.register_table_definition("INSERT INTO WATCH_JOBS (NAME,PATHNAME,BITMASK,RECURSIVE,ENABLED) VALUES ('WATCH_JOB','/home/jon/watch_job',#{bitmask.bitmask},'F','T')")
|
28
|
+
ConradFiler::DB.register_table_definition("INSERT INTO ID_TYPES (NAME,MATCH_REGEXP,IGNORE_CASE) VALUES ('ID_TYPE','.\\.MP3$','T')")
|
29
|
+
ConradFiler::DB.register_table_definition("INSERT INTO ID_TYPES (NAME,MATCH_REGEXP,IGNORE_CASE) VALUES ('ID_TYPE2','.\\.m4a$','T')")
|
30
|
+
ConradFiler::DB.register_table_definition("INSERT INTO RULES (NAME) VALUES ('RULE')")
|
31
|
+
ConradFiler::DB.register_table_definition("INSERT INTO RULE_WATCH_JOBS (RULE, WATCH_JOB) VALUES ('RULE', 'WATCH_JOB')")
|
32
|
+
ConradFiler::DB.register_table_definition("INSERT INTO RULE_ID_TYPES (RULE, ID_TYPE) VALUES ('RULE', 'ID_TYPE')")
|
33
|
+
ConradFiler::DB.register_table_definition("INSERT INTO RULE_ID_TYPES (RULE, ID_TYPE) VALUES ('RULE', 'ID_TYPE2')")
|
34
|
+
|
35
|
+
|
36
|
+
DEFAULT_CONFIG_FILENAME = '/etc/conrad_filer.conf'
|
37
|
+
#DEFAULT_DB_FILENAME = '/var/local/conrad_filer/conrad_filer.sdb'
|
38
|
+
DEFAULT_DB_FILENAME = 'conrad_filer.sdb'
|
39
|
+
#DEFAULT_LOG_FILENAME = '/var/local/conrad_filer/conrad_filer.log'
|
40
|
+
DEFAULT_LOG_FILENAME = 'conrad_filer.log'
|
41
|
+
|
42
|
+
def initialize(filename="")
|
43
|
+
@cfg_filename = filename
|
44
|
+
self.determine_file_locations
|
45
|
+
self.open_database
|
46
|
+
end
|
47
|
+
|
48
|
+
def determine_file_locations
|
49
|
+
@cfg_filename = DEFAULT_CONFIG_FILENAME if @cfg_filename.length == 0
|
50
|
+
@file_config = ParseConfig.new(@cfg_filename)
|
51
|
+
@db_filename = @file_config.get_value('database').to_s
|
52
|
+
@db_filename = DEFAULT_DB_FILENAME if @db_filename.length == 0
|
53
|
+
@log_filename = @file_config.get_value('logfile').to_s
|
54
|
+
@log_filename = DEFAULT_LOG_FILENAME if @log_filename.length == 0
|
55
|
+
end
|
56
|
+
|
57
|
+
def open_database
|
58
|
+
@db = ConradFiler::DB.new(@db_filename)
|
59
|
+
@db.open
|
60
|
+
end
|
61
|
+
|
62
|
+
def get_watch_jobs
|
63
|
+
objs = Hash.new
|
64
|
+
rows = @db.execute("SELECT * FROM WATCH_JOBS WHERE ENABLED = 'T'")
|
65
|
+
rows.each do |row|
|
66
|
+
obj = ConradFiler::WatchJob.new(@db, row)
|
67
|
+
objs[row['NAME']] = obj
|
68
|
+
end
|
69
|
+
objs
|
70
|
+
end
|
71
|
+
|
72
|
+
def disable_watch_job(watch_job_name)
|
73
|
+
@db.execute("UPDATE WATCH_JOBS SET ENABLED = 'F' WHERE NAME = '#{watch_job_name}'")
|
74
|
+
end
|
75
|
+
|
76
|
+
def get_id_types
|
77
|
+
objs = Hash.new
|
78
|
+
@db.execute("SELECT * FROM ID_TYPES").each do |row|
|
79
|
+
obj = ConradFiler::IdType.new(@db, row)
|
80
|
+
objs[row['NAME']] = obj
|
81
|
+
end
|
82
|
+
objs
|
83
|
+
end
|
84
|
+
|
85
|
+
def get_rules
|
86
|
+
objs = Hash.new
|
87
|
+
@db.execute("SELECT * FROM RULES").each do |row|
|
88
|
+
obj = ConradFiler::Rule.new(@db, row)
|
89
|
+
row['WATCH_JOBS'] = @db.execute_as_array("SELECT WATCH_JOB FROM RULE_WATCH_JOBS WHERE RULE = #{row['NAME']}")
|
90
|
+
row['ID_TYPES'] = @db.execute_as_array("SELECT ID_TYPE FROM RULE_ID_TYPES WHERE RULE = #{row['NAME']}")
|
91
|
+
objs[row['NAME']] = obj
|
92
|
+
end
|
93
|
+
objs
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
include ObjectSpace
|
2
|
+
require 'sqlite3'
|
3
|
+
|
4
|
+
module ConradFiler
|
5
|
+
|
6
|
+
class DB
|
7
|
+
|
8
|
+
@@table_defs = Array.new
|
9
|
+
@@table_defs << "CREATE TABLE DIR_INFO (DIR_NAME TEXT, DIR_HASH INTEGER, FULL_PATH TEXT)"
|
10
|
+
|
11
|
+
## Class Methods ##
|
12
|
+
|
13
|
+
# Register the sql that will be called if the database needs to be created
|
14
|
+
def self.register_table_definition(sql)
|
15
|
+
@@table_defs << sql
|
16
|
+
end
|
17
|
+
|
18
|
+
# Finalizer definition for class instances (registered in ConradDB::open method if the database is successfully opened)
|
19
|
+
def self.finalize_instance(db)
|
20
|
+
proc { db.close }
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
## Instance Methods ##
|
25
|
+
|
26
|
+
# Initialize the new instance
|
27
|
+
def initialize(filename)
|
28
|
+
@filename = filename
|
29
|
+
end
|
30
|
+
|
31
|
+
# Create the configuration database
|
32
|
+
def create
|
33
|
+
self.delete
|
34
|
+
@db = SQLite3::Database.new(@filename)
|
35
|
+
@@table_defs.each do |sql|
|
36
|
+
puts sql
|
37
|
+
@db.execute(sql)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Open the configuration database
|
42
|
+
def open
|
43
|
+
if File.exists?(@filename)
|
44
|
+
@db = SQLite3::Database.open(@filename)
|
45
|
+
else
|
46
|
+
self.create
|
47
|
+
end
|
48
|
+
@db.results_as_hash = true
|
49
|
+
# Register the finalizer to make sure the db connection is closed
|
50
|
+
ObjectSpace.define_finalizer(self, self.class.finalize_instance(@db))
|
51
|
+
self
|
52
|
+
end
|
53
|
+
|
54
|
+
# Execute the sql
|
55
|
+
def execute(sql)
|
56
|
+
@db.execute(sql)
|
57
|
+
end
|
58
|
+
|
59
|
+
# Execute the sql as normal, but return an array from the first column of the result set
|
60
|
+
def execute_as_array(sql)
|
61
|
+
return_array = Array.new
|
62
|
+
@db.results_as_hash = false
|
63
|
+
self.execute(sql).each do |rec|
|
64
|
+
return_array << rec[0]
|
65
|
+
end
|
66
|
+
@db.results_as_hash = true
|
67
|
+
return_array
|
68
|
+
end
|
69
|
+
|
70
|
+
# Close the configuration database
|
71
|
+
def close
|
72
|
+
@db.close
|
73
|
+
|
74
|
+
# Unegister all finalizers since they are no longer needed
|
75
|
+
ObjectSpace.undefine_finalizer(self)
|
76
|
+
end
|
77
|
+
|
78
|
+
# Delete the configuration database
|
79
|
+
def delete
|
80
|
+
if File.exists?(@filename)
|
81
|
+
File.delete(@filename)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
module ConradFiler
|
4
|
+
|
5
|
+
class FileSystemEntry
|
6
|
+
class DBDir
|
7
|
+
puts "DBDir loading"
|
8
|
+
def initialize
|
9
|
+
@instvar
|
10
|
+
@@classvar
|
11
|
+
end
|
12
|
+
def self.from_path
|
13
|
+
end
|
14
|
+
|
15
|
+
class DBFile
|
16
|
+
puts "DBFile loading"
|
17
|
+
def initialize
|
18
|
+
@instvar
|
19
|
+
@@classvar
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'conrad_filer/db'
|
2
|
+
|
3
|
+
module ConradFiler
|
4
|
+
|
5
|
+
class DirPollingJob
|
6
|
+
|
7
|
+
ConradFiler::DB.register_table_definition("CREATE TABLE WATCH_LIST (DIRECTORY TEXT, GLOB TEXT, INC_SUBDIR TEXT, RECURSIVE TEXT)")
|
8
|
+
|
9
|
+
## Class Methods ##
|
10
|
+
|
11
|
+
def self.query
|
12
|
+
"SELECT DIRECTORY, GLOB, INC_SUBDIR, POLLING_DELAY FROM POLLING_JOBS"
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.all_jobs(db)
|
16
|
+
jobs = Array.new
|
17
|
+
db.execute(self.query) do |row|
|
18
|
+
jobs << self.new(row)
|
19
|
+
end
|
20
|
+
jobs
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
## Instance Methods ##
|
25
|
+
|
26
|
+
def initialize(an_array)
|
27
|
+
@rec = an_array
|
28
|
+
end
|
29
|
+
|
30
|
+
def include_subdirs?
|
31
|
+
@rec[3] == "T"
|
32
|
+
end
|
33
|
+
|
34
|
+
def directory
|
35
|
+
@rec[1]
|
36
|
+
end
|
37
|
+
|
38
|
+
def glob
|
39
|
+
@rec[2]
|
40
|
+
end
|
41
|
+
|
42
|
+
def perform
|
43
|
+
# Loop over all entries in the directory and collect the files and subdirectories
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'conrad_filer/db'
|
2
|
+
|
3
|
+
module ConradFiler
|
4
|
+
|
5
|
+
# An ID Type describes files in a directory using a regular expression. It is used by a rule to define which file types the rule applies to.
|
6
|
+
class IdType
|
7
|
+
|
8
|
+
def initialize(db, a_hash)
|
9
|
+
@db_rec = a_hash
|
10
|
+
end
|
11
|
+
|
12
|
+
def matches_pathname?(pathname)
|
13
|
+
pathname =~ self.get_regexp
|
14
|
+
end
|
15
|
+
|
16
|
+
def match_regexp
|
17
|
+
@db_rec['MATCH_REGEXP']
|
18
|
+
end
|
19
|
+
|
20
|
+
def ignore_case
|
21
|
+
@db_rec['IGNORE_CASE'][0,1].upcase == "T"
|
22
|
+
end
|
23
|
+
|
24
|
+
def get_regexp
|
25
|
+
return @regexp if @regexp != nil
|
26
|
+
@regexp = Regexp.new(self.match_regexp, self.ignore_case)
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'conrad_filer/db'
|
2
|
+
require 'inotify'
|
3
|
+
|
4
|
+
module ConradFiler
|
5
|
+
|
6
|
+
# A rule describes an action to perform on a file that matches one of its defined id types
|
7
|
+
class Rule
|
8
|
+
|
9
|
+
attr_accessor :db_rec
|
10
|
+
|
11
|
+
def initialize(db, a_hash)
|
12
|
+
@db_rec = a_hash
|
13
|
+
end
|
14
|
+
|
15
|
+
def get_watch_job_names
|
16
|
+
@db_rec['WATCH_JOBS']
|
17
|
+
end
|
18
|
+
|
19
|
+
def get_id_type_names
|
20
|
+
@db_rec['ID_TYPES']
|
21
|
+
end
|
22
|
+
|
23
|
+
def name
|
24
|
+
@db_rec['NAME']
|
25
|
+
end
|
26
|
+
|
27
|
+
def on_watch_event(watch_job, id_type, notify_event, path)
|
28
|
+
bitmask = Inotify::InotifyBitmask.new(notify_event.mask)
|
29
|
+
if notify_event.name.class == String
|
30
|
+
pathname = File.join(path, notify_event.name)
|
31
|
+
else
|
32
|
+
pathname = path
|
33
|
+
end
|
34
|
+
puts "[ConradRule::on_watch_event] '#{pathname}' #{bitmask.as_array_of_symbols}"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'conrad_filer/db'
|
2
|
+
require 'inotify'
|
3
|
+
|
4
|
+
module ConradFiler
|
5
|
+
|
6
|
+
# A watch job describes directories to monitor and includes a bitmask describing which actions to monitor
|
7
|
+
class WatchJob
|
8
|
+
|
9
|
+
def initialize(db, a_hash)
|
10
|
+
@db_rec = a_hash
|
11
|
+
@bitmask = Inotify::InotifyBitmask.new(@db_rec['BITMASK'])
|
12
|
+
@rules = Hash.new {Array.new}
|
13
|
+
@inotify_thread = Inotify::InotifyThread.new
|
14
|
+
end
|
15
|
+
|
16
|
+
def id_types
|
17
|
+
@rules.keys
|
18
|
+
end
|
19
|
+
|
20
|
+
def add_rule(id_type, rule)
|
21
|
+
@rules[id_type] = @rules[id_type] << rule
|
22
|
+
end
|
23
|
+
|
24
|
+
def recursive?
|
25
|
+
@db_rec['RECURSIVE'][0,1].upcase == "T"
|
26
|
+
end
|
27
|
+
|
28
|
+
def pathname
|
29
|
+
@db_rec['PATHNAME']
|
30
|
+
end
|
31
|
+
|
32
|
+
def bitmask
|
33
|
+
@bitmask.bitmask
|
34
|
+
end
|
35
|
+
|
36
|
+
def is_valid?
|
37
|
+
@rules.size > 0
|
38
|
+
end
|
39
|
+
|
40
|
+
def prepare_inotify_thread
|
41
|
+
@inotify_thread.add_watch(self.pathname, self.bitmask, self.recursive?)
|
42
|
+
@inotify_thread.register_event(self.bitmask, &(self.method(:process_event)))
|
43
|
+
end
|
44
|
+
|
45
|
+
def start_inotify_thread
|
46
|
+
self.prepare_inotify_thread
|
47
|
+
@inotify_thread.start_thread
|
48
|
+
end
|
49
|
+
|
50
|
+
def start_inotify_loop
|
51
|
+
self.prepare_inotify_thread
|
52
|
+
@inotify_thread.start_loop
|
53
|
+
end
|
54
|
+
|
55
|
+
def process_event(notify_event, path)
|
56
|
+
bitmask = Inotify::InotifyBitmask.new(notify_event.mask)
|
57
|
+
puts "[ConradWatchJob::process_event] '#{path}/#{notify_event.name}' #{bitmask.as_array_of_symbols}"
|
58
|
+
@rules.each_pair do |id_type, rules_for_id_type|
|
59
|
+
if id_type.matches_pathname?(File.join(path,notify_event.name.to_s))
|
60
|
+
rules_for_id_type.each do |rule|
|
61
|
+
rule.on_watch_event(self, id_type, notify_event, path)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
#def register_event(bitmask, &block)
|
68
|
+
# @inotify_thread.register_event(bitmask, &block)
|
69
|
+
#end
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
data/lib/conrad_filer.rb
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
require 'conrad_filer/version'
|
2
|
+
require 'conrad_filer/cli'
|
3
|
+
require 'conrad_filer/config'
|
4
|
+
require 'conrad_filer/db'
|
5
|
+
require 'conrad_filer/id_type'
|
6
|
+
require 'conrad_filer/rule'
|
7
|
+
require 'conrad_filer/watch_job'
|
8
|
+
require 'conrad_filer/db_objects'
|
9
|
+
require 'conrad_filer/dir_polling_job'
|
@@ -0,0 +1,96 @@
|
|
1
|
+
require 'find'
|
2
|
+
require 'inotify'
|
3
|
+
|
4
|
+
module Inotify
|
5
|
+
|
6
|
+
class InotifyBitmask
|
7
|
+
|
8
|
+
@@symbols_and_bits = {
|
9
|
+
:in_access => 0, 0 => :in_access, # File was accessed (read) (*)
|
10
|
+
:in_modify => 1, 1 => :in_modify, # File was modified (*)
|
11
|
+
:in_attrib => 2, 2 => :in_attrib, # Metadata changed (permissions, timestamps, extended attributes, etc.) (*)
|
12
|
+
:in_close_write => 3, 3 => :in_close_write, # File opened for writing was closed (*)
|
13
|
+
:in_close_nowrite => 4, 4 => :in_close_nowrite, # File not opened for writing was closed (*)
|
14
|
+
:in_open => 5, 5 => :in_open, # File was opened (*)
|
15
|
+
:in_moved_from => 6, 6 => :in_moved_from, # File moved out of watched directory (*)
|
16
|
+
:in_moved_to => 7, 7 => :in_moved_to, # File moved into watched directory (*)
|
17
|
+
:in_create => 8, 8 => :in_create, # File/directory created in watched directory (*)
|
18
|
+
:in_delete => 9, 9 => :in_delete, # File/directory deleted from watched directory (*)
|
19
|
+
:in_delete_self => 10, 10 => :in_delete_self, # Watched file/directory was itself deleted
|
20
|
+
:in_move_self => 11, 11 => :in_move_self, # Watched file/directory was itself moved
|
21
|
+
:in_unmount => 13, 13 => :in_unmount, # Backing fs was unmounted
|
22
|
+
:in_q_overflow => 14, 14 => :in_q_overflow, # Event queued overflowed
|
23
|
+
:in_ignored => 15, 15 => :in_ignored, # File was ignored
|
24
|
+
:in_mask_add => 29, 29 => :in_mask_add, # add to the mask of an already existing watch
|
25
|
+
:in_isdir => 30, 30 => :in_isdir, # event occurred against dir
|
26
|
+
:in_oneshot => 31, 31 => :in_oneshot # only send event once
|
27
|
+
}
|
28
|
+
@@symbols_to_masks = {
|
29
|
+
:in_access => 0b00000000000000000000000000000001,
|
30
|
+
:in_modify => 0b00000000000000000000000000000010,
|
31
|
+
:in_attrib => 0b00000000000000000000000000000100,
|
32
|
+
:in_close_write => 0b00000000000000000000000000001000,
|
33
|
+
:in_close_nowrite => 0b00000000000000000000000000010000,
|
34
|
+
:in_open => 0b00000000000000000000000000100000,
|
35
|
+
:in_moved_from => 0b00000000000000000000000001000000,
|
36
|
+
:in_moved_to => 0b00000000000000000000000010000000,
|
37
|
+
:in_create => 0b00000000000000000000000100000000,
|
38
|
+
:in_delete => 0b00000000000000000000001000000000,
|
39
|
+
:in_delete_self => 0b00000000000000000000010000000000,
|
40
|
+
:in_move_self => 0b00000000000000000000100000000000,
|
41
|
+
:in_unmount => 0b00000000000000000010000000000000,
|
42
|
+
:in_q_overflow => 0b00000000000000000100000000000000,
|
43
|
+
:in_ignored => 0b00000000000000001000000000000000,
|
44
|
+
:in_mask_add => 0b00100000000000000000000000000000,
|
45
|
+
:in_isdir => 0b01000000000000000000000000000000,
|
46
|
+
:in_oneshot => 0b10000000000000000000000000000000
|
47
|
+
}
|
48
|
+
|
49
|
+
attr_reader :bitmask
|
50
|
+
@symbols
|
51
|
+
|
52
|
+
def self.all_events
|
53
|
+
inotify_bitmask = self.new(0b00000000000000000000111111111111) # All standard events
|
54
|
+
end
|
55
|
+
def initialize(bitmask=0)
|
56
|
+
if bitmask.class == String
|
57
|
+
@bitmask = bitmask.to_i
|
58
|
+
else
|
59
|
+
@bitmask = bitmask
|
60
|
+
end
|
61
|
+
end
|
62
|
+
def set_flag(a_symbol)
|
63
|
+
@bitmask = @bitmask | @@symbols_to_masks[a_symbol]
|
64
|
+
end
|
65
|
+
def unset_flag(a_symbol)
|
66
|
+
@bitmask = @bitmask & ~@@symbols_to_masks[a_symbol]
|
67
|
+
end
|
68
|
+
|
69
|
+
def test?(bitmask2)
|
70
|
+
(@bitmask & bitmask2) != 0
|
71
|
+
end
|
72
|
+
def watch_ignored?
|
73
|
+
self.test?(Inotify::IGNORED)
|
74
|
+
end
|
75
|
+
def is_directory?
|
76
|
+
self.test?(Inotify::ISDIR)
|
77
|
+
end
|
78
|
+
def overflow?
|
79
|
+
self.test?(Inotify::Q_OVERFLOW)
|
80
|
+
end
|
81
|
+
def unmount?
|
82
|
+
self.test?(Inotify::UNMOUNT)
|
83
|
+
end
|
84
|
+
def as_array_of_symbols
|
85
|
+
return @symbols if @symbols != nil
|
86
|
+
@symbols = Array.new
|
87
|
+
31.downto(0) do |i|
|
88
|
+
if @bitmask[i] == 1
|
89
|
+
a_symbol = @@symbols_and_bits[i]
|
90
|
+
@symbols << a_symbol if a_symbol != nil
|
91
|
+
end
|
92
|
+
end
|
93
|
+
@symbols
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
Binary file
|
@@ -0,0 +1,77 @@
|
|
1
|
+
require 'find'
|
2
|
+
require 'inotify'
|
3
|
+
|
4
|
+
module Inotify
|
5
|
+
|
6
|
+
class InotifyThread
|
7
|
+
def initialize
|
8
|
+
@inotify = Inotify.new
|
9
|
+
@watch_list = Hash.new
|
10
|
+
@reg_events = Hash.new {Array.new}
|
11
|
+
@die_on_move = true
|
12
|
+
end
|
13
|
+
|
14
|
+
def start_thread
|
15
|
+
@thread = Thread.new do
|
16
|
+
self.start_loop
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def start_loop
|
21
|
+
@inotify.each_event do |notify_event|
|
22
|
+
@reg_events.keys.each do |reg_event|
|
23
|
+
if (notify_event.mask & reg_event) > 0
|
24
|
+
@reg_events[reg_event].each do |event_block|
|
25
|
+
# There is no way to get the new path if the watched directory is moved, so stop watching it
|
26
|
+
if @die_on_move and InotifyBitmask.new(notify_event.mask).test?(:in_move_self)
|
27
|
+
self.remove_watch(@watch_list[notify_event.wd])
|
28
|
+
break
|
29
|
+
else
|
30
|
+
event_block.call(notify_event, @watch_list[notify_event.wd])
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def end_thread
|
39
|
+
@thread.kill
|
40
|
+
end
|
41
|
+
|
42
|
+
def add_watch(pathname, bitmask, recursive)
|
43
|
+
bm = InotifyBitmask.new(bitmask)
|
44
|
+
if @die_on_move
|
45
|
+
bm.set_flag(:in_move_self)
|
46
|
+
end
|
47
|
+
full_path = File.expand_path(pathname)
|
48
|
+
wd = @inotify.add_watch(full_path, bm.bitmask)
|
49
|
+
return wd if wd < 0
|
50
|
+
@watch_list[wd] = full_path
|
51
|
+
if recursive and FileTest.directory?(full_path)
|
52
|
+
Dir.foreach(full_path) do |filename|
|
53
|
+
full_filename = File.join(full_path, filename)
|
54
|
+
if FileTest.directory?(full_filename)
|
55
|
+
if filename[0] != ?.
|
56
|
+
wd = self.add_watch(full_filename, bm.bitmask, recursive)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
wd
|
62
|
+
end
|
63
|
+
|
64
|
+
def remove_watch(pathname)
|
65
|
+
wd = @watch_list[pathname]
|
66
|
+
if !wd.nil?
|
67
|
+
@inotify.rm_watch(wd)
|
68
|
+
@watch_list.delete(wd)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def register_event(bitmask, &block)
|
73
|
+
@reg_events[bitmask] = @reg_events[bitmask] << block
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
data/lib/inotify.rb
ADDED
data/lib/test_run.rb
ADDED
metadata
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: conrad_filer
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 29
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 0.0.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Jon Raiford
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-09-03 00:00:00 -04:00
|
19
|
+
default_executable:
|
20
|
+
dependencies: []
|
21
|
+
|
22
|
+
description: Think of it as applying a firewall ruleset to file management.
|
23
|
+
email:
|
24
|
+
- jon@raiford.org
|
25
|
+
executables:
|
26
|
+
- conrad_filer.rb
|
27
|
+
extensions: []
|
28
|
+
|
29
|
+
extra_rdoc_files: []
|
30
|
+
|
31
|
+
files:
|
32
|
+
- .project
|
33
|
+
- README
|
34
|
+
- bin/conrad_filer.rb
|
35
|
+
- lib/conrad_filer.rb
|
36
|
+
- lib/conrad_filer/cli.rb
|
37
|
+
- lib/conrad_filer/config.rb
|
38
|
+
- lib/conrad_filer/db.rb
|
39
|
+
- lib/conrad_filer/db_objects.rb
|
40
|
+
- lib/conrad_filer/dir_polling_job.rb
|
41
|
+
- lib/conrad_filer/id_type.rb
|
42
|
+
- lib/conrad_filer/rule.rb
|
43
|
+
- lib/conrad_filer/version.rb
|
44
|
+
- lib/conrad_filer/watch_job.rb
|
45
|
+
- lib/inotify.rb
|
46
|
+
- lib/inotify/bitmask.rb
|
47
|
+
- lib/inotify/inotify_native.so
|
48
|
+
- lib/inotify/thread.rb
|
49
|
+
- lib/test_run.rb
|
50
|
+
has_rdoc: true
|
51
|
+
homepage: ""
|
52
|
+
licenses: []
|
53
|
+
|
54
|
+
post_install_message:
|
55
|
+
rdoc_options: []
|
56
|
+
|
57
|
+
require_paths:
|
58
|
+
- lib
|
59
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
60
|
+
none: false
|
61
|
+
requirements:
|
62
|
+
- - ">="
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
hash: 3
|
65
|
+
segments:
|
66
|
+
- 0
|
67
|
+
version: "0"
|
68
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
69
|
+
none: false
|
70
|
+
requirements:
|
71
|
+
- - ">="
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
hash: 3
|
74
|
+
segments:
|
75
|
+
- 0
|
76
|
+
version: "0"
|
77
|
+
requirements: []
|
78
|
+
|
79
|
+
rubyforge_project:
|
80
|
+
rubygems_version: 1.3.7
|
81
|
+
signing_key:
|
82
|
+
specification_version: 3
|
83
|
+
summary: A rule-based file manager
|
84
|
+
test_files: []
|
85
|
+
|