When downloading a folder with a great many entries below, time to first byte can cause a tcp disconnection for timeout by a reverse proxy, this starts the download asap, and ignores unreadable entries because of AV or rights. It tries to generate a valid zip even if some entries are missing for those reasons
This commit is contained in:
@@ -1505,10 +1505,17 @@ export class DocumentsService {
|
|||||||
if (token) throw new CrudException("Invalid token", 401);
|
if (token) throw new CrudException("Invalid token", 401);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param id DriveItemID to download. If it's a folder, a zip file will be returned
|
||||||
|
* @param versionId Optional specific version to download
|
||||||
|
* @param {Function} beginArchiveTransmit If a folder,
|
||||||
|
* called when the zip file can begin streaming, otherwise not called at all.
|
||||||
|
*/
|
||||||
download = async (
|
download = async (
|
||||||
id: string,
|
id: string,
|
||||||
versionId: string | null,
|
versionId: string | null,
|
||||||
|
beginArchiveTransmit: (readable: archiver.Archiver) => void,
|
||||||
context: DriveExecutionContext,
|
context: DriveExecutionContext,
|
||||||
): Promise<{
|
): Promise<{
|
||||||
archive?: archiver.Archiver;
|
archive?: archiver.Archiver;
|
||||||
@@ -1522,7 +1529,7 @@ export class DocumentsService {
|
|||||||
const item = await this.get(id, null, context);
|
const item = await this.get(id, null, context);
|
||||||
|
|
||||||
if (item.item.is_directory) {
|
if (item.item.is_directory) {
|
||||||
return { archive: await this.createZip([id], context) };
|
return { archive: await this.createZip([id], beginArchiveTransmit, context) };
|
||||||
}
|
}
|
||||||
|
|
||||||
let version = item.item.last_version_cache;
|
let version = item.item.last_version_cache;
|
||||||
@@ -1541,11 +1548,13 @@ export class DocumentsService {
|
|||||||
* Creates a zip archive containing the drive items.
|
* Creates a zip archive containing the drive items.
|
||||||
*
|
*
|
||||||
* @param {string[]} ids - the drive item list
|
* @param {string[]} ids - the drive item list
|
||||||
|
* @param {Function} beginArchiveTransmit - Called when the zip file can begin streaming
|
||||||
* @param {DriveExecutionContext} context - the execution context
|
* @param {DriveExecutionContext} context - the execution context
|
||||||
* @returns {Promise<archiver.Archiver>} the created archive.
|
* @returns {Promise<archiver.Archiver>} the created archive.
|
||||||
*/
|
*/
|
||||||
createZip = async (
|
createZip = async (
|
||||||
ids: string[] = [],
|
ids: string[] = [],
|
||||||
|
beginArchiveTransmit: (archive: archiver.Archiver) => void,
|
||||||
context: DriveExecutionContext,
|
context: DriveExecutionContext,
|
||||||
): Promise<archiver.Archiver> => {
|
): Promise<archiver.Archiver> => {
|
||||||
if (!context) {
|
if (!context) {
|
||||||
@@ -1561,19 +1570,31 @@ export class DocumentsService {
|
|||||||
this.logger.error("error while creating ZIP file: ", error);
|
this.logger.error("error while creating ZIP file: ", error);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let didBeginTransmission = false;
|
||||||
for (const id of ids) {
|
for (const id of ids) {
|
||||||
if (!(await checkAccess(id, null, "read", this.repository, context))) {
|
if (!(await checkAccess(id, null, "read", this.repository, context))) {
|
||||||
this.logger.warn(`not enough permissions to download ${id}, skipping`);
|
this.logger.warn({ id }, `not enough permissions to download ${id}, skipping`);
|
||||||
return;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await addDriveItemToArchive(id, null, archive, this.repository, context);
|
await addDriveItemToArchive(
|
||||||
} catch (error) {
|
id,
|
||||||
console.error(error);
|
null,
|
||||||
this.logger.warn("failed to add item to archive", error);
|
archive,
|
||||||
|
archive => {
|
||||||
|
if (didBeginTransmission) return;
|
||||||
|
didBeginTransmission = true;
|
||||||
|
beginArchiveTransmit(archive);
|
||||||
|
},
|
||||||
|
this.repository,
|
||||||
|
context,
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
this.logger.warn({ err, id }, "failed to add item to archive");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (!didBeginTransmission) beginArchiveTransmit(archive);
|
||||||
|
|
||||||
//TODO[ASH] why do we need this call??
|
//TODO[ASH] why do we need this call??
|
||||||
archive.finalize();
|
archive.finalize();
|
||||||
|
|||||||
@@ -345,6 +345,9 @@ export const addDriveItemToArchive = async (
|
|||||||
id: string,
|
id: string,
|
||||||
entity: DriveFile | null,
|
entity: DriveFile | null,
|
||||||
archive: archiver.Archiver,
|
archive: archiver.Archiver,
|
||||||
|
beginArchiveTransmit:
|
||||||
|
| "ONLY_SET_THIS_VALUE_IF_ALREADY_CALLED"
|
||||||
|
| ((archive: archiver.Archiver) => void),
|
||||||
repository: Repository<DriveFile>,
|
repository: Repository<DriveFile>,
|
||||||
context: CompanyExecutionContext,
|
context: CompanyExecutionContext,
|
||||||
prefix?: string,
|
prefix?: string,
|
||||||
@@ -368,6 +371,10 @@ export const addDriveItemToArchive = async (
|
|||||||
}
|
}
|
||||||
|
|
||||||
archive.append(file.file, { name: item.name, prefix: prefix ?? "" });
|
archive.append(file.file, { name: item.name, prefix: prefix ?? "" });
|
||||||
|
if (beginArchiveTransmit !== "ONLY_SET_THIS_VALUE_IF_ALREADY_CALLED") {
|
||||||
|
beginArchiveTransmit(archive);
|
||||||
|
beginArchiveTransmit = "ONLY_SET_THIS_VALUE_IF_ALREADY_CALLED";
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
let nextPage = "";
|
let nextPage = "";
|
||||||
@@ -384,6 +391,7 @@ export const addDriveItemToArchive = async (
|
|||||||
child.id,
|
child.id,
|
||||||
child,
|
child,
|
||||||
archive,
|
archive,
|
||||||
|
beginArchiveTransmit,
|
||||||
repository,
|
repository,
|
||||||
context,
|
context,
|
||||||
`${prefix || ""}${item.name}/`,
|
`${prefix || ""}${item.name}/`,
|
||||||
|
|||||||
@@ -551,21 +551,21 @@ export class DocumentsController {
|
|||||||
const archiveOrFile = await globalResolver.services.documents.documents.download(
|
const archiveOrFile = await globalResolver.services.documents.documents.download(
|
||||||
id,
|
id,
|
||||||
versionId,
|
versionId,
|
||||||
|
archive => {
|
||||||
|
archive.on("finish", () => {
|
||||||
|
response.status(200);
|
||||||
|
});
|
||||||
|
|
||||||
|
archive.on("error", () => {
|
||||||
|
response.internalServerError();
|
||||||
|
});
|
||||||
|
|
||||||
|
archive.pipe(response.raw);
|
||||||
|
},
|
||||||
context,
|
context,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (archiveOrFile.archive) {
|
if (archiveOrFile.archive) {
|
||||||
const archive = archiveOrFile.archive;
|
|
||||||
|
|
||||||
archive.on("finish", () => {
|
|
||||||
response.status(200);
|
|
||||||
});
|
|
||||||
|
|
||||||
archive.on("error", () => {
|
|
||||||
response.internalServerError();
|
|
||||||
});
|
|
||||||
|
|
||||||
archive.pipe(response.raw);
|
|
||||||
return response;
|
return response;
|
||||||
} else if (archiveOrFile.file) {
|
} else if (archiveOrFile.file) {
|
||||||
const data = archiveOrFile.file;
|
const data = archiveOrFile.file;
|
||||||
@@ -623,22 +623,27 @@ export class DocumentsController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const archive = await globalResolver.services.documents.documents.createZip(ids, context);
|
await globalResolver.services.documents.documents.createZip(
|
||||||
reply.raw.setHeader(
|
ids,
|
||||||
"content-disposition",
|
archive => {
|
||||||
formatAttachmentContentDispositionHeader("twake_drive.zip"),
|
reply.raw.setHeader(
|
||||||
|
"content-disposition",
|
||||||
|
formatAttachmentContentDispositionHeader("twake_drive.zip"),
|
||||||
|
);
|
||||||
|
|
||||||
|
archive.on("finish", () => {
|
||||||
|
reply.status(200);
|
||||||
|
});
|
||||||
|
|
||||||
|
archive.on("error", () => {
|
||||||
|
reply.internalServerError();
|
||||||
|
});
|
||||||
|
|
||||||
|
archive.pipe(reply.raw);
|
||||||
|
},
|
||||||
|
context,
|
||||||
);
|
);
|
||||||
|
|
||||||
archive.on("finish", () => {
|
|
||||||
reply.status(200);
|
|
||||||
});
|
|
||||||
|
|
||||||
archive.on("error", () => {
|
|
||||||
reply.internalServerError();
|
|
||||||
});
|
|
||||||
|
|
||||||
archive.pipe(reply.raw);
|
|
||||||
|
|
||||||
return reply;
|
return reply;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error({ error: `${error}` }, "failed to send zip file");
|
logger.error({ error: `${error}` }, "failed to send zip file");
|
||||||
|
|||||||
Reference in New Issue
Block a user