default to wal mode
This commit is contained in:
@@ -1,6 +1,8 @@
|
|||||||
# Backend
|
# Backend
|
||||||
tmp/
|
tmp/
|
||||||
*.db
|
*.db
|
||||||
|
*.db-shm
|
||||||
|
*.db-wal
|
||||||
messages.db
|
messages.db
|
||||||
build-errors.log
|
build-errors.log
|
||||||
sbv
|
sbv
|
||||||
|
|||||||
@@ -2,6 +2,30 @@
|
|||||||
|
|
||||||
This guide covers administrative features in SBV.
|
This guide covers administrative features in SBV.
|
||||||
|
|
||||||
|
## Database Journal Mode
|
||||||
|
|
||||||
|
SBV uses SQLite with WAL (Write-Ahead Logging) mode by default. WAL mode provides better concurrent access, allowing you to browse messages while imports are running.
|
||||||
|
|
||||||
|
If your data directory is on a network filesystem (NFS, SMB/CIFS), WAL mode may not work correctly. In this case, use the `-journal` flag to switch to rollback journal mode:
|
||||||
|
|
||||||
|
Docker:
|
||||||
|
```bash
|
||||||
|
docker run ... ghcr.io/lowcarbdev/sbv:stable ./sbv -journal
|
||||||
|
```
|
||||||
|
|
||||||
|
Binary:
|
||||||
|
```bash
|
||||||
|
./sbv -journal
|
||||||
|
```
|
||||||
|
|
||||||
|
Docker Compose:
|
||||||
|
```yaml
|
||||||
|
services:
|
||||||
|
sbv:
|
||||||
|
image: ghcr.io/lowcarbdev/sbv:stable
|
||||||
|
command: ["./sbv", "-journal"]
|
||||||
|
```
|
||||||
|
|
||||||
## User Management
|
## User Management
|
||||||
|
|
||||||
### List All Users
|
### List All Users
|
||||||
|
|||||||
@@ -25,6 +25,20 @@ func InitAuthDB(filepath string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set busy timeout for better concurrent access
|
||||||
|
_, err = authDB.Exec("PRAGMA busy_timeout=5000;")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to set busy timeout: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enable WAL mode if requested (better for concurrent reads during writes)
|
||||||
|
if UseWALMode {
|
||||||
|
_, err = authDB.Exec("PRAGMA journal_mode=WAL;")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to enable WAL mode: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
createTableSQL := `
|
createTableSQL := `
|
||||||
CREATE TABLE IF NOT EXISTS users (
|
CREATE TABLE IF NOT EXISTS users (
|
||||||
id TEXT PRIMARY KEY,
|
id TEXT PRIMARY KEY,
|
||||||
|
|||||||
@@ -145,6 +145,7 @@ func (s *AutoImportService) processFile(userID, filePath, filename string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
logWriter.log("Starting import of %s", filename)
|
logWriter.log("Starting import of %s", filename)
|
||||||
|
startTime := time.Now()
|
||||||
|
|
||||||
// Get username from auth database
|
// Get username from auth database
|
||||||
username, err := GetUsernameByID(userID)
|
username, err := GetUsernameByID(userID)
|
||||||
@@ -192,10 +193,13 @@ func (s *AutoImportService) processFile(userID, filePath, filename string) {
|
|||||||
completePath = filepath.Join(completeDir, filename)
|
completePath = filepath.Join(completeDir, filename)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
duration := time.Since(startTime)
|
||||||
|
|
||||||
if parseErr != nil {
|
if parseErr != nil {
|
||||||
logWriter.log("ERROR: Import failed: %v", parseErr)
|
logWriter.log("ERROR: Import failed: %v", parseErr)
|
||||||
logWriter.log("File will remain in ingest directory for manual review")
|
logWriter.log("File will remain in ingest directory for manual review")
|
||||||
slog.Error("Import failed", "userID", userID, "file", filename, "error", parseErr)
|
logWriter.log("Import duration: %s", duration)
|
||||||
|
slog.Error("Import failed", "userID", userID, "file", filename, "error", parseErr, "duration", duration)
|
||||||
} else {
|
} else {
|
||||||
// Move file to complete directory
|
// Move file to complete directory
|
||||||
if err := os.Rename(filePath, completePath); err != nil {
|
if err := os.Rename(filePath, completePath); err != nil {
|
||||||
@@ -211,9 +215,9 @@ func (s *AutoImportService) processFile(userID, filePath, filename string) {
|
|||||||
slog.Warn("Failed to move log file", "userID", userID, "error", err)
|
slog.Warn("Failed to move log file", "userID", userID, "error", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
logWriter.log("Import completed successfully")
|
logWriter.log("Import completed successfully in %s", duration)
|
||||||
logWriter.log("File moved to: %s", completePath)
|
logWriter.log("File moved to: %s", completePath)
|
||||||
slog.Info("Import completed", "userID", userID, "file", filename)
|
slog.Info("Import completed", "userID", userID, "file", filename, "duration", duration)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+43
-15
@@ -19,6 +19,9 @@ var db *sql.DB
|
|||||||
var userDBs = make(map[string]*sql.DB)
|
var userDBs = make(map[string]*sql.DB)
|
||||||
var userDBsMutex sync.RWMutex
|
var userDBsMutex sync.RWMutex
|
||||||
|
|
||||||
|
// UseWALMode controls whether WAL journal mode is enabled for databases
|
||||||
|
var UseWALMode bool
|
||||||
|
|
||||||
// truncateString truncates a string to maxLen characters for logging
|
// truncateString truncates a string to maxLen characters for logging
|
||||||
func truncateString(s string, maxLen int) string {
|
func truncateString(s string, maxLen int) string {
|
||||||
if len(s) <= maxLen {
|
if len(s) <= maxLen {
|
||||||
@@ -58,6 +61,20 @@ func InitDB(filepath string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set busy timeout for better concurrent access
|
||||||
|
_, err = db.Exec("PRAGMA busy_timeout=5000;")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to set busy timeout: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enable WAL mode if requested (better for concurrent reads during writes)
|
||||||
|
if UseWALMode {
|
||||||
|
_, err = db.Exec("PRAGMA journal_mode=WAL;")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to enable WAL mode: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
createTableSQL := `
|
createTableSQL := `
|
||||||
-- Unified table for SMS messages, MMS messages, and call logs
|
-- Unified table for SMS messages, MMS messages, and call logs
|
||||||
-- record_type: 1 = SMS, 2 = MMS, 3 = call
|
-- record_type: 1 = SMS, 2 = MMS, 3 = call
|
||||||
@@ -152,6 +169,20 @@ func InitUserDB(userID string, filepath string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set busy timeout for better concurrent access
|
||||||
|
_, err = userDB.Exec("PRAGMA busy_timeout=5000;")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to set busy timeout: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enable WAL mode if requested (better for concurrent reads during writes)
|
||||||
|
if UseWALMode {
|
||||||
|
_, err = userDB.Exec("PRAGMA journal_mode=WAL;")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to enable WAL mode: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
createTableSQL := `
|
createTableSQL := `
|
||||||
-- Unified table for SMS messages, MMS messages, and call logs
|
-- Unified table for SMS messages, MMS messages, and call logs
|
||||||
-- record_type: 1 = SMS, 2 = MMS, 3 = call
|
-- record_type: 1 = SMS, 2 = MMS, 3 = call
|
||||||
@@ -237,32 +268,29 @@ func InitUserDB(userID string, filepath string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetUserDB retrieves the database connection for a specific user
|
// GetUserDB retrieves the database connection for a specific user, creating it if it doesn't exist
|
||||||
func GetUserDB(userID string, username string) (*sql.DB, error) {
|
func GetUserDB(userID string, username string) (*sql.DB, error) {
|
||||||
userDBsMutex.RLock()
|
userDBsMutex.RLock()
|
||||||
defer userDBsMutex.RUnlock()
|
|
||||||
|
|
||||||
userDB, exists := userDBs[userID]
|
userDB, exists := userDBs[userID]
|
||||||
|
userDBsMutex.RUnlock()
|
||||||
|
|
||||||
if !exists {
|
if !exists {
|
||||||
// Try to open the database if it exists
|
// Database not in cache, try to open or create it
|
||||||
dbPathPrefix := os.Getenv("DB_PATH_PREFIX")
|
dbPathPrefix := os.Getenv("DB_PATH_PREFIX")
|
||||||
if dbPathPrefix == "" {
|
if dbPathPrefix == "" {
|
||||||
dbPathPrefix = "."
|
dbPathPrefix = "."
|
||||||
}
|
}
|
||||||
// Use UUID as database filename instead of sanitized username
|
// Use UUID as database filename instead of sanitized username
|
||||||
filepath := fmt.Sprintf("%s/sbv_%s.db", dbPathPrefix, userID)
|
filepath := fmt.Sprintf("%s/sbv_%s.db", dbPathPrefix, userID)
|
||||||
if _, err := os.Stat(filepath); err == nil {
|
|
||||||
// Database file exists, try to open it
|
// InitUserDB will create the database if it doesn't exist
|
||||||
userDBsMutex.RUnlock()
|
if err := InitUserDB(userID, filepath); err != nil {
|
||||||
if err := InitUserDB(userID, filepath); err != nil {
|
return nil, fmt.Errorf("failed to initialize user database: %w", err)
|
||||||
userDBsMutex.RLock()
|
|
||||||
return nil, fmt.Errorf("failed to open user database: %w", err)
|
|
||||||
}
|
|
||||||
userDBsMutex.RLock()
|
|
||||||
userDB = userDBs[userID]
|
|
||||||
} else {
|
|
||||||
return nil, fmt.Errorf("user database not found for user %s", username)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
userDBsMutex.RLock()
|
||||||
|
userDB = userDBs[userID]
|
||||||
|
userDBsMutex.RUnlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
return userDB, nil
|
return userDB, nil
|
||||||
|
|||||||
@@ -23,8 +23,12 @@ func main() {
|
|||||||
// Parse CLI flags
|
// Parse CLI flags
|
||||||
resetPassword := flag.String("reset-password", "", "Reset password for the specified username")
|
resetPassword := flag.String("reset-password", "", "Reset password for the specified username")
|
||||||
listUsers := flag.Bool("list-users", false, "List all users")
|
listUsers := flag.Bool("list-users", false, "List all users")
|
||||||
|
journalMode := flag.Bool("journal", false, "Use rollback journal mode instead of WAL (for network filesystems)")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
|
// Use WAL mode by default, unless -journal flag is set
|
||||||
|
internal.UseWALMode = !*journalMode
|
||||||
|
|
||||||
// Initialize slog logger
|
// Initialize slog logger
|
||||||
logger = slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
|
logger = slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
|
||||||
Level: slog.LevelInfo,
|
Level: slog.LevelInfo,
|
||||||
|
|||||||
Reference in New Issue
Block a user