208 lines
4.6 KiB
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)
|
|
}
|
|
}
|
|
}
|