diff --git a/dfssc/sign.go b/dfssc/sign.go
index f5df004df1fb04cfb81d35fe5030318fb19704da..aff20e51ad683fea43f5c848dd34824e02f64cba 100644
--- a/dfssc/sign.go
+++ b/dfssc/sign.go
@@ -10,6 +10,8 @@ import (
func signContract(args []string) {
filename := args[0]
fmt.Println("You are going to sign the following contract:")
+ showContract(args)
+
contract := getContract(filename)
if contract == nil {
os.Exit(1)
@@ -24,6 +26,8 @@ func signContract(args []string) {
fmt.Fprintln(os.Stderr, err)
os.Exit(2)
}
+
+ manager.OnSignerStatusUpdate = signFeedbackFn
err = manager.ConnectToPeers()
if err != nil {
fmt.Fprintln(os.Stderr, err)
@@ -49,6 +53,7 @@ func signContract(args []string) {
fmt.Println("Everybody is ready, starting the signature", signatureUUID)
// Signature
+ manager.OnProgressUpdate = signProgressFn
err = manager.Sign()
if err != nil {
fmt.Fprintln(os.Stderr, err)
@@ -64,3 +69,13 @@ func signContract(args []string) {
fmt.Println("Signature complete! See .proof file for evidences.")
}
+
+func signFeedbackFn(mail string, status sign.SignerStatus, data string) {
+ if status == sign.StatusConnecting {
+ fmt.Println("- Trying to connect with", mail, "/", data)
+ } else if status == sign.StatusConnected {
+ fmt.Println(" Successfully connected!", "[", data, "]")
+ }
+}
+
+func signProgressFn(current int, max int) {}
diff --git a/dfssc/sign/protocol.go b/dfssc/sign/protocol.go
index d0f944c7c40630f232ca88d2766f88a36b0877ad..2165cc737e3b6719b3e8c97095a4d96b14e249d9 100644
--- a/dfssc/sign/protocol.go
+++ b/dfssc/sign/protocol.go
@@ -29,12 +29,15 @@ func (m *SignatureManager) Sign() error {
// Cooldown delay, let other clients wake-up their channels
time.Sleep(time.Second)
+ seqLen := len(m.sequence)
+
// Promess rounds
// Follow the sequence until there is no next occurence of me
for m.currentIndex >= 0 {
+ m.OnProgressUpdate(m.currentIndex, seqLen+1)
dAPI.DLog("starting round at index [" + fmt.Sprintf("%d", m.currentIndex) + "] with nextIndex=" + fmt.Sprintf("%d", nextIndex))
- // Set of the promise we are waiting for
+ // Set of promises we are waiting for
var pendingSet []uint32
pendingSet, err = common.GetPendingSet(m.sequence, myID, m.currentIndex)
if err != nil {
@@ -58,6 +61,7 @@ func (m *SignatureManager) Sign() error {
}
}
+ m.OnProgressUpdate(seqLen, seqLen+1)
dAPI.DLog("entering signature round")
// Signature round
err = m.ExchangeAllSignatures()
@@ -65,6 +69,7 @@ func (m *SignatureManager) Sign() error {
return err
}
dAPI.DLog("exiting signature round")
+ m.OnProgressUpdate(seqLen+1, seqLen+1)
// Network's job is done, cleaning time
// Shutdown and platform client and TODO peer server & connections
@@ -125,7 +130,7 @@ func (m *SignatureManager) promiseRound(pendingSet, sendSet []uint32, myID uint3
// Verifying we sent all the due promises
for range sendSet {
- _ = <-c
+ <-c
}
}
diff --git a/dfssc/sign/starter.go b/dfssc/sign/starter.go
index 42978b55c840beefbb4a5c864e8a69baf7bbcbb1..23a86ec203981010bbba76dee363ed9d90111490 100644
--- a/dfssc/sign/starter.go
+++ b/dfssc/sign/starter.go
@@ -42,6 +42,10 @@ type SignatureManager struct {
keyHash [][]byte
mail string
archives *Archives
+
+ // Callbacks
+ OnSignerStatusUpdate func(mail string, status SignerStatus, data string)
+ OnProgressUpdate func(current int, end int)
}
// Archives stores the received and sent messages, as evidence if needed
@@ -139,10 +143,11 @@ func (m *SignatureManager) addPeer(user *pAPI.User) (ready bool, err error) {
}
addrPort := user.Ip + ":" + strconv.Itoa(int(user.Port))
- fmt.Println("- Trying to connect with", user.Email, "/", addrPort)
+ m.OnSignerStatusUpdate(user.Email, StatusConnecting, addrPort)
conn, err := net.Connect(addrPort, m.auth.Cert, m.auth.Key, m.auth.CA)
if err != nil {
+ m.OnSignerStatusUpdate(user.Email, StatusError, err.Error())
return false, err
}
@@ -158,12 +163,13 @@ func (m *SignatureManager) addPeer(user *pAPI.User) (ready bool, err error) {
defer cancel()
msg, err := client.Discover(ctx, &cAPI.Hello{Version: dfss.Version})
if err != nil {
+ m.OnSignerStatusUpdate(user.Email, StatusError, err.Error())
return false, err
}
// Printing answer: application version
// TODO check certificate
- fmt.Println(" Successfully connected!", "[", msg.Version, "]")
+ m.OnSignerStatusUpdate(user.Email, StatusConnected, msg.Version)
// Check if we have any other peer to connect to
if lastConnection == nil {
diff --git a/dfssc/sign/status.go b/dfssc/sign/status.go
new file mode 100644
index 0000000000000000000000000000000000000000..4d1d83544f38f4b18e14b40ba13a97c7778d9635
--- /dev/null
+++ b/dfssc/sign/status.go
@@ -0,0 +1,12 @@
+package sign
+
+// SignerStatus represents the current state of a signer.
+type SignerStatus int
+
+// These constants represent the different states of a signer.
+const (
+ StatusWaiting SignerStatus = iota
+ StatusConnecting
+ StatusConnected
+ StatusError
+)
diff --git a/gui/authform/authform.go b/gui/authform/authform.go
index 2d15aa4f5626e940a30ed7b49f14c092e94c806c..8a0445a41d8e411c88bb938b0e52e0036f7eeec4 100644
--- a/gui/authform/authform.go
+++ b/gui/authform/authform.go
@@ -41,3 +41,9 @@ func NewWidget(conf *config.Config, onAuth func()) *Widget {
return &Widget{QWidget: form}
}
+
+func (w *Widget) Q() *ui.QWidget {
+ return w.QWidget
+}
+
+func (w *Widget) Tick() {}
diff --git a/gui/contractform/contractform.go b/gui/contractform/contractform.go
index 7f80b1290525fed83e1c0321c12c0baf9fed3265..9cd9ec371d5c9d14929e6ad99494104c2f9f128d 100644
--- a/gui/contractform/contractform.go
+++ b/gui/contractform/contractform.go
@@ -86,3 +86,9 @@ func (w *Widget) SignersList() (list []string) {
return
}
+
+func (w *Widget) Q() *ui.QWidget {
+ return w.QWidget
+}
+
+func (w *Widget) Tick() {}
diff --git a/gui/main.go b/gui/main.go
index 30348eaa60fc0707409badf65265884f35970b0c..24fe004d62a6dda74aeaefc46549182a0d0d7368 100644
--- a/gui/main.go
+++ b/gui/main.go
@@ -5,41 +5,100 @@ import (
"dfss/gui/authform"
"dfss/gui/config"
"dfss/gui/contractform"
+ "dfss/gui/signform"
"dfss/gui/userform"
"github.com/visualfc/goqt/ui"
)
+type window struct {
+ *ui.QMainWindow
+
+ current widget
+ conf *config.Config
+}
+
+type widget interface {
+ Q() *ui.QWidget
+ Tick()
+}
+
func main() {
// Load configuration
conf := config.Load()
// Start first window
ui.Run(func() {
- window := ui.NewMainWindow()
-
- var newuser *userform.Widget
- var newauth *authform.Widget
- var newcontract *contractform.Widget
-
- newauth = authform.NewWidget(&conf, func() {
- window.SetCentralWidget(newcontract)
- })
-
- newuser = userform.NewWidget(&conf, func(pwd string) {
- window.SetCentralWidget(newauth)
- })
-
- newcontract = contractform.NewWidget(&conf)
+ w := &window{
+ QMainWindow: ui.NewMainWindow(),
+ conf: &conf,
+ }
if conf.Authenticated {
- window.SetCentralWidget(newcontract)
+ w.addActions()
+ w.showNewContractForm()
} else if conf.Registered {
- window.SetCentralWidget(newauth)
+ w.showAuthForm()
} else {
- window.SetCentralWidget(newuser)
+ w.showUserForm()
}
- window.SetWindowTitle("DFSS Client v" + dfss.Version)
- window.Show()
+ timer := ui.NewTimerWithParent(w)
+ timer.OnTimeout(func() {
+ w.current.Tick()
+ })
+ timer.StartWithMsec(1000)
+
+ w.SetWindowTitle("DFSS Client v" + dfss.Version)
+ w.SetWindowIcon(ui.NewIconWithFilename(":/images/digital_signature_pen.png"))
+ w.Show()
})
}
+
+func (w *window) addActions() {
+ openAct := ui.NewActionWithTextParent("&Open", w)
+ openAct.SetShortcuts(ui.QKeySequence_Open)
+ openAct.OnTriggered(func() {
+ w.showSignForm()
+ })
+ w.MenuBar().AddAction(openAct)
+}
+
+func (w *window) setScreen(wi widget) {
+ old := w.CentralWidget()
+ w.SetCentralWidget(wi.Q())
+ w.current = wi
+ if old != nil {
+ old.DeleteLater()
+ }
+}
+
+func (w *window) showUserForm() {
+ w.setScreen(userform.NewWidget(w.conf, func(pwd string) {
+ w.showAuthForm()
+ }))
+}
+
+func (w *window) showAuthForm() {
+ w.setScreen(authform.NewWidget(w.conf, func() {
+ w.showNewContractForm()
+ w.addActions()
+ }))
+}
+
+func (w *window) showNewContractForm() {
+ w.setScreen(contractform.NewWidget(w.conf))
+}
+
+func (w *window) showSignForm() {
+ home := config.GetHomeDir()
+ filter := "Contract file (*.json);;Any (*.*)"
+ filename := ui.QFileDialogGetOpenFileNameWithParentCaptionDirFilterSelectedfilterOptions(w, "Select the contract file", home, filter, &filter, 0)
+ if filename != "" {
+ config.PasswordDialog(func(err error, pwd string) {
+ widget := signform.NewWidget(w.conf, filename, pwd)
+ if widget != nil {
+ w.setScreen(widget)
+ }
+ })
+ }
+}
diff --git a/gui/signform/icons.go b/gui/signform/icons.go
new file mode 100644
index 0000000000000000000000000000000000000000..c9a07cd3d501653cba3267ae34eb6a944aa13d49
--- /dev/null
+++ b/gui/signform/icons.go
@@ -0,0 +1,31 @@
+package signform
+
+import (
+ "dfss/dfssc/sign"
+ "github.com/visualfc/goqt/ui"
+)
+
+var icons map[sign.SignerStatus]*ui.QIcon
+var icons_labels = map[sign.SignerStatus]string{
+ sign.StatusWaiting: "Waiting",
+ sign.StatusConnecting: "Connecting",
+ sign.StatusConnected: "Connected",
+ sign.StatusError: "Error",
+}
+
+var iconsLoaded = false
+
+func loadIcons() {
+ if iconsLoaded {
+ return
+ }
+
+ icons = map[sign.SignerStatus]*ui.QIcon{
+ sign.StatusWaiting: ui.NewIconWithFilename(":/images/time.png"),
+ sign.StatusConnecting: ui.NewIconWithFilename(":/images/time.png"),
+ sign.StatusConnected: ui.NewIconWithFilename(":/images/connected.png"),
+ sign.StatusError: ui.NewIconWithFilename(":/images/error.png"),
+ }
+
+ iconsLoaded = true
+}
diff --git a/gui/signform/signform.go b/gui/signform/signform.go
index 338c06277c956e3716c81a30dc7ae817c0b64b8c..311738e8943f056ff4d93dd85594a6ef03b220e1 100644
--- a/gui/signform/signform.go
+++ b/gui/signform/signform.go
@@ -1,20 +1,155 @@
package signform
import (
+ "encoding/json"
+ "io/ioutil"
+
+ "dfss/dfssc/sign"
+ "dfss/dfssp/contract"
"dfss/gui/config"
"github.com/visualfc/goqt/ui"
)
+type line struct {
+ status sign.SignerStatus
+ info, mail string
+ cellA, cellB, cellC *ui.QTableWidgetItem
+}
+
type Widget struct {
*ui.QWidget
+
+ manager *sign.SignatureManager
+ contract *contract.JSON
+ table *ui.QTableWidget
+ progressBar *ui.QProgressBar
+ feedbackLabel *ui.QLabel
+ lines []line
+ statusMax, statusCurrent int32
+ feedback string
}
-func NewWidget(conf *config.Config, onAuth func()) *Widget {
+func NewWidget(conf *config.Config, filename, pwd string) *Widget {
+ loadIcons()
file := ui.NewFileWithName(":/signform/signform.ui")
loader := ui.NewUiLoader()
form := loader.Load(file)
-
w := &Widget{QWidget: form}
+ w.feedbackLabel = ui.NewLabelFromDriver(w.FindChild("mainLabel"))
+ w.table = ui.NewTableWidgetFromDriver(w.FindChild("signersTable"))
+ w.progressBar = ui.NewProgressBarFromDriver(w.FindChild("progressBar"))
+
+ data, err := ioutil.ReadFile(filename)
+ if err != nil {
+ return nil
+ }
+
+ w.contract = new(contract.JSON)
+ err = json.Unmarshal(data, w.contract)
+ if err != nil {
+ return nil
+ }
+
+ home := config.GetHomeDir()
+ m, err := sign.NewSignatureManager(
+ home+config.CAFile,
+ home+config.CertFile,
+ home+config.KeyFile,
+ conf.Platform,
+ pwd,
+ 9005, // TODO change port
+ w.contract,
+ )
+ if err != nil {
+ // TODO
+ }
+
+ w.manager = m
+ w.manager.OnSignerStatusUpdate = w.signerUpdated
+ w.manager.OnProgressUpdate = func(current int, max int) {
+ w.statusCurrent = int32(current)
+ w.statusMax = int32(max)
+ }
+
+ w.initLines()
+ w.signerUpdated(conf.Email, sign.StatusConnected, "It's you!")
+ go func() {
+ err = w.execute()
+ if err != nil {
+ w.feedback = err.Error()
+ } else {
+ w.feedback = "Contract signed successfully!"
+ }
+ }()
+
return w
}
+
+func (w *Widget) execute() error {
+ w.feedback = "Connecting to peers..."
+ err := w.manager.ConnectToPeers()
+ if err != nil {
+ return err
+ }
+
+ w.feedback = "Waiting for peers..."
+ _, err = w.manager.SendReadySign()
+ if err != nil {
+ return err
+ }
+
+ w.feedback = "Signature in progress..."
+ err = w.manager.Sign()
+ if err != nil {
+ return err
+ }
+
+ w.feedback = "Storing file..."
+ return w.manager.PersistSignaturesToFile() // TODO choose destination
+}
+
+func (w *Widget) signerUpdated(mail string, status sign.SignerStatus, data string) {
+ for i, s := range w.contract.Signers {
+ if s.Email == mail {
+ w.lines[i].status = status
+ w.lines[i].info = data
+ break
+ }
+ }
+}
+
+// Tick updates the whole screen, as we cannot directly update the screen on each callback.
+// It is called by the screen coordinator via a timer.
+func (w *Widget) Tick() {
+ w.feedbackLabel.SetText(w.feedback)
+ w.progressBar.SetMaximum(w.statusMax)
+ w.progressBar.SetValue(w.statusCurrent)
+ for _, l := range w.lines {
+ l.cellA.SetIcon(icons[l.status])
+ l.cellA.SetText(icons_labels[l.status])
+ l.cellB.SetText(l.info)
+ }
+}
+
+func (w *Widget) initLines() {
+ w.table.SetRowCount(int32(len(w.contract.Signers)))
+ for i, s := range w.contract.Signers {
+ status := ui.NewTableWidgetItemWithIconTextType(icons[sign.StatusWaiting], icons_labels[sign.StatusWaiting], int32(ui.QTableWidgetItem_UserType))
+ w.table.SetItem(int32(i), 0, status)
+
+ info := ui.NewTableWidgetItemWithTextType("", int32(ui.QTableWidgetItem_UserType))
+ w.table.SetItem(int32(i), 1, info)
+
+ mail := ui.NewTableWidgetItemWithTextType(s.Email, int32(ui.QTableWidgetItem_UserType))
+ w.table.SetItem(int32(i), 2, mail)
+
+ // We must store items, otherwise GC will remove it and cause the application to crash.
+ // Trust me, hard to debug...
+ w.lines = append(w.lines, line{sign.StatusWaiting, "", s.Email, status, info, mail})
+ }
+}
+
+func (w *Widget) Q() *ui.QWidget {
+ return w.QWidget
+}
diff --git a/gui/signform/signform.ui b/gui/signform/signform.ui
index f72664fd5c6615c5067f0e2e45595e49b1d08a7c..8c4d41b6d5da97ad7b35da1d1f30d205ad4e862b 100644
--- a/gui/signform/signform.ui
+++ b/gui/signform/signform.ui
@@ -50,8 +50,20 @@
-
+
+
+ 0
+ 0
+
+
+
+
+ 400
+ 0
+
+
- <html><head/><body><p><span style=" font-size:18pt;">Preparing signature...</span></p></body></html>
+ Preparing signature...
@@ -82,6 +94,9 @@
-
+
+ QAbstractItemView::NoEditTriggers
+
true
@@ -101,7 +116,7 @@
200
- 200
+ 50
true
@@ -122,7 +137,7 @@
- Ping
+ Info
diff --git a/gui/userform/userform.go b/gui/userform/userform.go
index fc998a06d5f25219cea4da3ad0508fead7a92d5e..8f424e77bcd974c4c7f1428118cfecbfa703bc0d 100644
--- a/gui/userform/userform.go
+++ b/gui/userform/userform.go
@@ -71,3 +71,9 @@ func copyCA(from string, to string) error {
return ioutil.WriteFile(to, file, 0600)
}
+
+func (w *Widget) Q() *ui.QWidget {
+ return w.QWidget
+}
+
+func (w *Widget) Tick() {}