Skip to content

Commit

Permalink
feat: fetch file name (#2)
Browse files Browse the repository at this point in the history
feat: fetch file name
  • Loading branch information
ido-pluto authored Nov 27, 2023
1 parent c1616a0 commit cdd3788
Show file tree
Hide file tree
Showing 6 changed files with 110 additions and 16 deletions.
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ Arguments:
Options:
-V, --version output the version number
-s --save [path] Save directory
-s --save [path] Save location (directory/file)
-f --full-name Show full name of the file while downloading, even if it long
-h, --help display help for command
Expand Down Expand Up @@ -82,13 +82,13 @@ interface IStreamProgress {


class FastDownload implements IStreamProgress {
constructor(url: string, savePath: string, options?: TurboDownloaderOptions) {
}
constructor(url: string, savePath: string, options?: TurboDownloaderOptions);

static async fetchFilename(url: string);
}

class CopyProgress implements IStreamProgress {
constructor(fromPath: string, toPath: string) {
}
constructor(fromPath: string, toPath: string);
}
```

Expand Down
38 changes: 38 additions & 0 deletions package-lock.json

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

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
"@commitlint/config-conventional": "^17.7.0",
"@semantic-release/exec": "^6.0.3",
"@types/cli-progress": "^3.11.0",
"@types/content-disposition": "^0.5.8",
"@types/fs-extra": "^11.0.1",
"@types/node": "^20.4.9",
"@types/progress-stream": "^2.0.2",
Expand All @@ -80,6 +81,7 @@
"chalk": "^5.3.0",
"cli-progress": "^3.12.0",
"commander": "^10.0.0",
"content-disposition": "^0.5.4",
"execa": "^7.2.0",
"fs-extra": "^11.1.1",
"level": "^8.0.0",
Expand Down
26 changes: 21 additions & 5 deletions src/cli/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,41 @@ import {Command} from "commander";
import {packageJson} from "../const.js";
import pullFileCLI from "../download/index.js";
import {truncateText} from "../utils/truncate-text.js";
import findDownloadDir from "./utils/find-download-dir.js";
import {FastDownload} from "../index.js";
import findDownloadDir, {downloadToDirectory} from "./utils/find-download-dir.js";
import {setCommand} from "./commands/set.js";


const pullCommand = new Command();
pullCommand
.version(packageJson.version)
.description("Pull/copy files from remote server/local directory")
.argument("[files...]", "Files to pull/copy")
.option("-s --save [path]", "Save directory")
.option("-s --save [path]", "Save location (directory/file)")
.option("-f --full-name", "Show full name of the file while downloading, even if it long")
.action(async (files = [], {save, fullName}: { save?: string, fullName?: boolean }) => {
.action(async (files: string[] = [], {save, fullName}: { save?: string, fullName?: boolean }) => {
let specificFileName: null | string = null;

if (files.length === 0) {
pullCommand.outputHelp();
process.exit(0);
}

if (save && !await downloadToDirectory(save)) {
specificFileName = path.basename(save);
save = path.dirname(save);
}

const pullLogs: string[] = [];
for (const file of files) {
const fileName = path.basename(file);
for (const [index, file ] of Object.entries(files)) {
let fileName = path.basename(file);

if (specificFileName) {
fileName = files.length > 1 ? specificFileName + index : specificFileName;
} else if (file.startsWith("http")) {
fileName = await FastDownload.fetchFilename(file);
}

const downloadTag = fullName ? fileName : truncateText(fileName);
const downloadPath = path.join(save || await findDownloadDir(fileName), fileName);
const fileFullPath = new URL(file, pathToFileURL(process.cwd()));
Expand Down
10 changes: 10 additions & 0 deletions src/cli/utils/find-download-dir.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import path from "path";
import fs from "fs-extra";
import {getWithDefault} from "../../settings/settings.js";

const DEFAULT_DOWNLOAD_DIR = process.cwd();
Expand All @@ -8,3 +9,12 @@ export default async function findDownloadDir(fileName: string) {
const defaultLocation = await getWithDefault("default");
return downloadLocation || defaultLocation || DEFAULT_DOWNLOAD_DIR;
}

export async function downloadToDirectory(path: string) {
try {
const stats = await fs.lstat(path);
return stats.isDirectory();
} catch {
return false;
}
}
40 changes: 34 additions & 6 deletions src/download/stream-progress/fast-download.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,24 @@
import TurboDownloader, {TurboDownloaderOptions} from "turbo-downloader";
import wretch from "wretch";
import fs from "fs-extra";
import contentDisposition from "content-disposition";
import {IStreamProgress} from "./istream-progress.js";

const DEFAULT_FILE_NAME = "file";

export default class FastDownload implements IStreamProgress {
private _downloader?: TurboDownloader.default;
private _redirectedURL?: string;

constructor(private _url: string, private _savePath: string, private _options?: Partial<TurboDownloaderOptions>) {
constructor(private _url: string, private _savePath: string, private _options?: Partial<TurboDownloaderOptions>) {
}

public async init() {
await this._fetchFileInfo();
await fs.ensureFile(this._savePath);

this._downloader = new FastDownload._TurboDownloaderClass({
url: await this._getRedirectedURL(),
url: this._redirectedURL!,
destFile: this._savePath,
chunkSize: 50 * 1024 * 1024,
concurrency: 8,
Expand All @@ -23,24 +27,48 @@ export default class FastDownload implements IStreamProgress {
});
}

private async _getRedirectedURL() {
private async _fetchFileInfo() {
const {url} = await wretch(this._url)
.head()
.res()
.catch(error => {
throw new Error(`Error while getting file head: ${error.status}`);
});
return this._redirectedURL = url;

this._redirectedURL = url;


}

public async progress(callback: (progressBytes: number, totalBytes: number) => void) {
if (!this._downloader)
throw new Error("Downloader is not initialized");
if (!this._downloader) throw new Error("Downloader is not initialized");
await (this._downloader as any).download(callback);
}

private static get _TurboDownloaderClass(): typeof TurboDownloader.default {
if (TurboDownloader && "default" in TurboDownloader) return TurboDownloader.default;
return TurboDownloader;
}

/**
* Fetches filename from `content-disposition` header. If it's not present, extract it from the `pathname` of the url
* @param {string} url
*/
public static async fetchFilename(url: string) {
const contentDispositionHeader = await wretch(url)
.head()
.res(response => response.headers.get("content-disposition"))
.catch(error => {
throw new Error(`Error while getting file head: ${error.status}`);
});

const parsed = new URL(url);
const defaultFilename = decodeURIComponent(parsed.pathname.split("/").pop() ?? DEFAULT_FILE_NAME);

if (!contentDispositionHeader)
return defaultFilename;

const {parameters} = contentDisposition.parse(contentDispositionHeader);
return parameters.filename ?? defaultFilename;
}
}

0 comments on commit cdd3788

Please sign in to comment.