Skip to content

Commit 3c979ed

Browse files
committed
feat(save): emit save events. Print progress bars on save
add save event publication to dsfs, move all progress bar output up into cmd package and replace progress bar package with "mpb" with explicit support for displaying multiple progress bars at once. To pull this off lib.Instance gets a new .Bus() accessor method. This setup brings progress bar output closer to the way desktop / API driven events display progress.
1 parent 63c3d44 commit 3c979ed

File tree

21 files changed

+337
-121
lines changed

21 files changed

+337
-121
lines changed

base/archive/archive_test.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"github.com/qri-io/qfs"
1313
"github.com/qri-io/qri/base/dsfs"
1414
testPeers "github.com/qri-io/qri/config/test"
15+
"github.com/qri-io/qri/event"
1516
)
1617

1718
func TestGenerateFilename(t *testing.T) {
@@ -132,7 +133,7 @@ func testFS() (qfs.Filesystem, map[string]string, error) {
132133
ds.SetBodyFile(dataf)
133134

134135
fs := qfs.NewMemFS()
135-
dskey, err := dsfs.WriteDataset(ctx, nil, fs, ds, pk, dsfs.SaveSwitches{})
136+
dskey, err := dsfs.WriteDataset(ctx, nil, fs, event.NilBus, ds, pk, dsfs.SaveSwitches{})
136137
if err != nil {
137138
return fs, ns, err
138139
}
@@ -179,7 +180,7 @@ func testFSWithVizAndTransform() (qfs.Filesystem, map[string]string, error) {
179180
privKey := testPeers.GetTestPeerInfo(10).PrivKey
180181

181182
var dsLk sync.Mutex
182-
dskey, err := dsfs.WriteDataset(ctx, &dsLk, st, ds, privKey, dsfs.SaveSwitches{Pin: true})
183+
dskey, err := dsfs.WriteDataset(ctx, &dsLk, st, event.NilBus, ds, privKey, dsfs.SaveSwitches{Pin: true})
183184
if err != nil {
184185
return st, ns, err
185186
}

base/dsfs/commit.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
"github.com/qri-io/qfs"
1717
"github.com/qri-io/qri/base/friendly"
1818
"github.com/qri-io/qri/base/toqtype"
19+
"github.com/qri-io/qri/event"
1920
)
2021

2122
// Timestamp is an function for getting commit timestamps
@@ -62,13 +63,19 @@ func loadCommit(ctx context.Context, fs qfs.Filesystem, path string) (st *datase
6263
return dataset.UnmarshalCommit(data)
6364
}
6465

65-
func commitFileAddFunc(privKey crypto.PrivKey) addWriteFileFunc {
66+
func commitFileAddFunc(privKey crypto.PrivKey, pub event.Publisher) addWriteFileFunc {
6667
return func(ds *dataset.Dataset, wfs *writeFiles) error {
6768
if ds.Commit == nil {
6869
return nil
6970
}
7071

7172
hook := func(ctx context.Context, f qfs.File, added map[string]string) (io.Reader, error) {
73+
go event.PublishLogError(ctx, pub, log, event.ETDatasetSaveProgress, event.DsSaveEvent{
74+
Username: ds.Peername,
75+
Name: ds.Name,
76+
Message: "finalizing",
77+
Completion: 0.9,
78+
})
7279

7380
if cff, ok := wfs.body.(*computeFieldsFile); ok {
7481
updateScriptPaths(ds, added)

base/dsfs/compute_fields.go

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"github.com/qri-io/dataset/dsstats"
1515
"github.com/qri-io/jsonschema"
1616
"github.com/qri-io/qfs"
17+
"github.com/qri-io/qri/event"
1718
)
1819

1920
type computeFieldsFile struct {
@@ -48,7 +49,15 @@ var (
4849
_ statsComponentFile = (*computeFieldsFile)(nil)
4950
)
5051

51-
func newComputeFieldsFile(ctx context.Context, dsLk *sync.Mutex, fs qfs.Filesystem, pk crypto.PrivKey, ds, prev *dataset.Dataset, sw SaveSwitches) (qfs.File, error) {
52+
func newComputeFieldsFile(
53+
ctx context.Context,
54+
dsLk *sync.Mutex,
55+
fs qfs.Filesystem,
56+
pub event.Publisher,
57+
pk crypto.PrivKey,
58+
ds *dataset.Dataset,
59+
prev *dataset.Dataset,
60+
sw SaveSwitches) (qfs.File, error) {
5261
var (
5362
bf = ds.BodyFile()
5463
bfPrev qfs.File
@@ -84,7 +93,7 @@ func newComputeFieldsFile(ctx context.Context, dsLk *sync.Mutex, fs qfs.Filesyst
8493
done: make(chan error),
8594
}
8695

87-
go cff.handleRows(ctx)
96+
go cff.handleRows(ctx, pub)
8897

8998
return cff, nil
9099
}
@@ -151,7 +160,7 @@ func (cff *computeFieldsFile) StatsComponent() (*dataset.Stats, error) {
151160
}, nil
152161
}
153162

154-
func (cff *computeFieldsFile) handleRows(ctx context.Context) {
163+
func (cff *computeFieldsFile) handleRows(ctx context.Context, pub event.Publisher) {
155164
var (
156165
batchBuf *dsio.EntryBuffer
157166
st = cff.ds.Structure
@@ -201,6 +210,16 @@ func (cff *computeFieldsFile) handleRows(ctx context.Context) {
201210
return
202211
}
203212

213+
// publish here so we know that if the user sees the "processing body file"
214+
// message, we know that a compute-fields-file has made it all the way through
215+
// setup
216+
go event.PublishLogError(ctx, pub, log, event.ETDatasetSaveProgress, event.DsSaveEvent{
217+
Username: cff.ds.Peername,
218+
Name: cff.ds.Name,
219+
Message: "processing body file",
220+
Completion: 0.1,
221+
})
222+
204223
go func() {
205224
err = dsio.EachEntry(r, func(i int, ent dsio.Entry, err error) error {
206225
if err != nil {

base/dsfs/dataset.go

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"github.com/qri-io/dataset"
1212
"github.com/qri-io/dataset/validate"
1313
"github.com/qri-io/qfs"
14+
"github.com/qri-io/qri/event"
1415
)
1516

1617
// number of entries to per batch when processing body data in WriteDataset
@@ -142,6 +143,7 @@ func CreateDataset(
142143
ctx context.Context,
143144
source qfs.Filesystem,
144145
destination qfs.Filesystem,
146+
pub event.Publisher,
145147
ds *dataset.Dataset,
146148
prev *dataset.Dataset,
147149
pk crypto.PrivKey,
@@ -192,17 +194,34 @@ func CreateDataset(
192194
}
193195

194196
// lock for editing dataset pointer
195-
var dsLk = &sync.Mutex{}
197+
var (
198+
dsLk = &sync.Mutex{}
199+
peername = ds.Peername
200+
name = ds.Name
201+
)
196202

197-
bodyFile, err := newComputeFieldsFile(ctx, dsLk, source, pk, ds, prev, sw)
203+
bodyFile, err := newComputeFieldsFile(ctx, dsLk, source, pub, pk, ds, prev, sw)
198204
if err != nil {
199205
return "", err
200206
}
201207
ds.SetBodyFile(bodyFile)
202208

203-
path, err := WriteDataset(ctx, dsLk, destination, ds, pk, sw)
209+
go event.PublishLogError(ctx, pub, log, event.ETDatasetSaveStarted, event.DsSaveEvent{
210+
Username: peername,
211+
Name: name,
212+
Message: "save started",
213+
Completion: 0,
214+
})
215+
216+
path, err := WriteDataset(ctx, dsLk, destination, pub, ds, pk, sw)
204217
if err != nil {
205218
log.Debug(err.Error())
219+
event.PublishLogError(ctx, pub, log, event.ETDatasetSaveCompleted, event.DsSaveEvent{
220+
Username: peername,
221+
Name: name,
222+
Error: err,
223+
Completion: 1.0,
224+
})
206225
return "", err
207226
}
208227

@@ -211,16 +230,30 @@ func CreateDataset(
211230
// the caller doesn't use the ds arg afterward
212231
// might make sense to have a wrapper function that writes and loads on success
213232
if err := DerefDataset(ctx, destination, ds); err != nil {
233+
event.PublishLogError(ctx, pub, log, event.ETDatasetSaveCompleted, event.DsSaveEvent{
234+
Username: peername,
235+
Name: name,
236+
Error: err,
237+
Completion: 1.0,
238+
})
214239
return path, err
215240
}
216-
return path, nil
241+
242+
return path, pub.Publish(ctx, event.ETDatasetSaveCompleted, event.DsSaveEvent{
243+
Username: peername,
244+
Name: name,
245+
Message: "dataset saved",
246+
Path: path,
247+
Completion: 1.0,
248+
})
217249
}
218250

219251
// WriteDataset persists a datasets to a destination filesystem
220252
func WriteDataset(
221253
ctx context.Context,
222254
dsLk *sync.Mutex,
223255
destination qfs.Filesystem,
256+
pub event.Publisher,
224257
ds *dataset.Dataset,
225258
pk crypto.PrivKey,
226259
sw SaveSwitches,
@@ -240,7 +273,7 @@ func WriteDataset(
240273
addStatsFile,
241274
addReadmeFile,
242275
vizFilesAddFunc(destination, sw),
243-
commitFileAddFunc(pk),
276+
commitFileAddFunc(pk, pub),
244277
addDatasetFile,
245278
}
246279

base/dsfs/dataset_test.go

Lines changed: 54 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"testing"
1313
"time"
1414

15+
"github.com/google/go-cmp/cmp"
1516
"github.com/qri-io/dataset"
1617
"github.com/qri-io/dataset/dsio"
1718
"github.com/qri-io/dataset/dstest"
@@ -20,6 +21,7 @@ import (
2021
"github.com/qri-io/qfs"
2122
"github.com/qri-io/qri/base/toqtype"
2223
testPeers "github.com/qri-io/qri/config/test"
24+
"github.com/qri-io/qri/event"
2325
)
2426

2527
func TestLoadDataset(t *testing.T) {
@@ -47,7 +49,7 @@ func TestLoadDataset(t *testing.T) {
4749
info := testPeers.GetTestPeerInfo(10)
4850
pk := info.PrivKey
4951

50-
apath, err := WriteDataset(ctx, &sync.Mutex{}, fs, ds, pk, SaveSwitches{})
52+
apath, err := WriteDataset(ctx, &sync.Mutex{}, fs, event.NilBus, ds, pk, SaveSwitches{})
5153
if err != nil {
5254
t.Errorf(err.Error())
5355
return
@@ -156,7 +158,7 @@ func TestCreateDataset(t *testing.T) {
156158
t.Fatalf("creating test case: %s", err)
157159
}
158160

159-
_, err = CreateDataset(ctx, fs, fs, tc.Input, c.prev, privKey, SaveSwitches{ShouldRender: true})
161+
_, err = CreateDataset(ctx, fs, fs, event.NilBus, tc.Input, c.prev, privKey, SaveSwitches{ShouldRender: true})
160162
if err == nil {
161163
t.Fatalf("CreateDataset expected error. got nil")
162164
}
@@ -189,7 +191,7 @@ func TestCreateDataset(t *testing.T) {
189191
t.Fatalf("creating test case: %s", err)
190192
}
191193

192-
path, err := CreateDataset(ctx, fs, fs, tc.Input, c.prev, privKey, SaveSwitches{ShouldRender: true})
194+
path, err := CreateDataset(ctx, fs, fs, event.NilBus, tc.Input, c.prev, privKey, SaveSwitches{ShouldRender: true})
193195
if err != nil {
194196
t.Fatalf("CreateDataset: %s", err)
195197
}
@@ -218,7 +220,7 @@ func TestCreateDataset(t *testing.T) {
218220
}
219221

220222
t.Run("no_priv_key", func(t *testing.T) {
221-
_, err := CreateDataset(ctx, fs, fs, nil, nil, nil, SaveSwitches{ShouldRender: true})
223+
_, err := CreateDataset(ctx, fs, fs, event.NilBus, nil, nil, nil, SaveSwitches{ShouldRender: true})
222224
if err == nil {
223225
t.Fatal("expected call without prvate key to error")
224226
}
@@ -242,7 +244,7 @@ func TestCreateDataset(t *testing.T) {
242244
t.Errorf("case nil body and previous body files, error reading data file: %s", err.Error())
243245
}
244246
expectedErr := "bodyfile or previous bodyfile needed"
245-
_, err = CreateDataset(ctx, fs, fs, ds, nil, privKey, SaveSwitches{ShouldRender: true})
247+
_, err = CreateDataset(ctx, fs, fs, event.NilBus, ds, nil, privKey, SaveSwitches{ShouldRender: true})
246248
if err.Error() != expectedErr {
247249
t.Errorf("case nil body and previous body files, error mismatch: expected '%s', got '%s'", expectedErr, err.Error())
248250
}
@@ -272,7 +274,7 @@ func TestCreateDataset(t *testing.T) {
272274
}
273275
ds.SetBodyFile(qfs.NewMemfileBytes("body.csv", bodyBytes))
274276

275-
path, err := CreateDataset(ctx, fs, fs, ds, dsPrev, privKey, SaveSwitches{ShouldRender: true})
277+
path, err := CreateDataset(ctx, fs, fs, event.NilBus, ds, dsPrev, privKey, SaveSwitches{ShouldRender: true})
276278
if err != nil && err.Error() != expectedErr {
277279
t.Fatalf("mismatch: expected %q, got %q", expectedErr, err.Error())
278280
} else if err == nil {
@@ -312,7 +314,7 @@ func TestDatasetSaveCustomTimestamp(t *testing.T) {
312314
}
313315
ds.SetBodyFile(qfs.NewMemfileBytes("/body.json", []byte(`[]`)))
314316

315-
path, err := CreateDataset(ctx, fs, fs, ds, nil, privKey, SaveSwitches{})
317+
path, err := CreateDataset(ctx, fs, fs, event.NilBus, ds, nil, privKey, SaveSwitches{})
316318
if err != nil {
317319
t.Fatal(err)
318320
}
@@ -324,6 +326,47 @@ func TestDatasetSaveCustomTimestamp(t *testing.T) {
324326
}
325327
}
326328

329+
func TestDatasetSaveEvents(t *testing.T) {
330+
ctx, cancel := context.WithCancel(context.Background())
331+
defer cancel()
332+
333+
fs := qfs.NewMemFS()
334+
privKey := testPeers.GetTestPeerInfo(10).PrivKey
335+
bus := event.NewBus(ctx)
336+
337+
fired := map[event.Type]int{}
338+
bus.Subscribe(func(ctx context.Context, t event.Type, payload interface{}) error {
339+
fired[t]++
340+
return nil
341+
},
342+
event.ETDatasetSaveStarted,
343+
event.ETDatasetSaveProgress,
344+
event.ETDatasetSaveCompleted,
345+
)
346+
347+
ds := &dataset.Dataset{
348+
Commit: &dataset.Commit{
349+
Timestamp: time.Date(2100, 1, 2, 3, 4, 5, 6, time.Local),
350+
},
351+
Structure: &dataset.Structure{Format: "json", Schema: dataset.BaseSchemaArray},
352+
}
353+
ds.SetBodyFile(qfs.NewMemfileBytes("/body.json", []byte(`[]`)))
354+
355+
if _, err := CreateDataset(ctx, fs, fs, bus, ds, nil, privKey, SaveSwitches{}); err != nil {
356+
t.Fatal(err)
357+
}
358+
359+
expect := map[event.Type]int{
360+
event.ETDatasetSaveStarted: 1,
361+
event.ETDatasetSaveProgress: 2,
362+
event.ETDatasetSaveCompleted: 1,
363+
}
364+
365+
if diff := cmp.Diff(expect, fired); diff != "" {
366+
t.Errorf("fired event count mismatch. (-want +got):%s\n", diff)
367+
}
368+
}
369+
327370
// BaseTabularSchema is the base schema for tabular data
328371
// NOTE: Do not use if possible, prefer github.com/qri-io/dataset/tabular
329372
// TODO(dustmop): Possibly move this to tabular package
@@ -374,7 +417,7 @@ func TestCreateDatasetBodyTooLarge(t *testing.T) {
374417
}
375418
nextDs.SetBodyFile(qfs.NewMemfileBytes(testBodyPath, testBodyBytes))
376419

377-
path, err := CreateDataset(ctx, fs, fs, &nextDs, &prevDs, privKey, SaveSwitches{ShouldRender: true})
420+
path, err := CreateDataset(ctx, fs, fs, event.NilBus, &nextDs, &prevDs, privKey, SaveSwitches{ShouldRender: true})
378421
if err != nil {
379422
t.Fatalf("CreateDataset: %s", err)
380423
}
@@ -403,7 +446,7 @@ func TestWriteDataset(t *testing.T) {
403446
info := testPeers.GetTestPeerInfo(10)
404447
pk := info.PrivKey
405448

406-
if _, err := WriteDataset(ctx, &sync.Mutex{}, fs, &dataset.Dataset{}, pk, SaveSwitches{Pin: true}); err == nil || err.Error() != "cannot save empty dataset" {
449+
if _, err := WriteDataset(ctx, &sync.Mutex{}, fs, event.NilBus, &dataset.Dataset{}, pk, SaveSwitches{Pin: true}); err == nil || err.Error() != "cannot save empty dataset" {
407450
t.Errorf("didn't reject empty dataset: %s", err)
408451
}
409452

@@ -426,7 +469,7 @@ func TestWriteDataset(t *testing.T) {
426469

427470
ds := tc.Input
428471

429-
got, err := WriteDataset(ctx, &sync.Mutex{}, fs, ds, pk, SaveSwitches{Pin: true})
472+
got, err := WriteDataset(ctx, &sync.Mutex{}, fs, event.NilBus, ds, pk, SaveSwitches{Pin: true})
430473
if !(err == nil && c.err == "" || err != nil && err.Error() == c.err) {
431474
t.Errorf("case %d error mismatch. expected: '%s', got: '%s'", i, c.err, err)
432475
continue
@@ -1011,7 +1054,7 @@ func BenchmarkCreateDatasetCSV(b *testing.B) {
10111054
_, dataset := GenerateDataset(b, sampleSize, "csv")
10121055

10131056
b.StartTimer()
1014-
_, err := CreateDataset(ctx, fs, fs, dataset, nil, privKey, SaveSwitches{ShouldRender: true})
1057+
_, err := CreateDataset(ctx, fs, fs, event.NilBus, dataset, nil, privKey, SaveSwitches{ShouldRender: true})
10151058
if err != nil {
10161059
b.Errorf("error creating dataset: %s", err.Error())
10171060
}

base/dsfs/testdata_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"github.com/qri-io/dataset"
1111
"github.com/qri-io/qfs"
1212
testPeers "github.com/qri-io/qri/config/test"
13+
"github.com/qri-io/qri/event"
1314
)
1415

1516
var AirportCodes = &dataset.Dataset{
@@ -178,7 +179,7 @@ func makeFilestore() (map[string]string, qfs.Filesystem, error) {
178179

179180
ds.SetBodyFile(qfs.NewMemfileBytes(fmt.Sprintf("/body.%s", ds.Structure.Format), data))
180181

181-
dskey, err := WriteDataset(ctx, &sync.Mutex{}, fs, ds, pk, SaveSwitches{Pin: true})
182+
dskey, err := WriteDataset(ctx, &sync.Mutex{}, fs, event.NilBus, ds, pk, SaveSwitches{Pin: true})
182183
if err != nil {
183184
return datasets, nil, fmt.Errorf("dataset: %s write error: %s", k, err.Error())
184185
}

0 commit comments

Comments
 (0)