<commit_before>/*************************************************************************
 *   Copyright (C) 2011-2016 by <NAME>                       *
 *   paul-louis (at) ageneau (dot) org                                   *
 *                                                                       *
 *   This file is part of Teapotnet.                                     *
 *                                                                       *
 *   Teapotnet 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.                 *
 *                                                                       *
 *   Teapotnet 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 Teapotnet.                                       *
 *   If not, see <http://www.gnu.org/licenses/>.                         *
 *************************************************************************/

#include "tpn/board.hpp"
#include "tpn/resource.hpp"
#include "tpn/cache.hpp"
#include "tpn/html.hpp"
#include "tpn/user.hpp"
#include "tpn/store.hpp"
#include "tpn/config.hpp"

#include "pla/jsonserializer.hpp"
#include "pla/binaryserializer.hpp"
#include "pla/object.hpp"

namespace tpn
{

Board::Board(const String &name, const String &secret, const String &displayName) :
	mName(name),
	mDisplayName(displayName),
	mSecret(secret),
	mHasNew(false),
	mUnread(0)
{
	Assert(!mName.empty() && mName[0] == '/');	// TODO
	
	Interface::Instance->add(urlPrefix(), this);
	
	String prefix = "/mail" + mName;
	
	Set<BinaryString> digests;
	Store::Instance->retrieveValue(Store::Hash(prefix), digests);
	
	for(auto it = digests.begin(); it != digests.end(); ++it)
	{
		//LogDebug("Board", "Retrieved digest: " + it->toString());
		if(fetch(Network::Link::Null, prefix, "/", *it, false))
			incoming(Network::Link::Null, prefix, "/", *it);
	}
	
	publish(prefix);
	subscribe(prefix);
}

Board::~Board(void)
{
	Interface::Instance->remove(urlPrefix(), this);
	
	String prefix = "/mail" + mName;
	unpublish(prefix);
	unsubscribe(prefix);
}

String Board::urlPrefix(void) const
{
	std::unique_lock<std::mutex> lock(mMutex);
	return "/mail" + mName;
}

bool Board::hasNew(void) const
{
	std::unique_lock<std::mutex> lock(mMutex);
	bool value = false;
	std::swap(mHasNew, value);
	return value;
}

int Board::unread(void) const
{
	// TODO
	return 0;
}

BinaryString Board::digest(void) const
{
	std::unique_lock<std::mutex> lock(mMutex);
	return mDigest;
}

bool Board::add(const Mail &mail, bool noIssue)
{
	{
		std::unique_lock<std::mutex> lock(mMutex);
		
		if(mail.empty() || mMails.contains(mail))
			return false;
	
		const Mail &mail = *mMails.insert(mail).first;
		mUnorderedMails.append(&mail);
	}
	
	if(!noIssue) issue("/mail" + mName, mail);
	process();
	publish("/mail" + mName);
	
	mCondition.notify_all();
	return true;
}

void Board::addMergeUrl(const String &url)
{
	std::unique_lock<std::mutex> lock(mMutex);
	mMergeUrls.insert(url);
}

void Board::removeMergeUrl(const String &url)
{
	std::unique_lock<std::mutex> lock(mMutex);
	mMergeUrls.erase(url);
}

void Board::process(void)
{
	// Private, no sync
	
	try {
		// Write messages to temporary file
		String tempFileName = File::TempName();
		File tempFile(tempFileName, File::Truncate);
		BinarySerializer serializer(&tempFile);
		for(auto it = mMails.begin(); it != mMails.end(); ++it)
			serializer << *it;
		tempFile.close();
		
		// Move to cache and process
		Resource resource;
		resource.cache(tempFileName, mName, "mail", mSecret);
		
		// Retrieve digest and store it
		mDigest = resource.digest();
		Store::Instance->storeValue(Store::Hash("/mail" + mName), mDigest, Store::Permanent);
		
		LogDebug("Board::process", "Board processed: " + mDigest.toString());
	}
	catch(const Exception &e)
	{
		LogWarn("Board::process", String("Board processing failed: ") + e.what());
	}
}

bool Board::anounce(const Network::Link &link, const String &prefix, const String &path, List<BinaryString> &targets)
{
	std::unique_lock<std::mutex> lock(mMutex);
	targets.clear();
	
	if(mDigest.empty()) 
		return false;
	
	targets.push_back(mDigest);
	return true;
}

bool Board::incoming(const Network::Link &link, const String &prefix, const String &path, const BinaryString &target)
{
	{
		std::unique_lock<std::mutex> lock(mMutex);
		if(target == mDigest) 
			return false;
	}
	
	if(fetch(link, prefix, path, target, true))
	{
		try {
			std::unique_lock<std::mutex> lock(mMutex);
			
			Resource resource(target, true);	// local only (already fetched)
			if(resource.type() != "mail")
				return false;
			
			Resource::Reader reader(&resource, mSecret);
			BinarySerializer serializer(&reader);
			Mail mail;
			unsigned count = 0;
			while(!!(serializer >> mail))
			{
				if(mail.empty())
					continue;
				
				if(!mMails.contains(mail))
				{
					const Mail &mail = *mMails.insert(mail).first;
					mUnorderedMails.append(&mail);
					
					++mUnread;
					mHasNew = true;
				}
				
				++count;
			}
			
			if(count == mMails.size())
			{
				mDigest = target;
			}
			else {<commit_msg>Fixed segfault in Board<commit_after>/*************************************************************************
 *   Copyright (C) 2011-2016 by <NAME>                       *
 *   paul-louis (at) ageneau (dot) org                                   *
 *                                                                       *
 *   This file is part of Teapotnet.                                     *
 *                                                                       *
 *   Teapotnet 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.                 *
 *                                                                       *
 *   Teapotnet 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 Teapotnet.                                       *
 *   If not, see <http://www.gnu.org/licenses/>.                         *
 *************************************************************************/

#include "tpn/board.hpp"
#include "tpn/resource.hpp"
#include "tpn/cache.hpp"
#include "tpn/html.hpp"
#include "tpn/user.hpp"
#include "tpn/store.hpp"
#include "tpn/config.hpp"

#include "pla/jsonserializer.hpp"
#include "pla/binaryserializer.hpp"
#include "pla/object.hpp"

namespace tpn
{

Board::Board(const String &name, const String &secret, const String &displayName) :
	mName(name),
	mDisplayName(displayName),
	mSecret(secret),
	mHasNew(false),
	mUnread(0)
{
	Assert(!mName.empty() && mName[0] == '/');	// TODO
	
	Interface::Instance->add(urlPrefix(), this);
	
	String prefix = "/mail" + mName;
	
	Set<BinaryString> digests;
	Store::Instance->retrieveValue(Store::Hash(prefix), digests);
	
	for(auto it = digests.begin(); it != digests.end(); ++it)
	{
		//LogDebug("Board", "Retrieved digest: " + it->toString());
		if(fetch(Network::Link::Null, prefix, "/", *it, false))
			incoming(Network::Link::Null, prefix, "/", *it);
	}
	
	publish(prefix);
	subscribe(prefix);
}

Board::~Board(void)
{
	Interface::Instance->remove(urlPrefix(), this);
	
	String prefix = "/mail" + mName;
	unpublish(prefix);
	unsubscribe(prefix);
}

String Board::urlPrefix(void) const
{
	std::unique_lock<std::mutex> lock(mMutex);
	return "/mail" + mName;
}

bool Board::hasNew(void) const
{
	std::unique_lock<std::mutex> lock(mMutex);
	bool value = false;
	std::swap(mHasNew, value);
	return value;
}

int Board::unread(void) const
{
	// TODO
	return 0;
}

BinaryString Board::digest(void) const
{
	std::unique_lock<std::mutex> lock(mMutex);
	return mDigest;
}

bool Board::add(const Mail &mail, bool noIssue)
{
	{
		std::unique_lock<std::mutex> lock(mMutex);
		
		if(mail.empty() || mMails.contains(mail))
			return false;
	
		auto it = mMails.insert(mail);
		const Mail &m = *it.first;
		mUnorderedMails.append(&m);
	}
	
	if(!noIssue) issue("/mail" + mName, mail);
	process();
	publish("/mail" + mName);
	
	mCondition.notify_all();
	return true;
}

void Board::addMergeUrl(const String &url)
{
	std::unique_lock<std::mutex> lock(mMutex);
	mMergeUrls.insert(url);
}

void Board::removeMergeUrl(const String &url)
{
	std::unique_lock<std::mutex> lock(mMutex);
	mMergeUrls.erase(url);
}

void Board::process(void)
{
	// Private, no sync
	
	try {
		// Write messages to temporary file
		String tempFileName = File::TempName();
		File tempFile(tempFileName, File::Truncate);
		BinarySerializer serializer(&tempFile);
		for(auto it = mMails.begin(); it != mMails.end(); ++it)
			serializer << *it;
		tempFile.close();
		
		// Move to cache and process
		Resource resource;
		resource.cache(tempFileName, mName, "mail", mSecret);
		
		// Retrieve digest and store it
		mDigest = resource.digest();
		Store::Instance->storeValue(Store::Hash("/mail" + mName), mDigest, Store::Permanent);
		
		LogDebug("Board::process", "Board processed: " + mDigest.toString());
	}
	catch(const Exception &e)
	{
		LogWarn("Board::process", String("Board processing failed: ") + e.what());
	}
}

bool Board::anounce(const Network::Link &link, const String &prefix, const String &path, List<BinaryString> &targets)
{
	std::unique_lock<std::mutex> lock(mMutex);
	targets.clear();
	
	if(mDigest.empty()) 
		return false;
	
	targets.push_back(mDigest);
	return true;
}

bool Board::incoming(const Network::Link &link, const String &prefix, const String &path, const BinaryString &target)
{
	{
		std::unique_lock<std::mutex> lock(mMutex);
		if(target == mDigest) 
			return false;
	}
	
	if(fetch(link, prefix, path, target, true))
	{
		try {
			std::unique_lock<std::mutex> lock(mMutex);
			
			Resource resource(target, true);	// local only (already fetched)
			if(resource.type() != "mail")
				return false;
			
			Resource::Reader reader(&resource, mSecret);
			BinarySerializer serializer(&reader);
			Mail mail;
			unsigned count = 0;
			while(!!(serializer >> mail))
			{
				if(mail.empty())
					continue;
				
				if(!mMails.contains(mail))
				{
					auto it = mMails.insert(mail);
					const Mail &m = *it.first;
					mUnorderedMails.append(&m);
					
					++mUnread;
					mHasNew = true;
				}
				
				++count;
			}
			
			if(count == mMails.size())
			{
				mDigest = target;
			}
			else {
{'fraction_non_alphanumeric': 0.1642409808591338, 'fraction_numerical': 0.0022933756725765195, 'mean_word_length': 2.8162234937731405, 'pattern_counts': {'":': 0, '<': 33, '<?xml version=': 0, '>': 43, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 30, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7435410', 'n_tokens_mistral': 3873, 'n_tokens_neox': 3490, 'n_words': 843}