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/signalproject/
├── 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