From da03fbe8f2a1e38f76f2f4ba67494b22963efd20 Mon Sep 17 00:00:00 2001 From: sawka Date: Sun, 12 May 2024 09:52:12 -0700 Subject: [PATCH] blockstore setup/migrations, wavebase setup --- db/db.go | 9 + db/migrations-blockstore/000001_init.down.sql | 3 + db/migrations-blockstore/000001_init.up.sql | 20 +++ frontend/app.tsx | 2 + frontend/main.ts | 2 - go.mod | 15 +- go.sum | 26 ++- main.go | 17 +- pkg/blockstore/blockstore.go | 51 ++++++ pkg/blockstore/dbsetup.go | 162 ++++++++++++++++++ pkg/wavebase/wavebase.go | 90 ++++++++++ 11 files changed, 389 insertions(+), 8 deletions(-) create mode 100644 db/db.go create mode 100644 db/migrations-blockstore/000001_init.down.sql create mode 100644 db/migrations-blockstore/000001_init.up.sql create mode 100644 pkg/blockstore/blockstore.go create mode 100644 pkg/blockstore/dbsetup.go create mode 100644 pkg/wavebase/wavebase.go diff --git a/db/db.go b/db/db.go new file mode 100644 index 000000000..b04d199a7 --- /dev/null +++ b/db/db.go @@ -0,0 +1,9 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + +package db + +import "embed" + +//go:embed migrations-blockstore/*.sql +var BlockstoreMigrationFS embed.FS diff --git a/db/migrations-blockstore/000001_init.down.sql b/db/migrations-blockstore/000001_init.down.sql new file mode 100644 index 000000000..1cb72b3fd --- /dev/null +++ b/db/migrations-blockstore/000001_init.down.sql @@ -0,0 +1,3 @@ +DROP TABLE block_file; + +DROP TABLE block_data; diff --git a/db/migrations-blockstore/000001_init.up.sql b/db/migrations-blockstore/000001_init.up.sql new file mode 100644 index 000000000..dd4b70a0e --- /dev/null +++ b/db/migrations-blockstore/000001_init.up.sql @@ -0,0 +1,20 @@ +CREATE TABLE db_block_file ( + blockid varchar(36) NOT NULL, + name varchar(200) NOT NULL, + maxsize bigint NOT NULL, + circular boolean NOT NULL, + size bigint NOT NULL, + createdts bigint NOT NULL, + modts bigint NOT NULL, + meta json NOT NULL, + PRIMARY KEY (blockid, name) +); + +CREATE TABLE db_block_data ( + blockid varchar(36) NOT NULL, + name varchar(200) NOT NULL, + partidx int NOT NULL, + data blob NOT NULL, + PRIMARY KEY(blockid, name, partidx) +); + diff --git a/frontend/app.tsx b/frontend/app.tsx index afabc94c6..994695f1a 100644 --- a/frontend/app.tsx +++ b/frontend/app.tsx @@ -6,6 +6,8 @@ import { Greet } from "./bindings/main/GreetService.js"; import { Events } from "@wailsio/runtime"; import * as rx from "rxjs"; +import "/public/style.less"; + const jotaiStore = createStore(); const counterSubject = rx.interval(1000).pipe(rx.map((i) => i)); const timeAtom = jotai.atom("No time yet"); diff --git a/frontend/main.ts b/frontend/main.ts index 5b81fb9cd..488e54c56 100644 --- a/frontend/main.ts +++ b/frontend/main.ts @@ -5,8 +5,6 @@ import * as React from "react"; import { createRoot } from "react-dom/client"; import { App } from "./app.tsx"; -import "./public/style.less"; - document.addEventListener("DOMContentLoaded", () => { let reactElem = React.createElement(App, null, null); let elem = document.getElementById("main"); diff --git a/go.mod b/go.mod index d7160d1ad..757ffca7d 100644 --- a/go.mod +++ b/go.mod @@ -1,10 +1,16 @@ -module thenextwave +module github.com/wavetermdev/thenextwave go 1.22 toolchain go1.22.1 -require github.com/wailsapp/wails/v3 v3.0.0-alpha.0 +require ( + github.com/golang-migrate/migrate/v4 v4.17.1 + github.com/jmoiron/sqlx v1.4.0 + github.com/mattn/go-sqlite3 v1.14.22 + github.com/sawka/txwrap v0.1.2 + github.com/wailsapp/wails/v3 v3.0.0-alpha.0 +) require ( dario.cat/mergo v1.0.0 // indirect @@ -21,7 +27,9 @@ require ( github.com/go-ole/go-ole v1.2.6 // indirect github.com/godbus/dbus/v5 v5.1.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect - github.com/google/uuid v1.3.0 // indirect + github.com/google/uuid v1.4.0 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e // indirect github.com/kevinburke/ssh_config v1.2.0 // indirect @@ -40,6 +48,7 @@ require ( github.com/wailsapp/go-webview2 v1.0.9 // indirect github.com/wailsapp/mimetype v1.4.1 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect + go.uber.org/atomic v1.7.0 // indirect golang.org/x/crypto v0.21.0 // indirect golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df // indirect golang.org/x/mod v0.12.0 // indirect diff --git a/go.sum b/go.sum index 5b1c017e9..0ff590522 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= @@ -38,18 +40,29 @@ github.com/go-git/go-git/v5 v5.11.0 h1:XIZc1p+8YzypNr34itUfSvYJcv+eYdTnTvOZ2vD3c github.com/go-git/go-git/v5 v5.11.0/go.mod h1:6GFcX2P3NM7FPBfpePbpLd21XxsgdAt+lKqXmCUiUCY= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= +github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/golang-migrate/migrate/v4 v4.17.1 h1:4zQ6iqL6t6AiItphxJctQb3cFqWiSpMnX7wLTPnnYO4= +github.com/golang-migrate/migrate/v4 v4.17.1/go.mod h1:m8hinFyWBn0SA4QKHuKh175Pm9wjmxj3S2Mia7dbXzM= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= -github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= +github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e h1:Q3+PugElBCf4PFpxhErSzU3/PY5sFL5Z6rfv4AbGAck= github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e/go.mod h1:alcuEEnZsY1WQsagKhZDsoPCRoOijYqhZvPwLG0kzVs= +github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o= +github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= @@ -63,6 +76,8 @@ github.com/leaanthony/go-ansi-parser v1.6.1 h1:xd8bzARK3dErqkPFtoF9F3/HgN8UQk0ed github.com/leaanthony/go-ansi-parser v1.6.1/go.mod h1:+vva/2y4alzVmmIEpk9QDhA7vLC5zKDTRwfZGOp3IWU= github.com/leaanthony/u v1.1.0 h1:2n0d2BwPVXSUq5yhe8lJPHdxevE2qK5G99PMStMZMaI= github.com/leaanthony/u v1.1.0/go.mod h1:9+o6hejoRljvZ3BzdYlVL0JYCwtnAsVuN9pVTQcaRfI= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lmittmann/tint v1.0.4 h1:LeYihpJ9hyGvE0w+K2okPTGUdVLfng1+nDNVR4vWISc= github.com/lmittmann/tint v1.0.4/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE= github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE= @@ -72,6 +87,8 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= +github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M= github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= @@ -89,6 +106,8 @@ github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDN github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM= github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA= +github.com/sawka/txwrap v0.1.2 h1:v8xS0Z1LE7/6vMZA81PYihI+0TSR6Zm1MalzzBIuXKc= +github.com/sawka/txwrap v0.1.2/go.mod h1:T3nlw2gVpuolo6/XEetvBbk1oMXnY978YmBFy1UyHvw= github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= @@ -96,6 +115,7 @@ github.com/skeema/knownhosts v1.2.1 h1:SHWdIUa82uGZz+F+47k8SY4QhhI291cXCpopT1lK2 github.com/skeema/knownhosts v1.2.1/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= @@ -106,6 +126,8 @@ github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4X github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= diff --git a/main.go b/main.go index 2005a5017..c857855dc 100644 --- a/main.go +++ b/main.go @@ -10,6 +10,9 @@ import ( "log" "time" + "github.com/wavetermdev/thenextwave/pkg/blockstore" + "github.com/wavetermdev/thenextwave/pkg/wavebase" + "github.com/wailsapp/wails/v3/pkg/application" ) @@ -26,6 +29,18 @@ func (g *GreetService) Greet(name string) string { } func main() { + err := wavebase.EnsureWaveHomeDir() + if err != nil { + log.Printf("error ensuring wave home dir: %v\n", err) + return + } + log.Printf("wave home dir: %s\n", wavebase.GetWaveHomeDir()) + err = blockstore.InitBlockstore() + if err != nil { + log.Printf("error initializing blockstore: %v\n", err) + return + } + app := application.New(application.Options{ Name: "NextWave", Description: "The Next Wave Terminal", @@ -65,7 +80,7 @@ func main() { }() // blocking - err := app.Run() + err = app.Run() // If an error occurred while running the application, log it and exit. if err != nil { diff --git a/pkg/blockstore/blockstore.go b/pkg/blockstore/blockstore.go new file mode 100644 index 000000000..39b8455a4 --- /dev/null +++ b/pkg/blockstore/blockstore.go @@ -0,0 +1,51 @@ +// Copyright 2024, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + +package blockstore + +import ( + "database/sql/driver" + "encoding/json" +) + +type FileOptsType struct { + MaxSize int64 + Circular bool + IJson bool +} + +func (f *FileOptsType) Scan(value interface{}) error { + return json.Unmarshal(value.([]byte), f) +} + +func (f FileOptsType) Value() (driver.Value, error) { + barr, err := json.Marshal(f) + if err != nil { + return nil, err + } + return string(barr), nil +} + +type FileMeta map[string]any + +func (m *FileMeta) Scan(value interface{}) error { + return json.Unmarshal(value.([]byte), m) +} + +func (m FileMeta) Value() (driver.Value, error) { + barr, err := json.Marshal(m) + if err != nil { + return nil, err + } + return string(barr), nil +} + +type BlockFile struct { + BlockId string `json:"blockid"` + Name string `json:"name"` + Size int64 `json:"size"` + CreatedTs int64 `json:"createdts"` + ModTs int64 `json:"modts"` + Opts FileOptsType `json:"opts"` + Meta FileMeta `json:"meta"` +} diff --git a/pkg/blockstore/dbsetup.go b/pkg/blockstore/dbsetup.go new file mode 100644 index 000000000..500158d7e --- /dev/null +++ b/pkg/blockstore/dbsetup.go @@ -0,0 +1,162 @@ +package blockstore + +// setup for blockstore db +// includes migration support and txwrap setup + +import ( + "context" + "fmt" + "log" + "path" + "sync" + "time" + + "github.com/wavetermdev/thenextwave/pkg/wavebase" + + "github.com/golang-migrate/migrate/v4" + _ "github.com/golang-migrate/migrate/v4/database/sqlite3" + "github.com/golang-migrate/migrate/v4/source/iofs" + "github.com/jmoiron/sqlx" + _ "github.com/mattn/go-sqlite3" + "github.com/sawka/txwrap" + + dbfs "github.com/wavetermdev/thenextwave/db" +) + +const BlockstoreDbName = "blockstore.db" + +type SingleConnDBGetter struct { + SingleConnLock *sync.Mutex +} + +type TxWrap = txwrap.TxWrap + +var dbWrap *SingleConnDBGetter = &SingleConnDBGetter{SingleConnLock: &sync.Mutex{}} +var globalDBLock = &sync.Mutex{} +var globalDB *sqlx.DB +var globalDBErr error + +func InitBlockstore() error { + err := MigrateBlockstore() + if err != nil { + return err + } + ctx, cancelFn := context.WithTimeout(context.Background(), 2*time.Second) + defer cancelFn() + _, err = GetDB(ctx) + if err != nil { + return err + } + log.Printf("blockstore initialized\n") + return nil +} + +func GetDBName() string { + scHome := wavebase.GetWaveHomeDir() + return path.Join(scHome, BlockstoreDbName) +} + +func GetDB(ctx context.Context) (*sqlx.DB, error) { + if txwrap.IsTxWrapContext(ctx) { + return nil, fmt.Errorf("cannot call GetDB from within a running transaction") + } + globalDBLock.Lock() + defer globalDBLock.Unlock() + if globalDB == nil && globalDBErr == nil { + dbName := GetDBName() + globalDB, globalDBErr = sqlx.Open("sqlite3", fmt.Sprintf("file:%s?cache=shared&mode=rwc&_journal_mode=WAL&_busy_timeout=5000", dbName)) + if globalDBErr != nil { + globalDBErr = fmt.Errorf("opening db[%s]: %w", dbName, globalDBErr) + log.Printf("[db] error: %v\n", globalDBErr) + } else { + log.Printf("[db] successfully opened db %s\n", dbName) + } + } + return globalDB, globalDBErr +} + +func (dbg *SingleConnDBGetter) GetDB(ctx context.Context) (*sqlx.DB, error) { + db, err := GetDB(ctx) + if err != nil { + return nil, err + } + dbg.SingleConnLock.Lock() + return db, nil +} + +func (dbg *SingleConnDBGetter) ReleaseDB(db *sqlx.DB) { + dbg.SingleConnLock.Unlock() +} + +func WithTx(ctx context.Context, fn func(tx *TxWrap) error) error { + return txwrap.DBGWithTx(ctx, dbWrap, fn) +} + +func WithTxRtn[RT any](ctx context.Context, fn func(tx *TxWrap) (RT, error)) (RT, error) { + var rtn RT + txErr := WithTx(ctx, func(tx *TxWrap) error { + temp, err := fn(tx) + if err != nil { + return err + } + rtn = temp + return nil + }) + return rtn, txErr +} + +func MakeBlockstoreMigrate() (*migrate.Migrate, error) { + fsVar, err := iofs.New(dbfs.BlockstoreMigrationFS, "migrations-blockstore") + if err != nil { + return nil, fmt.Errorf("opening iofs: %w", err) + } + dbUrl := fmt.Sprintf("sqlite3://%s", GetDBName()) + m, err := migrate.NewWithSourceInstance("iofs", fsVar, dbUrl) + if err != nil { + return nil, fmt.Errorf("making blockstore migration db[%s]: %w", GetDBName(), err) + } + return m, nil +} + +func MigrateBlockstore() error { + log.Printf("migrate blockstore\n") + m, err := MakeBlockstoreMigrate() + if err != nil { + return err + } + curVersion, dirty, err := GetMigrateVersion(m) + if dirty { + return fmt.Errorf("cannot migrate up, database is dirty") + } + if err != nil { + return fmt.Errorf("cannot get current migration version: %v", err) + } + defer m.Close() + err = m.Up() + if err != nil && err != migrate.ErrNoChange { + return fmt.Errorf("migrating blockstore: %w", err) + } + newVersion, _, err := GetMigrateVersion(m) + if err != nil { + return fmt.Errorf("cannot get new migration version: %v", err) + } + if newVersion != curVersion { + log.Printf("[db] blockstore migration done, version %d -> %d\n", curVersion, newVersion) + } + return nil +} + +func GetMigrateVersion(m *migrate.Migrate) (uint, bool, error) { + if m == nil { + var err error + m, err = MakeBlockstoreMigrate() + if err != nil { + return 0, false, err + } + } + curVersion, dirty, err := m.Version() + if err == migrate.ErrNilVersion { + return 0, false, nil + } + return curVersion, dirty, err +} diff --git a/pkg/wavebase/wavebase.go b/pkg/wavebase/wavebase.go new file mode 100644 index 000000000..0b8a8d4ae --- /dev/null +++ b/pkg/wavebase/wavebase.go @@ -0,0 +1,90 @@ +package wavebase + +import ( + "errors" + "fmt" + "io/fs" + "os" + "path" + "strings" + "sync" +) + +const DefaultWaveHome = "~/.w2" +const WaveHomeVarName = "WAVETERM_HOME" +const WaveDevVarName = "WAVETERM_DEV" +const HomeVarName = "HOME" + +var baseLock = &sync.Mutex{} +var ensureDirCache = map[string]bool{} + +func IsDevMode() bool { + pdev := os.Getenv(WaveDevVarName) + return pdev != "" +} + +func GetHomeDir() string { + homeVar := os.Getenv(HomeVarName) + if homeVar == "" { + return "/" + } + return homeVar +} + +func ExpandHomeDir(pathStr string) string { + if pathStr != "~" && !strings.HasPrefix(pathStr, "~/") { + return pathStr + } + homeDir := GetHomeDir() + if pathStr == "~" { + return homeDir + } + return path.Join(homeDir, pathStr[2:]) +} + +func GetWaveHomeDir() string { + homeVar := os.Getenv(WaveHomeVarName) + if homeVar != "" { + return ExpandHomeDir(homeVar) + } + return ExpandHomeDir(DefaultWaveHome) +} + +func EnsureWaveHomeDir() error { + return CacheEnsureDir(GetWaveHomeDir(), "wavehome", 0700, "wave home directory") +} + +func CacheEnsureDir(dirName string, cacheKey string, perm os.FileMode, dirDesc string) error { + baseLock.Lock() + ok := ensureDirCache[cacheKey] + baseLock.Unlock() + if ok { + return nil + } + err := TryMkdirs(dirName, perm, dirDesc) + if err != nil { + return err + } + baseLock.Lock() + ensureDirCache[cacheKey] = true + baseLock.Unlock() + return nil +} + +func TryMkdirs(dirName string, perm os.FileMode, dirDesc string) error { + info, err := os.Stat(dirName) + if errors.Is(err, fs.ErrNotExist) { + err = os.MkdirAll(dirName, perm) + if err != nil { + return fmt.Errorf("cannot make %s %q: %w", dirDesc, dirName, err) + } + info, err = os.Stat(dirName) + } + if err != nil { + return fmt.Errorf("error trying to stat %s: %w", dirDesc, err) + } + if !info.IsDir() { + return fmt.Errorf("%s %q must be a directory", dirDesc, dirName) + } + return nil +}