You've already forked joplin
							
							
				mirror of
				https://github.com/laurent22/joplin.git
				synced 2025-10-31 00:07:48 +02:00 
			
		
		
		
	Desktop, Cli: Fixes #9484: Fixes issue with resources having no associated files when the RAW import process is interrupted
This commit is contained in:
		| @@ -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 | ||||
|  | ||||
|  | ||||
|  | ||||
| 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(``); | ||||
| 	}); | ||||
|  | ||||
| 	it('should handle duplicate names', async () => { | ||||
|   | ||||
| @@ -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; | ||||
| 	} | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user