├── Screensaver.dropzone ├── Say Text.dropzone ├── Timestamp.dropzone ├── Sleep.dropzone ├── Time.dropzone ├── Print.dropzone ├── FileMerge.dropzone ├── Entourage Create Message with Text.dropzone ├── Changes.dropzone ├── is.gd.dropzone ├── Kaleidoscope.dropzone ├── Create text clipping.dropzone ├── Test Destination.dropzone ├── pastie-isgd.rb.dropzone ├── Finder Path.dropzone ├── Zip Files.dropzone ├── Copy to Terminal path.dropzone ├── FWMount.dropzone ├── Secure Eraser.dropzone ├── bit.ly.dropzone ├── Open URLS.dropzone ├── Desktop Picture.dropzone ├── Clipboard Links to Markdown.dropzone ├── LinkBunch.dropzone ├── Pastebin.dropzone ├── Tumblr-net-http.dropzone ├── Tumblr.dropzone ├── ImageShack.dropzone ├── Dropbox.dropzone ├── Media Filer.dropzone ├── WAZBlobs.dropzone ├── GitHub Gist.dropzone ├── Posterous.dropzone ├── TwitDoc.dropzone ├── gdocs_uploader.dropzone ├── LinkSmasher.dropzone ├── TwitPic.dropzone ├── Filer.dropzone ├── UpShot.dropzone ├── Download.dropzone ├── SCP Upload.dropzone ├── SCP Upload with Gallery.dropzone └── aws.dropzone /Screensaver.dropzone: -------------------------------------------------------------------------------- 1 | #!/usr/bin/ruby 2 | 3 | # Dropzone Destination Info 4 | # Name: Screensaver 5 | # Description: Starts the screensaver. 6 | # Events: Clicked 7 | # Creator: Florian Poeck 8 | # URL: http://macfidelity.de 9 | # IconURL: http://aptonic.com/destinations/icons/screensaver.png 10 | 11 | def clicked 12 | `osascript -e 'tell application "ScreenSaverEngine" to activate'` 13 | end -------------------------------------------------------------------------------- /Say Text.dropzone: -------------------------------------------------------------------------------- 1 | #!/usr/bin/ruby 2 | 3 | # Dropzone Destination Info 4 | # Name: Say Text 5 | # Description: Text dragged onto this destination will be read aloud to you. 6 | # Events: Dragged 7 | # Handles: NSStringPboardType 8 | # Creator: Florian Poeck 9 | # URL: http://macfidelity.de 10 | # IconURL: http://aptonic.com/destinations/icons/say.png 11 | 12 | def dragged 13 | system "say \"#{$items[0]}\" &" 14 | end -------------------------------------------------------------------------------- /Timestamp.dropzone: -------------------------------------------------------------------------------- 1 | #!/usr/bin/ruby 2 | 3 | # Dropzone Destination Info 4 | # Name: Timestamp 5 | # Description: Displays the current timestamp and copies it to the clipboard. 6 | # Events: Clicked 7 | # Creator: Tinu Cleatus 8 | # URL: http://blog.tinucleatus.com 9 | # IconURL: http://disk.tinucleatus.com/TM256.png 10 | 11 | def clicked 12 | date = Time.now.to_i 13 | $dz.finish(date) 14 | $dz.url(date) 15 | end -------------------------------------------------------------------------------- /Sleep.dropzone: -------------------------------------------------------------------------------- 1 | #!/usr/bin/ruby 2 | 3 | # Dropzone Destination Info 4 | # Name: Sleep 5 | # Description: Puts your Mac to sleep. 6 | # Events: Clicked 7 | # Creator: Paul William 8 | # URL: http://entropytheblog.com 9 | # IconURL: http://aptonic.com/destinations/icons/sleep.png 10 | 11 | def clicked 12 | $dz.finish("Sleeping...") 13 | $dz.url(false) 14 | `osascript -e 'tell application "System Events" to sleep'` 15 | end -------------------------------------------------------------------------------- /Time.dropzone: -------------------------------------------------------------------------------- 1 | #!/usr/bin/ruby 2 | 3 | # Dropzone Destination Info 4 | # Name: Date & Time 5 | # Description: Displays the current date and time and copies it to the clipboard. 6 | # Events: Clicked 7 | # Creator: Paul William 8 | # URL: http://entropytheblog.com 9 | # IconURL: http://aptonic.com/destinations/icons/clock.png 10 | 11 | def clicked 12 | date = `date`.strip 13 | $dz.finish(date) 14 | $dz.url(date) 15 | end -------------------------------------------------------------------------------- /Print.dropzone: -------------------------------------------------------------------------------- 1 | #!/usr/bin/ruby 2 | 3 | # Dropzone Destination Info 4 | # Name: Print 5 | # Description: Dropped files or images will be printed to the default printer. 6 | # Handles: NSFilenamesPboardType 7 | # Events: Dragged 8 | # Creator: Aptonic Software 9 | # URL: http://aptonic.com 10 | # IconURL: http://aptonic.com/destinations/icons/print.png 11 | 12 | def dragged 13 | $items.each {|item| `lp \"#{item}\" >& /dev/null &`} 14 | $dz.finish("Printing...") 15 | $dz.url(false) 16 | end -------------------------------------------------------------------------------- /FileMerge.dropzone: -------------------------------------------------------------------------------- 1 | # Dropzone Destination Info 2 | # Name: Diff files with FileMerge 3 | # Description: You can diff files with dragging them 4 | # Handles: NSFilenamesPboardType 5 | # Events: dragged 6 | # Creator: Gergo Sulymosi 7 | # URL: http://twitter.com/trekdemo 8 | # IconURL: http://trek.2sided.hu/uploads/filemergeicon.png 9 | 10 | def dragged 11 | $dz.begin("Opening FileMerge...") 12 | 13 | unless $items.size == 2 14 | $dz.finish("You must drag two files!") 15 | $dz.url(false) 16 | exit 17 | end 18 | 19 | system('opendiff', $items.first, $items.last) 20 | 21 | $dz.url(false) 22 | end -------------------------------------------------------------------------------- /Entourage Create Message with Text.dropzone: -------------------------------------------------------------------------------- 1 | #!/usr/bin/ruby 2 | 3 | # Dropzone Destination Info 4 | # Name: Mail Text 5 | # Description: Creates new Entourage mail message from dropped text. 6 | # Handles: NSStringPboardType 7 | # Events: Dragged 8 | # Creator: Eric Sylvester 9 | # URL: http://www.logansportmemorial.org 10 | # IconURL: http://www.logansportmemorial.org/icons/entourage.png 11 | 12 | def dragged 13 | $items.each {|item| `osascript -e 'tell application \"Microsoft Entourage\"' -e 'activate' -e 'set newMessage to make new draft window with properties {to recipients:\"\", content:\"#{item}\"}' -e 'end tell'`} 14 | end -------------------------------------------------------------------------------- /Changes.dropzone: -------------------------------------------------------------------------------- 1 | # Dropzone Destination Info 2 | # Name: Diff files with Changes 3 | # Description: Drag two files to run a diff with Changes.app (must install the Terminal utility from the Changes menu) 4 | # Handles: NSFilenamesPboardType 5 | # Events: dragged 6 | # Creator: Brett Terpstra, w/code by Gergo Sulymosi 7 | # URL: http://brettterpstra.com 8 | # IconURL: http://abyss.designheresy.com/changesicon.png 9 | 10 | def dragged 11 | $dz.begin("Opening Changes...") 12 | 13 | unless $items.size == 2 14 | $dz.finish("You must drag two files!") 15 | $dz.url(false) 16 | exit 17 | end 18 | 19 | system('chdiff', $items.first, $items.last) 20 | 21 | $dz.url(false) 22 | end -------------------------------------------------------------------------------- /is.gd.dropzone: -------------------------------------------------------------------------------- 1 | #!/usr/bin/ruby 2 | 3 | # Dropzone Destination Info 4 | # Name: Is.Gd 5 | # Description: A dropped URL will be converted to a Is.Gd URL. 6 | # Handles: NSStringPboardType 7 | # Creator: Aptonic Software 8 | # URL: http://aptonic.com 9 | # IconURL: http://aptonic.com/destinations/icons/is.gd.png 10 | 11 | def dragged 12 | $dz.determinate(false) 13 | $dz.begin("Getting is.gd URL") 14 | 15 | if $items[0] =~ /http/ 16 | url = IsGd.minify($items[0]) 17 | $dz.finish("URL is now on clipboard") 18 | $dz.url(url) 19 | else 20 | $dz.finish("Invalid URL") 21 | $dz.url(false) 22 | end 23 | end 24 | 25 | def clicked 26 | system("open http://is.gd/") 27 | end 28 | 29 | -------------------------------------------------------------------------------- /Kaleidoscope.dropzone: -------------------------------------------------------------------------------- 1 | # Dropzone Destination Info 2 | # Name: Diff files with Kaleidoscope 3 | # Description: Drag two files to run a diff with Kaleidoscope.app (must install the Terminal utility from the Kaleidoscope preferences) 4 | # Handles: NSFilenamesPboardType 5 | # Events: dragged 6 | # Creator: Brett Terpstra, w/code by Gergo Sulymosi 7 | # URL: http://brettterpstra.com 8 | # IconURL: http://assets.brettterpstra.com/Kaleidoscope_icon.png 9 | 10 | def dragged 11 | $dz.begin("Opening Changes...") 12 | 13 | unless $items.size == 2 14 | $dz.finish("You must drag two files!") 15 | $dz.url(false) 16 | exit 17 | end 18 | 19 | system('ksdiff', $items.first, $items.last) 20 | 21 | $dz.url(false) 22 | end 23 | -------------------------------------------------------------------------------- /Create text clipping.dropzone: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # Dropzone Destination Info 4 | # Name: Create text clip 5 | # Description: Creates a text clip with the dragged text 6 | # Handles: NSStringPboardType 7 | # Creator: Edgar Suarez 8 | # URL: http://e.dgar.org 9 | # IconURL: http://aptonic.com/destinations/icons/text.png 10 | # OptionsNIB: ChooseFolder 11 | 12 | def dragged 13 | destination = "#{ENV['EXTRA_PATH']}" 14 | 15 | $dz.determinate(false) 16 | $dz.begin("Creating text clip...") 17 | 18 | File.open("#{destination}/#{$items[0][0..28]}.textClipping", "w") do |f| 19 | f.puts $items[0] 20 | end 21 | 22 | $dz.finish("Text clip saved") 23 | $dz.url(false) 24 | end 25 | 26 | def clicked 27 | system("open \"#{ENV['EXTRA_PATH']}\"") 28 | end 29 | -------------------------------------------------------------------------------- /Test Destination.dropzone: -------------------------------------------------------------------------------- 1 | #!/usr/bin/ruby 2 | 3 | # Dropzone Destination Info 4 | # Name: Test Destination 5 | # Description: Provides the ability to do some cool thing. 6 | # Handles: NSFilenamesPboardType 7 | # Events: Clicked, Dragged 8 | # Creator: Aptonic Software 9 | # URL: http://aptonic.com 10 | # IconURL: http://aptonic.com/destinations/icons/test.png 11 | 12 | def dragged 13 | $dz.determinate(true) 14 | file_path = $items[0] 15 | $dz.begin("Doing something with #{file_path}...") 16 | 17 | $dz.percent(20) 18 | sleep(1) 19 | $dz.percent(50) 20 | sleep(1) 21 | $dz.percent(100) 22 | sleep(1) 23 | 24 | $dz.finish("Finished Task") 25 | $dz.url(false) 26 | end 27 | 28 | def clicked 29 | $dz.finish("You clicked me!") 30 | $dz.url(false) 31 | end -------------------------------------------------------------------------------- /pastie-isgd.rb.dropzone: -------------------------------------------------------------------------------- 1 | #!/usr/bin/ruby 2 | 3 | # Dropzone Destination Info 4 | # Name: Pastie / Is.Gd 5 | # Description: Sends the dropped text to the Pastie (pastie.org) service and minifies the resulting Pastie URL using Is.Gd. 6 | # Handles: NSStringPboardType 7 | # Events: Clicked, Dragged 8 | # Creator: Paul William 9 | # URL: http://entropytheblog.com 10 | # IconURL: http://aptonic.com/destinations/icons/pastie-is.gd.png 11 | 12 | def dragged 13 | $dz.determinate(false) 14 | $dz.begin("Performing Paste...") 15 | 16 | pastie = Pastie::API.new 17 | url = pastie.paste($items[0], 'plain_text', false) 18 | url = IsGd.minify(url) 19 | $dz.finish("URL is now on clipboard") 20 | $dz.url(url) 21 | end 22 | 23 | def clicked 24 | system("open http://pastie.org/") 25 | end -------------------------------------------------------------------------------- /Finder Path.dropzone: -------------------------------------------------------------------------------- 1 | #!/usr/bin/ruby 2 | 3 | # Dropzone Destination Info 4 | # Name: Finder Path 5 | # Description: Copies the path of the currently selected item in the Finder to the clipboard. 6 | # Handles: NSFilenamesPboardType 7 | # Events: Clicked 8 | # Creator: Aptonic Software 9 | # URL: http://aptonic.com 10 | # IconURL: http://aptonic.com/destinations/icons/finder.png 11 | 12 | def clicked 13 | 14 | path=`osascript <& /dev/null") 32 | Rsync.do_copy(zipfile.gsub(/ /, '\ '), desktop, true) 33 | 34 | $dz.finish("ZIP created") 35 | $dz.url(false) 36 | end -------------------------------------------------------------------------------- /Copy to Terminal path.dropzone: -------------------------------------------------------------------------------- 1 | #!/usr/bin/ruby 2 | 3 | # Dropzone Destination Info 4 | # Name: Copy to Terminal path 5 | # Description: Dropped files will be copied to the current Terminal.app path. 6 | # Handles: NSFilenamesPboardType 7 | # Events: Dragged 8 | # Creator: Aptonic Software 9 | # URL: http://aptonic.com 10 | # IconURL: http://aptonic.com/destinations/icons/terminal.png 11 | 12 | def dragged 13 | 14 | `osascript < name, 'link' => url } 49 | norepeat.push name 50 | percent += inc 51 | $dz.percent(percent) 52 | } 53 | output = output.sort {|a,b| a['title'] <=> b['title']} 54 | o = "" 55 | output.each { |x| 56 | o += "[#{x['title']}]: #{x['link']}\n" 57 | } 58 | $dz.finish("#{output.length} links found") 59 | %x{echo "#{o}"|pbcopy} 60 | $dz.url(false) 61 | end 62 | end 63 | 64 | def dragged 65 | clicked($items.to_s) 66 | end -------------------------------------------------------------------------------- /LinkBunch.dropzone: -------------------------------------------------------------------------------- 1 | #!/usr/bin/ruby 2 | 3 | # Dropzone Destination Info 4 | # Name: LinkBuncher 5 | # Description: Scans clipboard or dropped text for links and creates a LinkBunch (http://linkbun.ch) 6 | # Handles: NSStringPboardType 7 | # Events: Clicked, Dragged 8 | # Creator: Brett Terpstra 9 | # URL: http://brettterpstra.com 10 | # IconURL: http://brettterpstra.com/destinations/icons/TextCleaner.png 11 | 12 | require 'net/http' 13 | require 'cgi' 14 | 15 | def bunch_links 16 | $KCODE = 'u' 17 | links = $text.gsub(/http([^\s]+)\n/,'http\\1').scan /(https?:\/\/[^ \n"']+)/m 18 | unless links.empty? 19 | linkbunch = CGI.escape(links.join("\n")) 20 | url = "http://linkbun.ch/linkbunch.php?links=#{linkbunch}&bunch=Bunch&mode=api" 21 | res = Net::HTTP.get_response(URI.parse(url)) 22 | if res.code.to_i == 200 23 | %x{/usr/local/bin/growlnotify -a "Mail.app" -m "#{links.length} links bunched" -t "Linkbunch in Clipboard"} if File.exists?("/usr/local/bin/growlnotify") 24 | $dz.finish("#{links.length} links bunched") 25 | return res.body 26 | else 27 | %x{/usr/local/bin/growlnotify -a "Mail.app" -m "Son of a bitch." -t "Linkbunch returned an error"} if File.exists?("/usr/local/bin/growlnotify") 28 | $dz.finish("Linkbunch returned an error") 29 | return false 30 | end 31 | else 32 | %x{/usr/local/bin/growlnotify -a "Mail.app" -m "Select something with links in it. And say no to drugs." -t "No links detected"} if File.exists?("/usr/local/bin/growlnotify") 33 | $dz.finish("No links detected") 34 | return false 35 | end 36 | end 37 | 38 | 39 | def clicked(was_dragged = false) 40 | $dz.begin("Scanning for links") 41 | $dz.determinate(false) 42 | `pbpaste|pbcopy` 43 | $text = %x{__CF_USER_TEXT_ENCODING=$UID:0x8000100:0x8000100 pbpaste}.strip unless was_dragged == true 44 | $dz.url(bunch_links()) 45 | end 46 | 47 | def dragged 48 | $text = $items.to_s 49 | clicked(true) 50 | end 51 | -------------------------------------------------------------------------------- /Pastebin.dropzone: -------------------------------------------------------------------------------- 1 | #!/usr/bin/ruby 2 | 3 | # Dropzone Destination Info 4 | # Name: Pastebin 5 | # Description: Copies text from clipboard to pastebin.com. Replaces clipboard with pastebin url. Dragging text is possible as well. 6 | # Handles: NSStringPboardType 7 | # Events: Clicked, Dragged 8 | # Creator: CasperT 9 | # URL: portableinfo.com 10 | # IconURL: http://portableinfo.com/pastebin.png 11 | 12 | def dragged 13 | $dz.begin("Sending text to pastebin...") 14 | $dz.determinate(false) 15 | 16 | data = $items[0] 17 | format = getFormat() 18 | $dz.finish("Pastebin URL Copied!") 19 | $dz.url(getPastebinUrl(data, format)) 20 | end 21 | 22 | def clicked 23 | $dz.begin("Sending text to pastebin...") 24 | $dz.determinate(false) 25 | 26 | 27 | data = readClipboard() 28 | format = getFormat() 29 | $dz.finish("Pastebin URL Copied!") 30 | $dz.url(getPastebinUrl(data, format)) 31 | end 32 | 33 | def readClipboard 34 | IO.popen('pbpaste') {|clipboard| clipboard.read} 35 | end 36 | 37 | def getPastebinUrl(data, format) 38 | require 'net/http' 39 | Net::HTTP.post_form(URI.parse("http://pastebin.com/pastebin.php"), 40 | {"parent_pid" => "", 41 | "format" => format, 42 | "code2" => data, 43 | "poster" => "", 44 | "paste" => "Send", 45 | "expiry" => "m", 46 | "email" => ""}).header['Location'] 47 | end 48 | 49 | def getFormat 50 | result = `./CocoaDialog dropdown --text "Which Format?" --items "None" "bash" "c" "cpp" "html4strict" "java" "javascript" "lua" "perl" "php" "python" "ruby" "text" "abap" "actionscript" "ada" "apache" "applescript" "asm" "asp" "autoit" "bash" "blitzbasic" "bnf" "c" "c_mac" "caddcl" "cadlisp" "cpp" "csharp" "cfm" "css" "d" "delphi" "diff" "dos" "eiffel" "erlang" "fortran" "freebasic" "genero" "gml" "groovy" "haskell" "html4strict" "idl" "ini" "inno" "java" "javascript" "latex" "lisp" "lua" "lsl2" "matlab" "m68k" "mpasm" "mirc" "mysql" "nsis" "objc" "ocaml" "oobas" "oracle8" "pascal" "perl" "php" "plswl" "python" "qbasic" "rails" "robots" "ruby" "scheme" "smalltalk" "smarty" "sql" "tcl" "unreal" "vb" "vbnet" "visualfoxpro" "xml" "z80" --button1 "Ok" ‑‑no‑cancel --float --string-output` 51 | return result.split("\n")[1] 52 | end -------------------------------------------------------------------------------- /Tumblr-net-http.dropzone: -------------------------------------------------------------------------------- 1 | #!/usr/bin/ruby 2 | 3 | # Dropzone Destination Info 4 | # Name: Tumblr 5 | # Description: Share stuff on Tumblr. Holding down option allows you to enter a post title. 6 | # Handles: NSFilenamesPboardType 7 | # Events: Clicked, Dragged 8 | # KeyModifiers: Option 9 | # Creator: Tinu Cleatus 10 | # URL: http://blog.tinucleatus.com 11 | # IconURL: http://dl.dropbox.com/u/177542/tumblr_logo.png 12 | # OptionsNIB: Login 13 | # LoginTitle: Tumblr Login Details 14 | 15 | # TODO: Get the users tumblr url so that the url on pasteboard works 16 | 17 | module Tumblr 18 | class Api 19 | require 'net/http' 20 | require 'uri' 21 | 22 | attr_accessor :form_data, :result_body 23 | 24 | def initialize(type, &block) 25 | self.form_data = Hash.new 26 | 27 | self.add_form_data('email', ENV['USERNAME']) 28 | self.add_form_data('password', ENV['PASSWORD']) 29 | self.add_form_data('type', type) 30 | 31 | block.call(self) 32 | self.result_body = self.run_commands.body 33 | end 34 | 35 | def add_form_data(key, value) 36 | self.form_data[key] = value 37 | end 38 | 39 | def run_commands 40 | url = URI.parse("http://www.tumblr.com/api/write") 41 | req = Net::HTTP::Post.new(url.path) 42 | req.set_form_data(self.form_data) 43 | h = Net::HTTP.new(url.host, url.port) 44 | return h.start {|http| http.request(req) } 45 | end 46 | end 47 | end 48 | 49 | 50 | def dragged 51 | $dz.determinate(false) 52 | file_path = $items[0] 53 | filename = File.basename(file_path) 54 | title_text = "" 55 | 56 | if ENV['KEY_MODIFIERS'] == "Option" 57 | output = `./CocoaDialog standard-inputbox --title "Enter Title" --e --informative-text "Enter the title for your post:"` 58 | button, title_text = output.split("\n") 59 | 60 | if button == "2" or title_text == nil 61 | return_error("Cancelled") 62 | end 63 | end 64 | 65 | $dz.begin("Uploading #{filename}...") 66 | 67 | begin 68 | url = Tumblr::Api.new('photo') do |config| 69 | config.add_form_data('caption', title_text) 70 | config.add_form_data('data', File.open(file_path).read) 71 | end 72 | $dz.finish("URL is now on clipboard.") 73 | $dz.url(url.result_body) 74 | rescue Exception => e 75 | return_error("Error uploading!") 76 | end 77 | 78 | end 79 | 80 | def return_error(message) 81 | $dz.finish(message) 82 | $dz.url(false) 83 | return 84 | end -------------------------------------------------------------------------------- /Tumblr.dropzone: -------------------------------------------------------------------------------- 1 | #!/usr/bin/ruby 2 | 3 | # Dropzone Destination Info 4 | # Name: Tumblr 5 | # Description: Post photos to Tumblr. Holding down option allows you to enter a post title. 6 | # Handles: NSFilenamesPboardType 7 | # Events: Clicked, Dragged 8 | # KeyModifiers: Option 9 | # Creator: Tinu Cleatus 10 | # URL: http://blog.tinucleatus.com 11 | # IconURL: http://dl.dropbox.com/u/177542/tumblr_logo.png 12 | # OptionsNIB: Login 13 | # LoginTitle: Tumblr Login Details 14 | 15 | # TODO: Get the users tumblr url so that the url on pasteboard works 16 | 17 | def dragged 18 | $dz.determinate(true) 19 | 20 | file_path = $items[0] 21 | filename = File.basename(file_path) 22 | title_text = "" 23 | 24 | if ENV['KEY_MODIFIERS'] == "Option" 25 | output = `./CocoaDialog standard-inputbox --title "Enter Title" --e --informative-text "Enter the title for your post:"` 26 | button, title_text = output.split("\n") 27 | 28 | if button == "2" or title_text == nil 29 | $dz.finish("Cancelled") 30 | $dz.url(false) 31 | return 32 | end 33 | end 34 | 35 | $dz.begin("Uploading #{filename}...") 36 | 37 | last_output = 0 38 | response = "" 39 | 40 | title_text.gsub!('"', '\"') 41 | title_text.gsub!('$', '\$') 42 | file_path = file_path.gsub('"', '\"') 43 | 44 | IO.popen("/usr/bin/curl -# -F 'email=#{ENV['USERNAME']}' -F 'password=#{ENV['PASSWORD']}' -F 'type=photo' -F 'data=@#{file_path}' -F \"caption=#{title_text}\" http://www.tumblr.com/api/write 2>&1 | tr -u \"\r\" \"\n\"") do |f| 45 | 46 | while line = f.gets do 47 | if line =~ /%/ 48 | line_split = line.split(" ") 49 | file_percent_raw = line_split[1] 50 | if file_percent_raw != nil 51 | file_percent = file_percent_raw.to_i 52 | if last_output != file_percent 53 | $dz.percent(file_percent) 54 | $dz.determinate(false) if file_percent == 100 55 | end 56 | last_output = file_percent 57 | end 58 | else 59 | response += line 60 | end 61 | end 62 | 63 | end 64 | 65 | begin 66 | if response =~ /\b\d+\b/ 67 | $dz.finish("URL is now on clipboard!") 68 | $dz.url(response) 69 | else 70 | $dz.error("Tumblr Upload Failed", response) 71 | end 72 | rescue 73 | $dz.finish("Error uploading!") 74 | $dz.url(false) 75 | end 76 | end 77 | 78 | def clicked 79 | system("open http://tumblr.com") 80 | end -------------------------------------------------------------------------------- /ImageShack.dropzone: -------------------------------------------------------------------------------- 1 | #!/usr/bin/ruby 2 | 3 | # Dropzone Destination Info 4 | # Name: ImageShack 5 | # Description: Uploads an image to ImageShack. Holding down option causes the resulting URL to be minified using the Is.Gd service. 6 | # Handles: NSFilenamesPboardType 7 | # Events: Clicked, Dragged 8 | # KeyModifiers: Option 9 | # Creator: Paul William 10 | # URL: http://entropytheblog.com 11 | # IconURL: http://aptonic.com/destinations/icons/imageshack.png 12 | 13 | require "rexml/document" 14 | 15 | FF_USERAGENT = "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv:1.9.0.6) Gecko/2009011913 Firefox/3.0.6" 16 | 17 | def dragged 18 | $dz.determinate(true) 19 | 20 | file_path = $items[0] 21 | filename = File.basename(file_path) 22 | 23 | allowed_exts = ["jpg", "jpeg", "gif", "tif", "tiff", "png", "bmp"] 24 | ext = File.extname(file_path).downcase[1..-1] 25 | 26 | if not allowed_exts.include?(ext) 27 | $dz.finish("Not an Image") 28 | $dz.url(false) 29 | Process.exit 30 | end 31 | 32 | $dz.begin("Uploading #{filename}...") 33 | 34 | last_output = 0 35 | is_receiving_xml = false 36 | xml_output = "" 37 | 38 | file_path = file_path.gsub('"', '\"') 39 | IO.popen("/usr/bin/curl -# -A '#{FF_USERAGENT}' -F 'xml=yes' -F \"fileupload=@#{file_path}\" http://www.imageshack.us/index.php 2>&1 | tr -u \"\r\" \"\n\"") do |f| 40 | while line = f.gets do 41 | if line =~ /%/ and not is_receiving_xml 42 | line_split = line.split(" ") 43 | file_percent_raw = line_split[1] 44 | if file_percent_raw != nil 45 | file_percent = file_percent_raw.to_i 46 | if last_output != file_percent 47 | $dz.percent(file_percent) 48 | $dz.determinate(false) if file_percent == 100 49 | end 50 | last_output = file_percent 51 | end 52 | else 53 | if line =~ /xml/ or is_receiving_xml 54 | is_receiving_xml = true 55 | xml_output += line 56 | else 57 | handle_errors(line) 58 | end 59 | end 60 | end 61 | end 62 | 63 | begin 64 | url = "" 65 | doc = REXML::Document.new(xml_output) 66 | doc.elements.each("links/image_link") {|e| url = e.text} 67 | raise "Error getting URL" if url == nil or url == "" 68 | url = IsGd.minify(url) if ENV['KEY_MODIFIERS'] == "Option" 69 | $dz.finish("URL is now on clipboard") 70 | $dz.url(url) 71 | rescue 72 | $dz.finish("Upload Failed") 73 | $dz.url(false) 74 | end 75 | 76 | end 77 | 78 | def handle_errors(line) 79 | if line[0..4] == "curl:" 80 | if line[6..-1] =~ /Couldn't resolve/ 81 | $dz.error("ImageShack Upload Error", "Please check your network connection.") 82 | else 83 | $dz.error("ImageShack Upload Error", line[6..-1]) 84 | end 85 | end 86 | end 87 | 88 | def clicked 89 | system("open http://imageshack.us/") 90 | end -------------------------------------------------------------------------------- /Dropbox.dropzone: -------------------------------------------------------------------------------- 1 | #!/usr/bin/ruby 2 | 3 | # Dropzone Destination Info 4 | # Name: Dropbox 5 | # Description: Share files via Dropbox by copying them to your Dropbox Public folder and putting the URL on the clipboard. 6 | # Handles: NSFilenamesPboardType 7 | # Creator: Philipp Fehre 8 | # URL: http://sideshowcoder.com 9 | # IconURL: http://aptonic.com/destinations/icons/dropbox.png 10 | # Events: Dragged, Clicked 11 | # OptionsNIB: DropboxLogin 12 | 13 | require 'base64' 14 | require 'set' 15 | 16 | # Global variables for Dropbox settings 17 | @dropboxPubDir = "" 18 | @dropboxPublicBaseURL = "http://dl.getdropbox.com/u/" 19 | 20 | def dropbox? 21 | # Get Dropbox Public directory from the Dropbox database file, if this file is not present 22 | # Dropbox is most likely not installed 23 | begin 24 | File.foreach(ENV['HOME'] + "/.dropbox/host.db") do |line| 25 | @dropboxPubDir = Base64.decode64(line) + "/Public/" 26 | end 27 | return true 28 | rescue Errno::ENOENT 29 | # Dropbox is not installed on this machine 30 | return false 31 | end 32 | end 33 | 34 | 35 | def dragged 36 | $dz.determinate(true) 37 | # Check if Dropbox is installed and set Public path 38 | if not dropbox? 39 | $dz.finish("Dropbox is not installed") 40 | $dz.url(false) 41 | return 42 | end 43 | 44 | # Handle Drag 45 | if $items.length > 1 46 | # More than 1 item dragged 47 | # Create zip of all items and name it after the first item 48 | dir_name = /\A(\w*)/.match($items[0].split(File::SEPARATOR).last) 49 | zipfile = ZipFiles.zip($items, "#{dir_name}.zip") 50 | path = zipfile 51 | elsif File.directory?($items[0]) 52 | # 1 Folder was dragged 53 | # Create a Zip from the folder and name it after the folder 54 | dir_name = $items[0].split(File::SEPARATOR).last 55 | zipfile = ZipFiles.zip($items[0], "#{dir_name}.zip") 56 | path = zipfile 57 | else 58 | # Only 1 item dragged 59 | # Handle only the file by itself 60 | path = $items[0] 61 | end 62 | 63 | 64 | # Need to strip quotes which are passed when using a Zipfile 65 | # This might be a ruby bug but the regexp should take care of that even if it is fixed 66 | # some time in the futur 67 | path.gsub!(/\A(['"])(.*)\1\z/, '\2') 68 | 69 | # Copy file to Dropbox Public dir and place create URL on Clipboard 70 | $dz.begin("Copying #{File.basename(path)} ...") 71 | Rsync.do_copy(path, @dropboxPubDir, false) 72 | $dz.finish("URL is now on clipboard") 73 | $dz.url("#{@dropboxPublicBaseURL}#{ ENV['USERNAME']}/#{File.basename(path)}") 74 | end 75 | 76 | def clicked 77 | # Check for Dropbox and set Public Directory path 78 | if not dropbox? 79 | $dz.determinate(false) 80 | $dz.finish("Dropbox is not installed") 81 | $dz.url(false) 82 | else 83 | # Open Finder at Public Directory using Applescript 84 | `osascript -e 'tell application "Finder"' -e 'activate' -e 'open folder\ 85 | POSIX file "#{@dropboxPubDir}"' -e 'end tell'` 86 | end 87 | end 88 | 89 | -------------------------------------------------------------------------------- /Media Filer.dropzone: -------------------------------------------------------------------------------- 1 | #!/usr/bin/ruby 2 | 3 | # Dropzone Destination Info 4 | # Name: Media Filer 5 | # Description: Moves media files to the selected folder and categorizes by type. 6 | # Handles: NSFilenamesPboardType 7 | # Events: Dragged 8 | # Creator: Brett Terpstra 9 | # URL: http://brettterpstra.com 10 | # IconURL: http://brettterpstra.com/destinations/icons/MediaFiler.png 11 | # OptionsNIB: ChooseFolder 12 | 13 | require 'logger' 14 | 15 | def time_stamp(oldname) 16 | return File.basename(oldname) if File.basename(oldname) =~ /^\d{10}/ 17 | curdate = `date +'%m%d%y%H%M'` 18 | newname = curdate.to_s.strip + '_' + File.basename(oldname).gsub(/[^A-Za-z0-9\.]/,'-') 19 | return newname 20 | end 21 | 22 | def dragged 23 | log = Logger.new(File.expand_path('~/Library/Application Support/Dropzone/DropzoneMediaFiler.log'),10,1024000) 24 | $dz.determinate(true) 25 | $dz.begin("Filing #{$items.length} item(s) in #{ENV["EXTRA_PATH"]}") 26 | 27 | input = $items 28 | percent = 0 29 | inc = 100 / input.length 30 | input.each {|file_path| 31 | $dz.begin("Filing #{File.basename(file_path)}") 32 | $dz.percent(percent) 33 | itemtype = %x{mdls -name 'kMDItemContentTypeTree' -raw "#{file_path}"}.gsub(/["()\n]|public\./,'').split(',').map {|item| item.strip! }.delete_if {|item| item.nil?} 34 | file_kind = 'other' 35 | file_kind = 'image' if itemtype.include?("image") 36 | file_kind = 'movie' if itemtype.include?("movie") 37 | file_kind = 'music' if itemtype.include?("audio") 38 | $dz.begin("Moving #{file_kind} #{file_path} to #{ENV['EXTRA_PATH']}") 39 | if file_kind == 'movie' 40 | newname = time_stamp(file_path) 41 | %x{mkdir -p "#{ENV['EXTRA_PATH']}/Video"} unless File.directory?("#{ENV['EXTRA_PATH']}/Video") 42 | %x{mv "#{file_path}" "#{ENV['EXTRA_PATH']}/Video/#{newname}"} 43 | log.info "#{File.basename(file_path)} => #{ENV['EXTRA_PATH']}/Video/#{newname}" 44 | elsif file_kind == 'image' 45 | newname = time_stamp(file_path) 46 | %x{mkdir -p "#{ENV['EXTRA_PATH']}/Image"} unless File.directory?("#{ENV['EXTRA_PATH']}/Image") 47 | %x{mv "#{file_path}" "#{ENV['EXTRA_PATH']}/Image/#{newname}"} 48 | log.info "#{File.basename(file_path)} => #{ENV['EXTRA_PATH']}/Image/#{newname}" 49 | elsif file_kind == 'music' 50 | %x{mkdir -p "#{ENV['EXTRA_PATH']}/Audio"} unless File.directory?("#{ENV['EXTRA_PATH']}/Audio") 51 | %x{mv "#{file_path}" "#{ENV['EXTRA_PATH']}/Audio"} 52 | log.info "#{File.basename(file_path)} => #{ENV['EXTRA_PATH']}/Audio" 53 | elsif file_kind == 'other' 54 | %x{mkdir -p "#{ENV['EXTRA_PATH']}/Other"} unless File.directory?("#{ENV['EXTRA_PATH']}/Other") 55 | %x{mv "#{file_path}" "#{ENV['EXTRA_PATH']}/Other"} 56 | log.info "#{File.basename(file_path)} => #{ENV['EXTRA_PATH']}/Other" 57 | end 58 | percent = percent + inc 59 | } 60 | log.close 61 | $dz.finish("Finished filing #{input.length} files") 62 | $dz.url(false) 63 | end 64 | 65 | def clicked 66 | 67 | end 68 | -------------------------------------------------------------------------------- /WAZBlobs.dropzone: -------------------------------------------------------------------------------- 1 | #!/usr/bin/ruby 2 | 3 | # Dropzone Destination Info 4 | # Name: WAZ Blobs 5 | # Description: Upload your Blobs to Windows Azure Blobs service dragging and dropping your files to Dropzone. 6 | # Handles: NSFilenamesPboardType 7 | # Creator: Juan Pablo Garcia Dalolla 8 | # URL: http://blogs.southworks.net/jpgarcia 9 | # IconURL: http://waz-explorer.heroku.com/images/azure-logo.png 10 | # Events: Dragged, Clicked 11 | # OptionsNIB: Login 12 | # LoginTitle: Windows Azure Login Details (account_name, access_key) 13 | 14 | require 'rubygems' 15 | 16 | WAZ_EXPLORER_URL = "http://waz-explorer.heroku.com/containers/dropzone/blobs" 17 | 18 | def meet_gem_deps? 19 | dep = Gem::Dependency.new('waz-storage', Gem::Requirement.default) 20 | !Gem.source_index.search(dep).empty? 21 | end 22 | 23 | if !meet_gem_deps? 24 | $dz.error("waz-storage gem required!", "Install the required gem from a terminal:\r\n'sudo gem install waz-storage --source http://gemcutter.org'") 25 | $dz.url(false) 26 | else 27 | require 'waz-blobs' 28 | end 29 | 30 | def dragged 31 | begin 32 | $dz.determinate(true) 33 | 34 | WAZ::Storage::Base.establish_connection!( :account_name => ENV['USERNAME'], 35 | :access_key => ENV['PASSWORD']) 36 | 37 | if !WAZ::Storage::Base.connected? 38 | $dz.error("Connection Failed!", "Check your Windows Azure account_name and access_key on Dropzone settings section.") 39 | $dz.url(false) 40 | return 41 | end 42 | 43 | container = WAZ::Blobs::Container.find('dropzone') 44 | container = WAZ::Blobs::Container.create('dropzone') if container.nil? 45 | container.public_access = true 46 | 47 | $items.each_with_index{ |item,i| 48 | $dz.begin("Storing item #{i+1}/#{$items.length} on Windows Azure...") 49 | mimetype = IO.popen("file -Ib '#{item}'").readline.gsub(/\n/,"") 50 | mimetype = 'plain/text' if mimetype.empty? 51 | contents = "" 52 | File.open(item, 'r') { |file| 53 | while line = file.gets 54 | contents << line 55 | end 56 | } 57 | 58 | $dz.percent(((i+1).to_f/$items.length)*100) 59 | sleep(1) 60 | container.store(CGI.escape(File.basename(item)), contents, mimetype) 61 | } 62 | 63 | $dz.finish("#{$items.length} #{$items.length == 1 ? "Blob" : "Blobs"} uploaded.") 64 | $dz.url(WAZ_EXPLORER_URL) 65 | rescue RestClient::RequestFailed 66 | $dz.error("Upload error!", "Check your Windows Azure account_name and access_key on Dropzone settings section.") if $!.message.match(/'HTTP status code 403'/) 67 | $dz.url(false) 68 | rescue SocketError 69 | $dz.error("Upload error!", "Check your network connection.") 70 | $dz.url(false) 71 | rescue 72 | $dz.error("WAZ Blobs error!", "View the debug console for for info 'Command-Shift-D'. [#{$!}]") 73 | $dz.url(false) 74 | end 75 | end 76 | 77 | def clicked 78 | system("open #{WAZ_EXPLORER_URL}") 79 | end 80 | -------------------------------------------------------------------------------- /GitHub Gist.dropzone: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # Dropzone Destination Info 4 | # Name: GitHub Gist 5 | # Description: Drag text to share it instantly via GitHub Gist. Hold down option to make a public Gist. Click to copy and goto the last URL. 6 | # Handles: NSStringPboardType 7 | # Events: Clicked, Dragged 8 | # KeyModifiers: Option 9 | # Creator: Edgar Suarez 10 | # URL: http://e.dgar.org 11 | # IconURL: http://aptonic.com/destinations/icons/gist.png 12 | # OptionsNIB: Login 13 | # LoginTitle: Your GitHub username and API token 14 | 15 | require 'open-uri' 16 | require 'net/http' 17 | require 'yaml' 18 | 19 | class Gist 20 | @@gist_url = 'http://gist.github.com/%s' 21 | @@last_url_filename = File.expand_path("~/.dz_last_gist") 22 | 23 | class << self 24 | 25 | def post(content, private_gist) 26 | url = URI.parse('http://gist.github.com/api/v1/yaml/new') 27 | request = Net::HTTP.post_form(url, build_params(prompt_filename, content, private_gist)) 28 | if request.code.to_i == 200 29 | response = YAML.load(request.body) 30 | persist(@@gist_url % response['gists'][0][:repo]) 31 | else 32 | raise "Error: #{request.code}" 33 | end 34 | end 35 | 36 | def get_last_url 37 | if File.readable?(@@last_url_filename) 38 | File.open(@@last_url_filename).read.chomp 39 | else 40 | $dz.finish("No gists yet") 41 | $dz.url(false) 42 | Process.exit! 43 | end 44 | end 45 | 46 | private 47 | 48 | def prompt_filename 49 | output = `./CocoaDialog standard-inputbox --title "Gist name" --e --informative-text "Enter gist name:"` 50 | button, filename = output.split("\n") 51 | 52 | if button == "2" 53 | $dz.finish("Cancelled") 54 | $dz.url(false) 55 | Process.exit! 56 | end 57 | 58 | if filename == nil 59 | $dz.finish("Empty name") 60 | $dz.url(false) 61 | Process.exit! 62 | end 63 | 64 | return filename 65 | end 66 | 67 | def persist(url) 68 | File.open(@@last_url_filename, 'w') do |f| 69 | f.puts url 70 | end 71 | url 72 | end 73 | 74 | def build_params(filename, content, private_gist) 75 | { 76 | "files[#{filename}]" => content, 77 | :login => ENV['USERNAME'], 78 | :token => ENV['PASSWORD'], 79 | }.merge(private_gist ? { 'private' => 'true' } : {}) 80 | end 81 | 82 | end 83 | end 84 | 85 | # DZ events 86 | 87 | def dragged 88 | $dz.determinate(false) 89 | $dz.begin("Creating gist...") 90 | begin 91 | url = Gist.post($items[0], ENV['KEY_MODIFIERS'] == "") 92 | $dz.finish("URL is now on clipboard") 93 | $dz.url(url) 94 | rescue Exception => e 95 | $dz.finish("Error uploading gist!") 96 | $dz.url(false) 97 | exit 98 | end 99 | 100 | end 101 | 102 | def clicked 103 | url = Gist.get_last_url 104 | $dz.finish("URL is now on clipboard") 105 | $dz.url(url) 106 | system("open #{url}") 107 | end 108 | -------------------------------------------------------------------------------- /Posterous.dropzone: -------------------------------------------------------------------------------- 1 | #!/usr/bin/ruby 2 | 3 | # Dropzone Destination Info 4 | # Name: Posterous 5 | # Description: Share stuff on Posterous. Holding down option allows you to enter a post title. 6 | # Handles: NSFilenamesPboardType 7 | # Events: Clicked, Dragged 8 | # KeyModifiers: Option 9 | # Creator: Aptonic Software 10 | # URL: http://aptonic.com 11 | # IconURL: http://aptonic.com/destinations/icons/posterous.png 12 | # OptionsNIB: Login 13 | # LoginTitle: Posterous Login Details 14 | 15 | require "rexml/document" 16 | 17 | def dragged 18 | $dz.determinate(true) 19 | 20 | file_path = $items[0] 21 | filename = File.basename(file_path) 22 | title_text = "" 23 | 24 | if ENV['KEY_MODIFIERS'] == "Option" 25 | output = `./CocoaDialog standard-inputbox --title "Enter Title" --e --informative-text "Enter the title for your post:"` 26 | button, title_text = output.split("\n") 27 | 28 | if button == "2" or title_text == nil 29 | $dz.finish("Cancelled") 30 | $dz.url(false) 31 | return 32 | end 33 | end 34 | 35 | $dz.begin("Uploading #{filename}...") 36 | 37 | last_output = 0 38 | is_receiving_xml = false 39 | xml_output = "" 40 | 41 | title_text.gsub!('"', '\"') 42 | title_text.gsub!('$', '\$') 43 | file_path = file_path.gsub('"', '\"') 44 | 45 | IO.popen("/usr/bin/curl -# -u #{ENV['USERNAME']}:#{ENV['PASSWORD']} -F \"title=#{title_text}\" -F 'media=@#{file_path}' http://posterous.com/api/newpost 2>&1 | tr -u \"\r\" \"\n\"") do |f| 46 | while line = f.gets do 47 | if line =~ /%/ and not is_receiving_xml 48 | line_split = line.split(" ") 49 | file_percent_raw = line_split[1] 50 | if file_percent_raw != nil 51 | file_percent = file_percent_raw.to_i 52 | if last_output != file_percent 53 | $dz.percent(file_percent) 54 | $dz.determinate(false) if file_percent == 100 55 | end 56 | last_output = file_percent 57 | end 58 | else 59 | if line =~ /xml/ or is_receiving_xml 60 | is_receiving_xml = true 61 | xml_output += line 62 | else 63 | handle_errors(line) 64 | end 65 | end 66 | end 67 | end 68 | 69 | begin 70 | url = "" 71 | doc = REXML::Document.new(xml_output) 72 | root = doc.root 73 | status = root.attributes["stat"] 74 | 75 | if status == "ok" 76 | doc.elements.each("rsp/post/url") {|e| url = e.text} 77 | else 78 | $dz.error("Posterous Upload Error", root.elements[1].attributes["msg"]) 79 | end 80 | 81 | $dz.finish("URL is now on clipboard") 82 | $dz.url(url) 83 | rescue 84 | $dz.finish("Upload Failed") 85 | $dz.url(false) 86 | end 87 | end 88 | 89 | def handle_errors(line) 90 | if line[0..4] == "curl:" 91 | if line[6..-1] =~ /Couldn't resolve/ 92 | $dz.error("Posterous Upload Error", "Please check your network connection.") 93 | else 94 | $dz.error("Posterous Upload Error", line[6..-1]) 95 | end 96 | end 97 | end 98 | 99 | def clicked 100 | system("open http://posterous.com") 101 | end -------------------------------------------------------------------------------- /TwitDoc.dropzone: -------------------------------------------------------------------------------- 1 | #!/usr/bin/ruby 2 | 3 | # Dropzone Destination Info 4 | # Name: TwitDoc 5 | # Description: Share documents via the TwitDoc service. Holding down option allows you to enter a tweet to send now. 6 | # Handles: NSFilenamesPboardType 7 | # Events: Clicked, Dragged 8 | # KeyModifiers: Option 9 | # Creator: Paul William 10 | # URL: http://entropytheblog.com 11 | # IconURL: http://aptonic.com/destinations/icons/twitdoc.png 12 | # OptionsNIB: Login 13 | # LoginTitle: Twitter Login Details 14 | 15 | require "rexml/document" 16 | 17 | def dragged 18 | $dz.determinate(true) 19 | 20 | file_path = $items[0] 21 | filename = File.basename(file_path) 22 | should_tweet = false 23 | tweet_text = "" 24 | 25 | if ENV['KEY_MODIFIERS'] == "Option" 26 | should_tweet = true 27 | output = `./CocoaDialog standard-inputbox --title "Enter Tweet" --e --informative-text "Enter the text for your tweet:"` 28 | button, tweet_text = output.split("\n") 29 | 30 | if button == "2" or tweet_text == nil 31 | $dz.finish("Cancelled") 32 | $dz.url(false) 33 | return 34 | end 35 | end 36 | 37 | $dz.begin("Uploading #{filename}...") 38 | 39 | last_output = 0 40 | is_receiving_xml = false 41 | xml_output = "" 42 | 43 | api_action = (should_tweet ? "uploadAndTweet" : "upload") 44 | tweet_text.gsub!('"', '\"') 45 | tweet_text.gsub!('$', '\$') 46 | file_path = file_path.gsub('"', '\"') 47 | 48 | IO.popen("/usr/bin/curl -# -F 'username=#{ENV['USERNAME']}' -F 'password=#{ENV['PASSWORD']}' -F \"file_1=@#{file_path}\" -F \"message=#{tweet_text}\" http://twitdoc.com/api/#{api_action} 2>&1 | tr -u \"\r\" \"\n\"") do |f| 49 | while line = f.gets do 50 | if line =~ /%/ and not is_receiving_xml 51 | line_split = line.split(" ") 52 | file_percent_raw = line_split[1] 53 | if file_percent_raw != nil 54 | file_percent = file_percent_raw.to_i 55 | if last_output != file_percent 56 | $dz.percent(file_percent) 57 | $dz.determinate(false) if file_percent == 100 58 | end 59 | last_output = file_percent 60 | end 61 | else 62 | if line =~ /xml/ or is_receiving_xml 63 | is_receiving_xml = true 64 | xml_output += line 65 | else 66 | handle_errors(line) 67 | end 68 | end 69 | end 70 | end 71 | 72 | begin 73 | url = "" 74 | doc = REXML::Document.new(xml_output) 75 | root = doc.root 76 | status = root.attributes["status"] 77 | 78 | if status == "ok" 79 | doc.elements.each("rsp/doc/short_url") {|e| url = e.text} 80 | else 81 | $dz.error("TwitDoc Upload Error", root.elements[1].attributes["msg"]) 82 | end 83 | 84 | $dz.finish("URL is now on clipboard") 85 | $dz.url(url) 86 | rescue 87 | $dz.finish("Upload Failed") 88 | $dz.url(false) 89 | end 90 | end 91 | 92 | def handle_errors(line) 93 | if line[0..4] == "curl:" 94 | if line[6..-1] =~ /Couldn't resolve/ 95 | $dz.error("TwitDoc Upload Error", "Please check your network connection.") 96 | else 97 | $dz.error("TwitDoc Upload Error", line[6..-1]) 98 | end 99 | end 100 | end 101 | 102 | 103 | def clicked 104 | system("open http://twitdoc.com/") 105 | end -------------------------------------------------------------------------------- /gdocs_uploader.dropzone: -------------------------------------------------------------------------------- 1 | #!/usr/bin/ruby 2 | 3 | # Dropzone Destination Info 4 | # Name: GDocs Uploader 5 | # Description: Uploads documents to Google Docs 6 | # Handles: NSFilenamesPboardType 7 | # Events: clicked, dragged 8 | # KeyModifiers: Command 9 | # Creator: Christof Dorner 10 | # URL: http://chdorner.com/pages/projects/dropzone-plugins#gdocs_uploader 11 | # IconURL: http://chdorner.com/files/gdocs_uploader_icon.png 12 | # OptionsNIB: Login 13 | # LoginTitle: Google Documents Login Details 14 | 15 | # Version: 0.2 16 | 17 | require 'rubygems' 18 | require 'gdata' 19 | require 'mime/types' 20 | require 'rexml/document' 21 | 22 | def dragged 23 | $dz.determinate(true) 24 | url = '' 25 | 26 | $dz.begin("Authenticate with Google") 27 | $dz.percent(10) 28 | client = GData::Client::DocList.new 29 | 30 | begin 31 | client.clientlogin(ENV['USERNAME'], ENV['PASSWORD']) 32 | rescue GData::Client::AuthorizationError 33 | $dz.error("Login Failure", "Oops, something went wrong while logging you in. Check the credentials") 34 | rescue GData::Client::CaptchaError 35 | $dz.error("Login Failure", "There was an error with loggin you in, try to login to Google Docs in your browser and then try again.") 36 | rescue SocketError 37 | $dz.error("No connection", "Cannot connect to the Google Docs service, are you connected to the internet?") 38 | rescue Exception 39 | $dz.error("Unkown error", "An unkown error happened.") 40 | end 41 | sleep(1) 42 | 43 | updoc = "Uploading document" 44 | updoc += "s" unless $items.size == 1 45 | $dz.begin(updoc) 46 | $dz.percent(60) 47 | errors = [] 48 | $items.each do |file_path| 49 | begin 50 | mime_type = MIME::Types.type_for file_path 51 | response = client.make_file_request(:post, 'http://docs.google.com/feeds/documents/private/full', file_path, mime_type) 52 | doc = REXML::Document.new(response.body) 53 | doc.elements.each("entry/link") do |element| 54 | if element.attributes['rel'] == 'alternate' 55 | url = element.attributes['href'] 56 | end 57 | end 58 | rescue GData::Client::UnknownError 59 | if $!.to_s.include?("request error 415") 60 | errors << "You tried to upload a document which is not supported by Google Docs service. (" + file_path + ")" 61 | else 62 | $dz.error("Unkown Error", "An unkown error happened.") 63 | end 64 | rescue NoMethodError 65 | if $!.to_s.include?("Malformed Content-Type") 66 | $dz.error("Unsupported Document Format", "Could not read the document format.") 67 | end 68 | rescue Exception 69 | $dz.error("Unkown error", "An unkown error happened.") 70 | end 71 | end 72 | errors.each do |error| 73 | $dz.error("Unsupported Document", error) 74 | end 75 | sleep (1) 76 | 77 | updoc = "Document" 78 | updoc += "s" unless $items.size == 1 79 | updoc += " uploaded" 80 | $dz.finish(updoc) 81 | if $items.size > 1 82 | $dz.url(gdocs_home_url) 83 | elsif url != '' 84 | $dz.url(url) 85 | else 86 | $dz.url(false) 87 | end 88 | end 89 | 90 | def clicked 91 | system("open " + gdocs_home_url) 92 | end 93 | 94 | def gdocs_home_url 95 | domain = ENV['USERNAME'].split('@')[1] 96 | if domain == 'gmail.com' 97 | url = 'https://docs.google.com/' 98 | else 99 | url = 'http://docs.google.com/a/' + domain 100 | end 101 | end -------------------------------------------------------------------------------- /LinkSmasher.dropzone: -------------------------------------------------------------------------------- 1 | #!/usr/bin/ruby 2 | 3 | # Dropzone Destination Info 4 | # Name: LinkSmasher 5 | # Description: Drop text/url, or click to scan clipboard. If multiple links are found, they'll be collected into one url with linkbun.ch, a single url will be shortened with bit.ly. 6 | # Handles: NSStringPboardType 7 | # Creator: Brett Terpstra, w/code by Sergej Müller 8 | # URL: http://brettterpstra.com 9 | # IconURL: http://abyss.designheresy.com/LinkSmash.png 10 | # OptionsNIB: Login 11 | # LoginTitle: bit.ly Login Details 12 | 13 | require 'open-uri' 14 | require 'net/http' 15 | require 'cgi' 16 | require 'rexml/document' 17 | 18 | def get_bitly_link(text) 19 | if text =~ /(http|https):\/\/[\w\-_]+(\.[\w\-_]+)+([\w\-\.,@?^=%&:\/~\+#]*[\w\-\@^=%&\/~\+#])?/m 20 | $dz.begin("Getting Bit.ly link") 21 | url = $1 22 | Net::HTTP.start('api.bit.ly') do |http| 23 | req = Net::HTTP::Get.new('/shorten?version=2.0.1&format=xml&history=1&longUrl=' + CGI::escape(url)) 24 | req.basic_auth ENV['USERNAME'], ENV['PASSWORD'] 25 | res = http.request(req) 26 | 27 | doc = REXML::Document.new(res.body) 28 | doc.elements.each("bitly/statusCode") do |a| 29 | if a.text == "ERROR" 30 | $dz.finish("Invalid user or password") 31 | $dz.url(false) 32 | else 33 | doc.elements.each("bitly/results/nodeKeyVal/shortUrl") do |b| 34 | if b.text.empty? 35 | $dz.finish("Empty URL returned") 36 | $dz.url(false) 37 | else 38 | $dz.finish("Copied short URL") 39 | $dz.url(b.text) 40 | end 41 | end 42 | end 43 | end 44 | end 45 | else 46 | $dz.finish("Invalid URL") 47 | $dz.url(false) 48 | end 49 | end 50 | 51 | def bunch_links(text) 52 | $dz.begin("Bunching links") 53 | links = text.scan(/((?:http|https):\/\/[\w\-_]+(?:\.[\w\-_]+)+(?:[\w\-\.,@?^=%&:\/~\+#]*[\w\-\@^=%&\/~\+#])?)/m) 54 | unless links.empty? 55 | linkbunch = CGI.escape(links.join("\n")) 56 | url = "http://www.linkbun.ch/linkbunch.php?links=#{linkbunch}&bunch=Bunch&mode=api" 57 | res = Net::HTTP.get_response(URI.parse(url)) 58 | if res.code.to_i == 200 59 | $dz.finish("#{links.length} links bunched") 60 | $dz.url(res.body) 61 | else 62 | $dz.finish("Linkbunch returned an error") 63 | $dz.url(false) 64 | end 65 | else 66 | $dz.finish("No links detected") 67 | $dz.url(false) 68 | end 69 | end 70 | 71 | 72 | def dragged 73 | $dz.determinate(false) 74 | $dz.begin("Checking links") 75 | count = $items[0].scan(/((?:http|https):\/\/[\w\-_]+(?:\.[\w\-_]+)+(?:[\w\-\.,@?^=%&:\/~\+#]*[\w\-\@^=%&\/~\+#])?)/m).length 76 | if count > 1 77 | bunch_links($items[0]) 78 | elsif count == 1 79 | get_bitly_link($items[0]) 80 | else 81 | $dz.finish("No links detected") 82 | $dz.url(false) 83 | end 84 | end 85 | 86 | def clicked 87 | $dz.determinate(false) 88 | $dz.begin("Scanning clipboard for links") 89 | `pbpaste|pbcopy` 90 | text = %x{__CF_USER_TEXT_ENCODING=$UID:0x8000100:0x8000100 pbpaste}.strip 91 | count = text.scan(/((?:http|https):\/\/[\w\-_]+(?:\.[\w\-_]+)+(?:[\w\-\.,@?^=%&:\/~\+#]*[\w\-\@^=%&\/~\+#])?)/m).length 92 | if count == 1 93 | get_bitly_link(text) 94 | elsif count > 1 95 | bunch_links(text) 96 | else 97 | $dz.finish("No links detected") 98 | $dz.url(false) 99 | end 100 | end 101 | -------------------------------------------------------------------------------- /TwitPic.dropzone: -------------------------------------------------------------------------------- 1 | #!/usr/bin/ruby 2 | 3 | # Dropzone Destination Info 4 | # Name: TwitPic 5 | # Description: Share photos via the TwitPic service. Holding down option allows you to enter a tweet to send now. 6 | # Handles: NSFilenamesPboardType 7 | # Events: Clicked, Dragged 8 | # KeyModifiers: Option 9 | # Creator: Paul William 10 | # URL: http://entropytheblog.com 11 | # IconURL: http://aptonic.com/destinations/icons/twitpic.png 12 | # OptionsNIB: Login 13 | # LoginTitle: Twitter Login Details 14 | 15 | require "rexml/document" 16 | 17 | def dragged 18 | $dz.determinate(true) 19 | 20 | file_path = $items[0] 21 | filename = File.basename(file_path) 22 | should_tweet = false 23 | tweet_text = "" 24 | 25 | allowed_exts = ["jpg", "jpeg", "gif", "tif", "tiff", "png", "bmp"] 26 | ext = File.extname(file_path).downcase[1..-1] 27 | 28 | if not allowed_exts.include?(ext) 29 | $dz.finish("Not an Image") 30 | $dz.url(false) 31 | Process.exit 32 | end 33 | 34 | if ENV['KEY_MODIFIERS'] == "Option" 35 | should_tweet = true 36 | output = `./CocoaDialog standard-inputbox --title "Enter Tweet" --e --informative-text "Enter the text for your tweet:"` 37 | button, tweet_text = output.split("\n") 38 | 39 | if button == "2" or tweet_text == nil 40 | $dz.finish("Cancelled") 41 | $dz.url(false) 42 | return 43 | end 44 | end 45 | 46 | $dz.begin("Uploading #{filename}...") 47 | 48 | last_output = 0 49 | is_receiving_xml = false 50 | xml_output = "" 51 | 52 | api_action = (should_tweet ? "uploadAndPost" : "upload") 53 | tweet_text.gsub!('"', '\"') 54 | tweet_text.gsub!('$', '\$') 55 | file_path = file_path.gsub('"', '\"') 56 | 57 | IO.popen("/usr/bin/curl -# -F 'username=#{ENV['USERNAME']}' -F 'password=#{ENV['PASSWORD']}' -F \"media=@#{file_path}\" -F \"message=#{tweet_text}\" http://twitpic.com/api/#{api_action} 2>&1 | tr -u \"\r\" \"\n\"") do |f| 58 | while line = f.gets do 59 | if line =~ /%/ and not is_receiving_xml 60 | line_split = line.split(" ") 61 | file_percent_raw = line_split[1] 62 | if file_percent_raw != nil 63 | file_percent = file_percent_raw.to_i 64 | if last_output != file_percent 65 | $dz.percent(file_percent) 66 | $dz.determinate(false) if file_percent == 100 67 | end 68 | last_output = file_percent 69 | end 70 | else 71 | if line =~ /xml/ or is_receiving_xml 72 | is_receiving_xml = true 73 | xml_output += line 74 | else 75 | handle_errors(line) 76 | end 77 | end 78 | end 79 | end 80 | 81 | begin 82 | url = "" 83 | doc = REXML::Document.new(xml_output) 84 | root = doc.root 85 | status = (should_tweet ? root.attributes["status"] : root.attributes["stat"]) 86 | 87 | if status == "ok" 88 | doc.elements.each("rsp/mediaurl") {|e| url = e.text} 89 | else 90 | $dz.error("TwitPic Upload Error", root.elements[1].attributes["msg"]) 91 | end 92 | 93 | $dz.finish("URL is now on clipboard") 94 | $dz.url(url) 95 | rescue 96 | $dz.finish("Upload Failed") 97 | $dz.url(false) 98 | end 99 | end 100 | 101 | def handle_errors(line) 102 | if line[0..4] == "curl:" 103 | if line[6..-1] =~ /Couldn't resolve/ 104 | $dz.error("TwitPic Upload Error", "Please check your network connection.") 105 | else 106 | $dz.error("TwitPic Upload Error", line[6..-1]) 107 | end 108 | end 109 | end 110 | 111 | def clicked 112 | system("open http://twitpic.com/") 113 | end -------------------------------------------------------------------------------- /Filer.dropzone: -------------------------------------------------------------------------------- 1 | #!/usr/bin/ruby 2 | 3 | # Dropzone Destination Info 4 | # Name: Filer 5 | # Description: Files based on OpenMeta tags 6 | # Handles: NSFilenamesPboardType 7 | # Creator: Brett Terpstra 8 | # URL: http://brettterpstra.com 9 | # IconURL: http://brettterpstra.com/destinations/icons/filer2.png 10 | 11 | # This script works with OpenMeta tagging (http://code.google.com/p/openmeta/) 12 | # 13 | # Target folders are tagged with "°target" and a unique tag name 14 | # e.g. "°target reference" 15 | # "°" is option-shift-8 16 | # 17 | # 1 level of subtargets are recognized, using "°subtarget" 18 | # e.g. "°subtarget snippet" 19 | # 20 | # The folder structure would look like this: 21 | # ~/Documents/Reference <- tagged "°target reference" 22 | # | 23 | # - Snippets <- tagged "°subtarget snippet" 24 | # 25 | # The file dropped on the Filer would be tagged "reference snippet" to have it placed 26 | # into ~/Documents/Reference/Snippets. If it were tagged with just "reference" it would 27 | # be moved into ~/Documents/Reference, and if it were just tagged "snippet" it would 28 | # throw an error. Subtargets are optional and only function if there's a target match. 29 | # 30 | # Filer searches the dropped files tags for one that matches an existing °target folder 31 | # It then checks for subtarget matches only within the target folder, therefore, subtargets 32 | # can be repeated in the system, as long as there's a unique target containing them. If no 33 | # subtarget is found, the main target is assumed to be the destination 34 | # 35 | # Dropped files/folders are moved using UNIX 'mv'. Dropped folders are not recursed. 36 | # 37 | # If the Growl command line utility is installed in '/usr/local/bin/growlnotify', Growl updates 38 | # will be sent. A logfile is created in '~/Library/Application Support/Dropzone/DropzoneFiler.log' 39 | # and shows what files were moved where. Files without tags or without tags that match a target 40 | # are noted as warnings in the log file (and sticky growl messages). 41 | require 'logger' 42 | 43 | def dragged 44 | log = Logger.new(File.expand_path('~/Library/Application Support/Dropzone/DropzoneFiler.log'),10,1024000) 45 | $dz.determinate(true) 46 | $dz.begin("Handling files") 47 | input = $items 48 | percent = filedcount = errorcount = 0 49 | inc = 100 / input.length 50 | input.each {|file_path| 51 | $dz.begin("Filing #{File.basename(file_path)}") 52 | $dz.percent(percent) 53 | # tags = %x{/usr/local/bin/openmeta -l -p "#{file_path}" | grep tags | awk '{print substr($0, index($0,$2))}'}.split(' ') 54 | tags = %x{mdls -name 'kOMUserTags' -raw "#{file_path}"|awk '/[^()]/ {print $NF}'|tr -d '\n'}.split(',') 55 | break if tags.empty? 56 | filed = false 57 | tags.each { |tag| 58 | target = %x{mdfind "((kOMUserTags == '#{tag}'cd) && (kOMUserTags == '°target') && (kMDItemContentTypeTree == 'public.folder'))"|awk 'NR>1{exit};1'}.strip 59 | unless target.empty? 60 | tags.each { |tag| 61 | subtarget = %x{mdfind -onlyin "#{target}" "((kOMUserTags == '#{tag}'cd) && (kOMUserTags == '°subtarget') && (kMDItemContentTypeTree == 'public.folder'))"|awk 'NR>1{exit};1'}.strip 62 | target = subtarget unless subtarget.empty? 63 | } 64 | %x{mv "#{file_path}" "#{target}/"} 65 | # Rsync.do_copy($dz,file_path.to_a,target,move) 66 | log.info "#{File.basename(file_path)} => #{target}" 67 | %x{/usr/local/bin/growlnotify -a "Dropzone.app" -m "Filed: #{File.basename(file_path)}\nto: #{target}" -t "Dropzone Filer"} if File.exists?("/usr/local/bin/growlnotify") 68 | filed = true 69 | break 70 | end 71 | } 72 | if filed 73 | filedcount += 1 74 | filed = false 75 | else 76 | errorcount += 1 77 | %x{/usr/local/bin/growlnotify -a "Dropzone.app" -s -m "Couldn't file #{File.basename(file_path)}" -t "Dropzone Filer"} if File.exists?("/usr/local/bin/growlnotify") 78 | log.warn "** Couldn't find a target for #{File.basename(file_path)}" 79 | log.warn "** Tagged: #{tags.join(', ')}" 80 | end 81 | percent = percent + inc 82 | } 83 | if filedcount > 0 84 | output = "Filed #{filedcount} items" 85 | else 86 | output = "No items filed" 87 | end 88 | output += ", #{errorcount} errors" if errorcount > 0 89 | log.info output 90 | log.close 91 | $dz.finish(output) 92 | $dz.url("0") 93 | end 94 | 95 | def clicked 96 | 97 | end -------------------------------------------------------------------------------- /UpShot.dropzone: -------------------------------------------------------------------------------- 1 | #!/usr/bin/ruby 2 | require 'rexml/document' 3 | require "base64" 4 | 5 | 6 | # Dropzone Destination Info 7 | # Name: Send to UpShot 8 | # Description: Allows pictures to be uploaded on upshot webservice. 9 | # Handles: NSFilenamesPboardType 10 | # Events: Clicked, Dragged 11 | # KeyModifiers: Command 12 | # Creator: Studio Melipone SARL 13 | # URL: http://upshotapp.com 14 | # IconURL: http://aptonic.com/destinations/icons/upshot.png 15 | # OptionsNIB: Login 16 | 17 | def dragged 18 | $dz.determinate(false) 19 | 20 | file_path = $items[0] 21 | filename = File.basename(file_path) 22 | auth_xml_output = "" 23 | 24 | allowed_exts = ["jpg", "jpeg", "png"] 25 | ext = File.extname(file_path).downcase[1..-1] 26 | 27 | if not allowed_exts.include?(ext) 28 | $dz.finish("Not a valid Image") 29 | $dz.url(false) 30 | Process.exit 31 | end 32 | 33 | $dz.begin("Login to your account") 34 | 35 | # Thank you to twitpic script cause we first did it with Net::HTTP code, and it was really not that efficient. 36 | IO.popen("/usr/bin/curl -# -H 'Content-Type:application/xml' -H 'Accept:application/xml' -X GET --basic -u #{ENV['USERNAME']}:#{ENV['PASSWORD']} http://www.upshotapp.com/users/get_id 2>&1 | tr -u \"\r\" \"\n\"") do |f| 37 | while line = f.gets do 38 | auth_xml_output += line 39 | end 40 | end 41 | 42 | doc = REXML::Document.new(auth_xml_output) 43 | error = [] 44 | infos = [] 45 | doc.elements.each('hash/id') do |ele| 46 | infos << ele.text 47 | end 48 | doc.elements.each('hash/state') do |ele| 49 | infos << ele.text 50 | end 51 | doc.elements.each('hash/error') do |ele| 52 | error << ele.text 53 | end 54 | 55 | if !error.empty? 56 | finish_text = "Error: #{error[0]}" 57 | url = false 58 | elsif !infos.empty? 59 | $items.each do |file_path| 60 | $dz.begin("Sending your file: #{file_path}...") 61 | 62 | title = File.basename(file_path) 63 | 64 | last_output = 0 65 | is_receiving_xml = false 66 | upload_xml_output = "" 67 | 68 | if ENV['KEY_MODIFIERS'] == "Command" 69 | output = `./CocoaDialog standard-inputbox --title "Enter Title" --e --informative-text "Enter the title for your the UpShot for your file : #{title}"` 70 | button, title = output.split("\n") 71 | 72 | if button == "2" or title == nil 73 | $dz.finish("Cancelled") 74 | $dz.url(false) 75 | return 76 | end 77 | 78 | # title.downcase.normalize(:kd).gsub(/[^a-z0-9\s]/im,'').gsub(/\s+/, '-').to_s 79 | end 80 | 81 | title.gsub!("'", "'") 82 | 83 | $dz.determinate(true) 84 | 85 | IO.popen("/usr/bin/curl -# -F 'upshot[file]=@#{file_path};type=image/#{ext}' -F 'upshot[title]=#{title}' -F '_method=put' -H 'Accept:application/xml' -u #{ENV['USERNAME']}:#{ENV['PASSWORD']} http://www.upshotapp.com/en/users/#{infos[0]}/upshots/dropzone 2>&1 | tr -u \"\r\" \"\n\"") do |f| 86 | while line = f.gets do 87 | if line =~ /%/ and not is_receiving_xml 88 | line_split = line.split(" ") 89 | file_percent_raw = line_split[1] 90 | if file_percent_raw != nil 91 | file_percent = file_percent_raw.to_i 92 | if last_output != file_percent 93 | $dz.percent(file_percent) 94 | $dz.determinate(false) if file_percent == 100 95 | end 96 | last_output = file_percent 97 | end 98 | else 99 | if line =~ /xml/ or is_receiving_xml 100 | is_receiving_xml = true 101 | upload_xml_output += line 102 | else 103 | handle_errors(line) 104 | end 105 | end 106 | end 107 | end 108 | 109 | upshot = REXML::Document.new(upload_xml_output) 110 | ids = [] 111 | upshot_errors = [] 112 | upshot.elements.each('upshot/id') do |ele| 113 | ids << ele.text 114 | end 115 | upshot.elements.each('errors/error') do |ele| 116 | upshot_errors << ele.text 117 | end 118 | end 119 | 120 | finish_text = "Dashboard URL is in Clipboard." 121 | url = "http://www.upshotapp.com/en/dashboard" 122 | else 123 | finish_text = "unknown problem. Contact us." 124 | url = false 125 | end 126 | 127 | $dz.finish(finish_text) 128 | $dz.url(url) 129 | Process.exit 130 | 131 | end 132 | 133 | def clicked 134 | system("open http://upshotapp.com/") 135 | end 136 | 137 | def handle_errors(line) 138 | if line[0..4] == "curl:" 139 | if line[6..-1] =~ /Couldn't resolve/ 140 | $dz.error("UpShot Upload Error", "Please check your network connection.") 141 | else 142 | $dz.error("UpShot Upload Error", line[6..-1]) 143 | end 144 | end 145 | end -------------------------------------------------------------------------------- /Download.dropzone: -------------------------------------------------------------------------------- 1 | #!/usr/bin/ruby 2 | 3 | # Dropzone Destination Info 4 | # Name: Download 5 | # Description: Download files 6 | # Handles: NSStringPboardType 7 | # Events: Dragged 8 | # Creator: Marc E. 9 | # URL: http://www.jardinpresente.com.ar/svn/utiles/trunk/dropzone/Download.dropzone 10 | # IconURL: http://aptonic.com/destinations/icons/network.png 11 | # OptionsNIB: ChooseFolder 12 | 13 | require 'net/http' 14 | 15 | # We put net/http/stats here instead of require 16 | # Source: http://blog.obloh.com/posts/show/5 17 | 18 | 19 | # Add methods to have stats and rules when downloading. 20 | class Net::HTTP 21 | # This methods has the same behavior than get_response. However the block takes 22 | # 2 arguments (response and a string of bytes). Bytes must not be retrieve 23 | # via response.bytes_read, because they have already been read to compute stats. 24 | # 25 | # require 'net/http' 26 | # require 'net/http/stats' 27 | # 28 | # content = '' 29 | # uri = URI.parse('http://ftp.ruby-lang.org/pub/ruby/1.8/ruby-1.8.6.tar.gz') 30 | # response = Net::HTTP.get_response_with_stats(uri) do |resp, bytes| 31 | # content << bytes 32 | # puts "#{resp.bytes_percent}% downloaded at #{(resp.bytes_rate / 1024).to_i} Ko/s, remaining #{resp.left_time.to_i} seconds" 33 | # end 34 | # This script will print something like: 35 | # 13% downloaded at 59 Ko/s, remaining 65 seconds 36 | # See Net::HTTP::Stats to know more about attributes. 37 | def self.get_response_with_stats(url, &block) 38 | response = Net::HTTP.get_response(url) do |resp| 39 | resp.extend(Net::HTTP::Stats) 40 | resp.initialize_stats 41 | resp.read_body do |bytes| 42 | resp.bytes_read += bytes.size 43 | block.call(resp, bytes) if block 44 | end 45 | end 46 | response 47 | end 48 | 49 | # This method works exactly as Net::HTTP.get_response_with_stats, but it takes 50 | # a 2nd Hash argument which describes rules. 51 | # Raises Net::HTTP::Stats::MaxSizeExceed, Net::HTTP::Stats::MaxSpentTimeExceed 52 | # or Net::HTTP::Stats::MaxLeftTimeExceed if a rule isn't respected. 53 | # 54 | # require 'net/http' 55 | # require 'net/http/stats' 56 | # 57 | # rules = { 58 | # # Download is interrupted if spent time is greater (in sec). 59 | # :max_time => 5 * 60, 60 | # # Download is interrupted if estimated time is greater (in sec). 61 | # :max_left_time => 5 * 60, 62 | # # Download is interrupted if body is greater (in byte). 63 | # :max_size => 50 * 1024 * 1024, 64 | # # Wait some time before checking max_time and max_left_time (in sec). 65 | # # something between 5 and 20 seconds should be good. 66 | # :min_time => 15 67 | # } 68 | # 69 | # content = '' 70 | # uri = URI.parse('http://ftp.ruby-lang.org/pub/ruby/1.8/ruby-1.8.6.tar.gz') 71 | # response = Net::HTTP.get_response_with_rules(uri, rules) do |resp, bytes| 72 | # content << bytes 73 | # end 74 | def self.get_response_with_rules(url, rules, &block) 75 | response = Net::HTTP.get_response_with_stats(url) do |resp, bytes| 76 | msg = '' 77 | if resp.bytes_count > rules[:max_size] 78 | raise Stats::MaxSizeExceed.new("Response size (#{resp.bytes_count}) exceed maximun content length allowed (#{rules[:max_size]}).", resp) 79 | elsif resp.spent_time > rules[:max_time] 80 | raise Stats::MaxSpentTimeExceed.new("Maximun time allowed (#{rules[:max_time]}) have been exceed", resp) 81 | elsif resp.spent_time > rules[:min_time] and resp.left_time > rules[:max_left_time] 82 | raise Stats::MaxLeftTimeExceed.new("Left time expected (#{resp.left_time.to_i}) exceed maximun left time allowed (#{rules[:max_left_time]})", resp) 83 | end 84 | block.call(resp, bytes) if block 85 | end 86 | response 87 | end 88 | 89 | module Stats 90 | # Body size 91 | attr_accessor :bytes_count 92 | 93 | # Bytes already downloaded 94 | attr_accessor :bytes_read 95 | 96 | # Rate (byte/sec) 97 | attr_accessor :bytes_rate 98 | 99 | # Percent of bytes downloaded 100 | attr_accessor :bytes_percent 101 | 102 | # Spent time (second) 103 | attr_accessor :spent_time 104 | 105 | # Remaining time (second) 106 | attr_accessor :left_time 107 | 108 | # When the download started (Time) 109 | attr_accessor :started_at 110 | 111 | # Called by Net::HTTP.get_response_with_stats. 112 | def initialize_stats #:nodoc: 113 | @bytes_count = self['content-length'].to_i 114 | @bytes_read = 0 115 | @bytes_rate = 0 116 | @bytes_percent = 0 117 | @spend_time = 0 118 | @left_time = 0 119 | @started_at = Time.now 120 | end 121 | 122 | # Updates stats. 123 | def bytes_read=(bytes_read) #:nodoc: 124 | @bytes_read = bytes_read 125 | @spent_time = Time.now - @started_at 126 | @bytes_rate = @bytes_read / @spent_time if @spent_time != 0 127 | @left_time = (@bytes_count - @bytes_read) / @bytes_rate if @bytes_rate != 0 128 | @bytes_percent = @bytes_read * 100 / @bytes_count if @bytes_count != 0 129 | @bytes_read 130 | end 131 | 132 | # Generic error class. Should not be instanced. 133 | class Error < Net::HTTPError 134 | end 135 | 136 | # Raised if downloaded file is too big. 137 | class MaxSizeExceed < Net::HTTP::Stats::Error 138 | end 139 | 140 | # Raised if spent time is to long. 141 | class MaxSpentTimeExceed < Net::HTTP::Stats::Error 142 | end 143 | 144 | # Raised if remaining time is too long. 145 | class MaxLeftTimeExceed < Net::HTTP::Stats::Error 146 | end 147 | end 148 | end 149 | 150 | def dragged 151 | # Problem with certain URI http://francia.jardinpresente.com.ar/Paso%20a%20Paso%20-%201º%20Fecha%20-%20Apertura%202009.avi 152 | # Even when escaped() 153 | 154 | uri = URI.parse(URI.escape($items[0])) 155 | filename = uri.path.split(File::SEPARATOR)[-1] 156 | 157 | $dz.determinate(true) 158 | $dz.begin("Starting download of #{filename}...") 159 | 160 | File.open(File.join(ENV['EXTRA_PATH'], filename), 'w') do |file| 161 | response = Net::HTTP.get_response_with_stats(uri) do |resp, bytes| 162 | file.write(bytes) 163 | $dz.percent(resp.bytes_percent) 164 | time_left = [resp.left_time.to_i/3600, resp.left_time.to_i/60 % 60, resp.left_time.to_i % 60].map{|t| t.to_s.rjust(2,'0')}.join(':') 165 | $dz.begin("#{resp.bytes_percent}% done - #{time_left} remaining at #{(resp.bytes_rate / 1024).to_i} KB/s") 166 | end 167 | end 168 | 169 | $dz.finish("Download complete") 170 | $dz.url(false) 171 | end 172 | 173 | -------------------------------------------------------------------------------- /SCP Upload.dropzone: -------------------------------------------------------------------------------- 1 | #!/usr/bin/ruby 2 | 3 | # Dropzone Destination Info 4 | # Name: SCP Upload 5 | # Description: Allows files to be uploaded to a remote SSH server. If the option key is held down then files are zipped up before uploading. 6 | # Handles: NSFilenamesPboardType 7 | # Events: Dragged, TestConnection 8 | # KeyModifiers: Option 9 | # Creator: Holistic Software Buggers 10 | # URL: http://antiframeworks.com 11 | # IconURL: http://aptonic.com/destinations/icons/scp.png 12 | # OptionsNIB: ExtendedLogin 13 | # DefaultPort: 22 14 | 15 | require 'scp' 16 | 17 | $host_info = {:server => ENV['SERVER'], 18 | :port => ENV['PORT'], 19 | :username => ENV['USERNAME'], 20 | :password => ENV['PASSWORD']} 21 | 22 | def dragged 23 | delete_zip = false 24 | 25 | if ENV['KEY_MODIFIERS'] == "Option" 26 | # Zip up files before uploading 27 | if $items.length == 1 28 | # Use directory or file name as zip file name 29 | dir_name = $items[0].split(File::SEPARATOR)[-1] 30 | file = ZipFiles.zip($items, "#{dir_name}.zip") 31 | else 32 | file = ZipFiles.zip($items, "files.zip") 33 | end 34 | 35 | # Remove quotes 36 | items = file[1..-2] 37 | delete_zip = true 38 | else 39 | # Recursive upload 40 | items = $items 41 | end 42 | 43 | $dz.begin("Starting transfer...") 44 | $dz.determinate(false) 45 | 46 | remote_paths = SCP.do_upload(items, ENV['REMOTE_PATH'], $host_info) 47 | ZipFiles.delete_zip(items) if delete_zip 48 | 49 | # Put URL of uploaded file on pasteboard 50 | finish_text = "Upload Complete" 51 | 52 | if remote_paths.length == 1 53 | filename = remote_paths[0].split(File::SEPARATOR)[-1].strip[0..-2] 54 | 55 | if ENV['ROOT_URL'] != nil 56 | slash = (ENV['ROOT_URL'][-1,1] == "/" ? "" : "/") 57 | url = ENV['ROOT_URL'] + slash + filename 58 | finish_text = "URL is now on clipboard" 59 | else 60 | url = filename 61 | end 62 | else 63 | url = false 64 | end 65 | 66 | $dz.finish(finish_text) 67 | $dz.url(url) 68 | end 69 | 70 | def testconnection 71 | SCP.test_connection($host_info) 72 | end 73 | 74 | 75 | 76 | 77 | class SCP 78 | 79 | def self.do_upload(source_files, destination, host_info) 80 | last_percent = 0 81 | last_uploaded_path = "" 82 | set_determinate = false 83 | uploaded_file_paths = [] 84 | 85 | host_info = self.sanitize_host_info(host_info) 86 | destination = (destination != nil ? destination : "~/") 87 | 88 | self.upload(source_files, destination, host_info[:server], host_info[:port], 89 | host_info[:username], host_info[:password]) do |percent, remote_path| 90 | remote_file = remote_path.split(File::SEPARATOR)[-1][0..-2] 91 | 92 | if remote_path != last_uploaded_path 93 | $dz.begin("Uploading #{remote_file}...") 94 | uploaded_file_paths << remote_path 95 | end 96 | 97 | last_uploaded_path = remote_path 98 | 99 | if not set_determinate 100 | # Switch to determinate now file sizes have been calculated and upload is beginning 101 | $dz.determinate(true) 102 | set_determinate = true 103 | end 104 | 105 | $dz.percent(percent) if last_percent != percent 106 | last_percent = percent 107 | end 108 | 109 | return uploaded_file_paths 110 | end 111 | 112 | def self.upload(localpaths, remotedir, host, port, user, pass, &block) 113 | alert_title = "SCP Upload Error" 114 | 115 | begin 116 | Net::SSH.start(host, user, {:password => pass, :port => port}) do |ssh| 117 | files = [] 118 | size = 0 119 | 120 | localpaths.each do |localpath| 121 | path = self.path_contents(localpath, remotedir) 122 | files.concat path[:files] 123 | size += path[:size] 124 | end 125 | 126 | transferred = 0 127 | $dz.begin("Uploading #{files.size} files...") if files.length > 1 128 | files.each do |local, remote| 129 | if local.empty? then 130 | # Try to create the directory 131 | begin 132 | ssh.exec! "mkdir \"#{remote}\"" 133 | rescue 134 | # $dz.error("Error creating directory", $!) 135 | # Remote already exists? 136 | end 137 | else 138 | begin 139 | # Send the file 140 | bytesSent = 0 141 | ssh.scp.upload!(local, remote) do |ch, name, sent, total| 142 | bytesSent = sent 143 | if size != 0 144 | percent = ((transferred + sent) * 100 / size) 145 | else 146 | percent = 100 147 | end 148 | yield percent, remote 149 | end 150 | transferred += bytesSent 151 | rescue 152 | $dz.error(alert_title, $!) 153 | end 154 | end 155 | end 156 | end 157 | rescue Timeout::Error 158 | $dz.error(alert_title, "Connection timed out.") 159 | rescue SocketError 160 | $dz.error(alert_title, "Server not found.") 161 | rescue Net::SSH::AuthenticationFailed 162 | $dz.error(alert_title, "Username or password incorrect.") 163 | rescue 164 | $dz.error(error_title, $!) 165 | end 166 | end 167 | 168 | def self.path_contents(localfile, remotedir) 169 | files = [] 170 | size = 0 171 | filestat = File.stat(localfile) 172 | 173 | # Check if this is a file or directory 174 | if filestat.directory? then 175 | remotedir += ('/' + File.basename(localfile)).gsub('//', '/') 176 | # Make sure we create the remote directory later 177 | files.push ['', remotedir] 178 | # Recurse through dir 179 | Dir.foreach localfile do |file| 180 | next if file == '.' or file == '..' 181 | local = (localfile + '/' + file).gsub('//', '/') 182 | begin 183 | p = path_contents(local, remotedir) 184 | rescue 185 | next 186 | end 187 | size += p[:size] 188 | files.concat p[:files] 189 | end 190 | 191 | elsif filestat.file? then 192 | # Increment the size 193 | size += File.size localfile; 194 | remotefile = (remotedir + '/' + File.basename(localfile)).gsub('//', '/') 195 | files.push [localfile, "\"" + remotefile + "\""] 196 | end 197 | return { :files => files, :size => size } 198 | end 199 | 200 | def self.sanitize_host_info(host_info) 201 | host_info[:port] = (host_info[:port] != nil ? host_info[:port].to_i : 22) 202 | return host_info 203 | end 204 | 205 | def self.test_connection(host_info) 206 | host_info = self.sanitize_host_info(host_info) 207 | error_title = "Connection Failed" 208 | begin 209 | Net::SSH.start(host_info[:server], host_info[:username], {:password => host_info[:password], :port => host_info[:port]}) do |ssh| 210 | end 211 | rescue Timeout::Error 212 | $dz.error(error_title, "Connection timed out.") 213 | rescue SocketError 214 | $dz.error(error_title, "Server not found.") 215 | rescue Net::SSH::AuthenticationFailed 216 | $dz.error("Authentication Failed", "Username or password incorrect.") 217 | rescue 218 | $dz.error(error_title, $!) 219 | end 220 | 221 | $dz.alert("Connection Successful", "SCP connection succeeded.") 222 | end 223 | 224 | end -------------------------------------------------------------------------------- /SCP Upload with Gallery.dropzone: -------------------------------------------------------------------------------- 1 | #!/usr/bin/ruby 2 | 3 | # Dropzone Destination Info 4 | # Name: SCP Upload with Gallery Creation 5 | # Description: Uploads files to a remote SSH server. If multiple files are being uploaded, a 'gallery' is created. 'Option' zips the files up. 6 | # Handles: NSFilenamesPboardType 7 | # Events: Dragged, TestConnection 8 | # KeyModifiers: Option, Control 9 | # Creator: wulfovitch - based on code from Holistic Software Buggers 10 | # URL: http://wulfovitch.net 11 | # IconURL: http://wulfovitch.net/images/scp_image_upload.png 12 | # OptionsNIB: ExtendedLogin 13 | # DefaultPort: 22 14 | 15 | require 'scp' 16 | 17 | $host_info = {:server => ENV['SERVER'], 18 | :port => ENV['PORT'], 19 | :username => ENV['USERNAME'], 20 | :password => ENV['PASSWORD']} 21 | 22 | $image_max_width = '1024' 23 | $image_max_height = '1024' 24 | 25 | def dragged 26 | delete_zip = false 27 | 28 | if ENV['KEY_MODIFIERS'] == "Option" 29 | # Zip up files before uploading 30 | if $items.length == 1 31 | # Use directory or file name as zip file name 32 | dir_name = $items[0].split(File::SEPARATOR)[-1] 33 | file = ZipFiles.zip($items, "#{dir_name}.zip") 34 | else 35 | file = ZipFiles.zip($items, "files.zip") 36 | end 37 | 38 | # Remove quotes 39 | items = file[1..-2] 40 | delete_zip = true 41 | else 42 | 43 | title_text = "" 44 | if ENV['KEY_MODIFIERS'] == "Control" 45 | output = `./CocoaDialog standard-inputbox --title "Enter Title" --e --informative-text "Enter the name for your files:"` 46 | button, title_text = output.split("\n") 47 | 48 | if button == "2" or title_text == nil 49 | $dz.finish("Cancelled") 50 | $dz.url(false) 51 | return 52 | end 53 | end 54 | 55 | $dz.begin("Resizing Images...") 56 | $dz.determinate(true) 57 | tmp_folder = "/tmp/dz_scp_tmp/" 58 | if title_text.empty? 59 | upload_folder_name = "#{Time.now.strftime("%Y-%m-%d_%I-%M-%S")}" 60 | title_text = "#{Time.now.strftime("%Y-%m-%d %I:%M:%S")}" 61 | else 62 | upload_folder_name = "#{Time.now.strftime("%Y-%m-%d_")}#{title_text}" 63 | title_text = "#{title_text} • #{Time.now.strftime("%Y-%m-%d")}" 64 | end 65 | upload_folder = "#{tmp_folder}#{upload_folder_name}/" 66 | system("/bin/mkdir #{tmp_folder}") 67 | system("/bin/mkdir #{upload_folder}") 68 | system("/bin/mkdir #{upload_folder}#{upload_folder_name}") 69 | items = Array.new 70 | $items.each_with_index { |item, index| 71 | file = File.basename(item, '.*') 72 | ext = File.extname(item).downcase 73 | if image?(item) 74 | resized_image = "#{upload_folder}#{file}#{ext}" 75 | convert_command_found = system("/usr/local/bin/convert #{item} -resize '#{$image_max_width}x#{$image_max_height}>' #{resized_image}") 76 | if convert_command_found 77 | items << resized_image 78 | else 79 | items << item 80 | end 81 | else 82 | items << item 83 | end 84 | $dz.percent((((index+1).to_f / $items.length.to_f) * 100.0).to_i) 85 | } 86 | 87 | # only one file is uploaded, no need for a special folder 88 | if items.length == 1 89 | upload_folder_name = nil 90 | else 91 | # create html file, if multiple files are present 92 | f = File.new("#{upload_folder}index.html", "w") 93 | if f 94 | f.syswrite("\n") 96 | f.syswrite("\n") 97 | f.syswrite("\n") 98 | f.syswrite(" #{title_text}\n") 99 | f.syswrite(" \n") 100 | f.syswrite("\n") 101 | f.syswrite("\n") 102 | items.each_with_index { |item, index| 103 | f.syswrite("

\n") 104 | slash = (ENV['ROOT_URL'][-1,1] == "/" ? "" : "/") 105 | filename = "#{File.basename(item, '.*')}#{File.extname(item)}" 106 | if image?(filename) 107 | f.syswrite(" \"#{filename}\"/\n") 108 | else 109 | f.syswrite(" #{filename}\n") 110 | end 111 | f.syswrite("

\n") 112 | } 113 | f.syswrite("\n") 114 | f.syswrite("") 115 | items << f.path 116 | end 117 | end 118 | end 119 | 120 | if $items.length == 1 121 | 122 | end 123 | 124 | $dz.begin("Starting transfer...") 125 | $dz.determinate(false) 126 | 127 | 128 | unless upload_folder_name.nil? 129 | # create folder on remote location 130 | SCP.do_upload("#{upload_folder}#{upload_folder_name}", ENV['REMOTE_PATH'], $host_info) 131 | # drop files into the just created folder 132 | remote_paths = SCP.do_upload(items, ENV['REMOTE_PATH']+upload_folder_name, $host_info) 133 | else 134 | remote_paths = SCP.do_upload(items, ENV['REMOTE_PATH'], $host_info) 135 | end 136 | 137 | ZipFiles.delete_zip(items) if delete_zip 138 | 139 | # Put URL of uploaded file on pasteboard 140 | finish_text = "Upload Complete" 141 | if remote_paths.length == 1 142 | filename = remote_paths[0].split(File::SEPARATOR)[-1].strip[0..-2] 143 | 144 | if ENV['ROOT_URL'] != nil 145 | slash = (ENV['ROOT_URL'][-1,1] == "/" ? "" : "/") 146 | url = ENV['ROOT_URL'] + slash + filename 147 | finish_text = "URL is now on clipboard" 148 | else 149 | url = filename 150 | end 151 | else 152 | filename = items.at(-1).split(File::SEPARATOR)[-1].strip[0..-1] 153 | slash = (ENV['ROOT_URL'][-1,1] == "/" ? "" : "/") 154 | url = ENV['ROOT_URL'] + slash + upload_folder_name 155 | finish_text = "URL is now on clipboard" 156 | end 157 | 158 | system("/bin/rm -rf #{tmp_folder}") 159 | 160 | $dz.finish(finish_text) 161 | $dz.url(url) 162 | end 163 | 164 | def image?(filename) 165 | allowed_exts = ["jpg", "jpeg", "gif", "tif", "tiff", "png", "bmp"] 166 | ext = File.extname(filename).downcase[1..-1] 167 | allowed_exts.include?(ext) 168 | end 169 | 170 | def testconnection 171 | SCP.test_connection($host_info) 172 | end 173 | 174 | 175 | 176 | 177 | class SCP 178 | 179 | def self.do_upload(source_files, destination, host_info) 180 | last_percent = 0 181 | last_uploaded_path = "" 182 | set_determinate = false 183 | uploaded_file_paths = [] 184 | 185 | host_info = self.sanitize_host_info(host_info) 186 | destination = (destination != nil ? destination : "~/") 187 | 188 | self.upload(source_files, destination, host_info[:server], host_info[:port], 189 | host_info[:username], host_info[:password]) do |percent, remote_path| 190 | remote_file = remote_path.split(File::SEPARATOR)[-1][0..-2] 191 | 192 | if remote_path != last_uploaded_path 193 | $dz.begin("Uploading #{remote_file}...") 194 | uploaded_file_paths << remote_path 195 | end 196 | 197 | last_uploaded_path = remote_path 198 | 199 | if not set_determinate 200 | # Switch to determinate now file sizes have been calculated and upload is beginning 201 | $dz.determinate(true) 202 | set_determinate = true 203 | end 204 | 205 | $dz.percent(percent) if last_percent != percent 206 | last_percent = percent 207 | end 208 | 209 | return uploaded_file_paths 210 | end 211 | 212 | def self.upload(localpaths, remotedir, host, port, user, pass, &block) 213 | alert_title = "SCP Upload Error" 214 | 215 | begin 216 | Net::SSH.start(host, user, {:password => pass, :port => port}) do |ssh| 217 | files = [] 218 | size = 0 219 | 220 | localpaths.each do |localpath| 221 | path = self.path_contents(localpath, remotedir) 222 | files.concat path[:files] 223 | size += path[:size] 224 | end 225 | 226 | transferred = 0 227 | $dz.begin("Uploading #{files.size} files...") if files.length > 1 228 | files.each do |local, remote| 229 | if local.empty? then 230 | # Try to create the directory 231 | begin 232 | ssh.exec! "mkdir \"#{remote}\"" 233 | rescue 234 | # $dz.error("Error creating directory", $!) 235 | # Remote already exists? 236 | end 237 | else 238 | begin 239 | # Send the file 240 | bytesSent = 0 241 | ssh.scp.upload!(local, remote) do |ch, name, sent, total| 242 | bytesSent = sent 243 | if size != 0 244 | percent = ((transferred + sent) * 100 / size) 245 | else 246 | percent = 100 247 | end 248 | yield percent, remote 249 | end 250 | transferred += bytesSent 251 | rescue 252 | $dz.error(alert_title, $!) 253 | end 254 | end 255 | end 256 | end 257 | rescue Timeout::Error 258 | $dz.error(alert_title, "Connection timed out.") 259 | rescue SocketError 260 | $dz.error(alert_title, "Server not found.") 261 | rescue Net::SSH::AuthenticationFailed 262 | $dz.error(alert_title, "Username or password incorrect.") 263 | rescue 264 | $dz.error(error_title, $!) 265 | end 266 | end 267 | 268 | def self.path_contents(localfile, remotedir) 269 | files = [] 270 | size = 0 271 | filestat = File.stat(localfile) 272 | 273 | # Check if this is a file or directory 274 | if filestat.directory? then 275 | remotedir += ('/' + File.basename(localfile)).gsub('//', '/') 276 | # Make sure we create the remote directory later 277 | files.push ['', remotedir] 278 | # Recurse through dir 279 | Dir.foreach localfile do |file| 280 | next if file == '.' or file == '..' 281 | local = (localfile + '/' + file).gsub('//', '/') 282 | begin 283 | p = path_contents(local, remotedir) 284 | rescue 285 | next 286 | end 287 | size += p[:size] 288 | files.concat p[:files] 289 | end 290 | 291 | elsif filestat.file? then 292 | # Increment the size 293 | size += File.size localfile; 294 | remotefile = (remotedir + '/' + File.basename(localfile)).gsub('//', '/') 295 | files.push [localfile, "\"" + remotefile + "\""] 296 | end 297 | return { :files => files, :size => size } 298 | end 299 | 300 | def self.sanitize_host_info(host_info) 301 | host_info[:port] = (host_info[:port] != nil ? host_info[:port].to_i : 22) 302 | return host_info 303 | end 304 | 305 | def self.test_connection(host_info) 306 | host_info = self.sanitize_host_info(host_info) 307 | error_title = "Connection Failed" 308 | begin 309 | Net::SSH.start(host_info[:server], host_info[:username], {:password => host_info[:password], :port => host_info[:port]}) do |ssh| 310 | end 311 | rescue Timeout::Error 312 | $dz.error(error_title, "Connection timed out.") 313 | rescue SocketError 314 | $dz.error(error_title, "Server not found.") 315 | rescue Net::SSH::AuthenticationFailed 316 | $dz.error("Authentication Failed", "Username or password incorrect.") 317 | rescue 318 | $dz.error(error_title, $!) 319 | end 320 | 321 | $dz.alert("Connection Successful", "SCP connection succeeded.") 322 | end 323 | 324 | end -------------------------------------------------------------------------------- /aws.dropzone: -------------------------------------------------------------------------------- 1 | # Dropzone Destination Info 2 | # Name: AWS 3 | # Description: Make droplets for your aws/s3 for specific bucket 4 | # Handles: NSFilenamesPboardType 5 | # Events: dragged 6 | # KeyModifiers: Option 7 | # Creator: Edgar J. Suárez 8 | # URL: http://e.dgar.org 9 | # IconURL: http://aws3-edgarjs-images.s3.amazonaws.com/awss3.png 10 | # OptionsNIB: ExtendedLogin 11 | # LoginTitle: Username is for Access Key ID, Password is for Secret Key, and Remote Path is for bucket name 12 | 13 | # The code below is from http://developer.amazonwebservices.com/connect/entry.jspa?externalID=135&categoryID=47 14 | require 'base64' 15 | require 'cgi' 16 | require 'openssl' 17 | require 'digest/sha1' 18 | require 'net/https' 19 | require 'rexml/document' 20 | require 'time' 21 | require 'uri' 22 | 23 | # this wasn't added until v 1.8.3 24 | if (RUBY_VERSION < '1.8.3') 25 | class Net::HTTP::Delete < Net::HTTPRequest 26 | METHOD = 'DELETE' 27 | REQUEST_HAS_BODY = false 28 | RESPONSE_HAS_BODY = true 29 | end 30 | end 31 | 32 | # this module has two big classes: AWSAuthConnection and 33 | # QueryStringAuthGenerator. both use identical apis, but the first actually 34 | # performs the operation, while the second simply outputs urls with the 35 | # appropriate authentication query string parameters, which could be used 36 | # in another tool (such as your web browser for GETs). 37 | module S3 38 | DEFAULT_HOST = 's3.amazonaws.com' 39 | PORTS_BY_SECURITY = { true => 443, false => 80 } 40 | METADATA_PREFIX = 'x-amz-meta-' 41 | AMAZON_HEADER_PREFIX = 'x-amz-' 42 | 43 | # Location constraint for CreateBucket 44 | module BucketLocation 45 | DEFAULT = nil 46 | EU = 'EU' 47 | end 48 | 49 | # builds the canonical string for signing. 50 | def S3.canonical_string(method, bucket="", path="", path_args={}, headers={}, expires=nil) 51 | interesting_headers = {} 52 | headers.each do |key, value| 53 | lk = key.downcase 54 | if (lk == 'content-md5' or 55 | lk == 'content-type' or 56 | lk == 'date' or 57 | lk =~ /^#{AMAZON_HEADER_PREFIX}/o) 58 | interesting_headers[lk] = value.to_s.strip 59 | end 60 | end 61 | 62 | # these fields get empty strings if they don't exist. 63 | interesting_headers['content-type'] ||= '' 64 | interesting_headers['content-md5'] ||= '' 65 | 66 | # just in case someone used this. it's not necessary in this lib. 67 | if interesting_headers.has_key? 'x-amz-date' 68 | interesting_headers['date'] = '' 69 | end 70 | 71 | # if you're using expires for query string auth, then it trumps date 72 | # (and x-amz-date) 73 | if not expires.nil? 74 | interesting_headers['date'] = expires 75 | end 76 | 77 | buf = "#{method}\n" 78 | interesting_headers.sort { |a, b| a[0] <=> b[0] }.each do |key, value| 79 | if key =~ /^#{AMAZON_HEADER_PREFIX}/o 80 | buf << "#{key}:#{value}\n" 81 | else 82 | buf << "#{value}\n" 83 | end 84 | end 85 | 86 | # build the path using the bucket and key 87 | if not bucket.empty? 88 | buf << "/#{bucket}" 89 | end 90 | # append the key (it might be empty string) 91 | # append a slash regardless 92 | buf << "/#{path}" 93 | 94 | # if there is an acl, logging, or torrent parameter 95 | # add them to the string 96 | if path_args.has_key?('acl') 97 | buf << '?acl' 98 | elsif path_args.has_key?('torrent') 99 | buf << '?torrent' 100 | elsif path_args.has_key?('logging') 101 | buf << '?logging' 102 | elsif path_args.has_key?('location') 103 | buf << '?location' 104 | end 105 | 106 | return buf 107 | end 108 | 109 | # encodes the given string with the aws_secret_access_key, by taking the 110 | # hmac-sha1 sum, and then base64 encoding it. optionally, it will also 111 | # url encode the result of that to protect the string if it's going to 112 | # be used as a query string parameter. 113 | def S3.encode(aws_secret_access_key, str, urlencode=false) 114 | digest = OpenSSL::Digest::Digest.new('sha1') 115 | b64_hmac = 116 | Base64.encode64( 117 | OpenSSL::HMAC.digest(digest, aws_secret_access_key, str)).strip 118 | 119 | if urlencode 120 | return CGI::escape(b64_hmac) 121 | else 122 | return b64_hmac 123 | end 124 | end 125 | 126 | # build the path_argument string 127 | def S3.path_args_hash_to_string(path_args={}) 128 | arg_string = '' 129 | path_args.each { |k, v| 130 | arg_string << (arg_string.empty? ? '?' : '&') 131 | arg_string << k 132 | if not v.nil? 133 | arg_string << "=#{CGI::escape(v)}" 134 | end 135 | } 136 | return arg_string 137 | end 138 | 139 | # uses Net::HTTP to interface with S3. note that this interface should only 140 | # be used for smaller objects, as it does not stream the data. if you were 141 | # to download a 1gb file, it would require 1gb of memory. also, this class 142 | # creates a new http connection each time. it would be greatly improved with 143 | # some connection pooling. 144 | class AWSAuthConnection 145 | attr_accessor :calling_format 146 | 147 | def initialize(aws_access_key_id, aws_secret_access_key, is_secure=true, 148 | server=DEFAULT_HOST, port=PORTS_BY_SECURITY[is_secure], 149 | calling_format=CallingFormat::SUBDOMAIN) 150 | @aws_access_key_id = aws_access_key_id 151 | @aws_secret_access_key = aws_secret_access_key 152 | @server = server 153 | @is_secure = is_secure 154 | @calling_format = calling_format 155 | @port = port 156 | end 157 | 158 | def create_bucket(bucket, headers={}) 159 | return Response.new(make_request('PUT', bucket, '', {}, headers)) 160 | end 161 | 162 | def create_located_bucket(bucket, location=BucketLocation::DEFAULT, headers={}) 163 | if (location != BucketLocation::DEFAULT) 164 | xmlbody = "#{location}" 165 | end 166 | return Response.new(make_request('PUT', bucket, '', {}, headers, xmlbody)) 167 | end 168 | 169 | def check_bucket_exists(bucket) 170 | begin 171 | make_request('HEAD', bucket, '', {}, {}) 172 | return true 173 | rescue Net::HTTPServerException 174 | response = $!.response 175 | return false if (response.code.to_i == 404) 176 | raise 177 | end 178 | end 179 | 180 | # takes options :prefix, :marker, :max_keys, and :delimiter 181 | def list_bucket(bucket, options={}, headers={}) 182 | path_args = {} 183 | options.each { |k, v| 184 | path_args[k] = v.to_s 185 | } 186 | 187 | return ListBucketResponse.new(make_request('GET', bucket, '', path_args, headers)) 188 | end 189 | 190 | def delete_bucket(bucket, headers={}) 191 | return Response.new(make_request('DELETE', bucket, '', {}, headers)) 192 | end 193 | 194 | def put(bucket, key, object, headers={}) 195 | object = S3Object.new(object) if not object.instance_of? S3Object 196 | 197 | return Response.new( 198 | make_request('PUT', bucket, CGI::escape(key), {}, headers, object.data, object.metadata) 199 | ) 200 | end 201 | 202 | def get(bucket, key, headers={}) 203 | return GetResponse.new(make_request('GET', bucket, CGI::escape(key), {}, headers)) 204 | end 205 | 206 | def delete(bucket, key, headers={}) 207 | return Response.new(make_request('DELETE', bucket, CGI::escape(key), {}, headers)) 208 | end 209 | 210 | def get_bucket_logging(bucket, headers={}) 211 | return GetResponse.new(make_request('GET', bucket, '', {'logging' => nil}, headers)) 212 | end 213 | 214 | def put_bucket_logging(bucket, logging_xml_doc, headers={}) 215 | return Response.new(make_request('PUT', bucket, '', {'logging' => nil}, headers, logging_xml_doc)) 216 | end 217 | 218 | def get_bucket_acl(bucket, headers={}) 219 | return get_acl(bucket, '', headers) 220 | end 221 | 222 | # returns an xml document representing the access control list. 223 | # this could be parsed into an object. 224 | def get_acl(bucket, key, headers={}) 225 | return GetResponse.new(make_request('GET', bucket, CGI::escape(key), {'acl' => nil}, headers)) 226 | end 227 | 228 | def put_bucket_acl(bucket, acl_xml_doc, headers={}) 229 | return put_acl(bucket, '', acl_xml_doc, headers) 230 | end 231 | 232 | # sets the access control policy for the given resource. acl_xml_doc must 233 | # be a string in the acl xml format. 234 | def put_acl(bucket, key, acl_xml_doc, headers={}) 235 | return Response.new( 236 | make_request('PUT', bucket, CGI::escape(key), {'acl' => nil}, headers, acl_xml_doc, {}) 237 | ) 238 | end 239 | 240 | def get_bucket_location(bucket) 241 | return LocationResponse.new(make_request('GET', bucket, '', {'location' => nil}, {})) 242 | end 243 | 244 | def list_all_my_buckets(headers={}) 245 | return ListAllMyBucketsResponse.new(make_request('GET', '', '', {}, headers)) 246 | end 247 | 248 | private 249 | def make_request(method, bucket='', key='', path_args={}, headers={}, data='', metadata={}) 250 | 251 | # build the domain based on the calling format 252 | server = '' 253 | if bucket.empty? 254 | # for a bucketless request (i.e. list all buckets) 255 | # revert to regular domain case since this operation 256 | # does not make sense for vanity domains 257 | server = @server 258 | elsif @calling_format == CallingFormat::SUBDOMAIN 259 | server = "#{bucket}.#{@server}" 260 | elsif @calling_format == CallingFormat::VANITY 261 | server = bucket 262 | else 263 | server = @server 264 | end 265 | 266 | # build the path based on the calling format 267 | path = '' 268 | if (not bucket.empty?) and (@calling_format == CallingFormat::PATH) 269 | path << "/#{bucket}" 270 | end 271 | # add the slash after the bucket regardless 272 | # the key will be appended if it is non-empty 273 | path << "/#{key}" 274 | 275 | # build the path_argument string 276 | # add the ? in all cases since 277 | # signature and credentials follow path args 278 | path << S3.path_args_hash_to_string(path_args) 279 | 280 | while true 281 | http = Net::HTTP.new(server, @port) 282 | http.use_ssl = @is_secure 283 | http.start do 284 | req = method_to_request_class(method).new(path) 285 | 286 | set_headers(req, headers) 287 | set_headers(req, metadata, METADATA_PREFIX) 288 | 289 | set_aws_auth_header(req, @aws_access_key_id, @aws_secret_access_key, bucket, key, path_args) 290 | if req.request_body_permitted? 291 | resp = http.request(req, data) 292 | else 293 | resp = http.request(req) 294 | end 295 | 296 | case resp.code.to_i 297 | when 100..299 298 | return resp 299 | when 300..399 # redirect 300 | location = resp['location'] 301 | # handle missing location like a normal http error response 302 | resp.error! if !location 303 | uri = URI.parse(resp['location']) 304 | server = uri.host 305 | path = uri.request_uri 306 | # try again... 307 | else 308 | resp.error! 309 | end 310 | end # http.start 311 | end # while 312 | end 313 | 314 | def method_to_request_class(method) 315 | case method 316 | when 'GET' 317 | return Net::HTTP::Get 318 | when 'HEAD' 319 | return Net::HTTP::Head 320 | when 'PUT' 321 | return Net::HTTP::Put 322 | when 'DELETE' 323 | return Net::HTTP::Delete 324 | else 325 | raise "Unsupported method #{method}" 326 | end 327 | end 328 | 329 | # set the Authorization header using AWS signed header authentication 330 | def set_aws_auth_header(request, aws_access_key_id, aws_secret_access_key, bucket='', key='', path_args={}) 331 | # we want to fix the date here if it's not already been done. 332 | request['Date'] ||= Time.now.httpdate 333 | 334 | # ruby will automatically add a random content-type on some verbs, so 335 | # here we add a dummy one to 'supress' it. change this logic if having 336 | # an empty content-type header becomes semantically meaningful for any 337 | # other verb. 338 | request['Content-Type'] ||= '' 339 | 340 | canonical_string = 341 | S3.canonical_string(request.method, bucket, key, path_args, request.to_hash, nil) 342 | encoded_canonical = S3.encode(aws_secret_access_key, canonical_string) 343 | 344 | request['Authorization'] = "AWS #{aws_access_key_id}:#{encoded_canonical}" 345 | end 346 | 347 | def set_headers(request, headers, prefix='') 348 | headers.each do |key, value| 349 | request[prefix + key] = value 350 | end 351 | end 352 | end 353 | 354 | 355 | # This interface mirrors the AWSAuthConnection class above, but instead 356 | # of performing the operations, this class simply returns a url that can 357 | # be used to perform the operation with the query string authentication 358 | # parameters set. 359 | class QueryStringAuthGenerator 360 | attr_accessor :calling_format 361 | attr_accessor :expires 362 | attr_accessor :expires_in 363 | attr_reader :server 364 | attr_reader :port 365 | attr_reader :is_secure 366 | 367 | # by default, expire in 1 minute 368 | DEFAULT_EXPIRES_IN = 60 369 | 370 | def initialize(aws_access_key_id, aws_secret_access_key, is_secure=true, 371 | server=DEFAULT_HOST, port=PORTS_BY_SECURITY[is_secure], 372 | format=CallingFormat::SUBDOMAIN) 373 | @aws_access_key_id = aws_access_key_id 374 | @aws_secret_access_key = aws_secret_access_key 375 | @protocol = is_secure ? 'https' : 'http' 376 | @server = server 377 | @port = port 378 | @calling_format = format 379 | @is_secure = is_secure 380 | # by default expire 381 | @expires_in = DEFAULT_EXPIRES_IN 382 | end 383 | 384 | # set the expires value to be a fixed time. the argument can 385 | # be either a Time object or else seconds since epoch. 386 | def expires=(value) 387 | @expires = value 388 | @expires_in = nil 389 | end 390 | 391 | # set the expires value to expire at some point in the future 392 | # relative to when the url is generated. value is in seconds. 393 | def expires_in=(value) 394 | @expires_in = value 395 | @expires = nil 396 | end 397 | 398 | def create_bucket(bucket, headers={}) 399 | return generate_url('PUT', bucket, '', {}, headers) 400 | end 401 | 402 | # takes options :prefix, :marker, :max_keys, and :delimiter 403 | def list_bucket(bucket, options={}, headers={}) 404 | path_args = {} 405 | options.each { |k, v| 406 | path_args[k] = v.to_s 407 | } 408 | return generate_url('GET', bucket, '', path_args, headers) 409 | end 410 | 411 | def delete_bucket(bucket, headers={}) 412 | return generate_url('DELETE', bucket, '', {}, headers) 413 | end 414 | 415 | # don't really care what object data is. it's just for conformance with the 416 | # other interface. If this doesn't work, check tcpdump to see if the client is 417 | # putting a Content-Type header on the wire. 418 | def put(bucket, key, object=nil, headers={}) 419 | object = S3Object.new(object) if not object.instance_of? S3Object 420 | return generate_url('PUT', bucket, CGI::escape(key), {}, merge_meta(headers, object)) 421 | end 422 | 423 | def get(bucket, key, headers={}) 424 | return generate_url('GET', bucket, CGI::escape(key), {}, headers) 425 | end 426 | 427 | def delete(bucket, key, headers={}) 428 | return generate_url('DELETE', bucket, CGI::escape(key), {}, headers) 429 | end 430 | 431 | def get_bucket_logging(bucket, headers={}) 432 | return generate_url('GET', bucket, '', {'logging' => nil}, headers) 433 | end 434 | 435 | def put_bucket_logging(bucket, logging_xml_doc, headers={}) 436 | return generate_url('PUT', bucket, '', {'logging' => nil}, headers) 437 | end 438 | 439 | def get_acl(bucket, key='', headers={}) 440 | return generate_url('GET', bucket, CGI::escape(key), {'acl' => nil}, headers) 441 | end 442 | 443 | def get_bucket_acl(bucket, headers={}) 444 | return get_acl(bucket, '', headers) 445 | end 446 | 447 | # don't really care what acl_xml_doc is. 448 | # again, check the wire for Content-Type if this fails. 449 | def put_acl(bucket, key, acl_xml_doc, headers={}) 450 | return generate_url('PUT', bucket, CGI::escape(key), {'acl' => nil}, headers) 451 | end 452 | 453 | def put_bucket_acl(bucket, acl_xml_doc, headers={}) 454 | return put_acl(bucket, '', acl_xml_doc, headers) 455 | end 456 | 457 | def list_all_my_buckets(headers={}) 458 | return generate_url('GET', '', '', {}, headers) 459 | end 460 | 461 | 462 | private 463 | # generate a url with the appropriate query string authentication 464 | # parameters set. 465 | def generate_url(method, bucket="", key="", path_args={}, headers={}) 466 | expires = 0 467 | if not @expires_in.nil? 468 | expires = Time.now.to_i + @expires_in 469 | elsif not @expires.nil? 470 | expires = @expires 471 | else 472 | raise "invalid expires state" 473 | end 474 | 475 | canonical_string = 476 | S3::canonical_string(method, bucket, key, path_args, headers, expires) 477 | encoded_canonical = 478 | S3::encode(@aws_secret_access_key, canonical_string) 479 | 480 | url = CallingFormat.build_url_base(@protocol, @server, @port, bucket, @calling_format) 481 | 482 | path_args["Signature"] = encoded_canonical.to_s 483 | path_args["Expires"] = expires.to_s 484 | path_args["AWSAccessKeyId"] = @aws_access_key_id.to_s 485 | arg_string = S3.path_args_hash_to_string(path_args) 486 | 487 | return "#{url}/#{key}#{arg_string}" 488 | end 489 | 490 | def merge_meta(headers, object) 491 | final_headers = headers.clone 492 | if not object.nil? and not object.metadata.nil? 493 | object.metadata.each do |k, v| 494 | final_headers[METADATA_PREFIX + k] = v 495 | end 496 | end 497 | return final_headers 498 | end 499 | end 500 | 501 | class S3Object 502 | attr_accessor :data 503 | attr_accessor :metadata 504 | def initialize(data, metadata={}) 505 | @data, @metadata = data, metadata 506 | end 507 | end 508 | 509 | # class for storing calling format constants 510 | module CallingFormat 511 | PATH = 0 # http://s3.amazonaws.com/bucket/key 512 | SUBDOMAIN = 1 # http://bucket.s3.amazonaws.com/key 513 | VANITY = 2 # http:///key -- vanity_domain resolves to s3.amazonaws.com 514 | 515 | # build the url based on the calling format, and bucket 516 | def CallingFormat.build_url_base(protocol, server, port, bucket, format) 517 | build_url_base = "#{protocol}://" 518 | if bucket.empty? 519 | build_url_base << "#{server}:#{port}" 520 | elsif format == SUBDOMAIN 521 | build_url_base << "#{bucket}.#{server}:#{port}" 522 | elsif format == VANITY 523 | build_url_base << "#{bucket}:#{port}" 524 | else 525 | build_url_base << "#{server}:#{port}/#{bucket}" 526 | end 527 | return build_url_base 528 | end 529 | end 530 | 531 | class Owner 532 | attr_accessor :id 533 | attr_accessor :display_name 534 | end 535 | 536 | class ListEntry 537 | attr_accessor :key 538 | attr_accessor :last_modified 539 | attr_accessor :etag 540 | attr_accessor :size 541 | attr_accessor :storage_class 542 | attr_accessor :owner 543 | end 544 | 545 | class ListProperties 546 | attr_accessor :name 547 | attr_accessor :prefix 548 | attr_accessor :marker 549 | attr_accessor :max_keys 550 | attr_accessor :delimiter 551 | attr_accessor :is_truncated 552 | attr_accessor :next_marker 553 | end 554 | 555 | class CommonPrefixEntry 556 | attr_accessor :prefix 557 | end 558 | 559 | # Parses the list bucket output into a list of ListEntry objects, and 560 | # a list of CommonPrefixEntry objects if applicable. 561 | class ListBucketParser 562 | attr_reader :properties 563 | attr_reader :entries 564 | attr_reader :common_prefixes 565 | 566 | def initialize 567 | reset 568 | end 569 | 570 | def tag_start(name, attributes) 571 | if name == 'ListBucketResult' 572 | @properties = ListProperties.new 573 | elsif name == 'Contents' 574 | @curr_entry = ListEntry.new 575 | elsif name == 'Owner' 576 | @curr_entry.owner = Owner.new 577 | elsif name == 'CommonPrefixes' 578 | @common_prefix_entry = CommonPrefixEntry.new 579 | end 580 | end 581 | 582 | # we have one, add him to the entries list 583 | def tag_end(name) 584 | text = @curr_text.strip 585 | # this prefix is the one we echo back from the request 586 | if name == 'Name' 587 | @properties.name = text 588 | elsif name == 'Prefix' and @is_echoed_prefix 589 | @properties.prefix = text 590 | @is_echoed_prefix = nil 591 | elsif name == 'Marker' 592 | @properties.marker = text 593 | elsif name == 'MaxKeys' 594 | @properties.max_keys = text.to_i 595 | elsif name == 'Delimiter' 596 | @properties.delimiter = text 597 | elsif name == 'IsTruncated' 598 | @properties.is_truncated = text == 'true' 599 | elsif name == 'NextMarker' 600 | @properties.next_marker = text 601 | elsif name == 'Contents' 602 | @entries << @curr_entry 603 | elsif name == 'Key' 604 | @curr_entry.key = text 605 | elsif name == 'LastModified' 606 | @curr_entry.last_modified = text 607 | elsif name == 'ETag' 608 | @curr_entry.etag = text 609 | elsif name == 'Size' 610 | @curr_entry.size = text.to_i 611 | elsif name == 'StorageClass' 612 | @curr_entry.storage_class = text 613 | elsif name == 'ID' 614 | @curr_entry.owner.id = text 615 | elsif name == 'DisplayName' 616 | @curr_entry.owner.display_name = text 617 | elsif name == 'CommonPrefixes' 618 | @common_prefixes << @common_prefix_entry 619 | elsif name == 'Prefix' 620 | # this is the common prefix for keys that match up to the delimiter 621 | @common_prefix_entry.prefix = text 622 | end 623 | @curr_text = '' 624 | end 625 | 626 | def text(text) 627 | @curr_text += text 628 | end 629 | 630 | def xmldecl(version, encoding, standalone) 631 | # ignore 632 | end 633 | 634 | # get ready for another parse 635 | def reset 636 | @is_echoed_prefix = true; 637 | @entries = [] 638 | @curr_entry = nil 639 | @common_prefixes = [] 640 | @common_prefix_entry = nil 641 | @curr_text = '' 642 | end 643 | end 644 | 645 | class ListAllMyBucketsParser 646 | attr_reader :entries 647 | 648 | def initialize 649 | reset 650 | end 651 | 652 | def tag_start(name, attributes) 653 | if name == 'Bucket' 654 | @curr_bucket = Bucket.new 655 | end 656 | end 657 | 658 | # we have one, add him to the entries list 659 | def tag_end(name) 660 | text = @curr_text.strip 661 | if name == 'Bucket' 662 | @entries << @curr_bucket 663 | elsif name == 'Name' 664 | @curr_bucket.name = text 665 | elsif name == 'CreationDate' 666 | @curr_bucket.creation_date = text 667 | end 668 | @curr_text = '' 669 | end 670 | 671 | def text(text) 672 | @curr_text += text 673 | end 674 | 675 | def xmldecl(version, encoding, standalone) 676 | # ignore 677 | end 678 | 679 | # get ready for another parse 680 | def reset 681 | @entries = [] 682 | @owner = nil 683 | @curr_bucket = nil 684 | @curr_text = '' 685 | end 686 | end 687 | 688 | class ErrorResponseParser 689 | attr_reader :code 690 | 691 | def self.parse(msg) 692 | parser = ErrorResponseParser.new 693 | REXML::Document.parse_stream(msg, parser) 694 | parser.code 695 | end 696 | 697 | def initialize 698 | @state = :init 699 | @code = nil 700 | end 701 | 702 | def tag_start(name, attributes) 703 | case @state 704 | when :init 705 | if name == 'Error' 706 | @state = :tag_error 707 | else 708 | @state = :bad 709 | end 710 | when :tag_error 711 | if name == 'Code' 712 | @state = :tag_code 713 | @code = '' 714 | else 715 | @state = :bad 716 | end 717 | end 718 | end 719 | 720 | # we have one, add him to the entries list 721 | def tag_end(name) 722 | case @state 723 | when :tag_code 724 | @state = :done 725 | end 726 | end 727 | 728 | def text(text) 729 | @code += text if @state == :tag_code 730 | end 731 | 732 | def xmldecl(version, encoding, standalone) 733 | # ignore 734 | end 735 | end 736 | 737 | class LocationParser 738 | attr_reader :location 739 | 740 | def self.parse(msg) 741 | parser = LocationParser.new 742 | REXML::Document.parse_stream(msg, parser) 743 | return parser.location 744 | end 745 | 746 | def initialize 747 | @state = :init 748 | @location = nil 749 | end 750 | 751 | def tag_start(name, attributes) 752 | if @state == :init 753 | if name == 'LocationConstraint' 754 | @state = :tag_locationconstraint 755 | @location = '' 756 | else 757 | @state = :bad 758 | end 759 | end 760 | end 761 | 762 | # we have one, add him to the entries list 763 | def tag_end(name) 764 | case @state 765 | when :tag_locationconstraint 766 | @state = :done 767 | end 768 | end 769 | 770 | def text(text) 771 | @location += text if @state == :tag_locationconstraint 772 | end 773 | 774 | def xmldecl(version, encoding, standalone) 775 | # ignore 776 | end 777 | end 778 | 779 | class Response 780 | attr_reader :http_response 781 | def initialize(response) 782 | @http_response = response 783 | end 784 | 785 | def message 786 | if @http_response.body 787 | @http_response.body 788 | else 789 | "#{@http_response.code} #{@http_response.message}" 790 | end 791 | end 792 | end 793 | 794 | class Bucket 795 | attr_accessor :name 796 | attr_accessor :creation_date 797 | end 798 | 799 | class GetResponse < Response 800 | attr_reader :object 801 | def initialize(response) 802 | super(response) 803 | metadata = get_aws_metadata(response) 804 | data = response.body 805 | @object = S3Object.new(data, metadata) 806 | end 807 | 808 | # parses the request headers and pulls out the s3 metadata into a hash 809 | def get_aws_metadata(response) 810 | metadata = {} 811 | response.each do |key, value| 812 | if key =~ /^#{METADATA_PREFIX}(.*)$/oi 813 | metadata[$1] = value 814 | end 815 | end 816 | return metadata 817 | end 818 | end 819 | 820 | class ListBucketResponse < Response 821 | attr_reader :properties 822 | attr_reader :entries 823 | attr_reader :common_prefix_entries 824 | 825 | def initialize(response) 826 | super(response) 827 | if response.is_a? Net::HTTPSuccess 828 | parser = ListBucketParser.new 829 | REXML::Document.parse_stream(response.body, parser) 830 | @properties = parser.properties 831 | @entries = parser.entries 832 | @common_prefix_entries = parser.common_prefixes 833 | else 834 | @entries = [] 835 | end 836 | end 837 | end 838 | 839 | class ListAllMyBucketsResponse < Response 840 | attr_reader :entries 841 | def initialize(response) 842 | super(response) 843 | if response.is_a? Net::HTTPSuccess 844 | parser = ListAllMyBucketsParser.new 845 | REXML::Document.parse_stream(response.body, parser) 846 | @entries = parser.entries 847 | else 848 | @entries = [] 849 | end 850 | end 851 | end 852 | 853 | class LocationResponse < Response 854 | attr_reader :location 855 | 856 | def initialize(response) 857 | super(response) 858 | if response.is_a? Net::HTTPSuccess 859 | @location = LocationParser.parse(response.body) 860 | end 861 | end 862 | end 863 | end 864 | 865 | # Custom module for handling dropzone 866 | module DropZone 867 | class AWS 868 | include ::S3 869 | 870 | attr_reader :bucket, :connection 871 | 872 | def initialize(access_key, secret_key, bucket_name, public_read = false) 873 | @access_key, @secret_key, @bucket = access_key, secret_key, bucket_name 874 | @public_read = public_read 875 | connect! 876 | end 877 | 878 | def public_read? 879 | @public_read 880 | end 881 | 882 | def create_bucket 883 | @connection.create_bucket(bucket) 884 | end 885 | 886 | def bucket_exists? 887 | @connection.check_bucket_exists(bucket) 888 | end 889 | 890 | def upload(file_path) 891 | content_type = `file -bi #{file_path}`.strip 892 | basename = File.basename(file_path) 893 | @connection.put(@bucket, basename, 894 | S3Object.new(File.open(file_path).read, { 895 | "uploaded-from" => "DropZone AWS" 896 | }), { 897 | 'Content-Type' => content_type 898 | }.merge(public_read? ? {'x-amz-acl' => 'public-read'} : {})) 899 | 900 | if public_read? 901 | generator = QueryStringAuthGenerator.new(@access_key, @secret_key) 902 | url = generator.get(@bucket, basename).gsub(/\?.*$/, '') 903 | else 904 | false 905 | end 906 | end 907 | 908 | private 909 | 910 | def connect! 911 | @connection = AWSAuthConnection.new(@access_key, @secret_key) 912 | end 913 | end 914 | end 915 | 916 | def dragged 917 | $dz.determinate(false) 918 | file_path = $items[0] 919 | bucket = 920 | if ENV['REMOTE_PATH'].nil? || ENV['REMOTE_PATH'] == '' 921 | "dropzone-#{Time.now.to_i}" 922 | else 923 | ENV['REMOTE_PATH'].downcase 924 | end 925 | aws = DropZone::AWS.new(ENV['USERNAME'], ENV['PASSWORD'], bucket, ENV['KEY_MODIFIERS'] == "Option") 926 | 927 | begin 928 | $dz.begin("Uploading file...") 929 | 930 | aws.create_bucket unless aws.bucket_exists? 931 | url = aws.upload(file_path) 932 | 933 | $dz.finish("File uploaded!") 934 | $dz.url(url) 935 | system("open #{url}") if aws.public_read? 936 | 937 | rescue Exception => e 938 | $dz.finish(e) 939 | $dz.url(false) 940 | end 941 | end 942 | --------------------------------------------------------------------------------