/*
 * Copyright 2017 Richard Hughes <richard@hughsie.com>
 *
 * SPDX-License-Identifier: LGPL-2.1-or-later
 */

#define G_LOG_DOMAIN "FuRemote"

#include "config.h"

#include "fwupd-remote-private.h"

#include "fu-remote.h"

struct _FuRemote {
	FwupdRemote parent_instance;
};

G_DEFINE_TYPE(FuRemote, fu_remote, FWUPD_TYPE_REMOTE)

#define FWUPD_REMOTE_CONFIG_DEFAULT_REFRESH_INTERVAL 86400 /* 24h */

/**
 * fu_remote_load_from_filename:
 * @self: a #FwupdRemote
 * @filename: (not nullable): a filename
 * @cancellable: (nullable): optional #GCancellable
 * @error: (nullable): optional return location for an error
 *
 * Loads metadata about the remote from a keyfile.
 * This can be called zero or multiple times for each remote.
 *
 * Returns: %TRUE for success
 **/
gboolean
fu_remote_load_from_filename(FwupdRemote *self,
			     const gchar *filename,
			     GCancellable *cancellable,
			     GError **error)
{
	const gchar *group = "fwupd Remote";
	g_autofree gchar *id = NULL;
	g_autoptr(GKeyFile) kf = NULL;

	g_return_val_if_fail(FWUPD_IS_REMOTE(self), FALSE);
	g_return_val_if_fail(filename != NULL, FALSE);
	g_return_val_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable), FALSE);
	g_return_val_if_fail(error == NULL || *error == NULL, FALSE);

	/* set ID */
	id = g_path_get_basename(filename);
	fwupd_remote_set_id(self, id);

	/* load file */
	kf = g_key_file_new();
	if (!g_key_file_load_from_file(kf, filename, G_KEY_FILE_NONE, error))
		return FALSE;

	/* the first remote sets the URI, even if it's file:// to the cache */
	if (g_key_file_has_key(kf, group, "MetadataURI", NULL)) {
		g_autofree gchar *tmp = g_key_file_get_string(kf, group, "MetadataURI", NULL);
		if (g_str_has_prefix(tmp, "file://")) {
			const gchar *filename_cache = tmp;
			if (g_str_has_prefix(filename_cache, "file://"))
				filename_cache += 7;
			if (g_file_test(filename_cache, G_FILE_TEST_IS_DIR))
				fwupd_remote_set_kind(self, FWUPD_REMOTE_KIND_DIRECTORY);
			else
				fwupd_remote_set_kind(self, FWUPD_REMOTE_KIND_LOCAL);
			fwupd_remote_set_filename_cache(self, filename_cache);
		} else if (g_str_has_prefix(tmp, "http://") || g_str_has_prefix(tmp, "https://") ||
			   g_str_has_prefix(tmp, "ipfs://") || g_str_has_prefix(tmp, "ipns://")) {
			fwupd_remote_set_kind(self, FWUPD_REMOTE_KIND_DOWNLOAD);
			fwupd_remote_set_refresh_interval(
			    self,
			    FWUPD_REMOTE_CONFIG_DEFAULT_REFRESH_INTERVAL);
			fwupd_remote_set_metadata_uri(self, tmp);
		}
	}

	/* all keys are optional */
	if (g_key_file_has_key(kf, group, "Enabled", NULL)) {
		if (g_key_file_get_boolean(kf, group, "Enabled", NULL))
			fwupd_remote_add_flag(self, FWUPD_REMOTE_FLAG_ENABLED);
		else
			fwupd_remote_remove_flag(self, FWUPD_REMOTE_FLAG_ENABLED);
	}
	if (g_key_file_has_key(kf, group, "ApprovalRequired", NULL)) {
		if (g_key_file_get_boolean(kf, group, "ApprovalRequired", NULL))
			fwupd_remote_add_flag(self, FWUPD_REMOTE_FLAG_APPROVAL_REQUIRED);
		else
			fwupd_remote_remove_flag(self, FWUPD_REMOTE_FLAG_APPROVAL_REQUIRED);
	}
	if (g_key_file_has_key(kf, group, "NoPhasedUpdates", NULL)) {
		if (g_key_file_get_boolean(kf, group, "NoPhasedUpdates", NULL))
			fwupd_remote_add_flag(self, FWUPD_REMOTE_FLAG_NO_PHASED_UPDATES);
		else
			fwupd_remote_remove_flag(self, FWUPD_REMOTE_FLAG_NO_PHASED_UPDATES);
	}
	if (g_key_file_has_key(kf, group, "Title", NULL)) {
		g_autofree gchar *tmp = g_key_file_get_string(kf, group, "Title", NULL);
		fwupd_remote_set_title(self, tmp);
	}
	if (g_key_file_has_key(kf, group, "PrivacyURI", NULL)) {
		g_autofree gchar *tmp = g_key_file_get_string(kf, group, "PrivacyURI", NULL);
		fwupd_remote_set_privacy_uri(self, tmp);
	}
	if (g_key_file_has_key(kf, group, "RefreshInterval", NULL)) {
		fwupd_remote_set_refresh_interval(
		    self,
		    g_key_file_get_uint64(kf, group, "RefreshInterval", NULL));
	}
	if (g_key_file_has_key(kf, group, "ReportURI", NULL)) {
		g_autofree gchar *tmp = g_key_file_get_string(kf, group, "ReportURI", NULL);
		fwupd_remote_set_report_uri(self, tmp);
	}
	if (g_key_file_has_key(kf, group, "Username", NULL)) {
		g_autofree gchar *tmp = g_key_file_get_string(kf, group, "Username", NULL);
		fwupd_remote_set_username(self, tmp);
	}
	if (g_key_file_has_key(kf, group, "Password", NULL)) {
		g_autofree gchar *tmp = g_key_file_get_string(kf, group, "Password", NULL);
		fwupd_remote_set_password(self, tmp);
	}
	if (g_key_file_has_key(kf, group, "FirmwareBaseURI", NULL)) {
		g_autofree gchar *tmp = g_key_file_get_string(kf, group, "FirmwareBaseURI", NULL);
		fwupd_remote_set_firmware_base_uri(self, tmp);
	}
	if (g_key_file_has_key(kf, group, "OrderBefore", NULL)) {
		g_autofree gchar *tmp = g_key_file_get_string(kf, group, "OrderBefore", NULL);
		fwupd_remote_set_order_before(self, tmp);
	}
	if (g_key_file_has_key(kf, group, "OrderAfter", NULL)) {
		g_autofree gchar *tmp = g_key_file_get_string(kf, group, "OrderAfter", NULL);
		fwupd_remote_set_order_after(self, tmp);
	}
	if (g_key_file_has_key(kf, group, "AutomaticReports", NULL)) {
		if (g_key_file_get_boolean(kf, group, "AutomaticReports", NULL))
			fwupd_remote_add_flag(self, FWUPD_REMOTE_FLAG_AUTOMATIC_REPORTS);
		else
			fwupd_remote_remove_flag(self, FWUPD_REMOTE_FLAG_AUTOMATIC_REPORTS);
	}
	if (g_key_file_has_key(kf, group, "AutomaticSecurityReports", NULL)) {
		if (g_key_file_get_boolean(kf, group, "AutomaticSecurityReports", NULL))
			fwupd_remote_add_flag(self, FWUPD_REMOTE_FLAG_AUTOMATIC_SECURITY_REPORTS);
		else
			fwupd_remote_remove_flag(self,
						 FWUPD_REMOTE_FLAG_AUTOMATIC_SECURITY_REPORTS);
	}

	/* old versions of fwupd used empty strings to mean "unset" and the package manager
	 * might not have replaced the file marked as a config file due to modification */
	if (g_strcmp0(fwupd_remote_get_username(self), "") == 0 &&
	    g_strcmp0(fwupd_remote_get_password(self), "") == 0) {
		fwupd_remote_set_username(self, NULL);
		fwupd_remote_set_password(self, NULL);
	}

	/* success */
	fwupd_remote_set_filename_source(self, filename);
	return TRUE;
}

/**
 * fu_remote_save_to_filename:
 * @self: a #FwupdRemote
 * @filename: (not nullable): a filename
 * @cancellable: (nullable): optional #GCancellable
 * @error: (nullable): optional return location for an error
 *
 * Saves metadata about the remote to a keyfile.
 *
 * Returns: %TRUE for success
 **/
gboolean
fu_remote_save_to_filename(FwupdRemote *self,
			   const gchar *filename,
			   GCancellable *cancellable,
			   GError **error)
{
	const gchar *group = "fwupd Remote";
	g_autoptr(GKeyFile) kf = g_key_file_new();

	g_return_val_if_fail(FWUPD_IS_REMOTE(self), FALSE);
	g_return_val_if_fail(filename != NULL, FALSE);
	g_return_val_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable), FALSE);
	g_return_val_if_fail(error == NULL || *error == NULL, FALSE);

	/* optional keys */
	if (fwupd_remote_get_metadata_uri(self) != NULL)
		g_key_file_set_string(kf,
				      group,
				      "MetadataURI",
				      fwupd_remote_get_metadata_uri(self));
	if (fwupd_remote_get_title(self) != NULL)
		g_key_file_set_string(kf, group, "Title", fwupd_remote_get_title(self));
	if (fwupd_remote_get_privacy_uri(self) != NULL)
		g_key_file_set_string(kf, group, "PrivacyURI", fwupd_remote_get_privacy_uri(self));
	if (fwupd_remote_get_report_uri(self) != NULL)
		g_key_file_set_string(kf, group, "ReportURI", fwupd_remote_get_report_uri(self));
	if (fwupd_remote_get_refresh_interval(self) != 0)
		g_key_file_set_uint64(kf,
				      group,
				      "RefreshInterval",
				      fwupd_remote_get_refresh_interval(self));
	if (fwupd_remote_get_username(self) != NULL)
		g_key_file_set_string(kf, group, "Username", fwupd_remote_get_username(self));
	if (fwupd_remote_get_password(self) != NULL)
		g_key_file_set_string(kf, group, "Password", fwupd_remote_get_password(self));
	if (fwupd_remote_get_firmware_base_uri(self) != NULL)
		g_key_file_set_string(kf,
				      group,
				      "FirmwareBaseURI",
				      fwupd_remote_get_firmware_base_uri(self));
	if (fwupd_remote_get_order_after(self) != NULL) {
		g_autofree gchar *str = g_strjoinv(";", fwupd_remote_get_order_after(self));
		g_key_file_set_string(kf, group, "OrderAfter", str);
	}
	if (fwupd_remote_get_order_before(self) != NULL) {
		g_autofree gchar *str = g_strjoinv(";", fwupd_remote_get_order_before(self));
		g_key_file_set_string(kf, group, "OrderBefore", str);
	}
	if (fwupd_remote_has_flag(self, FWUPD_REMOTE_FLAG_ENABLED))
		g_key_file_set_boolean(kf, group, "Enabled", TRUE);
	if (fwupd_remote_has_flag(self, FWUPD_REMOTE_FLAG_APPROVAL_REQUIRED))
		g_key_file_set_boolean(kf, group, "ApprovalRequired", TRUE);
	if (fwupd_remote_has_flag(self, FWUPD_REMOTE_FLAG_AUTOMATIC_REPORTS))
		g_key_file_set_boolean(kf, group, "AutomaticReports", TRUE);
	if (fwupd_remote_has_flag(self, FWUPD_REMOTE_FLAG_AUTOMATIC_SECURITY_REPORTS))
		g_key_file_set_boolean(kf, group, "AutomaticSecurityReports", TRUE);

	/* save file */
	return g_key_file_save_to_file(kf, filename, error);
}

/**
 * fu_remote_clean:
 * @self: a #FwupdRemote
 * @error: (nullable): optional return location for an error
 *
 * Cleans all files in the remote cachedir.
 *
 * Returns: %TRUE for success
 **/
gboolean
fu_remote_clean(FwupdRemote *self, GError **error)
{
	g_autofree gchar *dirname = NULL;
	g_autoptr(GPtrArray) files = NULL;
	g_autoptr(GError) error_local = NULL;

	g_return_val_if_fail(FWUPD_IS_REMOTE(self), FALSE);
	g_return_val_if_fail(error == NULL || *error == NULL, FALSE);

	dirname = g_path_get_dirname(fwupd_remote_get_filename_cache(self));
	files = fu_path_get_files(dirname, &error_local);
	if (files == NULL) {
		if (g_error_matches(error_local, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE))
			return TRUE;
		g_propagate_error(error, g_steal_pointer(&error_local));
		return FALSE;
	}
	for (guint i = 0; i < files->len; i++) {
		const gchar *fn = g_ptr_array_index(files, i);
		g_autoptr(GFile) file = g_file_new_for_path(fn);

		/* cache */
		if (!g_file_delete(file, NULL, error)) {
			fwupd_error_convert(error);
			g_prefix_error(error, "failed to delete %s: ", fn);
			return FALSE;
		}
	}

	/* success */
	if (!fwupd_remote_ensure_checksum_sig(self, error))
		return FALSE;
	return fwupd_remote_ensure_mtime(self, error);
}

static void
fu_remote_init(FuRemote *self)
{
}

static void
fu_remote_class_init(FuRemoteClass *klass)
{
}

FwupdRemote *
fu_remote_new(void)
{
	return FWUPD_REMOTE(g_object_new(FU_TYPE_REMOTE, NULL));
}
