mirror of
https://github.com/nextcloud/android.git
synced 2024-12-20 16:02:01 +03:00
Merge remote-tracking branch 'origin/master' into dev
This commit is contained in:
commit
fc92c734e7
25 changed files with 519 additions and 432 deletions
4
.github/workflows/analysis.yml
vendored
4
.github/workflows/analysis.yml
vendored
|
@ -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
|
||||
|
|
4
.github/workflows/assembleFlavors.yml
vendored
4
.github/workflows/assembleFlavors.yml
vendored
|
@ -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
|
||||
|
|
2
.github/workflows/autoApproveDependabot.yml
vendored
2
.github/workflows/autoApproveDependabot.yml
vendored
|
@ -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 }}"
|
||||
|
|
4
.github/workflows/check.yml
vendored
4
.github/workflows/check.yml
vendored
|
@ -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
|
||||
|
|
44
.github/workflows/codeql.yml
vendored
44
.github/workflows/codeql.yml
vendored
|
@ -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
|
||||
|
|
40
.github/workflows/detectNewJavaFiles.yml
vendored
40
.github/workflows/detectNewJavaFiles.yml
vendored
|
@ -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
|
||||
|
|
2
.github/workflows/detectSnapshot.yml
vendored
2
.github/workflows/detectSnapshot.yml
vendored
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
4
.github/workflows/qa.yml
vendored
4
.github/workflows/qa.yml
vendored
|
@ -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"
|
||||
|
|
17
.github/workflows/screenShotTest.yml
vendored
17
.github/workflows/screenShotTest.yml
vendored
|
@ -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() }}
|
||||
|
|
4
.github/workflows/stale.yml
vendored
4
.github/workflows/stale.yml
vendored
|
@ -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
|
||||
|
|
13
.github/workflows/unit-tests.yml
vendored
13
.github/workflows/unit-tests.yml
vendored
|
@ -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
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 80 KiB After Width: | Height: | Size: 75 KiB |
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
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<FileEntity>
|
||||
|
||||
@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<FileEntity>
|
||||
|
||||
@Query("SELECT * FROM filelist WHERE file_owner = :fileOwner ORDER BY ${ProviderTableMeta.FILE_DEFAULT_SORT_ORDER}")
|
||||
fun getAllFiles(fileOwner: String): List<FileEntity>
|
||||
|
||||
@Query("SELECT * FROM filelist WHERE path LIKE :pathPattern AND file_owner = :fileOwner ORDER BY path ASC")
|
||||
fun getFolderWithDescendants(pathPattern: String, fileOwner: String): List<FileEntity>
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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<FileEntity> fileEntities =
|
||||
fileDao.getFolderWithDescendants(oldPath + "%", user.getAccountName());
|
||||
|
||||
/// 2. prepare a batch of update operations to change all the descendants
|
||||
ArrayList<ContentProviderOperation> operations = new ArrayList<>(cursor.getCount());
|
||||
ArrayList<ContentProviderOperation> operations = new ArrayList<>(fileEntities.size());
|
||||
String defaultSavePath = FileStorageUtils.getSavePath(user.getAccountName());
|
||||
List<String> originalPathsToTriggerMediaScan = new ArrayList<>();
|
||||
List<String> 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<OCFile> getFolderContent(long parentId, boolean onlyOnDevice) {
|
||||
Log_OC.d(TAG, "getFolderContent - start");
|
||||
List<OCFile> 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<FileEntity> 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<OCFile> getGalleryItems(long startDate, long endDate) {
|
||||
List<OCFile> files = new ArrayList<>();
|
||||
Log_OC.d(TAG, "getGalleryItems - start: " + startDate + ", " + endDate);
|
||||
|
||||
Uri requestURI = ProviderTableMeta.CONTENT_URI;
|
||||
Cursor cursor;
|
||||
List<FileEntity> 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<OCFile> 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<OCFile> getAllFiles() {
|
||||
String selection = ProviderTableMeta.FILE_ACCOUNT_OWNER + "= ? ";
|
||||
String[] selectionArgs = new String[]{user.getAccountName()};
|
||||
// TODO - Apparently this method is used only by tests
|
||||
List<FileEntity> fileEntities = fileDao.getAllFiles(user.getAccountName());
|
||||
List<OCFile> folderContent = new ArrayList<>(fileEntities.size());
|
||||
|
||||
List<OCFile> 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;
|
||||
|
|
|
@ -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<String>? = null
|
||||
|
||||
@Inject
|
||||
lateinit var localBroadcastManager: LocalBroadcastManager
|
||||
|
||||
|
@ -95,8 +99,9 @@ open class FolderPickerActivity :
|
|||
View.VISIBLE
|
||||
findViewById<View>(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<Parcelable?>(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"
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
49
app/src/main/java/com/owncloud/android/utils/PathUtils.kt
Normal file
49
app/src/main/java/com/owncloud/android/utils/PathUtils.kt
Normal file
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
|
@ -69,15 +69,6 @@
|
|||
android:theme="@style/Button.Primary"
|
||||
app:cornerRadius="@dimen/button_corner_radius" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/community_text"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingBottom="@dimen/standard_half_padding"
|
||||
android:paddingTop="@dimen/standard_half_padding"
|
||||
android:text="@string/community_testing_version_text"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/community_contribute_headline"
|
||||
android:layout_width="wrap_content"
|
||||
|
|
|
@ -145,7 +145,7 @@
|
|||
<string name="community_testing_bug_text">Fehler gefunden? Komisches Verhalten?</string>
|
||||
<string name="community_testing_headline">Helfen Sie durch Testen</string>
|
||||
<string name="community_testing_report_text">Fehlerbericht auf GitHub erstellen</string>
|
||||
<string name="community_testing_version_text">Möchten Sie uns beim Testen der nächsten Version unterstützen?</string>
|
||||
<string name="community_testing_version_text">Helfen Sie uns beim Testen der nächsten Version!</string>
|
||||
<string name="configure_new_media_folder_detection_notifications">Konfigurieren</string>
|
||||
<string name="confirm_removal">Lokale Verschlüsselung entfernen</string>
|
||||
<string name="confirmation_remove_file_alert">Wollen Sie %1$s wirklich löschen?</string>
|
||||
|
|
|
@ -523,7 +523,6 @@
|
|||
<string name="community_testing_headline">Help by testing</string>
|
||||
<string name="community_testing_bug_text">Found a bug? Oddments?</string>
|
||||
<string name="community_testing_report_text">Report an issue on GitHub</string>
|
||||
<string name="community_testing_version_text">Interested in helping out by testing what will be the next version?</string>
|
||||
<string name="community_beta_headline">Test the dev version</string>
|
||||
<string name="community_beta_text">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.</string>
|
||||
<string name="community_release_candidate_headline">Release candidate</string>
|
||||
|
|
114
app/src/test/java/com/owncloud/android/utils/PathUtilsTest.kt
Normal file
114
app/src/test/java/com/owncloud/android/utils/PathUtilsTest.kt
Normal file
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
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<Array<Any>> = 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<Array<Any>> = 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<Pair<String, String>> = 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<Array<Any>> {
|
||||
val otherAncestors: Array<Array<Any>> = indirectAncestors.map {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
arrayOf(it.first, it.second, false) as Array<Any>
|
||||
}.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<Array<Any>> {
|
||||
val otherAncestors: Array<Array<Any>> = indirectAncestors.map {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
arrayOf(it.first, it.second, true) as Array<Any>
|
||||
}.toTypedArray()
|
||||
return directParents + nonAncestors + otherAncestors
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue