evolved/main.go

208 lines
4.6 KiB
Go

package main
import (
"context"
"flag"
"fmt"
"log/slog"
"os"
"os/signal"
"os/user"
"path"
"path/filepath"
"strings"
"syscall"
"time"
"github.com/fsnotify/fsnotify"
"github.com/godbus/dbus/v5"
"github.com/jmoiron/sqlx"
_ "github.com/mattn/go-sqlite3"
)
var (
cliFlagIsDebug = flag.Bool("debug", false, "Enable debug output.")
cliFlagDesktopFileName = flag.String("desktop-file", "org.gnome.Evolution.desktop", "Desktop file name from /usr/share/applications or ~/.local/share/applications which is used for launching Evolution.")
databasesPaths = make([]string, 0)
dbusSession *dbus.Conn
)
func main() {
// CTRL+C handler.
signalHandler := make(chan os.Signal, 1)
shutdownDone := make(chan bool, 1)
signal.Notify(signalHandler, os.Interrupt, syscall.SIGTERM)
slog.Info("Starting Evolved...")
flag.Parse()
if *cliFlagIsDebug {
slog.SetDefault(slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelDebug})))
slog.Debug("Debug output enabled!")
}
dbusConn, err := dbus.ConnectSessionBus()
if err != nil {
slog.Error("Failed to connect to dbus session bus!", "error", err)
os.Exit(3)
}
dbusSession = dbusConn
watcher, err := fsnotify.NewWatcher()
if err != nil {
slog.Error("Failed to start filesystem changes watcher!", "error", err)
os.Exit(1)
}
if err := getEvolutionMailDatabasesPaths(); err != nil {
slog.Error("Failed to get Evolution mail databases paths!", "error", err)
os.Exit(2)
}
for _, databasePath := range databasesPaths {
slog.Info("Starting listening for filesystem changes in Evolution directory...", "path", databasePath)
_ = watcher.Add(databasePath)
}
go watchFSNotifications(watcher)
unreadCount, err := getEvolutionUnreadMailsCount()
if err != nil {
slog.Error("Failed to get unread counts!", "error", err)
os.Exit(4)
}
emitDBusSignal(unreadCount)
slog.Info("Evolved started.")
go func() {
<-signalHandler
slog.Info("Shutting down Evolved...")
if err := watcher.Close(); err != nil {
slog.Error("Failed to stop filesystem watcher!", "error", err)
}
emitDBusSignal(0)
shutdownDone <- true
}()
<-shutdownDone
slog.Info("Evolved stopped.")
os.Exit(0)
}
func emitDBusSignal(unreadCount uint) {
params := make(map[string]dbus.Variant)
params["count"] = dbus.MakeVariant(unreadCount)
params["count-visible"] = dbus.MakeVariant(unreadCount > 0)
if err := dbusSession.Emit(
"/",
"com.canonical.Unity.LauncherEntry.Update",
"application://"+*cliFlagDesktopFileName,
params,
); err != nil {
slog.Error("Failed to emit badge data via dbus!", "error", err)
}
}
func getEvolutionMailDatabasesPaths() error {
userData, err := user.Current()
if err != nil {
return fmt.Errorf("getEvolutionMailDatabasesPaths: get current user: %w", err)
}
if err := filepath.Walk(path.Join(userData.HomeDir, ".cache", "evolution", "mail"), func(path string, info os.FileInfo, err error) error {
if err == nil && strings.HasSuffix(info.Name(), "folders.db") {
databasesPaths = append(databasesPaths, path)
}
return nil
}); err != nil {
return fmt.Errorf("getEvolutionMailDatabasesPaths: get databases paths: %w", err)
}
return nil
}
func getEvolutionUnreadMailsCount() (uint, error) {
unreadCount := uint(0)
ctx, _ := context.WithTimeout(context.Background(), time.Second*5)
for _, dbFile := range databasesPaths {
db, err := sqlx.Connect("sqlite3", dbFile)
if err != nil {
slog.Error("Failed to open Evolution database file!", "file", dbFile, "error", err)
continue
}
counts := make([]uint, 0)
if err := db.SelectContext(ctx, &counts, "SELECT unread_count FROM folders"); err != nil {
slog.Error("Failed to get unread counts!", "file", dbFile, "error", err)
continue
}
for _, count := range counts {
unreadCount += count
}
if err := db.Close(); err != nil {
slog.Error("Failed to close database file for reading! Expect unexpected!", "file", dbFile, "error", err)
continue
}
}
return unreadCount, nil
}
func watchFSNotifications(watcher *fsnotify.Watcher) {
slog.Debug("Starting filesystem watcher goroutine...")
for {
select {
case event, ok := <-watcher.Events:
if !ok {
return
}
slog.Debug("Got filesystem event", "event", event.Name, "op", event.Op.String())
unreadCount, err := getEvolutionUnreadMailsCount()
if err != nil {
slog.Error("Failed to get unread count in watcher goroutine!", "error", err)
continue
}
slog.Info("Got unread count", "count", unreadCount)
emitDBusSignal(unreadCount)
case err, ok := <-watcher.Errors:
if !ok {
return
}
slog.Error("Got error from filesystem watcher!", "error", err)
}
}
}