Arch Linux Brasil Repo Web View

Comecei a trabalhar numa idéia de criar uma sequência de scripts, que gerariam uma espécie de relatório, em html, das atividades no repositório Arch Linux Brasil.

A idéia surgiu quando eu resolvi ver o que existia no Repo Brasil, e sinceramente me incomodou ficar vendo as pastas em ftp. Porque não estão criar uma página com esse conteúdo?

Sem a pretensão de criar algo grande, comecei a trabalhar. E o resultado, até agora, é esse: http://danielbalieiro.archlinux-br.org/

Não sei se vou terminar. Sinceramente já me dou por satisfeito :) .  MASSSS resolvi colar aqui o código, para caso alguem queira brincar com isso.

O código usa as gem´s:

libarchive_ruby
sqlite3-ruby

init.rb:

require "downloader.rb"
require "archiver.rb"
require "classes.rb"
require "database.rb"
require "htmlmaker.rb"

# Chama o Downloader.
# Retonar um Hash contendo:
# 	key: repositório
# 	value: caminho do arquivo no disco (relativo)
def download
	downloader = Downloader.new("http://repo.archlinux-br.org/", ["i686", "x86_64"], "archlinuxbr.db.tar.gz")
	downloader.start
end

#
# Extrai os dados dos pacotes do arquivo do repositório
# Recebe:
#	key: repositorio
#	value: caminho do arquivo no disco (relativo)
#
def extract(key, value)
	puts("\nIniciando arquivador para o respositório: #{key}")
	archiver = Archiver.new(key, value)
	archiver.start
end

# Insere os repositórios e pacotes no banco de dados
def put_database(packages_by_repository)
	puts("\nAlimentando banco de dados...")
	db_name = "archlinux.db"
	d = Database.new(db_name)
	d.write(packages_by_repository)
	d.close
	puts("Banco de dados pronto!")
	db_name
end

def generate_html(db_name)
	puts("\nGerando relatório em HTML")
	html = HtmlMaker.new(db_name)
	html.start
	puts("Relatório HTML gerado!");
end

# Método principal, efetua a chamada dos demais metódos
def execute
	# hash com os pacotes por repositorio.
	# Exemplo:
	#	key = repo.archlinux-br.org-i686
	# 	value: array de Package.class
	packages_by_repository = {}
	begin
		download.each do |key, value|
			packages_by_repository[key] = extract(key, value)
		end
		db_name = put_database packages_by_repository
		generate_html db_name
	rescue Exception => e
		puts("\n!! ERRO: #{e} (#{e.class})!")
		puts("!! Detalhes: ")
		puts("\t"+e.backtrace.join("\n\t"))
	end
end

# Chamada inicial
execute

htmlmaker.rb:

require 'ftools'
require 'iconv'

class HtmlMaker
	def initialize(db_name)
		@dist_dir = 'C:\\java\\Apache2.2\\htdocs'
		@db = Database.new(db_name)
		@repositories = @db.find_repositories
	end

	def start
		index
		@db.close
	end

	def index
		cd = Iconv.new('utf-8', 'iso-8859-1')
		File.open(@dist_dir + File::SEPARATOR + 'index.html', 'w') do |file|
			file.puts(cd.iconv('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1-strict.dtd ">'))
			file.puts(cd.iconv('<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">'))
			file.puts(cd.iconv(head))
			file.puts(cd.iconv(body))
			file.puts(cd.iconv('</html>'))
		end
	end	

	def body
		body = '<body>'
		body += head_container
		body += content
		body += foot
		body += '</body>'
	end

	def foot
		ft = '<div class="foot">'
	    ft += ' Copyright &amp;copy; 2002-2008 <a href="mailto:jvinet@zeroflux.org" title="contact Judd Vinet">Judd Vinet</a> e <a href="mailto:aaron@archlinux.org" title="contact Aaron Griffin">Aaron Griffin</a>.<br />'
	    ft += '     O nome e o logo Arch Linux são marcas reconhecidas. Alguns direitos reservados.'
		ft += '</div>'
		ft
	end

	def content
		content = ' <div id="content"> '
		content += content_right
		content += content_left
		content += ' </div> '
		content
	end

	def content_left
		cl = ' <div class="left">'
		cl += '		<div id="about" class="box">'
		cl += '			<h2>Relatório de Atividades no Repositório!</h2>'
		cl += '				<p>O objetivo desse projeto é prover ao usuário do repositório do Arch Linux do Brasil, uma forma simples, rápida e clara de visualizar as modificações no repositório nacional.</p>'
		cl += '		</div>'
		cl += '		<br /><br />'
		cl += '	<div style="float:right;position:relative;bottom:-25px"></div>'
		cl += '	'

		@db.find_repositories.each do |repo|
			cl += '	<h2 class="title">Últimas Atualizações no Repositório: '+repo.arch+'</h2>'
			cl += '	<div>'
			cl += '		<table width="100%">'

			cl += '			<tr>'
			cl += '				<th align="left">Nome</td>'
			cl += '				<th align="left">Versão</td>'
			cl += '				<th align="right">Data</td>'
			cl += '			</tr>'

			@db.last_packages_by_repository(repo.id).each do |package|
				cl += '			<tr>'
				cl += '				<td align="left">'+package.name+'</td>'
				cl += '				<td align="left">'+package.version+'</td>'
				cl += '				<td align="right">'+package.date.strftime("%d / %m / %Y")+'</td>'
				cl += '			</tr>'
			end

			cl += '		</table> '
			cl += '		<br /><br />'
			cl += '	</div>'
		end

		cl += '</div> '
		cl
	end

	def content_right
		cr = ' <div class="right"> '
		cr += ' 	<div id="updates">'
		cr += ' 		<table width="100%">'
		cr += ' 			<tr>'
		cr += ' 				<td><h3>Atualizações Recentes</h3></td>'
		cr += ' 				<td style="vertical-align:top;text-align:right">'
		cr += ' 					<a href="/feeds/packages.xml">'
		cr += ' 						<img src="/media/rss.png" alt="RSS Feed" />'
		cr += ' 					</a>'
		cr += ' 				</td>'
		cr += ' 			</tr>'

		@db.last_packages.each do |package|
			cr += ' 			<tr>'
			cr += ' 				<td>'
			cr += ' 					<a href="'+package.url+'">'
			cr += package.name
			cr += ' 					</a>'
			cr += ' 				</td>'
			cr += ' 				<td>'+package.arch+'</td>'
			cr += ' 			</tr>'
		end

		cr += ' 		</table>'
		cr += ' 	</div>'
		cr += ' </div> '
		cr
	end

	def head_container
		hc = '<div id="head_container"> '
        hc += '   <div id="title">'
        hc += '      <div id="logo">'
		hc += '		<h1 id="archtitle">'
		hc += '			<a href="/" title="Arch Linux (Home)">Arch Linux</a>'
		hc += '		</h1>'
		hc += '	  </div>'
        hc += '    </div>'
        hc += '    <div id="main_nav">'
        hc += '        <ul>'
		hc += '			<li><a href="http://archlinux-br.org">Arch Linux Brasil</a></li>'
        hc += '            <li><a href="http://github.com">Código Fonte</a></li>'
        hc += '            <li class="selected"><a href="/">Site</a></li>'
        hc += '        </ul>'
        hc += '    </div>'
		hc += '    <div id="brdmenu" class="inbox">'
        hc += '        Página gerada em: '+Time.now.strftime("%d / %m / %Y")
        hc += '    </div>'
        hc += '</div>'
		hc
	end

	def head
		head =  ' <head> '
		head += ' <title>Arch Linux Brasil - Relatório de Atividades no Repositório</title>'
		head += ' <meta http-equiv="content-type" content="text/html; charset=utf-8" />'
		head += ' <link rel="stylesheet" href="/media/default.css" />'
		head += ' <link rel="icon" href="/media/favicon.ico" type="image/x-icon" />'
		head += ' <link rel="shortcut icon" href="/media/favicon.ico" type="image/x-icon" />'
		head += ' <link rel="alternate" type="application/rss+xml" title="Arch Linux Brasil - Atualizações de Pacotes" href="/feeds/packages.xml" />'
		head += ' </head> '
		head
	end
end

downloader.rb:

require 'net/http'
require 'ftools'

#
# Classe responsável por efetuar o download do arquivo "db" do repositório
#
class Downloader
	# Construtor
	# Parametros:
	#	url: caminho do repositório
	#	repo: tipo de repositório (exemplo: i686)
	#	file_name: nome do arquivo que vai ser feito o download
	def initialize(url, repo, file_name)
		@url 		= URI.parse(url)
		@file_name 	= file_name
		@repo 		= repo
	end

	# Método principal de negócio, chamado pelo init.rb
	def start
		fileahash = {}
		puts("Iniciando download... ")
		puts("\tHost: " + @url.host.to_s)
		puts("\tNome do arquivo: " + @file_name)
		@repo.each do |repo|
			puts("\tRepositório: " + repo)

			Net::HTTP.start(@url.host) do |http|
				resp 		= http.get("/" + repo + "/" + @file_name)
				file_path 	= real_path(@url.host.to_s, repo) + File::SEPARATOR + @file_name
				fileahash[@url.host.to_s + '/' + repo] = file_path
				open(file_path, "wb") do |file|
					file.write(resp.body)
				end
			end
		end
		puts("Download concluído!")
		fileahash
	end

	# Método para criar as pastas necessárias à organização do download
	def real_path(host, repo)
		d = 'download' + File::SEPARATOR + host + File::SEPARATOR + repo + File::SEPARATOR
		if ! File.exist? d
			File.makedirs d
		end
		d
	end
end

database.rb:

require 'sqlite3'
require 'ftools'

class Database
	def initialize(db_name)
		@db = nil

		if ! File.exist? db_name
			@db = SQLite3::Database.new(db_name)
			create_tables.each do |sql|
				@db.execute(sql)
			end
		end

		@db = SQLite3::Database.new(db_name) if @db == nil
	end

	def close
		@db.close
	end

	def insert_repository(repo)
		id = 0
		host = repo.split('/')[0]
		arch = repo.split('/')[1]
		r = @db.execute("select * from repositories where host = '#{host}' and arch = '#{arch}';")
		if r.empty?
			id = generate_index('repositories')
			sql = "insert into repositories(id, description, host, arch) "
			sql += " values (#{id}, '#{repo}', '#{host}', '#{arch}' );"
			@db.execute(sql)
		elsif r != nil
			r.each do |row|
				id = row[0]
			end
		end

		id.to_i
	end

	def insert_package(package, repository_id)
		sql = " select * from packages where name = '#{package.name}' "
		sql += " and version = '#{package.version}' "
		sql += " and arch = '#{package.arch}' "
		r = @db.execute(sql)	

		if r.empty?
			filename = ''
			filename = package.filename.tr("'", '') if ! package.filename.nil?

			name = ''
			name = package.name.tr("'", '') if ! package.name.nil?

			desc = ''
			desc = package.desc.tr("'", '') if ! package.desc.nil? 

			packager = ''
			packager = package.packager.tr("'", '') if ! package.packager.nil?

			url = ''
			url = package.url.tr("'", '') if ! package.url.nil?

			id = generate_index('packages')
			sql = "insert into packages ( "
			sql += " id, repository_id, filename, name, version, desc, md5sum, "
			sql += " url, arch, builddate, packager, repository"
			sql += " ) "
			sql += " values ( "
			sql += " #{id}, #{repository_id}, '#{filename}', '#{name}', "
			sql += " '#{package.version}', '#{desc}', '#{package.md5sum}', "
			sql += " '#{url}', '#{package.arch}', '#{package.date}', "
			sql += " '#{packager}', '#{package.repository}' "
			sql += " ) ;"
			@db.execute(sql)
		end
	end

	def write(packages_by_repository)
		packages_by_repository.each do |key, values|
			puts("\n\tAlimentando Repositório: #{key}")
			repository_id = insert_repository(key)
			values.each do |package|
				puts("\t\tAlimentando pacote: #{package.name}")
				insert_package(package, repository_id)
			end
		end
	end

	def generate_index(table)
		index = 0
		@db.execute("select max(id) from #{table};") do |row|
			index = row[0]
		end

		return index.to_i + 1
	end

	def find_repositories
		repos = []
		sql = 'select id, description, host, arch from repositories order by host;'
		@db.execute(sql) do |row|
			repo = Repository.new
			repo.id = row[0]
			repo.description = row[1]
			repo.host = row[2]
			repo.arch = row[3]
			repos << repo
		end
		repos
	end

	def row_to_package(row)
		p = Package.new
		p.id = row[0]
		p.filename = row[1]
		p.name = row[2]
		p.version = row[3]
		p.desc = row[4]
		p.md5sum = row[5]
		p.url = row[6]
		p.arch = row[7]
		p.date = Time.at(row[8].to_i)
		# p.date = Time.parse(row[8])
		p.packager = row[9]
		p.repository = row[10]
		p
	end

	def last_packages_by_repository(repository_id)
		packages = []
		sql = 'select id, '
		sql += 'filename, '
		sql += 'name, '
		sql += 'version, '
		sql += 'desc, '
		sql += 'md5sum, '
		sql += 'url, '
		sql += 'arch, '
		sql += 'builddate, '
		sql += 'packager, '
		sql += 'repository '
		sql += ' from packages '
		sql += ' where repository_id = '+repository_id
		sql += ' order by builddate desc '
		sql +=  ' limit 0, 5; '

		@db.execute(sql) do |row|
			packages << row_to_package(row)
		end		

		packages
	end

	def last_packages
		packages = []
		sql = 'select id, '
		sql += 'filename, '
		sql += 'name, '
		sql += 'version, '
		sql += 'desc, '
		sql += 'md5sum, '
		sql += 'url, '
		sql += 'arch, '
		sql += 'builddate, '
		sql += 'packager, '
		sql += 'repository '
		sql += ' from packages '
		sql += ' order by builddate desc '
		sql +=  ' limit 0, 28; '

		@db.execute(sql) do |row|
			packages << row_to_package(row)
		end		

		packages
	end

	def create_tables
		sqls = []
		sql = ' create table repositories ( '
		sql += ' id int(10) constraint pk_repositories primary key,'
		sql += ' description varchar(100),'
		sql += ' host varchar(100) not null, '
		sql += ' arch varchar(10) not null'
		sql += ' );'
		sqls << sql

		sql = '  create table packages ('
		sql += ' 	id int(10) constraint pk_packages primary key,'
		sql += ' 	repository_id int(10),'
		sql += ' 	filename varchar(100),'
		sql += ' 	name varchar(70),'
		sql += ' 	version varchar(20),'
		sql += ' 	desc varchar(1000),'
		sql += ' 	md5sum varchar(100),'
		sql += ' 	url varchar(100),'
		sql += ' 	arch varchar(10),'
		sql += ' 	builddate int(20),'
		sql += ' 	packager varchar(100),'
		sql += ' 	repository varchar(100)	'
		sql += ' );'
		sqls << sql
		sqls
	end
end

classes.rb:

# Classe que representa uma Package
class Package
	attr_accessor :filename, :name, :version, :desc, :md5sum, :url, :arch, :date, :packager, :repository, :id

	def to_s
		s = "Package: repository=(#{repository}) filename=(#{filename}) name=(#{name}) version =(#{version}) "
		s += "desc=(#{desc}) md5sum=(#{md5sum}) url=(#{url}) arch=(#{arch}) "
		s += "date=(#{date}) packager=(#{packager}) id=(#{id})"
	end
end

# Classe que representa um Repositorio
class Repository
	attr_accessor :id, :description, :host, :arch

	def to_s
		s = "Repository: id=(#{id}) description=(#{description}) host=(#{host}) arch=(#{arch})"
	end
end

archiver.rb:

require 'libarchive_ruby'
require 'ftools'
require "classes.rb"
require 'time'

#
# Classe responsável por ler o arquivo "db" e gerar classes Package
#
class Archiver
	# Construtor
	# Parametros:
	#	key: repositorio
	# 	value: arquivo a ser analisado
	def initialize(key, value)
		@file = value
		@repo = key
	end

	# Converte as informações do arquivo 'desc', dentro do arquivo "db" e devolve array de string.
	# Cada linha é uma entrada do array
	def convert(data)
		array = []
		data.each_line do |line|
			if ! line.lstrip.empty?
				array << line[0...-1]
			end
		end
		array << '%REPOSITORY%'
		array << @repo
		array
	end

	# Gera um "map" com as informações do arquivo a ser analisado.
	#	Retorno:
	#	key: nome do pacote
	#	value: dados em string do pacote (array)
	def generate_map
		map = {}
		Archive.read_open_filename(@file) do |ar|
			while entry = ar.next_header
				name = entry.pathname
				data = ar.read_data

				if name[-5,5] == (File::SEPARATOR + 'desc')
					puts("\tLendo pacote: #{name.split('/')[0]}")
					map[name.split('/')[0]] = convert(data)
				end
			end
		end
		map
	end

	# Converte a string (array) de dados do pacote em um objeto Package
	def converto_to_package(data_array)
		p = Package.new
		i = 0
		data_array.each do |data|
			i += 1
			if data.include? '%FILENAME%'
				p.filename = data_array[i]
			elsif data.include? '%NAME%'
				p.name = data_array[i]
			elsif data.include? '%VERSION%'
				p.version = data_array[i]
			elsif data.include? '%DESC%'
				p.desc = data_array[i]
			elsif data.include? '%MD5SUM%'
				p.md5sum = data_array[i]
			elsif data.include? '%URL%'
				p.url = data_array[i]
			elsif data.include? '%ARCH%'
				p.arch = data_array[i]
			elsif data.include? '%BUILDDATE%'
				p.date = data_array[i]
			#Time.at(data_array[i].to_i)
			elsif data.include? '%PACKAGER%'
				p.packager = data_array[i]
			elsif data.include? '%REPOSITORY%'
				p.repository = data_array[i]
			end
		end
		p
	end

	# Método principal da classe, efetua a chamada dos demais
	def start
		packages = []
		generate_map.each do |key, value|
			packages << converto_to_package(value)
		end
		puts("Arquivador concluído!")
		packages
	end
end

t+

Share