Skip to content

Commit e5ba9d2

Browse files
authored
Merge pull request #257 from ActivityWatch/dev/sync-stuff
another sync PR
2 parents 014c9c0 + 3398993 commit e5ba9d2

File tree

16 files changed

+786
-317
lines changed

16 files changed

+786
-317
lines changed

.gitignore

+7-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
target
2-
**/*.rs.bk
3-
NDK
42
.cargo/config
3+
NDK
4+
5+
**/*.rs.bk
56
*.zip
67
*.profraw
8+
9+
*.sqlite*
10+
*.db
11+
*.db-journal

aw-client-rust/src/lib.rs

+33-7
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,26 @@ use serde_json::Map;
1212

1313
pub use aw_models::{Bucket, BucketMetadata, Event};
1414

15-
#[derive(Debug)]
1615
pub struct AwClient {
1716
client: reqwest::blocking::Client,
1817
pub baseurl: String,
1918
pub name: String,
2019
pub hostname: String,
2120
}
2221

22+
impl std::fmt::Debug for AwClient {
23+
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
24+
write!(f, "AwClient(baseurl={:?})", self.baseurl)
25+
}
26+
}
27+
2328
impl AwClient {
2429
pub fn new(ip: &str, port: &str, name: &str) -> AwClient {
2530
let baseurl = format!("http://{}:{}", ip, port);
26-
let client = reqwest::blocking::Client::new();
31+
let client = reqwest::blocking::Client::builder()
32+
.timeout(std::time::Duration::from_secs(120))
33+
.build()
34+
.unwrap();
2735
let hostname = gethostname::gethostname().into_string().unwrap();
2836
AwClient {
2937
client,
@@ -44,9 +52,18 @@ impl AwClient {
4452
self.client.get(&url).send()?.json()
4553
}
4654

47-
pub fn create_bucket(&self, bucketname: &str, buckettype: &str) -> Result<(), reqwest::Error> {
48-
let url = format!("{}/api/0/buckets/{}", self.baseurl, bucketname);
49-
let data = Bucket {
55+
pub fn create_bucket(&self, bucket: &Bucket) -> Result<(), reqwest::Error> {
56+
let url = format!("{}/api/0/buckets/{}", self.baseurl, bucket.id);
57+
self.client.post(&url).json(bucket).send()?;
58+
Ok(())
59+
}
60+
61+
pub fn create_bucket_simple(
62+
&self,
63+
bucketname: &str,
64+
buckettype: &str,
65+
) -> Result<(), reqwest::Error> {
66+
let bucket = Bucket {
5067
bid: None,
5168
id: bucketname.to_string(),
5269
client: self.name.clone(),
@@ -58,8 +75,7 @@ impl AwClient {
5875
created: None,
5976
last_updated: None,
6077
};
61-
self.client.post(&url).json(&data).send()?;
62-
Ok(())
78+
self.create_bucket(&bucket)
6379
}
6480

6581
pub fn delete_bucket(&self, bucketname: &str) -> Result<(), reqwest::Error> {
@@ -103,6 +119,16 @@ impl AwClient {
103119
Ok(())
104120
}
105121

122+
pub fn insert_events(
123+
&self,
124+
bucketname: &str,
125+
events: Vec<Event>,
126+
) -> Result<(), reqwest::Error> {
127+
let url = format!("{}/api/0/buckets/{}/events", self.baseurl, bucketname);
128+
self.client.post(&url).json(&events).send()?;
129+
Ok(())
130+
}
131+
106132
pub fn heartbeat(
107133
&self,
108134
bucketname: &str,

aw-client-rust/tests/test.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,9 @@ mod test {
7676

7777
let bucketname = format!("aw-client-rust-test_{}", client.hostname);
7878
let buckettype = "test-type";
79-
client.create_bucket(&bucketname, &buckettype).unwrap();
79+
client
80+
.create_bucket_simple(&bucketname, &buckettype)
81+
.unwrap();
8082

8183
let bucket = client.get_bucket(&bucketname).unwrap();
8284
assert!(bucket.id == bucketname);

aw-datastore/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ mod worker;
2222
pub use self::datastore::DatastoreInstance;
2323
pub use self::worker::Datastore;
2424

25+
#[derive(Debug, Clone)]
2526
pub enum DatastoreMethod {
2627
Memory(),
2728
File(String),

aw-datastore/src/worker.rs

+63-51
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ pub enum Response {
5353
Count(i64),
5454
KeyValue(KeyValue),
5555
StringVec(Vec<String>),
56+
// Used to indicate that no response should occur at all (not even an empty one)
57+
NoResponse(),
5658
}
5759

5860
#[allow(clippy::large_enum_variant)]
@@ -78,6 +80,7 @@ pub enum Command {
7880
GetKeyValue(String),
7981
GetKeysStarting(String),
8082
DeleteKeyValue(String),
83+
Close(),
8184
}
8285

8386
fn _unwrap_response(
@@ -118,7 +121,7 @@ impl DatastoreWorker {
118121

119122
fn work_loop(&mut self, method: DatastoreMethod) {
120123
// Open SQLite connection
121-
let mut conn = match method {
124+
let mut conn = match &method {
122125
DatastoreMethod::Memory() => {
123126
Connection::open_in_memory().expect("Failed to create in-memory datastore")
124127
}
@@ -150,12 +153,20 @@ impl DatastoreWorker {
150153
// Start handling and respond to requests
151154
loop {
152155
let last_commit_time: DateTime<Utc> = Utc::now();
153-
let mut transaction = conn
154-
.transaction_with_behavior(TransactionBehavior::Immediate)
155-
.unwrap();
156+
let mut tx: Transaction =
157+
match conn.transaction_with_behavior(TransactionBehavior::Immediate) {
158+
Ok(tx) => tx,
159+
Err(err) => {
160+
error!("Unable to start transaction! {:?}", err);
161+
// Wait 1s before retrying
162+
std::thread::sleep(std::time::Duration::from_millis(1000));
163+
continue;
164+
}
165+
};
166+
tx.set_drop_behavior(DropBehavior::Commit);
167+
156168
self.uncommited_events = 0;
157169
self.commit = false;
158-
transaction.set_drop_behavior(DropBehavior::Commit);
159170
loop {
160171
let (request, response_sender) = match self.responder.poll() {
161172
Ok((req, res_sender)) => (req, res_sender),
@@ -166,19 +177,28 @@ impl DatastoreWorker {
166177
break;
167178
}
168179
};
169-
let response = self.handle_request(request, &mut ds, &transaction);
170-
response_sender.respond(response);
180+
let response = self.handle_request(request, &mut ds, &tx);
181+
match response {
182+
// The NoResponse is used by commands like close(), which should
183+
// not be responded to, as the requester might have disappeared.
184+
Ok(Response::NoResponse()) => (),
185+
_ => response_sender.respond(response),
186+
}
171187
let now: DateTime<Utc> = Utc::now();
172188
let commit_interval_passed: bool = (now - last_commit_time) > Duration::seconds(15);
173-
if self.commit || commit_interval_passed || self.uncommited_events > 100 {
189+
if self.commit
190+
|| commit_interval_passed
191+
|| self.uncommited_events > 100
192+
|| self.quit
193+
{
174194
break;
175195
};
176196
}
177197
debug!(
178198
"Commiting DB! Force commit {}, {} uncommited events",
179199
self.commit, self.uncommited_events
180200
);
181-
match transaction.commit() {
201+
match tx.commit() {
182202
Ok(_) => (),
183203
Err(err) => panic!("Failed to commit datastore transaction! {}", err),
184204
}
@@ -193,32 +213,30 @@ impl DatastoreWorker {
193213
&mut self,
194214
request: Command,
195215
ds: &mut DatastoreInstance,
196-
transaction: &Transaction,
216+
tx: &Transaction,
197217
) -> Result<Response, DatastoreError> {
198218
match request {
199-
Command::CreateBucket(bucket) => match ds.create_bucket(&transaction, bucket) {
219+
Command::CreateBucket(bucket) => match ds.create_bucket(tx, bucket) {
200220
Ok(_) => {
201221
self.commit = true;
202222
Ok(Response::Empty())
203223
}
204224
Err(e) => Err(e),
205225
},
206-
Command::DeleteBucket(bucketname) => {
207-
match ds.delete_bucket(&transaction, &bucketname) {
208-
Ok(_) => {
209-
self.commit = true;
210-
Ok(Response::Empty())
211-
}
212-
Err(e) => Err(e),
226+
Command::DeleteBucket(bucketname) => match ds.delete_bucket(tx, &bucketname) {
227+
Ok(_) => {
228+
self.commit = true;
229+
Ok(Response::Empty())
213230
}
214-
}
231+
Err(e) => Err(e),
232+
},
215233
Command::GetBucket(bucketname) => match ds.get_bucket(&bucketname) {
216234
Ok(b) => Ok(Response::Bucket(b)),
217235
Err(e) => Err(e),
218236
},
219237
Command::GetBuckets() => Ok(Response::BucketMap(ds.get_buckets())),
220238
Command::InsertEvents(bucketname, events) => {
221-
match ds.insert_events(&transaction, &bucketname, events) {
239+
match ds.insert_events(tx, &bucketname, events) {
222240
Ok(events) => {
223241
self.uncommited_events += events.len();
224242
self.last_heartbeat.insert(bucketname.to_string(), None); // invalidate last_heartbeat cache
@@ -228,13 +246,7 @@ impl DatastoreWorker {
228246
}
229247
}
230248
Command::Heartbeat(bucketname, event, pulsetime) => {
231-
match ds.heartbeat(
232-
&transaction,
233-
&bucketname,
234-
event,
235-
pulsetime,
236-
&mut self.last_heartbeat,
237-
) {
249+
match ds.heartbeat(tx, &bucketname, event, pulsetime, &mut self.last_heartbeat) {
238250
Ok(e) => {
239251
self.uncommited_events += 1;
240252
Ok(Response::Event(e))
@@ -243,31 +255,25 @@ impl DatastoreWorker {
243255
}
244256
}
245257
Command::GetEvent(bucketname, event_id) => {
246-
match ds.get_event(&transaction, &bucketname, event_id) {
258+
match ds.get_event(tx, &bucketname, event_id) {
247259
Ok(el) => Ok(Response::Event(el)),
248260
Err(e) => Err(e),
249261
}
250262
}
251263
Command::GetEvents(bucketname, starttime_opt, endtime_opt, limit_opt) => {
252-
match ds.get_events(
253-
&transaction,
254-
&bucketname,
255-
starttime_opt,
256-
endtime_opt,
257-
limit_opt,
258-
) {
264+
match ds.get_events(tx, &bucketname, starttime_opt, endtime_opt, limit_opt) {
259265
Ok(el) => Ok(Response::EventList(el)),
260266
Err(e) => Err(e),
261267
}
262268
}
263269
Command::GetEventCount(bucketname, starttime_opt, endtime_opt) => {
264-
match ds.get_event_count(&transaction, &bucketname, starttime_opt, endtime_opt) {
270+
match ds.get_event_count(tx, &bucketname, starttime_opt, endtime_opt) {
265271
Ok(n) => Ok(Response::Count(n)),
266272
Err(e) => Err(e),
267273
}
268274
}
269275
Command::DeleteEventsById(bucketname, event_ids) => {
270-
match ds.delete_events_by_id(&transaction, &bucketname, event_ids) {
276+
match ds.delete_events_by_id(tx, &bucketname, event_ids) {
271277
Ok(()) => Ok(Response::Empty()),
272278
Err(e) => Err(e),
273279
}
@@ -276,26 +282,26 @@ impl DatastoreWorker {
276282
self.commit = true;
277283
Ok(Response::Empty())
278284
}
279-
Command::InsertKeyValue(key, data) => {
280-
match ds.insert_key_value(&transaction, &key, &data) {
281-
Ok(()) => Ok(Response::Empty()),
282-
Err(e) => Err(e),
283-
}
284-
}
285-
Command::GetKeyValue(key) => match ds.get_key_value(&transaction, &key) {
285+
Command::InsertKeyValue(key, data) => match ds.insert_key_value(tx, &key, &data) {
286+
Ok(()) => Ok(Response::Empty()),
287+
Err(e) => Err(e),
288+
},
289+
Command::GetKeyValue(key) => match ds.get_key_value(tx, &key) {
286290
Ok(result) => Ok(Response::KeyValue(result)),
287291
Err(e) => Err(e),
288292
},
289-
Command::GetKeysStarting(pattern) => {
290-
match ds.get_keys_starting(&transaction, &pattern) {
291-
Ok(result) => Ok(Response::StringVec(result)),
292-
Err(e) => Err(e),
293-
}
294-
}
295-
Command::DeleteKeyValue(key) => match ds.delete_key_value(&transaction, &key) {
293+
Command::GetKeysStarting(pattern) => match ds.get_keys_starting(tx, &pattern) {
294+
Ok(result) => Ok(Response::StringVec(result)),
295+
Err(e) => Err(e),
296+
},
297+
Command::DeleteKeyValue(key) => match ds.delete_key_value(tx, &key) {
296298
Ok(()) => Ok(Response::Empty()),
297299
Err(e) => Err(e),
298300
},
301+
Command::Close() => {
302+
self.quit = true;
303+
Ok(Response::NoResponse())
304+
}
299305
}
300306
}
301307
}
@@ -516,4 +522,10 @@ impl Datastore {
516522
Err(e) => Err(e),
517523
}
518524
}
525+
526+
// TODO: Should this block until worker has stopped?
527+
pub fn close(&self) {
528+
info!("Sending close request to database");
529+
self.requester.request(Command::Close()).unwrap();
530+
}
519531
}

aw-server/src/main.rs

+7-2
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ struct Opts {
2727
#[clap(long)]
2828
port: Option<String>,
2929
/// Path to database override
30+
/// Also implies --no-legacy-import if no db found
3031
#[clap(long)]
3132
dbpath: Option<String>,
3233
/// Path to webui override
@@ -69,7 +70,7 @@ async fn main() -> Result<(), rocket::Error> {
6970
}
7071

7172
// Set db path if overridden
72-
let db_path: String = if let Some(dbpath) = opts.dbpath {
73+
let db_path: String = if let Some(dbpath) = opts.dbpath.clone() {
7374
dbpath
7475
} else {
7576
dirs::db_path(testing)
@@ -86,7 +87,11 @@ async fn main() -> Result<(), rocket::Error> {
8687
};
8788
info!("Using aw-webui assets at path {:?}", asset_path);
8889

89-
let legacy_import = !opts.no_legacy_import;
90+
// Only use legacy import if opts.dbpath is not set
91+
let legacy_import = !opts.no_legacy_import && opts.dbpath.is_none();
92+
if opts.dbpath.is_some() {
93+
info!("Since custom dbpath is set, --no-legacy-import is implied");
94+
}
9095

9196
let device_id: String = if let Some(id) = opts.device_id {
9297
id

aw-sync/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ name = "aw_sync"
99
path = "src/lib.rs"
1010

1111
[[bin]]
12-
name = "aw-sync-rust"
12+
name = "aw-sync"
1313
path = "src/main.rs"
1414

1515
[dependencies]

0 commit comments

Comments
 (0)