Use storage service for call links

This commit is contained in:
ayumi-signal 2024-09-04 11:06:06 -07:00 committed by GitHub
parent 50447b7686
commit 5a75246e42
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
23 changed files with 583 additions and 50 deletions

View file

@ -31,12 +31,17 @@ import type {
CallHistoryPagination,
CallLogEventTarget,
} from '../types/CallDisposition';
import type { CallLinkStateType, CallLinkType } from '../types/CallLink';
import type {
CallLinkRecord,
CallLinkStateType,
CallLinkType,
} from '../types/CallLink';
import type { AttachmentDownloadJobType } from '../types/AttachmentDownload';
import type { GroupSendEndorsementsData } from '../types/GroupSendEndorsements';
import type { SyncTaskType } from '../util/syncTasks';
import type { AttachmentBackupJobType } from '../types/AttachmentBackup';
import type { SingleProtoJobQueue } from '../jobs/singleProtoJobQueue';
import type { DeleteCallLinkOptions } from './server/callLinks';
export type ReadableDB = Database & { __readable_db: never };
export type WritableDB = ReadableDB & { __writable_db: never };
@ -568,6 +573,8 @@ type ReadableInterface = {
callLinkExists(roomId: string): boolean;
getAllCallLinks: () => ReadonlyArray<CallLinkType>;
getCallLinkByRoomId: (roomId: string) => CallLinkType | undefined;
getCallLinkRecordByRoomId: (roomId: string) => CallLinkRecord | undefined;
getAllCallLinkRecordsWithAdminKey(): ReadonlyArray<CallLinkRecord>;
getAllMarkedDeletedCallLinks(): ReadonlyArray<CallLinkType>;
getMessagesBetween: (
conversationId: string,
@ -799,13 +806,14 @@ type WritableInterface = {
markCallHistoryMissed(callIds: ReadonlyArray<string>): void;
getRecentStaleRingsAndMarkOlderMissed(): ReadonlyArray<MaybeStaleCallHistory>;
insertCallLink(callLink: CallLinkType): void;
updateCallLink(callLink: CallLinkType): void;
updateCallLinkAdminKeyByRoomId(roomId: string, adminKey: string): void;
updateCallLinkState(
roomId: string,
callLinkState: CallLinkStateType
): CallLinkType;
beginDeleteAllCallLinks(): void;
beginDeleteCallLink(roomId: string): void;
beginDeleteCallLink(roomId: string, options: DeleteCallLinkOptions): void;
finalizeDeleteCallLink(roomId: string): void;
_removeAllCallLinks(): void;
deleteCallLinkFromSync(roomId: string): void;

View file

@ -174,10 +174,13 @@ import {
callLinkExists,
getAllCallLinks,
getCallLinkByRoomId,
getCallLinkRecordByRoomId,
insertCallLink,
updateCallLink,
updateCallLinkAdminKeyByRoomId,
updateCallLinkState,
beginDeleteAllCallLinks,
getAllCallLinkRecordsWithAdminKey,
getAllMarkedDeletedCallLinks,
finalizeDeleteCallLink,
beginDeleteCallLink,
@ -304,6 +307,8 @@ export const DataReader: ServerReadableInterface = {
callLinkExists,
getAllCallLinks,
getCallLinkByRoomId,
getCallLinkRecordByRoomId,
getAllCallLinkRecordsWithAdminKey,
getAllMarkedDeletedCallLinks,
getMessagesBetween,
getNearbyMessageFromDeletedSet,
@ -439,6 +444,7 @@ export const DataWriter: ServerWritableInterface = {
saveCallHistory,
markCallHistoryMissed,
insertCallLink,
updateCallLink,
updateCallLinkAdminKeyByRoomId,
updateCallLinkState,
beginDeleteAllCallLinks,
@ -6445,6 +6451,14 @@ function eraseStorageServiceState(db: WritableDB): void {
storageVersion = null,
storageUnknownFields = null,
storageNeedsSync = 0;
-- Call links
UPDATE callLinks
SET
storageID = null,
storageVersion = null,
storageUnknownFields = null,
storageNeedsSync = 0;
`);
}

View file

@ -0,0 +1,38 @@
// Copyright 2024 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import type { Database } from '@signalapp/better-sqlite3';
import type { LoggerType } from '../../types/Logging';
export const version = 1190;
export function updateToSchemaVersion1190(
currentVersion: number,
db: Database,
logger: LoggerType
): void {
if (currentVersion >= 1190) {
return;
}
db.transaction(() => {
db.exec(`
ALTER TABLE callLinks ADD COLUMN storageID TEXT;
ALTER TABLE callLinks ADD COLUMN storageVersion INTEGER;
ALTER TABLE callLinks ADD COLUMN storageUnknownFields BLOB;
ALTER TABLE callLinks ADD COLUMN storageNeedsSync INTEGER NOT NULL DEFAULT 0;
ALTER TABLE callLinks ADD COLUMN deletedAt INTEGER;
`);
db.prepare(
`
UPDATE callLinks
SET deletedAt = $deletedAt
WHERE deleted = 1;
`
).run({
deletedAt: new Date().getTime(),
});
db.pragma('user_version = 1190');
})();
logger.info('updateToSchemaVersion1190: success!');
}

View file

@ -94,10 +94,11 @@ import { updateToSchemaVersion1140 } from './1140-call-links-deleted-column';
import { updateToSchemaVersion1150 } from './1150-expire-timer-version';
import { updateToSchemaVersion1160 } from './1160-optimize-calls-unread-count';
import { updateToSchemaVersion1170 } from './1170-update-call-history-unread-index';
import { updateToSchemaVersion1180 } from './1180-add-attachment-download-source';
import {
updateToSchemaVersion1180,
updateToSchemaVersion1190,
version as MAX_VERSION,
} from './1180-add-attachment-download-source';
} from './1190-call-links-storage';
function updateToSchemaVersion1(
currentVersion: number,
@ -2060,6 +2061,7 @@ export const SCHEMA_VERSIONS = [
updateToSchemaVersion1160,
updateToSchemaVersion1170,
updateToSchemaVersion1180,
updateToSchemaVersion1190,
];
export class DBVersionFromFutureError extends Error {

View file

@ -2,7 +2,11 @@
// SPDX-License-Identifier: AGPL-3.0-only
import { CallLinkRootKey } from '@signalapp/ringrtc';
import type { CallLinkStateType, CallLinkType } from '../../types/CallLink';
import type {
CallLinkRecord,
CallLinkStateType,
CallLinkType,
} from '../../types/CallLink';
import {
callLinkRestrictionsSchema,
callLinkRecordSchema,
@ -31,6 +35,19 @@ export function getCallLinkByRoomId(
db: ReadableDB,
roomId: string
): CallLinkType | undefined {
const callLinkRecord = getCallLinkRecordByRoomId(db, roomId);
if (!callLinkRecord) {
return undefined;
}
return callLinkFromRecord(callLinkRecord);
}
// When you need to access all the fields (such as deleted and storage fields)
export function getCallLinkRecordByRoomId(
db: ReadableDB,
roomId: string
): CallLinkRecord | undefined {
const row = prepare(db, 'SELECT * FROM callLinks WHERE roomId = $roomId').get(
{
roomId,
@ -41,7 +58,7 @@ export function getCallLinkByRoomId(
return undefined;
}
return callLinkFromRecord(callLinkRecordSchema.parse(row));
return callLinkRecordSchema.parse(row);
}
export function getAllCallLinks(db: ReadableDB): ReadonlyArray<CallLinkType> {
@ -69,7 +86,11 @@ function _insertCallLink(db: WritableDB, callLink: CallLinkType): void {
name,
restrictions,
revoked,
expiration
expiration,
storageID,
storageVersion,
storageUnknownFields,
storageNeedsSync
) VALUES (
$roomId,
$rootKey,
@ -77,7 +98,11 @@ function _insertCallLink(db: WritableDB, callLink: CallLinkType): void {
$name,
$restrictions,
$revoked,
$expiration
$expiration,
$storageID,
$storageVersion,
$storageUnknownFields,
$storageNeedsSync
)
`
).run(data);
@ -87,6 +112,30 @@ export function insertCallLink(db: WritableDB, callLink: CallLinkType): void {
_insertCallLink(db, callLink);
}
export function updateCallLink(db: WritableDB, callLink: CallLinkType): void {
const { roomId, rootKey } = callLink;
assertRoomIdMatchesRootKey(roomId, rootKey);
const data = callLinkToRecord(callLink);
// Do not write roomId or rootKey since they should never change
db.prepare(
`
UPDATE callLinks
SET
adminKey = $adminKey,
name = $name,
restrictions = $restrictions,
revoked = $revoked,
expiration = $expiration,
storageID = $storageID,
storageVersion = $storageVersion,
storageUnknownFields = $storageUnknownFields,
storageNeedsSync = $storageNeedsSync
WHERE roomId = $roomId
`
).run(data);
}
export function updateCallLinkState(
db: WritableDB,
roomId: string,
@ -167,7 +216,16 @@ export function deleteCallLinkFromSync(db: WritableDB, roomId: string): void {
})();
}
export function beginDeleteCallLink(db: WritableDB, roomId: string): void {
export type DeleteCallLinkOptions = {
storageNeedsSync: boolean;
deletedAt?: number;
};
export function beginDeleteCallLink(
db: WritableDB,
roomId: string,
options: DeleteCallLinkOptions
): void {
db.transaction(() => {
// If adminKey is null, then we should delete the call link
const [deleteNonAdminCallLinksQuery, deleteNonAdminCallLinksParams] = sql`
@ -182,11 +240,17 @@ export function beginDeleteCallLink(db: WritableDB, roomId: string): void {
// Skip this query if the call is already deleted
if (result.changes === 0) {
const { storageNeedsSync } = options;
const deletedAt = options.deletedAt ?? new Date().getTime();
// If the admin key is not null, we should mark it for deletion
const [markAdminCallLinksDeletedQuery, markAdminCallLinksDeletedParams] =
sql`
UPDATE callLinks
SET deleted = 1
SET
deleted = 1,
deletedAt = ${deletedAt},
storageNeedsSync = ${storageNeedsSync ? 1 : 0}
WHERE adminKey IS NOT NULL
AND roomId = ${roomId};
`;
@ -201,14 +265,21 @@ export function beginDeleteCallLink(db: WritableDB, roomId: string): void {
}
export function beginDeleteAllCallLinks(db: WritableDB): void {
const deletedAt = new Date().getTime();
db.transaction(() => {
const [markAdminCallLinksDeletedQuery] = sql`
const [markAdminCallLinksDeletedQuery, markAdminCallLinksDeletedParams] =
sql`
UPDATE callLinks
SET deleted = 1
SET
deleted = 1,
deletedAt = ${deletedAt},
storageNeedsSync = 1
WHERE adminKey IS NOT NULL;
`;
db.prepare(markAdminCallLinksDeletedQuery).run();
db.prepare(markAdminCallLinksDeletedQuery).run(
markAdminCallLinksDeletedParams
);
const [deleteNonAdminCallLinksQuery] = sql`
DELETE FROM callLinks
@ -219,6 +290,21 @@ export function beginDeleteAllCallLinks(db: WritableDB): void {
})();
}
// When you need to access the deleted field
export function getAllCallLinkRecordsWithAdminKey(
db: ReadableDB
): ReadonlyArray<CallLinkRecord> {
const [query] = sql`
SELECT * FROM callLinks
WHERE adminKey IS NOT NULL
AND rootKey IS NOT NULL;
`;
return db
.prepare(query)
.all()
.map(item => callLinkRecordSchema.parse(item));
}
export function getAllMarkedDeletedCallLinks(
db: ReadableDB
): ReadonlyArray<CallLinkType> {
@ -231,9 +317,13 @@ export function getAllMarkedDeletedCallLinks(
.map(item => callLinkFromRecord(callLinkRecordSchema.parse(item)));
}
// TODO: Run this after uploading storage records, maybe periodically on startup
export function finalizeDeleteCallLink(db: WritableDB, roomId: string): void {
const [query, params] = sql`
DELETE FROM callLinks WHERE roomId = ${roomId} AND deleted = 1;
DELETE FROM callLinks
WHERE roomId = ${roomId}
AND deleted = 1
AND storageNeedsSync = 0;
`;
db.prepare(query).run(params);
}