Reminders scripting is slow and lacking

Hi All,

See below two scripts I made for batch changing of priorities of reminders.

Here’s three things I don’t like about it:

  1. It’s slow
  2. Reminders has no “selection” so you can’t simply have the script act on selected reminders. Instead, the user needs to select which reminders to change from a list.
  3. Also because Reminders has no way to telling which list is selected, user needs to select which list they want. (the second script “fixes” this by showing a huge list of all incomplete reminders for all lists, but this is slower and will yield a potentially huge list)

I should also mention that both of these scripts run way faster through ASE then when executed from the script menu. The slowness I’m concerned about is with calls to Reminders; it takes too long to get reminder information and too long to set the priorities.

Note the commented sections in the first script: I figured it had to be faster to set reminder priorities using the applescript list of reminders that I had already created than to have Reminders search through entire lists repeatedly right? Wrong! It runs way faster using the calls to individual Reminders lists than when simply iterating through the applescript list of reminders that would be created at the beginning of the script.

So two questions:

  1. How can I make this run faster?
  2. Am I correct in my statements that there is no way to identify the active list or the selected reminders through applescript?

Thanks!

First the script that requires the user to choose both the list and reminders:

property PriorityList : {{value:0, name:"None"}, {value:9, name:"Low"}, {value:5, name:"Medium"}, {value:1, name:"High"}}

on run
	tell application "Reminders"
		set lName to name of every list
		set dName to name of default list
	end tell
	tell me to activate
	set lName to choose from list lName with prompt "Change priority of reminders of list:" default items {dName} without empty selection allowed
	if lName is false then
		return 1
	else
		set lName to lName as string
	end if
	tell application "Reminders"
		tell list lName
			set rNames to name of every reminder whose completed is false
			--set rList to every reminder whose completed is false
			--set rNames to {}
			--repeat with r in rList
			--	set end of rNames to name of r
			--end repeat
		end tell
	end tell
	tell me to activate
	set rNames to choose from list rNames with prompt "Change priority of reminders:" with multiple selections allowed without empty selection allowed
	if rNames is false then return 1
	set newP to {}
	repeat with p in PriorityList
		set end of newP to name of p
	end repeat
	tell me to activate
	set newP to choose from list newP with prompt "Set priority to:" without empty selection allowed
	if newP is false then return 1
	repeat with p in PriorityList
		if newP as string is name of p then
			set newP to value of p
			exit repeat
		end if
	end repeat
	tell application "Reminders"
		tell list lName
			repeat with n in rNames
				set (priority of every reminder whose completed is false and priority is not newP and name is (n as string)) to newP
			end repeat
		end tell
		--repeat with n in rNames
		--	repeat with r in rList
		--		if (name of r) as string is n as string then
		--			set priority of r to newP
		--		end if
		--	end repeat
		--end repeat
	end tell
	return 0
end run

Then the script that only requires the user to select reminders:

property PriorityList : {{value:0, name:"None"}, {value:9, name:"Low"}, {value:5, name:"Medium"}, {value:1, name:"High"}}
property Delim : " --> "

on run
	tell application "Reminders"
		set lNames to name of every list
	end tell
	set fullRNames to {}
	tell application "Reminders"
		repeat with l in lNames
			set rNames to {}
			tell list l
				set rNames to (name of every reminder whose completed is false)
			end tell
			if (count of rNames) > 0 then
				repeat with n in rNames
					set end of fullRNames to l & Delim & n
				end repeat
			end if
		end repeat
	end tell
	tell me to activate
	set rNames to choose from list fullRNames with prompt "Change priority of reminders:" with multiple selections allowed without empty selection allowed
	if rNames is false then return 1
	set newP to {}
	repeat with p in PriorityList
		set end of newP to name of p
	end repeat
	tell me to activate
	set newP to choose from list newP with prompt "Set priority to:" without empty selection allowed
	if newP is false then return 1
	repeat with p in PriorityList
		if newP as string is name of p then
			set newP to value of p
			exit repeat
		end if
	end repeat
	repeat with n in rNames
		set lNameParse to parseLine(n, Delim)
		set lName to item 1 of lNameParse
		set rName to (items 2 through -1 of lNameParse) as string
		tell application "Reminders" to tell list lName
			set (priority of every reminder whose completed is false and priority is not newP and name is rName) to newP
		end tell
	end repeat
	return 0
end run

on parseLine(theLine, delimiter)
	-- This came from Nigel Garvey
	
	set astid to AppleScript's text item delimiters
	set AppleScript's text item delimiters to {delimiter}
	set theTextItems to theLine's text items
	set AppleScript's text item delimiters to astid
	
	repeat with i from 1 to (count theTextItems)
		if (item i of theTextItems is "") then set item i of theTextItems to missing value
	end repeat
	
	return theTextItems's every text
end parseLine

Hi scriptim,

You can get the selected reminders with ui scripting. For example:

tell application "Reminders"
	activate
	name of every reminder
end tell

tell application "System Events"
	keystroke "c" using command down
end tell

set cb to the clipboard

→ "[ ] Countdown Timer
remind me 11/21/13

[ ] Change oil; filter leak
remind me 12/19/13

"
From there you can easily parse the result to get the name of selected reminders.

gl,
kel

Thanks for the suggestion!

It led to the following script. Now the speed issue is the main issue, and it’s even worse now because the line that actually changes the priority applies to the complete list of reminders over all lists. it’s a little faster than just checking the names of reminders since the boolean check for completion is faster than the string comparison, but still painfully slow. If I could find a decent way of finding which list a reminder belonged to then the calls could be faster by asking a particular list to check its contents, but every way I’ve tried to find the list of a reminder just results in more requests sent to Reminders and just slow things down even more.

A bigger curiosity though: Why does this run so much faster from within ASE than with the applescript runner (running it from the script menu)? Is there a way to have ASE run my scripts for me (extra bonus for not having to leave it running all the time)?

Any other tips for how to speed this up? Especially considering that the big holdup seems to be waiting on Reminders to finish handling applescript events, I wonder if some of the tricks I’ve seen but never use, like putting everything inside a script object or using properties in place of variables.

Thanks

property PriorityList : {{value:0, name:"None"}, {value:9, name:"Low"}, {value:5, name:"Medium"}, {value:1, name:"High"}}

on run
	tell application "Reminders" to activate
	tell application "System Events" to tell process "Reminders" to keystroke "c" using command down
	delay 0.3
	set RawText to the clipboard
	set RawText to paragraphs of RawText
	set rNames to {}
	repeat with aLine in RawText
		if (count of (aLine as string)) > 0 and item 1 of (aLine as string) is "[" then
			set end of rNames to (items 2 through -1 of parseline(aLine, tab)) as string
		end if
	end repeat
	if (count of rNames) > 0 then
		tell me to activate
		set newP to {}
		repeat with p in PriorityList
			set end of newP to name of p
		end repeat
		set newP to choose from list newP with prompt "Set priority to:" without empty selection allowed
		if newP is false then return 1
		repeat with p in PriorityList
			if newP as string is name of p then
				set newP to value of p
				exit repeat
			end if
		end repeat
		tell application "Reminders"
			set lNames to name of every list
			repeat with n in rNames
				set (priority of every reminder whose completed is false and priority is not newP and name is (n as string)) to newP
			end repeat
		end tell
	end if
	return 0
end run

on parseline(theLine, delimiter)
	-- This came from Nigel Garvey
	
	set astid to AppleScript's text item delimiters
	set AppleScript's text item delimiters to {delimiter}
	set theTextItems to theLine's text items
	set AppleScript's text item delimiters to astid
	
	repeat with i from 1 to (count theTextItems)
		if (item i of theTextItems is "") then set item i of theTextItems to missing value
	end repeat
	
	return theTextItems's every text
end parseline

Hi scriptim,

You can get the list for a reminder with 'container":

tell application "Reminders"
	activate
	set this_reminder to first reminder whose name is "Countdown Timer"
	container of this_reminder
end tell

→ list id “D00D1764-ADB4-43D7-893F-FE46050A159F” of application “Reminders”

I’m in a rush now, but will read your post better when I’m done.

gl,
kel

Thanks again! I had tried that as well, but it become three events for Reminders to do to finally set the priority of that first reminder. Assuming that the rest of the selected reminders are all in the same list, you might then save some time by using that list for the rest of the searches. If you have reminders from multiple lists selected, however, then you need some way to test if the next reminder in the selection is in the same list or not, and if not do another search of all reminders to find the list of the new reminder.

Although I didn’t even know I could select reminders in multiple lists simultaneously until I started messing with this :slight_smile: Easy enough to just assume all reminders are in the same list and actually have a decent running time.

Any thoughts on the runtime difference between ASE and AS Runner?

Thanks

Hi scriptim,

As a workaround, you could place tags in the name of a reminder that says which list it is in. For instance, I might name a reminder “Exercise (1-2)”. Another workaround is to place the list name in the notes as a tag. e.g.

tell application "Reminders"
	activate
end tell

tell application "System Events"
	keystroke "c" using command down
end tell

set cb to the clipboard
-- etc.

tell application "Reminders"
	every reminder whose name is "hello" and body is "Reminders"
end tell

If you have reminders with same name in the same list, then you might number them. It wouldn’t be too hard to create a script numbering all reminders with same name in a list.

How easy it would have been if they just added selection, huh.

Edited: ignore that last part in my post. That won’t help if you just have the name of the reminder.

gl,
kel