void	ChooserDialog::PopulateServicesList( void )
{
	ServiceTypeVector::iterator		i;
	CString							type;
	CString							desc;
	std::string						tmp;
	
	// Add a fixed list of known services.
	
	if( mServiceTypes.empty() )
	{
		const KnownServiceEntry *		service;
		
		for( service = kKnownServiceTable; service->serviceType; ++service )
		{
			ServiceTypeInfo		info;
			
			info.serviceType 	= service->serviceType;
			info.description 	= service->description;
			info.urlScheme 		= service->urlScheme;
			mServiceTypes.push_back( info );
		}
	}
	
	// Add each service to the list.
	
	for( i = mServiceTypes.begin(); i != mServiceTypes.end(); ++i )
	{
		const char *		p;
		const char *		q;
		
		p  = ( *i ).serviceType.c_str();
		if( *p == '_' ) ++p;							// Skip leading '_'.
		q  = strchr( p, '.' );							// Find first '.'.
		if( q )	tmp.assign( p, (size_t)( q - p ) );		// Use only up to the first '.'.
		else	tmp.assign( p );						// No '.' so use the entire string.
		UTF8StringToStringObject( tmp.c_str(), type );
		UTF8StringToStringObject( ( *i ).description.c_str(), desc );
		
		int		n;
		
		n = mServiceList.GetItemCount();
		mServiceList.InsertItem( n, type );
		mServiceList.SetItemText( n, 1, desc );
	}
	
	// Select the first service type by default.
	
	if( !mServiceTypes.empty() )
	{
		mServiceList.SetItemState( 0, LVIS_SELECTED | LVIS_FOCUSED, LVIS_SELECTED | LVIS_FOCUSED );
	}
}
OSStatus
CConfigPropertySheet::DecodeDomainName( const char * raw, CString & decoded )
{
	char nextLabel[128] = "\0";
	char decodedDomainString[kDNSServiceMaxDomainName];
    char * buffer = (char *) raw;
    int labels = 0, i;
    char text[64];
	const char *label[128];
	OSStatus	err;
    
	// Initialize

	decodedDomainString[0] = '\0';

    // Count the labels

	while ( *buffer )
	{
		label[labels++] = buffer;
		buffer = (char *) GetNextLabel(buffer, text);
    }
        
    buffer = (char*) raw;

    for (i = 0; i < labels; i++)
	{
		buffer = (char *)GetNextLabel(buffer, nextLabel);
        strcat(decodedDomainString, nextLabel);
        strcat(decodedDomainString, ".");
    }
    
    // Remove trailing dot from domain name.
    
	decodedDomainString[ strlen( decodedDomainString ) - 1 ] = '\0';

	// Convert to Unicode

	err = UTF8StringToStringObject( decodedDomainString, decoded );

	return err;
}
Exemplo n.º 3
0
// Printer::EventHandler implementation
OSStatus
CSecondPage::OnAddPrinter(
				uint32_t		inInterfaceIndex,
				const char *	inName,	
				const char *	inType,	
				const char *	inDomain,
				bool			moreComing)
{
	Printer						*	printer;
	Service						*	service;
	CPrinterSetupWizardSheet	*	psheet;
	DWORD							printerNameCount;
	bool							newPrinter = false;
	OSStatus						err = kNoErr;

	check( IsWindow( m_hWnd ) );

	m_browseList.SetRedraw(FALSE);

	psheet = reinterpret_cast<CPrinterSetupWizardSheet*>(GetParent());
	require_quiet( psheet, exit );

	printer = Lookup( inName );

	if (printer == NULL)
	{
		try
		{
			printer = new Printer;
		}
		catch (...)
		{
			printer = NULL;
		}

		require_action( printer, exit, err = E_OUTOFMEMORY );

		printer->window		=	this;
		printer->name		=	inName;
		
		err = UTF8StringToStringObject(inName, printer->displayName);
		check_noerr( err );
		printer->actualName	=	printer->displayName;
		printer->installed	=	false;
		printer->deflt		=	false;
		printer->resolving	=	0;

		//
		// Compare this name against printers that are already installed
		// to avoid name clashes.  Rename as necessary
		// to come up with a unique name.
		//
		printerNameCount = 2;

		for (;;)
		{
			PrinterNameMap::iterator it;

			it = m_printerNames.find(printer->actualName);

			if (it != m_printerNames.end())
			{
				printer->actualName.Format(L"%s (%d)", printer->displayName, printerNameCount);
			}
			else
			{
				break;
			}

			printerNameCount++;
		}

		newPrinter = true;
	}

	check( printer );

	service = printer->LookupService( inType );

	if ( service != NULL )
	{
		service->refs++;
	}
	else
	{
		try
		{
			service = new Service;
		}
		catch (...)
		{
			service = NULL;
		}

		require_action( service, exit, err = E_OUTOFMEMORY );
		
		service->printer	=	printer;
		service->ifi		=	inInterfaceIndex;
		service->type		=	inType;
		service->domain		=	inDomain;
		service->qtotal		=	1;
		service->refs		=	1;
		service->serviceRef	=	NULL;

		printer->services.push_back( service );

		//
		// if the printer is selected, then we'll want to start a
		// resolve on this guy
		//

		if ( m_selected == printer )
		{
			StartResolve( service );
		}
	}
	
	if ( newPrinter )
	{
		printer->item = m_browseList.InsertItem(printer->displayName);

		m_browseList.SetItemData( printer->item, (DWORD_PTR) printer );

		m_printers.push_back( printer );
		
		m_browseList.SortChildren(TVI_ROOT);
		
		if ( printer->name == m_selectedName )
		{
			m_browseList.SelectItem( printer->item );
		}

		//
		// if the searching item is still in the list
		// get rid of it
		//
		// note that order is important here.  Insert the printer
		// item before removing the placeholder so we always have
		// an item in the list to avoid experiencing the bug
		// in Microsoft's implementation of CTreeCtrl
		//
		if (m_emptyListItem != NULL)
		{
			m_browseList.DeleteItem(m_emptyListItem);
			m_emptyListItem = NULL;
			m_browseList.EnableWindow(TRUE);
		}
	}

exit:

	if (!moreComing)
	{
		m_browseList.SetRedraw(TRUE);
		m_browseList.Invalidate();
	}

	return err;
}
Exemplo n.º 4
0
void DNSSD_API
CSecondPage::OnResolve(
				DNSServiceRef			inRef,
				DNSServiceFlags			inFlags,
				uint32_t				inInterfaceIndex,
				DNSServiceErrorType		inErrorCode,
				const char *			inFullName,	
				const char *			inHostName, 
				uint16_t 				inPort,
				uint16_t 				inTXTSize,
				const char *			inTXT,
				void *					inContext )
{
	DEBUG_UNUSED(inFullName);
	DEBUG_UNUSED(inInterfaceIndex);
	DEBUG_UNUSED(inFlags);
	DEBUG_UNUSED(inRef);

	CSecondPage	*	self;
	Service		*	service;
	Queue		*	q;
	bool			qtotalDefined = false;
	uint32_t		qpriority = kDefaultPriority;
	CString			qname;
	int				idx;
	OSStatus		err;

	require_noerr( inErrorCode, exit );

	service = reinterpret_cast<Service*>( inContext );
	require_quiet( service, exit);

	check( service->refs != 0 );

	self = service->printer->window;
	require_quiet( self, exit );

	err = self->StopOperation( service->serviceRef );
	require_noerr( err, exit );
	
	//
	// hold on to the hostname...
	//
	err = UTF8StringToStringObject( inHostName, service->hostname );
	require_noerr( err, exit );

	//
	// <rdar://problem/3739200> remove the trailing dot on hostname
	//
	idx = service->hostname.ReverseFind('.');

	if ((idx > 1) && ((service->hostname.GetLength() - 1) == idx))
	{
		service->hostname.Delete(idx, 1);
	}

	//
	// hold on to the port
	//
	service->portNumber = ntohs(inPort);

	//
	// parse the text record.
	//

	err = self->ParseTextRecord( service, inTXTSize, inTXT, qtotalDefined, qname, qpriority );
	require_noerr( err, exit );

	if ( service->qtotal == 1 )
	{	
		//
		// create a new queue
		//
		try
		{
			q = new Queue;
		}
		catch (...)
		{
			q = NULL;
		}

		require_action( q, exit, err = E_OUTOFMEMORY );

		if ( qtotalDefined )
		{
			q->name = qname;
		}

		q->priority = qpriority;
		
		service->queues.push_back( q );

		//
		// we've completely resolved this service
		//

		self->OnResolveService( service );
	}
	else
	{
		//
		// if qtotal is more than 1, then we need to get additional
		// text records.  if not, then this service is considered
		// resolved
		//

		err = DNSServiceQueryRecord(&service->serviceRef, 0, inInterfaceIndex, inFullName, kDNSServiceType_TXT, kDNSServiceClass_IN, OnQuery, (void*) service );
		require_noerr( err, exit );

		err = self->StartOperation( service->serviceRef );
		require_noerr( err, exit );
	}

exit:

	return;
}
Exemplo n.º 5
0
OSStatus
CSecondPage::ParseTextRecord( Service * service, uint16_t inTXTSize, const char * inTXT, bool & qtotalDefined, CString & qname, uint32_t & qpriority )
{
	bool		rpOnly = true;
	OSStatus	err = kNoErr;
	
	while (inTXTSize)
	{
		char buf[256];

		unsigned char num = *inTXT;
		check( (int) num < inTXTSize );

		if ( num )
		{
			memset(buf, 0, sizeof(buf));
			memcpy(buf, inTXT + 1, num);

			CString elem;

			err = UTF8StringToStringObject( buf, elem );
			require_noerr( err, exit );

			int curPos = 0;

			CString key = elem.Tokenize(L"=", curPos);
			CString val = elem.Tokenize(L"=", curPos);

			key.MakeLower();

			if ( key == L"rp" )
			{
				qname = val;
			}
			else
			{
				rpOnly = false;

				if ((key == L"usb_mfg") || (key == L"usb_manufacturer"))
				{
					service->usb_MFG = val;
				}
				else if ((key == L"usb_mdl") || (key == L"usb_model"))
				{
					service->usb_MDL = val;
				}
				else if (key == L"ty")
				{
					service->description = val;
				}
				else if (key == L"product")
				{
					service->product = val;
				}
				else if (key == L"note")
				{
					service->location = val;
				}
				else if (key == L"qtotal")
				{
					service->qtotal = (unsigned short) _ttoi((LPCTSTR) val);
					qtotalDefined = true;
				}
				else if (key == L"priority")
				{
					qpriority = _ttoi((LPCTSTR) val);
				}
			}
		}

		inTXTSize -= (num + 1);
		inTXT += (num + 1);
	}

exit:

	if ( rpOnly )
	{
		qtotalDefined = true;
	}

	return err;
}
Exemplo n.º 6
0
OSStatus
CPrinterSetupWizardSheet::ParseTextRecord( Service * service, Queue * q, uint16_t inTXTSize, const char * inTXT )
{
	check( service );
	check( q );

	// <rdar://problem/3946587> Use TXTRecord APIs declared in dns_sd.h
	
	bool			qtotalDefined	= false;
	const void	*	val;
	char			buf[256];
	uint8_t			len;
	OSStatus		err				= kNoErr;

	// <rdar://problem/3987680> Default to queue "lp"

	q->name = L"lp";

	// <rdar://problem/4003710> Default pdl key to be "application/postscript"

	q->pdl = L"application/postscript";

	if ( ( val = TXTRecordGetValuePtr( inTXTSize, inTXT, "rp", &len ) ) != NULL )
	{
		// Stringize val ( doesn't have trailing '\0' yet )

		memcpy( buf, val, len );
		buf[len] = '\0';

		err = UTF8StringToStringObject( buf, q->name );
		require_noerr( err, exit );
	}
	
	if ( ( val = TXTRecordGetValuePtr( inTXTSize, inTXT, "pdl", &len ) ) != NULL )
	{
		// Stringize val ( doesn't have trailing '\0' yet )

		memcpy( buf, val, len );
		buf[len] = '\0';

		err = UTF8StringToStringObject( buf, q->pdl );
		require_noerr( err, exit );
	}
	
	if ( ( ( val = TXTRecordGetValuePtr( inTXTSize, inTXT, "usb_mfg", &len ) ) != NULL ) ||
	     ( ( val = TXTRecordGetValuePtr( inTXTSize, inTXT, "usb_manufacturer", &len ) ) != NULL ) )
	{
		// Stringize val ( doesn't have trailing '\0' yet )

		memcpy( buf, val, len );
		buf[len] = '\0';

		err = UTF8StringToStringObject( buf, q->usb_MFG );
		require_noerr( err, exit );
	}
	
	if ( ( ( val = TXTRecordGetValuePtr( inTXTSize, inTXT, "usb_mdl", &len ) ) != NULL ) ||
	     ( ( val = TXTRecordGetValuePtr( inTXTSize, inTXT, "usb_model", &len ) ) != NULL ) )
	{
		// Stringize val ( doesn't have trailing '\0' yet )

		memcpy( buf, val, len );
		buf[len] = '\0';

		err = UTF8StringToStringObject( buf, q->usb_MDL );
		require_noerr( err, exit );
	}

	if ( ( val = TXTRecordGetValuePtr( inTXTSize, inTXT, "ty", &len ) ) != NULL )
	{
		// Stringize val ( doesn't have trailing '\0' yet )

		memcpy( buf, val, len );
		buf[len] = '\0';

		err = UTF8StringToStringObject( buf, q->description );
		require_noerr( err, exit );
	}
		
	if ( ( val = TXTRecordGetValuePtr( inTXTSize, inTXT, "product", &len ) ) != NULL )
	{
		// Stringize val ( doesn't have trailing '\0' yet )

		memcpy( buf, val, len );
		buf[len] = '\0';

		err = UTF8StringToStringObject( buf, q->product );
		require_noerr( err, exit );
	}

	if ( ( val = TXTRecordGetValuePtr( inTXTSize, inTXT, "note", &len ) ) != NULL )
	{
		// Stringize val ( doesn't have trailing '\0' yet )

		memcpy( buf, val, len );
		buf[len] = '\0';

		err = UTF8StringToStringObject( buf, q->location );
		require_noerr( err, exit );
	}

	if ( ( val = TXTRecordGetValuePtr( inTXTSize, inTXT, "qtotal", &len ) ) != NULL )
	{
		// Stringize val ( doesn't have trailing '\0' yet )

		memcpy( buf, val, len );
		buf[len] = '\0';

		service->qtotal = (unsigned short) atoi( buf );
		qtotalDefined = true;
	}

	if ( ( val = TXTRecordGetValuePtr( inTXTSize, inTXT, "priority", &len ) ) != NULL )
	{
		// Stringize val ( doesn't have trailing '\0' yet )

		memcpy( buf, val, len );
		buf[len] = '\0';

		q->priority = atoi( buf );
	}

	// <rdar://problem/4124524> Was this printer discovered via OS X Printer Sharing?

	if ( TXTRecordContainsKey( inTXTSize, inTXT, "printer-state" ) || TXTRecordContainsKey( inTXTSize, inTXT, "printer-type" ) )
	{
		service->printer->isSharedFromOSX = true;
	}

exit:

	// The following code is to fix a problem with older HP 
	// printers that don't include "qtotal" in their text
	// record.  We'll check to see if the q->name is "TEXT"
	// and if so, we're going to modify it to be "lp" so
	// that we don't use the wrong queue

	if ( !err && !qtotalDefined && ( q->name == L"TEXT" ) )
	{
		q->name = "lp";
	}

	return err;
}
Exemplo n.º 7
0
Printer*
CPrinterSetupWizardSheet::OnAddPrinter(
								uint32_t 		inInterfaceIndex,
								const char *	inName,	
								const char *	inType,	
								const char *	inDomain,
								bool			moreComing)
{
	Printer	*	printer = NULL;
	DWORD		printerNameCount;
	OSStatus	err;

	DEBUG_UNUSED( inInterfaceIndex );
	DEBUG_UNUSED( inType );
	DEBUG_UNUSED( inDomain );

	try
	{
		printer = new Printer;
	}
	catch (...)
	{
		printer = NULL;
	}

	require_action( printer, exit, err = E_OUTOFMEMORY );

	printer->window		=	this;
	printer->name		=	inName;
	
	err = UTF8StringToStringObject(inName, printer->displayName);
	check_noerr( err );
	printer->actualName	=	printer->displayName;
	printer->installed	=	false;
	printer->deflt		=	false;
	printer->resolving	=	0;

	// Compare this name against printers that are already installed
	// to avoid name clashes.  Rename as necessary
	// to come up with a unique name.

	printerNameCount = 2;

	for (;;)
	{
		CPrinterSetupWizardSheet::PrinterNames::iterator it;

		// <rdar://problem/4141221> Don't use find to do comparisons because we need to
		// do a case insensitive string comparison

		for ( it = m_printerNames.begin(); it != m_printerNames.end(); it++ )
		{
			if ( (*it).CompareNoCase( printer->actualName ) == 0 )
			{
				break;
			}
		}

		if (it != m_printerNames.end())
		{
			printer->actualName.Format(L"%s (%d)", printer->displayName, printerNameCount);
		}
		else
		{
			break;
		}

		printerNameCount++;
	}

	m_printers.push_back( printer );

	if ( GetActivePage() == &m_pgSecond )
	{
		m_pgSecond.OnAddPrinter( printer, moreComing );
	}

exit:

	return printer;
}
Exemplo n.º 8
0
void DNSSD_API
	ExplorerBarWindow::ResolveCallBack(
		DNSServiceRef			inRef,
		DNSServiceFlags			inFlags,
		uint32_t				inInterfaceIndex,
		DNSServiceErrorType		inErrorCode,
		const char *			inFullName,	
		const char *			inHostName, 
		uint16_t 				inPort,
		uint16_t 				inTXTSize,
		const char *			inTXT,
		void *					inContext )
{
	ExplorerBarWindow *			obj;
	ServiceHandlerEntry *		handler;
	OSStatus					err;
	
	DEBUG_UNUSED( inRef );
	DEBUG_UNUSED( inFlags );
	DEBUG_UNUSED( inErrorCode );
	DEBUG_UNUSED( inFullName );
	
	require_noerr( inErrorCode, exit );
	handler = (ServiceHandlerEntry *) inContext;
	check( handler );
	obj = handler->obj;
	check( obj );
	
	try
	{
		ResolveInfo *		resolve;
		int					idx;
		
		dlog( kDebugLevelNotice, "resolved %s on ifi %d to %s\n", inFullName, inInterfaceIndex, inHostName );
		
		// Stop resolving after the first good result.
		
		obj->StopResolve();
		
		// Post a message to the main thread so it can handle it since MFC is not thread safe.
		
		resolve = new ResolveInfo;
		require_action( resolve, exit, err = kNoMemoryErr );
		
		UTF8StringToStringObject( inHostName, resolve->host );

		// rdar://problem/3841564
		// 
		// strip trailing dot from hostname because some flavors of Windows
		// have trouble parsing it.

		idx = resolve->host.ReverseFind('.');

		if ((idx > 1) && ((resolve->host.GetLength() - 1) == idx))
		{
			resolve->host.Delete(idx, 1);
		}

		resolve->port		= ntohs( inPort );
		resolve->ifi		= inInterfaceIndex;
		resolve->handler	= handler;
		
		err = resolve->txt.SetData( inTXT, inTXTSize );
		check_noerr( err );
		
		obj->OnResolve(resolve);
	}
	catch( ... )
	{
		dlog( kDebugLevelError, "ResolveCallBack: exception thrown\n" );
	}

exit:
	return;
}
Exemplo n.º 9
0
void DNSSD_API
	ExplorerBarWindow::BrowseCallBack(
		DNSServiceRef 			inRef,
		DNSServiceFlags 		inFlags,
		uint32_t 				inInterfaceIndex,
		DNSServiceErrorType 	inErrorCode,
		const char *			inName,	
		const char *			inType,	
		const char *			inDomain,	
		void *					inContext )
{
	ServiceHandlerEntry *		obj;
	ServiceInfo *				service;
	OSStatus					err;
	
	DEBUG_UNUSED( inRef );
	
	obj		=	NULL;
	service = NULL;
	
	require_noerr( inErrorCode, exit );
	obj = reinterpret_cast < ServiceHandlerEntry * > ( inContext );
	check( obj );
	check( obj->obj );
	
	//
	// set the UI to hold off on updates
	//
	obj->obj->mTree.SetRedraw(FALSE);

	try
	{
		service = new ServiceInfo;
		require_action( service, exit, err = kNoMemoryErr );
		
		err = UTF8StringToStringObject( inName, service->displayName );
		check_noerr( err );

		service->name = strdup( inName );
		require_action( service->name, exit, err = kNoMemoryErr );
		
		service->type = strdup( inType );
		require_action( service->type, exit, err = kNoMemoryErr );
		
		service->domain = strdup( inDomain );
		require_action( service->domain, exit, err = kNoMemoryErr );
		
		service->ifi 		= inInterfaceIndex;
		service->handler	= obj;

		service->refs		= 1;
		
		if (inFlags & kDNSServiceFlagsAdd) obj->obj->OnServiceAdd   (service);
		else                               obj->obj->OnServiceRemove(service);
	
		service = NULL;
	}
	catch( ... )
	{
		dlog( kDebugLevelError, "BrowseCallBack: exception thrown\n" );
	}
	
exit:
	//
	// If no more coming, then update UI
	//
	if (obj && obj->obj && ((inFlags & kDNSServiceFlagsMoreComing) == 0))
	{
		obj->obj->mTree.SetRedraw(TRUE);
		obj->obj->mTree.Invalidate();
	}

	if( service )
	{
		delete service;
	}
}
void	ChooserDialog::UpdateInfoDisplay( void )
{
	int							selectedItem;
	std::string					name;
	CString						s;
	std::string					ip;
	std::string					ifIP;
	std::string					text;
	std::string					textNewLines;
	std::string					hostName;
	CWnd *						item;
	std::string::iterator		i;
	
	// Display the service instance if it is selected. Otherwise, clear all the info.
	
	selectedItem = mChooserList.GetNextItem( -1, LVNI_SELECTED );
	if( selectedItem >= 0 )
	{
		ServiceInstanceInfo *		p;
		
		assert( selectedItem < (int) mServiceInstances.size() );
		p = &mServiceInstances[ selectedItem ];
		
		name 		= p->name;
		ip 			= p->ip;
		ifIP 		= p->ifIP;
		text 		= p->text;
		hostName	= p->hostName;

		// Sync up the list items with the actual data (IP address may change).
		
		UTF8StringToStringObject( ip.c_str(), s );
		mChooserList.SetItemText( selectedItem, 1, s );
	}
	
	// Name
	
	item = (CWnd *) this->GetDlgItem( IDC_INFO_NAME_TEXT );
	assert( item );
	UTF8StringToStringObject( name.c_str(), s );
	item->SetWindowText( s );
	
	// IP
	
	item = (CWnd *) this->GetDlgItem( IDC_INFO_IP_TEXT );
	assert( item );
	UTF8StringToStringObject( ip.c_str(), s );
	item->SetWindowText( s );
	
	// Interface
	
	item = (CWnd *) this->GetDlgItem( IDC_INFO_INTERFACE_TEXT );
	assert( item );
	UTF8StringToStringObject( ifIP.c_str(), s );
	item->SetWindowText( s );
	

	item = (CWnd *) this->GetDlgItem( IDC_INFO_HOST_NAME_TEXT );
	assert( item );
	UTF8StringToStringObject( hostName.c_str(), s );
	item->SetWindowText( s );

	// Text
	
	item = (CWnd *) this->GetDlgItem( IDC_INFO_TEXT_TEXT );
	assert( item );
	for( i = text.begin(); i != text.end(); ++i )
	{
		if( *i == '\1' )
		{
			textNewLines += "\r\n";
		}
		else
		{
			textNewLines += *i;
		}
	}
	UTF8StringToStringObject( textNewLines.c_str(), s );
	item->SetWindowText( s );
}
LONG	ChooserDialog::OnResolve( WPARAM inWParam, LPARAM inLParam )
{
	ServiceInstanceInfo *						p;
	std::auto_ptr < ServiceInstanceInfo >		pAutoPtr;
	int											selectedType;
	int											n;
	int											i;
	bool										found;
	
	UNUSED_ALWAYS( inWParam );
	
	assert( inLParam );
	p = reinterpret_cast <ServiceInstanceInfo *> ( inLParam );
	pAutoPtr.reset( p );
	
	// Make sure it is for an item of the correct type. This handles any resolves that may have been queued up.
	
	selectedType = mServiceList.GetNextItem( -1, LVNI_SELECTED );
	assert( selectedType >= 0 );
	if( selectedType >= 0 )
	{
		assert( selectedType <= (int) mServiceTypes.size() );
		if( p->type != mServiceTypes[ selectedType ].serviceType )
		{
			goto exit;
		}
	}
	
	// Search to see if we know about this service instance. If so, update its info. Otherwise, add it to the list.
	
	found = false;
	n = (int) mServiceInstances.size();
	for( i = 0; i < n; ++i )
	{
		ServiceInstanceInfo *		q;
		
		// If the name, type, domain, and interface matches, treat it as the same service instance.
				
		q = &mServiceInstances[ i ];
		if( ( p->name 	== q->name ) 	&& 
			( p->type 	== q->type ) 	&& 
			( p->domain	== q->domain ) 	&& 
			( p->ifIP 	== q->ifIP ) )
		{
			found = true;
			break;
		}
	}
	if( found )
	{
		mServiceInstances[ i ] = *p;
	}
	else
	{
		CString		s;
		
		mServiceInstances.push_back( *p );
		UTF8StringToStringObject( p->name.c_str(), s );
		mChooserList.InsertItem( n, s );
		
		UTF8StringToStringObject( p->ip.c_str(), s );
		mChooserList.SetItemText( n, 1, s );
		
		// If this is the only item, select it.
		
		if( n == 0 )
		{
			mChooserList.SetItemState( n, LVIS_SELECTED | LVIS_FOCUSED, LVIS_SELECTED | LVIS_FOCUSED );
		}
	}
	UpdateInfoDisplay();

exit:
	return( 0 );
}