Generating a directory listing from an S3 bucket

Posted 11 June 2014

I was after a quick and easy way to generate a directory listing from an S3 bucket, but all of the scripts I found were horribly convoluted, and difficult to customise the resultant markup.

With this jQuery plugin, you can provide your own markup function, or use the default one provided which will generate a listing using UL.

/**
 * jQuery Plugin: S3 Browser
 *
 * A simple and lightweight jQuery plugin to generate a directly
 * listing of an Amazon S3 Bucket.
 *
 * @author Aidan Lister <aidan@php.net>
 * @author GreenJello <f.bagnardi@gmail.com>
 * @version 1.1.0
 */
(function ( $ ) {
    $.fn.s3browser = function(options) {
        var prefix = $(this).attr('data-prefix');
        var settings = $.extend({
            bucket: $(this).attr('data-bucket'),
            prefix: prefix,
        }, options);

        var s3 = new AWS.S3({params: {Bucket: settings.bucket}});

        var S3params = {
          Bucket: settings.bucket,
          Prefix: settings.prefix
        }

        var context = this
        s3.listObjects(S3params, function(error, data) {

          // Write any error to the context element
          if (error !== null) {
            context.text(error);
            return;
          }

          // Check for empty result
          if (data.Contents.length == 0) {
            context.text('No files found.');
            return;
          }

          // Our algorithm relies on files coming before folders
          data.Contents.sort(function(a, b) {
              return a.Key.split("/").length > b.Key.split("/").length;
          });

          // Build the heirarchical file map
          var fileStructure = {}
          jQuery.each(data.Contents, function(index, s3obj) {
              var pathSegments = s3obj.Key.substring(prefix.length).split("/");
              parseSegments(fileStructure, pathSegments, s3obj);
          });

          // Write the HTML
          var markup = createMarkup(fileStructure);
          context.html(markup);
        });


        // Recursively iterate all of the path segments in a single filename
        function parseSegments(fileStructure, rest, s3obj) {
            if (rest.length <= 2) {
                pushTo(fileStructure, rest[0], rest[1], s3obj);
            }
            else {
                var subStructure = fileStructure[rest[0]] || {};
                fileStructure[rest[0]] = subStructure;
                parseSegments(subStructure, rest.slice(1), s3obj);
            }
        }

        // Small wrapper to emulate a defaultdict
        function pushTo(fileStructure, key, value, s3obj) {
            if (!fileStructure[key]) {
                fileStructure[key] = [];
            }
            if (value) {
                fileStructure[key].push({
                  'label': value,
                  's3item': s3obj
                });
            }
        }

        // Recursive function to generate the folder markup.
        function createMarkup(folder) {
            var markup = '<ul>';
            for (var key in folder) {
                markup += '<li>';
                if ('label' in folder[key]) {
                  var s3item = folder[key]['s3item']
                  var url = s3.getSignedUrl('getObject', {Bucket: settings.bucket, Key: s3item.Key});
                  var size = (s3item.Size / 1024).toFixed(1)
                  markup += '<a href="'+url+'">'+folder[key]['label']+'</a> ('+size+'kb)';
                } else {
                  markup += key;
                  markup += createMarkup(folder[key]);
                }
                markup += '</li>';
            }
            markup += '</ul>';
            return markup;
        }

        return this;
    };
}( jQuery ));

You can grab the code from Github or on the jQuery plugins website.