Refactor sticker sync logic

This commit is contained in:
Fedor Indutny
2025-02-06 11:00:55 -08:00
committed by GitHub
parent ce6ae78230
commit 9ac46b8e8a
11 changed files with 264 additions and 187 deletions

View File

@@ -2343,17 +2343,17 @@ export async function startApp(): Promise<void> {
if (status === 'installed' && isRemove) { if (status === 'installed' && isRemove) {
window.reduxActions.stickers.uninstallStickerPack(id, key, { window.reduxActions.stickers.uninstallStickerPack(id, key, {
fromSync: true, actionSource: 'syncMessage',
}); });
} else if (isInstall) { } else if (isInstall) {
if (status === 'downloaded') { if (status === 'downloaded') {
window.reduxActions.stickers.installStickerPack(id, key, { window.reduxActions.stickers.installStickerPack(id, key, {
fromSync: true, actionSource: 'syncMessage',
}); });
} else { } else {
void Stickers.downloadStickerPack(id, key, { void Stickers.downloadStickerPack(id, key, {
finalStatus: 'installed', finalStatus: 'installed',
fromSync: true, actionSource: 'syncMessage',
}); });
} }
} }

View File

@@ -11,13 +11,25 @@ import { Tabs } from '../Tabs';
export type OwnProps = { export type OwnProps = {
readonly blessedPacks: ReadonlyArray<StickerPackType>; readonly blessedPacks: ReadonlyArray<StickerPackType>;
readonly closeStickerPackPreview: () => unknown; readonly closeStickerPackPreview: () => unknown;
readonly downloadStickerPack: (packId: string, packKey: string) => unknown; readonly downloadStickerPack: (
packId: string,
packKey: string,
options: { actionSource: 'ui' }
) => unknown;
readonly i18n: LocalizerType; readonly i18n: LocalizerType;
readonly installStickerPack: (packId: string, packKey: string) => unknown; readonly installStickerPack: (
packId: string,
packKey: string,
options: { actionSource: 'ui' }
) => unknown;
readonly installedPacks: ReadonlyArray<StickerPackType>; readonly installedPacks: ReadonlyArray<StickerPackType>;
readonly knownPacks?: ReadonlyArray<StickerPackType>; readonly knownPacks?: ReadonlyArray<StickerPackType>;
readonly receivedPacks: ReadonlyArray<StickerPackType>; readonly receivedPacks: ReadonlyArray<StickerPackType>;
readonly uninstallStickerPack: (packId: string, packKey: string) => unknown; readonly uninstallStickerPack: (
packId: string,
packKey: string,
options: { actionSource: 'ui' }
) => unknown;
}; };
export type Props = OwnProps; export type Props = OwnProps;
@@ -47,7 +59,7 @@ export const StickerManager = React.memo(function StickerManagerInner({
return; return;
} }
knownPacks.forEach(pack => { knownPacks.forEach(pack => {
downloadStickerPack(pack.id, pack.key); downloadStickerPack(pack.id, pack.key, { actionSource: 'ui' });
}); });
// When this component is created, it's initially not part of the DOM, and then it's // When this component is created, it's initially not part of the DOM, and then it's

View File

@@ -12,8 +12,16 @@ export type OwnProps = {
readonly i18n: LocalizerType; readonly i18n: LocalizerType;
readonly pack: StickerPackType; readonly pack: StickerPackType;
readonly onClickPreview?: (sticker: StickerPackType) => unknown; readonly onClickPreview?: (sticker: StickerPackType) => unknown;
readonly installStickerPack?: (packId: string, packKey: string) => unknown; readonly installStickerPack?: (
readonly uninstallStickerPack?: (packId: string, packKey: string) => unknown; packId: string,
packKey: string,
options: { actionSource: 'ui' }
) => unknown;
readonly uninstallStickerPack?: (
packId: string,
packKey: string,
options: { actionSource: 'ui' }
) => unknown;
}; };
export type Props = OwnProps; export type Props = OwnProps;
@@ -37,7 +45,7 @@ export const StickerManagerPackRow = React.memo(
(e: React.MouseEvent) => { (e: React.MouseEvent) => {
e.stopPropagation(); e.stopPropagation();
if (installStickerPack) { if (installStickerPack) {
installStickerPack(id, key); installStickerPack(id, key, { actionSource: 'ui' });
} }
}, },
[id, installStickerPack, key] [id, installStickerPack, key]
@@ -47,7 +55,7 @@ export const StickerManagerPackRow = React.memo(
(e: React.MouseEvent) => { (e: React.MouseEvent) => {
e.stopPropagation(); e.stopPropagation();
if (isBlessed && uninstallStickerPack) { if (isBlessed && uninstallStickerPack) {
uninstallStickerPack(id, key); uninstallStickerPack(id, key, { actionSource: 'ui' });
} else { } else {
setUninstalling(true); setUninstalling(true);
} }
@@ -58,7 +66,7 @@ export const StickerManagerPackRow = React.memo(
const handleConfirmUninstall = React.useCallback(() => { const handleConfirmUninstall = React.useCallback(() => {
clearUninstalling(); clearUninstalling();
if (uninstallStickerPack) { if (uninstallStickerPack) {
uninstallStickerPack(id, key); uninstallStickerPack(id, key, { actionSource: 'ui' });
} }
}, [id, key, clearUninstalling, uninstallStickerPack]); }, [id, key, clearUninstalling, uninstallStickerPack]);

View File

@@ -19,10 +19,18 @@ export type OwnProps = {
readonly downloadStickerPack: ( readonly downloadStickerPack: (
packId: string, packId: string,
packKey: string, packKey: string,
options?: { finalStatus?: 'installed' | 'downloaded' } options: { finalStatus?: 'installed' | 'downloaded'; actionSource: 'ui' }
) => unknown;
readonly installStickerPack: (
packId: string,
packKey: string,
options: { actionSource: 'ui' }
) => unknown;
readonly uninstallStickerPack: (
packId: string,
packKey: string,
options: { actionSource: 'ui' }
) => unknown; ) => unknown;
readonly installStickerPack: (packId: string, packKey: string) => unknown;
readonly uninstallStickerPack: (packId: string, packKey: string) => unknown;
readonly pack?: StickerPackType; readonly pack?: StickerPackType;
readonly i18n: LocalizerType; readonly i18n: LocalizerType;
}; };
@@ -90,7 +98,7 @@ export const StickerPreviewModal = React.memo(
React.useEffect(() => { React.useEffect(() => {
if (pack && pack.status === 'known') { if (pack && pack.status === 'known') {
downloadStickerPack(pack.id, pack.key); downloadStickerPack(pack.id, pack.key, { actionSource: 'ui' });
} }
if ( if (
pack && pack &&
@@ -99,6 +107,7 @@ export const StickerPreviewModal = React.memo(
pack.attemptedStatus === 'installed') pack.attemptedStatus === 'installed')
) { ) {
downloadStickerPack(pack.id, pack.key, { downloadStickerPack(pack.id, pack.key, {
actionSource: 'ui',
finalStatus: pack.attemptedStatus, finalStatus: pack.attemptedStatus,
}); });
} }
@@ -130,10 +139,13 @@ export const StickerPreviewModal = React.memo(
if (isInstalled) { if (isInstalled) {
setConfirmingUninstall(true); setConfirmingUninstall(true);
} else if (pack.status === 'ephemeral') { } else if (pack.status === 'ephemeral') {
downloadStickerPack(pack.id, pack.key, { finalStatus: 'installed' }); downloadStickerPack(pack.id, pack.key, {
finalStatus: 'installed',
actionSource: 'ui',
});
handleClose(); handleClose();
} else { } else {
installStickerPack(pack.id, pack.key); installStickerPack(pack.id, pack.key, { actionSource: 'ui' });
handleClose(); handleClose();
} }
}, [ }, [
@@ -149,7 +161,7 @@ export const StickerPreviewModal = React.memo(
if (!pack) { if (!pack) {
return; return;
} }
uninstallStickerPack(pack.id, pack.key); uninstallStickerPack(pack.id, pack.key, { actionSource: 'ui' });
setConfirmingUninstall(false); setConfirmingUninstall(false);
// closeStickerPackPreview is called by <ConfirmationDialog />'s onClose // closeStickerPackPreview is called by <ConfirmationDialog />'s onClose
}, [uninstallStickerPack, setConfirmingUninstall, pack]); }, [uninstallStickerPack, setConfirmingUninstall, pack]);

View File

@@ -409,13 +409,9 @@ async function generateManifest(
} }
} }
log.info(
`storageService.upload(${version}): ` +
`adding uninstalled stickerPacks=${uninstalledStickerPacks.length}`
);
const uninstalledStickerPackIds = new Set<string>(); const uninstalledStickerPackIds = new Set<string>();
let newlyUninstalledPacks = 0;
uninstalledStickerPacks.forEach(stickerPack => { uninstalledStickerPacks.forEach(stickerPack => {
const storageRecord = new Proto.StorageRecord(); const storageRecord = new Proto.StorageRecord();
storageRecord.stickerPack = toStickerPackRecord(stickerPack); storageRecord.stickerPack = toStickerPackRecord(stickerPack);
@@ -431,22 +427,19 @@ async function generateManifest(
}); });
if (isNewItem) { if (isNewItem) {
postUploadUpdateFunctions.push(() => { newlyUninstalledPacks += 1;
void DataWriter.addUninstalledStickerPack({ postUploadUpdateFunctions.push(() =>
DataWriter.addUninstalledStickerPack({
...stickerPack, ...stickerPack,
storageID, storageID,
storageVersion: version, storageVersion: version,
storageNeedsSync: false, storageNeedsSync: false,
}); })
}); );
} }
}); });
log.info( let newlyInstalledPacks = 0;
`storageService.upload(${version}): ` +
`adding installed stickerPacks=${installedStickerPacks.length}`
);
installedStickerPacks.forEach(stickerPack => { installedStickerPacks.forEach(stickerPack => {
if (uninstalledStickerPackIds.has(stickerPack.id)) { if (uninstalledStickerPackIds.has(stickerPack.id)) {
log.error( log.error(
@@ -456,7 +449,7 @@ async function generateManifest(
window.reduxActions.stickers.uninstallStickerPack( window.reduxActions.stickers.uninstallStickerPack(
stickerPack.id, stickerPack.id,
stickerPack.key, stickerPack.key,
{ fromSync: true } { actionSource: 'storageService' }
); );
return; return;
} }
@@ -473,17 +466,27 @@ async function generateManifest(
}); });
if (isNewItem) { if (isNewItem) {
postUploadUpdateFunctions.push(() => { newlyInstalledPacks += 1;
void DataWriter.createOrUpdateStickerPack({ postUploadUpdateFunctions.push(() =>
...stickerPack, DataWriter.updateStickerPackInfo({
id: stickerPack.id,
key: stickerPack.key,
storageID, storageID,
storageVersion: version, storageVersion: version,
storageNeedsSync: false, storageNeedsSync: false,
}); position: stickerPack.position,
}); })
);
} }
}); });
log.info(
`storageService.upload(${version}): stickerPacks ` +
`installed=${newlyInstalledPacks}/${installedStickerPacks.length} ` +
`uninstalled=${newlyUninstalledPacks}/${uninstalledStickerPacks.length}`
);
log.info( log.info(
`storageService.upload(${version}): ` + `storageService.upload(${version}): ` +
`adding callLinks=${callLinkDbRecords.length}` `adding callLinks=${callLinkDbRecords.length}`
@@ -1498,10 +1501,15 @@ async function processManifest(
`storageService.process(${version}): localKey=${missingKey} was not ` + `storageService.process(${version}): localKey=${missingKey} was not ` +
'in remote manifest' 'in remote manifest'
); );
void DataWriter.createOrUpdateStickerPack({ void DataWriter.updateStickerPackInfo({
...stickerPack, id: stickerPack.id,
key: stickerPack.key,
storageID: undefined, storageID: undefined,
storageVersion: undefined, storageVersion: undefined,
storageUnknownFields: undefined,
storageNeedsSync: false,
uninstalledAt: stickerPack.uninstalledAt,
}); });
}); });

View File

@@ -1937,13 +1937,24 @@ export async function mergeStickerPackRecord(
`newPosition=${stickerPack.position ?? '?'}` `newPosition=${stickerPack.position ?? '?'}`
); );
if (localStickerPack && !wasUninstalled && isUninstalled) { if (!wasUninstalled && isUninstalled) {
assertDev(localStickerPack.key, 'Installed sticker pack has no key'); if (localStickerPack != null) {
window.reduxActions.stickers.uninstallStickerPack( assertDev(localStickerPack.key, 'Installed sticker pack has no key');
localStickerPack.id, window.reduxActions.stickers.uninstallStickerPack(
localStickerPack.key, localStickerPack.id,
{ fromStorageService: true } localStickerPack.key,
); {
actionSource: 'storageService',
uninstalledAt: stickerPack.uninstalledAt,
}
);
} else {
strictAssert(
stickerPack.key == null && stickerPack.uninstalledAt != null,
'Created sticker pack must be already uninstalled'
);
await DataWriter.addUninstalledStickerPack(stickerPack);
}
} else if ((!localStickerPack || wasUninstalled) && !isUninstalled) { } else if ((!localStickerPack || wasUninstalled) && !isUninstalled) {
assertDev(stickerPack.key, 'Sticker pack does not have key'); assertDev(stickerPack.key, 'Sticker pack does not have key');
@@ -1953,13 +1964,13 @@ export async function mergeStickerPackRecord(
stickerPack.id, stickerPack.id,
stickerPack.key, stickerPack.key,
{ {
fromStorageService: true, actionSource: 'storageService',
} }
); );
} else { } else {
void Stickers.downloadStickerPack(stickerPack.id, stickerPack.key, { void Stickers.downloadStickerPack(stickerPack.id, stickerPack.key, {
finalStatus: 'installed', finalStatus: 'installed',
fromStorageService: true, actionSource: 'storageService',
}); });
} }
} }

View File

@@ -934,11 +934,12 @@ type WritableInterface = {
createOrUpdateStickerPack: (pack: StickerPackType) => void; createOrUpdateStickerPack: (pack: StickerPackType) => void;
createOrUpdateStickerPacks: (packs: ReadonlyArray<StickerPackType>) => void; createOrUpdateStickerPacks: (packs: ReadonlyArray<StickerPackType>) => void;
// Returns previous sticker pack status
updateStickerPackStatus: ( updateStickerPackStatus: (
id: string, id: string,
status: StickerPackStatusType, status: StickerPackStatusType,
options?: { timestamp: number } options?: { timestamp: number }
) => void; ) => StickerPackStatusType | null;
updateStickerPackInfo: (info: StickerPackInfoType) => void; updateStickerPackInfo: (info: StickerPackInfoType) => void;
createOrUpdateSticker: (sticker: StickerType) => void; createOrUpdateSticker: (sticker: StickerType) => void;
createOrUpdateStickers: (sticker: ReadonlyArray<StickerType>) => void; createOrUpdateStickers: (sticker: ReadonlyArray<StickerType>) => void;
@@ -959,9 +960,10 @@ type WritableInterface = {
addUninstalledStickerPacks: ( addUninstalledStickerPacks: (
pack: ReadonlyArray<UninstalledStickerPackType> pack: ReadonlyArray<UninstalledStickerPackType>
) => void; ) => void;
removeUninstalledStickerPack: (packId: string) => void; // Returns `true` if sticker pack was previously uninstalled
installStickerPack: (packId: string, timestamp: number) => void; installStickerPack: (packId: string, timestamp: number) => boolean;
uninstallStickerPack: (packId: string, timestamp: number) => void; // Returns `true` if sticker pack was not previously uninstalled
uninstallStickerPack: (packId: string, timestamp: number) => boolean;
clearAllErrorStickerPackAttempts: () => void; clearAllErrorStickerPackAttempts: () => void;
updateEmojiUsage: (shortName: string, timeUsed?: number) => void; updateEmojiUsage: (shortName: string, timeUsed?: number) => void;

View File

@@ -507,7 +507,6 @@ export const DataWriter: ServerWritableInterface = {
getUnresolvedStickerPackReferences, getUnresolvedStickerPackReferences,
addUninstalledStickerPack, addUninstalledStickerPack,
addUninstalledStickerPacks, addUninstalledStickerPacks,
removeUninstalledStickerPack,
installStickerPack, installStickerPack,
uninstallStickerPack, uninstallStickerPack,
clearAllErrorStickerPackAttempts, clearAllErrorStickerPackAttempts,
@@ -5251,10 +5250,6 @@ function createOrUpdateStickerPack(
status, status,
stickerCount, stickerCount,
title, title,
storageID,
storageVersion,
storageUnknownFields,
storageNeedsSync,
} = pack; } = pack;
if (!id) { if (!id) {
throw new Error( throw new Error(
@@ -5262,21 +5257,6 @@ function createOrUpdateStickerPack(
); );
} }
let { position } = pack;
// Assign default position
if (!isNumber(position)) {
position = db
.prepare<EmptyQuery>(
`
SELECT IFNULL(MAX(position) + 1, 0)
FROM sticker_packs
`
)
.pluck()
.get();
}
const row = db const row = db
.prepare<Query>( .prepare<Query>(
` `
@@ -5299,11 +5279,6 @@ function createOrUpdateStickerPack(
status, status,
stickerCount, stickerCount,
title, title,
position: position ?? 0,
storageID: storageID ?? null,
storageVersion: storageVersion ?? null,
storageUnknownFields: storageUnknownFields ?? null,
storageNeedsSync: storageNeedsSync ? 1 : 0,
}; };
if (row) { if (row) {
@@ -5320,12 +5295,7 @@ function createOrUpdateStickerPack(
lastUsed = $lastUsed, lastUsed = $lastUsed,
status = $status, status = $status,
stickerCount = $stickerCount, stickerCount = $stickerCount,
title = $title, title = $title
position = $position,
storageID = $storageID,
storageVersion = $storageVersion,
storageUnknownFields = $storageUnknownFields,
storageNeedsSync = $storageNeedsSync
WHERE id = $id; WHERE id = $id;
` `
).run(payload); ).run(payload);
@@ -5333,6 +5303,21 @@ function createOrUpdateStickerPack(
return; return;
} }
let { position } = pack;
// Assign default position when inserting a row
if (!isNumber(position)) {
position = db
.prepare<EmptyQuery>(
`
SELECT IFNULL(MAX(position) + 1, 0)
FROM sticker_packs
`
)
.pluck()
.get();
}
db.prepare<Query>( db.prepare<Query>(
` `
INSERT INTO sticker_packs ( INSERT INTO sticker_packs (
@@ -5348,11 +5333,7 @@ function createOrUpdateStickerPack(
status, status,
stickerCount, stickerCount,
title, title,
position, position
storageID,
storageVersion,
storageUnknownFields,
storageNeedsSync
) values ( ) values (
$attemptedStatus, $attemptedStatus,
$author, $author,
@@ -5366,14 +5347,10 @@ function createOrUpdateStickerPack(
$status, $status,
$stickerCount, $stickerCount,
$title, $title,
$position, $position
$storageID,
$storageVersion,
$storageUnknownFields,
$storageNeedsSync
) )
` `
).run(payload); ).run({ ...payload, position: position ?? 0 });
} }
function createOrUpdateStickerPacks( function createOrUpdateStickerPacks(
db: WritableDB, db: WritableDB,
@@ -5390,21 +5367,27 @@ function updateStickerPackStatus(
id: string, id: string,
status: StickerPackStatusType, status: StickerPackStatusType,
options?: { timestamp: number } options?: { timestamp: number }
): void { ): StickerPackStatusType | null {
const timestamp = options ? options.timestamp || Date.now() : Date.now(); const timestamp = options ? options.timestamp || Date.now() : Date.now();
const installedAt = status === 'installed' ? timestamp : null; const installedAt = status === 'installed' ? timestamp : null;
db.prepare<Query>( return db.transaction(() => {
` const [select, selectParams] = sql`
UPDATE sticker_packs SELECT status FROM sticker_packs WHERE id IS ${id};
SET status = $status, installedAt = $installedAt `;
WHERE id = $id;
` const oldStatus = db.prepare(select).pluck().get(selectParams);
).run({
id, const [update, updateParams] = sql`
status, UPDATE sticker_packs
installedAt, SET status = ${status}, installedAt = ${installedAt}
}); WHERE id IS ${id}
`;
db.prepare(update).run(updateParams);
return oldStatus;
})();
} }
function updateStickerPackInfo( function updateStickerPackInfo(
db: WritableDB, db: WritableDB,
@@ -5415,6 +5398,7 @@ function updateStickerPackInfo(
storageUnknownFields, storageUnknownFields,
storageNeedsSync, storageNeedsSync,
uninstalledAt, uninstalledAt,
position,
}: StickerPackInfoType }: StickerPackInfoType
): void { ): void {
if (uninstalledAt) { if (uninstalledAt) {
@@ -5443,7 +5427,8 @@ function updateStickerPackInfo(
storageID = $storageID, storageID = $storageID,
storageVersion = $storageVersion, storageVersion = $storageVersion,
storageUnknownFields = $storageUnknownFields, storageUnknownFields = $storageUnknownFields,
storageNeedsSync = $storageNeedsSync storageNeedsSync = $storageNeedsSync,
position = $position
WHERE id = $id; WHERE id = $id;
` `
).run({ ).run({
@@ -5452,6 +5437,7 @@ function updateStickerPackInfo(
storageVersion: storageVersion ?? null, storageVersion: storageVersion ?? null,
storageUnknownFields: storageUnknownFields ?? null, storageUnknownFields: storageUnknownFields ?? null,
storageNeedsSync: storageNeedsSync ? 1 : 0, storageNeedsSync: storageNeedsSync ? 1 : 0,
position: position || 0,
}); });
} }
} }
@@ -5774,17 +5760,17 @@ function addUninstalledStickerPack(
): void { ): void {
db.prepare<Query>( db.prepare<Query>(
` `
INSERT OR REPLACE INTO uninstalled_sticker_packs INSERT OR REPLACE INTO uninstalled_sticker_packs
( (
id, uninstalledAt, storageID, storageVersion, storageUnknownFields, id, uninstalledAt, storageID, storageVersion, storageUnknownFields,
storageNeedsSync storageNeedsSync
) )
VALUES VALUES
( (
$id, $uninstalledAt, $storageID, $storageVersion, $unknownFields, $id, $uninstalledAt, $storageID, $storageVersion, $unknownFields,
$storageNeedsSync $storageNeedsSync
) )
` `
).run({ ).run({
id: pack.id, id: pack.id,
uninstalledAt: pack.uninstalledAt, uninstalledAt: pack.uninstalledAt,
@@ -5805,9 +5791,10 @@ function addUninstalledStickerPacks(
})(); })();
} }
function removeUninstalledStickerPack(db: WritableDB, packId: string): void { function removeUninstalledStickerPack(db: WritableDB, packId: string): void {
db.prepare<Query>( const [query, params] = sql`
'DELETE FROM uninstalled_sticker_packs WHERE id IS $id' DELETE FROM uninstalled_sticker_packs WHERE id IS ${packId}
).run({ id: packId }); `;
db.prepare(query).run(params);
} }
function getUninstalledStickerPacks( function getUninstalledStickerPacks(
db: ReadableDB db: ReadableDB
@@ -5876,39 +5863,57 @@ function installStickerPack(
db: WritableDB, db: WritableDB,
packId: string, packId: string,
timestamp: number timestamp: number
): void { ): boolean {
return db.transaction(() => { return db.transaction(() => {
const status = 'installed'; const status = 'installed';
updateStickerPackStatus(db, packId, status, { timestamp });
removeUninstalledStickerPack(db, packId); removeUninstalledStickerPack(db, packId);
const oldStatus = updateStickerPackStatus(db, packId, status, {
timestamp,
});
const wasPreviouslyUninstalled = oldStatus !== 'installed';
if (wasPreviouslyUninstalled) {
const [query, params] = sql`
UPDATE sticker_packs SET
storageNeedsSync = 1
WHERE id IS ${packId};
`;
db.prepare(query).run(params);
}
return wasPreviouslyUninstalled;
})(); })();
} }
function uninstallStickerPack( function uninstallStickerPack(
db: WritableDB, db: WritableDB,
packId: string, packId: string,
timestamp: number timestamp: number
): void { ): boolean {
return db.transaction(() => { return db.transaction(() => {
const status = 'downloaded'; const status = 'downloaded';
updateStickerPackStatus(db, packId, status); const oldStatus = updateStickerPackStatus(db, packId, status);
db.prepare<Query>( const wasPreviouslyInstalled = oldStatus === 'installed';
`
const [query, params] = sql`
UPDATE sticker_packs SET UPDATE sticker_packs SET
storageID = NULL, storageID = NULL,
storageVersion = NULL, storageVersion = NULL,
storageUnknownFields = NULL, storageUnknownFields = NULL,
storageNeedsSync = 0 storageNeedsSync = 0
WHERE id = $packId; WHERE id = ${packId}
` `;
).run({ packId });
db.prepare(query).run(params);
addUninstalledStickerPack(db, { addUninstalledStickerPack(db, {
id: packId, id: packId,
uninstalledAt: timestamp, uninstalledAt: timestamp,
storageNeedsSync: true, storageNeedsSync: wasPreviouslyInstalled,
}); });
return wasPreviouslyInstalled;
})(); })();
} }
function getAllStickers(db: ReadableDB): Array<StickerType> { function getAllStickers(db: ReadableDB): Array<StickerType> {

View File

@@ -10,11 +10,12 @@ import type {
StickerPackType as StickerPackDBType, StickerPackType as StickerPackDBType,
} from '../../sql/Interface'; } from '../../sql/Interface';
import { DataReader, DataWriter } from '../../sql/Client'; import { DataReader, DataWriter } from '../../sql/Client';
import type { RecentStickerType } from '../../types/Stickers'; import type { ActionSourceType, RecentStickerType } from '../../types/Stickers';
import { import {
downloadStickerPack as externalDownloadStickerPack, downloadStickerPack as externalDownloadStickerPack,
maybeDeletePack, maybeDeletePack,
} from '../../types/Stickers'; } from '../../types/Stickers';
import { drop } from '../../util/drop';
import { storageServiceUploadJob } from '../../services/storage'; import { storageServiceUploadJob } from '../../services/storage';
import { sendStickerPackSync } from '../../shims/textsecure'; import { sendStickerPackSync } from '../../shims/textsecure';
import { trigger } from '../../shims/events'; import { trigger } from '../../shims/events';
@@ -74,7 +75,7 @@ type StickerAddedAction = ReadonlyDeep<{
type InstallStickerPackPayloadType = ReadonlyDeep<{ type InstallStickerPackPayloadType = ReadonlyDeep<{
packId: string; packId: string;
fromSync: boolean; actionSource: ActionSourceType;
status: 'installed'; status: 'installed';
installedAt: number; installedAt: number;
recentStickers: Array<RecentStickerType>; recentStickers: Array<RecentStickerType>;
@@ -93,7 +94,7 @@ type ClearInstalledStickerPackAction = ReadonlyDeep<{
type UninstallStickerPackPayloadType = ReadonlyDeep<{ type UninstallStickerPackPayloadType = ReadonlyDeep<{
packId: string; packId: string;
fromSync: boolean; actionSource: ActionSourceType;
status: 'downloaded'; status: 'downloaded';
installedAt?: undefined; installedAt?: undefined;
recentStickers: Array<RecentStickerType>; recentStickers: Array<RecentStickerType>;
@@ -199,12 +200,21 @@ function stickerPackAdded(
function downloadStickerPack( function downloadStickerPack(
packId: string, packId: string,
packKey: string, packKey: string,
options?: { finalStatus?: 'installed' | 'downloaded' } {
finalStatus,
actionSource,
}: {
finalStatus?: 'installed' | 'downloaded';
actionSource: ActionSourceType;
}
): NoopActionType { ): NoopActionType {
const { finalStatus } = options || { finalStatus: undefined };
// We're just kicking this off, since it will generate more redux events // We're just kicking this off, since it will generate more redux events
void externalDownloadStickerPack(packId, packKey, { finalStatus }); drop(
externalDownloadStickerPack(packId, packKey, {
finalStatus,
actionSource,
})
);
return { return {
type: 'NOOP', type: 'NOOP',
@@ -215,41 +225,33 @@ function downloadStickerPack(
function installStickerPack( function installStickerPack(
packId: string, packId: string,
packKey: string, packKey: string,
options: { { actionSource }: { actionSource: ActionSourceType }
fromSync?: boolean;
fromStorageService?: boolean;
fromBackup?: boolean;
} = {}
): InstallStickerPackAction { ): InstallStickerPackAction {
return { return {
type: 'stickers/INSTALL_STICKER_PACK', type: 'stickers/INSTALL_STICKER_PACK',
payload: doInstallStickerPack(packId, packKey, options), payload: doInstallStickerPack(packId, packKey, { actionSource }),
}; };
} }
async function doInstallStickerPack( async function doInstallStickerPack(
packId: string, packId: string,
packKey: string, packKey: string,
options: { { actionSource }: { actionSource: ActionSourceType }
fromSync?: boolean;
fromStorageService?: boolean;
fromBackup?: boolean;
} = {}
): Promise<InstallStickerPackPayloadType> { ): Promise<InstallStickerPackPayloadType> {
const {
fromSync = false,
fromStorageService = false,
fromBackup = false,
} = options;
const timestamp = Date.now(); const timestamp = Date.now();
await DataWriter.installStickerPack(packId, timestamp); const changed = await DataWriter.installStickerPack(packId, timestamp);
if (!fromSync && !fromStorageService && !fromBackup) { if (actionSource === 'ui') {
// Kick this off, but don't wait for it // Kick this off, but don't wait for it
void sendStickerPackSync(packId, packKey, true); drop(sendStickerPackSync(packId, packKey, true));
} }
if (!fromStorageService && !fromBackup) { if (
// Don't cause storageService loop
actionSource !== 'storageService' &&
// Stickers downloaded on startup should already be synced
actionSource !== 'startup' &&
changed
) {
storageServiceUploadJob({ reason: 'doInstallServicePack' }); storageServiceUploadJob({ reason: 'doInstallServicePack' });
} }
@@ -257,7 +259,7 @@ async function doInstallStickerPack(
return { return {
packId, packId,
fromSync, actionSource,
status: 'installed', status: 'installed',
installedAt: timestamp, installedAt: timestamp,
recentStickers: recentStickers.map(item => ({ recentStickers: recentStickers.map(item => ({
@@ -269,32 +271,44 @@ async function doInstallStickerPack(
function uninstallStickerPack( function uninstallStickerPack(
packId: string, packId: string,
packKey: string, packKey: string,
options: { fromSync?: boolean; fromStorageService?: boolean } = {} {
actionSource,
uninstalledAt,
}: { actionSource: ActionSourceType; uninstalledAt?: number }
): UninstallStickerPackAction { ): UninstallStickerPackAction {
return { return {
type: 'stickers/UNINSTALL_STICKER_PACK', type: 'stickers/UNINSTALL_STICKER_PACK',
payload: doUninstallStickerPack(packId, packKey, options), payload: doUninstallStickerPack(packId, packKey, {
actionSource,
uninstalledAt,
}),
}; };
} }
async function doUninstallStickerPack( async function doUninstallStickerPack(
packId: string, packId: string,
packKey: string, packKey: string,
options: { fromSync?: boolean; fromStorageService?: boolean } = {} {
actionSource,
uninstalledAt = Date.now(),
}: { actionSource: ActionSourceType; uninstalledAt?: number }
): Promise<UninstallStickerPackPayloadType> { ): Promise<UninstallStickerPackPayloadType> {
const { fromSync = false, fromStorageService = false } = options; const changed = await DataWriter.uninstallStickerPack(packId, uninstalledAt);
const timestamp = Date.now();
await DataWriter.uninstallStickerPack(packId, timestamp);
// If there are no more references, it should be removed // If there are no more references, it should be removed
await maybeDeletePack(packId); await maybeDeletePack(packId);
if (!fromSync && !fromStorageService) { if (actionSource === 'ui') {
// Kick this off, but don't wait for it // Kick this off, but don't wait for it
void sendStickerPackSync(packId, packKey, false); drop(sendStickerPackSync(packId, packKey, false));
} }
if (!fromStorageService) { if (
// Don't cause storageService loop
actionSource !== 'storageService' &&
// Stickers downloaded on startup should already be synced
actionSource !== 'startup' &&
changed
) {
storageServiceUploadJob({ reason: 'doUninstallStickerPack' }); storageServiceUploadJob({ reason: 'doUninstallStickerPack' });
} }
@@ -302,7 +316,7 @@ async function doUninstallStickerPack(
return { return {
packId, packId,
fromSync, actionSource,
status: 'downloaded', status: 'downloaded',
installedAt: undefined, installedAt: undefined,
recentStickers: recentStickers.map(item => ({ recentStickers: recentStickers.map(item => ({
@@ -438,7 +452,8 @@ export function reducer(
action.type === 'stickers/UNINSTALL_STICKER_PACK_FULFILLED' action.type === 'stickers/UNINSTALL_STICKER_PACK_FULFILLED'
) { ) {
const { payload } = action; const { payload } = action;
const { fromSync, installedAt, packId, status, recentStickers } = payload; const { actionSource, installedAt, packId, status, recentStickers } =
payload;
const { packs } = state; const { packs } = state;
const existingPack = packs[packId]; const existingPack = packs[packId];
@@ -453,7 +468,7 @@ export function reducer(
} }
const isBlessed = state.blessedPacks[packId]; const isBlessed = state.blessedPacks[packId];
const installedPack = !fromSync && !isBlessed ? packId : null; const installedPack = actionSource === 'ui' && !isBlessed ? packId : null;
return { return {
...state, ...state,

View File

@@ -32,6 +32,12 @@ import { isNotNil } from '../util/isNotNil';
import { encryptLegacyAttachment } from '../util/encryptLegacyAttachment'; import { encryptLegacyAttachment } from '../util/encryptLegacyAttachment';
import { AttachmentDisposition } from '../util/getLocalAttachmentUrl'; import { AttachmentDisposition } from '../util/getLocalAttachmentUrl';
export type ActionSourceType =
| 'startup'
| 'syncMessage'
| 'storageService'
| 'ui';
export type StickerType = { export type StickerType = {
packId: string; packId: string;
stickerId: number; stickerId: number;
@@ -265,6 +271,7 @@ export function downloadQueuedPacks(): void {
downloadStickerPack(id, key, { downloadStickerPack(id, key, {
finalStatus: status, finalStatus: status,
suppressError: true, suppressError: true,
actionSource: 'startup',
}) })
); );
} }
@@ -634,9 +641,7 @@ export async function downloadEphemeralPack(
} }
export type DownloadStickerPackOptions = Readonly<{ export type DownloadStickerPackOptions = Readonly<{
fromSync?: boolean; actionSource: ActionSourceType;
fromStorageService?: boolean;
fromBackup?: boolean;
finalStatus?: StickerPackStatusType; finalStatus?: StickerPackStatusType;
suppressError?: boolean; suppressError?: boolean;
}>; }>;
@@ -644,7 +649,7 @@ export type DownloadStickerPackOptions = Readonly<{
export async function downloadStickerPack( export async function downloadStickerPack(
packId: string, packId: string,
packKey: string, packKey: string,
options: DownloadStickerPackOptions = {} options: DownloadStickerPackOptions
): Promise<void> { ): Promise<void> {
// This will ensure that only one download process is in progress at any given time // This will ensure that only one download process is in progress at any given time
return downloadQueue.add(async () => { return downloadQueue.add(async () => {
@@ -664,9 +669,7 @@ async function doDownloadStickerPack(
packKey: string, packKey: string,
{ {
finalStatus = 'downloaded', finalStatus = 'downloaded',
fromSync = false, actionSource,
fromStorageService = false,
fromBackup = false,
suppressError = false, suppressError = false,
}: DownloadStickerPackOptions }: DownloadStickerPackOptions
): Promise<void> { ): Promise<void> {
@@ -788,9 +791,11 @@ async function doDownloadStickerPack(
status: 'pending', status: 'pending',
createdAt: Date.now(), createdAt: Date.now(),
stickers: {}, stickers: {},
storageNeedsSync: !fromStorageService && !fromBackup,
title: proto.title ?? '', title: proto.title ?? '',
author: proto.author ?? '', author: proto.author ?? '',
// Redux handles these
storageNeedsSync: false,
}; };
await DataWriter.createOrUpdateStickerPack(pack); await DataWriter.createOrUpdateStickerPack(pack);
stickerPackAdded(pack); stickerPackAdded(pack);
@@ -865,9 +870,7 @@ async function doDownloadStickerPack(
// No-op // No-op
} else if (finalStatus === 'installed') { } else if (finalStatus === 'installed') {
await installStickerPack(packId, packKey, { await installStickerPack(packId, packKey, {
fromSync, actionSource,
fromStorageService,
fromBackup,
}); });
} else { } else {
// Mark the pack as complete // Mark the pack as complete

View File

@@ -704,6 +704,7 @@ export function createIPCEvents(
installStickerPack: async (packId, key) => { installStickerPack: async (packId, key) => {
void Stickers.downloadStickerPack(packId, key, { void Stickers.downloadStickerPack(packId, key, {
finalStatus: 'installed', finalStatus: 'installed',
actionSource: 'ui',
}); });
}, },