Advertisement

Writing Destinations for Dropzone

by
Student iconAre you a student? Get a yearly Tuts+ subscription for $45 →

Dropzone is not just another FTP upload client, but a great utilities platform for processing files and text. Dropzone is a collection of destinations that can have two types of actions: drag and drop action and/or a click action. There are a lot of pre-programmed destinations to use, but you can also write your own destinations in Ruby!

Introducing Dropzone

Dropzone Dropdown Menu
Dropzone Dropdown Menu

When you launch Dropzone, it places a new menu bar icon at the top of the screen. When you click on it, it exposes all the destinations you have configured. This drop area can have as many destinations as you want to configure. Files or text can be dropped on these targets or you can click on them to do the click action.

Dropzone Circles
Dropzone Circles

When you move the mouse to the far left, or right depending on your configuration, the Dropzone circles appear. You can have, at most, five different destinations in the circles. You can drag files to these destination circles to perform the action on them, or drag files to the menu bar drop-down for the same effect. Dropzone gives a nice indicator of the progress of the action.

Dropzone Dropdown Menu Selecting Preferences
Dropzone Dropdown Menu: Selecting Preferences

To configure your different destinations, just click on the menu bar icon and select Preferences….

Dropzone Destinations Dialog
Dropzone Destinations Dialog

The preferences dialog opens to the list of destinations. Here, new destinations can be created, and old destinations can be edited or deleted.

The default actions you can use are:

  • Move Files
  • Amazon S3
  • Copy Files
  • Copy Files
  • Copy Files
  • Image Shack
  • Install Application
  • Open Application
  • Save Text
  • Share Text
  • TwitPic

You can also download many others from their web site as well.

A destination is simply a single file of Ruby code with the extension changed to .dropzone instead of .rb. Therefore, anyone with a little knowledge of Ruby can create their own destinations and share them with others.

Dropzone Setting Dialog
Dropzone Setting Dialog

The Settings Dialog allows you to turn on the circles, choose the circles to be on the left or right side of the screen, and edit the actions that you have loaded in to your system. Editing an action simply calls the default text editor to edit the destination file.

Tip: All destination files are located at ~/Library/Application Support/Dropzone/Destination Scripts/. Any changes you make to a file there is used immediately. Therefore, you can simply create files there to install them as valid actions in the dialog. When you double click on any Dropzone destination, it is simply copied to this directory.

Compress/Convert Image Destination

I work with pictures for tutorials and blog posts all the time. I am always having to make them smaller and scale them down to a more feasible size. That can be time consuming and boring. But, automation is what Dropzone is all about. A good example destination would be one to receive picture files and convert them while saving the original in a backup directory.

If you are new to Ruby and would like to learn more about it, you should check out The Fundamentals of Ruby course on Tuts+.

Tip: In the download zip for this lesson, I put in an Alfred Workflow for viewing the Dropzone Destination Scripts directory.

To start off, create a file in the Dropzone scripts directory called CompressImage.dropzone. All Dropzone destination scripts start off with a header. This header tells Dropzone how to interact with this destination. In the file created, place the following text:

 #!/usr/bin/ruby

 # Dropzone Destination Info
 # Name: Compress Image
 # Description: This destination is for compressing the images given. You must have imagemagick library(http://www.imagemagick.org/script/index.php) installed. It is best to install from HomeBrew (http://brew.sh/).
 # Handles: NSFilenamesPboardType
 # Events: Clicked, Dragged
 # Creator: Richard Guay <raguay@customct.com>
 # URL: http://customct.com
 # IconURL: http://www.customct.com/images/CompressFile.png

The first line is the standard sha-bang for a ruby script. After the sha-bang, there is a comment block containing the necessary information for Dropzone to make use of the destination. All of these fields are required to be in each destination file. The different fields are:

Name

Name simply is the name you want Dropzone to show the user in the action list. It is best not to call this a destination here, but have a name that accurately describes the action that is performed.

Description

Description is the text displayed for the action. Dropzone only displays two lines. The one here is too large to be seen completely inside the dialog box. But, the user will see that and can look at the destination file to see the whole thing. There is currently no better place to document some things about the destination.

Handles

Handles describes the types of actions that the destination will take. The two possibilities are NSFilenamesPboardType and NSStringPboardType.

The NSFilenamesPboardType tells Dropzone that you only take full system filenames. The NSStringPboardType tells Dropzone that you only take strings. If you list both with a comma, then Dropzone will send you both types. Inside the destination code, you can check the environment variable ENV['DRAGGED_TYPE'] to see which type was passed to the destination. For this destination, only the NSFilenamesPboardType is needed because only files are being worked upon.

Events

Events tells Dropzone what type of events the destination can handle. There are only two events: Dragged and Clicked. A Dragged event will allow the user to drop files and/or text on the destination. A Clicked event will allow the destination to be Clicked for an action. This destination uses both separated by a comma.

Creator

Creator tells Dropzone who the author of the destination is and their email address. This is displayed on the Destination Dialog when the destination action is added to the user’s list of destinations.

URL

URL gives Dropzone a URL to be displayed for more information for the destination. Therefore, the author can supply a link to their own web site or a page on their web site that explains how to use the destination. This is also displayed in the Destination Dialog when the destination action is added to the user’s list of destinations.

IconURL

IconURL give Dropzone a URL to get the icon to be displayed for this destination. This icon is used for the circle dropzones and the menu drop-down dropzones. This icon should be somewhat descriptive of the destination’s action and should be somewhere that is readily available (ie: it would be best if it was not on your personal system with a DynDNS pointing to it. It might not be readily available. But, a public Dropbox link would work. That is, as long as it is not deleted!). Here, I have the icon on my web site for downloading in to Dropzone.

Destination Action Functions

The destination has to define a function for each action listed as usable with the destination. This destination will use both: Dragged action in the dragged function for processing files, and Clicked action in the clicked function for getting preferences for the destination to use. This destination needs to have the graphic target width and extension type to use in the conversion process.

The dragged function is first. Add the following to the destination file:

    require 'fileutils'

    def dragged
       #
       # Turn on determinate mode.
       #
       $dz.determinate(true)

       #
       # Set the default return string to an error.
       #
       result = "You have to set the defaults first!"

       #
       # Get the data values.
       #
       dataDir = File.expand_path("~/Library/Application Support/Dropzone/Destination Data/")
       if File.directory?(dataDir)
          #
          # get the defaults.
          #
          $defaults = IO.readlines("#{dataDir}/CompressFiles.txt")
          $size = $defaults[0].split(":")[1].strip
          $ext = $defaults[1].split(":")[1].strip

          #
          # Process each image file.
          #
          total = $items.count

          #
          # Tell dropzone to start...
          #
          $dz.begin("Start compressing #{total} images...")

          #
          # Create the temporary directory for the originals.
          #
          tmpDir = File.dirname($items[0]) + "/tmp/"
          if ! File.directory?(tmpDir)
             #
             # Directory does not exist! Create it!
             #
             FileUtils.mkdir_p(tmpDir)
          end


          #
          # Index over all of the given images.
          #
          for index in 0 ... total
             #
             # Copy the original to the tmp directory. Rsync would be the     preferred
             # method, but it messes up the percentage graph on the user     interface.
             #
             # Rsync.do_copy($items[index], tmpDir, false)
             #
             FileUtils.copy_file($items[index], "#{tmpDir}#{File.basename($items[index])}")

             #
             # Create the new file name with the extension supplied by the user.
             #
             $newFilePath = "#{$items[index].chomp(File.extname($items[index]))}#{$ext}"

             #
             # Convert the image file.
             #
             output = `/usr/local/bin/convert  -background white -quality 90% -alpha background -alpha off +dither -colors 256 -flatten -transparent none -resize #{$size} \"#{$items[index]}\" \"#{$newFilePath}\";`

             #
             # If the conversion does not destroy the original, then remove the original.
             #
             if File.extname($items[index]) != $ext
                File.delete($items[index])
             end

             #
             # Tell Dropzone what percentage is done.
             #
             $dz.percent((((index + 1)*100)/total).to_i)
          end

          #
          # Set the results string to finished.
          #
          result = "Finished Compressing."
       end

       #
       # Tell the user that it is done.
       #
       $dz.finish(result)

       #
       # Finish out the dropzone protocal. If you want a url in the     clipboard, pass it
       # here. If you just want to copy text to the clipboard, use $dz.text()instead.
       # Either $dz.url() or $dz.text() has to be the last thing in the dragged method.
       #
       $dz.url(false)
    end

The first item declares any libraries that will be needed for the destination. Here, the fileutils library is needed to move files around. The program author recommends using the Rsync library that is pre-loaded into Dropzone, but it has a side effect of resetting the progress bar each time it is called. The author of Dropzone said that that effect will be selectable on or off in the next version of Dropzone.

The Rsync library will query the user if a file already exists where one is being moved or if higher permissions is required. The fileutils library will not and just give an error. Therefore, using the fileutils library requires more caution.

The dragged function first tells Dropzone that this action will take a determined amount of time. The $dz.determinate(true) function performs this. If you are uploading to the Internet or something similar, you would set this to false. If set to false, the progress meter will just display a scrolling effect across the entire dialog instead of trying to show progress.

Next, the result string is set to a default value for displaying to the user. If the next if..then statement is false, then it will be displayed.

The dataDir is set to the full address of the data directory to be used for storing the defaults for the destination to use. If that directory exists, the defaults are read for the graphic width and the extension to give the graphic. Otherwise, the user is told to set the preferences first.

Tip: The next version of Dropzone will have an API for working with a data directory.

The $items array is the list of files given to the dropzone in a drag action. This is a standard Ruby array and is set by the Dropzone program before calling the destination scripts.

The $dz.begin() function has to be called to display the progress bar with the text passed to the function. If this function is not called, the progress bar is not displayed and Dropzone does not know what state it is in.

Next, the tmp directory is created in the directory that contains the original graphics. The original graphic is copied here before any action is taken on them to perserve the original graphic in case the user does not like the result.

The for..end block is used to loop over every graphic file in the $items array. The actual conversion is done with the ImageMagick library’s convert function. Since this is not a standard library loaded on the the Mac OS X system, the user of the destination needs to install it. The best solution is to install Home Brew and use the command brew install imagemagick to install the library. That is easy to do and gives much better results than trying to compile and install it yourself!

After performing the graphic conversion and compression, the progress meter needs updated. That is what $dz.percent() does. You calculate the integer amount that is completed. The value should be between 0 and 100.

When the loop is finished, the result string is set to a descriptive message that is sent to the user using $dz.finish(). After that, the $dz.url() or $dz.text() functions have to be called. The $dz.url() can be either false or a proper URL string to be sent to the user and displayed in a browser. The $dz.text() is the same, except for the text given is passed directly to the clipboard (or pasteboard). One of these functions has to be called before exiting or Dropzone will not know if the destination is finished.

Next, the clicked function is defined. You can add this code to the script:

    def clicked
       #
       # The clicked handler should get the size and extension to use and
       # save it in the configuration file. Save data in the
       # ~/Library/Application Support/Dropzone/Destination Data/CompressFiles.txt
       #

       #
       # Set the default return string to the error condition.
       #
       result = "Sorry, you canceled out."

       #
       # Request the width of the graphic.
       #
       button1, width =`./CocoaDialog standard-inputbox --title "Compress Files: Graphic Width" --e --informative-text "What width? "`.split("\n")

       #
       # See if the user canceled out. Do not continue if they cancel.
       #
       if button1 != "2"
          #
          # Ask for the graphic file type to end up with.
          #
          button2, extnum =`./CocoaDialog dropdown  --title "Compress Files: Graphic Format" --text "What Graphic Format?" --items ".jpg" ".png" ".gif"  --button1 "Ok" --button2 "Cancel"`.split("\n")

          #
          # See if the user canceled out. Do not continue if they cancel.
          #
          if button2 != "2"
             #
             # Change the dropdown number to a string.
             #
             case extnum.to_i
             when 0
                ext = ".jpg"
             when 1
                ext = ".png"
             when 2
                ext = ".gif"
             end

             #
             # Make sure the data directory exists.
             #
             dataDir = File.expand_path("~/Library/Application Support/Dropzone/Destination Data/")
             if ! File.directory?(dataDir)
                #
                # Directory does not exist! Create it!
                #
                FileUtils.mkdir_p(dataDir)
             end

             #
             # Write the data file. Do not append, but delete and write fresh!
             #
             File.open("#{dataDir}/CompressFiles.txt", 'w') { |file| file.write("Size:#{width}\nExt:#{ext}") }

             #
             # Tell the user by setting the return string to what the user gave.
             #
             result = "Size: #{width} px, Ext: #{ext}"
          end
       end

       #
       # Tell the user that it is done.
       #
       $dz.finish(result)

       #
       # Finish out the dropzone protocal. If you want a url in the     clipboard, pass it
       # here. If you just want to copy text to the clipboard, use $dz.text() instead.
       # Either $dz.url() or $dz.text() has to be the last thing in the clicked method.
       #
       $dz.url(false)
    end

The clicked function simply uses the external program CocoaDialog to get the width target of the graphic and then again for the extension to use. CocoaDialog is included with Dropzone, but you can download and use it in your own scripts as well.

Once the information is obtained from the user, then the defaults directory is created if it does not exist and the preferences stored there in a data file called CompressFiles.txt. The dragged function reads this file.

Lastly, the preferences are echoed back to the user using the $dz.finish() function and calling the dz.url() function to end the process.

Testing

If you open the terminal to the ~/Library/Application Support/Dropzone/Destination Scripts/ directory, you will see a sub-directory called lib. Go to that directory and you can test the target. By typing:

ruby runner.rb CompressImage.dropzone clicked

You can test the clicked function of the destination. Likewise, you can type:

ruby runner.rb CompressImage.dropzone dragged <filename>

This will run the dragged function with the <filename> file. You have to press the enter key several times to get through the whole process. If you place puts function calls in the code to display variable values, you can use it to debug your code.

You can also see the output of a running destination by pressing Shift-Command-D on the Dropzone menu dropdown. Dropzone will then display the Output Console that will display anything the destination script outputs. A handy debugging feature.

In the Future

Dropzone is being actively developed and other programmers are creating destination files. You can view alternate destinations here or fork Dropzone’s GitHub account, add your destination file, and give a pull request.

Dropzone Dropdown Menu

Dropzone Dropdown Menu

The author, John Winter, told me that the new version will be out in a few months. You can get a sneek preview of version 3.0 above! Version 3.0 will have some improvements in the API and expansions to the user interface.

Conclusion

I hope you enjoy this destination as much as I do. I am using it everyday in my jobs. You can now take the basics here and experiment with your own. If you make a new destination, please share it in the comments below.

Advertisement