Only in modules/book: .#book.module.1.483
Common subdirectories: modules/book-old/CVS and modules/book/CVS
diff -up modules/book-old/book.admin.inc modules/book/book.admin.inc
--- modules/book-old/book.admin.inc	2009-01-11 16:19:17.000000000 -0500
+++ modules/book/book.admin.inc	2009-01-20 11:26:03.000000000 -0500
@@ -12,9 +12,9 @@
 function book_admin_overview() {
   $rows = array();
   foreach (book_get_books() as $book) {
-    $rows[] = array(l($book['title'], $book['href'], $book['options']), l(t('edit order and titles'), "admin/content/book/" . $book['nid']));
-  }
-  $headers = array(t('Book'), t('Operations'));
+      $rows[] = array(l($book['title'], $book['href'], $book['options']), l(t('edit order and titles'), "admin/content/book/". $book['nid']), l(t('delete book'), 'admin/content/book/delete/'. $book['nid']));
+    }
+    $headers = array(t('Book'), t('Operations'), t('Delete'));
 
   return theme('table', $headers, $rows);
 }
diff -up modules/book-old/book.module modules/book/book.module
--- modules/book-old/book.module	2009-01-19 22:18:40.000000000 -0500
+++ modules/book/book.module	2009-01-22 13:29:59.000000000 -0500
@@ -159,6 +159,13 @@ function book_menu() {
     'access arguments' => array('access content'),
     'type' => MENU_CALLBACK,
   );
+  $items['admin/content/book/delete/%node'] = array(
+    'title' => 'Delete Book',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('book_delete_confirm', 4),
+    'access callback' => '_book_delete_access',
+    'type' => MENU_CALLBACK,  
+  );
 
   return $items;
 }
@@ -178,6 +185,13 @@ function _book_outline_remove_access($no
 }
 
 /**
+ * Menu item access callback - determine if the user delete entire books.
+ */
+function _book_delete_access() {
+  return user_access('administer book outlines') && user_access('bypass node access');
+}
+
+/**
  * Implementation of hook_init().
  */
 function book_init() {
@@ -1217,3 +1231,87 @@ function book_menu_subtree_data($item) {
 
   return $tree[$cid];
 }
+
+/**
+ * Menu callback.  Ask for confirmation of book deletion.
+ */
+function book_delete_confirm(&$form_state, $node) {
+  $form['bid'] = array(
+    '#type' => 'value',
+    '#value' => $node->nid,
+  );
+
+  return confirm_form($form,
+    t('Are you sure you want to delete the entire book %title?', array('%title' => $node->title)),
+    isset($_GET['destination']) ? $_GET['destination'] : 'admin/content/book',
+    t('This action cannot be undone.'),
+    t('Delete'),
+    t('Cancel')
+  );
+}
+
+/**
+ * Execute full book deletion using batch processing.
+ */
+function book_delete_confirm_submit($form, &$form_state) {
+  if ($form_state['values']['confirm']) {
+    $bid = $form_state['values']['bid'];
+    $batch = array(
+     'title' => t('Deleting book'),
+     'operations' => array(
+        array('book_delete', array($bid)),
+      ),
+     'finished' => '_book_delete_finished',
+    );
+    batch_set($batch);
+    batch_process();
+  }
+}
+
+/**
+ * Batch processing callback.  Delete an entire book 5 nodes at a time.
+ */
+function book_delete($bid, &$context) {
+  if (!isset($context['sandbox']['progress'])) {
+    $context['sandbox']['max'] = db_result(db_query("SELECT COUNT(nid) FROM {book} WHERE bid = %d", $bid));
+    $context['sandbox']['progress'] = 0;
+    $context['sandbox']['highest_nid'] = 0;
+    $context['sandbox']['bid'] = $bid;
+  }
+  
+  // Delete 5 nodes at a time.
+  $result = db_query_range("SELECT nid FROM {book} WHERE bid = %d AND nid > %d AND nid <> %d ORDER BY nid ASC", $context['sandbox']['bid'], $context['sandbox']['highest_nid'], $context['sandbox']['bid'], 0, 5);
+  while ($nid = db_result($result)) {
+    node_delete($nid);
+    // Update our progress information.
+    $context['sandbox']['progress']++;
+    $context['sandbox']['highest_nid'] = $nid;
+  }
+  
+  // Delete the top book node last.
+  if ($context['sandbox']['progress'] == $context['sandbox']['max'] - 1) {
+    node_delete($context['sandbox']['bid']);
+    $context['sandbox']['progress']++;
+  }
+
+  // Multistep processing : report progress.
+  if ($context['sandbox']['progress'] != $context['sandbox']['max']) {
+    $context['finished'] = $context['sandbox']['progress'] / $context['sandbox']['max'];
+  }
+}
+
+/**
+ * Book delete batch 'finished' callback.
+ */
+function _book_delete_finished($success, $results, $operations) {
+  if ($success) {
+    drupal_set_message(t('The book has been deleted.'));
+  }
+  else {
+    drupal_set_message(t('An error occurred and processing did not complete.'), 'error');
+    $message = format_plural(count($results), '1 item successfully deleted:', '@count items successfully deleted:');
+    $message .= theme('item_list', $results);
+    drupal_set_message($message);
+  }
+  drupal_goto('admin/content/book');
+}
\ No newline at end of file
diff -up modules/book-old/book.test modules/book/book.test
--- modules/book-old/book.test	2008-12-23 09:32:08.000000000 -0500
+++ modules/book/book.test	2009-01-22 13:30:23.000000000 -0500
@@ -7,7 +7,7 @@ class BookTestCase extends DrupalWebTest
   function getInfo() {
     return array(
       'name' => t('Book functionality'),
-      'description' => t('Create a book, add pages, and test book interface.'),
+      'description' => t('Create a book, add pages, test book interface, and then delete.'),
       'group' => t('Book'),
     );
   }
@@ -23,6 +23,7 @@ class BookTestCase extends DrupalWebTest
     // Create users.
     $book_author = $this->drupalCreateUser(array('create new books', 'create book content', 'edit own book content', 'add content to books'));
     $web_user = $this->drupalCreateUser(array('access printer-friendly version'));
+    $admin_user = $this->drupalCreateUser(array('administer book outlines', 'bypass node access'));
 
     // Create new book.
     $this->drupalLogin($book_author);
@@ -76,6 +77,11 @@ class BookTestCase extends DrupalWebTest
     $this->book = $other_book;
     $this->checkBookNode($other_book, array($node), false, false, $node);
     $this->checkBookNode($node, NULL, $other_book, $other_book, false);
+    
+    // Check that the book can be deleted.
+    $this->drupalLogout();
+    $this->drupalLogin($admin_user);
+    $this->deleteBook($book->nid, $book->title);
   }
 
   /**
@@ -171,6 +177,31 @@ class BookTestCase extends DrupalWebTest
 
     return $node;
   }
+  
+  /**
+   * Delete the entire Book
+   *
+   * @param integer $book_nid the Book's node id.
+   * @param string $book_title the Book's title.
+   */
+  function deleteBook($book_nid, $book_title) {
+    /// Make sure the book pages are in the database and the delete link shows up on the book list page.
+    $this->assertFalse(db_result(db_query('SELECT COUNT(nid) FROM {book} WHERE bid = %d', $book_nid)) == 0, t('Book pages are listed in database.'));
+    $this->drupalGet('admin/content/book/list');
+    $this->assertRaw(l(t('delete book'), 'admin/content/book/delete/' .$book_nid), t('The delete link shows up on the book list'));
+    
+    // Delete the book
+    $edit = array();
+    $this->drupalGet('admin/content/book/delete/' .$book_nid);
+    $this->assertRaw(t('Are you sure you want to delete the entire book %title?', array('%title' => $book_title)), t('Confirm that user wants to delete book.'));
+    $this->drupalPost(NULL, $edit, t('Delete'));
+
+    // Confirm deletion.
+    $this->assertRaw(t('The book has been deleted.'), t('Message confirms deletion'));
+    $this->drupalGet('admin/content/book/list');
+    $this->assertNoRaw(l(t('delete book'), 'admin/content/book/delete/'. $book_nid), t('The delete link does not show up on the book list'));
+    $this->assertTrue(db_result(db_query('SELECT COUNT(nid) FROM {book} WHERE bid = %d', $book_nid)) == 0, t('Book pages not in database.'));   
+  }
 }
 
 class BookBlockTestCase extends DrupalWebTestCase {
