diff --git a/db_badger.go b/db_badger.go index 40e5fcf..bbefafc 100644 --- a/db_badger.go +++ b/db_badger.go @@ -33,7 +33,7 @@ func (ld *badgerLoadedDatabase) Keepalive(ndata *navData) { ld.arena = append(ld.arena, ndata) } -func (ld *badgerLoadedDatabase) RenderForNav(f *TMainForm, ndata *navData) { +func (ld *badgerLoadedDatabase) RenderForNav(f *TMainForm, ndata *navData) error { // Load properties @@ -74,13 +74,13 @@ func (ld *badgerLoadedDatabase) RenderForNav(f *TMainForm, ndata *navData) { return nil }) if err != nil { - vcl.ShowMessage(fmt.Sprintf("Failed to load data: %s", err.Error())) - return + return err } // Valid vcl_stringgrid_columnwidths(f.contentBox) f.contentBox.SetEnabled(true) + return nil } func (n *badgerLoadedDatabase) ApplyChanges(f *TMainForm, ndata *navData) error { diff --git a/db_bolt.go b/db_bolt.go index de09827..7167072 100644 --- a/db_bolt.go +++ b/db_bolt.go @@ -39,7 +39,7 @@ func (ld *boltLoadedDatabase) Keepalive(ndata *navData) { ld.arena = append(ld.arena, ndata) } -func (ld *boltLoadedDatabase) RenderForNav(f *TMainForm, ndata *navData) { +func (ld *boltLoadedDatabase) RenderForNav(f *TMainForm, ndata *navData) error { // Load properties @@ -72,13 +72,13 @@ func (ld *boltLoadedDatabase) RenderForNav(f *TMainForm, ndata *navData) { return nil }) if err != nil { - vcl.ShowMessage(fmt.Sprintf("Failed to load data for bucket %q: %s", bucketDisplayName, err.Error())) - return + return err } // Valid vcl_stringgrid_columnwidths(f.contentBox) f.contentBox.SetEnabled(true) + return nil } func (n *boltLoadedDatabase) ApplyChanges(f *TMainForm, ndata *navData) error { @@ -143,13 +143,14 @@ func (ld *boltLoadedDatabase) NavContext(ndata *navData) (ret []contextAction, e if len(ndata.bucketPath) > 0 { ret = append(ret, contextAction{"Delete bucket", ld.DeleteBucket}) } + return } -func (ld *boltLoadedDatabase) AddChildBucket(ndata *navData) { +func (ld *boltLoadedDatabase) AddChildBucket(ndata *navData) error { bucketName := "" if !vcl.InputQuery(APPNAME, "Enter a name for the new bucket:", &bucketName) { - return // cancel + return nil // cancel } err := ld.db.Update(func(tx *bbolt.Tx) error { @@ -164,11 +165,12 @@ func (ld *boltLoadedDatabase) AddChildBucket(ndata *navData) { return err }) if err != nil { - vcl.ShowMessageFmt("Error adding bucket: %v", err) + return fmt.Errorf("Error adding bucket: %w", err) } + return nil } -func (ld *boltLoadedDatabase) DeleteBucket(ndata *navData) { +func (ld *boltLoadedDatabase) DeleteBucket(ndata *navData) error { err := ld.db.Update(func(tx *bbolt.Tx) error { // Find parent of this bucket. if len(ndata.bucketPath) >= 2 { @@ -181,8 +183,9 @@ func (ld *boltLoadedDatabase) DeleteBucket(ndata *navData) { } }) if err != nil { - vcl.ShowMessageFmt("Error deleting bucket %q: %v", strings.Join(ndata.bucketPath, `/`), err) + return fmt.Errorf("Error deleting bucket %q: %w", strings.Join(ndata.bucketPath, `/`), err) } + return nil } func (ld *boltLoadedDatabase) ExecQuery(query string, resultArea *vcl.TStringGrid) error { diff --git a/db_debconf.go b/db_debconf.go index a0c44d3..09011b1 100644 --- a/db_debconf.go +++ b/db_debconf.go @@ -35,7 +35,7 @@ func (ld *debconfLoadedDatabase) Keepalive(ndata *navData) { ld.arena = append(ld.arena, ndata) } -func (ld *debconfLoadedDatabase) RenderForNav(f *TMainForm, ndata *navData) { +func (ld *debconfLoadedDatabase) RenderForNav(f *TMainForm, ndata *navData) error { // Load properties @@ -67,6 +67,7 @@ func (ld *debconfLoadedDatabase) RenderForNav(f *TMainForm, ndata *navData) { // Valid vcl_stringgrid_columnwidths(f.contentBox) f.contentBox.SetEnabled(true) + return nil } func (n *debconfLoadedDatabase) ApplyChanges(f *TMainForm, ndata *navData) error { diff --git a/db_none.go b/db_none.go index c509e83..f844df1 100644 --- a/db_none.go +++ b/db_none.go @@ -18,8 +18,9 @@ func (n *noLoadedDatabase) RootElement() *vcl.TTreeNode { return nil } -func (n *noLoadedDatabase) RenderForNav(f *TMainForm, ndata *navData) { +func (n *noLoadedDatabase) RenderForNav(f *TMainForm, ndata *navData) error { f.propertiesBox.SetText("Open a database to get started...") + return nil } func (n *noLoadedDatabase) ApplyChanges(f *TMainForm, ndata *navData) error { diff --git a/db_pebble.go b/db_pebble.go index 31f7f34..0bb6a3c 100644 --- a/db_pebble.go +++ b/db_pebble.go @@ -35,7 +35,7 @@ func (ld *pebbleLoadedDatabase) Keepalive(ndata *navData) { ld.arena = append(ld.arena, ndata) } -func (ld *pebbleLoadedDatabase) RenderForNav(f *TMainForm, ndata *navData) { +func (ld *pebbleLoadedDatabase) RenderForNav(f *TMainForm, ndata *navData) error { ctx := context.Background() @@ -59,8 +59,7 @@ func (ld *pebbleLoadedDatabase) RenderForNav(f *TMainForm, ndata *navData) { k := itr.Key() v, err := itr.ValueAndErr() if err != nil { - vcl.ShowMessage(fmt.Sprintf("Failed to load data for key %q: %s", formatAny(k), err.Error())) - return + return fmt.Errorf("Failed to load data for key %q: %w", formatAny(k), err) } rpos := f.contentBox.RowCount() @@ -72,6 +71,7 @@ func (ld *pebbleLoadedDatabase) RenderForNav(f *TMainForm, ndata *navData) { // Valid vcl_stringgrid_columnwidths(f.contentBox) f.contentBox.SetEnabled(true) + return nil } func (n *pebbleLoadedDatabase) ApplyChanges(f *TMainForm, ndata *navData) error { diff --git a/db_redis.go b/db_redis.go index 8073b42..0955b2a 100644 --- a/db_redis.go +++ b/db_redis.go @@ -40,34 +40,32 @@ func (ld *redisLoadedDatabase) Keepalive(ndata *navData) { ld.arena = append(ld.arena, ndata) } -func (ld *redisLoadedDatabase) RenderForNav(f *TMainForm, ndata *navData) { +func (ld *redisLoadedDatabase) RenderForNav(f *TMainForm, ndata *navData) error { ctx := context.Background() if len(ndata.bucketPath) == 0 { // Top-level: Show info() on main Properties tab infostr, err := ld.db.Info(ctx).Result() if err != nil { - vcl.ShowMessage(fmt.Sprintf("Retreiving database info: %v", err)) - return + return fmt.Errorf("Retreiving database info: %w", err) } f.propertiesBox.SetText(infostr) // Leave data tab disabled (default behaviour) + return nil } else if len(ndata.bucketPath) == 1 { // One selected database // Figure out its content err := ld.db.Do(ctx, "SELECT", ndata.bucketPath[0]).Err() if err != nil { - vcl.ShowMessage(fmt.Sprintf("Switching to database %q: %v", ndata.bucketPath[0], err)) - return + return fmt.Errorf("Switching to database %q: %w", ndata.bucketPath[0], err) } allKeys, err := ld.db.Keys(ctx, "*").Result() if err != nil { - vcl.ShowMessage(fmt.Sprintf("Listing keys in database %q: %v", ndata.bucketPath[0], err)) - return + return fmt.Errorf("Listing keys in database %q: %w", ndata.bucketPath[0], err) } f.propertiesBox.SetText(fmt.Sprintf("Database %s\nTotal keys: %d\n", ndata.bucketPath[0], len(allKeys))) @@ -84,8 +82,7 @@ func (ld *redisLoadedDatabase) RenderForNav(f *TMainForm, ndata *navData) { for _, key := range allKeys { typeName, err := ld.db.Type(ctx, key).Result() if err != nil { - vcl.ShowMessage(fmt.Sprintf("Loading %q/%q: %v", ndata.bucketPath[0], key, err)) - return + return fmt.Errorf("Loading %q/%q: %w", ndata.bucketPath[0], key, err) } rpos := f.contentBox.RowCount() @@ -97,16 +94,14 @@ func (ld *redisLoadedDatabase) RenderForNav(f *TMainForm, ndata *navData) { case "string": val, err := ld.db.Get(ctx, key).Result() if err != nil { - vcl.ShowMessage(fmt.Sprintf("Loading %q/%q: %v", ndata.bucketPath[0], key, err)) - return + return fmt.Errorf("Loading %q/%q: %w", ndata.bucketPath[0], key, err) } f.contentBox.SetCells(2, rpos, val) case "hash": val, err := ld.db.HGetAll(ctx, key).Result() if err != nil { - vcl.ShowMessage(fmt.Sprintf("Loading %q/%q: %v", ndata.bucketPath[0], key, err)) - return + return fmt.Errorf("Loading %q/%q: %w", ndata.bucketPath[0], key, err) } // It's a map[string]string f.contentBox.SetCells(2, rpos, formatAny(val)) @@ -129,10 +124,10 @@ func (ld *redisLoadedDatabase) RenderForNav(f *TMainForm, ndata *navData) { // Valid vcl_stringgrid_columnwidths(f.contentBox) f.contentBox.SetEnabled(true) + return nil } else { - vcl.ShowMessage(fmt.Sprintf("Unexpected nav position %q", ndata.bucketPath)) - return + return fmt.Errorf("Unexpected nav position %q", ndata.bucketPath) } } diff --git a/db_sqlite.go b/db_sqlite.go index c792b17..2f1d851 100644 --- a/db_sqlite.go +++ b/db_sqlite.go @@ -2,6 +2,7 @@ package main import ( "database/sql" + "errors" "fmt" "path/filepath" "unsafe" @@ -36,15 +37,17 @@ func (ld *sqliteLoadedDatabase) Keepalive(ndata *navData) { ld.arena = append(ld.arena, ndata) } -func (ld *sqliteLoadedDatabase) RenderForNav(f *TMainForm, ndata *navData) { +func (ld *sqliteLoadedDatabase) RenderForNav(f *TMainForm, ndata *navData) error { if len(ndata.bucketPath) == 0 { // Top-level f.propertiesBox.SetText("Please select...") + return nil } else if len(ndata.bucketPath) == 1 { // Category (tables, ...) f.propertiesBox.SetText("Please select...") + return nil } else if len(ndata.bucketPath) == 2 && ndata.bucketPath[0] == sqliteTablesCaption { // Render for specific table @@ -67,8 +70,7 @@ func (ld *sqliteLoadedDatabase) RenderForNav(f *TMainForm, ndata *navData) { // work even when there are 0 results columnNames, err := ld.sqliteGetColumnNamesForTable(tableName) if err != nil { - vcl.ShowMessageFmt("Failed to load columns for table %q: %s", tableName, err.Error()) - return + return fmt.Errorf("Failed to load columns for table %q: %w", tableName) } populateColumns(columnNames, f.contentBox) @@ -79,8 +81,7 @@ func (ld *sqliteLoadedDatabase) RenderForNav(f *TMainForm, ndata *navData) { // Select * with small limit datar, err := ld.db.Query(`SELECT * FROM "` + tableName + `" LIMIT 1000`) // WARNING can't prepare this parameter, but it comes from the DB (trusted) if err != nil { - vcl.ShowMessageFmt("Failed to load data for table %q: %s", tableName, err.Error()) - return + return fmt.Errorf("Failed to load data for table %q: %w", tableName, err) } defer datar.Close() populateRows(datar, f.contentBox) @@ -88,9 +89,11 @@ func (ld *sqliteLoadedDatabase) RenderForNav(f *TMainForm, ndata *navData) { // We successfully populated the data grid vcl_stringgrid_columnwidths(f.contentBox) f.contentBox.SetEnabled(true) + return nil } else { // ??? unknown + return errors.New("?") } diff --git a/loadedDatabase.go b/loadedDatabase.go index 3ea6381..21fcb7d 100644 --- a/loadedDatabase.go +++ b/loadedDatabase.go @@ -11,7 +11,7 @@ var ErrNotSupported error = errors.New("Unsupported action for this database typ type contextAction struct { Name string - Callback func(ndata *navData) + Callback func(ndata *navData) error } // loadedDatabase is a DB-agnostic interface for each loaded database. @@ -19,7 +19,7 @@ type loadedDatabase interface { DisplayName() string DriverName() string RootElement() *vcl.TTreeNode - RenderForNav(f *TMainForm, ndata *navData) + RenderForNav(f *TMainForm, ndata *navData) error ApplyChanges(f *TMainForm, ndata *navData) error ExecQuery(query string, resultArea *vcl.TStringGrid) error NavChildren(ndata *navData) ([]string, error) diff --git a/main.go b/main.go index b94c7fb..dbfa56d 100644 --- a/main.go +++ b/main.go @@ -507,7 +507,10 @@ func (f *TMainForm) OnNavContextPopup(sender vcl.IObject, mousePos types.TPoint, mnuAction.SetCaption(action.Name) cb := action.Callback // Copy to avoid reuse of loop variable mnuAction.SetOnClick(func(sender vcl.IObject) { - cb(ndata) + err = cb(ndata) + if err != nil { + vcl.ShowMessage(err.Error()) + } f.OnNavContextRefresh(curItem, ndata) }) mnu.Items().Add(mnuAction) @@ -779,7 +782,13 @@ func (f *TMainForm) OnNavChange(sender vcl.IObject, node *vcl.TTreeNode) { f.propertiesBox.Clear() vcl_stringgrid_clear(f.contentBox) - ld.RenderForNav(f, ndata) // Handover to the database type's own renderer function + err := ld.RenderForNav(f, ndata) // Handover to the database type's own renderer function + if err != nil { + vcl.ShowMessage(err.Error()) + + // Ensure elements are disabled + f.contentBox.SetEnabled(false) + } // We're in charge of common status bar text updates f.StatusBar.SetSimpleText(ld.DisplayName() + " | " + ld.DriverName())