Skip to content

Makes document attachment usage consider external attachments #1600

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 24 commits into from
Jun 5, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
3938de5
Updates attachment size calculation to consider external
Spoffy May 5, 2025
9b5638b
Updates attachment size on restored
Spoffy May 5, 2025
49e5890
Adds tests for attachment file sizes being restored
Spoffy May 5, 2025
b5c102d
Extracts file size updating into method
Spoffy May 5, 2025
09692d4
Removes archive determinism change
Spoffy May 5, 2025
922a781
Makes attachment transfer only update file size if changed
Spoffy May 9, 2025
85e1469
Adds DocumentUsage.ts
Spoffy May 12, 2025
aacb5ee
Adds document usage tests
Spoffy May 10, 2025
bc5ae34
Adds helpers for testing external attachments
Spoffy May 11, 2025
8776ddb
Reduces DocumentUsage tests to bare minimum
Spoffy May 13, 2025
eb2e61d
Renames DocumentUsage.ts to DocUsageTracking.ts
Spoffy May 13, 2025
572b042
Avoid accessing DB if nothing to do
Spoffy May 31, 2025
90889ae
Avoids SaaS linter error
Spoffy May 31, 2025
545b753
Renames doc in test
Spoffy May 31, 2025
3d4c312
Removes unnecessary login fix
Spoffy May 31, 2025
aa16df7
Integrates env saving into existing funcs
Spoffy May 31, 2025
a3d5583
Merge branch 'main' into spoffy/attachment-size-calc
Spoffy May 31, 2025
58c797f
Merge branch 'main' into spoffy/attachment-size-calc
Spoffy Jun 2, 2025
054792a
Revert "Integrates env saving into existing funcs"
Spoffy Jun 2, 2025
e8acbee
Refactors external attachments helper into own file
Spoffy Jun 2, 2025
e6f70e8
Inlines function in DocUsageTracking tests
Spoffy Jun 2, 2025
a55f7e6
Adds explicit error for sandbox problems during attachment size update
Spoffy Jun 2, 2025
6a75682
Cleans up DocUsageTracking tests
Spoffy Jun 3, 2025
e121a2b
Rethrows errors during file size update
Spoffy Jun 3, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions app/server/lib/ActiveDoc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ import {LogMethods} from 'app/server/lib/LogMethods';
import {ISandboxOptions} from 'app/server/lib/NSandbox';
import {NullSandbox, UnavailableSandboxMethodError} from 'app/server/lib/NullSandbox';
import {DocRequests} from 'app/server/lib/Requests';
import {SandboxError} from 'app/server/lib/sandboxUtil';
import {
getDocSessionAccess,
getDocSessionAccessOrNull,
Expand Down Expand Up @@ -1054,6 +1055,8 @@ export class ActiveDoc extends EventEmitter {
unused: 0,
};

const sizesToUpdate = new Map<string, number>();

await unpackTarArchive(tarFile, async (file) => {
try {
const fileIdent = archiveFilePathToAttachmentIdent(file.path);
Expand All @@ -1063,6 +1066,7 @@ export class ActiveDoc extends EventEmitter {
fallbackStoreId,
);
if (isAdded) {
sizesToUpdate.set(fileIdent, file.size);
results.added += 1;
} else {
results.unused += 1;
Expand All @@ -1076,6 +1080,11 @@ export class ActiveDoc extends EventEmitter {
}
});

// This updates _grist_Attachments.fileSize with the size of the uploaded files.
// This prevents a loophole where a user has altered `fileSize`, imported the altered document,
// then restored the originals.
await this._updateAttachmentFileSizesUsingFileIdent(docSession, sizesToUpdate);

return results;
}

Expand Down Expand Up @@ -2614,6 +2623,45 @@ export class ActiveDoc extends EventEmitter {
}];
}

private async _updateAttachmentFileSizesUsingFileIdent(docSession: OptDocSession,
newFileSizesByFileIdent: Map<string, number>): Promise<void> {
const rowIdsToUpdate: number[] = [];
const newFileSizesForRows: CellValue[] = [];
const attachments = this.docData?.getMetaTable("_grist_Attachments").getRecords();
for (const attachmentRec of attachments ?? []) {
const newSize = newFileSizesByFileIdent.get(attachmentRec.fileIdent);
if (newSize && newSize !== attachmentRec.fileSize) {
rowIdsToUpdate.push(attachmentRec.id);
newFileSizesForRows.push(newSize);
}
}

if (rowIdsToUpdate.length === 0) {
return;
}

const action: BulkUpdateRecord = ['BulkUpdateRecord', '_grist_Attachments', rowIdsToUpdate, {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it worth checking if rowIdsToUpdate is empty and returning earlier (without all external calls)?

fileSize: newFileSizesForRows
}];

try {
await this._applyUserActionsWithExtendedOptions(
docSession,
[action],
{attachment: true},
);
} catch (e) {
if (e instanceof SandboxError) {
this._log.error(null, "Attachment sizes could not be updated due to a sandbox error: ", e);
throw new Error("Attachment sizes could not be updated due to a sandbox error", { cause: e });
}
throw e;
}

// Updates doc's overall attachment usage to reflect any changes to file sizes.
await this._updateAttachmentsSize();
}

/**
* If the software is newer than the document, migrate the document by fetching all tables, and
* giving them to the sandbox so that it can produce migration actions.
Expand Down
3 changes: 3 additions & 0 deletions app/server/lib/Archive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ export function create_tar_archive(
export interface UnpackedFile {
path: string;
data: stream.Readable;
size: number;
}

export async function unpackTarArchive(
Expand All @@ -144,6 +145,8 @@ export async function unpackTarArchive(
onFile({
path: header.name,
data: contentStream,
// Realistically this should never be undefined - it's mandatory for files in a .tar archive
size: header.size ?? 0,
})
// No sensible behaviour when an error is thrown by onFile - it's onFile's responsibility
// to handle it.
Expand Down
9 changes: 8 additions & 1 deletion app/server/lib/DocStorage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1385,7 +1385,14 @@ export class DocStorage implements ISQLiteDB, OnDemandStorage {
-- to make LENGTH() quickly read the stored length instead of actually reading the blob data.
-- We use LENGTH() in the first place instead of _grist_Attachments.fileSize because the latter can
-- be changed by users.
SELECT MAX(LENGTH(files.data)) AS len
-- External attachments have no internal blob data, so we need to use fileSize for those.
-- To avoid tampering with fileSize in offline records, we update it whenever files are
-- uploaded or retrieved.
SELECT
CASE WHEN files.storageId IS NOT NULL
THEN MAX(meta.fileSize)
ELSE MAX(LENGTH(files.data))
END AS len
FROM _gristsys_Files AS files
JOIN _grist_Attachments AS meta
ON meta.fileIdent = files.ident
Expand Down
Loading