User:Estemi/History Of WineAppDB Generator
Jump to navigation
Jump to search
I created and maintain a script-generated file, History Of WineAppDB.gif. It takes a long time and is very cumbersome. Given that I could lose interest in the file or die horribly, I offer the script here.
Run it with an internet connection in a directory with a font file 'font.ttf' and it should work perfectly. Should.
#!/usr/bin/env python
# WineStats 0.5.1 -- Graphs WineAppDB test results from .9 to the present
# TODO: more graphing techniques; less like spaghetti
"""
It takes forever to do the whole thing at once. You might want to do the
first section and the rest seperately. Run twice with the --nograph and the
--nodownload flags each to accomplish this. If your download fails, use the
--resume <int> flag with the appID where the script broke off.
If Imagemagick is installed on your computer, the script will compose an
animated gif automatically. Otherwise, used GIMP to open the imgs folder
as layers and save as an animated gif.
See http://commons.wikimedia.org/wiki/File:History_Of_WineAppDB.gif for more
information, or its uploader's commons page if you have questions.
"""
#-----------------------------------------------------------------------------
# Assemble resources
#-----------------------------------------------------------------------------
# Modules
import os
import sys
import glob
import Image
import ImageDraw
import ImageFont
import time
import urllib
# Global variables
appdb = 'http://appdb.winehq.org/objectManager.php?'
app_url = appdb + 'sClass=application&iId='
show_all_macro = 'bShowAll=true&bIsQueue=false&bIsRejected=false&'
last_app_macro = 'sClass=application&iPage=1&sOrderBy=appId&bAscending=false'
download_data = draw_graph = True
start_time = end_time = time.ctime()
start_app = end_app = 0
apps_dir = 'apps'
imgs_dir = 'imgs'
font_file = 'font.ttf'
resume = False
make_gif = True
failed = []
# Command line arguments
args = sys.argv
args.remove(__file__)
if '-h' in args or '--help' in args:
print('Usage: python ' + __file__ + ' [ options ... ]')
print('Collects data from Wine\'s AppDB (http://appdb.winehq.org/) and '
+ 'graphs them.')
print('\nOptions:')
print('\t--nodownload\t\tDo not download new data.')
print('\t--nograph\t\tDo not draw graphs after download.')
print('\t--resume <int>\t\tStart download at certain app (default: 0).')
print('\t--stopat <int>\t\tStop download at certain app (default: last).')
print('\t-h or --help\t\tPrint this message.')
sys.exit()
if '--resume' in args:
arg_index = args.index('--resume')
int_index = arg_index + 1
resume = True
try:
start_app = int(args[int_index])
del args[int_index]
del args[arg_index]
except:
print(__file__ + ': use --resume <int> where <int> is an application '
+ 'id in AppDB.')
print('Try python ' + __file__ + ' --help for more information.')
sys.exit()
if '--stopat' in args:
arg_index = args.index('--stopat')
int_index = arg_index + 1
try:
end_app = int(args[int_index])
del args[int_index]
del args[arg_index]
except:
print(__file__ + ': use --stopat <int> where <int> is an '
+ 'application id in AppDB.')
print('Try python ' + __file__ + ' --help for more information.')
sys.exit()
if '--nodownload' in args:
arg_index = args.index('--nodownload')
del args[arg_index]
download_data = False
if '--nograph' in args:
arg_index = args.index('--nograph')
del args[arg_index]
draw_graph = False
if len(args) > 0:
print(__file__ + ': Unkown or malformatted option ' + args[0])
print('Try python ' + __file__ + ' --help for more information.')
sys.exit()
#-----------------------------------------------------------------------------
# Assert that the script is running in a valid environment
#-----------------------------------------------------------------------------
if draw_graph:
assert os.path.isfile(font_file), 'Must have %s in directory.' % font_file
assert not os.path.exists(imgs_dir), '%s directory exists.' % imgs_dir
if download_data:
if not resume:
assert not os.path.exists(apps_dir), '%s directory exists.' % apps_dir
assert end_app == 0 or end_app > start_app, 'No app data to fetch.'
assert download_data or draw_graph, 'Neither getting data or rendering it.'
# Test if imagemagic available for GIF animation
try:
if draw_graph and os.system('if which convert > /dev/null; then return 1; fi') == 0:
raise Exception
except:
print 'Imagemagick not available. Will not compile animated GIF file.'
make_gif = False
#-----------------------------------------------------------------------------
# Fetch app data from server, consolidate, and save in apps directory
#-----------------------------------------------------------------------------
def analyse_result(result):
tags = result.split('><')
del tags[:7] #first seven are trash
if len(tags) < 5:
return ['null', 'null', 'null']
date = tags[0].replace('td>', '').replace('</td', '')
version = tags[1].replace('td>', '').replace('. </td',
'').replace(' </td', '')
rating = tags[4].replace('td>', '').replace(' </td', '')
return [date, version, rating]
def get_test_results(url):
html = get_html(url)
for line in html:
if line.find('<table class="historyTable"') > -1:
break
if line.find('</thead><tbody>') > -1:
data = line.split('</thead><tbody>')[1]
elif line.find('</thead>') > -1:
data = line.split('</thead>')[1]
else:
return None
data = data.split('</tr>')
del data[len(data)-1] #useless
testResults = []
for datum in data:
testResults.append(analyse_result(datum))
return testResults
def get_versions(html):
for line in html:
if line.find('Class=version') > -1:
break
parts = line.split('onclick="DoNav(')
del parts[0]
urls = []
for version in range(len(parts)):
urlEnd = parts[version].find(')')
url = parts[version][0:urlEnd].replace("'", "").replace('amp;',
'').replace('objectManager.php?', 'objectManager.php?'
+ show_all_macro)
urls.append(url)
return urls
def make_apps(startApp, endApp):
if not resume:
os.mkdir(apps_dir)
print('This is gonna take awhile. Go out for a movie or something.')
for appID in range(startApp, endApp):
if appID % 5 == 0:
print('%s out of %s data retrieved' % (str(appID), str(endApp)))
try:
make_app(appID)
except:
print('Error loading application ' + str(appID))
failed.append(appID)
print('Download and write complete.')
end_time = time.ctime()
if len(failed) > 0:
print('The following applications failed:')
for appID in failed:
print(appID)
def make_app(appID):
html = get_html(app_url, appID)
name = get_name(html)
if is_valid_app(name):
versionURLs = get_versions(html)
versionsData = get_version_data(versionURLs)
make_app_file(name, versionsData)
def make_app_file(name, versionsData):
savePath = os.path.join(apps_dir, name+'.txt')
file = open(savePath, 'w')
file.write(name + '\n\n')
for version in versionsData:
file.write('version[\n')
write_version(version, file)
file.write(']\n\n')
file.close()
def write_version(version, file):
for testResult in version:
file.write(testResult[0] + ',' + testResult[1] + ','
+ testResult[2] + '\n')
def get_version_data(versionURLs):
data = []
for url in versionURLs:
testResults = get_test_results(url)
if testResults != None:
data.append(testResults)
return data
def get_html(url, tailID=None):
if tailID is not None:
url = url + str(tailID+1)
return urllib.urlopen(url)
def get_name(html):
for line in html:
if line.find('<title>') > -1:
title = line.replace('<title>WineHQ', '').replace('</title>\n',
'').replace('-', '').replace('/', '').strip()
return title
def is_valid_app(name):
if name == '':
return False
return True
def get_last_app():
url = appdb + last_app_macro
html = get_html(url)
for line in html:
if line.find('<td>Description</td></tr>') > -1:
appid = line.split('<td>Description</td></tr>')[1].split(
'</td>')[1].replace('<td>', '')
return int(appid)
if download_data:
if end_app == 0: end_app = get_last_app()
make_apps(start_app, end_app)
else:
print('Skipping download of application data....')
#-----------------------------------------------------------------------------
# Create an object hierachy with the data
#-----------------------------------------------------------------------------
class AppDB():
def __init__(self):
self.apps = []
for infile in glob.glob(os.path.join(apps_dir, '*.txt')):
app = App(infile)
self.apps.append(app)
self.wineVersions = self.get_wine_versions()
def get_wine_versions(self):
wineVersions = []
for app in self.apps:
for wineVersion in app.wineVersions:
if wineVersion not in wineVersions:
wineVersions.append(wineVersion)
wineVersions.sort()
return wineVersions
def create_visual(self):
os.mkdir(imgs_dir)
self.logPath = self.make_report_log()
visuals = []
for version in self.wineVersions:
garbage = 0
bronze = 0
silver = 0
gold = 0
platinum = 0
for app in self.apps:
quality = app.get_quality_at(version, self.wineVersions)
if quality == "Garbage": garbage+=1
elif quality == "Bronze": bronze+=1
elif quality == "Silver": silver+=1
elif quality == "Gold": gold+=1
elif quality == "Platinum": platinum+=1
filename = os.path.join(imgs_dir, version + '.png')
create_visual(garbage, bronze, silver, gold, platinum, filename)
visuals.append(filename)
self.print_data(version, str(garbage), str(bronze), str(silver),
str(gold), str(platinum))
if make_gif:
visuals_string = ' '.join(visuals)
command = ('convert -delay 10 -loop 0 -dispose Background ' +
visuals_string + ' wine_animation.gif')
os.system(command)
def make_report_log(self):
logPath = 'appdb_retrieve_log.txt'
log = open(logPath, 'w')
if start_time == end_time:
log.write('Data retrieval time unknown. Graphs drawn at %s\n\n'
% start_time)
else:
log.write('Data retrieved from %s to %s\n\n'
% (start_time, end_time))
log.close()
return logPath
def print_data(self, ver, gar, bro, sil, gol, pla):
verString = '%s -- Pl:%s Go:%s Si:%s Br:%s Ga:%s' \
% (ver, pla, gol, sil, bro, gar)
print verString
log = open(self.logPath, 'a')
log.write(verString + '\n')
log.close()
class App():
def __init__(self, appFile):
self.name = os.path.split(appFile)[1].replace('.txt', '')
self.versions = self.make_versions(appFile)
self.wineVersions = self.get_wine_versions()
def make_versions(self, filename):
file = open(filename, 'r')
data = file.read()
file.seek(0)
versions = []
numVersions = data.count('version[\n')
versionsRead = 0
while versionsRead < numVersions:
line = file.readline()
if line == 'version[\n':
versionResults = []
while line != ']\n':
line = file.readline()
results = line.replace('\n', '').split(',')
if len(results) == 3:
versionResults.append(results)
versions.append(Version(versionResults))
versionsRead+=1
file.close()
return versions
def get_wine_versions(self):
wineVersions = []
for version in self.versions:
for wineVersion in version.wineVersions:
if wineVersion not in wineVersions:
wineVersions.append(wineVersion)
return wineVersions
def get_quality_at(self, wineVersion, allVersions):
reports = []
for version in self.versions:
quality = version.get_quality_at(wineVersion, allVersions)
if quality is not None:
reports.append(quality)
if len(reports) > 0:
score = self.average_qualities(reports)
return score
def average_qualities(self, qualities):
score = 0.0
for quality in qualities:
if quality == 'Garbage': score+=0
elif quality == 'Bronze': score+=1
elif quality == 'Silver': score+=2
elif quality == 'Gold': score+=3
elif quality == 'Platinum': score+=4
average = score / len(qualities)
if average < 0.5:
return "Garbage"
elif 0.5 <= average < 1.5:
return "Bronze"
elif 1.5 <= average < 2.5:
return "Silver"
elif 2.5 <= average < 3.5:
return "Gold"
elif average >= 3.5:
return "Platinum"
class Version():
def __init__(self, versionResults):
self.results = self.make_reports(versionResults)
self.wineVersions = self.get_wine_versions()
def make_reports(self, results):
testResults = []
for result in results:
#fix wine for sorting
subvers = result[1].split('.')
if len(subvers) > 2:
if int(subvers[2]) < 10:
subvers[2] = '0' + subvers[2]
result[1] = '%s.%s.%s' % tuple(subvers)
else:
# and remove non-release builds (eg 2384594) from test results
if len(subvers) < 2:
continue
testResults.append(Result(result[0], result[1], result[2]))
return testResults
def get_wine_versions(self):
wineVersions = []
for report in self.results:
wineVersion = report.wine
if wineVersion not in wineVersions:
wineVersions.append(wineVersion)
return wineVersions
def get_quality_at(self, wineVersion, allVersions):
searchIndex = allVersions.index(wineVersion)
for report in self.results:
reportIndex = allVersions.index(report.wine)
if reportIndex <= searchIndex and searchIndex - reportIndex < 16:
return report.score
class Result():
def __init__(self, date, wine, score):
self.date = date
self.wine = wine
self.score = score
appdb = AppDB()
#-----------------------------------------------------------------------------
# Create an image with data from object hierarchy
#-----------------------------------------------------------------------------
def create_visual(garbageApps, bronzeApps, silverApps, goldApps, platinumApps, saveFile):
imHeight = 500
imWidth = 750
garbageColor = '#999966'
bronzeColor = '#fcba0a'
silverColor = '#c0c0c0'
goldColor = '#fff600'
platinumColor = '#ececec'
myfont = ImageFont.truetype(font_file, 50)
im = Image.new('RGB', (imWidth, imHeight), color='#ffffff')
rectHeight = 75
draw = ImageDraw.Draw(im)
text = os.path.split(saveFile)[1].replace('.png', '')
draw.text([25,20], text, fill='#000000', font=myfont)
garbagex1 = imWidth/2 + garbageApps/3
garbagey1 = imHeight
garbagex2 = imWidth/2 - garbageApps/3
garbagey2 = imHeight-rectHeight
draw.rectangle([garbagex1, garbagey1, garbagex2, garbagey2],
fill=garbageColor, outline='#000000')
bronzex1 = imWidth/2 + bronzeApps/3
bronzey1 = imHeight - rectHeight
bronzex2 = imWidth/2 - bronzeApps/3
bronzey2 = imHeight - rectHeight*2
draw.rectangle([bronzex1, bronzey1, bronzex2, bronzey2],
fill=bronzeColor, outline='#000000')
silverx1 = imWidth/2 + silverApps/3
silvery1 = imHeight - rectHeight*2
silverx2 = imWidth/2 - silverApps/3
silvery2 = imHeight - rectHeight*3
draw.rectangle([silverx1, silvery1, silverx2, silvery2],
fill=silverColor, outline='#000000')
goldx1 = imWidth/2 + goldApps/3
goldy1 = imHeight - rectHeight*3
goldx2 = imWidth/2 - goldApps/3
goldy2 = imHeight - rectHeight*4
draw.rectangle([goldx1, goldy1, goldx2, goldy2],
fill=goldColor, outline='#000000')
platinumx1 = imWidth/2 + platinumApps/3
platinumy1 = imHeight - rectHeight*4
platinumx2 = imWidth/2 - platinumApps/3
platinumy2 = imHeight - rectHeight*5
draw.rectangle([platinumx1, platinumy1, platinumx2, platinumy2],
fill=platinumColor, outline='#000000')
del draw
im.save(saveFile)
appdb.create_visual()