SQLite Backup 按 CGO build tag 隔离,支持静态编译(Alpine musl)
This commit is contained in:
@@ -3,7 +3,6 @@ package database
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"database/sql"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -26,7 +25,6 @@ import (
|
|||||||
"github.com/mhsanaei/3x-ui/v3/internal/util/random"
|
"github.com/mhsanaei/3x-ui/v3/internal/util/random"
|
||||||
"github.com/mhsanaei/3x-ui/v3/internal/xray"
|
"github.com/mhsanaei/3x-ui/v3/internal/xray"
|
||||||
|
|
||||||
"github.com/mattn/go-sqlite3"
|
|
||||||
"gorm.io/driver/postgres"
|
"gorm.io/driver/postgres"
|
||||||
"gorm.io/driver/sqlite"
|
"gorm.io/driver/sqlite"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
@@ -2281,86 +2279,6 @@ func IsSQLiteDB(file io.ReaderAt) (bool, error) {
|
|||||||
return bytes.Equal(buf, signature), nil
|
return bytes.Equal(buf, signature), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func BackupSQLite(dstPath string) (err error) {
|
|
||||||
if IsPostgres() {
|
|
||||||
return errors.New("sqlite backup is unavailable for PostgreSQL")
|
|
||||||
}
|
|
||||||
if db == nil {
|
|
||||||
return errors.New("database is not initialized")
|
|
||||||
}
|
|
||||||
if _, err := os.Lstat(dstPath); err == nil {
|
|
||||||
return fmt.Errorf("sqlite backup destination already exists: %s", dstPath)
|
|
||||||
} else if !errors.Is(err, os.ErrNotExist) {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
if err != nil {
|
|
||||||
_ = os.Remove(dstPath)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), backupSQLiteTimeout)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
sourceDB, err := db.DB()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
sourceConn, err := sourceDB.Conn(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer sourceConn.Close()
|
|
||||||
|
|
||||||
destinationDB, err := sql.Open("sqlite3", dstPath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer destinationDB.Close()
|
|
||||||
destinationConn, err := destinationDB.Conn(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer destinationConn.Close()
|
|
||||||
|
|
||||||
return sourceConn.Raw(func(sourceDriver any) error {
|
|
||||||
source, ok := sourceDriver.(*sqlite3.SQLiteConn)
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("unexpected SQLite source connection type %T", sourceDriver)
|
|
||||||
}
|
|
||||||
return destinationConn.Raw(func(destinationDriver any) error {
|
|
||||||
destination, ok := destinationDriver.(*sqlite3.SQLiteConn)
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("unexpected SQLite destination connection type %T", destinationDriver)
|
|
||||||
}
|
|
||||||
backup, err := destination.Backup("main", source, "main")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
finished := false
|
|
||||||
defer func() {
|
|
||||||
if !finished {
|
|
||||||
_ = backup.Finish()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
for {
|
|
||||||
done, err := backup.Step(backupSQLiteStepPages())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if done {
|
|
||||||
finished = true
|
|
||||||
return backup.Finish()
|
|
||||||
}
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
return ctx.Err()
|
|
||||||
case <-time.After(10 * time.Millisecond):
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func ValidateSQLiteDB(dbPath string) error {
|
func ValidateSQLiteDB(dbPath string) error {
|
||||||
if _, err := os.Stat(dbPath); err != nil {
|
if _, err := os.Stat(dbPath); err != nil {
|
||||||
|
|||||||
@@ -0,0 +1,96 @@
|
|||||||
|
//go:build cgo
|
||||||
|
|
||||||
|
package database
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/mattn/go-sqlite3"
|
||||||
|
)
|
||||||
|
|
||||||
|
func BackupSQLite(dstPath string) (err error) {
|
||||||
|
if IsPostgres() {
|
||||||
|
return errors.New("sqlite backup is unavailable for PostgreSQL")
|
||||||
|
}
|
||||||
|
if db == nil {
|
||||||
|
return errors.New("database is not initialized")
|
||||||
|
}
|
||||||
|
if _, err := os.Lstat(dstPath); err == nil {
|
||||||
|
return fmt.Errorf("sqlite backup destination already exists: %s", dstPath)
|
||||||
|
} else if !errors.Is(err, os.ErrNotExist) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
_ = os.Remove(dstPath)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), backupSQLiteTimeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
sourceDB, err := db.DB()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
sourceConn, err := sourceDB.Conn(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer sourceConn.Close()
|
||||||
|
|
||||||
|
destinationDB, err := sql.Open("sqlite3", dstPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer destinationDB.Close()
|
||||||
|
destinationConn, err := destinationDB.Conn(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer destinationConn.Close()
|
||||||
|
|
||||||
|
return sourceConn.Raw(func(sourceDriver any) error {
|
||||||
|
source, ok := sourceDriver.(*sqlite3.SQLiteConn)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("unexpected SQLite source connection type %T", sourceDriver)
|
||||||
|
}
|
||||||
|
return destinationConn.Raw(func(destinationDriver any) error {
|
||||||
|
destination, ok := destinationDriver.(*sqlite3.SQLiteConn)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("unexpected SQLite destination connection type %T", destinationDriver)
|
||||||
|
}
|
||||||
|
backup, err := destination.Backup("main", source, "main")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
finished := false
|
||||||
|
defer func() {
|
||||||
|
if !finished {
|
||||||
|
_ = backup.Finish()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
for {
|
||||||
|
done, err := backup.Step(backupSQLiteStepPages())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if done {
|
||||||
|
finished = true
|
||||||
|
return backup.Finish()
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return ctx.Err()
|
||||||
|
case <-time.After(10 * time.Millisecond):
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
//go:build !cgo
|
||||||
|
|
||||||
|
package database
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
|
func BackupSQLite(dstPath string) error {
|
||||||
|
return errors.New("sqlite backup requires CGO build")
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user