Skip to content

Commit

Permalink
Client: Better status display for activities (fixes #110)
Browse files Browse the repository at this point in the history
  • Loading branch information
indyjo committed Aug 10, 2015
1 parent a35c4b2 commit a15a38b
Show file tree
Hide file tree
Showing 7 changed files with 186 additions and 135 deletions.
277 changes: 149 additions & 128 deletions share/bitwrk-client/htroot/js/activity.js
@@ -1,134 +1,155 @@
function setActivities(node, activitiesjson, serverUrl) {
var activities = JSON.parse(activitiesjson);
// while (node.hasChildNodes()) {
// node.removeChild(node.lastChild);
// }
var activities = JSON.parse(activitiesjson);
// while (node.hasChildNodes()) {
// node.removeChild(node.lastChild);
// }

// build a dictionary of existing nodes
var itemNodesByKey = {}
for (var i=0; i<node.childNodes.length; i++) {
var item = node.childNodes[i];
var key = item.Key
if (key) {
itemNodesByKey[key] = item
}
}

// iterate over activity list
for (var i=0; i<activities.length; i++) {
var activity = activities[i];
var key = activity.Key;
var info = activity.Info;

var item = itemNodesByKey[key];
delete itemNodesByKey[key];

var needsCreate = true;
var needsFill = true;
if (item) {
// Item existed already. Check for equality.
needsCreate = false;
var info2 = item.Info;
if (info.Accepted !== info2.Accepted
|| info.Rejected !== info2.Rejected
|| info.Alive !== info2.Alive
|| info.Rejected !== info2.Rejected
|| info.Article !== info2.Article
|| info.TxId !== info2.TxId
|| info.BidId !== info2.BidId)
{
// structural change -> refill
while (item.hasChildNodes()) {
item.removeChild(item.lastChild);
}
} else if (info.Amount !== info2.Amount
|| info.Info !== info2.Info)
{
// content change -> just update
needsFill = false;
} else {
// no change at all
continue
}

}

if (needsCreate) {
// Item is new -> append div to parent node
item = document.createElement("div");
node.insertBefore(item, node.firstChild);
// Update key attribute
item.Key = key;
}


if (needsFill) {
// Item is either new or has been emptied -> create children
if (info.Accepted || !info.Alive) {
item.innerHTML =
'<div class="type"></div>' +
'<div class="price"></div>' +
'<div class="article"></div>' +
(info.BidId?' \u00bb <a>Bid</a>':'') +
(info.TxId?' \u00bb <a>Tx</a>':'') +
'<div class="info"></div>';
} else {
item.innerHTML =
'<div class="type"></div>' +
'<button class="closebtn btn btn-primary btn-xs">Permit</button>' +
'<div class="article"></div>' +
'<div class="info"></div>';
}
item.setAttribute("class", info.Alive?"activity":"activity history");
// Update info attribute
item.Info = info;
}

var childIdx = 0;
item.childNodes[childIdx++].textContent = info.Type;
if (info.Accepted || info.Rejected) {
item.childNodes[childIdx++].textContent = info.Amount;
item.childNodes[childIdx++].textContent = info.Article;
if (info.BidId) {
childIdx++; // Skip text
var url= serverUrl + "/bid/" + info.BidId
item.childNodes[childIdx++].setAttribute("onclick", "showIframeDialog('" + url + "')");
}
if (info.TxId) {
childIdx++; // Skip text
var url= serverUrl + "/tx/" + info.TxId
item.childNodes[childIdx++].setAttribute("onclick", "showIframeDialog('" + url + "')");
}
} else {
item.childNodes[childIdx++].onclick = function(info) {
return function() {
showMandateDialog(info.Type, info.Article);
};
}(info);
item.childNodes[childIdx++].textContent = info.Article;
}
item.childNodes[childIdx++].textContent = info.Info;
}

// delete removed nodes
for (var key in itemNodesByKey) {
if (itemNodesByKey.hasOwnProperty(key)) {
node.removeChild(itemNodesByKey[key]);
}
}
// build a dictionary of existing nodes
var itemNodesByKey = {}
for (var i = 0; i < node.childNodes.length; i++) {
var item = node.childNodes[i];
var key = item.Key
if (key) {
itemNodesByKey[key] = item
}
}

// iterate over activity list
for (var i = 0; i < activities.length; i++) {
var activity = activities[i];
var key = activity.Key;
var info = activity.Info;

var item = itemNodesByKey[key];
delete itemNodesByKey[key];

var needsCreate = true;
var needsFill = true;
if (item) {
// Item existed already. Check for equality.
needsCreate = false;
var info2 = item.Info;
if (info.Accepted !== info2.Accepted
|| info.Rejected !== info2.Rejected
|| info.Alive !== info2.Alive
|| info.Rejected !== info2.Rejected
|| info.Article !== info2.Article
|| info.TxId !== info2.TxId || info.BidId !== info2.BidId
|| info.Phase !== info2.Phase) {
// structural change -> refill
while (item.hasChildNodes()) {
item.removeChild(item.lastChild);
}
} else if (info.Amount !== info2.Amount || info.Info !== info2.Info
|| info.Phase !== info2.Phase
|| info.BytesTotal !== info2.BytesTotal
|| info.BytesToTransfer !== info2.BytesToTransfer
|| info.BytesTransferred !== info2.BytesTransferred) {
// content change -> just update
needsFill = false;
} else {
// no change at all
continue
}

}

if (needsCreate) {
// Item is new -> append div to parent node
item = document.createElement("div");
node.insertBefore(item, node.firstChild);
// Update key attribute
item.Key = key;
}

if (needsFill) {
// Item is either new or has been emptied -> create children
if (info.Accepted || !info.Alive) {
item.innerHTML = '<div class="type"></div>'
+ '<div class="price"></div>'
+ '<div class="article"></div>'
+ (info.BidId ? ' \u00bb <a>Bid</a>' : '')
+ (info.TxId ? ' \u00bb <a>Tx</a>' : '')
+ '_phase_dummy_' + '<div class="info"></div>';
} else {
item.innerHTML = '<div class="type"></div>'
+ '<button class="closebtn btn btn-primary btn-xs">Permit</button>'
+ '<div class="article"></div>' + '_phase_dummy_'
+ '<div class="info"></div>';
}
item.setAttribute("class", info.Alive ? "activity"
: "activity history");
// Update info attribute
item.Info = info;
}

var childIdx = 0;
item.childNodes[childIdx++].textContent = '#' + key + ': ' + info.Type;
if (info.Accepted || info.Rejected) {
item.childNodes[childIdx++].textContent = info.Amount;
item.childNodes[childIdx++].textContent = info.Article;
if (info.BidId) {
childIdx++; // Skip text
var url = serverUrl + "/bid/" + info.BidId
item.childNodes[childIdx++].setAttribute("onclick",
"showIframeDialog('" + url + "')");
}
if (info.TxId) {
childIdx++; // Skip text
var url = serverUrl + "/tx/" + info.TxId
item.childNodes[childIdx++].setAttribute("onclick",
"showIframeDialog('" + url + "')");
}
} else {
item.childNodes[childIdx++].onclick = function(info) {
return function() {
showMandateDialog(info.Type, info.Article);
};
}(info);
item.childNodes[childIdx++].textContent = info.Article;
}

var phaseHtml = "";
if (info.Phase == "TRANSMITTING") {
if (info.BytesTransferred > 0) {
phaseHtml = phaseHtml
+ (info.BytesTransferred / 1024).toFixed(2) + 'k ';
}
if (info.BytesToTransfer > 0) {
phaseHtml = phaseHtml + 'of '
+ (info.BytesToTransfer / 1024).toFixed(2) + 'k ';
}
} else {
if (info.BytesTotal > 0) {
phaseHtml = phaseHtml + (info.BytesTotal / 1024).toFixed(2)
+ 'k of work data';
}
}
phaseHtml = ((info.Phase !== '') ? ' \u00bb ' + info.Phase + ' ' : ' ')
+ ((phaseHtml === '') ? '' : '(' + phaseHtml + ')');
item.childNodes[childIdx++].data = phaseHtml;

item.childNodes[childIdx++].textContent = info.Info === undefined ? ""
: info.Info;
}

// delete removed nodes
for ( var key in itemNodesByKey) {
if (itemNodesByKey.hasOwnProperty(key)) {
node.removeChild(itemNodesByKey[key]);
}
}
}

function updateActivities(serverUrl) {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState === 4 && xhr.status == 200 ){
setActivities(
document.getElementById("activities"),
xhr.responseText,
serverUrl);
}
};
xhr.open("GET", "/activities");
xhr.setRequestHeader("Accept", "application/json");
xhr.send();
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState === 4 && xhr.status == 200) {
setActivities(document.getElementById("activities"),
xhr.responseText, serverUrl);
}
};
xhr.open("GET", "/activities");
xhr.setRequestHeader("Accept", "application/json");
xhr.send();
}
6 changes: 6 additions & 0 deletions src/github.com/indyjo/bitwrk-client/activity.go
Expand Up @@ -63,6 +63,12 @@ type ActivityState struct {
Amount money.Money
BidId, TxId string
Info string
Phase string // The phase the activity's active object is in

// Information about a transmission in progress
BytesTotal uint64
BytesToTransfer uint64
BytesTransferred uint64
}

type ActivityManager struct {
Expand Down
8 changes: 7 additions & 1 deletion src/github.com/indyjo/bitwrk-client/buy.go
Expand Up @@ -345,13 +345,19 @@ func (a *BuyActivity) sendMissingChunksAndReturnResult(log bitwrk.Logger, client
defer pipeIn.Close()
mwriter := multipart.NewWriter(pipeOut)

// Communicate status back
progressCallback := func(bytesToTransfer, bytesTransferred uint64) {
a.bytesToTransfer = bytesToTransfer
a.bytesTransferred = bytesTransferred
}

// Write work chunks into pipe for HTTP request
go func() {
defer pipeOut.Close()
if part, err := mwriter.CreateFormFile("chunkdata", "chunkdata.bin"); err != nil {
pipeOut.CloseWithError(err)
return
} else if err := cafs.WriteRequestedChunks(a.workFile, wishList, part); err != nil {
} else if err := cafs.WriteRequestedChunks(a.workFile, wishList, part, progressCallback); err != nil {
pipeOut.CloseWithError(err)
return
}
Expand Down
Expand Up @@ -258,6 +258,7 @@ func serveInternal(workerManager *client.WorkerManager, exit chan<- error) {
mux.HandleFunc("/id", handleId)
mux.HandleFunc("/version", handleVersion)
mux.HandleFunc("/cafsdebug", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html")
client.GetActivityManager().GetStorage().DumpStatistics(cafs.NewWriterPrinter(w))
})
mux.HandleFunc("/stackdump", func(w http.ResponseWriter, r *http.Request) {
Expand Down
25 changes: 21 additions & 4 deletions src/github.com/indyjo/bitwrk-client/trade.go
Expand Up @@ -53,6 +53,9 @@ type Trade struct {
buyerSecret *bitwrk.Thash
workFile cafs.File

bytesToTransfer uint64
bytesTransferred uint64

encResultFile cafs.File
encResultKey *bitwrk.Tkey
encResultHashSig string
Expand Down Expand Up @@ -348,17 +351,21 @@ func (t *Trade) GetState() *ActivityState {
info := ""
if t.lastError != nil {
info = t.lastError.Error()
} else if t.tx != nil {
info = t.tx.Phase.String()
}

phase := ""
if t.tx != nil {
phase = t.tx.Phase.String()
} else if t.bid != nil {
info = t.bid.State.String()
phase = t.bid.State.String()
}

price := t.price
if t.tx != nil {
price = t.tx.Price
}

return &ActivityState{
result := &ActivityState{
Type: t.bidType.String(),
Article: t.article,
Alive: t.alive,
Expand All @@ -368,7 +375,17 @@ func (t *Trade) GetState() *ActivityState {
BidId: t.bidId,
TxId: t.txId,
Info: info,
Phase: phase,

BytesToTransfer: t.bytesToTransfer,
BytesTransferred: t.bytesTransferred,
}

if t.workFile != nil {
result.BytesTotal = uint64(t.workFile.Size())
}

return result
}

func (t *Trade) Permit(identity *bitcoin.KeyPair, price money.Money) bool {
Expand Down
2 changes: 1 addition & 1 deletion src/github.com/indyjo/bitwrk-common
Submodule bitwrk-common updated 1 files
+1 −1 money/money.go
2 changes: 1 addition & 1 deletion src/github.com/indyjo/cafs
Submodule cafs updated 2 files
+23 −3 ramstorage.go
+60 −23 remotesync.go

0 comments on commit a15a38b

Please sign in to comment.