--- book_copy.module	2008-10-31 01:35:32.000000000 +0100
+++ book_copy_new.module	2010-08-26 15:22:11.000000000 +0200
@@ -74,82 +74,184 @@ function book_copy_link($type, $node = N
 }
 
 /**
- * This function is responsible for cloning an entire book
- * This will also maintain book hieararchy in the new book.
+ * This function kicks of the batch for cloning an entire book
+ * The batch will also maintain book hieararchy in the new book.
  *
- * if 
-*/
+ */
 function book_copy_copy_book($book, $subtree = 0) {
-  $newbid = 0;
+  if (($operations = book_copy_get_batchoperations($book, $subtree)) !== false) {
+    $batch = array(
+      'operations' => $operations,
+      'finished' => '_book_copy_batch_finished',
+      'title' => t('Derive a copy from @book', array('@book' => $book->title)),
+      'progress_message' => t('Copying in progress.'),
+    );
+    batch_set($batch);
+    batch_process('');
+  }
+}
 
+/**
+ * Return all the operations-array for the batch.
+ */
+function book_copy_get_batchoperations($book, $subtree = 0) {
+  $operations = false;
   if (isset($book->book)) {
-    $bid = $book->book['bid'];
-    $book = node_load(array('nid' => $bid));
+    $operations = array(
+      array('_book_copy_batch_init',  array($book, $subtree)),
+      array('_book_copy_batch_copy_page', array($subtree)),
+      array('_book_copy_batch_update_history', array()),
+    );
+  }
+  return $operations;
+}
 
-    if ($subtree !== 0) {
-      // only grab those nodes matching our sub-tree
-      $mlid = db_fetch_array(db_query("SELECT ml.mlid FROM {menu_links} ml LEFT JOIN {book} b ON b.mlid = ml.mlid WHERE b.nid = %d", $subtree->nid));
-      $where = 'AND (ml.plid = %d OR ml.p1 = %d OR ml.p2 = %d OR ml.p3 = %d OR ml.p4 = %d OR ml.p5 = %d OR ml.p6 = %d OR ml.p7 = %d OR ml.p8 = %d OR ml.p9 = %d)';
-      $args = array_fill(0, 10, $mlid['mlid']);
-      array_unshift($args, $bid);
-    }
-    else {
-      $where = '';
-      $args = array($bid);
-    }
+/**
+ * Initialise the batch and collect all book-pages that are to be copied.
+ */
+function _book_copy_batch_init($book, $subtree, &$context) {
+  //Init required context-variables
+  $context['results']['book_copy']['oldbid']    = $book->book['bid'];
+  $context['results']['book_copy']['newbid']    = 0;
+  $context['results']['book_copy']['newbook']   = null;
+  $context['results']['book_copy']['nidmap']    = array();
+  $context['results']['book_copy']['bookpages'] = array();
+  
+  $bid  = $book->book['bid'];
+  $book = node_load(array('nid' => $bid));
+
+  if ($subtree !== 0) {
+    // only grab those nodes matching our sub-tree
+    $mlid  = db_fetch_array(db_query("SELECT ml.mlid FROM {menu_links} ml LEFT JOIN {book} b ON b.mlid = ml.mlid WHERE b.nid = %d", $subtree->nid));
+    $where = 'AND (ml.plid = %d OR ml.p1 = %d OR ml.p2 = %d OR ml.p3 = %d OR ml.p4 = %d OR ml.p5 = %d OR ml.p6 = %d OR ml.p7 = %d OR ml.p8 = %d OR ml.p9 = %d)';
+    $args  = array_fill(0, 10, $mlid['mlid']);
+    array_unshift($args, $bid);
+  } else {
+    $where = '';
+    $args  = array($bid);
+  }
 
-    module_load_include('inc', 'clone', 'clone.pages');
+  $result = db_query("SELECT ml.menu_name, ml.mlid, ml.plid, ml.link_path, ml.router_path, ml.link_title, ml.module, b.nid
+  FROM {menu_links} ml LEFT JOIN {book} b ON b.mlid = ml.mlid WHERE b.bid = %d ". $where ." ORDER BY ml.depth", $args);
+  if ($result) {
+    $context['results']['book_copy']['bookpages'] = array();
+    while ($row = db_fetch_array($result)) {
+      $context['results']['book_copy']['bookpages'][] = $row;
+    }
+  }
+  $context['message'] = t('Collected all bookpages.');
+}
 
-    $result = db_query("SELECT ml.menu_name, ml.mlid, ml.plid, ml.link_path, ml.router_path, ml.link_title, ml.module, b.nid
-    FROM {menu_links} ml LEFT JOIN {book} b ON b.mlid = ml.mlid WHERE b.bid = %d ". $where ." ORDER BY ml.depth", $args);
-    if ($result) {
-      $mlinks = array();
-      $nidmap = array();
-      $mlidmap = array();
-      while ($row = db_fetch_array($result)) {
-        $plid = $row['plid'];
-        $mlid = $row['mlid'];
-
-        // copy each node keeping reference to new nid via old nid (this copies the old book structure)
-        $nidmap[$row['nid']] = clone_node_save($row['nid']); // clone_node_save() returns the nid of the new node.
-
-        $node = node_load(array('nid' => $nidmap[$row['nid']]));
-
-        $mlidmap[$mlid] = $node->book['mlid'];
-
-        if ($newbid == 0) {
-          $newbid = $nidmap[$row['nid']];
-          $message = $row['link_title'];
-          // The function signature is: hook_book_copy_alter(&$node, $oldbid, $newbid);
-          drupal_alter("book_copy", $node, $bid, $newbid);
-        }
+/**
+ * Take care of the copying.
+ */
+function _book_copy_batch_copy_page($subtree, &$context) {
+  if (!isset($context['sandbox']['progress'])) {
+    $context['sandbox']['progress'] = 0;
+    $context['sandbox']['max']      = count($context['results']['book_copy']['bookpages']);
+    $context['sandbox']['mlinks']   = array();
+    $context['sandbox']['mlidmap']  = array();
+  }
+  
+  module_load_include('inc', 'clone', 'clone.pages');
+  
+  //reference variables to improve readability
+  $bid     = $context['results']['book_copy']['oldbid'];
+  $newbid  = &$context['results']['book_copy']['newbid'];
+  $mlinks  = &$context['sandbox']['mlinks'];
+  $nidmap  = &$context['results']['book_copy']['nidmap'];
+  $mlidmap = &$context['sandbox']['mlidmap'];
+  
+  $bookpage = $context['results']['book_copy']['bookpages'][$context['sandbox']['progress']];
+  $plid     = $bookpage['plid'];
+  $mlid     = $bookpage['mlid'];
+
+  // copy each node keeping reference to new nid via old nid (this copies the old book structure)
+  $nidmap[$bookpage['nid']] = clone_node_save($bookpage['nid']); // clone_node_save() returns the nid of the new node.
+
+  $node = node_load(array('nid' => $nidmap[$bookpage['nid']]));
+
+  $mlidmap[$mlid] = $node->book['mlid'];
+
+  if ($newbid == 0) {
+    $newbid                                     = $nidmap[$bookpage['nid']];
+    $context['results']['book_copy']['newbook'] = $node;
+    $context['results']['book_copy']['message'] = $bookpage['link_title'];
+    // The function signature is: hook_book_copy_alter(&$node, $oldbid, $newbid);
+    drupal_alter("book_copy", $node, $bid, $newbid);
+  }
         
-        if ($subtree != 0 || $node->nid != $newbid) {
-          // we need to set the bid for this node;
-          $node->book['bid'] = $newbid;
-          node_save($node);
-        }
-
-        // fix lower level nested links
-        if (isset($mlidmap[$plid])) {
-          $node->book['plid'] = $mlidmap[$plid];
-          menu_link_save($node->book);
-        }
-      }
+  if ($subtree != 0 || $node->nid != $newbid) {
+    // we need to set the bid for this node;
+    $node->book['bid'] = $newbid;
+    node_save($node);
+  }
+
+  // fix lower level nested links
+  if (isset($mlidmap[$plid])) {
+    $node->book['plid'] = $mlidmap[$plid];
+    menu_link_save($node->book);
+  }
+  
+  $context['sandbox']['progress']++;
+  if ($context['sandbox']['progress'] != $context['sandbox']['max'] && $context['sandbox']['max'] > 0) {
+    $context['finished'] = $context['sandbox']['progress'] / $context['sandbox']['max'];
+    $context['message'] = t('Page @current of @max has been copied.', array('@current' => $context['sandbox']['progress'], '@max' => $context['sandbox']['max'],));
+  } else {
+    $context['message'] = t('Last page copied. Updating history now.');
+  }
+}
+
+/**
+ * Update the history.
+ */
+function _book_copy_batch_update_history(&$context) {
+  if (!isset($context['sandbox']['progress']) && $context['results']['book_copy']['newbid'] != 0) {
+    $context['sandbox']['progress'] = 0;
+    $context['sandbox']['max']      = count($context['results']['book_copy']['nidmap']);
+    $context['sandbox']['nidmap']   = $context['results']['book_copy']['nidmap'];
+  } else if ($context['results']['book_copy']['newbid'] != 0) {    
+    //reference variables to improve readability
+    $bid    = $context['results']['book_copy']['oldbid'];
+    $newbid = &$context['results']['book_copy']['newbid'];
     
+    for ($i = 0; $i < 20; $i++) {
+      $entry = each($context['sandbox']['nidmap']);
+      if ($entry === false) {
+        break;
+      }
+      
+      $snid = $entry['key'];
+      $nid  = $entry['value'];
+      
       // add book copy reference and node copy history
-      foreach ($nidmap as $snid => $nid) {
       db_query('INSERT INTO {book_copy_history} (nid, bid, sbid, snid, copied) VALUES (%d, %d, %d, %d, %d)', $nid, $newbid, $bid, $snid, time());
+      
+      $context['sandbox']['progress']++;
+      if ($context['sandbox']['progress'] != $context['sandbox']['max']) {
+        $context['finished'] = $context['sandbox']['progress'] / $context['sandbox']['max'];
+      } else {
+        $context['finished'] = 1;
+        $context['message'] = t('History is now up to date.');
       }
+    }
+  }
+}
 
-      $book = node_load(array('nid' => $newbid));
+/**
+ * Prepare gathered information and redirect.
+ */
+function _book_copy_batch_finished($success, $results, $operations) {
+  if ($success) {
+    if ($results['book_copy']['newbid'] != 0) {
+      $newbid = $results['book_copy']['newbid'];
+      $book = $results['book_copy']['newbook'];
       $book->bookcopydata = array();
-      $book->bookcopydata['message'] = t('Successfully cloned "%message", now viewing copy.', array('%message' => $message));
+      $book->bookcopydata['message'] = t('Successfully cloned "%message", now viewing copy.', array('%message' => $results['book_copy']['message']));
       if (_book_outline_access($book)) {
-        $book->bookcopydata['url'] = 'node/'. $newbid .'/outline';
-      }
-      else {
-        $book->bookcopydata['url'] = 'node/'. $newbid;
+        $book->bookcopydata['url'] = 'node/'. $newbid . '/outline';
+      } else {
+        $book->bookcopydata['url'] = drupal_get_path_alias('node/'. $newbid);
       }
 
       // The function signature is: hook_book_copy_goto_alter(&$data);
@@ -157,6 +259,15 @@ function book_copy_copy_book($book, $sub
       drupal_set_message($book->bookcopydata['message']);
       drupal_goto($book->bookcopydata['url']);  // requires user has 'administer book outline' or can access personal books
     }
+  } else {
+    //delete all previously imported nodes
+    drupal_set_message('An error occured!', 'error');
+    foreach ($results['book_copy']['nidmap'] as $nid) {
+      node_delete($nid);
+    }
+    if ($context['results']['book_copy']['oldbid'] != 0) {
+      drupal_goto(drupal_get_path_alias('node/' . $context['results']['book_copy']['oldbid']));
+    }
   }
 }
 
