ducky/devices
2015-11-12
Child:600326d50e74
ducky/devices/devices.go
Initial attempt. Create the basic types (Device, DeviceType, DeviceChange) that we'll be using in the service. Also, create our Storer interface, and the helper methods to store, retrieve, and update information in the datastore. Also, we're using Godep, so check in our Godeps folder and the vendor folder. Note that this only works on Go 1.5 and later, with the GO15VENDOREXPERIMENT environment variable set to 1.
1 package devices
3 import (
4 "errors"
5 "log"
6 "time"
8 "golang.org/x/net/context"
10 "code.secondbit.org/uuid.hg"
11 )
13 var (
14 // ErrDeviceNotFound is returned when the specified device couldn't be found.
15 ErrDeviceNotFound = errors.New("device not found")
16 )
18 // Device represents a specific device that updates can be pushed to.
19 type Device struct {
20 ID uuid.ID
21 Name string
22 Owner uuid.ID
23 Type DeviceType
24 Created time.Time
25 LastSeen time.Time
26 PushToken string
27 }
29 // ApplyChange returns a Device that is a copy of the passed Device,
30 // but with the passed DeviceChange applied.
31 func ApplyChange(d Device, change DeviceChange) Device {
32 result := d
33 if change.Name != nil {
34 result.Name = *change.Name
35 }
36 if change.Owner != nil {
37 result.Owner = *change.Owner
38 } else {
39 // We don't want to accidentally leave a slice that
40 // is owned by both behind.
41 result.Owner = d.Owner.Copy()
42 }
43 if change.Type != nil {
44 result.Type = *change.Type
45 }
46 if change.Created != nil {
47 result.Created = *change.Created
48 }
49 if change.LastSeen != nil {
50 result.LastSeen = *change.LastSeen
51 }
52 if change.PushToken != nil {
53 result.PushToken = *change.PushToken
54 }
55 return result
56 }
58 // DeviceType is an enum specifying which type of Device it is. Usually,
59 // this is something like `android_phone` or `android_tablet`.
60 type DeviceType string
62 // DeviceChange represents a set of changes to a Device that will be used
63 // to update a Device.
64 type DeviceChange struct {
65 DeviceID uuid.ID
66 Name *string
67 Owner *uuid.ID
68 Type *DeviceType
69 Created *time.Time
70 LastSeen *time.Time
71 PushToken *string
72 }
74 // Storer is an interface to control how data is stored in and retrieved from
75 // the datastore.
76 type Storer interface {
77 GetDevices(ids []uuid.ID, c context.Context) (map[string]Device, error)
78 UpdateDevice(change DeviceChange, c context.Context) error
79 DeleteDevices(ids []uuid.ID, c context.Context) error
80 CreateDevices(devices []Device, c context.Context) error
81 ListDevicesByOwner(user uuid.ID, c context.Context) ([]Device, error)
83 // These are used for testing only.
84 Factory(c context.Context) (Storer, error)
85 Destroy(c context.Context) error
86 }
88 // GetMany returns as many of the Devices specified by the passed IDs as possible.
89 // They are returned as a map, with the key being the string version of the ID.
90 // No error will be returned if a Device can't be found.
91 func GetMany(ids []uuid.ID, c context.Context) (map[string]Device, error) {
92 results := map[string]Device{}
93 storer, err := getStorer(c)
94 if err != nil {
95 log.Printf("Error retrieving Storer: %+v\n", err)
96 return results, err
97 }
98 results, err = storer.GetDevices(ids, c)
99 if err != nil {
100 log.Printf("Error retrieving Devices from %T: %+v\n", storer, err)
101 return results, err
102 }
103 return results, nil
104 }
106 // Get returns the Device specified by the passed ID. If the Device can't be found,
107 // an ErrDeviceNotFound error is returned.
108 func Get(id uuid.ID, c context.Context) (Device, error) {
109 results, err := GetMany([]uuid.ID{id}, c)
110 if err != nil {
111 return Device{}, err
112 }
113 result, ok := results[id.String()]
114 if !ok {
115 return Device{}, ErrDeviceNotFound
116 }
117 return result, nil
118 }
120 // Update applies the DeviceChange to the passed Device, and returns the result. If
121 // the Device can't be found, an ErrDeviceNotFound error was returned.
122 func Update(device Device, change DeviceChange, c context.Context) (Device, error) {
123 storer, err := getStorer(c)
124 if err != nil {
125 log.Printf("Error retrieving Storer: %+v\n", err)
126 return Device{}, err
127 }
128 change.DeviceID = device.ID
129 err = storer.UpdateDevice(change, c)
130 if err != nil {
131 return Device{}, err
132 }
133 return ApplyChange(device, change), nil
134 }
136 // DeleteMany removes the passed IDs from the datastore. No error is returned if the
137 // ID doesn't correspond to a Device in the datastore.
138 func DeleteMany(ids []uuid.ID, c context.Context) error {
139 storer, err := getStorer(c)
140 if err != nil {
141 log.Printf("Error retrieving Storer: %+v\n", err)
142 return err
143 }
144 return storer.DeleteDevices(ids, c)
145 }
147 // Delete removes the passed ID from the datastore. No error is returned if the ID doesn't
148 // correspond to a Device in the datastore.
149 func Delete(id uuid.ID, c context.Context) error {
150 return DeleteMany([]uuid.ID{id}, c)
151 }
153 // CreateMany stores the passed Devices in the datastore, assigning default values if
154 // necessary. The Devices that were ultimately stored (including any default values, if
155 // applicable) are returned.
156 func CreateMany(devices []Device, c context.Context) ([]Device, error) {
157 storer, err := getStorer(c)
158 if err != nil {
159 log.Printf("Error retrieving Storer: %+v\n", err)
160 return []Device{}, err
161 }
162 modified := make([]Device, 0, len(devices))
163 for _, device := range devices {
164 if device.ID.IsZero() {
165 device.ID = uuid.NewID()
166 }
167 if device.Created.IsZero() {
168 device.Created = time.Now()
169 }
170 if device.LastSeen.IsZero() {
171 device.LastSeen = time.Now()
172 }
173 modified = append(modified, device)
174 }
175 err = storer.CreateDevices(devices, c)
176 if err != nil {
177 return []Device{}, err
178 }
179 return modified, nil
180 }
182 // Create stores the passed Device in the datastore, assigning default values if
183 // necessary. The Devices that were ultimately stored (including any default values, if
184 // applicable) are returned.
185 func Create(device Device, c context.Context) (Device, error) {
186 devices, err := CreateMany([]Device{device}, c)
187 if err != nil {
188 return Device{}, err
189 }
190 // There should never be a case where we don't return a result.
191 // Ideally, we'd return an error here instead of letting the panic
192 // happen, but seeing as I can't come up with a reason the error would
193 // occur, I'm having trouble coming up with a reasonable error to return.
194 return devices[0], nil
195 }
197 // ListByOwner returns a slice of all the Devices with an Owner property that
198 // matches the passed ID. There's no guarantee on the order the Devices will be
199 // returned in.
200 func ListByOwner(user uuid.ID, c context.Context) ([]Device, error) {
201 // 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.
202 storer, err := getStorer(c)
203 if err != nil {
204 log.Printf("Error retrieving Storer: %+v\n", err)
205 return []Device{}, err
206 }
207 devices, err := storer.ListDevicesByOwner(user, c)
208 return devices, err
209 }