revenc 0.1.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/.document +5 -0
- data/.gitattributes +1 -0
- data/.gitignore +30 -0
- data/CLONING.rdoc +108 -0
- data/LICENSE +20 -0
- data/README.rdoc +198 -0
- data/Rakefile +64 -0
- data/VERSION +1 -0
- data/bin/revenc +81 -0
- data/config/cucumber.yml +7 -0
- data/examples/rsync/encrypted_data/key/encfs6.xml +35 -0
- data/examples/rsync/revenc.conf +18 -0
- data/examples/rsync/scripts/passphrase +1 -0
- data/examples/rsync/unencrypted_data/test_file1.txt +1 -0
- data/examples/rsync/unencrypted_data/test_file2.txt +1 -0
- data/examples/simple/encfs6.xml +35 -0
- data/examples/simple/passphrase +1 -0
- data/examples/simple/unencrypted_data/test_file1.txt +1 -0
- data/examples/simple/unencrypted_data/test_file2.txt +1 -0
- data/features/app.feature +59 -0
- data/features/bin.feature +35 -0
- data/features/configuration.feature +98 -0
- data/features/copy.feature +169 -0
- data/features/generator.feature +15 -0
- data/features/mount.feature +133 -0
- data/features/step_definitions/.gitignore +0 -0
- data/features/step_definitions/revenc_steps.rb +64 -0
- data/features/support/aruba.rb +21 -0
- data/features/support/env.rb +4 -0
- data/features/support/hooks.rb +6 -0
- data/features/unmount.feature +58 -0
- data/lib/revenc/app.rb +128 -0
- data/lib/revenc/encfs_wrapper.rb +96 -0
- data/lib/revenc/errors.rb +78 -0
- data/lib/revenc/io.rb +265 -0
- data/lib/revenc/lockfile.rb +66 -0
- data/lib/revenc.rb +22 -0
- data/spec/revenc/error_spec.rb +50 -0
- data/spec/revenc/io_spec.rb +185 -0
- data/spec/revenc/lockfile_spec.rb +44 -0
- data/spec/spec.opts +2 -0
- data/spec/spec_helper.rb +16 -0
- data/spec/watchr.rb +142 -0
- metadata +179 -0
data/lib/revenc/app.rb
ADDED
@@ -0,0 +1,128 @@
|
|
1
|
+
require 'configatron'
|
2
|
+
require 'term/ansicolor'
|
3
|
+
|
4
|
+
class String
|
5
|
+
include Term::ANSIColor
|
6
|
+
end
|
7
|
+
|
8
|
+
module Revenc
|
9
|
+
|
10
|
+
AVAILABLE_ACTIONS = %w[mount unmount copy]
|
11
|
+
|
12
|
+
class App
|
13
|
+
|
14
|
+
def initialize(base_dir, options={})
|
15
|
+
@base_dir = base_dir
|
16
|
+
@options = options
|
17
|
+
if @options[:verbose]
|
18
|
+
puts "base_dir: #{@base_dir}".cyan
|
19
|
+
puts "options: #{@options.inspect}".cyan
|
20
|
+
end
|
21
|
+
configure(options)
|
22
|
+
end
|
23
|
+
|
24
|
+
def run
|
25
|
+
begin
|
26
|
+
|
27
|
+
if action_argument_required?
|
28
|
+
action = ARGV.shift
|
29
|
+
unless AVAILABLE_ACTIONS.include?(action)
|
30
|
+
if action.nil?
|
31
|
+
puts "revenc action required"
|
32
|
+
else
|
33
|
+
puts "revenc invalid action: #{action}"
|
34
|
+
end
|
35
|
+
puts "revenc --help for more information"
|
36
|
+
exit 1
|
37
|
+
end
|
38
|
+
puts "revenc run action: #{action}".cyan if @options[:verbose]
|
39
|
+
raise "action #{action} not implemented" unless respond_to?(action)
|
40
|
+
result = send(action)
|
41
|
+
else
|
42
|
+
#
|
43
|
+
# default action if action_argument_required? is false
|
44
|
+
#
|
45
|
+
result = 0
|
46
|
+
end
|
47
|
+
|
48
|
+
exit(result ? 0 : 1)
|
49
|
+
|
50
|
+
rescue SystemExit => e
|
51
|
+
# This is the normal exit point, exit code from the send result
|
52
|
+
# or exit from another point in the system
|
53
|
+
puts "revenc run system exit: #{e}, status code: #{e.status}".green if @options[:verbose]
|
54
|
+
exit(e.status)
|
55
|
+
rescue Exception => e
|
56
|
+
STDERR.puts("revenc command failed, error(s) follow:")
|
57
|
+
STDERR.puts("#{e.message}".red)
|
58
|
+
STDERR.puts(e.backtrace.join("\n")) if @options[:verbose]
|
59
|
+
exit(1)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
#
|
64
|
+
# app commands start
|
65
|
+
#
|
66
|
+
# TODO: Add status command, use encfsctl
|
67
|
+
|
68
|
+
def mount
|
69
|
+
EncfsWrapper.new(@base_dir, @options).mount(ARGV.shift, ARGV.shift)
|
70
|
+
end
|
71
|
+
|
72
|
+
def unmount
|
73
|
+
EncfsWrapper.new(@base_dir, @options).unmount(ARGV.shift)
|
74
|
+
end
|
75
|
+
|
76
|
+
def copy
|
77
|
+
EncfsWrapper.new(@base_dir, @options).copy(ARGV.shift, ARGV.shift)
|
78
|
+
end
|
79
|
+
|
80
|
+
#
|
81
|
+
# app commands end
|
82
|
+
#
|
83
|
+
|
84
|
+
private
|
85
|
+
|
86
|
+
# true if application requires an action to be specified on the command line
|
87
|
+
def action_argument_required?
|
88
|
+
!AVAILABLE_ACTIONS.empty?
|
89
|
+
end
|
90
|
+
|
91
|
+
# read options for YAML config with ERB processing and initialize configatron
|
92
|
+
def configure(options)
|
93
|
+
# TODO: read ~/.revenc.conf before looking in the current folder for revenc.conf, read BOTH files
|
94
|
+
config = @options[:config]
|
95
|
+
config = File.join(@base_dir, 'revenc.conf') unless config
|
96
|
+
if File.exists?(config)
|
97
|
+
# load configatron options from the config file
|
98
|
+
puts "loading config file: #{config}".cyan if @options[:verbose]
|
99
|
+
configatron.configure_from_yaml(config)
|
100
|
+
else
|
101
|
+
# user specified a config file?
|
102
|
+
raise "config file not found" if @options[:config]
|
103
|
+
# no error if user did not specify config file
|
104
|
+
puts "#{config} not found".yellow if @options[:verbose]
|
105
|
+
end
|
106
|
+
|
107
|
+
#
|
108
|
+
# set defaults, these will NOT override setting read from YAML
|
109
|
+
#
|
110
|
+
configatron.mount.source.set_default(:name, nil)
|
111
|
+
configatron.mount.mountpoint.set_default(:name, nil)
|
112
|
+
configatron.mount.passphrasefile.set_default(:name, 'passphrase')
|
113
|
+
configatron.mount.keyfile.set_default(:name, 'encfs6.xml')
|
114
|
+
configatron.mount.set_default(:cmd, nil)
|
115
|
+
configatron.mount.set_default(:executable, nil)
|
116
|
+
|
117
|
+
configatron.unmount.mountpoint.set_default(:name, nil)
|
118
|
+
configatron.unmount.set_default(:cmd, nil)
|
119
|
+
configatron.unmount.set_default(:executable, nil)
|
120
|
+
|
121
|
+
configatron.copy.source.set_default(:name, nil)
|
122
|
+
configatron.copy.destination.set_default(:name, nil)
|
123
|
+
configatron.copy.set_default(:cmd, nil)
|
124
|
+
configatron.copy.set_default(:executable, nil)
|
125
|
+
end
|
126
|
+
|
127
|
+
end
|
128
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
module Revenc
|
2
|
+
|
3
|
+
class EncfsWrapper
|
4
|
+
|
5
|
+
def initialize(base_dir, options)
|
6
|
+
raise ArgumentError, "Options should be a hash" unless options.is_a?(Hash)
|
7
|
+
@base_dir = base_dir
|
8
|
+
@options = options
|
9
|
+
end
|
10
|
+
|
11
|
+
def mount(source=nil, mount_point_folder=nil)
|
12
|
+
|
13
|
+
# add params from config file if not specified
|
14
|
+
source = configatron.mount.source.name unless source
|
15
|
+
mount_point_folder = configatron.mount.mountpoint.name unless mount_point_folder
|
16
|
+
|
17
|
+
# sanity check params
|
18
|
+
raise "source folder not specified" unless source
|
19
|
+
raise "mountpoint not specified" unless mount_point_folder
|
20
|
+
|
21
|
+
mount_point_options = @options.merge(:passphrasefile => configatron.mount.passphrasefile.name)
|
22
|
+
mount_point_options = mount_point_options.merge(:keyfile => configatron.mount.keyfile.name)
|
23
|
+
mount_point_options = mount_point_options.merge(:cmd => configatron.mount.cmd) if configatron.mount.cmd
|
24
|
+
mount_point_options = mount_point_options.merge(:executable => configatron.mount.executable) if configatron.mount.executable
|
25
|
+
|
26
|
+
mount_point = MountPoint.new(mount_point_folder, source, mount_point_options)
|
27
|
+
|
28
|
+
if @options[:verbose]
|
29
|
+
puts "mount: source=#{mount_point.source.name}".cyan
|
30
|
+
puts "mount: mountpoint=#{mount_point.name}".cyan
|
31
|
+
puts "mount: passphrasefile=#{mount_point.passphrasefile.name}".cyan
|
32
|
+
puts "mount: keyfile=#{mount_point.keyfile.name}".cyan
|
33
|
+
puts "mount: cmd=#{mount_point.cmd}".cyan
|
34
|
+
puts "mount: executable=#{mount_point.executable}".cyan
|
35
|
+
end
|
36
|
+
|
37
|
+
mount_point.execute
|
38
|
+
end
|
39
|
+
|
40
|
+
def unmount(foldername = nil)
|
41
|
+
|
42
|
+
# add param from config file if not specified, try specific unmount
|
43
|
+
foldername = configatron.unmount.mountpoint.name unless foldername
|
44
|
+
# fallback to mount.mountpoint if specified
|
45
|
+
foldername = configatron.mount.mountpoint.name unless foldername
|
46
|
+
|
47
|
+
# sanity check params
|
48
|
+
raise "mountpoint not specified" unless foldername
|
49
|
+
|
50
|
+
unmount_point_options = @options || {}
|
51
|
+
unmount_point_options = unmount_point_options.merge(:cmd => configatron.unmount.cmd) if configatron.umount.cmd
|
52
|
+
unmount_point_options = unmount_point_options.merge(:executable => configatron.unmount.executable) if configatron.umount.executable
|
53
|
+
unmount_point = UnmountPoint.new(foldername, unmount_point_options)
|
54
|
+
|
55
|
+
if @options[:verbose]
|
56
|
+
puts "unmount: mountpoint=#{unmount_point.mountpoint.name}".cyan
|
57
|
+
puts "unmount: cmd=#{unmount_point.cmd}".cyan
|
58
|
+
puts "unmount: executable=#{unmount_point.executable}".cyan
|
59
|
+
end
|
60
|
+
|
61
|
+
unmount_point.execute
|
62
|
+
end
|
63
|
+
|
64
|
+
def copy(source=nil, destination=nil)
|
65
|
+
|
66
|
+
# add params from config file if not specified
|
67
|
+
source = configatron.copy.source.name unless source
|
68
|
+
# fallback
|
69
|
+
source = configatron.mount.mountpoint.name unless source
|
70
|
+
destination = configatron.copy.destination.name unless destination
|
71
|
+
|
72
|
+
# sanity check params
|
73
|
+
raise "source folder not specified" unless source
|
74
|
+
raise "destination not specified" unless destination
|
75
|
+
|
76
|
+
copy_options = @options || {}
|
77
|
+
copy_options = copy_options.merge(:cmd => configatron.copy.cmd) if configatron.copy.cmd
|
78
|
+
copy_options = copy_options.merge(:executable => configatron.copy.executable) if configatron.copy.executable
|
79
|
+
copy_options = copy_options.merge(:mountpoint => configatron.mount.mountpoint.name) if configatron.mount.mountpoint.name
|
80
|
+
|
81
|
+
copy_folder = CopySourceFolder.new( source, destination, copy_options)
|
82
|
+
|
83
|
+
if @options[:verbose]
|
84
|
+
puts "copy: source=#{copy_folder.name}".cyan
|
85
|
+
puts "copy: destination=#{copy_folder.destination.name}".cyan
|
86
|
+
puts "copy: cmd=#{copy_folder.cmd}".cyan
|
87
|
+
puts "copy: executable=#{copy_folder.executable}".cyan
|
88
|
+
end
|
89
|
+
|
90
|
+
copy_folder.execute
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
96
|
+
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module Revenc
|
2
|
+
|
3
|
+
class Errors
|
4
|
+
include Enumerable
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@errors ={}
|
8
|
+
end
|
9
|
+
|
10
|
+
# add errors, error_on can be a symbol or object instance
|
11
|
+
def add(error_on, message = "Unknown error")
|
12
|
+
|
13
|
+
# humanize error_on
|
14
|
+
if error_on.is_a?(Symbol)
|
15
|
+
error_on_str = error_on.to_s
|
16
|
+
else
|
17
|
+
error_on_str = underscore(error_on.class.name)
|
18
|
+
end
|
19
|
+
error_on_str = error_on_str.gsub(/\//, '_')
|
20
|
+
error_on_str = error_on_str.gsub(/_/, ' ')
|
21
|
+
error_on_str = error_on_str.gsub(/^revenc/, '').strip
|
22
|
+
#error_on_str = error_on_str.capitalize
|
23
|
+
|
24
|
+
@errors[error_on_str] ||= []
|
25
|
+
@errors[error_on_str] << message.to_s
|
26
|
+
end
|
27
|
+
|
28
|
+
def empty?
|
29
|
+
@errors.empty?
|
30
|
+
end
|
31
|
+
|
32
|
+
def clear
|
33
|
+
@errors = {}
|
34
|
+
end
|
35
|
+
|
36
|
+
def each
|
37
|
+
@errors.each_key { |attr| @errors[attr].each { |msg| yield attr, msg } }
|
38
|
+
end
|
39
|
+
|
40
|
+
def size
|
41
|
+
@errors.values.inject(0) { |error_count, attribute| error_count + attribute.size }
|
42
|
+
end
|
43
|
+
|
44
|
+
alias_method :count, :size
|
45
|
+
alias_method :length, :size
|
46
|
+
|
47
|
+
def messages
|
48
|
+
messages = []
|
49
|
+
|
50
|
+
@errors.each_key do |attr|
|
51
|
+
@errors[attr].each do |message|
|
52
|
+
next unless message
|
53
|
+
attr_name = attr.to_s
|
54
|
+
messages << attr_name + ' ' + message
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
messages
|
59
|
+
end
|
60
|
+
|
61
|
+
def to_sentences
|
62
|
+
messages.join("\n")
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
# from ActiveSupport
|
68
|
+
def underscore(camel_cased_word)
|
69
|
+
camel_cased_word.to_s.gsub(/::/, '/').
|
70
|
+
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
|
71
|
+
gsub(/([a-z\d])([A-Z])/,'\1_\2').
|
72
|
+
tr("-", "_").
|
73
|
+
downcase
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
data/lib/revenc/io.rb
ADDED
@@ -0,0 +1,265 @@
|
|
1
|
+
module Revenc
|
2
|
+
|
3
|
+
class BasicAction
|
4
|
+
|
5
|
+
def initialize(name=nil, options={})
|
6
|
+
raise ArgumentError, "Options should be a hash" unless options.is_a?(Hash)
|
7
|
+
@name = name
|
8
|
+
@options = options
|
9
|
+
end
|
10
|
+
|
11
|
+
def name
|
12
|
+
@name
|
13
|
+
end
|
14
|
+
|
15
|
+
def name=(value)
|
16
|
+
@name = value
|
17
|
+
end
|
18
|
+
|
19
|
+
def options
|
20
|
+
@options ||= {}
|
21
|
+
end
|
22
|
+
|
23
|
+
def options=(value)
|
24
|
+
@options = value
|
25
|
+
end
|
26
|
+
|
27
|
+
def errors
|
28
|
+
@errors ||= Errors.new
|
29
|
+
end
|
30
|
+
|
31
|
+
def exists?
|
32
|
+
false
|
33
|
+
end
|
34
|
+
|
35
|
+
def validate
|
36
|
+
end
|
37
|
+
|
38
|
+
def valid?
|
39
|
+
errors.clear
|
40
|
+
validate
|
41
|
+
errors.empty?
|
42
|
+
end
|
43
|
+
|
44
|
+
def cmd
|
45
|
+
return nil unless @cmd
|
46
|
+
# process ERB
|
47
|
+
render(@cmd)
|
48
|
+
end
|
49
|
+
|
50
|
+
def cmd=(value)
|
51
|
+
@cmd = value
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def system_cmd(cmd=nil)
|
57
|
+
raise "ERROR: cmd not given" unless cmd
|
58
|
+
return true if options[:dry_run]
|
59
|
+
system cmd
|
60
|
+
end
|
61
|
+
|
62
|
+
# Runs the YAML file through ERB
|
63
|
+
def render(value, b = binding)
|
64
|
+
ERB.new(value).result(b)
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
|
69
|
+
class FileSystemEntity < BasicAction
|
70
|
+
|
71
|
+
# return fully qualified name
|
72
|
+
def name
|
73
|
+
return @name unless (@name && File.exists?(@name))
|
74
|
+
File.expand_path(@name)
|
75
|
+
end
|
76
|
+
|
77
|
+
def exists?
|
78
|
+
File.exists?(name) if name
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
|
83
|
+
class TextFile < FileSystemEntity
|
84
|
+
|
85
|
+
|
86
|
+
def empty?
|
87
|
+
return true unless exists?
|
88
|
+
|
89
|
+
contents = nil
|
90
|
+
File.open(@name, "r") do |f|
|
91
|
+
contents = f.read
|
92
|
+
end
|
93
|
+
contents.empty?
|
94
|
+
end
|
95
|
+
|
96
|
+
def validate
|
97
|
+
errors.add(self, "filename not specified") if @name.nil?
|
98
|
+
errors.add(self, "not found") unless exists?
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
class PassphraseFile < TextFile
|
103
|
+
|
104
|
+
def initialize(name='passphrase', options={})
|
105
|
+
super name, options
|
106
|
+
end
|
107
|
+
|
108
|
+
def validate
|
109
|
+
super
|
110
|
+
errors.add(self, "is empty") if empty?
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
class KeyFile < TextFile
|
115
|
+
|
116
|
+
def initialize(name='encfs6.xml', options={})
|
117
|
+
super name, options
|
118
|
+
end
|
119
|
+
|
120
|
+
def validate
|
121
|
+
super
|
122
|
+
errors.add(self, "is empty") if exists? && empty?
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
class FileFolder < FileSystemEntity
|
127
|
+
|
128
|
+
def exists?
|
129
|
+
!@name.nil? && File.directory?(@name)
|
130
|
+
end
|
131
|
+
|
132
|
+
def empty?
|
133
|
+
return true unless exists?
|
134
|
+
Dir.entries(@name).sort == [".", ".."].sort
|
135
|
+
end
|
136
|
+
|
137
|
+
def validate
|
138
|
+
errors.add(self, "not found") unless exists?
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
class SourceFolder < FileFolder
|
143
|
+
|
144
|
+
def validate
|
145
|
+
super
|
146
|
+
errors.add(self, "is empty") if exists? && empty?
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
class ActionFolder < FileFolder
|
151
|
+
attr_accessor :passphrasefile
|
152
|
+
attr_accessor :keyfile
|
153
|
+
|
154
|
+
def initialize(name=nil, options={})
|
155
|
+
super
|
156
|
+
@passphrasefile = PassphraseFile.new(options[:passphrasefile])
|
157
|
+
@keyfile = KeyFile.new(options[:keyfile])
|
158
|
+
@cmd = options[:cmd]
|
159
|
+
@executable = options[:executable]
|
160
|
+
end
|
161
|
+
|
162
|
+
def validate
|
163
|
+
super
|
164
|
+
errors.add(self, "executable filename not specified") unless @executable
|
165
|
+
errors.add(self, "#{@executable} executable not found") if executable.empty?
|
166
|
+
errors.add(self, "cmd not specified") unless cmd
|
167
|
+
end
|
168
|
+
|
169
|
+
def executable
|
170
|
+
return nil unless @executable
|
171
|
+
result = `which #{@executable}`
|
172
|
+
result.strip
|
173
|
+
end
|
174
|
+
|
175
|
+
# run the action if valid and return true if successful
|
176
|
+
def execute
|
177
|
+
raise errors.to_sentences unless valid?
|
178
|
+
|
179
|
+
# default failing command
|
180
|
+
result = false
|
181
|
+
|
182
|
+
# protect command from recursion
|
183
|
+
mutex = Revenc::Mutex.new
|
184
|
+
lock_sucessful = mutex.execute do
|
185
|
+
result = system_cmd(cmd)
|
186
|
+
end
|
187
|
+
|
188
|
+
raise "action failed, lock file present" unless lock_sucessful
|
189
|
+
result
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
class MountPoint < ActionFolder
|
194
|
+
attr_accessor :source
|
195
|
+
|
196
|
+
def initialize(name=nil, source_folder_name=nil, options={})
|
197
|
+
super name, options
|
198
|
+
@source = SourceFolder.new(source_folder_name)
|
199
|
+
@cmd = options[:cmd] || "cat <%= passphrasefile.name %> | ENCFS6_CONFIG=<%= keyfile.name %> \
|
200
|
+
<%= executable %> --stdinpass --reverse <%= source.name %> <%= mountpoint.name %> -- -o ro"
|
201
|
+
@executable = options[:executable] || 'encfs'
|
202
|
+
end
|
203
|
+
|
204
|
+
# allow clarity in config files, instead of <%= name %> you can use <%= mountpoint.name %>
|
205
|
+
def mountpoint
|
206
|
+
self
|
207
|
+
end
|
208
|
+
|
209
|
+
def validate
|
210
|
+
super
|
211
|
+
errors.add(self, "is not empty") unless empty?
|
212
|
+
errors.add(self, source.errors.to_sentences) unless source.valid?
|
213
|
+
errors.add(self, keyfile.errors.to_sentences) unless keyfile.valid?
|
214
|
+
errors.add(self, passphrasefile.errors.to_sentences) unless passphrasefile.valid?
|
215
|
+
end
|
216
|
+
|
217
|
+
end
|
218
|
+
|
219
|
+
class UnmountPoint < ActionFolder
|
220
|
+
|
221
|
+
def initialize(name=nil, options={})
|
222
|
+
super
|
223
|
+
@cmd = options[:cmd] || "<%= executable %> -u <%= mountpoint.name %>"
|
224
|
+
@executable = options[:executable] || 'fusermount'
|
225
|
+
end
|
226
|
+
|
227
|
+
# allow clarity in config files, instead of <%= name %> you can use <%= mountpoint.name %>
|
228
|
+
def mountpoint
|
229
|
+
self
|
230
|
+
end
|
231
|
+
|
232
|
+
end
|
233
|
+
|
234
|
+
class DestinationPoint < BasicAction
|
235
|
+
end
|
236
|
+
|
237
|
+
class CopySourceFolder < ActionFolder
|
238
|
+
attr_accessor :destination
|
239
|
+
attr_accessor :mountpoint
|
240
|
+
|
241
|
+
def initialize(name=nil, destination_name=nil, options={})
|
242
|
+
super name, options
|
243
|
+
@destination = DestinationPoint.new(destination_name)
|
244
|
+
@mountpoint = MountPoint.new(options[:mountpoint])
|
245
|
+
@cmd = options[:cmd] || "<%= executable %> -r <%= source.name %> <%= destination.name %>"
|
246
|
+
@executable = options[:executable] || 'cp'
|
247
|
+
end
|
248
|
+
|
249
|
+
# allow clarity in config files, instead of <%= name %> you can use <%= source.name %>
|
250
|
+
def source
|
251
|
+
self
|
252
|
+
end
|
253
|
+
|
254
|
+
def validate
|
255
|
+
super
|
256
|
+
errors.add(self, "is empty") if exists? && empty?
|
257
|
+
errors.add(self, "mountpoint not found") if (mountpoint.name && !mountpoint.exists?)
|
258
|
+
errors.add(self, "mountpoint is empty") if (mountpoint.name && mountpoint.exists? && mountpoint.empty?)
|
259
|
+
errors.add(self, destination.errors.to_sentences) unless destination.valid?
|
260
|
+
end
|
261
|
+
|
262
|
+
end
|
263
|
+
|
264
|
+
end
|
265
|
+
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module Revenc
|
2
|
+
|
3
|
+
class Lockfile
|
4
|
+
attr_accessor :lockfile
|
5
|
+
|
6
|
+
def initialize(lockfile=nil)
|
7
|
+
raise ArgumentError, "lockfile not specified" unless lockfile
|
8
|
+
@lockfile = lockfile
|
9
|
+
end
|
10
|
+
|
11
|
+
def locked?
|
12
|
+
return false unless File.exists?(lockfile)
|
13
|
+
result = false
|
14
|
+
open(lockfile, 'w') do |f|
|
15
|
+
# exclusive non-blocking lock
|
16
|
+
result = !lock(f, File::LOCK_EX | File::LOCK_NB)
|
17
|
+
end
|
18
|
+
result
|
19
|
+
end
|
20
|
+
|
21
|
+
# flock, get a file lock
|
22
|
+
#
|
23
|
+
# Typical usage:
|
24
|
+
#
|
25
|
+
# open('output', 'w') do |f|
|
26
|
+
# flock(f, File::LOCK_EX) do |f|
|
27
|
+
# f << "write to file"
|
28
|
+
# end
|
29
|
+
# end
|
30
|
+
def lock(file, mode)
|
31
|
+
result = file.flock(mode)
|
32
|
+
if result
|
33
|
+
begin
|
34
|
+
yield file if block_given?
|
35
|
+
ensure
|
36
|
+
file.flock(File::LOCK_UN)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
return result
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
class Mutex < Lockfile
|
44
|
+
|
45
|
+
def initialize(lockfile='revenc.lck')
|
46
|
+
super lockfile
|
47
|
+
end
|
48
|
+
|
49
|
+
def execute
|
50
|
+
result = false
|
51
|
+
begin
|
52
|
+
open(@lockfile, 'w') do |f|
|
53
|
+
# exclusive non-blocking lock
|
54
|
+
result = lock(f, File::LOCK_EX | File::LOCK_NB) do |f|
|
55
|
+
yield if block_given?
|
56
|
+
end
|
57
|
+
end
|
58
|
+
ensure
|
59
|
+
# clean up but only if we have a positive result meaning we wrote the lockfile
|
60
|
+
FileUtils.rm(@lockfile) if (result && File.exists?(@lockfile))
|
61
|
+
end
|
62
|
+
result
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
data/lib/revenc.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
$:.unshift File.dirname(__FILE__) # For use/testing when no gem is installed
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
|
5
|
+
module Revenc
|
6
|
+
|
7
|
+
# return the contents of the VERSION file
|
8
|
+
# VERSION format: 0.0.0
|
9
|
+
def self.version
|
10
|
+
version_info_file = File.join(File.dirname(__FILE__), *%w[.. VERSION])
|
11
|
+
File.open(version_info_file, "r") do |f|
|
12
|
+
f.read
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
require 'revenc/app'
|
19
|
+
require 'revenc/io'
|
20
|
+
require 'revenc/errors'
|
21
|
+
require 'revenc/lockfile'
|
22
|
+
require 'revenc/encfs_wrapper'
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe Revenc::Errors do
|
4
|
+
|
5
|
+
before(:each) do
|
6
|
+
@error_obj = Revenc::Errors.new
|
7
|
+
end
|
8
|
+
|
9
|
+
it "should be empty when created" do
|
10
|
+
@error_obj.should be_empty
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should return a count of the errors" do
|
14
|
+
@error_obj.should be_empty
|
15
|
+
@error_obj.add(:test_error1)
|
16
|
+
@error_obj.add(:test_error2)
|
17
|
+
@error_obj.should_not be_empty
|
18
|
+
@error_obj.size.should == 2
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should clear the errors" do
|
22
|
+
@error_obj.should be_empty
|
23
|
+
@error_obj.add(:test_error1)
|
24
|
+
@error_obj.add(:test_error2)
|
25
|
+
@error_obj.should_not be_empty
|
26
|
+
@error_obj.size.should be(2)
|
27
|
+
@error_obj.clear
|
28
|
+
@error_obj.should be_empty
|
29
|
+
@error_obj.size.should be(0)
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should return the errors in full sentences for errors on symbol" do
|
33
|
+
@error_obj.should be_empty
|
34
|
+
@error_obj.add(:test_error1, "Error no 1")
|
35
|
+
@error_obj.add(:test_error2, "Error no 2")
|
36
|
+
@error_obj.size.should be(2)
|
37
|
+
@error_obj.to_sentences.should == "test error1 Error no 1\ntest error2 Error no 2"
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should return the errors in full sentences for errors on class names" do
|
41
|
+
@error_obj.should be_empty
|
42
|
+
@error_obj.add(@error_obj, "error no 1")
|
43
|
+
@error_obj.add(@error_obj, "error no 2")
|
44
|
+
@error_obj.size.should be(2)
|
45
|
+
@error_obj.to_sentences.should == "errors error no 1\nerrors error no 2"
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
|