@axium/storage 0.24.1 → 0.24.2
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/dist/client/api.js +2 -2
- package/dist/client/cli/commands.js +93 -37
- package/dist/server/item.d.ts +0 -1
- package/dist/server/item.js +0 -1
- package/dist/server/raw.js +2 -5
- package/package.json +1 -1
package/dist/client/api.js
CHANGED
|
@@ -5,9 +5,9 @@ import { StorageItemMetadata } from '../common.js';
|
|
|
5
5
|
import '../polyfills.js';
|
|
6
6
|
import { warnOnce } from 'ioium';
|
|
7
7
|
function rawStorage(suffix) {
|
|
8
|
-
const raw =
|
|
8
|
+
const raw = '/raw/storage' + (suffix ? '/' + suffix : '');
|
|
9
9
|
if (prefix[0] == '/')
|
|
10
|
-
return raw;
|
|
10
|
+
return origin + raw;
|
|
11
11
|
const url = new URL(prefix);
|
|
12
12
|
url.pathname = raw;
|
|
13
13
|
return url;
|
|
@@ -55,7 +55,9 @@ import { Command } from 'commander';
|
|
|
55
55
|
import * as io from 'ioium/node';
|
|
56
56
|
import mime from 'mime';
|
|
57
57
|
import * as fs from 'node:fs';
|
|
58
|
-
import { basename } from 'node:path';
|
|
58
|
+
import { basename, join, parse } from 'node:path';
|
|
59
|
+
import { createInterface } from 'node:readline/promises';
|
|
60
|
+
import { stringbool } from 'zod';
|
|
59
61
|
import { colorItem, formatItems, streamRead } from '../../node.js';
|
|
60
62
|
import * as api from '../api.js';
|
|
61
63
|
import { getDirectory, resolveItem, resolvePathWithParent, syncCache, writeCache } from '../local.js';
|
|
@@ -102,6 +104,32 @@ export const remove = new Command('remove')
|
|
|
102
104
|
writeCache();
|
|
103
105
|
}
|
|
104
106
|
});
|
|
107
|
+
async function doUpload(local, name, size, parentId, text = name) {
|
|
108
|
+
const env_1 = { stack: [], error: void 0, hasError: false };
|
|
109
|
+
try {
|
|
110
|
+
const type = mime.getType(local) || 'application/octet-stream';
|
|
111
|
+
const _ = __addDisposableResource(env_1, io.start('Uploading ' + text), false);
|
|
112
|
+
const item = await api.createItem(streamRead(local), {
|
|
113
|
+
parentId,
|
|
114
|
+
name,
|
|
115
|
+
size,
|
|
116
|
+
type,
|
|
117
|
+
onProgress(uploaded, total) {
|
|
118
|
+
io.progress(uploaded, total, Math.round((uploaded / total) * 100) + '%', `${formatBytes(BigInt(uploaded))}/${formatBytes(BigInt(total))}`);
|
|
119
|
+
},
|
|
120
|
+
});
|
|
121
|
+
const { items } = await syncCache();
|
|
122
|
+
items.push(item);
|
|
123
|
+
return item;
|
|
124
|
+
}
|
|
125
|
+
catch (e_1) {
|
|
126
|
+
env_1.error = e_1;
|
|
127
|
+
env_1.hasError = true;
|
|
128
|
+
}
|
|
129
|
+
finally {
|
|
130
|
+
__disposeResources(env_1);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
105
133
|
export const upload = new Command('upload')
|
|
106
134
|
.description('Upload a file or folder')
|
|
107
135
|
.argument('<local>', 'local file or folder path to upload')
|
|
@@ -110,48 +138,76 @@ export const upload = new Command('upload')
|
|
|
110
138
|
.option('-r, --recursive', 'operate recursively on local directories')
|
|
111
139
|
.option('-T, --no-target-directory', 'always treat the remote path as a file')
|
|
112
140
|
.action(async (local, remotePath, opts) => {
|
|
113
|
-
const
|
|
141
|
+
const env_2 = { stack: [], error: void 0, hasError: false };
|
|
114
142
|
try {
|
|
115
143
|
const stats = fs.statSync(local);
|
|
116
144
|
const existingTarget = await resolveItem(remotePath);
|
|
117
145
|
let { parent, name } = await resolvePathWithParent(remotePath);
|
|
118
|
-
if (stats.isDirectory()) {
|
|
119
|
-
if (
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
146
|
+
if (!stats.isDirectory()) {
|
|
147
|
+
if (existingTarget?.type == 'inode/directory') {
|
|
148
|
+
if (opts.targetDirectory) {
|
|
149
|
+
parent = existingTarget;
|
|
150
|
+
name = basename(local);
|
|
151
|
+
}
|
|
152
|
+
else
|
|
153
|
+
throw 'Directory exists at remote path: ' + existingTarget.name;
|
|
154
|
+
}
|
|
155
|
+
else if (existingTarget && !opts.force)
|
|
156
|
+
throw 'File exists at remote path, use --force to overwrite it';
|
|
157
|
+
await doUpload(local, name, stats.size, parent?.id);
|
|
158
|
+
writeCache();
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
if (!opts.recursive)
|
|
162
|
+
throw '--recursive/-r not specified but the local path is a directory';
|
|
163
|
+
if (existingTarget)
|
|
164
|
+
throw 'Folder exists at remote path. Merging is not supported yet.';
|
|
165
|
+
const rl = __addDisposableResource(env_2, createInterface({
|
|
166
|
+
input: process.stdin,
|
|
167
|
+
output: process.stdout,
|
|
168
|
+
}), false);
|
|
169
|
+
const toUpload = [];
|
|
170
|
+
let sum = 0n;
|
|
171
|
+
// Sort to make sure directories come first, e.g. `example` and `example/duck`
|
|
172
|
+
for (const path of fs.readdirSync(local, { recursive: true, encoding: 'utf8' }).sort((a, b) => a.localeCompare(b))) {
|
|
173
|
+
const full = join(local, path);
|
|
174
|
+
const stats = fs.statSync(full, { bigint: true });
|
|
175
|
+
toUpload.push({ path, stats, full });
|
|
176
|
+
sum += stats.size;
|
|
123
177
|
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
178
|
+
const { data, error } = stringbool()
|
|
179
|
+
.default(false)
|
|
180
|
+
.safeParse(await rl.question(`Upload ${toUpload.length} files totaling ${formatBytes(sum)}? [y/N]: `).catch(() => io.exit('Aborted.')));
|
|
181
|
+
if (error || !data)
|
|
182
|
+
io.exit('Aborted.');
|
|
183
|
+
const { id } = await io.track('Creating directory', api.createDirectory(name, parent?.id));
|
|
184
|
+
const dirs = new Map();
|
|
185
|
+
for (const { path, stats, full } of toUpload) {
|
|
186
|
+
const { dir, base } = parse(path);
|
|
187
|
+
let parentId;
|
|
188
|
+
if (dir) {
|
|
189
|
+
const md = dirs.get(dir);
|
|
190
|
+
if (!md)
|
|
191
|
+
throw `Could not get metadata for the directory '${dir}'.`;
|
|
192
|
+
parentId = md.id;
|
|
128
193
|
}
|
|
129
194
|
else
|
|
130
|
-
|
|
195
|
+
parentId = id;
|
|
196
|
+
if (stats.isDirectory()) {
|
|
197
|
+
await io.track('Creating directory: ' + path, api.createDirectory(base, parentId));
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
await doUpload(full, base, Number(stats.size), parentId, path);
|
|
201
|
+
}
|
|
131
202
|
}
|
|
132
|
-
else if (existingTarget && !opts.force)
|
|
133
|
-
throw 'File exists at remote path, use --force to overwrite it';
|
|
134
|
-
const type = mime.getType(local) || 'application/octet-stream';
|
|
135
|
-
const _ = __addDisposableResource(env_1, io.start('Uploading ' + name), false);
|
|
136
|
-
const item = await api.createItem(streamRead(local), {
|
|
137
|
-
parentId: parent?.id,
|
|
138
|
-
name,
|
|
139
|
-
size: stats.size,
|
|
140
|
-
type,
|
|
141
|
-
onProgress(uploaded, total) {
|
|
142
|
-
io.progress(uploaded, total, Math.round((uploaded / total) * 100) + '%', `${formatBytes(BigInt(uploaded))}/${formatBytes(BigInt(total))}`);
|
|
143
|
-
},
|
|
144
|
-
});
|
|
145
|
-
const { items } = await syncCache();
|
|
146
|
-
items.push(item);
|
|
147
203
|
writeCache();
|
|
148
204
|
}
|
|
149
|
-
catch (
|
|
150
|
-
|
|
151
|
-
|
|
205
|
+
catch (e_2) {
|
|
206
|
+
env_2.error = e_2;
|
|
207
|
+
env_2.hasError = true;
|
|
152
208
|
}
|
|
153
209
|
finally {
|
|
154
|
-
__disposeResources(
|
|
210
|
+
__disposeResources(env_2);
|
|
155
211
|
}
|
|
156
212
|
});
|
|
157
213
|
export const download = new Command('download')
|
|
@@ -159,7 +215,7 @@ export const download = new Command('download')
|
|
|
159
215
|
.argument('<remote>', 'remote file path to download')
|
|
160
216
|
.argument('[local]', 'local path to save the file to')
|
|
161
217
|
.action(async (remotePath, localPath) => {
|
|
162
|
-
const
|
|
218
|
+
const env_3 = { stack: [], error: void 0, hasError: false };
|
|
163
219
|
try {
|
|
164
220
|
const item = await resolveItem(remotePath);
|
|
165
221
|
if (!item)
|
|
@@ -167,7 +223,7 @@ export const download = new Command('download')
|
|
|
167
223
|
if (item.type == 'inode/directory')
|
|
168
224
|
throw "Can't download directories yet.";
|
|
169
225
|
localPath ||= item.name;
|
|
170
|
-
const _ = __addDisposableResource(
|
|
226
|
+
const _ = __addDisposableResource(env_3, io.start('Downloading to ' + localPath), false);
|
|
171
227
|
const stream = await api.downloadItemStream(item.id);
|
|
172
228
|
const size = Number(item.size);
|
|
173
229
|
io.progress(0, size);
|
|
@@ -180,11 +236,11 @@ export const download = new Command('download')
|
|
|
180
236
|
}
|
|
181
237
|
fileStream.end();
|
|
182
238
|
}
|
|
183
|
-
catch (
|
|
184
|
-
|
|
185
|
-
|
|
239
|
+
catch (e_3) {
|
|
240
|
+
env_3.error = e_3;
|
|
241
|
+
env_3.hasError = true;
|
|
186
242
|
}
|
|
187
243
|
finally {
|
|
188
|
-
__disposeResources(
|
|
244
|
+
__disposeResources(env_3);
|
|
189
245
|
}
|
|
190
246
|
});
|
package/dist/server/item.d.ts
CHANGED
package/dist/server/item.js
CHANGED
|
@@ -153,7 +153,6 @@ export function startUpload(init, session, itemId) {
|
|
|
153
153
|
const hash = createHash('BLAKE2b512'), stream = Writable.toWeb(createWriteStream(file));
|
|
154
154
|
inProgress.set(token.toBase64(), {
|
|
155
155
|
hash,
|
|
156
|
-
hashStream: Writable.toWeb(hash),
|
|
157
156
|
file,
|
|
158
157
|
stream,
|
|
159
158
|
uploadedBytes: 0n,
|
package/dist/server/raw.js
CHANGED
|
@@ -63,16 +63,13 @@ addRoute({
|
|
|
63
63
|
const counter = new TransformStream({
|
|
64
64
|
transform(chunk, controller) {
|
|
65
65
|
actualSize += BigInt(chunk.length);
|
|
66
|
+
upload.hash.update(chunk);
|
|
66
67
|
controller.enqueue(chunk);
|
|
67
68
|
},
|
|
68
69
|
});
|
|
69
|
-
const [forFile, forHash] = request.body.pipeThrough(counter).tee();
|
|
70
70
|
/* @todo Figure out if we need to handle stream cancellation differently.
|
|
71
71
|
Right now an error with this chunk cancels the streams but may not cleanly fail the upload */
|
|
72
|
-
await
|
|
73
|
-
forFile.pipeTo(upload.stream, { preventClose: true }),
|
|
74
|
-
forHash.pipeTo(upload.hashStream, { preventClose: true }),
|
|
75
|
-
]);
|
|
72
|
+
await request.body.pipeThrough(counter).pipeTo(upload.stream, { preventClose: true });
|
|
76
73
|
if (actualSize != size) {
|
|
77
74
|
upload.remove();
|
|
78
75
|
await audit('storage_size_mismatch', upload.userId, { item: null });
|