/**
 * vim: sw=4 ts=4:
 *
 * Gnome Apt package cache view/controller
 *
 * 	(C) 2003, 2004 Filip Van Raemdonck <mechanix@debian.org>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of version 2 of the GNU General Public License as
 * published by the Free Software Foundation.
 *
 * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307
 * USA
 *
 * 	$Id$
 *
 **/

#include <apt-pkg/algorithms.h>
#include <apt-pkg/configuration.h>
#include <apt-pkg/dpkgpm.h>
#include <apt-pkg/error.h>
#include <apt-pkg/pkgsystem.h>
#include <apt-pkg/sourcelist.h>
#include <apt-pkg/strutl.h>

#include <gtk/gtk.h>
#include "acquirestatus.h"
#include "cachecontrol.h"
#include "depresolver.h"
#include "dialogprogress.h"
#include "childprocess.h"
#include "gnome-apt.h"

struct _GAptCacheControlPrivate {
	GAptCacheFile* gcf;
#if 0
	bool RunInstall (pkgPackageManager*);
	bool confirm (void);
#endif
};

static void gapt_cache_control_class_init (GAptCacheControlClass*);
static void gapt_cache_control_init (GAptCacheControl*);

GType
gapt_cache_control_get_type (void) {
	static GType gobj_type = 0;

	if (!gobj_type) {
		static const GTypeInfo gobj_info = {
			sizeof (GAptCacheControlClass), NULL, NULL,
			(GClassInitFunc) gapt_cache_control_class_init,
			NULL, NULL, sizeof (GAptCacheControl), 0,
			(GInstanceInitFunc) gapt_cache_control_init
		};

		gobj_type = g_type_register_static (G_TYPE_OBJECT, "GAptCacheControl", &gobj_info, (GTypeFlags) 0);
	}

	return gobj_type;
}

static void
gapt_cache_control_finalize (GObject* obj) {
	g_free (GAPT_CACHE_CONTROL (obj)->priv);
}

static void
gapt_cache_control_class_init (GAptCacheControlClass* klass) {
	((GObjectClass*) klass)->finalize = gapt_cache_control_finalize;
}

static void
gapt_cache_control_init (GAptCacheControl* gcc) {
	gcc->priv = g_new0 (GAptCacheControlPrivate, 1);
}

GObject*
gapt_cache_control_new (GAptCacheFile* cf) {
	GAptCacheControl* gcc = GAPT_CACHE_CONTROL (g_object_new (GAPT_CACHE_CONTROL_TYPE, NULL));
	gcc->priv->gcf = cf;

	return G_OBJECT (gcc);
}

#define C_PACKAGE 0
#define N_COLUMNS 1

static void
store_add_category (GtkTreeStore* store, const gchar* category_name, vector<string> names) {
	if (!names.empty()) {
		GtkTreeIter iter;

		gtk_tree_store_append (store, &iter, NULL);
		gtk_tree_store_set (store, &iter, C_PACKAGE, category_name, -1);

		sort (names.begin(), names.end());

		vector<string>::iterator i = names.begin();
		while (i != names.end()) {
			GtkTreeIter sub;

			gtk_tree_store_append (store, &sub, &iter);
			gtk_tree_store_set (store, &sub, C_PACKAGE, i->c_str(), -1);

			++i;
		}
	}
}

static void
get_changes (GAptCache* cache, vector<string>& p_remove, vector<string>& p_install,
      vector<string>& p_keep, vector<string>& p_upgrade, vector<string>& p_hold) {
	pkgCache::PkgIterator i = cache->PkgBegin();
	while (!i.end()) {
		if ((*cache)[i].Delete()) {
			p_remove.push_back (i.Name());
		} else if ((*cache)[i].NewInstall()) {
			p_install.push_back (i.Name());
		} else if ((*cache)[i].Upgradable() && !(*cache)[i].Upgrade()
		      && !(*cache)[i].Delete() && i->CurrentVer) {
			p_keep.push_back (i.Name());
		} else if ((*cache)[i].Upgrade()) {
			p_upgrade.push_back (i.Name());
		} else if ((*cache)[i].InstallVer != (pkgCache::Version*) (i.CurrentVer())
		      && i->SelectedState == pkgCache::State::Hold) {
			p_hold.push_back (i.Name());
		}

		++i;
	}
}

static gchar*
stats_string (GAptCache* cache) {
	gulong install = 0, upgrade = 0;

	pkgCache::PkgIterator i = cache->PkgBegin();
	while (!i.end()) {
		if ((*cache)[i].NewInstall()) {
			++install;
		} else if ((*cache)[i].Upgrade()) {
			++upgrade;
		}

		++i;
	}

	return g_strdup_printf (_("%lu packages upgraded, %lu packages newly "
	      "installed, %lu packages to remove.\n%lu packages not upgraded, "
	      "%lu packages not fully installed or removed."),
	      upgrade, install, cache->DelCount(), cache->KeepCount(), cache->BadCount());
}

GtkWidget*
gapt_cache_control_pending (GAptCacheControl* gcc) {
	/* FIXME: create a custom GtkBin class for HIG compliant `groups' */
	GtkWidget* vbox = gtk_vbox_new (FALSE, GAPT_PAD);
	gtk_container_set_border_width (GTK_CONTAINER (vbox), GAPT_PAD);

	GtkWidget* label = gtk_label_new (_("<b>Pending changes</b>"));
	gtk_label_set_use_markup (GTK_LABEL (label), TRUE);
	gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
	gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0);

	GtkWidget* hbox = gtk_hbox_new (FALSE, GAPT_PAD);
	gtk_box_pack_start (GTK_BOX (vbox), hbox, TRUE, TRUE, 0);

	label = gtk_label_new ("");
	gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, GAPT_PAD);

	GtkWidget* child = gtk_vbox_new (FALSE, 0);
	gtk_box_pack_start (GTK_BOX (hbox), child, TRUE, TRUE, 0);

	GtkWidget* sw = gtk_scrolled_window_new (NULL, NULL);
	gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
	gtk_box_pack_start (GTK_BOX (child), sw, TRUE, TRUE, GAPT_PAD);

	GtkTreeIter iter;
	GtkTreeStore* store = gtk_tree_store_new (N_COLUMNS, G_TYPE_STRING);

	vector<string> p_remove, p_install, p_keep, p_upgrade, p_hold;
	get_changes (gcc->priv->gcf->cache(), p_remove, p_install, p_keep, p_upgrade, p_hold);

	store_add_category (store, _("To be installed"), p_install);
	store_add_category (store, _("To be removed"), p_remove);
	store_add_category (store, _("To be kept"), p_keep);
	store_add_category (store, _("To be upgraded"), p_upgrade);

	GtkWidget* tree = gtk_tree_view_new_with_model (GTK_TREE_MODEL (store));
	GtkCellRenderer* renderer = gtk_cell_renderer_text_new();
	GtkTreeViewColumn* column = gtk_tree_view_column_new_with_attributes (
	      NULL, renderer, "text", C_PACKAGE, NULL);
	gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (tree), FALSE);
	gtk_tree_view_append_column (GTK_TREE_VIEW (tree), column);
	gtk_container_add (GTK_CONTAINER (sw), tree);

	gchar* strlbl = stats_string (gcc->priv->gcf->cache());
	label = gtk_label_new (strlbl);
	gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
	gtk_box_pack_start (GTK_BOX (child), label, FALSE, FALSE, 0);
	g_free (strlbl);

	return vbox;
}

static string
broken_string (GAptCache* cache) {
	string out;

	out += _("The following packages have unmet dependencies:\n");

	pkgCache::PkgIterator I = cache->PkgBegin();
	for ( ; !I.end(); I++) {
		if (!(*cache)[I].InstBroken()) continue;

		out += "  ";
		out +=  I.Name();
		out += ":";
		/* FIXME: get the pango string width */
		int Indent = strlen (I.Name()) + 3;
		bool First = true;
		if ((*cache)[I].InstVerIter (*cache).end()) {
			out += "\n";
			continue;
		}

		for (pkgCache::DepIterator D = (*cache)[I].InstVerIter (*cache).DependsList(); !D.end(); ) {
			// Compute a single dependency element (glob or)
			pkgCache::DepIterator Start;
			pkgCache::DepIterator End;
			D.GlobOr (Start, End);

			if (!cache->IsImportantDep (End) ||
			      ((*cache)[End] & pkgDepCache::DepGInstall) == pkgDepCache::DepGInstall)
				continue;

			if (!First)
				for (int J = 0; J != Indent; J++)
					out += ' ';
			First = false;

			out += ' ';
			out += End.DepType();
			out += ": ";
			out += End.TargetPkg().Name();

			// Show a quick summary of the version requirements
			if (End.TargetVer()) {
				out += " (";
				out += End.CompType();
				out += " ";
				out += End.TargetVer();
				out += ")";
			}

			/* Show a summary of the target package if possible. In the case
			   of virtual packages we show nothing */
			pkgCache::PkgIterator Targ = End.TargetPkg();
			if (!Targ->ProvidesList) {
				out += _(" but ");
				pkgCache::VerIterator Ver = (*cache)[Targ].InstVerIter (*cache);
				if (!Ver.end()) {
					out += Ver.VerStr();
					out += _(" is installed");
				} else {
					if ((*cache)[Targ].CandidateVerIter (*cache).end()) {
						if (!Targ->ProvidesList)
							out += _("it is not installable");
						else
							out += _("it is a virtual package");
					} else
						out += _("it is not installed");
				}
			}

			out += "\n";
		}
	}
	return out;
}

void
gapt_cache_control_smart_mark_upgrades (GAptCacheControl* gcc) {
	GAptCache* c = gcc->priv->gcf->cache();

	if (!pkgDistUpgrade (*c)) {
		string broken = _("Unable to figure out an upgrade path, please make "
		      "some changes manually first and then try again.\n\n");
		broken += broken_string (c);

		const gchar* str = broken.c_str();
		if (!g_utf8_validate (str, -1, NULL)) {
			str = g_locale_to_utf8 (str, -1, NULL, NULL, NULL);
		}

		GtkWidget* dialog = gtk_message_dialog_new (
		      NULL, GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR,
		      GTK_BUTTONS_OK, str);
		gnome_apt_setup_dialog (dialog);
		gtk_dialog_run (GTK_DIALOG (dialog));
		gtk_widget_destroy (dialog);
	}
}

static void
popup_details_win (GtkWidget* wdg, string msg) {
	GtkWidget* dialog = gtk_dialog_new_with_buttons (_("Fetch Details"),
	      GTK_WINDOW (wdg), (GtkDialogFlags) (GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT),
		  GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE, NULL);
	gtk_dialog_set_has_separator (GTK_DIALOG (dialog), FALSE);
	gtk_window_set_default_size (GTK_WINDOW (dialog), 600, 360);

	GtkWidget* box = gtk_vbox_new (FALSE, 0);
	gtk_container_set_border_width (GTK_CONTAINER (box), 6);
	gtk_container_add (GTK_CONTAINER (GTK_DIALOG (dialog)->vbox), box);

	GtkWidget* sw = gtk_scrolled_window_new (0, 0);
	gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
	gtk_container_add (GTK_CONTAINER (box), sw);

	GtkWidget* lbl = gtk_label_new (msg.c_str());
	gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (sw), lbl);

	gtk_widget_show_all (dialog);
	gtk_dialog_run (GTK_DIALOG (dialog));
	gtk_widget_destroy (dialog);
}

#define GA_RESPONSE_DETAILS 1
static bool
fetch_broken (pkgAcquire* fetcher) {
	string errors;

	if (_error->empty()) return true;
	while (!_error->empty()) {
		string tstr;
		_error->PopMessage (tstr);
		errors += tstr;
	}

	if (!errors.empty()) {
		GtkWidget* dialog = gtk_message_dialog_new (
		      NULL, GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE,
		      _("There were errors while trying to retrieve some packages; "
		        "do you want to try and continue with those that didn't fail?"));
		gtk_dialog_add_buttons (GTK_DIALOG (dialog), _("Details"), GA_RESPONSE_DETAILS,
		      GTK_STOCK_NO, GTK_RESPONSE_NO, GTK_STOCK_YES, GTK_RESPONSE_YES, NULL);
		gtk_dialog_set_has_separator (GTK_DIALOG (dialog), FALSE);
		gnome_apt_setup_dialog (dialog);

		gboolean keep_running = TRUE;
		gint reply = gtk_dialog_run (GTK_DIALOG (dialog));
		while (keep_running) {
			if (reply != GA_RESPONSE_DETAILS) {
				gtk_widget_destroy (dialog);
				keep_running = FALSE;
				if (reply != GTK_RESPONSE_YES) {
					if (reply != GTK_RESPONSE_NO) {
						g_warning ("Got response: %d", reply);
					}
					return false;
				}
			} else {
				popup_details_win (dialog, errors);
				reply = gtk_dialog_run (GTK_DIALOG (dialog));
			}
		}
	}
	return true;
}

static bool
cache_reopen (GAptCacheFile* c) {
	bool ret = true;

	GtkWidget* dlg = gtk_dialog_new_with_buttons (
	      _("Opening GNOME Apt's Package Cache File"), NULL,
	      GtkDialogFlags (GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_NO_SEPARATOR),
	      GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, NULL);
	gnome_apt_setup_dialog (dlg);
	gtk_window_set_default_size (GTK_WINDOW (dlg), 300, -1);

	GtkWidget* vbox = gtk_vbox_new (FALSE, GAPT_PAD);
	gtk_container_set_border_width (GTK_CONTAINER (vbox), GAPT_PAD);
	gtk_container_add (GTK_CONTAINER (GTK_DIALOG (dlg)->vbox), vbox);

	gtk_widget_show_all (dlg);
	if (!c->Open (OpDialogProgress (vbox).This())) {
		gnome_apt_fatal_dialog (_("Fatal error opening the package cache file "
		      "which describes the packages on your system."));
		ret = false;
	}

	gtk_widget_destroy (dlg);
	return ret;
}

bool
gapt_cache_control_install (GAptCacheControl* gcc, pkgPackageManager* pm) {
	ChildDialog cd (_("Running dpkg"),
	      _("Interact with the package installer in the window below."));

	/* Give up the lock on the cache while the child is running */
	_system->UnLock();
	if (_error->PendingError()) {
		_error->DumpErrors();
	}

	int pid = cd.fork();
	if (pid == -1) {
		string message = _("Unable to run the package manager\n");
		message += g_strerror (errno);
		GtkWidget* dialog = gtk_message_dialog_new (
		      NULL, GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR,
		      GTK_BUTTONS_OK, message.c_str());
		gnome_apt_setup_dialog (dialog);
		gtk_dialog_run (GTK_DIALOG (dialog));
		gtk_widget_destroy (dialog);
		return false;
	} else if (pid == 0) {
		/* No GUI after this! */
		cout << _("  GNOME Apt: Running dpkg...") << endl;
		cout << flush;

		pkgPackageManager::OrderResult Res = pm->DoInstall();

		gint ret = EXIT_SUCCESS;

		if (Res == pkgPackageManager::Completed) {
			cout << _("  GNOME Apt: Install successful. It is now safe to close this window.") << endl;
		} else if (Res == pkgPackageManager::Failed || _error->PendingError()) {
			_error->DumpErrors();
			cerr << _("  GNOME Apt: Install failed. It is now safe to close this window.") << endl;
			ret = EXIT_FAILURE;
		} else {
			cout << _("GNOME Apt: Ugh, I think you need to swap media, "
			      "and gnome-apt doesn't know what do do about this right now. "
			      "Apologies... will be fixed in a future version.") << endl;
			ret = EXIT_FAILURE;
		}

		cout << flush;
		cerr << flush;
		_exit (ret);

	}
	
	/* else */
	bool retval = cd.run();
	if (_error->PendingError()) {
		gnome_apt_error_dialog ("Problems were encountered while running dpkg.");
	}

	if (!_system->Lock())
		gnome_apt_fatal_dialog (_("Failed to get the lock back from dpkg"));

	if (!cache_reopen (gcc->priv->gcf)) {
		return false;
	}

	return retval;
}

bool
gapt_cache_control_confirm (GAptCacheControl* gcc) {
	GAptCache* c = gcc->priv->gcf->cache();

	bool* added = new bool[c->Head().PackageCount];
	/* Set whole array to false */
	memset (static_cast<void*> (added), '\0', c->Head().PackageCount * sizeof (bool));

	string essential;

	pkgCache::PkgIterator i = c->PkgBegin();
	while (!i.end()) {
		if ((i->Flags & pkgCache::Flag::Essential) != pkgCache::Flag::Essential) {
			++i;
			continue;
		}

		if ((*c)[i].Delete()) {
			/* The essential package is being removed */
			if (!added[i->ID]) {
				added[i->ID] = true;
				essential += string (i.Name()) + "\n";
			}
		}

		if (!i->CurrentVer) {
			++i;
			continue;
		}

		pkgCache::DepIterator d = i.CurrentVer().DependsList();
		while (!d.end()) {
			if (d->Type != pkgCache::Dep::PreDepends && d->Type != pkgCache::Dep::Depends) {
				++d;
				continue;
			}

			pkgCache::PkgIterator P = d.SmartTargetPkg();
			if ((*c)[P].Delete()) {
				if (added[P->ID]) {
					++d;
					continue;
				}
				added[P->ID] = true;

				char buf[300];
				g_snprintf (buf, 299, "%s (due to %s) ", P.Name(), i.Name());
				essential += buf;
			}
			++d;
		}
		++i;
	}
	delete []added;

	if (!essential.empty()) {
		string stern_warning = _("WARNING: The following essential packages will be removed\n");
		stern_warning += essential;
		stern_warning += _("This should NOT be done unless you know exactly what you are doing!\n"
		      "Are you sure you want to break your system?");

		GtkWidget* dialog = gtk_message_dialog_new (
		      NULL, GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_QUESTION,
		      GTK_BUTTONS_YES_NO, stern_warning.c_str());
		gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_NO);
		gnome_apt_setup_dialog (dialog);

		gint response = gtk_dialog_run (GTK_DIALOG (dialog));
		gtk_widget_destroy (dialog);
		if (response != GTK_RESPONSE_YES) {
			if (response != GTK_RESPONSE_NO) {
				g_warning ("Got response: %d", response);
			}
			return false;
		}
	}

	GtkWidget* dialog = gtk_dialog_new_with_buttons (_("Complete Run"), NULL,
	      GtkDialogFlags (GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_NO_SEPARATOR),
	      GTK_STOCK_STOP, GTK_RESPONSE_CANCEL, GTK_STOCK_APPLY, GTK_RESPONSE_OK, NULL);
	gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_CANCEL);
	gnome_apt_setup_dialog (dialog);
	gtk_window_set_default_size (GTK_WINDOW (dialog), 400, 360);

	gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox),
	      gapt_cache_control_pending (gcc), TRUE, TRUE, GAPT_PAD);

	GtkWidget* vbox = gtk_vbox_new (FALSE, GAPT_PAD);
	gtk_container_set_border_width (GTK_CONTAINER (vbox), GAPT_PAD);
	gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox), vbox, FALSE, FALSE, 0);

	GtkWidget* label = gtk_label_new (_("Gnome-Apt is ready to perform the "
	      "changes you have requested.\nClick <i>Apply</i> to proceed."));
	gtk_label_set_use_markup (GTK_LABEL (label), TRUE);
	gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
	gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0);

	gtk_widget_show_all (dialog);
	gint response = gtk_dialog_run (GTK_DIALOG (dialog));
	gtk_widget_destroy (dialog);

	if (response == GTK_RESPONSE_OK) {
		return true;
	} else if (response != GTK_RESPONSE_CANCEL && response != GTK_RESPONSE_DELETE_EVENT) {
		g_warning ("Got response: %d", response);
	}

	return false;
}

gboolean
gapt_cache_control_check (GAptCacheControl* gcc, gchar* msg) {
	GAptCache* c = gcc->priv->gcf->cache();

	if (c->BrokenCount()) {
		string message = string (msg) + _(" you should correct this situation "
		      "before you can commit any changes. (Use the `Fix Broken' menu "
			  "item if you want me to do it for you)\n\n");
		message += broken_string (c);

		const gchar* str = message.c_str();
		if (!g_utf8_validate (str, -1, NULL)) {
			str = g_locale_to_utf8 (str, -1, NULL, NULL, NULL);
		}

		GtkWidget* dialog = gtk_message_dialog_new (
		      NULL, GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR,
		      GTK_BUTTONS_CLOSE, str);
		gtk_dialog_set_has_separator (GTK_DIALOG (dialog), FALSE);
		gnome_apt_setup_dialog (dialog);
		gtk_dialog_run (GTK_DIALOG (dialog));
		gtk_widget_destroy (dialog);
		return false;
	}

	return true;
}

gboolean
gapt_cache_control_complete (GAptCacheControl* gcc) {
	GAptCache* c = gcc->priv->gcf->cache();

	if (!c->DelCount() && !c->InstCount() && !c->BadCount()) {
		GtkWidget* dialog = gtk_message_dialog_new (
		      NULL, GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_INFO, GTK_BUTTONS_CLOSE,
		      _("There are no packages to be deleted or installed."));
		gnome_apt_setup_dialog (dialog);
		gtk_dialog_run (GTK_DIALOG (dialog));
		gtk_widget_destroy (dialog);
		return true;	/* Don't generate an error. */
	}

	if (!gapt_cache_control_check (gcc, _("Committing the changes you requested "
	      "would leave the system in a broken state;"))) {
		return false;
	}

	if (gapt_cache_control_confirm (gcc)) {
		if (_config->FindB ("APT::Gnome::Simulate")) {
			pkgSimulate PM (c);
			return PM.DoInstall();
		}

		/* Create the text record parser */
		pkgRecords Recs (*c);
		if (_error->PendingError())
			return false;

		/* Lock the archive directory */
		if (!_config->FindB ("Debug::NoLocking", false)) {
			FileFd Lock (GetLock (_config->FindDir ("Dir::Cache::Archives") + "lock"));
			if (_error->PendingError())
				return _error->Error (_("Unable to lock the download directory"));
		}

		/* Create the download object */
		GAcqStatus Stat;
		pkgAcquire Fetcher(&Stat);

		/* Read the source list */
		pkgSourceList List;
		if (!List.ReadMainList())
			return _error->Error (_("The list of sources could not be read."));

		/* Create the package manager and prepare to download */
		pkgDPkgPM PM (c);
		if (!PM.GetArchives (&Fetcher, &List, &Recs))
			return false;

		/* Display statistics */
		double FetchBytes = Fetcher.FetchNeeded();
		double DebBytes = Fetcher.TotalNeeded();
		if (DebBytes != c->DebSize()) {
			cerr << DebBytes << ',' << c->DebSize() << endl;
			cerr << "How odd.. The sizes didn't match, email apt@packages.debian.org" << endl;
		}

		string confirm;
		gchar buf[512];

		if (DebBytes != FetchBytes)
			g_snprintf (buf, 511, _("Need to get %sB/%sB of archives\n"),
			      SizeToStr (FetchBytes).c_str(), SizeToStr (DebBytes).c_str());
		else
			g_snprintf (buf, 511, _("Need to get %sB of archives\n"),
			      SizeToStr (DebBytes).c_str());
		confirm += buf;

		if (c->UsrSize() >= 0)
			g_snprintf (buf, 511, _("%sB will be used\n"), SizeToStr (c->UsrSize()).c_str());
		else
			g_snprintf (buf, 511, _("%sB will be freed\n"), SizeToStr (0 - c->UsrSize()).c_str());
		confirm += buf;

		if (_error->PendingError())
			return false;

		confirm += _("Continue?");

		GtkWidget* dialog = gtk_message_dialog_new (
		      NULL, GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_QUESTION,
		      GTK_BUTTONS_YES_NO, confirm.c_str());
		gnome_apt_setup_dialog (dialog);

		int reply = gtk_dialog_run (GTK_DIALOG (dialog));
		gtk_widget_destroy (dialog);
		if (reply != GTK_RESPONSE_YES) {
			if (reply != GTK_RESPONSE_NO) {
				g_warning ("Got response: %d", reply);
			}
			return true;	/* Not an error, just a bailout */
		}

		pkgAcquire::RunResult res = Fetcher.Run();
		if (res == pkgAcquire::Failed) {
			return false;
		} else if (res == pkgAcquire::Cancelled) {
			return true;	/* Bail out */
		} else if (res != pkgAcquire::Continue) {
			g_assert_not_reached();
		}

		/* not an error, just a bailout */
		if (!fetch_broken (&Fetcher)) return true;

		/* Try to deal with missing package files */
		if (!PM.FixMissing()) {
			return _error->Error (_("Unable to correct missing packages."));
		}

		return gapt_cache_control_install (gcc, &PM);
	}

	/* User cancelled (not an error) */
	return true;
}

gboolean
gapt_cache_control_update (GAptCacheControl* gcc) {
	pkgSourceList List;
	if (!List.ReadMainList()) {
		return false;
	}

	if (!_config->FindB ("Debug::NoLocking", false)) {
		FileFd Lock (GetLock (_config->FindDir ("Dir::State::Lists") + "lock"));
		if (_error->PendingError()) {
			return _error->Error ("Unable to lock the list directory");
		}
	}

	GAcqStatus Stat;
	pkgAcquire Fetcher (&Stat);

	/* Populate the download object with the source selection */
	if (!List.GetIndexes (&Fetcher)) {
		return false;
	}

	pkgAcquire::RunResult res = Fetcher.Run();
	if (res == pkgAcquire::Failed) {
		return false;
	} else if (res == pkgAcquire::Cancelled) {
		return true;	/* Bailout */
	} else if (res != pkgAcquire::Continue) {
		g_assert_not_reached();
	}

	/* Clean out any old list files */
	if (!Fetcher.Clean (_config->FindDir ("Dir::State::lists")) ||
	      !Fetcher.Clean (_config->FindDir ("Dir::State::lists") + "partial/")) {
		return false;
	}

	return cache_reopen (gcc->priv->gcf);
}

void
gapt_cache_control_remove_package (GAptCacheControl* gcc, pkgCache::Package* pkg) {
	/* FIXME: this belongs down in the cache (which should be able to signal of itself) */
	pkgCache::PkgIterator i (*(gcc->priv->gcf->cache()), pkg);
	if (i->Flags & pkgCache::Flag::Essential) {
		GtkWidget* dialog = gtk_message_dialog_new (
		      NULL, GTK_DIALOG_DESTROY_WITH_PARENT,
		      GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO,
		      _("You are trying to remove an essential package.\n"
		        "This WILL break your system, and is a very, very bad idea.\n"
		        "Are you sure you want to go ahead?"));

		gnome_apt_setup_dialog (dialog);
		gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_NO);
		gint reply = gtk_dialog_run (GTK_DIALOG (dialog));

		if (reply != GTK_RESPONSE_YES) {
			if (reply != GTK_RESPONSE_NO) {
				g_warning ("Got response: %d", reply);
			}
			return;
		}
	}

	gcc->priv->gcf->cache()->MarkDelete (i);
}

gboolean
gapt_cache_control_install_package (GAptCacheControl* gcc, pkgCache::Package* pkg) {
	pkgCache::PkgIterator i (*(gcc->priv->gcf->cache()), pkg);
	bool autoinstall = _config->FindB ("APT::Gnome::AutoInstall");

	gcc->priv->gcf->cache()->MarkInstall (i, autoinstall);

	pkgDepCache::StateCache& state = (*(gcc->priv->gcf->cache()))[i];
	if (!state.Install()) {
		g_warning ("gnome-apt bug: We permitted install, but install did not work");
	}

	if (!autoinstall && state.InstBroken()) {
		GAptDepResolver deps (gcc->priv->gcf->cache());
/* FIXME: list what we'd have to do to fix things. */
/* How to go at it: get the list of deps, do autoinstall, see what changed.
 * But how to revert, then ask...? */
		string message = _("Installing the selected package would introduce unsatisfied "
		      "dependencies.\nShould I try adding and/or removing packages to resolve "
		      "the problems?");

		GtkWidget* dialog = gtk_dialog_new_with_buttons ("Unsatisfied Dependencies", NULL,
		      GtkDialogFlags (GTK_DIALOG_NO_SEPARATOR | GTK_DIALOG_DESTROY_WITH_PARENT),
			  _("Ignore"), GTK_RESPONSE_REJECT,
		      GTK_STOCK_NO, GTK_RESPONSE_NO,
		      GTK_STOCK_YES, GTK_RESPONSE_YES,
		      NULL);
		gnome_apt_setup_dialog (dialog);
		gtk_window_set_default_size (GTK_WINDOW (dialog), 600, 450);

		GtkWidget* vbox = gtk_vbox_new (FALSE, GAPT_PAD);
		gtk_container_set_border_width (GTK_CONTAINER (vbox), GAPT_PAD);
		gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox), vbox, TRUE, TRUE, 0);

		gtk_box_pack_start (GTK_BOX (vbox), deps.depview(), TRUE, TRUE, 0);

		GtkWidget* lbl = gtk_label_new (message.c_str());
		gtk_misc_set_alignment (GTK_MISC (lbl), 0.0, 0.5);
		gtk_box_pack_start (GTK_BOX (vbox), lbl, FALSE, FALSE, 0);

		gtk_widget_show_all (dialog);
		int reply = gtk_dialog_run (GTK_DIALOG (dialog));
		gtk_widget_destroy (dialog);
		if (reply == GTK_RESPONSE_YES) {
			/* Force things */
			gcc->priv->gcf->cache()->MarkInstall (i, true);
		} else if (reply == GTK_RESPONSE_REJECT) {
			/* Nothing to do */
		} else {
			if (reply != GTK_RESPONSE_NO) {
				g_warning ("Got response: %d", reply);
			}
			/* Try reverting to previous state - possible infinite loop? */
			gcc->priv->gcf->cache()->MarkKeep (i);
			return FALSE;
		}
	}

	return TRUE;
}

