Changing AppleScript-created date-based folder structure

applescriptfinder

I have an AppleScript that has been written and it works as such, but I need to change the way it creates the folder structure. The script does the following:

  • Folder is selected which contains files (in my case it will be photos).
  • It will then look at the pictures created date.
  • Create a YYYY (Year folder if not already created).
  • Create a MM (Month folder if not already created).
  • Create a DD (Day folder if not already created).
  • It will then move the photo into this folder and repeat for the next photo and repeat until completed.

The current folder structure is created as follows:

2018 "YYYY"
├── 2018-01 "MM"
├── 2018-02

This is great and works as designed, but I have changed my mind on how I would like the folders to look like. I would like the following structure (which is almost the same just a different naming structure:

2018
├── 001 January
│   ├── 20180101
│   └── 20180102
├── 002 February
│   ├── 20180201
│   └── 20180202
└── 003 March
    ├── 20180301
    └── 20180302

Now I have tried to work out where the script generates this but I have failed, so now I am turning to this great place for some help.

on run
    SortFiles(POSIX path of (choose folder))
end run

on open (DroppedFolder)
    set DroppedFolder to POSIX path of DroppedFolder
    if text (length of text of DroppedFolder) of DroppedFolder is not "/" then quit
    SortFiles(DroppedFolder)
end open

on SortFiles(SortFolder)
    set AppleScript's text item delimiters to return
    set SortFolderContents to the text items of (do shell script "find '" & SortFolder & "' -type f")
    set FolderMakeList to {}
    repeat with ThisItem in SortFolderContents
        set ThisFile to ThisItem as string
        if ThisFile does not contain "/." then
            tell application "Finder"
                set DateString to text 1 thru 7 of ((creation date of ((POSIX file ThisFile) as alias)) as «class isot» as string)
                set ThisFilesFolder to SortFolder & text 1 thru 4 of DateString & "/"
                set ThisFilesSubfolder to ThisFilesFolder & text 1 thru 7 of DateString & "/"
            end tell
            if ThisFilesFolder is not in FolderMakeList then
                try
                    do shell script ("mkdir '" & ThisFilesFolder & "'")
                end try
                set FolderMakeList to FolderMakeList & ThisFilesFolder
            end if
            if ThisFilesSubfolder is not in FolderMakeList then
                try
                    do shell script ("mkdir '" & ThisFilesSubfolder & "'")
                end try
                set FolderMakeList to FolderMakeList & ThisFilesSubfolder
            end if
            try
                do shell script ("mv '" & ThisFile & "' '" & ThisFilesSubfolder & "'")
            end try
        end if
    end repeat
    return FolderMakeList
end SortFiles

Best Answer

The part of your script that names the folders is this line:

set ThisFilesFolder to SortFolder & (do shell script ("date -r " & ThisFilesEpoch & " \"+%b-%Y\"")) & "/"

However, in my brief test of your script, it doesn't appear to create the folder structure you described at all, which isn't a surprise to me given that the shell command used to generate the date string from which the folder is named is this:

date -r %time% "+%b-%Y"

where %time% is inserted by way of the variable ThisFilesEpoch and represents the number of seconds since the Epoch (January 1, 1970) that the file (or, rather, its inode) was created. This shell function, date, takes this value (a large integer), which it understands as being a value in "Unix time", and re-formats it using a canonical representation as given by the tokens %b (representing a three-letter abbreviation for the month) and %Y (representing a four-digit number for the year).

So, for instance:

date -r 1534615274 "+%b-%Y"

returns:

Aug-2018

into which the files from August 2018 I had in my chosen folder were moved. It did not create separate day-of-the-month folders, nor even separate year folders; only "month-year" folders. So I am confused that you appear to have described what might be a different script entirely.

The script also has a number of qualities about it that irk me a great deal, such as:

  1. Its lazy usage of try...end try;

  2. The insertion of literal single quotes surrounding a variable being used as part of a string, e.g.

    do shell script "find '" & SortFolder & "' -type f" 
    

    instead of

    do shell script "find " & quoted form of SortFolder & " -type f" 
    
  3. This bizarre formulation:

    if text (length of text of DroppedFolder) of DroppedFolder is not "/" then...
    

    which could be written as

    if text -1 of DroppedFolder is not "/" then...
    

    or as

    if the last character of DroppedFolder is not "/" then...
    

    or even

    if DroppedFolder does not end with "/" then...
    
  4. And repeated use of do shell script, which is fine as a command tool to be invoked thoughtfully when needed, but when it populates the script as densely as it does this one, one wonders why the script wasn't just written as a shell/bash script to begin with. It's also very costly in terms of overhead to create and destroy shell processes one after another like this script does.

Given these stylistic and functional drawbacks, plus the confusion over what the script is supposed to be doing currently versus what it actually appears to do, I felt it warranted a re-write:

use Finder : application "Finder"
property rootdir : missing value

# The default run handler when run within Script Editor
on run
    using terms from scripting additions
        set root to choose folder
    end using terms from
    set rootdir to Finder's folder root                         -- '
    sortFiles()
end run

# Processes items dropped onto the applet.  The handler expects one or more 
# file references in the form of an alias list, and will process each item in 
# the list in turn.  If an item is not a folder reference, it is ignored.
# For the rest, the contents of each folder is reorganised.
on open droppedItems as list
    repeat with drop in droppedItems
        set rootdir to Finder's item drop
        if rootdir's class = folder then sortFiles()
    end repeat
end open

# Obtains every file in the root directory and all subfolders, moving them
# to new folders nested within the root directory and organised by year, month,
# and date of each file's creation
to sortFiles()
    repeat with f in the list of fileitems()
        set [yyyy, m, dd] to [year, month, day] of (get ¬
            the creation date of Finder's file f)               -- '

        set mmm to text -3 thru -1 of ("00" & (m * 1)) -- e.g. "008"
        set mm to text -2 thru -1 of mmm -- e.g. "08"
        set dd to text -2 thru -1 of ("0" & dd) -- e.g. "01"

        (my newFolderNamed:yyyy inFolder:rootdir)
        (my newFolderNamed:[mmm, space, m] inFolder:result)

        move Finder's file f to my newFolderNamed:[yyyy, mm, dd] ¬
            inFolder:result                                     -- '
    end repeat
end sortFiles

# A handler to house a script object that enumerates the contents of a 
# directory to its full depth
on fileitems()
    script
        property list : every document file in the ¬
            entire contents of the rootdir ¬
            as alias list
    end script
end fileitems

# Creates a new folder with the supplied name in the specified directory, 
# checking first to see whether a folder with that name already exists.  
# In either case, a Finder reference to the folder is returned.
to newFolderNamed:(dirname as text) inFolder:dir
    local dirname, dir

    tell (a reference to folder dirname in the dir) ¬
        to if it exists then return it

    make new folder at the dir with properties {name:dirname}
end newFolderNamed:inFolder:

I would urge you to test this script initially on a test directory containing sample files. In fact, the worst case scenario is the same as with your current script, which is the situation where you choose the wrong directory by mistake. Assuming the correct directory is selected, then the worst case scenario is that files don't get moved, so it's generally a very safe script.

I re-wrote the open handler too, to (hopefully) allow it to cater for multiple items being dropped onto the applet. This means you could, for example, drop two folders onto it and have both folders' contents re-structured. However, I haven't tested this. Again, the worst case scenario here is that it doesn't do anything, which will be the case if I am wrong about what type of file reference gets passed to the open handler (I assume it will be alias objects).