Browse Source

部分代码进行翻译处理

master
张献维 8 months ago
parent
commit
6af8a23606
  1. 33
      .gitignore
  2. 6
      golang_learn/data_func/go_to_excel/demo03/README.md
  3. 6
      golang_learn/data_func/go_to_excel/demo03/build.bat
  4. 21
      golang_learn/data_func/go_to_excel/demo03/main.exe.manifest
  5. 190
      golang_learn/data_func/go_to_excel/demo03/main.go
  6. BIN
      golang_learn/data_func/go_to_excel/demo03/main.syso
  7. BIN
      golang_learn/data_func/go_to_excel/demo03/rc.ico
  8. 2
      golang_learn/data_func/go_to_git/main.go
  9. 17
      golang_learn/data_func/go_to_http/demo04/main.go
  10. 55
      golang_learn/data_func/go_to_pdf/main.go
  11. 21
      golang_learn/data_office/get_hzb_xml/go_read_hzb/main.exe.manifest
  12. 90
      golang_learn/data_office/get_hzb_xml/go_read_hzb/main.go
  13. BIN
      golang_learn/data_office/get_hzb_xml/go_read_hzb/main.syso
  14. BIN
      golang_learn/data_office/get_hzb_xml/go_read_hzb/rc.ico
  15. 1
      golang_learn/data_office/get_hzb_xml/go_read_hzb/说明.md
  16. 11
      golang_learn/go.mod
  17. 48
      golang_learn/go.sum
  18. 1
      gui_wails01_learn/.gitignore
  19. 6
      gui_wails01_learn/backend/api/rpc.go
  20. 42
      gui_wails01_learn/backend/app.go
  21. 24
      gui_wails01_learn/backend/assemblywrapper.go
  22. 90
      gui_wails01_learn/backend/buildchain.go
  23. 17
      gui_wails01_learn/backend/cmd/update/main.go
  24. 27
      gui_wails01_learn/backend/dashboard.go
  25. 4
      gui_wails01_learn/backend/lcm.go
  26. 45
      gui_wails01_learn/backend/login.go
  27. 2
      gui_wails01_learn/backend/models/address.go
  28. 2
      gui_wails01_learn/backend/models/path.go
  29. 2
      gui_wails01_learn/backend/models/txhistory.go
  30. 2
      gui_wails01_learn/backend/models/wallet.go
  31. 79
      gui_wails01_learn/backend/settings.go
  32. 89
      gui_wails01_learn/backend/transactions.go
  33. 6
      gui_wails01_learn/backend/utils.go
  34. 190
      gui_wails01_learn/backend/wallet.go
  35. 20364
      gui_wails01_learn/frontend/package-lock.json
  36. 87
      gui_wails01_learn/frontend/package.json
  37. 6
      gui_wails01_learn/frontend/src/App.vue
  38. 7
      gui_wails01_learn/frontend/src/pages/Login.vue
  39. 5
      gui_wails01_learn/main.go

33
.gitignore

@ -12,35 +12,18 @@
*.out *.out
/.idea/.gitignore /.idea/.gitignore
/golang_frame/go_to_beego/.idea/.gitignore /golang_frame/go_to_beego/.idea/
/golang_frame/go_to_gin/go_to_gin_init/.idea/.gitignore /golang_frame/go_to_gin/go_to_gin_init/.idea/
/golang_frame/go_to_iris/.idea/.gitignore /golang_frame/go_to_iris/.idea/
/golang_frame/go_to_gin/go_to_gin_init/go.sum /golang_frame/go_to_gin/go_to_gin_init/go.sum
/golang_frame/go_to_beego/.idea/go_to_beego.iml
/golang_frame/go_to_gin/go_to_gin_init/.idea/go_to_gin_init.iml
/golang_frame/go_to_iris/.idea/go_to_iris.iml
/golang_learn/.idea/golang_learn.iml
/.idea/golangs_learn.iml /.idea/golangs_learn.iml
/.idea/misc.xml /.idea/misc.xml
/golang_frame/go_to_beego/.idea/misc.xml
/golang_frame/go_to_gin/go_to_gin_init/.idea/misc.xml
/golang_frame/go_to_iris/.idea/misc.xml
/.idea/modules.xml /.idea/modules.xml
/golang_frame/go_to_beego/.idea/modules.xml
/golang_frame/go_to_gin/go_to_gin_init/.idea/modules.xml
/golang_frame/go_to_iris/.idea/modules.xml
/.idea/vcs.xml /.idea/vcs.xml
/golang_frame/go_to_beego/.idea/vcs.xml /golang_frame/.idea/
/golang_frame/go_to_gin/go_to_gin_init/.idea/vcs.xml /golang_learn/.idea/
/golang_frame/go_to_iris/.idea/vcs.xml /golang_frame/frame阅读/.idea/
/golang_frame/.idea/.gitignore
/golang_learn/.idea/.gitignore
/golang_frame/.idea/golang_frame.iml
/golang_frame/.idea/misc.xml
/golang_learn/.idea/misc.xml
/golang_frame/.idea/modules.xml
/golang_learn/.idea/modules.xml
/golang_frame/.idea/vcs.xml
/golang_learn/.idea/vcs.xml
/golang_learn/data_func/go_to_TjHz/J21/J2101.go /golang_learn/data_func/go_to_TjHz/J21/J2101.go
*/frontend/dist/* */frontend/dist/*
*.csv
*.log

6
golang_learn/data_func/go_to_excel/demo03/README.md

@ -0,0 +1,6 @@
# 说明
> 实现读取同目录下的目录A下的excel文件,并将列3出现重复数据输出到文件a-日期.csv文件中
>
>

6
golang_learn/data_func/go_to_excel/demo03/build.bat

@ -0,0 +1,6 @@
set GO111MODULE=on
SET CGO_ENABLED=0
set GOARCH=386
rsrc -manifest main.manifest -ico main.ico -o main.syso
go generate
go build -ldflags="-s -w -H=windowsgui" -o main.exe

21
golang_learn/data_func/go_to_excel/demo03/main.exe.manifest

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity
version="1.0.0.0"
processorArchitecture="x86"
name="controls"
type="win32"
></assemblyIdentity>
<dependency>
<dependentAssembly>
<assemblyIdentity
type="win32"
name="Microsoft.Windows.Common-Controls"
version="6.0.0.0"
processorArchitecture="*"
publicKeyToken="6595b64144ccf1df"
language="*"
></assemblyIdentity>
</dependentAssembly>
</dependency>
</assembly>

190
golang_learn/data_func/go_to_excel/demo03/main.go

@ -0,0 +1,190 @@
package main
import (
"encoding/csv"
"fmt"
"os"
"path/filepath"
"strings"
"time"
"github.com/360EntSecGroup-Skylar/excelize"
"github.com/extrame/xls"
)
type Data struct {
File string
Xh string
A0101 string
A0184 string
}
var end []Data
func main() {
dir, err := os.Getwd()
if err != nil {
fmt.Println("获取当前目录失败:", err)
return
}
_, err = os.Stat(dir + "/a")
if os.IsNotExist(err) {
fmt.Println("目录下不存在 'a' 目录,程序停止。")
os.Exit(1)
}
dir = dir + "/a"
file := ""
// 数据读取
err = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if !info.IsDir() {
if filepath.Ext(path) == ".xlsx" {
err := readXlsxFile(path)
if err != nil {
file = file + path + "\n"
}
} else if filepath.Ext(path) == ".xls" {
err := readXlsFile(path)
if err != nil {
file = file + path + "\n"
}
}
}
return nil
})
if err != nil {
_ = fmt.Errorf("读取文件发生根本性错误")
}
// 数据对比
m := convert(end)
var allData []Data
for s := range m {
if len(m[s]) > 1 {
for i := range m[s] {
allData = append(allData, m[s][i])
}
}
}
aa := time.Now().Format("20060102150405")
outputFile, err := os.Create("相同文件-" + aa + ".csv")
if err != nil {
_ = fmt.Errorf("生成保存问题发生错误")
}
defer func(outputFile *os.File) {
err := outputFile.Close()
if err != nil {
}
}(outputFile)
if len(file) > 0 {
err = writeToFile("报错文件-"+aa+".log", file)
if err != nil {
return
}
}
writer := csv.NewWriter(outputFile)
defer writer.Flush()
err = writer.Write([]string{"重复值", "来源文件", "行号", "姓名"})
if err != nil {
_ = fmt.Errorf("写头信息发生错误")
}
for _, d := range allData {
err := writer.Write([]string{"`" + d.A0184, d.File, d.Xh, d.A0101})
if err != nil {
_ = fmt.Errorf("书写内容时,发生错误:%s", d)
}
}
fmt.Println("处理完成,结果输出到 a.csv 文件。")
}
func writeToFile(filename string, content string) error {
file, err := os.OpenFile(filename, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
return err
}
defer func(file *os.File) {
err := file.Close()
if err != nil {
}
}(file)
_, err = file.WriteString(content)
if err != nil {
return err
}
return nil
}
// readXlsFile 读取xls文件
func readXlsxFile(filePath string) error {
f, err := excelize.OpenFile(filePath)
if err != nil {
fmt.Println("无法正常读取excel内容:", err)
return err
}
// defer f.Close()
for i := range f.GetSheetMap() {
rows := f.GetRows(f.GetSheetMap()[i])
fmt.Println("Reading xlsx file:", filePath)
for _, row := range rows {
if len(row) > 2 {
if row != nil && !strings.Contains(row[0], "序号") {
a := Data{filePath, r(row[0]), r(row[1]), r(row[2])}
end = append(end, a)
fmt.Println(a)
}
}
}
}
return nil
}
// readXlsxFile 读取xlsx文件
func readXlsFile(filePath string) error {
xlsFile, err := xls.Open(filePath, "utf-8")
if err != nil {
fmt.Println("无法正常读取excel内容:", err)
return err
}
sheet := xlsFile.GetSheet(0)
if sheet != nil {
fmt.Println("读取文件:", filePath)
for i := 0; i <= int(sheet.MaxRow); i++ {
row := sheet.Row(i)
if row != nil && !strings.Contains(row.Col(0), "序号") {
a := Data{filePath, r(row.Col(0)), r(row.Col(1)), r(row.Col(2))}
end = append(end, a)
fmt.Println(a)
}
}
}
return nil
}
func r(s string) string {
return strings.ReplaceAll(s, "\n", "")
}
// convert 数据分组展示
func convert(dataSlice []Data) map[string][]Data {
result := make(map[string][]Data)
for _, data := range dataSlice {
result[data.A0184] = append(result[data.A0184], data)
}
return result
}

BIN
golang_learn/data_func/go_to_excel/demo03/main.syso

Binary file not shown.

BIN
golang_learn/data_func/go_to_excel/demo03/rc.ico

Binary file not shown.

After

Width:  |  Height:  |  Size: 264 KiB

2
golang_learn/data_func/go_to_git/main.go

@ -12,7 +12,7 @@ import (
func main() { func main() {
projectDir := "E:\\Code-Java\\全国公务员\\" // 替换为指定目录 p 的路径 projectDir := "E:\\Code-Java\\全国公务员\\" // 替换为指定目录 p 的路径
author := "张献维" // 替换为指定的用户名称 author := "张献维" // 替换为指定的用户名称
since := "2023-12-12" // 替换为指定的开始日期 since := "2023-11-16" // 替换为指定的开始日期
// 更改工作目录到指定目录 p // 更改工作目录到指定目录 p
if err := os.Chdir(projectDir); err != nil { if err := os.Chdir(projectDir); err != nil {

17
golang_learn/data_func/go_to_http/demo04/main.go

@ -0,0 +1,17 @@
package main
import (
"log"
"net/http"
)
func handler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
jsonResponse := `{"a0184":"123456789"}`
w.Write([]byte(jsonResponse))
}
func main() {
http.HandleFunc("/demo", handler)
log.Fatal(http.ListenAndServe(":8080", nil))
}

55
golang_learn/data_func/go_to_pdf/main.go

@ -0,0 +1,55 @@
package main
import (
"fmt"
"os"
"pdfcpu/pkg/pdfcpu"
"time"
)
func mergePDFs(pdfDir string) {
// 检查 pdfs 目录是否存在
_, err := os.Stat(pdfDir)
if err != nil {
// 如果不存在,则创建该目录
err = os.Mkdir(pdfDir, 0755)
if err != nil {
fmt.Println("创建 pdfs 目录失败:", err)
return
}
}
// 打开 pdfs 目录
files, err := os.ReadDir(pdfDir)
if err != nil {
fmt.Println("读取 pdfs 目录失败:", err)
return
}
// 关闭 pdfs 目录
err = files.Close()
if err != nil {
fmt.Println("关闭 pdfs 目录失败:", err)
return
}
// 获取当前时间
t := time.Now()
// 格式化时间为 yyyyMMddhhmmss
formattedTime := t.Format("20060102150405")
// 合并 PDF 文件
mergedPDFPath := fmt.Sprintf("%s/merged-%s.pdf", pdfDir, formattedTime)
err = pdfcpu.CombineFiles(pdfDir, mergedPDFPath)
if err != nil {
fmt.Println("合并 PDF 文件失败:", err)
return
}
fmt.Println("PDF 文件合并成功")
}
func main() {
// 指定要合并 PDF 文件的目录路径
pdfDir := "path/to/your/pdfs/directory"
mergePDFs(pdfDir)
}

21
golang_learn/data_office/get_hzb_xml/go_read_hzb/main.exe.manifest

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity
version="1.0.0.0"
processorArchitecture="x86"
name="controls"
type="win32"
></assemblyIdentity>
<dependency>
<dependentAssembly>
<assemblyIdentity
type="win32"
name="Microsoft.Windows.Common-Controls"
version="6.0.0.0"
processorArchitecture="*"
publicKeyToken="6595b64144ccf1df"
language="*"
></assemblyIdentity>
</dependentAssembly>
</dependency>
</assembly>

90
golang_learn/data_office/get_hzb_xml/go_read_hzb/main.go

@ -0,0 +1,90 @@
package main
import (
"fmt"
"os"
"os/exec"
"runtime"
)
func executeCommand(command string) error {
var cmd *exec.Cmd
switch runtime.GOOS {
case "linux":
cmd = exec.Command("bash", "-c", command)
case "windows":
cmd = exec.Command("cmd", "/C", command)
default:
return fmt.Errorf("unsupported operating system")
}
output, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("error executing command: %v. Output: %s", err, output)
}
return nil
}
func fileExists(path string) bool {
_, err := os.Stat(path)
if err == nil {
return true
}
if os.IsNotExist(err) {
return false
}
return false
}
func main() {
dir, err := os.Getwd()
if err != nil {
fmt.Println("获取当前目录失败:", err)
return
}
fmt.Println("当前目录为:", dir)
dir = "D:\\hzb2023\\hzb2023"
// 判断是否存在 a.hzb
if !fileExists(dir + "/a.hzb") {
fmt.Println("a.hzb 文件不存在。")
return
}
// 判断是否存在 tomcat8 目录
if !fileExists(dir + "/tomcat8") {
fmt.Println("tomcat8 目录不存在。")
return
}
// 判断是否存在 a 目录,如果存在则删除并重新创建
if fileExists(dir + "/a") {
err := os.RemoveAll(dir + "/a")
if err != nil {
fmt.Println("删除目录 a 失败:", err)
return
}
}
err = os.Mkdir(dir+"/a", os.ModePerm)
if err != nil {
fmt.Println("创建目录 a 失败:", err)
return
}
err = os.Mkdir(dir+"/a/Photos", os.ModePerm)
if err != nil {
fmt.Println("创建目录 a/Photos 失败:", err)
return
}
result := fmt.Sprintf("cd %s/tomcat8/webapps/qggwy/WEB-INF/classes/static/drc/ && "+
"set Hy7zEncryptPath=%s/tomcat8/webapps/qggwy/WEB-INF/classes/static/drc/fremjkn.drc && "+
"set Hy7zJarPath=%s/tomcat8/webapps/qggwy/WEB-INF/classes/static/drc/ergny.jth && "+
"%s/tomcat8/JDK1.8/jre/bin/java -jar -Xms512m -XX:PermSize=256M -XX:MaxPermSize=512M "+
"%s/tomcat8/webapps/qggwy/WEB-INF/classes/static/drc/ergny.jth unzip %s/a.hzb %s/a %s/a/Photos Z93z4H5b6z7d35b8",
dir, dir, dir, dir, dir, dir, dir, dir)
err = executeCommand(result)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
}

BIN
golang_learn/data_office/get_hzb_xml/go_read_hzb/main.syso

Binary file not shown.

BIN
golang_learn/data_office/get_hzb_xml/go_read_hzb/rc.ico

Binary file not shown.

After

Width:  |  Height:  |  Size: 264 KiB

1
golang_learn/data_office/get_hzb_xml/go_read_hzb/说明.md

@ -0,0 +1 @@
手动解压文件操作

11
golang_learn/go.mod

@ -15,6 +15,8 @@ require (
github.com/bitly/go-simplejson v0.5.0 github.com/bitly/go-simplejson v0.5.0
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869
github.com/braintree/manners v0.0.0-20160418043613-82a8879fc5fd github.com/braintree/manners v0.0.0-20160418043613-82a8879fc5fd
github.com/extrame/ole2 v0.0.0-20160812065207-d69429661ad7 // indirect
github.com/extrame/xls v0.0.1
github.com/go-ini/ini v1.62.0 github.com/go-ini/ini v1.62.0
github.com/go-sql-driver/mysql v1.6.0 github.com/go-sql-driver/mysql v1.6.0
github.com/gobwas/glob v0.2.3 // indirect github.com/gobwas/glob v0.2.3 // indirect
@ -35,6 +37,7 @@ require (
github.com/mattn/go-sqlite3 v2.0.3+incompatible github.com/mattn/go-sqlite3 v2.0.3+incompatible
github.com/mitchellh/go-homedir v1.0.0 github.com/mitchellh/go-homedir v1.0.0
github.com/mzky/zip v0.0.0-20210817031838-1b692015aed2 github.com/mzky/zip v0.0.0-20210817031838-1b692015aed2
github.com/pdfcpu/pdfcpu v0.8.0 // indirect
github.com/pingcap/log v0.0.0-20211215031037-e024ba4eb0ee github.com/pingcap/log v0.0.0-20211215031037-e024ba4eb0ee
github.com/riftbit/go-systray v1.0.2 github.com/riftbit/go-systray v1.0.2
github.com/robfig/cron/v3 v3.0.1 github.com/robfig/cron/v3 v3.0.1
@ -48,11 +51,11 @@ require (
github.com/twgh/xcgui v1.3.360 github.com/twgh/xcgui v1.3.360
github.com/xuri/excelize/v2 v2.5.0 github.com/xuri/excelize/v2 v2.5.0
github.com/yeka/zip v0.0.0-20180914125537-d046722c6feb github.com/yeka/zip v0.0.0-20180914125537-d046722c6feb
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519
golang.org/x/exp/errors v0.0.0-20210715201039-d37aa40e8013 golang.org/x/exp/errors v0.0.0-20210715201039-d37aa40e8013
golang.org/x/image v0.0.0-20210220032944-ac19c3e999fb golang.org/x/image v0.15.0
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e golang.org/x/sys v0.5.0
golang.org/x/text v0.3.7 golang.org/x/text v0.14.0
gopkg.in/Knetic/govaluate.v3 v3.0.0 // indirect gopkg.in/Knetic/govaluate.v3 v3.0.0 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v2 v2.4.0

48
golang_learn/go.sum

@ -127,6 +127,10 @@ github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5y
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/extrame/ole2 v0.0.0-20160812065207-d69429661ad7 h1:n+nk0bNe2+gVbRI8WRbLFVwwcBQ0rr5p+gzkKb6ol8c=
github.com/extrame/ole2 v0.0.0-20160812065207-d69429661ad7/go.mod h1:GPpMrAfHdb8IdQ1/R2uIRBsNfnPnwsYE9YYI5WyY1zw=
github.com/extrame/xls v0.0.1 h1:jI7L/o3z73TyyENPopsLS/Jlekm3nF1a/kF5hKBvy/k=
github.com/extrame/xls v0.0.1/go.mod h1:iACcgahst7BboCpIMSpnFs4SKyU9ZjsvZBfNbUxZOJI=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fredbi/uri v0.0.0-20181227131451-3dcfdacbaaf3 h1:FDqhDm7pcsLhhWl1QtD8vlzI4mm59llRvNzrFg6/LAA= github.com/fredbi/uri v0.0.0-20181227131451-3dcfdacbaaf3 h1:FDqhDm7pcsLhhWl1QtD8vlzI4mm59llRvNzrFg6/LAA=
github.com/fredbi/uri v0.0.0-20181227131451-3dcfdacbaaf3/go.mod h1:CzM2G82Q9BDUvMTGHnXf/6OExw/Dz2ivDj48nVg7Lg8= github.com/fredbi/uri v0.0.0-20181227131451-3dcfdacbaaf3/go.mod h1:CzM2G82Q9BDUvMTGHnXf/6OExw/Dz2ivDj48nVg7Lg8=
@ -269,6 +273,10 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/hhrutter/lzw v1.0.0 h1:laL89Llp86W3rRs83LvKbwYRx6INE8gDn0XNb1oXtm0=
github.com/hhrutter/lzw v1.0.0/go.mod h1:2HC6DJSn/n6iAZfgM3Pg+cP1KxeWc3ezG8bBqW5+WEo=
github.com/hhrutter/tiff v1.0.1 h1:MIus8caHU5U6823gx7C6jrfoEvfSTGtEFRiM8/LOzC0=
github.com/hhrutter/tiff v1.0.1/go.mod h1:zU/dNgDm0cMIa8y8YwcYBeuEEveI4B0owqHyiPpJPHc=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
@ -323,6 +331,7 @@ github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaO
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-oci8 v0.1.1 h1:aEUDxNAyDG0tv8CA3TArnDQNyc4EhnWlsfxRgDHABHM= github.com/mattn/go-oci8 v0.1.1 h1:aEUDxNAyDG0tv8CA3TArnDQNyc4EhnWlsfxRgDHABHM=
github.com/mattn/go-oci8 v0.1.1/go.mod h1:wjDx6Xm9q7dFtHJvIlrI99JytznLw5wQ4R+9mNXJwGI= github.com/mattn/go-oci8 v0.1.1/go.mod h1:wjDx6Xm9q7dFtHJvIlrI99JytznLw5wQ4R+9mNXJwGI=
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U= github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U=
github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
@ -353,6 +362,8 @@ github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W
github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg= github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pdfcpu/pdfcpu v0.8.0 h1:SuEB4uVsPFz1nb802r38YpFpj9TtZh/oB0bGG34IRZw=
github.com/pdfcpu/pdfcpu v0.8.0/go.mod h1:jj03y/KKrwigt5xCi8t7px2mATcKuOzkIOoCX62yMho=
github.com/pelletier/go-toml v1.0.1/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.0.1/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pelletier/go-toml v1.9.3 h1:zeC5b1GviRUyKYd6OJPvBU/mcVDVoL1OhT17FCt5dSQ= github.com/pelletier/go-toml v1.9.3 h1:zeC5b1GviRUyKYd6OJPvBU/mcVDVoL1OhT17FCt5dSQ=
@ -392,6 +403,8 @@ github.com/richardlehane/msoleps v1.0.1 h1:RfrALnSNXzmXLbGct/P2b4xkFz4e8Gmj/0Vj9
github.com/richardlehane/msoleps v1.0.1/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg= github.com/richardlehane/msoleps v1.0.1/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
github.com/riftbit/go-systray v1.0.2 h1:8cT4UEREfN3NxpErxHC/7G8cR/t/hAiO/nZng5pmSAQ= github.com/riftbit/go-systray v1.0.2 h1:8cT4UEREfN3NxpErxHC/7G8cR/t/hAiO/nZng5pmSAQ=
github.com/riftbit/go-systray v1.0.2/go.mod h1:4Gw5HqutBhqXW3sRq41ggHZZqC1k/KOU4dix6G6frj0= github.com/riftbit/go-systray v1.0.2/go.mod h1:4Gw5HqutBhqXW3sRq41ggHZZqC1k/KOU4dix6G6frj0=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
@ -477,8 +490,9 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.3.8 h1:Nw158Q8QN+CPgTmVRByhVwapp8Mm1e2blinhmx4wx5E=
github.com/yuin/goldmark v1.3.8/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.3.8/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.13 h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yuin/gopher-lua v0.0.0-20171031051903-609c9cd26973/go.mod h1:aEV29XrmTYFr3CiRxZeGHpkvbwq+prZduBqMaascyCU= github.com/yuin/gopher-lua v0.0.0-20171031051903-609c9cd26973/go.mod h1:aEV29XrmTYFr3CiRxZeGHpkvbwq+prZduBqMaascyCU=
go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
@ -509,8 +523,9 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@ -527,8 +542,10 @@ golang.org/x/exp/errors v0.0.0-20210715201039-d37aa40e8013/go.mod h1:YgqsNsAu4fT
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.0.0-20210220032944-ac19c3e999fb h1:fqpd0EBDzlHRCjiphRR5Zo/RSWWQlWv34418dnEixWk=
golang.org/x/image v0.0.0-20210220032944-ac19c3e999fb/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20210220032944-ac19c3e999fb/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.11.0/go.mod h1:bglhjqbqVuEb9e9+eNR45Jfu7D+T4Qan+NhQk8Ck2P8=
golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8=
golang.org/x/image v0.15.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@ -553,6 +570,8 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -596,8 +615,10 @@ golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLd
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd h1:O7DYs+zxREGLKzKoMQrtrEacpb0ZVXA5rIwylE2Xchk=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0 h1:L4ZwwTvKW9gr0ZMS1yrHD9GZhIuVjOBBnaKH+SPQK0Q=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@ -621,6 +642,8 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -677,11 +700,15 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -690,8 +717,11 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@ -749,8 +779,10 @@ golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4f
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.5 h1:ouewzE6p+/VEB31YYnTbEJdi8pFqKp4P4n85vwo3DHA=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

1
gui_wails01_learn/.gitignore

@ -7,3 +7,4 @@ mollywallet.rc
build build
.DS_Store .DS_Store
/.idea/ /.idea/
/frontend/npminstall-debug.log

6
gui_wails01_learn/backend/api/rpc.go

@ -15,8 +15,8 @@ type Signal struct {
Msg string Msg string
} }
// InitRPCServer initializes the RPC server that listens to incoming LCM tasks // InitRPCServer 初始化监听传入LCM任务的RPC服务器
// by the RPC clients // 由RPC客户端执行
func InitRPCServer() error { func InitRPCServer() error {
endpoints := new(RPCEndpoints) endpoints := new(RPCEndpoints)
// Publish the receivers methods // Publish the receivers methods
@ -38,7 +38,7 @@ func InitRPCServer() error {
go func() { go func() {
err = http.Serve(listener, nil) err = http.Serve(listener, nil)
if err != nil { if err != nil {
errs <- fmt.Errorf("Error serving: %v", err) errs <- fmt.Errorf("error serving: %v", err)
return return
} }
}() }()

42
gui_wails01_learn/backend/app.go

@ -15,8 +15,8 @@ import (
"github.com/grvlle/constellation_wallet/backend/models" "github.com/grvlle/constellation_wallet/backend/models"
) )
// WalletApplication holds all application specific objects // WalletApplication 保存所有特定于应用程序的对象
// such as the Client/Server event bus and logger // 例如客户端/服务器事件总线和记录器
type WalletApplication struct { type WalletApplication struct {
Version string Version string
RT *wails.Runtime RT *wails.Runtime
@ -72,25 +72,28 @@ type WalletApplication struct {
} }
} }
// WailsShutdown is called when the application is closed // WailsShutdown 在应用程序关闭时调用
func (a *WalletApplication) WailsShutdown() { func (a *WalletApplication) WailsShutdown() {
a.wallet = models.Wallet{} a.wallet = models.Wallet{}
close(a.killSignal) // Kills the Go Routines close(a.killSignal) //杀死 Go 例程
a.DB.Close() a.DB.Close()
} }
// WailsInit initializes the Client and Server side bindings // WailsInit 初始化客户端和服务器端绑定
func (a *WalletApplication) WailsInit(runtime *wails.Runtime) error { func (a *WalletApplication) WailsInit(runtime *wails.Runtime) error {
var err error var err error
// 初始化日志对象
a.log = logrus.New() a.log = logrus.New()
err = a.initDirectoryStructure() err = a.initDirectoryStructure()
if err != nil { if err != nil {
a.log.Errorln("Unable to set up directory structure. Reason: ", err) a.log.Errorln("无法设置目录结构。原因: ", err)
} }
// 初始化输出日志
a.initLogger() a.initLogger()
// 初始化 RPC 服务
err = api.InitRPCServer() err = api.InitRPCServer()
if err != nil { if err != nil {
a.log.Panicf("Unable to initialize RPC Server. Reason: %v", err) a.log.Panicf("Unable to initialize RPC Server. Reason: %v", err)
@ -101,18 +104,20 @@ func (a *WalletApplication) WailsInit(runtime *wails.Runtime) error {
a.NewUser = false a.NewUser = false
a.TransactionFinished = true a.TransactionFinished = true
a.RT = runtime a.RT = runtime
a.killSignal = make(chan struct{}) // Used to kill go routines and hand back system resources a.killSignal = make(chan struct{}) // 用于终止go例程并交还系统资源
a.wallet.Currency = "USD" // Set default currency a.wallet.Currency = "USD" // 设置默认货币
a.WalletCLI.URL = "https://github.com/Constellation-Labs/constellation/releases/download" a.WalletCLI.URL = "https://github.com/Constellation-Labs/constellation/releases/download"
a.WalletCLI.Version = "2.6.0" a.WalletCLI.Version = "2.6.0"
a.Version = "1.2.0" a.Version = "1.2.0"
// 创建数据库连接
a.DB, err = gorm.Open("sqlite3", a.paths.DAGDir+"/store.db") a.DB, err = gorm.Open("sqlite3", a.paths.DAGDir+"/store.db")
if err != nil { if err != nil {
a.log.Panicln("failed to connect database", err) a.log.Panicln("failed to connect database", err)
} }
// Migrate the schema // Migrate the schema::迁移架构
a.DB.AutoMigrate(&models.Wallet{}, &models.TXHistory{}, &models.Path{}) a.DB.AutoMigrate(&models.Wallet{}, &models.TXHistory{}, &models.Path{})
// 查询 JDK 目录信息
a.detectJavaPath() a.detectJavaPath()
a.initMainnetConnection() a.initMainnetConnection()
a.newReleaseAvailable() a.newReleaseAvailable()
@ -134,7 +139,7 @@ func (a *WalletApplication) initLogger() {
}) })
} }
// Initializes the Directory Structure and stores the paths to the WalletApplication struct. // 初始化目录结构并存储 WalletApplication 结构的路径。
func (a *WalletApplication) initDirectoryStructure() error { func (a *WalletApplication) initDirectoryStructure() error {
user, err := user.Current() user, err := user.Current()
@ -160,7 +165,7 @@ func (a *WalletApplication) initDirectoryStructure() error {
return nil return nil
} }
// initMainnetConnection populates the WalletApplication struct with mainnet data // initMainnetConnection 用主网数据填充 WalletApplication 结构
func (a *WalletApplication) initMainnetConnection() { func (a *WalletApplication) initMainnetConnection() {
a.Network.URL = "http://lb.constellationnetwork.io:9000" // Temp a.Network.URL = "http://lb.constellationnetwork.io:9000" // Temp
@ -175,20 +180,19 @@ func (a *WalletApplication) initMainnetConnection() {
a.Network.BlockExplorer.Handles.CollectTX = "/transactions?sender=" a.Network.BlockExplorer.Handles.CollectTX = "/transactions?sender="
} }
// APIError reported by the blockexplerer/loadbalancer are reported in the following format // APIError 由 blockexplerer/loadbalancer 报告的 APIError 按以下格式报告
// {"error": "Cannot find transactions for sender"} // {“error”: “找不到发件人的交易”}
type APIError struct { type APIError struct {
Error string Error string
} }
// verifyAPIResponse takes API response converted to a byte array and checks if the API returned // verifyAPIResponse 将 API 响应转换为字节数组,并检查 API 是否返回了一个错误。如果是这样,它将返回错误消息。
// an error. If it did, it'll return the error message.
func (a *WalletApplication) verifyAPIResponse(r []byte) (bool, string) { func (a *WalletApplication) verifyAPIResponse(r []byte) (bool, string) {
APIErr := APIError{} APIErr := APIError{}
if string(r[3:8]) == "error" { if string(r[3:8]) == "error" {
err := json.Unmarshal(r, &APIErr) err := json.Unmarshal(r, &APIErr)
if err != nil { if err != nil {
a.log.Errorln("Unable to parse API error. Reason: ", err) a.log.Errorln("无法解析 API 错误。原因: ", err)
} }
return false, APIErr.Error return false, APIErr.Error
} }
@ -198,7 +202,7 @@ func (a *WalletApplication) verifyAPIResponse(r []byte) (bool, string) {
func (a *WalletApplication) sendSuccess(msg string) { func (a *WalletApplication) sendSuccess(msg string) {
if len(msg) > 200 { if len(msg) > 200 {
msg = string(msg[:200]) // Restrict error size for frontend msg = msg[:200] // 限制前端的错误大小
a.RT.Events.Emit("success", msg) a.RT.Events.Emit("success", msg)
return return
} }
@ -208,7 +212,7 @@ func (a *WalletApplication) sendSuccess(msg string) {
func (a *WalletApplication) sendWarning(msg string) { func (a *WalletApplication) sendWarning(msg string) {
if len(msg) > 200 { if len(msg) > 200 {
msg = string(msg[:200]) // Restrict error size for frontend msg = msg[:200] // 限制前端的错误大小
a.RT.Events.Emit("warning", msg) a.RT.Events.Emit("warning", msg)
return return
} }
@ -222,7 +226,7 @@ func (a *WalletApplication) sendError(msg string, err error) {
if err != nil { if err != nil {
b := []byte(err.Error()) b := []byte(err.Error())
if len(b) > 80 { if len(b) > 80 {
errStr = string(b[:80]) // Restrict error size for frontend errStr = string(b[:80]) // 限制前端的错误大小
} else if len(b) < 80 { } else if len(b) < 80 {
errStr = string(b) errStr = string(b)
} else { } else {

24
gui_wails01_learn/backend/assemblywrapper.go

@ -55,14 +55,14 @@ func (a *WalletApplication) WalletKeystoreAccess() bool {
rescueStdout := os.Stdout rescueStdout := os.Stdout
r, w, err := os.Pipe() r, w, err := os.Pipe()
if err != nil { if err != nil {
a.log.Errorln("Unable to pipe STDOUT, Reason: ", err) a.log.Errorln("无法通过 STDOUT 管道传输,原因: ", err)
a.sendError("Unable to pipe STDOUT, Reason: ", err) a.sendError("无法通过 STDOUT 管道传输,原因: ", err)
} }
os.Stdout = w os.Stdout = w
err = a.runWalletCMD("wallet", "show-address", "--keystore="+a.wallet.KeyStorePath, "--alias="+a.wallet.WalletAlias, "--env_args=true") err = a.runWalletCMD("wallet", "show-address", "--keystore="+a.wallet.KeyStorePath, "--alias="+a.wallet.WalletAlias, "--env_args=true")
if err != nil { if err != nil {
a.log.Warn("KeyStore Access Rejected!") a.log.Warn("KeyStore 访问被拒绝!")
a.LoginError("Access Denied. Please make sure that you have typed in the correct credentials.") a.LoginError("访问被拒绝。请确保您输入了正确的凭据。")
a.KeyStoreAccess = false a.KeyStoreAccess = false
return a.KeyStoreAccess return a.KeyStoreAccess
} }
@ -72,8 +72,8 @@ func (a *WalletApplication) WalletKeystoreAccess() bool {
w.Close() w.Close()
dagAddress, err := ioutil.ReadAll(r) dagAddress, err := ioutil.ReadAll(r)
if err != nil { if err != nil {
a.log.Errorln("Unable to read address from STDOUT", err) a.log.Errorln("无法从 STDOUT 读取地址", err)
a.sendError("Unable to read address from STDOUT", err) a.sendError("无法从 STDOUT 读取地址", err)
} }
// if STDOUT prefix of show-address output isn't DAG // if STDOUT prefix of show-address output isn't DAG
@ -86,7 +86,7 @@ func (a *WalletApplication) WalletKeystoreAccess() bool {
a.KeyStoreAccess = false a.KeyStoreAccess = false
a.log.Warn("KeyStore Access Rejected!") a.log.Warn("KeyStore Access Rejected!")
a.LoginError("Access Denied. Please make sure that you have typed in the correct credentials.") a.LoginError("访问被拒绝。请确保您输入了正确的凭据。")
return a.KeyStoreAccess return a.KeyStoreAccess
} }
@ -98,8 +98,8 @@ func (a *WalletApplication) GenerateDAGAddress() string {
rescueStdout := os.Stdout rescueStdout := os.Stdout
r, w, err := os.Pipe() r, w, err := os.Pipe()
if err != nil { if err != nil {
a.log.Errorln("Unable to pipe STDOUT, Reason: ", err) a.log.Errorln("无法通过 STDOUT 管道传输,原因: ", err)
a.sendError("Unable to pipe STDOUT, Reason: ", err) a.sendError("无法通过 STDOUT 管道传输,原因: ", err)
return "" return ""
} }
os.Stdout = w os.Stdout = w
@ -114,8 +114,8 @@ func (a *WalletApplication) GenerateDAGAddress() string {
w.Close() w.Close()
dagAddress, err := ioutil.ReadAll(r) dagAddress, err := ioutil.ReadAll(r)
if err != nil { if err != nil {
a.log.Errorln("Unable to read address from STDOUT", err) a.log.Errorln("无法从 STDOUT 读取地址", err)
a.sendError("Unable to read address from STDOUT", err) a.sendError("无法从 STDOUT 读取地址", err)
} }
os.Stdout = rescueStdout os.Stdout = rescueStdout
a.wallet.Address = string(dagAddress[:40]) a.wallet.Address = string(dagAddress[:40])
@ -140,7 +140,7 @@ func (a *WalletApplication) CheckAndFetchWalletCLI() bool {
a.log.Info(keytoolPath + " file exists. Skipping downloading") a.log.Info(keytoolPath + " file exists. Skipping downloading")
} else { } else {
if err := a.fetchWalletJar("cl-keytool.jar", keytoolPath); err != nil { if err := a.fetchWalletJar("cl-keytool.jar", keytoolPath); err != nil {
a.log.Errorln("Unable to fetch or store cl-keytool.jar", err) a.log.Errorln("无法获取或存储cl-keytool.jar", err)
return false return false
} }
} }

90
gui_wails01_learn/backend/buildchain.go

@ -8,27 +8,27 @@ import (
"github.com/grvlle/constellation_wallet/backend/models" "github.com/grvlle/constellation_wallet/backend/models"
) )
// formTXChain retains the order of the blockchain across all accounts. // formTXChain 在所有账户中保留区块链的顺序。
// Also calls the methods to create block objects (write json to file), // 同时调用创建块对象的方法(将 json 写入文件),
// and the method that pushes them to the network(HTTP POST). // 以及将它们推送到网络的方法 (HTTP POST)。
// To retain order, formTXChain will poll the last sent TX for it's Failed state. // 为了保持顺序,formTXChain 将轮询最后发送的 TX 的 Failed 状态。
// if the last TX failed, it'll switch up the order to account for that not to break the chain. // 如果最后一个 TX 失败,它将切换顺序以考虑不会中断链。
// This means that all failed attempts at creating a block is also stored in the DB with // 这意味着所有创建数据块的失败尝试也会存储在数据库中,并带有
// a Failed state. // Failed (失败) 状态。
func (a *WalletApplication) formTXChain(amount int64, fee int64, address string, ptxObj *Transaction, ltxObj *Transaction) { func (a *WalletApplication) formTXChain(amount int64, fee int64, address string, ptxObj *Transaction, ltxObj *Transaction) {
statusLastTX := models.TXHistory{} statusLastTX := models.TXHistory{}
if err := a.DB.Last(&statusLastTX).Error; err != nil { if err := a.DB.Last(&statusLastTX).Error; err != nil {
a.log.Warnln("No previous TX detected for this wallet. Reason: ", err) a.log.Warnln("未检测到此钱包的先前 TX。原因: ", err)
} }
if statusLastTX.Failed { if statusLastTX.Failed {
a.log.Warnln("Previous Transaction has a failed state. Adapting...", statusLastTX.Failed) a.log.Warnln("Previous Transaction 具有 failed 状态。适应...", statusLastTX.Failed)
} }
// Queries the number of previous transactions for this wallet. // 查询此钱包的先前交易数量。
numberOfTX := a.DB.Model(&a.wallet).Association("TXHistory").Count() numberOfTX := a.DB.Model(&a.wallet).Association("TXHistory").Count()
// First TX does not contain a TXref // 第一个 TX 不包含 TXref
if numberOfTX == 0 { if numberOfTX == 0 {
a.log.Infoln("Detected that this is the first TX sent from this key.") a.log.Infoln("Detected that this is the first TX sent from this key.")
a.produceTXObject(amount, fee, address, a.paths.LastTXFile, a.paths.EmptyTXFile) a.produceTXObject(amount, fee, address, a.paths.LastTXFile, a.paths.EmptyTXFile)
@ -38,10 +38,10 @@ func (a *WalletApplication) formTXChain(amount int64, fee int64, address string,
a.log.Infoln("Number of previous TX's detected: ", numberOfTX) a.log.Infoln("Number of previous TX's detected: ", numberOfTX)
// Manually control the second TX, to ensure the following order // 手动控制第二个 TX,确保顺序如下
if numberOfTX == 1 { if numberOfTX == 1 {
// If the first transaction failed, enforce the order. // 如果第一个事务失败,则强制执行顺序。
if statusLastTX.Failed { if statusLastTX.Failed {
a.log.Warnln("Detected that the previous transaction failed.") a.log.Warnln("Detected that the previous transaction failed.")
a.produceTXObject(amount, fee, address, a.paths.LastTXFile, a.paths.EmptyTXFile) a.produceTXObject(amount, fee, address, a.paths.LastTXFile, a.paths.EmptyTXFile)
@ -49,8 +49,8 @@ func (a *WalletApplication) formTXChain(amount int64, fee int64, address string,
return return
} }
// Check for edge case where PrevTXFile has already been written and needs to be referenced. // 检查已写入 PrevTXFile 并需要引用的边缘情况。
// This occurs when a wallet with 1 previous tx has been imported. // 当导入具有 1 个 tx 的钱包时,会发生这种情况。
prevTXFileContents := a.loadTXFromFile(a.paths.PrevTXFile) prevTXFileContents := a.loadTXFromFile(a.paths.PrevTXFile)
if a.WalletImported && prevTXFileContents != "" { if a.WalletImported && prevTXFileContents != "" {
a.log.Warnln("One previous transaction has been imported. Using that as reference.") a.log.Warnln("One previous transaction has been imported. Using that as reference.")
@ -64,10 +64,10 @@ func (a *WalletApplication) formTXChain(amount int64, fee int64, address string,
return return
} }
// Returns the TX object that has the highest ordinal (the highest determines if it's to be referenced or reference the other tx) //返回具有最高序数的 TX 对象(最高确定它是要引用还是引用另一个 tx)
newTX := a.determineBlockOrder(ptxObj, ltxObj) newTX := a.determineBlockOrder(ptxObj, ltxObj)
// If the last TX is in failed state, we reset the order. // 如果最后一个 TX 处于 failed 状态,我们将重置顺序。
if newTX == a.paths.PrevTXFile && statusLastTX.Failed { if newTX == a.paths.PrevTXFile && statusLastTX.Failed {
a.produceTXObject(amount, fee, address, a.paths.LastTXFile, a.paths.PrevTXFile) a.produceTXObject(amount, fee, address, a.paths.LastTXFile, a.paths.PrevTXFile)
a.sendTransaction(a.paths.LastTXFile) a.sendTransaction(a.paths.LastTXFile)
@ -84,7 +84,7 @@ func (a *WalletApplication) formTXChain(amount int64, fee int64, address string,
} }
func (a *WalletApplication) determineBlockOrder(ptxObj, ltxObj *Transaction) string { func (a *WalletApplication) determineBlockOrder(ptxObj, ltxObj *Transaction) string {
// The higher ordinal will always be the TX carrying the TX Ref. // 较高的序数将始终是带有 TX Ref 的 TX。
a.log.Info("Last TX Ordinal: ", ltxObj.LastTxRef.Ordinal, " Previous TX Ordinal: ", ptxObj.LastTxRef.Ordinal) a.log.Info("Last TX Ordinal: ", ltxObj.LastTxRef.Ordinal, " Previous TX Ordinal: ", ptxObj.LastTxRef.Ordinal)
if ltxObj.LastTxRef.Ordinal > ptxObj.LastTxRef.Ordinal { if ltxObj.LastTxRef.Ordinal > ptxObj.LastTxRef.Ordinal {
return a.paths.PrevTXFile return a.paths.PrevTXFile
@ -93,8 +93,7 @@ func (a *WalletApplication) determineBlockOrder(ptxObj, ltxObj *Transaction) str
} }
// convertToTXObject takes the Path to the prev_tx and last_tx files and returns a // convertToTXObject 获取 prev_tx 的 Path 并last_tx文件,并返回一个指向两个 workable 对象的指针。
// pointer to two workable objects.
func (a *WalletApplication) convertToTXObject(ptx, ltx string) (*Transaction, *Transaction) { func (a *WalletApplication) convertToTXObject(ptx, ltx string) (*Transaction, *Transaction) {
var ptxObj Transaction var ptxObj Transaction
var ltxObj Transaction var ltxObj Transaction
@ -113,9 +112,9 @@ func (a *WalletApplication) convertToTXObject(ptx, ltx string) (*Transaction, *T
return &ptxObj, &ltxObj return &ptxObj, &ltxObj
} }
/* Called upon wallet import */ /* 在钱包导入时调用 */
// TXReference is used to parse the previous tx of an imported wallet. // TXReference 用于解析导入的 wallet 之前的 tx。
type TXReference struct { type TXReference struct {
Hash string `json:"hash"` Hash string `json:"hash"`
Amount int64 `json:"amount"` Amount int64 `json:"amount"`
@ -173,19 +172,18 @@ type TXReference struct {
} `json:"transactionOriginal"` } `json:"transactionOriginal"`
} }
// rebuildTxChainState will query the blockexplorer for a transacion and write it to a.paths.PrevTXFile. // rebuildTxChainState 将查询区块浏览器以获取交易并将其写入 a.paths.PrevTXFile。 这将允许导入的钱包引用最后发送的交易。
// This will allow an imported wallet to reference the last transaction sent.
func (a *WalletApplication) rebuildTxChainState(lastTXHash string) error { func (a *WalletApplication) rebuildTxChainState(lastTXHash string) error {
a.log.Info("Sending API call to block explorer on: " + a.Network.BlockExplorer.Handles.Transactions) a.log.Info("向区块浏览器发送 API 调用: " + a.Network.BlockExplorer.Handles.Transactions)
resp, err := http.Get(a.Network.BlockExplorer.URL + a.Network.BlockExplorer.Handles.Transactions + lastTXHash) resp, err := http.Get(a.Network.BlockExplorer.URL + a.Network.BlockExplorer.Handles.Transactions + lastTXHash)
if err != nil { if err != nil {
a.log.Errorln("Failed to send HTTP request. Reason: ", err) a.log.Errorln("发送 HTTP 请求失败。原因: ", err)
if err := a.DB.Model(&a.wallet).Where("wallet_alias = ?", a.wallet.WalletAlias).Delete(&a.wallet).Error; err != nil { if err := a.DB.Model(&a.wallet).Where("wallet_alias = ?", a.wallet.WalletAlias).Delete(&a.wallet).Error; err != nil {
a.log.Errorln("Unable to delete wallet upon failed import. Reason: ", err) a.log.Errorln("导入失败时无法删除 wallet。原因: ", err)
return err return err
} }
a.LoginError("Unable to collect previous TX's from blockexplorer. Please check your internet connection.") a.LoginError("无法从 BlockExplorer 收集以前的 TX。请检查您的互联网连接。")
return err return err
} }
defer resp.Body.Close() defer resp.Body.Close()
@ -193,20 +191,20 @@ func (a *WalletApplication) rebuildTxChainState(lastTXHash string) error {
if resp.Body != nil { if resp.Body != nil {
bodyBytes, err := ioutil.ReadAll(resp.Body) bodyBytes, err := ioutil.ReadAll(resp.Body)
if err != nil { if err != nil {
a.LoginError("Unable to collect previous TX's from blockexplorer. Please try again later.") a.LoginError("无法从 BlockExplorer 收集以前的 TX。请稍后重试。")
a.log.Errorln("Unable to collect previous TX's from blockexplorer. Reason: ", err) a.log.Errorln("无法从 BlockExplorer 收集以前的 TX。原因: ", err)
} }
ok, error := a.verifyAPIResponse(bodyBytes) ok, e := a.verifyAPIResponse(bodyBytes)
// Blockexplorer returns below string when no previous transactions are found // 当没有找到以前的交易时,BlockExplorer 返回以下字符串
if !ok && error != "Cannot find transaction" { if !ok && e != "Cannot find transaction" {
a.log.Errorln("API returned the following error", error) a.log.Errorln("API 返回以下错误", e)
// If unable to import last transaction, remove wallet from DB and logout. // 如果无法导入最后一笔交易,请从数据库中删除钱包并注销。
if err := a.DB.Model(&a.wallet).Where("wallet_alias = ?", a.wallet.WalletAlias).Delete(&a.wallet).Error; err != nil { if err := a.DB.Model(&a.wallet).Where("wallet_alias = ?", a.wallet.WalletAlias).Delete(&a.wallet).Error; err != nil {
a.log.Errorln("Unable to delete wallet upon failed import. Reason: ", err) a.log.Errorln("导入失败时无法删除 wallet。原因: ", err)
return err return err
} }
a.log.Panicln("Unable to import previous transactions") // TODO: logout user from wallet a.log.Panicln("无法导入以前的交易") // TODO: logout user from wallet
a.LoginError("The wallet import failed. Please check your internet connection and try again.") a.LoginError("钱包导入失败。请检查您的互联网连接,然后重试。")
return err return err
} }
@ -214,15 +212,15 @@ func (a *WalletApplication) rebuildTxChainState(lastTXHash string) error {
lastTX := TXReference{} lastTX := TXReference{}
err = json.Unmarshal(bodyBytes, &lastTX) err = json.Unmarshal(bodyBytes, &lastTX)
if err != nil { if err != nil {
a.log.Errorln("Unable to fetch TX history from block explorer. Reason: ", err) a.log.Errorln("无法从区块浏览器获取 TX 历史记录。原因: ", err)
a.sendError("Unable to fetch TX history from block explorer. Reason: ", err) a.sendError("无法从区块浏览器获取 TX 历史记录。原因: ", err)
return err return err
} }
// Marshal so that we can unmarshat into tx object -> // Marshal so that we can unmarshat into tx object ->
b, err := json.Marshal(lastTX.TransactionOriginal) b, err := json.Marshal(lastTX.TransactionOriginal)
if err != nil { if err != nil {
a.log.Errorln("Unable to parse last transaction hash. Reason: ", err) a.log.Errorln("无法解析最后一个交易哈希。原因: ", err)
a.sendError("Unable to fetch TX history from block explorer. Reason: ", err) a.sendError("无法从区块浏览器获取 TX 历史记录。原因: ", err)
return err return err
} }
@ -230,16 +228,16 @@ func (a *WalletApplication) rebuildTxChainState(lastTXHash string) error {
tx := Transaction{} tx := Transaction{}
err = json.Unmarshal(b, &tx) err = json.Unmarshal(b, &tx)
if err != nil { if err != nil {
a.log.Errorln("Unable to parse last transaction hash. Reason: ", err) a.log.Errorln("无法解析最后一个交易哈希。原因: ", err)
a.sendError("Unable to fetch TX history from block explorer. Reason: ", err) a.sendError("无法从区块浏览器获取 TX 历史记录。原因: ", err)
return err return err
} }
// Converting to json // Converting to json
txBytes, err := json.Marshal(tx) txBytes, err := json.Marshal(tx)
if err != nil { if err != nil {
a.log.Errorln("Unable to parse last transaction hash. Reason: ", err) a.log.Errorln("无法解析最后一个交易哈希。原因: ", err)
a.sendError("Unable to fetch TX history from block explorer. Reason: ", err) a.sendError("无法从区块浏览器获取 TX 历史记录。原因: ", err)
return err return err
} }

17
gui_wails01_learn/backend/cmd/update/main.go

@ -165,8 +165,7 @@ func (u *Update) DownloadAppBinary() (string, error) {
return filePath, nil return filePath, nil
} }
// VerifyChecksum takes a file path and will check the file sha256 checksum against the checksum included // VerifyChecksum 采用文件路径,并将根据包含的校验和检查文件 sha256 校验, 如果不匹配,则返回 false
// in the downlaod returns false if there's a missmatch
func (u *Update) VerifyChecksum(filePathZip string) (bool, error) { func (u *Update) VerifyChecksum(filePathZip string) (bool, error) {
// Download checksum // Download checksum
filename := "checksum.sha256" filename := "checksum.sha256"
@ -235,7 +234,7 @@ func (u *Update) TerminateAppService() error {
return nil return nil
} }
// BackupApp backs up the old binary in case of a failed update. // BackupApp 会在更新失败时备份旧的二进制文件。
func (u *Update) BackupApp() error { func (u *Update) BackupApp() error {
_, fileExt := getUserOS() _, fileExt := getUserOS()
@ -254,7 +253,7 @@ func (u *Update) BackupApp() error {
return nil return nil
} }
// ReplaceAppBinary replaces the old update module and molly binary with the newly downloaded ones. // ReplaceAppBinary 将旧的更新模块和 molly 二进制文件替换为新下载的更新模块和 molly 二进制文件。
func (u *Update) ReplaceAppBinary(contents *unzippedContents) error { func (u *Update) ReplaceAppBinary(contents *unzippedContents) error {
// Replace old molly binary with the new one // Replace old molly binary with the new one
_, fileExt := getUserOS() _, fileExt := getUserOS()
@ -280,7 +279,7 @@ func (u *Update) ReplaceAppBinary(contents *unzippedContents) error {
return nil return nil
} }
// LaunchAppBinary executes the new molly binary // LaunchAppBinary 执行新的 molly 二进制文件
func (u *Update) LaunchAppBinary() error { func (u *Update) LaunchAppBinary() error {
cmd := exec.Command(*u.oldMollyBinaryPath) cmd := exec.Command(*u.oldMollyBinaryPath)
err := cmd.Start() err := cmd.Start()
@ -290,7 +289,7 @@ func (u *Update) LaunchAppBinary() error {
return nil return nil
} }
// RestoreBackup restores the backed up files // RestoreBackup 恢复备份的文件
func (u *Update) RestoreBackup() error { func (u *Update) RestoreBackup() error {
log.Infoln("Restoring Backup...") log.Infoln("Restoring Backup...")
@ -316,7 +315,7 @@ func (u *Update) RestoreBackup() error {
} }
// CleanUp removes uneccesary artifacts from the update process // CleanUp 从更新过程中删除不重要的工件
func (u *Update) CleanUp() error { func (u *Update) CleanUp() error {
if fileExists(*u.dagFolderPath + "/mollywallet.zip") { if fileExists(*u.dagFolderPath + "/mollywallet.zip") {
@ -399,7 +398,7 @@ func copy(src string, dst string) error {
return nil return nil
} }
// Unzips archive to dstPath, returns path to wallet binary // unzipArchive 将存档解压缩到 dstPath,返回钱包二进制文件的路径
func unzipArchive(zippedArchive, dstPath string) (*unzippedContents, error) { func unzipArchive(zippedArchive, dstPath string) (*unzippedContents, error) {
uz := unzip.New(zippedArchive, dstPath+"/"+"new_build/") uz := unzip.New(zippedArchive, dstPath+"/"+"new_build/")
@ -417,7 +416,7 @@ func unzipArchive(zippedArchive, dstPath string) (*unzippedContents, error) {
return contents, err return contents, err
} }
// getUserOS returns the users OS as well as the file extension of executables for said OS // getUserOS 返回用户的操作系统以及所述操作系统的可执行文件的文件扩展名
func getUserOS() (string, string) { func getUserOS() (string, string) {
var osBuild string var osBuild string
var fileExt string var fileExt string

27
gui_wails01_learn/backend/dashboard.go

@ -20,8 +20,7 @@ const (
updateIntervalPieChart = 60 // Seconds updateIntervalPieChart = 60 // Seconds
) )
// ChartData contains all the datapoints for the Charts // ChartData 包含图表的所有数据点在 Dashboard 上。
// on the Dashboard.
type ChartData struct { type ChartData struct {
NodesOnline struct { NodesOnline struct {
Labels []string `json:"labels"` Labels []string `json:"labels"`
@ -39,8 +38,7 @@ type ChartData struct {
} `json:"throughput"` } `json:"throughput"`
} }
// ChartDataInit initializes the ChartData struct with datapoints for // ChartDataInit 使用以下数据点初始化 ChartData 结构体钱包中的图表。这些作为 chart_data.json 存储在 fs 上
// the charts in the wallet. These are stored on the fs as chart_data.json
func (a *WalletApplication) ChartDataInit() *ChartData { func (a *WalletApplication) ChartDataInit() *ChartData {
cd := &ChartData{} cd := &ChartData{}
@ -93,7 +91,7 @@ func (a *WalletApplication) ChartDataInit() *ChartData {
return cd return cd
} }
// Populates the Nodes Online pie chart with data from the block explorer. // 使用区块浏览器中的数据填充 Nodes Online 饼图。
func (a *WalletApplication) nodeStats(cd *ChartData) { func (a *WalletApplication) nodeStats(cd *ChartData) {
go func() { go func() {
for { for {
@ -101,7 +99,7 @@ func (a *WalletApplication) nodeStats(cd *ChartData) {
case <-a.killSignal: case <-a.killSignal:
return return
default: default:
// Will populate the chart with random data // 将使用随机数据填充图表
for i := range cd.NodesOnline.Series { for i := range cd.NodesOnline.Series {
cd.NodesOnline.Series[i] = rand.Intn(dummyValue) cd.NodesOnline.Series[i] = rand.Intn(dummyValue)
} }
@ -203,9 +201,9 @@ func (a *WalletApplication) pollTokenBalance() {
}() }()
} }
// pricePoller polls the min-api.cryptocompare REST API for DAG token value. // pricePoller 轮询 min-api.cryptocompare REST API 以获取 DAG 令牌值。
// Once polled, it'll Emit the token value to Dashboard.vue for full token // 轮询后,它会将 token 值发送到 Dashboard.vue 以获取完整 token
// balance evaluation against USD. // 相对于美元的余额评估。
func (a *WalletApplication) pricePoller() { func (a *WalletApplication) pricePoller() {
const ( const (
@ -297,11 +295,11 @@ func (a *WalletApplication) pricePoller() {
}() }()
} }
// StoreTermsOfServiceStateDB stores the Terms of Service state in the user DB // StoreTermsOfServiceStateDB 将服务条款状态存储在用户 DB 中
func (a *WalletApplication) StoreTermsOfServiceStateDB(termsOfService bool) bool { func (a *WalletApplication) StoreTermsOfServiceStateDB(termsOfService bool) bool {
if err := a.DB.Model(&a.wallet).Where("wallet_alias = ?", a.wallet.WalletAlias).Update("TermsOfService", termsOfService).Error; err != nil { if err := a.DB.Model(&a.wallet).Where("wallet_alias = ?", a.wallet.WalletAlias).Update("TermsOfService", termsOfService).Error; err != nil {
a.log.Errorln("Unable to store termsOfService state. Reason: ", err) a.log.Errorln("无法存储 termsOfService 状态。原因: ", err)
a.sendError("Unable to store termsOfService state persistently. Reason: ", err) a.sendError("无法持久存储 termsOfService 状态。原因: ", err)
return false return false
} }
return true return true
@ -321,8 +319,7 @@ type tokenPriceAlt struct {
} `json:"data"` } `json:"data"`
} }
// getTokenPriceAlternateRoute will kick in in case the main token poller API will return // getTokenPriceAlternateRoute 将在主令牌轮询器 API 返回时启动损坏的有效负载。
// broken payload.
func getTokenPriceAlternateRoute() (float64, float64, error) { func getTokenPriceAlternateRoute() (float64, float64, error) {
var tokenpriceUSD, tokenpriceBTC float64 var tokenpriceUSD, tokenpriceBTC float64
@ -381,7 +378,7 @@ func getTokenPriceAlternateRoute() (float64, float64, error) {
return tokenpriceUSD, tokenpriceBTC, err return tokenpriceUSD, tokenpriceBTC, err
} }
// UpdateCounter will count up from the last time a card was updated. // UpdateCounter 将从上次更新卡片开始计数。
func UpdateCounter(countFrom int, counter string, unit time.Duration, runtime *wails.Runtime) { func UpdateCounter(countFrom int, counter string, unit time.Duration, runtime *wails.Runtime) {
go func() { go func() {
for i := countFrom; i > 0; i-- { for i := countFrom; i > 0; i-- {

4
gui_wails01_learn/backend/lcm.go

@ -68,7 +68,7 @@ func (u *UpdateWallet) GetLatestRelease() string {
resp, err := http.Get(url) resp, err := http.Get(url)
if err != nil { if err != nil {
u.app.log.Errorln("Failed to send HTTP request. Reason: ", err) u.app.log.Errorln("发送 HTTP 请求失败。原因: ", err)
return "" return ""
} }
if resp == nil { if resp == nil {
@ -80,7 +80,7 @@ func (u *UpdateWallet) GetLatestRelease() string {
bodyBytes, err := ioutil.ReadAll(resp.Body) bodyBytes, err := ioutil.ReadAll(resp.Body)
if err != nil { if err != nil {
u.app.log.Warn("Unable to update token balance. Reason: ", err) u.app.log.Warn("无法更新 Token 余额。原因: ", err)
return "" return ""
} }

45
gui_wails01_learn/backend/login.go

@ -9,15 +9,14 @@ import (
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
) )
// LoginError takes a string and pushes it to the login screen as an errror // LoginError 获取一个字符串并将其作为 error 推送到登录屏幕
func (a *WalletApplication) LoginError(errMsg string) { func (a *WalletApplication) LoginError(errMsg string) {
if errMsg != "" { if errMsg != "" {
a.RT.Events.Emit("login_error", errMsg, true) a.RT.Events.Emit("login_error", errMsg, true)
} }
} }
// Login is called from the FE when a user logs in with a wallet object // Login 当用户使用 wallet 对象登录时,FE 会调用 Login 已在数据库中
// already in the DB
func (a *WalletApplication) Login(keystorePath, keystorePassword, keyPassword, alias string) bool { func (a *WalletApplication) Login(keystorePath, keystorePassword, keyPassword, alias string) bool {
alias = strings.ToLower(alias) alias = strings.ToLower(alias)
@ -26,36 +25,36 @@ func (a *WalletApplication) Login(keystorePath, keystorePassword, keyPassword, a
WalletAlias: alias} WalletAlias: alias}
if runtime.GOOS == "windows" && !a.javaInstalled() { if runtime.GOOS == "windows" && !a.javaInstalled() {
a.LoginError("Unable to detect your Java path. Please make sure that Java has been installed.") a.LoginError("无法检测到 Java 路径。请确保已安装 Java。")
return false return false
} }
if !a.TransactionFinished { if !a.TransactionFinished {
a.log.Warn("Cannot login to another wallet during a pending transaction.") a.log.Warn("在待处理交易期间无法登录其他钱包。")
a.LoginError("Cannot login to another wallet during a pending transaction.") a.LoginError("在待处理交易期间无法登录其他钱包。")
return false return false
} }
if keystorePath == "" { if keystorePath == "" {
a.LoginError("Please provide a path to the KeyStore file.") a.LoginError("请提供 KeyStore 文件的路径。")
return false return false
} }
if !a.passwordsProvided(keystorePassword, keyPassword, alias) { if !a.passwordsProvided(keystorePassword, keyPassword, alias) {
a.log.Warnln("One or more passwords were not provided.") a.log.Warnln("未提供一个或多个密码。")
return false return false
} }
os.Setenv("CL_STOREPASS", keystorePassword) _ = os.Setenv("CL_STOREPASS", keystorePassword)
os.Setenv("CL_KEYPASS", keyPassword) _ = os.Setenv("CL_KEYPASS", keyPassword)
if err := a.DB.First(&a.wallet, "wallet_alias = ?", alias).Error; err != nil { if err := a.DB.First(&a.wallet, "wallet_alias = ?", alias).Error; err != nil {
a.log.Errorln("Unable to query database object for existing wallet. Reason: ", err) a.log.Errorln("无法查询现有钱包的数据库对象。原因: ", err)
return a.ImportWallet(keystorePath, keystorePassword, keyPassword, alias) return a.ImportWallet(keystorePath, keystorePassword, keyPassword, alias)
} }
if !a.WalletKeystoreAccess() { if !a.WalletKeystoreAccess() {
a.LoginError("Access Denied. Please make sure that you have typed in the correct credentials.") a.LoginError("访问被拒绝。请确保您输入了正确的凭据。")
return false return false
} }
@ -64,7 +63,7 @@ func (a *WalletApplication) Login(keystorePath, keystorePassword, keyPassword, a
a.log.Infoln("PrivateKey path: ", keystorePath) a.log.Infoln("PrivateKey path: ", keystorePath)
} }
// Check password strings against salted hashes stored in DB. Also make sure KeyStore has been accessed. // 根据 DB 中存储的加盐哈希检查密码字符串。还要确保已访问 KeyStore。
if a.CheckAccess(keystorePassword, a.wallet.KeystorePasswordHash) && a.CheckAccess(keyPassword, a.wallet.KeyPasswordHash) && a.KeyStoreAccess { if a.CheckAccess(keystorePassword, a.wallet.KeystorePasswordHash) && a.CheckAccess(keyPassword, a.wallet.KeyPasswordHash) && a.KeyStoreAccess {
a.UserLoggedIn = true a.UserLoggedIn = true
@ -73,7 +72,7 @@ func (a *WalletApplication) Login(keystorePath, keystorePassword, keyPassword, a
} else { } else {
a.UserLoggedIn = false a.UserLoggedIn = false
a.LoginError("Access Denied. Please make sure that you have typed in the correct credentials.") a.LoginError("访问被拒绝。请确保您输入了正确的凭据。")
} }
if a.UserLoggedIn && a.KeyStoreAccess && !a.NewUser { if a.UserLoggedIn && a.KeyStoreAccess && !a.NewUser {
@ -89,12 +88,12 @@ func (a *WalletApplication) Login(keystorePath, keystorePassword, keyPassword, a
return a.UserLoggedIn return a.UserLoggedIn
} }
// CheckTermsOfService is called from the FE to check the termsOfService has been accepted // CheckTermsOfService 从 FE 调用 CheckTermsOfService 以检查 termsOfService 是否已被接受
func (a *WalletApplication) CheckTermsOfService() bool { func (a *WalletApplication) CheckTermsOfService() bool {
return a.wallet.TermsOfService return a.wallet.TermsOfService
} }
// LogOut will reset the wallet UI and clear the wallet objects // LogOut 将重置钱包 UI 并清除钱包对象
func (a *WalletApplication) LogOut() bool { func (a *WalletApplication) LogOut() bool {
if a.TransactionFinished { if a.TransactionFinished {
a.UserLoggedIn = false a.UserLoggedIn = false
@ -105,7 +104,7 @@ func (a *WalletApplication) LogOut() bool {
return false return false
} }
// ImportKey is called from the frontend when browsing the fs for a keyfile // ImportKey 在浏览 fs 中查找密钥文件时从前端调用
func (a *WalletApplication) ImportKey() string { func (a *WalletApplication) ImportKey() string {
var keyfile = a.RT.Dialog.SelectFile() var keyfile = a.RT.Dialog.SelectFile()
if keyfile == "" { if keyfile == "" {
@ -121,7 +120,7 @@ func (a *WalletApplication) ImportKey() string {
return keyfile return keyfile
} }
// SelectDirToStoreKey is called from the FE when creating a new keyfile // SelectDirToStoreKey 在创建新的密钥文件时,从 FE 调用 SelectDirToStoreKey
func (a *WalletApplication) SelectDirToStoreKey() string { func (a *WalletApplication) SelectDirToStoreKey() string {
var keyfile = a.RT.Dialog.SelectSaveFile() var keyfile = a.RT.Dialog.SelectSaveFile()
@ -137,7 +136,7 @@ func (a *WalletApplication) SelectDirToStoreKey() string {
return keyfile return keyfile
} }
// GenerateSaltedHash converts plain text to a salted hash // GenerateSaltedHash 将纯文本转换为加盐哈希
func (a *WalletApplication) GenerateSaltedHash(s string) (string, error) { func (a *WalletApplication) GenerateSaltedHash(s string) (string, error) {
saltedBytes := []byte(s) saltedBytes := []byte(s)
hashedBytes, err := bcrypt.GenerateFromPassword(saltedBytes, bcrypt.DefaultCost) hashedBytes, err := bcrypt.GenerateFromPassword(saltedBytes, bcrypt.DefaultCost)
@ -148,18 +147,18 @@ func (a *WalletApplication) GenerateSaltedHash(s string) (string, error) {
return hash, nil return hash, nil
} }
// CheckAccess verifies that the user has entered the correct password // CheckAccess 验证用户是否输入了正确的密码
func (a *WalletApplication) CheckAccess(password, passwordHash string) bool { func (a *WalletApplication) CheckAccess(password, passwordHash string) bool {
err := a.Compare(password, passwordHash) err := a.Compare(password, passwordHash)
if err != nil { if err != nil {
a.log.Warnln("User tried to login with the wrong credentials!") a.log.Warnln("用户尝试使用错误的凭据登录!")
return false return false
} }
a.log.Infoln("Password check OK") a.log.Infoln("密码检查通过")
return true return true
} }
// Compare compares a string with a salted hash // Compare 将字符串与加盐哈希进行比较
func (a *WalletApplication) Compare(s, hash string) error { func (a *WalletApplication) Compare(s, hash string) error {
incoming := []byte(s) incoming := []byte(s)
existing := []byte(hash) existing := []byte(hash)

2
gui_wails01_learn/backend/models/address.go

@ -1,4 +1,4 @@
package models package models
// Address holds the DAG address // Address 保存 DAG 地址
type Address string type Address string

2
gui_wails01_learn/backend/models/path.go

@ -1,6 +1,6 @@
package models package models
// Path carries the file paths // Path 承载文件路径
type Path struct { type Path struct {
ID uint `json:"id"` ID uint `json:"id"`
Alias string `json:"alias"` Alias string `json:"alias"`

2
gui_wails01_learn/backend/models/txhistory.go

@ -1,6 +1,6 @@
package models package models
// TXHistory stores inidividual transactions // TXHistory 存储个人交易
type TXHistory struct { type TXHistory struct {
ID uint `json:"id"` ID uint `json:"id"`
Alias string `json:"alias"` Alias string `json:"alias"`

2
gui_wails01_learn/backend/models/wallet.go

@ -6,7 +6,7 @@ import (
_ "github.com/jinzhu/gorm/dialects/sqlite" _ "github.com/jinzhu/gorm/dialects/sqlite"
) )
// Wallet holds all wallet information. // Wallet 钱包保存所有钱包信息。
type Wallet struct { type Wallet struct {
ID uint `gorm:"AUTO_INCREMENT"` ID uint `gorm:"AUTO_INCREMENT"`
CreatedAt time.Time CreatedAt time.Time

79
gui_wails01_learn/backend/settings.go

@ -8,7 +8,7 @@ import (
"strings" "strings"
) )
// UploadImage will forward the image path of the selected image. // UploadImage 将转发所选图像的图片路径。
func (a *WalletApplication) UploadImage() string { func (a *WalletApplication) UploadImage() string {
filePath := a.RT.Dialog.SelectFile() filePath := a.RT.Dialog.SelectFile()
splitPath := strings.Split(filePath, "/") splitPath := strings.Split(filePath, "/")
@ -28,15 +28,20 @@ func (a *WalletApplication) UploadImage() string {
a.sendError("Unable to find Image on the path provided. ", err) a.sendError("Unable to find Image on the path provided. ", err)
return "None" return "None"
} }
defer file.Close() defer func(file *os.File) {
err := file.Close()
if err != nil {
return
}
}(file)
img, _, err := image.DecodeConfig(file) img, _, err := image.DecodeConfig(file)
if err != nil { if err != nil {
a.log.Info("Attempting to decode as JPEG") a.log.Info("Attempting to decode as JPEG")
img, err = jpeg.DecodeConfig(file) img, err = jpeg.DecodeConfig(file)
if err != nil { if err != nil {
a.log.Errorln("Unable to decode image configuration", err) a.log.Errorln("无法解码图像配置", err)
a.sendError("Unable to change Image. ", err) a.sendError("无法更改图像。", err)
return "None" return "None"
} }
} }
@ -44,7 +49,7 @@ func (a *WalletApplication) UploadImage() string {
a.log.Info("Uploaded image resolution is set to ", img.Height, "x", img.Width) a.log.Info("Uploaded image resolution is set to ", img.Height, "x", img.Width)
if img.Height >= 201 || img.Width >= 201 { if img.Height >= 201 || img.Width >= 201 {
a.log.Warnf("Image resolution is too big. Cannot be bigger than 200x200 ") a.log.Warnf("图像分辨率太大。不能大于 200x200")
return "None" return "None"
} }
@ -52,62 +57,62 @@ func (a *WalletApplication) UploadImage() string {
return filename return filename
} }
// GetImagePath is called from the Login.Vue. It'll query the DB for the user's profile picture // GetImagePath 从 Login.Vue 中调用。它将查询 DB 以获取用户的个人资料图片并将其返回给 FE 进行展示。
// and return it to the FE to be displayed.
func (a *WalletApplication) GetImagePath() string { func (a *WalletApplication) GetImagePath() string {
if err := a.DB.Model(&a.wallet).Where("wallet_alias = ?", a.wallet.WalletAlias).Error; err != nil { if err := a.DB.Model(&a.wallet).Where("wallet_alias = ?", a.wallet.WalletAlias).Error; err != nil {
a.log.Errorln("Unable to query the DB record for the Image path. Reason: ", err) a.log.Errorln("无法查询 Image 路径的数据库记录。原因: ", err)
a.sendError("Unable to query the DB record for the Image path. Reason: ", err) a.sendError("无法查询 Image 路径的数据库记录。原因: ", err)
return "" return ""
} }
a.log.Infoln("Profile Picture selected: ", a.wallet.ProfilePicture) a.log.Infoln("已选择个人资料图片: ", a.wallet.ProfilePicture)
return a.wallet.ProfilePicture return a.wallet.ProfilePicture
} }
// StoreImagePathInDB stores the path to where the profile picture is located in the database // StoreImagePathInDB 存储个人资料图片在数据库中位置的路径
func (a *WalletApplication) StoreImagePathInDB(path string) bool { func (a *WalletApplication) StoreImagePathInDB(path string) bool {
if err := a.DB.Model(&a.wallet).Where("wallet_alias = ?", a.wallet.WalletAlias).Update("ProfilePicture", path).Error; err != nil { if err := a.DB.Model(&a.wallet).
a.log.Errorln("Unable to update the DB record with the Image path. Reason: ", err) Where("wallet_alias = ?", a.wallet.WalletAlias).Update("ProfilePicture", path).Error; err != nil {
a.sendError("Unable to update the DB record with the Image path. Reason: ", err) a.log.Errorln("无法使用 Image path (映像路径) 更新 DB 记录。原因: ", err)
a.sendError("无法使用 Image path (映像路径) 更新 DB 记录。原因: ", err)
return false return false
} }
return true return true
} }
// GetWalletTag is called from the Login.Vue // GetWalletTag 从 Login.Vue 调用
func (a *WalletApplication) GetWalletTag() string { func (a *WalletApplication) GetWalletTag() string {
if err := a.DB.Model(&a.wallet).Where("wallet_alias = ?", a.wallet.WalletAlias).Error; err != nil { if err := a.DB.Model(&a.wallet).Where("wallet_alias = ?", a.wallet.WalletAlias).Error; err != nil {
a.log.Errorln("Unable to query the DB record for the Image path. Reason: ", err) a.log.Errorln("无法查询 Image 路径的数据库记录。原因: ", err)
a.sendError("Unable to query the DB record for the Image path. Reason: ", err) a.sendError("无法查询 Image 路径的数据库记录。原因: ", err)
} }
a.log.Infoln("Wallet Tag selected: ", a.wallet.WalletTag) a.log.Infoln("已选择 Wallet Tag: ", a.wallet.WalletTag)
return a.wallet.WalletTag return a.wallet.WalletTag
} }
// StoreWalletLabelInDB takes a wallet label string entered by a user and stores it in the database // StoreWalletLabelInDB 获取用户输入的钱包标签字符串并将其存储在数据库中
func (a *WalletApplication) StoreWalletLabelInDB(walletTag string) bool { func (a *WalletApplication) StoreWalletLabelInDB(walletTag string) bool {
if err := a.DB.Model(&a.wallet).Where("wallet_alias = ?", a.wallet.WalletAlias).Update("WalletTag", walletTag).Error; err != nil { if err := a.DB.Model(&a.wallet).Where("wallet_alias = ?", a.wallet.WalletAlias).Update("WalletTag", walletTag).Error; err != nil {
a.log.Errorln("Unable to update the DB record with the wallet tag. Reason: ", err) a.log.Errorln("无法使用 wallet 标签更新 DB 记录。原因: ", err)
a.sendError("Unable to update the DB record with the wallet tag. Reason: ", err) a.sendError("无法使用 wallet 标签更新 DB 记录。原因: ", err)
return false return false
} }
return true return true
} }
// GetUserTheme is called from the Login.Vue // GetUserTheme 从 Login.Vue 调用
func (a *WalletApplication) GetUserTheme() bool { func (a *WalletApplication) GetUserTheme() bool {
if err := a.DB.Model(&a.wallet).Where("wallet_alias = ?", a.wallet.WalletAlias).Error; err != nil { if err := a.DB.Model(&a.wallet).Where("wallet_alias = ?", a.wallet.WalletAlias).Error; err != nil {
a.log.Errorln("Unable to query the DB record for the Image path. Reason: ", err) a.log.Errorln("无法查询 Image 路径的数据库记录。原因: ", err)
a.sendError("Unable to query the DB record for the Image path. Reason: ", err) a.sendError("无法查询 Image 路径的数据库记录。原因: ", err)
} }
if a.wallet.DarkMode { if a.wallet.DarkMode {
a.log.Infoln("Dark mode enabled") a.log.Infoln("启用深色模式")
} }
return a.wallet.DarkMode return a.wallet.DarkMode
} }
// StoreDarkModeStateDB stores the darkmode state in the user DB // StoreDarkModeStateDB 将暗模式状态存储在用户数据库中
func (a *WalletApplication) StoreDarkModeStateDB(darkMode bool) bool { func (a *WalletApplication) StoreDarkModeStateDB(darkMode bool) bool {
if err := a.DB.Model(&a.wallet).Where("wallet_alias = ?", a.wallet.WalletAlias).Update("DarkMode", darkMode).Error; err != nil { if err := a.DB.Model(&a.wallet).Where("wallet_alias = ?", a.wallet.WalletAlias).Update("DarkMode", darkMode).Error; err != nil {
a.log.Errorln("Unable to store darkmode state. Reason: ", err) a.log.Errorln("Unable to store darkmode state. Reason: ", err)
@ -117,11 +122,11 @@ func (a *WalletApplication) StoreDarkModeStateDB(darkMode bool) bool {
return true return true
} }
// StoreCurrencyStateDB stores the currency state in the user DB // StoreCurrencyStateDB 将货币状态存储在用户 DB 中
func (a *WalletApplication) StoreCurrencyStateDB(currency string) bool { func (a *WalletApplication) StoreCurrencyStateDB(currency string) bool {
if err := a.DB.Model(&a.wallet).Where("wallet_alias = ?", a.wallet.WalletAlias).Update("Currency", currency).Error; err != nil { if err := a.DB.Model(&a.wallet).Where("wallet_alias = ?", a.wallet.WalletAlias).Update("Currency", currency).Error; err != nil {
a.log.Errorln("Unable to store currency state. Reason: ", err) a.log.Errorln("无法存储货币状态。原因: ", err)
a.sendError("Unable to store currency state persistently. Reason: ", err) a.sendError("无法持久存储货币状态。原因: ", err)
return false return false
} }
totalCurrencyBalance := 0.0 totalCurrencyBalance := 0.0
@ -136,7 +141,7 @@ func (a *WalletApplication) StoreCurrencyStateDB(currency string) bool {
return true return true
} }
// UpdateMolly is called from the frontend and triggers the application update // UpdateMolly 从前端调用并触发应用程序更新
func (a *WalletApplication) UpdateMolly() { func (a *WalletApplication) UpdateMolly() {
update := new(UpdateWallet) update := new(UpdateWallet)
update.app = a update.app = a
@ -150,13 +155,23 @@ func CopyFile(src, dst string) error {
if err != nil { if err != nil {
return err return err
} }
defer in.Close() defer func(in *os.File) {
err := in.Close()
if err != nil {
return
}
}(in)
out, err := os.Create(dst) out, err := os.Create(dst)
if err != nil { if err != nil {
return err return err
} }
defer out.Close() defer func(out *os.File) {
err := out.Close()
if err != nil {
return
}
}(out)
_, err = io.Copy(out, in) _, err = io.Copy(out, in)
if err != nil { if err != nil {

89
gui_wails01_learn/backend/transactions.go

@ -12,7 +12,7 @@ import (
"github.com/grvlle/constellation_wallet/backend/models" "github.com/grvlle/constellation_wallet/backend/models"
) )
// Transaction contains all tx information // Transaction 包含所有 tx 信息
type Transaction struct { type Transaction struct {
Edge struct { Edge struct {
ObservationEdge struct { ObservationEdge struct {
@ -56,9 +56,9 @@ type Transaction struct {
IsTest bool `json:"isTest"` IsTest bool `json:"isTest"`
} }
/* Send a transaction */ /* 发送交易 */
// TriggerTXFromFE will initate a new transaction triggered from the frontend. // TriggerTXFromFE 将发起一个从前端触发的新交易。
func (a *WalletApplication) TriggerTXFromFE(amount float64, fee float64, address string) bool { func (a *WalletApplication) TriggerTXFromFE(amount float64, fee float64, address string) bool {
amountConverted := int64(amount * 1e8) amountConverted := int64(amount * 1e8)
feeConverted := int64(fee * 1e8) feeConverted := int64(fee * 1e8)
@ -70,14 +70,14 @@ func (a *WalletApplication) TriggerTXFromFE(amount float64, fee float64, address
return a.TransactionFailed return a.TransactionFailed
} }
// PrepareTransaction is triggered from the frontend (Transaction.vue) and will initialize a new tx. // PrepareTransaction 从前端 (Transaction.vue) 触发,并将初始化一个新的 tx。
// methods called are defined in buildchain.go // 调用的方法在 buildchain.go 中定义
func (a *WalletApplication) PrepareTransaction(amount int64, fee int64, address string) { func (a *WalletApplication) PrepareTransaction(amount int64, fee int64, address string) {
balance, err := a.GetTokenBalance() balance, err := a.GetTokenBalance()
if err != nil { if err != nil {
a.log.Errorln("Error when querying wallet balance. Reason: ", err) a.log.Errorln("查询钱包余额时出错。原因: ", err)
a.sendWarning("Unable to poll balance for wallet. Please try again later.") a.sendWarning("无法轮询钱包的余额。请稍后重试。")
a.TransactionFailed = true a.TransactionFailed = true
return return
} }
@ -93,7 +93,7 @@ func (a *WalletApplication) PrepareTransaction(amount int64, fee int64, address
if a.TransactionFinished { if a.TransactionFinished {
a.TransactionFinished = false a.TransactionFinished = false
// Asynchronously inform FE of TX state in wallet. // 异步通知 FE 钱包中的 TX 状态。
go func() { go func() {
for !a.TransactionFinished { for !a.TransactionFinished {
a.RT.Events.Emit("tx_in_transit", a.TransactionFinished) a.RT.Events.Emit("tx_in_transit", a.TransactionFinished)
@ -111,19 +111,19 @@ func (a *WalletApplication) PrepareTransaction(amount int64, fee int64, address
} }
func (a *WalletApplication) putTXOnNetwork(tx *Transaction) (bool, string) { func (a *WalletApplication) putTXOnNetwork(tx *Transaction) (bool, string) {
a.log.Info("Attempting to communicate with mainnet on: " + a.Network.URL + a.Network.Handles.Transaction) a.log.Info("尝试与 上的主网通信: " + a.Network.URL + a.Network.Handles.Transaction)
/* TEMPORARILY COMMENTED OUT */ /* 暂时注释掉 */
a.log.Warnln("TX Ordinal:", tx.Edge.Data.LastTxRef.Ordinal) a.log.Warnln("TX 序数:", tx.Edge.Data.LastTxRef.Ordinal)
bytesRepresentation, err := json.Marshal(tx) bytesRepresentation, err := json.Marshal(tx)
if err != nil { if err != nil {
a.log.Errorln("Unable to parse JSON data for transaction", err) a.log.Errorln("无法解析事务的 JSON 数据", err)
a.sendError("Unable to parse JSON data for transaction", err) a.sendError("无法解析事务的 JSON 数据", err)
return false, "" return false, ""
} }
resp, err := http.Post(a.Network.URL+a.Network.Handles.Transaction, "application/json", bytes.NewBuffer(bytesRepresentation)) resp, err := http.Post(a.Network.URL+a.Network.Handles.Transaction, "application/json", bytes.NewBuffer(bytesRepresentation))
if err != nil { if err != nil {
a.log.Errorln("Failed to send HTTP request. Reason: ", err) a.log.Errorln("发送 HTTP 请求失败。原因: ", err)
a.sendError("Unable to send request to mainnet. Please check your internet connection. Reason: ", err) a.sendError("无法向主网发送请求。请检查您的互联网连接。原因: ", err)
return false, "" return false, ""
} }
defer resp.Body.Close() defer resp.Body.Close()
@ -132,20 +132,20 @@ func (a *WalletApplication) putTXOnNetwork(tx *Transaction) (bool, string) {
bodyBytes, err := ioutil.ReadAll(resp.Body) bodyBytes, err := ioutil.ReadAll(resp.Body)
if err != nil { if err != nil {
a.log.Errorln(string(bodyBytes)) a.log.Errorln(string(bodyBytes))
a.log.Errorln("Failed to read the response body. Reason: ", err) a.log.Errorln("无法读取响应正文。原因: ", err)
} }
bodyString := string(bodyBytes[1:65]) bodyString := string(bodyBytes[1:65])
a.log.Infoln("The bytesize of the request body: ", len(bodyBytes)) a.log.Infoln("请求正文的 bytesize: ", len(bodyBytes))
if len(bodyBytes) == 66 { if len(bodyBytes) == 66 {
a.log.Info("Transaction Hash: ", bodyString) a.log.Info("交易哈希: ", bodyString)
a.TxPending(bodyString) a.TxPending(bodyString)
a.log.Infoln("Transaction has been successfully sent to the network.") a.log.Infoln("交易已成功发送到网络。")
a.sendSuccess("Transaction successfully sent!") a.sendSuccess("交易发送成功!")
return true, bodyString return true, bodyString
} }
a.log.Warn(bodyString) a.log.Warn(bodyString)
a.sendWarning("Unable to put transaction on the network. Reason: " + bodyString) a.sendWarning("无法将交易放到网络上。原因: " + bodyString)
return false, "" return false, ""
} }
@ -169,8 +169,8 @@ func (a *WalletApplication) sendTransaction(txFile string) *models.TXHistory {
bytes := []byte(txObject) bytes := []byte(txObject)
err := json.Unmarshal(bytes, &tx) err := json.Unmarshal(bytes, &tx)
if err != nil { if err != nil {
a.sendError("Unable to parse the last transaction. Reason:", err) a.sendError("无法解析最后一个事务。原因:", err)
a.log.Errorf("Unable to parse contents of last_tx. Reason: %s", err) a.log.Errorf("无法解析 last_tx 的内容。原因: %s", err)
return nil return nil
} }
@ -201,7 +201,7 @@ func (a *WalletApplication) sendTransaction(txFile string) *models.TXHistory {
Status: "Error", Status: "Error",
Failed: true, Failed: true,
} }
a.log.Errorln("TX Failed, storing with failed state.") a.log.Errorln("TX 失败,存储失败状态。")
a.storeTX(txData) a.storeTX(txData)
a.TransactionFinished = true a.TransactionFinished = true
a.TransactionFailed = true a.TransactionFailed = true
@ -214,20 +214,20 @@ func (a *WalletApplication) storeTX(txData *models.TXHistory) {
return return
} }
if err := a.DB.Model(&a.wallet).Where("wallet_alias = ?", a.wallet.WalletAlias).Association("TXHistory").Append(txData).Error; err != nil { if err := a.DB.Model(&a.wallet).Where("wallet_alias = ?", a.wallet.WalletAlias).Association("TXHistory").Append(txData).Error; err != nil {
a.log.Errorln("Unable to update the DB record with the new TX. Reason: ", err) a.log.Errorln("无法使用新的 TX 更新 DB 记录。原因: ", err)
a.sendError("Unable to update the DB record with the new TX. Reason: ", err) a.sendError("无法使用新的 TX 更新 DB 记录。原因: ", err)
} }
a.log.Infoln("Successfully stored tx in DB") a.log.Infoln("已成功将 tx 存储在 DB 中")
} }
// loadTXFromFile takes a file, scans it and returns it in an object // loadTXFromFile 获取文件,扫描它并在对象中返回它
func (a *WalletApplication) loadTXFromFile(txFile string) string { func (a *WalletApplication) loadTXFromFile(txFile string) string {
var txObjects string var txObjects string
fi, err := os.Stat(txFile) fi, err := os.Stat(txFile)
if err != nil { if err != nil {
a.log.Errorln("Unable to stat last_tx. Reason: ", err) a.log.Errorln("无法统计last_tx。原因: ", err)
a.sendError("Unable to stat last_tx. Reason: ", err) a.sendError("无法统计last_tx。原因: ", err)
return "" return ""
} }
// get the size // get the size
@ -239,8 +239,8 @@ func (a *WalletApplication) loadTXFromFile(txFile string) string {
file, err := os.Open(txFile) // acct file, err := os.Open(txFile) // acct
if err != nil { if err != nil {
a.log.Errorln("Unable to open TX file. Reason: ", err) a.log.Errorln("无法打开 TX 文件。原因: ", err)
a.sendError("Unable to read last tx. Aborting... Reason: ", err) a.sendError("无法读取最后的 tx。中止...原因: ", err)
return "" return ""
} }
@ -254,21 +254,20 @@ func (a *WalletApplication) loadTXFromFile(txFile string) string {
return txObjects return txObjects
} }
/* Query TX */ /* 查询 TX */
// TxProcessed will query the last transaction. If no answer is returned, it means it's processed and the // TxProcessed 将查询最后一笔交易。如果未返回任何答案,则表示该答案已处理,并且 method 将返回 true。
// method will return true.
func (a *WalletApplication) TxProcessed(TXHash string) bool { func (a *WalletApplication) TxProcessed(TXHash string) bool {
a.log.Info("Communicating with mainnet on: " + a.Network.URL + a.Network.Handles.Transaction + "/" + TXHash) a.log.Info("Communicating with mainnet on: " + a.Network.URL + a.Network.Handles.Transaction + "/" + TXHash)
resp, err := http.Get(a.Network.URL + a.Network.Handles.Transaction + "/" + TXHash) resp, err := http.Get(a.Network.URL + a.Network.Handles.Transaction + "/" + TXHash)
if err != nil { if err != nil {
a.log.Errorln("Failed to send HTTP request. Reason: ", err) a.log.Errorln("发送 HTTP 请求失败。原因: ", err)
if err := a.DB.Model(&a.wallet).Where("wallet_alias = ?", a.wallet.WalletAlias).Delete(&a.wallet).Error; err != nil { if err := a.DB.Model(&a.wallet).Where("wallet_alias = ?", a.wallet.WalletAlias).Delete(&a.wallet).Error; err != nil {
a.log.Errorln("Unable to delete wallet upon failed import. Reason: ", err) a.log.Errorln("导入失败时无法删除 wallet。原因: ", err)
return false return false
} }
a.log.Errorln("Unable to verify transaction status. Please check your internet connection.") a.log.Errorln("无法验证交易状态。请检查您的互联网连接。")
return false return false
} }
defer resp.Body.Close() defer resp.Body.Close()
@ -282,17 +281,17 @@ func (a *WalletApplication) TxProcessed(TXHash string) bool {
return false return false
} }
// Declared an empty interface // 声明为空接口
var result map[string]interface{} var result map[string]interface{}
// Unmarshal or Decode the JSON to the interface. // 将 JSON 解组或解码到接口。
err = json.Unmarshal(bodyBytes, &result) err = json.Unmarshal(bodyBytes, &result)
if err != nil { if err != nil {
return false return false
} }
if result["cbBaseHash"] != nil { if result["cbBaseHash"] != nil {
a.log.Infoln("CheckPoint Hash :", result["cbBaseHash"]) a.log.Infoln("CheckPoint 哈希:", result["cbBaseHash"])
return true return true
} }
@ -307,7 +306,7 @@ type txStatus struct {
Error string Error string
} }
// TxPending takes a TX Hash and updates the frontend with the current status (Pending/Error/Complete) // TxPending 获取 TX 哈希并使用当前状态(Pending/Error/Complete)更新前端
func (a *WalletApplication) TxPending(TXHash string) { func (a *WalletApplication) TxPending(TXHash string) {
status := &txStatus{ status := &txStatus{
@ -332,7 +331,7 @@ func (a *WalletApplication) TxPending(TXHash string) {
if retryCounter == 29 { if retryCounter == 29 {
// Register failed transaction // Register failed transaction
a.sendWarning("Unable to get verification of processed transaction from the network. Please try again later.") a.sendWarning("Unable to get verification of processed transaction from the network. 请稍后重试。")
a.log.Errorf("Unable to get status from the network on transaction: %s", TXHash) a.log.Errorf("Unable to get status from the network on transaction: %s", TXHash)
a.RT.Events.Emit("tx_pending", status.Error) a.RT.Events.Emit("tx_pending", status.Error)
if err := a.DB.Table("tx_histories").Where("hash = ?", TXHash).Updates(map[string]interface{}{"status": status.Error, "failed": true}).Error; err != nil { if err := a.DB.Table("tx_histories").Where("hash = ?", TXHash).Updates(map[string]interface{}{"status": status.Error, "failed": true}).Error; err != nil {
@ -357,8 +356,8 @@ func (a *WalletApplication) TxPending(TXHash string) {
} }
} }
a.log.Infof("Transaction %v has been successfully processed", TXHash) a.log.Infof("已成功处理 %v 交易", TXHash)
a.sendSuccess("Transaction " + TXHash[:30] + "... has been successfully processed") a.sendSuccess("交易 " + TXHash[:30] + " 已成功处理")
if err := a.DB.Table("tx_histories").Where("hash = ?", TXHash).UpdateColumn("status", status.Complete).Error; err != nil { if err := a.DB.Table("tx_histories").Where("hash = ?", TXHash).UpdateColumn("status", status.Complete).Error; err != nil {
a.log.Errorln("Unable to query database object for the imported wallet. Reason: ", err) a.log.Errorln("Unable to query database object for the imported wallet. Reason: ", err)
a.LoginError("Unable to query database object for the imported wallet.") a.LoginError("Unable to query database object for the imported wallet.")

6
gui_wails01_learn/backend/utils.go

@ -18,8 +18,8 @@ import (
"github.com/dustin/go-humanize" "github.com/dustin/go-humanize"
) )
// newReleaseAvailable generates a notification to FE everytime a new release on // newReleaseAvailable 每次发布新版本时,都会向FE生成通知
// GitHub doesn't match a.Version. // GitHub与.Version不匹配。
func (a *WalletApplication) newReleaseAvailable() { func (a *WalletApplication) newReleaseAvailable() {
update := new(UpdateWallet) update := new(UpdateWallet)
update.app = a update.app = a
@ -89,7 +89,7 @@ func (a *WalletApplication) detectJavaPath() {
} }
} }
//normalizeAmounts takes amount/fee in int64 and normalizes it. Example: passing 821500000000 will return 8215 // normalizeAmounts takes amount/fee in int64 and normalizes it. Example: passing 821500000000 will return 8215
func normalizeAmounts(i int64) (string, error) { func normalizeAmounts(i int64) (string, error) {
f := fmt.Sprintf("%.8f", float64(i)/1e8) f := fmt.Sprintf("%.8f", float64(i)/1e8)
return f, nil return f, nil

190
gui_wails01_learn/backend/wallet.go

@ -4,6 +4,7 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"io"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"os" "os"
@ -15,7 +16,7 @@ import (
"github.com/grvlle/constellation_wallet/backend/models" "github.com/grvlle/constellation_wallet/backend/models"
) )
// ImportWallet is triggered when a user logs into a new Molly wallet for the first time // ImportWallet 在用户首次登录新的 Molly 钱包时触发
func (a *WalletApplication) ImportWallet(keystorePath, keystorePassword, keyPassword, alias string) bool { func (a *WalletApplication) ImportWallet(keystorePath, keystorePassword, keyPassword, alias string) bool {
alias = strings.ToLower(alias) alias = strings.ToLower(alias)
@ -25,7 +26,7 @@ func (a *WalletApplication) ImportWallet(keystorePath, keystorePassword, keyPass
Currency: "USD"} Currency: "USD"}
if runtime.GOOS == "windows" && !a.javaInstalled() { if runtime.GOOS == "windows" && !a.javaInstalled() {
a.LoginError("Unable to detect your Java path. Please make sure that Java has been installed.") a.LoginError("无法检测到 Java 路径。请确保已安装 Java。")
return false return false
} }
@ -36,12 +37,12 @@ func (a *WalletApplication) ImportWallet(keystorePath, keystorePassword, keyPass
} }
if keystorePath == "" { if keystorePath == "" {
a.LoginError("Please provide a path to the KeyStore file.") a.LoginError("请提供 KeyStore 文件的路径。")
return false return false
} }
if !a.passwordsProvided(keystorePassword, keyPassword, alias) { if !a.passwordsProvided(keystorePassword, keyPassword, alias) {
a.log.Warnln("One or more passwords were not provided.") a.log.Warnln("未提供一个或多个密码。")
return false return false
} }
@ -55,21 +56,21 @@ func (a *WalletApplication) ImportWallet(keystorePath, keystorePassword, keyPass
if !a.DB.NewRecord(&a.wallet) { if !a.DB.NewRecord(&a.wallet) {
keystorePasswordHashed, err := a.GenerateSaltedHash(keystorePassword) keystorePasswordHashed, err := a.GenerateSaltedHash(keystorePassword)
if err != nil { if err != nil {
a.log.Errorln("Unable to generate password hash. Reason: ", err) a.log.Errorln("无法生成密码哈希。原因: ", err)
a.LoginError("Unable to generate password hash.") a.LoginError("无法生成密码哈希。")
return false return false
} }
keyPasswordHashed, err := a.GenerateSaltedHash(keyPassword) keyPasswordHashed, err := a.GenerateSaltedHash(keyPassword)
if err != nil { if err != nil {
a.log.Errorln("Unable to generate password hash. Reason: ", err) a.log.Errorln("无法生成密码哈希。原因: ", err)
a.LoginError("Unable to generate password hash.") a.LoginError("无法生成密码哈希。")
return false return false
} }
if err := a.DB.Create(&a.wallet).Error; err != nil { if err := a.DB.Create(&a.wallet).Error; err != nil {
a.log.Errorln("Unable to create database object for the imported wallet. Reason: ", err) a.log.Errorln("无法为导入的 wallet 创建数据库对象。原因: ", err)
a.LoginError("Unable to create database object for the imported wallet. Maybe it has already been imported? Try to login.") a.LoginError("无法为导入的 wallet 创建数据库对象。也许它已经被导入了?尝试登录。")
return false return false
} }
@ -79,18 +80,18 @@ func (a *WalletApplication) ImportWallet(keystorePath, keystorePassword, keyPass
err = a.createTXFiles() err = a.createTXFiles()
if err != nil { if err != nil {
a.log.Fatalln("Unable to create TX files. Check fs permissions. Reason: ", err) a.log.Fatalln("无法创建 TX 文件。检查 fs 权限。原因: ", err)
a.sendError("Unable to create TX files. Check fs permissions. Reason: ", err) a.sendError("无法创建 TX 文件。检查 fs 权限。原因: ", err)
} }
if err := a.DB.Model(&a.wallet).Where("wallet_alias = ?", a.wallet.WalletAlias).Update("Path", models.Path{LastTXFile: a.paths.LastTXFile, PrevTXFile: a.paths.PrevTXFile, EmptyTXFile: a.paths.EmptyTXFile}).Error; err != nil { if err := a.DB.Model(&a.wallet).Where("wallet_alias = ?", a.wallet.WalletAlias).Update("Path", models.Path{LastTXFile: a.paths.LastTXFile, PrevTXFile: a.paths.PrevTXFile, EmptyTXFile: a.paths.EmptyTXFile}).Error; err != nil {
a.log.Errorln("Unable to update the DB record with the tmp tx-paths. Reason: ", err) a.log.Errorln("无法使用 tmp tx-paths 更新数据库记录。原因: ", err)
a.sendError("Unable to update the DB record with the tmp tx-paths. Reason: ", err) a.sendError("无法使用 tmp tx-paths 更新数据库记录。原因: ", err)
} }
if err := a.DB.Where("wallet_alias = ?", a.wallet.WalletAlias).First(&a.wallet).Updates(&models.Wallet{KeyStorePath: keystorePath, KeystorePasswordHash: keystorePasswordHashed, KeyPasswordHash: keyPasswordHashed}).Error; err != nil { if err := a.DB.Where("wallet_alias = ?", a.wallet.WalletAlias).First(&a.wallet).Updates(&models.Wallet{KeyStorePath: keystorePath, KeystorePasswordHash: keystorePasswordHashed, KeyPasswordHash: keyPasswordHashed}).Error; err != nil {
a.log.Errorln("Unable to query database object for the imported wallet. Reason: ", err) a.log.Errorln("无法查询导入的 wallet 的数据库对象。原因: ", err)
a.LoginError("Unable to query database object for the imported wallet.") a.LoginError("无法查询导入的 wallet 的数据库对象。")
return false return false
} }
@ -99,10 +100,10 @@ func (a *WalletApplication) ImportWallet(keystorePath, keystorePassword, keyPass
a.WalletImported = true a.WalletImported = true
err = a.initWallet(keystorePath) err = a.initWallet(keystorePath)
if err != nil { if err != nil {
a.log.Errorln("Failed to initialize wallet. Reason: ", err) a.log.Errorln("初始化 wallet 失败。原因: ", err)
// If unable to import previous transactions, remove wallet from DB and logout. // 如果无法导入以前的交易,请从 DB 中删除钱包并注销。
if err := a.DB.Model(&a.wallet).Where("wallet_alias = ?", a.wallet.WalletAlias).Delete(&a.wallet).Error; err != nil { if err := a.DB.Model(&a.wallet).Where("wallet_alias = ?", a.wallet.WalletAlias).Delete(&a.wallet).Error; err != nil {
a.log.Errorln("Unable to delete wallet upon failed import. Reason: ", err) a.log.Errorln("导入失败时无法删除 wallet。原因: ", err)
return false return false
} }
return false return false
@ -110,7 +111,7 @@ func (a *WalletApplication) ImportWallet(keystorePath, keystorePassword, keyPass
return true return true
} else if a.DB.NewRecord(&a.wallet) { // There may already be an existing DB record in some circumstances. } else if a.DB.NewRecord(&a.wallet) { // 在某些情况下,可能已经存在 DB 记录。
a.DB.First(&a.wallet) a.DB.First(&a.wallet)
a.UserLoggedIn = false a.UserLoggedIn = false
@ -118,7 +119,7 @@ func (a *WalletApplication) ImportWallet(keystorePath, keystorePassword, keyPass
a.WalletImported = true a.WalletImported = true
err := a.initWallet(a.wallet.KeyStorePath) err := a.initWallet(a.wallet.KeyStorePath)
if err != nil { if err != nil {
a.log.Errorln("Faled to initialize wallet. Reason: ", err) a.log.Errorln("初始化 wallet。原因: ", err)
return false return false
} }
return true return true
@ -128,29 +129,29 @@ func (a *WalletApplication) ImportWallet(keystorePath, keystorePassword, keyPass
return false return false
} }
// CreateWallet is called when creating a new wallet in frontend component Login.vue // CreateWallet 在前端组件 Login.vue 中创建新钱包时调用
func (a *WalletApplication) CreateWallet(keystorePath, keystorePassword, keyPassword, alias, label string) bool { func (a *WalletApplication) CreateWallet(keystorePath, keystorePassword, keyPassword, alias, label string) bool {
alias = strings.ToLower(alias) alias = strings.ToLower(alias)
if runtime.GOOS == "windows" && !a.javaInstalled() { if runtime.GOOS == "windows" && !a.javaInstalled() {
a.LoginError("Unable to detect your Java path. Please make sure that Java has been installed.") a.LoginError("无法检测到 Java 路径。请确保已安装 Java。")
return false return false
} }
if !a.TransactionFinished { if !a.TransactionFinished {
a.log.Warn("Cannot Create wallet in a pending transaction.") a.log.Warn("无法在待处理交易中创建钱包。")
a.LoginError("Cannot create a new wallet while there's a pending transaction.") a.LoginError("当有待处理交易时,无法创建新钱包。")
return false return false
} }
if keystorePath == "" { if keystorePath == "" {
a.LoginError("Please provide a path to store the KeyStore file.") a.LoginError("请提供用于存储 KeyStore 文件的路径。")
return false return false
} }
if !a.passwordsProvided(keystorePassword, keyPassword, alias) { if !a.passwordsProvided(keystorePassword, keyPassword, alias) {
a.log.Warnln("One or more passwords were not provided.") a.log.Warnln("未提供一个或多个密码。")
return false return false
} }
@ -163,15 +164,15 @@ func (a *WalletApplication) CreateWallet(keystorePath, keystorePassword, keyPass
keystorePasswordHashed, err := a.GenerateSaltedHash(keystorePassword) keystorePasswordHashed, err := a.GenerateSaltedHash(keystorePassword)
if err != nil { if err != nil {
a.log.Errorln("Unable to generate password hash. Reason: ", err) a.log.Errorln("无法生成密码哈希。原因: ", err)
a.sendError("Unable to generate password hash. Reason: ", err) a.sendError("无法生成密码哈希。原因: ", err)
return false return false
} }
keyPasswordHashed, err := a.GenerateSaltedHash(keyPassword) keyPasswordHashed, err := a.GenerateSaltedHash(keyPassword)
if err != nil { if err != nil {
a.log.Errorln("Unable to generate password hash. Reason: ", err) a.log.Errorln("无法生成密码哈希。原因: ", err)
a.sendError("Unable to generate password hash. Reason: ", err) a.sendError("无法生成密码哈希。原因: ", err)
return false return false
} }
@ -184,14 +185,14 @@ func (a *WalletApplication) CreateWallet(keystorePath, keystorePassword, keyPass
if !a.DB.NewRecord(&a.wallet) { if !a.DB.NewRecord(&a.wallet) {
if err := a.DB.Create(&a.wallet).Error; err != nil { if err := a.DB.Create(&a.wallet).Error; err != nil {
a.log.Errorln("Unable to create database object for new wallet. Reason: ", err) a.log.Errorln("无法为新钱包创建数据库对象。原因: ", err)
a.LoginError("Unable to create new wallet. Alias already exists.") a.LoginError("无法创建新钱包。别名 已存在。")
return false return false
} }
if err := a.DB.Where("wallet_alias = ?", alias).First(&a.wallet).Updates(&models.Wallet{KeyStorePath: keystorePath, KeystorePasswordHash: keystorePasswordHashed, KeyPasswordHash: keyPasswordHashed}).Error; err != nil { if err := a.DB.Where("wallet_alias = ?", alias).First(&a.wallet).Updates(&models.Wallet{KeyStorePath: keystorePath, KeystorePasswordHash: keystorePasswordHashed, KeyPasswordHash: keyPasswordHashed}).Error; err != nil {
a.log.Errorln("Unable to query database object for new wallet after wallet creation. Reason: ", err) a.log.Errorln("创建钱包后,无法查询新钱包的数据库对象。原因: ", err)
a.sendError("Unable to query database object for new wallet after wallet creation. Reason: ", err) a.sendError("创建钱包后,无法查询新钱包的数据库对象。原因: ", err)
return false return false
} }
@ -203,8 +204,8 @@ func (a *WalletApplication) CreateWallet(keystorePath, keystorePassword, keyPass
a.wallet.Address = a.GenerateDAGAddress() a.wallet.Address = a.GenerateDAGAddress()
if err := a.DB.Model(&a.wallet).Where("wallet_alias = ?", a.wallet.WalletAlias).Update("Address", a.wallet.Address).Error; err != nil { if err := a.DB.Model(&a.wallet).Where("wallet_alias = ?", a.wallet.WalletAlias).Update("Address", a.wallet.Address).Error; err != nil {
a.log.Errorln("Unable to update db object new wallet, with the DAG address. Reason: ", err) a.log.Errorln("无法使用 DAG 地址更新 db 对象 new wallet。原因: ", err)
a.sendError("Unable to update db object new wallet, with the DAG address. Reason. Reason: ", err) a.sendError("无法使用 DAG 地址更新 db 对象 new wallet。原因. Reason: ", err)
} }
a.KeyStoreAccess = a.WalletKeystoreAccess() a.KeyStoreAccess = a.WalletKeystoreAccess()
@ -215,8 +216,8 @@ func (a *WalletApplication) CreateWallet(keystorePath, keystorePassword, keyPass
err := a.createTXFiles() err := a.createTXFiles()
if err != nil { if err != nil {
a.log.Fatalln("Unable to create TX files. Check fs permissions. Reason: ", err) a.log.Fatalln("无法创建 TX 文件。检查 fs 权限。原因: ", err)
a.sendError("Unable to create TX files. Check fs permissions. Reason: ", err) a.sendError("无法创建 TX 文件。检查 fs 权限。原因: ", err)
} }
if err := a.DB.Where("wallet_alias = ?", a.wallet.WalletAlias).First(&a.wallet).Update("Path", models.Path{LastTXFile: a.paths.LastTXFile, PrevTXFile: a.paths.PrevTXFile, EmptyTXFile: a.paths.EmptyTXFile}).Error; err != nil { if err := a.DB.Where("wallet_alias = ?", a.wallet.WalletAlias).First(&a.wallet).Update("Path", models.Path{LastTXFile: a.paths.LastTXFile, PrevTXFile: a.paths.PrevTXFile, EmptyTXFile: a.paths.EmptyTXFile}).Error; err != nil {
@ -344,73 +345,79 @@ func (a *WalletApplication) initTXFilePath() {
func (a *WalletApplication) initTXFromDB() { func (a *WalletApplication) initTXFromDB() {
if err := a.DB.Model(&a.wallet).Where("alias = ?", a.wallet.WalletAlias).Association("TXHistory").Find(&a.wallet.TXHistory).Error; err != nil { if err := a.DB.Model(&a.wallet).Where("alias = ?", a.wallet.WalletAlias).Association("TXHistory").Find(&a.wallet.TXHistory).Error; err != nil {
a.log.Error("Unable to initialize historic transactions from DB. Reason: ", err) a.log.Error("无法从 DB 初始化历史事务。原因: ", err)
a.sendError("Unable to initialize historic transactions from DB. Reason: ", err) a.sendError("无法从 DB 初始化历史事务。原因: ", err)
return return
} }
allTX := []models.TXHistory{} allTX := []models.TXHistory{}
for i, tx := range a.wallet.TXHistory { for i, tx := range a.wallet.TXHistory {
allTX = append([]models.TXHistory{tx}, allTX...) // prepend to reverse list for FE allTX = append([]models.TXHistory{tx}, allTX...) // 在 FE 的 reverse list 之前
if a.wallet.TXHistory[i].Status == "Pending" { if a.wallet.TXHistory[i].Status == "Pending" {
a.TxPending(a.wallet.TXHistory[i].Hash) a.TxPending(a.wallet.TXHistory[i].Hash)
} }
} }
a.RT.Events.Emit("update_tx_history", allTX) // Pass the tx to the frontend as a new transaction. a.RT.Events.Emit("update_tx_history", allTX) // 将 tx 作为新交易传递给前端。
} }
// initTXFromBlockExplorer is called when an existing wallet is imported. // initTXFromBlockExplorer 在导入现有 wallet 时调用。
func (a *WalletApplication) initTXFromBlockExplorer() error { func (a *WalletApplication) initTXFromBlockExplorer() error {
a.log.Info("Sending API call to block explorer on: " + a.Network.BlockExplorer.URL + a.Network.BlockExplorer.Handles.CollectTX + a.wallet.Address) a.log.Info("向区块浏览器发送 API 调用: " + a.Network.BlockExplorer.URL + a.Network.BlockExplorer.Handles.CollectTX + a.wallet.Address)
resp, err := http.Get(a.Network.BlockExplorer.URL + a.Network.BlockExplorer.Handles.CollectTX + a.wallet.Address) resp, err := http.Get(a.Network.BlockExplorer.URL + a.Network.BlockExplorer.Handles.CollectTX + a.wallet.Address)
if err != nil { if err != nil {
a.log.Errorln("Failed to send HTTP request. Reason: ", err) a.log.Errorln("发送 HTTP 请求失败。原因: ", err)
a.LoginError("Unable to collect previous transactions from blockexplorer.") a.LoginError("无法从 BlockExplorer 收集以前的交易。")
return err return err
} }
defer resp.Body.Close() defer func(Body io.ReadCloser) {
err := Body.Close()
if err != nil {
return
}
}(resp.Body)
if resp.Body != nil { if resp.Body != nil {
bodyBytes, err := ioutil.ReadAll(resp.Body) // bodyBytes, err := ioutil.ReadAll(resp.Body)
bodyBytes, err := io.ReadAll(resp.Body)
if err != nil { if err != nil {
a.LoginError("Unable to collect previous transactions from blockexplorer. Try again later.") a.LoginError("无法从 BlockExplorer 收集以前的交易。 请稍后重试。")
a.log.Errorln("Unable to collect previous transactions from blockexplorer. Reason: ", err) a.log.Errorln("无法从 BlockExplorer 收集以前的交易。 原因: ", err)
return err return err
} }
ok, error := a.verifyAPIResponse(bodyBytes) ok, e := a.verifyAPIResponse(bodyBytes)
// Blockexplorer returns below string when no previous transactions are found // 当没有找到以前的交易时,BlockExplorer 返回以下字符串
if !ok && error != "Cannot find transactions for sender" { if !ok {
a.log.Errorln("API returned the following error", error) if e != "Cannot find transactions for sender" {
a.LoginError("The wallet import failed. Please check your internet connection and try again.") a.log.Errorln("API 返回以下错误", e)
return errors.New(error) a.LoginError("钱包导入失败。请检查您的互联网连接,然后重试。")
} return errors.New(e)
} else {
// If no previous transactions for imported wallet - proceed // 如果导入的钱包之前没有发生过交易 - 继续
if !ok && error == "Cannot find transactions for sender" { a.log.Info("无法检测到任何以前的交易。")
a.log.Info("Unable to detect any previous transactions.") return nil
return nil }
} }
allTX := []models.TXHistory{} var allTX []models.TXHistory
err = json.Unmarshal(bodyBytes, &allTX) err = json.Unmarshal(bodyBytes, &allTX)
if err != nil { if err != nil {
a.log.Errorln("Unable to fetch TX history from block explorer. Reason: ", err) a.log.Errorln("无法从区块浏览器获取 TX 历史记录。原因: ", err)
a.sendError("Unable to fetch TX history from block explorer. Reason: ", err) a.sendError("无法从区块浏览器获取 TX 历史记录。原因: ", err)
return err return err
} }
// Reverse order // 倒序
for i := len(allTX)/2 - 1; i >= 0; i-- { for i := len(allTX)/2 - 1; i >= 0; i-- {
opp := len(allTX) - 1 - i opp := len(allTX) - 1 - i
allTX[i], allTX[opp] = allTX[opp], allTX[i] allTX[i], allTX[opp] = allTX[opp], allTX[i]
} }
a.log.Infof("Successfully collected %d previous transactions. Updating local state...", len(allTX)) a.log.Infof("已成功收集 %d 笔以前的交易。更新本地状态...", len(allTX))
for i, tx := range allTX { for i, tx := range allTX {
@ -428,31 +435,31 @@ func (a *WalletApplication) initTXFromBlockExplorer() error {
if i+1 == len(allTX) { if i+1 == len(allTX) {
// 向区块链发送信息
err := a.rebuildTxChainState(tx.Hash) err := a.rebuildTxChainState(tx.Hash)
if err != nil { if err != nil {
a.log.Errorln(err) a.log.Errorln(err)
// If unable to import previous transactions, remove wallet from DB and logout. // 如何无法导入以前的交易, 从 DB 中删除钱包并注销。
//TODO: logout // TODO logout
if err := a.DB.Model(&a.wallet).Where("wallet_alias = ?", a.wallet.WalletAlias).Delete(&a.wallet).Error; err != nil { if err := a.DB.Model(&a.wallet).Where("wallet_alias = ?", a.wallet.WalletAlias).Delete(&a.wallet).Error; err != nil {
a.log.Errorln("Unable to delete wallet upon failed import. Reason: ", err) a.log.Errorln("导入失败时无法删除 wallet。原因: ", err)
return err return err
} }
a.log.Panicln("Unable to import previous transactions") a.log.Panicln("无法导入以前的交易")
a.LoginError("Unable to collect previous TX's from blockexplorer. Please try again later.") a.LoginError("无法从 BlockExplorer 收集以前的 TX。请稍后重试。")
} }
} }
} }
} else { } else {
a.log.Info("Unable to detect any previous transactions.") a.log.Info("无法检测到任何以前的交易。")
return nil return nil
} }
return nil return nil
} }
// PassKeysToFrontend emits the keys to the settings.Vue component on a // passKeysToFrontend 将键发送到设置中。Vue 组件 5 秒间隔
// 5 second interval
func (a *WalletApplication) passKeysToFrontend() { func (a *WalletApplication) passKeysToFrontend() {
if a.wallet.KeyStorePath != "" && a.wallet.Address != "" { if a.wallet.KeyStorePath != "" && a.wallet.Address != "" {
go func() { go func() {
@ -469,43 +476,43 @@ func (a *WalletApplication) passKeysToFrontend() {
func (a *WalletApplication) passwordsProvided(keystorePassword, keyPassword, alias string) bool { func (a *WalletApplication) passwordsProvided(keystorePassword, keyPassword, alias string) bool {
if keystorePassword == "" { if keystorePassword == "" {
a.LoginError("Please provide a Key Store password.") a.LoginError("请提供密钥库密码。")
return false return false
} else if keyPassword == "" { } else if keyPassword == "" {
a.LoginError("Please provide a Key Password.") a.LoginError("请提供密钥密码。")
return false return false
} else if alias == "" { } else if alias == "" {
a.LoginError("An Alias has not been provided.") a.LoginError("尚未提供别名。")
return false return false
} }
return true return true
} }
// GetTokenBalance polls and parses the token balance of a wallet and returns it as a float64. // GetTokenBalance 轮询并解析钱包的代币余额,并将其返回为 float64。
func (a *WalletApplication) GetTokenBalance() (float64, error) { func (a *WalletApplication) GetTokenBalance() (float64, error) {
a.log.Debug("Contacting mainnet on: " + a.Network.URL + a.Network.Handles.Balance + " Sending the following payload: " + a.wallet.Address) a.log.Debug("通过以下方式联系主网: " + a.Network.URL + a.Network.Handles.Balance + " 发送以下有效负载:" + a.wallet.Address)
resp, err := http.Get(a.Network.URL + a.Network.Handles.Balance + a.wallet.Address) resp, err := http.Get(a.Network.URL + a.Network.Handles.Balance + a.wallet.Address)
if err != nil { if err != nil {
a.log.Errorln("Failed to send HTTP request. Reason: ", err) a.log.Errorln("发送 HTTP 请求失败。原因: ", err)
return 0, err return 0, err
} }
if resp == nil { if resp == nil {
a.log.Errorln("Killing pollTokenBalance after 10 failed attempts to get balance from mainnet, Reason: ", err) a.log.Errorln("在尝试从主网获取余额 10 次失败后杀死 pollTokenBalance,原因: ", err)
a.sendWarning("Unable to showcase current balance. Please check your internet connectivity and restart the wallet application.") a.sendWarning("无法显示当前余额。请检查您的互联网连接并重新启动钱包应用程序。")
return 0, err return 0, err
} }
defer resp.Body.Close() defer resp.Body.Close()
bodyBytes, err := ioutil.ReadAll(resp.Body) bodyBytes, err := ioutil.ReadAll(resp.Body)
if err != nil { if err != nil {
a.log.Warn("Unable to update token balance. Reason: ", err) a.log.Warn("无法更新 Token 余额。原因: ", err)
return 0, err return 0, err
} }
var result map[string]interface{} var result map[string]interface{}
// Unmarshal or Decode the JSON to the interface. // 将 JSON 解组或解码到接口。
err = json.Unmarshal(bodyBytes, &result) err = json.Unmarshal(bodyBytes, &result)
if err != nil { if err != nil {
return 0, err return 0, err
@ -516,27 +523,24 @@ func (a *WalletApplication) GetTokenBalance() (float64, error) {
s = "0" // Empty means zero s = "0" // Empty means zero
} }
a.log.Infoln("Parsed the following balance: ", s) a.log.Infoln("解析了以下余额: ", s)
b, ok := s.(float64) b, ok := s.(float64)
if !ok { if !ok {
if err != nil {
a.log.Warnln("Unable to parse balance. Reason:", err)
}
return 0, err return 0, err
} }
f := fmt.Sprintf("%.2f", b/1e8) // Reverse normalized float f := fmt.Sprintf("%.2f", b/1e8) // Reverse normalized float
a.log.Infoln("Normalized the following balance: ", f) a.log.Infoln("规范化以下余额: ", f)
balance, err := strconv.ParseFloat(f, 64) balance, err := strconv.ParseFloat(f, 64)
if err != nil { if err != nil {
a.log.Warnln("Unable to type cast string to float for token balance poller. Check your internet connectivity") a.log.Warnln("无法为令牌余额 poller 键入将字符串转换为浮点数。检查您的互联网连接")
return 0, err return 0, err
} }
a.log.Infoln("Returning the following balance: ", balance) a.log.Infoln("返回以下余额: ", balance)
return balance, nil return balance, nil
} }

20364
gui_wails01_learn/frontend/package-lock.json

File diff suppressed because it is too large

87
gui_wails01_learn/frontend/package.json

@ -3,57 +3,58 @@
"author": "vito<gralle89@gmail.com>", "author": "vito<gralle89@gmail.com>",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "npm run serve",
"serve": "vue-cli-service serve", "serve": "vue-cli-service serve",
"build": "vue-cli-service build", "build": "vue-cli-service build",
"lint": "vue-cli-service lint" "lint": "vue-cli-service lint"
}, },
"dependencies": { "dependencies": {
"@types/vuelidate": "^0.7.13", "@types/vuelidate": "^0.7.13",
"@wailsapp/runtime": "^1.0.11", "@wailsapp/runtime": "1.0.11",
"babel-helper-vue-jsx-merge-props": "^2.0.3", "babel-helper-vue-jsx-merge-props": "2.0.3",
"bootstrap": "^4.4.1", "bootstrap": "4.4.1",
"chartist": "^0.11.4", "chartist": "0.11.4",
"core-js": "^2.6.11", "core-js": "2.6.11",
"es6-promise": "^4.2.8", "es6-promise": "4.2.8",
"jquery": "^3.5.0", "jquery": "3.5.0",
"node-sass": "^4.13.1", "node-sass": "4.13.1",
"popper.js": "^1.16.1", "popper.js": "1.16.1",
"sass-loader": "^8.0.2", "sass-loader": "8.0.2",
"serialize-javascript": "^2.1.2", "serialize-javascript": "2.1.2",
"update": "^0.7.4", "update": "0.7.4",
"vue": "^2.6.11", "vue": "2.6.11",
"vue-clickaway": "^2.2.2", "vue-clickaway": "2.2.2",
"vue-js-toggle-button": "^1.3.3", "vue-js-toggle-button": "1.3.3",
"vue-loading-spinner": "^1.0.11", "vue-loading-spinner": "1.0.11",
"vue-notifyjs": "^0.4.3", "vue-notifyjs": "0.4.3",
"vue-progressbar": "^0.7.5", "vue-progressbar": "0.7.5",
"vue-router": "^3.1.6", "vue-router": "3.1.6",
"vue-select": "^3.10.3", "vue-select": "3.10.3",
"vue-spinner": "^1.0.3", "vue-spinner": "1.0.3",
"vue-spinner-component": "^1.0.5", "vue-spinner-component": "1.0.5",
"idle-vue": "^2.0.5", "idle-vue": "2.0.5",
"sweetalert2": "^9.10.12", "sweetalert2": "9.10.12",
"@sweetalert2/theme-bootstrap-4": "^3.1.4", "@sweetalert2/theme-bootstrap-4": "3.1.4",
"vuelidate": "^0.7.5", "vuelidate": "0.7.5",
"vuex": "^3.1.3", "vuex": "3.1.3",
"webpack": "^4.42.0", "webpack": "4.42.0",
"yarn": "^1.22.4" "yarn": "1.22.4"
}, },
"devDependencies": { "devDependencies": {
"@vue/cli-plugin-babel": "^3.12.1", "@vue/cli-plugin-babel": "3.12.1",
"@vue/cli-plugin-eslint": "^3.12.1", "@vue/cli-plugin-eslint": "3.12.1",
"@vue/cli-service": "^4.3.1", "@vue/cli-service": "4.3.1",
"acorn": "^6.4.1", "acorn": "6.4.1",
"babel-eslint": "^10.1.0", "babel-eslint": "10.1.0",
"eslint": "^5.8.0", "eslint": "5.8.0",
"eslint-plugin-vue": "^5.0.0", "eslint-plugin-vue": "5.0.0",
"eventsource-polyfill": "^0.9.6", "eventsource-polyfill": "0.9.6",
"json-loader": "^0.5.7", "json-loader": "0.5.7",
"kind-of": "^6.0.3", "kind-of": "6.0.3",
"minimist": "^1.2.3", "minimist": "1.2.3",
"prerender-spa-plugin": "^3.4.0", "prerender-spa-plugin": "3.4.0",
"vue-template-compiler": "^2.6.11", "vue-template-compiler": "2.6.11",
"webpack-hot-middleware": "^2.24.3" "webpack-hot-middleware": "2.24.3"
}, },
"eslintConfig": { "eslintConfig": {
"root": true, "root": true,

6
gui_wails01_learn/frontend/src/App.vue

@ -5,7 +5,7 @@
<notifications></notifications> <notifications></notifications>
<router-view></router-view> <router-view></router-view>
</div> </div>
<page-overlay text="Applying Update. Please wait..." :isActive="overlay" /> <page-overlay text="应用正在更新。请稍候..." :isActive="overlay" />
</div> </div>
</template> </template>
@ -40,7 +40,7 @@ export default {
}); });
if (this.isLoggedIn) { if (this.isLoggedIn) {
swalPopup.fire({ swalPopup.fire({
title: "You have been idle for 5 minutes.", title: "您已闲置 5 分钟。",
html: "To keep your Molly wallet safe from unauthorised access it will automatically logout in <b>10</b> seconds", html: "To keep your Molly wallet safe from unauthorised access it will automatically logout in <b>10</b> seconds",
showConfirmButton: false, showConfirmButton: false,
showCancelButton: false, showCancelButton: false,
@ -187,7 +187,7 @@ export default {
// Transactions.vue sockets // Transactions.vue sockets
window.wails.Events.On("update_tx_history", txHistoryFull => { window.wails.Events.On("update_tx_history", txHistoryFull => {
if (Object.entries(txHistoryFull).length != 0) { if (Object.entries(txHistoryFull).length !== 0) {
this.$store.commit({type: 'transaction/updateFullTxHistory', txHistoryFull}); this.$store.commit({type: 'transaction/updateFullTxHistory', txHistoryFull});
} }
}); });

7
gui_wails01_learn/frontend/src/pages/Login.vue

@ -7,7 +7,7 @@
<div class="col mx-auto login-box"> <div class="col mx-auto login-box">
<div class="input-box"> <div class="input-box">
<div> <div>
<label class="control-label">Select your private key (key.p12)</label> <label class="control-label">选择您的私钥 key.p12</label>
<file-selector <file-selector
v-model="keystorePath" v-model="keystorePath"
:placeholder="keystorePath" :placeholder="keystorePath"
@ -43,13 +43,13 @@
<div class="row"> <div class="row">
<div class="col"> <div class="col">
<p-button type="primary" block @click.native="login()"> <p-button type="primary" block @click.native="login()">
<span style="display: block;"> LOGIN</span> <span style="display: block;"> 登录</span>
</p-button> </p-button>
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="col"> <div class="col">
<p class="text-right">Don't have a wallet yet? Create one <a href="javascript:void(0)" @click="newWallet()">here!</a></p> <p class="text-right">还没有钱包吗 <a href="javascript:void(0)" @click="newWallet()">创建一个</a></p>
</div> </div>
</div> </div>
</div> </div>
@ -67,6 +67,7 @@
export default { export default {
name: "login-screen", name: "login-screen",
data: () => ({ data: () => ({
form: {},
keystorePassword: "", keystorePassword: "",
KeyPassword: "", KeyPassword: "",
overlay: false overlay: false

5
gui_wails01_learn/main.go

@ -19,7 +19,10 @@ func init() {
func main() { func main() {
if runtime.GOOS != "windows" { if runtime.GOOS != "windows" {
os.Setenv("PATH", "/usr/bin:/sbin") // This is neccessary when interacting with the CLI on RedHat and other linux distros err := os.Setenv("PATH", "/usr/bin:/sbin")
if err != nil {
return
} // This is neccessary when interacting with the CLI on RedHat and other linux distros
} }
js := mewn.String("./frontend/dist/app.js") js := mewn.String("./frontend/dist/app.js")

Loading…
Cancel
Save