Desktop, Cli: Fixes #9484: Fixes issue with resources having no associated files when the RAW import process is interrupted

pull/9514/head
Laurent Cozic 2023-12-14 17:17:21 +00:00
parent b11006c3a7
commit f244f44c7b
2 changed files with 54 additions and 21 deletions

View File

@ -1,10 +1,12 @@
import { writeFile, remove } from 'fs-extra';
import { writeFile, remove, mkdirp } from 'fs-extra';
import Folder from '../../models/Folder';
import Note from '../../models/Note';
import { createTempDir, setupDatabaseAndSynchronizer, switchClient } from '../../testing/test-utils';
import { FolderEntity, NoteEntity } from '../database/types';
import { createTempDir, setupDatabaseAndSynchronizer, supportDir, switchClient } from '../../testing/test-utils';
import { FolderEntity, NoteEntity, ResourceEntity } from '../database/types';
import InteropService from './InteropService';
import { ImportOptions } from './types';
import { copyFile } from 'fs/promises';
import Resource from '../../models/Resource';
const extractId = (rawContent: string): string => {
const lines = rawContent.split('\n');
@ -52,6 +54,8 @@ type_: 2`;
const rawNote1 = `Note 1
![photo.jpg](:/b3ab7288b56d4dbf884e73bea1248dd1)
id: 7e5e0c7202414cd38e2db12e2e92ac91
parent_id: 15fa3f4abe89429b8836cdc5859fe74b
created_time: 2022-08-29T14:43:06.961Z
@ -110,13 +114,39 @@ conflict_original_id:
master_key_id:
type_: 1`;
const rawResource = `Resource 1
id: b3ab7288b56d4dbf884e73bea1248dd1
mime: image/jpeg
filename:
created_time: 2023-12-14T16:54:47.661Z
updated_time: 2023-12-14T16:54:49.956Z
user_created_time: 2023-12-14T16:54:47.661Z
user_updated_time: 2023-12-14T16:54:49.956Z
file_extension: jpg
encryption_cipher_text:
encryption_applied: 0
encryption_blob_encrypted: 0
size: 2720
is_shared: 0
share_id:
master_key_id:
user_data:
blob_updated_time: 1702572887661
type_: 4`;
let tempDir: string;
const createFiles = async () => {
const resourceDir = `${tempDir}/resources`;
await mkdirp(resourceDir);
await writeFile(makeFilePath(tempDir, rawFolder1), rawFolder1);
await writeFile(makeFilePath(tempDir, rawFolder2), rawFolder2);
await writeFile(makeFilePath(tempDir, rawNote1), rawNote1);
await writeFile(makeFilePath(tempDir, rawNote2), rawNote2);
await writeFile(makeFilePath(tempDir, rawResource), rawResource);
await copyFile(`${supportDir}/photo.jpg`, `${resourceDir}/${extractId(rawResource)}.jpg`);
};
describe('InteropService_Importer_Raw', () => {
@ -146,21 +176,32 @@ describe('InteropService_Importer_Raw', () => {
const folder2: FolderEntity = await Folder.loadByTitle('sub-notebook');
const note1: NoteEntity = await Note.loadByTitle('Note 1');
const note2: NoteEntity = await Note.loadByTitle('Note 2');
const resource: ResourceEntity = await Resource.loadByTitle('Resource 1');
// Check that all items have been created
expect(folder1).toBeTruthy();
expect(folder2).toBeTruthy();
expect(note1).toBeTruthy();
expect(note2).toBeTruthy();
expect(resource).toBeTruthy();
// Check that all IDs have been replaced - we don't keep the original
// IDs when importing data.
expect(folder1.id).not.toBe(extractId(rawFolder1));
expect(folder2.id).not.toBe(extractId(rawFolder2));
expect(note1.id).not.toBe(extractId(rawNote1));
expect(note2.id).not.toBe(extractId(rawNote2));
expect(resource.id).not.toBe(extractId(rawResource));
// Check that the notes are linked to the correct folder IDs
expect(folder1.parent_id).toBe('');
expect(folder2.parent_id).toBe(folder1.id);
expect(note1.parent_id).toBe(folder1.id);
expect(note2.parent_id).toBe(folder2.id);
// Check that the resource is still linked to the note and with the
// correct ID.
expect(note1.body).toBe(`![photo.jpg](:/${resource.id})`);
});
it('should handle duplicate names', async () => {

View File

@ -106,9 +106,19 @@ export default class InteropService_Importer_Raw extends InteropService_Importer
item.title = await Folder.findUniqueItemTitle(item.title, item.parent_id);
} else if (itemType === BaseModel.TYPE_RESOURCE) {
const sourceId = item.id;
if (!itemIdMap[item.id]) itemIdMap[item.id] = uuid.create();
item.id = itemIdMap[item.id];
createdResources[item.id] = item;
const sourceResourcePath = `${this.sourcePath_}/resources/${Resource.filename({ ...item, id: sourceId })}`;
const destPath = Resource.fullPath(item);
if (await shim.fsDriver().exists(sourceResourcePath)) {
await shim.fsDriver().copy(sourceResourcePath, destPath);
} else {
result.warnings.push(sprintf('Could not find resource file: %s', sourceResourcePath));
}
} else if (itemType === BaseModel.TYPE_TAG) {
const tag = await Tag.loadByTitle(item.title);
if (tag) {
@ -149,24 +159,6 @@ export default class InteropService_Importer_Raw extends InteropService_Importer
await NoteTag.save(noteTag, { isNew: true });
}
if (await shim.fsDriver().isDirectory(`${this.sourcePath_}/resources`)) {
const resourceStats = await shim.fsDriver().readDirStats(`${this.sourcePath_}/resources`);
for (let i = 0; i < resourceStats.length; i++) {
const resourceFilePath = `${this.sourcePath_}/resources/${resourceStats[i].path}`;
const oldId = Resource.pathToId(resourceFilePath);
const newId = itemIdMap[oldId];
if (!newId) {
result.warnings.push(sprintf('Resource file is not referenced in any note and so was not imported: %s', oldId));
continue;
}
const resource = createdResources[newId];
const destPath = Resource.fullPath(resource);
await shim.fsDriver().copy(resourceFilePath, destPath);
}
}
return result;
}
}