]
regex_checks = {
- 'Web Site': http_checks,
- 'Source Code': http_checks,
+ 'WebSite': http_checks,
+ 'SourceCode': http_checks,
'Repo': https_enforcings,
- 'Issue Tracker': http_checks + [
+ 'IssueTracker': http_checks + [
(re.compile(r'.*github\.com/[^/]+/[^/]+/*$'),
"/issues is missing"),
(re.compile(r'.*gitlab\.com/[^/]+/[^/]+/*$'),
def check_regexes(app):
for f, checks in regex_checks.items():
for m, r in checks:
- v = app.get_field(f)
+ v = app.get(f)
t = metadata.fieldtype(f)
if t == metadata.TYPE_MULTILINE:
for l in v.splitlines():
'code.google.com',
]
if any(s in app.Repo for s in usual_sites):
- for f in ['Web Site', 'Source Code', 'Issue Tracker', 'Changelog']:
- v = app.get_field(f)
+ for f in ['WebSite', 'SourceCode', 'IssueTracker', 'Changelog']:
+ v = app.get(f)
if any(s in v for s in old_sites):
yield "App is in '%s' but has a link to '%s'" % (app.Repo, v)
links_seen = set()
for f in ['Source Code', 'Web Site', 'Issue Tracker', 'Changelog']:
- v = app.get_field(f)
+ v = app.get(f)
if not v:
continue
v = v.lower()
'Current Version',
'Current Version Code',
'No Source Since',
+ 'Build',
'comments', # For formats that don't do inline comments
'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.AntiFeatures = []
self.Provides = None
self.comments = {}
self.added = None
self.lastupdated = None
- self._modified = set()
-
- @classmethod
- def field_to_attr(cls, f):
- """
- 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]
+ def __getattr__(self, name):
+ if name in self:
+ return self[name]
else:
- self.__dict__[k].append(v)
+ raise AttributeError("No such attribute: " + name)
- def update_fields(self, d):
- '''Like dict.update(), but using human-readable field names'''
- 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 __setattr__(self, name, value):
+ self[name] = value
- def update(self, d):
- '''Like dict.update()'''
- for k, v in d.__dict__.items():
- if k == '_modified':
- continue
- 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 __delattr__(self, name):
+ if name in self:
+ del self[name]
+ else:
+ raise AttributeError("No such attribute: " + name)
def get_last_build(self):
if len(self.builds) > 0:
fieldtypes = {
'Description': TYPE_MULTILINE,
- 'Maintainer Notes': TYPE_MULTILINE,
+ 'MaintainerNotes': TYPE_MULTILINE,
'Categories': TYPE_LIST,
'AntiFeatures': TYPE_LIST,
- 'Build Version': TYPE_BUILD,
+ 'BuildVersion': TYPE_BUILD,
'Build': TYPE_BUILD_V2,
- 'Use Built': TYPE_OBSOLETE,
+ 'UseBuilt': TYPE_OBSOLETE,
}
def fieldtype(name):
+ name = name.replace(' ', '')
if name in fieldtypes:
return fieldtypes[name]
return TYPE_STRING
def check_metadata(app):
for v in valuetypes:
for k in v.fields:
- if k not in app._modified:
- continue
- v.check(app.__dict__[k], app.id)
+ v.check(app[k], app.id)
# Formatter for descriptions. Create an instance, and call parseline() with
esc_newlines = re.compile(r'\\( |\n)')
-# This function uses __dict__ to be faster
def post_metadata_parse(app):
-
- for k in app._modified:
- v = app.__dict__[k]
+ # TODO keep native types, convert only for .txt metadata
+ for k, v in app.items():
if type(v) in (float, int):
- app.__dict__[k] = str(v)
+ app[k] = str(v)
builds = []
- for build in app.builds:
- if not isinstance(build, Build):
- build = Build(build)
- builds.append(build)
-
- for k in build._modified:
- v = build.__dict__[k]
- 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 'builds' in app:
+ for build in app['builds']:
+ if not isinstance(build, Build):
+ build = Build(build)
+ builds.append(build)
- if not app.Description:
- app.Description = 'No description available'
+ if not app.get('Description'):
+ app['Description'] = 'No description available'
app.builds = sorted_builds(builds)
# TODO create schema using https://pypi.python.org/pypi/jsonschema
jsoninfo = json.load(mf, parse_int=lambda s: s,
parse_float=lambda s: s)
- app.update_fields(jsoninfo)
+ app.update(jsoninfo)
for f in ['Description', 'Maintainer Notes']:
- v = app.get_field(f)
- app.set_field(f, '\n'.join(v))
+ v = app.get(f)
+ if v:
+ app[f] = '\n'.join(v)
return app
def parse_yaml_metadata(mf, app):
yamlinfo = yaml.load(mf, Loader=YamlLoader)
- app.update_fields(yamlinfo)
+ app.update(yamlinfo)
return app
build = None
vc_seen = set()
+ app.builds = []
+
c = 0
for line in mf:
c += 1
except ValueError:
warn_or_exception("Invalid metadata in " + linedesc)
+ if f not in app_fields:
+ warn_or_exception('Unrecognised app field: ' + f)
+
# Translate obsolete fields...
if f == 'Market Version':
f = 'Current Version'
if f == 'Market Version Code':
f = 'Current Version Code'
+ f = f.replace(' ', '')
+
ftype = fieldtype(f)
if ftype not in [TYPE_BUILD, TYPE_BUILD_V2]:
add_comments(f)
warn_or_exception("Unexpected text on same line as "
+ f + " in " + linedesc)
elif ftype == TYPE_STRING:
- app.set_field(f, v)
+ app[f] = v
elif ftype == TYPE_LIST:
- app.set_field(f, split_list_values(v))
+ app[f] = split_list_values(v)
elif ftype == TYPE_BUILD:
if v.endswith("\\"):
mode = 2
elif mode == 1: # Multiline field
if line == '.':
mode = 0
- app.set_field(f, '\n'.join(multiline_lines))
+ app[f] = '\n'.join(multiline_lines)
del multiline_lines[:]
else:
multiline_lines.append(line)
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):
if key not in app.comments:
return
w_comment(line)
def w_field_always(f, v=None):
+ key = field_to_attr(f)
if v is None:
- v = app.get_field(f)
- w_comments(f)
+ v = app.get(key)
+ w_comments(key)
w_field(f, v)
def w_field_nonempty(f, v=None):
+ key = field_to_attr(f)
if v is None:
- v = app.get_field(f)
- w_comments(f)
+ v = app.get(key)
+ w_comments(key)
if v:
w_field(f, v)
generated_redirects = {}
for appid in sortedids:
- app = apps[appid]
+ app = metadata.App(apps[appid])
wikidata = ''
if app.Disabled:
:param repodirs: the repo directories to process
"""
for appid, app in apps.items():
- for build in app.builds:
+ for build in app['builds']:
if not build.disable:
continue
apkfilename = appid + '_' + str(build.vercode) + '.apk'
element.setAttribute('packageName', packageName)
for appid in sortedids:
- app = apps[appid]
+ app = metadata.App(apps[appid])
if app.Disabled is not None:
continue
and config['make_current_version_link'] \
and repodir == 'repo': # only create these
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'
current_version_path = os.path.join(repodir, current_version_file)
if os.path.islink(apklinkname):
logging.debug("Don't know when " + appid + " was last updated")
if bestver == UNSET_VERSION_CODE:
+
if app.Name is None:
app.Name = app.AutoName or appid
app.icon = None
print('Skipping ImportTest!')
return
- app = fdroidserver.metadata.get_default_app_info()
+ app = fdroidserver.metadata.App()
app.UpdateCheckMode = "Tags"
root_dir, src_dir = import_proxy.get_metadata_from_url(app, url)
self.assertEqual(app.RepoType, 'git')
apps = fdroidserver.metadata.read_metadata(xref=True)
for appid in ('org.smssecure.smssecure', 'org.adaway', 'org.videolan.vlc'):
- app = apps[appid]
savepath = os.path.join('metadata', 'dump', appid + '.yaml')
- frommeta = app.field_dict()
+ frommeta = dict(apps[appid])
self.assertTrue(appid in apps)
with open(savepath, 'r') as f:
frompickle = yaml.load(f)
AntiFeatures: []
-Archive Policy: null
-Author Email: null
-Author Name: null
-Auto Name: AdAway
-Auto Update Mode: Version v%v
+ArchivePolicy: null
+AuthorEmail: null
+AuthorName: null
+AutoName: AdAway
+AutoUpdateMode: Version v%v
Binaries: null
Bitcoin: null
Categories:
- System
- Security
Changelog: ''
-Current Version: '3.0'
-Current Version Code: '52'
+CurrentVersion: '3.0'
+CurrentVersionCode: '52'
Description: 'An ad blocker that uses the hosts file. The hosts file
contains a list of mappings between hostnames and IP addresses. When
Disabled: null
Donate: http://sufficientlysecure.org/index.php/adaway
FlattrID: '369138'
-Issue Tracker: https://github.com/dschuermann/ad-away/issues
+IssueTracker: https://github.com/dschuermann/ad-away/issues
License: GPLv3
Litecoin: null
-Maintainer Notes: ''
+MaintainerNotes: ''
Name: null
-No Source Since: ''
+NoSourceSince: ''
Provides: org.sufficientlysecure.adaway
Repo: https://github.com/dschuermann/ad-away.git
-Repo Type: git
-Requires Root: true
-Source Code: https://github.com/dschuermann/ad-away
+RepoType: git
+RequiresRoot: true
+SourceCode: https://github.com/dschuermann/ad-away
Summary: Block advertisements
-Update Check Data: null
-Update Check Ignore: null
-Update Check Mode: Tags
-Update Check Name: null
-Vercode Operation: null
-Web Site: http://sufficientlysecure.org/index.php/adaway
+UpdateCheckData: null
+UpdateCheckIgnore: null
+UpdateCheckMode: Tags
+UpdateCheckName: null
+VercodeOperation: null
+WebSite: http://sufficientlysecure.org/index.php/adaway
added: null
builds:
- antcommands: []
AntiFeatures: []
-Archive Policy: null
-Author Email: null
-Author Name: null
-Auto Name: SMSSecure
-Auto Update Mode: Version v%v
+ArchivePolicy: null
+AuthorEmail: null
+AuthorName: null
+AutoName: SMSSecure
+AutoUpdateMode: Version v%v
Binaries: null
Bitcoin: null
Categories:
- Phone & SMS
Changelog: ''
-Current Version: 0.6.0
-Current Version Code: '102'
+CurrentVersion: 0.6.0
+CurrentVersionCode: '102'
Description: 'SMSSecure is an SMS/MMS application that allows you to protect your
privacy while communicating with friends.
Disabled: null
Donate: null
FlattrID: null
-Issue Tracker: https://github.com/SMSSecure/SMSSecure/issues
+IssueTracker: https://github.com/SMSSecure/SMSSecure/issues
License: GPLv3
Litecoin: null
-Maintainer Notes: ''
+MaintainerNotes: ''
Name: null
-No Source Since: ''
+NoSourceSince: ''
Provides: null
Repo: https://github.com/SMSSecure/SMSSecure
-Repo Type: git
-Requires Root: false
-Source Code: https://github.com/SMSSecure/SMSSecure
+RepoType: git
+RequiresRoot: false
+SourceCode: https://github.com/SMSSecure/SMSSecure
Summary: Send encrypted text messages (SMS)
-Update Check Data: null
-Update Check Ignore: null
-Update Check Mode: Tags
-Update Check Name: null
-Vercode Operation: null
-Web Site: http://www.smssecure.org
+UpdateCheckData: null
+UpdateCheckIgnore: null
+UpdateCheckMode: Tags
+UpdateCheckName: null
+VercodeOperation: null
+WebSite: http://www.smssecure.org
added: null
builds:
- antcommands: []
AntiFeatures: []
-Archive Policy: 9 versions
-Author Email: null
-Author Name: null
-Auto Name: VLC
-Auto Update Mode: None
+ArchivePolicy: 9 versions
+AuthorEmail: null
+AuthorName: null
+AutoName: VLC
+AutoUpdateMode: None
Binaries: null
Bitcoin: null
Categories:
- Multimedia
Changelog: ''
-Current Version: 1.2.6
-Current Version Code: '1030005'
+CurrentVersion: 1.2.6
+CurrentVersionCode: '1030005'
Description: 'Video and audio player that supports a wide range of formats,
for both local and remote playback.
Disabled: null
Donate: http://www.videolan.org/contribute.html#money
FlattrID: null
-Issue Tracker: http://www.videolan.org/support/index.html#bugs
+IssueTracker: http://www.videolan.org/support/index.html#bugs
License: GPLv3
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
'
Name: null
-No Source Since: ''
+NoSourceSince: ''
Provides: null
Repo: git://git.videolan.org/vlc-ports/android.git
-Repo Type: git
-Requires Root: false
-Source Code: http://git.videolan.org/?p=vlc-ports/android.git;a=summary
+RepoType: git
+RequiresRoot: false
+SourceCode: http://git.videolan.org/?p=vlc-ports/android.git;a=summary
Summary: Media player
-Update Check Data: null
-Update Check Ignore: null
-Update Check Mode: Tags
-Update Check Name: null
-Vercode Operation: '%c + 5'
-Web Site: http://www.videolan.org/vlc/download-android.html
+UpdateCheckData: null
+UpdateCheckIgnore: null
+UpdateCheckMode: Tags
+UpdateCheckName: null
+VercodeOperation: '%c + 5'
+WebSite: http://www.videolan.org/vlc/download-android.html
added: null
builds:
- antcommands: []
{
- "Auto Name": "AdAway",
- "Auto Update Mode": "Version v%v",
+ "AutoName": "AdAway",
+ "AutoUpdateMode": "Version v%v",
"Categories": ["System", "Security"],
- "Current Version": "3.0",
- "Current Version Code": 52,
+ "CurrentVersion": "3.0",
+ "CurrentVersionCode": 52,
"Description": [
"An ad blocker that uses the hosts file. The hosts file",
"contains a list of mappings between hostnames and IP addresses. When",
],
"Donate": "http://sufficientlysecure.org/index.php/adaway",
"FlattrID": "369138",
- "Issue Tracker": "https://github.com/dschuermann/ad-away/issues",
+ "IssueTracker": "https://github.com/dschuermann/ad-away/issues",
"License": "GPLv3",
"Provides": "org.sufficientlysecure.adaway",
"Repo": "https://github.com/dschuermann/ad-away.git",
- "Repo Type": "git",
- "Requires Root": true,
- "Source Code": "https://github.com/dschuermann/ad-away",
+ "RepoType": "git",
+ "RequiresRoot": true,
+ "SourceCode": "https://github.com/dschuermann/ad-away",
"Summary": "Block advertisements",
- "Update Check Mode": "Tags",
- "Web Site": "http://sufficientlysecure.org/index.php/adaway",
+ "UpdateCheckMode": "Tags",
+ "WebSite": "http://sufficientlysecure.org/index.php/adaway",
"builds": [
{
Categories:
- Multimedia
License: GPLv3
-Web Site: http://www.videolan.org/vlc/download-android.html
-Source Code: http://git.videolan.org/?p=vlc-ports/android.git;a=summary
-Issue Tracker: "http://www.videolan.org/support/index.html#bugs"
+WebSite: http://www.videolan.org/vlc/download-android.html
+SourceCode: http://git.videolan.org/?p=vlc-ports/android.git;a=summary
+IssueTracker: "http://www.videolan.org/support/index.html#bugs"
Donate: "http://www.videolan.org/contribute.html#money"
-Auto Name: VLC
+AutoName: VLC
Summary: Media player
Description: |
Video and audio player that supports a wide range of formats,
[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
builds:
buildjni: no
ndk: r10d
-Maintainer Notes: |
+MaintainerNotes: |
Instructions and dependencies here: http://wiki.videolan.org/AndroidCompile
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
# +2: x86
# +3: arm
# +4: armv7 (CV)
-Archive Policy: 9 versions
-Auto Update Mode: None
-Update Check Mode: Tags
+ArchivePolicy: 9 versions
+AutoUpdateMode: None
+UpdateCheckMode: Tags
# Only use higher vercode ops, if we do build those arches
-Vercode Operation: "%c + 5"
-Current Version: 1.2.6
-Current Version Code: 1030005
+VercodeOperation: "%c + 5"
+CurrentVersion: 1.2.6
+CurrentVersionCode: 1030005