SQLite Backup 按 CGO build tag 隔离,支持静态编译(Alpine musl)
This commit is contained in:
@@ -3,7 +3,6 @@ package database
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -26,7 +25,6 @@ import (
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/util/random"
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/xray"
|
||||
|
||||
"github.com/mattn/go-sqlite3"
|
||||
"gorm.io/driver/postgres"
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
@@ -2281,86 +2279,6 @@ func IsSQLiteDB(file io.ReaderAt) (bool, error) {
|
||||
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 {
|
||||
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