function qa_recalc_perform_step(&$state)
{
    $continue = false;
    @(list($operation, $length, $next, $done) = explode(',', $state));
    switch ($operation) {
        case 'doreindexcontent':
            qa_recalc_transition($state, 'doreindexcontent_pagereindex');
            break;
        case 'doreindexcontent_pagereindex':
            $pages = qa_db_pages_get_for_reindexing($next, 10);
            if (count($pages)) {
                require_once QA_INCLUDE_DIR . 'qa-app-format.php';
                $lastpageid = max(array_keys($pages));
                foreach ($pages as $pageid => $page) {
                    if (!($page['flags'] & QA_PAGE_FLAGS_EXTERNAL)) {
                        $searchmodules = qa_load_modules_with('search', 'unindex_page');
                        foreach ($searchmodules as $searchmodule) {
                            $searchmodule->unindex_page($pageid);
                        }
                        $searchmodules = qa_load_modules_with('search', 'index_page');
                        if (count($searchmodules)) {
                            $indextext = qa_viewer_text($page['content'], 'html');
                            foreach ($searchmodules as $searchmodule) {
                                $searchmodule->index_page($pageid, $page['tags'], $page['heading'], $page['content'], 'html', $indextext);
                            }
                        }
                    }
                }
                $next = 1 + $lastpageid;
                $done += count($pages);
                $continue = true;
            } else {
                qa_recalc_transition($state, 'doreindexcontent_postcount');
            }
            break;
        case 'doreindexcontent_postcount':
            qa_db_qcount_update();
            qa_db_acount_update();
            qa_db_ccount_update();
            qa_recalc_transition($state, 'doreindexcontent_postreindex');
            break;
        case 'doreindexcontent_postreindex':
            $posts = qa_db_posts_get_for_reindexing($next, 10);
            if (count($posts)) {
                require_once QA_INCLUDE_DIR . 'qa-app-format.php';
                $lastpostid = max(array_keys($posts));
                qa_db_prepare_for_reindexing($next, $lastpostid);
                qa_suspend_update_counts();
                foreach ($posts as $postid => $post) {
                    qa_post_unindex($postid);
                    qa_post_index($postid, $post['type'], $post['questionid'], $post['parentid'], $post['title'], $post['content'], $post['format'], qa_viewer_text($post['content'], $post['format']), $post['tags'], $post['categoryid']);
                }
                $next = 1 + $lastpostid;
                $done += count($posts);
                $continue = true;
            } else {
                qa_db_truncate_indexes($next);
                qa_recalc_transition($state, 'doreindexposts_wordcount');
            }
            break;
        case 'doreindexposts_wordcount':
            $wordids = qa_db_words_prepare_for_recounting($next, 1000);
            if (count($wordids)) {
                $lastwordid = max($wordids);
                qa_db_words_recount($next, $lastwordid);
                $next = 1 + $lastwordid;
                $done += count($wordids);
                $continue = true;
            } else {
                qa_db_tagcount_update();
                // this is quick so just do it here
                qa_recalc_transition($state, 'doreindexposts_complete');
            }
            break;
        case 'dorecountposts':
            qa_recalc_transition($state, 'dorecountposts_postcount');
            break;
        case 'dorecountposts_postcount':
            qa_db_qcount_update();
            qa_db_acount_update();
            qa_db_ccount_update();
            qa_db_unaqcount_update();
            qa_db_unselqcount_update();
            qa_recalc_transition($state, 'dorecountposts_votecount');
            break;
        case 'dorecountposts_votecount':
            $postids = qa_db_posts_get_for_recounting($next, 1000);
            if (count($postids)) {
                $lastpostid = max($postids);
                qa_db_posts_votes_recount($next, $lastpostid);
                $next = 1 + $lastpostid;
                $done += count($postids);
                $continue = true;
            } else {
                qa_recalc_transition($state, 'dorecountposts_acount');
            }
            break;
        case 'dorecountposts_acount':
            $postids = qa_db_posts_get_for_recounting($next, 1000);
            if (count($postids)) {
                $lastpostid = max($postids);
                qa_db_posts_answers_recount($next, $lastpostid);
                $next = 1 + $lastpostid;
                $done += count($postids);
                $continue = true;
            } else {
                qa_db_unupaqcount_update();
                qa_recalc_transition($state, 'dorecountposts_complete');
            }
            break;
        case 'dorecalcpoints':
            qa_recalc_transition($state, 'dorecalcpoints_usercount');
            break;
        case 'dorecalcpoints_usercount':
            qa_db_userpointscount_update();
            // for progress update - not necessarily accurate
            qa_recalc_transition($state, 'dorecalcpoints_recalc');
            break;
        case 'dorecalcpoints_recalc':
            $userids = qa_db_users_get_for_recalc_points($next, 10);
            if (count($userids)) {
                $lastuserid = max($userids);
                qa_db_users_recalc_points($next, $lastuserid);
                $next = 1 + $lastuserid;
                $done += count($userids);
                $continue = true;
            } else {
                qa_db_truncate_userpoints($next);
                qa_db_userpointscount_update();
                // quick so just do it here
                qa_recalc_transition($state, 'dorecalcpoints_complete');
            }
            break;
        case 'dorefillevents':
            qa_recalc_transition($state, 'dorefillevents_qcount');
            break;
        case 'dorefillevents_qcount':
            qa_db_qcount_update();
            qa_recalc_transition($state, 'dorefillevents_refill');
            break;
        case 'dorefillevents_refill':
            $questionids = qa_db_qs_get_for_event_refilling($next, 1);
            if (count($questionids)) {
                require_once QA_INCLUDE_DIR . 'qa-app-events.php';
                require_once QA_INCLUDE_DIR . 'qa-app-updates.php';
                require_once QA_INCLUDE_DIR . 'qa-util-sort.php';
                $lastquestionid = max($questionids);
                foreach ($questionids as $questionid) {
                    //	Retrieve all posts relating to this question
                    list($question, $childposts, $achildposts) = qa_db_select_with_pending(qa_db_full_post_selectspec(null, $questionid), qa_db_full_child_posts_selectspec(null, $questionid), qa_db_full_a_child_posts_selectspec(null, $questionid));
                    //	Merge all posts while preserving keys as postids
                    $posts = array($questionid => $question);
                    foreach ($childposts as $postid => $post) {
                        $posts[$postid] = $post;
                    }
                    foreach ($achildposts as $postid => $post) {
                        $posts[$postid] = $post;
                    }
                    //	Creation and editing of each post
                    foreach ($posts as $postid => $post) {
                        $followonq = $post['basetype'] == 'Q' && $postid != $questionid;
                        qa_create_event_for_q_user($questionid, $postid, $followonq ? QA_UPDATE_FOLLOWS : null, $post['userid'], @$posts[$post['parentid']]['userid'], $post['created']);
                        if (isset($post['updated']) && !$followonq) {
                            qa_create_event_for_q_user($questionid, $postid, $post['updatetype'], $post['lastuserid'], $post['userid'], $post['updated']);
                        }
                    }
                    //	Tags and categories of question
                    qa_create_event_for_tags($question['tags'], $questionid, null, $question['userid'], $question['created']);
                    qa_create_event_for_category($question['categoryid'], $questionid, null, $question['userid'], $question['created']);
                    //	Collect comment threads
                    $parentidcomments = array();
                    foreach ($posts as $postid => $post) {
                        if ($post['basetype'] == 'C') {
                            $parentidcomments[$post['parentid']][$postid] = $post;
                        }
                    }
                    //	For each comment thread, notify all previous comment authors of each comment in the thread (could get slow)
                    foreach ($parentidcomments as $parentid => $comments) {
                        $keyuserids = array();
                        qa_sort_by($comments, 'created');
                        foreach ($comments as $comment) {
                            foreach ($keyuserids as $keyuserid => $dummy) {
                                if ($keyuserid != $comment['userid'] && $keyuserid != @$posts[$parentid]['userid']) {
                                    qa_db_event_create_not_entity($keyuserid, $questionid, $comment['postid'], null, $comment['userid'], $comment['created']);
                                }
                            }
                            if (isset($comment['userid'])) {
                                $keyuserids[$comment['userid']] = true;
                            }
                        }
                    }
                }
                $next = 1 + $lastquestionid;
                $done += count($questionids);
                $continue = true;
            } else {
                qa_recalc_transition($state, 'dorefillevents_complete');
            }
            break;
        case 'dorecalccategories':
            qa_recalc_transition($state, 'dorecalccategories_postcount');
            break;
        case 'dorecalccategories_postcount':
            qa_db_acount_update();
            qa_db_ccount_update();
            qa_recalc_transition($state, 'dorecalccategories_postupdate');
            break;
        case 'dorecalccategories_postupdate':
            $postids = qa_db_posts_get_for_recategorizing($next, 100);
            if (count($postids)) {
                $lastpostid = max($postids);
                qa_db_posts_recalc_categoryid($next, $lastpostid);
                qa_db_posts_calc_category_path($next, $lastpostid);
                $next = 1 + $lastpostid;
                $done += count($postids);
                $continue = true;
            } else {
                qa_recalc_transition($state, 'dorecalccategories_recount');
            }
            break;
        case 'dorecalccategories_recount':
            $categoryids = qa_db_categories_get_for_recalcs($next, 10);
            if (count($categoryids)) {
                $lastcategoryid = max($categoryids);
                foreach ($categoryids as $categoryid) {
                    qa_db_ifcategory_qcount_update($categoryid);
                }
                $next = 1 + $lastcategoryid;
                $done += count($categoryids);
                $continue = true;
            } else {
                qa_recalc_transition($state, 'dorecalccategories_backpaths');
            }
            break;
        case 'dorecalccategories_backpaths':
            $categoryids = qa_db_categories_get_for_recalcs($next, 10);
            if (count($categoryids)) {
                $lastcategoryid = max($categoryids);
                qa_db_categories_recalc_backpaths($next, $lastcategoryid);
                $next = 1 + $lastcategoryid;
                $done += count($categoryids);
                $continue = true;
            } else {
                qa_recalc_transition($state, 'dorecalccategories_complete');
            }
            break;
        case 'dodeletehidden':
            qa_recalc_transition($state, 'dodeletehidden_comments');
            break;
        case 'dodeletehidden_comments':
            $posts = qa_db_posts_get_for_deleting('C', $next, 1);
            if (count($posts)) {
                require_once QA_INCLUDE_DIR . 'qa-app-posts.php';
                $postid = $posts[0];
                qa_post_delete($postid);
                $next = 1 + $postid;
                $done++;
                $continue = true;
            } else {
                qa_recalc_transition($state, 'dodeletehidden_answers');
            }
            break;
        case 'dodeletehidden_answers':
            $posts = qa_db_posts_get_for_deleting('A', $next, 1);
            if (count($posts)) {
                require_once QA_INCLUDE_DIR . 'qa-app-posts.php';
                $postid = $posts[0];
                qa_post_delete($postid);
                $next = 1 + $postid;
                $done++;
                $continue = true;
            } else {
                qa_recalc_transition($state, 'dodeletehidden_questions');
            }
            break;
        case 'dodeletehidden_questions':
            $posts = qa_db_posts_get_for_deleting('Q', $next, 1);
            if (count($posts)) {
                require_once QA_INCLUDE_DIR . 'qa-app-posts.php';
                $postid = $posts[0];
                qa_post_delete($postid);
                $next = 1 + $postid;
                $done++;
                $continue = true;
            } else {
                qa_recalc_transition($state, 'dodeletehidden_complete');
            }
            break;
        default:
            $state = '';
            break;
    }
    if ($continue) {
        $state = $operation . ',' . $length . ',' . $next . ',' . $done;
    }
    return $continue && $done < $length;
}
function qa_recalc_perform_step(&$state)
{
    $continue = false;
    @(list($operation, $length, $next, $done) = explode("\t", $state));
    switch ($operation) {
        case 'doreindexcontent':
            qa_recalc_transition($state, 'doreindexcontent_pagereindex');
            break;
        case 'doreindexcontent_pagereindex':
            $pages = qa_db_pages_get_for_reindexing($next, 10);
            if (count($pages)) {
                require_once QA_INCLUDE_DIR . 'app/format.php';
                $lastpageid = max(array_keys($pages));
                foreach ($pages as $pageid => $page) {
                    if (!($page['flags'] & QA_PAGE_FLAGS_EXTERNAL)) {
                        $searchmodules = qa_load_modules_with('search', 'unindex_page');
                        foreach ($searchmodules as $searchmodule) {
                            $searchmodule->unindex_page($pageid);
                        }
                        $searchmodules = qa_load_modules_with('search', 'index_page');
                        if (count($searchmodules)) {
                            $indextext = qa_viewer_text($page['content'], 'html');
                            foreach ($searchmodules as $searchmodule) {
                                $searchmodule->index_page($pageid, $page['tags'], $page['heading'], $page['content'], 'html', $indextext);
                            }
                        }
                    }
                }
                $next = 1 + $lastpageid;
                $done += count($pages);
                $continue = true;
            } else {
                qa_recalc_transition($state, 'doreindexcontent_postcount');
            }
            break;
        case 'doreindexcontent_postcount':
            qa_db_qcount_update();
            qa_db_acount_update();
            qa_db_ccount_update();
            qa_recalc_transition($state, 'doreindexcontent_postreindex');
            break;
        case 'doreindexcontent_postreindex':
            $posts = qa_db_posts_get_for_reindexing($next, 10);
            if (count($posts)) {
                require_once QA_INCLUDE_DIR . 'app/format.php';
                $lastpostid = max(array_keys($posts));
                qa_db_prepare_for_reindexing($next, $lastpostid);
                qa_suspend_update_counts();
                foreach ($posts as $postid => $post) {
                    qa_post_unindex($postid);
                    qa_post_index($postid, $post['type'], $post['questionid'], $post['parentid'], $post['title'], $post['content'], $post['format'], qa_viewer_text($post['content'], $post['format']), $post['tags'], $post['categoryid']);
                }
                $next = 1 + $lastpostid;
                $done += count($posts);
                $continue = true;
            } else {
                qa_db_truncate_indexes($next);
                qa_recalc_transition($state, 'doreindexposts_wordcount');
            }
            break;
        case 'doreindexposts_wordcount':
            $wordids = qa_db_words_prepare_for_recounting($next, 1000);
            if (count($wordids)) {
                $lastwordid = max($wordids);
                qa_db_words_recount($next, $lastwordid);
                $next = 1 + $lastwordid;
                $done += count($wordids);
                $continue = true;
            } else {
                qa_db_tagcount_update();
                // this is quick so just do it here
                qa_recalc_transition($state, 'doreindexposts_complete');
            }
            break;
        case 'dorecountposts':
            qa_recalc_transition($state, 'dorecountposts_postcount');
            break;
        case 'dorecountposts_postcount':
            qa_db_qcount_update();
            qa_db_acount_update();
            qa_db_ccount_update();
            qa_db_unaqcount_update();
            qa_db_unselqcount_update();
            qa_recalc_transition($state, 'dorecountposts_votecount');
            break;
        case 'dorecountposts_votecount':
            $postids = qa_db_posts_get_for_recounting($next, 1000);
            if (count($postids)) {
                $lastpostid = max($postids);
                qa_db_posts_votes_recount($next, $lastpostid);
                $next = 1 + $lastpostid;
                $done += count($postids);
                $continue = true;
            } else {
                qa_recalc_transition($state, 'dorecountposts_acount');
            }
            break;
        case 'dorecountposts_acount':
            $postids = qa_db_posts_get_for_recounting($next, 1000);
            if (count($postids)) {
                $lastpostid = max($postids);
                qa_db_posts_answers_recount($next, $lastpostid);
                $next = 1 + $lastpostid;
                $done += count($postids);
                $continue = true;
            } else {
                qa_db_unupaqcount_update();
                qa_recalc_transition($state, 'dorecountposts_complete');
            }
            break;
        case 'dorecalcpoints':
            qa_recalc_transition($state, 'dorecalcpoints_usercount');
            break;
        case 'dorecalcpoints_usercount':
            qa_db_userpointscount_update();
            // for progress update - not necessarily accurate
            qa_db_uapprovecount_update();
            // needs to be somewhere and this is the most appropriate place
            qa_recalc_transition($state, 'dorecalcpoints_recalc');
            break;
        case 'dorecalcpoints_recalc':
            $recalccount = 10;
            $userids = qa_db_users_get_for_recalc_points($next, $recalccount + 1);
            // get one extra so we know where to start from next
            $gotcount = count($userids);
            $recalccount = min($recalccount, $gotcount);
            // can't recalc more than we got
            if ($recalccount > 0) {
                $lastuserid = $userids[$recalccount - 1];
                qa_db_users_recalc_points($next, $lastuserid);
                $done += $recalccount;
            } else {
                $lastuserid = $next;
            }
            // for truncation
            if ($gotcount > $recalccount) {
                // more left to do
                $next = $userids[$recalccount];
                // start next round at first one not recalculated
                $continue = true;
            } else {
                qa_db_truncate_userpoints($lastuserid);
                qa_db_userpointscount_update();
                // quick so just do it here
                qa_recalc_transition($state, 'dorecalcpoints_complete');
            }
            break;
        case 'dorefillevents':
            qa_recalc_transition($state, 'dorefillevents_qcount');
            break;
        case 'dorefillevents_qcount':
            qa_db_qcount_update();
            qa_recalc_transition($state, 'dorefillevents_refill');
            break;
        case 'dorefillevents_refill':
            $questionids = qa_db_qs_get_for_event_refilling($next, 1);
            if (count($questionids)) {
                require_once QA_INCLUDE_DIR . 'app/events.php';
                require_once QA_INCLUDE_DIR . 'app/updates.php';
                require_once QA_INCLUDE_DIR . 'util/sort.php';
                $lastquestionid = max($questionids);
                foreach ($questionids as $questionid) {
                    //	Retrieve all posts relating to this question
                    list($question, $childposts, $achildposts) = qa_db_select_with_pending(qa_db_full_post_selectspec(null, $questionid), qa_db_full_child_posts_selectspec(null, $questionid), qa_db_full_a_child_posts_selectspec(null, $questionid));
                    //	Merge all posts while preserving keys as postids
                    $posts = array($questionid => $question);
                    foreach ($childposts as $postid => $post) {
                        $posts[$postid] = $post;
                    }
                    foreach ($achildposts as $postid => $post) {
                        $posts[$postid] = $post;
                    }
                    //	Creation and editing of each post
                    foreach ($posts as $postid => $post) {
                        $followonq = $post['basetype'] == 'Q' && $postid != $questionid;
                        if ($followonq) {
                            $updatetype = QA_UPDATE_FOLLOWS;
                        } elseif ($post['basetype'] == 'C' && @$posts[$post['parentid']]['basetype'] == 'Q') {
                            $updatetype = QA_UPDATE_C_FOR_Q;
                        } elseif ($post['basetype'] == 'C' && @$posts[$post['parentid']]['basetype'] == 'A') {
                            $updatetype = QA_UPDATE_C_FOR_A;
                        } else {
                            $updatetype = null;
                        }
                        qa_create_event_for_q_user($questionid, $postid, $updatetype, $post['userid'], @$posts[$post['parentid']]['userid'], $post['created']);
                        if (isset($post['updated']) && !$followonq) {
                            qa_create_event_for_q_user($questionid, $postid, $post['updatetype'], $post['lastuserid'], $post['userid'], $post['updated']);
                        }
                    }
                    //	Tags and categories of question
                    qa_create_event_for_tags($question['tags'], $questionid, null, $question['userid'], $question['created']);
                    qa_create_event_for_category($question['categoryid'], $questionid, null, $question['userid'], $question['created']);
                    //	Collect comment threads
                    $parentidcomments = array();
                    foreach ($posts as $postid => $post) {
                        if ($post['basetype'] == 'C') {
                            $parentidcomments[$post['parentid']][$postid] = $post;
                        }
                    }
                    //	For each comment thread, notify all previous comment authors of each comment in the thread (could get slow)
                    foreach ($parentidcomments as $parentid => $comments) {
                        $keyuserids = array();
                        qa_sort_by($comments, 'created');
                        foreach ($comments as $comment) {
                            foreach ($keyuserids as $keyuserid => $dummy) {
                                if ($keyuserid != $comment['userid'] && $keyuserid != @$posts[$parentid]['userid']) {
                                    qa_db_event_create_not_entity($keyuserid, $questionid, $comment['postid'], QA_UPDATE_FOLLOWS, $comment['userid'], $comment['created']);
                                }
                            }
                            if (isset($comment['userid'])) {
                                $keyuserids[$comment['userid']] = true;
                            }
                        }
                    }
                }
                $next = 1 + $lastquestionid;
                $done += count($questionids);
                $continue = true;
            } else {
                qa_recalc_transition($state, 'dorefillevents_complete');
            }
            break;
        case 'dorecalccategories':
            qa_recalc_transition($state, 'dorecalccategories_postcount');
            break;
        case 'dorecalccategories_postcount':
            qa_db_acount_update();
            qa_db_ccount_update();
            qa_recalc_transition($state, 'dorecalccategories_postupdate');
            break;
        case 'dorecalccategories_postupdate':
            $postids = qa_db_posts_get_for_recategorizing($next, 100);
            if (count($postids)) {
                $lastpostid = max($postids);
                qa_db_posts_recalc_categoryid($next, $lastpostid);
                qa_db_posts_calc_category_path($next, $lastpostid);
                $next = 1 + $lastpostid;
                $done += count($postids);
                $continue = true;
            } else {
                qa_recalc_transition($state, 'dorecalccategories_recount');
            }
            break;
        case 'dorecalccategories_recount':
            $categoryids = qa_db_categories_get_for_recalcs($next, 10);
            if (count($categoryids)) {
                $lastcategoryid = max($categoryids);
                foreach ($categoryids as $categoryid) {
                    qa_db_ifcategory_qcount_update($categoryid);
                }
                $next = 1 + $lastcategoryid;
                $done += count($categoryids);
                $continue = true;
            } else {
                qa_recalc_transition($state, 'dorecalccategories_backpaths');
            }
            break;
        case 'dorecalccategories_backpaths':
            $categoryids = qa_db_categories_get_for_recalcs($next, 10);
            if (count($categoryids)) {
                $lastcategoryid = max($categoryids);
                qa_db_categories_recalc_backpaths($next, $lastcategoryid);
                $next = 1 + $lastcategoryid;
                $done += count($categoryids);
                $continue = true;
            } else {
                qa_recalc_transition($state, 'dorecalccategories_complete');
            }
            break;
        case 'dodeletehidden':
            qa_recalc_transition($state, 'dodeletehidden_comments');
            break;
        case 'dodeletehidden_comments':
            $posts = qa_db_posts_get_for_deleting('C', $next, 1);
            if (count($posts)) {
                require_once QA_INCLUDE_DIR . 'app/posts.php';
                $postid = $posts[0];
                qa_post_delete($postid);
                $next = 1 + $postid;
                $done++;
                $continue = true;
            } else {
                qa_recalc_transition($state, 'dodeletehidden_answers');
            }
            break;
        case 'dodeletehidden_answers':
            $posts = qa_db_posts_get_for_deleting('A', $next, 1);
            if (count($posts)) {
                require_once QA_INCLUDE_DIR . 'app/posts.php';
                $postid = $posts[0];
                qa_post_delete($postid);
                $next = 1 + $postid;
                $done++;
                $continue = true;
            } else {
                qa_recalc_transition($state, 'dodeletehidden_questions');
            }
            break;
        case 'dodeletehidden_questions':
            $posts = qa_db_posts_get_for_deleting('Q', $next, 1);
            if (count($posts)) {
                require_once QA_INCLUDE_DIR . 'app/posts.php';
                $postid = $posts[0];
                qa_post_delete($postid);
                $next = 1 + $postid;
                $done++;
                $continue = true;
            } else {
                qa_recalc_transition($state, 'dodeletehidden_complete');
            }
            break;
        case 'doblobstodisk':
            qa_recalc_transition($state, 'doblobstodisk_move');
            break;
        case 'doblobstodisk_move':
            $blob = qa_db_get_next_blob_in_db($next);
            if (isset($blob)) {
                require_once QA_INCLUDE_DIR . 'app/blobs.php';
                require_once QA_INCLUDE_DIR . 'db/blobs.php';
                if (qa_write_blob_file($blob['blobid'], $blob['content'], $blob['format'])) {
                    qa_db_blob_set_content($blob['blobid'], null);
                }
                $next = 1 + $blob['blobid'];
                $done++;
                $continue = true;
            } else {
                qa_recalc_transition($state, 'doblobstodisk_complete');
            }
            break;
        case 'doblobstodb':
            qa_recalc_transition($state, 'doblobstodb_move');
            break;
        case 'doblobstodb_move':
            $blob = qa_db_get_next_blob_on_disk($next);
            if (isset($blob)) {
                require_once QA_INCLUDE_DIR . 'app/blobs.php';
                require_once QA_INCLUDE_DIR . 'db/blobs.php';
                $content = qa_read_blob_file($blob['blobid'], $blob['format']);
                qa_db_blob_set_content($blob['blobid'], $content);
                qa_delete_blob_file($blob['blobid'], $blob['format']);
                $next = 1 + $blob['blobid'];
                $done++;
                $continue = true;
            } else {
                qa_recalc_transition($state, 'doblobstodb_complete');
            }
            break;
        default:
            $state = '';
            break;
    }
    if ($continue) {
        $state = $operation . "\t" . $length . "\t" . $next . "\t" . $done;
    }
    return $continue && $done < $length;
}
function qa_recalc_perform_step(&$state)
{
    $continue = false;
    @(list($operation, $length, $next, $done) = explode(',', $state));
    switch ($operation) {
        case 'doreindexposts':
            qa_recalc_transition($state, 'doreindexposts_postcount');
            break;
        case 'doreindexposts_postcount':
            qa_db_qcount_update();
            qa_db_acount_update();
            qa_db_ccount_update();
            qa_recalc_transition($state, 'doreindexposts_reindex');
            break;
        case 'doreindexposts_reindex':
            $posts = qa_db_posts_get_for_reindexing($next, 10);
            if (count($posts)) {
                require_once QA_INCLUDE_DIR . 'qa-app-format.php';
                $lastpostid = max(array_keys($posts));
                qa_db_prepare_for_reindexing($next, $lastpostid);
                foreach ($posts as $postid => $post) {
                    qa_post_index($postid, $post['type'], $post['questionid'], $post['title'], qa_viewer_text($post['content'], $post['format']), $post['tags'], true);
                }
                $next = 1 + $lastpostid;
                $done += count($posts);
                $continue = true;
            } else {
                qa_db_truncate_indexes($next);
                qa_recalc_transition($state, 'doreindexposts_wordcount');
            }
            break;
        case 'doreindexposts_wordcount':
            $wordids = qa_db_words_prepare_for_recounting($next, 1000);
            if (count($wordids)) {
                $lastwordid = max($wordids);
                qa_db_words_recount($next, $lastwordid);
                $next = 1 + $lastwordid;
                $done += count($wordids);
                $continue = true;
            } else {
                qa_db_tagcount_update();
                // this is quick so just do it here
                qa_recalc_transition($state, 'doreindexposts_complete');
            }
            break;
        case 'dorecountposts':
            qa_recalc_transition($state, 'dorecountposts_postcount');
            break;
        case 'dorecountposts_postcount':
            qa_db_qcount_update();
            qa_db_acount_update();
            qa_db_ccount_update();
            qa_db_unaqcount_update();
            qa_recalc_transition($state, 'dorecountposts_recount');
            break;
        case 'dorecountposts_recount':
            $postids = qa_db_posts_get_for_recounting($next, 1000);
            if (count($postids)) {
                $lastpostid = max($postids);
                qa_db_posts_recount($next, $lastpostid);
                $next = 1 + $lastpostid;
                $done += count($postids);
                $continue = true;
            } else {
                qa_recalc_transition($state, 'dorecountposts_complete');
            }
            break;
        case 'dorecalcpoints':
            qa_recalc_transition($state, 'dorecalcpoints_usercount');
            break;
        case 'dorecalcpoints_usercount':
            qa_db_userpointscount_update();
            // for progress update - not necessarily accurate
            qa_recalc_transition($state, 'dorecalcpoints_recalc');
            break;
        case 'dorecalcpoints_recalc':
            $userids = qa_db_users_get_for_recalc_points($next, 10);
            if (count($userids)) {
                $lastuserid = max($userids);
                qa_db_users_recalc_points($next, $lastuserid);
                $next = 1 + $lastuserid;
                $done += count($userids);
                $continue = true;
            } else {
                qa_db_truncate_userpoints($next);
                qa_db_userpointscount_update();
                // quick so just do it here
                qa_recalc_transition($state, 'dorecalcpoints_complete');
            }
            break;
        case 'dorecalccategories':
            qa_recalc_transition($state, 'dorecalccategories_postcount');
            break;
        case 'dorecalccategories_postcount':
            qa_db_acount_update();
            qa_db_ccount_update();
            qa_recalc_transition($state, 'dorecalccategories_postupdate');
            break;
        case 'dorecalccategories_postupdate':
            $postids = qa_db_posts_get_for_recategorizing($next, 100);
            if (count($postids)) {
                $lastpostid = max($postids);
                qa_db_posts_recalc_categoryid($next, $lastpostid);
                qa_db_posts_calc_category_path($next, $lastpostid);
                $next = 1 + $lastpostid;
                $done += count($postids);
                $continue = true;
            } else {
                qa_recalc_transition($state, 'dorecalccategories_recount');
            }
            break;
        case 'dorecalccategories_recount':
            $categoryids = qa_db_categories_get_for_recalcs($next, 10);
            if (count($categoryids)) {
                $lastcategoryid = max($categoryids);
                foreach ($categoryids as $categoryid) {
                    qa_db_ifcategory_qcount_update($categoryid);
                }
                $next = 1 + $lastcategoryid;
                $done += count($categoryids);
                $continue = true;
            } else {
                qa_recalc_transition($state, 'dorecalccategories_backpaths');
            }
            break;
        case 'dorecalccategories_backpaths':
            $categoryids = qa_db_categories_get_for_recalcs($next, 10);
            if (count($categoryids)) {
                $lastcategoryid = max($categoryids);
                qa_db_categories_recalc_backpaths($next, $lastcategoryid);
                $next = 1 + $lastcategoryid;
                $done += count($categoryids);
                $continue = true;
            } else {
                qa_recalc_transition($state, 'dorecalccategories_complete');
            }
            break;
        case 'dodeletehidden':
            qa_recalc_transition($state, 'dodeletehidden_comments');
            break;
        case 'dodeletehidden_comments':
            $posts = qa_db_posts_get_for_deleting('C', $next, 1);
            if (count($posts)) {
                $postid = $posts[0];
                $oldcomment = qa_db_single_select(qa_db_full_post_selectspec(null, $postid));
                $parent = qa_db_single_select(qa_db_full_post_selectspec(null, $oldcomment['parentid']));
                if ($parent['basetype'] == 'Q') {
                    $question = $parent;
                    $answer = null;
                } else {
                    $question = qa_db_single_select(qa_db_full_post_selectspec(null, $parent['parentid']));
                    $answer = $parent;
                }
                qa_comment_delete($oldcomment, $question, $answer, null, null, null);
                $next = 1 + $postid;
                $done++;
                $continue = true;
            } else {
                qa_recalc_transition($state, 'dodeletehidden_answers');
            }
            break;
        case 'dodeletehidden_answers':
            $posts = qa_db_posts_get_for_deleting('A', $next, 1);
            if (count($posts)) {
                $postid = $posts[0];
                $oldanswer = qa_db_single_select(qa_db_full_post_selectspec(null, $postid));
                $question = qa_db_single_select(qa_db_full_post_selectspec(null, $oldanswer['parentid']));
                qa_answer_delete($oldanswer, $question, null, null, null);
                $next = 1 + $postid;
                $done++;
                $continue = true;
            } else {
                qa_recalc_transition($state, 'dodeletehidden_questions');
            }
            break;
        case 'dodeletehidden_questions':
            $posts = qa_db_posts_get_for_deleting('Q', $next, 1);
            if (count($posts)) {
                $postid = $posts[0];
                $oldquestion = qa_db_single_select(qa_db_full_post_selectspec(null, $postid));
                qa_question_delete($oldquestion, null, null, null);
                $next = 1 + $postid;
                $done++;
                $continue = true;
            } else {
                qa_recalc_transition($state, 'dodeletehidden_complete');
            }
            break;
        default:
            $state = '';
            break;
    }
    if ($continue) {
        $state = $operation . ',' . $length . ',' . $next . ',' . $done;
    }
    return $continue && $done < $length;
}