]> cygwin.com Git - cygwin-apps/setup.git/blob - site.cc
aa605617e3cf303e97123a9c930f347c2e1c009b
[cygwin-apps/setup.git] / site.cc
1 /*
2 * Copyright (c) 2000, Red Hat, Inc.
3 *
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.
8 *
9 * A copy of the GNU General Public License can be found at
10 * http://www.gnu.org/
11 *
12 * Written by DJ Delorie <dj@cygnus.com>
13 *
14 */
15
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. */
18
19 #include <string>
20 #include <algorithm>
21
22 #include "site.h"
23 #include "win32.h"
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <process.h>
27
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"
36
37 #include "propsheet.h"
38
39 #include "threebar.h"
40 #include "ControlAdjuster.h"
41 #include "Exception.h"
42 #include "String++.h"
43
44 #define MIRROR_LIST_URL "https://cygwin.com/mirrors.lst"
45
46 extern ThreeBarProgressPage Progress;
47
48 /*
49 What to do if dropped mirrors are selected.
50 */
51 enum
52 {
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.
56 };
57
58 /*
59 Sizing information.
60 */
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}
67 };
68
69 SitePage::SitePage ()
70 {
71 sizeProcessor.AddControlInfo (SiteControlsInfo);
72 }
73
74 #include "getopt++/StringArrayOption.h"
75 #include "getopt++/BoolOption.h"
76 #include "UserSettings.h"
77
78 bool cache_is_usable;
79 bool cache_needs_writing;
80 std::string cache_warn_urls;
81
82 /* Selected sites */
83 SiteList site_list;
84
85 /* Fresh mirrors + selected sites */
86 SiteList all_site_list;
87
88 /* Previously fresh + cached before */
89 SiteList cached_site_list;
90
91 /* Stale selected sites to warn about and add to cache */
92 SiteList dropped_site_list;
93
94 StringArrayOption SiteOption('s', "site", IDS_HELPTEXT_SITE);
95 BoolOption OnlySiteOption(false, 'O', "only-site", IDS_HELPTEXT_ONLY_SITE);
96 extern BoolOption UnsupportedOption;
97
98 SiteSetting::SiteSetting (): saved (false)
99 {
100 std::vector<std::string> SiteOptionStrings = SiteOption;
101 if (SiteOptionStrings.size())
102 {
103 for (std::vector<std::string>::const_iterator n = SiteOptionStrings.begin ();
104 n != SiteOptionStrings.end (); ++n)
105 registerSavedSite (n->c_str ());
106 }
107 else
108 getSavedSites ();
109 }
110
111 const char *
112 SiteSetting::lastMirrorKey ()
113 {
114 if (UnsupportedOption)
115 return "last-mirror-unsupported";
116
117 return "last-mirror";
118 }
119
120 void
121 SiteSetting::save()
122 {
123 io_stream *f = UserSettings::instance().open (lastMirrorKey ());
124 if (f)
125 {
126 for (SiteList::const_iterator n = site_list.begin ();
127 n != site_list.end (); ++n)
128 *f << n->url;
129 delete f;
130 }
131 saved = true;
132 }
133
134 SiteSetting::~SiteSetting ()
135 {
136 if (!saved)
137 save ();
138 }
139
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,
146 const std::string &_redir = "")
147 {
148 url = _url;
149 servername = _servername;
150 area = _area;
151 location = _location;
152 from_mirrors_lst = _from_mirrors_lst;
153 noshow = _noshow;
154 redir = _redir;
155
156 /* Canonicalize URL to ensure it ends with a '/' */
157 if (url.at(url.length()-1) != '/')
158 url.append("/");
159
160 /* displayed_url is protocol and site name part of url */
161 std::string::size_type path_offset = url.find ("/", url.find ("//") + 2);
162 displayed_url = url.substr(0, path_offset);
163
164 /* the sorting key is hostname components in reverse order (to sort by country code)
165 plus the url (to ensure uniqueness) */
166 key = std::string();
167 std::string::size_type last_idx = displayed_url.length () - 1;
168 std::string::size_type idx = url.find_last_of("./", last_idx);
169 if (last_idx - idx == 3)
170 {
171 /* Sort non-country TLDs (.com, .net, ...) together. */
172 key += " ";
173 }
174 do
175 {
176 key += url.substr(idx + 1, last_idx - idx);
177 key += " ";
178 last_idx = idx - 1;
179 idx = url.find_last_of("./", last_idx);
180 if (idx == std::string::npos)
181 idx = 0;
182 } while (idx > 0);
183 key += url;
184 }
185
186 bool
187 site_list_type::operator == (site_list_type const &rhs) const
188 {
189 return stricmp (key.c_str(), rhs.key.c_str()) == 0;
190 }
191
192 bool
193 site_list_type::operator < (site_list_type const &rhs) const
194 {
195 return stricmp (key.c_str(), rhs.key.c_str()) < 0;
196 }
197
198 /*
199 A SiteList is maintained as an in-order std::vector of site_list_type, by
200 replacing it with a new object with the new item inserted in the correct
201 place.
202
203 Yes, we could just use an ordered container, instead.
204 */
205 static void
206 site_list_insert(SiteList &site_list, site_list_type newsite)
207 {
208 SiteList::iterator i = find (site_list.begin(), site_list.end(), newsite);
209 if (i == site_list.end())
210 {
211 SiteList result;
212 merge (site_list.begin(), site_list.end(),
213 &newsite, &newsite + 1,
214 inserter (result, result.begin()));
215 site_list = result;
216 }
217 else
218 *i = newsite;
219 }
220
221 static void
222 save_dialog (HWND h)
223 {
224 // Remove anything that was previously in the selected site list.
225 site_list.clear ();
226
227 HWND listbox = GetDlgItem (h, IDC_URL_LIST);
228 int sel_count = SendMessage (listbox, LB_GETSELCOUNT, 0, 0);
229 if (sel_count > 0)
230 {
231 int sel_buffer[sel_count];
232 SendMessage (listbox, LB_GETSELITEMS, sel_count, (LPARAM) sel_buffer);
233 for (int n = 0; n < sel_count; n++)
234 {
235 int mirror =
236 SendMessage (listbox, LB_GETITEMDATA, sel_buffer[n], 0);
237 site_list.push_back (all_site_list[mirror]);
238 }
239 }
240 }
241
242 // This is called only for lists of mirrors that came (now or in a
243 // previous setup run) from mirrors.lst.
244 void
245 load_site_list (SiteList& theSites, char *theString)
246 {
247 char *bol, *eol, *nl;
248
249 nl = theString;
250 while (*nl)
251 {
252 bol = nl;
253 for (eol = bol; *eol && *eol != '\n'; eol++);
254 if (*eol)
255 nl = eol + 1;
256 else
257 nl = eol;
258 while (eol > bol && eol[-1] == '\r')
259 eol--;
260 *eol = 0;
261 if (*bol == '#' || !*bol)
262 continue;
263 /* Accept only the URL schemes we can understand. */
264 if (strncmp(bol, "http://", 7) == 0 ||
265 strncmp(bol, "https://", 8) == 0 ||
266 strncmp(bol, "ftp://", 6) == 0 ||
267 strncmp(bol, "ftps://", 7) == 0)
268 {
269 int i;
270 char *semi[4];
271
272 /* split into up to 4 semicolon-delimited parts */
273 for (i = 0; i < 4; i++)
274 semi[i] = 0;
275
276 char *p = bol;
277 for (i = 0; i < 4; i++)
278 {
279 semi[i] = strchr (p, ';');
280 if (!semi[i])
281 break;
282
283 *semi[i] = 0;
284 p = ++semi[i];
285 }
286
287 /* Ignore malformed lines */
288 if (!semi[0] || !semi[1] || !semi[2])
289 continue;
290
291 /* fourth part is an optional, comma-delimited set of flags */
292 bool noshow = FALSE;
293 const char *redir = "";
294
295 char *flag = semi[3];
296 while (flag)
297 {
298 if (strncmp(flag, "noshow", 6) == 0)
299 noshow = TRUE;
300 else if (strncmp(flag, "redir=", 6) == 0)
301 redir = flag+6;
302
303 flag = strchr (flag, ',');
304 if (flag)
305 *flag++ = 0;
306 }
307
308 /* add site to list */
309 site_list_type newsite (bol, semi[0], semi[1], semi[2], true, noshow, redir);
310 site_list_insert (theSites, newsite);
311 }
312 else
313 {
314 Log (LOG_BABBLE) << "Discarding line '" << bol << "' due to unknown protocol" << endLog;
315 }
316 }
317 }
318
319 static void
320 migrate_selected_site_list()
321 {
322 const std::string http = "http://";
323
324 for (SiteList::iterator i = site_list.begin();
325 i != site_list.end();
326 ++i)
327 {
328 /* If the saved selected site URL starts with "http://", and the same URL,
329 but starting with "https://" appears in the mirror list, migrate to
330 "https://" */
331 if (strnicmp(i->url.c_str(), http.c_str(), strlen(http.c_str())) == 0)
332 {
333 std::string migrated_site = "https://";
334 migrated_site.append(i->url.substr(http.length()));
335
336 site_list_type migrate(migrated_site, "", "", "", false);
337 SiteList::iterator j = find (all_site_list.begin(),
338 all_site_list.end(), migrate);
339 if (j != all_site_list.end())
340 {
341 Log (LOG_PLAIN) << "Migrated " << i->url << " to " << migrated_site << endLog;
342 *i = migrate;
343 }
344 }
345
346 /* If the saved selected site URL appears in the site list with a redir
347 flag, replace with the redirected URL */
348 {
349 SiteList::iterator j = find (all_site_list.begin(),
350 all_site_list.end(), *i);
351
352 if (j != all_site_list.end())
353 {
354 if (!j->redir.empty())
355 {
356 site_list_type migrate(j->redir, "", "", "", false);
357 Log (LOG_PLAIN) << "Migrated " << i->url << " to " << j->redir << endLog;
358 *i = migrate;
359 }
360 }
361 }
362 }
363 }
364
365 static int
366 get_site_list (HINSTANCE h, HWND owner)
367 {
368 char *theMirrorString, *theCachedString;
369
370 if (UnsupportedOption)
371 return 0;
372
373 const char *cached_mirrors = OnlySiteOption ? NULL : UserSettings::instance().get ("mirrors-lst");
374 if (cached_mirrors)
375 {
376 Log (LOG_BABBLE) << "Loaded cached mirror list" << endLog;
377 cache_is_usable = true;
378 }
379 else
380 {
381 Log (LOG_BABBLE) << "Cached mirror list unavailable" << endLog;
382 cache_is_usable = false;
383 cached_mirrors = "";
384 }
385
386 std::string mirrors = OnlySiteOption ? std::string ("") : get_url_to_string (MIRROR_LIST_URL, owner);
387 if (mirrors.size())
388 cache_needs_writing = true;
389 else
390 {
391 if (!cached_mirrors[0])
392 {
393 if (!OnlySiteOption)
394 note(owner, IDS_NO_MIRROR_LST);
395 Log (LOG_BABBLE) << "Defaulting to empty mirror list" << endLog;
396 }
397 else
398 {
399 mirrors = cached_mirrors;
400 Log (LOG_BABBLE) << "Using cached mirror list" << endLog;
401 }
402 cache_is_usable = false;
403 cache_needs_writing = false;
404 }
405 theMirrorString = new_cstr_char_array (mirrors);
406 theCachedString = new_cstr_char_array (cached_mirrors);
407
408 load_site_list (all_site_list, theMirrorString);
409 load_site_list (cached_site_list, theCachedString);
410
411 delete[] theMirrorString;
412 delete[] theCachedString;
413
414 migrate_selected_site_list();
415
416 return 0;
417 }
418
419 /* List of machines that should not be used by default when saved
420 in "last-mirror". */
421 #define NOSAVE1 "ftp://sourceware.org/"
422 #define NOSAVE1_LEN (sizeof (NOSAVE2) - 1)
423 #define NOSAVE2 "ftp://sources.redhat.com/"
424 #define NOSAVE2_LEN (sizeof (NOSAVE1) - 1)
425 #define NOSAVE3 "ftp://gcc.gnu.org/"
426 #define NOSAVE3_LEN (sizeof (NOSAVE3) - 1)
427
428 void
429 SiteSetting::registerSavedSite (const char * site)
430 {
431 site_list_type tempSite(site, "", "", "", false);
432
433 /* Don't default to certain machines if they suffer from bandwidth
434 limitations. */
435 if (strnicmp (site, NOSAVE1, NOSAVE1_LEN) == 0
436 || strnicmp (site, NOSAVE2, NOSAVE2_LEN) == 0
437 || strnicmp (site, NOSAVE3, NOSAVE3_LEN) == 0)
438 return;
439
440 site_list_insert (all_site_list, tempSite);
441 site_list.push_back (tempSite);
442 }
443
444 void
445 SiteSetting::getSavedSites ()
446 {
447 const char *buf = UserSettings::instance().get (lastMirrorKey ());
448 if (!buf)
449 return;
450 char *fg_ret = strdup (buf);
451 for (char *site = strtok (fg_ret, "\n"); site; site = strtok (NULL, "\n"))
452 registerSavedSite (site);
453 free (fg_ret);
454 }
455
456 static DWORD WINAPI
457 do_download_site_info_thread (void *p)
458 {
459 HANDLE *context;
460 HINSTANCE hinst;
461 HWND h;
462 context = (HANDLE *) p;
463
464 SetThreadUILanguage(langid);
465
466 try
467 {
468 hinst = (HINSTANCE) (context[0]);
469 h = (HWND) (context[1]);
470 static bool downloaded = false;
471 if (!downloaded && get_site_list (hinst, h))
472 {
473 // Error: Couldn't download the site info.
474 // Go back to the Net setup page.
475 mbox (h, IDS_GET_SITELIST_ERROR, MB_OK);
476
477 // Tell the progress page that we're done downloading
478 Progress.PostMessageNow (WM_APP_SITE_INFO_DOWNLOAD_COMPLETE, 0, IDD_NET);
479 }
480 else
481 {
482 downloaded = true;
483 // Everything worked, go to the site select page
484 // Tell the progress page that we're done downloading
485 Progress.PostMessageNow (WM_APP_SITE_INFO_DOWNLOAD_COMPLETE, 0, IDD_SITE);
486 }
487 }
488 TOPLEVEL_CATCH((HWND) context[1], "site");
489
490 ExitThread(0);
491 }
492
493 static HANDLE context[2];
494
495 void
496 do_download_site_info (HINSTANCE hinst, HWND owner)
497 {
498
499 context[0] = hinst;
500 context[1] = owner;
501
502 DWORD threadID;
503 CreateThread (NULL, 0, do_download_site_info_thread, context, 0, &threadID);
504 }
505
506 static INT_PTR CALLBACK
507 drop_proc (HWND h, UINT message, WPARAM wParam, LPARAM lParam)
508 {
509 switch (message)
510 {
511 case WM_INITDIALOG:
512 eset(h, IDC_DROP_MIRRORS, cache_warn_urls);
513 /* Should this be set by default? */
514 // CheckDlgButton (h, IDC_DROP_NOWARN, BST_CHECKED);
515 SetFocus (GetDlgItem(h, IDC_DROP_NOWARN));
516 return FALSE;
517 break;
518 case WM_COMMAND:
519 switch (LOWORD (wParam))
520 {
521 case IDYES:
522 if (IsDlgButtonChecked (h, IDC_DROP_NOWARN) == BST_CHECKED)
523 EndDialog (h, CACHE_ACCEPT_NOWARN);
524 else
525 EndDialog (h, CACHE_ACCEPT_WARN);
526 break;
527
528 case IDNO:
529 EndDialog (h, CACHE_REJECT);
530 break;
531
532 default:
533 return 0;
534 }
535 return TRUE;
536 break;
537 default:
538 return FALSE;
539 }
540 }
541
542 int check_dropped_mirrors (HWND h)
543 {
544 cache_warn_urls = "";
545 dropped_site_list.clear ();
546
547 for (SiteList::const_iterator n = site_list.begin ();
548 n != site_list.end (); ++n)
549 {
550 SiteList::iterator i = find (all_site_list.begin(), all_site_list.end(),
551 *n);
552 if (i == all_site_list.end() || !i->from_mirrors_lst)
553 {
554 SiteList::iterator j = find (cached_site_list.begin(),
555 cached_site_list.end(), *n);
556 if (j != cached_site_list.end())
557 {
558 Log (LOG_PLAIN) << "Dropped selected mirror: " << n->url
559 << endLog;
560 dropped_site_list.push_back (*j);
561 if (cache_warn_urls.size())
562 cache_warn_urls += "\r\n";
563 cache_warn_urls += i->url;
564 }
565 }
566 }
567 if (cache_warn_urls.size())
568 {
569 if (unattended_mode)
570 return CACHE_ACCEPT_WARN;
571 return DialogBox (hinstance, MAKEINTRESOURCE (IDD_DROPPED), h,
572 drop_proc);
573 }
574 return CACHE_ACCEPT_NOWARN;
575 }
576
577 void write_cache_list (io_stream *f, const SiteList& theSites)
578 {
579 for (SiteList::const_iterator n = theSites.begin ();
580 n != theSites.end (); ++n)
581 if (n->from_mirrors_lst)
582 *f << (n->url + ";" + n->servername + ";" + n->area + ";"
583 + n->location);
584 }
585
586 void save_cache_file (int cache_action)
587 {
588 io_stream *f = UserSettings::instance().open ("mirrors-lst");
589 if (f)
590 {
591 write_cache_list (f, all_site_list);
592 if (cache_action == CACHE_ACCEPT_WARN)
593 {
594 Log (LOG_PLAIN) << "Adding dropped mirrors to cache to warn again."
595 << endLog;
596 *f << "# Following mirrors re-added by setup.exe to warn again about dropped urls.";
597 write_cache_list (f, dropped_site_list);
598 }
599 delete f;
600 }
601 }
602
603 bool SitePage::Create ()
604 {
605 return PropertyPage::Create (IDD_SITE);
606 }
607
608 void
609 SitePage::OnInit ()
610 {
611 AddTooltip (IDC_EDIT_USER_URL, IDS_USER_URL_TOOLTIP);
612 }
613
614 long
615 SitePage::OnNext ()
616 {
617 HWND h = GetHWND ();
618 int cache_action = CACHE_ACCEPT_NOWARN;
619
620 save_dialog (h);
621
622 if (cache_is_usable && !(cache_action = check_dropped_mirrors (h)))
623 return -1;
624
625 if (cache_needs_writing)
626 save_cache_file (cache_action);
627
628 // Log all the selected URLs from the list.
629 for (SiteList::const_iterator n = site_list.begin ();
630 n != site_list.end (); ++n)
631 Log (LOG_PLAIN) << "site: " << n->url << endLog;
632
633 Progress.SetActivateTask (WM_APP_START_SETUP_INI_DOWNLOAD);
634 return IDD_INSTATUS;
635
636 return 0;
637 }
638
639 long
640 SitePage::OnBack ()
641 {
642 HWND h = GetHWND ();
643
644 save_dialog (h);
645
646 // Go back to the net connection type page
647 return 0;
648 }
649
650 void
651 SitePage::OnActivate ()
652 {
653 // Fill the list box with all known sites.
654 PopulateListBox ();
655
656 // Load the user URL box with nothing - it is in the list already.
657 eset (GetHWND (), IDC_EDIT_USER_URL, "");
658
659 // Get the enabled/disabled states of the controls set accordingly.
660 CheckControlsAndDisableAccordingly ();
661 }
662
663 long
664 SitePage::OnUnattended ()
665 {
666 if (SendMessage (GetDlgItem (IDC_URL_LIST), LB_GETSELCOUNT, 0, 0) > 0)
667 return OnNext ();
668
669 Log (LOG_PLAIN) << "No package repository site(s) specified" << endLog;
670 return -2;
671 }
672
673 void
674 SitePage::CheckControlsAndDisableAccordingly () const
675 {
676 DWORD ButtonFlags = PSWIZB_BACK;
677
678 // Check that at least one download site is selected.
679 if (SendMessage (GetDlgItem (IDC_URL_LIST), LB_GETSELCOUNT, 0, 0) > 0)
680 {
681 // At least one site selected, enable "Next".
682 ButtonFlags |= PSWIZB_NEXT;
683 }
684 GetOwner ()->SetButtons (ButtonFlags);
685 }
686
687 void
688 SitePage::PopulateListBox ()
689 {
690 std::vector <int> sel_indicies;
691 HWND listbox = GetDlgItem (IDC_URL_LIST);
692
693 // Populate the list box with the URLs.
694 SendMessage (listbox, LB_RESETCONTENT, 0, 0);
695 for (SiteList::const_iterator i = all_site_list.begin ();
696 i != all_site_list.end (); ++i)
697 {
698 // If selected, always show
699 SiteList::iterator f = find (site_list.begin(), site_list.end(), *i);
700 if (f == site_list.end())
701 {
702 // Otherwise, hide redundant legacy URLs:
703 if (i->noshow)
704 continue;
705 }
706
707 int j = SendMessage (listbox, LB_ADDSTRING, 0,
708 (LPARAM) i->displayed_url.c_str());
709 // Set the ListBox item data to the index into all_site_list
710 SendMessage (listbox, LB_SETITEMDATA, j, (i - all_site_list.begin()));
711
712 // For every selected item, remember the index
713 if (f != site_list.end())
714 {
715 sel_indicies.push_back(j);
716 }
717 }
718
719 // Select the selected ones.
720 for (std::vector <int>::const_iterator n = sel_indicies.begin ();
721 n != sel_indicies.end (); ++n)
722 {
723 int index = *n;
724 // Highlight the selected item
725 SendMessage (listbox, LB_SELITEMRANGE, TRUE, (index << 16) | index);
726 // Make sure it's fully visible
727 SendMessage (listbox, LB_SETCARETINDEX, index, FALSE);
728 }
729 }
730
731 bool SitePage::OnMessageCmd (int id, HWND hwndctl, UINT code)
732 {
733 switch (id)
734 {
735 case IDC_EDIT_USER_URL:
736 {
737 // Set the default pushbutton to ADD if the user is entering text.
738 if (code == EN_CHANGE)
739 SendMessage (GetHWND (), DM_SETDEFID, (WPARAM) IDC_BUTTON_ADD_URL, 0);
740 break;
741 }
742 case IDC_URL_LIST:
743 {
744 if (code == LBN_SELCHANGE)
745 {
746 CheckControlsAndDisableAccordingly ();
747 save_dialog (GetHWND ());
748 }
749 break;
750 }
751 case IDC_BUTTON_ADD_URL:
752 {
753 if (code == BN_CLICKED)
754 {
755 // User pushed the Add button.
756 std::string other_url = egetString (GetHWND (), IDC_EDIT_USER_URL);
757 if (other_url.size())
758 {
759 site_list_type newsite (other_url, "", "", "", false);
760 SiteList::iterator i = find (all_site_list.begin(),
761 all_site_list.end(), newsite);
762 if (i == all_site_list.end())
763 {
764 all_site_list.push_back (newsite);
765 Log (LOG_BABBLE) << "Adding site: " << other_url << endLog;
766 site_list.push_back (newsite);
767 }
768 else
769 site_list.push_back (*i);
770
771 // Update the list box.
772 PopulateListBox ();
773 // And allow the user to continue
774 CheckControlsAndDisableAccordingly ();
775 eset (GetHWND (), IDC_EDIT_USER_URL, "");
776 }
777 }
778 break;
779 }
780 default:
781 // Wasn't recognized or handled.
782 return false;
783 }
784
785 // Was handled since we never got to default above.
786 return true;
787 }
This page took 0.073136 seconds and 6 git commands to generate.