···11+#!/usr/bin/ruby
22+# $Id: itunes-rsync.rb,v 1.1 2009/01/27 08:49:12 jcs Exp $
33+#
44+# rsync an itunes playlist with another directory, most likely a usb music
55+# device
66+#
77+# requires the rubyosa gem ("sudo gem install rubyosa")
88+#
99+# Copyright (c) 2009 joshua stein <jcs@jcs.org>
1010+#
1111+# Redistribution and use in source and binary forms, with or without
1212+# modification, are permitted provided that the following conditions
1313+# are met:
1414+#
1515+# 1. Redistributions of source code must retain the above copyright
1616+# notice, this list of conditions and the following disclaimer.
1717+# 2. Redistributions in binary form must reproduce the above copyright
1818+# notice, this list of conditions and the following disclaimer in the
1919+# documentation and/or other materials provided with the distribution.
2020+# 3. The name of the author may not be used to endorse or promote products
2121+# derived from this software without specific prior written permission.
2222+#
2323+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR
2424+# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
2525+# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
2626+# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
2727+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
2828+# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
2929+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
3030+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
3131+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
3232+# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
3333+#
3434+3535+require "rubygems"
3636+require "rbosa"
3737+3838+if !ARGV[1]
3939+ puts "usage: #{$0} <itunes playlist> <destination directory>"
4040+ exit
4141+end
4242+4343+playlist = ARGV[0]
4444+4545+if Dir[ARGV[1]].any?
4646+ destdir = ARGV[1]
4747+4848+ if !destdir.match(/\/$/)
4949+ destdir += "/"
5050+ end
5151+else
5252+ puts "error: directory \"#{destdir}\" does not exist, exiting"
5353+ exit
5454+end
5555+5656+print "querying itunes for playlist \"#{playlist}\"... "
5757+5858+# disable a stupid xml deprecation warning
5959+$VERBOSE = nil
6060+itunes = OSA.app("iTunes")
6161+6262+itpl = itunes.sources.select{|s| s.name == "Library" }.first.
6363+ user_playlists.select{|p| p.name.downcase == playlist.downcase }.first
6464+6565+if !itpl
6666+ puts "could not locate, exiting"
6767+ exit
6868+end
6969+7070+tracks = itpl.file_tracks.map{|t| t.location }
7171+7272+puts "found #{tracks.length} track#{tracks.length == 1 ? '' : 's'}."
7373+7474+# figure out where all of them are stored by checking for the greatest common
7575+# directory of every track
7676+gcd = ""
7777+(1 .. tracks.map{|t| t.length }.max).each do |s|
7878+ piece = tracks[0][0 .. s - 1]
7979+8080+ ok = true
8181+ tracks.each do |t|
8282+ if t[0 .. s - 1] != piece
8383+ ok = false
8484+ end
8585+ end
8686+8787+ if ok
8888+ gcd = piece
8989+ else
9090+ break
9191+ end
9292+end
9393+9494+# setup work dir
9595+td = `mktemp -d /tmp/itunesrsync.XXXXX`.strip
9696+9797+# mirror directory structure and create symlinks
9898+print "linking files under #{td}/... "
9999+100100+tracks.each do |t|
101101+ shortpath = t[gcd.length .. t.length - 1]
102102+ tmppath = "#{td}/#{shortpath}"
103103+104104+ if !Dir[File.dirname(tmppath)].any?
105105+ system("mkdir", "-p", File.dirname(tmppath))
106106+ end
107107+108108+ system("ln", "-s", t, tmppath)
109109+end
110110+111111+puts "done."
112112+113113+# times don't ever seem to match up, so only check size
114114+puts "rsyncing to #{destdir}... "
115115+system("rsync", "-Lrv", "--size-only", "--delete", "#{td}/", destdir)
116116+117117+print "cleaning up... "
118118+system("rm", "-rf", td)
119119+puts "done."