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:
Ivan Fontosh
2026-10-10 21:05:55 +08:00
parent 877fbaa8e7
commit 5c0c72892d
4 changed files with 40 additions and 21 deletions
+22 -18
View File
@@ -1893,25 +1893,29 @@ async function main() {
registerHandler(ipcChannels.project.importFoundry, async ({ sourcePath }) => {
emitZipProgress({ kind: 'import', stage: 'copy', percent: 0, detail: 'Импорт Foundry…' });
try {
const { project, pendingAppPlaylists } = await projectStore.importProjectFromFoundry(sourcePath, (p) => {
emitZipProgress({
kind: 'import',
stage:
p.stage === 'unzip' ? 'unzip' : p.stage === 'zip' ? 'zip' : p.stage === 'done' ? 'done' : 'copy',
percent: p.percent,
...(p.detail ? { detail: p.detail } : null),
});
});
if (playlistsStore && pendingAppPlaylists.length > 0) {
for (const pl of pendingAppPlaylists) {
const created = await playlistsStore.createPlaylist(pl.name);
const paths = pl.tracks.map((t) => t.absPath);
if (paths.length > 0) {
await playlistsStore.addAudioFiles(created.id, paths);
const { project } = await projectStore.importProjectFromFoundry(
sourcePath,
(p) => {
emitZipProgress({
kind: 'import',
stage:
p.stage === 'unzip' ? 'unzip' : p.stage === 'zip' ? 'zip' : p.stage === 'done' ? 'done' : 'copy',
percent: p.percent,
...(p.detail ? { detail: p.detail } : null),
});
},
async (pendingAppPlaylists) => {
if (!playlistsStore || pendingAppPlaylists.length === 0) return;
for (const pl of pendingAppPlaylists) {
const created = await playlistsStore.createPlaylist(pl.name);
const paths = pl.tracks.map((t) => t.absPath);
if (paths.length > 0) {
await playlistsStore.addAudioFiles(created.id, paths);
}
}
}
emitPlaylistsState();
}
emitPlaylistsState();
},
);
emitZipProgress({ kind: 'import', stage: 'done', percent: 100, detail: 'Готово' });
emitSessionState();
return { project };
@@ -67,6 +67,15 @@ void test('zipStore: closeOpenProject is serialized with open on projectSwitchCh
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', () => {
const src = fs.readFileSync(path.join(here, 'zipStore.ts'), 'utf8');
assert.match(src, /async exportProjectZipToPath/);
+7 -1
View File
@@ -108,6 +108,7 @@ import {
buildProjectFromFoundryDocuments,
loadFoundryDocumentsForImport,
type FoundryImportProgress,
type FoundryPendingAppPlaylist,
} from '../foundry/foundryImport';
import { getAppSemanticVersion } from '../versionInfo';
@@ -2129,7 +2130,8 @@ export class ZipProjectStore {
async importProjectFromFoundry(
sourcePath: string,
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();
const loaded = await loadFoundryDocumentsForImport(sourcePath, onProgress);
try {
@@ -2159,6 +2161,10 @@ export class ZipProjectStore {
await this.writeCacheProject(cacheDir, project);
onProgress?.({ stage: 'zip', percent: 90, detail: 'Сборка проекта…' });
await this.enqueuePack(cacheDir, zipPath);
// Архив распакован во временную папку. Плейлисты читают её по absPath — копировать до cleanup.
if (importPlaylists && pendingAppPlaylists.length > 0) {
await importPlaylists(pendingAppPlaylists);
}
onProgress?.({ stage: 'done', percent: 100, detail: 'Готово' });
return { project: this.openProject.project, pendingAppPlaylists };
} finally {