ducky/devices

Paddy 2015-11-12 Parent:b6494e1a499e Child:408abf6e48d3

1:600326d50e74 Go to Latest

ducky/devices/devices.go

Move DeviceType to its own file, add helper and constants. Make a device_type.go file, to avoid a mess in the devices.go file. Move the DeviceType definition over to the new file. Also, while we're here, set up a few of the contstants we know we'll need. These are the DeviceTypes we intend to support, such as Android phones, Android tablets, and Chrome extensions. Also, set up a helper method that will determine whether a DeviceType is "valid", i.e. if we have a constant defined for it. DeviceTypes, in general, are mostly intended to be used (at the moment, at least) to customise how we display devices to users. Basically, they allow us to display an at least semi-accurate depiction of the device.

History
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 // DeviceChange represents a set of changes to a Device that will be used
59 // to update a Device.
60 type DeviceChange struct {
61 DeviceID uuid.ID
62 Name *string
63 Owner *uuid.ID
64 Type *DeviceType
65 Created *time.Time
66 LastSeen *time.Time
67 PushToken *string
68 }
70 // Storer is an interface to control how data is stored in and retrieved from
71 // the datastore.
72 type Storer interface {
73 GetDevices(ids []uuid.ID, c context.Context) (map[string]Device, error)
74 UpdateDevice(change DeviceChange, c context.Context) error
75 DeleteDevices(ids []uuid.ID, c context.Context) error
76 CreateDevices(devices []Device, c context.Context) error
77 ListDevicesByOwner(user uuid.ID, c context.Context) ([]Device, error)
79 // These are used for testing only.
80 Factory(c context.Context) (Storer, error)
81 Destroy(c context.Context) error
82 }
84 // GetMany returns as many of the Devices specified by the passed IDs as possible.
85 // They are returned as a map, with the key being the string version of the ID.
86 // No error will be returned if a Device can't be found.
87 func GetMany(ids []uuid.ID, c context.Context) (map[string]Device, error) {
88 results := map[string]Device{}
89 storer, err := getStorer(c)
90 if err != nil {
91 log.Printf("Error retrieving Storer: %+v\n", err)
92 return results, err
93 }
94 results, err = storer.GetDevices(ids, c)
95 if err != nil {
96 log.Printf("Error retrieving Devices from %T: %+v\n", storer, err)
97 return results, err
98 }
99 return results, nil
100 }
102 // Get returns the Device specified by the passed ID. If the Device can't be found,
103 // an ErrDeviceNotFound error is returned.
104 func Get(id uuid.ID, c context.Context) (Device, error) {
105 results, err := GetMany([]uuid.ID{id}, c)
106 if err != nil {
107 return Device{}, err
108 }
109 result, ok := results[id.String()]
110 if !ok {
111 return Device{}, ErrDeviceNotFound
112 }
113 return result, nil
114 }
116 // Update applies the DeviceChange to the passed Device, and returns the result. If
117 // the Device can't be found, an ErrDeviceNotFound error was returned.
118 func Update(device Device, change DeviceChange, c context.Context) (Device, error) {
119 storer, err := getStorer(c)
120 if err != nil {
121 log.Printf("Error retrieving Storer: %+v\n", err)
122 return Device{}, err
123 }
124 change.DeviceID = device.ID
125 err = storer.UpdateDevice(change, c)
126 if err != nil {
127 return Device{}, err
128 }
129 return ApplyChange(device, change), nil
130 }
132 // DeleteMany removes the passed IDs from the datastore. No error is returned if the
133 // ID doesn't correspond to a Device in the datastore.
134 func DeleteMany(ids []uuid.ID, c context.Context) error {
135 storer, err := getStorer(c)
136 if err != nil {
137 log.Printf("Error retrieving Storer: %+v\n", err)
138 return err
139 }
140 return storer.DeleteDevices(ids, c)
141 }
143 // Delete removes the passed ID from the datastore. No error is returned if the ID doesn't
144 // correspond to a Device in the datastore.
145 func Delete(id uuid.ID, c context.Context) error {
146 return DeleteMany([]uuid.ID{id}, c)
147 }
149 // CreateMany stores the passed Devices in the datastore, assigning default values if
150 // necessary. The Devices that were ultimately stored (including any default values, if
151 // applicable) are returned.
152 func CreateMany(devices []Device, c context.Context) ([]Device, error) {
153 storer, err := getStorer(c)
154 if err != nil {
155 log.Printf("Error retrieving Storer: %+v\n", err)
156 return []Device{}, err
157 }
158 modified := make([]Device, 0, len(devices))
159 for _, device := range devices {
160 if device.ID.IsZero() {
161 device.ID = uuid.NewID()
162 }
163 if device.Created.IsZero() {
164 device.Created = time.Now()
165 }
166 if device.LastSeen.IsZero() {
167 device.LastSeen = time.Now()
168 }
169 modified = append(modified, device)
170 }
171 err = storer.CreateDevices(devices, c)
172 if err != nil {
173 return []Device{}, err
174 }
175 return modified, nil
176 }
178 // Create stores the passed Device in the datastore, assigning default values if
179 // necessary. The Devices that were ultimately stored (including any default values, if
180 // applicable) are returned.
181 func Create(device Device, c context.Context) (Device, error) {
182 devices, err := CreateMany([]Device{device}, c)
183 if err != nil {
184 return Device{}, err
185 }
186 // There should never be a case where we don't return a result.
187 // Ideally, we'd return an error here instead of letting the panic
188 // happen, but seeing as I can't come up with a reason the error would
189 // occur, I'm having trouble coming up with a reasonable error to return.
190 return devices[0], nil
191 }
193 // ListByOwner returns a slice of all the Devices with an Owner property that
194 // matches the passed ID. There's no guarantee on the order the Devices will be
195 // returned in.
196 func ListByOwner(user uuid.ID, c context.Context) ([]Device, error) {
197 // 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.
198 storer, err := getStorer(c)
199 if err != nil {
200 log.Printf("Error retrieving Storer: %+v\n", err)
201 return []Device{}, err
202 }
203 devices, err := storer.ListDevicesByOwner(user, c)
204 return devices, err
205 }