def main():

    # check we're not top level
    if os.path.exists('./application.py'):
        print 'You cannot run these tools from the top level directory'
        sys.exit(1)

    # remove appstream
    if os.path.exists('./appstream'):
        shutil.rmtree('./appstream')
    if os.path.exists('./icons'):
        shutil.rmtree('./icons')

    # the status HTML page goes here too
    if not os.path.exists('./screenshots'):
        os.makedirs('./screenshots')

    files_all = glob.glob("./packages/*.rpm")
    files = _do_newest_filtering(files_all)
    files.sort()

    log = LoggerItem()
    job = Build()

    for f in files:
        log.update_key(f)
        try:
            job.build(f)
        except Exception as e:
            log.write(LoggerItem.WARNING, str(e))
    job.write_appstream()
    def write_appstream(self):

        valid_apps = []
        for key in self.completed:
            log = LoggerItem(key)
            valid_apps = self.completed[key]

            # group fonts of the same family
            fltr = FontFileFilter()
            valid_apps = fltr.merge(valid_apps)

            # create AppStream XML and icons
            filename = './appstream/' + key + '.xml'
            filename_icons = "./appstream/%s-icons.tar" % key
            root = ET.Element("applications")
            root.set("version", "0.1")
            for app in valid_apps:
                try:
                    app.build_xml(root)
                    #app.write_status()
                except UnicodeEncodeError, e:
                    log.write(LoggerItem.WARNING,
                              "Failed to build %s: %s" % (app.app_id, str(e)))
                    continue
                except TypeError, e:
                    log.write(LoggerItem.WARNING,
                              "Failed to build %s: %s" % (app.app_id, str(e)))
                    continue
 def __init__(self, pkg, cfg):
     self.log = LoggerItem()
     self.app_id = None
     self.app_id_full = None
     self.names = {}
     self.categories = []
     self.descriptions = {}
     self.comments = {}
     self.mimetypes = None
     self.urls = {}
     self.licence = None
     self.pkgnames = []
     self.metadata = {}
     if pkg:
         self.licence = pkg.licence
         self.pkgnames = [pkg.name]
         if pkg.homepage_url:
             self.urls[u'homepage'] = pkg.homepage_url
     self.icon = None
     self.keywords = []
     self.cached_icon = False
     self.cfg = cfg
     self.screenshots = []
     self.compulsory_for_desktop = []
     self.type_id = None
     self.project_group = None
     self.requires_appdata = False
     self.thumbnail_screenshots = True
    def __init__(self):
        self.cfg = config.Config()
        self.log = LoggerItem()
        self.application_ids = {}

        # used as a temp location
        if os.path.exists('./tmp'):
            shutil.rmtree('./tmp')
        os.makedirs('./tmp')
        if os.path.exists('./' + self.cfg.distro_name + '.xml.gz'):
            os.remove('./' + self.cfg.distro_name + '.xml.gz')
        if os.path.exists('./' + self.cfg.distro_name + '-icons.tar.gz'):
            os.remove('./' + self.cfg.distro_name + '-icons.tar.gz')
class Compose:

    def __init__(self):
        self.cfg = config.Config()
        self.log = LoggerItem()
        self.application_ids = {}

        # used as a temp location
        if os.path.exists('./tmp'):
            shutil.rmtree('./tmp')
        os.makedirs('./tmp')
        if os.path.exists('./' + self.cfg.distro_name + '.xml.gz'):
            os.remove('./' + self.cfg.distro_name + '.xml.gz')
        if os.path.exists('./' + self.cfg.distro_name + '-icons.tar.gz'):
            os.remove('./' + self.cfg.distro_name + '-icons.tar.gz')

    def run(self):

        # setup the output XML
        master_root = ET.Element("applications")
        master_root.set("version", "0.1")
        master_tree = ET.ElementTree(master_root)

        # find any extra appstream files
        files = glob.glob("../appstream-extra/*.xml")
        for f in files:
            tree = ET.parse(f)
            root = tree.getroot()
            self.log.update_key(os.path.basename(f))
            for app in root:
                app_id = app.find('id')
                if app_id is None:
                    self.log.write(LoggerItem.WARNING, "appstream id not found")
                    continue

                # add everything
                new = ET.SubElement(master_root, 'application')
                for elem in app:
                    new.append(elem)

                # check for screenshots in ../screenshots-extra/${id}/*
                tmp = Application(None, self.cfg)
                tmp.set_id(app_id.text)
                self.log.write(LoggerItem.INFO, "adding %s" % tmp.app_id_full)
                overrides = glob.glob("../screenshots-extra/%s/*.png" % tmp.app_id)
                if len(overrides) > 0:
                    self.log.write(LoggerItem.INFO,
                                   "adding %i screenshot overrides" % len(overrides))
                for ss_fn in overrides:
                    tmp.add_screenshot_filename(ss_fn)
                tmp.build_xml_screenshots(new)

        # add the generated appstream files
        files = glob.glob("./appstream/*.xml")
        files.sort()

        recognised_types = ['desktop', 'codec', 'font', 'inputmethod']
        for filename in files:
            self.log.update_key(filename)
            try:
                tree = ET.parse(filename)
            except ET.ParseError, e:
                self.log.write(LoggerItem.WARNING, "XML could not be parsed: %s" % str(e))
                continue
            root = tree.getroot()
            for app in root:
                app_id = app.find('id')

                # check type is known
                app_id_type = app_id.get('type')
                if app_id_type not in recognised_types:
                    self.log.write(LoggerItem.WARNING,
                              "appstream id type %s not recognised" % app_id_type)
                    continue

                # detect duplicate IDs in the data
                if self.application_ids.has_key(app_id):
                    found = self.application_ids[app_id.text]
                    self.log.write(LoggerItem.WARNING,
                              "duplicate ID found in %s and %s" % (filename, found))
                    continue

                # add everything that isn't private
                new = ET.SubElement(master_root, 'application')
                for elem in app:
                    if elem.tag.startswith("X-"):
                        continue
                    new.append(elem)

                # success
                self.application_ids[app_id.text] = filename
                self.log.write(LoggerItem.INFO, "adding %s" % app_id.text)

        # write to compressed file
        master = gzip.open('./' + self.cfg.distro_name + '.xml.gz', 'wb')
        master_tree.write(master, 'UTF-8')
        master.close()

        # we have to do this as "tar --concatenate" is broken
        files = glob.glob("./appstream/*.tar")
        files.sort()
        for f in files:
            tar = tarfile.open(f, "r")
            tar.extractall(path='./tmp')
            tar.close()

        # create master icons file
        tar = tarfile.open('./' + self.cfg.distro_name + '-icons.tar', "w")
        files = glob.glob("./tmp/*.png")
        for f in files:
            tar.add(f, arcname=f.split('/')[-1])
        tar.close()

        # compress to save a few Mb
        f_in = open('./' + self.cfg.distro_name + '-icons.tar', 'rb')
        f_out = gzip.open('./' + self.cfg.distro_name + '-icons.tar.gz', 'wb')
        f_out.writelines(f_in)
        f_out.close()
        f_in.close()
        os.remove('./' + self.cfg.distro_name + '-icons.tar')
def main():
    log = LoggerItem()
    cfg = Config()

    # read in AppStream file into several Application objects
    f = gzip.open(sys.argv[1], 'rb')
    tree = ET.parse(f)
    apps = []
    for app in tree.getroot():
        a = Application(None, cfg)
        for elem in app:
            if elem.tag == 'id':
                a.set_id(elem.text)
                a.type_id = elem.get('type')
                log.update_key(a.app_id_full)
                log.write(LoggerItem.INFO, "parsing")
            elif elem.tag == 'name':
                if elem.get(XML_LANG):
                    continue
                a.names['C'] = ensure_unicode(elem.text)
            elif elem.tag == 'summary':
                if elem.get(XML_LANG):
                    continue
                a.comments['C'] = ensure_unicode(elem.text)
            elif elem.tag == 'pkgname':
                a.pkgnames.append(ensure_unicode(elem.text))
            elif elem.tag == 'appcategories':
                for elem2 in elem:
                    a.categories.append(ensure_unicode(elem2.text))
            elif elem.tag == 'keywords':
                for elem2 in elem:
                    a.keywords.append(ensure_unicode(elem2.text))
            elif elem.tag == 'url':
                a.urls[elem.get('type')] = ensure_unicode(elem.text)
            elif elem.tag == 'compulsory_for_desktop':
                a.compulsory_for_desktop.append(ensure_unicode(elem.text))
            elif elem.tag == 'project_group':
                a.project_group = ensure_unicode(elem.text)
            elif elem.tag == 'description':
                description = ''
                if len(elem._children):
                    for elem2 in elem:
                        description += elem2.text + u' '
                else:
                    description = elem.text
                a.descriptions['C'] = ensure_unicode(description)
            elif elem.tag == 'screenshots':
                if a.type_id == 'font':
                    continue
                for elem2 in elem:
                    if elem2.tag != 'screenshot':
                        continue
                    caption = None
                    for elem3 in elem2:
                        if elem3.tag == 'caption':
                            caption = elem3.text
                        elif elem3.tag == 'image':
                            if elem3.get('type') != 'source':
                                continue
                            s = Screenshot(a.app_id, None, caption)
                            s.basename = os.path.basename(elem3.text)
                            a.screenshots.append(s)
        apps.append(a)
    f.close()

    # build status page
    status = open('./screenshots/status.html', 'w')
    status.write('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 ' +
                 'Transitional//EN" ' +
                 '"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\n')
    status.write('<html xmlns="http://www.w3.org/1999/xhtml">\n')
    status.write('<head>\n')
    status.write('<meta http-equiv="Content-Type" content="text/html; ' +
                          'charset=UTF-8" />\n')
    status.write('<title>Application Data Review</title>\n')
    status.write('</head>\n')
    status.write('<body>\n')

    status.write('<h1>Executive summary</h1>\n')
    status.write('<ul>\n')

    # long descriptions
    cnt = 0
    total = len(apps)
    for app in apps:
        if len(app.descriptions) > 0:
            cnt += 1
    tmp = 100 * cnt / total
    status.write("<li>Applications in Fedora with long descriptions: %i (%i%%)</li>" % (cnt, tmp))

    # keywords
    cnt = 0
    total = len(apps)
    for app in apps:
        if len(app.keywords) > 0:
            cnt += 1
    tmp = 100 * cnt / total
    status.write("<li>Applications in Fedora with keywords: %i (%i%%)</li>" % (cnt, tmp))

    # categories
    cnt = 0
    total = len(apps)
    for app in apps:
        if len(app.categories) > 0:
            cnt += 1
    tmp = 100 * cnt / total
    status.write("<li>Applications in Fedora with categories: %i (%i%%)</li>" % (cnt, tmp))

    # screenshots
    cnt = 0
    total = len(apps)
    for app in apps:
        if len(app.screenshots) > 0:
            cnt += 1
    tmp = 100 * cnt / total
    status.write("<li>Applications in Fedora with screenshots: %i (%i%%)</li>" % (cnt, tmp))

    # project apps with appdata
    for project_group in ['GNOME', 'KDE', 'XFCE']:
        cnt = 0
        total = 0
        for app in apps:
            if app.project_group != project_group:
                continue
            total += 1
            if len(app.screenshots) > 0 or len(app.descriptions) > 0:
                cnt += 1
        tmp = 0
        if total > 0:
            tmp = 100 * cnt / total
        status.write("<li>Applications in %s with AppData: %i (%i%%)</li>" % (project_group, cnt, tmp))
    status.write('</ul>\n')

    # write applications
    status.write('<h1>Applications</h1>\n')
    for app in apps:
        if app.type_id == 'font':
            continue
        if app.type_id == 'inputmethod':
            continue
        if app.type_id == 'codec':
            continue
        log.update_key(app.app_id_full)
        log.write(LoggerItem.INFO, "writing")
        try:
            status.write(_to_utf8(_to_html(app)))
        except AttributeError, e:
            log.write(LoggerItem.WARNING, "failed to write %s: %s" % (app, str(e)))
            continue
def update():

    log = LoggerItem()

    # create if we're starting from nothing
    if not os.path.exists('./packages'):
        os.makedirs('./packages')

    # get extra packages needed for some applications
    cfg = Config()
    extra_packages = []
    for e in cfg.get_package_data_list():
        extra_packages.append(e[1])

    # find out what we've got already
    files = glob.glob("./packages/*.rpm")
    files.sort()
    existing = {}
    for f in files:
        fd = os.open(f, os.O_RDONLY)
        try:
            hdr = _ts.hdrFromFdno(fd)
        except Exception as e:
            pass
        else:
            existing[hdr.name] = f
        os.close(fd)
    log.write(LoggerItem.INFO, "found %i existing packages for %s" % (len(existing), cfg.distro_tag))

    # setup yum
    yb = yum.YumBase()
    yb.preconf.releasever = cfg.distro_tag[1:]
    yb.doConfigSetup(errorlevel=-1, debuglevel=-1)
    yb.conf.cache = 0

    # reget the metadata every day
    for repo in yb.repos.listEnabled():
        repo.metadata_expire = 60 * 60 * 24  # 24 hours

    # what is native for this arch
    basearch = rpmUtils.arch.getBaseArch()
    if basearch == 'i386':
        basearch_list = ['i386', 'i486', 'i586', 'i686']
    else:
        basearch_list = [basearch]
    basearch_list.append('noarch')

    # ensure all the repos are enabled
    for repo_id in cfg.repo_ids:
        repo = yb.repos.getRepo(repo_id)
        repo.enable()

    # find all packages
    downloaded = {}
    try:
        pkgs = yb.pkgSack
    except yum.Errors.NoMoreMirrorsRepoError as e:
        log.write(LoggerItem.FAILED, str(e))
        sys.exit(1)
    newest_packages = _do_newest_filtering(pkgs)
    newest_packages.sort()
    for pkg in newest_packages:

        log.update_key(pkg.nvra)

        # not our repo
        if pkg.repoid not in cfg.repo_ids:
            continue

        # not our arch
        if pkg.arch not in basearch_list:
            continue

        # don't download blacklisted packages
        if pkg.name in cfg.get_package_blacklist():
            continue

        # make sure the metadata exists
        repo = yb.repos.getRepo(pkg.repoid)

        # find out if any of the files ship a desktop file
        interesting_files = []
        for instfile in pkg.returnFileEntries():
            for match in cfg.get_interesting_installed_files():
                if fnmatch.fnmatch(instfile, match):
                    interesting_files.append(instfile)
                    break

        # don't download packages without desktop files
        if len(interesting_files) == 0 and pkg.name not in extra_packages:
            continue

        # get base name without the slash
        relativepath = pkg.returnSimple('relativepath')
        pos = relativepath.rfind('/')
        if pos != -1:
            relativepath = relativepath[pos+1:]

        # is in cache?
        path = './packages/' + relativepath
        if os.path.exists(path) and os.path.getsize(path) == int(pkg.returnSimple('packagesize')):
            log.write(LoggerItem.INFO, "up to date")
            downloaded[pkg.name] = True
        else:
            pkg.localpath = path

            # download now
            log.update_key(os.path.basename(path))
            log.write(LoggerItem.INFO, "downloading")
            repo.getPackage(pkg)

            # do we have an old version of this?
            if existing.has_key(pkg.name):
                log.update_key(os.path.basename(existing[pkg.name]))
                log.write(LoggerItem.INFO, "deleting")
                os.remove(existing[pkg.name])
        downloaded[pkg.name] = True

    if len(downloaded) == 0:
        log.update_key()
        log.write(LoggerItem.INFO, "no packages downloaded for %s" % cfg.distro_tag)
        return

    # have any packages been removed?
    log.update_key()
    for i in existing:
        if not downloaded.has_key(i):
            log.write(LoggerItem.INFO, "deleting %s" % existing[i])
            os.remove(existing[i])
class Application:

    def __init__(self, pkg, cfg):
        self.log = LoggerItem()
        self.app_id = None
        self.app_id_full = None
        self.names = {}
        self.categories = []
        self.descriptions = {}
        self.comments = {}
        self.mimetypes = None
        self.urls = {}
        self.licence = None
        self.pkgnames = []
        self.metadata = {}
        if pkg:
            self.licence = pkg.licence
            self.pkgnames = [pkg.name]
            if pkg.homepage_url:
                self.urls[u'homepage'] = pkg.homepage_url
        self.icon = None
        self.keywords = []
        self.cached_icon = False
        self.cfg = cfg
        self.screenshots = []
        self.compulsory_for_desktop = []
        self.type_id = None
        self.project_group = None
        self.requires_appdata = False
        self.thumbnail_screenshots = True

    def __str__(self):
        s = {}
        s['app-id'] = self.app_id_full
        s['type_id'] = self.type_id
        s['names'] = self.names
        s['comments'] = self.comments
        s['categories'] = self.categories
        s['descriptions'] = self.descriptions
        s['mimetypes'] = self.mimetypes
        s['urls'] = self.urls
        s['metadata'] = self.metadata
        s['licence'] = self.licence
        s['pkgnames'] = self.pkgnames
        s['icon'] = self.icon
        s['keywords'] = self.keywords
        s['cached_icon'] = self.cached_icon
        s['screenshots'] = self.screenshots
        s['compulsory_for_desktop'] = self.compulsory_for_desktop
        s['project_group'] = self.project_group
        s['requires_appdata'] = self.requires_appdata
        return str(s)

    def add_appdata_file(self):

        # do we have an AppData file?
        filename = './tmp/usr/share/appdata/' + self.app_id + '.appdata.xml'
        fn_extra = '../appdata-extra/' + self.type_id + '/' + self.app_id + '.appdata.xml'
        if os.path.exists(filename) and os.path.exists(fn_extra):
            self.log.write(LoggerItem.INFO,
                           "deleting %s as upstream AppData file exists" % fn_extra)
            os.remove(fn_extra)

        # just use the extra file in places of the missing upstream one
        if os.path.exists(fn_extra):
            filename = fn_extra

        # need to extract details
        if not os.path.exists(filename):
            return False

        data = AppData()
        if not data.extract(filename):
            self.log.write(LoggerItem.WARNING,
                           "AppData file '%s' could not be parsed" % filename)
            return False

        # check AppData file validates
        enable_validation = self.type_id != 'font'
        if enable_validation and os.path.exists('/usr/bin/appdata-validate'):
            env = os.environ
            p = subprocess.Popen(['/usr/bin/appdata-validate',
                                  '--relax', filename],
                                 cwd='.', env=env, stdout=subprocess.PIPE)
            p.wait()
            if p.returncode:
                for line in p.stdout:
                    line = line.replace('\n', '').decode('utf-8')
                    self.log.write(LoggerItem.WARNING,
                                   "AppData did not validate: %s" % line)

        # check the id matches
        if data.get_id() != self.app_id and data.get_id() != self.app_id_full:
            self.log.write(LoggerItem.WARNING,
                           "The AppData id does not match: " + self.app_id)
            return False

        # check the licence is okay
        if data.get_licence() not in self.cfg.get_content_licences():
            self.log.write(LoggerItem.WARNING,
                           "The AppData licence is not okay for " +
                           self.app_id + ': \'' +
                           data.get_licence() + '\'')
            return False

        # if we have an override, use it for all languages
        tmp = data.get_names()
        if tmp:
            self.names = tmp

        # if we have an override, use it for all languages
        tmp = data.get_summaries()
        if tmp:
            self.comments = tmp

        # get metadata
        tmp = data.get_metadata()
        if tmp:
            # and extra packages we want to add in?
            if 'ExtraPackages' in tmp:
                for pkg in tmp['ExtraPackages'].split(','):
                    if pkg not in self.pkgnames:
                        self.pkgnames.append(pkg)
                del tmp['ExtraPackages']
            self.metadata.update(tmp)

        # get optional bits
        tmp = data.get_urls()
        if tmp:
            for key in tmp:
                self.urls[key] = tmp[key]
        tmp = data.get_project_group()
        if tmp:
            self.project_group = tmp
        try:
            self.descriptions = data.get_descriptions()
        except StandardError, e:
            self.log.write(LoggerItem.WARNING,
                           "failed to add description: %s" % str(e))

        # get screenshots
        tmp = data.get_screenshots()
        for image in tmp:
            self.log.write(LoggerItem.INFO, "downloading %s" % image)
            self.add_screenshot_url(image)

        # get compulsory_for_desktop
        for c in data.get_compulsory_for_desktop():
            if c not in self.compulsory_for_desktop:
                self.compulsory_for_desktop.append(c)
        return True