System Events file list

Hi,

Trying to use System Events seems to be harder than using Finder, but its a little faster I think. Properties in System Events are not consistent. So, I needed the try/error block in the following script But anyway, I’m thinking that many (if not all) of the errors occur because the program can’t find the user library, if I choose the user’s home folder. If so, do I need to make the library visible first?


set err_log to ""
set the_stack to {}
set stack_ref to a reference to the_stack
set file_list to {}
set file_list_ref to a reference to file_list
-- push 1
set end of stack_ref to (choose folder)

repeat until the_stack is {}
	-- pop first folder
	set this_folder to item 1 of stack_ref
	set the_stack to rest of stack_ref
	-- get items of this_folder
	tell application "System Events"
		set item_list to (every item of this_folder)
		repeat with this_item in item_list
			try
				set item_class to (class of this_item)
				if item_class is folder then
					set end of the_stack to ((path of this_item) as alias)
				else
					set end of file_list_ref to ((path of this_item) as alias)
				end if
			on error err_msg
				set err_log to (err_log & return & err_msg)
			end try
		end repeat
	end tell
end repeat
{err_log, file_list}

Note: I used err_log to list the errors to show in the result window, because getting the log in Script Editor takes too long. I didn’t go through every line of the log.

How do you fix this to cover every type of System Events’s objects?

Thanks,

Can you explain what you mean here? You’re only asking for one property, class. What error(s) are being generated?

Hi Shane,

What I mean is that properties of some objects, aren’t included in the properties of other objects. e.g. the 'kind" property is not a property of every object in the file system. There is no universal property, that I can see.

editted: repaced i.e. with e.g.

Thanks,

The “universal” property is class, inherited from the item class. You’re getting the items in a folder, and the class of elements a folder can contain is listed in the dictionary. So by finding an item’s class, you know what properties are available. Trying to fudge things by skipping over problems with a try block often just masks other problems, and makes bugs harder to track down.

In your posted script, for example, you are only getting one property: class. And that is something every item has as a property.

So let me ask again: in your sample, what errors are being generated?

The reason I used ‘class’ is that ‘kind’ didn’t work in distinguishing a folder. What am I doing wrong here? I want package folders to be files.

ok, What kind of error? I kept modifying the script, but I think there’s one here that gave the errors.

BTW, I only used the error block to see what was happening and try to solve the problem.

File packages are file packages, but as subclasses of file, they inherit file properties. What do you want to use kind for?

You’re not explaining what you’re after from the script. And you still won’t let on about those errors :frowning:

Hi Shane,

Sorry If I was so slow. Here’s the script with the error handler out of it:


set err_log to ""
set the_stack to {}
set stack_ref to a reference to the_stack
set file_list to {}
set file_list_ref to a reference to file_list
-- push 1
set end of stack_ref to (choose folder)

repeat until the_stack is {}
	-- pop first folder
	set this_folder to item 1 of stack_ref
	set the_stack to rest of stack_ref
	-- get items of this_folder
	tell application "System Events"
		set item_list to (every item of this_folder)
		repeat with this_item in item_list
			--try
			set item_class to (class of this_item)
			if item_class is folder then
				set end of the_stack to ((path of this_item) as alias)
			else
				set end of file_list_ref to ((path of this_item) as alias)
			end if
			--on error err_msg
			--set err_log to (err_log & return & err_msg)
			--end try
		end repeat
	end tell
end repeat
{err_log, file_list}

Here’s the error on choosing my home folder:

error “System Events got an error: Can’t get file "Macintosh HD:Users:kelhome:Library:Saved Application State:com.apple.TextEdit.savedState".” number -1728 from file “Macintosh HD:Users:kelhome:Library:Saved Application State:com.apple.TextEdit.savedState”

Maybe I gotta rethink this. Your thoughts are helpful.

Thanks,

OK, if you look at com.apple.TextEdit.savedState, you’ll see it’s shown as an alias, and a tiny one at that. In fact it looks like it’s a symlink, which Finder calls aliases because they are similar.

My guess is that System Events can’t handle symlinks correctly, which is disappointing but not all that surprising.

Hi Shane,

So you can’t get the class of a symlink?

Editted: and maybe you can’t get the path to an alias in this situation. I gotta look at that.

Thanks,

p.s. Forgive me this is a completely new system.

So I think this is what is happening:

When you ask SE for the items in a folder, if an item is a symlink, it gets resolved. So in the log you see what you’d expect to be:

file “Macintosh HD:Users:shane:Library:Saved Application State:com.apple.mail.savedState”

becomes:

file “Macintosh HD:Users:shane:Library:Containers:com.apple.mail:Data:Library:Saved Application State:com.apple.mail.savedState”

That’s fine and dandy, the path has been resolved correctly, except that when you ask for the class of it, you get an error saying SE can’t get such a file. And that’s presumably because there is no file at that path – it’s a folder.

tell application id "com.apple.systemevents" -- System Events.app
	set x to folder "Macintosh HD:Users:shane:Library:Saved Application State:com.apple.TextEdit.savedState" -- OK
	set x to file "Macintosh HD:Users:shane:Library:Saved Application State:com.apple.TextEdit.savedState" -- errors
end tell

Nasty bug.

Unfortunately I can’t think of any workaround other than error trapping.

Hello.

How about using Spotlight instead?

The script below shows you which properties of the contenttype three of Spotlight you can search for after having selected said file/folder in Finder.

You use it like this, in a do shell script, but with paths and content type changed.

set bndlist to paragraphs of (do shell script " cd ~/Applications ; mdfind -onlyin . kMDItemContentType == com.apple.application-bundle ")

-- New faster way to create record by Nigel Garvey

-- http://macscripter.net/viewtopic.php?id=39265
--  SPOTLIGHT INFO FOR SELECTED ITEMS
-- PARTS ©1998 Sal Soghoian, Apple Computer
-- Parts © 2012 McUsr I'd rather have you referring to this post at Macscripter.net http://macscripter.net/edit.php?id=154116
-- than posting it elsewhere!
(*
	Compiled  By McUsr 09/08/12:
	Based upen INFO FOR SELECTED Items, uses mouramartins convert mdlsinfo (spotlight metadata to list).
	New code converts mdls info to a list directly
*)
property metaList : missing value
property genericIcon : a reference to file ((path to library folder from system domain as Unicode text) & "CoreServices:CoreTypes.bundle:Contents:Resources:GenericEditionFileIcon.icns")
property ScriptTitle : "Spotlight Meta Data for Items info"

set infoIcon to a reference to file ((path to library folder from system domain as text) & "CoreServices:CoreTypes.bundle:Contents:Resources:AlertNoteIcon.icns")

set idOfFrontApp to getfrontAppId()
set ReportText to ""
set failed to false
try
	
	tell application "Finder"
		activate
		set selected_items_list to (get selection) as alias list
		
		set selCount to count selected_items_list
		
		if selCount ≠ 0 then set last_item to the last item of the selected_items_list as text
		
		try
			set startPath to target of its Finder window 1 as alias
		on error
			set starpath to path to desktop folder
		end try
		
	end tell
	
	
	if selCount = 0 then
		tell application "SystemUIServer"
			activate
			try
				set the selected_items_list to (choose file with prompt "Choose the files or folders you want to see info for for" default location startPath with multiple selections allowed)
			on error
				set failed to true
			end try
		end tell
		
		set selCount to count selected_items_list
		if ((count of the selected_items_list) = 0) or failed then
			alertDialog({aTextMessage:"No files or applications are selected.", aTextTitle:my ScriptTitle, timeInSecs:300, btnAsList:{"Ok"}, iconAsFileRef:infoIcon, bundleIdOfFrontApp:idOfFrontApp})
			abortNicely({bundleIdFrontApp:idOfFrontApp})
		end if
		set last_item to the last item of the selected_items_list as text
		
	end if
	
	set failed to false
	tell application "SystemUIServer"
		activate
		try
			set outputType to button returned of (display dialog "Please choose form of output" with title ScriptTitle buttons {"Cancel", "Report", "Dialogs"} cancel button 1 default button 3 with icon genericIcon)
		on error
			set failed to true
		end try
	end tell
	if failed then abortNicely({bundleIdFrontApp:idOfFrontApp})
	
	repeat with this_item in the selected_items_list
		if outputType is "Dialogs" then
			set ReportText to ""
		end if
		tell application "Finder" to set item_name to name of this_item
		
		set filepath to quoted form of POSIX path of (this_item as alias)
		set metaRec to metaDataRecord for filepath
		set metaList to Rec2UserKeyValues(metaRec)
		if outputType is "Report" then
			set ReportText to ReportText & return & "==================================" & return & "Name: " & item_name & return
		else
			set ReportText to ReportText & return & "Name: " & item_name & return
		end if
		
		repeat with i from 1 to (count metaList)
			
			set ReportText to ReportText & item 1 of item i of my metaList & ": "
			if class of item 2 of item i of my metaList = list then
				set oltids to AppleScript's text item delimiters
				set AppleScript's text item delimiters to "\" , \""
				set tmpTxt to item 2 of item i of my metaList as text
				set AppleScript's text item delimiters to oltids
				set ReportText to ReportText & "{ \"" & tmpTxt & "\" }" & return
			else
				set ReportText to ReportText & item 2 of item i of my metaList & return
			end if
		end repeat
		
		
		if outputType is "Dialogs" then
			set the clipboard to ReportText
			if contents of this_item is the last_item or selCount is 1 then
				set the button_list to {"Done"}
			else
				set the button_list to {"Cancel", "Next"}
			end if
			-- display the information
			with timeout of 900 seconds
				tell application "SystemUIServer"
					activate
					
					display dialog ReportText with title ScriptTitle buttons the button_list default button (the last item of the button_list)
				end tell
			end timeout
		end if
	end repeat
	if outputType is "Report" then
		
		tell application "TextEdit"
			activate
			make new document at the front
			set text of front document to "F i l e   i n f o r m a t i o n " & return & ReportText
		end tell
	end if
on error e number N
	if not failed then
		alertDialog({aTextMessage:e & " " & N, aTextTitle:my ScriptTitle, timeInSecs:300, btnAsList:{"Ok"}, iconAsFileRef:infoIcon, bundleIdOfFrontApp:idOfFrontApp})
		abortNicely({bundleIdFrontApp:idOfFrontApp})
	end if
end try


on metaDataRecord for fp
	-- http://macscripter.net/edit.php?id=154143 NG
	-- Ensure that we have a quoted POSIX path to the file.
	if (fp's class is text) then
		if (fp does not start with "'/") then
			if (fp does not start with "/") then set fp to POSIX path of fp
			set fp to quoted form of fp
		end if
	else
		set fp to quoted form of POSIX path of (fp as alias)
	end if
	
	-- Get the metadata text and edit it almost to compilability. Mark date entries with unlikely tags.  ;)
	set rs to do shell script "mdls " & fp & " | sed -Ee 's| *=|:|' -e 's|^ +([^\"][a-zA-Z]*[^\"])$|\"\\1\"|' -e 's|\"?([0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2} [+-][0-9]{4})\"?|<McUsr>\\1</McUsr>|' -e 's|\\($|{ ¬|' -e 's|^\\)|}|' -e 's|,$|, ¬|' -e 's|([^¬])$|\\1, ¬|'"
	
	if ((count rs) > 0) then
		-- Append braces for the record representation.
		set rs to "{" & text 1 thru -4 of rs & "}"
		
		set astid to AppleScript's text item delimiters
		-- Zap any stray commas at the ends of lists.
		set AppleScript's text item delimiters to ", ¬" & return & "}"
		set rs to rs's text items
		set AppleScript's text item delimiters to " ¬" & return & "}"
		set rs to rs as text
		-- Replace the ISO dates with AppleScript dates transposed to the computer's time zone and coerced to text as per the local preferences. Requires AS 2.1 (Snow Leopard) or later for the multiple TIDs.
		set AppleScript's text item delimiters to {"<McUsr>", "</McUsr>"}
		set rs to rs's text items
		repeat with i from 2 to (count rs) by 2
			set dateString to item i of rs
			set item i of rs to "date \"" & getASLocalDate(dateString) & "\""
		end repeat
		set AppleScript's text item delimiters to ""
		set rs to rs as text
		set AppleScript's text item delimiters to astid
		
		-- Return the "compiled" record.
		return (run script rs)
	else
		return {}
	end if
end metaDataRecord

-- Return a local AppleScript date from a given ISO date/time with time-zone displacement.
on getASLocalDate(ISODate)
	-- Get the ISO date/time as as AppleScript date object. (The date object will be reused for different purposes below to save multiple calls to 'current date'.)
	tell (current date) to set {day, {year, its month, day, its hours, its minutes, its seconds}, ASDate} to {1, words 1 thru 6 of ISODate, it}
	
	-- Subtract the time-zone displacement to transpose to GMT.
	set astid to AppleScript's text item delimiters
	set AppleScript's text item delimiters to space
	tell (text item -1 of ISODate) as integer to set ASGMTDate to ASDate - (it div 100 * hours + it mod 100 * minutes)
	set AppleScript's text item delimiters to astid
	
	-- Subtract the Unix era start from the GMT date to get the number of seconds since then.
	tell ASDate to set {day, month, year, time} to {1, 1, 1970, 0}
	set eraTime to ASGMTDate - ASDate
	-- Coerce this figure to text in straight decimal notation.
	if (eraTime > 99999999) then
		set eraTime to (eraTime div 100000000 as text) & text 2 thru 9 of (100000000 + eraTime mod 100000000 as integer as text)
	else if (eraTime < -99999999) then
		set eraTime to (eraTime div 100000000 as text) & text 3 thru 10 of (-100000000 + eraTime mod 100000000 as integer as text)
	else
		set eraTime to eraTime as text
	end if
	
	-- Run the figure through "date" to get the corresponding machine-local date/time and convert to AS.
	do shell script ("date -r " & eraTime & " '+%Y %m %d %H %M %S'")
	tell ASDate to set {year, its month, day, its hours, its minutes, its seconds} to words of result
	
	return ASDate
end getASLocalDate

on getfrontAppId() -- Returns bundleid of active app
	local frontappId
	set frontappId to ""
	tell application "System Events"
		set frontappId to bundle identifier of first application process whose frontmost is true
	end tell
	return frontappId
end getfrontAppId

on abortNicely(R) -- Returns Nothing
	
	tell application "System Events" to tell application process id (bundleIdFrontApp of R)
		key down control
		key code 118
		key up control
	end tell
	error number -128
end abortNicely

on Rec2UserKeyValues(recAny)
	-- http://macscripter.net/viewtopic.php?id=36842 yiam-jin-qui
	-- USE THE CLIPBOARD TO MAKE THE RECORD KEYS LEGIBLE
	set the clipboard to recAny
	set recLegible to (the clipboard as record)
	set lngPairs to count of (recAny as list)
	if lngPairs < 1 then return {}
	
	-- COLLECT ANY USER-DEFINED KEY-VALUE PAIRS
	set lstKeyValue to {}
	try
		set lstUser to list of recLegible
	on error
		display dialog (do shell script "osascript -e 'the clipboard as record'") buttons "OK" default button 1 with title "Contents of record"
		return {}
	end try
	
	repeat with i from 1 to (length of lstUser) - 1 by 2
		set end of lstKeyValue to {item i of lstUser, item (i + 1) of lstUser}
	end repeat
	
	-- IF ANY PAIRS ARE MISSING, TRY SOME SYSTEM-DEFINED KEYNAMES
	if (count of lstKeyValue) < lngPairs then
		try
			set beginning of lstKeyValue to {"Date", date of recAny}
		end try
		try
			set beginning of lstKeyValue to {"Name", name of recAny}
		end try
	end if
	lstKeyValue
end Rec2UserKeyValues

on alertDialog(R) -- Returns Nothing
	-- R : {aTextMessage:theMessage,aTextTitle:thetitle,timeInSecs:lenToTimeout,btnAsList:theButton,iconAsFileRef:theIcon,bundleIdOfFrontApp:frontappId}
	local res, failed, e, N
	set failed to false
	tell application "SystemUIServer"
		activate
		try
			if (iconAsFileRef of R) is null then
				set res to button returned of (display dialog (aTextMessage of R) with title (aTextTitle of R) giving up after (timeInSecs of R) buttons (btnAsList of R) default button 1)
			else
				set res to button returned of (display dialog (aTextMessage of R) with title (aTextTitle of R) giving up after (timeInSecs of R) buttons (btnAsList of R) default button 1 with icon (iconAsFileRef of R))
			end if
			if res = "" then set failed to true
		on error e number N
			set failed to true
			
		end try
	end tell
	if failed is true then
		abortNicely({bundleIdFrontApp:(bundleIdOfFrontApp of R)}) -- Returns Nothing
	end if
	return
end alertDialog

For one thing, I don’t think the folders SE is failing on are indexed by Spotlight.

Yes, true, I think that folder isn’t a part of a Spotlight Search, came to think about it.

There must be a way with using stat or xattr or something, to get the attribute of a folder, I’ll have a look.

By the way, I think that not following sym-link isn’t a bug in System Events, but there is lacking a parameter: “with following symlinks”, this should also check for cycles if possible. (Recursive routine, that traverses a folder, and you have a symlink to the parent or parent’s folder.)

Here is a function that returns true if a file, hopefully a directoryis a bundle.

on isBundle for pxFileToCheck
	return (do shell script "/bin/ls -l -@ " & pxFileToCheck & " |sed -e 1d -e 2q |grep -q Contents && echo \"true\" || echo \"false\" ")
end isBundle

The idea is that you feed it the posix path of disk item from System Events.

You can read about extended attributs here

But it is following the symlink; you can see the path has changed. The problem is that it’s not allowing for the fact that a symlink file might point to a folder rather than another file.

Hello.

I can’t really test the problem, as I am still on Snow Leopard, so that nuance slipped b.

However, by following symbolic links, my understanding is that, that is something that happens to folders, you don’t follow the symbolic linke for a file, you just resolve it. But say you are traversing a folder structure, then you follow the sym-link, if you cd to the directory the symlink points to.

That is the way I interpret it, and is also how I understand it in the documentation for the find command.

Hopefully the handler above works, even on symbolic links.

I don’t think you’re quite getting the point of what’s happening. There’s a file that’s a symlink, and it points to a folder. System Events works out the path to the new folder OK, but returns it in a System Events file object, instead of a System Events folder object. It is then unable to use that file object, because there really is no such file (file meaning file in terms of SE’s object model) – the thing at that path is a System Events folder.

I got it.

The horse is beaten to death, but what you describe is very much not following symbolic links. It is short circuited, either intentional or unintentional.

The "hack for finding xattrs above doesn’t work very well, it only works for Application bundles. I know there is a hfs attribute for bundles, but it is too early to look for that now, going back to bed.

Edit

No rest for the wicked! I have googled around a little, and drawn the conclusion that the way I figure that something is a bundle, should work for every bundle, except for framework bundles.

There are a slew of test you can do on a file, you’ll see them if you write “help test” in a terminal window, as the isBundle handler above, is hardly effective without preliminary tests.

And I am able to use mdfind everywhere, but maybe I have tinkered with the LIbrary and System folders, or with Spotlight in order to index them.

Hi everybody,

Thanks for the input. I fell asleep trying to resolve this. Maybe the answer will come when I get up again.

Thanks a lot,

Hello.

I’ll just add this handler that returns the path that a symlink points to. Maybe it is easier to resolve a symlink with Finder, and look if the class is alias, or if there is an original item property?

to getOrig of aSymlink for apath
	-- returns symlink for a path, probably relative to the
	-- path that was cd'd into
	set symlPath to (do shell script "cd " & apath & "; /bin/ls -l@  " & aSymlink & "|sed -n 's/\\(^l.*-> \\)\\(.*\\)/\\2/p'")
	return symlPath
end getOrig