convert App to subclass of dict to support parsing/dumping libs

Python is heavily based on its core data types, and dict is one of the more
important ones.  Even classes are basically a wrapper around a dict. This
converts metadata.App to be a subclass of dict so it can behave like a dict
when being dumped and loaded.  This makes its drastically easier to use
different data formats for build metadata and for sending data to the
client.  This approach will ultimately mean we no longer have to maintain
custom parsing and dumping code.

This also means then that the YAML/JSON field names will not have spaces in
them, and they will match exactly what it used as the dict keys once the
data is parsed, as well as matching exactly the instance attribute names:

* CurrentVersion: 1.2.6
* app['CurrentVersion'] == '1.2.6'
* app.CurrentVersion == '1.2.6'

Inspired by:
https://goodcode.io/articles/python-dict-object/
This commit is contained in:
Hans-Christoph Steiner 2016-11-23 17:25:59 +01:00
parent 4625651192
commit b7fc7f2228
10 changed files with 165 additions and 229 deletions

View file

@ -63,10 +63,10 @@ http_checks = https_enforcings + http_url_shorteners + [
] ]
regex_checks = { regex_checks = {
'Web Site': http_checks, 'WebSite': http_checks,
'Source Code': http_checks, 'SourceCode': http_checks,
'Repo': https_enforcings, 'Repo': https_enforcings,
'Issue Tracker': http_checks + [ 'IssueTracker': http_checks + [
(re.compile(r'.*github\.com/[^/]+/[^/]+/*$'), (re.compile(r'.*github\.com/[^/]+/[^/]+/*$'),
"/issues is missing"), "/issues is missing"),
(re.compile(r'.*gitlab\.com/[^/]+/[^/]+/*$'), (re.compile(r'.*gitlab\.com/[^/]+/[^/]+/*$'),
@ -121,7 +121,7 @@ regex_checks = {
def check_regexes(app): def check_regexes(app):
for f, checks in regex_checks.items(): for f, checks in regex_checks.items():
for m, r in checks: for m, r in checks:
v = app.get_field(f) v = app.get(f)
t = metadata.fieldtype(f) t = metadata.fieldtype(f)
if t == metadata.TYPE_MULTILINE: if t == metadata.TYPE_MULTILINE:
for l in v.splitlines(): for l in v.splitlines():
@ -183,8 +183,8 @@ def check_old_links(app):
'code.google.com', 'code.google.com',
] ]
if any(s in app.Repo for s in usual_sites): if any(s in app.Repo for s in usual_sites):
for f in ['Web Site', 'Source Code', 'Issue Tracker', 'Changelog']: for f in ['WebSite', 'SourceCode', 'IssueTracker', 'Changelog']:
v = app.get_field(f) v = app.get(f)
if any(s in v for s in old_sites): if any(s in v for s in old_sites):
yield "App is in '%s' but has a link to '%s'" % (app.Repo, v) yield "App is in '%s' but has a link to '%s'" % (app.Repo, v)
@ -241,7 +241,7 @@ def check_duplicates(app):
links_seen = set() links_seen = set()
for f in ['Source Code', 'Web Site', 'Issue Tracker', 'Changelog']: for f in ['Source Code', 'Web Site', 'Issue Tracker', 'Changelog']:
v = app.get_field(f) v = app.get(f)
if not v: if not v:
continue continue
v = v.lower() v = v.lower()

View file

@ -98,15 +98,21 @@ app_fields = set([
'Current Version', 'Current Version',
'Current Version Code', 'Current Version Code',
'No Source Since', 'No Source Since',
'Build',
'comments', # For formats that don't do inline comments 'comments', # For formats that don't do inline comments
'builds', # For formats that do builds as a list 'builds', # For formats that do builds as a list
]) ])
class App(): class App(dict):
def __init__(self, copydict=None):
if copydict:
super().__init__(copydict)
return
super().__init__()
def __init__(self):
self.Disabled = None self.Disabled = None
self.AntiFeatures = [] self.AntiFeatures = []
self.Provides = None self.Provides = None
@ -148,94 +154,21 @@ class App():
self.comments = {} self.comments = {}
self.added = None self.added = None
self.lastupdated = None self.lastupdated = None
self._modified = set()
@classmethod def __getattr__(self, name):
def field_to_attr(cls, f): if name in self:
""" return self[name]
Translates human-readable field names to attribute names, e.g.
'Auto Name' to 'AutoName'
"""
return f.replace(' ', '')
@classmethod
def attr_to_field(cls, k):
"""
Translates attribute names to human-readable field names, e.g.
'AutoName' to 'Auto Name'
"""
if k in app_fields:
return k
f = re.sub(r'([a-z])([A-Z])', r'\1 \2', k)
return f
def field_dict(self):
"""
Constructs an old-fashioned dict with the human-readable field
names. Should only be used for tests.
"""
d = {}
for k, v in self.__dict__.items():
if k == 'builds':
d['builds'] = []
for build in v:
b = {k: v for k, v in build.__dict__.items() if not k.startswith('_')}
d['builds'].append(b)
elif not k.startswith('_'):
f = App.attr_to_field(k)
d[f] = v
return d
def get_field(self, f):
"""Gets the value associated to a field name, e.g. 'Auto Name'"""
if f not in app_fields:
warn_or_exception('Unrecognised app field: ' + f)
k = App.field_to_attr(f)
return getattr(self, k)
def set_field(self, f, v):
"""Sets the value associated to a field name, e.g. 'Auto Name'"""
if f not in app_fields:
warn_or_exception('Unrecognised app field: ' + f)
k = App.field_to_attr(f)
self.__dict__[k] = v
self._modified.add(k)
def append_field(self, f, v):
"""Appends to the value associated to a field name, e.g. 'Auto Name'"""
if f not in app_fields:
warn_or_exception('Unrecognised app field: ' + f)
k = App.field_to_attr(f)
if k not in self.__dict__:
self.__dict__[k] = [v]
else: else:
self.__dict__[k].append(v) raise AttributeError("No such attribute: " + name)
def update_fields(self, d): def __setattr__(self, name, value):
'''Like dict.update(), but using human-readable field names''' self[name] = value
for f, v in d.items():
if f == 'builds':
for b in v:
build = Build()
build.update_flags(b)
self.builds.append(build)
else:
self.set_field(f, v)
def update(self, d): def __delattr__(self, name):
'''Like dict.update()''' if name in self:
for k, v in d.__dict__.items(): del self[name]
if k == '_modified': else:
continue raise AttributeError("No such attribute: " + name)
elif k == 'builds':
for b in v:
build = Build()
del(b.__dict__['_modified'])
build.update_flags(b.__dict__)
self.builds.append(build)
elif v:
self.__dict__[k] = v
self._modified.add(k)
def get_last_build(self): def get_last_build(self):
if len(self.builds) > 0: if len(self.builds) > 0:
@ -256,16 +189,17 @@ TYPE_BUILD_V2 = 8
fieldtypes = { fieldtypes = {
'Description': TYPE_MULTILINE, 'Description': TYPE_MULTILINE,
'Maintainer Notes': TYPE_MULTILINE, 'MaintainerNotes': TYPE_MULTILINE,
'Categories': TYPE_LIST, 'Categories': TYPE_LIST,
'AntiFeatures': TYPE_LIST, 'AntiFeatures': TYPE_LIST,
'Build Version': TYPE_BUILD, 'BuildVersion': TYPE_BUILD,
'Build': TYPE_BUILD_V2, 'Build': TYPE_BUILD_V2,
'Use Built': TYPE_OBSOLETE, 'UseBuilt': TYPE_OBSOLETE,
} }
def fieldtype(name): def fieldtype(name):
name = name.replace(' ', '')
if name in fieldtypes: if name in fieldtypes:
return fieldtypes[name] return fieldtypes[name]
return TYPE_STRING return TYPE_STRING
@ -518,9 +452,7 @@ valuetypes = {
def check_metadata(app): def check_metadata(app):
for v in valuetypes: for v in valuetypes:
for k in v.fields: for k in v.fields:
if k not in app._modified: v.check(app[k], app.id)
continue
v.check(app.__dict__[k], app.id)
# Formatter for descriptions. Create an instance, and call parseline() with # Formatter for descriptions. Create an instance, and call parseline() with
@ -896,44 +828,21 @@ def sorted_builds(builds):
esc_newlines = re.compile(r'\\( |\n)') esc_newlines = re.compile(r'\\( |\n)')
# This function uses __dict__ to be faster
def post_metadata_parse(app): def post_metadata_parse(app):
# TODO keep native types, convert only for .txt metadata
for k in app._modified: for k, v in app.items():
v = app.__dict__[k]
if type(v) in (float, int): if type(v) in (float, int):
app.__dict__[k] = str(v) app[k] = str(v)
builds = [] builds = []
for build in app.builds: if 'builds' in app:
if not isinstance(build, Build): for build in app['builds']:
build = Build(build) if not isinstance(build, Build):
builds.append(build) build = Build(build)
builds.append(build)
for k in build._modified: if not app.get('Description'):
v = build.__dict__[k] app['Description'] = 'No description available'
if type(v) in (float, int):
build.__dict__[k] = str(v)
continue
ftype = flagtype(k)
if ftype == TYPE_SCRIPT:
build.__dict__[k] = re.sub(esc_newlines, '', v).lstrip().rstrip()
elif ftype == TYPE_BOOL:
# TODO handle this using <xsd:element type="xsd:boolean> in a schema
if isinstance(v, str):
build.__dict__[k] = _decode_bool(v)
elif ftype == TYPE_STRING:
if isinstance(v, bool) and v:
build.__dict__[k] = 'yes'
elif ftype == TYPE_LIST:
if isinstance(v, bool) and v:
build.__dict__[k] = ['yes']
elif isinstance(v, str):
build.__dict__[k] = [v]
if not app.Description:
app.Description = 'No description available'
app.builds = sorted_builds(builds) app.builds = sorted_builds(builds)
@ -1039,17 +948,18 @@ def parse_json_metadata(mf, app):
# TODO create schema using https://pypi.python.org/pypi/jsonschema # TODO create schema using https://pypi.python.org/pypi/jsonschema
jsoninfo = json.load(mf, parse_int=lambda s: s, jsoninfo = json.load(mf, parse_int=lambda s: s,
parse_float=lambda s: s) parse_float=lambda s: s)
app.update_fields(jsoninfo) app.update(jsoninfo)
for f in ['Description', 'Maintainer Notes']: for f in ['Description', 'Maintainer Notes']:
v = app.get_field(f) v = app.get(f)
app.set_field(f, '\n'.join(v)) if v:
app[f] = '\n'.join(v)
return app return app
def parse_yaml_metadata(mf, app): def parse_yaml_metadata(mf, app):
yamlinfo = yaml.load(mf, Loader=YamlLoader) yamlinfo = yaml.load(mf, Loader=YamlLoader)
app.update_fields(yamlinfo) app.update(yamlinfo)
return app return app
@ -1128,6 +1038,8 @@ def parse_txt_metadata(mf, app):
build = None build = None
vc_seen = set() vc_seen = set()
app.builds = []
c = 0 c = 0
for line in mf: for line in mf:
c += 1 c += 1
@ -1162,12 +1074,17 @@ def parse_txt_metadata(mf, app):
except ValueError: except ValueError:
warn_or_exception("Invalid metadata in " + linedesc) warn_or_exception("Invalid metadata in " + linedesc)
if f not in app_fields:
warn_or_exception('Unrecognised app field: ' + f)
# Translate obsolete fields... # Translate obsolete fields...
if f == 'Market Version': if f == 'Market Version':
f = 'Current Version' f = 'Current Version'
if f == 'Market Version Code': if f == 'Market Version Code':
f = 'Current Version Code' f = 'Current Version Code'
f = f.replace(' ', '')
ftype = fieldtype(f) ftype = fieldtype(f)
if ftype not in [TYPE_BUILD, TYPE_BUILD_V2]: if ftype not in [TYPE_BUILD, TYPE_BUILD_V2]:
add_comments(f) add_comments(f)
@ -1177,9 +1094,9 @@ def parse_txt_metadata(mf, app):
warn_or_exception("Unexpected text on same line as " warn_or_exception("Unexpected text on same line as "
+ f + " in " + linedesc) + f + " in " + linedesc)
elif ftype == TYPE_STRING: elif ftype == TYPE_STRING:
app.set_field(f, v) app[f] = v
elif ftype == TYPE_LIST: elif ftype == TYPE_LIST:
app.set_field(f, split_list_values(v)) app[f] = split_list_values(v)
elif ftype == TYPE_BUILD: elif ftype == TYPE_BUILD:
if v.endswith("\\"): if v.endswith("\\"):
mode = 2 mode = 2
@ -1212,7 +1129,7 @@ def parse_txt_metadata(mf, app):
elif mode == 1: # Multiline field elif mode == 1: # Multiline field
if line == '.': if line == '.':
mode = 0 mode = 0
app.set_field(f, '\n'.join(multiline_lines)) app[f] = '\n'.join(multiline_lines)
del multiline_lines[:] del multiline_lines[:]
else: else:
multiline_lines.append(line) multiline_lines.append(line)
@ -1240,6 +1157,23 @@ def parse_txt_metadata(mf, app):
def write_plaintext_metadata(mf, app, w_comment, w_field, w_build): def write_plaintext_metadata(mf, app, w_comment, w_field, w_build):
def field_to_attr(f):
"""
Translates human-readable field names to attribute names, e.g.
'Auto Name' to 'AutoName'
"""
return f.replace(' ', '')
def attr_to_field(k):
"""
Translates attribute names to human-readable field names, e.g.
'AutoName' to 'Auto Name'
"""
if k in app_fields:
return k
f = re.sub(r'([a-z])([A-Z])', r'\1 \2', k)
return f
def w_comments(key): def w_comments(key):
if key not in app.comments: if key not in app.comments:
return return
@ -1247,15 +1181,17 @@ def write_plaintext_metadata(mf, app, w_comment, w_field, w_build):
w_comment(line) w_comment(line)
def w_field_always(f, v=None): def w_field_always(f, v=None):
key = field_to_attr(f)
if v is None: if v is None:
v = app.get_field(f) v = app.get(key)
w_comments(f) w_comments(key)
w_field(f, v) w_field(f, v)
def w_field_nonempty(f, v=None): def w_field_nonempty(f, v=None):
key = field_to_attr(f)
if v is None: if v is None:
v = app.get_field(f) v = app.get(key)
w_comments(f) w_comments(key)
if v: if v:
w_field(f, v) w_field(f, v)

View file

@ -99,7 +99,7 @@ def update_wiki(apps, sortedids, apks):
generated_redirects = {} generated_redirects = {}
for appid in sortedids: for appid in sortedids:
app = apps[appid] app = metadata.App(apps[appid])
wikidata = '' wikidata = ''
if app.Disabled: if app.Disabled:
@ -302,7 +302,7 @@ def delete_disabled_builds(apps, apkcache, repodirs):
:param repodirs: the repo directories to process :param repodirs: the repo directories to process
""" """
for appid, app in apps.items(): for appid, app in apps.items():
for build in app.builds: for build in app['builds']:
if not build.disable: if not build.disable:
continue continue
apkfilename = appid + '_' + str(build.vercode) + '.apk' apkfilename = appid + '_' + str(build.vercode) + '.apk'
@ -1111,7 +1111,7 @@ def make_index(apps, sortedids, apks, repodir, archive):
element.setAttribute('packageName', packageName) element.setAttribute('packageName', packageName)
for appid in sortedids: for appid in sortedids:
app = apps[appid] app = metadata.App(apps[appid])
if app.Disabled is not None: if app.Disabled is not None:
continue continue
@ -1265,7 +1265,7 @@ def make_index(apps, sortedids, apks, repodir, archive):
and config['make_current_version_link'] \ and config['make_current_version_link'] \
and repodir == 'repo': # only create these and repodir == 'repo': # only create these
namefield = config['current_version_name_source'] namefield = config['current_version_name_source']
sanitized_name = re.sub('''[ '"&%?+=/]''', '', app.get_field(namefield)) sanitized_name = re.sub('''[ '"&%?+=/]''', '', app.get(namefield))
apklinkname = sanitized_name + '.apk' apklinkname = sanitized_name + '.apk'
current_version_path = os.path.join(repodir, current_version_file) current_version_path = os.path.join(repodir, current_version_file)
if os.path.islink(apklinkname): if os.path.islink(apklinkname):
@ -1621,6 +1621,7 @@ def main():
logging.debug("Don't know when " + appid + " was last updated") logging.debug("Don't know when " + appid + " was last updated")
if bestver == UNSET_VERSION_CODE: if bestver == UNSET_VERSION_CODE:
if app.Name is None: if app.Name is None:
app.Name = app.AutoName or appid app.Name = app.AutoName or appid
app.icon = None app.icon = None

View file

@ -38,7 +38,7 @@ class ImportTest(unittest.TestCase):
print('Skipping ImportTest!') print('Skipping ImportTest!')
return return
app = fdroidserver.metadata.get_default_app_info() app = fdroidserver.metadata.App()
app.UpdateCheckMode = "Tags" app.UpdateCheckMode = "Tags"
root_dir, src_dir = import_proxy.get_metadata_from_url(app, url) root_dir, src_dir = import_proxy.get_metadata_from_url(app, url)
self.assertEqual(app.RepoType, 'git') self.assertEqual(app.RepoType, 'git')

View file

@ -38,9 +38,8 @@ class MetadataTest(unittest.TestCase):
apps = fdroidserver.metadata.read_metadata(xref=True) apps = fdroidserver.metadata.read_metadata(xref=True)
for appid in ('org.smssecure.smssecure', 'org.adaway', 'org.videolan.vlc'): for appid in ('org.smssecure.smssecure', 'org.adaway', 'org.videolan.vlc'):
app = apps[appid]
savepath = os.path.join('metadata', 'dump', appid + '.yaml') savepath = os.path.join('metadata', 'dump', appid + '.yaml')
frommeta = app.field_dict() frommeta = dict(apps[appid])
self.assertTrue(appid in apps) self.assertTrue(appid in apps)
with open(savepath, 'r') as f: with open(savepath, 'r') as f:
frompickle = yaml.load(f) frompickle = yaml.load(f)

View file

@ -1,17 +1,17 @@
AntiFeatures: [] AntiFeatures: []
Archive Policy: null ArchivePolicy: null
Author Email: null AuthorEmail: null
Author Name: null AuthorName: null
Auto Name: AdAway AutoName: AdAway
Auto Update Mode: Version v%v AutoUpdateMode: Version v%v
Binaries: null Binaries: null
Bitcoin: null Bitcoin: null
Categories: Categories:
- System - System
- Security - Security
Changelog: '' Changelog: ''
Current Version: '3.0' CurrentVersion: '3.0'
Current Version Code: '52' CurrentVersionCode: '52'
Description: 'An ad blocker that uses the hosts file. The hosts file Description: 'An ad blocker that uses the hosts file. The hosts file
contains a list of mappings between hostnames and IP addresses. When contains a list of mappings between hostnames and IP addresses. When
@ -38,24 +38,24 @@ Description: 'An ad blocker that uses the hosts file. The hosts file
Disabled: null Disabled: null
Donate: http://sufficientlysecure.org/index.php/adaway Donate: http://sufficientlysecure.org/index.php/adaway
FlattrID: '369138' FlattrID: '369138'
Issue Tracker: https://github.com/dschuermann/ad-away/issues IssueTracker: https://github.com/dschuermann/ad-away/issues
License: GPLv3 License: GPLv3
Litecoin: null Litecoin: null
Maintainer Notes: '' MaintainerNotes: ''
Name: null Name: null
No Source Since: '' NoSourceSince: ''
Provides: org.sufficientlysecure.adaway Provides: org.sufficientlysecure.adaway
Repo: https://github.com/dschuermann/ad-away.git Repo: https://github.com/dschuermann/ad-away.git
Repo Type: git RepoType: git
Requires Root: true RequiresRoot: true
Source Code: https://github.com/dschuermann/ad-away SourceCode: https://github.com/dschuermann/ad-away
Summary: Block advertisements Summary: Block advertisements
Update Check Data: null UpdateCheckData: null
Update Check Ignore: null UpdateCheckIgnore: null
Update Check Mode: Tags UpdateCheckMode: Tags
Update Check Name: null UpdateCheckName: null
Vercode Operation: null VercodeOperation: null
Web Site: http://sufficientlysecure.org/index.php/adaway WebSite: http://sufficientlysecure.org/index.php/adaway
added: null added: null
builds: builds:
- antcommands: [] - antcommands: []

View file

@ -1,16 +1,16 @@
AntiFeatures: [] AntiFeatures: []
Archive Policy: null ArchivePolicy: null
Author Email: null AuthorEmail: null
Author Name: null AuthorName: null
Auto Name: SMSSecure AutoName: SMSSecure
Auto Update Mode: Version v%v AutoUpdateMode: Version v%v
Binaries: null Binaries: null
Bitcoin: null Bitcoin: null
Categories: Categories:
- Phone & SMS - Phone & SMS
Changelog: '' Changelog: ''
Current Version: 0.6.0 CurrentVersion: 0.6.0
Current Version Code: '102' CurrentVersionCode: '102'
Description: 'SMSSecure is an SMS/MMS application that allows you to protect your Description: 'SMSSecure is an SMS/MMS application that allows you to protect your
privacy while communicating with friends. privacy while communicating with friends.
@ -35,24 +35,24 @@ Description: 'SMSSecure is an SMS/MMS application that allows you to protect you
Disabled: null Disabled: null
Donate: null Donate: null
FlattrID: null FlattrID: null
Issue Tracker: https://github.com/SMSSecure/SMSSecure/issues IssueTracker: https://github.com/SMSSecure/SMSSecure/issues
License: GPLv3 License: GPLv3
Litecoin: null Litecoin: null
Maintainer Notes: '' MaintainerNotes: ''
Name: null Name: null
No Source Since: '' NoSourceSince: ''
Provides: null Provides: null
Repo: https://github.com/SMSSecure/SMSSecure Repo: https://github.com/SMSSecure/SMSSecure
Repo Type: git RepoType: git
Requires Root: false RequiresRoot: false
Source Code: https://github.com/SMSSecure/SMSSecure SourceCode: https://github.com/SMSSecure/SMSSecure
Summary: Send encrypted text messages (SMS) Summary: Send encrypted text messages (SMS)
Update Check Data: null UpdateCheckData: null
Update Check Ignore: null UpdateCheckIgnore: null
Update Check Mode: Tags UpdateCheckMode: Tags
Update Check Name: null UpdateCheckName: null
Vercode Operation: null VercodeOperation: null
Web Site: http://www.smssecure.org WebSite: http://www.smssecure.org
added: null added: null
builds: builds:
- antcommands: [] - antcommands: []

View file

@ -1,16 +1,16 @@
AntiFeatures: [] AntiFeatures: []
Archive Policy: 9 versions ArchivePolicy: 9 versions
Author Email: null AuthorEmail: null
Author Name: null AuthorName: null
Auto Name: VLC AutoName: VLC
Auto Update Mode: None AutoUpdateMode: None
Binaries: null Binaries: null
Bitcoin: null Bitcoin: null
Categories: Categories:
- Multimedia - Multimedia
Changelog: '' Changelog: ''
Current Version: 1.2.6 CurrentVersion: 1.2.6
Current Version Code: '1030005' CurrentVersionCode: '1030005'
Description: 'Video and audio player that supports a wide range of formats, Description: 'Video and audio player that supports a wide range of formats,
for both local and remote playback. for both local and remote playback.
@ -22,10 +22,10 @@ Description: 'Video and audio player that supports a wide range of formats,
Disabled: null Disabled: null
Donate: http://www.videolan.org/contribute.html#money Donate: http://www.videolan.org/contribute.html#money
FlattrID: null FlattrID: null
Issue Tracker: http://www.videolan.org/support/index.html#bugs IssueTracker: http://www.videolan.org/support/index.html#bugs
License: GPLv3 License: GPLv3
Litecoin: null Litecoin: null
Maintainer Notes: 'Instructions and dependencies here: http://wiki.videolan.org/AndroidCompile MaintainerNotes: 'Instructions and dependencies here: http://wiki.videolan.org/AndroidCompile
see http://buildbot.videolan.org/builders/ for version code scheme see http://buildbot.videolan.org/builders/ for version code scheme
@ -42,19 +42,19 @@ Maintainer Notes: 'Instructions and dependencies here: http://wiki.videolan.org/
' '
Name: null Name: null
No Source Since: '' NoSourceSince: ''
Provides: null Provides: null
Repo: git://git.videolan.org/vlc-ports/android.git Repo: git://git.videolan.org/vlc-ports/android.git
Repo Type: git RepoType: git
Requires Root: false RequiresRoot: false
Source Code: http://git.videolan.org/?p=vlc-ports/android.git;a=summary SourceCode: http://git.videolan.org/?p=vlc-ports/android.git;a=summary
Summary: Media player Summary: Media player
Update Check Data: null UpdateCheckData: null
Update Check Ignore: null UpdateCheckIgnore: null
Update Check Mode: Tags UpdateCheckMode: Tags
Update Check Name: null UpdateCheckName: null
Vercode Operation: '%c + 5' VercodeOperation: '%c + 5'
Web Site: http://www.videolan.org/vlc/download-android.html WebSite: http://www.videolan.org/vlc/download-android.html
added: null added: null
builds: builds:
- antcommands: [] - antcommands: []

View file

@ -1,9 +1,9 @@
{ {
"Auto Name": "AdAway", "AutoName": "AdAway",
"Auto Update Mode": "Version v%v", "AutoUpdateMode": "Version v%v",
"Categories": ["System", "Security"], "Categories": ["System", "Security"],
"Current Version": "3.0", "CurrentVersion": "3.0",
"Current Version Code": 52, "CurrentVersionCode": 52,
"Description": [ "Description": [
"An ad blocker that uses the hosts file. The hosts file", "An ad blocker that uses the hosts file. The hosts file",
"contains a list of mappings between hostnames and IP addresses. When", "contains a list of mappings between hostnames and IP addresses. When",
@ -21,16 +21,16 @@
], ],
"Donate": "http://sufficientlysecure.org/index.php/adaway", "Donate": "http://sufficientlysecure.org/index.php/adaway",
"FlattrID": "369138", "FlattrID": "369138",
"Issue Tracker": "https://github.com/dschuermann/ad-away/issues", "IssueTracker": "https://github.com/dschuermann/ad-away/issues",
"License": "GPLv3", "License": "GPLv3",
"Provides": "org.sufficientlysecure.adaway", "Provides": "org.sufficientlysecure.adaway",
"Repo": "https://github.com/dschuermann/ad-away.git", "Repo": "https://github.com/dschuermann/ad-away.git",
"Repo Type": "git", "RepoType": "git",
"Requires Root": true, "RequiresRoot": true,
"Source Code": "https://github.com/dschuermann/ad-away", "SourceCode": "https://github.com/dschuermann/ad-away",
"Summary": "Block advertisements", "Summary": "Block advertisements",
"Update Check Mode": "Tags", "UpdateCheckMode": "Tags",
"Web Site": "http://sufficientlysecure.org/index.php/adaway", "WebSite": "http://sufficientlysecure.org/index.php/adaway",
"builds": [ "builds": [
{ {

View file

@ -1,12 +1,12 @@
Categories: Categories:
- Multimedia - Multimedia
License: GPLv3 License: GPLv3
Web Site: http://www.videolan.org/vlc/download-android.html WebSite: http://www.videolan.org/vlc/download-android.html
Source Code: http://git.videolan.org/?p=vlc-ports/android.git;a=summary SourceCode: http://git.videolan.org/?p=vlc-ports/android.git;a=summary
Issue Tracker: "http://www.videolan.org/support/index.html#bugs" IssueTracker: "http://www.videolan.org/support/index.html#bugs"
Donate: "http://www.videolan.org/contribute.html#money" Donate: "http://www.videolan.org/contribute.html#money"
Auto Name: VLC AutoName: VLC
Summary: Media player Summary: Media player
Description: | Description: |
Video and audio player that supports a wide range of formats, Video and audio player that supports a wide range of formats,
@ -14,7 +14,7 @@ Description: |
[http://git.videolan.org/?p=vlc-ports/android.git;a=blob_plain;f=NEWS NEWS] [http://git.videolan.org/?p=vlc-ports/android.git;a=blob_plain;f=NEWS NEWS]
Repo Type: git RepoType: git
Repo: git://git.videolan.org/vlc-ports/android.git Repo: git://git.videolan.org/vlc-ports/android.git
builds: builds:
@ -875,7 +875,7 @@ builds:
buildjni: no buildjni: no
ndk: r10d ndk: r10d
Maintainer Notes: | MaintainerNotes: |
Instructions and dependencies here: http://wiki.videolan.org/AndroidCompile Instructions and dependencies here: http://wiki.videolan.org/AndroidCompile
see http://buildbot.videolan.org/builders/ for version code scheme see http://buildbot.videolan.org/builders/ for version code scheme
The VLC srclib commit can be found out from TESTED_HASH value in compile.sh The VLC srclib commit can be found out from TESTED_HASH value in compile.sh
@ -902,10 +902,10 @@ Maintainer Notes: |
# +2: x86 # +2: x86
# +3: arm # +3: arm
# +4: armv7 (CV) # +4: armv7 (CV)
Archive Policy: 9 versions ArchivePolicy: 9 versions
Auto Update Mode: None AutoUpdateMode: None
Update Check Mode: Tags UpdateCheckMode: Tags
# Only use higher vercode ops, if we do build those arches # Only use higher vercode ops, if we do build those arches
Vercode Operation: "%c + 5" VercodeOperation: "%c + 5"
Current Version: 1.2.6 CurrentVersion: 1.2.6
Current Version Code: 1030005 CurrentVersionCode: 1030005