// SiaDriveDlg.cpp : implementation file // #include "stdafx.h" #include "SiaDriveApp.h" #include "SiaDriveDlg.h" #include "afxdialogex.h" #include #include #include #include "../SiaDrive.Dokan.Api/SiaDokanDrive.h" #ifdef _DEBUG #define new DEBUG_NEW #endif #define WM_TRAYNOTIFY WM_USER + 100 /* * Home Test wept saxophone dialect depth update jaunt loincloth asleep lush gnome laptop upper olive itches essential neither feel fewest siblings brunt tasked upwards coal niece faked dating hedgehog magically abort * Work Test names amaze when afraid inmate hull hexagon etched niece rudely tudor unopened acidic swagger emit shrugged noises tycoon leech tubes shrugged bulb inexact plywood tuesday rims cease excess aces * Work Test2 ornament alkaline gasp pepper upkeep ablaze number sizes toyed sawmill looking bygones dwarf nerves session cake jerseys niche arrow howls omission verification identity waffle pockets giving hiding river acoustic */ static std::set GetAvailableDrives() { static const std::vector alpha = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z' }; std::bitset<26> drives(~GetLogicalDrives() & 0xFFFFFFFF); std::set avail; for (size_t i = 0; i < alpha.size(); i++) { if (drives[i]) { avail.insert(std::string(1, alpha[i])); } } return std::move(avail); } static bool IsRefreshKeyMessage(const MSG *message) { return message && ((message->message == WM_KEYDOWN) || (message->message == WM_KEYUP)) && (message->wParam == VK_F5); } #pragma region CAboutDialog // CAboutDlg dialog used for App About class CAboutDlg : public CDialogEx { public: CAboutDlg(); // Dialog Data #ifdef AFX_DESIGN_TIME enum { IDD = IDD_ABOUTBOX }; #endif protected: virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support // Implementation protected: DECLARE_MESSAGE_MAP() }; CAboutDlg::CAboutDlg() : CDialogEx(IDD_ABOUTBOX) { } void CAboutDlg::DoDataExchange(CDataExchange* pDX) { CDialogEx::DoDataExchange(pDX); } BEGIN_MESSAGE_MAP(CAboutDlg, CDialogEx) END_MESSAGE_MAP() #pragma endregion // CSiaDriveDlg dialog #pragma region CSiaDriveDialog Event Map BEGIN_DHTML_EVENT_MAP(CSiaDriveDlg) DHTML_EVENT_ONCLICK(_T("ID_Renter_Edit"), OnButtonRenterEdit) DHTML_EVENT_ONCLICK(_T("ButtonOK"), OnButtonOK) DHTML_EVENT_ONCLICK(_T("CreateWalletButton"), OnButtonCreateWallet) DHTML_EVENT_ONCLICK(_T("ConfirmSeedButton"), OnButtonConfirmSeed) DHTML_EVENT_ONCLICK(_T("UnlockWalletButton"), OnButtonUnlockWallet) DHTML_EVENT_ONCLICK(_T("ID_MountButton"), OnButtonMount) END_DHTML_EVENT_MAP() #pragma endregion CSiaDriveDlg::CSiaDriveDlg(CWnd* pParent /*=NULL*/) : CDHtmlDialog(IDD_SIADRIVE_DIALOG, IDR_HTML_SIADRIVE_DIALOG, pParent), _uiThreadId(GetCurrentThreadId()) { m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME); } void CSiaDriveDlg::DoDataExchange(CDataExchange* pDX) { CDHtmlDialog::DoDataExchange(pDX); DDX_DHtml_ElementInnerText(pDX, _T("WalletCreatedSeed"), _walletCreatedSeed); } BOOL CSiaDriveDlg::PreTranslateMessage(MSG* pMsg) { //TODO: Implement copy/paste context menu if (IsRefreshKeyMessage(pMsg) || (pMsg->message == WM_RBUTTONDOWN) || (pMsg->message == WM_RBUTTONDBLCLK)) { return TRUE; } return CDHtmlDialog::PreTranslateMessage(pMsg); } BEGIN_MESSAGE_MAP(CSiaDriveDlg, CDHtmlDialog) ON_WM_SYSCOMMAND() ON_WM_TIMER() ON_MESSAGE(WM_TRAYNOTIFY, &CSiaDriveDlg::OnTrayNotification) ON_COMMAND(ID_MNU_ITEM_TOGGLE, &CSiaDriveDlg::OnMnuItemToggle) ON_COMMAND(ID_MNU_ITEM_EXIT, &CSiaDriveDlg::OnMnuItemExit) ON_WM_SIZE() END_MESSAGE_MAP() HRESULT CSiaDriveDlg::OnButtonConfirmSeed(IHTMLElement* /*pElement*/) { _seedCreation = false; _walletCreatedSeed = L""; ReloadDisplay(); return S_OK; } HRESULT CSiaDriveDlg::OnButtonCreateWallet(IHTMLElement* /*pElement*/) { if (!_seedCreation) { _seedCreation = true; KillTimer(IDT_UPDATE); String seed; if (ApiSuccess(_siaApi->GetWallet()->Create(SiaSeedLanguage::English, seed))) { DisplaySeedCreated(seed); } } return S_OK; } HRESULT CSiaDriveDlg::OnButtonMount(IHTMLElement* /*pElement*/) { return S_OK; } HRESULT CSiaDriveDlg::OnButtonOK(IHTMLElement* /*pElement*/) { ShowWindow(SW_HIDE); return S_OK; } HRESULT CSiaDriveDlg::OnButtonRenterEdit(IHTMLElement* /*pElement*/) { DisplayPopUp(L"ID_Edit_Renter_Popup"); return S_FALSE; } HRESULT CSiaDriveDlg::OnButtonUnlockWallet(IHTMLElement* /*pElement*/) { SetBodyEnabled(false); String pwd = GetWalletUnlockPassword(); std::thread th([this, pwd]() { if (ApiSuccess(_siaApi->GetWallet()->Unlock(pwd))) { QueueUiAction([this]() { SetWalletUnlockPassword(L""); SetBodyEnabled(true); ReloadDisplay(); }); } else { QueueUiAction([this]() { SetWalletUnlockPassword(L""); AfxMessageBox(L"Invalid password entered"); SetBodyEnabled(true); SetFocusToElement(L"ID_WalletUnlockPwd"); }); } }); th.detach(); return S_OK; } std::unique_ptr d; void CSiaDriveDlg::OnDocumentComplete(LPDISPATCH, LPCTSTR) { KillTimer(IDT_UPDATE); KillTimer(IDT_UI_ACTION_QUEUE); // Create new API to clear all cached data used by threaded implementations _siaApi.reset(new CSiaApi({ L"localhost", 9980, L"1.1.1" }, &_siaConfig)); ClearDisplay(); CallClientScript(L"setAvailableDrives", json(GetAvailableDrives()), nullptr); if (!d) { d.reset(new Dokan::CSiaDokanDrive(*_siaApi, &_siaConfig)); d->Mount('A', CA2W(_siaConfig.GetCacheFolder().c_str()).m_psz, 10); } SetTimer(IDT_UPDATE, 2000, nullptr); SetTimer(IDT_UI_ACTION_QUEUE, 100, nullptr); if ((_connected = UpdateUi(false))) { ConfigureWallet(); } } BOOL CSiaDriveDlg::OnInitDialog() { CDHtmlDialog::OnInitDialog(); _tray.Create(this, IDR_TRAY_MENU, L"SiaDrive", m_hIcon, WM_TRAYNOTIFY); _tray.SetDefaultMenuItem(ID_MNU_ITEM_TOGGLE, FALSE); // Add "About..." menu item to system menu. // IDM_ABOUTBOX must be in the system command range. ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX); ASSERT(IDM_ABOUTBOX < 0xF000); CMenu* pSysMenu = GetSystemMenu(FALSE); if (pSysMenu != NULL) { BOOL bNameValid; CString strAboutMenu; bNameValid = strAboutMenu.LoadString(IDS_ABOUTBOX); ASSERT(bNameValid); if (!strAboutMenu.IsEmpty()) { pSysMenu->AppendMenu(MF_SEPARATOR); pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu); } } // Set the icon for this dialog. The framework does this automatically // when the application's main window is not a dialog SetIcon(m_hIcon, TRUE); // Set big icon SetIcon(m_hIcon, FALSE); // Set small icon return TRUE; // return TRUE unless you set the focus to a control } void CSiaDriveDlg::OnMnuItemExit() { OnCancel(); } void CSiaDriveDlg::OnMnuItemToggle() { const BOOL visible = IsWindowVisible(); ShowWindow(visible ? SW_HIDE : SW_SHOW); if (!visible && IsIconic()) { ShowWindow(SW_RESTORE); SetForegroundWindow(); } } void CSiaDriveDlg::OnPaint() { if (IsIconic()) { CPaintDC dc(this); // device context for painting SendMessage(WM_ICONERASEBKGND, reinterpret_cast(dc.GetSafeHdc()), 0); // Center icon in client rectangle int cxIcon = GetSystemMetrics(SM_CXICON); int cyIcon = GetSystemMetrics(SM_CYICON); CRect rect; GetClientRect(&rect); int x = (rect.Width() - cxIcon + 1) / 2; int y = (rect.Height() - cyIcon + 1) / 2; // Draw the icon dc.DrawIcon(x, y, m_hIcon); } else { CDHtmlDialog::OnPaint(); } } HCURSOR CSiaDriveDlg::OnQueryDragIcon() { return static_cast(m_hIcon); } void CSiaDriveDlg::OnSize(UINT nType, int cx, int cy) { if (nType == SIZE_MINIMIZED) { ShowWindow(SW_HIDE); } } void CSiaDriveDlg::OnSysCommand(UINT nID, LPARAM lParam) { if ((nID & 0xFFF0) == IDM_ABOUTBOX) { CAboutDlg dlgAbout; dlgAbout.DoModal(); } else { CDHtmlDialog::OnSysCommand(nID, lParam); } } void CSiaDriveDlg::OnTimer(UINT_PTR nIDEvent) { switch (nIDEvent) { case IDT_UPDATE: { UpdateUi(); } break; case IDT_UI_ACTION_QUEUE: { ProcessUiActionQueue(); } break; default: break; } } LRESULT CSiaDriveDlg::OnTrayNotification(WPARAM wParam, LPARAM lParam) { _tray.OnTrayNotification(wParam, lParam); return 0; } BOOL CSiaDriveDlg::CallClientScript(LPCTSTR pStrFuncName, CComVariant* pOutVarRes) { BOOL bRes = FALSE; CComVariant vaResult; CComPtr pIDoc2; if (SUCCEEDED(this->GetDHtmlDocument(&pIDoc2))) //Uses CDHtmlDialog as 'this' { //Getting IDispatch for Java Script objects CComPtr spScript; if (SUCCEEDED(pIDoc2->get_Script(&spScript))) { //Find dispid for given function in the object CComBSTR bstrMember(pStrFuncName); DISPID dispid = NULL; if (SUCCEEDED(spScript->GetIDsOfNames(IID_NULL, &bstrMember, 1, LOCALE_USER_DEFAULT, &dispid))) { EXCEPINFO excepInfo; memset(&excepInfo, 0, sizeof excepInfo); DISPPARAMS dispparams; memset(&dispparams, 0, sizeof dispparams); dispparams.cArgs = 0; dispparams.cNamedArgs = 0; UINT nArgErr = static_cast(-1); // initialize to invalid arg //Call JavaScript function bRes = SUCCEEDED(spScript->Invoke(dispid, IID_NULL, 0, DISPATCH_METHOD, &dispparams, &vaResult, &excepInfo, &nArgErr)); } } } if (pOutVarRes) *pOutVarRes = vaResult; return bRes; } BOOL CSiaDriveDlg::CallClientScript(LPCTSTR pStrFuncName, const String& data, CComVariant* pOutVarRes) { BOOL bRes = FALSE; CComVariant vaResult; CComPtr pIDoc2; if (SUCCEEDED(this->GetDHtmlDocument(&pIDoc2))) //Uses CDHtmlDialog as 'this' { //Getting IDispatch for Java Script objects CComPtr spScript; if (SUCCEEDED(pIDoc2->get_Script(&spScript))) { //Find dispid for given function in the object CComBSTR bstrMember(pStrFuncName); DISPID dispid = NULL; if (SUCCEEDED(spScript->GetIDsOfNames(IID_NULL, &bstrMember, 1, LOCALE_USER_DEFAULT, &dispid))) { VARIANT v[1]; v[0] = CComVariant(data.c_str()); //Putting parameters DISPPARAMS dispparams; memset(&dispparams, 0, sizeof dispparams); dispparams.cArgs = 1; dispparams.rgvarg = v; dispparams.cNamedArgs = 0; EXCEPINFO excepInfo; memset(&excepInfo, 0, sizeof excepInfo); UINT nArgErr = (UINT)-1; // initialize to invalid arg //Call JavaScript function bRes = SUCCEEDED(spScript->Invoke(dispid, IID_NULL, 0, DISPATCH_METHOD, &dispparams, &vaResult, &excepInfo, &nArgErr)); } } } if (pOutVarRes) *pOutVarRes = vaResult; return bRes; } BOOL CSiaDriveDlg::CallClientScript(LPCTSTR pStrFuncName, const json& json, CComVariant* pOutVarRes) { String data = CA2W(json.dump().c_str()).m_psz; return CallClientScript(pStrFuncName, data, pOutVarRes); } void CSiaDriveDlg::ClearDisplay() { SetServerVersion(L"..."); SetClientVersion(L"1.0.0"); SetWalletConfirmedBalance(L"..."); SetWalletUnconfirmedBalance(L"..."); SetWalletTotalBalance(L"..."); SetWalletReceiveAddress(L"..."); SetRenterAllocatedFunds(0); SetRenterAvailableFunds(0); SetRenterUsedFunds(0); SetRenterHosts(0); SetRenterTotalAvailable(0.0); SetRenterTotalUsed(0); SetRenterTotalRemain(0.0); SetRenterTotalUploadProgress(100); SetConsensusHeight(0); } void CSiaDriveDlg::ConfigureWallet() { if (_siaApi->GetWallet()->GetCreated()) { RemoveCreateWalletItems(); if (_siaApi->GetWallet()->GetLocked()) { DisplayUnlockWallet(); } else { SetMainWindow(L"ID_TabWindow"); switch (_siaConfig.GetUI_Main_TabIndex()) { case RENTER_TAB: { DisplayRenterTab(); } break; default: break; } } } else { DisplayCreateWallet(); } } void CSiaDriveDlg::DisplayCreateWallet() { SetMainWindow(L"create_wallet"); } void CSiaDriveDlg::DisplayPopUp(const String& name) { CallClientScript(L"displayPopUp", name, nullptr); } void CSiaDriveDlg::DisplayRenterTab() { SetTabWindow(L"ID_Tab_Renter"); } void CSiaDriveDlg::DisplaySeedCreated(const String& seed) { SetMainWindow(L"disp_wallet_seed"); _walletCreatedSeed = seed.c_str(); UpdateData(FALSE); } void CSiaDriveDlg::DisplayUnlockWallet() { SetMainWindow(L"unlock_wallet"); SetFocusToElement(L"ID_WalletUnlockPwd"); } HRESULT CSiaDriveDlg::GetDomNodeAndElementById(const String& id, CComPtr& node, CComPtr& elem) { HRESULT hr; if (SUCCEEDED((hr = GetElement(id.c_str(), &elem)))) { hr = elem->QueryInterface(IID_IHTMLDOMNode, reinterpret_cast(&node)); } return hr; } HRESULT CSiaDriveDlg::GetDomNodeById(const String& id, CComPtr& node) { CComPtr elem; return GetDomNodeAndElementById(id, node, elem); } String CSiaDriveDlg::GetWalletUnlockPassword() { CComVariant result; CallClientScript(L"getWalletUnlockPassword", &result); return result.bstrVal ? result.bstrVal : L""; } void CSiaDriveDlg::ProcessUiActionQueue() { std::deque> items; { std::lock_guard l(_uiActionQueueMutex); items = _uiActionQueue; _uiActionQueue.clear(); } while (items.size()) { items.front()(); items.pop_front(); } } void CSiaDriveDlg::QueueUiAction(std::function fn) { if (GetCurrentThreadId() == _uiThreadId) { fn(); } else { std::lock_guard l(_uiActionQueueMutex); _uiActionQueue.push_back(fn); } } void CSiaDriveDlg::ReloadDisplay() { KillTimer(IDT_UPDATE); ClearDisplay(); this->Navigate(this->m_strCurrentUrl); } void CSiaDriveDlg::RemoveCreateWalletItems() { RemoveDomNodeById(L"create_wallet"); RemoveDomNodeById(L"disp_wallet_seed"); } void CSiaDriveDlg::RemoveDomNodeById(const String& id) { CComPtr element; if (SUCCEEDED(GetDomNodeById(id.c_str(), element))) { CComPtr parent; if (SUCCEEDED(element->get_parentNode(&parent))) { CComPtr removed; parent->removeChild(element, &removed); } } } void CSiaDriveDlg::SetBodyEnabled(const bool& enabled) { CComPtr disabledNode; CComPtr disabledElem; if (SUCCEEDED(GetDomNodeAndElementById(L"ID_DisabledOverlay", disabledNode, disabledElem))) { CComPtr style; if (SUCCEEDED(disabledElem->get_style(&style))) { style->put_display(enabled ? L"none" : L"block"); } } } void CSiaDriveDlg::SetChildWindow(const String& parentName, const String& name) { CComPtr mainNode; if (SUCCEEDED(GetDomNodeById(parentName, mainNode))) { CComPtr child; if (SUCCEEDED(mainNode->get_firstChild(&child))) { CComPtr removed; if (SUCCEEDED(mainNode->removeChild(child, &removed))) { CComPtr body; if (SUCCEEDED(GetDomNodeById(L"CSiaDriveDlg", body))) { CComPtr element; if (SUCCEEDED(removed->QueryInterface(IID_IHTMLElement, reinterpret_cast(&element)))) { CComPtr style; if (SUCCEEDED(element->get_style(&style))) { style->put_display(L"none"); } CComPtr added; body->appendChild(removed, &added); } } } } if (!name.empty()) { CComPtr divNode; CComPtr divElement; if (SUCCEEDED(GetDomNodeAndElementById(name, divNode, divElement))) { CComPtr parent; if (SUCCEEDED(divNode->get_parentNode(&parent))) { CComPtr removedNode; if (SUCCEEDED(parent->removeChild(divNode, &removedNode))) { CComPtr appendedNode; if (SUCCEEDED(mainNode->appendChild(removedNode, &appendedNode))) { CComPtr style; if (SUCCEEDED(divElement->get_style(&style))) { style->put_display(L"block"); } } } } } } } } void CSiaDriveDlg::SetClientVersion(const String& version) { CallClientScript(L"setClientVersion", version, nullptr); } void CSiaDriveDlg::SetConsensusHeight(const std::uint64_t& height) { CallClientScript(L"setConsensusHeight", height ? std::to_wstring(height) : L"...", nullptr); } void CSiaDriveDlg::SetMainWindow(const String& name) { SetChildWindow(L"main_window", name); } void CSiaDriveDlg::SetRenterAllocatedFunds(const SiaCurrency& currency) { CallClientScript(L"setRenterAllocatedFunds", SiaCurrencyToString(currency), nullptr); } void CSiaDriveDlg::SetRenterAvailableFunds(const SiaCurrency& currency) { CallClientScript(L"setRenterAvailableFunds", SiaCurrencyToString(currency), nullptr); } void CSiaDriveDlg::SetRenterHosts(const std::uint64_t& hosts) { CallClientScript(L"setRenterHosts", std::to_wstring(hosts), nullptr); } void CSiaDriveDlg::SetRenterTotalAvailable(const double& total) { CallClientScript(L"setRenterTotalAvailGb", std::to_wstring(total), nullptr); } void CSiaDriveDlg::SetRenterTotalRemain(const double& total) { CallClientScript(L"setRenterTotalRemainGb", std::to_wstring(total), nullptr); } void CSiaDriveDlg::SetRenterTotalUsed(const std::uint64_t& bytes) { double total = bytes ? bytes / (1024.0 * 1024.0 * 1024.0) : 0.0; CallClientScript(L"setRenterTotalUsedGb", std::to_wstring(total), nullptr); } void CSiaDriveDlg::SetRenterUsedFunds(const SiaCurrency& currency) { CallClientScript(L"setRenterUsedFunds", SiaCurrencyToString(currency), nullptr); } void CSiaDriveDlg::SetRenterTotalUploadProgress(const std::uint32_t& progress) { CallClientScript(L"setRenterTotalUploadProgress", std::to_wstring(progress), nullptr); } void CSiaDriveDlg::SetServerVersion(const String& version) { CallClientScript(L"setServerVersion", version, nullptr); } void CSiaDriveDlg::SetTabWindow(const String& name) { SetChildWindow(L"ID_ActiveTab", name); } void CSiaDriveDlg::SetWalletConfirmedBalance(const String& balance) { CallClientScript(L"setWalletConfirmedBalance", balance, nullptr); } void CSiaDriveDlg::SetWalletReceiveAddress(const String& address) { _receiveAddress = address; CallClientScript(L"setWalletReceiveAddress", address, nullptr); } void CSiaDriveDlg::SetWalletTotalBalance(const String& balance) { CallClientScript(L"setWalletTotalBalance", balance, nullptr); } void CSiaDriveDlg::SetWalletUnconfirmedBalance(const String& balance) { CallClientScript(L"setWalletUnconfirmedBalance", balance, nullptr); } void CSiaDriveDlg::SetWalletUnlockPassword(const String& password) { CallClientScript(L"setWalletUnlockPassword", password, nullptr); } bool CSiaDriveDlg::UpdateSiaInfo() { // TODO Needs to be async const String serverVersion = _siaApi->GetServerVersion(); if (serverVersion.length()) { if (_siaApi->GetWallet()->Refresh()) { SiaCurrency confirmed; if (ApiSuccess(_siaApi->GetWallet()->GetConfirmedBalance(confirmed))) { SiaCurrency unconfirmed; if (ApiSuccess(_siaApi->GetWallet()->GetUnonfirmedBalance(unconfirmed))) { SiaCurrency total = confirmed + unconfirmed; SetServerVersion(serverVersion); SetWalletConfirmedBalance(SiaCurrencyToString(confirmed)); SetWalletUnconfirmedBalance(SiaCurrencyToString(unconfirmed)); SetWalletTotalBalance(SiaCurrencyToString(total)); if (_receiveAddress.empty() || _receiveAddress == L"...") { String receiveAddress; _siaApi->GetWallet()->GetAddress(receiveAddress); SetWalletReceiveAddress(receiveAddress); } SiaCurrency allocatedFunds = _siaApi->GetRenter()->GetFunds(); SiaCurrency unspentFunds = _siaApi->GetRenter()->GetUnspent(); SetRenterAllocatedFunds(allocatedFunds); SetRenterAvailableFunds(unspentFunds); SetRenterUsedFunds(allocatedFunds - unspentFunds); SetRenterHosts(_siaApi->GetRenter()->GetHosts()); SetRenterTotalUsed(_siaApi->GetRenter()->GetTotalUsedBytes()); SiaCurrency t = _siaApi->GetRenter()->GetTotalUsedBytes() ? _siaApi->GetRenter()->GetTotalUsedBytes() / (1024.0 * 1024.0 * 1024.0) : 0.0; auto a = (t / (allocatedFunds - unspentFunds)) * allocatedFunds; SetRenterTotalAvailable(a.ToDouble()); SetRenterTotalRemain((a-t).ToDouble()); SetRenterTotalUploadProgress(_siaApi->GetRenter()->GetTotalUploadProgress()); SetConsensusHeight(_siaApi->GetConsensus()->GetHeight()); return true; } } } } ClearDisplay(); return false; } bool CSiaDriveDlg::UpdateUi(const bool& refresh) { bool ret = UpdateSiaInfo(); if (ret) { if (!_connected && !_seedCreation) { ReloadDisplay(); } } else if (refresh && !_seedCreation) { _connected = false; ReloadDisplay(); } return ret; }