fix(foundry): copy playlists before deleting the unpacked archive
Archive import removed the temp folder before playlist tracks were read, so zip imports with music failed. The dialog hint now names .zip / .fvtt instead of .world. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+8
-4
@@ -1893,7 +1893,9 @@ async function main() {
|
|||||||
registerHandler(ipcChannels.project.importFoundry, async ({ sourcePath }) => {
|
registerHandler(ipcChannels.project.importFoundry, async ({ sourcePath }) => {
|
||||||
emitZipProgress({ kind: 'import', stage: 'copy', percent: 0, detail: 'Импорт Foundry…' });
|
emitZipProgress({ kind: 'import', stage: 'copy', percent: 0, detail: 'Импорт Foundry…' });
|
||||||
try {
|
try {
|
||||||
const { project, pendingAppPlaylists } = await projectStore.importProjectFromFoundry(sourcePath, (p) => {
|
const { project } = await projectStore.importProjectFromFoundry(
|
||||||
|
sourcePath,
|
||||||
|
(p) => {
|
||||||
emitZipProgress({
|
emitZipProgress({
|
||||||
kind: 'import',
|
kind: 'import',
|
||||||
stage:
|
stage:
|
||||||
@@ -1901,8 +1903,9 @@ async function main() {
|
|||||||
percent: p.percent,
|
percent: p.percent,
|
||||||
...(p.detail ? { detail: p.detail } : null),
|
...(p.detail ? { detail: p.detail } : null),
|
||||||
});
|
});
|
||||||
});
|
},
|
||||||
if (playlistsStore && pendingAppPlaylists.length > 0) {
|
async (pendingAppPlaylists) => {
|
||||||
|
if (!playlistsStore || pendingAppPlaylists.length === 0) return;
|
||||||
for (const pl of pendingAppPlaylists) {
|
for (const pl of pendingAppPlaylists) {
|
||||||
const created = await playlistsStore.createPlaylist(pl.name);
|
const created = await playlistsStore.createPlaylist(pl.name);
|
||||||
const paths = pl.tracks.map((t) => t.absPath);
|
const paths = pl.tracks.map((t) => t.absPath);
|
||||||
@@ -1911,7 +1914,8 @@ async function main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
emitPlaylistsState();
|
emitPlaylistsState();
|
||||||
}
|
},
|
||||||
|
);
|
||||||
emitZipProgress({ kind: 'import', stage: 'done', percent: 100, detail: 'Готово' });
|
emitZipProgress({ kind: 'import', stage: 'done', percent: 100, detail: 'Готово' });
|
||||||
emitSessionState();
|
emitSessionState();
|
||||||
return { project };
|
return { project };
|
||||||
|
|||||||
@@ -67,6 +67,15 @@ void test('zipStore: closeOpenProject is serialized with open on projectSwitchCh
|
|||||||
assert.match(src, /Открытие проекта отменено/);
|
assert.match(src, /Открытие проекта отменено/);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
void test('zipStore: Foundry archive playlists are copied before temp unzip cleanup', () => {
|
||||||
|
const src = fs.readFileSync(path.join(here, 'zipStore.ts'), 'utf8');
|
||||||
|
const fn = src.slice(src.indexOf('async importProjectFromFoundry'));
|
||||||
|
const body = fn.slice(0, fn.indexOf('\n async exportProjectZipToPath'));
|
||||||
|
const copyAt = body.indexOf('await importPlaylists(pendingAppPlaylists)');
|
||||||
|
const cleanupAt = body.indexOf('await loaded.cleanup()');
|
||||||
|
assert.ok(copyAt > 0 && cleanupAt > copyAt);
|
||||||
|
});
|
||||||
|
|
||||||
void test('zipStore: exportProjectZipToPath flushes saveNow for currently open project', () => {
|
void test('zipStore: exportProjectZipToPath flushes saveNow for currently open project', () => {
|
||||||
const src = fs.readFileSync(path.join(here, 'zipStore.ts'), 'utf8');
|
const src = fs.readFileSync(path.join(here, 'zipStore.ts'), 'utf8');
|
||||||
assert.match(src, /async exportProjectZipToPath/);
|
assert.match(src, /async exportProjectZipToPath/);
|
||||||
|
|||||||
@@ -108,6 +108,7 @@ import {
|
|||||||
buildProjectFromFoundryDocuments,
|
buildProjectFromFoundryDocuments,
|
||||||
loadFoundryDocumentsForImport,
|
loadFoundryDocumentsForImport,
|
||||||
type FoundryImportProgress,
|
type FoundryImportProgress,
|
||||||
|
type FoundryPendingAppPlaylist,
|
||||||
} from '../foundry/foundryImport';
|
} from '../foundry/foundryImport';
|
||||||
import { getAppSemanticVersion } from '../versionInfo';
|
import { getAppSemanticVersion } from '../versionInfo';
|
||||||
|
|
||||||
@@ -2129,7 +2130,8 @@ export class ZipProjectStore {
|
|||||||
async importProjectFromFoundry(
|
async importProjectFromFoundry(
|
||||||
sourcePath: string,
|
sourcePath: string,
|
||||||
onProgress?: (p: FoundryImportProgress) => void,
|
onProgress?: (p: FoundryImportProgress) => void,
|
||||||
): Promise<{ project: Project; pendingAppPlaylists: import('../foundry/foundryImport').FoundryPendingAppPlaylist[] }> {
|
importPlaylists?: (lists: FoundryPendingAppPlaylist[]) => Promise<void>,
|
||||||
|
): Promise<{ project: Project; pendingAppPlaylists: FoundryPendingAppPlaylist[] }> {
|
||||||
await this.ensureRoots();
|
await this.ensureRoots();
|
||||||
const loaded = await loadFoundryDocumentsForImport(sourcePath, onProgress);
|
const loaded = await loadFoundryDocumentsForImport(sourcePath, onProgress);
|
||||||
try {
|
try {
|
||||||
@@ -2159,6 +2161,10 @@ export class ZipProjectStore {
|
|||||||
await this.writeCacheProject(cacheDir, project);
|
await this.writeCacheProject(cacheDir, project);
|
||||||
onProgress?.({ stage: 'zip', percent: 90, detail: 'Сборка проекта…' });
|
onProgress?.({ stage: 'zip', percent: 90, detail: 'Сборка проекта…' });
|
||||||
await this.enqueuePack(cacheDir, zipPath);
|
await this.enqueuePack(cacheDir, zipPath);
|
||||||
|
// Архив распакован во временную папку. Плейлисты читают её по absPath — копировать до cleanup.
|
||||||
|
if (importPlaylists && pendingAppPlaylists.length > 0) {
|
||||||
|
await importPlaylists(pendingAppPlaylists);
|
||||||
|
}
|
||||||
onProgress?.({ stage: 'done', percent: 100, detail: 'Готово' });
|
onProgress?.({ stage: 'done', percent: 100, detail: 'Готово' });
|
||||||
return { project: this.openProject.project, pendingAppPlaylists };
|
return { project: this.openProject.project, pendingAppPlaylists };
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -355,7 +355,7 @@ export const EDITOR_MESSAGES: Record<EditorLocale, Record<string, string>> = {
|
|||||||
|
|
||||||
'foundryImport.title': 'Импорт из Foundry',
|
'foundryImport.title': 'Импорт из Foundry',
|
||||||
'foundryImport.hint':
|
'foundryImport.hint':
|
||||||
'Выберите папку или архив мира (.world) либо модуля Foundry VTT (версии 11+). Будет создан новый проект.',
|
'Выберите папку мира или модуля Foundry VTT (версия 11+) либо архив (.zip / .fvtt). Будет создан новый проект.',
|
||||||
'foundryImport.sourceType': 'ТИП ИСТОЧНИКА',
|
'foundryImport.sourceType': 'ТИП ИСТОЧНИКА',
|
||||||
'foundryImport.folder': 'Папка',
|
'foundryImport.folder': 'Папка',
|
||||||
'foundryImport.archive': 'Архив (.zip / .fvtt)',
|
'foundryImport.archive': 'Архив (.zip / .fvtt)',
|
||||||
@@ -1182,7 +1182,7 @@ export const EDITOR_MESSAGES: Record<EditorLocale, Record<string, string>> = {
|
|||||||
|
|
||||||
'foundryImport.title': 'Import from Foundry',
|
'foundryImport.title': 'Import from Foundry',
|
||||||
'foundryImport.hint':
|
'foundryImport.hint':
|
||||||
'Choose a Foundry VTT world or module folder or archive (version 11+). A new project will be created.',
|
'Choose a Foundry VTT world or module folder, or an archive (.zip / .fvtt), version 11+. A new project will be created.',
|
||||||
'foundryImport.sourceType': 'SOURCE TYPE',
|
'foundryImport.sourceType': 'SOURCE TYPE',
|
||||||
'foundryImport.folder': 'Folder',
|
'foundryImport.folder': 'Folder',
|
||||||
'foundryImport.archive': 'Archive (.zip / .fvtt)',
|
'foundryImport.archive': 'Archive (.zip / .fvtt)',
|
||||||
|
|||||||
Reference in New Issue
Block a user