diff --git a/applications/datastore.go b/applications/datastore.go index d23d9e5e..c80f08b1 100644 --- a/applications/datastore.go +++ b/applications/datastore.go @@ -66,13 +66,13 @@ func (store pgStore) New(a *Application) (string, error) { dynamic_size, is_validated ) - VALUES ($0, $1, $2, $3, $4, $5, $6) + VALUES ($1, $2, $3, $4, $5, $6, $7) ON CONFLICT (name, version) DO UPDATE SET - identifier=$1, - short_version=$2, - bundle_size=$4, - dynamic_size=$5, - is_validated=$6 + identifier=$2, + short_version=$3, + bundle_size=$5, + dynamic_size=$6, + is_validated=$7 RETURNING application_uuid;`, a.Name, a.Identifier, @@ -92,7 +92,16 @@ func (store pgStore) New(a *Application) (string, error) { // Retrieve a list of applications func (store pgStore) Applications(params ...interface{}) ([]Application, error) { - stmt := `SELECT * FROM applications` + stmt := `SELECT + application_uuid, + name, + identifier, + short_version, + version, + bundle_size, + dynamic_size, + is_validated + FROM applications` stmt = addWhereFilters(stmt, "OR", params...) var apps []Application @@ -106,13 +115,25 @@ func (store pgStore) Applications(params ...interface{}) ([]Application, error) // Retrieve only applications which are installed on the given device. func (store pgStore) GetApplicationsByDeviceUUID(deviceUUID string) ([]Application, error) { + if len(deviceUUID) == 0 { + return nil, errors.New("empty uuid supplied to GetApplicationsByDeviceUUID") + } + var apps []Application - query := `SELECT * FROM applications + query := `SELECT + applications.application_uuid AS application_uuid, + name, + identifier, + short_version, + version, + bundle_size, + dynamic_size, + is_validated + FROM applications RIGHT JOIN devices_applications ON applications.application_uuid = devices_applications.application_uuid WHERE devices_applications.device_uuid=$1` err := store.Select(&apps, query, deviceUUID) - if err != nil { return nil, err } @@ -122,6 +143,14 @@ func (store pgStore) GetApplicationsByDeviceUUID(deviceUUID string) ([]Applicati // Associate the given applications with the given device uuid by inserting into `device_applications`. func (store pgStore) SaveApplicationByDeviceUUID(deviceUUID string, app *Application) error { + if deviceUUID == "" { + return errors.New("empty uuid supplied to SaveApplicationByDeviceUUID for deviceUUID") + } + + if app.UUID == "" { + return errors.New("empty uuid supplied to SaveApplicationByDeviceUUID for application") + } + stmt := `INSERT INTO devices_applications ( device_uuid, application_uuid ) VALUES ($1, $2)` diff --git a/connect/service.go b/connect/service.go index 10394cb7..a40a0a36 100644 --- a/connect/service.go +++ b/connect/service.go @@ -41,6 +41,11 @@ func (svc service) Acknowledge(ctx context.Context, req mdm.Response) (int, erro if err := svc.ackQueryResponses(req); err != nil { return 0, err } + case "InstalledApplicationList": + if err := svc.ackInstalledApplicationList(req); err != nil { + fmt.Printf("Got an error acknowledging InstalledApplicationList: %v\n", err) + return 0, err + } default: // Need to handle the absence of RequestType in IOS8 devices if req.QueryResponses.UDID != "" { @@ -137,62 +142,93 @@ func (svc service) ackQueryResponses(req mdm.Response) error { // Acknowledge a response to `InstalledApplicationList`. func (svc service) ackInstalledApplicationList(req mdm.Response) error { - device, err := svc.devices.GetDeviceByUDID(req.UDID) + fmt.Println("Acknowledging installed applications") + + device, err := svc.devices.GetDeviceByUDID(req.UDID, "device_uuid") if err != nil { - return err + return errors.Wrap(err, "getting a device record by udid") + } + + requestApps := make([]apps.Application, len(req.InstalledApplicationList)) + // Update or insert application records that do not exist, returning the UUID so that it can be inserted for + // the device sending the response. + for _, reqApp := range req.InstalledApplicationList { + identifier := sql.NullString{reqApp.Identifier, reqApp.Identifier != ""} + shortVersion := sql.NullString{reqApp.ShortVersion, reqApp.ShortVersion != ""} + version := sql.NullString{reqApp.Version, reqApp.Version != ""} + + bundleSize := sql.NullInt64{} + bundleSize.Scan(reqApp.BundleSize) + + dynamicSize := sql.NullInt64{} + dynamicSize.Scan(reqApp.DynamicSize) + + newApp := apps.Application{ + Name: reqApp.Name, + Identifier: identifier, + ShortVersion: shortVersion, + Version: version, + BundleSize: bundleSize, + DynamicSize: dynamicSize, + } + appUuid, err := svc.apps.New(&newApp) + if err != nil { + return err + } + + newApp.UUID = appUuid + requestApps = append(requestApps, newApp) } deviceApps, err := svc.apps.GetApplicationsByDeviceUUID(device.UUID) if err != nil { - return err + return errors.Wrap(err, "getting applications by device uuid") } - var removed []apps.Application = make([]apps.Application, len(req.InstalledApplicationList)) - var deviceAppsRemaining []apps.Application = make([]apps.Application, len(req.InstalledApplicationList)) + var deviceRemoved []apps.Application = make([]apps.Application, len(req.InstalledApplicationList)) + var deviceNotRemoved []apps.Application = make([]apps.Application, len(req.InstalledApplicationList)) // Check to see whether installed applications exist in the latest response // If they do not, they are added to the removed slice. // TODO: This is a pretty horrible algorithm and I should re-design it at some point. m. + fmt.Println("Determining apps removed") removedouter: for _, deviceApp := range deviceApps { - for _, app := range req.InstalledApplicationList { - if deviceApp.Version.Valid && deviceApp.Version.String == app.Version && deviceApp.Name == app.Name { - deviceAppsRemaining = append(deviceAppsRemaining, deviceApp) + for _, app := range requestApps { + if deviceApp.Version == app.Version && deviceApp.Name == app.Name { + deviceNotRemoved = append(deviceNotRemoved, deviceApp) continue removedouter } } - removed = append(removed, deviceApp) + deviceRemoved = append(deviceRemoved, deviceApp) } // Any installed applications that are already represented in the `applications` table AND // allocated to the device in `devices_applications` should be skipped. var updated []apps.Application = make([]apps.Application, len(req.InstalledApplicationList)) + fmt.Println("Determining apps changed or added") skip: - for _, ackApp := range req.InstalledApplicationList { - for _, app := range deviceAppsRemaining { - if app.Name == ackApp.Name && app.Version.Valid && app.Version.String == ackApp.Version { + for _, ackApp := range requestApps { + for _, app := range deviceNotRemoved { + if app.Name == ackApp.Name && app.Version == ackApp.Version { continue skip } } - identifier := sql.NullString{ackApp.Identifier, ackApp.Identifier != ""} - - appUpdated := apps.Application{ - Name: ackApp.Name, - Identifier: identifier, - //ShortVersion: sql.NullString{}.Scan(ackApp.ShortVersion), - //Version: sql.NullString{}.Scan(ackApp.Version), - //BundleSize: sql.NullInt64{}.Scan(ackApp.BundleSize), - //DynamicSize: sql.NullInt64{}.Scan(ackApp.DynamicSize), - //IsValidated: sql.NullBool{}.Scan(ackApp.IsValidated), - } - updated = append(updated, appUpdated) + updated = append(updated, ackApp) } - fmt.Printf("removed %#v\n", removed) - fmt.Printf("updated %#v\n", updated) + fmt.Printf("removed %d application(s)\n", len(deviceRemoved)) + fmt.Printf("updated %d application(s)\n", len(updated)) + + for _, insertApp := range updated { + if err := svc.apps.SaveApplicationByDeviceUUID(device.UUID, &insertApp); err != nil { + fmt.Printf("could not save application, no valid uuid: %s\n", insertApp.Name) + // return errors.Wrap(err, "saving installed application for a device") + } + } return nil }