<commit_before>/*
    This file is part of Akregator.

    Copyright (C) 2004 Stanislav Karchebny <Stanislav.Karchebny@kdemail.net>
                  2005-2008 Frank Osterfeld <osterfeld@kde.org>
    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.

    As a special exception, permission is given to link this program
    with any edition of Qt, and distribute the resulting executable,
    without including the source code for Qt in the source distribution.
*/

#include "articlelistview.h"
#include "actionmanager.h"
#include "akregatorconfig.h"
#include "article.h"
#include "articlemodel.h"
#include "kernel.h"
#include "types.h"

#include <utils/filtercolumnsproxymodel.h>

#include <KIcon>
#include <KLocale>
#include <KUrl>
#include <KMenu>

#include <QApplication>
#include <QContextMenuEvent>
#include <QHeaderView>
#include <QKeyEvent>
#include <QList>
#include <QMenu>
#include <QPaintEvent>
#include <QPalette>
#include <QScrollBar>

#include <cassert>

using namespace boost;
using namespace Akregator;

FilterDeletedProxyModel::FilterDeletedProxyModel( QObject* parent ) : QSortFilterProxyModel( parent )
{
    setDynamicSortFilter( true );
}

bool FilterDeletedProxyModel::filterAcceptsRow( int source_row, const QModelIndex& source_parent ) const
{
    return !sourceModel()->index( source_row, 0, source_parent ).data( ArticleModel::IsDeletedRole ).toBool();
}

SortColorizeProxyModel::SortColorizeProxyModel( QObject* parent ) : QSortFilterProxyModel( parent ), m_keepFlagIcon( KIcon( "mail-mark-important" ) )
{
}

bool SortColorizeProxyModel::filterAcceptsRow ( int source_row, const QModelIndex& source_parent ) const
{
    if ( source_parent.isValid() )
        return false;

    for ( uint i = 0; i < m_matchers.size(); ++i )
    {
        if ( !static_cast<ArticleModel*>( sourceModel() )->rowMatches( source_row, m_matchers[i] ) )
            return false;
    }

    return true;
}

void SortColorizeProxyModel::setFilters( const std::vector<shared_ptr<const Filters::AbstractMatcher> >&  matchers )
{
    if ( m_matchers == matchers )
        return;
    m_matchers = matchers;
    invalidateFilter();
}

QVariant SortColorizeProxyModel::data( const QModelIndex& idx, int role ) const
{
    if ( !idx.isValid() || !sourceModel() )
        return QVariant();

    const QModelIndex sourceIdx = mapToSource( idx );

    switch ( role )
    {
        case Qt::ForegroundRole:
        {
            switch ( static_cast<ArticleStatus>( sourceIdx.data( ArticleModel::StatusRole ).toInt() ) )
            {
                case Unread:
                {
                    return Settings::useCustomColors() ?
                        Settings::colorUnreadArticles() : Qt::blue;
                }
                case New:
                {
                    return Settings::useCustomColors() ?
                        Settings::colorNewArticles() : Qt::red;
                }
                case Read:
                {
                    return QApplication::palette().color( QPalette::WindowText );
                }
            }
        }
        break;
        case Qt::DecorationRole:
        {
            if ( sourceIdx.column() == ArticleModel::ItemTitleColumn )
            {
                return sourceIdx.data( ArticleModel::IsImportantRole ).toBool() ? m_keepFlagIcon : QVariant();
            }
        }
        break;
    }
    return sourceIdx.data( role );
}

namespace {

    static bool isRead( const QModelIndex& idx )
    {
        if ( !idx.isValid() )
            return false;

        return static_cast<ArticleStatus>( idx.data( ArticleModel::StatusRole ).toInt() ) == Read;
    }
}

void ArticleListView::setArticleModel( ArticleModel* model )
{
    if ( !model ) {
        setModel( model );
        return;
    }

    m_proxy = new SortColorizeProxyModel( model );
    m_proxy->setSourceModel( model );
    m_proxy->setSortRole( ArticleModel::SortRole );
    m_proxy->setFilters( m_matchers );
    FilterDeletedProxyModel* const proxy2 = new FilterDeletedProxyModel( model );
    proxy2->setSortRole( ArticleModel::SortRole );
    proxy2->setSourceModel( m_proxy );

    FilterColumnsProxyModel* const columnsProxy = new FilterColumnsProxyModel( model );
    columnsProxy->setSortRole( ArticleModel::SortRole );
    columnsProxy->setSourceModel( proxy2 );
    columnsProxy->setColumnEnabled( ArticleModel::ItemTitleColumn );
    columnsProxy->setColumnEnabled( ArticleModel::FeedTitleColumn );
    columnsProxy->setColumnEnabled( ArticleModel::DateColumn );
    columnsProxy->setColumnEnabled( ArticleModel::AuthorColumn );

    setModel( columnsProxy );
    header()->setContextMenuPolicy( Qt::CustomContextMenu );
}

void ArticleListView::showHeaderMenu(const QPoint& pos)
{
    if ( !model() )
        return;

    QPointer<KMenu> menu = new KMenu( this );
    menu->addTitle( i18n( "Columns" ) );
    menu->setAttribute( Qt::WA_DeleteOnClose );

    const int colCount = model()->columnCount();
    for ( int i = 0; i < colCount; ++i )
    {
        QAction* act = menu->addAction( model()->headerData( i, Qt::Horizontal ).toString() );
        act->setCheckable( true );
        act->setData( i );
        act->setChecked( !header()->isSectionHidden( i ) );
    }

    QPointer<QObject> that( this );
    QAction * const action = menu->exec( header()->mapToGlobal( pos ) );
    if ( that && action ) {
        const int col = action->data().toInt();
        if ( action->isChecked() )
            header()->showSection( col );
        else
            header()->hideSection( col );
    }
    delete menu;
}

void ArticleListView::saveHeaderSettings()
{
    if ( model() )
    {
        if ( m_columnMode == FeedMode )
            m_feedHeaderState = header()->saveState();
        if ( m_columnMode == GroupMode )
            m_groupHeaderState = header()->saveState();
    }

    KConfigGroup conf( Settings::self()->config(), "General" );
    conf.writeEntry( "ArticleListFeedHeaders", m_feedHeaderState.toBase64() );
    conf.writeEntry( "ArticleListGroupHeaders", m_groupHeaderState.toBase64() );
}

void ArticleListView::loadHeaderSettings()
{
    KConfigGroup conf( Settings::self()->config(), "General" );
    m_feedHeaderState = QByteArray::fromBase64( conf.readEntry( "ArticleListFeedHeaders" ).toAscii() );
    m_groupHeaderState = QByteArray::fromBase64( conf.readEntry( "ArticleListGroupHeaders" ).toAscii() );
}

QItemSelectionModel* ArticleListView::articleSelectionModel() const
{
    return selectionModel();
}

const QAbstractItemView* ArticleListView::itemView() const
{
    return this;
}

QAbstractItemView* ArticleListView::itemView()
{
    return this;
}

QPoint ArticleListView::scrollBarPositions() const
{
    return QPoint( horizontalScrollBar()->value(), verticalScrollBar()->value() );
}

void ArticleListView::setScrollBarPositions( const QPoint& p )
{
    horizontalScrollBar()->setValue( p.x() );
    verticalScrollBar()->setValue( p.y() );
}

void ArticleListView::setGroupMode()
{
    if ( m_columnMode == GroupMode )
        return;

    header()->restoreState( m_groupHeaderState );
    m_columnMode = GroupMode;
}

void ArticleListView::setFeedMode()
{
    if ( m_columnMode == FeedMode )
        return;

    header()->restoreState( m_feedHeaderState );
    m_columnMode = FeedMode;
}

ArticleListView::~ArticleListView()
{
    saveHeaderSettings();
}

void ArticleListView::setIsAggregation( bool aggregation )
{
    if ( aggregation == m_isAggregation )
        return;
    m_isAggregation = aggregation;
    if ( aggregation )
        setGroupMode();
    else
        setFeedMode();
}

ArticleListView::ArticleListView( QWidget* parent )
    : QTreeView(parent),
    m_columnMode( Unspecified ),
    m_isAggregation( false )
{
    setSortingEnabled( true );
    setAlternatingRowColors( true );
    setSelectionMode( QAbstractItemView::ExtendedSelection );
    setUniformRowHeights( true );
    setRootIsDecorated( false );
    setAllColumnsShowFocus( true );
    setMinimumSize( 250, 150 );
    setWhatsThis( i18n("
Article list
"
        "Here you can browse articles from the currently selected feed. "
        "You can also manage articles, as marking them as persistent (\"Keep Article\") or delete them, using the right mouse button menu."
        "To view the web page of the article, you can open the article internally in a tab or in an external browser window."));

    //connect exactly once
    disconnect( header(), SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showHeaderMenu(QPoint)) );
    connect( header(), SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showHeaderMenu(QPoint)) );
    loadHeaderSettings();
}

void ArticleListView::mousePressEvent( QMouseEvent *ev )
{
    // let's push the event, so we can use currentIndex() to get the newly selected article..
    QTreeView::mousePressEvent( ev );

    if( ev->button() == Qt::MidButton ) {
        QModelIndex idx( currentIndex() );
        const KUrl url = currentIndex().data( ArticleModel::LinkRole ).value<KUrl>();

        emit signalMouseButtonPressed( ev->button(), url );
    }
}

#if 0 // unused
namespace {
    static QString itemIdForIndex( const QModelIndex& index )
    {
        return index.isValid() ? index.data( ArticleModel::ItemIdRole ).toString() : QString();
    }

    static QStringList itemIdsForIndexes( const QModelIndexList& indexes )
    {
        QStringList articles;
        Q_FOREACH ( const QModelIndex i, indexes )
        {
            articles.append( itemIdForIndex( i ) );
        }

        return articles;
    }
}
#endif

void ArticleListView::contextMenuEvent( QContextMenuEvent* event )
{
    QWidget* w = ActionManager::getInstance()->container( "article_popup" );
    QMenu* popup = qobject_cast<QMenu*>( w );
    if ( popup )
        popup->exec( event->globalPos() );
}

void ArticleListView::paintEvent( QPaintEvent* e )
{
    QTreeView::paintEvent( e );

#ifdef __GNUC__
#warning The distinction between empty node and 0 items after filtering is hard to port to interview
#endif
#if 0
    QString message;

    if ( !model() || model()->rowCount() > 0 ) // article list is not empty
    {
        if (visibleArticles() == 0)
        {
            message = i18n("
"
                            "
No matches
"
                            "Filter does not match any articles, "
                            "please change your criteria and try again."
                            "
");
        }
    }
    else if ( !model() ) // article list is empty
    {
        if (!d->node) // no node selected
        {
            message = i18n("
"
                       "
No feed selected
"
                       "This area is article list. "
                       "Select a feed from the feed list "
                       "and you will see its articles here."
                       "
");
        }
    }

    if (!message.isNull())
        paintInfoBox( message, viewport(), palette() );
#endif
}

void ArticleListView::setModel( QAbstractItemModel* m )
{
    bool groupMode = ( m_columnMode == GroupMode );

    QAbstractItemModel* const oldModel = model();
    if ( oldModel )
    {
        if ( groupMode ) m_groupHeaderState = header()->saveState();
        else m_feedHeaderState = header()->saveState();
    }

    QTreeView::setModel( m );
    if ( m )
        header()->restoreState( groupMode ? m_groupHeaderState : m_feedHeaderState );
}

void ArticleListView::slotClear()
{
    setModel( 0L );
}

void ArticleListView::slotPreviousArticle()
{
    if ( !model() )
        return;

    const QModelIndex idx = currentIndex();
    const int newRow = qMax( 0, ( idx.isValid() ? idx.row() : model()->rowCount() ) - 1 );
    const QModelIndex newIdx = idx.isValid() ? idx.sibling( newRow, 0 ) : model()->index( newRow, 0 );
    selectIndex( newIdx );
}

void ArticleListView::slotNextArticle()
{
    if ( !model() )
        return;

    const QModelIndex idx = currentIndex();
    const int newRow = idx.isValid() ? ( idx.row() + 1 ) : 0;
    const QModelIndex newIdx = model()->index( qMin( newRow, model()->rowCount() - 1 ), 0 );
    selectIndex( newIdx );
}

void ArticleListView::slotNextUnreadArticle()
{
    if (!model())
        return;

    const int rowCount = model()->rowCount();
    const int startRow = qMin( rowCount - 1, ( currentIndex().isValid() ? currentIndex().row() + 1 : 0 ) );

    int i = startRow;
    bool foundUnread = false;

    do
    {
        if ( !::isRead( model()->index( i, 0 ) ) )
            foundUnread = true;
        else
            i = (i + 1) % rowCount;
    }
    while ( !foundUnread && i != startRow );

    if ( foundUnread )
    {
        selectIndex( model()->index( i, 0 ) );
    }
}

void ArticleListView::selectIndex( const QModelIndex& idx )
{
    if ( !idx.isValid() )
        return;
    setCurrentIndex( idx );
    clearSelection();
    Q_ASSERT( selectionModel() );
    selectionModel()->select( idx, QItemSelectionModel::Select | QItemSelectionModel::Rows );
    scrollTo( idx, PositionAtCenter );
}

void ArticleListView::slotPreviousUnreadArticle()
{
    if ( !model() )
        return;

    const int rowCount = model()->rowCount();
    const int startRow = qMax( 0, ( currentIndex().isValid() ? currentIndex().row() : rowCount ) - 1 );

    int i = startRow;
    bool foundUnread = false;

    do
    {
        if ( !::isRead( model()->index( i, 0 ) ) )
            foundUnread = true;
        else
            i = i > 0 ? i - 1 : rowCount - 1;
    }
    while ( !foundUnread && i != startRow );

    if ( foundUnread )
    {
        selectIndex( model()->index( i, 0 ) );
    }
}

void ArticleListView::forceFilterUpdate()
{
    if ( m_proxy )
        m_proxy->invalidate();
}

void ArticleListView::setFilters( const std::vector<shared_ptr<const Filters::AbstractMatcher> >& matchers )
{
    if ( m_matchers == matchers )
        return;
    m_matchers = matchers;
    if ( m_proxy )
        m_proxy->setFilters( matchers );
}

#include "articlelistview.moc"
<commit_msg>Work around possible Qt 4.4 bug causing the last column to expand beyond the viewport width.  Re-evaluate this after the transition to Qt 4.5.<commit_after>/*
    This file is part of Akregator.

    Copyright (C) 2004 Stanislav Karchebny <Stanislav.Karchebny@kdemail.net>
                  2005-2008 Frank Osterfeld <osterfeld@kde.org>
    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.

    As a special exception, permission is given to link this program
    with any edition of Qt, and distribute the resulting executable,
    without including the source code for Qt in the source distribution.
*/

#include "articlelistview.h"
#include "actionmanager.h"
#include "akregatorconfig.h"
#include "article.h"
#include "articlemodel.h"
#include "kernel.h"
#include "types.h"

#include <utils/filtercolumnsproxymodel.h>

#include <KIcon>
#include <KLocale>
#include <KUrl>
#include <KMenu>

#include <QApplication>
#include <QContextMenuEvent>
#include <QHeaderView>
#include <QKeyEvent>
#include <QList>
#include <QMenu>
#include <QPaintEvent>
#include <QPalette>
#include <QScrollBar>

#include <cassert>

using namespace boost;
using namespace Akregator;

FilterDeletedProxyModel::FilterDeletedProxyModel( QObject* parent ) : QSortFilterProxyModel( parent )
{
    setDynamicSortFilter( true );
}

bool FilterDeletedProxyModel::filterAcceptsRow( int source_row, const QModelIndex& source_parent ) const
{
    return !sourceModel()->index( source_row, 0, source_parent ).data( ArticleModel::IsDeletedRole ).toBool();
}

SortColorizeProxyModel::SortColorizeProxyModel( QObject* parent ) : QSortFilterProxyModel( parent ), m_keepFlagIcon( KIcon( "mail-mark-important" ) )
{
}

bool SortColorizeProxyModel::filterAcceptsRow ( int source_row, const QModelIndex& source_parent ) const
{
    if ( source_parent.isValid() )
        return false;

    for ( uint i = 0; i < m_matchers.size(); ++i )
    {
        if ( !static_cast<ArticleModel*>( sourceModel() )->rowMatches( source_row, m_matchers[i] ) )
            return false;
    }

    return true;
}

void SortColorizeProxyModel::setFilters( const std::vector<shared_ptr<const Filters::AbstractMatcher> >&  matchers )
{
    if ( m_matchers == matchers )
        return;
    m_matchers = matchers;
    invalidateFilter();
}

QVariant SortColorizeProxyModel::data( const QModelIndex& idx, int role ) const
{
    if ( !idx.isValid() || !sourceModel() )
        return QVariant();

    const QModelIndex sourceIdx = mapToSource( idx );

    switch ( role )
    {
        case Qt::ForegroundRole:
        {
            switch ( static_cast<ArticleStatus>( sourceIdx.data( ArticleModel::StatusRole ).toInt() ) )
            {
                case Unread:
                {
                    return Settings::useCustomColors() ?
                        Settings::colorUnreadArticles() : Qt::blue;
                }
                case New:
                {
                    return Settings::useCustomColors() ?
                        Settings::colorNewArticles() : Qt::red;
                }
                case Read:
                {
                    return QApplication::palette().color( QPalette::WindowText );
                }
            }
        }
        break;
        case Qt::DecorationRole:
        {
            if ( sourceIdx.column() == ArticleModel::ItemTitleColumn )
            {
                return sourceIdx.data( ArticleModel::IsImportantRole ).toBool() ? m_keepFlagIcon : QVariant();
            }
        }
        break;
    }
    return sourceIdx.data( role );
}

namespace {

    static bool isRead( const QModelIndex& idx )
    {
        if ( !idx.isValid() )
            return false;

        return static_cast<ArticleStatus>( idx.data( ArticleModel::StatusRole ).toInt() ) == Read;
    }
}

void ArticleListView::setArticleModel( ArticleModel* model )
{
    if ( !model ) {
        setModel( model );
        return;
    }

    m_proxy = new SortColorizeProxyModel( model );
    m_proxy->setSourceModel( model );
    m_proxy->setSortRole( ArticleModel::SortRole );
    m_proxy->setFilters( m_matchers );
    FilterDeletedProxyModel* const proxy2 = new FilterDeletedProxyModel( model );
    proxy2->setSortRole( ArticleModel::SortRole );
    proxy2->setSourceModel( m_proxy );

    FilterColumnsProxyModel* const columnsProxy = new FilterColumnsProxyModel( model );
    columnsProxy->setSortRole( ArticleModel::SortRole );
    columnsProxy->setSourceModel( proxy2 );
    columnsProxy->setColumnEnabled( ArticleModel::ItemTitleColumn );
    columnsProxy->setColumnEnabled( ArticleModel::FeedTitleColumn );
    columnsProxy->setColumnEnabled( ArticleModel::DateColumn );
    columnsProxy->setColumnEnabled( ArticleModel::AuthorColumn );

    setModel( columnsProxy );
    header()->setContextMenuPolicy( Qt::CustomContextMenu );
}

void ArticleListView::showHeaderMenu(const QPoint& pos)
{
    if ( !model() )
        return;

    QPointer<KMenu> menu = new KMenu( this );
    menu->addTitle( i18n( "Columns" ) );
    menu->setAttribute( Qt::WA_DeleteOnClose );

    const int colCount = model()->columnCount();
    for ( int i = 0; i < colCount; ++i )
    {
        QAction* act = menu->addAction( model()->headerData( i, Qt::Horizontal ).toString() );
        act->setCheckable( true );
        act->setData( i );
        act->setChecked( !header()->isSectionHidden( i ) );
    }

    QPointer<QObject> that( this );
    QAction * const action = menu->exec( header()->mapToGlobal( pos ) );
    if ( that && action ) {
        const int col = action->data().toInt();
        if ( action->isChecked() )
            header()->showSection( col );
        else
            header()->hideSection( col );
    }
    delete menu;
}

void ArticleListView::saveHeaderSettings()
{
    if ( model() )
    {
        if ( m_columnMode == FeedMode )
            m_feedHeaderState = header()->saveState();
        if ( m_columnMode == GroupMode )
            m_groupHeaderState = header()->saveState();
    }

    KConfigGroup conf( Settings::self()->config(), "General" );
    conf.writeEntry( "ArticleListFeedHeaders", m_feedHeaderState.toBase64() );
    conf.writeEntry( "ArticleListGroupHeaders", m_groupHeaderState.toBase64() );
}

void ArticleListView::loadHeaderSettings()
{
    KConfigGroup conf( Settings::self()->config(), "General" );
    m_feedHeaderState = QByteArray::fromBase64( conf.readEntry( "ArticleListFeedHeaders" ).toAscii() );
    m_groupHeaderState = QByteArray::fromBase64( conf.readEntry( "ArticleListGroupHeaders" ).toAscii() );
}

QItemSelectionModel* ArticleListView::articleSelectionModel() const
{
    return selectionModel();
}

const QAbstractItemView* ArticleListView::itemView() const
{
    return this;
}

QAbstractItemView* ArticleListView::itemView()
{
    return this;
}

QPoint ArticleListView::scrollBarPositions() const
{
    return QPoint( horizontalScrollBar()->value(), verticalScrollBar()->value() );
}

void ArticleListView::setScrollBarPositions( const QPoint& p )
{
    horizontalScrollBar()->setValue( p.x() );
    verticalScrollBar()->setValue( p.y() );
}

void ArticleListView::setGroupMode()
{
    if ( m_columnMode == GroupMode )
        return;

    // The next line (used three times in this file) is a workaround for a
    // possible Qt 4.4.3 bug that causes the last column to expand beyond
    // the viewport width.  QHeaderViewPrivate::lastSectionSize may not be
    // initialised when QHeaderViewPrivate::resizeSections() is called,
    // doing the resizeSection() here ensures that it has a sensible value.
    // This may not be necessary with Qt 4.5.
    header()->resizeSection( header()->count() - 1, 1 );

    header()->restoreState( m_groupHeaderState );
    m_columnMode = GroupMode;
}

void ArticleListView::setFeedMode()
{
    if ( m_columnMode == FeedMode )
        return;

    header()->resizeSection( header()->count() - 1, 1 );
    header()->restoreState( m_feedHeaderState );
    m_columnMode = FeedMode;
}

ArticleListView::~ArticleListView()
{
    saveHeaderSettings();
}

void ArticleListView::setIsAggregation( bool aggregation )
{
    if ( aggregation == m_isAggregation )
        return;
    m_isAggregation = aggregation;
    if ( aggregation )
        setGroupMode();
    else
        setFeedMode();
}

ArticleListView::ArticleListView( QWidget* parent )
    : QTreeView(parent),
    m_columnMode( Unspecified ),
    m_isAggregation( false )
{
    setSortingEnabled( true );
    setAlternatingRowColors( true );
    setSelectionMode( QAbstractItemView::ExtendedSelection );
    setUniformRowHeights( true );
    setRootIsDecorated( false );
    setAllColumnsShowFocus( true );
    setMinimumSize( 250, 150 );
    setWhatsThis( i18n("
Article list
"
        "Here you can browse articles from the currently selected feed. "
        "You can also manage articles, as marking them as persistent (\"Keep Article\") or delete them, using the right mouse button menu."
        "To view the web page of the article, you can open the article internally in a tab or in an external browser window."));

    //connect exactly once
    disconnect( header(), SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showHeaderMenu(QPoint)) );
    connect( header(), SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showHeaderMenu(QPoint)) );
    loadHeaderSettings();
}

void ArticleListView::mousePressEvent( QMouseEvent *ev )
{
    // let's push the event, so we can use currentIndex() to get the newly selected article..
    QTreeView::mousePressEvent( ev );

    if( ev->button() == Qt::MidButton ) {
        QModelIndex idx( currentIndex() );
        const KUrl url = currentIndex().data( ArticleModel::LinkRole ).value<KUrl>();

        emit signalMouseButtonPressed( ev->button(), url );
    }
}

#if 0 // unused
namespace {
    static QString itemIdForIndex( const QModelIndex& index )
    {
        return index.isValid() ? index.data( ArticleModel::ItemIdRole ).toString() : QString();
    }

    static QStringList itemIdsForIndexes( const QModelIndexList& indexes )
    {
        QStringList articles;
        Q_FOREACH ( const QModelIndex i, indexes )
        {
            articles.append( itemIdForIndex( i ) );
        }

        return articles;
    }
}
#endif

void ArticleListView::contextMenuEvent( QContextMenuEvent* event )
{
    QWidget* w = ActionManager::getInstance()->container( "article_popup" );
    QMenu* popup = qobject_cast<QMenu*>( w );
    if ( popup )
        popup->exec( event->globalPos() );
}

void ArticleListView::paintEvent( QPaintEvent* e )
{
    QTreeView::paintEvent( e );

#ifdef __GNUC__
#warning The distinction between empty node and 0 items after filtering is hard to port to interview
#endif
#if 0
    QString message;

    if ( !model() || model()->rowCount() > 0 ) // article list is not empty
    {
        if (visibleArticles() == 0)
        {
            message = i18n("
"
                            "
No matches
"
                            "Filter does not match any articles, "
                            "please change your criteria and try again."
                            "
");
        }
    }
    else if ( !model() ) // article list is empty
    {
        if (!d->node) // no node selected
        {
            message = i18n("
"
                       "
No feed selected
"
                       "This area is article list. "
                       "Select a feed from the feed list "
                       "and you will see its articles here."
                       "
");
        }
    }

    if (!message.isNull())
        paintInfoBox( message, viewport(), palette() );
#endif
}

void ArticleListView::setModel( QAbstractItemModel* m )
{
    bool groupMode = ( m_columnMode == GroupMode );

    QAbstractItemModel* const oldModel = model();
    if ( oldModel )
    {
        if ( groupMode ) m_groupHeaderState = header()->saveState();
        else m_feedHeaderState = header()->saveState();
    }

    QTreeView::setModel( m );
    if ( m )
    {
        header()->resizeSection( header()->count() - 1, 1 );
        header()->restoreState( groupMode ? m_groupHeaderState : m_feedHeaderState );
    }
}

void ArticleListView::slotClear()
{
    setModel( 0L );
}

void ArticleListView::slotPreviousArticle()
{
    if ( !model() )
        return;

    const QModelIndex idx = currentIndex();
    const int newRow = qMax( 0, ( idx.isValid() ? idx.row() : model()->rowCount() ) - 1 );
    const QModelIndex newIdx = idx.isValid() ? idx.sibling( newRow, 0 ) : model()->index( newRow, 0 );
    selectIndex( newIdx );
}

void ArticleListView::slotNextArticle()
{
    if ( !model() )
        return;

    const QModelIndex idx = currentIndex();
    const int newRow = idx.isValid() ? ( idx.row() + 1 ) : 0;
    const QModelIndex newIdx = model()->index( qMin( newRow, model()->rowCount() - 1 ), 0 );
    selectIndex( newIdx );
}

void ArticleListView::slotNextUnreadArticle()
{
    if (!model())
        return;

    const int rowCount = model()->rowCount();
    const int startRow = qMin( rowCount - 1, ( currentIndex().isValid() ? currentIndex().row() + 1 : 0 ) );

    int i = startRow;
    bool foundUnread = false;

    do
    {
        if ( !::isRead( model()->index( i, 0 ) ) )
            foundUnread = true;
        else
            i = (i + 1) % rowCount;
    }
    while ( !foundUnread && i != startRow );

    if ( foundUnread )
    {
        selectIndex( model()->index( i, 0 ) );
    }
}

void ArticleListView::selectIndex( const QModelIndex& idx )
{
    if ( !idx.isValid() )
        return;
    setCurrentIndex( idx );
    clearSelection();
    Q_ASSERT( selectionModel() );
    selectionModel()->select( idx, QItemSelectionModel::Select | QItemSelectionModel::Rows );
    scrollTo( idx, PositionAtCenter );
}

void ArticleListView::slotPreviousUnreadArticle()
{
    if ( !model() )
        return;

    const int rowCount = model()->rowCount();
    const int startRow = qMax( 0, ( currentIndex().isValid() ? currentIndex().row() : rowCount ) - 1 );

    int i = startRow;
    bool foundUnread = false;

    do
    {
        if ( !::isRead( model()->index( i, 0 ) ) )
            foundUnread = true;
        else
            i = i > 0 ? i - 1 : rowCount - 1;
    }
    while ( !foundUnread && i != startRow );

    if ( foundUnread )
    {
        selectIndex( model()->index( i, 0 ) );
    }
}

void ArticleListView::forceFilterUpdate()
{
    if ( m_proxy )
        m_proxy->invalidate();
}

void ArticleListView::setFilters( const std::vector<shared_ptr<const Filters::AbstractMatcher> >& matchers )
{
    if ( m_matchers == matchers )
        return;
    m_matchers = matchers;
    if ( m_proxy )
        m_proxy->setFilters( matchers );
}

#include "articlelistview.moc"
<|endoftext|>


<commit_before>#include "core/numeric.h"
#include "lsearch_cgdescent.h"

using namespace nano;

bool lsearch_cgdescent_t::updateU(const solver_state_t& state0,
        solver_state_t& a, solver_state_t& b, solver_state_t& c)
{
        assert(0 < m_theta && m_theta < 1);

        for (int i = 0; i < max_iterations(); ++ i)
        {
                if (evaluate(state0, (1 - m_theta) * a.t + m_theta * b.t, a, b, c))
                {
                        return true;
                }
                else if (!c.has_descent())
                {
                        b = c;
                        return false;
                }
                else if (c.has_approx_armijo(state0, m_epsilon))
                {
                        a = c;
                }
                else
                {
                        b = c;
                }
        }

        return false;
}

bool lsearch_cgdescent_t::update(const solver_state_t& state0,
        solver_state_t& a, solver_state_t& b, solver_state_t& c)
{
        if (c.t <= a.t || c.t >= b.t)
        {
                return false;
        }
        else if (!c.has_descent())
        {
                b = c;
                return false;
        }
        else if (c.has_approx_armijo(state0, m_epsilon))
        {
                a = c;
                return false;
        }
        else
        {
                b = c;
                return updateU(state0, a, b, c);
        }
}

static auto secant(const scalar_t ta, const scalar_t dga, const scalar_t tb, const scalar_t dgb)
{
        const auto tsec = (ta * dgb - tb * dga) / (dgb - dga);
        const auto tbis = (ta + tb) / 2;
        return (std::isfinite(tsec) && ta < tsec && tsec < tb) ? tsec : tbis;
        // todo: use cubic interpolation here if possible!
}

bool lsearch_cgdescent_t::secant2(const solver_state_t& state0,
        solver_state_t& a, solver_state_t& b, solver_state_t& c)
{
        const auto ta = a.t, dga = a.dg();
        const auto tb = b.t, dgb = b.dg();
        const auto tc = secant(ta, dga, tb, dgb);

        if (evaluate(state0, tc, a, b, c))
        {
                return true;
        }
        else if (update(state0, a, b, c))
        {
                return true;
        }
        else if (std::fabs(tc - a.t) < epsilon0<scalar_t>())
        {
                return  evaluate(state0, secant(ta, dga, a.t, a.dg()), a, b, c) ||
                        update(state0, a, b, c);
        }
        else if (std::fabs(tc - b.t) < epsilon0<scalar_t>())
        {
                return  evaluate(state0, secant(tb, dgb, b.t, b.dg()), a, b, c) ||
                        update(state0, a, b, c);
        }
        else
        {
                return false;
        }
}

bool lsearch_cgdescent_t::bracket(const solver_state_t& state0,
        solver_state_t& a, solver_state_t& b, solver_state_t& c)
{
        assert(m_ro > 1);

        solver_state_t last_a = a;
        for (int i = 0; i < max_iterations(); ++ i)
        {
                if (!c.has_descent())
                {
                        a = last_a;
                        b = c;
                        return false;
                }
                else if (!c.has_approx_armijo(state0, m_epsilon))
                {
                        a = state0;
                        b = c;
                        return updateU(state0, a, b, c);
                }
                else
                {
                        last_a = c;
                        if (evaluate(state0, m_ro * c.t, a, b, c))
                        {
                                return true;
                        }
                }
        }

        return false;
}

bool lsearch_cgdescent_t::evaluate(const solver_state_t& state0, const scalar_t t,
        solver_state_t& c)
{
        // check overflow
        if (!c.update(state0, t))
        {
                return true;
        }

        // check Armijo+Wolfe conditions or the approximate versions
        const auto done =
                (!m_approx && c.has_armijo(state0, c1()) && c.has_wolfe(state0, c2())) ||
                (m_approx && c.has_approx_armijo(state0, m_epsilon) && c.has_approx_wolfe(state0, c1(), c2()));

        if (done && !m_approx)
        {
                // decide if to switch permanently to the approximate Wolfe conditions
                m_approx = std::fabs(c.f - state0.f) <= m_omega * m_sumC;
        }

        return done;
}

bool lsearch_cgdescent_t::evaluate(const solver_state_t& state0, const scalar_t t,
        const solver_state_t& a, const solver_state_t& b, solver_state_t& c)
{
        if (evaluate(state0, t, c))
        {
                return true;
        }

        // check if the search interval is too small
        if (std::fabs(b.t - a.t) < epsilon0<scalar_t>())
        {
                return true;
        }

        // go on on updating the search interval
        return false;
}

bool lsearch_cgdescent_t::get(const solver_state_t& state0, const scalar_t t0, solver_state_t& state)
{
        // estimate an upper bound of the function value
        // (to be used for the approximate Wolfe condition)
        m_sumQ = 1 + m_sumQ * m_delta;
        m_sumC = m_sumC + (std::fabs(state0.f) - m_sumC) / m_sumQ;
        m_epsilon = m_epsilon0 * m_sumC;

        // evaluate the initial step length
        auto& c = state;
        if (evaluate(state0, t0, c))
        {
                return true;
        }

        // bracket the initial step size
        auto a = state0, b = c;
        if (bracket(state0, a, b, c))
        {
                return true;
        }

        // iteratively update the search interval [a, b]
        for (int i = 0; i < max_iterations(); ++ i)
        {
                // secant interpolation
                const auto prev_width = std::fabs(b.t - a.t);
                if (secant2(state0, a, b, c))
                {
                        return true;
                }

                // update search interval
                if (b.t - a.t > m_gamma * prev_width)
                {
                        if (    evaluate(state0, (a.t + b.t) / 2, a, b, c) ||
                                update(state0, a, b, c))
                        {
                                return true;
                        }
                }
        }

        return false;
}
<commit_msg>more efficient cg-descent (use cubic interpolation if possible)<commit_after>#include "core/numeric.h"
#include "lsearch_cgdescent.h"

using namespace nano;

bool lsearch_cgdescent_t::updateU(const solver_state_t& state0,
        solver_state_t& a, solver_state_t& b, solver_state_t& c)
{
        assert(0 < m_theta && m_theta < 1);

        for (int i = 0; i < max_iterations(); ++ i)
        {
                if (evaluate(state0, (1 - m_theta) * a.t + m_theta * b.t, a, b, c))
                {
                        return true;
                }
                else if (!c.has_descent())
                {
                        b = c;
                        return false;
                }
                else if (c.has_approx_armijo(state0, m_epsilon))
                {
                        a = c;
                }
                else
                {
                        b = c;
                }
        }

        return false;
}

bool lsearch_cgdescent_t::update(const solver_state_t& state0,
        solver_state_t& a, solver_state_t& b, solver_state_t& c)
{
        if (c.t <= a.t || c.t >= b.t)
        {
                return false;
        }
        else if (!c.has_descent())
        {
                b = c;
                return false;
        }
        else if (c.has_approx_armijo(state0, m_epsilon))
        {
                a = c;
                return false;
        }
        else
        {
                b = c;
                return updateU(state0, a, b, c);
        }
}

static auto cubic(
        const scalar_t u, const scalar_t fu, const scalar_t gu,
        const scalar_t v, const scalar_t fv, const scalar_t gv)
{
        // fit cubic: q(x) = a*x^3 + b*x^2 + c*x + d
        //      given: q(u) = fu, q'(u) = gu
        //      given: q(v) = fv, q'(v) = gv
        // minimizer: solution of 3*a*x^2 + 2*b*x + c = 0
        // see "Numerical optimization", Nocedal & Wright, 2nd edition, p.59
        const auto d1 = gu + gv - 3 * (fu - fv) / (u - v);
        const auto d2 = (v > u ? +1 : -1) * std::sqrt(d1 * d1 - gu * gv);
        return v - (v - u) * (gv + d2 - d1) / (gv - gu + 2 * d2);
}

static auto quadratic(
        const scalar_t u, const scalar_t gu,
        const scalar_t v, const scalar_t gv)
{
        // fit quadratic: q(x) = a*x^2 + b*x + c
        //      given: q'(u) = gu
        //      given: q'(v) = gv
        // minimizer: -b/2a
        return (v * gu - u * gv) / (gu - gv);
}

static auto bisection(
        const scalar_t u, const scalar_t v)
{
        // minimizer: (u+v)/2
        return (u + v) / 2;
}

static auto interpolate(
        const scalar_t u, const scalar_t fu, const scalar_t gu,
        const scalar_t v, const scalar_t fv, const scalar_t gv)
{
        const auto tc = cubic(u, fu, gu, v, fv, gv);
        if (std::isfinite(tc) && std::min(u, v) < tc && tc < std::max(u, v))
        {
                return tc;
        }

        const auto tq = quadratic(u, gu, v, gv);
        if (std::isfinite(tc) && std::min(u, v) < tq && tq < std::max(u, v))
        {
                return tq;
        }

        return bisection(u, v);
}

bool lsearch_cgdescent_t::secant2(const solver_state_t& state0,
        solver_state_t& a, solver_state_t& b, solver_state_t& c)
{
        const auto ta = a.t, fa = a.f, dga = a.dg();
        const auto tb = b.t, fb = b.f, dgb = b.dg();
        const auto tc = interpolate(ta, fa, dga, tb, fb, dgb);

        if (evaluate(state0, tc, a, b, c))
        {
                return true;
        }
        else if (update(state0, a, b, c))
        {
                return true;
        }
        else if (std::fabs(tc - a.t) < epsilon0<scalar_t>())
        {
                return  evaluate(state0, interpolate(ta, fa, dga, a.t, a.f, a.dg()), a, b, c) ||
                        update(state0, a, b, c);
        }
        else if (std::fabs(tc - b.t) < epsilon0<scalar_t>())
        {
                return  evaluate(state0, interpolate(tb, fb, dgb, b.t, b.f, b.dg()), a, b, c) ||
                        update(state0, a, b, c);
        }
        else
        {
                return false;
        }
}

bool lsearch_cgdescent_t::bracket(const solver_state_t& state0,
        solver_state_t& a, solver_state_t& b, solver_state_t& c)
{
        assert(m_ro > 1);

        solver_state_t last_a = a;
        for (int i = 0; i < max_iterations(); ++ i)
        {
                if (!c.has_descent())
                {
                        a = last_a;
                        b = c;
                        return false;
                }
                else if (!c.has_approx_armijo(state0, m_epsilon))
                {
                        a = state0;
                        b = c;
                        return updateU(state0, a, b, c);
                }
                else
                {
                        last_a = c;
                        if (evaluate(state0, m_ro * c.t, a, b, c))
                        {
                                return true;
                        }
                }
        }

        return false;
}

bool lsearch_cgdescent_t::evaluate(const solver_state_t& state0, const scalar_t t,
        solver_state_t& c)
{
        // check overflow
        if (!c.update(state0, t))
        {
                return true;
        }

        // check Armijo+Wolfe conditions or the approximate versions
        const auto done =
                (!m_approx && c.has_armijo(state0, c1()) && c.has_wolfe(state0, c2())) ||
                (m_approx && c.has_approx_armijo(state0, m_epsilon) && c.has_approx_wolfe(state0, c1(), c2()));

        if (done && !m_approx)
        {
                // decide if to switch permanently to the approximate Wolfe conditions
                m_approx = std::fabs(c.f - state0.f) <= m_omega * m_sumC;
        }

        return done;
}

bool lsearch_cgdescent_t::evaluate(const solver_state_t& state0, const scalar_t t,
        const solver_state_t& a, const solver_state_t& b, solver_state_t& c)
{
        if (evaluate(state0, t, c))
        {
                return true;
        }

        // check if the search interval is too small
        if (std::fabs(b.t - a.t) < epsilon0<scalar_t>())
        {
                return true;
        }

        // go on on updating the search interval
        return false;
}

bool lsearch_cgdescent_t::get(const solver_state_t& state0, const scalar_t t0, solver_state_t& state)
{
        // estimate an upper bound of the function value
        // (to be used for the approximate Wolfe condition)
        m_sumQ = 1 + m_sumQ * m_delta;
        m_sumC = m_sumC + (std::fabs(state0.f) - m_sumC) / m_sumQ;
        m_epsilon = m_epsilon0 * m_sumC;

        // evaluate the initial step length
        auto& c = state;
        if (evaluate(state0, t0, c))
        {
                return true;
        }

        // bracket the initial step size
        auto a = state0, b = c;
        if (bracket(state0, a, b, c))
        {
                return true;
        }

        // iteratively update the search interval [a, b]
        for (int i = 0; i < max_iterations(); ++ i)
        {
                // secant interpolation
                const auto prev_width = std::fabs(b.t - a.t);
                if (secant2(state0, a, b, c))
                {
                        return true;
                }

                // update search interval
                if (b.t - a.t > m_gamma * prev_width)
                {
                        if (    evaluate(state0, (a.t + b.t) / 2, a, b, c) ||
                                update(state0, a, b, c))
                        {
                                return true;
                        }
                }
        }

        return false;
}
<|endoftext|>


<commit_before>// The libMesh Finite Element Library.
// Copyright (C) 2002-2012 Benjamin S. Kirk, John W. Peterson, Roy H. Stogner

// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.

// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.

// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

#include "libmesh/twostep_time_solver.h"
#include "libmesh/diff_system.h"
#include "libmesh/euler_solver.h"
#include "libmesh/numeric_vector.h"

namespace libMesh
{

TwostepTimeSolver::TwostepTimeSolver (sys_type& s)
  : AdaptiveTimeSolver(s)

{
  // We start with a reasonable time solver: implicit Euler
  core_time_solver.reset(new EulerSolver(s));
}

TwostepTimeSolver::~TwostepTimeSolver ()
{
}

void TwostepTimeSolver::solve()
{
  libmesh_assert(core_time_solver.get());

  // The core_time_solver will handle any first_solve actions
  first_solve = false;

  // We may have to repeat timesteps entirely if our error is bad
  // enough
  bool max_tolerance_met = false;

  // Calculating error values each time
  Real single_norm(0.), double_norm(0.), error_norm(0.),
       relative_error(0.);

  while (!max_tolerance_met)
    {
      // If we've been asked to reduce deltat if necessary, make sure
      // the core timesolver does so
      core_time_solver->reduce_deltat_on_diffsolver_failure =
        this->reduce_deltat_on_diffsolver_failure;

      if (!quiet)
        {
          libMesh::out << "\n === Computing adaptive timestep === "
                        << std::endl;
        }

      // Use the double-length timestep first (so the
      // old_nonlinear_solution won't have to change)
      core_time_solver->solve();

      // Save a copy of the double-length nonlinear solution
      // and the old nonlinear solution
      AutoPtr<NumericVector<Number> > double_solution =
        _system.solution->clone();
      AutoPtr<NumericVector<Number> > old_solution =
        _system.get_vector("_old_nonlinear_solution").clone();

      double_norm = calculate_norm(_system, *double_solution);
      if (!quiet)
        {
	  libMesh::out << "Double norm = " << double_norm << std::endl;
        }

      // Then reset the initial guess for our single-length calcs
      *(_system.solution) = _system.get_vector("_old_nonlinear_solution");

      // Call two single-length timesteps
      // Be sure that the core_time_solver does not change the
      // timestep here.  (This is unlikely because it just succeeded
      // with a timestep twice as large!)
      // FIXME: even if diffsolver failure is unlikely, we ought to
      // do *something* if it happens
      core_time_solver->reduce_deltat_on_diffsolver_failure = 0;

      Real old_time = _system.time;
      Real old_deltat = _system.deltat;
      _system.deltat *= 0.5;
      core_time_solver->solve();
      core_time_solver->advance_timestep();
      core_time_solver->solve();

      single_norm = calculate_norm(_system, *_system.solution);
      if (!quiet)
        {
          libMesh::out << "Single norm = " << single_norm << std::endl;
        }

      // Reset the core_time_solver's reduce_deltat... value.
      core_time_solver->reduce_deltat_on_diffsolver_failure =
        this->reduce_deltat_on_diffsolver_failure;

      // But then back off just in case our advance_timestep() isn't
      // called.
      // FIXME: this probably doesn't work with multistep methods
      _system.get_vector("_old_nonlinear_solution") = *old_solution;
      _system.time = old_time;
      _system.deltat = old_deltat;

      // Find the relative error
      *double_solution -= *(_system.solution);
      error_norm  = calculate_norm(_system, *double_solution);
      relative_error = error_norm / _system.deltat /
        std::max(double_norm, single_norm);

      // If the relative error makes no sense, we're done
      if (!double_norm && !single_norm)
        return;

      if (!quiet)
        {
          libMesh::out << "Error norm = " << error_norm << std::endl;
          libMesh::out << "Local relative error = "
		        << (error_norm /
                            std::max(double_norm, single_norm))
                        << std::endl;
          libMesh::out << "Global relative error = "
		        << (error_norm / _system.deltat /
                            std::max(double_norm, single_norm))
                        << std::endl;
          libMesh::out << "old delta t = " << _system.deltat << std::endl;
        }

      // If we haven't met our upper error tolerance, we'll have to
      // repeat this timestep entirely
      if (this->upper_tolerance && relative_error > this->upper_tolerance)
        {
	  // Reset the initial guess for our next try
	  *(_system.solution) =
            _system.get_vector("_old_nonlinear_solution");

          // Chop delta t in half
          _system.deltat /= 2.;

          if (!quiet)
            {
              libMesh::out << "Failed to meet upper error tolerance"
                            << std::endl;
              libMesh::out << "Retrying with delta t = "
                            << _system.deltat << std::endl;
            }
        }
      else
        max_tolerance_met = true;
    }

  // Otherwise, compare the relative error to the tolerance
  // and adjust deltat
  last_deltat = _system.deltat;

  const Real global_shrink_or_growth_factor =
    std::pow(this->target_tolerance / relative_error,
	     static_cast<Real>(1. / core_time_solver->error_order()));

  const Real local_shrink_or_growth_factor =
    std::pow(this->target_tolerance /
	     (error_norm/std::max(double_norm, single_norm)),
	     static_cast<Real>(1. / (core_time_solver->error_order()+1.)));

  if (!quiet)
    {
      libMesh::out << "The global growth/shrink factor is: "
		    << global_shrink_or_growth_factor << std::endl;
      libMesh::out << "The local growth/shrink factor is: "
		    << local_shrink_or_growth_factor << std::endl;
    }

  // The local s.o.g. factor is based on the expected **local**
  // truncation error for the timestepping method, the global
  // s.o.g. factor is based on the method's **global** truncation
  // error.  You can shrink/grow the timestep to attempt to satisfy
  // either a global or local time-discretization error tolerance.

  Real shrink_or_growth_factor =
    this->global_tolerance ? global_shrink_or_growth_factor :
                             local_shrink_or_growth_factor;

  if (this->max_growth && this->max_growth < shrink_or_growth_factor)
    {
      if (!quiet && this->global_tolerance)
        {
	  libMesh::out << "delta t is constrained by max_growth" << std::endl;
	}
        shrink_or_growth_factor = this->max_growth;
    }

  _system.deltat *= shrink_or_growth_factor;

  // Restrict deltat to max-allowable value if necessary
  if ((this->max_deltat != 0.0) && (_system.deltat > this->max_deltat))
    {
      if (!quiet)
	{
	  libMesh::out << "delta t is constrained by maximum-allowable delta t."
                        << std::endl;
	}
      _system.deltat = this->max_deltat;
    }

  // Restrict deltat to min-allowable value if necessary
  if ((this->min_deltat != 0.0) && (_system.deltat < this->min_deltat))
    {
      if (!quiet)
	{
	  libMesh::out << "delta t is constrained by minimum-allowable delta t."
                        << std::endl;
	}
      _system.deltat = this->min_deltat;
    }

  if (!quiet)
    {
      libMesh::out << "new delta t = " << _system.deltat << std::endl;
    }
}

} // namespace libMesh
<commit_msg>Allow TimeSolver tolerances relative to first step<commit_after>// The libMesh Finite Element Library.
// Copyright (C) 2002-2012 Benjamin S. Kirk, John W. Peterson, Roy H. Stogner

// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.

// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.

// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

#include "libmesh/twostep_time_solver.h"
#include "libmesh/diff_system.h"
#include "libmesh/euler_solver.h"
#include "libmesh/numeric_vector.h"

namespace libMesh
{

TwostepTimeSolver::TwostepTimeSolver (sys_type& s)
  : AdaptiveTimeSolver(s)

{
  // We start with a reasonable time solver: implicit Euler
  core_time_solver.reset(new EulerSolver(s));
}

TwostepTimeSolver::~TwostepTimeSolver ()
{
}

void TwostepTimeSolver::solve()
{
  libmesh_assert(core_time_solver.get());

  // The core_time_solver will handle any first_solve actions
  first_solve = false;

  // We may have to repeat timesteps entirely if our error is bad
  // enough
  bool max_tolerance_met = false;

  // Calculating error values each time
  Real single_norm(0.), double_norm(0.), error_norm(0.),
       relative_error(0.);

  while (!max_tolerance_met)
    {
      // If we've been asked to reduce deltat if necessary, make sure
      // the core timesolver does so
      core_time_solver->reduce_deltat_on_diffsolver_failure =
        this->reduce_deltat_on_diffsolver_failure;

      if (!quiet)
        {
          libMesh::out << "\n === Computing adaptive timestep === "
                        << std::endl;
        }

      // Use the double-length timestep first (so the
      // old_nonlinear_solution won't have to change)
      core_time_solver->solve();

      // Save a copy of the double-length nonlinear solution
      // and the old nonlinear solution
      AutoPtr<NumericVector<Number> > double_solution =
        _system.solution->clone();
      AutoPtr<NumericVector<Number> > old_solution =
        _system.get_vector("_old_nonlinear_solution").clone();

      double_norm = calculate_norm(_system, *double_solution);
      if (!quiet)
        {
	  libMesh::out << "Double norm = " << double_norm << std::endl;
        }

      // Then reset the initial guess for our single-length calcs
      *(_system.solution) = _system.get_vector("_old_nonlinear_solution");

      // Call two single-length timesteps
      // Be sure that the core_time_solver does not change the
      // timestep here.  (This is unlikely because it just succeeded
      // with a timestep twice as large!)
      // FIXME: even if diffsolver failure is unlikely, we ought to
      // do *something* if it happens
      core_time_solver->reduce_deltat_on_diffsolver_failure = 0;

      Real old_time = _system.time;
      Real old_deltat = _system.deltat;
      _system.deltat *= 0.5;
      core_time_solver->solve();
      core_time_solver->advance_timestep();
      core_time_solver->solve();

      single_norm = calculate_norm(_system, *_system.solution);
      if (!quiet)
        {
          libMesh::out << "Single norm = " << single_norm << std::endl;
        }

      // Reset the core_time_solver's reduce_deltat... value.
      core_time_solver->reduce_deltat_on_diffsolver_failure =
        this->reduce_deltat_on_diffsolver_failure;

      // But then back off just in case our advance_timestep() isn't
      // called.
      // FIXME: this probably doesn't work with multistep methods
      _system.get_vector("_old_nonlinear_solution") = *old_solution;
      _system.time = old_time;
      _system.deltat = old_deltat;

      // Find the relative error
      *double_solution -= *(_system.solution);
      error_norm  = calculate_norm(_system, *double_solution);
      relative_error = error_norm / _system.deltat /
        std::max(double_norm, single_norm);

      // If the relative error makes no sense, we're done
      if (!double_norm && !single_norm)
        return;

      if (!quiet)
        {
          libMesh::out << "Error norm = " << error_norm << std::endl;
          libMesh::out << "Local relative error = "
		        << (error_norm /
                            std::max(double_norm, single_norm))
                        << std::endl;
          libMesh::out << "Global relative error = "
		        << (error_norm / _system.deltat /
                            std::max(double_norm, single_norm))
                        << std::endl;
          libMesh::out << "old delta t = " << _system.deltat << std::endl;
        }

      // If our upper tolerance is negative, that means we want to set
      // it based on the first successful time step
      if (this->upper_tolerance < 0)
        this->upper_tolerance = -this->upper_tolerance * relative_error;

      // If we haven't met our upper error tolerance, we'll have to
      // repeat this timestep entirely
      if (this->upper_tolerance && relative_error > this->upper_tolerance)
        {
	  // Reset the initial guess for our next try
	  *(_system.solution) =
            _system.get_vector("_old_nonlinear_solution");

          // Chop delta t in half
          _system.deltat /= 2.;

          if (!quiet)
            {
              libMesh::out << "Failed to meet upper error tolerance"
                            << std::endl;
              libMesh::out << "Retrying with delta t = "
                            << _system.deltat << std::endl;
            }
        }
      else
        max_tolerance_met = true;
    }

  // Otherwise, compare the relative error to the tolerance
  // and adjust deltat
  last_deltat = _system.deltat;

  // If our target tolerance is negative, that means we want to set
  // it based on the first successful time step
  if (this->target_tolerance < 0)
    this->target_tolerance = -this->target_tolerance * relative_error;

  const Real global_shrink_or_growth_factor =
    std::pow(this->target_tolerance / relative_error,
	     static_cast<Real>(1. / core_time_solver->error_order()));

  const Real local_shrink_or_growth_factor =
    std::pow(this->target_tolerance /
	     (error_norm/std::max(double_norm, single_norm)),
	     static_cast<Real>(1. / (core_time_solver->error_order()+1.)));

  if (!quiet)
    {
      libMesh::out << "The global growth/shrink factor is: "
		    << global_shrink_or_growth_factor << std::endl;
      libMesh::out << "The local growth/shrink factor is: "
		    << local_shrink_or_growth_factor << std::endl;
    }

  // The local s.o.g. factor is based on the expected **local**
  // truncation error for the timestepping method, the global
  // s.o.g. factor is based on the method's **global** truncation
  // error.  You can shrink/grow the timestep to attempt to satisfy
  // either a global or local time-discretization error tolerance.

  Real shrink_or_growth_factor =
    this->global_tolerance ? global_shrink_or_growth_factor :
                             local_shrink_or_growth_factor;

  if (this->max_growth && this->max_growth < shrink_or_growth_factor)
    {
      if (!quiet && this->global_tolerance)
        {
	  libMesh::out << "delta t is constrained by max_growth" << std::endl;
	}
        shrink_or_growth_factor = this->max_growth;
    }

  _system.deltat *= shrink_or_growth_factor;

  // Restrict deltat to max-allowable value if necessary
  if ((this->max_deltat != 0.0) && (_system.deltat > this->max_deltat))
    {
      if (!quiet)
	{
	  libMesh::out << "delta t is constrained by maximum-allowable delta t."
                        << std::endl;
	}
      _system.deltat = this->max_deltat;
    }

  // Restrict deltat to min-allowable value if necessary
  if ((this->min_deltat != 0.0) && (_system.deltat < this->min_deltat))
    {
      if (!quiet)
	{
	  libMesh::out << "delta t is constrained by minimum-allowable delta t."
                        << std::endl;
	}
      _system.deltat = this->min_deltat;
    }

  if (!quiet)
    {
      libMesh::out << "new delta t = " << _system.deltat << std::endl;
    }
}

} // namespace libMesh
<|endoftext|>


<commit_before>#include "StorageRecovery.h"
#include "System/FileSystem.h"
#include "StorageEnvironment.h"
#include "FDGuard.h"
#include "PointerGuard.h"

bool StorageRecovery::TryRecovery(StorageEnvironment* env_)
{
    Buffer  toc, tocNew;

    env = env_;

    toc.Write(env->envPath);
    toc.Append("toc");
    toc.NullTerminate();
    tocNew.Write(env->envPath);
    tocNew.Append("toc.new");
    tocNew.NullTerminate();
    if (TryReadTOC(tocNew))
    {
        FS_Delete(toc.GetBuffer());
        FS_Rename(tocNew.GetBuffer(), toc.GetBuffer());
    }
    else
    {
        if (!TryReadTOC(toc))
            return false;

        FS_Delete(tocNew.GetBuffer());
    }

    CreateMemoChunks();

    ReadFileChunks();

    // compute the max. (logSegmentID, commandID) for each shard's chunk
    // log entries smaller must not be applied to its memo chunk
    ComputeShardRecovery();

    ReplayLogSegments();

    return true;
}

bool StorageRecovery::TryReadTOC(Buffer& filename)
{
    uint32_t    offset, size, rest, checksum, compChecksum;
    Buffer      buffer;
    ReadBuffer  parse, dataPart;
    FDGuard     fd;

    if (fd.Open(filename.GetBuffer(), FS_READONLY) == INVALID_FD)
        return false;

    offset = 0;
    size = 4;
    buffer.Allocate(size);
    if (FS_FileRead(fd.GetFD(), buffer.GetBuffer(), size) != size)
        return false;
    buffer.SetLength(size);

    // first 4 bytes on all pages is the page size
    parse.Wrap(buffer);
    if (!parse.ReadLittle32(size))
        return false;

    if (size == 0)
        return false;

    rest = size - buffer.GetLength();
    // read rest
    buffer.Allocate(size);
    if (FS_FileRead(fd.GetFD(), buffer.GetPosition(), rest) != rest)
        return false;
    buffer.SetLength(size);

    parse.Wrap(buffer);
    parse.Advance(4); // already read size
    parse.ReadLittle32(checksum);
    dataPart.Wrap(buffer.GetBuffer() + 8, buffer.GetLength() - 8);
    compChecksum = dataPart.GetChecksum();
    if (compChecksum != checksum)
        return false;
    parse.Advance(4);

    ReadShards(parse);

    fd.Close();

    return true;
}

bool StorageRecovery::ReadShards(ReadBuffer& parse)
{
    uint32_t    numShards, i;

    if (!parse.ReadLittle32(numShards))
        return false;
    parse.Advance(4);

    for (i = 0; i < numShards; i++)
    {
        if (!ReadShard(parse))
            return false;
    }

    return true;
}

bool StorageRecovery::ReadShard(ReadBuffer& parse)
{
    uint32_t            numChunks, i;
    uint64_t            chunkID;
    StorageShard*       shard;
    StorageFileChunk*   fileChunk;
    Buffer              tmp;

    PointerGuard<StorageShard> shardGuard(new StorageShard);

    shard = shardGuard.Get();

    if (!parse.ReadLittle16(shard->contextID))
        return false;
    parse.Advance(2);
    if (!parse.ReadLittle64(shard->tableID))
        return false;
    parse.Advance(8);
    if (!parse.ReadLittle64(shard->shardID))
        return false;
    parse.Advance(8);
    if (!parse.ReadLittle64(shard->logSegmentID))
        return false;
    parse.Advance(8);
    if (!parse.ReadLittle32(shard->logCommandID))
        return false;
    parse.Advance(4);
    parse.Advance(parse.Readf("%#B", &shard->firstKey));
    parse.Advance(parse.Readf("%#B", &shard->lastKey));
    parse.Advance(parse.Readf("%b", &shard->useBloomFilter));

    if (!parse.ReadLittle32(numChunks))
        return false;
    parse.Advance(4);

    for (i = 0; i < numChunks; i++)
    {
        if (!parse.ReadLittle64(chunkID))
            return false;
        parse.Advance(8);

        if (chunkID >= env->nextChunkID)
            env->nextChunkID = chunkID + 1;

        fileChunk = env->GetFileChunk(chunkID);

        if (fileChunk == NULL)
        {
            fileChunk = new StorageFileChunk;

            tmp.Write(env->envPath);
            tmp.Appendf("chunks/chunk.%020U", chunkID);
            fileChunk->SetFilename(tmp);
            fileChunk->written = true;

            env->fileChunks.Append(fileChunk);
        }

        shard->chunks.Add(fileChunk);
    }

    env->shards.Append(shardGuard.Release());

    return true;
}

void StorageRecovery::CreateMemoChunks()
{
    StorageShard*   it;

    FOREACH(it, env->shards)
    {
        it->memoChunk = new StorageMemoChunk;
        it->memoChunk->SetChunkID(env->nextChunkID++);
        it->memoChunk->SetUseBloomFilter(it->UseBloomFilter());
    }
}

void StorageRecovery::ReadFileChunks()
{
    StorageFileChunk* it;

    FOREACH(it, env->fileChunks)
        it->ReadHeaderPage();
}

void StorageRecovery::ComputeShardRecovery()
{
    StorageShard*       itShard;
    StorageChunk**      itChunk;
    StorageFileChunk*   fileChunk;

    FOREACH(itShard, env->shards)
    {
        FOREACH(itChunk, itShard->chunks)
        {
            fileChunk = (StorageFileChunk*) (*itChunk);

            if (fileChunk->GetLogSegmentID() > itShard->recoveryLogSegmentID)
            {
                itShard->recoveryLogSegmentID = fileChunk->GetLogSegmentID();
                itShard->recoveryLogCommandID = fileChunk->GetLogCommandID();
            }
            else if (fileChunk->GetLogSegmentID() == itShard->recoveryLogSegmentID)
            {
                if (fileChunk->GetLogCommandID() > itShard->GetLogCommandID())
                    itShard->recoveryLogCommandID = fileChunk->GetLogCommandID();
            }
        }
    }
}

void StorageRecovery::ReplayLogSegments()
{
    const char*         filename;
    Buffer              tmp;
    FS_Dir              dir;
    FS_DirEntry         entry;

    Log_Message("Replaying log segments...");

    tmp.Write(env->logPath);
    tmp.NullTerminate();

    dir = FS_OpenDir(tmp.GetBuffer());
    if (dir == FS_INVALID_DIR)
    {
        Log_Message("Unable to open log directory: %s", tmp.GetBuffer());
        Log_Message("Exiting...");
        ASSERT_FAIL();
    }

    while ((entry = FS_ReadDir(dir)) != FS_INVALID_DIR_ENTRY)
    {
        filename = FS_DirEntryName(entry);

        if (FS_IsSpecial(filename))
            continue;

        if (FS_IsDirectory(filename))
            continue;

        tmp.Write(filename);
        if (ReadBuffer(tmp).BeginsWith("log."))
        {
            tmp.Write(env->logPath);
            tmp.Append(filename);
            ReplayLogSegment(tmp);
        }
    }

    FS_CloseDir(dir);

    Log_Message("Done.");
}

bool StorageRecovery::ReplayLogSegment(Buffer& filename)
{
    // create a StorageLogSegment for each
    // and for each (logSegmentID, commandID) => (contextID, shardID)
    // look at that shard's computed max., if the log is bigger, then execute the command
    // against the MemoChunk

    bool                usePrevious;
    char                type;
    uint16_t            contextID, klen;
    uint32_t            size, rest, checksum, compChecksum, vlen;
    uint64_t            logSegmentID, shardID, logCommandID;
    ReadBuffer          parse, dataPart, key, value;
    Buffer              buffer;
    FDGuard             fd;
    StorageLogSegment*  logSegment;

    Log_Message("Replaying log segment %B...", &filename);

    filename.NullTerminate();

    if (fd.Open(filename.GetBuffer(), FS_READONLY) == INVALID_FD)
    {
        Log_Message("Unable to open log file: %s", filename.GetBuffer());
        Log_Message("Exiting...");
        ASSERT_FAIL();
    }

    size = 8;
    buffer.Allocate(size);
    if (FS_FileRead(fd.GetFD(), buffer.GetBuffer(), size) != size)
        return false;
    buffer.SetLength(size);

    // first 8 byte is the logSegmentID
    parse.Wrap(buffer);
    if (!parse.ReadLittle64(logSegmentID))
        return false;
    parse.Advance(8);

    logCommandID = 1;

    while (true)
    {
        size = 4;
        buffer.Allocate(size);
        if (FS_FileRead(fd.GetFD(), buffer.GetBuffer(), size) != size)
            break;
        buffer.SetLength(size);

        parse.Wrap(buffer);
        if (!parse.ReadLittle32(size))
            break;
        buffer.Allocate(size);

        rest = size - buffer.GetLength();
        if (rest < 4)
            break;
        if (FS_FileRead(fd.GetFD(), buffer.GetPosition(), rest) != rest)
            break;
        buffer.SetLength(size);

        parse.Wrap(buffer.GetBuffer() + 4, 4);
        if (!parse.ReadLittle32(checksum))
            break;
        dataPart.Wrap(buffer.GetBuffer() + 8, buffer.GetLength() - 8);
        compChecksum = dataPart.GetChecksum();
        if (checksum != compChecksum)
            break;

        parse.Wrap(buffer.GetBuffer() + 8, buffer.GetLength() - 8);
        while (parse.GetLength() > 0)
        {
            if (parse.GetLength() < 1)
                break;
            parse.ReadChar(type);
            parse.Advance(1);

            if (parse.GetLength() < 1)
                break;
            parse.Readf("%b", &usePrevious);
            parse.Advance(1);

            if (!usePrevious)
            {
                if (parse.GetLength() < 2)
                    break;
                parse.ReadLittle16(contextID);
                parse.Advance(2);

                if (parse.GetLength() < 8)
                    break;
                parse.ReadLittle64(shardID);
                parse.Advance(8);
            }

            if (parse.GetLength() < 2)
                break;
            if (!parse.ReadLittle16(klen))
                break;
            parse.Advance(2);

            if (parse.GetLength() < klen)
                break;
            key.Wrap(parse.GetBuffer(), klen);
            parse.Advance(klen);

            if (type == STORAGE_LOGSEGMENT_COMMAND_SET)
            {
                if (parse.GetLength() < 4)
                    break;
                if (!parse.ReadLittle32(vlen))
                    break;
                parse.Advance(4);

                if (parse.GetLength() < vlen)
                    break;
                value.Wrap(parse.GetBuffer(), vlen);
                parse.Advance(vlen);
            }

            if (type == STORAGE_LOGSEGMENT_COMMAND_SET)
                ExecuteSet(logSegmentID, logCommandID, contextID, shardID, key, value);
            else if (type == STORAGE_LOGSEGMENT_COMMAND_DELETE)
                ExecuteDelete(logSegmentID, logCommandID, contextID, shardID, key);
            else
                ASSERT_FAIL();

            logCommandID++;
        }
    }

    logSegment = new StorageLogSegment;
    logSegment->logSegmentID = logSegmentID;
    logSegment->filename.Write(filename);
    env->logSegments.Append(logSegment);
    if (env->nextLogSegmentID <= logSegmentID)
        env->nextLogSegmentID = logSegmentID + 1;

    fd.Close();

    return true;
}

void StorageRecovery::ExecuteSet(
                         uint64_t logSegmentID, uint32_t logCommandID,
                         uint16_t contextID, uint64_t shardID,
                         ReadBuffer& key, ReadBuffer& value)
{
    StorageShard*       shard;
    StorageMemoChunk*   memoChunk;

    shard  = env->GetShard(contextID, shardID);
    if (shard == NULL)
        return; // shard was deleted

    if (shard->logSegmentID > logSegmentID)
        return; // shard was deleted and re-created

    if (shard->logSegmentID == logSegmentID && shard->logCommandID > logCommandID)
        return; // shard was deleted and re-created

    if (shard->recoveryLogSegmentID > logSegmentID)
        return; // this command is already present in a file chunk

    if (shard->recoveryLogSegmentID == logSegmentID && shard->recoveryLogCommandID > logCommandID)
        return; // this command is already present in a file chunk

    memoChunk = shard->GetMemoChunk();
    assert(memoChunk != NULL);
    if (!memoChunk->Set(key, value))
        ASSERT_FAIL();

    Log_Message("%U, %U, %R, %R", contextID, shardID, &key, &value);

    memoChunk->RegisterLogCommand(logSegmentID, logCommandID);
}

void StorageRecovery::ExecuteDelete(
                         uint64_t logSegmentID, uint32_t logCommandID,
                         uint16_t contextID, uint64_t shardID,
                         ReadBuffer& key)
{
    StorageShard*       shard;
    StorageMemoChunk*   memoChunk;

    shard  = env->GetShard(contextID, shardID);
    if (shard == NULL)
        return; // shard was deleted

    if (shard->logSegmentID > logSegmentID)
        return; // shard was deleted and re-created

    if (shard->logSegmentID == logSegmentID && shard->logCommandID > logCommandID)
        return; // shard was deleted and re-created

    if (shard->recoveryLogSegmentID > logSegmentID)
        return; // this command is already present in a file chunk

    if (shard->recoveryLogSegmentID == logSegmentID && shard->recoveryLogCommandID > logCommandID)
        return; // this command is already present in a file chunk

    memoChunk = shard->GetMemoChunk();
    assert(memoChunk != NULL);
    if (!memoChunk->Delete(key))
        ASSERT_FAIL();

    memoChunk->RegisterLogCommand(logSegmentID, logCommandID);
}
<commit_msg>Removed debug log output.<commit_after>#include "StorageRecovery.h"
#include "System/FileSystem.h"
#include "StorageEnvironment.h"
#include "FDGuard.h"
#include "PointerGuard.h"

bool StorageRecovery::TryRecovery(StorageEnvironment* env_)
{
    Buffer  toc, tocNew;

    env = env_;

    toc.Write(env->envPath);
    toc.Append("toc");
    toc.NullTerminate();
    tocNew.Write(env->envPath);
    tocNew.Append("toc.new");
    tocNew.NullTerminate();
    if (TryReadTOC(tocNew))
    {
        FS_Delete(toc.GetBuffer());
        FS_Rename(tocNew.GetBuffer(), toc.GetBuffer());
    }
    else
    {
        if (!TryReadTOC(toc))
            return false;

        FS_Delete(tocNew.GetBuffer());
    }

    CreateMemoChunks();

    ReadFileChunks();

    // compute the max. (logSegmentID, commandID) for each shard's chunk
    // log entries smaller must not be applied to its memo chunk
    ComputeShardRecovery();

    ReplayLogSegments();

    return true;
}

bool StorageRecovery::TryReadTOC(Buffer& filename)
{
    uint32_t    offset, size, rest, checksum, compChecksum;
    Buffer      buffer;
    ReadBuffer  parse, dataPart;
    FDGuard     fd;

    if (fd.Open(filename.GetBuffer(), FS_READONLY) == INVALID_FD)
        return false;

    offset = 0;
    size = 4;
    buffer.Allocate(size);
    if (FS_FileRead(fd.GetFD(), buffer.GetBuffer(), size) != size)
        return false;
    buffer.SetLength(size);

    // first 4 bytes on all pages is the page size
    parse.Wrap(buffer);
    if (!parse.ReadLittle32(size))
        return false;

    if (size == 0)
        return false;

    rest = size - buffer.GetLength();
    // read rest
    buffer.Allocate(size);
    if (FS_FileRead(fd.GetFD(), buffer.GetPosition(), rest) != rest)
        return false;
    buffer.SetLength(size);

    parse.Wrap(buffer);
    parse.Advance(4); // already read size
    parse.ReadLittle32(checksum);
    dataPart.Wrap(buffer.GetBuffer() + 8, buffer.GetLength() - 8);
    compChecksum = dataPart.GetChecksum();
    if (compChecksum != checksum)
        return false;
    parse.Advance(4);

    ReadShards(parse);

    fd.Close();

    return true;
}

bool StorageRecovery::ReadShards(ReadBuffer& parse)
{
    uint32_t    numShards, i;

    if (!parse.ReadLittle32(numShards))
        return false;
    parse.Advance(4);

    for (i = 0; i < numShards; i++)
    {
        if (!ReadShard(parse))
            return false;
    }

    return true;
}

bool StorageRecovery::ReadShard(ReadBuffer& parse)
{
    uint32_t            numChunks, i;
    uint64_t            chunkID;
    StorageShard*       shard;
    StorageFileChunk*   fileChunk;
    Buffer              tmp;

    PointerGuard<StorageShard> shardGuard(new StorageShard);

    shard = shardGuard.Get();

    if (!parse.ReadLittle16(shard->contextID))
        return false;
    parse.Advance(2);
    if (!parse.ReadLittle64(shard->tableID))
        return false;
    parse.Advance(8);
    if (!parse.ReadLittle64(shard->shardID))
        return false;
    parse.Advance(8);
    if (!parse.ReadLittle64(shard->logSegmentID))
        return false;
    parse.Advance(8);
    if (!parse.ReadLittle32(shard->logCommandID))
        return false;
    parse.Advance(4);
    parse.Advance(parse.Readf("%#B", &shard->firstKey));
    parse.Advance(parse.Readf("%#B", &shard->lastKey));
    parse.Advance(parse.Readf("%b", &shard->useBloomFilter));

    if (!parse.ReadLittle32(numChunks))
        return false;
    parse.Advance(4);

    for (i = 0; i < numChunks; i++)
    {
        if (!parse.ReadLittle64(chunkID))
            return false;
        parse.Advance(8);

        if (chunkID >= env->nextChunkID)
            env->nextChunkID = chunkID + 1;

        fileChunk = env->GetFileChunk(chunkID);

        if (fileChunk == NULL)
        {
            fileChunk = new StorageFileChunk;

            tmp.Write(env->envPath);
            tmp.Appendf("chunks/chunk.%020U", chunkID);
            fileChunk->SetFilename(tmp);
            fileChunk->written = true;

            env->fileChunks.Append(fileChunk);
        }

        shard->chunks.Add(fileChunk);
    }

    env->shards.Append(shardGuard.Release());

    return true;
}

void StorageRecovery::CreateMemoChunks()
{
    StorageShard*   it;

    FOREACH(it, env->shards)
    {
        it->memoChunk = new StorageMemoChunk;
        it->memoChunk->SetChunkID(env->nextChunkID++);
        it->memoChunk->SetUseBloomFilter(it->UseBloomFilter());
    }
}

void StorageRecovery::ReadFileChunks()
{
    StorageFileChunk* it;

    FOREACH(it, env->fileChunks)
        it->ReadHeaderPage();
}

void StorageRecovery::ComputeShardRecovery()
{
    StorageShard*       itShard;
    StorageChunk**      itChunk;
    StorageFileChunk*   fileChunk;

    FOREACH(itShard, env->shards)
    {
        FOREACH(itChunk, itShard->chunks)
        {
            fileChunk = (StorageFileChunk*) (*itChunk);

            if (fileChunk->GetLogSegmentID() > itShard->recoveryLogSegmentID)
            {
                itShard->recoveryLogSegmentID = fileChunk->GetLogSegmentID();
                itShard->recoveryLogCommandID = fileChunk->GetLogCommandID();
            }
            else if (fileChunk->GetLogSegmentID() == itShard->recoveryLogSegmentID)
            {
                if (fileChunk->GetLogCommandID() > itShard->GetLogCommandID())
                    itShard->recoveryLogCommandID = fileChunk->GetLogCommandID();
            }
        }
    }
}

void StorageRecovery::ReplayLogSegments()
{
    const char*         filename;
    Buffer              tmp;
    FS_Dir              dir;
    FS_DirEntry         entry;

    Log_Message("Replaying log segments...");

    tmp.Write(env->logPath);
    tmp.NullTerminate();

    dir = FS_OpenDir(tmp.GetBuffer());
    if (dir == FS_INVALID_DIR)
    {
        Log_Message("Unable to open log directory: %s", tmp.GetBuffer());
        Log_Message("Exiting...");
        ASSERT_FAIL();
    }

    while ((entry = FS_ReadDir(dir)) != FS_INVALID_DIR_ENTRY)
    {
        filename = FS_DirEntryName(entry);

        if (FS_IsSpecial(filename))
            continue;

        if (FS_IsDirectory(filename))
            continue;

        tmp.Write(filename);
        if (ReadBuffer(tmp).BeginsWith("log."))
        {
            tmp.Write(env->logPath);
            tmp.Append(filename);
            ReplayLogSegment(tmp);
        }
    }

    FS_CloseDir(dir);

    Log_Message("Done.");
}

bool StorageRecovery::ReplayLogSegment(Buffer& filename)
{
    // create a StorageLogSegment for each
    // and for each (logSegmentID, commandID) => (contextID, shardID)
    // look at that shard's computed max., if the log is bigger, then execute the command
    // against the MemoChunk

    bool                usePrevious;
    char                type;
    uint16_t            contextID, klen;
    uint32_t            size, rest, checksum, compChecksum, vlen;
    uint64_t            logSegmentID, shardID, logCommandID;
    ReadBuffer          parse, dataPart, key, value;
    Buffer              buffer;
    FDGuard             fd;
    StorageLogSegment*  logSegment;

    Log_Message("Replaying log segment %B...", &filename);

    filename.NullTerminate();

    if (fd.Open(filename.GetBuffer(), FS_READONLY) == INVALID_FD)
    {
        Log_Message("Unable to open log file: %s", filename.GetBuffer());
        Log_Message("Exiting...");
        ASSERT_FAIL();
    }

    size = 8;
    buffer.Allocate(size);
    if (FS_FileRead(fd.GetFD(), buffer.GetBuffer(), size) != size)
        return false;
    buffer.SetLength(size);

    // first 8 byte is the logSegmentID
    parse.Wrap(buffer);
    if (!parse.ReadLittle64(logSegmentID))
        return false;
    parse.Advance(8);

    logCommandID = 1;

    while (true)
    {
        size = 4;
        buffer.Allocate(size);
        if (FS_FileRead(fd.GetFD(), buffer.GetBuffer(), size) != size)
            break;
        buffer.SetLength(size);

        parse.Wrap(buffer);
        if (!parse.ReadLittle32(size))
            break;
        buffer.Allocate(size);

        rest = size - buffer.GetLength();
        if (rest < 4)
            break;
        if (FS_FileRead(fd.GetFD(), buffer.GetPosition(), rest) != rest)
            break;
        buffer.SetLength(size);

        parse.Wrap(buffer.GetBuffer() + 4, 4);
        if (!parse.ReadLittle32(checksum))
            break;
        dataPart.Wrap(buffer.GetBuffer() + 8, buffer.GetLength() - 8);
        compChecksum = dataPart.GetChecksum();
        if (checksum != compChecksum)
            break;

        parse.Wrap(buffer.GetBuffer() + 8, buffer.GetLength() - 8);
        while (parse.GetLength() > 0)
        {
            if (parse.GetLength() < 1)
                break;
            parse.ReadChar(type);
            parse.Advance(1);

            if (parse.GetLength() < 1)
                break;
            parse.Readf("%b", &usePrevious);
            parse.Advance(1);

            if (!usePrevious)
            {
                if (parse.GetLength() < 2)
                    break;
                parse.ReadLittle16(contextID);
                parse.Advance(2);

                if (parse.GetLength() < 8)
                    break;
                parse.ReadLittle64(shardID);
                parse.Advance(8);
            }

            if (parse.GetLength() < 2)
                break;
            if (!parse.ReadLittle16(klen))
                break;
            parse.Advance(2);

            if (parse.GetLength() < klen)
                break;
            key.Wrap(parse.GetBuffer(), klen);
            parse.Advance(klen);

            if (type == STORAGE_LOGSEGMENT_COMMAND_SET)
            {
                if (parse.GetLength() < 4)
                    break;
                if (!parse.ReadLittle32(vlen))
                    break;
                parse.Advance(4);

                if (parse.GetLength() < vlen)
                    break;
                value.Wrap(parse.GetBuffer(), vlen);
                parse.Advance(vlen);
            }

            if (type == STORAGE_LOGSEGMENT_COMMAND_SET)
                ExecuteSet(logSegmentID, logCommandID, contextID, shardID, key, value);
            else if (type == STORAGE_LOGSEGMENT_COMMAND_DELETE)
                ExecuteDelete(logSegmentID, logCommandID, contextID, shardID, key);
            else
                ASSERT_FAIL();

            logCommandID++;
        }
    }

    logSegment = new StorageLogSegment;
    logSegment->logSegmentID = logSegmentID;
    logSegment->filename.Write(filename);
    env->logSegments.Append(logSegment);
    if (env->nextLogSegmentID <= logSegmentID)
        env->nextLogSegmentID = logSegmentID + 1;

    fd.Close();

    return true;
}

void StorageRecovery::ExecuteSet(
                         uint64_t logSegmentID, uint32_t logCommandID,
                         uint16_t contextID, uint64_t shardID,
                         ReadBuffer& key, ReadBuffer& value)
{
    StorageShard*       shard;
    StorageMemoChunk*   memoChunk;

    shard  = env->GetShard(contextID, shardID);
    if (shard == NULL)
        return; // shard was deleted

    if (shard->logSegmentID > logSegmentID)
        return; // shard was deleted and re-created

    if (shard->logSegmentID == logSegmentID && shard->logCommandID > logCommandID)
        return; // shard was deleted and re-created

    if (shard->recoveryLogSegmentID > logSegmentID)
        return; // this command is already present in a file chunk

    if (shard->recoveryLogSegmentID == logSegmentID && shard->recoveryLogCommandID > logCommandID)
        return; // this command is already present in a file chunk

    memoChunk = shard->GetMemoChunk();
    assert(memoChunk != NULL);
    if (!memoChunk->Set(key, value))
        ASSERT_FAIL();

    memoChunk->RegisterLogCommand(logSegmentID, logCommandID);
}

void StorageRecovery::ExecuteDelete(
                         uint64_t logSegmentID, uint32_t logCommandID,
                         uint16_t contextID, uint64_t shardID,
                         ReadBuffer& key)
{
    StorageShard*       shard;
    StorageMemoChunk*   memoChunk;

    shard  = env->GetShard(contextID, shardID);
    if (shard == NULL)
        return; // shard was deleted

    if (shard->logSegmentID > logSegmentID)
        return; // shard was deleted and re-created

    if (shard->logSegmentID == logSegmentID && shard->logCommandID > logCommandID)
        return; // shard was deleted and re-created

    if (shard->recoveryLogSegmentID > logSegmentID)
        return; // this command is already present in a file chunk

    if (shard->recoveryLogSegmentID == logSegmentID && shard->recoveryLogCommandID > logCommandID)
        return; // this command is already present in a file chunk

    memoChunk = shard->GetMemoChunk();
    assert(memoChunk != NULL);
    if (!memoChunk->Delete(key))
        ASSERT_FAIL();

    memoChunk->RegisterLogCommand(logSegmentID, logCommandID);
}
<|endoftext|>


<commit_before>#ifndef SASS_UTF8_STRING
#define SASS_UTF8_STRING

#include <string>

namespace Sass {
  namespace UTF_8 {
    using std::string;
    // class utf8_string {
    //   string s_;
    // public:
    //   utf8_string(const string &s): s_(s) {}
    //   utf8_string(const char* c): s_(string(c)) {}

    //   char operator[](size_t i);
    //   size_t length();
    //   size_t byte_to_char(size_t i);
    // };

    // function that will count the number of code points (utf-8 characters) from the given beginning to the given end
    size_t code_point_count(const string& str, size_t start, size_t end) {
      size_t len = 0;
      size_t i = start;

      while (i < end) {
        unsigned char c = static_cast<unsigned char>(str[i]);
        if (c < 128) {
          // it's a single-byte character
          ++len;
          ++i;
        }
        // it's a multi bit sequence and presumably it's a leading bit
        else {
          ++i; // go to the next byte
          // see if it's still part of the sequence
          while ((i < end) && ((static_cast<unsigned char>(str[i]) & 0b11000000) == 0b10000000)) {
            ++i;
          }
          // when it's not [aka a new leading bit], increment and move on
          ++len;
        }
      }
      return len;
    }

    size_t code_point_count(const string& str) {
      return code_point_count(str, 0, str.length());
    }

    // function that will return the byte offset of a code point in a
    size_t code_point_offset_to_byte_offset(const string& str, size_t offset) {
      size_t i = 0;
      size_t len = 0;

      while (len < offset) {
        unsigned char c = static_cast<unsigned char>(str[i]);
        if (c < 128) {
          // it's a single-byte character
          ++len;
          ++i;
        }
        // it's a multi bit sequence and presumably it's a leading bit
        else {
          ++i; // go to the next byte
          // see if it's still part of the sequence
          while ((i < str.length()) && ((static_cast<unsigned char>(str[i]) & 0b11000000) == 0b10000000)) {
            ++i;
          }
          // when it's not [aka a new leading bit], increment and move on
          ++len;
        }
      }
      return i;
    }

    // function that returns number of bytes in a character in a string
    size_t length_of_code_point_at(const string& str, size_t pos) {
      unsigned char c = static_cast<unsigned char>(str[pos]);
      size_t i = 0;
      if(c < 128) {
        return 1;
      } else {
        ++i; // go to the next byte
        ++pos;
        // see if it's still part of the sequence
        while ((i < str.length()) && ((static_cast<unsigned char>(str[pos]) & 0b11000000) == 0b10000000)) {
          ++i;
          ++pos;
        }
      }
      return i;
    }

    // function that will return a normalized index, given a crazy one
    size_t normalize_index(int index, size_t len) {
      int signed_len = len;
      // assuming the index is 1-based
      // we are returning a 0-based index
      if (index > 0 && index <= signed_len) {
        // positive and within string length
        return index-1;
      }
      else if (index > signed_len) {
        // positive and past string length
        return len;
      }
      else if (index == 0) {
        return 0;
      }
      else if (std::abs(index) <= signed_len) {
        // negative and within string length
        return index + signed_len;
      }
      else {
        // negative and past string length
        return 0;
      }
    }

  }
}

#endif <commit_msg>including a necessary header file<commit_after>#ifndef SASS_UTF8_STRING
#define SASS_UTF8_STRING

#include <string>
#include <cmath>

namespace Sass {
  namespace UTF_8 {
    using std::string;
    // class utf8_string {
    //   string s_;
    // public:
    //   utf8_string(const string &s): s_(s) {}
    //   utf8_string(const char* c): s_(string(c)) {}

    //   char operator[](size_t i);
    //   size_t length();
    //   size_t byte_to_char(size_t i);
    // };

    // function that will count the number of code points (utf-8 characters) from the given beginning to the given end
    size_t code_point_count(const string& str, size_t start, size_t end) {
      size_t len = 0;
      size_t i = start;

      while (i < end) {
        unsigned char c = static_cast<unsigned char>(str[i]);
        if (c < 128) {
          // it's a single-byte character
          ++len;
          ++i;
        }
        // it's a multi bit sequence and presumably it's a leading bit
        else {
          ++i; // go to the next byte
          // see if it's still part of the sequence
          while ((i < end) && ((static_cast<unsigned char>(str[i]) & 0b11000000) == 0b10000000)) {
            ++i;
          }
          // when it's not [aka a new leading bit], increment and move on
          ++len;
        }
      }
      return len;
    }

    size_t code_point_count(const string& str) {
      return code_point_count(str, 0, str.length());
    }

    // function that will return the byte offset of a code point in a
    size_t code_point_offset_to_byte_offset(const string& str, size_t offset) {
      size_t i = 0;
      size_t len = 0;

      while (len < offset) {
        unsigned char c = static_cast<unsigned char>(str[i]);
        if (c < 128) {
          // it's a single-byte character
          ++len;
          ++i;
        }
        // it's a multi bit sequence and presumably it's a leading bit
        else {
          ++i; // go to the next byte
          // see if it's still part of the sequence
          while ((i < str.length()) && ((static_cast<unsigned char>(str[i]) & 0b11000000) == 0b10000000)) {
            ++i;
          }
          // when it's not [aka a new leading bit], increment and move on
          ++len;
        }
      }
      return i;
    }

    // function that returns number of bytes in a character in a string
    size_t length_of_code_point_at(const string& str, size_t pos) {
      unsigned char c = static_cast<unsigned char>(str[pos]);
      size_t i = 0;
      if(c < 128) {
        return 1;
      } else {
        ++i; // go to the next byte
        ++pos;
        // see if it's still part of the sequence
        while ((i < str.length()) && ((static_cast<unsigned char>(str[pos]) & 0b11000000) == 0b10000000)) {
          ++i;
          ++pos;
        }
      }
      return i;
    }

    // function that will return a normalized index, given a crazy one
    size_t normalize_index(int index, size_t len) {
      int signed_len = len;
      // assuming the index is 1-based
      // we are returning a 0-based index
      if (index > 0 && index <= signed_len) {
        // positive and within string length
        return index-1;
      }
      else if (index > signed_len) {
        // positive and past string length
        return len;
      }
      else if (index == 0) {
        return 0;
      }
      else if (std::abs(index) <= signed_len) {
        // negative and within string length
        return index + signed_len;
      }
      else {
        // negative and past string length
        return 0;
      }
    }

  }
}

#endif <|endoftext|>