1# Copyright 2009 Google Inc. All Rights Reserved. 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15 16"""Cocoa frontend implementation for namebench.""" 17 18__author__ = 'tstromberg@google.com (Thomas Stromberg)' 19 20 21import os 22import sys 23import traceback 24from Foundation import * 25from AppKit import * 26from objc import IBAction, IBOutlet 27 28from libnamebench import addr_util 29from libnamebench import base_ui 30from libnamebench import config 31from libnamebench import conn_quality 32from libnamebench import nameserver_list 33from libnamebench import util 34from libnamebench import version 35 36# How much room do we have in the UI for status messages? 37MAX_STATUS_LENGTH = 68 38 39 40class controller(NSWindowController, base_ui.BaseUI): 41 """Controller class associated with the main window.""" 42 nameserver_form = IBOutlet() 43 include_global = IBOutlet() 44 include_regional = IBOutlet() 45 include_censorship_checks = IBOutlet() 46 data_source = IBOutlet() 47 health_performance = IBOutlet() 48 enable_sharing = IBOutlet() 49 location = IBOutlet() 50 query_count = IBOutlet() 51 run_count = IBOutlet() 52 status = IBOutlet() 53 spinner = IBOutlet() 54 button = IBOutlet() 55 56 def awakeFromNib(self): 57 """Initializes our class, called automatically by Cocoa.""" 58 self.SetupDataStructures() 59 self.resource_dir = os.path.join(os.getcwd(), 'namebench.app', 'Contents', 'Resources') 60 61 conf_file = util.FindDataFile('config/namebench.cfg') 62 (self.options, self.supplied_ns, self.global_ns, self.regional_ns) = config.GetConfiguration(filename=conf_file) 63 # TODO(tstromberg): Consider moving this into a thread for faster loading. 64 self.UpdateStatus('Discovering sources') 65 self.LoadDataSources() 66 self.UpdateStatus('Discovering location') 67 self.DiscoverLocation() 68 self.UpdateStatus('Populating Form...') 69 self.setFormDefaults() 70 self.UpdateStatus('namebench %s is ready!' % version.VERSION) 71 72 @IBAction 73 def startJob_(self, sender): 74 """Trigger for the 'Start Benchmark' button, starts benchmark thread.""" 75 self.ProcessForm() 76 self.UpdateStatus('Starting benchmark thread') 77 t = NSThread.alloc().initWithTarget_selector_object_(self, self.benchmarkThread, None) 78 t.start() 79 80 def UpdateStatus(self, message, count=None, total=None, error=False, debug=False): 81 """Update the status message at the bottom of the window.""" 82 if error: 83 return self.displayError(message, error) 84 if total: 85 state = '%s [%s/%s]' % (message, count, total) 86 elif count: 87 state = '%s%s' % (message, '.' * count) 88 else: 89 state = message 90 91 state = state.replace('%', '%%') 92 print state 93 NSLog(state) 94 95 self.status.setStringValue_(state[0:MAX_STATUS_LENGTH]) 96 97 def ProcessForm(self): 98 """Parse the form fields and populate class variables.""" 99 self.UpdateStatus('Processing form inputs') 100 self.preferred = self.supplied_ns 101 self.include_internal = False 102 103 if not int(self.include_global.stringValue()): 104 self.UpdateStatus('Not using global') 105 self.global_ns = [] 106 else: 107 self.preferred.extend(self.global_ns) 108 if not int(self.include_regional.stringValue()): 109 self.UpdateStatus('Not using regional') 110 self.regional_ns = [] 111 112 if int(self.enable_sharing.stringValue()): 113 self.options.upload_results = True 114 115 if int(self.include_censorship_checks.stringValue()): 116 self.options.enable_censorship_checks = True 117 118 print self.health_performance.titleOfSelectedItem() 119 if 'Slow' in self.health_performance.titleOfSelectedItem(): 120 self.options.health_thread_count = 10 121 122 self.options.input_source = self.data_src.ConvertSourceTitleToType(self.data_source.titleOfSelectedItem()) 123 self.UpdateStatus('Supplied servers: %s' % self.nameserver_form.stringValue()) 124 self.preferred.extend(addr_util.ExtractIPTuplesFromString(self.nameserver_form.stringValue())) 125 self.options.query_count = int(self.query_count.stringValue()) 126 127 def benchmarkThread(self): 128 """Run the benchmarks, designed to be run in a thread.""" 129 pool = NSAutoreleasePool.alloc().init() 130 self.spinner.startAnimation_(self) 131 self.button.setEnabled_(False) 132 self.UpdateStatus('Preparing benchmark') 133 try: 134 self.PrepareTestRecords() 135 self.PrepareNameServers() 136 self.PrepareBenchmark() 137 self.RunAndOpenReports() 138 except nameserver_list.OutgoingUdpInterception: 139 (exc_type, exception, tb) = sys.exc_info() 140 self.UpdateStatus('Outgoing requests were intercepted!', 141 error=str(exception)) 142 except nameserver_list.TooFewNameservers: 143 (exc_type, exception, tb) = sys.exc_info() 144 self.UpdateStatus('Too few nameservers to test', error=str(exception)) 145 except conn_quality.OfflineConnection: 146 (exc_type, exception, tb) = sys.exc_info() 147 self.UpdateStatus('The connection appears to be offline!', error=str(exception)) 148 except: 149 (exc_type, exception, tb) = sys.exc_info() 150 traceback.print_exc(tb) 151 error_msg = '\n'.join(traceback.format_tb(tb)[-4:]) 152 self.UpdateStatus('FAIL: %s' % exception, error=error_msg) 153 154 self.spinner.stopAnimation_(self) 155 self.button.setEnabled_(True) 156 # This seems weird, but recommended by http://pyobjc.sourceforge.net/documentation/pyobjc-core/intro.html 157 del pool 158 159 def displayError(self, msg, details): 160 """Display an alert drop-down message.""" 161 NSLog('ERROR: %s - %s' % (msg, details)) 162 alert = NSAlert.alloc().init() 163 alert.setMessageText_(msg) 164 alert.setInformativeText_(details) 165 buttonPressed = alert.runModal() 166 167 def setFormDefaults(self): 168 """Set up the form with sane initial values.""" 169 nameservers_string = ', '.join(nameserver_list.InternalNameServers()) 170 self.nameserver_form.setStringValue_(nameservers_string) 171 self.query_count.setStringValue_(self.options.query_count) 172 self.query_count.setStringValue_(self.options.query_count) 173 174 self.location.removeAllItems() 175 if self.country: 176 self.location.addItemWithTitle_(self.country) 177 self.location.addItemWithTitle_('(Other)') 178 else: 179 self.location.addItemWithTitle_('(automatic)') 180 181 self.health_performance.removeAllItems() 182 self.health_performance.addItemWithTitle_('Fast') 183 self.health_performance.addItemWithTitle_('Slow (unstable network)') 184 185 self.data_source.removeAllItems() 186 self.data_source.addItemsWithTitles_(self.data_src.ListSourceTitles()) 187 188