Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 74 additions & 0 deletions aw-datastore/src/datastore.rs
Original file line number Diff line number Diff line change
Expand Up @@ -966,4 +966,78 @@ 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.
pub fn migrate_hostname(
&mut self,
conn: &Connection,
new_hostname: &str,
) -> Result<usize, DatastoreError> {
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)
}
}
43 changes: 43 additions & 0 deletions aw-datastore/src/worker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ pub enum Command {
GetKeyValue(String),
SetKeyValue(String, String),
DeleteKeyValue(String),
RenameBucket(String, String),
MigrateHostname(String),
Close(),
}

Expand Down Expand Up @@ -305,6 +307,24 @@ 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) => {
if count > 0 {
self.commit = true;
}
Ok(Response::Count(count as i64))
}
Err(e) => Err(e),
}
}
Command::Close() => {
self.quit = true;
Ok(Response::Empty())
Expand Down Expand Up @@ -529,6 +549,29 @@ 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<usize, DatastoreError> {
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");
Expand Down
32 changes: 32 additions & 0 deletions aw-server/src/android/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,38 @@ 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_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,
Expand Down
Loading