Save multiple documents in subfolder

Once I create a persistent teken for a directory and inside that directory I add a subfolder that receives the active document that I save in.tiff format, but I cannot save the other documents, as I receive an Error: A Folder with given name exists . Cannot overwrite a Folder.
How do I fix this and save the next documents inside the subfolder??


js:

try {
	const fs = require('uxp').storage.localFileSystem;
	// const directory = await fs.getFolder();  //abre uma tela que permite escolher a pasta
	// const persistentToken = await fs.createPersistentToken(directory);//cria um token persistente para a pasta
	// localStorage.setItem('directory', JSON.stringify(persistentToken)); //associa o token criado Ă  entrada da pasta

	token = await JSON.parse(localStorage.getItem("directory")); //selecione o token salvo
	const folder = await fs.getEntryForPersistentToken(token); //extrai o caminho da pasta persistente
	const subfolder = await folder.createFolder("SubFolder");  //Crie as pastas e subpastas desejadas)
	
	const newfile = app.activeDocument.name;
	//cria o arquivo em modo modal
	await core.executeAsModal(async () => {  
		const newFile = await subfolder.createFile(newfile, { overwrite: true });
		const saveFile = await fs.createSessionToken(newFile);
		await app.batchPlay([{"_obj": "save","as":{"_obj": "TIFF","byteOrder":{"_enum": "platform","_value": "IBMPC"},"layerCompression":{"_enum": "encoding","_value": "RLE"}},"in": {"_path": saveFile,"_kind": "local"}}], { });
	});
} 
catch (e) {console.log(e);}	

If you call this piece of code on every file, it tries to await folder.createFolder("SubFolder"); also every time

I actually provided a “find or create folder” helper function in another thread a few days ago:

2 Likes

I confess that this process to import, save or export files in UXP plugins is my biggest challenge, I’m not familiar with it and I’m willing to face it head on with my rough questions.
@Timothy_Bennett I read this topic several times, but I couldn’t do it the way you found it, the process of verifying the existence of the subfolder.
That’s all I managed to do, but to no avail!

try {
	 const fs = require('uxp').storage.localFileSystem;
	// const directory = await fs.getFolder();  //abre uma tela que permite escolher a pasta
	// const persistentToken = await fs.createPersistentToken(directory);//cria um token persistente para a pasta
	// localStorage.setItem('directory', JSON.stringify(persistentToken)); //associa o token criado Ă  entrada da pasta

	token = await JSON.parse(localStorage.getItem("directory")); //selecione o token salvo
	const folder = await fs.getEntryForPersistentToken(token); //extrai o caminho da pasta persistente
    		const entries = await folder.getEntries();
	const checkSubfolder = entries.filter((entry) => entry.isFolder).find((subFolder) => subFolder.name === "SubFolder");
	
	if (!checkSubfolder) {
		const subfolder = await folder.createFolder("SubFolder");  //Crie as pastas e subpastas desejadas)
	}


	const newfile = app.activeDocument.name;
	//cria o arquivo em modo modal
	await core.executeAsModal(async () => {  
		const newFile = await subfolder.createFile(newfile, { overwrite: true });
		const saveFile = await fs.createSessionToken(newFile);
		await app.batchPlay([{"_obj": "save","as":{"_obj": "TIFF","byteOrder":{"_enum": "platform","_value": "IBMPC"},"layerCompression":{"_enum": "encoding","_value": "RLE"}},"in": {"_path": saveFile,"_kind": "local"}}], { });
	});
} 
catch (e) {console.log(e);}

The script verifies, creates but does not save the file
log

ReferenceError: subfolder is not defined
1 Like

I modified this code but it doesn’t save, just create a subfolder. Please, can anyone help me?

try {
 const fs = require('uxp').storage.localFileSystem;
// const directory = await fs.getFolder();  //abre uma tela que permite escolher a pasta
// const persistentToken = await fs.createPersistentToken(directory);//cria um token persistente para a pasta
// localStorage.setItem('directory', JSON.stringify(persistentToken)); //associa o token criado Ă  entrada da pasta

token = await JSON.parse(localStorage.getItem("directory")); //selecione o token salvo
const folder = await fs.getEntryForPersistentToken(token); //extrai o caminho da pasta persistente

const findOrCreateFolder = async (parentFolder, folderName) => {
	 const entries = await parentFolder.getEntries();
 
	 const foundFolder = entries.filter((entry) => entry.isFolder).find((subFolder) => subFolder.name === folderName);
 
	 if (foundFolder) {
		 return foundFolder
	 }
	 return await parentFolder.createFolder(folderName)
 }

 const main = async () => {
	// The  findOrCreateFolder function and all other code goes here - including whatever defines the folder variable that is going to be passed to findOrCreateFolder 
	const theFolder = await findOrCreateFolder(folder, "SubFolder");
 }
 
 main();

const newfile = app.activeDocument.name;
//cria o arquivo em modo modal
await core.executeAsModal(async () => {  
	const newFile = await theFolder.createFile(newfile, { overwrite: true });
	const saveFile = await fs.createSessionToken(newFile);
	await app.batchPlay([{"_obj": "save","as":{"_obj": "TIFF","byteOrder":{"_enum": "platform","_value": "IBMPC"},"layerCompression":{"_enum": "encoding","_value": "RLE"}},"in": {"_path": saveFile,"_kind": "local"}}], { });
});
} 
catch (e) {console.log(e);}

Your problem in both examples is one of scope, but let’s just debug your first example.

The subfolder variable is only scoped to your if statement and so it doesn’t exist by the time you call it.

If you changed it to this then subfolder will still exist when you call and will have a value.

// Initialize subfolder in global scope, and as a let so it's value can updated later.
// If .find doesn't find a matching folder name it returns undefined 
let subfolder = entries.filter((entry) => entry.isFolder).find((subFolder) => subFolder.name === "SubFolder");

// If subfolder is undefined then create it and assign to subfolder let
if (!subfolder) {
   subfolder = await folder.createFolder("SubFolder"); 
}

// subfolder continues to exist in global scope and cannot be undefined 
1 Like

Hi @Timothy_Bennett now it worked perfectly! The understanding became much clearer! Thanks for sharing your knowledge.
I’ll post the script for anyone who needs it.

try {
	 const fs = require('uxp').storage.localFileSystem;
	// const directory = await fs.getFolder();  //abre uma tela que permite escolher a pasta
	// const persistentToken = await fs.createPersistentToken(directory);//cria um token persistente para a pasta
	// localStorage.setItem('directory', JSON.stringify(persistentToken)); //associa o token criado Ă  entrada da pasta
	token = await JSON.parse(localStorage.getItem("directory")); //selecione o token salvo
	const folder = await fs.getEntryForPersistentToken(token); //extrai o caminho da pasta persistente

            const entries = await folder.getEntries();
	let subfolder = entries.filter((entry) => entry.isFolder).find((subFolder) => subFolder.name === "SubFolder");

	// If subfolder is undefined then create it and assign to subfolder let
	if (!subfolder) {
		subfolder = await folder.createFolder("SubFolder"); 
	}

	const newfile = app.activeDocument.name;
	//cria o arquivo em modo modal
	await core.executeAsModal(async () => {  
		const newFile = await subfolder.createFile(newfile, { overwrite: true });
		const saveFile = await fs.createSessionToken(newFile);
		await app.batchPlay([{"_obj": "save","as":{"_obj": "TIFF","byteOrder":{"_enum": "platform","_value": "IBMPC"},"layerCompression":{"_enum": "encoding","_value": "RLE"}},"in": {"_path": saveFile,"_kind": "local"}}], { });
	});
} 
catch (e) {console.log(e);}
3 Likes

Even better would be to wrap all that up in a helper function that you can reuse at any time, for any folder, in any UXP plugin as per the post I linked.

You actually do that successfully in your second example, but it’s all encapsulated in, or rather scoped to, the main function and so theFolder no longer exists by the time you call it to save.
In that example code you just need to move the save stuff inside the main function.

1 Like

But to expand on what’s happening there;
When we define a variable in JS we can provide a function call as the value so that the function can execute first and it’s return value becomes the variable’s value.
Consider that this

const foo = "bar"

is the same as

function getBar() = {
   return "bar"
}

const foo = getBar()

or even as an inline anonymous function

const foo = () => {
   return "bar"
}

As you can already see this is really powerful; especially when you consider that the function logic can be as complex as you like, but it really helps in writing code that is declarative rather than imperative.
A good approach to take is that a function should:
• do one job
• return a value
• be agnostic, i.e. it knows nothing about the code that called it or the code it’s returning a value to.

To use our findOrCreateFolder as an example, all it knows is that it’s going to be passed a parent folder entry object and a string of the subfolder name to find. It’s either going to find a matching subfolder and return it, or it’s going to create a subfolder and return that. It’s always going to return the named subfolder regardless of whether it existed beforehand, and it works for any parent folder/name combination.

I like to use verbose names for helper functions as it aids readable code. In this entirely fictitious code each helper function returns a value that is assigned to each const and passed into the next function in turn:

const file = getFileData(path);

const processedFile = processFile(file);

const token = getTokenForProcessedFile(processedFile);

I’d also store the code for those helper functions in a separate JS file that I’d import at the top of my code:

const { getFileData, processFile, getTokenForProcessedFile } = require("path/to/helpers/file")
3 Likes

Spoiler - you could do the same for your save functionality…

1 Like

@Timothy_Bennett thanks for your teachings!
While you are still available, I would like to ask you another question:
If I already have the document open and active and I can get its directory path (app.activeDocument.path), why do I have to use fs.getFolder(); to set the output location of the files?

That’s a design choice by Adobe to protect Marketplace customers from unscrupulous developers who might use full access to your file system for unsavoury practices.

The only real workaround is to have the user select a folder once on initial plugin installation, save that as a persistent token, and then reload it every time the plugin is loaded.
The issue with this is that you’re expecting users to buy into a set working folder/drive.
I’ve used this successfully for enterprise plugins, but in that instance every output location is known in advance, not so good if you want dynamic save locations.
There has been previous talk from Adobe about allowing full access to the filesystem (in particular in regards to comments from enterprise devs), but I have no idea if, and when that might be coming

1 Like

Now I understood! The real reason we don’t have full access to the file system is for security reasons, but I already understood how to program this file import and export system using tokens, thanks to your teachings, I think I already feel confident in starting to migrate my CEP mega panel to a UXP plugin. @Timothy_Bennett, thanks again. :handshake: :wink:

2 Likes

thank you, really usefull