Index: feeds.install
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/feeds/feeds.install,v
retrieving revision 1.13.2.1
diff -u -p -r1.13.2.1 feeds.install
--- feeds.install	25 Sep 2010 16:07:50 -0000	1.13.2.1
+++ feeds.install	5 Nov 2010 00:44:03 -0000
@@ -130,6 +130,12 @@ function feeds_schema() {
         'default' => '',
         'description' => t('The hash of the item.'),
       ),
+      'touched' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+        'description' => t('Mark when a feed item has last been touched, even if not updated.'),
+      ),
     ),
     'primary key' => array('nid'),
     'indexes' => array(
@@ -567,3 +573,20 @@ function feeds_update_6013() {
   variable_set('feeds_reschedule', TRUE);
   return array();
 }
+
+/**
+ * Add touched column to feeds_node_item.
+ */
+function feeds_update_60014() {
+  $ret = array();
+
+  $spec = array(
+    'type' => 'int',
+    'not null' => TRUE,
+    'default' => 0,
+    'description' => t('Mark when a feed item has last been touched, even if not updated.'),
+  );
+  
+  db_add_field($ret, 'feeds_node_item', 'touched', $spec);
+  return $ret;
+}
\ No newline at end of file
Index: includes/FeedsBatch.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/feeds/includes/FeedsBatch.inc,v
retrieving revision 1.15.2.1
diff -u -p -r1.15.2.1 FeedsBatch.inc
--- includes/FeedsBatch.inc	25 Oct 2010 22:43:03 -0000	1.15.2.1
+++ includes/FeedsBatch.inc	5 Nov 2010 00:44:03 -0000
@@ -150,6 +150,8 @@ class FeedsImportBatch extends FeedsBatc
   public $feed_nid;
   public $created;
   public $updated;
+  public $unpublished;
+  public $deleted;
 
   public function __construct($raw = '', $feed_nid = 0) {
     parent::__construct();
Index: plugins/FeedsNodeProcessor.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/feeds/plugins/FeedsNodeProcessor.inc,v
retrieving revision 1.51.2.2
diff -u -p -r1.51.2.2 FeedsNodeProcessor.inc
--- plugins/FeedsNodeProcessor.inc	25 Oct 2010 22:43:03 -0000	1.51.2.2
+++ plugins/FeedsNodeProcessor.inc	5 Nov 2010 00:44:04 -0000
@@ -15,6 +15,10 @@ define('FEEDS_NODE_SKIP_EXISTING', 0);
 define('FEEDS_NODE_REPLACE_EXISTING', 1);
 define('FEEDS_NODE_UPDATE_EXISTING', 2);
 
+define('FEEDS_SYNC_REMOVED_IGNORE', 0);
+define('FEEDS_SYNC_REMOVED_UNPUBLISH', 1);
+define('FEEDS_SYNC_REMOVED_DELETE', 3);
+
 /**
  * Creates nodes from feed items.
  */
@@ -32,12 +36,21 @@ class FeedsNodeProcessor extends FeedsPr
     }
 
     while ($item = $batch->shiftItem()) {
+      // TODO: There might be an existing indicator of when batch processing started?
+      if (!isset($batch_started)) $batch_started = FEEDS_REQUEST_TIME;
 
       // Create/update if item does not exist or update existing is enabled.
       if (!($nid = $this->existingItemId($batch, $source)) || ($this->config['update_existing'] != FEEDS_SKIP_EXISTING)) {
         // Only proceed if item has actually changed.
         $hash = $this->hash($item);
         if (!empty($nid) && $hash == $this->getHash($nid)) {
+          // To be able to take action on items that have been removed, just indicate that this was still in the feed.
+          db_query("UPDATE {feeds_node_item} SET touched = %d WHERE nid = %d", FEEDS_REQUEST_TIME, $nid);
+
+          // If this item was previously removed, but has now appeared again, make sure the corresponding node is published.
+          // TODO: There probably should be a message here for what just happened.
+          db_query("UPDATE {node} SET status = 1 WHERE nid = %d", $nid);
+
           continue;
         }
 
@@ -68,6 +81,11 @@ class FeedsNodeProcessor extends FeedsPr
       }
     }
 
+    // Handle nodes whose corresponding feed items have been removed.
+    if ($this->config['sync'] != FEEDS_SYNC_REMOVED_IGNORE) {
+      $this->syncNodes($batch, $source, $batch_started);
+    }
+    
     // Set messages.
     if ($batch->created) {
       drupal_set_message(format_plural($batch->created, 'Created @number @type node.', 'Created @number @type nodes.', array('@number' => $batch->created, '@type' => node_get_types('name', $this->config['content_type']))));
@@ -75,8 +93,14 @@ class FeedsNodeProcessor extends FeedsPr
     elseif ($batch->updated) {
       drupal_set_message(format_plural($batch->updated, 'Updated @number @type node.', 'Updated @number @type nodes.', array('@number' => $batch->updated, '@type' => node_get_types('name', $this->config['content_type']))));
     }
+    elseif ($batch->unpublished) {
+      drupal_set_message(format_plural($batch->unpublished, 'Unpublished @number @type node.', 'Unpublished @number @type nodes.', array('@number' => $batch->unpublished, '@type' => node_get_types('name', $this->config['content_type']))));
+    }
+    elseif ($batch->deleted) {
+      drupal_set_message(format_plural($batch->deleted, 'Deleted @number @type node.', 'Deleted @number @type nodes.', array('@number' => $batch->deleted, '@type' => node_get_types('name', $this->config['content_type']))));
+    }
     else {
-      drupal_set_message(t('There is no new content.'));
+      drupal_set_message(t('There have been no content changes.'));
     }
     $batch->setProgress(FEEDS_PROCESSING, FEEDS_BATCH_COMPLETE);
   }
@@ -138,6 +162,28 @@ class FeedsNodeProcessor extends FeedsPr
   }
 
   /**
+   * Keep nodes in sync with feed items.
+   */
+  public function syncNodes($batch, $source, $batch_started) {
+    $sql = "SELECT n.nid FROM {feeds_node_item} fni LEFT JOIN {node} n ON fni.nid = n.nid WHERE fni.feed_nid = %d AND n.status = 1 AND fni.touched < %d";
+    $result = db_query($sql, $source->feed_nid, $batch_started);
+
+    while($node = db_fetch_object($result)) {
+      if ($this->config['sync'] == FEEDS_SYNC_REMOVED_UNPUBLISH) {
+        $node = node_load(array('nid' => $node->nid));
+        $node->status = 0;
+        $node->log = 'Unpublished by FeedsNodeProcessor - '. format_date(time());
+        node_save($node);
+        $batch->unpublished++;
+      }
+      elseif ($this->config['sync'] == FEEDS_SYNC_REMOVED_DELETE) {
+        node_delete($node->nid);
+        $batch->deleted++;
+      }
+    }
+  }
+
+  /**
    * Override parent::configDefaults().
    */
   public function configDefaults() {
@@ -150,6 +196,7 @@ class FeedsNodeProcessor extends FeedsPr
       'expire' => FEEDS_EXPIRE_NEVER,
       'mappings' => array(),
       'author' => 0,
+      'sync' => FEEDS_SYNC_REMOVED_IGNORE,
     );
   }
 
@@ -205,6 +252,17 @@ class FeedsNodeProcessor extends FeedsPr
       ),
       '#default_value' => $this->config['update_existing'],
     );
+    $form['sync'] = array(
+      '#type' => 'radios',
+      '#title' => t('Keep nodes and feed items in sync'),
+      '#description' => t('Select how nodes corresponding to removed feed items should be handled. This applies if you want to only display nodes that map to items currently in the feed.'),
+      '#options' => array(
+        FEEDS_SYNC_REMOVED_IGNORE => 'Do nothing',
+        FEEDS_SYNC_REMOVED_UNPUBLISH => 'Unpublish the node',
+        FEEDS_SYNC_REMOVED_DELETE => 'Delete the node',
+      ),
+      '#default_value' => $this->config['sync'],
+    );
     return $form;
   }
 
@@ -351,6 +409,7 @@ class FeedsNodeProcessor extends FeedsPr
     if ($populate) {
       $node->type = $this->config['content_type'];
       $node->changed = FEEDS_REQUEST_TIME;
+      $node->status = 1;
       $node->format = $this->config['input_format'];
       $node->feeds_node_item = new stdClass();
       $node->feeds_node_item->id = $this->id;
@@ -358,6 +417,7 @@ class FeedsNodeProcessor extends FeedsPr
       $node->feeds_node_item->feed_nid = $feed_nid;
       $node->feeds_node_item->url = '';
       $node->feeds_node_item->guid = '';
+      $node->feeds_node_item->touched = FEEDS_REQUEST_TIME;
     }
 
     static $included;
