conrad_filer 0.0.1
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/.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
|
+
|