Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add support for auto backup db file #1950

Merged
merged 1 commit into from
Jul 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions api/v1/system_setting.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"encoding/json"
"fmt"
"net/http"
"strconv"
"strings"

"github.com/labstack/echo/v4"
Expand Down Expand Up @@ -39,6 +40,8 @@ const (
SystemSettingMemoDisplayWithUpdatedTsName SystemSettingName = "memo-display-with-updated-ts"
// SystemSettingOpenAIConfigName is the name of OpenAI config.
SystemSettingOpenAIConfigName SystemSettingName = "openai-config"
// SystemSettingAutoBackupIntervalName is the name of auto backup interval as seconds.
SystemSettingAutoBackupIntervalName SystemSettingName = "auto-backup-interval"
)

// CustomizedProfile is the struct definition for SystemSettingCustomizedProfileName system setting item.
Expand Down Expand Up @@ -139,6 +142,20 @@ func (upsert UpsertSystemSettingRequest) Validate() error {
if err := json.Unmarshal([]byte(upsert.Value), &value); err != nil {
return fmt.Errorf(systemSettingUnmarshalError, settingName)
}
case SystemSettingAutoBackupIntervalName:
var value string
if err := json.Unmarshal([]byte(upsert.Value), &value); err != nil {
return fmt.Errorf(systemSettingUnmarshalError, settingName)
}
if value != "" {
v, err := strconv.Atoi(value)
if err != nil {
return fmt.Errorf(systemSettingUnmarshalError, settingName)
}
if v < 0 {
return fmt.Errorf("backup interval should > 0")
}
}
case SystemSettingTelegramBotTokenName:
if upsert.Value == "" {
return nil
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ require (
golang.org/x/mod v0.8.0
golang.org/x/net v0.7.0
golang.org/x/oauth2 v0.5.0
modernc.org/sqlite v1.22.1
modernc.org/sqlite v1.24.0
)

require (
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -662,8 +662,8 @@ modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds=
modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
modernc.org/sqlite v1.22.1 h1:P2+Dhp5FR1RlVRkQ3dDfCiv3Ok8XPxqpe70IjYVA9oE=
modernc.org/sqlite v1.22.1/go.mod h1:OrDj17Mggn6MhE+iPbBNf7RGKODDE9NFT0f3EwDzJqk=
modernc.org/sqlite v1.24.0 h1:EsClRIWHGhLTCX44p+Ri/JLD+vFGo0QGjasg2/F9TlI=
modernc.org/sqlite v1.24.0/go.mod h1:OrDj17Mggn6MhE+iPbBNf7RGKODDE9NFT0f3EwDzJqk=
modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY=
modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw=
modernc.org/tcl v1.15.2 h1:C4ybAYCGJw968e+Me18oW55kD/FexcHbqH2xak1ROSY=
Expand Down
48 changes: 48 additions & 0 deletions server/backup.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package server

import (
"context"
"fmt"
"strconv"
"time"

apiv1 "github.com/usememos/memos/api/v1"
"github.com/usememos/memos/common/log"
"github.com/usememos/memos/store"
"go.uber.org/zap"
)

func autoBackup(ctx context.Context, s *store.Store) {
intervalStr := s.GetSystemSettingValueWithDefault(&ctx, apiv1.SystemSettingAutoBackupIntervalName.String(), "")
if intervalStr == "" {
log.Info("no SystemSettingAutoBackupIntervalName setting, disable auto backup")
return
}

interval, err := strconv.Atoi(intervalStr)
if err != nil || interval <= 0 {
log.Error(fmt.Sprintf("invalid SystemSettingAutoBackupIntervalName value %s, disable auto backup", intervalStr), zap.Error(err))
return
}

log.Info("enable auto backup every " + intervalStr + " seconds")
ticker := time.NewTicker(time.Duration(interval) * time.Second)
defer ticker.Stop()

var t time.Time
for {
select {
case <-ctx.Done():
log.Info("stop auto backup graceful.")
return
case t = <-ticker.C:
}

filename := s.Profile.DSN + t.Format("-20060102-150405.bak")
log.Info(fmt.Sprintf("create backup to %s", filename))
err := s.BackupTo(ctx, filename)
if err != nil {
log.Error("fail to create backup", zap.Error(err))
}
}
}
1 change: 1 addition & 0 deletions server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ func (s *Server) Start(ctx context.Context) error {
}

go s.telegramBot.Start(ctx)
go autoBackup(ctx, s.Store)

return s.e.Start(fmt.Sprintf(":%d", s.Profile.Port))
}
Expand Down
39 changes: 39 additions & 0 deletions store/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ package store
import (
"context"
"database/sql"
"fmt"
"sync"

"github.com/usememos/memos/server/profile"
"modernc.org/sqlite"
)

// Store provides database access to all raw objects.
Expand All @@ -31,6 +33,43 @@ func (s *Store) GetDB() *sql.DB {
return s.db
}

func (s *Store) BackupTo(ctx context.Context, filename string) error {
conn, err := s.db.Conn(ctx)
if err != nil {
return fmt.Errorf("fail to get conn %s", err)
}
defer conn.Close()

err = conn.Raw(func(driverConn any) error {
type backuper interface {
NewBackup(string) (*sqlite.Backup, error)
}
backupConn, ok := driverConn.(backuper)
if !ok {
return fmt.Errorf("db connection is not a sqlite backuper")
}

bck, err := backupConn.NewBackup(filename)
if err != nil {
return fmt.Errorf("fail to create sqlite backup %s", err)
}

for more := true; more; {
more, err = bck.Step(-1)
if err != nil {
return fmt.Errorf("fail to execute sqlite backup %s", err)
}
}

return bck.Finish()
})
if err != nil {
return fmt.Errorf("fail to backup %s", err)
}

return nil
}

func (s *Store) Vacuum(ctx context.Context) error {
tx, err := s.db.BeginTx(ctx, nil)
if err != nil {
Expand Down
Loading