Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use progress bar to show the progress of copying files #13

Merged
merged 1 commit into from
Nov 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 50 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"@tsconfig/node20": "^20.1.2",
"@types/chai": "^4.3.9",
"@types/chai-as-promised": "^7.1.8",
"@types/cli-progress": "^3.11.5",
"@types/jest": "^29.5.8",
"@types/node": "^20.8.10",
"@typescript-eslint/eslint-plugin": "^6.9.1",
Expand All @@ -47,5 +48,8 @@
},
"lint-staged": {
"**/*": "prettier --write --ignore-unknown"
},
"dependencies": {
"cli-progress": "^3.12.0"
}
}
36 changes: 22 additions & 14 deletions src/downloader/downloader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,16 @@ import {
} from "./configuration";
import { FileCopier } from "./file-copier";
import { GroupByDatePlacementStrategy, TargetPlacementStrategy } from "./placement-strategy";
import { ProgressTracker } from "./progress";

export class Downloader {
private readonly configurationPromise: Promise<DriveDownloadConfiguration> = readAutoDownloadConfiguration();

private readonly progressTracker = new ProgressTracker();
private readonly fileCopier: FileCopier = new FileCopier({
async onTargetAlreadyExists(sourceFile, targetFile) {
console.warn(
"%s already exists in %s: skip copying %s",
path.basename(targetFile),
path.dirname(targetFile),
sourceFile
onTargetAlreadyExists: async (sourceFile, targetFile) => {
this.progressTracker.log(
`${path.basename(targetFile)} already exists in ${path.dirname(targetFile)}: skip copying ${sourceFile}`
);
return { action: "skip" };
},
Expand Down Expand Up @@ -47,6 +47,8 @@ export class Downloader {
// to avoid having multiple 'threads' reading and writing from/to the same physical device.
await this.downloadNewFilesFromDir(sourceDir, dirDownloadConfig);
}

this.progressTracker.stop();
} else {
console.warn("[%s] Could not find any supported directories: do nothing", drivePath);
}
Expand All @@ -55,9 +57,8 @@ export class Downloader {
private async downloadNewFilesFromDir(sourceDir: string, configuration: DirectoryDownloadConfig) {
const cursor = await readDirectoryCursor(sourceDir);
if (!cursor) {
console.warn(
"[%s] Cursor file not found: this appears to be the first time we are processing this directory",
sourceDir
this.progressTracker.log(
`[${sourceDir}] Cursor file not found: this appears to be the first time we are processing this directory`
);
}

Expand All @@ -67,27 +68,34 @@ export class Downloader {
});

if (newFiles.length > 0) {
console.info("[%s] Found %i new files in total (in this source directory)", sourceDir, newFiles.length);
this.progressTracker.log(
`[${sourceDir}] Found ${newFiles.length} new files in total (in this source directory)`
);
const progressBar = this.progressTracker.startTracking(sourceDir, newFiles.length);

const placementStrategy = this.resolveTargetPlacementStrategy(configuration.target);
for (const fileRelativePath of newFiles) {
const targetFilePath = await placementStrategy.resolveTargetPath(`${sourceDir}/${fileRelativePath}`);
console.log("[%s] Copy %s to %s", sourceDir, fileRelativePath, targetFilePath);

progressBar.setStatusSummary(`${fileRelativePath} => ${targetFilePath}`);
await this.fileCopier.copy(`${sourceDir}/${fileRelativePath}`, targetFilePath);
progressBar.increment();
}

const lastProcessedFile = newFiles[newFiles.length - 1];
console.info("[%s] Save final cursor position: %s", sourceDir, lastProcessedFile);
progressBar.setStatusSummary(`Saving final cursor position: ${lastProcessedFile}`);

saveDirectoryCursor(sourceDir, {
await saveDirectoryCursor(sourceDir, {
...cursor,
sequential: {
...cursor?.sequential,
lastProcessedFile,
},
});
progressBar.setStatusSummary(`Saved final cursor position: ${lastProcessedFile}`);
progressBar.stop();
} else {
console.info("[%s] No new files found: do nothing", sourceDir);
this.progressTracker.log(`[${sourceDir}] No new files found`);
}
}

Expand Down
63 changes: 63 additions & 0 deletions src/downloader/progress.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { MultiBar, Presets, SingleBar } from "cli-progress";

export class ProgressTracker {
private readonly multiBar: MultiBar;

constructor() {
this.multiBar = new MultiBar(
{
format: "[{sourceDir}] Downloading new files {bar} {percentage}% | {value}/{total} | Speed: {speed} | {summary}",
barCompleteChar: "\u2588",
barIncompleteChar: "\u2591",
hideCursor: true,
},
Presets.shades_grey
);
}

/**
* Start tracking progress of copying a new batch of files.
* @param sourceDir
* @param total total number of new files in this batch
* @returns new single-line progress bar
*/
public startTracking(sourceDir: string, total: number): SingleDirBar {
return new SingleDirBar(this.multiBar.create(total, 0, { sourceDir, speed: "N/A" }));
}

/**
* Log a message above all progress bars.
* @param message
*/
public log(message: string) {
this.multiBar.log(`${message}\n`);
}

public stop() {
this.multiBar.stop();
}
}

/**
* Tracks the progress of copying a given batch of files.
*/
export class SingleDirBar {
constructor(private readonly progressBar: SingleBar) {}

/**
* Set short summary of the current state of the overall operation progress
* (shown to the right of the progress bar).
* @param summary
*/
public setStatusSummary(summary: string) {
this.progressBar.increment(0, { summary });
}

public increment() {
return this.progressBar.increment();
}

public stop() {
this.progressBar.stop();
}
}