More cleanup. Added copyright notice.

This commit is contained in:
Jeff Walter 2018-09-22 14:22:42 -05:00
parent 8213d70258
commit 648de10f10
17 changed files with 465 additions and 180 deletions

6
Gopkg.lock generated
View File

@ -2,12 +2,12 @@
[[projects]] [[projects]]
digest = "1:7365acd48986e205ccb8652cc746f09c8b7876030d53710ea6ef7d0bd0dcd7ca" branch = "master"
digest = "1:4a28080faacdaabfe54a78ba0bfb41468744cbcda6af15748b917ca725d3749c"
name = "github.com/pkg/errors" name = "github.com/pkg/errors"
packages = ["."] packages = ["."]
pruneopts = "" pruneopts = ""
revision = "645ef00459ed84a119197bfb8d8205042c6df63d" revision = "c059e472caf75dbe73903f6521a20abac245b17f"
version = "v0.8.0"
[[projects]] [[projects]]
branch = "master" branch = "master"

51
arlo.go
View File

@ -1,6 +1,23 @@
/*
* Copyright (c) 2018 Jeffrey Walter <jeffreydwalter@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package arlo package arlo
import ( import (
"net/http"
"sync" "sync"
"github.com/jeffreydwalter/arlo-golang/internal/request" "github.com/jeffreydwalter/arlo-golang/internal/request"
@ -20,13 +37,14 @@ type Arlo struct {
func newArlo(user string, pass string) (arlo *Arlo) { func newArlo(user string, pass string) (arlo *Arlo) {
c, _ := request.NewClient(BaseUrl)
// Add important headers. // Add important headers.
c.BaseHttpHeader.Add("DNT", "1") baseHeaders := make(http.Header)
c.BaseHttpHeader.Add("schemaVersion", "1") baseHeaders.Add("DNT", "1")
c.BaseHttpHeader.Add("Host", "arlo.netgear.com") baseHeaders.Add("schemaVersion", "1")
c.BaseHttpHeader.Add("Referer", "https://arlo.netgear.com/") baseHeaders.Add("Host", "arlo.netgear.com")
baseHeaders.Add("Referer", "https://arlo.netgear.com/")
c, _ := request.NewClient(BaseUrl, baseHeaders)
return &Arlo{ return &Arlo{
user: user, user: user,
@ -40,8 +58,8 @@ func Login(user string, pass string) (arlo *Arlo, err error) {
body := map[string]string{"email": arlo.user, "password": arlo.pass} body := map[string]string{"email": arlo.user, "password": arlo.pass}
resp, err := arlo.post(LoginUri, "", body, nil) resp, err := arlo.post(LoginUri, "", body, nil)
if err := checkHttpRequest(resp, err, "login request failed"); err != nil { if err != nil {
return nil, err return nil, errors.WithMessage(err, "failed to login")
} }
defer resp.Body.Close() defer resp.Body.Close()
@ -52,7 +70,7 @@ func Login(user string, pass string) (arlo *Arlo, err error) {
if loginResponse.Success { if loginResponse.Success {
// Cache the auth token. // Cache the auth token.
arlo.client.BaseHttpHeader.Add("Authorization", loginResponse.Data.Token) arlo.client.AddHeader("Authorization", loginResponse.Data.Token)
// Save the account info with the arlo struct. // Save the account info with the arlo struct.
arlo.Account = loginResponse.Data arlo.Account = loginResponse.Data
@ -75,10 +93,10 @@ func (a *Arlo) Logout() error {
// GetDevices returns an array of all devices. // GetDevices returns an array of all devices.
// When you call Login, this method is called and all devices are cached in the arlo object. // When you call Login, this method is called and all devices are cached in the arlo object.
func (a *Arlo) GetDevices() (devices Devices, err error) { func (a *Arlo) GetDevices() (devices *Devices, err error) {
resp, err := a.get(DevicesUri, "", nil) resp, err := a.get(DevicesUri, "", nil)
if err := checkHttpRequest(resp, err, "failed to get devices"); err != nil { if err != nil {
return nil, err return nil, errors.WithMessage(err, "failed to get devices")
} }
defer resp.Body.Close() defer resp.Body.Close()
@ -95,11 +113,12 @@ func (a *Arlo) GetDevices() (devices Devices, err error) {
return nil, errors.New("no devices found") return nil, errors.New("no devices found")
} }
// Cache a pointer to the arlo object with each device.
for i := range deviceResponse.Data { for i := range deviceResponse.Data {
deviceResponse.Data[i].arlo = a deviceResponse.Data[i].arlo = a
} }
// disconnect all of the basestations from the EventStream. // Disconnect all of the basestations from the EventStream.
for i := range a.Basestations { for i := range a.Basestations {
if err := a.Basestations[i].Disconnect(); err != nil { if err := a.Basestations[i].Disconnect(); err != nil {
return nil, errors.WithMessage(err, "failed to get devices") return nil, errors.WithMessage(err, "failed to get devices")
@ -108,8 +127,8 @@ func (a *Arlo) GetDevices() (devices Devices, err error) {
a.rwmutex.Lock() a.rwmutex.Lock()
// Cache the devices as their respective types. // Cache the devices as their respective types.
a.Cameras = deviceResponse.Data.GetCameras() a.Cameras = *deviceResponse.Data.GetCameras()
a.Basestations = deviceResponse.Data.GetBasestations() a.Basestations = *deviceResponse.Data.GetBasestations()
a.rwmutex.Unlock() a.rwmutex.Unlock()
// subscribe each basestation to the EventStream. // subscribe each basestation to the EventStream.
@ -119,7 +138,7 @@ func (a *Arlo) GetDevices() (devices Devices, err error) {
} }
} }
return deviceResponse.Data, nil return &deviceResponse.Data, nil
} }
// UpdateDisplayOrder sets the display order according to the order defined in the DeviceOrder given. // UpdateDisplayOrder sets the display order according to the order defined in the DeviceOrder given.

View File

@ -1 +1,17 @@
/*
* Copyright (c) 2018 Jeffrey Walter <jeffreydwalter@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package arlo package arlo

View File

@ -1,3 +1,19 @@
/*
* Copyright (c) 2018 Jeffrey Walter <jeffreydwalter@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package arlo package arlo
import ( import (

View File

@ -1,3 +1,19 @@
/*
* Copyright (c) 2018 Jeffrey Walter <jeffreydwalter@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package arlo package arlo
import ( import (
@ -17,7 +33,7 @@ type Basestation struct {
eventStream *eventStream eventStream *eventStream
} }
// Basestations is an array of Basestation objects. // Basestations is a slice of Basestation objects.
type Basestations []Basestation type Basestations []Basestation
// Find returns a basestation with the device id passed in. // Find returns a basestation with the device id passed in.

173
camera.go
View File

@ -1,3 +1,19 @@
/*
* Copyright (c) 2018 Jeffrey Walter <jeffreydwalter@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package arlo package arlo
import ( import (
@ -11,7 +27,7 @@ import (
// This type is here just for semantics. Some methods explicitly require a device of a certain type. // This type is here just for semantics. Some methods explicitly require a device of a certain type.
type Camera Device type Camera Device
// Cameras is an array of Camera objects. // Cameras is a slice of Camera objects.
type Cameras []Camera type Cameras []Camera
// Find returns a camera with the device id passed in. // Find returns a camera with the device id passed in.
@ -103,83 +119,6 @@ func (c *Camera) SetBrightness(brightness int) (response *EventStreamResponse, e
return b.makeEventStreamRequest(payload, msg) return b.makeEventStreamRequest(payload, msg)
} }
// StartStream returns a json object containing the rtmps url to the requested video stream.
// You will need the to install a library to handle streaming of this protocol: https://pypi.python.org/pypi/python-librtmp
//
// The request to /users/devices/startStream returns:
// NOTE: { "url":"rtsp://vzwow09-z2-prod.vz.netgear.com:80/vzmodulelive?egressToken=b1b4b675_ac03_4182_9844_043e02a44f71&userAgent=web&cameraId=48B4597VD8FF5_1473010750131" }
func (c *Camera) StartStream() (response *StreamResponse, err error) {
payload := EventStreamPayload{
Action: "set",
Resource: fmt.Sprintf("cameras/%s", c.DeviceId),
PublishResponse: true,
Properties: map[string]string{
"activityState": "startUserStream",
"cameraId": c.DeviceId,
},
TransId: genTransId(),
From: fmt.Sprintf("%s_%s", c.UserId, TransIdPrefix),
To: c.ParentId,
}
msg := "failed to start stream"
resp, err := c.arlo.post(DeviceStartStreamUri, c.XCloudId, payload, nil)
if err := checkHttpRequest(resp, err, msg); err != nil {
return nil, err
}
defer resp.Body.Close()
if err := resp.Decode(response); err != nil {
return nil, err
}
if !response.Success {
return nil, errors.WithMessage(errors.New("status was false"), msg)
}
response.Data.Url = strings.Replace(response.Data.Url, "rtsp://", "rtsps://", 1)
return response, nil
}
// TakeSnapshot causes the camera to record a snapshot.
func (c *Camera) TakeSnapshot() (response *StreamResponse, err error) {
msg := "failed to take snapshot"
response, err = c.StartStream()
if err != nil {
return nil, errors.WithMessage(err, msg)
}
body := map[string]string{"deviceId": c.DeviceId, "parentId": c.ParentId, "xcloudId": c.XCloudId, "olsonTimeZone": c.Properties.OlsonTimeZone}
resp, err := c.arlo.post(DeviceTakeSnapshotUri, c.XCloudId, body, nil)
if err := checkRequest(resp, err, "failed to update device name"); err != nil {
return nil, errors.WithMessage(err, msg)
}
return response, nil
}
// StartRecording causes the camera to start recording and returns a url that you must start reading from using ffmpeg
// or something similar.
func (c *Camera) StartRecording() (response *StreamResponse, err error) {
msg := "failed to start recording"
response, err = c.StartStream()
if err != nil {
return nil, errors.WithMessage(err, msg)
}
body := map[string]string{"deviceId": c.DeviceId, "parentId": c.ParentId, "xcloudId": c.XCloudId, "olsonTimeZone": c.Properties.OlsonTimeZone}
resp, err := c.arlo.post(DeviceStartRecordUri, c.XCloudId, body, nil)
if err := checkRequest(resp, err, "failed to update device name"); err != nil {
return nil, errors.WithMessage(err, msg)
}
return response, nil
}
func (c *Camera) EnableMotionAlerts(sensitivity int, zones []string) (response *EventStreamResponse, err error) { func (c *Camera) EnableMotionAlerts(sensitivity int, zones []string) (response *EventStreamResponse, err error) {
payload := EventStreamPayload{ payload := EventStreamPayload{
Action: "set", Action: "set",
@ -321,3 +260,81 @@ func (c *Camera) SetAlertNotificationMethods(action string, email, push bool) (r
} }
return b.makeEventStreamRequest(payload, msg) return b.makeEventStreamRequest(payload, msg)
} }
// StartStream returns a json object containing the rtmps url to the requested video stream.
// You will need the to install a library to handle streaming of this protocol: https://pypi.python.org/pypi/python-librtmp
//
// The request to /users/devices/startStream returns:
// NOTE: { "url":"rtsp://vzwow09-z2-prod.vz.netgear.com:80/vzmodulelive?egressToken=b1b4b675_ac03_4182_9844_043e02a44f71&userAgent=web&cameraId=48B4597VD8FF5_1473010750131" }
func (c *Camera) StartStream() (url string, err error) {
payload := EventStreamPayload{
Action: "set",
Resource: fmt.Sprintf("cameras/%s", c.DeviceId),
PublishResponse: true,
Properties: map[string]string{
"activityState": "startUserStream",
"cameraId": c.DeviceId,
},
TransId: genTransId(),
From: fmt.Sprintf("%s_%s", c.UserId, TransIdPrefix),
To: c.ParentId,
}
msg := "failed to start stream"
resp, err := c.arlo.post(DeviceStartStreamUri, c.XCloudId, payload, nil)
if err != nil {
return "", errors.WithMessage(err, msg)
}
defer resp.Body.Close()
response := new(StreamResponse)
if err := resp.Decode(response); err != nil {
return "", err
}
if !response.Success {
return "", errors.WithMessage(errors.New("status was false"), msg)
}
response.URL = strings.Replace(response.URL, "rtsp://", "rtsps://", 1)
return response.URL, nil
}
// TakeSnapshot causes the camera to record a snapshot.
func (c *Camera) TakeSnapshot() (url string, err error) {
msg := "failed to take snapshot"
url, err = c.StartStream()
if err != nil {
return "", errors.WithMessage(err, msg)
}
body := map[string]string{"deviceId": c.DeviceId, "parentId": c.ParentId, "xcloudId": c.XCloudId, "olsonTimeZone": c.Properties.OlsonTimeZone}
resp, err := c.arlo.post(DeviceTakeSnapshotUri, c.XCloudId, body, nil)
if err := checkRequest(resp, err, "failed to update device name"); err != nil {
return "", errors.WithMessage(err, msg)
}
return url, nil
}
// StartRecording causes the camera to start recording and returns a url that you must start reading from using ffmpeg
// or something similar.
func (c *Camera) StartRecording() (url string, err error) {
msg := "failed to start recording"
url, err = c.StartStream()
if err != nil {
return "", errors.WithMessage(err, msg)
}
body := map[string]string{"deviceId": c.DeviceId, "parentId": c.ParentId, "xcloudId": c.XCloudId, "olsonTimeZone": c.Properties.OlsonTimeZone}
resp, err := c.arlo.post(DeviceStartRecordUri, c.XCloudId, body, nil)
if err := checkRequest(resp, err, "failed to update device name"); err != nil {
return "", errors.WithMessage(err, msg)
}
return url, nil
}

View File

@ -1,3 +1,19 @@
/*
* Copyright (c) 2018 Jeffrey Walter <jeffreydwalter@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package arlo package arlo
const ( const (
@ -7,7 +23,6 @@ const (
SubscribeUri = "/client/subscribe?token=%s" SubscribeUri = "/client/subscribe?token=%s"
UnsubscribeUri = "/client/unsubscribe" UnsubscribeUri = "/client/unsubscribe"
NotifyUri = "/users/devices/notify/%s" NotifyUri = "/users/devices/notify/%s"
ResetUri = "/users/library/reset"
ServiceLevelUri = "/users/serviceLevel" ServiceLevelUri = "/users/serviceLevel"
OffersUri = "/users/payment/offers" OffersUri = "/users/payment/offers"
UserProfileUri = "/users/profile" UserProfileUri = "/users/profile"
@ -18,8 +33,9 @@ const (
UserLocationsUri = "/users/locations" UserLocationsUri = "/users/locations"
UserLocationUri = "/users/locations/%s" UserLocationUri = "/users/locations/%s"
LibraryUri = "/users/library" LibraryUri = "/users/library"
LibraryRecycleUri = "/users/library/recycle"
LibraryMetadataUri = "/users/library/metadata" LibraryMetadataUri = "/users/library/metadata"
LibraryRecycleUri = "/users/library/recycle"
LibraryResetUri = "/users/library/reset"
DevicesUri = "/users/devices" DevicesUri = "/users/devices"
DeviceRenameUri = "/users/devices/renameDevice" DeviceRenameUri = "/users/devices/renameDevice"
DeviceDisplayOrderUri = "/users/devices/displayOrder" DeviceDisplayOrderUri = "/users/devices/displayOrder"

View File

@ -1,3 +1,19 @@
/*
* Copyright (c) 2018 Jeffrey Walter <jeffreydwalter@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package arlo package arlo
// A Device is the device data, this can be a camera, basestation, arloq, etc. // A Device is the device data, this can be a camera, basestation, arloq, etc.
@ -42,7 +58,7 @@ type Device struct {
XCloudId string `json:"xCloudId"` XCloudId string `json:"xCloudId"`
} }
// Devices is an array of Device objects. // Devices is a slice of Device objects.
type Devices []Device type Devices []Device
// A DeviceOrder holds a map of device ids and a numeric index. The numeric index is the device order. // A DeviceOrder holds a map of device ids and a numeric index. The numeric index is the device order.
@ -70,9 +86,9 @@ func (ds *Devices) Find(deviceId string) *Device {
return nil return nil
} }
func (ds *Devices) FindCameras(basestationId string) Cameras { func (ds Devices) FindCameras(basestationId string) Cameras {
cs := new(Cameras) cs := new(Cameras)
for _, d := range *ds { for _, d := range ds {
if d.ParentId == basestationId { if d.ParentId == basestationId {
*cs = append(*cs, Camera(d)) *cs = append(*cs, Camera(d))
} }
@ -93,11 +109,11 @@ func (d Device) IsCamera() bool {
// I did this because some device types, like arloq, don't have a basestation. // I did this because some device types, like arloq, don't have a basestation.
// So, when interacting with them you must treat them like a basestation and a camera. // So, when interacting with them you must treat them like a basestation and a camera.
// Cameras also includes devices of this type, so you can get the same data there or cast. // Cameras also includes devices of this type, so you can get the same data there or cast.
func (ds Devices) GetBasestations() Basestations { func (ds Devices) GetBasestations() *Basestations {
var basestations Basestations basestations := new(Basestations)
for _, d := range ds { for _, d := range ds {
if d.IsBasestation() || !d.IsCamera() { if d.IsBasestation() || !d.IsCamera() {
basestations = append(basestations, Basestation{Device: d}) *basestations = append(*basestations, Basestation{Device: d})
} }
} }
return basestations return basestations
@ -107,11 +123,11 @@ func (ds Devices) GetBasestations() Basestations {
// I did this because some device types, like arloq, don't have a basestation. // I did this because some device types, like arloq, don't have a basestation.
// So, when interacting with them you must treat them like a basestation and a camera. // So, when interacting with them you must treat them like a basestation and a camera.
// Basestations also includes devices of this type, so you can get the same data there or cast. // Basestations also includes devices of this type, so you can get the same data there or cast.
func (ds Devices) GetCameras() Cameras { func (ds Devices) GetCameras() *Cameras {
var cameras Cameras cameras := new(Cameras)
for _, d := range ds { for _, d := range ds {
if d.IsCamera() || !d.IsBasestation() { if d.IsCamera() || !d.IsBasestation() {
cameras = append(cameras, Camera(d)) *cameras = append(*cameras, Camera(d))
} }
} }
return cameras return cameras

View File

@ -1,3 +1,19 @@
/*
* Copyright (c) 2018 Jeffrey Walter <jeffreydwalter@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package arlo package arlo
import ( import (
@ -74,7 +90,7 @@ func (e *eventStream) listen() (connected chan bool) {
/* /*
fmt.Println("Got event message.") fmt.Println("Got event message.")
fmt.Printf("EVENT: %s\n", event.Event) fmt.Printf("EVENT: %s\n", event.Event)
fmt.Printf("DATA: %s\n", event.Data) fmt.Printf("DATA: %s\n", event.URL)
*/ */
if event.Data != nil { if event.Data != nil {

View File

@ -1,12 +1,30 @@
/*
* Copyright (c) 2018 Jeffrey Walter <jeffreydwalter@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package request package request
import ( import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"io" "io"
"log"
"net/http" "net/http"
"net/http/cookiejar" "net/http/cookiejar"
"net/url" "net/url"
"sync"
"github.com/pkg/errors" "github.com/pkg/errors"
@ -15,11 +33,12 @@ import (
type Client struct { type Client struct {
BaseURL *url.URL BaseURL *url.URL
BaseHttpHeader *http.Header BaseHeaders *http.Header
HttpClient *http.Client HttpClient *http.Client
rwmutex sync.RWMutex
} }
func NewClient(baseurl string) (*Client, error) { func NewClient(baseURL string, baseHeaders http.Header) (*Client, error) {
var err error var err error
var jar *cookiejar.Jar var jar *cookiejar.Jar
@ -30,7 +49,7 @@ func NewClient(baseurl string) (*Client, error) {
} }
var u *url.URL var u *url.URL
if u, err = url.Parse(baseurl); err != nil { if u, err = url.Parse(baseURL); err != nil {
return nil, errors.Wrap(err, "failed to create client object") return nil, errors.Wrap(err, "failed to create client object")
} }
@ -41,11 +60,17 @@ func NewClient(baseurl string) (*Client, error) {
return &Client{ return &Client{
BaseURL: u, BaseURL: u,
BaseHttpHeader: &header, BaseHeaders: &header,
HttpClient: &http.Client{Jar: jar}, HttpClient: &http.Client{Jar: jar},
}, nil }, nil
} }
func (c *Client) AddHeader(key, value string) {
c.rwmutex.Lock()
c.BaseHeaders.Set(key, value)
c.rwmutex.Unlock()
}
func (c *Client) Get(uri string, header http.Header) (*Response, error) { func (c *Client) Get(uri string, header http.Header) (*Response, error) {
req, err := c.newRequest("GET", uri, nil, header) req, err := c.newRequest("GET", uri, nil, header)
if err != nil { if err != nil {
@ -80,7 +105,7 @@ func (c *Client) newRequest(method string, uri string, body interface{}, header
return nil, errors.Wrap(err, "failed to create request object") return nil, errors.Wrap(err, "failed to create request object")
} }
} }
// log.Printf("\n\nBODY (%s): %s\n\n", uri, buf) log.Printf("\n\nBODY (%s): %s\n\n", uri, buf)
u := c.BaseURL.String() + uri u := c.BaseURL.String() + uri
req, err := http.NewRequest(method, u, buf) req, err := http.NewRequest(method, u, buf)
@ -88,7 +113,11 @@ func (c *Client) newRequest(method string, uri string, body interface{}, header
return nil, errors.Wrap(err, "failed to create request object") return nil, errors.Wrap(err, "failed to create request object")
} }
for k, v := range *c.BaseHttpHeader { c.rwmutex.RLock()
baseHeaders := *c.BaseHeaders
c.rwmutex.RUnlock()
for k, v := range baseHeaders {
for _, h := range v { for _, h := range v {
//log.Printf("Adding header (%s): (%s - %s)\n\n", u, k, h) //log.Printf("Adding header (%s): (%s - %s)\n\n", u, k, h)
req.Header.Set(k, h) req.Header.Set(k, h)
@ -116,8 +145,8 @@ func (c *Client) newResponse(resp *http.Response) (*Response, error) {
func (c *Client) do(req *Request) (*Response, error) { func (c *Client) do(req *Request) (*Response, error) {
//log.Printf("\n\nCOOKIES (%s): %v\n\n", req.URL, c.HttpClient.Jar.Cookies(req.URL)) log.Printf("\n\nCOOKIES (%s): %v\n\n", req.URL, c.HttpClient.Jar.Cookies(req.URL))
//log.Printf("\n\nHEADERS (%s): %v\n\n", req.URL, req.Header) log.Printf("\n\nHEADERS (%s): %v\n\n", req.URL, req.Header)
resp, err := c.HttpClient.Do(&req.Request) resp, err := c.HttpClient.Do(&req.Request)
if err != nil { if err != nil {

View File

@ -1,3 +1,19 @@
/*
* Copyright (c) 2018 Jeffrey Walter <jeffreydwalter@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package request package request
import ( import (

View File

@ -1,3 +1,19 @@
/*
* Copyright (c) 2018 Jeffrey Walter <jeffreydwalter@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package request package request
import ( import (

View File

@ -1,3 +1,19 @@
/*
* Copyright (c) 2018 Jeffrey Walter <jeffreydwalter@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package util package util
import ( import (

View File

@ -1,7 +1,26 @@
/*
* Copyright (c) 2018 Jeffrey Walter <jeffreydwalter@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package arlo package arlo
import ( import (
"fmt"
"time" "time"
"github.com/pkg/errors"
) )
// LibraryMetaData is the library meta data. // LibraryMetaData is the library meta data.
@ -30,38 +49,53 @@ type Recording struct {
UtcCreatedDate int64 `json:"utcCreatedDate"` UtcCreatedDate int64 `json:"utcCreatedDate"`
CurrentState string `json:"currentState"` CurrentState string `json:"currentState"`
MediaDuration string `json:"mediaDuration"` MediaDuration string `json:"mediaDuration"`
UniqueId string `json:"uniqueId"`
} }
type Library []Recording type Library []Recording
func (a *Arlo) GetLibraryMetaData(fromDate, toDate time.Time) (response *LibraryMetaDataResponse, err error) { func (a *Arlo) GetLibraryMetaData(fromDate, toDate time.Time) (libraryMetaData *LibraryMetaData, err error) {
msg := "failed to get library metadata"
body := map[string]string{"dateFrom": fromDate.Format("20060102"), "dateTo": toDate.Format("20060102")} body := map[string]string{"dateFrom": fromDate.Format("20060102"), "dateTo": toDate.Format("20060102")}
resp, err := a.post(LibraryMetadataUri, "", body, nil) resp, err := a.post(LibraryMetadataUri, "", body, nil)
if err := checkHttpRequest(resp, err, "failed to get library metadata"); err != nil { if err != nil {
return nil, errors.WithMessage(err, msg)
}
defer resp.Body.Close()
response := new(LibraryMetaDataResponse)
if err := resp.Decode(&response); err != nil {
return nil, err return nil, err
} }
if err := resp.Decode(response); err != nil { if !response.Success {
return nil, err return nil, errors.New(msg)
} }
return response, nil return &response.Data, nil
} }
func (a *Arlo) GetLibrary(fromDate, toDate time.Time) (response *LibraryResponse, err error) { func (a *Arlo) GetLibrary(fromDate, toDate time.Time) (library *Library, err error) {
msg := "failed to get library"
body := map[string]string{"dateFrom": fromDate.Format("20060102"), "dateTo": toDate.Format("20060102")} body := map[string]string{"dateFrom": fromDate.Format("20060102"), "dateTo": toDate.Format("20060102")}
resp, err := a.post(LibraryUri, "", body, nil) resp, err := a.post(LibraryUri, "", body, nil)
if err := checkHttpRequest(resp, err, "failed to get library"); err != nil { if err != nil {
return nil, errors.WithMessage(err, msg)
}
defer resp.Body.Close()
response := new(LibraryResponse)
if err := resp.Decode(&response); err != nil {
return nil, err return nil, err
} }
if err := resp.Decode(response); err != nil { if !response.Success {
return nil, err return nil, errors.New(msg)
} }
return response, nil return &response.Data, nil
} }
/* /*
@ -71,9 +105,9 @@ func (a *Arlo) GetLibrary(fromDate, toDate time.Time) (response *LibraryResponse
NOTE: {"data": [{"createdDate": r.CreatedDate, "utcCreatedDate": r.UtcCreatedDate, "deviceId": r.DeviceId}]} is all that's really required. NOTE: {"data": [{"createdDate": r.CreatedDate, "utcCreatedDate": r.UtcCreatedDate, "deviceId": r.DeviceId}]} is all that's really required.
*/ */
func (a *Arlo) DeleteRecording(r Recording) error { func (a *Arlo) DeleteRecording(r *Recording) error {
body := map[string]Library{"data": {r}} body := map[string]Library{"data": {*r}}
resp, err := a.post(LibraryRecycleUri, "", body, nil) resp, err := a.post(LibraryRecycleUri, "", body, nil)
return checkRequest(resp, err, "failed to delete recording") return checkRequest(resp, err, "failed to delete recording")
} }
@ -85,9 +119,11 @@ func (a *Arlo) DeleteRecording(r Recording) error {
NOTE: {"data": [{"createdDate": r.CreatedDate, "utcCreatedDate": r.UtcCreatedDate, "deviceId": r.DeviceId}]} is all that's really required. NOTE: {"data": [{"createdDate": r.CreatedDate, "utcCreatedDate": r.UtcCreatedDate, "deviceId": r.DeviceId}]} is all that's really required.
*/ */
func (a *Arlo) BatchDeleteRecordings(l Library) error { func (a *Arlo) BatchDeleteRecordings(l *Library) error {
body := map[string]Library{"data": l} body := map[string]Library{"data": *l}
resp, err := a.post(LibraryRecycleUri, "", body, nil) fmt.Printf("%+v\n", body)
return checkRequest(resp, err, "failed to delete recordings") return nil
//resp, err := a.post(LibraryRecycleUri, "", body, nil)
//return checkRequest(resp, err, "failed to delete recordings")
} }

View File

@ -1,3 +1,19 @@
/*
* Copyright (c) 2018 Jeffrey Walter <jeffreydwalter@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package arlo package arlo
// LoginResponse is an intermediate struct used when parsing data from the Login() call. // LoginResponse is an intermediate struct used when parsing data from the Login() call.
@ -24,12 +40,12 @@ type LibraryResponse struct {
} }
type StreamResponse struct { type StreamResponse struct {
Data StreamUrl URL string `json:"url"`
Status Status
} }
type RecordingResponse struct { type RecordingResponse struct {
Data StreamUrl URL string `json:"url"`
Status Status
} }

View File

@ -1,3 +1,19 @@
/*
* Copyright (c) 2018 Jeffrey Walter <jeffreydwalter@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package arlo package arlo
/* /*
@ -59,7 +75,7 @@ type Connectivity struct {
ActiveNetwork string `json:"activeNetwork,omitempty"` ActiveNetwork string `json:"activeNetwork,omitempty"`
APN string `json:"apn,omitempty"` APN string `json:"apn,omitempty"`
CarrierFw string `json:"carrierFw,omitempty"` CarrierFw string `json:"carrierFw,omitempty"`
Connected bool `json:"connected"` Connected bool `json:"connected,omitempty"`
FWVersion string `json:"fwVersion,omitempty"` FWVersion string `json:"fwVersion,omitempty"`
ICCID string `json:"iccid,omitempty"` ICCID string `json:"iccid,omitempty"`
IMEI string `json:"imei,omitempty"` IMEI string `json:"imei,omitempty"`
@ -115,10 +131,6 @@ type Favorite struct {
Favorite uint8 `json:"Favorite"` Favorite uint8 `json:"Favorite"`
} }
type StreamUrl struct {
Url string `json:"url"`
}
type BaseDetectionProperties struct { type BaseDetectionProperties struct {
Armed bool `json:"armed"` Armed bool `json:"armed"`
Sensitivity int `json:"sensitivity"` Sensitivity int `json:"sensitivity"`
@ -240,8 +252,8 @@ type EventStreamPayload struct {
To string `json:"to"` To string `json:"to"`
} }
// Data is part of the Status message fragment returned by most calls to the Arlo API. // URL is part of the Status message fragment returned by most calls to the Arlo API.
// Data is only populated when Success is false. // URL is only populated when Success is false.
type Data struct { type Data struct {
Message string `json:"message,omitempty"` Message string `json:"message,omitempty"`
Reason string `json:"reason,omitempty"` Reason string `json:"reason,omitempty"`
@ -250,6 +262,6 @@ type Data struct {
// Status is the message fragment returned from most http calls to the Arlo API. // Status is the message fragment returned from most http calls to the Arlo API.
type Status struct { type Status struct {
Data `json:"Data,omitempty"` Data `json:"URL,omitempty"`
Success bool `json:"success"` Success bool `json:"success"`
} }

76
util.go
View File

@ -1,10 +1,28 @@
/*
* Copyright (c) 2018 Jeffrey Walter <jeffreydwalter@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package arlo package arlo
import ( import (
"fmt" "fmt"
"io"
"math" "math"
"math/rand" "math/rand"
"net/http" "net/http"
"os"
"strconv" "strconv"
"strings" "strings"
"time" "time"
@ -15,25 +33,12 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
) )
func checkHttpRequest(resp *request.Response, err error, msg string) error { func checkRequest(resp *request.Response, err error, msg string) error {
if resp.StatusCode != 200 {
return errors.WithMessage(errors.New(fmt.Sprintf("http request failed: %s (%d)", resp.Status, resp.StatusCode)), msg)
}
if err != nil { if err != nil {
return errors.WithMessage(err, msg) return errors.WithMessage(err, msg)
} }
return nil
}
func checkRequest(resp *request.Response, err error, msg string) error {
defer resp.Body.Close() defer resp.Body.Close()
if err := checkHttpRequest(resp, err, msg); err != nil {
return err
}
var status Status var status Status
if err := resp.Decode(&status); err != nil { if err := resp.Decode(&status); err != nil {
return err return err
@ -59,31 +64,38 @@ func genTransId() string {
} }
func (a *Arlo) get(uri, xCloudId string, header http.Header) (*request.Response, error) { func (a *Arlo) get(uri, xCloudId string, header http.Header) (*request.Response, error) {
if len(xCloudId) > 0 { a.client.AddHeader("xcloudId", xCloudId)
a.rwmutex.Lock()
a.client.BaseHttpHeader.Set("xcloudId", xCloudId)
a.rwmutex.Unlock()
}
return a.client.Get(uri, header) return a.client.Get(uri, header)
} }
func (a *Arlo) put(uri, xCloudId string, body interface{}, header http.Header) (*request.Response, error) { func (a *Arlo) put(uri, xCloudId string, body interface{}, header http.Header) (*request.Response, error) {
if len(xCloudId) > 0 { a.client.AddHeader("xcloudId", xCloudId)
a.rwmutex.Lock()
a.client.BaseHttpHeader.Set("xcloudId", xCloudId)
a.rwmutex.Unlock()
}
return a.client.Put(uri, body, header) return a.client.Put(uri, body, header)
} }
func (a *Arlo) post(uri, xCloudId string, body interface{}, header http.Header) (*request.Response, error) { func (a *Arlo) post(uri, xCloudId string, body interface{}, header http.Header) (*request.Response, error) {
if len(xCloudId) > 0 { a.client.AddHeader("xcloudId", xCloudId)
a.rwmutex.Lock()
a.client.BaseHttpHeader.Set("xcloudId", xCloudId)
a.rwmutex.Unlock()
}
return a.client.Post(uri, body, header) return a.client.Post(uri, body, header)
} }
func (a *Arlo) DownloadFile(url, to string) error {
msg := fmt.Sprintf("failed to download file (%s) => (%s)", url, to)
resp, err := a.get(url, "", nil)
if err != nil {
return errors.WithMessage(err, msg)
}
defer resp.Body.Close()
f, err := os.Create(to)
if err != nil {
return errors.WithMessage(err, msg)
}
_, err = io.Copy(f, resp.Body)
defer f.Close()
if err != nil {
return errors.WithMessage(err, msg)
}
return nil
}