memos/mobile/server.go

164 lines
3.8 KiB
Go

package mobile
import (
"context"
"fmt"
"log/slog"
"os"
"path/filepath"
"sync"
"github.com/usememos/memos/internal/profile"
"github.com/usememos/memos/internal/version"
"github.com/usememos/memos/server"
"github.com/usememos/memos/store"
"github.com/usememos/memos/store/db"
)
// ServerConfig holds the configuration for the mobile server.
type ServerConfig struct {
// DataDir is the directory where memos data will be stored
DataDir string
// Port is the port to bind the server to
Port int
// Addr is the address to bind to (use "0.0.0.0" for network access, "" for localhost only)
Addr string
// Mode can be "prod", "dev", or "demo"
Mode string
}
// MobileServer wraps the memos server for use in mobile applications.
type MobileServer struct {
server *server.Server
ctx context.Context
cancel context.CancelFunc
mu sync.Mutex
logger *slog.Logger
}
var (
globalServer *MobileServer
serverMu sync.Mutex
)
// NewServer creates a new mobile server instance with the given configuration.
// Returns the server URL on success.
func NewServer(dataDir string, port int, addr string, mode string) (string, error) {
serverMu.Lock()
defer serverMu.Unlock()
if globalServer != nil {
return "", fmt.Errorf("server already running")
}
// Set up logger
logLevel := slog.LevelInfo
if mode == "dev" {
logLevel = slog.LevelDebug
}
logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
Level: logLevel,
}))
slog.SetDefault(logger)
// Ensure data directory exists
if err := os.MkdirAll(dataDir, 0755); err != nil {
return "", fmt.Errorf("failed to create data directory: %w", err)
}
// Create profile
instanceProfile := &profile.Profile{
Mode: mode,
Addr: addr,
Port: port,
Data: dataDir,
Driver: "sqlite",
DSN: "",
Version: version.GetCurrentVersion(mode),
}
if err := instanceProfile.Validate(); err != nil {
return "", fmt.Errorf("invalid profile: %w", err)
}
ctx, cancel := context.WithCancel(context.Background())
// Create database driver
dbDriver, err := db.NewDBDriver(instanceProfile)
if err != nil {
cancel()
return "", fmt.Errorf("failed to create db driver: %w", err)
}
// Create store and migrate
storeInstance := store.New(dbDriver, instanceProfile)
if err := storeInstance.Migrate(ctx); err != nil {
cancel()
return "", fmt.Errorf("failed to migrate: %w", err)
}
// Create server
s, err := server.NewServer(ctx, instanceProfile, storeInstance)
if err != nil {
cancel()
return "", fmt.Errorf("failed to create server: %w", err)
}
globalServer = &MobileServer{
server: s,
ctx: ctx,
cancel: cancel,
logger: logger,
}
// Start server in background
go func() {
if err := s.Start(ctx); err != nil {
logger.Error("server error", "error", err)
}
}()
// Construct server URL
serverURL := fmt.Sprintf("http://localhost:%d", port)
if addr != "" && addr != "localhost" && addr != "127.0.0.1" {
serverURL = fmt.Sprintf("http://%s:%d", addr, port)
}
logger.Info("Memos server started", "url", serverURL)
return serverURL, nil
}
// StopServer stops the running server.
func StopServer() error {
serverMu.Lock()
defer serverMu.Unlock()
if globalServer == nil {
return fmt.Errorf("no server running")
}
globalServer.mu.Lock()
defer globalServer.mu.Unlock()
globalServer.logger.Info("Stopping memos server")
globalServer.server.Shutdown(globalServer.ctx)
globalServer.cancel()
globalServer = nil
return nil
}
// IsServerRunning returns true if the server is currently running.
func IsServerRunning() bool {
serverMu.Lock()
defer serverMu.Unlock()
return globalServer != nil
}
// GetDataDirectory returns an appropriate data directory for iOS.
// This should be called from Swift/Objective-C with the app's document directory.
func GetDataDirectory(documentsDir string) string {
return filepath.Join(documentsDir, "memos-data")
}