Add backup attachment restore idle state
This commit is contained in:
		
					parent
					
						
							
								56ccd02232
							
						
					
				
			
			
				commit
				
					
						8601baa7f5
					
				
			
		
					 13 changed files with 122 additions and 17 deletions
				
			
		| 
						 | 
				
			
			@ -4787,6 +4787,14 @@
 | 
			
		|||
    "messageformat": "Restore paused",
 | 
			
		||||
    "description": "Label indicating media (attachment) download progress has been paused (due to user interaction)"
 | 
			
		||||
  },
 | 
			
		||||
  "icu:BackupMediaDownloadProgress__title-idle": {
 | 
			
		||||
    "messageformat": "{currentSize} of {totalSize} restored",
 | 
			
		||||
    "description": "Label indicating media (attachment) download progress"
 | 
			
		||||
  },
 | 
			
		||||
  "icu:BackupMediaDownloadProgress__description-idle": {
 | 
			
		||||
    "messageformat": "Media will continue to download in the background",
 | 
			
		||||
    "description": "Description text when attachment download is idle, and will continue in the background"
 | 
			
		||||
  },
 | 
			
		||||
  "icu:BackupMediaDownloadProgress__button-pause": {
 | 
			
		||||
    "messageformat": "Pause transfer",
 | 
			
		||||
    "description": "Text for button to pause media (attachment) download after backup impor"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,7 +4,6 @@
 | 
			
		|||
.BackupMediaDownloadProgress {
 | 
			
		||||
  border-radius: 10px;
 | 
			
		||||
  display: flex;
 | 
			
		||||
  align-items: center;
 | 
			
		||||
  gap: 10px;
 | 
			
		||||
  padding: 11px;
 | 
			
		||||
  padding-inline-end: 16px;
 | 
			
		||||
| 
						 | 
				
			
			@ -26,6 +25,10 @@
 | 
			
		|||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.BackupMediaDownloadProgress__icon {
 | 
			
		||||
  margin-top: 6px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.BackupMediaDownloadProgress__icon--complete {
 | 
			
		||||
  &::after {
 | 
			
		||||
    content: '';
 | 
			
		||||
| 
						 | 
				
			
			@ -46,6 +49,27 @@
 | 
			
		|||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.BackupMediaDownloadProgress__icon--idle {
 | 
			
		||||
  &::after {
 | 
			
		||||
    content: '';
 | 
			
		||||
    display: block;
 | 
			
		||||
    width: 24px;
 | 
			
		||||
    height: 24px;
 | 
			
		||||
    @include light-theme {
 | 
			
		||||
      @include color-svg(
 | 
			
		||||
        '../images/icons/v3/backup/backup-bold.svg',
 | 
			
		||||
        $color-ultramarine
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
    @include dark-theme {
 | 
			
		||||
      @include color-svg(
 | 
			
		||||
        '../images/icons/v3/backup/backup-bold.svg',
 | 
			
		||||
        $color-ultramarine-light
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
button.BackupMediaDownloadProgress__button {
 | 
			
		||||
  @include button-reset;
 | 
			
		||||
  @include font-subtitle-bold;
 | 
			
		||||
| 
						 | 
				
			
			@ -86,7 +110,7 @@ button.BackupMediaDownloadProgress__button-close {
 | 
			
		|||
    width: 20px;
 | 
			
		||||
    height: 20px;
 | 
			
		||||
    @include light-theme {
 | 
			
		||||
      @include color-svg('../images/icons/v3/x/x.svg', $color-gray-75);
 | 
			
		||||
      @include color-svg('../images/icons/v3/x/x.svg', $color-gray-45);
 | 
			
		||||
    }
 | 
			
		||||
    @include dark-theme {
 | 
			
		||||
      @include color-svg('../images/icons/v3/x/x.svg', $color-gray-20);
 | 
			
		||||
| 
						 | 
				
			
			@ -102,7 +126,7 @@ button.BackupMediaDownloadProgress__button-close {
 | 
			
		|||
  min-height: 36px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.BackupMediaDownloadProgress__progressbar-hint {
 | 
			
		||||
.BackupMediaDownloadProgress__description {
 | 
			
		||||
  @include font-subtitle;
 | 
			
		||||
 | 
			
		||||
  @include light-theme {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -45,6 +45,14 @@ export function Paused(args: PropsType): JSX.Element {
 | 
			
		|||
  return <BackupMediaDownloadProgress {...args} isPaused />;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function Idle(args: PropsType): JSX.Element {
 | 
			
		||||
  return <BackupMediaDownloadProgress {...args} isIdle />;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function PausedAndIdle(args: PropsType): JSX.Element {
 | 
			
		||||
  return <BackupMediaDownloadProgress {...args} isPaused isIdle />;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function Complete(args: PropsType): JSX.Element {
 | 
			
		||||
  return (
 | 
			
		||||
    <BackupMediaDownloadProgress {...args} downloadedBytes={args.totalBytes} />
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -14,6 +14,7 @@ export type PropsType = Readonly<{
 | 
			
		|||
  i18n: LocalizerType;
 | 
			
		||||
  downloadedBytes: number;
 | 
			
		||||
  totalBytes: number;
 | 
			
		||||
  isIdle: boolean;
 | 
			
		||||
  isPaused: boolean;
 | 
			
		||||
  handleCancel: VoidFunction;
 | 
			
		||||
  handleClose: VoidFunction;
 | 
			
		||||
| 
						 | 
				
			
			@ -25,6 +26,7 @@ export function BackupMediaDownloadProgress({
 | 
			
		|||
  i18n,
 | 
			
		||||
  downloadedBytes,
 | 
			
		||||
  totalBytes,
 | 
			
		||||
  isIdle,
 | 
			
		||||
  isPaused,
 | 
			
		||||
  handleCancel: handleConfirmedCancel,
 | 
			
		||||
  handleClose,
 | 
			
		||||
| 
						 | 
				
			
			@ -45,31 +47,57 @@ export function BackupMediaDownloadProgress({
 | 
			
		|||
    downloadedBytes / totalBytes
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  const closeButton = (
 | 
			
		||||
    <button
 | 
			
		||||
      type="button"
 | 
			
		||||
      onClick={handleClose}
 | 
			
		||||
      className="BackupMediaDownloadProgress__button-close"
 | 
			
		||||
      aria-label={i18n('icu:close')}
 | 
			
		||||
    />
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  let content: JSX.Element | undefined;
 | 
			
		||||
  let icon: JSX.Element | undefined;
 | 
			
		||||
  let actionButton: JSX.Element | undefined;
 | 
			
		||||
  if (fractionComplete === 1) {
 | 
			
		||||
    icon = <div className="BackupMediaDownloadProgress__icon--complete" />;
 | 
			
		||||
    icon = (
 | 
			
		||||
      <div className="BackupMediaDownloadProgress__icon BackupMediaDownloadProgress__icon--complete" />
 | 
			
		||||
    );
 | 
			
		||||
    content = (
 | 
			
		||||
      <>
 | 
			
		||||
        <div className="BackupMediaDownloadProgress__title">
 | 
			
		||||
          {i18n('icu:BackupMediaDownloadProgress__title-complete')}
 | 
			
		||||
        </div>
 | 
			
		||||
        <div className="BackupMediaDownloadProgress__progressbar-hint">
 | 
			
		||||
        <div className="BackupMediaDownloadProgress__description">
 | 
			
		||||
          {formatFileSize(downloadedBytes)}
 | 
			
		||||
        </div>
 | 
			
		||||
      </>
 | 
			
		||||
    );
 | 
			
		||||
    actionButton = (
 | 
			
		||||
      <button
 | 
			
		||||
        type="button"
 | 
			
		||||
        onClick={handleClose}
 | 
			
		||||
        className="BackupMediaDownloadProgress__button-close"
 | 
			
		||||
        aria-label={i18n('icu:close')}
 | 
			
		||||
      />
 | 
			
		||||
    actionButton = closeButton;
 | 
			
		||||
  } else if (isIdle && !isPaused) {
 | 
			
		||||
    icon = (
 | 
			
		||||
      <div className="BackupMediaDownloadProgress__icon BackupMediaDownloadProgress__icon--idle" />
 | 
			
		||||
    );
 | 
			
		||||
    content = (
 | 
			
		||||
      <>
 | 
			
		||||
        <div className="BackupMediaDownloadProgress__title">
 | 
			
		||||
          {i18n('icu:BackupMediaDownloadProgress__title-idle', {
 | 
			
		||||
            currentSize: formatFileSize(downloadedBytes),
 | 
			
		||||
            totalSize: formatFileSize(totalBytes),
 | 
			
		||||
          })}
 | 
			
		||||
        </div>
 | 
			
		||||
        <div className="BackupMediaDownloadProgress__description">
 | 
			
		||||
          {i18n('icu:BackupMediaDownloadProgress__description-idle')}
 | 
			
		||||
        </div>
 | 
			
		||||
      </>
 | 
			
		||||
    );
 | 
			
		||||
    actionButton = closeButton;
 | 
			
		||||
  } else {
 | 
			
		||||
    icon = <ProgressCircle fractionComplete={fractionComplete} />;
 | 
			
		||||
    icon = (
 | 
			
		||||
      <div className="BackupMediaDownloadProgress__icon">
 | 
			
		||||
        <ProgressCircle fractionComplete={fractionComplete} />
 | 
			
		||||
      </div>
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    if (isPaused) {
 | 
			
		||||
      content = (
 | 
			
		||||
| 
						 | 
				
			
			@ -94,7 +122,7 @@ export function BackupMediaDownloadProgress({
 | 
			
		|||
            {i18n('icu:BackupMediaDownloadProgress__title-in-progress')}
 | 
			
		||||
          </div>
 | 
			
		||||
 | 
			
		||||
          <div className="BackupMediaDownloadProgress__progressbar-hint">
 | 
			
		||||
          <div className="BackupMediaDownloadProgress__description">
 | 
			
		||||
            {i18n('icu:BackupMediaDownloadProgress__progressbar-hint', {
 | 
			
		||||
              currentSize: formatFileSize(downloadedBytes),
 | 
			
		||||
              totalSize: formatFileSize(totalBytes),
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -139,6 +139,7 @@ const useProps = (overrideProps: OverridePropsType = {}): PropsType => {
 | 
			
		|||
    },
 | 
			
		||||
    backupMediaDownloadProgress: {
 | 
			
		||||
      downloadBannerDismissed: false,
 | 
			
		||||
      isIdle: false,
 | 
			
		||||
      isPaused: false,
 | 
			
		||||
      totalBytes: 0,
 | 
			
		||||
      downloadedBytes: 0,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -62,6 +62,7 @@ export type PropsType = {
 | 
			
		|||
  backupMediaDownloadProgress: {
 | 
			
		||||
    totalBytes: number;
 | 
			
		||||
    downloadedBytes: number;
 | 
			
		||||
    isIdle: boolean;
 | 
			
		||||
    isPaused: boolean;
 | 
			
		||||
    downloadBannerDismissed: boolean;
 | 
			
		||||
  };
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -22,6 +22,7 @@ import {
 | 
			
		|||
  AttachmentSizeError,
 | 
			
		||||
  type AttachmentType,
 | 
			
		||||
  AttachmentVariant,
 | 
			
		||||
  mightBeOnBackupTier,
 | 
			
		||||
} from '../types/Attachment';
 | 
			
		||||
import { __DEPRECATED$getMessageById } from '../messages/getMessageById';
 | 
			
		||||
import {
 | 
			
		||||
| 
						 | 
				
			
			@ -185,7 +186,14 @@ export class AttachmentDownloadManager extends JobManager<CoreAttachmentDownload
 | 
			
		|||
      size: attachment.size,
 | 
			
		||||
      ciphertextSize: getAttachmentCiphertextLength(attachment.size),
 | 
			
		||||
      attachment,
 | 
			
		||||
      source,
 | 
			
		||||
      // If the attachment does not have a backupLocator, we don't want to store it as a
 | 
			
		||||
      // "backup import" attachment, since it's really just a normal attachment that we'll
 | 
			
		||||
      // try to download from the transit tier (or it's an invalid attachment, etc.). We
 | 
			
		||||
      // may need to extend the attachment_downloads table in the future to better
 | 
			
		||||
      // differentiate source vs. location.
 | 
			
		||||
      source: mightBeOnBackupTier(attachment)
 | 
			
		||||
        ? source
 | 
			
		||||
        : AttachmentDownloadSource.STANDARD,
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    if (!parseResult.success) {
 | 
			
		||||
| 
						 | 
				
			
			@ -237,6 +245,13 @@ export class AttachmentDownloadManager extends JobManager<CoreAttachmentDownload
 | 
			
		|||
      messageIds
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static async waitForIdle(callback?: VoidFunction): Promise<void> {
 | 
			
		||||
    await AttachmentDownloadManager.instance.waitForIdle();
 | 
			
		||||
    if (callback) {
 | 
			
		||||
      callback();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type DependenciesType = {
 | 
			
		||||
| 
						 | 
				
			
			@ -284,7 +299,7 @@ async function runDownloadAttachmentJob({
 | 
			
		|||
      };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (job.attachment.backupLocator?.mediaName) {
 | 
			
		||||
    if (mightBeOnBackupTier(job.attachment)) {
 | 
			
		||||
      const currentDownloadedSize =
 | 
			
		||||
        window.storage.get('backupMediaDownloadCompletedBytes') ?? 0;
 | 
			
		||||
      drop(
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -88,6 +88,7 @@ export abstract class JobManager<CoreJobType> {
 | 
			
		|||
 | 
			
		||||
    this.enabled = true;
 | 
			
		||||
    await this.params.markAllJobsInactive();
 | 
			
		||||
    await this.maybeStartJobs();
 | 
			
		||||
    this.tick();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -241,7 +242,7 @@ export abstract class JobManager<CoreJobType> {
 | 
			
		|||
        timestamp: Date.now(),
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      if (nextJobs.length === 0) {
 | 
			
		||||
      if (nextJobs.length === 0 && this.activeJobs.size === 0) {
 | 
			
		||||
        if (this.idleCallbacks.length > 0) {
 | 
			
		||||
          const callbacks = this.idleCallbacks;
 | 
			
		||||
          this.idleCallbacks = [];
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -109,6 +109,7 @@ import { getRoomIdFromRootKey } from '../../util/callLinksRingrtc';
 | 
			
		|||
import { loadAllAndReinitializeRedux } from '../allLoaders';
 | 
			
		||||
import { resetBackupMediaDownloadProgress } from '../../util/backupMediaDownload';
 | 
			
		||||
import { getEnvironment, isTestEnvironment } from '../../environment';
 | 
			
		||||
import { drop } from '../../util/drop';
 | 
			
		||||
 | 
			
		||||
const MAX_CONCURRENCY = 10;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -339,6 +340,11 @@ export class BackupImportStream extends Writable {
 | 
			
		|||
        !isTestEnvironment(getEnvironment())
 | 
			
		||||
      ) {
 | 
			
		||||
        await AttachmentDownloadManager.start();
 | 
			
		||||
        drop(
 | 
			
		||||
          AttachmentDownloadManager.waitForIdle(async () => {
 | 
			
		||||
            await window.storage.put('backupMediaDownloadIdle', true);
 | 
			
		||||
          })
 | 
			
		||||
        );
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      done();
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -258,10 +258,12 @@ export const getBackupMediaDownloadProgress = createSelector(
 | 
			
		|||
    downloadedBytes: number;
 | 
			
		||||
    isPaused: boolean;
 | 
			
		||||
    downloadBannerDismissed: boolean;
 | 
			
		||||
    isIdle: boolean;
 | 
			
		||||
  } => ({
 | 
			
		||||
    totalBytes: state.backupMediaDownloadTotalBytes ?? 0,
 | 
			
		||||
    downloadedBytes: state.backupMediaDownloadCompletedBytes ?? 0,
 | 
			
		||||
    isPaused: state.backupMediaDownloadPaused ?? false,
 | 
			
		||||
    isIdle: state.backupMediaDownloadIdle ?? false,
 | 
			
		||||
    downloadBannerDismissed: state.backupMediaDownloadBannerDismissed ?? false,
 | 
			
		||||
  })
 | 
			
		||||
);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -409,6 +409,15 @@ describe('AttachmentDownloadManager/JobManager', () => {
 | 
			
		|||
        idx % 2 === 0
 | 
			
		||||
          ? AttachmentDownloadSource.BACKUP_IMPORT
 | 
			
		||||
          : AttachmentDownloadSource.STANDARD,
 | 
			
		||||
      digest: `digestFor${idx}`,
 | 
			
		||||
      attachment: {
 | 
			
		||||
        contentType: MIME.IMAGE_JPEG,
 | 
			
		||||
        size: 128,
 | 
			
		||||
        digest: `digestFor${idx}`,
 | 
			
		||||
        backupLocator: {
 | 
			
		||||
          mediaName: 'medianame',
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
    }));
 | 
			
		||||
    // make one of the backup job messages visible to test that code path as well
 | 
			
		||||
    downloadManager?.updateVisibleTimelineMessages(['message-0', 'message-1']);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										1
									
								
								ts/types/Storage.d.ts
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								ts/types/Storage.d.ts
									
										
									
									
										vendored
									
									
								
							| 
						 | 
				
			
			@ -147,6 +147,7 @@ export type StorageAccessType = {
 | 
			
		|||
  backupMediaDownloadCompletedBytes: number;
 | 
			
		||||
  backupMediaDownloadPaused: boolean;
 | 
			
		||||
  backupMediaDownloadBannerDismissed: boolean;
 | 
			
		||||
  backupMediaDownloadIdle: boolean;
 | 
			
		||||
  setBackupSignatureKey: boolean;
 | 
			
		||||
  lastReceivedAtCounter: number;
 | 
			
		||||
  preferredReactionEmoji: ReadonlyArray<string>;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -17,6 +17,7 @@ export async function resetBackupMediaDownloadItems(): Promise<void> {
 | 
			
		|||
    window.storage.remove('backupMediaDownloadCompletedBytes'),
 | 
			
		||||
    window.storage.remove('backupMediaDownloadBannerDismissed'),
 | 
			
		||||
    window.storage.remove('backupMediaDownloadPaused'),
 | 
			
		||||
    window.storage.remove('backupMediaDownloadIdle'),
 | 
			
		||||
  ]);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue