Click here to Skip to main content
15,887,135 members
Articles / Desktop Programming / MFC

IntelliDisk

Rate me:
Please Sign up or sign in to vote.
3.08/5 (4 votes)
20 Feb 2024GPL36 min read 11K   4   3
About the IntelliDisk app that uses many components published on CodeProject
This article is about the IntelliDisk application which is a free alternative Windows version to Microsoft OneDrive and uses many components that have been published on CodeProject.

IntelliDisk

Introduction

IntelliDisk application allows users to save files across multiple devices, and have them automatically sync on these devices. This means someone can access and work on the same document in multiple locations. IntelliDisk application provides relatively easy access to cloud storage space, allowing options to share content with others. IntelliDisk application is free as in free speech/free beer. It is developed as a three-tier architecture, which separates application into three logical and physical computing tiers.

What Is Three-Tier Architecture?

Three-tier architecture is a well-established software application architecture that organizes applications into three logical and physical computing tiers: the presentation tier, or user interface; the application tier, where data is processed; and the data tier, where the data associated with the application is stored and managed.

The chief benefit of three-tier architecture is that because each tier runs on its own infrastructure, each tier can be developed simultaneously by a separate development team, and can be updated or scaled as needed without impacting the other tiers.

Presentation Tier

The presentation tier is the user interface and communication layer of the application, where the end user interacts with the application. Its main purpose is to display information to and collect information from the user. This top-level tier can run on a web browser, as desktop application, or a graphical user interface (GUI), for example. Web presentation tiers are usually developed using HTML, CSS and JavaScript. Desktop applications can be written in a variety of languages depending on the platform.

Application Tier

The application tier, also known as the logic tier or middle tier, is the heart of the application. In this tier, information collected in the presentation tier is processed - sometimes against other information in the data tier - using business logic, a specific set of business rules. The application tier can also add, delete or modify data in the data tier. The application tier is typically developed using C++, Python, Java, Perl, PHP or Ruby, and communicates with the data tier using API calls.

Data Tier

The data tier, sometimes called database tier, data access tier or back-end, is where the information processed by the application is stored and managed. This can be a relational database management system such as PostgreSQL, MySQL, MariaDB, Oracle, DB2, Informix or Microsoft SQL Server, or in a NoSQL Database server such as Cassandra, CouchDB or MongoDB. In a three-tier application, all communication goes through the application tier. The presentation tier and the data tier cannot communicate directly with one another.

Tier vs. Layer

In discussions of three-tier architecture, layer is often used interchangeably – and mistakenly – for tier, as in 'presentation layer' or 'business logic layer'. They aren't the same. A 'layer' refers to a functional division of the software, but a 'tier' refers to a functional division of the software that runs on infrastructure separate from the other divisions. The Contacts app on your phone, for example, is a three-layer application, but a single-tier application, because all three layers run on your phone. The difference is important, because layers can't offer the same benefits as tiers.

Three-Tier Architecture

Benefits of Three-Tier Architecture

Again, the chief benefit of three-tier architecture is its logical and physical separation of functionality. Each tier can run on a separate operating system and server platform - e.g., web server, application server, database server - that best fits its functional requirements. And each tier runs on at least one dedicated server hardware or virtual server, so the services of each tier can be customized and optimized without impacting the other tiers. Other benefits (compared to single- or two-tier architecture) include:

  • Faster development: Because each tier can be developed simultaneously by different teams, an organization can bring the application to market faster, and programmers can use the latest and best languages and tools for each tier.
  • Improved scalability: Any tier can be scaled independently of the others as needed.
  • Improved reliability: An outage in one tier is less likely to impact the availability or performance of the other tiers.
  • Improved security: Because the presentation tier and data tier can't communicate directly, a well-designed application tier can function as a sort of internal firewall, preventing SQL injections and other malicious exploits.

IntelliDisk's Presentation Tier

The IntelliDisk's presentation tier is implemented as Desktop MFC Application running on Microsoft Windows 10+, developed using Microsoft Visual C++. Please check the Client folder from GitHub repository.

C++
bool DownloadFile(CWSocket& pApplicationSocket, const std::wstring& strFilePath)
{
    SHA256 pSHA256;
    unsigned char pFileBuffer[MAX_BUFFER] = { 0, };
    try
    {
        g_strCurrentDocument = strFilePath;
        TRACE(_T("[DownloadFile] %s\n"), strFilePath.c_str());
        CFile pBinaryFile(strFilePath.c_str(), CFile::modeWrite | 
                          CFile::modeCreate | CFile::typeBinary);
        ULONGLONG nFileLength = 0;
        int nLength = (int)(sizeof(nFileLength) + 5);
        ZeroMemory(pFileBuffer, sizeof(pFileBuffer));
        if (ReadBuffer(pApplicationSocket, pFileBuffer, nLength, false, false))
        {
            CopyMemory(&nFileLength, &pFileBuffer[3], sizeof(nFileLength));
            TRACE(_T("nFileLength = %llu\n"), nFileLength);

            ULONGLONG nFileIndex = 0;
            while (nFileIndex < nFileLength)
            {
                nLength = (int)sizeof(pFileBuffer);
                ZeroMemory(pFileBuffer, sizeof(pFileBuffer));
                if (ReadBuffer(pApplicationSocket, pFileBuffer, nLength, false, false))
                {
                    nFileIndex += (nLength - 5);
                    pSHA256.update(&pFileBuffer[3], nLength - 5);

                    pBinaryFile.Write(&pFileBuffer[3], nLength - 5);
                }
            }
        }
        else
        {
            TRACE(_T("Invalid nFileLength!\n"));
            pBinaryFile.Close();
            return false;
        }
        const std::string strDigestSHA256 = pSHA256.toString(pSHA256.digest());
        nLength = (int)strDigestSHA256.length() + 5;
        ZeroMemory(pFileBuffer, sizeof(pFileBuffer));
        if (ReadBuffer(pApplicationSocket, pFileBuffer, nLength, false, true))
        {
            const std::string strCommand = (char*)&pFileBuffer[3];
            if (strDigestSHA256.compare(strCommand) != 0)
            {
                TRACE(_T("Invalid SHA256!\n"));
                pBinaryFile.Close();
                return false;
            }
        }
        pBinaryFile.Close();
        g_strCurrentDocument.empty();
    }
    catch (CFileException* pException)
    {
        const int nErrorLength = 0x100;
        TCHAR lpszErrorMessage[nErrorLength] = { 0, };
        pException->GetErrorMessage(lpszErrorMessage, nErrorLength);
        TRACE(_T("%s\n"), lpszErrorMessage);
        pException->Delete();
        return false;
    }
    return true;
}

bool UploadFile(CWSocket& pApplicationSocket, const std::wstring& strFilePath)
{
    SHA256 pSHA256;
    unsigned char pFileBuffer[MAX_BUFFER] = { 0, };
    try
    {
        TRACE(_T("[UploadFile] %s\n"), strFilePath.c_str());
        CFile pBinaryFile(strFilePath.c_str(), CFile::modeRead | CFile::typeBinary);
        ULONGLONG nFileLength = pBinaryFile.GetLength();
        int nLength = sizeof(nFileLength);
        if (WriteBuffer(pApplicationSocket, (unsigned char*)&nFileLength, 
                                             nLength, false, false))
        {
            ULONGLONG nFileIndex = 0;
            while (nFileIndex < nFileLength)
            {
                nLength = pBinaryFile.Read(pFileBuffer, MAX_BUFFER - 5);
                nFileIndex += nLength;
                if (WriteBuffer(pApplicationSocket, pFileBuffer, nLength, false, false))
                {
                    pSHA256.update(pFileBuffer, nLength);
                }
                else
                {
                    pBinaryFile.Close();
                    return false;
                }
            }
        }
        else
        {
            TRACE(_T("Invalid nFileLength!\n"));
            pBinaryFile.Close();
            return false;
        }
        const std::string strDigestSHA256 = pSHA256.toString(pSHA256.digest());
        nLength = (int)strDigestSHA256.length() + 1;
        if (WriteBuffer(pApplicationSocket, 
           (unsigned char*)strDigestSHA256.c_str(), nLength, false, true))
        {
            TRACE(_T("Upload Done!\n"));
        }
        else
        {
            pBinaryFile.Close();
            return false;
        }
        pBinaryFile.Close();
    }
    catch (CFileException* pException)
    {
        const int nErrorLength = 0x100;
        TCHAR lpszErrorMessage[nErrorLength] = { 0, };
        pException->GetErrorMessage(lpszErrorMessage, nErrorLength);
        TRACE(_T("%s\n"), lpszErrorMessage);
        pException->Delete();
        return false;
    }
    return true;
}

DWORD WINAPI ProducerThread(LPVOID lpParam)
{
    unsigned char pBuffer[MAX_BUFFER] = { 0, };
    int nLength = 0;

    CMainFrame* pMainFrame = (CMainFrame*)lpParam;
    CWSocket& pApplicationSocket = pMainFrame->m_pApplicationSocket;
    HANDLE& hSocketMutex = pMainFrame->m_hSocketMutex;

    while (g_bThreadRunning)
    {
        try
        {
            WaitForSingleObject(hSocketMutex, INFINITE);

            if (!pApplicationSocket.IsCreated())
            {
                pApplicationSocket.CreateAndConnect
                (pMainFrame->m_strServerIP, pMainFrame->m_nServerPort);
                const std::string strCommand = "IntelliDisk";
                nLength = (int)strCommand.length() + 1;
                if (WriteBuffer(pApplicationSocket, 
                   (unsigned char*) strCommand.c_str(), nLength, true, false))
                {
                    TRACE(_T("Client connected!\n"));
                    const std::string strMachineID = GetMachineID();
                    int nComputerLength = (int)strMachineID.length() + 1;
                    if (WriteBuffer(pApplicationSocket, 
                       (unsigned char*) strMachineID.c_str(), 
                        nComputerLength, false, true))
                    {
                        TRACE(_T("Logged In!\n"));
                        g_bIsConnected = true;
                        MessageBeep(MB_OK);
                    }
                }
            }
            else
            {
                if (pApplicationSocket.IsReadible(1000))
                {
                    g_nPingCount = 0;
                    nLength = sizeof(pBuffer);
                    ZeroMemory(pBuffer, sizeof(pBuffer));
                    if (ReadBuffer(pApplicationSocket, pBuffer, nLength, true, false))
                    {
                        const std::string strCommand = (char*)&pBuffer[3];
                        TRACE(_T("strCommand = %s\n"), 
                              utf8_to_wstring(strCommand).c_str());
                        if (strCommand.compare("Restart") == 0)
                        {
                            nLength = sizeof(pBuffer);
                            ZeroMemory(pBuffer, sizeof(pBuffer));
                            if (((nLength = 
                                 pApplicationSocket.Receive(pBuffer, nLength)) > 0) &&
                                (EOT == pBuffer[nLength - 1]))
                            {
                                TRACE(_T("EOT Received\n"));
                            }
                            TRACE(_T("Restart!\n"));
                            pApplicationSocket.Close();
                            g_bIsConnected = false;
                        }
                        else if (strCommand.compare("NotifyDownload") == 0)
                        {
                            nLength = sizeof(pBuffer);
                            ZeroMemory(pBuffer, sizeof(pBuffer));
                            if (ReadBuffer(pApplicationSocket, pBuffer, 
                                nLength, false, false))
                            {
                                const std::wstring strUNICODE = 
                                decode_filepath(utf8_to_wstring((char*)&pBuffer[3]));
                                // AddNewItem(ID_FILE_DOWNLOAD, strUNICODE, lpParam);

                                CString strMessage;
                                strMessage.Format(_T("Downloading %s..."), 
                                                      strUNICODE.c_str());
                                pMainFrame->ShowMessage
                                     (strMessage.GetBuffer(), strUNICODE);
                                strMessage.ReleaseBuffer();

                                TRACE(_T("Downloading %s...\n"), strUNICODE.c_str());
                                VERIFY(DownloadFile(pApplicationSocket, strUNICODE));
                            }
                        }
                        else if (strCommand.compare("NotifyDelete") == 0)
                        {
                            nLength = sizeof(pBuffer);
                            ZeroMemory(pBuffer, sizeof(pBuffer));
                            if (ReadBuffer(pApplicationSocket, 
                                pBuffer, nLength, false, false))
                            {
                                const std::wstring strUNICODE = 
                                decode_filepath(utf8_to_wstring((char*)&pBuffer[3]));
                                VERIFY(DeleteFile(strUNICODE.c_str()));
                            }
                        }
                    }
                }
                else
                {
                    if (60 == ++g_nPingCount) // every 60 seconds
                    {
                        g_nPingCount = 0;
                        const std::string strCommand = "Ping";
                        nLength = (int)strCommand.length() + 1;
                        if (WriteBuffer(pApplicationSocket, 
                           (unsigned char*) strCommand.c_str(), nLength, true, true))
                        {
                            TRACE(_T("Ping!\n"));
                        }
                    }
                }
            }

            ReleaseSemaphore(hSocketMutex, 1, nullptr);
        }
        catch (CWSocketException* pException)
        {
            const int nErrorLength = 0x100;
            TCHAR lpszErrorMessage[nErrorLength] = { 0, };
            pException->GetErrorMessage(lpszErrorMessage, nErrorLength);
            TRACE(_T("%s\n"), lpszErrorMessage);
            pException->Delete();
            ReleaseSemaphore(hSocketMutex, 1, nullptr);
            pApplicationSocket.Close();
            g_bIsConnected = false;
            Sleep(1000);
            continue;
        }
    }
    TRACE(_T("exiting...\n"));
    return 0;
}

DWORD WINAPI ConsumerThread(LPVOID lpParam)
{
    int nLength = 0;

    CMainFrame* pMainFrame = (CMainFrame*)lpParam;
    HANDLE& hOccupiedSemaphore = pMainFrame->m_hOccupiedSemaphore;
    HANDLE& hEmptySemaphore = pMainFrame->m_hEmptySemaphore;
    HANDLE& hResourceMutex = pMainFrame->m_hResourceMutex;
    HANDLE& hSocketMutex = pMainFrame->m_hSocketMutex;

    CWSocket& pApplicationSocket = pMainFrame->m_pApplicationSocket;

    while (g_bThreadRunning)
    {
        WaitForSingleObject(hOccupiedSemaphore, INFINITE);
        WaitForSingleObject(hResourceMutex, INFINITE);

        const int nFileEvent = pMainFrame->m_pResourceArray
                               [pMainFrame->m_nNextOut].nFileEvent;
        const std::wstring& strFilePath = 
              pMainFrame->m_pResourceArray[pMainFrame->m_nNextOut].strFilePath;
        TRACE(_T("[ConsumerThread] nFileEvent = %d, 
              strFilePath = \"%s\"\n"), nFileEvent, strFilePath.c_str());
        pMainFrame->m_nNextOut++;
        pMainFrame->m_nNextOut %= NOTIFY_FILE_SIZE;

        if (ID_STOP_PROCESS == nFileEvent)
        {
            TRACE(_T("Stopping...\n"));
            g_bThreadRunning = false;
        }
        else if (ID_FILE_DOWNLOAD == nFileEvent)
        {
            CString strMessage;
            strMessage.Format(_T("Downloading %s..."), strFilePath.c_str());
            pMainFrame->ShowMessage(strMessage.GetBuffer(), strFilePath);
            strMessage.ReleaseBuffer();
        }
        else if (ID_FILE_UPLOAD == nFileEvent)
        {
            CString strMessage;
            strMessage.Format(_T("Uploading %s..."), strFilePath.c_str());
            pMainFrame->ShowMessage(strMessage.GetBuffer(), strFilePath);
            strMessage.ReleaseBuffer();
        }
        else if (ID_FILE_DELETE == nFileEvent)
        {
            CString strMessage;
            strMessage.Format(_T("Deleting %s..."), strFilePath.c_str());
            pMainFrame->ShowMessage(strMessage.GetBuffer(), strFilePath);
            strMessage.ReleaseBuffer();
        }

        ReleaseSemaphore(hResourceMutex, 1, nullptr);
        ReleaseSemaphore(hEmptySemaphore, 1, nullptr);

        while (g_bThreadRunning && !g_bIsConnected);

        WaitForSingleObject(hSocketMutex, INFINITE);

        try
        {
            if (pApplicationSocket.IsWritable(1000))
            {
                if (ID_STOP_PROCESS == nFileEvent)
                {
                    const std::string strCommand = "Close";
                    nLength = (int)strCommand.length() + 1;
                    if (WriteBuffer(pApplicationSocket, 
                       (unsigned char*) strCommand.c_str(), nLength, true, true))
                    {
                        TRACE("Closing...\n");
                        pApplicationSocket.Close();
                    }
                }
                else
                {
                    if (ID_FILE_DOWNLOAD == nFileEvent)
                    {
                        // TRACE(_T("Downloading %s...\n"), strFilePath.c_str());
                        // VERIFY(DownloadFile(pApplicationSocket, strFilePath));
                    }
                    else
                    {
                        if (ID_FILE_UPLOAD == nFileEvent)
                        {
                            const std::string strCommand = "Upload";
                            nLength = (int)strCommand.length() + 1;
                            if (WriteBuffer(pApplicationSocket, 
                            (unsigned char*) strCommand.c_str(), nLength, true, false))
                            {
                                const std::string strASCII = 
                                      wstring_to_utf8(encode_filepath(strFilePath));
                                const int nFileNameLength = (int)strASCII.length() + 1;
                                if (WriteBuffer(pApplicationSocket, 
                                   (unsigned char*) strASCII.c_str(), 
                                    nFileNameLength, false, false))
                                {
                                    TRACE(_T("Uploading %s...\n"), strFilePath.c_str());
                                    VERIFY(UploadFile(pApplicationSocket, strFilePath));
                                }
                            }
                        }
                        else
                        {
                            if (ID_FILE_DELETE == nFileEvent)
                            {
                                const std::string strCommand = "Delete";
                                nLength = (int)strCommand.length() + 1;
                                if (WriteBuffer(pApplicationSocket, 
                                   (unsigned char*) strCommand.c_str(), 
                                    nLength, true, false))
                                {
                                    const std::string strASCII = 
                                      wstring_to_utf8(encode_filepath(strFilePath));
                                    const int nFileNameLength = 
                                        (int)strASCII.length() + 1;
                                    if (WriteBuffer(pApplicationSocket, 
                                       (unsigned char*) strASCII.c_str(), 
                                        nFileNameLength, false, true))
                                    {
                                        TRACE(_T("Deleting %s...\n"), 
                                                  strFilePath.c_str());
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
        catch (CWSocketException* pException)
        {
            const int nErrorLength = 0x100;
            TCHAR lpszErrorMessage[nErrorLength] = { 0, };
            pException->GetErrorMessage(lpszErrorMessage, nErrorLength);
            TRACE(_T("%s\n"), lpszErrorMessage);
            pException->Delete();
            g_bIsConnected = false;
        }

        ReleaseSemaphore(hSocketMutex, 1, nullptr);
    }
    return 0;
}

void AddNewItem(const int nFileEvent, const std::wstring& strFilePath, LPVOID lpParam)
{
    if ((g_strCurrentDocument.compare(strFilePath) == 0) && 
                                                   (ID_FILE_UPLOAD == nFileEvent))
    {
        return;
    }

    CMainFrame* pMainFrame = (CMainFrame*)lpParam;
    HANDLE& hOccupiedSemaphore = pMainFrame->m_hOccupiedSemaphore;
    HANDLE& hEmptySemaphore = pMainFrame->m_hEmptySemaphore;
    HANDLE& hResourceMutex = pMainFrame->m_hResourceMutex;

    WaitForSingleObject(hEmptySemaphore, INFINITE);
    WaitForSingleObject(hResourceMutex, INFINITE);

    TRACE(_T("[AddNewItem] nFileEvent = %d, strFilePath = \"%s\"\n"), 
                                        nFileEvent, strFilePath.c_str());
    pMainFrame->m_pResourceArray[pMainFrame->m_nNextIn].nFileEvent = nFileEvent;
    pMainFrame->m_pResourceArray[pMainFrame->m_nNextIn].strFilePath = strFilePath;
    pMainFrame->m_nNextIn++;
    pMainFrame->m_nNextIn %= NOTIFY_FILE_SIZE;

    ReleaseSemaphore(hResourceMutex, 1, nullptr);
    ReleaseSemaphore(hOccupiedSemaphore, 1, nullptr);
}

IntelliDisk's Application Tier

The IntelliDisk's application tier is implemented as NT Service running on Microsoft Windows 10+, developed using Microsoft Visual C++. Please check the Server folder from GitHub repository.

C++
void PushNotification(const int& nSocketIndex, const int nFileEvent, 
                      const std::wstring& strFilePath)
{
    NOTIFY_FILE_ITEM* pThreadData = g_pThreadData[nSocketIndex];
    if ((pThreadData->hResourceMutex != nullptr) &&
        (pThreadData->hEmptySemaphore != nullptr) &&
        (pThreadData->hOccupiedSemaphore != nullptr))
    {
        WaitForSingleObject(pThreadData->hEmptySemaphore, INFINITE);
        WaitForSingleObject(pThreadData->hResourceMutex, INFINITE);

        TRACE(_T("[PushNotification] nFileEvent = %d, 
              strFilePath = \"%s\"\n"), nFileEvent, strFilePath.c_str());
        pThreadData->arrNotifyData[pThreadData->nNextIn].nFileEvent = nFileEvent;
        pThreadData->arrNotifyData[pThreadData->nNextIn].strFilePath = strFilePath;
        pThreadData->nNextIn++;
        pThreadData->nNextIn %= NOTIFY_FILE_SIZE;

        ReleaseSemaphore(pThreadData->hResourceMutex, 1, nullptr);
        ReleaseSemaphore(pThreadData->hOccupiedSemaphore, 1, nullptr);
    }
}

void PopNotification(const int& nSocketIndex, int& nFileEvent, std::wstring& strFilePath)
{
    NOTIFY_FILE_ITEM* pThreadData = g_pThreadData[nSocketIndex];
    if ((pThreadData->hResourceMutex != nullptr) &&
        (pThreadData->hEmptySemaphore != nullptr) &&
        (pThreadData->hOccupiedSemaphore != nullptr))
    {
        WaitForSingleObject(pThreadData->hOccupiedSemaphore, INFINITE);
        WaitForSingleObject(pThreadData->hResourceMutex, INFINITE);

        nFileEvent = pThreadData->arrNotifyData[pThreadData->nNextOut].nFileEvent;
        strFilePath = pThreadData->arrNotifyData[pThreadData->nNextOut].strFilePath;
        pThreadData->nNextOut++;
        pThreadData->nNextOut %= NOTIFY_FILE_SIZE;
        TRACE(_T("[PopNotification] nFileEvent = %d, 
              strFilePath = \"%s\"\n"), nFileEvent, strFilePath.c_str());

        ReleaseSemaphore(pThreadData->hResourceMutex, 1, nullptr);
        ReleaseSemaphore(pThreadData->hEmptySemaphore, 1, nullptr);
    }
}

DWORD WINAPI IntelliDiskThread(LPVOID lpParam)
{
    unsigned char pBuffer[MAX_BUFFER] = { 0, };
    int nLength = 0;
    const int nSocketIndex = *(int*)(lpParam);
    TRACE(_T("nSocketIndex = %d\n"), (nSocketIndex));
    CWSocket& pApplicationSocket = g_pClientSocket[nSocketIndex];
    ASSERT(pApplicationSocket.IsCreated());
    std::wstring strComputerID;

    g_pThreadData[nSocketIndex] = new NOTIFY_FILE_ITEM;
    ASSERT(g_pThreadData[nSocketIndex] != nullptr);
    NOTIFY_FILE_ITEM* pThreadData = g_pThreadData[nSocketIndex];
    ASSERT(pThreadData != nullptr);
    pThreadData->hOccupiedSemaphore = CreateSemaphore
                                      (nullptr, 0, NOTIFY_FILE_SIZE, nullptr);
    pThreadData->hEmptySemaphore = CreateSemaphore
                 (nullptr, NOTIFY_FILE_SIZE, NOTIFY_FILE_SIZE, nullptr);
    pThreadData->hResourceMutex = CreateSemaphore(nullptr, 1, 1, nullptr);
    pThreadData->nNextIn = pThreadData->nNextOut = 0;

    g_bIsConnected[nSocketIndex] = false;

    while (bThreadRunning)
    {
        try
        {
            if (!pApplicationSocket.IsCreated())
                break;

            if (pApplicationSocket.IsReadible(1000))
            {
                nLength = sizeof(pBuffer);
                ZeroMemory(pBuffer, sizeof(pBuffer));
                if (ReadBuffer(nSocketIndex, pApplicationSocket, 
                    pBuffer, nLength, true, false))
                {
                    const std::string strCommand = (char*) &pBuffer[3];
                    if (strCommand.compare("IntelliDisk") == 0)
                    {
                        TRACE(_T("Client connected!\n"));
                        nLength = sizeof(pBuffer);
                        ZeroMemory(pBuffer, sizeof(pBuffer));
                        if (ReadBuffer(nSocketIndex, 
                            pApplicationSocket, pBuffer, nLength, false, true))
                        {
                            strComputerID = utf8_to_wstring((char*) &pBuffer[3]);
                            TRACE(_T("Logged In: %s!\n"), strComputerID.c_str());
                            g_bIsConnected[nSocketIndex] = true;
                        }
                    }
                    else
                    {
                        if (strCommand.compare("Close") == 0)
                        {
                            nLength = sizeof(pBuffer);
                            ZeroMemory(pBuffer, sizeof(pBuffer));
                            if (((nLength = pApplicationSocket.Receive
                                 (pBuffer, nLength)) > 0) &&
                                (EOT == pBuffer[nLength - 1]))
                            {
                                TRACE(_T("EOT Received\n"));
                            }
                            pApplicationSocket.Close();
                            TRACE(_T("Logged Out: %s!\n"), strComputerID.c_str());
                            break;
                        }
                        else
                        {
                            if (strCommand.compare("Ping") == 0)
                            {
                                nLength = sizeof(pBuffer);
                                ZeroMemory(pBuffer, sizeof(pBuffer));
                                if (((nLength = pApplicationSocket.Receive
                                     (pBuffer, nLength)) > 0) &&
                                    (EOT == pBuffer[nLength - 1]))
                                {
                                    TRACE(_T("EOT Received\n"));
                                }
                                TRACE(_T("Ping!\n"));
                            }
                            else
                            {
                                if (strCommand.compare("Download") == 0)
                                {
                                    nLength = sizeof(pBuffer);
                                    ZeroMemory(pBuffer, sizeof(pBuffer));
                                    if (ReadBuffer(nSocketIndex, 
                                    pApplicationSocket, pBuffer, nLength, false, false))
                                    {
                                        const std::wstring& strFilePath = 
                                              utf8_to_wstring((char*) &pBuffer[3]);
                                        TRACE(_T("Downloading %s...\n"), 
                                                  strFilePath.c_str());
                                        VERIFY(DownloadFile(nSocketIndex, 
                                               pApplicationSocket, strFilePath));
                                    }
                                }
                                else
                                {
                                    if (strCommand.compare("Upload") == 0)
                                    {
                                        nLength = sizeof(pBuffer);
                                        ZeroMemory(pBuffer, sizeof(pBuffer));
                                        if (ReadBuffer(nSocketIndex, 
                                            pApplicationSocket, pBuffer, nLength, 
                                            false, false))
                                        {
                                            const std::wstring& strFilePath = 
                                               utf8_to_wstring((char*) &pBuffer[3]);
                                            TRACE(_T("Uploading %s...\n"), 
                                                      strFilePath.c_str());
                                            VERIFY(UploadFile(nSocketIndex, 
                                                   pApplicationSocket, strFilePath));
                                            // Notify all other Clients
                                            for (int nThreadIndex = 0; 
                                            nThreadIndex < g_nSocketCount; nThreadIndex++)
                                            {
                                                if (nThreadIndex != 
                                                nSocketIndex) // skip current thread queue
                                                    PushNotification(nThreadIndex, 
                                                    ID_FILE_DOWNLOAD, strFilePath);
                                            }
                                        }
                                    }
                                    else
                                    {
                                        if (strCommand.compare("Delete") == 0)
                                        {
                                            nLength = sizeof(pBuffer);
                                            ZeroMemory(pBuffer, sizeof(pBuffer));
                                            if (ReadBuffer(nSocketIndex, 
                                                pApplicationSocket, pBuffer, 
                                                nLength, false, false))
                                            {
                                                const std::wstring& strFilePath = 
                                                utf8_to_wstring((char*) &pBuffer[3]);
                                                TRACE(_T("Deleting %s...\n"), 
                                                          strFilePath.c_str());
                                                VERIFY(DeleteFile(nSocketIndex, 
                                                pApplicationSocket, strFilePath));
                                                // Notify all other Clients
                                                for (int nThreadIndex = 0; 
                                                     nThreadIndex < g_nSocketCount; 
                                                     nThreadIndex++)
                                                {
                                                    if (nThreadIndex != 
                                                        nSocketIndex)  // skip current 
                                                                       // thread queue
                                                        PushNotification(nThreadIndex, 
                                                        ID_FILE_DELETE, strFilePath);
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
            else
            {
                if (!bThreadRunning)
                {
                    const std::string strCommand = "Restart";
                    nLength = (int)strCommand.length() + 1;
                    if (WriteBuffer(nSocketIndex, pApplicationSocket, 
                       (unsigned char*)strCommand.c_str(), nLength, true, true))
                    {
                        TRACE(_T("Restart!\n"));
                    }
                }
                else
                {
                    if (pThreadData->nNextIn != pThreadData->nNextOut)
                    {
                        int nFileEvent = 0;
                        std::wstring strFilePath;
                        PopNotification(nSocketIndex, nFileEvent, strFilePath);
                        if (ID_FILE_DOWNLOAD == nFileEvent)
                        {
                            const std::string strCommand = "NotifyDownload";
                            nLength = (int)strCommand.length() + 1;
                            if (WriteBuffer(nSocketIndex, pApplicationSocket, 
                            (unsigned char*)strCommand.c_str(), nLength, true, false))
                            {
                                const std::string strFileName = 
                                           wstring_to_utf8(strFilePath);
                                nLength = (int)strFileName.length() + 1;
                                if (WriteBuffer(nSocketIndex, 
                                    pApplicationSocket, 
                                    (unsigned char*)strFileName.c_str(), 
                                     nLength, false, false))
                                {
                                    TRACE(_T("Downloading %s...\n"), 
                                              strFilePath.c_str());
                                    VERIFY(DownloadFile(nSocketIndex, 
                                           pApplicationSocket, strFilePath));
                                }
                            }
                        }
                        else if (ID_FILE_DELETE == nFileEvent)
                        {
                            const std::string strCommand = "NotifyDelete";
                            nLength = (int)strCommand.length() + 1;
                            if (WriteBuffer(nSocketIndex, pApplicationSocket, 
                            (unsigned char*)strCommand.c_str(), nLength, true, false))
                            {
                                const std::string strFileName = 
                                           wstring_to_utf8(strFilePath);
                                nLength = (int)strFileName.length() + 1;
                                if (WriteBuffer(nSocketIndex, 
                                    pApplicationSocket, 
                                    (unsigned char*)strFileName.c_str(), 
                                     nLength, false, false))
                                {
                                    TRACE(_T("Deleting %s...\n"), strFilePath.c_str());
                                }
                            }
                        }
                    }
                }
            }
        }
        catch (CWSocketException* pException)
        {
            const int nErrorLength = 0x100;
            TCHAR lpszErrorMessage[nErrorLength] = { 0, };
            pException->GetErrorMessage(lpszErrorMessage, nErrorLength);
            TRACE(_T("%s\n"), lpszErrorMessage);
            pException->Delete();
            pApplicationSocket.Close();
            g_bIsConnected[nSocketIndex] = false;
            break;
        }
    }

    if (pThreadData->hResourceMutex != nullptr)
    {
        VERIFY(CloseHandle(pThreadData->hResourceMutex));
        pThreadData->hResourceMutex = nullptr;
    }

    if (pThreadData->hEmptySemaphore != nullptr)
    {
        VERIFY(CloseHandle(pThreadData->hEmptySemaphore));
        pThreadData->hEmptySemaphore = nullptr;
    }

    if (pThreadData->hOccupiedSemaphore != nullptr)
    {
        VERIFY(CloseHandle(pThreadData->hOccupiedSemaphore));
        pThreadData->hOccupiedSemaphore = nullptr;
    }

    delete g_pThreadData[nSocketIndex];
    g_pThreadData[nSocketIndex] = nullptr;

    TRACE(_T("exiting...\n"));
    return 0;
}

int g_nServicePort = IntelliDiskPort;
DWORD WINAPI CreateDatabase(LPVOID lpParam)
{
    UNREFERENCED_PARAMETER(lpParam);
    try
    {
        TRACE(_T("CreateDatabase()\n"));
        g_nServicePort = LoadServicePort();
        if (!LoadAppSettings(g_strHostName, g_nHostPort, g_strDatabase, 
                             g_strUsername, g_strPassword))
        {
            // return (DWORD)-1;
        }
        g_pServerSocket.CreateAndBind(g_nServicePort, SOCK_STREAM, AF_INET);
        if (g_pServerSocket.IsCreated())
        {
            bThreadRunning = true;
            g_pServerSocket.Listen(MAX_SOCKET_CONNECTIONS);
            while (bThreadRunning)
            {
                g_pServerSocket.Accept(g_pClientSocket[g_nSocketCount]);
                if (bThreadRunning)
                {
                    const int nSocketIndex = g_nSocketCount;
                    g_hThreadArray[g_nThreadCount] = 
                      CreateThread(nullptr, 0, IntelliDiskThread, 
                      (int*)&nSocketIndex, 0, &m_dwThreadID[g_nThreadCount]);
                    ASSERT(g_hThreadArray[g_nThreadCount] != nullptr);
                    g_nSocketCount++;
                    g_nThreadCount++;
                }
                else
                {
                    g_pClientSocket[g_nSocketCount].Close();
                    g_nSocketCount++;
                }
            }
        }
        g_pServerSocket.Close();
    }
    catch (CWSocketException* pException)
    {
        const int nErrorLength = 0x100;
        TCHAR lpszErrorMessage[nErrorLength] = { 0, };
        pException->GetErrorMessage(lpszErrorMessage, nErrorLength);
        TRACE(_T("%s\n"), lpszErrorMessage);
        pException->Delete();
    }
    return 0;
}

void StartProcessingThread()
{
    TRACE(_T("StartProcessingThread()\n"));
    g_hThreadArray[g_nThreadCount] = CreateThread(nullptr, 0, 
       CreateDatabase, nullptr, 0, &m_dwThreadID[g_nThreadCount]);
    ASSERT(g_hThreadArray[g_nThreadCount] != nullptr);
    g_nThreadCount++;
}

void StopProcessingThread()
{
    CWSocket pClosingSocket;
    try
    {
        TRACE(_T("StopProcessingThread()\n"));
        if (bThreadRunning)
        {
            bThreadRunning = false;
            pClosingSocket.CreateAndConnect(IntelliDiskIP, g_nServicePort);
            WaitForMultipleObjects(g_nThreadCount, g_hThreadArray, TRUE, INFINITE);
            pClosingSocket.Close();
            for (int nIndex = 0; nIndex < g_nSocketCount; nIndex++)
                g_pClientSocket[nIndex].Close();
            g_nSocketCount = 0;
            g_nThreadCount = 0;
        }
    }
    catch (CWSocketException* pException)
    {
        const int nErrorLength = 0x100;
        TCHAR lpszErrorMessage[nErrorLength] = { 0, };
        pException->GetErrorMessage(lpszErrorMessage, nErrorLength);
        TRACE(_T("%s\n"), lpszErrorMessage);
        pException->Delete();
    }
}

IntelliDisk's Data Tier

The IntelliDisk's data tier is currently implemented with a MySQL database; other relational databases could be used if this tier is explicitly extended.

C++
class CFilenameInsertAccessor // sets the data for inserting one row intro FILENAME table
{
public:
    // Parameter values
    TCHAR m_lpszFilepath[4000];
    __int64 m_nFilesize;

    BEGIN_ODBC_PARAM_MAP(CFilenameInsertAccessor)
        SET_ODBC_PARAM_TYPE(SQL_PARAM_INPUT)
        ODBC_PARAM_ENTRY(1, m_lpszFilepath)
        ODBC_PARAM_ENTRY(2, m_nFilesize)
    END_ODBC_PARAM_MAP()

    DEFINE_ODBC_COMMAND(CFilenameInsertAccessor, _T("INSERT INTO `filename` 
                       (`filepath`, `filesize`) VALUES (?, ?);"))

        // You may wish to call this function if you are inserting a record and wish to
        // initialize all the fields, if you are not going to explicitly set all of them.
        void ClearRecord() noexcept
    {
        memset(this, 0, sizeof(*this));
    }
};

class CFilenameInsert : public CODBC::CAccessor<CFilenameInsertAccessor> // execute 
                        // INSERT statement for FILENAME table; no output returned
{
public:
    // Methods
    bool Execute(CODBC::CConnection& pDbConnect, const std::wstring& lpszFilepath, 
                 const __int64& nFilesize)
    {
        ClearRecord();
        // Create the statement object
        CODBC::CStatement statement;
        SQLRETURN nRet = statement.Create(pDbConnect);
        ODBC_CHECK_RETURN_FALSE(nRet, statement);

        // Prepare the statement
        nRet = statement.Prepare(GetDefaultCommand());
        ODBC_CHECK_RETURN_FALSE(nRet, statement);

        // Bind the parameters
        _tcscpy_s(m_lpszFilepath, _countof(m_lpszFilepath), lpszFilepath.c_str());
        m_nFilesize = nFilesize;
        nRet = BindParameters(statement);
        ODBC_CHECK_RETURN_FALSE(nRet, statement);

        // Execute the statement
        nRet = statement.Execute();
        ODBC_CHECK_RETURN_FALSE(nRet, statement);
        return true;
    }
};

class CFilenameSelectAccessor // sets the data for selecting one row intro FILENAME table
{
public:
    // Parameter values
    TCHAR m_lpszFilepath[4000];

    BEGIN_ODBC_PARAM_MAP(CFilenameSelectAccessor)
        SET_ODBC_PARAM_TYPE(SQL_PARAM_INPUT)
        ODBC_PARAM_ENTRY(1, m_lpszFilepath)
    END_ODBC_PARAM_MAP()

    DEFINE_ODBC_COMMAND(CFilenameSelectAccessor, _T
    ("SET @last_filename_id = (SELECT `filename_id` 
      FROM `filename` WHERE `filepath` = ?);"))

        // You may wish to call this function if you are inserting a record and wish to
        // initialize all the fields, if you are not going to explicitly set all of them.
        void ClearRecord() noexcept
    {
        memset(this, 0, sizeof(*this));
    }
};

class CFilenameSelect : 
public CODBC::CAccessor<CFilenameSelectAccessor> // execute SELECT statement 
                                 // for FILENAME table; no output returned
{
public:
    // Methods
    bool Execute(CODBC::CConnection& pDbConnect, const std::wstring& lpszFilepath)
    {
        ClearRecord();
        // Create the statement object
        CODBC::CStatement statement;
        SQLRETURN nRet = statement.Create(pDbConnect);
        ODBC_CHECK_RETURN_FALSE(nRet, statement);

        // Prepare the statement
        nRet = statement.Prepare(GetDefaultCommand());
        ODBC_CHECK_RETURN_FALSE(nRet, statement);

        // Bind the parameters
        _tcscpy_s(m_lpszFilepath, _countof(m_lpszFilepath), lpszFilepath.c_str());
        nRet = BindParameters(statement);
        ODBC_CHECK_RETURN_FALSE(nRet, statement);

        // Execute the statement
        nRet = statement.Execute();
        ODBC_CHECK_RETURN_FALSE(nRet, statement);
        return true;
    }
};

class CFilenameUpdateAccessor // sets the data for updating one row intro FILENAME table
{
public:
    // Parameter values
    __int64 m_nFilesize;

    BEGIN_ODBC_PARAM_MAP(CFilenameUpdateAccessor)
        SET_ODBC_PARAM_TYPE(SQL_PARAM_INPUT)
        ODBC_PARAM_ENTRY(1, m_nFilesize)
    END_ODBC_PARAM_MAP()

    DEFINE_ODBC_COMMAND(CFilenameUpdateAccessor, _T
    ("UPDATE `filename` SET `filesize` = ? WHERE `filename_id` = @last_filename_id;"))

        // You may wish to call this function if you are inserting a record and wish to
        // initialize all the fields, if you are not going to explicitly set all of them.
        void ClearRecord() noexcept
    {
        memset(this, 0, sizeof(*this));
    }
};

class CFilenameUpdate : 
public CODBC::CAccessor<CFilenameUpdateAccessor> // execute UPDATE statement 
                                            // for FILENAME table; no output returned
{
public:
    // Methods
    bool Execute(CODBC::CConnection& pDbConnect, const __int64& nFilesize)
    {
        ClearRecord();
        // Create the statement object
        CODBC::CStatement statement;
        SQLRETURN nRet = statement.Create(pDbConnect);
        ODBC_CHECK_RETURN_FALSE(nRet, statement);

        // Prepare the statement
        nRet = statement.Prepare(GetDefaultCommand());
        ODBC_CHECK_RETURN_FALSE(nRet, statement);

        // Bind the parameters
        m_nFilesize = nFilesize;
        nRet = BindParameters(statement);
        ODBC_CHECK_RETURN_FALSE(nRet, statement);

        // Execute the statement
        nRet = statement.Execute();
        ODBC_CHECK_RETURN_FALSE(nRet, statement);
        return true;
    }
};

class CFiledataInsertAccessor // sets the data for inserting one row intro FILENAME table
{
public:
    // Parameter values
    TCHAR m_lpszContent[0x20000];
    __int64 m_nBase64;

    BEGIN_ODBC_PARAM_MAP(CFiledataInsertAccessor)
        SET_ODBC_PARAM_TYPE(SQL_PARAM_INPUT)
        ODBC_PARAM_ENTRY(1, m_lpszContent)
        ODBC_PARAM_ENTRY(2, m_nBase64)
    END_ODBC_PARAM_MAP()

    DEFINE_ODBC_COMMAND(CFiledataInsertAccessor, _T
    ("INSERT INTO `filedata` (`filename_id`, `content`, `base64`) 
      VALUES (@last_filename_id, ?, ?);"))

        // You may wish to call this function if you are inserting a record and wish to
        // initialize all the fields, if you are not going to explicitly set all of them.
        void ClearRecord() noexcept
    {
        memset(this, 0, sizeof(*this));
    }
};

class CFiledataInsert : 
 public CODBC::CAccessor<CFiledataInsertAccessor> // execute INSERT statement 
                                        // for FILENAME table; no output returned
{
public:
    // Methods
    bool Execute(CODBC::CConnection& pDbConnect, 
                 const std::wstring& lpszContent, const __int64& nBase64)
    {
        ClearRecord();
        // Create the statement object
        CODBC::CStatement statement;
        SQLRETURN nRet = statement.Create(pDbConnect);
        ODBC_CHECK_RETURN_FALSE(nRet, statement);

        // Prepare the statement
        nRet = statement.Prepare(GetDefaultCommand());
        ODBC_CHECK_RETURN_FALSE(nRet, statement);

        // Bind the parameters
        _tcscpy_s(m_lpszContent, _countof(m_lpszContent), lpszContent.c_str());
        m_nBase64 = nBase64;
        nRet = BindParameters(statement);
        ODBC_CHECK_RETURN_FALSE(nRet, statement);

        // Execute the statement
        nRet = statement.Execute();
        ODBC_CHECK_RETURN_FALSE(nRet, statement);
        return true;
    }
};

class CFilesizeSelectAccessor
{
public:
    // Parameter values
    __int64 m_nFilesize;

    BEGIN_ODBC_PARAM_MAP(CFilesizeSelectAccessor)
    END_ODBC_PARAM_MAP()

    BEGIN_ODBC_COLUMN_MAP(CFilesizeSelectAccessor)
        ODBC_COLUMN_ENTRY(1, m_nFilesize)
    END_ODBC_COLUMN_MAP()

    DEFINE_ODBC_COMMAND(CFilesizeSelectAccessor, _T
    ("SELECT `filesize` FROM `filename` WHERE `filename_id` = @last_filename_id;"))

        // You may wish to call this function if you are inserting a record and wish to
        // initialize all the fields, if you are not going to explicitly set all of them.
        void ClearRecord() noexcept
    {
        memset(this, 0, sizeof(*this));
    }
};

class CFilesizeSelect : public CODBC::CCommand<CODBC::CAccessor<CFilesizeSelectAccessor>>
{
public:
    // Methods
    bool Iterate(const CODBC::CConnection& pDbConnect, 
    ULONGLONG& nFileLength, _In_ bool bBind = true, 
    _In_opt_ CODBC::SQL_ATTRIBUTE* pAttributes = nullptr, _In_ ULONG nAttributes = 0)
    {
        nFileLength = 0;
        // Validate our parameters
        SQLRETURN nRet{ Open(pDbConnect, GetDefaultCommand(), 
                             bBind, pAttributes, nAttributes) };
        ODBC_CHECK_RETURN_FALSE(nRet, m_Command);

        // Iterate through the returned recordset
        while (true)
        {
            ClearRecord();
            nRet = m_Command.FetchNext();
            if (!SQL_SUCCEEDED(nRet))
                break;
            nFileLength = m_nFilesize;
        }

        return true;
    }
};

class CFiledataSelectAccessor
{
public:
    // Parameter values
    TCHAR m_lpszContent[0x20000];
    __int64 m_nBase64;

    BEGIN_ODBC_PARAM_MAP(CFiledataSelectAccessor)
    END_ODBC_PARAM_MAP()

    BEGIN_ODBC_COLUMN_MAP(CFiledataSelectAccessor)
        ODBC_COLUMN_ENTRY(1, m_lpszContent)
        ODBC_COLUMN_ENTRY(2, m_nBase64)
    END_ODBC_COLUMN_MAP()

    DEFINE_ODBC_COMMAND(CFiledataSelectAccessor, 
    _T("SELECT `content`, `base64` FROM `filedata` 
    WHERE `filename_id` = @last_filename_id ORDER BY `filedata_id` ASC;"))

        // You may wish to call this function if you are inserting a record and wish to
        // initialize all the fields, if you are not going to explicitly set all of them.
        void ClearRecord() noexcept
    {
        memset(this, 0, sizeof(*this));
    }
};

class CFiledataSelect : public CODBC::CCommand<CODBC::CAccessor<CFiledataSelectAccessor>>
{
public:
    // Methods
    bool Iterate(const CODBC::CConnection& pDbConnect, 
                 const int nSocketIndex, CWSocket& pApplicationSocket, 
                 SHA256& pSHA256, _In_ bool bBind = true, 
                 _In_opt_ CODBC::SQL_ATTRIBUTE* pAttributes = nullptr, 
                 _In_ ULONG nAttributes = 0)
    {
        // Validate our parameters
        SQLRETURN nRet{ Open(pDbConnect, GetDefaultCommand(), 
                             bBind, pAttributes, nAttributes) };
        ODBC_CHECK_RETURN_FALSE(nRet, m_Command);

        // Iterate through the returned recordset
        while (true)
        {
            ClearRecord();
            nRet = m_Command.FetchNext();
            if (!SQL_SUCCEEDED(nRet))
                break;
            TRACE(_T("m_nBase64 = %lld\n"), m_nBase64);
            std::string decoded = base64_decode(wstring_to_utf8(m_lpszContent));
            ASSERT(m_nBase64 == decoded.length());
            if (WriteBuffer(nSocketIndex, pApplicationSocket, 
               (unsigned char*)decoded.data(), (int)decoded.length(), false, false))
            {
                pSHA256.update((unsigned char*)decoded.data(), decoded.length());
            }
            else
            {
                return false;
            }
        }

        return true;
    }
};

const int MAX_BUFFER = 0x10000;

bool ConnectToDatabase(CODBC::CEnvironment& pEnvironment, CODBC::CConnection& pConnection)
{
    CODBC::String sConnectionOutString;
    TCHAR sConnectionInString[0x100];

    std::wstring strHostName;
    int nHostPort = 3306;
    std::wstring strDatabase;
    std::wstring strUsername;
    std::wstring strPassword;
    if (!LoadAppSettings(strHostName, nHostPort, strDatabase, strUsername, strPassword))
        return false;
    const std::wstring strHostPort = utf8_to_wstring(std::to_string(nHostPort));

    SQLRETURN nRet = pEnvironment.Create();
    ODBC_CHECK_RETURN_FALSE(nRet, pEnvironment);

    nRet = pEnvironment.SetAttr(SQL_ATTR_ODBC_VERSION, SQL_OV_ODBC3_80);
    ODBC_CHECK_RETURN_FALSE(nRet, pEnvironment);

    nRet = pEnvironment.SetAttrU(SQL_ATTR_CONNECTION_POOLING, SQL_CP_DEFAULT);
    ODBC_CHECK_RETURN_FALSE(nRet, pEnvironment);

    // nRet = pEnvironment.SetAttr(SQL_ATTR_AUTOCOMMIT, SQL_AUTOCOMMIT_OFF);
    // ODBC_CHECK_RETURN_FALSE(nRet, pEnvironment);

    nRet = pConnection.Create(pEnvironment);
    ODBC_CHECK_RETURN_FALSE(nRet, pConnection);

    _stprintf(sConnectionInString, _T("Driver={MySQL ODBC 8.0 Unicode Driver};
              Server=%s;Port=%s;Database=%s;User=%s;Password=%s;"),
        strHostName.c_str(), strHostPort.c_str(), strDatabase.c_str(), 
                             strUsername.c_str(), strPassword.c_str());
    nRet = pConnection.DriverConnect(const_cast<SQLTCHAR*>
           (reinterpret_cast<const SQLTCHAR*>(sConnectionInString)), 
            sConnectionOutString);
    ODBC_CHECK_RETURN_FALSE(nRet, pConnection);

    return true;
}

bool DownloadFile(const int nSocketIndex, CWSocket& pApplicationSocket, 
                  const std::wstring& strFilePath)
{
    CODBC::CEnvironment pEnvironment;
    CODBC::CConnection pConnection;
    SHA256 pSHA256;

    std::array<CODBC::SQL_ATTRIBUTE, 2> attributes
    { {
      { SQL_ATTR_CONCURRENCY,        
        reinterpret_cast<SQLPOINTER>(SQL_CONCUR_ROWVER), SQL_IS_INTEGER },
      { SQL_ATTR_CURSOR_SENSITIVITY, 
        reinterpret_cast<SQLPOINTER>(SQL_INSENSITIVE),   SQL_IS_INTEGER }
    } };

    ULONGLONG nFileLength = 0;
    CFilenameSelect pFilenameSelect;
    CFilesizeSelect pFilesizeSelect;
    CFiledataSelect pFiledataSelect;
    TRACE(_T("[DownloadFile] %s\n"), strFilePath.c_str());
    if (!ConnectToDatabase(pEnvironment, pConnection) ||
        !pFilenameSelect.Execute(pConnection, strFilePath) ||
        !pFilesizeSelect.Iterate(pConnection, nFileLength, 
        true, attributes.data(), static_cast<ULONG>(attributes.size())))
    {
        TRACE("MySQL operation failed!\n");
        return false;
    }

    TRACE(_T("nFileLength = %llu\n"), nFileLength);
    int nLength = sizeof(nFileLength);
    if (WriteBuffer(nSocketIndex, pApplicationSocket, 
                   (unsigned char*)&nFileLength, nLength, false, false))
    {
        if ((nFileLength > 0) &&
            !pFiledataSelect.Iterate(pConnection, nSocketIndex, 
             pApplicationSocket, pSHA256, true, attributes.data(), 
             static_cast<ULONG>(attributes.size())))
        {
            TRACE("MySQL operation failed!\n");
            return false;
        }
    }
    else
    {
        TRACE(_T("Invalid nFileLength!\n"));
        return false;
    }
    const std::string strDigestSHA256 = pSHA256.toString(pSHA256.digest());
    nLength = (int)strDigestSHA256.length() + 1;
    if (WriteBuffer(nSocketIndex, pApplicationSocket, 
       (unsigned char*)strDigestSHA256.c_str(), nLength, false, true))
    {
        TRACE(_T("Download Done!\n"));
    }
    else
    {
        return false;
    }
    pConnection.Disconnect();
    return true;
}

bool UploadFile(const int nSocketIndex, CWSocket& pApplicationSocket, 
                const std::wstring& strFilePath)
{
    CODBC::CEnvironment pEnvironment;
    CODBC::CConnection pConnection;
    SHA256 pSHA256;
    unsigned char pFileBuffer[MAX_BUFFER] = { 0, };

    CGenericStatement pGenericStatement;
    CFilenameInsert pFilenameInsert;
    CFilenameSelect pFilenameSelect;
    CFilenameUpdate pFilenameUpdate;
    CFiledataInsert pFiledataInsert;
    TRACE(_T("[UploadFile] %s\n"), strFilePath.c_str());
    if (!ConnectToDatabase(pEnvironment, pConnection))
    {
        TRACE("MySQL operation failed!\n");
        return false;
    }

    ULONGLONG nFileLength = 0;
    int nLength = (int)(sizeof(nFileLength) + 5);
    ZeroMemory(pFileBuffer, sizeof(pFileBuffer));
    if (ReadBuffer(nSocketIndex, pApplicationSocket, pFileBuffer, nLength, false, false))
    {
        CopyMemory(&nFileLength, &pFileBuffer[3], sizeof(nFileLength));
        TRACE(_T("nFileLength = %llu\n"), nFileLength);
        if (!pFilenameInsert.Execute(pConnection, strFilePath, nFileLength) ||
            !pGenericStatement.Execute(pConnection, _T
            ("SET @last_filename_id = LAST_INSERT_ID()")))
        {
            if (!pFilenameSelect.Execute(pConnection, 
                strFilePath) || // need to UPDATE it not to INSERT it
                !pGenericStatement.Execute(pConnection, 
                _T("DELETE FROM `filedata` WHERE `filename_id` = @last_filename_id")) ||
                !pFilenameUpdate.Execute(pConnection, nFileLength))
            {
                TRACE("MySQL operation failed!\n");
                return false;
            }
        }

        ULONGLONG nFileIndex = 0;
        while (nFileIndex < nFileLength)
        {
            nLength = (int)sizeof(pFileBuffer);
            ZeroMemory(pFileBuffer, sizeof(pFileBuffer));
            if (ReadBuffer(nSocketIndex, pApplicationSocket, 
                           pFileBuffer, nLength, false, false))
            {
                nFileIndex += (nLength - 5);
                pSHA256.update(&pFileBuffer[3], nLength - 5);

                std::string encoded = base64_encode
                (reinterpret_cast<const unsigned char *>(&pFileBuffer[3]), nLength - 5);
                if (!pFiledataInsert.Execute(pConnection, 
                    utf8_to_wstring(encoded), nLength - 5))
                {
                    TRACE("MySQL operation failed!\n");
                    return false;
                }
            }
            else
            {
                return false;
            }
        }
    }
    else
    {
        TRACE(_T("Invalid nFileLength!\n"));
        return false;
    }
    const std::string strDigestSHA256 = pSHA256.toString(pSHA256.digest());
    nLength = (int)strDigestSHA256.length() + 5;
    ZeroMemory(pFileBuffer, sizeof(pFileBuffer));
    if (ReadBuffer(nSocketIndex, pApplicationSocket, pFileBuffer, nLength, false, true))
    {
        const std::string strCommand = (char*)&pFileBuffer[3];
        if (strDigestSHA256.compare(strCommand) != 0)
        {
            TRACE(_T("Invalid SHA256!\n"));
            return false;
        }
    }
    pConnection.Disconnect();
    return true;
}

#define EOT 0x04
bool DeleteFile(const int /*nSocketIndex*/, CWSocket& pApplicationSocket, 
                const std::wstring& strFilePath)
{
    CODBC::CEnvironment pEnvironment;
    CODBC::CConnection pConnection;

    CGenericStatement pGenericStatement;
    CFilenameSelect pFilenameSelect;
    if (!ConnectToDatabase(pEnvironment, pConnection) ||
        !pFilenameSelect.Execute(pConnection, strFilePath) ||
        !pGenericStatement.Execute(pConnection, _T
        ("DELETE FROM `filedata` WHERE `filename_id` = @last_filename_id")) ||
        !pGenericStatement.Execute(pConnection, _T
        ("DELETE FROM `filename` WHERE `filename_id` = @last_filename_id")))
    {
        TRACE("MySQL operation failed!\n");
        return false;
    }

    unsigned char pFileBuffer[MAX_BUFFER] = { 0, };
    int nLength = sizeof(pFileBuffer);
    ZeroMemory(pFileBuffer, sizeof(pFileBuffer));
    if (((nLength = pApplicationSocket.Receive(pFileBuffer, nLength)) > 0) &&
        (EOT == pFileBuffer[nLength - 1]))
    {
        TRACE(_T("EOT Received\n"));
    }
    pConnection.Disconnect();
    return true;
}

Setup of this Application

  • Install MySQL ODBC connector;
  • Choose a MySQL hosting service and create the MySQL database;
  • Configure Server instance (create IntelliDisk.xml configuration file):

    MySQL's Settings

    XML
    <?xml version="1.0" encoding="UTF-16" standalone="no"?>
    <xml>
        <IntelliDisk>
            <ServicePort>8080</ServicePort>
            <HostName>localhost</HostName>
            <HostPort>3306</HostPort>
            <Database>MySQL_database</Database>
            <Username>MySQL_username</Username>
            <Password>MySQL_password</Password>
        </IntelliDisk>
    </xml>
  • Configure Client instance (change Servers's IP & Port):

    IntelliDisk's Settings

    Note: You should check the option "Start IntelliDisk automatically when I sign in to Windows".

Final Words

IntelliDisk application uses many components that have been published on Code Project. Many thanks to:

  • My CMFCListView form view (see source code);
  • René Nyffenegger for his base64 library;
  • Jérémy LAMBERT for his SHA256 class;
  • PJ Naughter for his CHLinkCtrl class;
  • PJ Naughter for his CInstanceChecker class;
  • PJ Naughter for his ODBCWrappers class;
  • PJ Naughter for his CTrayNotifyIcon class;
  • PJ Naughter for his CVersionInfo class;
  • PJ Naughter for his CWSocket class;
  • PJ Naughter for his CXMLAppSettings class;
  • Youry M. Jukov for his CNotifyDirCheck class.

The Good, the Bad and the Ugly

The good thing is that I learned to create/use Windows services. The Bad thing is that this project is just a proof of concept, not ready for commercial deployment. The ugly thing is that I used ODBC connection for MySQL database, and it should be a Microsoft SQL Server implementation instead.

Future plans: To implement secured OpenSSL connections between server and clients

Points of Interest

I couldn't find a secure way to generate a computer's ID, so I concatenated user's name with computer's name:

C++
const std::string GetMachineID()
{
    DWORD nLength = 0x1000;
    TCHAR lpszUserName[0x1000] = { 0, };
    if (GetUserNameEx(NameUserPrincipal, lpszUserName, &nLength))
    {
        lpszUserName[nLength] = 0;
        TRACE(_T("UserName = %s\n"), lpszUserName);
    }
    else
    {
        nLength = 0x1000;
        if (GetUserName(lpszUserName, &nLength) != 0)
        {
            lpszUserName[nLength] = 0;
            TRACE(_T("UserName = %s\n"), lpszUserName);
        }
    }

    nLength = 0x1000;
    TCHAR lpszComputerName[0x1000] = { 0, };
    if (GetComputerNameEx(ComputerNamePhysicalDnsFullyQualified, 
        lpszComputerName, &nLength))
    {
        lpszComputerName[nLength] = 0;
        TRACE(_T("ComputerName = %s\n"), lpszComputerName);
    }
    else
    {
        nLength = 0x1000;
        if (GetComputerName(lpszComputerName, &nLength) != 0)
        {
            lpszComputerName[nLength] = 0;
            TRACE(_T("ComputerName = %s\n"), lpszComputerName);
        }
    }

    std::wstring result(lpszUserName);
    result += _T(":");
    result += lpszComputerName;
    return wstring_to_utf8(result);
}

And this is how I get the special folder path:

C++
const std::wstring GetSpecialFolder()
{
    WCHAR* lpszSpecialFolderPath = nullptr;
    if ((SHGetKnownFolderPath(FOLDERID_Profile, 0, nullptr, 
                              &lpszSpecialFolderPath)) == S_OK)
    {
        std::wstring result(lpszSpecialFolderPath);
        CoTaskMemFree(lpszSpecialFolderPath);
        result += _T("\\IntelliDisk\\");
        return result;
    }
    return _T("");
}

History

  • Version 1.01 (July 14th, 2023): Initial release
  • Version 1.02 (August 20th, 2023):
    • Changed article's download link. Updated the About dialog (email & website).
    • Added social media links: Twitter, LinkedIn, Facebook, and Instagram.
    • Added shortcuts to GitHub repository's Issues, Discussions, and Wiki.
  • Version 1.03 (November 5th, 2023):
    • Updated PJ Naughter's CTrayNotifyIcon library to the latest version available.
      Fixed an issue where the CTrayNotifyIcon::OnTrayNotification callback method would not work correctly if the m_NotifyIconData.uTimeout member variable gets updated during runtime of client applications. This can occur when you call CTrayNotifyIcon::SetBalloonDetails. Thanks to Maisala Tuomo for reporting this bug.
    • Updated PJ Naughter's AppSettings library to the latest version available.
      Optimized construction of various std::vector and std::[w]string instances throughout the codebase.
  • Version 1.04 (December 17th, 2023): Updated PJ Naughter's ODBCWrappers library to the latest version available.
    Updated module to remove usage of _if_exists by now using ODBCVER and _ATL_MODULES preprocessor macro checks along with SFINAE.
  • Version 1.05 (January 1st, 2024):
    • Switched to Visual Studio Enterprise 2022 (some changes were made in the source code).
    • Added setup project for this solution!
  • Version 1.06 (January 13th, 2024):
    • Added LICENSE to installation folder.
    • Updated Jérémy LAMBERT's SHA256 library to the latest version available.
      Make digest() return a std::array - this way, memory management is automatic, and the compiler can more easily detect out-of-bounds reads or writes.
  • Version 1.07 (January 26th, 2024): Added ReleaseNotes.html and SoftwareContentRegister.html to GitHub repo.
  • Version 1.08 (February 21st, 2024): Switched MFC application' theme back to native Windows.
This article was originally posted at https://github.com/mihaimoga/IntelliDisk

License

This article, along with any associated source code and files, is licensed under The GNU General Public License (GPLv3)


Written By
Software Developer NXP Semiconductors
Romania Romania
My professional background includes knowledge of analyst programmer for Microsoft Visual C++, Microsoft Visual C#, Microsoft Visual Basic, Sun Java, assembly for Intel 80x86 microprocessors, assembly for PIC microcontrollers (produced by Microchip Inc.), relational databases (MySQL, Oracle, SQL Server), concurrent version systems, bug tracking systems, web design (HTML5, CSS3, XML, PHP/MySQL, JavaScript).

Comments and Discussions

 
SuggestionGood work Pin
Mircea Neacsu14-Jul-23 14:15
Mircea Neacsu14-Jul-23 14:15 
GeneralRe: Good work Pin
Ștefan-Mihai MOGA14-Jul-23 16:16
professionalȘtefan-Mihai MOGA14-Jul-23 16:16 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.