This week I want to go over a short script that I’ve written recently in order to help automate some of the tasks I have picked up around the office. Some of these tasks involve moving around files and folders on the network. I got tired of manually copying and pasting folders, so I wrote a script to help make moving large amounts of files and folders easier. There’s plenty of ways that this script could be made more in-depth, but this is a practical example of a way to make your life a little bit easier.
The biggest thing I wanted to do with this script was to copy entire folders around. This script does not introduce any new concepts if you’re a regular reader of the LDC, but it is a good example of putting together several concepts in a very simple manner.
Originally I was going to put together several loops, but soon I realized that this was an impractical solution and decided that recursion was the easiest way to make sure that the script was both simple to read and simple in execution. The script as a whole is about 60 lines of code. Let’s take a look:
/* Parse.ls
*
* Author: Joshua Kwiatkowski
*
* Rev 01 JCK 09/28/18
*
* Notes: Copy files and folders from one location to another
*/
//Declare function
int parse(string old_folder, string new_folder, int recursive);
//Main
void main() {
string old, new;
int recurse;
//Get original folder
old = BrowseFolder("Pick Originating Folder");
//If canceled bail
if (GetLastError() == ERROR_CANCEL) {
return;
}
//Get destination folder
new = BrowseFolder("Pick Destination Folder", old);
//If canceled bail
if (GetLastError() == ERROR_CANCEL) {
return;
}
//Ask if you want to do subfolders
recurse = YesNoBox('Q', "Move subfolders in addition to files?");
//If Canceled
if (recurse == IDCANCEL) {
return;
}
//Start the process
parse(old, new, recurse);
}
int parse(string old_folder, string new_folder, int recursive) {
handle hDS;
int rc;
string filename;
//Start the parse
hDS = GetFirstFile(old_folder + "*.*");
//Assume there are more files than this
boolean moreFiles;
moreFiles = true;
//Are there more files?
while (moreFiles) {
//Get the filename
filename = GetName(hDS);
//Is the filename not the home or previous folder?
if (filename != "." && filename != "..") {
//Is the file a folder?
if (IsFolder(old_folder + filename)) {
//Are we supposed to move subfolders?
if (recursive == IDYES) {
//Create the folder
CreateFolder(new_folder + filename);
//Call yourself
parse(old_folder + filename + "\\", new_folder + filename + "\\", recursive);
}
}
//If not a folder
else {
//Make sure it's a file
if (IsFile(old_folder + filename)) {
//Copy the file to the new location
rc = CopyFile(old_folder + filename, new_folder + filename);
}
}
}
//Get the next file
rc = GetNextFile(hDS);
//If out of files
if (rc == ERROR_EOD) {
//Break out of the loop
moreFiles = false;
}
}
//Close the parse handle
CloseHandle(hDS);
//Return
return ERROR_NONE;
}
As you can see, there’s nothing overly complicated. Let’s go ahead and break down what is being done in this script. The main function has to get some user input before we can start the real workhorse function of the script.
void main() {
string old, new;
int recurse;
//Get original folder
old = BrowseFolder("Pick Originating Folder");
//If canceled exit
if (GetLastError() == ERROR_CANCEL) {
return;
}
//Get destination folder
new = BrowseFolder("Pick Destination Folder", old);
//If canceled bail
if (GetLastError() == ERROR_CANCEL) {
return;
}
//Ask if you want to do subfolders
recurse = YesNoBox('Q', "Move subfolders in addition to files?");
//If Canceled
if (recurse == IDCANCEL) {
return;
}
//Start the process
parse(old, new, recurse);
}
We get two folders from the user: a source folder and a destination folder. I’m not doing very much error checking other than to make sure that the user doesn’t press the cancel button. In reality, you’d want to do more, like making sure the user has write access to the locations and so forth. Also, you’d want to query the user about copying over or replacing files with duplicate names in the new location. For simplicity’s sake and to focus on our recursion, we’ll skip these important checks.
We also ask the user to decide if we should look through subfolders and copy everything there or not. This is where the recursive operation comes into play. Copying everything will run the copy operation recursively on every file, subfolder, and so on until a stop condition is reached (i.e., all of the files have been copied). This means that the script could run for a while depending on the speed of your hard disk and the amount of files and subfolders the folder to be copied has. If the user selects no to whether or not the subfolders should be copied, the script skips folders and only runs on the files in the folder. Finally, if the user closes the dialog rather than pressing yes or no will abort the script at that point. If the user closes the dialog or hits cancel, the script stops and doesn’t execute. Note that the destination folder dialog will start opened to the folder that the user picks for the source folder. This can help the user if he or she is selecting folders that are near one another.
Once we have the user’s preferences, we take that information and we start the parse function. That’s the last thing that the main function has to do, because the parse function takes over here and goes until the script ends.
int parse(string old_folder, string new_folder, int recursive) {
handle hDS;
int rc;
string filename;
//Start the parse
hDS = GetFirstFile(old_folder + "*.*");
//Assume there are more files than this
boolean moreFiles;
moreFiles = true;
//Are there more files?
while (moreFiles) {
We get a file copy started by creating a Legato FolderEnumeration object and getting the handle to it. We pass the object the source folder and add “*.*” to the end of the path. Again, you might want to put more error checking in here sinply to make sure the old_folder variable contains a valid, qualified path and so forth. Appending the “*.*” string means that the file parse will go through every single file in the folder. Next we set a boolean signifying if there are more files to be parsed, and by default we set it to true. This means that when we get to the beginning of the while loop we’re sure that we go through the loop at least once.
//Get the filename
filename = GetName(hDS);
//Is the filename not the home or previous folder?
if (filename != "." && filename != "..") {
//Is the file a folder?
if (IsFolder(old_folder + filename)) {
//Are we supposed to move subfolders?
if (recursive == IDYES) {
//Create the folder
CreateFolder(new_folder + filename);
//Call yourself
parse(old_folder + filename + "\\", new_folder + filename + "\\", recursive);
}
}
//If not a folder
else {
//Make sure it's a file
if (IsFile(old_folder + filename)) {
//Copy the file to the new location
rc = CopyFile(old_folder + filename, new_folder + filename);
}
}
}
Next is the meat of the loop. We get the file name of the current file using the GetName SDK function, and then we check to make sure that it’s not the shorthand for the current folder or the parent folder. Next, we check to see if the file that we have is a folder using the IsFolder function. If it is, we check to see if we are supposed to copy the subfolders or not. If yes, we create the folder in the new location via the CreateFolder function and then we call our parse routine again with the old location in the folder and the new location set to the new folder we just created, and then we pass along that we are in recursive mode
If the file is not a folder, we make sure that it is a file with the IsFile function and then we copy the file to the new location. Once more, you could and probably should enhance the error-checking for use in a more public environment, but for our purposes here we’ll keep it simple.
//Get the next file
rc = GetNextFile(hDS);
//If out of files
if (rc == ERROR_EOD) {
//Break out of the loop
moreFiles = false;
}
}
//Close the parse handle
CloseHandle(hDS);
//Return
return ERROR_NONE;
}
Finally we get the next file in the folder using the GetNextFile function and see if the return code is that there are no more files (ERROR_EOD). If this was the last file in the parse object we set the moreFiles boolean to false, which will break us out of the loop and becomes the end condition for this recursive operation. After the loop ends, we close the handle to our FolderEnumeration object and then we return. You’ll notice I left the return type of this function as an int. This is to allow easy expansion to returning additional information to the previous recursive folder parsing operations. For example, if a file fails, you could report that error, allow it to bubble up the recursive levels, and then have the initial script report that an error was encountered somewhere within it. I’m not currently doing that, but it is a change that could be made pretty easily.
And there we have it, a simple script that loops and recursively runs if necessary until everything in that original folder is copied to its new location. There is a lot of room for expansion on this script. Obviously the error checking we mentioned would be good to add, including checking for an overwrite or replacement condition. Excluding or including only certain file types would be one idea for making this script more functional. Everything depends upon your requirements as a developer. My requirements were simple, but yours may be more complex. This is a good example of taking some simple requirements and turning it into a simple script, while Steve’s example a couple weeks ago on creating a page break template manager was a better example at taking more complex requirements and turning them into a more polished result. This just goes to show that Legato can be used for many different purposes, from simple to complex.
Joshua Kwiatkowski is a developer at Novaworks, primarily working on Novaworks’ cloud-based solution, GoFiler Online. He is a graduate of the Rochester Institute of Technology with a Bachelor of Science degree in Game Design and Development. He has been with the company since 2013. |
Additional Resources
Novaworks’ Legato Resources
Legato Script Developers LinkedIn Group
Primer: An Introduction to Legato