Modify script to output an RTF text file

Hi Mike.

“/volumes/files/inventory” is already a POSIX path, so it shouldn’t have 'POSIX path of ’ in front of it.

The effects you were getting were caused by my assuming that the characters in the root path would be in the same cases as in the names of the items on the disk and that the path itself would end with a slash. The root folder name’s taken almost directly from the path and so is shown in the same case as in the path. The non-matching cases and the lack of a trailing slash meant the root path wasn’t being recognised at the beginnings of the found paths.

I’ve now conveniently fixed all this by inserting a line near the beginning to ensure that the root path matches the script’s assumptions. :slight_smile:

Let me know if there are still any problems.

Hi again Nigel, it looks like I’ll need one more little tweak. When I copied the script into a run Applescript window and tried to run it I got a Syntax Error. It worked fine in Script Editor. If I remark out the command it runs.

“Expected end of line but found property” with “value” highlighted.

set theFont to |⌘|'s class "NSFont"'s fontWithName:("Times New Roman") |size|:(12.0)
   if (theFont is not missing value) then
       tell styledText to addAttribute:(|⌘|'s NSFontAttributeName) value:(theFont) range:({location:0, |length|:its |length|()})
   end if

Thanks,
Mike.

Model: Mac Mini
AppleScript: 2.4
Browser: Safari 600.3.18
Operating System: Mac OS X (10.10)

Possibly you have an OSAX installed which uses the term ‘value’. Try putting bars round the term here.

set theFont to |⌘|'s class "NSFont"'s fontWithName:("Times New Roman") |size|:(12.0)
if (theFont is not missing value) then
	tell styledText to addAttribute:(|⌘|'s NSFontAttributeName) |value|:(theFont) range:({location:0, |length|:its |length|()})
end if

XMLLib.osax uses value.

[format]XMLFind‚v : select a child (or children) satisfying a simple criterion: the name of the XML element and/or the key and value of an attribute. XMLFind is a poor man’s XMLXPath suitable (and fast) for simple queries and is not aware of the namespace specifiers
XMLFind XMLRef : the parent where the search occurs
[name string] : the name of the element
[key string] : the key of the attribute
[value string] : the value of the attribute
[all occurrences boolean] : returns a list of all occurrences. Default : false
→ XMLRef : or a list of XMLRefs with all occurrences[/format]

[format]PlistMatch‚v : return a list of dictionaries containing a given key or a given (key, value) pair.
PlistMatch CFRef : an array containing dictionaries
key string : the key to match. For a more complex request, omit this parameter and provide the “using” parameter
[using string] : a selecting path (see PlistChild)
[value string, real, or boolean] : the requested value of the key
→ list of CFRef[/format]

Yvan KOENIG running El Capitan 10.11.6 in French (VALLAURIS, France) vendredi 16 septembre 2016 21:37:06

Thanks for that tweak, that was the problem. The script is working great!!

I have question using your script above in concert with a script written by Shane Stanley that I have combined below. Shane’s script moves anyfile after X number of days to another location and is working perfectly. What would the extra commands be, to tell Shane’s script, that ONLY if anyfile was moved, then it should continue and run the “Folder Inventory” scripting. If no move was made the script would stop. The file moved will always have a different name. Combining these 2 scripts would produce a new inventory list as soon as a change took place. Also, I have observed that when the script successfully moves a file the result window shows “false”. If it doesn’t move anything the result window is blank.

Thanks for your help,
Mike.

use AppleScript version "2.4" -- Yosemite (10.10) or later
use framework "Foundation"
use scripting additions
use framework "AppKit"

on moveFilesFrom:posixFolderPath toFolder:destPosixPath numberOfDaysOld:numDays
	-- get date limit
	set dateLimit to current application's NSDate's dateWithTimeIntervalSinceNow:-(numDays * days)
	set dateLimit to current application's NSCalendar's currentCalendar()'s startOfDayForDate:dateLimit
	-- make URLs of POSIX paths
	set destFolderURL to current application's |NSURL|'s fileURLWithPath:destPosixPath
	set sourceFolderURL to current application's |NSURL|'s fileURLWithPath:posixFolderPath
	-- get file manager
	set theNSFileManager to current application's NSFileManager's defaultManager()
	-- get contents of directory, ignoring invisible items
	set theURLs to theNSFileManager's contentsOfDirectoryAtURL:sourceFolderURL includingPropertiesForKeys:{} options:(current application's NSDirectoryEnumerationSkipsHiddenFiles) |error|:(missing value)
	-- loop through URLs
	repeat with aURL in theURLs
		-- get date added
		set {theResult, theDate} to (aURL's getResourceValue:(reference) forKey:(current application's NSURLAddedToDirectoryDateKey) |error|:(missing value))
		-- test date
		if theResult as boolean and (theDate's compare:dateLimit) as integer < 0 then
			-- get name of file
			set theName to aURL's lastPathComponent()
			-- make new URL
			set destURL to (destFolderURL's URLByAppendingPathComponent:theName)
			-- move file
			(theNSFileManager's moveItemAtURL:aURL toURL:destURL |error|:(missing value))
			-- check destination for already exists,
			if (destURL's checkResourceIsReachableAndReturnError:(missing value)) as boolean then
				-- if yes, Delete
				(theNSFileManager's removeItemAtURL:aURL |error|:(missing value))
			end if
		end if
	end repeat
end moveFilesFrom:toFolder:numberOfDaysOld:

#=====

# Define your own paths here
set posixFolderPath to "volumes/files 1.5T/inventory/Folder A"
set destPosixPath to "volumes/files 1.5T/inventory/Folder B"

my moveFilesFrom:posixFolderPath toFolder:destPosixPath numberOfDaysOld:30

set posixFolderPath to "volumes/files 1.5T/inventory/Folder C"
set destPosixPath to "volumes/files 1.5T/inventory/Folder D"

my moveFilesFrom:posixFolderPath toFolder:destPosixPath numberOfDaysOld:30





main()

on main()
	-- set this_folder to POSIX path of (choose folder with prompt "Choose the root folder")
	set this_folder to "/volumes/files 1.5t/inventory"
	set log_file_path to POSIX path of ((path to desktop as text) & "Folder Inventory.rtf")
	
	-- Ensure that the root path has the correct cases and a trailing slash.
	set this_folder to POSIX path of ((POSIX file this_folder) as alias)
	
	set |⌘| to current application
	set rootURL to |⌘|'s class "NSURL"'s fileURLWithPath:(this_folder)
	
	-- Set up an NSFileManager enumerator and get the folder's "entire contents" (visible files and folders as NSURLs).
	set theFileManager to |⌘|'s class "NSFileManager"'s defaultManager()
	set dirAndPackageKeys to |⌘|'s class "NSArray"'s arrayWithArray:({|⌘|'s NSURLIsDirectoryKey, |⌘|'s NSURLIsPackageKey})
	set theEnumerator to theFileManager's enumeratorAtURL:(rootURL) includingPropertiesForKeys:(dirAndPackageKeys) options:((|⌘|'s NSDirectoryEnumerationSkipsHiddenFiles) + (|⌘|'s NSDirectoryEnumerationSkipsPackageDescendants as integer)) errorHandler:(missing value)
	set entireContents to theEnumerator's allObjects()
	
	-- Initialise an NSMutableAttributedString with the title line and two linefeeds.
	set styledText to |⌘|'s class "NSMutableAttributedString"'s alloc()'s initWithString:("Start " & (current date) & linefeed & linefeed)
	
	-- Make an attribute dictionary specifying blue as a foreground colour.
	set blue to |⌘|'s class "NSColor"'s colorWithCalibratedRed:(0.0) green:(0.0) blue:(1.0) alpha:(1.0)
	set blueText to |⌘|'s class "NSDictionary"'s dictionaryWithObject:(blue) forKey:(|⌘|'s NSForegroundColorAttributeName)
	
	-- Make an NSAttributedString with the root folder name and the blue foreground colour and append it to the NSMutableAttributedString.
	set thisAddition to |⌘|'s class "NSAttributedString"'s alloc()'s initWithString:(rootURL's lastPathComponent) attributes:(blueText)
	tell styledText to appendAttributedString:(thisAddition)
	
	-- Work through the "entire contents", truncating and colouring the paths as required and appending them to the NSMutableAttributedString.
	set truncationRegex to |⌘|'s class "NSString"'s stringWithString:("^" & this_folder & "|[^/]++/")
	set LF to |⌘|'s class "NSString"'s stringWithString:(linefeed)
	repeat with thisItem in entireContents
		set thisPath to thisItem's |path|()
		-- Replace the root path and any other container components in this path with spaces, leaving just the indented name.
		set indentedName to (thisPath's stringByReplacingOccurrencesOfString:(truncationRegex) withString:("  ") options:(|⌘|'s NSRegularExpressionSearch) range:({location:0, |length|:thisPath's |length|()}))
		-- Put a linefeed on the front, ready for appending to the preceding text.
		set textToAppend to (LF's stringByAppendingString:(indentedName))
		
		-- Get this item's directory and package values. If they're the same, the item's either a file or a package, otherwise it's a folder.
		set dpDictionary to (thisItem's resourceValuesForKeys:(dirAndPackageKeys) |error|:(missing value))
		set treatingAsFile to ((dpDictionary's objectForKey:(|⌘|'s NSURLIsDirectoryKey))'s isEqual:(dpDictionary's objectForKey:(|⌘|'s NSURLIsPackageKey))) as boolean
		
		-- Create an NSAttributedString with the doctored path, coloured or not as appropriate, and append it to the mutable one.
		if (treatingAsFile) then
			set thisAddition to (|⌘|'s class "NSAttributedString"'s alloc()'s initWithString:(textToAppend))
		else
			set thisAddition to (|⌘|'s class "NSAttributedString"'s alloc()'s initWithString:(textToAppend) attributes:(blueText))
		end if
		tell styledText to appendAttributedString:(thisAddition)
	end repeat
	
	-- Set the required font if you don't like the default Helvetica Neue 12.0.
	set theFont to |⌘|'s class "NSFont"'s fontWithName:("Times New Roman") |size|:(12.0)
	if (theFont is not missing value) then
		tell styledText to addAttribute:(|⌘|'s NSFontAttributeName) value:(theFont) range:({location:0, |length|:its |length|()})
	end if
	
	-- Extract RTF data from the attribute string and save using a URL derived from the given log file path.
	set theRTFData to styledText's RTFFromRange:({location:0, |length|:styledText's |length|()}) documentAttributes:(missing value)
	set logFileURL to |⌘|'s class "NSURL"'s fileURLWithPath:(log_file_path)
	tell theRTFData to writeToURL:(logFileURL) atomically:(true)
	say "Finished"
end main

Model: Mac Mini
AppleScript: 2.4
Browser: Safari 600.3.18
Operating System: Mac OS X (10.10)

If I’ve understood what you want, it would look something like this. I’ve inserted a boolean flag in Shane’s handler which gets set to ‘true’ if any items are moved. This is returned at the end of the handler and if it’s ‘true’, my handler (now renamed makeInventory()) makes an inventory of the folder from which the items were moved. I haven’t been able to test it, but it looks OK.

use AppleScript version "2.4" -- Yosemite (10.10) or later
use framework "Foundation"
use scripting additions
use framework "AppKit"

main()

on main()
	# Define your own paths here
	set posixFolderPath to "volumes/files 1.5T/NEW"
	set destPosixPath to "volumes/files 1.5T/OLD"
	
	set moveOccurred to (my moveFilesFrom:posixFolderPath toFolder:destPosixPath numberOfDaysOld:4)
	if (moveOccurred) then makeInventory(posixFolderPath)
end main

on moveFilesFrom:posixFolderPath toFolder:destPosixPath numberOfDaysOld:numDays
	-- get date limit
	set dateLimit to current application's NSDate's dateWithTimeIntervalSinceNow:-(numDays * days)
	set dateLimit to current application's NSCalendar's currentCalendar()'s startOfDayForDate:dateLimit
	-- make URLs of POSIX paths
	set destFolderURL to current application's |NSURL|'s fileURLWithPath:destPosixPath
	set sourceFolderURL to current application's |NSURL|'s fileURLWithPath:posixFolderPath
	-- get file manager
	set theNSFileManager to current application's NSFileManager's defaultManager()
	-- get contents of directory, ignoring invisible items
	set theURLs to theNSFileManager's contentsOfDirectoryAtURL:sourceFolderURL includingPropertiesForKeys:{} options:(current application's NSDirectoryEnumerationSkipsHiddenFiles) |error|:(missing value)
	-- Initialise a flag indicating whether any files have been moved. It'll be changed to 'true' below if any are.
	set moveOccurred to false
	-- loop through URLs
	repeat with aURL in theURLs
		-- get date added
		set {theResult, theDate} to (aURL's getResourceValue:(reference) forKey:(current application's NSURLAddedToDirectoryDateKey) |error|:(missing value))
		-- test date
		if theResult as boolean and (theDate's compare:dateLimit) as integer < 0 then
			-- get name of file
			set theName to aURL's lastPathComponent()
			-- make new URL
			set destURL to (destFolderURL's URLByAppendingPathComponent:theName)
			-- move file
			(theNSFileManager's moveItemAtURL:aURL toURL:destURL |error|:(missing value))
			-- Note the fact.
			set moveOccurred to true
			-- check destination for already exists,
			if (destURL's checkResourceIsReachableAndReturnError:(missing value)) as boolean then
				-- if yes, Delete
				(theNSFileManager's removeItemAtURL:aURL |error|:(missing value))
			end if
		end if
	end repeat
	return moveOccurred
end moveFilesFrom:toFolder:numberOfDaysOld:

on makeInventory(this_folder)
	-- set this_folder to POSIX path of (choose folder with prompt "Choose the root folder")
	--set this_folder to "/volumes/files/new"
	set log_file_path to POSIX path of ((path to desktop as text) & "Folder Inventory.rtf")
	
	-- Ensure that the root path has the correct cases and a trailing slash.
	set this_folder to POSIX path of ((POSIX file this_folder) as alias)
	
	set |⌘| to current application
	set rootURL to |⌘|'s class "NSURL"'s fileURLWithPath:(this_folder)
	
	-- Set up an NSFileManager enumerator and get the folder's "entire contents" (visible files and folders as NSURLs).
	set theFileManager to |⌘|'s class "NSFileManager"'s defaultManager()
	set dirAndPackageKeys to |⌘|'s class "NSArray"'s arrayWithArray:({|⌘|'s NSURLIsDirectoryKey, |⌘|'s NSURLIsPackageKey})
	set theEnumerator to theFileManager's enumeratorAtURL:(rootURL) includingPropertiesForKeys:(dirAndPackageKeys) options:((|⌘|'s NSDirectoryEnumerationSkipsHiddenFiles) + (|⌘|'s NSDirectoryEnumerationSkipsPackageDescendants as integer)) errorHandler:(missing value)
	set entireContents to theEnumerator's allObjects()
	
	-- Initialise an NSMutableAttributedString with the title line and two linefeeds.
	set styledText to |⌘|'s class "NSMutableAttributedString"'s alloc()'s initWithString:("Start " & (current date) & linefeed & linefeed)
	
	-- Make an attribute dictionary specifying blue as a foreground colour.
	set blue to |⌘|'s class "NSColor"'s colorWithCalibratedRed:(0.0) green:(0.0) blue:(1.0) alpha:(1.0)
	set blueText to |⌘|'s class "NSDictionary"'s dictionaryWithObject:(blue) forKey:(|⌘|'s NSForegroundColorAttributeName)
	
	-- Make an NSAttributedString with the root folder name and the blue foreground colour and append it to the NSMutableAttributedString.
	set thisAddition to |⌘|'s class "NSAttributedString"'s alloc()'s initWithString:(rootURL's lastPathComponent) attributes:(blueText)
	tell styledText to appendAttributedString:(thisAddition)
	
	-- Work through the "entire contents", truncating and colouring the paths as required and appending them to the NSMutableAttributedString.
	set truncationRegex to |⌘|'s class "NSString"'s stringWithString:("^" & this_folder & "|[^/]++/")
	set LF to |⌘|'s class "NSString"'s stringWithString:(linefeed)
	repeat with thisItem in entireContents
		set thisPath to thisItem's |path|()
		-- Replace the root path and any other container components in this path with spaces, leaving just the indented name.
		set indentedName to (thisPath's stringByReplacingOccurrencesOfString:(truncationRegex) withString:("  ") options:(|⌘|'s NSRegularExpressionSearch) range:({location:0, |length|:thisPath's |length|()}))
		-- Put a linefeed on the front, ready for appending to the preceding text.
		set textToAppend to (LF's stringByAppendingString:(indentedName))
		
		-- Get this item's directory and package values. If they're the same, the item's either a file or a package, otherwise it's a folder.
		set dpDictionary to (thisItem's resourceValuesForKeys:(dirAndPackageKeys) |error|:(missing value))
		set treatingAsFile to ((dpDictionary's objectForKey:(|⌘|'s NSURLIsDirectoryKey))'s isEqual:(dpDictionary's objectForKey:(|⌘|'s NSURLIsPackageKey))) as boolean
		
		-- Create an NSAttributedString with the doctored path, coloured or not as appropriate, and append it to the mutable one.
		if (treatingAsFile) then
			set thisAddition to (|⌘|'s class "NSAttributedString"'s alloc()'s initWithString:(textToAppend))
		else
			set thisAddition to (|⌘|'s class "NSAttributedString"'s alloc()'s initWithString:(textToAppend) attributes:(blueText))
		end if
		tell styledText to appendAttributedString:(thisAddition)
	end repeat
	
	-- Set the required font if you don't like the default Helvetica Neue 12.0.
	set theFont to |⌘|'s class "NSFont"'s fontWithName:("Times New Roman") |size|:(12.0)
	if (theFont is not missing value) then
		tell styledText to addAttribute:(|⌘|'s NSFontAttributeName) |value|:(theFont) range:({location:0, |length|:its |length|()})
	end if
	
	-- Extract RTF data from the attribute string and save using a URL derived from the given log file path.
	set theRTFData to styledText's RTFFromRange:({location:0, |length|:styledText's |length|()}) documentAttributes:(missing value)
	set logFileURL to |⌘|'s class "NSURL"'s fileURLWithPath:(log_file_path)
	tell theRTFData to writeToURL:(logFileURL) atomically:(true)
	say "Finished"
end makeInventory

Hi Nigel, I’ve edited my previous post at the end of the first script to move files, under “Define your own paths here”, to more accurately show you what I’m trying to do. I have an Inventory main folder with 4 folders inside. Any files that have been in Folder A more than 30 days will be moved to Folder B. Any files in Folder C more than 30 days will be moved to Folder D. The Folder Inventory RTF should still be generated from the main enclosing folder “Inventory” as shown earlier in this thread.

These 2 scripts are the beginning pieces of a longer script that processes the Inventory RTF further. So, that’s the reason I asked for the script to only generate the Inventory RTF when a file has been moved. Perhaps a better/easier request would be for an instruction line that stops the script if no files were moved. The script as shown does work now, to move the files and generate the RTF. I just don’t want the complete script to run if no files were moved.

Your patience and your help are greatly appreciated,
Mike.

Model: Mac Mini
AppleScript: 2.4
Browser: Safari 600.3.18
Operating System: Mac OS X (10.10)

With regard to the results you see in Script Editor from the version of Shane’s handler you posted, they’re simply the results (if any) from the last thing it does before it finishes. It doesn’t explicity return a result.

I’m actually a bit puzzled by that part of the handler. It moves each qualifying item to the new location, then checks to see if an item with that name already exists there. If there is one ” and there should be by then, one way or another ” the handler tries to delete the item in the source location. The results shown in Script Editor are consistent with that.

Presumably the intended action is to check the destination first and delete any conflicting item there before attempting the move. I’ve adjusted the handler code to do that below. As in my script above, it also now explicitly returns whether or not a move took place. In the light of your clarification, I’ve renamed my own handler logFolderHierarchy() and have made both the root path and the path to the RTF file its parameters. That way, you only need to specify the paths explicity once, near the top of the script, and can use the variables to feed whatever code you add later. Edit: I’ve also made it more efficient by styling the text per block of colour instead of per line.

use AppleScript version "2.4" -- Yosemite (10.10) or later
use framework "Foundation"
use scripting additions
use framework "AppKit"

main()

on main()
	# Define your own paths here
	set inventoryFolderPath to "Volumes/Files 1.5T/Inventory"
	set logFilePath to POSIX path of ((path to desktop as text) & "Folder Inventory.rtf")
	
	set folderAPath to inventoryFolderPath & "/Folder A"
	set folderBPath to inventoryFolderPath & "/Folder B"
	
	set AtoBMoveOccurred to (my moveFilesFrom:folderAPath toFolder:folderBPath numberOfDaysOld:30)
	
	set folderCPath to inventoryFolderPath & "/Folder C"
	set folderDPath to inventoryFolderPath & "/Folder D"
	
	set CtoDMoveOccurred to (my moveFilesFrom:folderCPath toFolder:folderDPath numberOfDaysOld:30)
	
	-- Finish here if no items were moved.
	if (not ((AtoBMoveOccurred) or (CtoDMoveOccurred))) then return
	
	logFolderHierarchy(inventoryFolderPath, logFilePath)
end main

-- Move items from one folder to another if they've been there more than a certain number of calendar days.
-- Handler by Shane Stanley. 'moveOccurred' flag added and destination check altered by NG.
on moveFilesFrom:posixFolderPath toFolder:destPosixPath numberOfDaysOld:numDays
	-- get date limit
	set dateLimit to current application's NSDate's dateWithTimeIntervalSinceNow:-(numDays * days)
	set dateLimit to current application's NSCalendar's currentCalendar()'s startOfDayForDate:dateLimit
	-- make URLs of POSIX paths
	set destFolderURL to current application's |NSURL|'s fileURLWithPath:destPosixPath
	set sourceFolderURL to current application's |NSURL|'s fileURLWithPath:posixFolderPath
	-- get file manager
	set theNSFileManager to current application's NSFileManager's defaultManager()
	-- get contents of directory, ignoring invisible items
	set theURLs to theNSFileManager's contentsOfDirectoryAtURL:sourceFolderURL includingPropertiesForKeys:{} options:(current application's NSDirectoryEnumerationSkipsHiddenFiles) |error|:(missing value)
	-- Initialise a flag indicating whether any files have been moved. It'll be changed to 'true' below if any are.
	set moveOccurred to false
	-- loop through URLs
	repeat with aURL in theURLs
		-- get date added
		set {theResult, theDate} to (aURL's getResourceValue:(reference) forKey:(current application's NSURLAddedToDirectoryDateKey) |error|:(missing value))
		-- test date
		if theResult as boolean and (theDate's compare:dateLimit) as integer < 0 then
			-- get name of file
			set theName to aURL's lastPathComponent()
			-- make new URL
			set destURL to (destFolderURL's URLByAppendingPathComponent:theName)
			-- check destination for already exists,
			if (destURL's checkResourceIsReachableAndReturnError:(missing value)) as boolean then
				-- if yes, Delete
				(theNSFileManager's removeItemAtURL:destURL |error|:(missing value))
			end if
			-- move file
			(theNSFileManager's moveItemAtURL:aURL toURL:destURL |error|:(missing value))
			-- Note that at least one move has occurred.
			set moveOccurred to true
		end if
	end repeat
	
	return moveOccurred
end moveFilesFrom:toFolder:numberOfDaysOld:

-- List a folder's entire visible hierarchy (names only, indented according to depth; folder names in blue) and save as RTF text.
-- Handler by Nigel Garvey.
on logFolderHierarchy(rootPath, logFilePath)
	-- The paths are assumed to be POSIX paths. Ensure that rootPath has the correct cases and a trailing slash.
	set rootPath to POSIX path of ((POSIX file rootPath) as alias)
	
	set |⌘| to current application
	set rootURL to |⌘|'s class "NSURL"'s fileURLWithPath:(rootPath)
	
	-- Set up an NSFileManager enumerator and get the folder's "entire contents" (visible files and folders as NSURLs).
	set theFileManager to |⌘|'s class "NSFileManager"'s defaultManager()
	set dirAndPackageKeys to |⌘|'s class "NSArray"'s arrayWithArray:({|⌘|'s NSURLIsDirectoryKey, |⌘|'s NSURLIsPackageKey})
	set theEnumerator to theFileManager's enumeratorAtURL:(rootURL) includingPropertiesForKeys:(dirAndPackageKeys) options:((|⌘|'s NSDirectoryEnumerationSkipsHiddenFiles) + (|⌘|'s NSDirectoryEnumerationSkipsPackageDescendants as integer)) errorHandler:(missing value)
	set entireContents to theEnumerator's allObjects()
	
	-- Initialise an NSMutableAttributedString with the title line and two linefeeds.
	set styledText to |⌘|'s class "NSMutableAttributedString"'s alloc()'s initWithString:("Start " & (current date) & linefeed & linefeed)
	
	-- Make an attribute dictionary specifying blue as a foreground colour.
	set blue to |⌘|'s class "NSColor"'s colorWithCalibratedRed:(0.0) green:(0.0) blue:(1.0) alpha:(1.0)
	set blueText to |⌘|'s class "NSDictionary"'s dictionaryWithObject:(blue) forKey:(|⌘|'s NSForegroundColorAttributeName)
	
	-- Start a "run" of folder names with the name of the root folder.
	set currentRun to rootURL's lastPathComponent()'s mutableCopy()
	set isFileRun to false
	
	-- Work through the "entire contents", replacing container components in the paths with indents. If an item's of the same type (file or folder) as the item before, append its name to the current run of names. If not, style the current run and append it to the styled text, then start a new run with the current name.
	set rootAndContainerRegex to |⌘|'s class "NSString"'s stringWithString:("^" & rootPath & "|[^/]++/")
	set LF to |⌘|'s class "NSString"'s stringWithString:(linefeed)
	repeat with thisItem in entireContents
		set thisPath to thisItem's |path|()
		-- Replace the root path and any other container components in this path with spaces, leaving just an indented name.
		set indentedName to (thisPath's stringByReplacingOccurrencesOfString:(rootAndContainerRegex) withString:("  ") options:(|⌘|'s NSRegularExpressionSearch) range:({location:0, |length|:thisPath's |length|()}))
		-- Append to a linefeed, ready for appending to the preceding text.
		set indentedNameLine to (LF's stringByAppendingString:(indentedName))
		
		-- Get this item's directory and package flags. If they're equal, the item's either a file or a package. Otherwise it's a folder.
		set dpDictionary to (thisItem's resourceValuesForKeys:(dirAndPackageKeys) |error|:(missing value))
		set treatingAsFile to ((dpDictionary's objectForKey:(|⌘|'s NSURLIsDirectoryKey))'s isEqual:(dpDictionary's objectForKey:(|⌘|'s NSURLIsPackageKey))) as boolean
		-- Is the item of the same type as for the current run of names?
		if ((treatingAsFile) = (isFileRun)) then
			-- If so, simply append its indented name to the run text.
			tell currentRun to appendString:(indentedNameLine)
		else
			-- Otherwise, make an NSAttributedString with the current run text, coloured or not as appropriate.
			if (isFileRun) then
				set thisColourBlock to (|⌘|'s class "NSAttributedString"'s alloc()'s initWithString:(currentRun))
			else
				set thisColourBlock to (|⌘|'s class "NSAttributedString"'s alloc()'s initWithString:(currentRun) attributes:(blueText))
			end if
			-- Append this to the main styled text.
			tell styledText to appendAttributedString:(thisColourBlock)
			-- Start a new run text with the current name line and set the run-type flag accordingly.
			set currentRun to indentedNameLine's mutableCopy()
			set isFileRun to treatingAsFile
		end if
	end repeat
	-- Append the final run to the styled text at the end. 
	if (isFileRun) then
		set thisColourBlock to (|⌘|'s class "NSAttributedString"'s alloc()'s initWithString:(currentRun))
	else
		set thisColourBlock to (|⌘|'s class "NSAttributedString"'s alloc()'s initWithString:(currentRun) attributes:(blueText))
	end if
	tell styledText to appendAttributedString:(thisColourBlock)
	
	-- Set the required font if you don't like the default Helvetica Neue 12.0.
	set theFont to |⌘|'s class "NSFont"'s fontWithName:("Geneva") |size|:(12.0)
	if (theFont is not missing value) then
		tell styledText to addAttribute:(|⌘|'s NSFontAttributeName) |value|:(theFont) range:({location:0, |length|:its |length|()})
	end if
	
	-- Extract RTF data from the styled text and save using a URL derived from the given log file path.
	set theRTFData to styledText's RTFFromRange:({location:0, |length|:styledText's |length|()}) documentAttributes:(missing value)
	set logFileURL to |⌘|'s class "NSURL"'s fileURLWithPath:(logFilePath)
	tell theRTFData to writeToURL:(logFileURL) atomically:(true)
	say "Finished"
end logFolderHierarchy

Well spotted. This is my fault – I typed aURL instead of destURL, and I wasn’t specific about where exactly it should be inserted in the original script. Unfortunately it’s ended up in the wrong place.

Hi, thanks for your description of how the handlers work. Wouldn’t it be more efficient to have the script check the destination folder first, and if the file already exists, do nothing, rather than to delete it and move in a new one? (The files I’m moving are video files as large as 4Gb) Then, because the file already exists in the destination folder, delete the file that was to be moved. I think that is why Shane’s instruction was to delete the file in the source folder. That was the way I worded my description to him. So now, the combined script should stop, if the file already exists in destination(delete the newer file), because no move to destination folder was made.

I believe the following instruction is what you wrote to stop the script.

if (not ((AtoBMoveOccurred) or (CtoDMoveOccurred))) then return

I thought that I would be able to add more script to the end of your script, but found from my testing, that it doesn’t stop the SCRIPT if it doesn’t move a file, it just stops it from generating the Inventory RTF, and then continues running my additional script(just plain Applescript not AObjc) that I added to the end. I use a few programs to processes the RTF file into an EPUB file and eventually syncing it to an iPad. It takes about 5 mins.
How do I make my additional scripting run only if a move was made?
Thanks for your help.
Mike.

Model: Mac Mini
AppleScript: 2.4
Browser: Safari 600.3.18
Operating System: Mac OS X (10.10)

It’s a handler which moves an item to another folder. The usual assumption in such a case is that if you call the handler, you want to move the item. The reason for checking the destination first is that the system itself won’t move the item if there’s already another with the same name in the destination folder. That other item has to be deleted first. Since the handler was doing screwy things (as described above), I fixed it for the most probably intended action given its comments. I hadn’t read and wasn’t answering a query from another thread and the only reason given in this one for files not being moved is their being in the source folder for less than thirty days. (And strictly speaking, even that has little to do with saving an RTF text with some lines coloured blue.)

I was assuming the rest of your code would go after the logFolderHierarchy() call in the main() handler. I didn’t make that clear. If you put it there, it won’t be executed either if the handler finishes early. If that’s not practical for some reason, you could change the exit line to:

if (not ((AtoBMoveOccurred) or (CtoDMoveOccurred))) then error number -128

error number -128 is the “User canceled” error, which simply stops a script on the spot.

Hi, the complete script is working perfectly. I’m sorry if I had become a pest by asking so many questions. Your explanations throughout were very helpful. I added the “User Cancel” error -128 as you suggested and I also added an “else” to the movefile section of the script.

Finally, I would like to say thank you very much to Nigel Garvey, Shane Stanley, and Yvan Koenig for your willingness to take the time out of your day to craft these scripts for me and others on this site. Your knowledge and generosity is a credit to macscripter.net !!!

Mike.

use AppleScript version "2.4" -- Yosemite (10.10) or later
use framework "Foundation"
use scripting additions
use framework "AppKit"

main()

on main()
	# Define your own paths here
	set inventoryFolderPath to "Volumes/Files 1.5T/Inventory"
	set logFilePath to POSIX path of ((path to desktop as text) & "Folder Inventory.rtf")
	
	set folderAPath to inventoryFolderPath & "/Folder A"
	set folderBPath to inventoryFolderPath & "/Folder B"
	
	set AtoBMoveOccurred to (my moveFilesFrom:folderAPath toFolder:folderBPath numberOfDaysOld:30)
	
	set folderCPath to inventoryFolderPath & "/Folder C"
	set folderDPath to inventoryFolderPath & "/Folder D"
	
	set CtoDMoveOccurred to (my moveFilesFrom:folderCPath toFolder:folderDPath numberOfDaysOld:30)
	
	-- Finish here if no items were moved.
	if (not ((AtoBMoveOccurred) or (CtoDMoveOccurred))) then error number -128
	
	logFolderHierarchy(inventoryFolderPath, logFilePath)
end main

-- Move items from one folder to another if they've been there more than a certain number of calendar days.
-- Handler by Shane Stanley. 'moveOccurred' flag added and destination check altered by NG.
on moveFilesFrom:posixFolderPath toFolder:destPosixPath numberOfDaysOld:numDays
	-- get date limit
	set dateLimit to current application's NSDate's dateWithTimeIntervalSinceNow:-(numDays * days)
	set dateLimit to current application's NSCalendar's currentCalendar()'s startOfDayForDate:dateLimit
	-- make URLs of POSIX paths
	set destFolderURL to current application's |NSURL|'s fileURLWithPath:destPosixPath
	set sourceFolderURL to current application's |NSURL|'s fileURLWithPath:posixFolderPath
	-- get file manager
	set theNSFileManager to current application's NSFileManager's defaultManager()
	-- get contents of directory, ignoring invisible items
	set theURLs to theNSFileManager's contentsOfDirectoryAtURL:sourceFolderURL includingPropertiesForKeys:{} options:(current application's NSDirectoryEnumerationSkipsHiddenFiles) |error|:(missing value)
	-- Initialise a flag indicating whether any files have been moved. It'll be changed to 'true' below if any are.
	set moveOccurred to false
	-- loop through URLs
	repeat with aURL in theURLs
		-- get date added
		set {theResult, theDate} to (aURL's getResourceValue:(reference) forKey:(current application's NSURLAddedToDirectoryDateKey) |error|:(missing value))
		-- test date
		if theResult as boolean and (theDate's compare:dateLimit) as integer < 0 then
			-- get name of file
			set theName to aURL's lastPathComponent()
			-- make new URL
			set destURL to (destFolderURL's URLByAppendingPathComponent:theName)
			-- check destination for already exists,
			if (destURL's checkResourceIsReachableAndReturnError:(missing value)) as boolean then
				-- if yes, Delete
				(theNSFileManager's removeItemAtURL:aURL |error|:(missing value))
			else
				-- move file
				(theNSFileManager's moveItemAtURL:aURL toURL:destURL |error|:(missing value))
				-- Note that at least one move has occurred.
				set moveOccurred to true
			end if
		end if
	end repeat
	return moveOccurred
end moveFilesFrom:toFolder:numberOfDaysOld:

-- List a folder's entire visible hierarchy (names only, indented according to depth; folder names in blue) and save as RTF text.
-- Handler by Nigel Garvey.
on logFolderHierarchy(rootPath, logFilePath)
	-- The paths are assumed to be POSIX paths. Ensure that rootPath has the correct cases and a trailing slash.
	set rootPath to POSIX path of ((POSIX file rootPath) as alias)
	
	set |⌘| to current application
	set rootURL to |⌘|'s class "NSURL"'s fileURLWithPath:(rootPath)
	
	-- Set up an NSFileManager enumerator and get the folder's "entire contents" (visible files and folders as NSURLs).
	set theFileManager to |⌘|'s class "NSFileManager"'s defaultManager()
	set dirAndPackageKeys to |⌘|'s class "NSArray"'s arrayWithArray:({|⌘|'s NSURLIsDirectoryKey, |⌘|'s NSURLIsPackageKey})
	set theEnumerator to theFileManager's enumeratorAtURL:(rootURL) includingPropertiesForKeys:(dirAndPackageKeys) options:((|⌘|'s NSDirectoryEnumerationSkipsHiddenFiles) + (|⌘|'s NSDirectoryEnumerationSkipsPackageDescendants as integer)) errorHandler:(missing value)
	set entireContents to theEnumerator's allObjects()
	
	-- Initialise an NSMutableAttributedString with the title line and two linefeeds.
	set styledText to |⌘|'s class "NSMutableAttributedString"'s alloc()'s initWithString:("Start " & (current date) & linefeed & linefeed)
	
	-- Make an attribute dictionary specifying blue as a foreground colour.
	set blue to |⌘|'s class "NSColor"'s colorWithCalibratedRed:(0.0) green:(0.0) blue:(0.6) alpha:(1.0)
	set blueText to |⌘|'s class "NSDictionary"'s dictionaryWithObject:(blue) forKey:(|⌘|'s NSForegroundColorAttributeName)
	set green to |⌘|'s class "NSColor"'s colorWithCalibratedRed:(0.0) green:(0.4) blue:(0.0) alpha:(1.0)
	set greenText to |⌘|'s class "NSDictionary"'s dictionaryWithObject:(green) forKey:(|⌘|'s NSForegroundColorAttributeName)
	
	-- Start a "run" of folder names with the name of the root folder.
	set currentRun to rootURL's lastPathComponent()'s mutableCopy()
	set isFileRun to false
	
	-- Work through the "entire contents", replacing container components in the paths with indents. If an item's of the same type (file or folder) as the item before, append its name to the current run of names. If not, style the current run and append it to the styled text, then start a new run with the current name.
	set rootAndContainerRegex to |⌘|'s class "NSString"'s stringWithString:("^" & rootPath & "|[^/]++/")
	set LF to |⌘|'s class "NSString"'s stringWithString:(linefeed)
	repeat with thisItem in entireContents
		set thisPath to thisItem's |path|()
		-- Replace the root path and any other container components in this path with spaces, leaving just an indented name.
		set indentedName to (thisPath's stringByReplacingOccurrencesOfString:(rootAndContainerRegex) withString:("	") options:(|⌘|'s NSRegularExpressionSearch) range:({location:0, |length|:thisPath's |length|()}))
		-- Append to a linefeed, ready for appending to the preceding text.
		set indentedNameLine to (LF's stringByAppendingString:(indentedName))
		
		-- Get this item's directory and package flags. If they're equal, the item's either a file or a package. Otherwise it's a folder.
		set dpDictionary to (thisItem's resourceValuesForKeys:(dirAndPackageKeys) |error|:(missing value))
		set treatingAsFile to ((dpDictionary's objectForKey:(|⌘|'s NSURLIsDirectoryKey))'s isEqual:(dpDictionary's objectForKey:(|⌘|'s NSURLIsPackageKey))) as boolean
		-- Is the item of the same type as for the current run of names?
		if ((treatingAsFile) = (isFileRun)) then
			-- If so, simply append its indented name to the run text.
			tell currentRun to appendString:(indentedNameLine)
		else
			-- Otherwise, make an NSAttributedString with the current run text, coloured or not as appropriate.
			if (isFileRun) then
				set thisColourBlock to (|⌘|'s class "NSAttributedString"'s alloc()'s initWithString:(currentRun) attributes:(greenText))
			else
				set thisColourBlock to (|⌘|'s class "NSAttributedString"'s alloc()'s initWithString:(currentRun) attributes:(blueText))
			end if
			-- Append this to the main styled text.
			tell styledText to appendAttributedString:(thisColourBlock)
			-- Start a new run text with the current name line and set the run-type flag accordingly.
			set currentRun to indentedNameLine's mutableCopy()
			set isFileRun to treatingAsFile
		end if
	end repeat
	-- Append the final run to the styled text at the end. 
	if (isFileRun) then
		set thisColourBlock to (|⌘|'s class "NSAttributedString"'s alloc()'s initWithString:(currentRun) attributes:(greenText))
	else
		set thisColourBlock to (|⌘|'s class "NSAttributedString"'s alloc()'s initWithString:(currentRun) attributes:(blueText))
	end if
	tell styledText to appendAttributedString:(thisColourBlock)
	
	-- Set the required font if you don't like the default Helvetica Neue 12.0.
	set theFont to |⌘|'s class "NSFont"'s fontWithName:("Geneva") |size|:(14.0)
	if (theFont is not missing value) then
		tell styledText to addAttribute:(|⌘|'s NSFontAttributeName) value:(theFont) range:({location:0, |length|:its |length|()})
	end if
	
	-- Extract RTF data from the styled text and save using a URL derived from the given log file path.
	set theRTFData to styledText's RTFFromRange:({location:0, |length|:styledText's |length|()}) documentAttributes:(missing value)
	set logFileURL to |⌘|'s class "NSURL"'s fileURLWithPath:(logFilePath)
	tell theRTFData to writeToURL:(logFileURL) atomically:(true)
	say "Finished"
end logFolderHierarchy

Model: Mac Mini
AppleScript: 2.4
Browser: Safari 600.3.18
Operating System: Mac OS X (10.10)

Hi Mike.

Glad it came together. I see you’ve been having fun with the colours. :slight_smile:

I noticed my development script for the logFolderHierarchy() folder still on my desktop today. After a bit of a rethink, I’ve come up with the version below, which is somewhat faster. Instead of building up the styled text block by block, it initialises it complete and simply colours in the folder names. As posted here, it reproduces Mike’s preference for basically green(ish) Geneva 14.0 pt text with blue folder names.

use AppleScript version "2.4"
use scripting additions
use framework "Foundation"
use framework "AppKit"

set rootFolderPath to POSIX path of (choose folder with prompt "Choose the root folder")
set logFilePath to POSIX path of ((path to desktop as text) & "Folder Inventory.rtf")
logFolderHierarchy(rootFolderPath, logFilePath)

on logFolderHierarchy(rootPath, logFilePath)
	-- The paths are assumed to be POSIX paths. Ensure here that rootPath has the correct cases and a trailing slash.
	set rootPath to POSIX path of ((POSIX file rootPath) as alias)
	
	set |⌘| to current application
	
	-- Set up an NSFileManager enumerator and get the root folder's "entire contents" (visible files and folders) as NSURLs.
	set theFileManager to |⌘|'s class "NSFileManager"'s defaultManager()
	set rootURL to |⌘|'s class "NSURL"'s fileURLWithPath:(rootPath)
	set dirAndPackageKeys to |⌘|'s class "NSArray"'s arrayWithArray:({|⌘|'s NSURLIsDirectoryKey, |⌘|'s NSURLIsPackageKey})
	set theEnumerator to theFileManager's enumeratorAtURL:(rootURL) includingPropertiesForKeys:(dirAndPackageKeys) options:((|⌘|'s NSDirectoryEnumerationSkipsHiddenFiles) + (|⌘|'s NSDirectoryEnumerationSkipsPackageDescendants as integer)) errorHandler:(missing value)
	set entireContents to theEnumerator's allObjects()
	
	-- Create an indented listing of the contents' names.
	set pathListing to (entireContents's valueForKey:("path"))'s componentsJoinedByString:(linefeed)
	set rootAndContainerRegex to "(?m)^" & rootPath & "|[^/\\n]++/"
	set indentedListing to (pathListing's stringByReplacingOccurrencesOfString:(rootAndContainerRegex) withString:(tab) options:(|⌘|'s NSRegularExpressionSearch) range:({location:0, |length|:pathListing's |length|()}))
	
	-- Construct the full text, unstyled, working out the ranges in it of the root folder line and the whole of the indented text.
	set fullText to |⌘|'s class "NSMutableString"'s stringWithString:("Start " & (current date) & linefeed & linefeed)
	set rootFolderLine to rootURL's |path|() -- or, if preferred, rootURL's lastPathComponent()
	set rootFolderLineRange to {location:(fullText's |length|()), |length|:(rootFolderLine's |length|())}
	tell fullText to appendString:(rootFolderLine)
	tell fullText to appendString:(linefeed)
	set indentedTextRange to {location:fullText's |length|(), |length|:indentedListing's |length|()}
	tell fullText to appendString:(indentedListing)
	-- Get the individual ranges of all the lines in the indented part of the text.
	set indentedLineRegex to |⌘|'s class "NSRegularExpression"'s regularExpressionWithPattern:("(?m)^.++$") options:(0) |error|:(missing value)
	set indentedLineRanges to (indentedLineRegex's matchesInString:(fullText) options:(0) range:(indentedTextRange))'s valueForKey:("range")
	
	-- Initialise a mutable styled text with the full text in green(ish) Geneva 14.0 point.
	set fontFamilyAttribute to |⌘|'s class "NSDictionary"'s dictionaryWithObject:("Geneva") forKey:(|⌘|'s NSFontFamilyAttribute)
	set fontDescriptor to |⌘|'s class "NSFontDescriptor"'s fontDescriptorWithFontAttributes:(fontFamilyAttribute)
	set theFont to |⌘|'s class "NSFont"'s fontWithDescriptor:(fontDescriptor) |size|:(14.0) -- or |size|:(12.0) for 12.0 pt text.
	set green to |⌘|'s class "NSColor"'s colorWithCalibratedRed:(0.0) green:(0.4) blue:(0.0) alpha:(1.0)
	set mainStyleAttributes to |⌘|'s class "NSDictionary"'s dictionaryWithObjects:({theFont, green}) forKeys:({|⌘|'s NSFontAttributeName, |⌘|'s NSForegroundColorAttributeName})
	-- Use the following line instead of the above two to get the default black.
	-- set mainStyleAttributes to |⌘|'s class "NSDictionary"'s dictionaryWithObject:(theFont) forKey:(|⌘|'s NSFontAttributeName)
	set styledText to |⌘|'s class "NSMutableAttributedString"'s alloc()'s initWithString:(fullText) attributes:(mainStyleAttributes)
	
	-- Get a blue colour to apply to folder names.
	set blue to |⌘|'s class "NSColor"'s colorWithCalibratedRed:(0.0) green:(0.0) blue:(1.0) alpha:(1.0)
	-- Apply it to the root folder line in the styled text .
	tell styledText to addAttribute:(|⌘|'s NSForegroundColorAttributeName) value:(blue) range:(rootFolderLineRange)
	-- . and to any of the indented lines corresponding to folder URLs in the entireContents array.
	-- Preset a dictionary for comparison with resource results, suggested by Shane Stanley.
	set isFolderDict to |⌘|'s class "NSDictionary"'s dictionaryWithObjects:({true, false}) forKeys:(dirAndPackageKeys) -- directory yes, package no
	repeat with i from 1 to (entireContents's |count|())
		set thisURL to item i of entireContents
		set dpDictionary to (thisURL's resourceValuesForKeys:(dirAndPackageKeys) |error|:(missing value))
		if ((dpDictionary's isEqualToDictionary:isFolderDict) as boolean) then
			-- This URL targets a folder. Get the corresponding range from the indentedLineRanges array and colour that range in the styled text.
			tell styledText to addAttribute:(|⌘|'s NSForegroundColorAttributeName) value:(blue) range:(item i of indentedLineRanges)
		end if
	end repeat
	
	-- Write the styled text as RTF to a new file at the log file destination.
	set theRTFData to styledText's RTFFromRange:({location:0, |length|:styledText's |length|()}) documentAttributes:(missing value)
	set logFileURL to |⌘|'s class "NSURL"'s fileURLWithPath:(logFilePath)
	tell theRTFData to writeToURL:(logFileURL) atomically:(true)
	say "Finished"
end logFolderHierarchy

Edit: Minor but effective modification as suggested by Shane in the following post.

Given that the repeat loop might be called a lot of times, you might be able to optimise it a little by reducing the number of times items need to be resolved or converted to/from Cocoa objects. So perhaps replace the repeat loop with something like this:

set isFolderDict to current application's NSDictionary's dictionaryWithObjects:dirAndPackageKeys forKeys:{true, false} -- directory yes, package no
set NSForegroundColorAttributeName to |⌘|'s NSForegroundColorAttributeName -- resolve once
repeat with i from 1 to (entireContents's |count|())
	set thisURL to item i of entireContents
	set dpDictionary to (thisURL's resourceValuesForKeys:(dirAndPackageKeys) |error|:(missing value))
	if (dpDictionary's isEqualToDictionary:isFolderDict) as boolean then
		-- This URL targets a folder. Get the corresponding range from the indentedLineRanges array and colour that range in the styled text.
		tell styledText to addAttribute:NSForegroundColorAttributeName value:(blue) range:(item i of indentedLineRanges)
	end if
end repeat

Thanks Shane!

The dictionary idea definitely speeds things up even further, although of course the objects and keys should be transposed:

set isFolderDict to current application's NSDictionary's dictionaryWithObjects:{true, false} forKeys:dirAndPackageKeys -- directory yes, package no

I’ll modify the handler above accordingly.

Pre-resolving the constant doesn’t appear to make any appreciable or consistent difference ” not with a hierarchy of 7124 items, anyway. Maybe it would with a larger hierarchy and with the script not running in a “scripting environment”.

Damn :frowning: If only I had a dollar for every time I did that…

. or with a greater number of folder names to recolour. :rolleyes:

Hi Nigel and Shane, thanks very much for the revised Applescript. FYI, I tried to match the colors that Script Editor uses for blue and green, 0.0, 0.0, 0.6 for blue and 0.0, 0.4, 0.0 for green. I like the contrast and it’s easy to read.

Thanks again, Mike.

Model: Mac Mini
AppleScript: 2.4
Browser: Safari 600.3.18
Operating System: Mac OS X (10.10)

You can incorporate code to match them exactly:

set defaults to current application's NSUserDefaults's alloc()'s init()'s persistentDomainForName:"com.apple.applescript"
set theColors to defaults's valueForKey:"AppleScriptSourceAttributes"
set green to (theColors's objectAtIndex:6)'s valueForKey:"NSColor"
set green to (current application's NSUnarchiver's unarchiveObjectWithData:green)
set blue to (theColors's objectAtIndex:3)'s valueForKey:"NSColor"
set blue to (current application's NSUnarchiver's unarchiveObjectWithData:blue)

The index numbers are zero-based indexes in the order they appear in the preferences panel.