Skip to content
Merged
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
3 changes: 1 addition & 2 deletions ios/Gekidou/Sources/Gekidou/Storage/Database+Channels.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,7 @@ extension Database {
INNER JOIN MyChannel my ON c.id=my.id AND c.delete_at = 0 \
INNER JOIN Team t ON c.team_id=t.id
"""
let stmt = try db.prepare(stmtString)
let count = (try stmt.scalar() as? Int64) ?? 0
let count = try db.scalar(stmtString) as? Int64 ?? 0
return count > 0
} catch {
return false
Expand Down
4 changes: 2 additions & 2 deletions ios/Gekidou/Sources/Gekidou/Storage/Database+Mentions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ extension Database {
INNER JOIN Channel c INDEXED BY sqlite_autoindex_Channel_1 ON c.id=my.id \
WHERE c.delete_at = 0 AND mys.notify_props NOT LIKE '%"mark_unread":"mention"%'
"""
let mentions = try? db.prepare(stmtString).scalar() as? Double
let mentions = try? db.scalar(stmtString) as? Double
return Int(mentions ?? 0)
}

Expand All @@ -35,7 +35,7 @@ extension Database {
INNER JOIN MyChannelSettings mys ON mys.id=c.id \
WHERE c.delete_at = 0 AND mys.notify_props NOT LIKE '%"mark_unread":"mention"%'
"""
let mentions = try? db.prepare(stmtString).scalar() as? Double
let mentions = try? db.scalar(stmtString) as? Double
return Int(mentions ?? 0)
}

Expand Down
2 changes: 1 addition & 1 deletion ios/Gekidou/Sources/Gekidou/Storage/Database+System.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import SQLite

extension Database {
public func getDeviceToken() -> String? {
if let db = try? Connection(DEFAULT_DB_PATH) {
if let db = try? openConnection(DEFAULT_DB_PATH) {
let idCol = Expression<String>("id")
let valueCol = Expression<String>("value")
let query = globalTable.select(valueCol).filter(idCol == "deviceToken")
Expand Down
22 changes: 10 additions & 12 deletions ios/Gekidou/Sources/Gekidou/Storage/Database+Team.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,21 +56,19 @@ extension Database {
}

public func queryAllMyTeamIds(_ serverUrl: String) -> [String]? {
if let db = try? getDatabaseForServer(serverUrl) {
do {
let db = try getDatabaseForServer(serverUrl)
let idCol = Expression<String>("id")
if let myTeams = try? db.prepare(myTeamTable.select(idCol)) {
return myTeams.compactMap { row in
do {
return try row.get(idCol)
} catch {
GekidouLogger.shared.log(.error, "Gekidou Database: Failed to get team ID from row for server %{public}@ - %{public}@", serverUrl, String(describing: error))
return nil
}
}
let iterator = try db.prepareRowIterator(myTeamTable.select(idCol))
var teamIds = [String]()
while let row = try iterator.failableNext() {
teamIds.append(try row.get(idCol))
}
return teamIds.isEmpty ? nil : teamIds
} catch {
GekidouLogger.shared.log(.error, "Gekidou Database: Failed to get team IDs for server %{public}@ - %{public}@", serverUrl, String(describing: error))
return nil
}

return nil
}

public func insertTeam(_ db: Connection, _ team: Team) throws {
Expand Down
7 changes: 3 additions & 4 deletions ios/Gekidou/Sources/Gekidou/Storage/Database+Thread.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,9 @@ extension Database {
var teamIds = [String]()
if teamId.isEmpty {
let idCol = Expression<String>("id")
if let myTeams = try? db.prepare(myTeamTable.select(idCol)) {
if let ids = try? myTeams.map({ try $0.get(idCol) }) {
teamIds.append(contentsOf: ids)
}
let iterator = try db.prepareRowIterator(myTeamTable.select(idCol))
while let row = try iterator.failableNext() {
teamIds.append(try row.get(idCol))
}
} else {
teamIds.append(teamId)
Expand Down
35 changes: 21 additions & 14 deletions ios/Gekidou/Sources/Gekidou/Storage/Database+Users.swift
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,11 @@ extension Database {
let idCol = Expression<String>("id")
let query = userTable.where(idCol == userId)

let results: [User] = try db.prepare(query).map {try $0.decode()}
let iterator = try db.prepareRowIterator(query)
var results = [User]()
while let row = try iterator.failableNext() {
results.append(try row.decode())
}
updateAt = results.first?.lastPictureUpdate

} catch {
Expand All @@ -80,32 +84,35 @@ extension Database {

public func queryUsers(byIds userIds: Set<String>, forServerUrl serverUrl: String) -> Set<String> {
var result: Set<String> = Set()
if let db = try? getDatabaseForServer(serverUrl) {

do {
let db = try getDatabaseForServer(serverUrl)
let idCol = Expression<String>("id")
if let users = try? db.prepare(
let iterator = try db.prepareRowIterator(
userTable.select(idCol).filter(userIds.contains(idCol))
) {
for user in users {
result.insert(user[idCol])
}
)
while let user = try iterator.failableNext() {
result.insert(try user.get(idCol))
}
} catch {
GekidouLogger.shared.log(.error, "Gekidou Database: Failed to query users by IDs for server %{public}@ - %{public}@", serverUrl, String(describing: error))
}

return result
}

public func queryUsers(byUsernames usernames: Set<String>, forServerUrl serverUrl: String) -> Set<String> {
var result: Set<String> = Set()
if let db = try? getDatabaseForServer(serverUrl) {
do {
let db = try getDatabaseForServer(serverUrl)
let usernameCol = Expression<String>("username")
if let users = try? db.prepare(
let iterator = try db.prepareRowIterator(
userTable.select(usernameCol).filter(usernames.contains(usernameCol))
) {
for user in users {
result.insert(user[usernameCol])
}
)
while let user = try iterator.failableNext() {
result.insert(try user.get(usernameCol))
}
} catch {
GekidouLogger.shared.log(.error, "Gekidou Database: Failed to query users by usernames for server %{public}@ - %{public}@", serverUrl, String(describing: error))
}

return result
Expand Down
48 changes: 34 additions & 14 deletions ios/Gekidou/Sources/Gekidou/Storage/Database.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,14 @@ enum DatabaseError: Error {
case InsertError(_ statement: String)
}

/// Default busy timeout (in seconds) for SQLite connections.
/// When the main app and NotificationService extension access the shared
/// database concurrently, SQLite may return SQLITE_BUSY ("database is locked").
/// Setting a busy timeout tells SQLite to retry internally for up to this many
/// seconds before returning an error, preventing transient lock contention from
/// becoming a fatal crash (via SQLite.swift's `try!` in FailableIterator.next).
private let defaultBusyTimeout: Double = 5.0

extension DatabaseError: LocalizedError {
var errorDescription: String? {
switch self {
Expand Down Expand Up @@ -91,6 +99,15 @@ public class Database: NSObject {
super.init()
}

/// Creates a new SQLite Connection with WAL journal mode and a busy timeout,
/// so concurrent readers/writers (main app + notification extension) don't
/// immediately fail with SQLITE_BUSY.
internal func openConnection(_ path: String, readonly: Bool = false) throws -> Connection {
let db = try Connection(path, readonly: readonly)
db.busyTimeout = defaultBusyTimeout
return db
}

@objc public func getOnlyServerUrlObjc() -> String {
do {
return try getOnlyServerUrl()
Expand All @@ -106,14 +123,15 @@ public class Database: NSObject {

public func getOnlyServerUrl() throws -> String {
do {
let db = try Connection(DEFAULT_DB_PATH)
let db = try openConnection(DEFAULT_DB_PATH)
let url = Expression<String>("url")
let identifier = Expression<String>("identifier")
let lastActiveAt = Expression<Int64>("last_active_at")
let query = serversTable.select(url).filter(lastActiveAt > 0 && identifier != "")

var serverUrl: String?
for result in try db.prepare(query) {
let iterator = try db.prepareRowIterator(query)
while let result = try iterator.failableNext() {
if (serverUrl != nil) {
throw DatabaseError.MultipleServers
}
Expand All @@ -134,7 +152,7 @@ public class Database: NSObject {

public func getServerUrlForServer(_ id: String) throws -> String {
do {
let db = try Connection(DEFAULT_DB_PATH)
let db = try openConnection(DEFAULT_DB_PATH)
let url = Expression<String>("url")
let identifier = Expression<String>("identifier")
let query = serversTable.select(url).filter(identifier == id)
Expand All @@ -152,14 +170,15 @@ public class Database: NSObject {
}

public func getAllActiveDatabases<T: Codable>() -> [T] {
guard let db = try? Connection(DEFAULT_DB_PATH) else {return []}
guard let db = try? openConnection(DEFAULT_DB_PATH) else {return []}
let lastActiveAt = Expression<Int64>("last_active_at")
let identifier = Expression<String>("identifier")
let query = serversTable.filter(lastActiveAt > 0 && identifier != "").order(lastActiveAt.desc)
do {
let rows = try db.prepare(query)
let servers: [T] = try rows.map { row in
return try row.decode()
let iterator = try db.prepareRowIterator(query)
var servers = [T]()
while let row = try iterator.failableNext() {
servers.append(try row.decode())
}

return servers
Expand All @@ -169,15 +188,16 @@ public class Database: NSObject {
}

public func getAllActiveServerUrls() -> [String] {
guard let db = try? Connection(DEFAULT_DB_PATH) else {return []}
guard let db = try? openConnection(DEFAULT_DB_PATH) else {return []}
let lastActiveAt = Expression<Int64>("last_active_at")
let identifier = Expression<String>("identifier")
let url = Expression<String>("url")
let query = serversTable.filter(lastActiveAt > 0 && identifier != "").order(lastActiveAt.desc)
do {
let rows = try db.prepare(query)
let servers: [String] = try rows.map { row in
return try row.get(url)
let iterator = try db.prepareRowIterator(query)
var servers = [String]()
while let row = try iterator.failableNext() {
servers.append(try row.get(url))
}

return servers
Expand All @@ -187,7 +207,7 @@ public class Database: NSObject {
}

public func getCurrentServerDatabase<T: Codable>() -> T? {
guard let db = try? Connection(DEFAULT_DB_PATH) else {return nil}
guard let db = try? openConnection(DEFAULT_DB_PATH) else {return nil}
do {
let lastActiveAt = Expression<Int64>("last_active_at")
let identifier = Expression<String>("identifier")
Expand All @@ -205,14 +225,14 @@ public class Database: NSObject {
}

internal func getDatabaseForServer(_ serverUrl: String) throws -> Connection {
let db = try Connection(DEFAULT_DB_PATH)
let db = try openConnection(DEFAULT_DB_PATH)
let url = Expression<String>("url")
let dbPath = Expression<String>("db_path")
let query = serversTable.select(dbPath).where(url == serverUrl)

if let result = try db.pluck(query) {
let path = try result.get(dbPath)
return try Connection(path)
return try openConnection(path)
}

throw DatabaseError.NoResults(query.expression.description)
Expand Down
Loading
Loading