Blame apps/lensfun-update-data

Packit Service 48484a
#!/usr/bin/env python3
Packit Service 48484a
# -*- coding: utf-8 -*-
Packit Service 48484a
Packit Service 48484a
"""This program fetches the latest version of the Lensfun database from the
Packit Service 48484a
Internet and places it on the local system.  This way, the user can update the
Packit Service 48484a
database conveniently.  Unfortunately, we have to take into account that the
Packit Service 48484a
Git database may have a too new format for the locally installed Lensfun.
Packit Service 48484a
Then, it may fetch backports of the database from other URLs.
Packit Service 48484a
Packit Service 48484a
This program must be called with root privileges.  It stores the new database
Packit Service 48484a
in `/var/lib/lensfun-updates`.
Packit Service 48484a
Packit Service 48484a
The repository of databases resides at a base URL.  Below that URL, there is
Packit Service 48484a
the file versions.json.  It contains a list with three elements.  The first is
Packit Service 48484a
the database timestamp, the second is a list of available version numbers, and
Packit Service 48484a
the third is a list of strings which represents further alternative base URLs
Packit Service 48484a
to look at.  So, the file may contain the following::
Packit Service 48484a
Packit Service 48484a
    [1386797501, [1, 2, 3], ["http://wilson.bronger.org/"]]
Packit Service 48484a
Packit Service 48484a
All URLs must end with a slash.  For every version number, there must be a file
Packit Service 48484a
called version_<versionnumber>.tar.bz2.  So in our case, there must be the
Packit Service 48484a
files
Packit Service 48484a
Packit Service 48484a
::
Packit Service 48484a
Packit Service 48484a
    version_1.tar.bz2
Packit Service 48484a
    version_2.tar.bz2
Packit Service 48484a
    version_3.tar.bz2
Packit Service 48484a
Packit Service 48484a
in the same directory as versions.json.  These tar balls contain the Lensfun
Packit Service 48484a
database with the given timestamp and version.
Packit Service 48484a
Packit Service 48484a
The timestamps are the number of seconds since the Epoch as an int.
Packit Service 48484a
Packit Service 48484a
Packit Service 48484a
Diagnostics:
Packit Service 48484a
Packit Service 48484a
Status code 0 -- successfully installed updates
Packit Service 48484a
            1 -- no newer upstream database found for last installed Lensfun
Packit Service 48484a
            3 -- no location was responsive; maybe network problems
Packit Service 48484a
Packit Service 48484a
"""
Packit Service 48484a
Packit Service 48484a
import urllib.request, shutil, sys, os, time, calendar, tarfile, json, glob
Packit Service 48484a
import lensfun
Packit Service 48484a
Packit Service 48484a
Packit Service 48484a
database_version = lensfun.get_database_version()
Packit Service 48484a
Packit Service 48484a
lensfun_updates_dir = "/var/lib/lensfun-updates"
Packit Service 48484a
if os.name == "posix" and os.geteuid() != 0:
Packit Service 48484a
    lensfun_updates_dir = os.path.join(os.path.expanduser("~"),
Packit Service 48484a
                                       ".local", "share", "lensfun", "updates")
Packit Service 48484a
    print("Info: root privileges needed for updating the system database.")
Packit Service 48484a
    print("Info: updating user DB in '%s'" % lensfun_updates_dir)
Packit Service 48484a
Packit Service 48484a
Packit Service 48484a
def seconds_since_epoch():
Packit Service 48484a
    return calendar.timegm(time.gmtime())
Packit Service 48484a
Packit Service 48484a
Packit Service 48484a
def detect_local_timestamp(version):
Packit Service 48484a
    if version == database_version:
Packit Service 48484a
        return lensfun.get_core_database()[0]
Packit Service 48484a
Packit Service 48484a
    def detect_single_timestamp(path):
Packit Service 48484a
        try:
Packit Service 48484a
            return int(open(os.path.join(path, "version_{}".format(version), "timestamp.txt")).read())
Packit Service 48484a
        except (FileNotFoundError, ValueError):
Packit Service 48484a
            return 0
Packit Service 48484a
Packit Service 48484a
    directory_candidates = {"/usr/share/lensfun", "/usr/local/share/lensfun",
Packit Service 48484a
                            "/var/lib/lensfun-updates", os.path.expanduser("~/.local/share/lensfun/updates")}
Packit Service 48484a
    return max(map(detect_single_timestamp, directory_candidates))
Packit Service 48484a
Packit Service 48484a
Packit Service 48484a
class Location:
Packit Service 48484a
Packit Service 48484a
    def __init__(self, base_url, version, timestamp):
Packit Service 48484a
        self.base_url, self.version, self.timestamp = base_url, version, timestamp
Packit Service 48484a
Packit Service 48484a
    def __lt__(self, other):
Packit Service 48484a
        return self.timestamp < other.timestamp
Packit Service 48484a
Packit Service 48484a
    def extract(self, directory):
Packit Service 48484a
        tar = tarfile.open(fileobj=urllib.request.urlopen(self.base_url + "version_{}.tar.bz2".format(self.version)),
Packit Service 48484a
                           mode="r|*")
Packit Service 48484a
        tar.extractall(directory)
Packit Service 48484a
        tar.close()
Packit Service 48484a
Packit Service 48484a
Packit Service 48484a
def detect_local_database_versions():
Packit Service 48484a
    versions = {database_version}
Packit Service 48484a
    for directory in glob.glob("/usr/share/lensfun/version_*") + glob.glob("/usr/local/share/lensfun/version_*"):
Packit Service 48484a
        if os.path.isdir(directory):
Packit Service 48484a
            try:
Packit Service 48484a
                versions.add(int(directory.partition("version_")[2]))
Packit Service 48484a
            except ValueError:
Packit Service 48484a
                pass
Packit Service 48484a
    return versions
Packit Service 48484a
local_database_versions = detect_local_database_versions()
Packit Service 48484a
Packit Service 48484a
Packit Service 48484a
locations = {version: set() for version in local_database_versions}
Packit Service 48484a
local_timestamps = {version: detect_local_timestamp(version) for version in local_database_versions}
Packit Service 48484a
seen_urls = set()
Packit Service 48484a
at_least_one_responsive_location = False
Packit Service 48484a
Packit Service 48484a
def read_location(base_url):
Packit Service 48484a
    global at_least_one_responsive_location
Packit Service 48484a
    if base_url not in seen_urls and len(seen_urls) < 50:
Packit Service 48484a
        seen_urls.add(base_url)
Packit Service 48484a
        print("Reading {} …".format(base_url + "versions.json"))
Packit Service 48484a
        try:
Packit Service 48484a
            response = urllib.request.urlopen(base_url + "versions.json")
Packit Service 48484a
        except (urllib.error.HTTPError, ValueError):
Packit Service 48484a
            print("  Error: URL could not be opened.")
Packit Service 48484a
        else:
Packit Service 48484a
            try:
Packit Service 48484a
                timestamp, versions, alternatives = json.loads(response.read().decode("utf-8"))
Packit Service 48484a
            except ValueError:
Packit Service 48484a
                print("  Error: Invalid data received.")
Packit Service 48484a
            else:
Packit Service 48484a
                at_least_one_responsive_location = True
Packit Service 48484a
                versions = set(versions) & set(locations)
Packit Service 48484a
                for version in versions:
Packit Service 48484a
                    if timestamp > local_timestamps[version]:
Packit Service 48484a
                        locations[version].add(Location(base_url, version, timestamp))
Packit Service 48484a
                for base_url in alternatives:
Packit Service 48484a
                    read_location(base_url)
Packit Service 48484a
Packit Service 48484a
read_location("http://lensfun.sourceforge.net/db/")
Packit Service 48484a
read_location("http://wilson.bronger.org/lensfun-db/")
Packit Service 48484a
Packit Service 48484a
if not at_least_one_responsive_location:
Packit Service 48484a
    print("Fatal: No location was responsive.  Network down?")
Packit Service 48484a
    sys.exit(3)
Packit Service 48484a
elif not locations[database_version]:
Packit Service 48484a
    print("Info: No newer database was found for last installed Lensfun.")
Packit Service 48484a
    sys.exit(1)
Packit Service 48484a
Packit Service 48484a
Packit Service 48484a
for version, location_list in locations.items():
Packit Service 48484a
    try:
Packit Service 48484a
        best_location = max(location_list)
Packit Service 48484a
    except ValueError:
Packit Service 48484a
        continue
Packit Service 48484a
    updates_dir = os.path.join(lensfun_updates_dir, "version_{}".format(version))
Packit Service 48484a
    shutil.rmtree(updates_dir, ignore_errors=True)
Packit Service 48484a
    os.makedirs(updates_dir)
Packit Service 48484a
    best_location.extract(updates_dir)
Packit Service 48484a
    open(os.path.join(updates_dir, "timestamp.txt"), "w").write(str(best_location.timestamp))
Packit Service 48484a
    print("Successfully updated the database in " + updates_dir + ".")