Writing "data" type values to a pList / Pages Recent Docs

Hello.

I changed this

set plists to paragraphs of (do shell script "cd " & quoted form of pxpth & "; ls * |sed -n 's/\\(.*\\)\\(\\.LSSharedFileList.plist\\)/\\1/p'")

into this:

set plists to paragraphs of (do shell script "cd " & quoted form of pxpth & "; ls * |sed -e '/lockfile/ d' -e 's/\\(.*\\)\\(\\.LSSharedFileList.plist\\)/\\1/p'")

as those lockfiles shouln’t be in the list, whatever the version of OS X

I am done with this now, unless of course other issues pops up.

Big thanks to Yvan Koenig for taking the time! :slight_smile:

Hello.

An “l” was erratically inserted in front of the “ls” command in the script in post #18. It is fixed

Hello McUsrII

The late version is wrong too.
There is no reason why it would behave differently under 10.7.



property sTitle : "Recent Items Forensics"
property recentItemIcon : a reference to file ((path to library folder from system domain as text) & "CoreServices:CoreTypes.bundle:Contents:Resources:RecentItemsIcon.icns")

set plpth to (path to preferences folder from user domain)
set pxpth to POSIX path of plpth
set plists to paragraphs of (do shell script "cd " & quoted form of pxpth & "; l ls * |sed -e '/lockfile/ d' -e 's/\\(.*\\)\\(\\.LSSharedFileList.plist\\)/\\1/p'")

(*
"" # yes, empty string
*)
set shtnms to {}
set {tids, AppleScript's text item delimiters} to {AppleScript's text item delimiters, "."}
repeat with i from 1 to count plists
	set end of shtnms to (text 2 thru -1 of ("0" & i)) & space & space & (text item -1 of item i of plists)
end repeat
shtnms
(*
{} # and of course empy list.
*)

Here is a subset of the contents of my Preferences folder.

com.apple.helpviewer.plist
com.apple.HIToolbox.plist
com.apple.iApps.plist
com.apple.iBooksAuthor.LSSharedFileList.plist
com.apple.iBooksAuthor.plist
com.apple.iBooksAuthor.plist.szdgta0
com.apple.iCal.helper.plist
com.apple.iCal.plist

com.apple.iWork.Numbers.LSSharedFileList.plist
com.apple.iWork.Numbers.plist
com.apple.iWork.Numbers.plist.5JX2rRo
com.apple.iWork.Numbers.plist.iEcVCfm

com.apple.iWork.Pages.LSSharedFileList.plist
com.apple.iWork.Pages.plist

com.apple.preference.desktopscreeneffect.plist
com.apple.PreferenceSync.plist
com.apple.Preview.LSSharedFileList.plist
com.apple.Preview.plist
com.apple.print.add.plist

com.apple.ProblemReporter.plist
com.apple.PropertyListEditor.LSSharedFileList.plist
com.apple.PropertyListEditor.plist

com.apple.screencapture.plist
com.apple.screensaver.plist
com.apple.ScriptEditor.id.04007A09-67FC-4C65-8856-DBD2836F7C3A.plist
com.apple.ScriptEditor.id.Sans-titre-2.plist
com.apple.ScriptEditor2.LSSharedFileList.plist
com.apple.ScriptEditor2.plist
com.apple.security.certreq.plist
com.apple.security.pboxd.plist

com.apple.SystemProfiler.plist
com.apple.systemsound.plist
com.apple.systemuiserver.plist
com.apple.Terminal.plist
com.apple.TextEdit.LSSharedFileList.plist
com.apple.TextEdit.SandboxedPersistentURLs.LSSharedFileList.plist

com.apple.ubd.plist
com.apple.universalaccess.plist
com.apple.VoiceOver4

net.sourceforge.skim-app.skim.plist
net.sourceforge.wxhexeditor.plist
org.clindberg.ManOpen.LSSharedFileList.plist
org.clindberg.ManOpen.plist

org.mozilla.camino.plist
org.mozilla.crashreporter.plist
org.mozilla.firefox.plist
org.openoffice.script.LSSharedFileList.plist
org.openoffice.script.plist
org.videolan.vlc
org.videolan.vlc.LSSharedFileList.plist
org.videolan.vlc.plist

Don’t worry, the xx.lockfile ones don’t appear because I asked the system to hide them.

Yvan KOENIG (VALLAURIS, France) samedi 9 mars 2013 21:27:13

Hello.

I forgot to put back in an “n” in the sed statement when I created the one that removes the lockfiles.

It should work properly now, sorry for the trouble. :expressionless:

OK, I see what’s happening now. The data is being returned, but its AppleScript class is any (that’s what **** means), whereas it should be data (the **** would be rdat). AppleScript does indeed have a data class, and System Events should be using it here. Unfortunately there’s no AS coercion between any and data, which is a shame because all the data is there. And System Events doesn’t know how to pass back items of class any.

That’s really a System Events bug, and someone should probably log it as such, if for no other reason than the fix is probably very simple.

FWIW, this is how you would do it with ASObjC Runner, which does respect the data class:

tell application "ASObjC Runner"
	set recPlistValue to read plist at prefsFile
	set myCustomListitems to CustomListItems of RecentDocuments of recPlistValue
	-- remove items from list
	set CustomListItems of RecentDocuments of recPlistValue to myCustomListitems
	save plist to prefsFile containing recPlistValue
end tell

As you know, I’m an ass with sed.

May you give the edited instruction ?

Yvan KOENIG (VALLAURIS, France) dimanche 10 mars 2013 10:16:50

Hello Yvan.

I updated the script in post #18 with working code, before I wrote that post. The last sed expression is now prepended with -ne instead of -e

Thanks, I missed it.

As you build the list used by Choose file with a single word, the entry dedicated to openOffice is “script” which isn’t really meaningful.
{
“1 Preview”,
“2 PropertyListEditor”,
“3 ScriptEditor2”,
“4 TextEdit”,
“5 SandboxedPersistentURLs”,
“6 iBooksAuthor”,
“7 Numbers”,
“8 Pages”,
“9 ManOpen”,
“10 script”,
“11 vlc”}
Using :


repeat with i from 1 to count plists
	set end of shtnms to (text 2 thru -1 of ("0" & i)) & space & space & (text items -2 thru -1 of item i of plists)
end repeat

seems to be better.
{
“1 apple.Preview”,
“2 apple.PropertyListEditor”,
“3 apple.ScriptEditor2”,
“4 apple.TextEdit”,
“5 TextEdit.SandboxedPersistentURLs”,
“6 apple.iBooksAuthor”,
“7 iWork.Numbers”,
“8 iWork.Pages”,
“9 clindberg.ManOpen”,
“10 openoffice.script”,
“11 videolan.vlc”}

Yvan KOENIG (VALLAURIS, France) dimanche 10 mars 2013 11:30:33

Hello.

I see your problem, right now I am really reading, just having a quick break.

I’ll come back with a scheme, with two items lists, one for words that should be deleted at front.

One that contains words that should be deleted at end, then you and everybody can just enter
the offending words, into their respective lists, and make them go away! :slight_smile:

Hello Yvan!

If you are happy with:


repeat with i from 1 to count plists
	set end of shtnms to (text 2 thru -1 of ("0" & i)) & space & space & (text items -2 thru -1 of item i of plists)
end repeat

Then I’ll change the code to that without further ado. This is kind of a “Technical” script anyways: The program name doesn’t come out singlehandely, but the dialog is more informative, as to what rencent items preferences list you are to edit, especially for openoffice.

So, in this case informative trumps good-looking! :slight_smile:

Consider the script changed as soon as I ended editing this post.

Edit

The script in post #18 is now updated with what I consider the final version, it should work flawlessly forwards, but I think it won’t work for at least Tiger and backwards, due to the Preferences suite of System Events.

Thank you very much for your help Yvan! :slight_smile:

FWIW, here’s an ASObjC Runner version of MacUsrII’s script:

set plpth to POSIX path of (path to preferences folder from user domain)
tell application id "au.com.myriad-com.ASObjC-Runner" -- ASObjC Runner.app
	set theFiles to enumerate folder plpth match suffix "LSSharedFileList" with posix -- ignores extension
	set fileNames to value for key path "lastPathComponent.stringByDeletingPathExtension.stringByDeletingPathExtension" of item theFiles -- get just names, delete .plist, then delete .LSSharedFileList
end tell
set chosenapp to choose from list fileNames with prompt "Choose app to clean up recent items for" without multiple selections allowed
if chosenapp is false then error number -128
set chosenapp to item 1 of chosenapp
if running of application id chosenapp is true then
	display dialog "You must quit  " & chosenapp & " in order to continue." buttons {"OK"} default button 1
	error number -128
end if
tell application id "au.com.myriad-com.ASObjC-Runner" -- ASObjC Runner.app
	set recPlistValue to read plist at plpth & chosenapp & ".LSSharedFileList.plist"
	set myCustomListitems to CustomListItems of RecentDocuments of recPlistValue
	set recentNames to value for key path "Name" of item myCustomListitems -- get Name values for dialog
end tell
set namesToCut to (choose from list recentNames with prompt "Delete document(s) from " & chosenapp & "'s Recent Items list:" with multiple selections allowed)
if (namesToCut is false) then error number -128

tell application id "au.com.myriad-com.ASObjC-Runner" -- ASObjC Runner.app
	set filteredList to refine list myCustomListitems using predicates {{"NOT Name in %@", {namesToCut}}} -- filter list
	set CustomListItems of RecentDocuments of recPlistValue to filteredList -- modify record
	save plist to plpth & chosenapp & ".LSSharedFileList.plist" containing recPlistValue --save
end tell

The idea was Dewshi’s, and like with most of my scripts, that are worth something Nigel Garvery has done the hard work either by providing a handler, or the idea. I merely iced the cake, especially on this one, given the complexity of the problem which I wasn’t really aware of, before you pointed it out in the post above.

Yvan Koenig, really helped to get “things right”. So I regard it as more of a community project, than my script.

Though I take the full responsibility for an faults. :slight_smile:

Here’s a defaults/sed method.

set domain to "com.apple.textedit.LSSharedFileList"

-- Parse the "Open Recent" document names from the plist file.
set RecentNames to paragraphs of (do shell script ("defaults read " & domain & " RecentDocuments | sed -En '/^[[:space:]]*CustomListItems[[:space:]]*=[[:space:]]*\\(/,/^[[:space:]]+\\)/ s/^[[:space:]]+Name = \"(.+)\";$/\\1/p '"))

-- Ask which items to remove from the menu.
activate
set namesToCut to (choose from list RecentNames with prompt "Delete document(s):" with multiple selections allowed)
if (namesToCut is false) then error number -128

-- Join the selected names with vertical bars to use as an OR sequence in a regex.
set astid to AppleScript's text item delimiters
-- Escape any regex characters which may occur in the names!
set charactersToEscape to "|()?*+{}{}/."
repeat with i from 1 to (count namesToCut)
	set thisName to item i of namesToCut
	repeat with thisCharacter in charactersToEscape
		if (thisName contains thisCharacter) then
			set AppleScript's text item delimiters to thisCharacter's contents
			set bits to thisName's text items
			set AppleScript's text item delimiters to "\\" & thisCharacter
			set thisName to bits as text
		end if
	end repeat
	set item i of namesToCut to thisName
end repeat
set AppleScript's text item delimiters to "|"
set namesToCut to namesToCut as text
set AppleScript's text item delimiters to astid

-- Derive an edited CustomListItems "array" to use in a "defaults write" shell script.
set newData to quoted form of text 1 thru -2 of (do shell script ("defaults read " & domain & " RecentDocuments |
sed -En '/^[[:space:]]*CustomListItems[[:space:]]*=[[:space:]]*\\(/,/^[[:space:]]+\\)/ {
	H ;
	/^[[:space:]]+\\)/ {
		g ;
		s/[,[:space:]\\n]+\\{[^\"]+\"(" & namesToCut & ")\";[[:space:]\\n]+\\}//g ;
		s/^[^(]*\\(,?/\\(/ ;
		s/(\\n[[:space:]]+\\));/\\1/ ;
		p ;
	} ;
} ;'") without altering line endings)

-- Write the edited array back to the plist file.
do shell script ("defaults write " & domain & " RecentDocuments -dict-add \"CustomListItems\" " & newData)

Edit: Tightened up for potential clashes when there are regex characters in the names.

Hello Nigel! :slight_smile:

Faster and faster!

I really tried to implement something like that, but I wanted to preserve uniqueness of the entries. Say you are removing entries in the plist editor… What I basically wanted to do was to generate a list with delete statements for sed to execute, based on the line numbers, calculated from the item numbers to be deleted.

It turns out that the layout in paragraphs differs for different recent items files. TextWrangler uses 5 lines per file, whereas TextEdit uses 10.

So, I’ll stick with your former solution, that is more than sufficient!

Edit

Getting it on a little distance, I see how I can preserve uniquness among items with the same name.

Calculating the distance in between two items, is what does the trick, and I’m the one having an SDD disk, not
knowing how this performs on a usual HD, I’ll incorporate the changes.

Hello.

If the former was the final version, then this is the optimized version, thanks to Nigels code from above.

Edit

Maybe all of this was in vain, as I use accented characters, and other who use accented characters will discover the same: This version doesn’t treat accented characters very well. you may get \\U008 sequences. in the dialog.

I want to make a point of the fact that the utf-16 is only deleted from the file, not written back, so it looks good under the recent documents of your program.

I am not going to fix this, or try to fix this before I have thought it thru at least. :slight_smile:

Edit++

If the treatment of utf-16 diacriticals doesn’t please you go to the script in post #18 or Shane’s version in post #31.

This won’t be fixed. The reason is that there are two kinds of utf-16 in the Apple-world besides big endian, loose and tight (wrong terms).
The loose encoding scheme stores diacritical’s in separate codepoints, this is what Apple uses for filenames.
With tight encoding, the diacriticals are stored with the character in one codepoint.

Normally, when you use an operating system call to to parse a value, the Darwin layer transforms the loose utf-16 into utf-8 for us. What has happened here, is that the loose utf-16 is stored inside a text file, and doesn’t enjoy the transformation by Darwin.

I am not doing all this for speed, and then having to do a do shell script for each and every name that is going to be displayed"in the hope of managing to decode/encode it right by some construct/incantation, (and lots of trial and errors). It defies the whole purpose of using sed/defaults in the first place.

There are also other possibilities for optimizing this, but the cost/performance ratio expressed by readability/speed, just doesn’t justify it, it is almost undreadable, as it is.

So, stick with it, or let it be. :slight_smile:


-- http://macscripter.net/viewtopic.php?pid=161038#p161038
-- New version that uses sed and defaults for doing the job quicker!
property sTitle : "Recent Items Forensics"
property recentItemIcon : a reference to file ((path to library folder from system domain as text) & "CoreServices:CoreTypes.bundle:Contents:Resources:RecentItemsIcon.icns")
-- We get in a reasonably list of the files with recent items for applications, that we choose one from
set AppleScript's text item delimiters to ""
set plpth to (path to preferences folder from user domain)
set pxpth to POSIX path of plpth
set plists to paragraphs of (do shell script "cd " & quoted form of pxpth & "; ls * |sed -e '/lockfile/ d' -ne 's/\\(.*\\)\\(\\.LSSharedFileList.plist\\)/\\1/p'")
set shtnms to {}
set {tids, AppleScript's text item delimiters} to {AppleScript's text item delimiters, "."}
repeat with i from 1 to count plists
	set end of shtnms to (text -2 thru -1 of ("0" & i)) & space & space & (text items -2 thru -1 of item i of plists)
end repeat
set AppleScript's text item delimiters to tids
set chosenapp to choose from list shtnms with title sTitle with prompt "Choose app to clean up recent items for" default items item 1 of shtnms without multiple selections allowed

-- We have chosen an app, we must verify that it isn't running, since it may have unsaved documents, we quit the script if it is so
if chosenapp is not false then
	set chosenapp to chosenapp as text
	set pfnm to text 1 thru 2 of chosenapp
	set chosenapp to text 5 thru -1 of chosenapp
	
	if running of application id (item pfnm of plists) is true then
		display dialog "You must quit  " & chosenapp & " in order to continue." with title sTitle buttons {"Ok"} default button 1 with icon recentItemIcon
		error number -128
	end if
	-- we check to see that the recent item plist isn't a zero sized file on disk!
	tell application "System Events"
		set daPlist to ((item pfnm of plists) & ".LSSharedFileList.plist")
		-- Here we read in the plist file and everything is as earlier.
		if (size of disk item daPlist of (plpth as alias)) = 0 then
			tell me to display dialog "Nothing to remove ..." with title sTitle buttons {"Ok"} default button 1 with icon recentItemIcon
			error number -128
		end if
		
	end tell
	
	set domain to (item pfnm of plists) & ".LSSharedFileList"
	repeat
		set recentNames to paragraphs of (do shell script ("defaults read " & domain & " RecentDocuments | sed -En '/^[[:space:]]*CustomListItems[[:space:]]*=[[:space:]]*\\(/,/\\)/ s/^[[:space:]]+Name = (.+);$/\\1/p '"))
		-- checks to see that the file indeed contains something
		set recentNamesCount to length of recentNames
		if recentNamesCount = 0 then
			display dialog "Nothing to remove ..." with title sTitle buttons {"Ok"} default button 1 with icon recentItemIcon
			error number -128
		end if
		
		
		set namesToCut to {}
		-- We have to create the list, and remove any "" as not every name has gotten those in the recent items list
		repeat with i from 1 to recentNamesCount
			set AppleScript's text item delimiters to "\""
			set recentNmTms to text items of item i of recentNames
			try
				if length of recentNmTms > 1 then
					if item 2 of recentNmTms ≠ "" then
						set item i of recentNames to item 2 of recentNmTms
					else
						set item i of recentNames to item 1 of recentNmTms
					end if
				end if
			end try
			set AppleScript's text item delimiters to tids
			set item i of recentNames to (text -2 thru -1 of ("0" & i)) & space & space & item i of recentNames
			
		end repeat
		
		set namesToCut to (choose from list recentNames with title sTitle with prompt "Delete document(s) from " & chosenapp & "´s Recent Items list:" default items item 1 of recentNames with multiple selections allowed)
		if (namesToCut is false) then error number -128
		
		set numberlist to {} -- We do calculate the ranges, which we are to delete in the current recent items property list file, so we incinerate the items we want to remove
		repeat with i from 1 to (count namesToCut)
			set mn to ((text 1 thru 2 of item i of namesToCut) as number)
			set end of numberlist to "" & (1 + ((mn - 1) * 5)) & "," & (mn * 5) & "d"
			-- *TOTALLY DEPENDENT ON THE LAYOUT OF RECENT ITEMS. 2 LINES UP FRONT, 5 LINES FOR EACH OF THEM.
		end repeat
		-- Join the selected names with vertical bars to use as an OR sequence in a regex.
		set AppleScript's text item delimiters to ";"
		set numberlist to (numberlist as text)
		set AppleScript's text item delimiters to tids
		-- all done with selecting the items, we filter out anything we won't keep
		set newData to quoted form of text 1 thru -2 of (do shell script "defaults read " & domain & " RecentDocuments |sed -e " & quoted form of numberlist without altering line endings)
		-- and writes it back into the existing dictionary
		do shell script ("defaults write " & domain & " RecentDocuments -dict \"CustomListItems\" " & newData)
		
		tell me to display dialog "I removed " & (count namesToCut) & " items(s) from" & chosenapp & "´s Recent items." with title sTitle buttons {"Cancel", "Again"} default button 1 with icon recentItemIcon
		
	end repeat
end if

It’s easier with entry numbers:

set domain to "com.apple.iWork.Pages.LSSharedFileList"

-- Parse the "Open Recent" document names from the plist file.
set RecentNames to paragraphs of (do shell script ("defaults read " & domain & " RecentDocuments | sed -En '/^[[:space:]]*CustomListItems[[:space:]]*=[[:space:]]*\\(/,/^[[:space:]]+\\)/ s/^[[:space:]]+Name[[:space:]]*=[[:space:]\"]*(.*(\\\\\"|[^\"]))\"?;$/\\1/p' |sed  '= ' | sed  'N ; s/\\n/. /'"))

-- Ask which items to remove from the menu.
activate
set namesToCut to (choose from list RecentNames with prompt "Delete document(s):" with multiple selections allowed)
if (namesToCut is false) then error number -128

-- Create individual "delete numbered entry" lines matching the numbers displayed with the selected names.
set cutRegex to linefeed
repeat with i from (count namesToCut) to 1 by -1
	set cutRegex to cutRegex & "s/[,[:space:]]+\\{[^\"}]+(\"[^[:cntrl:]]+\\n[^}]*)?\\}//" & word 1 of item i of namesToCut & " ;" & linefeed
end repeat

-- Derive an edited CustomListItems "array" to use in a "defaults write" shell script.
set newData to quoted form of text 1 thru -2 of (do shell script ("defaults read " & domain & " RecentDocuments | sed -En '
/\\(/,$ H ;
$ {
	g ;" & ¬
	cutRegex & ¬
	"s/^[^(]+\\(,?/\\(/ ;
	s/\\);[^)]+$/\\)/p ;
}'") without altering line endings)

-- Write the edited array back to the plist file.
do shell script ("defaults write " & domain & " RecentDocuments -dict-add \"CustomListItems\" " & newData)

Edit: The regexes in this script no longer assume the file names are in quotes.

Hello.

I’ll leave it as it is, as I can’t easily bypass, or overcome the wrongful encoding of characters with diacriticals in the filenames. :slight_smile:

I have a solid hunch that iconv won’t be able to solve it, since it isn’t a “normal” utf-16 format, and since the text with diacriticals has been transformed into errantly utf-8 before iconv gets it, (After it has been processed by sed.)

If you want to implement your latest version feel free.

I am done with this. (Though a fun pastime during the weekend). :slight_smile:

Hello

The script in post #35 doesn work!.

I was a little bit to quick about it, though I am sure it worked at some point in time, or maybe I didn’t test it fully.

Either way, it doesn’t work, and with my experience of encoding problems, which I cant over come, it won’t be fixed.

The script that works as it should is in post nr #20. Unless you want to roll your own.

To clarify a little: it’s not really a UTF-16 issue, it’s a Unicode issue – UTF-16 is just a way to store Unicode, and the same thing can happen with UTF-8. And the terms you were trying to remember are precomposed and decomposed.

I was dead sure it worked, the code above, the dictionary looks like it should, when it is written out and read back in again, still TextEdit hangs when I go to the recent items menu.

decomposed… That sounds like going out with the garbage, no wonder why I forgot it. :stuck_out_tongue:

I’m sorry for the sloppyness, I know that UTF-X is just different representations of the unicode character set.

What is interesting to know is how you solve this, I have recently got it in the face, when dealing with filenames and the ICU library, I figured I’d either have to compile the byte-sequence back “manually” into precomposed, or find a work around, which I did.

As far as I am concerned it is an Apple issue. :slight_smile:

Are there any routines in any library you know of that performs that service?

Thanks!