11use aw_client_rust:: AwClient ;
22use aw_models:: { Bucket , Event } ;
3- use chrono:: { TimeDelta , Utc } ;
4- use dirs:: config_dir;
3+ use chrono:: { DateTime , Duration as ChronoDuration , NaiveDateTime , TimeDelta , Utc } ;
4+ use regex:: Regex ;
5+ use std:: path:: PathBuf ;
56use env_logger:: Env ;
67use log:: { info, warn} ;
78use reqwest;
89use serde_json:: { Map , Value } ;
910use serde_yaml;
1011use std:: env;
12+ use dirs:: config_dir;
1113use std:: fs:: { DirBuilder , File } ;
1214use std:: io:: prelude:: * ;
1315use std:: thread:: sleep;
1416use tokio:: time:: { interval, Duration } ;
1517
18+ fn parse_time_string ( time_str : & str ) -> Option < ChronoDuration > {
19+ let re = Regex :: new ( r"^(\d+)([dhm])$" ) . unwrap ( ) ;
20+ if let Some ( caps) = re. captures ( time_str) {
21+ let amount: i64 = caps. get ( 1 ) ?. as_str ( ) . parse ( ) . ok ( ) ?;
22+ let unit = caps. get ( 2 ) ?. as_str ( ) ;
23+
24+ match unit {
25+ "d" => Some ( ChronoDuration :: days ( amount) ) ,
26+ "h" => Some ( ChronoDuration :: hours ( amount) ) ,
27+ "m" => Some ( ChronoDuration :: minutes ( amount) ) ,
28+ _ => None ,
29+ }
30+ } else {
31+ None
32+ }
33+ }
34+
35+ async fn sync_historical_data (
36+ client : & reqwest:: Client ,
37+ aw_client : & AwClient ,
38+ username : & str ,
39+ apikey : & str ,
40+ from_time : ChronoDuration ,
41+ ) -> Result < ( ) , Box < dyn std:: error:: Error > > {
42+ let from_timestamp = ( Utc :: now ( ) - from_time) . timestamp ( ) ;
43+ let url = format ! (
44+ "http://ws.audioscrobbler.com/2.0/?method=user.getrecenttracks&user={}&api_key={}&format=json&limit=200&from={}" ,
45+ username, apikey, from_timestamp
46+ ) ;
47+
48+ let response = client. get ( & url) . send ( ) . await ?;
49+ let v: Value = response. json ( ) . await ?;
50+
51+ if let Some ( tracks) = v[ "recenttracks" ] [ "track" ] . as_array ( ) {
52+ info ! ( "Syncing {} historical tracks..." , tracks. len( ) ) ;
53+ for track in tracks. iter ( ) . rev ( ) {
54+ let mut event_data: Map < String , Value > = Map :: new ( ) ;
55+
56+ event_data. insert ( "title" . to_string ( ) , track[ "name" ] . to_owned ( ) ) ;
57+ event_data. insert (
58+ "artist" . to_string ( ) ,
59+ track[ "artist" ] [ "#text" ] . to_owned ( ) ,
60+ ) ;
61+ event_data. insert (
62+ "album" . to_string ( ) ,
63+ track[ "album" ] [ "#text" ] . to_owned ( ) ,
64+ ) ;
65+
66+ // Get timestamp from the track
67+ if let Some ( date) = track[ "date" ] [ "uts" ] . as_str ( ) {
68+ if let Ok ( timestamp) = date. parse :: < i64 > ( ) {
69+ // TODO: remove the deprecated from_utc and from_timestamp
70+ let event = Event {
71+ id : None ,
72+ timestamp : DateTime :: < Utc > :: from_utc ( NaiveDateTime :: from_timestamp ( timestamp, 0 ) , Utc ) ,
73+ duration : TimeDelta :: seconds ( 30 ) ,
74+ data : event_data,
75+ } ;
76+
77+ aw_client
78+ . insert_event ( "aw-watcher-lastfm" , & event)
79+ . await
80+ . unwrap_or_else ( |e| {
81+ warn ! ( "Error inserting historical event: {:?}" , e) ;
82+ } ) ;
83+ }
84+ }
85+ }
86+ info ! ( "Historical sync completed!" ) ;
87+ }
88+
89+ Ok ( ( ) )
90+ }
91+
1692fn get_config_path ( ) -> Option < std:: path:: PathBuf > {
1793 config_dir ( ) . map ( |mut path| {
1894 path. push ( "activitywatch" ) ;
@@ -52,17 +128,43 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
52128
53129 let args: Vec < String > = env:: args ( ) . collect ( ) ;
54130 let mut port: u16 = 5600 ;
55- if args. len ( ) > 1 {
56- for idx in 1 ..args. len ( ) {
57- if args[ idx] == "--port" {
58- port = args[ idx + 1 ] . parse ( ) . expect ( "Invalid port number" ) ;
59- break ;
131+ let mut sync_duration: Option < ChronoDuration > = None ;
132+
133+ let mut idx = 1 ;
134+ while idx < args. len ( ) {
135+ match args[ idx] . as_str ( ) {
136+ "--port" => {
137+ if idx + 1 < args. len ( ) {
138+ port = args[ idx + 1 ] . parse ( ) . expect ( "Invalid port number" ) ;
139+ idx += 2 ;
140+ } else {
141+ panic ! ( "--port requires a value" ) ;
142+ }
60143 }
61- if args [ idx ] == "--testing" {
144+ "--testing" => {
62145 port = 5699 ;
146+ idx += 1 ;
147+ }
148+ "--sync" => {
149+ if idx + 1 < args. len ( ) {
150+ sync_duration = Some ( parse_time_string ( & args[ idx + 1 ] )
151+ . expect ( "Invalid sync duration format. Use format: 7d, 24h, or 30m" ) ) ;
152+ idx += 2 ;
153+ } else {
154+ panic ! ( "--sync requires a duration value (e.g., 7d, 24h, 30m)" ) ;
155+ }
63156 }
64- if args[ idx] == "--help" {
65- println ! ( "Usage: aw-watcher-lastfm-rust [--testing] [--port PORT] [--help]" ) ;
157+ "--help" => {
158+ println ! ( "Usage: aw-watcher-lastfm-rust [--testing] [--port PORT] [--sync DURATION] [--help]" ) ;
159+ println ! ( "\n Options:" ) ;
160+ println ! ( " --testing Use testing port (5699)" ) ;
161+ println ! ( " --port PORT Specify custom port" ) ;
162+ println ! ( " --sync DURATION Sync historical data (format: 7d, 24h, 30m)" ) ;
163+ println ! ( " --help Show this help message" ) ;
164+ return Ok ( ( ) ) ;
165+ }
166+ _ => {
167+ println ! ( "Unknown argument: {}" , args[ idx] ) ;
66168 return Ok ( ( ) ) ;
67169 }
68170 }
@@ -75,10 +177,12 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
75177 env_logger:: init_from_env ( env) ;
76178
77179 if !config_path. exists ( ) {
78- DirBuilder :: new ( )
79- . recursive ( true )
80- . create ( config_dir)
81- . expect ( "Unable to create directory" ) ;
180+ if !config_dir. exists ( ) {
181+ DirBuilder :: new ( )
182+ . recursive ( true )
183+ . create ( & config_dir)
184+ . expect ( "Unable to create directory" ) ;
185+ }
82186 let mut file = File :: create ( & config_path) . expect ( "Unable to create file" ) ;
83187 file. write_all ( b"apikey: your-api-key\n username: your_username\n polling_interval: 10" )
84188 . expect ( "Unable to write to file" ) ;
@@ -138,6 +242,16 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
138242 . build ( )
139243 . unwrap ( ) ;
140244
245+ // Handle historical sync if requested
246+ if let Some ( duration) = sync_duration {
247+ info ! ( "Starting historical sync..." ) ;
248+ match sync_historical_data ( & client, & aw_client, & username, & apikey, duration) . await {
249+ Ok ( _) => info ! ( "Historical sync completed successfully" ) ,
250+ Err ( e) => warn ! ( "Error during historical sync: {:?}" , e) ,
251+ }
252+ info ! ( "Starting real-time tracking..." ) ;
253+ }
254+
141255 loop {
142256 interval. tick ( ) . await ;
143257
0 commit comments