#!/usr/bin/env python3
# eos-transient-setup – configures the system for transient sessions
# Copyright (C) 2016-2018 Endless Mobile, Ltd.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

import argparse
import glob
import json
import logging
import os
import subprocess
import sys
import tempfile

import gi
gi.require_version('OSTree', '1.0')
# pylint: disable=wrong-import-position
from gi.repository import GLib, Gio, OSTree  # noqa: E402

log = logging.getLogger(sys.argv[0])

EOS_INSTALLER = 'com.endlessm.Installer.desktop'
EOS_INSTALLER_PATH = os.path.join('/usr/share/applications', EOS_INSTALLER)
LOCAL_APPS_DIR = '/usr/local/share/applications'
LOCAL_DESKTOP_PATH = os.path.join(LOCAL_APPS_DIR, EOS_INSTALLER)

LIVE_SETTINGS_DB = '/var/lib/eos-image-defaults/settings.live'
USER_PROFILE_PATH = '/usr/local/share/dconf/profile/user'
USER_PROFILE = '''user-db:user
file-db:/var/lib/eos-image-defaults/settings.live
file-db:/var/lib/eos-image-defaults/settings
file-db:/usr/share/eos-default-settings/settings
'''

SHELL_SCHEMA = 'org.gnome.shell'
FAVORITE_APPS_KEY = 'favorite-apps'

GS_SCHEMA = 'org.gnome.software'
ALLOW_UPDATES = 'allow-updates'


class AdjustGSettings:
    def __init__(self):
        self.keyfile = GLib.KeyFile()

    def update(self, schema, key, variant):
        """Stages 'variant' as the new value for 'key' in 'schema'."""
        value = variant.print_(False)
        log.info('Updating %s: %s to %s', schema, key, value)
        self.keyfile.set_string(schema.replace('.', '/'), key, value)

    def prepare(self):
        """Stage all settings to be overridden."""
        self.update_favorite_apps()
        self.disallow_app_center_updates()

    def write_dconf_compile(self):
        """Write dconf database with overridden settings.

        We also adjust the 'user' profile to use it.
        """
        with tempfile.TemporaryDirectory(suffix='.d') as tempdir:
            keyfile_path = os.path.join(tempdir, '00-live')
            log.info('writing keyfile to %s', keyfile_path)
            self.keyfile.save_to_file(keyfile_path)

            os.makedirs(os.path.dirname(LIVE_SETTINGS_DB), exist_ok=True)

            args = ['dconf', 'compile', LIVE_SETTINGS_DB, tempdir]
            log.info('$ %s', ' '.join(args))
            subprocess.check_call(args)

        log.info('Installing new DConf profile to %s', USER_PROFILE_PATH)
        os.makedirs(os.path.dirname(USER_PROFILE_PATH), exist_ok=True)
        with open(USER_PROFILE_PATH, 'w') as user_profile_file:
            user_profile_file.write(USER_PROFILE)

    def write_stdout(self):
        """Write keyfile with overridden settings to stdout, for debugging."""
        data, _ = self.keyfile.to_data()
        print(data)

    def update_favorite_apps(self):
        """Adjust default favourite apps, which are shown on the taskbar."""
        settings = Gio.Settings(schema=SHELL_SCHEMA)
        favorite_apps = settings.get_strv(FAVORITE_APPS_KEY)

        # Prepend installer icon
        if EOS_INSTALLER not in favorite_apps:
            favorite_apps.insert(0, EOS_INSTALLER)

        self.update(SHELL_SCHEMA, FAVORITE_APPS_KEY,
                    GLib.Variant('as', favorite_apps))

    def disallow_app_center_updates(self):
        """Forbid installing app updates."""
        self.update(GS_SCHEMA, ALLOW_UPDATES, GLib.Variant('b', False))


def install_installer_desktop_file():
    """Make eos-installer visible in user sessions.

    eos-installer is shipped in all images, but its desktop file contains
    NoDisplay=true. Make a copy with this setting removed so it can be added to
    the desktop and taskbar and found via search.
    """
    log.info('Copying %s to %s with NoDisplay removed',
             EOS_INSTALLER_PATH, LOCAL_DESKTOP_PATH)
    os.makedirs(LOCAL_APPS_DIR, exist_ok=True)

    eos_installer_desktop = GLib.KeyFile()
    eos_installer_desktop.load_from_file(
        EOS_INSTALLER_PATH,
        GLib.KeyFileFlags.KEEP_COMMENTS | GLib.KeyFileFlags.KEEP_TRANSLATIONS,
    )
    eos_installer_desktop.remove_key('Desktop Entry', 'NoDisplay')
    eos_installer_desktop.save_to_file(LOCAL_DESKTOP_PATH)


def reduce_ostree_min_free_space():
    '''Don't require any free space on disk when installing apps. On live
    systems, free space is at most half of physical RAM, and running out is not
    a big deal.'''
    repo = OSTree.Repo.new_default()
    repo.open()
    config = repo.copy_config()
    # -size alone takes precedence but set both for clarity.
    config.set_string('core', 'min-free-space-size', '0MB')
    config.set_integer('core', 'min-free-space-percent', 0)
    repo.write_config(config)


def main():
    """Configures system settings for live sessions."""
    parser = argparse.ArgumentParser(description=main.__doc__)
    parser.add_argument(
        "--dry-run",
        action="store_true",
        help="Just print the DConf keyfile to stdout",
    )
    args = parser.parse_args()

    logging.basicConfig(
        level=logging.INFO,
        format='%(name)s:%(lineno)-3s %(funcName)20s %(levelname)7s: '
               '%(message)s')

    setup = AdjustGSettings()
    setup.prepare()

    if args.dry_run:
        setup.write_stdout()
    else:
        setup.write_dconf_compile()

        reduce_ostree_min_free_space()
        install_installer_desktop_file()


if __name__ == '__main__':
    main()
