Why does this script screw up the finder?

I use TVShows to subscribe to numerous tv torrents and this script, saved as TVtasks.app, watches over them. I used Lingon to set this to run once an hour. (I haven’t tried in six months or so, but Folder Actions in Leopard were behaving poorly, so I had to find some other way of running this script.)

It scans TVShows’ download folder, tries to figure out which show each torrent is for, move it, and open it. Transmission will download it to a temp folder and move it back to its final destination upon completion. TVtasks will then scan my Television folder for fresh videos, using file labels to track what has or hasn’t been processed. adds them to iTunes, and tries to figure out the show, season, and episode number of videos, then grab the episode title from TVrage.

The end result is a largely hassle-free, invisible flow; once I manually subscribe to a show’s torrent and add its search pattern and name to showTokens, they magically appear named and ready to go in iTunes/FrontRow. The regular expression matching uses SatImage.osax, which I’ve only tested through 3.2.0.

I am an AppleScript novice, so plenty of suck in here. But it works. Mostly. I’ve had one big problem in all versions of Leopard up to 10.5.4 (currently installed).

Intermittently, something snaps and “(every file in entire contents of folder tvFolderPath whose label index is not 6)” stops returning files even though I know that there are fresh, unlabeled videos. The FInder knows it, too, as I have a savedSearch which shows them. This is a long-standing issue that I have never been able to figure out, despite rewriting the torrent and video handlers numerous times. “Entire contents … as alias list” does not throw an error when this happens, so the script remains ignorant of any problems.

Relaunching the finder always fixes the problem. For some voodoo reason, opening and saving the script seems to fix it sometimes.

The only evidence I’ve found of a problem in the console is that I will start getting reports that locum terminated once an hour, but it’s not always concurrent with the problem.

So, what’s the deal?

FYI: this script is set up with the assumption that you have checked “copy music to iTunes folder” in your iTunes prefs. If not, then this script will probably mess stuff up.

-- path of your torrent folder
property torrentFolderPath : "Video:Maintenance:Torrents:Incoming"

-- path to the root of your tv folders
property tvFolderPath : "Video:Television"

-- path to folder where temporary folder will go
property chaffParentPath : "Video:Maintenance:Torrents"

-- list of of grep patterns and preferred human-readable names.
-- DON'T use parentheses in your matching patterns; it will throw off the season/episode number extraction
-- (truncated for clarity's sake)
property showTokens : {¬
	{"american.?idol", "American Idol"}, ¬
	{"bionic.?woman", "Bionic Woman"}, ¬
	{"Bones", "Bones"}, ¬
	{"dancing.?w.{0,4}the.?stars.{0,5}", "Dancing with the Stars"}, ¬
	{"my.?name.?is.?Earl", "My Name is Earl"}, ¬
	{"30.?Rock", "30 Rock"} ¬
		}

checkForTorrents()

on checkForTorrents()
	tell application "Finder"
		set torrentFolder to folder torrentFolderPath
		try
			set freshTorrents to (files of torrentFolder whose name extension is "torrent") as alias list
		on error
			try
				set freshTorrents to (files of torrentFolder whose name extension is "torrent") as alias as list
			on error
				try
					set freshTorrents to {}
				end try
			end try
		end try
		
		do shell script "logger TVtasks found " & (length of freshTorrents) & " TORRENTS"
		
		repeat with t in freshTorrents
			repeat 1 times
				set tInfo to my parseShowStuff(name of t as text)
				if tInfo's pShow is not "Unknown" then
					set tShow to tInfo's pShow as text
					set tSeason to tInfo's pSeason as text
					set showFolder to tvFolderPath & ":" & tShow
					set seasonFolder to showFolder & ":Season " & tSeason
					
					if not (exists folder showFolder) then
						make new folder at folder tvFolderPath with properties {name:tShow}
					end if
					if not (exists folder seasonFolder) then
						make new folder at folder showFolder with properties {name:"Season " & (tSeason as integer)}
					end if
					
					try
						set finalTorrent to (duplicate t to folder seasonFolder with replacing) as alias
						if exists file finalTorrent then
							delete t
							-- open the torrent in the background 
							do shell script "open -g " & (quoted form of POSIX path of finalTorrent)
						end if
					end try
				else
					(*
				Well, we downloaded the torrent for a reason. go get it anyway.
				 The end result will be that it will get added and sit in the movie folder.
				 NB: the quicktime reference file will always expect to find the actual
				 movie file in the Unknown folder.
				 *)
					try
						set finalTorrent to (duplicate t to folder (tvFolderPath & ":Unknown") with replacing) as alias
						if exists file finalTorrent then
							delete t
							-- open the torrent in the background 
							try
								do shell script "open -g " & (quoted form of POSIX path of finalTorrent)
							on error
								try
									open finalTorrent
								end try
							end try
						end if
					end try
				end if
			end repeat
		end repeat
	end tell
	checkForVideos()
end checkForTorrents

on checkForVideos()
	tell application "Finder"
		try
			set freshVideos to a reference to (every file in entire contents of folder tvFolderPath whose label index is not 6) as alias list
		on error
			try
				set freshVideos to a reference to (every file in entire contents of folder tvFolderPath whose label index is not 6) as alias as list
			on error
				set freshVideos to {}
			end try
		end try
		do shell script "logger TVtasks found " & (length of freshVideos) & " VIDEOS"

		--(assuming you "checked copy to iTunes folder" pref in iTunes)
		if not (exists folder (chaffParentPath & ":Chaff")) then make new folder at folder chaffParentPath with properties {name:"Chaff"}
		repeat with v in freshVideos
			repeat 1 times
				-- Cannibalized from Andree Dettmer's Movie2iTunes
				if v's name extension is in {"mov", "mpg", "mpeg", "mp4", "avi", "wmv", "swf", "m2v", "flv", "wma", "divx", "mkv"} and v as text does not contain "sample" then
					set tempMovie to chaffParentPath & ":Chaff:" & (name of v as text) & ".mov"
					do shell script "php -r 'echo $argv[1].rawurlencode($argv[3]).$argv[2];' " & quoted form of "<?xml version=\"1.0\"?><?quicktime type=\"application/x-quicktime-media-link\"?><embed src=\"file:/" & " " & quoted form of "\"/>" & " " & quoted form of POSIX path of v & " >> " & quoted form of POSIX path of tempMovie
					set vInfo to my parseShowStuff(name of v as text)
					set theTitle to false -- remember to check if this is still necessary
					if vInfo's pShow is not "Unknown" then set theTitle to my getEpisodeName(vInfo)
					tell application "iTunes"
						(*
							Telling the actual track seemed easiest. I recall getting knocked around by
							a whole lot of "You can't get there from here" messages. It also seemed to be
							the only way to set multiple properties, which feels a tad faster than telling
							iTunes to change one property at a time.
							*)
						tell (add tempMovie as alias)
							if vInfo's pShow is not "Unknown" then
								-- We can set the show info, sans episode name, if the parser figured things out
								try
									set {show, season number, episode number, track number} to {vInfo's pShow, vInfo's pSeason, vInfo's pEpisode, vInfo's pEpisode}
								end try
								-- If we got the episode's title, then set it and make it a tv show.
								if theTitle is not false then
									try
										set {name, unplayed, video kind} to {theTitle, true, TV show}
									end try
								end if
							end if
							tell application "Finder" to set v's label index to 6
						end tell
					end tell
				else if v's name extension is "rar" then
					--I hate RARs, but you still get 'em sometimes. Open them in the background
					--NB: it is expected that you have a program set up to open RAR files, like StuffitExpander.
					do shell script "open -g " & (quoted form of POSIX path of (v as text))
					set v's label index to 6
				end if
			end repeat
		end repeat
		--regardless of success, we're dumping these temp files (assuming you "checked copy to iTunes folder" pref in iTunes)
		delete folder (chaffParentPath & ":Chaff")
	end tell
end checkForVideos

on getEpisodeName(epInfo)
	-- go to tvrage.com and get the episode information. chop out the episode title.
	set getEp to "'if($fp=fopen(\"http://www.tvrage.com/quickinfo.php?show=\".urlencode($argv[1]).\"&ep=\".urlencode($argv[2]),\"r\") ) {while(!feof($fp)){$line=fgets($fp,1024);list ($sec,$val)=explode(\"@\",$line,2);if($sec==\"Episode Info\"){list($ep,$title,$airdate)=explode(\"^\",$val);echo \"^\".$title;}}fclose($fp);}'"
	set theTitle to (do shell script "php  -r " & getEp & " \"" & epInfo's pShow & "\" \"" & epInfo's pSeason & "x" & epInfo's pEpisode & "\"")
	if theTitle starts with "^" then
-- sloppy way to tell that it worked
		return (text 2 thru -1 of theTitle)
	else
		--bad form to have the variable alternate between a string and a boolean. oh well.
		return false
	end if
end getEpisodeName

on parseShowStuff(theString)
	set parsedInfo to {pShow:"Unknown"}
	
	repeat with parseItem in showTokens
		try
			(*
			Look for the grep pattern from the property showTokens, followed by a season/episode pattern. This does 
			not handle date-based file names. Satimage returns an error when the grep isn't found, thus
			you must wrap it in a try block. If it works, theInfo is a string "S# E#", e.g. "1 13".
			*)
			set theInfo to find text ((item 1 of parseItem as string) & "[^0-9]{0,4}([0-9]{1,2})[^0-9]{0,3}([0-9]{2})") in theString using "\\1 \\2" with regexp and string result without case sensitive
			set parsedInfo to {pShow:item 2 of parseItem, pSeason:word 1 of theInfo, pEpisode:word 2 of theInfo}
			
			--if we got this far, then it worked. no need to keep repeating
			exit repeat
		end try
	end repeat
	return parsedInfo
end parseShowStuff

I’ve tested it with SatImage 3.3.1; the script still works and still exhibits the same problem. I’ve gone through the effort to put every statement within a try block, hoping to find anything. No errors; it’s still the Finder not returning the updated files.

Hi, aetheros.

The Finder in OS X sometimes takes a while to get round to updating its knowledge of what’s in its folders. You could try using its ‘update’ command to make it do that immediately. I don’t know how well this works in 10.5. In earlier systems, I think I remember it was sometimes necessary to do it twice.

tell application "Finder"
	update every file in entire contents of folder tvFolderPath
	set freshVideos to (every file in entire contents of folder tvFolderPath whose label index is not 6) as alias list
	-- etc.
end tell

You don’t need to use ‘a reference to’ where you have it in your script.

Thanks for the help. Unfortunately, update didn’t do the trick. Sigh.

Interesting…

I thought I’d give something else a try; maybe using a different filter. So instead of filtering on label index, I picked up everything modified in the last 2 hours and then filtered on label index in the repeat. It worked.

I’ll come back and report after it’s had a few more runs in the wild.

That stops picking up files, too.

So I gave up on using “every file in entire contents” and resorted to using the terminal.

set recentFiles to (do shell script "cd /Volumes; find Video/Television -name \"*?.??*\" -newermt '70 minutes ago' -not -name \"*.torrent\"")

This seems to be working reliably. It also has the benefit of working instantly without completely bogging down the system for 30-60 seconds. By cd-ing into /Volumes and leaving of the trailing slash on “Video/Television/”, all files come up with a easily converted path to handle them in Applescript.

Then I iterate paragraphs and convert each one into an alias, via Satimage’s change command:

set v to (file (change "/" in paragraph p of recentFiles into ":") as alias)

And then make sure they’re unlabeled videos and process them.

Yay. (whew.)

The SatImage addition is not needed, this is the proper way,
it also coerces automatically /Volumes/myDisk/ to myDisk:


set v to POSIX file (paragraph p of recentFiles) as alias