diff --git a/libs/database/schemas/org.fdroid.database.FDroidDatabaseInt/3.json b/libs/database/schemas/org.fdroid.database.FDroidDatabaseInt/3.json new file mode 100644 index 000000000..be3b21d61 --- /dev/null +++ b/libs/database/schemas/org.fdroid.database.FDroidDatabaseInt/3.json @@ -0,0 +1,1098 @@ +{ + "formatVersion": 1, + "database": { + "version": 3, + "identityHash": "0f46ee261c488b0d38dae6aa541b57cc", + "entities": [ + { + "tableName": "CoreRepository", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`repoId` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `icon` TEXT, `address` TEXT NOT NULL, `webBaseUrl` TEXT, `timestamp` INTEGER NOT NULL, `version` INTEGER, `formatVersion` TEXT, `maxAge` INTEGER, `description` TEXT NOT NULL, `certificate` TEXT)", + "fields": [ + { + "fieldPath": "repoId", + "columnName": "repoId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "icon", + "columnName": "icon", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "webBaseUrl", + "columnName": "webBaseUrl", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "timestamp", + "columnName": "timestamp", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "version", + "columnName": "version", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "formatVersion", + "columnName": "formatVersion", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "maxAge", + "columnName": "maxAge", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "certificate", + "columnName": "certificate", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "repoId" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Mirror", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`repoId` INTEGER NOT NULL, `url` TEXT NOT NULL, `location` TEXT, PRIMARY KEY(`repoId`, `url`), FOREIGN KEY(`repoId`) REFERENCES `CoreRepository`(`repoId`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "repoId", + "columnName": "repoId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "location", + "columnName": "location", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "repoId", + "url" + ] + }, + "indices": [], + "foreignKeys": [ + { + "table": "CoreRepository", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "repoId" + ], + "referencedColumns": [ + "repoId" + ] + } + ] + }, + { + "tableName": "AntiFeature", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`repoId` INTEGER NOT NULL, `id` TEXT NOT NULL, `icon` TEXT NOT NULL, `name` TEXT NOT NULL, `description` TEXT NOT NULL, PRIMARY KEY(`repoId`, `id`), FOREIGN KEY(`repoId`) REFERENCES `CoreRepository`(`repoId`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "repoId", + "columnName": "repoId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "icon", + "columnName": "icon", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "repoId", + "id" + ] + }, + "indices": [], + "foreignKeys": [ + { + "table": "CoreRepository", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "repoId" + ], + "referencedColumns": [ + "repoId" + ] + } + ] + }, + { + "tableName": "Category", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`repoId` INTEGER NOT NULL, `id` TEXT NOT NULL, `icon` TEXT NOT NULL, `name` TEXT NOT NULL, `description` TEXT NOT NULL, PRIMARY KEY(`repoId`, `id`), FOREIGN KEY(`repoId`) REFERENCES `CoreRepository`(`repoId`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "repoId", + "columnName": "repoId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "icon", + "columnName": "icon", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "repoId", + "id" + ] + }, + "indices": [], + "foreignKeys": [ + { + "table": "CoreRepository", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "repoId" + ], + "referencedColumns": [ + "repoId" + ] + } + ] + }, + { + "tableName": "ReleaseChannel", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`repoId` INTEGER NOT NULL, `id` TEXT NOT NULL, `icon` TEXT NOT NULL, `name` TEXT NOT NULL, `description` TEXT NOT NULL, PRIMARY KEY(`repoId`, `id`), FOREIGN KEY(`repoId`) REFERENCES `CoreRepository`(`repoId`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "repoId", + "columnName": "repoId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "icon", + "columnName": "icon", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "repoId", + "id" + ] + }, + "indices": [], + "foreignKeys": [ + { + "table": "CoreRepository", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "repoId" + ], + "referencedColumns": [ + "repoId" + ] + } + ] + }, + { + "tableName": "RepositoryPreferences", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`repoId` INTEGER NOT NULL, `weight` INTEGER NOT NULL, `enabled` INTEGER NOT NULL, `lastUpdated` INTEGER, `lastETag` TEXT, `userMirrors` TEXT, `disabledMirrors` TEXT, `username` TEXT, `password` TEXT, PRIMARY KEY(`repoId`))", + "fields": [ + { + "fieldPath": "repoId", + "columnName": "repoId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "weight", + "columnName": "weight", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "enabled", + "columnName": "enabled", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastUpdated", + "columnName": "lastUpdated", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "lastETag", + "columnName": "lastETag", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "userMirrors", + "columnName": "userMirrors", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "disabledMirrors", + "columnName": "disabledMirrors", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "username", + "columnName": "username", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "password", + "columnName": "password", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "repoId" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "AppMetadata", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`repoId` INTEGER NOT NULL, `packageName` TEXT NOT NULL, `added` INTEGER NOT NULL, `lastUpdated` INTEGER NOT NULL, `name` TEXT, `summary` TEXT, `description` TEXT, `localizedName` TEXT, `localizedSummary` TEXT, `webSite` TEXT, `changelog` TEXT, `license` TEXT, `sourceCode` TEXT, `issueTracker` TEXT, `translation` TEXT, `preferredSigner` TEXT, `video` TEXT, `authorName` TEXT, `authorEmail` TEXT, `authorWebSite` TEXT, `authorPhone` TEXT, `donate` TEXT, `liberapayID` TEXT, `liberapay` TEXT, `openCollective` TEXT, `bitcoin` TEXT, `litecoin` TEXT, `flattrID` TEXT, `categories` TEXT, `isCompatible` INTEGER NOT NULL, PRIMARY KEY(`repoId`, `packageName`), FOREIGN KEY(`repoId`) REFERENCES `CoreRepository`(`repoId`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "repoId", + "columnName": "repoId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "packageName", + "columnName": "packageName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "added", + "columnName": "added", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastUpdated", + "columnName": "lastUpdated", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "summary", + "columnName": "summary", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "localizedName", + "columnName": "localizedName", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "localizedSummary", + "columnName": "localizedSummary", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "webSite", + "columnName": "webSite", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "changelog", + "columnName": "changelog", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "license", + "columnName": "license", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "sourceCode", + "columnName": "sourceCode", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "issueTracker", + "columnName": "issueTracker", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "translation", + "columnName": "translation", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "preferredSigner", + "columnName": "preferredSigner", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "video", + "columnName": "video", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "authorName", + "columnName": "authorName", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "authorEmail", + "columnName": "authorEmail", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "authorWebSite", + "columnName": "authorWebSite", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "authorPhone", + "columnName": "authorPhone", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "donate", + "columnName": "donate", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "liberapayID", + "columnName": "liberapayID", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "liberapay", + "columnName": "liberapay", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "openCollective", + "columnName": "openCollective", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "bitcoin", + "columnName": "bitcoin", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "litecoin", + "columnName": "litecoin", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "flattrID", + "columnName": "flattrID", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "categories", + "columnName": "categories", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "isCompatible", + "columnName": "isCompatible", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "repoId", + "packageName" + ] + }, + "indices": [], + "foreignKeys": [ + { + "table": "CoreRepository", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "repoId" + ], + "referencedColumns": [ + "repoId" + ] + } + ] + }, + { + "ftsVersion": "FTS4", + "ftsOptions": { + "tokenizer": "simple", + "tokenizerArgs": [], + "contentTable": "AppMetadata", + "languageIdColumnName": "", + "matchInfo": "FTS4", + "notIndexedColumns": [], + "prefixSizes": [], + "preferredOrder": "ASC" + }, + "contentSyncTriggers": [ + "CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_AppMetadataFts_BEFORE_UPDATE BEFORE UPDATE ON `AppMetadata` BEGIN DELETE FROM `AppMetadataFts` WHERE `docid`=OLD.`rowid`; END", + "CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_AppMetadataFts_BEFORE_DELETE BEFORE DELETE ON `AppMetadata` BEGIN DELETE FROM `AppMetadataFts` WHERE `docid`=OLD.`rowid`; END", + "CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_AppMetadataFts_AFTER_UPDATE AFTER UPDATE ON `AppMetadata` BEGIN INSERT INTO `AppMetadataFts`(`docid`, `repoId`, `packageName`, `localizedName`, `localizedSummary`) VALUES (NEW.`rowid`, NEW.`repoId`, NEW.`packageName`, NEW.`localizedName`, NEW.`localizedSummary`); END", + "CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_AppMetadataFts_AFTER_INSERT AFTER INSERT ON `AppMetadata` BEGIN INSERT INTO `AppMetadataFts`(`docid`, `repoId`, `packageName`, `localizedName`, `localizedSummary`) VALUES (NEW.`rowid`, NEW.`repoId`, NEW.`packageName`, NEW.`localizedName`, NEW.`localizedSummary`); END" + ], + "tableName": "AppMetadataFts", + "createSql": "CREATE VIRTUAL TABLE IF NOT EXISTS `${TABLE_NAME}` USING FTS4(`repoId` INTEGER NOT NULL, `packageName` TEXT NOT NULL, `localizedName` TEXT, `localizedSummary` TEXT, content=`AppMetadata`)", + "fields": [ + { + "fieldPath": "repoId", + "columnName": "repoId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "packageName", + "columnName": "packageName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "localizedName", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "summary", + "columnName": "localizedSummary", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "LocalizedFile", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`repoId` INTEGER NOT NULL, `packageName` TEXT NOT NULL, `type` TEXT NOT NULL, `locale` TEXT NOT NULL, `name` TEXT NOT NULL, `sha256` TEXT, `size` INTEGER, `ipfsCidV1` TEXT, PRIMARY KEY(`repoId`, `packageName`, `type`, `locale`), FOREIGN KEY(`repoId`, `packageName`) REFERENCES `AppMetadata`(`repoId`, `packageName`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "repoId", + "columnName": "repoId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "packageName", + "columnName": "packageName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "locale", + "columnName": "locale", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "sha256", + "columnName": "sha256", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "size", + "columnName": "size", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "ipfsCidV1", + "columnName": "ipfsCidV1", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "repoId", + "packageName", + "type", + "locale" + ] + }, + "indices": [], + "foreignKeys": [ + { + "table": "AppMetadata", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "repoId", + "packageName" + ], + "referencedColumns": [ + "repoId", + "packageName" + ] + } + ] + }, + { + "tableName": "LocalizedFileList", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`repoId` INTEGER NOT NULL, `packageName` TEXT NOT NULL, `type` TEXT NOT NULL, `locale` TEXT NOT NULL, `name` TEXT NOT NULL, `sha256` TEXT, `size` INTEGER, `ipfsCidV1` TEXT, PRIMARY KEY(`repoId`, `packageName`, `type`, `locale`, `name`), FOREIGN KEY(`repoId`, `packageName`) REFERENCES `AppMetadata`(`repoId`, `packageName`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "repoId", + "columnName": "repoId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "packageName", + "columnName": "packageName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "locale", + "columnName": "locale", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "sha256", + "columnName": "sha256", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "size", + "columnName": "size", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "ipfsCidV1", + "columnName": "ipfsCidV1", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "repoId", + "packageName", + "type", + "locale", + "name" + ] + }, + "indices": [], + "foreignKeys": [ + { + "table": "AppMetadata", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "repoId", + "packageName" + ], + "referencedColumns": [ + "repoId", + "packageName" + ] + } + ] + }, + { + "tableName": "Version", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`repoId` INTEGER NOT NULL, `packageName` TEXT NOT NULL, `versionId` TEXT NOT NULL, `added` INTEGER NOT NULL, `releaseChannels` TEXT, `antiFeatures` TEXT, `whatsNew` TEXT, `isCompatible` INTEGER NOT NULL, `file_name` TEXT NOT NULL, `file_sha256` TEXT NOT NULL, `file_size` INTEGER, `file_ipfsCidV1` TEXT, `src_name` TEXT, `src_sha256` TEXT, `src_size` INTEGER, `src_ipfsCidV1` TEXT, `manifest_versionName` TEXT NOT NULL, `manifest_versionCode` INTEGER NOT NULL, `manifest_maxSdkVersion` INTEGER, `manifest_nativecode` TEXT, `manifest_features` TEXT, `manifest_usesSdk_minSdkVersion` INTEGER, `manifest_usesSdk_targetSdkVersion` INTEGER, `manifest_signer_sha256` TEXT, `manifest_signer_hasMultipleSigners` INTEGER, PRIMARY KEY(`repoId`, `packageName`, `versionId`), FOREIGN KEY(`repoId`, `packageName`) REFERENCES `AppMetadata`(`repoId`, `packageName`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "repoId", + "columnName": "repoId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "packageName", + "columnName": "packageName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "versionId", + "columnName": "versionId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "added", + "columnName": "added", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "releaseChannels", + "columnName": "releaseChannels", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "antiFeatures", + "columnName": "antiFeatures", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "whatsNew", + "columnName": "whatsNew", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "isCompatible", + "columnName": "isCompatible", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "file.name", + "columnName": "file_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "file.sha256", + "columnName": "file_sha256", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "file.size", + "columnName": "file_size", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "file.ipfsCidV1", + "columnName": "file_ipfsCidV1", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "src.name", + "columnName": "src_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "src.sha256", + "columnName": "src_sha256", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "src.size", + "columnName": "src_size", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "src.ipfsCidV1", + "columnName": "src_ipfsCidV1", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "manifest.versionName", + "columnName": "manifest_versionName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "manifest.versionCode", + "columnName": "manifest_versionCode", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "manifest.maxSdkVersion", + "columnName": "manifest_maxSdkVersion", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "manifest.nativecode", + "columnName": "manifest_nativecode", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "manifest.features", + "columnName": "manifest_features", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "manifest.usesSdk.minSdkVersion", + "columnName": "manifest_usesSdk_minSdkVersion", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "manifest.usesSdk.targetSdkVersion", + "columnName": "manifest_usesSdk_targetSdkVersion", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "manifest.signer.sha256", + "columnName": "manifest_signer_sha256", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "manifest.signer.hasMultipleSigners", + "columnName": "manifest_signer_hasMultipleSigners", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "repoId", + "packageName", + "versionId" + ] + }, + "indices": [], + "foreignKeys": [ + { + "table": "AppMetadata", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "repoId", + "packageName" + ], + "referencedColumns": [ + "repoId", + "packageName" + ] + } + ] + }, + { + "tableName": "VersionedString", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`repoId` INTEGER NOT NULL, `packageName` TEXT NOT NULL, `versionId` TEXT NOT NULL, `type` TEXT NOT NULL, `name` TEXT NOT NULL, `version` INTEGER, PRIMARY KEY(`repoId`, `packageName`, `versionId`, `type`, `name`), FOREIGN KEY(`repoId`, `packageName`, `versionId`) REFERENCES `Version`(`repoId`, `packageName`, `versionId`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "repoId", + "columnName": "repoId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "packageName", + "columnName": "packageName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "versionId", + "columnName": "versionId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "version", + "columnName": "version", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "repoId", + "packageName", + "versionId", + "type", + "name" + ] + }, + "indices": [], + "foreignKeys": [ + { + "table": "Version", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "repoId", + "packageName", + "versionId" + ], + "referencedColumns": [ + "repoId", + "packageName", + "versionId" + ] + } + ] + }, + { + "tableName": "AppPrefs", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`packageName` TEXT NOT NULL, `ignoreVersionCodeUpdate` INTEGER NOT NULL, `preferredRepoId` INTEGER, `appPrefReleaseChannels` TEXT, PRIMARY KEY(`packageName`))", + "fields": [ + { + "fieldPath": "packageName", + "columnName": "packageName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "ignoreVersionCodeUpdate", + "columnName": "ignoreVersionCodeUpdate", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "preferredRepoId", + "columnName": "preferredRepoId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "appPrefReleaseChannels", + "columnName": "appPrefReleaseChannels", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "packageName" + ] + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [ + { + "viewName": "LocalizedIcon", + "createSql": "CREATE VIEW `${VIEW_NAME}` AS SELECT * FROM LocalizedFile WHERE type='icon'" + }, + { + "viewName": "HighestVersion", + "createSql": "CREATE VIEW `${VIEW_NAME}` AS SELECT repoId, packageName, antiFeatures FROM Version\n GROUP BY repoId, packageName HAVING MAX(manifest_versionCode)" + }, + { + "viewName": "PreferredRepo", + "createSql": "CREATE VIEW `${VIEW_NAME}` AS SELECT packageName, repoId AS preferredRepoId FROM AppMetadata\n JOIN RepositoryPreferences AS pref USING (repoId)\n LEFT JOIN AppPrefs USING (packageName)\n WHERE repoId = COALESCE(preferredRepoId, repoId)\n GROUP BY packageName HAVING MAX(pref.weight)" + } + ], + "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, '0f46ee261c488b0d38dae6aa541b57cc')" + ] + } +} \ No newline at end of file diff --git a/libs/database/schemas/org.fdroid.database.FDroidDatabaseInt/4.json b/libs/database/schemas/org.fdroid.database.FDroidDatabaseInt/4.json new file mode 100644 index 000000000..387a13340 --- /dev/null +++ b/libs/database/schemas/org.fdroid.database.FDroidDatabaseInt/4.json @@ -0,0 +1,1098 @@ +{ + "formatVersion": 1, + "database": { + "version": 4, + "identityHash": "da80afc4abe8139730ef7a8e53cea098", + "entities": [ + { + "tableName": "CoreRepository", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`repoId` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `icon` TEXT, `address` TEXT NOT NULL, `webBaseUrl` TEXT, `timestamp` INTEGER NOT NULL, `version` INTEGER, `formatVersion` TEXT, `maxAge` INTEGER, `description` TEXT NOT NULL, `certificate` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "repoId", + "columnName": "repoId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "icon", + "columnName": "icon", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "webBaseUrl", + "columnName": "webBaseUrl", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "timestamp", + "columnName": "timestamp", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "version", + "columnName": "version", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "formatVersion", + "columnName": "formatVersion", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "maxAge", + "columnName": "maxAge", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "certificate", + "columnName": "certificate", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "repoId" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Mirror", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`repoId` INTEGER NOT NULL, `url` TEXT NOT NULL, `location` TEXT, PRIMARY KEY(`repoId`, `url`), FOREIGN KEY(`repoId`) REFERENCES `CoreRepository`(`repoId`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "repoId", + "columnName": "repoId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "location", + "columnName": "location", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "repoId", + "url" + ] + }, + "indices": [], + "foreignKeys": [ + { + "table": "CoreRepository", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "repoId" + ], + "referencedColumns": [ + "repoId" + ] + } + ] + }, + { + "tableName": "AntiFeature", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`repoId` INTEGER NOT NULL, `id` TEXT NOT NULL, `icon` TEXT NOT NULL, `name` TEXT NOT NULL, `description` TEXT NOT NULL, PRIMARY KEY(`repoId`, `id`), FOREIGN KEY(`repoId`) REFERENCES `CoreRepository`(`repoId`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "repoId", + "columnName": "repoId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "icon", + "columnName": "icon", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "repoId", + "id" + ] + }, + "indices": [], + "foreignKeys": [ + { + "table": "CoreRepository", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "repoId" + ], + "referencedColumns": [ + "repoId" + ] + } + ] + }, + { + "tableName": "Category", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`repoId` INTEGER NOT NULL, `id` TEXT NOT NULL, `icon` TEXT NOT NULL, `name` TEXT NOT NULL, `description` TEXT NOT NULL, PRIMARY KEY(`repoId`, `id`), FOREIGN KEY(`repoId`) REFERENCES `CoreRepository`(`repoId`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "repoId", + "columnName": "repoId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "icon", + "columnName": "icon", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "repoId", + "id" + ] + }, + "indices": [], + "foreignKeys": [ + { + "table": "CoreRepository", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "repoId" + ], + "referencedColumns": [ + "repoId" + ] + } + ] + }, + { + "tableName": "ReleaseChannel", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`repoId` INTEGER NOT NULL, `id` TEXT NOT NULL, `icon` TEXT NOT NULL, `name` TEXT NOT NULL, `description` TEXT NOT NULL, PRIMARY KEY(`repoId`, `id`), FOREIGN KEY(`repoId`) REFERENCES `CoreRepository`(`repoId`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "repoId", + "columnName": "repoId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "icon", + "columnName": "icon", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "repoId", + "id" + ] + }, + "indices": [], + "foreignKeys": [ + { + "table": "CoreRepository", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "repoId" + ], + "referencedColumns": [ + "repoId" + ] + } + ] + }, + { + "tableName": "RepositoryPreferences", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`repoId` INTEGER NOT NULL, `weight` INTEGER NOT NULL, `enabled` INTEGER NOT NULL, `lastUpdated` INTEGER, `lastETag` TEXT, `userMirrors` TEXT, `disabledMirrors` TEXT, `username` TEXT, `password` TEXT, PRIMARY KEY(`repoId`))", + "fields": [ + { + "fieldPath": "repoId", + "columnName": "repoId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "weight", + "columnName": "weight", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "enabled", + "columnName": "enabled", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastUpdated", + "columnName": "lastUpdated", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "lastETag", + "columnName": "lastETag", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "userMirrors", + "columnName": "userMirrors", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "disabledMirrors", + "columnName": "disabledMirrors", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "username", + "columnName": "username", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "password", + "columnName": "password", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "repoId" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "AppMetadata", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`repoId` INTEGER NOT NULL, `packageName` TEXT NOT NULL, `added` INTEGER NOT NULL, `lastUpdated` INTEGER NOT NULL, `name` TEXT, `summary` TEXT, `description` TEXT, `localizedName` TEXT, `localizedSummary` TEXT, `webSite` TEXT, `changelog` TEXT, `license` TEXT, `sourceCode` TEXT, `issueTracker` TEXT, `translation` TEXT, `preferredSigner` TEXT, `video` TEXT, `authorName` TEXT, `authorEmail` TEXT, `authorWebSite` TEXT, `authorPhone` TEXT, `donate` TEXT, `liberapayID` TEXT, `liberapay` TEXT, `openCollective` TEXT, `bitcoin` TEXT, `litecoin` TEXT, `flattrID` TEXT, `categories` TEXT, `isCompatible` INTEGER NOT NULL, PRIMARY KEY(`repoId`, `packageName`), FOREIGN KEY(`repoId`) REFERENCES `CoreRepository`(`repoId`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "repoId", + "columnName": "repoId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "packageName", + "columnName": "packageName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "added", + "columnName": "added", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastUpdated", + "columnName": "lastUpdated", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "summary", + "columnName": "summary", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "localizedName", + "columnName": "localizedName", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "localizedSummary", + "columnName": "localizedSummary", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "webSite", + "columnName": "webSite", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "changelog", + "columnName": "changelog", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "license", + "columnName": "license", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "sourceCode", + "columnName": "sourceCode", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "issueTracker", + "columnName": "issueTracker", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "translation", + "columnName": "translation", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "preferredSigner", + "columnName": "preferredSigner", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "video", + "columnName": "video", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "authorName", + "columnName": "authorName", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "authorEmail", + "columnName": "authorEmail", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "authorWebSite", + "columnName": "authorWebSite", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "authorPhone", + "columnName": "authorPhone", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "donate", + "columnName": "donate", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "liberapayID", + "columnName": "liberapayID", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "liberapay", + "columnName": "liberapay", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "openCollective", + "columnName": "openCollective", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "bitcoin", + "columnName": "bitcoin", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "litecoin", + "columnName": "litecoin", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "flattrID", + "columnName": "flattrID", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "categories", + "columnName": "categories", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "isCompatible", + "columnName": "isCompatible", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "repoId", + "packageName" + ] + }, + "indices": [], + "foreignKeys": [ + { + "table": "CoreRepository", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "repoId" + ], + "referencedColumns": [ + "repoId" + ] + } + ] + }, + { + "ftsVersion": "FTS4", + "ftsOptions": { + "tokenizer": "simple", + "tokenizerArgs": [], + "contentTable": "AppMetadata", + "languageIdColumnName": "", + "matchInfo": "FTS4", + "notIndexedColumns": [], + "prefixSizes": [], + "preferredOrder": "ASC" + }, + "contentSyncTriggers": [ + "CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_AppMetadataFts_BEFORE_UPDATE BEFORE UPDATE ON `AppMetadata` BEGIN DELETE FROM `AppMetadataFts` WHERE `docid`=OLD.`rowid`; END", + "CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_AppMetadataFts_BEFORE_DELETE BEFORE DELETE ON `AppMetadata` BEGIN DELETE FROM `AppMetadataFts` WHERE `docid`=OLD.`rowid`; END", + "CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_AppMetadataFts_AFTER_UPDATE AFTER UPDATE ON `AppMetadata` BEGIN INSERT INTO `AppMetadataFts`(`docid`, `repoId`, `packageName`, `localizedName`, `localizedSummary`) VALUES (NEW.`rowid`, NEW.`repoId`, NEW.`packageName`, NEW.`localizedName`, NEW.`localizedSummary`); END", + "CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_AppMetadataFts_AFTER_INSERT AFTER INSERT ON `AppMetadata` BEGIN INSERT INTO `AppMetadataFts`(`docid`, `repoId`, `packageName`, `localizedName`, `localizedSummary`) VALUES (NEW.`rowid`, NEW.`repoId`, NEW.`packageName`, NEW.`localizedName`, NEW.`localizedSummary`); END" + ], + "tableName": "AppMetadataFts", + "createSql": "CREATE VIRTUAL TABLE IF NOT EXISTS `${TABLE_NAME}` USING FTS4(`repoId` INTEGER NOT NULL, `packageName` TEXT NOT NULL, `localizedName` TEXT, `localizedSummary` TEXT, content=`AppMetadata`)", + "fields": [ + { + "fieldPath": "repoId", + "columnName": "repoId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "packageName", + "columnName": "packageName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "localizedName", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "summary", + "columnName": "localizedSummary", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "LocalizedFile", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`repoId` INTEGER NOT NULL, `packageName` TEXT NOT NULL, `type` TEXT NOT NULL, `locale` TEXT NOT NULL, `name` TEXT NOT NULL, `sha256` TEXT, `size` INTEGER, `ipfsCidV1` TEXT, PRIMARY KEY(`repoId`, `packageName`, `type`, `locale`), FOREIGN KEY(`repoId`, `packageName`) REFERENCES `AppMetadata`(`repoId`, `packageName`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "repoId", + "columnName": "repoId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "packageName", + "columnName": "packageName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "locale", + "columnName": "locale", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "sha256", + "columnName": "sha256", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "size", + "columnName": "size", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "ipfsCidV1", + "columnName": "ipfsCidV1", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "repoId", + "packageName", + "type", + "locale" + ] + }, + "indices": [], + "foreignKeys": [ + { + "table": "AppMetadata", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "repoId", + "packageName" + ], + "referencedColumns": [ + "repoId", + "packageName" + ] + } + ] + }, + { + "tableName": "LocalizedFileList", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`repoId` INTEGER NOT NULL, `packageName` TEXT NOT NULL, `type` TEXT NOT NULL, `locale` TEXT NOT NULL, `name` TEXT NOT NULL, `sha256` TEXT, `size` INTEGER, `ipfsCidV1` TEXT, PRIMARY KEY(`repoId`, `packageName`, `type`, `locale`, `name`), FOREIGN KEY(`repoId`, `packageName`) REFERENCES `AppMetadata`(`repoId`, `packageName`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "repoId", + "columnName": "repoId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "packageName", + "columnName": "packageName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "locale", + "columnName": "locale", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "sha256", + "columnName": "sha256", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "size", + "columnName": "size", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "ipfsCidV1", + "columnName": "ipfsCidV1", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "repoId", + "packageName", + "type", + "locale", + "name" + ] + }, + "indices": [], + "foreignKeys": [ + { + "table": "AppMetadata", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "repoId", + "packageName" + ], + "referencedColumns": [ + "repoId", + "packageName" + ] + } + ] + }, + { + "tableName": "Version", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`repoId` INTEGER NOT NULL, `packageName` TEXT NOT NULL, `versionId` TEXT NOT NULL, `added` INTEGER NOT NULL, `releaseChannels` TEXT, `antiFeatures` TEXT, `whatsNew` TEXT, `isCompatible` INTEGER NOT NULL, `file_name` TEXT NOT NULL, `file_sha256` TEXT NOT NULL, `file_size` INTEGER, `file_ipfsCidV1` TEXT, `src_name` TEXT, `src_sha256` TEXT, `src_size` INTEGER, `src_ipfsCidV1` TEXT, `manifest_versionName` TEXT NOT NULL, `manifest_versionCode` INTEGER NOT NULL, `manifest_maxSdkVersion` INTEGER, `manifest_nativecode` TEXT, `manifest_features` TEXT, `manifest_usesSdk_minSdkVersion` INTEGER, `manifest_usesSdk_targetSdkVersion` INTEGER, `manifest_signer_sha256` TEXT, `manifest_signer_hasMultipleSigners` INTEGER, PRIMARY KEY(`repoId`, `packageName`, `versionId`), FOREIGN KEY(`repoId`, `packageName`) REFERENCES `AppMetadata`(`repoId`, `packageName`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "repoId", + "columnName": "repoId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "packageName", + "columnName": "packageName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "versionId", + "columnName": "versionId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "added", + "columnName": "added", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "releaseChannels", + "columnName": "releaseChannels", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "antiFeatures", + "columnName": "antiFeatures", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "whatsNew", + "columnName": "whatsNew", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "isCompatible", + "columnName": "isCompatible", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "file.name", + "columnName": "file_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "file.sha256", + "columnName": "file_sha256", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "file.size", + "columnName": "file_size", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "file.ipfsCidV1", + "columnName": "file_ipfsCidV1", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "src.name", + "columnName": "src_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "src.sha256", + "columnName": "src_sha256", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "src.size", + "columnName": "src_size", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "src.ipfsCidV1", + "columnName": "src_ipfsCidV1", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "manifest.versionName", + "columnName": "manifest_versionName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "manifest.versionCode", + "columnName": "manifest_versionCode", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "manifest.maxSdkVersion", + "columnName": "manifest_maxSdkVersion", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "manifest.nativecode", + "columnName": "manifest_nativecode", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "manifest.features", + "columnName": "manifest_features", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "manifest.usesSdk.minSdkVersion", + "columnName": "manifest_usesSdk_minSdkVersion", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "manifest.usesSdk.targetSdkVersion", + "columnName": "manifest_usesSdk_targetSdkVersion", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "manifest.signer.sha256", + "columnName": "manifest_signer_sha256", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "manifest.signer.hasMultipleSigners", + "columnName": "manifest_signer_hasMultipleSigners", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "repoId", + "packageName", + "versionId" + ] + }, + "indices": [], + "foreignKeys": [ + { + "table": "AppMetadata", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "repoId", + "packageName" + ], + "referencedColumns": [ + "repoId", + "packageName" + ] + } + ] + }, + { + "tableName": "VersionedString", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`repoId` INTEGER NOT NULL, `packageName` TEXT NOT NULL, `versionId` TEXT NOT NULL, `type` TEXT NOT NULL, `name` TEXT NOT NULL, `version` INTEGER, PRIMARY KEY(`repoId`, `packageName`, `versionId`, `type`, `name`), FOREIGN KEY(`repoId`, `packageName`, `versionId`) REFERENCES `Version`(`repoId`, `packageName`, `versionId`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "repoId", + "columnName": "repoId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "packageName", + "columnName": "packageName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "versionId", + "columnName": "versionId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "version", + "columnName": "version", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "repoId", + "packageName", + "versionId", + "type", + "name" + ] + }, + "indices": [], + "foreignKeys": [ + { + "table": "Version", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "repoId", + "packageName", + "versionId" + ], + "referencedColumns": [ + "repoId", + "packageName", + "versionId" + ] + } + ] + }, + { + "tableName": "AppPrefs", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`packageName` TEXT NOT NULL, `ignoreVersionCodeUpdate` INTEGER NOT NULL, `preferredRepoId` INTEGER, `appPrefReleaseChannels` TEXT, PRIMARY KEY(`packageName`))", + "fields": [ + { + "fieldPath": "packageName", + "columnName": "packageName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "ignoreVersionCodeUpdate", + "columnName": "ignoreVersionCodeUpdate", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "preferredRepoId", + "columnName": "preferredRepoId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "appPrefReleaseChannels", + "columnName": "appPrefReleaseChannels", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "packageName" + ] + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [ + { + "viewName": "LocalizedIcon", + "createSql": "CREATE VIEW `${VIEW_NAME}` AS SELECT * FROM LocalizedFile WHERE type='icon'" + }, + { + "viewName": "HighestVersion", + "createSql": "CREATE VIEW `${VIEW_NAME}` AS SELECT repoId, packageName, antiFeatures FROM Version\n GROUP BY repoId, packageName HAVING MAX(manifest_versionCode)" + }, + { + "viewName": "PreferredRepo", + "createSql": "CREATE VIEW `${VIEW_NAME}` AS SELECT packageName, repoId AS preferredRepoId FROM AppMetadata\n JOIN RepositoryPreferences AS pref USING (repoId)\n LEFT JOIN AppPrefs USING (packageName)\n WHERE repoId = COALESCE(preferredRepoId, repoId)\n GROUP BY packageName HAVING MAX(pref.weight)" + } + ], + "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, 'da80afc4abe8139730ef7a8e53cea098')" + ] + } +} \ No newline at end of file diff --git a/libs/database/src/dbTest/java/org/fdroid/database/DbTest.kt b/libs/database/src/dbTest/java/org/fdroid/database/DbTest.kt index 4f96668e3..f0ceb3155 100644 --- a/libs/database/src/dbTest/java/org/fdroid/database/DbTest.kt +++ b/libs/database/src/dbTest/java/org/fdroid/database/DbTest.kt @@ -76,9 +76,9 @@ internal abstract class DbTest { certificate: String = CERTIFICATE, lastTimestamp: Long = -1, ): Long { - val repoId = db.getRepositoryDao().insertEmptyRepo(address) + val repoId = db.getRepositoryDao().insertEmptyRepo(address, certificate = certificate) val streamReceiver = DbV1StreamReceiver(db, repoId) { true } - val indexProcessor = IndexV1StreamProcessor(streamReceiver, certificate, lastTimestamp) + val indexProcessor = IndexV1StreamProcessor(streamReceiver, lastTimestamp) db.runInTransaction { assets.open(indexAssetPath).use { indexStream -> indexProcessor.process(indexStream) @@ -93,9 +93,9 @@ internal abstract class DbTest { version: Long = 42L, certificate: String = CERTIFICATE, ): Long { - val repoId = db.getRepositoryDao().insertEmptyRepo(address) + val repoId = db.getRepositoryDao().insertEmptyRepo(address, certificate = certificate) val streamReceiver = DbV2StreamReceiver(db, repoId) { true } - val indexProcessor = IndexV2FullStreamProcessor(streamReceiver, certificate) + val indexProcessor = IndexV2FullStreamProcessor(streamReceiver) db.runInTransaction { assets.open(indexAssetPath).use { indexStream -> indexProcessor.process(version, indexStream) {} diff --git a/libs/database/src/dbTest/java/org/fdroid/database/IndexV1InsertTest.kt b/libs/database/src/dbTest/java/org/fdroid/database/IndexV1InsertTest.kt index 0eb217adf..0f2c515c3 100644 --- a/libs/database/src/dbTest/java/org/fdroid/database/IndexV1InsertTest.kt +++ b/libs/database/src/dbTest/java/org/fdroid/database/IndexV1InsertTest.kt @@ -62,7 +62,7 @@ internal class IndexV1InsertTest : DbTest() { private fun streamIndex(path: String): Long { val repoId = db.getRepositoryDao().insertEmptyRepo("https://f-droid.org/repo") val streamReceiver = TestStreamReceiver(repoId) - val indexProcessor = IndexV1StreamProcessor(streamReceiver, null, -1) + val indexProcessor = IndexV1StreamProcessor(streamReceiver, -1) db.runInTransaction { assets.open(path).use { indexStream -> indexProcessor.process(indexStream) @@ -80,7 +80,7 @@ internal class IndexV1InsertTest : DbTest() { val streamReceiver = TestStreamReceiver(repoId) { if (cIn.byteCount > 0) throw SerializationException() } - val indexProcessor = IndexV1StreamProcessor(streamReceiver, null, -1) + val indexProcessor = IndexV1StreamProcessor(streamReceiver, -1) cIn.use { indexStream -> indexProcessor.process(indexStream) } @@ -100,8 +100,8 @@ internal class IndexV1InsertTest : DbTest() { private val callback: () -> Unit = {}, ) : IndexV1StreamReceiver { private val streamReceiver = DbV1StreamReceiver(db, repoId) { true } - override fun receive(repo: RepoV2, version: Long, certificate: String?) { - streamReceiver.receive(repo, version, certificate) + override fun receive(repo: RepoV2, version: Long) { + streamReceiver.receive(repo, version) callback() } diff --git a/libs/database/src/dbTest/java/org/fdroid/database/IndexV2InsertTest.kt b/libs/database/src/dbTest/java/org/fdroid/database/IndexV2InsertTest.kt index 63e1e042f..fffc872f6 100644 --- a/libs/database/src/dbTest/java/org/fdroid/database/IndexV2InsertTest.kt +++ b/libs/database/src/dbTest/java/org/fdroid/database/IndexV2InsertTest.kt @@ -64,7 +64,7 @@ internal class IndexV2InsertTest : DbTest() { db.runInTransaction { val repoId = db.getRepositoryDao().insertEmptyRepo("http://example.org") val streamReceiver = DbV2StreamReceiver(db, repoId, compatibilityChecker) - val indexProcessor = IndexV2FullStreamProcessor(streamReceiver, "") + val indexProcessor = IndexV2FullStreamProcessor(streamReceiver) cIn.use { indexStream -> indexProcessor.process(42, indexStream) {} } diff --git a/libs/database/src/dbTest/java/org/fdroid/database/MultiRepoMigrationTest.kt b/libs/database/src/dbTest/java/org/fdroid/database/MultiRepoMigrationTest.kt index c799b9e5f..ce162ab9e 100644 --- a/libs/database/src/dbTest/java/org/fdroid/database/MultiRepoMigrationTest.kt +++ b/libs/database/src/dbTest/java/org/fdroid/database/MultiRepoMigrationTest.kt @@ -232,6 +232,7 @@ internal class MultiRepoMigrationTest { // now get the Room DB, so we can use our DAOs for verifying the migration databaseBuilder(getApplicationContext(), FDroidDatabaseInt::class.java, TEST_DB) + .addMigrations(MIGRATION_2_3) .allowMainThreadQueries() .build() .use { db -> @@ -274,15 +275,11 @@ internal class MultiRepoMigrationTest { // now get the Room DB, so we can use our DAOs for verifying the migration databaseBuilder(getApplicationContext(), FDroidDatabaseInt::class.java, TEST_DB) + .addMigrations(MIGRATION_2_3) .allowMainThreadQueries() .build().use { db -> - // repo without cert did not get migrated - assertEquals(1, db.getRepositoryDao().getRepositories().size) - val repo = db.getRepositoryDao().getRepositories()[0] - // cert is still null - assertNull(repo.certificate) - // address still the same - assertEquals(fdroidRepo.address, repo.address) + // repo without cert did not get migrated, because we auto-migrate to latest version + assertEquals(0, db.getRepositoryDao().getRepositories().size) } } @@ -314,6 +311,7 @@ internal class MultiRepoMigrationTest { // now get the Room DB, so we can use our DAOs for verifying the migration databaseBuilder(getApplicationContext(), FDroidDatabaseInt::class.java, TEST_DB) + .addMigrations(MIGRATION_2_3) .allowMainThreadQueries() .build().use { db -> check(db) diff --git a/libs/database/src/dbTest/java/org/fdroid/database/RepoCertNonNullMigrationTest.kt b/libs/database/src/dbTest/java/org/fdroid/database/RepoCertNonNullMigrationTest.kt new file mode 100644 index 000000000..72d59b7b6 --- /dev/null +++ b/libs/database/src/dbTest/java/org/fdroid/database/RepoCertNonNullMigrationTest.kt @@ -0,0 +1,87 @@ +package org.fdroid.database + +import android.content.ContentValues +import android.database.sqlite.SQLiteDatabase +import androidx.room.Room +import androidx.room.testing.MigrationTestHelper +import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory +import androidx.test.core.app.ApplicationProvider.getApplicationContext +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation +import org.fdroid.database.Converters.localizedTextV2toString +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import kotlin.test.assertEquals + +private const val TEST_DB = "migration-test" + +@RunWith(AndroidJUnit4::class) +internal class RepoCertNonNullMigrationTest { + + @get:Rule + val helper: MigrationTestHelper = MigrationTestHelper( + instrumentation = getInstrumentation(), + databaseClass = FDroidDatabaseInt::class.java, + specs = emptyList(), + openFactory = FrameworkSQLiteOpenHelperFactory(), + ) + + @Test + fun migrateRepos() { + helper.createDatabase(TEST_DB, 2).use { db -> + // Database has schema version 2. Insert some data using SQL queries. + // We can't use DAO classes because they expect the latest schema. + val repoId1 = db.insert( + CoreRepository.TABLE, + SQLiteDatabase.CONFLICT_FAIL, + ContentValues().apply { + put("name", localizedTextV2toString(mapOf("en-US" to "foo"))) + put("description", localizedTextV2toString(mapOf("en-US" to "bar"))) + put("address", "https://example.org/repo") + put("certificate", "0123") + put("timestamp", -1) + }) + db.insert( + RepositoryPreferences.TABLE, + SQLiteDatabase.CONFLICT_FAIL, + ContentValues().apply { + put("repoId", repoId1) + put("enabled", true) + put("weight", Long.MAX_VALUE) + }) + val repoId2 = db.insert( + CoreRepository.TABLE, + SQLiteDatabase.CONFLICT_FAIL, + ContentValues().apply { + put("name", localizedTextV2toString(mapOf("en-US" to "no cert"))) + put("description", localizedTextV2toString(mapOf("en-US" to "no cert desc"))) + put("address", "https://example.com/repo") + put("timestamp", -1) + }) + db.insert( + RepositoryPreferences.TABLE, + SQLiteDatabase.CONFLICT_FAIL, + ContentValues().apply { + put("repoId", repoId2) + put("enabled", true) + put("weight", Long.MAX_VALUE - 2) + }) + } + + // Re-open the database with version 2, auto-migrations are applied automatically + helper.runMigrationsAndValidate(TEST_DB, 4, true, MIGRATION_2_3).close() + + // now get the Room DB, so we can use our DAOs for verifying the migration + Room.databaseBuilder(getApplicationContext(), FDroidDatabaseInt::class.java, TEST_DB) + .addMigrations(MIGRATION_2_3) + .allowMainThreadQueries() + .build().use { db -> + // repo without cert did not get migrated, the other one did + assertEquals(1, db.getRepositoryDao().getRepositories().size) + val repo = db.getRepositoryDao().getRepositories()[0] + assertEquals("https://example.org/repo", repo.address) + } + } + +} diff --git a/libs/database/src/dbTest/java/org/fdroid/database/RepositoryDaoTest.kt b/libs/database/src/dbTest/java/org/fdroid/database/RepositoryDaoTest.kt index 2084cc8a3..1a1f079c5 100644 --- a/libs/database/src/dbTest/java/org/fdroid/database/RepositoryDaoTest.kt +++ b/libs/database/src/dbTest/java/org/fdroid/database/RepositoryDaoTest.kt @@ -265,19 +265,6 @@ internal class RepositoryDaoTest : DbTest() { assertEquals(repositoryPreferences, repoDao.getRepositoryPreferences(repoId)) } - @Test - fun certGetsUpdated() { - val repoId = repoDao.insertOrReplace(getRandomRepo()) - assertEquals(1, repoDao.getRepositories().size) - assertEquals(null, repoDao.getRepositories()[0].certificate) - - val cert = getRandomString() - repoDao.updateRepository(repoId, cert) - - assertEquals(1, repoDao.getRepositories().size) - assertEquals(cert, repoDao.getRepositories()[0].certificate) - } - @Test fun testGetMinRepositoryWeight() { assertEquals(Int.MAX_VALUE, repoDao.getMinRepositoryWeight()) @@ -344,13 +331,13 @@ internal class RepositoryDaoTest : DbTest() { ) // we'll add an archive repo for repo1 to the list [3, 5, (1, 1a), 4, 2] - repoDao.updateRepository(repoId1, "1234abcd") val repo1 = repoDao.getRepository(repoId1) ?: fail() + repoDao.updateRepository(repo1.repository.copy(certificate = "1234abcd")) val repo1a = InitialRepository( name = getRandomString(), address = "https://example.org/archive", description = getRandomString(), - certificate = repo1.certificate ?: fail(), + certificate = "1234abcd", // same as repo1 version = 42L, enabled = false, ) diff --git a/libs/database/src/dbTest/java/org/fdroid/index/IndexUpdaterTest.kt b/libs/database/src/dbTest/java/org/fdroid/index/IndexUpdaterTest.kt index a20d3b6e2..aff1da593 100644 --- a/libs/database/src/dbTest/java/org/fdroid/index/IndexUpdaterTest.kt +++ b/libs/database/src/dbTest/java/org/fdroid/index/IndexUpdaterTest.kt @@ -17,7 +17,7 @@ internal class IndexUpdaterTest { address = "http://example.org/", timestamp = 1337L, formatVersion = IndexFormatVersion.TWO, - certificate = null, + certificate = "abcd", version = 2001, weight = 0, lastUpdated = 23L, diff --git a/libs/database/src/dbTest/java/org/fdroid/index/v1/IndexV1UpdaterTest.kt b/libs/database/src/dbTest/java/org/fdroid/index/v1/IndexV1UpdaterTest.kt index 167c416c0..89ed3d0fb 100644 --- a/libs/database/src/dbTest/java/org/fdroid/index/v1/IndexV1UpdaterTest.kt +++ b/libs/database/src/dbTest/java/org/fdroid/index/v1/IndexV1UpdaterTest.kt @@ -57,10 +57,10 @@ internal class IndexV1UpdaterTest : DbTest() { @Test fun testIndexV1Processing() { - val repoId = repoDao.insertEmptyRepo(TESTY_CANONICAL_URL) + val repoId = repoDao.insertEmptyRepo(TESTY_CANONICAL_URL, certificate = TESTY_CERT) val repo = repoDao.getRepository(repoId) ?: fail() downloadIndex(repo, TESTY_JAR) - val result = indexUpdater.updateNewRepo(repo, TESTY_FINGERPRINT).noError() + val result = indexUpdater.update(repo).noError() assertIs(result) // repo got updated @@ -124,7 +124,7 @@ internal class IndexV1UpdaterTest : DbTest() { val repoId = repoDao.insertEmptyRepo(TESTY_CANONICAL_URL) val repo = repoDao.getRepository(repoId) ?: fail() downloadIndex(repo, TESTY_JAR) - val result = indexUpdater.updateNewRepo(repo, "not the right fingerprint") + val result = indexUpdater.update(repo) assertIs(result) assertIs(result.e) @@ -141,7 +141,7 @@ internal class IndexV1UpdaterTest : DbTest() { val futureRepo = repo.copy(repository = repo.repository.copy(timestamp = System.currentTimeMillis())) downloadIndex(futureRepo, TESTY_JAR) - val result = indexUpdater.updateNewRepo(futureRepo, TESTY_FINGERPRINT) + val result = indexUpdater.update(futureRepo) assertIs(result) assertIs(result.e) assertFalse((result.e as OldIndexException).isSameTimestamp) @@ -208,7 +208,7 @@ internal class IndexV1UpdaterTest : DbTest() { val repoId = repoDao.insertEmptyRepo("http://example.org") val repo = repoDao.getRepository(repoId) ?: fail() downloadIndex(repo, jar) - return indexUpdater.updateNewRepo(repo, null) + return indexUpdater.update(repo) } /** diff --git a/libs/database/src/dbTest/java/org/fdroid/index/v2/IndexV2UpdaterTest.kt b/libs/database/src/dbTest/java/org/fdroid/index/v2/IndexV2UpdaterTest.kt index 337208df0..66fc0bcc5 100644 --- a/libs/database/src/dbTest/java/org/fdroid/index/v2/IndexV2UpdaterTest.kt +++ b/libs/database/src/dbTest/java/org/fdroid/index/v2/IndexV2UpdaterTest.kt @@ -21,15 +21,14 @@ import org.fdroid.test.TestDataMaxV2 import org.fdroid.test.TestDataMidV2 import org.fdroid.test.TestDataMinV2 import org.fdroid.test.VerifierConstants.CERTIFICATE -import org.fdroid.test.VerifierConstants.FINGERPRINT import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.rules.TemporaryFolder import org.junit.runner.RunWith import kotlin.test.assertEquals +import kotlin.test.assertIs import kotlin.test.assertNull -import kotlin.test.assertTrue import kotlin.test.fail @RunWith(AndroidJUnit4::class) @@ -57,18 +56,18 @@ internal class IndexV2UpdaterTest : DbTest() { @Test fun testFullIndexEmptyToMin() { - val repoId = repoDao.insertEmptyRepo("http://example.org") + val repoId = repoDao.insertEmptyRepo("http://example.org", certificate = CERTIFICATE) val repo = prepareUpdate( repoId = repoId, entryPath = "diff-empty-min/$SIGNED_FILE_NAME", jsonPath = "index-min-v2.json", indexFileV2 = TestDataEntry.emptyToMin.index ) - val result = indexUpdater.updateNewRepo(repo, FINGERPRINT).noError() + val result = indexUpdater.update(repo).noError() assertEquals(IndexUpdateResult.Processed, result) assertDbEquals(repoId, TestDataMinV2.index) - // check that certificate and format version got entered + // check that format version got entered and certificate stayed the same val updatedRepo = repoDao.getRepository(repoId) ?: fail() assertEquals(TWO, updatedRepo.formatVersion) assertEquals(CERTIFICATE, updatedRepo.certificate) @@ -77,14 +76,14 @@ internal class IndexV2UpdaterTest : DbTest() { @Test fun testFullIndexEmptyToMid() { - val repoId = repoDao.insertEmptyRepo("http://example.org") + val repoId = repoDao.insertEmptyRepo("http://example.org", certificate = CERTIFICATE) val repo = prepareUpdate( repoId = repoId, entryPath = "diff-empty-mid/$SIGNED_FILE_NAME", jsonPath = "index-mid-v2.json", indexFileV2 = TestDataEntry.emptyToMid.index ) - val result = indexUpdater.updateNewRepo(repo, FINGERPRINT).noError() + val result = indexUpdater.update(repo).noError() assertEquals(IndexUpdateResult.Processed, result) assertDbEquals(repoId, TestDataMidV2.index) assertTimestampRecent(repoDao.getRepository(repoId)?.lastUpdated) @@ -92,14 +91,14 @@ internal class IndexV2UpdaterTest : DbTest() { @Test fun testFullIndexEmptyToMax() { - val repoId = repoDao.insertEmptyRepo("http://example.org") + val repoId = repoDao.insertEmptyRepo("http://example.org", certificate = CERTIFICATE) val repo = prepareUpdate( repoId = repoId, entryPath = "diff-empty-max/$SIGNED_FILE_NAME", jsonPath = "index-max-v2.json", indexFileV2 = TestDataEntry.emptyToMax.index ) - val result = indexUpdater.updateNewRepo(repo, FINGERPRINT).noError() + val result = indexUpdater.update(repo).noError() assertEquals(IndexUpdateResult.Processed, result) assertDbEquals(repoId, TestDataMaxV2.index) assertTimestampRecent(repoDao.getRepository(repoId)?.lastUpdated) @@ -123,7 +122,6 @@ internal class IndexV2UpdaterTest : DbTest() { @Test fun testDiffEmptyToMin() { val repoId = streamIndexV2IntoDb("index-empty-v2.json") - repoDao.updateRepository(repoId, CERTIFICATE) val repo = prepareUpdate( repoId = repoId, entryPath = "diff-empty-min/$SIGNED_FILE_NAME", @@ -139,7 +137,6 @@ internal class IndexV2UpdaterTest : DbTest() { @Test fun testDiffMidToMax() { val repoId = streamIndexV2IntoDb("index-mid-v2.json") - repoDao.updateRepository(repoId, CERTIFICATE) val repo = prepareUpdate( repoId = repoId, entryPath = "diff-empty-max/$SIGNED_FILE_NAME", @@ -155,7 +152,6 @@ internal class IndexV2UpdaterTest : DbTest() { @Test fun testSameTimestampUnchanged() { val repoId = streamIndexV2IntoDb("index-min-v2.json") - repoDao.updateRepository(repoId, CERTIFICATE) val repo = prepareUpdate( repoId = repoId, entryPath = "diff-empty-min/$SIGNED_FILE_NAME", @@ -171,7 +167,6 @@ internal class IndexV2UpdaterTest : DbTest() { @Test fun testHigherTimestampUnchanged() { val repoId = streamIndexV2IntoDb("index-mid-v2.json") - repoDao.updateRepository(repoId, CERTIFICATE) val repo = prepareUpdate( repoId = repoId, entryPath = "diff-empty-min/$SIGNED_FILE_NAME", @@ -186,7 +181,6 @@ internal class IndexV2UpdaterTest : DbTest() { @Test fun testNoDiffFoundIndexFallback() { val repoId = streamIndexV2IntoDb("index-empty-v2.json") - repoDao.updateRepository(repoId, CERTIFICATE) // fake timestamp of internal repo, so we will fail to find a diff in entry.json val newRepo = repoDao.getRepository(repoId)?.repository?.copy(timestamp = 22) ?: fail() repoDao.updateRepository(newRepo) @@ -203,20 +197,6 @@ internal class IndexV2UpdaterTest : DbTest() { @Test fun testWrongFingerprint() { - val repoId = repoDao.insertEmptyRepo("http://example.org") - val repo = prepareUpdate( - repoId = repoId, - entryPath = "diff-empty-min/$SIGNED_FILE_NAME", - jsonPath = "index-min-v2.json", - indexFileV2 = TestDataEntry.emptyToMin.index - ) - val result = indexUpdater.updateNewRepo(repo, "wrong fingerprint") - assertTrue(result is IndexUpdateResult.Error) - assertTrue(result.e is SigningException) - } - - @Test - fun testNormalUpdateOnRepoWithMissingFingerprint() { val repoId = repoDao.insertEmptyRepo("http://example.org") val repo = prepareUpdate( repoId = repoId, @@ -225,8 +205,8 @@ internal class IndexV2UpdaterTest : DbTest() { indexFileV2 = TestDataEntry.emptyToMin.index ) val result = indexUpdater.update(repo) - assertTrue(result is IndexUpdateResult.Error) - assertTrue(result.e is IllegalArgumentException) + assertIs(result) + assertIs(result.e) } /** diff --git a/libs/database/src/main/java/org/fdroid/database/DbV1StreamReceiver.kt b/libs/database/src/main/java/org/fdroid/database/DbV1StreamReceiver.kt index 5fc39bd59..dad73040e 100644 --- a/libs/database/src/main/java/org/fdroid/database/DbV1StreamReceiver.kt +++ b/libs/database/src/main/java/org/fdroid/database/DbV1StreamReceiver.kt @@ -26,9 +26,9 @@ internal class DbV1StreamReceiver( private val locales: LocaleListCompat = getLocales(Resources.getSystem().configuration) - override fun receive(repo: RepoV2, version: Long, certificate: String?) { + override fun receive(repo: RepoV2, version: Long) { db.getRepositoryDao().clear(repoId) - db.getRepositoryDao().update(repoId, repo, version, ONE, certificate) + db.getRepositoryDao().update(repoId, repo, version, ONE) } override fun receive(packageName: String, m: MetadataV2) { diff --git a/libs/database/src/main/java/org/fdroid/database/DbV2StreamReceiver.kt b/libs/database/src/main/java/org/fdroid/database/DbV2StreamReceiver.kt index e4bce8e99..a3ccb0d13 100644 --- a/libs/database/src/main/java/org/fdroid/database/DbV2StreamReceiver.kt +++ b/libs/database/src/main/java/org/fdroid/database/DbV2StreamReceiver.kt @@ -36,10 +36,10 @@ internal class DbV2StreamReceiver( } @Synchronized - override fun receive(repo: RepoV2, version: Long, certificate: String) { + override fun receive(repo: RepoV2, version: Long) { repo.walkFiles(nonNullFileV2) clearRepoDataIfNeeded() - db.getRepositoryDao().update(repoId, repo, version, TWO, certificate) + db.getRepositoryDao().update(repoId, repo, version, TWO) } @Synchronized diff --git a/libs/database/src/main/java/org/fdroid/database/FDroidDatabase.kt b/libs/database/src/main/java/org/fdroid/database/FDroidDatabase.kt index 5f586a30c..6b3c81218 100644 --- a/libs/database/src/main/java/org/fdroid/database/FDroidDatabase.kt +++ b/libs/database/src/main/java/org/fdroid/database/FDroidDatabase.kt @@ -16,7 +16,7 @@ import java.util.concurrent.Callable // When bumping this version, please make sure to add one (or more) migration(s) below! // Consider also providing tests for that migration. // Don't forget to commit the new schema to the git repo as well. - version = 2, + version = 4, entities = [ // repo CoreRepository::class, @@ -44,6 +44,8 @@ import java.util.concurrent.Callable exportSchema = true, autoMigrations = [ AutoMigration(1, 2, MultiRepoMigration::class), + // 2 to 3 is a manual migration + AutoMigration(3, 4), // add future migrations here (if they are easy enough to be done automatically) ], ) diff --git a/libs/database/src/main/java/org/fdroid/database/FDroidDatabaseHolder.kt b/libs/database/src/main/java/org/fdroid/database/FDroidDatabaseHolder.kt index 620bd1a82..a8f87c3cf 100644 --- a/libs/database/src/main/java/org/fdroid/database/FDroidDatabaseHolder.kt +++ b/libs/database/src/main/java/org/fdroid/database/FDroidDatabaseHolder.kt @@ -58,6 +58,7 @@ public object FDroidDatabaseHolder { FDroidDatabaseInt::class.java, name, ).apply { + addMigrations(MIGRATION_2_3) // We allow destructive migration (if no real migration was provided), // so we have the option to nuke the DB in production (if that will ever be needed). fallbackToDestructiveMigration() diff --git a/libs/database/src/main/java/org/fdroid/database/Migrations.kt b/libs/database/src/main/java/org/fdroid/database/Migrations.kt index ec5e6250f..2e23057b2 100644 --- a/libs/database/src/main/java/org/fdroid/database/Migrations.kt +++ b/libs/database/src/main/java/org/fdroid/database/Migrations.kt @@ -4,6 +4,7 @@ import android.content.ContentValues import android.database.Cursor import android.database.sqlite.SQLiteDatabase.CONFLICT_FAIL import androidx.room.migration.AutoMigrationSpec +import androidx.room.migration.Migration import androidx.sqlite.db.SupportSQLiteDatabase import mu.KotlinLogging @@ -104,3 +105,13 @@ internal class MultiRepoMigration : AutoMigrationSpec { fun isArchive(): Boolean = address.trimEnd('/').endsWith("/archive") } } + +/** + * Removes all repos without a certificate as those are broken anyway + * and force us to handle repos without certs. + */ +internal val MIGRATION_2_3 = object : Migration(2, 3) { + override fun migrate(db: SupportSQLiteDatabase) { + db.delete(CoreRepository.TABLE, "certificate IS NULL", null) + } +} diff --git a/libs/database/src/main/java/org/fdroid/database/Repository.kt b/libs/database/src/main/java/org/fdroid/database/Repository.kt index c5135bfa7..293bfb1e0 100644 --- a/libs/database/src/main/java/org/fdroid/database/Repository.kt +++ b/libs/database/src/main/java/org/fdroid/database/Repository.kt @@ -31,7 +31,7 @@ internal data class CoreRepository( val formatVersion: IndexFormatVersion?, val maxAge: Int?, val description: LocalizedTextV2 = emptyMap(), - val certificate: String?, + val certificate: String, ) { internal companion object { const val TABLE = "CoreRepository" @@ -47,7 +47,7 @@ internal fun RepoV2.toCoreRepository( repoId: Long = 0, version: Long, formatVersion: IndexFormatVersion? = null, - certificate: String? = null, + certificate: String, ) = CoreRepository( repoId = repoId, name = name, @@ -99,7 +99,7 @@ public data class Repository internal constructor( address: String, timestamp: Long, formatVersion: IndexFormatVersion, - certificate: String?, + certificate: String, version: Long, weight: Int, lastUpdated: Long, @@ -135,7 +135,7 @@ public data class Repository internal constructor( public val timestamp: Long get() = repository.timestamp public val version: Long get() = repository.version ?: 0 public val formatVersion: IndexFormatVersion? get() = repository.formatVersion - public val certificate: String? get() = repository.certificate + public val certificate: String get() = repository.certificate /** * True if this repository is an archive repo. diff --git a/libs/database/src/main/java/org/fdroid/database/RepositoryDao.kt b/libs/database/src/main/java/org/fdroid/database/RepositoryDao.kt index 6c8347410..f467d398b 100644 --- a/libs/database/src/main/java/org/fdroid/database/RepositoryDao.kt +++ b/libs/database/src/main/java/org/fdroid/database/RepositoryDao.kt @@ -165,11 +165,13 @@ internal interface RepositoryDaoInt : RepositoryDao { } @Transaction + @VisibleForTesting @Deprecated("Use insert instead") fun insertEmptyRepo( address: String, username: String? = null, password: String? = null, + certificate: String = "6789" // just used for testing ): Long { val repo = CoreRepository( name = mapOf("en-US" to address), @@ -179,7 +181,7 @@ internal interface RepositoryDaoInt : RepositoryDao { version = null, formatVersion = null, maxAge = null, - certificate = null, + certificate = certificate, ) val repoId = insertOrReplace(repo) val currentMinWeight = getMinRepositoryWeight() @@ -197,7 +199,12 @@ internal interface RepositoryDaoInt : RepositoryDao { @Transaction @VisibleForTesting fun insertOrReplace(repository: RepoV2, version: Long = 0): Long { - val repoId = insertOrReplace(repository.toCoreRepository(version = version)) + val repoId = insertOrReplace( + repository.toCoreRepository( + version = version, + certificate = "0123", // just for testing + ) + ) val currentMinWeight = getMinRepositoryWeight() val repositoryPreferences = RepositoryPreferences(repoId, currentMinWeight - 2) insert(repositoryPreferences) @@ -258,9 +265,9 @@ internal interface RepositoryDaoInt : RepositoryDao { repository: RepoV2, version: Long, formatVersion: IndexFormatVersion, - certificate: String?, ) { - update(repository.toCoreRepository(repoId, version, formatVersion, certificate)) + val repo = getRepository(repoId) ?: error("Repo with id $repoId did not exist") + update(repository.toCoreRepository(repoId, version, formatVersion, repo.certificate)) insertRepoTables(repoId, repository) } @@ -274,16 +281,6 @@ internal interface RepositoryDaoInt : RepositoryDao { @Update fun updateRepository(repo: CoreRepository): Int - /** - * Updates the certificate for the [Repository] with the given [repoId]. - * This should be used for V1 index updating where we only get the full cert - * after reading the entire index file. - * V2 index should use [update] instead as there the certificate is known - * before reading full index. - */ - @Query("UPDATE ${CoreRepository.TABLE} SET certificate = :certificate WHERE repoId = :repoId") - fun updateRepository(repoId: Long, certificate: String) - @Update fun updateRepositoryPreferences(preferences: RepositoryPreferences) diff --git a/libs/database/src/main/java/org/fdroid/index/IndexUpdater.kt b/libs/database/src/main/java/org/fdroid/index/IndexUpdater.kt index f2eaf7286..1695b550e 100644 --- a/libs/database/src/main/java/org/fdroid/index/IndexUpdater.kt +++ b/libs/database/src/main/java/org/fdroid/index/IndexUpdater.kt @@ -56,24 +56,11 @@ public abstract class IndexUpdater { */ public abstract val formatVersion: IndexFormatVersion - /** - * Updates a new [repo] for the first time. - */ - public fun updateNewRepo( - repo: Repository, - expectedSigningFingerprint: String?, - ): IndexUpdateResult = catchExceptions { - update(repo, null, expectedSigningFingerprint) - } - /** * Updates an existing [repo] with a known [Repository.certificate]. */ - public fun update( - repo: Repository, - ): IndexUpdateResult = catchExceptions { - require(repo.certificate != null) { "Repo ${repo.address} had no certificate" } - update(repo, repo.certificate, null) + public fun update(repo: Repository): IndexUpdateResult = catchExceptions { + updateRepo(repo) } private fun catchExceptions(block: () -> IndexUpdateResult): IndexUpdateResult { @@ -86,11 +73,7 @@ public abstract class IndexUpdater { } } - protected abstract fun update( - repo: Repository, - certificate: String?, - fingerprint: String?, - ): IndexUpdateResult + protected abstract fun updateRepo(repo: Repository): IndexUpdateResult } internal fun Downloader.setIndexUpdateListener( diff --git a/libs/database/src/main/java/org/fdroid/index/RepoUpdater.kt b/libs/database/src/main/java/org/fdroid/index/RepoUpdater.kt index fce2a125a..926824934 100644 --- a/libs/database/src/main/java/org/fdroid/index/RepoUpdater.kt +++ b/libs/database/src/main/java/org/fdroid/index/RepoUpdater.kt @@ -51,29 +51,8 @@ public class RepoUpdater( /** * Updates the given [repo]. - * If [Repository.certificate] is null, - * the repo is considered to be new this being the first update. */ - public fun update( - repo: Repository, - fingerprint: String? = null, - ): IndexUpdateResult { - return if (repo.certificate == null) { - // This is a new repo without a certificate - updateNewRepo(repo, fingerprint) - } else { - update(repo) - } - } - - private fun updateNewRepo( - repo: Repository, - expectedSigningFingerprint: String?, - ): IndexUpdateResult = update(repo) { updater -> - updater.updateNewRepo(repo, expectedSigningFingerprint) - } - - private fun update(repo: Repository): IndexUpdateResult = update(repo) { updater -> + public fun update(repo: Repository): IndexUpdateResult = update(repo) { updater -> updater.update(repo) } diff --git a/libs/database/src/main/java/org/fdroid/index/v1/IndexV1Updater.kt b/libs/database/src/main/java/org/fdroid/index/v1/IndexV1Updater.kt index 535342588..ed4f822ff 100644 --- a/libs/database/src/main/java/org/fdroid/index/v1/IndexV1Updater.kt +++ b/libs/database/src/main/java/org/fdroid/index/v1/IndexV1Updater.kt @@ -35,11 +35,7 @@ public class IndexV1Updater( public override val formatVersion: IndexFormatVersion = ONE private val db: FDroidDatabaseInt = database as FDroidDatabaseInt - override fun update( - repo: Repository, - certificate: String?, - fingerprint: String?, - ): IndexUpdateResult { + override fun updateRepo(repo: Repository): IndexUpdateResult { // Normally, we shouldn't allow repository downgrades and assert the condition below. // However, F-Droid is concerned that late v2 bugs will require users to downgrade to v1, // as it happened already with the migration from v0 to v1. @@ -61,21 +57,16 @@ public class IndexV1Updater( if (!downloader.hasChanged()) return IndexUpdateResult.Unchanged val eTag = downloader.cacheTag - val verifier = IndexV1Verifier(file, certificate, fingerprint) + val verifier = IndexV1Verifier(file, repo.certificate, null) db.runInTransaction { - val (cert, _) = verifier.getStreamAndVerify { inputStream -> + verifier.getStreamAndVerify { inputStream -> listener?.onUpdateProgress(repo, 0, 0) val streamReceiver = DbV1StreamReceiver(db, repo.repoId, compatibilityChecker) - val streamProcessor = - IndexV1StreamProcessor(streamReceiver, certificate, repo.timestamp) + val streamProcessor = IndexV1StreamProcessor(streamReceiver, repo.timestamp) streamProcessor.process(inputStream) } - // update certificate, if we didn't have any before - val repoDao = db.getRepositoryDao() - if (certificate == null) { - repoDao.updateRepository(repo.repoId, cert) - } // update RepositoryPreferences with timestamp and ETag (for v1) + val repoDao = db.getRepositoryDao() val updatedPrefs = repo.preferences.copy( lastUpdated = System.currentTimeMillis(), lastETag = eTag, diff --git a/libs/database/src/main/java/org/fdroid/index/v2/IndexV2Updater.kt b/libs/database/src/main/java/org/fdroid/index/v2/IndexV2Updater.kt index f13c54b80..792027b86 100644 --- a/libs/database/src/main/java/org/fdroid/index/v2/IndexV2Updater.kt +++ b/libs/database/src/main/java/org/fdroid/index/v2/IndexV2Updater.kt @@ -34,12 +34,8 @@ public class IndexV2Updater( public override val formatVersion: IndexFormatVersion = TWO private val db: FDroidDatabaseInt = database as FDroidDatabaseInt - override fun update( - repo: Repository, - certificate: String?, - fingerprint: String?, - ): IndexUpdateResult { - val (cert, entry) = getCertAndEntry(repo, certificate, fingerprint) + override fun updateRepo(repo: Repository): IndexUpdateResult { + val (_, entry) = getCertAndEntry(repo, repo.certificate) // don't process repos that we already did process in the past if (entry.timestamp <= repo.timestamp) return IndexUpdateResult.Unchanged // get diff, if available @@ -47,7 +43,7 @@ public class IndexV2Updater( return if (diff == null || repo.formatVersion == ONE) { // no diff found (or this is upgrade from v1 repo), so do full index update val streamReceiver = DbV2StreamReceiver(db, repo.repoId, compatibilityChecker) - val streamProcessor = IndexV2FullStreamProcessor(streamReceiver, cert) + val streamProcessor = IndexV2FullStreamProcessor(streamReceiver) processStream(repo, entry.index, entry.version, streamProcessor) } else { // use available diff @@ -57,11 +53,7 @@ public class IndexV2Updater( } } - private fun getCertAndEntry( - repo: Repository, - certificate: String?, - fingerprint: String?, - ): Pair { + private fun getCertAndEntry(repo: Repository, certificate: String): Pair { val file = tempFileProvider.createTempFile() val downloader = downloaderFactory.createWithTryFirstMirror( repo = repo, @@ -73,7 +65,7 @@ public class IndexV2Updater( } try { downloader.download() - val verifier = EntryVerifier(file, certificate, fingerprint) + val verifier = EntryVerifier(file, certificate, null) return verifier.getStreamAndVerify { inputStream -> IndexParser.parseEntry(inputStream) } diff --git a/libs/database/src/main/java/org/fdroid/repo/RepoAdder.kt b/libs/database/src/main/java/org/fdroid/repo/RepoAdder.kt index e1e468acc..812ea0b56 100644 --- a/libs/database/src/main/java/org/fdroid/repo/RepoAdder.kt +++ b/libs/database/src/main/java/org/fdroid/repo/RepoAdder.kt @@ -366,7 +366,7 @@ internal class RepoAdder( address = uri.toString(), timestamp = -1L, formatVersion = indexFormatVersion, - certificate = null, + certificate = "This is fake and will be replaced by real cert before saving in DB.", version = 0L, weight = 0, lastUpdated = -1L, diff --git a/libs/database/src/main/java/org/fdroid/repo/RepoV2Fetcher.kt b/libs/database/src/main/java/org/fdroid/repo/RepoV2Fetcher.kt index ca2360f65..4c16ee94c 100644 --- a/libs/database/src/main/java/org/fdroid/repo/RepoV2Fetcher.kt +++ b/libs/database/src/main/java/org/fdroid/repo/RepoV2Fetcher.kt @@ -56,8 +56,8 @@ internal class RepoV2Fetcher( log.info { "Downloaded entry, now streaming index..." } - val streamReceiver = RepoV2StreamReceiver(receiver, repo.username, repo.password) - val streamProcessor = IndexV2FullStreamProcessor(streamReceiver, cert) + val streamReceiver = RepoV2StreamReceiver(receiver, cert, repo.username, repo.password) + val streamProcessor = IndexV2FullStreamProcessor(streamReceiver) val digestInputStream = if (uri.scheme?.startsWith("http") == true) { // stream index for http(s) downloads val indexRequest = DownloadRequest( diff --git a/libs/database/src/main/java/org/fdroid/repo/RepoV2StreamReceiver.kt b/libs/database/src/main/java/org/fdroid/repo/RepoV2StreamReceiver.kt index 31011ce18..b743a8eb9 100644 --- a/libs/database/src/main/java/org/fdroid/repo/RepoV2StreamReceiver.kt +++ b/libs/database/src/main/java/org/fdroid/repo/RepoV2StreamReceiver.kt @@ -19,6 +19,7 @@ import org.fdroid.index.v2.RepoV2 internal open class RepoV2StreamReceiver( private val receiver: RepoPreviewReceiver, + private val certificate: String, private val username: String?, private val password: String?, ) : IndexV2StreamReceiver { @@ -28,7 +29,7 @@ internal open class RepoV2StreamReceiver( repo: RepoV2, version: Long, formatVersion: IndexFormatVersion, - certificate: String?, + certificate: String, username: String?, password: String?, ) = Repository( @@ -80,7 +81,7 @@ internal open class RepoV2StreamReceiver( private val locales: LocaleListCompat = getLocales(Resources.getSystem().configuration) - override fun receive(repo: RepoV2, version: Long, certificate: String) { + override fun receive(repo: RepoV2, version: Long) { receiver.onRepoReceived( getRepository( repo = repo, diff --git a/libs/database/src/test/java/org/fdroid/database/DbV2StreamReceiverTest.kt b/libs/database/src/test/java/org/fdroid/database/DbV2StreamReceiverTest.kt index 004720c30..6dc797671 100644 --- a/libs/database/src/test/java/org/fdroid/database/DbV2StreamReceiverTest.kt +++ b/libs/database/src/test/java/org/fdroid/database/DbV2StreamReceiverTest.kt @@ -27,25 +27,25 @@ internal class DbV2StreamReceiverTest { timestamp = 42L, ) every { db.getRepositoryDao() } returns mockk(relaxed = true) - dbV2StreamReceiver.receive(repoV2, 42L, "cert") + dbV2StreamReceiver.receive(repoV2, 42L) // icon file without leading / does not pass val repoV2NoSlash = repoV2.copy(icon = mapOf("en" to FileV2(name = "foo", sha256 = "bar", size = 23L))) assertFailsWith { - dbV2StreamReceiver.receive(repoV2NoSlash, 42L, "cert") + dbV2StreamReceiver.receive(repoV2NoSlash, 42L) } // icon file without sha256 hash fails val repoNoSha256 = repoV2.copy(icon = mapOf("en" to FileV2(name = "/foo", size = 23L))) assertFailsWith { - dbV2StreamReceiver.receive(repoNoSha256, 42L, "cert") + dbV2StreamReceiver.receive(repoNoSha256, 42L) } // icon file without size fails val repoNoSize = repoV2.copy(icon = mapOf("en" to FileV2(name = "/foo", sha256 = "bar"))) assertFailsWith { - dbV2StreamReceiver.receive(repoNoSize, 42L, "cert") + dbV2StreamReceiver.receive(repoNoSize, 42L) } } }