Suggest URLs for updated setup based on build architecture
[cygwin-setup.git] / site.cc
blob569235a97f7d1024e953193f64f95dfa50b65460
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", IDS_HELPTEXT_SITE);
95 BoolOption OnlySiteOption(false, 'O', "only-site", IDS_HELPTEXT_ONLY_SITE);
96 extern BoolOption UnsupportedOption;
98 SiteSetting::SiteSetting (): saved (false)
100 std::vector<std::string> SiteOptionStrings = SiteOption;
101 if (SiteOptionStrings.size())
103 for (std::vector<std::string>::const_iterator n = SiteOptionStrings.begin ();
104 n != SiteOptionStrings.end (); ++n)
105 registerSavedSite (n->c_str ());
107 else
108 getSavedSites ();
111 const char *
112 SiteSetting::lastMirrorKey ()
114 if (UnsupportedOption)
115 return "last-mirror-unsupported";
117 return "last-mirror";
120 void
121 SiteSetting::save()
123 io_stream *f = UserSettings::instance().open (lastMirrorKey ());
124 if (f)
126 for (SiteList::const_iterator n = site_list.begin ();
127 n != site_list.end (); ++n)
128 *f << n->url;
129 delete f;
131 saved = true;
134 SiteSetting::~SiteSetting ()
136 if (!saved)
137 save ();
140 site_list_type::site_list_type (const std::string &_url,
141 const std::string &_servername,
142 const std::string &_area,
143 const std::string &_location,
144 bool _from_mirrors_lst,
145 bool _noshow = false)
147 url = _url;
148 servername = _servername;
149 area = _area;
150 location = _location;
151 from_mirrors_lst = _from_mirrors_lst;
152 noshow = _noshow;
154 /* Canonicalize URL to ensure it ends with a '/' */
155 if (url.at(url.length()-1) != '/')
156 url.append("/");
158 /* displayed_url is protocol and site name part of url */
159 std::string::size_type path_offset = url.find ("/", url.find ("//") + 2);
160 displayed_url = url.substr(0, path_offset);
162 /* the sorting key is hostname components in reverse order (to sort by country code)
163 plus the url (to ensure uniqueness) */
164 key = std::string();
165 std::string::size_type last_idx = displayed_url.length () - 1;
166 std::string::size_type idx = url.find_last_of("./", last_idx);
167 if (last_idx - idx == 3)
169 /* Sort non-country TLDs (.com, .net, ...) together. */
170 key += " ";
174 key += url.substr(idx + 1, last_idx - idx);
175 key += " ";
176 last_idx = idx - 1;
177 idx = url.find_last_of("./", last_idx);
178 if (idx == std::string::npos)
179 idx = 0;
180 } while (idx > 0);
181 key += url;
184 bool
185 site_list_type::operator == (site_list_type const &rhs) const
187 return stricmp (key.c_str(), rhs.key.c_str()) == 0;
190 bool
191 site_list_type::operator < (site_list_type const &rhs) const
193 return stricmp (key.c_str(), rhs.key.c_str()) < 0;
197 A SiteList is maintained as an in-order std::vector of site_list_type, by
198 replacing it with a new object with the new item inserted in the correct
199 place.
201 Yes, we could just use an ordered container, instead.
203 static void
204 site_list_insert(SiteList &site_list, site_list_type newsite)
206 SiteList::iterator i = find (site_list.begin(), site_list.end(), newsite);
207 if (i == site_list.end())
209 SiteList result;
210 merge (site_list.begin(), site_list.end(),
211 &newsite, &newsite + 1,
212 inserter (result, result.begin()));
213 site_list = result;
215 else
216 *i = newsite;
219 static void
220 save_dialog (HWND h)
222 // Remove anything that was previously in the selected site list.
223 site_list.clear ();
225 HWND listbox = GetDlgItem (h, IDC_URL_LIST);
226 int sel_count = SendMessage (listbox, LB_GETSELCOUNT, 0, 0);
227 if (sel_count > 0)
229 int sel_buffer[sel_count];
230 SendMessage (listbox, LB_GETSELITEMS, sel_count, (LPARAM) sel_buffer);
231 for (int n = 0; n < sel_count; n++)
233 int mirror =
234 SendMessage (listbox, LB_GETITEMDATA, sel_buffer[n], 0);
235 site_list.push_back (all_site_list[mirror]);
240 // This is called only for lists of mirrors that came (now or in a
241 // previous setup run) from mirrors.lst.
242 void
243 load_site_list (SiteList& theSites, char *theString)
245 char *bol, *eol, *nl;
247 nl = theString;
248 while (*nl)
250 bol = nl;
251 for (eol = bol; *eol && *eol != '\n'; eol++);
252 if (*eol)
253 nl = eol + 1;
254 else
255 nl = eol;
256 while (eol > bol && eol[-1] == '\r')
257 eol--;
258 *eol = 0;
259 if (*bol == '#' || !*bol)
260 continue;
261 /* Accept only the URL schemes we can understand. */
262 if (strncmp(bol, "http://", 7) == 0 ||
263 strncmp(bol, "https://", 8) == 0 ||
264 strncmp(bol, "ftp://", 6) == 0 ||
265 strncmp(bol, "ftps://", 7) == 0)
267 int i;
268 char *semi[4];
270 /* split into up to 4 semicolon-delimited parts */
271 for (i = 0; i < 4; i++)
272 semi[i] = 0;
274 char *p = bol;
275 for (i = 0; i < 4; i++)
277 semi[i] = strchr (p, ';');
278 if (!semi[i])
279 break;
281 *semi[i] = 0;
282 p = ++semi[i];
285 /* Ignore malformed lines */
286 if (!semi[0] || !semi[1] || !semi[2])
287 continue;
289 bool noshow = semi[3] && (strncmp(semi[3], "noshow", 6) == 0);
291 site_list_type newsite (bol, semi[0], semi[1], semi[2], true, noshow);
292 site_list_insert (theSites, newsite);
294 else
296 Log (LOG_BABBLE) << "Discarding line '" << bol << "' due to unknown protocol" << endLog;
301 static void
302 migrate_selected_site_list()
304 const std::string http = "http://";
306 for (SiteList::iterator i = site_list.begin();
307 i != site_list.end();
308 ++i)
310 /* If the saved selected site URL starts with "http://", and the same URL,
311 but starting with "https://" appears in the mirror list, migrate to
312 "https://" */
313 if (strnicmp(i->url.c_str(), http.c_str(), strlen(http.c_str())) == 0)
315 std::string migrated_site = "https://";
316 migrated_site.append(i->url.substr(http.length()));
318 site_list_type migrate(migrated_site, "", "", "", false);
319 SiteList::iterator j = find (all_site_list.begin(),
320 all_site_list.end(), migrate);
321 if (j != all_site_list.end())
323 Log (LOG_PLAIN) << "Migrated " << i->url << " to " << migrated_site << endLog;
324 *i = migrate;
330 static int
331 get_site_list (HINSTANCE h, HWND owner)
333 char *theMirrorString, *theCachedString;
335 if (UnsupportedOption)
336 return 0;
338 const char *cached_mirrors = OnlySiteOption ? NULL : UserSettings::instance().get ("mirrors-lst");
339 if (cached_mirrors)
341 Log (LOG_BABBLE) << "Loaded cached mirror list" << endLog;
342 cache_is_usable = true;
344 else
346 Log (LOG_BABBLE) << "Cached mirror list unavailable" << endLog;
347 cache_is_usable = false;
348 cached_mirrors = "";
351 std::string mirrors = OnlySiteOption ? std::string ("") : get_url_to_string (MIRROR_LIST_URL, owner);
352 if (mirrors.size())
353 cache_needs_writing = true;
354 else
356 if (!cached_mirrors[0])
358 if (!OnlySiteOption)
359 note(owner, IDS_NO_MIRROR_LST);
360 Log (LOG_BABBLE) << "Defaulting to empty mirror list" << endLog;
362 else
364 mirrors = cached_mirrors;
365 Log (LOG_BABBLE) << "Using cached mirror list" << endLog;
367 cache_is_usable = false;
368 cache_needs_writing = false;
370 theMirrorString = new_cstr_char_array (mirrors);
371 theCachedString = new_cstr_char_array (cached_mirrors);
373 load_site_list (all_site_list, theMirrorString);
374 load_site_list (cached_site_list, theCachedString);
376 delete[] theMirrorString;
377 delete[] theCachedString;
379 migrate_selected_site_list();
381 return 0;
384 /* List of machines that should not be used by default when saved
385 in "last-mirror". */
386 #define NOSAVE1 "ftp://sourceware.org/"
387 #define NOSAVE1_LEN (sizeof (NOSAVE2) - 1)
388 #define NOSAVE2 "ftp://sources.redhat.com/"
389 #define NOSAVE2_LEN (sizeof (NOSAVE1) - 1)
390 #define NOSAVE3 "ftp://gcc.gnu.org/"
391 #define NOSAVE3_LEN (sizeof (NOSAVE3) - 1)
393 void
394 SiteSetting::registerSavedSite (const char * site)
396 site_list_type tempSite(site, "", "", "", false);
398 /* Don't default to certain machines if they suffer from bandwidth
399 limitations. */
400 if (strnicmp (site, NOSAVE1, NOSAVE1_LEN) == 0
401 || strnicmp (site, NOSAVE2, NOSAVE2_LEN) == 0
402 || strnicmp (site, NOSAVE3, NOSAVE3_LEN) == 0)
403 return;
405 site_list_insert (all_site_list, tempSite);
406 site_list.push_back (tempSite);
409 void
410 SiteSetting::getSavedSites ()
412 const char *buf = UserSettings::instance().get (lastMirrorKey ());
413 if (!buf)
414 return;
415 char *fg_ret = strdup (buf);
416 for (char *site = strtok (fg_ret, "\n"); site; site = strtok (NULL, "\n"))
417 registerSavedSite (site);
418 free (fg_ret);
421 static DWORD WINAPI
422 do_download_site_info_thread (void *p)
424 HANDLE *context;
425 HINSTANCE hinst;
426 HWND h;
427 context = (HANDLE *) p;
429 SetThreadUILanguage(langid);
433 hinst = (HINSTANCE) (context[0]);
434 h = (HWND) (context[1]);
435 static bool downloaded = false;
436 if (!downloaded && get_site_list (hinst, h))
438 // Error: Couldn't download the site info.
439 // Go back to the Net setup page.
440 mbox (h, IDS_GET_SITELIST_ERROR, MB_OK);
442 // Tell the progress page that we're done downloading
443 Progress.PostMessageNow (WM_APP_SITE_INFO_DOWNLOAD_COMPLETE, 0, IDD_NET);
445 else
447 downloaded = true;
448 // Everything worked, go to the site select page
449 // Tell the progress page that we're done downloading
450 Progress.PostMessageNow (WM_APP_SITE_INFO_DOWNLOAD_COMPLETE, 0, IDD_SITE);
453 TOPLEVEL_CATCH((HWND) context[1], "site");
455 ExitThread(0);
458 static HANDLE context[2];
460 void
461 do_download_site_info (HINSTANCE hinst, HWND owner)
464 context[0] = hinst;
465 context[1] = owner;
467 DWORD threadID;
468 CreateThread (NULL, 0, do_download_site_info_thread, context, 0, &threadID);
471 static INT_PTR CALLBACK
472 drop_proc (HWND h, UINT message, WPARAM wParam, LPARAM lParam)
474 switch (message)
476 case WM_INITDIALOG:
477 eset(h, IDC_DROP_MIRRORS, cache_warn_urls);
478 /* Should this be set by default? */
479 // CheckDlgButton (h, IDC_DROP_NOWARN, BST_CHECKED);
480 SetFocus (GetDlgItem(h, IDC_DROP_NOWARN));
481 return FALSE;
482 break;
483 case WM_COMMAND:
484 switch (LOWORD (wParam))
486 case IDYES:
487 if (IsDlgButtonChecked (h, IDC_DROP_NOWARN) == BST_CHECKED)
488 EndDialog (h, CACHE_ACCEPT_NOWARN);
489 else
490 EndDialog (h, CACHE_ACCEPT_WARN);
491 break;
493 case IDNO:
494 EndDialog (h, CACHE_REJECT);
495 break;
497 default:
498 return 0;
500 return TRUE;
501 break;
502 default:
503 return FALSE;
507 int check_dropped_mirrors (HWND h)
509 cache_warn_urls = "";
510 dropped_site_list.clear ();
512 for (SiteList::const_iterator n = site_list.begin ();
513 n != site_list.end (); ++n)
515 SiteList::iterator i = find (all_site_list.begin(), all_site_list.end(),
516 *n);
517 if (i == all_site_list.end() || !i->from_mirrors_lst)
519 SiteList::iterator j = find (cached_site_list.begin(),
520 cached_site_list.end(), *n);
521 if (j != cached_site_list.end())
523 Log (LOG_PLAIN) << "Dropped selected mirror: " << n->url
524 << endLog;
525 dropped_site_list.push_back (*j);
526 if (cache_warn_urls.size())
527 cache_warn_urls += "\r\n";
528 cache_warn_urls += i->url;
532 if (cache_warn_urls.size())
534 if (unattended_mode)
535 return CACHE_ACCEPT_WARN;
536 return DialogBox (hinstance, MAKEINTRESOURCE (IDD_DROPPED), h,
537 drop_proc);
539 return CACHE_ACCEPT_NOWARN;
542 void write_cache_list (io_stream *f, const SiteList& theSites)
544 for (SiteList::const_iterator n = theSites.begin ();
545 n != theSites.end (); ++n)
546 if (n->from_mirrors_lst)
547 *f << (n->url + ";" + n->servername + ";" + n->area + ";"
548 + n->location);
551 void save_cache_file (int cache_action)
553 io_stream *f = UserSettings::instance().open ("mirrors-lst");
554 if (f)
556 write_cache_list (f, all_site_list);
557 if (cache_action == CACHE_ACCEPT_WARN)
559 Log (LOG_PLAIN) << "Adding dropped mirrors to cache to warn again."
560 << endLog;
561 *f << "# Following mirrors re-added by setup.exe to warn again about dropped urls.";
562 write_cache_list (f, dropped_site_list);
564 delete f;
568 bool SitePage::Create ()
570 return PropertyPage::Create (IDD_SITE);
573 void
574 SitePage::OnInit ()
576 AddTooltip (IDC_EDIT_USER_URL, IDS_USER_URL_TOOLTIP);
579 long
580 SitePage::OnNext ()
582 HWND h = GetHWND ();
583 int cache_action = CACHE_ACCEPT_NOWARN;
585 save_dialog (h);
587 if (cache_is_usable && !(cache_action = check_dropped_mirrors (h)))
588 return -1;
590 if (cache_needs_writing)
591 save_cache_file (cache_action);
593 // Log all the selected URLs from the list.
594 for (SiteList::const_iterator n = site_list.begin ();
595 n != site_list.end (); ++n)
596 Log (LOG_PLAIN) << "site: " << n->url << endLog;
598 Progress.SetActivateTask (WM_APP_START_SETUP_INI_DOWNLOAD);
599 return IDD_INSTATUS;
601 return 0;
604 long
605 SitePage::OnBack ()
607 HWND h = GetHWND ();
609 save_dialog (h);
611 // Go back to the net connection type page
612 return 0;
615 void
616 SitePage::OnActivate ()
618 // Fill the list box with all known sites.
619 PopulateListBox ();
621 // Load the user URL box with nothing - it is in the list already.
622 eset (GetHWND (), IDC_EDIT_USER_URL, "");
624 // Get the enabled/disabled states of the controls set accordingly.
625 CheckControlsAndDisableAccordingly ();
628 long
629 SitePage::OnUnattended ()
631 if (SendMessage (GetDlgItem (IDC_URL_LIST), LB_GETSELCOUNT, 0, 0) > 0)
632 return OnNext ();
633 else
634 return -2;
637 void
638 SitePage::CheckControlsAndDisableAccordingly () const
640 DWORD ButtonFlags = PSWIZB_BACK;
642 // Check that at least one download site is selected.
643 if (SendMessage (GetDlgItem (IDC_URL_LIST), LB_GETSELCOUNT, 0, 0) > 0)
645 // At least one site selected, enable "Next".
646 ButtonFlags |= PSWIZB_NEXT;
648 GetOwner ()->SetButtons (ButtonFlags);
651 void
652 SitePage::PopulateListBox ()
654 std::vector <int> sel_indicies;
655 HWND listbox = GetDlgItem (IDC_URL_LIST);
657 // Populate the list box with the URLs.
658 SendMessage (listbox, LB_RESETCONTENT, 0, 0);
659 for (SiteList::const_iterator i = all_site_list.begin ();
660 i != all_site_list.end (); ++i)
662 // If selected, always show
663 SiteList::iterator f = find (site_list.begin(), site_list.end(), *i);
664 if (f == site_list.end())
666 // Otherwise, hide redundant legacy URLs:
667 if (i->noshow)
668 continue;
671 int j = SendMessage (listbox, LB_ADDSTRING, 0,
672 (LPARAM) i->displayed_url.c_str());
673 // Set the ListBox item data to the index into all_site_list
674 SendMessage (listbox, LB_SETITEMDATA, j, (i - all_site_list.begin()));
676 // For every selected item, remember the index
677 if (f != site_list.end())
679 sel_indicies.push_back(j);
683 // Select the selected ones.
684 for (std::vector <int>::const_iterator n = sel_indicies.begin ();
685 n != sel_indicies.end (); ++n)
687 int index = *n;
688 // Highlight the selected item
689 SendMessage (listbox, LB_SELITEMRANGE, TRUE, (index << 16) | index);
690 // Make sure it's fully visible
691 SendMessage (listbox, LB_SETCARETINDEX, index, FALSE);
695 bool SitePage::OnMessageCmd (int id, HWND hwndctl, UINT code)
697 switch (id)
699 case IDC_EDIT_USER_URL:
701 // Set the default pushbutton to ADD if the user is entering text.
702 if (code == EN_CHANGE)
703 SendMessage (GetHWND (), DM_SETDEFID, (WPARAM) IDC_BUTTON_ADD_URL, 0);
704 break;
706 case IDC_URL_LIST:
708 if (code == LBN_SELCHANGE)
710 CheckControlsAndDisableAccordingly ();
711 save_dialog (GetHWND ());
713 break;
715 case IDC_BUTTON_ADD_URL:
717 if (code == BN_CLICKED)
719 // User pushed the Add button.
720 std::string other_url = egetString (GetHWND (), IDC_EDIT_USER_URL);
721 if (other_url.size())
723 site_list_type newsite (other_url, "", "", "", false);
724 SiteList::iterator i = find (all_site_list.begin(),
725 all_site_list.end(), newsite);
726 if (i == all_site_list.end())
728 all_site_list.push_back (newsite);
729 Log (LOG_BABBLE) << "Adding site: " << other_url << endLog;
730 site_list.push_back (newsite);
732 else
733 site_list.push_back (*i);
735 // Update the list box.
736 PopulateListBox ();
737 // And allow the user to continue
738 CheckControlsAndDisableAccordingly ();
739 eset (GetHWND (), IDC_EDIT_USER_URL, "");
742 break;
744 default:
745 // Wasn't recognized or handled.
746 return false;
749 // Was handled since we never got to default above.
750 return true;