@@ -1533,6 +1533,70 @@ def _txn(txn: LoggingTransaction) -> int:
15331533
15341534 return rows
15351535
1536+ async def check_too_many_devices_for_user (self , user_id : str ) -> Collection [str ]:
1537+ """Check if the user has a lot of devices, and if so return the set of
1538+ devices we can prune.
1539+
1540+ This does *not* return hidden devices or devices with E2E keys.
1541+ """
1542+
1543+ num_devices = await self .db_pool .simple_select_one_onecol (
1544+ table = "devices" ,
1545+ keyvalues = {"user_id" : user_id , "hidden" : False },
1546+ retcol = "COALESCE(COUNT(*), 0)" ,
1547+ desc = "count_devices" ,
1548+ )
1549+
1550+ # We let users have up to ten devices without pruning.
1551+ if num_devices <= 10 :
1552+ return ()
1553+
1554+ # We prune everything older than N days.
1555+ max_last_seen = self ._clock .time_msec () - 14 * 24 * 60 * 60 * 1000
1556+
1557+ if num_devices > 50 :
1558+ # If the user has more than 50 devices, then we chose a last seen
1559+ # that ensures we keep at most 50 devices.
1560+ sql = """
1561+ SELECT last_seen FROM devices
1562+ WHERE
1563+ user_id = ?
1564+ AND NOT hidden
1565+ AND last_seen IS NOT NULL
1566+ AND key_json IS NULL
1567+ ORDER BY last_seen DESC
1568+ LIMIT 1
1569+ OFFSET 50
1570+ """
1571+
1572+ rows = await self .db_pool .execute (
1573+ "check_too_many_devices_for_user_last_seen" , None , sql , (user_id ,)
1574+ )
1575+ if rows :
1576+ max_last_seen = max (rows [0 ][0 ], max_last_seen )
1577+
1578+ # Now fetch the devices to delete.
1579+ sql = """
1580+ SELECT DISTINCT device_id FROM devices
1581+ LEFT JOIN e2e_device_keys_json USING (user_id, device_id)
1582+ WHERE
1583+ user_id = ?
1584+ AND NOT hidden
1585+ AND last_seen < ?
1586+ AND key_json IS NULL
1587+ """
1588+
1589+ def check_too_many_devices_for_user_txn (
1590+ txn : LoggingTransaction ,
1591+ ) -> Collection [str ]:
1592+ txn .execute (sql , (user_id , max_last_seen ))
1593+ return {device_id for device_id , in txn }
1594+
1595+ return await self .db_pool .runInteraction (
1596+ "check_too_many_devices_for_user" ,
1597+ check_too_many_devices_for_user_txn ,
1598+ )
1599+
15361600
15371601class DeviceStore (DeviceWorkerStore , DeviceBackgroundUpdateStore ):
15381602 # Because we have write access, this will be a StreamIdGenerator
@@ -1591,6 +1655,7 @@ async def store_device(
15911655 values = {},
15921656 insertion_values = {
15931657 "display_name" : initial_device_display_name ,
1658+ "last_seen" : self ._clock .time_msec (),
15941659 "hidden" : False ,
15951660 },
15961661 desc = "store_device" ,
@@ -1636,7 +1701,7 @@ async def store_device(
16361701 )
16371702 raise StoreError (500 , "Problem storing device." )
16381703
1639- async def delete_devices (self , user_id : str , device_ids : List [str ]) -> None :
1704+ async def delete_devices (self , user_id : str , device_ids : Collection [str ]) -> None :
16401705 """Deletes several devices.
16411706
16421707 Args:
0 commit comments