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

About this user

Sam McCall

« Newer Snippets
Older Snippets »
Showing 1-1 of 1 total  RSS 

Ace2 mod manager

#!/usr/local/bin/ruby

require 'open-uri'
require 'fileutils'

# should be placed inside WoW directory
# usage:
# ace_updater                  - updates all installed mods
# ace_updater update           - updates all installed mods
# ace_updater update Omen Grid - updates selected mods
# ace_updater add Threat-1.0   - install a new mod
# ace_updater delete FuBar     - uninstall an existing mod
# add the -v flag to an update to show changes from the changelog.

# You may need to adjust zip_extract for your system.
# file is the zip file to be extracted
# target_dir is the existing directory to extract into.

class Updater
	# requirers some OS customisation, this WFM on Mac OS X
	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? #non-ace
			next if list and not list.include?(mod) #not on the list

			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) # this would be lame
			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")
			# for some reason these often get wrong privs
			FileUtils.chmod 0644,changelog
			if revision and @opts.include?('-v') # show changelog
				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

« Newer Snippets
Older Snippets »
Showing 1-1 of 1 total  RSS