Perforce Chronicle 2012.2/486814
API Documentation

Search_ManageController Class Reference

Manages the search and index. More...

List of all members.

Public Member Functions

 indexAction ()
 Show a manage search page.
 optimizeAction ()
 optimize the Lucene search index.
 rebuildAction ()
 Rebuild the Lucene search index.
 statusAction ()
 Provide a status update in Json format.

Public Attributes

 $contexts
const REBUILD_BATCH_SIZE = 100

Protected Member Functions

 _removeSearchIndex ($indexName)
 Remove the search index by deleting all files from its folder on disk.
 _setActiveSearchIndex ($index)
 Make a search index active.

Static Protected Member Functions

static _nomaliseIndexName ($index)
 Normalise a Lucene search index name.

Protected Attributes

 $_activeIndexPath = 'search-index'
 $_maintenanceLockFile = 'search.maintenance.lock.file'
 $_maintenanceStatusFile = 'search.maintenance.status.file'
 $_statusFile = null

Detailed Description

Manages the search and index.

Copyright:
2011-2012 Perforce Software. All rights reserved
License:
Please see LICENSE.txt in top-level folder of this distribution.
Version:
2012.2/486814

Member Function Documentation

static Search_ManageController::_nomaliseIndexName ( index) [static, protected]

Normalise a Lucene search index name.

  • Remove extra spaces on both ends.
  • Remove any slashes ('/', '\') on both ends.
Parameters:
string$indexthe original index name
Returns:
string the index name with spaces and slashes removed from both ends
    {
        // if the name is not a string
        if (!is_string($index)) {
            return '';
        }

        // trim spaces and slashes
        $index = trim($index, " \t\n\r\0\x0B/\\");

        return $index;
    }
Search_ManageController::_removeSearchIndex ( indexName) [protected]

Remove the search index by deleting all files from its folder on disk.

Parameters:
string$indexNamethe folder name of a search index
Returns:
boolean true, if success false, otherwise
    {
        // if the index folder is an empty string, nothing to do
        if (strlen($indexName) == 0) {
            return true;
        }

        $indexDirectory = P4Cms_Site::fetchActive()->getDataPath() . '/' . $indexName;

        // if the index does not exist, nothing to do
        if (!file_exists($indexDirectory)) {
            return true;
        }

        $files = scandir($indexDirectory);

        // remove all files in the search index folder
        foreach ($files as $file) {
            if (is_dir($file)) {
                continue;
            }

            unlink($indexDirectory . '/' . $file);
        }

        return rmdir($indexDirectory);
    }
Search_ManageController::_setActiveSearchIndex ( index) [protected]

Make a search index active.

Parameters:
string$indexthe search index directory
Returns:
boolean true, if success; false, otherwise
    {
        // if $index is not a string or it's an empty string
        // we cannot get search index
        $index = $this->_nomaliseIndexName($index);
        if (strlen($index) == 0) {
            throw new Zend_Search_Exception(
                'Require a folder name to set the active Search index.'
            );
        }

        $activeIndex = $this->_activeIndexPath;

        // nothing to do if the index given is the active index
        if ($index == $activeIndex) {
            return true;
        }

        // remove the active index contents
        if (!$this->_removeSearchIndex($activeIndex)) {
            throw new Zend_Search_Exception(
                "Failed removing the active search index: $activeIndex."
            );
        }

        $dataPath   = P4Cms_Site::fetchActive()->getDataPath() . '/';
        $newPath    = $dataPath . $index;
        $activePath = $dataPath . $activeIndex;

        return rename($newPath, $activePath);
    }
Search_ManageController::indexAction ( )

Show a manage search page.

    {
        // enforce permissions.
        $this->acl->check('search', 'manage');

        $request = $this->getRequest();

        // get the search form.
        $form = new Search_Form_Manage;

        if ($request->isPost()) {
            $data = $request->getPost();

            if ( $form->isValid($data)) {

                $maxBufferedDocs = $data['maxBufferedDocs'];
                $maxMergeDocs    = $data['maxMergeDocs'];
                $mergeFactor     = $data['mergeFactor'];

                $config = array();

                if (strlen($maxBufferedDocs) != 0) {
                    $config['maxBufferedDocs'] = $maxBufferedDocs;
                } else {
                    $config['maxBufferedDocs'] = Search_Module::getMaxBufferedDocs();
                }

                if (strlen($maxMergeDocs) != 0) {
                    $config['maxMergeDocs'] = $maxMergeDocs;
                } else {
                    $config['maxMergeDocs'] = Search_Module::getMaxMergeDocs();
                }

                if (strlen($mergeFactor) != 0) {
                    $config['mergeFactor'] = $mergeFactor;
                } else {
                    $config['mergeFactor'] = Search_Module::getMergeFactor();
                }

                $this->_saveConfig($config);

                P4Cms_Notifications::add(
                    'Search configuration saved.',
                    P4Cms_Notifications::SEVERITY_SUCCESS
                );

                $this->redirector->gotoSimple('index');
            }
        } else {
            $data = array();

            $data['maxBufferedDocs'] = Search_Module::getMaxBufferedDocs();
            $data['maxMergeDocs']    = (Search_Module::getMaxMergeDocs()
                                        && (Search_Module::getMaxMergeDocs() != PHP_INT_MAX))
                                     ? Search_Module::getMaxMergeDocs()
                                     : '';
            $data['mergeFactor']     = Search_Module::getMergeFactor();

            $form->populate($data);
        }

        $this->view->form = $form;

        $this->getHelper('layout')->setLayout('manage-layout');
        $this->view->headTitle()->set('Manage Search');
    }
Search_ManageController::optimizeAction ( )

optimize the Lucene search index.

    {
        // enforce permissions.
        $this->acl->check('search', 'manage');

        // check if there is another maintenance task (optimize/rebuild)
        // running. if it is, redirect to the status page
        $maitenanceLockFile = P4Cms_Site::fetchActive()->getDataPath()
                            . '/' . $this->_maintenanceLockFile;

        if (file_exists($maitenanceLockFile)) {
            $redirector = $this->_helper->getHelper('redirector');
            $redirector->gotoSimple('status');
            return;
        }

        // create the maintenance lock file
        touch($maitenanceLockFile);

        P4Cms_Log::log(
            "optimize Search Index: BEGIN; pid=". getmypid(),
            P4Cms_Log::DEBUG
        );

        // put the current task in the session
        $_SESSION['searchMaintenanceTask'] = 'optimize';

        $this->_writeStatusFile(
            array(
                'action'  => 'optimize',
                'label'   => 'optimizing search index',
                'message' => 'Start optimizing search index.',
                'time'    => time(),
                'done'    => false,
            )
        );

        // close the session but continue running, since index rebuilt may
        // take longer than browser timeout
        $this->getHelper('browserDisconnect')->disconnect('status', 10);

        $index = Search_Module::factory();
        $index->optimize();
        $this->_writeStatusFile(
            array(
                'action'  => 'optimize',
                'label'   => 'optimizing search index',
                'message' => 'Done. Search index optimization completed.',
                'time'    => time(),
                'done'    => true,
            )
        );
        unlink($maitenanceLockFile);
    }
Search_ManageController::rebuildAction ( )

Rebuild the Lucene search index.

p4cms.search.index.rebuild Return a Zend_Paginator of P4Cms_Content entries (or null) to be included when the search index is rebuilt.

    {
        // enforce permissions.
        $this->acl->check('search', 'manage');

        // check if there is another maintenance task (optimize/rebuild)
        // running. if it is, redirect to the status page
        $maitenanceLockFile = P4Cms_Site::fetchActive()->getDataPath()
                            . '/' . $this->_maintenanceLockFile;

        if (file_exists($maitenanceLockFile)) {
            $redirector = $this->_helper->getHelper('redirector');
            $redirector->gotoSimple('status');
            return;
        }

        // create the maintenance lock file
        touch($maitenanceLockFile);

        P4Cms_Log::log(
            "Rebuild Search Index: BEGIN; pid=". getmypid(),
            P4Cms_Log::DEBUG
        );

        // put the current task in the session
        $_SESSION['searchMaintenanceTask'] = 'rebuild';

        // put the status file's filename in the session
        $this->_statusFile = tempnam('/tmp', 'p4cms-search-rebuild.'. getmypid() .'.');
        $_SESSION['searchMaintenanceStatusFile'] = $this->_statusFile;

        $this->_writeStatusFile(
            array(
                'action'  => 'rebuild',
                'label'   => 'rebuilding search index',
                'message' => 'Start rebuilding search index.',
                'time'    => time(),
                'done'    => false,
            )
        );

        // close the session but continue running, since index rebuilt may
        // take longer than browser timeout
        $this->getHelper('browserDisconnect')->disconnect('status', 60 * 24);

        // clear the current index, if any
        // and create a new one
        $index = Search_Module::factory('temp-index');

        // publish the search index rebuild topic, expects subscribers to
        // return Zend_Paginator instances
        $feedbacks = P4Cms_PubSub::publish('p4cms.search.index.rebuild');

        $entryCount = 0;

        // get the total number of content entries
        foreach ($feedbacks as $feedback) {
            if ($feedback instanceof Zend_Paginator) {
                $entryCount += $feedback->getTotalItemCount();
            }
        }

        // start to rebuild the search index
        $count = 0;

        foreach ($feedbacks as $feedback) {
            // if the feedback is not a paginator as expected, skip it
            if (!$feedback instanceof Zend_Paginator) {
                continue;
            }

            $feedback->setItemCountPerPage(self::REBUILD_BATCH_SIZE);

            // for each page, get the items and index them
            for ($i = 1; $i <= $feedback->count(); $i++) {
                // set the current page
                $feedback->setCurrentPageNumber($i);

                // if there is no items in the current page, nothing to do
                if ($feedback->getCurrentItemCount() == 0) {
                    continue;
                }
                $itemCountPerPage = $feedback->getCurrentItemCount();

                // get a batch of entries
                $this->_writeStatusFile(
                    array(
                        'action'  => 'rebuild',
                        'label'   => 'fetching entries',
                        'message' => "Fetching the $i batch of $entryCount existing entries...",
                        'time'    => time(),
                        'done'    => false,
                    )
                );

                // get the items in the current page
                $items = $feedback->getCurrentItems();

                // update the status
                $this->_writeStatusFile(
                    array(
                        'action'  => 'rebuild',
                        'label'   => 'rebuilding',
                        'message' => "Start rebuilding from the $i batch of $entryCount existing entries.",
                        'time'    => time(),
                        'done'    => false,
                    )
                );

                foreach ($items as $item) {
                    if (!$item instanceof Zend_Search_Lucene_Document) {
                        if (method_exists($item, 'toLuceneDocument')) {
                            try {
                                $item = $item->toLuceneDocument();
                            } catch (Zend_Filter_Exception $e) {
                                P4Cms_Log::logException(
                                    'Failed converting content to Lucene document.',
                                    $e
                                );

                                continue;
                            } catch (Zend_Search_Lucene_Exception $e) {
                                P4Cms_Log::logException(
                                    'Failed converting content to Lucene document.',
                                    $e
                                );

                                continue;
                            }
                        } else {
                            continue;
                        }
                    }

                    $index->addDocument($item);

                    $count++;

                    // update the status
                    $this->_writeStatusFile(
                        array(
                            'action'  => 'rebuild',
                            'label'   => 'indexing content',
                            'message' => "Indexing content no. $count of $entryCount.",
                            'count'   => $count,
                            'total'   => $entryCount,
                            'time'    => time(),
                            'done'    => false,
                        )
                    );
                }
            }
        }

        // optimize the index after it's rebuilt
        $this->_writeStatusFile(
            array(
                'action'  => 'optimize',
                'label'   => 'optimizing search index',
                'index'   => 'temp-index',
                'message' => 'Optimizing the search index after rebuild...',
                'time'    => time(),
                'done'    => false,
            )
        );

        $index->optimize();

        $this->_writeStatusFile(
            array(
                'action'  => 'optimize',
                'label'   => 'optimizing search index',
                'index'   => 'temp-index',
                'message' => "Done.  Search Index has been rebuilt.",
                'time'    => time(),
                'done'    => true
            )
        );
        Search_Module::clearSearchInstances();
        $this->_setActiveSearchIndex('temp-index');
        unlink($maitenanceLockFile);
    }
Search_ManageController::statusAction ( )

Provide a status update in Json format.

    {
        // enforce permissions.
        $this->acl->check('search', 'manage');

        $statusFile = P4Cms_Site::fetchActive()->getDataPath()
                    . '/' . $this->_maintenanceStatusFile;

        if (!file_exists($statusFile) ) {
            $status = array(
                'message' => 'Search Index maintenance task completed',
                'done' => true
            );
            $this->view->status = $status;
            return;
        }

        $status = $this->_readStatusFile();

        // for optimize, get the progress and merge it to the status file contents
        if (isset($status['action']) &&
            ($status['action'] == 'optimize') &&
            !$status['done']) {

            if (isset($status['index'])) {
                $status = array_merge(
                    $status,
                    $this->_getoptimizeProgress($status['index'])
                );
            } else {
                $status = array_merge($status, $this->_getoptimizeProgress());
            }
        }

        if (!array_key_exists('searchMaintenanceTask', $_SESSION)) {
            $status['message'] = "A Search Index '" . ucfirst($status['action'])
                               . "' operation is currently running. Its status and progress is below -- "
                               . $status['message'];
        }

        $this->contextSwitch->initContext('json');

        $this->view->status = $status;
    }

Member Data Documentation

Search_ManageController::$_activeIndexPath = 'search-index' [protected]
Search_ManageController::$_maintenanceLockFile = 'search.maintenance.lock.file' [protected]
Search_ManageController::$_maintenanceStatusFile = 'search.maintenance.status.file' [protected]
Search_ManageController::$_statusFile = null [protected]
Search_ManageController::$contexts
Initial value:
 array(
        'status' => array('json' => array('POST', 'GET'))
    )

The documentation for this class was generated from the following file: