require 'open-uri'
require 'fileutils'
class Updater
def zip_extract(file,target_dir)
system(
"unzip", "-q", file, "-d", target_dir
) or raise "Error extracting (#{$?}): unzip #{file.inspect} -d #{target_dir.inspect}"
end
def initialize(wow_path,opts,listing_url="http://files.wowace.com/")
@opts=opts
@wow_path = wow_path
@addons_dir = File.join(@wow_path,"Interface","Addons")
@addons_download_dir = File.join(@wow_path,"Interface","ace_updater")
@listing_url=listing_url
end
def mod_index
@mod_index ||= begin
puts "Fetching Ace mod index from #{@listing_url}..."
m={}
open(@listing_url) {|f|
f.read
}.scan(
%r{<td>\s*<a href="([^"]+)">([^>]+)</a>\s*</td>\s*<td>r(\d+)</td>}
) {|url,mod,revision|
m[mod] = [revision.to_i,url]
}
puts "#{m.length} entries found."
puts
m
end
end
def each_installed_mod
Dir.foreach(@addons_dir) {|addon|
next if ["..","."].include? addon
yield addon,installed_revision(addon)
}
end
def installed_revision(addon)
addon_dir = File.join(@addons_dir,addon)
x = Dir.glob(File.join(addon_dir,"Changelog-#{addon}-r*.xml"))
return nil if x.empty?
x[0] =~ /Changelog-#{addon}-r(\d+)\.xml/
$1.to_i
end
def get_changes(changelog,local_revision)
changes=[]
current_rev = "?????"
reading_changes = false
File.open(changelog) {|f|
f.read
}.each_line{|l|
l.strip!
if l =~ /^r(\d+) \|/
current_rev = $1.to_i
break if current_rev <= local_revision
elsif l=~/^-+$/
reading_changes = false
elsif l=~/^$/
reading_changes = true
else
changes << [current_rev,l] if reading_changes
end
}
changes.stable_sort_by {|r,t| r}
end
def update_mods(list=nil)
list=nil if list.empty?
remote_mods = mod_index()
puts "Scanning #{list ? 'chosen' : 'all'} mods for updates..."
jobs = []
each_installed_mod {|mod,revision|
next if revision.nil?
next if list and not list.include?(mod)
remote_revision,url = remote_mods[mod]
if remote_revision.nil? or remote_revision < revision
nonfatal("#{mod}: local is newer than remote (r#{revision.inspect} vs r#{remote_revision.inspect}). Skipping.")
elsif remote_revision > revision
jobs << [mod,revision,remote_revision,url]
end
list.delete mod if list
}
list.each {|x|
$stderr.puts "WARNING: #{x} is not an installed Ace addon, skipping update."
} if list
if jobs.length == 0
puts "#{list ? 'Chosen' : 'All'} mods up to date."
else
install_mods(jobs)
end
end
def add_mods(modlist)
remote_mods = mod_index()
jobs = modlist.map {|mod|
if remote_mods.key?(mod)
rev,url = remote_mods[mod]
[mod,nil,rev,url]
else
$stderr.puts "WARNING: #{mod} not found in mod index, skipping"
end
}.compact
install_mods(jobs)
end
def delete_mods(modlist)
modlist = modlist.map {|x|
next if ['.','..'].include?(x)
rev = installed_revision(x)
if rev
[x,rev]
else
nonfatal("#{x} is not an installed Ace addon, skipping")
end
}.compact
modlist.each_with_index {|(mod,rev),i|
status(i,modlist.length,"delete",mod,rev)
FileUtils.remove_dir(File.join(@addons_dir,mod))
}
end
def install_mods(jobs)
FileUtils.remove_dir(@addons_download_dir) if File.directory?(@addons_download_dir)
Dir.mkdir(@addons_download_dir)
jobs.each_with_index{|(mod,revision,remote_revision,url),index|
status(index, jobs.length, (revision ? 'update' : 'add'), mod, *([revision,remote_revision].compact))
zipfile = File.join(@addons_download_dir,"#{mod}.zip")
File.open(zipfile,'w') {|f|
open(url) {|data|
FileUtils.copy_stream(data,f)
}
}
zip_extract(zipfile,@addons_download_dir)
expected_output_dir = File.join(@addons_download_dir,mod)
destination_dir = File.join(@addons_dir,mod)
backup_dir = "#{destination_dir}.backup"
unless File.directory?(expected_output_dir)
nonfatal("#{expected_output_dir} not created as expected")
nonfatal("Check #{zipfile}")
nonfatal("Skipping installation of #{mod}")
end
changelog = File.join(expected_output_dir,"Changelog-#{mod}-r#{remote_revision}.xml")
FileUtils.chmod 0644,changelog
if revision and @opts.include?('-v')
changes = get_changes(changelog,revision)
changes.each {|revision,text|
puts(" * [r%d] %s" % [revision,text])
}
end
FileUtils.remove(zipfile)
FileUtils.mv(destination_dir,backup_dir) if revision
FileUtils.mv(expected_output_dir,destination_dir)
FileUtils.remove_dir(backup_dir) if revision
}
FileUtils.remove_dir(@addons_download_dir)
end
def status(sequence,total,action,mod,rev1,rev2=nil)
rev2 = " -> r#{rev2}" if rev2
puts("[%2d/%2d] %6s %16s r%d%s" % [sequence+1,total,action,mod,rev1,rev2])
end
def nonfatal(s)
$stderr.puts("WARNING: #{s}")
end
end
class Array
def stable_sort_by
n = 0
sort_by {|x| n+= 1; [yield(x), n]}
end
end
if __FILE__ == $0
opts,args = ARGV.partition {|text| text[0] == ?- }
args << 'update' if args.empty?
command = args.shift.downcase
updater = Updater.new(File.dirname(__FILE__),opts)
case command
when 'update'
updater.update_mods(args)
when 'add'
raise "Must specify mods to add" if args.empty?
updater.add_mods(args)
when 'delete'
raise "Must specify mods to delete" if args.empty?
updater.delete_mods(args)
else
puts "Valid commands: update add delete"
end
end