You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@whimsical.apache.org by Sam Ruby <ru...@apache.org> on 2015/12/22 17:37:21 UTC

[whimsy.git] [1/1] Commit 83a28dc: more progress towards drag/drop

Commit 83a28dcc7a02a2c5893e7daf1ba978744ae0e651:
    more progress towards drag/drop


Branch: refs/heads/secmail
Author: Sam Ruby <ru...@intertwingly.net>
Committer: Sam Ruby <ru...@intertwingly.net>
Pusher: rubys <ru...@apache.org>

------------------------------------------------------------
www/secmail/README                                           | +++++++++ -
www/secmail/models/attachment.rb                             | +++++++++ -
www/secmail/models/mailbox.rb                                | + -
www/secmail/models/message.rb                                | ++++++++ -
www/secmail/server.rb                                        | +++ ---
www/secmail/views/actions/drop.json.rb                       | ++++++++++ ---
www/secmail/views/parts.js.rb                                | ++++++++++ ---
------------------------------------------------------------
139 changes: 116 additions, 23 deletions.
------------------------------------------------------------


diff --git a/www/secmail/README b/www/secmail/README
index d18715f..161a350 100644
--- a/www/secmail/README
+++ b/www/secmail/README
@@ -1,6 +1,7 @@
 This directory contains a script that fetches and parsed secretary emails
 and a server that will enable exploration of those emails.
 
+
 Usage:
 
   rake fetch
@@ -17,6 +18,11 @@ Notes:
 
   Secretary email archive currently requires about approximately 11 Gigabytes.
 
+  Some functions will require installations of imageMagick and pdftk.
+
+  OS X El Capitan users may want to look at:
+    http://stackoverflow.com/a/33248310
+
 Overview of files:
 
   Gemfile:            Ruby configuration (installation of gems)
@@ -46,7 +52,9 @@ Overview of control flow:
 
     html views:       Method names that start with an underscore generate HTML.
                       This HTML may pull in scripts, stylesheets, and have
-                      inline code that renders other views.  
+                      inline code that renders other views.  Views in the
+                      actions subdirectory produce responses to HTTP post
+                      requests.
 
     js views:         This code is converted from Ruby to JavaScript.
                       This conversion is aware of React.js and will perform
diff --git a/www/secmail/models/attachment.rb b/www/secmail/models/attachment.rb
index aed6bde..eae4613 100644
--- a/www/secmail/models/attachment.rb
+++ b/www/secmail/models/attachment.rb
@@ -1,14 +1,45 @@
 class Attachment
-  def initialize(headers, part)
+  IMAGE_TYPES = %w(.gif, .jpg, .jpeg, .png)
+  attr_reader :headers
+
+  def initialize(message, headers, part)
+    @message = message
     @headers = headers
     @part = part
   end
 
   def content_type
-    @part.content_type
+    headers[:mine] || @part.content_type
   end
 
   def body
-    @part.body
+    headers[:content] || @part.body
+  end
+
+  def safe_name
+    name = @part.filename
+    name.gsub! /^\W/, ''
+    name.gsub! /[^\w.]/, '_'
+    name.untaint
+  end
+
+  def as_pdf
+    file = Tempfile.new([safe_name, '.pdf'], encoding: Encoding::BINARY)
+    file.write(body)
+    file.rewind
+
+    return file if content_type.end_with? '/pdf'
+    return file if @part.filename.end_with? '.pdf'
+
+    ext = File.extname(@part.filename).downcase
+
+    if IMAGE_TYPES.include? ext or content_type.start_with? 'image/'
+      pdf = Tempfile.new([safe_name, '.pdf'], encoding: Encoding::BINARY)
+      system 'convert', file.path, pdf.path
+      file.unlink
+      return pdf
+    end
+
+    return file
   end
 end
diff --git a/www/secmail/models/mailbox.rb b/www/secmail/models/mailbox.rb
index ab82d0c..148cc1b 100644
--- a/www/secmail/models/mailbox.rb
+++ b/www/secmail/models/mailbox.rb
@@ -110,7 +110,7 @@ def messages
   def find(hash)
     headers = YAML.load_file(yaml_file) rescue {}
     email = messages.find {|message| Mailbox.hash(message) == hash}
-    Message.new(headers[hash], email) if email
+    Message.new(self, hash, headers[hash], email) if email
   end
 
   #
diff --git a/www/secmail/models/message.rb b/www/secmail/models/message.rb
index 2247b59..af66139 100644
--- a/www/secmail/models/message.rb
+++ b/www/secmail/models/message.rb
@@ -1,5 +1,7 @@
 class Message
-  def initialize(headers, email)
+  def initialize(mailbox, hash, headers, email)
+    @hash = hash
+    @mailbox = mailbox
     @headers = headers
     @email = email
   end
@@ -14,8 +16,8 @@ def find(name)
       attach.filename == name or attach['Content-ID'].to_s == name
     end
 
-    if part
-      Attachment.new(headers, part)
+    if headers
+      Attachment.new(self, headers, part)
     end
   end
 
@@ -46,4 +48,30 @@ def html_part
   def text_part
     mail.html_part
   end
+
+  def attachments
+    @headers[:attachments].map {|attachment| attachment[:name]}
+  end
+
+  def update_attachment name, values
+    attachment = find(name)
+    if attachment
+      attachment.headers.merge! values
+      write
+    end
+  end
+
+  def delete_attachment name
+    attachment = find(name)
+    if attachment
+      @headers[:attachments].delete attachment.headers
+      write
+    end
+  end
+
+  def write
+    @mailbox.update do |yaml|
+      yaml[@hash] = @headers
+    end
+  end
 end
diff --git a/www/secmail/server.rb b/www/secmail/server.rb
index 1b211a9..00dcff8 100644
--- a/www/secmail/server.rb
+++ b/www/secmail/server.rb
@@ -78,9 +78,9 @@
 
 # list of parts for a single message
 get %r{^/(\d{6})/(\w+)/_index_$} do |month, hash|
-  @message = Mailbox.new(month).headers[hash]
-  pass unless @message
-  @attachments = @message[:attachments]
+  message = Mailbox.new(month).find(hash)
+  pass unless message
+  @attachments = message.attachments
   _html :parts
 end
 
diff --git a/www/secmail/views/actions/drop.json.rb b/www/secmail/views/actions/drop.json.rb
index 32cb2c2..3f25943 100644
--- a/www/secmail/views/actions/drop.json.rb
+++ b/www/secmail/views/actions/drop.json.rb
@@ -6,12 +6,25 @@
 
 mbox = Mailbox.new(month)
 message = mbox.find(hash)
-source = message.find(@source)
-target = message.find(@target)
 
-STDERR.puts source.inspect
-STDERR.puts target.inspect
-FileUtils.mkdir_p "work/#@message"
+begin
+  source = message.find(@source).as_pdf
+  target = message.find(@target).as_pdf
 
+  output = Tempfile.new('output')
 
-{success: true}
+  Kernel.system 'pdftk', target.path, source.path, 'cat', 'output',
+    output.path
+
+  message.update_attachment @target, content: output.read,
+    mime: 'application/pdf'
+
+  message.delete_attachment @source
+
+ensure
+  source.unlink if source
+  target.unlink if target
+  output.unlink if output
+end
+
+{attachments: message.attachments, selected: @target}
diff --git a/www/secmail/views/parts.js.rb b/www/secmail/views/parts.js.rb
index 14bb4d5..80120b0 100644
--- a/www/secmail/views/parts.js.rb
+++ b/www/secmail/views/parts.js.rb
@@ -2,6 +2,7 @@ class Parts < React
   def initialize
     @selected = nil
     @busy = false
+    @attachments = []
   end
 
   def render
@@ -15,12 +16,13 @@ def render
       onDragEnd: self.dragEnd,
       onDrop: self.drop,
       onContextMenu: self.menu,
+      onClick: self.select
     }
 
-    _ul @@attachments, ref: 'attachments' do |attachment|
+    _ul @attachments, ref: 'attachments' do |attachment|
+      options[:className] = ('selected' if attachment == @selected)
       _li options do
-        _a attachment.name, href: attachment.name, target: 'content',
-          draggable: 'false'
+        _a attachment, href: attachment, target: 'content', draggable: 'false'
       end
     end
 
@@ -33,10 +35,15 @@ def render
     _img.spinner src: '../../rotatingclock-slow2.gif' if @busy
   end
 
+  # initialize attachments list with the data from the server
+  def componentWillMount()
+    @attachments = @@attachments
+  end
+
   # disable context menu and register mouse and keyboard handlers
   def componentDidMount()
     $menu.style.display = :none
-    window.onmousedown = self.click
+    window.onmousedown = self.window_click
 
     # register keyboard handler on parent window and all frames
     window.parent.onkeydown = self.keydown
@@ -57,16 +64,19 @@ def menu(event)
   end
 
   # hide context menu whenever a click is received outside the menu
-  def click(event)
+  def window_click(event)
     target = event.target
     while target
       return if target.class == 'contextMenu'
       target = target.parentNode
     end
-    console.log 'click'
     $menu.style.display = :none
   end
 
+  def select(event)
+    @selected = event.currentTarget.querySelector('a').getAttribute('href')
+  end
+
   def keydown(event)
     if event.keyCode == 8 or event.keyCode == 46 # backspace or delete
       if event.metaKey
@@ -135,8 +145,11 @@ def drop(event)
     @busy = true
     @drag = nil
     HTTP.post '../../actions/drop', data do |response| 
+      @attachments = response.attachments
+      @selected = href
       @busy = false
       target.classList.remove 'drop-target'
+      window.parent.frames.content.location.href=response.selected
     end
   end