isundil 9 months ago
commit
0f4d5db94a

+ 3 - 0
.gitignore

@@ -0,0 +1,3 @@
+/.vs
+/x64
+/test

+ 112 - 0
Crypto.vcxproj

@@ -0,0 +1,112 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="17.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <ItemGroup Label="ProjectConfigurations">
+    <ProjectConfiguration Include="Debug|x64">
+      <Configuration>Debug</Configuration>
+      <Platform>x64</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Release|x64">
+      <Configuration>Release</Configuration>
+      <Platform>x64</Platform>
+    </ProjectConfiguration>
+  </ItemGroup>
+  <ItemGroup>
+    <ClCompile Include="Crypto\Crypto.cpp" />
+  </ItemGroup>
+  <ItemGroup>
+    <ClInclude Include="Crypto\Crypto.h" />
+  </ItemGroup>
+  <PropertyGroup Label="Globals">
+    <ProjectGuid>{4814536A-05B1-47B7-8843-4C654A601717}</ProjectGuid>
+    <Keyword>QtVS_v304</Keyword>
+    <WindowsTargetPlatformVersion Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">10.0</WindowsTargetPlatformVersion>
+    <WindowsTargetPlatformVersion Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">10.0</WindowsTargetPlatformVersion>
+    <QtMsBuild Condition="'$(QtMsBuild)'=='' OR !Exists('$(QtMsBuild)\qt.targets')">$(MSBuildProjectDirectory)\QtMsBuild</QtMsBuild>
+  </PropertyGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'" Label="Configuration">
+    <ConfigurationType>DynamicLibrary</ConfigurationType>
+    <PlatformToolset>v143</PlatformToolset>
+    <UseDebugLibraries>true</UseDebugLibraries>
+    <CharacterSet>Unicode</CharacterSet>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'" Label="Configuration">
+    <ConfigurationType>DynamicLibrary</ConfigurationType>
+    <PlatformToolset>v143</PlatformToolset>
+    <UseDebugLibraries>false</UseDebugLibraries>
+    <WholeProgramOptimization>true</WholeProgramOptimization>
+    <CharacterSet>Unicode</CharacterSet>
+  </PropertyGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+  <ImportGroup Condition="Exists('$(QtMsBuild)\qt_defaults.props')">
+    <Import Project="$(QtMsBuild)\qt_defaults.props" />
+  </ImportGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'" Label="QtSettings">
+    <QtInstall>Qt 6.7.2</QtInstall>
+    <QtModules>core</QtModules>
+    <QtBuildConfig>debug</QtBuildConfig>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'" Label="QtSettings">
+    <QtInstall>Qt 6.7.2</QtInstall>
+    <QtModules>core</QtModules>
+    <QtBuildConfig>release</QtBuildConfig>
+  </PropertyGroup>
+  <Target Name="QtMsBuildNotFound" BeforeTargets="CustomBuild;ClCompile" Condition="!Exists('$(QtMsBuild)\qt.targets') or !Exists('$(QtMsBuild)\qt.props')">
+    <Message Importance="High" Text="QtMsBuild: could not locate qt.targets, qt.props; project may not build correctly." />
+  </Target>
+  <ImportGroup Label="ExtensionSettings" />
+  <ImportGroup Label="Shared" />
+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+    <Import Project="$(QtMsBuild)\Qt.props" />
+  </ImportGroup>
+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+    <Import Project="$(QtMsBuild)\Qt.props" />
+  </ImportGroup>
+  <PropertyGroup Label="UserMacros" />
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
+    <IncludePath>$(SolutionDir)\Engine;$(SolutionDir)\fakeRaid;$(SolutionDir)\Crypto;$(IncludePath)</IncludePath>
+    <IntDir>$(SolutionDir)\$(Platform)\$(Configuration)\$(MSBuildProjectName)\</IntDir>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
+    <IncludePath>$(SolutionDir)\Engine;$(SolutionDir)\fakeRaid;$(SolutionDir)\Crypto;$(IncludePath)</IncludePath>
+    <IntDir>$(SolutionDir)\$(Platform)\$(Configuration)\$(MSBuildProjectName)\</IntDir>
+  </PropertyGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'" Label="Configuration">
+    <ClCompile>
+      <MultiProcessorCompilation>true</MultiProcessorCompilation>
+      <PreprocessorDefinitions>CRYPTO_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <WarningLevel>Level3</WarningLevel>
+      <SDLCheck>true</SDLCheck>
+      <ConformanceMode>true</ConformanceMode>
+    </ClCompile>
+    <Link>
+      <SubSystem>Windows</SubSystem>
+      <GenerateDebugInformation>true</GenerateDebugInformation>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'" Label="Configuration">
+    <ClCompile>
+      <MultiProcessorCompilation>true</MultiProcessorCompilation>
+      <PreprocessorDefinitions>CRYPTO_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <WarningLevel>Level3</WarningLevel>
+      <SDLCheck>true</SDLCheck>
+      <ConformanceMode>true</ConformanceMode>
+      <FunctionLevelLinking>true</FunctionLevelLinking>
+      <IntrinsicFunctions>true</IntrinsicFunctions>
+    </ClCompile>
+    <Link>
+      <SubSystem>Windows</SubSystem>
+      <GenerateDebugInformation>false</GenerateDebugInformation>
+      <EnableCOMDATFolding>true</EnableCOMDATFolding>
+      <OptimizeReferences>true</OptimizeReferences>
+    </Link>
+  </ItemDefinitionGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+  <ImportGroup Condition="Exists('$(QtMsBuild)\qt.targets')">
+    <Import Project="$(QtMsBuild)\qt.targets" />
+  </ImportGroup>
+  <ImportGroup Label="ExtensionTargets">
+  </ImportGroup>
+</Project>

+ 35 - 0
Crypto.vcxproj.filters

@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <ItemGroup>
+    <Filter Include="Source Files">
+      <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
+      <Extensions>qml;cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
+    </Filter>
+    <Filter Include="Header Files">
+      <UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
+      <Extensions>h;hh;hpp;hxx;hm;inl;inc;xsd</Extensions>
+    </Filter>
+    <Filter Include="Resource Files">
+      <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
+      <Extensions>qrc;rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
+    </Filter>
+    <Filter Include="Form Files">
+      <UniqueIdentifier>{99349809-55BA-4b9d-BF79-8FDBB0286EB3}</UniqueIdentifier>
+      <Extensions>ui</Extensions>
+    </Filter>
+    <Filter Include="Translation Files">
+      <UniqueIdentifier>{639EADAA-A684-42e4-A9AD-28FC9BCB8F7C}</UniqueIdentifier>
+      <Extensions>ts</Extensions>
+    </Filter>
+  </ItemGroup>
+  <ItemGroup>
+    <ClCompile Include="Crypto\Crypto.cpp">
+      <Filter>Source Files</Filter>
+    </ClCompile>
+  </ItemGroup>
+  <ItemGroup>
+    <ClInclude Include="Crypto\Crypto.h">
+      <Filter>Header Files</Filter>
+    </ClInclude>
+  </ItemGroup>
+</Project>

+ 12 - 0
Crypto.vcxproj.user

@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <PropertyGroup />
+  <PropertyGroup Label="QtSettings" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+    <QtTouchProperty>
+    </QtTouchProperty>
+  </PropertyGroup>
+  <PropertyGroup Label="QtSettings" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+    <QtTouchProperty>
+    </QtTouchProperty>
+  </PropertyGroup>
+</Project>

+ 23 - 0
Crypto/Crypto.cpp

@@ -0,0 +1,23 @@
+#include <QFile>
+#include <QCryptographicHash>
+#include "Crypto.h"
+
+using namespace craftlab::fakeraid;
+
+Crypto::Crypto()
+{
+}
+
+void Crypto::Compute(const std::string& dir, FileAndSum& file)
+{
+    if (file.isDir)
+        return;
+    const std::string path = dir + "/" + file.fileName;
+    QFile f(path.c_str());
+    if (!f.open(QFile::ReadOnly))
+        throw std::runtime_error("Cannot open file for reading: " + path);
+    QCryptographicHash hash(QCryptographicHash::Algorithm::Sha256);
+    if (!hash.addData(&f))
+        throw std::runtime_error("Cannot read file: " + path);
+    file.checksum = hash.result().toHex();
+}

+ 14 - 0
Crypto/Crypto.h

@@ -0,0 +1,14 @@
+#pragma once
+
+#include "exports.h"
+#include "FileDefinition.h"
+
+namespace craftlab::fakeraid
+{
+    class CRYPTO_EXPORT Crypto
+    {
+    public:
+        Crypto();
+        void Compute(const std::string& path, FileAndSum& file);
+    };
+}

+ 177 - 0
Engine.vcxproj

@@ -0,0 +1,177 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <ItemGroup Label="ProjectConfigurations">
+    <ProjectConfiguration Include="Debug|Win32">
+      <Configuration>Debug</Configuration>
+      <Platform>Win32</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Release|Win32">
+      <Configuration>Release</Configuration>
+      <Platform>Win32</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Debug|x64">
+      <Configuration>Debug</Configuration>
+      <Platform>x64</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Release|x64">
+      <Configuration>Release</Configuration>
+      <Platform>x64</Platform>
+    </ProjectConfiguration>
+  </ItemGroup>
+  <PropertyGroup Label="Globals">
+    <VCProjectVersion>17.0</VCProjectVersion>
+    <Keyword>Win32Proj</Keyword>
+    <ProjectGuid>{f62d1642-3be3-44a3-a03a-116930004deb}</ProjectGuid>
+    <RootNamespace>Engine</RootNamespace>
+    <WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
+  </PropertyGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
+    <ConfigurationType>DynamicLibrary</ConfigurationType>
+    <UseDebugLibraries>true</UseDebugLibraries>
+    <PlatformToolset>v143</PlatformToolset>
+    <CharacterSet>Unicode</CharacterSet>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
+    <ConfigurationType>DynamicLibrary</ConfigurationType>
+    <UseDebugLibraries>false</UseDebugLibraries>
+    <PlatformToolset>v143</PlatformToolset>
+    <WholeProgramOptimization>true</WholeProgramOptimization>
+    <CharacterSet>Unicode</CharacterSet>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
+    <ConfigurationType>DynamicLibrary</ConfigurationType>
+    <UseDebugLibraries>true</UseDebugLibraries>
+    <PlatformToolset>v143</PlatformToolset>
+    <CharacterSet>Unicode</CharacterSet>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
+    <ConfigurationType>DynamicLibrary</ConfigurationType>
+    <UseDebugLibraries>false</UseDebugLibraries>
+    <PlatformToolset>v143</PlatformToolset>
+    <WholeProgramOptimization>true</WholeProgramOptimization>
+    <CharacterSet>Unicode</CharacterSet>
+  </PropertyGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+  <ImportGroup Label="ExtensionSettings">
+  </ImportGroup>
+  <ImportGroup Label="Shared">
+  </ImportGroup>
+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+  </ImportGroup>
+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+  </ImportGroup>
+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+  </ImportGroup>
+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+  </ImportGroup>
+  <PropertyGroup Label="UserMacros" />
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
+    <IncludePath>$(SolutionDir)\Engine;$(SolutionDir)\fakeRaid;$(SolutionDir)\Crypto;$(IncludePath)</IncludePath>
+    <IntDir>$(SolutionDir)\$(Platform)\$(Configuration)\$(MSBuildProjectName)\</IntDir>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
+    <IncludePath>$(SolutionDir)\Engine;$(SolutionDir)\fakeRaid;$(SolutionDir)\Crypto;$(IncludePath)</IncludePath>
+    <IntDir>$(SolutionDir)\$(Platform)\$(Configuration)\$(MSBuildProjectName)\</IntDir>
+  </PropertyGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+    <ClCompile>
+      <WarningLevel>Level3</WarningLevel>
+      <SDLCheck>true</SDLCheck>
+      <PreprocessorDefinitions>WIN32;_DEBUG;ENGINE_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <ConformanceMode>true</ConformanceMode>
+      <PrecompiledHeader>Use</PrecompiledHeader>
+      <PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
+    </ClCompile>
+    <Link>
+      <SubSystem>Windows</SubSystem>
+      <GenerateDebugInformation>true</GenerateDebugInformation>
+      <EnableUAC>false</EnableUAC>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+    <ClCompile>
+      <WarningLevel>Level3</WarningLevel>
+      <FunctionLevelLinking>true</FunctionLevelLinking>
+      <IntrinsicFunctions>true</IntrinsicFunctions>
+      <SDLCheck>true</SDLCheck>
+      <PreprocessorDefinitions>WIN32;NDEBUG;ENGINE_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <ConformanceMode>true</ConformanceMode>
+      <PrecompiledHeader>Use</PrecompiledHeader>
+      <PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
+    </ClCompile>
+    <Link>
+      <SubSystem>Windows</SubSystem>
+      <EnableCOMDATFolding>true</EnableCOMDATFolding>
+      <OptimizeReferences>true</OptimizeReferences>
+      <GenerateDebugInformation>true</GenerateDebugInformation>
+      <EnableUAC>false</EnableUAC>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+    <ClCompile>
+      <WarningLevel>Level3</WarningLevel>
+      <SDLCheck>true</SDLCheck>
+      <PreprocessorDefinitions>ENGINE_API;_DEBUG;ENGINE_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <ConformanceMode>true</ConformanceMode>
+      <PrecompiledHeader>Use</PrecompiledHeader>
+      <PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
+      <LanguageStandard>stdcpp20</LanguageStandard>
+    </ClCompile>
+    <Link>
+      <SubSystem>Windows</SubSystem>
+      <GenerateDebugInformation>true</GenerateDebugInformation>
+      <EnableUAC>false</EnableUAC>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+    <ClCompile>
+      <WarningLevel>Level3</WarningLevel>
+      <FunctionLevelLinking>true</FunctionLevelLinking>
+      <IntrinsicFunctions>true</IntrinsicFunctions>
+      <SDLCheck>true</SDLCheck>
+      <PreprocessorDefinitions>ENGINE_API;NDEBUG;ENGINE_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <ConformanceMode>true</ConformanceMode>
+      <PrecompiledHeader>Use</PrecompiledHeader>
+      <PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
+      <LanguageStandard>stdcpp20</LanguageStandard>
+    </ClCompile>
+    <Link>
+      <SubSystem>Windows</SubSystem>
+      <EnableCOMDATFolding>true</EnableCOMDATFolding>
+      <OptimizeReferences>true</OptimizeReferences>
+      <GenerateDebugInformation>true</GenerateDebugInformation>
+      <EnableUAC>false</EnableUAC>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemGroup>
+    <ClInclude Include="Engine\fileDefinition.h" />
+    <ClInclude Include="Engine\fileDiff.h" />
+    <ClInclude Include="engine\IEngine.h" />
+    <ClInclude Include="engine/framework.h" />
+    <ClInclude Include="engine/pch.h" />
+  </ItemGroup>
+  <ItemGroup>
+    <ClCompile Include="engine/dllmain.cpp" />
+    <ClCompile Include="engine/Engine.cpp" />
+    <ClCompile Include="engine/pch.cpp">
+      <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Create</PrecompiledHeader>
+      <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Create</PrecompiledHeader>
+      <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">Create</PrecompiledHeader>
+      <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Create</PrecompiledHeader>
+    </ClCompile>
+    <ClCompile Include="Engine\fileDiff.cpp" />
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="Crypto.vcxproj">
+      <Project>{4814536a-05b1-47b7-8843-4c654a601717}</Project>
+    </ProjectReference>
+  </ItemGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+  <ImportGroup Label="ExtensionTargets">
+  </ImportGroup>
+</Project>

+ 48 - 0
Engine.vcxproj.filters

@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <ItemGroup>
+    <Filter Include="Source Files">
+      <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
+      <Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
+    </Filter>
+    <Filter Include="Header Files">
+      <UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
+      <Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
+    </Filter>
+    <Filter Include="Resource Files">
+      <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
+      <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
+    </Filter>
+  </ItemGroup>
+  <ItemGroup>
+    <ClInclude Include="engine\IEngine.h">
+      <Filter>Header Files</Filter>
+    </ClInclude>
+    <ClInclude Include="engine/framework.h">
+      <Filter>Header Files</Filter>
+    </ClInclude>
+    <ClInclude Include="engine/pch.h">
+      <Filter>Header Files</Filter>
+    </ClInclude>
+    <ClInclude Include="Engine\fileDefinition.h">
+      <Filter>Header Files</Filter>
+    </ClInclude>
+    <ClInclude Include="Engine\fileDiff.h">
+      <Filter>Header Files</Filter>
+    </ClInclude>
+  </ItemGroup>
+  <ItemGroup>
+    <ClCompile Include="engine/dllmain.cpp">
+      <Filter>Source Files</Filter>
+    </ClCompile>
+    <ClCompile Include="engine/Engine.cpp">
+      <Filter>Source Files</Filter>
+    </ClCompile>
+    <ClCompile Include="engine/pch.cpp">
+      <Filter>Source Files</Filter>
+    </ClCompile>
+    <ClCompile Include="Engine\fileDiff.cpp">
+      <Filter>Source Files</Filter>
+    </ClCompile>
+  </ItemGroup>
+</Project>

+ 4 - 0
Engine.vcxproj.user

@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <PropertyGroup />
+</Project>

+ 136 - 0
Engine/Engine.cpp

@@ -0,0 +1,136 @@
+#include "pch.h"
+#include "IEngine.h"
+#include "Crypto.h"
+#include <stdexcept>
+#include <filesystem>
+
+using namespace craftlab::fakeraid;
+
+namespace craftlab::fakeraid
+{
+	class Engine : public IEngine
+	{
+	public:
+		Engine(const std::vector<std::string>& path);
+
+		void DirExistsOrThrow() const override;
+		std::vector<std::string> GetRootPaths() const override;
+		std::vector<std::string> GetPaths() const override;
+		std::deque<std::string> GetCurrentDir() const override;
+		FileAndSumListByRepositoryIndex ListFiles() override;
+		void Cd(const std::string& dirName) override;
+
+	private:
+		FileAndSumList ListFiles(const std::string& root, int repositoryIndex);
+		std::string BuildCurrentDirPath(const std::string& root ="") const;
+
+		const std::vector<std::string> rootPaths;
+		std::deque<std::string> currentDir;
+	};
+}
+
+Engine::Engine(const std::vector<std::string>& path): rootPaths(path), currentDir()
+{}
+
+std::vector<std::string> Engine::GetRootPaths() const
+{
+	return rootPaths;
+}
+
+std::vector<std::string> Engine::GetPaths() const
+{
+	std::vector<std::string> paths;
+	const std::string workingDir = BuildCurrentDirPath();
+	std::transform(rootPaths.begin(), rootPaths.end(), std::back_inserter(paths), [&workingDir](const std::string& val) { return val + "/" + workingDir; });
+	return paths;
+}
+
+std::deque<std::string> Engine::GetCurrentDir() const
+{
+	return currentDir;
+}
+
+std::string Engine::BuildCurrentDirPath(const std::string& root) const
+{
+	std::stringstream ss;
+	ss << root;
+	bool written = !root.empty();
+
+	for (const std::string& path : currentDir)
+	{
+		if (written)
+			ss << "/";
+		ss << path;
+		written = true;
+	}
+	return ss.str();
+}
+
+void Engine::Cd(const std::string& dirName)
+{
+	currentDir.push_back(dirName);
+}
+
+FileAndSumList Engine::ListFiles(const std::string& root, int repositoryIndex)
+{
+	FileAndSumList result;
+	const std::string path = BuildCurrentDirPath(root);
+	Crypto cryptoEngine;
+	for (const std::filesystem::directory_entry& file : std::filesystem::directory_iterator(path))
+	{
+		if (file.is_directory() || file.is_regular_file())
+		{
+			FileAndSum fileInfos;
+			fileInfos.fileName = file.path().filename().string();
+			fileInfos.isDir = file.is_directory();
+			fileInfos.repositoryIndex = repositoryIndex;
+			try
+			{
+				cryptoEngine.Compute(path, fileInfos);
+			}
+			catch (std::runtime_error&)
+			{}
+			result.push_back(fileInfos);
+		}
+	}
+	return result;
+}
+
+FileAndSumListByRepositoryIndex Engine::ListFiles()
+{
+	FileAndSumListByRepositoryIndex result;
+	int index = 0;
+	for (const auto& i : rootPaths)
+	{
+		const FileAndSumList files = ListFiles(i, index++);
+		result.FileAndSumListByRepositoryIndex.push_back(files);
+		for (const FileAndSum& file : files)
+			result.FileList[file.fileName] = file;
+	}
+	return result;
+}
+
+void Engine::DirExistsOrThrow() const
+{
+	char errorBuffer[1024];
+	for (const std::string& fullPath : GetPaths())
+	{
+		struct stat fileInfo;
+
+		if (stat(fullPath.c_str(), &fileInfo))
+		{
+			strerror_s(errorBuffer, errno);
+			throw std::runtime_error("cannot access " + fullPath + ": " + errorBuffer);
+		}
+		if (!(fileInfo.st_mode & S_IFDIR))
+		{
+			strerror_s(errorBuffer, ENOTDIR);
+			throw std::runtime_error("cannot access " + fullPath + ": " + errorBuffer);
+		}
+	}
+}
+
+IEngine* EngineManager::Open(const std::vector<std::string>& path)
+{
+	return new Engine(path);
+}

+ 28 - 0
Engine/IEngine.h

@@ -0,0 +1,28 @@
+#pragma once
+
+#include <deque>
+#include "FileDefinition.h"
+#include "exports.h"
+
+namespace craftlab::fakeraid
+{
+	class IEngine
+	{
+	public:
+		virtual ~IEngine() {};
+
+		virtual void DirExistsOrThrow() const =0;
+		virtual std::vector<std::string> GetRootPaths() const = 0;
+		virtual std::vector<std::string> GetPaths() const = 0;
+		virtual std::deque<std::string> GetCurrentDir() const =0;
+		virtual FileAndSumListByRepositoryIndex ListFiles() =0;
+		virtual void Cd(const std::string& dirName) =0;
+	};
+
+	class EngineManager
+	{
+	public:
+		EngineManager() =delete;
+		ENGINEAPI_EXPORT static IEngine* Open(const std::vector<std::string>& paths);
+	};
+}

+ 19 - 0
Engine/dllmain.cpp

@@ -0,0 +1,19 @@
+// dllmain.cpp : Defines the entry point for the DLL application.
+#include "pch.h"
+
+BOOL APIENTRY DllMain( HMODULE hModule,
+                       DWORD  ul_reason_for_call,
+                       LPVOID lpReserved
+                     )
+{
+    switch (ul_reason_for_call)
+    {
+    case DLL_PROCESS_ATTACH:
+    case DLL_THREAD_ATTACH:
+    case DLL_THREAD_DETACH:
+    case DLL_PROCESS_DETACH:
+        break;
+    }
+    return TRUE;
+}
+

+ 30 - 0
Engine/fileDefinition.h

@@ -0,0 +1,30 @@
+#pragma once
+
+#include <map>
+#include <string>
+#include <vector>
+
+namespace craftlab::fakeraid
+{
+	typedef std::string CheckSum;
+
+	struct File
+	{
+		std::string fileName;
+		bool isDir;
+		int repositoryIndex;
+	};
+
+	struct FileAndSum: File
+	{
+		CheckSum checksum;
+	};
+
+	typedef std::vector<FileAndSum> FileAndSumList;
+
+	struct FileAndSumListByRepositoryIndex
+	{
+		std::vector<FileAndSumList> FileAndSumListByRepositoryIndex;
+		std::map<std::string, FileAndSum> FileList;
+	};
+}

+ 93 - 0
Engine/fileDiff.cpp

@@ -0,0 +1,93 @@
+#include <set>
+#include "pch.h"
+#include "FileDiff.h"
+
+using namespace craftlab::fakeraid;
+
+FileDiff::FileDiff()
+{
+}
+
+FileDiff::~FileDiff()
+{
+}
+
+FileDiff::FileVersionList FileDiff::GroupBy(const FileAndSumListByRepositoryIndex& fileListsWithRepo, size_t indexCount)
+{
+	FileVersionList result;
+	for (const FileAndSumList& fileLists : fileListsWithRepo.FileAndSumListByRepositoryIndex)
+	{
+		for (const FileAndSum& i : fileLists)
+		{
+			if (!result.contains(i.fileName))
+				result.emplace(i.fileName, std::vector<std::optional<FileAndSum>>(indexCount, std::nullopt));
+			result[i.fileName][i.repositoryIndex] = i;
+		}
+	}
+	return result;
+}
+
+std::vector<int> FileDiff::computeVersionIds(const ListOfOptionalFileAndSum& versions)
+{
+	std::vector<int> result;
+	std::map<CheckSum, int> versionList;
+	for (const OptionalFileAndSum& version : versions)
+	{
+		if (version.has_value())
+		{
+			if (versionList.contains(version->checksum))
+				result.push_back(versionList[version->checksum]);
+			else
+			{
+				int nextId = (int) versionList.size() + 1;
+				versionList.emplace(version->checksum, nextId);
+				result.push_back(nextId);
+			}
+		}
+		else
+			result.push_back(0);
+	}
+	return result;
+}
+
+void FileDiff::CheckVersions(DiffResult& output, const std::string& fileName, const FileDiff::ListOfOptionalFileAndSum& versions)
+{
+	const auto& firstVersionIter = std::find_if(versions.begin(), versions.end(), [](const OptionalFileAndSum& i) { return i.has_value(); });
+	const FileAndSum& firstVersion = firstVersionIter->value();
+	std::vector<bool> filePresent(versions.size(), true);
+	int idx =0;
+
+	for (const OptionalFileAndSum& i : versions)
+	{
+		if (!i.has_value())
+		{
+			filePresent[idx] = false;
+			continue;
+		}
+		if (firstVersion.checksum != i->checksum)
+		{
+			output.differentFiles.emplace(fileName, computeVersionIds(versions));
+			return;
+		}
+	}
+	if (std::find(filePresent.begin(), filePresent.end(), false) != filePresent.end())
+	{
+		if (firstVersion.isDir)
+			output.missingDirs.emplace(fileName, filePresent);
+		else
+			output.missingFiles.emplace(fileName, filePresent);
+	}
+	else
+		output.correctFiles.push_back(firstVersion);
+}
+
+DiffResult FileDiff::Process(const FileAndSumListByRepositoryIndex& fileLists)
+{
+	DiffResult result;
+	const FileVersionList& fileAndVersionLists = GroupBy(fileLists, fileLists.FileAndSumListByRepositoryIndex.size());
+
+	for (const std::pair<std::string, ListOfOptionalFileAndSum>& key : fileAndVersionLists)
+		CheckVersions(result, key.first, key.second);
+	result.FileList = fileLists.FileList;
+	return result;
+}

+ 35 - 0
Engine/fileDiff.h

@@ -0,0 +1,35 @@
+#pragma once
+
+#include <optional>
+#include "exports.h"
+#include "FileDefinition.h"
+
+namespace craftlab::fakeraid
+{
+	struct DiffResult
+	{
+		std::map<std::string, std::vector<bool>> missingDirs;
+		std::map<std::string, std::vector<bool>> missingFiles;
+		std::map<std::string, std::vector<int>> differentFiles; // fileName -> { versionId, versionId, versionId }
+		std::vector<File> correctFiles;
+		std::map<std::string, FileAndSum> FileList;
+	};
+
+	class FileDiff
+	{
+	public:
+		ENGINEAPI_EXPORT FileDiff();
+		ENGINEAPI_EXPORT ~FileDiff();
+
+		ENGINEAPI_EXPORT DiffResult Process(const FileAndSumListByRepositoryIndex& fileList);
+
+	private:
+		typedef std::optional<FileAndSum> OptionalFileAndSum;
+		typedef std::vector<OptionalFileAndSum> ListOfOptionalFileAndSum;
+		typedef std::map<std::string, ListOfOptionalFileAndSum> FileVersionList;
+
+		FileVersionList GroupBy(const FileAndSumListByRepositoryIndex& fileList, size_t indexCount);
+		std::vector<int> computeVersionIds(const ListOfOptionalFileAndSum& versions);
+		void CheckVersions(DiffResult& output, const std::string& fileName, const ListOfOptionalFileAndSum& versions);
+	};
+}

+ 5 - 0
Engine/framework.h

@@ -0,0 +1,5 @@
+#pragma once
+
+#define WIN32_LEAN_AND_MEAN             // Exclude rarely-used stuff from Windows headers
+// Windows Header Files
+#include <windows.h>

+ 5 - 0
Engine/pch.cpp

@@ -0,0 +1,5 @@
+// pch.cpp: source file corresponding to the pre-compiled header
+
+#include "pch.h"
+
+// When you are using pre-compiled headers, this source file is necessary for compilation to succeed.

+ 13 - 0
Engine/pch.h

@@ -0,0 +1,13 @@
+// pch.h: This is a precompiled header file.
+// Files listed below are compiled only once, improving build performance for future builds.
+// This also affects IntelliSense performance, including code completion and many code browsing features.
+// However, files listed here are ALL re-compiled if any one of them is updated between builds.
+// Do not add files here that you will be updating frequently as this negates the performance advantage.
+
+#ifndef PCH_H
+#define PCH_H
+
+// add headers that you want to pre-compile here
+#include "framework.h"
+
+#endif //PCH_H

+ 34 - 0
fakeRaid.sln

@@ -0,0 +1,34 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.12.35707.178
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "fakeRaid", "fakeRaid.vcxproj", "{8964FB88-5696-4D58-B832-124F25BEA5E8}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Engine", "Engine.vcxproj", "{F62D1642-3BE3-44A3-A03A-116930004DEB}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Crypto", "Crypto.vcxproj", "{4814536A-05B1-47B7-8843-4C654A601717}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|x64 = Debug|x64
+		Release|x64 = Release|x64
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{8964FB88-5696-4D58-B832-124F25BEA5E8}.Debug|x64.ActiveCfg = Debug|x64
+		{8964FB88-5696-4D58-B832-124F25BEA5E8}.Debug|x64.Build.0 = Debug|x64
+		{8964FB88-5696-4D58-B832-124F25BEA5E8}.Release|x64.ActiveCfg = Release|x64
+		{8964FB88-5696-4D58-B832-124F25BEA5E8}.Release|x64.Build.0 = Release|x64
+		{F62D1642-3BE3-44A3-A03A-116930004DEB}.Debug|x64.ActiveCfg = Debug|x64
+		{F62D1642-3BE3-44A3-A03A-116930004DEB}.Debug|x64.Build.0 = Debug|x64
+		{F62D1642-3BE3-44A3-A03A-116930004DEB}.Release|x64.ActiveCfg = Release|x64
+		{F62D1642-3BE3-44A3-A03A-116930004DEB}.Release|x64.Build.0 = Release|x64
+		{4814536A-05B1-47B7-8843-4C654A601717}.Debug|x64.ActiveCfg = Debug|x64
+		{4814536A-05B1-47B7-8843-4C654A601717}.Debug|x64.Build.0 = Debug|x64
+		{4814536A-05B1-47B7-8843-4C654A601717}.Release|x64.ActiveCfg = Release|x64
+		{4814536A-05B1-47B7-8843-4C654A601717}.Release|x64.Build.0 = Release|x64
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+EndGlobal

+ 135 - 0
fakeRaid.vcxproj

@@ -0,0 +1,135 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="17.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <ItemGroup Label="ProjectConfigurations">
+    <ProjectConfiguration Include="Debug|x64">
+      <Configuration>Debug</Configuration>
+      <Platform>x64</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Release|x64">
+      <Configuration>Release</Configuration>
+      <Platform>x64</Platform>
+    </ProjectConfiguration>
+  </ItemGroup>
+  <ItemGroup>
+    <ClCompile Include="fakeRaid\browseModal.cpp" />
+    <ClCompile Include="fakeRaid\conflictItemWidget.cpp" />
+    <ClCompile Include="fakeRaid\conflictModal.cpp" />
+    <ClCompile Include="fakeRaid\fileItem.cpp" />
+    <ClCompile Include="fakeRaid\iconProvider.cpp" />
+    <ClCompile Include="fakeRaid\main.cpp" />
+    <ClCompile Include="fakeRaid\mainWindow.cpp" />
+  </ItemGroup>
+  <ItemGroup>
+    <QtMoc Include="fakeRaid\mainWindow.h" />
+  </ItemGroup>
+  <ItemGroup>
+    <QtUic Include="fakeRaid\BrowseModal.ui" />
+    <QtUic Include="fakeRaid\ConflictItemWidget.ui" />
+    <QtUic Include="fakeRaid\ConflictModal.ui" />
+    <QtUic Include="fakeRaid\MainWindow.ui" />
+  </ItemGroup>
+  <ItemGroup>
+    <ClInclude Include="fakeRaid\browseModal.h" />
+    <QtMoc Include="fakeRaid\conflictItemWidget.h" />
+    <QtMoc Include="fakeRaid\conflictModal.h" />
+    <ClInclude Include="fakeRaid\exports.h" />
+    <ClInclude Include="fakeRaid\fileItem.h" />
+    <ClInclude Include="fakeRaid\iconProvider.h" />
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="Engine.vcxproj">
+      <Project>{f62d1642-3be3-44a3-a03a-116930004deb}</Project>
+    </ProjectReference>
+  </ItemGroup>
+  <PropertyGroup Label="Globals">
+    <ProjectGuid>{8964FB88-5696-4D58-B832-124F25BEA5E8}</ProjectGuid>
+    <Keyword>QtVS_v304</Keyword>
+    <WindowsTargetPlatformVersion Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">10.0</WindowsTargetPlatformVersion>
+    <WindowsTargetPlatformVersion Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">10.0</WindowsTargetPlatformVersion>
+    <QtMsBuild Condition="'$(QtMsBuild)'=='' OR !Exists('$(QtMsBuild)\qt.targets')">$(MSBuildProjectDirectory)\QtMsBuild</QtMsBuild>
+  </PropertyGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'" Label="Configuration">
+    <ConfigurationType>Application</ConfigurationType>
+    <PlatformToolset>v143</PlatformToolset>
+    <UseDebugLibraries>true</UseDebugLibraries>
+    <CharacterSet>Unicode</CharacterSet>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'" Label="Configuration">
+    <ConfigurationType>Application</ConfigurationType>
+    <PlatformToolset>v143</PlatformToolset>
+    <UseDebugLibraries>false</UseDebugLibraries>
+    <WholeProgramOptimization>true</WholeProgramOptimization>
+    <CharacterSet>Unicode</CharacterSet>
+  </PropertyGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+  <ImportGroup Condition="Exists('$(QtMsBuild)\qt_defaults.props')">
+    <Import Project="$(QtMsBuild)\qt_defaults.props" />
+  </ImportGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'" Label="QtSettings">
+    <QtInstall>Qt 6.7.2</QtInstall>
+    <QtModules>core;gui;widgets</QtModules>
+    <QtBuildConfig>debug</QtBuildConfig>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'" Label="QtSettings">
+    <QtInstall>Qt 6.7.2</QtInstall>
+    <QtModules>core;gui;widgets</QtModules>
+    <QtBuildConfig>release</QtBuildConfig>
+  </PropertyGroup>
+  <Target Name="QtMsBuildNotFound" BeforeTargets="CustomBuild;ClCompile" Condition="!Exists('$(QtMsBuild)\qt.targets') or !Exists('$(QtMsBuild)\qt.props')">
+    <Message Importance="High" Text="QtMsBuild: could not locate qt.targets, qt.props; project may not build correctly." />
+  </Target>
+  <ImportGroup Label="ExtensionSettings" />
+  <ImportGroup Label="Shared" />
+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+    <Import Project="$(QtMsBuild)\Qt.props" />
+  </ImportGroup>
+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+    <Import Project="$(QtMsBuild)\Qt.props" />
+  </ImportGroup>
+  <PropertyGroup Label="UserMacros" />
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
+    <IncludePath>$(SolutionDir)\Engine;$(SolutionDir)\fakeRaid;$(SolutionDir)\Crypto;$(IncludePath)</IncludePath>
+    <IntDir>$(SolutionDir)\$(Platform)\$(Configuration)\$(MSBuildProjectName)\</IntDir>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
+    <IncludePath>$(SolutionDir)\Engine;$(SolutionDir)\fakeRaid;$(SolutionDir)\Crypto;$(IncludePath)</IncludePath>
+    <IntDir>$(SolutionDir)\$(Platform)\$(Configuration)\$(MSBuildProjectName)\</IntDir>
+  </PropertyGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'" Label="Configuration">
+    <ClCompile>
+      <MultiProcessorCompilation>true</MultiProcessorCompilation>
+      <WarningLevel>Level3</WarningLevel>
+      <SDLCheck>true</SDLCheck>
+      <ConformanceMode>true</ConformanceMode>
+    </ClCompile>
+    <Link>
+      <SubSystem>Console</SubSystem>
+      <GenerateDebugInformation>true</GenerateDebugInformation>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'" Label="Configuration">
+    <ClCompile>
+      <MultiProcessorCompilation>true</MultiProcessorCompilation>
+      <WarningLevel>Level3</WarningLevel>
+      <SDLCheck>true</SDLCheck>
+      <ConformanceMode>true</ConformanceMode>
+      <FunctionLevelLinking>true</FunctionLevelLinking>
+      <IntrinsicFunctions>true</IntrinsicFunctions>
+    </ClCompile>
+    <Link>
+      <SubSystem>Console</SubSystem>
+      <GenerateDebugInformation>false</GenerateDebugInformation>
+      <EnableCOMDATFolding>true</EnableCOMDATFolding>
+      <OptimizeReferences>true</OptimizeReferences>
+    </Link>
+  </ItemDefinitionGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+  <ImportGroup Condition="Exists('$(QtMsBuild)\qt.targets')">
+    <Import Project="$(QtMsBuild)\qt.targets" />
+  </ImportGroup>
+  <ImportGroup Label="ExtensionTargets">
+  </ImportGroup>
+</Project>

+ 87 - 0
fakeRaid.vcxproj.filters

@@ -0,0 +1,87 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <ItemGroup>
+    <Filter Include="Source Files">
+      <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
+      <Extensions>qml;cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
+    </Filter>
+    <Filter Include="Header Files">
+      <UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
+      <Extensions>h;hh;hpp;hxx;hm;inl;inc;xsd</Extensions>
+    </Filter>
+    <Filter Include="Resource Files">
+      <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
+      <Extensions>qrc;rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
+    </Filter>
+    <Filter Include="Form Files">
+      <UniqueIdentifier>{99349809-55BA-4b9d-BF79-8FDBB0286EB3}</UniqueIdentifier>
+      <Extensions>ui</Extensions>
+    </Filter>
+    <Filter Include="Translation Files">
+      <UniqueIdentifier>{639EADAA-A684-42e4-A9AD-28FC9BCB8F7C}</UniqueIdentifier>
+      <Extensions>ts</Extensions>
+    </Filter>
+  </ItemGroup>
+  <ItemGroup>
+    <ClCompile Include="fakeRaid\mainWindow.cpp">
+      <Filter>Source Files</Filter>
+    </ClCompile>
+    <ClCompile Include="fakeRaid\main.cpp">
+      <Filter>Source Files</Filter>
+    </ClCompile>
+    <ClCompile Include="fakeRaid\browseModal.cpp">
+      <Filter>Source Files</Filter>
+    </ClCompile>
+    <ClCompile Include="fakeRaid\conflictModal.cpp">
+      <Filter>Source Files</Filter>
+    </ClCompile>
+    <ClCompile Include="fakeRaid\conflictItemWidget.cpp">
+      <Filter>Source Files</Filter>
+    </ClCompile>
+    <ClCompile Include="fakeRaid\iconProvider.cpp">
+      <Filter>Source Files</Filter>
+    </ClCompile>
+    <ClCompile Include="fakeRaid\fileItem.cpp">
+      <Filter>Source Files</Filter>
+    </ClCompile>
+  </ItemGroup>
+  <ItemGroup>
+    <QtUic Include="fakeRaid\MainWindow.ui">
+      <Filter>Form Files</Filter>
+    </QtUic>
+    <QtUic Include="fakeRaid\BrowseModal.ui">
+      <Filter>Form Files</Filter>
+    </QtUic>
+    <QtUic Include="fakeRaid\ConflictModal.ui">
+      <Filter>Form Files</Filter>
+    </QtUic>
+    <QtUic Include="fakeRaid\ConflictItemWidget.ui">
+      <Filter>Form Files</Filter>
+    </QtUic>
+  </ItemGroup>
+  <ItemGroup>
+    <QtMoc Include="fakeRaid\mainWindow.h">
+      <Filter>Header Files</Filter>
+    </QtMoc>
+    <QtMoc Include="fakeRaid\conflictItemWidget.h">
+      <Filter>Header Files</Filter>
+    </QtMoc>
+    <QtMoc Include="fakeRaid\conflictModal.h">
+      <Filter>Header Files</Filter>
+    </QtMoc>
+  </ItemGroup>
+  <ItemGroup>
+    <ClInclude Include="fakeRaid\exports.h">
+      <Filter>Header Files</Filter>
+    </ClInclude>
+    <ClInclude Include="fakeRaid\browseModal.h">
+      <Filter>Header Files</Filter>
+    </ClInclude>
+    <ClInclude Include="fakeRaid\iconProvider.h">
+      <Filter>Header Files</Filter>
+    </ClInclude>
+    <ClInclude Include="fakeRaid\fileItem.h">
+      <Filter>Header Files</Filter>
+    </ClInclude>
+  </ItemGroup>
+</Project>

+ 12 - 0
fakeRaid.vcxproj.user

@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <PropertyGroup />
+  <PropertyGroup Label="QtSettings" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+    <QtTouchProperty>
+    </QtTouchProperty>
+  </PropertyGroup>
+  <PropertyGroup Label="QtSettings" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+    <QtTouchProperty>
+    </QtTouchProperty>
+  </PropertyGroup>
+</Project>

+ 102 - 0
fakeRaid/BrowseModal.ui

@@ -0,0 +1,102 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>BrowseModal</class>
+ <widget class="QDialog" name="BrowseModal">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>401</width>
+    <height>179</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Dialog</string>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout">
+   <item>
+    <widget class="QGroupBox" name="groupBox">
+     <property name="title">
+      <string>Groupe</string>
+     </property>
+     <layout class="QHBoxLayout" name="horizontalLayout_2">
+      <item>
+       <widget class="QLineEdit" name="lineEdit"/>
+      </item>
+      <item>
+       <widget class="QPushButton" name="pushButton">
+        <property name="text">
+         <string>Parcourir</string>
+        </property>
+       </widget>
+      </item>
+     </layout>
+    </widget>
+   </item>
+   <item>
+    <widget class="QGroupBox" name="groupBox_2">
+     <property name="title">
+      <string>Groupe</string>
+     </property>
+     <layout class="QHBoxLayout" name="horizontalLayout_3">
+      <item>
+       <widget class="QLineEdit" name="lineEdit_2"/>
+      </item>
+      <item>
+       <widget class="QPushButton" name="pushButton_2">
+        <property name="text">
+         <string>Parcourir</string>
+        </property>
+       </widget>
+      </item>
+     </layout>
+    </widget>
+   </item>
+   <item>
+    <widget class="QDialogButtonBox" name="buttonBox">
+     <property name="orientation">
+      <enum>Qt::Horizontal</enum>
+     </property>
+     <property name="standardButtons">
+      <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+     </property>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <resources/>
+ <connections>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>accepted()</signal>
+   <receiver>BrowseModal</receiver>
+   <slot>accept()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>248</x>
+     <y>254</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>157</x>
+     <y>274</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>rejected()</signal>
+   <receiver>BrowseModal</receiver>
+   <slot>reject()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>316</x>
+     <y>260</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>286</x>
+     <y>274</y>
+    </hint>
+   </hints>
+  </connection>
+ </connections>
+</ui>

+ 116 - 0
fakeRaid/ConflictItemWidget.ui

@@ -0,0 +1,116 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ConflictItemWidget</class>
+ <widget class="QWidget" name="ConflictItemWidget">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>396</width>
+    <height>98</height>
+   </rect>
+  </property>
+  <property name="minimumSize">
+   <size>
+    <width>0</width>
+    <height>0</height>
+   </size>
+  </property>
+  <property name="windowTitle">
+   <string>Form</string>
+  </property>
+  <layout class="QHBoxLayout" name="horizontalLayout_3">
+   <item>
+    <widget class="QLabel" name="warningIcon">
+     <property name="text">
+      <string>WarningIcon</string>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <layout class="QVBoxLayout" name="verticalLayout">
+     <item>
+      <layout class="QHBoxLayout" name="horizontalLayout_2">
+       <item>
+        <widget class="QLabel" name="fileIcon">
+         <property name="text">
+          <string>fileIcon</string>
+         </property>
+        </widget>
+       </item>
+       <item>
+        <widget class="QLabel" name="filename">
+         <property name="text">
+          <string>fileName</string>
+         </property>
+        </widget>
+       </item>
+       <item>
+        <spacer name="horizontalSpacer_2">
+         <property name="orientation">
+          <enum>Qt::Horizontal</enum>
+         </property>
+         <property name="sizeHint" stdset="0">
+          <size>
+           <width>40</width>
+           <height>20</height>
+          </size>
+         </property>
+        </spacer>
+       </item>
+      </layout>
+     </item>
+     <item>
+      <layout class="QHBoxLayout" name="horizontalLayout">
+       <item>
+        <spacer name="horizontalSpacer">
+         <property name="orientation">
+          <enum>Qt::Horizontal</enum>
+         </property>
+         <property name="sizeHint" stdset="0">
+          <size>
+           <width>40</width>
+           <height>20</height>
+          </size>
+         </property>
+        </spacer>
+       </item>
+       <item>
+        <widget class="QPushButton" name="buttonCopy">
+         <property name="text">
+          <string>Copy file</string>
+         </property>
+         <property name="checkable">
+          <bool>true</bool>
+         </property>
+        </widget>
+       </item>
+       <item>
+        <widget class="QPushButton" name="buttonRemove">
+         <property name="text">
+          <string>Remove</string>
+         </property>
+         <property name="checkable">
+          <bool>true</bool>
+         </property>
+        </widget>
+       </item>
+       <item>
+        <widget class="QPushButton" name="buttonIgnore">
+         <property name="text">
+          <string>Ignore</string>
+         </property>
+         <property name="checkable">
+          <bool>true</bool>
+         </property>
+        </widget>
+       </item>
+      </layout>
+     </item>
+    </layout>
+   </item>
+  </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>

+ 59 - 0
fakeRaid/ConflictModal.ui

@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ConflictModal</class>
+ <widget class="QDialog" name="ConflictModal">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>637</width>
+    <height>376</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Dialog</string>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout">
+   <item>
+    <widget class="QListWidget" name="listWidget">
+     <property name="selectionMode">
+      <enum>QAbstractItemView::NoSelection</enum>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <layout class="QHBoxLayout" name="horizontalLayout">
+     <item>
+      <spacer name="horizontalSpacer">
+       <property name="orientation">
+        <enum>Qt::Horizontal</enum>
+       </property>
+       <property name="sizeHint" stdset="0">
+        <size>
+         <width>40</width>
+         <height>20</height>
+        </size>
+       </property>
+      </spacer>
+     </item>
+     <item>
+      <widget class="QPushButton" name="buttonIgnoreAll">
+       <property name="text">
+        <string>Ignore All</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QPushButton" name="buttonProcess">
+       <property name="text">
+        <string>Process</string>
+       </property>
+      </widget>
+     </item>
+    </layout>
+   </item>
+  </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>

+ 59 - 0
fakeRaid/MainWindow.ui

@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>MainWindow</class>
+ <widget class="QMainWindow" name="MainWindow">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>800</width>
+    <height>600</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>MainWindow</string>
+  </property>
+  <widget class="QWidget" name="centralwidget">
+   <layout class="QVBoxLayout" name="verticalLayout">
+    <item>
+     <widget class="QListView" name="listView">
+      <property name="editTriggers">
+       <set>QAbstractItemView::NoEditTriggers</set>
+      </property>
+      <property name="defaultDropAction">
+       <enum>Qt::IgnoreAction</enum>
+      </property>
+      <property name="viewMode">
+       <enum>QListView::IconMode</enum>
+      </property>
+     </widget>
+    </item>
+   </layout>
+  </widget>
+  <widget class="QMenuBar" name="menubar">
+   <property name="geometry">
+    <rect>
+     <x>0</x>
+     <y>0</y>
+     <width>800</width>
+     <height>21</height>
+    </rect>
+   </property>
+   <widget class="QMenu" name="menuFichier">
+    <property name="title">
+     <string>Fichier</string>
+    </property>
+    <addaction name="actionOuvrir"/>
+   </widget>
+   <addaction name="menuFichier"/>
+  </widget>
+  <widget class="QStatusBar" name="statusbar"/>
+  <action name="actionOuvrir">
+   <property name="text">
+    <string>Ouvrir</string>
+   </property>
+  </action>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>

+ 48 - 0
fakeRaid/browseModal.cpp

@@ -0,0 +1,48 @@
+#include <QFileDialog>
+#include "browseModal.h"
+
+using namespace craftlab::fakeraid;
+
+BrowseModal::BrowseModal(bool cancellable, const std::vector<std::string>& previous)
+{
+	ui.setupUi(this);
+	if (!cancellable)
+	{
+		setWindowFlags(windowFlags() & ~Qt::WindowCloseButtonHint);
+		ui.buttonBox->setStandardButtons(QDialogButtonBox::StandardButton::Open);
+	}
+	else
+	{
+		ui.buttonBox->setStandardButtons(QDialogButtonBox::StandardButton::Open | QDialogButtonBox::StandardButton::Cancel);
+		connect(ui.buttonBox, &QDialogButtonBox::rejected, this, [this]() { rejected = true; });
+	}
+	connect(ui.pushButton, &QPushButton::clicked, this, [this]() { BrowseForInput(*ui.lineEdit); });
+	connect(ui.pushButton_2, &QPushButton::clicked, this, [this]() { BrowseForInput(*ui.lineEdit_2); });
+	if (previous.size() > 0)
+		ui.lineEdit->setText(previous[0].c_str());
+	if (previous.size() > 1)
+		ui.lineEdit_2->setText(previous[1].c_str());
+	setModal(this);
+}
+
+BrowseModal::~BrowseModal()
+{}
+
+void BrowseModal::BrowseForInput(QLineEdit& input)
+{
+	QString path = QFileDialog::getExistingDirectory(this, "FakeRaid", input.text());
+	if (!path.isNull())
+		input.setText(path);
+}
+
+std::vector<std::string> BrowseModal::Display(bool cancellable, const std::vector<std::string>& previous)
+{
+	BrowseModal dialog(cancellable, previous);
+	dialog.exec();
+	if (dialog.rejected)
+		return std::vector<std::string> {};
+	std::vector<std::string> result;
+	result.push_back(dialog.ui.lineEdit->text().toStdString());
+	result.push_back(dialog.ui.lineEdit_2->text().toStdString());
+	return result;
+}

+ 24 - 0
fakeRaid/browseModal.h

@@ -0,0 +1,24 @@
+#pragma once
+
+#include <QDialog>
+#include "ui_BrowseModal.h"
+
+namespace craftlab::fakeraid
+{
+	class BrowseModal : public QDialog
+	{
+	public:
+		~BrowseModal();
+
+		static std::vector<std::string> Display(bool cancellable, const std::vector<std::string>& previous);
+
+	private:
+		BrowseModal() =delete;
+		BrowseModal(bool cancellable, const std::vector<std::string>& previous);
+
+		void BrowseForInput(QLineEdit& input);
+
+		bool rejected = false;
+		Ui_BrowseModal ui;
+	};
+}

+ 56 - 0
fakeRaid/conflictItemWidget.cpp

@@ -0,0 +1,56 @@
+#include "conflictItemWidget.h"
+#include "iconProvider.h"
+
+using namespace craftlab::fakeraid;
+
+ConflictItemWidget::ConflictItemWidget(QListWidget* parent, const QFile& file, const std::string& _fileName): QWidget(parent), filename(_fileName)
+{
+	std::filesystem::path p(file.fileName().toStdString());
+	ui.setupUi(this);
+	ui.warningIcon->setPixmap(IconProvider::WarningIcon(*this));
+	ui.fileIcon->setPixmap(IconProvider::FromFile(file));
+	ui.filename->setText(p.filename().string().c_str());
+
+	connect(ui.buttonCopy, &QPushButton::clicked, this, [this]() { SetAction(Action::Copy); });
+	connect(ui.buttonRemove, &QPushButton::clicked, this, [this]() { SetAction(Action::Remove); });
+	connect(ui.buttonIgnore, &QPushButton::clicked, this, [this]() { SetAction(Action::Ignore); });
+}
+
+void ConflictItemWidget::SetAction(Action&& action)
+{
+	if (currentAction != action)
+	{
+		ui.buttonCopy->setChecked(action == Action::Copy);
+		ui.buttonRemove->setChecked(action == Action::Remove);
+		ui.buttonIgnore->setChecked(action == Action::Ignore);
+		currentAction = action;
+		emit ActionChanged();
+	}
+}
+
+std::string ConflictItemWidget::GetFileName() const
+{ return filename; }
+
+ConflictItemWidget::Action ConflictItemWidget::GetAction() const
+{
+	return currentAction;
+}
+
+ConflictItemWidget* ConflictItemWidget::FromMissingFile(QListWidget* parent, const std::vector<std::string>& rootPaths, const std::string& filename, const std::vector<bool>& version)
+{
+	const std::string fullPathFirstFound = rootPaths[std::distance(version.begin(), std::find(version.begin(), version.end(), true))] + "/" + filename;
+	ConflictItemWidget* result = new ConflictItemWidget(parent, QFile(fullPathFirstFound.c_str()), filename);
+	return result;
+}
+
+ConflictItemWidget* ConflictItemWidget::FromMissingDir(QListWidget* parent, const std::vector<std::string>& rootPaths, const std::string& filename, const std::vector<bool>& version)
+{
+	return FromMissingFile(parent, rootPaths, filename, version);
+}
+
+ConflictItemWidget* ConflictItemWidget::FromConflict(QListWidget* parent, const std::vector<std::string>& rootPaths, const std::string& filename, const std::vector<int>& version)
+{
+	const std::string fullPathFirstFound = rootPaths[std::distance(version.begin(), std::find(version.begin(), version.end(), true))] + "/" + filename;
+	ConflictItemWidget* result = new ConflictItemWidget(parent, QFile(fullPathFirstFound.c_str()), filename);
+	return result;
+}

+ 41 - 0
fakeRaid/conflictItemWidget.h

@@ -0,0 +1,41 @@
+#pragma once
+
+#include <QFile>
+#include <QWidget>
+#include <QListWidget>
+#include "FileDiff.h"
+#include "ui_ConflictItemWidget.h"
+
+namespace craftlab::fakeraid
+{
+	class ConflictItemWidget : public QWidget
+	{
+	Q_OBJECT
+	public:
+		enum class Action
+		{
+			Undefined,
+			Copy,
+			Remove,
+			Ignore
+		};
+
+		static ConflictItemWidget* FromMissingFile(QListWidget* parent, const std::vector<std::string>& rootPaths, const std::string& filename, const std::vector<bool>& version);
+		static ConflictItemWidget* FromMissingDir(QListWidget* parent, const std::vector<std::string>& rootPaths, const std::string& filename, const std::vector<bool>& version);
+		static ConflictItemWidget* FromConflict(QListWidget* parent, const std::vector<std::string>& rootPaths, const std::string& filename, const std::vector<int>& version);
+
+		Action GetAction() const;
+		void SetAction(Action&& action);
+		std::string GetFileName() const;
+
+	signals:
+		void ActionChanged();
+
+	private:
+		ConflictItemWidget(QListWidget* parent, const QFile& fullPath, const std::string& filename);
+
+		const std::string filename;
+		Action currentAction = Action::Undefined;
+		Ui_ConflictItemWidget ui;
+	};
+}

+ 83 - 0
fakeRaid/conflictModal.cpp

@@ -0,0 +1,83 @@
+#include <QListWidgetItem>
+#include <QFileDialog>
+#include "conflictModal.h"
+#include "ConflictItemWidget.h"
+#include "fileDiff.h"
+
+using namespace craftlab::fakeraid;
+
+ConflictModal::ConflictModal(const std::vector<std::string>& rootPaths, const DiffResult& _diffResult): diffResult(std::make_unique<DiffResult>(_diffResult))
+{
+	ui.setupUi(this);
+	for (const std::pair<std::string, std::vector<bool>>& i : diffResult->missingFiles)
+		AddConflictingFile(ConflictItemWidget::FromMissingFile(ui.listWidget, rootPaths, i.first, i.second));
+	for (const std::pair<std::string, std::vector<bool>>& i : diffResult->missingDirs)
+		AddConflictingFile(ConflictItemWidget::FromMissingDir(ui.listWidget, rootPaths, i.first, i.second));
+	for (const std::pair<std::string, std::vector<int>>& i : diffResult->differentFiles)
+		AddConflictingFile(ConflictItemWidget::FromConflict(ui.listWidget, rootPaths, i.first, i.second));
+	setModal(true);
+	ui.buttonProcess->setEnabled(false);
+	connect(ui.buttonIgnoreAll, &QPushButton::clicked, this, [this]() { for (ConflictItemWidget* item: conflicts) item->SetAction(ConflictItemWidget::Action::Ignore); Process(); });
+	connect(ui.buttonProcess, &QPushButton::clicked, this, &ConflictModal::Process);
+}
+
+ConflictModal::~ConflictModal()
+{}
+
+void ConflictModal::Process()
+{
+	close();
+}
+
+void ConflictModal::AddConflictingFile(ConflictItemWidget* widget)
+{
+	QListWidgetItem* listItem = new QListWidgetItem(ui.listWidget);
+
+	listItem->setSizeHint(widget->size());
+	ui.listWidget->addItem(listItem);
+	ui.listWidget->setItemWidget(listItem, widget);
+	conflicts.push_back(widget);
+	connect(widget, &ConflictItemWidget::ActionChanged, this, &ConflictModal::UpdateProcessButtonStatus);
+}
+
+void ConflictModal::UpdateProcessButtonStatus()
+{
+	bool pending = std::any_of(
+		conflicts.begin(),
+		conflicts.end(),
+		[](const ConflictItemWidget* o) { return o->GetAction() == ConflictItemWidget::Action::Undefined; });
+	ui.buttonProcess->setEnabled(!pending);
+}
+
+DiffResult ConflictModal::ComputeOutcome() const
+{
+	DiffResult result;
+	result.correctFiles = diffResult->correctFiles;
+	result.FileList = diffResult->FileList;
+	for (const ConflictItemWidget* i : conflicts)
+	{
+		FileAndSum originalFile = diffResult->FileList.at(i->GetFileName());
+		if (i->GetAction() == ConflictItemWidget::Action::Copy)
+			result.correctFiles.push_back(originalFile);
+		else if (i->GetAction() == ConflictItemWidget::Action::Remove)
+			;
+		else if (i->GetAction() == ConflictItemWidget::Action::Ignore ||
+			i->GetAction() == ConflictItemWidget::Action::Undefined)
+		{
+			if (diffResult->differentFiles.find(i->GetFileName()) != diffResult->differentFiles.end())
+				result.differentFiles[i->GetFileName()] = diffResult->differentFiles.at(i->GetFileName());
+			else if (originalFile.isDir)
+				result.missingDirs[i->GetFileName()] = diffResult->missingDirs.at(i->GetFileName());
+			else
+				result.missingFiles[i->GetFileName()] = diffResult->missingFiles.at(i->GetFileName());
+		}
+	}
+	return result;
+}
+
+DiffResult ConflictModal::Display(const IEngine& engine, const DiffResult& diffResult)
+{
+	ConflictModal dialog(engine.GetRootPaths(), diffResult);
+	dialog.exec();
+	return dialog.ComputeOutcome();
+}

+ 35 - 0
fakeRaid/conflictModal.h

@@ -0,0 +1,35 @@
+#pragma once
+
+#include <QDialog>
+#include "IEngine.h"
+#include "ui_ConflictModal.h"
+
+namespace craftlab::fakeraid
+{
+	class ConflictItemWidget;
+	struct DiffResult;
+
+	class ConflictModal : public QDialog
+	{
+	Q_OBJECT
+	public:
+		~ConflictModal();
+
+		static DiffResult Display(const IEngine& engine, const DiffResult& diffResult);
+		DiffResult ComputeOutcome() const;
+
+	private slots:
+		void UpdateProcessButtonStatus();
+		void Process();
+
+	private:
+		ConflictModal() =delete;
+		ConflictModal(const std::vector<std::string>& rootPaths, const DiffResult& diffResult);
+
+		void AddConflictingFile(ConflictItemWidget* widget);
+
+		std::vector<ConflictItemWidget*> conflicts;
+		std::unique_ptr<DiffResult> diffResult;
+		Ui_ConflictModal ui;
+	};
+}

+ 13 - 0
fakeRaid/exports.h

@@ -0,0 +1,13 @@
+#pragma once
+
+#ifdef ENGINE_API
+#  define ENGINEAPI_EXPORT _declspec(dllexport)
+#else
+#  define ENGINEAPI_EXPORT _declspec(dllimport)
+#endif
+
+#if defined(CRYPTO_LIB)
+# define CRYPTO_EXPORT _declspec(dllexport)
+#else
+# define CRYPTO_EXPORT _declspec(dllimport)
+#endif

+ 29 - 0
fakeRaid/fileItem.cpp

@@ -0,0 +1,29 @@
+#include <QVariant>
+#include "fileItem.h"
+#include "iconProvider.h"
+
+using namespace craftlab::fakeraid::ui;
+
+FileItem::FileItem(const QWidget& parent, const std::vector<std::string>& rootPaths, const File& f, bool isCorrectFile) :
+		filename(f.fileName),
+		icon(isCorrectFile ? IconProvider::FromFile(rootPaths[f.repositoryIndex] + "/" + f.fileName) : IconProvider::CriticalIcon(parent)),
+		isDir(f.isDir)
+{
+	setText(filename.c_str());
+	setIcon(icon);
+}
+
+std::string FileItem::GetFilename() const
+{
+	return filename;
+}
+
+FileItemDelegate::FileItemDelegate(QObject* parent): QItemDelegate(parent)
+{}
+
+void FileItemDelegate::paint(QPainter* painter,
+	const QStyleOptionViewItem& option,
+	const QModelIndex& index) const
+{
+	QItemDelegate::paint(painter, option, index);
+}

+ 31 - 0
fakeRaid/fileItem.h

@@ -0,0 +1,31 @@
+#pragma once
+
+#include <QStyledItemDelegate>
+#include <QItemDelegate>
+#include <QStandardItemModel>
+#include <QStandardItem>
+#include "FileDefinition.h"
+
+namespace craftlab::fakeraid::ui
+{
+	class FileItem : public QStandardItem
+	{
+	public:
+		FileItem(const QWidget& parent, const std::vector<std::string>& rootPaths, const File& f, bool isCorrectFile =true);
+		std::string GetFilename() const;
+
+	private:
+		const std::string filename;
+		const QPixmap icon;
+		const bool isDir;
+	};
+
+	class FileItemDelegate : public QItemDelegate
+	{
+	public:
+		FileItemDelegate(QObject* parent = nullptr);
+		void paint(QPainter* painter,
+			const QStyleOptionViewItem& option,
+			const QModelIndex& index) const override;
+	};
+}

+ 27 - 0
fakeRaid/iconProvider.cpp

@@ -0,0 +1,27 @@
+#include <QFileIconProvider>
+#include <QStyle>
+#include "iconProvider.h"
+
+using namespace craftlab::fakeraid;
+
+QPixmap IconProvider::FromFile(const std::string& path, const QSize& size)
+{
+	return iconProvider->icon(QFileInfo(path.c_str())).pixmap(size);
+}
+
+QPixmap IconProvider::FromFile(const QFile& path, const QSize& size)
+{
+	return iconProvider->icon(QFileInfo(path)).pixmap(size);
+}
+
+QPixmap IconProvider::WarningIcon(const QWidget& object, const QSize& size)
+{
+	return object.style()->standardIcon(QStyle::SP_MessageBoxWarning).pixmap(size);
+}
+
+QPixmap IconProvider::CriticalIcon(const QWidget& object, const QSize& size)
+{
+	return object.style()->standardIcon(QStyle::SP_MessageBoxCritical).pixmap(size);
+}
+
+QAbstractFileIconProvider* IconProvider::iconProvider = new QFileIconProvider();

+ 20 - 0
fakeRaid/iconProvider.h

@@ -0,0 +1,20 @@
+#pragma once
+
+#include <QAbstractFileIconProvider>
+#include <QIcon>
+#include <QWidget>
+
+namespace craftlab::fakeraid
+{
+	class IconProvider
+	{
+	public:
+		static QPixmap WarningIcon(const QWidget& object, const QSize& size = QSize(48, 48));
+		static QPixmap CriticalIcon(const QWidget& object, const QSize& size = QSize(48, 48));
+		static QPixmap FromFile(const std::string& fullPath, const QSize& size = QSize(48, 48));
+		static QPixmap FromFile(const QFile& file, const QSize& size = QSize(48, 48));
+
+	private:
+		static QAbstractFileIconProvider* iconProvider;
+	};
+}

+ 12 - 0
fakeRaid/main.cpp

@@ -0,0 +1,12 @@
+
+#include <QApplication>
+#include "mainWindow.h"
+
+using namespace craftlab::fakeraid;
+
+int main(int ac, char** av)
+{
+	QApplication app(ac, av);
+	ui::MainWindow window;
+	return app.exec();
+}

+ 175 - 0
fakeRaid/mainWindow.cpp

@@ -0,0 +1,175 @@
+#include <QMessageBox>
+#include "mainWindow.h"
+#include "conflictModal.h"
+#include "browseModal.h"
+#include "fileDiff.h"
+#include "fileItem.h"
+
+using namespace craftlab::fakeraid::ui;
+
+MainWindow::MainWindow()
+	: fileListItemModel(std::make_unique<QStandardItemModel>(this))
+{
+	window = std::make_unique<QMainWindow>();
+
+	ui.setupUi(window.get());
+	ui.listView->setModel(fileListItemModel.get());
+	ui.listView->setItemDelegate(new FileItemDelegate(this));
+
+	connect(ui.actionOuvrir, &QAction::triggered, this, &MainWindow::MenuBarActionTrigerred);
+	connect(ui.listView, &QListView::doubleClicked, this, [this](const QModelIndex& index)
+	{
+		if (index.isValid() && currentDiffResult)
+		{
+			const auto& fileIter = currentDiffResult->FileList.find(qvariant_cast<QString>(index.data(Qt::DisplayRole)).toStdString());
+			if (fileIter != currentDiffResult->FileList.end())
+				OnListViewDoubleClick(fileIter->second);
+		}
+	});
+	window->show();
+
+	DisplaySourceWindow(false, std::vector<std::string>({ "C:\\Users\\isund\\projects\\fakeRaid\\test\\A", "C:\\Users\\isund\\projects\\fakeRaid\\test\\B" }));
+}
+
+MainWindow::~MainWindow()
+{
+}
+
+bool MainWindow::PathExists(const IEngine& engine) const
+{
+	try
+	{
+		engine.DirExistsOrThrow();
+	}
+	catch (const std::runtime_error& err)
+	{
+		QMessageBox::critical(window.get(), QString("Fake Raid"), err.what());
+		return false;
+	}
+	return true;
+}
+
+void MainWindow::UpdateFileList()
+{
+	fileListItemModel->clear();
+	for (const File& file : currentDiffResult->correctFiles)
+		fileListItemModel->appendRow(new FileItem(*ui.listView, engine->GetRootPaths(), file));
+	std::vector<std::string> allFilenames;
+	std::transform(currentDiffResult->differentFiles.begin(), currentDiffResult->differentFiles.end(), std::back_inserter(allFilenames), [](const std::pair<std::string, std::vector<int>>& i) { return i.first; });
+	std::transform(currentDiffResult->missingFiles.begin(), currentDiffResult->missingFiles.end(), std::back_inserter(allFilenames), [](const std::pair<std::string, std::vector<bool>>& i) { return i.first; });
+	std::transform(currentDiffResult->missingDirs.begin(), currentDiffResult->missingDirs.end(), std::back_inserter(allFilenames), [](const std::pair<std::string, std::vector<bool>>& i) { return i.first; });
+	std::sort(allFilenames.begin(), allFilenames.end());
+	for (const std::string& file : allFilenames)
+	{
+		fileListItemModel->appendRow(new FileItem(*ui.listView, engine->GetRootPaths(), currentDiffResult->FileList[file], false));
+	}
+}
+
+void MainWindow::ListFiles()
+{
+	if (!engine)
+		return;
+	currentDiffResult = std::make_unique<DiffResult>(FileDiff().Process(engine->ListFiles()));
+	if (!currentDiffResult->differentFiles.empty() || !currentDiffResult->missingDirs.empty() || !currentDiffResult->missingFiles.empty())
+		currentDiffResult = std::make_unique<DiffResult>(ConflictModal::Display(*engine, *currentDiffResult));
+	std::sort(currentDiffResult->correctFiles.begin(), currentDiffResult->correctFiles.end(), [](const File& a, const File& b) { return a.fileName < b.fileName; });
+
+	UpdateFileList();
+}
+
+void MainWindow::FileEdit(const File& file)
+{
+	// FIXME
+}
+
+bool MainWindow::IsFileCorrent(const File& file) const
+{
+	return std::find_if(
+		currentDiffResult->correctFiles.begin(),
+		currentDiffResult->correctFiles.end(),
+		[&file](const File& f)
+		{
+			return file.fileName == f.fileName;
+		})
+	!= currentDiffResult->correctFiles.end();
+}
+
+void MainWindow::OnListViewDoubleClick(const FileAndSum& file)
+{
+	if (IsFileCorrent(file))
+	{
+		if (file.isDir)
+		{
+			if (engine)
+			{
+				engine->Cd(file.fileName);
+				OnEngineUpdated();
+			}
+		}
+		else
+		{
+			FileEdit(file);
+		}
+		return;
+	}
+	if (!engine)
+		return;
+	DiffResult askConflict;
+	bool missingFile = currentDiffResult->missingFiles.find(file.fileName) != currentDiffResult->missingFiles.end();
+	bool missingDir = currentDiffResult->missingDirs.find(file.fileName) != currentDiffResult->missingDirs.end();
+	askConflict.FileList[file.fileName] = file;
+	if (missingFile)
+		askConflict.missingFiles[file.fileName] = currentDiffResult->missingFiles[file.fileName];
+	else if (missingDir)
+		askConflict.missingDirs[file.fileName] = currentDiffResult->missingDirs[file.fileName];
+	else
+		askConflict.differentFiles[file.fileName] = currentDiffResult->differentFiles[file.fileName];
+	const DiffResult& result = ConflictModal::Display(*engine, askConflict);
+	if (result.missingDirs.empty() && result.missingFiles.empty() && result.differentFiles.empty())
+	{
+		if (!result.correctFiles.empty())
+			currentDiffResult->correctFiles.push_back(file);
+		if (missingFile)
+			currentDiffResult->missingFiles.erase(currentDiffResult->missingFiles.find(file.fileName));
+		else if (missingDir)
+			currentDiffResult->missingDirs.erase(currentDiffResult->missingDirs.find(file.fileName));
+		else
+			currentDiffResult->differentFiles.erase(currentDiffResult->differentFiles.find(file.fileName));
+		UpdateFileList();
+	}
+}
+
+void MainWindow::OnEngineUpdated()
+{
+	ListFiles();
+}
+
+void MainWindow::DisplaySourceWindow(bool canCancel, const std::vector<std::string>& previous)
+{
+	const std::vector<std::string> folders = BrowseModal::Display(canCancel, previous);
+	if (folders.empty())
+	{
+		if (canCancel)
+			return;
+		return DisplaySourceWindow(canCancel, previous);
+	}
+	IEngine* newEngine = EngineManager::Open(folders);
+	if (!PathExists(*newEngine))
+	{
+		delete newEngine;
+		return DisplaySourceWindow(false, folders);
+	}
+	engine.reset(newEngine);
+	OnEngineUpdated();
+}
+void MainWindow::DisplaySourceWindow(bool canCancel)
+{
+	if (engine == nullptr)
+		return DisplaySourceWindow(canCancel, std::vector<std::string>());
+	return DisplaySourceWindow(canCancel, engine->GetRootPaths());
+}
+
+void MainWindow::MenuBarActionTrigerred(bool checked)
+{
+	DisplaySourceWindow(true);
+}

+ 46 - 0
fakeRaid/mainWindow.h

@@ -0,0 +1,46 @@
+#pragma once
+
+#include <QStandardItemModel>
+#include <QStandardItem>
+#include <QMainWindow>
+#include <QObject>
+#include "ui_MainWindow.h"
+#include "IEngine.h"
+
+namespace craftlab::fakeraid
+{
+	struct DiffResult;
+}
+
+namespace craftlab::fakeraid::ui
+{
+	class MainWindow : public QObject
+	{
+	Q_OBJECT
+	public:
+		MainWindow();
+		~MainWindow();
+
+	public:
+		void DisplaySourceWindow(bool canCancel, const std::vector<std::string>& previousPaths);
+		void DisplaySourceWindow(bool canCancel);
+
+	private Q_SLOTS:
+		void MenuBarActionTrigerred(bool checked =false);
+
+	private:
+		bool PathExists(const IEngine& engine) const;
+		void OnEngineUpdated();
+		void ListFiles();
+		void OnListViewDoubleClick(const FileAndSum& filename);
+		void FileEdit(const File& f);
+		bool IsFileCorrent(const File& file) const;
+		void UpdateFileList();
+
+		std::unique_ptr<QStandardItemModel> fileListItemModel;
+		std::unique_ptr<QMainWindow> window;
+		std::unique_ptr<IEngine> engine;
+		std::unique_ptr<DiffResult> currentDiffResult;
+		Ui_MainWindow ui;
+	};
+}