Add tests for endpoint NewStream.
Rename OpenWithVidPid to OpenWithVIDPID.
This commit is contained in:
@@ -34,13 +34,13 @@ func TestClaimAndRelease(t *testing.T) {
|
|||||||
)
|
)
|
||||||
c := NewContext()
|
c := NewContext()
|
||||||
defer c.Close()
|
defer c.Close()
|
||||||
dev, err := c.OpenDeviceWithVidPid(0x8888, 0x0002)
|
dev, err := c.OpenDeviceWithVIDPID(0x8888, 0x0002)
|
||||||
if dev == nil {
|
if dev == nil {
|
||||||
t.Fatal("OpenDeviceWithVidPid(0x8888, 0x0002): got nil device, need non-nil")
|
t.Fatal("OpenDeviceWithVIDPID(0x8888, 0x0002): got nil device, need non-nil")
|
||||||
}
|
}
|
||||||
defer dev.Close()
|
defer dev.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("OpenDeviceWithVidPid(0x8888, 0x0002): %v", err)
|
t.Fatalf("OpenDeviceWithVIDPID(0x8888, 0x0002): %v", err)
|
||||||
}
|
}
|
||||||
cfg, err := dev.Config(cfgNum)
|
cfg, err := dev.Config(cfgNum)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
81
usb/endpoint_stream_test.go
Normal file
81
usb/endpoint_stream_test.go
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
// Copyright 2017 the gousb Authors. All rights reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package usb
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestEndpointReadStream(t *testing.T) {
|
||||||
|
lib, done := newFakeLibusb()
|
||||||
|
defer done()
|
||||||
|
|
||||||
|
goodTransfers := 7
|
||||||
|
go func() {
|
||||||
|
var num int
|
||||||
|
for {
|
||||||
|
xfr := lib.waitForSubmitted()
|
||||||
|
if xfr == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if num < goodTransfers {
|
||||||
|
xfr.status = TransferCompleted
|
||||||
|
xfr.length = len(xfr.buf)
|
||||||
|
} else {
|
||||||
|
xfr.status = TransferError
|
||||||
|
xfr.length = 0
|
||||||
|
}
|
||||||
|
xfr.done <- struct{}{}
|
||||||
|
num++
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
ctx := NewContext()
|
||||||
|
dev, err := ctx.OpenDeviceWithVIDPID(0x9999, 0x0001)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("OpenDeviceWithVIDPID(9999, 0001): %v", err)
|
||||||
|
}
|
||||||
|
defer dev.Close()
|
||||||
|
cfg, err := dev.Config(1)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("%s.Config(1): %v", dev, err)
|
||||||
|
}
|
||||||
|
defer cfg.Close()
|
||||||
|
intf, err := cfg.Interface(0, 0)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("%s.Interface(0, 0): %v", cfg, err)
|
||||||
|
}
|
||||||
|
defer intf.Close()
|
||||||
|
ep, err := intf.InEndpoint(2)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("%s.Endpoint(2): %v", intf, err)
|
||||||
|
}
|
||||||
|
stream, err := ep.NewStream(1024, 5)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("%s.NewStream(1024, 5): %v", ep, err)
|
||||||
|
}
|
||||||
|
defer stream.Close()
|
||||||
|
var got int
|
||||||
|
want := goodTransfers * 1024
|
||||||
|
buf := make([]byte, 1024)
|
||||||
|
for got <= want {
|
||||||
|
num, err := stream.Read(buf)
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
got += num
|
||||||
|
}
|
||||||
|
if got != want {
|
||||||
|
t.Errorf("%s.Read(): read %d bytes, want %d")
|
||||||
|
}
|
||||||
|
}
|
@@ -163,9 +163,9 @@ func TestEndpointInOut(t *testing.T) {
|
|||||||
|
|
||||||
ctx := NewContext()
|
ctx := NewContext()
|
||||||
defer ctx.Close()
|
defer ctx.Close()
|
||||||
d, err := ctx.OpenDeviceWithVidPid(0x9999, 0x0001)
|
d, err := ctx.OpenDeviceWithVIDPID(0x9999, 0x0001)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("OpenDeviceWithVidPid(0x9999, 0x0001): got error %v, want nil", err)
|
t.Fatalf("OpenDeviceWithVIDPID(0x9999, 0x0001): got error %v, want nil", err)
|
||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
if err := d.Close(); err != nil {
|
if err := d.Close(); err != nil {
|
||||||
|
141
usb/fakelibusb_devices.go
Normal file
141
usb/fakelibusb_devices.go
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
// Copyright 2017 the gousb Authors. All rights reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package usb
|
||||||
|
|
||||||
|
// fake devices connected through the fakeLibusb stack.
|
||||||
|
var fakeDevices = []*Descriptor{
|
||||||
|
// Bus 001 Device 001: ID 9999:0001
|
||||||
|
// One config, one interface, one setup,
|
||||||
|
// two endpoints: 0x01 OUT, 0x82 IN.
|
||||||
|
&Descriptor{
|
||||||
|
Bus: 1,
|
||||||
|
Address: 1,
|
||||||
|
Spec: Version(2, 0),
|
||||||
|
Device: Version(1, 0),
|
||||||
|
Vendor: ID(0x9999),
|
||||||
|
Product: ID(0x0001),
|
||||||
|
Protocol: 255,
|
||||||
|
Configs: map[int]ConfigInfo{1: {
|
||||||
|
Config: 1,
|
||||||
|
MaxPower: Milliamperes(100),
|
||||||
|
Interfaces: []InterfaceInfo{{
|
||||||
|
Number: 0,
|
||||||
|
AltSettings: []InterfaceSetting{{
|
||||||
|
Number: 0,
|
||||||
|
Alternate: 0,
|
||||||
|
Class: ClassVendorSpec,
|
||||||
|
Endpoints: map[int]EndpointInfo{
|
||||||
|
1: {
|
||||||
|
Number: 1,
|
||||||
|
Direction: EndpointDirectionOut,
|
||||||
|
MaxPacketSize: 512,
|
||||||
|
TransferType: TransferTypeBulk,
|
||||||
|
},
|
||||||
|
2: {
|
||||||
|
Number: 2,
|
||||||
|
Direction: EndpointDirectionIn,
|
||||||
|
MaxPacketSize: 512,
|
||||||
|
TransferType: TransferTypeBulk,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
}},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
// Bus 001 Device 002: ID 8888:0002
|
||||||
|
// One config, two interfaces. interface #0 with no endpoints,
|
||||||
|
// interface #1 with two alt setups with different packet sizes for
|
||||||
|
// endpoints. Two isochronous endpoints, 0x05 OUT and 0x86 OUT.
|
||||||
|
&Descriptor{
|
||||||
|
Bus: 1,
|
||||||
|
Address: 2,
|
||||||
|
Spec: Version(2, 0),
|
||||||
|
Device: Version(1, 3),
|
||||||
|
Vendor: ID(0x8888),
|
||||||
|
Product: ID(0x0002),
|
||||||
|
Protocol: 255,
|
||||||
|
Configs: map[int]ConfigInfo{1: {
|
||||||
|
Config: 1,
|
||||||
|
MaxPower: Milliamperes(100),
|
||||||
|
Interfaces: []InterfaceInfo{{
|
||||||
|
Number: 0,
|
||||||
|
AltSettings: []InterfaceSetting{{
|
||||||
|
Number: 0,
|
||||||
|
Alternate: 0,
|
||||||
|
Class: ClassVendorSpec,
|
||||||
|
}},
|
||||||
|
}, {
|
||||||
|
Number: 1,
|
||||||
|
AltSettings: []InterfaceSetting{{
|
||||||
|
Number: 1,
|
||||||
|
Alternate: 0,
|
||||||
|
Class: ClassVendorSpec,
|
||||||
|
Endpoints: map[int]EndpointInfo{
|
||||||
|
5: {
|
||||||
|
Number: 5,
|
||||||
|
Direction: EndpointDirectionOut,
|
||||||
|
MaxPacketSize: 3 * 1024,
|
||||||
|
TransferType: TransferTypeIsochronous,
|
||||||
|
UsageType: IsoUsageTypeData,
|
||||||
|
},
|
||||||
|
6: {
|
||||||
|
Number: 6,
|
||||||
|
Direction: EndpointDirectionIn,
|
||||||
|
MaxPacketSize: 3 * 1024,
|
||||||
|
TransferType: TransferTypeIsochronous,
|
||||||
|
UsageType: IsoUsageTypeData,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
Number: 1,
|
||||||
|
Alternate: 1,
|
||||||
|
Class: ClassVendorSpec,
|
||||||
|
Endpoints: map[int]EndpointInfo{
|
||||||
|
5: {
|
||||||
|
Number: 5,
|
||||||
|
Direction: EndpointDirectionOut,
|
||||||
|
MaxPacketSize: 2 * 1024,
|
||||||
|
TransferType: TransferTypeIsochronous,
|
||||||
|
},
|
||||||
|
6: {
|
||||||
|
Number: 6,
|
||||||
|
Direction: EndpointDirectionIn,
|
||||||
|
MaxPacketSize: 2 * 1024,
|
||||||
|
TransferType: TransferTypeIsochronous,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
Number: 1,
|
||||||
|
Alternate: 2,
|
||||||
|
Class: ClassVendorSpec,
|
||||||
|
Endpoints: map[int]EndpointInfo{
|
||||||
|
5: {
|
||||||
|
Number: 5,
|
||||||
|
Direction: EndpointDirectionIn,
|
||||||
|
MaxPacketSize: 1024,
|
||||||
|
TransferType: TransferTypeIsochronous,
|
||||||
|
},
|
||||||
|
6: {
|
||||||
|
Number: 6,
|
||||||
|
Direction: EndpointDirectionIn,
|
||||||
|
MaxPacketSize: 1024,
|
||||||
|
TransferType: TransferTypeIsochronous,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
}},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
}
|
@@ -21,134 +21,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
|
||||||
// fake devices connected through the fakeLibusb stack.
|
|
||||||
fakeDevices = []*Descriptor{
|
|
||||||
// Bus 001 Device 001: ID 9999:0001
|
|
||||||
// One config, one interface, one setup,
|
|
||||||
// two endpoints: 0x01 OUT, 0x82 IN.
|
|
||||||
&Descriptor{
|
|
||||||
Bus: 1,
|
|
||||||
Address: 1,
|
|
||||||
Spec: Version(2, 0),
|
|
||||||
Device: Version(1, 0),
|
|
||||||
Vendor: ID(0x9999),
|
|
||||||
Product: ID(0x0001),
|
|
||||||
Protocol: 255,
|
|
||||||
Configs: map[int]ConfigInfo{1: {
|
|
||||||
Config: 1,
|
|
||||||
MaxPower: Milliamperes(100),
|
|
||||||
Interfaces: []InterfaceInfo{{
|
|
||||||
Number: 0,
|
|
||||||
AltSettings: []InterfaceSetting{{
|
|
||||||
Number: 0,
|
|
||||||
Alternate: 0,
|
|
||||||
Class: ClassVendorSpec,
|
|
||||||
Endpoints: map[int]EndpointInfo{
|
|
||||||
1: {
|
|
||||||
Number: 1,
|
|
||||||
Direction: EndpointDirectionOut,
|
|
||||||
MaxPacketSize: 512,
|
|
||||||
TransferType: TransferTypeBulk,
|
|
||||||
},
|
|
||||||
2: {
|
|
||||||
Number: 2,
|
|
||||||
Direction: EndpointDirectionIn,
|
|
||||||
MaxPacketSize: 512,
|
|
||||||
TransferType: TransferTypeBulk,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}},
|
|
||||||
}},
|
|
||||||
}},
|
|
||||||
},
|
|
||||||
// Bus 001 Device 002: ID 8888:0002
|
|
||||||
// One config, two interfaces. interface #0 with no endpoints,
|
|
||||||
// interface #1 with two alt setups with different packet sizes for
|
|
||||||
// endpoints. Two isochronous endpoints, 0x05 OUT and 0x86 OUT.
|
|
||||||
&Descriptor{
|
|
||||||
Bus: 1,
|
|
||||||
Address: 2,
|
|
||||||
Spec: Version(2, 0),
|
|
||||||
Device: Version(1, 3),
|
|
||||||
Vendor: ID(0x8888),
|
|
||||||
Product: ID(0x0002),
|
|
||||||
Protocol: 255,
|
|
||||||
Configs: map[int]ConfigInfo{1: {
|
|
||||||
Config: 1,
|
|
||||||
MaxPower: Milliamperes(100),
|
|
||||||
Interfaces: []InterfaceInfo{{
|
|
||||||
Number: 0,
|
|
||||||
AltSettings: []InterfaceSetting{{
|
|
||||||
Number: 0,
|
|
||||||
Alternate: 0,
|
|
||||||
Class: ClassVendorSpec,
|
|
||||||
}},
|
|
||||||
}, {
|
|
||||||
Number: 1,
|
|
||||||
AltSettings: []InterfaceSetting{{
|
|
||||||
Number: 1,
|
|
||||||
Alternate: 0,
|
|
||||||
Class: ClassVendorSpec,
|
|
||||||
Endpoints: map[int]EndpointInfo{
|
|
||||||
5: {
|
|
||||||
Number: 5,
|
|
||||||
Direction: EndpointDirectionOut,
|
|
||||||
MaxPacketSize: 3 * 1024,
|
|
||||||
TransferType: TransferTypeIsochronous,
|
|
||||||
UsageType: IsoUsageTypeData,
|
|
||||||
},
|
|
||||||
6: {
|
|
||||||
Number: 6,
|
|
||||||
Direction: EndpointDirectionIn,
|
|
||||||
MaxPacketSize: 3 * 1024,
|
|
||||||
TransferType: TransferTypeIsochronous,
|
|
||||||
UsageType: IsoUsageTypeData,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}, {
|
|
||||||
Number: 1,
|
|
||||||
Alternate: 1,
|
|
||||||
Class: ClassVendorSpec,
|
|
||||||
Endpoints: map[int]EndpointInfo{
|
|
||||||
5: {
|
|
||||||
Number: 5,
|
|
||||||
Direction: EndpointDirectionOut,
|
|
||||||
MaxPacketSize: 2 * 1024,
|
|
||||||
TransferType: TransferTypeIsochronous,
|
|
||||||
},
|
|
||||||
6: {
|
|
||||||
Number: 6,
|
|
||||||
Direction: EndpointDirectionIn,
|
|
||||||
MaxPacketSize: 2 * 1024,
|
|
||||||
TransferType: TransferTypeIsochronous,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}, {
|
|
||||||
Number: 1,
|
|
||||||
Alternate: 2,
|
|
||||||
Class: ClassVendorSpec,
|
|
||||||
Endpoints: map[int]EndpointInfo{
|
|
||||||
5: {
|
|
||||||
Number: 5,
|
|
||||||
Direction: EndpointDirectionIn,
|
|
||||||
MaxPacketSize: 1024,
|
|
||||||
TransferType: TransferTypeIsochronous,
|
|
||||||
},
|
|
||||||
6: {
|
|
||||||
Number: 6,
|
|
||||||
Direction: EndpointDirectionIn,
|
|
||||||
MaxPacketSize: 1024,
|
|
||||||
TransferType: TransferTypeIsochronous,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}},
|
|
||||||
}},
|
|
||||||
}},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
type fakeDevice struct {
|
type fakeDevice struct {
|
||||||
desc *Descriptor
|
desc *Descriptor
|
||||||
alt uint8
|
alt uint8
|
||||||
@@ -335,6 +207,7 @@ func newFakeLibusb() (*fakeLibusb, func() error) {
|
|||||||
libusb = fl
|
libusb = fl
|
||||||
return fl, func() error {
|
return fl, func() error {
|
||||||
defer func() { libusb = origLibusb }()
|
defer func() { libusb = origLibusb }()
|
||||||
|
close(fl.submitted)
|
||||||
if got := len(fl.ts); got > 0 {
|
if got := len(fl.ts); got > 0 {
|
||||||
for t := range fl.ts {
|
for t := range fl.ts {
|
||||||
fl.free(t)
|
fl.free(t)
|
||||||
|
@@ -120,6 +120,9 @@ func (r ReadStream) Read(p []byte) (int, error) {
|
|||||||
// was encountered earlier.
|
// was encountered earlier.
|
||||||
// Close cannot be called concurrently with Read.
|
// Close cannot be called concurrently with Read.
|
||||||
func (r ReadStream) Close() error {
|
func (r ReadStream) Close() error {
|
||||||
|
if r.s.transfers == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
r.s.setDelayedErr(io.EOF)
|
r.s.setDelayedErr(io.EOF)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@@ -105,7 +105,7 @@ func (r readRes) String() string {
|
|||||||
return buf.String()
|
return buf.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestReadStream(t *testing.T) {
|
func TestTransferReadStream(t *testing.T) {
|
||||||
for tcNum, tc := range []struct {
|
for tcNum, tc := range []struct {
|
||||||
desc string
|
desc string
|
||||||
closeBefore int
|
closeBefore int
|
||||||
|
@@ -76,13 +76,13 @@ func (c *Context) ListDevices(each func(desc *Descriptor) bool) ([]*Device, erro
|
|||||||
return ret, reterr
|
return ret, reterr
|
||||||
}
|
}
|
||||||
|
|
||||||
// OpenDeviceWithVidPid opens Device from specific VendorId and ProductId.
|
// OpenDeviceWithVIDPID opens Device from specific VendorId and ProductId.
|
||||||
// If none is found, it returns nil and nil error. If there are multiple devices
|
// If none is found, it returns nil and nil error. If there are multiple devices
|
||||||
// with the same VID/PID, it will return one of them, picked arbitrarily.
|
// with the same VID/PID, it will return one of them, picked arbitrarily.
|
||||||
// If there were any errors during device list traversal, it is possible
|
// If there were any errors during device list traversal, it is possible
|
||||||
// it will return a non-nil device and non-nil error. A Device.Close() must
|
// it will return a non-nil device and non-nil error. A Device.Close() must
|
||||||
// be called to release the device if the returned device wasn't nil.
|
// be called to release the device if the returned device wasn't nil.
|
||||||
func (c *Context) OpenDeviceWithVidPid(vid, pid ID) (*Device, error) {
|
func (c *Context) OpenDeviceWithVIDPID(vid, pid ID) (*Device, error) {
|
||||||
var found bool
|
var found bool
|
||||||
devs, err := c.ListDevices(func(desc *Descriptor) bool {
|
devs, err := c.ListDevices(func(desc *Descriptor) bool {
|
||||||
if found {
|
if found {
|
||||||
|
@@ -53,7 +53,7 @@ func TestListDevices(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOpenDeviceWithVidPid(t *testing.T) {
|
func TestOpenDeviceWithVIDPID(t *testing.T) {
|
||||||
_, done := newFakeLibusb()
|
_, done := newFakeLibusb()
|
||||||
defer done()
|
defer done()
|
||||||
|
|
||||||
@@ -69,16 +69,16 @@ func TestOpenDeviceWithVidPid(t *testing.T) {
|
|||||||
} {
|
} {
|
||||||
c := NewContext()
|
c := NewContext()
|
||||||
defer c.Close()
|
defer c.Close()
|
||||||
dev, err := c.OpenDeviceWithVidPid(d.vid, d.pid)
|
dev, err := c.OpenDeviceWithVIDPID(d.vid, d.pid)
|
||||||
if (dev != nil) != d.exists {
|
if (dev != nil) != d.exists {
|
||||||
t.Errorf("OpenDeviceWithVidPid(%s/%s): device != nil is %v, want %v", ID(d.vid), ID(d.pid), dev != nil, d.exists)
|
t.Errorf("OpenDeviceWithVIDPID(%s/%s): device != nil is %v, want %v", ID(d.vid), ID(d.pid), dev != nil, d.exists)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("OpenDeviceWithVidPid(%s/%s): got error %v, want nil", ID(d.vid), ID(d.pid), err)
|
t.Errorf("OpenDeviceWithVIDPID(%s/%s): got error %v, want nil", ID(d.vid), ID(d.pid), err)
|
||||||
}
|
}
|
||||||
if dev != nil {
|
if dev != nil {
|
||||||
if dev.Descriptor.Vendor != ID(d.vid) || dev.Descriptor.Product != ID(d.pid) {
|
if dev.Descriptor.Vendor != ID(d.vid) || dev.Descriptor.Product != ID(d.pid) {
|
||||||
t.Errorf("OpenDeviceWithVidPid(%s/%s): the device returned has VID/PID %s/%s, different from specified in the arguments", ID(d.vid), ID(d.pid), dev.Descriptor.Vendor, dev.Descriptor.Product)
|
t.Errorf("OpenDeviceWithVIDPID(%s/%s): the device returned has VID/PID %s/%s, different from specified in the arguments", ID(d.vid), ID(d.pid), dev.Descriptor.Vendor, dev.Descriptor.Product)
|
||||||
}
|
}
|
||||||
dev.Close()
|
dev.Close()
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user