From 74038a58a1b65cf854744a5c4789aaf6a0f125d7 Mon Sep 17 00:00:00 2001 From: jms-guy Date: Thu, 2 Oct 2025 12:49:26 -0400 Subject: [PATCH 1/3] added active session viewing command --- .gitignore | 2 +- cmd/cli/commands.go | 48 +++++++++++++++++------- cmd/cli/root.go | 1 + cmd/cli/timekeep.go | 12 ++++++ internal/database/active_sessions.sql.go | 27 +++++++++++++ internal/repository/repo.go | 6 +++ sql/queries/active_sessions.sql | 3 ++ 7 files changed, 85 insertions(+), 14 deletions(-) diff --git a/.gitignore b/.gitignore index 1ebc693..e409e58 100644 --- a/.gitignore +++ b/.gitignore @@ -31,4 +31,4 @@ go.work.sum # .idea/ # .vscode/ -timekeep.db \ No newline at end of file +testbuild.sh \ No newline at end of file diff --git a/cmd/cli/commands.go b/cmd/cli/commands.go index 2383399..e1658be 100644 --- a/cmd/cli/commands.go +++ b/cmd/cli/commands.go @@ -225,19 +225,6 @@ func (s *CLIService) ResetStats(args []string, all bool) error { return nil } -// Formats a time.Duration value to display hours, minutes or seconds -func (s *CLIService) formatDuration(prefix string, duration time.Duration) { - if duration < time.Minute { - fmt.Printf("%s%d seconds\n", prefix, int(duration.Seconds())) - } else if duration < time.Hour { - fmt.Printf("%s%d minutes\n", prefix, int(duration.Minutes())) - } else { - hours := int(duration.Hours()) - minutes := int(duration.Minutes()) % 60 - fmt.Printf("%s%dh %dm\n", prefix, hours, minutes) - } -} - // Removes active session and session records for all programs func (s *CLIService) ResetAllDatabase() error { err := s.AsRepo.RemoveAllSessions(context.Background()) @@ -275,3 +262,38 @@ func (s *CLIService) ResetDatabaseForProgram(program string) error { return nil } + +// Prints a list of currently active sessions being tracked by service +func (s *CLIService) GetActiveSessions() error { + activeSessions, err := s.AsRepo.GetAllActiveSessions(context.Background()) + if err != nil { + return fmt.Errorf("error getting active sessions: %w", err) + } + if len(activeSessions) == 0 { + fmt.Println("No active sessions.") + return nil + } + + fmt.Println("Active sessions: ") + for _, session := range activeSessions { + duration := time.Since(session.StartTime) + sessionDetails := fmt.Sprintf(" • %s - ", session.ProgramName) + + s.formatDuration(sessionDetails, duration) + } + + return nil +} + +// Formats a time.Duration value to display hours, minutes or seconds +func (s *CLIService) formatDuration(prefix string, duration time.Duration) { + if duration < time.Minute { + fmt.Printf("%s%d seconds\n", prefix, int(duration.Seconds())) + } else if duration < time.Hour { + fmt.Printf("%s%d minutes\n", prefix, int(duration.Minutes())) + } else { + hours := int(duration.Hours()) + minutes := int(duration.Minutes()) % 60 + fmt.Printf("%s%dh %dm\n", prefix, hours, minutes) + } +} diff --git a/cmd/cli/root.go b/cmd/cli/root.go index 75641cf..9396a52 100644 --- a/cmd/cli/root.go +++ b/cmd/cli/root.go @@ -24,6 +24,7 @@ func (s *CLIService) RootCmd() *cobra.Command { rootCmd.AddCommand(s.refreshCmd()) rootCmd.AddCommand(s.resetStatsCmd()) rootCmd.AddCommand(s.pingServiceCmd()) + rootCmd.AddCommand(s.getActiveSessionsCmd()) return rootCmd } diff --git a/cmd/cli/timekeep.go b/cmd/cli/timekeep.go index 7ea71e6..90fcc3d 100644 --- a/cmd/cli/timekeep.go +++ b/cmd/cli/timekeep.go @@ -126,3 +126,15 @@ func (s *CLIService) pingServiceCmd() *cobra.Command { }, } } + +func (s *CLIService) getActiveSessionsCmd() *cobra.Command { + return &cobra.Command{ + Use: "active", + Aliases: []string{"Active", "ACTIVE"}, + Short: "Get list of current active sessions being tracked", + Args: cobra.ExactArgs(0), + RunE: func(cmd *cobra.Command, args []string) error { + return s.GetActiveSessions() + }, + } +} diff --git a/internal/database/active_sessions.sql.go b/internal/database/active_sessions.sql.go index f1da6bf..65262b2 100644 --- a/internal/database/active_sessions.sql.go +++ b/internal/database/active_sessions.sql.go @@ -37,6 +37,33 @@ func (q *Queries) GetActiveSession(ctx context.Context, programName string) (tim return start_time, err } +const getAllActiveSessions = `-- name: GetAllActiveSessions :many +SELECT id, program_name, start_time FROM active_sessions +` + +func (q *Queries) GetAllActiveSessions(ctx context.Context) ([]ActiveSession, error) { + rows, err := q.db.QueryContext(ctx, getAllActiveSessions) + if err != nil { + return nil, err + } + defer rows.Close() + var items []ActiveSession + for rows.Next() { + var i ActiveSession + if err := rows.Scan(&i.ID, &i.ProgramName, &i.StartTime); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + const removeActiveSession = `-- name: RemoveActiveSession :exec DELETE FROM active_sessions WHERE program_name = ? diff --git a/internal/repository/repo.go b/internal/repository/repo.go index 73cb1eb..3b68652 100644 --- a/internal/repository/repo.go +++ b/internal/repository/repo.go @@ -24,6 +24,7 @@ type ProgramRepository interface { type ActiveRepository interface { CreateActiveSession(ctx context.Context, arg database.CreateActiveSessionParams) error GetActiveSession(ctx context.Context, programName string) (time.Time, error) + GetAllActiveSessions(ctx context.Context) ([]database.ActiveSession, error) RemoveActiveSession(ctx context.Context, programName string) error RemoveAllSessions(ctx context.Context) error } @@ -96,6 +97,11 @@ func (s *sqliteStore) GetActiveSession(ctx context.Context, programName string) return result, err } +func (s *sqliteStore) GetAllActiveSessions(ctx context.Context) ([]database.ActiveSession, error) { + result, err := s.db.GetAllActiveSessions(ctx) + return result, err +} + func (s *sqliteStore) RemoveActiveSession(ctx context.Context, programName string) error { return s.db.RemoveActiveSession(ctx, programName) } diff --git a/sql/queries/active_sessions.sql b/sql/queries/active_sessions.sql index be5700c..cccd899 100644 --- a/sql/queries/active_sessions.sql +++ b/sql/queries/active_sessions.sql @@ -6,6 +6,9 @@ VALUES (?, ?); SELECT start_time FROM active_sessions WHERE program_name = ?; +-- name: GetAllActiveSessions :many +SELECT * FROM active_sessions; + -- name: RemoveActiveSession :exec DELETE FROM active_sessions WHERE program_name = ?; From 0172a60ab63bf9853ace599fe7860f4e7bf2aab5 Mon Sep 17 00:00:00 2001 From: jms-guy Date: Thu, 2 Oct 2025 12:55:45 -0400 Subject: [PATCH 2/3] updated documentation --- README.md | 2 +- docs/commands.md | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 0b4ca63..90891bc 100644 --- a/README.md +++ b/README.md @@ -176,7 +176,7 @@ sudo systemctl daemon-reload - Windows - Check for running processes on service start - CLI - commands - sorting for session history - - show active sessions + - show active sessions (✓) - enhance ping for more service info ## Contributing & Issues diff --git a/docs/commands.md b/docs/commands.md index 51de006..cdfdba7 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -3,24 +3,35 @@ - `add` - Add a program to begin tracking. Add name of program's executable file name. May specify any number of programs to track in a single command, seperated by spaces in between - `timekeep add notepad.exe`, `timekeep add notepad.exe code.exe chrome.exe` + - `rm` - Remove a program from tracking list. May specify any number of programs to remove in a single command, seperated by spaces in between. Takes `--all` flag to clear program list completely - `timekeep rm notepad.exe`, `timekeep rm --all` + - `ls` - Lists programs being tracked by service - `timekeep ls` + - `stats` - Shows stats for currently tracked programs. Accepts program name as argument to show in-depth stats for that program, else shows basic stats for all programs - `timekeep stats`, `timekeep stats notepad.exe` + +- `active` + - Display list of current active sessions being tracked by service + - `timekeep active` + - `history` - Shows session history for a given program - `timekeep history notepad.exe` + - `refresh` - Sends a manual refresh command to the service - `timekeep refresh` + - `reset` - Reset tracking stats for given programs. Accepts multiple arguments seperated by space. Takes `--all` flag to reset all stats - `timekeep reset notepad.exe`, `timekeep reset --all` + - `ping` - Gets current state of Timekeep service - `timekeep ping` From 6c41347d4fed9e25b7cc16c956f1cb77ccbb321d Mon Sep 17 00:00:00 2001 From: jms-guy Date: Thu, 2 Oct 2025 13:14:08 -0400 Subject: [PATCH 3/3] fixed function signature typo, ping cmd test. added active cmd test --- cmd/cli/commands_test.go | 13 ++++++++++++- cmd/cli/ping_unsupported.go | 7 +++++++ cmd/cli/ping_windows.go | 2 +- cmd/service/internal/events/events_unsupported.go | 6 +----- 4 files changed, 21 insertions(+), 7 deletions(-) create mode 100644 cmd/cli/ping_unsupported.go diff --git a/cmd/cli/commands_test.go b/cmd/cli/commands_test.go index bbfa977..902fd1a 100644 --- a/cmd/cli/commands_test.go +++ b/cmd/cli/commands_test.go @@ -222,5 +222,16 @@ func TestPingService(t *testing.T) { } err = s.PingService() - assert.Nil(t, err, "PingService should not err") + + assert.Contains(t, err.Error(), "service not running") +} + +func TestGetActiveSessions(t *testing.T) { + s, err := setupTestServiceWithPrograms(t, "notepad.exe", "code.exe") + if err != nil { + t.Fatalf("Failed to setup test service: %v", err) + } + + err = s.GetActiveSessions() + assert.Nil(t, err, "GetActiveSessions should not err") } diff --git a/cmd/cli/ping_unsupported.go b/cmd/cli/ping_unsupported.go new file mode 100644 index 0000000..2cf7bb2 --- /dev/null +++ b/cmd/cli/ping_unsupported.go @@ -0,0 +1,7 @@ +//go:build !windows && !linux + +package main + +func (s *CLIService) PingService() error { + return nil +} diff --git a/cmd/cli/ping_windows.go b/cmd/cli/ping_windows.go index a526366..43ac00f 100644 --- a/cmd/cli/ping_windows.go +++ b/cmd/cli/ping_windows.go @@ -1,4 +1,4 @@ -//go:build !linux +//go:build windows package main diff --git a/cmd/service/internal/events/events_unsupported.go b/cmd/service/internal/events/events_unsupported.go index 70cb633..75b8505 100644 --- a/cmd/service/internal/events/events_unsupported.go +++ b/cmd/service/internal/events/events_unsupported.go @@ -9,11 +9,7 @@ import ( "github.com/jms-guy/timekeep/internal/repository" ) -func (e *EventController) MonitorProcesses(logger *log.Logger, s *sessions.SessionManager, pr repository.ProgramRepository, a repository.ActiveRepository, h repository.ActiveRepository, programs []string) { - return -} - -func (e *EventController) startProcessMonitor(logger *log.Logger, programs []string) { +func (e *EventController) MonitorProcesses(logger *log.Logger, s *sessions.SessionManager, pr repository.ProgramRepository, a repository.ActiveRepository, h repository.HistoryRepository, programs []string) { return }