cbyte 1.0.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cbyte.js +35 -76
- package/package.json +19 -8
- package/superherobookult0000unse_h7z3_lcp.cbyte +68649 -0
package/cbyte.js
CHANGED
|
@@ -1,49 +1,23 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
// Prevent MaxListenersExceededWarning
|
|
4
3
|
require('events').defaultMaxListeners = 6000;
|
|
5
4
|
|
|
6
5
|
const fs = require('fs');
|
|
7
6
|
const crypto = require('crypto');
|
|
8
7
|
const { program } = require('commander');
|
|
9
|
-
const {
|
|
10
|
-
const FormData = require('form-data');
|
|
8
|
+
const { create } = require('ipfs-core');
|
|
11
9
|
const os = require('os');
|
|
12
10
|
const path = require('path');
|
|
13
|
-
const { spawn } = require('child_process');
|
|
14
|
-
const net = require('net');
|
|
15
|
-
|
|
16
|
-
const IPFS_API = process.env.IPFS_API || 'http://127.0.0.1:5001/api/v0';
|
|
17
|
-
|
|
18
|
-
// ------------------- IPFS Daemon Helpers -------------------
|
|
19
|
-
function isPortOpen(host, port) {
|
|
20
|
-
return new Promise((resolve) => {
|
|
21
|
-
const socket = new net.Socket();
|
|
22
|
-
socket.setTimeout(1000);
|
|
23
|
-
socket.once('connect', () => { socket.destroy(); resolve(true); });
|
|
24
|
-
socket.once('timeout', () => { socket.destroy(); resolve(false); });
|
|
25
|
-
socket.once('error', () => { resolve(false); });
|
|
26
|
-
socket.connect(port, host);
|
|
27
|
-
});
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
async function ensureIpfsDaemon() {
|
|
31
|
-
const host = '127.0.0.1';
|
|
32
|
-
const port = 5001;
|
|
33
11
|
|
|
34
|
-
|
|
35
|
-
if (running) return;
|
|
12
|
+
let ipfs = null;
|
|
36
13
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
if (attempts++ > 20) throw new Error('Failed to start IPFS daemon.');
|
|
44
|
-
await new Promise(r => setTimeout(r, 500));
|
|
14
|
+
// ------------------- IPFS Initialization -------------------
|
|
15
|
+
async function ensureIpfs() {
|
|
16
|
+
if (!ipfs) {
|
|
17
|
+
console.log('Starting embedded IPFS node...');
|
|
18
|
+
ipfs = await create({ repo: path.join(process.cwd(), '.ipfs') });
|
|
19
|
+
console.log('IPFS node started.');
|
|
45
20
|
}
|
|
46
|
-
console.log('IPFS daemon started.');
|
|
47
21
|
}
|
|
48
22
|
|
|
49
23
|
// ------------------- Utility Functions -------------------
|
|
@@ -59,30 +33,10 @@ function renderProgress(completed, total, label = '') {
|
|
|
59
33
|
process.stderr.write(`\r${label} [${bar}] ${(ratio*100).toFixed(1)}% (${completed}/${total})`);
|
|
60
34
|
}
|
|
61
35
|
|
|
62
|
-
// -------------------
|
|
63
|
-
async function ipfsBlockPut(data) {
|
|
64
|
-
const form = new FormData();
|
|
65
|
-
form.append('file', data, { filename: 'chunk' });
|
|
66
|
-
|
|
67
|
-
const res = await request(`${IPFS_API}/block/put?format=raw&mhtype=sha2-256`, {
|
|
68
|
-
method: 'POST',
|
|
69
|
-
body: form,
|
|
70
|
-
headers: form.getHeaders()
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
const body = await res.body.text();
|
|
74
|
-
const json = JSON.parse(body);
|
|
75
|
-
return json.Key;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
async function ipfsBlockGet(cid) {
|
|
79
|
-
const res = await request(`${IPFS_API}/block/get?arg=${cid}`);
|
|
80
|
-
const buffer = await res.body.arrayBuffer();
|
|
81
|
-
return Buffer.from(buffer);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// ------------------- Pack Function -------------------
|
|
36
|
+
// ------------------- Pack -------------------
|
|
85
37
|
async function pack(filePath, output, chunkSize = 4096, pin = false) {
|
|
38
|
+
await ensureIpfs();
|
|
39
|
+
|
|
86
40
|
if (!output) {
|
|
87
41
|
const base = path.basename(filePath, path.extname(filePath));
|
|
88
42
|
output = `${base}.cbyte`;
|
|
@@ -90,6 +44,7 @@ async function pack(filePath, output, chunkSize = 4096, pin = false) {
|
|
|
90
44
|
|
|
91
45
|
const data = fs.readFileSync(filePath);
|
|
92
46
|
const chunksMap = {};
|
|
47
|
+
|
|
93
48
|
for (let offset = 0; offset < data.length; offset += chunkSize) {
|
|
94
49
|
const chunk = data.slice(offset, offset + chunkSize);
|
|
95
50
|
const hash = sha256(chunk);
|
|
@@ -102,24 +57,24 @@ async function pack(filePath, output, chunkSize = 4096, pin = false) {
|
|
|
102
57
|
version: 1,
|
|
103
58
|
chunk_size: chunkSize,
|
|
104
59
|
original_size: data.length,
|
|
105
|
-
original_ext: path.extname(filePath),
|
|
60
|
+
original_ext: path.extname(filePath),
|
|
106
61
|
chunks: []
|
|
107
62
|
};
|
|
108
63
|
|
|
109
64
|
const hashes = Object.keys(chunksMap);
|
|
110
65
|
const total = hashes.length;
|
|
111
|
-
let
|
|
66
|
+
let completed = 0;
|
|
112
67
|
|
|
113
68
|
const concurrency = Math.max(1, Math.min(8, os.cpus().length));
|
|
114
69
|
for (let i = 0; i < hashes.length; i += concurrency) {
|
|
115
70
|
const batch = hashes.slice(i, i + concurrency);
|
|
116
71
|
await Promise.all(batch.map(async (hash) => {
|
|
117
72
|
const info = chunksMap[hash];
|
|
118
|
-
const cid = await
|
|
119
|
-
if (pin) await
|
|
120
|
-
manifest.chunks.push({ cid, length: info.chunk.length, count: info.count });
|
|
121
|
-
|
|
122
|
-
renderProgress(
|
|
73
|
+
const { cid } = await ipfs.block.put(info.chunk);
|
|
74
|
+
if (pin) await ipfs.pin.add(cid);
|
|
75
|
+
manifest.chunks.push({ cid: cid.toString(), length: info.chunk.length, count: info.count });
|
|
76
|
+
completed++;
|
|
77
|
+
renderProgress(completed, total, 'Packing');
|
|
123
78
|
}));
|
|
124
79
|
}
|
|
125
80
|
process.stderr.write('\n');
|
|
@@ -128,8 +83,10 @@ async function pack(filePath, output, chunkSize = 4096, pin = false) {
|
|
|
128
83
|
console.log(`Packed ${filePath} -> ${output}`);
|
|
129
84
|
}
|
|
130
85
|
|
|
131
|
-
// ------------------- Unpack
|
|
86
|
+
// ------------------- Unpack -------------------
|
|
132
87
|
async function unpack(manifestPath, output) {
|
|
88
|
+
await ensureIpfs();
|
|
89
|
+
|
|
133
90
|
const manifest = JSON.parse(fs.readFileSync(manifestPath));
|
|
134
91
|
if (!output) {
|
|
135
92
|
const base = path.basename(manifestPath, path.extname(manifestPath));
|
|
@@ -145,7 +102,7 @@ async function unpack(manifestPath, output) {
|
|
|
145
102
|
for (let i = 0; i < manifest.chunks.length; i += concurrency) {
|
|
146
103
|
const batch = manifest.chunks.slice(i, i + concurrency);
|
|
147
104
|
await Promise.all(batch.map(async (entry) => {
|
|
148
|
-
const data = await
|
|
105
|
+
const data = Buffer.from(await ipfs.block.get(entry.cid));
|
|
149
106
|
for (let j = 0; j < entry.count; j++) {
|
|
150
107
|
fs.writeSync(outFd, data.slice(0, entry.length));
|
|
151
108
|
completed++;
|
|
@@ -160,16 +117,19 @@ async function unpack(manifestPath, output) {
|
|
|
160
117
|
console.log(`Unpacked -> ${output}`);
|
|
161
118
|
}
|
|
162
119
|
|
|
163
|
-
// ------------------- Verify
|
|
120
|
+
// ------------------- Verify -------------------
|
|
164
121
|
async function verify(manifestPath) {
|
|
122
|
+
await ensureIpfs();
|
|
123
|
+
|
|
165
124
|
const manifest = JSON.parse(fs.readFileSync(manifestPath));
|
|
166
125
|
let verified = 0;
|
|
126
|
+
|
|
167
127
|
for (const { cid } of manifest.chunks) {
|
|
168
128
|
try {
|
|
169
|
-
await
|
|
129
|
+
await ipfs.block.get(cid);
|
|
170
130
|
verified++;
|
|
171
131
|
renderProgress(verified, manifest.chunks.length, 'Verifying');
|
|
172
|
-
} catch
|
|
132
|
+
} catch {
|
|
173
133
|
console.error(`Missing block ${cid}`);
|
|
174
134
|
process.exit(1);
|
|
175
135
|
}
|
|
@@ -178,12 +138,15 @@ async function verify(manifestPath) {
|
|
|
178
138
|
console.log('All blocks verified');
|
|
179
139
|
}
|
|
180
140
|
|
|
181
|
-
// ------------------- Pin
|
|
141
|
+
// ------------------- Pin -------------------
|
|
182
142
|
async function pin(manifestPath) {
|
|
143
|
+
await ensureIpfs();
|
|
144
|
+
|
|
183
145
|
const manifest = JSON.parse(fs.readFileSync(manifestPath));
|
|
184
146
|
let pinned = 0;
|
|
147
|
+
|
|
185
148
|
for (const { cid } of manifest.chunks) {
|
|
186
|
-
await
|
|
149
|
+
await ipfs.pin.add(cid);
|
|
187
150
|
pinned++;
|
|
188
151
|
renderProgress(pinned, manifest.chunks.length, 'Pinning');
|
|
189
152
|
}
|
|
@@ -200,29 +163,25 @@ program
|
|
|
200
163
|
.option('--chunk-size <bytes>', 'Chunk size', '4096')
|
|
201
164
|
.option('--pin', 'Pin blocks after upload', false)
|
|
202
165
|
.action(async (file, options) => {
|
|
203
|
-
await
|
|
204
|
-
await pack(file, options.output, parseInt(options['chunkSize']), options.pin);
|
|
166
|
+
await pack(file, options.output, parseInt(options.chunkSize), options.pin);
|
|
205
167
|
});
|
|
206
168
|
|
|
207
169
|
program
|
|
208
170
|
.command('unpack <manifest>')
|
|
209
171
|
.option('-o, --output <file>', 'Output file')
|
|
210
172
|
.action(async (manifest, options) => {
|
|
211
|
-
await ensureIpfsDaemon();
|
|
212
173
|
await unpack(manifest, options.output);
|
|
213
174
|
});
|
|
214
175
|
|
|
215
176
|
program
|
|
216
177
|
.command('verify <manifest>')
|
|
217
178
|
.action(async (manifest) => {
|
|
218
|
-
await ensureIpfsDaemon();
|
|
219
179
|
await verify(manifest);
|
|
220
180
|
});
|
|
221
181
|
|
|
222
182
|
program
|
|
223
183
|
.command('pin <manifest>')
|
|
224
184
|
.action(async (manifest) => {
|
|
225
|
-
await ensureIpfsDaemon();
|
|
226
185
|
await pin(manifest);
|
|
227
186
|
});
|
|
228
187
|
|
package/package.json
CHANGED
|
@@ -1,18 +1,29 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cbyte",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "Content-addressed deduplicated storage CLI using IPFS with automatic unpack extension retention",
|
|
5
5
|
"main": "cbyte.js",
|
|
6
|
-
"bin": {
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
"bin": {
|
|
7
|
+
"cbyte": "./cbyte.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"test": "node test.js"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"ipfs",
|
|
14
|
+
"cli",
|
|
15
|
+
"deduplication",
|
|
16
|
+
"storage",
|
|
17
|
+
"content-addressed"
|
|
18
|
+
],
|
|
9
19
|
"author": "Your Name <you@example.com>",
|
|
10
20
|
"license": "MIT",
|
|
11
21
|
"dependencies": {
|
|
12
22
|
"commander": "^11.0.0",
|
|
13
|
-
"
|
|
14
|
-
"
|
|
23
|
+
"form-data": "^4.0.0",
|
|
24
|
+
"undici": "^6.0.0"
|
|
15
25
|
},
|
|
16
|
-
"engines": {
|
|
26
|
+
"engines": {
|
|
27
|
+
"node": ">=20"
|
|
28
|
+
}
|
|
17
29
|
}
|
|
18
|
-
|