From 89b8cece94ca73bd09bc684d4391b1faf816c2b7 Mon Sep 17 00:00:00 2001 From: Brayo Date: Thu, 26 Mar 2026 13:40:42 +0300 Subject: [PATCH 1/2] feat(android): migrate unknown hostname --- aw-datastore/src/datastore.rs | 36 +++++++++++++++++++++++++++++++++++ aw-datastore/src/worker.rs | 28 +++++++++++++++++++++++++++ aw-server/src/android/mod.rs | 18 ++++++++++++++++++ 3 files changed, 82 insertions(+) diff --git a/aw-datastore/src/datastore.rs b/aw-datastore/src/datastore.rs index 253ae3d6..c176becd 100644 --- a/aw-datastore/src/datastore.rs +++ b/aw-datastore/src/datastore.rs @@ -966,4 +966,40 @@ impl DatastoreInstance { }, } } + + /// Migrates all buckets whose hostname is "unknown" or "Unknown" to `new_hostname`. + /// Events are left untouched; only the bucket metadata is updated. + /// Returns the number of buckets that were updated. + pub fn migrate_hostname( + &mut self, + conn: &Connection, + new_hostname: &str, + ) -> Result { + info!( + "Migrating hostname from 'unknown'/'Unknown' to '{}'", + new_hostname + ); + + let updated = match conn.execute( + "UPDATE buckets SET hostname = ?1 WHERE hostname = 'unknown' OR hostname = 'Unknown'", + [new_hostname], + ) { + Ok(n) => n, + Err(err) => { + return Err(DatastoreError::InternalError(format!( + "Failed to migrate hostname: {err}" + ))) + } + }; + + if updated > 0 { + info!("Migrated hostname for {} bucket(s)", updated); + // Refresh the in-memory cache so callers see the new hostnames immediately. + self.get_stored_buckets(conn)?; + } else { + info!("No buckets with hostname 'unknown'/'Unknown' found; nothing to migrate"); + } + + Ok(updated) + } } diff --git a/aw-datastore/src/worker.rs b/aw-datastore/src/worker.rs index b116a1f3..b457809e 100644 --- a/aw-datastore/src/worker.rs +++ b/aw-datastore/src/worker.rs @@ -77,6 +77,7 @@ pub enum Command { GetKeyValue(String), SetKeyValue(String, String), DeleteKeyValue(String), + MigrateHostname(String), Close(), } @@ -305,6 +306,17 @@ impl DatastoreWorker { Ok(()) => Ok(Response::Empty()), Err(e) => Err(e), }, + Command::MigrateHostname(new_hostname) => { + match ds.migrate_hostname(tx, &new_hostname) { + Ok(count) => { + if count > 0 { + self.commit = true; + } + Ok(Response::Count(count as i64)) + } + Err(e) => Err(e), + } + } Command::Close() => { self.quit = true; Ok(Response::Empty()) @@ -529,6 +541,22 @@ impl Datastore { _unwrap_response(receiver) } + /// Migrates all buckets whose hostname is "unknown" or "Unknown" to `new_hostname`. + /// Returns the number of buckets updated. + pub fn migrate_hostname(&self, new_hostname: &str) -> Result { + let cmd = Command::MigrateHostname(new_hostname.to_string()); + let receiver = self.requester.request(cmd).unwrap(); + match receiver.collect().unwrap() { + Ok(r) => match r { + Response::Count(n) => Ok(n as usize), + _ => Err(DatastoreError::InternalError( + "Unexpected response to MigrateHostname command".to_string(), + )), + }, + Err(e) => Err(e), + } + } + // Should block until worker has stopped pub fn close(&self) { info!("Sending close request to database"); diff --git a/aw-server/src/android/mod.rs b/aw-server/src/android/mod.rs index 3c32a13e..44791fd0 100644 --- a/aw-server/src/android/mod.rs +++ b/aw-server/src/android/mod.rs @@ -250,6 +250,24 @@ pub mod android { } } + #[no_mangle] + pub unsafe extern "C" fn Java_net_activitywatch_android_RustInterface_migrateHostname( + env: JNIEnv, + _: JClass, + hostname: JString, + ) -> jstring { + let hostname = jstring_to_string(&env, hostname); + if hostname.is_empty() { + return create_error_object(&env, "hostname must not be empty".to_string()); + } + match openDatastore().migrate_hostname(&hostname) { + Ok(count) => { + string_to_jstring(&env, format!("Migrated hostname for {} bucket(s)", count)) + } + Err(e) => create_error_object(&env, format!("Failed to migrate hostname: {:?}", e)), + } + } + #[no_mangle] pub unsafe extern "C" fn Java_net_activitywatch_android_RustInterface_query( env: JNIEnv, From 4309887f584ebaa2737c608fa44ce9f9b4f446f9 Mon Sep 17 00:00:00 2001 From: Brayo Date: Sun, 29 Mar 2026 17:57:45 +0300 Subject: [PATCH 2/2] feat(android): migrate legacy bucket name --- aw-datastore/src/datastore.rs | 38 +++++++++++++++++++++++++++++++++++ aw-datastore/src/worker.rs | 15 ++++++++++++++ aw-server/src/android/mod.rs | 14 +++++++++++++ 3 files changed, 67 insertions(+) diff --git a/aw-datastore/src/datastore.rs b/aw-datastore/src/datastore.rs index c176becd..473e50bb 100644 --- a/aw-datastore/src/datastore.rs +++ b/aw-datastore/src/datastore.rs @@ -967,6 +967,44 @@ impl DatastoreInstance { } } + /// Renames a bucket from `old_id` to `new_id`. + /// Events are left untouched because they reference the integer row ID, not the name. + /// Returns `NoSuchBucket` if `old_id` does not exist, or `BucketAlreadyExists` if + /// `new_id` is already taken. + pub fn rename_bucket( + &mut self, + conn: &Connection, + old_id: &str, + new_id: &str, + ) -> Result<(), DatastoreError> { + if !self.buckets_cache.contains_key(old_id) { + return Err(DatastoreError::NoSuchBucket(old_id.to_string())); + } + if self.buckets_cache.contains_key(new_id) { + return Err(DatastoreError::BucketAlreadyExists(new_id.to_string())); + } + + match conn.execute( + "UPDATE buckets SET name = ?1 WHERE name = ?2", + [new_id, old_id], + ) { + Ok(0) => Err(DatastoreError::NoSuchBucket(old_id.to_string())), + Ok(_) => { + info!("Renamed bucket '{}' to '{}'", old_id, new_id); + // Update the in-memory cache: remove the old entry and re-insert under the new id. + if let Some(mut bucket) = self.buckets_cache.remove(old_id) { + bucket.id = new_id.to_string(); + self.buckets_cache.insert(new_id.to_string(), bucket); + } + Ok(()) + } + Err(err) => Err(DatastoreError::InternalError(format!( + "Failed to rename bucket '{}' to '{}': {err}", + old_id, new_id + ))), + } + } + /// Migrates all buckets whose hostname is "unknown" or "Unknown" to `new_hostname`. /// Events are left untouched; only the bucket metadata is updated. /// Returns the number of buckets that were updated. diff --git a/aw-datastore/src/worker.rs b/aw-datastore/src/worker.rs index b457809e..978baeb1 100644 --- a/aw-datastore/src/worker.rs +++ b/aw-datastore/src/worker.rs @@ -77,6 +77,7 @@ pub enum Command { GetKeyValue(String), SetKeyValue(String, String), DeleteKeyValue(String), + RenameBucket(String, String), MigrateHostname(String), Close(), } @@ -306,6 +307,13 @@ impl DatastoreWorker { Ok(()) => Ok(Response::Empty()), Err(e) => Err(e), }, + Command::RenameBucket(old_id, new_id) => match ds.rename_bucket(tx, &old_id, &new_id) { + Ok(()) => { + self.commit = true; + Ok(Response::Empty()) + } + Err(e) => Err(e), + }, Command::MigrateHostname(new_hostname) => { match ds.migrate_hostname(tx, &new_hostname) { Ok(count) => { @@ -541,6 +549,13 @@ impl Datastore { _unwrap_response(receiver) } + /// Renames a bucket from `old_id` to `new_id`. + pub fn rename_bucket(&self, old_id: &str, new_id: &str) -> Result<(), DatastoreError> { + let cmd = Command::RenameBucket(old_id.to_string(), new_id.to_string()); + let receiver = self.requester.request(cmd).unwrap(); + _unwrap_response(receiver) + } + /// Migrates all buckets whose hostname is "unknown" or "Unknown" to `new_hostname`. /// Returns the number of buckets updated. pub fn migrate_hostname(&self, new_hostname: &str) -> Result { diff --git a/aw-server/src/android/mod.rs b/aw-server/src/android/mod.rs index 44791fd0..54776fbc 100644 --- a/aw-server/src/android/mod.rs +++ b/aw-server/src/android/mod.rs @@ -268,6 +268,20 @@ pub mod android { } } + #[no_mangle] + pub unsafe extern "C" fn Java_net_activitywatch_android_RustInterface_migrateAndroidBucketName( + env: JNIEnv, + _: JClass, + ) -> jstring { + match openDatastore().rename_bucket("aw-android-test", "aw-android") { + Ok(()) => string_to_jstring( + &env, + "Renamed bucket 'aw-android-test' to 'aw-android'".to_string(), + ), + Err(e) => create_error_object(&env, format!("Failed to rename bucket: {:?}", e)), + } + } + #[no_mangle] pub unsafe extern "C" fn Java_net_activitywatch_android_RustInterface_query( env: JNIEnv,