1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-11-23 22:36:32 +02:00

All: Fixes #13291: Improve performance of item deserialization (#13585)

This commit is contained in:
Henry Heino
2025-11-03 11:11:21 -08:00
committed by GitHub
parent cc9517f1a2
commit 6daa41ca66
2 changed files with 42 additions and 16 deletions

View File

@@ -1,3 +1,4 @@
import { Second } from '@joplin/utils/time';
import { afterAllCleanUp, setupDatabaseAndSynchronizer, switchClient, syncTargetId, synchronizerStart, msleep } from '../testing/test-utils';
import BaseItem from './BaseItem';
import Folder from './Folder';
@@ -42,6 +43,19 @@ describe('BaseItem', () => {
expect(unserialized2.title).toBe(folder2.title);
});
it.each([
'',
'\n\na\nb\nc\nç\nTest!\n Testing. \n',
'Test! ☺',
'Test! ☺\n\n\n',
])('should not modify body when unserializing (body: %j)', async (body) => {
const note = await Note.save({ title: 'note1', body });
expect(await Note.unserialize(await Note.serialize(note))).toMatchObject({
body,
});
});
it('should correctly unserialize note timestamps', async () => {
const folder = await Folder.save({ title: 'folder' });
const note = await Note.save({ title: 'note', parent_id: folder.id });
@@ -55,6 +69,22 @@ describe('BaseItem', () => {
expect(unserialized.user_updated_time).toEqual(note.user_updated_time);
});
it('should unserialize a very large note quickly', async () => {
const folder = await Folder.save({ title: 'folder' });
const note = await Note.save({ title: 'note', parent_id: folder.id });
const serialized = await Note.serialize({
...note,
// 2 MiB
body: '\n.'.repeat(1 * 1024 * 1024),
});
const start = performance.now();
await Note.unserialize(serialized);
// Locally, this passes in in < 2s, so 30s should be a safe upper bound.
expect(performance.now() - start).toBeLessThan(30 * Second);
});
it('should serialize geolocation fields', async () => {
const folder = await Folder.save({ title: 'folder' });
let note = await Note.save({ title: 'note', parent_id: folder.id });

View File

@@ -578,28 +578,24 @@ export default class BaseItem extends BaseModel {
const lines = content.split('\n');
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
let output: any = {};
let state = 'readingProps';
const body: string[] = [];
let body: string[] = [];
for (let i = lines.length - 1; i >= 0; i--) {
let line = lines[i];
if (state === 'readingProps') {
line = line.trim();
line = line.trim();
if (line === '') {
state = 'readingBody';
continue;
}
const p = line.indexOf(':');
if (p < 0) throw new Error(`Invalid property format: ${line}: ${content}`);
const key = line.substr(0, p).trim();
const value = line.substr(p + 1).trim();
output[key] = value;
} else if (state === 'readingBody') {
body.splice(0, 0, line);
// Props are separated from the body by a single blank line
if (line === '') {
body = lines.slice(0, i);
break;
}
const p = line.indexOf(':');
if (p < 0) throw new Error(`Invalid property format: ${line}: ${content}`);
const key = line.substr(0, p).trim();
const value = line.substr(p + 1).trim();
output[key] = value;
}
if (!output.type_) throw new Error(`Missing required property: type_: ${content}`);