Compare commits
	
		
			59 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 4e13d8dffd | |||
| 20e5efa711 | |||
| 6378740051 | |||
| b8ce7a667b | |||
| 281ca18d90 | |||
| 1725de6ace | |||
| 0ad7c03db0 | |||
| 1cdd0d113b | |||
| a0fae43690 | |||
| 04ac766125 | |||
| 9ac26467c0 | |||
| cbfc038839 | |||
| 7ae6462da0 | |||
| 09a3e5b90f | |||
| 1cddd17017 | |||
| 8fdc3a0428 | |||
| 18f10fc1b4 | |||
| 18331ae007 | |||
| f08242e93c | |||
| 23965230e2 | |||
| e43b261752 | |||
| f9b4cb71a5 | |||
| 96c05641bd | |||
| 2c5d1946ec | |||
| a6cbc5a9ed | |||
| 6008ae44a2 | |||
| 99096b2360 | |||
| 0e92459779 | |||
| a13eaaf523 | |||
| dca8d277d3 | |||
| 15c739c0b9 | |||
| fc2da972ef | |||
| e45eea7111 | |||
| 1e62d79c07 | |||
| c74c5ae5c0 | |||
| b0092c4a6e | |||
| 5ce6368c4a | |||
| 3e7e54da4b | |||
| 9f80d687b2 | |||
| 96411e877f | |||
| ac7e078c02 | |||
| e13314f5dc | |||
| b50c3e738a | |||
| 54ad6015b7 | |||
| 40e84ac230 | |||
| 767eaa0a47 | |||
| bb594e768c | |||
| 516bd99c4d | |||
| 571bfcf4b6 | |||
| 26f7a11d80 | |||
| 6fb8d1ba0c | |||
| 21588021d3 | |||
| 7441e0c15b | |||
| 142f3f6bf4 | |||
| 19ddb1c956 | |||
| 97467eae4d | |||
| 17c37b6568 | |||
| 57237ef2e8 | |||
| 8d8374be24 | 
							
								
								
									
										16
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,16 @@
 | 
				
			|||||||
 | 
					# development
 | 
				
			||||||
 | 
					qbolt
 | 
				
			||||||
 | 
					dummy-data/dummy-data*
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# 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-*
 | 
				
			||||||
@@ -1,5 +0,0 @@
 | 
				
			|||||||
mode:regexp
 | 
					 | 
				
			||||||
^build-qbolt-
 | 
					 | 
				
			||||||
^dummy-data/dummy-data$
 | 
					 | 
				
			||||||
\.pro\.user$
 | 
					 | 
				
			||||||
^build/
 | 
					 | 
				
			||||||
							
								
								
									
										109
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						@@ -1,68 +1,67 @@
 | 
				
			|||||||
 | 
					VERSION := 1.1.0
 | 
				
			||||||
 | 
					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
 | 
				
			||||||
 | 
					GO := go
 | 
				
			||||||
 | 
					MIQT_DOCKER := miqt-docker
 | 
				
			||||||
 | 
					MIQT_UIC := miqt-uic
 | 
				
			||||||
 | 
					MIQT_RCC := miqt-rcc
 | 
				
			||||||
 | 
					GO_WINRES := go-winres
 | 
				
			||||||
 | 
					SOURCES := $(wildcard *.go *.ui *.qrc) resources.go resources.rcc mainwindow_ui.go itemwindow_ui.go rsrc_windows_amd64.syso
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export PATH := /usr/lib/mxe/usr/bin:$(PATH)
 | 
					.PHONY: all
 | 
				
			||||||
GOFLAGS := -ldflags='-s -w' -gcflags='-trimpath=$(CURDIR)' -asmflags='-trimpath=$(CURDIR)'
 | 
					all: build/qbolt build/qbolt.exe
 | 
				
			||||||
VERSION := 1.0.0
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.PHONY: all libs dist clean
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
all: \
 | 
					 | 
				
			||||||
	build/linux/qbolt \
 | 
					 | 
				
			||||||
	build/win32/release/qbolt.exe
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
libs: \
 | 
					 | 
				
			||||||
	build/linux/qbolt.a \
 | 
					 | 
				
			||||||
	build/win32/qbolt.a
 | 
					 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
dist: \
 | 
					.PHONY: dist
 | 
				
			||||||
	build/dist/qbolt-${VERSION}-win32.zip \
 | 
					dist: build/qbolt-${VERSION}-windows-x86_64.zip build/qbolt-${VERSION}-debian12-x86_64.tar.xz
 | 
				
			||||||
	build/dist/qbolt-${VERSION}-src.tar.gz \
 | 
					 | 
				
			||||||
	build/dist/qbolt-${VERSION}-linux_amd64.tar.xz
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.PHONY: clean
 | 
				
			||||||
clean:
 | 
					clean:
 | 
				
			||||||
	if [ -f qbolt/qbolt.a ] ; then rm qbolt/qbolt.a ; fi
 | 
						rm -f qbolt
 | 
				
			||||||
	if [ -f qbolt ] ; then rm qbolt ; fi
 | 
						rm -rf build
 | 
				
			||||||
	if [ -d build ] ; then rm -r build ; fi
 | 
						mkdir -p build
 | 
				
			||||||
 | 
						touch build/.create_dir
 | 
				
			||||||
 | 
						rm -f windows-manifest.json
 | 
				
			||||||
 | 
						rm -f rsrc_windows_amd64.syso
 | 
				
			||||||
 | 
						rm -f resources.go
 | 
				
			||||||
 | 
						rm -f resources.rcc
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
# Build core golang shared library (linux)
 | 
					# Generated files
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
build/linux/qbolt.a: *.go
 | 
					resources.rcc resources.go: resources.qrc
 | 
				
			||||||
	mkdir -p build/linux
 | 
						$(MIQT_RCC) -Qt6 -Input resources.qrc
 | 
				
			||||||
	go build ${GOFLAGS} -buildmode=c-archive -o build/linux/qbolt.a
 | 
					 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
# Build core golang shared library (win32)
 | 
					mainwindow_ui.go: mainwindow.ui
 | 
				
			||||||
 | 
						$(MIQT_UIC) -Qt6 -InFile mainwindow.ui -OutFile mainwindow_ui.go
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					itemwindow_ui.go: itemwindow.ui
 | 
				
			||||||
 | 
						$(MIQT_UIC) -Qt6 -InFile itemwindow.ui -OutFile itemwindow_ui.go
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
build/win32/qbolt.a: *.go
 | 
					windows-manifest.json: windows-manifest.template.json Makefile
 | 
				
			||||||
	mkdir -p build/win32
 | 
						cat windows-manifest.template.json | sed -re 's_%VERSION%_$(VERSION)_' > windows-manifest.json
 | 
				
			||||||
	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
 | 
					 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
build/linux/qbolt: build/linux/qbolt.a qbolt/*
 | 
					rsrc_windows_amd64.syso: windows-manifest.json
 | 
				
			||||||
	cd build/linux && qmake ../../qbolt/qbolt.pro && make
 | 
						$(GO_WINRES) make --in windows-manifest.json
 | 
				
			||||||
 | 
						rm rsrc_windows_386.syso || true # we do not build x86_32
 | 
				
			||||||
# 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
 | 
					 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
# Windows binaries
 | 
					# Linux release
 | 
				
			||||||
 | 
					 | 
				
			||||||
build/win32/release/qbolt.exe: build/win32/qbolt.a qbolt/*
 | 
					 | 
				
			||||||
	cd build/win32 && i686-w64-mingw32.static-qmake-qt5 ../../qbolt/qbolt.pro && make
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# 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
 | 
					 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
build/dist/qbolt-${VERSION}-win32.zip: build/win32/dist/qbolt.exe
 | 
					build/qbolt: $(SOURCES)
 | 
				
			||||||
	mkdir -p build/dist
 | 
						CGO_CFLAGS='-Os -ffunction-sections -fdata-sections -flto=auto' CGO_CXXFLAGS='-Os -ffunction-sections -fdata-sections -flto=auto' CGO_LDFLAGS='-Wl,--gc-sections -flto=auto -fwhole-program' $(GO) build $(GOFLAGS_L) -o build/qbolt
 | 
				
			||||||
	zip -0 -j build/dist/qbolt-${VERSION}-win32.zip build/win32/dist/qbolt.exe
 | 
						upx build/qbolt
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Source code archives
 | 
					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/dist/qbolt-${VERSION}-src.tar.gz:
 | 
					build/qbolt.exe: $(SOURCES)
 | 
				
			||||||
	hg archive build/dist/qbolt-${VERSION}-src.tar.gz
 | 
						# -flto causes internal compiler error
 | 
				
			||||||
 | 
						$(MIQT_DOCKER) win64-qt6-static /bin/bash -c "CGO_CFLAGS='-Os -ffunction-sections -fdata-sections' CGO_CXXFLAGS='-Os -ffunction-sections -fdata-sections' CGO_LDFLAGS='-Wl,--gc-sections -fwhole-program' go build $(GOFLAGS_W) -o build/qbolt.exe"
 | 
				
			||||||
 | 
						# Must be stripped before upx'ing - @ref https://github.com/msys2/MSYS2-packages/issues/454
 | 
				
			||||||
 | 
						# However this removes the rsrc, loses the icon and causes a Defender detection
 | 
				
			||||||
 | 
						# strip build/qbolt.exe
 | 
				
			||||||
 | 
						# upx 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
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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()
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
							
								
								
									
										62
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,62 @@
 | 
				
			|||||||
 | 
					# qbolt
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					A graphical database manager for BoltDB.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					QBolt allows you to graphically view and edit the content of Bolt databases.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Written in Golang (Qt)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					2025-05-04 1.1.0
 | 
				
			||||||
 | 
					- New feature to import/export database as zip archive
 | 
				
			||||||
 | 
					- Upgrade to Qt 6
 | 
				
			||||||
 | 
					- Add keyboard shortcuts for refresh
 | 
				
			||||||
 | 
					- Improve High DPI support
 | 
				
			||||||
 | 
					- Rebuild artefacts with miqt v0.10.0, etcd-io/bbolt v1.4.0, go 1.23, Qt 6.8 (win64)
 | 
				
			||||||
 | 
					- [⬇ Download here](https://git.ivysaur.me/code.ivysaur.me/qbolt/releases/tag/v1.1.0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					2024-10-05 1.0.3
 | 
				
			||||||
 | 
					- Port from hybrid Go/C++ to now using [MIQT](https://github.com/mappu/miqt)
 | 
				
			||||||
 | 
					- Switch Windows build to win64
 | 
				
			||||||
 | 
					- Rebuild artefacts with miqt v0.5.0, etcd-io/bbolt v1.3.11, go 1.19 (deb12), go 1.23 (win64)
 | 
				
			||||||
 | 
					- [⬇ Download here](https://git.ivysaur.me/code.ivysaur.me/qbolt/releases/tag/v1.0.3)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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
 | 
				
			||||||
 | 
					- 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.
 | 
				
			||||||
 | 
					- [⬇ 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
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
							
								
								
									
										223
									
								
								bolt.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,223 @@
 | 
				
			|||||||
 | 
					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 []byte) 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(key, val)
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func Bolt_DeleteItem(db *bolt.DB, browse []string, key []byte) 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(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) error {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if len(browse) == 0 {
 | 
				
			||||||
 | 
							// root mode
 | 
				
			||||||
 | 
							return db.View(func(tx *bolt.Tx) error {
 | 
				
			||||||
 | 
								return tx.ForEach(func(k []byte, _ *bolt.Bucket) error {
 | 
				
			||||||
 | 
									return cb(string(k))
 | 
				
			||||||
 | 
								})
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 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 {
 | 
				
			||||||
 | 
									return cb(string(k))
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								return nil
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type ListItemInfo struct {
 | 
				
			||||||
 | 
						Name    []byte
 | 
				
			||||||
 | 
						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
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								kcopy := make([]byte, len(k))
 | 
				
			||||||
 | 
								copy(kcopy, k)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								return cb(ListItemInfo{kcopy, int64(len(v))})
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func Bolt_GetItem(db *bolt.DB, browse []string, key []byte) ([]byte, error) {
 | 
				
			||||||
 | 
						var ret []byte
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err := withBrowse_ReadOnly(db, browse, func(tx *bolt.Tx, bucket *bolt.Bucket) error {
 | 
				
			||||||
 | 
							d := bucket.Get([]byte(key))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							ret = make([]byte, len(d))
 | 
				
			||||||
 | 
							copy(ret, d)
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						return ret, err
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func Bolt_Close(db *bolt.DB) error {
 | 
				
			||||||
 | 
						return db.Close()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										0
									
								
								build/.create_dir
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								doc/image0.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 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  | 
@@ -1,15 +1,17 @@
 | 
				
			|||||||
package main
 | 
					package main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"fmt"
 | 
					 | 
				
			||||||
	"math/rand"
 | 
						"math/rand"
 | 
				
			||||||
	"os"
 | 
						"os"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/boltdb/bolt"
 | 
						bolt "go.etcd.io/bbolt"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
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 {
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										167
									
								
								export.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,167 @@
 | 
				
			|||||||
 | 
					package main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"archive/zip"
 | 
				
			||||||
 | 
						"bytes"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"io"
 | 
				
			||||||
 | 
						"io/fs"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
						"path"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func Bolt_ExportDatabaseToZip(dbpath, zippath string) error {
 | 
				
			||||||
 | 
						db, err := Bolt_Open(true, dbpath)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("Error opening database: %w", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						defer db.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						fh, err := os.OpenFile(zippath, os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0644)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("Error opening output file: %w", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						defer fh.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						zw := zip.NewWriter(fh)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Filenames in zip files cannot contain `/` characters. Mangle it
 | 
				
			||||||
 | 
						safename := func(n string) string {
 | 
				
			||||||
 | 
							return strings.ReplaceAll(string(n), `/`, `__`)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var process func(currentPath []string) error
 | 
				
			||||||
 | 
						process = func(currentPath []string) error {
 | 
				
			||||||
 | 
							return Bolt_ListBuckets(db, currentPath, func(bucket string) error {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Create entry for our own bucket
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								ourBucket := zip.FileHeader{
 | 
				
			||||||
 | 
									Name: path.Join(path.Join(Apply(currentPath, safename)...), safename(bucket)) + `/`, // Trailing slash = directory
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								ourBucket.SetMode(fs.ModeDir | 0755)
 | 
				
			||||||
 | 
								_, err := zw.CreateHeader(&ourBucket)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									return err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Child pathspec
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								childPath := CopySliceAdd(currentPath, bucket)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Create file entries for all non-bucket children
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								err = Bolt_ListItems(db, childPath, func(li ListItemInfo) error {
 | 
				
			||||||
 | 
									fileItem := zip.FileHeader{
 | 
				
			||||||
 | 
										Name: path.Join(path.Join(Apply(childPath, safename)...), safename(string(li.Name))),
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									fileItem.SetMode(0644)
 | 
				
			||||||
 | 
									fileW, err := zw.CreateHeader(&fileItem)
 | 
				
			||||||
 | 
									if err != nil {
 | 
				
			||||||
 | 
										return err
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									buff, err := Bolt_GetItem(db, childPath, []byte(li.Name))
 | 
				
			||||||
 | 
									if err != nil {
 | 
				
			||||||
 | 
										return err
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									_, err = io.CopyN(fileW, bytes.NewReader(buff), li.DataLen)
 | 
				
			||||||
 | 
									return err
 | 
				
			||||||
 | 
								})
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									return err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Recurse for all bucket-type children
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								process(childPath)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Done
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								return nil
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err = process([]string{})
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err = zw.Flush()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err = zw.Close()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return fh.Close()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func Bolt_ImportZipToDatabase(dbpath, zippath string) error {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						db, err := Bolt_Open(false, dbpath)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("Error opening target database: %w", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						defer db.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						fh, err := os.OpenFile(zippath, os.O_RDONLY, 0400)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("Error opening input archive: %w", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						defer fh.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						fstat, err := fh.Stat()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						zr, err := zip.NewReader(fh, fstat.Size())
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("Reading zip file format: %w", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, zf := range zr.File {
 | 
				
			||||||
 | 
							if strings.HasSuffix(zf.Name, `/`) || (zf.Mode()&fs.ModeDir) != 0 {
 | 
				
			||||||
 | 
								// Bucket
 | 
				
			||||||
 | 
								bucketPath := strings.Split(strings.TrimSuffix(zf.Name, `/`), `/`)
 | 
				
			||||||
 | 
								err = Bolt_CreateBucket(db, bucketPath[0:len(bucketPath)-1], bucketPath[len(bucketPath)-1])
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									return fmt.Errorf("Creating bucket %q: %w", zf.Name, err)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								// Object
 | 
				
			||||||
 | 
								objectPath := strings.Split(zf.Name, `/`)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								rc, err := zf.Open()
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									return err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								content, err := io.ReadAll(rc)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									return err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								err = Bolt_SetItem(db, objectPath[0:len(objectPath)-1], []byte(objectPath[len(objectPath)-1]), content)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									return err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								err = rc.Close()
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									return err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Done
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										12
									
								
								go.mod
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,12 @@
 | 
				
			|||||||
 | 
					module code.ivysaur.me/qbolt
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					go 1.23.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					toolchain go1.23.3
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					require (
 | 
				
			||||||
 | 
						github.com/mappu/miqt v0.10.0
 | 
				
			||||||
 | 
						go.etcd.io/bbolt v1.4.0
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					require golang.org/x/sys v0.32.0 // indirect
 | 
				
			||||||
							
								
								
									
										20
									
								
								go.sum
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,20 @@
 | 
				
			|||||||
 | 
					github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 | 
				
			||||||
 | 
					github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 | 
				
			||||||
 | 
					github.com/mappu/miqt v0.10.0 h1:w+ucRwdoIO7xS32us34lL2Mh0+aarywNpQz6c76ZSDY=
 | 
				
			||||||
 | 
					github.com/mappu/miqt v0.10.0/go.mod h1:xFg7ADaO1QSkmXPsPODoKe/bydJpRG9fgCYyIDl/h1U=
 | 
				
			||||||
 | 
					github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 | 
				
			||||||
 | 
					github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 | 
				
			||||||
 | 
					github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
 | 
				
			||||||
 | 
					github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
 | 
				
			||||||
 | 
					go.etcd.io/bbolt v1.3.11 h1:yGEzV1wPz2yVCLsD8ZAiGHhHVlczyC9d1rP43/VCRJ0=
 | 
				
			||||||
 | 
					go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I=
 | 
				
			||||||
 | 
					go.etcd.io/bbolt v1.4.0 h1:TU77id3TnN/zKr7CO/uk+fBCwF2jGcMuw2B/FMAzYIk=
 | 
				
			||||||
 | 
					go.etcd.io/bbolt v1.4.0/go.mod h1:AsD+OCi/qPN1giOX1aiLAha3o1U8rAz65bvN4j0sRuk=
 | 
				
			||||||
 | 
					golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
 | 
				
			||||||
 | 
					golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
 | 
				
			||||||
 | 
					golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18=
 | 
				
			||||||
 | 
					golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
				
			||||||
 | 
					golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
 | 
				
			||||||
 | 
					golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
 | 
				
			||||||
 | 
					gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 | 
				
			||||||
 | 
					gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 | 
				
			||||||
@@ -42,12 +42,6 @@
 | 
				
			|||||||
   </item>
 | 
					   </item>
 | 
				
			||||||
   <item row="1" column="0">
 | 
					   <item row="1" column="0">
 | 
				
			||||||
    <widget class="QFrame" name="frame">
 | 
					    <widget class="QFrame" name="frame">
 | 
				
			||||||
     <property name="frameShape">
 | 
					 | 
				
			||||||
      <enum>QFrame::StyledPanel</enum>
 | 
					 | 
				
			||||||
     </property>
 | 
					 | 
				
			||||||
     <property name="frameShadow">
 | 
					 | 
				
			||||||
      <enum>QFrame::Raised</enum>
 | 
					 | 
				
			||||||
     </property>
 | 
					 | 
				
			||||||
     <layout class="QGridLayout" name="gridLayout">
 | 
					     <layout class="QGridLayout" name="gridLayout">
 | 
				
			||||||
      <item row="0" column="0">
 | 
					      <item row="0" column="0">
 | 
				
			||||||
       <widget class="QDialogButtonBox" name="buttonBox">
 | 
					       <widget class="QDialogButtonBox" name="buttonBox">
 | 
				
			||||||
							
								
								
									
										77
									
								
								itemwindow_ui.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,77 @@
 | 
				
			|||||||
 | 
					// Generated by miqt-uic. To update this file, edit the .ui file in
 | 
				
			||||||
 | 
					// Qt Designer, and then run 'go generate'.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//go:generate miqt-uic -Qt6 -InFile itemwindow.ui -OutFile itemwindow_ui.go
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						qt "github.com/mappu/miqt/qt6"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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.NewQDialog(nil)
 | 
				
			||||||
 | 
						ItemWindow__objectName := qt.NewQAnyStringView3("ItemWindow")
 | 
				
			||||||
 | 
						ui.ItemWindow.SetObjectName(*ItemWindow__objectName)
 | 
				
			||||||
 | 
						ItemWindow__objectName.Delete() // setter copied value
 | 
				
			||||||
 | 
						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)
 | 
				
			||||||
 | 
						gridLayout_2__objectName := qt.NewQAnyStringView3("gridLayout_2")
 | 
				
			||||||
 | 
						ui.gridLayout_2.SetObjectName(*gridLayout_2__objectName)
 | 
				
			||||||
 | 
						gridLayout_2__objectName.Delete() // setter copied value
 | 
				
			||||||
 | 
						ui.gridLayout_2.SetVerticalSpacing(0)
 | 
				
			||||||
 | 
						ui.gridLayout_2.SetContentsMargins(0, 0, 0, 0)
 | 
				
			||||||
 | 
						ui.gridLayout_2.SetSpacing(6)
 | 
				
			||||||
 | 
						ui.contentArea = qt.NewQPlainTextEdit(ui.ItemWindow.QWidget)
 | 
				
			||||||
 | 
						contentArea__objectName := qt.NewQAnyStringView3("contentArea")
 | 
				
			||||||
 | 
						ui.contentArea.SetObjectName(*contentArea__objectName)
 | 
				
			||||||
 | 
						contentArea__objectName.Delete() // setter copied value
 | 
				
			||||||
 | 
						ui.contentArea.SetFrameShape(qt.QFrame__NoFrame)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ui.gridLayout_2.AddWidget2(ui.contentArea.QWidget, 0, 0)
 | 
				
			||||||
 | 
						ui.frame = qt.NewQFrame(ui.ItemWindow.QWidget)
 | 
				
			||||||
 | 
						frame__objectName := qt.NewQAnyStringView3("frame")
 | 
				
			||||||
 | 
						ui.frame.SetObjectName(*frame__objectName)
 | 
				
			||||||
 | 
						frame__objectName.Delete() // setter copied value
 | 
				
			||||||
 | 
						ui.gridLayout = qt.NewQGridLayout(ui.frame.QWidget)
 | 
				
			||||||
 | 
						gridLayout__objectName := qt.NewQAnyStringView3("gridLayout")
 | 
				
			||||||
 | 
						ui.gridLayout.SetObjectName(*gridLayout__objectName)
 | 
				
			||||||
 | 
						gridLayout__objectName.Delete() // setter copied value
 | 
				
			||||||
 | 
						ui.gridLayout.SetContentsMargins(11, 11, 11, 11)
 | 
				
			||||||
 | 
						ui.gridLayout.SetSpacing(6)
 | 
				
			||||||
 | 
						ui.buttonBox = qt.NewQDialogButtonBox(ui.frame.QWidget)
 | 
				
			||||||
 | 
						buttonBox__objectName := qt.NewQAnyStringView3("buttonBox")
 | 
				
			||||||
 | 
						ui.buttonBox.SetObjectName(*buttonBox__objectName)
 | 
				
			||||||
 | 
						buttonBox__objectName.Delete() // setter copied value
 | 
				
			||||||
 | 
						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() {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										404
									
								
								main.go
									
									
									
									
									
								
							
							
						
						@@ -1,396 +1,28 @@
 | 
				
			|||||||
package main
 | 
					package main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import "C"
 | 
					 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"encoding/binary"
 | 
					 | 
				
			||||||
	"encoding/json"
 | 
					 | 
				
			||||||
	"errors"
 | 
					 | 
				
			||||||
	"os"
 | 
						"os"
 | 
				
			||||||
	"time"
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/boltdb/bolt"
 | 
						qt "github.com/mappu/miqt/qt6"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const (
 | 
					var Version string = "v0.0.0-devel"
 | 
				
			||||||
	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(path string) (ObjectReference, *C.char, int) {
 | 
					 | 
				
			||||||
	opts := *bolt.DefaultOptions
 | 
					 | 
				
			||||||
	opts.Timeout = 10 * time.Second
 | 
					 | 
				
			||||||
	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
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
func main() {
 | 
					func main() {
 | 
				
			||||||
	// virtual
 | 
					
 | 
				
			||||||
 | 
						_ = qt.NewQApplication(os.Args)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						qt.QGuiApplication_SetApplicationDisplayName("QBolt")
 | 
				
			||||||
 | 
						qt.QGuiApplication_SetWindowIcon(qt.NewQIcon4(":/rsrc/database_lightning.png"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// High DPI tweaks
 | 
				
			||||||
 | 
						qt.QCoreApplication_SetAttribute2(qt.AA_EnableHighDpiScaling, true)
 | 
				
			||||||
 | 
						qt.QCoreApplication_SetAttribute2(qt.AA_UseHighDpiPixmaps, true)
 | 
				
			||||||
 | 
						qt.QCoreApplication_SetAttribute2(qt.AA_Use96Dpi, true)
 | 
				
			||||||
 | 
						qt.QGuiApplication_SetHighDpiScaleFactorRoundingPolicy(qt.PassThrough)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						w := NewMainWindow()
 | 
				
			||||||
 | 
						w.ui.MainWindow.Show()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						qt.QApplication_Exec()
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										586
									
								
								mainwindow.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,586 @@
 | 
				
			|||||||
 | 
					package main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
						"path/filepath"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						qt "github.com/mappu/miqt/qt6"
 | 
				
			||||||
 | 
						"github.com/mappu/miqt/qt6/mainthread"
 | 
				
			||||||
 | 
						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.ui.MainWindow.QWidget)
 | 
				
			||||||
 | 
						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.ui.MainWindow.QWidget)
 | 
				
			||||||
 | 
						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.actionExport_database_as_zip.OnTriggered(this.onactionExport_database_as_zip_triggered)
 | 
				
			||||||
 | 
						this.ui.actionCreate_database_from_zip.OnTriggered(this.onactionCreate_database_from_zip_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)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						this.ui.MainWindow.SetAcceptDrops(true)
 | 
				
			||||||
 | 
						this.ui.MainWindow.OnDragEnterEvent(this.onDragEnter)
 | 
				
			||||||
 | 
						this.ui.MainWindow.OnDragMoveEvent(this.onDragMove)
 | 
				
			||||||
 | 
						this.ui.MainWindow.OnDropEvent(this.onDropEvent)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						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.NewQVariant4(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) onDragEnter(super func(event *qt.QDragEnterEvent), event *qt.QDragEnterEvent) {
 | 
				
			||||||
 | 
						if !event.MimeData().HasUrls() {
 | 
				
			||||||
 | 
							event.Ignore()
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						event.Accept()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (this *MainWindow) onDragMove(super func(event *qt.QDragMoveEvent), event *qt.QDragMoveEvent) {
 | 
				
			||||||
 | 
						if !event.MimeData().HasUrls() {
 | 
				
			||||||
 | 
							event.Ignore()
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						event.SetDropAction(qt.CopyAction)
 | 
				
			||||||
 | 
						event.Accept()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (this *MainWindow) onDropEvent(super func(event *qt.QDropEvent), event *qt.QDropEvent) {
 | 
				
			||||||
 | 
						if !event.MimeData().HasUrls() {
 | 
				
			||||||
 | 
							event.Ignore()
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						event.Accept()
 | 
				
			||||||
 | 
						paths := event.MimeData().Urls()
 | 
				
			||||||
 | 
						for _, path := range paths {
 | 
				
			||||||
 | 
							lpath := path.ToLocalFile()
 | 
				
			||||||
 | 
							this.openDatabase(lpath, false)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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 (this *MainWindow) onactionExport_database_as_zip_triggered() {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						dbPath := qt.QFileDialog_GetOpenFileName2(this.Widget(), "Select bolt database...")
 | 
				
			||||||
 | 
						if dbPath == "" {
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						zipPath := qt.QFileDialog_GetSaveFileName4(this.Widget(), "Save as...", "", "Zip files (*.zip)")
 | 
				
			||||||
 | 
						if zipPath == "" {
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						go func() {
 | 
				
			||||||
 | 
							err := Bolt_ExportDatabaseToZip(dbPath, zipPath)
 | 
				
			||||||
 | 
							mainthread.Start(func() {
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									this.alert(fmt.Sprintf("Error exporting database as zip: %s", err.Error()))
 | 
				
			||||||
 | 
									return
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								this.alert("Exported as zip successfully.")
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						}()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (this *MainWindow) onactionCreate_database_from_zip_triggered() {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						zipPath := qt.QFileDialog_GetOpenFileName4(this.Widget(), "Select zip archive...", "", "Zip files (*.zip)")
 | 
				
			||||||
 | 
						if zipPath == "" {
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						dbPath := qt.QFileDialog_GetSaveFileName2(this.Widget(), "Save as...")
 | 
				
			||||||
 | 
						if dbPath == "" {
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Qt popped up a message saying 'will overwrite existing'
 | 
				
			||||||
 | 
						// Make that true
 | 
				
			||||||
 | 
						if err := os.Remove(dbPath); err != nil && !os.IsNotExist(err) {
 | 
				
			||||||
 | 
							this.alert(fmt.Sprintf("Error removing existing database for overwrite: %s", err.Error()))
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						go func() {
 | 
				
			||||||
 | 
							err := Bolt_ImportZipToDatabase(dbPath, zipPath)
 | 
				
			||||||
 | 
							mainthread.Start(func() {
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									this.alert(fmt.Sprintf("Error importing database from zip %q: %s", zipPath, err.Error()))
 | 
				
			||||||
 | 
									return
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								this.alert("Imported zip to database successfully.")
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						}()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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) error {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							child := qt.NewQTreeWidgetItem6(itm) // NewQTreeWidgetItem()
 | 
				
			||||||
 | 
							child.SetText(0, getDisplayName([]byte(qba)))
 | 
				
			||||||
 | 
							child.SetData(0, BinaryDataRole, qt.NewQVariant12([]byte(qba)))
 | 
				
			||||||
 | 
							child.SetIcon(0, qt.NewQIcon4(":/rsrc/table.png"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							itm.AddChild(child)
 | 
				
			||||||
 | 
							this.refreshBucketTree(child)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						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(),
 | 
				
			||||||
 | 
							"<b>QBolt "+Version+"</b><br>Graphical interface for managing Bolt databases<br><br>"+
 | 
				
			||||||
 | 
								"- <a href='https://github.com/boltdb/bolt'>About BoltDB</a><br>"+
 | 
				
			||||||
 | 
								"- <a href='http://www.famfamfam.com/lab/icons/silk/'>FamFamFam "Silk" icon set</a><br>"+
 | 
				
			||||||
 | 
								"- <a href='https://code.ivysaur.me/qbolt'>QBolt homepage</a><br>",
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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().MapToGlobalWithQPoint(pos))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							// Top-level item, show the database menu
 | 
				
			||||||
 | 
							this.databaseContext.Popup(this.ui.bucketTree.Viewport().MapToGlobalWithQPoint(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.NewQVariant12([]byte(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, string(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 []byte, 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, []byte(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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						key := index.DataWithRole(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 := ws.itm.Data(0, BinaryDataRole).ToByteArray()
 | 
				
			||||||
 | 
						if qt.QMessageBox_Question2(
 | 
				
			||||||
 | 
							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, string(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, []byte(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_Question2(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, 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)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -269,15 +269,26 @@
 | 
				
			|||||||
     <x>0</x>
 | 
					     <x>0</x>
 | 
				
			||||||
     <y>0</y>
 | 
					     <y>0</y>
 | 
				
			||||||
     <width>668</width>
 | 
					     <width>668</width>
 | 
				
			||||||
     <height>21</height>
 | 
					     <height>22</height>
 | 
				
			||||||
    </rect>
 | 
					    </rect>
 | 
				
			||||||
   </property>
 | 
					   </property>
 | 
				
			||||||
   <widget class="QMenu" name="menuFile">
 | 
					   <widget class="QMenu" name="menuFile">
 | 
				
			||||||
    <property name="title">
 | 
					    <property name="title">
 | 
				
			||||||
     <string>Fi&le</string>
 | 
					     <string>&File</string>
 | 
				
			||||||
    </property>
 | 
					    </property>
 | 
				
			||||||
 | 
					    <widget class="QMenu" name="menuConvert">
 | 
				
			||||||
 | 
					     <property name="title">
 | 
				
			||||||
 | 
					      <string>Convert</string>
 | 
				
			||||||
 | 
					     </property>
 | 
				
			||||||
 | 
					     <addaction name="actionExport_database_as_zip"/>
 | 
				
			||||||
 | 
					     <addaction name="actionCreate_database_from_zip"/>
 | 
				
			||||||
 | 
					    </widget>
 | 
				
			||||||
    <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="menuConvert"/>
 | 
				
			||||||
 | 
					    <addaction name="separator"/>
 | 
				
			||||||
    <addaction name="separator"/>
 | 
					    <addaction name="separator"/>
 | 
				
			||||||
    <addaction name="actionExit"/>
 | 
					    <addaction name="actionExit"/>
 | 
				
			||||||
   </widget>
 | 
					   </widget>
 | 
				
			||||||
@@ -318,6 +329,9 @@
 | 
				
			|||||||
   <property name="text">
 | 
					   <property name="text">
 | 
				
			||||||
    <string>&About QBolt</string>
 | 
					    <string>&About QBolt</string>
 | 
				
			||||||
   </property>
 | 
					   </property>
 | 
				
			||||||
 | 
					   <property name="shortcut">
 | 
				
			||||||
 | 
					    <string>F1</string>
 | 
				
			||||||
 | 
					   </property>
 | 
				
			||||||
  </action>
 | 
					  </action>
 | 
				
			||||||
  <action name="actionAbout_Qt">
 | 
					  <action name="actionAbout_Qt">
 | 
				
			||||||
   <property name="text">
 | 
					   <property name="text">
 | 
				
			||||||
@@ -371,6 +385,9 @@
 | 
				
			|||||||
   <property name="text">
 | 
					   <property name="text">
 | 
				
			||||||
    <string>Refresh buckets</string>
 | 
					    <string>Refresh buckets</string>
 | 
				
			||||||
   </property>
 | 
					   </property>
 | 
				
			||||||
 | 
					   <property name="shortcut">
 | 
				
			||||||
 | 
					    <string>F5</string>
 | 
				
			||||||
 | 
					   </property>
 | 
				
			||||||
  </action>
 | 
					  </action>
 | 
				
			||||||
  <action name="actionClear_selection">
 | 
					  <action name="actionClear_selection">
 | 
				
			||||||
   <property name="text">
 | 
					   <property name="text">
 | 
				
			||||||
@@ -395,6 +412,21 @@
 | 
				
			|||||||
    <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>
 | 
				
			||||||
 | 
					  <action name="actionExport_database_as_zip">
 | 
				
			||||||
 | 
					   <property name="text">
 | 
				
			||||||
 | 
					    <string>Export database as zip</string>
 | 
				
			||||||
 | 
					   </property>
 | 
				
			||||||
 | 
					  </action>
 | 
				
			||||||
 | 
					  <action name="actionCreate_database_from_zip">
 | 
				
			||||||
 | 
					   <property name="text">
 | 
				
			||||||
 | 
					    <string>Create database from zip</string>
 | 
				
			||||||
 | 
					   </property>
 | 
				
			||||||
 | 
					  </action>
 | 
				
			||||||
 </widget>
 | 
					 </widget>
 | 
				
			||||||
 <layoutdefault spacing="6" margin="11"/>
 | 
					 <layoutdefault spacing="6" margin="11"/>
 | 
				
			||||||
 <resources>
 | 
					 <resources>
 | 
				
			||||||
							
								
								
									
										390
									
								
								mainwindow_ui.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,390 @@
 | 
				
			|||||||
 | 
					// Generated by miqt-uic. To update this file, edit the .ui file in
 | 
				
			||||||
 | 
					// Qt Designer, and then run 'go generate'.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//go:generate miqt-uic -Qt6 -InFile mainwindow.ui -OutFile mainwindow_ui.go
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						qt "github.com/mappu/miqt/qt6"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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
 | 
				
			||||||
 | 
						menuConvert                      *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
 | 
				
			||||||
 | 
						actionExport_database_as_zip     *qt.QAction
 | 
				
			||||||
 | 
						actionCreate_database_from_zip   *qt.QAction
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// NewMainWindowUi creates all Qt widget classes for MainWindow.
 | 
				
			||||||
 | 
					func NewMainWindowUi() *MainWindowUi {
 | 
				
			||||||
 | 
						ui := &MainWindowUi{}
 | 
				
			||||||
 | 
						ui.MainWindow = qt.NewQMainWindow(nil)
 | 
				
			||||||
 | 
						MainWindow__objectName := qt.NewQAnyStringView3("MainWindow")
 | 
				
			||||||
 | 
						ui.MainWindow.SetObjectName(*MainWindow__objectName)
 | 
				
			||||||
 | 
						MainWindow__objectName.Delete() // setter copied value
 | 
				
			||||||
 | 
						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()
 | 
				
			||||||
 | 
						actionAbout_qbolt__objectName := qt.NewQAnyStringView3("actionAbout_qbolt")
 | 
				
			||||||
 | 
						ui.actionAbout_qbolt.SetObjectName(*actionAbout_qbolt__objectName)
 | 
				
			||||||
 | 
						actionAbout_qbolt__objectName.Delete() // setter copied value
 | 
				
			||||||
 | 
						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()
 | 
				
			||||||
 | 
						actionAbout_Qt__objectName := qt.NewQAnyStringView3("actionAbout_Qt")
 | 
				
			||||||
 | 
						ui.actionAbout_Qt.SetObjectName(*actionAbout_Qt__objectName)
 | 
				
			||||||
 | 
						actionAbout_Qt__objectName.Delete() // setter copied value
 | 
				
			||||||
 | 
						ui.actionOpen_database = qt.NewQAction()
 | 
				
			||||||
 | 
						actionOpen_database__objectName := qt.NewQAnyStringView3("actionOpen_database")
 | 
				
			||||||
 | 
						ui.actionOpen_database.SetObjectName(*actionOpen_database__objectName)
 | 
				
			||||||
 | 
						actionOpen_database__objectName.Delete() // setter copied value
 | 
				
			||||||
 | 
						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()
 | 
				
			||||||
 | 
						actionExit__objectName := qt.NewQAnyStringView3("actionExit")
 | 
				
			||||||
 | 
						ui.actionExit.SetObjectName(*actionExit__objectName)
 | 
				
			||||||
 | 
						actionExit__objectName.Delete() // setter copied value
 | 
				
			||||||
 | 
						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()
 | 
				
			||||||
 | 
						actionDisconnect__objectName := qt.NewQAnyStringView3("actionDisconnect")
 | 
				
			||||||
 | 
						ui.actionDisconnect.SetObjectName(*actionDisconnect__objectName)
 | 
				
			||||||
 | 
						actionDisconnect__objectName.Delete() // setter copied value
 | 
				
			||||||
 | 
						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()
 | 
				
			||||||
 | 
						actionDelete_bucket__objectName := qt.NewQAnyStringView3("actionDelete_bucket")
 | 
				
			||||||
 | 
						ui.actionDelete_bucket.SetObjectName(*actionDelete_bucket__objectName)
 | 
				
			||||||
 | 
						actionDelete_bucket__objectName.Delete() // setter copied value
 | 
				
			||||||
 | 
						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()
 | 
				
			||||||
 | 
						actionRefresh_buckets__objectName := qt.NewQAnyStringView3("actionRefresh_buckets")
 | 
				
			||||||
 | 
						ui.actionRefresh_buckets.SetObjectName(*actionRefresh_buckets__objectName)
 | 
				
			||||||
 | 
						actionRefresh_buckets__objectName.Delete() // setter copied value
 | 
				
			||||||
 | 
						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()
 | 
				
			||||||
 | 
						actionClear_selection__objectName := qt.NewQAnyStringView3("actionClear_selection")
 | 
				
			||||||
 | 
						ui.actionClear_selection.SetObjectName(*actionClear_selection__objectName)
 | 
				
			||||||
 | 
						actionClear_selection__objectName.Delete() // setter copied value
 | 
				
			||||||
 | 
						ui.actionNew_database = qt.NewQAction()
 | 
				
			||||||
 | 
						actionNew_database__objectName := qt.NewQAnyStringView3("actionNew_database")
 | 
				
			||||||
 | 
						ui.actionNew_database.SetObjectName(*actionNew_database__objectName)
 | 
				
			||||||
 | 
						actionNew_database__objectName.Delete() // setter copied value
 | 
				
			||||||
 | 
						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()
 | 
				
			||||||
 | 
						actionAdd_bucket__objectName := qt.NewQAnyStringView3("actionAdd_bucket")
 | 
				
			||||||
 | 
						ui.actionAdd_bucket.SetObjectName(*actionAdd_bucket__objectName)
 | 
				
			||||||
 | 
						actionAdd_bucket__objectName.Delete() // setter copied value
 | 
				
			||||||
 | 
						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()
 | 
				
			||||||
 | 
						actionOpen_database_as_read_only__objectName := qt.NewQAnyStringView3("actionOpen_database_as_read_only")
 | 
				
			||||||
 | 
						ui.actionOpen_database_as_read_only.SetObjectName(*actionOpen_database_as_read_only__objectName)
 | 
				
			||||||
 | 
						actionOpen_database_as_read_only__objectName.Delete() // setter copied value
 | 
				
			||||||
 | 
						ui.actionExport_database_as_zip = qt.NewQAction()
 | 
				
			||||||
 | 
						actionExport_database_as_zip__objectName := qt.NewQAnyStringView3("actionExport_database_as_zip")
 | 
				
			||||||
 | 
						ui.actionExport_database_as_zip.SetObjectName(*actionExport_database_as_zip__objectName)
 | 
				
			||||||
 | 
						actionExport_database_as_zip__objectName.Delete() // setter copied value
 | 
				
			||||||
 | 
						ui.actionCreate_database_from_zip = qt.NewQAction()
 | 
				
			||||||
 | 
						actionCreate_database_from_zip__objectName := qt.NewQAnyStringView3("actionCreate_database_from_zip")
 | 
				
			||||||
 | 
						ui.actionCreate_database_from_zip.SetObjectName(*actionCreate_database_from_zip__objectName)
 | 
				
			||||||
 | 
						actionCreate_database_from_zip__objectName.Delete() // setter copied value
 | 
				
			||||||
 | 
						ui.centralWidget = qt.NewQWidget(ui.MainWindow.QWidget)
 | 
				
			||||||
 | 
						centralWidget__objectName := qt.NewQAnyStringView3("centralWidget")
 | 
				
			||||||
 | 
						ui.centralWidget.SetObjectName(*centralWidget__objectName)
 | 
				
			||||||
 | 
						centralWidget__objectName.Delete() // setter copied value
 | 
				
			||||||
 | 
						ui.gridLayout = qt.NewQGridLayout(ui.centralWidget)
 | 
				
			||||||
 | 
						gridLayout__objectName := qt.NewQAnyStringView3("gridLayout")
 | 
				
			||||||
 | 
						ui.gridLayout.SetObjectName(*gridLayout__objectName)
 | 
				
			||||||
 | 
						gridLayout__objectName.Delete() // setter copied value
 | 
				
			||||||
 | 
						ui.gridLayout.SetContentsMargins(0, 0, 0, 0)
 | 
				
			||||||
 | 
						ui.gridLayout.SetSpacing(6)
 | 
				
			||||||
 | 
						ui.splitter = qt.NewQSplitter(ui.centralWidget)
 | 
				
			||||||
 | 
						splitter__objectName := qt.NewQAnyStringView3("splitter")
 | 
				
			||||||
 | 
						ui.splitter.SetObjectName(*splitter__objectName)
 | 
				
			||||||
 | 
						splitter__objectName.Delete() // setter copied value
 | 
				
			||||||
 | 
						ui.splitter.SetOrientation(qt.Horizontal)
 | 
				
			||||||
 | 
						ui.splitter.SetChildrenCollapsible(false)
 | 
				
			||||||
 | 
						ui.bucketTree = qt.NewQTreeWidget(ui.splitter.QWidget)
 | 
				
			||||||
 | 
						bucketTree__objectName := qt.NewQAnyStringView3("bucketTree")
 | 
				
			||||||
 | 
						ui.bucketTree.SetObjectName(*bucketTree__objectName)
 | 
				
			||||||
 | 
						bucketTree__objectName.Delete() // setter copied value
 | 
				
			||||||
 | 
						ui.bucketTree.SetContextMenuPolicy(qt.CustomContextMenu)
 | 
				
			||||||
 | 
						ui.bucketTree.SetUniformRowHeights(true)
 | 
				
			||||||
 | 
						ui.splitter.AddWidget(ui.bucketTree.QWidget)
 | 
				
			||||||
 | 
						ui.stackedWidget = qt.NewQStackedWidget(ui.splitter.QWidget)
 | 
				
			||||||
 | 
						stackedWidget__objectName := qt.NewQAnyStringView3("stackedWidget")
 | 
				
			||||||
 | 
						ui.stackedWidget.SetObjectName(*stackedWidget__objectName)
 | 
				
			||||||
 | 
						stackedWidget__objectName.Delete() // setter copied value
 | 
				
			||||||
 | 
						ui.databasePage = qt.NewQWidget(ui.stackedWidget.QWidget)
 | 
				
			||||||
 | 
						databasePage__objectName := qt.NewQAnyStringView3("databasePage")
 | 
				
			||||||
 | 
						ui.databasePage.SetObjectName(*databasePage__objectName)
 | 
				
			||||||
 | 
						databasePage__objectName.Delete() // setter copied value
 | 
				
			||||||
 | 
						ui.gridLayout_4 = qt.NewQGridLayout(ui.databasePage)
 | 
				
			||||||
 | 
						gridLayout_4__objectName := qt.NewQAnyStringView3("gridLayout_4")
 | 
				
			||||||
 | 
						ui.gridLayout_4.SetObjectName(*gridLayout_4__objectName)
 | 
				
			||||||
 | 
						gridLayout_4__objectName.Delete() // setter copied value
 | 
				
			||||||
 | 
						ui.gridLayout_4.SetContentsMargins(0, 0, 0, 0)
 | 
				
			||||||
 | 
						ui.gridLayout_4.SetSpacing(6)
 | 
				
			||||||
 | 
						ui.databaseTabWidget = qt.NewQTabWidget(ui.databasePage)
 | 
				
			||||||
 | 
						databaseTabWidget__objectName := qt.NewQAnyStringView3("databaseTabWidget")
 | 
				
			||||||
 | 
						ui.databaseTabWidget.SetObjectName(*databaseTabWidget__objectName)
 | 
				
			||||||
 | 
						databaseTabWidget__objectName.Delete() // setter copied value
 | 
				
			||||||
 | 
						ui.databasePropertiesTab = qt.NewQWidget(ui.databaseTabWidget.QWidget)
 | 
				
			||||||
 | 
						databasePropertiesTab__objectName := qt.NewQAnyStringView3("databasePropertiesTab")
 | 
				
			||||||
 | 
						ui.databasePropertiesTab.SetObjectName(*databasePropertiesTab__objectName)
 | 
				
			||||||
 | 
						databasePropertiesTab__objectName.Delete() // setter copied value
 | 
				
			||||||
 | 
						ui.gridLayout_2 = qt.NewQGridLayout(ui.databasePropertiesTab)
 | 
				
			||||||
 | 
						gridLayout_2__objectName := qt.NewQAnyStringView3("gridLayout_2")
 | 
				
			||||||
 | 
						ui.gridLayout_2.SetObjectName(*gridLayout_2__objectName)
 | 
				
			||||||
 | 
						gridLayout_2__objectName.Delete() // setter copied value
 | 
				
			||||||
 | 
						ui.gridLayout_2.SetContentsMargins(3, 3, 3, 3)
 | 
				
			||||||
 | 
						ui.gridLayout_2.SetSpacing(6)
 | 
				
			||||||
 | 
						ui.databasePropertiesArea = qt.NewQPlainTextEdit(ui.databasePropertiesTab)
 | 
				
			||||||
 | 
						databasePropertiesArea__objectName := qt.NewQAnyStringView3("databasePropertiesArea")
 | 
				
			||||||
 | 
						ui.databasePropertiesArea.SetObjectName(*databasePropertiesArea__objectName)
 | 
				
			||||||
 | 
						databasePropertiesArea__objectName.Delete() // setter copied value
 | 
				
			||||||
 | 
						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.NewQWidget(ui.stackedWidget.QWidget)
 | 
				
			||||||
 | 
						bucketPage__objectName := qt.NewQAnyStringView3("bucketPage")
 | 
				
			||||||
 | 
						ui.bucketPage.SetObjectName(*bucketPage__objectName)
 | 
				
			||||||
 | 
						bucketPage__objectName.Delete() // setter copied value
 | 
				
			||||||
 | 
						ui.gridLayout_3 = qt.NewQGridLayout(ui.bucketPage)
 | 
				
			||||||
 | 
						gridLayout_3__objectName := qt.NewQAnyStringView3("gridLayout_3")
 | 
				
			||||||
 | 
						ui.gridLayout_3.SetObjectName(*gridLayout_3__objectName)
 | 
				
			||||||
 | 
						gridLayout_3__objectName.Delete() // setter copied value
 | 
				
			||||||
 | 
						ui.gridLayout_3.SetContentsMargins(0, 0, 0, 0)
 | 
				
			||||||
 | 
						ui.gridLayout_3.SetSpacing(6)
 | 
				
			||||||
 | 
						ui.bucketTabWidget = qt.NewQTabWidget(ui.bucketPage)
 | 
				
			||||||
 | 
						bucketTabWidget__objectName := qt.NewQAnyStringView3("bucketTabWidget")
 | 
				
			||||||
 | 
						ui.bucketTabWidget.SetObjectName(*bucketTabWidget__objectName)
 | 
				
			||||||
 | 
						bucketTabWidget__objectName.Delete() // setter copied value
 | 
				
			||||||
 | 
						ui.bucketPropertiesTab = qt.NewQWidget(ui.bucketTabWidget.QWidget)
 | 
				
			||||||
 | 
						bucketPropertiesTab__objectName := qt.NewQAnyStringView3("bucketPropertiesTab")
 | 
				
			||||||
 | 
						ui.bucketPropertiesTab.SetObjectName(*bucketPropertiesTab__objectName)
 | 
				
			||||||
 | 
						bucketPropertiesTab__objectName.Delete() // setter copied value
 | 
				
			||||||
 | 
						ui.gridLayout_5 = qt.NewQGridLayout(ui.bucketPropertiesTab)
 | 
				
			||||||
 | 
						gridLayout_5__objectName := qt.NewQAnyStringView3("gridLayout_5")
 | 
				
			||||||
 | 
						ui.gridLayout_5.SetObjectName(*gridLayout_5__objectName)
 | 
				
			||||||
 | 
						gridLayout_5__objectName.Delete() // setter copied value
 | 
				
			||||||
 | 
						ui.gridLayout_5.SetContentsMargins(3, 3, 3, 3)
 | 
				
			||||||
 | 
						ui.gridLayout_5.SetSpacing(6)
 | 
				
			||||||
 | 
						ui.bucketPropertiesArea = qt.NewQPlainTextEdit(ui.bucketPropertiesTab)
 | 
				
			||||||
 | 
						bucketPropertiesArea__objectName := qt.NewQAnyStringView3("bucketPropertiesArea")
 | 
				
			||||||
 | 
						ui.bucketPropertiesArea.SetObjectName(*bucketPropertiesArea__objectName)
 | 
				
			||||||
 | 
						bucketPropertiesArea__objectName.Delete() // setter copied value
 | 
				
			||||||
 | 
						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.NewQWidget(ui.bucketTabWidget.QWidget)
 | 
				
			||||||
 | 
						bucketDataTab__objectName := qt.NewQAnyStringView3("bucketDataTab")
 | 
				
			||||||
 | 
						ui.bucketDataTab.SetObjectName(*bucketDataTab__objectName)
 | 
				
			||||||
 | 
						bucketDataTab__objectName.Delete() // setter copied value
 | 
				
			||||||
 | 
						ui.gridLayout_6 = qt.NewQGridLayout(ui.bucketDataTab)
 | 
				
			||||||
 | 
						gridLayout_6__objectName := qt.NewQAnyStringView3("gridLayout_6")
 | 
				
			||||||
 | 
						ui.gridLayout_6.SetObjectName(*gridLayout_6__objectName)
 | 
				
			||||||
 | 
						gridLayout_6__objectName.Delete() // setter copied value
 | 
				
			||||||
 | 
						ui.gridLayout_6.SetContentsMargins(3, 3, 3, 3)
 | 
				
			||||||
 | 
						ui.gridLayout_6.SetSpacing(6)
 | 
				
			||||||
 | 
						ui.bucketData = qt.NewQTreeWidget(ui.bucketDataTab)
 | 
				
			||||||
 | 
						bucketData__objectName := qt.NewQAnyStringView3("bucketData")
 | 
				
			||||||
 | 
						ui.bucketData.SetObjectName(*bucketData__objectName)
 | 
				
			||||||
 | 
						bucketData__objectName.Delete() // setter copied value
 | 
				
			||||||
 | 
						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.NewQPushButton(ui.bucketDataTab)
 | 
				
			||||||
 | 
						AddDataButton__objectName := qt.NewQAnyStringView3("AddDataButton")
 | 
				
			||||||
 | 
						ui.AddDataButton.SetObjectName(*AddDataButton__objectName)
 | 
				
			||||||
 | 
						AddDataButton__objectName.Delete() // setter copied value
 | 
				
			||||||
 | 
						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.NewQPushButton(ui.bucketDataTab)
 | 
				
			||||||
 | 
						DeleteDataButton__objectName := qt.NewQAnyStringView3("DeleteDataButton")
 | 
				
			||||||
 | 
						ui.DeleteDataButton.SetObjectName(*DeleteDataButton__objectName)
 | 
				
			||||||
 | 
						DeleteDataButton__objectName.Delete() // setter copied value
 | 
				
			||||||
 | 
						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.NewQMenuBar(ui.MainWindow.QWidget)
 | 
				
			||||||
 | 
						menuBar__objectName := qt.NewQAnyStringView3("menuBar")
 | 
				
			||||||
 | 
						ui.menuBar.SetObjectName(*menuBar__objectName)
 | 
				
			||||||
 | 
						menuBar__objectName.Delete() // setter copied value
 | 
				
			||||||
 | 
						ui.menuBar.Resize(668, 22)
 | 
				
			||||||
 | 
						ui.menuFile = qt.NewQMenu(ui.menuBar.QWidget)
 | 
				
			||||||
 | 
						menuFile__objectName := qt.NewQAnyStringView3("menuFile")
 | 
				
			||||||
 | 
						ui.menuFile.SetObjectName(*menuFile__objectName)
 | 
				
			||||||
 | 
						menuFile__objectName.Delete() // setter copied value
 | 
				
			||||||
 | 
						ui.menuConvert = qt.NewQMenu(ui.menuFile.QWidget)
 | 
				
			||||||
 | 
						menuConvert__objectName := qt.NewQAnyStringView3("menuConvert")
 | 
				
			||||||
 | 
						ui.menuConvert.SetObjectName(*menuConvert__objectName)
 | 
				
			||||||
 | 
						menuConvert__objectName.Delete() // setter copied value
 | 
				
			||||||
 | 
						ui.menuConvert.QWidget.AddAction(ui.actionExport_database_as_zip)
 | 
				
			||||||
 | 
						ui.menuConvert.QWidget.AddAction(ui.actionCreate_database_from_zip)
 | 
				
			||||||
 | 
						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.AddMenu(ui.menuConvert)
 | 
				
			||||||
 | 
						ui.menuFile.AddSeparator()
 | 
				
			||||||
 | 
						ui.menuFile.AddSeparator()
 | 
				
			||||||
 | 
						ui.menuFile.QWidget.AddAction(ui.actionExit)
 | 
				
			||||||
 | 
						ui.menuHelp = qt.NewQMenu(ui.menuBar.QWidget)
 | 
				
			||||||
 | 
						menuHelp__objectName := qt.NewQAnyStringView3("menuHelp")
 | 
				
			||||||
 | 
						ui.menuHelp.SetObjectName(*menuHelp__objectName)
 | 
				
			||||||
 | 
						menuHelp__objectName.Delete() // setter copied value
 | 
				
			||||||
 | 
						ui.menuHelp.QWidget.AddAction(ui.actionAbout_qbolt)
 | 
				
			||||||
 | 
						ui.menuHelp.QWidget.AddAction(ui.actionAbout_Qt)
 | 
				
			||||||
 | 
						ui.menuView = qt.NewQMenu(ui.menuBar.QWidget)
 | 
				
			||||||
 | 
						menuView__objectName := qt.NewQAnyStringView3("menuView")
 | 
				
			||||||
 | 
						ui.menuView.SetObjectName(*menuView__objectName)
 | 
				
			||||||
 | 
						menuView__objectName.Delete() // setter copied value
 | 
				
			||||||
 | 
						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.NewQToolBar(ui.MainWindow.QWidget)
 | 
				
			||||||
 | 
						mainToolBar__objectName := qt.NewQAnyStringView3("mainToolBar")
 | 
				
			||||||
 | 
						ui.mainToolBar.SetObjectName(*mainToolBar__objectName)
 | 
				
			||||||
 | 
						mainToolBar__objectName.Delete() // setter copied value
 | 
				
			||||||
 | 
						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.NewQStatusBar(ui.MainWindow.QWidget)
 | 
				
			||||||
 | 
						statusBar__objectName := qt.NewQAnyStringView3("statusBar")
 | 
				
			||||||
 | 
						ui.statusBar.SetObjectName(*statusBar__objectName)
 | 
				
			||||||
 | 
						statusBar__objectName.Delete() // setter copied value
 | 
				
			||||||
 | 
						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_qbolt.SetShortcut(qt.NewQKeySequence2(qt.QMainWindow_Tr("F1")))
 | 
				
			||||||
 | 
						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.actionRefresh_buckets.SetShortcut(qt.NewQKeySequence2(qt.QMainWindow_Tr("F5")))
 | 
				
			||||||
 | 
						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.actionExport_database_as_zip.SetText(qt.QMainWindow_Tr("Export database as zip"))
 | 
				
			||||||
 | 
						ui.actionCreate_database_from_zip.SetText(qt.QMainWindow_Tr("Create database from zip"))
 | 
				
			||||||
 | 
						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.menuConvert.SetTitle(qt.QMenu_Tr("Convert"))
 | 
				
			||||||
 | 
						ui.menuHelp.SetTitle(qt.QMenuBar_Tr("Help"))
 | 
				
			||||||
 | 
						ui.menuView.SetTitle(qt.QMenuBar_Tr("&View"))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										219
									
								
								qbolt/boltdb.cpp
									
									
									
									
									
								
							
							
						
						@@ -1,219 +0,0 @@
 | 
				
			|||||||
#include "boltdb.h"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <QtEndian>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
BoltDB::BoltDB()
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
BoltDB* BoltDB::createFrom(QString filePath, QString &errorOut)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    QByteArray filePathBytes(filePath.toUtf8());
 | 
					 | 
				
			||||||
    GoString filePathGS = Interop::toGoString_WeakRef(&filePathBytes);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    auto open_ret = ::Bolt_Open(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(QStringList 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(QStringList 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(QStringList 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(QStringList 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(QStringList 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(QStringList bucketPath, QString& errorOut, std::function<void(QByteArray, int64_t)> 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<qint64>(b.mid(0, 8));
 | 
					 | 
				
			||||||
        cb(b.mid(8), dataLen);
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
bool BoltDB::getData(QStringList bucketPath, QByteArray key, std::function<void(QByteArray)> onSuccess, std::function<void(QString)> 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<void(QByteArray)> onSuccess, std::function<void(QString)> 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(QStringList bucketPath, std::function<void(QByteArray)> onSuccess, std::function<void(QString)> 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);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,46 +0,0 @@
 | 
				
			|||||||
#ifndef BOLTDB_H
 | 
					 | 
				
			||||||
#define BOLTDB_H
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include "interop.h"
 | 
					 | 
				
			||||||
#include <functional>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
typedef std::function<void(QByteArray)> NameReciever;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class BoltDB
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
protected:
 | 
					 | 
				
			||||||
    BoltDB();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    GoInt64 gmsDbRef;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
public:
 | 
					 | 
				
			||||||
    static BoltDB* createFrom(QString filePath, QString &errorOut);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    bool listBucketsAtRoot(QString& errorOut, NameReciever cb);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    bool listBuckets(QStringList bucketPath, QString& errorOut, NameReciever cb);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    bool addBucket(QStringList bucketPath, QByteArray bucketName, QString& errorOut);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    bool deleteBucket(QStringList bucketPath, QByteArray bucketName, QString& errorOut);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    bool setItem(QStringList bucketPath, QByteArray keyName, QByteArray value, QString& errorOut);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    bool deleteItem(QStringList bucketPath, QByteArray keyName, QString& errorOut);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    bool listKeys(QStringList 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 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);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ~BoltDB();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
protected:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    bool pumpNext(GoInt64 jobRef, QString& errorOut, NameReciever cb);
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#endif // BOLTDB_H
 | 
					 | 
				
			||||||
@@ -1,42 +0,0 @@
 | 
				
			|||||||
#include "interop.h"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <QStringList>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Interop::Interop()
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
GoString Interop::toGoString_WeakRef(QByteArray *qba) {
 | 
					 | 
				
			||||||
    return GoString{qba->data(), qba->length()};
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
int64_t Interop::GetMagic() {
 | 
					 | 
				
			||||||
    return ::GetMagic();
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
GoSliceManagedWrapper::GoSliceManagedWrapper(QStringList *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).toUtf8() );
 | 
					 | 
				
			||||||
        strings[i].p = rawStrings[i].data();
 | 
					 | 
				
			||||||
        strings[i].n = rawStrings[i].size();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    slice.data = static_cast<void*>(strings);
 | 
					 | 
				
			||||||
    slice.len  = qsl->size(); //  * sizeof(GoString);
 | 
					 | 
				
			||||||
    slice.cap  = slice.len;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
GoSliceManagedWrapper::~GoSliceManagedWrapper()
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    delete[] strings;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,31 +0,0 @@
 | 
				
			|||||||
#ifndef INTEROP_H
 | 
					 | 
				
			||||||
#define INTEROP_H
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include "qbolt_cgo.h"
 | 
					 | 
				
			||||||
#include <QString>
 | 
					 | 
				
			||||||
#include <QList>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class GoSliceManagedWrapper {
 | 
					 | 
				
			||||||
    Q_DISABLE_COPY(GoSliceManagedWrapper)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
public:
 | 
					 | 
				
			||||||
    GoSliceManagedWrapper(QStringList *qsl);
 | 
					 | 
				
			||||||
    ~GoSliceManagedWrapper();
 | 
					 | 
				
			||||||
protected:
 | 
					 | 
				
			||||||
    QList<QByteArray> rawStrings;
 | 
					 | 
				
			||||||
public:
 | 
					 | 
				
			||||||
    GoSlice slice;
 | 
					 | 
				
			||||||
    GoString *strings;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class Interop
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
public:
 | 
					 | 
				
			||||||
    Interop();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    static GoString toGoString_WeakRef(QByteArray *qba);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    static int64_t GetMagic();
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#endif // INTEROP_H
 | 
					 | 
				
			||||||
@@ -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;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,25 +0,0 @@
 | 
				
			|||||||
#ifndef ITEMWINDOW_H
 | 
					 | 
				
			||||||
#define ITEMWINDOW_H
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <QDialog>
 | 
					 | 
				
			||||||
#include <QPlainTextEdit>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
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
 | 
					 | 
				
			||||||
@@ -1,23 +0,0 @@
 | 
				
			|||||||
#include "mainwindow.h"
 | 
					 | 
				
			||||||
#include <QApplication>
 | 
					 | 
				
			||||||
#include "interop.h"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <QDebug>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
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();
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,430 +0,0 @@
 | 
				
			|||||||
#include "mainwindow.h"
 | 
					 | 
				
			||||||
#include "ui_mainwindow.h"
 | 
					 | 
				
			||||||
#include "itemwindow.h"
 | 
					 | 
				
			||||||
#include "boltdb.h"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <QFileDialog>
 | 
					 | 
				
			||||||
#include <QMessageBox>
 | 
					 | 
				
			||||||
#include <QJsonDocument>
 | 
					 | 
				
			||||||
#include <QInputDialog>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
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;
 | 
					 | 
				
			||||||
#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*>() )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void MainWindow::on_actionNew_database_triggered()
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    QString file = QFileDialog::getSaveFileName(this, tr("Save new bolt database as..."));
 | 
					 | 
				
			||||||
    if (file.length()) {
 | 
					 | 
				
			||||||
        openDatabase(file);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void MainWindow::on_actionOpen_database_triggered()
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    QString file = QFileDialog::getOpenFileName(this, tr("Select bolt database..."));
 | 
					 | 
				
			||||||
    if (file.length()) {
 | 
					 | 
				
			||||||
        openDatabase(file);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void MainWindow::openDatabase(QString file)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    // Open
 | 
					 | 
				
			||||||
    QString error;
 | 
					 | 
				
			||||||
    auto *bdb = BoltDB::createFrom(file, 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);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void MainWindow::refreshBucketTree(QTreeWidgetItem* itm)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    QTreeWidgetItem *top = itm;
 | 
					 | 
				
			||||||
    QStringList browsePath;
 | 
					 | 
				
			||||||
    while(top->parent() != nullptr) {
 | 
					 | 
				
			||||||
        browsePath.push_front(top->text(0));
 | 
					 | 
				
			||||||
        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, QString::fromUtf8(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(),
 | 
					 | 
				
			||||||
        "<b>QBolt</b><br>Graphical interface for managing Bolt databases<br><br>"
 | 
					 | 
				
			||||||
        "- <a href='https://github.com/boltdb/bolt'>About BoltDB</a><br>"
 | 
					 | 
				
			||||||
        "- <a href='http://www.famfamfam.com/lab/icons/silk/'>FamFamFam "Silk" icon set</a><br>"
 | 
					 | 
				
			||||||
        "- <a href='https://code.ivysaur.me/qbolt'>QBolt homepage</a><br>"
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
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();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        QStringList browse;
 | 
					 | 
				
			||||||
        QTreeWidgetItem *top = current;
 | 
					 | 
				
			||||||
        while (top->parent() != nullptr) {
 | 
					 | 
				
			||||||
            browse.push_front(top->text(0));
 | 
					 | 
				
			||||||
            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, QStringList 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, QString::fromUtf8(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; \
 | 
					 | 
				
			||||||
    QStringList browse; \
 | 
					 | 
				
			||||||
    while(top->parent() != nullptr) { \
 | 
					 | 
				
			||||||
        browse.push_front(top->text(0)); \
 | 
					 | 
				
			||||||
        top = top->parent(); \
 | 
					 | 
				
			||||||
    } \
 | 
					 | 
				
			||||||
    auto *bdb = GET_BDB(top);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void MainWindow::openEditor(BoltDB *bdb, QStringList 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();
 | 
					 | 
				
			||||||
    QString key = model->data(model->index(index.row(), 0), 0).toString();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // DB lookup
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    bdb->getData(
 | 
					 | 
				
			||||||
        browse,
 | 
					 | 
				
			||||||
        key.toUtf8(),
 | 
					 | 
				
			||||||
        [=](QByteArray content) {
 | 
					 | 
				
			||||||
            openEditor(bdb, browse, key.toUtf8(), 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
 | 
					 | 
				
			||||||
    QString bucketToDelete = itm->text(0);
 | 
					 | 
				
			||||||
    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) {
 | 
					 | 
				
			||||||
        return;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // One level down
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    browse.pop_back();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    QString err;
 | 
					 | 
				
			||||||
    if (! bdb->deleteBucket(browse, bucketToDelete.toUtf8(), err)) {
 | 
					 | 
				
			||||||
        QMessageBox qmb;
 | 
					 | 
				
			||||||
        qmb.setText(tr("Error removing bucket: %1").arg(err));
 | 
					 | 
				
			||||||
        qmb.exec();
 | 
					 | 
				
			||||||
        return;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Refresh bucket list
 | 
					 | 
				
			||||||
    refreshBucketTree(itm->parent()); // sub-tree only
 | 
					 | 
				
			||||||
    ui->bucketTree->expandItem(itm->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]->text(0).toUtf8(), 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) );
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,68 +0,0 @@
 | 
				
			|||||||
#ifndef MAINWINDOW_H
 | 
					 | 
				
			||||||
#define MAINWINDOW_H
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include "boltdb.h"
 | 
					 | 
				
			||||||
#include <QMainWindow>
 | 
					 | 
				
			||||||
#include <QMenu>
 | 
					 | 
				
			||||||
#include <QTreeWidgetItem>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
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();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
protected:
 | 
					 | 
				
			||||||
    void openDatabase(QString file);
 | 
					 | 
				
			||||||
    void refreshBucketTree(QTreeWidgetItem* top);
 | 
					 | 
				
			||||||
    void refreshData(BoltDB *bdb, QStringList browse);
 | 
					 | 
				
			||||||
    void openEditor(BoltDB *bdb, QStringList saveAs, QByteArray saveAsKey, QByteArray currentContent);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
private:
 | 
					 | 
				
			||||||
    Ui::MainWindow *ui;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    QMenu *databaseContext;
 | 
					 | 
				
			||||||
    QMenu *bucketContext;
 | 
					 | 
				
			||||||
    QTreeWidgetItem* lastContextSelection;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#endif // MAINWINDOW_H
 | 
					 | 
				
			||||||
@@ -1,45 +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
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
linux: {
 | 
					 | 
				
			||||||
    QMAKE_LIBS += $$_PRO_FILE_PWD_/../build/linux/qbolt.a
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
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
 | 
					 | 
				
			||||||
@@ -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
 | 
					 | 
				
			||||||
							
								
								
									
										17
									
								
								resources.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,17 @@
 | 
				
			|||||||
 | 
					package main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//go:generate miqt-rcc -Input "resources.qrc" -OutputGo "resources.go" -OutputRcc "resources.rcc" -Qt6
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"embed"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						qt "github.com/mappu/miqt/qt6"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//go:embed resources.rcc
 | 
				
			||||||
 | 
					var _resourceRcc []byte
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func init() {
 | 
				
			||||||
 | 
						_ = embed.FS{}
 | 
				
			||||||
 | 
						qt.QResource_RegisterResourceWithRccData(&_resourceRcc[0])
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										
											BIN
										
									
								
								resources.rcc
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 Before Width: | Height: | Size: 733 B After Width: | Height: | Size: 733 B  | 
| 
		 Before Width: | Height: | Size: 685 B After Width: | Height: | Size: 685 B  | 
| 
		 Before Width: | Height: | Size: 541 B After Width: | Height: | Size: 541 B  | 
| 
		 Before Width: | Height: | Size: 390 B After Width: | Height: | Size: 390 B  | 
| 
		 Before Width: | Height: | Size: 658 B After Width: | Height: | Size: 658 B  | 
| 
		 Before Width: | Height: | Size: 763 B After Width: | Height: | Size: 763 B  | 
| 
		 Before Width: | Height: | Size: 775 B After Width: | Height: | Size: 775 B  | 
| 
		 Before Width: | Height: | Size: 715 B After Width: | Height: | Size: 715 B  | 
| 
		 Before Width: | Height: | Size: 796 B After Width: | Height: | Size: 796 B  | 
| 
		 Before Width: | Height: | Size: 688 B After Width: | Height: | Size: 688 B  | 
| 
		 Before Width: | Height: | Size: 778 B After Width: | Height: | Size: 778 B  | 
| 
		 Before Width: | Height: | Size: 635 B After Width: | Height: | Size: 635 B  | 
							
								
								
									
										
											BIN
										
									
								
								rsrc/qbolt.ico
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 1.1 KiB  | 
| 
		 Before Width: | Height: | Size: 566 B After Width: | Height: | Size: 566 B  | 
| 
		 Before Width: | Height: | Size: 663 B After Width: | Height: | Size: 663 B  | 
| 
		 Before Width: | Height: | Size: 660 B After Width: | Height: | Size: 660 B  | 
							
								
								
									
										40
									
								
								util.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,40 @@
 | 
				
			|||||||
 | 
					package main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"strconv"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func getDisplayName(qba []byte) string {
 | 
				
			||||||
 | 
						if len(qba) ==0 {
 | 
				
			||||||
 | 
							return "<empty>"
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ret := strconv.Quote(string(qba))
 | 
				
			||||||
 | 
						return ret[1 : len(ret)-1]
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 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]
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// CopySliceAdd copies a slice and adds one more thing on the end.
 | 
				
			||||||
 | 
					func CopySliceAdd[T comparable](base []T, and T) []T {	
 | 
				
			||||||
 | 
						ret := make([]T, len(base)+1)
 | 
				
			||||||
 | 
						copy(ret, base)
 | 
				
			||||||
 | 
						ret[len(ret)-1] = and
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						return ret
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Apply creates a new slice where every element of the input arr is transformed.
 | 
				
			||||||
 | 
					func Apply[T comparable](arr []T, transform func(T) T) []T {
 | 
				
			||||||
 | 
						ret := make([]T, len(arr))
 | 
				
			||||||
 | 
						for i := 0; i < len(arr); i++ {
 | 
				
			||||||
 | 
							ret[i] = transform(arr[i])
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return ret
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										17
									
								
								util_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -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")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										50
									
								
								windows-manifest.template.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -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
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||