File size: 2,608 Bytes
6a7089a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
package profiles

import (
	"encoding/json"
	"net/url"
	"os"
	"path/filepath"
	"sync"
	"time"

	"github.com/pinchtab/pinchtab/internal/bridge"
)

// userConfigDir returns the OS-appropriate app config directory.
// For backwards compatibility, if ~/.pinchtab exists and the new location
// doesn't, it returns ~/.pinchtab.
func userConfigDir() string {
	h, _ := os.UserHomeDir()
	legacyPath := filepath.Join(h, ".pinchtab")

	configDir, err := os.UserConfigDir()
	if err != nil {
		return legacyPath
	}

	newPath := filepath.Join(configDir, "pinchtab")

	// Backwards compatibility: if legacy location exists and new doesn't, use legacy
	if dirExists(legacyPath) && !dirExists(newPath) {
		return legacyPath
	}

	return newPath
}

// dirExists checks if a directory exists
func dirExists(path string) bool {
	info, err := os.Stat(path)
	if err != nil {
		return false
	}
	return info.IsDir()
}

type ActionTracker struct {
	logs map[string][]bridge.ActionRecord
	mu   sync.RWMutex
}

func NewActionTracker() *ActionTracker {
	t := &ActionTracker{
		logs: make(map[string][]bridge.ActionRecord),
	}
	_ = t.load()
	return t
}

func (t *ActionTracker) Record(profile string, record bridge.ActionRecord) {
	t.mu.Lock()
	defer t.mu.Unlock()
	t.logs[profile] = append(t.logs[profile], record)
	if len(t.logs[profile]) > 1000 {
		t.logs[profile] = t.logs[profile][len(t.logs[profile])-1000:]
	}
	_ = t.save()
}

func (t *ActionTracker) GetLogs(profile string, limit int) []bridge.ActionRecord {
	t.mu.RLock()
	defer t.mu.RUnlock()
	logs := t.logs[profile]
	if limit > 0 && len(logs) > limit {
		return logs[len(logs)-limit:]
	}
	return logs
}

func (t *ActionTracker) Analyze(profile string) bridge.AnalyticsReport {
	t.mu.RLock()
	defer t.mu.RUnlock()
	logs := t.logs[profile]

	report := bridge.AnalyticsReport{
		TotalActions: len(logs),
		CommonHosts:  make(map[string]int),
	}

	last24h := time.Now().Add(-24 * time.Hour)
	for _, l := range logs {
		if l.Timestamp.After(last24h) {
			report.Last24h++
		}
		if l.URL != "" {
			u, err := url.Parse(l.URL)
			if err == nil && u.Host != "" {
				report.CommonHosts[u.Host]++
			}
		}
	}
	return report
}

func (t *ActionTracker) save() error {
	path := filepath.Join(userConfigDir(), "action_logs.json")
	_ = os.MkdirAll(filepath.Dir(path), 0755)

	data, err := json.Marshal(t.logs)
	if err != nil {
		return err
	}
	return os.WriteFile(path, data, 0644)
}

func (t *ActionTracker) load() error {
	path := filepath.Join(userConfigDir(), "action_logs.json")
	data, err := os.ReadFile(path)
	if err != nil {
		return err
	}
	return json.Unmarshal(data, &t.logs)
}