diff --git a/app/schemas/com.nextcloud.client.database.NextcloudDatabase/81.json b/app/schemas/com.nextcloud.client.database.NextcloudDatabase/81.json
index 10d076c10d38..3dd2580e23ed 100644
--- a/app/schemas/com.nextcloud.client.database.NextcloudDatabase/81.json
+++ b/app/schemas/com.nextcloud.client.database.NextcloudDatabase/81.json
@@ -1206,4 +1206,4 @@
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '082a63031678a67879428f688f02d3b5')"
]
}
-}
\ No newline at end of file
+}
diff --git a/app/schemas/com.nextcloud.client.database.NextcloudDatabase/83.json b/app/schemas/com.nextcloud.client.database.NextcloudDatabase/83.json
new file mode 100644
index 000000000000..c27bba8f602e
--- /dev/null
+++ b/app/schemas/com.nextcloud.client.database.NextcloudDatabase/83.json
@@ -0,0 +1,1245 @@
+{
+ "formatVersion": 1,
+ "database": {
+ "version": 83,
+ "identityHash": "365a8731a100a61ae5029beb74acd02e",
+ "entities": [
+ {
+ "tableName": "arbitrary_data",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT, `cloud_id` TEXT, `key` TEXT, `value` TEXT)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "_id",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "cloudId",
+ "columnName": "cloud_id",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "key",
+ "columnName": "key",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "value",
+ "columnName": "value",
+ "affinity": "TEXT",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "_id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "capabilities",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT, `assistant` INTEGER, `account` TEXT, `version_mayor` INTEGER, `version_minor` INTEGER, `version_micro` INTEGER, `version_string` TEXT, `version_edition` TEXT, `extended_support` INTEGER, `core_pollinterval` INTEGER, `sharing_api_enabled` INTEGER, `sharing_public_enabled` INTEGER, `sharing_public_password_enforced` INTEGER, `sharing_public_expire_date_enabled` INTEGER, `sharing_public_expire_date_days` INTEGER, `sharing_public_expire_date_enforced` INTEGER, `sharing_public_send_mail` INTEGER, `sharing_public_upload` INTEGER, `sharing_user_send_mail` INTEGER, `sharing_resharing` INTEGER, `sharing_federation_outgoing` INTEGER, `sharing_federation_incoming` INTEGER, `files_bigfilechunking` INTEGER, `files_undelete` INTEGER, `files_versioning` INTEGER, `external_links` INTEGER, `server_name` TEXT, `server_color` TEXT, `server_text_color` TEXT, `server_element_color` TEXT, `server_slogan` TEXT, `server_logo` TEXT, `background_url` TEXT, `end_to_end_encryption` INTEGER, `end_to_end_encryption_keys_exist` INTEGER, `end_to_end_encryption_api_version` TEXT, `activity` INTEGER, `background_default` INTEGER, `background_plain` INTEGER, `richdocument` INTEGER, `richdocument_mimetype_list` TEXT, `richdocument_direct_editing` INTEGER, `richdocument_direct_templates` INTEGER, `richdocument_optional_mimetype_list` TEXT, `sharing_public_ask_for_optional_password` INTEGER, `richdocument_product_name` TEXT, `direct_editing_etag` TEXT, `user_status` INTEGER, `user_status_supports_emoji` INTEGER, `etag` TEXT, `files_locking_version` TEXT, `groupfolders` INTEGER, `drop_account` INTEGER, `security_guard` INTEGER, `forbidden_filename_characters` INTEGER, `forbidden_filenames` INTEGER, `forbidden_filename_extensions` INTEGER, `forbidden_filename_basenames` INTEGER)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "_id",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "assistant",
+ "columnName": "assistant",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "accountName",
+ "columnName": "account",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "versionMajor",
+ "columnName": "version_mayor",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "versionMinor",
+ "columnName": "version_minor",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "versionMicro",
+ "columnName": "version_micro",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "versionString",
+ "columnName": "version_string",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "versionEditor",
+ "columnName": "version_edition",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "extendedSupport",
+ "columnName": "extended_support",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "corePollinterval",
+ "columnName": "core_pollinterval",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "sharingApiEnabled",
+ "columnName": "sharing_api_enabled",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "sharingPublicEnabled",
+ "columnName": "sharing_public_enabled",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "sharingPublicPasswordEnforced",
+ "columnName": "sharing_public_password_enforced",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "sharingPublicExpireDateEnabled",
+ "columnName": "sharing_public_expire_date_enabled",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "sharingPublicExpireDateDays",
+ "columnName": "sharing_public_expire_date_days",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "sharingPublicExpireDateEnforced",
+ "columnName": "sharing_public_expire_date_enforced",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "sharingPublicSendMail",
+ "columnName": "sharing_public_send_mail",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "sharingPublicUpload",
+ "columnName": "sharing_public_upload",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "sharingUserSendMail",
+ "columnName": "sharing_user_send_mail",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "sharingResharing",
+ "columnName": "sharing_resharing",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "sharingFederationOutgoing",
+ "columnName": "sharing_federation_outgoing",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "sharingFederationIncoming",
+ "columnName": "sharing_federation_incoming",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "filesBigfilechunking",
+ "columnName": "files_bigfilechunking",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "filesUndelete",
+ "columnName": "files_undelete",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "filesVersioning",
+ "columnName": "files_versioning",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "externalLinks",
+ "columnName": "external_links",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "serverName",
+ "columnName": "server_name",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "serverColor",
+ "columnName": "server_color",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "serverTextColor",
+ "columnName": "server_text_color",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "serverElementColor",
+ "columnName": "server_element_color",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "serverSlogan",
+ "columnName": "server_slogan",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "serverLogo",
+ "columnName": "server_logo",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "serverBackgroundUrl",
+ "columnName": "background_url",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "endToEndEncryption",
+ "columnName": "end_to_end_encryption",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "endToEndEncryptionKeysExist",
+ "columnName": "end_to_end_encryption_keys_exist",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "endToEndEncryptionApiVersion",
+ "columnName": "end_to_end_encryption_api_version",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "activity",
+ "columnName": "activity",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "serverBackgroundDefault",
+ "columnName": "background_default",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "serverBackgroundPlain",
+ "columnName": "background_plain",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "richdocument",
+ "columnName": "richdocument",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "richdocumentMimetypeList",
+ "columnName": "richdocument_mimetype_list",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "richdocumentDirectEditing",
+ "columnName": "richdocument_direct_editing",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "richdocumentTemplates",
+ "columnName": "richdocument_direct_templates",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "richdocumentOptionalMimetypeList",
+ "columnName": "richdocument_optional_mimetype_list",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "sharingPublicAskForOptionalPassword",
+ "columnName": "sharing_public_ask_for_optional_password",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "richdocumentProductName",
+ "columnName": "richdocument_product_name",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "directEditingEtag",
+ "columnName": "direct_editing_etag",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "userStatus",
+ "columnName": "user_status",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "userStatusSupportsEmoji",
+ "columnName": "user_status_supports_emoji",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "etag",
+ "columnName": "etag",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "filesLockingVersion",
+ "columnName": "files_locking_version",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "groupfolders",
+ "columnName": "groupfolders",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "dropAccount",
+ "columnName": "drop_account",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "securityGuard",
+ "columnName": "security_guard",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "forbiddenFileNameCharacters",
+ "columnName": "forbidden_filename_characters",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "forbiddenFileNames",
+ "columnName": "forbidden_filenames",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "forbiddenFileNameExtensions",
+ "columnName": "forbidden_filename_extensions",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "forbiddenFilenameBaseNames",
+ "columnName": "forbidden_filename_basenames",
+ "affinity": "INTEGER",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "_id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "external_links",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT, `icon_url` TEXT, `language` TEXT, `type` INTEGER, `name` TEXT, `url` TEXT, `redirect` INTEGER)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "_id",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "iconUrl",
+ "columnName": "icon_url",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "language",
+ "columnName": "language",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "type",
+ "columnName": "type",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "url",
+ "columnName": "url",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "redirect",
+ "columnName": "redirect",
+ "affinity": "INTEGER",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "_id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "filelist",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT, `filename` TEXT, `encrypted_filename` TEXT, `path` TEXT, `path_decrypted` TEXT, `parent` INTEGER, `created` INTEGER, `modified` INTEGER, `content_type` TEXT, `content_length` INTEGER, `media_path` TEXT, `file_owner` TEXT, `last_sync_date` INTEGER, `last_sync_date_for_data` INTEGER, `modified_at_last_sync_for_data` INTEGER, `etag` TEXT, `etag_on_server` TEXT, `share_by_link` INTEGER, `permissions` TEXT, `remote_id` TEXT, `local_id` INTEGER NOT NULL DEFAULT -1, `update_thumbnail` INTEGER, `is_downloading` INTEGER, `favorite` INTEGER, `hidden` INTEGER, `is_encrypted` INTEGER, `etag_in_conflict` TEXT, `shared_via_users` INTEGER, `mount_type` INTEGER, `has_preview` INTEGER, `unread_comments_count` INTEGER, `owner_id` TEXT, `owner_display_name` TEXT, `note` TEXT, `sharees` TEXT, `rich_workspace` TEXT, `metadata_size` TEXT, `metadata_live_photo` TEXT, `locked` INTEGER, `lock_type` INTEGER, `lock_owner` TEXT, `lock_owner_display_name` TEXT, `lock_owner_editor` TEXT, `lock_timestamp` INTEGER, `lock_timeout` INTEGER, `lock_token` TEXT, `tags` TEXT, `metadata_gps` TEXT, `e2e_counter` INTEGER, `internal_two_way_sync_timestamp` INTEGER, `internal_two_way_sync_result` TEXT)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "_id",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "filename",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "encryptedName",
+ "columnName": "encrypted_filename",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "path",
+ "columnName": "path",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "pathDecrypted",
+ "columnName": "path_decrypted",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "parent",
+ "columnName": "parent",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "creation",
+ "columnName": "created",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "modified",
+ "columnName": "modified",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "contentType",
+ "columnName": "content_type",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "contentLength",
+ "columnName": "content_length",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "storagePath",
+ "columnName": "media_path",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "accountOwner",
+ "columnName": "file_owner",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "lastSyncDate",
+ "columnName": "last_sync_date",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "lastSyncDateForData",
+ "columnName": "last_sync_date_for_data",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "modifiedAtLastSyncForData",
+ "columnName": "modified_at_last_sync_for_data",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "etag",
+ "columnName": "etag",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "etagOnServer",
+ "columnName": "etag_on_server",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "sharedViaLink",
+ "columnName": "share_by_link",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "permissions",
+ "columnName": "permissions",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "remoteId",
+ "columnName": "remote_id",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "localId",
+ "columnName": "local_id",
+ "affinity": "INTEGER",
+ "notNull": true,
+ "defaultValue": "-1"
+ },
+ {
+ "fieldPath": "updateThumbnail",
+ "columnName": "update_thumbnail",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "isDownloading",
+ "columnName": "is_downloading",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "favorite",
+ "columnName": "favorite",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "hidden",
+ "columnName": "hidden",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "isEncrypted",
+ "columnName": "is_encrypted",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "etagInConflict",
+ "columnName": "etag_in_conflict",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "sharedWithSharee",
+ "columnName": "shared_via_users",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "mountType",
+ "columnName": "mount_type",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "hasPreview",
+ "columnName": "has_preview",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "unreadCommentsCount",
+ "columnName": "unread_comments_count",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "ownerId",
+ "columnName": "owner_id",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "ownerDisplayName",
+ "columnName": "owner_display_name",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "note",
+ "columnName": "note",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "sharees",
+ "columnName": "sharees",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "richWorkspace",
+ "columnName": "rich_workspace",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "metadataSize",
+ "columnName": "metadata_size",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "metadataLivePhoto",
+ "columnName": "metadata_live_photo",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "locked",
+ "columnName": "locked",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "lockType",
+ "columnName": "lock_type",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "lockOwner",
+ "columnName": "lock_owner",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "lockOwnerDisplayName",
+ "columnName": "lock_owner_display_name",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "lockOwnerEditor",
+ "columnName": "lock_owner_editor",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "lockTimestamp",
+ "columnName": "lock_timestamp",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "lockTimeout",
+ "columnName": "lock_timeout",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "lockToken",
+ "columnName": "lock_token",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "tags",
+ "columnName": "tags",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "metadataGPS",
+ "columnName": "metadata_gps",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "e2eCounter",
+ "columnName": "e2e_counter",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "internalTwoWaySync",
+ "columnName": "internal_two_way_sync_timestamp",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "internalTwoWaySyncResult",
+ "columnName": "internal_two_way_sync_result",
+ "affinity": "TEXT",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "_id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "filesystem",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT, `local_path` TEXT, `is_folder` INTEGER, `found_at` INTEGER, `upload_triggered` INTEGER, `syncedfolder_id` TEXT, `crc32` TEXT, `modified_at` INTEGER)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "_id",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "localPath",
+ "columnName": "local_path",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "fileIsFolder",
+ "columnName": "is_folder",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "fileFoundRecently",
+ "columnName": "found_at",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "fileSentForUpload",
+ "columnName": "upload_triggered",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "syncedFolderId",
+ "columnName": "syncedfolder_id",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "crc32",
+ "columnName": "crc32",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "fileModified",
+ "columnName": "modified_at",
+ "affinity": "INTEGER",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "_id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "ocshares",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT, `file_source` INTEGER, `item_source` INTEGER, `share_type` INTEGER, `shate_with` TEXT, `path` TEXT, `permissions` INTEGER, `shared_date` INTEGER, `expiration_date` INTEGER, `token` TEXT, `shared_with_display_name` TEXT, `is_directory` INTEGER, `user_id` TEXT, `id_remote_shared` INTEGER, `owner_share` TEXT, `is_password_protected` INTEGER, `note` TEXT, `hide_download` INTEGER, `share_link` TEXT, `share_label` TEXT)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "_id",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "fileSource",
+ "columnName": "file_source",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "itemSource",
+ "columnName": "item_source",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "shareType",
+ "columnName": "share_type",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "shareWith",
+ "columnName": "shate_with",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "path",
+ "columnName": "path",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "permissions",
+ "columnName": "permissions",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "sharedDate",
+ "columnName": "shared_date",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "expirationDate",
+ "columnName": "expiration_date",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "token",
+ "columnName": "token",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "shareWithDisplayName",
+ "columnName": "shared_with_display_name",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "isDirectory",
+ "columnName": "is_directory",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "userId",
+ "columnName": "user_id",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "idRemoteShared",
+ "columnName": "id_remote_shared",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "accountOwner",
+ "columnName": "owner_share",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "isPasswordProtected",
+ "columnName": "is_password_protected",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "note",
+ "columnName": "note",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "hideDownload",
+ "columnName": "hide_download",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "shareLink",
+ "columnName": "share_link",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "shareLabel",
+ "columnName": "share_label",
+ "affinity": "TEXT",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "_id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "synced_folders",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT, `local_path` TEXT, `remote_path` TEXT, `wifi_only` INTEGER, `charging_only` INTEGER, `existing` INTEGER, `enabled` INTEGER, `enabled_timestamp_ms` INTEGER, `subfolder_by_date` INTEGER, `account` TEXT, `upload_option` INTEGER, `name_collision_policy` INTEGER, `type` INTEGER, `hidden` INTEGER, `sub_folder_rule` INTEGER, `exclude_hidden` INTEGER, `last_scan_timestamp_ms` INTEGER)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "_id",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "localPath",
+ "columnName": "local_path",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "remotePath",
+ "columnName": "remote_path",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "wifiOnly",
+ "columnName": "wifi_only",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "chargingOnly",
+ "columnName": "charging_only",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "existing",
+ "columnName": "existing",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "enabled",
+ "columnName": "enabled",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "enabledTimestampMs",
+ "columnName": "enabled_timestamp_ms",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "subfolderByDate",
+ "columnName": "subfolder_by_date",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "account",
+ "columnName": "account",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "uploadAction",
+ "columnName": "upload_option",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "nameCollisionPolicy",
+ "columnName": "name_collision_policy",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "type",
+ "columnName": "type",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "hidden",
+ "columnName": "hidden",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "subFolderRule",
+ "columnName": "sub_folder_rule",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "excludeHidden",
+ "columnName": "exclude_hidden",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "lastScanTimestampMs",
+ "columnName": "last_scan_timestamp_ms",
+ "affinity": "INTEGER",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "_id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "list_of_uploads",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT, `local_path` TEXT, `remote_path` TEXT, `account_name` TEXT, `file_size` INTEGER, `status` INTEGER, `local_behaviour` INTEGER, `upload_time` INTEGER, `name_collision_policy` INTEGER, `is_create_remote_folder` INTEGER, `upload_end_timestamp` INTEGER, `last_result` INTEGER, `is_while_charging_only` INTEGER, `is_wifi_only` INTEGER, `created_by` INTEGER, `folder_unlock_token` TEXT)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "_id",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "localPath",
+ "columnName": "local_path",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "remotePath",
+ "columnName": "remote_path",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "accountName",
+ "columnName": "account_name",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "fileSize",
+ "columnName": "file_size",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "status",
+ "columnName": "status",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "localBehaviour",
+ "columnName": "local_behaviour",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "uploadTime",
+ "columnName": "upload_time",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "nameCollisionPolicy",
+ "columnName": "name_collision_policy",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "isCreateRemoteFolder",
+ "columnName": "is_create_remote_folder",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "uploadEndTimestamp",
+ "columnName": "upload_end_timestamp",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "lastResult",
+ "columnName": "last_result",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "isWhileChargingOnly",
+ "columnName": "is_while_charging_only",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "isWifiOnly",
+ "columnName": "is_wifi_only",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "createdBy",
+ "columnName": "created_by",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "folderUnlockToken",
+ "columnName": "folder_unlock_token",
+ "affinity": "TEXT",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "_id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "virtual",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT, `type` TEXT, `ocfile_id` INTEGER)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "_id",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "type",
+ "columnName": "type",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "ocFileId",
+ "columnName": "ocfile_id",
+ "affinity": "INTEGER",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "_id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ }
+ ],
+ "views": [],
+ "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, '365a8731a100a61ae5029beb74acd02e')"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/app/screenshots/gplay/debug/com.nextcloud.client.SettingsActivityIT_open.png b/app/screenshots/gplay/debug/com.nextcloud.client.SettingsActivityIT_open.png
index a5159c8740aa..9d35b9c6b572 100644
Binary files a/app/screenshots/gplay/debug/com.nextcloud.client.SettingsActivityIT_open.png and b/app/screenshots/gplay/debug/com.nextcloud.client.SettingsActivityIT_open.png differ
diff --git a/app/screenshots/gplay/debug/com.nextcloud.client.SettingsActivityIT_showMnemonic_Error.png b/app/screenshots/gplay/debug/com.nextcloud.client.SettingsActivityIT_showMnemonic_Error.png
index f2432977ce8e..4e7c8e82caad 100644
Binary files a/app/screenshots/gplay/debug/com.nextcloud.client.SettingsActivityIT_showMnemonic_Error.png and b/app/screenshots/gplay/debug/com.nextcloud.client.SettingsActivityIT_showMnemonic_Error.png differ
diff --git a/app/src/androidTest/java/com/owncloud/android/FileIT.java b/app/src/androidTest/java/com/owncloud/android/FileIT.java
index d5c24d3269cb..9f251d65c857 100644
--- a/app/src/androidTest/java/com/owncloud/android/FileIT.java
+++ b/app/src/androidTest/java/com/owncloud/android/FileIT.java
@@ -106,7 +106,6 @@ public void testRenameFolder() throws IOException {
assertTrue(new SynchronizeFolderOperation(targetContext,
folderPath,
user,
- System.currentTimeMillis(),
fileDataStorageManager)
.execute(targetContext)
.isSuccess());
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index af9fd5fae0f3..6f89c63ac939 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -255,6 +255,9 @@
+
@@ -627,4 +630,4 @@
-
\ No newline at end of file
+
diff --git a/app/src/main/java/com/nextcloud/client/database/NextcloudDatabase.kt b/app/src/main/java/com/nextcloud/client/database/NextcloudDatabase.kt
index bed77c0436a9..a4e0f74b8117 100644
--- a/app/src/main/java/com/nextcloud/client/database/NextcloudDatabase.kt
+++ b/app/src/main/java/com/nextcloud/client/database/NextcloudDatabase.kt
@@ -60,7 +60,8 @@ import com.owncloud.android.db.ProviderMeta
AutoMigration(from = 78, to = 79, spec = DatabaseMigrationUtil.ResetCapabilitiesPostMigration::class),
AutoMigration(from = 79, to = 80),
AutoMigration(from = 80, to = 81),
- AutoMigration(from = 81, to = 82)
+ AutoMigration(from = 81, to = 82),
+ AutoMigration(from = 82, to = 83)
],
exportSchema = true
)
diff --git a/app/src/main/java/com/nextcloud/client/database/dao/FileDao.kt b/app/src/main/java/com/nextcloud/client/database/dao/FileDao.kt
index 4dc78224018f..37ef4568c5be 100644
--- a/app/src/main/java/com/nextcloud/client/database/dao/FileDao.kt
+++ b/app/src/main/java/com/nextcloud/client/database/dao/FileDao.kt
@@ -49,4 +49,10 @@ interface FileDao {
@Query("SELECT * FROM filelist where file_owner = :fileOwner AND etag_in_conflict IS NOT NULL")
fun getFilesWithSyncConflict(fileOwner: String): List
+
+ @Query(
+ "SELECT * FROM filelist where file_owner = :fileOwner AND internal_two_way_sync_timestamp >= 0 " +
+ "ORDER BY internal_two_way_sync_timestamp DESC"
+ )
+ fun getInternalTwoWaySyncFolders(fileOwner: String): List
}
diff --git a/app/src/main/java/com/nextcloud/client/database/entity/FileEntity.kt b/app/src/main/java/com/nextcloud/client/database/entity/FileEntity.kt
index 2281a06ea9da..d223172bb91f 100644
--- a/app/src/main/java/com/nextcloud/client/database/entity/FileEntity.kt
+++ b/app/src/main/java/com/nextcloud/client/database/entity/FileEntity.kt
@@ -115,5 +115,9 @@ data class FileEntity(
@ColumnInfo(name = ProviderTableMeta.FILE_METADATA_GPS)
val metadataGPS: String?,
@ColumnInfo(name = ProviderTableMeta.FILE_E2E_COUNTER)
- val e2eCounter: Long?
+ val e2eCounter: Long?,
+ @ColumnInfo(name = ProviderTableMeta.FILE_INTERNAL_TWO_WAY_SYNC_TIMESTAMP)
+ val internalTwoWaySync: Long?,
+ @ColumnInfo(name = ProviderTableMeta.FILE_INTERNAL_TWO_WAY_SYNC_RESULT)
+ val internalTwoWaySyncResult: String?
)
diff --git a/app/src/main/java/com/nextcloud/client/di/ComponentsModule.java b/app/src/main/java/com/nextcloud/client/di/ComponentsModule.java
index a6ba1bcf15ec..37b1b10d98d4 100644
--- a/app/src/main/java/com/nextcloud/client/di/ComponentsModule.java
+++ b/app/src/main/java/com/nextcloud/client/di/ComponentsModule.java
@@ -54,6 +54,7 @@
import com.owncloud.android.ui.activity.FileDisplayActivity;
import com.owncloud.android.ui.activity.FilePickerActivity;
import com.owncloud.android.ui.activity.FolderPickerActivity;
+import com.owncloud.android.ui.activity.InternalTwoWaySyncActivity;
import com.owncloud.android.ui.activity.ManageAccountsActivity;
import com.owncloud.android.ui.activity.ManageSpaceActivity;
import com.owncloud.android.ui.activity.NotificationsActivity;
@@ -476,4 +477,7 @@ abstract class ComponentsModule {
@ContributesAndroidInjector
abstract TestJob testJob();
+
+ @ContributesAndroidInjector
+ abstract InternalTwoWaySyncActivity internalTwoWaySyncActivity();
}
diff --git a/app/src/main/java/com/nextcloud/client/jobs/BackgroundJobFactory.kt b/app/src/main/java/com/nextcloud/client/jobs/BackgroundJobFactory.kt
index 8bfc81c0301c..b42d1ef25d5e 100644
--- a/app/src/main/java/com/nextcloud/client/jobs/BackgroundJobFactory.kt
+++ b/app/src/main/java/com/nextcloud/client/jobs/BackgroundJobFactory.kt
@@ -95,6 +95,7 @@ class BackgroundJobFactory @Inject constructor(
GeneratePdfFromImagesWork::class -> createPDFGenerateWork(context, workerParameters)
HealthStatusWork::class -> createHealthStatusWork(context, workerParameters)
TestJob::class -> createTestJob(context, workerParameters)
+ InternalTwoWaySyncWork::class -> createInternalTwoWaySyncWork(context, workerParameters)
else -> null // caller falls back to default factory
}
}
@@ -277,4 +278,14 @@ class BackgroundJobFactory @Inject constructor(
backgroundJobManager.get()
)
}
+
+ private fun createInternalTwoWaySyncWork(context: Context, params: WorkerParameters): InternalTwoWaySyncWork {
+ return InternalTwoWaySyncWork(
+ context,
+ params,
+ accountManager,
+ powerManagementService,
+ connectivityService
+ )
+ }
}
diff --git a/app/src/main/java/com/nextcloud/client/jobs/BackgroundJobManager.kt b/app/src/main/java/com/nextcloud/client/jobs/BackgroundJobManager.kt
index ae6d35a5c39a..d460fd623f7c 100644
--- a/app/src/main/java/com/nextcloud/client/jobs/BackgroundJobManager.kt
+++ b/app/src/main/java/com/nextcloud/client/jobs/BackgroundJobManager.kt
@@ -168,4 +168,5 @@ interface BackgroundJobManager {
fun schedulePeriodicHealthStatus()
fun startHealthStatus()
fun bothFilesSyncJobsRunning(syncedFolderID: Long): Boolean
+ fun scheduleInternal2WaySync()
}
diff --git a/app/src/main/java/com/nextcloud/client/jobs/BackgroundJobManagerImpl.kt b/app/src/main/java/com/nextcloud/client/jobs/BackgroundJobManagerImpl.kt
index 76a9b836b8eb..af345d832bfd 100644
--- a/app/src/main/java/com/nextcloud/client/jobs/BackgroundJobManagerImpl.kt
+++ b/app/src/main/java/com/nextcloud/client/jobs/BackgroundJobManagerImpl.kt
@@ -84,6 +84,8 @@ internal class BackgroundJobManagerImpl(
const val JOB_PERIODIC_HEALTH_STATUS = "periodic_health_status"
const val JOB_IMMEDIATE_HEALTH_STATUS = "immediate_health_status"
+ const val JOB_INTERNAL_TWO_WAY_SYNC = "internal_two_way_sync"
+
const val JOB_TEST = "test_job"
const val MAX_CONTENT_TRIGGER_DELAY_MS = 10000L
@@ -647,4 +649,13 @@ internal class BackgroundJobManagerImpl(
request
)
}
+
+ override fun scheduleInternal2WaySync() {
+ val request = periodicRequestBuilder(
+ jobClass = InternalTwoWaySyncWork::class,
+ jobName = JOB_INTERNAL_TWO_WAY_SYNC
+ ).build()
+
+ workManager.enqueueUniquePeriodicWork(JOB_INTERNAL_TWO_WAY_SYNC, ExistingPeriodicWorkPolicy.KEEP, request)
+ }
}
diff --git a/app/src/main/java/com/nextcloud/client/jobs/InternalTwoWaySyncWork.kt b/app/src/main/java/com/nextcloud/client/jobs/InternalTwoWaySyncWork.kt
new file mode 100644
index 000000000000..18d32c7491a5
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/client/jobs/InternalTwoWaySyncWork.kt
@@ -0,0 +1,91 @@
+/*
+ * Nextcloud - Android Client
+ *
+ * SPDX-FileCopyrightText: 2024 Tobias Kaminsky
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+package com.nextcloud.client.jobs
+
+import android.content.Context
+import androidx.work.Worker
+import androidx.work.WorkerParameters
+import com.nextcloud.client.account.UserAccountManager
+import com.nextcloud.client.device.PowerManagementService
+import com.nextcloud.client.network.ConnectivityService
+import com.owncloud.android.MainApp
+import com.owncloud.android.datamodel.FileDataStorageManager
+import com.owncloud.android.lib.common.utils.Log_OC
+import com.owncloud.android.operations.SynchronizeFolderOperation
+import com.owncloud.android.utils.FileStorageUtils
+import java.io.File
+
+@Suppress("Detekt.NestedBlockDepth")
+class InternalTwoWaySyncWork(
+ private val context: Context,
+ params: WorkerParameters,
+ private val userAccountManager: UserAccountManager,
+ private val powerManagementService: PowerManagementService,
+ private val connectivityService: ConnectivityService
+) : Worker(context, params) {
+ override fun doWork(): Result {
+ Log_OC.d(TAG, "Worker started!")
+
+ var result = true
+
+ if (powerManagementService.isPowerSavingEnabled ||
+ !connectivityService.isConnected || connectivityService.isInternetWalled
+ ) {
+ Log_OC.d(TAG, "Not starting due to constraints!")
+ return Result.success()
+ }
+
+ val users = userAccountManager.allUsers
+
+ for (user in users) {
+ val fileDataStorageManager = FileDataStorageManager(user, context.contentResolver)
+ val folders = fileDataStorageManager.getInternalTwoWaySyncFolders(user)
+
+ for (folder in folders) {
+ val freeSpaceLeft = File(folder.storagePath).getFreeSpace()
+ val localFolderSize = FileStorageUtils.getFolderSize(File(folder.storagePath, MainApp.getDataFolder()))
+ val remoteFolderSize = folder.fileLength
+
+ if (freeSpaceLeft < (remoteFolderSize - localFolderSize)) {
+ Log_OC.d(TAG, "Not enough space left!")
+ result = false
+ }
+
+ Log_OC.d(TAG, "Folder ${folder.remotePath}: started!")
+ val operation = SynchronizeFolderOperation(context, folder.remotePath, user, fileDataStorageManager)
+ .execute(context)
+
+ if (operation.isSuccess) {
+ Log_OC.d(TAG, "Folder ${folder.remotePath}: finished!")
+ } else {
+ Log_OC.d(TAG, "Folder ${folder.remotePath} failed!")
+ result = false
+ }
+
+ folder.apply {
+ internalFolderSyncResult = operation.code.toString()
+ internalFolderSyncTimestamp = System.currentTimeMillis()
+ }
+
+ fileDataStorageManager.saveFile(folder)
+ }
+ }
+
+ return if (result) {
+ Log_OC.d(TAG, "Worker finished with success!")
+ Result.success()
+ } else {
+ Log_OC.d(TAG, "Worker finished with failure!")
+ Result.failure()
+ }
+ }
+
+ companion object {
+ const val TAG = "InternalTwoWaySyncWork"
+ }
+}
diff --git a/app/src/main/java/com/owncloud/android/MainApp.java b/app/src/main/java/com/owncloud/android/MainApp.java
index 9d8507b7b4b0..9fd3e2cb7abe 100644
--- a/app/src/main/java/com/owncloud/android/MainApp.java
+++ b/app/src/main/java/com/owncloud/android/MainApp.java
@@ -371,6 +371,7 @@ public void onCreate() {
backgroundJobManager.scheduleMediaFoldersDetectionJob();
backgroundJobManager.startMediaFoldersDetectionJob();
backgroundJobManager.schedulePeriodicHealthStatus();
+ backgroundJobManager.scheduleInternal2WaySync();
}
registerGlobalPassCodeProtection();
diff --git a/app/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java b/app/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java
index 95653322a517..5390b5d5c6e1 100644
--- a/app/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java
+++ b/app/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java
@@ -77,6 +77,7 @@
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import kotlin.Pair;
+@SuppressFBWarnings("CE")
public class FileDataStorageManager {
private static final String TAG = FileDataStorageManager.class.getSimpleName();
@@ -558,6 +559,8 @@ private ContentValues createContentValuesBase(OCFile fileOrFolder) {
cv.put(ProviderTableMeta.FILE_SHAREES, gson.toJson(fileOrFolder.getSharees()));
cv.put(ProviderTableMeta.FILE_TAGS, gson.toJson(fileOrFolder.getTags()));
cv.put(ProviderTableMeta.FILE_RICH_WORKSPACE, fileOrFolder.getRichWorkspace());
+ cv.put(ProviderTableMeta.FILE_INTERNAL_TWO_WAY_SYNC_TIMESTAMP, fileOrFolder.getInternalFolderSyncTimestamp());
+ cv.put(ProviderTableMeta.FILE_INTERNAL_TWO_WAY_SYNC_RESULT, fileOrFolder.getInternalFolderSyncResult());
return cv;
}
@@ -602,6 +605,8 @@ private ContentValues createContentValuesForFile(OCFile file) {
cv.put(ProviderTableMeta.FILE_METADATA_GPS, gson.toJson(file.getGeoLocation()));
cv.put(ProviderTableMeta.FILE_METADATA_LIVE_PHOTO, file.getLinkedFileIdForLivePhoto());
cv.put(ProviderTableMeta.FILE_E2E_COUNTER, file.getE2eCounter());
+ cv.put(ProviderTableMeta.FILE_INTERNAL_TWO_WAY_SYNC_TIMESTAMP, file.getInternalFolderSyncTimestamp());
+ cv.put(ProviderTableMeta.FILE_INTERNAL_TWO_WAY_SYNC_RESULT, file.getInternalFolderSyncResult());
return cv;
}
@@ -1035,6 +1040,7 @@ private OCFile createFileInstance(FileEntity fileEntity) {
ocFile.setLivePhoto(fileEntity.getMetadataLivePhoto());
ocFile.setHidden(nullToZero(fileEntity.getHidden()) == 1);
ocFile.setE2eCounter(fileEntity.getE2eCounter());
+ ocFile.setInternalFolderSyncTimestamp(fileEntity.getInternalTwoWaySync());
String sharees = fileEntity.getSharees();
// Surprisingly JSON deserialization causes significant overhead.
@@ -2477,4 +2483,29 @@ public List getFilesWithSyncConflict(User user) {
return files;
}
+
+ public List getInternalTwoWaySyncFolders(User user) {
+ List fileEntities = fileDao.getInternalTwoWaySyncFolders(user.getAccountName());
+ List files = new ArrayList<>(fileEntities.size());
+
+ for (FileEntity fileEntity : fileEntities) {
+ files.add(createFileInstance(fileEntity));
+ }
+
+ return files;
+ }
+
+ public boolean isPartOfInternalTwoWaySync(OCFile file) {
+ if (file.isInternalFolderSync()) {
+ return true;
+ }
+
+ while (file != null && !OCFile.ROOT_PATH.equals(file.getDecryptedRemotePath())) {
+ if (file.isInternalFolderSync()) {
+ return true;
+ }
+ file = getFileById(file.getParentId());
+ }
+ return false;
+ }
}
diff --git a/app/src/main/java/com/owncloud/android/datamodel/OCFile.java b/app/src/main/java/com/owncloud/android/datamodel/OCFile.java
index 566b1c06d3c9..5577bc182a32 100644
--- a/app/src/main/java/com/owncloud/android/datamodel/OCFile.java
+++ b/app/src/main/java/com/owncloud/android/datamodel/OCFile.java
@@ -117,6 +117,8 @@ public class OCFile implements Parcelable, Comparable, ServerFileInterfa
@Nullable
private GeoLocation geolocation;
private List tags = new ArrayList<>();
+ private Long internalFolderSyncTimestamp = -1L;
+ private String internalFolderSyncResult = "";
/**
* URI to the local path of the file contents, if stored in the device; cached after first call to
@@ -1051,6 +1053,26 @@ public void setE2eCounter(@Nullable Long e2eCounter) {
this.e2eCounter = e2eCounter;
}
}
+
+ public boolean isInternalFolderSync() {
+ return internalFolderSyncTimestamp >= 0;
+ }
+
+ public Long getInternalFolderSyncTimestamp() {
+ return internalFolderSyncTimestamp;
+ }
+
+ public void setInternalFolderSyncTimestamp(Long internalFolderSyncTimestamp) {
+ this.internalFolderSyncTimestamp = internalFolderSyncTimestamp;
+ }
+
+ public String getInternalFolderSyncResult() {
+ return internalFolderSyncResult;
+ }
+
+ public void setInternalFolderSyncResult(String internalFolderSyncResult) {
+ this.internalFolderSyncResult = internalFolderSyncResult;
+ }
public boolean isAPKorAAB() {
if ("gplay".equals(BuildConfig.FLAVOR)) {
diff --git a/app/src/main/java/com/owncloud/android/db/ProviderMeta.java b/app/src/main/java/com/owncloud/android/db/ProviderMeta.java
index 579c81fe4e21..3ea8d4b7e196 100644
--- a/app/src/main/java/com/owncloud/android/db/ProviderMeta.java
+++ b/app/src/main/java/com/owncloud/android/db/ProviderMeta.java
@@ -25,7 +25,7 @@
*/
public class ProviderMeta {
public static final String DB_NAME = "filelist";
- public static final int DB_VERSION = 82;
+ public static final int DB_VERSION = 83;
private ProviderMeta() {
// No instance
@@ -120,6 +120,8 @@ static public class ProviderTableMeta implements BaseColumns {
public static final String FILE_LOCK_TOKEN = "lock_token";
public static final String FILE_TAGS = "tags";
public static final String FILE_E2E_COUNTER = "e2e_counter";
+ public static final String FILE_INTERNAL_TWO_WAY_SYNC_TIMESTAMP = "internal_two_way_sync_timestamp";
+ public static final String FILE_INTERNAL_TWO_WAY_SYNC_RESULT = "internal_two_way_sync_result";
public static final List FILE_ALL_COLUMNS = Collections.unmodifiableList(Arrays.asList(
_ID,
@@ -171,7 +173,9 @@ static public class ProviderTableMeta implements BaseColumns {
FILE_METADATA_LIVE_PHOTO,
FILE_E2E_COUNTER,
FILE_TAGS,
- FILE_METADATA_GPS));
+ FILE_METADATA_GPS,
+ FILE_INTERNAL_TWO_WAY_SYNC_TIMESTAMP,
+ FILE_INTERNAL_TWO_WAY_SYNC_RESULT));
public static final String FILE_DEFAULT_SORT_ORDER = FILE_NAME + " collate nocase asc";
// Columns of ocshares table
diff --git a/app/src/main/java/com/owncloud/android/operations/RefreshFolderOperation.java b/app/src/main/java/com/owncloud/android/operations/RefreshFolderOperation.java
index aa76b4935e8f..086006d40559 100644
--- a/app/src/main/java/com/owncloud/android/operations/RefreshFolderOperation.java
+++ b/app/src/main/java/com/owncloud/android/operations/RefreshFolderOperation.java
@@ -697,6 +697,7 @@ private void setLocalFileDataOnUpdatedFile(OCFile remoteFile, OCFile localFile,
if (localFile != null) {
updatedFile.setFileId(localFile.getFileId());
updatedFile.setLastSyncDateForData(localFile.getLastSyncDateForData());
+ updatedFile.setInternalFolderSyncTimestamp(localFile.getInternalFolderSyncTimestamp());
updatedFile.setModificationTimestampAtLastSyncForData(
localFile.getModificationTimestampAtLastSyncForData()
);
diff --git a/app/src/main/java/com/owncloud/android/operations/SynchronizeFileOperation.java b/app/src/main/java/com/owncloud/android/operations/SynchronizeFileOperation.java
index 58ca7cd8432e..6caf81f4714b 100644
--- a/app/src/main/java/com/owncloud/android/operations/SynchronizeFileOperation.java
+++ b/app/src/main/java/com/owncloud/android/operations/SynchronizeFileOperation.java
@@ -295,6 +295,8 @@ private void requestForUpload(OCFile file) {
}
private void requestForDownload(OCFile file) {
+ Log_OC.d("InternalTwoWaySyncWork", "download file: " + file.getFileName());
+
FileDownloadHelper.Companion.instance().downloadFile(
mUser,
file);
diff --git a/app/src/main/java/com/owncloud/android/operations/SynchronizeFolderOperation.java b/app/src/main/java/com/owncloud/android/operations/SynchronizeFolderOperation.java
index 861a29e75838..9f48c9ce92a7 100644
--- a/app/src/main/java/com/owncloud/android/operations/SynchronizeFolderOperation.java
+++ b/app/src/main/java/com/owncloud/android/operations/SynchronizeFolderOperation.java
@@ -55,9 +55,6 @@ public class SynchronizeFolderOperation extends SyncOperation {
private static final String TAG = SynchronizeFolderOperation.class.getSimpleName();
- /** Time stamp for the synchronization process in progress */
- private long mCurrentSyncTime;
-
/** Remote path of the folder to synchronize */
private String mRemotePath;
@@ -95,17 +92,14 @@ public class SynchronizeFolderOperation extends SyncOperation {
* @param context Application context.
* @param remotePath Path to synchronize.
* @param user Nextcloud account where the folder is located.
- * @param currentSyncTime Time stamp for the synchronization process in progress.
*/
public SynchronizeFolderOperation(Context context,
String remotePath,
User user,
- long currentSyncTime,
FileDataStorageManager storageManager) {
super(storageManager);
mRemotePath = remotePath;
- mCurrentSyncTime = currentSyncTime;
this.user = user;
mContext = context;
mRemoteFolderChanged = false;
@@ -365,7 +359,7 @@ private void synchronizeData(List