1
+ use std:: collections:: HashSet ;
2
+ use std:: process:: Output ;
3
+
1
4
use chrono:: { DateTime , Utc } ;
2
5
use machineid_rs:: { Encryption , HWIDComponent , IdBuilder } ;
3
6
use sysinfo:: System ;
7
+ use tokio:: process:: Command ;
8
+ use tokio:: sync:: Mutex ;
4
9
use tokio:: time:: Duration ;
5
10
6
11
use super :: Result ;
@@ -29,6 +34,7 @@ pub struct Tracker {
29
34
collectors : Vec < Box < dyn Collect > > ,
30
35
can_track : bool ,
31
36
start_time : DateTime < Utc > ,
37
+ email : Mutex < Option < Vec < String > > > ,
32
38
}
33
39
34
40
impl Default for Tracker {
@@ -44,6 +50,7 @@ impl Default for Tracker {
44
50
collectors : vec ! [ ga_tracker, posthog_tracker] ,
45
51
can_track,
46
52
start_time,
53
+ email : Mutex :: new ( None ) ,
47
54
}
48
55
}
49
56
}
@@ -74,6 +81,7 @@ impl Tracker {
74
81
cwd : cwd ( ) ,
75
82
user : user ( ) ,
76
83
version : version ( ) ,
84
+ email : self . email ( ) . await . clone ( ) ,
77
85
} ;
78
86
79
87
// Dispatch the event to all collectors
@@ -86,6 +94,74 @@ impl Tracker {
86
94
87
95
Ok ( ( ) )
88
96
}
97
+
98
+ async fn email ( & ' static self ) -> Vec < String > {
99
+ let mut guard = self . email . lock ( ) . await ;
100
+ if guard. is_none ( ) {
101
+ * guard = Some ( email ( ) . await . into_iter ( ) . collect ( ) ) ;
102
+ }
103
+ guard. clone ( ) . unwrap_or_default ( )
104
+ }
105
+ }
106
+
107
+ // Get the email address
108
+ async fn email ( ) -> HashSet < String > {
109
+ fn parse ( output : Output ) -> Option < String > {
110
+ if output. status . success ( ) {
111
+ let text = String :: from_utf8_lossy ( & output. stdout ) . trim ( ) . to_string ( ) ;
112
+ if !text. is_empty ( ) {
113
+ return Some ( text) ;
114
+ }
115
+ }
116
+
117
+ None
118
+ }
119
+
120
+ // From Git
121
+ async fn git ( ) -> Option < String > {
122
+ let output = Command :: new ( "git" )
123
+ . args ( [ "config" , "--global" , "user.email" ] )
124
+ . output ( )
125
+ . await
126
+ . ok ( ) ?;
127
+
128
+ parse ( output)
129
+ }
130
+
131
+ // From SSH Keys
132
+ async fn ssh ( ) -> Option < HashSet < String > > {
133
+ // Single command to find all unique email addresses from .pub files
134
+ let output = Command :: new ( "sh" )
135
+ . args ( [
136
+ "-c" ,
137
+ "cat ~/.ssh/*.pub | grep -o '[^ ]\\ +@[^ ]\\ +\\ .[^ ]\\ +'" ,
138
+ ] )
139
+ . output ( )
140
+ . await
141
+ . ok ( ) ?;
142
+
143
+ Some ( parse ( output) ?. lines ( ) . map ( |o| o. to_owned ( ) ) . collect ( ) )
144
+ }
145
+
146
+ let git_email = git ( ) . await ;
147
+ let ssh_emails = ssh ( ) . await ;
148
+ let mut email_ids = HashSet :: new ( ) ;
149
+
150
+ if let Some ( email) = git_email {
151
+ if !email. trim ( ) . is_empty ( ) {
152
+ email_ids. insert ( email. trim ( ) . to_string ( ) ) ;
153
+ }
154
+ }
155
+
156
+ if let Some ( emails) = ssh_emails {
157
+ for email in emails {
158
+ if !email. trim ( ) . is_empty ( ) {
159
+ email_ids. insert ( email. trim ( ) . to_string ( ) ) ;
160
+ }
161
+ }
162
+ }
163
+
164
+ email_ids
89
165
}
90
166
91
167
// Generates a random client ID
@@ -141,6 +217,7 @@ fn os_name() -> String {
141
217
142
218
#[ cfg( test) ]
143
219
mod tests {
220
+
144
221
use lazy_static:: lazy_static;
145
222
146
223
use super :: * ;
0 commit comments