diff --git a/.github/workflows/analysis.yml b/.github/workflows/analysis.yml
index 6a3ce2ebc4..1f99e39fed 100644
--- a/.github/workflows/analysis.yml
+++ b/.github/workflows/analysis.yml
@@ -28,12 +28,12 @@ jobs:
echo "::set-output name=pr::${{ github.event.pull_request.number }}"
echo "::set-output name=repo::${{ github.event.pull_request.head.repo.full_name }}"
fi
- - uses: actions/checkout@v3
+ - uses: actions/checkout@755da8c3cf115ac066823e79a1e1788f8940201b # v3
with:
repository: ${{ steps.get-vars.outputs.repo }}
ref: ${{ steps.get-vars.outputs.branch }}
- name: Set up JDK 11
- uses: actions/setup-java@v3
+ uses: actions/setup-java@1df8dbefe2a8cbc99770194893dd902763bee34b # v3
with:
distribution: "temurin"
java-version: 11
diff --git a/.github/workflows/assembleFlavors.yml b/.github/workflows/assembleFlavors.yml
index 8979c1442f..6bec7e9545 100644
--- a/.github/workflows/assembleFlavors.yml
+++ b/.github/workflows/assembleFlavors.yml
@@ -15,9 +15,9 @@ jobs:
matrix:
flavor: [ Generic, Gplay, Huawei ]
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@755da8c3cf115ac066823e79a1e1788f8940201b # v3
- name: set up JDK 11
- uses: actions/setup-java@v3
+ uses: actions/setup-java@1df8dbefe2a8cbc99770194893dd902763bee34b # v3
with:
distribution: "temurin"
java-version: 11
diff --git a/.github/workflows/autoApproveDependabot.yml b/.github/workflows/autoApproveDependabot.yml
index 1946010fd3..8deb91bf16 100644
--- a/.github/workflows/autoApproveDependabot.yml
+++ b/.github/workflows/autoApproveDependabot.yml
@@ -10,7 +10,7 @@ jobs:
auto-approve:
runs-on: ubuntu-latest
steps:
- - uses: hmarr/auto-approve-action@v3.1.0
+ - uses: hmarr/auto-approve-action@de8ae18c173c131e182d4adf2c874d8d2308a85b # v3.1.0
if: github.actor == 'dependabot[bot]' || github.actor == 'dependabot-preview[bot]'
with:
github-token: "${{ secrets.GITHUB_TOKEN }}"
diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml
index 0c38dfa7c3..63f970d6ca 100644
--- a/.github/workflows/check.yml
+++ b/.github/workflows/check.yml
@@ -15,9 +15,9 @@ jobs:
matrix:
task: [ detekt, spotlessKotlinCheck ]
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@755da8c3cf115ac066823e79a1e1788f8940201b # v3
- name: Set up JDK 11
- uses: actions/setup-java@v3
+ uses: actions/setup-java@1df8dbefe2a8cbc99770194893dd902763bee34b # v3
with:
distribution: "temurin"
java-version: 11
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
index fb9b2f62a8..c68c81b46f 100644
--- a/.github/workflows/codeql.yml
+++ b/.github/workflows/codeql.yml
@@ -23,27 +23,27 @@ jobs:
strategy:
fail-fast: false
matrix:
- language: [ 'java' ]
+ language: [ 'java' ]
steps:
- - name: Checkout repository
- uses: actions/checkout@v3
- - name: Set Swap Space
- uses: pierotofy/set-swap-space@49819abfb41bd9b44fb781159c033dba90353a7c #v1.0
- with:
+ - name: Checkout repository
+ uses: actions/checkout@755da8c3cf115ac066823e79a1e1788f8940201b # v3
+ - name: Set Swap Space
+ uses: pierotofy/set-swap-space@49819abfb41bd9b44fb781159c033dba90353a7c # v1.0
+ with:
swap-size-gb: 10
- - name: Initialize CodeQL
- uses: github/codeql-action/init@v2
- with:
- languages: ${{ matrix.language }}
- - name: Set up JDK
- uses: actions/setup-java@v3
- with:
- distribution: "temurin"
- java-version: 11
- - name: Assemble
- run: |
- mkdir -p "$HOME/.gradle"
- echo "org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError" > "$HOME/.gradle/gradle.properties"
- ./gradlew assembleDebug
- - name: Perform CodeQL Analysis
- uses: github/codeql-action/analyze@v2
+ - name: Initialize CodeQL
+ uses: github/codeql-action/init@959cbb7472c4d4ad70cdfe6f4976053fe48ab394 # v2
+ with:
+ languages: ${{ matrix.language }}
+ - name: Set up JDK
+ uses: actions/setup-java@1df8dbefe2a8cbc99770194893dd902763bee34b # v3
+ with:
+ distribution: "temurin"
+ java-version: 11
+ - name: Assemble
+ run: |
+ mkdir -p "$HOME/.gradle"
+ echo "org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError" > "$HOME/.gradle/gradle.properties"
+ ./gradlew assembleDebug
+ - name: Perform CodeQL Analysis
+ uses: github/codeql-action/analyze@959cbb7472c4d4ad70cdfe6f4976053fe48ab394 # v2
diff --git a/.github/workflows/detectNewJavaFiles.yml b/.github/workflows/detectNewJavaFiles.yml
index 7107619e4e..b64964cef7 100644
--- a/.github/workflows/detectNewJavaFiles.yml
+++ b/.github/workflows/detectNewJavaFiles.yml
@@ -1,3 +1,4 @@
+# synced from @nextcloud/android-config
name: "Detect new java files"
on:
@@ -10,23 +11,22 @@ jobs:
detectNewJavaFiles:
runs-on: ubuntu-latest
steps:
- - id: file_changes
- uses: trilom/file-changes-action@v1.2.4
- with:
- output: ','
- - name: Detect new java files
- run: |
- if [ -z '${{ steps.file_changes.outputs.files_added }}' ]; then
- echo "No new files added"
- exit 0
- fi
- new_java=$(echo '${{ steps.file_changes.outputs.files_added }}' | tr ',' '\n' | grep '\.java$' | cat)
- if [ -n "$new_java" ]; then
- # shellcheck disable=SC2016
- printf 'New java files detected:\n```\n%s\n```\n' "$new_java" | tee "$GITHUB_STEP_SUMMARY"
- exit 1
- else
- echo "No new java files detected"
- exit 0
- fi
-
+ - id: file_changes
+ uses: trilom/file-changes-action@a6ca26c14274c33b15e6499323aac178af06ad4b # v1.2.4
+ with:
+ output: ','
+ - name: Detect new java files
+ run: |
+ if [ -z '${{ steps.file_changes.outputs.files_added }}' ]; then
+ echo "No new files added"
+ exit 0
+ fi
+ new_java=$(echo '${{ steps.file_changes.outputs.files_added }}' | tr ',' '\n' | grep '\.java$' | cat)
+ if [ -n "$new_java" ]; then
+ # shellcheck disable=SC2016
+ printf 'New java files detected:\n```\n%s\n```\n' "$new_java" | tee "$GITHUB_STEP_SUMMARY"
+ exit 1
+ else
+ echo "No new java files detected"
+ exit 0
+ fi
diff --git a/.github/workflows/detectSnapshot.yml b/.github/workflows/detectSnapshot.yml
index c529840486..d1c3c754ed 100644
--- a/.github/workflows/detectSnapshot.yml
+++ b/.github/workflows/detectSnapshot.yml
@@ -12,6 +12,6 @@ jobs:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@755da8c3cf115ac066823e79a1e1788f8940201b # v3
- name: Detect SNAPSHOT
run: scripts/analysis/detectSNAPSHOT.sh
diff --git a/.github/workflows/gradle-wrapper-validation.yml b/.github/workflows/gradle-wrapper-validation.yml
index d0d8f814e2..0e649b2e9e 100644
--- a/.github/workflows/gradle-wrapper-validation.yml
+++ b/.github/workflows/gradle-wrapper-validation.yml
@@ -12,5 +12,5 @@ jobs:
name: "Validation"
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v3
- - uses: gradle/wrapper-validation-action@v1
+ - uses: actions/checkout@755da8c3cf115ac066823e79a1e1788f8940201b # v3
+ - uses: gradle/wrapper-validation-action@55e685c48d84285a5b0418cd094606e199cca3b6 # v1
diff --git a/.github/workflows/qa.yml b/.github/workflows/qa.yml
index 59c3b803ab..7c228a5d0a 100644
--- a/.github/workflows/qa.yml
+++ b/.github/workflows/qa.yml
@@ -15,10 +15,10 @@ jobs:
- name: Check if secrets are available
run: echo "::set-output name=ok::${{ secrets.KS_PASS != '' }}"
id: check-secrets
- - uses: actions/checkout@v3
+ - uses: actions/checkout@755da8c3cf115ac066823e79a1e1788f8940201b # v3
if: ${{ steps.check-secrets.outputs.ok == 'true' }}
- name: set up JDK 11
- uses: actions/setup-java@v3
+ uses: actions/setup-java@1df8dbefe2a8cbc99770194893dd902763bee34b # v3
if: ${{ steps.check-secrets.outputs.ok == 'true' }}
with:
distribution: "temurin"
diff --git a/.github/workflows/screenShotTest.yml b/.github/workflows/screenShotTest.yml
index ac36160e9c..74b646102b 100644
--- a/.github/workflows/screenShotTest.yml
+++ b/.github/workflows/screenShotTest.yml
@@ -18,17 +18,17 @@ jobs:
color: [ blue ]
api-level: [ 27 ]
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@755da8c3cf115ac066823e79a1e1788f8940201b # v3
- name: Gradle cache
- uses: actions/cache@v3
+ uses: actions/cache@9b0c1fce7a93df8e3bb8926b0d6e9d89e92f20a7 # v3
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*') }}-${{ hashFiles('**/gradle/wrapper/gradle-wrapper.properties') }}
- name: AVD cache
- uses: actions/cache@v3
+ uses: actions/cache@9b0c1fce7a93df8e3bb8926b0d6e9d89e92f20a7 # v3
id: avd-cache
with:
path: |
@@ -36,14 +36,14 @@ jobs:
~/.android/adb*
key: avd-${{ matrix.api-level }}
- - uses: actions/setup-java@v3
+ - uses: actions/setup-java@1df8dbefe2a8cbc99770194893dd902763bee34b # v3
with:
distribution: "temurin"
java-version: 11
- name: create AVD and generate snapshot for caching
if: steps.avd-cache.outputs.cache-hit != 'true'
- uses: reactivecircus/android-emulator-runner@v2
+ uses: reactivecircus/android-emulator-runner@50986b1464923454c95e261820bc626f38490ec0 # v2
with:
api-level: ${{ matrix.api-level }}
force-avd-creation: false
@@ -64,12 +64,12 @@ jobs:
- name: Delete old comments
env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
if: ${{ always() }}
run: scripts/deleteOldComments.sh "${{ matrix.color }}-${{ matrix.scheme }}" "Screenshot" ${{github.event.number}}
- name: Run screenshot tests
- uses: reactivecircus/android-emulator-runner@v2
+ uses: reactivecircus/android-emulator-runner@50986b1464923454c95e261820bc626f38490ec0 # v2
with:
api-level: ${{ matrix.api-level }}
force-avd-creation: false
@@ -82,8 +82,7 @@ jobs:
if: ${{ failure() }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- run:
- scripts/uploadReport.sh "${{ secrets.LOG_USERNAME }}" "${{ secrets.LOG_PASSWORD }}" ${{github.event.number}} "${{ matrix.color }}-${{ matrix.scheme }}" "Screenshot" ${{github.event.number}}
+ run: scripts/uploadReport.sh "${{ secrets.LOG_USERNAME }}" "${{ secrets.LOG_PASSWORD }}" ${{github.event.number}} "${{ matrix.color }}-${{ matrix.scheme }}" "Screenshot" ${{github.event.number}}
- name: Archive Espresso results
uses: actions/upload-artifact@83fd05a356d7e2593de66fc9913b3002723633cb
if: ${{ always() }}
diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml
index e2124ec9f3..09cb471f10 100644
--- a/.github/workflows/stale.yml
+++ b/.github/workflows/stale.yml
@@ -1,4 +1,4 @@
----
+# synced from @nextcloud/android-config
name: 'Close stale issues'
on:
schedule:
@@ -14,7 +14,7 @@ jobs:
issues: write
pull-requests: write
steps:
- - uses: actions/stale@v6
+ - uses: actions/stale@5ebf00ea0e4c1561e9b43a292ed34424fb1d4578 # v6
with:
days-before-stale: 28
days-before-close: 14
diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml
index a64c5f69ef..f3e3baf73e 100644
--- a/.github/workflows/unit-tests.yml
+++ b/.github/workflows/unit-tests.yml
@@ -14,29 +14,28 @@ jobs:
test:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@755da8c3cf115ac066823e79a1e1788f8940201b # v3
- name: Set up JDK 11
- uses: actions/setup-java@v3
+ uses: actions/setup-java@1df8dbefe2a8cbc99770194893dd902763bee34b # v3
with:
distribution: "temurin"
java-version: 11
- name: Delete old comments
env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
if: ${{ always() }}
run: scripts/deleteOldComments.sh "test" "Unit" ${{github.event.number}}
- name: Run unit tests with coverage
- uses: gradle/gradle-build-action@v2
+ uses: gradle/gradle-build-action@3fbe033aaae657f011f88f29be9e65ed26bd29ef # v2
with:
arguments: jacocoTestGplayDebugUnitTest
- name: Upload failing results
if: ${{ failure() }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- run:
- scripts/uploadReport.sh "${{ secrets.LOG_USERNAME }}" "${{ secrets.LOG_PASSWORD }}" ${{github.event.number}} "test" "Unit" ${{github.event.number}}
+ run: scripts/uploadReport.sh "${{ secrets.LOG_USERNAME }}" "${{ secrets.LOG_PASSWORD }}" ${{github.event.number}} "test" "Unit" ${{github.event.number}}
- name: Upload coverage to codecov
- uses: codecov/codecov-action@v3
+ uses: codecov/codecov-action@d9f34f8cd5cb3b3eb79b3e4b5dae3a16df499a70 # v3
with:
token: ${{ secrets.CODECOV_TOKEN }}
flags: unit
diff --git a/app/screenshots/gplay/debug/com.nextcloud.client.CommunityActivityIT_open.png b/app/screenshots/gplay/debug/com.nextcloud.client.CommunityActivityIT_open.png
index 6160fc4005..c70bcfaee7 100644
Binary files a/app/screenshots/gplay/debug/com.nextcloud.client.CommunityActivityIT_open.png and b/app/screenshots/gplay/debug/com.nextcloud.client.CommunityActivityIT_open.png differ
diff --git a/app/src/main/java/com/nextcloud/client/database/DatabaseModule.kt b/app/src/main/java/com/nextcloud/client/database/DatabaseModule.kt
index 822c0380c9..92aff3f3f2 100644
--- a/app/src/main/java/com/nextcloud/client/database/DatabaseModule.kt
+++ b/app/src/main/java/com/nextcloud/client/database/DatabaseModule.kt
@@ -25,6 +25,7 @@ package com.nextcloud.client.database
import android.content.Context
import com.nextcloud.client.core.Clock
import com.nextcloud.client.database.dao.ArbitraryDataDao
+import com.nextcloud.client.database.dao.FileDao
import dagger.Module
import dagger.Provides
import javax.inject.Singleton
@@ -42,4 +43,9 @@ class DatabaseModule {
fun arbitraryDataDao(nextcloudDatabase: NextcloudDatabase): ArbitraryDataDao {
return nextcloudDatabase.arbitraryDataDao()
}
+
+ @Provides
+ fun fileDao(nextcloudDatabase: NextcloudDatabase): FileDao {
+ return nextcloudDatabase.fileDao()
+ }
}
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 62b7045a59..64b19a55a8 100644
--- a/app/src/main/java/com/nextcloud/client/database/NextcloudDatabase.kt
+++ b/app/src/main/java/com/nextcloud/client/database/NextcloudDatabase.kt
@@ -30,6 +30,7 @@ import androidx.room.RoomDatabase
import com.nextcloud.client.core.Clock
import com.nextcloud.client.core.ClockImpl
import com.nextcloud.client.database.dao.ArbitraryDataDao
+import com.nextcloud.client.database.dao.FileDao
import com.nextcloud.client.database.entity.ArbitraryDataEntity
import com.nextcloud.client.database.entity.CapabilityEntity
import com.nextcloud.client.database.entity.ExternalLinkEntity
@@ -65,6 +66,7 @@ import com.owncloud.android.db.ProviderMeta
abstract class NextcloudDatabase : RoomDatabase() {
abstract fun arbitraryDataDao(): ArbitraryDataDao
+ abstract fun fileDao(): FileDao
companion object {
const val FIRST_ROOM_DB_VERSION = 65
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
new file mode 100644
index 0000000000..21a7e34d70
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/client/database/dao/FileDao.kt
@@ -0,0 +1,64 @@
+/*
+ * Nextcloud Android client application
+ *
+ * @author Dariusz Olszewski
+ * Copyright (C) 2022 Dariusz Olszewski
+ * Copyright (C) 2022 Nextcloud GmbH
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License along with this program. If not, see .
+ *
+ */
+
+package com.nextcloud.client.database.dao
+
+import androidx.room.Dao
+import androidx.room.Query
+import com.nextcloud.client.database.entity.FileEntity
+import com.owncloud.android.db.ProviderMeta.ProviderTableMeta
+
+@Dao
+interface FileDao {
+ @Query("SELECT * FROM filelist WHERE _id = :id LIMIT 1")
+ fun getFileById(id: Long): FileEntity?
+
+ @Query("SELECT * FROM filelist WHERE path = :path AND file_owner = :fileOwner LIMIT 1")
+ fun getFileByEncryptedRemotePath(path: String, fileOwner: String): FileEntity?
+
+ @Query("SELECT * FROM filelist WHERE path_decrypted = :path AND file_owner = :fileOwner LIMIT 1")
+ fun getFileByDecryptedRemotePath(path: String, fileOwner: String): FileEntity?
+
+ @Query("SELECT * FROM filelist WHERE media_path = :path AND file_owner = :fileOwner LIMIT 1")
+ fun getFileByLocalPath(path: String, fileOwner: String): FileEntity?
+
+ @Query("SELECT * FROM filelist WHERE remote_id = :remoteId AND file_owner = :fileOwner LIMIT 1")
+ fun getFileByRemoteId(remoteId: String, fileOwner: String): FileEntity?
+
+ @Query("SELECT * FROM filelist WHERE parent = :parentId ORDER BY ${ProviderTableMeta.FILE_DEFAULT_SORT_ORDER}")
+ fun getFolderContent(parentId: Long): List
+
+ @Query(
+ "SELECT * FROM filelist WHERE modified >= :startDate" +
+ " AND modified < :endDate" +
+ " AND (content_type LIKE 'image/%' OR content_type LIKE 'video/%')" +
+ " AND file_owner = :fileOwner" +
+ " ORDER BY ${ProviderTableMeta.FILE_DEFAULT_SORT_ORDER}"
+ )
+ fun getGalleryItems(startDate: Long, endDate: Long, fileOwner: String): List
+
+ @Query("SELECT * FROM filelist WHERE file_owner = :fileOwner ORDER BY ${ProviderTableMeta.FILE_DEFAULT_SORT_ORDER}")
+ fun getAllFiles(fileOwner: String): List
+
+ @Query("SELECT * FROM filelist WHERE path LIKE :pathPattern AND file_owner = :fileOwner ORDER BY path ASC")
+ fun getFolderWithDescendants(pathPattern: String, 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 e05bd6fd63..f02a06af2a 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
@@ -31,7 +31,7 @@ import com.owncloud.android.db.ProviderMeta.ProviderTableMeta
data class FileEntity(
@PrimaryKey(autoGenerate = true)
@ColumnInfo(name = ProviderTableMeta._ID)
- val id: Int?,
+ val id: Long?,
@ColumnInfo(name = ProviderTableMeta.FILE_NAME)
val name: String?,
@ColumnInfo(name = ProviderTableMeta.FILE_ENCRYPTED_NAME)
@@ -41,25 +41,25 @@ data class FileEntity(
@ColumnInfo(name = ProviderTableMeta.FILE_PATH_DECRYPTED)
val pathDecrypted: String?,
@ColumnInfo(name = ProviderTableMeta.FILE_PARENT)
- val parent: Int?,
+ val parent: Long?,
@ColumnInfo(name = ProviderTableMeta.FILE_CREATION)
- val creation: Int?,
+ val creation: Long?,
@ColumnInfo(name = ProviderTableMeta.FILE_MODIFIED)
- val modified: Int?,
+ val modified: Long?,
@ColumnInfo(name = ProviderTableMeta.FILE_CONTENT_TYPE)
val contentType: String?,
@ColumnInfo(name = ProviderTableMeta.FILE_CONTENT_LENGTH)
- val contentLength: Int?,
+ val contentLength: Long?,
@ColumnInfo(name = ProviderTableMeta.FILE_STORAGE_PATH)
val storagePath: String?,
@ColumnInfo(name = ProviderTableMeta.FILE_ACCOUNT_OWNER)
val accountOwner: String?,
@ColumnInfo(name = ProviderTableMeta.FILE_LAST_SYNC_DATE)
- val lastSyncDate: Int?,
+ val lastSyncDate: Long?,
@ColumnInfo(name = ProviderTableMeta.FILE_LAST_SYNC_DATE_FOR_DATA)
- val lastSyncDateForData: Int?,
+ val lastSyncDateForData: Long?,
@ColumnInfo(name = ProviderTableMeta.FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA)
- val modifiedAtLastSyncForData: Int?,
+ val modifiedAtLastSyncForData: Long?,
@ColumnInfo(name = ProviderTableMeta.FILE_ETAG)
val etag: String?,
@ColumnInfo(name = ProviderTableMeta.FILE_ETAG_ON_SERVER)
@@ -111,7 +111,7 @@ data class FileEntity(
@ColumnInfo(name = ProviderTableMeta.FILE_LOCK_OWNER_EDITOR)
val lockOwnerEditor: String?,
@ColumnInfo(name = ProviderTableMeta.FILE_LOCK_TIMESTAMP)
- val lockTimestamp: Int?,
+ val lockTimestamp: Long?,
@ColumnInfo(name = ProviderTableMeta.FILE_LOCK_TIMEOUT)
val lockTimeout: Int?,
@ColumnInfo(name = ProviderTableMeta.FILE_LOCK_TOKEN)
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 b3eab7a91c..4321696775 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,9 @@ import android.text.TextUtils;
import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException;
import com.nextcloud.client.account.User;
+import com.nextcloud.client.database.NextcloudDatabase;
+import com.nextcloud.client.database.dao.FileDao;
+import com.nextcloud.client.database.entity.FileEntity;
import com.owncloud.android.MainApp;
import com.owncloud.android.db.ProviderMeta.ProviderTableMeta;
import com.owncloud.android.lib.common.network.WebdavEntry;
@@ -91,6 +94,9 @@ public class FileDataStorageManager {
private final ContentProviderClient contentProviderClient;
private final User user;
+ private final FileDao fileDao = NextcloudDatabase.getInstance(MainApp.getAppContext()).fileDao();
+ private final Gson gson = new Gson();
+
public FileDataStorageManager(User user, ContentResolver contentResolver) {
this.contentProviderClient = null;
this.contentResolver = contentResolver;
@@ -122,65 +128,53 @@ public class FileDataStorageManager {
private @Nullable
OCFile getFileByPath(String type, String path) {
- Cursor cursor = getFileCursorForValue(type, path);
- OCFile ocFile = null;
- if (cursor.moveToFirst()) {
- ocFile = createFileInstance(cursor);
- }
- cursor.close();
+ final boolean shouldUseEncryptedPath = ProviderTableMeta.FILE_PATH.equals(type);
+ FileEntity fileEntity = shouldUseEncryptedPath ?
+ fileDao.getFileByEncryptedRemotePath(path, user.getAccountName()) :
+ fileDao.getFileByDecryptedRemotePath(path, user.getAccountName());
- if (ocFile == null && OCFile.ROOT_PATH.equals(path)) {
+ if (fileEntity != null) {
+ return createFileInstance(fileEntity);
+ }
+
+ if (OCFile.ROOT_PATH.equals(path)) {
return createRootDir(); // root should always exist
}
- return ocFile;
+ return null;
}
public @Nullable
OCFile getFileById(long id) {
- Cursor cursor = getFileCursorForValue(ProviderTableMeta._ID, String.valueOf(id));
- OCFile ocFile = null;
-
- if (cursor.moveToFirst()) {
- ocFile = createFileInstance(cursor);
+ FileEntity fileEntity = fileDao.getFileById(id);
+ if (fileEntity != null) {
+ return createFileInstance(fileEntity);
}
- cursor.close();
-
- return ocFile;
+ return null;
}
public @Nullable
OCFile getFileByLocalPath(String path) {
- Cursor cursor = getFileCursorForValue(ProviderTableMeta.FILE_STORAGE_PATH, path);
- OCFile ocFile = null;
-
- if (cursor.moveToFirst()) {
- ocFile = createFileInstance(cursor);
+ FileEntity fileEntity = fileDao.getFileByLocalPath(path, user.getAccountName());
+ if (fileEntity != null) {
+ return createFileInstance(fileEntity);
}
- cursor.close();
-
- return ocFile;
+ return null;
}
public @Nullable
OCFile getFileByRemoteId(String remoteId) {
- Cursor cursor = getFileCursorForValue(ProviderTableMeta.FILE_REMOTE_ID, remoteId);
- OCFile ocFile = null;
-
- if (cursor.moveToFirst()) {
- ocFile = createFileInstance(cursor);
+ FileEntity fileEntity = fileDao.getFileByRemoteId(remoteId, user.getAccountName());
+ if (fileEntity != null) {
+ return createFileInstance(fileEntity);
}
- cursor.close();
-
- return ocFile;
+ return null;
}
- public boolean fileExists(long id) {
- return fileExists(ProviderTableMeta._ID, String.valueOf(id));
- }
+ public boolean fileExists(long id) { return fileDao.getFileById(id) != null; }
public boolean fileExists(String path) {
- return fileExists(ProviderTableMeta.FILE_PATH, path);
+ return fileDao.getFileByEncryptedRemotePath(path, user.getAccountName()) != null;
}
@@ -662,82 +656,60 @@ public class FileDataStorageManager {
throw new IllegalStateException("Parent folder of the target path does not exist!!");
}
- /// 1. get all the descendants of the moved element in a single QUERY
- Cursor cursor = null;
- if (getContentProviderClient() != null) {
- try {
- cursor = getContentProviderClient().query(
- ProviderTableMeta.CONTENT_URI,
- null,
- ProviderTableMeta.FILE_ACCOUNT_OWNER + AND + ProviderTableMeta.FILE_PATH + " LIKE ? ",
- new String[]{user.getAccountName(), ocFile.getRemotePath() + "%"},
- ProviderTableMeta.FILE_PATH + " ASC "
- );
- } catch (RemoteException e) {
- Log_OC.e(TAG, e.getMessage(), e);
- }
+ String oldPath = ocFile.getRemotePath();
- } else {
- cursor = getContentResolver().query(
- ProviderTableMeta.CONTENT_URI,
- null,
- ProviderTableMeta.FILE_ACCOUNT_OWNER + AND + ProviderTableMeta.FILE_PATH + " LIKE ? ",
- new String[]{user.getAccountName(), ocFile.getRemotePath() + "%"},
- ProviderTableMeta.FILE_PATH + " ASC "
- );
- }
+ /// 1. get all the descendants of the moved element in a single QUERY
+ List fileEntities =
+ fileDao.getFolderWithDescendants(oldPath + "%", user.getAccountName());
/// 2. prepare a batch of update operations to change all the descendants
- ArrayList operations = new ArrayList<>(cursor.getCount());
+ ArrayList operations = new ArrayList<>(fileEntities.size());
String defaultSavePath = FileStorageUtils.getSavePath(user.getAccountName());
List originalPathsToTriggerMediaScan = new ArrayList<>();
List newPathsToTriggerMediaScan = new ArrayList<>();
- if (cursor.moveToFirst()) {
- int lengthOfOldPath = ocFile.getRemotePath().length();
- int lengthOfOldStoragePath = defaultSavePath.length() + lengthOfOldPath;
- do {
- ContentValues contentValues = new ContentValues(); // keep construction in the loop
- OCFile childFile = createFileInstance(cursor);
+ int lengthOfOldPath = oldPath.length();
+ int lengthOfOldStoragePath = defaultSavePath.length() + lengthOfOldPath;
+ for (FileEntity fileEntity: fileEntities) {
+ ContentValues contentValues = new ContentValues(); // keep construction in the loop
+ OCFile childFile = createFileInstance(fileEntity);
+ contentValues.put(
+ ProviderTableMeta.FILE_PATH,
+ targetPath + childFile.getRemotePath().substring(lengthOfOldPath)
+ );
+
+ if (!childFile.isEncrypted()) {
contentValues.put(
- ProviderTableMeta.FILE_PATH,
+ ProviderTableMeta.FILE_PATH_DECRYPTED,
targetPath + childFile.getRemotePath().substring(lengthOfOldPath)
);
+ }
- if (!childFile.isEncrypted()) {
- contentValues.put(
- ProviderTableMeta.FILE_PATH_DECRYPTED,
- targetPath + childFile.getRemotePath().substring(lengthOfOldPath)
- );
+ if (childFile.getStoragePath() != null && childFile.getStoragePath().startsWith(defaultSavePath)) {
+ // update link to downloaded content - but local move is not done here!
+ String targetLocalPath = defaultSavePath + targetPath +
+ childFile.getStoragePath().substring(lengthOfOldStoragePath);
+
+ contentValues.put(ProviderTableMeta.FILE_STORAGE_PATH, targetLocalPath);
+
+ if (MimeTypeUtil.isMedia(childFile.getMimeType())) {
+ originalPathsToTriggerMediaScan.add(childFile.getStoragePath());
+ newPathsToTriggerMediaScan.add(targetLocalPath);
}
- if (childFile.getStoragePath() != null && childFile.getStoragePath().startsWith(defaultSavePath)) {
- // update link to downloaded content - but local move is not done here!
- String targetLocalPath = defaultSavePath + targetPath +
- childFile.getStoragePath().substring(lengthOfOldStoragePath);
+ }
- contentValues.put(ProviderTableMeta.FILE_STORAGE_PATH, targetLocalPath);
+ if (childFile.getRemotePath().equals(ocFile.getRemotePath())) {
+ contentValues.put(ProviderTableMeta.FILE_PARENT, targetParent.getFileId());
+ }
- if (MimeTypeUtil.isMedia(childFile.getMimeType())) {
- originalPathsToTriggerMediaScan.add(childFile.getStoragePath());
- newPathsToTriggerMediaScan.add(targetLocalPath);
- }
+ operations.add(
+ ContentProviderOperation.newUpdate(ProviderTableMeta.CONTENT_URI)
+ .withValues(contentValues)
+ .withSelection(ProviderTableMeta._ID + " = ?", new String[]{String.valueOf(childFile.getFileId())})
+ .build());
- }
-
- if (childFile.getRemotePath().equals(ocFile.getRemotePath())) {
- contentValues.put(ProviderTableMeta.FILE_PARENT, targetParent.getFileId());
- }
-
- operations.add(
- ContentProviderOperation.newUpdate(ProviderTableMeta.CONTENT_URI)
- .withValues(contentValues)
- .withSelection(ProviderTableMeta._ID + " = ?", new String[]{String.valueOf(childFile.getFileId())})
- .build());
-
- } while (cursor.moveToNext());
}
- cursor.close();
/// 3. apply updates in batch
try {
@@ -861,46 +833,18 @@ public class FileDataStorageManager {
}
private List getFolderContent(long parentId, boolean onlyOnDevice) {
+ Log_OC.d(TAG, "getFolderContent - start");
List folderContent = new ArrayList<>();
- Uri requestURI = Uri.withAppendedPath(ProviderTableMeta.CONTENT_URI_DIR, String.valueOf(parentId));
- Cursor cursor;
-
- if (getContentProviderClient() != null) {
- try {
- cursor = getContentProviderClient().query(
- requestURI,
- null,
- ProviderTableMeta.FILE_PARENT + "=?",
- new String[]{String.valueOf(parentId)},
- null
- );
- } catch (RemoteException e) {
- Log_OC.e(TAG, e.getMessage(), e);
- return folderContent;
+ List files = fileDao.getFolderContent(parentId);
+ for (FileEntity fileEntity: files) {
+ OCFile child = createFileInstance(fileEntity);
+ if (!onlyOnDevice || child.existsOnDevice()) {
+ folderContent.add(child);
}
- } else {
- cursor = getContentResolver().query(
- requestURI,
- null,
- ProviderTableMeta.FILE_PARENT + "=?",
- new String[]{String.valueOf(parentId)},
- null
- );
- }
-
- if (cursor != null) {
- if (cursor.moveToFirst()) {
- do {
- OCFile child = createFileInstance(cursor);
- if (!onlyOnDevice || child.existsOnDevice()) {
- folderContent.add(child);
- }
- } while (cursor.moveToNext());
- }
- cursor.close();
}
+ Log_OC.d(TAG, "getFolderContent - finished");
return folderContent;
}
@@ -914,47 +858,6 @@ public class FileDataStorageManager {
return ocFile;
}
- // TODO write test
- private boolean fileExists(String key, String value) {
- Cursor cursor = getFileCursorForValue(key, value);
- boolean isExists = false;
-
- if (cursor == null) {
- Log_OC.e(TAG, "Couldn't determine file existance, assuming non existance");
- } else {
- isExists = cursor.moveToFirst();
- cursor.close();
- }
-
- return isExists;
- }
-
- private Cursor getFileCursorForValue(String key, String value) {
- Cursor cursor;
- if (getContentResolver() != null) {
- cursor = getContentResolver()
- .query(ProviderTableMeta.CONTENT_URI,
- null,
- key + AND
- + ProviderTableMeta.FILE_ACCOUNT_OWNER
- + "=?",
- new String[]{value, user.getAccountName()}, null);
- } else {
- try {
- cursor = getContentProviderClient().query(
- ProviderTableMeta.CONTENT_URI,
- null,
- key + AND + ProviderTableMeta.FILE_ACCOUNT_OWNER
- + "=?", new String[]{value, user.getAccountName()},
- null);
- } catch (RemoteException e) {
- Log_OC.e(TAG, "Could not get file details: " + e.getMessage(), e);
- cursor = null;
- }
- }
- return cursor;
- }
-
@Nullable
private OCFile createFileInstanceFromVirtual(Cursor cursor) {
long fileId = cursor.getLong(cursor.getColumnIndexOrThrow(ProviderTableMeta.VIRTUAL_OCFILE_ID));
@@ -962,85 +865,88 @@ public class FileDataStorageManager {
return getFileById(fileId);
}
- private OCFile createFileInstance(Cursor cursor) {
- OCFile ocFile = null;
- if (cursor != null) {
- ocFile = new OCFile(cursor.getString(cursor.getColumnIndexOrThrow(ProviderTableMeta.FILE_PATH)));
- ocFile.setDecryptedRemotePath(getString(cursor, ProviderTableMeta.FILE_PATH_DECRYPTED));
- ocFile.setFileId(cursor.getLong(cursor.getColumnIndexOrThrow(ProviderTableMeta._ID)));
- ocFile.setParentId(cursor.getLong(cursor.getColumnIndexOrThrow(ProviderTableMeta.FILE_PARENT)));
- ocFile.setMimeType(cursor.getString(cursor.getColumnIndexOrThrow(ProviderTableMeta.FILE_CONTENT_TYPE)));
- ocFile.setStoragePath(cursor.getString(cursor.getColumnIndexOrThrow(ProviderTableMeta.FILE_STORAGE_PATH)));
- if (ocFile.getStoragePath() == null) {
- // try to find existing file and bind it with current account;
- // with the current update of SynchronizeFolderOperation, this won't be
- // necessary anymore after a full synchronization of the account
- File file = new File(FileStorageUtils.getDefaultSavePathFor(user.getAccountName(), ocFile));
- if (file.exists()) {
- ocFile.setStoragePath(file.getAbsolutePath());
- ocFile.setLastSyncDateForData(file.lastModified());
- }
+ private int nullToZero(Integer i) {
+ return (i == null) ? 0 : i;
+ }
+
+ private long nullToZero(Long i) {
+ return (i == null) ? 0 : i;
+ }
+
+ private OCFile createFileInstance(FileEntity fileEntity) {
+ OCFile ocFile = new OCFile(fileEntity.getPath());
+ ocFile.setDecryptedRemotePath(fileEntity.getPathDecrypted());
+ ocFile.setFileId(nullToZero(fileEntity.getId()));
+ ocFile.setParentId(nullToZero(fileEntity.getParent()));
+ ocFile.setMimeType(fileEntity.getContentType());
+ ocFile.setStoragePath(fileEntity.getStoragePath());
+ if (ocFile.getStoragePath() == null) {
+ // try to find existing file and bind it with current account;
+ // with the current update of SynchronizeFolderOperation, this won't be
+ // necessary anymore after a full synchronization of the account
+ File file = new File(FileStorageUtils.getDefaultSavePathFor(user.getAccountName(), ocFile));
+ if (file.exists()) {
+ ocFile.setStoragePath(file.getAbsolutePath());
+ ocFile.setLastSyncDateForData(file.lastModified());
}
- ocFile.setFileLength(cursor.getLong(cursor.getColumnIndexOrThrow(ProviderTableMeta.FILE_CONTENT_LENGTH)));
- ocFile.setCreationTimestamp(cursor.getLong(cursor.getColumnIndexOrThrow(ProviderTableMeta.FILE_CREATION)));
- ocFile.setModificationTimestamp(cursor.getLong(cursor.getColumnIndexOrThrow(ProviderTableMeta.FILE_MODIFIED)));
- ocFile.setModificationTimestampAtLastSyncForData(cursor.getLong(
- cursor.getColumnIndexOrThrow(ProviderTableMeta.FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA)));
- ocFile.setLastSyncDateForProperties(cursor.getLong(cursor.getColumnIndexOrThrow(ProviderTableMeta.FILE_LAST_SYNC_DATE)));
- ocFile.setLastSyncDateForData(cursor.getLong(cursor.getColumnIndexOrThrow(ProviderTableMeta.FILE_LAST_SYNC_DATE_FOR_DATA)));
- ocFile.setEtag(cursor.getString(cursor.getColumnIndexOrThrow(ProviderTableMeta.FILE_ETAG)));
- ocFile.setEtagOnServer(cursor.getString(cursor.getColumnIndexOrThrow(ProviderTableMeta.FILE_ETAG_ON_SERVER)));
- ocFile.setSharedViaLink(cursor.getInt(cursor.getColumnIndexOrThrow(ProviderTableMeta.FILE_SHARED_VIA_LINK)) == 1);
- ocFile.setSharedWithSharee(cursor.getInt(cursor.getColumnIndexOrThrow(ProviderTableMeta.FILE_SHARED_WITH_SHAREE)) == 1);
- ocFile.setPermissions(cursor.getString(cursor.getColumnIndexOrThrow(ProviderTableMeta.FILE_PERMISSIONS)));
- ocFile.setRemoteId(cursor.getString(cursor.getColumnIndexOrThrow(ProviderTableMeta.FILE_REMOTE_ID)));
- ocFile.setUpdateThumbnailNeeded(cursor.getInt(cursor.getColumnIndexOrThrow(ProviderTableMeta.FILE_UPDATE_THUMBNAIL)) == 1);
- ocFile.setDownloading(cursor.getInt(cursor.getColumnIndexOrThrow(ProviderTableMeta.FILE_IS_DOWNLOADING)) == 1);
- ocFile.setEtagInConflict(cursor.getString(cursor.getColumnIndexOrThrow(ProviderTableMeta.FILE_ETAG_IN_CONFLICT)));
- ocFile.setFavorite(cursor.getInt(cursor.getColumnIndexOrThrow(ProviderTableMeta.FILE_FAVORITE)) == 1);
- ocFile.setEncrypted(cursor.getInt(cursor.getColumnIndexOrThrow(ProviderTableMeta.FILE_IS_ENCRYPTED)) == 1);
-// if (ocFile.isEncrypted()) {
-// ocFile.setFileName(cursor.getString(cursor.getColumnIndexOrThrow(ProviderTableMeta.FILE_NAME)));
-// }
- ocFile.setMountType(WebdavEntry.MountType.values()[cursor.getInt(
- cursor.getColumnIndexOrThrow(ProviderTableMeta.FILE_MOUNT_TYPE))]);
- ocFile.setPreviewAvailable(cursor.getInt(cursor.getColumnIndexOrThrow(ProviderTableMeta.FILE_HAS_PREVIEW)) == 1);
- ocFile.setUnreadCommentsCount(cursor.getInt(cursor.getColumnIndexOrThrow(ProviderTableMeta.FILE_UNREAD_COMMENTS_COUNT)));
- ocFile.setOwnerId(cursor.getString(cursor.getColumnIndexOrThrow(ProviderTableMeta.FILE_OWNER_ID)));
- ocFile.setOwnerDisplayName(cursor.getString(cursor.getColumnIndexOrThrow(ProviderTableMeta.FILE_OWNER_DISPLAY_NAME)));
- ocFile.setNote(cursor.getString(cursor.getColumnIndexOrThrow(ProviderTableMeta.FILE_NOTE)));
- ocFile.setRichWorkspace(cursor.getString(cursor.getColumnIndexOrThrow(ProviderTableMeta.FILE_RICH_WORKSPACE)));
- ocFile.setLocked(cursor.getInt(cursor.getColumnIndexOrThrow(ProviderTableMeta.FILE_LOCKED)) == 1);
- final int lockTypeInt = cursor.getInt(cursor.getColumnIndexOrThrow(ProviderTableMeta.FILE_LOCK_TYPE));
- ocFile.setLockType(lockTypeInt != -1 ? FileLockType.fromValue(lockTypeInt) : null);
- ocFile.setLockOwnerId(cursor.getString(cursor.getColumnIndexOrThrow(ProviderTableMeta.FILE_LOCK_OWNER)));
- ocFile.setLockOwnerDisplayName(cursor.getString(cursor.getColumnIndexOrThrow(ProviderTableMeta.FILE_LOCK_OWNER_DISPLAY_NAME)));
- ocFile.setLockOwnerEditor(cursor.getString(cursor.getColumnIndexOrThrow(ProviderTableMeta.FILE_LOCK_OWNER_EDITOR)));
- ocFile.setLockTimestamp(cursor.getInt(cursor.getColumnIndexOrThrow(ProviderTableMeta.FILE_LOCK_TIMESTAMP)));
- ocFile.setLockTimeout(cursor.getInt(cursor.getColumnIndexOrThrow(ProviderTableMeta.FILE_LOCK_TIMEOUT)));
- ocFile.setLockToken(cursor.getString(cursor.getColumnIndexOrThrow(ProviderTableMeta.FILE_LOCK_TOKEN)));
+ }
+ ocFile.setFileLength(nullToZero(fileEntity.getContentLength()));
+ ocFile.setCreationTimestamp(nullToZero(fileEntity.getCreation()));
+ ocFile.setModificationTimestamp(nullToZero(fileEntity.getModified()));
+ ocFile.setModificationTimestampAtLastSyncForData(nullToZero(fileEntity.getModifiedAtLastSyncForData()));
+ ocFile.setLastSyncDateForProperties(nullToZero(fileEntity.getLastSyncDate()));
+ ocFile.setLastSyncDateForData(nullToZero(fileEntity.getLastSyncDateForData()));
+ ocFile.setEtag(fileEntity.getEtag());
+ ocFile.setEtagOnServer(fileEntity.getEtagOnServer());
+ ocFile.setSharedViaLink(nullToZero(fileEntity.getSharedViaLink()) == 1);
+ ocFile.setSharedWithSharee(nullToZero(fileEntity.getSharedWithSharee()) == 1);
+ ocFile.setPermissions(fileEntity.getPermissions());
+ ocFile.setRemoteId(fileEntity.getRemoteId());
+ ocFile.setUpdateThumbnailNeeded(nullToZero(fileEntity.getUpdateThumbnail()) == 1);
+ ocFile.setDownloading(nullToZero(fileEntity.isDownloading()) == 1);
+ ocFile.setEtagInConflict(fileEntity.getEtagInConflict());
+ ocFile.setFavorite(nullToZero(fileEntity.getFavorite()) == 1);
+ ocFile.setEncrypted(nullToZero(fileEntity.isEncrypted()) == 1);
+// if (ocFile.isEncrypted()) {
+// ocFile.setFileName(cursor.getString(cursor.getColumnIndexOrThrow(ProviderTableMeta.FILE_NAME)));
+// }
+ Integer mountType = fileEntity.getMountType(); // TODO - any default when NULL returned?
+ if (mountType != null) {
+ ocFile.setMountType(WebdavEntry.MountType.values()[mountType]);
+ }
+ ocFile.setPreviewAvailable(nullToZero(fileEntity.getHasPreview()) == 1);
+ ocFile.setUnreadCommentsCount(nullToZero(fileEntity.getUnreadCommentsCount()));
+ ocFile.setOwnerId(fileEntity.getOwnerId());
+ ocFile.setOwnerDisplayName(fileEntity.getOwnerDisplayName());
+ ocFile.setNote(fileEntity.getNote());
+ ocFile.setRichWorkspace(fileEntity.getRichWorkspace());
+ ocFile.setLocked(nullToZero(fileEntity.getLocked()) == 1);
+ final int lockTypeInt = nullToZero(fileEntity.getLockType()); // TODO - what value should be used for NULL???
+ ocFile.setLockType(lockTypeInt != -1 ? FileLockType.fromValue(lockTypeInt) : null);
+ ocFile.setLockOwnerId(fileEntity.getLockOwner());
+ ocFile.setLockOwnerDisplayName(fileEntity.getLockOwnerDisplayName());
+ ocFile.setLockOwnerEditor(fileEntity.getLockOwnerEditor());
+ ocFile.setLockTimestamp(nullToZero(fileEntity.getLockTimestamp()));
+ ocFile.setLockTimeout(nullToZero(fileEntity.getLockTimeout()));
+ ocFile.setLockToken(fileEntity.getLockToken());
-
- String sharees = cursor.getString(cursor.getColumnIndexOrThrow(ProviderTableMeta.FILE_SHAREES));
-
- if (sharees == null || NULL_STRING.equals(sharees) || sharees.isEmpty()) {
+ String sharees = fileEntity.getSharees();
+ if (sharees == null || NULL_STRING.equals(sharees) || sharees.isEmpty()) {
+ ocFile.setSharees(new ArrayList<>());
+ } else {
+ try {
+ ShareeUser[] shareesArray = gson.fromJson(sharees, ShareeUser[].class);
+ ocFile.setSharees(new ArrayList<>(Arrays.asList(shareesArray)));
+ } catch (JsonSyntaxException e) {
+ // ignore saved value due to api change
ocFile.setSharees(new ArrayList<>());
- } else {
- try {
- ShareeUser[] shareesArray = new Gson().fromJson(sharees, ShareeUser[].class);
-
- ocFile.setSharees(new ArrayList<>(Arrays.asList(shareesArray)));
- } catch (JsonSyntaxException e) {
- // ignore saved value due to api change
- ocFile.setSharees(new ArrayList<>());
- }
}
- String metadataSize = cursor.getString(cursor.getColumnIndexOrThrow(ProviderTableMeta.FILE_METADATA_SIZE));
- ImageDimension imageDimension = new Gson().fromJson(metadataSize, ImageDimension.class);
+ }
- if (imageDimension != null) {
- ocFile.setImageDimension(imageDimension);
- }
+ String metadataSize = fileEntity.getMetadataSize();
+ ImageDimension imageDimension = gson.fromJson(metadataSize, ImageDimension.class);
+ if (imageDimension != null) {
+ ocFile.setImageDimension(imageDimension);
}
return ocFile;
@@ -2193,64 +2099,17 @@ public class FileDataStorageManager {
}
public List getGalleryItems(long startDate, long endDate) {
- List files = new ArrayList<>();
+ Log_OC.d(TAG, "getGalleryItems - start: " + startDate + ", " + endDate);
- Uri requestURI = ProviderTableMeta.CONTENT_URI;
- Cursor cursor;
+ List fileEntities = fileDao.getGalleryItems(startDate, endDate, user.getAccountName());
+ Log_OC.d(TAG, "getGalleryItems - query complete, list size: " + fileEntities.size());
- if (getContentProviderClient() != null) {
- try {
- cursor = getContentProviderClient().query(
- requestURI,
- null,
- ProviderTableMeta.FILE_ACCOUNT_OWNER + AND +
- ProviderTableMeta.FILE_MODIFIED + ">=? AND " +
- ProviderTableMeta.FILE_MODIFIED + " AND (" +
- ProviderTableMeta.FILE_CONTENT_TYPE + " LIKE ? OR " +
- ProviderTableMeta.FILE_CONTENT_TYPE + " LIKE ? )",
- new String[]{
- user.getAccountName(),
- String.valueOf(startDate),
- String.valueOf(endDate),
- "image/%",
- "video/%"
- },
- null
- );
- } catch (RemoteException e) {
- Log_OC.e(TAG, e.getMessage(), e);
- return files;
- }
- } else {
- cursor = getContentResolver().query(
- requestURI,
- null,
- ProviderTableMeta.FILE_ACCOUNT_OWNER + AND +
- ProviderTableMeta.FILE_MODIFIED + ">=? AND " +
- ProviderTableMeta.FILE_MODIFIED + " AND (" +
- ProviderTableMeta.FILE_CONTENT_TYPE + " LIKE ? OR " +
- ProviderTableMeta.FILE_CONTENT_TYPE + " LIKE ? )",
- new String[]{
- user.getAccountName(),
- String.valueOf(startDate),
- String.valueOf(endDate),
- "image/%",
- "video/%"
- },
- null
- );
- }
-
- if (cursor != null) {
- if (cursor.moveToFirst()) {
- do {
- OCFile child = createFileInstance(cursor);
- files.add(child);
- } while (cursor.moveToNext());
- }
- cursor.close();
+ List files = new ArrayList<>(fileEntities.size());
+ for (FileEntity fileEntity: fileEntities) {
+ files.add(createFileInstance(fileEntity));
}
+ Log_OC.d(TAG, "getGalleryItems - finished");
return files;
}
@@ -2338,32 +2197,12 @@ public class FileDataStorageManager {
}
public List getAllFiles() {
- String selection = ProviderTableMeta.FILE_ACCOUNT_OWNER + "= ? ";
- String[] selectionArgs = new String[]{user.getAccountName()};
+ // TODO - Apparently this method is used only by tests
+ List fileEntities = fileDao.getAllFiles(user.getAccountName());
+ List folderContent = new ArrayList<>(fileEntities.size());
- List folderContent = new ArrayList<>();
-
- Uri requestURI = ProviderTableMeta.CONTENT_URI_DIR;
- Cursor cursor;
-
- if (getContentProviderClient() != null) {
- try {
- cursor = getContentProviderClient().query(requestURI, null, selection, selectionArgs, null);
- } catch (RemoteException e) {
- Log_OC.e(TAG, e.getMessage(), e);
- return folderContent;
- }
- } else {
- cursor = getContentResolver().query(requestURI, null, selection, selectionArgs, null);
- }
-
- if (cursor != null) {
- if (cursor.moveToFirst()) {
- do {
- folderContent.add(createFileInstance(cursor));
- } while (cursor.moveToNext());
- }
- cursor.close();
+ for (FileEntity fileEntity: fileEntities) {
+ folderContent.add(createFileInstance(fileEntity));
}
return folderContent;
diff --git a/app/src/main/java/com/owncloud/android/ui/activity/FolderPickerActivity.kt b/app/src/main/java/com/owncloud/android/ui/activity/FolderPickerActivity.kt
index 56f2e9458e..b9b7f11ef3 100644
--- a/app/src/main/java/com/owncloud/android/ui/activity/FolderPickerActivity.kt
+++ b/app/src/main/java/com/owncloud/android/ui/activity/FolderPickerActivity.kt
@@ -53,6 +53,7 @@ import com.owncloud.android.utils.DataHolderUtil
import com.owncloud.android.utils.DisplayUtils
import com.owncloud.android.utils.ErrorMessageAdapter
import com.owncloud.android.utils.FileSortOrder
+import com.owncloud.android.utils.PathUtils
import java.io.File
import javax.inject.Inject
@@ -74,6 +75,9 @@ open class FolderPickerActivity :
private var mChooseBtn: MaterialButton? = null
private var caption: String? = null
+ private var mAction: String? = null
+ private var mTargetFilePaths: ArrayList? = null
+
@Inject
lateinit var localBroadcastManager: LocalBroadcastManager
@@ -95,8 +99,9 @@ open class FolderPickerActivity :
View.VISIBLE
findViewById(R.id.switch_grid_view_button).visibility =
View.GONE
- if (intent.getStringExtra(EXTRA_ACTION) != null) {
- when (intent.getStringExtra(EXTRA_ACTION)) {
+ mAction = intent.getStringExtra(EXTRA_ACTION)
+ if (mAction != null) {
+ when (mAction) {
MOVE -> {
caption = resources.getText(R.string.move_to).toString()
mSearchOnlyFolders = true
@@ -118,9 +123,8 @@ open class FolderPickerActivity :
} else {
caption = themeUtils.getDefaultDisplayNameForRootFolder(this)
}
- if (intent.getParcelableExtra(EXTRA_CURRENT_FOLDER) != null) {
- file = intent.getParcelableExtra(EXTRA_CURRENT_FOLDER)
- }
+ mTargetFilePaths = intent.getStringArrayListExtra(EXTRA_FILE_PATHS)
+
if (savedInstanceState == null) {
createFragments()
}
@@ -146,7 +150,7 @@ open class FolderPickerActivity :
val listOfFolders = listOfFilesFragment
listOfFolders!!.listDirectory(folder, false, false)
startSyncFolderOperation(folder, false)
- updateNavigationElementsInActionBar()
+ updateUiElements()
}
}
@@ -205,7 +209,7 @@ open class FolderPickerActivity :
*/
override fun onBrowsedDownTo(directory: OCFile) {
file = directory
- updateNavigationElementsInActionBar()
+ updateUiElements()
// Sync Folder
startSyncFolderOperation(directory, false)
}
@@ -241,6 +245,9 @@ open class FolderPickerActivity :
// refresh list of files
refreshListOfFilesFragment(false)
+ file = listOfFilesFragment?.currentFile
+ updateUiElements()
+
// Listen for sync messages
val syncIntentFilter = IntentFilter(FileSyncAdapter.EVENT_FULL_SYNC_START)
syncIntentFilter.addAction(FileSyncAdapter.EVENT_FULL_SYNC_END)
@@ -317,7 +324,7 @@ open class FolderPickerActivity :
val root = storageManager.getFileByPath(OCFile.ROOT_PATH)
listOfFiles.listDirectory(root, false, false)
file = listOfFiles.currentFile
- updateNavigationElementsInActionBar()
+ updateUiElements()
startSyncFolderOperation(root, false)
}
}
@@ -331,7 +338,30 @@ open class FolderPickerActivity :
return
}
file = listOfFiles.currentFile
- updateNavigationElementsInActionBar()
+ updateUiElements()
+ }
+ }
+
+ private fun updateUiElements() {
+ toggleChooseEnabled()
+ updateNavigationElementsInActionBar()
+ }
+
+ private fun toggleChooseEnabled() {
+ mChooseBtn?.isEnabled = checkFolderSelectable()
+ }
+
+ // for copy and move, disable selecting parent folder of target files
+ private fun checkFolderSelectable(): Boolean {
+ return when {
+ mAction != COPY && mAction != MOVE -> true
+ mTargetFilePaths.isNullOrEmpty() -> true
+ file?.isFolder != true -> true
+ // all of the target files are already in the selected directory
+ mTargetFilePaths!!.all { PathUtils.isDirectParent(file.remotePath, it) } -> false
+ // some of the target files are parents of the selected folder
+ mTargetFilePaths!!.any { PathUtils.isAncestor(it, file.remotePath) } -> false
+ else -> true
}
}
@@ -378,9 +408,8 @@ open class FolderPickerActivity :
if (targetFiles != null) {
resultData.putParcelableArrayListExtra(EXTRA_FILES, targetFiles)
}
- val targetFilePaths = i.getStringArrayListExtra(EXTRA_FILE_PATHS)
- if (targetFilePaths != null) {
- resultData.putStringArrayListExtra(EXTRA_FILE_PATHS, targetFilePaths)
+ mTargetFilePaths.let {
+ resultData.putStringArrayListExtra(EXTRA_FILE_PATHS, it)
}
setResult(RESULT_OK, resultData)
finish()
@@ -560,9 +589,6 @@ open class FolderPickerActivity :
@JvmField
val EXTRA_ACTION = FolderPickerActivity::class.java.canonicalName?.plus(".EXTRA_ACTION")
- @JvmField
- val EXTRA_CURRENT_FOLDER = FolderPickerActivity::class.java.canonicalName?.plus(".EXTRA_CURRENT_FOLDER")
-
const val MOVE = "MOVE"
const val COPY = "COPY"
const val CHOOSE_LOCATION = "CHOOSE_LOCATION"
diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java b/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java
index 438027b82d..42d3a4d450 100644
--- a/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java
+++ b/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java
@@ -1227,7 +1227,6 @@ public class OCFileListFragment extends ExtendedListFragment implements
paths.add(file.getRemotePath());
}
action.putStringArrayListExtra(FolderPickerActivity.EXTRA_FILE_PATHS, paths);
- action.putExtra(FolderPickerActivity.EXTRA_CURRENT_FOLDER, mFile);
action.putExtra(FolderPickerActivity.EXTRA_ACTION, extraAction);
getActivity().startActivityForResult(action, requestCode);
}
diff --git a/app/src/main/java/com/owncloud/android/utils/PathUtils.kt b/app/src/main/java/com/owncloud/android/utils/PathUtils.kt
new file mode 100644
index 0000000000..342d893abb
--- /dev/null
+++ b/app/src/main/java/com/owncloud/android/utils/PathUtils.kt
@@ -0,0 +1,49 @@
+/*
+ * Nextcloud Android client application
+ *
+ * @author Álvaro Brey
+ * Copyright (C) 2022 Álvaro Brey
+ * Copyright (C) 2022 Nextcloud GmbH
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License along with this program. If not, see .
+ *
+ */
+
+package com.owncloud.android.utils
+
+import com.owncloud.android.datamodel.OCFile
+import java.io.File
+
+object PathUtils {
+ /**
+ * Returns `true` if [folderPath] is a direct parent of [filePath], `false` otherwise
+ */
+ fun isDirectParent(folderPath: String, filePath: String): Boolean {
+ return File(folderPath).path == File(filePath).parent
+ }
+
+ /**
+ * Returns `true` if [folderPath] is an ancestor of [filePath], `false` otherwise
+ *
+ * If [isDirectParent] is `true` for the same arguments, this function should return `true` as well
+ */
+ fun isAncestor(folderPath: String, filePath: String): Boolean {
+ if (folderPath.isEmpty() || filePath.isEmpty()) {
+ return false
+ }
+ val folderPathWithSlash =
+ if (folderPath.endsWith(OCFile.PATH_SEPARATOR)) folderPath else folderPath + OCFile.PATH_SEPARATOR
+ return filePath.startsWith(folderPathWithSlash)
+ }
+}
diff --git a/app/src/main/res/layout/community_layout.xml b/app/src/main/res/layout/community_layout.xml
index 880adb62aa..9e1ab9bcda 100755
--- a/app/src/main/res/layout/community_layout.xml
+++ b/app/src/main/res/layout/community_layout.xml
@@ -69,15 +69,6 @@
android:theme="@style/Button.Primary"
app:cornerRadius="@dimen/button_corner_radius" />
-
-
Fehler gefunden? Komisches Verhalten?
Helfen Sie durch Testen
Fehlerbericht auf GitHub erstellen
- Möchten Sie uns beim Testen der nächsten Version unterstützen?
+ Helfen Sie uns beim Testen der nächsten Version!
Konfigurieren
Lokale Verschlüsselung entfernen
Wollen Sie %1$s wirklich löschen?
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 840f042967..e4ad6665ca 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -523,7 +523,6 @@
Help by testing
Found a bug? Oddments?
Report an issue on GitHub
- Interested in helping out by testing what will be the next version?
Test the dev version
This includes all upcoming features and it is on the very bleeding edge. Bugs/errors can occur, if and when they do, please report of your findings.
Release candidate
diff --git a/app/src/test/java/com/owncloud/android/utils/PathUtilsTest.kt b/app/src/test/java/com/owncloud/android/utils/PathUtilsTest.kt
new file mode 100644
index 0000000000..f83eaadc42
--- /dev/null
+++ b/app/src/test/java/com/owncloud/android/utils/PathUtilsTest.kt
@@ -0,0 +1,114 @@
+/*
+ * Nextcloud Android client application
+ *
+ * @author Álvaro Brey
+ * Copyright (C) 2022 Álvaro Brey
+ * Copyright (C) 2022 Nextcloud GmbH
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License along with this program. If not, see .
+ *
+ */
+
+package com.owncloud.android.utils
+
+import org.junit.Assert.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import org.junit.runners.Suite
+
+private val directParents: Array> = arrayOf(
+ arrayOf("/bar", "/bar/foo.tgz", true),
+ arrayOf("/bar/", "/bar/foo.tgz", true),
+ arrayOf("/bar/", "/bar/foo/", true),
+ arrayOf("/bar/", "/bar/foo", true),
+ arrayOf("/", "/bar/", true),
+ arrayOf("/bar/", "/foo/bar", false)
+)
+
+private val nonAncestors: Array> = arrayOf(
+ arrayOf("/bar/", "/", false),
+ arrayOf("/bar/", "", false),
+ arrayOf("/", "", false),
+ arrayOf("", "", false),
+ arrayOf("", "/", false)
+)
+
+/**
+ * These should return `false` for [PathUtils.isDirectParent] but `true` for [PathUtils.isAncestor]
+ */
+private val indirectAncestors: List> = listOf(
+ Pair("/bar", "/bar/foo/baz.tgz"),
+ Pair("/bar/", "/bar/foo/baz.tgz"),
+ Pair("/bar/", "/bar/foo/baz/"),
+ Pair("/bar/", "/bar/foo/baz")
+)
+
+@RunWith(Suite::class)
+@Suite.SuiteClasses(
+ PathUtilsTest.IsDirectParent::class,
+ PathUtilsTest.IsAncestor::class
+)
+class PathUtilsTest {
+
+ @RunWith(Parameterized::class)
+ internal class IsDirectParent(
+ private val folderPath: String,
+ private val filePath: String,
+ private val isParent: Boolean
+ ) {
+
+ @Test
+ fun testIsParent() {
+ assertEquals("Wrong isParentPath result", isParent, PathUtils.isDirectParent(folderPath, filePath))
+ }
+
+ companion object {
+ @Parameterized.Parameters(name = "{0}, {1} => {2}")
+ @JvmStatic
+ fun urls(): Array> {
+ val otherAncestors: Array> = indirectAncestors.map {
+ @Suppress("UNCHECKED_CAST")
+ arrayOf(it.first, it.second, false) as Array
+ }.toTypedArray()
+ return directParents + nonAncestors + otherAncestors
+ }
+ }
+ }
+
+ @RunWith(Parameterized::class)
+ internal class IsAncestor(
+ private val folderPath: String,
+ private val filePath: String,
+ private val isAscendant: Boolean
+ ) {
+
+ @Test
+ fun testIsAncestor() {
+ assertEquals("Wrong isParentPath result", isAscendant, PathUtils.isAncestor(folderPath, filePath))
+ }
+
+ companion object {
+ @Parameterized.Parameters(name = "{0}, {1} => {2}")
+ @JvmStatic
+ fun urls(): Array> {
+ val otherAncestors: Array> = indirectAncestors.map {
+ @Suppress("UNCHECKED_CAST")
+ arrayOf(it.first, it.second, true) as Array
+ }.toTypedArray()
+ return directParents + nonAncestors + otherAncestors
+ }
+ }
+ }
+}