diff --git a/app/schemas/com.nextcloud.client.database.NextcloudDatabase/99.json b/app/schemas/com.nextcloud.client.database.NextcloudDatabase/99.json new file mode 100644 index 000000000000..7da654e6f2ee --- /dev/null +++ b/app/schemas/com.nextcloud.client.database.NextcloudDatabase/99.json @@ -0,0 +1,1308 @@ +{ + "formatVersion": 1, + "database": { + "version": 99, + "identityHash": "0f574aa10ac45b16ad92704fe0bffd86", + "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" + }, + { + "fieldPath": "cloudId", + "columnName": "cloud_id", + "affinity": "TEXT" + }, + { + "fieldPath": "key", + "columnName": "key", + "affinity": "TEXT" + }, + { + "fieldPath": "value", + "columnName": "value", + "affinity": "TEXT" + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "_id" + ] + } + }, + { + "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, `files_download_limit` INTEGER, `files_download_limit_default` INTEGER, `recommendation` INTEGER, `notes_folder_path` TEXT, `default_permissions` INTEGER, `user_status_supports_busy` INTEGER, `windows_compatible_filenames` INTEGER, `has_valid_subscription` INTEGER, `client_integration_json` TEXT)", + "fields": [ + { + "fieldPath": "id", + "columnName": "_id", + "affinity": "INTEGER" + }, + { + "fieldPath": "assistant", + "columnName": "assistant", + "affinity": "INTEGER" + }, + { + "fieldPath": "accountName", + "columnName": "account", + "affinity": "TEXT" + }, + { + "fieldPath": "versionMajor", + "columnName": "version_mayor", + "affinity": "INTEGER" + }, + { + "fieldPath": "versionMinor", + "columnName": "version_minor", + "affinity": "INTEGER" + }, + { + "fieldPath": "versionMicro", + "columnName": "version_micro", + "affinity": "INTEGER" + }, + { + "fieldPath": "versionString", + "columnName": "version_string", + "affinity": "TEXT" + }, + { + "fieldPath": "versionEditor", + "columnName": "version_edition", + "affinity": "TEXT" + }, + { + "fieldPath": "extendedSupport", + "columnName": "extended_support", + "affinity": "INTEGER" + }, + { + "fieldPath": "corePollinterval", + "columnName": "core_pollinterval", + "affinity": "INTEGER" + }, + { + "fieldPath": "sharingApiEnabled", + "columnName": "sharing_api_enabled", + "affinity": "INTEGER" + }, + { + "fieldPath": "sharingPublicEnabled", + "columnName": "sharing_public_enabled", + "affinity": "INTEGER" + }, + { + "fieldPath": "sharingPublicPasswordEnforced", + "columnName": "sharing_public_password_enforced", + "affinity": "INTEGER" + }, + { + "fieldPath": "sharingPublicExpireDateEnabled", + "columnName": "sharing_public_expire_date_enabled", + "affinity": "INTEGER" + }, + { + "fieldPath": "sharingPublicExpireDateDays", + "columnName": "sharing_public_expire_date_days", + "affinity": "INTEGER" + }, + { + "fieldPath": "sharingPublicExpireDateEnforced", + "columnName": "sharing_public_expire_date_enforced", + "affinity": "INTEGER" + }, + { + "fieldPath": "sharingPublicSendMail", + "columnName": "sharing_public_send_mail", + "affinity": "INTEGER" + }, + { + "fieldPath": "sharingPublicUpload", + "columnName": "sharing_public_upload", + "affinity": "INTEGER" + }, + { + "fieldPath": "sharingUserSendMail", + "columnName": "sharing_user_send_mail", + "affinity": "INTEGER" + }, + { + "fieldPath": "sharingResharing", + "columnName": "sharing_resharing", + "affinity": "INTEGER" + }, + { + "fieldPath": "sharingFederationOutgoing", + "columnName": "sharing_federation_outgoing", + "affinity": "INTEGER" + }, + { + "fieldPath": "sharingFederationIncoming", + "columnName": "sharing_federation_incoming", + "affinity": "INTEGER" + }, + { + "fieldPath": "filesBigfilechunking", + "columnName": "files_bigfilechunking", + "affinity": "INTEGER" + }, + { + "fieldPath": "filesUndelete", + "columnName": "files_undelete", + "affinity": "INTEGER" + }, + { + "fieldPath": "filesVersioning", + "columnName": "files_versioning", + "affinity": "INTEGER" + }, + { + "fieldPath": "externalLinks", + "columnName": "external_links", + "affinity": "INTEGER" + }, + { + "fieldPath": "serverName", + "columnName": "server_name", + "affinity": "TEXT" + }, + { + "fieldPath": "serverColor", + "columnName": "server_color", + "affinity": "TEXT" + }, + { + "fieldPath": "serverTextColor", + "columnName": "server_text_color", + "affinity": "TEXT" + }, + { + "fieldPath": "serverElementColor", + "columnName": "server_element_color", + "affinity": "TEXT" + }, + { + "fieldPath": "serverSlogan", + "columnName": "server_slogan", + "affinity": "TEXT" + }, + { + "fieldPath": "serverLogo", + "columnName": "server_logo", + "affinity": "TEXT" + }, + { + "fieldPath": "serverBackgroundUrl", + "columnName": "background_url", + "affinity": "TEXT" + }, + { + "fieldPath": "endToEndEncryption", + "columnName": "end_to_end_encryption", + "affinity": "INTEGER" + }, + { + "fieldPath": "endToEndEncryptionKeysExist", + "columnName": "end_to_end_encryption_keys_exist", + "affinity": "INTEGER" + }, + { + "fieldPath": "endToEndEncryptionApiVersion", + "columnName": "end_to_end_encryption_api_version", + "affinity": "TEXT" + }, + { + "fieldPath": "activity", + "columnName": "activity", + "affinity": "INTEGER" + }, + { + "fieldPath": "serverBackgroundDefault", + "columnName": "background_default", + "affinity": "INTEGER" + }, + { + "fieldPath": "serverBackgroundPlain", + "columnName": "background_plain", + "affinity": "INTEGER" + }, + { + "fieldPath": "richdocument", + "columnName": "richdocument", + "affinity": "INTEGER" + }, + { + "fieldPath": "richdocumentMimetypeList", + "columnName": "richdocument_mimetype_list", + "affinity": "TEXT" + }, + { + "fieldPath": "richdocumentDirectEditing", + "columnName": "richdocument_direct_editing", + "affinity": "INTEGER" + }, + { + "fieldPath": "richdocumentTemplates", + "columnName": "richdocument_direct_templates", + "affinity": "INTEGER" + }, + { + "fieldPath": "richdocumentOptionalMimetypeList", + "columnName": "richdocument_optional_mimetype_list", + "affinity": "TEXT" + }, + { + "fieldPath": "sharingPublicAskForOptionalPassword", + "columnName": "sharing_public_ask_for_optional_password", + "affinity": "INTEGER" + }, + { + "fieldPath": "richdocumentProductName", + "columnName": "richdocument_product_name", + "affinity": "TEXT" + }, + { + "fieldPath": "directEditingEtag", + "columnName": "direct_editing_etag", + "affinity": "TEXT" + }, + { + "fieldPath": "userStatus", + "columnName": "user_status", + "affinity": "INTEGER" + }, + { + "fieldPath": "userStatusSupportsEmoji", + "columnName": "user_status_supports_emoji", + "affinity": "INTEGER" + }, + { + "fieldPath": "etag", + "columnName": "etag", + "affinity": "TEXT" + }, + { + "fieldPath": "filesLockingVersion", + "columnName": "files_locking_version", + "affinity": "TEXT" + }, + { + "fieldPath": "groupfolders", + "columnName": "groupfolders", + "affinity": "INTEGER" + }, + { + "fieldPath": "dropAccount", + "columnName": "drop_account", + "affinity": "INTEGER" + }, + { + "fieldPath": "securityGuard", + "columnName": "security_guard", + "affinity": "INTEGER" + }, + { + "fieldPath": "forbiddenFileNameCharacters", + "columnName": "forbidden_filename_characters", + "affinity": "INTEGER" + }, + { + "fieldPath": "forbiddenFileNames", + "columnName": "forbidden_filenames", + "affinity": "INTEGER" + }, + { + "fieldPath": "forbiddenFileNameExtensions", + "columnName": "forbidden_filename_extensions", + "affinity": "INTEGER" + }, + { + "fieldPath": "forbiddenFilenameBaseNames", + "columnName": "forbidden_filename_basenames", + "affinity": "INTEGER" + }, + { + "fieldPath": "filesDownloadLimit", + "columnName": "files_download_limit", + "affinity": "INTEGER" + }, + { + "fieldPath": "filesDownloadLimitDefault", + "columnName": "files_download_limit_default", + "affinity": "INTEGER" + }, + { + "fieldPath": "recommendation", + "columnName": "recommendation", + "affinity": "INTEGER" + }, + { + "fieldPath": "notesFolderPath", + "columnName": "notes_folder_path", + "affinity": "TEXT" + }, + { + "fieldPath": "defaultPermissions", + "columnName": "default_permissions", + "affinity": "INTEGER" + }, + { + "fieldPath": "userStatusSupportsBusy", + "columnName": "user_status_supports_busy", + "affinity": "INTEGER" + }, + { + "fieldPath": "isWCFEnabled", + "columnName": "windows_compatible_filenames", + "affinity": "INTEGER" + }, + { + "fieldPath": "hasValidSubscription", + "columnName": "has_valid_subscription", + "affinity": "INTEGER" + }, + { + "fieldPath": "clientIntegrationJson", + "columnName": "client_integration_json", + "affinity": "TEXT" + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "_id" + ] + } + }, + { + "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" + }, + { + "fieldPath": "iconUrl", + "columnName": "icon_url", + "affinity": "TEXT" + }, + { + "fieldPath": "language", + "columnName": "language", + "affinity": "TEXT" + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "INTEGER" + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT" + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT" + }, + { + "fieldPath": "redirect", + "columnName": "redirect", + "affinity": "INTEGER" + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "_id" + ] + } + }, + { + "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, `uploaded` INTEGER, `file_indicator` TEXT)", + "fields": [ + { + "fieldPath": "id", + "columnName": "_id", + "affinity": "INTEGER" + }, + { + "fieldPath": "name", + "columnName": "filename", + "affinity": "TEXT" + }, + { + "fieldPath": "encryptedName", + "columnName": "encrypted_filename", + "affinity": "TEXT" + }, + { + "fieldPath": "path", + "columnName": "path", + "affinity": "TEXT" + }, + { + "fieldPath": "pathDecrypted", + "columnName": "path_decrypted", + "affinity": "TEXT" + }, + { + "fieldPath": "parent", + "columnName": "parent", + "affinity": "INTEGER" + }, + { + "fieldPath": "creation", + "columnName": "created", + "affinity": "INTEGER" + }, + { + "fieldPath": "modified", + "columnName": "modified", + "affinity": "INTEGER" + }, + { + "fieldPath": "contentType", + "columnName": "content_type", + "affinity": "TEXT" + }, + { + "fieldPath": "contentLength", + "columnName": "content_length", + "affinity": "INTEGER" + }, + { + "fieldPath": "storagePath", + "columnName": "media_path", + "affinity": "TEXT" + }, + { + "fieldPath": "accountOwner", + "columnName": "file_owner", + "affinity": "TEXT" + }, + { + "fieldPath": "lastSyncDate", + "columnName": "last_sync_date", + "affinity": "INTEGER" + }, + { + "fieldPath": "lastSyncDateForData", + "columnName": "last_sync_date_for_data", + "affinity": "INTEGER" + }, + { + "fieldPath": "modifiedAtLastSyncForData", + "columnName": "modified_at_last_sync_for_data", + "affinity": "INTEGER" + }, + { + "fieldPath": "etag", + "columnName": "etag", + "affinity": "TEXT" + }, + { + "fieldPath": "etagOnServer", + "columnName": "etag_on_server", + "affinity": "TEXT" + }, + { + "fieldPath": "sharedViaLink", + "columnName": "share_by_link", + "affinity": "INTEGER" + }, + { + "fieldPath": "permissions", + "columnName": "permissions", + "affinity": "TEXT" + }, + { + "fieldPath": "remoteId", + "columnName": "remote_id", + "affinity": "TEXT" + }, + { + "fieldPath": "localId", + "columnName": "local_id", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "-1" + }, + { + "fieldPath": "updateThumbnail", + "columnName": "update_thumbnail", + "affinity": "INTEGER" + }, + { + "fieldPath": "isDownloading", + "columnName": "is_downloading", + "affinity": "INTEGER" + }, + { + "fieldPath": "favorite", + "columnName": "favorite", + "affinity": "INTEGER" + }, + { + "fieldPath": "hidden", + "columnName": "hidden", + "affinity": "INTEGER" + }, + { + "fieldPath": "isEncrypted", + "columnName": "is_encrypted", + "affinity": "INTEGER" + }, + { + "fieldPath": "etagInConflict", + "columnName": "etag_in_conflict", + "affinity": "TEXT" + }, + { + "fieldPath": "sharedWithSharee", + "columnName": "shared_via_users", + "affinity": "INTEGER" + }, + { + "fieldPath": "mountType", + "columnName": "mount_type", + "affinity": "INTEGER" + }, + { + "fieldPath": "hasPreview", + "columnName": "has_preview", + "affinity": "INTEGER" + }, + { + "fieldPath": "unreadCommentsCount", + "columnName": "unread_comments_count", + "affinity": "INTEGER" + }, + { + "fieldPath": "ownerId", + "columnName": "owner_id", + "affinity": "TEXT" + }, + { + "fieldPath": "ownerDisplayName", + "columnName": "owner_display_name", + "affinity": "TEXT" + }, + { + "fieldPath": "note", + "columnName": "note", + "affinity": "TEXT" + }, + { + "fieldPath": "sharees", + "columnName": "sharees", + "affinity": "TEXT" + }, + { + "fieldPath": "richWorkspace", + "columnName": "rich_workspace", + "affinity": "TEXT" + }, + { + "fieldPath": "metadataSize", + "columnName": "metadata_size", + "affinity": "TEXT" + }, + { + "fieldPath": "metadataLivePhoto", + "columnName": "metadata_live_photo", + "affinity": "TEXT" + }, + { + "fieldPath": "locked", + "columnName": "locked", + "affinity": "INTEGER" + }, + { + "fieldPath": "lockType", + "columnName": "lock_type", + "affinity": "INTEGER" + }, + { + "fieldPath": "lockOwner", + "columnName": "lock_owner", + "affinity": "TEXT" + }, + { + "fieldPath": "lockOwnerDisplayName", + "columnName": "lock_owner_display_name", + "affinity": "TEXT" + }, + { + "fieldPath": "lockOwnerEditor", + "columnName": "lock_owner_editor", + "affinity": "TEXT" + }, + { + "fieldPath": "lockTimestamp", + "columnName": "lock_timestamp", + "affinity": "INTEGER" + }, + { + "fieldPath": "lockTimeout", + "columnName": "lock_timeout", + "affinity": "INTEGER" + }, + { + "fieldPath": "lockToken", + "columnName": "lock_token", + "affinity": "TEXT" + }, + { + "fieldPath": "tags", + "columnName": "tags", + "affinity": "TEXT" + }, + { + "fieldPath": "metadataGPS", + "columnName": "metadata_gps", + "affinity": "TEXT" + }, + { + "fieldPath": "e2eCounter", + "columnName": "e2e_counter", + "affinity": "INTEGER" + }, + { + "fieldPath": "internalTwoWaySync", + "columnName": "internal_two_way_sync_timestamp", + "affinity": "INTEGER" + }, + { + "fieldPath": "internalTwoWaySyncResult", + "columnName": "internal_two_way_sync_result", + "affinity": "TEXT" + }, + { + "fieldPath": "uploaded", + "columnName": "uploaded", + "affinity": "INTEGER" + }, + { + "fieldPath": "fileIndicator", + "columnName": "file_indicator", + "affinity": "TEXT" + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "_id" + ] + } + }, + { + "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" + }, + { + "fieldPath": "localPath", + "columnName": "local_path", + "affinity": "TEXT" + }, + { + "fieldPath": "fileIsFolder", + "columnName": "is_folder", + "affinity": "INTEGER" + }, + { + "fieldPath": "fileFoundRecently", + "columnName": "found_at", + "affinity": "INTEGER" + }, + { + "fieldPath": "fileSentForUpload", + "columnName": "upload_triggered", + "affinity": "INTEGER" + }, + { + "fieldPath": "syncedFolderId", + "columnName": "syncedfolder_id", + "affinity": "TEXT" + }, + { + "fieldPath": "crc32", + "columnName": "crc32", + "affinity": "TEXT" + }, + { + "fieldPath": "fileModified", + "columnName": "modified_at", + "affinity": "INTEGER" + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "_id" + ] + } + }, + { + "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, `download_limit_limit` INTEGER, `download_limit_count` INTEGER, `attributes` TEXT)", + "fields": [ + { + "fieldPath": "id", + "columnName": "_id", + "affinity": "INTEGER" + }, + { + "fieldPath": "fileSource", + "columnName": "file_source", + "affinity": "INTEGER" + }, + { + "fieldPath": "itemSource", + "columnName": "item_source", + "affinity": "INTEGER" + }, + { + "fieldPath": "shareType", + "columnName": "share_type", + "affinity": "INTEGER" + }, + { + "fieldPath": "shareWith", + "columnName": "shate_with", + "affinity": "TEXT" + }, + { + "fieldPath": "path", + "columnName": "path", + "affinity": "TEXT" + }, + { + "fieldPath": "permissions", + "columnName": "permissions", + "affinity": "INTEGER" + }, + { + "fieldPath": "sharedDate", + "columnName": "shared_date", + "affinity": "INTEGER" + }, + { + "fieldPath": "expirationDate", + "columnName": "expiration_date", + "affinity": "INTEGER" + }, + { + "fieldPath": "token", + "columnName": "token", + "affinity": "TEXT" + }, + { + "fieldPath": "shareWithDisplayName", + "columnName": "shared_with_display_name", + "affinity": "TEXT" + }, + { + "fieldPath": "isDirectory", + "columnName": "is_directory", + "affinity": "INTEGER" + }, + { + "fieldPath": "userId", + "columnName": "user_id", + "affinity": "TEXT" + }, + { + "fieldPath": "idRemoteShared", + "columnName": "id_remote_shared", + "affinity": "INTEGER" + }, + { + "fieldPath": "accountOwner", + "columnName": "owner_share", + "affinity": "TEXT" + }, + { + "fieldPath": "isPasswordProtected", + "columnName": "is_password_protected", + "affinity": "INTEGER" + }, + { + "fieldPath": "note", + "columnName": "note", + "affinity": "TEXT" + }, + { + "fieldPath": "hideDownload", + "columnName": "hide_download", + "affinity": "INTEGER" + }, + { + "fieldPath": "shareLink", + "columnName": "share_link", + "affinity": "TEXT" + }, + { + "fieldPath": "shareLabel", + "columnName": "share_label", + "affinity": "TEXT" + }, + { + "fieldPath": "downloadLimitLimit", + "columnName": "download_limit_limit", + "affinity": "INTEGER" + }, + { + "fieldPath": "downloadLimitCount", + "columnName": "download_limit_count", + "affinity": "INTEGER" + }, + { + "fieldPath": "attributes", + "columnName": "attributes", + "affinity": "TEXT" + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "_id" + ] + } + }, + { + "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" + }, + { + "fieldPath": "localPath", + "columnName": "local_path", + "affinity": "TEXT" + }, + { + "fieldPath": "remotePath", + "columnName": "remote_path", + "affinity": "TEXT" + }, + { + "fieldPath": "wifiOnly", + "columnName": "wifi_only", + "affinity": "INTEGER" + }, + { + "fieldPath": "chargingOnly", + "columnName": "charging_only", + "affinity": "INTEGER" + }, + { + "fieldPath": "existing", + "columnName": "existing", + "affinity": "INTEGER" + }, + { + "fieldPath": "enabled", + "columnName": "enabled", + "affinity": "INTEGER" + }, + { + "fieldPath": "enabledTimestampMs", + "columnName": "enabled_timestamp_ms", + "affinity": "INTEGER" + }, + { + "fieldPath": "subfolderByDate", + "columnName": "subfolder_by_date", + "affinity": "INTEGER" + }, + { + "fieldPath": "account", + "columnName": "account", + "affinity": "TEXT" + }, + { + "fieldPath": "uploadAction", + "columnName": "upload_option", + "affinity": "INTEGER" + }, + { + "fieldPath": "nameCollisionPolicy", + "columnName": "name_collision_policy", + "affinity": "INTEGER" + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "INTEGER" + }, + { + "fieldPath": "hidden", + "columnName": "hidden", + "affinity": "INTEGER" + }, + { + "fieldPath": "subFolderRule", + "columnName": "sub_folder_rule", + "affinity": "INTEGER" + }, + { + "fieldPath": "excludeHidden", + "columnName": "exclude_hidden", + "affinity": "INTEGER" + }, + { + "fieldPath": "lastScanTimestampMs", + "columnName": "last_scan_timestamp_ms", + "affinity": "INTEGER" + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "_id" + ] + } + }, + { + "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, `upload_end_timestamp_long` 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" + }, + { + "fieldPath": "localPath", + "columnName": "local_path", + "affinity": "TEXT" + }, + { + "fieldPath": "remotePath", + "columnName": "remote_path", + "affinity": "TEXT" + }, + { + "fieldPath": "accountName", + "columnName": "account_name", + "affinity": "TEXT" + }, + { + "fieldPath": "fileSize", + "columnName": "file_size", + "affinity": "INTEGER" + }, + { + "fieldPath": "status", + "columnName": "status", + "affinity": "INTEGER" + }, + { + "fieldPath": "localBehaviour", + "columnName": "local_behaviour", + "affinity": "INTEGER" + }, + { + "fieldPath": "uploadTime", + "columnName": "upload_time", + "affinity": "INTEGER" + }, + { + "fieldPath": "nameCollisionPolicy", + "columnName": "name_collision_policy", + "affinity": "INTEGER" + }, + { + "fieldPath": "isCreateRemoteFolder", + "columnName": "is_create_remote_folder", + "affinity": "INTEGER" + }, + { + "fieldPath": "uploadEndTimestamp", + "columnName": "upload_end_timestamp", + "affinity": "INTEGER" + }, + { + "fieldPath": "uploadEndTimestampLong", + "columnName": "upload_end_timestamp_long", + "affinity": "INTEGER" + }, + { + "fieldPath": "lastResult", + "columnName": "last_result", + "affinity": "INTEGER" + }, + { + "fieldPath": "isWhileChargingOnly", + "columnName": "is_while_charging_only", + "affinity": "INTEGER" + }, + { + "fieldPath": "isWifiOnly", + "columnName": "is_wifi_only", + "affinity": "INTEGER" + }, + { + "fieldPath": "createdBy", + "columnName": "created_by", + "affinity": "INTEGER" + }, + { + "fieldPath": "folderUnlockToken", + "columnName": "folder_unlock_token", + "affinity": "TEXT" + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "_id" + ] + } + }, + { + "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" + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT" + }, + { + "fieldPath": "ocFileId", + "columnName": "ocfile_id", + "affinity": "INTEGER" + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "_id" + ] + } + }, + { + "tableName": "offline_operations", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT, `offline_operations_parent_oc_file_id` INTEGER, `offline_operations_path` TEXT, `offline_operations_type` TEXT, `offline_operations_file_name` TEXT, `offline_operations_created_at` INTEGER, `offline_operations_modified_at` INTEGER)", + "fields": [ + { + "fieldPath": "id", + "columnName": "_id", + "affinity": "INTEGER" + }, + { + "fieldPath": "parentOCFileId", + "columnName": "offline_operations_parent_oc_file_id", + "affinity": "INTEGER" + }, + { + "fieldPath": "path", + "columnName": "offline_operations_path", + "affinity": "TEXT" + }, + { + "fieldPath": "type", + "columnName": "offline_operations_type", + "affinity": "TEXT" + }, + { + "fieldPath": "filename", + "columnName": "offline_operations_file_name", + "affinity": "TEXT" + }, + { + "fieldPath": "createdAt", + "columnName": "offline_operations_created_at", + "affinity": "INTEGER" + }, + { + "fieldPath": "modifiedAt", + "columnName": "offline_operations_modified_at", + "affinity": "INTEGER" + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "_id" + ] + } + }, + { + "tableName": "recommended_files", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `directory` TEXT NOT NULL, `extension` TEXT NOT NULL, `mime_type` TEXT NOT NULL, `has_preview` INTEGER NOT NULL, `reason` TEXT NOT NULL, `timestamp` INTEGER NOT NULL, `account_name` TEXT)", + "fields": [ + { + "fieldPath": "id", + "columnName": "_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "directory", + "columnName": "directory", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "extension", + "columnName": "extension", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "mimeType", + "columnName": "mime_type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "hasPreview", + "columnName": "has_preview", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "reason", + "columnName": "reason", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "timestamp", + "columnName": "timestamp", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "accountName", + "columnName": "account_name", + "affinity": "TEXT" + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "_id" + ] + } + }, + { + "tableName": "assistant", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `accountName` TEXT, `type` TEXT, `status` TEXT, `userId` TEXT, `appId` TEXT, `input` TEXT, `output` TEXT, `completionExpectedAt` INTEGER, `progress` INTEGER, `lastUpdated` INTEGER, `scheduledAt` INTEGER, `endedAt` INTEGER)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "accountName", + "columnName": "accountName", + "affinity": "TEXT" + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT" + }, + { + "fieldPath": "status", + "columnName": "status", + "affinity": "TEXT" + }, + { + "fieldPath": "userId", + "columnName": "userId", + "affinity": "TEXT" + }, + { + "fieldPath": "appId", + "columnName": "appId", + "affinity": "TEXT" + }, + { + "fieldPath": "input", + "columnName": "input", + "affinity": "TEXT" + }, + { + "fieldPath": "output", + "columnName": "output", + "affinity": "TEXT" + }, + { + "fieldPath": "completionExpectedAt", + "columnName": "completionExpectedAt", + "affinity": "INTEGER" + }, + { + "fieldPath": "progress", + "columnName": "progress", + "affinity": "INTEGER" + }, + { + "fieldPath": "lastUpdated", + "columnName": "lastUpdated", + "affinity": "INTEGER" + }, + { + "fieldPath": "scheduledAt", + "columnName": "scheduledAt", + "affinity": "INTEGER" + }, + { + "fieldPath": "endedAt", + "columnName": "endedAt", + "affinity": "INTEGER" + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + } + } + ], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '0f574aa10ac45b16ad92704fe0bffd86')" + ] + } +} \ 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 c82192516324..66cfc1667f68 100644 --- a/app/src/main/java/com/nextcloud/client/database/NextcloudDatabase.kt +++ b/app/src/main/java/com/nextcloud/client/database/NextcloudDatabase.kt @@ -93,8 +93,9 @@ import com.owncloud.android.db.ProviderMeta AutoMigration(from = 93, to = 94, spec = DatabaseMigrationUtil.ResetCapabilitiesPostMigration::class), AutoMigration(from = 94, to = 95, spec = DatabaseMigrationUtil.ResetCapabilitiesPostMigration::class), AutoMigration(from = 95, to = 96), - AutoMigration(from = 96, to = 97, spec = DatabaseMigrationUtil.ResetCapabilitiesPostMigration::class) + AutoMigration(from = 96, to = 97, spec = DatabaseMigrationUtil.ResetCapabilitiesPostMigration::class), // manual migration used for 97 to 98 + AutoMigration(from = 98, to = 99) ], 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 86d02064c7a3..4675d8e1f019 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 @@ -9,6 +9,7 @@ package com.nextcloud.client.database.dao import androidx.room.Dao import androidx.room.Query +import androidx.room.Transaction import androidx.room.Update import com.nextcloud.client.database.entity.FileEntity import com.owncloud.android.db.ProviderMeta.ProviderTableMeta @@ -146,4 +147,20 @@ interface FileDao { @Query("SELECT remote_id FROM filelist WHERE file_owner = :accountName AND remote_id IS NOT NULL") fun getAllRemoteIds(accountName: String): List + + @Transaction + fun updateFileIndicatorsBatch(updates: List>) { + updates.forEach { (fileId, indicator) -> + updateFileIndicator(fileId, indicator) + } + } + + @Query( + """ + UPDATE ${ProviderTableMeta.FILE_TABLE_NAME} + SET ${ProviderTableMeta.FILE_INDICATOR} = :indicator + WHERE ${ProviderTableMeta._ID} = :fileId + """ + ) + fun updateFileIndicator(fileId: Long, indicator: String?) } 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 175287ba738c..2c1a7cdbec06 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 @@ -121,5 +121,7 @@ data class FileEntity( @ColumnInfo(name = ProviderTableMeta.FILE_INTERNAL_TWO_WAY_SYNC_RESULT) val internalTwoWaySyncResult: String?, @ColumnInfo(name = ProviderTableMeta.FILE_UPLOADED) - val uploaded: Long? + val uploaded: Long?, + @ColumnInfo(name = ProviderTableMeta.FILE_INDICATOR) + val fileIndicator: String? ) diff --git a/app/src/main/java/com/nextcloud/client/files/FileIndicatorManager.kt b/app/src/main/java/com/nextcloud/client/files/FileIndicatorManager.kt new file mode 100644 index 000000000000..6496dbe9a20e --- /dev/null +++ b/app/src/main/java/com/nextcloud/client/files/FileIndicatorManager.kt @@ -0,0 +1,38 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2026 Alper Ozturk + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +package com.nextcloud.client.files + +import com.owncloud.android.R +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.update + +enum class FileIndicator { + Idle, + Syncing, + Error, + Synced; + + fun getIconId(): Int? = when (this) { + Idle -> null + Syncing -> R.drawable.ic_synchronizing + Error -> R.drawable.ic_synchronizing_error + Synced -> R.drawable.ic_synced + } +} + +object FileIndicatorManager { + private val _activeTransfers = MutableStateFlow>(emptyMap()) + val activeTransfers: StateFlow> = _activeTransfers + + fun update(fileId: Long, status: FileIndicator) { + _activeTransfers.update { current -> + current + (fileId to status) + } + } +} diff --git a/app/src/main/java/com/nextcloud/client/jobs/InternalTwoWaySyncWork.kt b/app/src/main/java/com/nextcloud/client/jobs/InternalTwoWaySyncWork.kt index 1d1c89fd9d3f..e60bfe0f7076 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/InternalTwoWaySyncWork.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/InternalTwoWaySyncWork.kt @@ -12,6 +12,8 @@ import androidx.work.Worker import androidx.work.WorkerParameters import com.nextcloud.client.account.UserAccountManager import com.nextcloud.client.device.PowerManagementService +import com.nextcloud.client.files.FileIndicator +import com.nextcloud.client.files.FileIndicatorManager import com.nextcloud.client.network.ConnectivityService import com.nextcloud.client.preferences.AppPreferences import com.owncloud.android.MainApp @@ -67,6 +69,7 @@ class InternalTwoWaySyncWork( } Log_OC.d(TAG, "Folder ${folder.remotePath}: started!") + FileIndicatorManager.update(folder.fileId, FileIndicator.Syncing) operation = SynchronizeFolderOperation(context, folder.remotePath, user, fileDataStorageManager, true) val operationResult = operation?.execute(context) @@ -86,6 +89,7 @@ class InternalTwoWaySyncWork( } fileDataStorageManager.saveFile(folder) + FileIndicatorManager.update(folder.fileId, FileIndicator.Idle) } } diff --git a/app/src/main/java/com/nextcloud/client/jobs/autoUpload/AutoUploadWorker.kt b/app/src/main/java/com/nextcloud/client/jobs/autoUpload/AutoUploadWorker.kt index 639a66ed8c75..73737377adb3 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/autoUpload/AutoUploadWorker.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/autoUpload/AutoUploadWorker.kt @@ -18,6 +18,8 @@ import com.nextcloud.client.account.UserAccountManager import com.nextcloud.client.database.entity.toOCUpload import com.nextcloud.client.database.entity.toUploadEntity import com.nextcloud.client.device.PowerManagementService +import com.nextcloud.client.files.FileIndicator +import com.nextcloud.client.files.FileIndicatorManager import com.nextcloud.client.jobs.BackgroundJobManager import com.nextcloud.client.jobs.upload.FileUploadBroadcastManager import com.nextcloud.client.jobs.upload.FileUploadWorker @@ -93,10 +95,18 @@ class AutoUploadWorker( return Result.retry() } + val storageManager = FileDataStorageManager(userAccountManager.user, context.contentResolver) + val parentDir = + storageManager.getFileByDecryptedRemotePath(syncedFolder.remotePath) + if (powerManagementService.isPowerSavingEnabled) { Log_OC.w(TAG, "power saving mode enabled") } + parentDir?.let { + FileIndicatorManager.update(it.fileId, FileIndicator.Syncing) + } + collectFileChangesFromContentObserverWork(contentUris) uploadFiles(syncedFolder) @@ -104,6 +114,10 @@ class AutoUploadWorker( syncedFolder.lastScanTimestampMs = System.currentTimeMillis() syncedFolderProvider.updateSyncFolder(syncedFolder) + parentDir?.let { + FileIndicatorManager.update(it.fileId, FileIndicator.Idle) + } + Log_OC.d(TAG, "✅ ${syncedFolder.remotePath} completed") Result.success() } catch (e: Exception) { @@ -273,6 +287,7 @@ class AutoUploadWorker( val ocAccount = OwnCloudAccount(user.toPlatformAccount(), context) val client = OwnCloudClientManagerFactory.getDefaultSingleton() .getClientFor(ocAccount, context) + val storageManager = FileDataStorageManager(user, context.contentResolver) trySetForeground() updateNotification() @@ -322,6 +337,11 @@ class AutoUploadWorker( val result = operation.execute(client) fileUploadBroadcastManager.sendStarted(operation, context) + val parentFile = + storageManager.getFileByDecryptedRemotePath(operation.file?.parentRemotePath) + parentFile?.let { + FileIndicatorManager.update(it.fileId, FileIndicator.Syncing) + } UploadErrorNotificationManager.handleResult( context, @@ -346,6 +366,10 @@ class AutoUploadWorker( } } + parentFile?.let { + FileIndicatorManager.update(it.fileId, FileIndicator.Idle) + } + val isLastInBatch = (batchIndex == filePathsWithIds.size - 1) if (isLastInBatch) { sendUploadFinishEvent(operation, result) diff --git a/app/src/main/java/com/nextcloud/client/jobs/download/FileDownloadWorker.kt b/app/src/main/java/com/nextcloud/client/jobs/download/FileDownloadWorker.kt index dfa4c12524ae..fb962312afed 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/download/FileDownloadWorker.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/download/FileDownloadWorker.kt @@ -21,6 +21,8 @@ import androidx.work.ForegroundInfo import androidx.work.WorkerParameters import com.nextcloud.client.account.User import com.nextcloud.client.account.UserAccountManager +import com.nextcloud.client.files.FileIndicator +import com.nextcloud.client.files.FileIndicatorManager import com.nextcloud.model.WorkerState import com.nextcloud.model.WorkerStateObserver import com.nextcloud.utils.ForegroundServiceHelper @@ -211,6 +213,7 @@ class FileDownloadWorker( file.remotePath, operation ) ?: Pair(null, null) + FileIndicatorManager.update(file.fileId, FileIndicator.Syncing) downloadKey?.let { requestedDownloads.add(downloadKey) @@ -354,6 +357,7 @@ class FileDownloadWorker( private fun checkDownloadError(result: RemoteOperationResult<*>) { if (result.isSuccess || downloadError != null) { + currentDownload?.file?.fileId?.let { FileIndicatorManager.update(it, FileIndicator.Synced) } notificationManager.dismissNotification() return } @@ -363,6 +367,8 @@ class FileDownloadWorker( } else { FileDownloadError.Failed } + + currentDownload?.file?.fileId?.let { FileIndicatorManager.update(it, FileIndicator.Error) } } private fun showDownloadErrorNotification(downloadError: FileDownloadError) { diff --git a/app/src/main/java/com/nextcloud/client/jobs/folderDownload/FolderDownloadWorker.kt b/app/src/main/java/com/nextcloud/client/jobs/folderDownload/FolderDownloadWorker.kt index 337ff1bf6ee7..07aa5397f13e 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/folderDownload/FolderDownloadWorker.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/folderDownload/FolderDownloadWorker.kt @@ -12,6 +12,8 @@ import androidx.work.CoroutineWorker import androidx.work.ForegroundInfo import androidx.work.WorkerParameters import com.nextcloud.client.account.UserAccountManager +import com.nextcloud.client.files.FileIndicator +import com.nextcloud.client.files.FileIndicatorManager import com.nextcloud.client.jobs.download.FileDownloadHelper import com.nextcloud.model.WorkerState import com.nextcloud.model.WorkerStateObserver @@ -75,6 +77,7 @@ class FolderDownloadWorker( return Result.failure() } + FileIndicatorManager.update(folder.fileId, FileIndicator.Syncing) Log_OC.d(TAG, "🕒 started for ${user.accountName} downloading ${folder.fileName}") trySetForeground(folder) @@ -108,9 +111,11 @@ class FolderDownloadWorker( setForeground(foregroundInfo) } + FileIndicatorManager.update(file.fileId, FileIndicator.Syncing) val operation = DownloadFileOperation(user, file, context) val operationResult = operation.execute(client) if (operationResult?.isSuccess == true && operation.downloadType === DownloadType.DOWNLOAD) { + FileIndicatorManager.update(file.fileId, FileIndicator.Synced) getOCFile(operation)?.let { ocFile -> downloadHelper.saveFile(ocFile, operation, storageManager) } @@ -127,13 +132,16 @@ class FolderDownloadWorker( if (result) { Log_OC.d(TAG, "✅ completed") + FileIndicatorManager.update(folderID, FileIndicator.Synced) Result.success() } else { Log_OC.d(TAG, "❌ failed") + FileIndicatorManager.update(folderID, FileIndicator.Error) Result.failure() } } catch (e: Exception) { Log_OC.d(TAG, "❌ failed reason: $e") + FileIndicatorManager.update(folderID, FileIndicator.Error) Result.failure() } finally { WorkerStateObserver.send(WorkerState.FolderDownloadCompleted(folder)) diff --git a/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadBroadcastManager.kt b/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadBroadcastManager.kt index 148e869ee8fc..96b8f998f712 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadBroadcastManager.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadBroadcastManager.kt @@ -102,6 +102,7 @@ class FileUploadBroadcastManager(private val broadcastManager: LocalBroadcastMan putExtra(FileUploadWorker.EXTRA_OLD_FILE_PATH, upload.originalStoragePath) putExtra(FileUploadWorker.ACCOUNT_NAME, upload.user.accountName) putExtra(FileUploadWorker.EXTRA_UPLOAD_RESULT, uploadResult.isSuccess) + putExtra(FileUploadWorker.EXTRA_BEHAVIOUR, upload.localBehaviour) if (unlinkedFromRemotePath != null) { putExtra(FileUploadWorker.EXTRA_LINKED_TO_PATH, unlinkedFromRemotePath) } diff --git a/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadWorker.kt b/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadWorker.kt index e64ca25ca296..2a423b13384f 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadWorker.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadWorker.kt @@ -17,6 +17,8 @@ import androidx.work.WorkerParameters import com.nextcloud.client.account.User import com.nextcloud.client.account.UserAccountManager import com.nextcloud.client.device.PowerManagementService +import com.nextcloud.client.files.FileIndicator +import com.nextcloud.client.files.FileIndicatorManager import com.nextcloud.client.jobs.BackgroundJobManager import com.nextcloud.client.jobs.BackgroundJobManagerImpl import com.nextcloud.client.jobs.utils.UploadErrorNotificationManager @@ -82,8 +84,10 @@ class FileUploadWorker( const val EXTRA_OLD_REMOTE_PATH = "OLD_REMOTE_PATH" const val EXTRA_OLD_FILE_PATH = "OLD_FILE_PATH" const val EXTRA_LINKED_TO_PATH = "LINKED_TO" - const val ACCOUNT_NAME = "ACCOUNT_NAME" + const val EXTRA_BEHAVIOUR = "BEHAVIOUR" const val EXTRA_ACCOUNT_NAME = "ACCOUNT_NAME" + + const val ACCOUNT_NAME = "ACCOUNT_NAME" const val ACTION_CANCEL_BROADCAST = "CANCEL" const val LOCAL_BEHAVIOUR_COPY = 0 const val LOCAL_BEHAVIOUR_MOVE = 1 @@ -225,6 +229,7 @@ class FileUploadWorker( val uploads = uploadsStorageManager.getUploadsByIds(uploadIds, accountName) val ocAccount = OwnCloudAccount(user.toPlatformAccount(), context) val client = OwnCloudClientManagerFactory.getDefaultSingleton().getClientFor(ocAccount, context) + val storageManager = FileDataStorageManager(user, context.contentResolver) for ((index, upload) in uploads.withIndex()) { ensureActive() @@ -243,8 +248,14 @@ class FileUploadWorker( } fileUploadBroadcastManager.sendAdded(context) - val operation = createUploadFileOperation(upload, user) + val operation = createUploadFileOperation(upload, user, storageManager) currentUploadFileOperation = operation + val parentFile = + storageManager.getFileByDecryptedRemotePath(operation.file?.parentRemotePath) + + parentFile?.let { + FileIndicatorManager.update(it.fileId, FileIndicator.Syncing) + } val currentIndex = (index + 1) val currentUploadIndex = (currentIndex + previouslyUploadedFileSize) @@ -259,6 +270,9 @@ class FileUploadWorker( upload(upload, operation, user, client) } currentUploadFileOperation = null + parentFile?.let { + FileIndicatorManager.update(it.fileId, FileIndicator.Idle) + } if (result.code == ResultCode.QUOTA_EXCEEDED) { Log_OC.w(TAG, "Quota exceeded, stopping uploads") @@ -308,7 +322,11 @@ class FileUploadWorker( return result } - private fun createUploadFileOperation(upload: OCUpload, user: User): UploadFileOperation = UploadFileOperation( + private fun createUploadFileOperation( + upload: OCUpload, + user: User, + storageManager: FileDataStorageManager + ): UploadFileOperation = UploadFileOperation( uploadsStorageManager, connectivityService, powerManagementService, @@ -321,7 +339,7 @@ class FileUploadWorker( upload.isUseWifiOnly, upload.isWhileChargingOnly, true, - FileDataStorageManager(user, context.contentResolver) + storageManager ).apply { addDataTransferProgressListener(this@FileUploadWorker) } 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 497776f6babf..849c9212c151 100644 --- a/app/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java +++ b/app/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java @@ -41,6 +41,7 @@ import com.nextcloud.client.database.dao.ShareDao; import com.nextcloud.client.database.entity.FileEntity; import com.nextcloud.client.database.entity.OfflineOperationEntity; +import com.nextcloud.client.files.FileIndicator; import com.nextcloud.client.jobs.offlineOperations.repository.OfflineOperationsRepository; import com.nextcloud.client.jobs.offlineOperations.repository.OfflineOperationsRepositoryType; import com.nextcloud.model.OfflineOperationRawType; @@ -785,6 +786,10 @@ public void saveFolder(OCFile folder, List updatedFiles, Collection, ServerFileInterfa private String reason = ""; // endregion + private String fileIndicator = FileIndicator.Idle.name(); + /** * URI to the local path of the file contents, if stored in the device; cached after first call to * {@link #getStorageUri()} @@ -212,6 +215,7 @@ private OCFile(Parcel source) { lockTimeout = source.readLong(); lockToken = source.readString(); livePhoto = source.readString(); + fileIndicator = source.readString(); } @Override @@ -258,6 +262,7 @@ public void writeToParcel(Parcel dest, int flags) { dest.writeLong(lockTimeout); dest.writeString(lockToken); dest.writeString(livePhoto); + dest.writeString(fileIndicator); } public String getLinkedFileIdForLivePhoto() { @@ -530,6 +535,7 @@ private void resetData() { lockToken = null; livePhoto = null; imageDimension = null; + fileIndicator = FileIndicator.Idle.name(); } /** @@ -1175,4 +1181,12 @@ public boolean hasValidParentId() { return getParentId() != 0; } } + + public void setFileIndicator(FileIndicator indicator) { + fileIndicator = indicator.name(); + } + + public FileIndicator getFileIndicator() { + return FileIndicator.valueOf(fileIndicator); + } } 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 280bd47673b3..6cd0bcd93f89 100644 --- a/app/src/main/java/com/owncloud/android/db/ProviderMeta.java +++ b/app/src/main/java/com/owncloud/android/db/ProviderMeta.java @@ -23,7 +23,7 @@ */ public class ProviderMeta { public static final String DB_NAME = "filelist"; - public static final int DB_VERSION = 98; + public static final int DB_VERSION = 99; private ProviderMeta() { // No instance @@ -91,6 +91,7 @@ static public class ProviderTableMeta implements BaseColumns { public static final String FILE_CREATION = "created"; public static final String FILE_MODIFIED = "modified"; public static final String FILE_UPLOADED = "uploaded"; + public static final String FILE_INDICATOR = "file_indicator"; public static final String FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA = "modified_at_last_sync_for_data"; public static final String FILE_CONTENT_LENGTH = "content_length"; public static final String FILE_CONTENT_TYPE = "content_type"; @@ -190,7 +191,8 @@ static public class ProviderTableMeta implements BaseColumns { FILE_TAGS, FILE_METADATA_GPS, FILE_INTERNAL_TWO_WAY_SYNC_TIMESTAMP, - FILE_INTERNAL_TWO_WAY_SYNC_RESULT); + FILE_INTERNAL_TWO_WAY_SYNC_RESULT, + FILE_INDICATOR); 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/SynchronizeFileOperation.java b/app/src/main/java/com/owncloud/android/operations/SynchronizeFileOperation.java index 4aba8f2d5f63..e3e1411e0c79 100644 --- a/app/src/main/java/com/owncloud/android/operations/SynchronizeFileOperation.java +++ b/app/src/main/java/com/owncloud/android/operations/SynchronizeFileOperation.java @@ -17,6 +17,8 @@ import android.text.TextUtils; import com.nextcloud.client.account.User; +import com.nextcloud.client.files.FileIndicator; +import com.nextcloud.client.files.FileIndicatorManager; import com.nextcloud.client.jobs.download.FileDownloadHelper; import com.nextcloud.client.jobs.upload.FileUploadHelper; import com.nextcloud.client.jobs.upload.FileUploadWorker; @@ -190,6 +192,8 @@ protected RemoteOperationResult run(OwnCloudClient client) { mLocalFile = getStorageManager().getFileByPath(mRemotePath); } + FileIndicatorManager.INSTANCE.update(mLocalFile.getFileId(), FileIndicator.Syncing); + if (!mLocalFile.isDown()) { /// easy decision requestForDownload(mLocalFile); @@ -291,6 +295,9 @@ protected RemoteOperationResult run(OwnCloudClient client) { if (postDialogEvent) { EventBus.getDefault().post(new DialogEvent(DialogEventType.SYNC)); } + + FileIndicatorManager.INSTANCE.update(mLocalFile.getFileId(), FileIndicator.Synced); + return result; } 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 ee77d865ea70..1f5456c6a679 100644 --- a/app/src/main/java/com/owncloud/android/operations/SynchronizeFolderOperation.java +++ b/app/src/main/java/com/owncloud/android/operations/SynchronizeFolderOperation.java @@ -15,6 +15,8 @@ import android.text.TextUtils; import com.nextcloud.client.account.User; +import com.nextcloud.client.files.FileIndicator; +import com.nextcloud.client.files.FileIndicatorManager; import com.nextcloud.client.jobs.download.FileDownloadHelper; import com.owncloud.android.datamodel.FileDataStorageManager; import com.owncloud.android.datamodel.OCFile; @@ -132,6 +134,8 @@ protected RemoteOperationResult run(OwnCloudClient client) { return new RemoteOperationResult<>(ResultCode.FILE_NOT_FOUND); } + FileIndicatorManager.INSTANCE.update(mLocalFolder.getFileId(), FileIndicator.Syncing); + result = checkForChanges(client); if (result.isSuccess()) { @@ -143,15 +147,18 @@ protected RemoteOperationResult run(OwnCloudClient client) { if (result.isSuccess()) { syncContents(client); + FileIndicatorManager.INSTANCE.update(mLocalFolder.getFileId(), FileIndicator.Synced); } } if (mCancellationRequested.get()) { + FileIndicatorManager.INSTANCE.update(mLocalFolder.getFileId(), FileIndicator.Error); throw new OperationCancelledException(); } } catch (OperationCancelledException e) { - result = new RemoteOperationResult(e); + FileIndicatorManager.INSTANCE.update(mLocalFolder.getFileId(), FileIndicator.Error); + result = new RemoteOperationResult<>(e); } return result; diff --git a/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt b/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt index 2b22bdb8f58f..6a5929d78043 100644 --- a/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt +++ b/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt @@ -47,7 +47,9 @@ import androidx.appcompat.widget.SearchView import androidx.core.view.MenuItemCompat import androidx.core.view.isVisible import androidx.fragment.app.Fragment +import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle import androidx.localbroadcastmanager.content.LocalBroadcastManager import com.google.android.material.appbar.AppBarLayout import com.google.android.material.dialog.MaterialAlertDialogBuilder @@ -61,6 +63,8 @@ import com.nextcloud.client.core.Clock import com.nextcloud.client.di.Injectable import com.nextcloud.client.editimage.EditImageActivity import com.nextcloud.client.files.DeepLinkHandler +import com.nextcloud.client.files.FileIndicator +import com.nextcloud.client.files.FileIndicatorManager import com.nextcloud.client.jobs.download.FileDownloadHelper import com.nextcloud.client.jobs.download.FileDownloadWorker import com.nextcloud.client.jobs.download.FileDownloadWorker.Companion.getDownloadAddedMessage @@ -157,6 +161,8 @@ import com.owncloud.android.utils.PushUtils import com.owncloud.android.utils.StringUtils import com.owncloud.android.utils.theme.CapabilityUtils import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.FlowPreview +import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.greenrobot.eventbus.EventBus @@ -279,6 +285,7 @@ class FileDisplayActivity : startMetadataSyncForRoot() handleBackPress() setupDrawer(menuItemId) + observeFileIndicatorState() } /** @@ -1079,7 +1086,7 @@ class FileDisplayActivity : return@isNetworkAndServerAvailable } - FileUploadHelper.Companion.instance().uploadNewFiles( + FileUploadHelper.instance().uploadNewFiles( user.orElseThrow( Supplier { RuntimeException() } ), @@ -1662,10 +1669,12 @@ class FileDisplayActivity : private inner class UploadFinishReceiver : BroadcastReceiver() { private val tag = "UploadFinishReceiver" + @Suppress("LongMethod") override fun onReceive(context: Context?, intent: Intent) { Log_OC.d(tag, "upload finish received broadcast") val uploadedRemotePath = intent.getStringExtra(FileUploadWorker.EXTRA_REMOTE_PATH) + val behaviour = intent.getIntExtra(FileUploadWorker.EXTRA_BEHAVIOUR, -1) val accountName = intent.getStringExtra(FileUploadWorker.ACCOUNT_NAME) val account = getAccount() val sameAccount = accountName != null && account != null && accountName == account.name @@ -1690,6 +1699,13 @@ class FileDisplayActivity : sameFile = file?.remotePath == uploadedRemotePath || renamedInUpload } + // only synced icon can be remove if local behaviour is not move + if (uploadWasFine && behaviour != FileUploadWorker.LOCAL_BEHAVIOUR_MOVE) { + file?.let { + setIdleFileIndicator(it, includeSubFiles = false) + } + } + if (sameAccount && sameFile && this@FileDisplayActivity.leftFragment is FileDetailFragment) { val fileDetailFragment = leftFragment as FileDetailFragment if (uploadWasFine) { @@ -2129,6 +2145,7 @@ class FileDisplayActivity : } supportInvalidateOptionsMenu() fetchRecommendedFilesIfNeeded(ignoreETag = true, currentDir) + setIdleFileIndicator(removedFile) } else { if (result.isSslRecoverableException) { mLastSslUntrustedServerResult = result @@ -2137,6 +2154,36 @@ class FileDisplayActivity : } } + private fun setIdleFileIndicator(file: OCFile, includeSubFiles: Boolean = true) { + FileIndicatorManager.update(file.fileId, FileIndicator.Idle) + + // while uploading files don't include so that downloaded icon can be removed for directory + if (!includeSubFiles) { + return + } + + // while removing files include sub files since it's needed + lifecycleScope.launch(Dispatchers.IO) { + if (user.isEmpty) { + return@launch + } + + if (file.isFolder) { + // clearing first depth child files + storageManager.fileDao.getSubfiles(file.fileId, user.get().accountName).forEach { + it.id?.let { id -> + FileIndicatorManager.update(id, FileIndicator.Idle) + } + } + } else { + val parent = storageManager.getFileById(file.parentId) + parent?.fileId?.let { parentId -> + FileIndicatorManager.update(parentId, FileIndicator.Idle) + } + } + } + } + private fun onRestoreFileVersionOperationFinish(result: RemoteOperationResult<*>) { if (result.isSuccess) { val file = getFile() @@ -3058,6 +3105,34 @@ class FileDisplayActivity : } // endregion + @Suppress("MagicNumber") + @OptIn(FlowPreview::class) + private fun observeFileIndicatorState() { + lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.STARTED) { + FileIndicatorManager.activeTransfers + .debounce(100) + .collect { indicators -> + Log_OC.d(TAG, "observing file indicators") + + withContext(Dispatchers.Main) { + // update UI with hot data + listOfFilesFragment?.adapter?.updateFileIndicators(indicators) + + // update cold data in background + launch(Dispatchers.IO) { + storageManager.fileDao.updateFileIndicatorsBatch( + indicators.map { (fileId, indicator) -> + fileId to indicator.name + } + ) + } + } + } + } + } + } + companion object { const val RESTART: String = "RESTART" const val ALL_FILES: String = "ALL_FILES" diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.java b/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.java index c93624ba352c..0c138f4a8d45 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.java +++ b/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.java @@ -30,6 +30,7 @@ import com.nextcloud.android.common.ui.theme.utils.ColorRole; import com.nextcloud.client.account.User; import com.nextcloud.client.database.entity.OfflineOperationEntity; +import com.nextcloud.client.files.FileIndicator; import com.nextcloud.client.jobs.upload.FileUploadHelper; import com.nextcloud.client.preferences.AppPreferences; import com.nextcloud.model.OfflineOperationType; @@ -77,6 +78,8 @@ import java.util.Date; import java.util.List; import java.util.Locale; +import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.UUID; @@ -1061,4 +1064,24 @@ public void removeAllFiles() { mFilesAll.clear(); notifyDataSetChanged(); } + + public void updateFileIndicators(Map indicators) { + if (indicators == null || indicators.isEmpty()) { + return; + } + + for (OCFile file: mFiles) { + final var fileIndicator = indicators.get(file.getFileId()); + if (fileIndicator == null) { + continue; + } + + if (Objects.equals(file.getFileIndicator(), fileIndicator)) { + continue; + } + + file.setFileIndicator(fileIndicator); + notifyItemChanged(file); + } + } } diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListDelegate.kt b/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListDelegate.kt index 343659e8938a..b0d63dd1e92e 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListDelegate.kt +++ b/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListDelegate.kt @@ -14,13 +14,12 @@ import androidx.core.content.ContextCompat import com.elyeproj.loaderviewlibrary.LoaderImageView import com.nextcloud.android.common.ui.theme.utils.ColorRole import com.nextcloud.client.account.User -import com.nextcloud.client.jobs.download.FileDownloadHelper +import com.nextcloud.client.files.FileIndicator import com.nextcloud.client.jobs.gallery.GalleryImageGenerationJob import com.nextcloud.client.jobs.gallery.GalleryImageGenerationListener import com.nextcloud.client.jobs.upload.FileUploadHelper import com.nextcloud.client.preferences.AppPreferences import com.nextcloud.utils.OCFileUtils -import com.nextcloud.utils.extensions.getSubfiles import com.nextcloud.utils.extensions.makeRounded import com.nextcloud.utils.extensions.setVisibleIf import com.nextcloud.utils.mdm.MDMConfig @@ -43,7 +42,6 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.cancel import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext @Suppress("LongParameterList", "TooManyFunctions") class OCFileListDelegate( @@ -315,7 +313,7 @@ class OCFileListDelegate( private fun bindGridMetadataViews(file: OCFile, gridViewHolder: ListViewHolder) { if (showMetadata) { - showLocalFileIndicator(file, gridViewHolder) + showFileIndicator(file, gridViewHolder) gridViewHolder.favorite.visibility = if (file.isFavorite) View.VISIBLE else View.GONE } else { gridViewHolder.localFileIndicator.visibility = View.GONE @@ -323,45 +321,18 @@ class OCFileListDelegate( } } - private suspend fun isFolderFullyDownloaded(file: OCFile): Boolean = withContext(Dispatchers.IO) { - file.isFolder && - storageManager.getSubfiles(file.fileId, user.accountName) - .takeIf { it.isNotEmpty() } - ?.all { it.isDown } == true - } - - private fun isSynchronizing(file: OCFile): Boolean { - val operationsServiceBinder = transferServiceGetter.operationsServiceBinder - val fileDownloadHelper = FileDownloadHelper.instance() - - return operationsServiceBinder?.isSynchronizing(user, file) == true || - fileDownloadHelper.isDownloading(user, file) || - fileUploadHelper.isUploading(file.remotePath, user.accountName) - } - - private fun showLocalFileIndicator(file: OCFile, holder: ListViewHolder) { - ioScope.launch { - val isFullyDownloaded = isFolderFullyDownloaded(file) - val isSyncing = isSynchronizing(file) - val hasConflict = (file.etagInConflict != null) - val isDown = file.isDown - - val icon = when { - isSyncing -> R.drawable.ic_synchronizing - hasConflict -> R.drawable.ic_synchronizing_error - isDown || isFullyDownloaded -> R.drawable.ic_synced - else -> null + private fun showFileIndicator(file: OCFile, holder: ListViewHolder) { + holder.localFileIndicator.run { + var indicator = file.fileIndicator.getIconId() + if (file.etagInConflict != null) { + indicator = FileIndicator.Error.getIconId() } - withContext(Dispatchers.Main) { - holder.localFileIndicator.run { - if (icon != null && showMetadata) { - setImageResource(icon) - visibility = View.VISIBLE - } else { - visibility = View.GONE - } - } + if (indicator != null && indicator != 0) { + setImageResource(indicator) + visibility = View.VISIBLE + } else { + visibility = View.GONE } } } diff --git a/app/src/test/java/com/owncloud/android/ui/adapter/MockOCFileListAdapterDataProvider.kt b/app/src/test/java/com/owncloud/android/ui/adapter/MockOCFileListAdapterDataProvider.kt index c2b71334a569..6c781896c98b 100644 --- a/app/src/test/java/com/owncloud/android/ui/adapter/MockOCFileListAdapterDataProvider.kt +++ b/app/src/test/java/com/owncloud/android/ui/adapter/MockOCFileListAdapterDataProvider.kt @@ -71,7 +71,8 @@ class MockOCFileListAdapterDataProvider : OCFileListAdapterDataProvider { e2eCounter = 0L, internalTwoWaySync = 0L, internalTwoWaySyncResult = null, - uploaded = 0L + uploaded = 0L, + fileIndicator = null ) }