Merged 1.3.x_branch into master

This commit is contained in:
2021-05-24 22:26:54 -05:00
20 changed files with 911 additions and 551 deletions

17
.eslintrc.json Normal file
View File

@@ -0,0 +1,17 @@
{
"env": {
"browser": true,
"es2021": true,
"jest/globals": true
},
"extends": "eslint:recommended",
"parserOptions": {
"ecmaVersion": 12,
"sourceType": "module"
},
"plugins": {
"jest"
},
"rules": {
}
}

1
.nvimrc Symbolic link
View File

@@ -0,0 +1 @@
.vimrc

7
.prettierrc.json Normal file
View File

@@ -0,0 +1,7 @@
{
"trailingComma": "es5",
"tabWidth": 2,
"semi": true,
"singleQuote": true,
"jsxBracketSameLine": true
}

2
.vimrc Normal file
View File

@@ -0,0 +1,2 @@
set autoread
set path+=.,src/**

View File

@@ -5,7 +5,7 @@
## Installing ## Installing
```shell ```shell
npm i @scottg1/repertory-js npm i @blockstorage/repertory-js
``` ```
## Repertory Configuration ## Repertory Configuration
@@ -55,7 +55,7 @@ also be set to a strong, random password.
## Example API Usage ## Example API Usage
```javascript ```javascript
const rep = require('@scottg1/repertory-js'); const rep = require('@blockstorage/repertory-js');
// Repertory host settings // Repertory host settings

View File

@@ -1,5 +1,5 @@
{ {
"name": "@scottg1/repertory-js", "name": "@blockstorage/repertory-js",
"version": "1.3.1-r1", "version": "1.3.1-r1",
"description": "A Node.js module for interfacing with Repertory's remote mount API", "description": "A Node.js module for interfacing with Repertory's remote mount API",
"scripts": { "scripts": {
@@ -35,6 +35,8 @@
"@babel/plugin-transform-runtime": "^7.13.9", "@babel/plugin-transform-runtime": "^7.13.9",
"@babel/preset-env": "^7.13.9", "@babel/preset-env": "^7.13.9",
"babel-plugin-transform-class-properties": "^6.24.1", "babel-plugin-transform-class-properties": "^6.24.1",
"eslint": "^7.22.0",
"eslint-plugin-jest": "^24.3.2",
"jest": "^26.6.3" "jest": "^26.6.3"
}, },
"type": "module", "type": "module",

View File

@@ -19,21 +19,23 @@ test(`socket receive data fails when decryption fails`, async () => {
on: (name, cb) => { on: (name, cb) => {
cbl[name] = cb; cbl[name] = cb;
}, },
} };
const conn = new connection('', 0, 'b', socket); const conn = new connection('', 0, 'b', socket);
let reject; let reject;
const mock_reject = jest.fn().mockImplementation(e => reject(e)); const mock_reject = jest.fn().mockImplementation((e) => reject(e));
conn.reject = mock_reject; conn.reject = mock_reject;
conn.resolve = jest.fn(); conn.resolve = jest.fn();
const p = new packet('a'); const p = new packet('a');
await p.encrypt(); await p.encrypt();
p.encode_top_ui32(p.buffer.length); p.encode_top_ui32(p.buffer.length);
await expect(new Promise((_, r) => { await expect(
reject = r; new Promise((_, r) => {
cbl['data'](Buffer.from(p.buffer)); reject = r;
})).rejects.toThrow(Error); cbl['data'](Buffer.from(p.buffer));
})
).rejects.toThrow(Error);
expect(mock_reject.mock.calls.length).toBe(1); expect(mock_reject.mock.calls.length).toBe(1);
}); });
@@ -42,8 +44,7 @@ test(`disconnect succeeds if an error is thrown`, async () => {
destroy: () => { destroy: () => {
throw new Error('mock destroy error'); throw new Error('mock destroy error');
}, },
on: () => { on: () => {},
},
}; };
const conn = new connection('', 0, 'b', socket); const conn = new connection('', 0, 'b', socket);
@@ -56,7 +57,7 @@ test(`send fails on socket error`, async () => {
on: (name, cb) => { on: (name, cb) => {
cbl[name] = cb; cbl[name] = cb;
}, },
} };
const conn = new connection('', 0, 'b', socket); const conn = new connection('', 0, 'b', socket);
const mock_reject = jest.fn(); const mock_reject = jest.fn();
@@ -73,7 +74,7 @@ test(`error is thrown when socket is closed`, async () => {
on: (name, cb) => { on: (name, cb) => {
cbl[name] = cb; cbl[name] = cb;
}, },
} };
const conn = new connection('', 0, 'b', socket); const conn = new connection('', 0, 'b', socket);
const mock_reject = jest.fn(); const mock_reject = jest.fn();

View File

@@ -19,7 +19,7 @@ test(`error on socket release is ignored`, async () => {
invoked = true; invoked = true;
throw new Error('mock release error'); throw new Error('mock release error');
}, },
} };
}); });
const mock_send = jest.fn(); const mock_send = jest.fn();
@@ -45,9 +45,8 @@ test(`connection pool send fails when connection send fails`, async () => {
const conn = new connection_pool(2, '', 20000); const conn = new connection_pool(2, '', 20000);
jest.spyOn(conn.pool, 'acquire').mockImplementation(() => { jest.spyOn(conn.pool, 'acquire').mockImplementation(() => {
return { return {
release: () => { release: () => {},
}, };
}
}); });
const mock_send = jest.fn(); const mock_send = jest.fn();

View File

@@ -1,4 +1,4 @@
import {get_version, instance_id, package_json} from '../utils/constants' import { get_version, instance_id, package_json } from '../utils/constants';
const uuid = require('uuid'); const uuid = require('uuid');

View File

@@ -1,13 +1,11 @@
import file from '../io/file'; import file from '../io/file';
jest.mock('../ops/index.js', () => ( jest.mock('../ops/index.js', () => ({
{ ...jest.requireActual('../ops/index.js'),
...(jest.requireActual('../ops/index.js')), close_file: jest.fn(),
close_file: jest.fn(), }));
}
));
import {close_file} from '../ops/index'; import { close_file } from '../ops/index';
test(`can close a closed file`, async () => { test(`can close a closed file`, async () => {
const f = new file(); const f = new file();

View File

@@ -1,6 +1,6 @@
import crypto from 'crypto'; import crypto from 'crypto';
import fs from 'fs'; import fs from 'fs';
import {Uint64BE} from 'int64-buffer'; import { Uint64BE } from 'int64-buffer';
import * as repertory from '../index.js'; import * as repertory from '../index.js';
import connection from '../networking/connection'; import connection from '../networking/connection';
@@ -10,18 +10,18 @@ const TEST_HOST = process.env.TEST_HOST || 'localhost';
const TEST_PASSWORD = process.env.TEST_PASSWORD || ''; const TEST_PASSWORD = process.env.TEST_PASSWORD || '';
const TEST_PORT = process.env.TEST_PORT || 20000; const TEST_PORT = process.env.TEST_PORT || 20000;
const calculate_sha256 = path => { const calculate_sha256 = (path) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const hash = crypto.createHash('sha256'); const hash = crypto.createHash('sha256');
fs.createReadStream(path) fs.createReadStream(path)
.on('data', data => hash.update(data)) .on('data', (data) => hash.update(data))
.on('error', err => reject(err)) .on('error', (err) => reject(err))
.on('end', () => { .on('end', () => {
const h = hash.digest('hex'); const h = hash.digest('hex');
console.log(path, h); console.log(path, h);
resolve(h); resolve(h);
}); });
}); });
}; };
@@ -43,8 +43,12 @@ test('can create a connection to repertory api', async () => {
test('create_pool returns a connection if pool size is <=1', async () => { test('create_pool returns a connection if pool size is <=1', async () => {
for (let i = 0; i < 2; i++) { for (let i = 0; i < 2; i++) {
const conn = const conn = await repertory.create_pool(
await repertory.create_pool(i, TEST_HOST, TEST_PORT, TEST_PASSWORD); i,
TEST_HOST,
TEST_PORT,
TEST_PASSWORD
);
expect(conn).toBeInstanceOf(connection); expect(conn).toBeInstanceOf(connection);
test_connection(conn, true); test_connection(conn, true);
@@ -53,8 +57,12 @@ test('create_pool returns a connection if pool size is <=1', async () => {
}); });
test('can create a connection pool', async () => { test('can create a connection pool', async () => {
const conn = const conn = await repertory.create_pool(
await repertory.create_pool(2, TEST_HOST, TEST_PORT, TEST_PASSWORD); 2,
TEST_HOST,
TEST_PORT,
TEST_PASSWORD
);
console.log(conn); console.log(conn);
expect(conn).toBeInstanceOf(connection_pool); expect(conn).toBeInstanceOf(connection_pool);
expect(conn.host_or_ip).toEqual(TEST_HOST); expect(conn.host_or_ip).toEqual(TEST_HOST);
@@ -68,8 +76,12 @@ test('can create a connection pool', async () => {
}); });
test('can get drive information using api', async () => { test('can get drive information using api', async () => {
const conn = const conn = await repertory.create_pool(
await repertory.create_pool(2, TEST_HOST, TEST_PORT, TEST_PASSWORD); 2,
TEST_HOST,
TEST_PORT,
TEST_PASSWORD
);
const api = repertory.create_api(conn); const api = repertory.create_api(conn);
const di = await api.get_drive_information(); const di = await api.get_drive_information();
console.log(di); console.log(di);
@@ -82,8 +94,12 @@ test('can get drive information using api', async () => {
}); });
test('can create and remove a directory using api', async () => { test('can create and remove a directory using api', async () => {
const conn = const conn = await repertory.create_pool(
await repertory.create_pool(2, TEST_HOST, TEST_PORT, TEST_PASSWORD); 2,
TEST_HOST,
TEST_PORT,
TEST_PASSWORD
);
const api = repertory.create_api(conn); const api = repertory.create_api(conn);
expect(await api.directory.create('/repertory_js')).toEqual(0); expect(await api.directory.create('/repertory_js')).toEqual(0);
expect(await api.directory.remove('/repertory_js')).toEqual(0); expect(await api.directory.remove('/repertory_js')).toEqual(0);
@@ -92,8 +108,12 @@ test('can create and remove a directory using api', async () => {
}); });
test('can get directory list and snapshot using api', async () => { test('can get directory list and snapshot using api', async () => {
const conn = const conn = await repertory.create_pool(
await repertory.create_pool(2, TEST_HOST, TEST_PORT, TEST_PASSWORD); 2,
TEST_HOST,
TEST_PORT,
TEST_PASSWORD
);
const api = repertory.create_api(conn); const api = repertory.create_api(conn);
const test_results = async (remote_path, page_count, get_page) => { const test_results = async (remote_path, page_count, get_page) => {
@@ -131,8 +151,12 @@ test('can get directory list and snapshot using api', async () => {
}); });
test('can create, close and delete a file using api', async () => { test('can create, close and delete a file using api', async () => {
const conn = const conn = await repertory.create_pool(
await repertory.create_pool(2, TEST_HOST, TEST_PORT, TEST_PASSWORD); 2,
TEST_HOST,
TEST_PORT,
TEST_PASSWORD
);
const api = repertory.create_api(conn); const api = repertory.create_api(conn);
const f = await api.file.create_or_open('/repertory_file.dat'); const f = await api.file.create_or_open('/repertory_file.dat');
console.log(f); console.log(f);
@@ -149,8 +173,12 @@ test('can create, close and delete a file using api', async () => {
}); });
test('can open, close and delete a file using api', async () => { test('can open, close and delete a file using api', async () => {
const conn = const conn = await repertory.create_pool(
await repertory.create_pool(2, TEST_HOST, TEST_PORT, TEST_PASSWORD); 2,
TEST_HOST,
TEST_PORT,
TEST_PASSWORD
);
const api = repertory.create_api(conn); const api = repertory.create_api(conn);
let f = await api.file.create_or_open('/repertory_file.dat'); let f = await api.file.create_or_open('/repertory_file.dat');
expect(await f.close()).toEqual(0); expect(await f.close()).toEqual(0);
@@ -170,8 +198,12 @@ test('can open, close and delete a file using api', async () => {
}); });
test('can write to and read from a file using api', async () => { test('can write to and read from a file using api', async () => {
const conn = const conn = await repertory.create_pool(
await repertory.create_pool(2, TEST_HOST, TEST_PORT, TEST_PASSWORD); 2,
TEST_HOST,
TEST_PORT,
TEST_PASSWORD
);
const api = repertory.create_api(conn); const api = repertory.create_api(conn);
const f = await api.file.create_or_open('/repertory_file.dat'); const f = await api.file.create_or_open('/repertory_file.dat');
@@ -193,8 +225,12 @@ test('can write to and read from a file using api', async () => {
}); });
test('can truncate a file using api', async () => { test('can truncate a file using api', async () => {
const conn = const conn = await repertory.create_pool(
await repertory.create_pool(2, TEST_HOST, TEST_PORT, TEST_PASSWORD); 2,
TEST_HOST,
TEST_PORT,
TEST_PASSWORD
);
const api = repertory.create_api(conn); const api = repertory.create_api(conn);
const f = await api.file.create_or_open('/repertory_file.dat'); const f = await api.file.create_or_open('/repertory_file.dat');
@@ -213,22 +249,34 @@ test('can truncate a file using api', async () => {
test('can upload and download a file using api', async () => { test('can upload and download a file using api', async () => {
try { try {
fs.unlinkSync('repertory_test.dat'); fs.unlinkSync('repertory_test.dat');
} catch { } catch {}
}
const conn = const conn = await repertory.create_pool(
await repertory.create_pool(2, TEST_HOST, TEST_PORT, TEST_PASSWORD); 2,
TEST_HOST,
TEST_PORT,
TEST_PASSWORD
);
const api = repertory.create_api(conn); const api = repertory.create_api(conn);
expect(await api.file.upload('test.dat', '/repertory_test.dat', expect(
(l, r, p, c) => { console.log(l, r, p, c); })) await api.file.upload('test.dat', '/repertory_test.dat', (l, r, p, c) => {
.toBeTruthy(); console.log(l, r, p, c);
})
).toBeTruthy();
expect(await api.file.download('/repertory_test.dat', 'repertory_test.dat', expect(
(l, r, p, c) => { console.log(l, r, p, c); })) await api.file.download(
.toBeTruthy(); '/repertory_test.dat',
'repertory_test.dat',
(l, r, p, c) => {
console.log(l, r, p, c);
}
)
).toBeTruthy();
expect(await calculate_sha256('test.dat')) expect(await calculate_sha256('test.dat')).toEqual(
.toEqual(await calculate_sha256('repertory_test.dat')); await calculate_sha256('repertory_test.dat')
);
expect(await api.file.delete('/repertory_test.dat')).toEqual(0); expect(await api.file.delete('/repertory_test.dat')).toEqual(0);
fs.unlinkSync('repertory_test.dat'); fs.unlinkSync('repertory_test.dat');
@@ -237,21 +285,39 @@ test('can upload and download a file using api', async () => {
}, 60000); }, 60000);
test('can download and overwrite a file using api', async () => { test('can download and overwrite a file using api', async () => {
const conn = const conn = await repertory.create_pool(
await repertory.create_pool(2, TEST_HOST, TEST_PORT, TEST_PASSWORD); 2,
TEST_HOST,
TEST_PORT,
TEST_PASSWORD
);
const api = repertory.create_api(conn); const api = repertory.create_api(conn);
expect(await api.file.upload('test.dat', '/repertory_test.dat', expect(
(l, r, p, c) => { console.log(l, r, p, c); })) await api.file.upload('test.dat', '/repertory_test.dat', (l, r, p, c) => {
.toBeTruthy(); console.log(l, r, p, c);
})
).toBeTruthy();
expect(await api.file.download('/repertory_test.dat', 'repertory_test.dat', expect(
(l, r, p, c) => { console.log(l, r, p, c); })) await api.file.download(
.toBeTruthy(); '/repertory_test.dat',
'repertory_test.dat',
(l, r, p, c) => {
console.log(l, r, p, c);
}
)
).toBeTruthy();
expect(await api.file.download('/repertory_test.dat', 'repertory_test.dat', expect(
(l, r, p, c) => { console.log(l, r, p, c); }, await api.file.download(
true)) '/repertory_test.dat',
.toBeTruthy(); 'repertory_test.dat',
(l, r, p, c) => {
console.log(l, r, p, c);
},
true
)
).toBeTruthy();
expect(await api.file.delete('/repertory_test.dat')).toEqual(0); expect(await api.file.delete('/repertory_test.dat')).toEqual(0);
fs.unlinkSync('repertory_test.dat'); fs.unlinkSync('repertory_test.dat');
@@ -260,21 +326,39 @@ test('can download and overwrite a file using api', async () => {
}, 60000); }, 60000);
test('download fails if overwrite is false using api', async () => { test('download fails if overwrite is false using api', async () => {
const conn = const conn = await repertory.create_pool(
await repertory.create_pool(2, TEST_HOST, TEST_PORT, TEST_PASSWORD); 2,
TEST_HOST,
TEST_PORT,
TEST_PASSWORD
);
const api = repertory.create_api(conn); const api = repertory.create_api(conn);
expect(await api.file.upload('test.dat', '/repertory_test.dat', expect(
(l, r, p, c) => { console.log(l, r, p, c); })) await api.file.upload('test.dat', '/repertory_test.dat', (l, r, p, c) => {
.toBeTruthy(); console.log(l, r, p, c);
})
).toBeTruthy();
expect(await api.file.download('/repertory_test.dat', 'repertory_test.dat', expect(
(l, r, p, c) => { console.log(l, r, p, c); })) await api.file.download(
.toBeTruthy(); '/repertory_test.dat',
'repertory_test.dat',
(l, r, p, c) => {
console.log(l, r, p, c);
}
)
).toBeTruthy();
await expect(api.file.download('/repertory_test.dat', 'repertory_test.dat', await expect(
(l, r, p, c) => { console.log(l, r, p, c); }, api.file.download(
false)) '/repertory_test.dat',
.rejects.toThrow(Error); 'repertory_test.dat',
(l, r, p, c) => {
console.log(l, r, p, c);
},
false
)
).rejects.toThrow(Error);
expect(await api.file.delete('/repertory_test.dat')).toEqual(0); expect(await api.file.delete('/repertory_test.dat')).toEqual(0);
fs.unlinkSync('repertory_test.dat'); fs.unlinkSync('repertory_test.dat');
@@ -283,17 +367,29 @@ test('download fails if overwrite is false using api', async () => {
}, 60000); }, 60000);
test('can upload and overwrite a file using api', async () => { test('can upload and overwrite a file using api', async () => {
const conn = const conn = await repertory.create_pool(
await repertory.create_pool(2, TEST_HOST, TEST_PORT, TEST_PASSWORD); 2,
TEST_HOST,
TEST_PORT,
TEST_PASSWORD
);
const api = repertory.create_api(conn); const api = repertory.create_api(conn);
expect(await api.file.upload('test.dat', '/repertory_test.dat', expect(
(l, r, p, c) => { console.log(l, r, p, c); })) await api.file.upload('test.dat', '/repertory_test.dat', (l, r, p, c) => {
.toBeTruthy(); console.log(l, r, p, c);
})
).toBeTruthy();
expect(await api.file.upload('test.dat', '/repertory_test.dat', expect(
(l, r, p, c) => { console.log(l, r, p, c); }, await api.file.upload(
true)) 'test.dat',
.toBeTruthy(); '/repertory_test.dat',
(l, r, p, c) => {
console.log(l, r, p, c);
},
true
)
).toBeTruthy();
expect(await api.file.delete('/repertory_test.dat')).toEqual(0); expect(await api.file.delete('/repertory_test.dat')).toEqual(0);
@@ -301,17 +397,29 @@ test('can upload and overwrite a file using api', async () => {
}, 60000); }, 60000);
test('upload fails if overwrite is false using api', async () => { test('upload fails if overwrite is false using api', async () => {
const conn = const conn = await repertory.create_pool(
await repertory.create_pool(2, TEST_HOST, TEST_PORT, TEST_PASSWORD); 2,
TEST_HOST,
TEST_PORT,
TEST_PASSWORD
);
const api = repertory.create_api(conn); const api = repertory.create_api(conn);
expect(await api.file.upload('test.dat', '/repertory_test.dat', expect(
(l, r, p, c) => { console.log(l, r, p, c); })) await api.file.upload('test.dat', '/repertory_test.dat', (l, r, p, c) => {
.toBeTruthy(); console.log(l, r, p, c);
})
).toBeTruthy();
await expect(api.file.upload('test.dat', '/repertory_test.dat', await expect(
(l, r, p, c) => { console.log(l, r, p, c); }, api.file.upload(
false)) 'test.dat',
.rejects.toThrow(Error); '/repertory_test.dat',
(l, r, p, c) => {
console.log(l, r, p, c);
},
false
)
).rejects.toThrow(Error);
expect(await api.file.delete('/repertory_test.dat')).toEqual(0); expect(await api.file.delete('/repertory_test.dat')).toEqual(0);
@@ -319,12 +427,18 @@ test('upload fails if overwrite is false using api', async () => {
}, 60000); }, 60000);
test('can resume download using api', async () => { test('can resume download using api', async () => {
const conn = const conn = await repertory.create_pool(
await repertory.create_pool(2, TEST_HOST, TEST_PORT, TEST_PASSWORD); 2,
TEST_HOST,
TEST_PORT,
TEST_PASSWORD
);
const api = repertory.create_api(conn); const api = repertory.create_api(conn);
expect(await api.file.upload('test.dat', '/repertory_test.dat', expect(
(l, r, p, c) => { console.log(l, r, p, c); })) await api.file.upload('test.dat', '/repertory_test.dat', (l, r, p, c) => {
.toBeTruthy(); console.log(l, r, p, c);
})
).toBeTruthy();
const fd = fs.openSync('test.dat', 'r'); const fd = fs.openSync('test.dat', 'r');
const buffer = Buffer.alloc(1024); const buffer = Buffer.alloc(1024);
@@ -333,13 +447,21 @@ test('can resume download using api', async () => {
fs.writeFileSync('repertory_test.dat', buffer); fs.writeFileSync('repertory_test.dat', buffer);
expect(await api.file.download('/repertory_test.dat', 'repertory_test.dat', expect(
(l, r, p, c) => { console.log(l, r, p, c); }, await api.file.download(
false, true)) '/repertory_test.dat',
.toBeTruthy(); 'repertory_test.dat',
(l, r, p, c) => {
console.log(l, r, p, c);
},
false,
true
)
).toBeTruthy();
expect(await calculate_sha256('test.dat')) expect(await calculate_sha256('test.dat')).toEqual(
.toEqual(await calculate_sha256('repertory_test.dat')); await calculate_sha256('repertory_test.dat')
);
expect(await api.file.delete('/repertory_test.dat')).toEqual(0); expect(await api.file.delete('/repertory_test.dat')).toEqual(0);
fs.unlinkSync('repertory_test.dat'); fs.unlinkSync('repertory_test.dat');
@@ -348,8 +470,12 @@ test('can resume download using api', async () => {
}, 60000); }, 60000);
test('can resume upload using api', async () => { test('can resume upload using api', async () => {
const conn = const conn = await repertory.create_pool(
await repertory.create_pool(2, TEST_HOST, TEST_PORT, TEST_PASSWORD); 2,
TEST_HOST,
TEST_PORT,
TEST_PASSWORD
);
const api = repertory.create_api(conn); const api = repertory.create_api(conn);
const fd = fs.openSync('test.dat', 'r'); const fd = fs.openSync('test.dat', 'r');
@@ -361,17 +487,31 @@ test('can resume upload using api', async () => {
await f.write(0, buffer); await f.write(0, buffer);
await f.close(); await f.close();
expect(await api.file.upload('test.dat', '/repertory_test.dat', expect(
(l, r, p, c) => { console.log(l, r, p, c); }, await api.file.upload(
false, true)) 'test.dat',
.toBeTruthy(); '/repertory_test.dat',
(l, r, p, c) => {
console.log(l, r, p, c);
},
false,
true
)
).toBeTruthy();
expect(await api.file.download('/repertory_test.dat', 'repertory_test.dat', expect(
(l, r, p, c) => { console.log(l, r, p, c); })) await api.file.download(
.toBeTruthy(); '/repertory_test.dat',
'repertory_test.dat',
(l, r, p, c) => {
console.log(l, r, p, c);
}
)
).toBeTruthy();
expect(await calculate_sha256('test.dat')) expect(await calculate_sha256('test.dat')).toEqual(
.toEqual(await calculate_sha256('repertory_test.dat')); await calculate_sha256('repertory_test.dat')
);
expect(await api.file.delete('/repertory_test.dat')).toEqual(0); expect(await api.file.delete('/repertory_test.dat')).toEqual(0);
fs.unlinkSync('repertory_test.dat'); fs.unlinkSync('repertory_test.dat');

View File

@@ -1,7 +1,7 @@
import file from './io/file' import file from './io/file';
import connection from './networking/connection'; import connection from './networking/connection';
import connection_pool from './networking/connection_pool'; import connection_pool from './networking/connection_pool';
import * as ops from './ops' import * as ops from './ops';
export const connect = async (host_or_ip, port, password) => { export const connect = async (host_or_ip, port, password) => {
const conn = new connection(host_or_ip, port, password); const conn = new connection(host_or_ip, port, password);
@@ -9,33 +9,53 @@ export const connect = async (host_or_ip, port, password) => {
return conn; return conn;
}; };
export const create_api = conn => { export const create_api = (conn) => {
return { return {
directory : { directory: {
create: async remote_path => ops.create_directory(conn, remote_path), create: async (remote_path) => ops.create_directory(conn, remote_path),
list: async (remote_path, page_reader_cb) => list: async (remote_path, page_reader_cb) =>
ops.list_directory(conn, remote_path, page_reader_cb), ops.list_directory(conn, remote_path, page_reader_cb),
remove: async remote_path => ops.remove_directory(conn, remote_path), remove: async (remote_path) => ops.remove_directory(conn, remote_path),
snapshot: async remote_path => { snapshot: async (remote_path) => {
return ops.snapshot_directory(conn, remote_path); return ops.snapshot_directory(conn, remote_path);
}, },
}, },
file : { file: {
create_or_open : async remote_path => new file( create_or_open: async (remote_path) =>
conn, await ops.create_or_open_file(conn, remote_path), remote_path), new file(
delete : async (remote_path) => ops.delete_file(conn, remote_path), conn,
download : await ops.create_or_open_file(conn, remote_path),
async (remote_path, local_path, progress_cb, overwrite, resume) => remote_path
ops.download_file(conn, remote_path, local_path, progress_cb, ),
overwrite, resume), delete: async (remote_path) => ops.delete_file(conn, remote_path),
open : async remote_path => download: async (
new file(conn, await ops.open_file(conn, remote_path), remote_path), remote_path,
upload : local_path,
async (local_path, remote_path, progress_cb, overwrite, resume) => progress_cb,
ops.upload_file(conn, local_path, remote_path, progress_cb, overwrite,
overwrite, resume), resume
) =>
ops.download_file(
conn,
remote_path,
local_path,
progress_cb,
overwrite,
resume
),
open: async (remote_path) =>
new file(conn, await ops.open_file(conn, remote_path), remote_path),
upload: async (local_path, remote_path, progress_cb, overwrite, resume) =>
ops.upload_file(
conn,
local_path,
remote_path,
progress_cb,
overwrite,
resume
),
}, },
get_drive_information : async () => ops.get_drive_information(conn), get_drive_information: async () => ops.get_drive_information(conn),
}; };
}; };

View File

@@ -17,8 +17,12 @@ export default class file {
async close() { async close() {
if (this.handle !== null) { if (this.handle !== null) {
const result = await ops.close_file(this.conn, this.remote_path, const result = await ops.close_file(
this.handle, this.thread_id); this.conn,
this.remote_path,
this.handle,
this.thread_id
);
if (result === 0) { if (result === 0) {
this.handle = null; this.handle = null;
} }
@@ -30,38 +34,59 @@ export default class file {
async get_size() { async get_size() {
if (this.handle === null) { if (this.handle === null) {
return Promise.reject(new Error('\'get_size()\' failed: invalid handle')); return Promise.reject(new Error("'get_size()' failed: invalid handle"));
} }
const attrs = await ops.get_file_attributes( const attrs = await ops.get_file_attributes(
this.conn, this.handle, this.remote_path, this.thread_id); this.conn,
this.handle,
this.remote_path,
this.thread_id
);
return attrs.size; return attrs.size;
} }
async read(offset, length) { async read(offset, length) {
if (this.handle === null) { if (this.handle === null) {
return Promise.reject(new Error('\'read()\' failed: invalid handle')); return Promise.reject(new Error("'read()' failed: invalid handle"));
} }
return ops.read_file(this.conn, this.handle, this.remote_path, offset, return ops.read_file(
length, this.thread_id); this.conn,
this.handle,
this.remote_path,
offset,
length,
this.thread_id
);
} }
async truncate(length) { async truncate(length) {
if (this.handle === null) { if (this.handle === null) {
return Promise.reject(new Error('\'truncate()\' failed: invalid handle')); return Promise.reject(new Error("'truncate()' failed: invalid handle"));
} }
return ops.truncate_file(this.conn, this.handle, this.remote_path, length, return ops.truncate_file(
this.thread_id); this.conn,
this.handle,
this.remote_path,
length,
this.thread_id
);
} }
async write(offset, buffer) { async write(offset, buffer) {
if (this.handle === null) { if (this.handle === null) {
return Promise.reject(new Error('\'write()\' failed: invalid handle')); return Promise.reject(new Error("'write()' failed: invalid handle"));
} }
return ops.write_file(this.conn, this.handle, this.remote_path, offset, return ops.write_file(
buffer, this.thread_id); this.conn,
this.handle,
this.remote_path,
offset,
buffer,
this.thread_id
);
} }
} }

View File

@@ -1,6 +1,6 @@
import Socket from 'net'; import Socket from 'net';
import * as constants from '../utils/constants' import * as constants from '../utils/constants';
import packet from './packet'; import packet from './packet';
@@ -33,13 +33,16 @@ export default class connection {
if (!this.socket) { if (!this.socket) {
try { try {
await new Promise((resolve, reject) => { await new Promise((resolve, reject) => {
this.socket = this.socket = Socket.createConnection(
Socket.createConnection(this.port, this.host_or_ip, err => { this.port,
this.host_or_ip,
(err) => {
if (err) { if (err) {
return reject(err) return reject(err);
} }
return resolve() return resolve();
}); }
);
}); });
} catch (err) { } catch (err) {
return Promise.reject(new Error(`'connect()' failed: ${err}`)); return Promise.reject(new Error(`'connect()' failed: ${err}`));
@@ -57,7 +60,7 @@ export default class connection {
buffer = null; buffer = null;
}; };
this.socket.on('data', chunk => { this.socket.on('data', (chunk) => {
buffer = buffer ? Buffer.concat([buffer, chunk]) : chunk; buffer = buffer ? Buffer.concat([buffer, chunk]) : chunk;
if (buffer.length > 4) { if (buffer.length > 4) {
const size = buffer.readUInt32BE(0); const size = buffer.readUInt32BE(0);
@@ -71,19 +74,20 @@ export default class connection {
const response = new packet(this.password); const response = new packet(this.password);
response.buffer = new Uint8Array(packet_data); response.buffer = new Uint8Array(packet_data);
response.decrypt() response
.then(() => { .decrypt()
resolve(response) .then(() => {
}) resolve(response);
.catch(e => { })
reject(e) .catch((e) => {
}) reject(e);
});
} }
} }
} }
}); });
this.socket.on('error', e => { this.socket.on('error', (e) => {
if (this.reject) { if (this.reject) {
const reject = this.reject; const reject = this.reject;
@@ -115,7 +119,7 @@ export default class connection {
this.connected = false; this.connected = false;
} }
} catch (e) { } catch (e) {
console.log(e) console.log(e);
} }
} }
@@ -131,7 +135,7 @@ export default class connection {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this.reject = reject; this.reject = reject;
this.resolve = resolve; this.resolve = resolve;
this.socket.write(Buffer.from(packet.buffer), null, err => { this.socket.write(Buffer.from(packet.buffer), null, (err) => {
if (err) { if (err) {
this.cleanup_handlers(); this.cleanup_handlers();
reject(err); reject(err);

View File

@@ -9,18 +9,18 @@ export default class connection_pool {
this.password = password; this.password = password;
if (pool_size > 1) { if (pool_size > 1) {
this.pool = new Pool({ this.pool = new Pool({
connect : {host : host_or_ip, port : port}, connect: { host: host_or_ip, port: port },
connectTimeout : 5000, connectTimeout: 5000,
pool : {max : pool_size, min : 2} pool: { max: pool_size, min: 2 },
}); });
} else { } else {
throw new Error("'pool_size' must be > 1"); throw new Error("'pool_size' must be > 1");
} }
} }
host_or_ip = ""; host_or_ip = '';
next_thread_id = 1; next_thread_id = 1;
password = ""; password = '';
port = 20000; port = 20000;
pool; pool;
shutdown = false; shutdown = false;
@@ -48,16 +48,19 @@ export default class connection_pool {
}; };
try { try {
const result = await new connection(this.host_or_ip, this.port, const result = await new connection(
this.password, socket) this.host_or_ip,
.send(method_name, packet, this.port,
optional_thread_id || socket.thread_id); this.password,
socket
).send(method_name, packet, optional_thread_id || socket.thread_id);
cleanup(); cleanup();
return result; return result;
} catch (err) { } catch (err) {
cleanup(); cleanup();
return Promise.reject( return Promise.reject(
new Error(`'send(${method_name})' failed: ${err}`)); new Error(`'send(${method_name})' failed: ${err}`)
);
} }
} catch (err) { } catch (err) {
return Promise.reject(new Error(`'acquire()' socket failed: ${err}`)); return Promise.reject(new Error(`'acquire()' socket failed: ${err}`));

View File

@@ -1,7 +1,7 @@
import {randomBytes} from 'crypto'; import { randomBytes } from 'crypto';
import {Int64BE, Uint64BE} from 'int64-buffer'; import { Int64BE, Uint64BE } from 'int64-buffer';
import {sha256} from 'js-sha256'; import { sha256 } from 'js-sha256';
import {TextEncoder} from 'text-encoding'; import { TextEncoder } from 'text-encoding';
import { import {
be_ui8_array_to_i16, be_ui8_array_to_i16,
@@ -20,7 +20,9 @@ import {
import JSChaCha20 from '../utils/jschacha20'; import JSChaCha20 from '../utils/jschacha20';
export default class packet { export default class packet {
constructor(token) { this.token = token; } constructor(token) {
this.token = token;
}
static HEADER = new TextEncoder().encode('repertory'); static HEADER = new TextEncoder().encode('repertory');
@@ -28,13 +30,14 @@ export default class packet {
decode_offset = 0; decode_offset = 0;
token; token;
append_buffer = buffer => { append_buffer = (buffer) => {
if (!(buffer instanceof Uint8Array)) { if (!(buffer instanceof Uint8Array)) {
throw new Error('Buffer must be of type Uint8Array'); throw new Error('Buffer must be of type Uint8Array');
} }
this.buffer = this.buffer = this.buffer
this.buffer ? new Uint8Array([...this.buffer, ...buffer ]) : buffer; ? new Uint8Array([...this.buffer, ...buffer])
: buffer;
}; };
clear = () => { clear = () => {
@@ -42,13 +45,15 @@ export default class packet {
this.decode_offset = 0; this.decode_offset = 0;
}; };
decode_buffer = length => { decode_buffer = (length) => {
if (!this.buffer) { if (!this.buffer) {
throw new Error('Invalid buffer'); throw new Error('Invalid buffer');
} }
const ret = const ret = this.buffer.slice(
this.buffer.slice(this.decode_offset, this.decode_offset + length); this.decode_offset,
this.decode_offset + length
);
this.decode_offset += length; this.decode_offset += length;
return Buffer.from(ret); return Buffer.from(ret);
}; };
@@ -68,9 +73,20 @@ export default class packet {
const flags = this.decode_ui32(); const flags = this.decode_ui32();
const directory = !!this.decode_ui8(); const directory = !!this.decode_ui8();
return { return {
mode, nlink, uid, gid, atime, mtime, ctime, birth_time, size, blocks, mode,
blksize, flags, directory, nlink,
} uid,
gid,
atime,
mtime,
ctime,
birth_time,
size,
blocks,
blksize,
flags,
directory,
};
}; };
decode_utf8 = () => { decode_utf8 = () => {
@@ -92,11 +108,13 @@ export default class packet {
throw new Error('String not found in buffer'); throw new Error('String not found in buffer');
}; };
decode_i8 = decode_i8 = () => {
() => { return ui8_array_to_i8(this.buffer, this.decode_offset++); }; return ui8_array_to_i8(this.buffer, this.decode_offset++);
};
decode_ui8 = decode_ui8 = () => {
() => { return ui8_array_to_ui8(this.buffer, this.decode_offset++); }; return ui8_array_to_ui8(this.buffer, this.decode_offset++);
};
decode_i16 = () => { decode_i16 = () => {
const ret = be_ui8_array_to_i16(this.buffer, this.decode_offset); const ret = be_ui8_array_to_i16(this.buffer, this.decode_offset);
@@ -124,7 +142,7 @@ export default class packet {
decode_i64 = () => { decode_i64 = () => {
const ret = new Int64BE( const ret = new Int64BE(
this.buffer.slice(this.decode_offset, this.decode_offset + 8), this.buffer.slice(this.decode_offset, this.decode_offset + 8)
); );
this.decode_offset += 8; this.decode_offset += 8;
return ret.toString(10); return ret.toString(10);
@@ -132,7 +150,7 @@ export default class packet {
decode_ui64 = () => { decode_ui64 = () => {
const ret = new Uint64BE( const ret = new Uint64BE(
this.buffer.slice(this.decode_offset, this.decode_offset + 8), this.buffer.slice(this.decode_offset, this.decode_offset + 8)
); );
this.decode_offset += 8; this.decode_offset += 8;
return ret.toString(10); return ret.toString(10);
@@ -146,10 +164,9 @@ export default class packet {
const key = Uint8Array.from(hash.array()); const key = Uint8Array.from(hash.array());
const nonce = this.buffer.slice(0, 12); const nonce = this.buffer.slice(0, 12);
this.buffer = new JSChaCha20(key, nonce, 0) this.buffer = new JSChaCha20(key, nonce, 0).decrypt(
.decrypt( this.buffer.slice(12)
this.buffer.slice(12), );
);
this.decode_offset = packet.HEADER.length; this.decode_offset = packet.HEADER.length;
@@ -164,65 +181,93 @@ export default class packet {
} }
}; };
encode_buffer = buffer => { this.append_buffer(new Uint8Array(buffer)); }; encode_buffer = (buffer) => {
this.append_buffer(new Uint8Array(buffer));
};
encode_i8 = num => { this.append_buffer(i8_to_ui8_array(num)); }; encode_i8 = (num) => {
this.append_buffer(i8_to_ui8_array(num));
};
encode_top_i8 = num => { this.push_buffer(i8_to_ui8_array(num)); }; encode_top_i8 = (num) => {
this.push_buffer(i8_to_ui8_array(num));
};
encode_u8 = num => { this.append_buffer(ui8_to_ui8_array(num)); }; encode_u8 = (num) => {
this.append_buffer(ui8_to_ui8_array(num));
};
encode_top_u8 = num => { this.push_buffer(ui8_to_ui8_array(num)); }; encode_top_u8 = (num) => {
this.push_buffer(ui8_to_ui8_array(num));
};
encode_i16 = num => { this.append_buffer(i16_to_be_ui8_array(num)); }; encode_i16 = (num) => {
this.append_buffer(i16_to_be_ui8_array(num));
};
encode_top_i16 = num => { this.push_buffer(i16_to_be_ui8_array(num)); }; encode_top_i16 = (num) => {
this.push_buffer(i16_to_be_ui8_array(num));
};
encode_ui16 = num => { this.append_buffer(ui16_to_be_ui8_array(num)); }; encode_ui16 = (num) => {
this.append_buffer(ui16_to_be_ui8_array(num));
};
encode_top_ui16 = num => { this.push_buffer(ui16_to_be_ui8_array(num)); }; encode_top_ui16 = (num) => {
this.push_buffer(ui16_to_be_ui8_array(num));
};
encode_i32 = num => { this.append_buffer(i32_to_be_ui8_array(num)); }; encode_i32 = (num) => {
this.append_buffer(i32_to_be_ui8_array(num));
};
encode_top_i32 = num => { this.push_buffer(i32_to_be_ui8_array(num)); }; encode_top_i32 = (num) => {
this.push_buffer(i32_to_be_ui8_array(num));
};
encode_ui32 = num => { this.append_buffer(ui32_to_be_ui8_array(num)); }; encode_ui32 = (num) => {
this.append_buffer(ui32_to_be_ui8_array(num));
};
encode_top_ui32 = num => { this.push_buffer(ui32_to_be_ui8_array(num)); }; encode_top_ui32 = (num) => {
this.push_buffer(ui32_to_be_ui8_array(num));
};
encode_i64 = num => { encode_i64 = (num) => {
this.append_buffer(new Uint8Array(new Int64BE(num).toArray())); this.append_buffer(new Uint8Array(new Int64BE(num).toArray()));
}; };
encode_top_i64 = encode_top_i64 = (num) => {
num => { this.push_buffer(new Uint8Array(new Int64BE(num).toArray())); }; this.push_buffer(new Uint8Array(new Int64BE(num).toArray()));
};
encode_ui64 = num => { encode_ui64 = (num) => {
this.append_buffer(new Uint8Array(new Uint64BE(num).toArray())); this.append_buffer(new Uint8Array(new Uint64BE(num).toArray()));
}; };
encode_top_ui64 = encode_top_ui64 = (num) => {
num => { this.push_buffer(new Uint8Array(new Uint64BE(num).toArray())); }; this.push_buffer(new Uint8Array(new Uint64BE(num).toArray()));
};
encode_utf8 = str => { encode_utf8 = (str) => {
if (!(typeof str === 'string' || str instanceof String)) { if (!(typeof str === 'string' || str instanceof String)) {
throw new Error('Value must be of type string'); throw new Error('Value must be of type string');
} }
const buffer = new Uint8Array([...new TextEncoder().encode(str), 0 ]); const buffer = new Uint8Array([...new TextEncoder().encode(str), 0]);
this.append_buffer(buffer); this.append_buffer(buffer);
}; };
encode_top_utf8 = str => { encode_top_utf8 = (str) => {
if (!(typeof str === 'string' || str instanceof String)) { if (!(typeof str === 'string' || str instanceof String)) {
throw new Error('Value must be of type string'); throw new Error('Value must be of type string');
} }
const buffer = new Uint8Array([...new TextEncoder().encode(str), 0 ]); const buffer = new Uint8Array([...new TextEncoder().encode(str), 0]);
this.push_buffer(buffer); this.push_buffer(buffer);
}; };
encrypt = async nonce => { encrypt = async (nonce) => {
try { try {
this.push_buffer(packet.HEADER); this.push_buffer(packet.HEADER);
const hash = sha256.create(); const hash = sha256.create();
@@ -242,12 +287,13 @@ export default class packet {
} }
}; };
push_buffer = buffer => { push_buffer = (buffer) => {
if (!(buffer instanceof Uint8Array)) { if (!(buffer instanceof Uint8Array)) {
throw new Error('Buffer must be of type Uint8Array'); throw new Error('Buffer must be of type Uint8Array');
} }
this.buffer = this.buffer = this.buffer
this.buffer ? new Uint8Array([...buffer, ...this.buffer ]) : buffer; ? new Uint8Array([...buffer, ...this.buffer])
: buffer;
}; };
} }

View File

@@ -1,5 +1,5 @@
import fs from 'fs'; import fs from 'fs';
import {Uint64BE} from 'int64-buffer'; import { Uint64BE } from 'int64-buffer';
import file from '../io/file'; import file from '../io/file';
import packet from '../networking/packet'; import packet from '../networking/packet';
@@ -9,8 +9,10 @@ const _snapshot_directory = async (conn, remote_path) => {
const request = new packet(); const request = new packet();
request.encode_utf8(remote_path); request.encode_utf8(remote_path);
const response = const response = await conn.send(
await conn.send('::RemoteJSONCreateDirectorySnapshot', request); '::RemoteJSONCreateDirectorySnapshot',
request
);
response.decode_ui32(); // Service flags response.decode_ui32(); // Service flags
const result = response.decode_i32(); const result = response.decode_i32();
@@ -28,15 +30,17 @@ const _snapshot_directory = async (conn, remote_path) => {
}; };
try { try {
const get_page = async page => { const get_page = async (page) => {
try { try {
const request = new packet(); const request = new packet();
request.encode_utf8(remote_path); request.encode_utf8(remote_path);
request.encode_ui64(data.handle); request.encode_ui64(data.handle);
request.encode_ui32(page); request.encode_ui32(page);
const response = const response = await conn.send(
await conn.send('::RemoteJSONReadDirectorySnapshot', request); '::RemoteJSONReadDirectorySnapshot',
request
);
response.decode_ui32(); // Service flags response.decode_ui32(); // Service flags
const result = response.decode_i32(); const result = response.decode_i32();
@@ -67,22 +71,29 @@ const _snapshot_directory = async (conn, remote_path) => {
} }
}; };
export const close_file = export const close_file = async (
async (conn, remote_path, handle, optional_thread_id) => { conn,
try { remote_path,
const request = new packet(); handle,
request.encode_utf8(remote_path); optional_thread_id
request.encode_ui64(handle); ) => {
try {
const request = new packet();
request.encode_utf8(remote_path);
request.encode_ui64(handle);
const response = const response = await conn.send(
await conn.send('::RemoteFUSERelease', request, optional_thread_id); '::RemoteFUSERelease',
response.decode_ui32(); // Service flags request,
optional_thread_id
);
response.decode_ui32(); // Service flags
return response.decode_i32(); return response.decode_i32();
} catch (err) { } catch (err) {
return Promise.reject(new Error(`'close_file' failed: ${err}`)); return Promise.reject(new Error(`'close_file' failed: ${err}`));
} }
}; };
export const create_directory = async (conn, remote_path) => { export const create_directory = async (conn, remote_path) => {
try { try {
@@ -99,28 +110,34 @@ export const create_directory = async (conn, remote_path) => {
} }
}; };
export const create_or_open_file = export const create_or_open_file = async (
async (conn, remote_path, optional_thread_id) => { conn,
try { remote_path,
const request = new packet(); optional_thread_id
request.encode_utf8(remote_path); ) => {
request.encode_ui16((7 << 6) | (5 << 3)); try {
request.encode_ui32(2 | 4); // Read-Write, Create const request = new packet();
request.encode_utf8(remote_path);
request.encode_ui16((7 << 6) | (5 << 3));
request.encode_ui32(2 | 4); // Read-Write, Create
const response = const response = await conn.send(
await conn.send('::RemoteFUSECreate', request, optional_thread_id); '::RemoteFUSECreate',
response.decode_ui32(); // Service flags request,
optional_thread_id
);
response.decode_ui32(); // Service flags
const result = response.decode_i32(); const result = response.decode_i32();
if (result === 0) { if (result === 0) {
return response.decode_ui64(); return response.decode_ui64();
}
return Promise.reject(new Error(`'create_or_open_file' error: ${result}`));
} catch (err) {
return Promise.reject(new Error(`'create_or_open_file' failed: ${err}`));
} }
};
return Promise.reject(new Error(`'create_or_open_file' error: ${result}`));
} catch (err) {
return Promise.reject(new Error(`'create_or_open_file' failed: ${err}`));
}
};
export const delete_file = async (conn, remote_path) => { export const delete_file = async (conn, remote_path) => {
try { try {
@@ -136,97 +153,113 @@ export const delete_file = async (conn, remote_path) => {
} }
}; };
export const download_file = export const download_file = async (
async (conn, remote_path, local_path, progress_cb, overwrite, resume) => { conn,
try { remote_path,
const src = new file(conn, await open_file(conn, remote_path), remote_path); local_path,
const cleanup = async fd => { progress_cb,
try { overwrite,
await src.close(); resume
} catch (err) { ) => {
console.log(err); try {
} const src = new file(conn, await open_file(conn, remote_path), remote_path);
try { const cleanup = async (fd) => {
if (fd !== undefined) {
fs.closeSync(fd);
}
} catch (err) {
console.log(err);
}
};
try { try {
const src_size = await src.get_size(); await src.close();
let dst_fd; } catch (err) {
console.log(err);
try { }
let offset = 0; try {
if (overwrite) { if (fd !== undefined) {
dst_fd = fs.openSync(local_path, 'w+'); fs.closeSync(fd);
} else if (resume) {
dst_fd = fs.openSync(local_path, 'r+');
const dst_size = fs.fstatSync(dst_fd).size;
if (dst_size === src_size) {
await cleanup(dst_fd);
return true;
}
if (dst_size > src_size) {
await cleanup(dst_fd);
return Promise.reject(new Error(
`'download_file' failed: destination is larger than source`));
}
offset = dst_size;
} else {
if (fs.existsSync(local_path)) {
await cleanup(dst_fd);
return Promise.reject(
new Error(`'download_file' failed: file exists`));
}
dst_fd = fs.openSync(local_path, 'wx+');
}
let remain = src_size - offset;
while (remain > 0) {
const to_write = remain >= 65536 ? 65536 : remain;
const buffer = await src.read(offset, to_write);
const written = fs.writeSync(dst_fd, buffer, 0, to_write, offset);
if (written > 0) {
remain -= written;
offset += written;
if (progress_cb) {
progress_cb(local_path, remote_path,
((src_size - remain) / src_size) * 100.0, false);
}
}
}
if (progress_cb) {
progress_cb(local_path, remote_path, 100, true);
}
await cleanup(dst_fd);
return true;
} catch (err) {
await cleanup(dst_fd);
return Promise.reject(new Error(`'download_file' failed: ${err}`));
} }
} catch (err) { } catch (err) {
await cleanup(); console.log(err);
}
};
try {
const src_size = await src.get_size();
let dst_fd;
try {
let offset = 0;
if (overwrite) {
dst_fd = fs.openSync(local_path, 'w+');
} else if (resume) {
dst_fd = fs.openSync(local_path, 'r+');
const dst_size = fs.fstatSync(dst_fd).size;
if (dst_size === src_size) {
await cleanup(dst_fd);
return true;
}
if (dst_size > src_size) {
await cleanup(dst_fd);
return Promise.reject(
new Error(
`'download_file' failed: destination is larger than source`
)
);
}
offset = dst_size;
} else {
if (fs.existsSync(local_path)) {
await cleanup(dst_fd);
return Promise.reject(
new Error(`'download_file' failed: file exists`)
);
}
dst_fd = fs.openSync(local_path, 'wx+');
}
let remain = src_size - offset;
while (remain > 0) {
const to_write = remain >= 65536 ? 65536 : remain;
const buffer = await src.read(offset, to_write);
const written = fs.writeSync(dst_fd, buffer, 0, to_write, offset);
if (written > 0) {
remain -= written;
offset += written;
if (progress_cb) {
progress_cb(
local_path,
remote_path,
((src_size - remain) / src_size) * 100.0,
false
);
}
}
}
if (progress_cb) {
progress_cb(local_path, remote_path, 100, true);
}
await cleanup(dst_fd);
return true;
} catch (err) {
await cleanup(dst_fd);
return Promise.reject(new Error(`'download_file' failed: ${err}`)); return Promise.reject(new Error(`'download_file' failed: ${err}`));
} }
} catch (err) { } catch (err) {
await cleanup();
return Promise.reject(new Error(`'download_file' failed: ${err}`)); return Promise.reject(new Error(`'download_file' failed: ${err}`));
} }
}; } catch (err) {
return Promise.reject(new Error(`'download_file' failed: ${err}`));
}
};
export const get_drive_information = async conn => { export const get_drive_information = async (conn) => {
try { try {
const response = const response = await conn.send(
await conn.send('::RemoteWinFSPGetVolumeInfo', new packet()); '::RemoteWinFSPGetVolumeInfo',
new packet()
);
response.decode_ui32(); // Service flags response.decode_ui32(); // Service flags
const result = response.decode_i32(); const result = response.decode_i32();
@@ -241,40 +274,52 @@ export const get_drive_information = async conn => {
} }
return Promise.reject( return Promise.reject(
new Error(`'get_drive_information' failed: ${result}`)); new Error(`'get_drive_information' failed: ${result}`)
);
} catch (err) { } catch (err) {
return Promise.reject(new Error(`'get_drive_information' failed: ${err}`)); return Promise.reject(new Error(`'get_drive_information' failed: ${err}`));
} }
}; };
export const get_file_attributes = export const get_file_attributes = async (
async (conn, handle, remote_path, optional_thread_id) => { conn,
try { handle,
const request = new packet(); remote_path,
request.encode_utf8(remote_path); optional_thread_id
request.encode_ui64(handle); ) => {
request.encode_ui32(0); try {
request.encode_ui32(0); const request = new packet();
request.encode_utf8(remote_path);
request.encode_ui64(handle);
request.encode_ui32(0);
request.encode_ui32(0);
const response = const response = await conn.send(
await conn.send('::RemoteFUSEFgetattr', request, optional_thread_id); '::RemoteFUSEFgetattr',
response.decode_ui32(); // Service flags request,
optional_thread_id
);
response.decode_ui32(); // Service flags
const result = response.decode_i32(); const result = response.decode_i32();
if (result === 0) { if (result === 0) {
return response.decode_stat(); return response.decode_stat();
}
return Promise.reject(new Error(`'get_file_attributes' failed: ${result}`));
} catch (err) {
return Promise.reject(new Error(`'get_file_attributes' failed: ${err}`));
} }
};
return Promise.reject(new Error(`'get_file_attributes' failed: ${result}`));
} catch (err) {
return Promise.reject(new Error(`'get_file_attributes' failed: ${err}`));
}
};
export const list_directory = async (conn, remote_path, page_reader_cb) => { export const list_directory = async (conn, remote_path, page_reader_cb) => {
const dir_snapshot = await _snapshot_directory(conn, remote_path); const dir_snapshot = await _snapshot_directory(conn, remote_path);
try { try {
await page_reader_cb(dir_snapshot.remote_path, dir_snapshot.page_count, dir_snapshot.get_page); await page_reader_cb(
dir_snapshot.remote_path,
dir_snapshot.page_count,
dir_snapshot.get_page
);
await dir_snapshot.release(); await dir_snapshot.release();
} catch (err) { } catch (err) {
await dir_snapshot.release(); await dir_snapshot.release();
@@ -288,8 +333,11 @@ export const open_file = async (conn, remote_path, optional_thread_id) => {
request.encode_utf8(remote_path); request.encode_utf8(remote_path);
request.encode_ui32(2); // Read-Write request.encode_ui32(2); // Read-Write
const response = const response = await conn.send(
await conn.send('::RemoteFUSEOpen', request, optional_thread_id); '::RemoteFUSEOpen',
request,
optional_thread_id
);
response.decode_ui32(); // Service flags response.decode_ui32(); // Service flags
const result = response.decode_i32(); const result = response.decode_i32();
@@ -302,28 +350,37 @@ export const open_file = async (conn, remote_path, optional_thread_id) => {
} }
}; };
export const read_file = export const read_file = async (
async (conn, handle, remote_path, offset, length, optional_thread_id) => { conn,
try { handle,
const request = new packet(); remote_path,
request.encode_utf8(remote_path); offset,
request.encode_ui64(length); length,
request.encode_ui64(offset); optional_thread_id
request.encode_ui64(handle); ) => {
try {
const request = new packet();
request.encode_utf8(remote_path);
request.encode_ui64(length);
request.encode_ui64(offset);
request.encode_ui64(handle);
const response = const response = await conn.send(
await conn.send('::RemoteFUSERead', request, optional_thread_id); '::RemoteFUSERead',
response.decode_ui32(); // Service flags request,
optional_thread_id
);
response.decode_ui32(); // Service flags
const result = response.decode_i32(); const result = response.decode_i32();
if (result === length) { if (result === length) {
return response.decode_buffer(result); return response.decode_buffer(result);
}
return Promise.reject(new Error(`'read_file' error: ${result}`));
} catch (err) {
return Promise.reject(new Error(`'read_file' failed: ${err}`));
} }
}; return Promise.reject(new Error(`'read_file' error: ${result}`));
} catch (err) {
return Promise.reject(new Error(`'read_file' failed: ${err}`));
}
};
export const remove_directory = async (conn, remote_path) => { export const remove_directory = async (conn, remote_path) => {
try { try {
@@ -341,146 +398,183 @@ export const remove_directory = async (conn, remote_path) => {
export const snapshot_directory = _snapshot_directory; export const snapshot_directory = _snapshot_directory;
export const truncate_file = export const truncate_file = async (
async (conn, handle, remote_path, length, optional_thread_id) => { conn,
try { handle,
const request = new packet(); remote_path,
request.encode_utf8(remote_path); length,
request.encode_ui64(length); optional_thread_id
request.encode_ui64(handle); ) => {
try {
const request = new packet();
request.encode_utf8(remote_path);
request.encode_ui64(length);
request.encode_ui64(handle);
const response = const response = await conn.send(
await conn.send('::RemoteFUSEFtruncate', request, optional_thread_id); '::RemoteFUSEFtruncate',
response.decode_ui32(); // Service flags request,
optional_thread_id
);
response.decode_ui32(); // Service flags
return response.decode_i32(); return response.decode_i32();
} catch (err) { } catch (err) {
return Promise.reject(new Error(`'truncate_file' failed: ${err}`)); return Promise.reject(new Error(`'truncate_file' failed: ${err}`));
} }
}; };
export const upload_file = export const upload_file = async (
async (conn, local_path, remote_path, progress_cb, overwrite, resume) => { conn,
try { local_path,
const src_fd = fs.openSync(local_path, 'r'); remote_path,
const cleanup = async f => { progress_cb,
try { overwrite,
fs.closeSync(src_fd); resume
} catch (err) { ) => {
console.log(err); try {
} const src_fd = fs.openSync(local_path, 'r');
try { const cleanup = async (f) => {
if (f) {
await f.close();
}
} catch (err) {
console.log(err);
}
};
try { try {
const src_st = fs.fstatSync(src_fd); fs.closeSync(src_fd);
let dst; } catch (err) {
const create_dest = async () => { console.log(err);
dst = new file(conn, await create_or_open_file(conn, remote_path), }
remote_path); try {
}; if (f) {
await f.close();
try {
let offset = 0;
if (overwrite) {
await create_dest();
const result = await dst.truncate(0);
if (result !== 0) {
await cleanup(dst);
return Promise.reject(new Error(`'upload_file' failed: ${result}`));
}
} else if (resume) {
await create_dest();
const dst_size = new Uint64BE(await dst.get_size()).toNumber();
if (dst_size === src_st.size) {
await cleanup(dst);
return true;
}
if (dst_size > src_st.size) {
await cleanup(dst);
return Promise.reject(new Error(
`'upload_file' failed: destination is larger than source`));
}
offset = dst_size;
} else {
try {
const f =
new file(conn, await open_file(conn, remote_path), remote_path);
await cleanup(f);
return Promise.reject(
new Error('\'upload_file\' failed: file exists'));
} catch (err) {
await create_dest();
}
}
let remain = src_st.size - offset;
const default_buffer = Buffer.alloc(65536 * 2);
while (remain > 0) {
const to_write =
remain >= default_buffer.length ? default_buffer.length : remain;
const buffer = to_write === default_buffer.length
? default_buffer
: Buffer.alloc(to_write);
fs.readSync(src_fd, buffer, 0, to_write, offset);
const written = await dst.write(offset, buffer);
if (written > 0) {
remain -= written;
offset += written;
if (progress_cb) {
progress_cb(local_path, remote_path,
((src_st.size - remain) / src_st.size) * 100.0,
false);
}
}
}
if (progress_cb) {
progress_cb(local_path, remote_path, 100, true);
}
await cleanup(dst);
return true;
} catch (err) {
await cleanup(dst);
return Promise.reject(new Error(`'upload_file' failed: ${err}`));
} }
} catch (err) { } catch (err) {
await cleanup(); console.log(err);
}
};
try {
const src_st = fs.fstatSync(src_fd);
let dst;
const create_dest = async () => {
dst = new file(
conn,
await create_or_open_file(conn, remote_path),
remote_path
);
};
try {
let offset = 0;
if (overwrite) {
await create_dest();
const result = await dst.truncate(0);
if (result !== 0) {
await cleanup(dst);
return Promise.reject(new Error(`'upload_file' failed: ${result}`));
}
} else if (resume) {
await create_dest();
const dst_size = new Uint64BE(await dst.get_size()).toNumber();
if (dst_size === src_st.size) {
await cleanup(dst);
return true;
}
if (dst_size > src_st.size) {
await cleanup(dst);
return Promise.reject(
new Error(
`'upload_file' failed: destination is larger than source`
)
);
}
offset = dst_size;
} else {
try {
const f = new file(
conn,
await open_file(conn, remote_path),
remote_path
);
await cleanup(f);
return Promise.reject(
new Error("'upload_file' failed: file exists")
);
} catch (err) {
await create_dest();
}
}
let remain = src_st.size - offset;
const default_buffer = Buffer.alloc(65536 * 2);
while (remain > 0) {
const to_write =
remain >= default_buffer.length ? default_buffer.length : remain;
const buffer =
to_write === default_buffer.length
? default_buffer
: Buffer.alloc(to_write);
fs.readSync(src_fd, buffer, 0, to_write, offset);
const written = await dst.write(offset, buffer);
if (written > 0) {
remain -= written;
offset += written;
if (progress_cb) {
progress_cb(
local_path,
remote_path,
((src_st.size - remain) / src_st.size) * 100.0,
false
);
}
}
}
if (progress_cb) {
progress_cb(local_path, remote_path, 100, true);
}
await cleanup(dst);
return true;
} catch (err) {
await cleanup(dst);
return Promise.reject(new Error(`'upload_file' failed: ${err}`)); return Promise.reject(new Error(`'upload_file' failed: ${err}`));
} }
} catch (err) { } catch (err) {
await cleanup();
return Promise.reject(new Error(`'upload_file' failed: ${err}`)); return Promise.reject(new Error(`'upload_file' failed: ${err}`));
} }
}; } catch (err) {
return Promise.reject(new Error(`'upload_file' failed: ${err}`));
}
};
export const write_file = export const write_file = async (
async (conn, handle, remote_path, offset, buffer, optional_thread_id) => { conn,
try { handle,
const request = new packet(); remote_path,
request.encode_utf8(remote_path); offset,
request.encode_ui64(buffer.length); buffer,
request.encode_buffer(buffer); optional_thread_id
request.encode_ui64(offset); ) => {
request.encode_ui64(handle); try {
const request = new packet();
request.encode_utf8(remote_path);
request.encode_ui64(buffer.length);
request.encode_buffer(buffer);
request.encode_ui64(offset);
request.encode_ui64(handle);
const response = const response = await conn.send(
await conn.send('::RemoteFUSEWrite', request, optional_thread_id); '::RemoteFUSEWrite',
response.decode_ui32(); // Service flags request,
optional_thread_id
);
response.decode_ui32(); // Service flags
const result = response.decode_i32(); const result = response.decode_i32();
if (result === buffer.length) { if (result === buffer.length) {
return result; return result;
}
return Promise.reject(new Error(`'write_file' error: ${result}`));
} catch (err) {
return Promise.reject(new Error(`'write_file' failed: ${err}`));
} }
}; return Promise.reject(new Error(`'write_file' error: ${result}`));
} catch (err) {
return Promise.reject(new Error(`'write_file' failed: ${err}`));
}
};

View File

@@ -1,10 +1,10 @@
export const is_big_endian_system = export const is_big_endian_system =
new Uint8Array(new Uint32Array([ 0x12345678 ]).buffer)[0] === 0x12; new Uint8Array(new Uint32Array([0x12345678]).buffer)[0] === 0x12;
export const is_little_endian_system = export const is_little_endian_system =
new Uint8Array(new Uint32Array([ 0x12345678 ]).buffer)[0] === 0x78; new Uint8Array(new Uint32Array([0x12345678]).buffer)[0] === 0x78;
export const i8_to_ui8_array = num => { export const i8_to_ui8_array = (num) => {
if (typeof num === 'string' || num instanceof String) { if (typeof num === 'string' || num instanceof String) {
num = parseInt(num, 10); num = parseInt(num, 10);
} }
@@ -21,7 +21,7 @@ export const ui8_array_to_i8 = (ar, offset) => {
return buffer.readInt8(0); return buffer.readInt8(0);
}; };
export const ui8_to_ui8_array = num => { export const ui8_to_ui8_array = (num) => {
if (typeof num === 'string' || num instanceof String) { if (typeof num === 'string' || num instanceof String) {
num = parseInt(num, 10); num = parseInt(num, 10);
} }
@@ -38,7 +38,7 @@ export const ui8_array_to_ui8 = (ar, offset) => {
return buffer.readUInt8(0); return buffer.readUInt8(0);
}; };
export const i16_to_be_ui8_array = num => { export const i16_to_be_ui8_array = (num) => {
if (typeof num === 'string' || num instanceof String) { if (typeof num === 'string' || num instanceof String) {
num = parseInt(num, 10); num = parseInt(num, 10);
} }
@@ -56,7 +56,7 @@ export const be_ui8_array_to_i16 = (ar, offset) => {
return buffer.readInt16BE(0); return buffer.readInt16BE(0);
}; };
export const ui16_to_be_ui8_array = num => { export const ui16_to_be_ui8_array = (num) => {
if (typeof num === 'string' || num instanceof String) { if (typeof num === 'string' || num instanceof String) {
num = parseInt(num, 10); num = parseInt(num, 10);
} }
@@ -74,7 +74,7 @@ export const be_ui8_array_to_ui16 = (ar, offset) => {
return buffer.readUInt16BE(0); return buffer.readUInt16BE(0);
}; };
export const i32_to_be_ui8_array = num => { export const i32_to_be_ui8_array = (num) => {
if (typeof num === 'string' || num instanceof String) { if (typeof num === 'string' || num instanceof String) {
num = parseInt(num, 10); num = parseInt(num, 10);
} }
@@ -92,7 +92,7 @@ export const be_ui8_array_to_i32 = (ar, offset) => {
return buffer.readInt32BE(0); return buffer.readInt32BE(0);
}; };
export const ui32_to_be_ui8_array = num => { export const ui32_to_be_ui8_array = (num) => {
if (typeof num === 'string' || num instanceof String) { if (typeof num === 'string' || num instanceof String) {
num = parseInt(num, 10); num = parseInt(num, 10);
} }

View File

@@ -1,6 +1,7 @@
const {v4: uuidv4} = require('uuid'); const { v4: uuidv4 } = require('uuid');
import _package_json from '../../package.json' import _package_json from '../../package.json';
export const instance_id = uuidv4(); export const instance_id = uuidv4();
export const package_json = _package_json; export const package_json = _package_json;
export const get_version = () => process.env.REPERTORY_JS_FORCE_VERSION || _package_json.version; export const get_version = () =>
process.env.REPERTORY_JS_FORCE_VERSION || _package_json.version;

View File

@@ -63,7 +63,7 @@
* *
* @constructor * @constructor
*/ */
var JSChaCha20 = function(key, nonce, counter) { var JSChaCha20 = function (key, nonce, counter) {
if (typeof counter === 'undefined') { if (typeof counter === 'undefined') {
counter = 0; counter = 0;
} }
@@ -175,7 +175,7 @@ var JSChaCha20 = function(key, nonce, counter) {
this._byteCounter = 0; this._byteCounter = 0;
}; };
JSChaCha20.prototype._chacha = function() { JSChaCha20.prototype._chacha = function () {
var mix = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; var mix = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
var i = 0; var i = 0;
var b = 0; var b = 0;
@@ -221,7 +221,7 @@ JSChaCha20.prototype._chacha = function() {
* @param {number} d * @param {number} d
* @private * @private
*/ */
JSChaCha20.prototype._quarterround = function(output, a, b, c, d) { JSChaCha20.prototype._quarterround = function (output, a, b, c, d) {
output[d] = this._rotl(output[d] ^ (output[a] += output[b]), 16); output[d] = this._rotl(output[d] ^ (output[a] += output[b]), 16);
output[b] = this._rotl(output[b] ^ (output[c] += output[d]), 12); output[b] = this._rotl(output[b] ^ (output[c] += output[d]), 12);
output[d] = this._rotl(output[d] ^ (output[a] += output[b]), 8); output[d] = this._rotl(output[d] ^ (output[a] += output[b]), 8);
@@ -242,7 +242,7 @@ JSChaCha20.prototype._quarterround = function(output, a, b, c, d) {
* @return {number} * @return {number}
* @private * @private
*/ */
JSChaCha20.prototype._get32 = function(data, index) { JSChaCha20.prototype._get32 = function (data, index) {
return ( return (
data[index++] ^ data[index++] ^
(data[index++] << 8) ^ (data[index++] << 8) ^
@@ -259,7 +259,7 @@ JSChaCha20.prototype._get32 = function(data, index) {
* @return {number} * @return {number}
* @private * @private
*/ */
JSChaCha20.prototype._rotl = function(data, shift) { JSChaCha20.prototype._rotl = function (data, shift) {
return (data << shift) | (data >>> (32 - shift)); return (data << shift) | (data >>> (32 - shift));
}; };
@@ -269,7 +269,7 @@ JSChaCha20.prototype._rotl = function(data, shift) {
* @param {Uint8Array} data * @param {Uint8Array} data
* @return {Uint8Array} * @return {Uint8Array}
*/ */
JSChaCha20.prototype.encrypt = function(data) { JSChaCha20.prototype.encrypt = function (data) {
return this._update(data); return this._update(data);
}; };
@@ -279,7 +279,7 @@ JSChaCha20.prototype.encrypt = function(data) {
* @param {Uint8Array} data * @param {Uint8Array} data
* @return {Uint8Array} * @return {Uint8Array}
*/ */
JSChaCha20.prototype.decrypt = function(data) { JSChaCha20.prototype.decrypt = function (data) {
return this._update(data); return this._update(data);
}; };
@@ -290,7 +290,7 @@ JSChaCha20.prototype.decrypt = function(data) {
* @return {Uint8Array} * @return {Uint8Array}
* @private * @private
*/ */
JSChaCha20.prototype._update = function(data) { JSChaCha20.prototype._update = function (data) {
if (!(data instanceof Uint8Array) || data.length === 0) { if (!(data instanceof Uint8Array) || data.length === 0) {
throw new Error('Data should be type of bytes (Uint8Array) and not empty!'); throw new Error('Data should be type of bytes (Uint8Array) and not empty!');
} }