<commit_before>// ------------------------------------------------------------------------
// Pion is a development platform for building Reactors that process Events
// ------------------------------------------------------------------------
// Copyright (C) 2007-2008 Atomic Labs, Inc.  (http://www.atomiclabs.com)
//
// Pion is free software: you can redistribute it and/or modify it under the
// terms of the GNU Affero General Public License as published by the Free
// Software Foundation, either version 3 of the License, or (at your option)
// any later version.
//
// Pion 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 Affero General Public License for
// more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with Pion.  If not, see <http://www.gnu.org/licenses/>.
//

#include <fstream>
#include <boost/filesystem/operations.hpp>
#include <boost/date_time/posix_time/posix_time_duration.hpp>
#include <pion/PionScheduler.hpp>
#include <pion/platform/ConfigManager.hpp>
#include <pion/platform/CodecFactory.hpp>
#include <pion/platform/ReactionEngine.hpp>
#include "LogInputReactor.hpp"

using namespace pion::platform;
namespace bfs = boost::filesystem;

namespace pion {		// begin namespace pion
namespace plugins {		// begin namespace plugins

// static members of LogInputReactor

const boost::uint32_t		LogInputReactor::DEFAULT_FREQUENCY = 1;
const std::string			LogInputReactor::CODEC_ELEMENT_NAME = "Codec";
const std::string			LogInputReactor::DIRECTORY_ELEMENT_NAME = "Directory";
const std::string			LogInputReactor::FILENAME_ELEMENT_NAME = "Filename";
const std::string			LogInputReactor::JUST_ONE_ELEMENT_NAME = "JustOne";
const std::string			LogInputReactor::FREQUENCY_ELEMENT_NAME = "Frequency";

// LogInputReactor member functions

void LogInputReactor::setConfig(const Vocabulary& v, const xmlNodePtr config_ptr)
{
	// first set config options for the Reactor base class
	boost::unique_lock<boost::mutex> reactor_lock(m_mutex);
	Reactor::setConfig(v, config_ptr);

	// get the Codec that the Reactor should use
	if (! ConfigManager::getConfigOption(CODEC_ELEMENT_NAME, m_codec_id, config_ptr))
		throw EmptyCodecException(getId());
	m_codec_ptr = getCodecFactory().getCodec(m_codec_id);
	PION_ASSERT(m_codec_ptr);

	// get the directory where the Reactor will look for new log files
	if (! ConfigManager::getConfigOption(DIRECTORY_ELEMENT_NAME, m_log_directory, config_ptr))
		throw EmptyDirectoryException(getId());

	// resolve paths relative to the ReactionEngine's config file location
	m_log_directory = getReactionEngine().resolveRelativePath(m_log_directory);

	// make sure that the directory exists
	if (! boost::filesystem::exists(m_log_directory) )
		throw DirectoryNotFoundException(m_log_directory);
	if (! boost::filesystem::is_directory(m_log_directory) )
		throw NotADirectoryException(m_log_directory);

	// get the filename regex to use for finding log files
	std::string filename_str;
	if (! ConfigManager::getConfigOption(FILENAME_ELEMENT_NAME, filename_str, config_ptr))
		throw EmptyFilenameException(getId());
	m_log_regex = filename_str;

	// check if the the Reactor should only read the first Event & duplicate it (for testing)
	m_just_one = false;
	std::string just_one_option;
	if (ConfigManager::getConfigOption(JUST_ONE_ELEMENT_NAME, just_one_option,
									   config_ptr))
	{
		if (just_one_option == "true")
			m_just_one = true;
	}

	// get the frequency to check for new logs (if defined)
	std::string frequency_str;
	if (ConfigManager::getConfigOption(FREQUENCY_ELEMENT_NAME, frequency_str, config_ptr)) {
		const boost::uint32_t frequency_value = boost::lexical_cast<boost::uint32_t>(frequency_str);
		if (frequency_value <= 0)
			throw BadFrequencyException(getId());
		m_frequency = frequency_value;
	} else {
		m_frequency = DEFAULT_FREQUENCY;
	}
}

void LogInputReactor::updateVocabulary(const Vocabulary& v)
{
	// first update anything in the Reactor base class that might be needed
	boost::unique_lock<boost::mutex> reactor_lock(m_mutex);
	Reactor::updateVocabulary(v);
	if (m_codec_ptr)
		m_codec_ptr->updateVocabulary(v);
}

void LogInputReactor::updateCodecs(void)
{
	// check if the codec was deleted (if so, stop now!)
	if (! getCodecFactory().hasPlugin(m_codec_id)) {
		stop();
	    boost::unique_lock<boost::mutex> reactor_lock(m_mutex);
		m_codec_ptr.reset();
	} else {
		// update the codec pointer
    	boost::unique_lock<boost::mutex> reactor_lock(m_mutex);
		m_codec_ptr = getCodecFactory().getCodec(m_codec_id);
	}
}

void LogInputReactor::operator()(const EventPtr& e)
{
	if (isRunning()) {
		boost::mutex::scoped_lock reactor_lock(m_mutex);
		incrementEventsIn();
		deliverEvent(e);
	}
}

void LogInputReactor::start(void)
{
	boost::unique_lock<boost::mutex> reactor_lock(m_mutex);
	if (! m_is_running) {
		// schedule a check for new log files
		scheduleLogFileCheck(0);
		m_is_running = true;
	}
}

void LogInputReactor::stop(void)
{
	boost::unique_lock<boost::mutex> reactor_lock(m_mutex);
	if (m_is_running) {
		// set flag to notify reader thread to shutdown
		PION_LOG_DEBUG(m_logger, "Stopping input log thread: " << getId());
		m_is_running = false;
		m_timer_ptr.reset();
	}
}

void LogInputReactor::scheduleLogFileCheck(boost::uint32_t seconds)
{
	if (seconds == 0) {
		getScheduler().post(boost::bind(&LogInputReactor::checkForLogFiles, this));
	} else {
		if (! m_timer_ptr)
			m_timer_ptr.reset(new boost::asio::deadline_timer(getScheduler().getIOService()));
		m_timer_ptr->expires_from_now(boost::posix_time::seconds(seconds));
		m_timer_ptr->async_wait(boost::bind(&LogInputReactor::checkForLogFiles, this));
	}
}

void LogInputReactor::checkForLogFiles(void)
{
	// make sure that the reactor is still running
	boost::unique_lock<boost::mutex> reactor_lock(m_mutex);
	if (! isRunning())
		return;

	PION_LOG_DEBUG(m_logger, "Checking for new log files in " << m_log_directory);

	// get the current logs located in the log directory
	LogFileCollection current_logs;
	getLogFilesInLogDirectory(current_logs);

	// remove logs from the consumed collection that are no longer there
	LogFileCollection::iterator temp_itr;
	LogFileCollection::iterator log_itr = m_logs_consumed.begin();
	while (log_itr != m_logs_consumed.end()) {
		temp_itr = log_itr++;
		if (current_logs.find(*temp_itr) == current_logs.end())
			m_logs_consumed.erase(temp_itr);
	}

	// check for an existing log that has not yet been consumed
	for (log_itr = current_logs.begin(); log_itr != current_logs.end(); ++log_itr) {
		if (m_logs_consumed.find(*log_itr) == m_logs_consumed.end())
			break;
	}

	if (log_itr == current_logs.end()) {
		// no new logs to consume

		// sleep until it is time to check again
		PION_LOG_DEBUG(m_logger, "No new logs (sleeping for " << m_frequency
					   << " seconds): " << m_log_directory);
		scheduleLogFileCheck(m_frequency);

	} else {
		// found a new log to consume
		m_logs_consumed.insert(*log_itr);

		// re-calculate the full path to the file
		bfs::path full_path(m_log_directory);
		full_path /= *log_itr;
		m_log_file = full_path.file_string();

		PION_LOG_DEBUG(m_logger, "Found a new log file to consume: " << m_log_file);
		scheduleReadFromLog(true);
	}
}

void LogInputReactor::readFromLog(bool use_one_thread)
{
	// make sure that the reactor is still running
	if (! isRunning())
		return;

	try {
		// open up the log file for reading (if not open already)
		if (! m_log_stream.is_open()) {
			m_log_stream.open(m_log_file.c_str(), std::ios::in | std::ios::binary);
			if (! m_log_stream.is_open())
				throw OpenLogException(m_log_file);
			else if (m_log_stream.eof())
				throw EmptyLogException(m_log_file);
		}

		EventPtr event_ptr;
		do {
			// read an Event from the log file (convert into an Event using the Codec)
        	boost::unique_lock<boost::mutex> reactor_lock(m_mutex);
			event_ptr = EventFactory::create(m_codec_ptr->getEventType());
			if (! m_codec_ptr->read(m_log_stream, *event_ptr))
				throw ReadEventException(m_log_file);
			// done with the codec (which is all that needs protecting here)
			//reactor_lock.unlock();

			// check if only Event should be read
			if (m_just_one) {
				PION_LOG_DEBUG(m_logger, "JustOne: generating lots of event copies for testing");
				m_log_stream.close();
				m_log_stream.clear();

				// just duplicate the event repeatedly until the Reactor is stopped
				EventPtr original_event_ptr(event_ptr);
				while (isRunning()) {
					// duplicate the original event
					event_ptr = EventFactory::create(m_codec_ptr->getEventType());
					*event_ptr += *original_event_ptr;
					// deliver the Event to connected Reactors
					incrementEventsIn();
					deliverEvent(event_ptr);
				}
				break;
			}

			// check for end of file
			if (m_log_stream.eof()) {
				// all done with this log file
				PION_LOG_DEBUG(m_logger, "Finished consuming log file: " << m_log_file);
				m_log_stream.close();
				m_log_stream.clear();

				// check for more logs
				scheduleLogFileCheck(0);

				// deliver the Event to connected Reactors
				incrementEventsIn();
				deliverEvent(event_ptr);
				break;
			} else {
				// more available: schedule another read operation?
				if (! use_one_thread) {
					scheduleReadFromLog(false);

					// deliver the Event to connected Reactors
					incrementEventsIn();
					deliverEvent(event_ptr);
					break;
				}

				// deliver the Event to connected Reactors
				incrementEventsIn();
				deliverEvent(event_ptr);
			}

		} while (isRunning());

	} catch (std::exception& e) {
		PION_LOG_ERROR(m_logger, e.what());
		m_log_stream.close();
		m_log_stream.clear();
		scheduleLogFileCheck(0);
	}
}

void LogInputReactor::getLogFilesInLogDirectory(LogFileCollection& files)
{
	bfs::path dir_path(m_log_directory);
	for (bfs::directory_iterator itr(dir_path); itr!=bfs::directory_iterator(); ++itr) {
		if (bfs::is_regular(itr->status())) {
			const std::string filename(itr->path().leaf());
			if (boost::regex_search(filename, m_log_regex))
				files.insert(filename);
		}
	}
}

}	// end namespace plugins
}	// end namespace pion

/// creates new LogInputReactor objects
extern "C" PION_PLUGIN_API pion::platform::Reactor *pion_create_LogInputReactor(void) {
	return new pion::plugins::LogInputReactor();
}

/// destroys LogInputReactor objects
extern "C" PION_PLUGIN_API void pion_destroy_LogInputReactor(pion::plugins::LogInputReactor *reactor_ptr) {
	delete reactor_ptr;
}
<commit_msg>Minor formatting cleanup<commit_after>// ------------------------------------------------------------------------
// Pion is a development platform for building Reactors that process Events
// ------------------------------------------------------------------------
// Copyright (C) 2007-2008 Atomic Labs, Inc.  (http://www.atomiclabs.com)
//
// Pion is free software: you can redistribute it and/or modify it under the
// terms of the GNU Affero General Public License as published by the Free
// Software Foundation, either version 3 of the License, or (at your option)
// any later version.
//
// Pion 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 Affero General Public License for
// more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with Pion.  If not, see <http://www.gnu.org/licenses/>.
//

#include <fstream>
#include <boost/filesystem/operations.hpp>
#include <boost/date_time/posix_time/posix_time_duration.hpp>
#include <pion/PionScheduler.hpp>
#include <pion/platform/ConfigManager.hpp>
#include <pion/platform/CodecFactory.hpp>
#include <pion/platform/ReactionEngine.hpp>
#include "LogInputReactor.hpp"

using namespace pion::platform;
namespace bfs = boost::filesystem;

namespace pion {		// begin namespace pion
namespace plugins {		// begin namespace plugins

// static members of LogInputReactor

const boost::uint32_t		LogInputReactor::DEFAULT_FREQUENCY = 1;
const std::string			LogInputReactor::CODEC_ELEMENT_NAME = "Codec";
const std::string			LogInputReactor::DIRECTORY_ELEMENT_NAME = "Directory";
const std::string			LogInputReactor::FILENAME_ELEMENT_NAME = "Filename";
const std::string			LogInputReactor::JUST_ONE_ELEMENT_NAME = "JustOne";
const std::string			LogInputReactor::FREQUENCY_ELEMENT_NAME = "Frequency";

// LogInputReactor member functions

void LogInputReactor::setConfig(const Vocabulary& v, const xmlNodePtr config_ptr)
{
	// first set config options for the Reactor base class
	boost::unique_lock<boost::mutex> reactor_lock(m_mutex);
	Reactor::setConfig(v, config_ptr);

	// get the Codec that the Reactor should use
	if (! ConfigManager::getConfigOption(CODEC_ELEMENT_NAME, m_codec_id, config_ptr))
		throw EmptyCodecException(getId());
	m_codec_ptr = getCodecFactory().getCodec(m_codec_id);
	PION_ASSERT(m_codec_ptr);

	// get the directory where the Reactor will look for new log files
	if (! ConfigManager::getConfigOption(DIRECTORY_ELEMENT_NAME, m_log_directory, config_ptr))
		throw EmptyDirectoryException(getId());

	// resolve paths relative to the ReactionEngine's config file location
	m_log_directory = getReactionEngine().resolveRelativePath(m_log_directory);

	// make sure that the directory exists
	if (! boost::filesystem::exists(m_log_directory) )
		throw DirectoryNotFoundException(m_log_directory);
	if (! boost::filesystem::is_directory(m_log_directory) )
		throw NotADirectoryException(m_log_directory);

	// get the filename regex to use for finding log files
	std::string filename_str;
	if (! ConfigManager::getConfigOption(FILENAME_ELEMENT_NAME, filename_str, config_ptr))
		throw EmptyFilenameException(getId());
	m_log_regex = filename_str;

	// check if the the Reactor should only read the first Event & duplicate it (for testing)
	m_just_one = false;
	std::string just_one_option;
	if (ConfigManager::getConfigOption(JUST_ONE_ELEMENT_NAME, just_one_option,
									   config_ptr))
	{
		if (just_one_option == "true")
			m_just_one = true;
	}

	// get the frequency to check for new logs (if defined)
	std::string frequency_str;
	if (ConfigManager::getConfigOption(FREQUENCY_ELEMENT_NAME, frequency_str, config_ptr)) {
		const boost::uint32_t frequency_value = boost::lexical_cast<boost::uint32_t>(frequency_str);
		if (frequency_value <= 0)
			throw BadFrequencyException(getId());
		m_frequency = frequency_value;
	} else {
		m_frequency = DEFAULT_FREQUENCY;
	}
}

void LogInputReactor::updateVocabulary(const Vocabulary& v)
{
	// first update anything in the Reactor base class that might be needed
	boost::unique_lock<boost::mutex> reactor_lock(m_mutex);
	Reactor::updateVocabulary(v);
	if (m_codec_ptr)
		m_codec_ptr->updateVocabulary(v);
}

void LogInputReactor::updateCodecs(void)
{
	// check if the codec was deleted (if so, stop now!)
	if (! getCodecFactory().hasPlugin(m_codec_id)) {
		stop();
	    boost::unique_lock<boost::mutex> reactor_lock(m_mutex);
		m_codec_ptr.reset();
	} else {
		// update the codec pointer
    	boost::unique_lock<boost::mutex> reactor_lock(m_mutex);
		m_codec_ptr = getCodecFactory().getCodec(m_codec_id);
	}
}

void LogInputReactor::operator()(const EventPtr& e)
{
	if (isRunning()) {
		boost::mutex::scoped_lock reactor_lock(m_mutex);
		incrementEventsIn();
		deliverEvent(e);
	}
}

void LogInputReactor::start(void)
{
	boost::unique_lock<boost::mutex> reactor_lock(m_mutex);
	if (! m_is_running) {
		// schedule a check for new log files
		scheduleLogFileCheck(0);
		m_is_running = true;
	}
}

void LogInputReactor::stop(void)
{
	boost::unique_lock<boost::mutex> reactor_lock(m_mutex);
	if (m_is_running) {
		// set flag to notify reader thread to shutdown
		PION_LOG_DEBUG(m_logger, "Stopping input log thread: " << getId());
		m_is_running = false;
		m_timer_ptr.reset();
	}
}

void LogInputReactor::scheduleLogFileCheck(boost::uint32_t seconds)
{
	if (seconds == 0) {
		getScheduler().post(boost::bind(&LogInputReactor::checkForLogFiles, this));
	} else {
		if (! m_timer_ptr)
			m_timer_ptr.reset(new boost::asio::deadline_timer(getScheduler().getIOService()));
		m_timer_ptr->expires_from_now(boost::posix_time::seconds(seconds));
		m_timer_ptr->async_wait(boost::bind(&LogInputReactor::checkForLogFiles, this));
	}
}

void LogInputReactor::checkForLogFiles(void)
{
	// make sure that the reactor is still running
	boost::unique_lock<boost::mutex> reactor_lock(m_mutex);
	if (! isRunning())
		return;

	PION_LOG_DEBUG(m_logger, "Checking for new log files in " << m_log_directory);

	// get the current logs located in the log directory
	LogFileCollection current_logs;
	getLogFilesInLogDirectory(current_logs);

	// remove logs from the consumed collection that are no longer there
	LogFileCollection::iterator temp_itr;
	LogFileCollection::iterator log_itr = m_logs_consumed.begin();
	while (log_itr != m_logs_consumed.end()) {
		temp_itr = log_itr++;
		if (current_logs.find(*temp_itr) == current_logs.end())
			m_logs_consumed.erase(temp_itr);
	}

	// check for an existing log that has not yet been consumed
	for (log_itr = current_logs.begin(); log_itr != current_logs.end(); ++log_itr) {
		if (m_logs_consumed.find(*log_itr) == m_logs_consumed.end())
			break;
	}

	if (log_itr == current_logs.end()) {
		// no new logs to consume

		// sleep until it is time to check again
		PION_LOG_DEBUG(m_logger, "No new logs (sleeping for " << m_frequency
					   << " seconds): " << m_log_directory);
		scheduleLogFileCheck(m_frequency);

	} else {
		// found a new log to consume
		m_logs_consumed.insert(*log_itr);

		// re-calculate the full path to the file
		bfs::path full_path(m_log_directory);
		full_path /= *log_itr;
		m_log_file = full_path.file_string();

		PION_LOG_DEBUG(m_logger, "Found a new log file to consume: " << m_log_file);
		scheduleReadFromLog(true);
	}
}

void LogInputReactor::readFromLog(bool use_one_thread)
{
	// make sure that the reactor is still running
	if (! isRunning())
		return;

	try {
		// open up the log file for reading (if not open already)
		if (! m_log_stream.is_open()) {
			m_log_stream.open(m_log_file.c_str(), std::ios::in | std::ios::binary);
			if (! m_log_stream.is_open())
				throw OpenLogException(m_log_file);
			else if (m_log_stream.eof())
				throw EmptyLogException(m_log_file);
		}

		EventPtr event_ptr;
		do {
			// read an Event from the log file (convert into an Event using the Codec)
			boost::unique_lock<boost::mutex> reactor_lock(m_mutex);
			event_ptr = EventFactory::create(m_codec_ptr->getEventType());
			if (! m_codec_ptr->read(m_log_stream, *event_ptr))
				throw ReadEventException(m_log_file);

			// check if only Event should be read
			if (m_just_one) {
				PION_LOG_DEBUG(m_logger, "JustOne: generating lots of event copies for testing");
				m_log_stream.close();
				m_log_stream.clear();

				// just duplicate the event repeatedly until the Reactor is stopped
				EventPtr original_event_ptr(event_ptr);
				while (isRunning()) {
					// duplicate the original event
					event_ptr = EventFactory::create(m_codec_ptr->getEventType());
					*event_ptr += *original_event_ptr;
					// deliver the Event to connected Reactors
					incrementEventsIn();
					deliverEvent(event_ptr);
				}
				break;
			}

			// check for end of file
			if (m_log_stream.eof()) {
				// all done with this log file
				PION_LOG_DEBUG(m_logger, "Finished consuming log file: " << m_log_file);
				m_log_stream.close();
				m_log_stream.clear();

				// check for more logs
				scheduleLogFileCheck(0);

				// deliver the Event to connected Reactors
				incrementEventsIn();
				deliverEvent(event_ptr);
				break;
			} else {
				// more available: schedule another read operation?
				if (! use_one_thread) {
					scheduleReadFromLog(false);

					// deliver the Event to connected Reactors
					incrementEventsIn();
					deliverEvent(event_ptr);
					break;
				}

				// deliver the Event to connected Reactors
				incrementEventsIn();
				deliverEvent(event_ptr);
			}

		} while (isRunning());

	} catch (std::exception& e) {
		PION_LOG_ERROR(m_logger, e.what());
		m_log_stream.close();
		m_log_stream.clear();
		scheduleLogFileCheck(0);
	}
}

void LogInputReactor::getLogFilesInLogDirectory(LogFileCollection& files)
{
	bfs::path dir_path(m_log_directory);
	for (bfs::directory_iterator itr(dir_path); itr!=bfs::directory_iterator(); ++itr) {
		if (bfs::is_regular(itr->status())) {
			const std::string filename(itr->path().leaf());
			if (boost::regex_search(filename, m_log_regex))
				files.insert(filename);
		}
	}
}

}	// end namespace plugins
}	// end namespace pion

/// creates new LogInputReactor objects
extern "C" PION_PLUGIN_API pion::platform::Reactor *pion_create_LogInputReactor(void) {
	return new pion::plugins::LogInputReactor();
}

/// destroys LogInputReactor objects
extern "C" PION_PLUGIN_API void pion_destroy_LogInputReactor(pion::plugins::LogInputReactor *reactor_ptr) {
	delete reactor_ptr;
}
<|endoftext|>


<commit_before>/***********************************************************************
OpenSync Plugin for KDE 3.x
Copyright (C) 2004 Stewart Heitmann <sheitmann@users.sourceforge.net>

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License version 2 as
published by the Free Software Foundation;

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS.
IN NO EVENT SHALL THE COPYRIGHT HOLDER(S) AND AUTHOR(S) BE LIABLE FOR ANY
CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

ALL LIABILITY, INCLUDING LIABILITY FOR INFRINGEMENT OF ANY PATENTS,
COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS, RELATING TO USE OF THIS
SOFTWARE IS DISCLAIMED.
*************************************************************************/
/*
 * 03 Nov 2004 - Eduardo Pereira Habkost <ehabkost@conectiva.com.br>
 * - Ported to OpenSync plugin interface
 */

extern "C"
{
#include <opensync/opensync.h>
#include "kaddrbook.h"
}

#include <kabc/stdaddressbook.h>
#include <kabc/vcardconverter.h>
#include <kcmdlineargs.h>
#include <kapplication.h>
#include <klocale.h>
#include <qsignal.h>
#include <qfile.h>

static
void unfold_vcard(char *vcard, size_t *size)
{
    char* in  = vcard;
    char* out = vcard;
    char *end = vcard + *size;
    while ( in < end)
    {
        /* remove any occurrences of "=[CR][LF]"                */
        /* these denote folded line markers in VCARD format.    */
        /* Dont know why, but Evolution uses the leading "="    */
        /* character to (presumably) denote a control sequence. */
        /* This is not quite how I interpret the VCARD RFC2426  */
        /* spec (section 2.6 line delimiting and folding).      */
        /* This seems to work though, so thats the main thing!  */
        if (in[0]=='=' && in[1]==13 && in[2]==10)
            in+=3;
        else
            *out++ = *in++;
    }
    *size = out - vcard;
}

class kaddrbook
{
    private:
        KABC::AddressBook* addressbookptr;
        KABC::Ticket* addressbookticket;
        QDateTime syncdate, newsyncdate;

        OSyncMember *member;
        OSyncHashTable *hashtable;

    public:
        kaddrbook(OSyncMember *memb)
            :member(memb)
        {
            //printf("kdepim_plugin: %s(%s)\n", __FUNCTION__);

            //get a handle to the standard KDE addressbook
            addressbookptr = KABC::StdAddressBook::self();

            //ensure a NULL Ticket ptr
            addressbookticket=NULL;

            hashtable = osync_hashtable_new();
            osync_hashtable_load(hashtable, member);

        };

        int get_changes(OSyncContext *ctx)
        {
            //printf("kdepim_plugin: kaddrbook::%s(newdbs=%d)\n", __FUNCTION__, newdbs);

            const char* multisync_debug = getenv("MULTISYNC_DEBUG");

            //remember when we started this current sync
            newsyncdate = QDateTime::currentDateTime();

            // We must reload the KDE addressbook in order to retrieve the latest changes.
            if (!addressbookptr->load())
            {
                if (multisync_debug)
                    printf("kdepim_plugin: couldnt reload KDE addressbook, aborting sync.\n");
                return -1;
            }
            if (multisync_debug)
                printf("kdepim_plugin: KDE addressbook reloaded OK.\n");

            //Lock the addressbook
            addressbookticket = addressbookptr->requestSaveTicket();
            if (!addressbookticket)
            {
                if (multisync_debug)
                    printf("kdepim_plugin: couldnt lock KDE addressbook, aborting sync.\n");
                return -1;
            }
            if (multisync_debug)
                printf("kdepim_plugin: KDE addressbook locked OK.\n");

            //printf("%s: %s : plugin UID list has %d entries\n", __FILE__, __FUNCTION__, uidlist.count());

            //Check the entries of the KDE addressbook against the last entries seen by the sync-engine
            for (KABC::AddressBook::Iterator it=addressbookptr->begin(); it!=addressbookptr->end(); it++ ) {
                //Get the revision date of the KDE addressbook entry.
                //Regard entries with invalid revision dates as having just been changed.
                QDateTime revdate = it->revision();
                if (!revdate.isValid())
                {
                    revdate = newsyncdate;      //use date of this sync as the revision date.
                    it->setRevision(revdate);   //update the Addressbook entry for future reference.
                }

                // gmalloc a changed_object for this phonebook entry
                //FIXME: deallocate it somewhere
                OSyncChange *chg= osync_change_new();

                QCString hash(revdate.toString());

                osync_change_set_hash(chg, hash);
                osync_change_set_uid(chg, it->uid().latin1());

                // Convert the VCARD data into a string
                KABC::VCardConverter converter;
                QString card = converter.createVCard(*it);
                QString data(card.latin1());
                //FIXME: deallocate data somewhere
                osync_change_set_data(chg, strdup(data), data.length(), 1);

                // set the remaining fields
                osync_change_set_objtype_string(chg, "contact");
                osync_change_set_objformat_string(chg, "vcard");
                osync_change_set_hash(chg, hash.data());
                /*FIXME: slowsync */
                if (osync_hashtable_detect_change(hashtable, chg, 0)) {
                    osync_context_report_change(ctx, chg);
                    osync_hashtable_update_hash(hashtable, chg);
                }

                //Append the changed_object to the return list
                osync_context_report_change(ctx, chg);
            }

            osync_hashtable_report_deleted(hashtable, ctx, 0);

            return 0;
        }

        int modify(OSyncChange *chg)
        {
            //printf("kdepim_plugin: kaddrbook::%s()\n",__FUNCTION__);

            int result = 0;
            const char* multisync_debug = getenv("MULTISYNC_DEBUG");

            // Ensure we still have a lock on the KDE addressbook (we ought to)
            if (addressbookticket==NULL)
            {
                //This should never happen, but just in case....
                if (multisync_debug)
                    printf("kdepim_plugin: lock on KDE addressbook was lost, aborting sync.\n");
                return -1;
            }

            KABC::VCardConverter converter;

            /* allocate and initialize return struct for this change entry */
            //FIXME: check how to return errors safely
            //modify_result = (syncobj_modify_result*)g_malloc0(sizeof(syncobj_modify_result));
            //modify_result->result = SYNC_MSG_MODIFYERROR;
            //modify_result->returnuid = NULL;

            OSyncObjType *type = osync_change_get_objtype(chg);
            // Do database modifications according to object type
            if (!strcmp(osync_objtype_get_name(type), "contact"))
            {

                OSyncChangeType chtype = osync_change_get_changetype(chg);
                char *uid = osync_change_get_uid(chg);
                /* treat modified objects without UIDs as if they were newly added objects */
                if (chtype == CHANGE_MODIFIED && !uid)
                    chtype = CHANGE_ADDED;

                // convert VCARD string from obj->comp into an Addresse object.
                char *data;
                size_t data_size;
                data = (char*)osync_change_get_data(chg);
                data_size = osync_change_get_datasize(chg);

                switch(chtype)
                {
                    case CHANGE_MODIFIED:
                    {
                        unfold_vcard(data, &data_size);

                        KABC::Addressee addressee = converter.parseVCard(QString::fromLatin1(data, data_size));

                        // ensure it has the correct UID
                        addressee.setUid(QString(uid));

                        // replace the current addressbook entry (if any) with the new one
                        addressbookptr->insertAddressee(addressee);

                        if (multisync_debug)
                        {
                            printf("kdepim_plugin: KDE ADDRESSBOOK ENTRY UPDATED (UID=%s)\n", uid);
                        }
                        result = 0;
                        break;
                    }

                    case CHANGE_ADDED:
                    {
                        // convert VCARD string from obj->comp into an Addresse object
                        // KABC::VCardConverter doesnt do VCARD unfolding so we must do it ourselves first.
                        unfold_vcard(data, &data_size);
                        KABC::Addressee addressee = converter.parseVCard(QString::fromLatin1(data, data_size));

                        // ensure it has a NULL UID
                        addressee.setUid(QString(NULL));

                        // add the new address to the addressbook
                        addressbookptr->insertAddressee(addressee);

                        if (multisync_debug)
                        {
                            printf("kdepim_plugin: KDE ADDRESSBOOK ENTRY ADDED (UID=%s)\n", addressee.uid().latin1());
                        }

                        // return the UID of the new entry along with the result
                        osync_change_set_uid(chg, addressee.uid().latin1());
                        result = 0;
                        break;
                    }

                    case CHANGE_DELETED:
                    {
                        if (uid==NULL)
                        {
                            result = 1;
                            break;
                        }

                        //find addressbook entry with matching UID and delete it
                        KABC::Addressee addressee = addressbookptr->findByUid(QString(uid));
                        if(!addressee.isEmpty())
                           addressbookptr->removeAddressee(addressee);

                        if (multisync_debug)
                            printf("kdepim_plugin: KDE ADDRESSBOOK ENTRY DELETED (UID=%s)\n", uid);

                        result = 0;
                        break;
                    }
                    default:
                        result = 0;
                }
            }
            //FIXME: handle unsupported objtypes

            return result;
        }

       void sync_done(bool success)
        {
            //printf("kdepim_plugin: kaddrbook::%s(%d)\n", __FUNCTION__, success);
            const char* multisync_debug = getenv("MULTISYNC_DEBUG");

            if (!addressbookticket)
            {
                //This should never happen, but just in case....
                if (multisync_debug)
                    printf("kdepim_plugin: lock on KDE addressbook was lost, aborting sync.\n");

                return;
            }

            if (success)
            {
                // Save and unlock the KDE addressbook
                addressbookptr->save(addressbookticket);
                if (multisync_debug)
                    printf("kdepim_plugin: KDE addressbook saved and unlocked.\n");

                //update the syncdate
                syncdate = newsyncdate;
            }
            else
            {
                // Unlock the KDE addressbook and discard all changes
                addressbookptr->releaseSaveTicket(addressbookticket);
                if (multisync_debug)
                    printf("kdepim_plugin: KDE addressbook unlocked and changes discarded.\n");

            }

            addressbookticket=NULL;
        }

};

static KApplication *applicationptr=NULL;
static char name[] = "kde-opensync-plugin";
static char *argv[] = {name,0};

static kaddrbook *addrbook_for_context(OSyncContext *ctx)
{
    return (kaddrbook *)osync_context_get_plugin_data(ctx);
}

static void *kde_initialize(OSyncMember *member)
{
    kaddrbook *addrbook;

    if (getenv ("MULTISYNC_DEBUG"))
        printf("kdepim_plugin: %s()\n",__FUNCTION__);

    printf("kdepim_plugin: %s\n", __FUNCTION__);
    KCmdLineArgs::init(1, argv, "kde-opensync-plugin", i18n("KOpenSync"), "KDE OpenSync plugin", "0.1", false);
    applicationptr = new KApplication();

    /* Allocate and initialise a kaddrbook object. */
    addrbook = new kaddrbook(member);
    if (!addrbook)
        //FIXME: Check if OSYNC_ERROR_GENERIC is the righ error code in this case
        return NULL;

    /* Return kaddrbook object to the sync engine */
    return (void*)addrbook;
}

static void kde_finalize(void *data)
{
    printf("kdepim_plugin: %s()\n", __FUNCTION__);

    kaddrbook *addrbook = (kaddrbook *)data;
    delete addrbook;

    if (applicationptr) {
        delete applicationptr;
        applicationptr = 0;
    }
}

static void kde_connect(OSyncContext *ctx)
{
    osync_context_report_success(ctx);
}

static void kde_disconnect(OSyncContext *ctx)
{
    osync_context_report_success(ctx);
}

static void kde_get_changeinfo(OSyncContext *ctx)
{
    kaddrbook *addrbook = addrbook_for_context(ctx);
    if (getenv ("MULTISYNC_DEBUG"))
        printf("kdepim_plugin: %s\n",__FUNCTION__);

    int err = addrbook->get_changes(ctx);
    if (err) {
        osync_context_report_error(ctx, OSYNC_ERROR_GENERIC, "Couldn't access KDE addressbook");
        return;
    }
    osync_context_report_success(ctx);
    return;
}

static osync_bool kde_commit_change(OSyncContext *ctx, OSyncChange *change)
{
    kaddrbook *addrbook = addrbook_for_context(ctx);
    int err;

    if (getenv ("MULTISYNC_DEBUG"))
        printf("kdepim_plugin: %s()\n",__FUNCTION__);

    err = addrbook->modify(change);

    //FIXME: check when call sync_done()
    addrbook->sync_done(!err);
    if (err)
        osync_context_report_error(ctx, OSYNC_ERROR_GENERIC, "Couldn't update KDE addressbook");
    else
        osync_context_report_success(ctx);

    /*FIXME: What should be returned? */
    return true;
}

extern "C" {
void get_info(OSyncPluginInfo *info)
{
    info->version = 1;
    info->name = "kde-sync";
    info->description = i18n("Plugin for the KDE 3.x Addressbook");

    info->functions.initialize = kde_initialize;
    info->functions.connect = kde_connect;
    info->functions.disconnect = kde_disconnect;
    info->functions.finalize = kde_finalize;
    info->functions.get_changeinfo = kde_get_changeinfo;

    osync_plugin_accept_objtype(info, "contact");
    osync_plugin_accept_objformat(info, "contact", "vcard");
    /*FIXME: check the differences between commit_change() and access() */
    osync_plugin_set_commit_objformat(info, "contact", "vcard", kde_commit_change);
    osync_plugin_set_access_objformat(info, "contact", "vcard", kde_commit_change);

}

}// extern "C"
<commit_msg>Changed printf() calls to osync_debug() Stupid bug fixed: don't report the changes twice<commit_after>/***********************************************************************
OpenSync Plugin for KDE 3.x
Copyright (C) 2004 Stewart Heitmann <sheitmann@users.sourceforge.net>

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License version 2 as
published by the Free Software Foundation;

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS.
IN NO EVENT SHALL THE COPYRIGHT HOLDER(S) AND AUTHOR(S) BE LIABLE FOR ANY
CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

ALL LIABILITY, INCLUDING LIABILITY FOR INFRINGEMENT OF ANY PATENTS,
COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS, RELATING TO USE OF THIS
SOFTWARE IS DISCLAIMED.
*************************************************************************/
/*
 * 03 Nov 2004 - Eduardo Pereira Habkost <ehabkost@conectiva.com.br>
 * - Ported to OpenSync plugin interface
 */

extern "C"
{
#include <opensync/opensync.h>
#include "kaddrbook.h"
}

#include <kabc/stdaddressbook.h>
#include <kabc/vcardconverter.h>
#include <kcmdlineargs.h>
#include <kapplication.h>
#include <klocale.h>
#include <qsignal.h>
#include <qfile.h>

static
void unfold_vcard(char *vcard, size_t *size)
{
    char* in  = vcard;
    char* out = vcard;
    char *end = vcard + *size;
    while ( in < end)
    {
        /* remove any occurrences of "=[CR][LF]"                */
        /* these denote folded line markers in VCARD format.    */
        /* Dont know why, but Evolution uses the leading "="    */
        /* character to (presumably) denote a control sequence. */
        /* This is not quite how I interpret the VCARD RFC2426  */
        /* spec (section 2.6 line delimiting and folding).      */
        /* This seems to work though, so thats the main thing!  */
        if (in[0]=='=' && in[1]==13 && in[2]==10)
            in+=3;
        else
            *out++ = *in++;
    }
    *size = out - vcard;
}

class kaddrbook
{
    private:
        KABC::AddressBook* addressbookptr;
        KABC::Ticket* addressbookticket;
        QDateTime syncdate, newsyncdate;

        OSyncMember *member;
        OSyncHashTable *hashtable;

    public:
        kaddrbook(OSyncMember *memb)
            :member(memb)
        {
            //osync_debug("kde", 3, "kdepim_plugin: %s(%s)\n", __FUNCTION__);

            //get a handle to the standard KDE addressbook
            addressbookptr = KABC::StdAddressBook::self();

            //ensure a NULL Ticket ptr
            addressbookticket=NULL;

            hashtable = osync_hashtable_new();
            osync_hashtable_load(hashtable, member);

        };

        int get_changes(OSyncContext *ctx)
        {
            //osync_debug("kde", 3, "kdepim_plugin: kaddrbook::%s(newdbs=%d)\n", __FUNCTION__, newdbs);

            //remember when we started this current sync
            newsyncdate = QDateTime::currentDateTime();

            // We must reload the KDE addressbook in order to retrieve the latest changes.
            if (!addressbookptr->load())
            {
                osync_debug("kde", 3, "kdepim_plugin: couldnt reload KDE addressbook, aborting sync.\n");
                return -1;
            }
            osync_debug("kde", 3, "kdepim_plugin: KDE addressbook reloaded OK.\n");

            //Lock the addressbook
            addressbookticket = addressbookptr->requestSaveTicket();
            if (!addressbookticket)
            {
                osync_debug("kde", 3, "kdepim_plugin: couldnt lock KDE addressbook, aborting sync.\n");
                return -1;
            }
            osync_debug("kde", 3, "kdepim_plugin: KDE addressbook locked OK.\n");

            //osync_debug("kde", 3, "%s: %s : plugin UID list has %d entries\n", __FILE__, __FUNCTION__, uidlist.count());

            //Check the entries of the KDE addressbook against the last entries seen by the sync-engine
            for (KABC::AddressBook::Iterator it=addressbookptr->begin(); it!=addressbookptr->end(); it++ ) {
                //Get the revision date of the KDE addressbook entry.
                //Regard entries with invalid revision dates as having just been changed.
                osync_debug("kde", 3, "new entry, uid: %s\n", it->uid().latin1());

                QDateTime revdate = it->revision();
                if (!revdate.isValid())
                {
                    revdate = newsyncdate;      //use date of this sync as the revision date.
                    it->setRevision(revdate);   //update the Addressbook entry for future reference.
                }

                // gmalloc a changed_object for this phonebook entry
                //FIXME: deallocate it somewhere
                OSyncChange *chg= osync_change_new();
                osync_change_set_member(chg, member);

                QCString hash(revdate.toString());

                osync_change_set_hash(chg, hash);
                osync_change_set_uid(chg, it->uid().latin1());

                // Convert the VCARD data into a string
                KABC::VCardConverter converter;
                QString card = converter.createVCard(*it);
                QString data(card.latin1());
                //FIXME: deallocate data somewhere
                osync_change_set_data(chg, strdup(data), data.length(), 1);

                // set the remaining fields
                osync_change_set_objtype_string(chg, "contact");
                osync_change_set_objformat_string(chg, "vcard");
                osync_change_set_hash(chg, hash.data());
                /*FIXME: slowsync */
                if (osync_hashtable_detect_change(hashtable, chg, 0)) {
                    osync_context_report_change(ctx, chg);
                    osync_hashtable_update_hash(hashtable, chg);
                }
            }

            osync_hashtable_report_deleted(hashtable, ctx, 0);

            return 0;
        }

        int modify(OSyncChange *chg)
        {
            //osync_debug("kde", 3, "kdepim_plugin: kaddrbook::%s()\n",__FUNCTION__);

            int result = 0;

            // Ensure we still have a lock on the KDE addressbook (we ought to)
            if (addressbookticket==NULL)
            {
                //This should never happen, but just in case....
                osync_debug("kde", 3, "kdepim_plugin: lock on KDE addressbook was lost, aborting sync.\n");
                return -1;
            }

            KABC::VCardConverter converter;

            /* allocate and initialize return struct for this change entry */
            //FIXME: check how to return errors safely
            //modify_result = (syncobj_modify_result*)g_malloc0(sizeof(syncobj_modify_result));
            //modify_result->result = SYNC_MSG_MODIFYERROR;
            //modify_result->returnuid = NULL;

            OSyncObjType *type = osync_change_get_objtype(chg);
            // Do database modifications according to object type
            if (!strcmp(osync_objtype_get_name(type), "contact"))
            {

                OSyncChangeType chtype = osync_change_get_changetype(chg);
                char *uid = osync_change_get_uid(chg);
                /* treat modified objects without UIDs as if they were newly added objects */
                if (chtype == CHANGE_MODIFIED && !uid)
                    chtype = CHANGE_ADDED;

                // convert VCARD string from obj->comp into an Addresse object.
                char *data;
                size_t data_size;
                data = (char*)osync_change_get_data(chg);
                data_size = osync_change_get_datasize(chg);

                switch(chtype)
                {
                    case CHANGE_MODIFIED:
                    {
                        unfold_vcard(data, &data_size);

                        KABC::Addressee addressee = converter.parseVCard(QString::fromLatin1(data, data_size));

                        // ensure it has the correct UID
                        addressee.setUid(QString(uid));

                        // replace the current addressbook entry (if any) with the new one
                        addressbookptr->insertAddressee(addressee);

                        osync_debug("kde", 3, "kdepim_plugin: KDE ADDRESSBOOK ENTRY UPDATED (UID=%s)\n", uid);
                        result = 0;
                        break;
                    }

                    case CHANGE_ADDED:
                    {
                        // convert VCARD string from obj->comp into an Addresse object
                        // KABC::VCardConverter doesnt do VCARD unfolding so we must do it ourselves first.
                        unfold_vcard(data, &data_size);
                        KABC::Addressee addressee = converter.parseVCard(QString::fromLatin1(data, data_size));

                        // ensure it has a NULL UID
                        addressee.setUid(QString(NULL));

                        // add the new address to the addressbook
                        addressbookptr->insertAddressee(addressee);

                        osync_debug("kde", 3, "kdepim_plugin: KDE ADDRESSBOOK ENTRY ADDED (UID=%s)\n", addressee.uid().latin1());

                        // return the UID of the new entry along with the result
                        osync_change_set_uid(chg, addressee.uid().latin1());
                        result = 0;
                        break;
                    }

                    case CHANGE_DELETED:
                    {
                        if (uid==NULL)
                        {
                            result = 1;
                            break;
                        }

                        //find addressbook entry with matching UID and delete it
                        KABC::Addressee addressee = addressbookptr->findByUid(QString(uid));
                        if(!addressee.isEmpty())
                           addressbookptr->removeAddressee(addressee);

                        osync_debug("kde", 3, "kdepim_plugin: KDE ADDRESSBOOK ENTRY DELETED (UID=%s)\n", uid);

                        result = 0;
                        break;
                    }
                    default:
                        result = 0;
                }
            }
            //FIXME: handle unsupported objtypes

            return result;
        }

       void sync_done(bool success)
        {
            //osync_debug("kde", 3, "kdepim_plugin: kaddrbook::%s(%d)\n", __FUNCTION__, success);

            if (!addressbookticket)
            {
                //This should never happen, but just in case
                osync_debug("kde", 3, "kdepim_plugin: lock on KDE addressbook was lost, aborting sync.\n");

                return;
            }

            if (success)
            {
                // Save and unlock the KDE addressbook
                addressbookptr->save(addressbookticket);
                osync_debug("kde", 3, "kdepim_plugin: KDE addressbook saved and unlocked.\n");

                //update the syncdate
                syncdate = newsyncdate;
            }
            else
            {
                // Unlock the KDE addressbook and discard all changes
                addressbookptr->releaseSaveTicket(addressbookticket);
                osync_debug("kde", 3, "kdepim_plugin: KDE addressbook unlocked and changes discarded.\n");

            }

            addressbookticket=NULL;
        }

};

static KApplication *applicationptr=NULL;
static char name[] = "kde-opensync-plugin";
static char *argv[] = {name,0};

static kaddrbook *addrbook_for_context(OSyncContext *ctx)
{
    return (kaddrbook *)osync_context_get_plugin_data(ctx);
}

static void *kde_initialize(OSyncMember *member)
{
    kaddrbook *addrbook;

    osync_debug("kde", 3, "kdepim_plugin: %s()\n",__FUNCTION__);

    osync_debug("kde", 3, "kdepim_plugin: %s\n", __FUNCTION__);
    KCmdLineArgs::init(1, argv, "kde-opensync-plugin", i18n("KOpenSync"), "KDE OpenSync plugin", "0.1", false);
    applicationptr = new KApplication();

    /* Allocate and initialise a kaddrbook object. */
    addrbook = new kaddrbook(member);
    if (!addrbook)
        //FIXME: Check if OSYNC_ERROR_GENERIC is the righ error code in this case
        return NULL;

    /* Return kaddrbook object to the sync engine */
    return (void*)addrbook;
}

static void kde_finalize(void *data)
{
    osync_debug("kde", 3, "kdepim_plugin: %s()\n", __FUNCTION__);

    kaddrbook *addrbook = (kaddrbook *)data;
    delete addrbook;

    if (applicationptr) {
        delete applicationptr;
        applicationptr = 0;
    }
}

static void kde_connect(OSyncContext *ctx)
{
    osync_context_report_success(ctx);
}

static void kde_disconnect(OSyncContext *ctx)
{
    osync_context_report_success(ctx);
}

static void kde_get_changeinfo(OSyncContext *ctx)
{
    kaddrbook *addrbook = addrbook_for_context(ctx);
    osync_debug("kde", 3, "kdepim_plugin: %s\n",__FUNCTION__);

    int err = addrbook->get_changes(ctx);
    if (err) {
        osync_context_report_error(ctx, OSYNC_ERROR_GENERIC, "Couldn't access KDE addressbook");
        return;
    }
    osync_context_report_success(ctx);
    return;
}

static osync_bool kde_commit_change(OSyncContext *ctx, OSyncChange *change)
{
    kaddrbook *addrbook = addrbook_for_context(ctx);
    int err;

    osync_debug("kde", 3, "kdepim_plugin: %s()\n",__FUNCTION__);

    err = addrbook->modify(change);

    //FIXME: check when call sync_done()
    addrbook->sync_done(!err);
    if (err)
        osync_context_report_error(ctx, OSYNC_ERROR_GENERIC, "Couldn't update KDE addressbook");
    else
        osync_context_report_success(ctx);

    /*FIXME: What should be returned? */
    return true;
}

extern "C" {
void get_info(OSyncPluginInfo *info)
{
    info->version = 1;
    info->name = "kde-sync";
    info->description = i18n("Plugin for the KDE 3.x Addressbook");

    info->functions.initialize = kde_initialize;
    info->functions.connect = kde_connect;
    info->functions.disconnect = kde_disconnect;
    info->functions.finalize = kde_finalize;
    info->functions.get_changeinfo = kde_get_changeinfo;

    osync_plugin_accept_objtype(info, "contact");
    osync_plugin_accept_objformat(info, "contact", "vcard");
    /*FIXME: check the differences between commit_change() and access() */
    osync_plugin_set_commit_objformat(info, "contact", "vcard", kde_commit_change);
    osync_plugin_set_access_objformat(info, "contact", "vcard", kde_commit_change);

}

}// extern "C"
<|endoftext|>


<commit_before>/*----------------------------------------------
   POPPRNT.C -- Popup Editor Printing Functions
  ----------------------------------------------*/

#include <windows.h>
#include <commdlg.h>
#include <string.h>
#include "poppad.h"
#include "resource.h"
#include "Footy2.h"

extern int activeFootyID;

BOOL bUserAbort ;
HWND hDlgPrint ;

BOOL CALLBACK PrintDlgProc (HWND hDlg, UINT msg, WPARAM /*wParam*/, LPARAM /*lParam*/)
     {
     switch (msg)
          {
          case WM_INITDIALOG :
               EnableMenuItem (GetSystemMenu (hDlg, FALSE), SC_CLOSE,
                                                            MF_GRAYED) ;
               return TRUE ;

          case WM_COMMAND :
               bUserAbort = TRUE ;
               EnableWindow (GetParent (hDlg), TRUE) ;
               DestroyWindow (hDlg) ;
               hDlgPrint = 0 ;
               return TRUE ;
          }
     return FALSE ;
     }

BOOL CALLBACK AbortProc (HDC /*hPrinterDC*/, int /*iCode*/)
     {
     MSG msg ;

     while (!bUserAbort && PeekMessage (&msg, NULL, 0, 0, PM_REMOVE))
          {
          if (!hDlgPrint || !IsDialogMessage (hDlgPrint, &msg))
               {
               TranslateMessage (&msg) ;
               DispatchMessage (&msg) ;
               }
          }
     return !bUserAbort ;
     }

BOOL PopPrntPrintFile (HINSTANCE hInst, HWND hwnd, HWND /*hwndEdit*/,
                                                   LPSTR szTitleName)
     {
     static DOCINFO  di = { sizeof (DOCINFO), "", NULL } ;
     static PRINTDLG pd ;
     BOOL            bSuccess ;
     int             yChar, iCharsPerLine, iLinesPerPage, iTotalLines,
                     iTotalPages, iPage, iLine, iLineNum ;
     TEXTMETRIC      tm ;
     WORD            iColCopy, iNoiColCopy ;

     pd.lStructSize         = sizeof (PRINTDLG) ;
     pd.hwndOwner           = hwnd ;
     pd.hDevMode            = NULL ;
     pd.hDevNames           = NULL ;
     pd.hDC                 = NULL ;
     pd.Flags               = PD_ALLPAGES | PD_COLLATE | PD_RETURNDC ;
     pd.nFromPage           = 0 ;
     pd.nToPage             = 0 ;
     pd.nMinPage            = 0 ;
     pd.nMaxPage            = 0 ;
     pd.nCopies             = 1 ;
     pd.hInstance           = NULL ;
     pd.lCustData           = 0L ;
     pd.lpfnPrintHook       = NULL ;
     pd.lpfnSetupHook       = NULL ;
     pd.lpPrintTemplateName = NULL ;
     pd.lpSetupTemplateName = NULL ;
     pd.hPrintTemplate      = NULL ;
     pd.hSetupTemplate      = NULL ;

     if (!PrintDlg (&pd))
          return TRUE ;

//     iTotalLines = (short) SendMessage (hwndEdit, EM_GETLINECOUNT, 0, 0L) ;
	 iTotalLines = (short)Footy2GetLines(activeFootyID);

     if (iTotalLines == 0)
          return TRUE ;

     GetTextMetrics (pd.hDC, &tm) ;
     yChar = tm.tmHeight + tm.tmExternalLeading ;

     iCharsPerLine = GetDeviceCaps (pd.hDC, HORZRES) / tm.tmAveCharWidth ;
     iLinesPerPage = GetDeviceCaps (pd.hDC, VERTRES) / yChar ;
     iTotalPages   = (iTotalLines + iLinesPerPage - 1) / iLinesPerPage ;

     EnableWindow (hwnd, FALSE) ;

     bSuccess   = TRUE ;
     bUserAbort = FALSE ;

     hDlgPrint = CreateDialog (hInst, (LPCTSTR) "PrintDlgBox", hwnd, (DLGPROC)PrintDlgProc) ;
     SetDlgItemText (hDlgPrint, IDD_FNAME, szTitleName) ;

     SetAbortProc (pd.hDC, (ABORTPROC)AbortProc) ;

	 di.lpszDocName = szTitleName;

     if (StartDoc (pd.hDC, &di) > 0)
          {
          for (iColCopy = 0 ;
               iColCopy < ((WORD) pd.Flags & PD_COLLATE ? pd.nCopies : 1) ;
               iColCopy++)
               {
               for (iPage = 0 ; iPage < iTotalPages ; iPage++)
                    {
                    for (iNoiColCopy = 0 ;
                         iNoiColCopy < (pd.Flags & PD_COLLATE ? 1 : pd.nCopies) ;
                         iNoiColCopy++)
                         {

                         if (StartPage (pd.hDC) < 0)
                              {
                              bSuccess = FALSE ;
                              break ;
                              }

                         for (iLine = 0 ; iLine < iLinesPerPage ; iLine++)
                              {
                              iLineNum = iLinesPerPage * iPage + iLine ;

                              if (iLineNum >= iTotalLines)
                                   break ;

//                              TextOut (pd.hDC, 0, yChar * iLine, pstrBuffer,
//                                   (int) SendMessage (hwndEdit, EM_GETLINE,
//                                   (WPARAM) iLineNum, (LPARAM) pstrBuffer)) ;
					//		  pstrBuffer = FootyGetLineData(activeFootyID, iLineNum+1);	// 2008-02-17 Shark++
					//		  TextOut(pd.hDC, 0, yChar * iLine, pstrBuffer,
					//			  FootyGetLineLen(activeFootyID, iLineNum+1));
							  LPCWSTR pstrBufferW = Footy2GetLineW(activeFootyID, iLineNum);	// 2008-02-28 Shark++ SpE܂Ԃ()
							  TextOutW(pd.hDC, 0, yChar * iLine, pstrBufferW,
								  Footy2GetLineLengthW(activeFootyID, iLineNum));
                              }

                         if (EndPage (pd.hDC) < 0)
                              {
                              bSuccess = FALSE ;
                              break ;
                              }

                         if (bUserAbort)
                              break ;
                         }

                    if (!bSuccess || bUserAbort)
                         break ;
                    }

               if (!bSuccess || bUserAbort)
                    break ;
               }
          }
     else
          bSuccess = FALSE ;

     if (bSuccess)
          EndDoc (pd.hDC) ;

     if (!bUserAbort)
          {
          EnableWindow (hwnd, TRUE) ;
          DestroyWindow (hDlgPrint) ;
          }

     DeleteDC (pd.hDC) ;

     return bSuccess && !bUserAbort ;
     }
<commit_msg>印刷機能にタブの反映・行番号の追加・フォント設定などをテスト実装<commit_after>/*----------------------------------------------
   POPPRNT.C -- Popup Editor Printing Functions
  ----------------------------------------------*/

#include <windows.h>
#include <commdlg.h>
#include <string.h>
#include <tchar.h>
#include <stdio.h>
#include "poppad.h"
#include "resource.h"
#include "Footy2.h"

extern int activeFootyID;

BOOL bUserAbort ;
HWND hDlgPrint ;

BOOL CALLBACK PrintDlgProc (HWND hDlg, UINT msg, WPARAM /*wParam*/, LPARAM /*lParam*/)
     {
     switch (msg)
          {
          case WM_INITDIALOG :
               EnableMenuItem (GetSystemMenu (hDlg, FALSE), SC_CLOSE,
                                                            MF_GRAYED) ;
               return TRUE ;

          case WM_COMMAND :
               bUserAbort = TRUE ;
               EnableWindow (GetParent (hDlg), TRUE) ;
               DestroyWindow (hDlg) ;
               hDlgPrint = 0 ;
               return TRUE ;
          }
     return FALSE ;
     }

BOOL CALLBACK AbortProc (HDC /*hPrinterDC*/, int /*iCode*/)
     {
     MSG msg ;

     while (!bUserAbort && PeekMessage (&msg, NULL, 0, 0, PM_REMOVE))
          {
          if (!hDlgPrint || !IsDialogMessage (hDlgPrint, &msg))
               {
               TranslateMessage (&msg) ;
               DispatchMessage (&msg) ;
               }
          }
     return !bUserAbort ;
     }

BOOL PopPrntPrintFile (HINSTANCE hInst, HWND hwnd, HWND /*hwndEdit*/,
                                                   LPSTR szTitleName)
     {
     static DOCINFO  di = { sizeof (DOCINFO), "", NULL } ;
     static PRINTDLG pd ;
     BOOL            bSuccess ;
     int             yChar, iCharsPerLine, iLinesPerPage, iTotalLines,
                     iTotalPages, iPage, iLine, iLineNum ;
     TEXTMETRIC      tm ;
     WORD            iColCopy, iNoiColCopy ;

     pd.lStructSize         = sizeof (PRINTDLG) ;
     pd.hwndOwner           = hwnd ;
     pd.hDevMode            = NULL ;
     pd.hDevNames           = NULL ;
     pd.hDC                 = NULL ;
     pd.Flags               = PD_ALLPAGES | PD_COLLATE | PD_RETURNDC ;
     pd.nFromPage           = 0 ;
     pd.nToPage             = 0 ;
     pd.nMinPage            = 0 ;
     pd.nMaxPage            = 0 ;
     pd.nCopies             = 1 ;
     pd.hInstance           = NULL ;
     pd.lCustData           = 0L ;
     pd.lpfnPrintHook       = NULL ;
     pd.lpfnSetupHook       = NULL ;
     pd.lpPrintTemplateName = NULL ;
     pd.lpSetupTemplateName = NULL ;
     pd.hPrintTemplate      = NULL ;
     pd.hSetupTemplate      = NULL ;

     if (!PrintDlg (&pd))
          return TRUE ;

//     iTotalLines = (short) SendMessage (hwndEdit, EM_GETLINECOUNT, 0, 0L) ;
	 iTotalLines = (short)Footy2GetLines(activeFootyID);

     if (iTotalLines == 0)
          return TRUE ;

	 TCHAR szLineNumber[64];
	 int nLineNumberCharWidth = 1;
	 _stprintf(szLineNumber, "%d:", iTotalLines);
	 nLineNumberCharWidth = lstrlen(szLineNumber) - 1;

//	 DEVMODE dm;
//	 GetDeviceCaps(

	 HFONT hFont = CreateFont(-MulDiv(10, GetDeviceCaps(pd.hDC, LOGPIXELSY), 72), 0, 0, 0, FW_DONTCARE, FALSE, FALSE, FALSE, DEFAULT_CHARSET,
				OUT_CHARACTER_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH, "lr SVbN");
	 HFONT hFoltOld = (HFONT)SelectObject(pd.hDC, hFont);

	 RECT rc = {0};
	 DrawText(pd.hDC, szLineNumber, -1, &rc, DT_CALCRECT);
	 int nLineNumberWidth = rc.right - rc.left;

	 DRAWTEXTPARAMS dtp = { sizeof(DRAWTEXTPARAMS), 4, };

     GetTextMetrics (pd.hDC, &tm) ;
     yChar = tm.tmHeight + tm.tmExternalLeading ;

	 SIZE sizePage;
	 sizePage.cx = GetDeviceCaps (pd.hDC, HORZRES);
	 sizePage.cy = GetDeviceCaps (pd.hDC, VERTRES);
     iCharsPerLine = sizePage.cx / tm.tmAveCharWidth ;
     iLinesPerPage = sizePage.cy / yChar ;
     iTotalPages   = (iTotalLines + iLinesPerPage - 1) / iLinesPerPage ;

     EnableWindow (hwnd, FALSE) ;

     bSuccess   = TRUE ;
     bUserAbort = FALSE ;

     hDlgPrint = CreateDialog (hInst, (LPCTSTR) "PrintDlgBox", hwnd, (DLGPROC)PrintDlgProc) ;
     SetDlgItemText (hDlgPrint, IDD_FNAME, szTitleName) ;

     SetAbortProc (pd.hDC, (ABORTPROC)AbortProc) ;

	 di.lpszDocName = szTitleName;

     if (StartDoc (pd.hDC, &di) > 0)
          {
          for (iColCopy = 0 ;
               iColCopy < ((WORD) pd.Flags & PD_COLLATE ? pd.nCopies : 1) ;
               iColCopy++)
               {
               for (iPage = 0 ; iPage < iTotalPages ; iPage++)
                    {
                    for (iNoiColCopy = 0 ;
                         iNoiColCopy < (pd.Flags & PD_COLLATE ? 1 : pd.nCopies) ;
                         iNoiColCopy++)
                         {

                         if (StartPage (pd.hDC) < 0)
                              {
                              bSuccess = FALSE ;
                              break ;
                              }

                         for (iLine = 0 ; iLine < iLinesPerPage ; iLine++)
                              {
                              iLineNum = iLinesPerPage * iPage + iLine ;

                              if (iLineNum >= iTotalLines)
                                   break ;

//                              TextOut (pd.hDC, 0, yChar * iLine, pstrBuffer,
//                                   (int) SendMessage (hwndEdit, EM_GETLINE,
//                                   (WPARAM) iLineNum, (LPARAM) pstrBuffer)) ;
					//		  pstrBuffer = FootyGetLineData(activeFootyID, iLineNum+1);	// 2008-02-17 Shark++
					//		  TextOut(pd.hDC, 0, yChar * iLine, pstrBuffer,
					//			  FootyGetLineLen(activeFootyID, iLineNum+1));
							  // sԍ
							  _stprintf(szLineNumber, "%*d ", nLineNumberCharWidth, iLineNum + 1);
							  TextOut(pd.hDC, 0, yChar * iLine, szLineNumber, lstrlen(szLineNumber));
							  // se
							  LPCWSTR pstrBufferW = Footy2GetLineW(activeFootyID, iLineNum);	// 2008-02-28 Shark++ SpE܂Ԃ()
							  SetRect(&rc, nLineNumberWidth, yChar * iLine, nLineNumberWidth + sizePage.cx, yChar * iLine + yChar);
							  DrawTextExW(pd.hDC, (LPWSTR)pstrBufferW,
								  Footy2GetLineLengthW(activeFootyID, iLineNum)
								  , &rc, DT_SINGLELINE|DT_EXPANDTABS|DT_NOPREFIX|DT_TABSTOP, &dtp);
                              }

                         if (EndPage (pd.hDC) < 0)
                              {
                              bSuccess = FALSE ;
                              break ;
                              }

                         if (bUserAbort)
                              break ;
                         }

                    if (!bSuccess || bUserAbort)
                         break ;
                    }

               if (!bSuccess || bUserAbort)
                    break ;
               }
          }
     else
          bSuccess = FALSE ;

     if (bSuccess)
          EndDoc (pd.hDC) ;

     if (!bUserAbort)
          {
          EnableWindow (hwnd, TRUE) ;
          DestroyWindow (hDlgPrint) ;
          }

	 DeleteObject( SelectObject(pd.hDC, hFoltOld) );

	 DeleteDC (pd.hDC) ;

     return bSuccess && !bUserAbort ;
     }
<|endoftext|>


<commit_before>// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
#include <vespa/fastos/fastos.h>
#include <vespa/vespalib/testkit/test_kit.h>
#include <vespa/vespalib/eval/function.h>
#include <vespa/vespalib/tensor/tensor.h>

#include <vespa/searchlib/attribute/attributefactory.h>
#include <vespa/searchlib/attribute/attributevector.h>
#include <vespa/searchlib/attribute/integerbase.h>
#include <vespa/searchlib/attribute/stringbase.h>
#include <vespa/searchlib/features/setup.h>
#include <vespa/searchlib/fef/test/as_tensor.h>
#include <vespa/searchlib/fef/test/indexenvironment.h>
#include <vespa/searchlib/fef/test/indexenvironmentbuilder.h>
#include <vespa/searchlib/fef/test/queryenvironment.h>
#include <vespa/searchlib/fef/test/ftlib.h>
#include <vespa/searchlib/features/tensor_from_labels_feature.h>
#include <vespa/searchlib/fef/fef.h>

using search::feature_t;
using namespace search::fef;
using namespace search::fef::test;
using namespace search::features;
using search::AttributeFactory;
using search::IntegerAttribute;
using search::StringAttribute;
using vespalib::eval::Value;
using vespalib::eval::Function;
using vespalib::tensor::Tensor;

typedef search::attribute::Config AVC;
typedef search::attribute::BasicType AVBT;
typedef search::attribute::CollectionType AVCT;
typedef search::AttributeVector::SP AttributePtr;
typedef FtTestApp FTA;

struct SetupFixture
{
    TensorFromLabelsBlueprint blueprint;
    IndexEnvironment indexEnv;
    SetupFixture()
        : blueprint(),
          indexEnv()
    {
    }
};

TEST_F("require that blueprint can be created from factory", SetupFixture)
{
    EXPECT_TRUE(FTA::assertCreateInstance(f.blueprint, "tensorFromLabels"));
}

TEST_F("require that setup fails if source spec is invalid", SetupFixture)
{
    FTA::FT_SETUP_FAIL(f.blueprint, f.indexEnv, StringList().add("source(foo)"));
}

TEST_F("require that setup succeeds with attribute source", SetupFixture)
{
    FTA::FT_SETUP_OK(f.blueprint, f.indexEnv, StringList().add("attribute(foo)"),
            StringList(), StringList().add("tensor"));
}

TEST_F("require that setup succeeds with query source", SetupFixture)
{
    FTA::FT_SETUP_OK(f.blueprint, f.indexEnv, StringList().add("query(foo)"),
            StringList(), StringList().add("tensor"));
}

struct ExecFixture
{
    BlueprintFactory factory;
    FtFeatureTest test;
    ExecFixture(const vespalib::string &feature)
        : factory(),
          test(factory, feature)
    {
        setup_search_features(factory);
        setupAttributeVectors();
        setupQueryEnvironment();
        ASSERT_TRUE(test.setup());
    }
    void setupAttributeVectors() {
        std::vector<AttributePtr> attrs;
        attrs.push_back(AttributeFactory::createAttribute("astr", AVC(AVBT::STRING,  AVCT::ARRAY)));
        attrs.push_back(AttributeFactory::createAttribute("aint", AVC(AVBT::INT32,  AVCT::ARRAY)));
        attrs.push_back(AttributeFactory::createAttribute("wsstr", AVC(AVBT::STRING,  AVCT::WSET)));

        for (const auto &attr : attrs) {
            attr->addReservedDoc();
            attr->addDocs(1);
            test.getIndexEnv().getAttributeManager().add(attr);
        }

        StringAttribute *astr = static_cast<StringAttribute *>(attrs[0].get());
        // Note that the weight parameter is not used
        astr->append(1, "a", 0);
        astr->append(1, "b", 0);
        astr->append(1, "c", 0);

        IntegerAttribute *aint = static_cast<IntegerAttribute *>(attrs[1].get());
        aint->append(1, 3, 0);
        aint->append(1, 5, 0);
        aint->append(1, 7, 0);

        for (const auto &attr : attrs) {
            attr->commit();
        }
    }
    void setupQueryEnvironment() {
        test.getQueryEnv().getProperties().add("astr_query", "[d e f]");
        test.getQueryEnv().getProperties().add("aint_query", "[11 13 17]");
    }
    const Tensor &extractTensor() {
        const Value::CREF *value = test.resolveObjectFeature();
        ASSERT_TRUE(value != nullptr);
        ASSERT_TRUE(value->get().is_tensor());
        return static_cast<const Tensor &>(*value->get().as_tensor());
    }
    const Tensor &execute() {
        test.executeOnly();
        return extractTensor();
    }
};

// Tests for attribute source:

TEST_F("require that array string attribute can be converted to tensor (default dimension)",
        ExecFixture("tensorFromLabels(attribute(astr))"))
{
    EXPECT_EQUAL(AsTensor("{ {astr:a}:1, {astr:b}:1, {astr:c}:1 }"), f.execute());
}

TEST_F("require that array string attribute can be converted to tensor (explicit dimension)",
        ExecFixture("tensorFromLabels(attribute(astr),dim)"))
{
    EXPECT_EQUAL(AsTensor("{ {dim:a}:1, {dim:b}:1, {dim:c}:1 }"), f.execute());
}

TEST_F("require that array integer attribute can be converted to tensor (default dimension)",
        ExecFixture("tensorFromLabels(attribute(aint))"))
{
    EXPECT_EQUAL(AsTensor("{ {aint:7}:1, {aint:3}:1, {aint:5}:1 }"), f.execute());
}

TEST_F("require that array attribute can be converted to tensor (explicit dimension)",
        ExecFixture("tensorFromLabels(attribute(aint),dim)"))
{
    EXPECT_EQUAL(AsTensor("{ {dim:7}:1, {dim:3}:1, {dim:5}:1 }"), f.execute());
}

TEST_F("require that empty tensor is created if attribute does not exists",
        ExecFixture("tensorFromLabels(attribute(null))"))
{
    EXPECT_EQUAL(AsEmptyTensor("tensor(null{})"), f.execute());
}

TEST_F("require that empty tensor is created if attribute type is not supported",
        ExecFixture("tensorFromLabels(attribute(wsstr))"))
{
    EXPECT_EQUAL(AsEmptyTensor("tensor(wsstr{})"), f.execute());
}

// Tests for query source:

TEST_F("require that string array from query can be converted to tensor (default dimension)",
        ExecFixture("tensorFromLabels(query(astr_query))"))
{
    EXPECT_EQUAL(AsTensor("{ {astr_query:d}:1, {astr_query:e}:1, {astr_query:f}:1 }"), f.execute());
}

TEST_F("require that integer array from query can be converted to tensor (default dimension)",
        ExecFixture("tensorFromLabels(query(aint_query))"))
{
    EXPECT_EQUAL(AsTensor("{ {aint_query:13}:1, {aint_query:17}:1, {aint_query:11}:1 }"), f.execute());
}

TEST_F("require that string array from query can be converted to tensor (explicit dimension)",
        ExecFixture("tensorFromLabels(query(astr_query),dim)"))
{
    EXPECT_EQUAL(AsTensor("{ {dim:d}:1, {dim:e}:1, {dim:f}:1 }"), f.execute());
}

TEST_F("require that empty tensor is created if query parameter is not found",
        ExecFixture("tensorFromLabels(query(null))"))
{
    EXPECT_EQUAL(AsEmptyTensor("tensor(null{})"), f.execute());
}

TEST_MAIN() { TEST_RUN_ALL(); }
<commit_msg>use spec to make tensors<commit_after>// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
#include <vespa/vespalib/testkit/test_kit.h>

#include <vespa/searchlib/attribute/attributefactory.h>
#include <vespa/searchlib/attribute/attributevector.h>
#include <vespa/searchlib/attribute/integerbase.h>
#include <vespa/searchlib/attribute/stringbase.h>
#include <vespa/searchlib/features/setup.h>
#include <vespa/searchlib/features/tensor_from_labels_feature.h>
#include <vespa/searchlib/fef/fef.h>
#include <vespa/searchlib/fef/test/ftlib.h>
#include <vespa/searchlib/fef/test/indexenvironment.h>
#include <vespa/searchlib/fef/test/indexenvironmentbuilder.h>
#include <vespa/searchlib/fef/test/queryenvironment.h>
#include <vespa/vespalib/eval/function.h>
#include <vespa/vespalib/tensor/tensor.h>
#include <vespa/vespalib/tensor/default_tensor_engine.h>

using search::feature_t;
using namespace search::fef;
using namespace search::fef::test;
using namespace search::features;
using search::AttributeFactory;
using search::IntegerAttribute;
using search::StringAttribute;
using vespalib::eval::Value;
using vespalib::eval::Function;
using vespalib::eval::TensorSpec;
using vespalib::tensor::DefaultTensorEngine;
using vespalib::tensor::Tensor;

typedef search::attribute::Config AVC;
typedef search::attribute::BasicType AVBT;
typedef search::attribute::CollectionType AVCT;
typedef search::AttributeVector::SP AttributePtr;
typedef FtTestApp FTA;

Tensor::UP make_tensor(const TensorSpec &spec) {
    auto tensor = DefaultTensorEngine::ref().create(spec);
    return Tensor::UP(dynamic_cast<Tensor*>(tensor.release()));
}

Tensor::UP make_empty(const vespalib::string &type) {
    return make_tensor(TensorSpec(type));
}

struct SetupFixture
{
    TensorFromLabelsBlueprint blueprint;
    IndexEnvironment indexEnv;
    SetupFixture()
        : blueprint(),
          indexEnv()
    {
    }
};

TEST_F("require that blueprint can be created from factory", SetupFixture)
{
    EXPECT_TRUE(FTA::assertCreateInstance(f.blueprint, "tensorFromLabels"));
}

TEST_F("require that setup fails if source spec is invalid", SetupFixture)
{
    FTA::FT_SETUP_FAIL(f.blueprint, f.indexEnv, StringList().add("source(foo)"));
}

TEST_F("require that setup succeeds with attribute source", SetupFixture)
{
    FTA::FT_SETUP_OK(f.blueprint, f.indexEnv, StringList().add("attribute(foo)"),
            StringList(), StringList().add("tensor"));
}

TEST_F("require that setup succeeds with query source", SetupFixture)
{
    FTA::FT_SETUP_OK(f.blueprint, f.indexEnv, StringList().add("query(foo)"),
            StringList(), StringList().add("tensor"));
}

struct ExecFixture
{
    BlueprintFactory factory;
    FtFeatureTest test;
    ExecFixture(const vespalib::string &feature)
        : factory(),
          test(factory, feature)
    {
        setup_search_features(factory);
        setupAttributeVectors();
        setupQueryEnvironment();
        ASSERT_TRUE(test.setup());
    }
    void setupAttributeVectors() {
        std::vector<AttributePtr> attrs;
        attrs.push_back(AttributeFactory::createAttribute("astr", AVC(AVBT::STRING,  AVCT::ARRAY)));
        attrs.push_back(AttributeFactory::createAttribute("aint", AVC(AVBT::INT32,  AVCT::ARRAY)));
        attrs.push_back(AttributeFactory::createAttribute("wsstr", AVC(AVBT::STRING,  AVCT::WSET)));

        for (const auto &attr : attrs) {
            attr->addReservedDoc();
            attr->addDocs(1);
            test.getIndexEnv().getAttributeManager().add(attr);
        }

        StringAttribute *astr = static_cast<StringAttribute *>(attrs[0].get());
        // Note that the weight parameter is not used
        astr->append(1, "a", 0);
        astr->append(1, "b", 0);
        astr->append(1, "c", 0);

        IntegerAttribute *aint = static_cast<IntegerAttribute *>(attrs[1].get());
        aint->append(1, 3, 0);
        aint->append(1, 5, 0);
        aint->append(1, 7, 0);

        for (const auto &attr : attrs) {
            attr->commit();
        }
    }
    void setupQueryEnvironment() {
        test.getQueryEnv().getProperties().add("astr_query", "[d e f]");
        test.getQueryEnv().getProperties().add("aint_query", "[11 13 17]");
    }
    const Tensor &extractTensor() {
        const Value::CREF *value = test.resolveObjectFeature();
        ASSERT_TRUE(value != nullptr);
        ASSERT_TRUE(value->get().is_tensor());
        return static_cast<const Tensor &>(*value->get().as_tensor());
    }
    const Tensor &execute() {
        test.executeOnly();
        return extractTensor();
    }
};

// Tests for attribute source:

TEST_F("require that array string attribute can be converted to tensor (default dimension)",
        ExecFixture("tensorFromLabels(attribute(astr))"))
{
    EXPECT_EQUAL(*make_tensor(TensorSpec("tensor(astr{})")
                              .add({{"astr", "a"}}, 1)
                              .add({{"astr", "b"}}, 1)
                              .add({{"astr", "c"}}, 1)), f.execute());
}

TEST_F("require that array string attribute can be converted to tensor (explicit dimension)",
        ExecFixture("tensorFromLabels(attribute(astr),dim)"))
{
    EXPECT_EQUAL(*make_tensor(TensorSpec("tensor(dim{})")
                              .add({{"dim", "a"}}, 1)
                              .add({{"dim", "b"}}, 1)
                              .add({{"dim", "c"}}, 1)), f.execute());
}

TEST_F("require that array integer attribute can be converted to tensor (default dimension)",
        ExecFixture("tensorFromLabels(attribute(aint))"))
{
    EXPECT_EQUAL(*make_tensor(TensorSpec("tensor(aint{})")
                              .add({{"aint", "7"}}, 1)
                              .add({{"aint", "3"}}, 1)
                              .add({{"aint", "5"}}, 1)), f.execute());
}

TEST_F("require that array attribute can be converted to tensor (explicit dimension)",
        ExecFixture("tensorFromLabels(attribute(aint),dim)"))
{
    EXPECT_EQUAL(*make_tensor(TensorSpec("tensor(dim{})")
                              .add({{"dim", "7"}}, 1)
                              .add({{"dim", "3"}}, 1)
                              .add({{"dim", "5"}}, 1)), f.execute());
}

TEST_F("require that empty tensor is created if attribute does not exists",
        ExecFixture("tensorFromLabels(attribute(null))"))
{
    EXPECT_EQUAL(*make_empty("tensor(null{})"), f.execute());
}

TEST_F("require that empty tensor is created if attribute type is not supported",
        ExecFixture("tensorFromLabels(attribute(wsstr))"))
{
    EXPECT_EQUAL(*make_empty("tensor(wsstr{})"), f.execute());
}

// Tests for query source:

TEST_F("require that string array from query can be converted to tensor (default dimension)",
        ExecFixture("tensorFromLabels(query(astr_query))"))
{
    EXPECT_EQUAL(*make_tensor(TensorSpec("tensor(astr_query{})")
                              .add({{"astr_query", "d"}}, 1)
                              .add({{"astr_query", "e"}}, 1)
                              .add({{"astr_query", "f"}}, 1)), f.execute());
}

TEST_F("require that integer array from query can be converted to tensor (default dimension)",
        ExecFixture("tensorFromLabels(query(aint_query))"))
{
    EXPECT_EQUAL(*make_tensor(TensorSpec("tensor(aint_query{})")
                              .add({{"aint_query", "13"}}, 1)
                              .add({{"aint_query", "17"}}, 1)
                              .add({{"aint_query", "11"}}, 1)), f.execute());
}

TEST_F("require that string array from query can be converted to tensor (explicit dimension)",
        ExecFixture("tensorFromLabels(query(astr_query),dim)"))
{
    EXPECT_EQUAL(*make_tensor(TensorSpec("tensor(dim{})")
                              .add({{"dim", "d"}}, 1)
                              .add({{"dim", "e"}}, 1)
                              .add({{"dim", "f"}}, 1)), f.execute());
}

TEST_F("require that empty tensor is created if query parameter is not found",
        ExecFixture("tensorFromLabels(query(null))"))
{
    EXPECT_EQUAL(*make_empty("tensor(null{})"), f.execute());
}

TEST_MAIN() { TEST_RUN_ALL(); }
<|endoftext|>