Diff:
---
 AntiVirus.cc                                   |   4 +-
 CliParseFeedback.h                             |  28 ----
 Feedback.h                                     |  58 ++++++++
 GetNetAuth.h                                   |  30 ++++
 IniDBBuilderPackage.cc                         |   4 +-
 IniDBBuilderPackage.h                          |   6 +-
 IniParseFeedback.h                             |  38 -----
 LogFile.cc                                     |  18 ++-
 LogFile.h                                      |   8 +-
 Makefile.am                                    |  20 ++-
 SiteSetting.cc                                 | 196 +++++++++++++++++++++++++
 site.h => SiteSetting.h                        |  59 +++-----
 UserSettings.cc                                |  12 +-
 UserSettings.h                                 |   4 +-
 choose.cc                                      |   4 +-
 cli/CliFeedback.h                              |  60 ++++++++
 cli/CliGetNetAuth.cc                           |  45 ++++++
 cli/CliGetNetAuth.h                            |  32 ++++
 cli/CliGetUrlFeedback.cc                       |  91 ++++++++++++
 cli/CliHashCheckFeedback.cc                    |  30 ++++
 CliParseFeedback.cc => cli/CliParseFeedback.cc |  28 ++--
 crypto.cc                                      |  18 ++-
 crypto.h                                       |   9 +-
 dialog.h                                       |   3 -
 download.cc                                    |  37 ++---
 download.h                                     |   6 +-
 fromcwd.cc                                     |   1 -
 geturl.cc                                      | 130 ++++------------
 geturl.h                                       |  13 +-
 gui/GuiFeedback.h                              |  69 +++++++++
 gui/GuiGetNetAuth.cc                           | 138 +++++++++++++++++
 gui/GuiGetNetAuth.h                            |  38 +++++
 gui/GuiGetUrlFeedback.cc                       | 119 +++++++++++++++
 gui/GuiHashCheckFeedback.cc                    |  34 +++++
 gui/GuiParseFeedback.cc                        | 149 +++++++++++++++++++
 site.cc => gui/SitePage.cc                     | 194 ++----------------------
 gui/SitePage.h                                 |  45 ++++++
 ini.cc                                         | 163 +++-----------------
 ini.h                                          |  13 +-
 inilex.ll                                      |   6 +-
 inilintmain.cc                                 |  10 +-
 install.cc                                     |   6 +-
 main.cc                                        |   8 +-
 msg.cc                                         |   5 +-
 net.cc                                         |   5 +
 netio.cc                                       | 125 +---------------
 netio.h                                        |  19 +--
 nio-ie5.cc                                     |   4 +-
 package_db.cc                                  |   6 +-
 package_db.h                                   |   3 +-
 package_meta.cc                                |  10 +-
 package_meta.h                                 |   5 +-
 package_source.cc                              |  33 ++---
 package_source.h                               |   8 +-
 threebar.cc                                    |   2 +-
 55 files changed, 1395 insertions(+), 814 deletions(-)

diff --git a/AntiVirus.cc b/AntiVirus.cc
index cc416cc7..cb8e8eee 100644
--- a/AntiVirus.cc
+++ b/AntiVirus.cc
@@ -16,8 +16,7 @@
 #include "AntiVirus.h"
 
 #include "getopt++/BoolOption.h"
-
-#include "LogSingleton.h"
+#include "LogFile.h"
 
 #include "win32.h"
 #include <stdio.h>
@@ -77,6 +76,7 @@ bool
 AntiVirusPage::Create ()
 {
     detect();
+    Logger().atexit(AntiVirus::AtExit);
     return PropertyPage::Create (NULL, dialog_cmd, IDD_VIRUS);
 }
 
diff --git a/CliParseFeedback.h b/CliParseFeedback.h
deleted file mode 100644
index a19659eb..00000000
--- a/CliParseFeedback.h
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (c) 2020 Jon Turney
- *
- *     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.
- *
- *     A copy of the GNU General Public License can be found at
- *     http://www.gnu.org/
- *
- */
-
-#include "IniParseFeedback.h"
-
-class CliParseFeedback : public IniParseFeedback
-{
-public:
-  virtual void progress (unsigned long const pos, unsigned long const max);
-  virtual void iniName (const std::string& name);
-  virtual void babble (const std::string& message) const;
-  virtual void warning (const std::string& message) const;
-  virtual void show_errors () const;
-  virtual void note_error(int lineno, const std::string &s);
-  virtual bool has_errors () const;
-private:
-  int error_count = 0;
-};
diff --git a/Feedback.h b/Feedback.h
new file mode 100644
index 00000000..4db7c4a1
--- /dev/null
+++ b/Feedback.h
@@ -0,0 +1,58 @@
+/*
+ *     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.
+ *
+ *     A copy of the GNU General Public License can be found at
+ *     http://www.gnu.org/
+ *
+ */
+
+#ifndef SETUP_FEEDBACK_H
+#define SETUP_FEEDBACK_H
+
+#include "win32.h"
+#include <string>
+
+/* Interface for feedback from ini parsing and URL fetching.
+ *
+ * Used to send feedback that users need but that should not interrupt
+ * processing.
+ *
+ * Fatal errors are (may be) thrown as exceptions.
+ */
+
+class Feedback
+{
+public:
+  // IniParsing
+  virtual void parse_init () = 0;
+  virtual void parse_finish () = 0;
+  virtual void progress (unsigned long const, unsigned long const) = 0;
+  virtual void iniName (const std::string& ) = 0;
+  virtual void babble (const std::string& ) const = 0;
+  virtual void warning (const std::string& ) const = 0;
+  virtual void show_errors () const = 0;
+  virtual void note_error(int lineno, const std::string &error) = 0;
+  virtual bool has_errors () const = 0;
+
+  // URL fetching
+  virtual void fetch_progress_disable (bool) = 0;
+  virtual void fetch_init (const std::string &url, int length) = 0;
+  virtual void fetch_set_length(int length) = 0;
+  virtual void fetch_set_total_length(long long int total_length) = 0;
+  virtual void fetch_progress (int bytes) = 0;
+  virtual void fetch_total_progress () = 0;
+  virtual void fetch_finish (int total_bytes) = 0;
+  virtual void fetch_fatal (const char *filename, const char *err) = 0;
+
+  // hash checking
+  virtual void hash_init (const char *hashalg, const std::string &url) = 0;
+  virtual void hash_progress (int bytes, int total_bytes) = 0;
+
+  //
+  virtual HWND owner () = 0;
+};
+
+#endif /* SETUP_FEEDBACK_H */
diff --git a/GetNetAuth.h b/GetNetAuth.h
new file mode 100644
index 00000000..0a26e859
--- /dev/null
+++ b/GetNetAuth.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2000, Red Hat, Inc.
+ *
+ *     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.
+ *
+ *     A copy of the GNU General Public License can be found at
+ *     http://www.gnu.org/
+ *
+ * Written by DJ Delorie <[email protected]>
+ *
+ */
+
+#ifndef SETUP_GETNETAUTH_H
+#define SETUP_GETNETAUTH_H
+
+class GetNetAuth
+{
+public:
+  /* Helper functions for http/ftp protocols.  Both return nonzero for
+     "cancel", zero for "ok".  They set net_proxy_user, etc, in
+     state.h */
+  virtual int get_auth () = 0;
+  virtual int get_proxy_auth () = 0;
+  virtual int get_ftp_auth () = 0;
+};
+
+#endif /* SETUP_GETNETAUTH_H */
diff --git a/IniDBBuilderPackage.cc b/IniDBBuilderPackage.cc
index def9539a..53c18f59 100644
--- a/IniDBBuilderPackage.cc
+++ b/IniDBBuilderPackage.cc
@@ -19,7 +19,7 @@
 
 #include "setup_version.h"
 
-#include "IniParseFeedback.h"
+#include "Feedback.h"
 #include "package_db.h"
 #include "package_meta.h"
 #include "ini.h"
@@ -29,7 +29,7 @@
 #include "PackageSpecification.h"
 #include <algorithm>
 
-IniDBBuilderPackage::IniDBBuilderPackage (IniParseFeedback const &aFeedback) :
+IniDBBuilderPackage::IniDBBuilderPackage (Feedback const &aFeedback) :
   currentSpec (0), _feedback (aFeedback), minimum_version_checked(FALSE) {}
 
 IniDBBuilderPackage::~IniDBBuilderPackage()
diff --git a/IniDBBuilderPackage.h b/IniDBBuilderPackage.h
index 2542dad5..a985d1a0 100644
--- a/IniDBBuilderPackage.h
+++ b/IniDBBuilderPackage.h
@@ -24,13 +24,13 @@
 #include "String++.h"
 #include "libsolv.h"
 
-class IniParseFeedback;
+class Feedback;
 class packagesource;
 
 class IniDBBuilderPackage:public IniDBBuilder
 {
 public:
-  IniDBBuilderPackage (IniParseFeedback const &);
+  IniDBBuilderPackage (Feedback const &);
   ~IniDBBuilderPackage ();
 
   void buildTimestamp (const std::string& );
@@ -89,7 +89,7 @@ private:
   SolverPool::addPackageData cbpv;
   std::set <std::string> replace_versions;
 
-  IniParseFeedback const &_feedback;
+  Feedback const &_feedback;
   bool minimum_version_checked;
 };
 
diff --git a/IniParseFeedback.h b/IniParseFeedback.h
deleted file mode 100644
index c3c78033..00000000
--- a/IniParseFeedback.h
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (c) 2002 Robert Collins.
- *
- *     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.
- *
- *     A copy of the GNU General Public License can be found at
- *     http://www.gnu.org/
- *
- * Written by Robert Collins <[email protected]>
- *
- */
-
-#ifndef SETUP_INIPARSEFEEDBACK_H
-#define SETUP_INIPARSEFEEDBACK_H
-
-
-#include <string>
-/* Strategy for feedback from IniParsing.
- * Used by the builder or parsing classes to send feedback that users need
- * but that should not interrupt parsing.
- * Fatal errors are thrown as exceptions.
- */
-class IniParseFeedback
-{
-public:
-  virtual void progress (unsigned long const, unsigned long const) = 0;
-  virtual void iniName (const std::string& ) = 0;
-  virtual void babble (const std::string& ) const = 0;
-  virtual void warning (const std::string& ) const = 0;
-  virtual void show_errors () const = 0;
-  virtual void note_error(int lineno, const std::string &error) = 0;
-  virtual bool has_errors () const = 0;
-};
-
-#endif /* SETUP_INIPARSEFEEDBACK_H */
diff --git a/LogFile.cc b/LogFile.cc
index ab2e3ec9..0022eff3 100644
--- a/LogFile.cc
+++ b/LogFile.cc
@@ -28,7 +28,6 @@
 #include <time.h>
 #include <string>
 #include <stdexcept>
-#include "AntiVirus.h"
 #include "filemanip.h"
 #include "String++.h"
 #include "getopt++/BoolOption.h"
@@ -115,12 +114,21 @@ LogFile::getFileName (int level) const
   return "<no log was in use>";
 }
 
+void
+LogFile::atexit(void (*func)(void))
+{
+  exit_fns.push_back(func);
+}
+
 void
 LogFile::exit (int exit_code, bool show_end_install_msg)
 {
-  AntiVirus::AtExit();
+  /* Execute any functions we want to run at exit (we don't use stdlib atexit()
+     because we want to allow them to potentially write to the log) */
+  for (auto i = exit_fns.rbegin(); i != exit_fns.rend(); ++i)
+      (*i)();
+
   static int been_here = 0;
-  /* Exitcode -1 is special... */
   if (been_here)
     ::exit (exit_code);
   been_here = 1;
@@ -132,8 +140,8 @@ LogFile::exit (int exit_code, bool show_end_install_msg)
       Log (LOG_PLAIN) << "note: " << wstring_to_string(buf) << endLog;
     }
 
-  /* ... in that it skips the boring log messages.  Exit code -1 is used when
-     just printing the help output and when we're self-elevating. */
+  /* Skip the log messages when just printing the help/version output, and when
+     we're self-elevating. */
   if (show_end_install_msg)
     Log (LOG_TIMESTAMP) << "Ending cygwin install" << endLog;
 
diff --git a/LogFile.h b/LogFile.h
index ddbc2dcd..8efa1b0d 100644
--- a/LogFile.h
+++ b/LogFile.h
@@ -18,6 +18,7 @@
 
 #include "LogSingleton.h"
 #include <sstream>
+#include <vector>
 
 // Logging class. Default logging level is PLAIN.
 class LogFile : public LogSingleton {
@@ -36,18 +37,21 @@ public:
    * but doesn't call generic C++ destructors
    */
   virtual void exit (int exit_code, bool show_end_install_msg = true)
-         __attribute__ ((noreturn));
+          __attribute__ ((noreturn));
+  virtual void atexit( void (*func)(void));
+
   virtual void flushAll ();
   virtual ~LogFile();
   // get a specific verbosity stream.
   virtual std::ostream &operator() (enum log_level level);
-  
+
 protected:
   LogFile(std::stringbuf *aStream);
   LogFile (LogFile const &); // no copy constructor
   LogFile &operator = (LogFile const&); // no assignment operator
   virtual void endEntry(); // the current in-progress entry is complete.
   static int exit_msg;
+  std::vector <void (*)(void)> exit_fns;
 private:
   void log_save (int babble, const std::string& filename, bool append);
 };
diff --git a/Makefile.am b/Makefile.am
index 82b9d3a5..4da206f2 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -66,8 +66,10 @@ inilint_LDADD = \
 inilint_SOURCES = \
        filemanip.cc \
        filemanip.h \
-       CliParseFeedback.cc \
-       CliParseFeedback.h \
+       cli/CliParseFeedback.cc \
+       cli/CliGetUrlFeedback.cc \
+       cli/CliHashCheckFeedback.cc \
+       cli/CliFeedback.h \
        LogSingleton.cc \
        LogSingleton.h \
        IniDBBuilder.h \
@@ -183,6 +185,12 @@ endif
        geturl.h \
        gpg-packet.cc \
        gpg-packet.h \
+       gui/GuiParseFeedback.cc \
+       gui/GuiGetNetAuth.cc \
+       gui/GuiGetNetAuth.h \
+       gui/GuiGetUrlFeedback.cc \
+       gui/GuiFeedback.h \
+       gui/GuiHashCheckFeedback.cc \
        ini.cc \
        ini.h \
        IniDBBuilder.h \
@@ -190,7 +198,7 @@ endif
        IniDBBuilderPackage.h \
        inilex.ll \
        iniparse.yy \
-       IniParseFeedback.h \
+       Feedback.h \
        install.cc \
        io_stream.cc \
        io_stream.h \
@@ -268,8 +276,10 @@ endif
        setup_version.c \
        sha2.h \
        sha2.c \
-       site.cc \
-       site.h \
+       gui/SitePage.cc \
+       gui/SitePage.h \
+       SiteSetting.cc \
+       SiteSetting.h \
        source.cc \
        source.h \
        SourceSetting.cc \
diff --git a/SiteSetting.cc b/SiteSetting.cc
new file mode 100644
index 00000000..9fde12ce
--- /dev/null
+++ b/SiteSetting.cc
@@ -0,0 +1,196 @@
+/*
+ * Copyright (c) 2000, Red Hat, Inc.
+ *
+ *     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.
+ *
+ *     A copy of the GNU General Public License can be found at
+ *     http://www.gnu.org/
+ *
+ * Written by DJ Delorie <[email protected]>
+ *
+ */
+
+#include "io_stream.h"
+#include "SiteSetting.h"
+#include "UserSettings.h"
+
+#include "getopt++/StringArrayOption.h"
+#include "getopt++/BoolOption.h"
+#include "resource.h"
+
+#include <algorithm>
+#include <string>
+#include <vector>
+#include <string.h>
+
+StringArrayOption SiteOption('s', "site", IDS_HELPTEXT_SITE);
+extern BoolOption UnsupportedOption;
+
+/* Selected sites */
+SiteList site_list;
+
+/* Fresh mirrors + selected sites */
+SiteList all_site_list;
+
+SiteSetting::SiteSetting (): saved (false)
+{
+  std::vector<std::string> SiteOptionStrings = SiteOption;
+  if (SiteOptionStrings.size())
+    {
+      for (std::vector<std::string>::const_iterator n = 
SiteOptionStrings.begin ();
+           n != SiteOptionStrings.end (); ++n)
+        registerSavedSite (n->c_str ());
+    }
+  else
+    getSavedSites ();
+}
+
+const char *
+SiteSetting::lastMirrorKey ()
+{
+  if (UnsupportedOption)
+    return "last-mirror-unsupported";
+
+  return "last-mirror";
+}
+
+void
+SiteSetting::save()
+{
+  io_stream *f = UserSettings::instance().open (lastMirrorKey ());
+  if (f)
+    {
+      for (SiteList::const_iterator n = site_list.begin ();
+           n != site_list.end (); ++n)
+        *f << n->url;
+      delete f;
+    }
+  saved = true;
+}
+
+SiteSetting::~SiteSetting ()
+{
+  if (!saved)
+    save ();
+}
+
+/* List of machines that should not be used by default when saved
+   in "last-mirror". */
+#define NOSAVE1 "ftp://sourceware.org/";
+#define NOSAVE1_LEN (sizeof (NOSAVE2) - 1)
+#define NOSAVE2 "ftp://sources.redhat.com/";
+#define NOSAVE2_LEN (sizeof (NOSAVE1) - 1)
+#define NOSAVE3 "ftp://gcc.gnu.org/";
+#define NOSAVE3_LEN (sizeof (NOSAVE3) - 1)
+
+void
+SiteSetting::registerSavedSite (const char * site)
+{
+  site_list_type tempSite(site, "", "", "", false);
+
+  /* Don't default to certain machines if they suffer from bandwidth
+     limitations. */
+  if (strnicmp (site, NOSAVE1, NOSAVE1_LEN) == 0
+      || strnicmp (site, NOSAVE2, NOSAVE2_LEN) == 0
+      || strnicmp (site, NOSAVE3, NOSAVE3_LEN) == 0)
+    return;
+
+  site_list_insert (all_site_list, tempSite);
+  site_list.push_back (tempSite);
+}
+
+void
+SiteSetting::getSavedSites ()
+{
+  const char *buf = UserSettings::instance().get (lastMirrorKey ());
+  if (!buf)
+    return;
+  char *fg_ret = strdup (buf);
+  for (char *site = strtok (fg_ret, "\n"); site; site = strtok (NULL, "\n"))
+    registerSavedSite (site);
+  free (fg_ret);
+}
+
+site_list_type::site_list_type (const std::string &_url,
+                                const std::string &_servername,
+                                const std::string &_area,
+                                const std::string &_location,
+                                bool _from_mirrors_lst,
+                                bool _noshow, /* default: false */
+                                const std::string &_redir /* default: "" */)
+{
+  url = _url;
+  servername = _servername;
+  area = _area;
+  location = _location;
+  from_mirrors_lst = _from_mirrors_lst;
+  noshow = _noshow;
+  redir = _redir;
+
+  /* Canonicalize URL to ensure it ends with a '/' */
+  if (url.at(url.length()-1) != '/')
+    url.append("/");
+
+  /* displayed_url is protocol and site name part of url */
+  std::string::size_type path_offset = url.find ("/", url.find ("//") + 2);
+  displayed_url = url.substr(0, path_offset);
+
+  /* the sorting key is hostname components in reverse order (to sort by 
country code)
+     plus the url (to ensure uniqueness) */
+  key = std::string();
+  std::string::size_type last_idx = displayed_url.length () - 1;
+  std::string::size_type idx = url.find_last_of("./", last_idx);
+  if (last_idx - idx == 3)
+  {
+    /* Sort non-country TLDs (.com, .net, ...) together. */
+    key += " ";
+  }
+  do
+  {
+    key += url.substr(idx + 1, last_idx - idx);
+    key += " ";
+    last_idx = idx - 1;
+    idx = url.find_last_of("./", last_idx);
+    if (idx == std::string::npos)
+      idx = 0;
+  } while (idx > 0);
+  key += url;
+}
+
+bool
+site_list_type::operator == (site_list_type const &rhs) const
+{
+  return stricmp (key.c_str(), rhs.key.c_str()) == 0;
+}
+
+bool
+site_list_type::operator < (site_list_type const &rhs) const
+{
+  return stricmp (key.c_str(), rhs.key.c_str()) < 0;
+}
+
+/*
+  A SiteList is maintained as an in-order std::vector of site_list_type, by
+  replacing it with a new object with the new item inserted in the correct
+  place.
+
+  Yes, we could just use an ordered container, instead.
+*/
+void
+site_list_insert(SiteList &site_list, site_list_type newsite)
+{
+  SiteList::iterator i = find (site_list.begin(), site_list.end(), newsite);
+  if (i == site_list.end())
+    {
+      SiteList result;
+      merge (site_list.begin(), site_list.end(),
+             &newsite, &newsite + 1,
+             inserter (result, result.begin()));
+      site_list = result;
+    }
+  else
+    *i = newsite;
+}
diff --git a/site.h b/SiteSetting.h
similarity index 74%
rename from site.h
rename to SiteSetting.h
index 387de29d..6d3ef87a 100644
--- a/site.h
+++ b/SiteSetting.h
@@ -13,45 +13,31 @@
  *
  */
 
-#ifndef SETUP_SITE_H
-#define SETUP_SITE_H
+#ifndef SETUP_SITESETTING_H
+#define SETUP_SITESETTING_H
 
-#include <string>
 #include <vector>
 
-#include "proppage.h"
-
-class SitePage : public PropertyPage
+class SiteSetting
 {
-public:
-  SitePage ();
-  virtual ~ SitePage ()
-  {
-  };
-
-  bool Create ();
-
-  virtual void OnInit ();
-  virtual void OnActivate ();
-  virtual long OnNext ();
-  virtual long OnBack ();
-  virtual long OnUnattended ();
-
-  virtual bool OnMessageCmd (int id, HWND hwndctl, UINT code);
-
-  void PopulateListBox();
-  void CheckControlsAndDisableAccordingly () const;
+  public:
+    SiteSetting ();
+    void save ();
+    ~SiteSetting ();
+  private:
+    bool saved;
+    void getSavedSites();
+    void registerSavedSite(char const *);
+    const char *lastMirrorKey();
 };
 
-void do_download_site_info (HINSTANCE h, HWND owner);
-
 class site_list_type
 {
 public:
   site_list_type () : url (), displayed_url (), key () {};
   site_list_type (const std::string& , const std::string& ,
-                  const std::string& , const std::string&, bool, bool,
-                  const std::string &);
+                  const std::string& , const std::string&, bool, bool = false,
+                  const std::string& = "");
   ~site_list_type () {};
   std::string url;
   // provided by mirrors.lst but not used
@@ -78,22 +64,11 @@ public:
 
 typedef std::vector <site_list_type> SiteList;
 
+void site_list_insert(SiteList &site_list, site_list_type newsite);
+
 /* user chosen sites */
 extern SiteList site_list;
 /* potential sites */
 extern SiteList all_site_list;
 
-class SiteSetting
-{
-  public:
-    SiteSetting ();
-    void save ();
-    ~SiteSetting ();
-  private:
-    bool saved;
-    void getSavedSites();
-    void registerSavedSite(char const *);
-    const char *lastMirrorKey();
-};
-
-#endif /* SETUP_SITE_H */
+#endif /* SETUP_SITESETTING_H */
diff --git a/UserSettings.cc b/UserSettings.cc
index c8ddd3d2..3ec6798d 100644
--- a/UserSettings.cc
+++ b/UserSettings.cc
@@ -65,14 +65,17 @@ UserSettings::extend_table (ssize_t i)
 }
 
 io_stream *
-UserSettings::open_settings (const char *filename, std::string &pathname)
+UserSettings::open_settings (const char *filename, std::string &dir, 
std::string &pathname)
 {
+  // first look for a settings file in specified dir
   pathname = "file://";
-  pathname += cwd;
-  if (!isdirsep (cwd[cwd.size () - 1]) && !isdirsep (filename[0]))
+  pathname += dir;
+  if (!isdirsep (dir[dir.size () - 1]) && !isdirsep (filename[0]))
     pathname += "/";
   pathname += filename;
   io_stream *f = io_stream::open(pathname, "rt", 0);
+
+  // if not found, look in cygwin installation
   if (!f)
     {
       pathname = "cygfile:///etc/setup/";
@@ -92,8 +95,7 @@ UserSettings::UserSettings ()
 void
 UserSettings::load (std::string local_dir)
 {
-  cwd = local_dir;
-  io_stream *f = open_settings ("setup.rc", filename);
+  io_stream *f = open_settings ("setup.rc", local_dir, filename);
 
   if (!f)
     return;
diff --git a/UserSettings.h b/UserSettings.h
index 3de06e1d..dc06ab23 100644
--- a/UserSettings.h
+++ b/UserSettings.h
@@ -27,7 +27,6 @@ private:
   ssize_t table_len;
 
   std::string filename;
-  std::string cwd;
 
 public:
   static class UserSettings *global;
@@ -44,8 +43,7 @@ public:
 
 private:
   void extend_table (ssize_t);
-  io_stream *open_settings (const char *, std::string&);
-
+  io_stream *open_settings (const char *, std::string &, std::string&);
 };
 
 #endif // SETUP_USERSETTINGS_H
diff --git a/choose.cc b/choose.cc
index c7831d9e..1bee0fa2 100644
--- a/choose.cc
+++ b/choose.cc
@@ -52,6 +52,7 @@
 #include "choose_cli.h"
 
 #include "threebar.h"
+#include "gui/GuiFeedback.h"
 #include "Generic.h"
 #include "ControlAdjuster.h"
 #include "prereq.h"
@@ -349,9 +350,10 @@ void
 ChooserPage::OnActivate()
 {
   SetBusy();
+  GuiFeedback feedback(GetHWND());
 
   packagedb db;
-  db.prep();
+  db.prep(feedback);
 
   if (!activated)
     {
diff --git a/cli/CliFeedback.h b/cli/CliFeedback.h
new file mode 100644
index 00000000..cb596503
--- /dev/null
+++ b/cli/CliFeedback.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2020 Jon Turney
+ *
+ *     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.
+ *
+ *     A copy of the GNU General Public License can be found at
+ *     http://www.gnu.org/
+ *
+ */
+
+#include "Feedback.h"
+
+class CliFeedback : public Feedback
+{
+  // ini parse
+public:
+  virtual void parse_init ();
+  virtual void parse_finish ();
+  virtual void progress (unsigned long const pos, unsigned long const max);
+  virtual void iniName (const std::string& name);
+  virtual void babble (const std::string& message) const;
+  virtual void warning (const std::string& message) const;
+  virtual void show_errors () const;
+  virtual void note_error(int lineno, const std::string &s);
+  virtual bool has_errors () const;
+private:
+  int error_count = 0;
+
+  // URL fetch
+public:
+  void fetch_progress_disable (bool);
+  void fetch_init (const std::string &url, int length);
+  void fetch_set_length(int length);
+  void fetch_set_total_length(long long int total_length);
+  void fetch_progress (int bytes);
+  void fetch_total_progress ();
+  void fetch_finish (int total_bytes);
+  void fetch_fatal (const char *filename, const char *err);
+
+private:
+  int max_bytes;
+  long long int total_download_bytes = 0; // meaning ???
+  long long int total_download_bytes_sofar = 0;
+
+  unsigned int last_tics;
+  unsigned int start_tics;
+
+  // hash checking
+public:
+  void hash_init (const char *hashalg, const std::string &url);
+  void hash_progress (int bytes, int total_bytes);
+
+  // owner
+public:
+  HWND owner () { return NULL; }
+
+};
diff --git a/cli/CliGetNetAuth.cc b/cli/CliGetNetAuth.cc
new file mode 100644
index 00000000..a1fde3b0
--- /dev/null
+++ b/cli/CliGetNetAuth.cc
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2000, 2001, Red Hat, Inc.
+ *
+ *     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.
+ *
+ *     A copy of the GNU General Public License can be found at
+ *     http://www.gnu.org/
+ *
+ */
+
+/* Query user for auth information required */
+
+#include "netio.h"
+#include "CliGetNetAuth.h"
+
+#include "LogFile.h"
+
+static int
+auth_common(const char *mode)
+{
+  Log (LOG_PLAIN) << mode << " not implemented" << endLog;
+  Logger ().exit (1);
+  return 1;
+}
+
+int
+CliGetNetAuth::get_auth ()
+{
+  return auth_common("get_auth");
+}
+
+int
+CliGetNetAuth::get_proxy_auth ()
+{
+  return auth_common("get_proxy_auth");
+}
+
+int
+CliGetNetAuth::get_ftp_auth ()
+{
+  return auth_common("get_ftp_auth");
+}
diff --git a/cli/CliGetNetAuth.h b/cli/CliGetNetAuth.h
new file mode 100644
index 00000000..7ff45206
--- /dev/null
+++ b/cli/CliGetNetAuth.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2000, Red Hat, Inc.
+ *
+ *     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.
+ *
+ *     A copy of the GNU General Public License can be found at
+ *     http://www.gnu.org/
+ *
+ * Written by DJ Delorie <[email protected]>
+ *
+ */
+
+#ifndef SETUP_CLI_GETNETAUTH_H
+#define SETUP_CLI_GETNETAUTH_H
+
+#include "GetNetAuth.h"
+
+class CliGetNetAuth : public GetNetAuth
+{
+public:
+  /* Helper functions for http/ftp protocols.  Both return nonzero for
+     "cancel", zero for "ok".  They set net_proxy_user, etc, in
+     state.h */
+  int get_auth ();
+  int get_proxy_auth ();
+  int get_ftp_auth ();
+};
+
+#endif /* SETUP_CLI_GETNETAUTH_H */
diff --git a/cli/CliGetUrlFeedback.cc b/cli/CliGetUrlFeedback.cc
new file mode 100644
index 00000000..1256118a
--- /dev/null
+++ b/cli/CliGetUrlFeedback.cc
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2024 Jon Turney
+ *
+ *     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.
+ *
+ *     A copy of the GNU General Public License can be found at
+ *     http://www.gnu.org/
+ *
+ */
+
+#include "cli/CliFeedback.h"
+#include "msg.h"
+#include "resource.h"
+#include <stdio.h>
+
+void
+CliFeedback::fetch_progress_disable(bool disable)
+{
+}
+
+void
+CliFeedback::fetch_init (const std::string &url, int length)
+{
+  max_bytes = length;
+  printf("Fetching: %s\n", url.c_str());
+  start_tics = GetTickCount ();
+}
+
+void
+CliFeedback::fetch_set_length(int length)
+{
+  max_bytes = length;
+}
+
+void
+CliFeedback::fetch_set_total_length(long long int total_length)
+{
+  total_download_bytes = total_length;
+  total_download_bytes_sofar = 0;
+}
+
+void
+CliFeedback::fetch_progress (int bytes)
+{
+  DWORD tics = GetTickCount ();
+  if (tics == start_tics)       // to prevent division by zero
+    return;
+  if (tics < last_tics + 200)   // to prevent flickering updates
+    return;
+  last_tics = tics;
+
+  double kbps = ((double)bytes) / (double)(tics - start_tics);
+
+  if (max_bytes > 0)
+    {
+      int perc = (int)(100.0 * ((double)bytes) / (double)max_bytes);
+      printf ("%d %%  (%dk/%dk)  %03.1f kB/s",
+              perc, bytes / 1000, max_bytes / 1000, kbps);
+
+    }
+  else
+    printf("%d  %2.1f kB/s", bytes, kbps);
+
+  if (total_download_bytes > 0)
+    {
+      int total_perc = (int)(100.0 * ((double)total_download_bytes_sofar + 
bytes/
+                                      (double)total_download_bytes));
+      printf("%d %%", total_perc);
+    }
+  printf("\n");
+}
+
+void
+CliFeedback::fetch_total_progress ()
+{
+}
+
+void
+CliFeedback::fetch_finish (int total_bytes)
+{
+  total_download_bytes_sofar += total_bytes;
+}
+
+void
+CliFeedback::fetch_fatal (const char *filename, const char *err)
+{
+  ::fatal (NULL, IDS_ERR_OPEN_WRITE, filename, err);
+}
diff --git a/cli/CliHashCheckFeedback.cc b/cli/CliHashCheckFeedback.cc
new file mode 100644
index 00000000..f5df9fcd
--- /dev/null
+++ b/cli/CliHashCheckFeedback.cc
@@ -0,0 +1,30 @@
+/*
+ *
+ *     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.
+ *
+ *     A copy of the GNU General Public License can be found at
+ *     http://www.gnu.org/
+ *
+ */
+
+#include "cli/CliFeedback.h"
+#include "resource.h"
+#include "String++.h"
+#include <iostream>
+
+void
+CliFeedback::hash_init(const char *hashalg, const std::string &shortname)
+{
+  std::wstring fmt = LoadStringW(IDS_PROGRESS_CHECKING_HASH);
+  std::wstring s = format(fmt, hashalg, shortname.c_str());
+  std::cout << wstring_to_string(s) << std::endl;
+}
+
+void
+CliFeedback::hash_progress(int bytes, int total_bytes)
+{
+  std::cout << bytes << "/" << total_bytes << "\r";
+}
diff --git a/CliParseFeedback.cc b/cli/CliParseFeedback.cc
similarity index 58%
rename from CliParseFeedback.cc
rename to cli/CliParseFeedback.cc
index 6dc48ba3..a58ee5a6 100644
--- a/CliParseFeedback.cc
+++ b/cli/CliParseFeedback.cc
@@ -11,35 +11,45 @@
  *
  */
 
-#include "CliParseFeedback.h"
+#include "cli/CliFeedback.h"
 #include "LogSingleton.h"
 #include <sstream>
 #include <iostream>
 
-void CliParseFeedback::progress (unsigned long const pos, unsigned long const 
max)
+void
+CliFeedback::parse_init()
 {
-  std::cout << pos << "/" << max << std::endl;
 }
 
-void CliParseFeedback::iniName (const std::string& name)
+void
+CliFeedback::parse_finish()
 {
 }
 
-void CliParseFeedback::babble (const std::string& message) const
+void CliFeedback::progress (unsigned long const pos, unsigned long const max)
+{
+  std::cout << pos << "/" << max << "\r";
+}
+
+void CliFeedback::iniName (const std::string& name)
+{
+}
+
+void CliFeedback::babble (const std::string& message) const
 {
   Log (LOG_BABBLE) << message << endLog;
 }
 
-void CliParseFeedback::warning (const std::string& message) const
+void CliFeedback::warning (const std::string& message) const
 {
   std::cout << "Warning: " << message << std::endl;
 }
 
-void CliParseFeedback::show_errors () const
+void CliFeedback::show_errors () const
 {
 }
 
-void CliParseFeedback::note_error(int lineno, const std::string &s)
+void CliFeedback::note_error(int lineno, const std::string &s)
 {
   std::ostringstream buf;
   buf << "line " << lineno << ": ";
@@ -48,7 +58,7 @@ void CliParseFeedback::note_error(int lineno, const 
std::string &s)
   error_count++;
 }
 
-bool CliParseFeedback::has_errors () const
+bool CliFeedback::has_errors () const
 {
   return (error_count > 0);
 }
diff --git a/crypto.cc b/crypto.cc
index a837f8da..96c5e605 100644
--- a/crypto.cc
+++ b/crypto.cc
@@ -18,6 +18,7 @@
 #include <unistd.h>
 #include <vector>
 #include "io_stream.h"
+#include "win32.h"
 #include "crypto.h"
 #include "compress.h"
 #include "gcrypt.h"
@@ -29,6 +30,7 @@
 #include "KeysSetting.h"
 #include "gpg-packet.h"
 #include "geturl.h"
+#include "Feedback.h"
 
 #ifndef CRYPTODEBUGGING
 #define CRYPTODEBUGGING         (0)
@@ -649,7 +651,7 @@ gcrypt_log_adaptor(void *priv, int level, const char *fmt, 
va_list args)
 
 /*  Verify the signature on an ini file.  Takes care of all key-handling.  */
 bool
-verify_ini_file_sig (io_stream *ini_file, io_stream *ini_sig_file, HWND owner)
+verify_ini_file_sig (io_stream *ini_file, io_stream *ini_sig_file, Feedback 
&feedback)
 {
   /*  Data returned from packet walker.  */
   struct sig_data sigdat;
@@ -674,7 +676,7 @@ verify_ini_file_sig (io_stream *ini_file, io_stream 
*ini_sig_file, HWND owner)
       gcry_check_version (NULL);
 
       if ((rv = gcry_control (GCRYCTL_SELFTEST)) != GPG_ERR_NO_ERROR)
-        ERRKIND (owner, IDS_CRYPTO_ERROR, rv, "libgcrypt selftest failed");
+        ERRKIND (feedback.owner(), IDS_CRYPTO_ERROR, rv, "libgcrypt selftest 
failed");
 
 #if CRYPTODEBUGGING
       gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 1);
@@ -687,7 +689,7 @@ verify_ini_file_sig (io_stream *ini_file, io_stream 
*ini_sig_file, HWND owner)
   rv = gcry_sexp_new (&cygwin_key, cygwin_pubkey_sexpr, strlen 
(cygwin_pubkey_sexpr), 1);
   if (rv != GPG_ERR_NO_ERROR)
     {
-      ERRKIND (owner, IDS_CRYPTO_ERROR, rv, "while creating pubkey s-expr.");
+      ERRKIND (feedback.owner(), IDS_CRYPTO_ERROR, rv, "while creating pubkey 
s-expr.");
     }
   else
     {
@@ -707,7 +709,7 @@ verify_ini_file_sig (io_stream *ini_file, io_stream 
*ini_sig_file, HWND owner)
       rv = gcry_sexp_new (&cygwin_old_key, cygwin_old_pubkey_sexpr, strlen 
(cygwin_old_pubkey_sexpr), 1);
       if (rv != GPG_ERR_NO_ERROR)
         {
-          ERRKIND (owner, IDS_CRYPTO_ERROR, rv, "while creating old pubkey 
s-expr.");
+          ERRKIND (feedback.owner(), IDS_CRYPTO_ERROR, rv, "while creating old 
pubkey s-expr.");
         }
       else
         {
@@ -791,7 +793,7 @@ verify_ini_file_sig (io_stream *ini_file, io_stream 
*ini_sig_file, HWND owner)
        }
       else
        {
-         ERRKIND (owner, IDS_CRYPTO_ERROR, rv, "invalid command-line pubkey 
s-expr.");
+         ERRKIND (feedback.owner(), IDS_CRYPTO_ERROR, rv, "invalid 
command-line pubkey s-expr.");
        }
     }
 
@@ -801,11 +803,11 @@ verify_ini_file_sig (io_stream *ini_file, io_stream 
*ini_sig_file, HWND owner)
                                        = ExtraKeysFiles.begin ();
                it != ExtraKeysFiles.end (); ++it)
     {
-      io_stream *keys = get_url_to_membuf (*it, owner);
+      io_stream *keys = get_url_to_membuf (*it, feedback);
       if (keys)
        {
          struct key_data kdat;
-         pkt_walk_packets (keys, key_file_walker, owner, 0, keys->get_size (), 
&kdat);
+         pkt_walk_packets (keys, key_file_walker, feedback.owner(), 0, 
keys->get_size (), &kdat);
          // We now have a vector of (some/any?) keys returned from
          // the walker; add them to the list to try.
          while (!kdat.keys.empty ())
@@ -835,7 +837,7 @@ verify_ini_file_sig (io_stream *ini_file, io_stream 
*ini_sig_file, HWND owner)
   sigdat.sign_data = ini_file;
   sigdat.keys_to_try = &keys_to_try;
 
-  pkt_walk_packets (ini_sig_file, sig_file_walker, owner, 0,
+  pkt_walk_packets (ini_sig_file, sig_file_walker, feedback.owner(), 0,
                     ini_sig_file->get_size (), &sigdat);
 
   sig_ok = sigdat.valid;
diff --git a/crypto.h b/crypto.h
index 661d86da..17389e40 100644
--- a/crypto.h
+++ b/crypto.h
@@ -17,20 +17,19 @@
 #define SETUP_CRYPTO_H
 
 /* This module uses libgcrypt functionality to verify signatures
- * on downloaded setup.ini or setup.bz2 files.
+ * on downloaded (compressed) setup.ini files.
  */
 
-/* for HWND */
-#include "win32.h" 
+class Feedback;
 class io_stream;
 
 /*  This is currently the only public API exported by the module; it
-  takes the contents of setup.ini or setup.bz2 in one (memory-based,
+  takes the contents of (compressed) setup.ini in one (memory-based,
   for preference) io_stream, and the contents of the related signature
   file in another.  It is called from ini.cc/do_remote_ini() and returns
   true if the signature verified OK; if it returns false, you MUST NOT
   use the failed ini file - doubly so if it's a compressed stream!  */
-extern bool verify_ini_file_sig (io_stream *ini_file, io_stream *ini_sig_file, 
HWND owner);
+bool verify_ini_file_sig (io_stream *ini_file, io_stream *ini_sig_file, 
Feedback &feedback);
 
 /*
 5.2.2.  Version 3 Signature Packet Format
diff --git a/dialog.h b/dialog.h
index 63c98ee6..ebbf6617 100644
--- a/dialog.h
+++ b/dialog.h
@@ -20,9 +20,6 @@
 
 #include "win32.h"
 
-/* global instance for the application; set in main.cc */
-extern HINSTANCE hinstance;
-
 /* used by main.cc to select the next do_* function */
 extern int next_dialog;
 
diff --git a/download.cc b/download.cc
index 5bfcebf0..fbe36e55 100644
--- a/download.cc
+++ b/download.cc
@@ -42,6 +42,7 @@
 #include "package_source.h"
 
 #include "threebar.h"
+#include "gui/GuiFeedback.h"
 
 #include "Exception.h"
 
@@ -51,14 +52,14 @@ extern ThreeBarProgressPage Progress;
 // user chooses to delete the file; otherwise throw an exception.
 static bool
 validateCachedPackage (const std::string& fullname, packagesource & pkgsource,
-                      HWND owner, bool check_hash, bool check_size)
+                       Feedback &feedback, bool check_hash, bool check_size)
 {
   try
     {
       if (check_size)
        pkgsource.check_size_and_cache (fullname);
       if (check_hash)
-       pkgsource.check_hash ();
+       pkgsource.check_hash (feedback);
       return true;
     }
   catch (Exception *e)
@@ -68,7 +69,7 @@ validateCachedPackage (const std::string& fullname, 
packagesource & pkgsource,
       if (strncmp (filename, "file://", 7) == 0)
        filename += 7;
       if (e->errNo() == APPERR_CORRUPT_PACKAGE
-         && yesno (owner, IDS_QUERY_CORRUPT, filename) == IDYES)
+         && yesno (feedback.owner(), IDS_QUERY_CORRUPT, filename) == IDYES)
        remove (filename);
       else
        throw e;
@@ -79,8 +80,8 @@ validateCachedPackage (const std::string& fullname, 
packagesource & pkgsource,
 /* 0 if not cached; may throw exception if validation fails.
  */
 int
-check_for_cached (packagesource & pkgsource, HWND owner, bool mirror_mode,
-                 bool check_hash)
+check_for_cached (packagesource & pkgsource, Feedback &feedback,
+                  bool mirror_mode, bool check_hash)
 {
   /* If the packagesource doesn't have a filename, it can't possibly be in the
      cache */
@@ -104,7 +105,7 @@ check_for_cached (packagesource & pkgsource, HWND owner, 
bool mirror_mode,
   // Already found one, which we can assume to have the right size.
   if (pkgsource.Cached())
     {
-      if (validateCachedPackage (pkgsource.Cached(), pkgsource, owner,
+      if (validateCachedPackage (pkgsource.Cached(), pkgsource, feedback,
                                 check_hash, false))
        return 1;
       // If we get here, pkgsource.Cached() was corrupt and deleted.
@@ -116,7 +117,7 @@ check_for_cached (packagesource & pkgsource, HWND owner, 
bool mirror_mode,
   */
   if (io_stream::exists (fullname))
     {
-      if (validateCachedPackage (fullname, pkgsource, owner, check_hash, true))
+      if (validateCachedPackage (fullname, pkgsource, feedback, check_hash, 
true))
        return 1;
       // If we get here, fullname was corrupt and deleted, but it
       // might have been cached.
@@ -133,7 +134,7 @@ check_for_cached (packagesource & pkgsource, HWND owner, 
bool mirror_mode,
       pkgsource.Canonical ();
     if (io_stream::exists(fullname))
        {
-         if (validateCachedPackage (fullname, pkgsource, owner, check_hash,
+         if (validateCachedPackage (fullname, pkgsource, feedback, check_hash,
                                     true))
            return 1;
          // If we get here, fullname was corrupt and deleted, but it
@@ -146,11 +147,11 @@ check_for_cached (packagesource & pkgsource, HWND owner, 
bool mirror_mode,
 
 /* download a file from a mirror site to the local cache. */
 static int
-download_one (packagesource & pkgsource, HWND owner)
+download_one (packagesource & pkgsource, Feedback &feedback)
 {
   try
     {
-      if (check_for_cached (pkgsource, owner))
+      if (check_for_cached (pkgsource, feedback))
         return 0;
     }
   catch (Exception * e)
@@ -158,7 +159,7 @@ download_one (packagesource & pkgsource, HWND owner)
       // We know what to do with these..
       if (e->errNo() == APPERR_CORRUPT_PACKAGE)
        {
-         fatal (owner, IDS_CORRUPT_PACKAGE, pkgsource.Canonical());
+         fatal (feedback.owner(), IDS_CORRUPT_PACKAGE, pkgsource.Canonical());
          return 1;
        }
       // Unexpected exception.
@@ -176,7 +177,7 @@ download_one (packagesource & pkgsource, HWND owner)
       io_stream::mkpath_p (PATH_TO_FILE, "file://" + local, 0);
 
       if (get_url_to_file(n->key + pkgsource.Canonical (),
-                         local + ".tmp", pkgsource.size, owner))
+                         local + ".tmp", pkgsource.size, feedback))
        {
          /* FIXME: note new source ? */
          continue;
@@ -189,7 +190,7 @@ download_one (packagesource & pkgsource, HWND owner)
                remove (local.c_str());
              rename ((local + ".tmp").c_str(), local.c_str());
              pkgsource.check_size_and_cache ("file://" + local);
-             pkgsource.check_hash ();
+             pkgsource.check_hash (feedback);
              Log (LOG_PLAIN) << "Downloaded " << local << endLog;
              success = 1;
              // FIXME: move the downloaded file to the 
@@ -274,10 +275,9 @@ static int
 do_download_thread (HINSTANCE h, HWND owner)
 {
   int errors = 0;
-  total_download_bytes = 0;
-  total_download_bytes_sofar = 0;
   download_failures.clear ();
 
+  GuiFeedback feedback(owner);
   Progress.SetText1 (IDS_PROGRESS_CHECKING);
   Progress.SetText2 ("");
   Progress.SetText3 ("");
@@ -286,6 +286,7 @@ do_download_thread (HINSTANCE h, HWND owner)
   const SolverTransactionList &t = db.solution.transactions();
 
   /* calculate the total size of the download */
+  long long int total_download_bytes = 0;
   for (SolverTransactionList::const_iterator i = t.begin (); i != t.end (); 
++i)
     {
       if (i->type != SolverTransaction::transInstall)
@@ -294,7 +295,7 @@ do_download_thread (HINSTANCE h, HWND owner)
 
       try
         {
-          if (!check_for_cached (*version.source(), owner))
+          if (!check_for_cached (*version.source(), feedback))
             total_download_bytes += version.source()->size;
         }
       catch (Exception * e)
@@ -308,6 +309,8 @@ do_download_thread (HINSTANCE h, HWND owner)
       Progress.SetBar2(std::distance(t.begin(), i) + 1, t.size());
     }
 
+  feedback.fetch_set_total_length(total_download_bytes);
+
   /* and do the download. FIXME: This here we assign a new name for the cached 
version
    * and check that above.
    */
@@ -319,7 +322,7 @@ do_download_thread (HINSTANCE h, HWND owner)
 
        {
          int e = 0;
-          e += download_one (*version.source(), owner);
+          e += download_one (*version.source(), feedback);
          errors += e;
          if (e)
            download_failures.push_back (version);
diff --git a/download.h b/download.h
index 9f4cb8ee..3f65153e 100644
--- a/download.h
+++ b/download.h
@@ -16,10 +16,10 @@
 #ifndef SETUP_DOWNLOAD_H
 #define SETUP_DOWNLOAD_H
 
-#include "win32.h"
+#include "Feedback.h"
 
 class packagesource;
-int check_for_cached (packagesource & pkgsource, HWND owner,
-                     bool mirror_mode = false, bool check_hash = true);
+int check_for_cached (packagesource & pkgsource, Feedback &feedback,
+                      bool mirror_mode = false, bool check_hash = true);
 
 #endif /* SETUP_DOWNLOAD_H */
diff --git a/fromcwd.cc b/fromcwd.cc
index 7acc0ecb..8cc50057 100644
--- a/fromcwd.cc
+++ b/fromcwd.cc
@@ -32,7 +32,6 @@
 
 #include "FindVisitor.h"
 #include "IniDBBuilderPackage.h"
-#include "IniParseFeedback.h"
 
 class SetupFindVisitor : public FindVisitor
 {
diff --git a/geturl.cc b/geturl.cc
index 679e4688..c4187a6c 100644
--- a/geturl.cc
+++ b/geturl.cc
@@ -15,12 +15,10 @@
  *
  */
 
-/* The purpose of this file is to act as a pretty interface to
-   netio.cc.  We add a progress dialog and some convenience functions
-   (like collect to string or file */
+/* The purpose of this file is to act as a pretty interface to netio.cc.  We 
add
+   a progress reporting and some convenience functions (like collect to string
+   or file) */
 
-#include "win32.h"
-#include "commctrl.h"
 
 #include <stdio.h>
 #include <stdlib.h>
@@ -30,87 +28,24 @@
 #include "geturl.h"
 #include "resource.h"
 #include "netio.h"
-#include "msg.h"
 #include "io_stream.h"
 #include "io_stream_memory.h"
 #include "state.h"
-#include "diskfull.h"
-#include "mount.h"
 #include "filemanip.h"
-
-#include "threebar.h"
+#include "String++.h"
 
 #include "Exception.h"
 
 #include "LogSingleton.h"
-
-extern ThreeBarProgressPage Progress;
-
-static int max_bytes = 0;
-static int is_local_install = 0;
-
-long long int total_download_bytes = 0;
-long long int total_download_bytes_sofar = 0;
-
-static DWORD start_tics;
+#include "Feedback.h"
 
 static void
-init_dialog (const std::string &url, int length)
+getUrlToStream (const std::string &_url, io_stream *output, Feedback &feedback)
 {
-  if (is_local_install)
-    return;
-
-  std::string::size_type divide = url.find_last_of('/');
-  max_bytes = length;
-  Progress.SetText1(IDS_PROGRESS_DOWNLOADING);
-  std::wstring fmt = LoadStringW(IDS_PROGRESS_DOWNLOADING_FROM);
-  std::wstring s = format(fmt,
-                          url.substr(divide + 1).c_str(),
-                          url.substr(0, divide).c_str());
-  Progress.SetText2(s.c_str());
-  Progress.SetText3(IDS_PROGRESS_CONNECTING);
-  Progress.SetBar1(0);
-  start_tics = GetTickCount ();
-}
+  // we turn off this feedback for local files
+  feedback.fetch_progress_disable((source == IDC_SOURCE_LOCALDIR));
 
-
-static void
-progress (int bytes)
-{
-  if (is_local_install)
-    return;
-  static char buf[100];
-  double kbps;
-  static unsigned int last_tics = 0;
-  DWORD tics = GetTickCount ();
-  if (tics == start_tics)      // to prevent division by zero
-    return;
-  if (tics < last_tics + 200)  // to prevent flickering updates
-    return;
-  last_tics = tics;
-
-  kbps = ((double)bytes) / (double)(tics - start_tics);
-  if (max_bytes > 0)
-    {
-      int perc = (int)(100.0 * ((double)bytes) / (double)max_bytes);
-      Progress.SetBar1(bytes, max_bytes);
-      sprintf (buf, "%d %%  (%dk/%dk)  %03.1f kB/s",
-              perc, bytes / 1000, max_bytes / 1000, kbps);
-      if (total_download_bytes > 0)
-         Progress.SetBar2(total_download_bytes_sofar + bytes,
-                          total_download_bytes);
-    }
-  else
-    sprintf (buf, "%d  %2.1f kB/s", bytes, kbps);
-
-  Progress.SetText3(buf);
-}
-
-static void
-getUrlToStream (const std::string &_url, io_stream *output)
-{
-  is_local_install = (source == IDC_SOURCE_LOCALDIR);
-  init_dialog (_url, 0);
+  feedback.fetch_init (_url, 0);
   NetIO *n = NetIO::open (_url.c_str(), true);
   if (!n || !n->ok ())
     {
@@ -119,10 +54,10 @@ getUrlToStream (const std::string &_url, io_stream *output)
     }
 
   if (n->file_size)
-    max_bytes = n->file_size;
+    feedback.fetch_set_length(n->file_size);
 
   int total_bytes = 0;
-  progress (0);
+  feedback.fetch_progress (0);
   while (1)
     {
       char buf[2048];
@@ -135,7 +70,7 @@ getUrlToStream (const std::string &_url, io_stream *output)
            /* FIXME: Show an error message */
            break;
          total_bytes += rlen;
-         progress (total_bytes);
+         feedback.fetch_progress (total_bytes);
        }
       else
        break;
@@ -148,13 +83,13 @@ getUrlToStream (const std::string &_url, io_stream *output)
 }
 
 io_stream *
-get_url_to_membuf (const std::string &_url, HWND owner)
+get_url_to_membuf (const std::string &_url, Feedback &feedback)
 {
   io_stream_memory *membuf = new io_stream_memory ();
-  try 
+  try
     {
-      getUrlToStream (_url, membuf);
-      
+      getUrlToStream (_url, membuf, feedback);
+
       if (membuf->seek (0, IO_SEEK_SET))
        {
          if (membuf)
@@ -175,9 +110,9 @@ get_url_to_membuf (const std::string &_url, HWND owner)
 
 // predicate: url has no '\0''s in it.
 std::string
-get_url_to_string (const std::string &_url, HWND owner)
+get_url_to_string (const std::string &_url, Feedback &feedback)
 {
-  io_stream *stream = get_url_to_membuf (_url, owner);
+  io_stream *stream = get_url_to_membuf (_url, feedback);
   if (!stream)
     return std::string();
   size_t bytes = stream->get_size ();
@@ -200,15 +135,11 @@ int
 get_url_to_file (const std::string &_url,
                  const std::string &_filename,
                  int expected_length,
-                HWND owner)
+                 Feedback &feedback)
 {
   Log (LOG_BABBLE) << "get_url_to_file " << _url << " " << _filename << endLog;
-  if (total_download_bytes > 0)
-    {
-      int df = diskfull (get_root_dir ().c_str());
-      Progress.SetBar3(df);
-    }
-  init_dialog (_url, expected_length);
+  feedback.fetch_total_progress();
+  feedback.fetch_init(_url, expected_length);
 
   remove (_filename.c_str());          /* but ignore errors */
 
@@ -225,14 +156,14 @@ get_url_to_file (const std::string &_url,
       const char *err = strerror (errno);
       if (!err)
        err = "(unknown error)";
-      fatal (owner, IDS_ERR_OPEN_WRITE, _filename.c_str(), err);
+      feedback.fetch_fatal (_filename.c_str(), err);
     }
 
   if (n->file_size)
-    max_bytes = n->file_size;
+    feedback.fetch_set_length(n->file_size);
 
   int total_bytes = 0;
-  progress (0);
+  feedback.fetch_progress(0);
   while (1)
     {
       char buf[8192];
@@ -242,22 +173,15 @@ get_url_to_file (const std::string &_url,
        break;
       fwrite (buf, 1, count, f);
       total_bytes += count;
-      progress (total_bytes);
+      feedback.fetch_progress (total_bytes);
     }
 
-  total_download_bytes_sofar += total_bytes;
-
   fclose (f);
   if (n)
     delete n;
 
-  if (total_download_bytes > 0)
-    {
-      int df = diskfull (get_root_dir ().c_str());
-         Progress.SetBar3(df);
-    }
-  Progress.SetText3("");
+  feedback.fetch_total_progress();
+  feedback.fetch_finish(total_bytes);
 
   return 0;
 }
-
diff --git a/geturl.h b/geturl.h
index f4e963ef..d9175435 100644
--- a/geturl.h
+++ b/geturl.h
@@ -16,19 +16,16 @@
 #ifndef SETUP_GETURL_H
 #define SETUP_GETURL_H
 
-/* Download files from the Internet.  These pop up a progress dialog;
-   don't forget to dismiss it when you're done downloading for a while */
+/* Download files from the Internet. */
 
 #include <string>
 
-extern long long int total_download_bytes;
-extern long long int total_download_bytes_sofar;
-
 class io_stream;
+class Feedback;
 
-io_stream *get_url_to_membuf (const std::string &_url, HWND owner);
-std::string get_url_to_string (const std::string &_url, HWND owner);
+io_stream *get_url_to_membuf (const std::string &_url, Feedback &feedback);
+std::string get_url_to_string (const std::string &_url, Feedback &feedback);
 int get_url_to_file (const std::string &_url, const std::string &_filename,
-                     int expected_size, HWND owner);
+                     int expected_size, Feedback &feedback);
 
 #endif /* SETUP_GETURL_H */
diff --git a/gui/GuiFeedback.h b/gui/GuiFeedback.h
new file mode 100644
index 00000000..7ac0c806
--- /dev/null
+++ b/gui/GuiFeedback.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2024 Jon Turney
+ *
+ *     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.
+ *
+ *     A copy of the GNU General Public License can be found at
+ *     http://www.gnu.org/
+ *
+ */
+
+#include "Feedback.h"
+
+class GuiFeedback : public Feedback
+{
+  // constructor
+public:
+  GuiFeedback(HWND hwnd) : owner_window(hwnd) { };
+
+  // ini parse
+public:
+  void parse_init ();
+  void parse_finish ();
+  void progress (unsigned long const, unsigned long const);
+  void iniName (const std::string& );
+  void babble (const std::string& ) const;
+  void warning (const std::string& ) const;
+  void show_errors () const;
+  void note_error(int lineno, const std::string &error);
+  bool has_errors () const;
+
+private:
+  unsigned int lastpct;
+  std::string filename;
+  std::string yyerror_messages;
+  int yyerror_count;
+
+  // URL fetch
+public:
+  void fetch_progress_disable (bool);
+  void fetch_init (const std::string &url, int length);
+  void fetch_set_length(int length);
+  void fetch_set_total_length(long long int total_length);
+  void fetch_progress (int bytes);
+  void fetch_total_progress ();
+  void fetch_finish (int total_bytes);
+  void fetch_fatal (const char *filename, const char *err);
+
+private:
+  int is_local_install = 0;
+  int max_bytes = 0;
+  long long int total_download_bytes = 0;
+  long long int total_download_bytes_sofar = 0;
+  DWORD start_tics;
+
+  // hash checking
+public:
+  void hash_init (const char *hashalg, const std::string &url);
+  void hash_progress (int bytes, int total_bytes);
+
+public:
+  // owner
+  HWND owner () { return owner_window; }
+
+private:
+  HWND owner_window;
+};
diff --git a/gui/GuiGetNetAuth.cc b/gui/GuiGetNetAuth.cc
new file mode 100644
index 00000000..a6d49176
--- /dev/null
+++ b/gui/GuiGetNetAuth.cc
@@ -0,0 +1,138 @@
+/*
+ * Copyright (c) 2000, 2001, Red Hat, Inc.
+ *
+ *     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.
+ *
+ *     A copy of the GNU General Public License can be found at
+ *     http://www.gnu.org/
+ *
+ */
+
+/* Query user for auth information required */
+
+#include "netio.h"
+#include "GuiGetNetAuth.h"
+
+#include "LogFile.h"
+
+#include "resource.h"
+#include "dialog.h"
+
+static char **user, **passwd;
+static int loading = 0;
+
+static void
+check_if_enable_ok (HWND h)
+{
+  int e = 0;
+  if (*user)
+    e = 1;
+  EnableWindow (GetDlgItem (h, IDOK), e);
+}
+
+static void
+load_dialog (HWND h)
+{
+  loading = 1;
+  eset (h, IDC_NET_USER, *user);
+  eset (h, IDC_NET_PASSWD, *passwd);
+  check_if_enable_ok (h);
+  loading = 0;
+}
+
+static void
+save_dialog (HWND h)
+{
+  *user = eget (h, IDC_NET_USER, *user);
+  *passwd = eget (h, IDC_NET_PASSWD, *passwd);
+  if (! *passwd) {
+    *passwd = new char[1];
+    (*passwd)[0] = '\0';
+  }
+}
+
+static BOOL
+auth_cmd (HWND h, int id, HWND hwndctl, UINT code)
+{
+  switch (id)
+    {
+
+    case IDC_NET_USER:
+    case IDC_NET_PASSWD:
+      if (code == EN_CHANGE && !loading)
+       {
+         save_dialog (h);
+         check_if_enable_ok (h);
+       }
+      break;
+
+    case IDOK:
+      save_dialog (h);
+      EndDialog (h, 0);
+      break;
+
+    case IDCANCEL:
+      EndDialog (h, 1);
+      Logger ().exit (1);
+      break;
+    }
+  return 0;
+}
+
+static INT_PTR CALLBACK
+auth_proc (HWND h, UINT message, WPARAM wParam, LPARAM lParam)
+{
+  switch (message)
+    {
+    case WM_INITDIALOG:
+      load_dialog (h);
+      return FALSE;
+    case WM_COMMAND:
+      auth_cmd (h, LOWORD(wParam), (HWND)lParam, HIWORD(wParam));
+      return 0;
+    }
+  return FALSE;
+}
+
+static int
+auth_common (int id, HWND owner)
+{
+  return DialogBox (NULL, MAKEINTRESOURCE (id), owner, auth_proc);
+}
+
+int
+GuiGetNetAuth::get_auth ()
+{
+  user = &NetIO::net_user;
+  passwd = &NetIO::net_passwd;
+  return auth_common (IDD_NET_AUTH, owner);
+}
+
+int
+GuiGetNetAuth::get_proxy_auth ()
+{
+  user = &NetIO::net_proxy_user;
+  passwd = &NetIO::net_proxy_passwd;
+  return auth_common (IDD_PROXY_AUTH, owner);
+}
+
+int
+GuiGetNetAuth::get_ftp_auth ()
+{
+  if (NetIO::net_ftp_user)
+    {
+      delete[] NetIO::net_ftp_user;
+      NetIO::net_ftp_user = NULL;
+    }
+  if (NetIO::net_ftp_passwd)
+    {
+      delete[] NetIO::net_ftp_passwd;
+      NetIO::net_ftp_passwd = NULL;
+    }
+  user = &NetIO::net_ftp_user;
+  passwd = &NetIO::net_ftp_passwd;
+  return auth_common (IDD_FTP_AUTH, owner);
+}
diff --git a/gui/GuiGetNetAuth.h b/gui/GuiGetNetAuth.h
new file mode 100644
index 00000000..21758f29
--- /dev/null
+++ b/gui/GuiGetNetAuth.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2000, Red Hat, Inc.
+ *
+ *     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.
+ *
+ *     A copy of the GNU General Public License can be found at
+ *     http://www.gnu.org/
+ *
+ * Written by DJ Delorie <[email protected]>
+ *
+ */
+
+#ifndef SETUP_GUI_GETNETAUTH_H
+#define SETUP_GUI_GETNETAUTH_H
+
+#include "win32.h"
+#include "GetNetAuth.h"
+
+class GuiGetNetAuth : public GetNetAuth
+{
+public:
+  GuiGetNetAuth (HWND owner_) : owner(owner_) { };
+
+  /* Helper functions for http/ftp protocols.  Both return nonzero for
+     "cancel", zero for "ok".  They set net_proxy_user, etc, in
+     state.h */
+  int get_auth ();
+  int get_proxy_auth ();
+  int get_ftp_auth ();
+
+private:
+  HWND owner;
+};
+
+#endif /* SETUP_GUI_GETNETAUTH_H */
diff --git a/gui/GuiGetUrlFeedback.cc b/gui/GuiGetUrlFeedback.cc
new file mode 100644
index 00000000..1b5ce9a1
--- /dev/null
+++ b/gui/GuiGetUrlFeedback.cc
@@ -0,0 +1,119 @@
+/*
+ * Copyright (c) 2024 Jon Turney
+ *
+ *     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.
+ *
+ *     A copy of the GNU General Public License can be found at
+ *     http://www.gnu.org/
+ *
+ */
+
+#include "win32.h"
+#include "commctrl.h"
+#include "resource.h"
+#include "diskfull.h"
+#include "mount.h"
+#include "threebar.h"
+#include "msg.h"
+#include "String++.h"
+
+#include "gui/GuiFeedback.h"
+
+extern ThreeBarProgressPage Progress;
+
+void
+GuiFeedback::fetch_set_total_length(long long int total_length)
+{
+  total_download_bytes = total_length;
+  total_download_bytes_sofar = 0;
+}
+
+void
+GuiFeedback::fetch_progress_disable(bool disable)
+{
+  is_local_install = disable;
+}
+
+void
+GuiFeedback::fetch_init (const std::string &url, int length)
+{
+  if (is_local_install)
+    return;
+
+  std::string::size_type divide = url.find_last_of('/');
+  max_bytes = length;
+  Progress.SetText1(IDS_PROGRESS_DOWNLOADING);
+  std::wstring fmt = LoadStringW(IDS_PROGRESS_DOWNLOADING_FROM);
+  std::wstring s = format(fmt,
+                          url.substr(divide + 1).c_str(),
+                          url.substr(0, divide).c_str());
+  Progress.SetText2(s.c_str());
+  Progress.SetText3(IDS_PROGRESS_CONNECTING);
+  Progress.SetBar1(0);
+  start_tics = GetTickCount ();
+}
+
+void
+GuiFeedback::fetch_set_length(int length)
+{
+  max_bytes = length;
+}
+
+void
+GuiFeedback::fetch_progress (int bytes)
+{
+  if (is_local_install)
+    return;
+
+  static char buf[100];
+  double kbps;
+  static unsigned int last_tics = 0;
+  DWORD tics = GetTickCount ();
+  if (tics == start_tics)       // to prevent division by zero
+    return;
+  if (tics < last_tics + 200)   // to prevent flickering updates
+    return;
+  last_tics = tics;
+
+  kbps = ((double)bytes) / (double)(tics - start_tics);
+  if (max_bytes > 0)
+    {
+      int perc = (int)(100.0 * ((double)bytes) / (double)max_bytes);
+      Progress.SetBar1(bytes, max_bytes);
+      sprintf (buf, "%d %%  (%dk/%dk)  %03.1f kB/s",
+               perc, bytes / 1000, max_bytes / 1000, kbps);
+      if (total_download_bytes > 0)
+          Progress.SetBar2(total_download_bytes_sofar + bytes,
+                           total_download_bytes);
+    }
+  else
+    sprintf (buf, "%d  %2.1f kB/s", bytes, kbps);
+
+  Progress.SetText3(buf);
+}
+
+void
+GuiFeedback::fetch_total_progress ()
+{
+  if (total_download_bytes > 0)
+    {
+      int df = diskfull (get_root_dir ().c_str());
+      Progress.SetBar3(df);
+    }
+}
+
+void
+GuiFeedback::fetch_finish (int total_bytes)
+{
+  total_download_bytes_sofar += total_bytes;
+  Progress.SetText3("");
+}
+
+void
+GuiFeedback::fetch_fatal (const char *filename, const char *err)
+{
+  ::fatal (owner_window, IDS_ERR_OPEN_WRITE, filename, err);
+}
diff --git a/gui/GuiHashCheckFeedback.cc b/gui/GuiHashCheckFeedback.cc
new file mode 100644
index 00000000..a0b4c57a
--- /dev/null
+++ b/gui/GuiHashCheckFeedback.cc
@@ -0,0 +1,34 @@
+/*
+ *
+ *     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.
+ *
+ *     A copy of the GNU General Public License can be found at
+ *     http://www.gnu.org/
+ *
+ */
+
+#include "gui/GuiFeedback.h"
+#include "resource.h"
+#include "threebar.h"
+#include "String++.h"
+
+extern ThreeBarProgressPage Progress;
+
+void
+GuiFeedback::hash_init(const char *hashalg, const std::string &shortname)
+{
+  std::wstring fmt = LoadStringW(IDS_PROGRESS_CHECKING_HASH);
+  std::wstring s = format(fmt, hashalg, shortname.c_str());
+  Progress.SetText1(s.c_str());
+  Progress.SetText4(IDS_PROGRESS_PROGRESS);
+  Progress.SetBar1(0);
+}
+
+void
+GuiFeedback::hash_progress(int bytes, int total_bytes)
+{
+  Progress.SetBar1(bytes, total_bytes);
+}
diff --git a/gui/GuiParseFeedback.cc b/gui/GuiParseFeedback.cc
new file mode 100644
index 00000000..6a2b7bc5
--- /dev/null
+++ b/gui/GuiParseFeedback.cc
@@ -0,0 +1,149 @@
+/*
+ * Copyright (c) 2000,2007 Red Hat, Inc.
+ *
+ *     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.
+ *
+ *     A copy of the GNU General Public License can be found at
+ *     http://www.gnu.org/
+ *
+ * Written by DJ Delorie <[email protected]>
+ *
+ */
+
+#include "Exception.h"
+#include "gui/GuiFeedback.h"
+
+#include "ini.h"
+#include "msg.h"
+#include "resource.h"
+#include "state.h"
+#include "threebar.h"
+
+extern ThreeBarProgressPage Progress;
+
+void
+GuiFeedback::parse_init ()
+{
+  Progress.SetText1 (IDS_PROGRESS_PARSING);
+  Progress.SetText2 ("");
+  Progress.SetText3 ("");
+  Progress.SetText4 (IDS_PROGRESS_PROGRESS);
+
+  lastpct = 0;
+  yyerror_count = 0;
+  yyerror_messages.clear ();
+}
+
+void
+GuiFeedback::parse_finish ()
+{
+  Progress.SetText2 ("");
+  Progress.SetText3 ("");
+  Progress.SetText4 (IDS_PROGRESS_PACKAGE);
+  Progress.SetBar1 (0);
+}
+
+void
+GuiFeedback::progress (unsigned long const pos, unsigned long const max)
+{
+  if (!max)
+    /* length not known or eof */
+    return;
+  if (lastpct == 100)
+    /* rounding down should mean this only ever fires once */
+    lastpct = 0;
+  if (pos * 100 / max > lastpct)
+    {
+      lastpct = pos * 100 / max;
+      /* Log (LOG_BABBLE) << lastpct << "% (" << pos << " of " << max
+         << " bytes of ini file read)" << endLog; */
+    }
+  Progress.SetBar1 (pos, max);
+
+  static char buf[100];
+  sprintf (buf, "%d %%  (%ldk/%ldk)", lastpct, pos/1000, max/1000);
+  Progress.SetText3 (buf);
+}
+
+void
+GuiFeedback::iniName (const std::string& name)
+{
+  Progress.SetText2 (name.c_str ());
+  Progress.SetText3 ("");
+  filename = name;
+}
+
+void
+GuiFeedback::babble (const std::string& message)const
+{
+  Log (LOG_BABBLE) << message << endLog;
+}
+
+void
+GuiFeedback::warning (const std::string& message)const
+{
+  mbox (Progress.GetHWND(), message.c_str (), "Warning", 0);
+}
+
+void
+GuiFeedback::note_error(int lineno, const std::string &error)
+{
+  char tmp[16];
+  sprintf (tmp, "%d", lineno);
+
+  std::string e = filename + " line " + tmp + ": " + error;
+
+  if (!yyerror_messages.empty ())
+    yyerror_messages += "\n";
+
+  yyerror_messages += e;
+  yyerror_count++;
+}
+
+bool
+GuiFeedback::has_errors () const
+{
+  return (yyerror_count > 0);
+}
+
+void
+GuiFeedback::show_errors () const
+{
+  mbox (Progress.GetHWND(), yyerror_messages.c_str (), "Parse Errors", 0);
+}
+
+static DWORD WINAPI
+do_ini_thread_reflector (void* p)
+{
+  HANDLE *context;
+  context = (HANDLE*)p;
+
+  SetThreadUILanguage(langid);
+
+  try
+  {
+    GuiFeedback feedback((HWND)context[1]);
+    bool succeeded = do_ini_thread(feedback);
+
+    // Tell the progress page that we're done downloading
+    Progress.PostMessageNow (WM_APP_SETUP_INI_DOWNLOAD_COMPLETE, 0, succeeded);
+  }
+  TOPLEVEL_CATCH ((HWND) context[1], "ini");
+
+  ExitThread (0);
+}
+
+static HANDLE context[2];
+
+void
+do_ini (HINSTANCE h, HWND owner)
+{
+  context[0] = h;
+  context[1] = owner;
+
+  DWORD threadID;
+  CreateThread (NULL, 0, do_ini_thread_reflector, context, 0, &threadID);
+}
diff --git a/site.cc b/gui/SitePage.cc
similarity index 76%
rename from site.cc
rename to gui/SitePage.cc
index aa605617..725d42b6 100644
--- a/site.cc
+++ b/gui/SitePage.cc
@@ -19,7 +19,6 @@
 #include <string>
 #include <algorithm>
 
-#include "site.h"
 #include "win32.h"
 #include <stdio.h>
 #include <stdlib.h>
@@ -32,7 +31,7 @@
 #include "msg.h"
 #include "LogSingleton.h"
 #include "io_stream.h"
-#include "site.h"
+#include "gui/SitePage.h"
 
 #include "propsheet.h"
 
@@ -40,6 +39,7 @@
 #include "ControlAdjuster.h"
 #include "Exception.h"
 #include "String++.h"
+#include "gui/GuiFeedback.h"
 
 #define MIRROR_LIST_URL "https://cygwin.com/mirrors.lst";
 
@@ -71,153 +71,23 @@ SitePage::SitePage ()
   sizeProcessor.AddControlInfo (SiteControlsInfo);
 }
 
-#include "getopt++/StringArrayOption.h"
 #include "getopt++/BoolOption.h"
 #include "UserSettings.h"
+#include "SiteSetting.h"
 
 bool cache_is_usable;
 bool cache_needs_writing;
 std::string cache_warn_urls;
 
-/* Selected sites */
-SiteList site_list;
-
-/* Fresh mirrors + selected sites */
-SiteList all_site_list;
-
 /* Previously fresh + cached before */
 SiteList cached_site_list;
 
 /* Stale selected sites to warn about and add to cache */
 SiteList dropped_site_list;
 
-StringArrayOption SiteOption('s', "site", IDS_HELPTEXT_SITE);
 BoolOption OnlySiteOption(false, 'O', "only-site", IDS_HELPTEXT_ONLY_SITE);
 extern BoolOption UnsupportedOption;
 
-SiteSetting::SiteSetting (): saved (false)
-{
-  std::vector<std::string> SiteOptionStrings = SiteOption;
-  if (SiteOptionStrings.size())
-    {
-      for (std::vector<std::string>::const_iterator n = 
SiteOptionStrings.begin ();
-          n != SiteOptionStrings.end (); ++n)
-       registerSavedSite (n->c_str ());
-    }
-  else
-    getSavedSites ();
-}
-
-const char *
-SiteSetting::lastMirrorKey ()
-{
-  if (UnsupportedOption)
-    return "last-mirror-unsupported";
-
-  return "last-mirror";
-}
-
-void 
-SiteSetting::save()
-{
-  io_stream *f = UserSettings::instance().open (lastMirrorKey ());
-  if (f)
-    {
-      for (SiteList::const_iterator n = site_list.begin ();
-          n != site_list.end (); ++n)
-       *f << n->url;
-      delete f;
-    }
-  saved = true;
-}
-
-SiteSetting::~SiteSetting ()
-{
-  if (!saved)
-    save ();
-}
-
-site_list_type::site_list_type (const std::string &_url,
-                               const std::string &_servername,
-                               const std::string &_area,
-                               const std::string &_location,
-                               bool _from_mirrors_lst,
-                                bool _noshow = false,
-                                const std::string &_redir = "")
-{
-  url = _url;
-  servername = _servername;
-  area = _area;
-  location = _location;
-  from_mirrors_lst = _from_mirrors_lst;
-  noshow = _noshow;
-  redir = _redir;
-
-  /* Canonicalize URL to ensure it ends with a '/' */
-  if (url.at(url.length()-1) != '/')
-    url.append("/");
-
-  /* displayed_url is protocol and site name part of url */
-  std::string::size_type path_offset = url.find ("/", url.find ("//") + 2);
-  displayed_url = url.substr(0, path_offset);
-
-  /* the sorting key is hostname components in reverse order (to sort by 
country code)
-     plus the url (to ensure uniqueness) */
-  key = std::string();
-  std::string::size_type last_idx = displayed_url.length () - 1;
-  std::string::size_type idx = url.find_last_of("./", last_idx);
-  if (last_idx - idx == 3)
-  {
-    /* Sort non-country TLDs (.com, .net, ...) together. */
-    key += " ";
-  }
-  do
-  {
-    key += url.substr(idx + 1, last_idx - idx);
-    key += " ";
-    last_idx = idx - 1;
-    idx = url.find_last_of("./", last_idx);
-    if (idx == std::string::npos)
-      idx = 0;
-  } while (idx > 0);
-  key += url;
-}
-
-bool
-site_list_type::operator == (site_list_type const &rhs) const
-{
-  return stricmp (key.c_str(), rhs.key.c_str()) == 0; 
-}
-
-bool
-site_list_type::operator < (site_list_type const &rhs) const
-{
-  return stricmp (key.c_str(), rhs.key.c_str()) < 0; 
-}
-
-/*
-  A SiteList is maintained as an in-order std::vector of site_list_type, by
-  replacing it with a new object with the new item inserted in the correct
-  place.
-
-  Yes, we could just use an ordered container, instead.
-*/
-static void
-site_list_insert(SiteList &site_list, site_list_type newsite)
-{
-  SiteList::iterator i = find (site_list.begin(), site_list.end(), newsite);
-  if (i == site_list.end())
-    {
-      SiteList result;
-      merge (site_list.begin(), site_list.end(),
-             &newsite, &newsite + 1,
-             inserter (result, result.begin()));
-      site_list = result;
-    }
-  else
-    *i = newsite;
-}
-
 static void
 save_dialog (HWND h)
 {
@@ -363,7 +233,7 @@ migrate_selected_site_list()
 }
 
 static int
-get_site_list (HINSTANCE h, HWND owner)
+get_site_list (Feedback &feedback)
 {
   char *theMirrorString, *theCachedString;
 
@@ -383,7 +253,7 @@ get_site_list (HINSTANCE h, HWND owner)
       cached_mirrors = "";
     }
 
-  std::string mirrors = OnlySiteOption ? std::string ("") : get_url_to_string 
(MIRROR_LIST_URL, owner);
+  std::string mirrors = OnlySiteOption ? std::string ("") : get_url_to_string 
(MIRROR_LIST_URL, feedback);
   if (mirrors.size())
     cache_needs_writing = true;
   else
@@ -391,7 +261,7 @@ get_site_list (HINSTANCE h, HWND owner)
       if (!cached_mirrors[0])
         {
           if (!OnlySiteOption)
-            note(owner, IDS_NO_MIRROR_LST);
+            note(feedback.owner(), IDS_NO_MIRROR_LST);
           Log (LOG_BABBLE) << "Defaulting to empty mirror list" << endLog;
         }
       else
@@ -416,68 +286,29 @@ get_site_list (HINSTANCE h, HWND owner)
   return 0;
 }
 
-/* List of machines that should not be used by default when saved
-   in "last-mirror". */
-#define NOSAVE1 "ftp://sourceware.org/";
-#define NOSAVE1_LEN (sizeof (NOSAVE2) - 1)
-#define NOSAVE2 "ftp://sources.redhat.com/";
-#define NOSAVE2_LEN (sizeof (NOSAVE1) - 1)
-#define NOSAVE3 "ftp://gcc.gnu.org/";
-#define NOSAVE3_LEN (sizeof (NOSAVE3) - 1)
-
-void
-SiteSetting::registerSavedSite (const char * site)
-{
-  site_list_type tempSite(site, "", "", "", false);
-
-  /* Don't default to certain machines if they suffer from bandwidth
-     limitations. */
-  if (strnicmp (site, NOSAVE1, NOSAVE1_LEN) == 0
-      || strnicmp (site, NOSAVE2, NOSAVE2_LEN) == 0
-      || strnicmp (site, NOSAVE3, NOSAVE3_LEN) == 0)
-    return;
-
-  site_list_insert (all_site_list, tempSite);
-  site_list.push_back (tempSite);
-}
-
-void
-SiteSetting::getSavedSites ()
-{
-  const char *buf = UserSettings::instance().get (lastMirrorKey ());
-  if (!buf)
-    return;
-  char *fg_ret = strdup (buf);
-  for (char *site = strtok (fg_ret, "\n"); site; site = strtok (NULL, "\n"))
-    registerSavedSite (site);
-  free (fg_ret);
-}
-
 static DWORD WINAPI
 do_download_site_info_thread (void *p)
 {
   HANDLE *context;
-  HINSTANCE hinst;
-  HWND h;
   context = (HANDLE *) p;
 
   SetThreadUILanguage(langid);
 
   try
   {
-    hinst = (HINSTANCE) (context[0]);
-    h = (HWND) (context[1]);
+    GuiFeedback feedback((HWND)(context[1]));
+
     static bool downloaded = false;
-    if (!downloaded && get_site_list (hinst, h))
+    if (!downloaded && get_site_list(feedback))
     {
       // Error: Couldn't download the site info.
       // Go back to the Net setup page.
-      mbox (h, IDS_GET_SITELIST_ERROR, MB_OK);
+      mbox (feedback.owner(), IDS_GET_SITELIST_ERROR, MB_OK);
 
       // Tell the progress page that we're done downloading
       Progress.PostMessageNow (WM_APP_SITE_INFO_DOWNLOAD_COMPLETE, 0, IDD_NET);
     }
-    else 
+    else
     {
       downloaded = true;
       // Everything worked, go to the site select page
@@ -568,8 +399,7 @@ int check_dropped_mirrors (HWND h)
     {
       if (unattended_mode)
        return CACHE_ACCEPT_WARN;
-      return DialogBox (hinstance, MAKEINTRESOURCE (IDD_DROPPED), h,
-                       drop_proc);
+      return DialogBox (NULL, MAKEINTRESOURCE (IDD_DROPPED), h, drop_proc);
     }
   return CACHE_ACCEPT_NOWARN;
 }
diff --git a/gui/SitePage.h b/gui/SitePage.h
new file mode 100644
index 00000000..1208338a
--- /dev/null
+++ b/gui/SitePage.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2001, Robert Collins.
+ *
+ *     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.
+ *
+ *     A copy of the GNU General Public License can be found at
+ *     http://www.gnu.org/
+ *
+ * Written by Robert Collins <[email protected]>
+ *
+ */
+
+#ifndef SETUP_SITE_H
+#define SETUP_SITE_H
+
+#include "proppage.h"
+
+class SitePage : public PropertyPage
+{
+public:
+  SitePage ();
+  virtual ~ SitePage ()
+  {
+  };
+
+  bool Create ();
+
+  virtual void OnInit ();
+  virtual void OnActivate ();
+  virtual long OnNext ();
+  virtual long OnBack ();
+  virtual long OnUnattended ();
+
+  virtual bool OnMessageCmd (int id, HWND hwndctl, UINT code);
+
+  void PopulateListBox();
+  void CheckControlsAndDisableAccordingly () const;
+};
+
+void do_download_site_info (HINSTANCE h, HWND owner);
+
+#endif /* SETUP_SITE_H */
diff --git a/ini.cc b/ini.cc
index d919d450..006d710e 100644
--- a/ini.cc
+++ b/ini.cc
@@ -33,32 +33,28 @@
 #include <process.h>
 
 #include "resource.h"
-#include "state.h"
 #include "geturl.h"
 #include "dialog.h"
 #include "mount.h"
-#include "site.h"
+#include "SiteSetting.h"
 #include "find.h"
-#include "IniParseFeedback.h"
+#include "Feedback.h"
 
 #include "io_stream.h"
 #include "io_stream_memory.h"
 
-#include "threebar.h"
-
 #include "getopt++/BoolOption.h"
 #include "getopt++/StringOption.h"
 #include "IniDBBuilderPackage.h"
 #include "compress.h"
-#include "Exception.h"
+#include "msg.h"
 #include "crypto.h"
 #include "package_db.h"
 
-extern ThreeBarProgressPage Progress;
-
 unsigned int setup_timestamp = 0;
 std::string ini_setup_version;
 // TODO: use C++11x initializer lists instead and drop the literal array
+static const std::string setup_exts[] = { "zst", "xz", "bz2", "ini" };
 IniList setup_ext_list (setup_exts,
                        setup_exts + (sizeof(setup_exts) / 
sizeof(*setup_exts)));
 
@@ -66,87 +62,6 @@ static StringOption SetupBaseNameOption ("setup", 'i', 
"ini-basename", IDS_HELPT
 static BoolOption NoVerifyOption (false, 'X', "no-verify", 
IDS_HELPTEXT_NO_VERIFY);
 static BoolOption NoVersionCheckOption (false, '\0', "no-version-check", 
IDS_HELPTEXT_NO_VERSION_CHECK);
 
-class GuiParseFeedback : public IniParseFeedback
-{
-public:
-  GuiParseFeedback () : lastpct (0)
-    {
-      Progress.SetText1 (IDS_PROGRESS_PARSING);
-      Progress.SetText2 ("");
-      Progress.SetText3 ("");
-      Progress.SetText4 (IDS_PROGRESS_PROGRESS);
-
-      yyerror_count = 0;
-      yyerror_messages.clear ();
-    }
-  virtual void progress (unsigned long const pos, unsigned long const max)
-    {
-      if (!max)
-       /* length not known or eof */
-       return;
-      if (lastpct == 100)
-       /* rounding down should mean this only ever fires once */
-       lastpct = 0;
-      if (pos * 100 / max > lastpct)
-       {
-         lastpct = pos * 100 / max;
-         /* Log (LOG_BABBLE) << lastpct << "% (" << pos << " of " << max
-           << " bytes of ini file read)" << endLog; */
-       }
-      Progress.SetBar1 (pos, max);
-
-      static char buf[100];
-      sprintf (buf, "%d %%  (%ldk/%ldk)", lastpct, pos/1000, max/1000);
-      Progress.SetText3 (buf);
-    }
-  virtual void iniName (const std::string& name)
-    {
-      Progress.SetText2 (name.c_str ());
-      Progress.SetText3 ("");
-      filename = name;
-    }
-  virtual void babble (const std::string& message)const
-    {
-      Log (LOG_BABBLE) << message << endLog;
-    }
-  virtual void warning (const std::string& message)const
-    {
-      mbox (Progress.GetHWND(), message.c_str (), "Warning", 0);
-    }
-  virtual void note_error(int lineno, const std::string &error)
-    {
-      char tmp[16];
-      sprintf (tmp, "%d", lineno);
-
-      std::string e = filename + " line " + tmp + ": " + error;
-
-      if (!yyerror_messages.empty ())
-        yyerror_messages += "\n";
-
-      yyerror_messages += e;
-      yyerror_count++;
-    }
-  virtual bool has_errors () const
-    {
-      return (yyerror_count > 0);
-    }
-  virtual void show_errors () const
-    {
-      mbox (Progress.GetHWND(), yyerror_messages.c_str (), "Parse Errors", 0);
-    }
-  virtual ~ GuiParseFeedback ()
-    {
-      Progress.SetText2 ("");
-      Progress.SetText3 ("");
-      Progress.SetText4 (IDS_PROGRESS_PACKAGE);
-      Progress.SetBar1 (0);
-    }
-private:
-  unsigned int lastpct;
-  std::string filename;
-  std::string yyerror_messages;
-  int yyerror_count;
-};
 
 std::string SetupArch()
 {
@@ -208,7 +123,7 @@ decompress_ini (io_stream *ini_file, std::string 
&current_ini_name)
 
 static io_stream*
 check_ini_sig (io_stream* ini_file, io_stream* ini_sig_file,
-              bool& sig_fail, const char* site, const char* sig_name, HWND 
owner)
+              bool& sig_fail, const char* site, const char* sig_name, Feedback 
&feedback)
 {
   /* Unless the NoVerifyOption is set, check the signature for the
      current setup and record the result.  On a failed signature check
@@ -222,15 +137,15 @@ check_ini_sig (io_stream* ini_file, io_stream* 
ini_sig_file,
        // TODO: download the ini + signature file instead
        if (casecompare (site, "localdir"))
          {
-           note (owner, IDS_SETUPINI_MISSING, sig_name, site);
+           note (feedback.owner(), IDS_SETUPINI_MISSING, sig_name, site);
            delete ini_file;
            ini_file = NULL;
            sig_fail = true;
          }
       }
-      else if (!verify_ini_file_sig (ini_file, ini_sig_file, owner))
+      else if (!verify_ini_file_sig (ini_file, ini_sig_file, feedback))
        {
-         note (owner, IDS_SIG_INVALID, sig_name, site);
+         note (feedback.owner(), IDS_SIG_INVALID, sig_name, site);
          delete ini_sig_file;
          ini_sig_file = NULL;
          delete ini_file;
@@ -242,7 +157,7 @@ check_ini_sig (io_stream* ini_file, io_stream* ini_sig_file,
 }
 
 static bool
-do_local_ini (HWND owner)
+do_local_ini (Feedback &myFeedback)
 {
   bool ini_error = false;
   io_stream *ini_file, *ini_sig_file;
@@ -250,7 +165,6 @@ do_local_ini (HWND owner)
   for (IniList::const_iterator n = found_ini_list.begin ();
        n != found_ini_list.end (); ++n)
     {
-      GuiParseFeedback myFeedback;
       IniDBBuilderPackage aBuilder (myFeedback);
       bool sig_fail = false;
       std::string current_ini_ext, current_ini_name, current_ini_sig_name;
@@ -261,13 +175,13 @@ do_local_ini (HWND owner)
       ini_sig_file = io_stream::open ("file://" + current_ini_sig_name, "rb", 
0);
       ini_file = io_stream::open ("file://" + current_ini_name, "rb", 0);
       ini_file = check_ini_sig (ini_file, ini_sig_file, sig_fail,
-                               "localdir", current_ini_sig_name.c_str (), 
owner);
+                               "localdir", current_ini_sig_name.c_str (), 
myFeedback);
       if (ini_file)
        ini_file = decompress_ini (ini_file, current_ini_name);
       if (!ini_file || sig_fail)
        {
          // no setup found or signature invalid
-         note (owner, IDS_SETUPINI_MISSING, SetupBaseName().c_str (),
+         note (myFeedback.owner(), IDS_SETUPINI_MISSING, SetupBaseName().c_str 
(),
                "localdir");
          ini_error = true;
        }
@@ -301,7 +215,7 @@ do_local_ini (HWND owner)
 }
 
 static bool
-do_remote_ini (HWND owner)
+do_remote_ini (Feedback &myFeedback)
 {
   bool ini_error = false;
   io_stream *ini_file = NULL, *ini_sig_file;
@@ -313,7 +227,6 @@ do_remote_ini (HWND owner)
   for (SiteList::const_iterator n = site_list.begin ();
        n != site_list.end (); ++n)
     {
-      GuiParseFeedback myFeedback;
       IniDBBuilderPackage aBuilder (myFeedback);
       bool sig_fail = false;
       std::string current_ini_ext, current_ini_name, current_ini_sig_name;
@@ -325,10 +238,10 @@ do_remote_ini (HWND owner)
          current_ini_ext = *ext;
          current_ini_name = n->url + SetupArch() + "/" + SetupBaseName() + "." 
+ current_ini_ext;
          current_ini_sig_name = current_ini_name + ".sig";
-         ini_sig_file = get_url_to_membuf (current_ini_sig_name, owner);
-         ini_file = get_url_to_membuf (current_ini_name, owner);
+         ini_sig_file = get_url_to_membuf (current_ini_sig_name, myFeedback);
+         ini_file = get_url_to_membuf (current_ini_name, myFeedback);
          ini_file = check_ini_sig (ini_file, ini_sig_file, sig_fail,
-                                   n->url.c_str (), current_ini_sig_name.c_str 
(), owner);
+                                   n->url.c_str (), current_ini_sig_name.c_str 
(), myFeedback);
          // stop searching as soon as we find a setup file
          if (ini_file)
            break;
@@ -338,7 +251,7 @@ do_remote_ini (HWND owner)
       if (!ini_file || sig_fail)
        {
          // no setup found or signature invalid
-         note (owner, IDS_SETUPINI_MISSING, SetupBaseName().c_str (), 
n->url.c_str ());
+         note (myFeedback.owner(), IDS_SETUPINI_MISSING, SetupBaseName().c_str 
(), n->url.c_str ());
          ini_error = true;
        }
       else
@@ -380,8 +293,8 @@ do_remote_ini (HWND owner)
   return ini_error;
 }
 
-static bool
-do_ini_thread (HINSTANCE h, HWND owner)
+bool
+do_ini_thread (Feedback &feedback)
 {
   packagedb db;
   db.init();
@@ -389,9 +302,9 @@ do_ini_thread (HINSTANCE h, HWND owner)
   bool ini_error = true;
 
   if (source == IDC_SOURCE_LOCALDIR)
-    ini_error = do_local_ini (owner);
+    ini_error = do_local_ini (feedback);
   else
-    ini_error = do_remote_ini (owner);
+    ini_error = do_remote_ini (feedback);
 
   if (ini_error)
     return false;
@@ -413,7 +326,7 @@ do_ini_thread (HINSTANCE h, HWND owner)
          if (old_timestamp && setup_timestamp
              && (old_timestamp > setup_timestamp))
            {
-             int yn = yesno (owner, IDS_OLD_SETUPINI);
+             int yn = yesno (feedback.owner(), IDS_OLD_SETUPINI);
              if (yn == IDNO)
                Logger ().exit (1);
            }
@@ -439,41 +352,9 @@ do_ini_thread (HINSTANCE h, HWND owner)
     {
       if ((version_compare (setup_version, ini_setup_version) < 0)
           && !NoVersionCheckOption)
-       note (owner, IDS_OLD_SETUP_VERSION, setup_version,
+       note (feedback.owner(), IDS_OLD_SETUP_VERSION, setup_version,
              ini_setup_version.c_str ());
     }
 
   return true;
 }
-
-static DWORD WINAPI
-do_ini_thread_reflector (void* p)
-{
-  HANDLE *context;
-  context = (HANDLE*)p;
-
-  SetThreadUILanguage(langid);
-
-  try
-  {
-    bool succeeded = do_ini_thread ((HINSTANCE)context[0], (HWND)context[1]);
-
-    // Tell the progress page that we're done downloading
-    Progress.PostMessageNow (WM_APP_SETUP_INI_DOWNLOAD_COMPLETE, 0, succeeded);
-  }
-  TOPLEVEL_CATCH ((HWND) context[1], "ini");
-
-  ExitThread (0);
-}
-
-static HANDLE context[2];
-
-void
-do_ini (HINSTANCE h, HWND owner)
-{
-  context[0] = h;
-  context[1] = owner;
-
-  DWORD threadID;
-  CreateThread (NULL, 0, do_ini_thread_reflector, context, 0, &threadID);
-}
diff --git a/ini.h b/ini.h
index e318a8ab..05b31e03 100644
--- a/ini.h
+++ b/ini.h
@@ -16,21 +16,24 @@
 #ifndef SETUP_INI_H
 #define SETUP_INI_H
 
-class io_stream;
+#include "win32.h"
 #include <string>
 #include <vector>
 
 typedef std::vector <std::string> IniList;
 extern IniList found_ini_list, setup_ext_list;
-const std::string setup_exts[] = { "zst", "xz", "bz2", "ini" };
+
 extern bool is_new_install;
 std::string SetupArch();
 std::string SetupBaseName();
 
-class IniState;
+class io_stream;
 class IniDBBuilder;
-class IniParseFeedback;
-void ini_init (io_stream *, IniDBBuilder *, IniParseFeedback &);
+class Feedback;
+
+void ini_init (io_stream *, IniDBBuilder *, Feedback &);
+bool do_ini_thread (Feedback &feedback);
+
 #define YYSTYPE char *
 
 /* When setup.ini is parsed, the information is stored according to
diff --git a/inilex.ll b/inilex.ll
index e25abf26..8ee92935 100644
--- a/inilex.ll
+++ b/inilex.ll
@@ -25,7 +25,7 @@
 #include "ini.h"
 #include "iniparse.hh"
 #include "String++.h"
-#include "IniParseFeedback.h"
+#include "Feedback.h"
 #include "sha2.h"
 
 #define YY_READ_BUF_SIZE 65536
@@ -167,10 +167,10 @@ B64       [a-zA-Z0-9_-]
 
 static io_stream *input_stream = 0;
 extern IniDBBuilder *iniBuilder;
-static IniParseFeedback *iniFeedback;
+static Feedback *iniFeedback;
 
 void
-ini_init(io_stream *stream, IniDBBuilder *aBuilder, IniParseFeedback 
&aFeedback)
+ini_init(io_stream *stream, IniDBBuilder *aBuilder, Feedback &aFeedback)
 {
   input_stream = stream;
   iniBuilder = aBuilder;
diff --git a/inilintmain.cc b/inilintmain.cc
index 886c152f..9b3b0b35 100644
--- a/inilintmain.cc
+++ b/inilintmain.cc
@@ -15,7 +15,7 @@
 
 #include "io_stream.h"
 #include "IniDBBuilderLint.h"
-#include "CliParseFeedback.h"
+#include "cli/CliFeedback.h"
 #include "ini.h"
 #include <iostream>
 #include <sstream>
@@ -46,7 +46,7 @@ main (int argc, char **argv)
       return 1;
     }
 
-  CliParseFeedback feedback;
+  CliFeedback feedback;
   IniDBBuilderLint builder;
   ini_init(ini_file, &builder, feedback);
 
@@ -63,3 +63,9 @@ get_root_dir ()
   static std::string empty;
   return empty;
 }
+
+void
+fatal (HWND owner, int id, ...)
+{
+  exit(1);
+}
diff --git a/install.cc b/install.cc
index 628dbd0c..578ff7ff 100644
--- a/install.cc
+++ b/install.cc
@@ -62,6 +62,7 @@
 #include "threebar.h"
 #include "Exception.h"
 #include "processlist.h"
+#include "gui/GuiFeedback.h"
 
 extern ThreeBarProgressPage Progress;
 
@@ -660,7 +661,7 @@ Installer::_installOne (packagemeta &pkgm,
                             dlg_data.processlist = plm.c_str ();
                             dlg_data.iteration = iteration;
 
-                            rc = DialogBoxParam(hinstance, MAKEINTRESOURCE 
(IDD_FILE_INUSE), owner, FileInuseDlgProc, (LPARAM)&dlg_data);
+                            rc = DialogBoxParam(NULL, MAKEINTRESOURCE 
(IDD_FILE_INUSE), owner, FileInuseDlgProc, (LPARAM)&dlg_data);
                           }
                         else
                           {
@@ -805,6 +806,7 @@ static void
 do_install_thread (HINSTANCE h, HWND owner)
 {
   int i;
+  GuiFeedback feedback(owner);
 
   num_installs = 0, num_uninstalls = 0;
   rebootneeded = false;
@@ -870,7 +872,7 @@ do_install_thread (HINSTANCE h, HWND owner)
     {
       try
       {
-        (*version.source ()).check_hash ();
+        (*version.source ()).check_hash (feedback);
       }
       catch (Exception *e)
       {
diff --git a/main.cc b/main.cc
index 91d0eca0..bedf3ec0 100644
--- a/main.cc
+++ b/main.cc
@@ -53,7 +53,7 @@
 #include "root.h"
 #include "localdir.h"
 #include "net.h"
-#include "site.h"
+#include "gui/SitePage.h"
 #include "choose.h"
 #include "prereq.h"
 #include "confirm.h"
@@ -74,6 +74,7 @@
 #include "SourceSetting.h"
 #include "ConnectionSetting.h"
 #include "KeysSetting.h"
+#include "SiteSetting.h"
 
 #include <wincon.h>
 #include <fstream>
@@ -85,8 +86,6 @@ extern char **_argv;
 bool is_new_install = false;
 std::string SetupIniDir;
 
-HINSTANCE hinstance;
-
 static StringChoiceOption::StringChoices symlink_types({
     {"native", SymlinkTypeNative},
     {"lnk", SymlinkTypeShortcut},
@@ -185,7 +184,7 @@ main_display ()
     }
 
   // Init window class lib
-  Window::SetAppInstance (hinstance);
+  Window::SetAppInstance (GetModuleHandle(NULL));
 
   // Create pages
   Splash.Create ();
@@ -230,7 +229,6 @@ int WINAPI
 WinMain (HINSTANCE h,
         HINSTANCE hPrevInstance, LPSTR command_line, int cmd_show)
 {
-  hinstance = h;
 
   // Make sure Windows DLLs only delay-load further DLLs from System32
   typedef BOOL (WINAPI *PFNSETDEFAULTDLLDIRECTORIES)(DWORD);
diff --git a/msg.cc b/msg.cc
index 8e344ff8..b53df860 100644
--- a/msg.cc
+++ b/msg.cc
@@ -23,7 +23,6 @@
 
 #include <stdio.h>
 #include <stdarg.h>
-#include "dialog.h"
 #include "state.h"
 #include "String++.h"
 #include "resource.h"
@@ -66,7 +65,7 @@ mbox (HWND owner, const char *buf, const char *name, int type)
     }
 
   char caption[32];
-  LoadString (hinstance, IDS_MBOX_CAPTION, caption, sizeof (caption));
+  LoadString (GetModuleHandle(NULL), IDS_MBOX_CAPTION, caption, sizeof 
(caption));
 
   return MessageBox (owner, buf, caption, type);
 }
@@ -76,7 +75,7 @@ mbox (HWND owner, const char *name, int type, int id, va_list 
args)
 {
   char buf[1000], fmt[1000];
 
-  if (LoadString (hinstance, id, fmt, sizeof (fmt)) <= 0)
+  if (LoadString (GetModuleHandle(NULL), id, fmt, sizeof (fmt)) <= 0)
     ExitProcess (0);
 
   vsnprintf (buf, 1000, fmt, args);
diff --git a/net.cc b/net.cc
index f460efc8..ee84e64d 100644
--- a/net.cc
+++ b/net.cc
@@ -33,6 +33,8 @@
 #include "propsheet.h"
 #include "threebar.h"
 #include "ConnectionSetting.h"
+#include "gui/GuiGetNetAuth.h"
+
 extern ThreeBarProgressPage Progress;
 
 static StringOption ProxyOption ("", 'p', "proxy", IDS_HELPTEXT_PROXY, false);
@@ -113,6 +115,9 @@ NetPage::OnInit ()
   if (!NetIO::net_method)
     NetIO::net_method = IDC_NET_PRECONFIG;
 
+  // install the interactive network auth getter
+  NetIO::auth_getter = new GuiGetNetAuth(GetHWND());
+
   if (proxyString.size ())
   {
     unsigned int pos = proxyString.find_last_of (':');
diff --git a/netio.cc b/netio.cc
index 631532a3..7d32f9f8 100644
--- a/netio.cc
+++ b/netio.cc
@@ -21,17 +21,10 @@
 
 #include "LogFile.h"
 
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
 #include <shlwapi.h>
 
 #include "resource.h"
-#include "state.h"
-#include "msg.h"
 #include "nio-ie5.h"
-#include "dialog.h"
 
 int NetIO::net_method;
 char *NetIO::net_proxy_host;
@@ -43,6 +36,7 @@ char *NetIO::net_proxy_user;
 char *NetIO::net_proxy_passwd;
 char *NetIO::net_ftp_user;
 char *NetIO::net_ftp_passwd;
+GetNetAuth *NetIO::auth_getter;
 
 int
 NetIO::ok ()
@@ -107,123 +101,6 @@ NetIO::open (char const *url, bool cachable)
   return rv;
 }
 
-
-static char **user, **passwd;
-static int loading = 0;
-
-static void
-check_if_enable_ok (HWND h)
-{
-  int e = 0;
-  if (*user)
-    e = 1;
-  EnableWindow (GetDlgItem (h, IDOK), e);
-}
-
-static void
-load_dialog (HWND h)
-{
-  loading = 1;
-  eset (h, IDC_NET_USER, *user);
-  eset (h, IDC_NET_PASSWD, *passwd);
-  check_if_enable_ok (h);
-  loading = 0;
-}
-
-static void
-save_dialog (HWND h)
-{
-  *user = eget (h, IDC_NET_USER, *user);
-  *passwd = eget (h, IDC_NET_PASSWD, *passwd);
-  if (! *passwd) {
-    *passwd = new char[1];
-    (*passwd)[0] = '\0';
-  }
-}
-
-static BOOL
-auth_cmd (HWND h, int id, HWND hwndctl, UINT code)
-{
-  switch (id)
-    {
-
-    case IDC_NET_USER:
-    case IDC_NET_PASSWD:
-      if (code == EN_CHANGE && !loading)
-       {
-         save_dialog (h);
-         check_if_enable_ok (h);
-       }
-      break;
-
-    case IDOK:
-      save_dialog (h);
-      EndDialog (h, 0);
-      break;
-
-    case IDCANCEL:
-      EndDialog (h, 1);
-      Logger ().exit (1);
-      break;
-    }
-  return 0;
-}
-
-static INT_PTR CALLBACK
-auth_proc (HWND h, UINT message, WPARAM wParam, LPARAM lParam)
-{
-  switch (message)
-    {
-    case WM_INITDIALOG:
-      load_dialog (h);
-      return FALSE;
-    case WM_COMMAND:
-      auth_cmd (h, LOWORD(wParam), (HWND)lParam, HIWORD(wParam));
-      return 0;
-    }
-  return FALSE;
-}
-
-static int
-auth_common (HINSTANCE h, int id, HWND owner)
-{
-  return DialogBox (h, MAKEINTRESOURCE (id), owner, auth_proc);
-}
-
-int
-NetIO::get_auth (HWND owner)
-{
-  user = &net_user;
-  passwd = &net_passwd;
-  return auth_common (hinstance, IDD_NET_AUTH, owner);
-}
-
-int
-NetIO::get_proxy_auth (HWND owner)
-{
-  user = &net_proxy_user;
-  passwd = &net_proxy_passwd;
-  return auth_common (hinstance, IDD_PROXY_AUTH, owner);
-}
-
-int
-NetIO::get_ftp_auth (HWND owner)
-{
-  if (net_ftp_user)
-    {
-      delete[] net_ftp_user;
-      net_ftp_user = NULL;
-    }
-  if (net_ftp_passwd)
-    {
-      delete[] net_ftp_passwd;
-      net_ftp_passwd = NULL;
-    }
-  user = &net_ftp_user;
-  passwd = &net_ftp_passwd;
-  return auth_common (hinstance, IDD_FTP_AUTH, owner);
-}
-
 const char *
 NetIO::net_method_name ()
 {
diff --git a/netio.h b/netio.h
index f2ecfdd3..98d435b5 100644
--- a/netio.h
+++ b/netio.h
@@ -16,23 +16,22 @@
 #ifndef SETUP_NETIO_H
 #define SETUP_NETIO_H
 
-#include "win32.h"
+#include "GetNetAuth.h"
 
 /* This is the parent class for all the access methods known to setup
    (i.e. ways to download files from the internet or other sources */
 
 class NetIO
 {
-protected:
+public:
   static char *net_user;
   static char *net_passwd;
   static char *net_proxy_user;
   static char *net_proxy_passwd;
   static char *net_ftp_user;
   static char *net_ftp_passwd;
+  static GetNetAuth *auth_getter;
 
-
-public:
   /* if nonzero, this is the estimated total file size */
   int file_size;
 
@@ -40,9 +39,8 @@ public:
   virtual ~ NetIO () {};
 
   /* The user calls this function to create a suitable accessor for
-     the given URL.  It uses the network setup state in state.h.  If
-     anything fails, either the return values is NULL or the returned
-     object is !ok() */
+     the given URL. If anything fails, either the return values is NULL
+     or the returned object is !ok() */
   static NetIO *open (char const *url, bool cachable);
 
   /* If !ok() that means the transfer isn't happening. */
@@ -57,13 +55,6 @@ public:
   static int net_proxy_port;
 
   static const char *net_method_name();
-
-  /* Helper functions for http/ftp protocols.  Both return nonzero for
-     "cancel", zero for "ok".  They set net_proxy_user, etc, in
-     state.h */
-  int get_auth (HWND owner);
-  int get_proxy_auth (HWND owner);
-  int get_ftp_auth (HWND owner);
 };
 
 #endif /* SETUP_NETIO_H */
diff --git a/nio-ie5.cc b/nio-ie5.cc
index 68bb1a82..f76434bf 100644
--- a/nio-ie5.cc
+++ b/nio-ie5.cc
@@ -253,14 +253,14 @@ try_again:
          if (type == 401)      /* authorization required */
            {
              flush_io ();
-             get_auth (NULL);
+             auth_getter->get_auth ();
              resend = 1;
              goto try_again;
            }
          else if (type == 407) /* proxy authorization required */
            {
              flush_io ();
-             get_proxy_auth (NULL);
+             auth_getter->get_proxy_auth ();
              resend = 1;
              goto try_again;
            }
diff --git a/package_db.cc b/package_db.cc
index aac018f1..9b74fc99 100644
--- a/package_db.cc
+++ b/package_db.cc
@@ -755,7 +755,7 @@ packagedb::fixup_source_package_ids()
 }
 
 void
-packagedb::prep()
+packagedb::prep(Feedback &feedback)
 {
   /* make packagedb ready for use for chooser */
   if (prepped)
@@ -770,8 +770,8 @@ packagedb::prep()
 
   /* XXX: this needs to be broken out somewhere where it can do progress
      reporting, as it can take a long time... */
-  if (source == IDC_SOURCE_DOWNLOAD || source ==IDC_SOURCE_LOCALDIR)
-    packagemeta::ScanDownloadedFiles (MirrorOption);
+  if (source == IDC_SOURCE_DOWNLOAD || source == IDC_SOURCE_LOCALDIR)
+    packagemeta::ScanDownloadedFiles (MirrorOption, feedback);
 
   setExistence ();
   fillMissingCategory ();
diff --git a/package_db.h b/package_db.h
index 3886bf22..c3c2b051 100644
--- a/package_db.h
+++ b/package_db.h
@@ -23,6 +23,7 @@
 class packagemeta;
 class io_stream;
 class PackageSpecification;
+class Feedback;
 
 typedef enum {
   PackageDB_Install,
@@ -67,7 +68,7 @@ public:
   void init();
   /* 0 on success */
   int flush ();
-  void prep();
+  void prep(Feedback &);
   /* Set the database to a "no changes requested" state.  */
   void noChanges ();
 
diff --git a/package_meta.cc b/package_meta.cc
index 322c28f9..85e0a7f2 100644
--- a/package_meta.cc
+++ b/package_meta.cc
@@ -642,7 +642,7 @@ packagemeta::logSelectionStatus() const
 
 /* scan for local copies of package */
 bool
-packagemeta::scan (const packageversion &pkg, bool mirror_mode)
+packagemeta::scan (const packageversion &pkg, bool mirror_mode, Feedback 
&feedback)
 {
   /* empty version */
   if (!pkg)
@@ -650,7 +650,7 @@ packagemeta::scan (const packageversion &pkg, bool 
mirror_mode)
 
   try
     {
-      if (!check_for_cached (*(pkg.source ()), NULL, mirror_mode, false)
+      if (!check_for_cached (*(pkg.source ()), feedback, mirror_mode, false)
           && ::source == IDC_SOURCE_LOCALDIR)
         return false;
     }
@@ -668,7 +668,7 @@ packagemeta::scan (const packageversion &pkg, bool 
mirror_mode)
 }
 
 void
-packagemeta::ScanDownloadedFiles (bool mirror_mode)
+packagemeta::ScanDownloadedFiles (bool mirror_mode, Feedback &feedback)
 {
   /* Look at every known package, in all the known mirror dirs,
    * and fill in the Cached attribute if it exists.
@@ -686,10 +686,10 @@ packagemeta::ScanDownloadedFiles (bool mirror_mode)
                           && (*i != pkg.installed
                               || pkg.installed == pkg.curr
                               || pkg.installed == pkg.exp);
-         bool accessible = scan (*i, lazy_scan);
+         bool accessible = scan (*i, lazy_scan, feedback);
          packageversion foo = *i;
          packageversion pkgsrcver = foo.sourcePackage ();
-         bool src_accessible = scan (pkgsrcver, lazy_scan);
+         bool src_accessible = scan (pkgsrcver, lazy_scan, feedback);
 
          /* For local installs, if there is no src and no bin, the version
           * is unavailable
diff --git a/package_meta.h b/package_meta.h
index 11448475..a76342bf 100644
--- a/package_meta.h
+++ b/package_meta.h
@@ -19,6 +19,7 @@
 class SolvableVersion;
 typedef SolvableVersion packageversion;
 class packagemeta;
+class Feedback;
 
 #include <set>
 #include <vector>
@@ -34,7 +35,7 @@ typedef std::pair<const std::string, std::vector<packagemeta 
*> > Category;
 class packagemeta
 {
 public:
-  static void ScanDownloadedFiles (bool);
+  static void ScanDownloadedFiles (bool, Feedback&);
   packagemeta (packagemeta const &);
   packagemeta (const std::string& pkgname)
     : name (pkgname), user_picked (false),
@@ -160,7 +161,7 @@ protected:
 private:
   std::string trustLabel(packageversion const &) const;
   std::vector <Script> scripts_;
-  static bool scan (const packageversion &pkg, bool mirror_mode);
+  static bool scan (const packageversion &pkg, bool mirror_mode, Feedback 
&feedback);
 
   _actions _action;
 
diff --git a/package_source.cc b/package_source.cc
index 2b5909bf..a652a3b3 100644
--- a/package_source.cc
+++ b/package_source.cc
@@ -21,13 +21,10 @@
 #include "sha2.h"
 #include "csu_util/MD5Sum.h"
 #include "LogFile.h"
-#include "threebar.h"
 #include "Exception.h"
 #include "filemanip.h"
 #include "io_stream.h"
-#include "resource.h"
-
-extern ThreeBarProgressPage Progress;
+#include "Feedback.h"
 
 site::site (const std::string& newkey) : key(newkey)
 {
@@ -65,19 +62,19 @@ packagesource::check_size_and_cache (const std::string 
fullname)
 }
 
 void
-packagesource::check_hash ()
+packagesource::check_hash (Feedback &feedback)
 {
   if (validated || cached.empty ())
     return;
 
   if (sha512_isSet)
     {
-      check_sha512 (cached);
+      check_sha512 (cached, feedback);
       validated = true;
     }
   else if (md5.isSet())
     {
-      check_md5 (cached);
+      check_md5 (cached, feedback);
       validated = true;
     }
   else
@@ -97,7 +94,7 @@ sha512_str (const unsigned char *in, char *buf)
 }
 
 void
-packagesource::check_sha512 (const std::string fullname) const
+packagesource::check_sha512 (const std::string fullname, Feedback &feedback) 
const
 {
   io_stream *thefile = io_stream::open (fullname, "rb", 0);
   if (!thefile)
@@ -112,19 +109,14 @@ packagesource::check_sha512 (const std::string fullname) 
const
   SHA512Init (&ctx);
 
   Log (LOG_BABBLE) << "Checking SHA512 for " << fullname << endLog;
-
-  std::wstring fmt = LoadStringW(IDS_PROGRESS_CHECKING_HASH);
-  std::wstring s = format(fmt, "SHA512", shortname.c_str());
-  Progress.SetText1 (s.c_str());
-  Progress.SetText4 (IDS_PROGRESS_PROGRESS);
-  Progress.SetBar1 (0);
+  feedback.hash_init("SHA512", shortname);
 
   unsigned char buffer[64 * 1024];
   ssize_t count;
   while ((count = thefile->read (buffer, sizeof (buffer))) > 0)
   {
     SHA512Update (&ctx, buffer, count);
-    Progress.SetBar1 (thefile->tell (), thefile->get_size ());
+    feedback.hash_progress(thefile->tell (), thefile->get_size ());
   }
   delete thefile;
   if (count < 0)
@@ -152,7 +144,7 @@ packagesource::check_sha512 (const std::string fullname) 
const
 }
 
 void
-packagesource::check_md5 (const std::string fullname) const
+packagesource::check_md5 (const std::string fullname, Feedback &feedback) const
 {
   io_stream *thefile = io_stream::open (fullname, "rb", 0);
   if (!thefile)
@@ -163,19 +155,14 @@ packagesource::check_md5 (const std::string fullname) 
const
   tempMD5.begin ();
 
   Log (LOG_BABBLE) << "Checking MD5 for " << fullname << endLog;
-
-  std::wstring fmt = LoadStringW(IDS_PROGRESS_CHECKING_HASH);
-  std::wstring s = format(fmt, "MD5", shortname);
-  Progress.SetText1 (s.c_str());
-  Progress.SetText4 (IDS_PROGRESS_PROGRESS);
-  Progress.SetBar1 (0);
+  feedback.hash_init("MD5", shortname);
 
   unsigned char buffer[64 * 1024];
   ssize_t count;
   while ((count = thefile->read (buffer, sizeof (buffer))) > 0)
     {
       tempMD5.append (buffer, count);
-      Progress.SetBar1 (thefile->tell (), thefile->get_size ());
+      feedback.hash_progress(thefile->tell (), thefile->get_size ());
     }
   delete thefile;
   if (count < 0)
diff --git a/package_source.h b/package_source.h
index fae46f70..663a6031 100644
--- a/package_source.h
+++ b/package_source.h
@@ -26,6 +26,8 @@
 #include "csu_util/MD5Sum.h"
 #include <vector>
 
+class Feedback;
+
 class site
 {
 public:
@@ -79,7 +81,7 @@ public:
   MD5Sum md5;
   /* The next two functions throw exceptions on failure.  */
   void check_size_and_cache (const std::string fullname);
-  void check_hash ();
+  void check_hash (Feedback &feedback);
   typedef std::vector <site> sitestype;
   sitestype sites;
 
@@ -89,8 +91,8 @@ private:
   std::string shortname;
   std::string cached;
   bool validated;
-  void check_sha512 (const std::string fullname) const;
-  void check_md5 (const std::string fullname) const;
+  void check_sha512 (const std::string fullname, Feedback &feedback) const;
+  void check_md5 (const std::string fullname, Feedback &feedback) const;
 };
 
 #endif /* SETUP_PACKAGE_SOURCE_H */
diff --git a/threebar.cc b/threebar.cc
index bc356c0a..16430bb5 100644
--- a/threebar.cc
+++ b/threebar.cc
@@ -23,7 +23,7 @@
 #include "resource.h"
 
 #include "dialog.h"
-#include "site.h"
+#include "gui/SitePage.h"
 
 #include "propsheet.h"
 #include "threebar.h"

Reply via email to