Skip to content

Commit 3ab8291

Browse files
authored
feat(sync): moved logic in sync-test scripts into rust, enabling sync with a single command (#430)
* feat(sync): moved logic in `test-sync-pull/push.sh` scripts into rust code, enabling easy sync with a single command * fix: fixes to sync dirs * fix: moved --sync-dir arg into sync-advanced subcommand, fixed warnings * fix: pass around client instead of port/testing * fix: fixed log dir for aw-sync, misc sync fixes and refactor
1 parent e1cd761 commit 3ab8291

13 files changed

+385
-110
lines changed

Cargo.lock

+14-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

aw-server/src/dirs.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -53,15 +53,15 @@ pub fn get_cache_dir() -> Result<PathBuf, ()> {
5353
}
5454

5555
#[cfg(not(target_os = "android"))]
56-
pub fn get_log_dir() -> Result<PathBuf, ()> {
56+
pub fn get_log_dir(module: &str) -> Result<PathBuf, ()> {
5757
let mut dir = appdirs::user_log_dir(Some("activitywatch"), None)?;
58-
dir.push("aw-server-rust");
58+
dir.push(module);
5959
fs::create_dir_all(dir.clone()).expect("Unable to create log dir");
6060
Ok(dir)
6161
}
6262

6363
#[cfg(target_os = "android")]
64-
pub fn get_log_dir() -> Result<PathBuf, ()> {
64+
pub fn get_log_dir(module: &str) -> Result<PathBuf, ()> {
6565
panic!("not implemented on Android");
6666
}
6767

@@ -87,7 +87,7 @@ fn test_get_dirs() {
8787
set_android_data_dir("/test");
8888

8989
get_cache_dir().unwrap();
90-
get_log_dir().unwrap();
90+
get_log_dir("aw-server-rust").unwrap();
9191
db_path(true).unwrap();
9292
db_path(false).unwrap();
9393
}

aw-server/src/logging.rs

+10-12
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,17 @@ use fern::colors::{Color, ColoredLevelConfig};
55

66
use crate::dirs;
77

8-
pub fn setup_logger(testing: bool, verbose: bool) -> Result<(), fern::InitError> {
8+
pub fn setup_logger(module: &str, testing: bool, verbose: bool) -> Result<(), fern::InitError> {
99
let mut logfile_path: PathBuf =
10-
dirs::get_log_dir().expect("Unable to get log dir to store logs in");
10+
dirs::get_log_dir(module).expect("Unable to get log dir to store logs in");
1111
fs::create_dir_all(logfile_path.clone()).expect("Unable to create folder for logs");
12-
logfile_path.push(
13-
chrono::Local::now()
14-
.format(if !testing {
15-
"aw-server_%Y-%m-%dT%H-%M-%S%z.log"
16-
} else {
17-
"aw-server-testing_%Y-%m-%dT%H-%M-%S%z.log"
18-
})
19-
.to_string(),
20-
);
12+
let filename = if !testing {
13+
format!("{}_%Y-%m-%dT%H-%M-%S%z.log", module)
14+
} else {
15+
format!("{}-testing_%Y-%m-%dT%H-%M-%S%z.log", module)
16+
};
17+
18+
logfile_path.push(chrono::Local::now().format(&filename).to_string());
2119

2220
log_panics::init();
2321

@@ -93,6 +91,6 @@ mod tests {
9391
#[ignore]
9492
#[test]
9593
fn test_setup_logger() {
96-
setup_logger(true, true).unwrap();
94+
setup_logger("aw-server-rust", true, true).unwrap();
9795
}
9896
}

aw-server/src/main.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,8 @@ async fn main() -> Result<(), rocket::Error> {
7070
testing = true;
7171
}
7272

73-
logging::setup_logger(testing, opts.verbose).expect("Failed to setup logging");
73+
logging::setup_logger("aw-server-rust", testing, opts.verbose)
74+
.expect("Failed to setup logging");
7475

7576
if testing {
7677
info!("Running server in Testing mode");

aw-sync/Cargo.toml

+4
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,16 @@ path = "src/main.rs"
1414

1515
[dependencies]
1616
log = "0.4"
17+
toml = "0.7"
1718
chrono = { version = "0.4", features = ["serde"] }
1819
serde = "1.0"
1920
serde_json = "1.0"
2021
reqwest = { version = "0.11", features = ["json", "blocking"] }
2122
clap = { version = "4.1", features = ["derive"] }
23+
appdirs = "0.2.0"
24+
dirs = "3.0.2"
2225
aw-server = { path = "../aw-server" }
2326
aw-models = { path = "../aw-models" }
2427
aw-datastore = { path = "../aw-datastore" }
2528
aw-client-rust = { path = "../aw-client-rust" }
29+
gethostname = "0.4.3"

aw-sync/src/dirs.rs

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
use dirs::home_dir;
2+
use std::fs;
3+
use std::path::PathBuf;
4+
5+
// TODO: This could be refactored to share logic with aw-server/src/dirs.rs
6+
// TODO: add proper config support
7+
#[allow(dead_code)]
8+
pub fn get_config_dir() -> Result<PathBuf, ()> {
9+
let mut dir = appdirs::user_config_dir(Some("activitywatch"), None, false)?;
10+
dir.push("aw-sync");
11+
fs::create_dir_all(dir.clone()).expect("Unable to create config dir");
12+
Ok(dir)
13+
}
14+
15+
pub fn get_server_config_path(testing: bool) -> Result<PathBuf, ()> {
16+
let dir = aw_server::dirs::get_config_dir()?;
17+
Ok(dir.join(if testing {
18+
"config-testing.toml"
19+
} else {
20+
"config.toml"
21+
}))
22+
}
23+
24+
pub fn get_sync_dir() -> Result<PathBuf, ()> {
25+
// TODO: make this configurable
26+
home_dir().map(|p| p.join("ActivityWatchSync")).ok_or(())
27+
}

aw-sync/src/lib.rs

+7
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,12 @@ pub use sync::sync_datastores;
1010
pub use sync::sync_run;
1111
pub use sync::SyncSpec;
1212

13+
mod sync_wrapper;
14+
pub use sync_wrapper::push;
15+
pub use sync_wrapper::{pull, pull_all};
16+
1317
mod accessmethod;
1418
pub use accessmethod::AccessMethod;
19+
20+
mod dirs;
21+
mod util;

aw-sync/src/main.rs

+76-40
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,10 @@ use clap::{Parser, Subcommand};
2323
use aw_client_rust::blocking::AwClient;
2424

2525
mod accessmethod;
26+
mod dirs;
2627
mod sync;
27-
28-
const DEFAULT_PORT: &str = "5600";
28+
mod sync_wrapper;
29+
mod util;
2930

3031
#[derive(Parser)]
3132
#[clap(version = "0.1", author = "Erik Bjäreholt")]
@@ -38,8 +39,8 @@ struct Opts {
3839
host: String,
3940

4041
/// Port of instance to connect to.
41-
#[clap(long, default_value = DEFAULT_PORT)]
42-
port: String,
42+
#[clap(long)]
43+
port: Option<String>,
4344

4445
/// Convenience option for using the default testing host and port.
4546
#[clap(long)]
@@ -48,42 +49,53 @@ struct Opts {
4849
/// Enable debug logging.
4950
#[clap(long)]
5051
verbose: bool,
51-
52-
/// Full path to sync directory.
53-
/// If not specified, exit.
54-
#[clap(long)]
55-
sync_dir: String,
56-
57-
/// Full path to sync db file
58-
/// Useful for syncing buckets from a specific db file in the sync directory.
59-
/// Must be a valid absolute path to a file in the sync directory.
60-
#[clap(long)]
61-
sync_db: Option<String>,
6252
}
6353

6454
#[derive(Subcommand)]
6555
enum Commands {
66-
/// Sync subcommand.
56+
/// Sync subcommand (basic)
57+
///
58+
/// Pulls remote buckets then pushes local buckets.
59+
Sync {
60+
/// Host(s) to pull from, comma separated. Will pull from all hosts if not specified.
61+
#[clap(long)]
62+
host: Option<String>,
63+
},
64+
65+
/// Sync subcommand (advanced)
6766
///
6867
/// Pulls remote buckets then pushes local buckets.
6968
/// First pulls remote buckets in the sync directory to the local aw-server.
7069
/// Then pushes local buckets from the aw-server to the local sync directory.
7170
#[clap(arg_required_else_help = true)]
72-
Sync {
71+
SyncAdvanced {
7372
/// Date to start syncing from.
7473
/// If not specified, start from beginning.
7574
/// NOTE: might be unstable, as count cannot be used to verify integrity of sync.
7675
/// Format: YYYY-MM-DD
7776
#[clap(long)]
7877
start_date: Option<String>,
78+
7979
/// Specify buckets to sync using a comma-separated list.
8080
/// If not specified, all buckets will be synced.
8181
#[clap(long)]
8282
buckets: Option<String>,
83+
8384
/// Mode to sync in. Can be "push", "pull", or "both".
8485
/// Defaults to "both".
8586
#[clap(long, default_value = "both")]
8687
mode: String,
88+
89+
/// Full path to sync directory.
90+
/// If not specified, exit.
91+
#[clap(long)]
92+
sync_dir: String,
93+
94+
/// Full path to sync db file
95+
/// Useful for syncing buckets from a specific db file in the sync directory.
96+
/// Must be a valid absolute path to a file in the sync directory.
97+
#[clap(long)]
98+
sync_db: Option<String>,
8799
},
88100
/// List buckets and their sync status.
89101
List {},
@@ -95,35 +107,59 @@ fn main() -> Result<(), Box<dyn Error>> {
95107

96108
info!("Started aw-sync...");
97109

98-
aw_server::logging::setup_logger(true, verbose).expect("Failed to setup logging");
99-
100-
let sync_directory = if opts.sync_dir.is_empty() {
101-
println!("No sync directory specified, exiting...");
102-
std::process::exit(1);
103-
} else {
104-
Path::new(&opts.sync_dir)
105-
};
106-
info!("Using sync dir: {}", sync_directory.display());
107-
108-
if let Some(sync_db) = &opts.sync_db {
109-
info!("Using sync db: {}", sync_db);
110-
}
110+
aw_server::logging::setup_logger("aw-sync", opts.testing, verbose)
111+
.expect("Failed to setup logging");
111112

112-
let port = if opts.testing && opts.port == DEFAULT_PORT {
113-
"5666"
114-
} else {
115-
&opts.port
116-
};
113+
let port = opts
114+
.port
115+
.or_else(|| Some(crate::util::get_server_port(opts.testing).ok()?.to_string()))
116+
.unwrap();
117117

118-
let client = AwClient::new(opts.host.as_str(), port, "aw-sync");
118+
let client = AwClient::new(opts.host.as_str(), port.as_str(), "aw-sync");
119119

120120
match &opts.command {
121+
// Perform basic sync
122+
Commands::Sync { host } => {
123+
// Pull
124+
match host {
125+
Some(host) => {
126+
let hosts: Vec<&str> = host.split(',').collect();
127+
for host in hosts.iter() {
128+
info!("Pulling from host: {}", host);
129+
sync_wrapper::pull(host, &client)?;
130+
}
131+
}
132+
None => {
133+
info!("Pulling from all hosts");
134+
sync_wrapper::pull_all(&client)?;
135+
}
136+
}
137+
138+
// Push
139+
info!("Pushing local data");
140+
sync_wrapper::push(&client)?;
141+
Ok(())
142+
}
121143
// Perform two-way sync
122-
Commands::Sync {
144+
Commands::SyncAdvanced {
123145
start_date,
124146
buckets,
125147
mode,
148+
sync_dir,
149+
sync_db,
126150
} => {
151+
let sync_directory = if sync_dir.is_empty() {
152+
error!("No sync directory specified, exiting...");
153+
std::process::exit(1);
154+
} else {
155+
Path::new(&sync_dir)
156+
};
157+
info!("Using sync dir: {}", sync_directory.display());
158+
159+
if let Some(sync_db) = &sync_db {
160+
info!("Using sync db: {}", sync_db);
161+
}
162+
127163
let start: Option<DateTime<Utc>> = start_date.as_ref().map(|date| {
128164
println!("{}", date.clone());
129165
chrono::NaiveDate::parse_from_str(&date.clone(), "%Y-%m-%d")
@@ -140,7 +176,7 @@ fn main() -> Result<(), Box<dyn Error>> {
140176
.as_ref()
141177
.map(|b| b.split(',').map(|s| s.to_string()).collect());
142178

143-
let sync_db: Option<PathBuf> = opts.sync_db.as_ref().map(|db| {
179+
let sync_db: Option<PathBuf> = sync_db.as_ref().map(|db| {
144180
let db_path = Path::new(db);
145181
if !db_path.is_absolute() {
146182
panic!("Sync db path must be absolute");
@@ -165,11 +201,11 @@ fn main() -> Result<(), Box<dyn Error>> {
165201
_ => panic!("Invalid mode"),
166202
};
167203

168-
sync::sync_run(client, &sync_spec, mode_enum)
204+
sync::sync_run(&client, &sync_spec, mode_enum)
169205
}
170206

171207
// List all buckets
172-
Commands::List {} => sync::list_buckets(&client, sync_directory),
208+
Commands::List {} => sync::list_buckets(&client),
173209
}?;
174210

175211
// Needed to give the datastores some time to commit before program is shut down.

0 commit comments

Comments
 (0)