Skip to content

feat(backup): enhance backup progress tracking and state management#280

Open
egalvis27 wants to merge 2 commits intomainfrom
fix/backups-progress-bar
Open

feat(backup): enhance backup progress tracking and state management#280
egalvis27 wants to merge 2 commits intomainfrom
fix/backups-progress-bar

Conversation

@egalvis27
Copy link

@egalvis27 egalvis27 commented Mar 20, 2026

What is Changed / Added


We've refactored the backup progress tracking system to provide a linear, consistent progress display, eliminating unexpected jumps and visual inconsistencies. Previously, each folder added to the backup was tracked independently, causing progress bar fluctuations and apparent setbacks. The new implementation treats the backup as a unified process with weighted progress calculation, significantly improving the user experience when monitoring backup operations.

@sonarqubecloud
Copy link

Quality Gate Failed Quality Gate failed

Failed conditions
69.5% Coverage on New Code (required ≥ 80%)

See analysis details on SonarQube Cloud

const backups = await backupsConfig.obtainBackupsInfo();
const container = await BackupsDependencyContainerFactory.build();
const backupService = container.get(BackupService);
try {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this needs to be wrapped inside a try?, the idea is to reduce the amount of try catch to the max and wrap and handle where we know 100% that the exception will be thrown, this way we reduce complexity

errors: BackupErrorsTracker,
signal: AbortSignal,
): Promise<void> {
) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think its better to keep the funtion signature

Comment on lines +30 to +42
const itemCounts = new Map<string, number>();

for (const backup of backups) {
if (signal.aborted) {
logger.debug({ tag: 'BACKUPS', msg: 'Precalculation aborted' });
break;
}

const count = await precalculateBackupItemCount(backup, container);
itemCounts.set(backup.folderUuid, count);
}

// eslint-disable-next-line no-await-in-loop
const result = await backupService.runWithRetry(backupInfo, signal, tracker);
if (result.isLeft()) {
const error = result.getLeft();
logger.debug({ tag: 'BACKUPS', msg: 'failed', error: error.cause });
// TODO: Make retryError extend DriveDesktopError to avoid this check
if (error instanceof DriveDesktopError && 'cause' in error && error.cause && isSyncError(error.cause)) {
errors.add(backupInfo.folderId, { name: backupInfo.name, error: error.cause });
const backupIds = backups.map((b) => b.folderUuid);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be its own function

}));

describe('BackupProgressTracker', () => {
describe('BackupProgressTracker - Functional approach', () => {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I dont think this comment adds something meaningful

this.processedItems = 0;
this.totalItems = 0;
initializeWeights(backupIds: string[], fileCounts: ReadonlyMap<string, number>): void {
this.state = initializeWeights(this.state, backupIds, fileCounts);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method could be renamed to something more meaningful, right? such as initializeBackupProgressWeights

Comment on lines +10 to +13
export const precalculateBackupItemCount = async (backupInfo: BackupInfo, container: Container) => {
try {
const localTreeBuilder = container.get(LocalTreeBuilder);
const remoteTreeBuilder = container.get(RemoteTreeBuilder);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

its better if you use function instead
also, its better if you pass down laready the LocalTreebuild and the remoteTreeBuilder as param instead of the container, this way its one resposability less that the function has

import { Container } from 'diod';

export const precalculateBackupItemCount = async (backupInfo: BackupInfo, container: Container) => {
try {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The same as i comented before, is it better if we just narrow down the tryCatch to where we 100% now it can throw, so that we arent constantly wrapping everything in tryCatch, and return a result type there, so that we know that the function has a potential error and must be handled

const localTreeBuilder = container.get(LocalTreeBuilder);
const remoteTreeBuilder = container.get(RemoteTreeBuilder);

const localTreeEither = await localTreeBuilder.run(backupInfo.pathname as AbsolutePath);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we are 100% sure that this is the case, then maybe we should change completly the property to AbsolutePath instead of string

batch: Array<LocalFile>,
signal: AbortSignal,
onFileProcessed: () => void,
): Promise<void> {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is going to be deleted on #283
maybe we can replace the callback for a return on the equivalent

@@ -22,6 +22,7 @@ export class FileBatchUploader {
remoteTree: RemoteTree,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same, this is going to be deleted in #283

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants