ducky/devices
ducky/devices/devices.go
Add ToMap and ToSlice helpers. Add helper functions that convert a map of ID-keyed Devices into a slice, and back. We'll use these a lot (especially with GetMany) as shorthand helper functions.
1 package devices
3 import (
4 "errors"
5 "fmt"
6 "log"
7 "time"
9 "golang.org/x/net/context"
11 "code.secondbit.org/uuid.hg"
12 )
14 var (
15 // ErrDeviceNotFound is returned when the specified device couldn't be found.
16 ErrDeviceNotFound = errors.New("device not found")
17 )
19 // Device represents a specific device that updates can be pushed to.
20 type Device struct {
21 ID uuid.ID
22 Name string
23 Owner uuid.ID
24 Type DeviceType
25 Created time.Time
26 LastSeen time.Time
27 PushToken string
28 }
30 // ApplyChange returns a Device that is a copy of the passed Device,
31 // but with the passed DeviceChange applied.
32 func ApplyChange(d Device, change DeviceChange) Device {
33 result := d
34 if change.Name != nil {
35 result.Name = *change.Name
36 }
37 if change.Owner != nil {
38 result.Owner = *change.Owner
39 } else {
40 // We don't want to accidentally leave a slice that
41 // is owned by both behind.
42 result.Owner = d.Owner.Copy()
43 }
44 if change.Type != nil {
45 result.Type = *change.Type
46 }
47 if change.Created != nil {
48 result.Created = *change.Created
49 }
50 if change.LastSeen != nil {
51 result.LastSeen = *change.LastSeen
52 }
53 if change.PushToken != nil {
54 result.PushToken = *change.PushToken
55 }
56 return result
57 }
59 // DeviceChange represents a set of changes to a Device that will be used
60 // to update a Device.
61 type DeviceChange struct {
62 DeviceID uuid.ID
63 Name *string
64 Owner *uuid.ID
65 Type *DeviceType
66 Created *time.Time
67 LastSeen *time.Time
68 PushToken *string
69 }
71 // Storer is an interface to control how data is stored in and retrieved from
72 // the datastore.
73 type Storer interface {
74 GetDevices(ids []uuid.ID, c context.Context) (map[string]Device, error)
75 UpdateDevice(change DeviceChange, c context.Context) error
76 DeleteDevices(ids []uuid.ID, c context.Context) error
77 CreateDevices(devices []Device, c context.Context) error
78 ListDevicesByOwner(user uuid.ID, c context.Context) ([]Device, error)
80 // These are used for testing only.
81 Factory(c context.Context) (Storer, error)
82 Destroy(c context.Context) error
83 }
85 type ErrDeviceAlreadyExists uuid.ID
87 func (e ErrDeviceAlreadyExists) Error() string {
88 return fmt.Sprintf("device with ID %s already exists in datastore", uuid.ID(e).String())
89 }
91 // GetMany returns as many of the Devices specified by the passed IDs as possible.
92 // They are returned as a map, with the key being the string version of the ID.
93 // No error will be returned if a Device can't be found.
94 func GetMany(ids []uuid.ID, c context.Context) (map[string]Device, error) {
95 results := map[string]Device{}
96 storer, err := getStorer(c)
97 if err != nil {
98 log.Printf("Error retrieving Storer: %+v\n", err)
99 return results, err
100 }
101 results, err = storer.GetDevices(ids, c)
102 if err != nil {
103 log.Printf("Error retrieving Devices from %T: %+v\n", storer, err)
104 return results, err
105 }
106 return results, nil
107 }
109 // Get returns the Device specified by the passed ID. If the Device can't be found,
110 // an ErrDeviceNotFound error is returned.
111 func Get(id uuid.ID, c context.Context) (Device, error) {
112 results, err := GetMany([]uuid.ID{id}, c)
113 if err != nil {
114 return Device{}, err
115 }
116 result, ok := results[id.String()]
117 if !ok {
118 return Device{}, ErrDeviceNotFound
119 }
120 return result, nil
121 }
123 // Update applies the DeviceChange to the passed Device, and returns the result. If
124 // the Device can't be found, an ErrDeviceNotFound error was returned.
125 func Update(device Device, change DeviceChange, c context.Context) (Device, error) {
126 storer, err := getStorer(c)
127 if err != nil {
128 log.Printf("Error retrieving Storer: %+v\n", err)
129 return Device{}, err
130 }
131 change.DeviceID = device.ID
132 err = storer.UpdateDevice(change, c)
133 if err != nil {
134 return Device{}, err
135 }
136 return ApplyChange(device, change), nil
137 }
139 // DeleteMany removes the passed IDs from the datastore. No error is returned if the
140 // ID doesn't correspond to a Device in the datastore.
141 func DeleteMany(ids []uuid.ID, c context.Context) error {
142 storer, err := getStorer(c)
143 if err != nil {
144 log.Printf("Error retrieving Storer: %+v\n", err)
145 return err
146 }
147 return storer.DeleteDevices(ids, c)
148 }
150 // Delete removes the passed ID from the datastore. No error is returned if the ID doesn't
151 // correspond to a Device in the datastore.
152 func Delete(id uuid.ID, c context.Context) error {
153 return DeleteMany([]uuid.ID{id}, c)
154 }
156 // CreateMany stores the passed Devices in the datastore, assigning default values if
157 // necessary. The Devices that were ultimately stored (including any default values, if
158 // applicable) are returned.
159 func CreateMany(devices []Device, c context.Context) ([]Device, error) {
160 storer, err := getStorer(c)
161 if err != nil {
162 log.Printf("Error retrieving Storer: %+v\n", err)
163 return []Device{}, err
164 }
165 modified := make([]Device, 0, len(devices))
166 for _, device := range devices {
167 if device.ID.IsZero() {
168 device.ID = uuid.NewID()
169 }
170 if device.Created.IsZero() {
171 device.Created = time.Now()
172 }
173 if device.LastSeen.IsZero() {
174 device.LastSeen = time.Now()
175 }
176 modified = append(modified, device)
177 }
178 err = storer.CreateDevices(devices, c)
179 if err != nil {
180 return []Device{}, err
181 }
182 return modified, nil
183 }
185 // Create stores the passed Device in the datastore, assigning default values if
186 // necessary. The Devices that were ultimately stored (including any default values, if
187 // applicable) are returned.
188 func Create(device Device, c context.Context) (Device, error) {
189 devices, err := CreateMany([]Device{device}, c)
190 if err != nil {
191 return Device{}, err
192 }
193 // There should never be a case where we don't return a result.
194 // Ideally, we'd return an error here instead of letting the panic
195 // happen, but seeing as I can't come up with a reason the error would
196 // occur, I'm having trouble coming up with a reasonable error to return.
197 return devices[0], nil
198 }
200 // ListByOwner returns a slice of all the Devices with an Owner property that
201 // matches the passed ID. There's no guarantee on the order the Devices will be
202 // returned in.
203 func ListByOwner(user uuid.ID, c context.Context) ([]Device, error) {
204 // BUG(paddy): Eventually, we'll need to support paging for devices. But right now, I don't foresee any user creating enough of them to make pagination worthwhile.
205 storer, err := getStorer(c)
206 if err != nil {
207 log.Printf("Error retrieving Storer: %+v\n", err)
208 return []Device{}, err
209 }
210 devices, err := storer.ListDevicesByOwner(user, c)
211 return devices, err
212 }
214 func ToMap(devices []Device) map[string]Device {
215 results := make(map[string]Device, len(devices))
216 for _, device := range devices {
217 results[device.ID.String()] = device
218 }
219 return results
220 }
222 func ToSlice(devices map[string]Device) []Device {
223 results := make([]Device, 0, len(devices))
224 for _, device := range devices {
225 results = append(results, device)
226 }
227 return results
228 }