diff --git a/.gitignore b/.gitignore index 1dd9f3d..7e42353 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,16 @@ -build-qbolt-* +# development +qbolt dummy-data/dummy-data* -*.pro.user$ -build/ + +# temporary build files +rsrc_windows_amd64.syso +windows-manifest.json + +# release build files +build/qbolt +build/qbolt.exe +build/*.xz +build/*.zip + +# local makefile definition scripts +make-* diff --git a/Makefile b/Makefile index e10298c..6528921 100644 --- a/Makefile +++ b/Makefile @@ -1,72 +1,60 @@ -export PATH := /usr/lib/mxe/usr/bin:$(PATH) -GOFLAGS := -ldflags='-s -w' -gcflags='-trimpath=$(CURDIR)' -asmflags='-trimpath=$(CURDIR)' -VERSION := 1.0.2 +VERSION := 1.0.3 +GOFLAGS_L := -ldflags='-s -w -X main.Version=v$(VERSION)' -buildvcs=false -gcflags='-trimpath=$(CURDIR)' -asmflags='-trimpath=$(CURDIR)' +GOFLAGS_W := -ldflags='-s -w -X main.Version=v$(VERSION) -H windowsgui' -buildvcs=false --tags=windowsqtstatic -gcflags='-trimpath=$(CURDIR)' -asmflags='-trimpath=$(CURDIR)' +SHELL := /bin/bash +# Allow overriding DOCKER with e.g. sudo docker +DOCKER := docker +MIQT_UIC := miqt-uic +MIQT_RCC := miqt-rcc +GO_WINRES := go-winres +SOURCES := $(wildcard *.go *.ui *.qrc) resources.go mainwindow_ui.go itemwindow_ui.go rsrc_windows_amd64.syso -.PHONY: all libs dist clean - -all: \ - build/linux/qbolt \ - build/win32/release/qbolt.exe - -libs: \ - build/linux/qbolt.a \ - build/win32/qbolt.a +.PHONY: all +all: build/qbolt build/qbolt.exe -dist: \ - build/dist/qbolt-${VERSION}-win32.zip \ - build/dist/qbolt-${VERSION}-linux_amd64.tar.xz +.PHONY: dist +dist: build/qbolt-${VERSION}-windows-x86_64.zip build/qbolt-${VERSION}-debian12-x86_64.tar.xz +.PHONY: clean clean: - if [ -f qbolt/qbolt.a ] ; then rm qbolt/qbolt.a ; fi - if [ -f qbolt ] ; then rm qbolt ; fi - if [ -d build ] ; then rm -r build ; fi + rm -f qbolt || true + rm -rf build || true + rm -f windows-manifest.json || true -# Build core golang shared library (linux) +# Generated files -build/linux/qbolt.a: *.go - mkdir -p build/linux - go build ${GOFLAGS} -buildmode=c-archive -o build/linux/qbolt.a +resources.rcc resources.go: resources.qrc + $(MIQT_RCC) resources.qrc -# Build core golang shared library (win32) +mainwindow_ui.go: mainwindow.ui + $(MIQT_UIC) -InFile mainwindow.ui -OutFile mainwindow_ui.go + +itemwindow_ui.go: itemwindow.ui + $(MIQT_UIC) -InFile itemwindow.ui -OutFile itemwindow_ui.go -build/win32/qbolt.a: *.go - mkdir -p build/win32 - CC=/usr/lib/mxe/usr/bin/i686-w64-mingw32.static-gcc CGO_ENABLED=1 GOARCH=386 GOOS=windows \ - go build ${GOFLAGS} -buildmode=c-archive -o build/win32/qbolt.a - -# Linux binaries +windows-manifest.json: windows-manifest.template.json Makefile + cat windows-manifest.template.json | sed -re 's_%VERSION%_$(VERSION)_' > windows-manifest.json -build/linux/qbolt: build/linux/qbolt.a qbolt/* - cd build/linux && qmake ../../qbolt/qbolt.pro && make - -# Linux distribution - -build/dist/qbolt-${VERSION}-linux_amd64.tar.xz: build/linux/qbolt - XZ_OPTS=-9 tar caf build/dist/qbolt-${VERSION}-linux_amd64.tar.xz -C build/linux qbolt --owner=0 --group=0 +rsrc_windows_amd64.syso: windows-manifest.json + $(GO_WINRES) make --in windows-manifest.json + rm rsrc_windows_386.syso || true # we do not build x86_32 -# Windows binaries - -build/win32/release/qbolt.exe: build/win32/qbolt.a qbolt/* - 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 - -build/win32/dist/qbolt.exe: build/win32/release/qbolt.exe - mkdir -p build/win32/dist - cp build/win32/release/qbolt.exe build/win32/dist/qbolt.exe -# upx --lzma build/win32/dist/qbolt.exe +# Linux release -build/dist/qbolt-${VERSION}-win32.zip: build/win32/dist/qbolt.exe - mkdir -p build/dist - zip -0 -j build/dist/qbolt-${VERSION}-win32.zip build/win32/dist/qbolt.exe +build/qbolt: $(SOURCES) + go build $(GOFLAGS_L) -o build/qbolt + upx build/qbolt + +build/qbolt-${VERSION}-debian12-x86_64.tar.xz: build/qbolt + XZ_OPTS=-9e tar caf build/qbolt-${VERSION}-debian12-x86_64.tar.xz -C build qbolt --owner=0 --group=0 + +# Windows release (docker) + +build/qbolt.exe: $(SOURCES) + ( $(DOCKER) image ls | fgrep qbolt-win64-cross ) || ( cd docker && $(DOCKER) build -t qbolt-win64-cross:latest -f win64-cross.Dockerfile . ) + $(DOCKER) run --rm -v $(CURDIR):/qbolt -w /qbolt qbolt-win64-cross:latest /bin/sh -c "go build $(GOFLAGS_W) -o build/qbolt.exe" + upx --force build/qbolt.exe + +build/qbolt-${VERSION}-windows-x86_64.zip: build/qbolt.exe + zip -9 -j build/qbolt-${VERSION}-windows-x86_64.zip build/qbolt.exe diff --git a/MemoryStore.go b/MemoryStore.go deleted file mode 100644 index 95ffbf3..0000000 --- a/MemoryStore.go +++ /dev/null @@ -1,56 +0,0 @@ -package main - -import "C" -import ( - "errors" - "sync" -) - -type ObjectReference int64 - -var NullObjectReference error = errors.New("Null object reference") - -// GoMemoryStore is a int->interface storage structure so that Go pointers are -// never exposed to C code. -type GoMemoryStore struct { - mtx sync.RWMutex - items map[int64]interface{} - next int64 -} - -func NewGoMemoryStore() *GoMemoryStore { - ret := GoMemoryStore{} - ret.items = make(map[int64]interface{}) - return &ret -} - -func (this *GoMemoryStore) Put(itm interface{}) ObjectReference { - this.mtx.Lock() - defer this.mtx.Unlock() - - key := this.next - this.items[key] = itm - this.next++ - return ObjectReference(key) -} - -func (this *GoMemoryStore) Get(i ObjectReference) (interface{}, bool) { - this.mtx.RLock() - defer this.mtx.RUnlock() - - ret, ok := this.items[int64(i)] - return ret, ok -} - -func (this *GoMemoryStore) Delete(i ObjectReference) { - this.mtx.Lock() - defer this.mtx.Unlock() - - delete(this.items, int64(i)) -} - -var gms *GoMemoryStore = nil - -func init() { - gms = NewGoMemoryStore() -} diff --git a/bolt.go b/bolt.go new file mode 100644 index 0000000..0705e4b --- /dev/null +++ b/bolt.go @@ -0,0 +1,219 @@ +package main + +import ( + "encoding/json" + "errors" + "os" + "time" + + bolt "go.etcd.io/bbolt" +) + +func Bolt_Open(readOnly bool, path string) (*bolt.DB, error) { + opts := *bolt.DefaultOptions + opts.Timeout = 10 * time.Second + opts.ReadOnly = readOnly + + return bolt.Open(path, os.FileMode(0644), &opts) +} + +func walkBuckets(tx *bolt.Tx, browse []string) (*bolt.Bucket, error) { + bucket := tx.Bucket([]byte(browse[0])) + if bucket == nil { + return nil, errors.New("Unknown bucket") + } + + for i := 1; i < len(browse); i += 1 { + bucket = bucket.Bucket([]byte(browse[i])) + if bucket == nil { + return nil, errors.New("Unknown bucket") + } + } + + return bucket, nil +} + +func withBrowse_ReadOnly(db *bolt.DB, browse []string, fn func(tx *bolt.Tx, bucket *bolt.Bucket) error) error { + if len(browse) == 0 { + // not a bucket + return errors.New("No bucket selected") + } + + return db.View(func(tx *bolt.Tx) error { + + bucket, err := walkBuckets(tx, browse) + if err != nil { + return err + } + + // Walked the bucket chain, now run the user callback + return fn(tx, bucket) + + }) +} + +func Bolt_CreateBucket(db *bolt.DB, browse []string, newBucket string) 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, err := walkBuckets(tx, browse) + if err != nil { + return err + } + + // Walked the bucket chain, now create the new bucket + _, err = bucket.CreateBucket([]byte(newBucket)) + return err + } + }) +} + +func Bolt_DeleteBucket(db *bolt.DB, browse []string, delBucket string) 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, err := walkBuckets(tx, browse) + if err != nil { + return err + } + + // Walked the bucket chain, now delete the selected bucket + return bucket.DeleteBucket([]byte(delBucket)) + } + }) +} + +func Bolt_SetItem(db *bolt.DB, browse []string, key, val string) error { + if len(browse) == 0 { + return errors.New("Can't create top-level items") + } + + return db.Update(func(tx *bolt.Tx) error { + + bucket, err := walkBuckets(tx, browse) + if err != nil { + return err + } + + return bucket.Put([]byte(key), []byte(val)) + }) +} + +func Bolt_DeleteItem(db *bolt.DB, browse []string, key string) error { + if len(browse) == 0 { + return errors.New("Can't create top-level items") + } + + return db.Update(func(tx *bolt.Tx) error { + + bucket, err := walkBuckets(tx, browse) + if err != nil { + return err + } + + return bucket.Delete([]byte(key)) + }) +} + +func Bolt_DBStats(db *bolt.DB) (string, error) { + jBytes, err := json.MarshalIndent(db.Stats(), "", " ") + if err != nil { + return "", err + } + + return string(jBytes), nil +} + +func Bolt_BucketStats(db *bolt.DB, browse []string) (string, error) { + var stats bolt.BucketStats + + err := withBrowse_ReadOnly(db, browse, func(tx *bolt.Tx, bucket *bolt.Bucket) error { + stats = bucket.Stats() + return nil + }) + if err != nil { + return "", err + } + + jBytes, err := json.MarshalIndent(stats, "", " ") + if err != nil { + return "", err + } + + return string(jBytes), err +} + +func Bolt_ListBuckets(db *bolt.DB, browse []string, cb func(b string)) error { + + if len(browse) == 0 { + // root mode + return db.View(func(tx *bolt.Tx) error { + return tx.ForEach(func(k []byte, _ *bolt.Bucket) error { + cb(string(k)) + return nil + }) + }) + + } + + // Nested-mode + return withBrowse_ReadOnly(db, browse, func(tx *bolt.Tx, bucket *bolt.Bucket) error { + return bucket.ForEach(func(k, v []byte) error { + // non-nil v means it's a data item + if v == nil { + cb(string(k)) + } + return nil + }) + }) +} + +type ListItemInfo struct { + Name string + DataLen int64 +} + +func Bolt_ListItems(db *bolt.DB, browse []string, cb func(ListItemInfo) error) error { + + if len(browse) == 0 { + return errors.New("No bucket specified") + } + + // Nested-mode + return withBrowse_ReadOnly(db, browse, func(tx *bolt.Tx, bucket *bolt.Bucket) error { + return bucket.ForEach(func(k, v []byte) error { + if v == nil { + return nil // nil v means it's a bucket, skip + } + + return cb(ListItemInfo{string(k), int64(len(v))}) + }) + }) +} + +func Bolt_GetItem(db *bolt.DB, browse []string, key string) (string, error) { + var ret string + + err := withBrowse_ReadOnly(db, browse, func(tx *bolt.Tx, bucket *bolt.Bucket) error { + d := bucket.Get([]byte(key)) + ret = string(d) + return nil + }) + return ret, err +} + +func Bolt_Close(db *bolt.DB) error { + return db.Close() +} diff --git a/build/.create_dir b/build/.create_dir new file mode 100644 index 0000000..e69de29 diff --git a/docker/win32-cross-qt-mxe.Dockerfile b/docker/win64-cross.Dockerfile similarity index 52% rename from docker/win32-cross-qt-mxe.Dockerfile rename to docker/win64-cross.Dockerfile index c50e9e4..f94aa46 100644 --- a/docker/win32-cross-qt-mxe.Dockerfile +++ b/docker/win64-cross.Dockerfile @@ -1,13 +1,20 @@ -FROM debian:bullseye +FROM golang:1.23-bookworm RUN DEBIAN_FRONTEND=noninteractive apt-get update && \ - apt-get install -qyy gnupg2 golang-go ca-certificates - + apt-get install -qyy gnupg2 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 install -qyy mxe-x86-64-w64-mingw32.static-qt5 && \ apt-get clean ENV PATH=/usr/lib/mxe/usr/bin:$PATH + +ENV CXX=x86_64-w64-mingw32.static-g++ +ENV CC=x86_64-w64-mingw32.static-gcc +ENV PKG_CONFIG=x86_64-w64-mingw32.static-pkg-config +ENV GOOS=windows +ENV CGO_ENABLED=1 + diff --git a/dummy-data/main.go b/dummy-data/main.go index 76e8e72..cafd624 100644 --- a/dummy-data/main.go +++ b/dummy-data/main.go @@ -1,11 +1,10 @@ package main import ( - //"fmt" "math/rand" "os" - bolt "github.com/boltdb/bolt" + bolt "go.etcd.io/bbolt" ) func random_name() string { diff --git a/go.mod b/go.mod index b16dcfb..63fe150 100644 --- a/go.mod +++ b/go.mod @@ -1,7 +1,10 @@ module code.ivysaur.me/qbolt -go 1.15 +go 1.23 -require github.com/boltdb/bolt v1.3.1 +require ( + github.com/mappu/miqt v0.5.0 + go.etcd.io/bbolt v1.3.11 +) -replace github.com/boltdb/bolt => go.etcd.io/bbolt v1.3.5 +require golang.org/x/sys v0.4.0 // indirect diff --git a/go.sum b/go.sum index 218565d..6174cd0 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,11 @@ -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= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/mappu/miqt v0.5.0 h1:BWajkNI9PWlWN6ZDgWKwv1gieBGEImRqlWS8ZqDmDfA= +github.com/mappu/miqt v0.5.0/go.mod h1:xFg7ADaO1QSkmXPsPODoKe/bydJpRG9fgCYyIDl/h1U= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +go.etcd.io/bbolt v1.3.11 h1:yGEzV1wPz2yVCLsD8ZAiGHhHVlczyC9d1rP43/VCRJ0= +go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I= +golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= +golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18= +golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/qbolt/itemwindow.ui b/itemwindow.ui similarity index 92% rename from qbolt/itemwindow.ui rename to itemwindow.ui index 2f2960e..f50fc37 100644 --- a/qbolt/itemwindow.ui +++ b/itemwindow.ui @@ -42,12 +42,6 @@ - - QFrame::StyledPanel - - - QFrame::Raised - diff --git a/itemwindow_ui.go b/itemwindow_ui.go new file mode 100644 index 0000000..b2dfa29 --- /dev/null +++ b/itemwindow_ui.go @@ -0,0 +1,71 @@ +// Generated by miqt-uic. To update this file, edit the .ui file in +// Qt Designer, and then run 'go generate'. +// +//go:generate miqt-uic -InFile itemwindow.ui -OutFile itemwindow_ui.go + +package main + +import ( + "github.com/mappu/miqt/qt" +) + +type ItemWindowUi struct { + ItemWindow *qt.QDialog + gridLayout_2 *qt.QGridLayout + contentArea *qt.QPlainTextEdit + frame *qt.QFrame + gridLayout *qt.QGridLayout + buttonBox *qt.QDialogButtonBox +} + +// NewItemWindowUi creates all Qt widget classes for ItemWindow. +func NewItemWindowUi() *ItemWindowUi { + ui := &ItemWindowUi{} + + ui.ItemWindow = qt.NewQDialog2(nil) + ui.ItemWindow.SetObjectName("ItemWindow") + ui.ItemWindow.Resize(370, 353) + ui.ItemWindow.SetWindowTitle("") + icon0 := qt.NewQIcon() + icon0.AddFile4(":/rsrc/database_lightning.png", qt.NewQSize(), qt.QIcon__Normal, qt.QIcon__Off) + ui.ItemWindow.SetWindowIcon(icon0) + + ui.gridLayout_2 = qt.NewQGridLayout(ui.ItemWindow.QWidget) + ui.gridLayout_2.SetObjectName("gridLayout_2") + ui.gridLayout_2.SetVerticalSpacing(0) + ui.gridLayout_2.SetContentsMargins(0, 0, 0, 0) + ui.gridLayout_2.SetSpacing(6) + + ui.contentArea = qt.NewQPlainTextEdit3(ui.ItemWindow.QWidget) + ui.contentArea.SetObjectName("contentArea") + ui.contentArea.SetFrameShape(qt.QFrame__NoFrame) + + ui.gridLayout_2.AddWidget2(ui.contentArea.QWidget, 0, 0) + + ui.frame = qt.NewQFrame2(ui.ItemWindow.QWidget) + ui.frame.SetObjectName("frame") + + ui.gridLayout = qt.NewQGridLayout(ui.frame.QWidget) + ui.gridLayout.SetObjectName("gridLayout") + ui.gridLayout.SetContentsMargins(11, 11, 11, 11) + ui.gridLayout.SetSpacing(6) + + ui.buttonBox = qt.NewQDialogButtonBox5(ui.frame.QWidget) + ui.buttonBox.SetObjectName("buttonBox") + ui.buttonBox.SetStandardButtons(qt.QDialogButtonBox__Cancel | qt.QDialogButtonBox__Save) + ui.buttonBox.OnAccepted(ui.ItemWindow.Accept) + ui.buttonBox.OnRejected(ui.ItemWindow.Reject) + + ui.gridLayout.AddWidget2(ui.buttonBox.QWidget, 0, 0) + + ui.gridLayout_2.AddWidget2(ui.frame.QWidget, 1, 0) + + ui.Retranslate() + + return ui +} + +// Retranslate reapplies all text translations. +func (ui *ItemWindowUi) Retranslate() { + +} diff --git a/main.go b/main.go index 09fdd65..8531fe1 100644 --- a/main.go +++ b/main.go @@ -1,398 +1,22 @@ package main -import "C" import ( - "encoding/binary" - "encoding/json" - "errors" "os" - "time" - bolt "github.com/boltdb/bolt" + "github.com/mappu/miqt/qt" ) -const ( - ERROR_AND_STOP_CALLING int64 = 100 - ERROR_AND_KEEP_CALLING = 101 - FINISHED_OK = 102 - REAL_MESSAGE = 103 -) - -const Magic int64 = 0x10203040 - -//export GetMagic -func GetMagic() int64 { - return Magic -} - -//export Bolt_Open -func Bolt_Open(readOnly bool, path string) (ObjectReference, *C.char, int) { - opts := *bolt.DefaultOptions - opts.Timeout = 10 * time.Second - opts.ReadOnly = readOnly - - ptrDB, err := bolt.Open(path, os.FileMode(0644), &opts) - if err != nil { - errMsg := err.Error() - return 0, C.CString(errMsg), len(errMsg) - } - - dbRef := gms.Put(ptrDB) - return dbRef, nil, 0 -} - -func withBoltDBReference(b ObjectReference, fn func(db *bolt.DB) error) error { - dbIFC, ok := gms.Get(b) - if !ok { - return NullObjectReference - } - - ptrDB, ok := dbIFC.(*bolt.DB) - if !ok { - return NullObjectReference - } - - return fn(ptrDB) -} - -func walkBuckets(tx *bolt.Tx, browse []string) (*bolt.Bucket, error) { - bucket := tx.Bucket([]byte(browse[0])) - if bucket == nil { - return nil, errors.New("Unknown bucket") - } - - for i := 1; i < len(browse); i += 1 { - bucket = bucket.Bucket([]byte(browse[i])) - if bucket == nil { - return nil, errors.New("Unknown bucket") - } - } - - return bucket, nil -} - -func withBrowse_ReadOnly(b_ref ObjectReference, browse []string, fn func(db *bolt.DB, tx *bolt.Tx, bucket *bolt.Bucket) error) error { - if len(browse) == 0 { - // not a bucket - return errors.New("No bucket selected") - } - - return withBoltDBReference(b_ref, func(db *bolt.DB) error { - return db.View(func(tx *bolt.Tx) error { - - bucket, err := walkBuckets(tx, browse) - if err != nil { - return err - } - - // Walked the bucket chain, now run the user callback - return fn(db, tx, bucket) - - }) - }) -} - -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, err := walkBuckets(tx, browse) - if err != nil { - return err - } - - // 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, err := walkBuckets(tx, browse) - if err != nil { - return err - } - - // Walked the bucket chain, now delete the selected bucket - return bucket.DeleteBucket([]byte(delBucket)) - } - }) - }) - - return err2triple(err) -} - -//export Bolt_SetItem -func Bolt_SetItem(b_ref ObjectReference, browse []string, key, val string) (int64, *C.char, int) { - if len(browse) == 0 { - return err2triple(errors.New("Can't create top-level items")) - } - - err := withBoltDBReference(b_ref, func(db *bolt.DB) error { - return db.Update(func(tx *bolt.Tx) error { - - bucket, err := walkBuckets(tx, browse) - if err != nil { - return err - } - - return bucket.Put([]byte(key), []byte(val)) - }) - }) - - return err2triple(err) -} - -//export Bolt_DeleteItem -func Bolt_DeleteItem(b_ref ObjectReference, browse []string, key string) (int64, *C.char, int) { - if len(browse) == 0 { - return err2triple(errors.New("Can't create top-level items")) - } - - err := withBoltDBReference(b_ref, func(db *bolt.DB) error { - return db.Update(func(tx *bolt.Tx) error { - - bucket, err := walkBuckets(tx, browse) - if err != nil { - return err - } - - return bucket.Delete([]byte(key)) - }) - }) - - return err2triple(err) -} - -type CallResponse struct { - s string - e error -} - -//export Bolt_DBStats -func Bolt_DBStats(b ObjectReference) (int64, *C.char, int) { - var stats bolt.Stats - - err := withBoltDBReference(b, func(db *bolt.DB) error { - stats = db.Stats() - return nil - }) - - jBytes, err := json.Marshal(stats) - if err != nil { - return err2triple(err) - } - - return REAL_MESSAGE, C.CString(string(jBytes)), len(jBytes) -} - -//export Bolt_BucketStats -func Bolt_BucketStats(b ObjectReference, browse []string) (int64, *C.char, int) { - var stats bolt.BucketStats - - err := withBrowse_ReadOnly(b, browse, func(db *bolt.DB, tx *bolt.Tx, bucket *bolt.Bucket) error { - stats = bucket.Stats() - return nil - }) - - if err != nil { - return err2triple(err) - } - - jBytes, err := json.Marshal(stats) - if err != nil { - return err2triple(err) - } - - return REAL_MESSAGE, C.CString(string(jBytes)), len(jBytes) -} - -type NextCall struct { - content chan CallResponse -} - -//export Bolt_ListBuckets -func Bolt_ListBuckets(b ObjectReference, browse []string) ObjectReference { - pNC := &NextCall{ - content: make(chan CallResponse, 0), - } - - pNC_Ref := gms.Put(pNC) - - go func() { - var err error - - if len(browse) == 0 { - // root mode - err = withBoltDBReference(b, func(db *bolt.DB) error { - return db.View(func(tx *bolt.Tx) error { - return tx.ForEach(func(k []byte, _ *bolt.Bucket) error { - pNC.content <- CallResponse{s: string(k)} - return nil - }) - }) - }) - - } else { - // Nested-mode - err = withBrowse_ReadOnly(b, browse, func(db *bolt.DB, tx *bolt.Tx, bucket *bolt.Bucket) error { - return bucket.ForEach(func(k, v []byte) error { - // non-nil v means it's a data item - if v == nil { - pNC.content <- CallResponse{s: string(k)} - } - return nil - }) - }) - - } - - if err != nil { - pNC.content <- CallResponse{e: err} - } - - close(pNC.content) - }() - - return pNC_Ref -} - -//export Bolt_ListItems -func Bolt_ListItems(b ObjectReference, browse []string) ObjectReference { - - pNC := &NextCall{ - content: make(chan CallResponse, 0), - } - - pNC_Ref := gms.Put(pNC) - - go func() { - var err error - - if len(browse) == 0 { - err = errors.New("No bucket specified") - } else { - // Nested-mode - err = withBrowse_ReadOnly(b, browse, func(db *bolt.DB, tx *bolt.Tx, bucket *bolt.Bucket) error { - return bucket.ForEach(func(k, v []byte) error { - if v == nil { - return nil // nil v means it's a bucket, skip - } - - itemLength := make([]byte, 8) - binary.LittleEndian.PutUint64(itemLength, uint64(len(v))) - pNC.content <- CallResponse{s: string(itemLength) + string(k)} - return nil - }) - }) - } - - if err != nil { - pNC.content <- CallResponse{e: err} - } - - close(pNC.content) - }() - - return pNC_Ref -} - -//export Bolt_GetItem -func Bolt_GetItem(b ObjectReference, browse []string, key string) (int64, *C.char, int) { - var ret *C.char = nil - var ret_len = 0 - - err := withBrowse_ReadOnly(b, browse, func(db *bolt.DB, tx *bolt.Tx, bucket *bolt.Bucket) error { - d := bucket.Get([]byte(key)) - ret = C.CString(string(d)) - ret_len = len(d) - return nil - }) - - if err != nil { - return err2triple(err) - } - - return REAL_MESSAGE, ret, ret_len -} - -//export GetNext -func GetNext(oRef ObjectReference) (int64, *C.char, int) { - pNC_Iface, ok := gms.Get(oRef) - if !ok { - return err2triple(NullObjectReference) - } - - pNC, ok := pNC_Iface.(*NextCall) - if !ok { - return err2triple(NullObjectReference) - } - - cr, ok := <-pNC.content - if !ok { - gms.Delete(oRef) - return err2triple(nil) - } - - if cr.e != nil { - msg := cr.e.Error() - return ERROR_AND_KEEP_CALLING, C.CString(msg), len(msg) - } - - return REAL_MESSAGE, C.CString(cr.s), len(cr.s) -} - -//export Bolt_ListBucketsAtRoot -func Bolt_ListBucketsAtRoot(b ObjectReference) ObjectReference { - return Bolt_ListBuckets(b, nil) -} - -//export Bolt_Close -func Bolt_Close(b ObjectReference) (*C.char, int) { - err := withBoltDBReference(b, func(db *bolt.DB) error { - return db.Close() - }) - - if err != nil { - msg := err.Error() - return C.CString(msg), len(msg) - } - - gms.Delete(b) - return nil, 0 -} +var Version string = "v0.0.0-devel" func main() { - // virtual + + _ = qt.NewQApplication(os.Args) + + qt.QGuiApplication_SetApplicationDisplayName("QBolt") + qt.QGuiApplication_SetWindowIcon(qt.NewQIcon4(":/rsrc/database_lightning.png")) + + w := NewMainWindow() + w.ui.MainWindow.Show() + + qt.QApplication_Exec() } diff --git a/mainwindow.go b/mainwindow.go new file mode 100644 index 0000000..4a54a26 --- /dev/null +++ b/mainwindow.go @@ -0,0 +1,496 @@ +package main + +import ( + "fmt" + "path/filepath" + "strconv" + "strings" + + "github.com/mappu/miqt/qt" + bolt "go.etcd.io/bbolt" +) + +type MainWindow struct { + ui *MainWindowUi + + databaseContext *qt.QMenu + bucketContext *qt.QMenu + lastContextSelection *qt.QTreeWidgetItem +} + +func NewMainWindow() *MainWindow { + this := &MainWindow{} + this.ui = NewMainWindowUi() + + this.on_bucketTree_currentItemChanged(nil, nil) + + this.databaseContext = qt.NewQMenu() + this.databaseContext.QWidget.AddAction(this.ui.actionRefresh_buckets) + this.databaseContext.QWidget.AddAction(this.ui.actionAdd_bucket) + this.databaseContext.AddSeparator() + this.databaseContext.QWidget.AddAction(this.ui.actionDisconnect) + + this.bucketContext = qt.NewQMenu() + this.bucketContext.QWidget.AddAction(this.ui.actionRefresh_buckets) + this.bucketContext.QWidget.AddAction(this.ui.actionAdd_bucket) + this.bucketContext.AddSeparator() + this.bucketContext.QWidget.AddAction(this.ui.actionDelete_bucket) + + // Connections + this.ui.actionNew_database.OnTriggered(this.on_actionNew_database_triggered) + this.ui.actionOpen_database.OnTriggered(this.on_actionOpen_database_triggered) + this.ui.actionOpen_database_as_read_only.OnTriggered(this.on_actionOpen_database_as_read_only_triggered) + this.ui.actionExit.OnTriggered(this.on_actionExit_triggered) + this.ui.actionAbout_Qt.OnTriggered(this.on_actionAbout_Qt_triggered) + this.ui.actionAbout_qbolt.OnTriggered(this.on_actionAbout_qbolt_triggered) + this.ui.actionDisconnect.OnTriggered(this.on_actionDisconnect_triggered) + this.ui.bucketTree.OnCustomContextMenuRequested(this.on_bucketTree_customContextMenuRequested) + this.ui.actionRefresh_buckets.OnTriggered(this.on_actionRefresh_buckets_triggered) + this.ui.bucketTree.OnCurrentItemChanged(this.on_bucketTree_currentItemChanged) + this.ui.actionClear_selection.OnTriggered(this.on_actionClear_selection_triggered) + this.ui.bucketData.OnDoubleClicked(this.on_bucketData_doubleClicked) + this.ui.actionAdd_bucket.OnTriggered(this.on_actionAdd_bucket_triggered) + this.ui.actionDelete_bucket.OnTriggered(this.on_actionDelete_bucket_triggered) + this.ui.AddDataButton.OnClicked(this.on_AddDataButton_clicked) + this.ui.DeleteDataButton.OnClicked(this.on_DeleteDataButton_clicked) + this.ui.bucketData.OnItemSelectionChanged(this.on_bucketData_itemSelectionChanged) + + return this +} + +const ( + BdbPointerRole = int(qt.UserRole + 1) + BinaryDataRole = int(qt.UserRole + 2) +) + +var bdbs []*bolt.DB = nil + +func SET_BDB(top *qt.QTreeWidgetItem, bdb *bolt.DB) { + idx := len(bdbs) + bdbs = append(bdbs, bdb) + top.SetData(0, BdbPointerRole, qt.NewQVariant7(idx)) // Don't store a Go pointer in Qt memory +} + +func GET_BDB(top *qt.QTreeWidgetItem) *bolt.DB { + if top == nil { + panic("Passed a nil QTreeWidgetItem") + } + + dataVariant := top.Data(0, BdbPointerRole) + if dataVariant == nil { + panic("Selected item has no bdb") + } + + return bdbs[dataVariant.ToInt()] +} + +func (this *MainWindow) Widget() *qt.QWidget { + return this.ui.centralWidget +} + +func (this *MainWindow) on_actionNew_database_triggered() { + file := qt.QFileDialog_GetSaveFileName2(this.Widget(), "Save new bolt database as...") + if len(file) > 0 { + this.openDatabase(file, false) + } +} + +func (this *MainWindow) on_actionOpen_database_triggered() { + file := qt.QFileDialog_GetOpenFileName2(this.Widget(), "Select bolt database...") + if len(file) > 0 { + this.openDatabase(file, false) + } +} + +func (this *MainWindow) on_actionOpen_database_as_read_only_triggered() { + file := qt.QFileDialog_GetOpenFileName2(this.Widget(), "Select bolt database...") + if len(file) > 0 { + this.openDatabase(file, true) + } +} + +func (this *MainWindow) alert(message string) { + qt.QMessageBox_Critical(this.Widget(), "qbolt", message) +} + +func (this *MainWindow) openDatabase(file string, readOnly bool) { + + // Open + bdb, err := Bolt_Open(readOnly, file) + if err != nil { + this.alert(fmt.Sprintf("Error opening database: %s", err.Error())) + return + } + + top := qt.NewQTreeWidgetItem() + top.SetText(0, filepath.Base(file)) + top.SetIcon(0, qt.NewQIcon4(":/rsrc/database.png")) + SET_BDB(top, bdb) + this.ui.bucketTree.AddTopLevelItem(top) + + this.refreshBucketTree(top) + this.ui.bucketTree.SetCurrentItem(top) + + this.ui.bucketTree.ExpandItem(top) +} + +func getDisplayName(qba string) string { + if qba == "" { + return "" + } + + ret := strconv.Quote(qba) + return ret[1 : len(ret)-1] +} + +func (this *MainWindow) refreshBucketTree(itm *qt.QTreeWidgetItem) { + ws := this.getSelection(itm) + + // Remove existing children + i := itm.ChildCount() + for i > 0 { + itm.TakeChild(i - 1).Delete() + i -= 1 + } + + err := Bolt_ListBuckets(ws.bdb, ws.browse, func(qba string) { + + child := qt.NewQTreeWidgetItem6(itm) // NewQTreeWidgetItem() + child.SetText(0, getDisplayName(qba)) + child.SetData(0, BinaryDataRole, qt.NewQVariant15(MakeQByteArray(qba))) + child.SetIcon(0, qt.NewQIcon4(":/rsrc/table.png")) + + itm.AddChild(child) + this.refreshBucketTree(child) + }) + if err != nil { + this.alert(fmt.Sprintf("Error listing buckets under %s: %s", strings.Join(ws.browse, `/`), err.Error())) + return + } +} + +func (this *MainWindow) on_actionExit_triggered() { + this.ui.MainWindow.Close() +} + +func (this *MainWindow) on_actionAbout_Qt_triggered() { + qt.QApplication_AboutQt() +} + +func (this *MainWindow) on_actionAbout_qbolt_triggered() { + qt.QMessageBox_About( + this.Widget(), + qt.QGuiApplication_ApplicationDisplayName(), + "QBolt "+Version+"
Graphical interface for managing Bolt databases

"+ + "- About BoltDB
"+ + "- FamFamFam "Silk" icon set
"+ + "- QBolt homepage
", + ) +} + +func (this *MainWindow) on_actionDisconnect_triggered() { + top := this.lastContextSelection + if top.Parent() != nil { + return // somehow we didn't select a top-level item + } + + bdb := GET_BDB(top) + + // Remove UI + this.ui.bucketTree.ClearSelection() + top.Delete() + + // Disconnect from DB + bdb.Close() +} + +func (this *MainWindow) on_bucketTree_customContextMenuRequested(pos *qt.QPoint) { + + itm := this.ui.bucketTree.ItemAt(pos) + if itm == nil { + return + } + + this.lastContextSelection = itm + + if itm.Parent() != nil { + // Child item, show the bucket menu + this.bucketContext.Popup(this.ui.bucketTree.Viewport().MapToGlobal(pos)) + + } else { + // Top-level item, show the database menu + this.databaseContext.Popup(this.ui.bucketTree.Viewport().MapToGlobal(pos)) + } +} + +func (this *MainWindow) on_actionRefresh_buckets_triggered() { + this.refreshBucketTree(this.lastContextSelection) +} + +func (this *MainWindow) on_bucketTree_currentItemChanged(current, previous *qt.QTreeWidgetItem) { + _ = previous // Q_UNUSED + if current == nil { + this.ui.stackedWidget.SetVisible(false) + return + } + + this.ui.stackedWidget.SetVisible(true) + + if current.Parent() == nil { + // Selected a database + this.ui.stackedWidget.SetCurrentWidget(this.ui.databasePage) + this.ui.databasePropertiesArea.Clear() + + bdb := GET_BDB(current) + stats, err := Bolt_DBStats(bdb) + if err != nil { + this.ui.databasePropertiesArea.SetPlainText(fmt.Sprintf("Error retrieving database statistics: %s", err.Error())) + } else { + this.ui.databasePropertiesArea.SetPlainText(stats) + } + + // Clean up foreign areas + this.ui.bucketPropertiesArea.Clear() + this.ui.bucketData.Clear() + + } else { + // Selected a bucket + + this.ui.stackedWidget.SetCurrentWidget(this.ui.bucketPage) + this.ui.bucketPropertiesArea.Clear() + + ws := this.getSelection(current) + + stats, err := Bolt_BucketStats(ws.bdb, ws.browse) + if err != nil { + this.ui.bucketPropertiesArea.SetPlainText(fmt.Sprintf("Error retrieving bucket statistics: %s", err.Error())) + } else { + this.ui.bucketPropertiesArea.SetPlainText(stats) + } + + // Load the data tab + this.refreshData(ws.bdb, ws.browse) + + // Clean up foreign areas + this.ui.databasePropertiesArea.Clear() + } +} + +func (this *MainWindow) refreshData(bdb *bolt.DB, browse []string) { + // Load the data tab + this.ui.bucketData.Clear() + + err := Bolt_ListItems(bdb, browse, func(lii ListItemInfo) error { + itm := qt.NewQTreeWidgetItem() + itm.SetText(0, getDisplayName(lii.Name)) + itm.SetData(0, BinaryDataRole, qt.NewQVariant15(MakeQByteArray(lii.Name))) + itm.SetText(1, fmt.Sprintf("%d", lii.DataLen)) + this.ui.bucketData.AddTopLevelItem(itm) + return nil + }) + + if err != nil { + this.alert(fmt.Sprintf("Error listing bucket content: %s", err.Error())) + return + } + + this.ui.bucketData.ResizeColumnToContents(0) + this.on_bucketData_itemSelectionChanged() +} + +func (this *MainWindow) on_actionClear_selection_triggered() { + this.ui.bucketTree.SetCurrentItem(nil) +} + +type windowSelection struct { + itm *qt.QTreeWidgetItem + top *qt.QTreeWidgetItem + browse []string + bdb *bolt.DB +} + +func (this *MainWindow) getWindowSelection() (windowSelection, bool) { + + itm := this.ui.bucketTree.CurrentItem() + if itm == nil { + return windowSelection{}, false // No selection + } + + return this.getSelection(itm), true +} + +func (this *MainWindow) getSelection(itm *qt.QTreeWidgetItem) windowSelection { + + top := itm + + var browse []string + for { + if top.Parent() == nil { + break + } else { + browse = append(browse, FromQByteArray(top.Data(0, BinaryDataRole).ToByteArray())) + top = top.Parent() + } + } + + ReverseSlice(browse) + + bdb := GET_BDB(top) + + return windowSelection{itm: itm, top: top, browse: browse, bdb: bdb} +} + +func (this *MainWindow) openEditor(bdb *bolt.DB, saveAs []string, saveAsKey string, currentContent []byte) { + iw := NewItemWindowUi() + + iw.contentArea.SetPlainText(string(currentContent)) + iw.ItemWindow.SetWindowTitle(getDisplayName(saveAsKey)) + iw.ItemWindow.SetWindowModality(qt.ApplicationModal) // we need this - otherwise we'll refresh a possibly-changed area after saving + iw.ItemWindow.OnFinished(func(exitCode int) { + if exitCode == int(qt.QDialog__Accepted) { + + err := Bolt_SetItem(bdb, saveAs, saveAsKey, iw.contentArea.ToPlainText()) + if err != nil { + this.alert(fmt.Sprintf("Error saving item content: %s", err.Error())) + } + + this.refreshData(bdb, saveAs) + } + iw.ItemWindow.DeleteLater() + }) + + iw.ItemWindow.Show() +} + +func (this *MainWindow) on_bucketData_doubleClicked(index *qt.QModelIndex) { + ws, ok := this.getWindowSelection() + if !ok { + return // no selection + } + + // Get item key + + model := index.Model() + key := FromQByteArray(model.Data2(model.Index(index.Row(), 0), BinaryDataRole).ToByteArray()) + + // DB lookup + content, err := Bolt_GetItem(ws.bdb, ws.browse, key) + if err != nil { + this.alert(fmt.Sprintf("Error loading item content: %s", err.Error())) + return + } + + this.openEditor(ws.bdb, ws.browse, key, []byte(content)) +} + +func (this *MainWindow) on_actionAdd_bucket_triggered() { + ws, ok := this.getWindowSelection() + if !ok { + return // no selection + } + + // Prompt for bucket name + + name := qt.QInputDialog_GetText(this.Widget(), "New bucket", "Enter a key for the new bucket:") + if len(name) == 0 { + return + } + + // Create + err := Bolt_CreateBucket(ws.bdb, ws.browse, name) + if err != nil { + this.alert(fmt.Sprintf("Error creating bucket: %s", err.Error())) + return + } + + // Refresh bucket list + this.refreshBucketTree(ws.itm) // sub-tree only + this.ui.bucketTree.ExpandItem(ws.itm) +} + +func (this *MainWindow) on_actionDelete_bucket_triggered() { + ws, ok := this.getWindowSelection() + if !ok { + return // no selection + } + + // Prompt for confirmation + bucketToDelete := FromQByteArray(ws.itm.Data(0, BinaryDataRole).ToByteArray()) + if qt.QMessageBox_Question4( + this.Widget(), + "Delete bucket", + fmt.Sprintf("Are you sure you want to remove the bucket '%s'?", getDisplayName(bucketToDelete)), + qt.QMessageBox__Yes, + qt.QMessageBox__Cancel, + ) != int(qt.QMessageBox__Yes) { + return + } + + parent := ws.itm.Parent() + + // One level down + + if len(ws.browse) > 0 { + ws.browse = ws.browse[0 : len(ws.browse)-1] + } + + err := Bolt_DeleteBucket(ws.bdb, ws.browse, bucketToDelete) + if err != nil { + this.alert(fmt.Sprintf("Error removing bucket: %s", err.Error())) + return + } + + // Refresh bucket list + this.refreshBucketTree(parent) // sub-tree only + this.ui.bucketTree.ExpandItem(parent) + this.ui.bucketTree.SetCurrentItem(parent) +} + +func (this *MainWindow) on_AddDataButton_clicked() { + ws, ok := this.getWindowSelection() + if !ok { + return // no selection + } + + // Prompt for bucket name + + name := qt.QInputDialog_GetText(this.Widget(), "New item", "Enter a key for the new item:") + if len(name) == 0 { + return + } + + this.openEditor(ws.bdb, ws.browse, name, []byte("")) +} + +func (this *MainWindow) on_DeleteDataButton_clicked() { + ws, ok := this.getWindowSelection() + if !ok { + return // no selection + } + + selection := this.ui.bucketData.SelectedItems() + if len(selection) == 0 { + return // nothing to do + } + + // Prompt for confirmation + if qt.QMessageBox_Question4(this.Widget(), "Delete items", fmt.Sprintf("Are you sure you want to remove %d item(s)?", len(selection)), qt.QMessageBox__Yes, qt.QMessageBox__Cancel) != int(qt.QMessageBox__Yes) { + return + } + + var i int = len(selection) + for i > 0 { + err := Bolt_DeleteItem(ws.bdb, ws.browse, FromQByteArray(selection[i-1].Data(0, BinaryDataRole).ToByteArray())) + if err != nil { + this.alert(fmt.Sprintf("Error removing item: %s", err.Error())) + return + } + i -= 1 + } + + this.refreshData(ws.bdb, ws.browse) +} + +func (this *MainWindow) on_bucketData_itemSelectionChanged() { + this.ui.DeleteDataButton.SetEnabled(len(this.ui.bucketData.SelectedItems()) > 0) +} diff --git a/qbolt/mainwindow.ui b/mainwindow.ui similarity index 99% rename from qbolt/mainwindow.ui rename to mainwindow.ui index 32e77b4..d122450 100644 --- a/qbolt/mainwindow.ui +++ b/mainwindow.ui @@ -269,12 +269,12 @@ 0 0 668 - 21 + 29 - Fi&le + &File diff --git a/mainwindow_ui.go b/mainwindow_ui.go new file mode 100644 index 0000000..2e585ba --- /dev/null +++ b/mainwindow_ui.go @@ -0,0 +1,325 @@ +// Generated by miqt-uic. To update this file, edit the .ui file in +// Qt Designer, and then run 'go generate'. +// +//go:generate miqt-uic -InFile mainwindow.ui -OutFile mainwindow_ui.go + +package main + +import ( + "github.com/mappu/miqt/qt" +) + +type MainWindowUi struct { + MainWindow *qt.QMainWindow + centralWidget *qt.QWidget + gridLayout *qt.QGridLayout + splitter *qt.QSplitter + bucketTree *qt.QTreeWidget + stackedWidget *qt.QStackedWidget + databasePage *qt.QWidget + gridLayout_4 *qt.QGridLayout + databaseTabWidget *qt.QTabWidget + databasePropertiesTab *qt.QWidget + gridLayout_2 *qt.QGridLayout + databasePropertiesArea *qt.QPlainTextEdit + bucketPage *qt.QWidget + gridLayout_3 *qt.QGridLayout + bucketTabWidget *qt.QTabWidget + bucketPropertiesTab *qt.QWidget + gridLayout_5 *qt.QGridLayout + bucketPropertiesArea *qt.QPlainTextEdit + bucketDataTab *qt.QWidget + gridLayout_6 *qt.QGridLayout + bucketData *qt.QTreeWidget + AddDataButton *qt.QPushButton + horizontalSpacer *qt.QSpacerItem + DeleteDataButton *qt.QPushButton + menuBar *qt.QMenuBar + menuFile *qt.QMenu + menuHelp *qt.QMenu + menuView *qt.QMenu + mainToolBar *qt.QToolBar + statusBar *qt.QStatusBar + actionAbout_qbolt *qt.QAction + actionAbout_Qt *qt.QAction + actionOpen_database *qt.QAction + actionExit *qt.QAction + actionDisconnect *qt.QAction + actionDelete_bucket *qt.QAction + actionRefresh_buckets *qt.QAction + actionClear_selection *qt.QAction + actionNew_database *qt.QAction + actionAdd_bucket *qt.QAction + actionOpen_database_as_read_only *qt.QAction +} + +// NewMainWindowUi creates all Qt widget classes for MainWindow. +func NewMainWindowUi() *MainWindowUi { + ui := &MainWindowUi{} + + ui.MainWindow = qt.NewQMainWindow2(nil) + ui.MainWindow.SetObjectName("MainWindow") + ui.MainWindow.Resize(668, 405) + ui.MainWindow.SetWindowTitle("QBolt") + icon0 := qt.NewQIcon() + icon0.AddFile4(":/rsrc/database_lightning.png", qt.NewQSize(), qt.QIcon__Normal, qt.QIcon__Off) + ui.MainWindow.SetWindowIcon(icon0) + + ui.actionAbout_qbolt = qt.NewQAction() + ui.actionAbout_qbolt.SetObjectName("actionAbout_qbolt") + icon1 := qt.NewQIcon() + icon1.AddFile4(":/rsrc/information.png", qt.NewQSize(), qt.QIcon__Normal, qt.QIcon__Off) + ui.actionAbout_qbolt.SetIcon(icon1) + + ui.actionAbout_Qt = qt.NewQAction() + ui.actionAbout_Qt.SetObjectName("actionAbout_Qt") + + ui.actionOpen_database = qt.NewQAction() + ui.actionOpen_database.SetObjectName("actionOpen_database") + icon2 := qt.NewQIcon() + icon2.AddFile4(":/rsrc/database.png", qt.NewQSize(), qt.QIcon__Normal, qt.QIcon__Off) + ui.actionOpen_database.SetIcon(icon2) + + ui.actionExit = qt.NewQAction() + ui.actionExit.SetObjectName("actionExit") + icon3 := qt.NewQIcon() + icon3.AddFile4(":/rsrc/door_out.png", qt.NewQSize(), qt.QIcon__Normal, qt.QIcon__Off) + ui.actionExit.SetIcon(icon3) + + ui.actionDisconnect = qt.NewQAction() + ui.actionDisconnect.SetObjectName("actionDisconnect") + icon4 := qt.NewQIcon() + icon4.AddFile4(":/rsrc/disconnect.png", qt.NewQSize(), qt.QIcon__Normal, qt.QIcon__Off) + ui.actionDisconnect.SetIcon(icon4) + + ui.actionDelete_bucket = qt.NewQAction() + ui.actionDelete_bucket.SetObjectName("actionDelete_bucket") + icon5 := qt.NewQIcon() + icon5.AddFile4(":/rsrc/table_delete.png", qt.NewQSize(), qt.QIcon__Normal, qt.QIcon__Off) + ui.actionDelete_bucket.SetIcon(icon5) + + ui.actionRefresh_buckets = qt.NewQAction() + ui.actionRefresh_buckets.SetObjectName("actionRefresh_buckets") + icon6 := qt.NewQIcon() + icon6.AddFile4(":/rsrc/arrow_refresh.png", qt.NewQSize(), qt.QIcon__Normal, qt.QIcon__Off) + ui.actionRefresh_buckets.SetIcon(icon6) + + ui.actionClear_selection = qt.NewQAction() + ui.actionClear_selection.SetObjectName("actionClear_selection") + + ui.actionNew_database = qt.NewQAction() + ui.actionNew_database.SetObjectName("actionNew_database") + icon7 := qt.NewQIcon() + icon7.AddFile4(":/rsrc/database_add.png", qt.NewQSize(), qt.QIcon__Normal, qt.QIcon__Off) + ui.actionNew_database.SetIcon(icon7) + + ui.actionAdd_bucket = qt.NewQAction() + ui.actionAdd_bucket.SetObjectName("actionAdd_bucket") + icon8 := qt.NewQIcon() + icon8.AddFile4(":/rsrc/table_add.png", qt.NewQSize(), qt.QIcon__Normal, qt.QIcon__Off) + ui.actionAdd_bucket.SetIcon(icon8) + + ui.actionOpen_database_as_read_only = qt.NewQAction() + ui.actionOpen_database_as_read_only.SetObjectName("actionOpen_database_as_read_only") + + ui.centralWidget = qt.NewQWidget2(ui.MainWindow.QWidget) + ui.centralWidget.SetObjectName("centralWidget") + + ui.gridLayout = qt.NewQGridLayout(ui.centralWidget) + ui.gridLayout.SetObjectName("gridLayout") + ui.gridLayout.SetContentsMargins(0, 0, 0, 0) + ui.gridLayout.SetSpacing(6) + + ui.splitter = qt.NewQSplitter3(ui.centralWidget) + ui.splitter.SetObjectName("splitter") + ui.splitter.SetOrientation(qt.Horizontal) + ui.splitter.SetChildrenCollapsible(false) + + ui.bucketTree = qt.NewQTreeWidget2(ui.splitter.QWidget) + ui.bucketTree.SetObjectName("bucketTree") + ui.bucketTree.SetContextMenuPolicy(qt.CustomContextMenu) + ui.bucketTree.SetUniformRowHeights(true) + ui.splitter.AddWidget(ui.bucketTree.QWidget) + + ui.stackedWidget = qt.NewQStackedWidget2(ui.splitter.QWidget) + ui.stackedWidget.SetObjectName("stackedWidget") + + ui.databasePage = qt.NewQWidget2(ui.stackedWidget.QWidget) + ui.databasePage.SetObjectName("databasePage") + + ui.gridLayout_4 = qt.NewQGridLayout(ui.databasePage) + ui.gridLayout_4.SetObjectName("gridLayout_4") + ui.gridLayout_4.SetContentsMargins(0, 0, 0, 0) + ui.gridLayout_4.SetSpacing(6) + + ui.databaseTabWidget = qt.NewQTabWidget2(ui.databasePage) + ui.databaseTabWidget.SetObjectName("databaseTabWidget") + + ui.databasePropertiesTab = qt.NewQWidget2(ui.databaseTabWidget.QWidget) + ui.databasePropertiesTab.SetObjectName("databasePropertiesTab") + + ui.gridLayout_2 = qt.NewQGridLayout(ui.databasePropertiesTab) + ui.gridLayout_2.SetObjectName("gridLayout_2") + ui.gridLayout_2.SetContentsMargins(3, 3, 3, 3) + ui.gridLayout_2.SetSpacing(6) + + ui.databasePropertiesArea = qt.NewQPlainTextEdit3(ui.databasePropertiesTab) + ui.databasePropertiesArea.SetObjectName("databasePropertiesArea") + ui.databasePropertiesArea.SetFrameShape(qt.QFrame__NoFrame) + ui.databasePropertiesArea.SetReadOnly(true) + + ui.gridLayout_2.AddWidget2(ui.databasePropertiesArea.QWidget, 0, 0) + icon9 := qt.NewQIcon() + icon9.AddFile4(":/rsrc/chart_bar.png", qt.NewQSize(), qt.QIcon__Normal, qt.QIcon__Off) + ui.databaseTabWidget.AddTab2(ui.databasePropertiesTab, icon9, "") + + ui.gridLayout_4.AddWidget2(ui.databaseTabWidget.QWidget, 0, 0) + ui.stackedWidget.AddWidget(ui.databasePage) + + ui.bucketPage = qt.NewQWidget2(ui.stackedWidget.QWidget) + ui.bucketPage.SetObjectName("bucketPage") + + ui.gridLayout_3 = qt.NewQGridLayout(ui.bucketPage) + ui.gridLayout_3.SetObjectName("gridLayout_3") + ui.gridLayout_3.SetContentsMargins(0, 0, 0, 0) + ui.gridLayout_3.SetSpacing(6) + + ui.bucketTabWidget = qt.NewQTabWidget2(ui.bucketPage) + ui.bucketTabWidget.SetObjectName("bucketTabWidget") + + ui.bucketPropertiesTab = qt.NewQWidget2(ui.bucketTabWidget.QWidget) + ui.bucketPropertiesTab.SetObjectName("bucketPropertiesTab") + + ui.gridLayout_5 = qt.NewQGridLayout(ui.bucketPropertiesTab) + ui.gridLayout_5.SetObjectName("gridLayout_5") + ui.gridLayout_5.SetContentsMargins(3, 3, 3, 3) + ui.gridLayout_5.SetSpacing(6) + + ui.bucketPropertiesArea = qt.NewQPlainTextEdit3(ui.bucketPropertiesTab) + ui.bucketPropertiesArea.SetObjectName("bucketPropertiesArea") + ui.bucketPropertiesArea.SetFrameShape(qt.QFrame__NoFrame) + ui.bucketPropertiesArea.SetReadOnly(true) + + ui.gridLayout_5.AddWidget2(ui.bucketPropertiesArea.QWidget, 0, 0) + icon10 := qt.NewQIcon() + icon10.AddFile4(":/rsrc/chart_bar.png", qt.NewQSize(), qt.QIcon__Normal, qt.QIcon__Off) + ui.bucketTabWidget.AddTab2(ui.bucketPropertiesTab, icon10, "") + + ui.bucketDataTab = qt.NewQWidget2(ui.bucketTabWidget.QWidget) + ui.bucketDataTab.SetObjectName("bucketDataTab") + + ui.gridLayout_6 = qt.NewQGridLayout(ui.bucketDataTab) + ui.gridLayout_6.SetObjectName("gridLayout_6") + ui.gridLayout_6.SetContentsMargins(3, 3, 3, 3) + ui.gridLayout_6.SetSpacing(6) + + ui.bucketData = qt.NewQTreeWidget2(ui.bucketDataTab) + ui.bucketData.SetObjectName("bucketData") + ui.bucketData.SetSelectionMode(qt.QAbstractItemView__ExtendedSelection) + ui.bucketData.SetIndentation(0) + ui.bucketData.SetRootIsDecorated(false) + ui.bucketData.SetUniformRowHeights(true) + ui.bucketData.SetItemsExpandable(false) + + ui.gridLayout_6.AddWidget3(ui.bucketData.QWidget, 0, 0, 1, 3) + + ui.AddDataButton = qt.NewQPushButton4(ui.bucketDataTab) + ui.AddDataButton.SetObjectName("AddDataButton") + icon11 := qt.NewQIcon() + icon11.AddFile4(":/rsrc/add.png", qt.NewQSize(), qt.QIcon__Normal, qt.QIcon__Off) + ui.AddDataButton.SetIcon(icon11) + + ui.gridLayout_6.AddWidget2(ui.AddDataButton.QWidget, 1, 0) + /* miqt-uic: no handler for spacer */ + + ui.DeleteDataButton = qt.NewQPushButton4(ui.bucketDataTab) + ui.DeleteDataButton.SetObjectName("DeleteDataButton") + icon12 := qt.NewQIcon() + icon12.AddFile4(":/rsrc/delete.png", qt.NewQSize(), qt.QIcon__Normal, qt.QIcon__Off) + ui.DeleteDataButton.SetIcon(icon12) + + ui.gridLayout_6.AddWidget2(ui.DeleteDataButton.QWidget, 1, 1) + icon13 := qt.NewQIcon() + icon13.AddFile4(":/rsrc/table.png", qt.NewQSize(), qt.QIcon__Normal, qt.QIcon__Off) + ui.bucketTabWidget.AddTab2(ui.bucketDataTab, icon13, "") + + ui.gridLayout_3.AddWidget2(ui.bucketTabWidget.QWidget, 0, 0) + ui.stackedWidget.AddWidget(ui.bucketPage) + ui.splitter.AddWidget(ui.stackedWidget.QWidget) + + ui.gridLayout.AddWidget2(ui.splitter.QWidget, 0, 0) + ui.MainWindow.SetCentralWidget(ui.centralWidget) // Set central widget + + ui.menuBar = qt.NewQMenuBar2(ui.MainWindow.QWidget) + ui.menuBar.SetObjectName("menuBar") + ui.menuBar.Resize(668, 29) + + ui.menuFile = qt.NewQMenu3(ui.menuBar.QWidget) + ui.menuFile.SetObjectName("menuFile") + ui.menuFile.QWidget.AddAction(ui.actionNew_database) + ui.menuFile.QWidget.AddAction(ui.actionOpen_database) + ui.menuFile.QWidget.AddAction(ui.actionOpen_database_as_read_only) + ui.menuFile.AddSeparator() + ui.menuFile.QWidget.AddAction(ui.actionExit) + + ui.menuHelp = qt.NewQMenu3(ui.menuBar.QWidget) + ui.menuHelp.SetObjectName("menuHelp") + ui.menuHelp.QWidget.AddAction(ui.actionAbout_qbolt) + ui.menuHelp.QWidget.AddAction(ui.actionAbout_Qt) + + ui.menuView = qt.NewQMenu3(ui.menuBar.QWidget) + ui.menuView.SetObjectName("menuView") + ui.menuView.QWidget.AddAction(ui.actionClear_selection) + ui.menuBar.AddMenu(ui.menuFile) + ui.menuBar.AddMenu(ui.menuView) + ui.menuBar.AddMenu(ui.menuHelp) + ui.MainWindow.SetMenuBar(ui.menuBar) + + ui.mainToolBar = qt.NewQToolBar4(ui.MainWindow.QWidget) + ui.mainToolBar.SetObjectName("mainToolBar") + ui.MainWindow.AddToolBar(qt.TopToolBarArea, ui.mainToolBar) + /* miqt-uic: no handler for mainToolBar attribute 'toolBarBreak' */ + ui.mainToolBar.QWidget.AddAction(ui.actionNew_database) + ui.mainToolBar.QWidget.AddAction(ui.actionOpen_database) + ui.mainToolBar.AddSeparator() + + ui.statusBar = qt.NewQStatusBar2(ui.MainWindow.QWidget) + ui.statusBar.SetObjectName("statusBar") + ui.MainWindow.SetStatusBar(ui.statusBar) + + ui.Retranslate() + + ui.stackedWidget.SetCurrentIndex(0) + ui.databaseTabWidget.SetCurrentIndex(0) + ui.bucketTabWidget.SetCurrentIndex(0) + + return ui +} + +// Retranslate reapplies all text translations. +func (ui *MainWindowUi) Retranslate() { + ui.actionAbout_qbolt.SetText(qt.QMainWindow_Tr("&About QBolt")) + ui.actionAbout_Qt.SetText(qt.QMainWindow_Tr("About &Qt")) + ui.actionOpen_database.SetText(qt.QMainWindow_Tr("&Open database...")) + ui.actionOpen_database.SetShortcut(qt.NewQKeySequence2(qt.QMainWindow_Tr("Ctrl+O"))) + ui.actionExit.SetText(qt.QMainWindow_Tr("&Exit")) + ui.actionDisconnect.SetText(qt.QMainWindow_Tr("Disconnect")) + ui.actionDelete_bucket.SetText(qt.QMainWindow_Tr("Delete bucket")) + ui.actionRefresh_buckets.SetText(qt.QMainWindow_Tr("Refresh buckets")) + ui.actionClear_selection.SetText(qt.QMainWindow_Tr("&Clear selection")) + ui.actionNew_database.SetText(qt.QMainWindow_Tr("&New database...")) + ui.actionAdd_bucket.SetText(qt.QMainWindow_Tr("Add bucket...")) + ui.actionOpen_database_as_read_only.SetText(qt.QMainWindow_Tr("Open database as read-only...")) + ui.bucketTree.HeaderItem().SetText(0, qt.QTreeWidget_Tr("Bucket")) + ui.databaseTabWidget.SetTabText(ui.databaseTabWidget.IndexOf(ui.databasePropertiesTab), qt.QTabWidget_Tr("Database")) + ui.databasePropertiesArea.SetPlainText(qt.QWidget_Tr("No selection")) + ui.bucketTabWidget.SetTabText(ui.bucketTabWidget.IndexOf(ui.bucketPropertiesTab), qt.QTabWidget_Tr("Bucket")) + ui.bucketTabWidget.SetTabText(ui.bucketTabWidget.IndexOf(ui.bucketDataTab), qt.QTabWidget_Tr("Data")) + ui.bucketData.HeaderItem().SetText(0, qt.QTreeWidget_Tr("Key")) + ui.bucketData.HeaderItem().SetText(1, qt.QTreeWidget_Tr("Data length")) + ui.AddDataButton.SetText(qt.QWidget_Tr("Add...")) + ui.DeleteDataButton.SetText(qt.QWidget_Tr("Delete...")) + ui.menuFile.SetTitle(qt.QMenuBar_Tr("&File")) + ui.menuHelp.SetTitle(qt.QMenuBar_Tr("Help")) + ui.menuView.SetTitle(qt.QMenuBar_Tr("&View")) +} diff --git a/qbolt/boltdb.cpp b/qbolt/boltdb.cpp deleted file mode 100644 index 2aa8db3..0000000 --- a/qbolt/boltdb.cpp +++ /dev/null @@ -1,219 +0,0 @@ -#include "boltdb.h" - -#include - -BoltDB::BoltDB() -{ - -} - -BoltDB* BoltDB::createFrom(QString filePath, bool readOnly, QString &errorOut) -{ - QByteArray filePathBytes(filePath.toUtf8()); - GoString filePathGS = Interop::toGoString_WeakRef(&filePathBytes); - - auto open_ret = ::Bolt_Open(readOnly, filePathGS); - if (open_ret.r2 != 0) { - errorOut = QString::fromUtf8(open_ret.r1, open_ret.r2); - free(open_ret.r1); - return nullptr; - } - - BoltDB *ret = new BoltDB(); - ret->gmsDbRef = open_ret.r0; - return ret; -} - -static const int ERROR_AND_STOP_CALLING = 100; -static const int ERROR_AND_KEEP_CALLING = 101; -static const int FINISHED_OK = 102; -static const int REAL_MESSAGE = 103; - -static bool handleTriple(int r0, char* r1, int64_t r2, QString& errorOut) { - if (r0 == ERROR_AND_STOP_CALLING) { - errorOut = QString::fromUtf8(r1, r2); - free(r1); - return false; - - } else if (r0 == FINISHED_OK) { - return true; - - } else { - // ?? unreachable - return false; - - } -} - -bool BoltDB::addBucket(const QList& bucketPath, QByteArray bucketName, QString& errorOut) -{ - GoSliceManagedWrapper browse(bucketPath); - GoString bucketNameGS = Interop::toGoString_WeakRef(&bucketName); - auto resp = ::Bolt_CreateBucket(this->gmsDbRef, browse.slice, bucketNameGS); - - return handleTriple(resp.r0, resp.r1, resp.r2, errorOut); -} - -bool BoltDB::deleteBucket(const QList& bucketPath, QByteArray bucketName, QString& errorOut) -{ - GoSliceManagedWrapper browse(bucketPath); - GoString bucketNameGS = Interop::toGoString_WeakRef(&bucketName); - auto resp = ::Bolt_DeleteBucket(this->gmsDbRef, browse.slice, bucketNameGS); - - return handleTriple(resp.r0, resp.r1, resp.r2, errorOut); -} - -bool BoltDB::setItem(const QList& bucketPath, QByteArray keyName, QByteArray value, QString& errorOut) -{ - GoSliceManagedWrapper browse(bucketPath); - GoString keyNameGS = Interop::toGoString_WeakRef(&keyName); - GoString valueGS = Interop::toGoString_WeakRef(&value); - auto resp = ::Bolt_SetItem(this->gmsDbRef, browse.slice, keyNameGS, valueGS); - - return handleTriple(resp.r0, resp.r1, resp.r2, errorOut); -} - -bool BoltDB::deleteItem(const QList& bucketPath, QByteArray keyName, QString& errorOut) -{ - GoSliceManagedWrapper browse(bucketPath); - GoString keyNameGS = Interop::toGoString_WeakRef(&keyName); - auto resp = ::Bolt_DeleteItem(this->gmsDbRef, browse.slice, keyNameGS); - - return handleTriple(resp.r0, resp.r1, resp.r2, errorOut); -} - - -bool BoltDB::listBucketsAtRoot(QString& errorOut, NameReciever cb) -{ - auto listJob = ::Bolt_ListBucketsAtRoot(this->gmsDbRef); - return pumpNext(listJob, errorOut, cb); -} - -bool BoltDB::listBuckets(const QList& bucketPath, QString& errorOut, NameReciever cb) -{ - if (bucketPath.size() == 0) { - return listBucketsAtRoot(errorOut, cb); - } - - GoSliceManagedWrapper browse(bucketPath); - auto listJob = ::Bolt_ListBuckets(this->gmsDbRef, browse.slice); - return pumpNext(listJob, errorOut, cb); -} - -bool BoltDB::listKeys(const QList& bucketPath, QString& errorOut, std::function cb) -{ - GoSliceManagedWrapper browse(bucketPath); - auto listJob = ::Bolt_ListItems(this->gmsDbRef, browse.slice); - return pumpNext(listJob, errorOut, [=](QByteArray b) { - // First 8 bytes are little-endian uint64 len - int64_t dataLen = qFromLittleEndian(b.mid(0, 8)); - cb(b.mid(8), dataLen); - }); -} - -bool BoltDB::getData(const QList& bucketPath, QByteArray key, std::function onSuccess, std::function onError) -{ - GoSliceManagedWrapper browse(bucketPath); - GoString keyGS = Interop::toGoString_WeakRef(&key); - auto resp = ::Bolt_GetItem(this->gmsDbRef, browse.slice, keyGS); - - if (resp.r0 == ERROR_AND_STOP_CALLING) { - onError(QString::fromUtf8(resp.r1, resp.r2)); - free(resp.r1); - return false; - - } else if (resp.r0 == REAL_MESSAGE) { - onSuccess(QByteArray(resp.r1, resp.r2)); - free(resp.r1); - return true; - - } else { - // ?? unreachable - return false; - - } -} - -bool BoltDB::pumpNext(GoInt64 jobRef, QString& errorOut, NameReciever cb) -{ - errorOut.clear(); - - for(;;) { - auto gnr = ::GetNext(jobRef); - - if (gnr.r0 == ERROR_AND_STOP_CALLING) { - errorOut.append(QString::fromUtf8(gnr.r1, gnr.r2)); // log error - free(gnr.r1); - break; // done - - } else if (gnr.r0 == ERROR_AND_KEEP_CALLING) { - errorOut.append(QString::fromUtf8(gnr.r1, gnr.r2)); // log error - free(gnr.r1); - continue; - - } else if (gnr.r0 == FINISHED_OK) { - // Once we hit this, the go-side will clean up the channel / associated goroutines - break; - - } else if (gnr.r0 == REAL_MESSAGE) { - cb(QByteArray(gnr.r1, gnr.r2)); - free(gnr.r1); - continue; - } - } - - return (errorOut.length() == 0); -} - -bool BoltDB::getStatsJSON(std::function onSuccess, std::function onError) -{ - auto statresp = Bolt_DBStats(this->gmsDbRef); - - if (statresp.r0 == ERROR_AND_STOP_CALLING) { - onError(QString::fromUtf8(statresp.r1, statresp.r2)); - free(statresp.r1); - return false; - - } else if (statresp.r0 == REAL_MESSAGE) { - onSuccess(QByteArray(statresp.r1, statresp.r2)); - free(statresp.r1); - return true; - - } else { - // ?? shouldn't be reachable - return false; - } -} - -bool BoltDB::getBucketStatsJSON(const QList& bucketPath, std::function onSuccess, std::function onError) -{ - GoSliceManagedWrapper sliceWrapper(bucketPath); - auto statresp = Bolt_BucketStats(this->gmsDbRef, sliceWrapper.slice); - - if (statresp.r0 == ERROR_AND_STOP_CALLING) { - QString err = QString::fromUtf8(statresp.r1, statresp.r2); - free(statresp.r1); - onError(err); - return false; - - } else if (statresp.r0 == REAL_MESSAGE) { - onSuccess(QByteArray(statresp.r1, statresp.r2)); - free(statresp.r1); - return true; - - } else { - // ?? shouldn't be reachable - return false; - } -} - -BoltDB::~BoltDB() -{ - auto err = ::Bolt_Close(this->gmsDbRef); - if (err.r1 != 0) { - // Error closing database! - // Need to display an alert... somewhere - - free(err.r0); - } -} diff --git a/qbolt/boltdb.h b/qbolt/boltdb.h deleted file mode 100644 index 65fe09e..0000000 --- a/qbolt/boltdb.h +++ /dev/null @@ -1,46 +0,0 @@ -#ifndef BOLTDB_H -#define BOLTDB_H - -#include "interop.h" -#include - -typedef std::function NameReciever; - -class BoltDB -{ -protected: - BoltDB(); - - GoInt64 gmsDbRef; - -public: - static BoltDB* createFrom(QString filePath, bool readOnly, QString &errorOut); - - bool listBucketsAtRoot(QString& errorOut, NameReciever cb); - - bool listBuckets(const QList& bucketPath, QString& errorOut, NameReciever cb); - - bool addBucket(const QList& bucketPath, QByteArray bucketName, QString& errorOut); - - bool deleteBucket(const QList& bucketPath, QByteArray bucketName, QString& errorOut); - - bool setItem(const QList& bucketPath, QByteArray keyName, QByteArray value, QString& errorOut); - - bool deleteItem(const QList& bucketPath, QByteArray keyName, QString& errorOut); - - bool listKeys(const QList& bucketPath, QString& errorOut, std::function cb); - - bool getData(const QList& bucketPath, QByteArray key, std::function onSuccess, std::function onError); - - bool getStatsJSON(std::function onSuccess, std::function onError); - - bool getBucketStatsJSON(const QList& bucketPath, std::function onSuccess, std::function onError); - - ~BoltDB(); - -protected: - - bool pumpNext(GoInt64 jobRef, QString& errorOut, NameReciever cb); -}; - -#endif // BOLTDB_H diff --git a/qbolt/interop.cpp b/qbolt/interop.cpp deleted file mode 100644 index a4e173f..0000000 --- a/qbolt/interop.cpp +++ /dev/null @@ -1,42 +0,0 @@ -#include "interop.h" - -#include - -Interop::Interop() -{ - -} - -GoString Interop::toGoString_WeakRef(QByteArray *qba) { - return GoString{qba->data(), qba->length()}; -} - -int64_t Interop::GetMagic() { - return ::GetMagic(); -} - -// - -GoSliceManagedWrapper::GoSliceManagedWrapper(const QList& qsl) : - rawStrings(), - slice(), - strings(nullptr) -{ - rawStrings.reserve(qsl.size()); - strings = new GoString[qsl.size()]; - - for (int i = 0; i < qsl.size(); ++i) { - rawStrings.push_back( qsl.at(i) ); - strings[i].p = rawStrings[i].data(); - strings[i].n = rawStrings[i].size(); - } - - slice.data = static_cast(strings); - slice.len = qsl.size(); // * sizeof(GoString); - slice.cap = slice.len; -} - -GoSliceManagedWrapper::~GoSliceManagedWrapper() -{ - delete[] strings; -} diff --git a/qbolt/interop.h b/qbolt/interop.h deleted file mode 100644 index fabb163..0000000 --- a/qbolt/interop.h +++ /dev/null @@ -1,31 +0,0 @@ -#ifndef INTEROP_H -#define INTEROP_H - -#include "qbolt_cgo.h" -#include -#include - -class GoSliceManagedWrapper { - Q_DISABLE_COPY(GoSliceManagedWrapper) - -public: - GoSliceManagedWrapper(const QList& qsl); - ~GoSliceManagedWrapper(); -protected: - QList rawStrings; -public: - GoSlice slice; - GoString *strings; -}; - -class Interop -{ -public: - Interop(); - - static GoString toGoString_WeakRef(QByteArray *qba); - - static int64_t GetMagic(); -}; - -#endif // INTEROP_H diff --git a/qbolt/itemwindow.cpp b/qbolt/itemwindow.cpp deleted file mode 100644 index 6ba97bf..0000000 --- a/qbolt/itemwindow.cpp +++ /dev/null @@ -1,18 +0,0 @@ -#include "itemwindow.h" -#include "ui_itemwindow.h" - -ItemWindow::ItemWindow(QWidget *parent) : - QDialog(parent), - ui(new Ui::ItemWindow) -{ - ui->setupUi(this); -} - -ItemWindow::~ItemWindow() -{ - delete ui; -} - -QPlainTextEdit* ItemWindow::ContentArea() const { - return ui->contentArea; -} diff --git a/qbolt/itemwindow.h b/qbolt/itemwindow.h deleted file mode 100644 index 326ae52..0000000 --- a/qbolt/itemwindow.h +++ /dev/null @@ -1,25 +0,0 @@ -#ifndef ITEMWINDOW_H -#define ITEMWINDOW_H - -#include -#include - -namespace Ui { -class ItemWindow; -} - -class ItemWindow : public QDialog -{ - Q_OBJECT - -public: - explicit ItemWindow(QWidget *parent = 0); - ~ItemWindow(); - - QPlainTextEdit* ContentArea() const; - -private: - Ui::ItemWindow *ui; -}; - -#endif // ITEMWINDOW_H diff --git a/qbolt/main.cpp b/qbolt/main.cpp deleted file mode 100644 index 7ee0f34..0000000 --- a/qbolt/main.cpp +++ /dev/null @@ -1,23 +0,0 @@ -#include "mainwindow.h" -#include -#include "interop.h" - -#include - -int main(int argc, char *argv[]) -{ - int64_t magic = Interop::GetMagic(); - if (magic != 0x10203040) { - qDebug() << "bad magic " << magic; - return 1; - } - - QApplication a(argc, argv); - QApplication::setApplicationDisplayName("QBolt"); - QApplication::setWindowIcon(QIcon(":/rsrc/database_lightning.png")); - - MainWindow w; - w.show(); - - return a.exec(); -} diff --git a/qbolt/mainwindow.cpp b/qbolt/mainwindow.cpp deleted file mode 100644 index d149055..0000000 --- a/qbolt/mainwindow.cpp +++ /dev/null @@ -1,478 +0,0 @@ -#include "mainwindow.h" -#include "ui_mainwindow.h" -#include "itemwindow.h" -#include "boltdb.h" - -#include -#include -#include -#include - -MainWindow::MainWindow(QWidget *parent) : - QMainWindow(parent), - ui(new Ui::MainWindow) -{ - ui->setupUi(this); - - on_bucketTree_currentItemChanged(nullptr, nullptr); - - 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); -} - -MainWindow::~MainWindow() -{ - delete ui; -} - -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(static_cast(bdb))) -#define GET_BDB(top) static_cast( top->data(0, BdbPointerRole).value() ) - -void MainWindow::on_actionNew_database_triggered() -{ - QString file = QFileDialog::getSaveFileName(this, tr("Save new bolt database as...")); - if (file.length()) { - openDatabase(file, false); - } -} - -void MainWindow::on_actionOpen_database_triggered() -{ - QString file = QFileDialog::getOpenFileName(this, tr("Select bolt database...")); - if (file.length()) { - openDatabase(file, false); - } -} - -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 - QString error; - auto *bdb = BoltDB::createFrom(file, readOnly, error); - if (bdb == nullptr) { - QMessageBox qmb; - qmb.setText(tr("Error opening database: %1").arg(error)); - qmb.exec(); - return; - } - - QTreeWidgetItem *top = new QTreeWidgetItem(); - top->setText(0, QFileInfo(file).fileName()); - top->setIcon(0, QIcon(":/rsrc/database.png")); - SET_BDB(top, bdb); - ui->bucketTree->addTopLevelItem(top); - - refreshBucketTree(top); - ui->bucketTree->setCurrentItem(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) -{ - QTreeWidgetItem *top = itm; - QList browsePath; - while(top->parent() != nullptr) { - browsePath.push_front(top->data(0, BinaryDataRole).toByteArray()); - top = top->parent(); - } - - // Remove existing children - for (int i = itm->childCount(); i --> 0;) { - delete itm->takeChild(i); - } - - auto *bdb = GET_BDB(top); - - QString error; - bool ok = bdb->listBuckets( - browsePath, - error, - [=](QByteArray qba){ - QTreeWidgetItem *child = new QTreeWidgetItem(); - child->setText(0, getDisplayName(qba)); - child->setData(0, BinaryDataRole, qba); - child->setIcon(0, QIcon(":/rsrc/table.png")); - itm->addChild(child); - - refreshBucketTree(child); - } - ); - - if (!ok) { - QMessageBox qmb; - qmb.setText(tr("Error listing buckets: %1").arg(error)); - qmb.exec(); - // (continue) - } -} - -void MainWindow::on_actionExit_triggered() -{ - close(); -} - -void MainWindow::on_actionAbout_Qt_triggered() -{ - QApplication::aboutQt(); -} - -void MainWindow::on_actionAbout_qbolt_triggered() -{ - QMessageBox::about( - this, - QApplication::applicationDisplayName(), - "QBolt
Graphical interface for managing Bolt databases

" - "- About BoltDB
" - "- FamFamFam "Silk" icon set
" - "- QBolt homepage
" - ); -} - -void MainWindow::on_actionDisconnect_triggered() -{ - QTreeWidgetItem *top = lastContextSelection; - if (top->parent()) { - return; // somehow we didn't select a top-level item - } - - auto *bdb = GET_BDB(top); - - // Remove UI - ui->bucketTree->clearSelection(); - delete top; - - // Disconnect from DB - delete bdb; -} - -void MainWindow::on_bucketTree_customContextMenuRequested(const QPoint &pos) -{ - auto *itm = ui->bucketTree->itemAt(pos); - if (itm == nullptr) { - return; - } - - lastContextSelection = itm; - - if (itm->parent() != nullptr) { - // Child item, show the bucket menu - bucketContext->popup(ui->bucketTree->mapToGlobal(pos)); - - } else { - // Top-level item, show the database menu - databaseContext->popup(ui->bucketTree->mapToGlobal(pos)); - } -} - -void MainWindow::on_actionRefresh_buckets_triggered() -{ - refreshBucketTree(lastContextSelection); -} - -void MainWindow::on_bucketTree_currentItemChanged(QTreeWidgetItem *current, QTreeWidgetItem *previous) -{ - Q_UNUSED(previous); - if (current == nullptr) { - ui->stackedWidget->setVisible(false); - return; - } - - ui->stackedWidget->setVisible(true); - - if (current->parent() == nullptr) { - // Selected a database - ui->stackedWidget->setCurrentWidget(ui->databasePage); - ui->databasePropertiesArea->clear(); - - auto *bdb = GET_BDB(current); - bdb->getStatsJSON( - [=](QByteArray j) { - auto doc = QJsonDocument::fromJson(j); - ui->databasePropertiesArea->setPlainText(QString::fromUtf8(doc.toJson(QJsonDocument::Indented))); - }, - [=](QString error) { - ui->databasePropertiesArea->setPlainText(tr("Error retrieving database statistics: %1").arg(error)); - } - ); - - // Clean up foreign areas - ui->bucketPropertiesArea->clear(); - ui->bucketData->clear(); - - } else { - // Selected a bucket - - ui->stackedWidget->setCurrentWidget(ui->bucketPage); - ui->bucketPropertiesArea->clear(); - - QList browse; - QTreeWidgetItem *top = current; - while (top->parent() != nullptr) { - browse.push_front(top->data(0, BinaryDataRole).toByteArray()); - top = top->parent(); - } - auto *bdb = GET_BDB(top); - - bdb->getBucketStatsJSON( - browse, - [=](QByteArray j) { - auto doc = QJsonDocument::fromJson(j); - ui->bucketPropertiesArea->setPlainText(QString::fromUtf8(doc.toJson(QJsonDocument::Indented))); - }, - [=](QString error) { - ui->bucketPropertiesArea->setPlainText(tr("Error retrieving bucket statistics: %1").arg(error)); - } - ); - - // Load the data tab - refreshData(bdb, browse); - - // Clean up foreign areas - ui->databasePropertiesArea->clear(); - } -} - -void MainWindow::refreshData(BoltDB *bdb, const QList& browse) -{ - // Load the data tab - ui->bucketData->clear(); - QString err; - bool ok = bdb->listKeys(browse, err, [=](QByteArray name, int64_t dataLen) { - auto *itm = new QTreeWidgetItem(); - itm->setText(0, getDisplayName(name)); - itm->setData(0, BinaryDataRole, name); - itm->setText(1, QString("%1").arg(dataLen)); - ui->bucketData->addTopLevelItem(itm); - }); - - if (! ok) { - QMessageBox qmb; - qmb.setText(tr("Error listing bucket content: %1").arg(err)); - qmb.exec(); - } - - ui->bucketData->resizeColumnToContents(0); - on_bucketData_itemSelectionChanged(); -} - -void MainWindow::on_actionClear_selection_triggered() -{ - ui->bucketTree->setCurrentItem(nullptr); -} - -#define GET_ITM_TOP_BROWSE_BDB \ - QTreeWidgetItem* itm = ui->bucketTree->currentItem(); \ - if (itm == nullptr) { \ - return; \ - } \ - QTreeWidgetItem* top = itm; \ - QList browse; \ - while(top->parent() != nullptr) { \ - browse.push_front(top->data(0, BinaryDataRole).toByteArray()); \ - top = top->parent(); \ - } \ - auto *bdb = GET_BDB(top); - -void MainWindow::openEditor(BoltDB *bdb, const QList& saveAs, QByteArray saveAsKey, QByteArray currentContent) -{ - auto iw = new ItemWindow(); - iw->ContentArea()->setPlainText(QString::fromUtf8(currentContent)); - iw->setWindowTitle(QString::fromUtf8(saveAsKey)); - iw->setWindowModality(Qt::ApplicationModal); // we need this - otherwise we'll refresh a possibly-changed area after saving - connect(iw, &ItemWindow::finished, iw, [=](int exitCode){ - if (exitCode == ItemWindow::Accepted) { - QString err; - if (! bdb->setItem(saveAs, saveAsKey, iw->ContentArea()->toPlainText().toUtf8(), err)) { - QMessageBox qmb; - qmb.setText(tr("Error saving item content: %1").arg(err)); - qmb.exec(); - } - - refreshData(bdb, saveAs); - } - iw->deleteLater(); - }); - - iw->show(); -} - -void MainWindow::on_bucketData_doubleClicked(const QModelIndex &index) -{ - GET_ITM_TOP_BROWSE_BDB; - - // Get item key - - auto model = index.model(); - const QByteArray& key = model->data(model->index(index.row(), 0), BinaryDataRole).toByteArray(); - - // DB lookup - - bdb->getData( - browse, - key, - [=](QByteArray content) { - openEditor(bdb, browse, key, content); - }, - [=](QString error) { - QMessageBox qmb; - qmb.setText(tr("Error loading item content: %1").arg(error)); - qmb.exec(); - } - ); - -} - -void MainWindow::on_actionAdd_bucket_triggered() -{ - GET_ITM_TOP_BROWSE_BDB; - - // 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() -{ - GET_ITM_TOP_BROWSE_BDB; - - // Prompt for confirmation - 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(getDisplayName(bucketToDelete)), - QMessageBox::Yes, - QMessageBox::Cancel - ) != QMessageBox::Yes - ) { - return; - } - - QTreeWidgetItem* parent = itm->parent(); - - // One level down - - browse.pop_back(); - - QString err; - if (! bdb->deleteBucket(browse, bucketToDelete, err)) { - QMessageBox qmb; - qmb.setText(tr("Error removing bucket: %1").arg(err)); - qmb.exec(); - return; - } - - // Refresh bucket list - refreshBucketTree(parent); // sub-tree only - ui->bucketTree->expandItem(parent); - ui->bucketTree->setCurrentItem(parent); -} - -void MainWindow::on_AddDataButton_clicked() -{ - GET_ITM_TOP_BROWSE_BDB; - - // Prompt for bucket name - - QString name = QInputDialog::getText(this, tr("New item"), tr("Enter a key for the new item:")); - if (name.length() == 0) { - return; - } - - openEditor(bdb, browse, name.toUtf8(), QByteArray()); -} - -void MainWindow::on_DeleteDataButton_clicked() -{ - GET_ITM_TOP_BROWSE_BDB; - - auto selection = ui->bucketData->selectedItems(); - if (selection.length() == 0) { - return; // nothing to do - } - - // Prompt for confirmation - if (QMessageBox::question(this, tr("Delete items"), tr("Are you sure you want to remove %1 item(s)?").arg(selection.length()), QMessageBox::Yes, QMessageBox::Cancel) != QMessageBox::Yes) { - return; - } - - QString err; - for (int i = selection.length(); i-->0;) { - if (! bdb->deleteItem(browse, selection[i]->data(0, BinaryDataRole).toByteArray(), err)) { - QMessageBox qmb; - qmb.setText(tr("Error removing item: %1").arg(err)); - qmb.exec(); - } - } - - refreshData(bdb, browse); -} - -void MainWindow::on_bucketData_itemSelectionChanged() -{ - ui->DeleteDataButton->setEnabled( (ui->bucketData->selectedItems().size() > 0) ); -} diff --git a/qbolt/mainwindow.h b/qbolt/mainwindow.h deleted file mode 100644 index ae541c8..0000000 --- a/qbolt/mainwindow.h +++ /dev/null @@ -1,70 +0,0 @@ -#ifndef MAINWINDOW_H -#define MAINWINDOW_H - -#include "boltdb.h" -#include -#include -#include - -namespace Ui { -class MainWindow; -} - -class MainWindow : public QMainWindow -{ - Q_OBJECT - -public: - explicit MainWindow(QWidget *parent = 0); - ~MainWindow(); - -private slots: - void on_actionOpen_database_triggered(); - - void on_actionExit_triggered(); - - void on_actionAbout_Qt_triggered(); - - void on_actionAbout_qbolt_triggered(); - - void on_actionDisconnect_triggered(); - - void on_bucketTree_customContextMenuRequested(const QPoint &pos); - - void on_actionRefresh_buckets_triggered(); - - void on_bucketTree_currentItemChanged(QTreeWidgetItem *current, QTreeWidgetItem *previous); - - void on_actionClear_selection_triggered(); - - void on_bucketData_doubleClicked(const QModelIndex &index); - - void on_actionNew_database_triggered(); - - void on_actionAdd_bucket_triggered(); - - void on_actionDelete_bucket_triggered(); - - void on_AddDataButton_clicked(); - - void on_DeleteDataButton_clicked(); - - void on_bucketData_itemSelectionChanged(); - - void on_actionOpen_database_as_read_only_triggered(); - -protected: - void openDatabase(QString file, bool readOnly); - void refreshBucketTree(QTreeWidgetItem* top); - void refreshData(BoltDB *bdb, const QList& browse); - void openEditor(BoltDB *bdb, const QList& saveAs, QByteArray saveAsKey, QByteArray currentContent); - -private: - Ui::MainWindow *ui; - - QMenu *databaseContext; - QMenu *bucketContext; - QTreeWidgetItem* lastContextSelection; -}; - -#endif // MAINWINDOW_H diff --git a/qbolt/qbolt.pro b/qbolt/qbolt.pro deleted file mode 100644 index 402b58d..0000000 --- a/qbolt/qbolt.pro +++ /dev/null @@ -1,48 +0,0 @@ -#------------------------------------------------- -# -# Project created by QtCreator 2017-05-15T19:38:33 -# -#------------------------------------------------- - -QT += core gui - -greaterThan(QT_MAJOR_VERSION, 4): QT += widgets - -TARGET = qbolt -TEMPLATE = app - -# Enforce Qt deprecations -DEFINES += QT_DEPRECATED_WARNINGS -DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 - -win32: { - # for some reason, qbolt_cgo.h never realises that Q_OS_WIN is defined for win32 builds... weird - DEFINES += CGO_WINDOWS - QMAKE_LIBS += $$_PRO_FILE_PWD_/../build/win32/qbolt.a - QMAKE_LIBS += -lntdll - - RC_ICONS = rsrc/qbolt.ico -} - -linux: { - QMAKE_LIBS += $$_PRO_FILE_PWD_/../build/linux/qbolt.a - QMAKE_LIBS += -lpthread -} - -SOURCES += main.cpp\ - mainwindow.cpp \ - interop.cpp \ - boltdb.cpp \ - itemwindow.cpp - -HEADERS += mainwindow.h \ - interop.h \ - boltdb.h \ - qbolt_cgo.h \ - itemwindow.h - -FORMS += mainwindow.ui \ - itemwindow.ui - -RESOURCES += \ - resources.qrc diff --git a/qbolt/qbolt_cgo.h b/qbolt/qbolt_cgo.h deleted file mode 100644 index 1c000d1..0000000 --- a/qbolt/qbolt_cgo.h +++ /dev/null @@ -1,10 +0,0 @@ -#ifndef QBOLT_CGO_H -#define QBOLT_CGO_H - -#if defined(Q_OS_WIN) || defined(CGO_WINDOWS) -#include "../build/win32/qbolt.h" -#else -#include "../build/linux/qbolt.h" -#endif - -#endif // QBOLT_CGO_H diff --git a/resources.go b/resources.go new file mode 100644 index 0000000..20abcf2 --- /dev/null +++ b/resources.go @@ -0,0 +1,17 @@ +package main + +//go:generate miqt-rcc.sh resources.qrc + +import ( + "embed" + + "github.com/mappu/miqt/qt" +) + +//go:embed resources.rcc +var _resourceRcc []byte + +func init() { + _ = embed.FS{} + qt.QResource_RegisterResourceWithRccData(&_resourceRcc[0]) +} diff --git a/qbolt/resources.qrc b/resources.qrc similarity index 100% rename from qbolt/resources.qrc rename to resources.qrc diff --git a/resources.rcc b/resources.rcc new file mode 100644 index 0000000..0f693c5 Binary files /dev/null and b/resources.rcc differ diff --git a/qbolt/rsrc/add.png b/rsrc/add.png similarity index 100% rename from qbolt/rsrc/add.png rename to rsrc/add.png diff --git a/qbolt/rsrc/arrow_refresh.png b/rsrc/arrow_refresh.png similarity index 100% rename from qbolt/rsrc/arrow_refresh.png rename to rsrc/arrow_refresh.png diff --git a/qbolt/rsrc/chart_bar.png b/rsrc/chart_bar.png similarity index 100% rename from qbolt/rsrc/chart_bar.png rename to rsrc/chart_bar.png diff --git a/qbolt/rsrc/database.png b/rsrc/database.png similarity index 100% rename from qbolt/rsrc/database.png rename to rsrc/database.png diff --git a/qbolt/rsrc/database_add.png b/rsrc/database_add.png similarity index 100% rename from qbolt/rsrc/database_add.png rename to rsrc/database_add.png diff --git a/qbolt/rsrc/database_connect.png b/rsrc/database_connect.png similarity index 100% rename from qbolt/rsrc/database_connect.png rename to rsrc/database_connect.png diff --git a/qbolt/rsrc/database_lightning.png b/rsrc/database_lightning.png similarity index 100% rename from qbolt/rsrc/database_lightning.png rename to rsrc/database_lightning.png diff --git a/qbolt/rsrc/delete.png b/rsrc/delete.png similarity index 100% rename from qbolt/rsrc/delete.png rename to rsrc/delete.png diff --git a/qbolt/rsrc/disconnect.png b/rsrc/disconnect.png similarity index 100% rename from qbolt/rsrc/disconnect.png rename to rsrc/disconnect.png diff --git a/qbolt/rsrc/door_out.png b/rsrc/door_out.png similarity index 100% rename from qbolt/rsrc/door_out.png rename to rsrc/door_out.png diff --git a/qbolt/rsrc/information.png b/rsrc/information.png similarity index 100% rename from qbolt/rsrc/information.png rename to rsrc/information.png diff --git a/qbolt/rsrc/page.png b/rsrc/page.png similarity index 100% rename from qbolt/rsrc/page.png rename to rsrc/page.png diff --git a/qbolt/rsrc/qbolt.ico b/rsrc/qbolt.ico similarity index 100% rename from qbolt/rsrc/qbolt.ico rename to rsrc/qbolt.ico diff --git a/qbolt/rsrc/table.png b/rsrc/table.png similarity index 100% rename from qbolt/rsrc/table.png rename to rsrc/table.png diff --git a/qbolt/rsrc/table_add.png b/rsrc/table_add.png similarity index 100% rename from qbolt/rsrc/table_add.png rename to rsrc/table_add.png diff --git a/qbolt/rsrc/table_delete.png b/rsrc/table_delete.png similarity index 100% rename from qbolt/rsrc/table_delete.png rename to rsrc/table_delete.png diff --git a/util.go b/util.go new file mode 100644 index 0000000..e66a557 --- /dev/null +++ b/util.go @@ -0,0 +1,24 @@ +package main + +import ( + "unsafe" + + "github.com/mappu/miqt/qt" +) + +// ReverseSlice reverses a slice. +// @ref https://stackoverflow.com/a/28058324 +func ReverseSlice[S ~[]E, E any](s S) { + for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 { + s[i], s[j] = s[j], s[i] + } +} + +func MakeQByteArray(s string) *qt.QByteArray { + return qt.NewQByteArray7(s, len(s)) +} + +func FromQByteArray(qba *qt.QByteArray) string { + var rawData []byte = unsafe.Slice((*byte)(qba.Data()), qba.Length()) + return string(rawData) // copy +} diff --git a/util_test.go b/util_test.go new file mode 100644 index 0000000..8e7efd7 --- /dev/null +++ b/util_test.go @@ -0,0 +1,17 @@ +package main + +import ( + "testing" +) + +func TestQByteArray(t *testing.T) { + foo := "the \x00 quick \x01 brown \x02 fox \x03 jumps \x04 over \x05 the \x06 lazy \x07 dog" + + qb := MakeQByteArray(foo) + + out := FromQByteArray(qb) + + if foo != out { + t.Errorf("Expected equal") + } +} diff --git a/windows-manifest.template.json b/windows-manifest.template.json new file mode 100644 index 0000000..868bc1a --- /dev/null +++ b/windows-manifest.template.json @@ -0,0 +1,50 @@ +{ + "RT_GROUP_ICON": { + "APP": { + "0000": "rsrc/database_lightning.png" + } + }, + "RT_VERSION": { + "#1": { + "0000": { + "fixed": { + "file_version": "%VERSION%.0", + "product_version": "%VERSION%.0" + }, + "info": { + "0409": { + "FileDescription": "QBolt Database Viewer", + "FileVersion": "%VERSION%.0", + "ProductName": "QBolt Database Viewer", + "ProductVersion": "%VERSION%.0" + } + } + } + } + }, + "RT_MANIFEST": { + "#1": { + "0409": { + "identity": { + "name": "", + "version": "" + }, + "description": "", + "minimum-os": "win7", + "execution-level": "as invoker", + "ui-access": false, + "auto-elevate": false, + "dpi-awareness": "per monitor v2", + "disable-theming": false, + "disable-window-filtering": false, + "high-resolution-scrolling-aware": false, + "ultra-high-resolution-scrolling-aware": false, + "long-path-aware": false, + "printer-driver-isolation": false, + "gdi-scaling": false, + "segment-heap": false, + "use-common-controls-v6": true + } + } + } +} \ No newline at end of file