Switch to generate code with go generate.
				
					
				
			This commit is contained in:
		
							
								
								
									
										153
									
								
								cmd/goupnpdcpgen/codetemplate.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										153
									
								
								cmd/goupnpdcpgen/codetemplate.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,153 @@
 | 
			
		||||
package main
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"html/template"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var packageTmpl = template.Must(template.New("package").Parse(`{{$name := .Metadata.Name}}
 | 
			
		||||
// Client for UPnP Device Control Protocol {{.Metadata.OfficialName}}.
 | 
			
		||||
// {{if .Metadata.DocURL}}
 | 
			
		||||
// This DCP is documented in detail at: {{.Metadata.DocURL}}{{end}}
 | 
			
		||||
//
 | 
			
		||||
// Typically, use one of the New* functions to create clients for services.
 | 
			
		||||
package {{$name}}
 | 
			
		||||
 | 
			
		||||
// ***********************************************************
 | 
			
		||||
// GENERATED FILE - DO NOT EDIT BY HAND. See README.md
 | 
			
		||||
// ***********************************************************
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"net/url"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/huin/goupnp"
 | 
			
		||||
	"github.com/huin/goupnp/soap"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Hack to avoid Go complaining if time isn't used.
 | 
			
		||||
var _ time.Time
 | 
			
		||||
 | 
			
		||||
// Device URNs:
 | 
			
		||||
const ({{range .DeviceTypes}}
 | 
			
		||||
	{{.Const}} = "{{.URN}}"{{end}}
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Service URNs:
 | 
			
		||||
const ({{range .ServiceTypes}}
 | 
			
		||||
	{{.Const}} = "{{.URN}}"{{end}}
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
{{range .Services}}
 | 
			
		||||
{{$srv := .}}
 | 
			
		||||
{{$srvIdent := printf "%s%s" .Name .Version}}
 | 
			
		||||
 | 
			
		||||
// {{$srvIdent}} is a client for UPnP SOAP service with URN "{{.URN}}". See
 | 
			
		||||
// goupnp.ServiceClient, which contains RootDevice and Service attributes which
 | 
			
		||||
// are provided for informational value.
 | 
			
		||||
type {{$srvIdent}} struct {
 | 
			
		||||
	goupnp.ServiceClient
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// New{{$srvIdent}}Clients discovers instances of the service on the network,
 | 
			
		||||
// and returns clients to any that are found. errors will contain an error for
 | 
			
		||||
// any devices that replied but which could not be queried, and err will be set
 | 
			
		||||
// if the discovery process failed outright.
 | 
			
		||||
//
 | 
			
		||||
// This is a typical entry calling point into this package.
 | 
			
		||||
func New{{$srvIdent}}Clients() (clients []*{{$srvIdent}}, errors []error, err error) {
 | 
			
		||||
	var genericClients []goupnp.ServiceClient
 | 
			
		||||
	if genericClients, errors, err = goupnp.NewServiceClients({{$srv.Const}}); err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	clients = new{{$srvIdent}}ClientsFromGenericClients(genericClients)
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// New{{$srvIdent}}ClientsByURL discovers instances of the service at the given
 | 
			
		||||
// URL, and returns clients to any that are found. An error is returned if
 | 
			
		||||
// there was an error probing the service.
 | 
			
		||||
//
 | 
			
		||||
// This is a typical entry calling point into this package when reusing an
 | 
			
		||||
// previously discovered service URL.
 | 
			
		||||
func New{{$srvIdent}}ClientsByURL(loc *url.URL) ([]*{{$srvIdent}}, error) {
 | 
			
		||||
	genericClients, err := goupnp.NewServiceClientsByURL(loc, {{$srv.Const}})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	return new{{$srvIdent}}ClientsFromGenericClients(genericClients), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// New{{$srvIdent}}ClientsFromRootDevice discovers instances of the service in
 | 
			
		||||
// a given root device, and returns clients to any that are found. An error is
 | 
			
		||||
// returned if there was not at least one instance of the service within the
 | 
			
		||||
// device. The location parameter is simply assigned to the Location attribute
 | 
			
		||||
// of the wrapped ServiceClient(s).
 | 
			
		||||
//
 | 
			
		||||
// This is a typical entry calling point into this package when reusing an
 | 
			
		||||
// previously discovered root device.
 | 
			
		||||
func New{{$srvIdent}}ClientsFromRootDevice(rootDevice *goupnp.RootDevice, loc *url.URL) ([]*{{$srvIdent}}, error) {
 | 
			
		||||
	genericClients, err := goupnp.NewServiceClientsFromRootDevice(rootDevice, loc, {{$srv.Const}})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	return new{{$srvIdent}}ClientsFromGenericClients(genericClients), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func new{{$srvIdent}}ClientsFromGenericClients(genericClients []goupnp.ServiceClient) []*{{$srvIdent}} {
 | 
			
		||||
	clients := make([]*{{$srvIdent}}, len(genericClients))
 | 
			
		||||
	for i := range genericClients {
 | 
			
		||||
		clients[i] = &{{$srvIdent}}{genericClients[i]}
 | 
			
		||||
	}
 | 
			
		||||
	return clients
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
{{range .SCPD.Actions}}{{/* loops over *SCPDWithURN values */}}
 | 
			
		||||
 | 
			
		||||
{{$winargs := $srv.WrapArguments .InputArguments}}
 | 
			
		||||
{{$woutargs := $srv.WrapArguments .OutputArguments}}
 | 
			
		||||
{{if $winargs.HasDoc}}
 | 
			
		||||
//
 | 
			
		||||
// Arguments:{{range $winargs}}{{if .HasDoc}}
 | 
			
		||||
//
 | 
			
		||||
// * {{.Name}}: {{.Document}}{{end}}{{end}}{{end}}
 | 
			
		||||
{{if $woutargs.HasDoc}}
 | 
			
		||||
//
 | 
			
		||||
// Return values:{{range $woutargs}}{{if .HasDoc}}
 | 
			
		||||
//
 | 
			
		||||
// * {{.Name}}: {{.Document}}{{end}}{{end}}{{end}}
 | 
			
		||||
func (client *{{$srvIdent}}) {{.Name}}({{range $winargs -}}
 | 
			
		||||
{{.AsParameter}}, {{end -}}
 | 
			
		||||
) ({{range $woutargs -}}
 | 
			
		||||
{{.AsParameter}}, {{end}} err error) {
 | 
			
		||||
	// Request structure.
 | 
			
		||||
	request := {{if $winargs}}&{{template "argstruct" $winargs}}{{"{}"}}{{else}}{{"interface{}(nil)"}}{{end}}
 | 
			
		||||
	// BEGIN Marshal arguments into request.
 | 
			
		||||
{{range $winargs}}
 | 
			
		||||
	if request.{{.Name}}, err = {{.Marshal}}; err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}{{end}}
 | 
			
		||||
	// END Marshal arguments into request.
 | 
			
		||||
 | 
			
		||||
	// Response structure.
 | 
			
		||||
	response := {{if $woutargs}}&{{template "argstruct" $woutargs}}{{"{}"}}{{else}}{{"interface{}(nil)"}}{{end}}
 | 
			
		||||
 | 
			
		||||
	// Perform the SOAP call.
 | 
			
		||||
	if err = client.SOAPClient.PerformAction({{$srv.URNParts.Const}}, "{{.Name}}", request, response); err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// BEGIN Unmarshal arguments from response.
 | 
			
		||||
{{range $woutargs}}
 | 
			
		||||
	if {{.Name}}, err = {{.Unmarshal "response"}}; err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}{{end}}
 | 
			
		||||
	// END Unmarshal arguments from response.
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
{{end}}
 | 
			
		||||
{{end}}
 | 
			
		||||
 | 
			
		||||
{{define "argstruct"}}struct {{"{"}}
 | 
			
		||||
{{range .}}{{.Name}} string
 | 
			
		||||
{{end}}{{"}"}}{{end}}
 | 
			
		||||
`))
 | 
			
		||||
							
								
								
									
										222
									
								
								cmd/goupnpdcpgen/dcp.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										222
									
								
								cmd/goupnpdcpgen/dcp.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,222 @@
 | 
			
		||||
package main
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"archive/zip"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io"
 | 
			
		||||
	"os"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/huin/goupnp"
 | 
			
		||||
	"github.com/huin/goupnp/scpd"
 | 
			
		||||
	"github.com/huin/goutil/codegen"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// DCP collects together information about a UPnP Device Control Protocol.
 | 
			
		||||
type DCP struct {
 | 
			
		||||
	Metadata     DCPMetadata
 | 
			
		||||
	DeviceTypes  map[string]*URNParts
 | 
			
		||||
	ServiceTypes map[string]*URNParts
 | 
			
		||||
	Services     []SCPDWithURN
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func newDCP(metadata DCPMetadata) *DCP {
 | 
			
		||||
	return &DCP{
 | 
			
		||||
		Metadata:     metadata,
 | 
			
		||||
		DeviceTypes:  make(map[string]*URNParts),
 | 
			
		||||
		ServiceTypes: make(map[string]*URNParts),
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (dcp *DCP) processZipFile(filename string) error {
 | 
			
		||||
	archive, err := zip.OpenReader(filename)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("error reading zip file %q: %v", filename, err)
 | 
			
		||||
	}
 | 
			
		||||
	defer archive.Close()
 | 
			
		||||
	for _, deviceFile := range globFiles("*/device/*.xml", archive) {
 | 
			
		||||
		if err := dcp.processDeviceFile(deviceFile); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	for _, scpdFile := range globFiles("*/service/*.xml", archive) {
 | 
			
		||||
		if err := dcp.processSCPDFile(scpdFile); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (dcp *DCP) processDeviceFile(file *zip.File) error {
 | 
			
		||||
	var device goupnp.Device
 | 
			
		||||
	if err := unmarshalXmlFile(file, &device); err != nil {
 | 
			
		||||
		return fmt.Errorf("error decoding device XML from file %q: %v", file.Name, err)
 | 
			
		||||
	}
 | 
			
		||||
	var mainErr error
 | 
			
		||||
	device.VisitDevices(func(d *goupnp.Device) {
 | 
			
		||||
		t := strings.TrimSpace(d.DeviceType)
 | 
			
		||||
		if t != "" {
 | 
			
		||||
			u, err := extractURNParts(t, deviceURNPrefix)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				mainErr = err
 | 
			
		||||
			}
 | 
			
		||||
			dcp.DeviceTypes[t] = u
 | 
			
		||||
		}
 | 
			
		||||
	})
 | 
			
		||||
	device.VisitServices(func(s *goupnp.Service) {
 | 
			
		||||
		u, err := extractURNParts(s.ServiceType, serviceURNPrefix)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			mainErr = err
 | 
			
		||||
		}
 | 
			
		||||
		dcp.ServiceTypes[s.ServiceType] = u
 | 
			
		||||
	})
 | 
			
		||||
	return mainErr
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (dcp *DCP) writeCode(outFile string, useGofmt bool) error {
 | 
			
		||||
	packageFile, err := os.Create(outFile)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	var output io.WriteCloser = packageFile
 | 
			
		||||
	if useGofmt {
 | 
			
		||||
		if output, err = codegen.NewGofmtWriteCloser(output); err != nil {
 | 
			
		||||
			packageFile.Close()
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if err = packageTmpl.Execute(output, dcp); err != nil {
 | 
			
		||||
		output.Close()
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	return output.Close()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (dcp *DCP) processSCPDFile(file *zip.File) error {
 | 
			
		||||
	scpd := new(scpd.SCPD)
 | 
			
		||||
	if err := unmarshalXmlFile(file, scpd); err != nil {
 | 
			
		||||
		return fmt.Errorf("error decoding SCPD XML from file %q: %v", file.Name, err)
 | 
			
		||||
	}
 | 
			
		||||
	scpd.Clean()
 | 
			
		||||
	urnParts, err := urnPartsFromSCPDFilename(file.Name)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("could not recognize SCPD filename %q: %v", file.Name, err)
 | 
			
		||||
	}
 | 
			
		||||
	dcp.Services = append(dcp.Services, SCPDWithURN{
 | 
			
		||||
		URNParts: urnParts,
 | 
			
		||||
		SCPD:     scpd,
 | 
			
		||||
	})
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type SCPDWithURN struct {
 | 
			
		||||
	*URNParts
 | 
			
		||||
	SCPD *scpd.SCPD
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *SCPDWithURN) WrapArguments(args []*scpd.Argument) (argumentWrapperList, error) {
 | 
			
		||||
	wrappedArgs := make(argumentWrapperList, len(args))
 | 
			
		||||
	for i, arg := range args {
 | 
			
		||||
		wa, err := s.wrapArgument(arg)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
		wrappedArgs[i] = wa
 | 
			
		||||
	}
 | 
			
		||||
	return wrappedArgs, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *SCPDWithURN) wrapArgument(arg *scpd.Argument) (*argumentWrapper, error) {
 | 
			
		||||
	relVar := s.SCPD.GetStateVariable(arg.RelatedStateVariable)
 | 
			
		||||
	if relVar == nil {
 | 
			
		||||
		return nil, fmt.Errorf("no such state variable: %q, for argument %q", arg.RelatedStateVariable, arg.Name)
 | 
			
		||||
	}
 | 
			
		||||
	cnv, ok := typeConvs[relVar.DataType.Name]
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return nil, fmt.Errorf("unknown data type: %q, for state variable %q, for argument %q", relVar.DataType.Type, arg.RelatedStateVariable, arg.Name)
 | 
			
		||||
	}
 | 
			
		||||
	return &argumentWrapper{
 | 
			
		||||
		Argument: *arg,
 | 
			
		||||
		relVar:   relVar,
 | 
			
		||||
		conv:     cnv,
 | 
			
		||||
	}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type argumentWrapper struct {
 | 
			
		||||
	scpd.Argument
 | 
			
		||||
	relVar *scpd.StateVariable
 | 
			
		||||
	conv   conv
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (arg *argumentWrapper) AsParameter() string {
 | 
			
		||||
	return fmt.Sprintf("%s %s", arg.Name, arg.conv.ExtType)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (arg *argumentWrapper) HasDoc() bool {
 | 
			
		||||
	rng := arg.relVar.AllowedValueRange
 | 
			
		||||
	return ((rng != nil && (rng.Minimum != "" || rng.Maximum != "" || rng.Step != "")) ||
 | 
			
		||||
		len(arg.relVar.AllowedValues) > 0)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (arg *argumentWrapper) Document() string {
 | 
			
		||||
	relVar := arg.relVar
 | 
			
		||||
	if rng := relVar.AllowedValueRange; rng != nil {
 | 
			
		||||
		var parts []string
 | 
			
		||||
		if rng.Minimum != "" {
 | 
			
		||||
			parts = append(parts, fmt.Sprintf("minimum=%s", rng.Minimum))
 | 
			
		||||
		}
 | 
			
		||||
		if rng.Maximum != "" {
 | 
			
		||||
			parts = append(parts, fmt.Sprintf("maximum=%s", rng.Maximum))
 | 
			
		||||
		}
 | 
			
		||||
		if rng.Step != "" {
 | 
			
		||||
			parts = append(parts, fmt.Sprintf("step=%s", rng.Step))
 | 
			
		||||
		}
 | 
			
		||||
		return "allowed value range: " + strings.Join(parts, ", ")
 | 
			
		||||
	}
 | 
			
		||||
	if len(relVar.AllowedValues) != 0 {
 | 
			
		||||
		return "allowed values: " + strings.Join(relVar.AllowedValues, ", ")
 | 
			
		||||
	}
 | 
			
		||||
	return ""
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (arg *argumentWrapper) Marshal() string {
 | 
			
		||||
	return fmt.Sprintf("soap.Marshal%s(%s)", arg.conv.FuncSuffix, arg.Name)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (arg *argumentWrapper) Unmarshal(objVar string) string {
 | 
			
		||||
	return fmt.Sprintf("soap.Unmarshal%s(%s.%s)", arg.conv.FuncSuffix, objVar, arg.Name)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type argumentWrapperList []*argumentWrapper
 | 
			
		||||
 | 
			
		||||
func (args argumentWrapperList) HasDoc() bool {
 | 
			
		||||
	for _, arg := range args {
 | 
			
		||||
		if arg.HasDoc() {
 | 
			
		||||
			return true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type URNParts struct {
 | 
			
		||||
	URN     string
 | 
			
		||||
	Name    string
 | 
			
		||||
	Version string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (u *URNParts) Const() string {
 | 
			
		||||
	return fmt.Sprintf("URN_%s_%s", u.Name, u.Version)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// extractURNParts extracts the name and version from a URN string.
 | 
			
		||||
func extractURNParts(urn, expectedPrefix string) (*URNParts, error) {
 | 
			
		||||
	if !strings.HasPrefix(urn, expectedPrefix) {
 | 
			
		||||
		return nil, fmt.Errorf("%q does not have expected prefix %q", urn, expectedPrefix)
 | 
			
		||||
	}
 | 
			
		||||
	parts := strings.SplitN(strings.TrimPrefix(urn, expectedPrefix), ":", 2)
 | 
			
		||||
	if len(parts) != 2 {
 | 
			
		||||
		return nil, fmt.Errorf("%q does not have a name and version", urn)
 | 
			
		||||
	}
 | 
			
		||||
	name, version := parts[0], parts[1]
 | 
			
		||||
	return &URNParts{urn, name, version}, nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										88
									
								
								cmd/goupnpdcpgen/fileutil.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								cmd/goupnpdcpgen/fileutil.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,88 @@
 | 
			
		||||
package main
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"archive/zip"
 | 
			
		||||
	"encoding/xml"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path"
 | 
			
		||||
	"regexp"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func acquireFile(specFilename string, xmlSpecURL string) error {
 | 
			
		||||
	if f, err := os.Open(specFilename); err != nil {
 | 
			
		||||
		if !os.IsNotExist(err) {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		f.Close()
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	resp, err := http.Get(xmlSpecURL)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	defer resp.Body.Close()
 | 
			
		||||
 | 
			
		||||
	if resp.StatusCode != http.StatusOK {
 | 
			
		||||
		return fmt.Errorf("could not download spec %q from %q: ",
 | 
			
		||||
			specFilename, xmlSpecURL, resp.Status)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	tmpFilename := specFilename + ".download"
 | 
			
		||||
	w, err := os.Create(tmpFilename)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	defer w.Close()
 | 
			
		||||
 | 
			
		||||
	_, err = io.Copy(w, resp.Body)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return os.Rename(tmpFilename, specFilename)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func globFiles(pattern string, archive *zip.ReadCloser) []*zip.File {
 | 
			
		||||
	var files []*zip.File
 | 
			
		||||
	for _, f := range archive.File {
 | 
			
		||||
		if matched, err := path.Match(pattern, f.Name); err != nil {
 | 
			
		||||
			// This shouldn't happen - all patterns are hard-coded, errors in them
 | 
			
		||||
			// are a programming error.
 | 
			
		||||
			panic(err)
 | 
			
		||||
		} else if matched {
 | 
			
		||||
			files = append(files, f)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return files
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func unmarshalXmlFile(file *zip.File, data interface{}) error {
 | 
			
		||||
	r, err := file.Open()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	decoder := xml.NewDecoder(r)
 | 
			
		||||
	defer r.Close()
 | 
			
		||||
	return decoder.Decode(data)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var scpdFilenameRe = regexp.MustCompile(
 | 
			
		||||
	`.*/([a-zA-Z0-9]+)([0-9]+)\.xml`)
 | 
			
		||||
 | 
			
		||||
func urnPartsFromSCPDFilename(filename string) (*URNParts, error) {
 | 
			
		||||
	parts := scpdFilenameRe.FindStringSubmatch(filename)
 | 
			
		||||
	if len(parts) != 3 {
 | 
			
		||||
		return nil, fmt.Errorf("SCPD filename %q does not have expected number of parts", filename)
 | 
			
		||||
	}
 | 
			
		||||
	name, version := parts[1], parts[2]
 | 
			
		||||
	return &URNParts{
 | 
			
		||||
		URN:     serviceURNPrefix + name + ":" + version,
 | 
			
		||||
		Name:    name,
 | 
			
		||||
		Version: version,
 | 
			
		||||
	}, nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										64
									
								
								cmd/goupnpdcpgen/goupnpdcpgen.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								cmd/goupnpdcpgen/goupnpdcpgen.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,64 @@
 | 
			
		||||
// Command to generate DCP package source from the XML specification.
 | 
			
		||||
package main
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"flag"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"log"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	deviceURNPrefix  = "urn:schemas-upnp-org:device:"
 | 
			
		||||
	serviceURNPrefix = "urn:schemas-upnp-org:service:"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func main() {
 | 
			
		||||
	var (
 | 
			
		||||
		dcpName  = flag.String("dcp_name", "", "Name of the DCP to generate.")
 | 
			
		||||
		specsDir = flag.String("specs_dir", ".", "Path to the specification storage directory. "+
 | 
			
		||||
			"This is used to find (and download if not present) the specification ZIP files.")
 | 
			
		||||
		useGofmt = flag.Bool("gofmt", true, "Pass the generated code through gofmt. "+
 | 
			
		||||
			"Disable this if debugging code generation and needing to see the generated code "+
 | 
			
		||||
			"prior to being passed through gofmt.")
 | 
			
		||||
	)
 | 
			
		||||
	flag.Parse()
 | 
			
		||||
 | 
			
		||||
	if err := run(*dcpName, *specsDir, *useGofmt); err != nil {
 | 
			
		||||
		log.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func run(dcpName, specsDir string, useGofmt bool) error {
 | 
			
		||||
	if err := os.MkdirAll(specsDir, os.ModePerm); err != nil {
 | 
			
		||||
		return fmt.Errorf("could not create specs-dir %q: %v\n", specsDir, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, d := range dcpMetadata {
 | 
			
		||||
		if d.Name != dcpName {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		specFilename := filepath.Join(specsDir, d.Name+".zip")
 | 
			
		||||
		err := acquireFile(specFilename, d.XMLSpecURL)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return fmt.Errorf("could not acquire spec for %s: %v", d.Name, err)
 | 
			
		||||
		}
 | 
			
		||||
		dcp := newDCP(d)
 | 
			
		||||
		if err := dcp.processZipFile(specFilename); err != nil {
 | 
			
		||||
			return fmt.Errorf("error processing spec for %s in file %q: %v", d.Name, specFilename, err)
 | 
			
		||||
		}
 | 
			
		||||
		for i, hack := range d.Hacks {
 | 
			
		||||
			if err := hack(dcp); err != nil {
 | 
			
		||||
				return fmt.Errorf("error with Hack[%d] for %s: %v", i, d.Name, err)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if err := dcp.writeCode(d.Name+".go", useGofmt); err != nil {
 | 
			
		||||
			return fmt.Errorf("error writing package %q: %v", dcp.Metadata.Name, err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return fmt.Errorf("could not find DCP with name %q", dcpName)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										69
									
								
								cmd/goupnpdcpgen/metadata.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								cmd/goupnpdcpgen/metadata.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,69 @@
 | 
			
		||||
package main
 | 
			
		||||
 | 
			
		||||
// DCP contains extra metadata to use when generating DCP source files.
 | 
			
		||||
type DCPMetadata struct {
 | 
			
		||||
	Name         string // What to name the Go DCP package.
 | 
			
		||||
	OfficialName string // Official name for the DCP.
 | 
			
		||||
	DocURL       string // Optional - URL for further documentation about the DCP.
 | 
			
		||||
	XMLSpecURL   string // Where to download the XML spec from.
 | 
			
		||||
	// Any special-case functions to run against the DCP before writing it out.
 | 
			
		||||
	Hacks []DCPHackFn
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var dcpMetadata = []DCPMetadata{
 | 
			
		||||
	{
 | 
			
		||||
		Name:         "internetgateway1",
 | 
			
		||||
		OfficialName: "Internet Gateway Device v1",
 | 
			
		||||
		DocURL:       "http://upnp.org/specs/gw/UPnP-gw-InternetGatewayDevice-v1-Device.pdf",
 | 
			
		||||
		XMLSpecURL:   "http://upnp.org/specs/gw/UPnP-gw-IGD-TestFiles-20010921.zip",
 | 
			
		||||
		Hacks:        []DCPHackFn{totalBytesHack},
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		Name:         "internetgateway2",
 | 
			
		||||
		OfficialName: "Internet Gateway Device v2",
 | 
			
		||||
		DocURL:       "http://upnp.org/specs/gw/UPnP-gw-InternetGatewayDevice-v2-Device.pdf",
 | 
			
		||||
		XMLSpecURL:   "http://upnp.org/specs/gw/UPnP-gw-IGD-Testfiles-20110224.zip",
 | 
			
		||||
		Hacks: []DCPHackFn{
 | 
			
		||||
			func(dcp *DCP) error {
 | 
			
		||||
				missingURN := "urn:schemas-upnp-org:service:WANIPv6FirewallControl:1"
 | 
			
		||||
				if _, ok := dcp.ServiceTypes[missingURN]; ok {
 | 
			
		||||
					return nil
 | 
			
		||||
				}
 | 
			
		||||
				urnParts, err := extractURNParts(missingURN, serviceURNPrefix)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					return err
 | 
			
		||||
				}
 | 
			
		||||
				dcp.ServiceTypes[missingURN] = urnParts
 | 
			
		||||
				return nil
 | 
			
		||||
			}, totalBytesHack,
 | 
			
		||||
		},
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		Name:         "av1",
 | 
			
		||||
		OfficialName: "MediaServer v1 and MediaRenderer v1",
 | 
			
		||||
		DocURL:       "http://upnp.org/specs/av/av1/",
 | 
			
		||||
		XMLSpecURL:   "http://upnp.org/specs/av/UPnP-av-TestFiles-20070927.zip",
 | 
			
		||||
	},
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func totalBytesHack(dcp *DCP) error {
 | 
			
		||||
	for _, service := range dcp.Services {
 | 
			
		||||
		if service.URN == "urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1" {
 | 
			
		||||
			variables := service.SCPD.StateVariables
 | 
			
		||||
			for key, variable := range variables {
 | 
			
		||||
				varName := variable.Name
 | 
			
		||||
				if varName == "TotalBytesSent" || varName == "TotalBytesReceived" {
 | 
			
		||||
					// Fix size of total bytes which is by default ui4 or maximum 4 GiB.
 | 
			
		||||
					variable.DataType.Name = "ui8"
 | 
			
		||||
					variables[key] = variable
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type DCPHackFn func(*DCP) error
 | 
			
		||||
							
								
								
									
										35
									
								
								cmd/goupnpdcpgen/typemap.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								cmd/goupnpdcpgen/typemap.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,35 @@
 | 
			
		||||
package main
 | 
			
		||||
 | 
			
		||||
type conv struct {
 | 
			
		||||
	FuncSuffix string
 | 
			
		||||
	ExtType    string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// typeConvs maps from a SOAP type (e.g "fixed.14.4") to the function name
 | 
			
		||||
// suffix inside the soap module (e.g "Fixed14_4") and the Go type.
 | 
			
		||||
var typeConvs = map[string]conv{
 | 
			
		||||
	"ui1":         {"Ui1", "uint8"},
 | 
			
		||||
	"ui2":         {"Ui2", "uint16"},
 | 
			
		||||
	"ui4":         {"Ui4", "uint32"},
 | 
			
		||||
	"ui8":         {"Ui8", "uint64"},
 | 
			
		||||
	"i1":          {"I1", "int8"},
 | 
			
		||||
	"i2":          {"I2", "int16"},
 | 
			
		||||
	"i4":          {"I4", "int32"},
 | 
			
		||||
	"int":         {"Int", "int64"},
 | 
			
		||||
	"r4":          {"R4", "float32"},
 | 
			
		||||
	"r8":          {"R8", "float64"},
 | 
			
		||||
	"number":      {"R8", "float64"}, // Alias for r8.
 | 
			
		||||
	"fixed.14.4":  {"Fixed14_4", "float64"},
 | 
			
		||||
	"float":       {"R8", "float64"},
 | 
			
		||||
	"char":        {"Char", "rune"},
 | 
			
		||||
	"string":      {"String", "string"},
 | 
			
		||||
	"date":        {"Date", "time.Time"},
 | 
			
		||||
	"dateTime":    {"DateTime", "time.Time"},
 | 
			
		||||
	"dateTime.tz": {"DateTimeTz", "time.Time"},
 | 
			
		||||
	"time":        {"TimeOfDay", "soap.TimeOfDay"},
 | 
			
		||||
	"time.tz":     {"TimeOfDayTz", "soap.TimeOfDay"},
 | 
			
		||||
	"boolean":     {"Boolean", "bool"},
 | 
			
		||||
	"bin.base64":  {"BinBase64", "[]byte"},
 | 
			
		||||
	"bin.hex":     {"BinHex", "[]byte"},
 | 
			
		||||
	"uri":         {"URI", "*url.URL"},
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user