storage: add copyDir

we changed listDir in c44863a9bb to list
a directory . this broke copy for files since a '/' is added when listing
the file.
This commit is contained in:
Girish Ramakrishnan
2025-08-25 23:45:14 +02:00
parent cdda8649fc
commit 31df40a841
7 changed files with 90 additions and 14 deletions

View File

@@ -16,6 +16,7 @@ exports = module.exports = {
exists,
download,
copy,
copyDir,
listDir,
@@ -376,7 +377,7 @@ function encodeCopySource(bucket, path) {
return `/${bucket}/${output}`;
}
async function copyFile(apiConfig, fullFromPath, fullToPath, fileSize, progressCallback) {
async function copyInternal(apiConfig, fullFromPath, fullToPath, fileSize, progressCallback) {
assert.strictEqual(typeof apiConfig, 'object');
assert.strictEqual(typeof fullFromPath, 'string');
assert.strictEqual(typeof fullToPath, 'string');
@@ -477,19 +478,40 @@ async function copy(apiConfig, fromPath, toPath, progressCallback) {
assert.strictEqual(typeof toPath, 'string');
assert.strictEqual(typeof progressCallback, 'function');
const fullFromPath = path.join(apiConfig.prefix, fromPath);
const fullToPath = path.join(apiConfig.prefix, toPath);
const params = {
Bucket: apiConfig.bucket,
Key: fullFromPath
};
const s3 = createS3Client(apiConfig, { retryStrategy: RETRY_STRATEGY }); // https://docs.aws.amazon.com/sdkref/latest/guide/feature-retry-behavior.html
const [error, data] = await safe(s3.headObject(params));
if (error && S3_NOT_FOUND(error)) throw new BoxError(BoxError.NOT_FOUND, `Path ${fromPath} not found`);
if (error) throw new BoxError(BoxError.EXTERNAL_ERROR, `Error headObject ${fromPath}. ${formatError(error)}`);
return await copyInternal(apiConfig, fullFromPath, fullToPath, data.ContentLength, progressCallback);
}
async function copyDir(apiConfig, fromPath, toPath, progressCallback) {
assert.strictEqual(typeof apiConfig, 'object');
assert.strictEqual(typeof fromPath, 'string');
assert.strictEqual(typeof toPath, 'string');
assert.strictEqual(typeof progressCallback, 'function');
let total = 0;
const concurrency = apiConfig.limits?.copyConcurrency || (apiConfig._provider === 's3' ? 500 : 10);
progressCallback({ message: `Copying with concurrency of ${concurrency}` });
progressCallback({ message: `Copying ${fromPath} to ${toPath} with concurrency of ${concurrency}` });
let marker = null;
while (true) {
const batch = await listDir(apiConfig, fromPath, 1000, marker); // returned entries are relative to fromPath
const batch = await listDir(apiConfig, fromPath, 1000, marker); // returned entries are relative to prefix
total += batch.entries.length;
progressCallback({ message: `Copying files from ${total-batch.entries.length}-${total}` });
await async.eachLimit(batch.entries, concurrency, async (entry) => {
const fullFromPath = path.join(apiConfig.prefix, entry.path);
const fullToPath = path.join(apiConfig.prefix, toPath, path.relative(fromPath, entry.path));
await copyFile(apiConfig, fullFromPath, fullToPath, entry.size, progressCallback);
await copyInternal(apiConfig, fullFromPath, fullToPath, entry.size, progressCallback);
});
if (!batch.marker) break;
marker = batch.marker;
@@ -543,7 +565,7 @@ async function removeDir(apiConfig, remotePathPrefix, progressCallback) {
let total = 0;
let marker = null;
while (true) {
const batch = await listDir(apiConfig, remotePathPrefix, 1000, marker); // returns entries relative to remotePathPrefix
const batch = await listDir(apiConfig, remotePathPrefix, 1000, marker); // returns entries relative to (root) prefix
const entries = batch.entries;
total += entries.length;