Wednesday, August 22, 2012

How to Flatten a Folder Structure in AppleScript

Introduction (i.e. The Problem)
You can skip this introduction if you already understand the problem and are trying to solve the same issue.  Just start at Describing the Solution.

Ok, so recently I was given a large archive of files.  These were old automated backups that I had nothing to do with in the past.  They were stored in individual directories.  The directories each had a name that indicated the date the backup was run.  Inside the directories were one or more files that indicated a time that the backup was started.  It looked a bit like this:


The above is much smaller than the actual directory but it gives you an idea.  There was a single top folder called "archive" that contained all the subdirectories (or folders.)  Each subdirectory had a date as the name in Year-Month-Day (yyyy-MM-dd) format separated by hyphens.  Under each directory was one or more files.  The files had names that indicated when their particular backup started in Hour-Minute-Second (HH-mm-ss) format separated by hyphens.

Now I had to run a batch application on every .bkp file to create a summary of each change for audit purposes.  This application could take an entire directory of .bkp files and generate the needed report.  It had one shortcoming though, it could take a single directory as its input and would look for every .bkp file but it was not recursive.  Meaning it would not check subdirectories.  That means I could manually point it to each "date" directory such as "02-03-2010" but not to the top level "archive" directory.  As there were over 500 "date" directories I really didn't want to have to do that manually.

Describing the Solution
What I needed was a way to move every .bkp file stored under a date directory up one level so the files were under the top level "archive" directory.  Then I could just point the batch application to the "archive" directory and it would run though every file.

However, in moving the files that only had the time as their name such as "11-00-00" out of its sub directory I would lose what date it was created on.  I'd rather not lose that information.

Even worse you can see several .bkp files have the same name so they could not all be moved into the top level directory without name conflicts.

What I needed was to prepend the subdirectory name such as "02-03-2010" to the .bkp filename so the filename was actually "02-03-2010_11-00-00.bkp" (in this case I used an underscore "_" character as a separator between the folder and the file name.)  Then I could move the file up to the top level directory and not worry about name conflicts or files being overwritten.

Searching the Web for a Solution
Since I'm on a Mac this seemed like a perfect job for AppleScript.  As a good engineer never reinvents the wheel I figured I'm not the first person to run into this problem so I could probably find a solution out there and just reuse it.

I found this first:  Flatten folder structure via AppleScript | Macworld
This script basically worked but it didn't solve the problem of duplicate named files being moved into the same directory.  I needed something a bit more advanced.

I also found this:  Mac OS X Hints
But that didn't seem to work and still didn't address the naming issues.

Unfortunately I didn't find any workable solutions.

Creating Our Own Solution
Ok, so here's the solution.  Looking at some of the code for the two links I listed above I wrote the following script.


-- Select the folder in the front most window that you want to flatten before running
-- this script deletes the folders it flattens so it can destroy data!
--
(* Behavior…

Select Folder_Top in the front most Finder window, then run script…

Before:
-Folder_Top (type: folder)
--A   (type: folder)
---1  (type: file)
---2  (type: file)
--B   (type: folder)
---1  (type: file)
---2  (type: file)

After:
-Folder_Top (type: folder)
--A_1  (type: file)
--A_2  (type: file)
--B_1  (type: file)
--B_2  (type: file)

*)

tell application "Finder"
set this_folder to (selection as alias)
set this_folder_list to every folder of this_folder
repeat with i in this_folder_list
set this_file_list to every file of i
repeat with x in this_file_list
set theFile to (name of x)
set theFolderName to name of container of x
set name of x to theFolderName & "_" & theFile -- change "_" to whatever seperater string you want.
end repeat
set this_file_list_with_new_name to every file of i
move this_file_list_with_new_name to this_folder
-- delete i  (* uncomment this line to delete the subdirectories when done*)
end repeat
end tell


You should be able to copy and past this into the AppleScript Editor.  Then go into the Finder and select your top level folder.  Go back to the Script Editor and click Run.  If you trust the script uncomment the delete line and it will remove the empty subdirectories when it is done moving the files.  Good luck!

PS
I've received a couple of emails that this can be useful in preparing to batch process videos with Handbrake if you don't want to use the Handbrake CLI (Command Line Interface.)  I added a mention here so if anyone searches specifically on Handbrake folders in Mac OS X they will find this entry.

PPS
I was not able to figure out how to do this using the more simple Automator in Mac OS X.  I realize you can use Automator to call an AppleScript but that defeats the point here.  If anyone can implement this logic using Automator I'd love to hear from you.