=== modified file 'feeds.module'
--- feeds.module	2009-11-16 14:52:38 +0000
+++ feeds.module	2009-11-30 21:24:26 +0000
@@ -257,14 +257,14 @@
             $source->addConfig($node->feeds);
             $result = $importer->fetcher->fetch($source);
             $result = $importer->parser->parse($result, $source);
-            if (!isset($result->value['title']) || trim($result->value['title']) == '') {
-              form_set_error('title', t('Could not retrieve title from feed.'), 'error');
-            }
-            else {
+            if ($result instanceof FeedsSyndicationParserResultInterface && $title = trim($result->getTitle())) {
               // Keep the title in a static cache and populate $node->title on
               // 'presave' as node module looses any changes to $node after
               // 'validate'.
-              $last_title = $result->value['title'];
+              $last_title = $title;
+            }
+            else {
+              form_set_error('title', t('Could not retrieve title from feed.'), 'error');
             }
           }
           catch (Exception $e) {

=== modified file 'includes/FeedsImporter.inc'
--- includes/FeedsImporter.inc	2009-10-21 22:49:47 +0000
+++ includes/FeedsImporter.inc	2009-11-30 21:24:26 +0000
@@ -11,54 +11,77 @@
 require_once(dirname(__FILE__) .'/FeedsSource.inc');
 
 /**
+ * Abstraction of a file handled by Feeds.
+ *
+ */
+interface FeedsFileInterface {
+  /**
+   * @var A string containg the default mime type to use when a
+   * FeedsFileInterface's mime type cannot be determined.
+   */
+  const DEFAULT_MIME_TYPE = 'application/octet-stream';
+
+  /**
+   * Get the content of the file as a string. When the content of the file
+   * cannot be represented safely as a string, the implementation should throw
+   * an exception.
+   *
+   * @return A strign containing the content of the file.
+   */
+  public function getContent();
+
+  /**
+   * Get the filename of a file for tthis FeedsFileInterface. If the actual
+   * file is a remote one, implementation should download  it to a temporary
+   * local file. This allow transparent access to the file by consumers.
+   *
+   * @return A string containing the filename of a local file.
+   */
+  public function getFile();
+
+  /**
+   * Returns the interne type of the file. The returned value must
+   * never be empty. Implementations should return
+   * <code>application/octet-stream</code> when a correct mime type cannot be
+   * determined.
+   *
+   * @return A string containing the internet media type of the file.
+   *
+   * @see FeedResultEnclosure::DEFAULT_MIME_TYPE
+   */
+  public function getMimeType();
+}
+
+/**
  * A Feeds result class.
  *
- * @see class FeedsFetcherResult
- * @see class FeedsParserResult
- */
-abstract class FeedsResult {
-
-  // An array of valid values for $type.
-  protected $valid_types = array();
-  // The type of this result.
-  protected $type;
-  // The value of this result.
-  protected $value;
-
-  /**
-   * Constructor: create object, validate class variables.
-   *
-   * @param $value
-   *   The value of this result.
-   * @param $type
-   *   The type of this result. Must be one of $valid_types.
-   */
-  public function __construct($value, $type) {
-    $this->__set('type', $type);
-    $this->__set('value', $value);
-  }
-
-  /**
-   * Control access to class variables.
-   */
-  public function __set($name, $value) {
-    if ($name == 'valid_types') {
-      throw new Exception(t('Cannot write FeedsResult::valid_types.'));
-    }
-    if ($name == 'type') {
-      if (!in_array($value, $this->valid_types)) {
-        throw new Exception(t('Invalid type "!type"', array('!type' => $value)));
-      }
-    }
-    $this->$name = $value;
-  }
-
-  /**
-   * Control access to class variables.
-   */
-  public function __get($name) {
-    return $this->$name;
-  }
+ * @see class FeedsFetcherResultInterface
+ * @see class FeedsParserResultInterface
+ */
+interface FeedsResultInterface {
+
+}
+
+/**
+ * Minimalist interface for <em>enclosures</em> (ie. a file attached to a
+ * FeedsResult item)
+ */
+interface FeedsResultEnclosureInterface extends FeedsFileInterface {
+  /**
+   * Returns the URL for the enclosure. Any valid URL is an acceptable result.
+   *
+   * @return A string containing a valid URL (RFC 1738) for the enclosure.
+   */
+  public function getUrl();
+
+  /**
+   * Returns the enclosure description.
+   *
+   * @return A string containing the description of the enclosure.
+   *
+   */
+  public function getDescription();
+
 }
 
 /**

=== modified file 'plugins/FeedsCSVParser.inc'
--- plugins/FeedsCSVParser.inc	2009-10-20 21:03:30 +0000
+++ plugins/FeedsCSVParser.inc	2009-11-30 21:26:51 +0000
@@ -9,16 +9,10 @@
   /**
    * Parses a raw string and returns a Feed object from it.
    */
-  public function parse(FeedsFetcherResult $fetcherResult, FeedsSource $source) {
+  public function parse(FeedsFetcherResultInterface $fetcherResult, FeedsSource $source) {
     feeds_include_library('ParserCSV.inc', 'ParserCSV');
 
-    if ($fetcherResult->type == 'text/filepath') {
-      $iterator = new ParserCSVIterator(realpath($fetcherResult->value));
-    }
-    // @todo: write string buffer iterator.
-    else {
-      throw new Exception(t('You must use CSV Parser with File Fetcher.'));
-    }
+    $iterator = new ParserCSVIterator(realpath($fetcherResult->getFile()));
 
     // Parse.
     $source_config = $source->getConfigFor($this);

=== modified file 'plugins/FeedsDataProcessor.inc'
--- plugins/FeedsDataProcessor.inc	2009-11-02 20:18:35 +0000
+++ plugins/FeedsDataProcessor.inc	2009-11-30 21:28:00 +0000
@@ -14,12 +14,12 @@
   /**
    * Implementation of FeedsProcessor::process().
    */
-  public function process(FeedsParserResult $parserResult, FeedsSource $source) {
+  public function process(FeedsParserResultInterface $parserResult, FeedsSource $source) {
 
     // Count number of created and updated nodes.
     $inserted  = $updated = 0;
 
-    foreach ($parserResult->value['items'] as $item) {
+    foreach ($parserResult->getItems() as $item) {
       if (!($id = $this->existingItemId($item, $source)) || $this->config['update_existing']) {
         // Map item to a data record, feed_nid and timestamp are mandatory.
         $data = array();

=== modified file 'plugins/FeedsFeedNodeProcessor.inc'
--- plugins/FeedsFeedNodeProcessor.inc	2009-11-18 16:53:48 +0000
+++ plugins/FeedsFeedNodeProcessor.inc	2009-11-30 21:28:27 +0000
@@ -15,12 +15,12 @@
   /**
    * Implementation of FeedsProcessor::process().
    */
-  public function process(FeedsParserResult $parserResult, FeedsSource $source) {
+  public function process(FeedsParserResultInterface $parserResult, FeedsSource $source) {
 
     // Count number of created and updated nodes.
     $created  = $updated = 0;
 
-    foreach ($parserResult->value['items'] as $item) {
+    foreach ($parserResult->getItems() as $item) {
 
       // If the target item does not exist OR if update_existing is enabled,
       // map and save.

=== modified file 'plugins/FeedsFetcher.inc'
--- plugins/FeedsFetcher.inc	2009-10-20 21:03:30 +0000
+++ plugins/FeedsFetcher.inc	2009-11-30 21:24:26 +0000
@@ -4,12 +4,8 @@
 /**
  * Defines the object a Fetcher returns on fetch().
  */
-class FeedsFetcherResult extends FeedsResult {
-  // Define valid types.
-  // @todo: does text/filepath make sense?
-  // @todo: If convenient, we could expand on this concept and build content
-  //   type negotiation between Fetchers and Parsers.
-  protected $valid_types = array('text/filepath', 'text/xml');
+interface FeedsFetcherResultInterface extends FeedsResultInterface, FeedsFileInterface {
+
 }
 
 /**

=== modified file 'plugins/FeedsFileFetcher.inc'
--- plugins/FeedsFileFetcher.inc	2009-10-20 21:03:30 +0000
+++ plugins/FeedsFileFetcher.inc	2009-11-30 21:24:26 +0000
@@ -18,7 +18,7 @@
     $source_config = $source->getConfigFor($this);
     // Just return path to file, contents can be read easily with
     // file_get_contents($file_path);
-    return new FeedsFetcherResult($source_config['source'], 'text/filepath');
+    return new FeedsFileFetcherResult($source_config['source']);
   }
 
   /**
@@ -61,4 +61,29 @@
       form_set_error('feeds][source', t('File needs to point to a file in your Drupal file system path.'));
     }
   }
+}
+
+class FeedsFileFetcherResult implements FeedsFetcherResultInterface {
+
+  private $file;
+  private $mime_type;
+
+  public function __construct($file) {
+    $this->file = $file;
+  }
+
+  public function getContent() {
+    return file_get_contents($this->file);
+  }
+
+  public function getFile() {
+    return $this->file;
+  }
+
+  public function getMimeType() {
+    if(!isset($this->mime_type)) {
+      $this->mime_type = file_get_mimetype($this->getFile());
+    }
+    return $this->mime_type;
+  }
 }
\ No newline at end of file

=== modified file 'plugins/FeedsHTTPFetcher.inc'
--- plugins/FeedsHTTPFetcher.inc	2009-11-17 20:14:30 +0000
+++ plugins/FeedsHTTPFetcher.inc	2009-11-30 21:24:26 +0000
@@ -34,7 +34,7 @@
     if ($result->code != 200) {
       throw new Exception(t('Download of @url failed with code !code.', array('@url' => $url, '!code' => $result->code)));
     }
-    return new FeedsFetcherResult($result->data, 'text/xml');
+    return new FeedsHTTPFetcherResult($result->data, 'text/xml');
   }
 
   /**
@@ -87,3 +87,34 @@
   }
 }
 
+class FeedsHTTPFetcherResult implements FeedsFetcherResultInterface {
+
+  private $content;
+  private $file;
+  private $mime_type;
+
+  public function __construct($content, $mime_type = 'application/octet-stream') {
+    $this->content = $content;
+    $this->mime_type = $mime_type;
+  }
+
+  public function getContent() {
+    return $this->content;
+  }
+
+  public function getFile() {
+    if(!isset($this->file)) {
+      //@todo get extension from mime_type
+      $dest = file_destination(file_directory_temp() . '/' . get_class($this). '.tmp', FILE_EXISTS_RENAME);
+      $file = file_save_data($this->content, $dest);
+      if($file === 0) {
+        throw new Exception(t('Cannot write content to %dest', array('%dest' => $dest)));
+      }
+    }
+    return $this->file;
+  }
+
+  public function getMimeType() {
+    return $this->mime_type;
+  }
+}

=== modified file 'plugins/FeedsNodeProcessor.inc'
--- plugins/FeedsNodeProcessor.inc	2009-11-24 23:20:26 +0000
+++ plugins/FeedsNodeProcessor.inc	2009-11-30 21:30:04 +0000
@@ -14,12 +14,12 @@
   /**
    * Implementation of FeedsProcessor::process().
    */
-  public function process(FeedsParserResult $parserResult, FeedsSource $source) {
+  public function process(FeedsParserResultInterface $parserResult, FeedsSource $source) {
 
     // Count number of created and updated nodes.
     $created  = $updated = 0;
 
-    foreach ($parserResult->value['items'] as $item) {
+    foreach ($parserResult->getItems() as $item) {
       // If the target item does not exist OR if update_existing is enabled,
       // map and save.
       if (!($nid = $this->existingItemId($item, $source)) || $this->config['update_existing']) {

=== modified file 'plugins/FeedsOPMLParser.inc'
--- plugins/FeedsOPMLParser.inc	2009-11-02 20:22:03 +0000
+++ plugins/FeedsOPMLParser.inc	2009-11-30 21:26:48 +0000
@@ -14,13 +14,8 @@
   /**
    * Parses a raw string and returns a Feed object from it.
    */
-  public function parse(FeedsFetcherResult $fetcherResult, FeedsSource $source) {
-    if ($fetcherResult->type == 'text/filepath') {
-      $string = file_get_contents($fetcherResult->value);
-    }
-    else {
-      $string = $fetcherResult->value;
-    }
+  public function parse(FeedsFetcherResultInterface $fetcherResult, FeedsSource $source) {
+    $string = $fetcherResult->getContent();
     feeds_include_library('opml_parser.inc', 'opml_parser');
     return new FeedsParserResult(opml_parser_parse($string), 'syndication');
   }

=== modified file 'plugins/FeedsParser.inc'
--- plugins/FeedsParser.inc	2009-10-20 21:03:30 +0000
+++ plugins/FeedsParser.inc	2009-11-30 21:24:26 +0000
@@ -2,20 +2,100 @@
 // $Id: FeedsParser.inc,v 1.1 2009/10/20 21:03:08 alexb Exp $
 
 /**
- * Defines the object a Parser returns on parser().
- */
-class FeedsParserResult extends FeedsResult {
-  // Define valid types.
-  // @todo: does this distinction make sense? We may be able to run with
-  // 'simple' and no special case for 'syndication'.
-  protected $valid_types = array('simple', 'syndication');
-
-  /**
-   * Override constructor to define a default type.
-   */
-  public function __construct($value, $type = 'simple') {
-    parent::__construct($value, $type);
-  }
+ * A parsed feed, the result of a FeedsParser::process.
+ *
+ * @see FeedsParser::process()
+ */
+interface FeedsParserResultInterface extends FeedsResultInterface {
+  /**
+   *@todo Can this method return an object implementing
+   * the ArrayAccess, Traversable and Countable interfaces ?
+   * is_array() on such an object returns FALSE. If support for object
+   * is announced, consumers have to double check with is_array()
+   * and instanceof.
+   *
+   * @return An array containing the feed items.
+   */
+  public function getItems();
+}
+
+/***
+ * Simple FeedsParserResultInterface implementation.
+ */
+class FeedsParserResult implements FeedsParserResultInterface {
+
+  private $items;
+
+  /**
+   *
+   * Create a new FeedsBaseParserResult. Set properties result from an array.
+   *
+   * @param $value
+   *   An array containing the various property of the FeedsResult.
+   */
+  public function __construct($value) {
+    $this->items = isset($value['items']) ? $value['items'] : array();
+  }
+
+  public function getItems() {
+    return $this->items;
+  }
+}
+
+interface FeedsSyndicationParserResultInterface extends FeedsParserResultInterface {
+  /**
+   *
+   * @return A string containing the link of the feed.
+   */
+  public function getLink();
+
+  /**
+   *
+   * @return A string containing the description of the feed.
+   */
+  public function getDescription();
+
+  /**
+   *
+   * @return A string containing the title of the feed.
+   */
+  public function getTitle();
+}
+
+/***
+ * Simple FeedsSyndicationParserResultInterface implementation.
+ */
+class FeedsSyndicationParserResult extends FeedsParserResult implements FeedsSyndicationParserResultInterface {
+  private $link;
+  private $title;
+  private $description;
+
+  /**
+   *
+   * Create a new FeedsBaseParserResult. Set properties result from an array.
+   *
+   * @param $value
+   *   An array containing the various property of the FeedsResult.
+   */
+  public function __construct($value) {
+    parent::__construct($value);
+    foreach(array('link', 'title', 'description') as $p) {
+      $this->{$p} = isset($value[$p]) ? $value[$p] : '';
+    }
+  }
+
+  public function getLink() {
+    return $this->link;
+  }
+
+  public function getDescription() {
+    return $this->description;
+  }
+
+  public function getTitle() {
+    return $this->title;
+  }
+
 }
 
 /**
@@ -40,7 +120,7 @@
    *
    * @todo: Should it be execute() ?
    */
-  public abstract function parse(FeedsFetcherResult $fetcherResult, FeedsSource $source);
+  public abstract function parse(FeedsFetcherResultInterface $fetcherResult, FeedsSource $source);
 
   /**
    * Clear all caches for results for given source.

=== modified file 'plugins/FeedsProcessor.inc'
--- plugins/FeedsProcessor.inc	2009-10-31 14:30:27 +0000
+++ plugins/FeedsProcessor.inc	2009-11-30 21:28:39 +0000
@@ -19,7 +19,7 @@
    *
    * @todo: Should it be execute()?
    */
-  public abstract function process(FeedsParserResult $parserResult, FeedsSource $source);
+  public abstract function process(FeedsParserResultInterface $parserResult, FeedsSource $source);
 
   /**
    * Remove all stored results or stored results up to a certain time for this

=== modified file 'plugins/FeedsSimplePieParser.inc'
--- plugins/FeedsSimplePieParser.inc	2009-11-02 20:05:10 +0000
+++ plugins/FeedsSimplePieParser.inc	2009-11-30 21:26:43 +0000
@@ -11,13 +11,9 @@
   /**
    * Parses a raw string and returns a Feed object from it.
    */
-  public function parse(FeedsFetcherResult $fetcherResult, FeedsSource $source) {
-    if ($fetcherResult->type == 'text/filepath') {
-      $string = file_get_contents($fetcherResult->value);
-    }
-    else {
-      $string = $fetcherResult->value;
-    }
+  public function parse(FeedsFetcherResultInterface $fetcherResult, FeedsSource $source) {
+    $string = $fetcherResult->getContent();
+
     feeds_include_library('simplepie.inc', 'simplepie');
 
     // Initialize SimplePie.
@@ -59,16 +55,8 @@
       $item['author_link'] = $author->link;
       $item['author_email'] = $author->email;
       // Enclosures
-      $enclosures = $simplepie_item->get_enclosures();
-      if (is_array($enclosures)) {
-        foreach ($enclosures as $enclosure) {
-          $mime = $enclosure->get_real_type();
-          if ($mime != '') {
-            list($type, $subtype) = split('/', $mime);
-            $item['enclosures'][$type][$subtype][] = $enclosure;
-          }
-        }
-      }
+      //Store the raw enclosures in the item, let getSourceElement handled it later (if needed)
+      $item['enclosures'] = $simplepie_item->get_enclosures();
       // Location
       $latitude = $simplepie_item->get_latitude();
       $longitude = $simplepie_item->get_longitude();
@@ -99,8 +87,14 @@
       $feed['items'][] = $item;
     }
     // Release parser.
+    if (version_compare(PHP_VERSION, '5.3.0', '<')) {
+      // Workaround PHP Bug #33595,
+      // see http://simplepie.org/wiki/faq/i_m_getting_memory_leaks
+      $parser->__destruct();
+    }
     unset($parser);
-    return new FeedsParserResult($feed, 'syndication');
+    unset($item);
+    return new FeedsSyndicationParserResult($feed);
   }
 
   /**
@@ -164,6 +158,31 @@
   }
 
   /**
+   * Get an element identified by $element_key of the given item.
+   * The element key corresponds to the values in the array returned by
+   * FeedsParser::getMappingSources().
+   */
+	public function getSourceElement($item, $element_key) {
+    switch($element_key) {
+      case 'enclosures':
+        $enclosures = isset($item[$element_key]) ? $item[$element_key] : FALSE;
+        $result = array();
+       if (is_array($enclosures)) {
+          foreach ($enclosures as $enclosure) {
+            $mime = $enclosure->get_real_type();
+            if ($mime != '') {
+              list($type, $subtype) = split('/', $mime);
+              $result[$type][$subtype][] = new FeedsSimplePieEnclosure($enclosure);
+            }
+          }
+        }
+        return $result;
+      default:
+        return parent::getSourceElement($item, $element_key);
+    }
+  }
+
+  /**
    * Returns cache directory. Creates it if it doesn't exist.
    */
   protected function cacheDirectory() {
@@ -181,4 +200,57 @@
     $words = array_slice($words, 0, 3);
     return implode(' ', $words);
   }
+}
+
+/**
+ * Adapter to present a SimplePie_Enclosure as a FeedResultEnclosure.
+ *
+ * @see FeedResultEnclosure
+ * @see SimplePie_Enclosure
+ */
+class FeedsSimplePieEnclosure implements FeedsResultEnclosureInterface {
+  private $simplepie_enclosure;
+
+  private $file;
+
+  function __construct(SimplePie_Enclosure $enclosure) {
+    $this->simplepie_enclosure = $enclosure;
+  }
+
+
+  public function getUrl() {
+  return $this->simplepie_enclosure->get_link();
+  }
+
+
+  public function getDescription() {
+    return $this->simplepie_enclosure->get_description();
+  }
+
+
+  public function getMimeType() {
+    $type = $this->simplepie_enclosure->get_real_type();
+    return !empty($type) ? $type : FeedsFileInterface::DEFAULT_MIME_TYPE;
+  }
+
+  public function getContent() {
+  feeds_include_library('http_request.inc', 'http_request');
+    $result = http_request_get($url);
+    if ($result->code != 200) {
+      throw new Exception(t('Download of @url failed with code !code.', array('@url' => $url, '!code' => $result->code)));
+    }
+    return file_get_contents($result->data);
+  }
+
+  public function getFile() {
+    if(!isset($this->file)) {
+      //@todo get extension from mime_type
+      $dest = file_destination(file_directory_temp() . '/' . get_class($this). '.tmp', FILE_EXISTS_RENAME);
+      $file = file_save_data($this->getContent(), $dest);
+      if($file === 0) {
+        throw new Exception(t('Cannot write content to %dest', array('%dest' => $dest)));
+      }
+    }
+    return $this->file;
+  }
 }
\ No newline at end of file

=== modified file 'plugins/FeedsSyndicationParser.inc'
--- plugins/FeedsSyndicationParser.inc	2009-11-02 19:58:37 +0000
+++ plugins/FeedsSyndicationParser.inc	2009-11-30 21:24:26 +0000
@@ -11,15 +11,10 @@
   /**
    * Parses a raw string and returns a Feed object from it.
    */
-  public function parse(FeedsFetcherResult $fetcherResult, FeedsSource $source) {
-    if ($fetcherResult->type == 'text/filepath') {
-      $string = file_get_contents($fetcherResult->value);
-    }
-    else {
-      $string = $fetcherResult->value;
-    }
+  public function parse(FeedsFetcherResultInterface $fetcherResult, FeedsSource $source) {
+    $string = $fetcherResult->getContent();
     feeds_include_library('common_syndication_parser.inc', 'common_syndication_parser');
-    return new FeedsParserResult(common_syndication_parser_parse($string), 'syndication');
+    return new FeedsSyndicationParserResult(common_syndication_parser_parse($string));
   }
 
   /**

=== modified file 'plugins/FeedsTermProcessor.inc'
--- plugins/FeedsTermProcessor.inc	2009-11-02 20:48:52 +0000
+++ plugins/FeedsTermProcessor.inc	2009-11-30 21:30:11 +0000
@@ -14,7 +14,7 @@
   /**
    * Implementation of FeedsProcessor::process().
    */
-  public function process(FeedsParserResult $parserResult, FeedsSource $source) {
+  public function process(FeedsParserResultInterface $parserResult, FeedsSource $source) {
 
     if (empty($this->config['vocabulary'])) {
       throw new Exception(t('You must define a vocabulary for Taxonomy term processor before importing.'));
@@ -23,7 +23,7 @@
     // Count number of created and updated nodes.
     $created  = $updated = $no_name = 0;
 
-    foreach ($parserResult->value['items'] as $item) {
+    foreach ($parserResult->getItems() as $item) {
 
       if (!($tid = $this->existingItemId($item, $source)) || $this->config['update_existing']) {
 

=== modified file 'plugins/FeedsUserProcessor.inc'
--- plugins/FeedsUserProcessor.inc	2009-11-02 20:26:57 +0000
+++ plugins/FeedsUserProcessor.inc	2009-11-30 21:29:53 +0000
@@ -14,12 +14,12 @@
   /**
    * Implementation of FeedsProcessor::process().
    */
-  public function process(FeedsParserResult $parserResult, FeedsSource $source) {
+  public function process(FeedsParserResultInterface $parserResult, FeedsSource $source) {
 
     // Count number of created and updated nodes.
     $created  = $updated = $failed = 0;
 
-    foreach ($parserResult->value['items'] as $item) {
+    foreach ($parserResult->getItems() as $item) {
 
       if (!($uid = $this->existingItemId($item, $source)) || $this->config['update_existing']) {
 

