Disable storage service while importing backup
This commit is contained in:
parent
17c908bbf4
commit
a527b88867
4 changed files with 147 additions and 132 deletions
|
@ -736,6 +736,7 @@ export async function startApp(): Promise<void> {
|
||||||
);
|
);
|
||||||
log.info('background/shutdown: shutting down messageReceiver');
|
log.info('background/shutdown: shutting down messageReceiver');
|
||||||
server.unregisterRequestHandler(messageReceiver);
|
server.unregisterRequestHandler(messageReceiver);
|
||||||
|
StorageService.disableStorageService();
|
||||||
messageReceiver.stopProcessing();
|
messageReceiver.stopProcessing();
|
||||||
await window.waitForAllBatchers();
|
await window.waitForAllBatchers();
|
||||||
}
|
}
|
||||||
|
@ -1312,12 +1313,8 @@ export async function startApp(): Promise<void> {
|
||||||
});
|
});
|
||||||
|
|
||||||
async function runStorageService({ reason }: { reason: string }) {
|
async function runStorageService({ reason }: { reason: string }) {
|
||||||
if (window.storage.get('backupDownloadPath')) {
|
await backupReady.promise;
|
||||||
log.info(
|
|
||||||
'background: not running storage service while downloading backup'
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
StorageService.enableStorageService();
|
StorageService.enableStorageService();
|
||||||
StorageService.runStorageServiceSyncJob({
|
StorageService.runStorageServiceSyncJob({
|
||||||
reason: `runStorageService/${reason}`,
|
reason: `runStorageService/${reason}`,
|
||||||
|
@ -1431,10 +1428,7 @@ export async function startApp(): Promise<void> {
|
||||||
drop(connect(true));
|
drop(connect(true));
|
||||||
|
|
||||||
// Connect messageReceiver back to websocket
|
// Connect messageReceiver back to websocket
|
||||||
afterStart();
|
drop(afterStart());
|
||||||
|
|
||||||
// Run storage service after linking
|
|
||||||
drop(runStorageService({ reason: 'background/registration_done' }));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
cancelInitializationMessage();
|
cancelInitializationMessage();
|
||||||
|
@ -1519,12 +1513,10 @@ export async function startApp(): Promise<void> {
|
||||||
resolveOnAppView = undefined;
|
resolveOnAppView = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
afterStart();
|
drop(afterStart());
|
||||||
}
|
}
|
||||||
|
|
||||||
const backupReady = explodePromise<void>();
|
async function afterStart() {
|
||||||
|
|
||||||
function afterStart() {
|
|
||||||
strictAssert(messageReceiver, 'messageReceiver must be initialized');
|
strictAssert(messageReceiver, 'messageReceiver must be initialized');
|
||||||
strictAssert(server, 'server must be initialized');
|
strictAssert(server, 'server must be initialized');
|
||||||
|
|
||||||
|
@ -1590,45 +1582,25 @@ export async function startApp(): Promise<void> {
|
||||||
onOffline();
|
onOffline();
|
||||||
}
|
}
|
||||||
|
|
||||||
drop(downloadBackup());
|
// Download backup before enabling request handler and storage service
|
||||||
}
|
try {
|
||||||
|
await backupsService.download({
|
||||||
|
onProgress: (currentBytes, totalBytes) => {
|
||||||
|
window.reduxActions.installer.updateBackupImportProgress({
|
||||||
|
currentBytes,
|
||||||
|
totalBytes,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
async function downloadBackup() {
|
|
||||||
strictAssert(server != null, 'server must be initialized');
|
|
||||||
strictAssert(
|
|
||||||
messageReceiver != null,
|
|
||||||
'MessageReceiver must be initialized'
|
|
||||||
);
|
|
||||||
|
|
||||||
const backupDownloadPath = window.storage.get('backupDownloadPath');
|
|
||||||
if (!backupDownloadPath) {
|
|
||||||
log.warn('downloadBackup: no backup download path, skipping');
|
|
||||||
backupReady.resolve();
|
backupReady.resolve();
|
||||||
server.registerRequestHandler(messageReceiver);
|
} catch (error) {
|
||||||
drop(runStorageService({ reason: 'downloadBackup/noPath' }));
|
backupReady.reject(error);
|
||||||
return;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
const absoluteDownloadPath =
|
|
||||||
window.Signal.Migrations.getAbsoluteDownloadsPath(backupDownloadPath);
|
|
||||||
log.info('downloadBackup: downloading...');
|
|
||||||
const hasBackup = await backupsService.download(absoluteDownloadPath, {
|
|
||||||
onProgress: (currentBytes, totalBytes) => {
|
|
||||||
window.reduxActions.installer.updateBackupImportProgress({
|
|
||||||
currentBytes,
|
|
||||||
totalBytes,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
await window.storage.remove('backupDownloadPath');
|
|
||||||
|
|
||||||
log.info(`downloadBackup: done, had backup=${hasBackup}`);
|
|
||||||
|
|
||||||
// Start storage service sync, etc
|
|
||||||
log.info('downloadBackup: processing websocket messages, storage service');
|
|
||||||
backupReady.resolve();
|
|
||||||
server.registerRequestHandler(messageReceiver);
|
server.registerRequestHandler(messageReceiver);
|
||||||
drop(runStorageService({ reason: 'downloadBackup/complete' }));
|
drop(runStorageService({ reason: 'afterStart' }));
|
||||||
}
|
}
|
||||||
|
|
||||||
window.getSyncRequest = (timeoutMillis?: number) => {
|
window.getSyncRequest = (timeoutMillis?: number) => {
|
||||||
|
@ -1693,6 +1665,8 @@ export async function startApp(): Promise<void> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let backupReady = explodePromise<void>();
|
||||||
|
|
||||||
let connectCount = 0;
|
let connectCount = 0;
|
||||||
let connectPromise: ExplodePromiseResultType<void> | undefined;
|
let connectPromise: ExplodePromiseResultType<void> | undefined;
|
||||||
let remotelyExpired = false;
|
let remotelyExpired = false;
|
||||||
|
@ -1727,7 +1701,14 @@ export async function startApp(): Promise<void> {
|
||||||
|
|
||||||
strictAssert(server !== undefined, 'WebAPI not connected');
|
strictAssert(server !== undefined, 'WebAPI not connected');
|
||||||
|
|
||||||
await backupReady.promise;
|
// Wait for backup to be downloaded
|
||||||
|
try {
|
||||||
|
await backupReady.promise;
|
||||||
|
} catch (error) {
|
||||||
|
log.error('background: backup download failed, not reconnecting', error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
log.info('background: connect unblocked by backups');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
connectPromise = explodePromise();
|
connectPromise = explodePromise();
|
||||||
|
@ -3039,8 +3020,12 @@ export async function startApp(): Promise<void> {
|
||||||
log.info('unlinkAndDisconnect: logging out');
|
log.info('unlinkAndDisconnect: logging out');
|
||||||
strictAssert(server !== undefined, 'WebAPI not initialized');
|
strictAssert(server !== undefined, 'WebAPI not initialized');
|
||||||
server.unregisterRequestHandler(messageReceiver);
|
server.unregisterRequestHandler(messageReceiver);
|
||||||
|
StorageService.disableStorageService();
|
||||||
messageReceiver.stopProcessing();
|
messageReceiver.stopProcessing();
|
||||||
|
|
||||||
|
backupReady.reject(new Error('Aborted'));
|
||||||
|
backupReady = explodePromise();
|
||||||
|
|
||||||
await server.logout();
|
await server.logout();
|
||||||
await window.waitForAllBatchers();
|
await window.waitForAllBatchers();
|
||||||
}
|
}
|
||||||
|
|
|
@ -76,6 +76,25 @@ export class BackupsService {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async download(
|
||||||
|
options: Omit<DownloadOptionsType, 'downloadOffset'>
|
||||||
|
): Promise<void> {
|
||||||
|
const backupDownloadPath = window.storage.get('backupDownloadPath');
|
||||||
|
if (!backupDownloadPath) {
|
||||||
|
log.warn('backups.download: no backup download path, skipping');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const absoluteDownloadPath =
|
||||||
|
window.Signal.Migrations.getAbsoluteDownloadsPath(backupDownloadPath);
|
||||||
|
log.info('backups.download: downloading...');
|
||||||
|
const hasBackup = await this.doDownload(absoluteDownloadPath, options);
|
||||||
|
|
||||||
|
await window.storage.remove('backupDownloadPath');
|
||||||
|
|
||||||
|
log.info(`backups.download: done, had backup=${hasBackup}`);
|
||||||
|
}
|
||||||
|
|
||||||
public async upload(): Promise<void> {
|
public async upload(): Promise<void> {
|
||||||
const fileName = `backup-${randomBytes(32).toString('hex')}`;
|
const fileName = `backup-${randomBytes(32).toString('hex')}`;
|
||||||
const filePath = join(window.BasePaths.temp, fileName);
|
const filePath = join(window.BasePaths.temp, fileName);
|
||||||
|
@ -155,86 +174,6 @@ export class BackupsService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async download(
|
|
||||||
downloadPath: string,
|
|
||||||
{ onProgress }: Omit<DownloadOptionsType, 'downloadOffset'>
|
|
||||||
): Promise<boolean> {
|
|
||||||
const controller = new AbortController();
|
|
||||||
|
|
||||||
// Abort previous download
|
|
||||||
this.downloadController?.abort();
|
|
||||||
this.downloadController = controller;
|
|
||||||
|
|
||||||
let downloadOffset = 0;
|
|
||||||
try {
|
|
||||||
({ size: downloadOffset } = await stat(downloadPath));
|
|
||||||
} catch (error) {
|
|
||||||
if (error.code !== 'ENOENT') {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
|
|
||||||
// File is missing - start from the beginning
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await ensureFile(downloadPath);
|
|
||||||
|
|
||||||
if (controller.signal.aborted) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const stream = await this.api.download({
|
|
||||||
downloadOffset,
|
|
||||||
onProgress,
|
|
||||||
abortSignal: controller.signal,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (controller.signal.aborted) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
await pipeline(
|
|
||||||
stream,
|
|
||||||
createWriteStream(downloadPath, {
|
|
||||||
flags: 'a',
|
|
||||||
start: downloadOffset,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
if (controller.signal.aborted) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.downloadController = undefined;
|
|
||||||
|
|
||||||
// Too late to cancel now
|
|
||||||
try {
|
|
||||||
await this.importFromDisk(downloadPath);
|
|
||||||
} finally {
|
|
||||||
await unlink(downloadPath);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
// Download canceled
|
|
||||||
if (error.name === 'AbortError') {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// No backup on the server
|
|
||||||
if (error instanceof HTTPError && error.code === 404) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await unlink(downloadPath);
|
|
||||||
} catch {
|
|
||||||
// Best-effort
|
|
||||||
}
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async importBackup(
|
public async importBackup(
|
||||||
createBackupStream: () => Readable,
|
createBackupStream: () => Readable,
|
||||||
backupType = BackupType.Ciphertext
|
backupType = BackupType.Ciphertext
|
||||||
|
@ -357,6 +296,86 @@ export class BackupsService {
|
||||||
return { isInBackupTier: true, cdnNumber: storedInfo.cdnNumber };
|
return { isInBackupTier: true, cdnNumber: storedInfo.cdnNumber };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async doDownload(
|
||||||
|
downloadPath: string,
|
||||||
|
{ onProgress }: Omit<DownloadOptionsType, 'downloadOffset'>
|
||||||
|
): Promise<boolean> {
|
||||||
|
const controller = new AbortController();
|
||||||
|
|
||||||
|
// Abort previous download
|
||||||
|
this.downloadController?.abort();
|
||||||
|
this.downloadController = controller;
|
||||||
|
|
||||||
|
let downloadOffset = 0;
|
||||||
|
try {
|
||||||
|
({ size: downloadOffset } = await stat(downloadPath));
|
||||||
|
} catch (error) {
|
||||||
|
if (error.code !== 'ENOENT') {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
// File is missing - start from the beginning
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await ensureFile(downloadPath);
|
||||||
|
|
||||||
|
if (controller.signal.aborted) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const stream = await this.api.download({
|
||||||
|
downloadOffset,
|
||||||
|
onProgress,
|
||||||
|
abortSignal: controller.signal,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (controller.signal.aborted) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
await pipeline(
|
||||||
|
stream,
|
||||||
|
createWriteStream(downloadPath, {
|
||||||
|
flags: 'a',
|
||||||
|
start: downloadOffset,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
if (controller.signal.aborted) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.downloadController = undefined;
|
||||||
|
|
||||||
|
// Too late to cancel now
|
||||||
|
try {
|
||||||
|
await this.importFromDisk(downloadPath);
|
||||||
|
} finally {
|
||||||
|
await unlink(downloadPath);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// Download canceled
|
||||||
|
if (error.name === 'AbortError') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// No backup on the server
|
||||||
|
if (error instanceof HTTPError && error.code === 404) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await unlink(downloadPath);
|
||||||
|
} catch {
|
||||||
|
// Best-effort
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
private async exportBackup(
|
private async exportBackup(
|
||||||
sink: Writable,
|
sink: Writable,
|
||||||
backupLevel: BackupLevel = BackupLevel.Messages,
|
backupLevel: BackupLevel = BackupLevel.Messages,
|
||||||
|
|
|
@ -2048,6 +2048,10 @@ export function enableStorageService(): void {
|
||||||
storageServiceEnabled = true;
|
storageServiceEnabled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function disableStorageService(): void {
|
||||||
|
storageServiceEnabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
export async function eraseAllStorageServiceState({
|
export async function eraseAllStorageServiceState({
|
||||||
keepUnknownFields = false,
|
keepUnknownFields = false,
|
||||||
}: { keepUnknownFields?: boolean } = {}): Promise<void> {
|
}: { keepUnknownFields?: boolean } = {}): Promise<void> {
|
||||||
|
|
|
@ -952,6 +952,7 @@ export default class AccountManager extends EventTarget {
|
||||||
const numberChanged =
|
const numberChanged =
|
||||||
!previousACI && previousNumber && previousNumber !== number;
|
!previousACI && previousNumber && previousNumber !== number;
|
||||||
|
|
||||||
|
let cleanStart = !previousACI && !previousPNI && !previousNumber;
|
||||||
if (uuidChanged || numberChanged || backupFile !== undefined) {
|
if (uuidChanged || numberChanged || backupFile !== undefined) {
|
||||||
if (uuidChanged) {
|
if (uuidChanged) {
|
||||||
log.warn(
|
log.warn(
|
||||||
|
@ -973,6 +974,8 @@ export default class AccountManager extends EventTarget {
|
||||||
try {
|
try {
|
||||||
await storage.protocol.removeAllData();
|
await storage.protocol.removeAllData();
|
||||||
log.info('createAccount: Successfully deleted previous data');
|
log.info('createAccount: Successfully deleted previous data');
|
||||||
|
|
||||||
|
cleanStart = true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error(
|
log.error(
|
||||||
'Something went wrong deleting data from previous number',
|
'Something went wrong deleting data from previous number',
|
||||||
|
@ -1091,6 +1094,13 @@ export default class AccountManager extends EventTarget {
|
||||||
throw missingCaseError(options);
|
throw missingCaseError(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set backup download path before storing credentials to ensure that
|
||||||
|
// storage service and message receiver are not operating
|
||||||
|
// until the backup is downloaded and imported.
|
||||||
|
if (isBackupEnabled() && cleanStart) {
|
||||||
|
await storage.put('backupDownloadPath', getRelativePath(createName()));
|
||||||
|
}
|
||||||
|
|
||||||
// `setCredentials` needs to be called
|
// `setCredentials` needs to be called
|
||||||
// before `saveIdentifyWithAttributes` since `saveIdentityWithAttributes`
|
// before `saveIdentifyWithAttributes` since `saveIdentityWithAttributes`
|
||||||
// indirectly calls `ConversationController.getConversationId()` which
|
// indirectly calls `ConversationController.getConversationId()` which
|
||||||
|
@ -1163,9 +1173,6 @@ export default class AccountManager extends EventTarget {
|
||||||
|
|
||||||
const regionCode = getRegionCodeForNumber(number);
|
const regionCode = getRegionCodeForNumber(number);
|
||||||
await storage.put('regionCode', regionCode);
|
await storage.put('regionCode', regionCode);
|
||||||
if (isBackupEnabled()) {
|
|
||||||
await storage.put('backupDownloadPath', getRelativePath(createName()));
|
|
||||||
}
|
|
||||||
await storage.protocol.hydrateCaches();
|
await storage.protocol.hydrateCaches();
|
||||||
|
|
||||||
const store = storage.protocol;
|
const store = storage.protocol;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue