Sort OSX Recent Items by Date..?

This hint:
http://bbs.applescript.net/viewtopic.php?id=10334
explains how to read the Apple recent items list.

However, is there a way to sort the items of such list (e.g. apps, docs) by date…?

The recent items script wouldn’t compile, but as a start, this will sort files by date:

set sortFolder to choose folder
tell application "Finder"
	set sortFiles to files of sortFolder
	set sortFiles to sort sortFiles by creation date -- or modification date
end tell

or try this:

set fileList to ""
set sortFolder to choose folder
tell application "Finder"
	set sortFiles to files of sortFolder
	set fileCount to (count of sortFiles) as string
	set sortFiles to sort sortFiles by creation date -- or modification date
	repeat with aFile in sortFiles
		set aFile to name of aFile as string --remove "name of" for alias list
		set fileList to fileList & aFile & return & return
	end repeat
	
end tell
--if there are a lot of files, they won't all show, but the count will be correct
display dialog fileCount & " Files:" & return & return & fileList

or this:

set fileList to {}
set sortFolder to choose folder
tell application "Finder"
	set sortFiles to files of sortFolder
	set fileCount to (count of sortFiles) as string
	set sortFiles to sort sortFiles by creation date -- or modification date
	repeat with aFile in sortFiles
		set aFile to aFile as string --removed "name of" for alias list
		set fileList to fileList & aFile
	end repeat
	
end tell

choose from list fileList

“defaults read” doesn’t look very helpful with Recent Items in Tiger. Here’s a non-shell method that uses System Events’s Property List Suite (Tiger only), Finder’s sort (ditto), and the magic of File Read/Write. The results are sorted lists of Finder items.

on getAliasesFrom(CLIrecords)
	set theAliases to {}
	set tempFile to (path to temporary items as Unicode text) & "aliasData.dat"
	set fRef to (open for access file tempFile with write permission)
	try
		repeat with i from 1 to (count CLIrecords)
			set eof fRef to 0
			write |Alias| of item i of CLIrecords to fRef
			set end of theAliases to (read fRef from 1 as alias)
		end repeat
	end try
	close access fRef
	
	return theAliases
end getAliasesFrom

set recentItems to (path to preferences as Unicode text) & "com.apple.recentitems.plist"
tell application "System Events"
	set riInfo to contents of property list file recentItems
	set appInfo to |CustomListItems| of (get value of property list item "Applications" of riInfo)
	set docInfo to |CustomListItems| of (get value of property list item "Documents" of riInfo)
	set servInfo to |CustomListItems| of (get value of property list item "Servers" of riInfo)
end tell

tell application "Finder"
	set sortedApps to (sort my getAliasesFrom(appInfo) by modification date)
	set sorteddocs to (sort my getAliasesFrom(docInfo) by modification date)
	-- set sortedServers to (sort my getAliasesFrom(servInfo) by modification date) -- Servers must be mounted.
end tell

Thanks for that, Nigel.

I didn’t know how to approach getting the recent items. One question, though, if you don’t mind -

The pipes are new to me. What is going on there?

Thanks again,

j

Nigel,

Indeed, seems that some write/read trick has to accomplish turning machine code back into aliases…
Inventive, but what’s happening…?

When I run the script, it gets an error and this is highlighted:

sort my getAliasesFrom(docInfo) by modification date

the event log shows

The script doesn’t error if I change the line to

set sorteddocs to (my getAliasesFrom(docInfo))

but I don’t know how to use the results.

It also errors when sorting by name or creation date. The apps sort properly. I haven’t tried servers because that doesn’t apply to me, but I would like to be able to fix the recent documents error. Any ideas?

thanks,

j

Hi, capitalj.

They’re something you can use with record property labels and with variable names to allow a bit more flexibility. For one thing, they can distinguish a user label or user variable from a keyword of the same name. Here, the writers of System Events (presumably) have chosen the label |Alias| to convey what the value represents without having to resort to the keyword alias. (It’s considered very bad form to use a keyword as a property label.)

Bars also allow variable names and property labels to contain spaces, but their use is quite rare.

set |Binky the Bunny Rabbit| to 7

Hi, Eelco.

The value of |Alias| in each record examined is a string of binary data that represents an alias. I don’t know of any AppleScript way to convert that to a functional alias, but the read command in the StandardAdditions is quite capable of reading it as alias, so I’ve saved the data to a temporary file and read it back that way. I think the data’s stored in that form in the plist itself (in Tiger), so in theory, with a bit more research, it might be possible to find and read it directly from the plist file rather than having to fetch it with System Events and save it to another file first. Maybe I’ll attempt that at more leisure… :rolleyes:

capitalj.

Your error’s puzzling. The fact that the applications sort OK but not the docs suggests there’s something odd about the docs. Presumably, your experiment .

set sorteddocs to (my getAliasesFrom(docInfo))

. returns a list of aliases?

Hi Nigel, thanks for the explanation.

Oddly, I ran it so I could post the results of the error, but it worked. Huh. Anyway -


	set sorteddocs to (my getAliasesFrom(docInfo))

gives me

That’s because I was looking at the event log, not the results. :rolleyes: And now, for the first time

set sorteddocs to (sort my getAliasesFrom(docInfo) by modification date)

returned this

but what happened previously - and repeatedly - was (reconstructed from the event log and shortened because this post is long enough already)

Hi, capitalj.

I’ve just managed to duplicate the error by deleting one of the recent documents and emptying the trash before running the script. Is that what happened with you, perhaps? It would keep erroring until all the non-extant recent documents had dropped out of the list. It shows that the aliases need to be checked for validity before attempting the sort.

This version checks the aliases as they’re obtained:

on getAliasesFrom(CLIrecords)
	set theAliases to {}
	set tempFile to (path to temporary items as Unicode text) & "aliasData.dat"
	set fRef to (open for access file tempFile with write permission)
	try
		repeat with i from 1 to (count CLIrecords)
			set eof fRef to 0
			write |Alias| of item i of CLIrecords to fRef
			set thisAlias to (read fRef from 1 as alias)
			try -- Check the alias before including it in the list.
				info for thisAlias
				set end of theAliases to thisAlias
			on error number -43
				display dialog "The file "" & thisAlias & "" no longer exists." buttons {"OK"} default button 1
			end try
		end repeat
	end try
	close access fRef
	
	return theAliases
end getAliasesFrom

set recentItems to (path to preferences as Unicode text) & "com.apple.recentitems.plist"
tell application "System Events"
	set riInfo to contents of property list file recentItems
	set appInfo to |CustomListItems| of (get value of property list item "Applications" of riInfo)
	set docInfo to |CustomListItems| of (get value of property list item "Documents" of riInfo)
	set servInfo to |CustomListItems| of (get value of property list item "Servers" of riInfo)
end tell

tell application "Finder"
	set sortedApps to (sort my getAliasesFrom(appInfo) by modification date)
	set sorteddocs to (sort my getAliasesFrom(docInfo) by modification date)
	-- set sortedServers to (sort my getAliasesFrom(servInfo) by modification date) -- Servers must be mounted.
end tell

I tested earlier to see what would happen if I moved a recent document before running the script and it all went OK. The alias was automatically updated. It just didn’t occur to me to try zapping one of the documents!

So the recent items update at some interval and aren’t necessarily accurate at the time the script runs. That’s a relief. I’d rather have a problem with the script than with my computer.

I think that’s what happened. I did trash a few files as I puttered around - not sure exactly about the order of events

I must have compounded the error because when it errored, I noticed one of the listed files was in the trash, so I emptied it and recreated the error I thought I was preventing. After I left everything alone for a while it worked properly.

Thanks for the help. Among other things, this will help me to improve my understanding of records and read/write.

I believe they’re still quite popular in some quarters, Mr. G… :wink:

Just to reinforce Nigel’s comments, this is mainly about representing the keys and values from a plist file as records, in a way that AppleScript can handle safely.

Developers may use a wide range of key labels - some of which, as Nigel has pointed out, might be interpreted as keywords of AppleScript, a scripting addition or an application. Other keys may not translate into AS record labels at all. Since wrapping identifiers in vertical bars allows the use of any character (thus avoiding the risk of such conflicts), they seem a sensible way to handle nested plist key/value pairs in an AppleScript context.

The following examples, taken from the com.apple.recentitems.plist file, demonstrate how certain keys could, if not enclosed in vertical bars, be treated. Note what happens to the formatting of some of them when you compile each script. (The zeros are merely dummy values to allow the use of records.)

Conditionally unaffected (vanilla AppleScript):

{maxdoc:0, Servers:0, Controller:0, MaxAmount:0, CustomListItems:0, Icon:0, maxapp:0, docs:0, apps:0}

These might initially look safe enough. However, the installation of certain Scripting Additions - or using the terms within an application tell block - might still cause some labels to be interpreted as keywords. The term icon, for example, is used by Finder - giving us a good example of why using existing or potential keywords as custom variables or record labels is not such a great idea:

set |test record| to {Icon:"Does this work?"}

display dialog |test record|'s Icon
--> [dialog] "Does this work?"

tell application "Finder" to display dialog |test record|'s Icon
--> [error number -1728 (errAENoSuchObject)] "Can't get icon of {Icon:\"Does this work?\"}."

AppleScript keywords:

{Applications:0, Alias:0, Name:0, Documents:0}

When these are compiled, they clearly become formatted as keywords, usually losing any capitalisation and, in some cases, their pluralisation: {application:0, alias:0, name:0, document:0}

Compile errors:

{max-doc:0, doc-aliases:0, max-app:0, app-aliases:0}

The hyphens here will drive the compiler nuts - since it would really like to parse the words on either side of them as 2 separate terms. This results in compile-time syntax errors (usually of number -2741 [OSASyntaxTypeError]), with a variety of possible messages, such as:

So the use of vertical bars side-steps all of these potential issues - and allows a plist’s key/value pairs to be represented by AppleScript records.

Of course, drilling deeper into a plist’s structure will eventually expose the strings of the keys at each level. To demonstrate, here’s a slight variation (though no improvement) on Nigel’s script:

to get_aliases from l
	set f to (path to temporary items as Unicode text) & "aliasData.dat"
	set r to (open for access file f with write permission)
	repeat with i in l
		set eof r to 0
		write i to r
		try
			set i's contents to (read r from 1 as alias) as Unicode text as alias
		on error
			set i's contents to false
		end try
	end repeat
	close access r
end get_aliases

set l to {"Applications", "Documents", "Servers"}
set p to (path to preferences as Unicode text) & "com.apple.recentitems.plist"
tell application "System Events" to tell property list file p to repeat with i in l
	set d to value of property list item "Alias" of property list items of ¬
		property list item "CustomListItems" of property list item i
	my (get_aliases from d)
	tell application "Finder" to set i's contents to sort d's aliases by modification date
end repeat
set {sortedApps, sorteddocs, sortedServers} to l

Dang! Could’ve sworn I’d registered Binky as exclusive intellectual property… :stuck_out_tongue:

set |Binky the Bunny Rabbitâ„¢| to {nose:"twitchy", tail:"fluffy", voice:"squeaky"}
|Binky the Bunny Rabbitâ„¢|'s tail --> "fluffy"

Hi kai.

Thanks for the additional information. It clears up a few details for me as I experiment with this script.

One last question - what is eof? As in

 set eof r to 0

thanks again,

j

My apologies, Mr. E… I should have said: “. and their use is quite common in rare quarters.” :wink:

“eof” is a common computer acronym for “end of file”. It’s not the last byte in the file, but the insertion point immediately after that.

The set eof command is one of the File Read/Write commands in the StandardAdditions. (set eof is the name of the command. It’s not the set command followed by eof.) It’s commonly used, as here, before writing fresh data to a file, in order to set the file’s length to zero. This effectually empties the file of its existing content. If we didn’t do that, and the new content was shorter than the previous lot, the end of the old content would remain in the file, protruding beyond the end of the new.

Touché, Mr G. :lol:

(You just beat me to the draw with the eof explanation, too. I prefer your version. Double touché. ;))

Thanks, Nigel.

That seems so obvious. But then, everything I know about computers has been learned in the past 11 months. So much to learn.

j

Hi again.

Although I haven’t tried to apply what I’m learning here to a new situation, I have been experimenting. A few times my attempts errored, and when I ran the script again, the temp file was still open - another error. I ended up logging out then in to close the file. What better way is there to cope with that situation?

I am mostly interested in seeing recent documents, but as an exercise in scripting, I came up with this (maybe I should have left out servers - that’s beyond my experience so far.)


set recentItemsLists to (choose from list {"Applications", "Documents", "Servers"} with prompt "Review which recent items?" with multiple selections allowed)
if recentItemsLists is false then error number -128 --user cancelled
if recentItemsLists contains "Servers" then display dialog "Servers must be mounted." with icon 2 

tell application "System Events" to tell property list file ((path to preferences as Unicode text) & "com.apple.recentitems.plist")
	repeat with theItem in recentItemsLists
		set recentUnsorted to value of property list item "Alias" of property list items of property list item "CustomListItems" of property list item theItem
		my (getAliases from recentUnsorted)
		tell application "Finder" to set theItem's contents to sort recentUnsorted's aliases by name
	end repeat
end tell

to getAliases from recentItems
	set tempFile to (open for access file ((path to temporary items as Unicode text) & "aliasData.dat") with write permission)
	repeat with theItem in recentItems
		set eof tempFile to 0
		write theItem to tempFile
		try
			set theItem's contents to (read tempFile from 1 as alias) as Unicode text as alias
		on error
			set theItem's contents to false
		end try
	end repeat
	close access tempFile
end getAliases

set {itemList, aliasList} to {{}, {}}
repeat with aList in recentItemsLists
	repeat with anItem in aList
		set itemName to name of anItem as string
		set itemAlias to anItem as string
		set {itemList, aliasList} to {itemList & itemName, aliasList & itemAlias}
	end repeat
end repeat

set itemChoice to (choose from list itemList with prompt "Which file would you like to open?" & return & return with multiple selections allowed)
if itemChoice is false then error number -128 --user cancelled

tell application "Finder"
	repeat with aChoice in itemChoice
		repeat with anAlias in aliasList
			if anAlias ends with aChoice then
				open file anAlias
			end if
		end repeat
	end repeat
end tell

Hi, capitalj.

It’s best to put everything that happens while a file’s open for access in a try block. That way, if there’s an error, the script will keep running long enough to close the access. In it’s simplest form:

to getAliases from recentItems
	set tempFile to (open for access file ((path to temporary items as Unicode text) & "aliasData.dat") with write permission)
	try
		-- Do stuff with the file.
		-- If there's an error, the script will recover and
		-- continue from the 'close access' line below.
	end try
	close access tempFile
end getAliases

The geography will obviously depend on how you want the script to react when there’s an error. This version simply closes the access and allows the error to go ahead:

to getAliases from recentItems
	set tempFile to (open for access file ((path to temporary items as Unicode text) & "aliasData.dat") with write permission)
	try
		-- Do stuff with the file.
		-- If there's an error, the script will recover and
		-- execute the 'on error' section below.
	on error errMsg number errNum
		-- In this case, close the access, then continue with the error.
		close access tempFile
		error errMsg number errNum
	end try
	close access tempFile
end getAliases

If you’re running the script in Script Editor, you should be able to close any stray accesses simply by quitting Script Editor. open for access doesn’t so much “open the file” as grant access to it to the application running the script. When the application quits, the access(es) are closed. (But don’t write the script on that assumption! The script itself should close the access.)

Late coming to this thread (working on next week’s cover article), but great stuff here. I guess I’m also a member of the “rare” users of pipes for variable delineation - but I haven’t ever posted any.

Just for the record, Mr. G’s emended version (the one that acknowledges missing docs) works a treat, but Mr. E’s variation lurks behind a rainbow wheel. I had to force quit Script Debugger after a substantial wait to see if it would pull through, alas. Lest he be concerned, however, I’ll admit that I use a unSanity haxie called “Menu Master” that probably breaks it because the “recent items” under the Apple Menu are stored a bit differently.

Thanks, Nigel.

I don’t remember if I tried simply quitting Script Editor. Due to my occasional habit of all night scripting binges, I might have gone muddleheaded and reached for the Bigger Hammer prematurely.

The script I posted doesn’t error so far (I can’t test the server part.) Should one additional try block suffice?

to getAliases from recentItems
	set tempFile to (open for access file ((path to temporary items as Unicode text) & "aliasData.dat") with write permission)
	try
		repeat with theItem in recentItems
			set eof tempFile to 0
			write theItem to tempFile
			try
				set theItem's contents to (read tempFile from 1 as alias) as Unicode text as alias
			on error
				set theItem's contents to false
			end try
		end repeat
	end try
	close access tempFile
end getAliases

Both (posts #9 and #11) worked for me as written. Originally I was trying to adapt Nigel’s (post #9) script when I got the open file error - I was trying (and failing because, perhaps, I was muddleheaded from last night’s scripting binge) to do what kai’s version solves with

on error
				set theItem's contents to false

j