Show overall progress for checking packages in package cache
[cygwin-setup.git] / site.cc
blob57f54ddebcc3f6b316c83065ac25469d5bc19e5d
1 /*
2 * Copyright (c) 2000, Red Hat, Inc.
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
9 * A copy of the GNU General Public License can be found at
10 * http://www.gnu.org/
12 * Written by DJ Delorie <dj@cygnus.com>
16 /* The purpose of this file is to get the list of mirror sites and ask
17 the user which mirror site they want to download from. */
19 #include <string>
20 #include <algorithm>
22 #include "site.h"
23 #include "win32.h"
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <process.h>
28 #include "dialog.h"
29 #include "resource.h"
30 #include "state.h"
31 #include "geturl.h"
32 #include "msg.h"
33 #include "LogSingleton.h"
34 #include "io_stream.h"
35 #include "site.h"
37 #include "propsheet.h"
39 #include "threebar.h"
40 #include "ControlAdjuster.h"
41 #include "Exception.h"
42 #include "String++.h"
44 #define MIRROR_LIST_URL "https://cygwin.com/mirrors.lst"
46 extern ThreeBarProgressPage Progress;
49 What to do if dropped mirrors are selected.
51 enum
53 CACHE_REJECT, // Go back to re-select mirrors.
54 CACHE_ACCEPT_WARN, // Go on. Warn again next time.
55 CACHE_ACCEPT_NOWARN // Go on. Don't warn again.
59 Sizing information.
61 static ControlAdjuster::ControlInfo SiteControlsInfo[] = {
62 {IDC_URL_LIST, CP_STRETCH, CP_STRETCH},
63 {IDC_EDIT_USER_URL, CP_STRETCH, CP_BOTTOM},
64 {IDC_BUTTON_ADD_URL, CP_RIGHT, CP_BOTTOM},
65 {IDC_SITE_USERURL, CP_LEFT, CP_BOTTOM},
66 {0, CP_LEFT, CP_TOP}
69 SitePage::SitePage ()
71 sizeProcessor.AddControlInfo (SiteControlsInfo);
74 #include "getopt++/StringArrayOption.h"
75 #include "getopt++/BoolOption.h"
76 #include "UserSettings.h"
78 bool cache_is_usable;
79 bool cache_needs_writing;
80 std::string cache_warn_urls;
82 /* Selected sites */
83 SiteList site_list;
85 /* Fresh mirrors + selected sites */
86 SiteList all_site_list;
88 /* Previously fresh + cached before */
89 SiteList cached_site_list;
91 /* Stale selected sites to warn about and add to cache */
92 SiteList dropped_site_list;
94 StringArrayOption SiteOption('s', "site", "Download site URL");
96 BoolOption OnlySiteOption(false, 'O', "only-site", "Do not download mirror list. Only use sites specified with -s.");
97 extern BoolOption UnsupportedOption;
99 SiteSetting::SiteSetting (): saved (false)
101 std::vector<std::string> SiteOptionStrings = SiteOption;
102 if (SiteOptionStrings.size())
104 for (std::vector<std::string>::const_iterator n = SiteOptionStrings.begin ();
105 n != SiteOptionStrings.end (); ++n)
106 registerSavedSite (n->c_str ());
108 else
109 getSavedSites ();
112 const char *
113 SiteSetting::lastMirrorKey ()
115 if (UnsupportedOption)
116 return "last-mirror-unsupported";
118 return "last-mirror";
121 void
122 SiteSetting::save()
124 io_stream *f = UserSettings::instance().open (lastMirrorKey ());
125 if (f)
127 for (SiteList::const_iterator n = site_list.begin ();
128 n != site_list.end (); ++n)
129 *f << n->url;
130 delete f;
132 saved = true;
135 SiteSetting::~SiteSetting ()
137 if (!saved)
138 save ();
141 site_list_type::site_list_type (const std::string &_url,
142 const std::string &_servername,
143 const std::string &_area,
144 const std::string &_location,
145 bool _from_mirrors_lst)
147 url = _url;
148 servername = _servername;
149 area = _area;
150 location = _location;
151 from_mirrors_lst = _from_mirrors_lst;
153 /* Canonicalize URL to ensure it ends with a '/' */
154 if (url.at(url.length()-1) != '/')
155 url.append("/");
157 /* displayed_url is protocol and site name part of url */
158 std::string::size_type path_offset = url.find ("/", url.find ("//") + 2);
159 displayed_url = url.substr(0, path_offset);
161 /* the sorting key is hostname components in reverse order (to sort by country code)
162 plus the url (to ensure uniqueness) */
163 key = std::string();
164 std::string::size_type last_idx = displayed_url.length () - 1;
165 std::string::size_type idx = url.find_last_of("./", last_idx);
166 if (last_idx - idx == 3)
168 /* Sort non-country TLDs (.com, .net, ...) together. */
169 key += " ";
173 key += url.substr(idx + 1, last_idx - idx);
174 key += " ";
175 last_idx = idx - 1;
176 idx = url.find_last_of("./", last_idx);
177 if (idx == std::string::npos)
178 idx = 0;
179 } while (idx > 0);
180 key += url;
183 site_list_type::site_list_type (site_list_type const &rhs)
185 key = rhs.key;
186 url = rhs.url;
187 servername = rhs.servername;
188 area = rhs.area;
189 location = rhs.location;
190 from_mirrors_lst = rhs.from_mirrors_lst;
191 displayed_url = rhs.displayed_url;
194 site_list_type &
195 site_list_type::operator= (site_list_type const &rhs)
197 key = rhs.key;
198 url = rhs.url;
199 servername = rhs.servername;
200 area = rhs.area;
201 location = rhs.location;
202 from_mirrors_lst = rhs.from_mirrors_lst;
203 displayed_url = rhs.displayed_url;
204 return *this;
207 bool
208 site_list_type::operator == (site_list_type const &rhs) const
210 return stricmp (key.c_str(), rhs.key.c_str()) == 0;
213 bool
214 site_list_type::operator < (site_list_type const &rhs) const
216 return stricmp (key.c_str(), rhs.key.c_str()) < 0;
220 A SiteList is maintained as an in-order std::vector of site_list_type, by
221 replacing it with a new object with the new item inserted in the correct
222 place.
224 Yes, we could just use an ordered container, instead.
226 static void
227 site_list_insert(SiteList &site_list, site_list_type newsite)
229 SiteList::iterator i = find (site_list.begin(), site_list.end(), newsite);
230 if (i == site_list.end())
232 SiteList result;
233 merge (site_list.begin(), site_list.end(),
234 &newsite, &newsite + 1,
235 inserter (result, result.begin()));
236 site_list = result;
238 else
239 *i = newsite;
242 static void
243 save_dialog (HWND h)
245 // Remove anything that was previously in the selected site list.
246 site_list.clear ();
248 HWND listbox = GetDlgItem (h, IDC_URL_LIST);
249 int sel_count = SendMessage (listbox, LB_GETSELCOUNT, 0, 0);
250 if (sel_count > 0)
252 int sel_buffer[sel_count];
253 SendMessage (listbox, LB_GETSELITEMS, sel_count, (LPARAM) sel_buffer);
254 for (int n = 0; n < sel_count; n++)
256 int mirror =
257 SendMessage (listbox, LB_GETITEMDATA, sel_buffer[n], 0);
258 site_list.push_back (all_site_list[mirror]);
263 // This is called only for lists of mirrors that came (now or in a
264 // previous setup run) from mirrors.lst.
265 void
266 load_site_list (SiteList& theSites, char *theString)
268 char *bol, *eol, *nl;
270 nl = theString;
271 while (*nl)
273 bol = nl;
274 for (eol = bol; *eol && *eol != '\n'; eol++);
275 if (*eol)
276 nl = eol + 1;
277 else
278 nl = eol;
279 while (eol > bol && eol[-1] == '\r')
280 eol--;
281 *eol = 0;
282 if (*bol == '#' || !*bol)
283 continue;
284 /* Accept only the URL schemes we can understand. */
285 if (strncmp(bol, "http://", 7) == 0 ||
286 strncmp(bol, "https://", 8) == 0 ||
287 strncmp(bol, "ftp://", 6) == 0 ||
288 strncmp(bol, "ftps://", 7) == 0)
290 char *semi = strchr (bol, ';');
291 char *semi2 = NULL;
292 char *semi3 = NULL;
293 if (semi)
295 *semi = 0;
296 semi++;
297 semi2 = strchr (semi, ';');
298 if (semi2)
300 *semi2 = 0;
301 semi2++;
302 semi3 = strchr (semi2, ';');
303 if (semi3)
305 *semi3 = 0;
306 semi3++;
311 /* Ignore malformed lines */
312 if (!semi || !semi2 || !semi3)
313 continue;
315 site_list_type newsite (bol, semi, semi2, semi3, true);
316 site_list_insert (theSites, newsite);
318 else
320 Log (LOG_BABBLE) << "Discarding line '" << bol << "' due to unknown protocol" << endLog;
325 static void
326 migrate_selected_site_list()
328 const std::string http = "http://";
330 for (SiteList::iterator i = site_list.begin();
331 i != site_list.end();
332 ++i)
334 /* If the saved selected site URL starts with "http://", and the same URL,
335 but starting with "https://" appears in the mirror list, migrate to
336 "https://" */
337 if (strnicmp(i->url.c_str(), http.c_str(), strlen(http.c_str())) == 0)
339 std::string migrated_site = "https://";
340 migrated_site.append(i->url.substr(http.length()));
342 site_list_type migrate(migrated_site, "", "", "", false);
343 SiteList::iterator j = find (all_site_list.begin(),
344 all_site_list.end(), migrate);
345 if (j != all_site_list.end())
347 Log (LOG_PLAIN) << "Migrated " << i->url << " to " << migrated_site << endLog;
348 *i = migrate;
354 static int
355 get_site_list (HINSTANCE h, HWND owner)
357 char *theMirrorString, *theCachedString;
359 if (UnsupportedOption)
360 return 0;
362 const char *cached_mirrors = OnlySiteOption ? NULL : UserSettings::instance().get ("mirrors-lst");
363 if (cached_mirrors)
365 Log (LOG_BABBLE) << "Loaded cached mirror list" << endLog;
366 cache_is_usable = true;
368 else
370 Log (LOG_BABBLE) << "Cached mirror list unavailable" << endLog;
371 cache_is_usable = false;
372 cached_mirrors = "";
375 std::string mirrors = OnlySiteOption ? std::string ("") : get_url_to_string (MIRROR_LIST_URL, owner);
376 if (mirrors.size())
377 cache_needs_writing = true;
378 else
380 if (!cached_mirrors[0])
382 if (!OnlySiteOption)
383 note(owner, IDS_NO_MIRROR_LST);
384 Log (LOG_BABBLE) << "Defaulting to empty mirror list" << endLog;
386 else
388 mirrors = cached_mirrors;
389 Log (LOG_BABBLE) << "Using cached mirror list" << endLog;
391 cache_is_usable = false;
392 cache_needs_writing = false;
394 theMirrorString = new_cstr_char_array (mirrors);
395 theCachedString = new_cstr_char_array (cached_mirrors);
397 load_site_list (all_site_list, theMirrorString);
398 load_site_list (cached_site_list, theCachedString);
400 delete[] theMirrorString;
401 delete[] theCachedString;
403 migrate_selected_site_list();
405 return 0;
408 /* List of machines that should not be used by default when saved
409 in "last-mirror". */
410 #define NOSAVE1 "ftp://sourceware.org/"
411 #define NOSAVE1_LEN (sizeof (NOSAVE2) - 1)
412 #define NOSAVE2 "ftp://sources.redhat.com/"
413 #define NOSAVE2_LEN (sizeof (NOSAVE1) - 1)
414 #define NOSAVE3 "ftp://gcc.gnu.org/"
415 #define NOSAVE3_LEN (sizeof (NOSAVE3) - 1)
417 void
418 SiteSetting::registerSavedSite (const char * site)
420 site_list_type tempSite(site, "", "", "", false);
422 /* Don't default to certain machines if they suffer from bandwidth
423 limitations. */
424 if (strnicmp (site, NOSAVE1, NOSAVE1_LEN) == 0
425 || strnicmp (site, NOSAVE2, NOSAVE2_LEN) == 0
426 || strnicmp (site, NOSAVE3, NOSAVE3_LEN) == 0)
427 return;
429 site_list_insert (all_site_list, tempSite);
430 site_list.push_back (tempSite);
433 void
434 SiteSetting::getSavedSites ()
436 const char *buf = UserSettings::instance().get (lastMirrorKey ());
437 if (!buf)
438 return;
439 char *fg_ret = strdup (buf);
440 for (char *site = strtok (fg_ret, "\n"); site; site = strtok (NULL, "\n"))
441 registerSavedSite (site);
442 free (fg_ret);
445 static DWORD WINAPI
446 do_download_site_info_thread (void *p)
448 HANDLE *context;
449 HINSTANCE hinst;
450 HWND h;
451 context = (HANDLE *) p;
453 SetThreadUILanguage(langid);
457 hinst = (HINSTANCE) (context[0]);
458 h = (HWND) (context[1]);
459 static bool downloaded = false;
460 if (!downloaded && get_site_list (hinst, h))
462 // Error: Couldn't download the site info.
463 // Go back to the Net setup page.
464 mbox (h, IDS_GET_SITELIST_ERROR, MB_OK);
466 // Tell the progress page that we're done downloading
467 Progress.PostMessageNow (WM_APP_SITE_INFO_DOWNLOAD_COMPLETE, 0, IDD_NET);
469 else
471 downloaded = true;
472 // Everything worked, go to the site select page
473 // Tell the progress page that we're done downloading
474 Progress.PostMessageNow (WM_APP_SITE_INFO_DOWNLOAD_COMPLETE, 0, IDD_SITE);
477 TOPLEVEL_CATCH((HWND) context[1], "site");
479 ExitThread(0);
482 static HANDLE context[2];
484 void
485 do_download_site_info (HINSTANCE hinst, HWND owner)
488 context[0] = hinst;
489 context[1] = owner;
491 DWORD threadID;
492 CreateThread (NULL, 0, do_download_site_info_thread, context, 0, &threadID);
495 static INT_PTR CALLBACK
496 drop_proc (HWND h, UINT message, WPARAM wParam, LPARAM lParam)
498 switch (message)
500 case WM_INITDIALOG:
501 eset(h, IDC_DROP_MIRRORS, cache_warn_urls);
502 /* Should this be set by default? */
503 // CheckDlgButton (h, IDC_DROP_NOWARN, BST_CHECKED);
504 SetFocus (GetDlgItem(h, IDC_DROP_NOWARN));
505 return FALSE;
506 break;
507 case WM_COMMAND:
508 switch (LOWORD (wParam))
510 case IDYES:
511 if (IsDlgButtonChecked (h, IDC_DROP_NOWARN) == BST_CHECKED)
512 EndDialog (h, CACHE_ACCEPT_NOWARN);
513 else
514 EndDialog (h, CACHE_ACCEPT_WARN);
515 break;
517 case IDNO:
518 EndDialog (h, CACHE_REJECT);
519 break;
521 default:
522 return 0;
524 return TRUE;
525 break;
526 default:
527 return FALSE;
531 int check_dropped_mirrors (HWND h)
533 cache_warn_urls = "";
534 dropped_site_list.clear ();
536 for (SiteList::const_iterator n = site_list.begin ();
537 n != site_list.end (); ++n)
539 SiteList::iterator i = find (all_site_list.begin(), all_site_list.end(),
540 *n);
541 if (i == all_site_list.end() || !i->from_mirrors_lst)
543 SiteList::iterator j = find (cached_site_list.begin(),
544 cached_site_list.end(), *n);
545 if (j != cached_site_list.end())
547 Log (LOG_PLAIN) << "Dropped selected mirror: " << n->url
548 << endLog;
549 dropped_site_list.push_back (*j);
550 if (cache_warn_urls.size())
551 cache_warn_urls += "\r\n";
552 cache_warn_urls += i->url;
556 if (cache_warn_urls.size())
558 if (unattended_mode)
559 return CACHE_ACCEPT_WARN;
560 return DialogBox (hinstance, MAKEINTRESOURCE (IDD_DROPPED), h,
561 drop_proc);
563 return CACHE_ACCEPT_NOWARN;
566 void write_cache_list (io_stream *f, const SiteList& theSites)
568 for (SiteList::const_iterator n = theSites.begin ();
569 n != theSites.end (); ++n)
570 if (n->from_mirrors_lst)
571 *f << (n->url + ";" + n->servername + ";" + n->area + ";"
572 + n->location);
575 void save_cache_file (int cache_action)
577 io_stream *f = UserSettings::instance().open ("mirrors-lst");
578 if (f)
580 write_cache_list (f, all_site_list);
581 if (cache_action == CACHE_ACCEPT_WARN)
583 Log (LOG_PLAIN) << "Adding dropped mirrors to cache to warn again."
584 << endLog;
585 *f << "# Following mirrors re-added by setup.exe to warn again about dropped urls.";
586 write_cache_list (f, dropped_site_list);
588 delete f;
592 bool SitePage::Create ()
594 return PropertyPage::Create (IDD_SITE);
597 long
598 SitePage::OnNext ()
600 HWND h = GetHWND ();
601 int cache_action = CACHE_ACCEPT_NOWARN;
603 save_dialog (h);
605 if (cache_is_usable && !(cache_action = check_dropped_mirrors (h)))
606 return -1;
608 if (cache_needs_writing)
609 save_cache_file (cache_action);
611 // Log all the selected URLs from the list.
612 for (SiteList::const_iterator n = site_list.begin ();
613 n != site_list.end (); ++n)
614 Log (LOG_PLAIN) << "site: " << n->url << endLog;
616 Progress.SetActivateTask (WM_APP_START_SETUP_INI_DOWNLOAD);
617 return IDD_INSTATUS;
619 return 0;
622 long
623 SitePage::OnBack ()
625 HWND h = GetHWND ();
627 save_dialog (h);
629 // Go back to the net connection type page
630 return 0;
633 void
634 SitePage::OnActivate ()
636 // Fill the list box with all known sites.
637 PopulateListBox ();
639 // Load the user URL box with nothing - it is in the list already.
640 eset (GetHWND (), IDC_EDIT_USER_URL, "");
642 // Get the enabled/disabled states of the controls set accordingly.
643 CheckControlsAndDisableAccordingly ();
646 long
647 SitePage::OnUnattended ()
649 if (SendMessage (GetDlgItem (IDC_URL_LIST), LB_GETSELCOUNT, 0, 0) > 0)
650 return OnNext ();
651 else
652 return -2;
655 void
656 SitePage::CheckControlsAndDisableAccordingly () const
658 DWORD ButtonFlags = PSWIZB_BACK;
660 // Check that at least one download site is selected.
661 if (SendMessage (GetDlgItem (IDC_URL_LIST), LB_GETSELCOUNT, 0, 0) > 0)
663 // At least one site selected, enable "Next".
664 ButtonFlags |= PSWIZB_NEXT;
666 GetOwner ()->SetButtons (ButtonFlags);
669 void
670 SitePage::PopulateListBox ()
672 int j;
673 HWND listbox = GetDlgItem (IDC_URL_LIST);
675 // Populate the list box with the URLs.
676 SendMessage (listbox, LB_RESETCONTENT, 0, 0);
677 for (SiteList::const_iterator i = all_site_list.begin ();
678 i != all_site_list.end (); ++i)
680 j = SendMessage (listbox, LB_ADDSTRING, 0,
681 (LPARAM) i->displayed_url.c_str());
682 SendMessage (listbox, LB_SETITEMDATA, j, j);
685 // Select the selected ones.
686 for (SiteList::const_iterator n = site_list.begin ();
687 n != site_list.end (); ++n)
689 SiteList::iterator i = find (all_site_list.begin(),
690 all_site_list.end(), *n);
691 if (i != all_site_list.end())
693 int index = i - all_site_list.begin();
695 // Highlight the selected item
696 SendMessage (listbox, LB_SELITEMRANGE, TRUE, (index << 16) | index);
697 // Make sure it's fully visible
698 SendMessage (listbox, LB_SETCARETINDEX, index, FALSE);
703 bool SitePage::OnMessageCmd (int id, HWND hwndctl, UINT code)
705 switch (id)
707 case IDC_EDIT_USER_URL:
709 // Set the default pushbutton to ADD if the user is entering text.
710 if (code == EN_CHANGE)
711 SendMessage (GetHWND (), DM_SETDEFID, (WPARAM) IDC_BUTTON_ADD_URL, 0);
712 break;
714 case IDC_URL_LIST:
716 if (code == LBN_SELCHANGE)
718 CheckControlsAndDisableAccordingly ();
719 save_dialog (GetHWND ());
721 break;
723 case IDC_BUTTON_ADD_URL:
725 if (code == BN_CLICKED)
727 // User pushed the Add button.
728 std::string other_url = egetString (GetHWND (), IDC_EDIT_USER_URL);
729 if (other_url.size())
731 site_list_type newsite (other_url, "", "", "", false);
732 SiteList::iterator i = find (all_site_list.begin(),
733 all_site_list.end(), newsite);
734 if (i == all_site_list.end())
736 all_site_list.push_back (newsite);
737 Log (LOG_BABBLE) << "Adding site: " << other_url << endLog;
738 site_list.push_back (newsite);
740 else
741 site_list.push_back (*i);
743 // Update the list box.
744 PopulateListBox ();
745 // And allow the user to continue
746 CheckControlsAndDisableAccordingly ();
747 eset (GetHWND (), IDC_EDIT_USER_URL, "");
750 break;
752 default:
753 // Wasn't recognized or handled.
754 return false;
757 // Was handled since we never got to default above.
758 return true;