diff --git a/main.go b/main.go index 07259ee..eb7a715 100644 --- a/main.go +++ b/main.go @@ -78,6 +78,79 @@ func withBrowse_ReadOnly(b_ref ObjectReference, browse []string, fn func(db *bol }) } +func err2triple(err error) (int64, *C.char, int) { + if err != nil { + msg := err.Error() + return ERROR_AND_STOP_CALLING, C.CString(msg), len(msg) + } + + return FINISHED_OK, nil, 0 +} + +//export Bolt_CreateBucket +func Bolt_CreateBucket(b_ref ObjectReference, browse []string, newBucket string) (int64, *C.char, int) { + err := withBoltDBReference(b_ref, func(db *bolt.DB) error { + return db.Update(func(tx *bolt.Tx) error { + + if len(browse) == 0 { + // Top-level bucket + _, err := tx.CreateBucket([]byte(newBucket)) + return err + + } else { + // Deeper bucket + bucket := tx.Bucket([]byte(browse[0])) + if bucket == nil { + return errors.New("Unknown bucket") + } + for i := 1; i < len(browse); i += 1 { + bucket = bucket.Bucket([]byte(browse[i])) + if bucket == nil { + return errors.New("Unknown bucket") + } + } + + // Walked the bucket chain, now create the new bucket + _, err := bucket.CreateBucket([]byte(newBucket)) + return err + } + }) + }) + + return err2triple(err) +} + +//export Bolt_DeleteBucket +func Bolt_DeleteBucket(b_ref ObjectReference, browse []string, delBucket string) (int64, *C.char, int) { + err := withBoltDBReference(b_ref, func(db *bolt.DB) error { + return db.Update(func(tx *bolt.Tx) error { + + if len(browse) == 0 { + // Top-level bucket + return tx.DeleteBucket([]byte(delBucket)) + + } else { + // Deeper bucket + bucket := tx.Bucket([]byte(browse[0])) + if bucket == nil { + return errors.New("Unknown bucket") + } + for i := 1; i < len(browse); i += 1 { + bucket = bucket.Bucket([]byte(browse[i])) + if bucket == nil { + return errors.New("Unknown bucket") + } + } + + // Walked the bucket chain, now delete the selected bucket + return bucket.DeleteBucket([]byte(delBucket)) + } + }) + }) + + return err2triple(err) +} + type CallResponse struct { s string e error @@ -94,7 +167,7 @@ func Bolt_DBStats(b ObjectReference) (int64, *C.char, int) { jBytes, err := json.Marshal(stats) if err != nil { - return ERROR_AND_STOP_CALLING, C.CString(err.Error()), len(err.Error()) + return err2triple(err) } return REAL_MESSAGE, C.CString(string(jBytes)), len(jBytes) @@ -110,12 +183,12 @@ func Bolt_BucketStats(b ObjectReference, browse []string) (int64, *C.char, int) }) if err != nil { - return ERROR_AND_STOP_CALLING, C.CString(err.Error()), len(err.Error()) + return err2triple(err) } jBytes, err := json.Marshal(stats) if err != nil { - return ERROR_AND_STOP_CALLING, C.CString(err.Error()), len(err.Error()) + return err2triple(err) } return REAL_MESSAGE, C.CString(string(jBytes)), len(jBytes) @@ -224,7 +297,7 @@ func Bolt_GetItem(b ObjectReference, browse []string, key string) (int64, *C.cha }) if err != nil { - return ERROR_AND_STOP_CALLING, C.CString(err.Error()), len(err.Error()) + return err2triple(err) } return REAL_MESSAGE, ret, ret_len @@ -234,20 +307,18 @@ func Bolt_GetItem(b ObjectReference, browse []string, key string) (int64, *C.cha func GetNext(oRef ObjectReference) (int64, *C.char, int) { pNC_Iface, ok := gms.Get(oRef) if !ok { - msg := NullObjectReference.Error() - return ERROR_AND_STOP_CALLING, C.CString(msg), len(msg) + return err2triple(NullObjectReference) } pNC, ok := pNC_Iface.(*NextCall) if !ok { - msg := NullObjectReference.Error() - return ERROR_AND_STOP_CALLING, C.CString(msg), len(msg) + return err2triple(NullObjectReference) } cr, ok := <-pNC.content if !ok { gms.Delete(oRef) - return FINISHED_OK, nil, 0 + return err2triple(nil) } if cr.e != nil { diff --git a/qbolt/boltdb.cpp b/qbolt/boltdb.cpp index d0a76ce..b908925 100644 --- a/qbolt/boltdb.cpp +++ b/qbolt/boltdb.cpp @@ -29,6 +29,33 @@ static const int ERROR_AND_KEEP_CALLING = 101; static const int FINISHED_OK = 102; static const int REAL_MESSAGE = 103; +bool BoltDB::addBucket(QStringList bucketPath, QByteArray bucketName, QString& errorOut) +{ + GoSliceManagedWrapper browse(&bucketPath); + GoString bucketNameGS = Interop::toGoString_WeakRef(&bucketName); + auto resp = ::Bolt_CreateBucket(this->gmsDbRef, browse.slice, bucketNameGS); + + if (resp.r0 == ERROR_AND_STOP_CALLING) { + errorOut = QString::fromUtf8(resp.r1, resp.r2); + free(resp.r1); + return false; + + } else if (resp.r0 == FINISHED_OK) { + return true; + + } else { + // ?? unreachable + return false; + + } +} + +bool BoltDB::deleteBucket(QStringList bucketPath, QByteArray bucketName, QString& errorOut) +{ + errorOut = "Not implemented"; + return false; // not implemented +} + bool BoltDB::listBucketsAtRoot(QString& errorOut, NameReciever cb) { auto listJob = ::Bolt_ListBucketsAtRoot(this->gmsDbRef); diff --git a/qbolt/boltdb.h b/qbolt/boltdb.h index 9ff39b6..8b80e90 100644 --- a/qbolt/boltdb.h +++ b/qbolt/boltdb.h @@ -20,6 +20,10 @@ public: bool listBuckets(QStringList bucketPath, QString& errorOut, NameReciever cb); + bool addBucket(QStringList bucketPath, QByteArray bucketName, QString& errorOut); + + bool deleteBucket(QStringList bucketPath, QByteArray bucketName, QString& errorOut); + bool listKeys(QStringList bucketPath, QString& errorOut, std::function cb); bool getData(QStringList bucketPath, QByteArray key, std::function onSuccess, std::function onError); diff --git a/qbolt/mainwindow.cpp b/qbolt/mainwindow.cpp index 2b16aa6..9879a2d 100644 --- a/qbolt/mainwindow.cpp +++ b/qbolt/mainwindow.cpp @@ -6,6 +6,7 @@ #include #include #include +#include MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), @@ -17,10 +18,14 @@ MainWindow::MainWindow(QWidget *parent) : databaseContext = new QMenu(); databaseContext->addAction(ui->actionRefresh_buckets); + databaseContext->addAction(ui->actionAdd_bucket); + databaseContext->addSeparator(); databaseContext->addAction(ui->actionDisconnect); bucketContext = new QMenu(); bucketContext->addAction(ui->actionRefresh_buckets); + bucketContext->addAction(ui->actionAdd_bucket); + bucketContext->addSeparator(); bucketContext->addAction(ui->actionDelete_bucket); } @@ -302,3 +307,69 @@ void MainWindow::on_bucketData_doubleClicked(const QModelIndex &index) ); } + +void MainWindow::on_actionAdd_bucket_triggered() +{ + QTreeWidgetItem* itm = lastContextSelection; + if (itm == nullptr) { + return; + } + + QTreeWidgetItem* top = itm; + QStringList browse; + while(top->parent() != nullptr) { + browse.push_front(top->text(0)); + top = top->parent(); + } + + // Get BDB + + auto *bdb = GET_BDB(top); + + // Prompt for bucket name + + QString name = QInputDialog::getText(this, tr("New bucket"), tr("Enter a key for the new bucket:")); + if (name.length() == 0) { + return; + } + + // Create + QString err; + if (! bdb->addBucket(browse, name.toUtf8(), err)) { + QMessageBox qmb; + qmb.setText(tr("Error creating bucket: %1").arg(err)); + qmb.exec(); + return; + } + + // Refresh bucket list + refreshBucketTree(itm); // sub-tree only + ui->bucketTree->expandItem(itm); +} + +void MainWindow::on_actionDelete_bucket_triggered() +{ + QTreeWidgetItem* itm = lastContextSelection; + if (itm == nullptr) { + return; + } + + QTreeWidgetItem* top = itm; + QStringList browse; + while(top->parent() != nullptr) { + browse.push_front(top->text(0)); + top = top->parent(); + } + + // Get BDB + + auto *bdb = GET_BDB(top); + + // Prompt for confirmation + + if (QMessageBox::question(this, tr("Delete bucket"), tr("Are you sure you want to remove the bucket '%s'?").arg(itm->text(0)), QMessageBox::Yes, QMessageBox::Cancel) != QMessageBox::Yes) { + return; + } + + +} diff --git a/qbolt/mainwindow.h b/qbolt/mainwindow.h index caa9fa5..bd26e30 100644 --- a/qbolt/mainwindow.h +++ b/qbolt/mainwindow.h @@ -40,6 +40,10 @@ private slots: void on_actionNew_database_triggered(); + void on_actionAdd_bucket_triggered(); + + void on_actionDelete_bucket_triggered(); + protected: void openDatabase(QString file); void refreshBucketTree(QTreeWidgetItem* top); diff --git a/qbolt/mainwindow.ui b/qbolt/mainwindow.ui index 50c0b22..f421812 100644 --- a/qbolt/mainwindow.ui +++ b/qbolt/mainwindow.ui @@ -297,8 +297,9 @@ - - false + + + :/rsrc/table_delete.png:/rsrc/table_delete.png Delete bucket @@ -320,7 +321,16 @@ :/rsrc/database_add.png:/rsrc/database_add.png - New database... + &New database... + + + + + + :/rsrc/table_add.png:/rsrc/table_add.png + + + Add bucket... diff --git a/qbolt/resources.qrc b/qbolt/resources.qrc index 3cc2483..98a91f3 100644 --- a/qbolt/resources.qrc +++ b/qbolt/resources.qrc @@ -5,5 +5,7 @@ rsrc/information.png rsrc/database_lightning.png rsrc/database.png + rsrc/table_add.png + rsrc/table_delete.png diff --git a/sample.db b/sample.db index 44fcf2d..b111fb7 100644 Binary files a/sample.db and b/sample.db differ