Skip to content

Latest commit

 

History

History
388 lines (329 loc) · 8.67 KB

File metadata and controls

388 lines (329 loc) · 8.67 KB
mkdir -p project/internal/domain
mkdir -p project/internal/ports/input
mkdir -p project/internal/ports/output
mkdir -p project/internal/ports/filesystem
mkdir -p project/internal/ports/signal
mkdir -p project/internal/adapters/input
mkdir -p project/internal/adapters/output
mkdir -p project/internal/adapters/filesystem
mkdir -p project/internal/adapters/signal
project/
├── go.mod
├── main.go
└── internal/
    ├── domain/
    │   └── signal_handler.go
    ├── ports/
    │   ├── input/
    │   │   └── config_loader.go
    │   ├── output/
    │   │   └── printer.go
    │   ├── filesystem/
    │   │   └── manager.go
    │   └── signal/
    │       └── notifier.go
    └── adapters/
        ├── input/
        │   └── user_adapter.go
        ├── output/
        │   └── console_printer.go
        ├── filesystem/
        │   └── os_manager.go
        └── signal/
            └── os_notifier.go

internal/ports/input/config_loader.go

package input

import "time"

// ConfigLoader is the port for loading user configuration.
type ConfigLoader interface {
    GetHomeDir() (string, error)
}

internal/ports/output/printer.go

package output

import "fmt"

// Printer is the output port for printing messages.
type Printer interface {
    Println(a ...interface{})
    Printf(format string, a ...interface{})
}

internal/ports/filesystem/manager.go

package filesystem

import "io"

// FileManager is the port for file system operations.
type FileManager interface {
    ReadFile(path string) ([]byte, error)
    OpenFile(path string, flag int, perm os.FileMode) (io.WriteCloser, error)
}

internal/ports/signal/notifier.go

package signal

import "os"

// SignalNotifier is the port for signal notification.
type SignalNotifier interface {
    Notify(channel chan os.Signal, signals ...os.Signal)
    GetChannel() chan os.Signal
}

// CustomSignal represents custom signals.
type CustomSignal interface {
    os.Signal
}

// SIGUSR1 and SIGUSR2 as custom signals for cross-platform.
const (
    SIGUSR1 CustomSignal = iota + 10 // Arbitrary value for SIGUSR1
    SIGUSR2 CustomSignal = iota + 12 // Arbitrary value for SIGUSR2
)

internal/adapters/input/user_adapter.go

package input

import (
    "os/user"

    ports "project/internal/ports/input"
)

// UserAdapter is the adapter for retrieving user information.
type UserAdapter struct{}

// NewUserAdapter creates a new UserAdapter.
func NewUserAdapter() ports.ConfigLoader {
    return &UserAdapter{}
}

// GetHomeDir implements ConfigLoader.
func (u *UserAdapter) GetHomeDir() (string, error) {
    usr, err := user.Current()
    if err != nil {
        return "", err
    }
    return usr.HomeDir, nil
}

internal/adapters/output/console_printer.go

package output

import "fmt"

// ConsolePrinter is the adapter for console output.
type ConsolePrinter struct{}

// NewConsolePrinter creates a new ConsolePrinter.
func NewConsolePrinter() Printer {
    return &ConsolePrinter{}
}

// Println implements Printer.
func (c *ConsolePrinter) Println(a ...interface{}) {
    fmt.Println(a...)
}

// Printf implements Printer.
func (c *ConsolePrinter) Printf(format string, a ...interface{}) {
    fmt.Printf(format, a...)
}

internal/adapters/filesystem/os_manager.go

package filesystem

import (
    "io"
    "os"

    ports "project/internal/ports/filesystem"
)

// OSFileManager is the adapter for OS file operations.
type OSFileManager struct{}

// NewOSFileManager creates a new OSFileManager.
func NewOSFileManager() ports.FileManager {
    return &OSFileManager{}
}

// ReadFile implements FileManager.
func (o *OSFileManager) ReadFile(path string) ([]byte, error) {
    return os.ReadFile(path)
}

// OpenFile implements FileManager.
func (o *OSFileManager) OpenFile(path string, flag int, perm os.FileMode) (io.WriteCloser, error) {
    return os.OpenFile(path, flag, perm)
}

internal/adapters/signal/os_notifier.go

package signal

import (
    "os"
    "os/signal"
    "syscall"

    ports "project/internal/ports/signal"
)

// OSSignalNotifier is the adapter for OS signal notification.
type OSSignalNotifier struct {
    channel chan os.Signal
}

// NewOSSignalNotifier creates a new OSSignalNotifier.
func NewOSSignalNotifier() ports.SignalNotifier {
    return &OSSignalNotifier{
        channel: make(chan os.Signal, 1),
    }
}

// Notify implements SignalNotifier.
func (o *OSSignalNotifier) Notify(signals ...os.Signal) {
    signal.Notify(o.channel, signals...)
}

// GetChannel implements SignalNotifier.
func (o *OSSignalNotifier) GetChannel() chan os.Signal {
    return o.channel
}

internal/domain/signal_handler.go

package domain

import (
    "fmt"
    "io"
    "log"
    "os"
    "path/filepath"
    "time"

    input "project/internal/ports/input"
    filesystem "project/internal/ports/filesystem"
    output "project/internal/ports/output"
    signal "project/internal/ports/signal"
)

const cfgFile = ".multi"

// SignalHandler is the core domain service for handling signals and configuration.
type SignalHandler struct {
    loader     input.ConfigLoader
    fm         filesystem.FileManager
    printer    output.Printer
    notifier   signal.SignalNotifier
    cfgPath    string
    duration   time.Duration
}

// NewSignalHandler creates a new SignalHandler with dependencies.
func NewSignalHandler(
    loader input.ConfigLoader,
    fm filesystem.FileManager,
    printer output.Printer,
    notifier signal.SignalNotifier,
) (*SignalHandler, error) {
    home, err := loader.GetHomeDir()
    if err != nil {
        return nil, err
    }
    sh := &SignalHandler{
        loader:   loader,
        fm:       fm,
        printer:  printer,
        notifier: notifier,
        cfgPath:  filepath.Join(home, cfgFile),
        duration: time.Second, // default
    }
    // Initial load
    if err := sh.loadSettings(); err != nil && !os.IsNotExist(err) {
        return nil, err
    }
    return sh, nil
}

// Run starts the signal handling loop.
func (sh *SignalHandler) Run() {
    sh.printer.Println("Start application...")
    sh.notifier.Notify(
        syscall.SIGHUP, syscall.SIGINT, syscall.SIGQUIT,
        signal.SIGUSR1, signal.SIGUSR2, syscall.SIGALRM,
    )
    c := sh.notifier.GetChannel()
    for {
        select {
        case s := <-c:
            if err := sh.handleSignal(s); err != nil {
                sh.printer.Printf("Error handling %v: %v\n", s, err)
                continue
            }
        default:
            time.Sleep(sh.duration)
            sh.printer.Printf("After %v Executing action!\n", sh.duration)
        }
    }
}

func (sh *SignalHandler) handleSignal(s os.Signal) error {
    switch s {
    case syscall.SIGHUP:
        return sh.loadSettings()
    case syscall.SIGALRM:
        return sh.saveSettings()
    case syscall.SIGINT:
        if err := sh.saveSettings(); err != nil {
            sh.printer.Println("Cannot save:", err)
            os.Exit(1)
        }
        fallthrough
    case syscall.SIGQUIT:
        os.Exit(0)
    case signal.SIGUSR1:
        sh.changeSettings(sh.duration * 2)
        return nil
    case signal.SIGUSR2:
        sh.changeSettings(sh.duration / 2)
        return nil
    }
    return nil
}

func (sh *SignalHandler) changeSettings(v time.Duration) {
    sh.duration = v
    sh.printer.Printf("Changed %v\n", v)
}

func (sh *SignalHandler) loadSettings() error {
    b, err := sh.fm.ReadFile(sh.cfgPath)
    if err != nil {
        return err
    }
    v, err := time.ParseDuration(string(b))
    if err != nil {
        return err
    }
    sh.duration = v
    sh.printer.Printf("Loaded %v\n", v)
    return nil
}

func (sh *SignalHandler) saveSettings() error {
    f, err := sh.fm.OpenFile(sh.cfgPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
    if err != nil {
        return err
    }
    defer f.Close()
    if _, err = io.WriteString(f, sh.duration.String()); err != nil {
        return err
    }
    sh.printer.Printf("Saved %v\n", sh.duration)
    return nil
}

main.go

package main

import (
    "log"

    "project/internal/adapters/filesystem"
    "project/internal/adapters/input"
    "project/internal/adapters/output"
    "project/internal/adapters/signal"
    "project/internal/domain"
)

func main() {
    loader := input.NewUserAdapter()
    fm := filesystem.NewOSFileManager()
    printer := output.NewConsolePrinter()
    notifier := signal.NewOSSignalNotifier()

    handler, err := domain.NewSignalHandler(loader, fm, printer, notifier)
    if err != nil {
        log.Fatal(err)
    }

    handler.Run()
}

go.mod

module project

go 1.21