/* The purpose of this file is to download all the files we need to
do the installation. */
-#if 0
-static const char *cvsid =
- "\n%%% $Id$\n";
-#endif
+#include "csu_util/rfc1738.h"
#include "download.h"
#include <stdio.h>
#include <unistd.h>
#include <process.h>
+#include <vector>
#include "resource.h"
#include "msg.h"
#include "dialog.h"
-#include "String++.h"
#include "geturl.h"
#include "state.h"
-#include "LogSingleton.h"
+#include "LogFile.h"
#include "filemanip.h"
#include "io_stream.h"
#include "package_db.h"
#include "package_meta.h"
-#include "package_version.h"
#include "package_source.h"
-#include "rfc1738.h"
-
#include "threebar.h"
-#include "libmd5-rfc/md5.h"
-
#include "Exception.h"
-#include "getopt++/BoolOption.h"
-
-using namespace std;
-
extern ThreeBarProgressPage Progress;
-static BoolOption NoMD5Option (false, '5', "no-md5", "Suppress MD5 checksum verification");
-
+// Return true if selected checks pass, false if they don't and the
+// user chooses to delete the file; otherwise throw an exception.
static bool
-validateCachedPackage (String const &fullname, packagesource & pkgsource)
+validateCachedPackage (const std::string& fullname, packagesource & pkgsource,
+ HWND owner, bool check_hash, bool check_size)
{
- DWORD size = get_file_size(fullname);
- if (size != pkgsource.size)
- {
- log (LOG_BABBLE) << "INVALID PACKAGE: " << fullname
- << " - Size mismatch: Ini-file: " << pkgsource.size
- << " != On-disk: " << size << endLog;
- return false;
- }
- if (pkgsource.md5.isSet() && !NoMD5Option)
+ try
{
- // check the MD5 sum of the cached file here
- io_stream *thefile = io_stream::open (fullname, "rb");
- if (!thefile)
- return 0;
- md5_state_t pns;
- md5_init (&pns);
-
- log (LOG_BABBLE) << "Checking MD5 for " << fullname << endLog;
-
- Progress.SetText1 ((String ("Checking MD5 for ") + pkgsource.Base()).cstr_oneuse());
- Progress.SetText4 ("Progress:");
- Progress.SetBar1 (0);
-
- unsigned char buffer[16384];
- ssize_t count;
- while ((count = thefile->read (buffer, 16384)) > 0)
- {
- md5_append (&pns, buffer, count);
- Progress.SetBar1 (thefile->tell(), thefile->get_size());
- }
- delete thefile;
- if (count < 0)
- throw new Exception (TOSTRING(__LINE__) " " __FILE__,
- String ("IO Error reading ") + fullname,
- APPERR_IO_ERROR);
-
- md5_byte_t tempdigest[16];
- md5_finish(&pns, tempdigest);
- md5 tempMD5;
- tempMD5.set (tempdigest);
-
- if (pkgsource.md5 != tempMD5)
- {
- log (LOG_BABBLE) << "INVALID PACKAGE: " << fullname
- << " - MD5 mismatch: Ini-file: " << pkgsource.md5.print()
- << " != On-disk: " << tempMD5.print() << endLog;
- return false;
- }
-
- log (LOG_BABBLE) << "MD5 verified OK: " << fullname
- << pkgsource.md5.print() << endLog;
+ if (check_size)
+ pkgsource.check_size_and_cache (fullname);
+ if (check_hash)
+ pkgsource.check_hash ();
+ return true;
+ }
+ catch (Exception *e)
+ {
+ pkgsource.set_cached ("");
+ const char *filename = fullname.c_str ();
+ if (strncmp (filename, "file://", 7) == 0)
+ filename += 7;
+ if (e->errNo() == APPERR_CORRUPT_PACKAGE
+ && yesno (owner, IDS_QUERY_CORRUPT, filename) == IDYES)
+ remove (filename);
+ else
+ throw e;
}
- return true;
+ return false;
}
-/* 0 on failure
+/* 0 if not cached; may throw exception if validation fails.
*/
int
-check_for_cached (packagesource & pkgsource)
+check_for_cached (packagesource & pkgsource, HWND owner, bool mirror_mode,
+ bool check_hash)
{
- /* search algo:
- 1) is there a legacy version in the cache dir available.
- (Note that the cache dir is represented by a mirror site of
- file://local_dir
- */
+ /* If the packagesource doesn't have a filename, it can't possibly be in the
+ cache */
+ if (!pkgsource.Canonical())
+ {
+ return 0;
+ }
+
+ /* Note that the cache dir is represented by a mirror site of file://local_dir */
+ std::string prefix = "file://" + local_dir + "/";
+ std::string fullname = prefix + pkgsource.Canonical();
+
+ if (mirror_mode)
+ {
+ /* Just assume correctness of mirror. */
+ if (!pkgsource.Cached())
+ pkgsource.set_cached (fullname);
+ return 1;
+ }
- // Already found one.
+ // Already found one, which we can assume to have the right size.
if (pkgsource.Cached())
- return 1;
-
- String prefix = String ("file://") + local_dir + "/";
- String fullname = prefix + pkgsource.Canonical();
- if (io_stream::exists(fullname))
- {
- if (validateCachedPackage (fullname, pkgsource))
- pkgsource.set_cached (fullname);
- else
- throw new Exception (TOSTRING(__LINE__) " " __FILE__,
- String ("Package validation failure for ") + fullname,
- APPERR_CORRUPT_PACKAGE);
- return 1;
- }
+ {
+ if (validateCachedPackage (pkgsource.Cached(), pkgsource, owner,
+ check_hash, false))
+ return 1;
+ // If we get here, pkgsource.Cached() was corrupt and deleted.
+ pkgsource.set_cached ("");
+ }
+
+ /*
+ 1) is there a legacy version in the cache dir available.
+ */
+ if (io_stream::exists (fullname))
+ {
+ if (validateCachedPackage (fullname, pkgsource, owner, check_hash, true))
+ return 1;
+ // If we get here, fullname was corrupt and deleted, but it
+ // might have been cached.
+ pkgsource.set_cached ("");
+ }
/*
2) is there a version from one of the selected mirror sites available ?
- */
+ */
for (packagesource::sitestype::const_iterator n = pkgsource.sites.begin();
n != pkgsource.sites.end(); ++n)
{
- String fullname = prefix + rfc1738_escape_part (n->key) + "/" +
+ std::string fullname = prefix + rfc1738_escape_part (n->key) + "/" +
pkgsource.Canonical ();
if (io_stream::exists(fullname))
- {
- if (validateCachedPackage (fullname, pkgsource))
- pkgsource.set_cached (fullname);
- else
- throw new Exception (TOSTRING(__LINE__) " " __FILE__,
- String ("Package validation failure for ") + fullname,
- APPERR_CORRUPT_PACKAGE);
- return 1;
- }
+ {
+ if (validateCachedPackage (fullname, pkgsource, owner, check_hash,
+ true))
+ return 1;
+ // If we get here, fullname was corrupt and deleted, but it
+ // might have been cached.
+ pkgsource.set_cached ("");
+ }
}
return 0;
}
{
try
{
- if (check_for_cached (pkgsource))
+ if (check_for_cached (pkgsource, owner))
return 0;
}
catch (Exception * e)
for (packagesource::sitestype::const_iterator n = pkgsource.sites.begin();
n != pkgsource.sites.end() && !success; ++n)
{
- String const local = local_dir + "/" +
+ const std::string local = local_dir + "/" +
rfc1738_escape_part (n->key) + "/" +
pkgsource.Canonical ();
- io_stream::mkpath_p (PATH_TO_FILE, String ("file://") + local);
+ io_stream::mkpath_p (PATH_TO_FILE, "file://" + local, 0);
- if (get_url_to_file(n->key + "/" + pkgsource.Canonical (),
+ if (get_url_to_file(n->key + pkgsource.Canonical (),
local + ".tmp", pkgsource.size, owner))
{
/* FIXME: note new source ? */
}
else
{
- size_t size = get_file_size (String("file://") + local + ".tmp");
- if (size == pkgsource.size)
+ try
{
- log (LOG_PLAIN) << "Downloaded " << local << endLog;
- if (_access (local.cstr_oneuse(), 0) == 0)
- remove (local.cstr_oneuse());
- rename ((local + ".tmp").cstr_oneuse(), local.cstr_oneuse());
+ if (_access (local.c_str(), 0) == 0)
+ remove (local.c_str());
+ rename ((local + ".tmp").c_str(), local.c_str());
+ pkgsource.check_size_and_cache ("file://" + local);
+ pkgsource.check_hash ();
+ Log (LOG_PLAIN) << "Downloaded " << local << endLog;
success = 1;
- pkgsource.set_cached (String ("file://") + local);
// FIXME: move the downloaded file to the
// original locations - without the mirror site dir in the way
continue;
}
- else
+ catch (Exception *e)
{
- log (LOG_PLAIN) << "Download " << local << " wrong size (" <<
- size << " actual vs " << pkgsource.size << " expected)" <<
- endLog;
- remove ((local + ".tmp").cstr_oneuse());
- continue;
+ remove (local.c_str());
+ pkgsource.set_cached ("");
+ if (e->errNo() == APPERR_CORRUPT_PACKAGE)
+ {
+ Log (LOG_PLAIN) << "Downloaded file " << local
+ << " is corrupt; deleting." << endLog;
+ continue;
+ }
+ else
+ {
+ Log (LOG_PLAIN) << "Unexpected exception while validating "
+ << "downloaded file " << local
+ << "; deleting." << endLog;
+ throw e;
+ }
}
}
}
if (success)
return 0;
- /* FIXME: Do we want to note this? if so how? */
return 1;
}
+static std::vector <packageversion> download_failures;
+static std::string download_warn_pkgs;
+
+static INT_PTR CALLBACK
+download_error_proc (HWND h, UINT message, WPARAM wParam, LPARAM lParam)
+{
+ switch (message)
+ {
+ case WM_INITDIALOG:
+ eset (h, IDC_DOWNLOAD_EDIT, download_warn_pkgs);
+ SetFocus (GetDlgItem(h, IDRETRY));
+ return FALSE;
+
+ case WM_COMMAND:
+ switch (LOWORD (wParam))
+ {
+ case IDRETRY:
+ case IDC_BACK:
+ case IDIGNORE:
+ case IDABORT:
+ EndDialog (h, LOWORD (wParam));
+ default:
+ // Not reached.
+ return 0;
+ }
+
+ default:
+ // Not handled.
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static int
+query_download_errors (HINSTANCE h, HWND owner)
+{
+ download_warn_pkgs = "";
+ Log (LOG_PLAIN) << "The following package(s) had download errors:" << endLog;
+ for (std::vector <packageversion>::const_iterator i = download_failures.begin (); i != download_failures.end (); i++)
+ {
+ packageversion pv = *i;
+ std::string pvs = pv.Name () + "-" + pv.Canonical_version ();
+ Log (LOG_PLAIN) << " " << pvs << endLog;
+ download_warn_pkgs += pvs + "\r\n";
+ }
+ return DialogBox (h, MAKEINTRESOURCE (IDD_DOWNLOAD_ERROR), owner,
+ download_error_proc);
+}
+
static int
do_download_thread (HINSTANCE h, HWND owner)
{
int errors = 0;
total_download_bytes = 0;
total_download_bytes_sofar = 0;
+ download_failures.clear ();
+
+ Progress.SetText1 (IDS_PROGRESS_CHECKING);
+ Progress.SetText2 ("");
+ Progress.SetText3 ("");
packagedb db;
- /* calculate the amount needed */
- for (vector <packagemeta *>::iterator i = db.packages.begin ();
- i != db.packages.end (); ++i)
+ const SolverTransactionList &t = db.solution.transactions();
+
+ /* calculate the total size of the download */
+ for (SolverTransactionList::const_iterator i = t.begin (); i != t.end (); ++i)
{
- packagemeta & pkg = **i;
- if (pkg.desired.changeRequested())
- {
- packageversion version = pkg.desired;
- packageversion sourceversion = version.sourcePackage();
- try
- {
- if (version.picked())
- {
- for (vector<packagesource>::iterator i =
- version.sources ()->begin();
- i != version.sources ()->end(); ++i)
- if (!check_for_cached (*i))
- total_download_bytes += i->size;
- }
- if (sourceversion.picked ())
- {
- for (vector<packagesource>::iterator i =
- sourceversion.sources ()->begin();
- i != sourceversion.sources ()->end(); ++i)
- if (!check_for_cached (*i))
- total_download_bytes += i->size;
- }
- }
- catch (Exception * e)
- {
- // We know what to do with these..
- if (e->errNo() == APPERR_CORRUPT_PACKAGE)
- fatal (owner, IDS_CORRUPT_PACKAGE, pkg.name.cstr_oneuse());
- // Unexpected exception.
- throw e;
- }
- }
+ if (i->type != SolverTransaction::transInstall)
+ continue;
+ packageversion version = i->version;
+
+ try
+ {
+ if (!check_for_cached (*version.source(), owner))
+ total_download_bytes += version.source()->size;
+ }
+ catch (Exception * e)
+ {
+ // We know what to do with these..
+ if (e->errNo() == APPERR_CORRUPT_PACKAGE)
+ fatal (owner, IDS_CORRUPT_PACKAGE, version.Name().c_str());
+ // Unexpected exception.
+ throw e;
+ }
+ Progress.SetBar2(std::distance(t.begin(), i) + 1, t.size());
}
/* and do the download. FIXME: This here we assign a new name for the cached version
* and check that above.
*/
- for (vector <packagemeta *>::iterator i = db.packages.begin ();
- i != db.packages.end (); ++i)
+ for (SolverTransactionList::const_iterator i = t.begin (); i != t.end (); ++i)
{
- packagemeta & pkg = **i;
- if (pkg.desired.changeRequested())
+ if (i->type != SolverTransaction::transInstall)
+ continue;
+ packageversion version = i->version;
+
{
int e = 0;
- packageversion version = pkg.desired;
- packageversion sourceversion = version.sourcePackage();
- if (version.picked())
- {
- for (vector<packagesource>::iterator i =
- version.sources ()->begin();
- i != version.sources ()->end(); ++i)
- e += download_one (*i, owner);
- }
- if (sourceversion && sourceversion.picked())
- {
- for (vector<packagesource>::iterator i =
- sourceversion.sources ()->begin();
- i != sourceversion.sources ()->end(); ++i)
- e += download_one (*i, owner);
- }
+ e += download_one (*version.source(), owner);
errors += e;
+ if (e)
+ download_failures.push_back (version);
#if 0
if (e)
pkg->action = ACTION_ERROR;
if (errors)
{
- if (yesno (owner, IDS_DOWNLOAD_INCOMPLETE) == IDYES)
+ // In unattended mode we retry the download, but not forever.
+ static int retries = 5;
+ int rc;
+ if (unattended_mode && --retries <= 0)
+ {
+ Log (LOG_PLAIN) << "download error in unattended_mode: out of retries" << endLog;
+ rc = IDABORT;
+ }
+ else if (unattended_mode)
+ {
+ Log (LOG_PLAIN) << "download error in unattended_mode: " << retries
+ << (retries > 1 ? " retries" : " retry") << " remaining." << endLog;
+ rc = IDRETRY;
+ }
+ else
+ rc = query_download_errors (h, owner);
+ switch (rc)
{
- return IDD_SITE;
+ case IDRETRY:
+ Progress.SetActivateTask (WM_APP_START_DOWNLOAD);
+ return IDD_INSTATUS;
+ case IDC_BACK:
+ return IDD_CHOOSE;
+ case IDABORT:
+ Logger ().setExitMsg (IDS_DOWNLOAD_INCOMPLETE_EXIT);
+ Logger ().exit (1);
+ case IDIGNORE:
+ break;
+ default:
+ break;
}
}
if (source == IDC_SOURCE_DOWNLOAD)
{
if (errors)
- exit_msg = IDS_DOWNLOAD_INCOMPLETE;
+ Logger ().setExitMsg (IDS_DOWNLOAD_INCOMPLETE_EXIT);
else if (!unattended_mode)
- exit_msg = IDS_DOWNLOAD_COMPLETE;
- return 0;
+ Logger ().setExitMsg (IDS_DOWNLOAD_COMPLETE);
+ return IDD_DESKTOP;
}
else
return IDD_S_INSTALL;
HANDLE *context;
context = (HANDLE *) p;
+ SetThreadUILanguage(langid);
+
try
{
int next_dialog =
do_download_thread ((HINSTANCE) context[0], (HWND) context[1]);
// Tell the progress page that we're done downloading
- Progress.PostMessage (WM_APP_DOWNLOAD_THREAD_COMPLETE, 0, next_dialog);
+ Progress.PostMessageNow (WM_APP_DOWNLOAD_THREAD_COMPLETE, 0, next_dialog);
}
- TOPLEVEL_CATCH("download");
+ TOPLEVEL_CATCH((HWND) context[1], "download");
ExitThread(0);
}