Index: modules/update/update-confirm.tpl.php
===================================================================
RCS file: modules/update/theme/update-confirm.tpl.php
diff -N modules/update/theme/update-confirm.tpl.php
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ modules/update/update-confirm.tpl.php	2 Jul 2009 04:11:57 -0000
@@ -0,0 +1,12 @@
+<h3> <?php print t('Step 1: Backup your site')?> </h3>
+
+<p><?php print t('We do not currently have a web based backup tool.  <a href="@backup_url">Learn more about how to take a
+backup</a>.', array ('@backup_url' => url('http://drupal.org/node/22281')));
+?></p>
+
+<br style="clear:both"/>
+<h3> <?php print t('Step 2: Provide your server connection details');?> </h3>
+
+<?php
+print drupal_render_children($form);
+?>
\ No newline at end of file
Index: modules/update/update.admin.inc
===================================================================
RCS file: modules/update/update.admin.inc
diff -N modules/update/update.admin.inc
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ modules/update/update.admin.inc	2 Jul 2009 04:11:59 -0000
@@ -0,0 +1,297 @@
+<?php
+// $Id$
+
+function update_update_form($form_state) {
+  
+  $form = array();
+  
+  if (isset($_SESSION['update_batch_results']) && $results = $_SESSION['update_batch_results']) {
+    unset($_SESSION['update_batch_results']);
+    _update_finished_report($form, $results);
+    return $form;
+  }
+  
+  //General elements and properties we want on both forms.
+  $form['#action'] = url('admin/update');
+  $form['submit'] = array(
+        '#name' => 'submit',
+        '#type' => 'submit',
+        '#value' => t('Continue'),
+        '#weight' => 100,
+  );
+  
+  if(!isset($form_state['values'])) {
+    //First step.
+    _update_available_updates_form($form);
+    return $form;
+  }
+  
+  if (is_array($form_state['values']) && array_filter($form_state['values']['extensions_to_update'])) {
+    //we've already submitted once and there are updates to deal with.
+    //if there is a form error, we show the list of possible FileTransfer backends.
+    _update_confirmation_form($form, $form_state);
+    return $form;
+  }
+}
+
+
+function _update_finished_report(&$form, $results) {
+  drupal_set_title(t("Update / Installation report"));
+    
+  if ($results['success']) {
+    drupal_set_message(t("Update was completed successfully!  Your site has been taken out of maintinance mode."));
+  } else {
+    drupal_set_message(t("Update failed! See the log below for more information. Your site is still in maintinance mode"), 'error');
+  }
+  
+  $form['log'] = array (
+    '#type' => 'item',
+    '#title' => t('Test results'),
+    '#markup' => theme('item_list', $results['messages']),
+  );
+}
+
+function _update_available_updates_form(&$form) {
+  
+  if ($available = update_get_available(TRUE)) {
+    module_load_include('inc', 'update', 'update.compare');
+    $extension_data = update_calculate_project_data($available);
+    foreach ($extension_data as $name => $project) {
+      //Filter out extensions which are dev versions, updated or core
+      if (($project['install_type'] == 'dev') || ($project['status'] == UPDATE_CURRENT) || ($project['project_type'] == 'core')) {
+        unset($extension_data[$name]);
+      }
+    }
+  }
+  else {
+    unset($form);
+    $form = array();
+    $form['message'] = array (
+      '#type' => 'item',
+      '#markup' => t('There was a problem getting update information.  Please try again later.'),
+    );
+    return;
+    //SHould do something like this:
+    //return theme('update_report', _update_no_data());
+  }
+  //First Step, select add-ons
+  $options = array();
+  foreach ($extension_data as $name => $project) {
+    $options[$name]['title'] = check_plain($project['title']);
+    $options[$name]['type'] = check_plain($project['project_type']);
+    $options[$name]['status'] = theme('update_status_project_status',$project['status']);
+    $options[$name]['installed_version'] = $project['existing_version'];
+    $options[$name]['recommended_version'] = $project['recommended'];
+  }
+  
+  if (!$options) {
+    $form = array();
+    $form['message'] = array (
+      '#type' => 'item',
+      '#markup' => t('All of your extensions are up to date.'),
+    );
+    return $form;
+  }
+  
+  $form['extensions_to_update'] = array (
+    '#type' => 'tableselect',
+    '#title' => 'choose',
+    '#options' => $options,
+    '#header' => array('title' => t('Title'),'type' => t('Type'), 'status' => t('Status'), 'installed_version' => t('Installed version'), 'recommended_version' => t('Recommended version')),
+  );
+}
+
+
+function _update_confirmation_form(&$form, &$form_state) {
+  
+  /**
+   * This is turning into a clusterfuck.
+   */
+  
+  $form['submit']['#name'] = 'process_updates';
+  if (variable_get('site_offline', FALSE) == FALSE) {
+    $form['submit']['#value'] = t('Put site into maintinance mode and install updates');
+  } else {
+    $form['submit']['#value'] = t('Install updates');
+  }
+  
+  $form['extensions_to_update'] = array (
+    '#type' => 'value',
+    '#value' => array_filter(array_values($form_state['values']['extensions_to_update'])),
+  );
+  
+  $form['#theme'] = 'update_confirm';
+  
+  
+  //Get all the available ways to transfer files
+  $available_backends = module_invoke_all('filetransfer_backends');
+  if (count($available_backends) == 0) {
+    unset($form['submit']);
+    //@TODO: Clean up this error handling
+    drupal_set_message(t('Unable to continue, not available methods fo file transfer'));
+  }
+  
+  uasort($available_backends, 'drupal_sort_weight');
+  //Get the currently selected option or previously saved option
+  $update_filetransfer_preferred = variable_get('update_filetransfer_preferred', NULL);
+  
+  if (isset($form_state['values']['connection_settings']['update_filetransfer_preferred'])) {
+    //We prefer the one last submitted.
+    $update_filetransfer_preferred = $form_state['values']['connection_settings']['update_filetransfer_preferred'];
+  }
+  
+  //If unset, take the first one available
+  $update_filetransfer_preferred = empty($update_filetransfer_preferred) ? key($available_backends) : $update_filetransfer_preferred;
+  
+  $form['connection_settings']['update_filetransfer_preferred'] = array (
+    '#type' => 'select',
+    '#title' => t('Connection method'),
+    '#default_value' => array($update_filetransfer_preferred => $available_backends[$update_filetransfer_preferred]['title']),
+    //@TODO: Add a JS handler for this to make it refresh the form.
+  );
+  
+  //Build a hidden fieldset for each one
+  foreach ($available_backends as $name => $backend) {
+    $form['connection_settings']['update_filetransfer_preferred']['#options'][$name] = $backend['title'];
+    $form['connection_settings'][$name] = array (
+      '#type' => 'fieldset',
+      '#attributes' =>array('class' => "filetransfer-$name", 'style' => 'display:none'), //we add this so we can show/hide later
+      '#title' => t('@backend connection settings', array('@backend' => $backend['title'])),
+    );
+    $current_settings = update_get_filetransfer_settings($name);
+    $form['connection_settings'][$name] += system_get_filetransfer_settings_form($name, $current_settings);
+  }
+  
+  //If the user has already picked one before, show it.
+  if ($update_filetransfer_preferred) {
+    $form['connection_settings'][$update_filetransfer_preferred]['#attributes']['style'] = '';
+  } else {
+    $form['connection_settings']['update_filetransfer_preferred']['#type'] = 'radios';
+  }
+  
+  $form['connection_settings']['#tree'] = TRUE;
+  
+  drupal_add_js(drupal_get_path('module', 'update').'/update.js', 'file');
+}
+
+
+function update_update_form_validate($form, &$form_state) {
+  //form_set_error('connection_settings', "poo");
+  if (isset($form_state['values']['connection_settings'])) {
+    _update_validate_filetransfer_settings($form, &$form_state);
+  }
+}
+
+function _update_validate_filetransfer_settings($form, &$form_state) {
+  $filetransfer_backend_to_test = $form_state['values']['connection_settings']['update_filetransfer_preferred'];
+  $connection_settings = $form_state['values']['connection_settings'][$filetransfer_backend_to_test];
+  $available_backends = module_invoke_all('filetransfer_backends');
+  $filetransfer_class = $available_backends[$filetransfer_backend_to_test]['class'];
+  $filetransfer = call_user_func_array("{$filetransfer_class}::factory", array(DRUPAL_ROOT, $connection_settings));
+  try {
+    $filetransfer->connect();
+  } catch(Exception $e) {
+    form_set_error('connection_settings', $e->getMessage());
+  }
+}
+
+function update_update_form_submit($form, &$form_state) {
+  //We always want to rebuild unless we are done.
+  $form_state['rebuild'] = TRUE;
+  
+  if ($form_state['clicked_button']['#name'] == 'process_updates') {
+    //save the connection settings to the DB.
+    $filetransfer_backend_to_use = $form_state['values']['connection_settings']['update_filetransfer_preferred'];
+    $connection_settings = $form_state['values']['connection_settings'][$filetransfer_backend_to_use];
+    
+    //This is a hack, perhaps we should add an attribute to non-stored fields?
+    //For SSH keys we wouldn't want to use a password field for instance.
+    $connection_settings_to_save = array();
+    foreach ($connection_settings as $key => $value) {
+      if ($form['connection_settings'][$filetransfer_backend_to_use][$key]['#type'] != 'password') {
+        $connection_settings_to_save[$key] = $value;
+      }
+    }
+    //Set this one as the preferred update method.
+    variable_set('update_filetransfer_preferred', $filetransfer_backend_to_use);
+    //Save the connection settings minus the password
+    update_set_filetransfer_settings($filetransfer_backend_to_use, $connection_settings_to_save);
+    
+    $operations = array();
+    foreach ($form_state['values']['extensions_to_update'] as $extension) {
+      //HACK: Bad workflow here... don't know how to determine what type of extension this is.
+      $extension_type = UPDATE_EXTENSION_TYPE_MODULE;
+      //put this in the begining (download everything first)
+      $operations['1_get_' . $extension] = array('update_get_extension', array($extension));
+      //put these on the end
+      $operations['2_put_' . $extension] = array(
+        'update_copy_extension',
+        array(
+          $extension,
+          $extension_type,
+          update_get_default_filetransfer($connection_settings)
+        ),
+      );
+      $operations['3_install_' . $extension] = array('update_install_extension', array($extension));
+    }
+    ksort($operations);
+    
+    $batch = array(
+      'title' => t('Installing updates'),
+      'init_message' => t('Preparing update operation'),
+      'operations' => $operations,
+      'finished' => 'update_finished',
+    );
+    batch_set($batch);
+    // When not in a form submit handler (which are the 'natural' places
+    // to set up a  batch processing), you additionally have to manually
+    // trigger redirection to the processing / progress bar page, using:
+    batch_process();
+    //theme('update_report', )
+  }
+}
+
+
+/**
+ *  Returns an icon
+ */
+function theme_update_status_project_status($status) {
+  switch ($status) {
+      case UPDATE_CURRENT:
+        $class = 'ok';
+        $icon = theme('image', 'misc/watchdog-ok.png', t('ok'), t('ok'));
+        break;
+      case UPDATE_UNKNOWN:
+      case UPDATE_NOT_FETCHED:
+        $class = 'unknown';
+        $icon = theme('image', 'misc/watchdog-warning.png', t('warning'), t('warning'));
+        break;
+      case UPDATE_NOT_SECURE:
+      case UPDATE_REVOKED:
+      case UPDATE_NOT_SUPPORTED:
+        $class = 'error';
+        $icon = theme('image', 'misc/watchdog-error.png', t('error'), t('error'));
+        break;
+      case UPDATE_NOT_CHECKED:
+      case UPDATE_NOT_CURRENT:
+      default:
+        $class = 'warning';
+        $icon = theme('image', 'misc/watchdog-warning.png', t('warning'), t('warning'));
+        break;
+    }
+    return $icon;
+}
+
+
+
+/**
+ * Theme project status report.
+ *
+ * @ingroup themeable
+ */
+function theme_update_available_updates_form($data) {
+  foreach ($data as $project) {
+    $rows[] = _update_build_project_status_row($project);
+  }
+  return theme('table', array('', 'Name', 'Type', 'Status'), $rows);
+}
Index: modules/update/update.info
===================================================================
RCS file: /cvs/drupal/drupal/modules/update/update.info,v
retrieving revision 1.5
diff -u -r1.5 update.info
--- modules/update/update.info	11 Oct 2008 02:33:12 -0000	1.5
+++ modules/update/update.info	2 Jul 2009 04:11:59 -0000
@@ -7,6 +7,7 @@
 files[] = update.module
 files[] = update.compare.inc
 files[] = update.fetch.inc
-files[] = update.report.inc
+files[] = update.admin.inc
 files[] = update.settings.inc
 files[] = update.install
+files[] = update.js
Index: modules/update/update.js
===================================================================
RCS file: modules/update/update.js
diff -N modules/update/update.js
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ modules/update/update.js	2 Jul 2009 04:11:59 -0000
@@ -0,0 +1,37 @@
+(function ($) {
+
+Drupal.behaviors.updateFileTransferForm = {
+  attach: function(context) {
+    Drupal.updateFileTransferForm = new Drupal.updateFileTransferForm($('#update-update-form'), Drupal.settings.updateFileTransferForm);
+  }
+}
+Drupal.updateFileTransferForm = function(form, data) {
+  var self = this;
+  this.form = form;
+  this.formSettings = data;
+
+  this.getFiletransferSelectBox().bind('change', function() {
+    self.changeFileTransferType();
+  });
+  
+  this.changeFileTransferType();
+
+};
+
+Drupal.updateFileTransferForm.prototype.getFiletransferSelectBox = function() {
+    return $("#edit-connection-settings-update-filetransfer-preferred", this.form);
+}
+
+Drupal.updateFileTransferForm.prototype.changeFileTransferType = function(){
+  
+  // take note of "self", there's a reason its there
+  var self = this;
+   
+  var currently_selected = this.getFiletransferSelectBox().val();
+  //show one form or the other.
+  $('fieldset', self.form).hide();
+  $('.filetransfer-' + currently_selected, self.form).fadeIn(200);
+}
+
+
+})(jQuery);
\ No newline at end of file
Index: modules/update/update.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/update/update.module,v
retrieving revision 1.37
diff -u -r1.37 update.module
--- modules/update/update.module	8 Jun 2009 05:00:11 -0000	1.37
+++ modules/update/update.module	2 Jul 2009 04:12:00 -0000
@@ -62,6 +62,16 @@
 define('UPDATE_MAX_FETCH_ATTEMPTS', 2);
 
 /**
+ * Used when determining how to act on a given extension when removing / installing
+ */
+define('UPDATE_EXTENSION_TYPE_MODULE', 1);
+
+/**
+ * Used when determining how to act on a given extension when removing / installing
+ */
+define('UPDATE_EXTENSION_TYPE_THEME', 2);
+
+/**
  * Implement hook_help().
  */
 function update_help($path, $arg) {
@@ -69,8 +79,9 @@
     case 'admin/reports/updates':
       global $base_url;
       $output = '<p>' . t('Here you can find information about available updates for your installed modules and themes. Note that each module or theme is part of a "project", which may or may not have the same name, and might include multiple modules or themes within it.') . '</p>';
-      $output .= '<p>' . t('To extend the functionality or to change the look of your site, a number of contributed <a href="@modules">modules</a> and <a href="@themes">themes</a> are available.', array('@modules' => 'http://drupal.org/project/modules', '@themes' => 'http://drupal.org/project/themes')) . '</p>';
-      $output .= '<p>' . t('Each time Drupal core or a contributed module or theme is updated, it is important that <a href="@update-php">update.php</a> is run.', array('@update-php' => url($base_url . '/update.php', array('external' => TRUE)))) . '</p>';
+      $output .= t('<strong>Key: </strong> !warning_icon Recommended update', array('!warning_icon' => theme('image', 'misc/watchdog-warning.png', t('warning'), t('warning'))));
+      $output .= t('&nbsp &nbsp; !error_icon Critical update / Security fix', array('!error_icon' => theme('image', 'misc/watchdog-error.png', t('error'), t('error'))));
+      
       return $output;
     case 'admin/build/themes':
     case 'admin/build/modules':
@@ -87,6 +98,12 @@
           }
         }
       }
+      
+    case 'admin/settings/updates/server-settings':
+      $output = '<p>' . t('In order to update / extend your site from the browser you need to configure how to connect to <strong>your</strong> server.');
+      $output .= t('  If you do not know what to enter on this form, please contact your hosting provider for help.') .'</p>';
+      return $output;
+    break;
 
     case 'admin/reports/updates/settings':
     case 'admin/reports/status':
@@ -125,14 +142,23 @@
  */
 function update_menu() {
   $items = array();
-
+  
   $items['admin/reports/updates'] = array(
     'title' => 'Available updates',
     'description' => 'Get a status report about available updates for your installed modules and themes.',
-    'page callback' => 'update_status',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('update_update_form'),
     'access arguments' => array('administer site configuration'),
     'weight' => 10,
   );
+  $items['admin/update'] = array(
+    'title' => 'Updating modules and themes',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('update_update_form'),
+    'access arguments' => array('administer site configuration'),
+    'weight' => 10,
+    'type' => MENU_CALLBACK,
+  );
   $items['admin/settings/updates'] = array(
     'title' => 'Updates',
     'description' => 'Change frequency of checks for available updates to your installed modules and themes, and how you would like to be notified.',
@@ -140,6 +166,19 @@
     'page arguments' => array('update_settings'),
     'access arguments' => array('administer site configuration'),
   );
+  $items['admin/settings/updates/updates'] = array(
+    'title' => 'Updates',
+    'type' => MENU_DEFAULT_LOCAL_TASK,
+    'weight' => -10,
+  );
+  $items['admin/settings/updates/server-settings'] = array(
+    'title' => 'Server Settings',
+    'page title' => 'Server Settings',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('update_settings_filetransfer'),
+    'access arguments' => array('administer site configuration'),
+    'type' => MENU_LOCAL_TASK,
+  );
   $items['admin/reports/updates/check'] = array(
     'title' => 'Manual update check',
     'page callback' => 'update_manual_status',
@@ -158,9 +197,19 @@
     'update_settings' => array(
       'arguments' => array('form' => NULL),
     ),
+    'update_confirm' => array(
+      'template' => 'theme/update-confirm',
+      'arguments' => array('form' => NULL),
+    ),
     'update_report' => array(
       'arguments' => array('data' => NULL),
     ),
+    'update_report_form' => array(
+      'arguments' => array('form' => NULL),
+    ),
+    'update_status_project_status' => array(
+      'arguments' => array('status' => NULL),
+    ),
     'update_version' => array(
       'arguments' => array('version' => NULL, 'tag' => NULL, 'class' => NULL),
     ),
@@ -632,3 +681,144 @@
 /**
  * @} End of "defgroup update_status_cache".
  */
+
+function _update_get_latest_version($name) {
+  if ($available = update_get_available(FALSE)) {
+    module_load_include('inc', 'update', 'update.compare');
+    $extension_data = update_calculate_project_data($available);
+    $project = $extension_data[$name];
+    return $project['releases'][$project['latest_version']];
+  }
+}
+
+
+function update_get_file($url) {
+  // Get each of the specified files.
+  $parsed_url = parse_url($url);
+  $local = file_directory_temp() . '/update-cache/' . basename($parsed_url['path']);
+  if (!file_exists(file_directory_temp() . '/update-cache/')) {
+    mkdir(file_directory_temp() . '/update-cache/');
+  }
+  // Check the cache and download the file if needed.
+  if (!file_exists($local)) {
+    // $result->data is the actual contents of the downloaded file. This saves
+    // it into a local file, whose path is stored in $local. $local is stored
+    // relative to the Drupal installation.
+    return system_retrieve_file($url, $local);
+  } else {
+    return $local;
+  }
+}
+
+function update_untar($file) {
+  $extraction_dir = file_directory_temp() . '/update-extraction';
+  if (!file_exists($extraction_dir)) {
+    mkdir($extraction_dir);
+  }
+  $archive_tar = new Archive_Tar($file);
+  return $archive_tar->extract($extraction_dir);
+}
+
+/**
+ * Helper function to determine if it is a module or a theme
+ */
+function _update_get_extension_type($directory) {
+  if (count(file_scan_directory($directory, "/\.module$/")) > 0) {
+    return UPDATE_EXTENSION_TYPE_MODULE;
+  }
+  else {
+    return UPDATE_EXTENSION_TYPE_THEME;
+  }
+}
+
+/**
+ * Batch operations
+ */ 
+
+function update_get_extension($extension_name, &$context) {
+  if (!isset($context['sandbox']['starting'])) {
+    $context['sandbox']['starting'] = 1;
+    $context['message'] = t('Downloading %extension', array('%extension' => $extension_name));
+    $context['finished'] = 1 / 2;
+    return;
+  }
+  $latest_version = _update_get_latest_version($extension_name);
+  if ($local_cache = update_get_file($latest_version['download_link'])) {
+    watchdog('update', t('Downloaded %extension to %local_cache', array('%extension' => $extension_name, '%local_cache' => $local_cache)));
+  } else {
+    $context['success'] = FALSE;
+    $content['results'][] = t('Failed to download %extension', array('%extension' => $extension_name));
+  }
+  
+  $context['finished'] = 1;
+}
+
+function update_copy_extension($extension_name, $extension_type, $filetransfer, &$context) {
+  if (!isset($context['sandbox']['starting'])) {
+    $context['sandbox']['starting'] = 1;
+    $context['message'] = t('Copying %extension to server', array('%extension' => $extension_name));
+    $context['finished'] = .5;
+    return;
+  }
+  
+  $latest_version = _update_get_latest_version($extension_name);
+  $local_cache = update_get_file($latest_version['download_link']);
+  $extension_destination_dir = ($extension_type == UPDATE_EXTENSION_TYPE_MODULE) ? DRUPAL_ROOT . '/sites/all/modules/' . $extension_name : DRUPAL_ROOT . '/sites/all/themes/' . $extension_name;
+  if (update_untar($local_cache)) {
+    $extension_source_dir = file_directory_temp() . '/update-extraction/' . $extension_name;  
+  }
+  try {
+    $filetransfer->removeDirectory($extension_destination_dir);
+    dd('Removed the dir ' . $extension_destination_dir);
+    $filetransfer->copyDirectory($extension_source_dir, $extension_destination_dir);
+    dd('Coppied the dir ' . $extension_source_dir . 'to' . $extension_destination_dir);
+  } catch(Exception $e) {
+    drupal_set_message(t($e->getMessage(), $e->arguments), 'error');
+    //Some better error handling is needed, but batch API doesn't seem to support any.
+    throw $e;
+  }
+  
+  $context['finished'] = 1;
+  
+}
+
+function update_install_extension($extension_name, &$context) {
+  if (!isset($context['sandbox']['done'])) {
+    $context['sandbox']['done'] = 0;
+  }
+  if (!$context['results']) {
+    $context['results'][] = t('Installed %extension', array('%extension' => $extension_name));
+  }
+  
+  $context['message'] = t('Installing %extension', array('%extension' => $extension_name));
+  sleep(1);
+  $context['sandbox']['done'] += 1;
+  $context['finished'] = $context['sandbox']['done'] / 4;
+}
+
+function update_finished($success, $results) {
+  if ($success) {
+    variable_set('site_offline', FALSE);
+  }
+  $_SESSION['update_batch_results']['success'] = $success;
+  $_SESSION['update_batch_results']['messages'] = $results;
+}
+
+function update_get_default_filetransfer($overrides = array()) {
+ //Fire up the connection class
+  $update_filetransfer_preferred = variable_get('update_filetransfer_preferred', NULL);
+  $settings = variable_get("update_filetransfer_connection_settings::{$update_filetransfer_preferred}", array());
+  $settings = array_merge($settings, $overrides);
+  $available_backends = module_invoke_all('filetransfer_backends');
+  $filetransfer = call_user_func_array("{$available_backends[$update_filetransfer_preferred]['class']}::factory", array(DRUPAL_ROOT, $settings));
+  return $filetransfer;
+}
+
+function update_get_filetransfer_settings($filetransfer_backend_name) {
+  return variable_get("update_filetransfer_connection_settings::{$filetransfer_backend_name}", array());
+}
+
+function update_set_filetransfer_settings($filetransfer_backend_name, $settings) {
+  variable_set("update_filetransfer_connection_settings::{$filetransfer_backend_name}", $settings);
+}
+
