How to save selected folders for later use

First, some background, because there might be a better solution to the problem I'm trying to solve.

I've been creating some playlists of random albums. I use a random number generator between 1 and say 1000 and use the number, say 55, as the index into my music database, so I can select the Artist -Album at position 55. Using dopus, I select the folder, click on the icon and copy and paste. I put 10 random album full path (NAS) folder names into a random.m3u file and then load that into mp3tag which then loads the individual music files (mp3, flac, etc) and then I can save the resulting playlist as random_01.m3u and off we go.

My first step on the road to nirvana would be to be able to select artist-album folders one at a time and write them to a fixed name text file, so after writing 10 full path folder names to the fixed name text file, I would process that (as above) and empty the file out for the next go. I think I know how to get the full path folder name from my recent jscript python button, which works fantastically well.

The bit I'm not sure about is writing to the text file. The only bit I could find in the manual is about Scripting.FileSystemObject and when I look that up on msdn I can see CreateTextFile, OpenTextFile, Write, etc. But these look like vbscript only to my inexperienced eye, and I would like to use jscript if possible.

Here's where I'm thinking you'll say, you don't want to do it like that, you want to do it like this....

I also found tbone's SelectEx (Command: SelectEx (extended Select command)) which I'm now going to read again and try to understand if it's a bridge too far for me or exactly the right thing.

Are you loading the first m3u into mp3tag so that it turns the list of folders into a list of files for the second m3u? You should be able to skip that extra step/program pretty easily, if so.

It should be fairly easy to make the whole thing a 1-click button, including the random number generation and output of the final m3u playlist. At least in theory.

All of Microsoft's scripting objects should work equally well with both VBScript and JScript. They usually give examples in both languages if you find the MSDN pages, e.g. CreateTextFile.

This JScript code works in Opus and will write two lines to a jstest.txt file on your desktop:

fso = new ActiveXObject("Scripting.FileSystemObject");
filePath = DOpus.FSUtil.Resolve("/desktop\\jstest.txt");
outFile = fso.CreateTextFile(filePath, true); // true means Overwrite
outFile.writeline("First line");
outFile.writeline("Second line");
outFile.Close();

That's right.

Thanks Leo. Just tried your text file writing code out in CLI so I'll see if I can start to build it up from there...

Reading this, I think SelectEx is able to achieve what you are trying to do:
This script button selects 10 random folders and writes contained files to a file "C:\MyPlaylist.m3u".
If you like the chosen folders to be selected or you only want specific filetypes in the playlist, then we can get that too.

[code]@script jscript

function OnClick(data){
var cmd = data.func.command, tab = data.func.sourcetab;
cmd.RunCommand('SelectEx PATH="'+tab.path+'" RANDOM FOLDERS ITEMCOUNT=10 SETVAR=SelexResult');
var folders = (""+tab.vars.get("SelexResult")).split("\n");
for(var f=0;f<folders.length;f++)
cmd.RunCommand('SelectEx LINEAR FILES ITEMCOUNTPERC=100 PATH="'+folders[f]+'" TOFILE="C:\MyPlayList.m3u" APPEND');
}
[/code]

Thanks tbone - at the moment I'm not sure how to implement SelectEx or how to update it when a new version comes out.

Also does SelectEx understand that I only want folders with music (the lowest level folders). The hierarchy is \NAS\sharename then A-Z folders then Artist folder, then Artist-Album folder(s), so I don't want a whole Artist selected, for example, or folder K.

Using SelectEx and trying to understand the scripts will be a tremendous challenge for my learning, but a worthwhile one. But also, it makes sense to build a simple approach from the ground up so that I can learn.

From leo's starting point above I've realised that I need to append folder path data to the text file on successive clicks. I've found this mechanism called TextStream:

msdn.microsoft.com/en-us/librar ... bt(v=vs.84.aspx

which contains this example:

JScript
var ForReading = 1, ForWriting = 2, ForAppending = 8;
var TristateUseDefault = -2, TristateTrue = -1, TristateFalse = 0;

var fso = new ActiveXObject("Scripting.FileSystemObject");

// Create the file, and obtain a file object for the file.
var filename = "c:\\testfile.txt";
fso.CreateTextFile(filename);
var fileObj = fso.GetFile(filename);

// Open a text stream for output.
var ts = fileObj.OpenAsTextStream(ForWriting, TristateUseDefault);

// Write to the text stream.
ts.WriteLine("Hello World!");
ts.WriteLine("The quick brown fox");
ts.Close();

// Open a text stream for input.
ts = fileObj.OpenAsTextStream(ForReading, TristateUseDefault);

// Read from the text stream and display the results.
while (!ts.AtEndOfStream) {
    var textLine = ts.ReadLine();
    document.write (textLine + "<br />");
}
ts.Close();

So I will now try to use this example to see if I can append data. There's some translation involved, and that's the learning challenge here... Anyway I'm going to run this in CLI and see what happens.

When I run the code below in CLI, which I've tried to do by updating Leo's code to use textstream (initially to write, then read, and append), I get this error:

Successfully initialized 'JScript' engine
Script started successfully
Error at line 6, position 1
Object expected (0x800a138f)
Script completed

so something to do with the line starting ts...

var ForReading = 1, ForWriting = 2, ForAppending = 8;
var TristateUseDefault = -2, TristateTrue = -1, TristateFalse = 0;
fso = new ActiveXObject("Scripting.FileSystemObject");
filePath = DOpus.FSUtil.Resolve("/desktop\\nrtest01.txt");
fileObj = fso.CreateTextFile(filePath);
ts = new fileObj.OpenAsTextStream(ForWriting, TristateUseDefault);
// Write to the text stream.
ts.WriteLine("Hello World!");
ts.WriteLine("The quick brown fox");
ts.Close();

Remove the "new" from the 6th line.

ok code now reads:

var ForReading = 1, ForWriting = 2, ForAppending = 8;
var TristateUseDefault = -2, TristateTrue = -1, TristateFalse = 0;
fso = new ActiveXObject("Scripting.FileSystemObject");
filePath = DOpus.FSUtil.Resolve("/desktop\\nrtest01.txt");
fileObj = fso.CreateTextFile(filePath);
ts = fileObj.OpenAsTextStream(ForWriting, TristateUseDefault);

// Write to the text stream.
ts.WriteLine("Hello World!");
ts.WriteLine("The quick brown fox");
ts.Close();

// Open a text stream for input.
ts = fileObj.OpenAsTextStream(ForReading, TristateUseDefault);

// Read from the text stream and display the results.
while (!ts.AtEndOfStream) {
    var textLine = ts.ReadLine();
    document.write (textLine + "<br />");
}
ts.Close();

and result is:

Successfully initialized 'JScript' engine
Script started successfully
Error at line 6, position 1
Object doesn't support this property or method (0x800a01b6)
Script completed

A lot of little errors! o) This is a fixed version of what you tried.
You really need to pay attention to what method you call on what object. Also, whenever you visit a website that uses javascript in its examples and document.write() to output something, then that won't work in DO, as document.write() is available in browsers only.

[code]var file = "d:\in_out.txt";
var ForReading = 1, ForWriting = 2, ForAppending = 8;
var TristateUseDefault = -2, TristateTrue = -1, TristateFalse = 0;
var fso = new ActiveXObject("Scripting.FileSystemObject");
var filePath = DOpus.FSUtil.Resolve(file);

//write to the file
var fileObj = fso.CreateTextFile(filePath, true);
fileObj.WriteLine("Hello World!");
fileObj.WriteLine("The quick brown fox");
fileObj.Close();
delete fileObj; //does not delete the file, just the variable

//read from the file
var fileObj = fso.GetFile(file);
var stream = fileObj.OpenAsTextStream( ForReading, TristateUseDefault);
while (!stream.AtEndOfStream) {
var textLine = stream.ReadLine();
DOpus.Output(textLine);
}
fileObj.Close();[/code]

I'd suggest using fso.OpenTextFile() for reading and for writing files, easier to understand/to remember:
ns7.webmasters.com/caspdoc/html/ ... method.htm
Same code, but this time using just OpenTextFile(). More consistent handling and one line shorter! o)

[code]var file = "d:\in_out.txt";
var ForReading = 1, ForWriting = 2, ForAppending = 8;
var TristateUseDefault = -2, TristateTrue = -1, TristateFalse = 0;
var fso = new ActiveXObject("Scripting.FileSystemObject");
var filePath = DOpus.FSUtil.Resolve(file);

//write to the file
var fileObj = fso.OpenTextFile(filePath, ForWriting, TristateTrue);
fileObj.WriteLine("Hello World!");
fileObj.WriteLine("The quick brown fox");
fileObj.Close();
delete fileObj; //does not delete the file, just the variable

//read from the file
var fileObj = fso.OpenTextFile(filePath, ForReading, TristateTrue);
while (!fileObj.AtEndOfStream) {
var textLine = fileObj.ReadLine();
DOpus.Output(textLine);
}
fileObj.Close();[/code]

Thanks tbone. In my innocence I thought the microsoft example was going to work pretty much without too much modification. I updated my attempt with your corrections, and worked out a couple more too, but this example now works.

var file = "/desktop\\nrtest01.txt";
var ForReading = 1, ForWriting = 2, ForAppending = 8;
var TristateUseDefault = -2, TristateTrue = -1, TristateFalse = 0;
var fso = new ActiveXObject("Scripting.FileSystemObject");
var filePath = DOpus.FSUtil.Resolve("/desktop\\nrtest01.txt");
var fileObj = fso.CreateTextFile(filePath, true);
fileObj.WriteLine("Hello World!");
fileObj.WriteLine("The quick brown fox");
fileObj.Close();
delete fileObj;

// Open a text stream for reading.
var fileObj = fso.GetFile(filePath);
var stream = fileObj.OpenAsTextStream(ForReading, TristateUseDefault);
while (!stream.AtEndOfStream) {
    var textLine = stream.ReadLine();
    DOpus.Output(textLine);
}
stream.Close();

Here is the output from CLI:

Successfully initialized 'JScript' engine
Script started successfully
Hello World!
The quick brown fox
Script completed

The reason for trying to get the textstream working is because that looks like one way (sure there are others!) of being able to append new lines to an existing text file. If I can append new lines to the end of an existing text file, then I can move on to being able to select a folder and then click the button to save the full path for that folder by appending it... that way I can end up with 10 random folder paths in a text file.

So I'll now move on to adding these as yet unproven lines and others to the script to allow appending:

var fileObj = fso.GetFile(filePath);
var stream = fileObj.OpenAsTextStream(ForAppending, TristateUseDefault);

stream.WriteLine("Hello World! extra appended line");
stream.WriteLine("The quick brown fox  gets appended");
stream.Close();

Er, I have actually just tried these lines out by adding them to the end of the script, and they worked. I checked them by opening up the text file and this is the contents:

Hello World!
The quick brown fox
Hello World! extra appended line
The quick brown fox  gets appended

:wink:

last thing tbone - I'll try the same thing with OpenTextFile to see if that supports appending as well... but that will be tomorrow.

Ok, good, there is progress visible! o) FWIW, OpenTextFile() can also be used to append lines when used with the ForAppending constant (this is writing to the file and moving the "cursor" to the end). So there's really no need for that Textstream thing, unless somebody knows what real advantage it has over using the generic OpenTextFile() method.

So instead of doing:
var fileObj = fso.GetFile(filePath);
var stream = fileObj.OpenAsTextStream(ForAppending, TristateTrue);

You can just use:
var fileObj = fso.OpenTextFile(filePath, ForAppending, TristateTrue);

PrePostEdit:
Too, late! o)

OpenTextFile seems to work fine. According to your doc link, it's still textstream, but opened in a different way:

I have a script now running in CLI that just appends lines to an existing text file, using OpenTextFile:

[code]var file = "/desktop\nrtest01.txt";
var ForReading = 1, ForWriting = 2, ForAppending = 8;
var TristateUseDefault = -2, TristateTrue = -1, TristateFalse = 0;
var fso = new ActiveXObject("Scripting.FileSystemObject");
var filePath = DOpus.FSUtil.Resolve(file);

//try replacing the next 2 lines with the 3rd
//var fileObj = fso.GetFile(filePath);
//var stream = fileObj.OpenAsTextStream(ForAppending, TristateUseDefault);
var stream = fso.OpenTextFile(filePath, ForAppending, TristateUseDefault);
// debug
DOpus.Output("file " + file);
DOpus.Output("filePath " + filePath);
//DOpus.Output("fileObj " + fileObj);
DOpus.Output("stream " + stream);
stream.WriteLine("Hello World! extra appended line 2 opentextfile");
stream.WriteLine("The quick brown fox gets appended 2 opentextfile");
stream.Close();[/code]

I will now try to take this code, put it in a button, add a bit to the front to capture the selected folder, and append the full folder name to the text file...

One thing I'm trying to understand from this exercise so far:

var declares a variable and = is assignment, so using var and = on the same statement is doing two things at the same time, and one of the problems I was having was not using var to declare. Presumably one could say:

var fso;
fso = new ActiveXObject("Scripting.FileSystemObject");

and that would be the equivalent of line 1 below, except my main question is "new" - why does the first line below have "new" and the second line doesn't? Also, does jscript have a way to force variables to be declared, as in other languages?

var fso = new ActiveXObject("Scripting.FileSystemObject"); var filePath = DOpus.FSUtil.Resolve(file);

OK the function now captures any folders selected when the button is clicked and appends them to a hard coded text file.

@script jscript
function OnClick(data)
{
	var tab = data.func.sourcetab
	var cmd = data.func.command

	if (tab.selected_dirs.count == 0)
	{
		DOpus.Output("selected dirs count is zero, so return");
		return;
	}

	var file = "/desktop\\nrtest01.txt";
	var ForReading =1, ForWriting = 2, ForAppending =8;
	var TristateUseDefault = -2, TristateTrue = -1, TristateFalse =0;
	var fso = new ActiveXObject("Scripting.FileSystemObject");
	var filePath = DOpus.FSUtil.Resolve(file);
	var stream = fso.OpenTextFile(filePath, ForAppending, TristateUseDefault);
	
	
	enumFiles = new Enumerator(tab.selected_dirs);
	enumFiles.moveFirst();

	while (enumFiles.atEnd() == false)
	{

		var folderPath = new String(enumFiles.item().realpath);
		DOpus.Output("folderPath " + folderPath);

		stream.WriteLine(folderPath);

		enumFiles.moveNext();
	} // end while

	stream.Close();
	
	return;
	
} // end function

So, phase 1 complete, and even in its basic form it's very useful, and I've learned a lot.

Would I now be able to select a folder (containing music, not a high level A-Z or artist folder) at random? If I could do that then I could use the function developed so far to review and save it, then find the next random folder, save that, etc.

p.s. Not sure if I should start a new thread for the next bit...