mirror of
https://github.com/whyorean/AuroraStore.git
synced 2026-08-01 01:39:12 -04:00
Move Exodus trackers to a local database
Replace the bundled exodus_trackers.json asset with a Room-backed tracker dictionary (exodus_tracker table) synced from the Exodus /api/trackers endpoint every 3 days via a WorkManager job enqueued after session verification on the splash screen. Per-app reports are still fetched live from the search API and their tracker ids resolved to details from the local table (best-effort). Also fixes Exodus report deserialization to read the snake_case version_code and creation_date keys.
This commit is contained in:
560
app/schemas/com.aurora.store.data.room.AuroraDatabase/11.json
Normal file
560
app/schemas/com.aurora.store.data.room.AuroraDatabase/11.json
Normal file
@@ -0,0 +1,560 @@
|
||||
{
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 11,
|
||||
"identityHash": "b0023e89b17406affdf61fc021efb2ad",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "download",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`packageName` TEXT NOT NULL, `versionCode` INTEGER NOT NULL, `offerType` INTEGER NOT NULL, `isInstalled` INTEGER NOT NULL, `displayName` TEXT NOT NULL, `iconURL` TEXT NOT NULL, `size` INTEGER NOT NULL, `id` INTEGER NOT NULL, `downloadStatus` TEXT NOT NULL, `progress` INTEGER NOT NULL, `speed` INTEGER NOT NULL, `timeRemaining` INTEGER NOT NULL, `totalFiles` INTEGER NOT NULL, `downloadedFiles` INTEGER NOT NULL, `fileList` TEXT NOT NULL, `sharedLibs` TEXT NOT NULL, `targetSdk` INTEGER NOT NULL, `downloadedAt` INTEGER NOT NULL, `requiresGMS` INTEGER NOT NULL, PRIMARY KEY(`packageName`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "packageName",
|
||||
"columnName": "packageName",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "versionCode",
|
||||
"columnName": "versionCode",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "offerType",
|
||||
"columnName": "offerType",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "isInstalled",
|
||||
"columnName": "isInstalled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "displayName",
|
||||
"columnName": "displayName",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "iconURL",
|
||||
"columnName": "iconURL",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "size",
|
||||
"columnName": "size",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "status",
|
||||
"columnName": "downloadStatus",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "progress",
|
||||
"columnName": "progress",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "speed",
|
||||
"columnName": "speed",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "timeRemaining",
|
||||
"columnName": "timeRemaining",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "totalFiles",
|
||||
"columnName": "totalFiles",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "downloadedFiles",
|
||||
"columnName": "downloadedFiles",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "fileList",
|
||||
"columnName": "fileList",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "sharedLibs",
|
||||
"columnName": "sharedLibs",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "targetSdk",
|
||||
"columnName": "targetSdk",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "downloadedAt",
|
||||
"columnName": "downloadedAt",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "requiresGMS",
|
||||
"columnName": "requiresGMS",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"packageName"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"tableName": "favourite",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`packageName` TEXT NOT NULL, `displayName` TEXT NOT NULL, `iconURL` TEXT NOT NULL, `added` INTEGER NOT NULL, `mode` TEXT NOT NULL, PRIMARY KEY(`packageName`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "packageName",
|
||||
"columnName": "packageName",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "displayName",
|
||||
"columnName": "displayName",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "iconURL",
|
||||
"columnName": "iconURL",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "added",
|
||||
"columnName": "added",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "mode",
|
||||
"columnName": "mode",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"packageName"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"tableName": "update",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`packageName` TEXT NOT NULL, `versionCode` INTEGER NOT NULL, `versionName` TEXT NOT NULL, `displayName` TEXT NOT NULL, `iconURL` TEXT NOT NULL, `changelog` TEXT NOT NULL, `id` INTEGER NOT NULL, `developerName` TEXT NOT NULL, `size` INTEGER NOT NULL, `updatedOn` TEXT NOT NULL, `hasValidCert` INTEGER NOT NULL, `offerType` INTEGER NOT NULL, `fileList` TEXT NOT NULL, `sharedLibs` TEXT NOT NULL, `targetSdk` INTEGER NOT NULL, `isIncompatible` INTEGER NOT NULL, PRIMARY KEY(`packageName`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "packageName",
|
||||
"columnName": "packageName",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "versionCode",
|
||||
"columnName": "versionCode",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "versionName",
|
||||
"columnName": "versionName",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "displayName",
|
||||
"columnName": "displayName",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "iconURL",
|
||||
"columnName": "iconURL",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "changelog",
|
||||
"columnName": "changelog",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "developerName",
|
||||
"columnName": "developerName",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "size",
|
||||
"columnName": "size",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "updatedOn",
|
||||
"columnName": "updatedOn",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "hasValidCert",
|
||||
"columnName": "hasValidCert",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "offerType",
|
||||
"columnName": "offerType",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "fileList",
|
||||
"columnName": "fileList",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "sharedLibs",
|
||||
"columnName": "sharedLibs",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "targetSdk",
|
||||
"columnName": "targetSdk",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "isIncompatible",
|
||||
"columnName": "isIncompatible",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"packageName"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"tableName": "ignored_update",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`packageName` TEXT NOT NULL, `ignoredVersionCode` INTEGER, PRIMARY KEY(`packageName`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "packageName",
|
||||
"columnName": "packageName",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "ignoredVersionCode",
|
||||
"columnName": "ignoredVersionCode",
|
||||
"affinity": "INTEGER"
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"packageName"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"tableName": "review",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`packageName` TEXT NOT NULL, `accountEmail` TEXT NOT NULL, `title` TEXT NOT NULL, `comment` TEXT NOT NULL, `rating` INTEGER NOT NULL, `commentId` TEXT NOT NULL, `userName` TEXT NOT NULL, `userPhotoUrl` TEXT NOT NULL, `appVersion` TEXT NOT NULL, `timeStamp` INTEGER NOT NULL, `synced` INTEGER NOT NULL, PRIMARY KEY(`packageName`, `accountEmail`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "packageName",
|
||||
"columnName": "packageName",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "accountEmail",
|
||||
"columnName": "accountEmail",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "title",
|
||||
"columnName": "title",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "comment",
|
||||
"columnName": "comment",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "rating",
|
||||
"columnName": "rating",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "commentId",
|
||||
"columnName": "commentId",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "userName",
|
||||
"columnName": "userName",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "userPhotoUrl",
|
||||
"columnName": "userPhotoUrl",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "appVersion",
|
||||
"columnName": "appVersion",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "timeStamp",
|
||||
"columnName": "timeStamp",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "synced",
|
||||
"columnName": "synced",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"packageName",
|
||||
"accountEmail"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"tableName": "account",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `type` TEXT NOT NULL, `email` TEXT NOT NULL, `displayName` TEXT, `profilePicUrl` TEXT, `aasToken` TEXT, `authToken` TEXT, `tokenType` TEXT NOT NULL, `authViaMicroG` INTEGER NOT NULL, `authDataJson` TEXT, `isDefault` INTEGER NOT NULL, `addedAt` INTEGER NOT NULL, PRIMARY KEY(`id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "type",
|
||||
"columnName": "type",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "email",
|
||||
"columnName": "email",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "displayName",
|
||||
"columnName": "displayName",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "profilePicUrl",
|
||||
"columnName": "profilePicUrl",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "aasToken",
|
||||
"columnName": "aasToken",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "authToken",
|
||||
"columnName": "authToken",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "tokenType",
|
||||
"columnName": "tokenType",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "authViaMicroG",
|
||||
"columnName": "authViaMicroG",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "authDataJson",
|
||||
"columnName": "authDataJson",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isDefault",
|
||||
"columnName": "isDefault",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "addedAt",
|
||||
"columnName": "addedAt",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"id"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"tableName": "app_account_binding",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`packageName` TEXT NOT NULL, `accountId` TEXT NOT NULL, PRIMARY KEY(`packageName`), FOREIGN KEY(`accountId`) REFERENCES `account`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "packageName",
|
||||
"columnName": "packageName",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "accountId",
|
||||
"columnName": "accountId",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"packageName"
|
||||
]
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_app_account_binding_accountId",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"accountId"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE INDEX IF NOT EXISTS `index_app_account_binding_accountId` ON `${TABLE_NAME}` (`accountId`)"
|
||||
}
|
||||
],
|
||||
"foreignKeys": [
|
||||
{
|
||||
"table": "account",
|
||||
"onDelete": "CASCADE",
|
||||
"onUpdate": "NO ACTION",
|
||||
"columns": [
|
||||
"accountId"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"id"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tableName": "exodus_tracker",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `signature` TEXT NOT NULL, `date` TEXT NOT NULL, PRIMARY KEY(`id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "url",
|
||||
"columnName": "url",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "signature",
|
||||
"columnName": "signature",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "date",
|
||||
"columnName": "date",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"id"
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"setupQueries": [
|
||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'b0023e89b17406affdf61fc021efb2ad')"
|
||||
]
|
||||
}
|
||||
}
|
||||
566
app/schemas/com.aurora.store.data.room.AuroraDatabase/12.json
Normal file
566
app/schemas/com.aurora.store.data.room.AuroraDatabase/12.json
Normal file
@@ -0,0 +1,566 @@
|
||||
{
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 12,
|
||||
"identityHash": "79baa70617146871e27d9a37a59f1814",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "download",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`packageName` TEXT NOT NULL, `versionCode` INTEGER NOT NULL, `offerType` INTEGER NOT NULL, `isInstalled` INTEGER NOT NULL, `displayName` TEXT NOT NULL, `iconURL` TEXT NOT NULL, `size` INTEGER NOT NULL, `id` INTEGER NOT NULL, `downloadStatus` TEXT NOT NULL, `progress` INTEGER NOT NULL, `speed` INTEGER NOT NULL, `timeRemaining` INTEGER NOT NULL, `totalFiles` INTEGER NOT NULL, `downloadedFiles` INTEGER NOT NULL, `fileList` TEXT NOT NULL, `sharedLibs` TEXT NOT NULL, `targetSdk` INTEGER NOT NULL, `downloadedAt` INTEGER NOT NULL, `requiresGMS` INTEGER NOT NULL, PRIMARY KEY(`packageName`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "packageName",
|
||||
"columnName": "packageName",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "versionCode",
|
||||
"columnName": "versionCode",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "offerType",
|
||||
"columnName": "offerType",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "isInstalled",
|
||||
"columnName": "isInstalled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "displayName",
|
||||
"columnName": "displayName",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "iconURL",
|
||||
"columnName": "iconURL",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "size",
|
||||
"columnName": "size",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "status",
|
||||
"columnName": "downloadStatus",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "progress",
|
||||
"columnName": "progress",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "speed",
|
||||
"columnName": "speed",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "timeRemaining",
|
||||
"columnName": "timeRemaining",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "totalFiles",
|
||||
"columnName": "totalFiles",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "downloadedFiles",
|
||||
"columnName": "downloadedFiles",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "fileList",
|
||||
"columnName": "fileList",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "sharedLibs",
|
||||
"columnName": "sharedLibs",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "targetSdk",
|
||||
"columnName": "targetSdk",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "downloadedAt",
|
||||
"columnName": "downloadedAt",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "requiresGMS",
|
||||
"columnName": "requiresGMS",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"packageName"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"tableName": "favourite",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`packageName` TEXT NOT NULL, `displayName` TEXT NOT NULL, `iconURL` TEXT NOT NULL, `added` INTEGER NOT NULL, `mode` TEXT NOT NULL, PRIMARY KEY(`packageName`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "packageName",
|
||||
"columnName": "packageName",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "displayName",
|
||||
"columnName": "displayName",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "iconURL",
|
||||
"columnName": "iconURL",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "added",
|
||||
"columnName": "added",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "mode",
|
||||
"columnName": "mode",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"packageName"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"tableName": "update",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`packageName` TEXT NOT NULL, `versionCode` INTEGER NOT NULL, `versionName` TEXT NOT NULL, `displayName` TEXT NOT NULL, `iconURL` TEXT NOT NULL, `changelog` TEXT NOT NULL, `id` INTEGER NOT NULL, `developerName` TEXT NOT NULL, `size` INTEGER NOT NULL, `updatedOn` TEXT NOT NULL, `hasValidCert` INTEGER NOT NULL, `offerType` INTEGER NOT NULL, `fileList` TEXT NOT NULL, `sharedLibs` TEXT NOT NULL, `targetSdk` INTEGER NOT NULL, `isIncompatible` INTEGER NOT NULL, PRIMARY KEY(`packageName`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "packageName",
|
||||
"columnName": "packageName",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "versionCode",
|
||||
"columnName": "versionCode",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "versionName",
|
||||
"columnName": "versionName",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "displayName",
|
||||
"columnName": "displayName",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "iconURL",
|
||||
"columnName": "iconURL",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "changelog",
|
||||
"columnName": "changelog",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "developerName",
|
||||
"columnName": "developerName",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "size",
|
||||
"columnName": "size",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "updatedOn",
|
||||
"columnName": "updatedOn",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "hasValidCert",
|
||||
"columnName": "hasValidCert",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "offerType",
|
||||
"columnName": "offerType",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "fileList",
|
||||
"columnName": "fileList",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "sharedLibs",
|
||||
"columnName": "sharedLibs",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "targetSdk",
|
||||
"columnName": "targetSdk",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "isIncompatible",
|
||||
"columnName": "isIncompatible",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"packageName"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"tableName": "ignored_update",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`packageName` TEXT NOT NULL, `ignoredVersionCode` INTEGER, PRIMARY KEY(`packageName`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "packageName",
|
||||
"columnName": "packageName",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "ignoredVersionCode",
|
||||
"columnName": "ignoredVersionCode",
|
||||
"affinity": "INTEGER"
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"packageName"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"tableName": "review",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`packageName` TEXT NOT NULL, `accountEmail` TEXT NOT NULL, `title` TEXT NOT NULL, `comment` TEXT NOT NULL, `rating` INTEGER NOT NULL, `commentId` TEXT NOT NULL, `userName` TEXT NOT NULL, `userPhotoUrl` TEXT NOT NULL, `appVersion` TEXT NOT NULL, `timeStamp` INTEGER NOT NULL, `synced` INTEGER NOT NULL, PRIMARY KEY(`packageName`, `accountEmail`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "packageName",
|
||||
"columnName": "packageName",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "accountEmail",
|
||||
"columnName": "accountEmail",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "title",
|
||||
"columnName": "title",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "comment",
|
||||
"columnName": "comment",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "rating",
|
||||
"columnName": "rating",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "commentId",
|
||||
"columnName": "commentId",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "userName",
|
||||
"columnName": "userName",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "userPhotoUrl",
|
||||
"columnName": "userPhotoUrl",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "appVersion",
|
||||
"columnName": "appVersion",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "timeStamp",
|
||||
"columnName": "timeStamp",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "synced",
|
||||
"columnName": "synced",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"packageName",
|
||||
"accountEmail"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"tableName": "account",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `type` TEXT NOT NULL, `email` TEXT NOT NULL, `displayName` TEXT, `profilePicUrl` TEXT, `aasToken` TEXT, `authToken` TEXT, `tokenType` TEXT NOT NULL, `authViaMicroG` INTEGER NOT NULL, `authDataJson` TEXT, `isDefault` INTEGER NOT NULL, `addedAt` INTEGER NOT NULL, PRIMARY KEY(`id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "type",
|
||||
"columnName": "type",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "email",
|
||||
"columnName": "email",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "displayName",
|
||||
"columnName": "displayName",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "profilePicUrl",
|
||||
"columnName": "profilePicUrl",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "aasToken",
|
||||
"columnName": "aasToken",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "authToken",
|
||||
"columnName": "authToken",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "tokenType",
|
||||
"columnName": "tokenType",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "authViaMicroG",
|
||||
"columnName": "authViaMicroG",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "authDataJson",
|
||||
"columnName": "authDataJson",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isDefault",
|
||||
"columnName": "isDefault",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "addedAt",
|
||||
"columnName": "addedAt",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"id"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"tableName": "app_account_binding",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`packageName` TEXT NOT NULL, `accountId` TEXT NOT NULL, PRIMARY KEY(`packageName`), FOREIGN KEY(`accountId`) REFERENCES `account`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "packageName",
|
||||
"columnName": "packageName",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "accountId",
|
||||
"columnName": "accountId",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"packageName"
|
||||
]
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_app_account_binding_accountId",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"accountId"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE INDEX IF NOT EXISTS `index_app_account_binding_accountId` ON `${TABLE_NAME}` (`accountId`)"
|
||||
}
|
||||
],
|
||||
"foreignKeys": [
|
||||
{
|
||||
"table": "account",
|
||||
"onDelete": "CASCADE",
|
||||
"onUpdate": "NO ACTION",
|
||||
"columns": [
|
||||
"accountId"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"id"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tableName": "exodus_tracker",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `signature` TEXT NOT NULL, `date` TEXT NOT NULL, `categories` TEXT NOT NULL, PRIMARY KEY(`id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "url",
|
||||
"columnName": "url",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "signature",
|
||||
"columnName": "signature",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "date",
|
||||
"columnName": "date",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "categories",
|
||||
"columnName": "categories",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"id"
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"setupQueries": [
|
||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '79baa70617146871e27d9a37a59f1814')"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
package com.aurora.store.data.providers
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import dagger.hilt.android.testing.HiltAndroidRule
|
||||
import dagger.hilt.android.testing.HiltAndroidTest
|
||||
import javax.inject.Inject
|
||||
import org.json.JSONObject
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@HiltAndroidTest
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class ExodusModuleTest {
|
||||
|
||||
@get:Rule
|
||||
var hiltAndroidRule = HiltAndroidRule(this)
|
||||
|
||||
@Inject
|
||||
lateinit var exodusTrackers: JSONObject
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
hiltAndroidRule.inject()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testTrackersJsonIsNotEmpty() {
|
||||
assertThat(exodusTrackers.toString()).isNotEmpty()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testTrackersJsonContainsTrackers() {
|
||||
val trackers = mapOf(
|
||||
"com.facebook.flipper" to 392,
|
||||
"com.google.analytics." to 48,
|
||||
"com.google.firebase.firebase_analytics" to 49,
|
||||
"com.google.ads." to 312
|
||||
)
|
||||
trackers.forEach { (codeSignature, id) ->
|
||||
assertThat(
|
||||
exodusTrackers.getJSONObject(id.toString())
|
||||
.getString("code_signature")
|
||||
.contains(codeSignature)
|
||||
).isTrue()
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -31,6 +31,7 @@ object Constants {
|
||||
const val EXODUS_SUBMIT_PAGE = "https://reports.exodus-privacy.eu.org/analysis/submit/#"
|
||||
const val EXODUS_REPORT_URL = "https://reports.exodus-privacy.eu.org/reports/"
|
||||
const val EXODUS_SEARCH_URL = "https://reports.exodus-privacy.eu.org/api/search/"
|
||||
const val EXODUS_TRACKERS_URL = "https://reports.exodus-privacy.eu.org/api/trackers"
|
||||
|
||||
const val PLEXUS_API_URL = "https://plexus.techlore.tech/api/v1/apps"
|
||||
const val PLEXUS_SEARCH_URL = "https://plexus.techlore.tech/?q="
|
||||
|
||||
@@ -63,6 +63,7 @@ import com.aurora.store.compose.composition.LocalNetworkStatus
|
||||
import com.aurora.store.compose.navigation.Destination
|
||||
import com.aurora.store.data.model.AuthState
|
||||
import com.aurora.store.data.model.NetworkStatus
|
||||
import com.aurora.store.data.work.ExodusTrackerWorker
|
||||
import com.aurora.store.util.CertUtil.GOOGLE_ACCOUNT_TYPE
|
||||
import com.aurora.store.util.CertUtil.GOOGLE_PLAY_AUTH_TOKEN_TYPE
|
||||
import com.aurora.store.util.CertUtil.GOOGLE_PLAY_CERT
|
||||
@@ -113,6 +114,7 @@ fun SplashScreen(
|
||||
AuthState.Valid, AuthState.SignedIn -> {
|
||||
anonymousLoading = false
|
||||
googleLoading = false
|
||||
ExodusTrackerWorker.enqueue(context)
|
||||
when {
|
||||
!deepLinkDevId.isNullOrBlank() -> onNavigateTo(
|
||||
Destination.DevProfile(deepLinkDevId)
|
||||
|
||||
137
app/src/main/java/com/aurora/store/data/ExodusRepository.kt
Normal file
137
app/src/main/java/com/aurora/store/data/ExodusRepository.kt
Normal file
@@ -0,0 +1,137 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Aurora OSS
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package com.aurora.store.data
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import com.aurora.Constants
|
||||
import com.aurora.extensions.TAG
|
||||
import com.aurora.gplayapi.network.IHttpClient
|
||||
import com.aurora.store.BuildConfig
|
||||
import com.aurora.store.data.model.ExodusReport
|
||||
import com.aurora.store.data.model.ExodusTracker
|
||||
import com.aurora.store.data.model.Report
|
||||
import com.aurora.store.data.model.TrackersResponse
|
||||
import com.aurora.store.data.room.exodus.TrackerDao
|
||||
import com.aurora.store.data.room.exodus.TrackerEntity
|
||||
import com.aurora.store.util.Preferences
|
||||
import com.aurora.store.util.Preferences.PREFERENCE_LAST_TRACKER_SYNC
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.serialization.json.Json
|
||||
import org.json.JSONObject
|
||||
|
||||
@Singleton
|
||||
class ExodusRepository @Inject constructor(
|
||||
private val httpClient: IHttpClient,
|
||||
private val json: Json,
|
||||
private val trackerDao: TrackerDao,
|
||||
@ApplicationContext private val context: Context
|
||||
) {
|
||||
|
||||
companion object {
|
||||
private val SYNC_INTERVAL_MS = TimeUnit.DAYS.toMillis(3)
|
||||
private const val CATEGORY_SEPARATOR = "\n"
|
||||
}
|
||||
|
||||
private val authHeaders: Map<String, String>
|
||||
get() = mapOf(
|
||||
"Content-Type" to Constants.JSON_MIME_TYPE,
|
||||
"Accept" to Constants.JSON_MIME_TYPE,
|
||||
"Authorization" to "Token ${BuildConfig.EXODUS_API_KEY}"
|
||||
)
|
||||
|
||||
suspend fun fetchReports(packageName: String): List<Report> = withContext(Dispatchers.IO) {
|
||||
try {
|
||||
val response = httpClient.get("${Constants.EXODUS_SEARCH_URL}$packageName", authHeaders)
|
||||
parseReports(String(response.responseBytes), packageName)
|
||||
} catch (exception: Exception) {
|
||||
Log.e(TAG, "Failed to fetch exodus reports", exception)
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseReports(response: String, packageName: String): List<Report> = try {
|
||||
val exodusObject = JSONObject(response).getJSONObject(packageName)
|
||||
json.decodeFromString<ExodusReport>(exodusObject.toString()).reports
|
||||
} catch (_: Exception) {
|
||||
emptyList()
|
||||
}
|
||||
|
||||
suspend fun syncTrackersIfStale() = withContext(Dispatchers.IO) {
|
||||
val lastSync = Preferences.getLong(context, PREFERENCE_LAST_TRACKER_SYNC)
|
||||
val isStale = System.currentTimeMillis() - lastSync > SYNC_INTERVAL_MS
|
||||
if (trackerDao.count() > 0 && !isStale) return@withContext
|
||||
|
||||
val response = httpClient.get(Constants.EXODUS_TRACKERS_URL, authHeaders)
|
||||
val trackers = json.decodeFromString<TrackersResponse>(String(response.responseBytes))
|
||||
.trackers.values.map {
|
||||
TrackerEntity(
|
||||
id = it.id,
|
||||
name = it.name,
|
||||
url = it.website,
|
||||
signature = it.codeSignature,
|
||||
date = it.creationDate,
|
||||
categories = it.categories.joinToString(CATEGORY_SEPARATOR)
|
||||
)
|
||||
}
|
||||
|
||||
if (trackers.isNotEmpty()) {
|
||||
trackerDao.purgeAndInsert(trackers)
|
||||
Preferences.putLong(context, PREFERENCE_LAST_TRACKER_SYNC, System.currentTimeMillis())
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun resolveTrackers(ids: List<Int>): List<ExodusTracker> {
|
||||
if (ids.isEmpty()) return emptyList()
|
||||
val byId = trackerDao.getByIds(ids).associateBy { it.id }
|
||||
return ids.map { id ->
|
||||
byId[id]?.let {
|
||||
ExodusTracker(
|
||||
id = it.id,
|
||||
name = it.name,
|
||||
url = it.url,
|
||||
signature = it.signature,
|
||||
date = it.date,
|
||||
categories = if (it.categories.isBlank()) {
|
||||
emptyList()
|
||||
} else {
|
||||
it.categories.split(CATEGORY_SEPARATOR)
|
||||
}
|
||||
)
|
||||
} ?: ExodusTracker(id = id, name = "Tracker #$id")
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getNewTrackers(
|
||||
packageName: String,
|
||||
installedVersionCode: Long
|
||||
): List<ExodusTracker> = computeNewTrackers(fetchReports(packageName), installedVersionCode)
|
||||
|
||||
suspend fun computeNewTrackers(
|
||||
reports: List<Report>,
|
||||
installedVersionCode: Long
|
||||
): List<ExodusTracker> {
|
||||
if (reports.isEmpty()) return emptyList()
|
||||
|
||||
val installedReport = reports.firstOrNull {
|
||||
it.versionCode.toLongOrNull() == installedVersionCode
|
||||
} ?: return emptyList()
|
||||
|
||||
val newestReport = reports.maxByOrNull { it.versionCode.toLongOrNull() ?: -1L }
|
||||
?: return emptyList()
|
||||
|
||||
val newestVersionCode = newestReport.versionCode.toLongOrNull() ?: return emptyList()
|
||||
if (newestVersionCode <= installedVersionCode) return emptyList()
|
||||
|
||||
val newIds = newestReport.trackers.toSet() - installedReport.trackers.toSet()
|
||||
return resolveTrackers(newIds.toList())
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,7 @@ package com.aurora.store.data.model
|
||||
|
||||
import android.os.Parcelable
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
@@ -36,9 +37,9 @@ data class Report(
|
||||
val id: Int = -1,
|
||||
val downloads: String = String(),
|
||||
val version: String = String(),
|
||||
val creationDate: String = String(),
|
||||
val updatedAt: String = String(),
|
||||
val versionCode: String = String(),
|
||||
@SerialName("creation_date") val creationDate: String = String(),
|
||||
@SerialName("updated_at") val updatedAt: String = String(),
|
||||
@SerialName("version_code") val versionCode: String = String(),
|
||||
val trackers: List<Int> = listOf()
|
||||
) : Parcelable
|
||||
|
||||
@@ -62,3 +63,18 @@ data class ExodusTracker(
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class TrackersResponse(
|
||||
val trackers: Map<String, TrackerDto> = emptyMap()
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class TrackerDto(
|
||||
val id: Int = 0,
|
||||
val name: String = String(),
|
||||
@SerialName("website") val website: String = String(),
|
||||
@SerialName("code_signature") val codeSignature: String = String(),
|
||||
@SerialName("creation_date") val creationDate: String = String(),
|
||||
val categories: List<String> = emptyList()
|
||||
)
|
||||
|
||||
@@ -11,6 +11,8 @@ import com.aurora.store.data.room.account.AppAccountBindingDao
|
||||
import com.aurora.store.data.room.download.Download
|
||||
import com.aurora.store.data.room.download.DownloadConverter
|
||||
import com.aurora.store.data.room.download.DownloadDao
|
||||
import com.aurora.store.data.room.exodus.TrackerDao
|
||||
import com.aurora.store.data.room.exodus.TrackerEntity
|
||||
import com.aurora.store.data.room.favourite.Favourite
|
||||
import com.aurora.store.data.room.favourite.FavouriteDao
|
||||
import com.aurora.store.data.room.review.LocalReview
|
||||
@@ -28,9 +30,10 @@ import com.aurora.store.data.room.update.UpdateDao
|
||||
IgnoredUpdate::class,
|
||||
LocalReview::class,
|
||||
Account::class,
|
||||
AppAccountBinding::class
|
||||
AppAccountBinding::class,
|
||||
TrackerEntity::class
|
||||
],
|
||||
version = 10,
|
||||
version = 12,
|
||||
exportSchema = true
|
||||
)
|
||||
@TypeConverters(DownloadConverter::class, AccountConverter::class)
|
||||
@@ -42,4 +45,5 @@ abstract class AuroraDatabase : RoomDatabase() {
|
||||
abstract fun reviewDao(): ReviewDao
|
||||
abstract fun accountDao(): AccountDao
|
||||
abstract fun appAccountBindingDao(): AppAccountBindingDao
|
||||
abstract fun trackerDao(): TrackerDao
|
||||
}
|
||||
|
||||
@@ -46,6 +46,14 @@ object MigrationHelper {
|
||||
override fun migrate(db: SupportSQLiteDatabase) = migrateFrom9To10(db)
|
||||
}
|
||||
|
||||
val MIGRATION_10_11 = object : Migration(10, 11) {
|
||||
override fun migrate(db: SupportSQLiteDatabase) = migrateFrom10To11(db)
|
||||
}
|
||||
|
||||
val MIGRATION_11_12 = object : Migration(11, 12) {
|
||||
override fun migrate(db: SupportSQLiteDatabase) = migrateFrom11To12(db)
|
||||
}
|
||||
|
||||
private const val TAG = "MigrationHelper"
|
||||
|
||||
private fun migrateFrom1To2(database: SupportSQLiteDatabase) {
|
||||
@@ -241,4 +249,52 @@ object MigrationHelper {
|
||||
database.endTransaction()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the exodus_tracker table for the API-synced tracker dictionary.
|
||||
*/
|
||||
private fun migrateFrom10To11(database: SupportSQLiteDatabase) {
|
||||
database.beginTransaction()
|
||||
try {
|
||||
database.execSQL(
|
||||
"CREATE TABLE IF NOT EXISTS `exodus_tracker` (" +
|
||||
"`id` INTEGER NOT NULL, " +
|
||||
"`name` TEXT NOT NULL, " +
|
||||
"`url` TEXT NOT NULL, " +
|
||||
"`signature` TEXT NOT NULL, " +
|
||||
"`date` TEXT NOT NULL, " +
|
||||
"PRIMARY KEY(`id`))"
|
||||
)
|
||||
database.setTransactionSuccessful()
|
||||
} catch (exception: Exception) {
|
||||
Log.e(TAG, "Failed while migrating from database version 10 to 11", exception)
|
||||
} finally {
|
||||
database.endTransaction()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a categories column to the exodus_tracker table, recreating it empty for the next sync.
|
||||
*/
|
||||
private fun migrateFrom11To12(database: SupportSQLiteDatabase) {
|
||||
database.beginTransaction()
|
||||
try {
|
||||
database.execSQL("DROP TABLE IF EXISTS `exodus_tracker`")
|
||||
database.execSQL(
|
||||
"CREATE TABLE IF NOT EXISTS `exodus_tracker` (" +
|
||||
"`id` INTEGER NOT NULL, " +
|
||||
"`name` TEXT NOT NULL, " +
|
||||
"`url` TEXT NOT NULL, " +
|
||||
"`signature` TEXT NOT NULL, " +
|
||||
"`date` TEXT NOT NULL, " +
|
||||
"`categories` TEXT NOT NULL, " +
|
||||
"PRIMARY KEY(`id`))"
|
||||
)
|
||||
database.setTransactionSuccessful()
|
||||
} catch (exception: Exception) {
|
||||
Log.e(TAG, "Failed while migrating from database version 11 to 12", exception)
|
||||
} finally {
|
||||
database.endTransaction()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ package com.aurora.store.data.room
|
||||
|
||||
import android.content.Context
|
||||
import androidx.room.Room
|
||||
import com.aurora.store.data.room.MigrationHelper.MIGRATION_10_11
|
||||
import com.aurora.store.data.room.MigrationHelper.MIGRATION_11_12
|
||||
import com.aurora.store.data.room.MigrationHelper.MIGRATION_1_2
|
||||
import com.aurora.store.data.room.MigrationHelper.MIGRATION_2_3
|
||||
import com.aurora.store.data.room.MigrationHelper.MIGRATION_3_4
|
||||
@@ -16,6 +18,7 @@ import com.aurora.store.data.room.account.AccountDao
|
||||
import com.aurora.store.data.room.account.AppAccountBindingDao
|
||||
import com.aurora.store.data.room.download.DownloadConverter
|
||||
import com.aurora.store.data.room.download.DownloadDao
|
||||
import com.aurora.store.data.room.exodus.TrackerDao
|
||||
import com.aurora.store.data.room.favourite.FavouriteDao
|
||||
import com.aurora.store.data.room.review.ReviewDao
|
||||
import com.aurora.store.data.room.update.IgnoredUpdateDao
|
||||
@@ -49,7 +52,9 @@ object RoomModule {
|
||||
MIGRATION_6_7,
|
||||
MIGRATION_7_8,
|
||||
MIGRATION_8_9,
|
||||
MIGRATION_9_10
|
||||
MIGRATION_9_10,
|
||||
MIGRATION_10_11,
|
||||
MIGRATION_11_12
|
||||
)
|
||||
.addTypeConverter(downloadConverter)
|
||||
.addTypeConverter(accountConverter)
|
||||
@@ -79,4 +84,7 @@ object RoomModule {
|
||||
@Provides
|
||||
fun providesAppAccountBindingDao(auroraDatabase: AuroraDatabase): AppAccountBindingDao =
|
||||
auroraDatabase.appAccountBindingDao()
|
||||
|
||||
@Provides
|
||||
fun providesTrackerDao(auroraDatabase: AuroraDatabase): TrackerDao = auroraDatabase.trackerDao()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Aurora OSS
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package com.aurora.store.data.room.exodus
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy
|
||||
import androidx.room.Query
|
||||
import androidx.room.Transaction
|
||||
|
||||
@Dao
|
||||
interface TrackerDao {
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
suspend fun insertAll(trackers: List<TrackerEntity>)
|
||||
|
||||
@Query("DELETE FROM exodus_tracker")
|
||||
suspend fun deleteAll()
|
||||
|
||||
@Query("SELECT * FROM exodus_tracker WHERE id IN (:ids)")
|
||||
suspend fun getByIds(ids: List<Int>): List<TrackerEntity>
|
||||
|
||||
@Query("SELECT COUNT(*) FROM exodus_tracker")
|
||||
suspend fun count(): Int
|
||||
|
||||
@Transaction
|
||||
suspend fun purgeAndInsert(trackers: List<TrackerEntity>) {
|
||||
deleteAll()
|
||||
insertAll(trackers)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Aurora OSS
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package com.aurora.store.data.room.exodus
|
||||
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
|
||||
@Entity(tableName = "exodus_tracker")
|
||||
data class TrackerEntity(
|
||||
@PrimaryKey val id: Int,
|
||||
val name: String,
|
||||
val url: String,
|
||||
val signature: String,
|
||||
val date: String,
|
||||
val categories: String
|
||||
)
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Aurora OSS
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package com.aurora.store.data.work
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import androidx.hilt.work.HiltWorker
|
||||
import androidx.work.Constraints
|
||||
import androidx.work.CoroutineWorker
|
||||
import androidx.work.ExistingWorkPolicy
|
||||
import androidx.work.NetworkType
|
||||
import androidx.work.OneTimeWorkRequestBuilder
|
||||
import androidx.work.WorkManager
|
||||
import androidx.work.WorkerParameters
|
||||
import com.aurora.extensions.TAG
|
||||
import com.aurora.store.data.ExodusRepository
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
|
||||
@HiltWorker
|
||||
class ExodusTrackerWorker @AssistedInject constructor(
|
||||
private val exodusRepository: ExodusRepository,
|
||||
@Assisted context: Context,
|
||||
@Assisted workerParams: WorkerParameters
|
||||
) : CoroutineWorker(context, workerParams) {
|
||||
|
||||
override suspend fun doWork(): Result = try {
|
||||
exodusRepository.syncTrackersIfStale()
|
||||
Result.success()
|
||||
} catch (exception: Exception) {
|
||||
Log.e(TAG, "Failed to sync exodus trackers", exception)
|
||||
Result.retry()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val EXODUS_TRACKER_WORKER = "EXODUS_TRACKER_WORKER"
|
||||
|
||||
fun enqueue(context: Context) {
|
||||
val constraints = Constraints.Builder()
|
||||
.setRequiredNetworkType(NetworkType.CONNECTED)
|
||||
.build()
|
||||
|
||||
val work = OneTimeWorkRequestBuilder<ExodusTrackerWorker>()
|
||||
.setConstraints(constraints)
|
||||
.build()
|
||||
|
||||
WorkManager.getInstance(context)
|
||||
.enqueueUniqueWork(EXODUS_TRACKER_WORKER, ExistingWorkPolicy.KEEP, work)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
/*
|
||||
* Aurora Store
|
||||
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
|
||||
*
|
||||
* Aurora Store is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Aurora Store is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Aurora Store. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
package com.aurora.store.module
|
||||
|
||||
import android.content.Context
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import java.nio.charset.StandardCharsets
|
||||
import javax.inject.Singleton
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONObject
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
object ExodusModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesLocalTrackersInfo(@ApplicationContext context: Context): JSONObject {
|
||||
val inputStream = context.assets.open("exodus_trackers.json")
|
||||
val bytes = ByteArray(inputStream.available())
|
||||
inputStream.read(bytes)
|
||||
inputStream.close()
|
||||
|
||||
val json = String(bytes, StandardCharsets.UTF_8)
|
||||
val jsonArray = JSONArray(json)
|
||||
return jsonArray.getJSONObject(0)
|
||||
}
|
||||
}
|
||||
@@ -68,6 +68,7 @@ object Preferences {
|
||||
const val PREFERENCE_UPDATES_EXTENDED = "PREFERENCE_UPDATES_EXTENDED"
|
||||
const val PREFERENCE_UPDATES_AUTO = "PREFERENCE_UPDATES_AUTO"
|
||||
const val PREFERENCE_UPDATES_CHECK_INTERVAL = "PREFERENCE_UPDATES_CHECK_INTERVAL"
|
||||
const val PREFERENCE_UPDATES_WARN_TRACKERS = "PREFERENCE_UPDATES_WARN_TRACKERS"
|
||||
const val PREFERENCES_UPDATES_RESTRICTIONS = "PREFERENCES_UPDATES_RESTRICTIONS"
|
||||
const val PREFERENCES_UPDATES_RESTRICTIONS_METERED = "PREFERENCES_UPDATES_RESTRICTIONS_METERED"
|
||||
const val PREFERENCES_UPDATES_RESTRICTIONS_IDLE = "PREFERENCES_UPDATES_RESTRICTIONS_IDLE"
|
||||
@@ -92,6 +93,8 @@ object Preferences {
|
||||
|
||||
const val PREFERENCE_CONFIRM_EXTERNAL_DEEPLINK = "PREFERENCE_CONFIRM_EXTERNAL_DEEPLINK"
|
||||
|
||||
const val PREFERENCE_LAST_TRACKER_SYNC = "PREFERENCE_LAST_TRACKER_SYNC"
|
||||
|
||||
private var prefs: SharedPreferences? = null
|
||||
|
||||
fun getPrefs(context: Context): SharedPreferences = when (BuildConfig.FLAVOR) {
|
||||
|
||||
@@ -6,44 +6,21 @@
|
||||
package com.aurora.store.viewmodel.details
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.aurora.store.data.ExodusRepository
|
||||
import com.aurora.store.data.model.ExodusTracker
|
||||
import com.aurora.store.data.model.Report
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import org.json.JSONObject
|
||||
|
||||
@HiltViewModel
|
||||
class ExodusViewModel @Inject constructor(
|
||||
private val exodusTrackers: JSONObject
|
||||
private val exodusRepository: ExodusRepository
|
||||
) : ViewModel() {
|
||||
|
||||
private val _trackers = MutableStateFlow<List<ExodusTracker>>(emptyList())
|
||||
val trackers = _trackers.asStateFlow()
|
||||
|
||||
fun getExodusTrackersFromReport(report: Report) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
val trackerObjects = report.trackers.map {
|
||||
exodusTrackers.getJSONObject(it.toString())
|
||||
}.toList()
|
||||
|
||||
_trackers.value = trackerObjects.map {
|
||||
ExodusTracker(
|
||||
id = it.getInt("id"),
|
||||
name = it.getString("name"),
|
||||
url = it.getString("website"),
|
||||
signature = it.getString("code_signature"),
|
||||
date = it.getString("creation_date"),
|
||||
description = it.getString("description"),
|
||||
networkSignature = it.getString("network_signature"),
|
||||
documentation = listOf(it.getString("documentation")),
|
||||
categories = listOf(it.getString("categories"))
|
||||
)
|
||||
}.toList()
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Resolves the tracker ids of [report] to their details via the local tracker table,
|
||||
* best-effort (ids missing from the table resolve to a `Tracker #<id>` placeholder).
|
||||
*/
|
||||
suspend fun resolveTrackers(report: Report): List<ExodusTracker> =
|
||||
exodusRepository.resolveTrackers(report.trackers)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user