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

Targz #121

Merged
merged 35 commits into from Mar 16, 2019
Merged

Targz #121

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
395079b
add wip broken monstrosity targz
bdistin Mar 12, 2019
ce4d742
better, but still super idk man
bdistin Mar 12, 2019
3e30ec2
uh, this actually made a file that winrar could open
bdistin Mar 12, 2019
9107f7f
add directory test
bdistin Mar 12, 2019
b81275e
work off base directory resolution
bdistin Mar 12, 2019
df51ab7
add atomic shortcuts and tests
bdistin Mar 12, 2019
1e53b16
add more unit tests
bdistin Mar 12, 2019
2760bdd
Merge branch 'master' into targz
bdistin Mar 12, 2019
4ab339d
fix type in Untar?
bdistin Mar 12, 2019
476e14e
add gzip methods
bdistin Mar 15, 2019
d3eedac
I think this should be more efficient
bdistin Mar 15, 2019
3e5bb26
simplify Tar
bdistin Mar 15, 2019
dbcf694
fix imports
bdistin Mar 15, 2019
4544c56
utilize scan in targz
bdistin Mar 15, 2019
b12417a
add some more types
bdistin Mar 15, 2019
de6385a
lint
bdistin Mar 15, 2019
c8420cb
utilize _read
bdistin Mar 15, 2019
21b5c9b
coverage fixes
bdistin Mar 15, 2019
0d7a1cd
remove state
bdistin Mar 15, 2019
91095e6
reorg
bdistin Mar 15, 2019
15a4b1b
obvious missed type, doesn't really effect anything though
bdistin Mar 15, 2019
fd64b01
so long as circulars aren't a problem, this should reduce dupe
bdistin Mar 15, 2019
4ba228f
fix failing test
bdistin Mar 15, 2019
4b050f7
better stage names
bdistin Mar 15, 2019
cf7bbae
rewrite Untar and make progress
bdistin Mar 15, 2019
a230754
fix some names
bdistin Mar 15, 2019
2f28c2b
still not correct, but probably moreso
bdistin Mar 15, 2019
54ec0fc
added some state, and now it works
bdistin Mar 15, 2019
d006318
remove extra state
bdistin Mar 15, 2019
73ddd51
more tests, but trouble in checksum valley
bdistin Mar 15, 2019
b863656
it wasn't the checksum, it was empty directories
bdistin Mar 15, 2019
470b68b
fix?
bdistin Mar 16, 2019
f2daedc
add a test, yolo
bdistin Mar 16, 2019
eba36ec
coverage adjustments
bdistin Mar 16, 2019
c23b310
Merge branch 'master' into targz
bdistin Mar 16, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 3 additions & 3 deletions package.json
Expand Up @@ -36,13 +36,13 @@
"node": ">=10.1.0"
},
"devDependencies": {
"@types/node": "^11.9.6",
"@types/node": "^11.11.0",
"ava": "^1.0.1",
"eslint": "^5.2.0",
"eslint-config-klasa": "github:dirigeants/klasa-lint",
"eslint-config-klasa": "dirigeants/klasa-lint",
"nyc": "^13.0.1",
"tslint": "^5.7.0",
"ts-node": "^8.0.3",
"tslint": "^5.7.0",
"typedoc": "^0.14.2",
"typescript": "^3.0.1"
},
Expand Down
3 changes: 3 additions & 0 deletions scripts/.azure-test-template.yml
Expand Up @@ -20,8 +20,11 @@ jobs:
versionSpec: $(nodeVersion)
displayName: 'Install Node.js'
- script: 'yarn'
displayName: 'Install Dependencies'
- script: 'yarn run test:coverage'
displayName: 'Run Tests'
- script: 'yarn run coverage:azure'
displayName: 'Generate Reports'
- task: PublishCodeCoverageResults@1
inputs:
codeCoverageTool: 'cobertura'
Expand Down
8 changes: 8 additions & 0 deletions src/index.ts
Expand Up @@ -21,6 +21,10 @@ export { default as ensureLink } from './nextra/createLink';
export { default as ensureLinkAtomic } from './nextra/createLinkAtomic';
export { default as ensureSymlink } from './nextra/createSymlink';
export { default as ensureSymlinkAtomic } from './nextra/createSymlinkAtomic';
export { default as gunzip } from './nextra/gunzip';
export { default as gunzipAtomic } from './nextra/gunzipAtomic';
export { default as gzip } from './nextra/gzip';
export { default as gzipAtomic } from './nextra/gzip';
export { default as linkAtomic } from './nextra/linkAtomic';
export { default as mkdirp } from './nextra/mkdirs';
export { default as mkdirs } from './nextra/mkdirs';
Expand All @@ -37,6 +41,10 @@ export { default as readJson } from './nextra/readJSON';
export { default as remove } from './nextra/remove';
export { default as scan } from './nextra/scan';
export { default as symlinkAtomic } from './nextra/symlinkAtomic';
export { default as targz } from './nextra/targz';
export { default as targzAtomic } from './nextra/targzAtomic';
export { default as unTargz } from './nextra/unTargz';
export { default as unTargzAtomic } from './nextra/unTargzAtomic';
export { default as writeFileAtomic } from './nextra/writeFileAtomic';
export { default as writeJSON } from './nextra/writeJSON';
export { default as writeJSONAtomic } from './nextra/writeJSONAtomic';
Expand Down
2 changes: 1 addition & 1 deletion src/nextra/copy.ts
@@ -1,6 +1,6 @@
import { resolve, dirname, join, basename } from 'path';

import { replaceEsc, isSrcKid } from '../util';
import { replaceEsc, isSrcKid } from '../utils/util';
import { access, readlink, mkdir, symlink, copyFile, lstat, stat, chmod, readdir, Stats } from '../fs';

import mkdirs from './mkdirs';
Expand Down
2 changes: 1 addition & 1 deletion src/nextra/copyFileAtomic.ts
@@ -1,4 +1,4 @@
import { tempFile } from '../util';
import { tempFile } from '../utils/util';
import { copyFile } from '../fs';

import move from './move';
Expand Down
23 changes: 23 additions & 0 deletions src/nextra/gunzip.ts
@@ -0,0 +1,23 @@
import { createGunzip } from 'zlib';

import { createWriteStream, createReadStream } from '../fs';
import { pipelinePromise } from '../utils/util';
import gunzipAtomic from './gunzipAtomic';

/**
* Un-Gzips a file
* @function gunzip
* @memberof fsn/nextra
* @param fileName The filename of the output file
* @param inputFile The filepath of the archive
* @param atomic If the unzip file should be created atomically
*/
export default async function gzip(fileName: string, inputFile: string, atomic: boolean = false): Promise<void> {
if (atomic) return gunzipAtomic(fileName, inputFile);

return pipelinePromise(
createReadStream(inputFile),
createGunzip(),
createWriteStream(fileName)
);
}
16 changes: 16 additions & 0 deletions src/nextra/gunzipAtomic.ts
@@ -0,0 +1,16 @@
import gunzip from './gunzip';
import move from './move';
import { tempFile } from '../utils/util';

/**
* Un-Gzips a file atomically.
* @function gunzipAtomic
* @memberof fsn/nextra
* @param fileName The filename of the output file
* @param inputFile The filepath of the archive
*/
export default async function gzipAtomic(fileName: string, inputFile: string): Promise<void> {
const tempPath = tempFile();
await gunzip(tempPath, inputFile);
return move(tempPath, fileName, { overwrite: true });
}
23 changes: 23 additions & 0 deletions src/nextra/gzip.ts
@@ -0,0 +1,23 @@
import { createGzip } from 'zlib';

import { createWriteStream, createReadStream } from '../fs';
import { pipelinePromise } from '../utils/util';
import gzipAtomic from './gzipAtomic';

/**
* Gzips a file
* @function gzip
* @memberof fsn/nextra
* @param fileName The filename of the archive
* @param inputFile The filepath of the input file
* @param atomic If the gzip file should be created
*/
export default async function gzip(fileName: string, inputFile: string, atomic: boolean = false): Promise<void> {
if (atomic) return gzipAtomic(fileName, inputFile);

return pipelinePromise(
createReadStream(inputFile),
createGzip(),
createWriteStream(fileName)
);
}
16 changes: 16 additions & 0 deletions src/nextra/gzipAtomic.ts
@@ -0,0 +1,16 @@
import gzip from './gzip';
import move from './move';
import { tempFile } from '../utils/util';

/**
* Gzips a file atomically.
* @function gzipAtomic
* @memberof fsn/nextra
* @param fileName The filename of the archive
* @param inputFile The filepath of the input file
*/
export default async function gzipAtomic(fileName: string, inputFile: string): Promise<void> {
const tempPath = tempFile();
await gzip(tempPath, inputFile);
return move(tempPath, fileName, { overwrite: true });
}
2 changes: 1 addition & 1 deletion src/nextra/linkAtomic.ts
@@ -1,4 +1,4 @@
import { tempFile } from '../util';
import { tempFile } from '../utils/util';
import { link } from '../fs';

import move from './move';
Expand Down
2 changes: 1 addition & 1 deletion src/nextra/mkdirs.ts
@@ -1,6 +1,6 @@
import { resolve, dirname, normalize, sep } from 'path';

import { isWindows } from '../util';
import { isWindows } from '../utils/util';
import { stat, mkdir } from '../fs';

/**
Expand Down
2 changes: 1 addition & 1 deletion src/nextra/move.ts
@@ -1,6 +1,6 @@
import { resolve, dirname } from 'path';

import { isSrcKid } from '../util';
import { isSrcKid } from '../utils/util';
import { access, rename, stat } from '../fs';

import remove from './remove';
Expand Down
2 changes: 1 addition & 1 deletion src/nextra/remove.ts
@@ -1,6 +1,6 @@
import { join } from 'path';

import { isWindows, setTimeoutPromise } from '../util';
import { isWindows, setTimeoutPromise } from '../utils/util';
import { lstat, unlink, rmdir, chmod, readdir } from '../fs';

/**
Expand Down
2 changes: 1 addition & 1 deletion src/nextra/symlinkAtomic.ts
@@ -1,4 +1,4 @@
import { tempFile } from '../util';
import { tempFile } from '../utils/util';
import { symlink } from '../fs';
import { SymLinkType } from './createSymlink';

Expand Down
35 changes: 35 additions & 0 deletions src/nextra/targz.ts
@@ -0,0 +1,35 @@
import { createGzip } from 'zlib';

import { createWriteStream } from '../fs';
import Tar from '../utils/Tar';
import { pipelinePromise } from '../utils/util';
import { dirname } from 'path';
import scan from './scan';
import targzAtomic from './targzAtomic';


/**
* Tar/Gzips a directory or array of files.
* @function targz
* @memberof fsn/nextra
* @param fileName The filename of the archive
* @param inputFiles The directory or array of filepaths to .tar.gz
* @param options The options for this .tar.gz
*/
export default async function targz(fileName: string, inputFiles: string | string[], atomic: boolean = false): Promise<void> {
if (atomic) return targzAtomic(fileName, inputFiles);
if (!Array.isArray(inputFiles)) inputFiles = [inputFiles];

const tar = new Tar(dirname(inputFiles[0]));

for (const input of inputFiles) {
const files = await scan(input, { filter: stats => stats.isFile() });
for (const [file, stats] of files) tar.append(file, stats);
}

return pipelinePromise(
tar,
createGzip(),
createWriteStream(fileName)
);
}
16 changes: 16 additions & 0 deletions src/nextra/targzAtomic.ts
@@ -0,0 +1,16 @@
import targz from './targz';
import move from './move';
import { tempFile } from '../utils/util';

/**
* Tar/Gzips a directory or array of files.
* @function targzAtomic
* @memberof fsn/nextra
* @param fileName The filename of the archive
* @param inputFiles The directory or array of filepaths to .tar.gz
*/
export default async function targzAtomic(fileName: string, inputFiles: string | string[]): Promise<void> {
const tempPath = tempFile();
await targz(tempPath, inputFiles);
return move(tempPath, fileName, { overwrite: true });
}
23 changes: 23 additions & 0 deletions src/nextra/unTargz.ts
@@ -0,0 +1,23 @@
import { createGunzip } from 'zlib';
import { resolve } from 'path';

import { createReadStream } from '../fs';
import Untar from '../utils/Untar';
import outputFile from './outputFile';
import outputFileAtomic from './outputFileAtomic';


/**
* Extracts files from .tar.gz archives.
* @function unTargz
* @memberof fsn/nextra
* @param outputDirectory The directory to extract to
* @param inputFile The archive file
* @param atomic The if the writes should be atomic
*/
export default async function unTargz(outputDirectory: string, inputFile: string, atomic: boolean = false): Promise<void> {
const tar = createReadStream(inputFile).pipe(createGunzip()).pipe(new Untar());
const writeMethod = atomic ? outputFile : outputFileAtomic;

for await (const { header, file } of tar.files()) await writeMethod(resolve(outputDirectory, header.filename), file);
}
14 changes: 14 additions & 0 deletions src/nextra/unTargzAtomic.ts
@@ -0,0 +1,14 @@
import unTargz from './unTargz';


/**
* Extracts files from .tar.gz archives and writes them atomically.
* @function unTargzAtomic
* @memberof fsn/nextra
* @param outputDirectory The directory to extract to
* @param inputFile The archive file
* @param atomic The if the writes should be atomic
*/
export default async function unTargzAtomic(outputDirectory: string, inputFile: string): Promise<void> {
return unTargz(outputDirectory, inputFile, true);
}
2 changes: 1 addition & 1 deletion src/nextra/writeFileAtomic.ts
@@ -1,4 +1,4 @@
import { tempFile } from '../util';
import { tempFile } from '../utils/util';
import { writeFile } from '../fs';

import move from './move';
Expand Down
64 changes: 64 additions & 0 deletions src/utils/Tar.ts
@@ -0,0 +1,64 @@
import { relative } from 'path';
import { Readable } from 'stream';
import { encodeHeader } from './header';
import { createReadStream, Stats } from '../fs';

export default class Tar extends Readable {

private base: string;
private written: number = 0;
private recordSize = 512;
private queue: Array<{ header: Buffer, file: Readable, size: number }> = [];

public constructor(base: string) {
super();

this.base = base;
}

public async _read(): Promise<void> {
if (!this.queue.length) {
this.push(null);
return;
}

const { header, file, size } = this.queue.shift();
const { written } = this;

this.written += header.length;

const fileChunks = [];

for await (const chunk of file) {
fileChunks.push(chunk);
this.written += chunk.length;
}

// Hard to produce, requires a size perfectibly divisible by the recordSize
/* istanbul ignore next */
const extraBytes = this.recordSize - (size % this.recordSize || this.recordSize);
this.written += extraBytes;

this.push(Buffer.concat([header, ...fileChunks, Buffer.alloc(extraBytes)], this.written - written));
}

public append(filepath: string, stats: Stats): void {
this.queue.push({
header: encodeHeader({
filename: relative(this.base, filepath),
mode: stats.mode,
uid: stats.uid,
gid: stats.gid,
size: stats.size,
mtime: Math.trunc(stats.mtime.valueOf() / 1000),
type: '0',
ustar: 'ustar ',
owner: '',
group: ''
}),
file: createReadStream(filepath),
size: stats.size
});
}

}