Commit cf3464317ba769af1ee5c9d94c70f5ffc282c887:
realtime updates of email changes
Branch: refs/heads/master
Author: Sam Ruby <[email protected]>
Committer: Sam Ruby <[email protected]>
Pusher: rubys <[email protected]>
------------------------------------------------------------
www/secmail/Gemfile | +++ ---
www/secmail/Rakefile | + -
www/secmail/models/events.rb | +++++++++
www/secmail/server.rb | +++++++++++++
www/secmail/views/index.js.rb | +++++++++ --
------------------------------------------------------------
124 changes: 117 additions, 7 deletions.
------------------------------------------------------------
diff --git a/www/secmail/Gemfile b/www/secmail/Gemfile
index 25f19b5..a1812fd 100644
--- a/www/secmail/Gemfile
+++ b/www/secmail/Gemfile
@@ -3,14 +3,14 @@ source 'https://rubygems.org'
gem 'mail'
gem 'rake'
gem 'zip'
-gem 'whimsy-asf'
+gem 'whimsy-asf', '~> 0.0.73'
gem 'sinatra'
gem 'sanitize'
-gem 'wunderbar', '~> 1.0.11'
+gem 'wunderbar', '~> 1.0.13'
gem 'ruby2js', '~> 2.0.12'
gem 'execjs'
+gem 'listen'
group :demo do
- gem 'listen'
gem 'puma'
end
diff --git a/www/secmail/Rakefile b/www/secmail/Rakefile
index fb0d600..a0137cb 100644
--- a/www/secmail/Rakefile
+++ b/www/secmail/Rakefile
@@ -32,7 +32,7 @@ end
desc 'WebServer that provides an interface to explore emails'
task :server => :bundle do
- ENV['RACK_ENV']='production'
+ ENV['RACK_ENV']='development'
require 'wunderbar'
module Wunderbar::Listen
diff --git a/www/secmail/models/events.rb b/www/secmail/models/events.rb
new file mode 100644
index 0000000..42ad142
--- /dev/null
+++ b/www/secmail/models/events.rb
@@ -0,0 +1,71 @@
+require 'listen'
+require 'thread'
+
+class Events
+ @@list = []
+
+ def initialize
+ @@list.push self
+
+ @events = Queue.new
+
+ @listener = Listen.to ARCHIVE do |modified, added, removed|
+ (modified + added).each do |file|
+ next unless file.end_with? '.yml'
+ mbox = Mailbox.new(File.basename(file))
+ @events.push({messages: mbox.client_headers})
+ end
+ end
+
+ @listener.start
+
+ @closed = false
+
+ # As some TCP/IP implementations will close idle sockets after as little
+ # as 30 seconds, sent out a heartbeat every 25 seconds. Due to limitations
+ # of some versions of Ruby (2.0, 2.1), this is lowered to every 5 seconds
+ # in development mode to allow for quicker restarting after a trap/signal.
+ Thread.new do
+ loop do
+ sleep(ENV['RACK_ENV'] == 'development' ? 5 : 25)
+ break if @closed
+ @events.push(:heartbeat)
+ end
+
+ @events.push(:exit)
+ @listener.stop
+ end
+ end
+
+ def pop
+ @events.pop
+ end
+
+ def close
+ @@list.delete self
+ @events.clear
+ @closed = true
+
+ begin
+ @events.push :exit
+ rescue ThreadError
+ # some versions of Ruby don't allow queue operations in traps
+ end
+ end
+
+ def self.shutdown
+ @@list.dup.each {|event| event.close}
+ end
+end
+
+# puma uses SIGUSR2
+restart_usr2 ||= trap 'SIGUSR2' do
+ restart_usr2.call if Proc === restart_usr2
+ Events.shutdown
+end
+
+# thin uses SIGHUP
+restart_hup ||= trap 'SIGHUP' do
+ restart_hup.call if Proc === restart_hup
+ Events.shutdown
+end
diff --git a/www/secmail/server.rb b/www/secmail/server.rb
index bb95918..198c5a4 100644
--- a/www/secmail/server.rb
+++ b/www/secmail/server.rb
@@ -12,6 +12,7 @@
require_relative 'helpers'
require_relative 'models/mailbox'
require_relative 'models/safetemp'
+require_relative 'models/events'
# list of messages
get '/' do
@@ -124,3 +125,27 @@
[200, {'Content-Type' => part.content_type}, part.body.to_s]
end
+
+# event stream for server sent events (a.k.a EventSource)
+get '/events', provides: 'text/event-stream' do
+ events = Events.new
+
+ stream :keep_open do |out|
+ out.callback {events.close}
+
+ loop do
+ event = events.pop
+
+ if Hash === event or Array === event
+ out << "data: #{JSON.dump(event)}\n\n"
+ elsif event == :heartbeat
+ out << ":\n"
+ elsif event == :exit
+ out.close
+ break
+ else
+ out << "data: #{event.inspect}\n\n"
+ end
+ end
+ end
+end
diff --git a/www/secmail/views/index.js.rb b/www/secmail/views/index.js.rb
index d0e0dba..90d35f7 100644
--- a/www/secmail/views/index.js.rb
+++ b/www/secmail/views/index.js.rb
@@ -57,8 +57,8 @@ def componentWillMount()
@nextmbox = @@mbox
end
- # on initial load, fetch latest mailbox and subscribe to keyboard events,
- # initialize selected item.
+ # on initial load, fetch latest mailbox, subscribe to keyboard and
+ # server side events, and initialize selected item.
def componentDidMount()
self.fetch_month() do
# for the first week of the month, fetch previous month too
@@ -66,7 +66,21 @@ def componentDidMount()
end
window.onkeydown = self.keydown
- self.selectRow Status.selected if @messages.length > 0
+
+ # when events are received, update messages
+ events = EventSource.new('events')
+ events.addEventListener :message do |event|
+ messages = JSON.parse(event.data).messages
+ self.merge messages if messages
+ end
+
+ # close connection on exit
+ window.addEventListener :unload do |event|
+ events.close()
+ end
+
+ # select row
+ self.selectRow Status.selected if @messages.length > 0
end
# when content changes, ensure selected message is visible