diff options
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.go | 378 |
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. |