Skip to content

Commit

Permalink
Janky fix for Android database migration loading
Browse files Browse the repository at this point in the history
  • Loading branch information
erskingardner committed Jan 30, 2025
1 parent e130868 commit 5bda026
Show file tree
Hide file tree
Showing 10 changed files with 243 additions and 17 deletions.
2 changes: 1 addition & 1 deletion src-tauri/gen/android/.idea/deploymentTargetSelector.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src-tauri/gen/android/.idea/gradle.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion src-tauri/gen/android/.idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
-- Accounts table with JSON fields for complex objects
CREATE TABLE accounts (
pubkey TEXT PRIMARY KEY,
metadata TEXT NOT NULL, -- JSON string for nostr Metadata
settings TEXT NOT NULL, -- JSON string for AccountSettings
onboarding TEXT NOT NULL, -- JSON string for AccountOnboarding
last_used INTEGER NOT NULL,
last_synced INTEGER NOT NULL,
active BOOLEAN NOT NULL DEFAULT FALSE
);

-- Create an index for faster lookups
CREATE INDEX idx_accounts_active ON accounts(active);

-- Create a unique partial index that only allows one TRUE value
CREATE UNIQUE INDEX idx_accounts_single_active ON accounts(active) WHERE active = TRUE;

-- Create a trigger to ensure only one active account
CREATE TRIGGER ensure_single_active_account
BEFORE UPDATE ON accounts
WHEN NEW.active = TRUE
BEGIN
UPDATE accounts SET active = FALSE WHERE active = TRUE AND pubkey != NEW.pubkey;
END;

-- Account-specific relays table
CREATE TABLE account_relays (
id INTEGER PRIMARY KEY AUTOINCREMENT,
url TEXT NOT NULL,
relay_type TEXT NOT NULL,
account_pubkey TEXT NOT NULL,
FOREIGN KEY (account_pubkey) REFERENCES accounts(pubkey) ON DELETE CASCADE
);

CREATE INDEX idx_account_relays_account ON account_relays(account_pubkey, relay_type);

-- Group-specific relays table (separate table)
CREATE TABLE group_relays (
id INTEGER PRIMARY KEY AUTOINCREMENT,
url TEXT NOT NULL,
relay_type TEXT NOT NULL,
account_pubkey TEXT NOT NULL,
group_id BLOB NOT NULL,
FOREIGN KEY (group_id, account_pubkey) REFERENCES groups(mls_group_id, account_pubkey) ON DELETE CASCADE
);

CREATE INDEX idx_group_relays_group ON group_relays(group_id, relay_type);
CREATE INDEX idx_group_relays_group_account ON group_relays(group_id, account_pubkey);

-- Groups table matching the Group struct
CREATE TABLE groups (
mls_group_id BLOB,
account_pubkey TEXT NOT NULL,
nostr_group_id TEXT NOT NULL,
name TEXT,
description TEXT,
admin_pubkeys TEXT NOT NULL, -- JSON array of strings
last_message_id TEXT,
last_message_at INTEGER,
group_type TEXT NOT NULL CHECK (group_type IN ('DirectMessage', 'Group')),
epoch INTEGER NOT NULL,
state TEXT NOT NULL CHECK (state IN ('Active', 'Inactive')),
FOREIGN KEY (account_pubkey) REFERENCES accounts(pubkey) ON DELETE CASCADE,
PRIMARY KEY (mls_group_id, account_pubkey)
);

CREATE INDEX idx_groups_mls_group_id ON groups(mls_group_id);
CREATE INDEX idx_groups_account ON groups(account_pubkey);
CREATE INDEX idx_groups_nostr ON groups(nostr_group_id);
CREATE INDEX idx_groups_mls_group_id_account ON groups(mls_group_id, account_pubkey);

-- Invites table matching the Invite struct
CREATE TABLE invites (
event_id TEXT PRIMARY KEY, -- the event_id of the 444 unsigned invite event
account_pubkey TEXT NOT NULL,
event TEXT NOT NULL, -- JSON string for UnsignedEvent
mls_group_id BLOB NOT NULL,
nostr_group_id TEXT NOT NULL,
group_name TEXT NOT NULL,
group_description TEXT NOT NULL,
group_admin_pubkeys TEXT NOT NULL, -- JSON array of strings
group_relays TEXT NOT NULL, -- JSON array of strings
inviter TEXT NOT NULL,
member_count INTEGER NOT NULL,
state TEXT NOT NULL,
outer_event_id TEXT, -- the event_id of the 1059 event that contained the invite

FOREIGN KEY (account_pubkey) REFERENCES accounts(pubkey) ON DELETE CASCADE
);

CREATE INDEX idx_invites_mls_group ON invites(mls_group_id);
CREATE INDEX idx_invites_state ON invites(state);
CREATE INDEX idx_invites_account ON invites(account_pubkey);
CREATE INDEX idx_invites_outer_event_id ON invites(outer_event_id);
CREATE INDEX idx_invites_event_id ON invites(event_id);

CREATE TABLE processed_invites (
event_id TEXT PRIMARY KEY, -- This is the outer event id of the 1059 gift wrap event
invite_event_id TEXT, -- This is the event id of the 444 invite event
account_pubkey TEXT NOT NULL, -- This is the pubkey of the account that processed the invite
processed_at INTEGER NOT NULL, -- This is the timestamp of when the invite was processed
state TEXT NOT NULL, -- This is the state of the invite processing
failure_reason TEXT, -- This is the reason the invite failed to process

FOREIGN KEY (account_pubkey) REFERENCES accounts(pubkey) ON DELETE CASCADE
);

CREATE INDEX idx_processed_invites_invite_event_id ON processed_invites(invite_event_id);

-- Messages table with full-text search
CREATE TABLE messages (
id INTEGER PRIMARY KEY AUTOINCREMENT,
event_id TEXT NOT NULL,
mls_group_id BLOB NOT NULL,
account_pubkey TEXT NOT NULL,
author_pubkey TEXT NOT NULL,
created_at INTEGER NOT NULL,
content TEXT NOT NULL,
tags TEXT, -- JSON array of nostr tags
event TEXT NOT NULL, -- JSON string for UnsignedEvent
outer_event_id TEXT NOT NULL, -- the event_id of the 445 event
FOREIGN KEY (mls_group_id, account_pubkey) REFERENCES groups(mls_group_id, account_pubkey) ON DELETE CASCADE,
FOREIGN KEY (account_pubkey) REFERENCES accounts(pubkey) ON DELETE CASCADE,
UNIQUE(event_id, account_pubkey) -- Ensure event_id is unique per account
);

CREATE INDEX idx_messages_group_time ON messages(mls_group_id, created_at);
CREATE INDEX idx_messages_account_time ON messages(account_pubkey, created_at);
CREATE INDEX idx_messages_author_time ON messages(author_pubkey, created_at);
CREATE INDEX idx_messages_outer_event_id ON messages(outer_event_id);
CREATE INDEX idx_messages_event_id ON messages(event_id);
CREATE INDEX idx_messages_event_id_account ON messages(event_id, account_pubkey);

-- Update processed_messages table to reference the new messages table structure
CREATE TABLE processed_messages (
id INTEGER PRIMARY KEY AUTOINCREMENT,
event_id TEXT NOT NULL, -- This is the outer event id of the 445 event
message_event_id TEXT NOT NULL, -- This is the inner UnsignedEvent's id. This is the id of the events stored in the messages table.
account_pubkey TEXT NOT NULL, -- This is the pubkey of the account that processed the message
processed_at INTEGER NOT NULL, -- This is the timestamp of when the message was processed
state TEXT NOT NULL, -- This is the state of the message processing
failure_reason TEXT, -- This is the reason the message failed to process
UNIQUE(event_id, account_pubkey),
FOREIGN KEY (account_pubkey) REFERENCES accounts(pubkey) ON DELETE CASCADE
);

CREATE INDEX idx_processed_messages_message_event_id ON processed_messages(message_event_id);
CREATE INDEX idx_processed_messages_event_id_account ON processed_messages(event_id, account_pubkey);

-- Full-text search for messages
CREATE VIRTUAL TABLE messages_fts USING fts5(
content,
id UNINDEXED, -- Change to reference the new id column
content='messages'
);

-- Update FTS triggers to use id instead of event_id
CREATE TRIGGER messages_ai AFTER INSERT ON messages BEGIN
INSERT INTO messages_fts(content, id) VALUES (new.content, new.id);
END;

CREATE TRIGGER messages_ad AFTER DELETE ON messages BEGIN
INSERT INTO messages_fts(messages_fts, content, id) VALUES('delete', old.content, old.id);
END;

CREATE TRIGGER messages_au AFTER UPDATE ON messages BEGIN
INSERT INTO messages_fts(messages_fts, content, id) VALUES('delete', old.content, old.id);
INSERT INTO messages_fts(content, id) VALUES (new.content, new.id);
END;
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,14 @@
android:viewportHeight="1080"
android:width="1080dp"
android:height="1080dp">
<path
android:pathData="M241 770V310H395V450.901L463 310H617V450.901L685 310H839V770H685V629.099L617 770H463V629.099L395 770H241ZM371 334H265V720.27L371 500.631V334ZM709 746H815V359.73L709 579.369V746ZM601.934 746L800.769 334H700.066L501.231 746H601.934ZM578.769 334H478.066L279.231 746H379.934L578.769 334ZM593 359.73L487 579.369V720.27L593 500.631V359.73Z"
android:fillType="evenOdd"
android:fillColor="#1F1F1F" />
</vector>
<group
android:scaleX="0.8"
android:scaleY="0.8"
android:translateX="108"
android:translateY="108">
<path
android:pathData="M241 770V310H395V450.901L463 310H617V450.901L685 310H839V770H685V629.099L617 770H463V629.099L395 770H241ZM371 334H265V720.27L371 500.631V334ZM709 746H815V359.73L709 579.369V746ZM601.934 746L800.769 334H700.066L501.231 746H601.934ZM578.769 334H478.066L279.231 746H379.934L578.769 334ZM593 359.73L487 579.369V720.27L593 500.631V359.73Z"
android:fillType="evenOdd"
android:fillColor="#1F1F1F" />
</group>
</vector>
2 changes: 1 addition & 1 deletion src-tauri/gen/android/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ buildscript {
mavenCentral()
}
dependencies {
classpath("com.android.tools.build:gradle:8.5.1")
classpath("com.android.tools.build:gradle:8.8.0")
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.25")
}
}
Expand Down
2 changes: 1 addition & 1 deletion src-tauri/gen/android/buildSrc/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,6 @@ repositories {

dependencies {
compileOnly(gradleApi())
implementation("com.android.tools.build:gradle:8.5.1")
implementation("com.android.tools.build:gradle:8.8.0")
}

Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#Tue May 10 19:22:52 CST 2022
distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
63 changes: 56 additions & 7 deletions src-tauri/src/database.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,21 @@
use sqlx::sqlite::SqlitePoolOptions;
use sqlx::{migrate::MigrateDatabase, Sqlite, SqlitePool};
use std::fs;
use std::path::PathBuf;
use std::time::Duration;
use tauri::{path::BaseDirectory, AppHandle, Manager};
use thiserror::Error;

const MIGRATION_FILES: &[(&str, &[u8])] = &[
(
"0001_initial.sql",
include_bytes!("../db_migrations/0001_initial.sql"),
),
// Add new migrations here in order, for example:
// ("0002_something.sql", include_bytes!("../db_migrations/0002_something.sql")),
// ("0003_another.sql", include_bytes!("../db_migrations/0003_another.sql")),
];

#[derive(Error, Debug)]
pub enum DatabaseError {
#[error("File system error: {0}")]
Expand Down Expand Up @@ -82,14 +93,52 @@ impl Database {

// Run migrations
tracing::debug!("Running migrations...");
let migrations_path = app_handle
.path()
.resolve("db_migrations", BaseDirectory::Resource)?;

sqlx::migrate::Migrator::new(migrations_path)
.await?
.run(&pool)
.await?;
let migrations_path = if cfg!(target_os = "android") {
// On Android, we need to copy migrations to a temporary directory
let temp_dir = app_handle.path().app_data_dir()?.join("temp_migrations");
if temp_dir.exists() {
fs::remove_dir_all(&temp_dir)?;
}
fs::create_dir_all(&temp_dir)?;

// Copy all migration files from the embedded assets
for (filename, content) in MIGRATION_FILES {
tracing::debug!("Writing migration file: {}", filename);
fs::write(temp_dir.join(filename), content)?;
}

temp_dir
} else {
app_handle
.path()
.resolve("db_migrations", BaseDirectory::Resource)?
};

tracing::debug!("Migrations path: {:?}", migrations_path);
if !migrations_path.exists() {
tracing::error!("Migrations directory not found at {:?}", migrations_path);
return Err(DatabaseError::FileSystem(std::io::Error::new(
std::io::ErrorKind::NotFound,
format!("Migrations directory not found at {:?}", migrations_path),
)));
}

match sqlx::migrate::Migrator::new(migrations_path).await {
Ok(migrator) => {
migrator.run(&pool).await?;
// Clean up temp directory on Android after successful migration
if cfg!(target_os = "android") {
if let Ok(temp_dir) = app_handle.path().app_data_dir() {
let _ = fs::remove_dir_all(temp_dir.join("temp_migrations"));
}
}
}
Err(e) => {
tracing::error!("Failed to create migrator: {:?}", e);
return Err(DatabaseError::Migrate(e));
}
}

Ok(Self {
pool,
Expand Down
2 changes: 2 additions & 0 deletions src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,14 @@ pub fn run() {
} else {
PathBuf::from(format!("{}/release", data_dir.to_string_lossy()))
};
std::fs::create_dir_all(&formatted_data_dir)?;

let formatted_logs_dir = if cfg!(dev) {
PathBuf::from(format!("{}/dev", logs_dir.to_string_lossy()))
} else {
PathBuf::from(format!("{}/release", logs_dir.to_string_lossy()))
};
std::fs::create_dir_all(&formatted_logs_dir)?;

setup_logging(formatted_logs_dir.clone())?;

Expand Down

0 comments on commit 5bda026

Please sign in to comment.