<commit_before>// Copyright (c) 2013, Matthew Harvey. All rights reserved.

#include "reconciliation_entry_list_ctrl.hpp"
#include "entry.hpp"
#include "filtered_entry_list_ctrl.hpp"
#include "finformat.hpp"
#include "locale.hpp"
#include "ordinary_journal.hpp"
#include "reconciliation_status_marker.hpp"
#include "summary_datum.hpp"
#include <boost/date_time/gregorian/gregorian.hpp>
#include <boost/optional.hpp>
#include <jewel/decimal.hpp>
#include <jewel/optional.hpp>
#include <wx/listctrl.h>
#include <vector>

using boost::optional;
using jewel::Decimal;
using jewel::value;
using std::vector;

// For debugging
	#include <jewel/debug_log.hpp>
	#include <iostream>
	using std::endl;

namespace gregorian = boost::gregorian;

namespace phatbooks
{
namespace gui
{

// TODO We can use "EVT_LIST_ITEM_COL_CLICK" to activate this
// on left click of the "Reconciled" column - which is more intuitive.
BEGIN_EVENT_TABLE(ReconciliationEntryListCtrl, FilteredEntryListCtrl)
	EVT_LIST_ITEM_RIGHT_CLICK
	(	wxID_ANY,
		ReconciliationEntryListCtrl::on_item_right_click
	)
END_EVENT_TABLE()

namespace
{
	int comment_col_num()
	{
		return 1;
	}
	int amount_col_num()
	{
		return 2;
	}
	int reconciled_col_num()
	{
		return 3;
	}
	int anon_num_columns()
	{
		return 4;
	}

}  // end anonymous namespace

ReconciliationEntryListCtrl::ReconciliationEntryListCtrl
(	ReconciliationListPanel* p_parent,
	wxSize const& p_size,
	Account const& p_account,
	gregorian::date const& p_min_date,
	gregorian::date const& p_max_date
):
	FilteredEntryListCtrl
	(	p_parent,
		p_size,
		p_account,
		optional<gregorian::date>(p_min_date),
		optional<gregorian::date>(p_max_date)
	),
	m_max_date(p_max_date),
	m_summary_data(0),
	m_closing_balance(0, p_account.commodity().precision()),
	m_reconciled_closing_balance(0, p_account.commodity().precision())
{
}

ReconciliationEntryListCtrl::~ReconciliationEntryListCtrl()
{
	delete m_summary_data;
	m_summary_data = 0;
}

void
ReconciliationEntryListCtrl::do_set_non_date_columns
(	long p_row,
	Entry const& p_entry
)
{
	SetItem
	(	p_row,
		comment_col_num(),
		bstring_to_wx(p_entry.comment())
	);
	SetItem
	(	p_row,
		amount_col_num(),
		finformat_wx(p_entry.amount(), locale(), false)
	);
	SetItem
	(	p_row,
		reconciled_col_num(),
		reconciliation_status_marker(p_entry.is_reconciled())
	);
	assert (num_columns() == 4);
	return;
}

void
ReconciliationEntryListCtrl::do_insert_non_date_columns()
{
	InsertColumn
	(	comment_col_num(),
		wxString("Memo"),
		wxLIST_FORMAT_LEFT
	);
	InsertColumn
	(	amount_col_num(),
		wxString("Amount"),
		wxLIST_FORMAT_RIGHT
	);
	InsertColumn
	(	reconciled_col_num(),
		wxString("Reconciled?"),
		wxLIST_FORMAT_RIGHT
	);
	assert (num_columns() == 4);
	return;
}

bool
ReconciliationEntryListCtrl::do_approve_entry(Entry const& p_entry) const
{
	if (p_entry.account() != account())
	{
		return false;
	}
	gregorian::date const date = p_entry.date();
	if (date > max_date())
	{
		return false;
	}
	if (date < min_date())
	{
		// We include unreconciled Entries even if they're prior to the
		// min_date(), providing they're not later than max_date().
		assert (date <= max_date());
		assert
		(	(date > database_connection().opening_balance_journal_date()) ||
			p_entry.is_reconciled()
		);
		return !p_entry.is_reconciled();
	}
	assert (p_entry.account() == account());
	assert (date >= min_date());
	assert (date <= max_date());
	return true;
}

int
ReconciliationEntryListCtrl::do_get_comment_col_num() const
{
	return comment_col_num();
}

int
ReconciliationEntryListCtrl::do_get_num_columns() const
{
	return anon_num_columns();
}

vector<SummaryDatum> const&
ReconciliationEntryListCtrl::do_get_summary_data() const
{
	assert (!m_summary_data->empty());
	m_summary_data->at(0).set_amount(m_closing_balance);
	m_summary_data->at(1).set_amount(m_reconciled_closing_balance);
	return *m_summary_data;
}

void
ReconciliationEntryListCtrl::do_initialize_summary_data()
{
	m_closing_balance = Decimal(0, account().commodity().precision());
	m_reconciled_closing_balance =
		Decimal(0, account().commodity().precision());
	assert (!m_summary_data);
	m_summary_data = new std::vector<SummaryDatum>;
	SummaryDatum a
	(	wxString("Closing balance"),
		m_closing_balance
	);
	m_summary_data->push_back(a);
	SummaryDatum b
	(	wxString("Reconciled balance"),
		m_reconciled_closing_balance
	);
	m_summary_data->push_back(b);
	return;
}

void
ReconciliationEntryListCtrl::do_process_candidate_entry_for_summary
(	Entry const& p_entry
)
{
	if (p_entry.account() != account())
	{
		return;
	}
	assert (p_entry.account() == account());
	jewel::Decimal const amount = p_entry.amount();
	if (p_entry.date() <= max_date())
	{
		m_closing_balance += amount;
		if (p_entry.is_reconciled()) m_reconciled_closing_balance += amount;
	}
	return;
}

void
ReconciliationEntryListCtrl::do_process_removal_for_summary(long p_row)
{
	Decimal const amount = amount_for_row(p_row);
	m_closing_balance -= amount;
#	ifndef NDEBUG
		wxListItem item;
		item.SetId(p_row);
		item.SetColumn(reconciled_col_num());
		GetItem(item);
		// TODO HIGH PRIORITY This assertion can fail!
		assert (item.GetText() == reconciliation_status_marker(false));
		// as we don't allow user to delete reconciled Entries.
		// so we don't need to adjust m_reconciled_closing_balance here.
#	endif
	return;
}

void
ReconciliationEntryListCtrl::on_item_right_click(wxListEvent& event)
{
	int const col = reconciled_col_num();
	Entry::Id const entry_id = event.GetData();
	long const pos = event.GetIndex();
	assert (FindItem(-1, entry_id) == pos);
	assert (entry_id >= 0);
	assert (GetItemData(pos) == static_cast<size_t>(entry_id));

	Entry entry(database_connection(), entry_id);
	bool const old_reconciliation_status = entry.is_reconciled();
	entry.set_whether_reconciled(!old_reconciliation_status);
	SetItem(pos, col, reconciliation_status_marker(entry.is_reconciled()));
	if (entry.is_reconciled())
	{
		m_reconciled_closing_balance += entry.amount();
	}
	else
	{
		m_reconciled_closing_balance -= entry.amount();
	}
	entry.save();
	ReconciliationListPanel* parent =
		dynamic_cast<ReconciliationListPanel*>(GetParent());
	assert (parent);
	parent->postconfigure_summary();
	return;
}

Decimal
ReconciliationEntryListCtrl::amount_for_row(long p_row) const
{
	wxListItem item;
	item.SetId(p_row);
	item.SetColumn(amount_col_num());
	GetItem(item);
	Decimal ret = wx_to_decimal(item.GetText(), locale());
	return ret;
}

boost::gregorian::date
ReconciliationEntryListCtrl::max_date() const
{
	return m_max_date;
}

}  // namespace gui
}  // namespace phatbooks
<commit_msg>Fixed ReconciliationEntryListCtrl::do_process_removal_for_summary(long) to account for the fact that on occasion reconciled Entries are in fact removed from the displayed list (albeit only to be immediately reinserted), viz. during the process of responding to a "journal edited" event.<commit_after>// Copyright (c) 2013, Matthew Harvey. All rights reserved.

#include "reconciliation_entry_list_ctrl.hpp"
#include "entry.hpp"
#include "filtered_entry_list_ctrl.hpp"
#include "finformat.hpp"
#include "locale.hpp"
#include "ordinary_journal.hpp"
#include "reconciliation_status_marker.hpp"
#include "summary_datum.hpp"
#include <boost/date_time/gregorian/gregorian.hpp>
#include <boost/optional.hpp>
#include <jewel/decimal.hpp>
#include <jewel/optional.hpp>
#include <wx/listctrl.h>
#include <vector>

using boost::optional;
using jewel::Decimal;
using jewel::value;
using std::vector;

// For debugging
	#include <jewel/debug_log.hpp>
	#include <iostream>
	using std::endl;

namespace gregorian = boost::gregorian;

namespace phatbooks
{
namespace gui
{

// TODO We can use "EVT_LIST_ITEM_COL_CLICK" to activate this
// on left click of the "Reconciled" column - which is more intuitive.
BEGIN_EVENT_TABLE(ReconciliationEntryListCtrl, FilteredEntryListCtrl)
	EVT_LIST_ITEM_RIGHT_CLICK
	(	wxID_ANY,
		ReconciliationEntryListCtrl::on_item_right_click
	)
END_EVENT_TABLE()

namespace
{
	int comment_col_num()
	{
		return 1;
	}
	int amount_col_num()
	{
		return 2;
	}
	int reconciled_col_num()
	{
		return 3;
	}
	int anon_num_columns()
	{
		return 4;
	}

}  // end anonymous namespace

ReconciliationEntryListCtrl::ReconciliationEntryListCtrl
(	ReconciliationListPanel* p_parent,
	wxSize const& p_size,
	Account const& p_account,
	gregorian::date const& p_min_date,
	gregorian::date const& p_max_date
):
	FilteredEntryListCtrl
	(	p_parent,
		p_size,
		p_account,
		optional<gregorian::date>(p_min_date),
		optional<gregorian::date>(p_max_date)
	),
	m_max_date(p_max_date),
	m_summary_data(0),
	m_closing_balance(0, p_account.commodity().precision()),
	m_reconciled_closing_balance(0, p_account.commodity().precision())
{
}

ReconciliationEntryListCtrl::~ReconciliationEntryListCtrl()
{
	delete m_summary_data;
	m_summary_data = 0;
}

void
ReconciliationEntryListCtrl::do_set_non_date_columns
(	long p_row,
	Entry const& p_entry
)
{
	SetItem
	(	p_row,
		comment_col_num(),
		bstring_to_wx(p_entry.comment())
	);
	SetItem
	(	p_row,
		amount_col_num(),
		finformat_wx(p_entry.amount(), locale(), false)
	);
	SetItem
	(	p_row,
		reconciled_col_num(),
		reconciliation_status_marker(p_entry.is_reconciled())
	);
	assert (num_columns() == 4);
	return;
}

void
ReconciliationEntryListCtrl::do_insert_non_date_columns()
{
	InsertColumn
	(	comment_col_num(),
		wxString("Memo"),
		wxLIST_FORMAT_LEFT
	);
	InsertColumn
	(	amount_col_num(),
		wxString("Amount"),
		wxLIST_FORMAT_RIGHT
	);
	InsertColumn
	(	reconciled_col_num(),
		wxString("Reconciled?"),
		wxLIST_FORMAT_RIGHT
	);
	assert (num_columns() == 4);
	return;
}

bool
ReconciliationEntryListCtrl::do_approve_entry(Entry const& p_entry) const
{
	if (p_entry.account() != account())
	{
		return false;
	}
	gregorian::date const date = p_entry.date();
	if (date > max_date())
	{
		return false;
	}
	if (date < min_date())
	{
		// We include unreconciled Entries even if they're prior to the
		// min_date(), providing they're not later than max_date().
		assert (date <= max_date());
		assert
		(	(date > database_connection().opening_balance_journal_date()) ||
			p_entry.is_reconciled()
		);
		return !p_entry.is_reconciled();
	}
	assert (p_entry.account() == account());
	assert (date >= min_date());
	assert (date <= max_date());
	return true;
}

int
ReconciliationEntryListCtrl::do_get_comment_col_num() const
{
	return comment_col_num();
}

int
ReconciliationEntryListCtrl::do_get_num_columns() const
{
	return anon_num_columns();
}

vector<SummaryDatum> const&
ReconciliationEntryListCtrl::do_get_summary_data() const
{
	assert (!m_summary_data->empty());
	m_summary_data->at(0).set_amount(m_closing_balance);
	m_summary_data->at(1).set_amount(m_reconciled_closing_balance);
	return *m_summary_data;
}

void
ReconciliationEntryListCtrl::do_initialize_summary_data()
{
	m_closing_balance = Decimal(0, account().commodity().precision());
	m_reconciled_closing_balance =
		Decimal(0, account().commodity().precision());
	assert (!m_summary_data);
	m_summary_data = new std::vector<SummaryDatum>;
	SummaryDatum a
	(	wxString("Closing balance"),
		m_closing_balance
	);
	m_summary_data->push_back(a);
	SummaryDatum b
	(	wxString("Reconciled balance"),
		m_reconciled_closing_balance
	);
	m_summary_data->push_back(b);
	return;
}

void
ReconciliationEntryListCtrl::do_process_candidate_entry_for_summary
(	Entry const& p_entry
)
{
	if (p_entry.account() != account())
	{
		return;
	}
	assert (p_entry.account() == account());
	jewel::Decimal const amount = p_entry.amount();
	if (p_entry.date() <= max_date())
	{
		m_closing_balance += amount;
		if (p_entry.is_reconciled()) m_reconciled_closing_balance += amount;
	}
	return;
}

void
ReconciliationEntryListCtrl::do_process_removal_for_summary(long p_row)
{
	// Item is in the visible list so must be removed from m_closing_balance
	Decimal const amount = amount_for_row(p_row);
	m_closing_balance -= amount;

	// Check whether the item is marked as reconciled in the visible list.
	// If it is, then its removal should impact m_reconciled_closing_balance.
	wxListItem item;
	item.SetId(p_row);
	item.SetColumn(reconciled_col_num());
	GetItem(item);
	if (item.GetText() == reconciliation_status_marker(true))
	{
		m_reconciled_closing_balance -= amount;
	}
	return;
}

void
ReconciliationEntryListCtrl::on_item_right_click(wxListEvent& event)
{
	int const col = reconciled_col_num();
	Entry::Id const entry_id = event.GetData();
	long const pos = event.GetIndex();
	assert (FindItem(-1, entry_id) == pos);
	assert (entry_id >= 0);
	assert (GetItemData(pos) == static_cast<size_t>(entry_id));

	Entry entry(database_connection(), entry_id);
	bool const old_reconciliation_status = entry.is_reconciled();
	entry.set_whether_reconciled(!old_reconciliation_status);
	SetItem(pos, col, reconciliation_status_marker(entry.is_reconciled()));
	if (entry.is_reconciled())
	{
		m_reconciled_closing_balance += entry.amount();
	}
	else
	{
		m_reconciled_closing_balance -= entry.amount();
	}
	entry.save();
	ReconciliationListPanel* parent =
		dynamic_cast<ReconciliationListPanel*>(GetParent());
	assert (parent);
	parent->postconfigure_summary();
	return;
}

Decimal
ReconciliationEntryListCtrl::amount_for_row(long p_row) const
{
	wxListItem item;
	item.SetId(p_row);
	item.SetColumn(amount_col_num());
	GetItem(item);
	Decimal ret = wx_to_decimal(item.GetText(), locale());
	return ret;
}

boost::gregorian::date
ReconciliationEntryListCtrl::max_date() const
{
	return m_max_date;
}

}  // namespace gui
}  // namespace phatbooks
<|endoftext|>


<commit_before>/*************************************************************************
 *
 *  OpenOffice.org - a multi-platform office productivity suite
 *
 *  $RCSfile: dbwiz.cxx,v $
 *
 *  $Revision: 1.20 $
 *
 *  last change: $Author: kz $ $Date: 2008-03-06 18:18:41 $
 *
 *  The Contents of this file are made available subject to
 *  the terms of GNU Lesser General Public License Version 2.1.
 *
 *
 *    GNU Lesser General Public License Version 2.1
 *    =============================================
 *    Copyright 2005 by Sun Microsystems, Inc.
 *    901 San Antonio Road, Palo Alto, CA 94303, USA
 *
 *    This library is free software; you can redistribute it and/or
 *    modify it under the terms of the GNU Lesser General Public
 *    License version 2.1, as published by the Free Software Foundation.
 *
 *    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
 *
 ************************************************************************/

// MARKER(update_precomp.py): autogen include statement, do not remove
#include "precompiled_dbaccess.hxx"

#ifndef DBAUI_DBWIZ_HXX
#include "dbwiz.hxx"
#endif
#ifndef _DBAUI_DBADMIN_HRC_
#include "dbadmin.hrc"
#endif
#ifndef _DBU_DLG_HRC_
#include "dbu_dlg.hrc"
#endif
#ifndef _DBAUI_DATASOURCEITEMS_HXX_
#include "dsitems.hxx"
#endif
#ifndef _SFXSTRITEM_HXX
#include <svtools/stritem.hxx>
#endif
#ifndef _SFXENUMITEM_HXX
#include <svtools/eitem.hxx>
#endif
#ifndef _SFXINTITEM_HXX
#include <svtools/intitem.hxx>
#endif
#ifndef _SV_MSGBOX_HXX
#include <vcl/msgbox.hxx>
#endif
#ifndef DBACCESS_SHARED_DBUSTRINGS_HRC
#include "dbustrings.hrc"
#endif
#ifndef _DBAUI_ADMINPAGES_HXX_
#include "adminpages.hxx"
#endif
#ifndef _DBAUI_GENERALPAGE_HXX_
#include "generalpage.hxx"
#endif
#ifndef _DBAUI_LOCALRESACCESS_HXX_
#include "localresaccess.hxx"
#endif
#ifndef _DBAUI_STRINGLISTITEM_HXX_
#include "stringlistitem.hxx"
#endif
#ifndef _DBAUI_PROPERTYSETITEM_HXX_
#include "propertysetitem.hxx"
#endif
#ifndef _UNOTOOLS_CONFIGNODE_HXX_
#include <unotools/confignode.hxx>
#endif
#ifndef DBAUI_CONNECTIONPAGE_HXX
#include "ConnectionPage.hxx"
#endif
#ifndef DBAUI_DRIVERSETTINGS_HXX
#include "DriverSettings.hxx"
#endif
#ifndef _DBAUI_DBADMINIMPL_HXX_
#include "DbAdminImpl.hxx"
#endif
#ifndef _DBA_DBACCESS_HELPID_HRC_
#include "dbaccess_helpid.hrc"
#endif

//.........................................................................
namespace dbaui
{
//.........................................................................
using namespace svt;
using namespace com::sun::star::uno;
using namespace com::sun::star::sdbc;
using namespace com::sun::star::lang;
using namespace com::sun::star::util;
using namespace com::sun::star::beans;
using namespace com::sun::star::container;

#define START_PAGE                      0
#define CONNECTION_PAGE                 1
#define ADDITIONAL_PAGE_DBASE           2
#define ADDITIONAL_PAGE_FLAT            3
#define ADDITIONAL_PAGE_LDAP            4
#define ADDITIONAL_PAGE_ADABAS          5
#define ADDITIONAL_PAGE_MYSQL_JDBC      6
#define ADDITIONAL_PAGE_MYSQL_ODBC      7
#define ADDITIONAL_PAGE_ORACLE_JDBC     8
#define ADDITIONAL_PAGE_ADO             9
#define ADDITIONAL_PAGE_ODBC           10
#define ADDITIONAL_USERDEFINED         11
#define ADDITIONAL_PAGE_MYSQL_NATIVE   12

DBG_NAME(ODbTypeWizDialog)
//=========================================================================
//= ODbTypeWizDialog
//=========================================================================
//-------------------------------------------------------------------------
ODbTypeWizDialog::ODbTypeWizDialog(Window* _pParent
                               ,SfxItemSet* _pItems
                               ,const Reference< XMultiServiceFactory >& _rxORB
                               ,const ::com::sun::star::uno::Any& _aDataSourceName
                               )
    :OWizardMachine(_pParent, ModuleRes(DLG_DATABASE_TYPE_CHANGE), WZB_NEXT | WZB_PREVIOUS | WZB_FINISH | WZB_CANCEL | WZB_HELP )
    ,m_pOutSet(NULL)
    ,m_bResetting(sal_False)
    ,m_bApplied(sal_False)
    ,m_bUIEnabled( sal_True )
{
    DBG_CTOR(ODbTypeWizDialog,NULL);
    m_pImpl = ::std::auto_ptr<ODbDataSourceAdministrationHelper>(new ODbDataSourceAdministrationHelper(_rxORB,this,this));
    m_pImpl->setDataSourceOrName(_aDataSourceName);
    Reference< XPropertySet > xDatasource = m_pImpl->getCurrentDataSource();
    m_pOutSet = new SfxItemSet( *_pItems->GetPool(), _pItems->GetRanges() );

    m_pImpl->translateProperties(xDatasource, *m_pOutSet);
    m_eType = m_pImpl->getDatasourceType(*m_pOutSet);

    SetPageSizePixel(LogicToPixel(::Size(PAGE_X, PAGE_Y), MAP_APPFONT));
    ShowButtonFixedLine(sal_True);
    defaultButton(WZB_NEXT);
    enableButtons(WZB_FINISH, sal_False);
    enableAutomaticNextButtonState( true );

    m_pPrevPage->SetHelpId(HID_DBWIZ_PREVIOUS);
    m_pNextPage->SetHelpId(HID_DBWIZ_NEXT);
    m_pCancel->SetHelpId(HID_DBWIZ_CANCEL);
    m_pFinish->SetHelpId(HID_DBWIZ_FINISH);
    m_pHelp->SetUniqueId(UID_DBWIZ_HELP);
    // no local resources needed anymore
    FreeResource();
    ActivatePage();
}

//-------------------------------------------------------------------------
ODbTypeWizDialog::~ODbTypeWizDialog()
{
    DBG_DTOR(ODbTypeWizDialog,NULL);
    delete m_pOutSet;
}
//-------------------------------------------------------------------------
IMPL_LINK(ODbTypeWizDialog, OnTypeSelected, OGeneralPage*, _pTabPage)
{
    m_eType = _pTabPage->GetSelectedType();
    switch(m_eType)
    {
        case DST_MOZILLA:
        case DST_OUTLOOK:
        case DST_OUTLOOKEXP:
        case DST_EVOLUTION:
        case DST_KAB:
        case DST_MACAB:
            enableButtons(WZB_NEXT,sal_False);
            enableButtons(WZB_FINISH,sal_True);
            break;
        default:
            enableButtons(WZB_NEXT,sal_True);
            enableButtons(WZB_FINISH,sal_False);
            break;
    }
    return 1L;
}
//-------------------------------------------------------------------------
WizardTypes::WizardState ODbTypeWizDialog::determineNextState( WizardState _nCurrentState ) const
{
    WizardTypes::WizardState nNextState = WZS_INVALID_STATE;
    switch(_nCurrentState)
    {
        case START_PAGE:
            switch(m_eType)
            {
                case DST_MOZILLA:
                case DST_OUTLOOK:
                case DST_OUTLOOKEXP:
                case DST_EVOLUTION:
                case DST_KAB:
                case DST_MACAB:
                    nNextState = WZS_INVALID_STATE;
                    break;
                default:
                    nNextState = CONNECTION_PAGE;
                    break;
            }
            break;
        case CONNECTION_PAGE:
            switch(m_eType)
            {
                case DST_MOZILLA:
                case DST_THUNDERBIRD:
                case DST_OUTLOOK:
                case DST_OUTLOOKEXP:
                case DST_EVOLUTION:
                case DST_KAB:
                case DST_MACAB:
                case DST_MSACCESS:
                case DST_MSACCESS_2007:
                case DST_JDBC:
                case DST_CALC:
                    nNextState = WZS_INVALID_STATE;
                    break;
                case DST_DBASE:
                    nNextState = ADDITIONAL_PAGE_DBASE;
                    break;
                case DST_FLAT:
                    nNextState = ADDITIONAL_PAGE_FLAT;
                    break;
                case DST_LDAP:
                    nNextState = ADDITIONAL_PAGE_LDAP;
                    break;
                case DST_ADABAS:
                    nNextState = ADDITIONAL_PAGE_ADABAS;
                    break;
                case DST_MYSQL_NATIVE:
                    nNextState = ADDITIONAL_PAGE_MYSQL_NATIVE;
                    break;
                case DST_MYSQL_JDBC:
                    nNextState = ADDITIONAL_PAGE_MYSQL_JDBC;
                    break;
                case DST_MYSQL_ODBC:
                    nNextState = ADDITIONAL_PAGE_MYSQL_ODBC;
                    break;
                case DST_ORACLE_JDBC:
                    nNextState = ADDITIONAL_PAGE_ORACLE_JDBC;
                    break;
                case DST_ADO:
                    nNextState = ADDITIONAL_PAGE_ADO;
                    break;
                case DST_ODBC:
                    nNextState = ADDITIONAL_PAGE_ODBC;
                    break;
                default:
                    nNextState = WZS_INVALID_STATE;
                    break;
            }
            break;
    }

    return nNextState;
}

// -----------------------------------------------------------------------------
const SfxItemSet* ODbTypeWizDialog::getOutputSet() const
{
    return m_pOutSet;
}
// -----------------------------------------------------------------------------
SfxItemSet* ODbTypeWizDialog::getWriteOutputSet()
{
    return m_pOutSet;
}
// -----------------------------------------------------------------------------
::std::pair< Reference<XConnection>,sal_Bool> ODbTypeWizDialog::createConnection()
{
    return m_pImpl->createConnection();
}
// -----------------------------------------------------------------------------
Reference< XMultiServiceFactory > ODbTypeWizDialog::getORB() const
{
    return m_pImpl->getORB();
}
// -----------------------------------------------------------------------------
Reference< XDriver > ODbTypeWizDialog::getDriver()
{
    return m_pImpl->getDriver();
}
// -----------------------------------------------------------------------------
DATASOURCE_TYPE ODbTypeWizDialog::getDatasourceType(const SfxItemSet& _rSet) const
{
    return m_pImpl->getDatasourceType(_rSet);
}
// -----------------------------------------------------------------------------
void ODbTypeWizDialog::clearPassword()
{
    m_pImpl->clearPassword();
}

// -----------------------------------------------------------------------------
TabPage* ODbTypeWizDialog::createPage(WizardState _nState)
{
    USHORT nStringId = STR_PAGETITLE_ADVANCED;
    TabPage* pPage = NULL;
    switch(_nState)
    {
        case START_PAGE: // start state
        {
            pPage = OGeneralPage::Create(this,*m_pOutSet);
            OGeneralPage* pGeneralPage = static_cast< OGeneralPage* >( pPage );
            pGeneralPage->SetTypeSelectHandler( LINK( this, ODbTypeWizDialog, OnTypeSelected));
//            pGeneralPage->SetCreationModeHandler( LINK( this, ODbTypeWizDialog, TODO ) );
            nStringId = STR_PAGETITLE_GENERAL;
        }
        break;
        case CONNECTION_PAGE:
            pPage = OConnectionTabPage::Create(this,*m_pOutSet);
            nStringId = STR_PAGETITLE_CONNECTION;
            break;

        case ADDITIONAL_PAGE_DBASE:
            pPage = ODriversSettings::CreateDbase(this,*m_pOutSet);
            break;
        case ADDITIONAL_PAGE_FLAT:
            pPage = ODriversSettings::CreateText(this,*m_pOutSet);
            break;
        case ADDITIONAL_PAGE_LDAP:
            pPage = ODriversSettings::CreateLDAP(this,*m_pOutSet);
            break;
        case ADDITIONAL_PAGE_ADABAS:
            pPage = ODriversSettings::CreateAdabas(this,*m_pOutSet);
            break;
        case ADDITIONAL_PAGE_MYSQL_JDBC:
            pPage = ODriversSettings::CreateMySQLJDBC(this,*m_pOutSet);
            break;
            case ADDITIONAL_PAGE_MYSQL_NATIVE:
            pPage = ODriversSettings::CreateMySQLNATIVE(this,*m_pOutSet);
            break;
        case ADDITIONAL_PAGE_MYSQL_ODBC:
            pPage = ODriversSettings::CreateMySQLODBC(this,*m_pOutSet);
            break;
        case ADDITIONAL_PAGE_ORACLE_JDBC:
            pPage = ODriversSettings::CreateOracleJDBC(this,*m_pOutSet);
            break;
        case ADDITIONAL_PAGE_ADO:
            pPage = ODriversSettings::CreateAdo(this,*m_pOutSet);
            break;
        case ADDITIONAL_PAGE_ODBC:
            pPage = ODriversSettings::CreateODBC(this,*m_pOutSet);
            break;
        case ADDITIONAL_USERDEFINED:
            pPage = ODriversSettings::CreateUser(this,*m_pOutSet);
            break;
        default:
            OSL_ENSURE(0,"Wrong state!");
            break;
    }

    // register ourself as modified listener
    if ( pPage )
    {
        static_cast<OGenericAdministrationPage*>(pPage)->SetServiceFactory(m_pImpl->getORB());
        static_cast<OGenericAdministrationPage*>(pPage)->SetAdminDialog(this,this);
        // open our own resource block, as the page titles are strings local to this block
        LocalResourceAccess aDummy(DLG_DATABASE_ADMINISTRATION, RSC_TABDIALOG);

        pPage->SetText(String(ModuleRes(nStringId)));
        defaultButton( _nState == START_PAGE ? WZB_NEXT : WZB_FINISH );
        enableButtons( WZB_FINISH, _nState == START_PAGE ? sal_False : sal_True);
        pPage->Show();
    }
    return pPage;
}
// -----------------------------------------------------------------------------
sal_Bool ODbTypeWizDialog::leaveState(WizardState _nState)
{
    SfxTabPage* pPage = static_cast<SfxTabPage*>(WizardDialog::GetPage(_nState));
    if ( pPage )
        pPage->FillItemSet(*m_pOutSet);
    return sal_True;
}
// -----------------------------------------------------------------------------
void ODbTypeWizDialog::setTitle(const ::rtl::OUString& _sTitle)
{
    SetText(_sTitle);
}
//-------------------------------------------------------------------------
void ODbTypeWizDialog::enableConfirmSettings( bool _bEnable )
{
    enableButtons( WZB_FINISH, _bEnable );
    // TODO:
    // this is hacky. At the moment, this method is used in only one case (#b6532894#).
    // As soon as it is to be used more wide-spread, we should find a proper concept
    // for enabling both the Next and Finish buttons, depending on the current page state.
    // Plus, the concept must also care for the case where those pages are embedded into
    // anormal tab dialog.
}
//-------------------------------------------------------------------------
sal_Bool ODbTypeWizDialog::saveDatasource()
{
    SfxTabPage* pPage = static_cast<SfxTabPage*>(WizardDialog::GetPage(getCurrentState()));
    if ( pPage )
        pPage->FillItemSet(*m_pOutSet);
    return sal_True;
}
// -----------------------------------------------------------------------------
IWizardPage* ODbTypeWizDialog::getWizardPage(TabPage* _pCurrentPage) const
{
    OGenericAdministrationPage* pPage = static_cast<OGenericAdministrationPage*>(_pCurrentPage);
    return pPage;
}
// -----------------------------------------------------------------------------
sal_Bool ODbTypeWizDialog::onFinish(sal_Int32 _nResult)
{
    saveDatasource();
    return m_pImpl->saveChanges(*m_pOutSet) ? OWizardMachine::onFinish(_nResult) : sal_False;
}
//.........................................................................
}   // namespace dbaui
//.........................................................................
<commit_msg>INTEGRATION: CWS changefileheader (1.20.12); FILE MERGED 2008/03/31 13:27:16 rt 1.20.12.1: #i87441# Change license header to LPGL v3.<commit_after>/*************************************************************************
 *
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * Copyright 2008 by Sun Microsystems, Inc.
 *
 * OpenOffice.org - a multi-platform office productivity suite
 *
 * $RCSfile: dbwiz.cxx,v $
 * $Revision: 1.21 $
 *
 * This file is part of OpenOffice.org.
 *
 * OpenOffice.org is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License version 3
 * only, as published by the Free Software Foundation.
 *
 * OpenOffice.org 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 version 3 for more details
 * (a copy is included in the LICENSE file that accompanied this code).
 *
 * You should have received a copy of the GNU Lesser General Public License
 * version 3 along with OpenOffice.org.  If not, see
 * <http://www.openoffice.org/license.html>
 * for a copy of the LGPLv3 License.
 *
 ************************************************************************/

// MARKER(update_precomp.py): autogen include statement, do not remove
#include "precompiled_dbaccess.hxx"

#ifndef DBAUI_DBWIZ_HXX
#include "dbwiz.hxx"
#endif
#ifndef _DBAUI_DBADMIN_HRC_
#include "dbadmin.hrc"
#endif
#ifndef _DBU_DLG_HRC_
#include "dbu_dlg.hrc"
#endif
#ifndef _DBAUI_DATASOURCEITEMS_HXX_
#include "dsitems.hxx"
#endif
#ifndef _SFXSTRITEM_HXX
#include <svtools/stritem.hxx>
#endif
#ifndef _SFXENUMITEM_HXX
#include <svtools/eitem.hxx>
#endif
#ifndef _SFXINTITEM_HXX
#include <svtools/intitem.hxx>
#endif
#ifndef _SV_MSGBOX_HXX
#include <vcl/msgbox.hxx>
#endif
#ifndef DBACCESS_SHARED_DBUSTRINGS_HRC
#include "dbustrings.hrc"
#endif
#ifndef _DBAUI_ADMINPAGES_HXX_
#include "adminpages.hxx"
#endif
#ifndef _DBAUI_GENERALPAGE_HXX_
#include "generalpage.hxx"
#endif
#ifndef _DBAUI_LOCALRESACCESS_HXX_
#include "localresaccess.hxx"
#endif
#ifndef _DBAUI_STRINGLISTITEM_HXX_
#include "stringlistitem.hxx"
#endif
#ifndef _DBAUI_PROPERTYSETITEM_HXX_
#include "propertysetitem.hxx"
#endif
#ifndef _UNOTOOLS_CONFIGNODE_HXX_
#include <unotools/confignode.hxx>
#endif
#ifndef DBAUI_CONNECTIONPAGE_HXX
#include "ConnectionPage.hxx"
#endif
#ifndef DBAUI_DRIVERSETTINGS_HXX
#include "DriverSettings.hxx"
#endif
#ifndef _DBAUI_DBADMINIMPL_HXX_
#include "DbAdminImpl.hxx"
#endif
#ifndef _DBA_DBACCESS_HELPID_HRC_
#include "dbaccess_helpid.hrc"
#endif

//.........................................................................
namespace dbaui
{
//.........................................................................
using namespace svt;
using namespace com::sun::star::uno;
using namespace com::sun::star::sdbc;
using namespace com::sun::star::lang;
using namespace com::sun::star::util;
using namespace com::sun::star::beans;
using namespace com::sun::star::container;

#define START_PAGE                      0
#define CONNECTION_PAGE                 1
#define ADDITIONAL_PAGE_DBASE           2
#define ADDITIONAL_PAGE_FLAT            3
#define ADDITIONAL_PAGE_LDAP            4
#define ADDITIONAL_PAGE_ADABAS          5
#define ADDITIONAL_PAGE_MYSQL_JDBC      6
#define ADDITIONAL_PAGE_MYSQL_ODBC      7
#define ADDITIONAL_PAGE_ORACLE_JDBC     8
#define ADDITIONAL_PAGE_ADO             9
#define ADDITIONAL_PAGE_ODBC           10
#define ADDITIONAL_USERDEFINED         11
#define ADDITIONAL_PAGE_MYSQL_NATIVE   12

DBG_NAME(ODbTypeWizDialog)
//=========================================================================
//= ODbTypeWizDialog
//=========================================================================
//-------------------------------------------------------------------------
ODbTypeWizDialog::ODbTypeWizDialog(Window* _pParent
                               ,SfxItemSet* _pItems
                               ,const Reference< XMultiServiceFactory >& _rxORB
                               ,const ::com::sun::star::uno::Any& _aDataSourceName
                               )
    :OWizardMachine(_pParent, ModuleRes(DLG_DATABASE_TYPE_CHANGE), WZB_NEXT | WZB_PREVIOUS | WZB_FINISH | WZB_CANCEL | WZB_HELP )
    ,m_pOutSet(NULL)
    ,m_bResetting(sal_False)
    ,m_bApplied(sal_False)
    ,m_bUIEnabled( sal_True )
{
    DBG_CTOR(ODbTypeWizDialog,NULL);
    m_pImpl = ::std::auto_ptr<ODbDataSourceAdministrationHelper>(new ODbDataSourceAdministrationHelper(_rxORB,this,this));
    m_pImpl->setDataSourceOrName(_aDataSourceName);
    Reference< XPropertySet > xDatasource = m_pImpl->getCurrentDataSource();
    m_pOutSet = new SfxItemSet( *_pItems->GetPool(), _pItems->GetRanges() );

    m_pImpl->translateProperties(xDatasource, *m_pOutSet);
    m_eType = m_pImpl->getDatasourceType(*m_pOutSet);

    SetPageSizePixel(LogicToPixel(::Size(PAGE_X, PAGE_Y), MAP_APPFONT));
    ShowButtonFixedLine(sal_True);
    defaultButton(WZB_NEXT);
    enableButtons(WZB_FINISH, sal_False);
    enableAutomaticNextButtonState( true );

    m_pPrevPage->SetHelpId(HID_DBWIZ_PREVIOUS);
    m_pNextPage->SetHelpId(HID_DBWIZ_NEXT);
    m_pCancel->SetHelpId(HID_DBWIZ_CANCEL);
    m_pFinish->SetHelpId(HID_DBWIZ_FINISH);
    m_pHelp->SetUniqueId(UID_DBWIZ_HELP);
    // no local resources needed anymore
    FreeResource();
    ActivatePage();
}

//-------------------------------------------------------------------------
ODbTypeWizDialog::~ODbTypeWizDialog()
{
    DBG_DTOR(ODbTypeWizDialog,NULL);
    delete m_pOutSet;
}
//-------------------------------------------------------------------------
IMPL_LINK(ODbTypeWizDialog, OnTypeSelected, OGeneralPage*, _pTabPage)
{
    m_eType = _pTabPage->GetSelectedType();
    switch(m_eType)
    {
        case DST_MOZILLA:
        case DST_OUTLOOK:
        case DST_OUTLOOKEXP:
        case DST_EVOLUTION:
        case DST_KAB:
        case DST_MACAB:
            enableButtons(WZB_NEXT,sal_False);
            enableButtons(WZB_FINISH,sal_True);
            break;
        default:
            enableButtons(WZB_NEXT,sal_True);
            enableButtons(WZB_FINISH,sal_False);
            break;
    }
    return 1L;
}
//-------------------------------------------------------------------------
WizardTypes::WizardState ODbTypeWizDialog::determineNextState( WizardState _nCurrentState ) const
{
    WizardTypes::WizardState nNextState = WZS_INVALID_STATE;
    switch(_nCurrentState)
    {
        case START_PAGE:
            switch(m_eType)
            {
                case DST_MOZILLA:
                case DST_OUTLOOK:
                case DST_OUTLOOKEXP:
                case DST_EVOLUTION:
                case DST_KAB:
                case DST_MACAB:
                    nNextState = WZS_INVALID_STATE;
                    break;
                default:
                    nNextState = CONNECTION_PAGE;
                    break;
            }
            break;
        case CONNECTION_PAGE:
            switch(m_eType)
            {
                case DST_MOZILLA:
                case DST_THUNDERBIRD:
                case DST_OUTLOOK:
                case DST_OUTLOOKEXP:
                case DST_EVOLUTION:
                case DST_KAB:
                case DST_MACAB:
                case DST_MSACCESS:
                case DST_MSACCESS_2007:
                case DST_JDBC:
                case DST_CALC:
                    nNextState = WZS_INVALID_STATE;
                    break;
                case DST_DBASE:
                    nNextState = ADDITIONAL_PAGE_DBASE;
                    break;
                case DST_FLAT:
                    nNextState = ADDITIONAL_PAGE_FLAT;
                    break;
                case DST_LDAP:
                    nNextState = ADDITIONAL_PAGE_LDAP;
                    break;
                case DST_ADABAS:
                    nNextState = ADDITIONAL_PAGE_ADABAS;
                    break;
                case DST_MYSQL_NATIVE:
                    nNextState = ADDITIONAL_PAGE_MYSQL_NATIVE;
                    break;
                case DST_MYSQL_JDBC:
                    nNextState = ADDITIONAL_PAGE_MYSQL_JDBC;
                    break;
                case DST_MYSQL_ODBC:
                    nNextState = ADDITIONAL_PAGE_MYSQL_ODBC;
                    break;
                case DST_ORACLE_JDBC:
                    nNextState = ADDITIONAL_PAGE_ORACLE_JDBC;
                    break;
                case DST_ADO:
                    nNextState = ADDITIONAL_PAGE_ADO;
                    break;
                case DST_ODBC:
                    nNextState = ADDITIONAL_PAGE_ODBC;
                    break;
                default:
                    nNextState = WZS_INVALID_STATE;
                    break;
            }
            break;
    }

    return nNextState;
}

// -----------------------------------------------------------------------------
const SfxItemSet* ODbTypeWizDialog::getOutputSet() const
{
    return m_pOutSet;
}
// -----------------------------------------------------------------------------
SfxItemSet* ODbTypeWizDialog::getWriteOutputSet()
{
    return m_pOutSet;
}
// -----------------------------------------------------------------------------
::std::pair< Reference<XConnection>,sal_Bool> ODbTypeWizDialog::createConnection()
{
    return m_pImpl->createConnection();
}
// -----------------------------------------------------------------------------
Reference< XMultiServiceFactory > ODbTypeWizDialog::getORB() const
{
    return m_pImpl->getORB();
}
// -----------------------------------------------------------------------------
Reference< XDriver > ODbTypeWizDialog::getDriver()
{
    return m_pImpl->getDriver();
}
// -----------------------------------------------------------------------------
DATASOURCE_TYPE ODbTypeWizDialog::getDatasourceType(const SfxItemSet& _rSet) const
{
    return m_pImpl->getDatasourceType(_rSet);
}
// -----------------------------------------------------------------------------
void ODbTypeWizDialog::clearPassword()
{
    m_pImpl->clearPassword();
}

// -----------------------------------------------------------------------------
TabPage* ODbTypeWizDialog::createPage(WizardState _nState)
{
    USHORT nStringId = STR_PAGETITLE_ADVANCED;
    TabPage* pPage = NULL;
    switch(_nState)
    {
        case START_PAGE: // start state
        {
            pPage = OGeneralPage::Create(this,*m_pOutSet);
            OGeneralPage* pGeneralPage = static_cast< OGeneralPage* >( pPage );
            pGeneralPage->SetTypeSelectHandler( LINK( this, ODbTypeWizDialog, OnTypeSelected));
//            pGeneralPage->SetCreationModeHandler( LINK( this, ODbTypeWizDialog, TODO ) );
            nStringId = STR_PAGETITLE_GENERAL;
        }
        break;
        case CONNECTION_PAGE:
            pPage = OConnectionTabPage::Create(this,*m_pOutSet);
            nStringId = STR_PAGETITLE_CONNECTION;
            break;

        case ADDITIONAL_PAGE_DBASE:
            pPage = ODriversSettings::CreateDbase(this,*m_pOutSet);
            break;
        case ADDITIONAL_PAGE_FLAT:
            pPage = ODriversSettings::CreateText(this,*m_pOutSet);
            break;
        case ADDITIONAL_PAGE_LDAP:
            pPage = ODriversSettings::CreateLDAP(this,*m_pOutSet);
            break;
        case ADDITIONAL_PAGE_ADABAS:
            pPage = ODriversSettings::CreateAdabas(this,*m_pOutSet);
            break;
        case ADDITIONAL_PAGE_MYSQL_JDBC:
            pPage = ODriversSettings::CreateMySQLJDBC(this,*m_pOutSet);
            break;
            case ADDITIONAL_PAGE_MYSQL_NATIVE:
            pPage = ODriversSettings::CreateMySQLNATIVE(this,*m_pOutSet);
            break;
        case ADDITIONAL_PAGE_MYSQL_ODBC:
            pPage = ODriversSettings::CreateMySQLODBC(this,*m_pOutSet);
            break;
        case ADDITIONAL_PAGE_ORACLE_JDBC:
            pPage = ODriversSettings::CreateOracleJDBC(this,*m_pOutSet);
            break;
        case ADDITIONAL_PAGE_ADO:
            pPage = ODriversSettings::CreateAdo(this,*m_pOutSet);
            break;
        case ADDITIONAL_PAGE_ODBC:
            pPage = ODriversSettings::CreateODBC(this,*m_pOutSet);
            break;
        case ADDITIONAL_USERDEFINED:
            pPage = ODriversSettings::CreateUser(this,*m_pOutSet);
            break;
        default:
            OSL_ENSURE(0,"Wrong state!");
            break;
    }

    // register ourself as modified listener
    if ( pPage )
    {
        static_cast<OGenericAdministrationPage*>(pPage)->SetServiceFactory(m_pImpl->getORB());
        static_cast<OGenericAdministrationPage*>(pPage)->SetAdminDialog(this,this);
        // open our own resource block, as the page titles are strings local to this block
        LocalResourceAccess aDummy(DLG_DATABASE_ADMINISTRATION, RSC_TABDIALOG);

        pPage->SetText(String(ModuleRes(nStringId)));
        defaultButton( _nState == START_PAGE ? WZB_NEXT : WZB_FINISH );
        enableButtons( WZB_FINISH, _nState == START_PAGE ? sal_False : sal_True);
        pPage->Show();
    }
    return pPage;
}
// -----------------------------------------------------------------------------
sal_Bool ODbTypeWizDialog::leaveState(WizardState _nState)
{
    SfxTabPage* pPage = static_cast<SfxTabPage*>(WizardDialog::GetPage(_nState));
    if ( pPage )
        pPage->FillItemSet(*m_pOutSet);
    return sal_True;
}
// -----------------------------------------------------------------------------
void ODbTypeWizDialog::setTitle(const ::rtl::OUString& _sTitle)
{
    SetText(_sTitle);
}
//-------------------------------------------------------------------------
void ODbTypeWizDialog::enableConfirmSettings( bool _bEnable )
{
    enableButtons( WZB_FINISH, _bEnable );
    // TODO:
    // this is hacky. At the moment, this method is used in only one case (#b6532894#).
    // As soon as it is to be used more wide-spread, we should find a proper concept
    // for enabling both the Next and Finish buttons, depending on the current page state.
    // Plus, the concept must also care for the case where those pages are embedded into
    // anormal tab dialog.
}
//-------------------------------------------------------------------------
sal_Bool ODbTypeWizDialog::saveDatasource()
{
    SfxTabPage* pPage = static_cast<SfxTabPage*>(WizardDialog::GetPage(getCurrentState()));
    if ( pPage )
        pPage->FillItemSet(*m_pOutSet);
    return sal_True;
}
// -----------------------------------------------------------------------------
IWizardPage* ODbTypeWizDialog::getWizardPage(TabPage* _pCurrentPage) const
{
    OGenericAdministrationPage* pPage = static_cast<OGenericAdministrationPage*>(_pCurrentPage);
    return pPage;
}
// -----------------------------------------------------------------------------
sal_Bool ODbTypeWizDialog::onFinish(sal_Int32 _nResult)
{
    saveDatasource();
    return m_pImpl->saveChanges(*m_pOutSet) ? OWizardMachine::onFinish(_nResult) : sal_False;
}
//.........................................................................
}   // namespace dbaui
//.........................................................................
<|endoftext|>


<commit_before>// Copyright (c) 2013, Matthew Harvey. All rights reserved.

#include "reconciliation_entry_list_ctrl.hpp"
#include "entry.hpp"
#include "filtered_entry_list_ctrl.hpp"
#include "finformat.hpp"
#include "locale.hpp"
#include "ordinary_journal.hpp"
#include "summary_datum.hpp"
#include <boost/date_time/gregorian/gregorian.hpp>
#include <boost/optional.hpp>
#include <jewel/decimal.hpp>
#include <jewel/optional.hpp>
#include <wx/listctrl.h>
#include <vector>

using boost::optional;
using jewel::Decimal;
using jewel::value;
using std::vector;

// For debugging
	#include <jewel/debug_log.hpp>
	#include <iostream>
	using std::endl;

namespace gregorian = boost::gregorian;

namespace phatbooks
{
namespace gui
{

// TODO We can use "EVT_LIST_ITEM_COL_CLICK" to activate this
// on left click of the "Reconciled" column - which is more intuitive.
BEGIN_EVENT_TABLE(ReconciliationEntryListCtrl, FilteredEntryListCtrl)
	EVT_LIST_ITEM_RIGHT_CLICK
	(	wxID_ANY,
		ReconciliationEntryListCtrl::on_item_right_click
	)
END_EVENT_TABLE()

namespace
{
	int comment_col_num()
	{
		return 1;
	}
	int amount_col_num()
	{
		return 2;
	}
	int reconciled_col_num()
	{
		return 3;
	}
	int anon_num_columns()
	{
		return 4;
	}

}  // end anonymous namespace

ReconciliationEntryListCtrl::ReconciliationEntryListCtrl
(	ReconciliationListPanel* p_parent,
	wxSize const& p_size,
	Account const& p_account,
	gregorian::date const& p_min_date,
	gregorian::date const& p_max_date
):
	FilteredEntryListCtrl
	(	p_parent,
		p_size,
		p_account,
		optional<gregorian::date>(p_min_date),
		optional<gregorian::date>(p_max_date)
	),
	m_max_date(p_max_date),
	m_summary_data(0),
	m_closing_balance(0, p_account.commodity().precision()),
	m_reconciled_closing_balance(0, p_account.commodity().precision())
{
}

ReconciliationEntryListCtrl::~ReconciliationEntryListCtrl()
{
	delete m_summary_data;
	m_summary_data = 0;
}

void
ReconciliationEntryListCtrl::do_set_non_date_columns
(	long p_row,
	Entry const& p_entry
)
{
	SetItem
	(	p_row,
		comment_col_num(),
		bstring_to_wx(p_entry.comment())
	);
	SetItem
	(	p_row,
		amount_col_num(),
		finformat_wx(p_entry.amount(), locale(), false)
	);
	SetItem
	(	p_row,
		reconciled_col_num(),
		(p_entry.is_reconciled()? wxString("Y"): wxString("N"))
	);
	assert (num_columns() == 4);
	return;
}

void
ReconciliationEntryListCtrl::do_insert_non_date_columns()
{
	InsertColumn
	(	comment_col_num(),
		wxString("Memo"),
		wxLIST_FORMAT_LEFT
	);
	InsertColumn
	(	amount_col_num(),
		wxString("Amount"),
		wxLIST_FORMAT_RIGHT
	);
	InsertColumn
	(	reconciled_col_num(),
		wxString("Reconciled?"),
		wxLIST_FORMAT_RIGHT
	);
	assert (num_columns() == 4);
	return;
}

bool
ReconciliationEntryListCtrl::do_approve_entry(Entry const& p_entry) const
{
	if (p_entry.account() != account())
	{
		return false;
	}
	gregorian::date const date = p_entry.date();
	if (date > max_date())
	{
		return false;
	}
	if (date < min_date())
	{
		// We include unreconciled Entries even if they're prior to the
		// min_date(), providing they're not later than max_date().
		assert (date <= max_date());
		assert
		(	(date > database_connection().opening_balance_journal_date()) ||
			p_entry.is_reconciled()
		);
		return !p_entry.is_reconciled();
	}
	assert (p_entry.account() == account());
	assert (date >= min_date());
	assert (date <= max_date());
	return true;
}

int
ReconciliationEntryListCtrl::do_get_comment_col_num() const
{
	return comment_col_num();
}

int
ReconciliationEntryListCtrl::do_get_num_columns() const
{
	return anon_num_columns();
}

vector<SummaryDatum> const&
ReconciliationEntryListCtrl::do_get_summary_data() const
{
	assert (!m_summary_data->empty());
	m_summary_data->at(0).set_amount(m_closing_balance);
	m_summary_data->at(1).set_amount(m_reconciled_closing_balance);
	return *m_summary_data;
}

void
ReconciliationEntryListCtrl::do_initialize_summary_data()
{
	m_closing_balance = Decimal(0, account().commodity().precision());
	m_reconciled_closing_balance =
		Decimal(0, account().commodity().precision());
	assert (!m_summary_data);
	m_summary_data = new std::vector<SummaryDatum>;
	SummaryDatum a
	(	wxString("Closing balance"),
		m_closing_balance
	);
	m_summary_data->push_back(a);
	SummaryDatum b
	(	wxString("Reconciled balance"),
		m_reconciled_closing_balance
	);
	m_summary_data->push_back(b);
	return;
}

void
ReconciliationEntryListCtrl::do_process_candidate_entry_for_summary
(	Entry const& p_entry
)
{
	if (p_entry.account() != account())
	{
		return;
	}
	assert (p_entry.account() == account());
	jewel::Decimal const amount = p_entry.amount();
	if (p_entry.date() <= max_date())
	{
		m_closing_balance += amount;
		if (p_entry.is_reconciled()) m_reconciled_closing_balance += amount;
	}
	return;
}

void
ReconciliationEntryListCtrl::do_process_removal_for_summary(long p_row)
{
	Decimal const amount = amount_for_row(p_row);
	m_closing_balance -= amount;
#	ifndef NDEBUG
		wxListItem item;
		item.SetId(p_row);
		item.SetColumn(reconciled_col_num());
		GetItem(item);
		// TODO HIGH PRIORITY This assertion can fail!
		assert (item.GetText() == wxString("N"));
		// as we don't allow user to delete reconciled Entries.
		// so we don't need to adjust m_reconciled_closing_balance here.
#	endif
	return;
}

void
ReconciliationEntryListCtrl::on_item_right_click(wxListEvent& event)
{
	int const col = reconciled_col_num();
	Entry::Id const entry_id = event.GetData();
	long const pos = event.GetIndex();
	assert (FindItem(-1, entry_id) == pos);
	assert (entry_id >= 0);
	assert (GetItemData(pos) == static_cast<size_t>(entry_id));

	Entry entry(database_connection(), entry_id);
	bool const old_reconciliation_status = entry.is_reconciled();
	entry.set_whether_reconciled(!old_reconciliation_status);
	SetItem(pos, col, entry.is_reconciled()? wxString("Y"): wxString("N"));
	if (entry.is_reconciled())
	{
		m_reconciled_closing_balance += entry.amount();
	}
	else
	{
		m_reconciled_closing_balance -= entry.amount();
	}
	entry.save();
	ReconciliationListPanel* parent =
		dynamic_cast<ReconciliationListPanel*>(GetParent());
	assert (parent);
	parent->postconfigure_summary();
	return;
}

Decimal
ReconciliationEntryListCtrl::amount_for_row(long p_row) const
{
	wxListItem item;
	item.SetId(p_row);
	item.SetColumn(amount_col_num());
	GetItem(item);
	Decimal ret = wx_to_decimal(item.GetText(), locale());
	return ret;
}

boost::gregorian::date
ReconciliationEntryListCtrl::max_date() const
{
	return m_max_date;
}

}  // namespace gui
}  // namespace phatbooks
<commit_msg>In ReconciliationEntryListCtrl, instead of using "Y" and "N" to indicate reconciliation status, now using a the unicode "thick check" mark and an empty string, respectively. Did some refactoring so that the relationship between the reconciliation status and the particular string that represents it, is located within a single function.<commit_after>// Copyright (c) 2013, Matthew Harvey. All rights reserved.

#include "reconciliation_entry_list_ctrl.hpp"
#include "entry.hpp"
#include "filtered_entry_list_ctrl.hpp"
#include "finformat.hpp"
#include "locale.hpp"
#include "ordinary_journal.hpp"
#include "summary_datum.hpp"
#include <boost/date_time/gregorian/gregorian.hpp>
#include <boost/optional.hpp>
#include <jewel/decimal.hpp>
#include <jewel/optional.hpp>
#include <wx/listctrl.h>
#include <vector>

using boost::optional;
using jewel::Decimal;
using jewel::value;
using std::vector;

// For debugging
	#include <jewel/debug_log.hpp>
	#include <iostream>
	using std::endl;

namespace gregorian = boost::gregorian;

namespace phatbooks
{
namespace gui
{

// TODO We can use "EVT_LIST_ITEM_COL_CLICK" to activate this
// on left click of the "Reconciled" column - which is more intuitive.
BEGIN_EVENT_TABLE(ReconciliationEntryListCtrl, FilteredEntryListCtrl)
	EVT_LIST_ITEM_RIGHT_CLICK
	(	wxID_ANY,
		ReconciliationEntryListCtrl::on_item_right_click
	)
END_EVENT_TABLE()

namespace
{
	int comment_col_num()
	{
		return 1;
	}
	int amount_col_num()
	{
		return 2;
	}
	int reconciled_col_num()
	{
		return 3;
	}
	int anon_num_columns()
	{
		return 4;
	}
	wxString reconciliation_status_mark(bool p_is_reconciled)
	{
		return
			p_is_reconciled?
			wxString("\u2714"):  // TODO HIGH PRIORITY Check that this "thick check" mark displays properly on Windows.
			wxString();
	}

}  // end anonymous namespace

ReconciliationEntryListCtrl::ReconciliationEntryListCtrl
(	ReconciliationListPanel* p_parent,
	wxSize const& p_size,
	Account const& p_account,
	gregorian::date const& p_min_date,
	gregorian::date const& p_max_date
):
	FilteredEntryListCtrl
	(	p_parent,
		p_size,
		p_account,
		optional<gregorian::date>(p_min_date),
		optional<gregorian::date>(p_max_date)
	),
	m_max_date(p_max_date),
	m_summary_data(0),
	m_closing_balance(0, p_account.commodity().precision()),
	m_reconciled_closing_balance(0, p_account.commodity().precision())
{
}

ReconciliationEntryListCtrl::~ReconciliationEntryListCtrl()
{
	delete m_summary_data;
	m_summary_data = 0;
}

void
ReconciliationEntryListCtrl::do_set_non_date_columns
(	long p_row,
	Entry const& p_entry
)
{
	SetItem
	(	p_row,
		comment_col_num(),
		bstring_to_wx(p_entry.comment())
	);
	SetItem
	(	p_row,
		amount_col_num(),
		finformat_wx(p_entry.amount(), locale(), false)
	);
	SetItem
	(	p_row,
		reconciled_col_num(),
		reconciliation_status_mark(p_entry.is_reconciled())
	);
	assert (num_columns() == 4);
	return;
}

void
ReconciliationEntryListCtrl::do_insert_non_date_columns()
{
	InsertColumn
	(	comment_col_num(),
		wxString("Memo"),
		wxLIST_FORMAT_LEFT
	);
	InsertColumn
	(	amount_col_num(),
		wxString("Amount"),
		wxLIST_FORMAT_RIGHT
	);
	InsertColumn
	(	reconciled_col_num(),
		wxString("Reconciled?"),
		wxLIST_FORMAT_RIGHT
	);
	assert (num_columns() == 4);
	return;
}

bool
ReconciliationEntryListCtrl::do_approve_entry(Entry const& p_entry) const
{
	if (p_entry.account() != account())
	{
		return false;
	}
	gregorian::date const date = p_entry.date();
	if (date > max_date())
	{
		return false;
	}
	if (date < min_date())
	{
		// We include unreconciled Entries even if they're prior to the
		// min_date(), providing they're not later than max_date().
		assert (date <= max_date());
		assert
		(	(date > database_connection().opening_balance_journal_date()) ||
			p_entry.is_reconciled()
		);
		return !p_entry.is_reconciled();
	}
	assert (p_entry.account() == account());
	assert (date >= min_date());
	assert (date <= max_date());
	return true;
}

int
ReconciliationEntryListCtrl::do_get_comment_col_num() const
{
	return comment_col_num();
}

int
ReconciliationEntryListCtrl::do_get_num_columns() const
{
	return anon_num_columns();
}

vector<SummaryDatum> const&
ReconciliationEntryListCtrl::do_get_summary_data() const
{
	assert (!m_summary_data->empty());
	m_summary_data->at(0).set_amount(m_closing_balance);
	m_summary_data->at(1).set_amount(m_reconciled_closing_balance);
	return *m_summary_data;
}

void
ReconciliationEntryListCtrl::do_initialize_summary_data()
{
	m_closing_balance = Decimal(0, account().commodity().precision());
	m_reconciled_closing_balance =
		Decimal(0, account().commodity().precision());
	assert (!m_summary_data);
	m_summary_data = new std::vector<SummaryDatum>;
	SummaryDatum a
	(	wxString("Closing balance"),
		m_closing_balance
	);
	m_summary_data->push_back(a);
	SummaryDatum b
	(	wxString("Reconciled balance"),
		m_reconciled_closing_balance
	);
	m_summary_data->push_back(b);
	return;
}

void
ReconciliationEntryListCtrl::do_process_candidate_entry_for_summary
(	Entry const& p_entry
)
{
	if (p_entry.account() != account())
	{
		return;
	}
	assert (p_entry.account() == account());
	jewel::Decimal const amount = p_entry.amount();
	if (p_entry.date() <= max_date())
	{
		m_closing_balance += amount;
		if (p_entry.is_reconciled()) m_reconciled_closing_balance += amount;
	}
	return;
}

void
ReconciliationEntryListCtrl::do_process_removal_for_summary(long p_row)
{
	Decimal const amount = amount_for_row(p_row);
	m_closing_balance -= amount;
#	ifndef NDEBUG
		wxListItem item;
		item.SetId(p_row);
		item.SetColumn(reconciled_col_num());
		GetItem(item);
		// TODO HIGH PRIORITY This assertion can fail!
		assert (item.GetText() == reconciliation_status_mark(false));
		// as we don't allow user to delete reconciled Entries.
		// so we don't need to adjust m_reconciled_closing_balance here.
#	endif
	return;
}

void
ReconciliationEntryListCtrl::on_item_right_click(wxListEvent& event)
{
	int const col = reconciled_col_num();
	Entry::Id const entry_id = event.GetData();
	long const pos = event.GetIndex();
	assert (FindItem(-1, entry_id) == pos);
	assert (entry_id >= 0);
	assert (GetItemData(pos) == static_cast<size_t>(entry_id));

	Entry entry(database_connection(), entry_id);
	bool const old_reconciliation_status = entry.is_reconciled();
	entry.set_whether_reconciled(!old_reconciliation_status);
	SetItem(pos, col, reconciliation_status_mark(entry.is_reconciled()));
	if (entry.is_reconciled())
	{
		m_reconciled_closing_balance += entry.amount();
	}
	else
	{
		m_reconciled_closing_balance -= entry.amount();
	}
	entry.save();
	ReconciliationListPanel* parent =
		dynamic_cast<ReconciliationListPanel*>(GetParent());
	assert (parent);
	parent->postconfigure_summary();
	return;
}

Decimal
ReconciliationEntryListCtrl::amount_for_row(long p_row) const
{
	wxListItem item;
	item.SetId(p_row);
	item.SetColumn(amount_col_num());
	GetItem(item);
	Decimal ret = wx_to_decimal(item.GetText(), locale());
	return ret;
}

boost::gregorian::date
ReconciliationEntryListCtrl::max_date() const
{
	return m_max_date;
}

}  // namespace gui
}  // namespace phatbooks
<|endoftext|>


<commit_before>// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "google/cloud/storage/benchmarks/aggregate_throughput_options.h"
#include "google/cloud/storage/benchmarks/benchmark_utils.h"
#include "google/cloud/storage/client.h"
#include "google/cloud/storage/grpc_plugin.h"
#include "google/cloud/grpc_options.h"
#include "google/cloud/internal/build_info.h"
#include "google/cloud/internal/getenv.h"
#include "google/cloud/internal/random.h"
#include "google/cloud/options.h"
#include "google/cloud/testing_util/command_line_parsing.h"
#include "google/cloud/testing_util/timer.h"
#include "absl/time/time.h"
#include <algorithm>
#include <atomic>
#include <future>
#include <iomanip>
#include <random>
#include <sstream>
#include <thread>
#include <vector>

namespace {
using ::google::cloud::storage_experimental::DefaultGrpcClient;
using ::google::cloud::testing_util::FormatSize;
using ::google::cloud::testing_util::Timer;
namespace gcs = google::cloud::storage;
namespace gcs_bm = google::cloud::storage_benchmarks;
using gcs_bm::AggregateThroughputOptions;
using gcs_bm::ApiName;

char const kDescription[] = R"""(A benchmark for aggregated throughput.

This program is used to evaluate the combined performance of GCS (the service)
and the C++ client library for GCS. It is not recommended as a benchmark for
the client library, as it introduces too many sources of performance variation.
It is useful when the C++ client library team collaborates with the GCS team to
measure changes in the service's performance.

The program measures the observed download throughput given a fixed "dataset",
that is, a collection of GCS objects contained in the same bucket, with a common
prefix. If needed, synthetic datasets can be created using the
`create_dataset.cc` in this directory. Given a dataset and some configuration
parameters the program will:

- Read the list of available objects in the dataset.
- Launch `thread-count` *download threads*, see below for their description.
- Every `reporting-interval` seconds the main thread will report the current
  wall time and the total number of bytes downloaded by all the download
  threads.
- After `running-time` seconds the main thread ask all other threads to
  terminate.
- The main thread then waits for all the "download threads", collects any
  metrics they choose to report, prints these metrics, and then exits.

While running, each download thread performs the following loop:

1) Create a gcs::Client object, see the `AggregateThroughputOptions` struct for
   details about how this client object can be configured.
2) Check if the program is shutting down. If so, just return some metrics.
3) Pick one of the objects in the dataset at random.
4) If so configured, pick a random portion of the object to download, otherwise
   simply download the full object.
5) Download the object, requesting `read-buffer-size` bytes from the client
   library at a time.
6) Once the requested buffer is received, increment a per-thread counter with
   the number of bytes received so far.
7) Keep track of the number of files downloaded, the gRPC peer used to download
   the object (if applicable) and other counters.
8) Go back to step (2) in this list.
)""";

google::cloud::StatusOr<AggregateThroughputOptions> ParseArgs(int argc,
                                                              char* argv[]);

struct ThreadConfig {
  std::atomic<std::int64_t> bytes_received{0};
  std::seed_seq::result_type seed;
};

using Counters = std::map<std::string, std::int64_t>;

Counters RunThread(gcs::Client client,
                   AggregateThroughputOptions const& options,
                   std::vector<gcs::ObjectMetadata> const& objects,
                   ThreadConfig& config);

template <typename Rep, typename Period>
std::string FormatBandwidthGbPerSecond(
    std::uintmax_t bytes, std::chrono::duration<Rep, Period> elapsed) {
  using ns = ::std::chrono::nanoseconds;
  auto const elapsed_ns = std::chrono::duration_cast<ns>(elapsed);
  if (elapsed_ns == ns(0)) return "NaN";

  auto const bandwidth =
      8 * static_cast<double>(bytes) / static_cast<double>(elapsed_ns.count());
  std::ostringstream os;
  os << std::fixed << std::setprecision(2) << bandwidth;
  return std::move(os).str();
}

template <typename Rep, typename Period>
std::string FormatBandwidthGiBPerSecond(
    std::uintmax_t bytes, std::chrono::duration<Rep, Period> elapsed) {
  using ::std::chrono::seconds;
  auto const elapsed_s = std::chrono::duration_cast<seconds>(elapsed);
  if (elapsed_s == seconds(0)) return "NaN";

  auto const bandwidth = static_cast<double>(bytes) / gcs_bm::kGiB /
                         static_cast<double>(elapsed_s.count());
  std::ostringstream os;
  os << std::fixed << std::setprecision(2) << bandwidth;
  return std::move(os).str();
}

}  // namespace

int main(int argc, char* argv[]) {
  auto options = ParseArgs(argc, argv);
  if (!options) {
    std::cerr << options.status() << "\n";
    return 1;
  }
  if (options->exit_after_parse) return 1;

  auto client = gcs::Client();
#if GOOGLE_CLOUD_CPP_STORAGE_HAVE_GRPC
  if (options->api == ApiName::kApiGrpc) {
    auto channels = options->grpc_channel_count;
    if (channels == 0) channels = (std::max)(options->thread_count / 4, 4);
    client = DefaultGrpcClient(
        google::cloud::Options{}.set<google::cloud::GrpcNumChannelsOption>(
            channels));
  }
#endif  // GOOGLE_CLOUD_CPP_STORAGE_HAVE_GRPC
  std::vector<gcs::ObjectMetadata> objects;
  std::uint64_t dataset_size = 0;
  for (auto& o : client.ListObjects(options->bucket_name,
                                    gcs::Prefix(options->object_prefix))) {
    if (!o) break;
    dataset_size += o->size();
    objects.push_back(*std::move(o));
  }
  if (objects.empty()) {
    std::cerr << "No objects found in bucket " << options->bucket_name
              << " starting with prefix " << options->object_prefix << "\n"
              << "Cannot run the benchmark with an empty dataset\n";
    return 1;
  }

  std::string notes = google::cloud::storage::version_string() + ";" +
                      google::cloud::internal::compiler() + ";" +
                      google::cloud::internal::compiler_flags();
  std::transform(notes.begin(), notes.end(), notes.begin(),
                 [](char c) { return c == '\n' ? ';' : c; });

  auto current_time = [] {
    auto constexpr kFormat = "%E4Y-%m-%dT%H:%M:%E*SZ";
    auto const t = absl::FromChrono(std::chrono::system_clock::now());
    return absl::FormatTime(kFormat, t, absl::UTCTimeZone());
  };

  std::cout << "# Start time: " << current_time()
            << "\n# Bucket Name: " << options->bucket_name
            << "\n# Object Prefix: " << options->object_prefix
            << "\n# Thread Count: " << options->thread_count
            << "\n# Reporting Interval: "
            << absl::FromChrono(options->reporting_interval)
            << "\n# Running Time: " << absl::FromChrono(options->running_time)
            << "\n# Read Size: " << options->read_size
            << "\n# Read Buffer Size: " << options->read_buffer_size
            << "\n# API: " << gcs_bm::ToString(options->api)
            << "\n# gRPC Channel Count: " << options->grpc_channel_count
            << "\n# Build Info: " << notes
            << "\n# Object Count: " << objects.size()
            << "\n# Dataset size: " << FormatSize(dataset_size) << std::endl;

  auto configs = [](std::size_t count) {
    std::random_device rd;
    std::vector<std::seed_seq::result_type> seeds(count);
    std::seed_seq({rd(), rd(), rd()}).generate(seeds.begin(), seeds.end());

    std::vector<ThreadConfig> config(seeds.size());
    for (std::size_t i = 0; i != config.size(); ++i) config[i].seed = seeds[i];
    return config;
  }(options->thread_count);

  std::cout << "CurrentTime,BytesReceived,CpuTimeMicroseconds\n"
            << current_time() << ",0,0\n";

  std::vector<std::future<Counters>> tasks(configs.size());
  std::transform(configs.begin(), configs.end(), tasks.begin(),
                 [&](ThreadConfig& c) {
                   return std::async(std::launch::async, RunThread, client,
                                     *options, objects, std::ref(c));
                 });

  auto accumulate_bytes_received = [&] {
    std::int64_t bytes_received = 0;
    for (auto const& t : configs) bytes_received += t.bytes_received.load();
    return bytes_received;
  };

  auto timer = Timer::PerProcess();
  auto usage = timer.Sample();
  while (usage.elapsed_time < options->running_time) {
    std::this_thread::sleep_for(options->reporting_interval);
    usage = timer.Sample();
    std::cout << current_time() << "," << accumulate_bytes_received() << ","
              << usage.cpu_time.count() << std::endl;
  }
  auto const bytes_received = accumulate_bytes_received();
  std::cout << "# Bytes Received: " << FormatSize(bytes_received)
            << "\n# Elapsed Time: " << absl::FromChrono(usage.elapsed_time)
            << "\n# Bandwidth: "
            << FormatBandwidthGbPerSecond(bytes_received, usage.elapsed_time)
            << "Gbit/s  "
            << FormatBandwidthGiBPerSecond(bytes_received, usage.elapsed_time)
            << "GiB/s\n";

  Counters accumulated;
  for (auto& t : tasks) {
    auto counters = t.get();
    for (auto const& kv : counters) accumulated[kv.first] += kv.second;
  }
  for (auto& kv : accumulated) {
    std::cout << "# counter " << kv.first << ": " << kv.second << "\n";
  }
  return 0;
}

namespace {

Counters RunThread(gcs::Client client,
                   AggregateThroughputOptions const& options,
                   std::vector<gcs::ObjectMetadata> const& objects,
                   ThreadConfig& config) {
  using clock = std::chrono::steady_clock;
  auto const deadline = clock::now() + options.running_time;
  auto generator = std::mt19937_64(config.seed);
  auto index =
      std::uniform_int_distribution<std::size_t>(0, objects.size() - 1);
  std::vector<char> buffer(options.read_buffer_size);
  auto const buffer_size = static_cast<std::streamsize>(buffer.size());
  Counters counters{{"download-count", 0}};
  // Using IfGenerationNotMatch(0) triggers JSON, as this feature is not
  // supported by XML.  Using IfGenerationNotMatch() -- without a value -- has
  // no effect.
  auto xml_hack = options.api == ApiName::kApiJson
                      ? gcs::IfGenerationNotMatch(0)
                      : gcs::IfGenerationNotMatch();
  while (clock::now() < deadline) {
    auto const& object = objects[index(generator)];
    auto const object_size = static_cast<std::int64_t>(object.size());
    auto range = gcs::ReadRange();
    if (options.read_size != 0 && options.read_size < object_size) {
      auto start = std::uniform_int_distribution<std::int64_t>(
          0, object_size - options.read_size);
      range = gcs::ReadRange(start(generator), options.read_size);
    }
    auto stream = client.ReadObject(object.bucket(), object.name(),
                                    gcs::Generation(object.generation()), range,
                                    xml_hack);
    while (stream.read(buffer.data(), buffer_size)) {
      config.bytes_received += stream.gcount();
    }
    stream.Close();
    ++counters["download-count"];
    auto peer = stream.headers().find(":grpc-context-peer");
    if (peer != stream.headers().end()) {
      ++counters[peer->first + "/" + peer->second];
    }
  }
  return counters;
}

using ::google::cloud::internal::GetEnv;

google::cloud::StatusOr<AggregateThroughputOptions> SelfTest(
    char const* argv0) {
  using google::cloud::internal::Sample;

  for (auto const& var : {"GOOGLE_CLOUD_CPP_STORAGE_TEST_BUCKET_NAME"}) {
    auto const value = GetEnv(var).value_or("");
    if (!value.empty()) continue;
    std::ostringstream os;
    os << "The environment variable " << var << " is not set or empty";
    return google::cloud::Status(google::cloud::StatusCode::kUnknown,
                                 std::move(os).str());
  }
  auto bucket_name =
      GetEnv("GOOGLE_CLOUD_CPP_STORAGE_TEST_BUCKET_NAME").value();
  auto client = gcs::Client{};
  (void)client.InsertObject(bucket_name,
                            "aggregate-throughput-benchmark/32KiB.bin",
                            std::string(32 * gcs_bm::kKiB, 'A'));
  return gcs_bm::ParseAggregateThroughputOptions(
      {
          argv0,
          "--bucket-name=" + bucket_name,
          "--object-prefix=aggregate-throughput-benchmark/",
          "--thread-count=1",
          "--reporting-interval=5s",
          "--running-time=15s",
          "--read-size=32KiB",
          "--read-buffer-size=16KiB",
          "--api=JSON",
      },
      kDescription);
}

google::cloud::StatusOr<AggregateThroughputOptions> ParseArgs(int argc,
                                                              char* argv[]) {
  bool auto_run =
      google::cloud::internal::GetEnv("GOOGLE_CLOUD_CPP_AUTO_RUN_EXAMPLES")
          .value_or("") == "yes";
  if (auto_run) return SelfTest(argv[0]);

  return gcs_bm::ParseAggregateThroughputOptions({argv, argv + argc},
                                                 kDescription);
}

}  // namespace
<commit_msg>feat(storage): more accurate aggregate bandwidth results (#6953)<commit_after>// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "google/cloud/storage/benchmarks/aggregate_throughput_options.h"
#include "google/cloud/storage/benchmarks/benchmark_utils.h"
#include "google/cloud/storage/client.h"
#include "google/cloud/storage/grpc_plugin.h"
#include "google/cloud/grpc_options.h"
#include "google/cloud/internal/build_info.h"
#include "google/cloud/internal/getenv.h"
#include "google/cloud/internal/random.h"
#include "google/cloud/options.h"
#include "google/cloud/testing_util/command_line_parsing.h"
#include "google/cloud/testing_util/timer.h"
#include "absl/time/time.h"
#include <algorithm>
#include <atomic>
#include <future>
#include <iomanip>
#include <random>
#include <sstream>
#include <thread>
#include <vector>

namespace {
using ::google::cloud::storage_experimental::DefaultGrpcClient;
using ::google::cloud::testing_util::FormatSize;
using ::google::cloud::testing_util::Timer;
namespace gcs = google::cloud::storage;
namespace gcs_bm = google::cloud::storage_benchmarks;
using gcs_bm::AggregateThroughputOptions;
using gcs_bm::ApiName;

char const kDescription[] = R"""(A benchmark for aggregated throughput.

This program is used to evaluate the combined performance of GCS (the service)
and the C++ client library for GCS. It is not recommended as a benchmark for
the client library, as it introduces too many sources of performance variation.
It is useful when the C++ client library team collaborates with the GCS team to
measure changes in the service's performance.

The program measures the observed download throughput given a fixed "dataset",
that is, a collection of GCS objects contained in the same bucket, with a common
prefix. If needed, synthetic datasets can be created using the
`create_dataset.cc` in this directory. Given a dataset and some configuration
parameters the program will:

- Read the list of available objects in the dataset.
- Launch `thread-count` *download threads*, see below for their description.
- Every `reporting-interval` seconds the main thread will report the current
  wall time and the total number of bytes downloaded by all the download
  threads.
- After `running-time` seconds the main thread ask all other threads to
  terminate.
- The main thread then waits for all the "download threads", collects any
  metrics they choose to report, prints these metrics, and then exits.

While running, each download thread performs the following loop:

1) Create a gcs::Client object, see the `AggregateThroughputOptions` struct for
   details about how this client object can be configured.
2) Check if the program is shutting down. If so, just return some metrics.
3) Pick one of the objects in the dataset at random.
4) If so configured, pick a random portion of the object to download, otherwise
   simply download the full object.
5) Download the object, requesting `read-buffer-size` bytes from the client
   library at a time.
6) Once the requested buffer is received, increment a per-thread counter with
   the number of bytes received so far.
7) Keep track of the number of files downloaded, the gRPC peer used to download
   the object (if applicable) and other counters.
8) Go back to step (2) in this list.
)""";

google::cloud::StatusOr<AggregateThroughputOptions> ParseArgs(int argc,
                                                              char* argv[]);

struct ThreadConfig {
  std::atomic<std::int64_t> bytes_received{0};
  std::seed_seq::result_type seed;
};

using Counters = std::map<std::string, std::int64_t>;

Counters RunThread(gcs::Client client,
                   AggregateThroughputOptions const& options,
                   std::vector<gcs::ObjectMetadata> const& objects,
                   ThreadConfig& config);

template <typename Rep, typename Period>
std::string FormatBandwidthGbPerSecond(
    std::uintmax_t bytes, std::chrono::duration<Rep, Period> elapsed) {
  using ns = ::std::chrono::nanoseconds;
  auto const elapsed_ns = std::chrono::duration_cast<ns>(elapsed);
  if (elapsed_ns == ns(0)) return "NaN";

  auto const bandwidth =
      8 * static_cast<double>(bytes) / static_cast<double>(elapsed_ns.count());
  std::ostringstream os;
  os << std::fixed << std::setprecision(2) << bandwidth;
  return std::move(os).str();
}

template <typename Rep, typename Period>
std::string FormatBandwidthGiBPerSecond(
    std::uintmax_t bytes, std::chrono::duration<Rep, Period> elapsed) {
  using ::std::chrono::seconds;
  auto const elapsed_s = std::chrono::duration_cast<seconds>(elapsed);
  if (elapsed_s == seconds(0)) return "NaN";

  auto const bandwidth = static_cast<double>(bytes) / gcs_bm::kGiB /
                         static_cast<double>(elapsed_s.count());
  std::ostringstream os;
  os << std::fixed << std::setprecision(2) << bandwidth;
  return std::move(os).str();
}

}  // namespace

int main(int argc, char* argv[]) {
  auto options = ParseArgs(argc, argv);
  if (!options) {
    std::cerr << options.status() << "\n";
    return 1;
  }
  if (options->exit_after_parse) return 1;

  auto client = gcs::Client();
#if GOOGLE_CLOUD_CPP_STORAGE_HAVE_GRPC
  if (options->api == ApiName::kApiGrpc) {
    auto channels = options->grpc_channel_count;
    if (channels == 0) channels = (std::max)(options->thread_count / 4, 4);
    client = DefaultGrpcClient(
        google::cloud::Options{}.set<google::cloud::GrpcNumChannelsOption>(
            channels));
  }
#endif  // GOOGLE_CLOUD_CPP_STORAGE_HAVE_GRPC
  std::vector<gcs::ObjectMetadata> objects;
  std::uint64_t dataset_size = 0;
  for (auto& o : client.ListObjects(options->bucket_name,
                                    gcs::Prefix(options->object_prefix))) {
    if (!o) break;
    dataset_size += o->size();
    objects.push_back(*std::move(o));
  }
  if (objects.empty()) {
    std::cerr << "No objects found in bucket " << options->bucket_name
              << " starting with prefix " << options->object_prefix << "\n"
              << "Cannot run the benchmark with an empty dataset\n";
    return 1;
  }

  std::string notes = google::cloud::storage::version_string() + ";" +
                      google::cloud::internal::compiler() + ";" +
                      google::cloud::internal::compiler_flags();
  std::transform(notes.begin(), notes.end(), notes.begin(),
                 [](char c) { return c == '\n' ? ';' : c; });

  auto current_time = [] {
    auto constexpr kFormat = "%E4Y-%m-%dT%H:%M:%E*SZ";
    auto const t = absl::FromChrono(std::chrono::system_clock::now());
    return absl::FormatTime(kFormat, t, absl::UTCTimeZone());
  };

  std::cout << "# Start time: " << current_time()
            << "\n# Bucket Name: " << options->bucket_name
            << "\n# Object Prefix: " << options->object_prefix
            << "\n# Thread Count: " << options->thread_count
            << "\n# Reporting Interval: "
            << absl::FromChrono(options->reporting_interval)
            << "\n# Running Time: " << absl::FromChrono(options->running_time)
            << "\n# Read Size: " << options->read_size
            << "\n# Read Buffer Size: " << options->read_buffer_size
            << "\n# API: " << gcs_bm::ToString(options->api)
            << "\n# gRPC Channel Count: " << options->grpc_channel_count
            << "\n# Build Info: " << notes
            << "\n# Object Count: " << objects.size()
            << "\n# Dataset size: " << FormatSize(dataset_size) << std::endl;

  auto configs = [](std::size_t count) {
    std::random_device rd;
    std::vector<std::seed_seq::result_type> seeds(count);
    std::seed_seq({rd(), rd(), rd()}).generate(seeds.begin(), seeds.end());

    std::vector<ThreadConfig> config(seeds.size());
    for (std::size_t i = 0; i != config.size(); ++i) config[i].seed = seeds[i];
    return config;
  }(options->thread_count);

  std::cout << "CurrentTime,BytesReceived,CpuTimeMicroseconds\n"
            << current_time() << ",0,0\n";

  std::vector<std::future<Counters>> tasks(configs.size());
  std::transform(configs.begin(), configs.end(), tasks.begin(),
                 [&](ThreadConfig& c) {
                   return std::async(std::launch::async, RunThread, client,
                                     *options, objects, std::ref(c));
                 });

  auto accumulate_bytes_received = [&] {
    std::int64_t bytes_received = 0;
    for (auto const& t : configs) bytes_received += t.bytes_received.load();
    return bytes_received;
  };

  using clock = std::chrono::steady_clock;
  auto deadline = clock::now() + options->running_time;
  auto timer = Timer::PerProcess();
  for (auto now = clock::now(); now < deadline; now = clock::now()) {
    std::this_thread::sleep_until(
        (std::min)(now + options->reporting_interval, deadline));
    auto const usage = timer.Sample();
    std::cout << current_time() << "," << accumulate_bytes_received() << ","
              << usage.cpu_time.count() << std::endl;
  }

  Counters accumulated;
  for (auto& t : tasks) {
    auto counters = t.get();
    for (auto const& kv : counters) accumulated[kv.first] += kv.second;
  }
  auto const usage = timer.Sample();
  auto const bytes_received = accumulate_bytes_received();
  std::cout << "# Bytes Received: " << FormatSize(bytes_received)
            << "\n# Elapsed Time: " << absl::FromChrono(usage.elapsed_time)
            << "\n# CPU Time: " << absl::FromChrono(usage.cpu_time)
            << "\n# Bandwidth: "
            << FormatBandwidthGbPerSecond(bytes_received, usage.elapsed_time)
            << "Gbit/s  "
            << FormatBandwidthGiBPerSecond(bytes_received, usage.elapsed_time)
            << "GiB/s\n";
  for (auto& kv : accumulated) {
    std::cout << "# counter " << kv.first << ": " << kv.second << "\n";
  }
  return 0;
}

namespace {

Counters RunThread(gcs::Client client,
                   AggregateThroughputOptions const& options,
                   std::vector<gcs::ObjectMetadata> const& objects,
                   ThreadConfig& config) {
  using clock = std::chrono::steady_clock;
  auto const deadline = clock::now() + options.running_time;
  auto generator = std::mt19937_64(config.seed);
  auto index =
      std::uniform_int_distribution<std::size_t>(0, objects.size() - 1);
  std::vector<char> buffer(options.read_buffer_size);
  auto const buffer_size = static_cast<std::streamsize>(buffer.size());
  Counters counters{{"download-count", 0}};
  // Using IfGenerationNotMatch(0) triggers JSON, as this feature is not
  // supported by XML.  Using IfGenerationNotMatch() -- without a value -- has
  // no effect.
  auto xml_hack = options.api == ApiName::kApiJson
                      ? gcs::IfGenerationNotMatch(0)
                      : gcs::IfGenerationNotMatch();
  while (clock::now() < deadline) {
    auto const& object = objects[index(generator)];
    auto const object_size = static_cast<std::int64_t>(object.size());
    auto range = gcs::ReadRange();
    if (options.read_size != 0 && options.read_size < object_size) {
      auto start = std::uniform_int_distribution<std::int64_t>(
          0, object_size - options.read_size);
      range = gcs::ReadRange(start(generator), options.read_size);
    }
    auto stream = client.ReadObject(object.bucket(), object.name(),
                                    gcs::Generation(object.generation()), range,
                                    xml_hack);
    while (stream.read(buffer.data(), buffer_size)) {
      config.bytes_received += stream.gcount();
    }
    stream.Close();
    ++counters["download-count"];
    auto peer = stream.headers().find(":grpc-context-peer");
    if (peer != stream.headers().end()) {
      ++counters[peer->first + "/" + peer->second];
    }
  }
  return counters;
}

using ::google::cloud::internal::GetEnv;

google::cloud::StatusOr<AggregateThroughputOptions> SelfTest(
    char const* argv0) {
  using google::cloud::internal::Sample;

  for (auto const& var : {"GOOGLE_CLOUD_CPP_STORAGE_TEST_BUCKET_NAME"}) {
    auto const value = GetEnv(var).value_or("");
    if (!value.empty()) continue;
    std::ostringstream os;
    os << "The environment variable " << var << " is not set or empty";
    return google::cloud::Status(google::cloud::StatusCode::kUnknown,
                                 std::move(os).str());
  }
  auto bucket_name =
      GetEnv("GOOGLE_CLOUD_CPP_STORAGE_TEST_BUCKET_NAME").value();
  auto client = gcs::Client{};
  (void)client.InsertObject(bucket_name,
                            "aggregate-throughput-benchmark/32KiB.bin",
                            std::string(32 * gcs_bm::kKiB, 'A'));
  return gcs_bm::ParseAggregateThroughputOptions(
      {
          argv0,
          "--bucket-name=" + bucket_name,
          "--object-prefix=aggregate-throughput-benchmark/",
          "--thread-count=1",
          "--reporting-interval=5s",
          "--running-time=15s",
          "--read-size=32KiB",
          "--read-buffer-size=16KiB",
          "--api=JSON",
      },
      kDescription);
}

google::cloud::StatusOr<AggregateThroughputOptions> ParseArgs(int argc,
                                                              char* argv[]) {
  bool auto_run =
      google::cloud::internal::GetEnv("GOOGLE_CLOUD_CPP_AUTO_RUN_EXAMPLES")
          .value_or("") == "yes";
  if (auto_run) return SelfTest(argv[0]);

  return gcs_bm::ParseAggregateThroughputOptions({argv, argv + argc},
                                                 kDescription);
}

}  // namespace
<|endoftext|>