1 2 #!/usr/local/bin/ruby 3 4 require 'open-uri' 5 require 'fileutils' 6 7 # should be placed inside WoW directory 8 # usage: 9 # ace_updater - updates all installed mods 10 # ace_updater update - updates all installed mods 11 # ace_updater update Omen Grid - updates selected mods 12 # ace_updater add Threat-1.0 - install a new mod 13 # ace_updater delete FuBar - uninstall an existing mod 14 # add the -v flag to an update to show changes from the changelog. 15 16 # You may need to adjust zip_extract for your system. 17 # file is the zip file to be extracted 18 # target_dir is the existing directory to extract into. 19 20 class Updater 21 # requirers some OS customisation, this WFM on Mac OS X 22 def zip_extract(file,target_dir) 23 system( 24 "unzip", "-q", file, "-d", target_dir 25 ) or raise "Error extracting (#{$?}): unzip #{file.inspect} -d #{target_dir.inspect}" 26 end 27 28 def initialize(wow_path,opts,listing_url="http://files.wowace.com/") 29 @opts=opts 30 @wow_path = wow_path 31 @addons_dir = File.join(@wow_path,"Interface","Addons") 32 @addons_download_dir = File.join(@wow_path,"Interface","ace_updater") 33 @listing_url=listing_url 34 end 35 36 def mod_index 37 @mod_index ||= begin 38 puts "Fetching Ace mod index from #{@listing_url}..." 39 m={} 40 open(@listing_url) {|f| 41 f.read 42 }.scan( 43 %r{<td>\s*<a href="([^"]+)">([^>]+)</a>\s*</td>\s*<td>r(\d+)</td>} 44 ) {|url,mod,revision| 45 m[mod] = [revision.to_i,url] 46 } 47 puts "#{m.length} entries found." 48 puts 49 m 50 end 51 end 52 53 def each_installed_mod 54 Dir.foreach(@addons_dir) {|addon| 55 next if ["..","."].include? addon 56 yield addon,installed_revision(addon) 57 } 58 end 59 60 def installed_revision(addon) 61 addon_dir = File.join(@addons_dir,addon) 62 x = Dir.glob(File.join(addon_dir,"Changelog-#{addon}-r*.xml")) 63 return nil if x.empty? 64 x[0] =~ /Changelog-#{addon}-r(\d+)\.xml/ 65 $1.to_i 66 end 67 68 def get_changes(changelog,local_revision) 69 changes=[] 70 current_rev = "?????" 71 reading_changes = false 72 File.open(changelog) {|f| 73 f.read 74 }.each_line{|l| 75 l.strip! 76 if l =~ /^r(\d+) \|/ 77 current_rev = $1.to_i 78 break if current_rev <= local_revision 79 elsif l=~/^-+$/ 80 reading_changes = false 81 elsif l=~/^$/ 82 reading_changes = true 83 else 84 changes << [current_rev,l] if reading_changes 85 end 86 } 87 changes.stable_sort_by {|r,t| r} 88 end 89 90 def update_mods(list=nil) 91 list=nil if list.empty? 92 93 remote_mods = mod_index() 94 95 puts "Scanning #{list ? 'chosen' : 'all'} mods for updates..." 96 jobs = [] 97 each_installed_mod {|mod,revision| 98 next if revision.nil? #non-ace 99 next if list and not list.include?(mod) #not on the list 100 101 remote_revision,url = remote_mods[mod] 102 if remote_revision.nil? or remote_revision < revision 103 nonfatal("#{mod}: local is newer than remote (r#{revision.inspect} vs r#{remote_revision.inspect}). Skipping.") 104 elsif remote_revision > revision 105 jobs << [mod,revision,remote_revision,url] 106 end 107 108 list.delete mod if list 109 } 110 list.each {|x| 111 $stderr.puts "WARNING: #{x} is not an installed Ace addon, skipping update." 112 } if list 113 114 if jobs.length == 0 115 puts "#{list ? 'Chosen' : 'All'} mods up to date." 116 else 117 install_mods(jobs) 118 end 119 end 120 121 def add_mods(modlist) 122 remote_mods = mod_index() 123 jobs = modlist.map {|mod| 124 if remote_mods.key?(mod) 125 rev,url = remote_mods[mod] 126 [mod,nil,rev,url] 127 else 128 $stderr.puts "WARNING: #{mod} not found in mod index, skipping" 129 end 130 }.compact 131 install_mods(jobs) 132 end 133 134 def delete_mods(modlist) 135 modlist = modlist.map {|x| 136 next if ['.','..'].include?(x) # this would be lame 137 rev = installed_revision(x) 138 if rev 139 [x,rev] 140 else 141 nonfatal("#{x} is not an installed Ace addon, skipping") 142 end 143 }.compact 144 145 modlist.each_with_index {|(mod,rev),i| 146 status(i,modlist.length,"delete",mod,rev) 147 FileUtils.remove_dir(File.join(@addons_dir,mod)) 148 } 149 end 150 151 def install_mods(jobs) 152 FileUtils.remove_dir(@addons_download_dir) if File.directory?(@addons_download_dir) 153 Dir.mkdir(@addons_download_dir) 154 jobs.each_with_index{|(mod,revision,remote_revision,url),index| 155 status(index, jobs.length, (revision ? 'update' : 'add'), mod, *([revision,remote_revision].compact)) 156 157 zipfile = File.join(@addons_download_dir,"#{mod}.zip") 158 159 File.open(zipfile,'w') {|f| 160 open(url) {|data| 161 FileUtils.copy_stream(data,f) 162 } 163 } 164 165 zip_extract(zipfile,@addons_download_dir) 166 expected_output_dir = File.join(@addons_download_dir,mod) 167 destination_dir = File.join(@addons_dir,mod) 168 backup_dir = "#{destination_dir}.backup" 169 170 unless File.directory?(expected_output_dir) 171 nonfatal("#{expected_output_dir} not created as expected") 172 nonfatal("Check #{zipfile}") 173 nonfatal("Skipping installation of #{mod}") 174 end 175 176 changelog = File.join(expected_output_dir,"Changelog-#{mod}-r#{remote_revision}.xml") 177 # for some reason these often get wrong privs 178 FileUtils.chmod 0644,changelog 179 if revision and @opts.include?('-v') # show changelog 180 changes = get_changes(changelog,revision) 181 changes.each {|revision,text| 182 puts(" * [r%d] %s" % [revision,text]) 183 } 184 end 185 186 FileUtils.remove(zipfile) 187 FileUtils.mv(destination_dir,backup_dir) if revision 188 FileUtils.mv(expected_output_dir,destination_dir) 189 FileUtils.remove_dir(backup_dir) if revision 190 } 191 FileUtils.remove_dir(@addons_download_dir) 192 end 193 194 def status(sequence,total,action,mod,rev1,rev2=nil) 195 rev2 = " -> r#{rev2}" if rev2 196 puts("[%2d/%2d] %6s %16s r%d%s" % [sequence+1,total,action,mod,rev1,rev2]) 197 end 198 199 def nonfatal(s) 200 $stderr.puts("WARNING: #{s}") 201 end 202 end 203 204 class Array 205 def stable_sort_by 206 n = 0 207 sort_by {|x| n+= 1; [yield(x), n]} 208 end 209 end 210 211 212 if __FILE__ == $0 213 opts,args = ARGV.partition {|text| text[0] == ?- } 214 args << 'update' if args.empty? 215 command = args.shift.downcase 216 217 updater = Updater.new(File.dirname(__FILE__),opts) 218 219 case command 220 when 'update' 221 updater.update_mods(args) 222 when 'add' 223 raise "Must specify mods to add" if args.empty? 224 updater.add_mods(args) 225 when 'delete' 226 raise "Must specify mods to delete" if args.empty? 227 updater.delete_mods(args) 228 else 229 puts "Valid commands: update add delete" 230 end 231 end 232
You need to create an account or log in to post comments to this site.