AppstreamCollection.fromYaml constructor
AppstreamCollection.fromYaml(
- String yaml
Decodes an Appstream collection in YAML format.
Implementation
factory AppstreamCollection.fromYaml(String yaml) {
var yamlDocuments = loadYamlDocuments(_removeInvalidDocuments(yaml));
if (yamlDocuments.isEmpty) {
throw FormatException('Empty YAML file');
}
var header = yamlDocuments[0];
if (header.contents is! YamlMap) {
throw FormatException('Invalid DEP-11 header');
}
var headerMap = (header.contents as YamlMap);
var file = headerMap['File'];
if (file != 'DEP-11') {
throw FormatException('Not a DEP-11 file');
}
var version = headerMap['Version'];
if (version == null) {
throw FormatException('Missing AppStream version');
}
var origin = headerMap['Origin'];
if (origin == null) {
throw FormatException('Missing repository origin');
}
var priority = headerMap['Priority'];
var mediaBaseUrl = headerMap['MediaBaseUrl'];
var architecture = headerMap['Architecture'];
var components = <AppstreamComponent>[];
for (var doc in yamlDocuments.skip(1)) {
var component = doc.contents as YamlMap;
var id = component['ID'];
if (id == null) {
throw FormatException('Missing component ID');
}
var typeName = component['Type'];
if (typeName == null) {
throw FormatException('Missing component type');
}
var type = _parseComponentType(typeName);
var package = component['Package'];
var name = component['Name'];
if (name == null) {
throw FormatException('Missing component name');
}
var summary = component['Summary'];
if (summary == null) {
throw FormatException('Missing component summary');
}
var description = component['Description'];
var developerName = component['DeveloperName'];
var projectLicense = component['ProjectLicense'];
var projectGroup = component['ProjectGroup'];
var icons = <AppstreamIcon>[];
var icon = component['Icon'];
if (icon != null) {
for (var type in icon.keys) {
switch (type) {
case 'stock':
icons.add(AppstreamStockIcon(icon[type]));
break;
case 'cached':
for (var i in icon[type]) {
icons.add(AppstreamCachedIcon(i['name'],
width: i['width'], height: i['height']));
}
break;
case 'local':
for (var i in icon[type]) {
icons.add(AppstreamLocalIcon(i['name'],
width: i['width'], height: i['height']));
}
break;
case 'remote':
for (var i in icon[type]) {
icons.add(AppstreamRemoteIcon(_makeUrl(mediaBaseUrl, i['url']),
width: i['width'], height: i['height']));
}
break;
}
}
}
var urls = <AppstreamUrl>[];
var url = component['Url'];
if (url != null) {
for (var typeName in url.keys) {
urls.add(
AppstreamUrl(url[typeName] ?? '', type: _parseUrlType(typeName)));
}
}
var launchables = <AppstreamLaunchable>[];
var launchable = component['Launchable'];
if (launchable != null) {
if (launchable is! YamlMap) {
throw FormatException('Invalid Launchable type');
}
for (var typeName in launchable.keys) {
var launchableList = launchable[typeName];
if (launchableList is! YamlList) {
throw FormatException('Invalid Launchable type');
}
switch (typeName) {
case 'desktop-id':
launchables.addAll(
launchableList.map((l) => AppstreamLaunchableDesktopId(l)));
break;
case 'service':
launchables.addAll(
launchableList.map((l) => AppstreamLaunchableService(l)));
break;
case 'cockpit-manifest':
launchables.addAll(launchableList
.map((l) => AppstreamLaunchableCockpitManifest(l)));
break;
case 'url':
launchables
.addAll(launchableList.map((l) => AppstreamLaunchableUrl(l)));
break;
}
}
}
var categories = <String>[];
var categoriesComponent = component['Categories'];
if (categoriesComponent != null) {
if (categoriesComponent is! YamlList) {
throw FormatException('Invalid Categories type');
}
categories.addAll(categoriesComponent.cast<String>());
}
var keywords = <String, List<String>>{};
var keywordsComponent = component['Keywords'];
if (keywordsComponent != null) {
if (keywordsComponent is! YamlMap) {
throw FormatException('Invalid Keywords type');
}
keywords = keywordsComponent.map(
(lang, keywordList) => MapEntry(
lang,
keywordList.nodes
.where((e) => e.value != null)
.map<String>((e) => e.value.toString())
.toList(),
),
);
}
var screenshots = <AppstreamScreenshot>[];
var screenshotsComponent = component['Screenshots'];
if (screenshotsComponent != null) {
if (screenshotsComponent is! YamlList) {
throw FormatException('Invalid Screenshots type');
}
for (var screenshot in screenshotsComponent) {
var isDefault = screenshot['default'] ?? 'false' == 'true';
var caption = screenshot['caption'];
var images = <AppstreamImage>[];
var thumbnails = screenshot['thumbnails'];
if (thumbnails != null) {
if (thumbnails is! YamlList) {
throw FormatException('Invalid thumbnails type');
}
for (var thumbnail in thumbnails) {
var url = thumbnail['url'];
if (url == null) {
throw FormatException('Image missing Url');
}
var width = thumbnail['width'];
var height = thumbnail['height'];
var lang = thumbnail['lang'];
images.add(AppstreamImage(
type: AppstreamImageType.thumbnail,
url: _makeUrl(mediaBaseUrl, url),
width: width,
height: height,
lang: lang));
}
}
var sourceImage = screenshot['source-image'];
if (sourceImage != null) {
var url = sourceImage['url'];
if (url == null) {
throw FormatException('Image missing Url');
}
var width = sourceImage['width'];
var height = sourceImage['height'];
var lang = sourceImage['lang'];
images.add(AppstreamImage(
type: AppstreamImageType.source,
url: _makeUrl(mediaBaseUrl, url),
width: width,
height: height,
lang: lang));
}
screenshots.add(AppstreamScreenshot(
images: images,
caption: caption != null
? _parseYamlTranslatedString(caption)
: const {},
isDefault: isDefault));
}
}
var compulsoryForDesktops = <String>[];
var compulsoryForDesktopsComponent = component['CompulsoryForDesktops'];
if (compulsoryForDesktopsComponent != null) {
if (compulsoryForDesktopsComponent is! YamlList) {
throw FormatException('Invalid CompulsoryForDesktops type');
}
compulsoryForDesktops
.addAll(compulsoryForDesktopsComponent.cast<String>());
}
var releases = <AppstreamRelease>[];
var releasesComponent = component['Releases'];
if (releasesComponent != null) {
if (releasesComponent is! YamlList) {
throw FormatException('Invalid Releases type');
}
for (var release in releasesComponent) {
if (release is! YamlMap) {
throw FormatException('Invalid release type');
}
var version = release['version'];
DateTime? date;
var dateAttribute = release['date'];
var unixTimestamp = release['unix-timestamp'];
if (unixTimestamp != null) {
date = DateTime.fromMillisecondsSinceEpoch(unixTimestamp * 1000,
isUtc: true);
} else if (dateAttribute != null) {
date = DateTime.parse(dateAttribute);
}
AppstreamReleaseType? type;
var typeName = release['type'];
if (typeName != null) {
type = _parseReleaseType(typeName);
}
AppstreamReleaseUrgency? urgency;
var urgencyName = release['urgency'];
if (urgencyName != null) {
urgency = _parseReleaseUrgency(urgencyName);
}
var description = release['description'];
var url = release['url']?['details'];
var issues = <AppstreamIssue>[];
var issuesComponent = release['issues'];
if (issuesComponent != null) {
if (issuesComponent is! YamlList) {
throw FormatException('Invalid issues type');
}
for (var issue in issuesComponent) {
if (issue is! YamlMap) {
throw FormatException('Invalid issue type');
}
var id = issue['id'];
if (id == null) {
throw FormatException('Issue missing id');
}
AppstreamIssueType? type;
var typeName = issue['type'];
if (typeName != null) {
type = _parseIssueType(typeName);
}
var url = issue['url'];
issues.add(AppstreamIssue(id,
type: type ?? AppstreamIssueType.generic, url: url));
}
}
releases.add(AppstreamRelease(
version: _parseYamlVersion(version),
date: date,
type: type ?? AppstreamReleaseType.stable,
urgency: urgency ?? AppstreamReleaseUrgency.medium,
description: description != null
? _parseYamlTranslatedString(description)
: const {},
url: url,
issues: issues));
}
}
var provides = <AppstreamProvides>[];
var providesComponent = component['Provides'];
if (providesComponent != null) {
if (providesComponent is! YamlMap) {
throw FormatException('Invalid Provides type');
}
for (var type in providesComponent.keys) {
var values = providesComponent[type];
if (values is! YamlList) {
throw FormatException('Invalid $type provides');
}
switch (type) {
case 'mediatypes':
case 'mimetypes':
provides.addAll(values.map((e) => AppstreamProvidesMediatype(e)));
break;
case 'libraries':
provides.addAll(values.map((e) => AppstreamProvidesLibrary(e)));
break;
case 'binaries':
provides.addAll(values.map((e) => AppstreamProvidesBinary(e)));
break;
case 'fonts':
for (var fontComponent in values) {
if (fontComponent is! YamlMap) {
throw FormatException('Invalid font provides');
}
var name = fontComponent['name'];
if (name == null) {
throw FormatException('Missing font name');
}
provides.add(AppstreamProvidesFont(name));
}
break;
case 'firmware':
for (var firmwareComponent in values) {
if (firmwareComponent is! YamlMap) {
throw FormatException('Invalid firmware provides');
}
var type = firmwareComponent['type'];
switch (type) {
case 'runtime':
var file = firmwareComponent['file'];
if (file == null) {
throw FormatException('Missing firmware file');
}
provides.add(AppstreamProvidesFirmware(
AppstreamFirmwareType.runtime, file));
break;
case 'flashed':
var guid = firmwareComponent['guid'];
if (guid == null) {
throw FormatException('Missing firmware guid');
}
provides.add(AppstreamProvidesFirmware(
AppstreamFirmwareType.flashed, guid));
break;
}
}
break;
case 'python2':
for (var moduleName in values) {
provides.add(AppstreamProvidesPython2(moduleName));
}
break;
case 'python3':
for (var moduleName in values) {
provides.add(AppstreamProvidesPython3(moduleName));
}
break;
case 'modaliases':
for (var modalias in values) {
provides.add(AppstreamProvidesModalias(modalias));
}
break;
case 'dbus':
for (var dbusComponent in values) {
if (dbusComponent is! YamlMap) {
throw FormatException('Invalid dbus provides');
}
var type = dbusComponent['type'];
if (type == null) {
throw FormatException('Missing DBus bus type');
}
var service = dbusComponent['service'];
if (service == null) {
throw FormatException('Missing DBus service name');
}
provides
.add(AppstreamProvidesDBus(_parseDBusType(type), service));
}
break;
case 'ids':
provides.addAll(values.map((e) => AppstreamProvidesId(e)));
break;
}
}
}
var languages = <AppstreamLanguage>[];
var languagesComponent = component['Languages'];
if (languagesComponent != null) {
if (languagesComponent is! YamlList) {
throw FormatException('Invalid Languages type');
}
for (var language in languagesComponent) {
if (language is! YamlMap) {
throw FormatException('Invalid language type');
}
var locale = language['locale'];
if (locale == null) {
throw FormatException('Missing language locale');
}
var percentage = language['percentage'];
languages.add(AppstreamLanguage(locale, percentage: percentage));
}
}
var contentRatings = <String, Map<String, AppstreamContentRating>>{};
var contentRatingComponent = component['ContentRating'];
if (contentRatingComponent != null) {
if (contentRatingComponent is! YamlMap) {
throw FormatException('Invalid ContentRating type');
}
for (var type in contentRatingComponent.keys) {
contentRatings[type] = contentRatingComponent[type]
.map<String, AppstreamContentRating>((key, value) =>
MapEntry(key as String, _parseContentRating(value)));
}
}
components.add(AppstreamComponent(
id: id,
type: type,
package: package,
name: _parseYamlTranslatedString(name),
summary: _parseYamlTranslatedString(summary),
description: description != null
? _parseYamlTranslatedString(description)
: const {},
developerName: developerName != null
? _parseYamlTranslatedString(developerName)
: const {},
projectLicense: projectLicense,
projectGroup: projectGroup,
icons: icons,
urls: urls,
launchables: launchables,
categories: categories,
keywords: keywords,
screenshots: screenshots,
compulsoryForDesktops: compulsoryForDesktops,
releases: releases,
provides: provides,
languages: languages,
contentRatings: contentRatings));
}
return AppstreamCollection(
version: _parseYamlVersion(version),
origin: origin,
architecture: architecture,
priority: priority,
components: components);
}