Skip to content

Commit

Permalink
publish to jsr
Browse files Browse the repository at this point in the history
  • Loading branch information
zhangfuxing committed Nov 18, 2024
1 parent e18ddc9 commit c6c0fd3
Show file tree
Hide file tree
Showing 12 changed files with 301 additions and 166 deletions.
19 changes: 19 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
name: Publish
on:
push:
branches:
- master

jobs:
publish:
runs-on: ubuntu-latest

permissions:
contents: read
id-token: write

steps:
- uses: actions/checkout@v4

- name: Publish package
run: npx jsr publish
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2020 zfx
Copyright (c) 2020 Deno Library

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
37 changes: 28 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
# unrar

[![JSR Version](https://jsr.io/badges/@deno-library/unrar)](https://jsr.io/@deno-library/unrar)

uncompress rar file for deno

## Useage
* upcompress all
```js
// Simply get an instance of UnrarAll
import unrar from "https://deno.land/x/[email protected]/mod.ts";
import unrar from "jsr:@deno-library/unrar" // or "https://deno.land/x/[email protected]/mod.ts";
// Equal to:
import { UnrarAll } from "https://deno.land/x/unrar@v1.0.1/mod.ts";
import { UnrarAll } from "jsr:@deno-library/unrar"
const unrar = new UnrarAll();
// If you do not want to use the default bin (the default bin only supports Windows)
import { UnrarAll } from "https://deno.land/x/unrar@v1.0.1/mod.ts";
import { UnrarAll } from "jsr:@deno-library/unrar"
const unrar = new UnrarAll(bin: "/x/.../UnRAR.exe");

const src = './test/password.rar';
Expand All @@ -36,7 +39,7 @@ const switches = ['-o+', '-idcd'];
* uncompress part
more exmaple in test folder
```ts
import { Unrar } from "https://deno.land/x/unrar@v1.0.1/mod.ts";
import { Unrar } from "jsr:@deno-library/unrar"
const src = './test/test.rar';
const dest = './test';
const uncompressedFile = './test/test2.txt';
Expand Down Expand Up @@ -98,19 +101,35 @@ export default new UnrarAll();

* uncompress part
```ts
interface constuctorOptions {
/**
* Options for the Unrar constructor.
*/
interface ConstructorOptions {
/**
* Path to the UnRAR executable.
*/
bin?: string;
/**
* Password for the RAR archive.
*/
password?: string;
}

interface uncompressOptions {
newName?: string
/**
* Options for the uncompress method.
*/
interface UncompressOptions {
/**
* New name for the extracted file.
*/
newName?: string;
}


interface Unrar {
constructor(filepath: string, options?: constuctorOptions);
constructor(filepath: string, options?: ConstructorOptions);
async list(): Promise<FileInfo[]>;
async uncompress(fileInfo: FileInfo, destDir: string, options?: uncompressOptions): Promise<void>;
async uncompress(fileInfo: FileInfo, destDir: string, options?: UncompressOptions): Promise<void>;
on(event: "progress", listener: (percent: string) => void): this;
}
```
Expand Down
5 changes: 5 additions & 0 deletions deno.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"name": "@deno-library/template",
"version": "1.0.0",
"exports": "./mod.ts"
}
40 changes: 40 additions & 0 deletions deno.lock

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

4 changes: 3 additions & 1 deletion deps.ts
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
export { default as EventEmitter } from "https://deno.land/x/events/mod.ts";
export { EventEmitter } from "node:events";
export { exists } from "jsr:@std/[email protected]";
export { writeAll } from "jsr:@std/[email protected]";
13 changes: 0 additions & 13 deletions fs.ts

This file was deleted.

140 changes: 72 additions & 68 deletions mod.ts
Original file line number Diff line number Diff line change
@@ -1,42 +1,74 @@
import { EventEmitter } from "./deps.ts";
import { exists } from "./fs.ts";
import { exists } from "./deps.ts";
export * from "./unrar.ts";

const reg_progress = /([\d]+)%/;
const reg_password = /^\r\nEnter password \(will not be echoed\)/;
// const reg_password = /^\r\nEnter password $will not be echoed$/;
const password_incorrect = "The specified password is incorrect";
const notRAR = "is not RAR archive";

/**
* Interface for additional options when uncompressing a RAR file.
*/
interface Options {
/** The command to use with the unrar tool, defaults to 'x' for extraction. */
command?: string;
/** An array of additional switches to pass to the unrar command, defaults to an empty array. */
switches?: string[];
}

/**
* A type representing a progress event listener function.
* @param percent - The current progress percentage as a string.
*/
interface Listener {
(percent: string): void;
}

/**
* Defines the methods and events that an object must implement to be considered an UnrarInterface.
*/
export interface UnrarInterface {
/**
* Uncompresses a RAR file to a destination directory.
* @param src - The source RAR file path.
* @param dest - The destination directory path where files will be extracted.
* @param options - Optional settings for the uncompression process.
* @returns A promise that resolves when the operation is complete.
*/
uncompress(src: string, dest: string, options: Options): Promise<void>;

/**
* Adds a listener for the 'progress' event.
* @param event - The name of the event, which should always be 'progress'.
* @param listener - The function to call when the 'progress' event is emitted.
* @returns The instance itself for chaining calls.
*/
on(event: "progress", listener: Listener): this;
}

/**
* A class implementing the UnrarInterface to handle RAR file decompression.
*/
export class UnrarAll extends EventEmitter implements UnrarInterface {
private decoder = new TextDecoder();
private bin: string;

/**
* Creates an instance of the UnrarAll class.
* @param bin - Path to the UnRAR executable, defaults to './bin/UnRAR.exe'.
*/
constructor(bin: string = "./bin/UnRAR.exe") {
super();
this.bin = bin;
}

/**
* uncompress .rar file
* - `src` source file path
* - `dest` destination folder path
* - `options` destination folder path
* - `command` command of unrar, default: x
* - `switches` switches of unrar, default: []
* Uncompresses a RAR file to the specified destination.
* @param src - The path to the RAR file to uncompress.
* @param dest - The path to the destination directory.
* @param options - Additional options for the uncompression process.
* @returns A promise that resolves once the file has been uncompressed.
*/
async uncompress(
src: string,
Expand All @@ -60,85 +92,57 @@ export class UnrarAll extends EventEmitter implements UnrarInterface {
switches.unshift("-p1");
}

const unrar = Deno.run({
cmd: [this.bin, command, ...switches, src, dest],
const cmd = new Deno.Command(this.bin, {
args: [command, ...switches, src, dest],
stdin: "null",
stdout: "piped",
stderr: "piped",
stdin: "null",
});
const unrar = cmd.spawn();

try {
if (!unrar.stdout) throw new Error("unexpected error");

let stdoutRead = await this.readMsg(unrar.stdout);
const reader = unrar.stdout.getReader();
const errorReader = unrar.stderr.getReader();

while ((stdoutRead) !== null) {
const stdout = this.decoder.decode(stdoutRead);
if (stdout.includes(notRAR)) {
try {
// Process output
while (true) {
const { value, done } = await reader.read();
if (done) break;
const msg = this.decoder.decode(value);
if (msg.includes(notRAR)) {
throw new Error(notRAR);
}
const match = msg.match(reg_progress);
if (match) this.emit("progress", match[0]);
}

const match = stdout.match(reg_progress);
if (match !== null) this.emit("progress", match[0]);
stdoutRead = await this.readMsg(unrar.stdout);
// Process errors
let errMsg = "";
while (true) {
const { value, done } = await errorReader.read();
if (done) break;
errMsg += this.decoder.decode(value, { stream: true });
}

// const stderrRead = await this.readMsg(unrar.stderr!);
// if (stderrRead !== null) {
// const stderr = this.decoder.decode(stderrRead);
// if (stderr.includes(password_incorrect)) {
// throw new Error("Password protected file");
// }
// throw new Error(stderr);
// }
const stderr = await unrar.stderrOutput();
if (stderr.length !== 0) {
const errMsg = this.decoder.decode(stderr);
if (errMsg) {
if (errMsg.includes(password_incorrect)) {
throw new Error("Password protected file");
}
throw new Error(errMsg);
}
this.emit("progress", "100%");
} finally {
unrar.stdout?.close();
// unrar.stderr?.close();
unrar.close();
}
}

private async readMsg(reader: Deno.Reader): Promise<Uint8Array | null> {
const arr: Uint8Array[] = [];
const n = 100;
let readed: number | null;
while (true) {
const p: Uint8Array = new Uint8Array(n);
readed = await reader.read(p);
if (readed === null) break;
if (readed < n) {
arr.push(p.subarray(0, readed));
break;
} else {
arr.push(p);
const status = await unrar.status;
if (!status.success) {
throw new Error(`Exit code: ${status.code}`);
}
}
if (readed === null) return readed;
const result = this.concatUint8Array(arr);
return result;
}

private concatUint8Array(arr: Uint8Array[]): Uint8Array {
const length = arr.reduce((pre, next) => pre + next.length, 0);
const result = new Uint8Array(length);
let offset = 0;
for (const v of arr) {
result.set(v, offset);
offset += v.length;
this.emit("progress", "100%");
} finally {
reader.releaseLock();
errorReader.releaseLock();
}
return result;
}
}

const unrarAll = new UnrarAll();

export default unrarAll;
// Export an instance of UnrarAll as the default export
export default new UnrarAll();
Loading

0 comments on commit c6c0fd3

Please sign in to comment.