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 { afterAllCleanUp, setupDatabaseAndSynchronizer, switchClient, syncTargetId, synchronizerStart, msleep } from '../testing/test-utils';
import BaseItem from './BaseItem'; import BaseItem from './BaseItem';
import Folder from './Folder'; import Folder from './Folder';
@@ -42,6 +43,19 @@ describe('BaseItem', () => {
expect(unserialized2.title).toBe(folder2.title); 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 () => { it('should correctly unserialize note timestamps', async () => {
const folder = await Folder.save({ title: 'folder' }); const folder = await Folder.save({ title: 'folder' });
const note = await Note.save({ title: 'note', parent_id: folder.id }); 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); 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 () => { it('should serialize geolocation fields', async () => {
const folder = await Folder.save({ title: 'folder' }); const folder = await Folder.save({ title: 'folder' });
let note = await Note.save({ title: 'note', parent_id: folder.id }); let note = await Note.save({ title: 'note', parent_id: folder.id });

View File

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