xwords/xwords4/android/scripts/info.py
Eric House d55dd7e206 change format of downloadable dict info. Late checkin: Android code's
been counting on this for several days.
2014-06-03 07:43:47 -07:00

420 lines
14 KiB
Python
Executable file

#!/usr/bin/python
# Script meant to be installed on eehouse.org.
import logging, shelve, hashlib, sys, json, subprocess, glob, os
import mk_for_download, mygit
import xwconfig
from stat import ST_CTIME
try:
from mod_python import apache
apacheAvailable = True
except ImportError:
apacheAvailable = False
# constants that are also used in UpdateCheckReceiver.java
VERBOSE = False
k_NAME = 'name'
k_AVERS = 'avers'
k_GVERS = 'gvers'
k_INSTALLER = 'installer'
k_DEVOK = 'devOK'
k_APP = 'app'
k_DICTS = 'dicts'
k_XLATEINFO = 'xlatinfo'
k_CALLBACK = 'callback'
k_LOCALE = 'locale'
k_XLATPROTO = 'proto'
k_XLATEVERS = 'xlatevers'
k_STRINGSHASH = 'strings'
k_OLD = 'old'
k_NEW = 'new'
k_PAIRS = 'pairs'
k_LANG = 'lang'
k_MD5SUM = 'md5sum'
k_INDEX = 'index'
k_ISUM = 'isum'
k_SUCCESS = 'success'
k_URL = 'url'
k_SUMS = 'sums'
k_COUNT = 'count'
# Version for those sticking with RELEASES
k_REL_REV = 'android_beta_81'
# Version for those getting intermediate builds
k_DBG_REV = 'android_beta_81-29-gb776b7c'
k_DBG_REV = 'android_beta_81-34-g73a1083'
k_suffix = '.xwd'
k_filebase = "/var/www/"
k_apkDir = "xw4/android/"
k_shelfFile = k_filebase + 'xw4/info_shelf_2'
k_urlbase = "http://eehouse.org"
k_versions = { 'org.eehouse.android.xw4': {
'version' : 74,
k_AVERS : 74,
k_URL : k_apkDir + 'XWords4-release_' + k_REL_REV + '.apk',
},
}
# k_versions_dbg = { 'org.eehouse.android.xw4': {
# 'version' : 74,
# k_AVERS : 74,
# k_GVERS : k_DBG_REV,
# k_URL : k_apkDir + 'XWords4-release_' + k_DBG_REV + '.apk',
# },
# }
s_shelf = None
logging.basicConfig(level=logging.DEBUG
,format='%(asctime)s [[%(levelname)s]] %(message)s'
,datefmt='%d %b %y %H:%M'
,filename='/tmp/info_py.log')
# ,filemode='w')
# This seems to be required to prime the pump somehow.
# logging.debug( "loaded...." )
def getInternalSum( filePath ):
filePath = k_filebase + "and_wordlists/" + filePath
proc = subprocess.Popen(['/usr/bin/perl',
'--',
'/var/www/xw4/dawg2dict.pl',
'-get-sum',
'-dict', filePath ],
stdout = subprocess.PIPE,
stderr = subprocess.PIPE)
return proc.communicate()[0].strip()
def md5Checksums( sums, filePath ):
if not filePath.endswith(k_suffix): filePath += k_suffix
if filePath in sums:
result = sums[filePath]
else:
logging.debug( "opening %s" % (k_filebase + "and_wordlists/" + filePath))
try:
file = open( k_filebase + "and_wordlists/" + filePath, 'rb' )
md5 = hashlib.md5()
while True:
buffer = file.read(128)
if not buffer: break
md5.update( buffer )
sums[filePath] = [ md5.hexdigest(),
getInternalSum( filePath ) ]
logging.debug( "figured sum for %s: %s" % (filePath,
sums[filePath] ) )
result = sums[filePath]
except:
# logging.debug( "Unexpected error: " + sys.exc_info()[0] )
result = None
return result
def getDictSums():
global s_shelf
# shelve will fail if permissions are wrong. That's ok for some
# testing: just make a fake shelf and test before saving it later.
try:
s_shelf = shelve.open(k_shelfFile)
except:
s_shelf = {}
if not k_SUMS in s_shelf: s_shelf[k_SUMS] = {}
if not k_COUNT in s_shelf: s_shelf[k_COUNT] = 0
s_shelf[k_COUNT] += 1
logging.debug( "Count now %d" % s_shelf[k_COUNT] )
return s_shelf[k_SUMS]
def getOrderedApks( path ):
apks = []
pattern = path + "/XWords4-release_android_beta_*.apk"
files = ((os.stat(apk).st_mtime, apk) for apk in glob.glob(pattern))
for mtime, file in sorted(files, reverse=True):
# logging.debug( file + ": " + str(mtime) )
apks.append( file )
return apks
# public, but deprecated
def curVersion( req, name, avers = 41, gvers = None, installer = None ):
global k_versions
result = { k_SUCCESS : True }
if apacheAvailable:
logging.debug( 'IP address of requester is %s'
% req.get_remote_host(apache.REMOTE_NAME) )
logging.debug( "name: %s; avers: %s; installer: %s; gvers: %s"
% (name, avers, installer, gvers) )
if name in k_versions:
versions = k_versions[name]
if versions[k_AVERS] > int(avers):
logging.debug( avers + " is old" )
result[k_URL] = k_urlbase + '/' + versions[k_URL]
else:
logging.debug(name + " is up-to-date")
else:
logging.debug( 'Error: bad name ' + name )
return json.dumps( result )
# public, but deprecated
def dictVersion( req, name, lang, md5sum ):
result = { k_SUCCESS : True }
if not name.endswith(k_suffix): name += k_suffix
dictSums = getDictSums()
path = lang + "/" + name
if not path in dictSums:
sums = md5Checksums( dictSums, path )
if sums:
dictSums[path] = sums
s_shelf[k_SUMS] = dictSums
if path in dictSums:
if not md5sum in dictSums[path]:
result[k_URL] = k_urlbase + "/and_wordlists/" + path
else:
logging.debug( path + " not known" )
if 'close' in s_shelf: s_shelf.close()
return json.dumps( result )
def getApp( params, name ):
result = None
if k_NAME in params:
name = params[k_NAME]
if name:
# If we're a dev device, always push the latest
if k_DEVOK in params and params[k_DEVOK]:
apks = getOrderedApks( k_filebase + k_apkDir )
if 0 < len(apks):
apk = apks[0]
# Does path NOT contain name of installed file
curApk = params[k_GVERS] + '.apk'
if curApk in apk:
logging.debug( "already have " + curApk )
else:
url = k_urlbase + '/' + apk[len(k_filebase):]
result = {k_URL: url}
logging.debug( result )
elif k_AVERS in params and k_GVERS in params:
avers = params[k_AVERS]
gvers = params[k_GVERS]
if k_INSTALLER in params: installer = params[k_INSTALLER]
else: installer = ''
logging.debug( "name: %s; avers: %s; installer: %s; gvers: %s"
% (name, avers, installer, gvers) )
if name in k_versions:
versForName = k_versions[name]
if versForName[k_AVERS] > int(avers):
result = {k_URL: k_urlbase + '/' + versForName[k_URL]}
elif k_GVERS in versForName and not gvers == versForName[k_GVERS]:
result = {k_URL: k_urlbase + '/' + versForName[k_URL]}
else:
logging.debug(name + " is up-to-date")
else:
logging.debug( 'Error: bad name ' + name )
else:
logging.debug( 'missing param' )
return result
# create obj containing array of objects each with 'lang' and 'xwds',
# the latter an array of objects giving info about a dict.
def listDicts():
ldict = {}
root = k_filebase + "and_wordlists/"
dictSums = getDictSums()
for path in glob.iglob( root + "*/*.xwd" ):
path = path.replace( root, '' )
lang, xwd = path.split( '/' )
if not lang in ldict: ldict[lang] = []
ldict[lang].append({ 'md5sums' : md5Checksums( dictSums, path ),
'xwd' : xwd,
})
# now format as we want 'em
langs = []
for lang, entry in ldict.iteritems():
obj = { 'lang' : lang,
'dicts' : entry,
}
langs.append( obj )
return { 'langs' : langs }
def getDicts( params ):
result = []
dictSums = getDictSums()
for param in params:
name = param[k_NAME]
lang = param[k_LANG]
md5sum = param[k_MD5SUM]
index = param[k_INDEX]
if not name.endswith(k_suffix): name += k_suffix
path = lang + "/" + name
if not path in dictSums:
sums = md5Checksums( dictSums, path )
if sums:
dictSums[path] = sums
s_shelf[k_SUMS] = dictSums
if path in dictSums:
if not md5sum in dictSums[path]:
cur = { k_URL : k_urlbase + "/and_wordlists/" + path,
k_INDEX : index, k_ISUM: dictSums[path][1] }
result.append( cur )
else:
logging.debug( path + " not known" )
if 'close' in s_shelf: s_shelf.close()
if 0 == len(result): result = None
return result
def variantFor( name ):
if name == 'xw4': result = 'XWords4'
logging.debug( 'variantFor(%s)=>%s' % (name, result))
return result
def getXlate( params, name, stringsHash ):
result = []
path = xwconfig.k_REPOPATH
logging.debug('creating repo with path ' + path)
repo = mygit.GitRepo( path )
logging.debug( "getXlate: %s, hash=%s" % (json.dumps(params), stringsHash) )
# logging.debug( 'status: ' + repo.status() )
# reduce org.eehouse.anroid.xxx to xxx, then turn it into a
# variant and get the contents of the R.java file
splits = name.split('.')
name = splits[-1]
variant = variantFor( name );
rPath = '%s/archive/R.java' % variant
rDotJava = repo.cat( rPath, stringsHash )
# Figure out the newest hash possible for translated strings.xml
# files. If our R.java's the newest, that's HEAD. Otherwise it's
# the revision BEFORE the revision that changed R.java
head = repo.getHeadRev()
logging.debug('head = %s' % head)
rjavarevs = repo.getRevsBetween(head, stringsHash, rPath)
assert( 1 >= len(rjavarevs) )
assert( stringsHash == rjavarevs[-1] )
if 1 == len(rjavarevs):
firstPossible = head
else:
firstPossible = rjavarevs[-2] + '^'
# get actual number for rev^
firstPossible = repo.getRevsBetween( firstPossible, firstPossible )[0]
logging.debug('firstPossible: %s' % firstPossible)
for entry in params:
curVers = entry[k_XLATEVERS]
if not curVers == firstPossible:
locale = entry[k_LOCALE]
data = mk_for_download.getXlationFor( repo, rDotJava, locale, \
firstPossible )
if data: result.append( { k_LOCALE: locale,
k_OLD: curVers,
k_NEW: firstPossible,
k_PAIRS: data,
} )
if 0 == len(result): result = None
logging.debug( "getXlate=>%s" % (json.dumps(result)) )
return result
# public
def getUpdates( req, params ):
result = { k_SUCCESS : True }
appResult = None
logging.debug( "getUpdates: got params: %s" % params )
asJson = json.loads( params )
if k_APP in asJson:
name = None
if k_NAME in asJson: name = asJson[k_NAME]
appResult = getApp( asJson[k_APP], name )
if appResult:
result[k_APP] = appResult
if k_DICTS in asJson:
dictsResult = getDicts( asJson[k_DICTS] )
if dictsResult:
result[k_DICTS] = dictsResult
# Let's not upgrade strings at the same time as we're upgrading the app
if appResult:
logging.debug( 'skipping xlation upgrade because app being updated' )
elif k_XLATEINFO in asJson and k_NAME in asJson and k_STRINGSHASH in asJson:
xlateResult = getXlate( asJson[k_XLATEINFO], asJson[k_NAME], asJson[k_STRINGSHASH] )
if xlateResult:
logging.debug( xlateResult )
result[k_XLATEINFO] = xlateResult;
else:
logging.debug( "NOT FOUND xlate info" )
logging.debug( 'getUpdates done' )
return json.dumps( result )
def clearShelf():
shelf = shelve.open(k_shelfFile)
shelf[k_SUMS] = {}
shelf.close()
def usage():
print "usage:", sys.argv[0], '--get-sums [lang/dict]*'
print ' | --test-get-app app <org.eehouse.app.name> avers gvers'
print ' | --test-get-dicts name lang curSum'
print ' | --list-apks [path/to/apks]'
print ' | --list-dicts'
print ' | --clear-shelf'
sys.exit(-1)
def main():
if 1 >= len(sys.argv): usage();
arg = sys.argv[1]
if arg == '--clear-shelf':
clearShelf()
elif arg == '--list-dicts':
dictsJson = listDicts()
print json.dumps( dictsJson )
elif arg == '--get-sums':
dictSums = getDictSums()
for arg in sys.argv[2:]:
print arg, md5Checksums(dictSums, arg)
s_shelf[k_SUMS] = dictSums
if 'close' in s_shelf: s_shelf.close()
elif arg == '--test-get-app':
if not 5 == len(sys.argv): usage()
params = { k_NAME: sys.argv[2],
k_AVERS: int(sys.argv[3]),
k_GVERS: sys.argv[4],
}
print getApp( params )
elif arg == '--test-get-dicts':
if not 5 == len(sys.argv): usage()
params = { k_NAME: sys.argv[2],
k_LANG : sys.argv[3],
k_MD5SUM : sys.argv[4],
k_INDEX : 0,
}
print getDicts( [params] )
elif arg == '--list-apks':
argc = len(sys.argv)
if argc >= 4: usage()
path = ""
if argc >= 3: path = sys.argv[2]
apks = getOrderedApks( path )
if 0 == len(apks): print "No apks in", path
for apk in apks:
print apk
else:
usage()
##############################################################################
if __name__ == '__main__':
main()