dougscripts.com

    Finesse

  • The basic mechanics of the script are finished and it will probably run fine...about 90% of the time. We need to add some details.

Is the remote user logged in and is iTunes active? It should be in order to add files to it.

The rsync routines will work even if "cleanuser" is not logged in. However, the osascript routine--the routine that actually adds the files to the remote iTunes--cannot work if "cleanuser" is not logged in and iTunes isn't running. Since that's pretty much the essence of the workflow, it wouldn't make much sense to go to all the trouble to run it and have it error because the remote user isn't logged in. Plus, we don't want rsync to copy any files if they're not going to be added to iTunes--they would be "stranded" in the iTunes Music folder.

Below is a handler that will check if the remote user is logged in and then attempt to launch iTunes. If either task cannot be performed, the handler returns false; otherwise, if successful, the handler returns true. Add this handler to the script:

on are_you_logged_in()
	set whoCom to quoted form of ("who -q | grep \"" & remUserName & "\"")
	try
		set rez to (do shell script ("ssh " & remAddr & space & whoCom))
		set osaCom to quoted form of ("osascript -e 'tell application\"iTunes\" to launch'")
		do shell script ("ssh " & remAddr & space & osaCom)
		return true
	on error
		return false
	end try
end are_you_logged_in


This handler ssh's the unix command who with the "q" option to the remote machine that returns the names of the users logged in. If this list contains the remUserName then it proceeds to try and launch iTunes. If iTunes can be launched , all's well and the handler returns true (if iTunes is already running then launch has no effect, but it will not error); if it cannot then the script will error, returning false.

Call it early in the script, right after the requisite variables are set:

-- == allocate globals

global remUserName, remHostIPAddr, remAddr

-- == init variables

set remUserName to "cleanuser" -- use your remote username
set remHostIPAddr to "192.168.1.206" -- use your remote IP address

set remAddr to (remUserName & "@" & remHostIPAddr) as text

if my are_you_logged_in() is false then return

Make sure that you have declared remUserName, remHost, and destAddr as globals at the top of the script.

Get iTunes Music folder paths

Rather than hard-code the source and destination path variables (locMusicLibrary and remMusicLibrary, respectively), you can use the routines below to get the location of the iTunes Music folder for each source and destination iTunes user. Then, the only variables that would need to be hard-coded in the script are remUserName and remHost. (Perhaps a mod to make would have no hard-coded variables in the script, but read from an external editable preferences file of some sort.)

The location of your iTunes Music folder is listed in your current iTunes library's "iTunes Music Library.xml" file, and the location of that file is listed in "com.apple.iApps.plist". Here's a subroutine to get the current user's current iTunes Music folder location:1

to get_music_folder()
	set o to (do shell script "defaults read com.apple.iApps iTunesRecentDatabasePaths") as text
	set xm to text ((offset of "\"" in o) + 1) through ((offset of ".xml" in o) + 3) of o
	set xml to (my replace_chars(xm, " ", "\\ ")) as text
	-- based on unix script by Dave Taylor
	return (do shell script "head -n 14 " & xml & ¬
		"| grep  '>Music Folder<' | cut -d/ -f4- | sed 's/localhost//g' | \\cut -d\\< -f1 | sed 's/%20/ /g'")
end get_music_folder


To get the remote user's iTunes Music library location, we'll ssh a variation of this.

to get_remote_music_folder()
	set dCom to quoted form of ("defaults read com.apple.iApps iTunesRecentDatabasePaths")
	set o to (do shell script ("ssh " & remAddr & space & dCom))
	set xm to text ((offset of "\"" in o) + 1) through ((offset of ".xml" in o) + 3) of o
	set xml to (my replace_chars(xm, " ", "\\ ")) as text
	-- again, based on unix script by Dave Taylor
	set hCom to quoted form of ("head -n 14 " & xml & ¬
		"| grep  '>Music Folder<' | cut -d/ -f4- | sed 's/localhost//g' | \\cut -d\\< -f1 | sed 's/%20/ /g'")
	return (do shell script ("ssh " & remAddr & space & hCom))
end get_remote_music_folder


Escape single quotes

The file path data returned from the first rsync routine may contain single quotes (apostrophes) and these have to be escaped before the perl and osascript routines. (Each of these commands is within single quotes so real single quotes inside them need to be escaped. In fact, for the osascript, the escapes have to be escaped!) Add this handler:

to fix_single_quotes(x)
	return my replace_chars(x, "'", "'\\''") as text
end fix_single_quotes


...and call it just as the first rsync command is run, like so:

set rezList to my text_to_list(my fix_single_quotes(do shell script theCommand), ASCII character 13)

...and later in the repeat loop:

repeat with fileToAdd in filesToAdd
	set fileToAdd to my fix_single_quotes(fileToAdd)
	-- and so on...

Optional: Exclude special folders

I've used rsync's "--exclude" option to prevent pesky ".DS_Store" files from being copied. But I can include as many "--exclude" patterns as I want. To prevent podcast, movie, and TV show files from being considered by rsync I'm going to create this excludeThese variable at the top of the script and then use it in each of my rsync codes:

-- place at top of script with other variables
set excludeThese to "--exclude 'Movies/' --exclude 'Podcasts/' --exclude 'TV Shows/'"

-- first rsync (dry run):
set theCommand to ("rsync -nEvauz --ignore-existing --exclude '.DS_Store'" & space & ¬
	excludeThese & space & ¬
	locMusicLibraryQuoted & space & ¬
	remAddr & ":" & remMusicLibraryEscapedQuoted)

-- second rsync (for real):
set theCommand to ("rsync -Evauz --ignore-existing --exclude '.DS_Store'" & space & ¬
	excludeThese & space & ¬
	locMusicLibraryQuoted & space & ¬
	remAddr & ":" & remMusicLibraryEscapedQuoted)


rsync will ignore the paths I've listed in excludeThese. You could also filter Artist folders, say to make sure your wife never gets any of your "Eagles of Death Metal" tracks, which, surprisingly, she really doesn't care for.

Optional: Update a text log using info from rsync's "--stats" option

One of rsync's many cool options is "--stats". It will report how many files were copied, size, and so on. You can then capture this info and dump it to a text log. For added measure, I've included the start and stop time of the script and a tally of how many files were added. Here's a terse run-through:

First, include the "--stats" option in the second rsync routine and make sure to set a variable to the result of the do shell script that runs it:

-- == rsync for real

set theCommand to ("rsync -Evauz --stats --ignore-existing --exclude '.DS_Store'" & space & ¬
	excludeThese & space & ¬
	locMusicLibraryQuoted & space & ¬
	remAddr & ":" & remMusicLibraryEscapedQuoted)


Add the following handler with the other handlers to get the current date and time and set a start_time variable to its result when the script begins:

to get_now()
	return (do shell script "date '+%x %X'")
end get_now


Then, initialize a variable as a counter, here trackAddedCounter, and increment it on each loop of the routine adding the files:

-- == add files to iTunes

set trackAddedCounter to 0
repeat with fileToAdd in filesToAdd
	set fileToAdd to my fix_single_quotes(fileToAdd)
	try
		with timeout of (3 * days) seconds
			set osaCom to quoted form of ¬
				("osascript -e 'tell application\"iTunes\" to add POSIX file \"" & fileToAdd & "\"'")
			do shell script ("ssh " & remAddr & space & osaCom)
			set trackAddedCounter to (trackAddedCounter + 1)
		end timeout
	on error m number n
		log m
		log n
	end try
end repeat


After that routine has run, prepare the output:

set logResults to ("Started: " & start_time & return & ¬
	"Finished: " & my get_now() & return & ¬
	rez & return & return & ¬
	"Files added to iTunes: " & trackAddedCounter & return & ¬
	"== == == == == == == == == == == ==" & return)


This would produce something like this in the logResults variable:

Started: 05/28/09 14:24:54 Finished: 05/28/09 14:27:08 Number of files: 66 Number of files transferred: 7 Total file size: 414440850 bytes Total transferred file size: 69328934 bytes Literal data: 69328934 bytes Matched data: 0 bytes File list size: 2331 File list generation time: 0.003 seconds File list transfer time: 0.000 seconds Total bytes sent: 68692715 Total bytes received: 216 sent 68692715 bytes received 216 bytes 570065.82 bytes/sec total size is 414440850 speedup is 6.03 Files added to iTunes: 7 == == == == == == == == == == == ==

Append it to a text file; I'll leave it to you to put that together.

 

The Other Way 'Round

This "push" workflow could be written the other way around, as a "pull" workflow, such that the local machine periodically checks the remote machine for new new files and copies them over to add to the local iTunes. That's the next project.

 

Get More Information:

See the man pages for:
who
grep
osascript
ssh
defaults
date

Here's a Linux-oriented rundown on sed, as good as any other.

Apple's Technical Note TN2065 should be read by anyone who uses do shell script, with especial regard to string quoting and escaping.

 

1This will be accurate providing your system is not suffering from Multiple Library Confusion, whereby the iApps.plist is not updated with the correct location of your current iTunes library files.

Site contents © 2001 - 2019 (that's right: 2001) Doug Adams and weblished by Doug Adams. Contact support AT dougscripts DOT com. About.
All rights reserved. Privacy.
AppleScript, iTunes, iPod, iPad, and iPhone are registered trademarks of Apple Inc. This site has no direct affiliation with Apple, Inc.
The one who says "it cannot be done" should not interrupt the one doing it.