[Aptitude-svn-commit] r4115 - in branches/aptitude-0.3/aptitude: . src src/cmdline src/generic

Daniel Burrows dburrows at costa.debian.org
Mon Sep 19 20:36:19 UTC 2005


Author: dburrows
Date: Mon Sep 19 20:36:15 2005
New Revision: 4115

Added:
   branches/aptitude-0.3/aptitude/src/cmdline/cmdline_spinner.cc
   branches/aptitude-0.3/aptitude/src/cmdline/cmdline_spinner.h
Modified:
   branches/aptitude-0.3/aptitude/ChangeLog
   branches/aptitude-0.3/aptitude/src/broken_indicator.cc
   branches/aptitude-0.3/aptitude/src/cmdline/Makefile.am
   branches/aptitude-0.3/aptitude/src/cmdline/cmdline_resolver.cc
   branches/aptitude-0.3/aptitude/src/generic/resolver_manager.cc
   branches/aptitude-0.3/aptitude/src/generic/resolver_manager.h
   branches/aptitude-0.3/aptitude/src/solution_dialog.cc
   branches/aptitude-0.3/aptitude/src/solution_screen.cc
   branches/aptitude-0.3/aptitude/src/ui.cc
Log:
Don't block the UI while the resolver is running.

Modified: branches/aptitude-0.3/aptitude/ChangeLog
==============================================================================
--- branches/aptitude-0.3/aptitude/ChangeLog	(original)
+++ branches/aptitude-0.3/aptitude/ChangeLog	Mon Sep 19 20:36:15 2005
@@ -1,5 +1,10 @@
 2005-09-19  Daniel Burrows  <dburrows at debian.org>
 
+	* src/broken_indicator.cc, src/cmdline/cmdline_resolver.cc, src/cmdline/cmdline_spinner.cc, src/cmdline/cmdline_spinner.h, src/cmdline/Makefile.am, src/generic/resolver_manager.cc, src/generic/resolver_manager.h, src/solution_dialog.cc, src/solution_screen.cc, src/ui.cc:
+
+	  Use the background thread to keep the UI responsive while the
+	  resolver is running.
+
 	* src/vscreen/vscreen.cc:
 
 	  Fix the timeout thread; it was utterly broken (whoops).

Modified: branches/aptitude-0.3/aptitude/src/broken_indicator.cc
==============================================================================
--- branches/aptitude-0.3/aptitude/src/broken_indicator.cc	(original)
+++ branches/aptitude-0.3/aptitude/src/broken_indicator.cc	Mon Sep 19 20:36:15 2005
@@ -23,8 +23,8 @@
 
 #include <generic/apt.h>
 #include <generic/aptcache.h>
-
 #include <generic/aptitude_resolver_universe.h>
+#include <generic/config_signal.h>
 #include <generic/problemresolver/exceptions.h>
 #include <generic/problemresolver/solution.h>
 #include <generic/resolver_manager.h>
@@ -34,6 +34,7 @@
 #include <vscreen/config/keybindings.h>
 #include <vscreen/fragment.h>
 #include <vscreen/vs_text_layout.h>
+#include <vscreen/vscreen.h>
 
 #include <apt-pkg/pkgsystem.h>
 
@@ -60,25 +61,23 @@
    */
   bool last_complete;
 
+  /** Tracks the phase of the visual "spinner". */
+  int spin_count;
+
   void handle_cache_reload()
   {
     if(apt_cache_file)
-      {
-	(*apt_cache_file)->package_state_changed.connect(sigc::mem_fun(*this, &broken_indicator::update));
-	resman->selected_solution_changed.connect(sigc::mem_fun(*this, &broken_indicator::update));
-      }
+      resman->state_changed.connect(sigc::mem_fun(*this, &broken_indicator::post_update));
 
     update();
   }
 
 protected:
   broken_indicator()
+    :spin_count(0)
   {
     if(apt_cache_file)
-      {
-	(*apt_cache_file)->package_state_changed.connect(sigc::mem_fun(*this, &broken_indicator::update));
-	resman->selected_solution_changed.connect(sigc::mem_fun(*this, &broken_indicator::update));
-      }
+      resman->state_changed.connect(sigc::mem_fun(*this, &broken_indicator::post_update));
 
     cache_closed.connect(sigc::mem_fun(*this, &broken_indicator::update));
     cache_reloaded.connect(sigc::mem_fun(*this, &broken_indicator::handle_cache_reload));
@@ -86,6 +85,85 @@
     set_bg_style(get_style("Error"));
 
     update();
+
+    vscreen_addtimeout(sigc::mem_fun(this, &broken_indicator::tick_timeout),
+		       aptcfg->FindI(PACKAGE "::Spin-Interval", 500));
+  }
+
+  static fragment *key_hint_fragment()
+  {
+    wstring next=global_bindings.readable_keyname("NextSolution");
+    wstring prev=global_bindings.readable_keyname("PrevSolution");
+    wstring examine=global_bindings.readable_keyname("ExamineSolution");
+    wstring apply=global_bindings.readable_keyname("ApplySolution");
+
+    vector<fragment *> key_hints;
+
+    key_hints.push_back(fragf(_("%s: Examine"),
+			      examine.c_str()));
+    key_hints.push_back(fragf(_("%s: Apply"),
+			      apply.c_str()));
+    key_hints.push_back(fragf(_("%s: Next"),
+			      next.c_str()));
+    key_hints.push_back(fragf(_("%s: Previous"),
+			      prev.c_str()));
+
+    return join_fragments(key_hints, L"  ");
+  }
+
+  void tick_timeout()
+  {
+    if(resman->background_thread_active())
+      {
+	++spin_count;
+	update();
+	vscreen_update();
+      }
+
+    vscreen_addtimeout(sigc::mem_fun(this, &broken_indicator::tick_timeout),
+		       aptcfg->FindI(PACKAGE "::Spin-Interval", 500));
+  }
+
+  std::string spin_string() const
+  {
+    switch(spin_count % 4)
+      {
+      case 0:
+	return ".";
+      case 1:
+	return "o";
+      case 2:
+	return "O";
+      case 3:
+	return "o";
+      default:
+	return "?";
+      }
+  }
+
+  struct update_event : public vscreen_event
+  {
+    broken_indicator *b;
+  public:
+    update_event(broken_indicator *_b)
+      : b(_b)
+    {
+    }
+
+    void dispatch()
+    {
+      b->update();
+    }
+  };
+
+  /** Post an update to run in the main thread; needed since the
+   *  selected_signal_changed signal might theoretically run from a
+   *  background thread.  (at the moment it shouldn't, but this will
+   *  help avoid nasty surprises)
+   */
+  void post_update()
+  {
+    vscreen_post_event(new update_event(this));
   }
 public:
   static ref_ptr<broken_indicator> create()
@@ -93,6 +171,7 @@
     return new broken_indicator;
   }
 
+  // TODO: split this monster up.
   void update()
   {
     if((!apt_cache_file) || !resman->resolver_exists())
@@ -103,140 +182,134 @@
 	return;
       }
 
-    wstring next=global_bindings.readable_keyname("NextSolution");
-    wstring prev=global_bindings.readable_keyname("PrevSolution");
-    wstring examine=global_bindings.readable_keyname("ExamineSolution");
-    wstring apply=global_bindings.readable_keyname("ApplySolution");
+    // Take a snapshot of the state.
+    resolver_manager::state state = resman->state_snapshot();
+
+    if(state.solutions_exhausted && state.generated_solutions == 0)
+      {
+	set_fragment(fragf(_("Unable to resolve dependencies.")));
+	last_sol.nullify();
+	show();
+	return;
+      }
 
-    // try to retrieve the current solution to show information about
-    // it.
-    try
+    // Handle the case where the resolver is churning away.
+    if(state.selected_solution >= state.generated_solutions)
       {
-	aptitude_solution sol=resman->get_current_solution();
+	// TODO: add a column-generating fragment that can
+	//       left/right justify stuff.
+	set_fragment(fragf(_("Resolving dependencies   %s%n%F"),
+			   spin_string().c_str(), key_hint_fragment()));
+	last_sol.nullify();
+	show();
+	return;
+      }
+
+    aptitude_solution sol = resman->get_solution(state.selected_solution);
 
-	if(sol == last_sol && resman->solution_generation_complete() == last_complete)
-	  return;
+    // This test always fails the first time update() is called, since
+    // sol is never NULL and last_sol is initialized to NULL.
+    if(sol == last_sol && state.solutions_exhausted == last_complete)
+      return;
 
-	last_sol = sol;
-	last_complete = resman->solution_generation_complete();
+    last_sol = sol;
+    last_complete = state.solutions_exhausted;
 
-	if(sol.get_actions().empty())
+    if(sol.get_actions().empty())
+      {
+	set_fragment(fragf("%s", _("Internal error: unexpected null solution.")));
+	show();
+	return;
+      }
+
+    int install_count=0, remove_count=0, keep_count=0, upgrade_count=0, downgrade_count=0;
+
+    for(imm::map<aptitude_universe::package, aptitude_solution::action>::const_iterator
+	  i = sol.get_actions().begin();
+	i != sol.get_actions().end(); ++i)
+      {
+	pkgCache::PkgIterator pkg=i->first.get_pkg();
+	pkgCache::VerIterator curver=pkg.CurrentVer();
+	pkgCache::VerIterator instver=(*apt_cache_file)[pkg].InstVerIter(*apt_cache_file);
+	pkgCache::VerIterator newver=i->second.ver.get_ver();
+
+	// If not, we have a problem.
+	assert(instver!=newver);
+
+	if(newver == curver)
+	  ++keep_count;
+	else if(curver.end())
+	  ++install_count;
+	else if(newver.end())
+	  ++remove_count;
+	else
 	  {
-	    set_fragment(fragf("%s", _("Internal error: unexpected null solution.")));
-	    show();
-	    return;
+	    int cmp=_system->VS->CmpVersion(curver.VerStr(),
+					    newver.VerStr());
+
+	    // The versions shouldn't be equal -- otherwise
+	    // something is majorly wrong.
+	    // assert(cmp!=0);
+	    //
+	    // The above is not true: consider, eg, the case of a
+	    // locally compiled package and a standard package.
+
+	    /** \todo indicate "sidegrades" separately? */
+	    if(cmp<=0)
+	      ++upgrade_count;
+	    else if(cmp>0)
+	      ++downgrade_count;
 	  }
 
-	int install_count=0, remove_count=0, keep_count=0, upgrade_count=0, downgrade_count=0;
+	vector<fragment *> fragments;
 
-	for(imm::map<aptitude_universe::package, aptitude_solution::action>::const_iterator
-	      i = sol.get_actions().begin();
-	    i != sol.get_actions().end(); ++i)
-	  {
-	    pkgCache::PkgIterator pkg=i->first.get_pkg();
-	    pkgCache::VerIterator curver=pkg.CurrentVer();
-	    pkgCache::VerIterator instver=(*apt_cache_file)[pkg].InstVerIter(*apt_cache_file);
-	    pkgCache::VerIterator newver=i->second.ver.get_ver();
-
-	    // If not, we have a problem.
-	    assert(instver!=newver);
-
-	    if(newver == curver)
-	      ++keep_count;
-	    else if(curver.end())
-	      ++install_count;
-	    else if(newver.end())
-	      ++remove_count;
-	    else
-	      {
-		int cmp=_system->VS->CmpVersion(curver.VerStr(),
-						newver.VerStr());
-
-		// The versions shouldn't be equal -- otherwise
-		// something is majorly wrong.
-		// assert(cmp!=0);
-		//
-		// The above is not true: consider, eg, the case of a
-		// locally compiled package and a standard package.
-
-		/** \todo indicate "sidegrades" separately? */
-		if(cmp<=0)
-		  ++upgrade_count;
-		else if(cmp>0)
-		  ++downgrade_count;
-	      }
-
-	    vector<fragment *> fragments;
-
-	    string countstr
-	      = ssprintf(resman->solution_generation_complete()?"[%d/%d]":"[%d(%d)/...]",
-			 resman->get_selected_solution()+1,
-			 resman->generated_solution_count());
-	    fragments.push_back(fragf("%s ", countstr.c_str()));
-
-	    fragments.push_back(fragf(_("Suggest ")));
-
-	    vector<fragment *> suggestions;
-
-	    if(install_count>1)
-	      suggestions.push_back(text_fragment(ssprintf(_("%d installs"),
-							   install_count)));
-	    else if(install_count == 1)
-	      suggestions.push_back(text_fragment(_("1 install")));
-
-	    if(remove_count>1)
-		suggestions.push_back(text_fragment(ssprintf(_("%d removals"),
-							     remove_count)));
-	    else if(remove_count == 1)
-	      suggestions.push_back(text_fragment(_("1 removal")));
-
-	    if(keep_count>1)
-	      suggestions.push_back(text_fragment(ssprintf(_("%d keeps"),
-							   keep_count)));
-	    else if(keep_count == 1)
-	      suggestions.push_back(text_fragment(_("1 keep")));
-
-	    if(upgrade_count>1)
-	      suggestions.push_back(text_fragment(ssprintf(_("%d upgrades"), upgrade_count)));
-	    else if(upgrade_count == 1)
-	      suggestions.push_back(text_fragment(_("1 upgrade")));
-
-	    if(downgrade_count>1)
-	      suggestions.push_back(text_fragment(ssprintf(_("%d downgrades"), downgrade_count)));
-	    else if(downgrade_count == 1)
-	      suggestions.push_back(text_fragment(_("1 downgrade")));
-
-	    fragments.push_back(join_fragments(suggestions,
-					       L","));
-
-	    fragments.push_back(newline_fragment());
-
-	    vector<fragment *> key_hints;
-
-	    key_hints.push_back(fragf(_("%s: Examine"),
-				      examine.c_str()));
-	    key_hints.push_back(fragf(_("%s: Apply"),
-				      apply.c_str()));
-	    key_hints.push_back(fragf(_("%s: Next"),
-				      next.c_str()));
-	    key_hints.push_back(fragf(_("%s: Previous"),
-				      prev.c_str()));
+	string countstr
+	  = ssprintf(state.solutions_exhausted?"[%d/%d]":"[%d(%d)/...]",
+		     state.selected_solution + 1,
+		     state.generated_solutions);
+	fragments.push_back(fragf("%s ", countstr.c_str()));
+
+	fragments.push_back(fragf(_("Suggest ")));
+
+	vector<fragment *> suggestions;
+
+	if(install_count>1)
+	  suggestions.push_back(text_fragment(ssprintf(_("%d installs"),
+						       install_count)));
+	else if(install_count == 1)
+	  suggestions.push_back(text_fragment(_("1 install")));
+
+	if(remove_count>1)
+	  suggestions.push_back(text_fragment(ssprintf(_("%d removals"),
+						       remove_count)));
+	else if(remove_count == 1)
+	  suggestions.push_back(text_fragment(_("1 removal")));
+
+	if(keep_count>1)
+	  suggestions.push_back(text_fragment(ssprintf(_("%d keeps"),
+						       keep_count)));
+	else if(keep_count == 1)
+	  suggestions.push_back(text_fragment(_("1 keep")));
+
+	if(upgrade_count>1)
+	  suggestions.push_back(text_fragment(ssprintf(_("%d upgrades"), upgrade_count)));
+	else if(upgrade_count == 1)
+	  suggestions.push_back(text_fragment(_("1 upgrade")));
+
+	if(downgrade_count>1)
+	  suggestions.push_back(text_fragment(ssprintf(_("%d downgrades"), downgrade_count)));
+	else if(downgrade_count == 1)
+	  suggestions.push_back(text_fragment(_("1 downgrade")));
+
+	fragments.push_back(join_fragments(suggestions,
+					   L","));
 
-	    fragments.push_back(join_fragments(key_hints,
-					       L"  "));
+	fragments.push_back(newline_fragment());
 
-	    fragment *f=sequence_fragment(fragments);
-	    set_fragment(hardwrapbox(f));
-	  }
-      }
-    catch(NoMoreTime)
-      {
-	set_fragment(fragf(_("Solution search failed.  %s: try harder"),
-			   next.c_str()));
-      }
-    catch(NoMoreSolutions)
-      {
-	set_fragment(fragf(_("Unable to resolve dependencies.")));
+	fragments.push_back(key_hint_fragment());
+
+	fragment *f=sequence_fragment(fragments);
+	set_fragment(hardwrapbox(f));
       }
 
     show();

Modified: branches/aptitude-0.3/aptitude/src/cmdline/Makefile.am
==============================================================================
--- branches/aptitude-0.3/aptitude/src/cmdline/Makefile.am	(original)
+++ branches/aptitude-0.3/aptitude/src/cmdline/Makefile.am	Mon Sep 19 20:36:15 2005
@@ -34,6 +34,8 @@
 	cmdline_show_broken.h \
 	cmdline_simulate.cc \
 	cmdline_simulate.h \
+	cmdline_spinner.cc \
+	cmdline_spinner.h \
 	cmdline_update.cc \
 	cmdline_update.h \
 	cmdline_upgrade.cc \

Modified: branches/aptitude-0.3/aptitude/src/cmdline/cmdline_resolver.cc
==============================================================================
--- branches/aptitude-0.3/aptitude/src/cmdline/cmdline_resolver.cc	(original)
+++ branches/aptitude-0.3/aptitude/src/cmdline/cmdline_resolver.cc	Mon Sep 19 20:36:15 2005
@@ -24,6 +24,7 @@
 #include "cmdline_prompt.h"
 #include "cmdline_show.h"
 #include "cmdline_show_broken.h"
+#include "cmdline_spinner.h"
 #include "cmdline_util.h"
 
 #include <aptitude.h>
@@ -332,6 +333,124 @@
     }
 }
 
+// TODO: make this generic?
+class cmdline_resolver_continuation : public resolver_manager::background_continuation
+{
+public:
+  struct resolver_result
+  {
+    /** If \b true, then NoMoreSolutions was thrown. */
+    bool out_of_solutions;
+
+    /** If \b true, then NoMoreTime was thrown. */
+    bool out_of_time;
+
+    /** If out_of_solutions and out_of_time are false, this is
+     *  the result returned by the resolver.
+     */
+    aptitude_solution sol;
+
+    resolver_result()
+      : out_of_solutions(false), out_of_time(false)
+    {
+    }
+
+    resolver_result(bool _out_of_solutions, bool _out_of_time)
+      : out_of_solutions(_out_of_solutions),
+	out_of_time(_out_of_time)
+    {
+    }
+
+    resolver_result(const aptitude_solution &_sol)
+      : out_of_solutions(false), out_of_time(false), sol(_sol)
+    {
+    }
+  };
+
+private:
+  threads::box<resolver_result> &retbox;
+
+public:
+  cmdline_resolver_continuation(threads::box<resolver_result> &_retbox)
+    : retbox(_retbox)
+  {
+  }
+
+  void success(const aptitude_solution &sol)
+  {
+    retbox.put(resolver_result(sol));
+  }
+
+  void no_more_solutions()
+  {
+    retbox.put(resolver_result(true, false));
+  }
+
+  void no_more_time()
+  {
+    retbox.put(resolver_result(false, true));
+  }
+
+  void interrupted()
+  {
+    abort();
+  }
+};
+
+/** \return the resolver's current solution; if it needs to be calculated
+ *          first, run the calculation in the background and display
+ *          a spinner in the foreground.
+ */
+static
+aptitude_solution calculate_current_solution()
+{
+  if(resman->get_selected_solution() < resman->generated_solution_count())
+    return resman->get_solution(resman->get_selected_solution());
+
+
+  cmdline_spinner spin;
+  spin.set_msg(_("Resolving dependencies..."));
+
+  threads::box<cmdline_resolver_continuation::resolver_result> retbox;
+
+  resman->get_solution_background(resman->generated_solution_count(),
+				  new cmdline_resolver_continuation(retbox));
+
+  cmdline_resolver_continuation::resolver_result res;
+  bool done = false;
+  // The number of milliseconds to step per display.
+  long spin_step = aptcfg->FindI(PACKAGE "::Spin-Interval", 500);
+
+  do
+    {
+      timeval until;
+      gettimeofday(&until, 0);
+
+      until.tv_usec += spin_step * 1000L;
+      until.tv_sec += until.tv_usec / (1000L * 1000L);
+      until.tv_usec = until.tv_usec % (1000L * 1000L);
+
+      timespec until_ts;
+      until_ts.tv_sec = until.tv_sec;
+      until_ts.tv_nsec = until.tv_usec * 1000;
+
+      done = retbox.timed_take(res, until_ts);
+
+      if(!done)
+	{
+	  spin.display();
+	  spin.tick();
+	}
+    } while(!done);
+
+  if(res.out_of_time)
+    throw NoMoreTime();
+  else if(res.out_of_solutions)
+    throw NoMoreSolutions();
+  else
+    return res.sol;
+}
+
 bool cmdline_resolve_deps(pkgset &to_install,
 			  pkgset &to_hold,
 			  pkgset &to_remove,
@@ -355,7 +474,7 @@
 	  {
 	    try
 	      {
-		aptitude_solution sol=resman->get_current_solution();
+		aptitude_solution sol = calculate_current_solution();
 
 		if(_error->PendingError())
 		  _error->DumpErrors();
@@ -391,17 +510,16 @@
 		switch(toupper(response[loc]))
 		  {
 		  case 'Y':
-		    (*apt_cache_file)->apply_solution(resman->get_current_solution(), NULL);
+		    (*apt_cache_file)->apply_solution(calculate_current_solution(), NULL);
 		    modified_pkgs=true;
 		    break;
 		  case 'N':
 		    {
 		      int curr_count = resman->generated_solution_count();
 
-		      // Move to the last cached solution.
 		      if(curr_count>0)
-			while(resman->get_selected_solution()<curr_count)
-			  resman->next_solution();
+			while(resman->get_selected_solution() < curr_count)
+			  resman->select_next_solution();
 		    }
 		    break;
 		  case 'Q':
@@ -426,11 +544,10 @@
 		    reject_or_mandate_version(string(response, 1), false);
 		    break;
 		  case '.':
-		    resman->next_solution();
+		    resman->select_next_solution();
 		    break;
 		  case ',':
-		    // As above, errors pop to the outer 'catch'
-		    resman->previous_solution();
+		    resman->select_previous_solution();
 		    break;
 		  case '?':
 		    cout << _("The following commands are available:") << endl;
@@ -488,7 +605,7 @@
 		      case 'Y':
 			try
 			  {
-			    resman->next_solution();
+			    calculate_current_solution();
 			    done=true;
 			  }
 			catch(NoMoreTime)
@@ -517,6 +634,7 @@
 	      {
 		cout << _("No more solutions.") << endl;
 		// Force it to re-print the last solution.
+		resman->select_previous_solution();
 		lastsol.nullify();
 	      }
 	  }

Added: branches/aptitude-0.3/aptitude/src/cmdline/cmdline_spinner.cc
==============================================================================
--- (empty file)
+++ branches/aptitude-0.3/aptitude/src/cmdline/cmdline_spinner.cc	Mon Sep 19 20:36:15 2005
@@ -0,0 +1,59 @@
+// cmdline_spinner.cc                               -*-c++-*-
+//
+//   Copyright (C) 2005 Daniel Burrows
+//
+//   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; see the file COPYING.  If not, write to
+//   the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+//   Boston, MA 02111-1307, USA.
+
+#include "cmdline_spinner.h"
+
+#include "cmdline_common.h"
+
+#include <iostream>
+
+cmdline_spinner::cmdline_spinner()
+  : count(0)
+{
+}
+
+char cmdline_spinner::spin_char() const
+{
+  switch(count % 4)
+    {
+    case 0:
+      return '.';
+    case 1:
+      return 'o';
+    case 2:
+      return 'O';
+    case 3:
+      return 'o';
+    default:
+      return '?';
+    }
+}
+
+void cmdline_spinner::display() const
+{
+  update_screen_width();
+
+  // Build the string to output.
+  std::string out(msg, 0, screen_width);
+  out.append(screen_width-1-out.size(), ' ');
+  if(out.size() < screen_width)
+    out += spin_char();
+
+  std::cout << '\r' << out << std::flush;
+}

Added: branches/aptitude-0.3/aptitude/src/cmdline/cmdline_spinner.h
==============================================================================
--- (empty file)
+++ branches/aptitude-0.3/aptitude/src/cmdline/cmdline_spinner.h	Mon Sep 19 20:36:15 2005
@@ -0,0 +1,58 @@
+// cmdline_spinner.h                             -*-c++-*-
+//
+//   Copyright (C) 2005 Daniel Burrows
+//
+//   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; see the file COPYING.  If not, write to
+//   the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+//   Boston, MA 02111-1307, USA.
+//
+//  A generic spinner (for use with the background resolver).
+
+#ifndef CMDLINE_SPINNER_H
+#define CMDLINE_SPINNER_H
+
+#include <string>
+
+/** A spinner for command-line output. */
+class cmdline_spinner
+{
+  std::string msg;
+
+  /** The number of spins. */
+  int count;
+public:
+  cmdline_spinner();
+
+  /** Display the current spinner state, overwriting the current
+   *  terminal line.
+   */
+  void display() const;
+
+  /** Return a spin character for the given tick count. */
+  virtual char spin_char() const;
+
+  /** Advance the spinner one position. */
+  void tick()
+  {
+    ++count;
+  }
+
+  /** Set the message displayed to the left of the spinner. */
+  void set_msg(const std::string &new_msg)
+  {
+    msg = new_msg;
+  }
+};
+
+#endif

Modified: branches/aptitude-0.3/aptitude/src/generic/resolver_manager.cc
==============================================================================
--- branches/aptitude-0.3/aptitude/src/generic/resolver_manager.cc	(original)
+++ branches/aptitude-0.3/aptitude/src/generic/resolver_manager.cc	Mon Sep 19 20:36:15 2005
@@ -37,7 +37,6 @@
   :cache(_cache),
    resolver(NULL),
    selected_solution(0),
-   out_of_time(false),
    out_of_solutions(false),
    background_thread_killed(false),
    resolver_null(true),
@@ -238,6 +237,12 @@
 
   if(resolver == NULL && cache->BrokenCount() > 0)
     create_resolver();
+
+  // Always signal a state change: we are signalling for the whole
+  // discard/create pair, and even if we didn't create a new resolver
+  // we have to inform the listeners that the old one went away
+  // (maybe).
+  state_changed();
 }
 
 void resolver_manager::discard_resolver()
@@ -270,10 +275,8 @@
     background_control_cond.wake_all();
   }
 
-  out_of_time.take();
   out_of_solutions.take();
   out_of_solutions.put(false);
-  out_of_time.put(false);
 }
 
 void resolver_manager::create_resolver()
@@ -361,25 +364,43 @@
   return rval;
 }
 
-bool resolver_manager::solutions_exhausted() // const
+bool resolver_manager::solutions_at_start() const
 {
   threads::mutex::lock l(mutex);
 
   if(!resolver_exists())
     return true;
   else
-    return selected_solution+1 == generated_solution_count() &&
-      solution_generation_complete();
+    return selected_solution == 0;
 }
 
-bool resolver_manager::solutions_at_start() const
+bool resolver_manager::background_thread_active()
 {
   threads::mutex::lock l(mutex);
 
-  if(!resolver_exists())
-    return true;
-  else
-    return selected_solution == 0;
+  threads::mutex::lock ctl_l(background_control_mutex);
+
+  return !pending_jobs.empty() || background_thread_in_resolver;
+}
+
+resolver_manager::state resolver_manager::state_snapshot()
+{
+  threads::mutex::lock l(mutex);
+
+  threads::mutex::lock ctl_l(background_control_mutex);
+
+  threads::mutex::lock sol_l(solutions_mutex);
+
+  state rval;
+
+  rval.selected_solution = selected_solution;
+  rval.generated_solutions = solutions.size();
+  rval.resolver_exists = (resolver != NULL);
+  rval.solutions_exhausted = out_of_solutions.take();
+  out_of_solutions.put(rval.solutions_exhausted);
+  rval.background_thread_active = (!pending_jobs.empty() || background_thread_in_resolver);
+
+  return rval;
 }
 
 aptitude_resolver::solution *resolver_manager::do_get_solution(unsigned int max_steps, unsigned int solution_num)
@@ -400,25 +421,19 @@
 	  solutions.push_back(new aptitude_resolver::solution(sol.clone()));
 	  sol_l.release();
 
-	  out_of_time.take();
 	  out_of_solutions.take();
 	  out_of_solutions.put(false);
-	  out_of_time.put(false);
 	}
       catch(NoMoreTime)
 	{
-	  out_of_time.take();
 	  out_of_solutions.take();
 	  out_of_solutions.put(false);
-	  out_of_time.put(true);
 	  throw NoMoreTime();
 	}
       catch(NoMoreSolutions)
 	{
-	  out_of_time.take();
 	  out_of_solutions.take();
 	  out_of_solutions.put(true);
-	  out_of_time.put(false);
 	  throw NoMoreSolutions();
 	}
     }
@@ -484,33 +499,12 @@
 
   assert(resolver);
 
+  {
+    threads::mutex::lock l2(solutions_mutex);
+    if(solution_num < solutions.size())
+      return *solutions[solution_num];
+  }
 
-  const generic_solution<aptitude_universe> *sol = NULL;
-  bool oot = false;
-  bool oos = false;
-  threads::mutex m;
-  threads::condition c;
-
-  get_solution_background(solution_num, new solution_return_continuation(sol, oot, oos, m, c));
-  threads::mutex::lock cond_l(m);
-
-  while(!sol && !oot && !oos)
-    c.wait(cond_l);
-
-  if(oot)
-    throw NoMoreTime();
-  else if(oos)
-    throw NoMoreSolutions();
-
-  return *sol;
-}
-
-
-const aptitude_resolver::solution &resolver_manager::get_current_solution()
-{
-  threads::mutex::lock l(mutex);
-
-  assert(resolver);
 
   const generic_solution<aptitude_universe> *sol = NULL;
   bool oot = false;
@@ -518,7 +512,7 @@
   threads::mutex m;
   threads::condition c;
 
-  get_current_solution_background(new solution_return_continuation(sol, oot, oos, m, c));
+  get_solution_background(solution_num, new solution_return_continuation(sol, oot, oos, m, c));
   threads::mutex::lock cond_l(m);
 
   while(!sol && !oot && !oos)
@@ -556,33 +550,6 @@
   background_control_cond.wake_all();
 }
 
-void resolver_manager::get_current_solution_background(background_continuation *k)
-{
-  threads::mutex::lock l(mutex);
-
-  assert(resolver);
-
-
-  bool oot = out_of_time.take();
-  bool oos = out_of_solutions.take();
-
-  out_of_time.put(oot);
-  out_of_solutions.put(oos);
-
-  // Only throw NoMoreTime when we're trying to generate a new
-  // solution.
-  threads::mutex::lock sol_l(solutions_mutex);
-  size_t solution_count = solutions.size();
-  sol_l.release();
-
-  if(oot && selected_solution >= solution_count)
-    throw NoMoreTime();
-  else if(oos && selected_solution >= solution_count)
-    throw NoMoreSolutions();
-  else
-    get_solution_background(selected_solution, k);
-}
-
 void resolver_manager::reject_version(const aptitude_resolver_version &ver)
 {
   threads::mutex::lock l(mutex);
@@ -602,12 +569,11 @@
 
   resolver->unreject_version(ver);
 
-  out_of_time.take();
+
   out_of_solutions.take();
   out_of_solutions.put(false);
-  out_of_time.put(false);
 
-  selected_solution_changed();
+  state_changed();
 }
 
 bool resolver_manager::is_rejected(const aptitude_resolver_version &ver)
@@ -637,12 +603,10 @@
 
   resolver->unmandate_version(ver);
 
-  out_of_time.take();
   out_of_solutions.take();
   out_of_solutions.put(false);
-  out_of_time.put(false);
 
-  selected_solution_changed();
+  state_changed();
 }
 
 bool resolver_manager::is_mandatory(const aptitude_resolver_version &ver)
@@ -672,12 +636,10 @@
 
   resolver->unharden(dep);
 
-  out_of_time.take();
   out_of_solutions.take();
   out_of_solutions.put(false);
-  out_of_time.put(false);
 
-  selected_solution_changed();
+  state_changed();
 }
 
 bool resolver_manager::is_hardened(const aptitude_resolver_dep &dep)
@@ -707,12 +669,10 @@
 
   resolver->unforce_break(dep);
 
-  selected_solution_changed();
+  state_changed();
 
-  out_of_time.take();
   out_of_solutions.take();
   out_of_solutions.put(false);
-  out_of_time.put(false);
 }
 
 bool resolver_manager::is_forced_broken(const aptitude_resolver_dep &dep)
@@ -723,43 +683,40 @@
   return resolver->is_forced_broken(dep);
 }
 
-void resolver_manager::select_next_solution()
+void resolver_manager::select_solution(unsigned int solnum)
 {
   threads::mutex::lock l(mutex);
 
   threads::mutex::lock sol_l(solutions_mutex);
-  if(selected_solution < solutions.size())
-    ++selected_solution;
+  if(solnum >= 0 && solnum <= solutions.size())
+    selected_solution = solnum;
   sol_l.release();
 
-  selected_solution_changed();
+  state_changed();
 }
 
-void resolver_manager::select_previous_solution()
+void resolver_manager::select_next_solution()
 {
   threads::mutex::lock l(mutex);
 
-  if(selected_solution == 0)
-    throw NoMoreSolutions();
-  else
-    --selected_solution;
-  selected_solution_changed();
-}
-
-const aptitude_resolver::solution &resolver_manager::next_solution()
-{
-  threads::mutex::lock l(mutex);
+  threads::mutex::lock sol_l(solutions_mutex);
+  if(selected_solution < solutions.size())
+    ++selected_solution;
+  sol_l.release();
 
-  select_next_solution();
-  return get_current_solution();
+  state_changed();
 }
 
-const aptitude_resolver::solution &resolver_manager::previous_solution()
+void resolver_manager::select_previous_solution()
 {
   threads::mutex::lock l(mutex);
 
-  select_previous_solution();
-  return get_current_solution();
+  threads::mutex::lock sol_l(solutions_mutex);
+  if(selected_solution > 0)
+    --selected_solution;
+  sol_l.release();
+
+  state_changed();
 }
 
 void resolver_manager::tweak_score(const pkgCache::PkgIterator &pkg,

Modified: branches/aptitude-0.3/aptitude/src/generic/resolver_manager.h
==============================================================================
--- branches/aptitude-0.3/aptitude/src/generic/resolver_manager.h	(original)
+++ branches/aptitude-0.3/aptitude/src/generic/resolver_manager.h	Mon Sep 19 20:36:15 2005
@@ -88,6 +88,27 @@
     virtual void interrupted() = 0;
   };
 
+  /** A snapshot of the state of the resolver. */
+  struct state
+  {
+    /** The currently selected solution. */
+    int selected_solution;
+
+    /** The number of already-generated solutions. */
+    int generated_solutions;
+
+    /** If \b true, then there are no more solutions to generate. */
+    bool solutions_exhausted;
+
+    /** If \b true, then the resolver is not \b null (i.e., it exists;
+     *  i.e., there are broken packages).
+     */
+    bool resolver_exists;
+
+    /** If \b true, the background thread has jobs. */
+    bool background_thread_active;
+  };
+
 private:
   /** Information about a single request posted to the background
    *  thread.
@@ -136,14 +157,6 @@
   /** The index of the currently selected solution. */
   unsigned int selected_solution;
 
-  /** If \b true, there are no solutions and the last attempt to
-   *  calculate one failed with NoMoreTime.
-   *
-   *  This is used so that the return value of get_current_solution()
-   *  is stable.
-   */
-  threads::box<bool> out_of_time;
-
   /** If \b true, a find_next_solution() call will terminate with
    *  NoMoreSolutions.  (this may be the case even if it is \b false)
    *
@@ -282,7 +295,7 @@
   unsigned int generated_solution_count() const;
 
   /** Get the selection location, which will be in the range
-   *  [0,generated_solution_count()-1).  Note that this is meaningless
+   *  [0,generated_solution_count()).  Note that this is meaningless
    *  if generated_solution_count==0.
    */
   unsigned int get_selected_solution() const {return selected_solution;}
@@ -292,9 +305,8 @@
    *  the list; will continue a search even if it ran out of time
    *  previously.
    *
-   *  If solution_num < generated_solution_count, it is always safe to
-   *  call this from a foreground thread; otherwise, an exception will
-   *  be thrown if a background resolver exists.
+   *  If solution_num refers to an already-generated solution, this
+   *  routine returns immediately (without suspending the thread).
    *
    *  \throw NoMoreSolutions if the list of solutions is exhausted
    *  \throw NoMoreTime if time is exhausted while searching for
@@ -305,18 +317,6 @@
    */
   const generic_solution<aptitude_universe> &get_solution(unsigned int solution_num);
 
-  /** Requires the resolver_exists() is \b true.  Return the currently
-   *  selected solution, perhaps generating it; however, will not
-   *  continue to search if the last search ran out of time.
-   *
-   *  \throw NoMoreSolutions if the list of solutions is exhausted
-   *  \throw NoMoreTime if time is exhausted while searching for the solution.
-   *  \throw ResolverManagerThreadClashException if a new solution
-   *         would be generated and a background thread exists.
-   */
-  const generic_solution<aptitude_universe> &get_current_solution();
-
-
   /** As get_solution, but run in a background thread if necessary.
    *
    *  \param solution_num the solution to retrieve
@@ -335,34 +335,25 @@
   void get_solution_background(unsigned int solution_num,
 			       background_continuation *k);
 
-  /** As get_current_solution, but run in a background thread if
-   *  necessary.  Updates to the selected solution will not alter the
-   *  solution being computed by the background thread.
-   *
-   *  \param solution_num the solution to retrieve
-   * 
-   *  \param k the continuation of the background process; see
-   *           get_background_solution.
-   *
-   *  \throw ResolverManagerThreadClashException if a background
-   *         resolver already exists.
-   */
-  void get_current_solution_background(background_continuation *k);
-
   /** If \b true, all solutions have been generated. */
   bool solution_generation_complete() /*const*/;
 
-  /** If \b true, then either there is no resolver, or the resolver
-   *  has been exhausted and the solution pointer is set to the last
-   *  solution.
-   */
-  bool solutions_exhausted() /* const */;
-
   /** If \b true, the solution pointer is set to the first
    *  solution.
    */
   bool solutions_at_start() const;
 
+  /** If \b true, the background thread is working on a job. */
+  bool background_thread_active();
+
+  /** Get a snapshot of the current resolver state; contains the
+   *  values that would be returned by get_selected_solution(),
+   *  generated_solution_count(), and solutions_exhausted(); however,
+   *  this snapshot is taken atomically.
+   */
+  state state_snapshot();
+
+
 
   /** Requires that resolver_exists() is \b true.  Temporarily rejects
    *  any solutions generated by the currently active installer that
@@ -419,6 +410,8 @@
   bool is_forced_broken(const aptitude_resolver_dep &dep);
 
 
+  /** Set the selection pointer to a particular solution. */
+  void select_solution(unsigned int solnum);
 
   /** Move the selection pointer to the next solution, without
    *  generating it.
@@ -432,28 +425,6 @@
    */
   void select_previous_solution();
 
-  /** Advance to the next solution.
-   *
-   *  \return the next solution.  This reference is not guaranteed to
-   *  persist if any other operation is performed on the cache; a
-   *  stack variable should be instantiated from it.
-   *
-   *  \throws NoMoreSolutions if there are no more solutions
-   *  \throws NoMoreTime if a solution could not be found before the
-   *                     cutoff time expired.
-   */
-  const generic_solution<aptitude_universe> &next_solution();
-
-  /** Return to the previous solution.
-   *
-   *  \return the previous solution.  This reference is not guaranteed
-   *  to persist if any other operation is performed on the cache; a
-   *  stack variable should be instantiated from it.
-   *
-   *  \throws NoMoreSolutions if solutions_at_start()
-   */
-  const generic_solution<aptitude_universe> &previous_solution();
-
   /** Tweak the resolver score of a particular package/version.  This
    *  requires that resolver_exists() and that the resolver is "fresh"
    *  (i.e., that next_solution() and current_solution() have never
@@ -472,11 +443,17 @@
    */
   void dump(std::ostream &out);
 
-  /** This signal is emitted when the selected solution changes, or
-   *  when the user takes an action that might change the number of
-   *  available solutions (such as un-rejecting a package).
+  /** This signal is emitted when the selected solution changes, when
+   *  the user takes an action that might change the number of
+   *  available solutions (such as un-rejecting a package), and when a
+   *  new resolver is created.
+   *
+   *  Note that this is NOT signalled when a new solution is added to
+   *  the solution list by the background thread.  You are free to
+   *  manually emit the signal, but of course be aware of threading
+   *  considerations if you do so.
    */
-  sigc::signal0<void> selected_solution_changed;
+  sigc::signal0<void> state_changed;
 };
 
 #endif

Modified: branches/aptitude-0.3/aptitude/src/solution_dialog.cc
==============================================================================
--- branches/aptitude-0.3/aptitude/src/solution_dialog.cc	(original)
+++ branches/aptitude-0.3/aptitude/src/solution_dialog.cc	Mon Sep 19 20:36:15 2005
@@ -32,6 +32,7 @@
 #include <vscreen/vs_scrollbar.h>
 #include <vscreen/vs_table.h>
 #include <vscreen/vs_text_layout.h>
+#include <vscreen/vscreen.h>
 
 #include <generic/apt.h>
 #include <generic/aptitude_resolver_universe.h>
@@ -50,22 +51,37 @@
   void handle_cache_reload()
   {
     if(apt_cache_file)
-      {
-	(*apt_cache_file)->package_state_changed.connect(sigc::mem_fun(*this, &solution_dialog::update));
-	resman->selected_solution_changed.connect(sigc::mem_fun(*this, &solution_dialog::update));
-      }
+      resman->state_changed.connect(sigc::mem_fun(*this, &solution_dialog::post_update));
 
     update();
   }
 
+  struct update_event : public vscreen_event
+  {
+    solution_dialog *dlg;
+
+  public:
+    update_event(solution_dialog *_dlg)
+      :dlg(_dlg)
+    {
+    }
+
+    void dispatch()
+    {
+      dlg->update();
+    }
+  };
+
+  void post_update()
+  {
+    vscreen_post_event(new update_event(this));
+  }
+
 protected:
   solution_dialog()
   {
     if(apt_cache_file)
-      {
-	(*apt_cache_file)->package_state_changed.connect(sigc::mem_fun(*this, &solution_dialog::update));
-	resman->selected_solution_changed.connect(sigc::mem_fun(*this, &solution_dialog::update));
-      }
+      resman->state_changed.connect(sigc::mem_fun(*this, &solution_dialog::post_update));
 
     cache_closed.connect(sigc::mem_fun(*this, &solution_dialog::update));
     cache_reloaded.connect(sigc::mem_fun(*this, &solution_dialog::handle_cache_reload));
@@ -96,29 +112,31 @@
 	return;
       }
 
-    try
-      {
-	aptitude_solution sol = resman->get_current_solution();
+    resolver_manager::state state = resman->state_snapshot();
 
-	if(sol == last_sol)
-	  return;
-
-	last_sol=sol;
-
-	if(sol.get_actions().empty())
-	  set_fragment(fragf("%s", _("Internal error: unexpected null solution.")));
-	else
-	  set_fragment(solution_fragment(sol));
-      }
-    catch(NoMoreSolutions)
+    if(state.solutions_exhausted && state.generated_solutions == 0)
       {
 	set_fragment(fragf("%s", _("No resolution found.")));
+	return;
       }
-    catch(NoMoreTime)
+
+    if(state.selected_solution >= state.generated_solutions)
       {
-	set_fragment(fragf(_("Time exhausted while searching for a solution (you can select \"Next Solution\" or press %s to try harder)."),
-			   global_bindings.readable_keyname("NextSolution").c_str()));
+	set_fragment(fragf("%s", _("Resolving dependencies...")));
+	return;
       }
+
+    aptitude_solution sol = resman->get_solution(state.selected_solution);
+
+    if(sol == last_sol)
+      return;
+
+    last_sol=sol;
+
+    if(sol.get_actions().empty())
+      set_fragment(fragf("%s", _("Internal error: unexpected null solution.")));
+    else
+      set_fragment(solution_fragment(sol));
   }
 };
 

Modified: branches/aptitude-0.3/aptitude/src/solution_screen.cc
==============================================================================
--- branches/aptitude-0.3/aptitude/src/solution_screen.cc	(original)
+++ branches/aptitude-0.3/aptitude/src/solution_screen.cc	Mon Sep 19 20:36:15 2005
@@ -765,10 +765,7 @@
   void attach_apt_cache_signals()
   {
     if(apt_cache_file)
-      {
-	(*apt_cache_file)->package_state_changed.connect(sigc::mem_fun(*this, &solution_screen::update));
-	resman->selected_solution_changed.connect(sigc::mem_fun(*this, &solution_screen::update));
-      }
+      resman->state_changed.connect(sigc::mem_fun(*this, &solution_screen::update));
   }
 
   /** Re-connects the signals attached to the apt cache file. */
@@ -832,31 +829,35 @@
 	return;
       }
 
-    try
-      {
-	aptitude_solution sol = resman->get_current_solution();
-
-	if(sol == last_sol)
-	  return;
+    resolver_manager::state state = resman->state_snapshot();
 
-	last_sol = sol;
-
-	if(sol.get_actions().empty())
-	  set_static_root(transcode(_("Internal error: unexpected null solution.")));
-	else
-	  {
-	    solution_tree->set_root(make_solution_tree(sol));
-	    story_tree->set_root(make_story_tree(sol));
-	  }
-      }
-    catch(NoMoreSolutions)
+    if(state.solutions_exhausted && state.generated_solutions == 0)
       {
 	set_static_root(transcode(_("No resolution found.")));
+	last_sol.nullify();
+	return;
+      }
+
+    if(state.selected_solution >= state.generated_solutions)
+      {
+	set_static_root(transcode(_("Resolving dependencies...")));
+	last_sol.nullify();
+	return;
       }
-    catch(NoMoreTime)
+
+    aptitude_solution sol = resman->get_solution(state.selected_solution);
+
+    if(sol == last_sol)
+      return;
+
+    last_sol = sol;
+
+    if(sol.get_actions().empty())
+      set_static_root(transcode(_("Internal error: unexpected null solution.")));
+    else
       {
-	set_static_root(transcode(ssprintf(_("Time exhausted while searching for a solution (you can select \"Next Solution\" or press %ls to try harder)."),
-					   global_bindings.readable_keyname("NextSolution").c_str())));
+	solution_tree->set_root(make_solution_tree(sol));
+	story_tree->set_root(make_story_tree(sol));
       }
   }
 };

Modified: branches/aptitude-0.3/aptitude/src/ui.cc
==============================================================================
--- branches/aptitude-0.3/aptitude/src/ui.cc	(original)
+++ branches/aptitude-0.3/aptitude/src/ui.cc	Mon Sep 19 20:36:15 2005
@@ -1042,6 +1042,7 @@
   popup_widget(w, true);
 }
 
+// FIXME: blocks.
 static void auto_fix_broken()
 {
   undo_group *undo=new apt_undo_group;
@@ -1051,7 +1052,7 @@
       assert(resman != NULL);
       assert(resman->resolver_exists());
 
-      aptitude_solution sol = resman->get_current_solution();
+      aptitude_solution sol = resman->get_solution(resman->get_selected_solution());
 
       (*apt_cache_file)->apply_solution(sol, undo);
       show_message(fragf("%s%n%n%F",
@@ -1426,33 +1427,129 @@
 }
 #endif
 
+static void start_solution_calculation();
+
+class interactive_continuation : public resolver_manager::background_continuation
+{
+  /** The manager associated with this continuation; usually resman. */
+  resolver_manager *manager;
+
+  /** Indicate that a new solution is available by invoking the
+   *  selection_changed signal.
+   */
+  class success_event : public vscreen_event
+  {
+    resolver_manager *manager;
+  public:
+    success_event(resolver_manager *_manager)
+      :manager(_manager)
+    {
+    }
+
+    void dispatch()
+    {
+      manager->state_changed();
+    }
+  };
+
+  class no_more_time_event : public vscreen_event
+  {
+    resolver_manager *manager;
+  public:
+    no_more_time_event(resolver_manager *_manager)
+      :manager(_manager)
+    {
+    }
+
+    void dispatch()
+    {
+      start_solution_calculation();
+    }
+  };
+
+  class no_more_solutions_event : public vscreen_event
+  {
+    resolver_manager *manager;
+  public:
+    no_more_solutions_event(resolver_manager *_manager)
+      :manager(_manager)
+    {
+    }
+
+    void dispatch()
+    {
+      resolver_manager::state st = manager->state_snapshot();
+
+      if(st.selected_solution == st.generated_solutions)
+	manager->select_previous_solution();
+      show_message(_("No more solutions."));
+    }
+  };
+public:
+  interactive_continuation(resolver_manager *_manager)
+    :manager(_manager)
+  {
+  }
+
+  void success(const aptitude_solution &sol)
+  {
+    vscreen_post_event(new success_event(manager));
+  }
+
+  void no_more_solutions()
+  {
+    vscreen_post_event(new no_more_solutions_event(manager));
+  }
+
+  void no_more_time()
+  {
+    vscreen_post_event(new no_more_time_event(manager));
+  }
+
+  void interrupted()
+  {
+  }
+};
+
+// If the current solution pointer is past the end of the solution
+// list, queue up a calculation for it in the background thread.
+static void start_solution_calculation()
+{
+  resolver_manager::state state = resman->state_snapshot();
+
+  if(state.resolver_exists &&
+     state.selected_solution == state.generated_solutions &&
+     !state.background_thread_active)
+    resman->get_solution_background(resman->get_selected_solution(),
+				    new interactive_continuation(resman));
+}
+
+static void do_connect_resolver_callback()
+{
+  resman->state_changed.connect(sigc::ptr_fun(&start_solution_calculation));
+}
+
 void do_next_solution()
 {
-  if(apt_cache_file && !resman->solutions_exhausted())
-    try
-      {
-	resman->next_solution();
-      }
-    catch(NoMoreSolutions)
-      {
-	/** \todo this message is bad */
-	show_message(_("All solutions exhausted."));
-      }
-    catch(NoMoreTime)
-      {
-	/** \todo this message is bad */
-	show_message(_("Ran out of time while trying to find a solution."));
-      }
-  else
+  resolver_manager::state state = resman->state_snapshot();
+
+  if(resman == NULL ||
+     state.selected_solution >= state.generated_solutions ||
+     (state.selected_solution == state.generated_solutions - 1 &&
+      state.solutions_exhausted))
     beep();
+  else
+    resman->select_next_solution();
 }
 
 void do_previous_solution()
 {
-  if(apt_cache_file && !resman->solutions_at_start())
-    resman->previous_solution();
-  else
+  resolver_manager::state state = resman->state_snapshot();
+
+  if(resman == NULL || state.selected_solution == 0)
     beep();
+  else
+    resman->select_previous_solution();
 }
 
 void do_apply_solution()
@@ -1462,7 +1559,7 @@
       undo_group *undo=new apt_undo_group;
       try
 	{
-	  (*apt_cache_file)->apply_solution(resman->get_current_solution(),
+	  (*apt_cache_file)->apply_solution(resman->get_solution(resman->get_selected_solution()),
 					    undo);
 	}
       catch(NoMoreSolutions)
@@ -1492,6 +1589,9 @@
 
 void do_examine_solution()
 {
+  if(resman == NULL || !resman->resolver_exists())
+    return;
+
   static vs_widget_ref solver;
 
   if(solver.valid())
@@ -1885,6 +1985,12 @@
 
   cache_closed.connect(sigc::ptr_fun(do_show_reload_message));
   cache_reloaded.connect(sigc::ptr_fun(do_hide_reload_message));
+  cache_reloaded.connect(sigc::ptr_fun(do_connect_resolver_callback));
+  if(apt_cache_file)
+    {
+      do_connect_resolver_callback();
+      start_solution_calculation();
+    }
   cache_reload_failed.connect(sigc::ptr_fun(do_hide_reload_message));
 
   add_menu(actions_menu, _("Actions"), menu_description);



More information about the Aptitude-svn-commit mailing list