Never been to DZone Snippets before?

Snippets is a public source code repository. Easily build up your personal collection of code snippets, categorize them with tags / keywords, and share them with the world

Ace2 mod manager (See related posts)

   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.


Click here to browse all 5555 code snippets

Related Posts