britebox 0.0.3 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ae190d938423b74e7df0f20150c509aef201a73f
4
- data.tar.gz: ef9bc4daf6cb23fe62984a4b55a5ce63315c9235
3
+ metadata.gz: 828f33e21107bef719e0cfe79663172057ae4191
4
+ data.tar.gz: f073980c5916b567d366e502f6830c8c4d233672
5
5
  SHA512:
6
- metadata.gz: f9f7b8ac141941117e93326f84bb93cda6615b9bc7d2247f97767bc9f03e4e395b958a4884a7dcba5c4eb11801e46da1f3f8362627a1085caa447d128c10f764
7
- data.tar.gz: 6f5222d89f56495be4404e8f7907272573e372d93996914e43a5d1e9cbc45c54310bcf44c2986083b2f410bfebf4d576e079936814c93f58dc2bbd3d2b9a1521
6
+ metadata.gz: 98f40a0354d69a0d3d8f210da9f6835923aa4e6daf75986843e8b1700e84cec8b403395d84e5a6e11666f80a8a0f2a0dbf84e7c0148f2587c4456ba8b6a1e70a
7
+ data.tar.gz: 62e5d74ced143d6bb1d7d6f9b9739736ad6eea7463a41b83a9cd434e0729fb7d10fc75b95dadde58eea48dbc6627d753284d9548b2429bf8ae2dc14cbec33bf9
data/README.md CHANGED
@@ -1,4 +1,63 @@
1
- britebox
2
- ========
1
+ britebox is CLI tool for batch email verification via BriteVerify API
2
+
3
+ # Installation
4
+
5
+ Type in terminal:
6
+
7
+ ```sh
8
+ $ gem install britebox
9
+ ```
10
+
11
+ Note: BriteVerify API key is required to use this gem
12
+
13
+ # Usage
14
+
15
+ At this moment only "watch" mode is supported. In this mode cli tool will watch target directory for csv and txt files with email column inside (exact column, column separator etc will be autodetected)
16
+
17
+ There are number of different configuration options supported, to preview them all type
18
+
19
+ ```sh
20
+ $ britebox watch --help
21
+ ```
22
+
23
+ Example:
24
+
25
+ ```sh
26
+ $ britebox watch --apikey api-key --dir /home/user/files
27
+ ```
28
+
29
+ If you tired typing api key and other settings over and over again, use `--save` flag.
30
+ Now all provided settings will be stored in ~/.britebox.json and will be loaded by default,
31
+ just type `britebox watch` next time.
32
+
33
+ Example:
34
+
35
+ ```sh
36
+ $ britebox watch --apikey api-key --dir /home/user/files --threads 5 --save
37
+ ...
38
+ Ctrl-C
39
+ ...
40
+ $ britebox watch
41
+ ```
42
+
43
+ # Web UI mode
44
+
45
+ Launch tool with key `--webui` to enable web interface.
46
+
47
+ ```sh
48
+ $ britebox watch --apikey api-key --dir /home/user/files --webui
49
+ Britebox web interface up and running, open http://localhost:7000/ in browser to see it
50
+
51
+ Watching directory /home/user/files
52
+ Output directory is /home/user/filesverified
53
+ Press Ctrl-C to quit
54
+ ```
55
+
56
+ ![screenshot](http://i.imgur.com/Zl1iAVf.png)
57
+
58
+
59
+
60
+
61
+
62
+
3
63
 
4
- BriteVerify API CLI tool
data/bin/britebox CHANGED
@@ -8,7 +8,35 @@ Thread.abort_on_exception = true
8
8
  THREAD_NUM_MAX = 10
9
9
 
10
10
  # Register action on Ctrl-C
11
- trap("SIGINT") { exit! }
11
+ [:SIGINT, :TERM, :INT].each do |sig|
12
+ trap(sig) { exit! }
13
+ end
14
+
15
+ # Hack - allow processing files with weird characters inside
16
+ class String
17
+ alias_method :orig_sub!, :sub!
18
+ def sub!(*args)
19
+ begin
20
+ orig_sub!(*args)
21
+ rescue ArgumentError
22
+ encode!("UTF-16BE", undef: :replace, invalid: :replace, replace: "")
23
+ encode!('UTF-8')
24
+ orig_sub!(*args)
25
+ end
26
+ end
27
+ end
28
+
29
+ # Hack - fix wdm issues on windows
30
+ module Listen
31
+ module Adapters
32
+ class Windows
33
+ def self.usable?
34
+ false
35
+ end
36
+ end
37
+ end
38
+ end
39
+
12
40
 
13
41
  commands = ['watch']
14
42
 
@@ -31,34 +59,56 @@ end
31
59
 
32
60
  @cli = Britebox::CLI.new
33
61
 
62
+ Britebox::Config.load
63
+
34
64
  command = $*.shift
35
65
  if command == 'watch'
36
66
  params = {}
37
- options = {}
38
67
 
39
68
  opts = OptionParser.new do |opts|
40
69
  opts.banner = "usage: britebox watch [OPTIONS]"
41
70
 
42
71
  opts.on('-d', '--dir WATCHDIR', 'Watch this directory for incoming files') do |v|
43
- params[:dir] = v
72
+ Britebox::Config.watch_dir = v
44
73
  end
45
74
 
46
75
  opts.on('-o', '--output OUTPUTDIR', 'Place verified files into this directory') do |v|
47
- params[:output] = v
76
+ Britebox::Config.out_dir = v
48
77
  end
49
78
 
50
79
  opts.on('-k', '--apikey APIKEY', 'BriteVerify API Key') do |v|
51
- params[:api_key] = v
80
+ Britebox::Config.api_key = v
52
81
  end
53
82
 
54
83
  opts.on('-t', '--threads THREADS', "Maximum number of parallel threads used for processing, "+
55
- "Default is #{Britebox::FileJobPool::THREAD_NUM_DEFAULT}, " +
84
+ "Default is #{Britebox::Config::DEFAULT_OPTIONS[:threads]}, " +
56
85
  "Maximum is #{THREAD_NUM_MAX}") do |v|
57
86
  unless (1..THREAD_NUM_MAX).include? v.to_i
58
87
  puts "Threads number should be in range 1..#{THREAD_NUM_MAX}"
59
88
  exit 1
60
89
  end
61
- params[:threads] = v.to_i
90
+ Britebox::Config.threads = v.to_i
91
+ end
92
+
93
+ opts.on('--save', 'Save options to config file') do |v|
94
+ params[:save] = true
95
+ end
96
+
97
+ opts.on('-p', '--port PORT', 'Set port number for web interface, ' +
98
+ "Default is #{Britebox::Config::DEFAULT_OPTIONS[:port]}") do |v|
99
+ unless (1..65535).include? v.to_i
100
+ puts "Port number should be in range 1..65535"
101
+ exit 1
102
+ end
103
+ Britebox::Config.port = v.to_i
104
+ end
105
+
106
+ opts.on('--ui', '--webui [on/off]', 'Enable Web UI') do |v|
107
+ Britebox::Config.ui_enabled = !['off', 'false', '0'].include?(v.to_s.downcase)
108
+ end
109
+
110
+ opts.on('--simulate [on/off]', 'Do not send requests to api, fake verification') do |v|
111
+ Britebox::Config.simulate = !['off', 'false', '0'].include?(v.to_s.downcase)
62
112
  end
63
113
 
64
114
  common_opts(opts)
@@ -71,7 +121,12 @@ if command == 'watch'
71
121
  exit 1
72
122
  end
73
123
 
74
- @cli.watch(params)
124
+ if params[:save]
125
+ Britebox::Config.save
126
+ end
127
+
128
+
129
+ @cli.watch
75
130
 
76
131
 
77
132
  end
data/lib/britebox.rb CHANGED
@@ -1,6 +1,10 @@
1
1
 
2
2
  require 'britebox/version'
3
3
  require 'britebox/helpers'
4
+ require 'britebox/config'
4
5
  require 'britebox/file_job'
5
6
  require 'britebox/file_job_pool'
6
- require 'britebox/export_buffer'
7
+ require 'britebox/export_buffer'
8
+ require 'britebox/event_log'
9
+ require 'britebox/web_ui'
10
+ require 'britebox/filtered_error_io'
data/lib/britebox/cli.rb CHANGED
@@ -9,40 +9,60 @@ module Britebox
9
9
 
10
10
  end
11
11
 
12
- def watch(params)
13
- @api_key = params[:api_key] || raise("Please provide BriteVerify API Key")
12
+ def watch
13
+ # Start WebUI
14
+ if Britebox::Config.ui_enabled
15
+ with_sanitized_stderr do
16
+ Thread.new do
17
+ logger = Logger.new(STDOUT)
18
+ logger.level = Logger::ERROR
19
+ Britebox::WebUI.run!(port: Britebox::Config.port, server_settings: {Logger: logger, AccessLog: []})
20
+ exit!
21
+ end
22
+ end
23
+ puts "Britebox web interface up and running, open http://localhost:#{Britebox::Config.port}/ in browser to see it"
24
+ puts
25
+ end
26
+
14
27
 
15
- if params[:dir]
16
- @dir = File.expand_path params[:dir]
28
+ @api_key = Config.api_key || raise("Please provide BriteVerify API Key")
29
+
30
+ if Config.watch_dir
31
+ @dir = File.expand_path Config.watch_dir
17
32
  else
18
33
  raise("Please provide directory-to-watch")
19
34
  end
20
35
 
21
36
  raise("Directory #{@dir} does not exist") unless File.exists? @dir
22
37
 
23
- if params[:output]
24
- @out_dir = File.expand_path params[:output]
38
+ if Config.out_dir
39
+ @out_dir = File.expand_path Config.out_dir
25
40
  else
26
41
  @out_dir = File.expand_path 'verified', @dir
27
42
  end
43
+ Config.out_dir = @out_dir
28
44
 
29
45
  FileUtils.mkdir_p(@out_dir) unless File.exists? @out_dir
30
46
 
31
- fj_options = {threads: params[:threads]}
32
-
33
47
  puts "Watching directory #{@dir}"
34
48
  puts "Output directory is #{@out_dir}"
49
+
50
+ if Britebox::Config.simulate
51
+ EventLog.add 'WARN', "Running in simulation mode. No real requests to API will be made."
52
+ end
53
+
35
54
  puts "Press Ctrl-C to quit"
55
+ puts
36
56
 
37
- fj_pool = FileJobPool.new(params[:threads])
57
+ $fj_pool = FileJobPool.new
38
58
 
39
- options = {filter: /\.(#{MONITOR_EXTENSIONS.join('|')})$/, ignore: /\//}
59
+ options = {filter: /\.(#{MONITOR_EXTENSIONS.join('|')})$/, ignore: /\//, polling_fallback_message: false}
40
60
 
41
61
  brite_client = BriteAPI::Client.new(@api_key)
42
62
 
43
63
  Listen.to(@dir, options) do |modified, added, removed|
44
64
  (modified + added).each do |file|
45
- fj_pool.process_file!(file, @dir, @out_dir, brite_client, fj_options)
65
+ $fj_pool.process_file!(file, @dir, @out_dir, brite_client)
46
66
  end
47
67
  end
48
68
 
@@ -53,12 +73,23 @@ module Britebox
53
73
 
54
74
  # Update current status in cycle
55
75
  loop do
56
- fj_pool.print_status
76
+ $fj_pool.print_status
57
77
  sleep 0.5
58
78
  end
79
+ end
59
80
 
81
+ private
60
82
 
61
-
83
+ def with_sanitized_stderr
84
+ $stderr = Britebox::FilteredErrorIO.new($stderr)
85
+ begin
86
+ yield
87
+ ensure
88
+ Thread.new do
89
+ sleep(1)
90
+ $stderr.restore!
91
+ end
92
+ end
62
93
  end
63
94
  end
64
95
  end
@@ -0,0 +1,91 @@
1
+ require 'json'
2
+ require 'singleton'
3
+
4
+ module Britebox
5
+ class Config
6
+ include Singleton
7
+
8
+ OPTIONS = [:threads, :api_key, :watch_dir, :out_dir, :port, :ui_enabled, :simulate]
9
+
10
+ DEFAULT_OPTIONS = {
11
+ threads: 10,
12
+ port: 7000
13
+ }
14
+
15
+ CONFIG_PATH = '~/.britebox.json'
16
+
17
+ def initialize
18
+ # load config file
19
+ init_defaults
20
+ end
21
+
22
+ def self.load
23
+ data = read_config
24
+
25
+ if data && data['config']
26
+ data['config'].each do |k, v|
27
+ self.instance.send("#{k}=", v) if self.instance.respond_to?(k)
28
+ end
29
+ true
30
+ else
31
+ false
32
+ end
33
+ end
34
+
35
+ def self.read_config
36
+ path = File.expand_path(CONFIG_PATH)
37
+ if File.exists? path
38
+ JSON.parse File.read path rescue nil
39
+ end
40
+ end
41
+
42
+ def self.values
43
+ data = {}
44
+ OPTIONS.each do |k|
45
+ data[k] = self.instance.send(k)
46
+ end
47
+ data
48
+ end
49
+
50
+ def self.save
51
+ path = File.expand_path(CONFIG_PATH)
52
+
53
+ data = read_config
54
+ data ||= {}
55
+ data['config'] ||= {}
56
+ OPTIONS.each do |key|
57
+ data['config'][key.to_s] = self.instance.send(key)
58
+ end
59
+ begin
60
+ File.open(path, 'w+') do |file|
61
+ file.write JSON.pretty_generate data
62
+ end
63
+ true
64
+ rescue Exception => ex
65
+ puts "Can't save config: #{ex.message}"
66
+ false
67
+ end
68
+ end
69
+
70
+ def init_defaults
71
+ DEFAULT_OPTIONS.each do |key, val|
72
+ send("#{key}=", val) if send(key).nil?
73
+ end
74
+ end
75
+
76
+ OPTIONS.each do |key|
77
+ attr_accessor key
78
+
79
+ define_singleton_method key do
80
+ self.instance.send(key)
81
+ end
82
+
83
+ define_singleton_method "#{key}=" do |val|
84
+ self.instance.send("#{key}=", val)
85
+ end
86
+ end
87
+
88
+
89
+
90
+ end
91
+ end
@@ -0,0 +1,35 @@
1
+ require 'singleton'
2
+ require 'ostruct'
3
+
4
+ module Britebox
5
+ class EventLog
6
+ include Singleton
7
+ attr_accessor :log_lines
8
+
9
+ def initialize
10
+ @log_lines = []
11
+ end
12
+
13
+ def self.empty?
14
+ self.instance.log_lines.empty?
15
+ end
16
+
17
+ def self.push(event_type, message)
18
+ event = OpenStruct.new(type: event_type, message: message, time: Time.now)
19
+ self.instance.log_lines.push(event)
20
+
21
+ # if Britebox::Config.log ...
22
+ # save event to log
23
+
24
+ true
25
+ end
26
+
27
+ def self.pop
28
+ self.instance.log_lines.pop
29
+ end
30
+
31
+ class << self
32
+ alias_method :add, :push
33
+ end
34
+ end
35
+ end
@@ -14,7 +14,7 @@ module Britebox
14
14
  @file_name = file_name
15
15
  @brite_client = brite_client
16
16
 
17
- @threads_count = options[:threads] || Britebox::FileJobPool::THREAD_NUM_DEFAULT
17
+ @threads_count = options[:threads]
18
18
  @queue = options[:queue]
19
19
 
20
20
  @status = 'pending'
@@ -33,6 +33,26 @@ module Britebox
33
33
  @semaphore = Mutex.new
34
34
  end
35
35
 
36
+ def as_json
37
+ {
38
+ id: id,
39
+ file_name: id,
40
+ status: @status,
41
+ threads: @threads_count,
42
+ size_total: @size_total,
43
+ size_processed: @size_processed,
44
+ started_at: @started_at,
45
+ processed_at: @processed_at,
46
+ duration: duration,
47
+ percent_complete: percent_complete,
48
+ error: @error
49
+ }
50
+ end
51
+
52
+ def id
53
+ File.basename(@file_name)
54
+ end
55
+
36
56
  def buffer_size
37
57
  @threads_count * 4
38
58
  end
@@ -52,8 +72,43 @@ module Britebox
52
72
  end
53
73
  end
54
74
 
75
+ def cancelled?
76
+ status == 'cancelled'
77
+ end
78
+
79
+ def verifying?
80
+ status == 'verifying'
81
+ end
82
+
83
+ def paused?
84
+ status == 'paused'
85
+ end
86
+
87
+ def pending?
88
+ status == 'pending'
89
+ end
90
+
91
+ def error?
92
+ status == 'error'
93
+ end
94
+
95
+ def status=(new_status)
96
+ return if status == new_status
97
+
98
+ # Release processing slot
99
+ if verifying? && ['paused', 'cancelled'].include?(new_status)
100
+ @queue.push(:flag) if @queue
101
+ end
102
+
103
+ if new_status == 'verifying'
104
+ raise("status 'verifying' can't be set, use 'pending' instead")
105
+ end
106
+
107
+ @status = new_status
108
+ end
109
+
55
110
  def verify!(file_name_to)
56
- return if @status == 'error'
111
+ return if error?
57
112
 
58
113
  test_lines = []
59
114
  begin
@@ -88,17 +143,39 @@ module Britebox
88
143
  @threads_count.times do
89
144
  @threads << Thread.new do
90
145
  loop do
146
+ # Pause all processing
147
+ loop do
148
+ break unless paused?
149
+ sleep(0.1)
150
+ end
151
+
152
+ break if cancelled?
153
+
154
+ # Wait for processing slot
155
+ if pending?
156
+ @semaphore.synchronize do
157
+ @queue.pop if @queue && pending?
158
+ @status = 'verifying'
159
+ end
160
+ end
161
+
91
162
  item = Timeout.timeout(1) { @in_buffer.pop } rescue nil
92
163
  break if item.nil?
93
164
 
94
165
  email = item[:line][@email_index]
95
166
  if email.to_s.match(EMAIL_PATTERN)
96
- begin
97
- contact = @brite_client.contacts.create(email: email)
98
- contact.verify!
99
- contact_status = [contact.status, contact.response[:email]['disposable'], contact.response[:email]['role_address']]
100
- rescue
101
- contact_status = ['error', false, false]
167
+ if Britebox::Config.simulate
168
+ # Do not send real requests in this mode
169
+ sleep(1)
170
+ contact_status = ['unknown', false, false]
171
+ else
172
+ begin
173
+ contact = @brite_client.contacts.create(email: email)
174
+ contact.verify!
175
+ contact_status = [contact.status, contact.response[:email]['disposable'], contact.response[:email]['role_address']]
176
+ rescue
177
+ contact_status = ['error', false, false]
178
+ end
102
179
  end
103
180
  else
104
181
  contact_status = ['invalid', false, false]
@@ -120,7 +197,8 @@ module Britebox
120
197
  end
121
198
  next if line.nil? || line.size == 0
122
199
  # Throttle file reading
123
- sleep(0.1) while @in_buffer.size > buffer_size
200
+ break if cancelled?
201
+ sleep(0.1) while @in_buffer.size > buffer_size && !cancelled?
124
202
 
125
203
  @in_buffer << {n: idx, line: line}
126
204
  idx += 1
@@ -132,7 +210,12 @@ module Britebox
132
210
  @out_buffer.close
133
211
 
134
212
  @processed_at = Time.now
135
- @status = 'complete'
213
+
214
+ if cancelled?
215
+ File.delete file_name_to
216
+ else
217
+ @status = 'complete'
218
+ end
136
219
 
137
220
  # Release the lock
138
221
  @queue.push(:flag) if @queue