65.9K
CodeProject 正在变化。 阅读更多。
Home

IntelliDisk

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.08/5 (4投票s)

2023 年 7 月 14 日

GPL3

6分钟阅读

viewsIcon

12270

关于使用 CodeProject 上发布的许多组件的 IntelliDisk 应用程序

IntelliDisk

引言

IntelliDisk 应用程序允许用户将文件保存到多个设备,并让它们在这些设备上自动同步。这意味着用户可以在多个位置访问和处理同一个文档。IntelliDisk 应用程序提供了相对容易的云存储空间访问,允许与他人共享内容。IntelliDisk 应用程序是免费的,如同言论自由/免费啤酒一样。它采用三层架构开发,将应用程序分离为三个逻辑和物理计算层。

什么是三层架构?

三层架构是一种成熟的软件应用程序架构,它将应用程序组织成三个逻辑和物理计算层:表示层(或用户界面);应用层(数据在此处理);以及数据层(应用程序相关数据在此存储和管理)。

三层架构的主要优点是,由于每一层都运行在自己的基础设施上,因此每一层都可以由独立的开发团队同时开发,并且可以根据需要进行更新或扩展,而不会影响其他层。

表示层

表示层是应用程序的用户界面和通信层,最终用户在此与应用程序交互。其主要目的是向用户显示信息并从用户那里收集信息。这个顶层可以运行在网络浏览器上,也可以作为桌面应用程序或图形用户界面(GUI)运行。网络表示层通常使用 HTML、CSS 和 JavaScript 开发。桌面应用程序可以使用各种语言编写,具体取决于平台。

应用层

应用层,也称为逻辑层或中间层,是应用程序的核心。在此层中,使用业务逻辑(一套特定的业务规则)处理在表示层收集的信息——有时会对照数据层中的其他信息进行处理。应用层还可以添加、删除或修改数据层中的数据。应用层通常使用 C++、Python、Java、Perl、PHP 或 Ruby 开发,并使用 API 调用与数据层通信。

数据层

数据层,有时也称为数据库层、数据访问层或后端,是应用程序处理的信息存储和管理的地方。这可以是关系型数据库管理系统,如 PostgreSQL、MySQL、MariaDB、Oracle、DB2、Informix 或 Microsoft SQL Server,也可以是 NoSQL 数据库服务器,如 Cassandra、CouchDB 或 MongoDB。在三层应用程序中,所有通信都通过应用层进行。表示层和数据层不能直接相互通信。

层(Tier)与分层(Layer)

在讨论三层架构时,“分层(layer)”经常与“层(tier)”互换使用——并且是错误的——例如“表示分层”或“业务逻辑分层”。它们不尽相同。“分层”指的是软件的功能划分,而“层”指的是运行在与其他划分独立的基础设施上的软件功能划分。例如,手机上的联系人应用是一个三层应用,但却是一个单层应用,因为所有三层都运行在你的手机上。这种区别很重要,因为分层不能提供与层相同的优势。

Three-Tier Architecture

三层架构的优势

同样,三层架构的主要优势在于其功能的逻辑和物理分离。每一层都可以运行在最适合其功能要求的不同操作系统和服务器平台(例如,Web 服务器、应用服务器、数据库服务器)上。并且每一层都运行在至少一台专用服务器硬件或虚拟服务器上,因此可以定制和优化每一层的服务,而不会影响其他层。其他优势(与单层或两层架构相比)包括:

  • 更快的开发:由于每一层都可以由不同的团队同时开发,组织可以更快地将应用程序推向市场,并且程序员可以为每一层使用最新和最好的语言和工具。
  • 改进的可伸缩性:任何层都可以根据需要独立于其他层进行伸缩。
  • 改进的可靠性:一层中的故障不太可能影响其他层的可用性或性能。
  • 改进的安全性:由于表示层和数据层不能直接通信,精心设计的应用层可以作为一种内部防火墙,防止 SQL 注入和其他恶意攻击。

IntelliDisk 的表示层

IntelliDisk 的表示层是作为在 Microsoft Windows 10+ 上运行的桌面 MFC 应用程序实现的,使用 Microsoft Visual C++ 开发。请查看 GitHub 仓库中的客户端文件夹

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 的应用层

IntelliDisk 的应用层是作为在 Microsoft Windows 10+ 上运行的 NT 服务实现的,使用 Microsoft Visual C++ 开发。请查看 GitHub 仓库中的服务器文件夹

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 的数据层

IntelliDisk 的数据层目前使用 MySQL 数据库实现;如果该层明确扩展,也可以使用其他关系型数据库。

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;
}

此应用程序的设置

  • 安装 MySQL ODBC 连接器
  • 选择一个 MySQL 托管服务并创建 MySQL 数据库;
  • 配置服务器实例(创建 IntelliDisk.xml 配置文件)

    MySQL's Settings

    <?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>
  • 配置客户端实例(更改服务器 IP 和端口)

    IntelliDisk's Settings

    注意:您应该勾选“当我登录 Windows 时自动启动 IntelliDisk”选项。

结束语

IntelliDisk 应用程序使用了许多已在 Code Project 上发布的组件。非常感谢:

  • 我的 CMFCListView 表单视图(请参阅源代码);
  • René Nyffenegger 的 base64 库;
  • Jérémy LAMBERT 的 SHA256 类;
  • PJ Naughter 的 CHLinkCtrl 类;
  • PJ Naughter 的 CInstanceChecker 类;
  • PJ Naughter 的 ODBCWrappers 类;
  • PJ Naughter 的 CTrayNotifyIcon 类;
  • PJ Naughter 的 CVersionInfo 类;
  • PJ Naughter 的 CWSocket 类;
  • PJ Naughter 的 CXMLAppSettings 类;
  • Youry M. Jukov 的 CNotifyDirCheck 类。

好的,坏的,丑陋的

好的一点是我学会了创建/使用 Windows 服务。坏的一点是这个项目只是一个概念验证不适合商业部署。丑陋的一点是我使用了 ODBC 连接 MySQL 数据库,而它应该是一个 Microsoft SQL Server 的实现。

未来计划:实现服务器和客户端之间安全的 OpenSSL 连接

关注点

我找不到生成计算机 ID 的安全方法,所以我将用户名与计算机名连接起来

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);
}

我就是这样获取特殊文件夹路径的

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("");
}

历史

  • 版本 1.01(2023 年 7 月 14 日):初始发布
  • 版本 1.02(2023 年 8 月 20 日)
    • 更改了文章的下载链接。更新了“关于”对话框(电子邮件和网站)。
    • 添加了社交媒体链接:Twitter、LinkedIn、Facebook 和 Instagram。
    • 添加了 GitHub 仓库的 Issues、Discussions 和 Wiki 的快捷方式。
  • 版本 1.03(2023 年 11 月 5 日)
    • 将 PJ Naughter 的 CTrayNotifyIcon 库更新到最新版本。
      修复了一个问题,即当客户端应用程序运行时 m_NotifyIconData.uTimeout 成员变量更新时,CTrayNotifyIcon::OnTrayNotification 回调方法无法正常工作。这可能发生在调用 CTrayNotifyIcon::SetBalloonDetails 时。感谢 Maisala Tuomo 报告此错误。
    • 将 PJ Naughter 的 AppSettings 库更新到最新版本。
      优化了整个代码库中各种 std::vectorstd::[w]string 实例的构建。
  • 版本 1.04(2023 年 12 月 17 日):将 PJ Naughter 的 ODBCWrappers 库更新到最新版本。
    更新了模块,通过使用 ODBCVER 和 _ATL_MODULES 预处理器宏检查以及 SFINAE 来移除对 _if_exists 的使用。
  • 版本 1.05(2024 年 1 月 1 日)
    • 切换到 Visual Studio Enterprise 2022(源代码进行了一些更改)。
    • 为该解决方案添加了安装项目!
  • 版本 1.06(2024 年 1 月 13 日)
    • 在安装文件夹中添加了 LICENSE。
    • 将 Jérémy LAMBERT 的 SHA256 库更新到最新版本。
      使 digest() 返回一个 std::array - 这样内存管理是自动的,并且编译器可以更容易地检测越界读写。
  • 版本 1.07(2024 年 1 月 26 日):在 GitHub 仓库中添加了 ReleaseNotes.html 和 SoftwareContentRegister.html。
  • 版本 1.08(2024 年 2 月 21 日):将 MFC 应用程序的主题切换回原生 Windows。
© . All rights reserved.