diff --git a/fdroidserver/build.py b/fdroidserver/build.py index e724a6db..54997fbf 100644 --- a/fdroidserver/build.py +++ b/fdroidserver/build.py @@ -29,7 +29,7 @@ import time import json from optparse import OptionParser -import common +import common, metadata from common import BuildException, VCSException, FDroidPopen def get_builder_vm_id(): @@ -816,7 +816,7 @@ def main(): sys.exit(1) # Get all apps... - apps = common.read_metadata(xref=not options.onserver) + apps = metadata.read_metadata(xref=not options.onserver) log_dir = 'logs' if not os.path.isdir(log_dir): diff --git a/fdroidserver/checkupdates.py b/fdroidserver/checkupdates.py index 0a417f73..97e0ac6e 100644 --- a/fdroidserver/checkupdates.py +++ b/fdroidserver/checkupdates.py @@ -28,7 +28,7 @@ from optparse import OptionParser import traceback import HTMLParser from distutils.version import LooseVersion -import common +import common, metadata from common import BuildException from common import VCSException @@ -295,7 +295,7 @@ def main(): config = common.read_config(options) # Get all apps... - apps = common.read_metadata(options.verbose) + apps = metadata.read_metadata(options.verbose) # Filter apps according to command-line options if options.package: @@ -453,7 +453,7 @@ def main(): if writeit: metafile = os.path.join('metadata', app['id'] + '.txt') - common.write_metadata(metafile, app) + metadata.write_metadata(metafile, app) if options.commit and logmsg: print "Commiting update for " + metafile gitcmd = ["git", "commit", "-m", diff --git a/fdroidserver/common.py b/fdroidserver/common.py index 1033d17d..8a3298d5 100644 --- a/fdroidserver/common.py +++ b/fdroidserver/common.py @@ -23,20 +23,15 @@ import stat import subprocess import time import operator -import cgi import Queue import threading import magic +import metadata + config = None options = None -# These can only contain 'yes' or 'no' -bool_keys = ( - 'submodules', 'oldsdkloc', - 'forceversion', 'forcevercode', - 'fixtrans', 'fixapos', 'novcheck') - def read_config(opts, config_file='config.py'): """Read the repository config @@ -52,7 +47,7 @@ def read_config(opts, config_file='config.py'): sys.exit(2) st = os.stat(config_file) if st.st_mode & stat.S_IRWXG or st.st_mode & stat.S_IRWXO: - print("WARNING: unsafe permissions on config.py (should be 0600)!") + print "WARNING: unsafe permissions on config.py (should be 0600)!" options = opts if not hasattr(options, 'verbose'): @@ -73,6 +68,11 @@ def read_config(opts, config_file='config.py'): execfile(config_file, config) return config +def getapkname(app, build): + return "%s_%s.apk" % (app['id'], build['vercode']) + +def getsrcname(app, build): + return "%s_%s_src.tar.gz" % (app['id'], build['vercode']) def getvcs(vcstype, remote, local): if vcstype == 'git': @@ -95,7 +95,7 @@ def getsrclibvcs(name): srclib_path = os.path.join('srclibs', name + ".txt") if not os.path.exists(srclib_path): raise VCSException("Missing srclib " + name) - return parse_srclib(srclib_path)['Repo Type'] + return metadata.parse_srclib(srclib_path)['Repo Type'] class vcs: def __init__(self, remote, local): @@ -457,625 +457,6 @@ class vcs_bzr(vcs): return [tag.split(' ')[0].strip() for tag in p.communicate()[0].splitlines()] - -# Get the type expected for a given metadata field. -def metafieldtype(name): - if name in ['Description', 'Maintainer Notes']: - return 'multiline' - if name == 'Requires Root': - return 'flag' - if name == 'Build Version': - return 'build' - if name == 'Build': - return 'buildv2' - if name == 'Use Built': - return 'obsolete' - return 'string' - - -# Parse metadata for a single application. -# -# 'metafile' - the filename to read. The package id for the application comes -# from this filename. Pass None to get a blank entry. -# -# Returns a dictionary containing all the details of the application. There are -# two major kinds of information in the dictionary. Keys beginning with capital -# letters correspond directory to identically named keys in the metadata file. -# Keys beginning with lower case letters are generated in one way or another, -# and are not found verbatim in the metadata. -# -# Known keys not originating from the metadata are: -# -# 'id' - the application's package ID -# 'builds' - a list of dictionaries containing build information -# for each defined build -# 'comments' - a list of comments from the metadata file. Each is -# a tuple of the form (field, comment) where field is -# the name of the field it preceded in the metadata -# file. Where field is None, the comment goes at the -# end of the file. Alternatively, 'build:version' is -# for a comment before a particular build version. -# 'descriptionlines' - original lines of description as formatted in the -# metadata file. -# -def parse_metadata(metafile): - - def parse_buildline(lines): - value = "".join(lines) - parts = [p.replace("\\,", ",") - for p in re.split(r"(? 20: - raise MetaDataException("Silly number of versions for archive policy") - except: - raise MetaDataException("Incomprehensible number of versions for archive policy") - - # Ensure all AntiFeatures are recognised... - if thisinfo['AntiFeatures']: - parts = thisinfo['AntiFeatures'].split(",") - for part in parts: - if (part != "Ads" and - part != "Tracking" and - part != "NonFreeNet" and - part != "NonFreeDep" and - part != "NonFreeAdd"): - raise MetaDataException("Unrecognised antifeature '" + part + "' in " \ - + metafile.name) - - return thisinfo - -def getvercode(build): - return "%s" % (build['vercode']) - -def getapkname(app, build): - return "%s_%s.apk" % (app['id'], getvercode(build)) - -def getsrcname(app, build): - return "%s_%s_src.tar.gz" % (app['id'], getvercode(build)) - -# Write a metadata file. -# -# 'dest' - The path to the output file -# 'app' - The app data -def write_metadata(dest, app): - - def writecomments(key): - written = 0 - for pf, comment in app['comments']: - if pf == key: - mf.write(comment + '\n') - written += 1 - if options.verbose and written > 0: - print "...writing comments for " + (key if key else 'EOF') - - def writefield(field, value=None): - writecomments(field) - if value is None: - value = app[field] - mf.write(field + ':' + value + '\n') - - mf = open(dest, 'w') - if app['Disabled']: - writefield('Disabled') - if app['AntiFeatures']: - writefield('AntiFeatures') - writefield('Categories') - writefield('License') - writefield('Web Site') - writefield('Source Code') - writefield('Issue Tracker') - if app['Donate']: - writefield('Donate') - if app['FlattrID']: - writefield('FlattrID') - if app['Bitcoin']: - writefield('Bitcoin') - if app['Litecoin']: - writefield('Litecoin') - mf.write('\n') - if app['Name']: - writefield('Name') - if app['Auto Name']: - writefield('Auto Name') - writefield('Summary') - writefield('Description', '') - for line in app['Description']: - mf.write(line + '\n') - mf.write('.\n') - mf.write('\n') - if app['Requires Root']: - writefield('Requires Root', 'Yes') - mf.write('\n') - if app['Repo Type']: - writefield('Repo Type') - writefield('Repo') - mf.write('\n') - for build in app['builds']: - writecomments('build:' + build['version']) - mf.write('Build:') - mf.write("%s,%s\n" % ( - build['version'], - getvercode(build))) - - # This defines the preferred order for the build items - as in the - # manual, they're roughly in order of application. - keyorder = ['disable', 'commit', 'subdir', 'submodules', 'init', - 'gradle', 'maven', 'oldsdkloc', 'target', 'compilesdk', - 'update', 'encoding', 'forceversion', 'forcevercode', 'rm', - 'fixtrans', 'fixapos', 'extlibs', 'srclibs', 'patch', - 'prebuild', 'scanignore', 'scandelete', 'build', 'buildjni', - 'preassemble', 'bindir', 'antcommand', 'novcheck'] - - def write_builditem(key, value): - if key not in ['version', 'vercode', 'origlines']: - if key in bool_keys: - if not value: - return - value = 'yes' - if options.verbose: - print "...writing {0} : {1}".format(key, value) - outline = ' %s=' % key - outline += '&& \\\n '.join([s.lstrip() for s in value.split('&& ')]) - outline += '\n' - mf.write(outline) - - for key in keyorder: - if key in build: - write_builditem(key, build[key]) - for key, value in build.iteritems(): - if not key in keyorder: - write_builditem(key, value) - mf.write('\n') - - if 'Maintainer Notes' in app: - writefield('Maintainer Notes', '') - for line in app['Maintainer Notes']: - mf.write(line + '\n') - mf.write('.\n') - mf.write('\n') - - - if app['Archive Policy']: - writefield('Archive Policy') - writefield('Auto Update Mode') - writefield('Update Check Mode') - if app['Vercode Operation']: - writefield('Vercode Operation') - if 'Update Check Data' in app: - writefield('Update Check Data') - if app['Current Version']: - writefield('Current Version') - writefield('Current Version Code') - mf.write('\n') - if app['No Source Since']: - writefield('No Source Since') - mf.write('\n') - writecomments(None) - mf.close() - - -# Read all metadata. Returns a list of 'app' objects (which are dictionaries as -# returned by the parse_metadata function. -def read_metadata(xref=True, package=None): - apps = [] - for basedir in ('metadata', 'tmp'): - if not os.path.exists(basedir): - os.makedirs(basedir) - for metafile in sorted(glob.glob(os.path.join('metadata', '*.txt'))): - if package is None or metafile == os.path.join('metadata', package + '.txt'): - try: - appinfo = parse_metadata(metafile) - except Exception, e: - raise MetaDataException("Problem reading metadata file %s: - %s" % (metafile, str(e))) - apps.append(appinfo) - - if xref: - # Parse all descriptions at load time, just to ensure cross-referencing - # errors are caught early rather than when they hit the build server. - def linkres(link): - for app in apps: - if app['id'] == link: - return ("fdroid.app:" + link, "Dummy name - don't know yet") - raise MetaDataException("Cannot resolve app id " + link) - for app in apps: - try: - description_html(app['Description'], linkres) - except Exception, e: - raise MetaDataException("Problem with description of " + app['id'] + - " - " + str(e)) - - return apps - -# Formatter for descriptions. Create an instance, and call parseline() with -# each line of the description source from the metadata. At the end, call -# end() and then text_plain, text_wiki and text_html will contain the result. -class DescriptionFormatter: - stNONE = 0 - stPARA = 1 - stUL = 2 - stOL = 3 - bold = False - ital = False - state = stNONE - text_plain = '' - text_wiki = '' - text_html = '' - linkResolver = None - def __init__(self, linkres): - self.linkResolver = linkres - def endcur(self, notstates=None): - if notstates and self.state in notstates: - return - if self.state == self.stPARA: - self.endpara() - elif self.state == self.stUL: - self.endul() - elif self.state == self.stOL: - self.endol() - def endpara(self): - self.text_plain += '\n' - self.text_html += '

' - self.state = self.stNONE - def endul(self): - self.text_html += '' - self.state = self.stNONE - def endol(self): - self.text_html += '' - self.state = self.stNONE - - def formatted(self, txt, html): - formatted = '' - if html: - txt = cgi.escape(txt) - while True: - index = txt.find("''") - if index == -1: - return formatted + txt - formatted += txt[:index] - txt = txt[index:] - if txt.startswith("'''"): - if html: - if self.bold: - formatted += '' - else: - formatted += '' - self.bold = not self.bold - txt = txt[3:] - else: - if html: - if self.ital: - formatted += '' - else: - formatted += '' - self.ital = not self.ital - txt = txt[2:] - - - def linkify(self, txt): - linkified_plain = '' - linkified_html = '' - while True: - index = txt.find("[") - if index == -1: - return (linkified_plain + self.formatted(txt, False), linkified_html + self.formatted(txt, True)) - linkified_plain += self.formatted(txt[:index], False) - linkified_html += self.formatted(txt[:index], True) - txt = txt[index:] - if txt.startswith("[["): - index = txt.find("]]") - if index == -1: - raise MetaDataException("Unterminated ]]") - url = txt[2:index] - if self.linkResolver: - url, urltext = self.linkResolver(url) - else: - urltext = url - linkified_html += '' + cgi.escape(urltext) + '' - linkified_plain += urltext - txt = txt[index+2:] - else: - index = txt.find("]") - if index == -1: - raise MetaDataException("Unterminated ]") - url = txt[1:index] - index2 = url.find(' ') - if index2 == -1: - urltxt = url - else: - urltxt = url[index2 + 1:] - url = url[:index2] - linkified_html += '' + cgi.escape(urltxt) + '' - linkified_plain += urltxt - if urltxt != url: - linkified_plain += ' (' + url + ')' - txt = txt[index+1:] - - def addtext(self, txt): - p, h = self.linkify(txt) - self.text_plain += p - self.text_html += h - - def parseline(self, line): - self.text_wiki += line + '\n' - if not line: - self.endcur() - elif line.startswith('*'): - self.endcur([self.stUL]) - if self.state != self.stUL: - self.text_html += '' + self.state = self.stNONE + def endol(self): + self.text_html += '' + self.state = self.stNONE + + def formatted(self, txt, html): + formatted = '' + if html: + txt = cgi.escape(txt) + while True: + index = txt.find("''") + if index == -1: + return formatted + txt + formatted += txt[:index] + txt = txt[index:] + if txt.startswith("'''"): + if html: + if self.bold: + formatted += '' + else: + formatted += '' + self.bold = not self.bold + txt = txt[3:] + else: + if html: + if self.ital: + formatted += '' + else: + formatted += '' + self.ital = not self.ital + txt = txt[2:] + + + def linkify(self, txt): + linkified_plain = '' + linkified_html = '' + while True: + index = txt.find("[") + if index == -1: + return (linkified_plain + self.formatted(txt, False), linkified_html + self.formatted(txt, True)) + linkified_plain += self.formatted(txt[:index], False) + linkified_html += self.formatted(txt[:index], True) + txt = txt[index:] + if txt.startswith("[["): + index = txt.find("]]") + if index == -1: + raise MetaDataException("Unterminated ]]") + url = txt[2:index] + if self.linkResolver: + url, urltext = self.linkResolver(url) + else: + urltext = url + linkified_html += '' + cgi.escape(urltext) + '' + linkified_plain += urltext + txt = txt[index+2:] + else: + index = txt.find("]") + if index == -1: + raise MetaDataException("Unterminated ]") + url = txt[1:index] + index2 = url.find(' ') + if index2 == -1: + urltxt = url + else: + urltxt = url[index2 + 1:] + url = url[:index2] + linkified_html += '' + cgi.escape(urltxt) + '' + linkified_plain += urltxt + if urltxt != url: + linkified_plain += ' (' + url + ')' + txt = txt[index+1:] + + def addtext(self, txt): + p, h = self.linkify(txt) + self.text_plain += p + self.text_html += h + + def parseline(self, line): + self.text_wiki += "%s\n" % line + if not line: + self.endcur() + elif line.startswith('*'): + self.endcur([self.stUL]) + if self.state != self.stUL: + self.text_html += '