From 9105f7f364ec8a38d02da42535cf784415899367 Mon Sep 17 00:00:00 2001 From: Muse Date: Mon, 19 Oct 2026 21:20:36 +0800 Subject: [PATCH] =?UTF-8?q?SQLite=20Backup=20=E6=8C=89=20CGO=20build=20tag?= =?UTF-8?q?=20=E9=9A=94=E7=A6=BB=EF=BC=8C=E6=94=AF=E6=8C=81=E9=9D=99?= =?UTF-8?q?=E6=80=81=E7=BC=96=E8=AF=91=EF=BC=88Alpine=20musl=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/database/db.go | 82 ------------------------ internal/database/db_backup_cgo.go | 96 ++++++++++++++++++++++++++++ internal/database/db_backup_nocgo.go | 9 +++ 3 files changed, 105 insertions(+), 82 deletions(-) create mode 100644 internal/database/db_backup_cgo.go create mode 100644 internal/database/db_backup_nocgo.go diff --git a/internal/database/db.go b/internal/database/db.go index 801c4ae..8a7e174 100644 --- a/internal/database/db.go +++ b/internal/database/db.go @@ -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 { diff --git a/internal/database/db_backup_cgo.go b/internal/database/db_backup_cgo.go new file mode 100644 index 0000000..9b5cff4 --- /dev/null +++ b/internal/database/db_backup_cgo.go @@ -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): + } + } + }) + }) +} + diff --git a/internal/database/db_backup_nocgo.go b/internal/database/db_backup_nocgo.go new file mode 100644 index 0000000..9d9442a --- /dev/null +++ b/internal/database/db_backup_nocgo.go @@ -0,0 +1,9 @@ +//go:build !cgo + +package database + +import "errors" + +func BackupSQLite(dstPath string) error { + return errors.New("sqlite backup requires CGO build") +}