aboutsummaryrefslogtreecommitdiffstats
path: root/Godeps/_workspace/src/github.com/huin/goupnp/gotasks/specgen_task.go
diff options
context:
space:
mode:
Diffstat (limited to 'Godeps/_workspace/src/github.com/huin/goupnp/gotasks/specgen_task.go')
-rw-r--r--Godeps/_workspace/src/github.com/huin/goupnp/gotasks/specgen_task.go378
1 files changed, 221 insertions, 157 deletions
diff --git a/Godeps/_workspace/src/github.com/huin/goupnp/gotasks/specgen_task.go b/Godeps/_workspace/src/github.com/huin/goupnp/gotasks/specgen_task.go
index 0ac1d4ff3..921e8c857 100644
--- a/Godeps/_workspace/src/github.com/huin/goupnp/gotasks/specgen_task.go
+++ b/Godeps/_workspace/src/github.com/huin/goupnp/gotasks/specgen_task.go
@@ -4,12 +4,11 @@ package gotasks
import (
"archive/zip"
- "bytes"
"encoding/xml"
"fmt"
"io"
- "io/ioutil"
"log"
+ "net/http"
"os"
"path"
"path/filepath"
@@ -28,6 +27,53 @@ var (
serviceURNPrefix = "urn:schemas-upnp-org:service:"
)
+// 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 futher 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",
+ },
+ {
+ 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
+ },
+ },
+ },
+ {
+ 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",
+ },
+}
+
+type DCPHackFn func(*DCP) error
+
// NAME
// specgen - generates Go code from the UPnP specification files.
//
@@ -35,104 +81,90 @@ var (
// The specification is available for download from:
//
// OPTIONS
-// -s, --spec_filename=<upnpresources.zip>
-// Path to the specification file, available from http://upnp.org/resources/upnpresources.zip
+// -s, --specs_dir=<spec directory>
+// Path to the specification storage directory. This is used to find (and download if not present) the specification ZIP files. Defaults to 'specs'
// -o, --out_dir=<output directory>
-// Path to the output directory. This is is where the DCP source files will be placed. Should normally correspond to the directory for github.com/huin/goupnp/dcps
+// Path to the output directory. This is is where the DCP source files will be placed. Should normally correspond to the directory for github.com/huin/goupnp/dcps. Defaults to '../dcps'
// --nogofmt
// Disable passing the output through gofmt. Do this if debugging code output problems and needing to see the generated code prior to being passed through gofmt.
func TaskSpecgen(t *tasking.T) {
- specFilename := t.Flags.String("spec-filename")
- if specFilename == "" {
- specFilename = t.Flags.String("s")
- }
- if specFilename == "" {
- t.Fatal("--spec_filename is required")
- }
- outDir := t.Flags.String("out-dir")
- if outDir == "" {
- outDir = t.Flags.String("o")
- }
- if outDir == "" {
- log.Fatal("--out_dir is required")
+ specsDir := fallbackStrValue("specs", t.Flags.String("specs_dir"), t.Flags.String("s"))
+ if err := os.MkdirAll(specsDir, os.ModePerm); err != nil {
+ t.Fatalf("Could not create specs-dir %q: %v\n", specsDir, err)
}
+ outDir := fallbackStrValue("../dcps", t.Flags.String("out_dir"), t.Flags.String("o"))
useGofmt := !t.Flags.Bool("nogofmt")
- specArchive, err := openZipfile(specFilename)
- if err != nil {
- t.Fatalf("Error opening spec file: %v", err)
- }
- defer specArchive.Close()
-
- dcpCol := newDcpsCollection()
- for _, f := range globFiles("standardizeddcps/*/*.zip", specArchive.Reader) {
- dirName := strings.TrimPrefix(f.Name, "standardizeddcps/")
- slashIndex := strings.Index(dirName, "/")
- if slashIndex == -1 {
- // Should not happen.
- t.Logf("Could not find / in %q", dirName)
- return
+NEXT_DCP:
+ for _, d := range dcpMetadata {
+ specFilename := filepath.Join(specsDir, d.Name+".zip")
+ err := acquireFile(specFilename, d.XMLSpecURL)
+ if err != nil {
+ t.Logf("Could not acquire spec for %s, skipping: %v\n", d.Name, err)
+ continue NEXT_DCP
}
- dirName = dirName[:slashIndex]
-
- dcp := dcpCol.dcpForDir(dirName)
- if dcp == nil {
- t.Logf("No alias defined for directory %q: skipping %s\n", dirName, f.Name)
- continue
- } else {
- t.Logf("Alias found for directory %q: processing %s\n", dirName, f.Name)
+ dcp := newDCP(d)
+ if err := dcp.processZipFile(specFilename); err != nil {
+ log.Printf("Error processing spec for %s in file %q: %v", d.Name, specFilename, err)
+ continue NEXT_DCP
}
-
- dcp.processZipFile(f)
- }
-
- for _, dcp := range dcpCol.dcpByAlias {
+ for i, hack := range d.Hacks {
+ if err := hack(dcp); err != nil {
+ log.Printf("Error with Hack[%d] for %s: %v", i, d.Name, err)
+ continue NEXT_DCP
+ }
+ }
+ dcp.writePackage(outDir, useGofmt)
if err := dcp.writePackage(outDir, useGofmt); err != nil {
log.Printf("Error writing package %q: %v", dcp.Metadata.Name, err)
+ continue NEXT_DCP
}
}
}
-// 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 futher documentation about the DCP.
+func fallbackStrValue(defaultValue string, values ...string) string {
+ for _, v := range values {
+ if v != "" {
+ return v
+ }
+ }
+ return defaultValue
}
-var dcpMetadataByDir = map[string]DCPMetadata{
- "Internet Gateway_1": {
- Name: "internetgateway1",
- OfficialName: "Internet Gateway Device v1",
- DocURL: "http://upnp.org/specs/gw/UPnP-gw-InternetGatewayDevice-v1-Device.pdf",
- },
- "Internet Gateway_2": {
- Name: "internetgateway2",
- OfficialName: "Internet Gateway Device v2",
- DocURL: "http://upnp.org/specs/gw/UPnP-gw-InternetGatewayDevice-v2-Device.pdf",
- },
-}
+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
+ }
-type dcpCollection struct {
- dcpByAlias map[string]*DCP
-}
+ resp, err := http.Get(xmlSpecURL)
+ if err != nil {
+ return err
+ }
+ defer resp.Body.Close()
-func newDcpsCollection() *dcpCollection {
- c := &dcpCollection{
- dcpByAlias: make(map[string]*DCP),
+ if resp.StatusCode != http.StatusOK {
+ return fmt.Errorf("could not download spec %q from %q: ",
+ specFilename, xmlSpecURL, resp.Status)
}
- for _, metadata := range dcpMetadataByDir {
- c.dcpByAlias[metadata.Name] = newDCP(metadata)
+
+ tmpFilename := specFilename + ".download"
+ w, err := os.Create(tmpFilename)
+ if err != nil {
+ return err
}
- return c
-}
+ defer w.Close()
-func (c *dcpCollection) dcpForDir(dirName string) *DCP {
- metadata, ok := dcpMetadataByDir[dirName]
- if !ok {
- return nil
+ _, err = io.Copy(w, resp.Body)
+ if err != nil {
+ return err
}
- return c.dcpByAlias[metadata.Name]
+
+ return os.Rename(tmpFilename, specFilename)
}
// DCP collects together information about a UPnP Device Control Protocol.
@@ -151,33 +183,37 @@ func newDCP(metadata DCPMetadata) *DCP {
}
}
-func (dcp *DCP) processZipFile(file *zip.File) {
- archive, err := openChildZip(file)
+func (dcp *DCP) processZipFile(filename string) error {
+ archive, err := zip.OpenReader(filename)
if err != nil {
- log.Println("Error reading child zip file:", err)
- return
+ return fmt.Errorf("error reading zip file %q: %v", filename, err)
}
+ defer archive.Close()
for _, deviceFile := range globFiles("*/device/*.xml", archive) {
- dcp.processDeviceFile(deviceFile)
+ if err := dcp.processDeviceFile(deviceFile); err != nil {
+ return err
+ }
}
for _, scpdFile := range globFiles("*/service/*.xml", archive) {
- dcp.processSCPDFile(scpdFile)
+ if err := dcp.processSCPDFile(scpdFile); err != nil {
+ return err
+ }
}
+ return nil
}
-func (dcp *DCP) processDeviceFile(file *zip.File) {
+func (dcp *DCP) processDeviceFile(file *zip.File) error {
var device goupnp.Device
if err := unmarshalXmlFile(file, &device); err != nil {
- log.Printf("Error decoding device XML from file %q: %v", file.Name, err)
- return
+ 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 {
- log.Println(err)
- return
+ mainErr = err
}
dcp.DeviceTypes[t] = u
}
@@ -185,11 +221,11 @@ func (dcp *DCP) processDeviceFile(file *zip.File) {
device.VisitServices(func(s *goupnp.Service) {
u, err := extractURNParts(s.ServiceType, serviceURNPrefix)
if err != nil {
- log.Println(err)
- return
+ mainErr = err
}
dcp.ServiceTypes[s.ServiceType] = u
})
+ return mainErr
}
func (dcp *DCP) writePackage(outDir string, useGofmt bool) error {
@@ -217,22 +253,21 @@ func (dcp *DCP) writePackage(outDir string, useGofmt bool) error {
return output.Close()
}
-func (dcp *DCP) processSCPDFile(file *zip.File) {
+func (dcp *DCP) processSCPDFile(file *zip.File) error {
scpd := new(scpd.SCPD)
if err := unmarshalXmlFile(file, scpd); err != nil {
- log.Printf("Error decoding SCPD XML from file %q: %v", file.Name, err)
- return
+ return fmt.Errorf("error decoding SCPD XML from file %q: %v", file.Name, err)
}
scpd.Clean()
urnParts, err := urnPartsFromSCPDFilename(file.Name)
if err != nil {
- log.Printf("Could not recognize SCPD filename %q: %v", file.Name, err)
- return
+ 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 {
@@ -240,7 +275,19 @@ type SCPDWithURN struct {
SCPD *scpd.SCPD
}
-func (s *SCPDWithURN) WrapArgument(arg scpd.Argument) (*argumentWrapper, error) {
+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)
@@ -250,7 +297,7 @@ func (s *SCPDWithURN) WrapArgument(arg scpd.Argument) (*argumentWrapper, error)
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,
+ Argument: *arg,
relVar: relVar,
conv: cnv,
}, nil
@@ -266,6 +313,12 @@ 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 {
@@ -295,6 +348,17 @@ 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 conv struct {
FuncSuffix string
ExtType string
@@ -325,49 +389,10 @@ var typeConvs = map[string]conv{
"boolean": conv{"Boolean", "bool"},
"bin.base64": conv{"BinBase64", "[]byte"},
"bin.hex": conv{"BinHex", "[]byte"},
+ "uri": conv{"URI", "*url.URL"},
}
-type closeableZipReader struct {
- io.Closer
- *zip.Reader
-}
-
-func openZipfile(filename string) (*closeableZipReader, error) {
- file, err := os.Open(filename)
- if err != nil {
- return nil, err
- }
- fi, err := file.Stat()
- if err != nil {
- return nil, err
- }
- archive, err := zip.NewReader(file, fi.Size())
- if err != nil {
- return nil, err
- }
- return &closeableZipReader{
- Closer: file,
- Reader: archive,
- }, nil
-}
-
-// openChildZip opens a zip file within another zip file.
-func openChildZip(file *zip.File) (*zip.Reader, error) {
- zipFile, err := file.Open()
- if err != nil {
- return nil, err
- }
- defer zipFile.Close()
-
- zipBytes, err := ioutil.ReadAll(zipFile)
- if err != nil {
- return nil, err
- }
-
- return zip.NewReader(bytes.NewReader(zipBytes), int64(len(zipBytes)))
-}
-
-func globFiles(pattern string, archive *zip.Reader) []*zip.File {
+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 {
@@ -435,14 +460,14 @@ var packageTmpl = template.Must(template.New("package").Parse(`{{$name := .Metad
// {{if .Metadata.DocURL}}
// This DCP is documented in detail at: {{.Metadata.DocURL}}{{end}}
//
-// Typically, use one of the New* functions to discover services on the local
-// network.
+// 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"
@@ -484,38 +509,77 @@ func New{{$srvIdent}}Clients() (clients []*{{$srvIdent}}, errors []error, err er
if genericClients, errors, err = goupnp.NewServiceClients({{$srv.Const}}); err != nil {
return
}
- clients = make([]*{{$srvIdent}}, len(genericClients))
+ 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
+ return clients
}
{{range .SCPD.Actions}}{{/* loops over *SCPDWithURN values */}}
-{{$inargs := .InputArguments}}{{$outargs := .OutputArguments}}
-// {{if $inargs}}Arguments:{{range $inargs}}{{$argWrap := $srv.WrapArgument .}}
+{{$winargs := $srv.WrapArguments .InputArguments}}
+{{$woutargs := $srv.WrapArguments .OutputArguments}}
+{{if $winargs.HasDoc}}
+//
+// Arguments:{{range $winargs}}{{if .HasDoc}}
//
-// * {{.Name}}: {{$argWrap.Document}}{{end}}{{end}}
+// * {{.Name}}: {{.Document}}{{end}}{{end}}{{end}}
+{{if $woutargs.HasDoc}}
//
-// {{if $outargs}}Return values:{{range $outargs}}{{$argWrap := $srv.WrapArgument .}}
+// Return values:{{range $woutargs}}{{if .HasDoc}}
//
-// * {{.Name}}: {{$argWrap.Document}}{{end}}{{end}}
-func (client *{{$srvIdent}}) {{.Name}}({{range $inargs}}{{/*
-*/}}{{$argWrap := $srv.WrapArgument .}}{{$argWrap.AsParameter}}, {{end}}{{/*
-*/}}) ({{range $outargs}}{{/*
-*/}}{{$argWrap := $srv.WrapArgument .}}{{$argWrap.AsParameter}}, {{end}} err error) {
+// * {{.Name}}: {{.Document}}{{end}}{{end}}{{end}}
+func (client *{{$srvIdent}}) {{.Name}}({{range $winargs}}{{/*
+*/}}{{.AsParameter}}, {{end}}{{/*
+*/}}) ({{range $woutargs}}{{/*
+*/}}{{.AsParameter}}, {{end}} err error) {
// Request structure.
- request := {{if $inargs}}&{{template "argstruct" $inargs}}{{"{}"}}{{else}}{{"interface{}(nil)"}}{{end}}
+ request := {{if $winargs}}&{{template "argstruct" $winargs}}{{"{}"}}{{else}}{{"interface{}(nil)"}}{{end}}
// BEGIN Marshal arguments into request.
-{{range $inargs}}{{$argWrap := $srv.WrapArgument .}}
- if request.{{.Name}}, err = {{$argWrap.Marshal}}; err != nil {
+{{range $winargs}}
+ if request.{{.Name}}, err = {{.Marshal}}; err != nil {
return
}{{end}}
// END Marshal arguments into request.
// Response structure.
- response := {{if $outargs}}&{{template "argstruct" $outargs}}{{"{}"}}{{else}}{{"interface{}(nil)"}}{{end}}
+ 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 {
@@ -523,8 +587,8 @@ func (client *{{$srvIdent}}) {{.Name}}({{range $inargs}}{{/*
}
// BEGIN Unmarshal arguments from response.
-{{range $outargs}}{{$argWrap := $srv.WrapArgument .}}
- if {{.Name}}, err = {{$argWrap.Unmarshal "response"}}; err != nil {
+{{range $woutargs}}
+ if {{.Name}}, err = {{.Unmarshal "response"}}; err != nil {
return
}{{end}}
// END Unmarshal arguments from response.