Index: AmazonS3StreamWrapper.inc
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
Subsystem: com.intellij.openapi.diff.impl.patch.BaseRevisionTextPatchEP
<+><?php\n\n/**\n * @file\n * Drupal stream wrapper implementation for Amazon S3\n *\n * Implements DrupalStreamWrapperInterface to provide an Amazon S3 wrapper with\n * the s3:// prefix\n */\nclass AmazonS3StreamWrapper implements DrupalStreamWrapperInterface {\n\n  /**\n   * @var String Instance URI referenced as \"s3://bucket/key\"\n   */\n  protected $uri;\n\n  /**\n   * @var AmazonS3 S3 connection object\n   */\n  protected $s3 = null;\n\n  /**\n   * @var string S3 bucket name\n   */\n  protected $bucket;\n\n  /**\n   * @var string Domain we use to access files over http\n   */\n  protected $domain;\n\n  /**\n   * @var int Current read/write position\n   */\n  protected $position = 0;\n\n  /**\n   * @var int Total size of the object as returned by S3 (Content-length)\n   */\n  protected $object_size = 0;\n\n  /**\n   * @var string Object read/write buffer, typically a file\n   */\n  protected $buffer = null;\n\n  /**\n   * @var boolean Whether $buffer is active as a write buffer\n   */\n  protected $buffer_write = false;\n\n  /**\n   * @var int Buffer length\n   */\n  protected $buffer_length = 0;\n\n  /**\n   * @var array directory listing\n   */\n  protected $dir = array();\n\n  /**\n   * @var array Default map for determining file mime types\n   */\n  protected static $mapping = null;\n\n  /**\n   * @var boolean Whether local file metadata caching is on\n   */\n  protected $caching = FALSE;\n\n  /**\n   * @var array Map for files that should be delivered with a torrent URL.\n   */\n  protected $torrents = array();\n\n  /**\n   * @var array Map for files that should have their Content-disposition header\n   * set to force \"save as\".\n   */\n  protected $saveas = array();\n\n  /**\n   * @var array Map for files that should have a URL will be created that times\n   * out in a designated number of seconds.\n   */\n  protected $presigned_urls = array();\n\n  /**\n   * Object constructor\n   *\n   * Sets the bucket name\n   */\n  public function __construct() {\n    $this->bucket = $bucket = variable_get('amazons3_bucket', '');\n\n    // CNAME support for customising S3 URLs\n    if (variable_get('amazons3_cname', 0)) {\n      $domain = variable_get('amazons3_domain', '');\n      if(strlen($domain) > 0) {\n        $this->domain = 'http://' . $domain;\n      }\n      else {\n        $this->domain = 'http://' . $this->bucket;\n      }\n    }\n    else {\n      $this->domain = 'http://' . $this->bucket . '.s3.amazonaws.com';\n    }\n\n    // Check whether local file caching is turned on\n    if (variable_get('amazons3_cache', FALSE)) {\n      $this->caching = TRUE;\n    }\n\n    // Torrent list\n    $torrents = explode(\"\\n\", variable_get('amazons3_torrents', ''));\n    $torrents = array_map('trim', $torrents);\n    $torrents = array_filter($torrents, 'strlen');\n    $this->torrents = $torrents;\n\n\n    // Presigned url-list\n    $presigned_urls = explode(\"\\n\", variable_get('amazons3_presigned_urls', ''));\n    $presigned_urls = array_map('trim', $presigned_urls);\n    $presigned_urls = array_filter($presigned_urls, 'strlen');\n    $this->presigned_urls = array();\n    foreach ($presigned_urls as $presigned_url) {\n      // Check for an explicit key.\n      $matches = array();\n      if (preg_match('/(.*)\\|(.*)/', $presigned_url, $matches)) {\n        $this->presigned_urls[$matches[2]] = $matches[1];\n      }\n      else {\n        $this->presigned_urls[$presigned_url] = 60;\n      }\n    }\n\n    // Force \"save as\" list\n    $saveas = explode(\"\\n\", variable_get('amazons3_saveas', ''));\n    $saveas = array_map('trim', $saveas );\n    $saveas  = array_filter($saveas , 'strlen');\n    $this->saveas  = $saveas;\n\n  }\n\n\n  /**\n   * Sets the stream resource URI.\n   *\n   * URIs are formatted as \"s3://bucket/key\"\n   *\n   * @return\n   *   Returns the current URI of the instance.\n   */\n  public function setUri($uri) {\n    $this->uri = $uri;\n  }\n\n  /**\n   * Returns the stream resource URI.\n   *\n   * URIs are formatted as \"s3://bucket/key\"\n   *\n   * @return\n   *   Returns the current URI of the instance.\n   */\n  public function getUri() {\n    return $this->uri;\n  }\n\n  /**\n   * Returns a web accessible URL for the resource.\n   *\n   * In the format http://mybucket.amazons3.com/myfile.jpg\n   *\n   * @return\n   *   Returns a string containing a web accessible URL for the resource.\n   */\n  public function getExternalUrl() {\n\n    // Image styles support\n    // Delivers the first request to an image from the private file system\n    // otherwise it returns an external URL to an image that has not been\n    // created yet\n    $path = explode('/', $this->getLocalPath());\n    if ($path[0] == 'styles') {\n      if (!$this->_amazons3_get_object($this->uri, $this->caching)) {\n        array_shift($path);\n        return url('system/files/styles/' . implode('/', $path), array('absolute' => true));\n      }\n    }\n\n    $local_path = $this->getLocalPath();\n\n    $info = array(\n      'download_type' => 'http',\n      'presigned_url' => FALSE,\n      'presigned_url_timeout' => 60,\n      'response' => array(),\n    );\n\n    // Allow other modules to change the download link type.\n    $info = array_merge($info, module_invoke_all('amazons3_url_info', $local_path, $info));\n\n    // UI overrides\n    // Torrent URLs\n    if ($info['download_type'] != 'torrent') {\n      foreach ($this->torrents as $path) {\n        if (preg_match('#' . strtr($path, '#', '\\#') . '#', $local_path)) {\n          $info['download_type'] = 'torrent';\n          break;\n        }\n      }\n    }\n    // Presigned URLs\n    if (!$info['presigned_url']) {\n      foreach ($this->presigned_urls as $path => $timeout) {\n        if (preg_match('#' . strtr($path, '#', '\\#') . '#', $local_path)) {\n          $info['presigned_url'] = TRUE;\n          $info['presigned_url_timeout'] = $timeout;\n          break;\n        }\n      }\n    }\n    // Save as\n    if ($info['download_type'] != 'torrent') {\n      foreach ($this->saveas as $path) {\n        if (preg_match('#' . strtr($path, '#', '\\#') . '#', $local_path)) {\n          $info['response']['content-disposition'] = 'attachment; filename=' . basename($local_path);\n          break;\n        }\n      }\n    }\n\n    $timeout = ($info['presigned_url']) ? time() + $info['presigned_url_timeout'] : 0;\n    $torrent = ($info['download_type'] == 'torrent') ? TRUE : FALSE;\n    $response = ($info['presigned_url']) ? $info['response'] : array();\n    if ($info['presigned_url'] || $info['download_type'] != 'http' || !empty($info['response'])) {\n      $url = $this->getS3()->get_object_url($this->bucket, $local_path, $timeout, array('torrent' => $torrent, 'response' => $response));\n      return $url;\n    }\n\n    $url = $this->domain . '/' . $local_path;\n    return $url;\n  }\n\n  /**\n   * Determine a file's media type\n   *\n   * Uses Drupal's mimetype mappings. Returns 'application/octet-stream' if\n   * no match is found.\n   *\n   *  @return\n   *   Returns a string representing the file's MIME type\n   */\n  public static function getMimeType($uri, $mapping = NULL) {\n\n    // Load the default file map\n    if (!isset(self::$mapping)) {\n      include_once DRUPAL_ROOT . '/includes/file.mimetypes.inc';\n      self::$mapping = file_mimetype_mapping();\n    }\n\n    $extension = '';\n    $file_parts = explode('.', basename($uri));\n\n    // Remove the first part: a full filename should not match an extension.\n    array_shift($file_parts);\n\n    // Iterate over the file parts, trying to find a match.\n    // For my.awesome.image.jpeg, we try:\n    //   - jpeg\n    //   - image.jpeg, and\n    //   - awesome.image.jpeg\n    while ($additional_part = array_pop($file_parts)) {\n      $extension = strtolower($additional_part . ($extension ? '.' . $extension : ''));\n      if (isset(self::$mapping['extensions'][$extension])) {\n        return self::$mapping['mimetypes'][self::$mapping['extensions'][$extension]];\n      }\n    }\n\n    return 'application/octet-stream';\n  }\n\n  /**\n   * Changes permissions of the resource.\n   *\n   * This doesn't do anything for the moment so just returns FALSE;\n   *\n   * @param $mode\n   *   Integer value for the permissions. Consult PHP chmod() documentation\n   *   for more information.\n   *\n   * @return\n   *   Returns TRUE on success or FALSE on failure.\n   */\n  public function chmod($mode) {\n  /**  $modes = str_split($mode);\n    if($modes[0] == '0') {\n      array_shift($modes);\n    }\n    if(count($modes) != 3) {\n      return FALSE;\n    }\n\n    if(intval($modes[2]) >= 4) {\n      $acl = AmazonS3::ACL_PUBLIC;\n    }\n    else {\n      $acl = AmazonS3::ACL_PRIVATE;\n    }\n\n    $local_path = $this->getLocalPath();\n    if($this->_is_dir() && (substr($local_path, -1) != '/')) {\n      $local_path .= '/';\n    }\n\n    $response = $this->getS3()->set_object_acl($this->bucket, $local_path, $acl);\n    return $response->isOK();**/\n    return TRUE;\n  }\n\n  /**\n   * Returns canonical, absolute path of the resource.\n   *\n   * @return\n   *   Returns FALSE as this wrapper does not provide an implementation.\n   */\n  public function realpath() {\n    return FALSE;\n  }\n\n  /**\n   * Gets the name of the directory from a given path.\n   *\n   * This method is usually accessed through drupal_dirname(), which wraps\n   * around the normal PHP dirname() function, which does not support stream\n   * wrappers.\n   *\n   * @param $uri\n   *   An optional URI.\n   *\n   * @return\n   *   A string containing the directory name, or FALSE if not applicable.\n   *\n   * @see drupal_dirname()\n   */\n  public function dirname($uri = NULL) {\n   list($scheme, $target) = explode('://', $uri, 2);\n   $target  = $this->getTarget($uri);\n   $dirname = dirname($target);\n\n   if ($dirname == '.') {\n     $dirname = '';\n   }\n\n   return $scheme . '://' . $dirname;\n  }\n\n  /**\n   * Support for fopen(), file_get_contents(), file_put_contents() etc.\n   *\n   * @param $uri\n   *   A string containing the URI to the file to open.\n   * @param $mode\n   *   The file mode (\"r\", \"wb\" etc.).\n   * @param $options\n   *   A bit mask of STREAM_USE_PATH and STREAM_REPORT_ERRORS.\n   * @param $opened_path\n   *   A string containing the path actually opened.\n   *\n   * @return\n   *   Returns TRUE if file was opened successfully.\n   *\n   * @see http://php.net/manual/en/streamwrapper.stream-open.php\n   */\n  public function stream_open($uri, $mode, $options, &$opened_path) {\n    $this->uri = $uri;\n\n    // if this stream is being opened for writing, clear the object buffer\n    // Return true as we'll create the object on fflush call\n    if (strpbrk($mode, 'wax')) {\n      $this->clearBuffer();\n      $this->write_buffer = TRUE;\n      return TRUE;\n    }\n    $metadata = $this->_amazons3_get_object($uri, $this->caching);\n    if ($metadata) {\n      $this->clearBuffer();\n      $this->write_buffer = false;\n      $this->object_size = $metadata['filesize'];\n      return TRUE;\n    }\n\n    return FALSE;\n  }\n\n  /**\n   * Support for fclose().\n   *\n   * Clears the object buffer and returns TRUE\n   *\n   * @return\n   *   TRUE\n   *\n   * @see http://php.net/manual/en/streamwrapper.stream-close.php\n   */\n  public function stream_close() {\n    $this->clearBuffer();\n    return TRUE;\n  }\n\n  /**\n   * Support for flock().\n   *\n   * @param $operation\n   *   One of the following:\n   *   - LOCK_SH to acquire a shared lock (reader).\n   *   - LOCK_EX to acquire an exclusive lock (writer).\n   *   - LOCK_UN to release a lock (shared or exclusive).\n   *   - LOCK_NB if you don't want flock() to block while locking (not\n   *     supported on Windows).\n   *\n   * @return\n   *   returns TRUE if lock was successful\n   *\n   * @see http://php.net/manual/en/streamwrapper.stream-lock.php\n   */\n  public function stream_lock($operation) {\n    return false;\n  }\n\n  /**\n   * Support for fread(), file_get_contents() etc.\n   *\n   * @param $count\n   *   Maximum number of bytes to be read.\n   *\n   * @return\n   *   The string that was read, or FALSE in case of an error.\n   *\n   * @see http://php.net/manual/en/streamwrapper.stream-read.php\n   */\n  public function stream_read($count) {\n    // make sure that count doesn't exceed object size\n    if ($count + $this->position > $this->object_size) {\n      $count = $this->object_size - $this->position;\n    }\n    $data = '';\n    if ($count > 0) {\n      $range_end = $this->position + $count - 1;\n      if ($range_end > $this->buffer_length) {\n        $opts = array(\n          'range' => $this->position . '-' . $range_end,\n        );\n        $response = $this->getS3()->get_object($this->bucket, $this->getLocalPath($this->uri), $opts);\n        if ($response->isOK()) {\n          $this->buffer .= $response->body;\n          $this->buffer_length += strlen($response->body);\n        }\n      }\n      $data = substr($this->buffer, $this->position, $count);\n      $this->position += strlen($data);\n    }\n    return $data;\n  }\n\n  /**\n   * Support for fwrite(), file_put_contents() etc.\n   *\n   * @param $data\n   *   The string to be written.\n   *\n   * @return\n   *   The number of bytes written (integer).\n   *\n   * @see http://php.net/manual/en/streamwrapper.stream-write.php\n   */\n  public function stream_write($data) {\n    $data_length = strlen($data);\n    $this->buffer .= $data;\n    $this->buffer_length += $data_length;\n    $this->position += $data_length;\n\n    return $data_length;\n  }\n\n  /**\n   * Support for feof().\n   *\n   * @return\n   *   TRUE if end-of-file has been reached.\n   *\n   * @see http://php.net/manual/en/streamwrapper.stream-eof.php\n   */\n  public function stream_eof() {\n    if (!$this->uri) {\n        return true;\n    }\n\n    return ($this->position >= $this->object_size);\n  }\n\n  /**\n   * Support for fseek().\n   *\n   * @param $offset\n   *   The byte offset to got to.\n   * @param $whence\n   *   SEEK_SET, SEEK_CUR, or SEEK_END.\n   *\n   * @return\n   *   TRUE on success.\n   *\n   * @see http://php.net/manual/en/streamwrapper.stream-seek.php\n   */\n  public function stream_seek($offset, $whence) {\n    switch($whence) {\n      case SEEK_CUR:\n        // Set position to current location plus $offset\n        $new_position = $this->position + $offset;\n        break;\n      case SEEK_END:\n        // Set position to eof plus $offset\n        $new_position = $this->object_size + $offset;\n        break;\n      case SEEK_SET:\n      default:\n        // Set position equal to $offset\n        $new_position = $offset;\n        break;\n    }\n\n    $ret = ($new_position >= 0 && $new_position <= $this->object_size);\n    if ($ret) {\n      $this->position = $new_position;\n    }\n    return $ret;\n  }\n\n  /**\n   * Support for fflush(). Flush current cached stream data to storage.\n   *\n   * @return\n   *   TRUE if data was successfully stored (or there was no data to store).\n   *\n   * @see http://php.net/manual/en/streamwrapper.stream-flush.php\n   */\n  public function stream_flush() {\n    if($this->write_buffer) {\n      $response = $this->getS3()->create_object($this->bucket, $this->getLocalPath(), array(\n        'body' => $this->buffer,\n        'acl' => AmazonS3::ACL_PUBLIC,\n        'contentType' => AmazonS3StreamWrapper::getMimeType($this->uri),\n      ));\n      if($response->isOK()) {\n        return TRUE;\n      }\n    }\n    $this->clearBuffer();\n    return FALSE;\n  }\n\n  /**\n   * Support for ftell().\n   *\n   * @return\n   *   The current offset in bytes from the beginning of file.\n   *\n   * @see http://php.net/manual/en/streamwrapper.stream-tell.php\n   */\n  public function stream_tell() {\n    return $this->position;\n  }\n\n  /**\n   * Support for fstat().\n   *\n   * @return\n   *   An array with file status, or FALSE in case of an error - see fstat()\n   *   for a description of this array.\n   *\n   * @see http://php.net/manual/en/streamwrapper.stream-stat.php\n   */\n  public function stream_stat() {\n    return $this->_stat();\n  }\n\n  /**\n   * Support for unlink().\n   *\n   * @param $uri\n   *   A string containing the uri to the resource to delete.\n   *\n   * @return\n   *   TRUE if resource was successfully deleted.\n   *\n   * @see http://php.net/manual/en/streamwrapper.unlink.php\n   */\n  public function unlink($uri) {\n    $response = $this->getS3()->delete_object($this->bucket, $this->getLocalPath($uri));\n    if($response->isOK()) {\n      // Delete from cache\n      db_delete('amazons3_file')\n        ->condition('uri', $uri)\n        ->execute();\n      return TRUE;\n    }\n    return FALSE;\n  }\n\n  /**\n   * Support for rename().\n   *\n   * @param $from_uri,\n   *   The uri to the file to rename.\n   * @param $to_uri\n   *   The new uri for file.\n   *\n   * @return\n   *   TRUE if file was successfully renamed.\n   *\n   * @see http://php.net/manual/en/streamwrapper.rename.php\n   */\n  public function rename($from_uri, $to_uri) {\n    $from = $this->getLocalPath($from_uri);\n    $to = $this->getLocalPath($to_uri);\n\n    /**\n     * @todo Finish this...\n    if ($this->getS3()->if_object_exists($this->bucket, $from) && !$this->getS3()->if_object_exists($this->bucket, $to)) {\n      // get a lock\n    }\n    **/\n\n    return FALSE;\n  }\n\n  /**\n   * Returns the local writable target of the resource within the stream.\n   *\n   * This function should be used in place of calls to realpath() or similar\n   * functions when attempting to determine the location of a file. While\n   * functions like realpath() may return the location of a read-only file, this\n   * method may return a URI or path suitable for writing that is completely\n   * separate from the URI used for reading.\n   *\n   * @param $uri\n   *   Optional URI.\n   *\n   * @return\n   *   Returns a string representing a location suitable for writing of a file,\n   *   or FALSE if unable to write to the file such as with read-only streams.\n   */\n  protected function getTarget($uri = NULL) {\n    if (!isset($uri)) {\n      $uri = $this->uri;\n    }\n\n    list($scheme, $target) = explode('://', $uri, 2);\n\n    // Remove erroneous leading or trailing forward-slashes and backslashes.\n    return trim($target, '\\/');\n  }\n\n  /**\n   * Support for mkdir().\n   *\n   * @param $uri\n   *   A string containing the URI to the directory to create.\n   * @param $mode\n   *   Permission flags - see mkdir().\n   * @param $options\n   *   A bit mask of STREAM_REPORT_ERRORS and STREAM_MKDIR_RECURSIVE.\n   *\n   * @return\n   *   TRUE if directory was successfully created.\n   *\n   * @see http://php.net/manual/en/streamwrapper.mkdir.php\n   */\n  public function mkdir($uri, $mode, $options) {\n\n    $recursive = (bool) ($options & STREAM_MKDIR_RECURSIVE);\n    $localpath = $this->getLocalPath($uri);\n\n    // s3 has no concept of directories, but we emulate it by creating an\n    // object of size 0 with a trailing \"/\"\n    $response = $this->getS3()->create_object($this->bucket, $localpath . '/', array('body' => ''));\n    if($response->isOk()) {\n      return TRUE;\n    }\n    return FALSE;\n  }\n\n  /**\n   * Support for rmdir().\n   *\n   * @param $uri\n   *   A string containing the URI to the directory to delete.\n   * @param $options\n   *   A bit mask of STREAM_REPORT_ERRORS.\n   *\n   * @return\n   *   TRUE if directory was successfully removed.\n   *\n   * @see http://php.net/manual/en/streamwrapper.rmdir.php\n   */\n  public function rmdir($uri, $options) {\n    $localpath = $this->getLocalPath($uri);\n    $s3 = $this->getS3();\n\n    $objects = $s3->get_object_list($this->bucket, array('prefix' => $localpath));\n    if (gettype($objects) === 'array') {\n      $or = db_or();\n      foreach($objects as $object) {\n        $s3->batch()->delete_object($this->bucket, $object);\n        // Delete from cache\n        $object_uri = 's3://' . rtrim($object,'/');\n        $or->condition('uri', $object_uri, '=');\n      }\n      db_delete('amazons3_file')->condition($or)->execute();\n      $responses = $s3->batch()->send();\n\n      if($responses->areOK()) {\n        return TRUE;\n      }\n    }\n    return FALSE;\n  }\n\n  /**\n   * Support for stat().\n   *\n   * @param $uri\n   *   A string containing the URI to get information about.\n   * @param $flags\n   *   A bit mask of STREAM_URL_STAT_LINK and STREAM_URL_STAT_QUIET.\n   *\n   * @return\n   *   An array with file status, or FALSE in case of an error - see fstat()\n   *   for a description of this array.\n   *\n   * @see http://php.net/manual/en/streamwrapper.url-stat.php\n   */\n  public function url_stat($uri, $flags) {\n    return $this->_stat($uri);\n  }\n\n  /**\n   * Support for opendir().\n   *\n   * @param $uri\n   *   A string containing the URI to the directory to open.\n   * @param $options\n   *   Unknown (parameter is not documented in PHP Manual).\n   *\n   * @return\n   *   TRUE on success.\n   *\n   * @see http://php.net/manual/en/streamwrapper.dir-opendir.php\n   */\n  public function dir_opendir($uri, $options) {\n    if ($uri == null) {\n      return FALSE;\n    }\n    else if(!$this->_is_dir($uri)) {\n      return FALSE;\n    }\n\n    $this->dir = array();\n    $path = $this->getLocalPath($uri);\n    $truncated = TRUE;\n    $marker = '';\n    if(strlen($path) == 0) {\n      $prefix = $path;\n    }\n    else {\n      $prefix = $path . '/';\n    }\n    $prefix_length = strlen($prefix);\n\n    while($truncated) {\n      $response = $this->getS3()->list_objects($this->bucket, array(\n          'delimiter' => '/',\n          'prefix' => $prefix,\n          'marker' => urlencode($marker),\n      ));\n      if ($response->isOK()) {\n\n        $this->dir[] = '.';\n        $this->dir[] = '..';\n\n        // Folders\n        if (isset($response->body->CommonPrefixes)) {\n          foreach($response->body->CommonPrefixes as $prefix) {\n            $marker = substr($prefix->Prefix, $prefix_length, strlen($prefix->Prefix) - $prefix_length - 1);\n            if(strlen($marker) > 0) {\n              $this->dir[] = $marker;\n            }\n          }\n        }\n\n        // Files\n        if(isset($response->body->Contents)) {\n          $contents = $response->body->to_stdClass()->Contents;\n          if (!is_array($contents)) {\n            $contents = array($contents);\n          }\n\n          foreach($contents as $content) {\n            $key = $content->Key;\n            if(substr_compare($key, '/', -1, 1) !== 0) {\n              $marker = basename($key);\n              $this->dir[] = $marker;\n            }\n          }\n        }\n\n        if(!isset($response->body->IsTruncated) || $response->body->IsTruncated == 'false') {\n          $truncated = FALSE;\n        }\n      }\n      else {\n        return FALSE;\n      }\n    }\n    return TRUE;\n  }\n\n  /**\n   * Support for readdir().\n   *\n   * @return\n   *   The next filename, or FALSE if there are no more files in the directory.\n   *\n   * @see http://php.net/manual/en/streamwrapper.dir-readdir.php\n   */\n  public function dir_readdir() {\n    $filename = current($this->dir);\n    if ($filename !== false) {\n        next($this->dir);\n    }\n    return $filename;\n  }\n\n  /**\n   * Support for rewinddir().\n   *\n   * @return\n   *   TRUE on success.\n   *\n   * @see http://php.net/manual/en/streamwrapper.dir-rewinddir.php\n   */\n  public function dir_rewinddir() {\n    reset($this->dir);\n    return TRUE;\n  }\n\n  /**\n   * Support for closedir().\n   *\n   * @return\n   *   TRUE on success.\n   *\n   * @see http://php.net/manual/en/streamwrapper.dir-closedir.php\n   */\n  public function dir_closedir() {\n    $this->dir = array();\n    return TRUE;\n  }\n\n  /**\n   * Return the local filesystem path.\n   *\n   * @param $uri\n   *   Optional URI, supplied when doing a move or rename.\n   */\n  protected function getLocalPath($uri = NULL) {\n    if (!isset($uri)) {\n      $uri = $this->uri;\n    }\n\n    $path  = str_replace('s3://', '', $uri);\n    $path = trim($path, '/');\n    return $path;\n  }\n\n  /**\n   * Gets the path that the wrapper is responsible for.\n   *\n   * @return\n   *   String specifying the path.\n   */\n  public function getDirectoryPath() {\n    return $this->domain;\n  }\n\n  /**\n   * Flush the object buffers\n   */\n  protected function clearBuffer() {\n    $this->position = 0;\n    $this->object_size = 0;\n    $this->buffer = null;\n    $this->buffer_write = false;\n    $this->buffer_length = 0;\n  }\n\n  /**\n   * Get the S3 connection object\n   *\n   * @return\n   *   S3 connection object (AmazonS3)\n   *\n   * @see http://docs.amazonwebservices.com/AWSSDKforPHP/latest/#i=AmazonS3\n   */\n  protected function getS3() {\n    if($this->s3 == null) {\n      $bucket = variable_get('amazons3_bucket', '');\n\n      if(!libraries_load('awssdk') && !isset($bucket)) {\n        drupal_set_message('Unable to load the AWS SDK. Please check you have installed the library correctly and configured your S3 credentials.'. 'error');\n      }\n      else if(!isset($bucket)) {\n        drupal_set_message('Bucket name not configured.'. 'error');\n      }\n      else {\n        try {\n         $this->s3 = new AmazonS3();\n         $this->bucket = $bucket;\n        }\n        catch(RequestCore_Exception $e){\n          drupal_set_message('There was a problem connecting to S3', 'error');\n        }\n        catch(Exception $e) {\n          drupal_set_message('There was a problem using S3: ' . $e->getMessage(), 'error');\n        }\n      }\n    }\n    return $this->s3;\n  }\n\n  /**\n   * Get file status\n   *\n   * @return\n   *   An array with file status, or FALSE in case of an error - see fstat()\n   *   for a description of this array.\n   *\n   * @see http://php.net/manual/en/streamwrapper.stream-stat.php\n   */\n  protected function _stat($uri = NULL) {\n    if(!isset($uri)) {\n      $uri = $this->uri;\n    }\n    $metadata = $this->_amazons3_get_object($uri, $this->caching);\n    if ($metadata) {\n      $stat = array();\n      $stat[0] = $stat['dev'] = 0;\n      $stat[1] = $stat['ino'] = 0;\n      $stat[2] = $stat['mode'] = $metadata['mode'];\n      $stat[3] = $stat['nlink'] = 0;\n      $stat[4] = $stat['uid'] = 0;\n      $stat[5] = $stat['gid'] = 0;\n      $stat[6] = $stat['rdev'] = 0;\n      $stat[7] = $stat['size'] = 0;\n      $stat[8] = $stat['atime'] = 0;\n      $stat[9] = $stat['mtime'] = 0;\n      $stat[10] = $stat['ctime'] = 0;\n      $stat[11] = $stat['blksize'] = 0;\n      $stat[12] = $stat['blocks'] = 0;\n\n      if (!$metadata['dir']) {\n        $stat[4] = $stat['uid'] = $metadata['uid'];\n        $stat[7] = $stat['size'] = $metadata['filesize'];\n        $stat[8] = $stat['atime'] = $metadata['timestamp'];\n        $stat[9] = $stat['mtime'] = $metadata['timestamp'];\n        $stat[10] = $stat['ctime'] = $metadata['timestamp'];\n      }\n      return $stat;\n    }\n    return FALSE;\n}\n\n\n/**\n * Determine whether the $uri is a directory\n *\n   * @param $uri\n   *   A string containing the uri to the resource to check. If none is given\n   *   defaults to $this->uri\n   *\n   * @return\n   *   TRUE if the resource is a directory\n   */\n  protected function _is_dir($uri = null) {\n    if($uri == null) {\n      $uri = $this->uri;\n    }\n    if($uri != null) {\n      $path = $this->getLocalPath($uri);\n      $response = $this->getS3()->list_objects($this->bucket, array(\n          'prefix' => $path . '/',\n          'max-keys' => 1,\n      ));\n      if($response && isset($response->body->Contents->Key)) {\n        return TRUE;\n      }\n    }\n    return FALSE;\n  }\n\n  /**\n   * CACHING FUNCTIONS\n   */\n\n  /**\n   * Try to fetch an object from the metadata cache, otherwise fetch it's\n   * info from S3 and populate the cache.\n   *\n   * @param uri\n   *   A string containing the uri of the resource to check.\n   * @param $cach\n   *   A boolean representing whether to check the cache for file information.\n   *\n   *  @return\n   *    An array if the $uri exists, otherwise FALSE.\n   */\n  protected function _amazons3_get_object($uri, $cache = TRUE) {\n    $uri = rtrim($uri,'/');\n\n    if ($cache) {\n      $metadata = $this->_amazons3_get_cache($uri);\n      if ($metadata) {\n        return $metadata;\n      }\n    }\n\n    $is_dir = $this->_is_dir($uri);\n    $metadata = NULL;\n    if ($is_dir) {\n      $mode = 0040000; // S_IFDIR indicating directory\n      $mode |= 0777;\n      $metadata = array(\n        'uri' => $uri,\n        'dir' => 1,\n        'mode' => $mode,\n      );\n    }\n    else {\n      $response = $this->getS3()->get_object_metadata($this->bucket, $this->getLocalPath($uri));\n      if ($response) {\n        $metadata = $this->_amazons3_format_response($uri, $response);\n        $metadata['dir'] = 0;\n        $metadata['mode'] = 0100000; // S_IFREG indicating file\n        $metadata['mode'] |= 0777; // everything is writeable\n      }\n    }\n    if (is_array($metadata)) {\n      // Save to the cache\n      db_merge('amazons3_file')\n        ->key(array('uri' => $metadata['uri']))\n        ->fields($metadata)\n        ->execute();\n      return $metadata;\n    }\n    return FALSE;\n  }\n\n  /**\n   * Fetch an object from the local metadata cache\n   *\n   * @param uri\n   *  A string containing the uri of the resource to check.\n   *\n   *  @return\n   *    An array if the $uri is in the cache, otherwise FALSE\n   */\n  protected function _amazons3_get_cache($uri) {\n    // Check cache for existing object.\n    $result = db_query(\"SELECT * FROM {amazons3_file} WHERE uri = :uri\", array(\n      ':uri' => $uri,\n    ));\n    $record = $result->fetchAssoc();\n    if ($record) {\n      return $record;\n    }\n    return FALSE;\n  }\n\n  /**\n   * Format returned file information from S3 into an array\n   *\n   * @param $uri\n   *   A string containing the uri of the resource to check.\n   * @param $response\n   *   An array containing the collective metadata for the Amazon S3 object\n   *\n   * @return\n   *   An array containing formatted metadata\n   */\n  protected function _amazons3_format_response($uri, $response) {\n    $metadata = array('uri' => $uri);\n    if (isset($response['Size'])) {\n      $metadata['filesize'] = $response['Size'];\n    }\n    if (isset($response['LastModified'])) {\n      $metadata['timestamp'] = date('U', strtotime((string) $response['LastModified']));\n    }\n    if (isset($response['Owner']['ID'])) {\n      $metadata['uid'] = (string) $response['Owner']['ID'];\n    }\n    return $metadata;\n  }\n}\n
===================================================================
--- AmazonS3StreamWrapper.inc	(revision 87507abba84c6e3a4f92dccc95fdf7319c8bed42)
+++ AmazonS3StreamWrapper.inc	(revision )
@@ -17,7 +17,7 @@
   /**
    * @var AmazonS3 S3 connection object
    */
-  protected $s3 = null;
+  protected $s3 = NULL;
 
   /**
    * @var string S3 bucket name
@@ -42,12 +42,12 @@
   /**
    * @var string Object read/write buffer, typically a file
    */
-  protected $buffer = null;
+  protected $buffer = NULL;
 
   /**
    * @var boolean Whether $buffer is active as a write buffer
    */
-  protected $buffer_write = false;
+  protected $buffer_write = FALSE;
 
   /**
    * @var int Buffer length
@@ -62,7 +62,7 @@
   /**
    * @var array Default map for determining file mime types
    */
-  protected static $mapping = null;
+  protected static $mapping = NULL;
 
   /**
    * @var boolean Whether local file metadata caching is on
@@ -91,13 +91,14 @@
    *
    * Sets the bucket name
    */
-  public function __construct() {
+  public function __construct()
+  {
     $this->bucket = $bucket = variable_get('amazons3_bucket', '');
 
     // CNAME support for customising S3 URLs
     if (variable_get('amazons3_cname', 0)) {
       $domain = variable_get('amazons3_domain', '');
-      if(strlen($domain) > 0) {
+      if (strlen($domain) > 0) {
         $this->domain = 'http://' . $domain;
       }
       else {
@@ -138,9 +139,9 @@
 
     // Force "save as" list
     $saveas = explode("\n", variable_get('amazons3_saveas', ''));
-    $saveas = array_map('trim', $saveas );
+    $saveas = array_map('trim', $saveas);
-    $saveas  = array_filter($saveas , 'strlen');
+    $saveas = array_filter($saveas, 'strlen');
-    $this->saveas  = $saveas;
+    $this->saveas = $saveas;
 
   }
 
@@ -153,7 +154,8 @@
    * @return
    *   Returns the current URI of the instance.
    */
-  public function setUri($uri) {
+  public function setUri($uri)
+  {
     $this->uri = $uri;
   }
 
@@ -165,7 +167,8 @@
    * @return
    *   Returns the current URI of the instance.
    */
-  public function getUri() {
+  public function getUri()
+  {
     return $this->uri;
   }
 
@@ -177,7 +180,8 @@
    * @return
    *   Returns a string containing a web accessible URL for the resource.
    */
-  public function getExternalUrl() {
+  public function getExternalUrl()
+  {
 
     // Image styles support
     // Delivers the first request to an image from the private file system
@@ -187,7 +191,7 @@
     if ($path[0] == 'styles') {
       if (!$this->_amazons3_get_object($this->uri, $this->caching)) {
         array_shift($path);
-        return url('system/files/styles/' . implode('/', $path), array('absolute' => true));
+        return url('system/files/styles/' . implode('/', $path), array('absolute' => TRUE));
       }
     }
 
@@ -237,7 +241,10 @@
     $torrent = ($info['download_type'] == 'torrent') ? TRUE : FALSE;
     $response = ($info['presigned_url']) ? $info['response'] : array();
     if ($info['presigned_url'] || $info['download_type'] != 'http' || !empty($info['response'])) {
-      $url = $this->getS3()->get_object_url($this->bucket, $local_path, $timeout, array('torrent' => $torrent, 'response' => $response));
+      $url = $this->getS3()->get_object_url($this->bucket, $local_path, $timeout, array(
+        'torrent' => $torrent,
+        'response' => $response
+      ));
       return $url;
     }
 
@@ -251,10 +258,11 @@
    * Uses Drupal's mimetype mappings. Returns 'application/octet-stream' if
    * no match is found.
    *
-   *  @return
+   * @return
    *   Returns a string representing the file's MIME type
    */
-  public static function getMimeType($uri, $mapping = NULL) {
+  public static function getMimeType($uri, $mapping = NULL)
+  {
 
     // Load the default file map
     if (!isset(self::$mapping)) {
@@ -295,25 +303,26 @@
    * @return
    *   Returns TRUE on success or FALSE on failure.
    */
-  public function chmod($mode) {
+  public function chmod($mode)
+  {
-  /**  $modes = str_split($mode);
+    /**  $modes = str_split($mode);
     if($modes[0] == '0') {
-      array_shift($modes);
+    array_shift($modes);
     }
     if(count($modes) != 3) {
-      return FALSE;
+    return FALSE;
     }
 
     if(intval($modes[2]) >= 4) {
-      $acl = AmazonS3::ACL_PUBLIC;
+    $acl = AmazonS3::ACL_PUBLIC;
     }
     else {
-      $acl = AmazonS3::ACL_PRIVATE;
+    $acl = AmazonS3::ACL_PRIVATE;
     }
 
     $local_path = $this->getLocalPath();
     if($this->_is_dir() && (substr($local_path, -1) != '/')) {
-      $local_path .= '/';
+    $local_path .= '/';
     }
 
     $response = $this->getS3()->set_object_acl($this->bucket, $local_path, $acl);
@@ -327,7 +336,8 @@
    * @return
    *   Returns FALSE as this wrapper does not provide an implementation.
    */
-  public function realpath() {
+  public function realpath()
+  {
     return FALSE;
   }
 
@@ -346,16 +356,17 @@
    *
    * @see drupal_dirname()
    */
-  public function dirname($uri = NULL) {
+  public function dirname($uri = NULL)
+  {
-   list($scheme, $target) = explode('://', $uri, 2);
+    list($scheme, $target) = explode('://', $uri, 2);
-   $target  = $this->getTarget($uri);
+    $target = $this->getTarget($uri);
-   $dirname = dirname($target);
+    $dirname = dirname($target);
 
-   if ($dirname == '.') {
-     $dirname = '';
-   }
+    if ($dirname == '.') {
+      $dirname = '';
+    }
 
-   return $scheme . '://' . $dirname;
+    return $scheme . '://' . $dirname;
   }
 
   /**
@@ -375,7 +386,8 @@
    *
    * @see http://php.net/manual/en/streamwrapper.stream-open.php
    */
-  public function stream_open($uri, $mode, $options, &$opened_path) {
+  public function stream_open($uri, $mode, $options, &$opened_path)
+  {
     $this->uri = $uri;
 
     // if this stream is being opened for writing, clear the object buffer
@@ -388,7 +400,7 @@
     $metadata = $this->_amazons3_get_object($uri, $this->caching);
     if ($metadata) {
       $this->clearBuffer();
-      $this->write_buffer = false;
+      $this->write_buffer = FALSE;
       $this->object_size = $metadata['filesize'];
       return TRUE;
     }
@@ -406,7 +418,8 @@
    *
    * @see http://php.net/manual/en/streamwrapper.stream-close.php
    */
-  public function stream_close() {
+  public function stream_close()
+  {
     $this->clearBuffer();
     return TRUE;
   }
@@ -427,8 +440,9 @@
    *
    * @see http://php.net/manual/en/streamwrapper.stream-lock.php
    */
-  public function stream_lock($operation) {
-    return false;
+  public function stream_lock($operation)
+  {
+    return FALSE;
   }
 
   /**
@@ -442,7 +456,8 @@
    *
    * @see http://php.net/manual/en/streamwrapper.stream-read.php
    */
-  public function stream_read($count) {
+  public function stream_read($count)
+  {
     // make sure that count doesn't exceed object size
     if ($count + $this->position > $this->object_size) {
       $count = $this->object_size - $this->position;
@@ -477,7 +492,8 @@
    *
    * @see http://php.net/manual/en/streamwrapper.stream-write.php
    */
-  public function stream_write($data) {
+  public function stream_write($data)
+  {
     $data_length = strlen($data);
     $this->buffer .= $data;
     $this->buffer_length += $data_length;
@@ -494,9 +510,10 @@
    *
    * @see http://php.net/manual/en/streamwrapper.stream-eof.php
    */
-  public function stream_eof() {
+  public function stream_eof()
+  {
     if (!$this->uri) {
-        return true;
+      return TRUE;
     }
 
     return ($this->position >= $this->object_size);
@@ -515,8 +532,9 @@
    *
    * @see http://php.net/manual/en/streamwrapper.stream-seek.php
    */
-  public function stream_seek($offset, $whence) {
+  public function stream_seek($offset, $whence)
+  {
-    switch($whence) {
+    switch ($whence) {
       case SEEK_CUR:
         // Set position to current location plus $offset
         $new_position = $this->position + $offset;
@@ -547,14 +565,15 @@
    *
    * @see http://php.net/manual/en/streamwrapper.stream-flush.php
    */
-  public function stream_flush() {
+  public function stream_flush()
+  {
-    if($this->write_buffer) {
+    if ($this->write_buffer) {
       $response = $this->getS3()->create_object($this->bucket, $this->getLocalPath(), array(
         'body' => $this->buffer,
         'acl' => AmazonS3::ACL_PUBLIC,
         'contentType' => AmazonS3StreamWrapper::getMimeType($this->uri),
       ));
-      if($response->isOK()) {
+      if ($response->isOK()) {
         return TRUE;
       }
     }
@@ -570,7 +589,8 @@
    *
    * @see http://php.net/manual/en/streamwrapper.stream-tell.php
    */
-  public function stream_tell() {
+  public function stream_tell()
+  {
     return $this->position;
   }
 
@@ -583,7 +603,8 @@
    *
    * @see http://php.net/manual/en/streamwrapper.stream-stat.php
    */
-  public function stream_stat() {
+  public function stream_stat()
+  {
     return $this->_stat();
   }
 
@@ -598,9 +619,10 @@
    *
    * @see http://php.net/manual/en/streamwrapper.unlink.php
    */
-  public function unlink($uri) {
+  public function unlink($uri)
+  {
     $response = $this->getS3()->delete_object($this->bucket, $this->getLocalPath($uri));
-    if($response->isOK()) {
+    if ($response->isOK()) {
       // Delete from cache
       db_delete('amazons3_file')
         ->condition('uri', $uri)
@@ -623,16 +645,17 @@
    *
    * @see http://php.net/manual/en/streamwrapper.rename.php
    */
-  public function rename($from_uri, $to_uri) {
+  public function rename($from_uri, $to_uri)
+  {
     $from = $this->getLocalPath($from_uri);
     $to = $this->getLocalPath($to_uri);
 
     /**
      * @todo Finish this...
     if ($this->getS3()->if_object_exists($this->bucket, $from) && !$this->getS3()->if_object_exists($this->bucket, $to)) {
-      // get a lock
+    // get a lock
     }
-    **/
+     **/
 
     return FALSE;
   }
@@ -653,7 +676,8 @@
    *   Returns a string representing a location suitable for writing of a file,
    *   or FALSE if unable to write to the file such as with read-only streams.
    */
-  protected function getTarget($uri = NULL) {
+  protected function getTarget($uri = NULL)
+  {
     if (!isset($uri)) {
       $uri = $this->uri;
     }
@@ -679,7 +703,8 @@
    *
    * @see http://php.net/manual/en/streamwrapper.mkdir.php
    */
-  public function mkdir($uri, $mode, $options) {
+  public function mkdir($uri, $mode, $options)
+  {
 
     $recursive = (bool) ($options & STREAM_MKDIR_RECURSIVE);
     $localpath = $this->getLocalPath($uri);
@@ -687,7 +712,7 @@
     // s3 has no concept of directories, but we emulate it by creating an
     // object of size 0 with a trailing "/"
     $response = $this->getS3()->create_object($this->bucket, $localpath . '/', array('body' => ''));
-    if($response->isOk()) {
+    if ($response->isOk()) {
       return TRUE;
     }
     return FALSE;
@@ -706,23 +731,24 @@
    *
    * @see http://php.net/manual/en/streamwrapper.rmdir.php
    */
-  public function rmdir($uri, $options) {
+  public function rmdir($uri, $options)
+  {
     $localpath = $this->getLocalPath($uri);
     $s3 = $this->getS3();
 
     $objects = $s3->get_object_list($this->bucket, array('prefix' => $localpath));
     if (gettype($objects) === 'array') {
       $or = db_or();
-      foreach($objects as $object) {
+      foreach ($objects as $object) {
         $s3->batch()->delete_object($this->bucket, $object);
         // Delete from cache
-        $object_uri = 's3://' . rtrim($object,'/');
+        $object_uri = 's3://' . rtrim($object, '/');
         $or->condition('uri', $object_uri, '=');
       }
       db_delete('amazons3_file')->condition($or)->execute();
       $responses = $s3->batch()->send();
 
-      if($responses->areOK()) {
+      if ($responses->areOK()) {
         return TRUE;
       }
     }
@@ -743,7 +769,8 @@
    *
    * @see http://php.net/manual/en/streamwrapper.url-stat.php
    */
-  public function url_stat($uri, $flags) {
+  public function url_stat($uri, $flags)
+  {
     return $this->_stat($uri);
   }
 
@@ -760,19 +787,22 @@
    *
    * @see http://php.net/manual/en/streamwrapper.dir-opendir.php
    */
-  public function dir_opendir($uri, $options) {
-    if ($uri == null) {
+  public function dir_opendir($uri, $options)
+  {
+    if ($uri == NULL) {
       return FALSE;
     }
-    else if(!$this->_is_dir($uri)) {
+    else {
+      if (!$this->_is_dir($uri)) {
-      return FALSE;
-    }
+        return FALSE;
+      }
+    }
 
     $this->dir = array();
     $path = $this->getLocalPath($uri);
     $truncated = TRUE;
     $marker = '';
-    if(strlen($path) == 0) {
+    if (strlen($path) == 0) {
       $prefix = $path;
     }
     else {
@@ -780,11 +810,11 @@
     }
     $prefix_length = strlen($prefix);
 
-    while($truncated) {
+    while ($truncated) {
       $response = $this->getS3()->list_objects($this->bucket, array(
-          'delimiter' => '/',
-          'prefix' => $prefix,
-          'marker' => urlencode($marker),
+        'delimiter' => '/',
+        'prefix' => $prefix,
+        'marker' => urlencode($marker),
       ));
       if ($response->isOK()) {
 
@@ -793,31 +823,31 @@
 
         // Folders
         if (isset($response->body->CommonPrefixes)) {
-          foreach($response->body->CommonPrefixes as $prefix) {
+          foreach ($response->body->CommonPrefixes as $prefix) {
             $marker = substr($prefix->Prefix, $prefix_length, strlen($prefix->Prefix) - $prefix_length - 1);
-            if(strlen($marker) > 0) {
+            if (strlen($marker) > 0) {
               $this->dir[] = $marker;
             }
           }
         }
 
         // Files
-        if(isset($response->body->Contents)) {
+        if (isset($response->body->Contents)) {
           $contents = $response->body->to_stdClass()->Contents;
           if (!is_array($contents)) {
             $contents = array($contents);
           }
 
-          foreach($contents as $content) {
+          foreach ($contents as $content) {
             $key = $content->Key;
-            if(substr_compare($key, '/', -1, 1) !== 0) {
+            if (substr_compare($key, '/', -1, 1) !== 0) {
               $marker = basename($key);
               $this->dir[] = $marker;
             }
           }
         }
 
-        if(!isset($response->body->IsTruncated) || $response->body->IsTruncated == 'false') {
+        if (!isset($response->body->IsTruncated) || $response->body->IsTruncated == 'false') {
           $truncated = FALSE;
         }
       }
@@ -836,10 +866,11 @@
    *
    * @see http://php.net/manual/en/streamwrapper.dir-readdir.php
    */
-  public function dir_readdir() {
+  public function dir_readdir()
+  {
     $filename = current($this->dir);
-    if ($filename !== false) {
+    if ($filename !== FALSE) {
-        next($this->dir);
+      next($this->dir);
     }
     return $filename;
   }
@@ -852,7 +883,8 @@
    *
    * @see http://php.net/manual/en/streamwrapper.dir-rewinddir.php
    */
-  public function dir_rewinddir() {
+  public function dir_rewinddir()
+  {
     reset($this->dir);
     return TRUE;
   }
@@ -865,7 +897,8 @@
    *
    * @see http://php.net/manual/en/streamwrapper.dir-closedir.php
    */
-  public function dir_closedir() {
+  public function dir_closedir()
+  {
     $this->dir = array();
     return TRUE;
   }
@@ -876,12 +909,13 @@
    * @param $uri
    *   Optional URI, supplied when doing a move or rename.
    */
-  protected function getLocalPath($uri = NULL) {
+  protected function getLocalPath($uri = NULL)
+  {
     if (!isset($uri)) {
       $uri = $this->uri;
     }
 
-    $path  = str_replace('s3://', '', $uri);
+    $path = str_replace('s3://', '', $uri);
     $path = trim($path, '/');
     return $path;
   }
@@ -892,18 +926,20 @@
    * @return
    *   String specifying the path.
    */
-  public function getDirectoryPath() {
+  public function getDirectoryPath()
+  {
     return $this->domain;
   }
 
   /**
    * Flush the object buffers
    */
-  protected function clearBuffer() {
+  protected function clearBuffer()
+  {
     $this->position = 0;
     $this->object_size = 0;
-    $this->buffer = null;
-    $this->buffer_write = false;
+    $this->buffer = NULL;
+    $this->buffer_write = FALSE;
     $this->buffer_length = 0;
   }
 
@@ -915,29 +951,31 @@
    *
    * @see http://docs.amazonwebservices.com/AWSSDKforPHP/latest/#i=AmazonS3
    */
-  protected function getS3() {
-    if($this->s3 == null) {
+  public function getS3()
+  {
+    if ($this->s3 == NULL) {
       $bucket = variable_get('amazons3_bucket', '');
 
-      if(!libraries_load('awssdk') && !isset($bucket)) {
+      if (!libraries_load('awssdk') && !isset($bucket)) {
-        drupal_set_message('Unable to load the AWS SDK. Please check you have installed the library correctly and configured your S3 credentials.'. 'error');
+        drupal_set_message('Unable to load the AWS SDK. Please check you have installed the library correctly and configured your S3 credentials.' . 'error');
       }
-      else if(!isset($bucket)) {
+      else {
+        if (!isset($bucket)) {
-        drupal_set_message('Bucket name not configured.'. 'error');
+          drupal_set_message('Bucket name not configured.' . 'error');
-      }
-      else {
-        try {
-         $this->s3 = new AmazonS3();
-         $this->bucket = $bucket;
+        }
+        else {
+          try {
+            $this->s3 = new AmazonS3();
+            $this->bucket = $bucket;
-        }
-        catch(RequestCore_Exception $e){
+          } catch (RequestCore_Exception $e) {
-          drupal_set_message('There was a problem connecting to S3', 'error');
-        }
+            drupal_set_message('There was a problem connecting to S3', 'error');
+          }
-        catch(Exception $e) {
+          catch (Exception $e) {
-          drupal_set_message('There was a problem using S3: ' . $e->getMessage(), 'error');
-        }
-      }
-    }
+            drupal_set_message('There was a problem using S3: ' . $e->getMessage(), 'error');
+          }
+        }
+      }
+    }
     return $this->s3;
   }
 
@@ -950,8 +988,9 @@
    *
    * @see http://php.net/manual/en/streamwrapper.stream-stat.php
    */
-  protected function _stat($uri = NULL) {
+  protected function _stat($uri = NULL)
+  {
-    if(!isset($uri)) {
+    if (!isset($uri)) {
       $uri = $this->uri;
     }
     $metadata = $this->_amazons3_get_object($uri, $this->caching);
@@ -981,12 +1020,12 @@
       return $stat;
     }
     return FALSE;
-}
+  }
 
 
-/**
- * Determine whether the $uri is a directory
- *
+  /**
+   * Determine whether the $uri is a directory
+   *
    * @param $uri
    *   A string containing the uri to the resource to check. If none is given
    *   defaults to $this->uri
@@ -994,17 +1033,18 @@
    * @return
    *   TRUE if the resource is a directory
    */
-  protected function _is_dir($uri = null) {
-    if($uri == null) {
+  protected function _is_dir($uri = NULL)
+  {
+    if ($uri == NULL) {
       $uri = $this->uri;
     }
-    if($uri != null) {
+    if ($uri != NULL) {
       $path = $this->getLocalPath($uri);
       $response = $this->getS3()->list_objects($this->bucket, array(
-          'prefix' => $path . '/',
-          'max-keys' => 1,
+        'prefix' => $path . '/',
+        'max-keys' => 1,
       ));
-      if($response && isset($response->body->Contents->Key)) {
+      if ($response && isset($response->body->Contents->Key)) {
         return TRUE;
       }
     }
@@ -1024,11 +1064,12 @@
    * @param $cach
    *   A boolean representing whether to check the cache for file information.
    *
-   *  @return
+   * @return
    *    An array if the $uri exists, otherwise FALSE.
    */
-  protected function _amazons3_get_object($uri, $cache = TRUE) {
+  public function _amazons3_get_object($uri, $cache = TRUE)
+  {
-    $uri = rtrim($uri,'/');
+    $uri = rtrim($uri, '/');
 
     if ($cache) {
       $metadata = $this->_amazons3_get_cache($uri);
@@ -1074,10 +1115,11 @@
    * @param uri
    *  A string containing the uri of the resource to check.
    *
-   *  @return
+   * @return
    *    An array if the $uri is in the cache, otherwise FALSE
    */
-  protected function _amazons3_get_cache($uri) {
+  protected function _amazons3_get_cache($uri)
+  {
     // Check cache for existing object.
     $result = db_query("SELECT * FROM {amazons3_file} WHERE uri = :uri", array(
       ':uri' => $uri,
@@ -1100,7 +1142,8 @@
    * @return
    *   An array containing formatted metadata
    */
-  protected function _amazons3_format_response($uri, $response) {
+  protected function _amazons3_format_response($uri, $response)
+  {
     $metadata = array('uri' => $uri);
     if (isset($response['Size'])) {
       $metadata['filesize'] = $response['Size'];
Index: amazons3_reference.inc
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- amazons3_reference.inc	(revision )
+++ amazons3_reference.inc	(revision )
@@ -0,0 +1,316 @@
+<?php
+/**
+ * @file
+ * A FileField extension to allow referencing of existing files.
+ *
+ * The "hooks" in this file are not true hooks, they're called individually
+ * from the main filefield_sources.module in the corresponding hook by the
+ * same name. Any of these hooks could be broken out into a separate module.
+ */
+define('AMAZONS3_REFERENCED_HINT_TEXT', 'Object.mp3');
+
+/**
+ * Implements hook_filefield_sources_info().
+ */
+function amazons3_filefield_sources_info()
+{
+  $source = array();
+
+  $source['amazons3_referenced'] = array(
+    'name' => t('S3 Autocomplete reference'),
+    'label' => t('S3 Reference'),
+    'description' => t('Reuse an existing file by entering its file name.'),
+    'process' => 'amazons3_referenced_process',
+    'value' => 'amazons3_referenced_value',
+    'weight' => 1,
+  );
+  return $source;
+}
+
+/**
+ * Implements hook_theme().
+ */
+function amazons3_theme()
+{
+  return array(
+    'amazons3_referenced_element' => array(
+      'render element' => 'element',
+      'file' => 'amazons3_reference.inc',
+    ),
+    'amazons3_referenced_autocomplete_item' => array(
+      'variables' => array('file' => NULL),
+      'file' => 'amazons3_reference.inc',
+    ),
+  );
+}
+
+/**
+ * Implements hook_filefield_sources_widgets().
+ */
+function amazons3_filefield_sources_widgets()
+{
+  // Add any widgets that your module supports here.
+  return array('file', 'commerce_file_generic');
+}
+
+/**
+ * Implements hook_filefield_source_settings().
+ */
+function filefield_source_amazons3_settings($op, $instance)
+{
+  $return = array();
+
+  if ($op == 'form') {
+    $settings = $instance['widget']['settings']['filefield_sources'];
+
+    $return['amazons3_referenced'] = array(
+      '#title' => t('S3 Autocomplete reference options'),
+      '#type' => 'fieldset',
+      '#collapsible' => TRUE,
+      '#collapsed' => TRUE,
+    );
+
+    $return['amazons3_referenced']['autocomplete'] = array(
+      '#title' => t('Match file name'),
+      '#options' => array(
+        '0' => t('Starts with string'),
+        '1' => t('Contains string'),
+      ),
+      '#type' => 'radios',
+      '#default_value' => isset($settings['amazons3_referenced']['autocomplete']) ? $settings['amazons3_referenced']['autocomplete'] : '0',
+    );
+  }
+  elseif ($op == 'save') {
+    $return['amazons3_referenced']['autocomplete'] = '0';
+  }
+
+  return $return;
+}
+
+/**
+ * A #process callback to extend the filefield_widget element type.
+ */
+function amazons3_referenced_process($element, &$form_state, $form)
+{
+
+  $element['amazons3_referenced'] = array(
+    '#weight' => 100.5,
+    '#theme' => 'amazons3_referenced_element',
+    '#filefield_source' => TRUE, // Required for proper theming.
+    '#filefield_sources_hint_text' => AMAZONS3_REFERENCED_HINT_TEXT,
+  );
+
+  $element['amazons3_referenced']['autocomplete'] = array(
+    '#type' => 'textfield',
+    '#autocomplete_path' => 'amazons3/reference/' . $element['#entity_type'] . '/' . $element['#bundle'] . '/' . $element['#field_name'],
+    '#description' => t('Choose a file from your S3 bucket.'),
+  );
+
+  $element['amazons3_referenced']['select'] = array(
+    '#name' => implode('_', $element['#array_parents']) . '_autocomplete_select',
+    '#type' => 'submit',
+    '#value' => t('Select'),
+    '#validate' => array(),
+    '#submit' => array('filefield_sources_field_submit'),
+    '#name' => $element['#name'] . '[amazons3_referenced][button]',
+    '#limit_validation_errors' => array($element['#parents']),
+    '#ajax' => array(
+      'path' => 'file/ajax/' . implode('/', $element['#array_parents']) . '/' . $form['form_build_id']['#value'],
+      'wrapper' => $element['#id'] . '-ajax-wrapper',
+      'effect' => 'fade',
+    ),
+  );
+
+  return $element;
+}
+
+function amazons3_referenced_field_validate(&$form, &$form_state)
+{
+  /*
+
+    $parents = array_slice($form_state['triggering_element']['#parents'], 0, -1);
+    //drupal_array_set_nested_value($form_state['input'], $parents, NULL);
+
+    // @todo: There has to be a "Drupal Way" of doing this.
+    if (!empty($form_state['values'][$parents[0]][$parents[1]][$parents[2]][$parents[3]]['autocomplete'])) {
+      $filename = $form_state['values'][$parents[0]][$parents[1]][$parents[2]][$parents[3]]['autocomplete'];
+
+      $frecord = amazons3_reference_create_file_record($filename);
+
+      if($frecord){
+        $form_state['values'][$parents[0]][$parents[1]][$parents[2]][$parents[3]]['fid'] = $frecord["fid"];
+      }else{
+        form_set_error($form, 'Please, check the file name');
+      }
+    }
+  */
+}
+
+
+function amazons3_reference_create_file_record($filename)
+{
+
+  libraries_load('awssdk');
+
+  $wrapper = new AmazonS3StreamWrapper();
+  $bucket = variable_get('amazons3_bucket', '');
+
+  $response = $wrapper->_amazons3_get_object($filename);
+  $metadata = $wrapper->getS3()->get_object_metadata($bucket, $filename);
+
+  if ($response) {
+    global $user;
+    $result = db_select('file_managed', 'fm')
+      ->fields('fm')
+      ->condition('uri', "s3://" . $filename)
+      ->execute()
+      ->fetchAssoc();
+
+    if ($result) {
+      return $result;
+    }
+
+    $record = array(
+      "uid" => $user->uid,
+      "filename" => $filename,
+      "uri" => "s3://" . $filename,
+      "filemime" => $wrapper->getMimeType($filename),
+      "filesize" => $metadata["Size"],
+      "status" => '1',
+      "timestamp" => time(),
+      "type" => 'video',
+      "origname" => $filename,
+    );
+    drupal_write_record('file_managed', $record);
+
+
+    return $record;
+  }
+  return FALSE;
+}
+
+/**
+ * A #filefield_value_callback function.
+ */
+function amazons3_referenced_value($element, &$item)
+{
+  if (isset($item['amazons3_referenced']['autocomplete']) && strlen($item['amazons3_referenced']['autocomplete']) > 0 && $item['amazons3_referenced']['autocomplete'] != AMAZONS3_REFERENCED_HINT_TEXT) {
+    $filename = $item['amazons3_referenced']['autocomplete'];
+    $record = amazons3_reference_create_file_record($filename);
+    $field = field_info_instance($element['#entity_type'], $element['#field_name'], $element['#bundle']);
+
+    if ($record) {
+      $fid = db_query('SELECT fid FROM {file_managed} WHERE uri = :filename', array(':filename' => "s3://" . $filename))->fetchField();
+
+      if ($file = file_load($fid)) {
+        // Now tell Drupal that our node is using the file.
+        file_usage_add($file, 'amazons3', 'preexisting', $file->fid);
+
+        // Below is commented out because this is not a local file.
+        if (filefield_sources_element_validate($element, (object) $file)) {
+          //$filename = filefield_sources_clean_filename($filename, $field['settings']['file_extensions']);
+          //$filepath = file_create_filename($filename, 's3://');
+
+          //if ($file = filefield_sources_save_file($filepath, $element['#upload_validators'], $element['#upload_location'])) {
+          $item = array_merge($item, (array) $file);
+          //}
+        }
+      }
+      else {
+        form_error($element, t('The referenced file could not be used because the file does not exist in the database.'));
+      }
+    }
+  }
+  // No matter what happens, clear the value from the autocomplete.
+  $item['amazons3_referenced']['autocomplete'] = '';
+}
+
+/**
+ * Menu callback; autocomplete.js callback to return a list of files.
+ */
+function amazons3_referenced_autocomplete($entity_type, $bundle_name, $field_name, $filename)
+{
+  $field = field_info_instance($entity_type, $field_name, $bundle_name);
+
+  $items = array();
+  if (!empty($field)) {
+    $files = amazons3_referenced_get_files($filename, $field);
+    foreach ($files as $file) {
+      $items[$file] = theme('amazons3_referenced_autocomplete_item', array('file' => $file));
+    }
+  }
+
+  drupal_json_output($items);
+}
+
+/**
+ * Theme the output of a single item in the autocomplete list.
+ */
+function theme_amazons3_referenced_autocomplete_item($variables)
+{
+  $file = $variables['file'];
+
+  $output = '';
+  $output .= '<div class="filefield-source-amazons3-reference-item">';
+  $output .= '<span class="filename">' . $file . '</span>';
+  $output .= '</div>';
+  return $output;
+}
+
+/**
+ * Theme the output of the autocomplete field.
+ */
+function theme_amazons3_referenced_element($variables)
+{
+  $element = $variables['element'];
+  $element['autocomplete']['#field_suffix'] = drupal_render($element['select']);
+  return '<div class="filefield-source filefield-source-amazons3 clear-block">' . drupal_render($element['autocomplete']) . '</div>';
+}
+
+/**
+ * Get all the files used within a particular field (or all fields).
+ *
+ * @param $file_name
+ *   The partial name of the file to retrieve.
+ * @param $instance
+ *   Optional. A CCK field array for which to filter returned files.
+ */
+function amazons3_referenced_get_files($filename, $instance = NULL)
+{
+  $instances = array();
+  if (!isset($instance)) {
+    foreach (field_info_fields() as $instance) {
+      if ($instance['type'] == 'file' || $instance['type'] == 'image') {
+        $instances[] = $instance;
+      }
+    }
+  }
+  else {
+    $instances = array($instance);
+  }
+
+  $files = array();
+  foreach ($instances as $instance) {
+    $field = field_info_field($instance['field_name']);
+
+    if (!isset($field['storage']['details']['sql']['FIELD_LOAD_CURRENT'])) {
+      continue;
+    }
+
+    $like = empty($instance['widget']['settings']['filefield_sources']['amazons3_referenced']['autocomplete']) ? ($filename . '%') : ('%' . $filename . '%');
+
+    $table = 'amazons3_file';
+    $query = db_select($table, 'af');
+    $query->fields('af', array('uri'));
+    $query->condition('af.uri', $like, 'LIKE');
+    $result = $query->execute();
+
+    if ($result) {
+      foreach ($result as $file) {
+        $files[$file->uri] = $file->uri;
+      }
+    }
+  }
+
+  return $files;
+}
\ No newline at end of file
Index: amazons3.module
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
Subsystem: com.intellij.openapi.diff.impl.patch.BaseRevisionTextPatchEP
<+><?php\n\n/**\n * @file\n * Provides S3 stream wrapper\n */\n\n/**\n * Implements hook_stream_wrappers().\n *\n * Create a stream wrapper for S3\n */\nfunction amazons3_stream_wrappers() {\n  return array(\n    's3' => array(\n      'name' => 'Amazon S3',\n      'class' => 'AmazonS3StreamWrapper',\n      'description' => t('Amazon Simple Storage Service'),\n    ),\n  );\n}\n\n/**\n * Implements hook_menu().\n */\nfunction amazons3_menu() {\n  $items = array();\n\n  $items['admin/config/media/amazons3'] = array(\n    'title' => 'Amazon S3',\n    'description' => 'Configure your S3 credentials',\n    'page callback' => 'drupal_get_form',\n    'page arguments' => array('amazons3_admin'),\n    'access arguments' => array('access administration pages'),\n  );\n\n  return $items;\n}\n\n/**\n * Implements hook_help().\n */\nfunction amazons3_help($path, $arg) {\n  switch ($path) {\n    case 'admin/config/media/amazons3':\n    if (module_exists('awssdk_ui')) {\n      return '<p>' . t('Amazon Web Services authentication can be configured at the <a href=\"@awssdk_config\">AWS SDK configuration page</a>.', array('@awssdk_config' => url('admin/config/media/awssdk'))) . '</p>';\n    }\n    else {\n      return '<p>' . t('Enable \\'AWS SDK for PHP UI\\' module to configure your Amazon Web Services authentication. Configuration can also be defined in the $conf array in settings.php.', array('@awssdk_config' => url('admin/config/media/awssdk'))) . '</p>';\n    }\n  }\n}\n\n/**\n * Implements hook_admin().\n */\nfunction amazons3_admin() {\n  $form = array();\n\n  $form['amazons3_bucket'] = array(\n      '#type'           => 'textfield',\n      '#title'          => t('Default Bucket Name'),\n      '#default_value'  => variable_get('amazons3_bucket', ''),\n      '#required'       => TRUE,\n  );\n\n  $form['amazons3_cache'] = array(\n    '#type'           => 'checkbox',\n    '#title'          => t('Enable database caching'),\n    '#description'    => t('Enable a local file metadata cache, this significantly reduces calls to S3'),\n    '#default_value'  => variable_get('amazons3_cache', 1),\n  );\n\n  $form['amazons3_cname'] = array(\n    '#type'           => 'checkbox',\n    '#title'          => t('Enable CNAME'),\n    '#description'    => t('Serve files from a custom domain by using an appropriately named bucket e.g. \"mybucket.mydomain.com\"'),\n    '#default_value'  => variable_get('amazons3_cname', 0),\n  );\n\n  $form['amazons3_domain'] = array(\n      '#type'           => 'textfield',\n      '#title'          => t('CDN Domain Name'),\n      '#description'    => t('If serving files from CloudFront then the bucket name can differ from the domain name.'),\n      '#default_value'  => variable_get('amazons3_domain', ''),\n      '#states'         => array(\n        'visible' => array(\n          ':input[id=edit-amazons3-cname]' => array('checked' => TRUE),\n        )\n      ),\n  );\n\n  $form['amazons3_torrents'] = array(\n    '#type' => 'textarea',\n    '#title' => t('Torrents'),\n    '#description' => t('A list of paths that should be delivered through a torrent url. Enter one value per line e.g. \"mydir/*\". Paths are relative to the Drupal file directory and use patterns as per <a href=\"@preg_match\">preg_match</a>. If no timeout is provided, it defaults to 60 seconds.', array('@preg_match' => 'http://php.net/preg_match')),\n    '#default_value' => variable_get('amazons3_torrents', ''),\n    '#rows' => 10,\n  );\n\n  $form['amazons3_presigned_urls'] = array(\n    '#type' => 'textarea',\n    '#title' => t('Presigned URLs'),\n    '#description' => t('A list of timeouts and paths that should be delivered through a presigned url. Enter one value per line, in the format timeout|path. e.g. \"60|mydir/*\". Paths are relative to the Drupal file directory and use patterns as per <a href=\"@preg_match\">preg_match</a>. If no timeout is provided, it defaults to 60 seconds.', array('@preg_match' => 'http://php.net/preg_match')),\n    '#default_value' => variable_get('amazons3_presigned_urls', ''),\n    '#rows' => 10,\n  );\n\n  $form['amazons3_saveas'] = array(\n    '#type' => 'textarea',\n    '#title' => t('Force Save As'),\n    '#description' => t('A list of paths that foce the user to save the file by using Content-disposition header. Prevents autoplay of media. Enter one value per line. e.g. \"mydir/*\". Paths are relative to the Drupal file directory and use patterns as per <a href=\"@preg_match\">preg_match</a>. Files must use a presigned url to use this.', array('@preg_match' => 'http://php.net/preg_match')),\n    '#default_value' => variable_get('amazons3_saveas', ''),\n    '#rows' => 10,\n  );\n\n  $form['amazons3_clear_cache'] = array(\n    '#type' => 'fieldset',\n    '#title' => t('Clear cache'),\n  );\n\n  $form['amazons3_clear_cache']['clear'] = array(\n    '#type' => 'submit',\n    '#value' => t('Clear file metadata cache'),\n    '#submit' => array('amazons3_clear_cache_submit'),\n  );\n\n  return system_settings_form($form);\n}\n\nfunction amazons3_admin_validate($form, &$form_state) {\n  $bucket = $form_state['values']['amazons3_bucket'];\n\n  if(!libraries_load('awssdk')) {\n    form_set_error('amazons3_bucket', t('Unable to load the AWS SDK. Please check you have installed the library correctly and configured your S3 credentials.'));\n  }\n  else if(!class_exists('AmazonS3')) {\n    form_set_error('amazons3_bucket', t('Cannot load AmazonS3 class. Please check the awssdk is installed correctly'));\n  }\n  else {\n    try {\n      $s3 = new AmazonS3();\n      // test connection\n      $user_id = $s3->get_canonical_user_id();\n      if(!$user_id['id']) {\n        form_set_error('amazons3_bucket', t('The S3 access credentials are invalid'));\n      }\n      else if(!$s3->if_bucket_exists($bucket)) {\n        form_set_error('amazons3_bucket', t('The bucket does not exist'));\n      }\n    }\n    catch(RequestCore_Exception $e){\n      if(strstr($e->getMessage(), 'SSL certificate problem')) {\n        form_set_error('amazons3_bucket', t('There was a problem with the SSL certificate. Try setting AWS_CERTIFICATE_AUTHORITY to true in \"libraries/awssdk/config.inc.php\". You may also have a curl library (e.g. the default shipped with MAMP) that does not contain trust certificates for the major authorities.'));\n      }\n      else {\n        form_set_error('amazons3_bucket', t('There was a problem connecting to S3'));\n      }\n\n    }\n    catch(Exception $e) {\n      form_set_error('amazons3_bucket', t('There was a problem using S3'));\n    }\n  }\n}\n\nfunction amazons3_image_style_flush($style) {\n  // Empty cached data that contains information about the style.\n  if(isset($style->old_name) && strlen($style->old_name) > 0) {\n    drupal_rmdir('s3://styles/' . $style->old_name);\n  }\n  if(isset($style->name) && strlen($style->name) > 0) {\n    drupal_rmdir('s3://styles/' . $style->name);\n  }\n}\n/**\n * Submit callback; clear file metadata cache.\n *\n */\nfunction amazons3_clear_cache_submit($form, &$form_state) {\n  db_query('TRUNCATE TABLE {amazons3_file}');\n  drupal_set_message(t('Cache cleared.'));\n}\n\n
===================================================================
--- amazons3.module	(revision 87507abba84c6e3a4f92dccc95fdf7319c8bed42)
+++ amazons3.module	(revision )
@@ -5,12 +5,15 @@
  * Provides S3 stream wrapper
  */
 
+module_load_include('inc', 'amazons3', 'amazons3_reference');
+
 /**
  * Implements hook_stream_wrappers().
  *
  * Create a stream wrapper for S3
  */
-function amazons3_stream_wrappers() {
+function amazons3_stream_wrappers()
+{
   return array(
     's3' => array(
       'name' => 'Amazon S3',
@@ -23,7 +26,8 @@
 /**
  * Implements hook_menu().
  */
-function amazons3_menu() {
+function amazons3_menu()
+{
   $items = array();
 
   $items['admin/config/media/amazons3'] = array(
@@ -34,61 +38,71 @@
     'access arguments' => array('access administration pages'),
   );
 
+  $items['amazons3/reference/%/%/%'] = array(
+    'page callback' => 'amazons3_referenced_autocomplete',
+    'page arguments' => array(2, 3, 4),
+    'access callback' => '_filefield_sources_field_access',
+    'access arguments' => array(2, 3, 4),
+    'file' => 'amazons3_reference.inc',
+    'type' => MENU_CALLBACK,
+  );
   return $items;
 }
 
 /**
  * Implements hook_help().
  */
-function amazons3_help($path, $arg) {
+function amazons3_help($path, $arg)
+{
   switch ($path) {
     case 'admin/config/media/amazons3':
-    if (module_exists('awssdk_ui')) {
-      return '<p>' . t('Amazon Web Services authentication can be configured at the <a href="@awssdk_config">AWS SDK configuration page</a>.', array('@awssdk_config' => url('admin/config/media/awssdk'))) . '</p>';
-    }
-    else {
-      return '<p>' . t('Enable \'AWS SDK for PHP UI\' module to configure your Amazon Web Services authentication. Configuration can also be defined in the $conf array in settings.php.', array('@awssdk_config' => url('admin/config/media/awssdk'))) . '</p>';
-    }
+      if (module_exists('awssdk_ui')) {
+        return '<p>' . t('Amazon Web Services authentication can be configured at the <a href="@awssdk_config">AWS SDK configuration page</a>.', array('@awssdk_config' => url('admin/config/media/awssdk'))) . '</p>';
+      }
+      else {
+        return '<p>' . t('Enable \'AWS SDK for PHP UI\' module to configure your Amazon Web Services authentication. Configuration can also be defined in the $conf array in settings.php.', array('@awssdk_config' => url('admin/config/media/awssdk'))) . '</p>';
+      }
   }
 }
 
 /**
  * Implements hook_admin().
  */
-function amazons3_admin() {
+function amazons3_admin()
+{
   $form = array();
 
   $form['amazons3_bucket'] = array(
-      '#type'           => 'textfield',
+    '#type' => 'textfield',
-      '#title'          => t('Default Bucket Name'),
+    '#title' => t('Default Bucket Name'),
-      '#default_value'  => variable_get('amazons3_bucket', ''),
+    '#default_value' => variable_get('amazons3_bucket', ''),
-      '#required'       => TRUE,
+    '#required' => TRUE,
   );
 
   $form['amazons3_cache'] = array(
-    '#type'           => 'checkbox',
+    '#type' => 'checkbox',
-    '#title'          => t('Enable database caching'),
+    '#title' => t('Enable database caching'),
-    '#description'    => t('Enable a local file metadata cache, this significantly reduces calls to S3'),
+    '#description' => t('Enable a local file metadata cache, this significantly reduces calls to S3'),
-    '#default_value'  => variable_get('amazons3_cache', 1),
+    '#default_value' => variable_get('amazons3_cache', 1),
   );
 
   $form['amazons3_cname'] = array(
-    '#type'           => 'checkbox',
+    '#type' => 'checkbox',
-    '#title'          => t('Enable CNAME'),
+    '#title' => t('Enable CNAME'),
-    '#description'    => t('Serve files from a custom domain by using an appropriately named bucket e.g. "mybucket.mydomain.com"'),
+    '#description' => t('Serve files from a custom domain by using an appropriately named bucket e.g. "mybucket.mydomain.com"'),
-    '#default_value'  => variable_get('amazons3_cname', 0),
+    '#default_value' => variable_get('amazons3_cname', 0),
   );
 
   $form['amazons3_domain'] = array(
-      '#type'           => 'textfield',
+    '#type' => 'textfield',
-      '#title'          => t('CDN Domain Name'),
+    '#title' => t('CDN Domain Name'),
-      '#description'    => t('If serving files from CloudFront then the bucket name can differ from the domain name.'),
+    '#description' => t('If serving files from CloudFront then the bucket name can differ from the domain name.'),
-      '#default_value'  => variable_get('amazons3_domain', ''),
+    '#default_value' => variable_get('amazons3_domain', ''),
-      '#states'         => array(
+    '#states' => array(
-        'visible' => array(
-          ':input[id=edit-amazons3-cname]' => array('checked' => TRUE),
-        )
-      ),
+      'visible' => array(
+        ':input[id=edit-amazons3-cname]' => array('checked' => TRUE),
+      )
+    ),
   );
 
   $form['amazons3_torrents'] = array(
@@ -126,60 +140,219 @@
     '#submit' => array('amazons3_clear_cache_submit'),
   );
 
+  $form['amazons3_clear_cache']['rebuild'] = array(
+    '#type' => 'submit',
+    '#value' => t('Rebuild file metadata cache'),
+    '#submit' => array('amazons3_batch_rebuild_cache'),
+  );
   return system_settings_form($form);
 }
 
-function amazons3_admin_validate($form, &$form_state) {
+function amazons3_admin_validate($form, &$form_state)
+{
   $bucket = $form_state['values']['amazons3_bucket'];
 
-  if(!libraries_load('awssdk')) {
+  if (!libraries_load('awssdk')) {
     form_set_error('amazons3_bucket', t('Unable to load the AWS SDK. Please check you have installed the library correctly and configured your S3 credentials.'));
   }
-  else if(!class_exists('AmazonS3')) {
+  else {
+    if (!class_exists('AmazonS3')) {
-    form_set_error('amazons3_bucket', t('Cannot load AmazonS3 class. Please check the awssdk is installed correctly'));
-  }
-  else {
-    try {
-      $s3 = new AmazonS3();
-      // test connection
-      $user_id = $s3->get_canonical_user_id();
+      form_set_error('amazons3_bucket', t('Cannot load AmazonS3 class. Please check the awssdk is installed correctly'));
+    }
+    else {
+      try {
+        $s3 = new AmazonS3();
+        // test connection
+        $user_id = $s3->get_canonical_user_id();
-      if(!$user_id['id']) {
+        if (!$user_id['id']) {
-        form_set_error('amazons3_bucket', t('The S3 access credentials are invalid'));
-      }
+          form_set_error('amazons3_bucket', t('The S3 access credentials are invalid'));
+        }
-      else if(!$s3->if_bucket_exists($bucket)) {
+        else {
+          if (!$s3->if_bucket_exists($bucket)) {
-        form_set_error('amazons3_bucket', t('The bucket does not exist'));
-      }
-    }
+            form_set_error('amazons3_bucket', t('The bucket does not exist'));
+          }
+        }
-    catch(RequestCore_Exception $e){
+      } catch (RequestCore_Exception $e) {
-      if(strstr($e->getMessage(), 'SSL certificate problem')) {
+        if (strstr($e->getMessage(), 'SSL certificate problem')) {
-        form_set_error('amazons3_bucket', t('There was a problem with the SSL certificate. Try setting AWS_CERTIFICATE_AUTHORITY to true in "libraries/awssdk/config.inc.php". You may also have a curl library (e.g. the default shipped with MAMP) that does not contain trust certificates for the major authorities.'));
-      }
-      else {
+          form_set_error('amazons3_bucket', t('There was a problem with the SSL certificate. Try setting AWS_CERTIFICATE_AUTHORITY to true in "libraries/awssdk/config.inc.php". You may also have a curl library (e.g. the default shipped with MAMP) that does not contain trust certificates for the major authorities.'));
+        }
+        else {
-        form_set_error('amazons3_bucket', t('There was a problem connecting to S3'));
+          form_set_error('amazons3_bucket', t('There was a problem connecting to S3: ' . $e));
-      }
+        }
 
-    }
+      }
-    catch(Exception $e) {
+      catch (Exception $e) {
-      form_set_error('amazons3_bucket', t('There was a problem using S3'));
+        form_set_error('amazons3_bucket', t('There was a problem using S3: ' . $e));
-    }
-  }
-}
+      }
+    }
+  }
+}
 
-function amazons3_image_style_flush($style) {
+function amazons3_image_style_flush($style)
+{
   // Empty cached data that contains information about the style.
-  if(isset($style->old_name) && strlen($style->old_name) > 0) {
+  if (isset($style->old_name) && strlen($style->old_name) > 0) {
     drupal_rmdir('s3://styles/' . $style->old_name);
   }
-  if(isset($style->name) && strlen($style->name) > 0) {
+  if (isset($style->name) && strlen($style->name) > 0) {
     drupal_rmdir('s3://styles/' . $style->name);
   }
 }
+
 /**
  * Submit callback; clear file metadata cache.
  *
  */
-function amazons3_clear_cache_submit($form, &$form_state) {
+function amazons3_clear_cache_submit($form, &$form_state)
+{
   db_query('TRUNCATE TABLE {amazons3_file}');
   drupal_set_message(t('Cache cleared.'));
 }
 
+
+/**
+ * Submit handler to rebuild the cache via the Batch API.
+ */
+function amazons3_batch_rebuild_cache($options1, $options2)
+{
+
+  //@ todo: Do not truncate the table, if we can find a way to get just the newly-added files.
+  db_query('TRUNCATE TABLE {amazons3_file}');
+
+  $batch = array(
+    'operations' => array(
+      array('amazons3_rebuild_process', array($options1, $options2)),
+    ),
+    'finished' => 'amazons3_rebuild_process_finished',
+    'title' => t('Processing Amazon S3 Cache Rebuild'),
+    'init_message' => t('Starting. Connecting to S3 and fetching objects. Please wait...'),
+    'progress_message' => t('Rebuilding the cache... @current of @total'),
+    'error_message' => t('Amazon S3 Cache Rebuild has encountered an error.'),
+  );
+  batch_set($batch);
+
+}
+
+/**
+ * Batch Operation Callback
+ */
+function amazons3_rebuild_process($options1, $options2, &$context)
+{
+
+  $bucket = variable_get('amazons3_bucket', '');
+
+  if (!libraries_load('awssdk')) {
+    drupal_set_message(t('Unable to load the AWS SDK. Please check you have installed the library correctly and configured your S3 credentials.'), 'error');
+  }
+  else {
+    if (!class_exists('AmazonS3')) {
+      drupal_set_message(t('Cannot load AmazonS3 class. Please check the awssdk is installed correctly'), 'error');
+    }
+    else {
+      try {
+        $s3 = new AmazonS3();
+
+        $user_id = $s3->get_canonical_user_id();
+        if (!$user_id['id']) {
+          drupal_set_message('The S3 access credentials are invalid', 'error');
+        }
+        else {
+          if (!$s3->if_bucket_exists($bucket)) {
+            drupal_set_message('The bucket does not exist', 'error');
+          }
+        }
+
+        $bucket = variable_get('amazons3_bucket', '');
+
+        $limit = 100; // @todo: Make this configurable.
+
+        // Total number of objects in the bucket.
+        $total = count($s3->get_object_list($bucket));
+
+
+        // Create a new AmazonS3StreamWrapper so we can use some of its functions. (They have been made public.)
+        // @todo: Use reflection to extend the protected functions in the public class, unless this is good enough.
+        $wrapper = new AmazonS3StreamWrapper();
+
+        if (!isset($context['sandbox']['progress'])) {
+
+          $objects = $s3->get_object_list($bucket, array('max-keys' => $limit));
+
+          $context['sandbox']['last_key'] = $objects[$limit - 1];
+          $context['sandbox']['progress'] = 0;
+          $context['sandbox']['current_node'] = 0;
+        }
+        else {
+          /// If we 've already started processing, remember the last URI so we can paginate from there later.
+          $objects = $s3->get_object_list($bucket,
+            array('max-keys' => $limit, 'marker' => $context['sandbox']['last_key']));
+
+          // Reset the last_key for this new batch.
+          $context['sandbox']['last_key'] = $objects[$limit - 1];
+        }
+
+        $context['sandbox']['max'] = $total;
+
+        // Grab each individual object's metadata and inset it into the database.
+        foreach ($objects as $object) {
+          //$metadata = $wrapper->_amazons3_get_object($object);
+
+          // Fudge the metadata for now. @todo: Uncomment above call for $metadata, but try to make it faster.
+          $metadata['filesize'] = 0;
+          $metadata['timestamp'] = time();
+          $metadata['dir'] = 0;
+          $metadata['mode'] = 33279;
+          $metadata['uid'] = 0;
+          $metadata['uri'] = $object;
+
+          if ($metadata['uri'] != ' ') {
+            $context['sandbox']['progress']++;
+            $context['results'][] += $object;
+            $context['sandbox']['current_node'] = $metadata['uri'];
+            $context['message'] = t('Now processing %current of %total',
+              array('%current' => $context['sandbox']['progress'], '%total' => $total));
+
+            db_merge('amazons3_file')
+              ->key(array('uri' => $metadata['uri']))
+              ->fields($metadata)
+              ->execute();
+
+            if ($context['sandbox']['progress'] != $context['sandbox']['max']) {
+              $context['finished'] = $context['sandbox']['progress'] / $context['sandbox']['max'];
+            }
+          }
+        }
+      } catch (RequestCore_Exception $e) {
+        if (strstr($e->getMessage(), 'SSL certificate problem')) {
+          drupal_set_message('There was a problem with the SSL certificate. Try setting AWS_CERTIFICATE_AUTHORITY to true in "libraries/awssdk/config.inc.php".
+        You may also have a curl library (e.g. the default shipped with MAMP) that does not contain trust certificates for the major authorities.', 'error');
+        }
+        else {
+          drupal_set_message('There was a problem connecting to S3: ' . $e, 'error');
+        }
+
+      }
+      catch (Exception $e) {
+        drupal_set_message('There was a problem using S3: ' . $e, 'error');
+      }
+    }
+  }
+}
+
+/**
+ * Batch 'finished' callback
+ */
+function amazons3_rebuild_process_finished($success, $results, $operations)
+{
+  if ($success) {
+    $message = t('Cache successfully rebuilt. !count files were added.', array('!count' => count($results)));
+  }
+  else {
+    $error_operation = reset($operations);
+    $message = t('An error occurred while processing %error_operation with arguments: @arguments', array(
+      '%error_operation' => $error_operation[0],
+      '@arguments' => print_r($error_operation[1], TRUE)
+    ));
+  }
+  drupal_set_message($message);
+}
\ No newline at end of file
