#!/usr/bin/env python3

# SPDX-FileCopyrightText: 2022-2023 Pablo Correa Gomez <ablocorrea@hotmail.com>
#
# SPDX-License-Identifier: GPL-3.0-or-later#

# Downloads and places AppStream data in its corresponding locations
# according to the AppStream specification.

import argparse
import os
from pathlib import Path
import re
from requests import get, head
import shutil
import subprocess
import sys
import tarfile
import time
from urllib.parse import urlparse

ARCH = Path('/etc/apk/arch').read_text().strip()
COMPONENTS_EXT = 'xml.gz'
ALPINE_AS_BASE_URL = 'https://appstream.alpinelinux.org/data/{}/{}'
ALPINE_AS_COMPONENTS_URL = ALPINE_AS_BASE_URL + f'/Components-{ARCH}.{COMPONENTS_EXT}'
ALPINE_AS_ICON_SIZES = ['64x64']
ALPINE_AS_ICONS_URL = ALPINE_AS_BASE_URL + '/icons-{}.tar.gz'
ORIGIN_FORMAT = 'alpinelinux-{}-{}'

AS_BASE_FOLDER = ''

def parse_path(path: str) -> (str, str):
    '''
    Gets an URL path as input, and returns the alpine's release
    and repo as output.
    '''
    split = path.split('/')
    release = split[-2]
    if release not in ('edge', 'latest-stable') and not re.match(r'^v\d+\.\d+$', release):
        raise Exception(f'Unsupported release {release}: {path} might be malformed!')
    repo = split[-1]
    if repo not in ['main', 'community', 'testing']:
        raise Exception(f'Unsupported repo {repo}: {path} might be malformed!')
    return (release, repo)


def read_apk_repos() -> list[(str, str)]:
    repos = []
    with open('/etc/apk/repositories', 'r') as f:
        for line in f.readlines():
            url = urlparse(line)
            if 'alpine' in url.netloc or (url.netloc is not None and 'alpine' in url.path):
                release, repo = parse_path(url.path)
                repos.append((release, repo))
            else:
                continue

    return repos


def setup_appstream_data(release: str, repo: str):
    component_url = ALPINE_AS_COMPONENTS_URL.format(release, repo)
    origin = ORIGIN_FORMAT.format(release, repo)
    local_path = f'{AS_BASE_FOLDER}/xml/{origin}.{COMPONENTS_EXT}'

    try:
        mtime = os.path.getmtime(local_path)
    except OSError:
        mtime = 0

    # Ask for the component only if it is newer than then downloaded one
    if_mod_since = time.strftime('%a, %d %b %Y %H:%M:%S GMT',
                                 time.gmtime(mtime))
    r = get(component_url, headers={'If-Modified-Since': if_mod_since})
    # Component exists, but was not modified since the date, skip downloads
    if r.status_code == 304:
        return
    # Make sure we do not continue if there was an error!
    if not r.ok:
        print("ERROR: status code not OK: {r.status_code}")
        sys.exit(1)

    if not os.path.exists(os.path.dirname(local_path)):
        os.makedirs(os.path.dirname(local_path))
    with open(local_path, 'wb') as f:
        f.write(r.content)

    for size in ALPINE_AS_ICON_SIZES:
        icon_url = ALPINE_AS_ICONS_URL.format(release, repo, size)
        r = get(icon_url, stream=True)
        icons = tarfile.open(fileobj=r.raw, mode='r')
        origin = ORIGIN_FORMAT.format(release, repo)
        icons.extractall(f'{AS_BASE_FOLDER}/icons/{origin}/{size}')


def clean_unused_appstream(repos: list[(str, str)]):
    for d in [f'{AS_BASE_FOLDER}/xml', f'{AS_BASE_FOLDER}/icons']:
        for f in os.listdir(d):
            # This is not ours!
            if not f.startswith('alpinelinux-'):
                continue
            release = f.split('-')[1]
            repo = f.split('-')[2].split('.')[0]
            if not (release, repo) in repos:
                f_path = f'{d}/{f}'
                if os.path.isfile(f_path):
                    os.remove(f_path)
                elif os.path.isdir(f_path):
                    shutil.rmtree(f_path)
                else:
                    print("ERROR: cannot clean {f_path}: file type unsupported")


if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument('target_dir',
                        help='Directory where to unpack the AppStream data',
                        nargs='?', default='/var/lib/swcatalog')
    AS_BASE_FOLDER = parser.parse_args().target_dir

    repos = read_apk_repos()
    for repo in repos:
        setup_appstream_data(repo[0], repo[1])
    clean_unused_appstream(repos)
    ret = subprocess.run(['/usr/bin/appstreamcli', 'refresh', '--source=os'])
    if ret.returncode != 0:
        sys.exit(1)
