github.com/status-im/status-go@v1.1.0/protocol/encryption/multidevice/persistence.go (about) 1 package multidevice 2 3 import "database/sql" 4 5 type sqlitePersistence struct { 6 db *sql.DB 7 } 8 9 func newSQLitePersistence(db *sql.DB) *sqlitePersistence { 10 return &sqlitePersistence{db: db} 11 } 12 13 // GetActiveInstallations returns the active installations for a given identity 14 func (s *sqlitePersistence) GetActiveInstallations(maxInstallations int, identity []byte) ([]*Installation, error) { 15 stmt, err := s.db.Prepare(`SELECT installation_id, version 16 FROM installations 17 WHERE enabled = 1 AND identity = ? 18 ORDER BY timestamp DESC 19 LIMIT ?`) 20 if err != nil { 21 return nil, err 22 } 23 24 var installations []*Installation 25 rows, err := stmt.Query(identity, maxInstallations) 26 if err != nil { 27 return nil, err 28 } 29 defer rows.Close() 30 31 for rows.Next() { 32 var ( 33 installationID string 34 version uint32 35 ) 36 err = rows.Scan( 37 &installationID, 38 &version, 39 ) 40 if err != nil { 41 return nil, err 42 } 43 installations = append(installations, &Installation{ 44 ID: installationID, 45 Version: version, 46 Enabled: true, 47 }) 48 49 } 50 51 return installations, nil 52 53 } 54 55 // GetInstallations returns all the installations for a given identity 56 // we both return the installations & the metadata 57 // metadata is currently stored in a separate table, as in some cases we 58 // might have metadata for a device, but no other information on the device 59 func (s *sqlitePersistence) GetInstallations(identity []byte) ([]*Installation, error) { 60 installationMap := make(map[string]*Installation) 61 var installations []*Installation 62 63 // We query both tables as sqlite does not support full outer joins 64 installationsStmt, err := s.db.Prepare(`SELECT installation_id, version, enabled, timestamp FROM installations WHERE identity = ?`) 65 if err != nil { 66 return nil, err 67 } 68 defer installationsStmt.Close() 69 70 installationRows, err := installationsStmt.Query(identity) 71 if err != nil { 72 return nil, err 73 } 74 75 for installationRows.Next() { 76 var installation Installation 77 err = installationRows.Scan( 78 &installation.ID, 79 &installation.Version, 80 &installation.Enabled, 81 &installation.Timestamp, 82 ) 83 if err != nil { 84 return nil, err 85 } 86 // We initialized to empty in this case as we want to 87 // return metadata as well in this endpoint, but not in others 88 installation.InstallationMetadata = &InstallationMetadata{} 89 installationMap[installation.ID] = &installation 90 } 91 92 metadataStmt, err := s.db.Prepare(`SELECT installation_id, name, device_type, fcm_token FROM installation_metadata WHERE identity = ?`) 93 if err != nil { 94 return nil, err 95 } 96 defer metadataStmt.Close() 97 98 metadataRows, err := metadataStmt.Query(identity) 99 if err != nil { 100 return nil, err 101 } 102 103 for metadataRows.Next() { 104 var ( 105 installationID string 106 name sql.NullString 107 deviceType sql.NullString 108 fcmToken sql.NullString 109 installation *Installation 110 ) 111 err = metadataRows.Scan( 112 &installationID, 113 &name, 114 &deviceType, 115 &fcmToken, 116 ) 117 if err != nil { 118 return nil, err 119 } 120 if _, ok := installationMap[installationID]; ok { 121 installation = installationMap[installationID] 122 } else { 123 installation = &Installation{ID: installationID} 124 } 125 installation.InstallationMetadata = &InstallationMetadata{ 126 Name: name.String, 127 DeviceType: deviceType.String, 128 FCMToken: fcmToken.String, 129 } 130 installationMap[installationID] = installation 131 } 132 133 for _, installation := range installationMap { 134 installations = append(installations, installation) 135 } 136 137 return installations, nil 138 } 139 140 // AddInstallations adds the installations for a given identity, maintaining the enabled flag 141 func (s *sqlitePersistence) AddInstallations(identity []byte, timestamp int64, installations []*Installation, defaultEnabled bool) ([]*Installation, error) { 142 tx, err := s.db.Begin() 143 if err != nil { 144 return nil, err 145 } 146 147 var insertedInstallations []*Installation 148 149 for _, installation := range installations { 150 stmt, err := tx.Prepare(`SELECT enabled, version 151 FROM installations 152 WHERE identity = ? AND installation_id = ? 153 LIMIT 1`) 154 if err != nil { 155 return nil, err 156 } 157 defer stmt.Close() 158 159 var oldEnabled bool 160 // We don't override version once we saw one 161 var oldVersion uint32 162 latestVersion := installation.Version 163 164 err = stmt.QueryRow(identity, installation.ID).Scan(&oldEnabled, &oldVersion) 165 if err != nil && err != sql.ErrNoRows { 166 return nil, err 167 } 168 169 if err == sql.ErrNoRows { 170 stmt, err = tx.Prepare(`INSERT INTO installations(identity, installation_id, timestamp, enabled, version) 171 VALUES (?, ?, ?, ?, ?)`) 172 if err != nil { 173 return nil, err 174 } 175 defer stmt.Close() 176 177 _, err = stmt.Exec( 178 identity, 179 installation.ID, 180 timestamp, 181 defaultEnabled, 182 latestVersion, 183 ) 184 if err != nil { 185 return nil, err 186 } 187 insertedInstallations = append(insertedInstallations, installation) 188 } else { 189 // We update timestamp if present without changing enabled, only if this is a new bundle 190 // and we set the version to the latest we ever saw 191 if oldVersion > installation.Version { 192 latestVersion = oldVersion 193 } 194 195 stmt, err = tx.Prepare(`UPDATE installations 196 SET timestamp = ?, enabled = ?, version = ? 197 WHERE identity = ? 198 AND installation_id = ? 199 AND timestamp < ?`) 200 if err != nil { 201 return nil, err 202 } 203 defer stmt.Close() 204 205 _, err = stmt.Exec( 206 timestamp, 207 oldEnabled, 208 latestVersion, 209 identity, 210 installation.ID, 211 timestamp, 212 ) 213 if err != nil { 214 return nil, err 215 } 216 } 217 218 } 219 220 if err := tx.Commit(); err != nil { 221 _ = tx.Rollback() 222 return nil, err 223 } 224 225 return insertedInstallations, nil 226 227 } 228 229 // EnableInstallation enables the installation 230 func (s *sqlitePersistence) EnableInstallation(identity []byte, installationID string) error { 231 stmt, err := s.db.Prepare(`UPDATE installations 232 SET enabled = 1 233 WHERE identity = ? AND installation_id = ?`) 234 if err != nil { 235 return err 236 } 237 238 _, err = stmt.Exec(identity, installationID) 239 return err 240 241 } 242 243 // DisableInstallation disable the installation 244 func (s *sqlitePersistence) DisableInstallation(identity []byte, installationID string) error { 245 stmt, err := s.db.Prepare(`UPDATE installations 246 SET enabled = 0 247 WHERE identity = ? AND installation_id = ?`) 248 if err != nil { 249 return err 250 } 251 252 _, err = stmt.Exec(identity, installationID) 253 return err 254 } 255 256 // SetInstallationMetadata sets the metadata for a given installation 257 func (s *sqlitePersistence) SetInstallationMetadata(identity []byte, installationID string, metadata *InstallationMetadata) error { 258 stmt, err := s.db.Prepare(`INSERT INTO installation_metadata(name, device_type, fcm_token, identity, installation_id) VALUES(?,?,?,?,?)`) 259 if err != nil { 260 return err 261 } 262 263 _, err = stmt.Exec(metadata.Name, metadata.DeviceType, metadata.FCMToken, identity, installationID) 264 return err 265 } 266 267 // SetInstallationName sets the only the name in metadata for a given installation 268 func (s *sqlitePersistence) SetInstallationName(identity []byte, installationID string, name string) error { 269 stmt, err := s.db.Prepare(`UPDATE installation_metadata 270 SET name = ? 271 WHERE identity = ? AND installation_id = ?`) 272 if err != nil { 273 return err 274 } 275 276 _, err = stmt.Exec(name, identity, installationID) 277 return err 278 }