View Single Post
Apparently the set of users who want to delete old stuff from OmniFocus <1.1 is a smaller set than I thought, since nobody's posted about a bug I ran across today. The script fails if it hits a repeating project with no subsections. I'm not entirely sure a project can contain other sections, so it's presumably a fairly common condition.

Anyway, here's the fixed Version 1.3 of the script, for the one person out there who might eventually download and use it. It also now pops up an alert when it's done running, reporting on how many items were actually deleted. Finally, now a project that's old enough will get axed if it's either "completed" or "dropped."

Code:
-- CLEAR OLD OMNIFOCUS ITEMS
-- Dave Howell, dh.omnifocus@grandfenwick.net
-- v1.3, October 3rd, 2008

-- 1.2: First publically released version.
-- 1.3: Added reporting window at end of run, and added "dropped" to valid states for a project to be deleted.

-- if a task or project was completed more than DaysBeforeDeleting days ago, then delete it
property DaysBeforeDeleting : 30


global Today -- loading the current date into Today saves a bit of time
global DeletedItemCount -- how many things got deleted?

on run
	set Today to current date
	set DeletedItemCount to 0
	tell application "OmniFocus"
		set CurrentDocument to default document
		tell CurrentDocument
			if number of document window is 0 then
				make new document window with properties {bounds:{0, 0, 1000, 500}}
			end if
		end tell
		set SectionList to every section of CurrentDocument
	end tell
	ProcessSections(SectionList)
	tell application "OmniFocus" to display alert "Expired Item Cleanup Completed" message (DeletedItemCount as rich text) & " items removed." buttons {"OK"} default button 1 giving up after 30
end run

-- We work through the lest backwards (ListCount to 1 by -1) because if we delete
-- a section, then the numbering of all the later sections changes. This frequently
-- causes a structure like "repeat with CurrentSection in items of SectionList" to malfunction.
-- However, see note at end of document.  if the list is empty, then the contents of the  
-- Repeat loop (count from 0 to 1 by -1) will simply be skipped. 

on ProcessSections(SectionList)
	using terms from application "OmniFocus"
		repeat with SectionIndex from (count of SectionList) to 1 by -1
			tell item SectionIndex of SectionList
				if class is project then
					if repetition is missing value then
						my ProcessTasks(every task)
						my DeleteIfOldEnough(item SectionIndex of SectionList)
					end if -- if it *does* repeat, then don't delete it.
				else -- not a project. Must be a folder.
					my ProcessSections(every section)
				end if
			end tell
		end repeat
	end using terms from
end ProcessSections

on ProcessTasks(TaskList)
	using terms from application "OmniFocus"
		repeat with TaskIndex from (count of TaskList) to 1 by -1
			set CurrentTask to item TaskIndex of TaskList
			my ProcessTasks(every task of CurrentTask)
			DeleteIfOldEnough(CurrentTask)
		end repeat
	end using terms from
end ProcessTasks

-- I wrote this routine as a whole bunch of sequential 'if' statements to make it really clear
-- what has to be true before a task or project gets deleted. If "AnItem" is a folder,
-- it will cause an error since folders don't have a "completed" property. So don't pass it any folders!

to DeleteIfOldEnough(AnItem)
	tell application "OmniFocus" to tell AnItem
		if completed is missing value or not completed then return -- this item isn't completed
		if repetition is not missing value then return -- this item will repeat at a later date
		if class of AnItem is project and not (status is done or status is dropped) then return --this project hasn't been marked as "done"
		if (Today - (completion date)) / days < DaysBeforeDeleting then return --this item was completed too recently
		delete -- Well, guess there's no reason not to delete this item...
	end tell
	set DeletedItemCount to DeletedItemCount + 1
end DeleteIfOldEnough

-- Endnote: if you ask many applications for a list of something (like mail messages or ToDo items
-- or whatever) you might get back something like {thingy 1 of application, thingy 2 of application}.
-- The problem with this is, if you start through the list, but delete "thingy 1," then 
-- try to ask the system about "thingy 2," well, there ISN'T a "thingy 2" anymore. The former
-- thingy two just became thingy one. Apple's "Mail" program used to do this constantly, so deleting
-- Mail messages was a big-time pain in the ass. (For all I know, the problem's still there; I had to 
-- rewrite my scripts years ago to work around it, and I'm not going to waste time fiddling with them
-- now to see if things are better.)
--
-- Omni, not all that surprisingly, does it the right way, passing back references that use unique, stable
-- IDs, like {task id "cBq9eJgsG-C" of document id "fSRWe8cGivB",  project id "npto5zRxqHL" of 
-- document id "fSRWe8cGivB"}, so even if one of the items in the list is deleted, the remaining members 
-- are as valid as they were when the list was first constructed. Therefore, it should be possible to
-- count forwards instead of backwards and still have the script work properly. But I know the backwards
-- count works, so I'm not (yet) going to bother fiddling with it. :)