2016-06-14 16:17:44 +00:00
|
|
|
package goupnp
|
|
|
|
|
|
|
|
import (
|
2023-05-19 20:23:55 +00:00
|
|
|
"context"
|
2016-06-14 16:17:44 +00:00
|
|
|
"fmt"
|
2022-08-19 16:34:07 +00:00
|
|
|
"net"
|
2016-06-14 16:17:44 +00:00
|
|
|
"net/url"
|
|
|
|
|
|
|
|
"github.com/huin/goupnp/soap"
|
|
|
|
)
|
|
|
|
|
|
|
|
// ServiceClient is a SOAP client, root device and the service for the SOAP
|
|
|
|
// client rolled into one value. The root device, location, and service are
|
|
|
|
// intended to be informational. Location can be used to later recreate a
|
|
|
|
// ServiceClient with NewServiceClientByURL if the service is still present;
|
|
|
|
// bypassing the discovery process.
|
|
|
|
type ServiceClient struct {
|
|
|
|
SOAPClient *soap.SOAPClient
|
|
|
|
RootDevice *RootDevice
|
|
|
|
Location *url.URL
|
|
|
|
Service *Service
|
2022-08-19 16:34:07 +00:00
|
|
|
localAddr net.IP
|
2016-06-14 16:17:44 +00:00
|
|
|
}
|
|
|
|
|
2023-05-19 20:23:55 +00:00
|
|
|
// NewServiceClientsCtx discovers services, and returns clients for them. err will
|
2016-06-14 16:17:44 +00:00
|
|
|
// report any error with the discovery process (blocking any device/service
|
|
|
|
// discovery), errors reports errors on a per-root-device basis.
|
2023-05-19 20:23:55 +00:00
|
|
|
func NewServiceClientsCtx(ctx context.Context, searchTarget string) (clients []ServiceClient, errors []error, err error) {
|
2016-06-14 16:17:44 +00:00
|
|
|
var maybeRootDevices []MaybeRootDevice
|
2023-05-19 20:23:55 +00:00
|
|
|
if maybeRootDevices, err = DiscoverDevicesCtx(ctx, searchTarget); err != nil {
|
2016-06-14 16:17:44 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
clients = make([]ServiceClient, 0, len(maybeRootDevices))
|
|
|
|
|
|
|
|
for _, maybeRootDevice := range maybeRootDevices {
|
|
|
|
if maybeRootDevice.Err != nil {
|
|
|
|
errors = append(errors, maybeRootDevice.Err)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2022-08-19 16:34:07 +00:00
|
|
|
deviceClients, err := newServiceClientsFromRootDevice(maybeRootDevice.Root, maybeRootDevice.Location, searchTarget, maybeRootDevice.LocalAddr)
|
2016-06-14 16:17:44 +00:00
|
|
|
if err != nil {
|
|
|
|
errors = append(errors, err)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
clients = append(clients, deviceClients...)
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2023-05-19 20:23:55 +00:00
|
|
|
// NewServiceClients is the legacy version of NewServiceClientsCtx, but uses
|
|
|
|
// context.Background() as the context.
|
|
|
|
func NewServiceClients(searchTarget string) (clients []ServiceClient, errors []error, err error) {
|
|
|
|
return NewServiceClientsCtx(context.Background(), searchTarget)
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewServiceClientsByURLCtx creates client(s) for the given service URN, for a
|
2016-06-14 16:17:44 +00:00
|
|
|
// root device at the given URL.
|
2023-05-19 20:23:55 +00:00
|
|
|
func NewServiceClientsByURLCtx(ctx context.Context, loc *url.URL, searchTarget string) ([]ServiceClient, error) {
|
|
|
|
rootDevice, err := DeviceByURLCtx(ctx, loc)
|
2016-06-14 16:17:44 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return NewServiceClientsFromRootDevice(rootDevice, loc, searchTarget)
|
|
|
|
}
|
|
|
|
|
2023-05-19 20:23:55 +00:00
|
|
|
// NewServiceClientsByURL is the legacy version of NewServiceClientsByURLCtx, but uses
|
|
|
|
// context.Background() as the context.
|
|
|
|
func NewServiceClientsByURL(loc *url.URL, searchTarget string) ([]ServiceClient, error) {
|
|
|
|
return NewServiceClientsByURLCtx(context.Background(), loc, searchTarget)
|
|
|
|
}
|
|
|
|
|
2016-06-14 16:17:44 +00:00
|
|
|
// NewServiceClientsFromDevice creates client(s) for the given service URN, in
|
|
|
|
// a given root device. The loc parameter is simply assigned to the
|
|
|
|
// Location attribute of the returned ServiceClient(s).
|
|
|
|
func NewServiceClientsFromRootDevice(rootDevice *RootDevice, loc *url.URL, searchTarget string) ([]ServiceClient, error) {
|
2022-08-19 16:34:07 +00:00
|
|
|
return newServiceClientsFromRootDevice(rootDevice, loc, searchTarget, nil)
|
|
|
|
}
|
|
|
|
|
|
|
|
func newServiceClientsFromRootDevice(
|
|
|
|
rootDevice *RootDevice,
|
|
|
|
loc *url.URL,
|
|
|
|
searchTarget string,
|
|
|
|
lAddr net.IP,
|
|
|
|
) ([]ServiceClient, error) {
|
2016-06-14 16:17:44 +00:00
|
|
|
device := &rootDevice.Device
|
|
|
|
srvs := device.FindService(searchTarget)
|
|
|
|
if len(srvs) == 0 {
|
|
|
|
return nil, fmt.Errorf("goupnp: service %q not found within device %q (UDN=%q)",
|
|
|
|
searchTarget, device.FriendlyName, device.UDN)
|
|
|
|
}
|
|
|
|
|
|
|
|
clients := make([]ServiceClient, 0, len(srvs))
|
|
|
|
for _, srv := range srvs {
|
|
|
|
clients = append(clients, ServiceClient{
|
|
|
|
SOAPClient: srv.NewSOAPClient(),
|
|
|
|
RootDevice: rootDevice,
|
|
|
|
Location: loc,
|
|
|
|
Service: srv,
|
2022-08-19 16:34:07 +00:00
|
|
|
localAddr: lAddr,
|
2016-06-14 16:17:44 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
return clients, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetServiceClient returns the ServiceClient itself. This is provided so that the
|
|
|
|
// service client attributes can be accessed via an interface method on a
|
|
|
|
// wrapping type.
|
|
|
|
func (client *ServiceClient) GetServiceClient() *ServiceClient {
|
|
|
|
return client
|
|
|
|
}
|
2022-08-19 16:34:07 +00:00
|
|
|
|
|
|
|
// LocalAddr returns the address from which the device was discovered (if known - otherwise empty).
|
|
|
|
func (client *ServiceClient) LocalAddr() net.IP {
|
|
|
|
return client.localAddr
|
|
|
|
}
|