diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b75d39a --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +/gen/ +/res/scripts +/res/vip-pool-server-list.txt +/res/keys/ +*.user +*.autosave +*~ +*.exe +.vs/ +.vscode/ +.DS_Store +msvc_make.bat diff --git a/README.md b/README.md new file mode 100644 index 0000000..c92897a --- /dev/null +++ b/README.md @@ -0,0 +1,85 @@ +# BTC tools GUI + +## Build + +### Linux + +See details: https://github.com/btccom/btctools-snap + +### Windows + +1. Compile and install [libbtctools](https://github.com/btccom/libbtctools), details:[libbtctools:README.md](https://github.com/btccom/libbtctools/blob/master/README.md#build-on-windows) + +2. Compile and install qt and libcurl (the vcpkg command used was already installed in the first step): + + ```bash + cd vcpkg + + # 32-bit + .\vcpkg install qt5:x86-windows-static curl:x86-windows-static + + # 64-bit + .\vcpkg install qt5:x64-windows-static curl:x64-windows-static + ``` + +3. Install Qt Creator, the installation package can be downloaded here: https://download.qt.io/official_releases/qtcreator/ + + After installation, open Qt Creator > Tools > Options > Kits, click "Add", add the following **qmake.exe** to it, and then click "Apply": + + - `F:\work\vcpkg\installed\x86-windows-static\tools\qt5\bin\qmake.exe` + + Fill in the **Name** input box: `Qt %{Qt:Version} x86` + + - `F:\work\vcpkg\installed\x64-windows-static\tools\qt5\bin\qmake.exe` + + Fill in the **Name** input box: `Qt %{Qt:Version} x64` + + Replace `F:\work\vcpkg` with your `vcpkg` installation path. + + ![qt-versions](doc/img/qt-versions.jpg) + +4. Click the "Kits" tab next to "Qt Versions", and then click "Desktop (x86)" in "Manual", select "Qt version" as the x86 version of qt you just added. Desktop (x86......)", and select "Qt version" as the x86 version of qt you just added. + + Then select "Desktop (x64......)" and choose "Qt version" as the x64 version of qt you just added. + + ![qt-kits](doc/img/qt-kits.jpg) + +5. Clone this project in the parent directory of the "libbtctools" project and create a symbolic link to "libbtctools": + + ```bash + git clone https://github.com/btccom/btctools-gui.git + cd btctools-gui + .\mklink.bat + ``` + +6. Use Qt Creator to open "btctools-gui.pro" in this project, then check the desired "Kits", and then click "Configure Project and click "Configure Project". + + ![qt-configure](doc/img/qt-configure.jpg) + +7. Click "Project" on the left side of Qt Creator, then click "Build" for 32-bit Kit in "Build & Run", then scroll to the "Build Environment" section. "Build Environment" section, click "Details" to expand the environment variable editor, click "Bulk Edit" button on the right side, fill in the following content (Modify `F:\work` to your actual installation path): + + ```bash + BTCTOOLS_INCLUDE_DIR=F:\work\lib32\btctools\include + BTCTOOLS_LIB_DIR=F:\work\lib32\btctools\lib + LUA_INCLUDE_DIR=F:\work\vcpkg\installed\x86-windows-static\include\luajit + LUA_LIB_DIR=F:\work\vcpkg\installed\x86-windows-static\lib + QT_INCLUDE_DIR=F:\work\vcpkg\installed\x86-windows-static\include + QT_LIB_DIR=F:\work\vcpkg\installed\x86-windows-static\lib + ``` + + If it is a 64-bit Kit, fill in the following: + + ```bash + BTCTOOLS_INCLUDE_DIR=F:\work\lib64\btctools\include + BTCTOOLS_LIB_DIR=F:\work\lib64\btctools\lib + LUA_INCLUDE_DIR=F:\work\vcpkg\installed\x64-windows-static\include\luajit + LUA_LIB_DIR=F:\work\vcpkg\installed\x64-windows-static\lib + QT_INCLUDE_DIR=F:\work\vcpkg\installed\x64-windows-static\include + QT_LIB_DIR=F:\work\vcpkg\installed\x64-windows-static\lib + ``` + + ![qt-envs.jpg](doc/img/qt-envs.jpg) + +8. Trying to compile the project for 32-bit and 64-bit Release. Since the libbtctools library only compiles the Release version, it cannot be Debug compiled. + +9. If you are prompted that the header file or library file cannot be found, remember to delete the temporary compilation folder, which is in the same directory as btctools-gui, and the directory name is like "build-btctools-gui-Desktop_x86_windows_msvc2022_pe_32bit-Release". The compiled exe is also located here. diff --git a/btctools-gui.pro b/btctools-gui.pro new file mode 100644 index 0000000..54be701 --- /dev/null +++ b/btctools-gui.pro @@ -0,0 +1,126 @@ +#------------------------------------------------- +# +# Project created by QtCreator 2017-03-20T15:38:12 +# +#------------------------------------------------- + +QT += core gui network widgets sql + +#------------------------------------------------- +# uncoment the follow line if the program cannot be executed in Windows XP. +#------------------------------------------------- + +TARGET = btctools-gui +TEMPLATE = app +RC_FILE = res/appinfo.rc + +SOURCES += src/main.cpp\ + src/minerscanner.cpp \ + src/minertablemodel.cpp \ + src/mainwindow.cpp \ + src/minerconfigurator.cpp \ + src/autoupdater.cpp \ + src/iprangeedit.cpp \ + src/iprangelistitem.cpp \ + src/minerrebooter.cpp \ + src/iprangeedititem.cpp \ + src/iprangewindow.cpp \ + src/passworddelegate.cpp \ + src/utils.cpp \ + src/settingwindow.cpp \ + src/checkmessagebox.cpp \ + src/upgradewindow.cpp \ + src/minerupgrader.cpp + +HEADERS += \ + src/passworddelegate.h \ + src/translation.h\ + src/minerscanner.h \ + src/minertablemodel.h \ + src/mainwindow.h \ + src/minerconfigurator.h \ + src/config.h \ + src/autoupdater.h \ + src/iprangeedit.h \ + src/iprangelistitem.h \ + src/minerrebooter.h \ + src/iprangewindow.h \ + src/iprangeedititem.h \ + src/utils.h \ + src/settingwindow.h \ + src/checkmessagebox.h \ + src/upgradewindow.h \ + src/minerupgrader.h + +FORMS += \ + res/ui/mainwindow.ui \ + res/ui/iprangewindow.ui \ + res/ui/settingwindow.ui \ + res/ui/upgradewindow.ui + +DISTFILES += \ + doc/miner-type-stat-spec.md \ + doc/auto-update-spec.md \ + .gitignore \ + res/locale/zh_CN.ts \ + res/appinfo.rc \ + res/update-data-linuxsnap-amd64.json \ + res/update-data-linuxsnap-arm64.json \ + res/update-data-winexe-i386.json + +RESOURCES += \ + res/resources.qrc + +TRANSLATIONS += \ + res/locale/zh_CN.ts + +win32 { + CONFIG(debug, debug|release) { + LIBS += -lbtctools -llua51 -lcryptopp-static -llibcurl-d -llibssl -llibcrypto \ + -lboost_regex-vc140-mt-gd -lboost_chrono-vc140-mt-gd -lboost_context-vc140-mt-gd \ + -lbz2d -lzlibd -lws2_32 -lwldap32 -lcrypt32 + } else { + LIBS += -lbtctools -llua51 -lcryptopp-static -llibcurl -llibssl -llibcrypto \ + -lboost_regex-vc140-mt -lboost_chrono-vc140-mt -lboost_context-vc140-mt \ + -lbz2 -lzlib -lws2_32 -lwldap32 -lcrypt32 + } +} else { + LIBS += -lbtctools -llua5.1 -lcryptopp -lcurl -lssl -lcrypto \ + -lboost_regex -lboost_system -lboost_chrono -lboost_context +} + +msvc: QMAKE_CXXFLAGS += -source-charset:utf-8 -D_WIN32_WINNT=0x0601 -DWIN32_LEAN_AND_MEAN + +defineTest(envNotEmpty) { + env = $$1 + isEmpty(env) { + return(false) + } else { + return(true) + } +} + +envNotEmpty("$$(QT_INCLUDE_DIR)") { + INCLUDEPATH += "$$(QT_INCLUDE_DIR)" + TR_EXCLUDE += "$$(QT_INCLUDE_DIR)/*" +} + +envNotEmpty("$$(BTCTOOLS_INCLUDE_DIR)") { + INCLUDEPATH += "$$(BTCTOOLS_INCLUDE_DIR)" + TR_EXCLUDE += "$$(BTCTOOLS_INCLUDE_DIR)/*" +} +envNotEmpty("$$(LUA_INCLUDE_DIR)") { + INCLUDEPATH += "$$(LUA_INCLUDE_DIR)" + TR_EXCLUDE += "$$(LUA_INCLUDE_DIR)/*" +} +envNotEmpty("$$(CRYPTOPP_INCLUDE_DIR)") { + INCLUDEPATH += "$$(CRYPTOPP_INCLUDE_DIR)" + TR_EXCLUDE += "$$(CRYPTOPP_INCLUDE_DIR)/*" +} + +envNotEmpty("$$(QT_LIB_DIR)"): LIBS += -L"$$(QT_LIB_DIR)" +envNotEmpty("$$(BTCTOOLS_LIB_DIR)"): LIBS += -L"$$(BTCTOOLS_LIB_DIR)" +envNotEmpty("$$(LUA_LIB_DIR)"): LIBS += -L"$$(LUA_LIB_DIR)" +envNotEmpty("$$(CRYPTOPP_LIB_DIR)"): LIBS += -L"$$(CRYPTOPP_LIB_DIR)" + +envNotEmpty("$$(PLATFORM_NAME)"): QMAKE_CXXFLAGS += -DPLATFORM_NAME="'\"$$(PLATFORM_NAME)\"'" diff --git a/build.bat b/build.bat new file mode 100644 index 0000000..19a498e --- /dev/null +++ b/build.bat @@ -0,0 +1,14 @@ +set BTCTOOLS_INCLUDE_DIR=C:\Users\hu60c\lib32\btctools\include +set BTCTOOLS_LIB_DIR=C:\Users\hu60c\lib32\btctools\lib +set LUA_INCLUDE_DIR=C:\Users\hu60c\vcpkg\installed\x86-windows-static\include\luajit +set LUA_LIB_DIR=C:\Users\hu60c\vcpkg\installed\x86-windows-static\lib +set CRYPTOPP_INCLUDE_DIR=C:\Users\hu60c\vcpkg\installed\x86-windows-static\include +set CRYPTOPP_LIB_DIR=C:\Users\hu60c\vcpkg\installed\x86-windows-static\lib + +md build +cd build + +C:\Users\hu60c\vcpkg\installed\x86-windows-static\tools\qt5\bin\qmake.exe ..\btctools-gui.pro +C:\Users\hu60c\vcpkg\downloads\tools\jom\jom-1.1.3\jom.exe + +cd .. diff --git a/doc/auto-update-spec.md b/doc/auto-update-spec.md new file mode 100644 index 0000000..9958cbb --- /dev/null +++ b/doc/auto-update-spec.md @@ -0,0 +1,26 @@ +# Auto Update Specification (Language: zh_CN) +## 自动更新规范 + +### 客户端的自动更新请求 +1. 请求形式为 HTTP GET。 +2. 支持 HTTPS。 +3. User-Agent 为 "BTC Tools v1.0" 这样的格式 +4. 客户端会附加扩展头信息 X-App-Version-Id 和 X-App-Version-Name 用于服务器端识别其版本。 + +### 服务器的自动更新响应 +1. 响应数据为JSON类型 +2. 格式如下: +```json +{ + "minVersionId": 1, + "versionId": 2, + "versionName": "v1.1", + "desc": "this is a test update\ntest update\ntest update", + "desc_zh_CN": "这是一个测试更新\n测试更新\n测试更新", + "downloadUrl": "https://pool.btc.com/tools" +} +``` +3. `minVersionId`为允许运行的最小版本号,小于该版本号的客户端将会要求强制更新。 +4. `versionId`为当前最新的版本号,`versionName`为对应的版本名。 +5. `desc_zh_CN`为中文更新说明,其他语言的更新说明为`desc_对应的语言`,如`desc_en_US`。 +6. 如果没有对应语言代码的更新说明,客户端使用`desc`做为其更新说明。目前建议`desc`中的内容使用英语。 diff --git a/doc/build-boost-for-xp/README.txt b/doc/build-boost-for-xp/README.txt new file mode 100644 index 0000000..31fc0a9 --- /dev/null +++ b/doc/build-boost-for-xp/README.txt @@ -0,0 +1,28 @@ +https://blogs.msdn.microsoft.com/vcblog/2012/10/08/windows-xp-targeting-with-c-in-visual-studio-2012/ + +Targeting from the Command Line + +Visual Studio 2012 solutions and projects which have been switched to the v110_xp toolset can be built from the command line using MSBuild or DEVENV without additional steps. + +However, if you wish to use CL and Link directly, additional steps are needed. Note that the steps below may be automated by creating a batch script. + + Set the path and environment variables for Visual Studio 2012 command-line builds. + Set the required SDK paths and compiler flags using the following commands: + + set INCLUDE=%ProgramFiles(x86)%\Microsoft SDKs\Windows\7.1A\Include;%INCLUDE% + set PATH=%ProgramFiles(x86)%\Microsoft SDKs\Windows\7.1A\Bin;%PATH% + set LIB=%ProgramFiles(x86)%\Microsoft SDKs\Windows\7.1A\Lib;%LIB% + set CL=/D_USING_V110_SDK71_;%CL% + + When targeting x64, set the lib path as follows: + set LIB=%ProgramFiles(x86)%\Microsoft SDKs\Windows\7.1A\Lib\x64;%LIB% + + Specify the correct subsystem and subsystem version for the linker based on the type of application you are building. Applications targeting the x86 version of Windows XP must specify subsystem version 5.01, and applications targeting x64 must specify version 5.02. + + For x86 console applications: + set LINK=/SUBSYSTEM:CONSOLE,5.01 %LINK% + + For x64 console applications: + set LINK=/SUBSYSTEM:CONSOLE,5.02 %LINK% + + Execute CL and Link as you normally would within the command prompt. diff --git a/doc/build-boost-for-xp/build-boost-for-xp.bat b/doc/build-boost-for-xp/build-boost-for-xp.bat new file mode 100644 index 0000000..95ab786 --- /dev/null +++ b/doc/build-boost-for-xp/build-boost-for-xp.bat @@ -0,0 +1,14 @@ +set baseDIR=%cd% +CALL "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvarsall.bat" amd64_x86 +cd %baseDIR% + +SET PATH=C:\Program Files (x86)\Microsoft SDKs\Windows\v7.1A\Bin;%PATH% +SET INCLUDE=C:\Program Files (x86)\Microsoft SDKs\Windows\v7.1A\Include;%INCLUDE% +SET LIB=C:\Program Files (x86)\Microsoft SDKs\Windows\v7.1A\Lib;%LIB% +SET CL=/D_USING_V110_SDK71_;%CL% + +CALL bootstrap +pause + +b2 --stagedir=./stage.xp --build-type=minimal toolset=msvc-14.1_xp variant=release link=static threading=multi runtime-link=static +pause diff --git a/doc/build-luajit-for-xp/README.txt b/doc/build-luajit-for-xp/README.txt new file mode 100644 index 0000000..31fc0a9 --- /dev/null +++ b/doc/build-luajit-for-xp/README.txt @@ -0,0 +1,28 @@ +https://blogs.msdn.microsoft.com/vcblog/2012/10/08/windows-xp-targeting-with-c-in-visual-studio-2012/ + +Targeting from the Command Line + +Visual Studio 2012 solutions and projects which have been switched to the v110_xp toolset can be built from the command line using MSBuild or DEVENV without additional steps. + +However, if you wish to use CL and Link directly, additional steps are needed. Note that the steps below may be automated by creating a batch script. + + Set the path and environment variables for Visual Studio 2012 command-line builds. + Set the required SDK paths and compiler flags using the following commands: + + set INCLUDE=%ProgramFiles(x86)%\Microsoft SDKs\Windows\7.1A\Include;%INCLUDE% + set PATH=%ProgramFiles(x86)%\Microsoft SDKs\Windows\7.1A\Bin;%PATH% + set LIB=%ProgramFiles(x86)%\Microsoft SDKs\Windows\7.1A\Lib;%LIB% + set CL=/D_USING_V110_SDK71_;%CL% + + When targeting x64, set the lib path as follows: + set LIB=%ProgramFiles(x86)%\Microsoft SDKs\Windows\7.1A\Lib\x64;%LIB% + + Specify the correct subsystem and subsystem version for the linker based on the type of application you are building. Applications targeting the x86 version of Windows XP must specify subsystem version 5.01, and applications targeting x64 must specify version 5.02. + + For x86 console applications: + set LINK=/SUBSYSTEM:CONSOLE,5.01 %LINK% + + For x64 console applications: + set LINK=/SUBSYSTEM:CONSOLE,5.02 %LINK% + + Execute CL and Link as you normally would within the command prompt. diff --git a/doc/build-luajit-for-xp/build-luajit-for-xp.bat b/doc/build-luajit-for-xp/build-luajit-for-xp.bat new file mode 100644 index 0000000..a0c1295 --- /dev/null +++ b/doc/build-luajit-for-xp/build-luajit-for-xp.bat @@ -0,0 +1,11 @@ +set baseDIR=%cd% +CALL "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvarsall.bat" amd64_x86 +cd %baseDIR% + +SET PATH=C:\Program Files (x86)\Microsoft SDKs\Windows\v7.1A\Bin;%PATH% +SET INCLUDE=C:\Program Files (x86)\Microsoft SDKs\Windows\v7.1A\Include;%INCLUDE% +SET LIB=C:\Program Files (x86)\Microsoft SDKs\Windows\v7.1A\Lib;%LIB% +SET CL=/D_USING_V110_SDK71_;%CL% + +CALL msvcbuild.bat static +pause diff --git a/doc/build-openssl-for-xp/build-openssl-for-xp.bat b/doc/build-openssl-for-xp/build-openssl-for-xp.bat new file mode 100644 index 0000000..20b0326 --- /dev/null +++ b/doc/build-openssl-for-xp/build-openssl-for-xp.bat @@ -0,0 +1,20 @@ +set baseDIR=%cd% +CALL "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvarsall.bat" amd64_x86 +cd %baseDIR% + +SET PATH=C:\Program Files (x86)\Microsoft SDKs\Windows\v7.1A\Bin;%PATH% +SET INCLUDE=C:\Program Files (x86)\Microsoft SDKs\Windows\v7.1A\Include;%INCLUDE% +SET LIB=C:\Program Files (x86)\Microsoft SDKs\Windows\v7.1A\Lib;%LIB% +SET CL=/D_USING_V110_SDK71_;%CL% + +perl Configure VC-WIN32 no-asm --prefix=build +pause + +CALL ms\do_ms +pause + +nmake -f ms\nt.mak +pause + +nmake -f ms\nt.mak install +pause diff --git a/doc/build-qt-for-xp/README.txt b/doc/build-qt-for-xp/README.txt new file mode 100644 index 0000000..0d897de --- /dev/null +++ b/doc/build-qt-for-xp/README.txt @@ -0,0 +1,35 @@ +QT5.7.0在win10下使用visual studio 2015编译(目标平台 xp) + +==================================================== +1. 修改%QTDIR%\qtbase\qmake\Makefile.win32,在 CFLAGS_BARE 后加入 -D_USING_V110_SDK71_ +2. 修改%QTDIR%\qtbase\mkspecs\common\msvc-desktop.conf,在DEFINES中加入_USING_V110_SDK71_ + 修改QMAKE_LFLAGS_CONSOLE = /SUBSYSTEM:CONSOLE,5.01 + 修改QMAKE_LFLAGS_WINDOWS = /SUBSYSTEM:WINDOWS,5.01 +3. 修改 ./mkspecs/common/msvc-base.conf + /SUBSYSTEM:CONSOLE@QMAKE_SUBSYSTEM_SUFFIX@ -> /SUBSYSTEM:CONSOLE,5.01 + /SUBSYSTEM:WINDOWS@QMAKE_SUBSYSTEM_SUFFIX@ -> /SUBSYSTEM:WINDOWS,5.01 +4. 修改 ./tools/configure/Makefile.win32 + /SUBSYSTEM:CONSOLE -> /SUBSYSTEM:CONSOLE,5.01 +5. 在“VS2015 x86本机工具命令提示符”中使用 .\build-qt-for-xp.bat 进行配置,然后使用 nmake 进行编译。 + + +怎样静态编译Qt程序 + +==================================================== +1. 打开文件夹下的qmake.conf文件,根据它include的内容再定位到相应的文件。我这里是上级文件夹下的common目录下的msvc-desktop.conf文件。 +2. 打开对应的文件后,找到以下编译标志: + QMAKE_CFLAGS_RELEASE = -O2 -MD + QMAKE_CFLAGS_RELEASE_WITH_DEBUGINFO += -O2 -MD -Zi + QMAKE_CFLAGS_DEBUG = -Zi -MDd + 将其中的MD全部修改为MT(见粗体字,也就是将动态编译修改为静态编译): + QMAKE_CFLAGS_RELEASE = -O2 -MT + QMAKE_CFLAGS_RELEASE_WITH_DEBUGINFO += -O2 -MT -Zi + QMAKE_CFLAGS_DEBUG = -Zi -MTd +3. 在“VS2015 x86本机工具命令提示符”中使用“.\configure ……”进行配置,然后使用 nmake 进行编译。 + + +使用Qt5.7.0 VS2015版本生成兼容XP的可执行程序 + +==================================================== +实际只需要在pro文件中加入这行: + QMAKE_LFLAGS_WINDOWS= /SUBSYSTEM:WINDOWS,5.01 \ No newline at end of file diff --git a/doc/build-qt-for-xp/build-qt-for-xp.bat b/doc/build-qt-for-xp/build-qt-for-xp.bat new file mode 100644 index 0000000..5015c98 --- /dev/null +++ b/doc/build-qt-for-xp/build-qt-for-xp.bat @@ -0,0 +1,17 @@ +set baseDIR=%cd% +CALL "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvarsall.bat" amd64_x86 +cd %baseDIR% + +SET PATH=C:\Program Files (x86)\Microsoft SDKs\Windows\v7.1A\Bin;%PATH% +SET INCLUDE=C:\Program Files (x86)\Microsoft SDKs\Windows\v7.1A\Include;%INCLUDE% +SET LIB=C:\Program Files (x86)\Microsoft SDKs\Windows\v7.1A\Lib;%LIB% +SET CL=/D_USING_V110_SDK71_;%CL% + +CALL .\configure.bat -mp -confirm-license -opensource -platform win32-msvc2015 -release -static -prefix C:\Qt\5.7.1-static-vs2017-xp -qt-zlib -qt-libpng -qt-libjpeg -opengl desktop -qt-freetype -no-qml-debug -no-angle -nomake tests -nomake examples -no-directwrite -ssl -openssl-linked OPENSSL_LIBS="-lGdi32 -lssleay32 -llibeay32" +pause + +jom -j8 +pause + +jom install +pause diff --git a/doc/img/qt-configure.jpg b/doc/img/qt-configure.jpg new file mode 100644 index 0000000..88bb149 Binary files /dev/null and b/doc/img/qt-configure.jpg differ diff --git a/doc/img/qt-envs.jpg b/doc/img/qt-envs.jpg new file mode 100644 index 0000000..add8e53 Binary files /dev/null and b/doc/img/qt-envs.jpg differ diff --git a/doc/img/qt-kits.jpg b/doc/img/qt-kits.jpg new file mode 100644 index 0000000..ea98bf3 Binary files /dev/null and b/doc/img/qt-kits.jpg differ diff --git a/doc/img/qt-versions.jpg b/doc/img/qt-versions.jpg new file mode 100644 index 0000000..61007a7 Binary files /dev/null and b/doc/img/qt-versions.jpg differ diff --git a/doc/miner-type-stat-spec.md b/doc/miner-type-stat-spec.md new file mode 100644 index 0000000..83db7b0 --- /dev/null +++ b/doc/miner-type-stat-spec.md @@ -0,0 +1,18 @@ +# Miner Type/Stat Specification (Language: zh_CN) +## 矿机类型/状态规范 + +### 扫描结果规范 +扫描器(btctools::miner::MinerScanner)返回的 Miner 结构体应该满足以下条件: +1. 若扫描过程没有发生错误,Miner.stat_ 应为 "success" +2. 若扫描过程中发生错误,Miner.stat_ 中应包含简单的错误信息 +3. 若扫描过程中发生错误,Miner.typeStr_ 应为 "unknown" +4. 若扫描过程中没有发生错误,但是未识别出矿机类型,Miner.typeStr_ 应为 "unknown" +5. 若识别出矿机类型,Miner.typeStr_ 应为小写的矿机类型名称(配置器要能正确识别该名称) + +### 配置结果规范 +配置器(btctools::miner::MinerConfigurator)返回的 Miner 结构体应该满足以下条件: +1. 不改变 Miner.ip_ 、 Miner.typeStr_ 、 Miner.fullTypeStr_ +2. 若配置过程发生错误,Miner.stat_ 中应包含简单的错误信息 +3. 若配置过程没有发生错误,并且矿机明确响应“配置成功”,则 Miner.stat_ 应为 "ok" +4. 若配置过程没有发生错误,但是矿机没有明确响应“配置成功”,则 Miner.stat_ 不能为 "ok",而是设置为相关的错误信息 +5. 不应将 Miner.stat_ 设置为 "success",成功时应设置为 "ok" diff --git a/doc/user-manual/zh-cn.md b/doc/user-manual/zh-cn.md new file mode 100644 index 0000000..a6fa24e --- /dev/null +++ b/doc/user-manual/zh-cn.md @@ -0,0 +1,39 @@ +# BTC Tools 使用说明 + +## 扫描矿机 +1. 下载该软件并打开,软件会自动导入您电脑所在局域网的IP范围。您也可以点击左上角的“+”号添加新的IP范围。只有IP地址在该范围内的矿机才能被扫描到。 +2. 点击“扫描矿机”,软件会进行扫描并把找到的矿机列在表格中。默认情况下,表格中只显示成功找到的矿机。如果您想看到每个IP地址的扫描结果,可以取消勾选软件左上部的“只显示成功矿机”。 +3. 矿机列表显示了矿机的类型、算力、温度、矿池等信息。目前,该软件支持绝大部分蚂蚁矿机(不支持S1、S3)和部分阿瓦隆矿机(如A7、A6等系列)。 + +## 配置矿机 +1. 输入矿池1、矿池2、矿池3的地址和端口(如cn.ss.btc.com:1800),可不输入“stratum+tcp://”。 +2. 然后输入矿池对应的子账户名(不需要在结尾加点“.”)。密码框可以留空。 +3. 如果不想修改某个矿池,可以取消它前面的勾。这样在配置矿机时该矿池会保持不变。 +4. 矿机名后缀默认保持不变(如原来的矿机名为aaa.001,您填写的子账户名为bbb,则修改后的矿机名为bbb.001)。您也可以选择使用“IP”做为后缀,如矿机的IP为“192.168.1.20”,子账户名为“bbb”,则修改后的矿机名为“bbb.1x20”。如果选择“清空”,则修改后的矿机名不包含后缀。 +5. 填好矿池信息后,先扫描矿池,确认您扫描到的就是您想要配置的矿机。随后点击配置矿池,等待配置完成即可。 + +## 常见问题 + +### IP范围该怎么输入 + +如果您的矿机IP范围是 `192.168.1.10` 到 `192.168.1.200`,您可以在IP范围框中输入 `192.168.1.10 ~ 200` (只需在五个框中输入对应的数字即可)。 + +目前每个IP范围框仅能指定一个C类网段(256个IP),如需要同时扫描多个网段,可以添加多个IP范围。 + +如果您只想扫描单个IP,可以只输入前四个框,如 `192.168.1.100 ~ 空`。 + +输入框前的复选框用来在扫描时启用或禁用该IP范围。取消勾选可在扫描时忽略该IP范围。最左上角的复选框可以用来全选/全否。 + +### 修改矿池时提示“抱歉,矿池 xxx 当前不被支持” + +目前,矿池1和矿池2只能填写为BTC.com矿池或BTC智能代理的地址。矿池3可以填写为非BTC.com矿池的地址。 + +### 点击“停止扫描”或“停止配置”之后,扫描/配置却没有马上停下来 + +为了保证矿机设置的完整性,在点击“停止配置”之后,需要等待正在配置的矿机完成配置才能成功停止。扫描矿机与此类似,需要等待正在扫描的矿机完成扫描才能停止。目前,扫描和配置均为100台矿机同时进行。 + +### 我的系统是 Windows XP,我在扫描矿机时卡在了 100% + +由于 Windows XP 系统中存在某个不确定的问题,导致扫描无法正常结束,我们还没有找到解决方法。 + +目前微软已经彻底停止了对 Windows XP 的支持服务,建议将您的电脑升级到 Windows 7 或后续版本以便顺利运行BTC批量工具。 diff --git a/lupdate.sh b/lupdate.sh new file mode 100755 index 0000000..2ae4b58 --- /dev/null +++ b/lupdate.sh @@ -0,0 +1,3 @@ +#/bin/sh +cd "$(dirname "$0")" +lupdate $(find . -name '*.h' -or -name '*.cpp' -or -name '*.ui') -ts ./res/locale/zh_CN.ts diff --git a/mklink.bat b/mklink.bat new file mode 100644 index 0000000..b1e0d3f --- /dev/null +++ b/mklink.bat @@ -0,0 +1,2 @@ +mklink /D res\scripts ..\..\libbtctools\src\lua\scripts +pause \ No newline at end of file diff --git a/mklink.sh b/mklink.sh new file mode 100755 index 0000000..a212d19 --- /dev/null +++ b/mklink.sh @@ -0,0 +1,2 @@ +#!/bin/sh +ln -s ../../libbtctools/src/lua/scripts res/scripts diff --git a/res/appinfo.rc b/res/appinfo.rc new file mode 100644 index 0000000..43e9f31 --- /dev/null +++ b/res/appinfo.rc @@ -0,0 +1,36 @@ +IDI_ICON1 ICON DISCARDABLE "../res/img/btctools-logo.ico" + +#include +#include "../src/config.h" + +/******************************************** +* about the .rc file: +* https://stackoverflow.com/questions/2784697/setting-application-info-in-a-qt-executable-file-on-windows +*********************************************/ + +VS_VERSION_INFO VERSIONINFO +FILEVERSION APP_VERSION_NUMS +PRODUCTVERSION APP_VERSION_NUMS +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904E4" + BEGIN + VALUE "CompanyName", APP_COMPANY_NAME + VALUE "FileDescription", APP_FULL_NAME + VALUE "FileVersion", APP_VERSION_NAME + VALUE "InternalName", APP_FULL_NAME + VALUE "LegalCopyright", APP_COPYRIGHT_LINE + VALUE "LegalTrademarks1", APP_LEGAL_INFO_LINE + VALUE "LegalTrademarks2", APP_LEGAL_INFO_LINE + VALUE "OriginalFilename", APP_FILE_NAME + VALUE "ProductName", APP_NAME + VALUE "ProductVersion", APP_VERSION_NAME + END + END + + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END diff --git a/res/img/btctools-logo.ico b/res/img/btctools-logo.ico new file mode 100644 index 0000000..e164685 Binary files /dev/null and b/res/img/btctools-logo.ico differ diff --git a/res/img/btctools-logo.svg b/res/img/btctools-logo.svg new file mode 100644 index 0000000..539bdcb --- /dev/null +++ b/res/img/btctools-logo.svg @@ -0,0 +1,49 @@ + + + + logo_BTCTool + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/locale/qt_zh_CN.qm b/res/locale/qt_zh_CN.qm new file mode 100644 index 0000000..d6f3648 Binary files /dev/null and b/res/locale/qt_zh_CN.qm differ diff --git a/res/locale/zh_CN.qm b/res/locale/zh_CN.qm new file mode 100644 index 0000000..bbb0b55 Binary files /dev/null and b/res/locale/zh_CN.qm differ diff --git a/res/locale/zh_CN.ts b/res/locale/zh_CN.ts new file mode 100644 index 0000000..a9734f2 --- /dev/null +++ b/res/locale/zh_CN.ts @@ -0,0 +1,2258 @@ + + + + + AutoUpdater + + + Parse update info failed: %1 + 解析更新信息失败:%1 + + + + Parse update info failed: must be object + 解析更新信息失败:必须为JSON对象 + + + + Auto Updater: the current version is the latest. + 自动更新:当前版本已为最新。 + + + + Version from server: %1 <%2> + 服务器上的版本: %1 <%2> + + + + CheckMessageBox + + + OK + 确定 + + + + Cancel + 取消 + + + + IpRangeListItem + + Empty + + + + + Empty + There is no IP range of the item. + + + + + IpRangeWindow + + + IP Range Editor + IP范围编辑器 + + + + Check All + 全选 + + + + + + + + + + + - + - + + + + Comment (Optional): + 备注(可选): + + + IP Range is Empty + IP范围为空 + + + Please add at least one IP range. + 请添加至少一个IP范围。 + + + + IP Range is not Complete + IP范围不完整 + + + + Please fill all parts of the IP range. + 请补全IP范围的所有部分。 + + + + MainWindow + + + BTC Tools + + + + IP Range + IP范围 + + + Scan Miners + 扫描矿机 + + + Modify Pools + 修改配置 + + + + Only Success Miners + 只显示成功矿机 + + + Config Miners + 配置矿机 + + + Reboot a Miner + 重启选中矿机 + + + + IP Ranges: + IP范围: + + + + Double click an item to edit. + 双击条目进行编辑 + + + Monitor Miners + 监控矿机 + + + Config All Miners + 配置所有矿机 + + + Config Selected Miners + 配置选中矿机 + + + Reboot All Miners + 重启所有矿机 + + + Reboot Selected Miners + 重启选中矿机 + + + + + Scan + 扫描 + + + + + Monitor + 监控 + + + + + Config All + 配置全部 + + + + + Config Selected + 配置选中 + + + + + Reboot All + 重启全部 + + + + + Reboot Selected + 重启选中 + + + + + Firmware Upgrade + 固件升级 + + + + Export + 导出 + + + + Settings + 设置 + + + + Pool 1: + 矿池1: + + + + us.ss.btc.com:1800 + cn.ss.btc.com:1800 + + + + + + Worker prefix + 矿机名前缀 + + + + + + SubAccount: + 子账户名: + + + + + + 123 + + + + + + + Worker Suffix: + 矿机名后缀: + + + + + + No Change + 不改变 + + + + + + Empty + 清空 + + + + us.ss.btc.com:443 + cn.ss.btc.com:443 + + + + us.ss.btc.com:25 + cn.ss.btc.com:25 + + + + When scanning, it will only list the miners whose status is "success". +Uncheck this to see the complete scan result. + 扫描时将仅显示状态为“成功”的矿机。 +取消勾选此项以查看完整扫描结果。 + + + + Clear the saved overclocking records and start a new try of overclocking. +Only for the miners that have the "Force Tuning" checkbox. + 清除已保存的超频结果并重新尝试超频。 +仅适用于设置界面带有“Force Tuning”复选框的矿机。 + + + Clear the saved overclocking records and start a new try of overclocking. + 清除已保存的超频结果并重新尝试超频。 + + + Clear the saved overclocking parameters and start a new try of overclocking. + 清除已保存的超频参数并重新尝试进行超频。 + + + + Re-overclocking + 重新超频 + + + + Only for some AntMiner firmwares that has "Low Power Mode" and "Low Power Enhanced Mode" checkboxes on its configuration page. +Known supported firmwares: AntMiner S9 firmware with above checkboxes. +The Overclock/Underclock form on the right provides frequency settings for more AntMiner models. + 仅适用于配置页面具有“Low Power Mode”和“Low Power Enhanced Mode”复选框的蚂蚁矿机固件。 +已知受支持的固件:具有上述复选框的蚂蚁矿机S9固件。 +右侧的“超频/降频”选单支持更多型号的蚂蚁矿机频率设置。 + + + + Only for some AntMiner firmwares that has "Mode" or "Work Mode" drop-down box / radio buttons on its configuration page. +Known supported firmwares: +* AntMiner L3/L3+/L3++/L5 firmware with "Mode" drop-down box. +* AntMiner S17/S17 Pro firmware with "Work Mode" radio buttons. +* AntMiner S9 firmware with "Working Mode" drop-down box. + 仅适用于配置页面具有“Mode”或“Work Mode”下拉框/单选框的蚂蚁矿机固件。 +已知受支持的固件: +* 具有“Mode”下拉框的蚂蚁矿机L3/L3+/L3++/L5 +* 具有“Work Mode”单选框组的蚂蚁矿机S17/S17 Pro +* 具有“Working Mode”下拉框的蚂蚁矿机S9 + + + + Only the model you selected will be changed and other models will keep their original settings. +The model list will be empty until you done a "Scan". + 只会改变您所选型号的设置,其他型号将保持原设置不变。 +型号列表在扫描完矿机后才会出现。 + + + + Work Mode + 工作模式 + + + + If one of your miner doesn't support this option, its option will not be changed. +The drop down box will be empty until a supported miner is scanned. +If you don't see the options you want, change the working mode first. + 如果您的某台矿机不支持您选择的模式,则它的超频设置不会改变。 +只有在扫描到受支持的矿机之后,下拉框里才会出现选项。 +如果您看不到想要的选项,请先尝试切换工作模式。 + + + <html><head/><body><p>Only for some AntMiner firmwares that has &quot;Mode&quot; or &quot;Work Mode&quot; drop-down box / radio buttons on its configuration page.</p><p>Known supported firmwares:</p><p>* AntMiner L3/L3+/L3++/L5 firmware with &quot;Mode&quot; drop-down box.</p><p>* AntMiner S17/S17 Pro firmware with &quot;Work Mode&quot; radio buttons.</p><p>* AntMiner S9 firmware with &quot;Working Mode&quot; drop-down box.</p></body></html> + 仅适用于配置页面具有“Mode”或“Work Mode”下拉框/单选框的蚂蚁矿机固件。 +已知受支持的固件: +* 具有“Mode”下拉框的蚂蚁矿机L3/L3+/L3++/L5 +* 具有“Work Mode”单选框组的蚂蚁矿机S17/S17 Pro +* 具有“Working Mode”下拉框的蚂蚁矿机S9 + + + Only for some AntMiner firmwares that has "Mode" or "Work Mode" drop-down box / radio buttons on its configuration page. +Known supported firmwares: +* AntMiner L5 firmware with "Mode" drop-down box. +* AntMiner S17/S17 Pro firmware with "Work Mode" radio buttons. +* AntMiner S9 firmware with "Working Mode" drop-down box. + 仅适用于配置页面具有“Mode”或“Work Mode”下拉框/单选框的蚂蚁矿机固件。 +已知受支持的固件: +* 具有“Mode”下拉框的蚂蚁矿机L5 +* 具有“Work Mode”单选框组的蚂蚁矿机S17/S17 Pro +* 具有“Working Mode”下拉框的蚂蚁矿机S9 + + + Only the model you selected will be changed and other models will keep the original frequency settings. +The model list will be empty until you done a "Scan". + 只有您所选型号的频率设置会被改变,其他型号将保持原设置不变。 +型号列表在矿机扫描完成后才会出现。 + + + + If one of your miner doesn't support this mode, its working mode will not be changed. +The drop down box will be empty until a supported miner is scanned. + 如果您的某台矿机不支持您选择的模式,则它的超频设置不会改变。 +只有在扫描到受支持的矿机之后,下拉框里才会出现选项。 + + + Only for some AntMiners. These features require firmware support. + 这些功能仅在部分蚂蚁矿机中可用,需要固件的支持。 + + + + Low Power Mode. Turn on ASICBoost to save energy and keep the same hashrate. + 开启ASICBoost以在保持算力不变的情况下减少功耗。 + + + + LPM + 低功耗 + + + + Low Power Enhanced Mode. Reduce the frequency to save more energy (hashrate will decrease). + 通过降频减少更多功耗(算力会降低)。 + + + + Enhanced LPM + 低功耗增强 + + + Only for some AntMiners. This feature requires firmware support. + 该功能仅在部分蚂蚁矿机中可用,需要固件的支持。 + + + + Overclock/Underclock: + 超频/降频: + + + Overclock: + 超频: + + + + Model + 型号 + + + Only the model you selected will be changed and other models will not be changed. + 只有您选择的型号的超频设置会被改变,您未选择的型号不会被改变。 + + + + Option + 选项 + + + If one of your miner doesn't support this mode, its working mode will not be changed. + 如果您的某台矿机不支持您选择的模式,则它的超频设置不会改变。 + + + These features require firmware support. + 这些功能需要固件的支持。 + + + AntMiner Power Control: + 蚂蚁矿机功耗控制: + + + + Power Control: + 功耗控制: + + + Turn on ASICBoost to save energy and keep the same hashrate. + 开启ASICBoost以在保持算力不变的情况下减少功耗。 + + + Reduce the frequency to save more energy (hashrate will decrease). + 通过降频减少更多功耗(算力会降低)。 + + + + AntMiner S9 doesn't support this. Keep it for future use. + 蚂蚁矿机S9不支持此功能。保留该选项以供未来使用。 + + + + Economic Mode (keep for future use) + 经济模式(保留供未来使用) + + + ASIC Boost + ASIC Boost + + + Low Power Mode + 低功耗模式 + + + Reduce the frequency to save more energy. + 通过降频减少更多功耗。 + + + Low Power Enhanced Mode + 低功耗增强模式 + + + AntMiner S9 doesn't support this. Keep it for future. + 蚂蚁矿机S9不支持此功能。保留该功能以供未来使用。 + + + Economic Mode + 经济模式 + + + + Double click: open a miner's control panel in the browser. +Right click: copy the text to the clipboard. + 双击:在浏览器中打开矿机后台。 +右击:复制内容到剪切板。 + + + Double click: open a miner's control panel in the browser. +Right click: copy the text to clipboard. + 双击:在浏览器中打开矿机后台。 +右击:复制内容到剪切板。 + + + Double click a miner to open its control panel in the browser. + 双击矿机可在浏览器中打开矿机后台 + + + Worker: + 矿机名: + + + IP Ranges + IP范围 + + + + + + + + + + - + + + + + Auto Import + 自动导入 + + + Modify Config + 修改配置 + + + + + + PWD: + 密码: + + + + + + IP + IP + + + None + + + + + Pool 2: + 矿池2: + + + + Pool 3: + 矿池3: + + + Cannot modify pools + 无法修改矿池 + + + All pools disabled. +Please enable at least one pool +by checking the box in front of it. + 所有矿池都被禁用。请勾选至少一个矿池。 + + + + Pool url is empty + 矿池地址为空 + + + + Your pool %1's url is empty, are you sure? + 矿池%1地址为空,您确定要修改? + + + Pool worker is empty + 矿机名为空 + + + Your pool %1's worker is empty, are you sure? + 矿池%1的矿机名为空,您确定要修改? + + + Don't support the pool. + 不支持当前矿池 + + + Sorry, the pool %1 is not supported at current. + 抱歉,矿池 %1 当前不被支持。 + + + You will modify pools + 即将修改矿池信息 + + + Do you want to modify pools for all miners now? + 是否立即修改所有矿机的矿池信息? + + + You will modify miners' configure + 即将修改矿机配置 + + + Do you want to modify configure for all miners now? + 是否立即修改所有矿机的配置? + + + Cannot modify config + 无法修改配置 + + + + SubAccount is empty + 子账户名为空 + + + + Your pool %1's sub-account is empty, are you sure? + 矿池%1的子账户名为空,您确定要修改? + + + + Stop Monitor + 停止监控 + + + + Stop Upgrade + 停止升级 + + + + %p% - Refresh after %1 seconds. Miner status count: + %p% - %1秒后刷新矿机信息。矿机状态统计: + + + + all %1 + 共%1台 + + + + success %1 + 成功%1台 + + + + ok %1 + 完成%1台 + + + + skip %1 + 跳过%1台 + + + + timeout %1 + 超时%1台 + + + + upgraded %1 + 已升级 %1 + + + + other %1 + 其他%1台 + + + + All configurations disabled. +Please enable at least one configuration +by checking the box in front of it. + 所有配置项都被禁用。请勾选至少一个配置项(矿池或功耗控制)。 + + + + %p% - Upgrading complete + %p% - 升级完成 + + + + No Miner to Upgrade + 没有可升级的矿机 + + + + No miner to upgrade, or all miners you selected be upgraded successfully at the last time. +Please select another miners. + 没有可升级的矿机,或者您选择的所有矿机都在上次升级好了。 +请选择其他矿机。 + + + + Upgrading complete, %1. + 升级完成,%1。 + + + + Please select one or more miners which you want to upgrade. + 请选择至少一台您想要升级的矿机。 + + + + No miner with matching model be selected + 矿机型号不匹配 + + + + You selected miners are not matching the model (%1) you want to upgrade. + 您选择的矿机与升级时选择的型号(%1)不匹配。 + + + + + Unsupported model + 暂不支持该型号升级 + + + + You will upgrade %1 miners + 即将升级%1台矿机 + + + Do you want to upgrade %1 %2 that your selected now? +Skip %3 miners that don't match the model. + 是否立即重启%1台选中的%2? +跳过%3台型号不匹配的矿机。 + + + + + No miner to upgrade + 没有可升级的矿机 + + + + No miner with matching model to upgrade + 矿机型号不匹配 + + + + You selected model (%1) to upgrade is not matching your miners. + 您要升级的型号(%1)与您的矿机不匹配。 + + + + You will upgrade ALL (%1) miners + 即将升级所有(%1台)矿机 + + + Do you want to upgrade <b>ALL</b> %1 %2 now?<br>Skip %3 miners that don't match the model. + 是否立即升级<b>所有</b>(%1台)%2?<br>跳过%3台型号不匹配的矿机。 + + + + I really want to upgrade ALL (%1) %2. + 我确定要升级所有(%1台)%2 + + + + %p% - Upgrading %1 + %p% - 正在升级 %1 + + + + Scan Failed + 扫描失败 + + + + Monitor Failed + 监控失败 + + + + Configure Failed + 配置失败 + + + + Reboot Failed + 重启失败 + + + + Upgrade Failed + 升级失败 + + + + Unexpected Error + 未知错误 + + + + Fill Pools in Above Form + 填充矿池信息到上方表单 + + + %p% - Continually refreshing miner's info: %1 + %p% - 正在持续刷新矿机信息:%1 + + + + Do you want to change pools, workers and passwords for %1 miners that your selected now? + 是否立即修改%1台选中矿机的矿池、矿机名和密码? + + + + You will configure ALL (%1) miners + 即将配置所有(%1台)矿机 + + + + Do you want to change pools, workers and passwords for <b>ALL</b> (%1) miners now? + 是否立即修改<b>所有</b>(%1台)矿机的矿池、矿机名和密码? + + + + I really want to configure ALL (%1) miners. + 我确定要配置所有(%1台)矿机 + + + + Do you want to reboot %1 miners that your selected now? + 是否立即重启%1台选中的矿机? + + + + You will reboot ALL (%1) miners + 即将重启所有(%1台)矿机 + + + + Do you want to reboot <b>ALL</b> %1 miners now? + 是否立即重启<b>所有</b>(%1台)矿机? + + + + I really want to reboot ALL (%1) miners. + 我确定要重启所有(%1台)矿机 + + + + Export to CSV + 导出为CSV表格 + + + + CSV Table File (*.csv) + CSV表格文件 (*.csv) + + + + Export to CSV failed + 导出为CSV失败 + + + + Open file failed: %1 + 打开文件失败:%1 + + + Open file failed: + 打开文件失败: + + + + Export to CSV succeeded + 导出为CSV成功 + + + + File has saved as %1 + 文件已保存为 %1 + + + + rebooted %1 + 已重启%1台 + + + %p% - Continually refreshing miner's info: %1. Miner status count: + %p% - 正在持续刷新矿机信息:%1。矿机状态统计: + + + + %p% - Monitor stopped. Miner status count: + %p% - 监控已停止。矿机状态统计: + + + + Monitor stopped. Miner status count: %1. + 监控已停止。矿机状态统计:%1。 + + + + Do you want to upgrade %1 %2 that your selected now?%3 + 是否立即升级%1台选中的%2?%3 + + + +Skip %1 miners that don't match the model. + \n跳过%3台型号不匹配的矿机。 + + + + Do you want to upgrade <b>ALL</b> %1 %2 now?%3 + 是否立即升级<b>所有</b>(%1台)%2?%3 + + + + + <br>Skip %3 miners that don't match the model. + <br>跳过%3台型号不匹配的矿机。 + + + + Edit + 编辑 + + + + %p% - Refreshing miner's info: %1 + %p% - 正在刷新矿机信息:%1 + + + + You will reboot %1 miners + 即将重启%1台矿机 + + + Do you want to reboot these miners now? + 是否立即重启这几台矿机? + + + You will configure miners + 即将配置矿机 + + + Do you want to change pools, workers and passwords for all miners now? + 是否立即修改所有矿机的矿池、矿机名和密码? + + + + Cannot configure miners + 无法配置矿机 + + + IP Range not Complete + IP范围不完整 + + + Please input all sections of IP range. + 请补全IP范围的所有部分。 + + + + IP Range is Empty + IP范围为空 + + + + Please add & enable at least one IP range. + 请添加并勾选至少一个IP范围。 + + + + Stop Scan + 停止扫描 + + + + %p% - Scanning complete + %p% - 扫描完成 + + + + + + + + Complete + 完成 + + + Scanning complete. + 扫描完成 + + + + + Stop Config + 停止配置 + + + + %p% - Configuring complete + %p% - 配置完成 + + + + No miner to configure, or all miners configured successfully at the last time. +Please scan miners if you want to configure again. + 没有可配置的矿机,或者所有矿机都在上次配置好了。 +如需再次配置,请先扫描矿机。 + + + + + Stop Reboot + 停止重启 + + + + %p% - Refreshing miner's info: %1. Miner status count: + %p% - 正在刷新矿机信息:%1。矿机状态统计: + + + %p% - Monitor stopped + %p% - 监控已停止 + + + Monitor stopped, %1. + 监控已停止,%1。 + + + + %p% - Rebooting complete + %p% - 重启完成 + + + + No Miner to Reboot + 没有需要重启的矿机 + + + + No miner to reboot, or all miners you selected be rebooted successfully at the last time. +Please select another miners. + 没有需要重启的矿机,或者您选择的所有矿机都在上次重启好了。 +请选择其他矿机。 + + + Rebooting complete. + 重启完成 + + + All miners configured successfully at the last time. +Please scan miners if you want to configure again. + 所有矿机都在上次配置好了,如需再次配置,请先扫描矿机。 + + + Configuring complete. + 配置完成 + + + Stop Modify + 停止修改 + + + %p% - Modifying complete + %p% - 修改完成 + + + + No Miner to Configure + 没有可配置的矿机 + + + All miners' configure modified at the last time. +Please scan miners if you want to modify again. + 所有矿机都在上次配置好了,如需再次配置,请先扫描矿机。 + + + Modifying complete. + 修改完成 + + + + BTC Tools Needs a Update + BTC Tools 需要更新 + + + + The current version %1 is outdated. +Please download the new version %2 from pool.btc.com. + 当前版本 %1 已过时。 +请从 pool.btc.com 下载最新版本 %2。 + + + + + + + + %p% - Stopping %1 + %p% - 正在停止 %1 + + + + + + + + %p% - Stopping... + %p% - 正在停止... + + + <br>Skip %1 miners that don't match the model. + <br>跳过%3台型号不匹配的矿机。 {1 ?} + + + + %p% - Scanning %1 + %p% - 正在扫描 %1 + + + %p% - Monitoring %1 + %p% - 正在监控 %1 + + + + Please select one or more miners which you want to configure. + 请选择至少一台您想要配置的矿机。 + + + + You will configure %1 miners + 即将配置%1台矿机 + + + Do you want to change pools, workers and passwords for your selected miners now? + 是否立即修改选中矿机的矿池、矿机名和密码? + + + + No miner to configure + 没有需要配置的矿机 + + + + + + + Please scan miners first. + 请先扫描矿机。 + + + + %p% - Configuring %1 + %p% - 正在配置 %1 + + + + + + No miner be selected + 未选择矿机 + + + %p% - Refreshing Miner's Info: %1. Miner Status Count: + %p% - 正在更新矿机信息:%1。矿机状态统计: + + + + Scanning complete, %1. + 扫描完成,%1。 + + + + Configuring complete, %1. + 配置完成,%1。 + + + + Rebooting complete, %1. + 重启完成,%1。 + + + + + BTCTools has not yet supported the firmware upgrade of this model, please wait for future updates. + BTCTools暂不支持该型号升级,请等待后续更新。 + + + + Copy + 复制 + + + + Open Control Panel + 打开矿机后台 + + + %p% - Refreshing Miner's Info: %1 + %p% - 正在更新矿机信息:%1 + + + + Please select one or more miners which you want to reboot. + 请选择至少一台您想要重启的矿机。 + + + Do you want to reboot your selected miners now? + 是否立即重启您选中的矿机? + + + + No miner to reboot + 没有需要重启的矿机 + + + Do you want to reboot all miners now? + 是否立即重启所有矿机? + + + + %p% - Rebooting %1 + %p% - 正在重启 %1 + + + + LAN: + 局域网: + + + + Network is Not Available + 网络不可用 + + + + It looks like your computer has no available network. +So auto importing the IP ranges from your network failed. +Please connect to a network and try again, or add IP ranges manually. +But without an available network, you could not scan or configure miners. + 看上去你的电脑没有可用的网络连接, +所以根据网络配置自动导入IP地址范围失败。 +你可以连接网络后再试一次,或者手动添加IP地址范围。 +不过在没有网络的情况下,扫描和配置矿机也无法进行。 + + + The current version %1 is outdated. +Please download the new version %2 from our website. + 当前版本 %1 已过时。 +请从我们的网站下载最新版本 %2。 + + + + A New Version is Available + 发现新版本 + + + + New features of BTC Tools %1: + +%2 + +Do you want to download it now? + BTC Tools %1 的新特性: + +%2 + +是否立即下载新版本? + + + + MinerConfigurator + + skip + 跳过 + + + configurating... + 正在配置… + + + + MinerTableModel + + + IP + IP + + + + Status + 状态 + + + + Type + 矿机类型 + + + Hash Rate 5s + 5秒算力 + + + + Working Mode + 工作模式 + + + + Hash Rate RT + 实时算力 + + + + Hash Rate avg + 平均算力 + + + + Temperature + 温度 + + + + Fan Speed + 风扇转速 + + + + Elapsed + 启动时间 + + + + Pool 1 + 矿池1 + + + Worker + 矿机名 + + + + Worker 1 + 矿机名1 + + + + Pool 2 + 矿池2 + + + + Worker 2 + 矿机名2 + + + + Pool 3 + 矿池3 + + + + Worker 3 + 矿机名3 + + + + Firmware + 固件版本 + + + + Software + 软件版本 + + + + Hardware + 硬件版本 + + + + Network + 网络类型 + + + + MAC Address + MAC地址 + + + + d + days + + + + + h + hours + 小时 + + + + m + minutes + + + + + s + seconds + + + + + success + 成功 + + + + failed + 失败 + + + + timeout + 超时 + + + + unknown + 未知 + + + + ok + 完成 + + + + skip + 跳过 + + + + refused + 连接被拒 + + + + rebooted + 重启完成 + + + scanning... + 等待扫描... + + + configurating... + 等待配置... + + + + rebooting... + 等待重启... + + + + pre-scanning... + 等待扫描... + + + ok (OC√) + 完成 (超频√) + + + + upgraded + 升级完成 + + + + pre-configurating... + 等待配置... + + + + apply cfg... + 应用配置... + + + + check if BOS... + 检查是否为BOS... + + + + get BOS info... + 获取BOS信息... + + + + get cfg... + 获取配置... + + + + get meta cfg... + 获取基础配置... + + + + get network info... + 获取网络信息... + + + + get token... + 获取操作授权... + + + + get token failed + 获取授权失败 + + + + https detect... + 类型检测(https)... + + + + It is not a BOS + 矿机不是BOS固件 + + + + login without pwd... + 无密码登录... + + + + perform reboot failed + 执行重启失败 + + + + pre-rebooting... + 等待重启... + + + + pre-upgrading... + 等待升级... + + + + read hashrate... + 读取算力... + + + + read overclock status... + 读取超频状态... + + + + read power mode... + 读取工作模式... + + + + read summary... + 读取概况... + + + + read temperature... + 读取温度... + + + + save cfg... + 保存配置... + + + + set power mode failed + 设置工作模式失败 + + + + set power mode... + 设置工作模式... + + + + unknown device + 未知设备 + + + + upgrading... + 升级中... + + + + http detect... + 类型检测... + + + + login... + 登录... + + + + find type... + 获取型号... + + + + find pools... + 获取矿池... + + + + get status... + 获取状态... + + + + get pools... + 获取矿池... + + + + read status... + 读取状态... + + + + read type... + 读取类型... + + + + read config... + 读取配置... + + + + read overclock option... + 读取超频选项... + + + + update config... + 更新配置... + + + + restart cgminer... + 重启cgminer... + + + + upload file... + 上传固件... + + + + wait finish... + 等待完成... + + + + waiting... + 等待开始... + + + + require password + 需要密码 + + + + login failed + 登录失败 + + + + read config failed + 读配置失败 + + + + read type failed + 读型号失败 + + + + read stat failed + 读状态失败 + + + + update config failed + 更新配置失败 + + + + parse pools failed + 解析矿池信息失败 + + + + wait finish timeout + 等待超时 + + + + not finish + 未完成 + + + + retry times + 重试次数 + + + + OC √ + 超频√ + + + + OC tuning... + 超频中... + + + + may succeeded + 可能已成功 + + + + Miner is slowly cooling down + 芯片正在复位... + + + + Cannot Find Signature + 固件未签名,无法刷入 + + + + Cannot Find Signature!!! + 固件未签名,无法刷入 + + + OC × + 超频× + + + + LPM + 低功耗模式 + + + + Enhanced LPM + 低功耗增强模式 + + + + inner error + 内部错误 + + + + Don't support + 不支持 + + + + unknown step name + 未知步骤 + + + + antminer-unknown + 蚂蚁矿机未知型号 + + + + Incorrect firmware + 固件不正确,无法刷入 + + + + Incorrect firmware!!! + 固件不正确,无法刷入 + + + + Request Entity Too Large + 上传的文件太大 + + + + Firmware file not found + 固件文件不存在 + + + not finished + 未完成 + + + + find antminer + 发现蚂蚁矿机 + + + + find avalon + 发现阿瓦隆矿机 + + + + QColorDialog + + + Pick Screen Color + 选择屏幕颜色 + + + + QObject + + + + + A critical error occured + 程序发生严重错误 + + + + Unknown error. + 错误原因未知。 + + + + Cannot open firmware %1: +%2 + 打开固件 %1 失败:%2 + + + + QPlatformTheme + + + OK + 确定 + + + + Save + 保存 + + + + Save All + 全部保存 + + + + Open + 打开 + + + + &Yes + + + + + Yes to &All + 全部是 + + + + &No + + + + + N&o to All + 全部否 + + + + Abort + 中断 + + + + Retry + 重试 + + + + Ignore + 忽略 + + + + Close + 关闭 + + + + Cancel + 取消 + + + + Discard + 放弃 + + + + Help + 帮助 + + + + Apply + 应用 + + + + Reset + 重置 + + + + Restore Defaults + 恢复默认 + + + + SettingWindow + + + Settings + 设置 + + + + Upgrading Timeout: + 升级超时时间: + + + + ConCurrent Scanning: + 扫描并发数: + + + + Auto Retry on Failure: + 失败时自动重试: + + + + times + + + + + ConCurrent Configuring: + 配置并发数: + + + + Scanning Timeout: + 扫描超时时间: + + + + + + miners + + + + + + + + seconds + + + + + Configuring Timeout: + 配置超时时间: + + + or Less than + 或低于 + + + + Highlight Abnormal Temperatures: More than + 温度异常标红:高于 + + + + + ℃ + + + + + Set last + 使用IP地址的后 + + + parts of IP address as worker name. + 段做为矿机名后缀。 + + + + Open AntMiner's control panel with password (uncheck it if you get +a blank page when opening miner's control panel from the tools). + 打开蚂蚁矿机控制面板时附带密码(如果从该工具打开矿机 +控制面板时得到空白页,请取消勾选此项) + + + Highlight Worker names that different from the miners' IP. + 矿机名与矿机IP不匹配标红 + + + + Miner Login Passwords: + 矿机登录密码: + + + + Tips: For security reasons, passwords will not be saved after you close the tool. + 提醒:为了保证安全,批量工具在退出后不会保存矿机密码。 + + + + Highlight too Low Hashrates: + 算力过低标红: + + + + + + + + + + + + ConCurrent Upgrading: + 升级并发数: + + + + Monitor Interval: + 监控刷新间隔: + + + Tips: large upgrading concurrency will got a <b>large memory usage</b><br>or <b>crash</b>. If you encounter any problems, set it to 5 or less. + 注意:设置较大的升级并发数会导致<b>内存占用过高</b>甚至<br><b>程序崩溃</b>。如果你遇到任何问题,请设置为5或者更小。 + + + + parts of IP address as worker name postfix. + 段做为矿机名后缀。 + + + + or Less than + 或低于 + + + + Highlight worker names that different from the miners' IP. + 矿机名与矿机IP不匹配标红 + + + Open miner control panel with password (uncheck it if you get +a blank page when opening miner control panel with the tools). + 打开矿机控制面板时附带密码(如果从该工具打开矿机 +控制面板时得到空白页,请取消勾选此项) + + + + + - + - + + + + + Miner Type + 矿机类型 + + + + Min Hash Rate + 最低算力 + + + + User Name + 用户名 + + + + Password + 密码 + + + + + No Item is Selected + 没有选择项目 + + + + + Please select an item to remove. + 请先选择要删除的项目。 + + + + UpgradeWindow + + + Firmware Upgrade + 固件升级 + + + + Miner Model: + 矿机型号: + + + + Firmware: + 固件: + + + + + + + + + + + - + - + + + + + No miners found. Please rescan first. + 没有矿机,请先重新扫描。 + + + + Keep settings + 升级后保留设置 + + + + Upgrade Selected + 升级选中 + + + + Upgrade All + 升级全部 + + + + Selected %1: %2 + All %1: %3 + 选中 %1: %2 +全部 %1: %3 + + + + Selected %1: %2 (CANNOT upgrade: %4) + All %1: %3 (CANNOT upgrade: %5) + 选中 %1: %2 (不支持升级: %4) +全部 %1: %3 (不支持升级: %5) + + + + Upgrading this model is not yet supported. + 暂不支持该型号固件升级 + + + + Miner Model is Empty + 矿机型号为空 + + + + Please select a miner model. + 请选择矿机型号 + + + + Firmware is Empty + 要升级的固件为空 + + + + Please select or add a firmware. + 请添加或选择一个固件。 + + + + Cannot add firmware + 添加固件失败 + + + + Miner model is empty, please rescan your miners. + 矿机型号为空,请先重新扫描矿机。 + + + + Select Firmware + 选择固件 + + + + Firmware (*.bmu *.tar.gz) +All (*) + 固件 (*.bmu *.tar.gz) +全部 (*) + + + diff --git a/res/resources.qrc b/res/resources.qrc new file mode 100644 index 0000000..8af772f --- /dev/null +++ b/res/resources.qrc @@ -0,0 +1,79 @@ + + + locale/zh_CN.qm + locale/qt_zh_CN.qm + + + scripts/configurator/AnthillOS.lua + scripts/configurator/AntminerHttpCgi.lua + scripts/configurator/AvalonDeviceCgi.lua + scripts/configurator/AvalonHttpLuci.lua + scripts/configurator/BosHttpLuci.lua + scripts/ConfiguratorHelper.lua + scripts/configurator/WhatsMinerHttpsLuci.lua + scripts/ExecutorBase.lua + scripts/HelperBase.lua + scripts/loop/base.lua + scripts/loop/cached.lua + scripts/loop/collection/MapWithArrayOfKeys.lua + scripts/loop/collection/ObjectCache.lua + scripts/loop/collection/OrderedSet.lua + scripts/loop/collection/PriorityQueue.lua + scripts/loop/collection/UnorderedArray.lua + scripts/loop/collection/UnorderedArraySet.lua + scripts/loop/compiler/Arguments.lua + scripts/loop/compiler/Conditional.lua + scripts/loop/compiler/Expression.lua + scripts/loop/component/base.lua + scripts/loop/component/contained.lua + scripts/loop/component/dynamic.lua + scripts/loop/component/intercepted.lua + scripts/loop/component/wrapped.lua + scripts/loop/debug/Inspector.lua + scripts/loop/debug/Matcher.lua + scripts/loop/debug/Verbose.lua + scripts/loop/debug/Viewer.lua + scripts/loop/multiple.lua + scripts/loop/object/Exception.lua + scripts/loop/object/Publisher.lua + scripts/loop/object/Wrapper.lua + scripts/loop/scoped.lua + scripts/loop/serial/FileStream.lua + scripts/loop/serial/Serializer.lua + scripts/loop/serial/SocketStream.lua + scripts/loop/serial/StringStream.lua + scripts/loop/simple.lua + scripts/loop/table.lua + scripts/loop/thread/CoSocket.lua + scripts/loop/thread/IOScheduler.lua + scripts/loop/thread/Scheduler.lua + scripts/loop/thread/SocketScheduler.lua + scripts/loop/thread/Timer.lua + scripts/MiningProgram.lua + scripts/rebooter/AnthillOS.lua + scripts/rebooter/AntminerHttpCgi.lua + scripts/rebooter/AvalonDeviceCgi.lua + scripts/rebooter/AvalonHttpLuci.lua + scripts/rebooter/BosHttpLuci.lua + scripts/RebooterHelper.lua + scripts/rebooter/WhatsMinerHttpsLuci.lua + scripts/scanner/AnthillOS.lua + scripts/scanner/AntminerHttpCgi.lua + scripts/scanner/AvalonDeviceCgi.lua + scripts/scanner/AvalonHttpLuci.lua + scripts/scanner/BosHttpLuci.lua + scripts/scanner/GenericCgminerApi.lua + scripts/ScannerHelper.lua + scripts/scanner/HttpAutoDetect.lua + scripts/scanner/WhatsMinerHttpsLuci.lua + scripts/upgrader/AnthillOS.lua + scripts/upgrader/AntminerHttpCgi.lua + scripts/upgrader/BosHttpLuci.lua + scripts/UpgraderHelper.lua + scripts/utils/date.lua + scripts/utils/dkjson.lua + scripts/utils/http.lua + scripts/utils/oop.lua + scripts/utils/utils.lua + + diff --git a/res/ui/iprangewindow.ui b/res/ui/iprangewindow.ui new file mode 100644 index 0000000..8e18d10 --- /dev/null +++ b/res/ui/iprangewindow.ui @@ -0,0 +1,148 @@ + + + IpRangeWindow + + + + 0 + 0 + 494 + 275 + + + + IP Range Editor + + + + + + 3 + + + 0 + + + 0 + + + 0 + + + + + Check All + + + + + + + + 0 + 0 + + + + + 25 + 16777215 + + + + + + + + + + + + + 0 + 0 + + + + + 25 + 16777215 + + + + - + + + + + + + + + + + + 0 + + + 0 + + + + + Comment (Optional): + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + IpRangeWindow + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + IpRangeWindow + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/res/ui/mainwindow.ui b/res/ui/mainwindow.ui new file mode 100644 index 0000000..0d36537 --- /dev/null +++ b/res/ui/mainwindow.ui @@ -0,0 +1,1008 @@ + + + MainWindow + + + + 0 + 0 + 1024 + 580 + + + + BTC Tools + + + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + + 1500 + 16777215 + + + + + + + + + + + IP Ranges: + + + + + + + + 0 + 0 + + + + + 25 + 16777215 + + + + + + + + + + + + + 0 + 0 + + + + + 25 + 16777215 + + + + - + + + + + + + + 0 + 0 + + + + Auto Import + + + + + + + + + + 0 + 0 + + + + + 100 + 100 + + + + + 300 + 16777215 + + + + + 0 + 0 + + + + Double click an item to edit. + + + false + + + false + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 0 + + + 0 + + + + + Scan + + + + + + + Monitor + + + + + + + false + + + Config All + + + + + + + false + + + Config Selected + + + + + + + false + + + Reboot All + + + + + + + false + + + Reboot Selected + + + + + + + false + + + Firmware Upgrade + + + + + + + Export + + + + + + + Settings + + + + + + + + 0 + 0 + + + + + + + + + + + + 0 + + + 5 + + + + + false + + + Qt::AlignCenter + + + + + + + + + 6 + + + 0 + + + 0 + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 0 + + + 0 + + + + + true + + + Pool 1: + + + true + + + + + + + + 200 + 16777215 + + + + us.ss.btc.com:1800 + + + + + + + Worker prefix + + + SubAccount: + + + + + + + + 200 + 16777215 + + + + + + + + + + + PWD: + + + + + + + + 200 + 16777215 + + + + 123 + + + + + + + Worker Suffix: + + + + + + + + 0 + 0 + + + + + 6 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + IP + + + false + + + + + + + No Change + + + true + + + + + + + Empty + + + + + + + + + + + + 0 + + + 0 + + + + + true + + + Pool 2: + + + true + + + + + + + + 200 + 16777215 + + + + us.ss.btc.com:443 + + + + + + + Worker prefix + + + SubAccount: + + + + + + + + 200 + 16777215 + + + + + + + + + + + PWD: + + + + + + + + 200 + 16777215 + + + + 123 + + + + + + + Worker Suffix: + + + + + + + + 0 + 0 + + + + + 6 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + IP + + + false + + + + + + + No Change + + + true + + + + + + + Empty + + + + + + + + + + + + 0 + + + 0 + + + + + Pool 3: + + + true + + + + + + + + 200 + 16777215 + + + + us.ss.btc.com:25 + + + + + + + Worker prefix + + + SubAccount: + + + + + + + + 200 + 16777215 + + + + + + + + + + + PWD: + + + + + + + + 200 + 16777215 + + + + 123 + + + + + + + Worker Suffix: + + + + + + + + 0 + 0 + + + + + 6 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + IP + + + false + + + + + + + No Change + + + true + + + + + + + Empty + + + + + + + + + + + + 0 + + + + + Only for some AntMiner firmwares that has "Mode" or "Work Mode" drop-down box / radio buttons on its configuration page. +Known supported firmwares: +* AntMiner L3/L3+/L3++/L5 firmware with "Mode" drop-down box. +* AntMiner S17/S17 Pro firmware with "Work Mode" radio buttons. +* AntMiner S9 firmware with "Working Mode" drop-down box. + + + Overclock/Underclock: + + + + + + + Model + + + + + + + false + + + + 50 + 0 + + + + + Arial + 10 + + + + Only the model you selected will be changed and other models will keep their original settings. +The model list will be empty until you done a "Scan". + + + + + + + Work Mode + + + + + + + false + + + + 30 + 0 + + + + + Arial + 10 + + + + If one of your miner doesn't support this mode, its working mode will not be changed. +The drop down box will be empty until a supported miner is scanned. + + + + + + + Option + + + + + + + false + + + + 100 + 0 + + + + + Arial + 10 + + + + If one of your miner doesn't support this option, its option will not be changed. +The drop down box will be empty until a supported miner is scanned. +If you don't see the options you want, change the working mode first. + + + + + + + + + + + + 10 + 0 + + + + When scanning, it will only list the miners whose status is "success". +Uncheck this to see the complete scan result. + + + Only Success Miners + + + true + + + true + + + + + + + + 10 + 0 + + + + Clear the saved overclocking records and start a new try of overclocking. +Only for the miners that have the "Force Tuning" checkbox. + + + Re-overclocking + + + + + + + Only for some AntMiner firmwares that has "Low Power Mode" and "Low Power Enhanced Mode" checkboxes on its configuration page. +Known supported firmwares: AntMiner S9 firmware with above checkboxes. +The Overclock/Underclock form on the right provides frequency settings for more AntMiner models. + + + Power Control: + + + + + + + false + + + Low Power Mode. Turn on ASICBoost to save energy and keep the same hashrate. + + + LPM + + + true + + + + + + + false + + + Low Power Enhanced Mode. Reduce the frequency to save more energy (hashrate will decrease). + + + Enhanced LPM + + + false + + + + + + + false + + + + 0 + 0 + + + + AntMiner S9 doesn't support this. Keep it for future use. + + + Economic Mode (keep for future use) + + + + + + + + + + + + + + + 0 + 0 + + + + + + + + + + + + + + 0 + 0 + + + + + + + + + + Double click: open a miner's control panel in the browser. +Right click: copy the text to the clipboard. + + + QAbstractItemView::ScrollPerItem + + + QAbstractItemView::ScrollPerPixel + + + true + + + + + + + + + diff --git a/res/ui/settingwindow.ui b/res/ui/settingwindow.ui new file mode 100644 index 0000000..5b159e0 --- /dev/null +++ b/res/ui/settingwindow.ui @@ -0,0 +1,609 @@ + + + SettingWindow + + + + 0 + 0 + 810 + 383 + + + + Settings + + + + + + 0 + + + 0 + + + + + 6 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 0 + + + + + seconds + + + + + + + Configuring Timeout: + + + + + + + + 80 + 16777215 + + + + + + + + ConCurrent Configuring: + + + + + + + Monitor Interval: + + + + + + + + 80 + 16777215 + + + + + + + + + 80 + 16777215 + + + + + + + + + 80 + 16777215 + + + + + + + + + 0 + 0 + + + + miners + + + + + + + + 80 + 16777215 + + + + + + + + miners + + + + + + + seconds + + + + + + + + 80 + 16777215 + + + + + + + + + 80 + 16777215 + + + + + + + + Upgrading Timeout: + + + + + + + ConCurrent Upgrading: + + + + + + + miners + + + + + + + seconds + + + + + + + seconds + + + + + + + + 0 + 0 + + + + ConCurrent Scanning: + + + + + + + + 0 + 0 + + + + Scanning Timeout: + + + + + + + Auto Retry on Failure: + + + + + + + + 80 + 16777215 + + + + + + + + times + + + + + + + + + 10 + + + + + + 0 + 0 + + + + Set last + + + + + + + + 50 + 0 + + + + 1 + + + 4 + + + 2 + + + + + + + + 0 + 0 + + + + parts of IP address as worker name postfix. + + + + + + + + + 10 + + + + + Highlight Abnormal Temperatures: More than + + + + + + + + 0 + 0 + + + + + + + + + + + + 80 + 16777215 + + + + + + + + + 0 + 0 + + + + + + + + + + + + 80 + 16777215 + + + + + + + + or Less than + + + + + + + + + 10 + + + + + Highlight worker names that different from the miners' IP. + + + + + + + + + 10 + + + + + Open AntMiner's control panel with password (uncheck it if you get +a blank page when opening miner's control panel from the tools). + + + false + + + + + + + + + + + + 6 + 0 + + + + + 6 + 16777215 + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + Miner Login Passwords: + + + + + + + + 25 + 16777215 + + + + + + + + + + + + + 25 + 16777215 + + + + - + + + + + + + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectRows + + + false + + + + + + + Tips: For security reasons, passwords will not be saved after you close the tool. + + + true + + + + + + + + + + 0 + 0 + + + + Highlight too Low Hashrates: + + + + + + + + 25 + 16777215 + + + + + + + + + + + + + 25 + 16777215 + + + + - + + + + + + + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectRows + + + false + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok|QDialogButtonBox::RestoreDefaults + + + + + + + + + buttonBox + accepted() + SettingWindow + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + SettingWindow + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/res/ui/upgradewindow.ui b/res/ui/upgradewindow.ui new file mode 100644 index 0000000..a144e0a --- /dev/null +++ b/res/ui/upgradewindow.ui @@ -0,0 +1,228 @@ + + + UpgradeWindow + + + + 0 + 0 + 548 + 120 + + + + Firmware Upgrade + + + + + + 0 + + + 0 + + + + + + + + 0 + 0 + + + + + 75 + 0 + + + + Miner Model: + + + + + + + + 0 + 0 + + + + + 200 + 0 + + + + + + + + + 0 + 0 + + + + + + + + + + + + + 75 + 0 + + + + Firmware: + + + + + + + + 0 + 0 + + + + + + + + + 0 + 0 + + + + + 25 + 16777215 + + + + + + + + + + + + + 25 + 16777215 + + + + - + + + + + + + + + + 0 + 0 + + + + + + + + 0 + + + + + text-align: right + + + No miners found. Please rescan first. + + + + + + + + 0 + 0 + + + + + + + + Keep settings + + + true + + + + + + + false + + + + 0 + 18 + + + + Upgrade Selected + + + + + + + false + + + + 0 + 18 + + + + Upgrade All + + + + + + + + + + 0 + 0 + + + + + + + + + + + diff --git a/res/update-data-linuxsnap-amd64.json b/res/update-data-linuxsnap-amd64.json new file mode 100644 index 0000000..a9a3e23 --- /dev/null +++ b/res/update-data-linuxsnap-amd64.json @@ -0,0 +1,8 @@ +{ + "minVersionId": 1, + "versionId": 1330, + "versionName": "v1.3.3", + "desc": "1. Added support for Avalon 12xx miners (Thanks to Vnish Firmware's CloudDev-C for a bug fix).\n\n2. Added support for Vnish Firmware.", + "desc_zh_CN": "1. 添加了对阿瓦隆12xx型矿机的支持(感谢Vnish固件团队的CloudDev-C提供的一个Bug修复)。\n\n2. 添加了对Vnish固件的支持。", + "downloadUrl": "https://snapcraft.io/btctools" +} diff --git a/res/update-data-linuxsnap-arm64.json b/res/update-data-linuxsnap-arm64.json new file mode 100644 index 0000000..a9a3e23 --- /dev/null +++ b/res/update-data-linuxsnap-arm64.json @@ -0,0 +1,8 @@ +{ + "minVersionId": 1, + "versionId": 1330, + "versionName": "v1.3.3", + "desc": "1. Added support for Avalon 12xx miners (Thanks to Vnish Firmware's CloudDev-C for a bug fix).\n\n2. Added support for Vnish Firmware.", + "desc_zh_CN": "1. 添加了对阿瓦隆12xx型矿机的支持(感谢Vnish固件团队的CloudDev-C提供的一个Bug修复)。\n\n2. 添加了对Vnish固件的支持。", + "downloadUrl": "https://snapcraft.io/btctools" +} diff --git a/res/update-data-linuxsnap-armhf.json b/res/update-data-linuxsnap-armhf.json new file mode 100644 index 0000000..a9a3e23 --- /dev/null +++ b/res/update-data-linuxsnap-armhf.json @@ -0,0 +1,8 @@ +{ + "minVersionId": 1, + "versionId": 1330, + "versionName": "v1.3.3", + "desc": "1. Added support for Avalon 12xx miners (Thanks to Vnish Firmware's CloudDev-C for a bug fix).\n\n2. Added support for Vnish Firmware.", + "desc_zh_CN": "1. 添加了对阿瓦隆12xx型矿机的支持(感谢Vnish固件团队的CloudDev-C提供的一个Bug修复)。\n\n2. 添加了对Vnish固件的支持。", + "downloadUrl": "https://snapcraft.io/btctools" +} diff --git a/res/update-data-winexe-i386.json b/res/update-data-winexe-i386.json new file mode 100644 index 0000000..6b33da0 --- /dev/null +++ b/res/update-data-winexe-i386.json @@ -0,0 +1,8 @@ +{ + "minVersionId": 1, + "versionId": 1330, + "versionName": "v1.3.3", + "desc": "1. Added support for Avalon 12xx miners (Thanks to Vnish Firmware's CloudDev-C for a bug fix).\n\n2. Added support for Vnish Firmware.", + "desc_zh_CN": "1. 添加了对阿瓦隆12xx型矿机的支持(感谢Vnish固件团队的CloudDev-C提供的一个Bug修复)。\n\n2. 添加了对Vnish固件的支持。", + "downloadUrl": "https:\/\/url.btc.com\/btc-tools-download" +} diff --git a/src/autoupdater.cpp b/src/autoupdater.cpp new file mode 100644 index 0000000..74f64f6 --- /dev/null +++ b/src/autoupdater.cpp @@ -0,0 +1,116 @@ +#include "autoupdater.h" +#include "config.h" +#include "utils.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +AutoUpdater::AutoUpdater() +{ +} + +void AutoUpdater::run() +{ + try + { + std::string response; + bool success = httpGET(Utils::debugMode() ? APP_AUTO_UPDATE_URL_DEBUG : APP_AUTO_UPDATE_URL, response); + + //-------------- get response -------------- + if (success) + { + QByteArray jsonBytes(response.data(), response.size()); + + //qDebug() << "json size:" << jsonBytes.size(); + //qDebug() << jsonBytes.data(); + //qDebug() << "end"; + + //-------------- parse JSON data -------------- + QJsonParseError parseError; + QJsonDocument document = QJsonDocument::fromJson(jsonBytes, &parseError); + + if (parseError.error != QJsonParseError::NoError) + { + throw std::runtime_error(tr("Parse update info failed: %1").arg(parseError.errorString()).toUtf8().data()); + } + + if (!document.isObject()) { + throw std::runtime_error(tr("Parse update info failed: must be object").toUtf8().data()); + } + + QJsonObject jsonMain = document.object(); + + int versionId = 0; + QString versionName = ""; + QString description = ""; + QString downloadUrl = ""; + bool forceUpdate = false; + + if (jsonMain.contains("versionId")) + { + QJsonValue value = jsonMain.value("versionId"); + versionId = value.toInt(); + } + + if (jsonMain.contains("minVersionId")) + { + QJsonValue value = jsonMain.value("minVersionId"); + int minVersionId = value.toInt(); + + if (APP_VERSION_ID < minVersionId) + { + forceUpdate = true; + } + } + + if (jsonMain.contains("versionName")) + { + QJsonValue value = jsonMain.value("versionName"); + versionName = value.toString(); + } + + QLocale locale; + QString descKeyWithLangCode = QString("desc_") + locale.name(); + + if (jsonMain.contains(descKeyWithLangCode)) + { + QJsonValue value = jsonMain.value(descKeyWithLangCode); + description = value.toString(); + } + else if (jsonMain.contains("desc")) + { + QJsonValue value = jsonMain.value("desc"); + description = value.toString(); + } + + if (jsonMain.contains("downloadUrl")) + { + QJsonValue value = jsonMain.value("downloadUrl"); + downloadUrl = value.toString(); + } + + //-------------- emit the updates -------------- + if (versionId > APP_VERSION_ID) + { + emit findUpdates(versionName, description, downloadUrl, forceUpdate); + } + else + { + qDebug() << tr("Auto Updater: the current version is the latest."); + qDebug() << tr("Version from server: %1 <%2>").arg(versionName).arg(downloadUrl); + } + } + } + catch (const std::exception &ex) + { + qDebug() << "AutoUpdater error: " << ex.what(); + } +} diff --git a/src/autoupdater.h b/src/autoupdater.h new file mode 100644 index 0000000..e78c81d --- /dev/null +++ b/src/autoupdater.h @@ -0,0 +1,28 @@ +#ifndef AUTOUPDATER_H +#define AUTOUPDATER_H + +#include +#include +#include +#include +#include +#include + +class AutoUpdater : public QThread +{ + Q_OBJECT + +public: + AutoUpdater(); + +protected: + void run(); + +signals: + void findUpdates(QString versionName, QString description, QString downloadUrl, bool forceUpdate); + +private: + +}; + +#endif // AUTOUPDATER_H diff --git a/src/checkmessagebox.cpp b/src/checkmessagebox.cpp new file mode 100644 index 0000000..e428052 --- /dev/null +++ b/src/checkmessagebox.cpp @@ -0,0 +1,64 @@ +#include "checkmessagebox.h" + +CheckMessageBox::CheckMessageBox(QMessageBox::Icon icon, const QString &title, const QString &text, + const QString &checkBoxText, QWidget *parent) + :QMessageBox(icon, title, text, NoButton, parent) +{ + chkBox_ = new QCheckBox(checkBoxText, this); + okBtn_ = new QPushButton(tr("OK"), this); + cancelBtn_ = new QPushButton(tr("Cancel"), this); + + setCheckBox(chkBox_); + addButton(okBtn_, AcceptRole); + addButton(cancelBtn_, RejectRole); + setDefaultButton(cancelBtn_); + + okBtn_->setEnabled(false); + connect(chkBox_, SIGNAL(stateChanged(int)), this, SLOT(changeOkBtnState(int))); +} + +CheckMessageBox::~CheckMessageBox() +{ + delete chkBox_; + delete okBtn_; + delete cancelBtn_; +} + +int CheckMessageBox::exec() +{ + QMessageBox::exec(); + return clickedButton() == okBtn_ ? Ok : Cancel; +} + +int CheckMessageBox::information(QWidget *parent, const QString &title, + const QString &text, const QString &checkBoxText) +{ + CheckMessageBox msgBox(Information, title, text, checkBoxText, parent); + return msgBox.exec(); +} + +int CheckMessageBox::question(QWidget *parent, const QString &title, + const QString &text, const QString &checkBoxText) +{ + CheckMessageBox msgBox(Question, title, text, checkBoxText, parent); + return msgBox.exec(); +} + +int CheckMessageBox::warning(QWidget *parent, const QString &title, + const QString &text, const QString &checkBoxText) +{ + CheckMessageBox msgBox(Warning, title, text, checkBoxText, parent); + return msgBox.exec(); +} + +int CheckMessageBox::critical(QWidget *parent, const QString &title, + const QString &text, const QString &checkBoxText) +{ + CheckMessageBox msgBox(Critical, title, text, checkBoxText, parent); + return msgBox.exec(); +} + +void CheckMessageBox::changeOkBtnState(int state) +{ + okBtn_->setEnabled(state != 0); +} diff --git a/src/checkmessagebox.h b/src/checkmessagebox.h new file mode 100644 index 0000000..f80335e --- /dev/null +++ b/src/checkmessagebox.h @@ -0,0 +1,38 @@ +#ifndef CHECKMESSAGEBOX_H +#define CHECKMESSAGEBOX_H + +#include +#include +#include +#include +#include + +class CheckMessageBox : public QMessageBox +{ + Q_OBJECT + +public: + CheckMessageBox(Icon icon, const QString &title, const QString &text, + const QString &checkBoxText, QWidget *parent = Q_NULLPTR); + ~CheckMessageBox(); + int exec(); + + static int information(QWidget *parent, const QString &title, + const QString &text, const QString &checkBoxText); + static int question(QWidget *parent, const QString &title, + const QString &text, const QString &checkBoxText); + static int warning(QWidget *parent, const QString &title, + const QString &text, const QString &checkBoxText); + static int critical(QWidget *parent, const QString &title, + const QString &text, const QString &checkBoxText); + +private slots: + void changeOkBtnState(int state); + +private: + QCheckBox *chkBox_; + QPushButton *okBtn_; + QPushButton *cancelBtn_; +}; + +#endif // CHECKMESSAGEBOX_H diff --git a/src/config.h b/src/config.h new file mode 100644 index 0000000..9cd4fe8 --- /dev/null +++ b/src/config.h @@ -0,0 +1,71 @@ +#ifndef CONFIG_H +#define CONFIG_H + +#define APP_VERSION_ID 1330 +#define APP_VERSION_NAME "v1.3.3" +#define APP_VERSION_NUMS 1,3,3,0 + +#define APP_NAME "BTC Tools" +#define APP_NAME_DEBUG "BTC Tools Debug Mode" + +#define APP_FULL_NAME APP_NAME " " APP_VERSION_NAME +#define APP_FULL_NAME_DEBUG APP_NAME_DEBUG " " APP_VERSION_NAME + +#define APP_COMPANY_NAME "BTC.com" +#define APP_COPYRIGHT_LINE "Copyright 2021 BTC.com" +#define APP_LEGAL_INFO_LINE "All Rights Reserved" + +#define APP_FILE_NAME APP_NAME ".exe" + +#ifdef PLATFORM_NAME + #define PLATFORM_SUFFIX "-" PLATFORM_NAME +#elif defined (WIN32) + #define PLATFORM_NAME "win32" + #define PLATFORM_SUFFIX "" +#else + #define PLATFORM_NAME "linux" + #define PLATFORM_SUFFIX "-linux" +#endif + +#define APP_AUTO_UPDATE_URL "https://url.btc.com/btc-tools-update" PLATFORM_SUFFIX +#define APP_AUTO_UPDATE_URL_DEBUG "http://localhost/update-data" PLATFORM_SUFFIX ".json" + +#define UI_CONFIG_FILE_PATH "./btctools.ini" +#define LOCALE_DIR_PATH ":/locale" + +#define POOL_SERVER_DOMAIN_REGEXP "^.*\\.ss\\.btc\\.com$" +#define POOL_SERVER_IP_REGEXP "^[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+$" + +#define SCAN_SESSION_TIMEOUT 3 +#define CONFIG_SESSION_TIMEOUT 10 +#define UPGRADE_SESSION_TIMEOUT 180 +#define MONITOR_INTERVAL 5 + +#define SCAN_STEP_SIZE 100 +#define CONFIG_STEP_SIZE 100 +// Warning: If the value is too large, it will insufficient +// memory when upgrading and the program will crash. +#define UPGRADE_SEND_FIRMWARE_STEP_SIZE 5 + +// Note: The time required to retry when the connection is refused +// on Windows is significantly longer than that on Linux. +#define AUTO_RETRY_TIMES 2 + +#define WORKER_NAME_IP_PARTS 2 + +#define IS_HIGHLIGHT_TEMPERATURE true +#define HIGHLIGHT_TEMPERATURE_MORE_THAN 90 +#define HIGHLIGHT_TEMPERATURE_LESS_THAN 0 +#define IS_HIGHLIGHT_LOW_HASHRATE true +#define IS_HIGHLIGHT_WRONG_WORKER_NAME true +#define OPEN_MINER_CP_WITH_PASSWORD true + +#define HIGHLIGHT_LOW_HASHRATES {{"Antminer S9", 10.0e12},\ + {"Antminer S7", 4.0e12}} + +#define DEFAULT_MINER_PASSWORDS {{"Antminer", "root", "root"},\ + {"AvalonDevice", "root", "root"},\ + {"Avalon", "root", ""},\ + {"WhatsMiner", "admin", "admin"}} + +#endif // CONFIG_H diff --git a/src/iprangeedit.cpp b/src/iprangeedit.cpp new file mode 100644 index 0000000..f7437c6 --- /dev/null +++ b/src/iprangeedit.cpp @@ -0,0 +1,335 @@ +#include "iprangeedit.h" +#include + +IpRangeEdit::IpRangeEdit(QWidget *parent) : QFrame(parent) +{ + setFrameShape( QFrame::StyledPanel ); + setFrameShadow( QFrame::Sunken ); + + QHBoxLayout* pLayout = new QHBoxLayout( this ); + setLayout( pLayout ); + pLayout->setContentsMargins( 0, 0, 0, 0 ); + pLayout->setSpacing( 0 ); + + for ( int i = 0; i < QTUTL_IP_SIZE; ++i ) + { + if ( i != 0 ) + { + QLabel* pDot = new QLabel( (i == QTUTL_IP_RANGE_SEPARATOR) ? "~" : ".", this ); + pDot->setStyleSheet( (i == QTUTL_IP_RANGE_SEPARATOR) ? + "background: white; font-family: Arial, \"Microsoft YaHei\", sans-serif" : + "background: white; font-family: Arial, \"Microsoft YaHei\", sans-serif; font-size: 18px"); + pLayout->addWidget( pDot ); + pLayout->setStretch( pLayout->count(), 0 ); + } + + m_pLineEdit[i] = new QLineEdit( this ); + QLineEdit* pEdit = m_pLineEdit[i]; + pEdit->installEventFilter( this ); + + pLayout->addWidget( pEdit ); + pLayout->setStretch( pLayout->count(), 1 ); + + pEdit->setFrame( false ); + pEdit->setAlignment( Qt::AlignCenter ); + + QFont font = pEdit->font(); + font.setStyleHint( QFont::Monospace ); + font.setFixedPitch( true ); + pEdit->setFont( font ); + + QRegExp rx ( "^(0|[1-9]|[1-9][0-9]|1[0-9][0-9]|2([0-4][0-9]|5[0-5]))$" ); + QValidator *validator = new QRegExpValidator(rx, pEdit); + pEdit->setValidator( validator ); + + } + + setMaximumWidth( 50 * QTUTL_IP_SIZE ); + + connect( this, SIGNAL(signalTextChanged(QLineEdit*)), + this, SLOT(slotTextChanged(QLineEdit*)), + Qt::QueuedConnection ); +} + +void IpRangeEdit::slotTextChanged( QLineEdit* pEdit ) +{ + for ( unsigned int i = 0; i < QTUTL_IP_SIZE; ++i ) + { + if ( pEdit == m_pLineEdit[i] ) + { + // auto filling with the end part + if (i < QTUTL_IP_RANGE_SEPARATOR) + { + m_pLineEdit[i + QTUTL_IP_RANGE_SEPARATOR]->setPlaceholderText(pEdit->text()); + } + + if ( ( pEdit->text().size() == MAX_DIGITS && pEdit->text().size() == pEdit->cursorPosition() ) || ( pEdit->text() == "0") ) + { + // auto-move to next item + if ( i+1 < QTUTL_IP_SIZE ) + { + m_pLineEdit[i+1]->setFocus(); + m_pLineEdit[i+1]->selectAll(); + } + } + } + } +} + +QString IpRangeEdit::regularIpNumber(const QString &ipNumber) +{ + // trim it + QString ipNumberTrimmed = ipNumber.trimmed(); + + // empty, not regular + if (ipNumberTrimmed.isEmpty()) + { + return ipNumberTrimmed; + } + + //not empty, convert to uint8_t and convert back. + uint8_t ipNumUint8 = ipNumberTrimmed.toUShort(); + return QString::number(ipNumUint8); +} + +bool IpRangeEdit::isIpRangeValid() +{ + for ( unsigned int i = 0; i < QTUTL_IP_RANGE_SEPARATOR; ++i ) + { + if (m_pLineEdit[i]->text().isEmpty()) + { + return false; + } + } + + return true; +} + +QString IpRangeEdit::ipRange() +{ + QString ipRange = ""; + + for ( int i = 0; i < QTUTL_IP_SIZE; ++i ) + { + if ( i != 0 ) + { + ipRange += (i == QTUTL_IP_RANGE_SEPARATOR) ? "-" : "."; + } + + QString ipPart = m_pLineEdit[i]->text(); + + if (ipPart.isEmpty() && i >= QTUTL_IP_RANGE_SEPARATOR) + { + ipPart = m_pLineEdit[i - QTUTL_IP_RANGE_SEPARATOR]->text(); + } + + ipRange += ipPart; + } + + return ipRange; +} + +QString IpRangeEdit::beautifiedIpRange() +{ + QString ipRange = ""; + + for ( int i = 0; i < QTUTL_IP_RANGE_SEPARATOR; ++i ) + { + if (i != 0) + { + ipRange += '.'; + } + + ipRange += m_pLineEdit[i]->text(); + } + + QStringList ipRangeEndList; + bool allSame = true; + + for ( int i = QTUTL_IP_RANGE_SEPARATOR; i < QTUTL_IP_SIZE; ++i ) + { + QString ipPartEnd = m_pLineEdit[i]->text(); + QString ipPartBegin = m_pLineEdit[i - QTUTL_IP_RANGE_SEPARATOR]->text(); + + if (ipPartEnd.isEmpty()) + { + ipPartEnd = ipPartBegin; + } + + if (ipPartEnd != ipPartBegin) + { + allSame = false; + ipRangeEndList.append(ipPartEnd); + } + else if (!allSame) + { + ipRangeEndList.append(ipPartEnd); + } + } + + if (!ipRangeEndList.isEmpty()) + { + ipRange += '~'; + ipRange += ipRangeEndList.join('.'); + } + + return ipRange; +} + +QString IpRangeEdit::toString() +{ + QString ipRange = ""; + + for ( int i = 0; i < QTUTL_IP_SIZE; ++i ) + { + if ( i != 0 ) + { + ipRange += (i == QTUTL_IP_RANGE_SEPARATOR) ? "-" : "."; + } + + ipRange += m_pLineEdit[i]->text(); + } + + return ipRange; +} + +void IpRangeEdit::setFromString(QString ipRange) +{ + QStringList sections = ipRange.trimmed().split(QRegExp("[^0-9\\.]")); + int count = sections.count(); + + if (count >= 1) + { + QStringList beginParts = sections.at(0).split(QRegExp("[^0-9]")); + + for (int i=0; i= 2) + { + QStringList endParts = sections.at(1).split(QRegExp("[^0-9]")); + + for (int i=QTUTL_IP_SIZE-1; i>=QTUTL_IP_RANGE_SEPARATOR && !endParts.isEmpty(); i--) + { + QString ipPart = endParts.back(); + endParts.pop_back(); + setValue(i, ipPart); + } + } +} + +QString IpRangeEdit::value(int section) +{ + if (section >= QTUTL_IP_SIZE) + { + throw QString("IpRangeEdit::value(): section %1 out of bounds!").arg(section); + } + + return m_pLineEdit[section]->text(); +} + +void IpRangeEdit::setValue(int section, QString value) +{ + if (section >= QTUTL_IP_SIZE) + { + throw QString("IpRangeEdit::value(): section %1 out of bounds!").arg(section); + } + + m_pLineEdit[section]->setText(regularIpNumber(value)); + + if (section < QTUTL_IP_RANGE_SEPARATOR) + { + m_pLineEdit[section + QTUTL_IP_RANGE_SEPARATOR]->setPlaceholderText(m_pLineEdit[section]->text()); + } +} + +bool IpRangeEdit::eventFilter(QObject *obj, QEvent *event) +{ + bool bRes = QFrame::eventFilter(obj, event); + + if ( event->type() == QEvent::KeyPress ) + { + QKeyEvent* pEvent = dynamic_cast( event ); + if ( pEvent ) + { + for ( unsigned int i = 0; i < QTUTL_IP_SIZE; ++i ) + { + QLineEdit* pEdit = m_pLineEdit[i]; + if ( pEdit == obj ) + { + switch ( pEvent->key() ) + { + case Qt::Key_Left: + if ( pEdit->cursorPosition() == 0 ) + { + // user wants to move to previous item + MovePrevLineEdit(i); + } + break; + + case Qt::Key_Right: + if ( pEdit->text().isEmpty() || (pEdit->text().size() == pEdit->cursorPosition()) ) + { + // user wants to move to next item + MoveNextLineEdit(i); + } + break; + + case Qt::Key_0: + if ( pEdit->text().isEmpty() || pEdit->text() == "0" ) + { + pEdit->setText("0"); + // user wants to move to next item + MoveNextLineEdit(i); + } + emit signalTextChanged( pEdit ); + break; + + case Qt::Key_Backspace: + if ( pEdit->text().isEmpty() || pEdit->cursorPosition() == 0) + { + // user wants to move to previous item + MovePrevLineEdit(i); + } + break; + + case Qt::Key_Comma: + case Qt::Key_Period: + MoveNextLineEdit(i); + break; + + default: + emit signalTextChanged( pEdit ); + break; + + } + } + } + } + } + + return bRes; +} + +void IpRangeEdit::MoveNextLineEdit(int i) +{ + if ( i+1 < QTUTL_IP_SIZE ) + { + m_pLineEdit[i+1]->setFocus(); + m_pLineEdit[i+1]->setCursorPosition( 0 ); + m_pLineEdit[i+1]->selectAll(); + } +} + +void IpRangeEdit::MovePrevLineEdit(int i) +{ + if ( i != 0 ) + { + m_pLineEdit[i-1]->setFocus(); + m_pLineEdit[i-1]->setCursorPosition( m_pLineEdit[i-1]->text().size() ); + m_pLineEdit[i-1]->selectAll(); + } +} diff --git a/src/iprangeedit.h b/src/iprangeedit.h new file mode 100644 index 0000000..5d29c49 --- /dev/null +++ b/src/iprangeedit.h @@ -0,0 +1,59 @@ +#ifndef IPRANGEEDIT_H +#define IPRANGEEDIT_H + +#include +#include +#include +#include +#include +#include +#include +#include + +/*********************************************************************** + * Copied from https://stackoverflow.com/questions/9306335/an-ip-address-widget-for-qt-similar-to-mfcs-ip-address-control + * And edited by Yihao Peng + ***********************************************************************/ + +class IpRangeEdit : public QFrame +{ + Q_OBJECT + +public: + IpRangeEdit(QWidget *parent = 0); + virtual bool eventFilter( QObject *obj, QEvent *event ); + + bool isIpRangeValid(); + QString ipRange(); + QString beautifiedIpRange(); + + QString toString(); + void setFromString(QString ipRange); + + QString value(int section); + void setValue(int section, QString value); + +public slots: + void slotTextChanged( QLineEdit* pEdit ); + +signals: + void signalTextChanged( QLineEdit* pEdit ); + +protected: + QString regularIpNumber(const QString &ipNumber); + +private: + enum + { + QTUTL_IP_SIZE = 8,// число октетов IP адресе + QTUTL_IP_RANGE_SEPARATOR = 4, // IP range separator position + MAX_DIGITS = 3 // число символов в LineEdit + }; + + QLineEdit *m_pLineEdit[QTUTL_IP_SIZE]; + void MoveNextLineEdit (int i); + void MovePrevLineEdit (int i); +}; + + +#endif // IPRANGEEDIT_H diff --git a/src/iprangeedititem.cpp b/src/iprangeedititem.cpp new file mode 100644 index 0000000..fe5be20 --- /dev/null +++ b/src/iprangeedititem.cpp @@ -0,0 +1,46 @@ +#include "iprangeedititem.h" + +IpRangeEditItem::IpRangeEditItem(QListWidget *parent, int row) +{ + parent_ = parent; + + child_ = new QWidget(this); + layout_ = new QHBoxLayout(child_); + + enable_ = new QCheckBox(child_); + ipRangeEdit_ = new IpRangeEdit(child_); + + layout_->setMargin(1); + + layout_->addWidget(enable_); + layout_->addWidget(ipRangeEdit_); + + enable_->setChecked(true); + + child_->setLayout(layout_); + + setSizeHint(QSize(0, 24)); + + if (row < 0) + { + row = parent_->count(); + } + + parent_->insertItem(row, this); + parent_->setItemWidget(this, child_); +} + +bool IpRangeEditItem::isEnabled() +{ + return enable_->isChecked(); +} + +void IpRangeEditItem::setEnabled(bool enabled) +{ + enable_->setChecked(enabled); +} + +IpRangeEdit *IpRangeEditItem::edit() +{ + return ipRangeEdit_; +} diff --git a/src/iprangeedititem.h b/src/iprangeedititem.h new file mode 100644 index 0000000..d56d311 --- /dev/null +++ b/src/iprangeedititem.h @@ -0,0 +1,34 @@ +#ifndef IPRANGEEDITITEM_H +#define IPRANGEEDITITEM_H + +#include +#include +#include +#include +#include +#include +#include "iprangeedit.h" + +class IpRangeEditItem : public QWidget, public QListWidgetItem +{ + Q_OBJECT + +public: + IpRangeEditItem(QListWidget *parent, int row = -1); + + bool isEnabled(); + void setEnabled(bool enabled); + + IpRangeEdit *edit(); + +private: + QListWidget *parent_; + QWidget *child_; + QHBoxLayout *layout_; + + QCheckBox *enable_; + QLabel *descText_; + IpRangeEdit *ipRangeEdit_; +}; + +#endif // IPRANGEEDITITEM_H diff --git a/src/iprangelistitem.cpp b/src/iprangelistitem.cpp new file mode 100644 index 0000000..0af83ec --- /dev/null +++ b/src/iprangelistitem.cpp @@ -0,0 +1,127 @@ +#include +#include "iprangelistitem.h" +#include "utils.h" + +IpRangeListItem::IpRangeListItem(QListWidget *parent, int row, QWidget *editWindowParent) : + ipRangeWindow_(editWindowParent) +{ + parent_ = parent; + + child_ = new QWidget(this); + layout_ = new QHBoxLayout(child_); + + enabled_ = new QCheckBox(child_); + enabled_->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred); + + descText_ = new QLabel(child_); + + layout_->setMargin(1); + + layout_->addWidget(enabled_); + layout_->addWidget(descText_); + + enabled_->setChecked(true); + + child_->setLayout(layout_); + + setSizeHint(QSize(0, 24)); + + if (row < 0) + { + row = parent_->count(); + } + + parent_->insertItem(row, this); + parent_->setItemWidget(this, child_); +} + +bool IpRangeListItem::isEnabled() +{ + return enabled_->isChecked(); +} + +void IpRangeListItem::setEnabled(bool enabled) +{ + enabled_->setChecked(enabled); +} + +bool IpRangeListItem::editWithNewWindow() +{ + QString oldItemStr = ipRangeWindow_.toString(); + + // init IP range + if (ipRangeWindow_.ipRangeNum() <= 0) + { + ipRangeWindow_.addIpRange(Utils::getInitIpRange()); + } + + int result = ipRangeWindow_.exec(); + bool accepted = (result == QDialog::Accepted); + + if (!accepted) + { + ipRangeWindow_.setFromString(oldItemStr); + } + + syncDataAndDisplay(); + return accepted; +} + +QStringList IpRangeListItem::enabledIpRanges() +{ + return enabledIpRanges_; +} + +QString IpRangeListItem::toString() +{ + QString itemStr = ipRangeWindow_.toString(); + + if (!enabled_->isChecked()) + { + itemStr = QString("#") + itemStr; + } + + return itemStr; +} + +void IpRangeListItem::setFromString(QString str) +{ + bool isEnabled = str.at(0).toLatin1() != '#'; + + setEnabled(isEnabled); + + if (!isEnabled) + { + str = str.right(str.size() - 1); + } + + ipRangeWindow_.setFromString(str); + syncDataAndDisplay(); +} + +void IpRangeListItem::syncDataAndDisplay() +{ + // sync data + enabledIpRanges_ = ipRangeWindow_.enabledIpRanges(); + + // sync display + QString desc = ""; + + QString comment = ipRangeWindow_.comment().trimmed(); + + if (!comment.isEmpty()) + { + desc += comment + ": "; + } + + QString ipRanges = ipRangeWindow_.beautifiedEnabledIpRange().join(", "); + + if (ipRanges.isEmpty()) + { + ipRanges = tr("Empty", "There is no IP range of the item."); + } + + desc += ipRanges; + + descText_->setText(desc); +} diff --git a/src/iprangelistitem.h b/src/iprangelistitem.h new file mode 100644 index 0000000..f22a13f --- /dev/null +++ b/src/iprangelistitem.h @@ -0,0 +1,46 @@ +#ifndef IPRANGELISTITEM_H +#define IPRANGELISTITEM_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include "iprangewindow.h" + +class IpRangeListItem : public QWidget, public QListWidgetItem +{ + Q_OBJECT + +public: + IpRangeListItem(QListWidget *parent, int row = -1, QWidget *editWindowParent = nullptr); + + bool isEnabled(); + void setEnabled(bool enabled); + + bool editWithNewWindow(); + + QStringList enabledIpRanges(); + + QString toString(); + void setFromString(QString ipRangeStr); + +protected: + void syncDataAndDisplay(); + +private: + QListWidget *parent_; + QWidget *child_; + QHBoxLayout *layout_; + + QCheckBox *enabled_; + QLabel *descText_; + + IpRangeWindow ipRangeWindow_; + QStringList enabledIpRanges_; +}; + +#endif // IPRANGELISTITEM_H diff --git a/src/iprangewindow.cpp b/src/iprangewindow.cpp new file mode 100644 index 0000000..d1b2cde --- /dev/null +++ b/src/iprangewindow.cpp @@ -0,0 +1,233 @@ +#include +#include +#include "iprangewindow.h" +#include "ui_iprangewindow.h" +#include "utils.h" + +IpRangeWindow::IpRangeWindow(QWidget *parent) : + QDialog(parent), + ui_(new Ui::IpRangeWindow) +{ + ui_->setupUi(this); + + // hide the question button from the title bar. + setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); +} + +IpRangeWindow::~IpRangeWindow() +{ + delete ui_; +} + +QString IpRangeWindow::comment() +{ + return ui_->commentEdit->text(); +} + +void IpRangeWindow::setComment(const QString &comment) +{ + ui_->commentEdit->setText(comment); +} + +int IpRangeWindow::ipRangeNum() +{ + return ui_->ipRangeBox->count(); +} + +void IpRangeWindow::accept() +{ + int count = ui_->ipRangeBox->count(); + + //********** allow empty ********** + /* + if (count <= 0) + { + QMessageBox::information(this, tr("IP Range is Empty"), tr("Please add at least one IP range.")); + return; + } + */ + + for (int row=0; row(ui_->ipRangeBox->item(row)); + + if (!item->edit()->isIpRangeValid()) + { + QMessageBox::information(this, tr("IP Range is not Complete"), tr("Please fill all parts of the IP range.")); + return; + } + } + + QDialog::accept(); +} + +QStringList IpRangeWindow::enabledIpRanges() +{ + QStringList enabledIpRange; + + int count = ui_->ipRangeBox->count(); + + for (int row=0; row(ui_->ipRangeBox->item(row)); + + if (item->isEnabled()) + { + enabledIpRange.append(item->edit()->ipRange()); + } + } + + return enabledIpRange; +} + +QStringList IpRangeWindow::beautifiedEnabledIpRange() +{ + QStringList enabledIpRange; + + int count = ui_->ipRangeBox->count(); + + for (int row=0; row(ui_->ipRangeBox->item(row)); + + if (item->isEnabled()) + { + enabledIpRange.append(item->edit()->beautifiedIpRange()); + } + } + + return enabledIpRange; +} + +void IpRangeWindow::addIpRange(const QString &ipRange, bool enabled) +{ + addIpRange(nullptr, ipRange, enabled); +} + +void IpRangeWindow::clearIpRange() +{ + ui_->ipRangeBox->clear(); +} + +QString IpRangeWindow::toString() +{ + QStringList ipRangeList; + + int count = ui_->ipRangeBox->count(); + + for (int row=0; row(ui_->ipRangeBox->item(row)); + + QString ipRangeStr = item->edit()->toString(); + + if (!item->isEnabled()) + { + ipRangeStr = QString("!") + ipRangeStr; + } + + ipRangeList.append(ipRangeStr); + } + + // comment cannot contains ':' + QString commentStr = comment().replace(':', ' '); + + return commentStr + ":" + ipRangeList.join(","); +} + +void IpRangeWindow::setFromString(QString str) +{ + int pos = str.indexOf(':'); + + if (pos < 0) + { + return; + } + + setComment(str.left(pos)); + clearIpRange(); + + QString ipRangeStr = str.right(str.size() - pos - 1); + QStringList ipRangeList = ipRangeStr.split(','); + + foreach (QString range, ipRangeList) + { + if (range.isEmpty()) + { + continue; + } + + bool enabled = range.at(0).toLatin1() != '!'; + + if (!enabled) + { + range = range.right(range.size() - 1); + } + + addIpRange(range, enabled); + } +} + +void IpRangeWindow::addIpRange(IpRangeEditItem *position, const QString &ipRange, bool enabled) +{ + int row = ui_->ipRangeBox->count(); + + if (position != nullptr) + { + row = ui_->ipRangeBox->row(position) + 1; + } + + auto item = new IpRangeEditItem(ui_->ipRangeBox, row); + + item->setEnabled(enabled); + + if (!ipRange.isEmpty()) + { + item->edit()->setFromString(ipRange); + } +} + +void IpRangeWindow::removeIpRange(IpRangeEditItem *ipRange) +{ + ui_->ipRangeBox->removeItemWidget(ipRange); + delete ipRange; +} + +void IpRangeWindow::on_addIpRangeButton_clicked() +{ + auto selectedIpRange = ui_->ipRangeBox->selectedItems(); + IpRangeEditItem *position = nullptr; + + if (!selectedIpRange.isEmpty()) + { + position = dynamic_cast(selectedIpRange.first()); + } + + addIpRange(position, Utils::getInitIpRange()); +} + +void IpRangeWindow::on_removeIpRangeButton_clicked() +{ + auto selectedIpRange = ui_->ipRangeBox->selectedItems(); + + if (!selectedIpRange.isEmpty()) + { + removeIpRange(dynamic_cast(selectedIpRange.first())); + } + else + { + removeIpRange(dynamic_cast(ui_->ipRangeBox->item(ui_->ipRangeBox->count() - 1))); + } +} + +void IpRangeWindow::on_ipRangeEnableAll_clicked() +{ + bool enabled = ui_->ipRangeEnableAll->isChecked(); + int size = ui_->ipRangeBox->count(); + + for (int i=0; i(ui_->ipRangeBox->item(i)); + item->setEnabled(enabled); + } +} diff --git a/src/iprangewindow.h b/src/iprangewindow.h new file mode 100644 index 0000000..0875aba --- /dev/null +++ b/src/iprangewindow.h @@ -0,0 +1,51 @@ +#ifndef IPRANGWINDOW_H +#define IPRANGWINDOW_H + +#include +#include +#include "iprangeedititem.h" + +namespace Ui { +class IpRangeWindow; +} + +class IpRangeWindow : public QDialog +{ + Q_OBJECT + +public: + explicit IpRangeWindow(QWidget *parent = 0); + ~IpRangeWindow(); + + QString comment(); + void setComment(const QString &comment); + + int ipRangeNum(); + QStringList enabledIpRanges(); + QStringList beautifiedEnabledIpRange(); + void addIpRange(const QString &ipRange = "", bool enabled = true); + void clearIpRange(); + + QString toString(); + void setFromString(QString str); + +public slots: + void accept(); + +protected: + // add & remove ip range + void addIpRange(IpRangeEditItem *position = nullptr, const QString &ipRange = "", bool enabled = true); + void removeIpRange(IpRangeEditItem *ipRange); + +private slots: + void on_addIpRangeButton_clicked(); + + void on_removeIpRangeButton_clicked(); + + void on_ipRangeEnableAll_clicked(); + +private: + Ui::IpRangeWindow *ui_; +}; + +#endif // IPRANGWINDOW_H diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..5c56b02 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,186 @@ +#include +#include "config.h" +#include "mainwindow.h" +#include +#include +#include +#include +#include +#include +#include +#include "utils.h" + +using namespace std; + +// load lua script from QT resource +bool scriptLoader(const string &name, string &content, string &errmsg) +{ + QString path(name.c_str()); + path.replace('.', '/'); + + // Use the file in the current directory in debug mode, + // otherwise use the file in the Qt resource. + path = QString("%1/lua/scripts/%2.lua").arg(Utils::debugMode() ? "." : ":").arg(path); + QFile file(path); + + if (file.exists()) + { + if (file.open(QIODevice::ReadOnly)) { + content = file.readAll().toStdString(); + return true; + } + else + { + errmsg = string(" resource failed: ") + file.errorString().toUtf8().data(); + return false; + } + } + else + { + errmsg = string(" resource not found: ") + path.toUtf8().data(); + return false; + } +} + +int main(int argc, char *argv[]) +{ + try + { + // --------------- parse command line args --------------- + + // command line args + QCommandLineOption argDebug("debug", "Enable debug mode"); + QCommandLineOption argNoScaling("no-scaling", "Disable high-DPI scaling for UI"); + QCommandLineOption argLangCode("lang", "Language code for UI", "lang"); + + QStringList argList; + int argPos; + + for (argPos=0; argPos +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "mainwindow.h" +#include "ui_mainwindow.h" +#include "iprangeedit.h" +#include "iprangelistitem.h" +#include "iprangewindow.h" +#include "checkmessagebox.h" +#include "utils.h" +#include "config.h" + +using namespace std; +using namespace btctools::miner; +using namespace btctools::utils; + +MainWindow::MainWindow(QWidget *parent) : + QMainWindow(parent), + ui_(new Ui::MainWindow), + settingWindow_(this), + minerPasswordList_(DEFAULT_MINER_PASSWORDS), + scanner_(minerPasswordList_), + configurator_(minerPasswordList_), + rebooter_(minerPasswordList_), + upgrader_(minerPasswordList_), + onlySuccessMiners_(false), + monitorRunning_(false), + openMinerCPWithPassword_(OPEN_MINER_CP_WITH_PASSWORD) +{ + ui_->setupUi(this); + + // set version name + this->setWindowTitle(Utils::debugMode() ? APP_FULL_NAME_DEBUG : APP_FULL_NAME); + + // set data model + minerModel_ = new MinerTableModel; + ui_->minersTable->setModel(minerModel_); + + //******* set columns' width of minersTable ******* + // elapsed + ui_->minersTable->setColumnWidth(MinerTableModel::COL_ELAPSED, 150); + // pools + ui_->minersTable->setColumnWidth(MinerTableModel::COL_POOL1, 160); + ui_->minersTable->setColumnWidth(MinerTableModel::COL_POOL2, 160); + ui_->minersTable->setColumnWidth(MinerTableModel::COL_POOL3, 160); + // workers + ui_->minersTable->setColumnWidth(MinerTableModel::COL_WORKER1, 140); + ui_->minersTable->setColumnWidth(MinerTableModel::COL_WORKER2, 140); + ui_->minersTable->setColumnWidth(MinerTableModel::COL_WORKER3, 140); + // network type + ui_->minersTable->setColumnWidth(MinerTableModel::COL_NETWORK, 80); + // mac address + ui_->minersTable->setColumnWidth(MinerTableModel::COL_MAC_ADDRESS, 140); + + // set header height + ui_->minersTable->horizontalHeader()->setFixedHeight(35); + // set default row height + //ui_->minersTable->verticalHeader()->setDefaultSectionSize(30); + + // init table sort + ui_->minersTable->sortByColumn(0, Qt::AscendingOrder); + + // allow swapping columns + ui_->minersTable->horizontalHeader()->setSectionsMovable(true); + + // context menu + initContextMenu(); + + // set button status + setAppStatus(AppStatus::NOT_SCAN); + + //----------------------- connect singals & slots ----------------------------- + // scanner + connect(&scanner_, SIGNAL(minerReporter(btctools::miner::Miner)), this, SLOT(onReceivedMiner(btctools::miner::Miner))); + + connect(&scanner_, SIGNAL(scanBegin()), this, SLOT(onActionBegin())); + connect(&scanner_, SIGNAL(scanProgress(int)), this, SLOT(onActionProgress(int))); + connect(&scanner_, SIGNAL(scanEnd()), this, SLOT(onActionEnd())); + + connect(&scanner_, SIGNAL(scanBegin()), this, SLOT(onScanBegin())); + connect(&scanner_, SIGNAL(scanEnd()), this, SLOT(onScanEnd())); + + connect(&scanner_, SIGNAL(scanException(std::exception)), this, SLOT(onReceivedException(std::exception))); + + // configurator + connect(&configurator_, SIGNAL(minerReporter(btctools::miner::Miner)), this, SLOT(onReceivedMiner(btctools::miner::Miner))); + + connect(&configurator_, SIGNAL(configBegin()), this, SLOT(onActionBegin())); + connect(&configurator_, SIGNAL(configProgress(int)), this, SLOT(onActionProgress(int))); + connect(&configurator_, SIGNAL(configEnd(bool)), this, SLOT(onActionEnd())); + + connect(&configurator_, SIGNAL(configBegin()), this, SLOT(onConfigBegin())); + connect(&configurator_, SIGNAL(configEnd(bool)), this, SLOT(onConfigEnd(bool))); + + connect(&configurator_, SIGNAL(configException(std::exception)), this, SLOT(onReceivedException(std::exception))); + + // rebooter + connect(&rebooter_, SIGNAL(minerReporter(btctools::miner::Miner)), this, SLOT(onReceivedMiner(btctools::miner::Miner))); + + connect(&rebooter_, SIGNAL(rebootBegin()), this, SLOT(onActionBegin())); + connect(&rebooter_, SIGNAL(rebootProgress(int)), this, SLOT(onActionProgress(int))); + connect(&rebooter_, SIGNAL(rebootEnd(bool)), this, SLOT(onActionEnd())); + + connect(&rebooter_, SIGNAL(rebootBegin()), this, SLOT(onRebootBegin())); + connect(&rebooter_, SIGNAL(rebootEnd(bool)), this, SLOT(onRebootEnd(bool))); + + connect(&rebooter_, SIGNAL(rebootException(std::exception)), this, SLOT(onReceivedException(std::exception))); + + // upgrader + connect(&upgrader_, SIGNAL(minerReporter(btctools::miner::Miner)), this, SLOT(onReceivedMiner(btctools::miner::Miner))); + + connect(&upgrader_, SIGNAL(upgradeBegin()), this, SLOT(onActionBegin())); + connect(&upgrader_, SIGNAL(upgradeProgress(int)), this, SLOT(onActionProgress(int))); + connect(&upgrader_, SIGNAL(upgradeEnd(bool)), this, SLOT(onActionEnd())); + + connect(&upgrader_, SIGNAL(upgradeBegin()), this, SLOT(onUpgradeBegin())); + connect(&upgrader_, SIGNAL(upgradeEnd(bool)), this, SLOT(onUpgradeEnd(bool))); + + connect(&upgrader_, SIGNAL(upgradeException(std::exception)), this, SLOT(onReceivedException(std::exception))); + + // auto updater + connect(&autoUpdater_, SIGNAL(findUpdates(QString, QString, QString, bool)), this, SLOT(onFindUpdates(QString, QString, QString, bool))); + + // UpgradeWindow + connect(&upgradeWindow_, SIGNAL(selectedMinerModelChanged(QString)), this, SLOT(onUpgradeWindowSelectedMinerModelChanged(QString))); + connect(&upgradeWindow_, SIGNAL(upgradeSelectedMiners()), this, SLOT(onUpgradeSelectedMiners())); + connect(&upgradeWindow_, SIGNAL(upgradeAllMiners()), this, SLOT(onUpgradeAllMiners())); + + + //----------------------- load UI config ----------------------------- + loadUiConfig(); + + //----------------------- run autoupdater ----------------------------- + autoUpdater_.start(); +} + +MainWindow::~MainWindow() +{ + delete ui_; + delete minerModel_; +} + +bool MainWindow::checkOnePoolAndNotice(int id, QLineEdit *poolUrl, QLineEdit *poolWorker) +{ + if (poolUrl->text().isEmpty()) + { + auto button = QMessageBox::information(this, tr("Pool url is empty"), tr("Your pool %1's url is empty, are you sure?").arg(id), + QMessageBox::Yes | QMessageBox::No, QMessageBox::No); + + if (button == QMessageBox::No) + { + return false; + } + } + + if (poolWorker->text().isEmpty()) + { + auto button = QMessageBox::information(this, tr("SubAccount is empty"), tr("Your pool %1's sub-account is empty, are you sure?").arg(id), + QMessageBox::Yes | QMessageBox::No, QMessageBox::No); + + if (button == QMessageBox::No) + { + return false; + } + } + + return true; +} + +void MainWindow::updateSelectedMinerItems() +{ + selectedMinerItems_.clear(); + QModelIndexList selectedMinersIndex = ui_->minersTable->selectionModel()->selectedIndexes(); + QList rowList; + + foreach (QModelIndex index, selectedMinersIndex) + { + if (!rowList.contains(index.row())) + { + rowList.append(index.row()); + const MinerItem *item = &(minerModel_->getMiner(index.row())); + selectedMinerItems_.append(item); + } + } +} + +void MainWindow::setAppStatus(AppStatus appStatus) +{ + ButtonStatus scanButton, monitorButton, + configAllButton, configSelButton, + rebootAllButton, rebootSelButton, + upgradeButton; + + appStatus_ = appStatus; + + switch (appStatus) + { + case AppStatus::NOT_SCAN: + scanButton = monitorButton = ButtonStatus::ENABLED; + configAllButton = + configSelButton = + rebootAllButton = + rebootSelButton = + upgradeButton = ButtonStatus::DISABLED; + break; + case AppStatus::SCANNING: + scanButton = ButtonStatus::STOPABLED; + monitorButton = + configAllButton = + configSelButton = + rebootAllButton = + rebootSelButton = + upgradeButton = ButtonStatus::DISABLED; + break; + case AppStatus::MONITORING: + monitorButton = ButtonStatus::STOPABLED; + scanButton = + configAllButton = + configSelButton = + rebootAllButton = + rebootSelButton = + upgradeButton = ButtonStatus::DISABLED; + break; + case AppStatus::CONFIGURATING_ALL: + configAllButton = ButtonStatus::STOPABLED; + scanButton = + monitorButton = + configSelButton = + rebootAllButton = + rebootSelButton = + upgradeButton = ButtonStatus::DISABLED; + break; + case AppStatus::CONFIGURATING_SEL: + configSelButton = ButtonStatus::STOPABLED; + scanButton = + monitorButton = + configAllButton = + rebootAllButton = + rebootSelButton = + upgradeButton = ButtonStatus::DISABLED; + break; + case AppStatus::REBOOTING_ALL: + rebootAllButton = ButtonStatus::STOPABLED; + scanButton = + monitorButton = + configAllButton = + configSelButton = + rebootSelButton = + upgradeButton = ButtonStatus::DISABLED; + break; + case AppStatus::REBOOTING_SEL: + rebootSelButton = ButtonStatus::STOPABLED; + scanButton = + monitorButton = + configAllButton = + configSelButton = + rebootAllButton = + upgradeButton = ButtonStatus::DISABLED; + break; + case AppStatus::UPGRADING_ALL: + case AppStatus::UPGRADING_SEL: + upgradeButton = ButtonStatus::STOPABLED; + scanButton = + monitorButton = + configAllButton = + configSelButton = + rebootAllButton = + rebootSelButton = ButtonStatus::DISABLED; + break; + // all buttons disable on prepare + case AppStatus::SCAN_PREPARE: + case AppStatus::MONITOR_PREPARE: + case AppStatus::CONFIG_ALL_PREPARE: + case AppStatus::CONFIG_SEL_PREPARE: + case AppStatus::REBOOT_ALL_PREPARE: + case AppStatus::REBOOT_SEL_PREPARE: + case AppStatus::UPGRADE_ALL_PREPARE: + case AppStatus::UPGRADE_SEL_PREPARE: + scanButton = + monitorButton = + configAllButton = + configSelButton = + rebootAllButton = + rebootSelButton = + upgradeButton = ButtonStatus::PREPARE; + break; + // all buttons enabled after the finish + case AppStatus::SCAN_FINISH: + case AppStatus::MONITOR_FINISH: + case AppStatus::REBOOT_FINISH: + case AppStatus::CONFIG_FINISH: + case AppStatus::UPGRADE_FINISH: + scanButton = + monitorButton = + configAllButton = + configSelButton = + rebootAllButton = + rebootSelButton = + upgradeButton = ButtonStatus::ENABLED; + break; + } + + + // change button status + + if (scanButton == ButtonStatus::PREPARE) + { + ui_->scanMinersButton->setEnabled(false); + } + else if (scanButton == ButtonStatus::STOPABLED) + { + ui_->scanMinersButton->setEnabled(true); + ui_->scanMinersButton->setText(tr("Stop Scan")); + } + else + { + ui_->scanMinersButton->setEnabled(scanButton == ButtonStatus::ENABLED); + ui_->scanMinersButton->setText(tr("Scan")); + } + + if (monitorButton == ButtonStatus::PREPARE) + { + ui_->monitorMinersButton->setEnabled(false); + } + else if (monitorButton == ButtonStatus::STOPABLED) + { + ui_->monitorMinersButton->setEnabled(true); + ui_->monitorMinersButton->setText(tr("Stop Monitor")); + } + else + { + ui_->monitorMinersButton->setEnabled(monitorButton == ButtonStatus::ENABLED); + ui_->monitorMinersButton->setText(tr("Monitor")); + } + + if (configAllButton == ButtonStatus::PREPARE) + { + ui_->configAllMinersButton->setEnabled(false); + } + else if (configAllButton == ButtonStatus::STOPABLED) + { + ui_->configAllMinersButton->setEnabled(true); + ui_->configAllMinersButton->setText(tr("Stop Config")); + } + else + { + ui_->configAllMinersButton->setEnabled(configAllButton == ButtonStatus::ENABLED); + ui_->configAllMinersButton->setText(tr("Config All")); + } + + if (configSelButton == ButtonStatus::PREPARE) + { + ui_->configSelectedMinersButton->setEnabled(false); + } + else if (configSelButton == ButtonStatus::STOPABLED) + { + ui_->configSelectedMinersButton->setEnabled(true); + ui_->configSelectedMinersButton->setText(tr("Stop Config")); + } + else + { + ui_->configSelectedMinersButton->setEnabled(configSelButton == ButtonStatus::ENABLED); + ui_->configSelectedMinersButton->setText(tr("Config Selected")); + } + + if (rebootAllButton == ButtonStatus::PREPARE) + { + ui_->rebootAllMinersButton->setEnabled(false); + } + else if (rebootAllButton == ButtonStatus::STOPABLED) + { + ui_->rebootAllMinersButton->setEnabled(true); + ui_->rebootAllMinersButton->setText(tr("Stop Reboot")); + } + else + { + ui_->rebootAllMinersButton->setEnabled(rebootAllButton == ButtonStatus::ENABLED); + ui_->rebootAllMinersButton->setText(tr("Reboot All")); + } + + if (rebootSelButton == ButtonStatus::PREPARE) + { + ui_->rebootSelectedMinersButton->setEnabled(false); + } + else if (rebootSelButton == ButtonStatus::STOPABLED) + { + ui_->rebootSelectedMinersButton->setEnabled(true); + ui_->rebootSelectedMinersButton->setText(tr("Stop Reboot")); + } + else + { + ui_->rebootSelectedMinersButton->setEnabled(rebootSelButton == ButtonStatus::ENABLED); + ui_->rebootSelectedMinersButton->setText(tr("Reboot Selected")); + } + + if (upgradeButton == ButtonStatus::PREPARE) + { + ui_->firmwareUpgradeButton->setEnabled(false); + } + else if (upgradeButton == ButtonStatus::STOPABLED) + { + ui_->firmwareUpgradeButton->setEnabled(true); + ui_->firmwareUpgradeButton->setText(tr("Stop Upgrade")); + } + else + { + ui_->firmwareUpgradeButton->setEnabled(upgradeButton == ButtonStatus::ENABLED); + ui_->firmwareUpgradeButton->setText(tr("Firmware Upgrade")); + } +} + +AppStatus MainWindow::getAppStatus() +{ + return appStatus_; +} + +void MainWindow::waitMonitorRefresh(int timeout) +{ + if (!monitorRunning_) { + return; + } + + if (timeout > 0) + { + ui_->actionProgress->setFormat(tr("%p% - Refresh after %1 seconds. Miner status count: ").arg(timeout) + getMinerCountStr(false)); + + // delay n seconds to restart, avoiding refresh too fast + QTimer::singleShot(1000, this, [this, timeout](){ + waitMonitorRefresh(timeout - 1); + }); + } + else + { + // The scanning has taken seconds. Refresh now. + onMonitorRefresh(); + } +} + +void MainWindow::loadUiConfig() +{ + QSettings conf(UI_CONFIG_FILE_PATH, QSettings::IniFormat); + + ui_->onlySuccessMiners->setChecked(conf.value("ui/onlySuccessMiners", ui_->onlySuccessMiners->isChecked()).toBool()); + + ui_->pool1Enabled->setChecked(conf.value("ui/pool1Enabled", ui_->pool1Enabled->isChecked()).toBool()); + ui_->pool2Enabled->setChecked(conf.value("ui/pool2Enabled", ui_->pool2Enabled->isChecked()).toBool()); + ui_->pool3Enabled->setChecked(conf.value("ui/pool3Enabled", ui_->pool3Enabled->isChecked()).toBool()); + + ui_->pool1Url->setText(conf.value("ui/pool1Url", ui_->pool1Url->text()).toString()); + ui_->pool2Url->setText(conf.value("ui/pool2Url", ui_->pool2Url->text()).toString()); + ui_->pool3Url->setText(conf.value("ui/pool3Url", ui_->pool3Url->text()).toString()); + + ui_->pool1Worker->setText(conf.value("ui/pool1Worker", ui_->pool1Worker->text()).toString()); + ui_->pool2Worker->setText(conf.value("ui/pool2Worker", ui_->pool2Worker->text()).toString()); + ui_->pool3Worker->setText(conf.value("ui/pool3Worker", ui_->pool3Worker->text()).toString()); + + ui_->pool1Pwd->setText(conf.value("ui/pool1Pwd", ui_->pool1Pwd->text()).toString()); + ui_->pool2Pwd->setText(conf.value("ui/pool2Pwd", ui_->pool2Pwd->text()).toString()); + ui_->pool3Pwd->setText(conf.value("ui/pool3Pwd", ui_->pool3Pwd->text()).toString()); + + ui_->pool1PostfixIp->setChecked(conf.value("ui/pool1PostfixIp", ui_->pool1PostfixIp->isChecked()).toBool()); + ui_->pool2PostfixIp->setChecked(conf.value("ui/pool2PostfixIp", ui_->pool2PostfixIp->isChecked()).toBool()); + ui_->pool3PostfixIp->setChecked(conf.value("ui/pool3PostfixIp", ui_->pool3PostfixIp->isChecked()).toBool()); + + ui_->pool1PostfixNoChange->setChecked(conf.value("ui/pool1PostfixNoChange", ui_->pool1PostfixNoChange->isChecked()).toBool()); + ui_->pool2PostfixNoChange->setChecked(conf.value("ui/pool2PostfixNoChange", ui_->pool2PostfixNoChange->isChecked()).toBool()); + ui_->pool3PostfixNoChange->setChecked(conf.value("ui/pool3PostfixNoChange", ui_->pool3PostfixNoChange->isChecked()).toBool()); + + ui_->pool1PostfixNone->setChecked(conf.value("ui/pool1PostfixNone", ui_->pool1PostfixNone->isChecked()).toBool()); + ui_->pool2PostfixNone->setChecked(conf.value("ui/pool2PostfixNone", ui_->pool2PostfixNone->isChecked()).toBool()); + ui_->pool3PostfixNone->setChecked(conf.value("ui/pool3PostfixNone", ui_->pool3PostfixNone->isChecked()).toBool()); + + ui_->powerControlEnabled->setChecked(conf.value("ui/powerControlEnabled", ui_->powerControlEnabled->isChecked()).toBool()); + ui_->asicBoost->setChecked(conf.value("ui/asicBoost", ui_->asicBoost->isChecked()).toBool()); + ui_->lowPowerMode->setChecked(conf.value("ui/lowPowerMode", ui_->lowPowerMode->isChecked()).toBool()); + ui_->economicMode->setChecked(conf.value("ui/economicMode", ui_->economicMode->isChecked()).toBool()); + ui_->overclockEnabled->setChecked(conf.value("ui/overclockEnabled", ui_->overclockEnabled->isChecked()).toBool()); + + // load ip ranges + setIpRangeGroups(conf.value("ui/ipRangeGroups").toString()); + + // auto import IP ranges if it's empty + if (ui_->ipRangeBox->count() == 0) + { + on_importLanRange_clicked(); + } + + // monitor interval + settingWindow_.setMonitorInterval(conf.value("monitor/interval", MONITOR_INTERVAL).toInt()); + // scan session timeout + scanner_.setSessionTimeout(conf.value("scanner/sessionTimeout", scanner_.sessionTimeout()).toInt()); + // config & reboot timeout + configurator_.setSessionTimeout(conf.value("configurator/sessionTimeout", configurator_.sessionTimeout()).toInt()); + rebooter_.setSessionTimeout(configurator_.sessionTimeout()); + // upgrade timeout + upgrader_.setSessionTimeout(conf.value("upgrader/sessionTimeout", upgrader_.sessionTimeout()).toInt()); + + // scan step size + scanner_.setStepSize(conf.value("scanner/stepSize", scanner_.stepSize()).toInt()); + // config, reboot & upgrade stepSize + configurator_.setStepSize(conf.value("configurator/stepSize", configurator_.stepSize()).toInt()); + rebooter_.setStepSize(configurator_.stepSize()); + upgrader_.setStepSize(configurator_.stepSize()); + // upgrade stepSize when sending firmware + upgrader_.setSendFirmwareStepSize(conf.value("upgrader/sendFirmwareStepSize", upgrader_.sendFirmwareStepSize()).toInt()); + + // auto retry times + settingWindow_.setAutoRetryTimes(conf.value("scanner/autoRetryTimes", AUTO_RETRY_TIMES).toInt()); + scanner_.setAutoRetryTimes(settingWindow_.autoRetryTimes()); + configurator_.setAutoRetryTimes(settingWindow_.autoRetryTimes()); + rebooter_.setAutoRetryTimes(settingWindow_.autoRetryTimes()); + upgrader_.setAutoRetryTimes(settingWindow_.autoRetryTimes()); + + // firmwares + upgradeWindow_.setFirmwareMap(Utils::stringToFirmwareMap(conf.value("upgrader/firmwares", + Utils::firmwareMapToString(upgradeWindow_.getFirmwareMap())).toString())); + + // ip parts for worker name + configurator_.setWorkerNameIpParts(conf.value("configurator/workerNameIpParts", configurator_.workerNameIpParts()).toInt()); + + // highlight options + minerModel_->setIsHighlightTemperature(conf.value("highlight/isHighlightTemperature", minerModel_->isHighlightTemperature()).toBool()); + minerModel_->setHighlightTemperatureMoreThan(conf.value("highlight/highlightTemperatureMoreThan", minerModel_->highlightTemperatureMoreThan()).toInt()); + minerModel_->setHighlightTemperatureLessThan(conf.value("highlight/highlightTemperatureLessThan", minerModel_->highlightTemperatureLessThan()).toInt()); + minerModel_->setIsHighlightTemperature(conf.value("highlight/isHighlightWrongWorkerName", minerModel_->isHighlightWrongWorkerName()).toBool()); + minerModel_->setIsHighlightTemperature(conf.value("highlight/isHighlightLowHashrate", minerModel_->isHighlightLowHashrate()).toBool()); + minerModel_->setHighlightLowHashrates(Utils::stringToMinerHashrateList(conf.value("highlight/highlightLowHashrates", + Utils::minerHashrateListToString(minerModel_->highlightLowHashrates())).toString())); + + // miner password + /*minerPasswordList_ = Utils::stringToMinerPasswordList(conf.value("login/minerPasswords", + Utils::minerPasswordListToString(minerPasswordList_)).toString());*/ + + // others + openMinerCPWithPassword_ = conf.value("feature/openMinerCPWithPassword", openMinerCPWithPassword_).toBool(); + + // reset some config by version + int appVersionId = conf.value("core/appVersionId", APP_VERSION_ID).toInt(); + if (appVersionId < 30) { + // Reduce configuration timeout to reduce the time required to configure some new miner firmwares + configurator_.setSessionTimeout(CONFIG_SESSION_TIMEOUT); + rebooter_.setSessionTimeout(CONFIG_SESSION_TIMEOUT); + } +} + +void MainWindow::saveUiConfig() +{ + QSettings conf(UI_CONFIG_FILE_PATH, QSettings::IniFormat); + + conf.setValue("core/appVersionId", APP_VERSION_ID); + + conf.setValue("ui/onlySuccessMiners", ui_->onlySuccessMiners->isChecked()); + + conf.setValue("ui/pool1Enabled", ui_->pool1Enabled->isChecked()); + conf.setValue("ui/pool2Enabled", ui_->pool2Enabled->isChecked()); + conf.setValue("ui/pool3Enabled", ui_->pool3Enabled->isChecked()); + + conf.setValue("ui/pool1Url", ui_->pool1Url->text()); + conf.setValue("ui/pool2Url", ui_->pool2Url->text()); + conf.setValue("ui/pool3Url", ui_->pool3Url->text()); + + conf.setValue("ui/pool1Worker", ui_->pool1Worker->text()); + conf.setValue("ui/pool2Worker", ui_->pool2Worker->text()); + conf.setValue("ui/pool3Worker", ui_->pool3Worker->text()); + + conf.setValue("ui/pool1Pwd", ui_->pool1Pwd->text()); + conf.setValue("ui/pool2Pwd", ui_->pool2Pwd->text()); + conf.setValue("ui/pool3Pwd", ui_->pool3Pwd->text()); + + conf.setValue("ui/pool1PostfixIp", ui_->pool1PostfixIp->isChecked()); + conf.setValue("ui/pool2PostfixIp", ui_->pool2PostfixIp->isChecked()); + conf.setValue("ui/pool3PostfixIp", ui_->pool3PostfixIp->isChecked()); + + conf.setValue("ui/pool1PostfixNoChange", ui_->pool1PostfixNoChange->isChecked()); + conf.setValue("ui/pool2PostfixNoChange", ui_->pool2PostfixNoChange->isChecked()); + conf.setValue("ui/pool3PostfixNoChange", ui_->pool3PostfixNoChange->isChecked()); + + conf.setValue("ui/pool1PostfixNone", ui_->pool1PostfixNone->isChecked()); + conf.setValue("ui/pool2PostfixNone", ui_->pool2PostfixNone->isChecked()); + conf.setValue("ui/pool3PostfixNone", ui_->pool3PostfixNone->isChecked()); + + conf.setValue("ui/powerControlEnabled", ui_->powerControlEnabled->isChecked()); + conf.setValue("ui/asicBoost", ui_->asicBoost->isChecked()); + conf.setValue("ui/lowPowerMode", ui_->lowPowerMode->isChecked()); + conf.setValue("ui/economicMode", ui_->economicMode->isChecked()); + conf.setValue("ui/overclockEnabled", ui_->overclockEnabled->isChecked()); + + // save ip ranges + conf.setValue("ui/ipRangeGroups", getIpRangeGroups()); + + // monitor interval + conf.setValue("monitor/interval", settingWindow_.monitorInterval()); + + // session timeout + conf.setValue("scanner/sessionTimeout", scanner_.sessionTimeout()); + conf.setValue("configurator/sessionTimeout", configurator_.sessionTimeout()); + conf.setValue("upgrader/sessionTimeout", upgrader_.sessionTimeout()); + + // scan step size + conf.setValue("scanner/stepSize", scanner_.stepSize()); + // config, reboot & upgrade step size + conf.setValue("configurator/stepSize", upgrader_.stepSize()); + // upgrade step size when sending firmware + conf.setValue("upgrader/sendFirmwareStepSize", upgrader_.sendFirmwareStepSize()); + + // auto retry times + conf.setValue("scanner/autoRetryTimes", settingWindow_.autoRetryTimes()); + + // firmwares + conf.setValue("upgrader/firmwares", Utils::firmwareMapToString(upgradeWindow_.getFirmwareMap())); + + // ip parts for worker name + conf.setValue("configurator/workerNameIpParts", configurator_.workerNameIpParts()); + + // highlight options + conf.setValue("highlight/isHighlightTemperature", minerModel_->isHighlightTemperature()); + conf.setValue("highlight/highlightTemperatureMoreThan", minerModel_->highlightTemperatureMoreThan()); + conf.setValue("highlight/highlightTemperatureLessThan", minerModel_->highlightTemperatureLessThan()); + conf.setValue("highlight/isHighlightWrongWorkerName", minerModel_->isHighlightWrongWorkerName()); + conf.setValue("highlight/isHighlightLowHashrate", minerModel_->isHighlightLowHashrate()); + conf.setValue("highlight/highlightLowHashrates", Utils::minerHashrateListToString(minerModel_->highlightLowHashrates())); + + // miner password + conf.setValue("login/minerPasswords", "" /*Utils::minerPasswordListToString(minerPasswordList_)*/); + + // others + conf.setValue("feature/openMinerCPWithPassword", openMinerCPWithPassword_); +} + +void MainWindow::syncOptionsFromSettingWindow() +{ + scanner_.setSessionTimeout(settingWindow_.scanningTimeout()); + scanner_.setStepSize(settingWindow_.concurrentScanning()); + + configurator_.setSessionTimeout(settingWindow_.configuringTimeout()); + configurator_.setStepSize(settingWindow_.concurrentConfiguring()); + + rebooter_.setSessionTimeout(configurator_.sessionTimeout()); + rebooter_.setStepSize(configurator_.stepSize()); + + upgrader_.setSessionTimeout(settingWindow_.upgradingTimeout()); + upgrader_.setStepSize(configurator_.stepSize()); + upgrader_.setSendFirmwareStepSize(settingWindow_.concurrentUpgrading()); + + // ip parts for worker name + configurator_.setWorkerNameIpParts(settingWindow_.workerNameIpParts()); + + // auto retry times + scanner_.setAutoRetryTimes(settingWindow_.autoRetryTimes()); + configurator_.setAutoRetryTimes(settingWindow_.autoRetryTimes()); + rebooter_.setAutoRetryTimes(settingWindow_.autoRetryTimes()); + upgrader_.setAutoRetryTimes(settingWindow_.autoRetryTimes()); + + minerModel_->setIsHighlightTemperature(settingWindow_.isHighlightTemperature()); + minerModel_->setHighlightTemperatureMoreThan(settingWindow_.highlightTemperatureMoreThan()); + minerModel_->setHighlightTemperatureLessThan(settingWindow_.highlightTemperatureLessThan()); + + minerModel_->setIsHighlightLowHashrate(settingWindow_.isHighlightLowHashrate()); + minerModel_->setHighlightLowHashrates(settingWindow_.highlightLowHashrates()); + + minerModel_->setIsHighlightWrongWorkerName(settingWindow_.isHighlightWrongWorkerName()); + + minerPasswordList_ = settingWindow_.minerPasswords(); + + openMinerCPWithPassword_ = settingWindow_.isOpenMinerCPWithPassword(); +} + +void MainWindow::syncOptionsToSettingWindow() +{ + settingWindow_.setScanningTimeout(scanner_.sessionTimeout()); + settingWindow_.setConcurrentScanning(scanner_.stepSize()); + + settingWindow_.setConfiguringTimeout(configurator_.sessionTimeout()); + settingWindow_.setConcurrentConfiguring(configurator_.stepSize()); + + settingWindow_.setUpgradingTimeout(upgrader_.sessionTimeout()); + settingWindow_.setConcurrentUpgrading(upgrader_.sendFirmwareStepSize()); + + // ip parts for worker name + settingWindow_.setWorkerNameIpParts(configurator_.workerNameIpParts()); + + settingWindow_.setIsHighlightTemperature(minerModel_->isHighlightTemperature()); + settingWindow_.setHighlightTemperatureMoreThan(minerModel_->highlightTemperatureMoreThan()); + settingWindow_.setHighlightTemperatureLessThan(minerModel_->highlightTemperatureLessThan()); + + settingWindow_.setIsHighlightLowHashrate(minerModel_->isHighlightLowHashrate()); + settingWindow_.setHighlightLowHashrates(minerModel_->highlightLowHashrates()); + + settingWindow_.setIsHighlightWrongWorkerName(minerModel_->isHighlightWrongWorkerName()); + + settingWindow_.setMinerPasswords(minerPasswordList_); + + settingWindow_.setIsOpenMinerCPWithPassword(openMinerCPWithPassword_); +} + +void MainWindow::addIpRangeFromString(IpRangeListItem *position, QString ipRangeStr) +{ + ipRangeStr = ipRangeStr.trimmed(); + + if (ipRangeStr.isEmpty()) + { + return; + } + + int row = ui_->ipRangeBox->count(); + + if (position != nullptr) + { + row = ui_->ipRangeBox->row(position) + 1; + } + + auto item = new IpRangeListItem(ui_->ipRangeBox, row, this); + + item->setFromString(ipRangeStr); +} + +void MainWindow::addIpRangeWithNewWindow(IpRangeListItem *position, bool enabled) +{ + int row = ui_->ipRangeBox->count(); + + if (position != nullptr) + { + row = ui_->ipRangeBox->row(position) + 1; + } + + auto item = new IpRangeListItem(ui_->ipRangeBox, row, this); + + item->setEnabled(enabled); + bool accepted = item->editWithNewWindow(); + + if (!accepted) + { + removeIpRange(item); + } +} + +void MainWindow::removeIpRange(IpRangeListItem *ipRange) +{ + ui_->ipRangeBox->removeItemWidget(ipRange); + delete ipRange; +} + +btctools::utils::IpGeneratorGroup MainWindow::makeIpRangeGenerators() +{ + btctools::utils::IpGeneratorGroup ipg; + int size = ui_->ipRangeBox->count(); + + for (int i=0; i(ui_->ipRangeBox->item(i)); + + if (item->isEnabled()) + { + foreach (QString ipRange, item->enabledIpRanges()) + { + ipg.addIpRange(ipRange.toStdString()); + } + } + } + + return ipg; +} + +QString MainWindow::getIpRangeGroups() +{ + QString ipRanges = ""; + + int size = ui_->ipRangeBox->count(); + int separatorEnd = size - 1; + + for (int i=0; i(ui_->ipRangeBox->item(i)); + + ipRanges += item->toString(); + + if (i < separatorEnd) + { + ipRanges += ";"; + } + } + + return ipRanges; +} + +void MainWindow::setIpRangeGroups(QString ipRanges) +{ + QStringList rangeList = ipRanges.split(";"); + + for (QString range : rangeList) + { + addIpRangeFromString(nullptr, range); + } +} + +void MainWindow::closeEvent(QCloseEvent *) +{ + saveUiConfig(); +} + +QString MainWindow::getMinerCountStr(bool onlySelected) +{ + int allMinerNum = 0; + int successMinerNum = 0; + int okMinerNum = 0; + int skipMinerNum = 0; + int timeoutMinerNum = 0; + int rebootedMinerNum = 0; + int upgradedMinerNum = 0; + int otherMinerNum = 0; + + if (onlySelected) + { + allMinerNum = selectedMinerItems_.size(); + + foreach (const MinerItem *item, selectedMinerItems_) + { + const std::string &stat = item->miner_.stat_; + + if (stat == "success") + { + successMinerNum++; + } + else if (stat == "ok") + { + okMinerNum++; + } + else if (stat == "skip") + { + skipMinerNum++; + } + else if (stat == "timeout") + { + timeoutMinerNum++; + } + else if (stat == "rebooted") + { + rebootedMinerNum++; + } + else if (stat == "upgraded") + { + upgradedMinerNum++; + } + else + { + otherMinerNum++; + } + } + } + else + { + minerModel_->updateMinerNumCount(); + + allMinerNum = minerModel_->allMinerNum(); + successMinerNum = minerModel_->successMinerNum(); + okMinerNum = minerModel_->okMinerNum(); + skipMinerNum = minerModel_->skipMinerNum(); + timeoutMinerNum = minerModel_->timeoutMinerNum(); + rebootedMinerNum = minerModel_->rebootedMinerNum(); + upgradedMinerNum = minerModel_->upgradedMinerNum(); + otherMinerNum = minerModel_->otherMinerNum(); + } + + QStringList minerCount; + + minerCount.append(tr("all %1").arg(allMinerNum)); + + if (successMinerNum > 0) + { + minerCount.append(tr("success %1").arg(successMinerNum)); + } + + if (okMinerNum > 0) + { + minerCount.append(tr("ok %1").arg(okMinerNum)); + } + + if (skipMinerNum > 0) + { + minerCount.append(tr("skip %1").arg(skipMinerNum)); + } + + if (timeoutMinerNum > 0) + { + minerCount.append(tr("timeout %1").arg(timeoutMinerNum)); + } + + if (rebootedMinerNum > 0) + { + minerCount.append(tr("rebooted %1").arg(rebootedMinerNum)); + } + + if (upgradedMinerNum > 0) + { + minerCount.append(tr("upgraded %1").arg(upgradedMinerNum)); + } + + if (otherMinerNum > 0) + { + minerCount.append(tr("other %1").arg(otherMinerNum)); + } + + return minerCount.join(", "); +} + +bool MainWindow::checkPoolsAndNotice() +{ + if (!ui_->pool1Enabled->isChecked() && !ui_->pool2Enabled->isChecked() && !ui_->pool3Enabled->isChecked() && + !ui_->powerControlEnabled->isChecked() && !ui_->overclockEnabled->isChecked()) + { + QMessageBox::information(this, tr("Cannot configure miners"), tr("All configurations disabled.\nPlease enable at least one configuration\nby checking the box in front of it.")); + return false; + } + + // TODO: not finished + + if (ui_->pool1Enabled->isChecked()) + { + bool ok = checkOnePoolAndNotice(1, ui_->pool1Url, ui_->pool1Worker); + + if (!ok) + { + return false; + } + } + + if (ui_->pool2Enabled->isChecked()) + { + bool ok = checkOnePoolAndNotice(2, ui_->pool2Url, ui_->pool2Worker); + + if (!ok) + { + return false; + } + } + + if (ui_->pool3Enabled->isChecked()) + { + bool ok = checkOnePoolAndNotice(3, ui_->pool3Url, ui_->pool3Worker); + + if (!ok) + { + return false; + } + } + + return true; +} + +bool MainWindow::checkIpRangesAndNotice() +{ + int size = ui_->ipRangeBox->count(); + int availableIpRangeNum = 0; + + for (int i=0; i(ui_->ipRangeBox->item(i)); + + if (item->isEnabled()) + { + availableIpRangeNum += item->enabledIpRanges().size(); + } + } + + if (availableIpRangeNum == 0) + { + QMessageBox::information(this, tr("IP Range is Empty"), tr("Please add & enable at least one IP range.")); + return false; + } + + return true; +} + +void MainWindow::onReceivedMiner(btctools::miner::Miner miner) +{ + ui_->actionProgress->setFormat(actionProgressTextFormat_.arg(QString(miner.ip_.c_str()))); + + if (onlySuccessMiners_ && miner.stat_ != "success" && miner.stat_ != "login failed") + { + return; + } + + minerModel_->addMiner(miner); +} + +void MainWindow::onActionBegin() +{ + ui_->actionProgress->setEnabled(true); + ui_->actionProgress->setValue(0); +} + +void MainWindow::onActionProgress(int percent) +{ + ui_->actionProgress->setValue(percent); +} + +void MainWindow::onActionEnd() +{ + ui_->actionProgress->setEnabled(false); + ui_->actionProgress->setValue(100); +} + +void MainWindow::onScanBegin() +{ + bool isMonitor = getAppStatus() == AppStatus::MONITOR_PREPARE; + + setAppStatus(isMonitor ? AppStatus::MONITORING : AppStatus::SCANNING); + + if (isMonitor) + { + lastRefreshTime_ = time(nullptr); + } +} + +void MainWindow::onScanEnd() +{ + if (monitorRunning_) + { + + int timeout = settingWindow_.monitorInterval() - (time(nullptr) - lastRefreshTime_); + waitMonitorRefresh(timeout); + } + else + { + bool isMonitor = getAppStatus() == AppStatus::MONITOR_PREPARE; + + if (isMonitor) + { + ui_->actionProgress->setFormat(tr("%p% - Monitor stopped. Miner status count: ") + getMinerCountStr(false)); + QMessageBox::information(this, tr("Complete"), tr("Monitor stopped. Miner status count: %1.").arg(getMinerCountStr(false))); + } + else + { + ui_->actionProgress->setFormat(tr("%p% - Scanning complete") + ", " + getMinerCountStr(false)); + QMessageBox::information(this, tr("Complete"), tr("Scanning complete, %1.").arg(getMinerCountStr(false))); + } + + setAppStatus(AppStatus::SCAN_FINISH); + } + + updateOverclockOption(); +} + +void MainWindow::onMonitorRefresh() +{ + if (!monitorRunning_) + { + return; + } + + actionProgressTextFormat_ = tr("%p% - Refreshing miner's info: %1. Miner status count: ") + getMinerCountStr(false); + + setAppStatus(AppStatus::MONITOR_PREPARE); + scanner_.wait(); + scanner_.setIpRange(makeIpRangeGenerators()); + scanner_.start(); +} + +void MainWindow::onConfigBegin() +{ + bool isSelected = getAppStatus() == AppStatus::CONFIG_SEL_PREPARE; + + setAppStatus(isSelected ? AppStatus::CONFIGURATING_SEL : AppStatus::CONFIGURATING_ALL); +} + +void MainWindow::onConfigEnd(bool allSkip) +{ + bool onlySelected = getAppStatus() == AppStatus::CONFIGURATING_SEL; + setAppStatus(AppStatus::CONFIG_FINISH); + + ui_->actionProgress->setFormat(tr("%p% - Configuring complete") + ", " + getMinerCountStr(onlySelected)); + + if (allSkip) + { + QMessageBox::information(this, tr("No Miner to Configure"), tr("No miner to configure, or all miners configured successfully at the last time.\n" + "Please scan miners if you want to configure again.")); + } + else + { + QMessageBox::information(this, tr("Complete"), tr("Configuring complete, %1.").arg(getMinerCountStr(onlySelected))); + } + + // Uncheck re-overclocking to prevent unexpected retries + ui_->reOverclocking->setChecked(false); +} + +void MainWindow::onRebootBegin() +{ + bool isSelected = getAppStatus() == AppStatus::REBOOT_SEL_PREPARE; + + setAppStatus(isSelected ? AppStatus::REBOOTING_SEL : AppStatus::REBOOTING_ALL); +} + +void MainWindow::onRebootEnd(bool allSkip) +{ + bool onlySelected = getAppStatus() == AppStatus::REBOOTING_SEL; + setAppStatus(AppStatus::REBOOT_FINISH); + + ui_->actionProgress->setFormat(tr("%p% - Rebooting complete") + ", " + getMinerCountStr(onlySelected)); + + if (allSkip) + { + QMessageBox::information(this, tr("No Miner to Reboot"), tr("No miner to reboot, or all miners you selected be rebooted successfully at the last time.\n" + "Please select another miners.")); + } + else + { + QMessageBox::information(this, tr("Complete"), tr("Rebooting complete, %1.").arg(getMinerCountStr(onlySelected))); + } +} + +void MainWindow::onUpgradeBegin() +{ + bool isSelected = getAppStatus() == AppStatus::UPGRADE_SEL_PREPARE; + + setAppStatus(isSelected ? AppStatus::UPGRADING_SEL : AppStatus::UPGRADING_ALL); +} + +void MainWindow::onUpgradeEnd(bool allSkip) +{ + bool onlySelected = getAppStatus() == AppStatus::UPGRADING_SEL; + setAppStatus(AppStatus::UPGRADE_FINISH); + + ui_->actionProgress->setFormat(tr("%p% - Upgrading complete") + ", " + getMinerCountStr(onlySelected)); + + if (allSkip) + { + QMessageBox::information(this, tr("No Miner to Upgrade"), tr("No miner to upgrade, or all miners you selected be upgraded successfully at the last time.\n" + "Please select another miners.")); + } + else + { + QMessageBox::information(this, tr("Complete"), tr("Upgrading complete, %1.").arg(getMinerCountStr(onlySelected))); + } +} + +void MainWindow::onFindUpdates(QString versionName, QString description, QString downloadUrl, bool forceUpdate) +{ + + if (forceUpdate) + { + QMessageBox::information(this, tr("BTC Tools Needs a Update"), tr("The current version %1 is outdated.\n" + "Please download the new version %2 from pool.btc.com."). + arg(APP_VERSION_NAME).arg(versionName)); + QDesktopServices::openUrl(QUrl(downloadUrl)); + close(); + } + else + { + auto button = QMessageBox::information(this, tr("A New Version is Available"), tr("New features of BTC Tools %1:\n\n%2\n\nDo you want to download it now?"). + arg(versionName).arg(description), QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); + + if (button == QMessageBox::Yes) + { + QDesktopServices::openUrl(QUrl(downloadUrl)); + } + } +} + +void MainWindow::copyMinerItemToClipboard(QModelIndex *pIndex) +{ + QModelIndex index; + if (pIndex == nullptr) + { + index = ui_->minersTable->currentIndex(); + } else { + index = *pIndex; + } + + if (!index.isValid()) + { + return; + } + + QString data = minerModel_->data(index).toString(); + QApplication::clipboard()->setText(data); +} + +void MainWindow::openMinerControlPanel(QModelIndex *pIndex) +{ + QModelIndex index; + if (pIndex == nullptr) + { + index = ui_->minersTable->currentIndex(); + } else { + index = *pIndex; + } + + if (!index.isValid()) + { + return; + } + + const MinerItem &item = minerModel_->getMiner(index.row()); + + QString url; + + if (openMinerCPWithPassword_ && item.miner_.typeStr_ == "AntminerHttpCgi") + { + QString passwordStr = ""; + MinerPassword *p = Utils::findMinerPassword(minerPasswordList_, item.miner_.fullTypeStr_.c_str()); + + if (p != nullptr) + { + passwordStr = QString("%1:%2@").arg(p->userName_).arg(p->password_); + } + + url = QString("http://%1%2/").arg(passwordStr).arg(item.miner_.ip_.c_str()); + } + else + { + url = QString("http://%1/").arg(item.miner_.ip_.c_str()); + } + + QDesktopServices::openUrl(QUrl(url)); +} + +void MainWindow::fillPoolsInForm(QModelIndex *pIndex) +{ + QModelIndex index; + if (pIndex == nullptr) + { + index = ui_->minersTable->currentIndex(); + } else { + index = *pIndex; + } + + if (!index.isValid()) + { + return; + } + + const Miner &miner = minerModel_->getMiner(index.row()).miner_; + + ui_->pool1Url->setText(miner.pool1_.url_.c_str()); + ui_->pool2Url->setText(miner.pool2_.url_.c_str()); + ui_->pool3Url->setText(miner.pool3_.url_.c_str()); + + ui_->pool1Worker->setText(Utils::getSubAccountName(miner.pool1_.worker_.c_str())); + ui_->pool2Worker->setText(Utils::getSubAccountName(miner.pool2_.worker_.c_str())); + ui_->pool3Worker->setText(Utils::getSubAccountName(miner.pool3_.worker_.c_str())); + + ui_->pool1Pwd->setText(miner.pool1_.passwd_.c_str()); + ui_->pool2Pwd->setText(miner.pool2_.passwd_.c_str()); + ui_->pool3Pwd->setText(miner.pool3_.passwd_.c_str()); +} + +void MainWindow::onMinerTableContextMenuClicked(const QPoint & /* pos */) +{ + if (ui_->minersTable->currentIndex().isValid()) + { + minerTableContextMenu_->exec(QCursor::pos()); + } +} + +void MainWindow::editIpRange() +{ + auto item = dynamic_cast(ui_->ipRangeBox->item(ui_->ipRangeBox->currentIndex().row())); + item->editWithNewWindow(); +} + +void MainWindow::onIpRangeBoxContextMenuClicked(const QPoint & /* pos */) +{ + if (ui_->ipRangeBox->currentIndex().isValid()) + { + ipRangeBoxContextMenu_->exec(QCursor::pos()); + } +} + +void MainWindow::onUpgradeWindowSelectedMinerModelChanged(QString newMinerModel) +{ + std::string model = newMinerModel.toUtf8().data(); + size_t selected, selectedDisabled, all, allDisabled; + getSelectedMinerNumberWithModel(newMinerModel, selected, selectedDisabled); + getAllMinerNumberWithModel(newMinerModel, all, allDisabled); + + upgradeWindow_.updateMinerCounter(newMinerModel, selected, selectedDisabled, all, allDisabled); +} + +void MainWindow::getSelectedMinerNumberWithModel(QString minerModel, size_t &selected, size_t &disabled) { + std::string model = minerModel.toUtf8().data(); + selected = 0; + disabled = 0; + + for (const auto &item : selectedMinerItems_) { + if (item->miner_.fullTypeStr_ == model) { + selected++; + if (item->miner_.opt("upgrader.disabled") == "true") { + disabled++; + } + } + } +} + +void MainWindow::getAllMinerNumberWithModel(QString minerModel, size_t &all, size_t &disabled) { + std::string model = minerModel.toUtf8().data(); + all = 0; + disabled = 0; + + for (const auto &item : minerModel_->getMiners()) { + if (item->miner_.fullTypeStr_ == model) { + all++; + if (item->miner_.opt("upgrader.disabled") == "true") { + disabled++; + } + } + } +} + +void MainWindow::onUpgradeSelectedMiners() +{ + upgradeMiners(true); +} + +void MainWindow::onUpgradeAllMiners() +{ + upgradeMiners(false); +} + +void MainWindow::upgradeMiners(bool onlySelected) +{ + if (upgrader_.isRunning()) + { + actionProgressTextFormat_ = tr("%p% - Stopping %1"); + ui_->actionProgress->setFormat(tr("%p% - Stopping...")); + + setAppStatus(onlySelected ? AppStatus::UPGRADE_SEL_PREPARE : AppStatus::UPGRADE_ALL_PREPARE); + + upgrader_.stop(); + } + else + { + QString minerModel = upgradeWindow_.getSelectedMinerModel(); + QString firmware = upgradeWindow_.getSelectedFirmware(); + bool keepSettings = upgradeWindow_.getKeepSettings(); + + upgrader_.setMinerModel(minerModel); + upgrader_.setFirmware(firmware); + upgrader_.setKeepSettings(keepSettings); + + if (onlySelected) + { + // don't need call this, it updated before + // updateSelectedMinerItems(); + + int selectedMinerSize = selectedMinerItems_.size(); + size_t upgradeMinerSize, disabledMinerSize; + getSelectedMinerNumberWithModel(minerModel, upgradeMinerSize, disabledMinerSize); + + if (selectedMinerSize < 1) + { + QMessageBox::information(this, tr("No miner be selected"), tr("Please select one or more miners which you want to upgrade.")); + return; + } + + if (upgradeMinerSize < 1) + { + QMessageBox::information(this, tr("No miner with matching model be selected"), tr("You selected miners are not matching the model (%1) you want to upgrade.").arg(minerModel)); + return; + } + + if (disabledMinerSize >= upgradeMinerSize) { + QMessageBox::information(this, tr("Unsupported model"), tr("BTCTools has not yet supported the firmware upgrade of this model, please wait for future updates.").arg(minerModel)); + return; + } + + auto button = QMessageBox::information(this, tr("You will upgrade %1 miners").arg(upgradeMinerSize), + tr("Do you want to upgrade %1 %2 that your selected now?%3") + .arg(upgradeMinerSize) + .arg(minerModel) + .arg(selectedMinerSize > upgradeMinerSize + ? tr("
Skip %3 miners that don't match the model.").arg(selectedMinerSize - upgradeMinerSize) + : QString("")), + QMessageBox::Yes | QMessageBox::No, QMessageBox::No); + + if (button == QMessageBox::No) + { + return; + } + + setAppStatus(AppStatus::UPGRADE_SEL_PREPARE); + upgrader_.setMiners(selectedMinerItems_); + } + else + { + int minerSize = minerModel_->allMinerNum(); + size_t upgradeMinerSize, disabledMinerSize; + getAllMinerNumberWithModel(minerModel, upgradeMinerSize, disabledMinerSize); + + if (minerSize < 1) + { + QMessageBox::information(this, tr("No miner to upgrade"), tr("Please scan miners first.")); + return; + } + + if (upgradeMinerSize < 1) + { + QMessageBox::information(this, tr("No miner with matching model to upgrade"), tr("You selected model (%1) to upgrade is not matching your miners.").arg(minerModel)); + return; + } + + if (disabledMinerSize >= upgradeMinerSize) { + QMessageBox::information(this, tr("Unsupported model"), tr("BTCTools has not yet supported the firmware upgrade of this model, please wait for future updates.").arg(minerModel)); + return; + } + + auto button = CheckMessageBox::information(this, tr("You will upgrade ALL (%1) miners").arg(upgradeMinerSize), + tr("Do you want to upgrade ALL %1 %2 now?%3") + .arg(upgradeMinerSize) + .arg(minerModel) + .arg(minerSize > upgradeMinerSize + ? tr("
Skip %3 miners that don't match the model.").arg(minerSize - upgradeMinerSize) + : QString("")), + tr("I really want to upgrade ALL (%1) %2.").arg(upgradeMinerSize).arg(minerModel)); + + if (button == CheckMessageBox::Cancel) + { + return; + } + + setAppStatus(AppStatus::UPGRADE_ALL_PREPARE); + upgrader_.setMiners(minerModel_->getMiners()); + } + + actionProgressTextFormat_ = tr("%p% - Upgrading %1"); + + onlySuccessMiners_ = false; + upgrader_.start(); + } + +} + +void MainWindow::updateOverclockOption() +{ + ui_->overclockModel->clear(); + ui_->overclockWorkingMode->clear(); + ui_->overclockLevelName->clear(); + + QStringList models = minerModel_->getMinerModels().toList(); + qSort(models); + + ui_->overclockModel->addItems(models); + + // Add a mouseover tooltip to fully display long information + for (size_t i=0; ioverclockModel->setItemData(i, models[i], Qt::ToolTipRole); + } + + if (models.size() > 0) { + ui_->overclockModel->setCurrentIndex(0); + syncOverclockWorkingMode(models[0]); + } +} + +void MainWindow::syncOverclockWorkingMode(const QString &model) { + ui_->overclockWorkingMode->clear(); + ui_->overclockLevelName->clear(); + + QStringList modes = minerModel_->getOverclockWorkingMode(model).toList(); + qSort(modes.begin(), modes.end(), qGreater()); + + ui_->overclockWorkingMode->addItems(modes); + + // Add a mouseover tooltip to fully display long information + size_t normalIndex = 0; + for (size_t i=0; ioverclockWorkingMode->setItemData(i, modes[i], Qt::ToolTipRole); + if (modes[i] == "Normal") { + normalIndex = i; + } + } + + if (modes.size() > 0) { + ui_->overclockWorkingMode->setCurrentIndex(normalIndex); + syncOverclockLevelName(model, modes[normalIndex]); + } +} + +void MainWindow::syncOverclockLevelName(const QString &model, const QString &workingMode) { + ui_->overclockLevelName->clear(); + + QStringList levels = minerModel_->getOverclockLevelName(model, workingMode).toList(); + qSort(levels.begin(), levels.end(), qGreater()); + + ui_->overclockLevelName->addItems(levels); + + // Add a mouseover tooltip to fully display long information + size_t normalIndex = 0; + for (size_t i=0; ioverclockLevelName->setItemData(i, levels[i], Qt::ToolTipRole); + if (levels[i] == "Normal") { + normalIndex = i; + } + } + + if (levels.size() > 0) { + ui_->overclockLevelName->setCurrentIndex(normalIndex); + } +} + +void MainWindow::onReceivedException(const std::exception &ex) +{ + QString title; + AppStatus newStatus; + + switch (appStatus_) { + case AppStatus::SCAN_PREPARE: + case AppStatus::SCANNING: + title = tr("Scan Failed"); + newStatus = AppStatus::SCAN_FINISH; + break; + case AppStatus::MONITOR_PREPARE: + case AppStatus::MONITORING: + title = tr("Monitor Failed"); + newStatus = AppStatus::MONITOR_FINISH; + break; + case AppStatus::CONFIG_ALL_PREPARE: + case AppStatus::CONFIG_SEL_PREPARE: + case AppStatus::CONFIGURATING_ALL: + case AppStatus::CONFIGURATING_SEL: + title = tr("Configure Failed"); + newStatus = AppStatus::CONFIG_FINISH; + break; + case AppStatus::REBOOT_ALL_PREPARE: + case AppStatus::REBOOT_SEL_PREPARE: + case AppStatus::REBOOTING_ALL: + case AppStatus::REBOOTING_SEL: + title = tr("Reboot Failed"); + newStatus = AppStatus::REBOOT_FINISH; + break; + case AppStatus::UPGRADE_ALL_PREPARE: + case AppStatus::UPGRADE_SEL_PREPARE: + case AppStatus::UPGRADING_ALL: + case AppStatus::UPGRADING_SEL: + title = tr("Upgrade Failed"); + newStatus = AppStatus::UPGRADE_FINISH; + break; + default: + title = tr("Unexpected Error"); + newStatus = AppStatus::SCAN_FINISH; + break; + + } + + QMessageBox::critical(this, title, ex.what()); + + // reset status + setAppStatus(newStatus); + onActionEnd(); +} + +void MainWindow::initContextMenu() +{ + // miner table + copyAction_ = new QAction(tr("Copy"), this); + connect(copyAction_, SIGNAL(triggered()), this, SLOT(copyMinerItemToClipboard())); + + openMinerCP_ = new QAction(tr("Open Control Panel"), this); + connect(openMinerCP_, SIGNAL(triggered()), this, SLOT(openMinerControlPanel())); + + fillPoolsInForm_ = new QAction(tr("Fill Pools in Above Form"), this); + connect(fillPoolsInForm_, SIGNAL(triggered()), this, SLOT(fillPoolsInForm())); + + minerTableContextMenu_ = new QMenu; + minerTableContextMenu_->addAction(copyAction_); + minerTableContextMenu_->addAction(openMinerCP_); + minerTableContextMenu_->addAction(fillPoolsInForm_); + + ui_->minersTable->setContextMenuPolicy(Qt::CustomContextMenu); + connect(ui_->minersTable, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(onMinerTableContextMenuClicked(QPoint))); + + // ip range box + editAction_ = new QAction(tr("Edit"), this); + connect(editAction_, SIGNAL(triggered()), this, SLOT(editIpRange())); + + ipRangeBoxContextMenu_ = new QMenu; + ipRangeBoxContextMenu_->addAction(editAction_); + + ui_->ipRangeBox->setContextMenuPolicy(Qt::CustomContextMenu); + connect(ui_->ipRangeBox, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(onIpRangeBoxContextMenuClicked(QPoint))); +} + +void MainWindow::on_scanMinersButton_clicked() +{ + if (scanner_.isRunning()) + { + actionProgressTextFormat_ = tr("%p% - Stopping %1"); + ui_->actionProgress->setFormat(tr("%p% - Stopping...")); + + setAppStatus(AppStatus::SCAN_PREPARE); + + scanner_.stop(); + } + else + { + if (!checkIpRangesAndNotice()) + { + return; + } + + setAppStatus(AppStatus::SCAN_PREPARE); + + onlySuccessMiners_ = ui_->onlySuccessMiners->isChecked(); + minerModel_->clear(); + + actionProgressTextFormat_ = tr("%p% - Scanning %1"); + + scanner_.setIpRange(makeIpRangeGenerators()); + scanner_.start(); + } +} + +void MainWindow::on_monitorMinersButton_clicked() +{ + if (monitorRunning_) + { + monitorRunning_ = false; + + actionProgressTextFormat_ = tr("%p% - Stopping %1"); + ui_->actionProgress->setFormat(tr("%p% - Stopping...")); + + setAppStatus(AppStatus::MONITOR_PREPARE); + + if (scanner_.isRunning()) { + scanner_.stop(); + } + else { + onScanEnd(); + } + } + else + { + if (!checkIpRangesAndNotice()) + { + return; + } + + setAppStatus(AppStatus::MONITOR_PREPARE); + + onlySuccessMiners_ = ui_->onlySuccessMiners->isChecked(); + minerModel_->clear(); + + actionProgressTextFormat_ = tr("%p% - Refreshing miner's info: %1"); + + scanner_.setIpRange(makeIpRangeGenerators()); + + monitorRunning_ = true; + scanner_.start(); + } +} + +void MainWindow::configMiners(bool onlySelected) +{ + if (configurator_.isRunning()) + { + actionProgressTextFormat_ = tr("%p% - Stopping %1"); + ui_->actionProgress->setFormat(tr("%p% - Stopping...")); + + setAppStatus(onlySelected ? AppStatus::CONFIG_SEL_PREPARE : AppStatus::CONFIG_ALL_PREPARE); + + configurator_.stop(); + } + else + { + //******* add miners ******* + if (onlySelected) + { + updateSelectedMinerItems(); + + int selectedMinerSize = selectedMinerItems_.size(); + + if (selectedMinerSize < 1) + { + QMessageBox::information(this, tr("No miner be selected"), tr("Please select one or more miners which you want to configure.")); + return; + } + + auto button = QMessageBox::information(this, tr("You will configure %1 miners").arg(selectedMinerSize), + tr("Do you want to change pools, workers and passwords for %1 miners that your selected now?").arg(selectedMinerSize), + QMessageBox::Yes | QMessageBox::No, QMessageBox::No); + + if (button == QMessageBox::No) + { + return; + } + + if (!checkPoolsAndNotice()) + { + return; + } + + setAppStatus(AppStatus::CONFIG_SEL_PREPARE); + configurator_.setMiners(selectedMinerItems_); + } + else + { + int minerSize = minerModel_->allMinerNum(); + + if (minerSize < 1) + { + QMessageBox::information(this, tr("No miner to configure"), tr("Please scan miners first.")); + return; + } + + auto button = CheckMessageBox::information(this, tr("You will configure ALL (%1) miners").arg(minerSize), + tr("Do you want to change pools, workers and passwords for ALL (%1) miners now?").arg(minerSize), + tr("I really want to configure ALL (%1) miners.").arg(minerSize)); + + if (button == CheckMessageBox::Cancel) + { + return; + } + + if (!checkPoolsAndNotice()) + { + return; + } + + setAppStatus(AppStatus::CONFIG_ALL_PREPARE); + configurator_.setMiners(minerModel_->getMiners()); + } + //******* end of add miners ******* + + //******* set pools ******* + MinerPools pools; + + //--------------- pool 1 --------------- + pools.pool1Enabled_ = ui_->pool1Enabled->isChecked(); + pools.pool1_.url_ = std::string(ui_->pool1Url->text().trimmed().toUtf8().data()); + pools.pool1_.worker_ = std::string(ui_->pool1Worker->text().trimmed().toUtf8().data()); + pools.pool1_.passwd_ = std::string(ui_->pool1Pwd->text().toUtf8().data()); + + if (ui_->pool1PostfixIp->isChecked()) + { + pools.pool1WorkerPostfix_ = PoolWorkerPostfix::Ip; + } + else if (ui_->pool1PostfixNoChange->isChecked()) + { + pools.pool1WorkerPostfix_ = PoolWorkerPostfix::NoChange; + } + else + { + pools.pool1WorkerPostfix_ = PoolWorkerPostfix::None; + } + + //--------------- pool 2 --------------- + pools.pool2Enabled_ = ui_->pool2Enabled->isChecked(); + pools.pool2_.url_ = std::string(ui_->pool2Url->text().trimmed().toUtf8().data()); + pools.pool2_.worker_ = std::string(ui_->pool2Worker->text().trimmed().toUtf8().data()); + pools.pool2_.passwd_ = std::string(ui_->pool2Pwd->text().toUtf8().data()); + + if (ui_->pool2PostfixIp->isChecked()) + { + pools.pool2WorkerPostfix_ = PoolWorkerPostfix::Ip; + } + else if (ui_->pool2PostfixNoChange->isChecked()) + { + pools.pool2WorkerPostfix_ = PoolWorkerPostfix::NoChange; + } + else + { + pools.pool2WorkerPostfix_ = PoolWorkerPostfix::None; + } + + //--------------- pool 3 --------------- + pools.pool3Enabled_ = ui_->pool3Enabled->isChecked(); + pools.pool3_.url_ = std::string(ui_->pool3Url->text().trimmed().toUtf8().data()); + pools.pool3_.worker_ = std::string(ui_->pool3Worker->text().trimmed().toUtf8().data()); + pools.pool3_.passwd_ = std::string(ui_->pool3Pwd->text().toUtf8().data()); + + if (ui_->pool3PostfixIp->isChecked()) + { + pools.pool3WorkerPostfix_ = PoolWorkerPostfix::Ip; + } + else if (ui_->pool3PostfixNoChange->isChecked()) + { + pools.pool3WorkerPostfix_ = PoolWorkerPostfix::NoChange; + } + else + { + pools.pool3WorkerPostfix_ = PoolWorkerPostfix::None; + } + + //--------------- power control --------------- + pools.powerControlEnabled_ = ui_->powerControlEnabled->isChecked(); + pools.asicBoost_ = ui_->asicBoost->isChecked(); + pools.lowPowerMode_ = ui_->lowPowerMode->isChecked(); + pools.economicMode_ = ui_->economicMode->isChecked(); + + //--------------- overclocking --------------- + pools.overclockEnabled_ = ui_->overclockEnabled->isChecked(); + pools.reOverclocking_ = ui_->reOverclocking->isChecked(); + pools.overclockMinerModel_ = ui_->overclockModel->currentText().toUtf8().data(); + pools.overclockWorkingMode_ = ui_->overclockWorkingMode->currentText(); + pools.overclockLevelName_ = ui_->overclockLevelName->currentText(); + + configurator_.setPools(pools); + //******* end of set pools ******* + + actionProgressTextFormat_ = tr("%p% - Configuring %1"); + + onlySuccessMiners_ = false; + configurator_.start(); + } +} + +void MainWindow::on_configAllMinersButton_clicked() +{ + configMiners(false); +} + +void MainWindow::on_configSelectedMinersButton_clicked() +{ + configMiners(true); +} + +void MainWindow::rebootMiners(bool onlySelected) +{ + if (rebooter_.isRunning()) + { + actionProgressTextFormat_ = tr("%p% - Stopping %1"); + ui_->actionProgress->setFormat(tr("%p% - Stopping...")); + + setAppStatus(onlySelected ? AppStatus::REBOOT_SEL_PREPARE : AppStatus::REBOOT_ALL_PREPARE); + + rebooter_.stop(); + } + else + { + if (onlySelected) + { + updateSelectedMinerItems(); + + int selectedMinerSize = selectedMinerItems_.size(); + + if (selectedMinerSize < 1) + { + QMessageBox::information(this, tr("No miner be selected"), tr("Please select one or more miners which you want to reboot.")); + return; + } + + auto button = QMessageBox::information(this, tr("You will reboot %1 miners").arg(selectedMinerSize), + tr("Do you want to reboot %1 miners that your selected now?").arg(selectedMinerSize), + QMessageBox::Yes | QMessageBox::No, QMessageBox::No); + + if (button == QMessageBox::No) + { + return; + } + + setAppStatus(AppStatus::REBOOT_SEL_PREPARE); + rebooter_.setMiners(selectedMinerItems_); + } + else + { + int minerSize = minerModel_->allMinerNum(); + + if (minerSize < 1) + { + QMessageBox::information(this, tr("No miner to reboot"), tr("Please scan miners first.")); + return; + } + + + auto button = CheckMessageBox::information(this, tr("You will reboot ALL (%1) miners").arg(minerSize), + tr("Do you want to reboot ALL %1 miners now?").arg(minerSize), + tr("I really want to reboot ALL (%1) miners.").arg(minerSize)); + + if (button == CheckMessageBox::Cancel) + { + return; + } + + setAppStatus(AppStatus::REBOOT_ALL_PREPARE); + rebooter_.setMiners(minerModel_->getMiners()); + } + + actionProgressTextFormat_ = tr("%p% - Rebooting %1"); + + onlySuccessMiners_ = false; + rebooter_.start(); + } +} + +void MainWindow::on_rebootAllMinersButton_clicked() +{ + rebootMiners(false); +} + +void MainWindow::on_rebootSelectedMinersButton_clicked() +{ + rebootMiners(true); +} + +void MainWindow::on_addIpRange_clicked() +{ + auto selectedIpRange = ui_->ipRangeBox->selectedItems(); + IpRangeListItem *position = nullptr; + + if (!selectedIpRange.isEmpty()) + { + position = dynamic_cast(selectedIpRange.first()); + } + + addIpRangeWithNewWindow(position); +} + +void MainWindow::on_removeIpRange_clicked() +{ + auto selectedIpRange = ui_->ipRangeBox->selectedItems(); + + if (!selectedIpRange.isEmpty()) + { + removeIpRange(dynamic_cast(selectedIpRange.first())); + } + else + { + removeIpRange(dynamic_cast(ui_->ipRangeBox->item(ui_->ipRangeBox->count() - 1))); + } +} + +void MainWindow::on_importLanRange_clicked() +{ + QStringList lanIpList = Utils::getLanIpList(); + + if (!lanIpList.isEmpty()) + { + for (auto iter=lanIpList.begin(); iter!=lanIpList.end(); iter++) + { + *iter = (*iter).left((*iter).lastIndexOf(".")) + ".0-255"; + } + + addIpRangeFromString(nullptr, tr("LAN:") + lanIpList.join(',')); + } + else + { + QMessageBox::information(this, tr("Network is Not Available"), tr("It looks like your computer has no available network.\n" + "So auto importing the IP ranges from your network failed.\n" + "Please connect to a network and try again, or add IP ranges manually.\n" + "But without an available network, you could not scan or configure miners.")); + } +} + +void MainWindow::on_ipRangeEnableAll_clicked() +{ + bool enabled = ui_->ipRangeEnableAll->isChecked(); + int size = ui_->ipRangeBox->count(); + + for (int i=0; i(ui_->ipRangeBox->item(i)); + item->setEnabled(enabled); + } +} + +void MainWindow::on_minersTable_doubleClicked(QModelIndex index) +{ + openMinerControlPanel(&index); +} + +void MainWindow::on_ipRangeBox_doubleClicked(QModelIndex index) +{ + auto item = dynamic_cast(ui_->ipRangeBox->item(index.row())); + item->editWithNewWindow(); +} + +void MainWindow::on_settingButton_clicked() +{ + syncOptionsToSettingWindow(); + + auto result = settingWindow_.exec(); + + if (result == QDialog::Accepted) + { + syncOptionsFromSettingWindow(); + } +} + +void MainWindow::on_exportToCSVButton_clicked() +{ + QString fileName = QFileDialog::getSaveFileName(this, + tr("Export to CSV"), "", tr("CSV Table File (*.csv)")); + + if (!fileName.isNull()) + { + QFile file(fileName); + + if(!file.open(QIODevice::WriteOnly | QIODevice::Text)) + { + QMessageBox::warning(this, tr("Export to CSV failed"), tr("Open file failed: %1").arg(file.errorString())); + return; + } + + // UTF-8 BOM + QByteArray bom; + bom.append('\xEF'); + bom.append('\xBB'); + bom.append('\xBF'); + // Microsoft Excel may display garbled characters without UTF-8 BOM. + file.write(bom); + + auto data = minerModel_->getTableCSV().toUtf8(); + file.write(data); + + file.close(); + + QMessageBox::information(this, tr("Export to CSV succeeded"), tr("File has saved as %1").arg(fileName)); + } +} + +void MainWindow::on_firmwareUpgradeButton_clicked() +{ + if (upgrader_.isRunning()) { + // stop it + upgradeMiners(appStatus_ == AppStatus::UPGRADING_SEL); + return; + } + + if (minerModel_->getMiners().size() < 1) { + QMessageBox::information(this, tr("No miner to upgrade"), tr("Please scan miners first.")); + return; + } + + updateSelectedMinerItems(); + upgradeWindow_.setMinerModels(minerModel_->getMinerModels()); + if (selectedMinerItems_.size() >= 1 && (*selectedMinerItems_.begin())->miner_.fullTypeStr_.empty() == false) { + upgradeWindow_.setSelectedMinerModel(QString::fromStdString((*selectedMinerItems_.begin())->miner_.fullTypeStr_)); + } + upgradeWindow_.exec(); +} + +void MainWindow::on_powerControlEnabled_toggled(bool checked) +{ + ui_->asicBoost->setEnabled(checked); + ui_->lowPowerMode->setEnabled(checked); + ui_->economicMode->setEnabled(checked); +} + +void MainWindow::on_pool1Enabled_toggled(bool checked) +{ + ui_->pool1Url->setEnabled(checked); + ui_->pool1Worker->setEnabled(checked); + ui_->pool1Pwd->setEnabled(checked); + ui_->pool1PostfixIp->setEnabled(checked); + ui_->pool1PostfixNoChange->setEnabled(checked); + ui_->pool1PostfixNone->setEnabled(checked); +} + +void MainWindow::on_pool2Enabled_toggled(bool checked) +{ + ui_->pool2Url->setEnabled(checked); + ui_->pool2Worker->setEnabled(checked); + ui_->pool2Pwd->setEnabled(checked); + ui_->pool2PostfixIp->setEnabled(checked); + ui_->pool2PostfixNoChange->setEnabled(checked); + ui_->pool2PostfixNone->setEnabled(checked); +} + +void MainWindow::on_pool3Enabled_toggled(bool checked) +{ + ui_->pool3Url->setEnabled(checked); + ui_->pool3Worker->setEnabled(checked); + ui_->pool3Pwd->setEnabled(checked); + ui_->pool3PostfixIp->setEnabled(checked); + ui_->pool3PostfixNoChange->setEnabled(checked); + ui_->pool3PostfixNone->setEnabled(checked); +} + +void MainWindow::on_overclockEnabled_toggled(bool checked) +{ + ui_->overclockModel->setEnabled(checked); + ui_->overclockWorkingMode->setEnabled(checked); + ui_->overclockLevelName->setEnabled(checked); +} + +void MainWindow::on_overclockModel_currentTextChanged(const QString &model) +{ + syncOverclockWorkingMode(model); +} + +void MainWindow::on_overclockWorkingMode_currentTextChanged(const QString &workingMode) +{ + syncOverclockLevelName(ui_->overclockModel->currentText(), workingMode); +} diff --git a/src/mainwindow.h b/src/mainwindow.h new file mode 100644 index 0000000..4e836bd --- /dev/null +++ b/src/mainwindow.h @@ -0,0 +1,230 @@ +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include +#include +#include +#include +#include +#include +#include +#include "settingwindow.h" +#include "upgradewindow.h" +#include "minertablemodel.h" +#include "minerscanner.h" +#include "minerconfigurator.h" +#include "minerrebooter.h" +#include "minerupgrader.h" +#include "autoupdater.h" +#include "iprangelistitem.h" +#include "iprangeedit.h" +#include "utils.h" +#include +#include + +namespace Ui { +class MainWindow; +} + +enum class AppStatus { + NOT_SCAN, + + SCAN_PREPARE, + SCANNING, + SCAN_FINISH, + + MONITOR_PREPARE, + MONITORING, + MONITOR_FINISH, + + CONFIG_ALL_PREPARE, + CONFIG_SEL_PREPARE, + CONFIGURATING_ALL, + CONFIGURATING_SEL, + CONFIG_FINISH, + + REBOOT_ALL_PREPARE, + REBOOT_SEL_PREPARE, + REBOOTING_ALL, + REBOOTING_SEL, + REBOOT_FINISH, + + UPGRADE_ALL_PREPARE, + UPGRADE_SEL_PREPARE, + UPGRADING_ALL, + UPGRADING_SEL, + UPGRADE_FINISH +}; + +enum class ButtonStatus { + PREPARE, + DISABLED, + ENABLED, + STOPABLED +}; + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + explicit MainWindow(QWidget *parent = 0); + ~MainWindow(); + +private slots: + void onReceivedMiner(btctools::miner::Miner miner); + void onActionBegin(); + void onActionProgress(int percent); + void onActionEnd(); + + void onScanBegin(); + void onScanEnd(); + void onMonitorRefresh(); + void waitMonitorRefresh(int time); + void onConfigBegin(); + void onConfigEnd(bool allSkip); + void onRebootBegin(); + void onRebootEnd(bool allSkip); + void onUpgradeBegin(); + void onUpgradeEnd(bool allSkip); + + void onFindUpdates(QString versionName, QString description, QString downloadUrl, bool forceUpdate); + + // miner table context menu + void copyMinerItemToClipboard(QModelIndex *index = nullptr); + void openMinerControlPanel(QModelIndex *index = nullptr); + void fillPoolsInForm(QModelIndex *index = nullptr); + void onMinerTableContextMenuClicked(const QPoint &pos); + + // ip range box context menu + void editIpRange(); + void onIpRangeBoxContextMenuClicked(const QPoint &pos); + + // from UpgradeWindow + void onUpgradeWindowSelectedMinerModelChanged(QString newMinerModel); + void onUpgradeSelectedMiners(); + void onUpgradeAllMiners(); + + // received an exception + void onReceivedException(const std::exception &ex); + + // scan miners + void on_scanMinersButton_clicked(); + void on_monitorMinersButton_clicked(); + // config miners + void on_configAllMinersButton_clicked(); + void on_configSelectedMinersButton_clicked(); + // reboot miners + void on_rebootAllMinersButton_clicked(); + void on_rebootSelectedMinersButton_clicked(); + + void on_removeIpRange_clicked(); + void on_addIpRange_clicked(); + void on_importLanRange_clicked(); + void on_ipRangeEnableAll_clicked(); + + void on_minersTable_doubleClicked(QModelIndex index); + void on_ipRangeBox_doubleClicked(QModelIndex index); + + void on_settingButton_clicked(); + void on_exportToCSVButton_clicked(); + void on_firmwareUpgradeButton_clicked(); + + void on_powerControlEnabled_toggled(bool checked); + void on_pool1Enabled_toggled(bool checked); + void on_pool2Enabled_toggled(bool checked); + void on_pool3Enabled_toggled(bool checked); + + void on_overclockEnabled_toggled(bool checked); + + void on_overclockModel_currentTextChanged(const QString &model); + + void on_overclockWorkingMode_currentTextChanged(const QString &arg1); + +protected: + // check datas + bool checkPoolsAndNotice(); + bool checkIpRangesAndNotice(); + bool checkOnePoolAndNotice(int id, QLineEdit *poolUrl, QLineEdit *poolWorker); + + void updateSelectedMinerItems(); + void rebootMiners(bool onlySelected); + void configMiners(bool onlySelected); + void upgradeMiners(bool onlySelected); + + // update views + void updateOverclockOption(); + void syncOverclockWorkingMode(const QString &model); + void syncOverclockLevelName(const QString &model, const QString &workingMode); + + // for upgrade + void getSelectedMinerNumberWithModel(QString minerModel, size_t &selected, size_t &disabled); + void getAllMinerNumberWithModel(QString minerModel, size_t &all, size_t &disabled); + + // app status + void setAppStatus(AppStatus appStatus); + AppStatus getAppStatus(); + + // load & save UI's config file + void loadUiConfig(); + void saveUiConfig(); + + // sync settings with settingWindow_ + void syncOptionsFromSettingWindow(); + void syncOptionsToSettingWindow(); + + // add & remove ip range + void addIpRangeFromString(IpRangeListItem *position = nullptr, QString ipRangeStr = ""); + void addIpRangeWithNewWindow(IpRangeListItem *position = nullptr, bool enabled = true); + void removeIpRange(IpRangeListItem *ipRange); + + // about IP range box + btctools::utils::IpGeneratorGroup makeIpRangeGenerators(); + QString getIpRangeGroups(); + void setIpRangeGroups(QString ipRanges); + + // window events + void closeEvent(QCloseEvent *event); + + // count miners + QString getMinerCountStr(bool onlySelected); + + // context menu + void initContextMenu(); + +private: + Ui::MainWindow *ui_; + SettingWindow settingWindow_; + UpgradeWindow upgradeWindow_; + + MinerTableModel *minerModel_; + MinerPasswordList minerPasswordList_; + + MinerScanner scanner_; + MinerConfigurator configurator_; + MinerRebooter rebooter_; + MinerUpgrader upgrader_; + + AutoUpdater autoUpdater_; + bool onlySuccessMiners_; + QString actionProgressTextFormat_; + QList selectedMinerItems_; + + AppStatus appStatus_; + bool monitorRunning_; + time_t lastRefreshTime_; + + // miner table context menu + QMenu *minerTableContextMenu_; + QAction *copyAction_; + QAction *openMinerCP_; + QAction *fillPoolsInForm_; + + bool openMinerCPWithPassword_; + + // IP range box context menu + QMenu *ipRangeBoxContextMenu_; + QAction *editAction_; +}; + +#endif // MAINWINDOW_H diff --git a/src/minerconfigurator.cpp b/src/minerconfigurator.cpp new file mode 100644 index 0000000..08bc82f --- /dev/null +++ b/src/minerconfigurator.cpp @@ -0,0 +1,271 @@ +#include +#include +#include +#include +#include "config.h" +#include "minerconfigurator.h" +#include "utils.h" + +MinerConfigurator::MinerConfigurator(MinerPasswordList &minerPasswordList) : + sessionTimeout_(CONFIG_SESSION_TIMEOUT), + stepSize_(CONFIG_STEP_SIZE), + workerNameIpParts_(WORKER_NAME_IP_PARTS), + minerPasswordList_(minerPasswordList) +{ + qRegisterMetaType("btctools::miner::Miner"); + qRegisterMetaType("std::exception"); +} + +void MinerConfigurator::setMiners(const QList miners) +{ + miners_ = std::move(miners); +} + +void MinerConfigurator::setPools(MinerPools pools) +{ + pools_ = std::move(pools); +} + +void MinerConfigurator::stop() +{ + stop_ = true; + + if (configuratorTempPointer_ != nullptr) + { + configuratorTempPointer_->stop(); + } +} + +int MinerConfigurator::sessionTimeout() +{ + return sessionTimeout_; +} + +void MinerConfigurator::setSessionTimeout(int sessionTimeout) +{ + sessionTimeout_ = sessionTimeout; +} + +int MinerConfigurator::stepSize() +{ + return stepSize_; +} + +void MinerConfigurator::setStepSize(int stepSize) +{ + stepSize_ = stepSize; +} + +void MinerConfigurator::setAutoRetryTimes(int times) +{ + autoRetryTimes_ = times; +} + +int MinerConfigurator::workerNameIpParts() +{ + return workerNameIpParts_; +} + +void MinerConfigurator::setWorkerNameIpParts(int parts) +{ + workerNameIpParts_ = parts; +} + +void MinerConfigurator::minerChangePool(btctools::miner::Miner &miner, btctools::miner::Pool &minerPool, btctools::miner::Pool &newPool, PoolWorkerPostfix workerPostfix) +{ + std::string oriWorker = minerPool.worker_; + minerPool = newPool; + + switch (workerPostfix) + { + case PoolWorkerPostfix::Ip: + if (!isPoolEmpty(minerPool)) + { + workerAddPostfixIp(minerPool.worker_, miner.ip_); + } + break; + case PoolWorkerPostfix::NoChange: + if (!isPoolEmpty(minerPool)) + { + workerAddPostfixNoChange(minerPool.worker_, oriWorker); + } + break; + case PoolWorkerPostfix::None: + // no action + break; + } +} + +void MinerConfigurator::workerAddPostfixNoChange(std::string &worker, const std::string &oriWorker) +{ + QString oriWorkerQString = oriWorker.c_str(); + oriWorkerQString = oriWorkerQString.trimmed(); + + // oriWorker is empty, don't add the postfix + if (oriWorkerQString.isEmpty()) + { + return; + } + + int pos = oriWorkerQString.indexOf("."); + + // there is no postfix of oriWorker + if (pos < 0) + { + return; + } + + if (!worker.empty() && worker.find('.') == std::string::npos) + { + worker += '.'; + } + + worker += oriWorkerQString.right(oriWorkerQString.size() - pos - 1).toStdString(); +} + +void MinerConfigurator::workerAddPostfixIp(std::string &worker, const std::string &ip) +{ + struct in_addr_btctools ipAddr; + ipAddr.S_un.S_addr = inet_addr(ip.c_str()); + + if (!worker.empty() && worker.find('.') == std::string::npos) + { + worker += '.'; + } + + char buffer[4][4]; + sprintf(buffer[0], "%hhu", ipAddr.S_un.S_un_b.s_b1); + sprintf(buffer[1], "%hhu", ipAddr.S_un.S_un_b.s_b2); + sprintf(buffer[2], "%hhu", ipAddr.S_un.S_un_b.s_b3); + sprintf(buffer[3], "%hhu", ipAddr.S_un.S_un_b.s_b4); + + QStringList ipParts; + + int beginPart = 4 - workerNameIpParts_; + + for (int i=beginPart; i<4; i++) { + ipParts.append(QString(buffer[i])); + } + + worker += ipParts.join('x').toStdString(); +} + +bool MinerConfigurator::isPoolEmpty(btctools::miner::Pool pool) +{ + return pool.url_.empty() && pool.worker_.empty(); +} + +void MinerConfigurator::run() +{ + try + { + stop_ = false; + emit configBegin(); + + // set miner passwords + btctools::utils::OOLuaHelper::setOpt("login.minerPasswords", Utils::minerPasswordListToString(minerPasswordList_).toUtf8().data()); + // set auto retry times + btctools::utils::OOLuaHelper::setOpt("network.autoRetryTimes", std::to_string(autoRetryTimes_)); + + bool allSkipped = true; + int allNumber = miners_.size(); + int finishedNumber = 0; + + btctools::miner::MinerSource minerSource([this, &allSkipped, &allNumber, &finishedNumber](btctools::miner::MinerYield &minerYield) + { + for (auto i=miners_.begin(); iminer_; + + // successed or skipped at the last modify, skipped. + if (miner.stat_ == std::string("ok") || miner.stat_ == std::string("skip")) + { + // no action + } + else if (miner.typeStr_ == std::string("unknown") || miner.typeStr_ == std::string("")) + { + miner.stat_ = "skip"; + emit minerReporter(miner); + } + else + { + allSkipped = false; + + miner.stat_ = "pre-configurating..."; + emit minerReporter(miner); + + + if (pools_.pool1Enabled_) + { + minerChangePool(miner, miner.pool1_, pools_.pool1_, pools_.pool1WorkerPostfix_); + } + + if (pools_.pool2Enabled_) + { + minerChangePool(miner, miner.pool2_, pools_.pool2_, pools_.pool2WorkerPostfix_); + } + + if (pools_.pool3Enabled_) + { + minerChangePool(miner, miner.pool3_, pools_.pool3_, pools_.pool3WorkerPostfix_); + } + + if (pools_.powerControlEnabled_) + { + miner.setOpt("config.antminer.asicBoost", pools_.asicBoost_ ? "true" : "false"); + miner.setOpt("config.antminer.lowPowerMode", pools_.lowPowerMode_ ? "true" : "false"); + miner.setOpt("config.antminer.economicMode", pools_.economicMode_ ? "true" : "false"); + } + + if (pools_.overclockEnabled_ && pools_.overclockMinerModel_ == miner.fullTypeStr_) { + miner.setOpt("config.antminer.overclockWorkingMode", pools_.overclockWorkingMode_.toUtf8().data()); + miner.setOpt("config.antminer.overclockLevelName", pools_.overclockLevelName_.toUtf8().data()); + } + + miner.setOpt("config.antminer.forceTuning", pools_.reOverclocking_ ? "true" : "false"); + + minerYield(miner); + } + + if (stop_) + { + break; + } + + finishedNumber++; + emit configProgress(finishedNumber * 100 / allNumber); + } + }); + + + btctools::miner::MinerConfigurator config(minerSource, stepSize_); + configuratorTempPointer_ = &config; + + auto source = config.run(sessionTimeout_); + + for (auto miner : source) + { + emit minerReporter(miner); + } + + configuratorTempPointer_ = nullptr; + + emit configEnd(allSkipped); + stop_ = true; + } + catch (std::exception& e) + { + std::cerr << "Exception: " << e.what() << std::endl; + emit configException(e); + } + catch (...) + { + std::cerr << "Unknown error!" << std::endl; + emit configException(std::runtime_error("Unknown error")); + } +} diff --git a/src/minerconfigurator.h b/src/minerconfigurator.h new file mode 100644 index 0000000..2465674 --- /dev/null +++ b/src/minerconfigurator.h @@ -0,0 +1,89 @@ +#ifndef MINERCONFIGURATOR_H +#define MINERCONFIGURATOR_H + +#include +#include +#include +#include + +#include "minertablemodel.h" +#include "utils.h" + +enum class PoolWorkerPostfix +{ + Ip, + NoChange, + None +}; + +struct MinerPools +{ + bool pool1Enabled_; + PoolWorkerPostfix pool1WorkerPostfix_; + btctools::miner::Pool pool1_; + + bool pool2Enabled_; + PoolWorkerPostfix pool2WorkerPostfix_; + btctools::miner::Pool pool2_; + + bool pool3Enabled_; + PoolWorkerPostfix pool3WorkerPostfix_; + btctools::miner::Pool pool3_; + + bool powerControlEnabled_; + bool asicBoost_; + bool lowPowerMode_; + bool economicMode_; + + bool overclockEnabled_; + bool reOverclocking_; + std::string overclockMinerModel_; + QString overclockWorkingMode_; + QString overclockLevelName_; +}; + +class MinerConfigurator : public QThread +{ + Q_OBJECT + +public: + MinerConfigurator(MinerPasswordList &minerPasswordList); + void setMiners(const QList miners); + void setPools(MinerPools pools); + void stop(); + int sessionTimeout(); + void setSessionTimeout(int sessionTimeout); + int stepSize(); + void setStepSize(int stepSize); + void setAutoRetryTimes(int times); + int workerNameIpParts(); + void setWorkerNameIpParts(int parts); + +protected: + void run(); + void minerChangePool(btctools::miner::Miner &miner, btctools::miner::Pool &minerPool, btctools::miner::Pool &newPool, PoolWorkerPostfix workerPostfix); + void workerAddPostfixNoChange(std::string &worker, const std::string &oriWorker); + void workerAddPostfixIp(std::string &worker, const std::string &ip); + bool isPoolEmpty(btctools::miner::Pool pool); + +signals: + void minerReporter(btctools::miner::Miner); + void configBegin(); + void configEnd(bool allSkip); + void configProgress(int percent); + void configException(std::exception); + +private: + volatile bool stop_; + QList miners_; + MinerPools pools_; + int sessionTimeout_; + int stepSize_; + int autoRetryTimes_; + int workerNameIpParts_; + MinerPasswordList &minerPasswordList_; + + btctools::miner::MinerConfigurator * volatile configuratorTempPointer_; +}; + +#endif // MINERCONFIGURATOR_H diff --git a/src/minerrebooter.cpp b/src/minerrebooter.cpp new file mode 100644 index 0000000..a141c15 --- /dev/null +++ b/src/minerrebooter.cpp @@ -0,0 +1,137 @@ +#include +#include "config.h" +#include "minerrebooter.h" +#include "utils.h" + +MinerRebooter::MinerRebooter(MinerPasswordList &minerPasswordList) : + sessionTimeout_(CONFIG_SESSION_TIMEOUT), + stepSize_(CONFIG_STEP_SIZE), + minerPasswordList_(minerPasswordList) +{ + +} + +void MinerRebooter::run() +{ + try + { + stop_ = false; + emit rebootBegin(); + + // set miner passwords + btctools::utils::OOLuaHelper::setOpt("login.minerPasswords", Utils::minerPasswordListToString(minerPasswordList_).toUtf8().data()); + // set auto retry times + btctools::utils::OOLuaHelper::setOpt("network.autoRetryTimes", std::to_string(autoRetryTimes_)); + + bool allSkipped = true; + int allNumber = miners_.size(); + int finishedNumber = 0; + + btctools::miner::MinerSource minerSource([this, &allSkipped, &allNumber, &finishedNumber](btctools::miner::MinerYield &minerYield) + { + foreach (const MinerItem *item, miners_) + { + if (stop_) + { + break; + } + + btctools::miner::Miner miner = item->miner_; + + // successed or skipped at the last modify, skipped. + if (miner.stat_ == std::string("rebooted") || miner.stat_ == std::string("skip")) + { + // no action + } + else if (miner.typeStr_ == std::string("unknown") || miner.typeStr_ == std::string("")) + { + miner.stat_ = "skip"; + emit minerReporter(miner); + } + else + { + allSkipped = false; + + miner.stat_ = "pre-rebooting..."; + emit minerReporter(miner); + + minerYield(miner); + } + + if (stop_) + { + break; + } + + finishedNumber++; + emit rebootProgress(finishedNumber * 100 / allNumber); + } + }); + + + btctools::miner::MinerConfigurator config(minerSource, stepSize_, "RebooterHelper"); + configuratorTempPointer_ = &config; + + auto source = config.run(sessionTimeout_); + + for (auto miner : source) + { + emit minerReporter(miner); + } + + configuratorTempPointer_ = nullptr; + + emit rebootEnd(allSkipped); + stop_ = true; + } + catch (std::exception& e) + { + std::cerr << "Exception: " << e.what() << std::endl; + emit rebootException(e); + } + catch (...) + { + std::cerr << "Unknown error!" << std::endl; + emit rebootException(std::runtime_error("Unknown error")); + } +} + +void MinerRebooter::setMiners(const QList miners) +{ + miners_ = std::move(miners); +} + +void MinerRebooter::stop() +{ + stop_ = true; + + if (configuratorTempPointer_ != nullptr) + { + configuratorTempPointer_->stop(); + } +} + +int MinerRebooter::sessionTimeout() +{ + return sessionTimeout_; +} + +void MinerRebooter::setSessionTimeout(int sessionTimeout) +{ + sessionTimeout_ = sessionTimeout; +} + +int MinerRebooter::stepSize() +{ + return stepSize_; +} + +void MinerRebooter::setStepSize(int stepSize) +{ + stepSize_ = stepSize; +} + +void MinerRebooter::setAutoRetryTimes(int times) +{ + autoRetryTimes_ = times; +} diff --git a/src/minerrebooter.h b/src/minerrebooter.h new file mode 100644 index 0000000..76098e7 --- /dev/null +++ b/src/minerrebooter.h @@ -0,0 +1,46 @@ +#ifndef MINERREBOOTER_H +#define MINERREBOOTER_H + +#include +#include +#include +#include + +#include "minertablemodel.h" +#include "utils.h" + +class MinerRebooter : public QThread +{ + Q_OBJECT +public: + MinerRebooter(MinerPasswordList &minerPasswordList); + void setMiners(const QList miners); + void stop(); + int sessionTimeout(); + void setSessionTimeout(int sessionTimeout); + int stepSize(); + void setStepSize(int stepSize); + void setAutoRetryTimes(int times); + +signals: + void minerReporter(btctools::miner::Miner); + void rebootBegin(); + void rebootEnd(bool allSkip); + void rebootProgress(int percent); + void rebootException(std::exception); + +protected: + void run(); + +private: + volatile bool stop_; + QList miners_; + int sessionTimeout_; + int stepSize_; + int autoRetryTimes_; + MinerPasswordList &minerPasswordList_; + + btctools::miner::MinerConfigurator * volatile configuratorTempPointer_; +}; + +#endif // MINERREBOOTER_H diff --git a/src/minerscanner.cpp b/src/minerscanner.cpp new file mode 100644 index 0000000..0fd976b --- /dev/null +++ b/src/minerscanner.cpp @@ -0,0 +1,130 @@ +#include +#include "config.h" +#include "minerscanner.h" +#include "utils.h" + +MinerScanner::MinerScanner(MinerPasswordList &minerPasswordList) : + sessionTimeout_(SCAN_SESSION_TIMEOUT), + stepSize_(SCAN_STEP_SIZE), + minerPasswordList_(minerPasswordList), + scannerTempPointer_(nullptr) +{ + qRegisterMetaType("btctools::miner::Miner"); + qRegisterMetaType("std::exception"); +} + +void MinerScanner::setIpRange(QString ipRange) +{ + ipg_.addIpRange(ipRange.toStdString()); +} + +void MinerScanner::setIpRange(btctools::utils::IpGeneratorGroup ipg) +{ + ipg_ = ipg; +} + +void MinerScanner::stop() +{ + stop_ = true; + + if (scannerTempPointer_ != nullptr){ + scannerTempPointer_->stop(); + } +} + + +int MinerScanner::sessionTimeout() +{ + return sessionTimeout_; +} + +void MinerScanner::setSessionTimeout(int sessionTimeout) +{ + sessionTimeout_ = sessionTimeout; +} + +int MinerScanner::stepSize() +{ + return stepSize_; +} + +void MinerScanner::setStepSize(int stepSize) +{ + stepSize_ = stepSize; +} + +void MinerScanner::setAutoRetryTimes(int times) +{ + autoRetryTimes_ = times; +} + +void MinerScanner::run() +{ + try + { + stop_ = false; + emit scanBegin(); + + // set miner passwords + btctools::utils::OOLuaHelper::setOpt("login.minerPasswords", Utils::minerPasswordListToString(minerPasswordList_).toUtf8().data()); + // set auto retry times + btctools::utils::OOLuaHelper::setOpt("network.autoRetryTimes", std::to_string(autoRetryTimes_)); + + int allNumber = ipg_.getIpNumber(); + int finishedNumber = 0; + + auto ipSourceWithCount = btctools::utils::IpStrSource([this, &finishedNumber, &allNumber](btctools::utils::IpStrYield &yield) + { + auto ipSource = ipg_.genIpRange(); + + for (auto ip : ipSource) + { + btctools::miner::Miner miner; + miner.ip_ = ip; + miner.stat_ = "pre-scanning..."; + + if (stop_) + { + break; + } + + emit minerReporter(miner); + + yield(ip); + + finishedNumber++; + + if (stop_) + { + break; + } + + emit scanProgress(finishedNumber * 100 / allNumber); + } + }); + + btctools::miner::MinerScanner scanner(ipSourceWithCount, stepSize_); + scannerTempPointer_ = &scanner; + + auto source = scanner.run(sessionTimeout_); + + for (auto miner : source) + { + emit minerReporter(miner); + } + + scannerTempPointer_ = nullptr; + emit scanEnd(); + stop_ = true; + } + catch (std::exception& e) + { + std::cerr << "Exception: " << e.what() << std::endl; + emit scanException(e); + } + catch (...) + { + std::cerr << "Unknown error!" << std::endl; + emit scanException(std::runtime_error("Unknown error")); + } +} diff --git a/src/minerscanner.h b/src/minerscanner.h new file mode 100644 index 0000000..92e6248 --- /dev/null +++ b/src/minerscanner.h @@ -0,0 +1,45 @@ +#ifndef MINERSCANNER_H +#define MINERSCANNER_H + +#include +#include +#include +#include "utils.h" + +class MinerScanner : public QThread +{ + Q_OBJECT + +public: + MinerScanner(MinerPasswordList &minerPasswordList); + void setIpRange(QString ipRange); + void setIpRange(btctools::utils::IpGeneratorGroup ipg); + void stop(); + int sessionTimeout(); + void setSessionTimeout(int sessionTimeout); + int stepSize(); + void setStepSize(int stepSize); + void setAutoRetryTimes(int times); + +protected: + void run(); + +signals: + void minerReporter(btctools::miner::Miner); + void scanBegin(); + void scanEnd(); + void scanProgress(int percent); + void scanException(std::exception); + +private: + btctools::utils::IpGeneratorGroup ipg_; + volatile bool stop_; + int sessionTimeout_; + int stepSize_; + int autoRetryTimes_; + MinerPasswordList &minerPasswordList_; + + btctools::miner::MinerScanner * volatile scannerTempPointer_; +}; + +#endif // MINERSCANNER_H diff --git a/src/minertablemodel.cpp b/src/minertablemodel.cpp new file mode 100644 index 0000000..de2b8cb --- /dev/null +++ b/src/minertablemodel.cpp @@ -0,0 +1,1286 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "minertablemodel.h" +#include "config.h" + +MinerItem::MinerItem(btctools::miner::Miner miner) : + ipLong_(btctools::utils::IpGenerator::ip2long(miner.ip_)), + miner_(std::move(miner)) +{ + // no more code +} + +MinerTableModel::MinerTableModel() +{ + initHighlightOptions(); + + //******* table's title ******* + titles_ << tr("IP") + << tr("Status") + << tr("Type") + << tr("Working Mode") + << tr("Hash Rate RT") + << tr("Hash Rate avg") + << tr("Temperature") + << tr("Fan Speed") + << tr("Elapsed") + << tr("Pool 1") + << tr("Worker 1") + << tr("Pool 2") + << tr("Worker 2") + << tr("Pool 3") + << tr("Worker 3") + << tr("Firmware") + << tr("Software") + << tr("Hardware") + << tr("Network") + << tr("MAC Address"); + + columnSize_ = titles_.size(); + + //******* create db & table ******* + minersDb_ = QSqlDatabase::addDatabase("QSQLITE"); + minersDb_.setDatabaseName(":memory:"); + minersDb_.open(); + + QSqlQuery query(minersDb_); + + bool success = query.exec( + "CREATE TABLE miner_sort(" + "ip INT primary key not null," + "status TEXT not null," + "type TEXT not null," + "working_mode TEXT not null," + "hashrate_5s REAL not null," + "hashrate_avg REAL not null," + "max_temperature INT not null," + "avg_fan_speed INT not null," + "elapsed INT not null," + "pool1 TEXT not null," + "worker1 TEXT not null," + "pool2 TEXT not null," + "worker2 TEXT not null," + "pool3 TEXT not null," + "worker3 TEXT not null," + "firmware TEXT not null," + "software TEXT not null," + "hardware TEXT not null," + "network TEXT not null," + "macaddr TEXT not null" + ")"); + + if (!success) + { + throw QString("MinerTableModel: create table `miner_sort` failed! ") + query.lastError().text(); + } + + //******* create index ******* + success = + query.exec("CREATE INDEX mrst_stat ON miner_sort(status)") && + query.exec("CREATE INDEX mrst_type ON miner_sort(type)") && + query.exec("CREATE INDEX mrst_wkmd ON miner_sort(working_mode)") && + query.exec("CREATE INDEX mrst_hs5s ON miner_sort(hashrate_5s)") && + query.exec("CREATE INDEX mrst_hsvg ON miner_sort(hashrate_avg)") && + query.exec("CREATE INDEX mrst_temp ON miner_sort(max_temperature)") && + query.exec("CREATE INDEX mrst_fspd ON miner_sort(avg_fan_speed)") && + query.exec("CREATE INDEX mrst_elps ON miner_sort(elapsed)") && + query.exec("CREATE INDEX mrst_pol1 ON miner_sort(pool1)") && + query.exec("CREATE INDEX mrst_wrk1 ON miner_sort(worker1)") && + query.exec("CREATE INDEX mrst_pol2 ON miner_sort(pool2)") && + query.exec("CREATE INDEX mrst_wrk2 ON miner_sort(worker2)") && + query.exec("CREATE INDEX mrst_pol3 ON miner_sort(pool3)") && + query.exec("CREATE INDEX mrst_wrk3 ON miner_sort(worker3)") && + query.exec("CREATE INDEX mrst_firm ON miner_sort(firmware)") && + query.exec("CREATE INDEX mrst_soft ON miner_sort(software)") && + query.exec("CREATE INDEX mrst_hard ON miner_sort(hardware)") && + query.exec("CREATE INDEX mrst_netw ON miner_sort(network)") && + query.exec("CREATE INDEX mrst_macr ON miner_sort(macaddr)"); + + if (!success) + { + throw QString("MinerTableModel: create index on `miner_sort` failed! ") + query.lastError().text(); + } + + //******* prepare sqls ******* + // findMinerHashrate5sWithIp_ + QSqlQuery selectQuery = QSqlQuery(minersDb_); + success = selectQuery.prepare("SELECT hashrate_5s FROM miner_sort WHERE ip=:ip"); + + if (!success) + { + throw QString("prepare select miner sql failed! ") + selectQuery.lastError().text(); + } + + findMinerHashrate5sWithIp_ = [selectQuery](uint32_t ip) mutable -> double + { + selectQuery.bindValue(":ip", ip); + bool success = selectQuery.exec(); + + if (!success || !selectQuery.first()) + { + throw QString("MinerTableModel: select from miner_sort failed! ") + selectQuery.lastError().text(); + } + + return selectQuery.value(0).toDouble(); + }; + + // findMinerHashrateAvgWithIp_ + selectQuery = QSqlQuery(minersDb_); + success = selectQuery.prepare("SELECT hashrate_avg FROM miner_sort WHERE ip=:ip"); + + if (!success) + { + throw QString("prepare select miner sql failed! ") + selectQuery.lastError().text(); + } + + findMinerHashrateAvgWithIp_ = [selectQuery](uint32_t ip) mutable -> double + { + selectQuery.bindValue(":ip", ip); + bool success = selectQuery.exec(); + + if (!success || !selectQuery.first()) + { + throw QString("MinerTableModel: select from miner_sort failed! ") + selectQuery.lastError().text(); + } + + return selectQuery.value(0).toDouble(); + }; + + // findMinerMaxTemperatureWithIp_ + selectQuery = QSqlQuery(minersDb_); + success = selectQuery.prepare("SELECT max_temperature FROM miner_sort WHERE ip=:ip"); + + if (!success) + { + throw QString("prepare select miner sql failed! ") + selectQuery.lastError().text(); + } + + findMinerMaxTemperatureWithIp_ = [selectQuery](uint32_t ip) mutable -> int + { + selectQuery.bindValue(":ip", ip); + bool success = selectQuery.exec(); + + if (!success || !selectQuery.first()) + { + throw QString("MinerTableModel: select from miner_sort failed! ") + selectQuery.lastError().text(); + } + + return selectQuery.value(0).toInt(); + }; + + // insert + insertMinerSql_ = QSqlQuery(minersDb_); + success = insertMinerSql_.prepare( + "INSERT INTO miner_sort(" + "ip, status, type, working_mode, hashrate_5s, hashrate_avg, " + "max_temperature, avg_fan_speed, elapsed, pool1, worker1, " + "pool2, worker2, pool3, worker3, " + "firmware, software, hardware, network, macaddr" + ") " + "VALUES(" + ":ip, :status, :type, :working_mode, :hashrate_5s, :hashrate_avg, " + ":max_temperature, :avg_fan_speed, :elapsed, :pool1, :worker1, " + ":pool2, :worker2, :pool3, :worker3, " + ":firmware, :software, :hardware, :network, :macaddr" + ")"); + + if (!success) + { + throw QString("MinerTableModel: prepare insert miner sql failed! ") + insertMinerSql_.lastError().text(); + } + + // update + updateMinerSql_ = QSqlQuery(minersDb_); + success = updateMinerSql_.prepare( + "UPDATE miner_sort " + "SET " + "status=:status, " + "type=:type, " + "working_mode=:working_mode, " + "hashrate_5s=:hashrate_5s, " + "hashrate_avg=:hashrate_avg, " + "max_temperature=:max_temperature, " + "avg_fan_speed=:avg_fan_speed, " + "elapsed=:elapsed, " + "pool1=:pool1, " + "worker1=:worker1, " + "pool2=:pool2, " + "worker2=:worker2, " + "pool3=:pool3, " + "worker3=:worker3, " + "firmware=:firmware, " + "software=:software, " + "hardware=:hardware, " + "network=:network, " + "macaddr=:macaddr " + "WHERE " + "ip=:ip"); + + if (!success) + { + throw QString("MinerTableModel: prepare update miner sql failed! ") + updateMinerSql_.lastError().text(); + } + + // number count + minerNumCountSql_ = QSqlQuery(minersDb_); + success = minerNumCountSql_.prepare("SELECT status,count(*) FROM miner_sort GROUP BY status"); + + if (!success) + { + throw QString("MinerTableModel: prepare number count sql failed! ") + minerNumCountSql_.lastError().text(); + } + + //******* init sort ******* + setSort("ip"); +} + +double MinerTableModel::hashrateToNum(QString hashrate) +{ + double rate = 0; + + hashrate = hashrate.trimmed(); + + if (hashrate.isEmpty()) + { + return 0; + } + + int pos = hashrate.indexOf(' '); + + if (pos > 0 && pos + 1 < hashrate.size()) + { + rate = hashrate.left(pos).toDouble(); + char unit = hashrate[pos + 1].toLatin1(); + + switch (unit) + { + case 'k': + rate *= 1.0e3; + break; + case 'M': + rate *= 1.0e6; + break; + case 'G': + rate *= 1.0e9; + break; + case 'T': + rate *= 1.0e12; + break; + case 'P': + rate *= 1.0e15; + break; + case 'E': + rate *= 1.0e18; + break; + case 'Z': + rate *= 1.0e21; + break; + case 'Y': + rate *= 1.0e24; + break; + } + } + else + { + rate = hashrate.toDouble(); + } + + return rate; +} + +int MinerTableModel::temperatureToNum(QString temperature) +{ + int maxTemp = 0; + + temperature = temperature.trimmed(); + + if (temperature.isEmpty()) + { + return 0; + } + + QStringList tempStrList = temperature.split(" / "); + + foreach (QString tempStr, tempStrList) + { + int tempInt = tempStr.toInt(); + + if (tempInt > maxTemp) + { + maxTemp = tempInt; + } + } + + return maxTemp; +} + +int MinerTableModel::fanSpeedToNum(QString fanSpeed) +{ + int count = 0; + int sum = 0; + + fanSpeed = fanSpeed.trimmed(); + + if (fanSpeed.isEmpty()) + { + return 0; + } + + QStringList strList = fanSpeed.split(" / "); + + foreach (QString str, strList) + { + sum += str.toInt(); + count++; + } + + return count > 0 ? (sum / count) : 0; +} + +int MinerTableModel::elapsedToNum(QString elapsed) +{ + int seconds = 0; + + elapsed = elapsed.trimmed(); + + if (elapsed.isEmpty()) + { + return 0; + } + + QStringList strList = elapsed.split(" "); + + foreach (QString str, strList) + { + char unit = str.at(str.size() - 1).toLatin1(); // get the last char + str = str.left(str.size() - 1); // strip the last char + int strInt = str.toInt(); + + switch (unit) + { + case 'd': // days + strInt *= 86400; + break; + case 'h': // hours + strInt *= 3600; + break; + case 'm': // minutes + strInt *= 60; + break; + case 's': // seconds + // strInt *= 1; + break; + } + + seconds += strInt; + } + + return seconds; +} + +QString MinerTableModel::regularWorkerName(QString workerName) +{ + if (workerName.isEmpty()) + { + return workerName; + } + + QRegExp workerPostfixExp("^.*\\.[0-9]+x[0-9]+$"); + + if (!workerPostfixExp.exactMatch(workerName)) + { + return workerName; + } + + int dotPos = workerName.lastIndexOf("."); + QString postfix = workerName.right(workerName.size() - dotPos - 1); + workerName = workerName.left(dotPos + 1); + + QStringList postfixIpList = postfix.split("x"); + + for (auto it=postfixIpList.begin(); it!=postfixIpList.end(); it++) + { + *it = (*it).rightJustified(3, '0'); + } + + postfix = postfixIpList.join("x"); + + return workerName + postfix; +} + +bool MinerTableModel::isWorkerNameWrong(QString workerName, const char *ip) const +{ + if (workerName.isEmpty()) + { + return false; + } + + QRegExp workerPostfixExp("^.*\\.[0-9]+x[0-9]+$"); + + // Only check the worker name that likes the format of "username.123x45". + if (workerPostfixExp.exactMatch(workerName)) + { + struct in_addr_btctools ipAddr; + ipAddr.S_un.S_addr = inet_addr(ip); + + // the buffer is what the worker's name should be. + char buffer[8]; + + sprintf(buffer, "%03hhux%03hhu", ipAddr.S_un.S_un_b.s_b3, ipAddr.S_un.S_un_b.s_b4); + + workerName = regularWorkerName(workerName); + + // strip the prefix (sub-account name). + workerName = workerName.right(workerName.size() - workerName.lastIndexOf('.') - 1); + + // worker name is different with its IP. + if (workerName != buffer) + { + return true; + } + } + + return false; +} + +bool MinerTableModel::isHashrateTooLow(QString minerType, double hashrate) const +{ + foreach (MinerHashrate minRate, highlightLowHashrates_) + { + if (minerType.toLower().contains(minRate.first.toLower())) + { + if (hashrate < minRate.second) + { + return true; + } + else + { + // Once it matches a miner type, it will no longer to match others. + // It can solve some miner type is the prefix of the other miner type, + // likes "Antminer L3+" and "Antminer L3". + // Now, put "Antminer L3+" before "Antminer L3", and we will get the right result. + return false; + } + } + } + + return false; +} + +void MinerTableModel::initHighlightOptions() +{ + isHighlightTemperature_ = IS_HIGHLIGHT_TEMPERATURE; + highlightTemperatureMoreThan_ = HIGHLIGHT_TEMPERATURE_MORE_THAN; + highlightTemperatureLessThan_ = HIGHLIGHT_TEMPERATURE_LESS_THAN; + + isHighlightLowHashrate_ = IS_HIGHLIGHT_LOW_HASHRATE; + highlightLowHashrates_ = HIGHLIGHT_LOW_HASHRATES; + + isHighlightWrongWorkerName_ = IS_HIGHLIGHT_WRONG_WORKER_NAME; + +} + +QString MinerTableModel::elapsedI18N(QString elapsed) +{ + elapsed = elapsed.replace("d", tr("d", "days")); + elapsed = elapsed.replace("h", tr("h", "hours")); + elapsed = elapsed.replace("m", tr("m", "minutes")); + elapsed = elapsed.replace("s", tr("s", "seconds")); + + return elapsed; +} + +QString MinerTableModel::partTranslate(const QString &originText) +{ + QString translatedText; + int begin = 0; + int end = 0; + + for (; end < originText.size(); end++) { + if (originText[end] == ',' || + originText[end] == ':' || + originText[end] == ';') { + if (begin < end) { + translatedText += tr(originText.midRef(begin, end-begin).toUtf8().data()); + } + translatedText += originText[end]; + begin = end + 1; + } else if (originText[end].isSpace()) { + if (begin == end) { + translatedText += originText[end]; + begin = end + 1; + } + } + } + + if (begin < end) { + translatedText += tr(originText.rightRef(end - begin + 1).trimmed().toUtf8().data()); + } + + return translatedText; +} + +bool MinerTableModel::isHighlightWrongWorkerName() const +{ + return isHighlightWrongWorkerName_; +} + +void MinerTableModel::setIsHighlightWrongWorkerName(bool isHighlightWrongWorkerName) +{ + isHighlightWrongWorkerName_ = isHighlightWrongWorkerName; +} + +MinerHashrateList MinerTableModel::highlightLowHashrates() const +{ + return highlightLowHashrates_; +} + +void MinerTableModel::setHighlightLowHashrates(const MinerHashrateList &highlightLowHashrates) +{ + highlightLowHashrates_ = highlightLowHashrates; +} + +bool MinerTableModel::isHighlightLowHashrate() const +{ + return isHighlightLowHashrate_; +} + +void MinerTableModel::setIsHighlightLowHashrate(bool isHighlightLowHashrate) +{ + isHighlightLowHashrate_ = isHighlightLowHashrate; +} + +int MinerTableModel::highlightTemperatureLessThan() const +{ + return highlightTemperatureLessThan_; +} + +void MinerTableModel::setHighlightTemperatureLessThan(int highlightTemperatureLessThan) +{ + highlightTemperatureLessThan_ = highlightTemperatureLessThan; +} + +int MinerTableModel::highlightTemperatureMoreThan() const +{ + return highlightTemperatureMoreThan_; +} + +void MinerTableModel::setHighlightTemperatureMoreThan(int highlightTemperatureMoreThan) +{ + highlightTemperatureMoreThan_ = highlightTemperatureMoreThan; +} + +bool MinerTableModel::isHighlightTemperature() const +{ + return isHighlightTemperature_; +} + +void MinerTableModel::setIsHighlightTemperature(bool isHighlightTemperature) +{ + isHighlightTemperature_ = isHighlightTemperature; +} + +void MinerTableModel::addMiner(btctools::miner::Miner miner) +{ + MinerItem item = MinerItem(std::move(miner)); + bool isUpdate = miners_.contains(item.ipLong_); + int position = findMinerSortPosition_(item); + + // insert or update `miner_sort` + bool success = false; + + if (isUpdate) + { + bindMinerValues(updateMinerSql_, item); + success = updateMinerSql_.exec(); + + if (!success) + { + throw QString("MinerTableModel: update `miner_sort` failed! ") + updateMinerSql_.lastError().text(); + } + + int newPosition = findMinerSortPosition_(miners_.value(item.ipLong_)); + + int rowBegin = qMin(position, newPosition); + int rowEnd = qMax(position, newPosition); + + miners_.insert(item.ipLong_, item); + emit dataChanged(index(rowBegin, 0), index(rowEnd, columnSize_ - 1)); + } + else + { + beginInsertRows(QModelIndex(), position, position); + bindMinerValues(insertMinerSql_, item); + success = insertMinerSql_.exec(); + + if (!success) + { + throw QString("MinerTableModel: insert `miner_sort` failed! ") + insertMinerSql_.lastError().text(); + } + + miners_.insert(item.ipLong_, item); + endInsertRows(); + } +} + +void MinerTableModel::setSort(int sortColumn, bool isDesc) +{ + const char *sortColumns[] = { + "ip", + "status", + "type", + "working_mode", + "hashrate_5s", + "hashrate_avg", + "max_temperature", + "avg_fan_speed", + "elapsed", + "pool1", + "worker1", + "pool2", + "worker2", + "pool3", + "worker3", + "firmware", + "software", + "hardware", + "network", + "macaddr" + }; + + setSort(sortColumns[sortColumn], isDesc); +} + +void MinerTableModel::setSort(const QString &sortColumn, bool isDesc) +{ + QString sql = QString("SELECT ip FROM miner_sort ORDER BY %1 %2 LIMIT :offset, 1").arg(sortColumn).arg(isDesc ? "DESC" : ""); + QSqlQuery selectMinerIpQuery(minersDb_); + bool success = selectMinerIpQuery.prepare(sql); + + if (!success) + { + throw QString("prepare select miner sql failed! ") + selectMinerIpQuery.lastError().text(); + } + + findMinerIpWithPosition_ = [selectMinerIpQuery](int offset) mutable -> uint32_t + { + selectMinerIpQuery.bindValue(":offset", offset); + bool success = selectMinerIpQuery.exec(); + + if (!success || !selectMinerIpQuery.first()) + { + throw QString("MinerTableModel: select ip from miner_sort failed! ") + selectMinerIpQuery.lastError().text(); + } + + return selectMinerIpQuery.value(0).toUInt(); + }; + + + //******* init findMinerSortPosition_ ******* + + sql = QString("SELECT count(*) FROM miner_sort WHERE %1 %2 :value").arg(sortColumn).arg(isDesc ? ">" : "<"); + + QSqlQuery findSortPositionQuery(minersDb_); + success = findSortPositionQuery.prepare(sql); + + if (!success) + { + throw QString("prepare select miner sql failed! ") + findSortPositionQuery.lastError().text(); + } + + std::function getMinerSortValue; + + //---------- Is there a simpler way? ---------- + if (sortColumn == "ip") + getMinerSortValue = [](const MinerItem &minerItem) { return minerItem.ipLong_; }; + else if (sortColumn == "status") + getMinerSortValue = [](const MinerItem &minerItem) { return minerItem.miner_.stat_.c_str(); }; + else if (sortColumn == "type") + getMinerSortValue = [](const MinerItem &minerItem) { return minerItem.miner_.fullTypeStr_.c_str(); }; + else if (sortColumn == "working_mode") + getMinerSortValue = [](const MinerItem &minerItem) { return minerItem.miner_.opt("antminer.overclock_working_mode").c_str(); }; + else if (sortColumn == "hashrate_5s") + getMinerSortValue = [](const MinerItem &minerItem) { return hashrateToNum(minerItem.miner_.opt("hashrate_5s").c_str()); }; + else if (sortColumn == "hashrate_avg") + getMinerSortValue = [](const MinerItem &minerItem) { return hashrateToNum(minerItem.miner_.opt("hashrate_avg").c_str()); }; + else if (sortColumn == "max_temperature") + getMinerSortValue = [](const MinerItem &minerItem) { return temperatureToNum(minerItem.miner_.opt("temperature").c_str()); }; + else if (sortColumn == "avg_fan_speed") + getMinerSortValue = [](const MinerItem &minerItem) { return fanSpeedToNum(minerItem.miner_.opt("fan_speed").c_str()); }; + else if (sortColumn == "elapsed") + getMinerSortValue = [](const MinerItem &minerItem) { return elapsedToNum(minerItem.miner_.opt("elapsed").c_str()); }; + else if (sortColumn == "pool1") + getMinerSortValue = [](const MinerItem &minerItem) { return minerItem.miner_.pool1_.url_.c_str(); }; + else if (sortColumn == "worker1") + getMinerSortValue = [](const MinerItem &minerItem) { return regularWorkerName(minerItem.miner_.pool1_.worker_.c_str()); }; + else if (sortColumn == "pool2") + getMinerSortValue = [](const MinerItem &minerItem) { return minerItem.miner_.pool2_.url_.c_str(); }; + else if (sortColumn == "worker2") + getMinerSortValue = [](const MinerItem &minerItem) { return regularWorkerName(minerItem.miner_.pool2_.worker_.c_str()); }; + else if (sortColumn == "pool3") + getMinerSortValue = [](const MinerItem &minerItem) { return minerItem.miner_.pool3_.url_.c_str(); }; + else if (sortColumn == "worker3") + getMinerSortValue = [](const MinerItem &minerItem) { return regularWorkerName(minerItem.miner_.pool3_.worker_.c_str()); }; + else if (sortColumn == "firmware") + getMinerSortValue = [](const MinerItem &minerItem) { return minerItem.miner_.opt("firmware_version").c_str(); }; + else if (sortColumn == "software") + getMinerSortValue = [](const MinerItem &minerItem) { return minerItem.miner_.opt("software_version").c_str(); }; + else if (sortColumn == "hardware") + getMinerSortValue = [](const MinerItem &minerItem) { return minerItem.miner_.opt("hardware_version").c_str(); }; + else if (sortColumn == "network") + getMinerSortValue = [](const MinerItem &minerItem) { return minerItem.miner_.opt("network_type").c_str(); }; + else if (sortColumn == "macaddr") + getMinerSortValue = [](const MinerItem &minerItem) { return minerItem.miner_.opt("mac_address").c_str(); }; + //---------- end of these getMinerSortValue ---------- + + findMinerSortPosition_ = [getMinerSortValue, findSortPositionQuery](const MinerItem &minerItem) mutable -> int + { + findSortPositionQuery.bindValue(":value", getMinerSortValue(minerItem)); + bool success = findSortPositionQuery.exec(); + + if (!success || !findSortPositionQuery.first()) + { + throw QString("MinerTableModel: find sort position from miner_sort failed! ") + findSortPositionQuery.lastError().text(); + } + + return findSortPositionQuery.value(0).toInt(); + }; +} + +void MinerTableModel::bindMinerValues(QSqlQuery &query, const MinerItem &minerItem) +{ + query.bindValue(":ip", minerItem.ipLong_); + query.bindValue(":status", minerItem.miner_.stat_.c_str()); + query.bindValue(":type", minerItem.miner_.fullTypeStr_.c_str()); + query.bindValue(":working_mode", minerItem.miner_.opt("antminer.overclock_working_mode").c_str()); + query.bindValue(":hashrate_5s", hashrateToNum(minerItem.miner_.opt("hashrate_5s").c_str())); + query.bindValue(":hashrate_avg", hashrateToNum(minerItem.miner_.opt("hashrate_avg").c_str())); + query.bindValue(":max_temperature", temperatureToNum(minerItem.miner_.opt("temperature").c_str())); + query.bindValue(":avg_fan_speed", fanSpeedToNum(minerItem.miner_.opt("fan_speed").c_str())); + query.bindValue(":elapsed", elapsedToNum(minerItem.miner_.opt("elapsed").c_str())); + query.bindValue(":pool1", minerItem.miner_.pool1_.url_.c_str()); + query.bindValue(":worker1", regularWorkerName(minerItem.miner_.pool1_.worker_.c_str())); + query.bindValue(":pool2", minerItem.miner_.pool2_.url_.c_str()); + query.bindValue(":worker2", regularWorkerName(minerItem.miner_.pool2_.worker_.c_str())); + query.bindValue(":pool3", minerItem.miner_.pool3_.url_.c_str()); + query.bindValue(":worker3", regularWorkerName(minerItem.miner_.pool3_.worker_.c_str())); + query.bindValue(":firmware", minerItem.miner_.opt("firmware_version").c_str()); + query.bindValue(":software", minerItem.miner_.opt("software_version").c_str()); + query.bindValue(":hardware", minerItem.miner_.opt("hardware_version").c_str()); + query.bindValue(":network", minerItem.miner_.opt("network_type").c_str()); + query.bindValue(":macaddr", minerItem.miner_.opt("mac_address").c_str()); +} + +void MinerTableModel::clear() +{ + beginResetModel(); + + miners_.clear(); + + QSqlQuery query(minersDb_); + bool success = query.exec(QString("DELETE FROM miner_sort")); + + if (!success) + { + throw QString("MinerTableModel: DELETE FROM `miner_sort` failed! ") + query.lastError().text(); + } + + endResetModel(); +} + +QList MinerTableModel::getMiners() +{ + QList minersVector; + int size = miners_.size(); + + for (int i=0; iindex(0, 0)); + int maxColumn = columnCount(this->index(0, 0)); + QStringList lines; + + // get header + QStringList header; + for (int c=0; cheaderData(c, Qt::Horizontal, Qt::DisplayRole).toString(); + + data = QString("\"%1\"").arg(data.replace("\"", "\"\"")); + header.append(data); + } + lines.append(header.join(',')); + + // get content + for (int l=0; lindex(l, c); + QString data = this->data(index).toString(); + + data = QString("\"%1\"").arg(data.replace("\"", "\"\"")); + line.append(data); + } + + lines.append(line.join(',')); + } + + return lines.join('\n'); +} + +QStringSet MinerTableModel::getMinerModels() +{ + QStringSet minerModels; + for (const auto &item : miners_) { + if (!item.miner_.fullTypeStr_.empty()) + minerModels.insert(QString::fromStdString(item.miner_.fullTypeStr_)); + } + return minerModels; +} + +QStringSet MinerTableModel::getOverclockWorkingMode(const QString &minerModel) +{ + QStringSet workingModes; + + for (const auto &item : miners_) { + if (minerModel != item.miner_.fullTypeStr_.c_str()) { + continue; + } + + QString modeJson = item.miner_.opt("antminer.overclock_option").c_str(); + if (modeJson.isEmpty()) { + continue; + } + + auto doc = QJsonDocument::fromJson(modeJson.toUtf8()); + if (!doc.isObject()) { + continue; + } + + auto modeInfo = doc.object()["ModeInfo"]; + if (!modeInfo.isArray()) { + continue; + } + + for (auto mode : modeInfo.toArray()) { + if (!mode.isObject()) { + continue; + } + auto modeName = mode.toObject()["ModeName"]; + if (!modeName.isString()) { + continue; + } + workingModes.insert(modeName.toString()); + } + } + + return workingModes; +} + +QStringSet MinerTableModel::getOverclockLevelName(const QString &minerModel, const QString &workingMode) { + QStringSet LevelNames; + + for (const auto &item : miners_) { + if (minerModel != item.miner_.fullTypeStr_.c_str()) { + continue; + } + + QString modeJson = item.miner_.opt("antminer.overclock_option").c_str(); + if (modeJson.isEmpty()) { + continue; + } + + auto doc = QJsonDocument::fromJson(modeJson.toUtf8()); + if (!doc.isObject()) { + continue; + } + + auto modeInfo = doc.object()["ModeInfo"]; + if (!modeInfo.isArray()) { + continue; + } + + for (auto mode : modeInfo.toArray()) { + if (!mode.isObject()) { + continue; + } + auto modeName = mode.toObject()["ModeName"]; + if (!modeName.isString()) { + continue; + } + if (modeName.toString() != workingMode) { + continue; + } + auto level = mode.toObject()["Level"]; + if (!level.isObject()) { + continue; + } + LevelNames += level.toObject().keys().toSet(); + } + } + + return LevelNames; +} + +int MinerTableModel::rowCount(const QModelIndex & /* parent */) const +{ + return miners_.size(); +} + +int MinerTableModel::columnCount(const QModelIndex & /* parent */) const +{ + return columnSize_; +} + +QVariant MinerTableModel::dataColor(const QModelIndex &index) const +{ + int col = index.column(); + int row = index.row(); + + if (col != COL_HASHRATE_RT && col != COL_HASHRATE_AVG && col != COL_TEMPERATURE && + col != COL_WORKER1 && col != COL_WORKER2 && col != COL_WORKER3) + { + return QVariant(); + } + + uint32_t ipLong = findMinerIpWithPosition_(row); + const btctools::miner::Miner &miner = miners_.value(ipLong).miner_; + + switch (col) + { + case COL_HASHRATE_RT: // hashrate_5s + { + if (!isHighlightLowHashrate_) + { + return QVariant(); + } + + double hashrate = findMinerHashrate5sWithIp_(ipLong); + + if (isHashrateTooLow(miner.fullTypeStr_.c_str(), hashrate)) + { + return QColor(Qt::red); + } + + break; + } + + case COL_HASHRATE_AVG: // hashrate_avg + { + if (!isHighlightLowHashrate_) + { + return QVariant(); + } + + double hashrate = findMinerHashrateAvgWithIp_(ipLong); + + if (isHashrateTooLow(miner.fullTypeStr_.c_str(), hashrate)) + { + return QColor(Qt::red); + } + + break; + } + + case COL_TEMPERATURE: // max_temperature + { + if (!isHighlightTemperature_) + { + return QVariant(); + } + + QString temperature = QString(miner.opt("temperature").c_str()).trimmed(); + + if (temperature.isEmpty()) + { + return QVariant(); + } + + QStringList tempStrList = temperature.split(" / "); + + foreach (QString tempStr, tempStrList) + { + int tempInt = tempStr.toInt(); + + // The miner will stop if temperature over 90℃. + // And a negative temperature means the miner has some problems. + if (tempInt > highlightTemperatureMoreThan_ || tempInt < highlightTemperatureLessThan_) + { + return QColor(Qt::red); + } + } + + break; + } + + case COL_WORKER1: // worker1 + { + if (!isHighlightWrongWorkerName_) + { + return QVariant(); + } + + if (isWorkerNameWrong(miner.pool1_.worker_.c_str(), miner.ip_.c_str())) + { + return QColor(Qt::red); + } + + break; + } + + case COL_WORKER2: // worker2 + { + if (!isHighlightWrongWorkerName_) + { + return QVariant(); + } + + if (isWorkerNameWrong(miner.pool2_.worker_.c_str(), miner.ip_.c_str())) + { + return QColor(Qt::red); + } + + break; + } + + case COL_WORKER3: // worker3 + { + if (!isHighlightWrongWorkerName_) + { + return QVariant(); + } + + if (isWorkerNameWrong(miner.pool3_.worker_.c_str(), miner.ip_.c_str())) + { + return QColor(Qt::red); + } + + break; + } + + default: // unknown + break; + } + + // default color + return QVariant(); +} + +QVariant MinerTableModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) + { + return QVariant(); + } + + if (role == Qt::TextAlignmentRole) + { + return int(Qt::AlignCenter | Qt::AlignVCenter); + } + else if (role == Qt::ForegroundRole) { + return dataColor(index); + } + else if (role != Qt::DisplayRole) + { + return QVariant(); + } + + int col = index.column(); + int row = index.row(); + + uint32_t ipLong = findMinerIpWithPosition_(row); + const btctools::miner::Miner &miner = miners_.value(ipLong).miner_; + + switch (col) + { + case COL_IP: + return QString(miner.ip_.c_str()); + case COL_STATUS: + return partTranslate(miner.stat_.c_str()); + case COL_TYPE: + return QString(miner.fullTypeStr_.c_str()); + case COL_WORKING_MODE: + return partTranslate(miner.opt("antminer.overclock_working_mode").c_str()); + + case COL_HASHRATE_RT: + return QString(miner.opt("hashrate_5s").c_str()); + case COL_HASHRATE_AVG: + return QString(miner.opt("hashrate_avg").c_str()); + case COL_TEMPERATURE: + return QString(miner.opt("temperature").c_str()); + case COL_FAN_SPEED: + return QString(miner.opt("fan_speed").c_str()); + case COL_ELAPSED: + return elapsedI18N(QString(miner.opt("elapsed").c_str())); + + // pool 1 + case COL_POOL1: + return QString(miner.pool1_.url_.c_str()).replace("stratum+tcp://", ""); + case COL_WORKER1: + return QString(miner.pool1_.worker_.c_str()); + // pool 2 + case COL_POOL2: + return QString(miner.pool2_.url_.c_str()).replace("stratum+tcp://", ""); + case COL_WORKER2: + return QString(miner.pool2_.worker_.c_str()); + // pool 3 + case COL_POOL3: + return QString(miner.pool3_.url_.c_str()).replace("stratum+tcp://", ""); + case COL_WORKER3: + return QString(miner.pool3_.worker_.c_str()); + + // versions + case COL_FIRMWARE: + return QString(miner.opt("firmware_version").c_str()); + case COL_SOFTWARE: + return QString(miner.opt("software_version").c_str()); + case COL_HARDWARE: + return QString(miner.opt("hardware_version").c_str()); + + // network informations + case COL_NETWORK: + return QString(miner.opt("network_type").c_str()); + case COL_MAC_ADDRESS: + return QString(miner.opt("mac_address").c_str()); + + // unknown + default: + return QString(""); + } +} + +QVariant MinerTableModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (orientation == Qt::Vertical) + { + return section; + } + + if (role != Qt::DisplayRole) + { + return QVariant(); + } + + return titles_.at(section); +} + +void MinerTableModel::sort(int column, Qt::SortOrder order) +{ + setSort(column, order == Qt::DescendingOrder); + emit dataChanged(index(0, 0), index(miners_.size() - 1, columnSize_ - 1)); +} + +void MinerTableModel::updateMinerNumCount() +{ + successMinerNum_ = 0; + timeoutMinerNum_ = 0; + okMinerNum_ = 0; + skipMinerNum_ = 0; + rebootedMinerNum_ = 0; + upgradedMinerNum_ = 0; + otherMinerNum_ = 0; + + bool success = minerNumCountSql_.exec(); + + if (!success) + { + throw QString("MinerTableModel: count number failed! ") + minerNumCountSql_.lastError().text(); + } + + while (minerNumCountSql_.next()) + { + QString stat = minerNumCountSql_.value(0).toString(); + int num = minerNumCountSql_.value(1).toInt(); + + if (stat == "success") + { + successMinerNum_ = num; + } + else if (stat == "timeout") + { + timeoutMinerNum_ = num; + } + else if (stat == "ok") + { + okMinerNum_ = num; + } + else if (stat == "skip") + { + skipMinerNum_ = num; + } + else if (stat == "rebooted") + { + rebootedMinerNum_ = num; + } + else if (stat == "upgraded") + { + upgradedMinerNum_ = num; + } + else + { + // there could be multiple other states. + // so sum them. + otherMinerNum_ += num; + } + } +} + +int MinerTableModel::allMinerNum() +{ + return miners_.size(); +} + +int MinerTableModel::successMinerNum() +{ + return successMinerNum_; +} + +int MinerTableModel::timeoutMinerNum() +{ + return timeoutMinerNum_; +} + +int MinerTableModel::okMinerNum() +{ + return okMinerNum_; +} + +int MinerTableModel::skipMinerNum() +{ + return skipMinerNum_; +} + +int MinerTableModel::rebootedMinerNum() +{ + return rebootedMinerNum_; +} + +int MinerTableModel::upgradedMinerNum() +{ + return upgradedMinerNum_; +} + +int MinerTableModel::otherMinerNum() +{ + return otherMinerNum_; +} diff --git a/src/minertablemodel.h b/src/minertablemodel.h new file mode 100644 index 0000000..02f0d3c --- /dev/null +++ b/src/minertablemodel.h @@ -0,0 +1,160 @@ +#ifndef MINERTABLEMODEL_H +#define MINERTABLEMODEL_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "utils.h" + +struct MinerItem +{ + uint32_t ipLong_; + btctools::miner::Miner miner_; + + MinerItem() = default; + MinerItem(btctools::miner::Miner miner); +}; + +class MinerTableModel : public QAbstractTableModel +{ + Q_OBJECT +public: + enum { + COL_IP, + COL_STATUS, + COL_TYPE, + COL_WORKING_MODE, + COL_HASHRATE_RT, + COL_HASHRATE_AVG, + COL_TEMPERATURE, + COL_FAN_SPEED, + COL_ELAPSED, + COL_POOL1, + COL_WORKER1, + COL_POOL2, + COL_WORKER2, + COL_POOL3, + COL_WORKER3, + COL_FIRMWARE, + COL_SOFTWARE, + COL_HARDWARE, + COL_NETWORK, + COL_MAC_ADDRESS + }; + + MinerTableModel(); + void addMiner(btctools::miner::Miner miner); + void clear(); + QList getMiners(); + const MinerItem & getMiner(int row); + const QString getTableCSV(); + QStringSet getMinerModels(); + QStringSet getOverclockWorkingMode(const QString &minerModel); + QStringSet getOverclockLevelName(const QString &minerModel, const QString &workingMode); + + int rowCount(const QModelIndex &parent) const; + int columnCount(const QModelIndex &parent) const; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + QVariant headerData(int section, Qt::Orientation orientation, int role) const; + void sort(int column, Qt::SortOrder order = Qt::AscendingOrder); + + // miner number count + void updateMinerNumCount(); + int allMinerNum(); + int successMinerNum(); + int timeoutMinerNum(); + int okMinerNum(); + int skipMinerNum(); + int rebootedMinerNum(); + int upgradedMinerNum(); + int otherMinerNum(); + + // getter & setter of highlight options + bool isHighlightTemperature() const; + void setIsHighlightTemperature(bool isHighlightTemperature); + + int highlightTemperatureMoreThan() const; + void setHighlightTemperatureMoreThan(int highlightTemperatureMoreThan); + + int highlightTemperatureLessThan() const; + void setHighlightTemperatureLessThan(int highlightTemperatureLessThan); + + bool isHighlightLowHashrate() const; + void setIsHighlightLowHashrate(bool isHighlightLowHashrate); + + MinerHashrateList highlightLowHashrates() const; + void setHighlightLowHashrates(const MinerHashrateList &highlightLowHashrates); + + bool isHighlightWrongWorkerName() const; + void setIsHighlightWrongWorkerName(bool isHighlightWrongWorkerName); + +protected: + QString getMinerOpt(const btctools::miner::Miner &miner, const std::string &key) const; + void setSort(const QString &sortColumn, bool isDesc = false); + void setSort(int sortColumn, bool isDesc = false); + void bindMinerValues(QSqlQuery &query, const MinerItem &minerItem); + QVariant dataColor(const QModelIndex &index) const; + + void initHighlightOptions(); + + // convert value from string to number + // usage: MinerTableModel::bindMinerValues(), MinerTableModel::setSort()::getMinerSortValue() + static double hashrateToNum(QString hashrate); + static int temperatureToNum(QString temperature); + static int fanSpeedToNum(QString fanSpeed); + static int elapsedToNum(QString elapsed); + static QString regularWorkerName(QString workerName); + + bool isWorkerNameWrong(QString workerName, const char *ip) const; + bool isHashrateTooLow(QString minerType, double hashrate) const; + + // i18n functions + static QString elapsedI18N(QString elapsed); + static QString partTranslate(const QString &originText); + +private: + QMap miners_; + QSqlDatabase minersDb_; + QStringList titles_; + int columnSize_; + + // prepared sql query + QSqlQuery insertMinerSql_; + QSqlQuery updateMinerSql_; + QSqlQuery minerNumCountSql_; + + std::function findMinerIpWithPosition_; + std::function findMinerHashrate5sWithIp_; + std::function findMinerHashrateAvgWithIp_; + std::function findMinerMaxTemperatureWithIp_; + std::function findMinerSortPosition_; + + // miner number count + int successMinerNum_; + int timeoutMinerNum_; + int okMinerNum_; + int skipMinerNum_; + int rebootedMinerNum_; + int upgradedMinerNum_; + int otherMinerNum_; + + // highlight options + bool isHighlightTemperature_; + int highlightTemperatureMoreThan_; + int highlightTemperatureLessThan_; + + bool isHighlightLowHashrate_; + MinerHashrateList highlightLowHashrates_; + + bool isHighlightWrongWorkerName_; +}; + +#endif // MINERTABLEMODEL_H diff --git a/src/minerupgrader.cpp b/src/minerupgrader.cpp new file mode 100644 index 0000000..27ebc8d --- /dev/null +++ b/src/minerupgrader.cpp @@ -0,0 +1,178 @@ +#include +#include "config.h" +#include "minerupgrader.h" +#include "utils.h" + +MinerUpgrader::MinerUpgrader(MinerPasswordList &minerPasswordList) : + sessionTimeout_(UPGRADE_SESSION_TIMEOUT), + stepSize_(CONFIG_STEP_SIZE), + sendFirmwareStepSize_(UPGRADE_SEND_FIRMWARE_STEP_SIZE), + minerPasswordList_(minerPasswordList) +{ + +} + +void MinerUpgrader::run() +{ + try + { + stop_ = false; + emit upgradeBegin(); + + // set miner passwords + btctools::utils::OOLuaHelper::setOpt("login.minerPasswords", Utils::minerPasswordListToString(minerPasswordList_).toUtf8().data()); + + // set auto retry times + btctools::utils::OOLuaHelper::setOpt("network.autoRetryTimes", std::to_string(autoRetryTimes_)); + + // set miner model + btctools::utils::OOLuaHelper::setOpt("upgrader.minerModel", minerModel_.toUtf8().data()); + + // set firmware + btctools::utils::OOLuaHelper::setOpt("upgrader.firmwareName", Utils::getAnsiString(firmware_)); + + // set keep settings + btctools::utils::OOLuaHelper::setOpt("upgrader.keepSettings", keepSettings_ ? "1" : "0"); + + // step size when sending firmwares + btctools::utils::OOLuaHelper::setOpt("upgrader.sendFirmwareStepSize", std::to_string(sendFirmwareStepSize_)); + + + bool allSkipped = true; + int allNumber = miners_.size(); + int finishedNumber = 0; + std::string model = minerModel_.toUtf8().data(); + + btctools::miner::MinerSource minerSource([this, &allSkipped, &allNumber, &finishedNumber, &model](btctools::miner::MinerYield &minerYield) + { + foreach (const MinerItem *item, miners_) + { + if (stop_) + { + break; + } + + btctools::miner::Miner miner = item->miner_; + + // successed or skipped at the last modify, skipped. + if (miner.stat_ == std::string("upgraded")) + { + // no action + } + else if (miner.fullTypeStr_ != model) + { + miner.stat_ = "skip"; + emit minerReporter(miner); + } + else + { + allSkipped = false; + + miner.stat_ = "pre-upgrading..."; + emit minerReporter(miner); + + minerYield(miner); + } + + if (stop_) + { + break; + } + + finishedNumber++; + emit upgradeProgress(finishedNumber * 100 / allNumber); + } + }); + + + btctools::miner::MinerConfigurator config(minerSource, stepSize_, "UpgraderHelper"); + configuratorTempPointer_ = &config; + + auto source = config.run(sessionTimeout_); + + for (auto miner : source) + { + emit minerReporter(miner); + } + + configuratorTempPointer_ = nullptr; + + emit upgradeEnd(allSkipped); + stop_ = true; + } + catch (std::exception& e) + { + std::cerr << "Exception: " << e.what() << std::endl; + emit upgradeException(e); + } + catch (...) + { + std::cerr << "Unknown error!" << std::endl; + emit upgradeException(std::runtime_error("Unknown error")); + } +} + +void MinerUpgrader::setMiners(const QList miners) +{ + miners_ = std::move(miners); +} + +void MinerUpgrader::setMinerModel(const QString &model) +{ + minerModel_ = model; +} + +void MinerUpgrader::setFirmware(const QString &firmware) +{ + firmware_ = firmware; +} + +void MinerUpgrader::setKeepSettings(bool keepSettings) +{ + keepSettings_ = keepSettings; +} + +void MinerUpgrader::stop() +{ + stop_ = true; + + if (configuratorTempPointer_ != nullptr) + { + configuratorTempPointer_->stop(); + } +} + +int MinerUpgrader::sessionTimeout() +{ + return sessionTimeout_; +} + +void MinerUpgrader::setSessionTimeout(int sessionTimeout) +{ + sessionTimeout_ = sessionTimeout; +} + +int MinerUpgrader::stepSize() +{ + return stepSize_; +} + +void MinerUpgrader::setStepSize(int stepSize) +{ + stepSize_ = stepSize; +} + +void MinerUpgrader::setAutoRetryTimes(int times) +{ + autoRetryTimes_ = times; +} + +int MinerUpgrader::sendFirmwareStepSize() +{ + return sendFirmwareStepSize_; +} + +void MinerUpgrader::setSendFirmwareStepSize(int stepSize) +{ + sendFirmwareStepSize_ = stepSize; +} diff --git a/src/minerupgrader.h b/src/minerupgrader.h new file mode 100644 index 0000000..2933d89 --- /dev/null +++ b/src/minerupgrader.h @@ -0,0 +1,58 @@ +#ifndef MINERUPGRADER_H +#define MINERUPGRADER_H + +#include +#include +#include +#include +#include + +#include "minertablemodel.h" +#include "utils.h" + +class MinerUpgrader : public QThread +{ + Q_OBJECT +public: + MinerUpgrader(MinerPasswordList &minerPasswordList); + void setMiners(const QList miners); + void setMinerModel(const QString &model); + void setFirmware(const QString &firmware); + void setKeepSettings(bool keepSettings); + void stop(); + int sessionTimeout(); + void setSessionTimeout(int sessionTimeout); + int stepSize(); + void setStepSize(int stepSize); + void setAutoRetryTimes(int times); + int sendFirmwareStepSize(); + void setSendFirmwareStepSize(int stepSize); + +signals: + void minerReporter(btctools::miner::Miner); + void upgradeBegin(); + void upgradeEnd(bool allSkip); + void upgradeProgress(int percent); + void upgradeException(std::exception); + +protected: + void run(); + +private: + volatile bool stop_; + QList miners_; + + QString minerModel_; + QString firmware_; + bool keepSettings_; + + int sessionTimeout_; + int stepSize_; + int autoRetryTimes_; + int sendFirmwareStepSize_; + MinerPasswordList &minerPasswordList_; + + btctools::miner::MinerConfigurator * volatile configuratorTempPointer_; +}; + +#endif // MINERUPGRADER_H diff --git a/src/passworddelegate.cpp b/src/passworddelegate.cpp new file mode 100644 index 0000000..d904539 --- /dev/null +++ b/src/passworddelegate.cpp @@ -0,0 +1,43 @@ +#include "passworddelegate.h" +#include + +PasswordDelegate::PasswordDelegate(QObject *parent) : + QItemDelegate(parent) +{ +} + +// TableView need to create an Editor +// Create Editor when we construct MyDelegate +// and return the Editor +QWidget* PasswordDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + + QLineEdit *editor = new QLineEdit(parent); + editor->setEchoMode(QLineEdit::Password); + return editor; +} + +// Then, we set the Editor +// Gets the data from Model and feeds the data to delegate Editor +void PasswordDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const +{ + // Get the value via index of the Model + QString value = index.model()->data(index, Qt::UserRole).toString(); + + // Put the value into the SpinBox + static_cast(editor)->setText(value); +} + +// When we modify data, this model reflect the change +// Data from the delegate to the model +void PasswordDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const +{ + model->setData(index, static_cast(editor)->text(), Qt::UserRole); + model->setData(index, displayText(static_cast(editor)->text()), Qt::DisplayRole); +} + +// Give the SpinBox the info on size and location +void PasswordDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + editor->setGeometry(option.rect); +} diff --git a/src/passworddelegate.h b/src/passworddelegate.h new file mode 100644 index 0000000..f2d483a --- /dev/null +++ b/src/passworddelegate.h @@ -0,0 +1,34 @@ +#ifndef PASSWORDDELEGATE_H +#define PASSWORDDELEGATE_H + +#include +#include +#include +#include +#include + +class PasswordDelegate : public QItemDelegate +{ + Q_OBJECT +public: + explicit PasswordDelegate(QObject *parent = 0); + + // Create Editor when we construct MyDelegate + QWidget* createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const; + + // Then, we set the Editor + void setEditorData(QWidget *editor, const QModelIndex &index) const; + + // When we modify data, this model reflect the change + void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const; + + // Give the SpinBox the info on size and location + void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const; + + inline static QString displayText(const QString &password) + { + return QString("*").repeated(password.size()); + } +}; + +#endif // PASSWORDDELEGATE_H diff --git a/src/settingwindow.cpp b/src/settingwindow.cpp new file mode 100644 index 0000000..1e7d3c8 --- /dev/null +++ b/src/settingwindow.cpp @@ -0,0 +1,365 @@ +#include +#include +#include +#include "settingwindow.h" +#include "ui_settingwindow.h" +#include "minertablemodel.h" +#include "minerscanner.h" +#include "passworddelegate.h" +#include "config.h" + +SettingWindow::SettingWindow(QWidget *parent) : + QDialog(parent), + ui_(new Ui::SettingWindow), + hashrateTableModel_(0, 2), + minerPasswordModel_(0, 3) +{ + ui_->setupUi(this); + + // hide the question button from the title bar. + setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); + + // init hashrate table + ui_->highlightTooLowHashrateBox->setModel(&hashrateTableModel_); + hashrateTableModel_.setHeaderData(0, Qt::Horizontal, tr("Miner Type")); + hashrateTableModel_.setHeaderData(1, Qt::Horizontal, tr("Min Hash Rate")); + + // init miner password table + // init hashrate table + ui_->minerPasswordsBox->setModel(&minerPasswordModel_); + ui_->minerPasswordsBox->setItemDelegateForColumn(2, new PasswordDelegate(this)); + minerPasswordModel_.setHeaderData(0, Qt::Horizontal, tr("Miner Type")); + minerPasswordModel_.setHeaderData(1, Qt::Horizontal, tr("User Name")); + minerPasswordModel_.setHeaderData(2, Qt::Horizontal, tr("Password")); + + ui_->highlightTooLowHashrateBox->setColumnWidth(0, 170); + ui_->highlightTooLowHashrateBox->setColumnWidth(1, 130); + + // only int to input + ui_->scanningTimeout->setValidator(new QIntValidator(0, 2147483647, this)); + ui_->configuringTimeout->setValidator(new QIntValidator(0, 2147483647, this)); + ui_->concurrentScanning->setValidator(new QIntValidator(1, 2147483647, this)); + ui_->upgradingTimeout->setValidator(new QIntValidator(0, 2147483647, this)); + ui_->concurrentUpgrading->setValidator(new QIntValidator(1, 2147483647, this)); + ui_->autoRetryTimes->setValidator(new QIntValidator(0, 2147483647, this)); + ui_->highlightTemperatureMoreThan->setValidator(new QIntValidator(-2147483647, 2147483647, this)); + ui_->highlightTemperatureLessThan->setValidator(new QIntValidator(-2147483647, 2147483647, this)); +} + +SettingWindow::~SettingWindow() +{ + delete ui_; +} + +int SettingWindow::monitorInterval() const +{ + return ui_->monitorInterval->text().toInt(); +} + +void SettingWindow::setMonitorInterval(int interval) +{ + ui_->monitorInterval->setText(QString::number(interval)); +} + +int SettingWindow::scanningTimeout() const +{ + return ui_->scanningTimeout->text().toInt(); +} + +void SettingWindow::setScanningTimeout(int timeout) +{ + ui_->scanningTimeout->setText(QString::number(timeout)); +} + +int SettingWindow::concurrentScanning() const +{ + return ui_->concurrentScanning->text().toInt(); +} + +void SettingWindow::setConcurrentScanning(int minerNum) +{ + ui_->concurrentScanning->setText(QString::number(minerNum)); +} + +int SettingWindow::configuringTimeout() const +{ + return ui_->configuringTimeout->text().toInt(); +} + +void SettingWindow::setConfiguringTimeout(int timeout) +{ + ui_->configuringTimeout->setText(QString::number(timeout)); +} + +int SettingWindow::concurrentConfiguring() const +{ + return ui_->concurrentConfiguring->text().toInt(); +} + +void SettingWindow::setConcurrentConfiguring(int minerNum) +{ + ui_->concurrentConfiguring->setText(QString::number(minerNum)); +} + +int SettingWindow::upgradingTimeout() const +{ + return ui_->upgradingTimeout->text().toInt(); +} + +void SettingWindow::setUpgradingTimeout(int timeout) +{ + ui_->upgradingTimeout->setText(QString::number(timeout)); +} + +int SettingWindow::concurrentUpgrading() const +{ + return ui_->concurrentUpgrading->text().toInt(); +} + +void SettingWindow::setConcurrentUpgrading(int minerNum) +{ + ui_->concurrentUpgrading->setText(QString::number(minerNum)); +} + +bool SettingWindow::isHighlightTemperature() const +{ + return ui_->isHighlightTemperature->isChecked(); +} + +void SettingWindow::setIsHighlightTemperature(bool isHighlightTemperature) +{ + ui_->isHighlightTemperature->setChecked(isHighlightTemperature); +} + +int SettingWindow::highlightTemperatureMoreThan() const +{ + return ui_->highlightTemperatureMoreThan->text().toInt(); +} + +void SettingWindow::setHighlightTemperatureMoreThan(int highlightTemperatureMoreThan) +{ + ui_->highlightTemperatureMoreThan->setText(QString::number(highlightTemperatureMoreThan)); +} + +int SettingWindow::highlightTemperatureLessThan() const +{ + return ui_->highlightTemperatureLessThan->text().toInt(); +} + +void SettingWindow::setHighlightTemperatureLessThan(int highlightTemperatureLessThan) +{ + ui_->highlightTemperatureLessThan->setText(QString::number(highlightTemperatureLessThan)); +} + +bool SettingWindow::isHighlightLowHashrate() const +{ + return ui_->isHighlightTooLowHashrate->isChecked(); +} + +void SettingWindow::setIsHighlightLowHashrate(bool isHighlightLowHashrate) +{ + ui_->isHighlightTooLowHashrate->setChecked(isHighlightLowHashrate); +} + +MinerHashrateList SettingWindow::highlightLowHashrates() const +{ + MinerHashrateList list; + + int rowCount = hashrateTableModel_.rowCount(); + + for(int row=0; rowisHighlightWrongWorkerName->isChecked(); +} + +void SettingWindow::setIsHighlightWrongWorkerName(bool isHighlightWrongWorkerName) +{ + ui_->isHighlightWrongWorkerName->setChecked(isHighlightWrongWorkerName); +} + +bool SettingWindow::isOpenMinerCPWithPassword() const +{ + return ui_->openMinerCPWithPassword->isChecked(); +} + +void SettingWindow::setIsOpenMinerCPWithPassword(bool isOpenMinerCPWithPassword) +{ + ui_->openMinerCPWithPassword->setChecked(isOpenMinerCPWithPassword); +} + +int SettingWindow::workerNameIpParts() const +{ + return ui_->workerNameIpParts->value(); +} + +void SettingWindow::setWorkerNameIpParts(int parts) +{ + ui_->workerNameIpParts->setValue(parts); +} + +int SettingWindow::autoRetryTimes() const +{ + return ui_->autoRetryTimes->text().toInt(); +} + +void SettingWindow::setAutoRetryTimes(int times) +{ + ui_->autoRetryTimes->setText(QString::number(times)); +} + +void SettingWindow::addHashrateRow(const QString &minerType, const QString &hashrate) +{ + hashrateTableModel_.appendRow(new QStandardItem()); + + int row = hashrateTableModel_.rowCount() - 1; + + hashrateTableModel_.setData(hashrateTableModel_.index(row, 0), minerType); + hashrateTableModel_.setData(hashrateTableModel_.index(row, 1), hashrate); + + hashrateTableModel_.item(row, 0)->setTextAlignment(Qt::AlignCenter); + hashrateTableModel_.item(row, 1)->setTextAlignment(Qt::AlignCenter); +} + +void SettingWindow::addMinerPasswordRow(const QString &minerType, const QString &userName, const QString &password) +{ + minerPasswordModel_.appendRow(new QStandardItem()); + + int row = minerPasswordModel_.rowCount() - 1; + + minerPasswordModel_.setData(minerPasswordModel_.index(row, 0), minerType); + minerPasswordModel_.setData(minerPasswordModel_.index(row, 1), userName); + minerPasswordModel_.setData(minerPasswordModel_.index(row, 2), password, Qt::UserRole); + minerPasswordModel_.setData(minerPasswordModel_.index(row, 2), PasswordDelegate::displayText(password), Qt::DisplayRole); + + minerPasswordModel_.item(row, 0)->setTextAlignment(Qt::AlignCenter); + minerPasswordModel_.item(row, 1)->setTextAlignment(Qt::AlignCenter); + minerPasswordModel_.item(row, 2)->setTextAlignment(Qt::AlignCenter); +} + +void SettingWindow::on_addHashRateButton_clicked() +{ + addHashrateRow(); +} + +void SettingWindow::on_removeHashRateButton_clicked() +{ + QModelIndex index = ui_->highlightTooLowHashrateBox->currentIndex(); + + if (index.row() >= 0) + { + hashrateTableModel_.removeRow(index.row()); + } + else + { + QMessageBox::information(this, tr("No Item is Selected"), tr("Please select an item to remove.")); + } +} + +void SettingWindow::on_buttonBox_clicked(QAbstractButton *button) +{ + if (button == ui_->buttonBox->button(QDialogButtonBox::RestoreDefaults)) + { + setMonitorInterval(MONITOR_INTERVAL); + + setScanningTimeout(SCAN_SESSION_TIMEOUT); + setConcurrentScanning(SCAN_STEP_SIZE); + + setConfiguringTimeout(CONFIG_SESSION_TIMEOUT); + setConcurrentConfiguring(CONFIG_STEP_SIZE); + + setUpgradingTimeout(UPGRADE_SESSION_TIMEOUT); + setConcurrentUpgrading(UPGRADE_SEND_FIRMWARE_STEP_SIZE); + + setWorkerNameIpParts(WORKER_NAME_IP_PARTS); + setAutoRetryTimes(AUTO_RETRY_TIMES); + + setIsHighlightTemperature(IS_HIGHLIGHT_TEMPERATURE); + setHighlightTemperatureMoreThan(HIGHLIGHT_TEMPERATURE_MORE_THAN); + setHighlightTemperatureLessThan(HIGHLIGHT_TEMPERATURE_LESS_THAN); + setIsHighlightLowHashrate(IS_HIGHLIGHT_LOW_HASHRATE); + setIsHighlightWrongWorkerName(IS_HIGHLIGHT_WRONG_WORKER_NAME); + + setHighlightLowHashrates(HIGHLIGHT_LOW_HASHRATES); + setMinerPasswords(DEFAULT_MINER_PASSWORDS); + + setIsOpenMinerCPWithPassword(OPEN_MINER_CP_WITH_PASSWORD); + } +} + +void SettingWindow::on_addMinerPassword_clicked() +{ + addMinerPasswordRow(); +} + +void SettingWindow::on_removeMinerPassword_clicked() +{ + QModelIndex index = ui_->minerPasswordsBox->currentIndex(); + + if (index.row() >= 0) + { + minerPasswordModel_.removeRow(index.row()); + } + else + { + QMessageBox::information(this, tr("No Item is Selected"), tr("Please select an item to remove.")); + } +} diff --git a/src/settingwindow.h b/src/settingwindow.h new file mode 100644 index 0000000..47e624d --- /dev/null +++ b/src/settingwindow.h @@ -0,0 +1,91 @@ +#ifndef SETTINGWINDOW_H +#define SETTINGWINDOW_H + +#include +#include +#include +#include "utils.h" + +namespace Ui { +class SettingWindow; +} + +class SettingWindow : public QDialog +{ + Q_OBJECT + +public: + explicit SettingWindow(QWidget *parent = 0); + ~SettingWindow(); + + // getter & setter of scanning & highlight options + int monitorInterval() const; + void setMonitorInterval(int interval); + + int scanningTimeout() const; + void setScanningTimeout(int timeout); + + int concurrentScanning() const; + void setConcurrentScanning(int minerNum); + + int configuringTimeout() const; + void setConfiguringTimeout(int timeout); + + int concurrentConfiguring() const; + void setConcurrentConfiguring(int minerNum); + + int upgradingTimeout() const; + void setUpgradingTimeout(int timeout); + + int concurrentUpgrading() const; + void setConcurrentUpgrading(int minerNum); + + bool isHighlightTemperature() const; + void setIsHighlightTemperature(bool isHighlightTemperature); + + int highlightTemperatureMoreThan() const; + void setHighlightTemperatureMoreThan(int highlightTemperatureMoreThan); + + int highlightTemperatureLessThan() const; + void setHighlightTemperatureLessThan(int highlightTemperatureLessThan); + + bool isHighlightLowHashrate() const; + void setIsHighlightLowHashrate(bool isHighlightLowHashrate); + + MinerHashrateList highlightLowHashrates() const; + void setHighlightLowHashrates(const MinerHashrateList &highlightLowHashrates); + + MinerPasswordList minerPasswords() const; + void setMinerPasswords(const MinerPasswordList &minerPasswords); + + bool isHighlightWrongWorkerName() const; + void setIsHighlightWrongWorkerName(bool isHighlightWrongWorkerName); + + bool isOpenMinerCPWithPassword() const; + void setIsOpenMinerCPWithPassword(bool isOpenMinerCPWithPassword); + + int workerNameIpParts() const; + void setWorkerNameIpParts(int parts); + + int autoRetryTimes() const; + void setAutoRetryTimes(int times); + +protected: + void addHashrateRow(const QString &minerType = "", const QString &hashrate = ""); + void addMinerPasswordRow(const QString &minerType = "", const QString &userName = "", const QString &password = ""); + +private slots: + void on_addHashRateButton_clicked(); + void on_removeHashRateButton_clicked(); + void on_buttonBox_clicked(QAbstractButton *button); + + void on_addMinerPassword_clicked(); + void on_removeMinerPassword_clicked(); + +private: + Ui::SettingWindow *ui_; + QStandardItemModel hashrateTableModel_; + QStandardItemModel minerPasswordModel_; +}; + +#endif // SETTINGWINDOW_H diff --git a/src/translation.h b/src/translation.h new file mode 100644 index 0000000..67b750f --- /dev/null +++ b/src/translation.h @@ -0,0 +1,108 @@ +#ifndef TRANSLATION_H +#define TRANSLATION_H + +#include + +static const char *Translation[] = +{ + QT_TRANSLATE_NOOP("MinerTableModel", "antminer-unknown"), + QT_TRANSLATE_NOOP("MinerTableModel", "apply cfg..."), + QT_TRANSLATE_NOOP("MinerTableModel", "Cannot Find Signature!!!"), + QT_TRANSLATE_NOOP("MinerTableModel", "Cannot Find Signature"), + QT_TRANSLATE_NOOP("MinerTableModel", "check if BOS..."), + QT_TRANSLATE_NOOP("MinerTableModel", "Don't support"), + QT_TRANSLATE_NOOP("MinerTableModel", "Enhanced LPM"), + QT_TRANSLATE_NOOP("MinerTableModel", "failed"), + QT_TRANSLATE_NOOP("MinerTableModel", "find antminer"), + QT_TRANSLATE_NOOP("MinerTableModel", "find avalon"), + QT_TRANSLATE_NOOP("MinerTableModel", "find pools..."), + QT_TRANSLATE_NOOP("MinerTableModel", "find type..."), + QT_TRANSLATE_NOOP("MinerTableModel", "Firmware file not found"), + QT_TRANSLATE_NOOP("MinerTableModel", "get BOS info..."), + QT_TRANSLATE_NOOP("MinerTableModel", "get cfg..."), + QT_TRANSLATE_NOOP("MinerTableModel", "get meta cfg..."), + QT_TRANSLATE_NOOP("MinerTableModel", "get network info..."), + QT_TRANSLATE_NOOP("MinerTableModel", "get pools..."), + QT_TRANSLATE_NOOP("MinerTableModel", "get status..."), + QT_TRANSLATE_NOOP("MinerTableModel", "get token..."), + QT_TRANSLATE_NOOP("MinerTableModel", "get token failed"), + QT_TRANSLATE_NOOP("MinerTableModel", "http detect..."), + QT_TRANSLATE_NOOP("MinerTableModel", "https detect..."), + QT_TRANSLATE_NOOP("MinerTableModel", "Incorrect firmware!!!"), + QT_TRANSLATE_NOOP("MinerTableModel", "Incorrect firmware"), + QT_TRANSLATE_NOOP("MinerTableModel", "inner error"), + QT_TRANSLATE_NOOP("MinerTableModel", "It is not a BOS"), + QT_TRANSLATE_NOOP("MinerTableModel", "login..."), + QT_TRANSLATE_NOOP("MinerTableModel", "login failed"), + QT_TRANSLATE_NOOP("MinerTableModel", "login without pwd..."), + QT_TRANSLATE_NOOP("MinerTableModel", "LPM"), + QT_TRANSLATE_NOOP("MinerTableModel", "may succeeded"), + QT_TRANSLATE_NOOP("MinerTableModel", "Miner is slowly cooling down"), + QT_TRANSLATE_NOOP("MinerTableModel", "not finish"), + QT_TRANSLATE_NOOP("MinerTableModel", "OC √"), + QT_TRANSLATE_NOOP("MinerTableModel", "OC tuning..."), + QT_TRANSLATE_NOOP("MinerTableModel", "ok"), + QT_TRANSLATE_NOOP("MinerTableModel", "parse pools failed"), + QT_TRANSLATE_NOOP("MinerTableModel", "perform reboot failed"), + QT_TRANSLATE_NOOP("MinerTableModel", "pre-configurating..."), + QT_TRANSLATE_NOOP("MinerTableModel", "pre-rebooting..."), + QT_TRANSLATE_NOOP("MinerTableModel", "pre-scanning..."), + QT_TRANSLATE_NOOP("MinerTableModel", "pre-upgrading..."), + QT_TRANSLATE_NOOP("MinerTableModel", "read config..."), + QT_TRANSLATE_NOOP("MinerTableModel", "read config failed"), + QT_TRANSLATE_NOOP("MinerTableModel", "read hashrate..."), + QT_TRANSLATE_NOOP("MinerTableModel", "read overclock option..."), + QT_TRANSLATE_NOOP("MinerTableModel", "read overclock status..."), + QT_TRANSLATE_NOOP("MinerTableModel", "read power mode..."), + QT_TRANSLATE_NOOP("MinerTableModel", "read stat failed"), + QT_TRANSLATE_NOOP("MinerTableModel", "read status..."), + QT_TRANSLATE_NOOP("MinerTableModel", "read summary..."), + QT_TRANSLATE_NOOP("MinerTableModel", "read temperature..."), + QT_TRANSLATE_NOOP("MinerTableModel", "read type..."), + QT_TRANSLATE_NOOP("MinerTableModel", "read type failed"), + QT_TRANSLATE_NOOP("MinerTableModel", "rebooted"), + QT_TRANSLATE_NOOP("MinerTableModel", "rebooting..."), + QT_TRANSLATE_NOOP("MinerTableModel", "refused"), + QT_TRANSLATE_NOOP("MinerTableModel", "Request Entity Too Large"), + QT_TRANSLATE_NOOP("MinerTableModel", "require password"), + QT_TRANSLATE_NOOP("MinerTableModel", "restart cgminer..."), + QT_TRANSLATE_NOOP("MinerTableModel", "retry times"), + QT_TRANSLATE_NOOP("MinerTableModel", "save cfg..."), + QT_TRANSLATE_NOOP("MinerTableModel", "set power mode failed"), + QT_TRANSLATE_NOOP("MinerTableModel", "set power mode..."), + QT_TRANSLATE_NOOP("MinerTableModel", "skip"), + QT_TRANSLATE_NOOP("MinerTableModel", "success"), + QT_TRANSLATE_NOOP("MinerTableModel", "timeout"), + QT_TRANSLATE_NOOP("MinerTableModel", "unknown"), + QT_TRANSLATE_NOOP("MinerTableModel", "unknown device"), + QT_TRANSLATE_NOOP("MinerTableModel", "unknown step name"), + QT_TRANSLATE_NOOP("MinerTableModel", "update config..."), + QT_TRANSLATE_NOOP("MinerTableModel", "update config failed"), + QT_TRANSLATE_NOOP("MinerTableModel", "upgraded"), + QT_TRANSLATE_NOOP("MinerTableModel", "upgrading..."), + QT_TRANSLATE_NOOP("MinerTableModel", "upload file..."), + QT_TRANSLATE_NOOP("MinerTableModel", "wait finish..."), + QT_TRANSLATE_NOOP("MinerTableModel", "wait finish timeout"), + QT_TRANSLATE_NOOP("MinerTableModel", "waiting..."), + QT_TRANSLATE_NOOP("QColorDialog", "Pick Screen Color"), + QT_TRANSLATE_NOOP("QPlatformTheme", "Abort"), + QT_TRANSLATE_NOOP("QPlatformTheme", "Apply"), + QT_TRANSLATE_NOOP("QPlatformTheme", "Cancel"), + QT_TRANSLATE_NOOP("QPlatformTheme", "Close"), + QT_TRANSLATE_NOOP("QPlatformTheme", "Discard"), + QT_TRANSLATE_NOOP("QPlatformTheme", "Help"), + QT_TRANSLATE_NOOP("QPlatformTheme", "Ignore"), + QT_TRANSLATE_NOOP("QPlatformTheme", "&No"), + QT_TRANSLATE_NOOP("QPlatformTheme", "N&o to All"), + QT_TRANSLATE_NOOP("QPlatformTheme", "OK"), + QT_TRANSLATE_NOOP("QPlatformTheme", "Open"), + QT_TRANSLATE_NOOP("QPlatformTheme", "Reset"), + QT_TRANSLATE_NOOP("QPlatformTheme", "Restore Defaults"), + QT_TRANSLATE_NOOP("QPlatformTheme", "Retry"), + QT_TRANSLATE_NOOP("QPlatformTheme", "Save"), + QT_TRANSLATE_NOOP("QPlatformTheme", "Save All"), + QT_TRANSLATE_NOOP("QPlatformTheme", "&Yes"), + QT_TRANSLATE_NOOP("QPlatformTheme", "Yes to &All"), +}; + +#endif // TRANSLATION_H diff --git a/src/upgradewindow.cpp b/src/upgradewindow.cpp new file mode 100644 index 0000000..34ae7dc --- /dev/null +++ b/src/upgradewindow.cpp @@ -0,0 +1,221 @@ +#include +#include +#include +#include +#include + +#include "upgradewindow.h" +#include "ui_upgradewindow.h" + +UpgradeWindow::UpgradeWindow(QWidget *parent) : + QDialog(parent), + ui_(new Ui::UpgradeWindow) +{ + ui_->setupUi(this); + + // hide the question button from the title bar. + setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); +} + +UpgradeWindow::~UpgradeWindow() +{ + delete ui_; +} + +void UpgradeWindow::setMinerModels(const QStringSet &minerModels) +{ + minerModels_ = minerModels; + syncView(); +} + +void UpgradeWindow::setSelectedMinerModel(const QString &minerModel) +{ + ui_->minerModelList->setCurrentText(minerModel); + syncFirmwareList(minerModel); +} + +void UpgradeWindow::setFirmwareMap(const FirmwareMap &firmwareMap) +{ + firmwareMap_.clear(); + + for (auto itr = firmwareMap.begin(); itr!=firmwareMap.end(); itr++) { + const QString &model = itr.key(); + const auto &firmwares = itr.value(); + + auto &firmwareSet = firmwareMap_[model]; + + for (auto file = firmwares.begin(); file!=firmwares.end(); file++) { + QFileInfo finfo(*file); + + // append only if file exists + if (finfo.isFile()) { + firmwareSet.insert(*file); + } + } + } + syncView(); +} + +const QStringSet &UpgradeWindow::getMinerModels() +{ + return minerModels_; +} + +const FirmwareMap &UpgradeWindow::getFirmwareMap() +{ + return firmwareMap_; +} + +QString UpgradeWindow::getSelectedMinerModel() +{ + return ui_->minerModelList->currentText(); +} + +QString UpgradeWindow::getSelectedFirmware() +{ + return ui_->firmwareList->currentText(); +} + +bool UpgradeWindow::getKeepSettings() +{ + return ui_->keepSettingsCheck->isChecked(); +} + +void UpgradeWindow::updateMinerCounter(QString minerModel, size_t selected, size_t selectedDisabled, size_t all, size_t allDisabled) +{ + ui_->upgradeSelectedBtn->setEnabled(selected != 0); + ui_->upgradeAllBtn->setEnabled(all != 0); + + if (all == 0) { + ui_->minerNumberLabel->setText(tr("No miners found. Please rescan first.")); + } + else if (allDisabled == 0) { + ui_->minerNumberLabel->setText(tr("Selected %1: %2\n All %1: %3") + .arg(minerModel) + .arg(selected) + .arg(all)); + } else if (allDisabled < all) { + ui_->minerNumberLabel->setText(tr("Selected %1: %2 (CANNOT upgrade: %4)\n All %1: %3 (CANNOT upgrade: %5)") + .arg(minerModel) + .arg(selected) + .arg(all) + .arg(selectedDisabled) + .arg(allDisabled)); + } else { + ui_->minerNumberLabel->setText(tr("Upgrading this model is not yet supported.")); + } +} + +void UpgradeWindow::syncView() +{ + QStringList models = minerModels_.toList(); + qSort(models); + + ui_->minerModelList->clear(); + ui_->minerModelList->addItems(models); + + if (minerModels_.size() > 0) { + ui_->minerModelList->setCurrentIndex(0); + const QString &model = *models.begin(); + syncFirmwareList(model); + } +} + +void UpgradeWindow::syncFirmwareList(const QString &model) +{ + QStringList firmwares = firmwareMap_[model].toList(); + qSort(firmwares); + + ui_->firmwareList->clear(); + ui_->firmwareList->addItems(firmwares); + + if (firmwares.size() > 0) { + ui_->firmwareList->setCurrentIndex(0); + } +} + +bool UpgradeWindow::checkInputAndNotice() +{ + if (getSelectedMinerModel().isEmpty()) { + QMessageBox::information(this, tr("Miner Model is Empty"), tr("Please select a miner model.")); + return false; + } + + if (getSelectedFirmware().isEmpty()) { + QMessageBox::information(this, tr("Firmware is Empty"), tr("Please select or add a firmware.")); + return false; + } + + return true; +} + +void UpgradeWindow::on_minerModelList_currentTextChanged(const QString &model) +{ + syncFirmwareList(model); + emit selectedMinerModelChanged(model); +} + +void UpgradeWindow::on_addFirmwareBtn_clicked() +{ + const QString &model = getSelectedMinerModel(); + if (model.isEmpty()) { + QMessageBox::information(this, tr("Cannot add firmware"), tr("Miner model is empty, please rescan your miners.")); + return; + } + auto &firmwares = firmwareMap_[model]; + + QFileDialog fileDialog(this); + fileDialog.setWindowTitle(tr("Select Firmware")); + fileDialog.setNameFilter(tr("Firmware (*.bmu *.tar.gz)\nAll (*)")); + fileDialog.setFileMode(QFileDialog::ExistingFiles); + //fileDialog.setViewMode(QFileDialog::Detail); + //fileDialog.setDirectory("."); + + auto success = fileDialog.exec(); + if(!success) { + return; + } + + for (auto file : fileDialog.selectedFiles()) { + if (firmwares.find(file) == firmwares.end()) { + // add only if not in the set + firmwares.insert(file); + ui_->firmwareList->addItem(file); + ui_->firmwareList->setCurrentIndex(firmwares.size() - 1); + } + else { + // or just update the selection + ui_->firmwareList->setCurrentText(file); + } + } +} + +void UpgradeWindow::on_delFirmwareBtn_clicked() +{ + QString model = getSelectedMinerModel(); + QString firmware = getSelectedFirmware(); + + auto &set = firmwareMap_[model]; + auto index = ui_->firmwareList->currentIndex(); + + if (index >= 0) { + ui_->firmwareList->removeItem(index); + set.remove(firmware); + } +} + +void UpgradeWindow::on_upgradeSelectedBtn_clicked() +{ + if (checkInputAndNotice()) { + close(); + emit upgradeSelectedMiners(); + } +} + +void UpgradeWindow::on_upgradeAllBtn_clicked() +{ + if (checkInputAndNotice()) { + close(); + emit upgradeAllMiners(); + } +} diff --git a/src/upgradewindow.h b/src/upgradewindow.h new file mode 100644 index 0000000..3559501 --- /dev/null +++ b/src/upgradewindow.h @@ -0,0 +1,63 @@ +#ifndef UPGRADEWINDOW_H +#define UPGRADEWINDOW_H + +#include +#include +#include +#include +#include +#include +#include "utils.h" + +namespace Ui { +class UpgradeWindow; +} + +class UpgradeWindow : public QDialog +{ + Q_OBJECT + +public: + explicit UpgradeWindow(QWidget *parent = 0); + ~UpgradeWindow(); + + void setMinerModels(const QStringSet &minerModels); + void setSelectedMinerModel(const QString &minerModel); + void setFirmwareMap(const FirmwareMap &firmwareMap); + const QStringSet& getMinerModels(); + const FirmwareMap& getFirmwareMap(); + + QString getSelectedMinerModel(); + QString getSelectedFirmware(); + bool getKeepSettings(); + + void updateMinerCounter(QString minerModel, size_t selected, size_t selectedDisabled, size_t all, size_t allDisabled); + +signals: + void selectedMinerModelChanged(QString newMinerModel); + void upgradeSelectedMiners(); + void upgradeAllMiners(); + +private slots: + void on_minerModelList_currentTextChanged(const QString &model); + + void on_addFirmwareBtn_clicked(); + + void on_delFirmwareBtn_clicked(); + + void on_upgradeSelectedBtn_clicked(); + + void on_upgradeAllBtn_clicked(); + +private: + void syncView(); + void syncFirmwareList(const QString &model); + bool checkInputAndNotice(); + + Ui::UpgradeWindow *ui_; + + QStringSet minerModels_; + FirmwareMap firmwareMap_; +}; + +#endif // UPGRADEWINDOW_H diff --git a/src/utils.cpp b/src/utils.cpp new file mode 100644 index 0000000..ed9933e --- /dev/null +++ b/src/utils.cpp @@ -0,0 +1,540 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "utils.h" +#include "config.h" + +#ifdef WIN32 +#include +#endif + +using std::string; + +bool Utils::debugMode_ = false; + +QStringList Utils::getLanIpList() +{ + QList addrList = QNetworkInterface::allAddresses(); + QStringList lanIpList; + + foreach (QHostAddress addr, addrList) + { + QString addrStr = addr.toString(); + + // no localhost, only IPv4, no automatic private address + if (addr != QHostAddress::LocalHost && addr.toIPv4Address() && !addrStr.startsWith("169.254.")) + { + lanIpList.append(addrStr); + } + } + + return lanIpList; +} + +QString Utils::getInitIpRange() +{ + QString initIpRange = "192.168..0-192.168..255"; + + QStringList lanIpList = Utils::getLanIpList(); + + if (!lanIpList.isEmpty()) + { + QString lanIpPrefix = lanIpList.at(0); + + // get the first two parts from an IP, just likes "192.168" from "192.168.1.100". + lanIpPrefix = lanIpPrefix.left(lanIpPrefix.lastIndexOf('.', lanIpPrefix.lastIndexOf('.') - 1)); + + // it likes "192.168..0-192.168..255". The third part is empty and will be filled by the user. + initIpRange = lanIpPrefix + "..0-255"; + } + + return initIpRange; +} + +MinerHashrateList Utils::stringToMinerHashrateList(const QString &minerHashrateString) +{ + MinerHashrateList minerHashrateList; + QStringList params = minerHashrateString.split('&'); + + foreach (QString param, params) + { + QStringList keyValue = param.split('='); + + if (keyValue.size() != 2) + { + continue; + } + + minerHashrateList.append(MinerHashrate(keyValue.at(0), keyValue.at(1).toDouble())); + } + + return minerHashrateList; +} + +QString Utils::minerHashrateListToString(const MinerHashrateList &minerHashrateList) +{ + QStringList strList; + + foreach (auto minerHashrate, minerHashrateList) + { + QString minerType = minerHashrate.first.replace('=', ' ').replace('&', ' '); // strip the special characters. + double hashrate = minerHashrate.second; + + strList.append(QString("%1=%2").arg(minerType).arg(hashrate)); + } + + return strList.join('&'); +} + +MinerPasswordList Utils::stringToMinerPasswordList(const QString &minerPasswordString) +{ + MinerPasswordList minerPasswordList; + QStringList params = minerPasswordString.split('&'); + + foreach (QString param, params) + { + QStringList keyValue = param.split(':'); + + if (keyValue.size() != 3) + { + continue; + } + + minerPasswordList.append(MinerPassword( + QString(QByteArray::fromBase64(keyValue.at(0).toUtf8())), + QString(QByteArray::fromBase64(keyValue.at(1).toUtf8())), + QString(QByteArray::fromBase64(keyValue.at(2).toUtf8())) + )); + } + + return minerPasswordList; +} + +QString Utils::minerPasswordListToString(const MinerPasswordList &minerPasswordList) +{ + QStringList strList; + + foreach (auto minerPassword, minerPasswordList) + { + strList.append(QString("%1:%2:%3") + .arg(QString(minerPassword.minerType_.toUtf8().toBase64())) + .arg(QString(minerPassword.userName_.toUtf8().toBase64())) + .arg(QString(minerPassword.password_.toUtf8().toBase64())) + ); + } + + return strList.join('&'); +} + +MinerPassword* Utils::findMinerPassword(MinerPasswordList &minerPasswordList, const QString &minerType) +{ + for (int i=0; iminerType_.toLower())) + { + return p; + } + } + + return nullptr; +} + +double Utils::unitNumberToDouble(QString unitNumber) +{ + double result = 0; + + unitNumber = unitNumber.trimmed(); + + if (unitNumber.isEmpty()) + { + return 0; + } + + int pos = [unitNumber] () -> int { + for (int i=0; i '9')) + { + return i; + } + } + + return -1; + }(); + + if (pos >= 0) + { + QString num = unitNumber.left(pos).trimmed(); + char unit = unitNumber.right(unitNumber.size() - pos).trimmed().at(0).toUpper().toLatin1(); + + result = num.toDouble(); + + switch (unit) + { + case 'K': + result *= 1.0e3; + break; + case 'M': + result *= 1.0e6; + break; + case 'G': + result *= 1.0e9; + break; + case 'T': + result *= 1.0e12; + break; + case 'P': + result *= 1.0e15; + break; + case 'E': + result *= 1.0e18; + break; + case 'Z': + result *= 1.0e21; + break; + case 'Y': + result *= 1.0e24; + break; + } + } + else + { + result = unitNumber.toDouble(); + } + + return result; +} + +QString Utils::doubleToUnitNumber(double num) +{ + QString unit = ""; + + if (num >= 1.0e24) { + num /= 1.0e24; + unit = "Y"; + } + else if (num >= 1.0e21) { + num /= 1.0e21; + unit = "Z"; + } + else if (num >= 1.0e18) { + num /= 1.0e18; + unit = "E"; + } + else if (num >= 1.0e15) { + num /= 1.0e15; + unit = "P"; + } + else if (num >= 1.0e12) { + num /= 1.0e12; + unit = "T"; + } + else if (num >= 1.0e9) { + num /= 1.0e9; + unit = "G"; + } + else if (num >= 1.0e6) { + num /= 1.0e6; + unit = "M"; + } + else if (num >= 1.0e3) { + num /= 1.0e3; + unit = "k"; + } + + return QString("%1%2").arg(num).arg(unit); +} + +std::string Utils::readFirmwareToString(const QString &filePath) +{ + QFile file(filePath); + if (!file.open(QIODevice::ReadOnly)) { + throw std::runtime_error(QObject::tr("Cannot open firmware %1:\n%2").arg(filePath).arg(file.errorString()).toUtf8().data()); + } + + std::string data; + data.resize(file.size()); + + file.read((char*)data.data(), file.size()); + + return data; +} + +QString Utils::firmwareMapToString(const FirmwareMap &map) +{ + QStringList models; + for (auto mItr=map.begin(); mItr!=map.end(); mItr++) { + QStringList firmwares; + for (auto fItr=mItr.value().begin(); fItr!=mItr.value().end(); fItr++) { + firmwares.append(fItr->toUtf8().toBase64()); + } + if (firmwares.isEmpty()) { + continue; + } + models.append(mItr.key().toUtf8().toBase64() + ":" + firmwares.join(';')); + } + return models.join('&'); +} + +FirmwareMap Utils::stringToFirmwareMap(const QString &mapStr) +{ + FirmwareMap map; + + if (mapStr.isEmpty()) { + return map; + } + + QStringList models = mapStr.split('&'); + for (auto mItr=models.begin(); mItr!=models.end(); mItr++) { + QStringList kv = mItr->split(':'); + if (kv.size() >= 2) { + QString model = QString(QByteArray::fromBase64(kv[0].toUtf8())); + QStringList firmwares = kv[1].split(';'); + for (auto fItr=firmwares.begin(); fItr!=firmwares.end(); fItr++) { + *fItr = QString(QByteArray::fromBase64(fItr->toUtf8())); + } + map[model] = firmwares.toSet(); + } + } + + return map; +} + +QString Utils::getSubAccountName(const QString &workerName) +{ + QStringList parts = workerName.split('.'); + if (parts.isEmpty()) { + return ""; + } + return parts[0]; +} + +string Utils::getAnsiString(const QString &oriString) { + return QTextCodec::codecForLocale()->fromUnicode(oriString).data(); +} + +bool Utils::isDir(const QString &path) { + QDir dir(path); + return dir.exists(); +} + +bool Utils::isFile(const QString &path) { + QFile file(path); + return file.exists(); +} + +// Copied from +bool Utils::copyRecursively(const QString &srcFilePath, const QString &tgtFilePath) +{ + QFileInfo srcFileInfo(srcFilePath); + if (srcFileInfo.isDir()) { + QDir targetDir(tgtFilePath); + targetDir.cdUp(); + if (!targetDir.mkdir(QFileInfo(tgtFilePath).fileName())) + return false; + QDir sourceDir(srcFilePath); + QStringList fileNames = sourceDir.entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot | QDir::Hidden | QDir::System); + foreach (const QString &fileName, fileNames) { + const QString newSrcFilePath + = srcFilePath + QDir::separator() + fileName; + const QString newTgtFilePath + = tgtFilePath + QDir::separator() + fileName; + if (!copyRecursively(newSrcFilePath, newTgtFilePath)) + return false; + } + } else { + if (!QFile::copy(srcFilePath, tgtFilePath)) + return false; + } + return true; +} + + +///////////////////////// HTTP Client based libcurl //////////////////////// + +struct CurlChunk { + char *memory; + size_t size; +}; + +static size_t +CurlWriteChunkCallback(void *contents, size_t size, size_t nmemb, void *userp) +{ + size_t realsize = size * nmemb; + struct CurlChunk *mem = (struct CurlChunk *)userp; + + mem->memory = (char *)realloc(mem->memory, mem->size + realsize + 1); + if(mem->memory == NULL) { + /* out of memory! */ + printf("not enough memory (realloc returned NULL)\n"); + return 0; + } + + memcpy(&(mem->memory[mem->size]), contents, realsize); + mem->size += realsize; + mem->memory[mem->size] = 0; + + return realsize; +} + +int sslContextFunction(void* curl, void* sslctx, void* userdata); + +bool httpPOST(const char *url, const char *userpwd, const char *postData, + std::string &response, long timeoutMs, const char *contentType) { + struct curl_slist *headers = NULL; + CURLcode status; + long code; + CURL *curl = curl_easy_init(); + struct CurlChunk chunk; + if (!curl) { + return false; + } + + chunk.memory = (char *)malloc(1); /* will be grown as needed by the realloc above */ + chunk.size = 0; /* no data at this point */ + + // RSK doesn't support 'Expect: 100-Continue' in 'HTTP/1.1'. + // So switch to 'HTTP/1.0'. + curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0); + + if (contentType != nullptr) { + string mineHeader = string("Content-Type: ") + string(contentType); + headers = curl_slist_append(headers, mineHeader.c_str()); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); + } + + curl_easy_setopt(curl, CURLOPT_URL, url); + + if (postData != nullptr) { + curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, (long)strlen(postData)); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postData); + } + + if (userpwd != nullptr) { + curl_easy_setopt(curl, CURLOPT_USERPWD, userpwd); + } + + // Follow redirect + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1); + +#ifdef WIN32 + // Use Windows cert stores + curl_easy_setopt(curl, CURLOPT_SSL_CTX_FUNCTION, sslContextFunction); +#endif + + curl_easy_setopt(curl, CURLOPT_USE_SSL, CURLUSESSL_TRY); + + curl_easy_setopt(curl, CURLOPT_USERAGENT, "BTCTools/" APP_VERSION_NAME); + curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS, timeoutMs); + + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlWriteChunkCallback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&chunk); + + status = curl_easy_perform(curl); + if (status != 0) { + qDebug() << "unable to request data from: " << url << ", error: " << curl_easy_strerror(status); + goto error; + } + + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code); + if (code != 200) { + qDebug() << "server responded with code: " << code; + goto error; + } + + response.assign(chunk.memory, chunk.size); + + curl_easy_cleanup(curl); + curl_slist_free_all(headers); + free(chunk.memory); + return true; + + +error: + if (curl) + curl_easy_cleanup(curl); + if (headers) + curl_slist_free_all(headers); + + free(chunk.memory); + return false; +} + +bool httpGET(const char *url, string &response, long timeoutMs) { + return httpPOST(url, nullptr, nullptr, response, timeoutMs, nullptr); +} + +bool httpGET(const char *url, const char *userpwd, + string &response, long timeoutMs) { + return httpPOST(url, userpwd, nullptr, response, timeoutMs, nullptr); +} + +#ifdef WIN32 +// Use Windows cert store +// From +std::vector m_trustedCertificateList; + +void addCertificatesForStore(LPCWSTR name) +{ + HCERTSTORE storeHandle = CertOpenSystemStore(NULL, name); + + if (storeHandle == nullptr) + { + return; + } + + PCCERT_CONTEXT windowsCertificate = CertEnumCertificatesInStore(storeHandle, nullptr); + while (windowsCertificate != nullptr) + { + X509 *opensslCertificate = d2i_X509(nullptr, const_cast(&windowsCertificate->pbCertEncoded), windowsCertificate->cbCertEncoded); + if (opensslCertificate == nullptr) + { + printf("A certificate could not be converted"); + } + else + { + m_trustedCertificateList.push_back(opensslCertificate); + } + + windowsCertificate = CertEnumCertificatesInStore(storeHandle, windowsCertificate); + } + + CertCloseStore(storeHandle, 0); +} + +void initCertStore() +{ + addCertificatesForStore(L"CA"); + addCertificatesForStore(L"AuthRoot"); + addCertificatesForStore(L"ROOT"); +} + +void setupSslContext(SSL_CTX* context) +{ + initCertStore(); + + X509_STORE* certStore = SSL_CTX_get_cert_store(context); + for(X509 *x509 : m_trustedCertificateList) + { + X509_STORE_add_cert(certStore, x509); + } +} + +int sslContextFunction(void* curl, void* sslctx, void* userdata) +{ + setupSslContext(reinterpret_cast(sslctx)); + return CURLE_OK; +} +#endif diff --git a/src/utils.h b/src/utils.h new file mode 100644 index 0000000..0d600bc --- /dev/null +++ b/src/utils.h @@ -0,0 +1,95 @@ +#ifndef UTILS_H +#define UTILS_H + +#include +#include +#include +#include +#include +#include +#include + +using QStringSet = QSet; +using FirmwareMap = QMap; + +using MinerHashrate = QPair; +using MinerHashrateList = QList; + +// +// IPv4 Internet address +// This is an 'on-wire' format structure. +// From in Windows SDK +// +struct in_addr_btctools { + union { + struct { uint8_t s_b1,s_b2,s_b3,s_b4; } S_un_b; + struct { uint16_t s_w1,s_w2; } S_un_w; + uint32_t S_addr; + } S_un; +}; + +typedef struct MinerPassword { + QString minerType_; + QString userName_; + QString password_; + + MinerPassword(QString minerType, QString userName, QString password) + : minerType_(minerType), userName_(userName), password_(password) + { + // empty body + } +} MinerPassword; + +using MinerPasswordList = QList; + + +class Utils +{ +private: + static bool debugMode_; + +public: + inline static void enableDebugMode(bool enabled = true) { + debugMode_ = enabled; + } + inline static bool debugMode() { + return debugMode_; + } + + static QStringList getLanIpList(); + static QString getInitIpRange(); + + static QString minerHashrateListToString(const MinerHashrateList &minerHashrateList); + static MinerHashrateList stringToMinerHashrateList(const QString &minerHashrateString); + + static QString minerPasswordListToString(const MinerPasswordList &minerPasswordList); + static MinerPasswordList stringToMinerPasswordList(const QString &minerPasswordString); + static MinerPassword* findMinerPassword(MinerPasswordList &minerPasswordList, const QString &minerType); + + static double unitNumberToDouble(QString unitNumber); + static QString doubleToUnitNumber(double num); + + static std::string readFirmwareToString(const QString &filePath); + static QString firmwareMapToString(const FirmwareMap &map); + static FirmwareMap stringToFirmwareMap(const QString &mapStr); + + static QString getSubAccountName(const QString &workerName); + + static std::string getAnsiString(const QString &string); + + static bool isDir(const QString &path); + static bool isFile(const QString &path); + static bool copyRecursively(const QString &sourceFolder, const QString &destFolder); +}; + + +///////////////////////// HTTP Client based libcurl //////////////////////// + +bool httpGET (const char *url, std::string &response, long timeoutMs = 0); +bool httpGET (const char *url, const char *userpwd, + std::string &response, long timeoutMs = 0); +bool httpPOST(const char *url, const char *userpwd, const char *postData, + std::string &response, long timeoutMs = 0, const char *contentType = nullptr); + + +#endif // UTILS_H