Compare commits
34 Commits
Author | SHA1 | Date |
---|---|---|
mappu | 6008ae44a2 | |
mappu | 99096b2360 | |
mappu | 0e92459779 | |
mappu | a13eaaf523 | |
mappu | dca8d277d3 | |
mappu | 15c739c0b9 | |
mappu | fc2da972ef | |
mappu | e45eea7111 | |
mappu | 1e62d79c07 | |
mappu | c74c5ae5c0 | |
mappu | b0092c4a6e | |
mappu | 5ce6368c4a | |
mappu | 3e7e54da4b | |
mappu | 9f80d687b2 | |
mappu | 96411e877f | |
mappu | ac7e078c02 | |
mappu | e13314f5dc | |
mappu | b50c3e738a | |
mappu | 54ad6015b7 | |
mappu | 40e84ac230 | |
mappu | 767eaa0a47 | |
mappu | bb594e768c | |
mappu | 516bd99c4d | |
mappu | 571bfcf4b6 | |
mappu | 26f7a11d80 | |
mappu | 6fb8d1ba0c | |
mappu | 21588021d3 | |
mappu | 7441e0c15b | |
mappu | 142f3f6bf4 | |
mappu | 19ddb1c956 | |
mappu | 97467eae4d | |
mappu | 17c37b6568 | |
mappu | 57237ef2e8 | |
mappu | 8d8374be24 |
|
@ -0,0 +1,4 @@
|
||||||
|
build-qbolt-*
|
||||||
|
dummy-data/dummy-data*
|
||||||
|
*.pro.user$
|
||||||
|
build/
|
|
@ -1,5 +0,0 @@
|
||||||
mode:regexp
|
|
||||||
^build-qbolt-
|
|
||||||
^dummy-data/dummy-data$
|
|
||||||
\.pro\.user$
|
|
||||||
^build/
|
|
20
Makefile
|
@ -1,7 +1,7 @@
|
||||||
|
|
||||||
export PATH := /usr/lib/mxe/usr/bin:$(PATH)
|
export PATH := /usr/lib/mxe/usr/bin:$(PATH)
|
||||||
GOFLAGS := -ldflags='-s -w' -gcflags='-trimpath=$(CURDIR)' -asmflags='-trimpath=$(CURDIR)'
|
GOFLAGS := -ldflags='-s -w' -gcflags='-trimpath=$(CURDIR)' -asmflags='-trimpath=$(CURDIR)'
|
||||||
VERSION := 1.0.0
|
VERSION := 1.0.2
|
||||||
|
|
||||||
.PHONY: all libs dist clean
|
.PHONY: all libs dist clean
|
||||||
|
|
||||||
|
@ -15,7 +15,6 @@ libs: \
|
||||||
|
|
||||||
dist: \
|
dist: \
|
||||||
build/dist/qbolt-${VERSION}-win32.zip \
|
build/dist/qbolt-${VERSION}-win32.zip \
|
||||||
build/dist/qbolt-${VERSION}-src.tar.gz \
|
|
||||||
build/dist/qbolt-${VERSION}-linux_amd64.tar.xz
|
build/dist/qbolt-${VERSION}-linux_amd64.tar.xz
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
|
@ -51,18 +50,23 @@ build/dist/qbolt-${VERSION}-linux_amd64.tar.xz: build/linux/qbolt
|
||||||
build/win32/release/qbolt.exe: build/win32/qbolt.a qbolt/*
|
build/win32/release/qbolt.exe: build/win32/qbolt.a qbolt/*
|
||||||
cd build/win32 && i686-w64-mingw32.static-qmake-qt5 ../../qbolt/qbolt.pro && make
|
cd build/win32 && i686-w64-mingw32.static-qmake-qt5 ../../qbolt/qbolt.pro && make
|
||||||
|
|
||||||
|
# Dockerized Windows build
|
||||||
|
|
||||||
|
.PHONY: build-docker-build-environment
|
||||||
|
build-docker-build-environment:
|
||||||
|
cd docker && docker build -t win32-cross-qt-mxe:latest -f win32-cross-qt-mxe.Dockerfile
|
||||||
|
|
||||||
|
.PHONY: build-windows-in-docker
|
||||||
|
build-windows-in-docker:
|
||||||
|
docker run --rm -v $(CURDIR):/qbolt win32-cross-qt-mxe:latest /bin/sh -c 'cd /qbolt && make build/win32/release/qbolt.exe'
|
||||||
|
|
||||||
# Windows distribution
|
# Windows distribution
|
||||||
|
|
||||||
build/win32/dist/qbolt.exe: build/win32/release/qbolt.exe
|
build/win32/dist/qbolt.exe: build/win32/release/qbolt.exe
|
||||||
mkdir -p build/win32/dist
|
mkdir -p build/win32/dist
|
||||||
cp build/win32/release/qbolt.exe build/win32/dist/qbolt.exe
|
cp build/win32/release/qbolt.exe build/win32/dist/qbolt.exe
|
||||||
upx --lzma build/win32/dist/qbolt.exe
|
# upx --lzma build/win32/dist/qbolt.exe
|
||||||
|
|
||||||
build/dist/qbolt-${VERSION}-win32.zip: build/win32/dist/qbolt.exe
|
build/dist/qbolt-${VERSION}-win32.zip: build/win32/dist/qbolt.exe
|
||||||
mkdir -p build/dist
|
mkdir -p build/dist
|
||||||
zip -0 -j build/dist/qbolt-${VERSION}-win32.zip build/win32/dist/qbolt.exe
|
zip -0 -j build/dist/qbolt-${VERSION}-win32.zip build/win32/dist/qbolt.exe
|
||||||
|
|
||||||
# Source code archives
|
|
||||||
|
|
||||||
build/dist/qbolt-${VERSION}-src.tar.gz:
|
|
||||||
hg archive build/dist/qbolt-${VERSION}-src.tar.gz
|
|
||||||
|
|
|
@ -0,0 +1,49 @@
|
||||||
|
# qbolt
|
||||||
|
|
||||||
|
A graphical database manager for BoltDB.
|
||||||
|
|
||||||
|
QBolt allows you to graphically view and edit the content of Bolt databases.
|
||||||
|
|
||||||
|
The project consists of two parts; a C binding (CGo) for the embeddable Bolt database engine, and a graphical interface built in C++/Qt that links to it.
|
||||||
|
|
||||||
|
Written in C++ (Qt), Golang (CGo)
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Open existing database or create new database
|
||||||
|
- Option to open database as readonly for concurrent use
|
||||||
|
- Create, list, edit and delete keys and buckets (including nested buckets)
|
||||||
|
- Safe for use with arbitrary binary key/bucket names (new ones created in UTF-8)
|
||||||
|
- View database and bucket statistics
|
||||||
|
- 100% Bolt compatibility via the real codebase
|
||||||
|
- Tested working on both Windows and Linux
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Source code content of `qbolt-x.x.x-src.tar.gz` is released under the ISC license.
|
||||||
|
BoltDB is released under the MIT license.
|
||||||
|
The Windows binary is released under LGPL-3+ owing to the static copy of Qt.
|
||||||
|
|
||||||
|
## See also
|
||||||
|
|
||||||
|
- BoltDB https://github.com/boltdb/bolt
|
||||||
|
|
||||||
|
## Changelog
|
||||||
|
|
||||||
|
2020-04-12 1.0.2
|
||||||
|
- Rebuild artefacts with etcd-io/bbolt v1.3.5, go 1.15, Qt 5.15, and new GCC versions
|
||||||
|
- Switch from hg to Git
|
||||||
|
- Use Go modules
|
||||||
|
- Add support for building Windows binary in Docker
|
||||||
|
- [⬇ Download here](https://git.ivysaur.me/code.ivysaur.me/qbolt/releases/tag/v1.0.2)
|
||||||
|
|
||||||
|
2017-06-19 1.0.1
|
||||||
|
- Feature: Option to open database as read-only
|
||||||
|
- Fix an issue with support for bucket names and keys not surviving UTF-8 roundtrips (now binary-clean)
|
||||||
|
- Fix an issue with crashing when deleting a bucket other than the selected one
|
||||||
|
- Fix a cosmetic issue with application icon on Windows
|
||||||
|
- [⬇ Download here](https://git.ivysaur.me/code.ivysaur.me/qbolt/releases/tag/v1.0.1)
|
||||||
|
|
||||||
|
2017-05-21 1.0.0
|
||||||
|
- Initial public release
|
||||||
|
- [⬇ Download here](https://git.ivysaur.me/code.ivysaur.me/qbolt/releases/tag/v1.0.0)
|
|
@ -1,31 +0,0 @@
|
||||||
A graphical database manager for BoltDB.
|
|
||||||
|
|
||||||
QBolt allows you to graphically view and edit the content of Bolt databases.
|
|
||||||
|
|
||||||
The project consists of two parts; a C binding (CGo) for the embeddable Bolt database engine, and a graphical interface built in C++/Qt that links to it.
|
|
||||||
|
|
||||||
Written in C++ (Qt), Golang (CGo)
|
|
||||||
|
|
||||||
=FEATURES=
|
|
||||||
|
|
||||||
- Open existing database or create new database
|
|
||||||
- Supports nested buckets
|
|
||||||
- Create and edit keys
|
|
||||||
- View database and bucket statistics
|
|
||||||
- 100% Bolt compatibility via the real codebase
|
|
||||||
- Tested working on both Windows and Linux
|
|
||||||
|
|
||||||
=LICENSE=
|
|
||||||
|
|
||||||
Source code content of `qbolt-x.x.x-src.tar.gz` is released under the ISC license.
|
|
||||||
BoltDB is released under the MIT license.
|
|
||||||
The Windows binary is released under LGPL-3+ owing to the static copy of Qt.
|
|
||||||
|
|
||||||
=SEE ALSO=
|
|
||||||
|
|
||||||
- BoltDB https://github.com/boltdb/bolt
|
|
||||||
|
|
||||||
=CHANGELOG=
|
|
||||||
|
|
||||||
2017-05-21 1.0.0
|
|
||||||
- Initial public release
|
|
|
@ -1,9 +0,0 @@
|
||||||
|
|
||||||
- Extra option to open read-only (allows concurrent access)
|
|
||||||
|
|
||||||
- Reduce unnecessary memory copies between QString/QByteArray
|
|
||||||
|
|
||||||
- Convert from item-based to model-based Qt widgets - needs deep integration with Bolt cursors...
|
|
||||||
|
|
||||||
- Remove dependence on UTF-8-encoded keys
|
|
||||||
|
|
After Width: | Height: | Size: 50 KiB |
Before Width: | Height: | Size: 90 KiB After Width: | Height: | Size: 90 KiB |
Before Width: | Height: | Size: 75 KiB After Width: | Height: | Size: 75 KiB |
Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 59 KiB |
|
@ -0,0 +1,13 @@
|
||||||
|
FROM debian:bullseye
|
||||||
|
|
||||||
|
RUN DEBIAN_FRONTEND=noninteractive apt-get update && \
|
||||||
|
apt-get install -qyy gnupg2 golang-go ca-certificates
|
||||||
|
|
||||||
|
RUN DEBIAN_FRONTEND=noninteractive \
|
||||||
|
echo "deb https://pkg.mxe.cc/repos/apt buster main" >/etc/apt/sources.list.d/mxeapt.list && \
|
||||||
|
apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 86B72ED9 && \
|
||||||
|
apt-get update && \
|
||||||
|
apt-get install -qyy mxe-i686-w64-mingw32.static-qt5 && \
|
||||||
|
apt-get clean
|
||||||
|
|
||||||
|
ENV PATH=/usr/lib/mxe/usr/bin:$PATH
|
|
@ -1,15 +1,18 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
//"fmt"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/boltdb/bolt"
|
bolt "github.com/boltdb/bolt"
|
||||||
)
|
)
|
||||||
|
|
||||||
func random_name() string {
|
func random_name() string {
|
||||||
return fmt.Sprintf("%08x-%08x-%08x", rand.Int63(), rand.Int63(), rand.Int63())
|
ret := make([]byte, 12)
|
||||||
|
rand.Read(ret)
|
||||||
|
return string(ret)
|
||||||
|
//return fmt.Sprintf("%08x-%08x-%08x", rand.Int63(), rand.Int63(), rand.Int63())
|
||||||
}
|
}
|
||||||
|
|
||||||
func fill_bucket(tx *bolt.Tx, bucket *bolt.Bucket) error {
|
func fill_bucket(tx *bolt.Tx, bucket *bolt.Bucket) error {
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
module code.ivysaur.me/qbolt
|
||||||
|
|
||||||
|
go 1.15
|
||||||
|
|
||||||
|
require github.com/boltdb/bolt v1.3.1
|
||||||
|
|
||||||
|
replace github.com/boltdb/bolt => go.etcd.io/bbolt v1.3.5
|
|
@ -0,0 +1,5 @@
|
||||||
|
github.com/boltdb/bolt v1.3.1 h1:JQmyP4ZBrce+ZQu0dY660FMfatumYDLun9hBCUVIkF4=
|
||||||
|
github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps=
|
||||||
|
go.etcd.io/bbolt v1.3.5 h1:XAzx9gjCb0Rxj7EoqcClPD1d5ZBxZJk0jbuoPHenBt0=
|
||||||
|
go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
|
||||||
|
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
6
main.go
|
@ -8,7 +8,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/boltdb/bolt"
|
bolt "github.com/boltdb/bolt"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -26,9 +26,11 @@ func GetMagic() int64 {
|
||||||
}
|
}
|
||||||
|
|
||||||
//export Bolt_Open
|
//export Bolt_Open
|
||||||
func Bolt_Open(path string) (ObjectReference, *C.char, int) {
|
func Bolt_Open(readOnly bool, path string) (ObjectReference, *C.char, int) {
|
||||||
opts := *bolt.DefaultOptions
|
opts := *bolt.DefaultOptions
|
||||||
opts.Timeout = 10 * time.Second
|
opts.Timeout = 10 * time.Second
|
||||||
|
opts.ReadOnly = readOnly
|
||||||
|
|
||||||
ptrDB, err := bolt.Open(path, os.FileMode(0644), &opts)
|
ptrDB, err := bolt.Open(path, os.FileMode(0644), &opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errMsg := err.Error()
|
errMsg := err.Error()
|
||||||
|
|
|
@ -7,12 +7,12 @@ BoltDB::BoltDB()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
BoltDB* BoltDB::createFrom(QString filePath, QString &errorOut)
|
BoltDB* BoltDB::createFrom(QString filePath, bool readOnly, QString &errorOut)
|
||||||
{
|
{
|
||||||
QByteArray filePathBytes(filePath.toUtf8());
|
QByteArray filePathBytes(filePath.toUtf8());
|
||||||
GoString filePathGS = Interop::toGoString_WeakRef(&filePathBytes);
|
GoString filePathGS = Interop::toGoString_WeakRef(&filePathBytes);
|
||||||
|
|
||||||
auto open_ret = ::Bolt_Open(filePathGS);
|
auto open_ret = ::Bolt_Open(readOnly, filePathGS);
|
||||||
if (open_ret.r2 != 0) {
|
if (open_ret.r2 != 0) {
|
||||||
errorOut = QString::fromUtf8(open_ret.r1, open_ret.r2);
|
errorOut = QString::fromUtf8(open_ret.r1, open_ret.r2);
|
||||||
free(open_ret.r1);
|
free(open_ret.r1);
|
||||||
|
@ -45,27 +45,27 @@ static bool handleTriple(int r0, char* r1, int64_t r2, QString& errorOut) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool BoltDB::addBucket(QStringList bucketPath, QByteArray bucketName, QString& errorOut)
|
bool BoltDB::addBucket(const QList<QByteArray>& bucketPath, QByteArray bucketName, QString& errorOut)
|
||||||
{
|
{
|
||||||
GoSliceManagedWrapper browse(&bucketPath);
|
GoSliceManagedWrapper browse(bucketPath);
|
||||||
GoString bucketNameGS = Interop::toGoString_WeakRef(&bucketName);
|
GoString bucketNameGS = Interop::toGoString_WeakRef(&bucketName);
|
||||||
auto resp = ::Bolt_CreateBucket(this->gmsDbRef, browse.slice, bucketNameGS);
|
auto resp = ::Bolt_CreateBucket(this->gmsDbRef, browse.slice, bucketNameGS);
|
||||||
|
|
||||||
return handleTriple(resp.r0, resp.r1, resp.r2, errorOut);
|
return handleTriple(resp.r0, resp.r1, resp.r2, errorOut);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool BoltDB::deleteBucket(QStringList bucketPath, QByteArray bucketName, QString& errorOut)
|
bool BoltDB::deleteBucket(const QList<QByteArray>& bucketPath, QByteArray bucketName, QString& errorOut)
|
||||||
{
|
{
|
||||||
GoSliceManagedWrapper browse(&bucketPath);
|
GoSliceManagedWrapper browse(bucketPath);
|
||||||
GoString bucketNameGS = Interop::toGoString_WeakRef(&bucketName);
|
GoString bucketNameGS = Interop::toGoString_WeakRef(&bucketName);
|
||||||
auto resp = ::Bolt_DeleteBucket(this->gmsDbRef, browse.slice, bucketNameGS);
|
auto resp = ::Bolt_DeleteBucket(this->gmsDbRef, browse.slice, bucketNameGS);
|
||||||
|
|
||||||
return handleTriple(resp.r0, resp.r1, resp.r2, errorOut);
|
return handleTriple(resp.r0, resp.r1, resp.r2, errorOut);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool BoltDB::setItem(QStringList bucketPath, QByteArray keyName, QByteArray value, QString& errorOut)
|
bool BoltDB::setItem(const QList<QByteArray>& bucketPath, QByteArray keyName, QByteArray value, QString& errorOut)
|
||||||
{
|
{
|
||||||
GoSliceManagedWrapper browse(&bucketPath);
|
GoSliceManagedWrapper browse(bucketPath);
|
||||||
GoString keyNameGS = Interop::toGoString_WeakRef(&keyName);
|
GoString keyNameGS = Interop::toGoString_WeakRef(&keyName);
|
||||||
GoString valueGS = Interop::toGoString_WeakRef(&value);
|
GoString valueGS = Interop::toGoString_WeakRef(&value);
|
||||||
auto resp = ::Bolt_SetItem(this->gmsDbRef, browse.slice, keyNameGS, valueGS);
|
auto resp = ::Bolt_SetItem(this->gmsDbRef, browse.slice, keyNameGS, valueGS);
|
||||||
|
@ -73,9 +73,9 @@ bool BoltDB::setItem(QStringList bucketPath, QByteArray keyName, QByteArray valu
|
||||||
return handleTriple(resp.r0, resp.r1, resp.r2, errorOut);
|
return handleTriple(resp.r0, resp.r1, resp.r2, errorOut);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool BoltDB::deleteItem(QStringList bucketPath, QByteArray keyName, QString& errorOut)
|
bool BoltDB::deleteItem(const QList<QByteArray>& bucketPath, QByteArray keyName, QString& errorOut)
|
||||||
{
|
{
|
||||||
GoSliceManagedWrapper browse(&bucketPath);
|
GoSliceManagedWrapper browse(bucketPath);
|
||||||
GoString keyNameGS = Interop::toGoString_WeakRef(&keyName);
|
GoString keyNameGS = Interop::toGoString_WeakRef(&keyName);
|
||||||
auto resp = ::Bolt_DeleteItem(this->gmsDbRef, browse.slice, keyNameGS);
|
auto resp = ::Bolt_DeleteItem(this->gmsDbRef, browse.slice, keyNameGS);
|
||||||
|
|
||||||
|
@ -89,20 +89,20 @@ bool BoltDB::listBucketsAtRoot(QString& errorOut, NameReciever cb)
|
||||||
return pumpNext(listJob, errorOut, cb);
|
return pumpNext(listJob, errorOut, cb);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool BoltDB::listBuckets(QStringList bucketPath, QString& errorOut, NameReciever cb)
|
bool BoltDB::listBuckets(const QList<QByteArray>& bucketPath, QString& errorOut, NameReciever cb)
|
||||||
{
|
{
|
||||||
if (bucketPath.size() == 0) {
|
if (bucketPath.size() == 0) {
|
||||||
return listBucketsAtRoot(errorOut, cb);
|
return listBucketsAtRoot(errorOut, cb);
|
||||||
}
|
}
|
||||||
|
|
||||||
GoSliceManagedWrapper browse(&bucketPath);
|
GoSliceManagedWrapper browse(bucketPath);
|
||||||
auto listJob = ::Bolt_ListBuckets(this->gmsDbRef, browse.slice);
|
auto listJob = ::Bolt_ListBuckets(this->gmsDbRef, browse.slice);
|
||||||
return pumpNext(listJob, errorOut, cb);
|
return pumpNext(listJob, errorOut, cb);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool BoltDB::listKeys(QStringList bucketPath, QString& errorOut, std::function<void(QByteArray, int64_t)> cb)
|
bool BoltDB::listKeys(const QList<QByteArray>& bucketPath, QString& errorOut, std::function<void(QByteArray, int64_t)> cb)
|
||||||
{
|
{
|
||||||
GoSliceManagedWrapper browse(&bucketPath);
|
GoSliceManagedWrapper browse(bucketPath);
|
||||||
auto listJob = ::Bolt_ListItems(this->gmsDbRef, browse.slice);
|
auto listJob = ::Bolt_ListItems(this->gmsDbRef, browse.slice);
|
||||||
return pumpNext(listJob, errorOut, [=](QByteArray b) {
|
return pumpNext(listJob, errorOut, [=](QByteArray b) {
|
||||||
// First 8 bytes are little-endian uint64 len
|
// First 8 bytes are little-endian uint64 len
|
||||||
|
@ -111,9 +111,9 @@ bool BoltDB::listKeys(QStringList bucketPath, QString& errorOut, std::function<v
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
bool BoltDB::getData(QStringList bucketPath, QByteArray key, std::function<void(QByteArray)> onSuccess, std::function<void(QString)> onError)
|
bool BoltDB::getData(const QList<QByteArray>& bucketPath, QByteArray key, std::function<void(QByteArray)> onSuccess, std::function<void(QString)> onError)
|
||||||
{
|
{
|
||||||
GoSliceManagedWrapper browse(&bucketPath);
|
GoSliceManagedWrapper browse(bucketPath);
|
||||||
GoString keyGS = Interop::toGoString_WeakRef(&key);
|
GoString keyGS = Interop::toGoString_WeakRef(&key);
|
||||||
auto resp = ::Bolt_GetItem(this->gmsDbRef, browse.slice, keyGS);
|
auto resp = ::Bolt_GetItem(this->gmsDbRef, browse.slice, keyGS);
|
||||||
|
|
||||||
|
@ -185,9 +185,9 @@ bool BoltDB::getStatsJSON(std::function<void(QByteArray)> onSuccess, std::functi
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool BoltDB::getBucketStatsJSON(QStringList bucketPath, std::function<void(QByteArray)> onSuccess, std::function<void(QString)> onError)
|
bool BoltDB::getBucketStatsJSON(const QList<QByteArray>& bucketPath, std::function<void(QByteArray)> onSuccess, std::function<void(QString)> onError)
|
||||||
{
|
{
|
||||||
GoSliceManagedWrapper sliceWrapper(&bucketPath);
|
GoSliceManagedWrapper sliceWrapper(bucketPath);
|
||||||
auto statresp = Bolt_BucketStats(this->gmsDbRef, sliceWrapper.slice);
|
auto statresp = Bolt_BucketStats(this->gmsDbRef, sliceWrapper.slice);
|
||||||
|
|
||||||
if (statresp.r0 == ERROR_AND_STOP_CALLING) {
|
if (statresp.r0 == ERROR_AND_STOP_CALLING) {
|
||||||
|
|
|
@ -14,27 +14,27 @@ protected:
|
||||||
GoInt64 gmsDbRef;
|
GoInt64 gmsDbRef;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static BoltDB* createFrom(QString filePath, QString &errorOut);
|
static BoltDB* createFrom(QString filePath, bool readOnly, QString &errorOut);
|
||||||
|
|
||||||
bool listBucketsAtRoot(QString& errorOut, NameReciever cb);
|
bool listBucketsAtRoot(QString& errorOut, NameReciever cb);
|
||||||
|
|
||||||
bool listBuckets(QStringList bucketPath, QString& errorOut, NameReciever cb);
|
bool listBuckets(const QList<QByteArray>& bucketPath, QString& errorOut, NameReciever cb);
|
||||||
|
|
||||||
bool addBucket(QStringList bucketPath, QByteArray bucketName, QString& errorOut);
|
bool addBucket(const QList<QByteArray>& bucketPath, QByteArray bucketName, QString& errorOut);
|
||||||
|
|
||||||
bool deleteBucket(QStringList bucketPath, QByteArray bucketName, QString& errorOut);
|
bool deleteBucket(const QList<QByteArray>& bucketPath, QByteArray bucketName, QString& errorOut);
|
||||||
|
|
||||||
bool setItem(QStringList bucketPath, QByteArray keyName, QByteArray value, QString& errorOut);
|
bool setItem(const QList<QByteArray>& bucketPath, QByteArray keyName, QByteArray value, QString& errorOut);
|
||||||
|
|
||||||
bool deleteItem(QStringList bucketPath, QByteArray keyName, QString& errorOut);
|
bool deleteItem(const QList<QByteArray>& bucketPath, QByteArray keyName, QString& errorOut);
|
||||||
|
|
||||||
bool listKeys(QStringList bucketPath, QString& errorOut, std::function<void(QByteArray, int64_t)> cb);
|
bool listKeys(const QList<QByteArray>& bucketPath, QString& errorOut, std::function<void(QByteArray, int64_t)> cb);
|
||||||
|
|
||||||
bool getData(QStringList bucketPath, QByteArray key, std::function<void(QByteArray)> onSuccess, std::function<void(QString)> onError);
|
bool getData(const QList<QByteArray>& bucketPath, QByteArray key, std::function<void(QByteArray)> onSuccess, std::function<void(QString)> onError);
|
||||||
|
|
||||||
bool getStatsJSON(std::function<void(QByteArray)> onSuccess, std::function<void(QString)> onError);
|
bool getStatsJSON(std::function<void(QByteArray)> onSuccess, std::function<void(QString)> onError);
|
||||||
|
|
||||||
bool getBucketStatsJSON(QStringList bucketPath, std::function<void(QByteArray)> onSuccess, std::function<void(QString)> onError);
|
bool getBucketStatsJSON(const QList<QByteArray>& bucketPath, std::function<void(QByteArray)> onSuccess, std::function<void(QString)> onError);
|
||||||
|
|
||||||
~BoltDB();
|
~BoltDB();
|
||||||
|
|
||||||
|
|
|
@ -17,22 +17,22 @@ int64_t Interop::GetMagic() {
|
||||||
|
|
||||||
//
|
//
|
||||||
|
|
||||||
GoSliceManagedWrapper::GoSliceManagedWrapper(QStringList *qsl) :
|
GoSliceManagedWrapper::GoSliceManagedWrapper(const QList<QByteArray>& qsl) :
|
||||||
rawStrings(),
|
rawStrings(),
|
||||||
slice(),
|
slice(),
|
||||||
strings(nullptr)
|
strings(nullptr)
|
||||||
{
|
{
|
||||||
rawStrings.reserve(qsl->size());
|
rawStrings.reserve(qsl.size());
|
||||||
strings = new GoString[qsl->size()];
|
strings = new GoString[qsl.size()];
|
||||||
|
|
||||||
for (int i = 0; i < qsl->size(); ++i) {
|
for (int i = 0; i < qsl.size(); ++i) {
|
||||||
rawStrings.push_back( qsl->at(i).toUtf8() );
|
rawStrings.push_back( qsl.at(i) );
|
||||||
strings[i].p = rawStrings[i].data();
|
strings[i].p = rawStrings[i].data();
|
||||||
strings[i].n = rawStrings[i].size();
|
strings[i].n = rawStrings[i].size();
|
||||||
}
|
}
|
||||||
|
|
||||||
slice.data = static_cast<void*>(strings);
|
slice.data = static_cast<void*>(strings);
|
||||||
slice.len = qsl->size(); // * sizeof(GoString);
|
slice.len = qsl.size(); // * sizeof(GoString);
|
||||||
slice.cap = slice.len;
|
slice.cap = slice.len;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ class GoSliceManagedWrapper {
|
||||||
Q_DISABLE_COPY(GoSliceManagedWrapper)
|
Q_DISABLE_COPY(GoSliceManagedWrapper)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
GoSliceManagedWrapper(QStringList *qsl);
|
GoSliceManagedWrapper(const QList<QByteArray>& qsl);
|
||||||
~GoSliceManagedWrapper();
|
~GoSliceManagedWrapper();
|
||||||
protected:
|
protected:
|
||||||
QList<QByteArray> rawStrings;
|
QList<QByteArray> rawStrings;
|
||||||
|
|
|
@ -35,6 +35,8 @@ MainWindow::~MainWindow()
|
||||||
}
|
}
|
||||||
|
|
||||||
static const int BdbPointerRole = Qt::UserRole + 1;
|
static const int BdbPointerRole = Qt::UserRole + 1;
|
||||||
|
static const int BinaryDataRole = Qt::UserRole + 2;
|
||||||
|
|
||||||
#define SET_BDB(top, bdb) top->setData(0, BdbPointerRole, QVariant::fromValue<void*>(static_cast<void*>(bdb)))
|
#define SET_BDB(top, bdb) top->setData(0, BdbPointerRole, QVariant::fromValue<void*>(static_cast<void*>(bdb)))
|
||||||
#define GET_BDB(top) static_cast<BoltDB*>( top->data(0, BdbPointerRole).value<void*>() )
|
#define GET_BDB(top) static_cast<BoltDB*>( top->data(0, BdbPointerRole).value<void*>() )
|
||||||
|
|
||||||
|
@ -42,7 +44,7 @@ void MainWindow::on_actionNew_database_triggered()
|
||||||
{
|
{
|
||||||
QString file = QFileDialog::getSaveFileName(this, tr("Save new bolt database as..."));
|
QString file = QFileDialog::getSaveFileName(this, tr("Save new bolt database as..."));
|
||||||
if (file.length()) {
|
if (file.length()) {
|
||||||
openDatabase(file);
|
openDatabase(file, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,15 +52,23 @@ void MainWindow::on_actionOpen_database_triggered()
|
||||||
{
|
{
|
||||||
QString file = QFileDialog::getOpenFileName(this, tr("Select bolt database..."));
|
QString file = QFileDialog::getOpenFileName(this, tr("Select bolt database..."));
|
||||||
if (file.length()) {
|
if (file.length()) {
|
||||||
openDatabase(file);
|
openDatabase(file, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::openDatabase(QString file)
|
void MainWindow::on_actionOpen_database_as_read_only_triggered()
|
||||||
|
{
|
||||||
|
QString file = QFileDialog::getOpenFileName(this, tr("Select bolt database..."));
|
||||||
|
if (file.length()) {
|
||||||
|
openDatabase(file, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::openDatabase(QString file, bool readOnly)
|
||||||
{
|
{
|
||||||
// Open
|
// Open
|
||||||
QString error;
|
QString error;
|
||||||
auto *bdb = BoltDB::createFrom(file, error);
|
auto *bdb = BoltDB::createFrom(file, readOnly, error);
|
||||||
if (bdb == nullptr) {
|
if (bdb == nullptr) {
|
||||||
QMessageBox qmb;
|
QMessageBox qmb;
|
||||||
qmb.setText(tr("Error opening database: %1").arg(error));
|
qmb.setText(tr("Error opening database: %1").arg(error));
|
||||||
|
@ -78,12 +88,38 @@ void MainWindow::openDatabase(QString file)
|
||||||
ui->bucketTree->expandItem(top);
|
ui->bucketTree->expandItem(top);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const QString getDisplayName(const QByteArray &qba) {
|
||||||
|
// FIXME the formatting isn't so great when control characters, etc. are used
|
||||||
|
// A C-style escape display, or the unicode-replacement-character would be preferable
|
||||||
|
QString ret(QString::fromUtf8(qba));
|
||||||
|
|
||||||
|
bool allPrintable = true;
|
||||||
|
for (auto i = ret.begin(), e = ret.end(); i != e; ++i) {
|
||||||
|
if (! i->isPrint()) {
|
||||||
|
allPrintable = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (allPrintable) {
|
||||||
|
return ret; // fine
|
||||||
|
}
|
||||||
|
|
||||||
|
// Some of the characters weren't printable.
|
||||||
|
// Build up a replacement string
|
||||||
|
QString replacement;
|
||||||
|
for (auto i = ret.begin(), e = ret.end(); i != e; ++i) {
|
||||||
|
replacement += i->isPrint() ? *i : QStringLiteral("\\u{%1}").arg(i->unicode());
|
||||||
|
}
|
||||||
|
return replacement;
|
||||||
|
}
|
||||||
|
|
||||||
void MainWindow::refreshBucketTree(QTreeWidgetItem* itm)
|
void MainWindow::refreshBucketTree(QTreeWidgetItem* itm)
|
||||||
{
|
{
|
||||||
QTreeWidgetItem *top = itm;
|
QTreeWidgetItem *top = itm;
|
||||||
QStringList browsePath;
|
QList<QByteArray> browsePath;
|
||||||
while(top->parent() != nullptr) {
|
while(top->parent() != nullptr) {
|
||||||
browsePath.push_front(top->text(0));
|
browsePath.push_front(top->data(0, BinaryDataRole).toByteArray());
|
||||||
top = top->parent();
|
top = top->parent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -100,7 +136,8 @@ void MainWindow::refreshBucketTree(QTreeWidgetItem* itm)
|
||||||
error,
|
error,
|
||||||
[=](QByteArray qba){
|
[=](QByteArray qba){
|
||||||
QTreeWidgetItem *child = new QTreeWidgetItem();
|
QTreeWidgetItem *child = new QTreeWidgetItem();
|
||||||
child->setText(0, QString::fromUtf8(qba));
|
child->setText(0, getDisplayName(qba));
|
||||||
|
child->setData(0, BinaryDataRole, qba);
|
||||||
child->setIcon(0, QIcon(":/rsrc/table.png"));
|
child->setIcon(0, QIcon(":/rsrc/table.png"));
|
||||||
itm->addChild(child);
|
itm->addChild(child);
|
||||||
|
|
||||||
|
@ -215,10 +252,10 @@ void MainWindow::on_bucketTree_currentItemChanged(QTreeWidgetItem *current, QTre
|
||||||
ui->stackedWidget->setCurrentWidget(ui->bucketPage);
|
ui->stackedWidget->setCurrentWidget(ui->bucketPage);
|
||||||
ui->bucketPropertiesArea->clear();
|
ui->bucketPropertiesArea->clear();
|
||||||
|
|
||||||
QStringList browse;
|
QList<QByteArray> browse;
|
||||||
QTreeWidgetItem *top = current;
|
QTreeWidgetItem *top = current;
|
||||||
while (top->parent() != nullptr) {
|
while (top->parent() != nullptr) {
|
||||||
browse.push_front(top->text(0));
|
browse.push_front(top->data(0, BinaryDataRole).toByteArray());
|
||||||
top = top->parent();
|
top = top->parent();
|
||||||
}
|
}
|
||||||
auto *bdb = GET_BDB(top);
|
auto *bdb = GET_BDB(top);
|
||||||
|
@ -242,14 +279,15 @@ void MainWindow::on_bucketTree_currentItemChanged(QTreeWidgetItem *current, QTre
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::refreshData(BoltDB *bdb, QStringList browse)
|
void MainWindow::refreshData(BoltDB *bdb, const QList<QByteArray>& browse)
|
||||||
{
|
{
|
||||||
// Load the data tab
|
// Load the data tab
|
||||||
ui->bucketData->clear();
|
ui->bucketData->clear();
|
||||||
QString err;
|
QString err;
|
||||||
bool ok = bdb->listKeys(browse, err, [=](QByteArray name, int64_t dataLen) {
|
bool ok = bdb->listKeys(browse, err, [=](QByteArray name, int64_t dataLen) {
|
||||||
auto *itm = new QTreeWidgetItem();
|
auto *itm = new QTreeWidgetItem();
|
||||||
itm->setText(0, QString::fromUtf8(name));
|
itm->setText(0, getDisplayName(name));
|
||||||
|
itm->setData(0, BinaryDataRole, name);
|
||||||
itm->setText(1, QString("%1").arg(dataLen));
|
itm->setText(1, QString("%1").arg(dataLen));
|
||||||
ui->bucketData->addTopLevelItem(itm);
|
ui->bucketData->addTopLevelItem(itm);
|
||||||
});
|
});
|
||||||
|
@ -275,14 +313,14 @@ void MainWindow::on_actionClear_selection_triggered()
|
||||||
return; \
|
return; \
|
||||||
} \
|
} \
|
||||||
QTreeWidgetItem* top = itm; \
|
QTreeWidgetItem* top = itm; \
|
||||||
QStringList browse; \
|
QList<QByteArray> browse; \
|
||||||
while(top->parent() != nullptr) { \
|
while(top->parent() != nullptr) { \
|
||||||
browse.push_front(top->text(0)); \
|
browse.push_front(top->data(0, BinaryDataRole).toByteArray()); \
|
||||||
top = top->parent(); \
|
top = top->parent(); \
|
||||||
} \
|
} \
|
||||||
auto *bdb = GET_BDB(top);
|
auto *bdb = GET_BDB(top);
|
||||||
|
|
||||||
void MainWindow::openEditor(BoltDB *bdb, QStringList saveAs, QByteArray saveAsKey, QByteArray currentContent)
|
void MainWindow::openEditor(BoltDB *bdb, const QList<QByteArray>& saveAs, QByteArray saveAsKey, QByteArray currentContent)
|
||||||
{
|
{
|
||||||
auto iw = new ItemWindow();
|
auto iw = new ItemWindow();
|
||||||
iw->ContentArea()->setPlainText(QString::fromUtf8(currentContent));
|
iw->ContentArea()->setPlainText(QString::fromUtf8(currentContent));
|
||||||
|
@ -312,15 +350,15 @@ void MainWindow::on_bucketData_doubleClicked(const QModelIndex &index)
|
||||||
// Get item key
|
// Get item key
|
||||||
|
|
||||||
auto model = index.model();
|
auto model = index.model();
|
||||||
QString key = model->data(model->index(index.row(), 0), 0).toString();
|
const QByteArray& key = model->data(model->index(index.row(), 0), BinaryDataRole).toByteArray();
|
||||||
|
|
||||||
// DB lookup
|
// DB lookup
|
||||||
|
|
||||||
bdb->getData(
|
bdb->getData(
|
||||||
browse,
|
browse,
|
||||||
key.toUtf8(),
|
key,
|
||||||
[=](QByteArray content) {
|
[=](QByteArray content) {
|
||||||
openEditor(bdb, browse, key.toUtf8(), content);
|
openEditor(bdb, browse, key, content);
|
||||||
},
|
},
|
||||||
[=](QString error) {
|
[=](QString error) {
|
||||||
QMessageBox qmb;
|
QMessageBox qmb;
|
||||||
|
@ -361,17 +399,27 @@ void MainWindow::on_actionDelete_bucket_triggered()
|
||||||
GET_ITM_TOP_BROWSE_BDB;
|
GET_ITM_TOP_BROWSE_BDB;
|
||||||
|
|
||||||
// Prompt for confirmation
|
// Prompt for confirmation
|
||||||
QString bucketToDelete = itm->text(0);
|
const QByteArray& bucketToDelete = itm->data(0, BinaryDataRole).toByteArray();
|
||||||
if (QMessageBox::question(this, tr("Delete bucket"), tr("Are you sure you want to remove the bucket '%1'?").arg(bucketToDelete), QMessageBox::Yes, QMessageBox::Cancel) != QMessageBox::Yes) {
|
if (
|
||||||
|
QMessageBox::question(
|
||||||
|
this,
|
||||||
|
tr("Delete bucket"),
|
||||||
|
tr("Are you sure you want to remove the bucket '%1'?").arg(getDisplayName(bucketToDelete)),
|
||||||
|
QMessageBox::Yes,
|
||||||
|
QMessageBox::Cancel
|
||||||
|
) != QMessageBox::Yes
|
||||||
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QTreeWidgetItem* parent = itm->parent();
|
||||||
|
|
||||||
// One level down
|
// One level down
|
||||||
|
|
||||||
browse.pop_back();
|
browse.pop_back();
|
||||||
|
|
||||||
QString err;
|
QString err;
|
||||||
if (! bdb->deleteBucket(browse, bucketToDelete.toUtf8(), err)) {
|
if (! bdb->deleteBucket(browse, bucketToDelete, err)) {
|
||||||
QMessageBox qmb;
|
QMessageBox qmb;
|
||||||
qmb.setText(tr("Error removing bucket: %1").arg(err));
|
qmb.setText(tr("Error removing bucket: %1").arg(err));
|
||||||
qmb.exec();
|
qmb.exec();
|
||||||
|
@ -379,9 +427,9 @@ void MainWindow::on_actionDelete_bucket_triggered()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Refresh bucket list
|
// Refresh bucket list
|
||||||
refreshBucketTree(itm->parent()); // sub-tree only
|
refreshBucketTree(parent); // sub-tree only
|
||||||
ui->bucketTree->expandItem(itm->parent());
|
ui->bucketTree->expandItem(parent);
|
||||||
|
ui->bucketTree->setCurrentItem(parent);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::on_AddDataButton_clicked()
|
void MainWindow::on_AddDataButton_clicked()
|
||||||
|
@ -414,7 +462,7 @@ void MainWindow::on_DeleteDataButton_clicked()
|
||||||
|
|
||||||
QString err;
|
QString err;
|
||||||
for (int i = selection.length(); i-->0;) {
|
for (int i = selection.length(); i-->0;) {
|
||||||
if (! bdb->deleteItem(browse, selection[i]->text(0).toUtf8(), err)) {
|
if (! bdb->deleteItem(browse, selection[i]->data(0, BinaryDataRole).toByteArray(), err)) {
|
||||||
QMessageBox qmb;
|
QMessageBox qmb;
|
||||||
qmb.setText(tr("Error removing item: %1").arg(err));
|
qmb.setText(tr("Error removing item: %1").arg(err));
|
||||||
qmb.exec();
|
qmb.exec();
|
||||||
|
|
|
@ -51,11 +51,13 @@ private slots:
|
||||||
|
|
||||||
void on_bucketData_itemSelectionChanged();
|
void on_bucketData_itemSelectionChanged();
|
||||||
|
|
||||||
|
void on_actionOpen_database_as_read_only_triggered();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void openDatabase(QString file);
|
void openDatabase(QString file, bool readOnly);
|
||||||
void refreshBucketTree(QTreeWidgetItem* top);
|
void refreshBucketTree(QTreeWidgetItem* top);
|
||||||
void refreshData(BoltDB *bdb, QStringList browse);
|
void refreshData(BoltDB *bdb, const QList<QByteArray>& browse);
|
||||||
void openEditor(BoltDB *bdb, QStringList saveAs, QByteArray saveAsKey, QByteArray currentContent);
|
void openEditor(BoltDB *bdb, const QList<QByteArray>& saveAs, QByteArray saveAsKey, QByteArray currentContent);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Ui::MainWindow *ui;
|
Ui::MainWindow *ui;
|
||||||
|
|
|
@ -278,6 +278,7 @@
|
||||||
</property>
|
</property>
|
||||||
<addaction name="actionNew_database"/>
|
<addaction name="actionNew_database"/>
|
||||||
<addaction name="actionOpen_database"/>
|
<addaction name="actionOpen_database"/>
|
||||||
|
<addaction name="actionOpen_database_as_read_only"/>
|
||||||
<addaction name="separator"/>
|
<addaction name="separator"/>
|
||||||
<addaction name="actionExit"/>
|
<addaction name="actionExit"/>
|
||||||
</widget>
|
</widget>
|
||||||
|
@ -395,6 +396,11 @@
|
||||||
<string>Add bucket...</string>
|
<string>Add bucket...</string>
|
||||||
</property>
|
</property>
|
||||||
</action>
|
</action>
|
||||||
|
<action name="actionOpen_database_as_read_only">
|
||||||
|
<property name="text">
|
||||||
|
<string>Open database as read-only...</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
</widget>
|
</widget>
|
||||||
<layoutdefault spacing="6" margin="11"/>
|
<layoutdefault spacing="6" margin="11"/>
|
||||||
<resources>
|
<resources>
|
||||||
|
|
|
@ -20,10 +20,13 @@ win32: {
|
||||||
DEFINES += CGO_WINDOWS
|
DEFINES += CGO_WINDOWS
|
||||||
QMAKE_LIBS += $$_PRO_FILE_PWD_/../build/win32/qbolt.a
|
QMAKE_LIBS += $$_PRO_FILE_PWD_/../build/win32/qbolt.a
|
||||||
QMAKE_LIBS += -lntdll
|
QMAKE_LIBS += -lntdll
|
||||||
|
|
||||||
|
RC_ICONS = rsrc/qbolt.ico
|
||||||
}
|
}
|
||||||
|
|
||||||
linux: {
|
linux: {
|
||||||
QMAKE_LIBS += $$_PRO_FILE_PWD_/../build/linux/qbolt.a
|
QMAKE_LIBS += $$_PRO_FILE_PWD_/../build/linux/qbolt.a
|
||||||
|
QMAKE_LIBS += -lpthread
|
||||||
}
|
}
|
||||||
|
|
||||||
SOURCES += main.cpp\
|
SOURCES += main.cpp\
|
||||||
|
|
After Width: | Height: | Size: 1.1 KiB |