Index: src/ch/FilterDlg.cpp
===================================================================
diff -u -r0d5b67ee96b435d63f7bf075dc8e28603793b187 -rf3c80778cfee0736195e00274c78040f7908ac5b
--- src/ch/FilterDlg.cpp	(.../FilterDlg.cpp)	(revision 0d5b67ee96b435d63f7bf075dc8e28603793b187)
+++ src/ch/FilterDlg.cpp	(.../FilterDlg.cpp)	(revision f3c80778cfee0736195e00274c78040f7908ac5b)
@@ -347,17 +347,17 @@
 	m_ffFilter.SetUseSize1(m_bSize != 0);
 	m_ffFilter.SetUseSize2(m_bSize2 != 0);
 
-	m_ffFilter.SetSizeType1((chengine::TFileFilter::ESizeCompareType)m_ctlSizeType1.GetCurSel());
-	m_ffFilter.SetSizeType2((chengine::TFileFilter::ESizeCompareType)m_ctlSizeType2.GetCurSel());
+	m_ffFilter.SetSizeType1((chengine::ECompareType)m_ctlSizeType1.GetCurSel());
+	m_ffFilter.SetSizeType2((chengine::ECompareType)m_ctlSizeType2.GetCurSel());
 
 	m_ffFilter.SetSize1(static_cast<unsigned __int64>(m_uiSize1)*static_cast<unsigned __int64>(GetMultiplier(m_ctlSize1Multi.GetCurSel())));
 	m_ffFilter.SetSize2(static_cast<unsigned __int64>(m_uiSize2)*static_cast<unsigned __int64>(GetMultiplier(m_ctlSize2Multi.GetCurSel())));
 
 	// date
 	m_ffFilter.SetDateType((chengine::TFileFilter::EDateType)m_ctlDateType.GetCurSel());
 
-	m_ffFilter.SetDateCmpType1((chengine::TFileFilter::EDateCompareType)m_ctlDateType1.GetCurSel());
-	m_ffFilter.SetDateCmpType2((chengine::TFileFilter::EDateCompareType)m_ctlDateType2.GetCurSel());
+	m_ffFilter.SetDateCmpType1((chengine::ECompareType)m_ctlDateType1.GetCurSel());
+	m_ffFilter.SetDateCmpType2((chengine::ECompareType)m_ctlDateType2.GetCurSel());
 
 	CTime tDate;
 	CTime tTime;
Index: src/libchcore/TFileTime.cpp
===================================================================
diff -u -rb26ced3298e3e7e51d91f3ac70b56746786da83b -rf3c80778cfee0736195e00274c78040f7908ac5b
--- src/libchcore/TFileTime.cpp	(.../TFileTime.cpp)	(revision b26ced3298e3e7e51d91f3ac70b56746786da83b)
+++ src/libchcore/TFileTime.cpp	(.../TFileTime.cpp)	(revision f3c80778cfee0736195e00274c78040f7908ac5b)
@@ -53,6 +53,26 @@
 		return m_ftTime.dwHighDateTime != rSrc.m_ftTime.dwHighDateTime || m_ftTime.dwLowDateTime != rSrc.m_ftTime.dwLowDateTime;
 	}
 
+	bool TFileTime::operator<(const TFileTime& rSrc) const
+	{
+		return ToUInt64() < rSrc.ToUInt64();
+	}
+
+	bool TFileTime::operator<=(const TFileTime& rSrc) const
+	{
+		return ToUInt64() <= rSrc.ToUInt64();
+	}
+
+	bool TFileTime::operator>(const TFileTime& rSrc) const
+	{
+		return ToUInt64() > rSrc.ToUInt64();
+	}
+
+	bool TFileTime::operator>=(const TFileTime& rSrc) const
+	{
+		return ToUInt64() >= rSrc.ToUInt64();
+	}
+
 	void TFileTime::FromUInt64(unsigned long long ullTime)
 	{
 		ULARGE_INTEGER uli;
Index: src/libchcore/TFileTime.h
===================================================================
diff -u -rb26ced3298e3e7e51d91f3ac70b56746786da83b -rf3c80778cfee0736195e00274c78040f7908ac5b
--- src/libchcore/TFileTime.h	(.../TFileTime.h)	(revision b26ced3298e3e7e51d91f3ac70b56746786da83b)
+++ src/libchcore/TFileTime.h	(.../TFileTime.h)	(revision f3c80778cfee0736195e00274c78040f7908ac5b)
@@ -35,6 +35,10 @@
 
 		bool operator==(const TFileTime& rSrc) const;
 		bool operator!=(const TFileTime& rSrc) const;
+		bool operator<(const TFileTime& rSrc) const;
+		bool operator<=(const TFileTime& rSrc) const;
+		bool operator>(const TFileTime& rSrc) const;
+		bool operator>=(const TFileTime& rSrc) const;
 
 		void SetCurrentTime();
 		const FILETIME& GetAsFiletime() const;
Index: src/libchengine/ECompareType.h
===================================================================
diff -u
--- src/libchengine/ECompareType.h	(revision 0)
+++ src/libchengine/ECompareType.h	(revision f3c80778cfee0736195e00274c78040f7908ac5b)
@@ -0,0 +1,33 @@
+#pragma once
+
+namespace chengine
+{
+	enum ECompareType
+	{
+		eCmp_Less = 0,
+		eCmp_LessOrEqual = 1,
+		eCmp_Equal = 2,
+		eCmp_GreaterOrEqual = 3,
+		eCmp_Greater = 4
+	};
+
+	template<class T>
+	bool CompareByType(const T& value1, const T& value2, ECompareType eCmpType)
+	{
+		switch(eCmpType)
+		{
+		case eCmp_Less:
+			return value1 < value2;
+		case eCmp_LessOrEqual:
+			return value1 <= value2;
+		case eCmp_Equal:
+			return value1 == value2;
+		case eCmp_GreaterOrEqual:
+			return value1 >= value2;
+		case eCmp_Greater:
+			return value1 > value2;
+		}
+
+		throw std::runtime_error("Invalid compare type");
+	}
+}
Index: src/libchengine/FeedbackRule.cpp
===================================================================
diff -u
--- src/libchengine/FeedbackRule.cpp	(revision 0)
+++ src/libchengine/FeedbackRule.cpp	(revision f3c80778cfee0736195e00274c78040f7908ac5b)
@@ -0,0 +1,331 @@
+#include "stdafx.h"
+#include "FeedbackRule.h"
+#include "../libstring/TString.h"
+#include "../libstring/TStringArray.h"
+
+using namespace serializer;
+using namespace string;
+
+namespace chengine
+{
+	FeedbackRule::FeedbackRule() :
+		m_bUseMask(m_setModifications, false),
+		m_spaMask(m_setModifications),
+		m_bUseExcludeMask(m_setModifications, false),
+		m_spaExcludeMask(m_setModifications),
+		m_bUseDateCompare(m_setModifications, false),
+		m_cmpLastModified(m_setModifications, eCmp_Equal),
+		m_bUseSizeCompare(m_setModifications, false),
+		m_cmpSize(m_setModifications, eCmp_Equal),
+		m_eResult(m_setModifications, eResult_Unknown)
+	{
+		m_setModifications[FeedbackRuleEnum::eMod_Added] = true;
+	}
+
+
+	FeedbackRule::FeedbackRule(const FeedbackRule& rSrc) :
+		serializer::SerializableObject<FeedbackRuleEnum::eMod_Last>(rSrc),
+		m_bUseMask(rSrc.m_bUseMask, m_setModifications),
+		m_spaMask(rSrc.m_spaMask, m_setModifications),
+		m_bUseExcludeMask(rSrc.m_bUseExcludeMask, m_setModifications),
+		m_spaExcludeMask(rSrc.m_spaExcludeMask, m_setModifications),
+		m_bUseDateCompare(rSrc.m_bUseDateCompare, m_setModifications),
+		m_cmpLastModified(rSrc.m_cmpLastModified, m_setModifications),
+		m_bUseSizeCompare(rSrc.m_bUseSizeCompare, m_setModifications),
+		m_cmpSize(rSrc.m_cmpSize, m_setModifications),
+		m_eResult(rSrc.m_eResult, m_setModifications)
+	{
+	}
+
+	FeedbackRule& FeedbackRule::operator=(const FeedbackRule& rSrc)
+	{
+		if(this == &rSrc)
+			return *this;
+
+		__super::operator=(rSrc);
+
+		SetData(rSrc);
+
+		return *this;
+	}
+
+	bool FeedbackRule::operator==(const FeedbackRule& rSrc) const
+	{
+		if(m_bUseMask != rSrc.m_bUseMask)
+			return false;
+		if(m_spaMask != rSrc.m_spaMask)
+			return false;
+
+		if(m_bUseExcludeMask != rSrc.m_bUseExcludeMask)
+			return false;
+
+		if(m_spaExcludeMask != rSrc.m_spaExcludeMask)
+			return false;
+
+		if(m_bUseDateCompare != rSrc.m_bUseDateCompare)
+			return false;
+		if(m_cmpLastModified != rSrc.m_cmpLastModified)
+			return false;
+
+		if(m_bUseSizeCompare != rSrc.m_bUseSizeCompare)
+			return false;
+		if(m_cmpSize != rSrc.m_cmpSize)
+			return false;
+
+		if(m_eResult != rSrc.m_eResult)
+			return false;
+
+		return true;
+	}
+
+	bool FeedbackRule::operator!=(const FeedbackRule& rSrc) const
+	{
+		return !operator==(rSrc);
+	}
+
+	void FeedbackRule::SetData(const FeedbackRule& rSrc)
+	{
+		if(this == &rSrc)
+			return;
+
+		m_bUseMask = rSrc.m_bUseMask;
+		m_spaMask = rSrc.m_spaMask;
+		m_bUseExcludeMask = rSrc.m_bUseExcludeMask;
+		m_spaExcludeMask = rSrc.m_spaExcludeMask;
+		m_bUseDateCompare = rSrc.m_bUseDateCompare;
+		m_cmpLastModified = rSrc.m_cmpLastModified;
+		m_bUseSizeCompare = rSrc.m_bUseSizeCompare;
+		m_cmpSize = rSrc.m_cmpSize;
+		m_eResult = rSrc.m_eResult;
+	}
+
+	bool FeedbackRule::Matches(const TFileInfoPtr& spSrcFile, const TFileInfoPtr& spDstFile, EFeedbackResult& eResult) const
+	{
+		eResult = eResult_Unknown;
+
+		if(m_bUseMask)
+		{
+			if(!m_spaMask.Get().MatchesAny(spDstFile->GetFullFilePath().GetFileName().ToString()))
+				return false;
+		}
+		if(m_bUseExcludeMask)
+		{
+			if(m_spaExcludeMask.Get().MatchesAny(spDstFile->GetFullFilePath().GetFileName().ToString()))
+				return false;
+		}
+		if(m_bUseDateCompare)
+		{
+			if(!CompareByType(spSrcFile->GetLastWriteTime(), spDstFile->GetLastWriteTime(), m_cmpLastModified))
+				return false;
+		}
+
+		if(m_bUseSizeCompare)
+		{
+			if(!CompareByType(spSrcFile->GetLength64(), spDstFile->GetLength64(), m_cmpSize))
+				return false;
+		}
+
+		eResult = m_eResult;
+		return true;
+	}
+
+	void FeedbackRule::InitColumns(serializer::IColumnsDefinition& rColumns)
+	{
+		rColumns.AddColumn(_T("id"), ColumnType<object_id_t>::value);
+		rColumns.AddColumn(_T("use_mask"), IColumnsDefinition::eType_bool);
+		rColumns.AddColumn(_T("mask"), IColumnsDefinition::eType_string);
+		rColumns.AddColumn(_T("use_exclude_mask"), IColumnsDefinition::eType_bool);
+		rColumns.AddColumn(_T("exclude_mask"), IColumnsDefinition::eType_string);
+		rColumns.AddColumn(_T("use_date_compare"), IColumnsDefinition::eType_bool);
+		rColumns.AddColumn(_T("date_compare_type"), IColumnsDefinition::eType_int);
+		rColumns.AddColumn(_T("use_size_compare"), IColumnsDefinition::eType_bool);
+		rColumns.AddColumn(_T("size_compare_type"), IColumnsDefinition::eType_int);
+		rColumns.AddColumn(_T("result"), IColumnsDefinition::eType_int);
+	}
+
+	void FeedbackRule::Store(const ISerializerContainerPtr& spContainer) const
+	{
+		bool bAdded = m_setModifications[FeedbackRuleEnum::eMod_Added];
+		if(m_setModifications.any())
+		{
+			ISerializerRowData& rRow = spContainer->GetRow(m_oidObjectID, bAdded);
+
+			if(bAdded || m_setModifications[FeedbackRuleEnum::eMod_UseMask])
+				rRow.SetValue(_T("use_mask"), m_bUseMask);
+			if(bAdded || m_setModifications[FeedbackRuleEnum::eMod_Mask])
+				rRow.SetValue(_T("mask"), GetCombinedMask());
+
+			if(bAdded || m_setModifications[FeedbackRuleEnum::eMod_UseExcludeMask])
+				rRow.SetValue(_T("use_exclude_mask"), m_bUseExcludeMask);
+			if(bAdded || m_setModifications[FeedbackRuleEnum::eMod_ExcludeMask])
+				rRow.SetValue(_T("exclude_mask"), GetCombinedExcludeMask());
+
+			if(bAdded || m_setModifications[FeedbackRuleEnum::eMod_UseDateCompare])
+				rRow.SetValue(_T("use_date_compare"), m_bUseDateCompare);
+			if(bAdded || m_setModifications[FeedbackRuleEnum::eMod_DateCompare])
+				rRow.SetValue(_T("date_compare_type"), m_cmpLastModified);
+
+			if(bAdded || m_setModifications[FeedbackRuleEnum::eMod_UseSizeCompare])
+				rRow.SetValue(_T("use_size_compare"), m_bUseSizeCompare);
+			if(bAdded || m_setModifications[FeedbackRuleEnum::eMod_SizeCompare])
+				rRow.SetValue(_T("size_compare_type"), m_cmpSize);
+
+			if(bAdded || m_setModifications[FeedbackRuleEnum::eMod_Result])
+				rRow.SetValue(_T("result"), m_eResult);
+
+			m_setModifications.reset();
+		}
+	}
+
+	void FeedbackRule::Load(const ISerializerRowReaderPtr& spRowReader)
+	{
+		TString strMask;
+
+		spRowReader->GetValue(_T("use_mask"), m_bUseMask.Modify());
+		spRowReader->GetValue(_T("mask"), strMask);
+		SetCombinedMask(strMask);
+
+		spRowReader->GetValue(_T("use_exclude_mask"), m_bUseExcludeMask.Modify());
+		spRowReader->GetValue(_T("exclude_mask"), strMask);
+		SetCombinedExcludeMask(strMask);
+
+		spRowReader->GetValue(_T("use_date_compare"), m_bUseDateCompare.Modify());
+		spRowReader->GetValue(_T("date_compare_type"), *(int*)&m_cmpLastModified.Modify());
+
+		spRowReader->GetValue(_T("use_size_compare"), m_bUseSizeCompare.Modify());
+		spRowReader->GetValue(_T("size_compare_type"), *(int*)&m_cmpSize.Modify());
+
+		spRowReader->GetValue(_T("result"), *(int*)&m_eResult.Modify());
+
+		m_setModifications.reset();
+	}
+
+	void FeedbackRule::StoreInConfig(TConfig& rConfig) const
+	{
+		SetConfigValue(rConfig, _T("IncludeMask.Use"), m_bUseMask.Get());
+		SetConfigValue(rConfig, _T("IncludeMask.MaskList.Mask"), m_spaMask.Get().ToSerializedStringArray());
+
+		SetConfigValue(rConfig, _T("ExcludeMask.Use"), m_bUseExcludeMask.Get());
+		SetConfigValue(rConfig, _T("ExcludeMask.MaskList.Mask"), m_spaExcludeMask.Get().ToSerializedStringArray());
+
+		SetConfigValue(rConfig, _T("DateCompare.Use"), m_bUseDateCompare.Get());
+		SetConfigValue(rConfig, _T("DateCompare.CompareType"), m_cmpLastModified.Get());
+
+		SetConfigValue(rConfig, _T("Result"), m_eResult.Get());
+	}
+
+	void FeedbackRule::ReadFromConfig(const TConfig& rConfig)
+	{
+		if(!GetConfigValue(rConfig, _T("IncludeMask.Use"), m_bUseMask.Modify()))
+			m_bUseMask = false;
+
+		TStringArray arrMask;
+		m_spaMask.Modify().Clear();
+		GetConfigValue(rConfig, _T("IncludeMask.MaskList.Mask"), arrMask);
+		m_spaMask.Modify().FromSerializedStringArray(arrMask);
+
+		if(!GetConfigValue(rConfig, _T("ExcludeMask.Use"), m_bUseExcludeMask.Modify()))
+			m_bUseExcludeMask = false;
+
+		m_spaExcludeMask.Modify().Clear();
+		GetConfigValue(rConfig, _T("ExcludeMask.MaskList.Mask"), arrMask);
+		m_spaExcludeMask.Modify().FromSerializedStringArray(arrMask);
+
+		if(!GetConfigValue(rConfig, _T("DateCompare.Use"), m_bUseDateCompare.Modify()))
+			m_bUseDateCompare = false;
+		if(!GetConfigValue(rConfig, _T("DateCompare.CompareType"), *(int*)m_cmpLastModified.Modify()))
+			m_cmpLastModified = eCmp_Equal;
+
+		if(!GetConfigValue(rConfig, _T("SizeCompare.Use"), m_bUseSizeCompare.Modify()))
+			m_bUseSizeCompare = false;
+		if(!GetConfigValue(rConfig, _T("SizeCompare.CompareType"), *(int*)m_cmpSize.Modify()))
+			m_cmpSize = eCmp_Equal;
+
+		if(!GetConfigValue(rConfig, _T("Result"), *(int*)m_eResult.Modify()))
+			m_eResult = eResult_Unknown;
+	}
+
+	void FeedbackRule::SetUseMask(bool bUseMask)
+	{
+		m_bUseMask = bUseMask;
+	}
+
+	bool FeedbackRule::GetUseMask() const
+	{
+		return m_bUseMask;
+	}
+
+	bool FeedbackRule::GetUseExcludeMask() const
+	{
+		return m_bUseExcludeMask;
+	}
+
+	void FeedbackRule::SetUseExcludeMask(bool bUseExcludeMask)
+	{
+		m_bUseExcludeMask = bUseExcludeMask;
+	}
+
+	bool FeedbackRule::GetUseDateCompare() const
+	{
+		return m_bUseDateCompare;
+	}
+
+	void FeedbackRule::SetUseDateCompare(bool bUseDateCompare)
+	{
+		m_bUseDateCompare = bUseDateCompare;
+	}
+
+	ECompareType FeedbackRule::GetDateCompareType() const
+	{
+		return m_cmpLastModified;
+	}
+
+	void FeedbackRule::SetDateCompareType(ECompareType eCmpType)
+	{
+		m_cmpLastModified = eCmpType;
+	}
+
+	bool FeedbackRule::GetUseSizeCompare() const
+	{
+		return m_bUseSizeCompare;
+	}
+
+	void FeedbackRule::SetUseSizeCompare(bool bUseSizeCompare)
+	{
+		m_bUseSizeCompare = bUseSizeCompare;
+	}
+
+	ECompareType FeedbackRule::GetSizeCompareType() const
+	{
+		return m_cmpSize;
+	}
+
+	void FeedbackRule::SetSizeCompareType(ECompareType eCmpType)
+	{
+		m_cmpSize = eCmpType;
+	}
+
+	TString FeedbackRule::GetCombinedMask() const
+	{
+		return m_spaMask.Get().ToString();
+	}
+
+	void FeedbackRule::SetCombinedMask(const TString& strMask)
+	{
+		TStringPatternArray& rPatterns = m_spaMask.Modify();
+		rPatterns.Clear();
+		rPatterns.FromString(strMask);
+	}
+
+	TString FeedbackRule::GetCombinedExcludeMask() const
+	{
+		return m_spaExcludeMask.Get().ToString();
+	}
+
+	void FeedbackRule::SetCombinedExcludeMask(const TString& strMask)
+	{
+		TStringPatternArray& rPatterns = m_spaExcludeMask.Modify();
+		rPatterns.Clear();
+		rPatterns.FromString(strMask);
+	}
+}
Index: src/libchengine/FeedbackRule.h
===================================================================
diff -u
--- src/libchengine/FeedbackRule.h	(revision 0)
+++ src/libchengine/FeedbackRule.h	(revision f3c80778cfee0736195e00274c78040f7908ac5b)
@@ -0,0 +1,101 @@
+#pragma once
+
+#include "libchengine.h"
+#include "../libserializer/SerializableObject.h"
+#include "../libstring/TStringPatternArray.h"
+#include "ECompareType.h"
+#include "EFeedbackResult.h"
+#include "../libserializer/SerializerDataTypes.h"
+#include <bitset>
+#include "../libserializer/TSharedModificationTracker.h"
+#include "TFileInfo.h"
+#include "TConfig.h"
+
+namespace chengine
+{
+	namespace FeedbackRuleEnum
+	{
+		enum EModifications
+		{
+			eMod_Added,
+			eMod_UseMask,
+			eMod_Mask,
+			eMod_UseExcludeMask,
+			eMod_ExcludeMask,
+			eMod_UseSizeCompare,
+			eMod_SizeCompare,
+			eMod_UseDateCompare,
+			eMod_DateCompare,
+			eMod_Result,
+
+			eMod_Last
+		};
+	}
+
+#pragma warning(push)
+#pragma warning(disable: 4251)
+	class LIBCHENGINE_API FeedbackRule : public serializer::SerializableObject<FeedbackRuleEnum::eMod_Last>
+	{
+	public:
+		FeedbackRule();
+		FeedbackRule(const FeedbackRule& rSrc);
+		FeedbackRule& operator=(const FeedbackRule& rSrc);
+
+		bool operator==(const FeedbackRule& rSrc) const;
+		bool operator!=(const FeedbackRule& rSrc) const;
+
+		void SetData(const FeedbackRule& rSrc);
+
+		bool Matches(const TFileInfoPtr& spSrcFile, const TFileInfoPtr& spDstFile, EFeedbackResult& eResult) const;
+
+		void Store(const serializer::ISerializerContainerPtr& spContainer) const override;
+		void Load(const serializer::ISerializerRowReaderPtr& spRowReader) override;
+		static void InitColumns(serializer::IColumnsDefinition& rColumns);
+
+		void StoreInConfig(TConfig& rConfig) const;
+		void ReadFromConfig(const TConfig& rConfig);
+
+		// get/set
+		// atrributes access
+		bool GetUseMask() const;
+		void SetUseMask(bool bUseMask);
+
+		string::TString GetCombinedMask() const;
+		void SetCombinedMask(const string::TString& pMask);
+
+		bool GetUseExcludeMask() const;
+		void SetUseExcludeMask(bool bUseExcludeMask);
+
+		string::TString GetCombinedExcludeMask() const;
+		void SetCombinedExcludeMask(const string::TString& pMask);
+
+		bool GetUseDateCompare() const;
+		void SetUseDateCompare(bool bUseDateCompare);
+
+		ECompareType GetDateCompareType() const;
+		void SetDateCompareType(ECompareType eCmpType);
+		
+		bool GetUseSizeCompare() const;
+		void SetUseSizeCompare(bool bUseSizeCompare);
+
+		ECompareType GetSizeCompareType() const;
+		void SetSizeCompareType(ECompareType eCmpType);
+
+	private:
+
+		// object identification
+
+		serializer::TSharedModificationTracker<bool, Bitset, FeedbackRuleEnum::eMod_UseMask> m_bUseMask;
+		serializer::TSharedModificationTracker<string::TStringPatternArray, Bitset, FeedbackRuleEnum::eMod_Mask> m_spaMask;
+		serializer::TSharedModificationTracker<bool, Bitset, FeedbackRuleEnum::eMod_UseExcludeMask> m_bUseExcludeMask;
+		serializer::TSharedModificationTracker<string::TStringPatternArray, Bitset, FeedbackRuleEnum::eMod_ExcludeMask> m_spaExcludeMask;
+
+		serializer::TSharedModificationTracker<bool, Bitset, FeedbackRuleEnum::eMod_UseDateCompare> m_bUseDateCompare;
+		serializer::TSharedModificationTracker<ECompareType, Bitset, FeedbackRuleEnum::eMod_DateCompare> m_cmpLastModified;
+		serializer::TSharedModificationTracker<bool, Bitset, FeedbackRuleEnum::eMod_UseSizeCompare> m_bUseSizeCompare;
+		serializer::TSharedModificationTracker<ECompareType, Bitset, FeedbackRuleEnum::eMod_SizeCompare> m_cmpSize;
+
+		serializer::TSharedModificationTracker<EFeedbackResult, Bitset, FeedbackRuleEnum::eMod_Result> m_eResult;
+	};
+#pragma warning(pop)
+}
Index: src/libchengine/FeedbackRuleList.cpp
===================================================================
diff -u
--- src/libchengine/FeedbackRuleList.cpp	(revision 0)
+++ src/libchengine/FeedbackRuleList.cpp	(revision f3c80778cfee0736195e00274c78040f7908ac5b)
@@ -0,0 +1,65 @@
+#include "stdafx.h"
+#include "FeedbackRuleList.h"
+#include "TConfigArray.h"
+#include "../libserializer/IColumnsDefinition.h"
+#include "TFileFilter.h"
+
+using namespace string;
+using namespace serializer;
+
+namespace chengine
+{
+	EFeedbackResult FeedbackRuleList::Matches(const TFileInfoPtr& spSrcFile, const TFileInfoPtr& spDstFile) const
+	{
+		if(m_vEntries.empty())
+			return eResult_Unknown;
+
+		for(const FeedbackRule& rRule : m_vEntries)
+		{
+			EFeedbackResult eResult = eResult_Unknown;
+			if(rRule.Matches(spSrcFile, spDstFile, eResult))
+				return eResult;
+		}
+
+		return eResult_Unknown;
+	}
+
+	void FeedbackRuleList::InitColumns(const serializer::ISerializerContainerPtr& spContainer) const
+	{
+		IColumnsDefinition& rColumns = spContainer->GetColumnsDefinition();
+		if(rColumns.IsEmpty())
+			TFileFilter::InitColumns(rColumns);
+	}
+
+	void FeedbackRuleList::StoreInConfig(TConfig& rConfig, PCTSTR pszNodeName) const
+	{
+		rConfig.DeleteNode(pszNodeName);
+		for(const FeedbackRule& rRule : m_vEntries)
+		{
+			TConfig cfgNode;
+			rRule.StoreInConfig(cfgNode);
+
+			TString strNode = TString(pszNodeName) + _T(".RuleDefinition");
+			rConfig.AddSubConfig(strNode.c_str(), cfgNode);
+		}
+	}
+
+	bool FeedbackRuleList::ReadFromConfig(const TConfig& rConfig, PCTSTR pszNodeName)
+	{
+		m_vEntries.clear();
+
+		TConfigArray vConfigs;
+		if(!rConfig.ExtractMultiSubConfigs(pszNodeName, vConfigs))
+			return false;
+
+		for(size_t stIndex = 0; stIndex < vConfigs.GetCount(); ++stIndex)
+		{
+			const TConfig& rCfg = vConfigs.GetAt(stIndex);
+			FeedbackRule rule;
+			rule.ReadFromConfig(rCfg);
+
+			m_vEntries.push_back(rule);
+		}
+		return true;
+	}
+}
Index: src/libchengine/FeedbackRuleList.h
===================================================================
diff -u
--- src/libchengine/FeedbackRuleList.h	(revision 0)
+++ src/libchengine/FeedbackRuleList.h	(revision f3c80778cfee0736195e00274c78040f7908ac5b)
@@ -0,0 +1,26 @@
+#pragma once
+
+#include "libchengine.h"
+#include "FeedbackRule.h"
+#include "TConfig.h"
+#include "../libserializer/SerializableContainer.h"
+
+namespace chengine
+{
+#pragma warning(push)
+#pragma warning(disable: 4251)
+
+	class LIBCHENGINE_API FeedbackRuleList : public serializer::SerializableContainer<FeedbackRule>
+	{
+	public:
+		EFeedbackResult Matches(const TFileInfoPtr& spSrcFile, const TFileInfoPtr& spDstFile) const;
+
+		void InitColumns(const serializer::ISerializerContainerPtr& spContainer) const override;
+
+		void StoreInConfig(TConfig& rConfig, PCTSTR pszNodeName) const;
+		bool ReadFromConfig(const TConfig& rConfig, PCTSTR pszNodeName);
+	};
+#pragma warning(pop)
+}
+
+CONFIG_MEMBER_SERIALIZATION(FeedbackRuleList)
Index: src/libchengine/TFileFilter.cpp
===================================================================
diff -u -rfadd6c9c628de875716d96c3a497b5bc6c8dca8a -rf3c80778cfee0736195e00274c78040f7908ac5b
--- src/libchengine/TFileFilter.cpp	(.../TFileFilter.cpp)	(revision fadd6c9c628de875716d96c3a497b5bc6c8dca8a)
+++ src/libchengine/TFileFilter.cpp	(.../TFileFilter.cpp)	(revision f3c80778cfee0736195e00274c78040f7908ac5b)
@@ -36,19 +36,19 @@
 		m_bUseExcludeMask(m_setModifications, false),
 		m_astrExcludeMask(m_setModifications),
 		m_bUseSize1(m_setModifications, false),
-		m_eSizeCmpType1(m_setModifications, eSizeCmp_Greater),
+		m_eSizeCmpType1(m_setModifications, eCmp_Greater),
 		m_ullSize1(m_setModifications, 0),
 		m_bUseSize2(m_setModifications, false),
-		m_eSizeCmpType2(m_setModifications, eSizeCmp_Less),
+		m_eSizeCmpType2(m_setModifications, eCmp_Less),
 		m_ullSize2(m_setModifications, 0),
 		m_eDateType(m_setModifications, eDateType_Created),
 		m_bUseDateTime1(m_setModifications, false),
-		m_eDateCmpType1(m_setModifications, eDateCmp_Greater),
+		m_eDateCmpType1(m_setModifications, eCmp_Greater),
 		m_bUseDate1(m_setModifications, false),
 		m_bUseTime1(m_setModifications, false),
 		m_tDateTime1(m_setModifications),
 		m_bUseDateTime2(m_setModifications, false),
-		m_eDateCmpType2(m_setModifications, eDateCmp_Less),
+		m_eDateCmpType2(m_setModifications, eCmp_Less),
 		m_bUseDate2(m_setModifications, false),
 		m_bUseTime2(m_setModifications, false),
 		m_tDateTime2(m_setModifications),
@@ -185,43 +185,8 @@
 		m_oidObjectID = rFilter.m_oidObjectID;
 		m_setModifications = rFilter.m_setModifications;
 
-		// files mask
-		m_bUseMask = rFilter.m_bUseMask;
-		m_astrMask = rFilter.m_astrMask;
+		SetData(rFilter);
 
-		m_bUseExcludeMask = rFilter.m_bUseExcludeMask;
-		m_astrExcludeMask = rFilter.m_astrExcludeMask;
-
-		// size filtering
-		m_bUseSize1 = rFilter.m_bUseSize1;
-		m_eSizeCmpType1 = rFilter.m_eSizeCmpType1;
-		m_ullSize1 = rFilter.m_ullSize1;
-		m_bUseSize2 = rFilter.m_bUseSize2;
-		m_eSizeCmpType2 = rFilter.m_eSizeCmpType2;
-		m_ullSize2 = rFilter.m_ullSize2;
-
-		// date filtering
-		m_bUseDateTime1 = rFilter.m_bUseDateTime1;
-		m_eDateType = rFilter.m_eDateType;
-		m_eDateCmpType1 = rFilter.m_eDateCmpType1;
-		m_bUseDate1 = rFilter.m_bUseDate1;
-		m_bUseTime1 = rFilter.m_bUseTime1;
-		m_tDateTime1 = rFilter.m_tDateTime1;
-
-		m_bUseDateTime2 = rFilter.m_bUseDateTime2;
-		m_eDateCmpType2 = rFilter.m_eDateCmpType2;
-		m_bUseDate2 = rFilter.m_bUseDate2;
-		m_bUseTime2 = rFilter.m_bUseTime2;
-		m_tDateTime2 = rFilter.m_tDateTime2;
-
-		// attribute filtering
-		m_bUseAttributes = rFilter.m_bUseAttributes;
-		m_iArchive = rFilter.m_iArchive;
-		m_iReadOnly = rFilter.m_iReadOnly;
-		m_iHidden = rFilter.m_iHidden;
-		m_iSystem = rFilter.m_iSystem;
-		m_iDirectory = rFilter.m_iDirectory;
-
 		return *this;
 	}
 
@@ -305,13 +270,13 @@
 		if (!GetConfigValue(rConfig, _T("SizeA.Use"), m_bUseSize1.Modify()))
 			m_bUseSize1 = false;
 		if (!GetConfigValue(rConfig, _T("SizeA.FilteringType"), *(int*)m_eSizeCmpType1.Modify()))
-			m_eSizeCmpType1 = eSizeCmp_Equal;
+			m_eSizeCmpType1 = eCmp_Equal;
 		if (!GetConfigValue(rConfig, _T("SizeA.Value"), m_ullSize1.Modify()))
 			m_ullSize1 = 0;
 		if (!GetConfigValue(rConfig, _T("SizeB.Use"), m_bUseSize2.Modify()))
 			m_bUseSize2 = false;
 		if (!GetConfigValue(rConfig, _T("SizeB.FilteringType"), *(int*)m_eSizeCmpType2.Modify()))
-			m_eSizeCmpType2 = eSizeCmp_Equal;
+			m_eSizeCmpType2 = eCmp_Equal;
 		if (!GetConfigValue(rConfig, _T("SizeB.Value"), m_ullSize2.Modify()))
 			m_ullSize2 = 0;
 
@@ -321,7 +286,7 @@
 		if (!GetConfigValue(rConfig, _T("DateA.Type"), *(int*)m_eDateType.Modify()))	// created/last modified/last accessed
 			m_eDateType = eDateType_Created;
 		if (!GetConfigValue(rConfig, _T("DateA.FilteringType"), *(int*)m_eDateCmpType1.Modify()))	// before/after
-			m_eDateCmpType1 = eDateCmp_Equal;
+			m_eDateCmpType1 = eCmp_Equal;
 		if (!GetConfigValue(rConfig, _T("DateA.EnableDatePart"), m_bUseDate1.Modify()))
 			m_bUseDate1 = false;
 		if (!GetConfigValue(rConfig, _T("DateA.EnableTimePart"), m_bUseTime1.Modify()))
@@ -333,7 +298,7 @@
 		if (!GetConfigValue(rConfig, _T("DateB.Type"), m_bUseDateTime2.Modify()))
 			m_bUseDateTime2 = false;
 		if (!GetConfigValue(rConfig, _T("DateB.FilteringType"), *(int*)m_eDateCmpType2.Modify()))
-			m_eDateCmpType2 = eDateCmp_Equal;
+			m_eDateCmpType2 = eCmp_Equal;
 		if (!GetConfigValue(rConfig, _T("DateB.EnableDatePart"), m_bUseDate2.Modify()))
 			m_bUseDate2 = false;
 
@@ -377,23 +342,23 @@
 		{
 			switch (m_eSizeCmpType1)
 			{
-			case eSizeCmp_Less:
+			case eCmp_Less:
 				if (m_ullSize1 <= spInfo->GetLength64())
 					return false;
 				break;
-			case eSizeCmp_LessOrEqual:
+			case eCmp_LessOrEqual:
 				if (m_ullSize1 < spInfo->GetLength64())
 					return false;
 				break;
-			case eSizeCmp_Equal:
+			case eCmp_Equal:
 				if (m_ullSize1 != spInfo->GetLength64())
 					return false;
 				break;
-			case eSizeCmp_GreaterOrEqual:
+			case eCmp_GreaterOrEqual:
 				if (m_ullSize1 > spInfo->GetLength64())
 					return false;
 				break;
-			case eSizeCmp_Greater:
+			case eCmp_Greater:
 				if (m_ullSize1 >= spInfo->GetLength64())
 					return false;
 				break;
@@ -404,23 +369,23 @@
 			{
 				switch (m_eSizeCmpType2)
 				{
-				case eSizeCmp_Less:
+				case eCmp_Less:
 					if (m_ullSize2 <= spInfo->GetLength64())
 						return false;
 					break;
-				case eSizeCmp_LessOrEqual:
+				case eCmp_LessOrEqual:
 					if (m_ullSize2 < spInfo->GetLength64())
 						return false;
 					break;
-				case eSizeCmp_Equal:
+				case eCmp_Equal:
 					if (m_ullSize2 != spInfo->GetLength64())
 						return false;
 					break;
-				case eSizeCmp_GreaterOrEqual:
+				case eCmp_GreaterOrEqual:
 					if (m_ullSize2 > spInfo->GetLength64())
 						return false;
 					break;
-				case eSizeCmp_Greater:
+				case eCmp_Greater:
 					if (m_ullSize2 >= spInfo->GetLength64())
 						return false;
 					break;
@@ -451,23 +416,23 @@
 			// ... and comparing
 			switch (m_eDateCmpType1)
 			{
-			case eDateCmp_Less:
+			case eCmp_Less:
 				if (tDiff >= 0)
 					return false;
 				break;
-			case eDateCmp_LessOrEqual:
+			case eCmp_LessOrEqual:
 				if (tDiff > 0)
 					return false;
 				break;
-			case eDateCmp_Equal:
+			case eCmp_Equal:
 				if (tDiff != 0)
 					return false;
 				break;
-			case eDateCmp_GreaterOrEqual:
+			case eCmp_GreaterOrEqual:
 				if (tDiff < 0)
 					return false;
 				break;
-			case eDateCmp_Greater:
+			case eCmp_Greater:
 				if (tDiff <= 0)
 					return false;
 				break;
@@ -481,23 +446,23 @@
 				// ... comparing
 				switch (m_eDateCmpType2)
 				{
-				case eDateCmp_Less:
+				case eCmp_Less:
 					if (tDiff >= 0)
 						return false;
 					break;
-				case eDateCmp_LessOrEqual:
+				case eCmp_LessOrEqual:
 					if (tDiff > 0)
 						return false;
 					break;
-				case eDateCmp_Equal:
+				case eCmp_Equal:
 					if (tDiff != 0)
 						return false;
 					break;
-				case eDateCmp_GreaterOrEqual:
+				case eCmp_GreaterOrEqual:
 					if (tDiff < 0)
 						return false;
 					break;
-				case eDateCmp_Greater:
+				case eCmp_Greater:
 					if (tDiff <= 0)
 						return false;
 					break;
@@ -749,12 +714,12 @@
 		m_bUseSize1 = bUseSize1;
 	}
 
-	TFileFilter::ESizeCompareType TFileFilter::GetSizeType1() const
+	ECompareType TFileFilter::GetSizeType1() const
 	{
 		return m_eSizeCmpType1;
 	}
 
-	void TFileFilter::SetSizeType1(ESizeCompareType eSizeType1)
+	void TFileFilter::SetSizeType1(ECompareType eSizeType1)
 	{
 		m_eSizeCmpType1 = eSizeType1;
 	}
@@ -779,12 +744,12 @@
 		m_bUseSize2 = bUseSize2;
 	}
 
-	TFileFilter::ESizeCompareType TFileFilter::GetSizeType2() const
+	ECompareType TFileFilter::GetSizeType2() const
 	{
 		return m_eSizeCmpType2;
 	}
 
-	void TFileFilter::SetSizeType2(ESizeCompareType eSizeType2)
+	void TFileFilter::SetSizeType2(ECompareType eSizeType2)
 	{
 		m_eSizeCmpType2 = eSizeType2;
 	}
@@ -819,12 +784,12 @@
 		m_bUseDateTime1 = bUseDateTime1;
 	}
 
-	TFileFilter::EDateCompareType TFileFilter::GetDateCmpType1() const
+	ECompareType TFileFilter::GetDateCmpType1() const
 	{
 		return m_eDateCmpType1;
 	}
 
-	void TFileFilter::SetDateCmpType1(TFileFilter::EDateCompareType eCmpType1)
+	void TFileFilter::SetDateCmpType1(ECompareType eCmpType1)
 	{
 		m_eDateCmpType1 = eCmpType1;
 	}
@@ -869,12 +834,12 @@
 		m_bUseDateTime2 = bUseDateTime2;
 	}
 
-	TFileFilter::EDateCompareType TFileFilter::GetDateCmpType2() const
+	ECompareType TFileFilter::GetDateCmpType2() const
 	{
 		return m_eDateCmpType2;
 	}
 
-	void TFileFilter::SetDateCmpType2(TFileFilter::EDateCompareType eCmpType2)
+	void TFileFilter::SetDateCmpType2(ECompareType eCmpType2)
 	{
 		m_eDateCmpType2 = eCmpType2;
 	}
Index: src/libchengine/TFileFilter.h
===================================================================
diff -u -rfadd6c9c628de875716d96c3a497b5bc6c8dca8a -rf3c80778cfee0736195e00274c78040f7908ac5b
--- src/libchengine/TFileFilter.h	(.../TFileFilter.h)	(revision fadd6c9c628de875716d96c3a497b5bc6c8dca8a)
+++ src/libchengine/TFileFilter.h	(.../TFileFilter.h)	(revision f3c80778cfee0736195e00274c78040f7908ac5b)
@@ -24,6 +24,7 @@
 #include <bitset>
 #include "../libstring/TStringPatternArray.h"
 #include "../libserializer/TSharedModificationTracker.h"
+#include "ECompareType.h"
 
 namespace chengine
 {
@@ -34,24 +35,6 @@
 	class LIBCHENGINE_API TFileFilter
 	{
 	public:
-		enum ESizeCompareType
-		{
-			eSizeCmp_Less = 0,
-			eSizeCmp_LessOrEqual = 1,
-			eSizeCmp_Equal = 2,
-			eSizeCmp_GreaterOrEqual = 3,
-			eSizeCmp_Greater = 4
-		};
-
-		enum EDateCompareType
-		{
-			eDateCmp_Less = 0,
-			eDateCmp_LessOrEqual = 1,
-			eDateCmp_Equal = 2,
-			eDateCmp_GreaterOrEqual = 3,
-			eDateCmp_Greater = 4
-		};
-
 		enum EDateType
 		{
 			eDateType_Created = 0,
@@ -101,17 +84,17 @@
 		bool GetUseSize1() const;
 		void SetUseSize1(bool bUseSize1);
 
-		ESizeCompareType GetSizeType1() const;
-		void SetSizeType1(ESizeCompareType eSizeType1);
+		ECompareType GetSizeType1() const;
+		void SetSizeType1(ECompareType eSizeType1);
 
 		unsigned long long GetSize1() const;
 		void SetSize1(unsigned long long ullSize1);
 
 		bool GetUseSize2() const;
 		void SetUseSize2(bool bUseSize2);
 
-		ESizeCompareType GetSizeType2() const;
-		void SetSizeType2(ESizeCompareType eSizeType2);
+		ECompareType GetSizeType2() const;
+		void SetSizeType2(ECompareType eSizeType2);
 
 		unsigned long long GetSize2() const;
 		void SetSize2(unsigned long long ullSize2);
@@ -124,8 +107,8 @@
 		bool GetUseDateTime1() const;
 		void SetUseDateTime1(bool bUseDateTime1);
 
-		TFileFilter::EDateCompareType GetDateCmpType1() const;
-		void SetDateCmpType1(TFileFilter::EDateCompareType eCmpType1);
+		ECompareType GetDateCmpType1() const;
+		void SetDateCmpType1(ECompareType eCmpType1);
 
 		bool GetUseDate1() const;
 		void SetUseDate1(bool tDate1);
@@ -140,8 +123,8 @@
 		bool GetUseDateTime2() const;
 		void SetUseDateTime2(bool bUseDateTime2);
 
-		TFileFilter::EDateCompareType GetDateCmpType2() const;
-		void SetDateCmpType2(TFileFilter::EDateCompareType eCmpType2);
+		ECompareType GetDateCmpType2() const;
+		void SetDateCmpType2(ECompareType eCmpType2);
 
 		bool GetUseDate2() const;
 		void SetUseDate2(bool tDate2);
@@ -225,26 +208,26 @@
 
 		// size filtering
 		serializer::TSharedModificationTracker<bool, Bitset, eMod_UseSize1> m_bUseSize1;
-		serializer::TSharedModificationTracker<ESizeCompareType, Bitset, eMod_SizeCmpType1> m_eSizeCmpType1;
+		serializer::TSharedModificationTracker<ECompareType, Bitset, eMod_SizeCmpType1> m_eSizeCmpType1;
 		serializer::TSharedModificationTracker<unsigned long long, Bitset, eMod_Size1> m_ullSize1;
 
 		serializer::TSharedModificationTracker<bool, Bitset, eMod_UseSize2> m_bUseSize2;
-		serializer::TSharedModificationTracker<ESizeCompareType, Bitset, eMod_SizeCmpType2> m_eSizeCmpType2;
+		serializer::TSharedModificationTracker<ECompareType, Bitset, eMod_SizeCmpType2> m_eSizeCmpType2;
 		serializer::TSharedModificationTracker<unsigned long long, Bitset, eMod_Size2> m_ullSize2;
 
 		// date filtering
 		serializer::TSharedModificationTracker<EDateType, Bitset, eMod_DateType> m_eDateType;	// created/last modified/last accessed
 
 		serializer::TSharedModificationTracker<bool, Bitset, eMod_UseDateTime1> m_bUseDateTime1;
 
-		serializer::TSharedModificationTracker<EDateCompareType, Bitset, eMod_DateCmpType1> m_eDateCmpType1;	// before/after
+		serializer::TSharedModificationTracker<ECompareType, Bitset, eMod_DateCmpType1> m_eDateCmpType1;	// before/after
 		serializer::TSharedModificationTracker<bool, Bitset, eMod_UseDate1> m_bUseDate1;
 		serializer::TSharedModificationTracker<bool, Bitset, eMod_UseTime1> m_bUseTime1;
 		serializer::TSharedModificationTracker<TDateTime, Bitset, eMod_DateTime1> m_tDateTime1;
 
 		serializer::TSharedModificationTracker<bool, Bitset, eMod_UseDateTime2> m_bUseDateTime2;
 
-		serializer::TSharedModificationTracker<EDateCompareType, Bitset, eMod_DateCmpType2> m_eDateCmpType2;
+		serializer::TSharedModificationTracker<ECompareType, Bitset, eMod_DateCmpType2> m_eDateCmpType2;
 		serializer::TSharedModificationTracker<bool, Bitset, eMod_UseDate2> m_bUseDate2;
 		serializer::TSharedModificationTracker<bool, Bitset, eMod_UseTime2> m_bUseTime2;
 		serializer::TSharedModificationTracker<TDateTime, Bitset, eMod_DateTime2> m_tDateTime2;
Index: src/libchengine/TFileFiltersArray.cpp
===================================================================
diff -u -r85b07e753393f661f7d8f528e4238ebb6e9e1204 -rf3c80778cfee0736195e00274c78040f7908ac5b
--- src/libchengine/TFileFiltersArray.cpp	(.../TFileFiltersArray.cpp)	(revision 85b07e753393f661f7d8f528e4238ebb6e9e1204)
+++ src/libchengine/TFileFiltersArray.cpp	(.../TFileFiltersArray.cpp)	(revision f3c80778cfee0736195e00274c78040f7908ac5b)
@@ -28,21 +28,13 @@
 
 namespace chengine
 {
-	TFileFiltersArray::TFileFiltersArray()
-	{
-	}
-
-	TFileFiltersArray::~TFileFiltersArray()
-	{
-	}
-
 	bool TFileFiltersArray::Match(const TFileInfoPtr& spInfo) const
 	{
-		if(m_vFilters.empty())
+		if(m_vEntries.empty())
 			return true;
 
 		// if only one of the filters matches - return true
-		for(std::vector<TFileFilter>::const_iterator iterFilter = m_vFilters.begin(); iterFilter != m_vFilters.end(); ++iterFilter)
+		for(std::vector<TFileFilter>::const_iterator iterFilter = m_vEntries.begin(); iterFilter != m_vEntries.end(); ++iterFilter)
 		{
 			if((*iterFilter).Match(spInfo))
 				return true;
@@ -54,7 +46,7 @@
 	void TFileFiltersArray::StoreInConfig(TConfig& rConfig, PCTSTR pszNodeName) const
 	{
 		rConfig.DeleteNode(pszNodeName);
-		for(const TFileFilter& rFilter : m_vFilters)
+		for(const TFileFilter& rFilter : m_vEntries)
 		{
 			TConfig cfgNode;
 			rFilter.StoreInConfig(cfgNode);
@@ -66,7 +58,7 @@
 
 	bool TFileFiltersArray::ReadFromConfig(const TConfig& rConfig, PCTSTR pszNodeName)
 	{
-		m_vFilters.clear();
+		m_vEntries.clear();
 
 		TConfigArray vConfigs;
 		if(!rConfig.ExtractMultiSubConfigs(pszNodeName, vConfigs))
@@ -78,100 +70,11 @@
 			TFileFilter tFilter;
 			tFilter.ReadFromConfig(rCfg);
 
-			m_vFilters.push_back(tFilter);
+			m_vEntries.push_back(tFilter);
 		}
 		return true;
 	}
 
-	bool TFileFiltersArray::IsEmpty() const
-	{
-		return m_vFilters.empty();
-	}
-
-	void TFileFiltersArray::Add(const TFileFilter& rFilter)
-	{
-		m_vFilters.push_back(rFilter);
-	}
-
-	bool TFileFiltersArray::SetAt(size_t stIndex, const TFileFilter& rNewFilter)
-	{
-		BOOST_ASSERT(stIndex < m_vFilters.size());
-		if(stIndex < m_vFilters.size())
-		{
-			TFileFilter& rFilter = m_vFilters.at(stIndex);
-
-			rFilter.SetData(rNewFilter);
-			return true;
-		}
-
-		return false;
-	}
-
-	const TFileFilter& TFileFiltersArray::GetAt(size_t stIndex) const
-	{
-		if(stIndex >= m_vFilters.size())
-			throw std::out_of_range("stIndex is out of range");
-
-		return m_vFilters.at(stIndex);
-	}
-
-	bool TFileFiltersArray::RemoveAt(size_t stIndex)
-	{
-		BOOST_ASSERT(stIndex < m_vFilters.size());
-		if(stIndex < m_vFilters.size())
-		{
-			m_setRemovedObjects.Add(m_vFilters[ stIndex ].GetObjectID());
-
-			m_vFilters.erase(m_vFilters.begin() + stIndex);
-			return true;
-		}
-
-		return false;
-	}
-
-	size_t TFileFiltersArray::GetCount() const
-	{
-		return m_vFilters.size();
-	}
-
-	void TFileFiltersArray::Clear()
-	{
-		for(const TFileFilter& rFilter : m_vFilters)
-		{
-			m_setRemovedObjects.Add(rFilter.GetObjectID());
-		}
-		m_vFilters.clear();
-	}
-
-	void TFileFiltersArray::Store(const ISerializerContainerPtr& spContainer) const
-	{
-		InitColumns(spContainer);
-
-		spContainer->DeleteRows(m_setRemovedObjects);
-		m_setRemovedObjects.Clear();
-
-		for(const TFileFilter& rFilter : m_vFilters)
-		{
-			rFilter.Store(spContainer);
-		}
-	}
-
-	void TFileFiltersArray::Load(const ISerializerContainerPtr& spContainer)
-	{
-		InitColumns(spContainer);
-
-		ISerializerRowReaderPtr spRowReader = spContainer->GetRowReader();
-		while(spRowReader->Next())
-		{
-			TFileFilter tFileFilter;
-			tFileFilter.Load(spRowReader);
-
-			tFileFilter.ResetModifications();
-
-			m_vFilters.push_back(tFileFilter);
-		}
-	}
-
 	void TFileFiltersArray::InitColumns(const ISerializerContainerPtr& spContainer) const
 	{
 		IColumnsDefinition& rColumns = spContainer->GetColumnsDefinition();
Index: src/libchengine/TFileFiltersArray.h
===================================================================
diff -u -r85b07e753393f661f7d8f528e4238ebb6e9e1204 -rf3c80778cfee0736195e00274c78040f7908ac5b
--- src/libchengine/TFileFiltersArray.h	(.../TFileFiltersArray.h)	(revision 85b07e753393f661f7d8f528e4238ebb6e9e1204)
+++ src/libchengine/TFileFiltersArray.h	(.../TFileFiltersArray.h)	(revision f3c80778cfee0736195e00274c78040f7908ac5b)
@@ -21,46 +21,27 @@
 
 #include "TFileFilter.h"
 #include "../libserializer/TRemovedObjects.h"
+#include "../libserializer/SerializableContainer.h"
 
 namespace chengine
 {
 	class TConfig;
 	class TFileInfo;
 	typedef std::shared_ptr<TFileInfo> TFileInfoPtr;
 
-	class LIBCHENGINE_API TFileFiltersArray
+#pragma warning(push)
+#pragma warning(disable: 4251)
+	class LIBCHENGINE_API TFileFiltersArray :  public serializer::SerializableContainer<TFileFilter>
 	{
 	public:
-		TFileFiltersArray();
-		~TFileFiltersArray();
-
 		bool Match(const TFileInfoPtr& spInfo) const;
 
 		void StoreInConfig(TConfig& rConfig, PCTSTR pszNodeName) const;
 		bool ReadFromConfig(const TConfig& rConfig, PCTSTR pszNodeName);
 
-		void Store(const serializer::ISerializerContainerPtr& spContainer) const;
-		void Load(const serializer::ISerializerContainerPtr& spContainer);
-
-		void InitColumns(const serializer::ISerializerContainerPtr& spContainer) const;
-
-		bool IsEmpty() const;
-
-		void Add(const TFileFilter& rFilter);
-		bool SetAt(size_t stIndex, const TFileFilter& rNewFilter);
-		const TFileFilter& GetAt(size_t stIndex) const;
-		bool RemoveAt(size_t stIndex);
-		size_t GetCount() const;
-
-		void Clear();
-
-	private:
-#pragma warning(push)
-#pragma warning(disable: 4251)
-		std::vector<TFileFilter> m_vFilters;
-#pragma warning(pop)
-		mutable serializer::TRemovedObjects m_setRemovedObjects;
+		void InitColumns(const serializer::ISerializerContainerPtr& spContainer) const override;
 	};
+#pragma warning(pop)
 }
 
 CONFIG_MEMBER_SERIALIZATION(TFileFiltersArray)
Index: src/libchengine/libchengine.vcxproj
===================================================================
diff -u -r2dea2d82eb5c11d9e92d42e47f876e58c4505c4b -rf3c80778cfee0736195e00274c78040f7908ac5b
--- src/libchengine/libchengine.vcxproj	(.../libchengine.vcxproj)	(revision 2dea2d82eb5c11d9e92d42e47f876e58c4505c4b)
+++ src/libchengine/libchengine.vcxproj	(.../libchengine.vcxproj)	(revision f3c80778cfee0736195e00274c78040f7908ac5b)
@@ -503,12 +503,15 @@
     <ClInclude Include="CommonDataTypes.h" />
     <ClInclude Include="ConfigNode.h" />
     <ClInclude Include="ConfigNodeContainer.h" />
+    <ClInclude Include="ECompareType.h" />
     <ClInclude Include="EFeedbackResult.h" />
     <ClInclude Include="EFileError.h" />
     <ClInclude Include="EngineConstants.h" />
     <ClInclude Include="EOperationTypes.h" />
     <ClInclude Include="ESubTaskTypes.h" />
     <ClInclude Include="ETaskCurrentState.h" />
+    <ClInclude Include="FeedbackRule.h" />
+    <ClInclude Include="FeedbackRuleList.h" />
     <ClInclude Include="IFeedbackHandler.h" />
     <ClInclude Include="IFeedbackHandlerFactory.h" />
     <ClInclude Include="IFilesystem.h" />
@@ -612,6 +615,8 @@
     </ClCompile>
     <ClCompile Include="ConfigNode.cpp" />
     <ClCompile Include="ConfigNodeContainer.cpp" />
+    <ClCompile Include="FeedbackRule.cpp" />
+    <ClCompile Include="FeedbackRuleList.cpp" />
     <ClCompile Include="IFeedbackHandler.cpp" />
     <ClCompile Include="IFeedbackHandlerFactory.cpp" />
     <ClCompile Include="IFilesystem.cpp" />
Index: src/libchengine/libchengine.vcxproj.filters
===================================================================
diff -u -r301444777085263aae7aff911dd56722f302597e -rf3c80778cfee0736195e00274c78040f7908ac5b
--- src/libchengine/libchengine.vcxproj.filters	(.../libchengine.vcxproj.filters)	(revision 301444777085263aae7aff911dd56722f302597e)
+++ src/libchengine/libchengine.vcxproj.filters	(.../libchengine.vcxproj.filters)	(revision f3c80778cfee0736195e00274c78040f7908ac5b)
@@ -381,6 +381,15 @@
     <ClInclude Include="TObsoleteFiles.h">
       <Filter>Source Files\Tools</Filter>
     </ClInclude>
+    <ClInclude Include="FeedbackRule.h">
+      <Filter>Source Files\Feedback</Filter>
+    </ClInclude>
+    <ClInclude Include="FeedbackRuleList.h">
+      <Filter>Source Files\Feedback</Filter>
+    </ClInclude>
+    <ClInclude Include="ECompareType.h">
+      <Filter>Source Files\Tools</Filter>
+    </ClInclude>
   </ItemGroup>
   <ItemGroup>
     <ClCompile Include="TSubTaskArray.cpp">
@@ -659,6 +668,12 @@
     <ClCompile Include="TObsoleteFiles.cpp">
       <Filter>Source Files\Tools</Filter>
     </ClCompile>
+    <ClCompile Include="FeedbackRule.cpp">
+      <Filter>Source Files\Feedback</Filter>
+    </ClCompile>
+    <ClCompile Include="FeedbackRuleList.cpp">
+      <Filter>Source Files\Feedback</Filter>
+    </ClCompile>
   </ItemGroup>
   <ItemGroup>
     <ResourceCompile Include="libchengine.rc">
Index: src/libserializer/SerializableContainer.h
===================================================================
diff -u
--- src/libserializer/SerializableContainer.h	(revision 0)
+++ src/libserializer/SerializableContainer.h	(revision f3c80778cfee0736195e00274c78040f7908ac5b)
@@ -0,0 +1,105 @@
+#pragma once
+
+namespace serializer
+{
+	template<class T>
+	class SerializableContainer
+	{
+	public:
+
+		void Store(const serializer::ISerializerContainerPtr& spContainer) const
+		{
+			InitColumns(spContainer);
+
+			spContainer->DeleteRows(m_setRemovedObjects);
+			m_setRemovedObjects.Clear();
+
+			for(const T& rEntry : m_vEntries)
+			{
+				rEntry.Store(spContainer);
+			}
+		}
+
+		void Load(const serializer::ISerializerContainerPtr& spContainer)
+		{
+			InitColumns(spContainer);
+
+			ISerializerRowReaderPtr spRowReader = spContainer->GetRowReader();
+			while(spRowReader->Next())
+			{
+				T tEntry;
+				tEntry.Load(spRowReader);
+
+				tEntry.ResetModifications();
+
+				m_vEntries.push_back(tEntry);
+			}
+		}
+
+		virtual void InitColumns(const serializer::ISerializerContainerPtr& spContainer) const = 0;
+
+		bool IsEmpty() const
+		{
+			return m_vEntries.empty();
+		}
+
+		void Add(const T& rEntry)
+		{
+			m_vEntries.push_back(rEntry);
+		}
+
+		bool SetAt(size_t stIndex, const T& rNewEntry)
+		{
+			BOOST_ASSERT(stIndex < m_vEntries.size());
+			if(stIndex < m_vEntries.size())
+			{
+				T& rEntry = m_vEntries.at(stIndex);
+
+				rEntry.SetData(rNewEntry);
+				return true;
+			}
+
+			return false;
+		}
+
+		const T& GetAt(size_t stIndex) const
+		{
+			if(stIndex >= m_vEntries.size())
+				throw std::out_of_range("stIndex is out of range");
+
+			return m_vEntries.at(stIndex);
+		}
+
+		bool RemoveAt(size_t stIndex)
+		{
+			BOOST_ASSERT(stIndex < m_vEntries.size());
+			if(stIndex < m_vEntries.size())
+			{
+				m_setRemovedObjects.Add(m_vEntries[stIndex].GetObjectID());
+
+				m_vEntries.erase(m_vEntries.begin() + stIndex);
+				return true;
+			}
+
+			return false;
+		}
+
+		size_t GetCount() const
+		{
+			return m_vEntries.size();
+		}
+
+		void Clear()
+		{
+			for(const T& rEntry : m_vEntries)
+			{
+				m_setRemovedObjects.Add(rEntry.GetObjectID());
+			}
+			m_vEntries.clear();
+		}
+
+	protected:
+		std::vector<T> m_vEntries;
+		mutable serializer::TRemovedObjects m_setRemovedObjects;
+	};
+}
Index: src/libserializer/SerializableObject.h
===================================================================
diff -u
--- src/libserializer/SerializableObject.h	(revision 0)
+++ src/libserializer/SerializableObject.h	(revision f3c80778cfee0736195e00274c78040f7908ac5b)
@@ -0,0 +1,79 @@
+#pragma once
+
+#include "ISerializerRowData.h"
+#include "ISerializerRowReader.h"
+#include "SerializerDataTypes.h"
+#include <bitset>
+
+namespace serializer
+{
+	template<size_t BitsetSize>
+	class SerializableObject
+	{
+	public:
+		SerializableObject();
+		SerializableObject(const SerializableObject& rSrc);
+		virtual ~SerializableObject();
+
+		SerializableObject& operator=(const SerializableObject& rSrc);
+
+		// serialization interface
+		virtual void Store(const serializer::ISerializerContainerPtr& spContainer) const = 0;
+		virtual void Load(const serializer::ISerializerRowReaderPtr& spRowReader) = 0;
+
+		serializer::object_id_t GetObjectID() const;
+		void SetObjectID(serializer::object_id_t oidObjectID);
+
+		void ResetModifications();
+
+	protected:
+		serializer::object_id_t m_oidObjectID = 0;
+
+		using Bitset = std::bitset<BitsetSize>;
+		mutable Bitset m_setModifications;
+	};
+
+	template<size_t BitsetSize>
+	serializer::SerializableObject<BitsetSize>::SerializableObject()
+	{
+	}
+
+	template<size_t BitsetSize>
+	serializer::SerializableObject<BitsetSize>::SerializableObject(const SerializableObject& rSrc) :
+		m_oidObjectID(rSrc.m_oidObjectID),
+		m_setModifications(rSrc.m_setModifications)
+	{
+	}
+
+	template<size_t BitsetSize>
+	serializer::SerializableObject<BitsetSize>::~SerializableObject()
+	{
+	}
+
+	template<size_t BitsetSize>
+	SerializableObject<BitsetSize>& serializer::SerializableObject<BitsetSize>::operator=(const SerializableObject& rSrc)
+	{
+		m_oidObjectID = rSrc.m_oidObjectID;
+		m_setModifications = rSrc.m_setModifications;
+
+		return *this;
+	}
+
+	template<size_t BitsetSize>
+	void serializer::SerializableObject<BitsetSize>::ResetModifications()
+	{
+		m_setModifications.reset();
+	}
+
+	template<size_t BitsetSize>
+	void serializer::SerializableObject<BitsetSize>::SetObjectID(serializer::object_id_t oidObjectID)
+	{
+		m_oidObjectID = oidObjectID;
+	}
+
+	template<size_t BitsetSize>
+	serializer::object_id_t serializer::SerializableObject<BitsetSize>::GetObjectID() const
+	{
+		return m_oidObjectID;
+	}
+}
Index: src/libserializer/SerializerDataTypes.h
===================================================================
diff -u -r0d5b67ee96b435d63f7bf075dc8e28603793b187 -rf3c80778cfee0736195e00274c78040f7908ac5b
--- src/libserializer/SerializerDataTypes.h	(.../SerializerDataTypes.h)	(revision 0d5b67ee96b435d63f7bf075dc8e28603793b187)
+++ src/libserializer/SerializerDataTypes.h	(.../SerializerDataTypes.h)	(revision f3c80778cfee0736195e00274c78040f7908ac5b)
@@ -21,7 +21,7 @@
 
 namespace serializer
 {
-	typedef unsigned long object_id_t;
+	using object_id_t = unsigned long;
 }
 
 #endif
Index: src/libserializer/TSharedModificationTracker.h
===================================================================
diff -u -r3921d82d9605d98b2281f3f42d9f9c8385b89a3e -rf3c80778cfee0736195e00274c78040f7908ac5b
--- src/libserializer/TSharedModificationTracker.h	(.../TSharedModificationTracker.h)	(revision 3921d82d9605d98b2281f3f42d9f9c8385b89a3e)
+++ src/libserializer/TSharedModificationTracker.h	(.../TSharedModificationTracker.h)	(revision f3c80778cfee0736195e00274c78040f7908ac5b)
@@ -75,6 +75,16 @@
 			return *this;
 		}
 
+		bool operator==(const TSharedModificationTracker& rValue) const
+		{
+			return m_tValue == rValue;
+		}
+
+		bool operator!=(const TSharedModificationTracker& rValue) const
+		{
+			return m_tValue != rValue;
+		}
+
 		operator const T&() const
 		{
 			return m_tValue;
Index: src/libserializer/libserializer.vcxproj
===================================================================
diff -u -r2dea2d82eb5c11d9e92d42e47f876e58c4505c4b -rf3c80778cfee0736195e00274c78040f7908ac5b
--- src/libserializer/libserializer.vcxproj	(.../libserializer.vcxproj)	(revision 2dea2d82eb5c11d9e92d42e47f876e58c4505c4b)
+++ src/libserializer/libserializer.vcxproj	(.../libserializer.vcxproj)	(revision f3c80778cfee0736195e00274c78040f7908ac5b)
@@ -507,6 +507,8 @@
     <ClInclude Include="ISerializerRowReader.h" />
     <ClInclude Include="ISQLiteSerializerSchema.h" />
     <ClInclude Include="resource.h" />
+    <ClInclude Include="SerializableContainer.h" />
+    <ClInclude Include="SerializableObject.h" />
     <ClInclude Include="SerializerDataTypes.h" />
     <ClInclude Include="TFakeFileSerializer.h" />
     <ClInclude Include="TModificationTracker.h" />
Index: src/libserializer/libserializer.vcxproj.filters
===================================================================
diff -u -r301444777085263aae7aff911dd56722f302597e -rf3c80778cfee0736195e00274c78040f7908ac5b
--- src/libserializer/libserializer.vcxproj.filters	(.../libserializer.vcxproj.filters)	(revision 301444777085263aae7aff911dd56722f302597e)
+++ src/libserializer/libserializer.vcxproj.filters	(.../libserializer.vcxproj.filters)	(revision f3c80778cfee0736195e00274c78040f7908ac5b)
@@ -128,6 +128,12 @@
     <ClInclude Include="resource.h">
       <Filter>Resource Files</Filter>
     </ClInclude>
+    <ClInclude Include="SerializableObject.h">
+      <Filter>Source Files\Serialization</Filter>
+    </ClInclude>
+    <ClInclude Include="SerializableContainer.h">
+      <Filter>Source Files\Serialization</Filter>
+    </ClInclude>
   </ItemGroup>
   <ItemGroup>
     <ClCompile Include="dllmain.cpp">