diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000000..75daf9c59e --- /dev/null +++ b/.editorconfig @@ -0,0 +1,7 @@ +root = true + +[*.{kt,kts}] +ktlint_standard_property-naming = disabled +ktlint_standard_value-argument-comment = disabled +ktlint_standard_kdoc-wrapping = disabled +ktlint_standard_no-consecutive-comments = disabled diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 0000000000..65ceffd6ab --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,9 @@ +# Run this command to always ignore specific commits in `git blame` +# git config blame.ignoreRevsFile .git-blame-ignore-revs + +# format all java files +94dae8259f075e4099cd92c258386dc39bfc30c1 + +# format all kotlin files +45178b3d348cd1fe768bcfc086b04b15ea5d69ea + diff --git a/.github/workflows/android-build.yml b/.github/workflows/android-build.yml index 23c42fb88e..c304c37817 100644 --- a/.github/workflows/android-build.yml +++ b/.github/workflows/android-build.yml @@ -13,30 +13,30 @@ concurrency: jobs: check_spotless: name: Check spotless - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up JDK 11 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: "temurin" java-version: 11 - name: Check formatting using spotless - uses: gradle/gradle-build-action@v2.4.2 + uses: gradle/actions/setup-gradle@v3 with: arguments: spotlessCheck --stacktrace build: name: Build debug - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up JDK 11 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: "temurin" java-version: 11 - name: Build with Gradle - uses: gradle/gradle-build-action@v2.4.2 + uses: gradle/actions/setup-gradle@v3 with: arguments: assembledebug --stacktrace \ No newline at end of file diff --git a/.github/workflows/android-debug-artifact-ondemand.yml b/.github/workflows/android-debug-artifact-ondemand.yml index 92601c5eaa..d3f4360288 100644 --- a/.github/workflows/android-debug-artifact-ondemand.yml +++ b/.github/workflows/android-debug-artifact-ondemand.yml @@ -8,7 +8,7 @@ on: jobs: apk: - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest if: github.event.comment.body == 'Build test apk' && (github.actor == 'VishalNehra' || github.actor == 'TranceLove' || github.actor == 'EmmanuelMess' || github.actor == 'VishnuSanal') steps: - name: Acknowledge the request with thumbs up reaction @@ -30,27 +30,27 @@ jobs: echo "::set-output name=repo_clone_url::${{ fromJson(steps.request.outputs.data).head.repo.clone_url }}" echo "::set-output name=repo_ssh_url::${{ fromJson(steps.request.outputs.data).head.repo.ssh_url }}" - name: Checkout PR Branch - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: token: ${{ secrets.GITHUB_TOKEN }} repository: ${{fromJson(steps.request.outputs.data).head.repo.full_name}} ref: ${{fromJson(steps.request.outputs.data).head.ref}} - name: Set up JDK 11 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: "temurin" java-version: 11 - name: Build with Gradle - uses: gradle/gradle-build-action@v2.4.2 + uses: gradle/actions/setup-gradle@v3 with: arguments: assembleDebug --stacktrace - name: Upload fdroid artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: Amaze-Fdroid-debug path: app/build/outputs/apk/fdroid/debug/app-fdroid-debug.apk - name: Upload play artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: Amaze-Play-debug path: app/build/outputs/apk/play/debug/app-play-debug.apk diff --git a/.github/workflows/android-debug-artifact-release.yml b/.github/workflows/android-debug-artifact-release.yml index 14604bfd2e..56cfdbf0d1 100644 --- a/.github/workflows/android-debug-artifact-release.yml +++ b/.github/workflows/android-debug-artifact-release.yml @@ -6,25 +6,25 @@ on: jobs: apk: - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up JDK 11 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: "temurin" java-version: 11 - name: Build with Gradle - uses: gradle/gradle-build-action@v2.4.2 + uses: gradle/actions/setup-gradle@v3 with: arguments: assembleDebug - name: Upload fdroid artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: Amaze-Fdroid-debug path: app/build/outputs/apk/fdroid/debug/app-fdroid-debug.apk - name: Upload play artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: Amaze-Play-debug path: app/build/outputs/apk/play/debug/app-play-debug.apk diff --git a/.github/workflows/android-feature.yml b/.github/workflows/android-feature.yml index 3eeb27438f..a551a2ebec 100644 --- a/.github/workflows/android-feature.yml +++ b/.github/workflows/android-feature.yml @@ -14,34 +14,34 @@ concurrency: jobs: check_spotless: name: Check spotless - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up JDK 11 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: "temurin" java-version: 11 - name: Check formatting using spotless - uses: gradle/gradle-build-action@v2.4.2 + uses: gradle/actions/setup-gradle@v3 with: arguments: spotlessCheck --stacktrace build: name: Build debug and run Jacoco tests - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up JDK 11 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: "temurin" java-version: 11 - name: Build with Gradle - uses: gradle/gradle-build-action@v2.4.2 + uses: gradle/actions/setup-gradle@v3 with: arguments: assembledebug --stacktrace - name: Run test cases - uses: gradle/gradle-build-action@v2.4.2 + uses: gradle/actions/setup-gradle@v3 with: arguments: jacocoTestPlayDebugUnitTestReport --stacktrace --info \ No newline at end of file diff --git a/.github/workflows/android-main.yml b/.github/workflows/android-main.yml index 5d15d1c254..331d75846e 100644 --- a/.github/workflows/android-main.yml +++ b/.github/workflows/android-main.yml @@ -14,37 +14,37 @@ concurrency: jobs: check_spotless: name: Check spotless - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up JDK 11 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: "temurin" java-version: 11 - name: Check formatting using spotless - uses: gradle/gradle-build-action@v2.4.2 + uses: gradle/actions/setup-gradle@v3 with: arguments: spotlessCheck build: name: Build debug, Jacoco test and publish to codacy - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Set up JDK 11 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: "temurin" java-version: 11 - name: Build with Gradle - uses: gradle/gradle-build-action@v2.4.2 + uses: gradle/actions/setup-gradle@v3 with: arguments: assembledebug - name: Run test cases - uses: gradle/gradle-build-action@v2.4.2 + uses: gradle/actions/setup-gradle@v3 with: arguments: jacocoTestPlayDebugUnitTestReport - name: Publish test cases @@ -70,15 +70,15 @@ jobs: api-level: [ 16, 19, 28 ] steps: - name: checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Java 15 - uses: actions/setup-java@v1 + uses: actions/setup-java@v4 with: java-version: 15 - name: Gradle cache - uses: gradle/gradle-build-action@v2.4.2 + uses: gradle/actions/setup-gradle@v3 - name: AVD cache - uses: actions/cache@v2 + uses: actions/cache@v4 id: avd-cache with: path: | diff --git a/README.md b/README.md index 6b18d24f5c..efca68c754 100644 --- a/README.md +++ b/README.md @@ -85,9 +85,10 @@ We strongly recommend using apk signed by us (either Play Store version or from ### License: Copyright (C) 2014-2018 Arpit Khurana - Copyright (C) 2014-2021 Vishal Nehra - Copyright (C) 2017-2021 Emmanuel Messulam - Copyright (C) 2018-2021 Raymond Lai + Copyright (C) 2014-2024 Vishal Nehra + Copyright (C) 2017-2024 Emmanuel Messulam + Copyright (C) 2018-2024 Raymond Lai + Copyright (C) 2019-2024 Vishnu Sanal T This file is part of Amaze File Manager. Amaze File Manager is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000000..c73ba27988 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,20 @@ +# Security Policy + +## Supported Versions + +`v3.8.5` supports Android 4.0 and above. +`v4.x.x` would only support Android 4.4 and above. + +Android devices that runs Android versions less that Android 4.4 +(Android KitKat) would not receive any more updates (the latest +supported version would be `v3.8.5`). + +## Reporting a Vulnerability + +Feel free to contact us via `support@teamamaze.xyz`. + +Please CC the maintainers too: +- `vishalmeham2@gmail.com` +- `airwave209gt@gmail.com` +- `emmanuelbendavid@gmail.com` +- `t.v.s10123@gmail.com` \ No newline at end of file diff --git a/spotless.license-java b/amaze.license.spotless.txt similarity index 100% rename from spotless.license-java rename to amaze.license.spotless.txt diff --git a/app/build.gradle b/app/build.gradle index 34cd3e4b97..b7d0ce2d23 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -4,9 +4,11 @@ apply plugin: 'kotlin-kapt' apply plugin: 'kotlin-parcelize' apply plugin: 'com.hiya.jacoco-android' apply plugin: "com.starter.easylauncher" +apply plugin: 'com.google.devtools.ksp' android { - compileSdkVersion 31 + namespace "com.amaze.filemanager" + compileSdk libs.versions.compileSdk.get().toInteger() packagingOptions { resources { excludes += ['proguard-project.txt', 'project.properties', 'META-INF/LICENSE.txt', 'META-INF/LICENSE', 'META-INF/NOTICE.txt', 'META-INF/NOTICE', 'META-INF/DEPENDENCIES.txt', 'META-INF/DEPENDENCIES'] @@ -16,10 +18,10 @@ android { defaultConfig { applicationId "com.amaze.filemanager" - minSdkVersion 19 - targetSdkVersion 31 - versionCode 118 - versionName "3.8.5" + minSdkVersion libs.versions.minSdk.get().toInteger() + targetSdkVersion libs.versions.targetSdk.get().toInteger() + versionCode 121 + versionName "3.10" multiDexEnabled true vectorDrawables.useSupportLibrary = true @@ -48,6 +50,7 @@ android { buildConfigField "String", "CRYPTO_IV", "\"LxbHiJhhUXcj\"" buildConfigField "String", "FTP_SERVER_KEYSTORE_PASSWORD", "\"vishal007\"" debuggable true //For "debug" banner on icon + enableUnitTestCoverage true } release { @@ -62,10 +65,10 @@ android { buildFeatures { viewBinding true + buildConfig true } - - flavorDimensions 'static' + flavorDimensions = ['static'] productFlavors { fdroid { @@ -80,18 +83,24 @@ android { } } + dependenciesInfo { + // Disables dependency metadata when building APKs. + includeInApk = false + // Disables dependency metadata when building Android App Bundles. + includeInBundle = false + } + sourceSets { test.java.srcDirs += '../testShared/src/test/java' } compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 + sourceCompatibility JavaVersion.VERSION_11 + targetCompatibility JavaVersion.VERSION_11 } kotlinOptions { - jvmTarget = '1.8' - useIR = true + jvmTarget = '11' } testOptions { @@ -108,171 +117,171 @@ android { dependencies { modules { + module("org.bouncycastle:bcprov-jdk15on") { + replacedBy("org.bouncycastle:bcprov-jdk18on") + } + module("org.bouncycastle:bcpkix-jdk15on") { + replacedBy("org.bouncycastle:bcpkix-jdk18on") + } module("org.bouncycastle:bcprov-jdk15to18") { - replacedBy("org.bouncycastle:bcprov-jdk15on") + replacedBy("org.bouncycastle:bcprov-jdk18on") } module("org.bouncycastle:bcpkix-jdk15to18") { - replacedBy("org.bouncycastle:bcpkix-jdk15on") + replacedBy("org.bouncycastle:bcpkix-jdk18on") } } implementation fileTree(include: ['*.jar'], dir: 'libs') - implementation 'com.readystatesoftware.systembartint:systembartint:1.0.3' - - implementation 'androidx.vectordrawable:vectordrawable-animated:1.1.0' - implementation 'androidx.legacy:legacy-support-v13:1.0.0' - implementation "com.google.android.material:material:$androidMaterialVersion" - implementation "androidx.fragment:fragment-ktx:$androidXFragmentVersion" - implementation "androidx.appcompat:appcompat:$androidXAppCompatVersion" - implementation 'androidx.palette:palette-ktx:1.0.0' - implementation 'androidx.cardview:cardview:1.0.0' - implementation "androidx.room:room-runtime:$roomVersion" - playImplementation "com.android.billingclient:billing:$androidBillingVersion" - kapt "androidx.room:room-compiler:$roomVersion" - // RxJava support for Room - implementation "androidx.room:room-rxjava2:$roomVersion" - kapt "androidx.annotation:annotation:$androidXAnnotationVersion" - - implementation "androidx.preference:preference:$androidXPrefVersion" - implementation "androidx.preference:preference-ktx:$androidXPrefVersion" + implementation libs.systembarTint + + implementation libs.androidX.vectordrawable.animated + implementation libs.androidX.legacySupportV13 + implementation libs.androidX.material + implementation libs.androidX.fragment + implementation libs.androidX.appcompat + implementation libs.androidX.preference + implementation libs.androidX.core + implementation libs.androidX.palette + implementation libs.androidX.cardview + implementation libs.androidX.constraintLayout + implementation libs.androidX.multidex //Multiple dex files + implementation libs.room.runtime + implementation libs.room.rxjava2 + + ksp libs.room.compiler + ksp libs.androidX.annotation //For tests - testImplementation "junit:junit:$junitVersion"//tests the app logic - testImplementation "org.robolectric:robolectric:$robolectricVersion"//tests android interaction - testImplementation "org.robolectric:shadows-httpclient:$robolectricVersion"//tests android interaction - testImplementation "androidx.test:core:$androidXTestVersion" - testImplementation "androidx.test:runner:$androidXTestVersion" - testImplementation "androidx.test:rules:$androidXTestVersion" - testImplementation "androidx.test.ext:junit:$androidXTestExtVersion" - testImplementation "org.mockito:mockito-core:$mockitoVersion" - testImplementation "org.mockito:mockito-inline:$mockitoVersion" - testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion" - testImplementation "org.apache.sshd:sshd-core:1.7.0" - testImplementation "org.awaitility:awaitility:$awaitilityVersion" - testImplementation "org.jsoup:jsoup:$jsoupVersion" - testImplementation "androidx.room:room-migration:$roomVersion" - testImplementation "io.mockk:mockk:$mockkVersion" - kaptTest "com.google.auto.service:auto-service:1.0-rc4" - - androidTestImplementation "junit:junit:$junitVersion"//tests the app logic - androidTestImplementation "androidx.test.espresso:espresso-core:$espressoVersion" - androidTestImplementation "androidx.test:core:$androidXTestVersion" - androidTestImplementation "androidx.test:runner:$androidXTestVersion" - androidTestImplementation "androidx.test:rules:$androidXTestVersion" - androidTestImplementation "androidx.test.ext:junit:$androidXTestExtVersion" - // androidTestImplementation "androidx.test.uiautomator:uiautomator:$uiAutomatorVersion" - androidTestImplementation "commons-net:commons-net:$commonsNetVersion" - androidTestImplementation "org.awaitility:awaitility:$awaitilityVersion" + testImplementation libs.junit//tests the app logic + testImplementation libs.robolectric//tests android interaction + testImplementation libs.robolectric.shadows.httpclient//tests android interaction + testImplementation libs.androidX.test.core + testImplementation libs.androidX.test.runner + testImplementation libs.androidX.test.rules + testImplementation libs.androidX.test.ext.junit + testImplementation libs.androidX.fragment.testing + testImplementation libs.mockito.core + testImplementation libs.mockito.inline + testImplementation libs.mockito.kotlin + testImplementation libs.apache.sshd + testImplementation libs.awaitility + testImplementation libs.jsoup + testImplementation libs.room.migration + testImplementation libs.mockk + testImplementation libs.kotlin.coroutine.test + testImplementation libs.androidX.core.testing + kspTest libs.auto.service + testImplementation 'ch.qos.logback:logback-classic:1.4.14' + + androidTestImplementation libs.junit //tests the app logic + androidTestImplementation libs.androidX.test.expresso + androidTestImplementation libs.androidX.test.core + androidTestImplementation libs.androidX.test.runner + androidTestImplementation libs.androidX.test.rules + androidTestImplementation libs.androidX.test.ext.junit + androidTestImplementation libs.androidX.test.uiautomator + androidTestImplementation libs.androidX.fragment.testing + androidTestImplementation libs.commons.net + androidTestImplementation libs.awaitility //Detect memory leaks - debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.7' - - implementation "org.apache.commons:commons-compress:$commonsCompressVersion" - - playImplementation "com.github.junrar:junrar:$junrarVersion" - - implementation "com.afollestad.material-dialogs:core:$materialDialogsVersion" - implementation "com.afollestad.material-dialogs:commons:$materialDialogsVersion" - - // https://mvnrepository.com/artifact/org.apache.mina/mina-core - implementation group: 'org.apache.mina', name: 'mina-core', version: '2.0.16' + debugImplementation libs.leakcanary.android - //implementation files('libs/ftplet-api-1.1.0-SNAPSHOT.jar') - // https://mvnrepository.com/artifact/org.apache.ftpserver/ftplet-api - implementation "org.apache.ftpserver:ftplet-api:$ftpserverVersion" + implementation libs.commons.compress - //implementation files('libs/ftpserver-core-1.1.0-SNAPSHOT.jar') - // https://mvnrepository.com/artifact/org.apache.ftpserver/ftpserver-core - implementation "org.apache.ftpserver:ftpserver-core:$ftpserverVersion" + implementation libs.materialdialogs.core + implementation libs.materialdialogs.commons - implementation 'org.greenrobot:eventbus:3.3.1' + implementation libs.apache.mina.core + implementation libs.apache.ftpserver.ftplet.api + implementation libs.apache.ftpserver.core - implementation 'com.android.volley:volley:1.2.1' + implementation libs.eventbus - implementation "com.github.topjohnwu.libsu:core:${libsuVersion}" - implementation "com.github.topjohnwu.libsu:io:${libsuVersion}" + implementation libs.libsu.core + implementation libs.libsu.io - playImplementation 'com.cloudrail:cloudrail-si-android:2.22.4' + playImplementation libs.cloudrail.si.android + playImplementation libs.junrar + playImplementation libs.google.play.billing - implementation 'com.github.PhilJay:MPAndroidChart:v3.0.2'//Nice charts and graphs + implementation libs.mpAndroidChart//Nice charts and graphs - implementation 'com.github.npgall:concurrent-trees:2.6.1'//Concurrent tries + implementation libs.concurrent.trees//Concurrent tries //SFTP - implementation "com.hierynomus:sshj:$sshjVersion" - + implementation libs.sshj //smb - implementation "eu.agno3.jcifs:jcifs-ng:$jcifsVersion" - + implementation libs.jcifs.ng //FTP - implementation "commons-net:commons-net:$commonsNetVersion" + implementation libs.commons.net + //OkHttp + implementation libs.okhttp - implementation "org.bouncycastle:bcpkix-jdk15on:$bouncyCastleVersion" - implementation "org.bouncycastle:bcprov-jdk15on:$bouncyCastleVersion" + implementation libs.bcpkix.jdk18on + implementation libs.bcprov.jdk18on //Glide: loads icons seemlessly - implementation "com.github.bumptech.glide:glide:$glideVersion" - implementation ("com.github.bumptech.glide:recyclerview-integration:$glideVersion") { + implementation libs.glide + implementation (libs.glide.recyclerView) { // Excludes the support library because it's already included by Glide. transitive = false } - kapt "com.github.bumptech.glide:compiler:$glideVersion" + ksp libs.glide.ksp - implementation "com.leinardi.android:speed-dial:$fabSpeedDialVersion" + implementation libs.speedDial //Simple library show - implementation('com.mikepenz:aboutlibraries:6.1.1@aar') { + implementation(libs.aboutLibraries) { transitive = true } - implementation 'androidx.multidex:multidex:2.0.1'//Multiple dex files - implementation 'androidx.constraintlayout:constraintlayout:1.1.3' - //zip4j: support password-protected zips - implementation "net.lingala.zip4j:zip4j:$zip4jVersion" + implementation libs.zip4j - implementation 'org.tukaani:xz:1.9' + implementation libs.xz - implementation 'io.reactivex.rxjava2:rxandroid:2.1.1' + implementation libs.rxandroid // Because RxAndroid releases are few and far between, it is recommended you also // explicitly depend on RxJava's latest version for bug fixes and new features. // (see https://github.com/ReactiveX/RxJava/releases for latest 3.x.x version) - implementation group: 'io.reactivex.rxjava2', name: 'rxjava', version: '2.2.9' + implementation libs.rxjava implementation project(':commons_compress_7z') implementation project(':file_operations') - implementation "androidx.core:core-ktx:$androidXCoreVersion" - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" - implementation "ch.acra:acra-core:5.7.0" - runtimeOnly 'org.slf4j:slf4j-api:1.7.36' - runtimeOnly "com.github.tony19:logback-android:$logbackAndroidVersion" + implementation project(':portscanner') - implementation 'com.google.code.gson:gson:2.9.1' + implementation libs.kotlin.stdlib.jdk8 + implementation libs.acra.core + implementation libs.slf4j.api + implementation libs.logback.android + testImplementation libs.logback.classic + + implementation libs.gson + implementation libs.amaze.trashBin +} +kotlin { + jvmToolchain(11) } -configurations.all { +configurations.configureEach { resolutionStrategy { dependencySubstitution { - substitute module("commons-logging:commons-logging-api:1.1") with module("commons-logging:commons-logging:1.1.1") - substitute module("com.android.support:support-annotations:27.1.1") with module("com.android.support:support-annotations:27.0.2") + substitute module("commons-logging:commons-logging-api:1.1") using module("commons-logging:commons-logging:1.1.1") + substitute module("com.android.support:support-annotations:27.1.1") using module("com.android.support:support-annotations:27.0.2") // These two lines are added to prevent possible class clashes between awaitility (which uses hamcrest 2.1) and junit (which uses hamcrest 1.3). - substitute module('org.hamcrest:hamcrest-core:1.3') with module("org.hamcrest:hamcrest:2.1") - substitute module('org.hamcrest:hamcrest-library:1.3') with module("org.hamcrest:hamcrest:2.1") + substitute module('org.hamcrest:hamcrest-core:1.3') using module("org.hamcrest:hamcrest:2.1") + substitute module('org.hamcrest:hamcrest-library:1.3') using module("org.hamcrest:hamcrest:2.1") } } } -configurations.all { config -> - // This workarounds logback's multiple bindings problem when running tests. - if (config.name.toLowerCase().contains('test')) { - config.resolutionStrategy.dependencySubstitution { - substitute module("com.github.tony19:logback-android:$logbackAndroidVersion") with module("ch.qos.logback:logback-classic:1.2.9") - } - } +configurations.testImplementation { + exclude module: 'logback-android' } -task supportOldLangCodes +tasks.register('supportOldLangCodes') [['id', 'in'], ['yi', 'ji'], ['he', 'iw']].forEach { sourceCode, destinationCode -> def copyTask = tasks.create('copyStrings' + sourceCode + 'Into' + destinationCode, Copy) { @@ -292,20 +301,22 @@ clean.dependsOn supportOldLangCodes clean.mustRunAfter supportOldLangCodes jacoco { - toolVersion = "${jacocoVersion}" + toolVersion = libs.versions.jacoco.get() } -jacocoAndroidUnitTestReport { - html.enabled true - xml.enabled true +tasks.withType(JacocoReport).configureEach { + reports { + csv.required.set(false) + html.required.set(true) + xml.required.set(true) + } } -tasks.withType(Test) { +tasks.withType(Test).configureEach { jacoco.includeNoLocationClasses = true jacoco.excludes = ['jdk.internal.*'] } - Properties props = new Properties() def propFile = new File('signing.properties') diff --git a/app/proguard.cfg b/app/proguard.cfg index 23298be7fc..19bb9c2bb5 100644 --- a/app/proguard.cfg +++ b/app/proguard.cfg @@ -117,4 +117,7 @@ #Keep constructors that involves an InputStream -keepclassmembers class * extends org.apache.commons.compress.compressors.CompressorInputStream { (java.io.InputStream); -} \ No newline at end of file +} + +-keep class com.amaze.trashbin.** { *; } +-dontwarn ch.qos.logback.core.net.* \ No newline at end of file diff --git a/app/src/androidTest/java/com/amaze/filemanager/asynchronous/services/ftp/FtpServiceEspressoTest.kt b/app/src/androidTest/java/com/amaze/filemanager/asynchronous/services/ftp/FtpServiceEspressoTest.kt index 3b7e4952c3..5083db921f 100644 --- a/app/src/androidTest/java/com/amaze/filemanager/asynchronous/services/ftp/FtpServiceEspressoTest.kt +++ b/app/src/androidTest/java/com/amaze/filemanager/asynchronous/services/ftp/FtpServiceEspressoTest.kt @@ -62,7 +62,6 @@ import java.util.concurrent.TimeUnit @Suppress("StringLiteralDuplication") @androidx.test.filters.Suppress class FtpServiceEspressoTest { - @get:Rule var serviceTestRule = ServiceTestRule() @@ -88,10 +87,11 @@ class FtpServiceEspressoTest { .remove(FtpService.KEY_PREFERENCE_USERNAME) .remove(FtpService.KEY_PREFERENCE_PASSWORD) .commit() - service = create( - Intent(FtpService.ACTION_START_FTPSERVER) - .putExtra(FtpService.TAG_STARTED_BY_TILE, false) - ) + service = + create( + Intent(FtpService.ACTION_START_FTPSERVER) + .putExtra(FtpService.TAG_STARTED_BY_TILE, false), + ) await().atMost(10, TimeUnit.SECONDS).until { FtpService.isRunning() && isServerReady() @@ -116,10 +116,11 @@ class FtpServiceEspressoTest { .remove(FtpService.KEY_PREFERENCE_USERNAME) .remove(FtpService.KEY_PREFERENCE_PASSWORD) .commit() - service = create( - Intent(FtpService.ACTION_START_FTPSERVER) - .putExtra(FtpService.TAG_STARTED_BY_TILE, false) - ) + service = + create( + Intent(FtpService.ACTION_START_FTPSERVER) + .putExtra(FtpService.TAG_STARTED_BY_TILE, false), + ) await().atMost(10, TimeUnit.SECONDS).until { FtpService.isRunning() && isServerReady() @@ -146,14 +147,15 @@ class FtpServiceEspressoTest { FtpService.KEY_PREFERENCE_PASSWORD, PasswordUtil.encryptPassword( ApplicationProvider.getApplicationContext(), - "passw0rD" - ) + "passw0rD", + ), ) .commit() - service = create( - Intent(FtpService.ACTION_START_FTPSERVER) - .putExtra(FtpService.TAG_STARTED_BY_TILE, false) - ) + service = + create( + Intent(FtpService.ACTION_START_FTPSERVER) + .putExtra(FtpService.TAG_STARTED_BY_TILE, false), + ) await().atMost(10, TimeUnit.SECONDS).until { FtpService.isRunning() && isServerReady() @@ -176,7 +178,7 @@ class FtpServiceEspressoTest { assertTrue( "No files found on device? It is also possible that app doesn't have " + "permission to access storage, which may occur on broken Android emulators", - files.isNotEmpty() + files.isNotEmpty(), ) var downloadFolderExists = false for (f in files) { @@ -187,7 +189,7 @@ class FtpServiceEspressoTest { assertTrue( "Download folder not found on device. Either storage is not available, " + "or something is really wrong with FtpService. Check logcat.", - downloadFolderExists + downloadFolderExists, ) } @@ -289,13 +291,14 @@ class FtpServiceEspressoTest { } private fun create(intent: Intent): FtpService { - val binder = serviceTestRule - .bindService( - intent.setClass( - ApplicationProvider.getApplicationContext(), - FtpService::class.java + val binder = + serviceTestRule + .bindService( + intent.setClass( + ApplicationProvider.getApplicationContext(), + FtpService::class.java, + ), ) - ) return ((binder as ObtainableServiceBinder).service as FtpService).also { it.onStartCommand(intent, 0, 0) } diff --git a/app/src/androidTest/java/com/amaze/filemanager/asynchronous/services/ftp/FtpServiceStaticMethodsTest.kt b/app/src/androidTest/java/com/amaze/filemanager/asynchronous/services/ftp/FtpServiceStaticMethodsTest.kt index 830e55fe91..85fc91332a 100644 --- a/app/src/androidTest/java/com/amaze/filemanager/asynchronous/services/ftp/FtpServiceStaticMethodsTest.kt +++ b/app/src/androidTest/java/com/amaze/filemanager/asynchronous/services/ftp/FtpServiceStaticMethodsTest.kt @@ -26,6 +26,7 @@ import android.os.Build.VERSION_CODES.N_MR1 import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.amaze.filemanager.utils.NetworkUtil import org.junit.Assert.assertNotNull import org.junit.Assert.fail import org.junit.Test @@ -41,7 +42,6 @@ import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) class FtpServiceStaticMethodsTest { - /** To test [FtpService.getLocalInetAddress] must not return an empty string. */ @Test fun testGetLocalInetAddressMustNotBeEmpty() { @@ -53,11 +53,11 @@ class FtpServiceStaticMethodsTest { */ if (SDK_INT >= N_MR1) { ApplicationProvider.getApplicationContext().run { - if (!FtpService.isConnectedToLocalNetwork(this)) { + if (!NetworkUtil.isConnectedToLocalNetwork(this)) { fail("Please connect your device to network to run this test!") } - FtpService.getLocalInetAddress(this).also { + NetworkUtil.getLocalInetAddress(this).also { assertNotNull(it) assertNotNull(it?.hostAddress) } diff --git a/app/src/androidTest/java/com/amaze/filemanager/database/UtilsHandlerTest.java b/app/src/androidTest/java/com/amaze/filemanager/database/UtilsHandlerTest.java index 9bc78d9883..b089ec9460 100644 --- a/app/src/androidTest/java/com/amaze/filemanager/database/UtilsHandlerTest.java +++ b/app/src/androidTest/java/com/amaze/filemanager/database/UtilsHandlerTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. diff --git a/app/src/androidTest/java/com/amaze/filemanager/filesystem/HybridFileParcelableTest.java b/app/src/androidTest/java/com/amaze/filemanager/filesystem/HybridFileParcelableTest.java index 4317c3a4c9..be1d8aef40 100644 --- a/app/src/androidTest/java/com/amaze/filemanager/filesystem/HybridFileParcelableTest.java +++ b/app/src/androidTest/java/com/amaze/filemanager/filesystem/HybridFileParcelableTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. diff --git a/app/src/androidTest/java/com/amaze/filemanager/filesystem/files/CryptUtilTest.java b/app/src/androidTest/java/com/amaze/filemanager/filesystem/files/CryptUtilTest.java index 12981e7081..7b7728b0e7 100644 --- a/app/src/androidTest/java/com/amaze/filemanager/filesystem/files/CryptUtilTest.java +++ b/app/src/androidTest/java/com/amaze/filemanager/filesystem/files/CryptUtilTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. diff --git a/app/src/androidTest/java/com/amaze/filemanager/filesystem/files/GenericCopyUtilEspressoTest.java b/app/src/androidTest/java/com/amaze/filemanager/filesystem/files/GenericCopyUtilEspressoTest.java index 3e854bb974..5ff4562d21 100644 --- a/app/src/androidTest/java/com/amaze/filemanager/filesystem/files/GenericCopyUtilEspressoTest.java +++ b/app/src/androidTest/java/com/amaze/filemanager/filesystem/files/GenericCopyUtilEspressoTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. diff --git a/app/src/androidTest/java/com/amaze/filemanager/ssh/SshClientUtilTest.java b/app/src/androidTest/java/com/amaze/filemanager/ssh/SshClientUtilTest.java index fd59e1d701..42774f9380 100644 --- a/app/src/androidTest/java/com/amaze/filemanager/ssh/SshClientUtilTest.java +++ b/app/src/androidTest/java/com/amaze/filemanager/ssh/SshClientUtilTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. diff --git a/app/src/androidTest/java/com/amaze/filemanager/test/DummyFileGenerator.java b/app/src/androidTest/java/com/amaze/filemanager/test/DummyFileGenerator.java index df1fb60601..43f65912e4 100644 --- a/app/src/androidTest/java/com/amaze/filemanager/test/DummyFileGenerator.java +++ b/app/src/androidTest/java/com/amaze/filemanager/test/DummyFileGenerator.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. diff --git a/app/src/androidTest/java/com/amaze/filemanager/test/StoragePermissionHelper.kt b/app/src/androidTest/java/com/amaze/filemanager/test/StoragePermissionHelper.kt index b491fc0fde..c71d892a0b 100644 --- a/app/src/androidTest/java/com/amaze/filemanager/test/StoragePermissionHelper.kt +++ b/app/src/androidTest/java/com/amaze/filemanager/test/StoragePermissionHelper.kt @@ -34,7 +34,6 @@ package com.amaze.filemanager.test // import org.junit.Assert.assertTrue object StoragePermissionHelper { - /** * This method is intended for Android R or above devices to obtain MANAGE_EXTERNAL_STORAGE * permission via UI Automator framework when running relevant Espresso tests. diff --git a/app/src/androidTest/java/com/amaze/filemanager/ui/activities/TextEditorActivityEspressoTest.java b/app/src/androidTest/java/com/amaze/filemanager/ui/activities/TextEditorActivityEspressoTest.java index 2629676aaf..e88eab8c01 100644 --- a/app/src/androidTest/java/com/amaze/filemanager/ui/activities/TextEditorActivityEspressoTest.java +++ b/app/src/androidTest/java/com/amaze/filemanager/ui/activities/TextEditorActivityEspressoTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. diff --git a/app/src/androidTest/java/com/amaze/filemanager/ui/fragments/BackupPrefsFragmentTest.kt b/app/src/androidTest/java/com/amaze/filemanager/ui/fragments/BackupPrefsFragmentTest.kt index a11ebb0fad..027f5c5128 100644 --- a/app/src/androidTest/java/com/amaze/filemanager/ui/fragments/BackupPrefsFragmentTest.kt +++ b/app/src/androidTest/java/com/amaze/filemanager/ui/fragments/BackupPrefsFragmentTest.kt @@ -37,7 +37,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import com.amaze.filemanager.R import com.amaze.filemanager.ui.activities.PreferencesActivity import com.amaze.filemanager.ui.fragments.preferencefragments.BackupPrefsFragment -import com.google.gson.* +import com.google.gson.GsonBuilder import com.google.gson.reflect.TypeToken import org.junit.Assert import org.junit.Test @@ -46,7 +46,6 @@ import java.io.File @RunWith(AndroidJUnit4::class) class BackupPrefsFragmentTest { - var storagePath = "/storage/emulated/0" var fileName = "amaze_backup.json" @@ -63,7 +62,7 @@ class BackupPrefsFragmentTest { File( storagePath + File.separator + - fileName + fileName, ) exportFile.delete() // delete if already exists @@ -80,7 +79,7 @@ class BackupPrefsFragmentTest { File( ApplicationProvider.getApplicationContext().cacheDir.absolutePath + File.separator + - fileName + fileName, ) Assert.assertTrue(tempFile.exists()) @@ -108,7 +107,7 @@ class BackupPrefsFragmentTest { File( storagePath + File.separator + - fileName + fileName, ) activityScenario.onActivity { preferencesActivity -> @@ -116,26 +115,29 @@ class BackupPrefsFragmentTest { .add(backupPrefsFragment, null) .commitNow() - val preferences = PreferenceManager - .getDefaultSharedPreferences(preferencesActivity) + val preferences = + PreferenceManager + .getDefaultSharedPreferences(preferencesActivity) val preferenceMap: Map = preferences.all - val inputString = file - .inputStream() - .bufferedReader() - .use { - it.readText() - } + val inputString = + file + .inputStream() + .bufferedReader() + .use { + it.readText() + } val type = object : TypeToken>() {}.type - val importMap: Map = GsonBuilder() - .create() - .fromJson( - inputString, - type - ) + val importMap: Map = + GsonBuilder() + .create() + .fromJson( + inputString, + type, + ) for ((key, value) in preferenceMap) { var mapValue = importMap[key] @@ -162,7 +164,7 @@ class BackupPrefsFragmentTest { File( storagePath + File.separator + - fileName + fileName, ) exportFile.delete() // delete if already exists @@ -178,28 +180,31 @@ class BackupPrefsFragmentTest { BackupPrefsFragment.IMPORT_BACKUP_FILE, Activity.RESULT_OK, Intent().setData( - Uri.fromFile(exportFile) - ) + Uri.fromFile(exportFile), + ), ) - val inputString = exportFile - .inputStream() - .bufferedReader() - .use { - it.readText() - } + val inputString = + exportFile + .inputStream() + .bufferedReader() + .use { + it.readText() + } val type = object : TypeToken>() {}.type - val importMap: Map = GsonBuilder() - .create() - .fromJson( - inputString, - type - ) + val importMap: Map = + GsonBuilder() + .create() + .fromJson( + inputString, + type, + ) - val preferences = PreferenceManager - .getDefaultSharedPreferences(preferencesActivity) + val preferences = + PreferenceManager + .getDefaultSharedPreferences(preferencesActivity) val preferenceMap: Map = preferences.all @@ -213,13 +218,14 @@ class BackupPrefsFragmentTest { preferences: SharedPreferences, importMap: Map, key: String?, - value: Any? + value: Any?, ): Boolean { when (value!!::class.simpleName) { "Boolean" -> return importMap[key] as Boolean == preferences.getBoolean(key, false) - "Float" -> importMap[key] as Float == - preferences.getFloat(key, 0f) + "Float" -> + importMap[key] as Float == + preferences.getFloat(key, 0f) "Int" -> { // since Gson parses Integer as Double val toInt = (importMap[key] as Double).toInt() diff --git a/app/src/androidTest/java/com/amaze/filemanager/utils/CryptUtilEspressoTest.kt b/app/src/androidTest/java/com/amaze/filemanager/utils/CryptUtilEspressoTest.kt index ef67fbb2d9..d6bba34995 100644 --- a/app/src/androidTest/java/com/amaze/filemanager/utils/CryptUtilEspressoTest.kt +++ b/app/src/androidTest/java/com/amaze/filemanager/utils/CryptUtilEspressoTest.kt @@ -47,10 +47,10 @@ import kotlin.random.Random @RunWith(AndroidJUnit4::class) @Suppress("StringLiteralDuplication") class CryptUtilEspressoTest { - @Rule @JvmField - val storagePermissionRule: GrantPermissionRule = GrantPermissionRule - .grant(android.Manifest.permission.WRITE_EXTERNAL_STORAGE) + val storagePermissionRule: GrantPermissionRule = + GrantPermissionRule + .grant(android.Manifest.permission.WRITE_EXTERNAL_STORAGE) /** * Sanity test of CryptUtil legacy method, to ensure refactoring won't break @@ -68,35 +68,36 @@ class CryptUtilEspressoTest { ArrayList(), "test.bin${CryptUtil.CRYPT_EXTENSION}", false, - null - ) - val targetFile = File( - Environment.getExternalStorageDirectory(), - "test.bin${CryptUtil.CRYPT_EXTENSION}" + null, ) + val targetFile = + File( + Environment.getExternalStorageDirectory(), + "test.bin${CryptUtil.CRYPT_EXTENSION}", + ) assertTrue(targetFile.exists()) if (SDK_INT < JELLY_BEAN_MR2) { // Quirks for SDK < 18. File is not encrypted at all. assertTrue( "Source and target file size should be the same = ${source.size}", - source.size.toLong() == targetFile.length() + source.size.toLong() == targetFile.length(), ) } else { assertTrue( "Source size = ${source.size} target file size = ${targetFile.length()}", - targetFile.length() > source.size + targetFile.length() > source.size, ) } sourceFile.delete() CryptUtil( AppConfig.getInstance(), HybridFileParcelable(targetFile.absolutePath).also { - it.size = targetFile.length() + it.setSize(targetFile.length()) }, Environment.getExternalStorageDirectory().absolutePath, ProgressHandler(), ArrayList(), - null + null, ) File(Environment.getExternalStorageDirectory(), "test.bin").run { assertTrue(this.exists()) @@ -119,27 +120,28 @@ class CryptUtilEspressoTest { ArrayList(), "test.bin${CryptUtil.AESCRYPT_EXTENSION}", true, - "12345678" - ) - val targetFile = File( - Environment.getExternalStorageDirectory(), - "test.bin${CryptUtil.AESCRYPT_EXTENSION}" + "12345678", ) + val targetFile = + File( + Environment.getExternalStorageDirectory(), + "test.bin${CryptUtil.AESCRYPT_EXTENSION}", + ) assertTrue(targetFile.exists()) assertTrue( "Source size = ${source.size} target file size = ${targetFile.length()}", - targetFile.length() > source.size + targetFile.length() > source.size, ) sourceFile.delete() CryptUtil( AppConfig.getInstance(), HybridFileParcelable(targetFile.absolutePath).also { - it.size = targetFile.length() + it.setSize(targetFile.length()) }, Environment.getExternalStorageDirectory().absolutePath, ProgressHandler(), ArrayList(), - "12345678" + "12345678", ) File(Environment.getExternalStorageDirectory(), "test.bin").run { assertTrue(this.exists()) diff --git a/app/src/androidTest/java/com/amaze/filemanager/utils/security/SecretKeygenEspressoTest.kt b/app/src/androidTest/java/com/amaze/filemanager/utils/security/SecretKeygenEspressoTest.kt index 773113c169..507bd8f8a6 100644 --- a/app/src/androidTest/java/com/amaze/filemanager/utils/security/SecretKeygenEspressoTest.kt +++ b/app/src/androidTest/java/com/amaze/filemanager/utils/security/SecretKeygenEspressoTest.kt @@ -37,7 +37,6 @@ import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) class SecretKeygenEspressoTest { - /** * Test [SecretKeygen.getSecretKey]. * @@ -51,6 +50,8 @@ class SecretKeygenEspressoTest { assertEquals("aes", this.algorithm.lowercase()) } ?: if (SDK_INT < ICE_CREAM_SANDWICH) { fail("Android version not supported") + } else { + // Do nothing but let it pass } } } diff --git a/app/src/fdroid/java/com/amaze/filemanager/asynchronous/asynctasks/CloudLoaderAsyncTask.java b/app/src/fdroid/java/com/amaze/filemanager/asynchronous/asynctasks/CloudLoaderAsyncTask.java index cbf5856349..e176ef4f38 100644 --- a/app/src/fdroid/java/com/amaze/filemanager/asynchronous/asynctasks/CloudLoaderAsyncTask.java +++ b/app/src/fdroid/java/com/amaze/filemanager/asynchronous/asynctasks/CloudLoaderAsyncTask.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2022 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. diff --git a/app/src/fdroid/java/com/amaze/filemanager/filesystem/compressed/extractcontents/helpers/RarExtractor.kt b/app/src/fdroid/java/com/amaze/filemanager/filesystem/compressed/extractcontents/helpers/RarExtractor.kt index 8039ef72e7..c01fa18f5d 100644 --- a/app/src/fdroid/java/com/amaze/filemanager/filesystem/compressed/extractcontents/helpers/RarExtractor.kt +++ b/app/src/fdroid/java/com/amaze/filemanager/filesystem/compressed/extractcontents/helpers/RarExtractor.kt @@ -29,7 +29,7 @@ class RarExtractor( filePath: String, outputPath: String, listener: OnUpdate, - updatePosition: UpdatePosition + updatePosition: UpdatePosition, ) : Extractor(context, filePath, outputPath, listener, updatePosition) { override fun extractWithFilter(filter: Filter) { // no-op diff --git a/app/src/fdroid/java/com/amaze/filemanager/filesystem/compressed/showcontents/helpers/RarDecompressor.kt b/app/src/fdroid/java/com/amaze/filemanager/filesystem/compressed/showcontents/helpers/RarDecompressor.kt index aceb23e1e6..e28f96590c 100644 --- a/app/src/fdroid/java/com/amaze/filemanager/filesystem/compressed/showcontents/helpers/RarDecompressor.kt +++ b/app/src/fdroid/java/com/amaze/filemanager/filesystem/compressed/showcontents/helpers/RarDecompressor.kt @@ -27,9 +27,8 @@ import com.amaze.filemanager.filesystem.compressed.showcontents.Decompressor class RarDecompressor(context: Context) : Decompressor(context) { override fun changePath( path: String, - addGoBackItem: Boolean - ) = - UnknownCompressedFileHelperCallable(filePath, addGoBackItem) + addGoBackItem: Boolean, + ) = UnknownCompressedFileHelperCallable(filePath, addGoBackItem) override fun realRelativeDirectory(dir: String): String { return "" diff --git a/app/src/fdroid/java/com/amaze/filemanager/utils/Billing.java b/app/src/fdroid/java/com/amaze/filemanager/utils/Billing.java index 69cb70d3aa..0093eefaa5 100644 --- a/app/src/fdroid/java/com/amaze/filemanager/utils/Billing.java +++ b/app/src/fdroid/java/com/amaze/filemanager/utils/Billing.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. diff --git a/app/src/fdroid/java/com/amaze/filemanager/utils/PackageInstallValidation.kt b/app/src/fdroid/java/com/amaze/filemanager/utils/PackageInstallValidation.kt index 838de95915..a45e25f512 100644 --- a/app/src/fdroid/java/com/amaze/filemanager/utils/PackageInstallValidation.kt +++ b/app/src/fdroid/java/com/amaze/filemanager/utils/PackageInstallValidation.kt @@ -26,7 +26,6 @@ import java.io.File * For non Google Play variant, this class does nothing. Just a stub. */ object PackageInstallValidation { - /** * Do nothing. */ diff --git a/app/src/fdroid/java/com/cloudrail/si/CloudRail.java b/app/src/fdroid/java/com/cloudrail/si/CloudRail.java index 1879a5510d..4deb9185e8 100644 --- a/app/src/fdroid/java/com/cloudrail/si/CloudRail.java +++ b/app/src/fdroid/java/com/cloudrail/si/CloudRail.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2022 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. diff --git a/app/src/fdroid/java/com/cloudrail/si/interfaces/CloudStorage.java b/app/src/fdroid/java/com/cloudrail/si/interfaces/CloudStorage.java index 2bd7b26e3e..853aacb113 100644 --- a/app/src/fdroid/java/com/cloudrail/si/interfaces/CloudStorage.java +++ b/app/src/fdroid/java/com/cloudrail/si/interfaces/CloudStorage.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2022 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. diff --git a/app/src/fdroid/java/com/cloudrail/si/services/Box.java b/app/src/fdroid/java/com/cloudrail/si/services/Box.java index 2b1fa0bc59..e1031449f8 100644 --- a/app/src/fdroid/java/com/cloudrail/si/services/Box.java +++ b/app/src/fdroid/java/com/cloudrail/si/services/Box.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2022 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. diff --git a/app/src/fdroid/java/com/cloudrail/si/services/Dropbox.java b/app/src/fdroid/java/com/cloudrail/si/services/Dropbox.java index 0da7ca0ce0..76d55c771e 100644 --- a/app/src/fdroid/java/com/cloudrail/si/services/Dropbox.java +++ b/app/src/fdroid/java/com/cloudrail/si/services/Dropbox.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2022 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. diff --git a/app/src/fdroid/java/com/cloudrail/si/services/GoogleDrive.java b/app/src/fdroid/java/com/cloudrail/si/services/GoogleDrive.java index 8796f1168f..2af786722c 100644 --- a/app/src/fdroid/java/com/cloudrail/si/services/GoogleDrive.java +++ b/app/src/fdroid/java/com/cloudrail/si/services/GoogleDrive.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2022 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. diff --git a/app/src/fdroid/java/com/cloudrail/si/services/OneDrive.java b/app/src/fdroid/java/com/cloudrail/si/services/OneDrive.java index 88275cecae..b94d8e8069 100644 --- a/app/src/fdroid/java/com/cloudrail/si/services/OneDrive.java +++ b/app/src/fdroid/java/com/cloudrail/si/services/OneDrive.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2022 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. diff --git a/app/src/fdroid/java/com/cloudrail/si/types/CloudMetaData.java b/app/src/fdroid/java/com/cloudrail/si/types/CloudMetaData.java index e6b5f6580f..4fa5b4e799 100644 --- a/app/src/fdroid/java/com/cloudrail/si/types/CloudMetaData.java +++ b/app/src/fdroid/java/com/cloudrail/si/types/CloudMetaData.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2022 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. diff --git a/app/src/fdroid/java/com/cloudrail/si/types/SpaceAllocation.java b/app/src/fdroid/java/com/cloudrail/si/types/SpaceAllocation.java index 798d9e25b7..54e3b78c68 100644 --- a/app/src/fdroid/java/com/cloudrail/si/types/SpaceAllocation.java +++ b/app/src/fdroid/java/com/cloudrail/si/types/SpaceAllocation.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2022 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. diff --git a/app/src/fdroid/java/com/github/junrar/exception/UnsupportedRarV5Exception.java b/app/src/fdroid/java/com/github/junrar/exception/UnsupportedRarV5Exception.java index 614c7e8a88..4ed70a93de 100644 --- a/app/src/fdroid/java/com/github/junrar/exception/UnsupportedRarV5Exception.java +++ b/app/src/fdroid/java/com/github/junrar/exception/UnsupportedRarV5Exception.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2022 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 3eb63f4c86..4735783704 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -20,8 +20,7 @@ --> + xmlns:tools="http://schemas.android.com/tools"> @@ -29,6 +28,7 @@ + @@ -39,6 +39,7 @@ + + android:banner="@drawable/about_header" + android:localeConfig="@xml/locales_config" + android:networkSecurityConfig="@xml/network_security_config"> + + + + + diff --git a/app/src/main/java/com/amaze/filemanager/AmazeFileManagerModule.java b/app/src/main/java/com/amaze/filemanager/AmazeFileManagerModule.java index 33baafd160..1250db15f1 100644 --- a/app/src/main/java/com/amaze/filemanager/AmazeFileManagerModule.java +++ b/app/src/main/java/com/amaze/filemanager/AmazeFileManagerModule.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. diff --git a/app/src/main/java/com/amaze/filemanager/LogHelper.java b/app/src/main/java/com/amaze/filemanager/LogHelper.java index 786a399288..38df523ce8 100644 --- a/app/src/main/java/com/amaze/filemanager/LogHelper.java +++ b/app/src/main/java/com/amaze/filemanager/LogHelper.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. diff --git a/app/src/main/java/com/amaze/filemanager/TagsHelper.java b/app/src/main/java/com/amaze/filemanager/TagsHelper.java index ce31071bdb..30d9b9e1d8 100644 --- a/app/src/main/java/com/amaze/filemanager/TagsHelper.java +++ b/app/src/main/java/com/amaze/filemanager/TagsHelper.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. diff --git a/app/src/main/java/com/amaze/filemanager/adapters/AppsRecyclerAdapter.kt b/app/src/main/java/com/amaze/filemanager/adapters/AppsRecyclerAdapter.kt index fa18dc62db..d3bf5ae753 100644 --- a/app/src/main/java/com/amaze/filemanager/adapters/AppsRecyclerAdapter.kt +++ b/app/src/main/java/com/amaze/filemanager/adapters/AppsRecyclerAdapter.kt @@ -68,7 +68,6 @@ import com.amaze.filemanager.utils.AnimUtils.marqueeAfterDelay import com.amaze.filemanager.utils.Utils import com.amaze.filemanager.utils.safeLet import java.io.File -import kotlin.collections.ArrayList import kotlin.math.roundToInt class AppsRecyclerAdapter( @@ -76,9 +75,8 @@ class AppsRecyclerAdapter( private val modelProvider: AppsAdapterPreloadModel, private val isBottomSheet: Boolean, private val adjustListViewCallback: AdjustListViewForTv, - private val appDataParcelableList: MutableList + private val appDataParcelableList: MutableList, ) : RecyclerView.Adapter() { - private val myChecked = SparseBooleanArray() private var appDataListItem: MutableList = mutableListOf() set(value) { @@ -108,8 +106,9 @@ class AppsRecyclerAdapter( appDataListItem = mutableListOf() } - private val mInflater: LayoutInflater get() = fragment.requireActivity() - .getSystemService(Activity.LAYOUT_INFLATER_SERVICE) as LayoutInflater + private val mInflater: LayoutInflater get() = + fragment.requireActivity() + .getSystemService(Activity.LAYOUT_INFLATER_SERVICE) as LayoutInflater companion object { const val TYPE_ITEM = 0 @@ -118,7 +117,10 @@ class AppsRecyclerAdapter( const val EMPTY_LAST_ITEM = 3 } - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int, + ): RecyclerView.ViewHolder { var view = View(fragment.requireContext()) when (viewType) { TYPE_ITEM -> { @@ -135,7 +137,7 @@ class AppsRecyclerAdapter( SpecialViewHolder.HEADER_SYSTEM_APP } else { SpecialViewHolder.HEADER_USER_APP - } + }, ) } EMPTY_LAST_ITEM -> { @@ -144,7 +146,7 @@ class AppsRecyclerAdapter( fragment.requireActivity().resources.getDimension(R.dimen.fab_height) + fragment.requireContext().resources .getDimension(R.dimen.fab_margin) - ).roundToInt() + ).roundToInt() return EmptyViewHolder(view) } else -> { @@ -157,7 +159,10 @@ class AppsRecyclerAdapter( return appDataListItem[position].listItemType } - override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + override fun onBindViewHolder( + holder: RecyclerView.ViewHolder, + position: Int, + ) { if (holder is AppHolder) { appDataListItem[position].appDataParcelable?.let { rowItem -> if (isBottomSheet) { @@ -172,7 +177,7 @@ class AppsRecyclerAdapter( if (holder.about != null && !isBottomSheet) { if ((fragment.requireActivity() as MainActivity).appTheme == AppTheme.LIGHT) { holder.about.setColorFilter( - Color.parseColor("#ff666666") + Color.parseColor("#ff666666"), ) } showPopup(holder.about, rowItem) @@ -180,27 +185,24 @@ class AppsRecyclerAdapter( holder.rl.setOnFocusChangeListener { _, _ -> adjustListViewCallback.adjustListViewForTv( holder, - fragment.requireActivity() as MainActivity + fragment.requireActivity() as MainActivity, ) } holder.txtTitle.text = rowItem.label - holder.packageName.text = rowItem.packageName holder.packageName.isSelected = true // for marquee - val enableMarqueeFilename = (fragment.requireActivity() as MainActivity) .getBoolean(PreferencesConstants.PREFERENCE_ENABLE_MARQUEE_FILENAME) if (enableMarqueeFilename) { - holder.txtTitle.ellipsize = if (enableMarqueeFilename) { - TextUtils.TruncateAt.MARQUEE - } else { - TextUtils.TruncateAt.MIDDLE - } + holder.txtTitle.ellipsize = + if (enableMarqueeFilename) { + TextUtils.TruncateAt.MARQUEE + } else { + TextUtils.TruncateAt.MIDDLE + } marqueeAfterDelay(2000, holder.txtTitle) } - - // File f = new File(rowItem.getDesc()); if (!isBottomSheet) { holder.txtDesc.text = rowItem.fileSize + " |" } @@ -212,7 +214,7 @@ class AppsRecyclerAdapter( } if (myChecked[position]) { holder.rl.setBackgroundColor( - Utils.getColor(fragment.context, R.color.appsadapter_background) + Utils.getColor(fragment.context, R.color.appsadapter_background), ) } else { if ((fragment.requireActivity() as MainActivity).appTheme == AppTheme.LIGHT) { @@ -232,7 +234,10 @@ class AppsRecyclerAdapter( * Set list elements * @param showSystemApps whether to filter system apps or not */ - fun setData(data: List, showSystemApps: Boolean) { + fun setData( + data: List, + showSystemApps: Boolean, + ) { appDataParcelableList.run { clear() val list = if (!showSystemApps) data.filter { !it.isSystemApp } else data @@ -250,34 +255,37 @@ class AppsRecyclerAdapter( safeLet( openFileParcelable.uri, openFileParcelable.mimeType, - openFileParcelable.useNewStack + openFileParcelable.useNewStack, ) { uri, mimeType, useNewStack -> - val intent = buildIntent( - uri, - mimeType, - useNewStack, - openFileParcelable.className, - openFileParcelable.packageName - ) + val intent = + buildIntent( + fragment.requireContext(), + uri, + mimeType, + useNewStack, + openFileParcelable.className, + openFileParcelable.packageName, + ) setLastOpenedApp( rowItem, - fragment.requireActivity() as PreferenceActivity + fragment.requireActivity() as PreferenceActivity, ) fragment.requireContext().startActivityCatchingSecurityException(intent) } } } else { - val i1 = fragment.requireContext().packageManager.getLaunchIntentForPackage( - rowItem.packageName - ) + val i1 = + fragment.requireContext().packageManager.getLaunchIntentForPackage( + rowItem.packageName, + ) if (i1 != null) { fragment.startActivity(i1) } else { Toast.makeText( fragment.context, fragment.getString(R.string.not_allowed), - Toast.LENGTH_LONG + Toast.LENGTH_LONG, ) .show() // TODO: Implement this method @@ -286,20 +294,20 @@ class AppsRecyclerAdapter( } } - private fun showPopup(v: View, rowItem: AppDataParcelable?) { + private fun showPopup( + v: View, + rowItem: AppDataParcelable?, + ) { v.setOnClickListener { view: View? -> var context = fragment.context if (( - fragment.requireActivity() - as MainActivity - ).appTheme.getSimpleTheme(fragment.requireContext()) == AppTheme.BLACK + fragment.requireActivity() + as MainActivity + ).appTheme == AppTheme.BLACK ) { context = ContextThemeWrapper(context, R.style.overflow_black) } - val popupMenu = PopupMenu( - context, - view - ) + val popupMenu = PopupMenu(context, view) popupMenu.setOnMenuItemClickListener { item: MenuItem -> val themedActivity: MainActivity = fragment.requireActivity() as MainActivity val colorAccent = themedActivity.accent @@ -333,12 +341,9 @@ class AppsRecyclerAdapter( Intent( Settings.ACTION_APPLICATION_DETAILS_SETTINGS, Uri.parse( - String.format( - "package:%s", - rowItem!!.packageName - ) - ) - ) + String.format("package:%s", rowItem!!.packageName), + ), + ), ) return@setOnMenuItemClickListener true } @@ -357,21 +362,26 @@ class AppsRecyclerAdapter( } private fun popupOpen(appDataParcelable: AppDataParcelable) { - val i1 = fragment - .context - ?.packageManager - ?.getLaunchIntentForPackage(appDataParcelable.packageName) - if (i1 != null) fragment.startActivity(i1) else Toast.makeText( - fragment.context, - fragment.getString(R.string.not_allowed), - Toast.LENGTH_LONG - ).show() + val i1 = + fragment + .context + ?.packageManager + ?.getLaunchIntentForPackage(appDataParcelable.packageName) + if (i1 != null) { + fragment.startActivity(i1) + } else { + Toast.makeText( + fragment.context, + fragment.getString(R.string.not_allowed), + Toast.LENGTH_LONG, + ).show() + } } private fun popupShare( appDataParcelable: AppDataParcelable, themedActivity: ThemedActivity, - colorAccent: Int + colorAccent: Int, ) { val arrayList2 = ArrayList() @@ -381,21 +391,21 @@ class AppsRecyclerAdapter( arrayList2, fragment.activity, themedActivity.utilsProvider.appTheme, - colorAccent + colorAccent, ) } private fun popupUninstall( appDataParcelable: AppDataParcelable, themedActivity: ThemedActivity, - colorAccent: Int + colorAccent: Int, ) { val f1 = HybridFileParcelable(appDataParcelable.path) f1.mode = OpenMode.ROOT if (appDataParcelable.isSystemApp) { // system package if ((fragment.requireActivity() as MainActivity).getBoolean( - PreferencesConstants.PREFERENCE_ROOTMODE + PreferencesConstants.PREFERENCE_ROOTMODE, ) ) { showDeleteSystemAppDialog(themedActivity, colorAccent, f1) @@ -403,14 +413,14 @@ class AppsRecyclerAdapter( Toast.makeText( fragment.context, fragment.getString(R.string.enablerootmde), - Toast.LENGTH_SHORT + Toast.LENGTH_SHORT, ) .show() } } else { FileUtils.uninstallPackage( appDataParcelable.packageName, - fragment.context + fragment.context, ) } } @@ -419,20 +429,22 @@ class AppsRecyclerAdapter( val intent1 = Intent(Intent.ACTION_VIEW) try { - intent1.data = Uri.parse( - String.format( - "market://details?id=%s", - appDataParcelable.packageName + intent1.data = + Uri.parse( + String.format( + "market://details?id=%s", + appDataParcelable.packageName, + ), ) - ) fragment.startActivity(intent1) } catch (ifPlayStoreNotInstalled: ActivityNotFoundException) { - intent1.data = Uri.parse( - String.format( - "https://play.google.com/store/apps/details?id=%s", - appDataParcelable.packageName + intent1.data = + Uri.parse( + String.format( + "https://play.google.com/store/apps/details?id=%s", + appDataParcelable.packageName, + ), ) - ) fragment.startActivity(intent1) } } @@ -440,15 +452,17 @@ class AppsRecyclerAdapter( private fun popupBackup(appDataParcelable: AppDataParcelable) { val baseApkFile = File(appDataParcelable.path) val filesToCopyList = ArrayList() - val dst = File( - Environment.getExternalStorageDirectory() - .path + "/app_backup" - ) + val dst = + File( + Environment.getExternalStorageDirectory() + .path + "/app_backup", + ) if (!dst.exists() || !dst.isDirectory) dst.mkdirs() - val intent = Intent( - fragment.context, - CopyService::class.java - ) + val intent = + Intent( + fragment.context, + CopyService::class.java, + ) val mainApkFile = RootHelper.generateBaseFile(baseApkFile, true) val startIndex = appDataParcelable.packageName.indexOf("_") val subString = appDataParcelable.packageName.substring(startIndex + 1) @@ -478,7 +492,7 @@ class AppsRecyclerAdapter( Toast.makeText( fragment.context, fragment.getString(R.string.copyingapks, filesToCopyList.size, dst.path), - Toast.LENGTH_LONG + Toast.LENGTH_LONG, ) .show() @@ -488,13 +502,13 @@ class AppsRecyclerAdapter( private fun showDeleteSystemAppDialog( themedActivity: ThemedActivity, colorAccent: Int, - f1: HybridFileParcelable + f1: HybridFileParcelable, ) { val builder1 = MaterialDialog.Builder(fragment.requireContext()) builder1 .theme( - themedActivity.appTheme.getMaterialDialogTheme(fragment.requireContext()) + themedActivity.appTheme.getMaterialDialogTheme(), ) .content(fragment.getString(R.string.unin_system_apk)) .title(fragment.getString(R.string.warning)) @@ -512,16 +526,18 @@ class AppsRecyclerAdapter( if (parent != "app" && parent != "priv-app") { val baseFile = HybridFileParcelable( - f1.getParent(fragment.context) + f1.getParent(fragment.context), ) baseFile.mode = OpenMode.ROOT files.add(baseFile) - } else files.add(f1) + } else { + files.add(f1) + } } else { files.add(f1) } - DeleteTask(fragment.requireContext()).execute(files) + DeleteTask(fragment.requireContext(), false).execute(files) } .build() .show() @@ -532,13 +548,13 @@ class AppsRecyclerAdapter( TYPE_ITEM, TYPE_HEADER_SYSTEM, TYPE_HEADER_THIRD_PARTY, - EMPTY_LAST_ITEM + EMPTY_LAST_ITEM, ) annotation class ListItemType data class ListItem( var appDataParcelable: AppDataParcelable?, - var listItemType: @ListItemType Int = TYPE_ITEM + var listItemType: @ListItemType Int = TYPE_ITEM, ) { constructor(listItemType: @ListItemType Int) : this(null, listItemType) } diff --git a/app/src/main/java/com/amaze/filemanager/adapters/ColorAdapter.java b/app/src/main/java/com/amaze/filemanager/adapters/ColorAdapter.java index ac194e5372..0c8c67fff1 100644 --- a/app/src/main/java/com/amaze/filemanager/adapters/ColorAdapter.java +++ b/app/src/main/java/com/amaze/filemanager/adapters/ColorAdapter.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. diff --git a/app/src/main/java/com/amaze/filemanager/adapters/CompressedExplorerAdapter.java b/app/src/main/java/com/amaze/filemanager/adapters/CompressedExplorerAdapter.java index 3917d23fb9..eab01ab74e 100644 --- a/app/src/main/java/com/amaze/filemanager/adapters/CompressedExplorerAdapter.java +++ b/app/src/main/java/com/amaze/filemanager/adapters/CompressedExplorerAdapter.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. @@ -23,7 +23,6 @@ import java.util.ArrayList; import java.util.List; -import com.amaze.filemanager.GlideApp; import com.amaze.filemanager.R; import com.amaze.filemanager.adapters.data.CompressedObjectParcelable; import com.amaze.filemanager.adapters.holders.CompressedItemViewHolder; @@ -39,6 +38,7 @@ import com.amaze.filemanager.ui.views.CircleGradientDrawable; import com.amaze.filemanager.utils.AnimUtils; import com.amaze.filemanager.utils.Utils; +import com.bumptech.glide.Glide; import android.app.Activity; import android.content.Context; @@ -53,10 +53,10 @@ import android.view.ViewGroup; import android.view.animation.Animation; import android.view.animation.AnimationUtils; -import android.widget.ImageButton; -import android.widget.ImageView; import android.widget.Toast; +import androidx.appcompat.widget.AppCompatImageButton; +import androidx.appcompat.widget.AppCompatImageView; import androidx.recyclerview.widget.RecyclerView; /** Created by Arpit on 25-01-2015 edited by Emmanuel Messulam */ @@ -127,7 +127,7 @@ public ArrayList getCheckedItemPositions() { * @param position the position of the item * @param imageView the circular {@link CircleGradientDrawable} that is to be animated */ - private void toggleChecked(int position, ImageView imageView) { + private void toggleChecked(int position, AppCompatImageView imageView) { compressedExplorerFragment.stopAnim(); stoppedAnimation = true; @@ -204,7 +204,7 @@ public CompressedItemViewHolder onCreateViewHolder(ViewGroup parent, int viewTyp } else if (viewType == TYPE_ITEM) { View v = mInflater.inflate(R.layout.rowlayout, parent, false); CompressedItemViewHolder vh = new CompressedItemViewHolder(v); - ImageButton about = v.findViewById(R.id.properties); + AppCompatImageButton about = v.findViewById(R.id.properties); about.setVisibility(View.INVISIBLE); return vh; } else { @@ -240,7 +240,7 @@ public void onBindViewHolder(final CompressedItemViewHolder holder, int position compressedExplorerFragment.getResources().getDisplayMetrics())); if (rowItem.type == CompressedObjectParcelable.TYPE_GOBACK) { - GlideApp.with(compressedExplorerFragment) + Glide.with(compressedExplorerFragment) .load(R.drawable.ic_arrow_left_white_24dp) .into(holder.genericIcon); gradientDrawable.setColor(Utils.getColor(context, R.color.goback_item)); @@ -248,9 +248,7 @@ public void onBindViewHolder(final CompressedItemViewHolder holder, int position holder.txtDesc.setText(""); holder.date.setText(R.string.goback); } else { - GlideApp.with(compressedExplorerFragment) - .load(rowItem.iconData.image) - .into(holder.genericIcon); + Glide.with(compressedExplorerFragment).load(rowItem.iconData.image).into(holder.genericIcon); if (compressedExplorerFragment.showLastModified) holder.date.setText(Utils.getDate(context, rowItem.date)); diff --git a/app/src/main/java/com/amaze/filemanager/adapters/HiddenAdapter.kt b/app/src/main/java/com/amaze/filemanager/adapters/HiddenAdapter.kt index 0e7ba31efe..e0dd9db9db 100644 --- a/app/src/main/java/com/amaze/filemanager/adapters/HiddenAdapter.kt +++ b/app/src/main/java/com/amaze/filemanager/adapters/HiddenAdapter.kt @@ -39,7 +39,6 @@ import com.amaze.filemanager.ui.activities.MainActivity import com.amaze.filemanager.ui.fragments.MainFragment import com.amaze.filemanager.utils.DataUtils import java.io.File -import java.util.ArrayList import kotlin.concurrent.thread /** @@ -53,22 +52,27 @@ class HiddenAdapter( private val sharedPrefs: SharedPreferences, hiddenFiles: List, var materialDialog: MaterialDialog?, - private val hide: Boolean + private val hide: Boolean, ) : RecyclerView.Adapter() { - companion object { private const val TAG = "HiddenAdapter" } private val hiddenFiles = hiddenFiles.toMutableList() - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HiddenViewHolder { + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int, + ): HiddenViewHolder { val mInflater = context.getSystemService(Activity.LAYOUT_INFLATER_SERVICE) as LayoutInflater val view = mInflater.inflate(R.layout.bookmarkrow, parent, false) return HiddenViewHolder(view) } - override fun onBindViewHolder(holder: HiddenViewHolder, position: Int) { + override fun onBindViewHolder( + holder: HiddenViewHolder, + position: Int, + ) { val file = hiddenFiles[position] holder.textTitle.text = file.getName(context) holder.textDescription.text = file.getReadablePath(file.path) @@ -80,13 +84,14 @@ class HiddenAdapter( // TODO: the "hide files" feature just hide files from view in Amaze and not create // .nomedia if (!file.isSmb && file.isDirectory(context)) { - val nomediaFile = HybridFileParcelable( - hiddenFiles[position].path + "/" + FileUtils.NOMEDIA_FILE - ) + val nomediaFile = + HybridFileParcelable( + hiddenFiles[position].path + "/" + FileUtils.NOMEDIA_FILE, + ) nomediaFile.mode = OpenMode.FILE val filesToDelete = ArrayList() filesToDelete.add(nomediaFile) - val task = DeleteTask(context) + val task = DeleteTask(context, false) task.execute(filesToDelete) } DataUtils.getInstance().removeHiddenFile(hiddenFiles[position].path) @@ -101,11 +106,13 @@ class HiddenAdapter( val fragmentActivity = mainFragment.requireActivity() if (file.isDirectory(context)) { fragmentActivity.runOnUiThread { + mainFragment.hideFab = false + mainFragment.requireMainActivity().showFab() mainFragment.loadlist( file.path, false, OpenMode.UNKNOWN, - false + false, ) } } else if (!file.isSmb) { @@ -113,7 +120,7 @@ class HiddenAdapter( FileUtils.openFile( File(file.path), (fragmentActivity as MainActivity), - sharedPrefs + sharedPrefs, ) } } diff --git a/app/src/main/java/com/amaze/filemanager/adapters/RecyclerAdapter.java b/app/src/main/java/com/amaze/filemanager/adapters/RecyclerAdapter.java index 047aadfba0..ace6284542 100644 --- a/app/src/main/java/com/amaze/filemanager/adapters/RecyclerAdapter.java +++ b/app/src/main/java/com/amaze/filemanager/adapters/RecyclerAdapter.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. @@ -20,7 +20,23 @@ package com.amaze.filemanager.adapters; -import static com.amaze.filemanager.filesystem.compressed.CompressedHelper.*; +import static com.amaze.filemanager.filesystem.compressed.CompressedHelper.fileExtension7zip; +import static com.amaze.filemanager.filesystem.compressed.CompressedHelper.fileExtensionApk; +import static com.amaze.filemanager.filesystem.compressed.CompressedHelper.fileExtensionApks; +import static com.amaze.filemanager.filesystem.compressed.CompressedHelper.fileExtensionBzip2; +import static com.amaze.filemanager.filesystem.compressed.CompressedHelper.fileExtensionBzip2TarLong; +import static com.amaze.filemanager.filesystem.compressed.CompressedHelper.fileExtensionBzip2TarShort; +import static com.amaze.filemanager.filesystem.compressed.CompressedHelper.fileExtensionGz; +import static com.amaze.filemanager.filesystem.compressed.CompressedHelper.fileExtensionGzipTarLong; +import static com.amaze.filemanager.filesystem.compressed.CompressedHelper.fileExtensionGzipTarShort; +import static com.amaze.filemanager.filesystem.compressed.CompressedHelper.fileExtensionJar; +import static com.amaze.filemanager.filesystem.compressed.CompressedHelper.fileExtensionLzma; +import static com.amaze.filemanager.filesystem.compressed.CompressedHelper.fileExtensionRar; +import static com.amaze.filemanager.filesystem.compressed.CompressedHelper.fileExtensionTar; +import static com.amaze.filemanager.filesystem.compressed.CompressedHelper.fileExtensionTarLzma; +import static com.amaze.filemanager.filesystem.compressed.CompressedHelper.fileExtensionTarXz; +import static com.amaze.filemanager.filesystem.compressed.CompressedHelper.fileExtensionXz; +import static com.amaze.filemanager.filesystem.compressed.CompressedHelper.fileExtensionZip; import static com.amaze.filemanager.ui.fragments.preferencefragments.PreferencesConstants.PREFERENCE_COLORIZE_ICONS; import static com.amaze.filemanager.ui.fragments.preferencefragments.PreferencesConstants.PREFERENCE_SHOW_FILE_SIZE; import static com.amaze.filemanager.ui.fragments.preferencefragments.PreferencesConstants.PREFERENCE_SHOW_GOBACK_BUTTON; @@ -31,12 +47,13 @@ import static com.amaze.filemanager.ui.fragments.preferencefragments.PreferencesConstants.PREFERENCE_USE_CIRCULAR_IMAGES; import java.util.ArrayList; +import java.util.Collections; import java.util.List; +import java.util.Objects; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.amaze.filemanager.GlideApp; import com.amaze.filemanager.R; import com.amaze.filemanager.adapters.data.IconDataParcelable; import com.amaze.filemanager.adapters.data.LayoutElementParcelable; @@ -47,8 +64,11 @@ import com.amaze.filemanager.adapters.holders.SpecialViewHolder; import com.amaze.filemanager.application.AppConfig; import com.amaze.filemanager.fileoperations.filesystem.OpenMode; +import com.amaze.filemanager.filesystem.PasteHelper; import com.amaze.filemanager.filesystem.files.CryptUtil; +import com.amaze.filemanager.filesystem.files.sort.DirSortBy; import com.amaze.filemanager.ui.ItemPopupMenu; +import com.amaze.filemanager.ui.activities.MainActivity; import com.amaze.filemanager.ui.activities.superclasses.PreferenceActivity; import com.amaze.filemanager.ui.colors.ColorUtils; import com.amaze.filemanager.ui.drag.RecyclerAdapterDragListener; @@ -62,7 +82,9 @@ import com.amaze.filemanager.ui.views.CircleGradientDrawable; import com.amaze.filemanager.utils.AnimUtils; import com.amaze.filemanager.utils.GlideConstants; +import com.amaze.filemanager.utils.MainActivityActionMode; import com.amaze.filemanager.utils.Utils; +import com.bumptech.glide.Glide; import com.bumptech.glide.integration.recyclerview.RecyclerViewPreloader; import com.bumptech.glide.load.DataSource; import com.bumptech.glide.load.engine.GlideException; @@ -84,15 +106,16 @@ import android.view.ViewGroup; import android.view.animation.Animation; import android.view.animation.AnimationUtils; -import android.widget.ImageView; import android.widget.PopupMenu; -import android.widget.TextView; +import android.widget.Toast; import androidx.annotation.IntDef; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.view.ActionMode; import androidx.appcompat.view.ContextThemeWrapper; +import androidx.appcompat.widget.AppCompatImageView; +import androidx.appcompat.widget.AppCompatTextView; import androidx.recyclerview.widget.RecyclerView; /** @@ -200,7 +223,7 @@ public RecyclerAdapter( * @param position the position of the item * @param imageView the check {@link CircleGradientDrawable} that is to be animated */ - public void toggleChecked(int position, ImageView imageView) { + public void toggleChecked(int position, AppCompatImageView imageView) { if (getItemsDigested().size() <= position || position < 0) { AppConfig.toast(context, R.string.operation_not_supported); return; @@ -374,6 +397,19 @@ public void toggleSameDates() { } } + public void toggleFill() { + ArrayList checkedItemsIndexes = getCheckedItemsIndex(); + Collections.sort(checkedItemsIndexes); + if (checkedItemsIndexes.size() >= 2) { + for (int i = checkedItemsIndexes.get(0); + i < checkedItemsIndexes.get(checkedItemsIndexes.size() - 1); + i++) { + Objects.requireNonNull(getItemsDigested()).get(i).setChecked(true); + notifyItemChanged(i); + } + } + } + public void toggleSimilarNames() { ArrayList checkedItemsIndexes = getCheckedItemsIndex(); for (int i = 0; i < checkedItemsIndexes.size(); i++) { @@ -596,7 +632,7 @@ private void setItems( preloader = new RecyclerViewPreloader<>( - GlideApp.with(mainFragment), + Glide.with(mainFragment), modelProvider, sizeProvider, GlideConstants.MAX_PRELOAD_FILES); @@ -605,34 +641,43 @@ private void setItems( } public void createHeaders(boolean invalidate, List uris) { - boolean[] headers = new boolean[] {false, false}; + if ((mainFragment.getMainFragmentViewModel() != null + && mainFragment.getMainFragmentViewModel().getDsort() == DirSortBy.NONE_ON_TOP) + || getItemsDigested() == null + || getItemsDigested().isEmpty()) { + return; + } else { + boolean[] headers = new boolean[] {false, false}; - for (int i = 0; i < getItemsDigested().size(); i++) { + for (int i = 0; i < getItemsDigested().size(); i++) { - if (getItemsDigested().get(i).layoutElementParcelable != null) { - LayoutElementParcelable nextItem = getItemsDigested().get(i).layoutElementParcelable; + if (getItemsDigested().get(i).layoutElementParcelable != null) { + LayoutElementParcelable nextItem = getItemsDigested().get(i).layoutElementParcelable; - if (!headers[0] && nextItem.isDirectory) { - headers[0] = true; - getItemsDigested().add(i, new ListItem(TYPE_HEADER_FOLDERS)); - uris.add(i, null); - continue; - } + if (nextItem != null) { + if (!headers[0] && nextItem.isDirectory) { + headers[0] = true; + getItemsDigested().add(i, new ListItem(TYPE_HEADER_FOLDERS)); + uris.add(i, null); + continue; + } - if (!headers[1] - && !nextItem.isDirectory - && !nextItem.title.equals(".") - && !nextItem.title.equals("..")) { - headers[1] = true; - getItemsDigested().add(i, new ListItem(TYPE_HEADER_FILES)); - uris.add(i, null); - continue; // leave this continue for symmetry + if (!headers[1] + && !nextItem.isDirectory + && !nextItem.title.equals(".") + && !nextItem.title.equals("..")) { + headers[1] = true; + getItemsDigested().add(i, new ListItem(TYPE_HEADER_FILES)); + uris.add(i, null); + continue; // leave this continue for symmetry + } + } } } - } - if (invalidate) { - notifyDataSetChanged(); + if (invalidate) { + notifyDataSetChanged(); + } } } @@ -763,12 +808,14 @@ private void bindViewHolderList(@NonNull final ItemViewHolder holder, int positi holder.baseItemView.setOnLongClickListener( p1 -> { + if (hasPendingPasteOperation()) return false; if (!isBackButton) { if (dragAndDropPreference == PreferencesConstants.PREFERENCE_DRAG_DEFAULT || (dragAndDropPreference == PreferencesConstants.PREFERENCE_DRAG_TO_MOVE_COPY && getItemsDigested().get(holder.getAdapterPosition()).getChecked() != ListItem.CHECKED)) { - toggleChecked(holder.getAdapterPosition(), holder.checkImageView); + mainFragment.registerListItemChecked( + holder.getAdapterPosition(), holder.checkImageView); } initDragListener(position, p1, holder); } @@ -776,10 +823,10 @@ && getItemsDigested().get(holder.getAdapterPosition()).getChecked() }); // clear previously cached icon - GlideApp.with(mainFragment).clear(holder.genericIcon); - GlideApp.with(mainFragment).clear(holder.pictureIcon); - GlideApp.with(mainFragment).clear(holder.apkIcon); - GlideApp.with(mainFragment).clear(holder.baseItemView); + Glide.with(mainFragment).clear(holder.genericIcon); + Glide.with(mainFragment).clear(holder.pictureIcon); + Glide.with(mainFragment).clear(holder.apkIcon); + Glide.with(mainFragment).clear(holder.baseItemView); holder.baseItemView.setOnClickListener( v -> { @@ -976,12 +1023,14 @@ private void bindViewHolderGrid(@NonNull final ItemViewHolder holder, int positi holder.baseItemView.setOnLongClickListener( p1 -> { + if (hasPendingPasteOperation()) return false; if (!isBackButton) { if (dragAndDropPreference == PreferencesConstants.PREFERENCE_DRAG_DEFAULT || (dragAndDropPreference == PreferencesConstants.PREFERENCE_DRAG_TO_MOVE_COPY && getItemsDigested().get(holder.getAdapterPosition()).getChecked() != ListItem.CHECKED)) { - toggleChecked(holder.getAdapterPosition(), holder.checkImageViewGrid); + mainFragment.registerListItemChecked( + holder.getAdapterPosition(), holder.checkImageViewGrid); } initDragListener(position, p1, holder); } @@ -990,10 +1039,10 @@ && getItemsDigested().get(holder.getAdapterPosition()).getChecked() // view is a grid view // clear previously cached icon - GlideApp.with(mainFragment).clear(holder.genericIcon); - GlideApp.with(mainFragment).clear(holder.iconLayout); - GlideApp.with(mainFragment).clear(holder.imageView1); - GlideApp.with(mainFragment).clear(holder.baseItemView); + Glide.with(mainFragment).clear(holder.genericIcon); + Glide.with(mainFragment).clear(holder.iconLayout); + Glide.with(mainFragment).clear(holder.imageView1); + Glide.with(mainFragment).clear(holder.baseItemView); holder.checkImageViewGrid.setColorFilter(accentColor); holder.baseItemView.setOnClickListener( @@ -1028,7 +1077,7 @@ && getItemsDigested().get(holder.getAdapterPosition()).getChecked() holder.genericIcon.setImageResource(R.drawable.ic_doc_apk_white); } } else { - GlideApp.with(mainFragment).load(rowItem.iconData.image).into(holder.genericIcon); + Glide.with(mainFragment).load(rowItem.iconData.image).into(holder.genericIcon); } if (holder.genericIcon.getVisibility() == View.VISIBLE) { @@ -1192,7 +1241,7 @@ private View getDragShadow(int selectionCount) { .setVisibility(View.VISIBLE); String rememberMovePreference = sharedPrefs.getString(PreferencesConstants.PREFERENCE_DRAG_AND_DROP_REMEMBERED, ""); - ImageView icon = + AppCompatImageView icon = mainFragment .getMainActivity() .getTabFragment() @@ -1204,7 +1253,7 @@ private View getDragShadow(int selectionCount) { .getTabFragment() .getDragPlaceholder() .findViewById(R.id.files_count_parent); - TextView filesCount = + AppCompatTextView filesCount = mainFragment .getMainActivity() .getTabFragment() @@ -1238,11 +1287,11 @@ private int getDragIconReference(String rememberMovePreference) { private void showThumbnailWithBackground( ItemViewHolder viewHolder, IconDataParcelable iconData, - ImageView view, + AppCompatImageView view, OnImageProcessed errorListener) { if (iconData.isImageBroken()) { viewHolder.genericIcon.setVisibility(View.VISIBLE); - GlideApp.with(mainFragment) + Glide.with(mainFragment) .load(R.drawable.ic_broken_image_white_24dp) .into(viewHolder.genericIcon); GradientDrawable gradientDrawable = (GradientDrawable) viewHolder.genericIcon.getBackground(); @@ -1253,7 +1302,7 @@ private void showThumbnailWithBackground( } viewHolder.genericIcon.setVisibility(View.VISIBLE); - GlideApp.with(mainFragment).load(iconData.loadingImage).into(viewHolder.genericIcon); + Glide.with(mainFragment).load(iconData.loadingImage).into(viewHolder.genericIcon); GradientDrawable gradientDrawable = (GradientDrawable) viewHolder.genericIcon.getBackground(); RequestListener requestListener = @@ -1265,7 +1314,7 @@ public boolean onLoadFailed( new Handler( msg -> { viewHolder.genericIcon.setVisibility(View.VISIBLE); - GlideApp.with(mainFragment) + Glide.with(mainFragment) .load(R.drawable.ic_broken_image_white_24dp) .into(viewHolder.genericIcon); return false; @@ -1301,7 +1350,7 @@ public boolean onResourceReady( private void showRoundedThumbnail( ItemViewHolder viewHolder, IconDataParcelable iconData, - ImageView view, + AppCompatImageView view, OnImageProcessed errorListener) { if (iconData.isImageBroken()) { View iconBackground = @@ -1311,7 +1360,7 @@ private void showRoundedThumbnail( viewHolder.genericIcon.setVisibility(View.VISIBLE); iconBackground.setBackgroundColor(grey_color); - GlideApp.with(mainFragment) + Glide.with(mainFragment) .load(R.drawable.ic_broken_image_white_24dp) .into(viewHolder.genericIcon); view.setVisibility(View.INVISIBLE); @@ -1324,7 +1373,7 @@ private void showRoundedThumbnail( getBoolean(PREFERENCE_USE_CIRCULAR_IMAGES) ? viewHolder.genericIcon : viewHolder.iconLayout; viewHolder.genericIcon.setVisibility(View.VISIBLE); - GlideApp.with(mainFragment).load(iconData.loadingImage).into(viewHolder.genericIcon); + Glide.with(mainFragment).load(iconData.loadingImage).into(viewHolder.genericIcon); view.setVisibility(View.INVISIBLE); RequestListener requestListener = @@ -1335,7 +1384,7 @@ public boolean onLoadFailed( iconBackground.setBackgroundColor(grey_color); new Handler( msg -> { - GlideApp.with(mainFragment) + Glide.with(mainFragment) .load(R.drawable.ic_broken_image_white_24dp) .into(viewHolder.genericIcon); return false; @@ -1366,9 +1415,9 @@ public boolean onResourceReady( } private void showPopup(@NonNull View view, @NonNull final LayoutElementParcelable rowItem) { + if (hasPendingPasteOperation()) return; Context currentContext = this.context; - if (mainFragment.getMainActivity().getAppTheme().getSimpleTheme(mainFragment.requireContext()) - == AppTheme.BLACK) { + if (mainFragment.getMainActivity().getAppTheme() == AppTheme.BLACK) { currentContext = new ContextThemeWrapper(context, R.style.overflow_black); } PopupMenu popupMenu = @@ -1392,6 +1441,7 @@ private void showPopup(@NonNull View view, @NonNull final LayoutElementParcelabl } } else { popupMenu.getMenu().findItem(R.id.book).setVisible(false); + popupMenu.getMenu().findItem(R.id.compress).setVisible(true); if (description.endsWith(fileExtensionZip) || description.endsWith(fileExtensionJar) @@ -1409,8 +1459,10 @@ private void showPopup(@NonNull View view, @NonNull final LayoutElementParcelabl || description.endsWith(fileExtensionGz) || description.endsWith(fileExtensionBzip2) || description.endsWith(fileExtensionLzma) - || description.endsWith(fileExtensionXz)) + || description.endsWith(fileExtensionXz)) { popupMenu.getMenu().findItem(R.id.ex).setVisible(true); + popupMenu.getMenu().findItem(R.id.compress).setVisible(false); + } } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { @@ -1421,10 +1473,50 @@ private void showPopup(@NonNull View view, @NonNull final LayoutElementParcelabl popupMenu.getMenu().findItem(R.id.encrypt).setVisible(true); } } + if (rowItem.getMode() == OpenMode.TRASH_BIN) { + popupMenu.getMenu().findItem(R.id.return_select).setVisible(false); + popupMenu.getMenu().findItem(R.id.cut).setVisible(false); + popupMenu.getMenu().findItem(R.id.cpy).setVisible(false); + popupMenu.getMenu().findItem(R.id.rename).setVisible(false); + popupMenu.getMenu().findItem(R.id.encrypt).setVisible(false); + popupMenu.getMenu().findItem(R.id.decrypt).setVisible(false); + popupMenu.getMenu().findItem(R.id.about).setVisible(false); + popupMenu.getMenu().findItem(R.id.compress).setVisible(false); + popupMenu.getMenu().findItem(R.id.share).setVisible(false); + popupMenu.getMenu().findItem(R.id.ex).setVisible(false); + popupMenu.getMenu().findItem(R.id.book).setVisible(false); + popupMenu.getMenu().findItem(R.id.restore).setVisible(true); + popupMenu.getMenu().findItem(R.id.delete).setVisible(true); + } popupMenu.show(); } + /** + * Helps in deciding whether to allow file modification or not, depending on the state of the + * copy/paste operation. + * + * @return true if there is an unfinished copy/paste operation, false otherwise. + */ + private boolean hasPendingPasteOperation() { + MainActivity mainActivity = mainFragment.getMainActivity(); + if (mainActivity == null) return false; + MainActivityActionMode mainActivityActionMode = mainActivity.mainActivityActionMode; + PasteHelper pasteHelper = mainActivityActionMode.getPasteHelper(); + + if (pasteHelper != null + && pasteHelper.getSnackbar() != null + && pasteHelper.getSnackbar().isShown()) { + Toast.makeText( + mainFragment.requireContext(), + mainFragment.getString(R.string.complete_paste_warning), + Toast.LENGTH_LONG) + .show(); + return true; + } + return false; + } + private boolean getBoolean(String key) { return preferenceActivity.getBoolean(key); } diff --git a/app/src/main/java/com/amaze/filemanager/adapters/SearchRecyclerViewAdapter.kt b/app/src/main/java/com/amaze/filemanager/adapters/SearchRecyclerViewAdapter.kt new file mode 100644 index 0000000000..5c6afc3259 --- /dev/null +++ b/app/src/main/java/com/amaze/filemanager/adapters/SearchRecyclerViewAdapter.kt @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2014-2023 Arpit Khurana , Vishal Nehra , + * Emmanuel Messulam, Raymond Lai and Contributors. + * + * This file is part of Amaze File Manager. + * + * Amaze File Manager is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.amaze.filemanager.adapters + +import android.content.Context +import android.text.Spannable +import android.text.SpannableString +import android.text.style.ForegroundColorSpan +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.appcompat.widget.AppCompatTextView +import androidx.core.content.ContextCompat +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import com.amaze.filemanager.R +import com.amaze.filemanager.application.AppConfig +import com.amaze.filemanager.asynchronous.asynctasks.searchfilesystem.SearchResult +import com.amaze.filemanager.ui.activities.MainActivity +import com.amaze.filemanager.ui.colors.ColorPreference +import java.util.Random + +class SearchRecyclerViewAdapter : + ListAdapter( + object : DiffUtil.ItemCallback() { + override fun areItemsTheSame( + oldItem: SearchResult, + newItem: SearchResult, + ): Boolean { + return oldItem.file.path == newItem.file.path && + oldItem.file.name == newItem.file.name + } + + override fun areContentsTheSame( + oldItem: SearchResult, + newItem: SearchResult, + ): Boolean { + return oldItem.file.path == newItem.file.path && + oldItem.file.name == newItem.file.name && + oldItem.matchRange == newItem.matchRange + } + }, + ) { + override fun onCreateViewHolder( + parent: ViewGroup, + type: Int, + ): ViewHolder { + val v: View = + LayoutInflater.from(parent.context) + .inflate(R.layout.search_row_item, parent, false) + return ViewHolder(v) + } + + override fun onBindViewHolder( + holder: SearchRecyclerViewAdapter.ViewHolder, + position: Int, + ) { + val (file, matchResult) = getItem(position) + + val colorPreference = + (AppConfig.getInstance().mainActivityContext as MainActivity).currentColorPreference + + val fileName = SpannableString(file.name) + fileName.setSpan( + ForegroundColorSpan(colorPreference.accent), + matchResult.first, + matchResult.last + 1, + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE, + ) + + holder.fileNameTV.text = fileName + holder.filePathTV.text = file.path.substring(0, file.path.lastIndexOf("/")) + + holder.colorView.setBackgroundColor(getRandomColor(holder.colorView.context)) + + if (file.isDirectory) { + holder.colorView.setBackgroundColor(colorPreference.primaryFirstTab) + } else { + holder.colorView.setBackgroundColor(colorPreference.accent) + } + } + + inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) { + val fileNameTV: AppCompatTextView + val filePathTV: AppCompatTextView + val colorView: View + + init { + + fileNameTV = view.findViewById(R.id.searchItemFileNameTV) + filePathTV = view.findViewById(R.id.searchItemFilePathTV) + colorView = view.findViewById(R.id.searchItemSampleColorView) + + view.setOnClickListener { + + val (file, _) = getItem(adapterPosition) + + if (!file.isDirectory) { + file.openFile( + AppConfig.getInstance().mainActivityContext as MainActivity?, + false, + ) + } else { + (AppConfig.getInstance().mainActivityContext as MainActivity?) + ?.goToMain(file.path) + } + + (AppConfig.getInstance().mainActivityContext as MainActivity?) + ?.appbar?.searchView?.hideSearchView() + } + } + } + + private fun getRandomColor(context: Context): Int { + return ContextCompat.getColor( + context, + ColorPreference.availableColors[ + Random().nextInt( + ColorPreference.availableColors.size - 1, + ), + ], + ) + } +} diff --git a/app/src/main/java/com/amaze/filemanager/adapters/data/AppDataParcelable.kt b/app/src/main/java/com/amaze/filemanager/adapters/data/AppDataParcelable.kt index 7f90bc8cbf..2ea826fa2d 100644 --- a/app/src/main/java/com/amaze/filemanager/adapters/data/AppDataParcelable.kt +++ b/app/src/main/java/com/amaze/filemanager/adapters/data/AppDataParcelable.kt @@ -35,5 +35,5 @@ class AppDataParcelable( var size: Long, var lastModification: Long, var isSystemApp: Boolean, - var openFileParcelable: OpenFileParcelable? + var openFileParcelable: OpenFileParcelable?, ) : Parcelable diff --git a/app/src/main/java/com/amaze/filemanager/adapters/data/AppDataSorter.kt b/app/src/main/java/com/amaze/filemanager/adapters/data/AppDataSorter.kt index 42e26b32a7..93a8219cda 100644 --- a/app/src/main/java/com/amaze/filemanager/adapters/data/AppDataSorter.kt +++ b/app/src/main/java/com/amaze/filemanager/adapters/data/AppDataSorter.kt @@ -21,7 +21,6 @@ package com.amaze.filemanager.adapters.data import com.amaze.filemanager.utils.safeLet -import java.util.Comparator class AppDataSorter(var sort: Int, isAscending: Boolean) : Comparator { @@ -31,7 +30,10 @@ class AppDataSorter(var sort: Int, isAscending: Boolean) : * Compares two elements and return negative, zero and positive integer if first argument is * less than, equal to or greater than second */ - override fun compare(file1: AppDataParcelable?, file2: AppDataParcelable?): Int { + override fun compare( + file1: AppDataParcelable?, + file2: AppDataParcelable?, + ): Int { safeLet(file1, file2) { f1, f2 -> if (f1.isSystemApp != f2.isSystemApp) { @@ -45,8 +47,9 @@ class AppDataSorter(var sort: Int, isAscending: Boolean) : } SORT_MODIF -> { // sort by last modified - return asc * java.lang.Long.valueOf(f1.lastModification) - .compareTo(f2.lastModification) + return asc * + java.lang.Long.valueOf(f1.lastModification) + .compareTo(f2.lastModification) } SORT_SIZE -> { // sort by size diff --git a/app/src/main/java/com/amaze/filemanager/adapters/data/CompressedObjectParcelable.java b/app/src/main/java/com/amaze/filemanager/adapters/data/CompressedObjectParcelable.java index f37b761bde..c9332c1944 100644 --- a/app/src/main/java/com/amaze/filemanager/adapters/data/CompressedObjectParcelable.java +++ b/app/src/main/java/com/amaze/filemanager/adapters/data/CompressedObjectParcelable.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. @@ -152,4 +152,13 @@ public boolean equals(Object obj) { && size == otherObj.size; } else return false; } + + @Override + public int hashCode() { + int result = (directory ? 1 : 0); + result = 31 * result + type; + result = 31 * result + name.hashCode(); + result = 31 * result + (int) (size ^ (size >>> 32)); + return result; + } } diff --git a/app/src/main/java/com/amaze/filemanager/adapters/data/IconDataParcelable.java b/app/src/main/java/com/amaze/filemanager/adapters/data/IconDataParcelable.java index 1761b7a3aa..4a34d93e30 100644 --- a/app/src/main/java/com/amaze/filemanager/adapters/data/IconDataParcelable.java +++ b/app/src/main/java/com/amaze/filemanager/adapters/data/IconDataParcelable.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. diff --git a/app/src/main/java/com/amaze/filemanager/adapters/data/LayoutElementParcelable.java b/app/src/main/java/com/amaze/filemanager/adapters/data/LayoutElementParcelable.java index 0a4d34b670..82539cefb3 100644 --- a/app/src/main/java/com/amaze/filemanager/adapters/data/LayoutElementParcelable.java +++ b/app/src/main/java/com/amaze/filemanager/adapters/data/LayoutElementParcelable.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. @@ -25,6 +25,7 @@ import com.amaze.filemanager.fileoperations.filesystem.OpenMode; import com.amaze.filemanager.filesystem.HybridFileParcelable; +import com.amaze.filemanager.filesystem.files.sort.ComparableParcelable; import com.amaze.filemanager.ui.icons.Icons; import com.amaze.filemanager.utils.Utils; @@ -35,7 +36,7 @@ import androidx.annotation.DrawableRes; import androidx.annotation.NonNull; -public class LayoutElementParcelable implements Parcelable { +public class LayoutElementParcelable implements Parcelable, ComparableParcelable { private static final String CURRENT_YEAR = String.valueOf(Calendar.getInstance().get(Calendar.YEAR)); @@ -47,10 +48,10 @@ public class LayoutElementParcelable implements Parcelable { public final String desc; public final String permissions; public final String symlink; - public final String size; - public final boolean isDirectory; - public final long date, longSize; - public final String dateModification; + public String size; + public boolean isDirectory; + public long date, longSize; + public String dateModification; public final boolean header; // same as hfile.modes but different than openmode in Main.java @@ -275,4 +276,25 @@ public LayoutElementParcelable[] newArray(int size) { return new LayoutElementParcelable[size]; } }; + + @Override + public boolean isDirectory() { + return isDirectory; + } + + @NonNull + @Override + public String getParcelableName() { + return title; + } + + @Override + public long getDate() { + return date; + } + + @Override + public long getSize() { + return longSize; + } } diff --git a/app/src/main/java/com/amaze/filemanager/adapters/data/OpenFileParcelable.kt b/app/src/main/java/com/amaze/filemanager/adapters/data/OpenFileParcelable.kt index 1507fef9a3..85618159eb 100644 --- a/app/src/main/java/com/amaze/filemanager/adapters/data/OpenFileParcelable.kt +++ b/app/src/main/java/com/amaze/filemanager/adapters/data/OpenFileParcelable.kt @@ -30,5 +30,5 @@ class OpenFileParcelable( var mimeType: String?, var useNewStack: Boolean?, var className: String?, - var packageName: String? + var packageName: String?, ) : Parcelable diff --git a/app/src/main/java/com/amaze/filemanager/adapters/data/StorageDirectoryParcelable.kt b/app/src/main/java/com/amaze/filemanager/adapters/data/StorageDirectoryParcelable.kt index 883d23293b..db94954518 100644 --- a/app/src/main/java/com/amaze/filemanager/adapters/data/StorageDirectoryParcelable.kt +++ b/app/src/main/java/com/amaze/filemanager/adapters/data/StorageDirectoryParcelable.kt @@ -32,18 +32,20 @@ data class StorageDirectoryParcelable( val name: String, @JvmField @DrawableRes - val iconRes: Int + val iconRes: Int, ) : Parcelable { - constructor(im: Parcel) : this( path = im.readString()!!, name = im.readString()!!, - iconRes = im.readInt() + iconRes = im.readInt(), ) override fun describeContents() = 0 - override fun writeToParcel(parcel: Parcel, i: Int) { + override fun writeToParcel( + parcel: Parcel, + i: Int, + ) { parcel.writeString(path) parcel.writeString(name) parcel.writeInt(iconRes) @@ -51,14 +53,15 @@ data class StorageDirectoryParcelable( companion object { @JvmField - val CREATOR = object : Parcelable.Creator { - override fun createFromParcel(parcel: Parcel): StorageDirectoryParcelable { - return StorageDirectoryParcelable(parcel) - } + val CREATOR = + object : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): StorageDirectoryParcelable { + return StorageDirectoryParcelable(parcel) + } - override fun newArray(size: Int): Array { - return arrayOfNulls(size) + override fun newArray(size: Int): Array { + return arrayOfNulls(size) + } } - } } } diff --git a/app/src/main/java/com/amaze/filemanager/adapters/glide/AppsAdapterPreloadModel.java b/app/src/main/java/com/amaze/filemanager/adapters/glide/AppsAdapterPreloadModel.java index 53d98db748..6651336998 100644 --- a/app/src/main/java/com/amaze/filemanager/adapters/glide/AppsAdapterPreloadModel.java +++ b/app/src/main/java/com/amaze/filemanager/adapters/glide/AppsAdapterPreloadModel.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. @@ -27,19 +27,18 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.amaze.filemanager.GlideApp; -import com.amaze.filemanager.GlideRequest; import com.amaze.filemanager.R; +import com.bumptech.glide.Glide; import com.bumptech.glide.ListPreloader; import com.bumptech.glide.RequestBuilder; import android.content.Context; import android.content.pm.PackageManager; import android.graphics.drawable.Drawable; -import android.widget.ImageView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.appcompat.widget.AppCompatImageView; import androidx.core.content.ContextCompat; import androidx.fragment.app.Fragment; @@ -51,12 +50,12 @@ public class AppsAdapterPreloadModel implements ListPreloader.PreloadModelProvid private final Logger LOG = LoggerFactory.getLogger(AppsAdapterPreloadModel.class); private Context mContext; - private GlideRequest request; + private RequestBuilder request; private List items; private boolean isBottomSheet; public AppsAdapterPreloadModel(Fragment f, boolean isBottomSheet) { - request = GlideApp.with(f).asDrawable().fitCenter(); + request = Glide.with(f).asDrawable().fitCenter(); this.mContext = f.requireContext(); this.isBottomSheet = isBottomSheet; } @@ -85,7 +84,7 @@ public RequestBuilder getPreloadRequestBuilder(String item) { } } - public void loadApkImage(String item, ImageView v) { + public void loadApkImage(String item, AppCompatImageView v) { if (isBottomSheet) { request.load(getApplicationIconFromPackageName(item)).into(v); } else { diff --git a/app/src/main/java/com/amaze/filemanager/adapters/glide/RecyclerPreloadModelProvider.java b/app/src/main/java/com/amaze/filemanager/adapters/glide/RecyclerPreloadModelProvider.java index 706c65e883..a892be042a 100644 --- a/app/src/main/java/com/amaze/filemanager/adapters/glide/RecyclerPreloadModelProvider.java +++ b/app/src/main/java/com/amaze/filemanager/adapters/glide/RecyclerPreloadModelProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. @@ -23,9 +23,8 @@ import java.util.Collections; import java.util.List; -import com.amaze.filemanager.GlideApp; -import com.amaze.filemanager.GlideRequest; import com.amaze.filemanager.adapters.data.IconDataParcelable; +import com.bumptech.glide.Glide; import com.bumptech.glide.ListPreloader; import com.bumptech.glide.RequestBuilder; import com.bumptech.glide.load.engine.DiskCacheStrategy; @@ -43,12 +42,12 @@ public class RecyclerPreloadModelProvider implements ListPreloader.PreloadModelProvider { private final List urisToLoad; - private final GlideRequest request; + private final RequestBuilder request; public RecyclerPreloadModelProvider( @NonNull Fragment fragment, @NonNull List uris, boolean isCircled) { urisToLoad = uris; - GlideRequest incompleteRequest = GlideApp.with(fragment).asDrawable(); + RequestBuilder incompleteRequest = Glide.with(fragment).asDrawable(); if (isCircled) { request = incompleteRequest.circleCrop(); diff --git a/app/src/main/java/com/amaze/filemanager/adapters/glide/RecyclerPreloadSizeProvider.java b/app/src/main/java/com/amaze/filemanager/adapters/glide/RecyclerPreloadSizeProvider.java index 21a14b2981..5bb868a8bf 100644 --- a/app/src/main/java/com/amaze/filemanager/adapters/glide/RecyclerPreloadSizeProvider.java +++ b/app/src/main/java/com/amaze/filemanager/adapters/glide/RecyclerPreloadSizeProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. diff --git a/app/src/main/java/com/amaze/filemanager/adapters/glide/apkimage/ApkImageDataFetcher.java b/app/src/main/java/com/amaze/filemanager/adapters/glide/apkimage/ApkImageDataFetcher.java index 4a46251b91..1cb4ca20dc 100644 --- a/app/src/main/java/com/amaze/filemanager/adapters/glide/apkimage/ApkImageDataFetcher.java +++ b/app/src/main/java/com/amaze/filemanager/adapters/glide/apkimage/ApkImageDataFetcher.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. diff --git a/app/src/main/java/com/amaze/filemanager/adapters/glide/apkimage/ApkImageModelLoader.java b/app/src/main/java/com/amaze/filemanager/adapters/glide/apkimage/ApkImageModelLoader.java index 18b66616eb..6cf0c89355 100644 --- a/app/src/main/java/com/amaze/filemanager/adapters/glide/apkimage/ApkImageModelLoader.java +++ b/app/src/main/java/com/amaze/filemanager/adapters/glide/apkimage/ApkImageModelLoader.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. diff --git a/app/src/main/java/com/amaze/filemanager/adapters/glide/apkimage/ApkImageModelLoaderFactory.java b/app/src/main/java/com/amaze/filemanager/adapters/glide/apkimage/ApkImageModelLoaderFactory.java index 5efc078ecb..45c3df7732 100644 --- a/app/src/main/java/com/amaze/filemanager/adapters/glide/apkimage/ApkImageModelLoaderFactory.java +++ b/app/src/main/java/com/amaze/filemanager/adapters/glide/apkimage/ApkImageModelLoaderFactory.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. diff --git a/app/src/main/java/com/amaze/filemanager/adapters/glide/cloudicon/CloudIconDataFetcher.kt b/app/src/main/java/com/amaze/filemanager/adapters/glide/cloudicon/CloudIconDataFetcher.kt index a33715505e..8e95b1f03e 100644 --- a/app/src/main/java/com/amaze/filemanager/adapters/glide/cloudicon/CloudIconDataFetcher.kt +++ b/app/src/main/java/com/amaze/filemanager/adapters/glide/cloudicon/CloudIconDataFetcher.kt @@ -35,21 +35,24 @@ class CloudIconDataFetcher( private val context: Context, private val path: String, private val width: Int, - private val height: Int + private val height: Int, ) : DataFetcher { - companion object { private val TAG = CloudIconDataFetcher::class.java.simpleName } private var inputStream: InputStream? = null - override fun loadData(priority: Priority, callback: DataFetcher.DataCallback) { + override fun loadData( + priority: Priority, + callback: DataFetcher.DataCallback, + ) { inputStream = CloudUtil.getThumbnailInputStreamForCloud(context, path) - val options = BitmapFactory.Options().also { - it.outWidth = width - it.outHeight = height - } + val options = + BitmapFactory.Options().also { + it.outWidth = width + it.outHeight = height + } val drawable = BitmapFactory.decodeStream(inputStream, null, options) callback.onDataReady(drawable) } diff --git a/app/src/main/java/com/amaze/filemanager/adapters/glide/cloudicon/CloudIconModelFactory.java b/app/src/main/java/com/amaze/filemanager/adapters/glide/cloudicon/CloudIconModelFactory.java index e69dd3aa03..caedb7d146 100644 --- a/app/src/main/java/com/amaze/filemanager/adapters/glide/cloudicon/CloudIconModelFactory.java +++ b/app/src/main/java/com/amaze/filemanager/adapters/glide/cloudicon/CloudIconModelFactory.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. diff --git a/app/src/main/java/com/amaze/filemanager/adapters/glide/cloudicon/CloudIconModelLoader.java b/app/src/main/java/com/amaze/filemanager/adapters/glide/cloudicon/CloudIconModelLoader.java index afc9e683e7..33edfb754d 100644 --- a/app/src/main/java/com/amaze/filemanager/adapters/glide/cloudicon/CloudIconModelLoader.java +++ b/app/src/main/java/com/amaze/filemanager/adapters/glide/cloudicon/CloudIconModelLoader.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. diff --git a/app/src/main/java/com/amaze/filemanager/adapters/holders/AppHolder.kt b/app/src/main/java/com/amaze/filemanager/adapters/holders/AppHolder.kt index b14091a4c0..84e85ab1f0 100644 --- a/app/src/main/java/com/amaze/filemanager/adapters/holders/AppHolder.kt +++ b/app/src/main/java/com/amaze/filemanager/adapters/holders/AppHolder.kt @@ -22,20 +22,21 @@ package com.amaze.filemanager.adapters.holders import android.view.View import android.view.ViewGroup -import android.widget.ImageButton -import android.widget.ImageView import android.widget.RelativeLayout -import android.widget.TextView +import androidx.appcompat.widget.AppCompatImageButton +import androidx.appcompat.widget.AppCompatImageView +import androidx.appcompat.widget.AppCompatTextView import androidx.core.view.marginBottom import androidx.core.view.marginLeft import androidx.core.view.marginTop import androidx.recyclerview.widget.RecyclerView import com.amaze.filemanager.R import com.amaze.filemanager.ui.views.ThemedTextView +import com.amaze.filemanager.utils.Utils class AppHolder(view: View) : RecyclerView.ViewHolder(view) { @JvmField - val apkIcon: ImageView = view.findViewById(R.id.apk_icon) + val apkIcon: AppCompatImageView = view.findViewById(R.id.apk_icon) @JvmField val txtTitle: ThemedTextView = view.findViewById(R.id.firstline) @@ -44,26 +45,31 @@ class AppHolder(view: View) : RecyclerView.ViewHolder(view) { val rl: RelativeLayout = view.findViewById(R.id.second) @JvmField - val txtDesc: TextView = view.findViewById(R.id.date) + val txtDesc: AppCompatTextView = view.findViewById(R.id.date) @JvmField - val about: ImageButton = view.findViewById(R.id.properties) + val about: AppCompatImageButton = view.findViewById(R.id.properties) @JvmField val summary: RelativeLayout = view.findViewById(R.id.summary) @JvmField - val packageName: TextView = view.findViewById(R.id.appManagerPackageName) + val packageName: AppCompatTextView = view.findViewById(R.id.appManagerPackageName) init { apkIcon.visibility = View.VISIBLE packageName.visibility = View.VISIBLE val layoutParams = txtDesc.layoutParams as ViewGroup.MarginLayoutParams - layoutParams.setMargins(txtDesc.marginLeft, txtDesc.marginTop, 8, txtDesc.marginBottom) + layoutParams.setMargins( + txtDesc.marginLeft, + txtDesc.marginTop, + Utils.dpToPx(view.context, 4), + txtDesc.marginBottom, + ) txtDesc.layoutParams = layoutParams - view.findViewById(R.id.picture_icon).visibility = View.GONE - view.findViewById(R.id.generic_icon).visibility = View.GONE + view.findViewById(R.id.picture_icon).visibility = View.GONE + view.findViewById(R.id.generic_icon).visibility = View.GONE } } diff --git a/app/src/main/java/com/amaze/filemanager/adapters/holders/CompressedItemViewHolder.kt b/app/src/main/java/com/amaze/filemanager/adapters/holders/CompressedItemViewHolder.kt index a49cd50a0c..bf1dc4d78d 100644 --- a/app/src/main/java/com/amaze/filemanager/adapters/holders/CompressedItemViewHolder.kt +++ b/app/src/main/java/com/amaze/filemanager/adapters/holders/CompressedItemViewHolder.kt @@ -21,8 +21,8 @@ package com.amaze.filemanager.adapters.holders import android.view.View -import android.widget.ImageView -import android.widget.TextView +import androidx.appcompat.widget.AppCompatImageView +import androidx.appcompat.widget.AppCompatTextView import androidx.recyclerview.widget.RecyclerView import com.amaze.filemanager.R import com.amaze.filemanager.ui.views.ThemedTextView @@ -30,28 +30,28 @@ import com.amaze.filemanager.ui.views.ThemedTextView class CompressedItemViewHolder(view: View) : RecyclerView.ViewHolder(view) { // each data item is just a string in this case @JvmField - val pictureIcon: ImageView = view.findViewById(R.id.picture_icon) + val pictureIcon: AppCompatImageView = view.findViewById(R.id.picture_icon) @JvmField - val genericIcon: ImageView = view.findViewById(R.id.generic_icon) + val genericIcon: AppCompatImageView = view.findViewById(R.id.generic_icon) @JvmField - val apkIcon: ImageView = view.findViewById(R.id.apk_icon) + val apkIcon: AppCompatImageView = view.findViewById(R.id.apk_icon) @JvmField val txtTitle: ThemedTextView = view.findViewById(R.id.firstline) @JvmField - val txtDesc: TextView = view.findViewById(R.id.secondLine) + val txtDesc: AppCompatTextView = view.findViewById(R.id.secondLine) @JvmField - val date: TextView = view.findViewById(R.id.date) + val date: AppCompatTextView = view.findViewById(R.id.date) - val perm: TextView = view.findViewById(R.id.permis) + val perm: AppCompatTextView = view.findViewById(R.id.permis) @JvmField val rl: View = view.findViewById(R.id.second) @JvmField - val checkImageView: ImageView = view.findViewById(R.id.check_icon) + val checkImageView: AppCompatImageView = view.findViewById(R.id.check_icon) } diff --git a/app/src/main/java/com/amaze/filemanager/adapters/holders/DonationViewHolder.kt b/app/src/main/java/com/amaze/filemanager/adapters/holders/DonationViewHolder.kt index c2593c297f..21a7a223d6 100644 --- a/app/src/main/java/com/amaze/filemanager/adapters/holders/DonationViewHolder.kt +++ b/app/src/main/java/com/amaze/filemanager/adapters/holders/DonationViewHolder.kt @@ -22,7 +22,7 @@ package com.amaze.filemanager.adapters.holders import android.view.View import android.widget.LinearLayout -import android.widget.TextView +import androidx.appcompat.widget.AppCompatTextView import androidx.recyclerview.widget.RecyclerView import com.amaze.filemanager.R @@ -31,11 +31,11 @@ class DonationViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { val ROOT_VIEW: LinearLayout = itemView.findViewById(R.id.adapter_donation_root) @JvmField - val TITLE: TextView = itemView.findViewById(R.id.adapter_donation_title) + val TITLE: AppCompatTextView = itemView.findViewById(R.id.adapter_donation_title) @JvmField - val SUMMARY: TextView = itemView.findViewById(R.id.adapter_donation_summary) + val SUMMARY: AppCompatTextView = itemView.findViewById(R.id.adapter_donation_summary) @JvmField - val PRICE: TextView = itemView.findViewById(R.id.adapter_donation_price) + val PRICE: AppCompatTextView = itemView.findViewById(R.id.adapter_donation_price) } diff --git a/app/src/main/java/com/amaze/filemanager/adapters/holders/HiddenViewHolder.kt b/app/src/main/java/com/amaze/filemanager/adapters/holders/HiddenViewHolder.kt index 0b19831a9f..23cdc016d9 100644 --- a/app/src/main/java/com/amaze/filemanager/adapters/holders/HiddenViewHolder.kt +++ b/app/src/main/java/com/amaze/filemanager/adapters/holders/HiddenViewHolder.kt @@ -21,9 +21,9 @@ package com.amaze.filemanager.adapters.holders import android.view.View -import android.widget.ImageButton import android.widget.LinearLayout -import android.widget.TextView +import androidx.appcompat.widget.AppCompatImageButton +import androidx.appcompat.widget.AppCompatTextView import androidx.recyclerview.widget.RecyclerView import com.amaze.filemanager.R @@ -34,13 +34,13 @@ import com.amaze.filemanager.R */ class HiddenViewHolder(view: View) : RecyclerView.ViewHolder(view) { @JvmField - val deleteButton: ImageButton = view.findViewById(R.id.delete_button) + val deleteButton: AppCompatImageButton = view.findViewById(R.id.delete_button) @JvmField - val textTitle: TextView = view.findViewById(R.id.filename) + val textTitle: AppCompatTextView = view.findViewById(R.id.filename) @JvmField - val textDescription: TextView = view.findViewById(R.id.file_path) + val textDescription: AppCompatTextView = view.findViewById(R.id.file_path) @JvmField val row: LinearLayout = view.findViewById(R.id.bookmarkrow) diff --git a/app/src/main/java/com/amaze/filemanager/adapters/holders/ItemViewHolder.kt b/app/src/main/java/com/amaze/filemanager/adapters/holders/ItemViewHolder.kt index 3681602210..a0e1685460 100644 --- a/app/src/main/java/com/amaze/filemanager/adapters/holders/ItemViewHolder.kt +++ b/app/src/main/java/com/amaze/filemanager/adapters/holders/ItemViewHolder.kt @@ -21,10 +21,10 @@ package com.amaze.filemanager.adapters.holders import android.view.View -import android.widget.ImageButton -import android.widget.ImageView import android.widget.RelativeLayout -import android.widget.TextView +import androidx.appcompat.widget.AppCompatImageButton +import androidx.appcompat.widget.AppCompatImageView +import androidx.appcompat.widget.AppCompatTextView import androidx.recyclerview.widget.RecyclerView import com.amaze.filemanager.R import com.amaze.filemanager.ui.views.ThemedTextView @@ -36,43 +36,43 @@ import com.amaze.filemanager.ui.views.ThemedTextView class ItemViewHolder(view: View) : RecyclerView.ViewHolder(view) { // each data item is just a string in this case @JvmField - val pictureIcon: ImageView? = view.findViewById(R.id.picture_icon) + val pictureIcon: AppCompatImageView? = view.findViewById(R.id.picture_icon) @JvmField - val genericIcon: ImageView = view.findViewById(R.id.generic_icon) + val genericIcon: AppCompatImageView = view.findViewById(R.id.generic_icon) @JvmField - val apkIcon: ImageView? = view.findViewById(R.id.apk_icon) + val apkIcon: AppCompatImageView? = view.findViewById(R.id.apk_icon) @JvmField - val imageView1: ImageView? = view.findViewById(R.id.icon_thumb) + val imageView1: AppCompatImageView? = view.findViewById(R.id.icon_thumb) @JvmField val txtTitle: ThemedTextView = view.findViewById(R.id.firstline) @JvmField - val txtDesc: TextView = view.findViewById(R.id.secondLine) + val txtDesc: AppCompatTextView = view.findViewById(R.id.secondLine) @JvmField - val date: TextView = view.findViewById(R.id.date) + val date: AppCompatTextView = view.findViewById(R.id.date) @JvmField - val perm: TextView = view.findViewById(R.id.permis) + val perm: AppCompatTextView = view.findViewById(R.id.permis) @JvmField val baseItemView: View = view.findViewById(R.id.second) @JvmField - val genericText: TextView? = view.findViewById(R.id.generictext) + val genericText: AppCompatTextView? = view.findViewById(R.id.generictext) @JvmField - val about: ImageButton = view.findViewById(R.id.properties) + val about: AppCompatImageButton = view.findViewById(R.id.properties) @JvmField - val checkImageView: ImageView? = view.findViewById(R.id.check_icon) + val checkImageView: AppCompatImageView? = view.findViewById(R.id.check_icon) @JvmField - val checkImageViewGrid: ImageView? = view.findViewById(R.id.check_icon_grid) + val checkImageViewGrid: AppCompatImageView? = view.findViewById(R.id.check_icon_grid) @JvmField val iconLayout: RelativeLayout? = view.findViewById(R.id.icon_frame_grid) diff --git a/app/src/main/java/com/amaze/filemanager/adapters/holders/SpecialViewHolder.kt b/app/src/main/java/com/amaze/filemanager/adapters/holders/SpecialViewHolder.kt index ec4a3e128e..f329e9685a 100644 --- a/app/src/main/java/com/amaze/filemanager/adapters/holders/SpecialViewHolder.kt +++ b/app/src/main/java/com/amaze/filemanager/adapters/holders/SpecialViewHolder.kt @@ -22,7 +22,7 @@ package com.amaze.filemanager.adapters.holders import android.content.Context import android.view.View -import android.widget.TextView +import androidx.appcompat.widget.AppCompatTextView import androidx.recyclerview.widget.RecyclerView import com.amaze.filemanager.R import com.amaze.filemanager.ui.provider.UtilitiesProvider @@ -36,10 +36,10 @@ class SpecialViewHolder( c: Context, view: View, utilsProvider: UtilitiesProvider, - val type: Int + val type: Int, ) : RecyclerView.ViewHolder(view) { // each data item is just a string in this case - private val txtTitle: TextView = view.findViewById(R.id.text) + private val txtTitle: AppCompatTextView = view.findViewById(R.id.text) companion object { const val HEADER_FILES = 0 diff --git a/app/src/main/java/com/amaze/filemanager/application/AppConfig.java b/app/src/main/java/com/amaze/filemanager/application/AppConfig.java index 09a33595f9..06a7a2b198 100644 --- a/app/src/main/java/com/amaze/filemanager/application/AppConfig.java +++ b/app/src/main/java/com/amaze/filemanager/application/AppConfig.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. @@ -20,6 +20,7 @@ package com.amaze.filemanager.application; +import java.io.File; import java.lang.ref.WeakReference; import java.util.concurrent.Callable; @@ -39,17 +40,21 @@ import com.amaze.filemanager.database.ExplorerDatabase; import com.amaze.filemanager.database.UtilitiesDatabase; import com.amaze.filemanager.database.UtilsHandler; +import com.amaze.filemanager.fileoperations.exceptions.ShellNotRunningException; +import com.amaze.filemanager.fileoperations.filesystem.OpenMode; +import com.amaze.filemanager.filesystem.HybridFile; import com.amaze.filemanager.filesystem.ssh.CustomSshJConfig; +import com.amaze.filemanager.ui.fragments.preferencefragments.PreferencesConstants; import com.amaze.filemanager.ui.provider.UtilitiesProvider; -import com.amaze.filemanager.utils.LruBitmapCache; import com.amaze.filemanager.utils.ScreenUtils; -import com.android.volley.RequestQueue; -import com.android.volley.toolbox.ImageLoader; -import com.android.volley.toolbox.Volley; +import com.amaze.trashbin.TrashBin; +import com.amaze.trashbin.TrashBinConfig; import android.app.Activity; import android.app.Application; import android.content.Context; +import android.content.SharedPreferences; +import android.os.Environment; import android.os.StrictMode; import android.widget.Toast; @@ -57,11 +62,13 @@ import androidx.annotation.Nullable; import androidx.annotation.StringRes; import androidx.appcompat.app.AppCompatDelegate; +import androidx.preference.PreferenceManager; import io.reactivex.Completable; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.schedulers.Schedulers; import jcifs.Config; +import jcifs.smb.SmbException; @AcraCore( buildConfigClass = BuildConfig.class, @@ -71,8 +78,6 @@ public class AppConfig extends GlideApplication { private Logger log = null; private UtilitiesProvider utilsProvider; - private RequestQueue requestQueue; - private ImageLoader imageLoader; private UtilsHandler utilsHandler; private WeakReference mainActivityContext; @@ -84,6 +89,11 @@ public class AppConfig extends GlideApplication { private ExplorerDatabase explorerDatabase; + private TrashBinConfig trashBinConfig; + private TrashBin trashBin; + private static final String TRASH_BIN_BASE_PATH = + Environment.getExternalStorageDirectory().getPath() + File.separator + ".AmazeData"; + public UtilitiesProvider getUtilsProvider() { return utilsProvider; } @@ -201,17 +211,6 @@ public static synchronized AppConfig getInstance() { return instance; } - public ImageLoader getImageLoader() { - if (requestQueue == null) { - requestQueue = Volley.newRequestQueue(getApplicationContext()); - } - - if (imageLoader == null) { - this.imageLoader = new ImageLoader(requestQueue, new LruBitmapCache()); - } - return imageLoader; - } - public UtilsHandler getUtilsHandler() { return utilsHandler; } @@ -270,4 +269,55 @@ protected void initACRA() { R.string.app_ui_crash)); } } + + public TrashBin getTrashBinInstance() { + if (trashBin == null) { + trashBin = + new TrashBin( + getApplicationContext(), + true, + getTrashBinConfig(), + s -> { + runInBackground( + () -> { + HybridFile file = new HybridFile(OpenMode.TRASH_BIN, s); + try { + file.delete(getMainActivityContext(), false); + } catch (ShellNotRunningException | SmbException e) { + log.warn("failed to delete file in trash bin cleanup", e); + } + }); + return true; + }, + null); + } + return trashBin; + } + + private TrashBinConfig getTrashBinConfig() { + if (trashBinConfig == null) { + SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this); + + int days = + sharedPrefs.getInt( + PreferencesConstants.KEY_TRASH_BIN_RETENTION_DAYS, + TrashBinConfig.RETENTION_DAYS_INFINITE); + long bytes = + sharedPrefs.getLong( + PreferencesConstants.KEY_TRASH_BIN_RETENTION_BYTES, + TrashBinConfig.RETENTION_BYTES_INFINITE); + int numOfFiles = + sharedPrefs.getInt( + PreferencesConstants.KEY_TRASH_BIN_RETENTION_NUM_OF_FILES, + TrashBinConfig.RETENTION_NUM_OF_FILES); + int intervalHours = + sharedPrefs.getInt( + PreferencesConstants.KEY_TRASH_BIN_CLEANUP_INTERVAL_HOURS, + TrashBinConfig.INTERVAL_CLEANUP_HOURS); + trashBinConfig = + new TrashBinConfig( + TRASH_BIN_BASE_PATH, days, bytes, numOfFiles, intervalHours, false, true); + } + return trashBinConfig; + } } diff --git a/app/src/main/java/com/amaze/filemanager/application/GlideApplication.java b/app/src/main/java/com/amaze/filemanager/application/GlideApplication.java index 32d5f2b4d6..8f234dc01c 100644 --- a/app/src/main/java/com/amaze/filemanager/application/GlideApplication.java +++ b/app/src/main/java/com/amaze/filemanager/application/GlideApplication.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/AbstractRepeatingRunnable.java b/app/src/main/java/com/amaze/filemanager/asynchronous/AbstractRepeatingRunnable.java index 8bd2230db3..0fe3a8a754 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/AbstractRepeatingRunnable.java +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/AbstractRepeatingRunnable.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/SaveOnDataUtilsChange.java b/app/src/main/java/com/amaze/filemanager/asynchronous/SaveOnDataUtilsChange.java index 25dc56f070..d19282ca35 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/SaveOnDataUtilsChange.java +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/SaveOnDataUtilsChange.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2021 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/AsyncTaskResult.java b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/AsyncTaskResult.java index a3123dddd7..a3fff01bbf 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/AsyncTaskResult.java +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/AsyncTaskResult.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/CountItemsOrAndSizeTask.java b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/CountItemsOrAndSizeTask.java index 2319ef3c30..78d2d58681 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/CountItemsOrAndSizeTask.java +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/CountItemsOrAndSizeTask.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. @@ -29,8 +29,8 @@ import android.content.Context; import android.os.AsyncTask; import android.text.format.Formatter; -import android.widget.TextView; +import androidx.appcompat.widget.AppCompatTextView; import androidx.core.util.Pair; /** @@ -39,12 +39,12 @@ public class CountItemsOrAndSizeTask extends AsyncTask, String> { private Context context; - private TextView itemsText; + private AppCompatTextView itemsText; private HybridFileParcelable file; private boolean isStorage; public CountItemsOrAndSizeTask( - Context c, TextView itemsText, HybridFileParcelable f, boolean storage) { + Context c, AppCompatTextView itemsText, HybridFileParcelable f, boolean storage) { this.context = c; this.itemsText = itemsText; file = f; diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/DbViewerTask.java b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/DbViewerTask.java index a4a3d79e6e..eea2b9649e 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/DbViewerTask.java +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/DbViewerTask.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/DeleteTask.java b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/DeleteTask.java index 15ec67cace..4f83f4bd0a 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/DeleteTask.java +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/DeleteTask.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. @@ -38,7 +38,7 @@ import com.amaze.filemanager.filesystem.SafRootHolder; import com.amaze.filemanager.filesystem.cloud.CloudUtil; import com.amaze.filemanager.filesystem.files.CryptUtil; -import com.amaze.filemanager.filesystem.files.FileUtils; +import com.amaze.filemanager.filesystem.files.MediaConnectionUtils; import com.amaze.filemanager.ui.activities.MainActivity; import com.amaze.filemanager.ui.fragments.CompressedExplorerFragment; import com.amaze.filemanager.ui.fragments.preferencefragments.PreferencesConstants; @@ -48,12 +48,9 @@ import com.cloudrail.si.interfaces.CloudStorage; import android.app.NotificationManager; -import android.content.ContentResolver; import android.content.Context; import android.content.Intent; -import android.net.Uri; import android.os.AsyncTask; -import android.provider.MediaStore; import android.widget.Toast; import androidx.annotation.NonNull; @@ -71,10 +68,13 @@ public class DeleteTask private final Context applicationContext; private final boolean rootMode; private CompressedExplorerFragment compressedExplorerFragment; + + private boolean doDeletePermanently; private final DataUtils dataUtils = DataUtils.getInstance(); - public DeleteTask(@NonNull Context applicationContext) { + public DeleteTask(@NonNull Context applicationContext, @NonNull boolean doDeletePermanently) { this.applicationContext = applicationContext.getApplicationContext(); + this.doDeletePermanently = doDeletePermanently; rootMode = PreferenceManager.getDefaultSharedPreferences(applicationContext) .getBoolean(PreferencesConstants.PREFERENCE_ROOTMODE, false); @@ -83,6 +83,7 @@ public DeleteTask(@NonNull Context applicationContext) { public DeleteTask( @NonNull Context applicationContext, CompressedExplorerFragment compressedExplorerFragment) { this.applicationContext = applicationContext.getApplicationContext(); + this.doDeletePermanently = false; rootMode = PreferenceManager.getDefaultSharedPreferences(applicationContext) .getBoolean(PreferencesConstants.PREFERENCE_ROOTMODE, false); @@ -112,13 +113,9 @@ protected final AsyncTaskResult doInBackground( } // delete file from media database - if (!file.isSmb()) { - try { - deleteFromMediaDatabase(applicationContext, file.getPath()); - } catch (Exception e) { - FileUtils.scanFile(applicationContext, files.toArray(new HybridFile[files.size()])); - } - } + if (!file.isSmb() && !file.isSftp()) + MediaConnectionUtils.scanFile( + applicationContext, files.toArray(new HybridFile[files.size()])); // delete file entry from encrypted database if (file.getName(applicationContext).endsWith(CryptUtil.CRYPT_EXTENSION)) { @@ -187,20 +184,19 @@ private boolean doDeleteFile(@NonNull HybridFileParcelable file) throws Exceptio } default: try { - return (file.delete(applicationContext, rootMode)); + /* SMB and SFTP (or any remote files that may support in the future) should not be + * supported by recycle bin. - TranceLove + */ + if (!doDeletePermanently + && !OpenMode.SMB.equals(file.getMode()) + && !OpenMode.SFTP.equals(file.getMode())) { + return file.moveToBin(applicationContext); + } + return file.delete(applicationContext, rootMode); } catch (ShellNotRunningException | SmbException e) { LOG.warn("failed to delete files", e); throw e; } } } - - private void deleteFromMediaDatabase(final Context context, final String file) { - final String where = MediaStore.MediaColumns.DATA + "=?"; - final String[] selectionArgs = new String[] {file}; - final ContentResolver contentResolver = context.getContentResolver(); - final Uri filesUri = MediaStore.Files.getContentUri("external"); - // Delete the entry from the media database. This will actually delete media files. - contentResolver.delete(filesUri, where, selectionArgs); - } } diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/LoadFilesListTask.java b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/LoadFilesListTask.java index 4ae437aaa6..2f1d29ef2f 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/LoadFilesListTask.java +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/LoadFilesListTask.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. @@ -49,6 +49,7 @@ import com.amaze.filemanager.filesystem.SafRootHolder; import com.amaze.filemanager.filesystem.cloud.CloudUtil; import com.amaze.filemanager.filesystem.files.FileListSorter; +import com.amaze.filemanager.filesystem.files.sort.SortType; import com.amaze.filemanager.filesystem.root.ListFilesCommand; import com.amaze.filemanager.ui.activities.MainActivityViewModel; import com.amaze.filemanager.ui.fragments.CloudSheetFragment; @@ -59,6 +60,9 @@ import com.amaze.filemanager.utils.OTGUtil; import com.amaze.filemanager.utils.OnAsyncTaskFinished; import com.amaze.filemanager.utils.OnFileFound; +import com.amaze.filemanager.utils.Utils; +import com.amaze.trashbin.TrashBin; +import com.amaze.trashbin.TrashBinFile; import com.cloudrail.si.interfaces.CloudStorage; import android.content.ContentResolver; @@ -72,9 +76,11 @@ import android.os.Bundle; import android.provider.MediaStore; import android.text.format.Formatter; +import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.appcompat.app.AlertDialog; import androidx.core.util.Pair; import jcifs.smb.SmbAuthException; @@ -83,7 +89,7 @@ import kotlin.collections.CollectionsKt; public class LoadFilesListTask - extends AsyncTask>> { + extends AsyncTask>> { private static final Logger LOG = LoggerFactory.getLogger(LoadFilesListTask.class); @@ -133,7 +139,9 @@ public LoadFilesListTask( MainFragmentViewModel mainFragmentViewModel = mainFragment.getMainFragmentViewModel(); MainActivityViewModel mainActivityViewModel = mainFragment.getMainActivityViewModel(); - if (OpenMode.UNKNOWN.equals(openmode) || OpenMode.CUSTOM.equals(openmode)) { + if (OpenMode.UNKNOWN.equals(openmode) + || OpenMode.CUSTOM.equals(openmode) + || OpenMode.TRASH_BIN.equals(openmode)) { hFile = new HybridFile(openmode, path); hFile.generateMode(mainFragment.getActivity()); openmode = hFile.getMode(); @@ -153,10 +161,12 @@ public LoadFilesListTask( case SMB: list = listSmb(hFile, mainActivityViewModel, mainFragment); break; + case FTP: case SFTP: list = listSftp(mainActivityViewModel); break; case CUSTOM: + case TRASH_BIN: list = getCachedMediaList(mainActivityViewModel); break; case OTG: @@ -189,7 +199,8 @@ public LoadFilesListTask( } if (list != null - && !(openmode == OpenMode.CUSTOM && ((path).equals("5") || (path).equals("6")))) { + && !(openmode == OpenMode.CUSTOM + && (("5").equals(path) || ("6").equals(path) || ("7").equals(path)))) { postListCustomPathProcess(list, mainFragment); } @@ -201,6 +212,41 @@ protected void onCancelled() { listener.onAsyncTaskFinished(null); } + @Override + protected void onProgressUpdate(Throwable... values) { + for (Throwable exception : values) { + if (exception instanceof SmbException) { + if ("/".equals(Uri.parse(path).getPath())) { + new AlertDialog.Builder(context.get()) + .setTitle(R.string.error_listfile_smb_title) + .setMessage( + context + .get() + .getString( + R.string.error_listfile_smb_noipcshare, + HybridFile.parseAndFormatUriForDisplay(path))) + .setPositiveButton( + android.R.string.ok, + (dialog, which) -> { + dialog.dismiss(); + }) + .show(); + } else { + Toast.makeText( + context.get(), + context + .get() + .getString( + R.string.error_listfile_smb, + HybridFile.parseAndFormatUriForDisplay(path), + exception.getMessage()), + Toast.LENGTH_LONG) + .show(); + } + } + } + } + @Override protected void onPostExecute(@Nullable Pair> list) { listener.onAsyncTaskFinished(list); @@ -212,6 +258,7 @@ private List getCachedMediaList( int mediaType = Integer.parseInt(path); if (5 == mediaType || 6 == mediaType + || 7 == mediaType || mainActivityViewModel.getMediaCacheHash().get(mediaType) == null || forceReload) { switch (Integer.parseInt(path)) { @@ -236,10 +283,13 @@ private List getCachedMediaList( case 6: list = listRecentFiles(); break; + case 7: + list = listTrashBinFiles(); + break; default: throw new IllegalStateException(); } - if (5 != mediaType && 6 != mediaType) { + if (5 != mediaType && 6 != mediaType && 7 != mediaType) { // not saving recent files in cache mainActivityViewModel.getMediaCacheHash().set(mediaType, list); } @@ -252,17 +302,7 @@ private List getCachedMediaList( private void postListCustomPathProcess( @NonNull List list, @NonNull MainFragment mainFragment) { - int sortType = SortHandler.getSortType(context.get(), path); - int sortBy; - int isAscending; - - if (sortType <= 3) { - sortBy = sortType; - isAscending = 1; - } else { - isAscending = -1; - sortBy = sortType - 4; - } + SortType sortType = SortHandler.getSortType(context.get(), path); MainFragmentViewModel viewModel = mainFragment.getMainFragmentViewModel(); @@ -287,7 +327,7 @@ private void postListCustomPathProcess( } } - Collections.sort(list, new FileListSorter(viewModel.getDsort(), sortBy, isAscending)); + Collections.sort(list, new FileListSorter(viewModel.getDsort(), sortType)); } private @Nullable LayoutElementParcelable createListParcelables(HybridFileParcelable baseFile) { @@ -554,6 +594,40 @@ else if (cursor.getCount() > 0 && cursor.moveToFirst()) { return recentFiles; } + private @Nullable List listTrashBinFiles() { + final Context context = this.context.get(); + + if (context == null) { + cancel(true); + return null; + } + + TrashBin trashBin = AppConfig.getInstance().getTrashBinInstance(); + List deletedFiles = new ArrayList<>(); + if (trashBin != null) { + for (TrashBinFile trashBinFile : trashBin.listFilesInBin()) { + HybridFile hybridFile = + new HybridFile( + OpenMode.TRASH_BIN, + trashBinFile.getDeletedPath( + AppConfig.getInstance().getTrashBinInstance().getConfig()), + trashBinFile.getFileName(), + trashBinFile.isDirectory()); + if (trashBinFile.getDeleteTime() != null) { + hybridFile.setLastModified(trashBinFile.getDeleteTime() * 1000); + } + LayoutElementParcelable element = hybridFile.generateLayoutElement(context, true); + element.date = trashBinFile.getDeleteTime(); + element.longSize = trashBinFile.getSizeBytes(); + element.size = Formatter.formatFileSize(context, trashBinFile.getSizeBytes()); + element.dateModification = Utils.getDate(context, trashBinFile.getDeleteTime() * 1000); + element.isDirectory = trashBinFile.isDirectory(); + deletedFiles.add(element); + } + } + return deletedFiles; + } + private @NonNull List listAppDataDirectories(@NonNull String basePath) { if (!GenericExtKt.containsPath(FileProperties.ANDROID_DEVICE_DATA_DIRS, basePath)) { throw new IllegalArgumentException("Invalid base path: [" + basePath + "]"); @@ -613,7 +687,8 @@ private List listSmb( if (!e.getMessage().toLowerCase().contains("denied")) { mainFragment.reauthenticateSmb(); } - LOG.warn("failed to load smb list, authentication issue", e); + LOG.warn("failed to load smb list, authentication issue: ", e); + publishProgress(e); return null; } catch (SmbException | NullPointerException e) { LOG.warn("Failed to load smb files for path: " + path, e); diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/LoadFolderSpaceDataTask.java b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/LoadFolderSpaceDataTask.java index fef19a1310..f0121c349b 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/LoadFolderSpaceDataTask.java +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/LoadFolderSpaceDataTask.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. @@ -137,7 +137,7 @@ private List createEntriesFromArray(long[] dataArray, boolean loading) } private void updateChart(String totalSpace, List entries) { - boolean isDarkTheme = appTheme.getMaterialDialogTheme(context) == Theme.DARK; + boolean isDarkTheme = appTheme.getMaterialDialogTheme() == Theme.DARK; PieDataSet set = new PieDataSet(entries, null); set.setColors(COLORS); diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/SearchTextTask.kt b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/SearchTextTask.kt index ee1f0f469d..98ab446fb7 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/SearchTextTask.kt +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/SearchTextTask.kt @@ -30,13 +30,12 @@ import org.slf4j.LoggerFactory import java.io.IOException import java.io.LineNumberReader import java.io.StringReader -import java.util.ArrayList class SearchTextTask( private val textToSearch: String, private val searchedText: String, private val updateListener: OnProgressUpdate, - private val listener: OnAsyncTaskFinished> + private val listener: OnAsyncTaskFinished>, ) : AsyncTask>() { private val lineNumberReader: LineNumberReader @@ -61,11 +60,12 @@ class SearchTextTask( log.warn("failed to search text", e) } charIndex = nextPosition - val index = SearchResultIndex( - charIndex, - charIndex + searchedText.length, - lineNumberReader.lineNumber - ) + val index = + SearchResultIndex( + charIndex, + charIndex + searchedText.length, + lineNumberReader.lineNumber, + ) searchResultIndices.add(index) publishProgress(index) charIndex++ diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/StatefulAsyncTask.kt b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/StatefulAsyncTask.kt index 1e36e891f8..89b2bbd1a7 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/StatefulAsyncTask.kt +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/StatefulAsyncTask.kt @@ -24,7 +24,6 @@ package com.amaze.filemanager.asynchronous.asynctasks * Interface to define state to Asynctask */ interface StatefulAsyncTask { - /** * Set callback to current async task. To be used to attach the context on * orientation change of fragment / activity diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/compress/AbstractCommonsArchiveHelperCallable.kt b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/compress/AbstractCommonsArchiveHelperCallable.kt index cd82d3eace..2aee2624aa 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/compress/AbstractCommonsArchiveHelperCallable.kt +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/compress/AbstractCommonsArchiveHelperCallable.kt @@ -32,15 +32,13 @@ import java.io.FileInputStream import java.io.IOException import java.io.InputStream import java.lang.ref.WeakReference -import java.util.* abstract class AbstractCommonsArchiveHelperCallable( context: Context, private val filePath: String, private val relativePath: String, - goBack: Boolean + goBack: Boolean, ) : CompressedHelperCallable(goBack) { - private val context: WeakReference = WeakReference(context) /** @@ -63,7 +61,7 @@ abstract class AbstractCommonsArchiveHelperCallable( if (!CompressedHelper.isEntryPathValid(name)) { AppConfig.toast( context.get(), - context.get()!!.getString(R.string.multiple_invalid_archive_entries) + context.get()!!.getString(R.string.multiple_invalid_archive_entries), ) return@run } @@ -76,15 +74,15 @@ abstract class AbstractCommonsArchiveHelperCallable( name.contains(CompressedHelper.SEPARATOR) && name.substring(0, name.lastIndexOf(CompressedHelper.SEPARATOR)) == relativePath - ) + ) if (isInBaseDir || isInRelativeDir) { elements.add( CompressedObjectParcelable( name, lastModifiedDate.time, size, - isDirectory - ) + isDirectory, + ), ) } } diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/compress/AbstractCompressedTarArchiveHelperCallable.kt b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/compress/AbstractCompressedTarArchiveHelperCallable.kt index bc720791bc..2ef831f663 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/compress/AbstractCompressedTarArchiveHelperCallable.kt +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/compress/AbstractCompressedTarArchiveHelperCallable.kt @@ -31,15 +31,15 @@ abstract class AbstractCompressedTarArchiveHelperCallable( context: Context, filePath: String, relativePath: String, - goBack: Boolean + goBack: Boolean, ) : AbstractCommonsArchiveHelperCallable(context, filePath, relativePath, goBack) { - private val compressorInputStreamConstructor: Constructor init { - compressorInputStreamConstructor = getCompressorInputStreamClass() - .getDeclaredConstructor(InputStream::class.java) + compressorInputStreamConstructor = + getCompressorInputStreamClass() + .getDeclaredConstructor(InputStream::class.java) compressorInputStreamConstructor.isAccessible = true } diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/compress/CompressedHelperCallable.kt b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/compress/CompressedHelperCallable.kt index 88e72fd6fd..5e0188353f 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/compress/CompressedHelperCallable.kt +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/compress/CompressedHelperCallable.kt @@ -23,27 +23,30 @@ package com.amaze.filemanager.asynchronous.asynctasks.compress import androidx.annotation.WorkerThread import com.amaze.filemanager.adapters.data.CompressedObjectParcelable import org.apache.commons.compress.archivers.ArchiveException -import java.util.* +import org.slf4j.Logger +import org.slf4j.LoggerFactory +import java.util.Collections import java.util.concurrent.Callable abstract class CompressedHelperCallable internal constructor( - private val createBackItem: Boolean + private val createBackItem: Boolean, ) : Callable> { + protected val logger: Logger = LoggerFactory.getLogger(javaClass) - @WorkerThread - @Throws(ArchiveException::class) - override fun call(): ArrayList { - val elements = ArrayList() - if (createBackItem) { - elements.add(0, CompressedObjectParcelable()) + @WorkerThread + @Throws(ArchiveException::class) + override fun call(): ArrayList { + val elements = ArrayList() + if (createBackItem) { + elements.add(0, CompressedObjectParcelable()) + } + + addElements(elements) + Collections.sort(elements, CompressedObjectParcelable.Sorter()) + return elements } - addElements(elements) - Collections.sort(elements, CompressedObjectParcelable.Sorter()) - return elements + @Throws(ArchiveException::class) + protected abstract fun addElements(elements: ArrayList) } - - @Throws(ArchiveException::class) - protected abstract fun addElements(elements: ArrayList) -} diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/compress/SevenZipHelperCallable.kt b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/compress/SevenZipHelperCallable.kt index 4b5fc19f41..45790663fb 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/compress/SevenZipHelperCallable.kt +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/compress/SevenZipHelperCallable.kt @@ -20,63 +20,81 @@ package com.amaze.filemanager.asynchronous.asynctasks.compress -import android.util.Log import com.amaze.filemanager.adapters.data.CompressedObjectParcelable import com.amaze.filemanager.fileoperations.filesystem.compressed.ArchivePasswordCache import com.amaze.filemanager.filesystem.compressed.CompressedHelper +import com.amaze.filemanager.filesystem.compressed.CompressedHelper.SEPARATOR import com.amaze.filemanager.filesystem.compressed.sevenz.SevenZFile import org.apache.commons.compress.PasswordRequiredException import org.apache.commons.compress.archivers.ArchiveException import java.io.File import java.io.IOException -import java.lang.UnsupportedOperationException class SevenZipHelperCallable( private val filePath: String, private val relativePath: String, - goBack: Boolean + goBack: Boolean, ) : CompressedHelperCallable(goBack) { - @Throws(ArchiveException::class) @Suppress("Detekt.RethrowCaughtException") override fun addElements(elements: ArrayList) { try { - val sevenzFile = if (ArchivePasswordCache.getInstance().containsKey(filePath)) { - SevenZFile( - File(filePath), - ArchivePasswordCache.getInstance()[filePath]!!.toCharArray() - ) - } else { - SevenZFile(File(filePath)) - } - for (entry in sevenzFile.entries) { - val name = entry.name - val isInBaseDir = ( - relativePath == "" && - !name.contains(CompressedHelper.SEPARATOR) + val sevenzFile = + if (ArchivePasswordCache.getInstance().containsKey(filePath)) { + SevenZFile( + File(filePath), + ArchivePasswordCache.getInstance()[filePath]!!.toCharArray(), ) - val isInRelativeDir = ( - name.contains(CompressedHelper.SEPARATOR) && - name.substring(0, name.lastIndexOf(CompressedHelper.SEPARATOR)) - == relativePath - ) - if (isInBaseDir || isInRelativeDir) { + } else { + SevenZFile(File(filePath)) + } + + val entriesMap = sevenzFile.entries.associateBy { it.name } + val entries = HashSet() + + // Start filter out the paths we need to present based on relativePath + + entries.addAll( + consolidate( + entriesMap.keys.filter { + it.startsWith(relativePath) + }, + if (relativePath == "") { + 0 + } else if (relativePath.isNotBlank() && !relativePath.contains(SEPARATOR)) { + 1 + } else { + relativePath.count { it == CompressedHelper.SEPARATOR_CHAR } + 1 + }, + ), + ) + + entries.forEach { path -> + if (entriesMap.containsKey(path)) { + entriesMap[path]?.let { entry -> + elements.add( + CompressedObjectParcelable( + entry.name, + try { + entry.lastModifiedDate.time + } catch (e: UnsupportedOperationException) { + logger.warn("Unable to get modified date for 7zip file", e) + 0L + }, + entry.size, + entry.isDirectory, + ), + ) + } + } else { elements.add( CompressedObjectParcelable( - entry.name, - try { - entry.lastModifiedDate.time - } catch (e: UnsupportedOperationException) { - Log.w( - javaClass.simpleName, - "Unable to get modified date for 7zip file" - ) - 0L - }, - entry.size, - entry.isDirectory - ) + path, + 0L, + 0, + true, + ), ) } } @@ -87,4 +105,34 @@ class SevenZipHelperCallable( throw ArchiveException(String.format("7zip archive %s is corrupt", filePath)) } } + + internal fun consolidate( + paths: Collection, + level: Int = 0, + ): Set { + return paths.mapNotNull { path -> + when (level) { + 0 -> { + if (path.contains(SEPARATOR)) { + path.substringBefore(SEPARATOR) + } else { + path + } + } + else -> { + if (path.contains(SEPARATOR)) { + path.split(SEPARATOR).let { + if (it.size > level) { + it.subList(0, level + 1).joinToString(SEPARATOR) + } else { + null + } + } + } else { + null + } + } + } + }.toSet() + } } diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/compress/TarBzip2HelperCallable.kt b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/compress/TarBzip2HelperCallable.kt index 444a0f59da..b5650ae5c7 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/compress/TarBzip2HelperCallable.kt +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/compress/TarBzip2HelperCallable.kt @@ -28,10 +28,8 @@ class TarBzip2HelperCallable( context: Context, filePath: String, relativePath: String, - goBack: Boolean + goBack: Boolean, ) : AbstractCompressedTarArchiveHelperCallable(context, filePath, relativePath, goBack) { - - override fun getCompressorInputStreamClass(): Class = - BZip2CompressorInputStream::class.java + override fun getCompressorInputStreamClass(): Class = BZip2CompressorInputStream::class.java } diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/compress/TarGzHelperCallable.kt b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/compress/TarGzHelperCallable.kt index 1f19fb47a0..9de41dd6d5 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/compress/TarGzHelperCallable.kt +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/compress/TarGzHelperCallable.kt @@ -28,10 +28,8 @@ class TarGzHelperCallable( context: Context, filePath: String, relativePath: String, - goBack: Boolean + goBack: Boolean, ) : AbstractCompressedTarArchiveHelperCallable(context, filePath, relativePath, goBack) { - - override fun getCompressorInputStreamClass(): Class = - GzipCompressorInputStream::class.java + override fun getCompressorInputStreamClass(): Class = GzipCompressorInputStream::class.java } diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/compress/TarHelperCallable.kt b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/compress/TarHelperCallable.kt index 37b31265b3..9c620f3369 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/compress/TarHelperCallable.kt +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/compress/TarHelperCallable.kt @@ -29,10 +29,8 @@ class TarHelperCallable( context: Context, filePath: String, relativePath: String, - goBack: Boolean + goBack: Boolean, ) : AbstractCommonsArchiveHelperCallable(context, filePath, relativePath, goBack) { - - override fun createFrom(inputStream: InputStream): ArchiveInputStream = - TarArchiveInputStream(inputStream) + override fun createFrom(inputStream: InputStream): ArchiveInputStream = TarArchiveInputStream(inputStream) } diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/compress/TarLzmaHelperCallable.kt b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/compress/TarLzmaHelperCallable.kt index 94e947a4e1..41e302d4aa 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/compress/TarLzmaHelperCallable.kt +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/compress/TarLzmaHelperCallable.kt @@ -28,10 +28,8 @@ class TarLzmaHelperCallable( context: Context, filePath: String, relativePath: String, - goBack: Boolean + goBack: Boolean, ) : AbstractCompressedTarArchiveHelperCallable(context, filePath, relativePath, goBack) { - - override fun getCompressorInputStreamClass(): Class = - LZMACompressorInputStream::class.java + override fun getCompressorInputStreamClass(): Class = LZMACompressorInputStream::class.java } diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/compress/TarXzHelperCallable.kt b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/compress/TarXzHelperCallable.kt index 228dfea621..24fe3e3192 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/compress/TarXzHelperCallable.kt +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/compress/TarXzHelperCallable.kt @@ -28,10 +28,8 @@ class TarXzHelperCallable( context: Context, filePath: String, relativePath: String, - goBack: Boolean + goBack: Boolean, ) : AbstractCompressedTarArchiveHelperCallable(context, filePath, relativePath, goBack) { - - override fun getCompressorInputStreamClass(): Class = - XZCompressorInputStream::class.java + override fun getCompressorInputStreamClass(): Class = XZCompressorInputStream::class.java } diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/compress/UnknownCompressedFileHelperCallable.kt b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/compress/UnknownCompressedFileHelperCallable.kt index e5c90b4582..56c49e0859 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/compress/UnknownCompressedFileHelperCallable.kt +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/compress/UnknownCompressedFileHelperCallable.kt @@ -21,7 +21,6 @@ package com.amaze.filemanager.asynchronous.asynctasks.compress import com.amaze.filemanager.adapters.data.CompressedObjectParcelable -import java.util.ArrayList /** * For gzip, bz2, lzma and xz compressed files. @@ -52,10 +51,9 @@ import java.util.ArrayList */ class UnknownCompressedFileHelperCallable( private val filePath: String, - goBack: Boolean + goBack: Boolean, ) : CompressedHelperCallable(goBack) { - override fun addElements(elements: ArrayList) { val entryName = filePath.substringAfterLast('/').substringBeforeLast('.') elements.add( @@ -63,8 +61,8 @@ class UnknownCompressedFileHelperCallable( entryName, 0L, 0L, - false - ) + false, + ), ) } } diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/compress/ZipHelperCallable.kt b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/compress/ZipHelperCallable.kt index 3ab30a333e..e8387d80b2 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/compress/ZipHelperCallable.kt +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/compress/ZipHelperCallable.kt @@ -32,102 +32,104 @@ import net.lingala.zip4j.model.FileHeader import org.apache.commons.compress.archivers.ArchiveException import java.io.File import java.lang.ref.WeakReference -import kotlin.collections.ArrayList class ZipHelperCallable( c: Context, realFileDirectory: String, dir: String?, - goback: Boolean + goback: Boolean, ) : CompressedHelperCallable(goback) { - private val context: WeakReference = WeakReference(c) private val fileLocation: Uri = Uri.parse(realFileDirectory) private val relativeDirectory: String? = dir @Throws(ArchiveException::class) @Suppress("ComplexMethod", "LongMethod") - public override fun addElements(elements: ArrayList) = try { - fileLocation.path?.run { - val zipfile = ZipFile(fileLocation.path) - val wholelist = filterValidEntryList(zipfile) - val strings = ArrayList() - for (entry in wholelist) { - val file = File(entry.path) - val y = entry.path.let { - if (it.startsWith("/")) { - it.substring(1, it.length) - } else { - it - } - } - if (relativeDirectory == null || relativeDirectory.trim { it <= ' ' }.isEmpty()) { - var path: String - var zipObj: CompressedObjectParcelable - if (file.parent == null || file.parent!!.isEmpty() || file.parent == "/") { - path = y - zipObj = CompressedObjectParcelable( - y, - entry.date, - entry.size, - entry.directory - ) - } else { - path = y.substring(0, y.indexOf("/") + 1) - zipObj = CompressedObjectParcelable( - path, - entry.date, - entry.size, - true - ) - } - if (!strings.contains(path)) { - elements.add(zipObj) - strings.add(path) - } - } else { - if (file.parent != null && - ( - file.parent == relativeDirectory || - file.parent == "/$relativeDirectory" - ) - ) { - if (!strings.contains(y)) { - elements.add( + public override fun addElements(elements: ArrayList) = + try { + fileLocation.path?.run { + val zipfile = ZipFile(fileLocation.path) + val wholelist = filterValidEntryList(zipfile) + val strings = ArrayList() + for (entry in wholelist) { + val file = File(entry.path) + val y = + entry.path.let { + if (it.startsWith("/")) { + it.substring(1, it.length) + } else { + it + } + } + if (relativeDirectory == null || relativeDirectory.trim { it <= ' ' }.isEmpty()) { + var path: String + var zipObj: CompressedObjectParcelable + if (file.parent == null || file.parent!!.isEmpty() || file.parent == "/") { + path = y + zipObj = CompressedObjectParcelable( y, entry.date, entry.size, - entry.directory + entry.directory, ) - ) - strings.add(y) - } - } else if (y.startsWith("$relativeDirectory/") && - y.length > relativeDirectory.length + 1 - ) { - val path1 = y.substring(relativeDirectory.length + 1, y.length) - val index = relativeDirectory.length + 1 + path1.indexOf("/") - val path = y.substring(0, index + 1) - if (!strings.contains(path)) { - elements.add( + } else { + path = y.substring(0, y.indexOf("/") + 1) + zipObj = CompressedObjectParcelable( - y.substring(0, index + 1), + path, entry.date, entry.size, - true + true, ) - ) + } + if (!strings.contains(path)) { + elements.add(zipObj) strings.add(path) } + } else { + if (file.parent != null && + ( + file.parent == relativeDirectory || + file.parent == "/$relativeDirectory" + ) + ) { + if (!strings.contains(y)) { + elements.add( + CompressedObjectParcelable( + y, + entry.date, + entry.size, + entry.directory, + ), + ) + strings.add(y) + } + } else if (y.startsWith("$relativeDirectory/") && + y.length > relativeDirectory.length + 1 + ) { + val path1 = y.substring(relativeDirectory.length + 1, y.length) + val index = relativeDirectory.length + 1 + path1.indexOf("/") + val path = y.substring(0, index + 1) + if (!strings.contains(path)) { + elements.add( + CompressedObjectParcelable( + y.substring(0, index + 1), + entry.date, + entry.size, + true, + ), + ) + strings.add(path) + } + } } } - } - } ?: throw ArchiveException(null) - } catch (e: ZipException) { - throw ArchiveException("Zip file is corrupt", e) - } + } ?: throw ArchiveException(null) + } catch (e: ZipException) { + throw ArchiveException("Zip file is corrupt", e) + } private fun filterValidEntryList(zipFile: ZipFile): List { val retval = ArrayList() @@ -137,7 +139,7 @@ class ZipHelperCallable( if (!CompressedHelper.isEntryPathValid(entry.fileName)) { AppConfig.toast( context.get(), - context.get()!!.getString(R.string.multiple_invalid_archive_entries) + context.get()!!.getString(R.string.multiple_invalid_archive_entries), ) continue } @@ -146,8 +148,8 @@ class ZipHelperCallable( entry.fileName, entry.lastModifiedTimeEpoch, entry.uncompressedSize, - entry.isDirectory - ) + entry.isDirectory, + ), ) } return retval diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/ftp/AbstractGetHostInfoTask.kt b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/ftp/AbstractGetHostInfoTask.kt index d8fea436bc..de2cd9f893 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/ftp/AbstractGetHostInfoTask.kt +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/ftp/AbstractGetHostInfoTask.kt @@ -31,9 +31,8 @@ import java.util.concurrent.Callable abstract class AbstractGetHostInfoTask>( private val hostname: String, private val port: Int, - private val callback: (V) -> Unit + private val callback: (V) -> Unit, ) : Task { - private lateinit var progressDialog: ProgressDialog /** @@ -42,11 +41,12 @@ abstract class AbstractGetHostInfoTask>( @MainThread open fun onPreExecute() { AppConfig.getInstance().run { - progressDialog = ProgressDialog.show( - this.mainActivityContext, - "", - this.resources.getString(R.string.processing) - ) + progressDialog = + ProgressDialog.show( + this.mainActivityContext, + "", + this.resources.getString(R.string.processing), + ) } } @@ -63,9 +63,9 @@ abstract class AbstractGetHostInfoTask>( R.string.ssh_connect_failed, hostname, port, - error.localizedMessage + error.localizedMessage, ), - Toast.LENGTH_LONG + Toast.LENGTH_LONG, ).show() } diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/ftp/auth/FtpAuthenticationTask.kt b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/ftp/auth/FtpAuthenticationTask.kt index d9132a0425..64983a3da5 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/ftp/auth/FtpAuthenticationTask.kt +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/ftp/auth/FtpAuthenticationTask.kt @@ -37,16 +37,16 @@ class FtpAuthenticationTask( private val port: Int, private val certInfo: JSONObject?, private val username: String, - private val password: String? + private val password: String?, + private val explicitTls: Boolean = false, ) : Task { - override fun getTask(): FtpAuthenticationTaskCallable { return if (protocol == FTP_URI_PREFIX) { FtpAuthenticationTaskCallable( host, port, username, - password ?: "" + password ?: "", ) } else { FtpsAuthenticationTaskCallable( @@ -54,7 +54,8 @@ class FtpAuthenticationTask( port, certInfo!!, username, - password ?: "" + password ?: "", + explicitTls, ) } } @@ -71,8 +72,8 @@ class FtpAuthenticationTask( R.string.ssh_connect_failed, host, port, - error.localizedMessage ?: error.message - ) + error.localizedMessage ?: error.message, + ), ) } } diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/ftp/auth/FtpAuthenticationTaskCallable.kt b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/ftp/auth/FtpAuthenticationTaskCallable.kt index 021663f89b..ae1cfe8393 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/ftp/auth/FtpAuthenticationTaskCallable.kt +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/ftp/auth/FtpAuthenticationTaskCallable.kt @@ -37,29 +37,29 @@ open class FtpAuthenticationTaskCallable( protected val hostname: String, protected val port: Int, protected val username: String, - protected val password: String + protected val password: String, ) : Callable { - @WorkerThread override fun call(): FTPClient { val ftpClient = createFTPClient() ftpClient.connectTimeout = CONNECT_TIMEOUT ftpClient.controlEncoding = Charsets.UTF_8.name() ftpClient.connect(hostname, port) - val loginSuccess = if (username.isBlank() && password.isBlank()) { - ftpClient.login( - FTPClientImpl.ANONYMOUS, - FTPClientImpl.generateRandomEmailAddressForLogin() - ) - } else { - ftpClient.login( - decode(username, UTF_8.name()), - decode( - PasswordUtil.decryptPassword(AppConfig.getInstance(), password), - UTF_8.name() + val loginSuccess = + if (username.isBlank() && password.isBlank()) { + ftpClient.login( + FTPClientImpl.ANONYMOUS, + FTPClientImpl.generateRandomEmailAddressForLogin(), ) - ) - } + } else { + ftpClient.login( + decode(username, UTF_8.name()), + decode( + PasswordUtil.decryptPassword(AppConfig.getInstance(), password), + UTF_8.name(), + ), + ) + } return if (loginSuccess) { ftpClient.enterLocalPassiveMode() ftpClient @@ -68,6 +68,5 @@ open class FtpAuthenticationTaskCallable( } } - protected open fun createFTPClient(): FTPClient = - NetCopyClientConnectionPool.ftpClientFactory.create(FTP_URI_PREFIX) + protected open fun createFTPClient(): FTPClient = NetCopyClientConnectionPool.ftpClientFactory.create(FTP_URI_PREFIX) } diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/ftp/auth/FtpsAuthenticationTaskCallable.kt b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/ftp/auth/FtpsAuthenticationTaskCallable.kt index 5d28d1d9e9..aa5e9b0d90 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/ftp/auth/FtpsAuthenticationTaskCallable.kt +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/ftp/auth/FtpsAuthenticationTaskCallable.kt @@ -22,6 +22,8 @@ package com.amaze.filemanager.asynchronous.asynctasks.ftp.auth import com.amaze.filemanager.application.AppConfig import com.amaze.filemanager.filesystem.ftp.FTPClientImpl +import com.amaze.filemanager.filesystem.ftp.FTPClientImpl.Companion.ARG_TLS +import com.amaze.filemanager.filesystem.ftp.FTPClientImpl.Companion.TLS_EXPLICIT import com.amaze.filemanager.filesystem.ftp.NetCopyClientConnectionPool import com.amaze.filemanager.filesystem.ftp.NetCopyClientConnectionPool.FTPS_URI_PREFIX import com.amaze.filemanager.utils.PasswordUtil @@ -38,25 +40,26 @@ class FtpsAuthenticationTaskCallable( port: Int, private val certInfo: JSONObject, username: String, - password: String + password: String, + private val explicitTls: Boolean, ) : FtpAuthenticationTaskCallable(hostname, port, username, password) { - override fun call(): FTPClient { val ftpClient = createFTPClient() as FTPSClient ftpClient.connectTimeout = NetCopyClientConnectionPool.CONNECT_TIMEOUT ftpClient.controlEncoding = Charsets.UTF_8.name() ftpClient.connect(hostname, port) - val loginSuccess = if (username.isBlank() && password.isBlank()) { - ftpClient.login( - FTPClientImpl.ANONYMOUS, - FTPClientImpl.generateRandomEmailAddressForLogin() - ) - } else { - ftpClient.login( - username, - PasswordUtil.decryptPassword(AppConfig.getInstance(), password) - ) - } + val loginSuccess = + if (username.isBlank() && password.isBlank()) { + ftpClient.login( + FTPClientImpl.ANONYMOUS, + FTPClientImpl.generateRandomEmailAddressForLogin(), + ) + } else { + ftpClient.login( + username, + PasswordUtil.decryptPassword(AppConfig.getInstance(), password), + ) + } return if (loginSuccess) { // RFC 2228 set protection buffer size to 0 ftpClient.execPBSZ(0) @@ -71,19 +74,27 @@ class FtpsAuthenticationTaskCallable( @Suppress("LabeledExpression") override fun createFTPClient(): FTPClient { + val uri = + buildString { + append(FTPS_URI_PREFIX) + if (explicitTls) { + append("?$ARG_TLS=$TLS_EXPLICIT") + } + } return ( - NetCopyClientConnectionPool.ftpClientFactory.create(FTPS_URI_PREFIX) + NetCopyClientConnectionPool.ftpClientFactory.create(uri.toString()) as FTPSClient - ).apply { - this.hostnameVerifier = HostnameVerifier { _, session -> - return@HostnameVerifier if (session.peerCertificateChain.isNotEmpty()) { - X509CertificateUtil.parse( - session.peerCertificateChain.first() - )[FINGERPRINT] == certInfo.get(FINGERPRINT) - } else { - false + ).apply { + this.hostnameVerifier = + HostnameVerifier { _, session -> + return@HostnameVerifier if (session.peerCertificateChain.isNotEmpty()) { + X509CertificateUtil.parse( + session.peerCertificateChain.first(), + )[FINGERPRINT] == certInfo.get(FINGERPRINT) + } else { + false + } } - } } } } diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/ftp/hostcert/FtpsGetHostCertificateTask.kt b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/ftp/hostcert/FtpsGetHostCertificateTask.kt index 2fa37674f2..b4c507fd01 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/ftp/hostcert/FtpsGetHostCertificateTask.kt +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/ftp/hostcert/FtpsGetHostCertificateTask.kt @@ -28,12 +28,11 @@ import java.lang.ref.WeakReference class FtpsGetHostCertificateTask( private val host: String, private val port: Int, + private val explicitTls: Boolean = false, context: Context, - callback: (JSONObject) -> Unit + callback: (JSONObject) -> Unit, ) : AbstractGetHostInfoTask(host, port, callback) { - val ctx: WeakReference = WeakReference(context) - override fun getTask(): FtpsGetHostCertificateTaskCallable = - FtpsGetHostCertificateTaskCallable(host, port) + override fun getTask(): FtpsGetHostCertificateTaskCallable = FtpsGetHostCertificateTaskCallable(host, port, explicitTls) } diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/ftp/hostcert/FtpsGetHostCertificateTaskCallable.kt b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/ftp/hostcert/FtpsGetHostCertificateTaskCallable.kt index 0cc7c476c4..42373ce5cf 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/ftp/hostcert/FtpsGetHostCertificateTaskCallable.kt +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/ftp/hostcert/FtpsGetHostCertificateTaskCallable.kt @@ -21,6 +21,8 @@ package com.amaze.filemanager.asynchronous.asynctasks.ftp.hostcert import androidx.annotation.WorkerThread +import com.amaze.filemanager.filesystem.ftp.FTPClientImpl.Companion.ARG_TLS +import com.amaze.filemanager.filesystem.ftp.FTPClientImpl.Companion.TLS_EXPLICIT import com.amaze.filemanager.filesystem.ftp.NetCopyClientConnectionPool import com.amaze.filemanager.filesystem.ftp.NetCopyClientConnectionPool.CONNECT_TIMEOUT import com.amaze.filemanager.filesystem.ftp.NetCopyClientConnectionPool.FTPS_URI_PREFIX @@ -33,9 +35,9 @@ import javax.net.ssl.HostnameVerifier open class FtpsGetHostCertificateTaskCallable( private val hostname: String, - private val port: Int + private val port: Int, + private val explicitTls: Boolean = false, ) : Callable { - @WorkerThread override fun call(): JSONObject? { val latch = CountDownLatch(1) @@ -43,14 +45,15 @@ open class FtpsGetHostCertificateTaskCallable( val ftpClient = createFTPClient() ftpClient.connectTimeout = CONNECT_TIMEOUT ftpClient.controlEncoding = Charsets.UTF_8.name() - ftpClient.hostnameVerifier = HostnameVerifier { _, session -> - if (session.peerCertificateChain.isNotEmpty()) { - val certinfo = X509CertificateUtil.parse(session.peerCertificateChain[0]) - result = JSONObject(certinfo) + ftpClient.hostnameVerifier = + HostnameVerifier { _, session -> + if (session.peerCertificateChain.isNotEmpty()) { + val certinfo = X509CertificateUtil.parse(session.peerCertificateChain[0]) + result = JSONObject(certinfo) + } + latch.countDown() + true } - latch.countDown() - true - } ftpClient.connect(hostname, port) latch.await() ftpClient.disconnect() @@ -58,5 +61,11 @@ open class FtpsGetHostCertificateTaskCallable( } protected open fun createFTPClient(): FTPSClient = - NetCopyClientConnectionPool.ftpClientFactory.create(FTPS_URI_PREFIX) as FTPSClient + NetCopyClientConnectionPool.ftpClientFactory.create( + if (explicitTls) { + "$FTPS_URI_PREFIX?$ARG_TLS=$TLS_EXPLICIT" + } else { + FTPS_URI_PREFIX + }, + ) as FTPSClient } diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/hashcalculator/CalculateHashCallback.java b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/hashcalculator/CalculateHashCallback.java index cea10c620a..67a2612054 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/hashcalculator/CalculateHashCallback.java +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/hashcalculator/CalculateHashCallback.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/hashcalculator/CalculateHashSftpCallback.java b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/hashcalculator/CalculateHashSftpCallback.java index 2430e7359f..8cd43cc9b1 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/hashcalculator/CalculateHashSftpCallback.java +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/hashcalculator/CalculateHashSftpCallback.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2021 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/hashcalculator/CalculateHashTask.kt b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/hashcalculator/CalculateHashTask.kt index 486c2cfacf..aaac1a27de 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/hashcalculator/CalculateHashTask.kt +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/hashcalculator/CalculateHashTask.kt @@ -23,8 +23,8 @@ package com.amaze.filemanager.asynchronous.asynctasks.hashcalculator import android.content.Context import android.view.View import android.widget.LinearLayout -import android.widget.TextView import android.widget.Toast +import androidx.appcompat.widget.AppCompatTextView import com.amaze.filemanager.R import com.amaze.filemanager.asynchronous.asynctasks.Task import com.amaze.filemanager.filesystem.HybridFileParcelable @@ -32,7 +32,7 @@ import com.amaze.filemanager.filesystem.files.FileUtils import org.slf4j.Logger import org.slf4j.LoggerFactory import java.lang.ref.WeakReference -import java.util.* +import java.util.Locale import java.util.concurrent.Callable data class Hash(val md5: String, val sha: String) @@ -40,19 +40,19 @@ data class Hash(val md5: String, val sha: String) class CalculateHashTask( private val file: HybridFileParcelable, context: Context, - view: View + view: View, ) : Task> { - private val log: Logger = LoggerFactory.getLogger(CalculateHashTask::class.java) - private val task: Callable = if (file.isSftp) { - CalculateHashSftpCallback(file) - } else if (file.isFtp) { - // Don't do this. Especially when FTPClient requires thread safety. - DoNothingCalculateHashCallback() - } else { - CalculateHashCallback(file, context) - } + private val task: Callable = + if (file.isSftp && !file.isDirectory(context)) { + CalculateHashSftpCallback(file) + } else if (file.isFtp || file.isDirectory(context)) { + // Don't do this. Especially when FTPClient requires thread safety. + DoNothingCalculateHashCallback() + } else { + CalculateHashCallback(file, context) + } private val context = WeakReference(context) private val view = WeakReference(view) @@ -78,8 +78,8 @@ class CalculateHashTask( val md5Text = hashes?.md5 ?: context.getString(R.string.unavailable) val shaText = hashes?.sha ?: context.getString(R.string.unavailable) - val md5HashText = view.findViewById(R.id.t9) - val sha256Text = view.findViewById(R.id.t10) + val md5HashText = view.findViewById(R.id.t9) + val sha256Text = view.findViewById(R.id.t10) val mMD5LinearLayout = view.findViewById(R.id.properties_dialog_md5) val mSHA256LinearLayout = view.findViewById(R.id.properties_dialog_sha256) @@ -94,7 +94,7 @@ class CalculateHashTask( context.resources.getString(R.string.md5).uppercase(Locale.getDefault()) + " " + context.resources.getString(R.string.properties_copied_clipboard), - Toast.LENGTH_SHORT + Toast.LENGTH_SHORT, ) .show() false @@ -105,7 +105,7 @@ class CalculateHashTask( context, context.resources.getString(R.string.hash_sha256) + " " + context.resources.getString(R.string.properties_copied_clipboard), - Toast.LENGTH_SHORT + Toast.LENGTH_SHORT, ) .show() false diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/movecopy/MoveFiles.java b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/movecopy/MoveFiles.java index a76c2b3a28..394a66bec3 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/movecopy/MoveFiles.java +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/movecopy/MoveFiles.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. @@ -47,7 +47,7 @@ /** * AsyncTask that moves files from source to destination by trying to rename files first, if they're * in the same filesystem, else starting the copy service. Be advised - do not start this AsyncTask - * directly but use {@link PrepareCopyTask} instead + * directly but use {@link PreparePasteTask} instead */ public class MoveFiles implements Callable { diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/movecopy/MoveFilesTask.kt b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/movecopy/MoveFilesTask.kt index 196f05d668..9d9a3b6566 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/movecopy/MoveFilesTask.kt +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/movecopy/MoveFilesTask.kt @@ -34,7 +34,7 @@ import com.amaze.filemanager.fileoperations.filesystem.OpenMode import com.amaze.filemanager.filesystem.HybridFile import com.amaze.filemanager.filesystem.HybridFileParcelable import com.amaze.filemanager.filesystem.files.CryptUtil -import com.amaze.filemanager.filesystem.files.FileUtils +import com.amaze.filemanager.filesystem.files.MediaConnectionUtils import com.amaze.filemanager.ui.activities.MainActivity import org.slf4j.Logger import org.slf4j.LoggerFactory @@ -43,7 +43,7 @@ data class MoveFilesReturn( val movedCorrectly: Boolean, val invalidOperation: Boolean, val destinationSize: Long, - val totalSize: Long + val totalSize: Long, ) class MoveFilesTask( @@ -52,9 +52,8 @@ class MoveFilesTask( val currentPath: String, context: Context, val mode: OpenMode, - val paths: ArrayList + val paths: ArrayList, ) : Task { - private val log: Logger = LoggerFactory.getLogger(MoveFilesTask::class.java) private val task: MoveFiles = MoveFiles(files, isRootExplorer, context, mode, paths) @@ -88,7 +87,7 @@ class MoveFilesTask( Toast.makeText( applicationContext, R.string.some_files_failed_invalid_operation, - Toast.LENGTH_LONG + Toast.LENGTH_LONG, ) .show() } @@ -97,17 +96,18 @@ class MoveFilesTask( val targetFiles: MutableList = ArrayList() val sourcesFiles: MutableList = ArrayList() for (f in files[i]) { - val file = HybridFile( - OpenMode.FILE, - paths[i] + "/" + f.getName(applicationContext) - ) + val file = + HybridFile( + OpenMode.FILE, + paths[i] + "/" + f.getName(applicationContext), + ) targetFiles.add(file) } for (hybridFileParcelables in files) { sourcesFiles.addAll(hybridFileParcelables) } - FileUtils.scanFile(applicationContext, sourcesFiles.toTypedArray()) - FileUtils.scanFile(applicationContext, targetFiles.toTypedArray()) + MediaConnectionUtils.scanFile(applicationContext, sourcesFiles.toTypedArray()) + MediaConnectionUtils.scanFile(applicationContext, targetFiles.toTypedArray()) } // updating encrypted db entry if any encrypted file was moved @@ -130,13 +130,16 @@ class MoveFilesTask( } } - private fun onMovedFail(destinationSize: Long, totalBytes: Long) { + private fun onMovedFail( + destinationSize: Long, + totalBytes: Long, + ) { if (totalBytes > 0 && destinationSize < totalBytes) { // destination don't have enough space; return Toast.makeText( applicationContext, applicationContext.resources.getString(R.string.in_safe), - Toast.LENGTH_LONG + Toast.LENGTH_LONG, ) .show() return diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/movecopy/PrepareCopyTask.java b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/movecopy/PrepareCopyTask.java deleted file mode 100644 index 4bd15f349a..0000000000 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/movecopy/PrepareCopyTask.java +++ /dev/null @@ -1,446 +0,0 @@ -/* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , - * Emmanuel Messulam, Raymond Lai and Contributors. - * - * This file is part of Amaze File Manager. - * - * Amaze File Manager is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.amaze.filemanager.asynchronous.asynctasks.movecopy; - -import static com.amaze.filemanager.fileoperations.filesystem.FolderStateKt.CAN_CREATE_FILES; -import static com.amaze.filemanager.fileoperations.filesystem.OperationTypeKt.COPY; -import static com.amaze.filemanager.fileoperations.filesystem.OperationTypeKt.MOVE; - -import java.io.File; -import java.lang.ref.WeakReference; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.Set; - -import com.afollestad.materialdialogs.DialogAction; -import com.afollestad.materialdialogs.MaterialDialog; -import com.amaze.filemanager.R; -import com.amaze.filemanager.asynchronous.asynctasks.TaskKt; -import com.amaze.filemanager.asynchronous.management.ServiceWatcherUtil; -import com.amaze.filemanager.asynchronous.services.CopyService; -import com.amaze.filemanager.databinding.CopyDialogBinding; -import com.amaze.filemanager.fileoperations.filesystem.FolderState; -import com.amaze.filemanager.fileoperations.filesystem.OpenMode; -import com.amaze.filemanager.filesystem.HybridFile; -import com.amaze.filemanager.filesystem.HybridFileParcelable; -import com.amaze.filemanager.filesystem.files.FileUtils; -import com.amaze.filemanager.ui.activities.MainActivity; -import com.amaze.filemanager.utils.Utils; - -import android.app.ProgressDialog; -import android.content.Context; -import android.content.Intent; -import android.os.AsyncTask; -import android.view.LayoutInflater; -import android.view.View; -import android.widget.CheckBox; -import android.widget.Toast; - -import androidx.annotation.IntDef; - -/** - * This AsyncTask works by creating a tree where each folder that can be fusioned together with - * another in the destination is a node (CopyNode). While the tree is being created an indeterminate - * ProgressDialog is shown. Each node is copied when the conflicts are dealt with (the dialog is - * shown, and the tree is walked via a BFS). If the process is cancelled (via the button in the - * dialog) the dialog closes without any more code to be executed, finishCopying() is never executed - * so no changes are made. - */ -public class PrepareCopyTask extends AsyncTask { - - private final String path; - private final Boolean move; - private final WeakReference mainActivity; - private final WeakReference context; - private int counter = 0; - private ProgressDialog dialog; - private boolean rootMode = false; - private OpenMode openMode = OpenMode.FILE; - private @DialogState int dialogState = UNKNOWN; - private boolean isRenameMoveSupport = false; - - // causes folder containing filesToCopy to be deleted - private ArrayList deleteCopiedFolder = null; - private CopyNode copyFolder; - private final ArrayList paths = new ArrayList<>(); - private final ArrayList> filesToCopyPerFolder = new ArrayList<>(); - private final ArrayList filesToCopy; // a copy of params sent to this - - private static final int UNKNOWN = -1; - private static final int DO_NOT_REPLACE = 0; - private static final int REPLACE = 1; - - @IntDef({UNKNOWN, DO_NOT_REPLACE, REPLACE}) - @interface DialogState {} - - public PrepareCopyTask( - String path, - Boolean move, - MainActivity con, - boolean rootMode, - OpenMode openMode, - ArrayList filesToCopy) { - this.move = move; - mainActivity = new WeakReference<>(con); - context = new WeakReference<>(con); - this.openMode = openMode; - this.rootMode = rootMode; - this.path = path; - this.filesToCopy = filesToCopy; - } - - @Override - protected void onPreExecute() { - super.onPreExecute(); - dialog = - ProgressDialog.show(context.get(), "", context.get().getString(R.string.processing), true); - } - - @Override - public void onProgressUpdate(String... message) { - Toast.makeText(context.get(), message[0], Toast.LENGTH_LONG).show(); - } - - @Override - protected CopyNode doInBackground(Void... params) { - long totalBytes = 0; - - if (openMode == OpenMode.OTG - || openMode == OpenMode.DROPBOX - || openMode == OpenMode.BOX - || openMode == OpenMode.GDRIVE - || openMode == OpenMode.ONEDRIVE - || openMode == OpenMode.ROOT) { - // no helper method for OTG to determine storage space - return null; - } - - HybridFile destination = new HybridFile(openMode, path); - destination.generateMode(context.get()); - - if (move - && destination.getMode() == openMode - && MoveFiles.getOperationSupportedFileSystem().contains(openMode)) { - // move/rename supported filesystems, skip checking for space - isRenameMoveSupport = true; - } - - totalBytes = FileUtils.getTotalBytes(filesToCopy, context.get()); - - if (destination.getUsableSpace() < totalBytes && !isRenameMoveSupport) { - publishProgress(context.get().getResources().getString(R.string.in_safe)); - return null; - } - - copyFolder = new CopyNode(path, filesToCopy); - - return copyFolder; - } - - private ArrayList checkConflicts( - final ArrayList filesToCopy, HybridFile destination) { - final ArrayList conflictingFiles = new ArrayList<>(); - destination.forEachChildrenFile( - context.get(), - rootMode, - file -> { - for (HybridFileParcelable j : filesToCopy) { - if (file.getName(context.get()).equals((j).getName(context.get()))) { - conflictingFiles.add(j); - } - } - }); - return conflictingFiles; - } - - @Override - protected void onPostExecute(CopyNode copyFolder) { - super.onPostExecute(copyFolder); - if (openMode == OpenMode.OTG - || openMode == OpenMode.GDRIVE - || openMode == OpenMode.DROPBOX - || openMode == OpenMode.BOX - || openMode == OpenMode.ONEDRIVE - || openMode == OpenMode.ROOT) { - - startService(filesToCopy, path, openMode); - } else { - - if (copyFolder == null) { - // not starting service as there's no sufficient space - dialog.dismiss(); - return; - } - - onEndDialog(null, null, null); - } - - dialog.dismiss(); - } - - private void startService( - ArrayList sourceFiles, String target, OpenMode openmode) { - Intent intent = new Intent(context.get(), CopyService.class); - intent.putParcelableArrayListExtra(CopyService.TAG_COPY_SOURCES, sourceFiles); - intent.putExtra(CopyService.TAG_COPY_TARGET, target); - intent.putExtra(CopyService.TAG_COPY_OPEN_MODE, openmode.ordinal()); - intent.putExtra(CopyService.TAG_COPY_MOVE, move); - intent.putExtra(CopyService.TAG_IS_ROOT_EXPLORER, rootMode); - ServiceWatcherUtil.runService(context.get(), intent); - } - - private void showDialog( - final String path, - final ArrayList filesToCopy, - final ArrayList conflictingFiles) { - int accentColor = mainActivity.get().getAccent(); - final MaterialDialog.Builder dialogBuilder = new MaterialDialog.Builder(context.get()); - CopyDialogBinding copyDialogBinding = - CopyDialogBinding.inflate(LayoutInflater.from(mainActivity.get())); - dialogBuilder.customView(copyDialogBinding.getRoot(), true); - - // textView - copyDialogBinding.fileNameText.setText(conflictingFiles.get(counter).getName(context.get())); - - // checkBox - final CheckBox checkBox = copyDialogBinding.checkBox; - Utils.setTint(context.get(), checkBox, accentColor); - dialogBuilder.theme(mainActivity.get().getAppTheme().getMaterialDialogTheme(context.get())); - dialogBuilder.title(context.get().getResources().getString(R.string.paste)); - dialogBuilder.positiveText(R.string.skip); - dialogBuilder.negativeText(R.string.overwrite); - dialogBuilder.neutralText(R.string.cancel); - dialogBuilder.positiveColor(accentColor); - dialogBuilder.negativeColor(accentColor); - dialogBuilder.neutralColor(accentColor); - dialogBuilder.onPositive( - (dialog, which) -> { - if (checkBox.isChecked()) dialogState = DO_NOT_REPLACE; - doNotReplaceFiles(path, filesToCopy, conflictingFiles); - }); - dialogBuilder.onNegative( - (dialog, which) -> { - if (checkBox.isChecked()) dialogState = REPLACE; - replaceFiles(path, filesToCopy, conflictingFiles); - }); - - final MaterialDialog dialog = dialogBuilder.build(); - dialog.show(); - if (filesToCopy.get(0).getParent(context.get()).equals(path)) { - View negative = dialog.getActionButton(DialogAction.NEGATIVE); - negative.setEnabled(false); - } - } - - private void onEndDialog( - String path, - ArrayList filesToCopy, - ArrayList conflictingFiles) { - if (conflictingFiles != null - && counter != conflictingFiles.size() - && conflictingFiles.size() > 0) { - if (dialogState == UNKNOWN) { - showDialog(path, filesToCopy, conflictingFiles); - } else if (dialogState == DO_NOT_REPLACE) { - doNotReplaceFiles(path, filesToCopy, conflictingFiles); - } else if (dialogState == REPLACE) { - replaceFiles(path, filesToCopy, conflictingFiles); - } - } else { - CopyNode c = !copyFolder.hasStarted() ? copyFolder.startCopy() : copyFolder.goToNextNode(); - - if (c != null) { - counter = 0; - - paths.add(c.getPath()); - filesToCopyPerFolder.add(c.filesToCopy); - - if (dialogState == UNKNOWN) { - onEndDialog(c.path, c.filesToCopy, c.conflictingFiles); - } else if (dialogState == DO_NOT_REPLACE) { - doNotReplaceFiles(c.path, c.filesToCopy, c.conflictingFiles); - } else if (dialogState == REPLACE) { - replaceFiles(c.path, c.filesToCopy, c.conflictingFiles); - } - } else { - finishCopying(paths, filesToCopyPerFolder); - } - } - } - - private void doNotReplaceFiles( - String path, - ArrayList filesToCopy, - ArrayList conflictingFiles) { - if (counter < conflictingFiles.size()) { - if (dialogState != UNKNOWN) { - filesToCopy.remove(conflictingFiles.get(counter)); - counter++; - } else { - for (int j = counter; j < conflictingFiles.size(); j++) { - filesToCopy.remove(conflictingFiles.get(j)); - } - counter = conflictingFiles.size(); - } - } - - onEndDialog(path, filesToCopy, conflictingFiles); - } - - private void replaceFiles( - String path, - ArrayList filesToCopy, - ArrayList conflictingFiles) { - if (counter < conflictingFiles.size()) { - if (dialogState != UNKNOWN) { - counter++; - } else { - counter = conflictingFiles.size(); - } - } - - onEndDialog(path, filesToCopy, conflictingFiles); - } - - private void finishCopying( - ArrayList paths, ArrayList> filesToCopyPerFolder) { - for (int i = 0; i < filesToCopyPerFolder.size(); i++) { - if (filesToCopyPerFolder.get(i) == null || filesToCopyPerFolder.get(i).size() == 0) { - filesToCopyPerFolder.remove(i); - paths.remove(i); - i--; - } - } - - if (filesToCopyPerFolder.size() != 0) { - @FolderState - int mode = mainActivity.get().mainActivityHelper.checkFolder(path, openMode, context.get()); - if (mode == CAN_CREATE_FILES && !path.contains("otg:/")) { - // This is used because in newer devices the user has to accept a permission, - // see MainActivity.onActivityResult() - mainActivity.get().oparrayListList = filesToCopyPerFolder; - mainActivity.get().oparrayList = null; - mainActivity.get().operation = move ? MOVE : COPY; - mainActivity.get().oppatheList = paths; - } else { - if (!move) { - for (int i = 0; i < filesToCopyPerFolder.size(); i++) { - startService(filesToCopyPerFolder.get(i), paths.get(i), openMode); - } - } else { - TaskKt.fromTask( - new MoveFilesTask( - filesToCopyPerFolder, rootMode, path, context.get(), openMode, paths)); - } - } - } else { - Toast.makeText( - context.get(), - context.get().getResources().getString(R.string.no_file_overwrite), - Toast.LENGTH_SHORT) - .show(); - } - } - - class CopyNode { - private final String path; - private final ArrayList filesToCopy; - private final ArrayList conflictingFiles; - private final ArrayList nextNodes = new ArrayList<>(); - - CopyNode(String p, ArrayList filesToCopy) { - path = p; - this.filesToCopy = filesToCopy; - - HybridFile destination = new HybridFile(openMode, path); - conflictingFiles = checkConflicts(filesToCopy, destination); - - for (int i = 0; i < conflictingFiles.size(); i++) { - if (conflictingFiles.get(i).isDirectory()) { - if (deleteCopiedFolder == null) deleteCopiedFolder = new ArrayList<>(); - - deleteCopiedFolder.add(new File(conflictingFiles.get(i).getPath())); - - nextNodes.add( - new CopyNode( - path + "/" + conflictingFiles.get(i).getName(context.get()), - conflictingFiles.get(i).listFiles(context.get(), rootMode))); - - filesToCopy.remove(filesToCopy.indexOf(conflictingFiles.get(i))); - conflictingFiles.remove(i); - i--; - } - } - } - - /** The next 2 methods are a BFS that runs through one node at a time. */ - private LinkedList queue = null; - - private Set visited = null; - - CopyNode startCopy() { - queue = new LinkedList<>(); - visited = new HashSet<>(); - - queue.add(this); - visited.add(this); - return this; - } - - /** - * @return true if there are no more nodes - */ - CopyNode goToNextNode() { - if (queue.isEmpty()) return null; - else { - CopyNode node = queue.element(); - CopyNode child; - if ((child = getUnvisitedChildNode(visited, node)) != null) { - visited.add(child); - queue.add(child); - return child; - } else { - queue.remove(); - return goToNextNode(); - } - } - } - - boolean hasStarted() { - return queue != null; - } - - String getPath() { - return path; - } - - private CopyNode getUnvisitedChildNode(Set visited, CopyNode node) { - for (CopyNode n : node.nextNodes) { - if (!visited.contains(n)) { - return n; - } - } - - return null; - } - } -} diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/movecopy/PreparePasteTask.kt b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/movecopy/PreparePasteTask.kt new file mode 100644 index 0000000000..e8e353194f --- /dev/null +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/movecopy/PreparePasteTask.kt @@ -0,0 +1,490 @@ +/* + * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Emmanuel Messulam, Raymond Lai and Contributors. + * + * This file is part of Amaze File Manager. + * + * Amaze File Manager is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.amaze.filemanager.asynchronous.asynctasks.movecopy + +import android.app.ProgressDialog +import android.content.Intent +import android.view.LayoutInflater +import android.widget.Toast +import androidx.appcompat.widget.AppCompatCheckBox +import com.afollestad.materialdialogs.DialogAction +import com.afollestad.materialdialogs.MaterialDialog +import com.amaze.filemanager.R +import com.amaze.filemanager.asynchronous.asynctasks.fromTask +import com.amaze.filemanager.asynchronous.asynctasks.movecopy.PreparePasteTask.CopyNode +import com.amaze.filemanager.asynchronous.management.ServiceWatcherUtil +import com.amaze.filemanager.asynchronous.services.CopyService +import com.amaze.filemanager.databinding.CopyDialogBinding +import com.amaze.filemanager.fileoperations.filesystem.CAN_CREATE_FILES +import com.amaze.filemanager.fileoperations.filesystem.COPY +import com.amaze.filemanager.fileoperations.filesystem.FolderState +import com.amaze.filemanager.fileoperations.filesystem.MOVE +import com.amaze.filemanager.fileoperations.filesystem.OpenMode +import com.amaze.filemanager.filesystem.FilenameHelper +import com.amaze.filemanager.filesystem.HybridFile +import com.amaze.filemanager.filesystem.HybridFileParcelable +import com.amaze.filemanager.filesystem.MakeDirectoryOperation +import com.amaze.filemanager.filesystem.files.FileUtils +import com.amaze.filemanager.ui.activities.MainActivity +import com.amaze.filemanager.utils.OnFileFound +import com.amaze.filemanager.utils.Utils +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.cancel +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import java.lang.ref.WeakReference +import java.util.LinkedList + +/** + * This helper class works by checking the conflicts during paste operation. After checking + * conflicts [MaterialDialog] is shown to user for each conflicting file. If the conflicting file + * is a directory, the conflicts are resolved by inserting a node in [CopyNode] tree and then doing + * BFS on this tree. + */ +class PreparePasteTask(strongRefMain: MainActivity) { + private lateinit var targetPath: String + private var isMove = false + private var isRootMode = false + private lateinit var openMode: OpenMode + private lateinit var filesToCopy: MutableList + + private val pathsList = ArrayList() + private val filesToCopyPerFolder = ArrayList>() + + private val context = WeakReference(strongRefMain) + + @Suppress("DEPRECATION") + private var progressDialog: ProgressDialog? = null + private val coroutineScope = CoroutineScope(Job() + Dispatchers.Default) + + private lateinit var destination: HybridFile + private val conflictingFiles: MutableList = mutableListOf() + private val conflictingDirActionMap = HashMap() + + private var skipAll = false + private var renameAll = false + private var overwriteAll = false + + private fun startService( + sourceFiles: ArrayList, + target: String, + openMode: OpenMode, + isMove: Boolean, + isRootMode: Boolean, + ) { + val intent = Intent(context.get(), CopyService::class.java) + intent.putParcelableArrayListExtra(CopyService.TAG_COPY_SOURCES, sourceFiles) + intent.putExtra(CopyService.TAG_COPY_TARGET, target) + intent.putExtra(CopyService.TAG_COPY_OPEN_MODE, openMode.ordinal) + intent.putExtra(CopyService.TAG_COPY_MOVE, isMove) + intent.putExtra(CopyService.TAG_IS_ROOT_EXPLORER, isRootMode) + ServiceWatcherUtil.runService(context.get(), intent) + } + + /** + * Starts execution of [PreparePasteTask] class. + */ + fun execute( + targetPath: String, + isMove: Boolean, + isRootMode: Boolean, + openMode: OpenMode, + filesToCopy: ArrayList, + ) { + this.targetPath = targetPath + this.isMove = isMove + this.isRootMode = isRootMode + this.openMode = openMode + this.filesToCopy = filesToCopy + + val isCloudOrRootMode = + openMode == OpenMode.OTG || + openMode == OpenMode.GDRIVE || + openMode == OpenMode.DROPBOX || + openMode == OpenMode.BOX || + openMode == OpenMode.ONEDRIVE || + openMode == OpenMode.ROOT + + if (isCloudOrRootMode) { + startService(filesToCopy, targetPath, openMode, isMove, isRootMode) + return + } + + val totalBytes = FileUtils.getTotalBytes(filesToCopy, context.get()) + destination = HybridFile(openMode, targetPath) + destination.generateMode(context.get()) + + if (filesToCopy.isNotEmpty() && + isMove && + filesToCopy[0].getParent(context.get()) == targetPath + ) { + Toast.makeText(context.get(), R.string.same_dir_move_error, Toast.LENGTH_SHORT).show() + return + } + + val isMoveSupported = + isMove && + destination.mode == openMode && + MoveFiles.getOperationSupportedFileSystem().contains(openMode) + + if (destination.usableSpace < totalBytes && + !isMoveSupported + ) { + Toast.makeText(context.get(), R.string.in_safe, Toast.LENGTH_SHORT).show() + return + } + @Suppress("DEPRECATION") + progressDialog = + ProgressDialog.show( + context.get(), + "", + context.get()?.getString(R.string.checking_conflicts), + ) + checkConflicts( + isRootMode, + filesToCopy, + destination, + conflictingFiles, + conflictingDirActionMap, + ) + } + + private fun checkConflicts( + isRootMode: Boolean, + filesToCopy: ArrayList, + destination: HybridFile, + conflictingFiles: MutableList, + conflictingDirActionMap: HashMap, + ) { + coroutineScope.launch { + destination.forEachChildrenFile( + context.get(), + isRootMode, + object : OnFileFound { + override fun onFileFound(file: HybridFileParcelable) { + for (fileToCopy in filesToCopy) { + if (file.getName(context.get()) == fileToCopy.getName(context.get())) { + conflictingFiles.add(fileToCopy) + } + } + } + }, + ) + withContext(Dispatchers.Main) { + prepareDialog(conflictingFiles, conflictingDirActionMap) + @Suppress("DEPRECATION") + progressDialog?.setMessage(context.get()?.getString(R.string.copying)) + } + resolveConflict(conflictingFiles, conflictingDirActionMap, filesToCopy) + } + } + + private suspend fun prepareDialog( + conflictingFiles: MutableList, + conflictingDirActionMap: HashMap, + ) { + if (conflictingFiles.isEmpty()) return + + val contextRef = context.get() ?: return + val accentColor = contextRef.accent + val dialogBuilder = MaterialDialog.Builder(contextRef) + val copyDialogBinding: CopyDialogBinding = + CopyDialogBinding.inflate(LayoutInflater.from(contextRef)) + dialogBuilder.customView(copyDialogBinding.root, true) + val checkBox: AppCompatCheckBox = copyDialogBinding.checkBox + + Utils.setTint(contextRef, checkBox, accentColor) + dialogBuilder.theme(contextRef.appTheme.getMaterialDialogTheme()) + dialogBuilder.title(contextRef.resources.getString(R.string.paste)) + dialogBuilder.positiveText(R.string.rename) + dialogBuilder.neutralText(R.string.skip) + dialogBuilder.positiveColor(accentColor) + dialogBuilder.negativeColor(accentColor) + dialogBuilder.neutralColor(accentColor) + dialogBuilder.negativeText(R.string.overwrite) + dialogBuilder.cancelable(false) + showDialog( + conflictingFiles, + conflictingDirActionMap, + copyDialogBinding, + dialogBuilder, + checkBox, + ) + } + + private suspend fun showDialog( + conflictingFiles: MutableList, + conflictingDirActionMap: HashMap, + copyDialogBinding: CopyDialogBinding, + dialogBuilder: MaterialDialog.Builder, + checkBox: AppCompatCheckBox, + ) { + val iterator = conflictingFiles.iterator() + while (iterator.hasNext()) { + val hybridFileParcelable = iterator.next() + copyDialogBinding.fileNameText.text = hybridFileParcelable.name + val dialog = dialogBuilder.build() + if (hybridFileParcelable.getParent(context.get()) == targetPath) { + dialog.getActionButton(DialogAction.NEGATIVE) + .isEnabled = false + } + val resultDeferred = CompletableDeferred() + dialogBuilder.onPositive { _, _ -> + resultDeferred.complete(DialogAction.POSITIVE) + } + dialogBuilder.onNegative { _, _ -> + resultDeferred.complete(DialogAction.NEGATIVE) + } + dialogBuilder.onNeutral { _, _ -> + resultDeferred.complete(DialogAction.NEUTRAL) + } + dialog.show() + when (resultDeferred.await()) { + DialogAction.POSITIVE -> { + if (checkBox.isChecked) { + renameAll = true + return + } + conflictingDirActionMap[hybridFileParcelable] = Action.RENAME + } + DialogAction.NEGATIVE -> { + if (checkBox.isChecked) { + overwriteAll = true + return + } + conflictingDirActionMap[hybridFileParcelable] = Action.OVERWRITE + } + DialogAction.NEUTRAL -> { + if (checkBox.isChecked) { + skipAll = true + return + } + conflictingDirActionMap[hybridFileParcelable] = Action.SKIP + } + } + iterator.remove() + } + } + + private fun resolveConflict( + conflictingFiles: MutableList, + conflictingDirActionMap: HashMap, + filesToCopy: ArrayList, + ) = coroutineScope.launch { + var index = conflictingFiles.size - 1 + if (renameAll) { + while (conflictingFiles.isNotEmpty()) { + conflictingDirActionMap[conflictingFiles[index]] = Action.RENAME + conflictingFiles.removeAt(index) + index-- + } + } else if (overwriteAll) { + while (conflictingFiles.isNotEmpty()) { + conflictingDirActionMap[conflictingFiles[index]] = Action.OVERWRITE + conflictingFiles.removeAt(index) + index-- + } + } else if (skipAll) { + while (conflictingFiles.isNotEmpty()) { + filesToCopy.remove(conflictingFiles.removeAt(index)) + index-- + } + } + + val rootNode = CopyNode(targetPath, ArrayList(filesToCopy)) + var currentNode: CopyNode? = rootNode.startCopy() + + while (currentNode != null) { + pathsList.add(currentNode.path) + filesToCopyPerFolder.add(currentNode.filesToCopy) + currentNode = rootNode.goToNextNode() + } + finishCopying() + } + + private suspend fun finishCopying() { + var index = 0 + while (index < filesToCopyPerFolder.size) { + if (filesToCopyPerFolder[index].size == 0) { + filesToCopyPerFolder.removeAt(index) + pathsList.removeAt(index) + index-- + } + index++ + } + if (filesToCopyPerFolder.isNotEmpty()) { + @FolderState + val mode: Int = + context.get()?.mainActivityHelper!! + .checkFolder(targetPath, openMode, context.get()) + if (mode == CAN_CREATE_FILES && !targetPath.contains("otg:/")) { + // This is used because in newer devices the user has to accept a permission, + // see MainActivity.onActivityResult() + context.get()?.oparrayListList = filesToCopyPerFolder + context.get()?.oparrayList = null + context.get()?.operation = if (isMove) MOVE else COPY + context.get()?.oppatheList = pathsList + } else { + if (!isMove) { + for (foldersIndex in filesToCopyPerFolder.indices) + startService( + filesToCopyPerFolder[foldersIndex], + pathsList[foldersIndex], + openMode, + isMove, + isRootMode, + ) + } else { + fromTask( + MoveFilesTask( + filesToCopyPerFolder, + isRootMode, + targetPath, + context.get()!!, + openMode, + pathsList, + ), + ) + } + } + } else { + withContext(Dispatchers.Main) { + Toast.makeText( + context.get(), + context.get()!!.resources.getString(R.string.no_file_overwrite), + Toast.LENGTH_SHORT, + ).show() + } + } + withContext(Dispatchers.Main) { + progressDialog?.dismiss() + } + coroutineScope.cancel() + } + + private inner class CopyNode( + val path: String, + val filesToCopy: ArrayList, + ) { + private val nextNodes: MutableList = mutableListOf() + private var queue: LinkedList? = null + private var visited: HashSet? = null + + init { + val iterator = filesToCopy.iterator() + while (iterator.hasNext()) { + val hybridFileParcelable = iterator.next() + if (conflictingDirActionMap.contains(hybridFileParcelable)) { + val fileAtTarget = + HybridFile( + hybridFileParcelable.mode, + path, + hybridFileParcelable.name, + hybridFileParcelable.isDirectory, + ) + when (conflictingDirActionMap[hybridFileParcelable]) { + Action.RENAME -> { + if (hybridFileParcelable.isDirectory) { + val newName = + FilenameHelper.increment(fileAtTarget).getName(context.get()) + val newPath = "$path/$newName" + val newDir = HybridFile(hybridFileParcelable.mode, newPath) + MakeDirectoryOperation.mkdirs(context.get()!!, newDir) + @Suppress("DEPRECATION") + nextNodes.add( + CopyNode( + newPath, + hybridFileParcelable.listFiles(context.get(), isRootMode), + ), + ) + iterator.remove() + } else { + filesToCopy[filesToCopy.indexOf(hybridFileParcelable)].name = + FilenameHelper.increment( + fileAtTarget, + ).getName(context.get()) + } + } + + Action.SKIP -> iterator.remove() + } + } + } + } + + /** + * Starts BFS traversal of tree. + * + * @return Root node + */ + fun startCopy(): CopyNode { + queue = LinkedList() + visited = HashSet() + queue!!.add(this) + visited!!.add(this) + return this + } + + /** + * Moves to the next unvisited node in tree. + * + * @return The next unvisited node if available, otherwise returns null. + */ + fun goToNextNode(): CopyNode? = + if (queue.isNullOrEmpty()) { + null + } else { + val node = queue!!.element() + val child = getUnvisitedChildNode(visited!!, node) + if (child != null) { + visited!!.add(child) + queue!!.add(child) + child + } else { + queue!!.remove() + goToNextNode() + } + } + + private fun getUnvisitedChildNode( + visited: Set, + node: CopyNode, + ): CopyNode? { + for (currentNode in node.nextNodes) { + if (!visited.contains(currentNode)) { + return currentNode + } + } + return null + } + } + + private class Action { + companion object { + const val SKIP = "skip" + const val RENAME = "rename" + const val OVERWRITE = "overwrite" + } + } +} diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/BasicSearch.kt b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/BasicSearch.kt new file mode 100644 index 0000000000..46bf493d2d --- /dev/null +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/BasicSearch.kt @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , + * Emmanuel Messulam, Raymond Lai and Contributors. + * + * This file is part of Amaze File Manager. + * + * Amaze File Manager is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.amaze.filemanager.asynchronous.asynctasks.searchfilesystem + +import android.content.Context +import com.amaze.filemanager.filesystem.HybridFileParcelable +import com.amaze.filemanager.filesystem.root.ListFilesCommand.listFiles + +class BasicSearch( + query: String, + path: String, + searchParameters: SearchParameters, + context: Context, +) : FileSearch(query, path, searchParameters) { + private val applicationContext = context.applicationContext + + override suspend fun search(filter: SearchFilter) { + listFiles( + path, + SearchParameter.ROOT in searchParameters, + SearchParameter.SHOW_HIDDEN_FILES in searchParameters, + { }, + ) { hybridFileParcelable: HybridFileParcelable -> + if (SearchParameter.SHOW_HIDDEN_FILES in searchParameters || + !hybridFileParcelable.isHidden + ) { + val resultRange = + filter.searchFilter(hybridFileParcelable.getName(applicationContext)) + if (resultRange != null) { + publishProgress(hybridFileParcelable, resultRange) + } + } + } + } +} diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/DeepSearch.kt b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/DeepSearch.kt new file mode 100644 index 0000000000..227626b161 --- /dev/null +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/DeepSearch.kt @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , + * Emmanuel Messulam, Raymond Lai and Contributors. + * + * This file is part of Amaze File Manager. + * + * Amaze File Manager is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.amaze.filemanager.asynchronous.asynctasks.searchfilesystem + +import android.content.Context +import com.amaze.filemanager.fileoperations.filesystem.OpenMode +import com.amaze.filemanager.filesystem.HybridFile +import kotlinx.coroutines.isActive +import org.slf4j.LoggerFactory +import kotlin.coroutines.coroutineContext + +class DeepSearch( + query: String, + path: String, + searchParameters: SearchParameters, + context: Context, + private val openMode: OpenMode, +) : FileSearch(query, path, searchParameters) { + private val LOG = LoggerFactory.getLogger(DeepSearch::class.java) + + private val applicationContext: Context + + init { + applicationContext = context.applicationContext + } + + /** + * Search for occurrences of a given text in file names and publish the result + * + * @param directory the current path + */ + override suspend fun search(filter: SearchFilter) { + val directory = HybridFile(openMode, path) + if (directory.isSmb) return + + if (directory.isDirectory(applicationContext)) { + // you have permission to read this directory + val worklist = ArrayDeque() + worklist.add(directory) + while (coroutineContext.isActive && worklist.isNotEmpty()) { + val nextFile = worklist.removeFirst() + nextFile.forEachChildrenFile( + applicationContext, + SearchParameter.ROOT in searchParameters, + ) { file -> + if (!file.isHidden || SearchParameter.SHOW_HIDDEN_FILES in searchParameters) { + val resultRange = filter.searchFilter(file.getName(applicationContext)) + if (resultRange != null) { + publishProgress(file, resultRange) + } + if (file.isDirectory(applicationContext)) { + worklist.add(file) + } + } + } + } + } else { + LOG.warn("Cannot search " + directory.path + ": Permission Denied") + } + } +} diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/FileSearch.kt b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/FileSearch.kt new file mode 100644 index 0000000000..a920e70538 --- /dev/null +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/FileSearch.kt @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , + * Emmanuel Messulam, Raymond Lai and Contributors. + * + * This file is part of Amaze File Manager. + * + * Amaze File Manager is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.amaze.filemanager.asynchronous.asynctasks.searchfilesystem + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import com.amaze.filemanager.asynchronous.asynctasks.searchfilesystem.FileSearch.SearchFilter +import com.amaze.filemanager.filesystem.HybridFileParcelable +import java.util.Locale +import java.util.regex.Pattern + +abstract class FileSearch( + protected val query: String, + protected val path: String, + protected val searchParameters: SearchParameters, +) { + private val mutableFoundFilesLiveData: MutableLiveData> = + MutableLiveData() + val foundFilesLiveData: LiveData> = mutableFoundFilesLiveData + private val foundFilesList: MutableList = mutableListOf() + + /** + * Search for files, whose names match [query], starting from [path] and add them to + * [foundFilesLiveData] + */ + suspend fun search() { + if (SearchParameter.REGEX !in searchParameters) { + // regex not turned on so we use simpleFilter + this.search(simpleFilter(query)) + } else { + if (SearchParameter.REGEX_MATCHES !in searchParameters) { + // only regex turned on so we use regexFilter + this.search(regexFilter(query)) + } else { + // regex turned on and names must match pattern so use regexMatchFilter + this.search(regexMatchFilter(query)) + } + } + } + + /** + * Search for files, whose names fulfill [filter], starting from [path] and add them to + * [foundFilesLiveData]. + */ + protected abstract suspend fun search(filter: SearchFilter) + + /** + * Add [file] to list of found files and post it to [foundFilesLiveData] + */ + protected fun publishProgress( + file: HybridFileParcelable, + matchRange: MatchRange, + ) { + foundFilesList.add(SearchResult(file, matchRange)) + mutableFoundFilesLiveData.postValue(foundFilesList) + } + + private fun simpleFilter(query: String): SearchFilter = + SearchFilter { fileName -> + // check case-insensitively if query is contained in fileName + val start = + fileName.lowercase(Locale.getDefault()).indexOf( + query.lowercase( + Locale.getDefault(), + ), + ) + if (start >= 0) { + start until start + query.length + } else { + null + } + } + + private fun regexFilter(query: String): SearchFilter { + val pattern = regexPattern(query) + return SearchFilter { fileName -> + // check case-insensitively if the pattern compiled from query can be found in fileName + val matcher = pattern.matcher(fileName) + if (matcher.find()) { + matcher.start() until matcher.end() + } else { + null + } + } + } + + private fun regexMatchFilter(query: String): SearchFilter { + val pattern = regexPattern(query) + return SearchFilter { fileName -> + // check case-insensitively if the pattern compiled from query matches fileName + if (pattern.matcher(fileName).matches()) { + fileName.indices + } else { + null + } + } + } + + private fun regexPattern(query: String): Pattern = + // compiles the given query into a Pattern + Pattern.compile( + bashRegexToJava(query), + Pattern.CASE_INSENSITIVE, + ) + + /** + * method converts bash style regular expression to java. See [Pattern] + * + * @return converted string + */ + private fun bashRegexToJava(originalString: String): String { + val stringBuilder = StringBuilder() + for (i in originalString.indices) { + when (originalString[i].toString() + "") { + "*" -> stringBuilder.append("\\w*") + "?" -> stringBuilder.append("\\w") + else -> stringBuilder.append(originalString[i]) + } + } + return stringBuilder.toString() + } + + fun interface SearchFilter { + /** + * If the file with the given [fileName] fulfills some predicate, returns the part that fulfills the predicate. + * Otherwise returns null. + */ + fun searchFilter(fileName: String): MatchRange? + } +} diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/IndexedSearch.kt b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/IndexedSearch.kt new file mode 100644 index 0000000000..56a1cc63f3 --- /dev/null +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/IndexedSearch.kt @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , + * Emmanuel Messulam, Raymond Lai and Contributors. + * + * This file is part of Amaze File Manager. + * + * Amaze File Manager is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.amaze.filemanager.asynchronous.asynctasks.searchfilesystem + +import android.database.Cursor +import android.provider.MediaStore +import com.amaze.filemanager.filesystem.RootHelper +import kotlinx.coroutines.isActive +import java.io.File +import kotlin.coroutines.coroutineContext + +class IndexedSearch( + query: String, + path: String, + searchParameters: SearchParameters, + private val cursor: Cursor, +) : FileSearch(query, path, searchParameters) { + override suspend fun search(filter: SearchFilter) { + if (cursor.count > 0 && cursor.moveToFirst()) { + do { + val nextPath = + cursor.getString( + cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.DATA), + ) + val displayName = + cursor.getString( + cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.DISPLAY_NAME), + ) + if (nextPath != null && displayName != null && nextPath.contains(path)) { + val resultRange = filter.searchFilter(displayName) + if (resultRange != null) { + val hybridFileParcelable = + RootHelper.generateBaseFile( + File(nextPath), + SearchParameter.SHOW_HIDDEN_FILES in searchParameters, + ) + if (hybridFileParcelable != null) { + publishProgress(hybridFileParcelable, resultRange) + } + } + } + } while (cursor.moveToNext() && coroutineContext.isActive) + } + + cursor.close() + } +} diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/SearchAsyncTask.java b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/SearchAsyncTask.java deleted file mode 100644 index ebfacc5aa9..0000000000 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/SearchAsyncTask.java +++ /dev/null @@ -1,218 +0,0 @@ -/* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , - * Emmanuel Messulam, Raymond Lai and Contributors. - * - * This file is part of Amaze File Manager. - * - * Amaze File Manager is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.amaze.filemanager.asynchronous.asynctasks.searchfilesystem; - -import static com.amaze.filemanager.ui.fragments.preferencefragments.PreferencesConstants.PREFERENCE_SHOW_HIDDENFILES; - -import java.util.regex.Pattern; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.amaze.filemanager.asynchronous.asynctasks.StatefulAsyncTask; -import com.amaze.filemanager.fileoperations.filesystem.OpenMode; -import com.amaze.filemanager.filesystem.HybridFile; -import com.amaze.filemanager.filesystem.HybridFileParcelable; -import com.amaze.filemanager.ui.fragments.SearchWorkerFragment; - -import android.content.Context; -import android.os.AsyncTask; - -import androidx.preference.PreferenceManager; - -public class SearchAsyncTask extends AsyncTask - implements StatefulAsyncTask { - - private final Logger LOG = LoggerFactory.getLogger(SearchAsyncTask.class); - - /** This necessarily leaks the context */ - private final Context applicationContext; - - private SearchWorkerFragment.HelperCallbacks callbacks; - private final String input; - private final boolean rootMode; - private final boolean isRegexEnabled; - private final boolean isMatchesEnabled; - private final HybridFile file; - - public SearchAsyncTask( - Context context, - String input, - OpenMode openMode, - boolean root, - boolean regex, - boolean matches, - String path) { - this.applicationContext = context.getApplicationContext(); - this.input = input; - rootMode = root; - isRegexEnabled = regex; - isMatchesEnabled = matches; - - this.file = new HybridFile(openMode, path); - } - - @Override - protected void onPreExecute() { - /* - * Note that we need to check if the callbacks are null in each - * method in case they are invoked after the Activity's and - * Fragment's onDestroy() method have been called. - */ - if (callbacks != null) { - callbacks.onPreExecute(input); - } - } - - // callbacks not checked for null because of possibility of - // race conditions b/w worker thread main thread - @Override - protected Void doInBackground(Void... params) { - if (file.isSmb()) return null; - - // level 1 - // if regex or not - if (!isRegexEnabled) { - search(file, input); - } else { - // compile the regular expression in the input - Pattern pattern = Pattern.compile(bashRegexToJava(input)); - // level 2 - if (!isMatchesEnabled) searchRegExFind(file, pattern); - else searchRegExMatch(file, pattern); - } - return null; - } - - @Override - public void onPostExecute(Void c) { - if (callbacks != null) { - callbacks.onPostExecute(input); - } - } - - @Override - protected void onCancelled() { - if (callbacks != null) callbacks.onCancelled(); - } - - @Override - public void onProgressUpdate(HybridFileParcelable... val) { - if (!isCancelled() && callbacks != null) { - callbacks.onProgressUpdate(val[0], input); - } - } - - @Override - public void setCallback(SearchWorkerFragment.HelperCallbacks helperCallbacks) { - this.callbacks = helperCallbacks; - } - - /** - * Recursively search for occurrences of a given text in file names and publish the result - * - * @param directory the current path - */ - private void search(HybridFile directory, final SearchFilter filter) { - if (directory.isDirectory( - applicationContext)) { // do you have permission to read this directory? - directory.forEachChildrenFile( - applicationContext, - rootMode, - file -> { - boolean showHiddenFiles = - PreferenceManager.getDefaultSharedPreferences(applicationContext) - .getBoolean(PREFERENCE_SHOW_HIDDENFILES, false); - - if ((!isCancelled() && (showHiddenFiles || !file.isHidden()))) { - if (filter.searchFilter(file.getName(applicationContext))) { - publishProgress(file); - } - if (file.isDirectory() && !isCancelled()) { - search(file, filter); - } - } - }); - } else { - LOG.warn("Cannot search " + directory.getPath() + ": Permission Denied"); - } - } - - /** - * Recursively search for occurrences of a given text in file names and publish the result - * - * @param file the current path - * @param query the searched text - */ - private void search(HybridFile file, final String query) { - search(file, fileName -> fileName.toLowerCase().contains(query.toLowerCase())); - } - - /** - * Recursively find a java regex pattern {@link Pattern} in the file names and publish the result - * - * @param file the current file - * @param pattern the compiled java regex - */ - private void searchRegExFind(HybridFile file, final Pattern pattern) { - search(file, fileName -> pattern.matcher(fileName).find()); - } - - /** - * Recursively match a java regex pattern {@link Pattern} with the file names and publish the - * result - * - * @param file the current file - * @param pattern the compiled java regex - */ - private void searchRegExMatch(HybridFile file, final Pattern pattern) { - search(file, fileName -> pattern.matcher(fileName).matches()); - } - - /** - * method converts bash style regular expression to java. See {@link Pattern} - * - * @return converted string - */ - private String bashRegexToJava(String originalString) { - StringBuilder stringBuilder = new StringBuilder(); - - for (int i = 0; i < originalString.length(); i++) { - switch (originalString.charAt(i) + "") { - case "*": - stringBuilder.append("\\w*"); - break; - case "?": - stringBuilder.append("\\w"); - break; - default: - stringBuilder.append(originalString.charAt(i)); - break; - } - } - - return stringBuilder.toString(); - } - - public interface SearchFilter { - boolean searchFilter(String fileName); - } -} diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/SearchParameter.kt b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/SearchParameter.kt new file mode 100644 index 0000000000..ec52862e84 --- /dev/null +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/SearchParameter.kt @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , + * Emmanuel Messulam, Raymond Lai and Contributors. + * + * This file is part of Amaze File Manager. + * + * Amaze File Manager is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.amaze.filemanager.asynchronous.asynctasks.searchfilesystem + +enum class SearchParameter { + ROOT, + REGEX, + REGEX_MATCHES, + SHOW_HIDDEN_FILES, + ; + + /** + * Returns [SearchParameters] containing [this] and [other] + */ + infix fun and(other: SearchParameter): SearchParameters = SearchParameters.of(this, other) + + /** + * Returns [SearchParameters] containing [this] and [other] + */ + operator fun plus(other: SearchParameter): SearchParameters = this and other +} diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/SearchParameters.kt b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/SearchParameters.kt new file mode 100644 index 0000000000..23337c5073 --- /dev/null +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/SearchParameters.kt @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , + * Emmanuel Messulam, Raymond Lai and Contributors. + * + * This file is part of Amaze File Manager. + * + * Amaze File Manager is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.amaze.filemanager.asynchronous.asynctasks.searchfilesystem + +import java.util.EnumSet + +typealias SearchParameters = EnumSet + +/** + * Returns [SearchParameters] extended by [other] + */ +infix fun SearchParameters.and(other: SearchParameter): SearchParameters = + SearchParameters.of( + other, + *this.toTypedArray(), + ) + +/** + * Returns [SearchParameters] extended by [other] + */ +operator fun SearchParameters.plus(other: SearchParameter): SearchParameters = this and other + +/** + * Returns [SearchParameters] that reflect the given Booleans + */ +fun searchParametersFromBoolean( + showHiddenFiles: Boolean = false, + isRegexEnabled: Boolean = false, + isRegexMatchesEnabled: Boolean = false, + isRoot: Boolean = false, +): SearchParameters { + val searchParameterList = mutableListOf() + + if (showHiddenFiles) searchParameterList.add(SearchParameter.SHOW_HIDDEN_FILES) + if (isRegexEnabled) searchParameterList.add(SearchParameter.REGEX) + if (isRegexMatchesEnabled) searchParameterList.add(SearchParameter.REGEX_MATCHES) + if (isRoot) searchParameterList.add(SearchParameter.ROOT) + + return if (searchParameterList.isEmpty()) { + SearchParameters.noneOf(SearchParameter::class.java) + } else { + SearchParameters.copyOf(searchParameterList) + } +} diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/SortSearchResultCallable.kt b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/SearchResult.kt similarity index 66% rename from app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/SortSearchResultCallable.kt rename to app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/SearchResult.kt index f87788ff59..e9aa24d41d 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/SortSearchResultCallable.kt +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/SearchResult.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2021 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. @@ -20,16 +20,11 @@ package com.amaze.filemanager.asynchronous.asynctasks.searchfilesystem -import com.amaze.filemanager.adapters.data.LayoutElementParcelable -import com.amaze.filemanager.filesystem.files.FileListSorter -import java.util.* -import java.util.concurrent.Callable +import com.amaze.filemanager.filesystem.HybridFileParcelable -class SortSearchResultCallable( - val elements: MutableList, - val sorter: FileListSorter -) : Callable { - override fun call() { - Collections.sort(elements, sorter) - } -} +data class SearchResult(val file: HybridFileParcelable, val matchRange: MatchRange) + +typealias MatchRange = IntProgression + +/** Returns the size of the [MatchRange] which means how many characters were matched */ +fun MatchRange.size(): Int = this.last - this.first diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/SearchResultListSorter.kt b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/SearchResultListSorter.kt new file mode 100644 index 0000000000..7dfa2b5d06 --- /dev/null +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/SearchResultListSorter.kt @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , + * Emmanuel Messulam, Raymond Lai and Contributors. + * + * This file is part of Amaze File Manager. + * + * Amaze File Manager is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.amaze.filemanager.asynchronous.asynctasks.searchfilesystem + +import com.amaze.filemanager.filesystem.files.FileListSorter +import com.amaze.filemanager.filesystem.files.sort.DirSortBy +import com.amaze.filemanager.filesystem.files.sort.SortBy +import com.amaze.filemanager.filesystem.files.sort.SortType +import java.util.Date +import java.util.concurrent.TimeUnit + +class SearchResultListSorter( + private val dirArg: DirSortBy, + private val sortType: SortType, + private val searchTerm: String, +) : Comparator { + private val fileListSorter: FileListSorter by lazy { FileListSorter(dirArg, sortType) } + + private val relevanceComparator: Comparator by lazy { + Comparator { o1, o2 -> + val currentTime = Date().time + val comparator = + compareBy { (item, matchRange) -> + // the match percentage of the search term in the name + val matchPercentageScore = + matchRange.size().toDouble() / item.getParcelableName().length.toDouble() + + // if the name starts with the search term + val startScore = (matchRange.first == 0).toInt() + + // if the search term is surrounded by separators + // e.g. "my-cat" more relevant than "mysterious" for search term "my" + val wordScore = + item.getParcelableName().split('-', '_', '.', ' ').any { + it.contentEquals( + searchTerm, + ignoreCase = true, + ) + }.toInt() + + val modificationDate = item.getDate() + // the time difference as minutes + val timeDiff = + TimeUnit.MILLISECONDS.toMinutes(currentTime - modificationDate) + // 30 days as minutes + val relevantModificationPeriod = TimeUnit.DAYS.toMinutes(30) + val timeScore = + if (timeDiff < relevantModificationPeriod) { + // if the file was modified within the last 30 days, the recency is normalized + (relevantModificationPeriod - timeDiff) / + relevantModificationPeriod.toDouble() + } else { + // for all older modification time, the recency doesn't change the relevancy + 0.0 + } + + 1.2 * matchPercentageScore + + 0.7 * startScore + + 0.7 * wordScore + + 0.6 * timeScore + } + // Reverts the sorting to make most relevant first + comparator.compare(o1, o2) * -1 + } + } + + private fun Boolean.toInt() = if (this) 1 else 0 + + override fun compare( + result1: SearchResult, + result2: SearchResult, + ): Int { + return when (sortType.sortBy) { + SortBy.RELEVANCE -> relevanceComparator.compare(result1, result2) + SortBy.SIZE, SortBy.TYPE, SortBy.LAST_MODIFIED, SortBy.NAME -> + fileListSorter.compare(result1.file, result2.file) + } + } +} diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/SortSearchResultTask.kt b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/SortSearchResultTask.kt deleted file mode 100644 index 18d159217d..0000000000 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/SortSearchResultTask.kt +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright (C) 2014-2021 Arpit Khurana , Vishal Nehra , - * Emmanuel Messulam, Raymond Lai and Contributors. - * - * This file is part of Amaze File Manager. - * - * Amaze File Manager is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.amaze.filemanager.asynchronous.asynctasks.searchfilesystem - -import com.amaze.filemanager.R -import com.amaze.filemanager.adapters.data.LayoutElementParcelable -import com.amaze.filemanager.asynchronous.asynctasks.Task -import com.amaze.filemanager.filesystem.files.FileListSorter -import com.amaze.filemanager.ui.fragments.MainFragment -import org.slf4j.Logger -import org.slf4j.LoggerFactory - -class SortSearchResultTask( - val elements: MutableList, - val sorter: FileListSorter, - val mainFragment: MainFragment, - val query: String -) : Task { - - private val log: Logger = LoggerFactory.getLogger(SortSearchResultTask::class.java) - - private val task = SortSearchResultCallable(elements, sorter) - - override fun getTask(): SortSearchResultCallable = task - - override fun onError(error: Throwable) { - log.error("Could not sort search results because of exception", error) - } - - override fun onFinish(value: Unit) { - val mainFragmentViewModel = mainFragment.mainFragmentViewModel - - if (mainFragmentViewModel == null) { - log.error( - "Could not show sorted search results because main fragment view model is null" - ) - return - } - - val mainActivity = mainFragment.mainActivity - - if (mainActivity == null) { - log.error("Could not show sorted search results because main activity is null") - return - } - - mainFragment.reloadListElements( - true, - true, - !mainFragmentViewModel.isList - ) // TODO: 7/7/2017 this is really inneffient, use RecycleAdapter's - - // createHeaders() - mainActivity.appbar.bottomBar.setPathText("") - mainActivity - .appbar - .bottomBar.fullPathText = mainActivity.getString(R.string.search_results, query) - mainFragmentViewModel.results = false - } -} diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/ssh/GetSshHostFingerprintTask.kt b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/ssh/GetSshHostFingerprintTask.kt index 8f7880382c..b30834d4fc 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/ssh/GetSshHostFingerprintTask.kt +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/ssh/GetSshHostFingerprintTask.kt @@ -27,13 +27,11 @@ class GetSshHostFingerprintTask( private val hostname: String, private val port: Int, private val firstContact: Boolean, - callback: (PublicKey) -> Unit + callback: (PublicKey) -> Unit, ) : AbstractGetHostInfoTask( - hostname, - port, - callback -) { - - override fun getTask(): GetSshHostFingerprintTaskCallable = - GetSshHostFingerprintTaskCallable(hostname, port, firstContact) + hostname, + port, + callback, + ) { + override fun getTask(): GetSshHostFingerprintTaskCallable = GetSshHostFingerprintTaskCallable(hostname, port, firstContact) } diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/ssh/GetSshHostFingerprintTaskCallable.kt b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/ssh/GetSshHostFingerprintTaskCallable.kt index f2b6a806d1..fe3567def8 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/ssh/GetSshHostFingerprintTaskCallable.kt +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/ssh/GetSshHostFingerprintTaskCallable.kt @@ -27,41 +27,49 @@ import net.schmizz.sshj.transport.verification.HostKeyVerifier import org.slf4j.Logger import org.slf4j.LoggerFactory import java.security.PublicKey -import java.util.* +import java.util.Collections import java.util.concurrent.Callable import java.util.concurrent.CountDownLatch class GetSshHostFingerprintTaskCallable( private val hostname: String, private val port: Int, - private val firstContact: Boolean = false + private val firstContact: Boolean = false, ) : Callable { - companion object { @JvmStatic - private val logger: Logger = LoggerFactory.getLogger( - GetSshHostFingerprintTaskCallable::class.java - ) + private val logger: Logger = + LoggerFactory.getLogger( + GetSshHostFingerprintTaskCallable::class.java, + ) } override fun call(): PublicKey { var holder: PublicKey? = null val latch = CountDownLatch(1) - val sshClient = NetCopyClientConnectionPool.sshClientFactory - .create(CustomSshJConfig()).also { - it.connectTimeout = NetCopyClientConnectionPool.CONNECT_TIMEOUT - it.addHostKeyVerifier(object : HostKeyVerifier { - override fun verify(hostname: String?, port: Int, key: PublicKey?): Boolean { - holder = key - latch.countDown() - return true - } - override fun findExistingAlgorithms( - hostname: String?, - port: Int - ): MutableList = Collections.emptyList() - }) - } + val sshClient = + NetCopyClientConnectionPool.sshClientFactory + .create(CustomSshJConfig()).also { + it.connectTimeout = NetCopyClientConnectionPool.CONNECT_TIMEOUT + it.addHostKeyVerifier( + object : HostKeyVerifier { + override fun verify( + hostname: String?, + port: Int, + key: PublicKey?, + ): Boolean { + holder = key + latch.countDown() + return true + } + + override fun findExistingAlgorithms( + hostname: String?, + port: Int, + ): MutableList = Collections.emptyList() + }, + ) + } return runCatching { sshClient.connect(hostname, port) latch.await() diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/ssh/PemToKeyPairObservable.kt b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/ssh/PemToKeyPairObservable.kt index 501be08903..b4af758b03 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/ssh/PemToKeyPairObservable.kt +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/ssh/PemToKeyPairObservable.kt @@ -48,13 +48,13 @@ import java.io.StringReader import java.security.KeyPair class PemToKeyPairObservable(private val pemFile: ByteArray) : ObservableOnSubscribe { - - private val converters = arrayOf( - JcaPemToKeyPairConverter(), - OpenSshPemToKeyPairConverter(), - OpenSshV1PemToKeyPairConverter(), - PuttyPrivateKeyToKeyPairConverter() - ) + private val converters = + arrayOf( + JcaPemToKeyPairConverter(), + OpenSshPemToKeyPairConverter(), + OpenSshV1PemToKeyPairConverter(), + PuttyPrivateKeyToKeyPairConverter(), + ) private var passwordFinder: PasswordFinder? = null private var errorMessage: String? = null @@ -75,13 +75,15 @@ class PemToKeyPairObservable(private val pemFile: ByteArray) : ObservableOnSubsc } } if (passwordFinder != null) { - errorMessage = AppConfig - .getInstance() - .getString(R.string.ssh_key_invalid_passphrase) + errorMessage = + AppConfig + .getInstance() + .getString(R.string.ssh_key_invalid_passphrase) } else { - errorMessage = AppConfig - .getInstance() - .getString(R.string.ssh_key_no_decoder_decrypt) + errorMessage = + AppConfig + .getInstance() + .getString(R.string.ssh_key_no_decoder_decrypt) } emitter.onError(IOException(errorMessage)) } @@ -93,14 +95,16 @@ class PemToKeyPairObservable(private val pemFile: ByteArray) : ObservableOnSubsc fun displayPassphraseDialog( exception: Throwable, positiveCallback: (() -> Unit), - negativeCallback: (() -> Unit) + negativeCallback: (() -> Unit), ) { - val builder = MaterialDialog.Builder( - AppConfig.getInstance().mainActivityContext!! - ) - val dialogLayout = DialogSingleedittextBinding.inflate( - LayoutInflater.from(AppConfig.getInstance().mainActivityContext) - ) + val builder = + MaterialDialog.Builder( + AppConfig.getInstance().mainActivityContext!!, + ) + val dialogLayout = + DialogSingleedittextBinding.inflate( + LayoutInflater.from(AppConfig.getInstance().mainActivityContext), + ) val wilTextfield: WarnableTextInputLayout = dialogLayout.singleedittextWarnabletextinputlayout val textfield = dialogLayout.singleedittextInput @@ -112,14 +116,16 @@ class PemToKeyPairObservable(private val pemFile: ByteArray) : ObservableOnSubsc .title(R.string.ssh_key_prompt_passphrase) .positiveText(R.string.ok) .onPositive { dialog: MaterialDialog, which: DialogAction? -> - passwordFinder = object : PasswordFinder { - override fun reqPassword(resource: Resource<*>?): CharArray { - return textfield.text.toString().toCharArray() - } - override fun shouldRetry(resource: Resource<*>?): Boolean { - return false + passwordFinder = + object : PasswordFinder { + override fun reqPassword(resource: Resource<*>?): CharArray { + return textfield.text.toString().toCharArray() + } + + override fun shouldRetry(resource: Resource<*>?): Boolean { + return false + } } - } dialog.dismiss() positiveCallback.invoke() } @@ -134,12 +140,12 @@ class PemToKeyPairObservable(private val pemFile: ByteArray) : ObservableOnSubsc AppConfig.getInstance().mainActivityContext, textfield, wilTextfield, - dialog.getActionButton(DialogAction.POSITIVE) + dialog.getActionButton(DialogAction.POSITIVE), ) { text: String -> if (text.isEmpty()) { WarnableTextInputValidator.ReturnState( WarnableTextInputValidator.ReturnState.STATE_ERROR, - R.string.field_empty + R.string.field_empty, ) } WarnableTextInputValidator.ReturnState() @@ -156,17 +162,18 @@ class PemToKeyPairObservable(private val pemFile: ByteArray) : ObservableOnSubsc AppConfig.getInstance() .resources .getString(R.string.ssh_pem_key_parse_error, result.localizedMessage), - Toast.LENGTH_LONG + Toast.LENGTH_LONG, ) .show() } private abstract inner class PemToKeyPairConverter { - fun convert(source: String): KeyPair? = runCatching { - throwingConvert(source) - }.onFailure { - log.warn("failed to convert pem to keypair", it) - }.getOrNull() + fun convert(source: String): KeyPair? = + runCatching { + throwingConvert(source) + }.onFailure { + log.warn("failed to convert pem to keypair", it) + }.getOrNull() protected abstract fun throwingConvert(source: String?): KeyPair? } diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/ssh/SshAuthenticationTask.kt b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/ssh/SshAuthenticationTask.kt index d992963289..b2a401fe1a 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/ssh/SshAuthenticationTask.kt +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/ssh/SshAuthenticationTask.kt @@ -39,9 +39,8 @@ class SshAuthenticationTask( private val hostKey: String, private val username: String, private val password: String? = null, - private val privateKey: KeyPair? = null + private val privateKey: KeyPair? = null, ) : Task { - override fun getTask(): SshAuthenticationTaskCallable = SshAuthenticationTaskCallable(hostname, port, hostKey, username, password, privateKey) @@ -60,11 +59,11 @@ class SshAuthenticationTask( R.string.ssh_connect_failed, hostname, port, - error.localizedMessage ?: error.message - ) + error.localizedMessage ?: error.message, + ), ) } else if (TransportException::class.java - .isAssignableFrom(error.javaClass) + .isAssignableFrom(error.javaClass) ) { val disconnectReason = TransportException::class.java.cast(error)!!.disconnectReason @@ -80,12 +79,12 @@ class SshAuthenticationTask( } else if (password != null) { AppConfig.toast( AppConfig.getInstance(), - R.string.ssh_authentication_failure_password + R.string.ssh_authentication_failure_password, ) } else if (privateKey != null) { AppConfig.toast( AppConfig.getInstance(), - R.string.ssh_authentication_failure_key + R.string.ssh_authentication_failure_key, ) } } diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/ssh/SshAuthenticationTaskCallable.kt b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/ssh/SshAuthenticationTaskCallable.kt index 0b35d3930c..a67c2fdb7c 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/ssh/SshAuthenticationTaskCallable.kt +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/ssh/SshAuthenticationTaskCallable.kt @@ -40,23 +40,23 @@ class SshAuthenticationTaskCallable( private val hostKey: String, private val username: String, private val password: String? = null, - private val privateKey: KeyPair? = null + private val privateKey: KeyPair? = null, ) : Callable { - init { require( - true == password?.isNotEmpty() || privateKey != null + true == password?.isNotEmpty() || privateKey != null, ) { "Must provide either password or privateKey" } } override fun call(): SSHClient { - val sshClient = NetCopyClientConnectionPool.sshClientFactory - .create(CustomSshJConfig()).also { - it.addHostKeyVerifier(hostKey) - it.connectTimeout = NetCopyClientConnectionPool.CONNECT_TIMEOUT - } + val sshClient = + NetCopyClientConnectionPool.sshClientFactory + .create(CustomSshJConfig()).also { + it.addHostKeyVerifier(hostKey) + it.connectTimeout = NetCopyClientConnectionPool.CONNECT_TIMEOUT + } return run { sshClient.connect(hostname, port) if (privateKey != null) { @@ -68,7 +68,7 @@ class SshAuthenticationTaskCallable( override fun getPublic(): PublicKey = privateKey.public override fun getType(): KeyType = KeyType.fromKey(public) - } + }, ) sshClient } else { @@ -77,10 +77,10 @@ class SshAuthenticationTaskCallable( decode( PasswordUtil.decryptPassword( AppConfig.getInstance(), - password!! + password!!, ), - UTF_8.name() - ) + UTF_8.name(), + ), ) sshClient } diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/texteditor/read/ReadTextFileCallable.java b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/texteditor/read/ReadTextFileCallable.java index 9090324aac..3611b68a97 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/texteditor/read/ReadTextFileCallable.java +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/texteditor/read/ReadTextFileCallable.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/texteditor/read/ReadTextFileTask.kt b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/texteditor/read/ReadTextFileTask.kt index 244214660f..361e541493 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/texteditor/read/ReadTextFileTask.kt +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/texteditor/read/ReadTextFileTask.kt @@ -37,26 +37,26 @@ import org.slf4j.Logger import org.slf4j.LoggerFactory import java.io.IOException import java.lang.ref.WeakReference -import java.util.* +import java.util.Locale class ReadTextFileTask( activity: TextEditorActivity, private val textEditorActivityWR: WeakReference, - private val appContextWR: WeakReference + private val appContextWR: WeakReference, ) : Task { - private val log: Logger = LoggerFactory.getLogger(ReadTextFileTask::class.java) private val task: ReadTextFileCallable init { val viewModel: TextEditorActivityViewModel by activity.viewModels() - task = ReadTextFileCallable( - activity.contentResolver, - viewModel.file, - activity.externalCacheDir, - activity.isRootExplorer - ) + task = + ReadTextFileCallable( + activity.contentResolver, + viewModel.file, + activity.externalCacheDir, + activity.isRootExplorer, + ) } override fun getTask(): ReadTextFileCallable = task @@ -66,20 +66,21 @@ class ReadTextFileTask( log.error("Error on text read", error) val applicationContext = appContextWR.get() ?: return - @StringRes val errorMessage: Int = when (error) { - is StreamNotFoundException -> { - R.string.error_file_not_found - } - is IOException -> { - R.string.error_io - } - is OutOfMemoryError -> { - R.string.error_file_too_large + @StringRes val errorMessage: Int = + when (error) { + is StreamNotFoundException -> { + R.string.error_file_not_found + } + is IOException -> { + R.string.error_io + } + is OutOfMemoryError -> { + R.string.error_file_too_large + } + else -> { + R.string.error + } } - else -> { - R.string.error - } - } Toast.makeText(applicationContext, errorMessage, Toast.LENGTH_SHORT).show() val textEditorActivity = textEditorActivityWR.get() ?: return textEditorActivity.dismissLoadingSnackbar() @@ -107,14 +108,15 @@ class ReadTextFileTask( if (isFileInCacheAndNotRoot) { textEditorActivity.setReadOnly() - val snackbar = Snackbar.make( - textEditorActivity.mainTextView, - R.string.file_read_only, - Snackbar.LENGTH_INDEFINITE - ) + val snackbar = + Snackbar.make( + textEditorActivity.mainTextView, + R.string.file_read_only, + Snackbar.LENGTH_INDEFINITE, + ) snackbar.setAction( textEditorActivity.resources.getString(R.string.got_it) - .uppercase(Locale.getDefault()) + .uppercase(Locale.getDefault()), ) { snackbar.dismiss() } snackbar.show() } @@ -127,15 +129,16 @@ class ReadTextFileTask( if (value.fileIsTooLong) { textEditorActivity.setReadOnly() - val snackbar = Snackbar.make( - textEditorActivity.mainTextView, - textEditorActivity.resources - .getString(R.string.file_too_long, ReadTextFileCallable.MAX_FILE_SIZE_CHARS), - Snackbar.LENGTH_INDEFINITE - ) + val snackbar = + Snackbar.make( + textEditorActivity.mainTextView, + textEditorActivity.resources + .getString(R.string.file_too_long, ReadTextFileCallable.MAX_FILE_SIZE_CHARS), + Snackbar.LENGTH_INDEFINITE, + ) snackbar.setAction( textEditorActivity.resources.getString(R.string.got_it) - .uppercase(Locale.getDefault()) + .uppercase(Locale.getDefault()), ) { snackbar.dismiss() } snackbar.show() } diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/texteditor/write/WriteTextFileCallable.java b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/texteditor/write/WriteTextFileCallable.java index e4be052993..06dcc7df03 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/texteditor/write/WriteTextFileCallable.java +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/texteditor/write/WriteTextFileCallable.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. @@ -72,7 +72,9 @@ public WriteTextFileCallable( @WorkerThread @Override public Unit call() - throws IOException, StreamNotFoundException, ShellNotRunningException, + throws IOException, + StreamNotFoundException, + ShellNotRunningException, IllegalArgumentException { OutputStream outputStream; File destFile = null; diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/texteditor/write/WriteTextFileTask.kt b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/texteditor/write/WriteTextFileTask.kt index e53f8ba57d..8345092c12 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/texteditor/write/WriteTextFileTask.kt +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/texteditor/write/WriteTextFileTask.kt @@ -40,23 +40,23 @@ class WriteTextFileTask( activity: TextEditorActivity, private val editTextString: String, private val textEditorActivityWR: WeakReference, - private val appContextWR: WeakReference + private val appContextWR: WeakReference, ) : Task { - private var log: Logger = LoggerFactory.getLogger(WriteTextFileTask::class.java) private val task: WriteTextFileCallable init { val viewModel: TextEditorActivityViewModel by activity.viewModels() - task = WriteTextFileCallable( - activity, - activity.contentResolver, - viewModel.file, - editTextString, - viewModel.cacheFile, - activity.isRootExplorer - ) + task = + WriteTextFileCallable( + activity, + activity.contentResolver, + viewModel.file, + editTextString, + viewModel.cacheFile, + activity.isRootExplorer, + ) } override fun getTask(): WriteTextFileCallable = task @@ -66,20 +66,21 @@ class WriteTextFileTask( log.error("Error on text write", error) val applicationContext = appContextWR.get() ?: return - @StringRes val errorMessage: Int = when (error) { - is StreamNotFoundException -> { - R.string.error_file_not_found - } - is IOException -> { - R.string.error_io - } - is ShellNotRunningException -> { - R.string.root_failure - } - else -> { - R.string.error + @StringRes val errorMessage: Int = + when (error) { + is StreamNotFoundException -> { + R.string.error_file_not_found + } + is IOException -> { + R.string.error_io + } + is ShellNotRunningException -> { + R.string.root_failure + } + else -> { + R.string.error + } } - } Toast.makeText(applicationContext, errorMessage, Toast.LENGTH_SHORT).show() } diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/broadcast_receivers/PackageReceiver.java b/app/src/main/java/com/amaze/filemanager/asynchronous/broadcast_receivers/PackageReceiver.java index c8cf9bf619..9128b78e2c 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/broadcast_receivers/PackageReceiver.java +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/broadcast_receivers/PackageReceiver.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/handlers/FileHandler.kt b/app/src/main/java/com/amaze/filemanager/asynchronous/handlers/FileHandler.kt index 8ba17a597f..f3132bff9b 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/handlers/FileHandler.kt +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/handlers/FileHandler.kt @@ -37,10 +37,10 @@ import java.lang.ref.WeakReference class FileHandler( mainFragment: MainFragment, private val listView: RecyclerView, - private val useThumbs: Boolean + private val useThumbs: Boolean, ) : Handler( - Looper.getMainLooper() -) { + Looper.getMainLooper(), + ) { private val mainFragment: WeakReference = WeakReference(mainFragment) private val log: Logger = LoggerFactory.getLogger(FileHandler::class.java) @@ -63,17 +63,19 @@ class FileHandler( log.error("Path is empty for file") return } - val fileCreated = HybridFile( - mainFragmentViewModel.openMode, - "${main.currentPath}/$path" - ) + val fileCreated = + HybridFile( + mainFragmentViewModel.openMode, + "${main.currentPath}/$path", + ) val newElement = fileCreated.generateLayoutElement(main.requireContext(), useThumbs) main.elementsList?.add(newElement) } CustomFileObserver.DELETED_ITEM -> { - val index = elementsList.withIndex().find { - File(it.value.desc).name == path - }?.index + val index = + elementsList.withIndex().find { + File(it.value.desc).name == path + }?.index if (index != null) { main.elementsList?.removeAt(index) @@ -89,13 +91,14 @@ class FileHandler( // no item left in list, recreate views main.reloadListElements( true, - mainFragmentViewModel.results, - !mainFragmentViewModel.isList + !mainFragmentViewModel.isList, ) } else { - val itemList = main.elementsList ?: listOf() - // we already have some elements in list view, invalidate the adapter - (listView.adapter as RecyclerAdapter).setItems(listView, itemList) + listView.adapter?.let { + val itemList = main.elementsList ?: listOf() + // we already have some elements in list view, invalidate the adapter + (listView.adapter as RecyclerAdapter).setItems(listView, itemList) + } } } else { // there was no list view, means the directory was empty diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/loaders/AppListLoader.java b/app/src/main/java/com/amaze/filemanager/asynchronous/loaders/AppListLoader.java index 82ea7df9ae..95ce11a101 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/loaders/AppListLoader.java +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/loaders/AppListLoader.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/management/ServiceWatcherUtil.java b/app/src/main/java/com/amaze/filemanager/asynchronous/management/ServiceWatcherUtil.java index 43b264d421..38030c793a 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/management/ServiceWatcherUtil.java +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/management/ServiceWatcherUtil.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. @@ -27,10 +27,13 @@ * this class can only handle progress with one object at a time. Hence, class also provides * convenience methods to serialize the service startup. */ -import static com.amaze.filemanager.asynchronous.management.ServiceWatcherUtil.ServiceStatusCallbacks.*; +import static com.amaze.filemanager.asynchronous.management.ServiceWatcherUtil.ServiceStatusCallbacks.STATE_HALTED; +import static com.amaze.filemanager.asynchronous.management.ServiceWatcherUtil.ServiceStatusCallbacks.STATE_RESUMED; +import static com.amaze.filemanager.asynchronous.management.ServiceWatcherUtil.ServiceStatusCallbacks.STATE_UNSET; import java.lang.ref.WeakReference; -import java.util.concurrent.*; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.TimeUnit; import com.amaze.filemanager.R; import com.amaze.filemanager.asynchronous.AbstractRepeatingRunnable; diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/services/AbstractProgressiveService.java b/app/src/main/java/com/amaze/filemanager/asynchronous/services/AbstractProgressiveService.java index 73ec722b0e..c952fe875a 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/services/AbstractProgressiveService.java +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/services/AbstractProgressiveService.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/services/CopyService.java b/app/src/main/java/com/amaze/filemanager/asynchronous/services/CopyService.java index e83fdd4af7..58ae8a0448 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/services/CopyService.java +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/services/CopyService.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. @@ -43,6 +43,7 @@ import com.amaze.filemanager.filesystem.files.CryptUtil; import com.amaze.filemanager.filesystem.files.FileUtils; import com.amaze.filemanager.filesystem.files.GenericCopyUtil; +import com.amaze.filemanager.filesystem.files.MediaConnectionUtils; import com.amaze.filemanager.filesystem.root.CopyFilesCommand; import com.amaze.filemanager.filesystem.root.MoveFileCommand; import com.amaze.filemanager.ui.activities.MainActivity; @@ -85,18 +86,12 @@ public class CopyService extends AbstractProgressiveService { private final IBinder mBinder = new ObtainableServiceBinder<>(this); private ServiceWatcherUtil watcherUtil; - private ProgressHandler progressHandler = new ProgressHandler(); + private final ProgressHandler progressHandler = new ProgressHandler(); private ProgressListener progressListener; // list of data packages, to initiate chart in process viewer fragment - private ArrayList dataPackages = new ArrayList<>(); - private int accentColor; - private SharedPreferences sharedPreferences; + private final ArrayList dataPackages = new ArrayList<>(); private RemoteViews customSmallContentViews, customBigContentViews; - private boolean isRootExplorer; - private long totalSize = 0L; - private int totalSourceFiles = 0; - @Override public void onCreate() { super.onCreate(); @@ -107,13 +102,13 @@ public void onCreate() { @Override public int onStartCommand(Intent intent, int flags, final int startId) { Bundle b = new Bundle(); - isRootExplorer = intent.getBooleanExtra(TAG_IS_ROOT_EXPLORER, false); + boolean isRootExplorer = intent.getBooleanExtra(TAG_IS_ROOT_EXPLORER, false); ArrayList files = intent.getParcelableArrayListExtra(TAG_COPY_SOURCES); String targetPath = intent.getStringExtra(TAG_COPY_TARGET); int mode = intent.getIntExtra(TAG_COPY_OPEN_MODE, OpenMode.UNKNOWN.ordinal()); final boolean move = intent.getBooleanExtra(TAG_COPY_MOVE, false); - sharedPreferences = PreferenceManager.getDefaultSharedPreferences(c); - accentColor = + SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(c); + int accentColor = ((AppConfig) getApplication()) .getUtilsProvider() .getColorPreference() @@ -238,7 +233,7 @@ private class DoInBackground extends AsyncTask { boolean move; private Copy copy; private String targetPath; - private boolean isRootExplorer; + private final boolean isRootExplorer; private int sourceProgress = 0; private DoInBackground(boolean isRootExplorer) { @@ -251,8 +246,8 @@ protected Void doInBackground(Bundle... p1) { // setting up service watchers and initial data packages // finding total size on background thread (this is necessary condition for SMB!) - totalSize = FileUtils.getTotalBytes(sourceFiles, c); - totalSourceFiles = sourceFiles.size(); + long totalSize = FileUtils.getTotalBytes(sourceFiles, c); + int totalSourceFiles = sourceFiles.size(); progressHandler.setSourceSize(totalSourceFiles); progressHandler.setTotalSize(totalSize); @@ -435,7 +430,7 @@ public void execute( } } } else { - for (HybridFileParcelable f : sourceFiles) failedFOps.add(f); + failedFOps.addAll(sourceFiles); return; } @@ -446,7 +441,7 @@ public void execute( for (HybridFileParcelable a : sourceFiles) { if (!failedFOps.contains(a)) toDelete.add(a); } - new DeleteTask(c).execute((toDelete)); + new DeleteTask(c, true).execute((toDelete)); } } @@ -455,7 +450,7 @@ void copyRoot(HybridFileParcelable sourceFile, HybridFile targetFile, boolean mo try { if (!move) { CopyFilesCommand.INSTANCE.copyFiles(sourceFile.getPath(), targetFile.getPath()); - } else if (move) { + } else { MoveFileCommand.INSTANCE.moveFile(sourceFile.getPath(), targetFile.getPath()); } ServiceWatcherUtil.position += sourceFile.getSize(); @@ -467,7 +462,7 @@ void copyRoot(HybridFileParcelable sourceFile, HybridFile targetFile, boolean mo e); failedFOps.add(sourceFile); } - FileUtils.scanFile(c, new HybridFile[] {targetFile}); + MediaConnectionUtils.scanFile(c, new HybridFile[] {targetFile}); } private void copyFiles( @@ -532,7 +527,7 @@ private void copyFiles( } } - private BroadcastReceiver receiver3 = + private final BroadcastReceiver receiver3 = new BroadcastReceiver() { @Override @@ -544,7 +539,6 @@ public void onReceive(Context context, Intent intent) { @Override public IBinder onBind(Intent arg0) { - // TODO Auto-generated method stub return mBinder; } } diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/services/DecryptService.java b/app/src/main/java/com/amaze/filemanager/asynchronous/services/DecryptService.java index c05d326b7b..494d2452f4 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/services/DecryptService.java +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/services/DecryptService.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. @@ -34,7 +34,6 @@ import com.amaze.filemanager.asynchronous.asynctasks.Task; import com.amaze.filemanager.asynchronous.asynctasks.TaskKt; import com.amaze.filemanager.asynchronous.management.ServiceWatcherUtil; -import com.amaze.filemanager.fileoperations.filesystem.OpenMode; import com.amaze.filemanager.filesystem.FileProperties; import com.amaze.filemanager.filesystem.HybridFile; import com.amaze.filemanager.filesystem.HybridFileParcelable; @@ -113,8 +112,6 @@ public int onStartCommand(Intent intent, int flags, int startId) { .getCurrentUserColorPreferences(this, sharedPreferences) .getAccent(); - OpenMode openMode = - OpenMode.values()[intent.getIntExtra(TAG_OPEN_MODE, OpenMode.UNKNOWN.ordinal())]; notificationManager = NotificationManagerCompat.from(getApplicationContext()); Intent notificationIntent = new Intent(this, MainActivity.class); notificationIntent.setAction(Intent.ACTION_MAIN); @@ -267,7 +264,7 @@ public Callable getTask() { // and the cache directory in case we're here because of the viewer try { new CryptUtil(context, baseFile, decryptPath, progressHandler, failedOps, password); - } catch (AESCrypt.DecryptFailureException e) { + } catch (AESCrypt.DecryptFailureException ignored) { } catch (Exception e) { LOG.error("Error decrypting " + baseFile.getPath(), e); @@ -295,7 +292,7 @@ public void onDestroy() { this.unregisterReceiver(cancelReceiver); } - private BroadcastReceiver cancelReceiver = + private final BroadcastReceiver cancelReceiver = new BroadcastReceiver() { @Override diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/services/EncryptService.java b/app/src/main/java/com/amaze/filemanager/asynchronous/services/EncryptService.java index 897b64a109..71980ec11d 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/services/EncryptService.java +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/services/EncryptService.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. @@ -33,7 +33,6 @@ import com.amaze.filemanager.asynchronous.asynctasks.Task; import com.amaze.filemanager.asynchronous.asynctasks.TaskKt; import com.amaze.filemanager.asynchronous.management.ServiceWatcherUtil; -import com.amaze.filemanager.fileoperations.filesystem.OpenMode; import com.amaze.filemanager.filesystem.FileProperties; import com.amaze.filemanager.filesystem.HybridFile; import com.amaze.filemanager.filesystem.HybridFileParcelable; @@ -77,20 +76,18 @@ public class EncryptService extends AbstractProgressiveService { private NotificationManagerCompat notificationManager; private NotificationCompat.Builder notificationBuilder; private Context context; - private IBinder mBinder = new ObtainableServiceBinder<>(this); - private ProgressHandler progressHandler = new ProgressHandler(); + private final IBinder binder = new ObtainableServiceBinder<>(this); + private final ProgressHandler progressHandler = new ProgressHandler(); private ProgressListener progressListener; // list of data packages, to initiate chart in process viewer fragment - private ArrayList dataPackages = new ArrayList<>(); + private final ArrayList dataPackages = new ArrayList<>(); private ServiceWatcherUtil serviceWatcherUtil; private long totalSize = 0L; private HybridFileParcelable baseFile; - private ArrayList failedOps = new ArrayList<>(); + private final ArrayList failedOps = new ArrayList<>(); private String targetFilename; - private int accentColor; private boolean useAesCrypt; private String password; - private SharedPreferences sharedPreferences; private RemoteViews customSmallContentViews, customBigContentViews; @Override @@ -110,16 +107,14 @@ public int onStartCommand(Intent intent, int flags, int startId) { if (useAesCrypt) { password = intent.getStringExtra(TAG_PASSWORD); } - sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context); - accentColor = + SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context); + int accentColor = ((AppConfig) getApplication()) .getUtilsProvider() .getColorPreference() .getCurrentUserColorPreferences(this, sharedPreferences) .getAccent(); - OpenMode openMode = - OpenMode.values()[intent.getIntExtra(TAG_OPEN_MODE, OpenMode.UNKNOWN.ordinal())]; notificationManager = NotificationManagerCompat.from(getApplicationContext()); Intent notificationIntent = new Intent(this, MainActivity.class); notificationIntent.setAction(Intent.ACTION_MAIN); @@ -284,7 +279,7 @@ public Callable getTask() { @Override public IBinder onBind(Intent intent) { - return mBinder; + return binder; } @Override @@ -293,7 +288,7 @@ public void onDestroy() { this.unregisterReceiver(cancelReceiver); } - private BroadcastReceiver cancelReceiver = + private final BroadcastReceiver cancelReceiver = new BroadcastReceiver() { @Override diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/services/ExtractService.java b/app/src/main/java/com/amaze/filemanager/asynchronous/services/ExtractService.java index 48a0d9fff9..28d1cdca05 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/services/ExtractService.java +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/services/ExtractService.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. @@ -24,7 +24,6 @@ import java.io.File; import java.io.IOException; -import java.lang.ref.WeakReference; import java.util.ArrayList; import org.apache.commons.compress.PasswordRequiredException; @@ -55,11 +54,12 @@ import android.os.AsyncTask; import android.os.IBinder; import android.text.TextUtils; -import android.widget.EditText; import android.widget.RemoteViews; import android.widget.Toast; +import androidx.annotation.Nullable; import androidx.annotation.StringRes; +import androidx.appcompat.widget.AppCompatEditText; import androidx.core.app.NotificationCompat; import androidx.core.app.NotificationManagerCompat; import androidx.preference.PreferenceManager; @@ -70,15 +70,14 @@ public class ExtractService extends AbstractProgressiveService { private final IBinder mBinder = new ObtainableServiceBinder<>(this); // list of data packages,// to initiate chart in process viewer fragment - private ArrayList dataPackages = new ArrayList<>(); + private final ArrayList dataPackages = new ArrayList<>(); private NotificationManagerCompat mNotifyManager; private NotificationCompat.Builder mBuilder; - private ProgressHandler progressHandler = new ProgressHandler(); + private final ProgressHandler progressHandler = new ProgressHandler(); private ProgressListener progressListener; - private int accentColor; - private SharedPreferences sharedPreferences; private RemoteViews customSmallContentViews, customBigContentViews; + private @Nullable DoWork extractingAsyncTask; public static final String KEY_PATH_ZIP = "zip"; public static final String KEY_ENTRIES_ZIP = "entries"; @@ -98,8 +97,9 @@ public int onStartCommand(Intent intent, int flags, final int startId) { String[] entries = intent.getStringArrayExtra(KEY_ENTRIES_ZIP); mNotifyManager = NotificationManagerCompat.from(getApplicationContext()); - sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()); - accentColor = + SharedPreferences sharedPreferences = + PreferenceManager.getDefaultSharedPreferences(getApplicationContext()); + int accentColor = ((AppConfig) getApplication()) .getUtilsProvider() .getColorPreference() @@ -153,7 +153,8 @@ public int onStartCommand(Intent intent, int flags, final int startId) { super.onStartCommand(intent, flags, startId); super.progressHalted(); - new DoWork(this, progressHandler, file, extractPath, entries).execute(); + extractingAsyncTask = new DoWork(progressHandler, file, extractPath, entries); + extractingAsyncTask.execute(); return START_NOT_STICKY; } @@ -216,6 +217,9 @@ protected void clearDataPackages() { @Override public void onDestroy() { super.onDestroy(); + if (extractingAsyncTask != null) { + extractingAsyncTask.cancel(true); + } unregisterReceiver(receiver1); } @@ -228,21 +232,15 @@ private long getTotalSize(String filePath) { } public class DoWork extends AsyncTask { - private WeakReference extractService; private String[] entriesToExtract; - private String extractionPath, compressedPath; - private ProgressHandler progressHandler; + private String extractionPath; + private final String compressedPath; + private final ProgressHandler progressHandler; private ServiceWatcherUtil watcherUtil; private boolean paused = false; private boolean passwordProtected = false; - private DoWork( - ExtractService extractService, - ProgressHandler progressHandler, - String cpath, - String epath, - String[] entries) { - this.extractService = new WeakReference<>(extractService); + private DoWork(ProgressHandler progressHandler, String cpath, String epath, String[] entries) { this.progressHandler = progressHandler; compressedPath = cpath; extractionPath = epath; @@ -254,8 +252,7 @@ protected Boolean doInBackground(Void... p) { while (!isCancelled()) { if (paused) continue; - final ExtractService extractService = this.extractService.get(); - if (extractService == null) return null; + final ExtractService extractService = ExtractService.this; File f = new File(compressedPath); String extractDirName = CompressedHelper.getFileName(f.getName()); @@ -394,9 +391,9 @@ protected void onProgressUpdate(IOException... values) { R.string.archive_password_prompt, R.string.authenticate_password, (dialog, which) -> { - EditText editText = dialog.getView().findViewById(R.id.singleedittext_input); + AppCompatEditText editText = dialog.getView().findViewById(R.id.singleedittext_input); ArchivePasswordCache.getInstance().put(compressedPath, editText.getText().toString()); - this.extractService.get().getDataPackages().clear(); + ExtractService.this.getDataPackages().clear(); this.paused = false; dialog.dismiss(); }, @@ -413,8 +410,7 @@ protected void onProgressUpdate(IOException... values) { @Override public void onPostExecute(Boolean hasInvalidEntries) { ArchivePasswordCache.getInstance().remove(compressedPath); - final ExtractService extractService = this.extractService.get(); - if (extractService == null) return; + final ExtractService extractService = ExtractService.this; // check whether watcherutil was initialized. It was not initialized when we got exception // in extracting the file @@ -453,7 +449,7 @@ private void toastOnParseError(IOException result) { * Class used for the client Binder. Because we know this service always runs in the same process * as its clients, we don't need to deal with IPC. */ - private BroadcastReceiver receiver1 = + private final BroadcastReceiver receiver1 = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/services/ZipService.kt b/app/src/main/java/com/amaze/filemanager/asynchronous/services/ZipService.kt index 2109d92461..d08164320d 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/services/ZipService.kt +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/services/ZipService.kt @@ -22,7 +22,11 @@ package com.amaze.filemanager.asynchronous.services import android.app.PendingIntent import android.app.PendingIntent.FLAG_UPDATE_CURRENT -import android.content.* +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.content.SharedPreferences import android.net.Uri import android.os.Build.VERSION.SDK_INT import android.os.Build.VERSION_CODES.O @@ -52,18 +56,21 @@ import io.reactivex.disposables.Disposable import io.reactivex.schedulers.Schedulers import org.slf4j.Logger import org.slf4j.LoggerFactory -import java.io.* +import java.io.BufferedInputStream +import java.io.BufferedOutputStream +import java.io.File +import java.io.FileInputStream +import java.io.IOException +import java.io.OutputStream import java.nio.file.Files import java.nio.file.Paths import java.nio.file.attribute.BasicFileAttributes -import java.util.* import java.util.zip.ZipEntry import java.util.zip.ZipException import java.util.zip.ZipOutputStream @Suppress("TooManyFunctions") // Hack. class ZipService : AbstractProgressiveService() { - private val log: Logger = LoggerFactory.getLogger(ZipService::class.java) private val mBinder: IBinder = ObtainableServiceBinder(this) @@ -85,7 +92,11 @@ class ZipService : AbstractProgressiveService() { registerReceiver(receiver1, IntentFilter(KEY_COMPRESS_BROADCAST_CANCEL)) } - override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int { + override fun onStartCommand( + intent: Intent, + flags: Int, + startId: Int, + ): Int { val mZipPath = intent.getStringExtra(KEY_COMPRESS_PATH) val baseFiles: ArrayList = intent.getParcelableArrayListExtra(KEY_COMPRESS_FILES)!! @@ -99,46 +110,48 @@ class ZipService : AbstractProgressiveService() { } } sharedPreferences = PreferenceManager.getDefaultSharedPreferences(applicationContext) - accentColor = (application as AppConfig) - .utilsProvider - .colorPreference - .getCurrentUserColorPreferences(this, sharedPreferences).accent - - val notificationIntent = Intent(this, MainActivity::class.java) - .putExtra(MainActivity.KEY_INTENT_PROCESS_VIEWER, true) - val pendingIntent = PendingIntent.getActivity( - this, - 0, - notificationIntent, - getPendingIntentFlag(0) - ) - + accentColor = + (application as AppConfig) + .utilsProvider + .colorPreference + .getCurrentUserColorPreferences(this, sharedPreferences).accent + val notificationIntent = + Intent(this, MainActivity::class.java) + .putExtra(MainActivity.KEY_INTENT_PROCESS_VIEWER, true) + val pendingIntent = + PendingIntent.getActivity( + this, + 0, + notificationIntent, + getPendingIntentFlag(0), + ) customSmallContentViews = RemoteViews(packageName, R.layout.notification_service_small) customBigContentViews = RemoteViews(packageName, R.layout.notification_service_big) - val stopIntent = Intent(KEY_COMPRESS_BROADCAST_CANCEL) - val stopPendingIntent = PendingIntent.getBroadcast( - applicationContext, - 1234, - stopIntent, - getPendingIntentFlag(FLAG_UPDATE_CURRENT) - ) - val action = NotificationCompat.Action( - R.drawable.ic_zip_box_grey, - getString(R.string.stop_ftp), - stopPendingIntent - ) - mBuilder = NotificationCompat.Builder(this, NotificationConstants.CHANNEL_NORMAL_ID) - .setSmallIcon(R.drawable.ic_zip_box_grey) - .setContentIntent(pendingIntent) - .setCustomContentView(customSmallContentViews) - .setCustomBigContentView(customBigContentViews) - .setCustomHeadsUpContentView(customSmallContentViews) - .setStyle(NotificationCompat.DecoratedCustomViewStyle()) - .addAction(action) - .setOngoing(true) - .setColor(accentColor) - + val stopPendingIntent = + PendingIntent.getBroadcast( + applicationContext, + 1234, + stopIntent, + getPendingIntentFlag(FLAG_UPDATE_CURRENT), + ) + val action = + NotificationCompat.Action( + R.drawable.ic_zip_box_grey, + getString(R.string.stop_ftp), + stopPendingIntent, + ) + mBuilder = + NotificationCompat.Builder(this, NotificationConstants.CHANNEL_NORMAL_ID) + .setSmallIcon(R.drawable.ic_zip_box_grey) + .setContentIntent(pendingIntent) + .setCustomContentView(customSmallContentViews) + .setCustomBigContentView(customBigContentViews) + .setCustomHeadsUpContentView(customSmallContentViews) + .setStyle(NotificationCompat.DecoratedCustomViewStyle()) + .addAction(action) + .setOngoing(true) + .setColor(accentColor) NotificationConstants.setMetadata(this, mBuilder, NotificationConstants.TYPE_NORMAL) startForeground(NotificationConstants.ZIP_ID, mBuilder.build()) initNotificationViews() @@ -178,9 +191,8 @@ class ZipService : AbstractProgressiveService() { inner class CompressTask( private val zipService: ZipService, private val baseFiles: ArrayList, - private val zipPath: String + private val zipPath: String, ) { - private lateinit var zos: ZipOutputStream private lateinit var watcherUtil: ServiceWatcherUtil @@ -202,13 +214,13 @@ class ZipService : AbstractProgressiveService() { baseFiles[0].getName(applicationContext), baseFiles.size, totalBytes, - false + false, ) execute( emitter, zipService.applicationContext, FileUtils.hybridListToFileArrayList(baseFiles), - zipPath + zipPath, ) emitter.onComplete() @@ -218,12 +230,13 @@ class ZipService : AbstractProgressiveService() { .subscribe( { watcherUtil.stopWatch() - val intent = Intent(MainActivity.KEY_INTENT_LOAD_LIST) - .putExtra(MainActivity.KEY_INTENT_LOAD_LIST_FILE, zipPath) + val intent = + Intent(MainActivity.KEY_INTENT_LOAD_LIST) + .putExtra(MainActivity.KEY_INTENT_LOAD_LIST_FILE, zipPath) zipService.sendBroadcast(intent) zipService.stopSelf() }, - { log.error(it.message ?: "ZipService.CompressAsyncTask.compress failed") } + { log.error(it.message ?: "ZipService.CompressAsyncTask.compress failed") }, ) } @@ -243,7 +256,7 @@ class ZipService : AbstractProgressiveService() { emitter: CompletableEmitter, context: Context, baseFiles: ArrayList, - zipPath: String + zipPath: String, ) { val out: OutputStream? val zipDirectory = File(zipPath) @@ -266,7 +279,7 @@ class ZipService : AbstractProgressiveService() { zos.close() context.sendBroadcast( Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE) - .setData(Uri.fromFile(zipDirectory)) + .setData(Uri.fromFile(zipDirectory)), ) } catch (e: IOException) { log.warn("failed to close zip streams", e) @@ -275,7 +288,10 @@ class ZipService : AbstractProgressiveService() { } @Throws(IOException::class, NullPointerException::class, ZipException::class) - private fun compressFile(file: File, path: String) { + private fun compressFile( + file: File, + path: String, + ) { if (progressHandler.cancelled) return if (!file.isDirectory) { zos.putNextEntry(createZipEntry(file, path)) @@ -286,7 +302,9 @@ class ZipService : AbstractProgressiveService() { if (!progressHandler.cancelled) { zos.write(buf, 0, len) ServiceWatcherUtil.position += len.toLong() - } else break + } else { + break + } } } return @@ -304,13 +322,17 @@ class ZipService : AbstractProgressiveService() { "$path/" } - private fun createZipEntry(file: File, path: String): ZipEntry = + private fun createZipEntry( + file: File, + path: String, + ): ZipEntry = ZipEntry("${createZipEntryPrefixWith(path)}${file.name}").apply { if (SDK_INT >= O) { - val attrs = Files.readAttributes( - Paths.get(file.absolutePath), - BasicFileAttributes::class.java - ) + val attrs = + Files.readAttributes( + Paths.get(file.absolutePath), + BasicFileAttributes::class.java, + ) setCreationTime(attrs.creationTime()) .setLastAccessTime(attrs.lastAccessTime()) .lastModifiedTime = attrs.lastModifiedTime() @@ -323,11 +345,15 @@ class ZipService : AbstractProgressiveService() { * Class used for the client Binder. Because we know this service always runs in the same process * as its clients, we don't need to deal with IPC. */ - private val receiver1: BroadcastReceiver = object : BroadcastReceiver() { - override fun onReceive(context: Context, intent: Intent) { - progressHandler.cancelled = true + private val receiver1: BroadcastReceiver = + object : BroadcastReceiver() { + override fun onReceive( + context: Context, + intent: Intent, + ) { + progressHandler.cancelled = true + } } - } override fun onBind(arg0: Intent): IBinder = mBinder diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/services/ftp/CommandFactoryFactory.kt b/app/src/main/java/com/amaze/filemanager/asynchronous/services/ftp/CommandFactoryFactory.kt index 018861df7b..ef13285490 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/services/ftp/CommandFactoryFactory.kt +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/services/ftp/CommandFactoryFactory.kt @@ -31,7 +31,6 @@ import org.apache.ftpserver.command.CommandFactoryFactory * Custom [CommandFactory] factory with custom commands. */ object CommandFactoryFactory { - /** * Encapsulate custom [CommandFactory] construction logic. Append custom AVBL and PWD command, * as well as feature flag in FEAT command if not using [AndroidFtpFileSystemView]. diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/services/ftp/FtpReceiver.kt b/app/src/main/java/com/amaze/filemanager/asynchronous/services/ftp/FtpReceiver.kt index bee0985693..9d71e405a9 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/services/ftp/FtpReceiver.kt +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/services/ftp/FtpReceiver.kt @@ -24,15 +24,18 @@ import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import android.util.Log +import androidx.core.content.ContextCompat import com.amaze.filemanager.BuildConfig.DEBUG import com.amaze.filemanager.asynchronous.services.ftp.FtpService.Companion.isRunning /** Created by yashwanthreddyg on 09-06-2016. */ class FtpReceiver : BroadcastReceiver() { - private val TAG = FtpReceiver::class.java.simpleName - override fun onReceive(context: Context, intent: Intent) { + override fun onReceive( + context: Context, + intent: Intent, + ) { if (DEBUG) { Log.v(TAG, "Received: ${intent.action}") } @@ -40,10 +43,12 @@ class FtpReceiver : BroadcastReceiver() { service.putExtras(intent) runCatching { if (intent.action == FtpService.ACTION_START_FTPSERVER && !isRunning()) { - context.startService(service) + ContextCompat.startForegroundService(context, service) } else if (intent.action == FtpService.ACTION_STOP_FTPSERVER) { context.stopService(service) - } else Unit + } else { + Unit + } }.onFailure { Log.e(TAG, "Failed to start/stop on intent ${it.message}") } diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/services/ftp/FtpService.kt b/app/src/main/java/com/amaze/filemanager/asynchronous/services/ftp/FtpService.kt index 097db8a122..d8003ce9e3 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/services/ftp/FtpService.kt +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/services/ftp/FtpService.kt @@ -27,9 +27,6 @@ import android.app.Service import android.content.Context import android.content.Intent import android.content.SharedPreferences -import android.net.ConnectivityManager -import android.net.NetworkCapabilities -import android.net.wifi.WifiManager import android.os.Build.VERSION.SDK_INT import android.os.Build.VERSION_CODES.KITKAT import android.os.Build.VERSION_CODES.LOLLIPOP @@ -66,12 +63,9 @@ import org.greenrobot.eventbus.EventBus import org.slf4j.Logger import org.slf4j.LoggerFactory import java.io.IOException -import java.net.InetAddress -import java.net.NetworkInterface -import java.net.UnknownHostException import java.security.GeneralSecurityException import java.security.KeyStore -import java.util.* +import java.util.LinkedList import javax.net.ssl.KeyManagerFactory import javax.net.ssl.TrustManagerFactory import kotlin.concurrent.thread @@ -87,7 +81,10 @@ class FtpService : Service(), Runnable { // Service will broadcast via event bus when server start/stop enum class FtpReceiverActions { - STARTED, STARTED_FROM_TILE, STOPPED, FAILED_TO_START + STARTED, + STARTED_FROM_TILE, + STOPPED, + FAILED_TO_START, } private var username: String? = null @@ -96,7 +93,11 @@ class FtpService : Service(), Runnable { private var isStartedByTile = false private lateinit var wakeLock: PowerManager.WakeLock - override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { + override fun onStartCommand( + intent: Intent?, + flags: Int, + startId: Int, + ): Int { isStartedByTile = true == intent?.getBooleanExtra(TAG_STARTED_BY_TILE, false) var attempts = 10 while (serverThread != null) { @@ -146,17 +147,19 @@ class FtpService : Service(), Runnable { commandFactory = CommandFactoryFactory.create(shouldUseAndroidFileSystem) - val usernamePreference = preferences.getString( - KEY_PREFERENCE_USERNAME, - DEFAULT_USERNAME - ) + val usernamePreference = + preferences.getString( + KEY_PREFERENCE_USERNAME, + DEFAULT_USERNAME, + ) if (usernamePreference != DEFAULT_USERNAME) { username = usernamePreference runCatching { - password = PasswordUtil.decryptPassword( - applicationContext, - preferences.getString(KEY_PREFERENCE_PASSWORD, "")!! - ) + password = + PasswordUtil.decryptPassword( + applicationContext, + preferences.getString(KEY_PREFERENCE_PASSWORD, "")!!, + ) isPasswordProtected = true }.onFailure { log.warn("failed to decrypt password in ftp service", it) @@ -173,10 +176,11 @@ class FtpService : Service(), Runnable { user.name = username user.password = password } - user.homeDirectory = preferences.getString( - KEY_PREFERENCE_PATH, - defaultPath(this@FtpService) - ) + user.homeDirectory = + preferences.getString( + KEY_PREFERENCE_PATH, + defaultPath(this@FtpService), + ) if (!preferences.getBoolean(KEY_PREFERENCE_READONLY, false)) { user.authorities = listOf(WritePermission()) } @@ -190,20 +194,23 @@ class FtpService : Service(), Runnable { val keyStore = KeyStore.getInstance("BKS") val keyStorePassword = BuildConfig.FTP_SERVER_KEYSTORE_PASSWORD.toCharArray() keyStore.load(resources.openRawResource(R.raw.key), keyStorePassword) - val keyManagerFactory = KeyManagerFactory - .getInstance(KeyManagerFactory.getDefaultAlgorithm()) + val keyManagerFactory = + KeyManagerFactory + .getInstance(KeyManagerFactory.getDefaultAlgorithm()) keyManagerFactory.init(keyStore, keyStorePassword) - val trustManagerFactory = TrustManagerFactory - .getInstance(TrustManagerFactory.getDefaultAlgorithm()) + val trustManagerFactory = + TrustManagerFactory + .getInstance(TrustManagerFactory.getDefaultAlgorithm()) trustManagerFactory.init(keyStore) - fac.sslConfiguration = DefaultSslConfiguration( - keyManagerFactory, - trustManagerFactory, - ClientAuth.WANT, - "TLS", - enabledCipherSuites, - "ftpserver" - ) + fac.sslConfiguration = + DefaultSslConfiguration( + keyManagerFactory, + trustManagerFactory, + ClientAuth.WANT, + "TLS", + enabledCipherSuites, + "ftpserver", + ) fac.isImplicitSsl = true } catch (e: GeneralSecurityException) { preferences.edit().putBoolean(KEY_PREFERENCE_SECURE, false).apply() @@ -216,17 +223,18 @@ class FtpService : Service(), Runnable { addListener("default", fac.createListener()) runCatching { - server = createServer().apply { - start() - EventBus.getDefault() - .post( - if (isStartedByTile) { - FtpReceiverActions.STARTED_FROM_TILE - } else { - FtpReceiverActions.STARTED - } - ) - } + server = + createServer().apply { + start() + EventBus.getDefault() + .post( + if (isStartedByTile) { + FtpReceiverActions.STARTED_FROM_TILE + } else { + FtpReceiverActions.STARTED + }, + ) + } }.onFailure { EventBus.getDefault().post(FtpReceiverActions.FAILED_TO_START) } @@ -254,12 +262,13 @@ class FtpService : Service(), Runnable { super.onTaskRemoved(rootIntent) val restartService = Intent(applicationContext, this.javaClass).setPackage(packageName) val flag = getPendingIntentFlag(FLAG_ONE_SHOT) - val restartServicePI = PendingIntent.getService( - applicationContext, - 1, - restartService, - flag - ) + val restartServicePI = + PendingIntent.getService( + applicationContext, + 1, + restartService, + flag, + ) val alarmService = applicationContext.getSystemService(ALARM_SERVICE) as AlarmManager alarmService[AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime() + 2000] = restartServicePI @@ -295,33 +304,34 @@ class FtpService : Service(), Runnable { private lateinit var _enabledCipherSuites: Array init { - _enabledCipherSuites = LinkedList().apply { - if (SDK_INT >= Q) { - add("TLS_AES_128_GCM_SHA256") - add("TLS_AES_256_GCM_SHA384") - add("TLS_CHACHA20_POLY1305_SHA256") - } - if (SDK_INT >= N) { - add("TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256") - add("TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256") - } - if (SDK_INT >= LOLLIPOP) { - add("TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA") - add("TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256") - add("TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA") - add("TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384") - add("TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA") - add("TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256") - add("TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA") - add("TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384") - add("TLS_RSA_WITH_AES_128_GCM_SHA256") - add("TLS_RSA_WITH_AES_256_GCM_SHA384") - } - if (SDK_INT < LOLLIPOP) { - add("TLS_RSA_WITH_AES_128_CBC_SHA") - add("TLS_RSA_WITH_AES_256_CBC_SHA") - } - }.toTypedArray() + _enabledCipherSuites = + LinkedList().apply { + if (SDK_INT >= Q) { + add("TLS_AES_128_GCM_SHA256") + add("TLS_AES_256_GCM_SHA384") + add("TLS_CHACHA20_POLY1305_SHA256") + } + if (SDK_INT >= N) { + add("TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256") + add("TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256") + } + if (SDK_INT >= LOLLIPOP) { + add("TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA") + add("TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256") + add("TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA") + add("TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384") + add("TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA") + add("TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256") + add("TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA") + add("TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384") + add("TLS_RSA_WITH_AES_128_GCM_SHA256") + add("TLS_RSA_WITH_AES_256_GCM_SHA384") + } + if (SDK_INT < LOLLIPOP) { + add("TLS_RSA_WITH_AES_128_CBC_SHA") + add("TLS_RSA_WITH_AES_256_CBC_SHA") + } + }.toTypedArray() } /** @@ -348,11 +358,11 @@ class FtpService : Service(), Runnable { @JvmStatic fun defaultPath(context: Context): String { return if (PreferenceManager.getDefaultSharedPreferences(context) - .getBoolean(KEY_PREFERENCE_SAF_FILESYSTEM, false) && SDK_INT > M + .getBoolean(KEY_PREFERENCE_SAF_FILESYSTEM, false) && SDK_INT > M ) { DocumentsContract.buildTreeDocumentUri( "com.android.externalstorage.documents", - "primary:" + "primary:", ).toString() } else { Environment.getExternalStorageDirectory().absolutePath @@ -368,110 +378,8 @@ class FtpService : Service(), Runnable { return !server.isStopped } - /** - * Is the device connected to local network, either Ethernet or Wifi? - */ - @JvmStatic - fun isConnectedToLocalNetwork(context: Context): Boolean { - val cm = context.getSystemService(CONNECTIVITY_SERVICE) as ConnectivityManager - var connected: Boolean - if (SDK_INT >= M) { - connected = cm.activeNetwork?.let { activeNetwork -> - cm.getNetworkCapabilities(activeNetwork)?.let { ni -> - ni.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) or - ni.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) - } ?: false - } ?: false - } else { - connected = cm.activeNetworkInfo?.let { ni -> - ni.isConnected && ( - ni.type and ( - ConnectivityManager.TYPE_WIFI - or ConnectivityManager.TYPE_ETHERNET - ) != 0 - ) - } ?: false - } - - if (!connected) { - connected = runCatching { - NetworkInterface.getNetworkInterfaces().toList().find { netInterface -> - netInterface.displayName.startsWith("rndis") or - netInterface.displayName.startsWith("wlan") - } - }.getOrElse { null } != null - } - - return connected - } - - /** - * Is the device connected to Wifi? - */ - @JvmStatic - fun isConnectedToWifi(context: Context): Boolean { - val cm = context.getSystemService(CONNECTIVITY_SERVICE) as ConnectivityManager - return if (SDK_INT >= M) { - cm.activeNetwork?.let { - cm.getNetworkCapabilities(it)?.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) - } ?: false - } else { - cm.activeNetworkInfo?.let { - it.isConnected && it.type == ConnectivityManager.TYPE_WIFI - } ?: false - } - } - - /** - * Determine device's IP address - */ - @JvmStatic - fun getLocalInetAddress(context: Context): InetAddress? { - if (!isConnectedToLocalNetwork(context)) { - return null - } - if (isConnectedToWifi(context)) { - val wm = context.applicationContext.getSystemService(WIFI_SERVICE) as WifiManager - val ipAddress = wm.connectionInfo.ipAddress - return if (ipAddress == 0) null else intToInet(ipAddress) - } - runCatching { - NetworkInterface.getNetworkInterfaces().iterator().forEach { netinterface -> - netinterface.inetAddresses.iterator().forEach { address -> - // this is the condition that sometimes gives problems - if (!address.isLoopbackAddress && - !address.isLinkLocalAddress - ) { - return address - } - } - } - }.onFailure { e -> - log.warn("failed to get local inet address", e) - } - return null - } - - private fun intToInet(value: Int): InetAddress? { - val bytes = ByteArray(4) - for (i in 0..3) { - bytes[i] = byteOfInt(value, i) - } - return try { - InetAddress.getByAddress(bytes) - } catch (e: UnknownHostException) { - // This only happens if the byte array has a bad length - null - } - } - - private fun byteOfInt(value: Int, which: Int): Byte { - val shift = which * 8 - return (value shr shift).toByte() - } - private fun getPort(preferences: SharedPreferences): Int { - return preferences.getInt(PORT_PREFERENCE_KEY, DEFAULT_PORT) + return preferences.getInt(FtpService.PORT_PREFERENCE_KEY, FtpService.DEFAULT_PORT) } } } diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/services/ftp/FtpTileService.java b/app/src/main/java/com/amaze/filemanager/asynchronous/services/ftp/FtpTileService.java index c9c44158f8..d0de75487b 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/services/ftp/FtpTileService.java +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/services/ftp/FtpTileService.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. @@ -24,6 +24,7 @@ import org.greenrobot.eventbus.Subscribe; import com.amaze.filemanager.R; +import com.amaze.filemanager.utils.NetworkUtil; import android.annotation.TargetApi; import android.content.Intent; @@ -64,8 +65,8 @@ public void onClick() { .sendBroadcast( new Intent(FtpService.ACTION_STOP_FTPSERVER).setPackage(getPackageName())); } else { - if (FtpService.isConnectedToWifi(getApplicationContext()) - || FtpService.isConnectedToLocalNetwork(getApplicationContext())) { + if (NetworkUtil.isConnectedToWifi(getApplicationContext()) + || NetworkUtil.isConnectedToLocalNetwork(getApplicationContext())) { Intent i = new Intent(FtpService.ACTION_START_FTPSERVER).setPackage(getPackageName()); i.putExtra(FtpService.TAG_STARTED_BY_TILE, true); getApplicationContext().sendBroadcast(i); diff --git a/app/src/main/java/com/amaze/filemanager/crashreport/AcraReportSender.kt b/app/src/main/java/com/amaze/filemanager/crashreport/AcraReportSender.kt index e528aa7630..0b599119b6 100644 --- a/app/src/main/java/com/amaze/filemanager/crashreport/AcraReportSender.kt +++ b/app/src/main/java/com/amaze/filemanager/crashreport/AcraReportSender.kt @@ -26,16 +26,18 @@ import org.acra.data.CrashReportData import org.acra.sender.ReportSender class AcraReportSender : ReportSender { - - override fun send(context: Context, errorContent: CrashReportData) { + override fun send( + context: Context, + errorContent: CrashReportData, + ) { ErrorActivity.reportError( context, errorContent, ErrorActivity.ErrorInfo.make( ErrorActivity.ERROR_UI_ERROR, "Application crash", - R.string.app_ui_crash - ) + R.string.app_ui_crash, + ), ) } diff --git a/app/src/main/java/com/amaze/filemanager/crashreport/AcraReportSenderFactory.kt b/app/src/main/java/com/amaze/filemanager/crashreport/AcraReportSenderFactory.kt index 56927b50ca..6fa9872961 100644 --- a/app/src/main/java/com/amaze/filemanager/crashreport/AcraReportSenderFactory.kt +++ b/app/src/main/java/com/amaze/filemanager/crashreport/AcraReportSenderFactory.kt @@ -26,7 +26,10 @@ import org.acra.sender.ReportSender import org.acra.sender.ReportSenderFactory class AcraReportSenderFactory : ReportSenderFactory { - override fun create(context: Context, config: CoreConfiguration): ReportSender { + override fun create( + context: Context, + config: CoreConfiguration, + ): ReportSender { return AcraReportSender() } diff --git a/app/src/main/java/com/amaze/filemanager/crashreport/ErrorActivity.java b/app/src/main/java/com/amaze/filemanager/crashreport/ErrorActivity.java index ade83ff7b2..331a00d6d7 100644 --- a/app/src/main/java/com/amaze/filemanager/crashreport/ErrorActivity.java +++ b/app/src/main/java/com/amaze/filemanager/crashreport/ErrorActivity.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. @@ -58,13 +58,13 @@ import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; -import android.widget.Button; -import android.widget.EditText; -import android.widget.TextView; import android.widget.Toast; import androidx.annotation.StringRes; import androidx.appcompat.app.ActionBar; +import androidx.appcompat.widget.AppCompatButton; +import androidx.appcompat.widget.AppCompatEditText; +import androidx.appcompat.widget.AppCompatTextView; import androidx.appcompat.widget.Toolbar; import androidx.core.app.NavUtils; @@ -109,7 +109,7 @@ public class ErrorActivity extends ThemedActivity { private ErrorInfo errorInfo; private Class returnActivity; private String currentTimeStamp; - private EditText userCommentBox; + private AppCompatEditText userCommentBox; public static void reportError( final Context context, @@ -197,14 +197,14 @@ public void onCreate(final Bundle savedInstanceState) { actionBar.setDisplayShowTitleEnabled(true); } - final Button reportEmailButton = findViewById(R.id.errorReportEmailButton); - final Button reportTelegramButton = findViewById(R.id.errorReportTelegramButton); - final Button copyButton = findViewById(R.id.errorReportCopyButton); - final Button reportGithubButton = findViewById(R.id.errorReportGitHubButton); + final AppCompatButton reportEmailButton = findViewById(R.id.errorReportEmailButton); + final AppCompatButton reportTelegramButton = findViewById(R.id.errorReportTelegramButton); + final AppCompatButton copyButton = findViewById(R.id.errorReportCopyButton); + final AppCompatButton reportGithubButton = findViewById(R.id.errorReportGitHubButton); userCommentBox = findViewById(R.id.errorCommentBox); - final TextView errorView = findViewById(R.id.errorView); - final TextView errorMessageView = findViewById(R.id.errorMessageView); + final AppCompatTextView errorView = findViewById(R.id.errorView); + final AppCompatTextView errorMessageView = findViewById(R.id.errorMessageView); returnActivity = MainActivity.class; errorInfo = intent.getParcelableExtra(ERROR_INFO); @@ -306,8 +306,8 @@ private void goToReturnActivity() { } private void buildInfo(final ErrorInfo info) { - final TextView infoLabelView = findViewById(R.id.errorInfoLabelsView); - final TextView infoView = findViewById(R.id.errorInfosView); + final AppCompatTextView infoLabelView = findViewById(R.id.errorInfoLabelsView); + final AppCompatTextView infoView = findViewById(R.id.errorInfosView); String text = ""; infoLabelView.setText(getString(R.string.info_labels).replace("\\n", "\n")); @@ -440,7 +440,7 @@ private String getOsString() { private void addGuruMeditation() { // just an easter egg - final TextView sorryView = findViewById(R.id.errorSorryView); + final AppCompatTextView sorryView = findViewById(R.id.errorSorryView); String text = sorryView.getText().toString(); text += "\n" + getString(R.string.guru_meditation); sorryView.setText(text); diff --git a/app/src/main/java/com/amaze/filemanager/database/CloudContract.java b/app/src/main/java/com/amaze/filemanager/database/CloudContract.java index f959fe2ab0..533f1b2e74 100644 --- a/app/src/main/java/com/amaze/filemanager/database/CloudContract.java +++ b/app/src/main/java/com/amaze/filemanager/database/CloudContract.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. diff --git a/app/src/main/java/com/amaze/filemanager/database/CloudHandler.java b/app/src/main/java/com/amaze/filemanager/database/CloudHandler.java index bdedf6d706..ad826679de 100644 --- a/app/src/main/java/com/amaze/filemanager/database/CloudHandler.java +++ b/app/src/main/java/com/amaze/filemanager/database/CloudHandler.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. diff --git a/app/src/main/java/com/amaze/filemanager/database/CryptHandler.kt b/app/src/main/java/com/amaze/filemanager/database/CryptHandler.kt index 1cef00b4cc..82cb9cf820 100644 --- a/app/src/main/java/com/amaze/filemanager/database/CryptHandler.kt +++ b/app/src/main/java/com/amaze/filemanager/database/CryptHandler.kt @@ -28,7 +28,6 @@ import org.slf4j.LoggerFactory /** Created by vishal on 15/4/17. */ object CryptHandler { - private val log: Logger = LoggerFactory.getLogger(CryptHandler::class.java) private val database: ExplorerDatabase = AppConfig.getInstance().explorerDatabase @@ -49,7 +48,10 @@ object CryptHandler { /** * Update specified new [EncryptedEntry] in database. */ - fun updateEntry(oldEncryptedEntry: EncryptedEntry, newEncryptedEntry: EncryptedEntry) { + fun updateEntry( + oldEncryptedEntry: EncryptedEntry, + newEncryptedEntry: EncryptedEntry, + ) { database.encryptedEntryDao().update(newEncryptedEntry).subscribeOn(Schedulers.io()) .subscribe() } diff --git a/app/src/main/java/com/amaze/filemanager/database/ExplorerDatabase.kt b/app/src/main/java/com/amaze/filemanager/database/ExplorerDatabase.kt index 65c9ec90e1..52b6fea189 100644 --- a/app/src/main/java/com/amaze/filemanager/database/ExplorerDatabase.kt +++ b/app/src/main/java/com/amaze/filemanager/database/ExplorerDatabase.kt @@ -44,11 +44,10 @@ import com.amaze.filemanager.database.models.explorer.Tab */ @Database( entities = [Tab::class, Sort::class, EncryptedEntry::class, CloudEntry::class], - version = ExplorerDatabase.DATABASE_VERSION + version = ExplorerDatabase.DATABASE_VERSION, ) @Suppress("StringLiteralDuplication", "ComplexMethod", "LongMethod") abstract class ExplorerDatabase : RoomDatabase() { - /** * Returns DAO for [Tab] objects. */ @@ -94,233 +93,243 @@ abstract class ExplorerDatabase : RoomDatabase() { private const val TEMP_TABLE_PREFIX = "temp_" // 1->2: add encrypted table (66f08f34) - internal val MIGRATION_1_2: Migration = object : Migration(1, 2) { - override fun migrate(database: SupportSQLiteDatabase) { - val CREATE_TABLE_ENCRYPTED = ( - "CREATE TABLE " + - TABLE_ENCRYPTED + - "(" + - COLUMN_ENCRYPTED_ID + - " INTEGER PRIMARY KEY," + - COLUMN_ENCRYPTED_PATH + - " TEXT," + - COLUMN_ENCRYPTED_PASSWORD + - " TEXT" + - ")" + internal val MIGRATION_1_2: Migration = + object : Migration(1, 2) { + override fun migrate(database: SupportSQLiteDatabase) { + val CREATE_TABLE_ENCRYPTED = ( + "CREATE TABLE " + + TABLE_ENCRYPTED + + "(" + + COLUMN_ENCRYPTED_ID + + " INTEGER PRIMARY KEY," + + COLUMN_ENCRYPTED_PATH + + " TEXT," + + COLUMN_ENCRYPTED_PASSWORD + + " TEXT" + + ")" ) - database.execSQL(CREATE_TABLE_ENCRYPTED) + database.execSQL(CREATE_TABLE_ENCRYPTED) + } } - } // 2->3: add cloud table (8a5ced1b) - internal val MIGRATION_2_3: Migration = object : Migration(2, 3) { - override fun migrate(database: SupportSQLiteDatabase) { - val CREATE_TABLE_CLOUD = ( - "CREATE TABLE " + - TABLE_CLOUD_PERSIST + - "(" + - COLUMN_CLOUD_ID + - " INTEGER PRIMARY KEY," + - COLUMN_CLOUD_SERVICE + - " INTEGER," + - COLUMN_CLOUD_PERSIST + - " TEXT" + - ")" + internal val MIGRATION_2_3: Migration = + object : Migration(2, 3) { + override fun migrate(database: SupportSQLiteDatabase) { + val CREATE_TABLE_CLOUD = ( + "CREATE TABLE " + + TABLE_CLOUD_PERSIST + + "(" + + COLUMN_CLOUD_ID + + " INTEGER PRIMARY KEY," + + COLUMN_CLOUD_SERVICE + + " INTEGER," + + COLUMN_CLOUD_PERSIST + + " TEXT" + + ")" ) - database.execSQL(CREATE_TABLE_CLOUD) + database.execSQL(CREATE_TABLE_CLOUD) + } } - } // 3->4: same as 2->3 (765140f6) - internal val MIGRATION_3_4: Migration = object : Migration(3, 4) { - override fun migrate(database: SupportSQLiteDatabase) = Unit - } + internal val MIGRATION_3_4: Migration = + object : Migration(3, 4) { + override fun migrate(database: SupportSQLiteDatabase) = Unit + } // 4->5: same as 3->4, same as 2->3 (37357436) - internal val MIGRATION_4_5: Migration = object : Migration(4, 5) { - override fun migrate(database: SupportSQLiteDatabase) = Unit - } + internal val MIGRATION_4_5: Migration = + object : Migration(4, 5) { + override fun migrate(database: SupportSQLiteDatabase) = Unit + } // 5->6: add sort table (fe7c0aba) - internal val MIGRATION_5_6: Migration = object : Migration(5, 6) { - override fun migrate(database: SupportSQLiteDatabase) { - database.execSQL( - "CREATE TABLE " + - TABLE_SORT + - "(" + - COLUMN_SORT_PATH + - " TEXT PRIMARY KEY," + - COLUMN_SORT_TYPE + - " INTEGER" + - ")" - ) + internal val MIGRATION_5_6: Migration = + object : Migration(5, 6) { + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL( + "CREATE TABLE " + + TABLE_SORT + + "(" + + COLUMN_SORT_PATH + + " TEXT PRIMARY KEY," + + COLUMN_SORT_TYPE + + " INTEGER" + + ")", + ) + } } - } - internal val MIGRATION_6_7: Migration = object : Migration(6, 7) { - override fun migrate(database: SupportSQLiteDatabase) { - database.execSQL( - "CREATE TABLE " + - TEMP_TABLE_PREFIX + - TABLE_TAB + - "(" + - COLUMN_TAB_NO + - " INTEGER PRIMARY KEY NOT NULL, " + - COLUMN_PATH + - " TEXT, " + - COLUMN_HOME + - " TEXT)" - ) - database.execSQL( - "INSERT INTO " + - TEMP_TABLE_PREFIX + - TABLE_TAB + - "(" + - COLUMN_TAB_NO + - "," + - COLUMN_PATH + - "," + - COLUMN_HOME + - ")" + - " SELECT " + - COLUMN_TAB_NO + - "," + - COLUMN_PATH + - "," + - COLUMN_HOME + - " FROM " + - TABLE_TAB - ) - database.execSQL("DROP TABLE $TABLE_TAB") - database.execSQL( - "ALTER TABLE $TEMP_TABLE_PREFIX$TABLE_TAB RENAME TO $TABLE_TAB" - ) - database.execSQL( - "CREATE TABLE " + - TEMP_TABLE_PREFIX + - TABLE_SORT + - "(" + - COLUMN_SORT_PATH + - " TEXT PRIMARY KEY NOT NULL, " + - COLUMN_SORT_TYPE + - " INTEGER NOT NULL)" - ) - database.execSQL( - "INSERT INTO $TEMP_TABLE_PREFIX$TABLE_SORT SELECT * FROM $TABLE_SORT" - ) - database.execSQL("DROP TABLE $TABLE_SORT") - database.execSQL( - "ALTER TABLE $TEMP_TABLE_PREFIX$TABLE_SORT RENAME TO $TABLE_SORT" - ) - database.execSQL( - "CREATE TABLE " + - TEMP_TABLE_PREFIX + - TABLE_ENCRYPTED + - "(" + - COLUMN_ENCRYPTED_ID + - " INTEGER PRIMARY KEY NOT NULL," + - COLUMN_ENCRYPTED_PATH + - " TEXT," + - COLUMN_ENCRYPTED_PASSWORD + - " TEXT)" - ) - database.execSQL( - "INSERT INTO " + - TEMP_TABLE_PREFIX + - TABLE_ENCRYPTED + - " SELECT * FROM " + - TABLE_ENCRYPTED - ) - database.execSQL("DROP TABLE $TABLE_ENCRYPTED") - database.execSQL( - "ALTER TABLE " + - TEMP_TABLE_PREFIX + - TABLE_ENCRYPTED + - " RENAME TO " + - TABLE_ENCRYPTED - ) - database.execSQL( - "CREATE TABLE " + - TEMP_TABLE_PREFIX + - TABLE_CLOUD_PERSIST + - "(" + - COLUMN_CLOUD_ID + - " INTEGER PRIMARY KEY NOT NULL," + - COLUMN_CLOUD_SERVICE + - " INTEGER," + - COLUMN_CLOUD_PERSIST + - " TEXT)" - ) - database.execSQL( - "INSERT INTO " + - TEMP_TABLE_PREFIX + - TABLE_CLOUD_PERSIST + - " SELECT * FROM " + - TABLE_CLOUD_PERSIST - ) - database.execSQL("DROP TABLE $TABLE_CLOUD_PERSIST") - database.execSQL( - "ALTER TABLE " + - TEMP_TABLE_PREFIX + - TABLE_CLOUD_PERSIST + - " RENAME TO " + - TABLE_CLOUD_PERSIST - ) + internal val MIGRATION_6_7: Migration = + object : Migration(6, 7) { + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL( + "CREATE TABLE " + + TEMP_TABLE_PREFIX + + TABLE_TAB + + "(" + + COLUMN_TAB_NO + + " INTEGER PRIMARY KEY NOT NULL, " + + COLUMN_PATH + + " TEXT, " + + COLUMN_HOME + + " TEXT)", + ) + database.execSQL( + "INSERT INTO " + + TEMP_TABLE_PREFIX + + TABLE_TAB + + "(" + + COLUMN_TAB_NO + + "," + + COLUMN_PATH + + "," + + COLUMN_HOME + + ")" + + " SELECT " + + COLUMN_TAB_NO + + "," + + COLUMN_PATH + + "," + + COLUMN_HOME + + " FROM " + + TABLE_TAB, + ) + database.execSQL("DROP TABLE $TABLE_TAB") + database.execSQL( + "ALTER TABLE $TEMP_TABLE_PREFIX$TABLE_TAB RENAME TO $TABLE_TAB", + ) + database.execSQL( + "CREATE TABLE " + + TEMP_TABLE_PREFIX + + TABLE_SORT + + "(" + + COLUMN_SORT_PATH + + " TEXT PRIMARY KEY NOT NULL, " + + COLUMN_SORT_TYPE + + " INTEGER NOT NULL)", + ) + database.execSQL( + "INSERT INTO $TEMP_TABLE_PREFIX$TABLE_SORT SELECT * FROM $TABLE_SORT", + ) + database.execSQL("DROP TABLE $TABLE_SORT") + database.execSQL( + "ALTER TABLE $TEMP_TABLE_PREFIX$TABLE_SORT RENAME TO $TABLE_SORT", + ) + database.execSQL( + "CREATE TABLE " + + TEMP_TABLE_PREFIX + + TABLE_ENCRYPTED + + "(" + + COLUMN_ENCRYPTED_ID + + " INTEGER PRIMARY KEY NOT NULL," + + COLUMN_ENCRYPTED_PATH + + " TEXT," + + COLUMN_ENCRYPTED_PASSWORD + + " TEXT)", + ) + database.execSQL( + "INSERT INTO " + + TEMP_TABLE_PREFIX + + TABLE_ENCRYPTED + + " SELECT * FROM " + + TABLE_ENCRYPTED, + ) + database.execSQL("DROP TABLE $TABLE_ENCRYPTED") + database.execSQL( + "ALTER TABLE " + + TEMP_TABLE_PREFIX + + TABLE_ENCRYPTED + + " RENAME TO " + + TABLE_ENCRYPTED, + ) + database.execSQL( + "CREATE TABLE " + + TEMP_TABLE_PREFIX + + TABLE_CLOUD_PERSIST + + "(" + + COLUMN_CLOUD_ID + + " INTEGER PRIMARY KEY NOT NULL," + + COLUMN_CLOUD_SERVICE + + " INTEGER," + + COLUMN_CLOUD_PERSIST + + " TEXT)", + ) + database.execSQL( + "INSERT INTO " + + TEMP_TABLE_PREFIX + + TABLE_CLOUD_PERSIST + + " SELECT * FROM " + + TABLE_CLOUD_PERSIST, + ) + database.execSQL("DROP TABLE $TABLE_CLOUD_PERSIST") + database.execSQL( + "ALTER TABLE " + + TEMP_TABLE_PREFIX + + TABLE_CLOUD_PERSIST + + " RENAME TO " + + TABLE_CLOUD_PERSIST, + ) + } } - } - internal val MIGRATION_7_8: Migration = object : Migration(7, 8) { - override fun migrate(database: SupportSQLiteDatabase) { - database.execSQL( - "UPDATE " + - TABLE_CLOUD_PERSIST + - " SET " + - COLUMN_CLOUD_SERVICE + - " = " + - COLUMN_CLOUD_SERVICE + - "+1" - ) + internal val MIGRATION_7_8: Migration = + object : Migration(7, 8) { + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL( + "UPDATE " + + TABLE_CLOUD_PERSIST + + " SET " + + COLUMN_CLOUD_SERVICE + + " = " + + COLUMN_CLOUD_SERVICE + + "+1", + ) + } } - } - internal val MIGRATION_8_9: Migration = object : Migration(8, 9) { - override fun migrate(database: SupportSQLiteDatabase) { - database.execSQL( - "UPDATE " + - TABLE_CLOUD_PERSIST + - " SET " + - COLUMN_CLOUD_SERVICE + - " = " + - COLUMN_CLOUD_SERVICE + - "+1" - ) + internal val MIGRATION_8_9: Migration = + object : Migration(8, 9) { + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL( + "UPDATE " + + TABLE_CLOUD_PERSIST + + " SET " + + COLUMN_CLOUD_SERVICE + + " = " + + COLUMN_CLOUD_SERVICE + + "+1", + ) + } } - } - internal val MIGRATION_9_10: Migration = object : Migration(9, 10) { - override fun migrate(database: SupportSQLiteDatabase) { - database.execSQL( - "UPDATE " + - TABLE_CLOUD_PERSIST + - " SET " + - COLUMN_CLOUD_SERVICE + - " = " + - COLUMN_CLOUD_SERVICE + - "+1" - ) + internal val MIGRATION_9_10: Migration = + object : Migration(9, 10) { + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL( + "UPDATE " + + TABLE_CLOUD_PERSIST + + " SET " + + COLUMN_CLOUD_SERVICE + + " = " + + COLUMN_CLOUD_SERVICE + + "+1", + ) + } } - } - internal val MIGRATION_10_11: Migration = object : Migration(10, DATABASE_VERSION) { - override fun migrate(database: SupportSQLiteDatabase) { - database.execSQL( - "UPDATE " + - TABLE_CLOUD_PERSIST + - " SET " + - COLUMN_CLOUD_SERVICE + - " = " + - COLUMN_CLOUD_SERVICE + - "-2" - ) + internal val MIGRATION_10_11: Migration = + object : Migration(10, DATABASE_VERSION) { + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL( + "UPDATE " + + TABLE_CLOUD_PERSIST + + " SET " + + COLUMN_CLOUD_SERVICE + + " = " + + COLUMN_CLOUD_SERVICE + + "-2", + ) + } } - } /** * Initialize the database. Optionally, may provide a custom way to create the database @@ -328,11 +337,12 @@ abstract class ExplorerDatabase : RoomDatabase() { */ @JvmStatic fun initialize(context: Context): ExplorerDatabase { - val builder = overrideDatabaseBuilder?.invoke(context) ?: Room.databaseBuilder( - context, - ExplorerDatabase::class.java, - DATABASE_NAME - ) + val builder = + overrideDatabaseBuilder?.invoke(context) ?: Room.databaseBuilder( + context, + ExplorerDatabase::class.java, + DATABASE_NAME, + ) return builder .addMigrations(MIGRATION_1_2) .addMigrations(MIGRATION_2_3) diff --git a/app/src/main/java/com/amaze/filemanager/database/SortHandler.java b/app/src/main/java/com/amaze/filemanager/database/SortHandler.java index 6812dda727..9313add374 100644 --- a/app/src/main/java/com/amaze/filemanager/database/SortHandler.java +++ b/app/src/main/java/com/amaze/filemanager/database/SortHandler.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. @@ -30,6 +30,7 @@ import com.amaze.filemanager.application.AppConfig; import com.amaze.filemanager.database.models.explorer.Sort; +import com.amaze.filemanager.filesystem.files.sort.SortType; import android.content.Context; import android.content.SharedPreferences; @@ -60,23 +61,25 @@ public static SortHandler getInstance() { return SortHandlerHolder.INSTANCE; } - public static int getSortType(Context context, String path) { + public static SortType getSortType(Context context, String path) { SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(context); final Set onlyThisFloders = sharedPref.getStringSet(PREFERENCE_SORTBY_ONLY_THIS, new HashSet<>()); final boolean onlyThis = onlyThisFloders.contains(path); final int globalSortby = Integer.parseInt(sharedPref.getString("sortby", "0")); + SortType globalSortType = SortType.getDirectorySortType(globalSortby); if (!onlyThis) { - return globalSortby; + return globalSortType; } Sort sort = SortHandler.getInstance().findEntry(path); if (sort == null) { - return globalSortby; + return globalSortType; } - return sort.type; + return SortType.getDirectorySortType(sort.type); } - public void addEntry(Sort sort) { + public void addEntry(String path, SortType sortType) { + Sort sort = new Sort(path, sortType.toDirectorySortInt()); database.sortDao().insert(sort).subscribeOn(Schedulers.io()).subscribe(); } @@ -84,7 +87,8 @@ public void clear(String path) { database.sortDao().clear(path).subscribeOn(Schedulers.io()).subscribe(); } - public void updateEntry(Sort oldSort, Sort newSort) { + public void updateEntry(Sort oldSort, String newPath, SortType newSortType) { + Sort newSort = new Sort(newPath, newSortType.toDirectorySortInt()); database.sortDao().update(newSort).subscribeOn(Schedulers.io()).subscribe(); } diff --git a/app/src/main/java/com/amaze/filemanager/database/TabHandler.java b/app/src/main/java/com/amaze/filemanager/database/TabHandler.java index 12f33ab2e6..122a963977 100644 --- a/app/src/main/java/com/amaze/filemanager/database/TabHandler.java +++ b/app/src/main/java/com/amaze/filemanager/database/TabHandler.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. diff --git a/app/src/main/java/com/amaze/filemanager/database/UtilitiesDatabase.kt b/app/src/main/java/com/amaze/filemanager/database/UtilitiesDatabase.kt index d61b7faad1..f2df627d27 100644 --- a/app/src/main/java/com/amaze/filemanager/database/UtilitiesDatabase.kt +++ b/app/src/main/java/com/amaze/filemanager/database/UtilitiesDatabase.kt @@ -65,14 +65,13 @@ import java.security.GeneralSecurityException History::class, com.amaze.filemanager.database.models.utilities.List::class, SmbEntry::class, - SftpEntry::class + SftpEntry::class, ], version = UtilitiesDatabase.DATABASE_VERSION, - exportSchema = false + exportSchema = false, ) @Suppress("StringLiteralDuplication", "ComplexMethod", "LongMethod") abstract class UtilitiesDatabase : RoomDatabase() { - /** * Returns DAO for [Hidden] objects. */ @@ -139,7 +138,7 @@ abstract class UtilitiesDatabase : RoomDatabase() { COLUMN_PATH + " TEXT UNIQUE" + ");" - ) + ) private const val queryHidden = ( "CREATE TABLE IF NOT EXISTS " + TABLE_HIDDEN + @@ -149,7 +148,7 @@ abstract class UtilitiesDatabase : RoomDatabase() { COLUMN_PATH + " TEXT UNIQUE" + ");" - ) + ) private const val queryList = ( "CREATE TABLE IF NOT EXISTS " + TABLE_LIST + @@ -159,7 +158,7 @@ abstract class UtilitiesDatabase : RoomDatabase() { COLUMN_PATH + " TEXT UNIQUE" + ");" - ) + ) private const val queryGrid = ( "CREATE TABLE IF NOT EXISTS " + TABLE_GRID + @@ -169,7 +168,7 @@ abstract class UtilitiesDatabase : RoomDatabase() { COLUMN_PATH + " TEXT UNIQUE" + ");" - ) + ) private const val queryBookmarks = ( "CREATE TABLE IF NOT EXISTS " + TABLE_BOOKMARKS + @@ -181,7 +180,7 @@ abstract class UtilitiesDatabase : RoomDatabase() { COLUMN_PATH + " TEXT UNIQUE" + ");" - ) + ) private const val querySmb = ( "CREATE TABLE IF NOT EXISTS " + TABLE_SMB + @@ -193,7 +192,7 @@ abstract class UtilitiesDatabase : RoomDatabase() { COLUMN_PATH + " TEXT UNIQUE" + ");" - ) + ) private const val querySftp = ( "CREATE TABLE IF NOT EXISTS " + TABLE_SFTP + @@ -211,214 +210,218 @@ abstract class UtilitiesDatabase : RoomDatabase() { COLUMN_PRIVATE_KEY + " TEXT" + ");" - ) + ) - internal val MIGRATION_1_2: Migration = object : Migration(1, 2) { - override fun migrate(database: SupportSQLiteDatabase) { - database.execSQL( - "CREATE TABLE IF NOT EXISTS " + - TABLE_SFTP + - " (" + - COLUMN_ID + - " INTEGER PRIMARY KEY," + - COLUMN_NAME + - " TEXT," + - COLUMN_PATH + - " TEXT UNIQUE," + - COLUMN_HOST_PUBKEY + - " TEXT," + - COLUMN_PRIVATE_KEY_NAME + - " TEXT," + - COLUMN_PRIVATE_KEY + - " TEXT" + - ");" - ) + internal val MIGRATION_1_2: Migration = + object : Migration(1, 2) { + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL( + "CREATE TABLE IF NOT EXISTS " + + TABLE_SFTP + + " (" + + COLUMN_ID + + " INTEGER PRIMARY KEY," + + COLUMN_NAME + + " TEXT," + + COLUMN_PATH + + " TEXT UNIQUE," + + COLUMN_HOST_PUBKEY + + " TEXT," + + COLUMN_PRIVATE_KEY_NAME + + " TEXT," + + COLUMN_PRIVATE_KEY + + " TEXT" + + ");", + ) + } } - } - internal val MIGRATION_2_3: Migration = object : Migration(2, 3) { - override fun migrate(database: SupportSQLiteDatabase) { - var backupTable = TEMP_TABLE_PREFIX + TABLE_HISTORY - database.execSQL(queryHistory.replace(TABLE_HISTORY, backupTable)) - database.execSQL( - "INSERT INTO $backupTable SELECT * FROM $TABLE_HISTORY group by path;" - ) - database.execSQL("DROP TABLE $TABLE_HISTORY;") - database.execSQL("ALTER TABLE $backupTable RENAME TO $TABLE_HISTORY;") - backupTable = TEMP_TABLE_PREFIX + TABLE_HIDDEN - database.execSQL(queryHidden.replace(TABLE_HIDDEN, backupTable)) - database.execSQL( - "INSERT INTO $backupTable SELECT * FROM $TABLE_HIDDEN group by path;" - ) - database.execSQL("DROP TABLE $TABLE_HIDDEN;") - database.execSQL("ALTER TABLE $backupTable RENAME TO $TABLE_HIDDEN;") - backupTable = TEMP_TABLE_PREFIX + TABLE_LIST - database.execSQL(queryList.replace(TABLE_LIST, backupTable)) - database.execSQL( - "INSERT INTO $backupTable SELECT * FROM $TABLE_LIST group by path;" - ) - database.execSQL("DROP TABLE $TABLE_LIST;") - database.execSQL("ALTER TABLE $backupTable RENAME TO $TABLE_LIST;") - backupTable = TEMP_TABLE_PREFIX + TABLE_GRID - database.execSQL(queryGrid.replace(TABLE_GRID, backupTable)) - database.execSQL( - "INSERT INTO $backupTable SELECT * FROM $TABLE_GRID group by path;" - ) - database.execSQL("DROP TABLE $TABLE_GRID;") - database.execSQL("ALTER TABLE $backupTable RENAME TO $TABLE_GRID;") - backupTable = TEMP_TABLE_PREFIX + TABLE_BOOKMARKS - database.execSQL(queryBookmarks.replace(TABLE_BOOKMARKS, backupTable)) - database.execSQL( - "INSERT INTO " + - backupTable + - " SELECT * FROM " + - TABLE_BOOKMARKS + - " group by path;" - ) - database.execSQL("DROP TABLE $TABLE_BOOKMARKS;") - database.execSQL("ALTER TABLE $backupTable RENAME TO $TABLE_BOOKMARKS;") - backupTable = TEMP_TABLE_PREFIX + TABLE_SMB - database.execSQL(querySmb.replace(TABLE_SMB, backupTable)) - database.execSQL( - "INSERT INTO $backupTable SELECT * FROM $TABLE_SMB group by path;" - ) - database.execSQL("DROP TABLE $TABLE_SMB;") - database.execSQL("ALTER TABLE $backupTable RENAME TO $TABLE_SMB;") - backupTable = TEMP_TABLE_PREFIX + TABLE_SFTP - database.execSQL(querySftp.replace(TABLE_SFTP, backupTable)) - database.execSQL( - "INSERT INTO $backupTable SELECT * FROM $TABLE_SFTP group by path;" - ) - database.execSQL("DROP TABLE $TABLE_SFTP;") - database.execSQL("ALTER TABLE $backupTable RENAME TO $TABLE_SFTP;") + internal val MIGRATION_2_3: Migration = + object : Migration(2, 3) { + override fun migrate(database: SupportSQLiteDatabase) { + var backupTable = TEMP_TABLE_PREFIX + TABLE_HISTORY + database.execSQL(queryHistory.replace(TABLE_HISTORY, backupTable)) + database.execSQL( + "INSERT INTO $backupTable SELECT * FROM $TABLE_HISTORY group by path;", + ) + database.execSQL("DROP TABLE $TABLE_HISTORY;") + database.execSQL("ALTER TABLE $backupTable RENAME TO $TABLE_HISTORY;") + backupTable = TEMP_TABLE_PREFIX + TABLE_HIDDEN + database.execSQL(queryHidden.replace(TABLE_HIDDEN, backupTable)) + database.execSQL( + "INSERT INTO $backupTable SELECT * FROM $TABLE_HIDDEN group by path;", + ) + database.execSQL("DROP TABLE $TABLE_HIDDEN;") + database.execSQL("ALTER TABLE $backupTable RENAME TO $TABLE_HIDDEN;") + backupTable = TEMP_TABLE_PREFIX + TABLE_LIST + database.execSQL(queryList.replace(TABLE_LIST, backupTable)) + database.execSQL( + "INSERT INTO $backupTable SELECT * FROM $TABLE_LIST group by path;", + ) + database.execSQL("DROP TABLE $TABLE_LIST;") + database.execSQL("ALTER TABLE $backupTable RENAME TO $TABLE_LIST;") + backupTable = TEMP_TABLE_PREFIX + TABLE_GRID + database.execSQL(queryGrid.replace(TABLE_GRID, backupTable)) + database.execSQL( + "INSERT INTO $backupTable SELECT * FROM $TABLE_GRID group by path;", + ) + database.execSQL("DROP TABLE $TABLE_GRID;") + database.execSQL("ALTER TABLE $backupTable RENAME TO $TABLE_GRID;") + backupTable = TEMP_TABLE_PREFIX + TABLE_BOOKMARKS + database.execSQL(queryBookmarks.replace(TABLE_BOOKMARKS, backupTable)) + database.execSQL( + "INSERT INTO " + + backupTable + + " SELECT * FROM " + + TABLE_BOOKMARKS + + " group by path;", + ) + database.execSQL("DROP TABLE $TABLE_BOOKMARKS;") + database.execSQL("ALTER TABLE $backupTable RENAME TO $TABLE_BOOKMARKS;") + backupTable = TEMP_TABLE_PREFIX + TABLE_SMB + database.execSQL(querySmb.replace(TABLE_SMB, backupTable)) + database.execSQL( + "INSERT INTO $backupTable SELECT * FROM $TABLE_SMB group by path;", + ) + database.execSQL("DROP TABLE $TABLE_SMB;") + database.execSQL("ALTER TABLE $backupTable RENAME TO $TABLE_SMB;") + backupTable = TEMP_TABLE_PREFIX + TABLE_SFTP + database.execSQL(querySftp.replace(TABLE_SFTP, backupTable)) + database.execSQL( + "INSERT INTO $backupTable SELECT * FROM $TABLE_SFTP group by path;", + ) + database.execSQL("DROP TABLE $TABLE_SFTP;") + database.execSQL("ALTER TABLE $backupTable RENAME TO $TABLE_SFTP;") + } } - } - internal val MIGRATION_3_4: Migration = object : Migration(3, 4) { - override fun migrate(database: SupportSQLiteDatabase) { - var backupTable = TEMP_TABLE_PREFIX + TABLE_HISTORY - database.execSQL( - queryHistory - .replace(TABLE_HISTORY, backupTable) - .replace("PRIMARY KEY,", "PRIMARY KEY NOT NULL,") - ) - database.execSQL( - "INSERT INTO $backupTable SELECT * FROM $TABLE_HISTORY group by path;" - ) - database.execSQL("DROP TABLE $TABLE_HISTORY;") - database.execSQL("ALTER TABLE $backupTable RENAME TO $TABLE_HISTORY;") - backupTable = TEMP_TABLE_PREFIX + TABLE_HIDDEN - database.execSQL( - queryHidden - .replace(TABLE_HIDDEN, backupTable) - .replace("PRIMARY KEY,", "PRIMARY KEY NOT NULL,") - ) - database.execSQL( - "INSERT INTO $backupTable SELECT * FROM $TABLE_HIDDEN group by path;" - ) - database.execSQL("DROP TABLE $TABLE_HIDDEN;") - database.execSQL("ALTER TABLE $backupTable RENAME TO $TABLE_HIDDEN;") - backupTable = TEMP_TABLE_PREFIX + TABLE_LIST - database.execSQL( - queryList - .replace(TABLE_LIST, backupTable) - .replace("PRIMARY KEY,", "PRIMARY KEY NOT NULL,") - ) - database.execSQL( - "INSERT INTO $backupTable SELECT * FROM $TABLE_LIST group by path;" - ) - database.execSQL("DROP TABLE $TABLE_LIST;") - database.execSQL("ALTER TABLE $backupTable RENAME TO $TABLE_LIST;") - backupTable = TEMP_TABLE_PREFIX + TABLE_GRID - database.execSQL( - queryGrid - .replace(TABLE_GRID, backupTable) - .replace("PRIMARY KEY,", "PRIMARY KEY NOT NULL,") - ) - database.execSQL( - "INSERT INTO $backupTable SELECT * FROM $TABLE_GRID group by path;" - ) - database.execSQL("DROP TABLE $TABLE_GRID;") - database.execSQL("ALTER TABLE $backupTable RENAME TO $TABLE_GRID;") - backupTable = TEMP_TABLE_PREFIX + TABLE_BOOKMARKS - database.execSQL( - queryBookmarks - .replace(TABLE_BOOKMARKS, backupTable) - .replace("PRIMARY KEY,", "PRIMARY KEY NOT NULL,") - ) - database.execSQL( - "INSERT INTO " + - backupTable + - " SELECT * FROM " + - TABLE_BOOKMARKS + - " group by path;" - ) - database.execSQL("DROP TABLE $TABLE_BOOKMARKS;") - database.execSQL("ALTER TABLE $backupTable RENAME TO $TABLE_BOOKMARKS;") - backupTable = TEMP_TABLE_PREFIX + TABLE_SMB - database.execSQL( - querySmb - .replace(TABLE_SMB, backupTable) - .replace("PRIMARY KEY,", "PRIMARY KEY NOT NULL,") - ) - database.execSQL( - "INSERT INTO $backupTable SELECT * FROM $TABLE_SMB group by path;" - ) - database.execSQL("DROP TABLE $TABLE_SMB;") - database.execSQL("ALTER TABLE $backupTable RENAME TO $TABLE_SMB;") - backupTable = TEMP_TABLE_PREFIX + TABLE_SFTP - database.execSQL( - querySftp - .replace(TABLE_SFTP, backupTable) - .replace("PRIMARY KEY,", "PRIMARY KEY NOT NULL,") - ) - database.execSQL( - "INSERT INTO $backupTable SELECT * FROM $TABLE_SFTP group by path;" - ) - database.execSQL("DROP TABLE $TABLE_SFTP;") - database.execSQL("ALTER TABLE $backupTable RENAME TO $TABLE_SFTP;") + internal val MIGRATION_3_4: Migration = + object : Migration(3, 4) { + override fun migrate(database: SupportSQLiteDatabase) { + var backupTable = TEMP_TABLE_PREFIX + TABLE_HISTORY + database.execSQL( + queryHistory + .replace(TABLE_HISTORY, backupTable) + .replace("PRIMARY KEY,", "PRIMARY KEY NOT NULL,"), + ) + database.execSQL( + "INSERT INTO $backupTable SELECT * FROM $TABLE_HISTORY group by path;", + ) + database.execSQL("DROP TABLE $TABLE_HISTORY;") + database.execSQL("ALTER TABLE $backupTable RENAME TO $TABLE_HISTORY;") + backupTable = TEMP_TABLE_PREFIX + TABLE_HIDDEN + database.execSQL( + queryHidden + .replace(TABLE_HIDDEN, backupTable) + .replace("PRIMARY KEY,", "PRIMARY KEY NOT NULL,"), + ) + database.execSQL( + "INSERT INTO $backupTable SELECT * FROM $TABLE_HIDDEN group by path;", + ) + database.execSQL("DROP TABLE $TABLE_HIDDEN;") + database.execSQL("ALTER TABLE $backupTable RENAME TO $TABLE_HIDDEN;") + backupTable = TEMP_TABLE_PREFIX + TABLE_LIST + database.execSQL( + queryList + .replace(TABLE_LIST, backupTable) + .replace("PRIMARY KEY,", "PRIMARY KEY NOT NULL,"), + ) + database.execSQL( + "INSERT INTO $backupTable SELECT * FROM $TABLE_LIST group by path;", + ) + database.execSQL("DROP TABLE $TABLE_LIST;") + database.execSQL("ALTER TABLE $backupTable RENAME TO $TABLE_LIST;") + backupTable = TEMP_TABLE_PREFIX + TABLE_GRID + database.execSQL( + queryGrid + .replace(TABLE_GRID, backupTable) + .replace("PRIMARY KEY,", "PRIMARY KEY NOT NULL,"), + ) + database.execSQL( + "INSERT INTO $backupTable SELECT * FROM $TABLE_GRID group by path;", + ) + database.execSQL("DROP TABLE $TABLE_GRID;") + database.execSQL("ALTER TABLE $backupTable RENAME TO $TABLE_GRID;") + backupTable = TEMP_TABLE_PREFIX + TABLE_BOOKMARKS + database.execSQL( + queryBookmarks + .replace(TABLE_BOOKMARKS, backupTable) + .replace("PRIMARY KEY,", "PRIMARY KEY NOT NULL,"), + ) + database.execSQL( + "INSERT INTO " + + backupTable + + " SELECT * FROM " + + TABLE_BOOKMARKS + + " group by path;", + ) + database.execSQL("DROP TABLE $TABLE_BOOKMARKS;") + database.execSQL("ALTER TABLE $backupTable RENAME TO $TABLE_BOOKMARKS;") + backupTable = TEMP_TABLE_PREFIX + TABLE_SMB + database.execSQL( + querySmb + .replace(TABLE_SMB, backupTable) + .replace("PRIMARY KEY,", "PRIMARY KEY NOT NULL,"), + ) + database.execSQL( + "INSERT INTO $backupTable SELECT * FROM $TABLE_SMB group by path;", + ) + database.execSQL("DROP TABLE $TABLE_SMB;") + database.execSQL("ALTER TABLE $backupTable RENAME TO $TABLE_SMB;") + backupTable = TEMP_TABLE_PREFIX + TABLE_SFTP + database.execSQL( + querySftp + .replace(TABLE_SFTP, backupTable) + .replace("PRIMARY KEY,", "PRIMARY KEY NOT NULL,"), + ) + database.execSQL( + "INSERT INTO $backupTable SELECT * FROM $TABLE_SFTP group by path;", + ) + database.execSQL("DROP TABLE $TABLE_SFTP;") + database.execSQL("ALTER TABLE $backupTable RENAME TO $TABLE_SFTP;") + } } - } - internal val MIGRATION_4_5: Migration = object : Migration(4, 5) { - override fun migrate(database: SupportSQLiteDatabase) { - val backupTable = TEMP_TABLE_PREFIX + TABLE_BOOKMARKS - database.execSQL( - queryBookmarks - .replace(TABLE_BOOKMARKS, backupTable) - .replace("PRIMARY KEY,", "PRIMARY KEY NOT NULL,") - ) - database.execSQL( - "INSERT INTO " + - backupTable + - "(" + - COLUMN_NAME + - "," + - COLUMN_PATH + - ") SELECT DISTINCT(" + - COLUMN_NAME + - "), " + - COLUMN_PATH + - " FROM " + - TABLE_BOOKMARKS - ) - database.execSQL("DROP TABLE $TABLE_BOOKMARKS;") - database.execSQL("ALTER TABLE $backupTable RENAME TO $TABLE_BOOKMARKS;") - database.execSQL( - "CREATE UNIQUE INDEX 'bookmarks_idx' ON " + - TABLE_BOOKMARKS + - "(" + - COLUMN_NAME + - ", " + - COLUMN_PATH + - ");" - ) + internal val MIGRATION_4_5: Migration = + object : Migration(4, 5) { + override fun migrate(database: SupportSQLiteDatabase) { + val backupTable = TEMP_TABLE_PREFIX + TABLE_BOOKMARKS + database.execSQL( + queryBookmarks + .replace(TABLE_BOOKMARKS, backupTable) + .replace("PRIMARY KEY,", "PRIMARY KEY NOT NULL,"), + ) + database.execSQL( + "INSERT INTO " + + backupTable + + "(" + + COLUMN_NAME + + "," + + COLUMN_PATH + + ") SELECT DISTINCT(" + + COLUMN_NAME + + "), " + + COLUMN_PATH + + " FROM " + + TABLE_BOOKMARKS, + ) + database.execSQL("DROP TABLE $TABLE_BOOKMARKS;") + database.execSQL("ALTER TABLE $backupTable RENAME TO $TABLE_BOOKMARKS;") + database.execSQL( + "CREATE UNIQUE INDEX 'bookmarks_idx' ON " + + TABLE_BOOKMARKS + + "(" + + COLUMN_NAME + + ", " + + COLUMN_PATH + + ");", + ) + } } - } private fun migratePasswordInUris( database: SupportSQLiteDatabase, - tableName: String + tableName: String, ): List { val updateSqls: MutableList = ArrayList() val cursor = @@ -430,21 +433,24 @@ abstract class UtilitiesDatabase : RoomDatabase() { val userCredentials = oldPath.substring(oldPath.indexOf("://") + 3, oldPath.lastIndexOf(AT)) if (userCredentials.contains(":")) { - val password = userCredentials.substring( - userCredentials.lastIndexOf(COLON) + 1 - ) + val password = + userCredentials.substring( + userCredentials.lastIndexOf(COLON) + 1, + ) if (!TextUtils.isEmpty(password)) { try { - val oldPassword = decryptPassword( - AppConfig.getInstance(), - password, - Base64.DEFAULT - ) - val newPassword = encryptPassword( - AppConfig.getInstance(), - oldPassword, - Base64.URL_SAFE - ) + val oldPassword = + decryptPassword( + AppConfig.getInstance(), + password, + Base64.DEFAULT, + ) + val newPassword = + encryptPassword( + AppConfig.getInstance(), + oldPassword, + Base64.URL_SAFE, + ) val newPath = oldPath.replace(password, newPassword!!) updateSqls.add( "UPDATE " + @@ -459,7 +465,7 @@ abstract class UtilitiesDatabase : RoomDatabase() { COLUMN_PATH + "='" + oldPath + - "'" + "'", ) } catch (e: GeneralSecurityException) { logger.error("Error migrating database records") @@ -474,16 +480,17 @@ abstract class UtilitiesDatabase : RoomDatabase() { return updateSqls } - internal val MIGRATION_5_6: Migration = object : Migration(5, DATABASE_VERSION) { - override fun migrate(database: SupportSQLiteDatabase) { - val updateSqls: MutableList = ArrayList() - updateSqls.addAll(migratePasswordInUris(database, TABLE_SMB)) - updateSqls.addAll(migratePasswordInUris(database, TABLE_SFTP)) - for (updateSql in updateSqls) { - database.execSQL(updateSql) + internal val MIGRATION_5_6: Migration = + object : Migration(5, DATABASE_VERSION) { + override fun migrate(database: SupportSQLiteDatabase) { + val updateSqls: MutableList = ArrayList() + updateSqls.addAll(migratePasswordInUris(database, TABLE_SMB)) + updateSqls.addAll(migratePasswordInUris(database, TABLE_SFTP)) + for (updateSql in updateSqls) { + database.execSQL(updateSql) + } } } - } /** * Initialize the database. Optionally, may provide a custom way to create the database @@ -495,7 +502,7 @@ abstract class UtilitiesDatabase : RoomDatabase() { overrideDatabaseBuilder?.invoke(context) ?: Room.databaseBuilder( context, UtilitiesDatabase::class.java, - DATABASE_NAME + DATABASE_NAME, ) return builder .allowMainThreadQueries() @@ -504,7 +511,7 @@ abstract class UtilitiesDatabase : RoomDatabase() { MIGRATION_2_3, MIGRATION_3_4, MIGRATION_4_5, - MIGRATION_5_6 + MIGRATION_5_6, ) .build() } diff --git a/app/src/main/java/com/amaze/filemanager/database/UtilsHandler.kt b/app/src/main/java/com/amaze/filemanager/database/UtilsHandler.kt index 6a194585fb..ecb870645f 100644 --- a/app/src/main/java/com/amaze/filemanager/database/UtilsHandler.kt +++ b/app/src/main/java/com/amaze/filemanager/database/UtilsHandler.kt @@ -41,7 +41,7 @@ import org.slf4j.LoggerFactory import java.io.File import java.io.IOException import java.security.GeneralSecurityException -import java.util.* +import java.util.LinkedList /** * Created by Vishal on 29-05-2017. Class handles database with tables having list of various @@ -52,13 +52,18 @@ import java.util.* */ class UtilsHandler( private val context: Context, - private val utilitiesDatabase: UtilitiesDatabase + private val utilitiesDatabase: UtilitiesDatabase, ) { - private val log: Logger = LoggerFactory.getLogger(UtilsHandler::class.java) enum class Operation { - HISTORY, HIDDEN, LIST, GRID, BOOKMARKS, SMB, SFTP + HISTORY, + HIDDEN, + LIST, + GRID, + BOOKMARKS, + SMB, + SFTP, } /** @@ -84,7 +89,7 @@ class UtilsHandler( utilitiesDatabase .listEntryDao() .insert( - com.amaze.filemanager.database.models.utilities.List(operationData.path) + com.amaze.filemanager.database.models.utilities.List(operationData.path), ) .subscribeOn(Schedulers.io()) .subscribe() @@ -118,9 +123,9 @@ class UtilsHandler( operationData.name, operationData.hostKey, operationData.sshKeyName, - operationData.sshKey - ) - ) + operationData.sshKey, + ), + ), ) .subscribeOn(Schedulers.io()) .subscribe() @@ -171,13 +176,14 @@ class UtilsHandler( */ fun addCommonBookmarks() { val sd = Environment.getExternalStorageDirectory() - val dirs = arrayOf( - File(sd, Environment.DIRECTORY_DCIM).absolutePath, - File(sd, Environment.DIRECTORY_DOWNLOADS).absolutePath, - File(sd, Environment.DIRECTORY_MOVIES).absolutePath, - File(sd, Environment.DIRECTORY_MUSIC).absolutePath, - File(sd, Environment.DIRECTORY_PICTURES).absolutePath - ) + val dirs = + arrayOf( + File(sd, Environment.DIRECTORY_DCIM).absolutePath, + File(sd, Environment.DIRECTORY_DOWNLOADS).absolutePath, + File(sd, Environment.DIRECTORY_MOVIES).absolutePath, + File(sd, Environment.DIRECTORY_MUSIC).absolutePath, + File(sd, Environment.DIRECTORY_PICTURES).absolutePath, + ) for (dir in dirs) { saveToDatabase(OperationData(Operation.BOOKMARKS, File(dir).name, dir)) } @@ -193,7 +199,7 @@ class UtilsHandler( path: String, hostKey: String?, sshKeyName: String?, - sshKey: String? + sshKey: String?, ) { utilitiesDatabase .sftpEntryDao() @@ -222,8 +228,8 @@ class UtilsHandler( get() { val paths = LinkedList() for ( - history in utilitiesDatabase.historyEntryDao().list().subscribeOn(Schedulers.io()) - .blockingGet() + history in utilitiesDatabase.historyEntryDao().list().subscribeOn(Schedulers.io()) + .blockingGet() ) { paths.add(history.path) } @@ -237,8 +243,8 @@ class UtilsHandler( get() { val paths = ConcurrentRadixTree(DefaultCharArrayNodeFactory()) for ( - path in utilitiesDatabase.hiddenEntryDao().listPaths().subscribeOn(Schedulers.io()) - .blockingGet() + path in utilitiesDatabase.hiddenEntryDao().listPaths().subscribeOn(Schedulers.io()) + .blockingGet() ) { paths.put(path, VoidValue.SINGLETON) } @@ -249,17 +255,19 @@ class UtilsHandler( * Return list of paths using list view. */ val listViewList: ArrayList - get() = ArrayList( - utilitiesDatabase.listEntryDao().listPaths().subscribeOn(Schedulers.io()).blockingGet() - ) + get() = + ArrayList( + utilitiesDatabase.listEntryDao().listPaths().subscribeOn(Schedulers.io()).blockingGet(), + ) /** * Return list of paths using grid view. */ val gridViewList: ArrayList - get() = ArrayList( - utilitiesDatabase.gridEntryDao().listPaths().subscribeOn(Schedulers.io()).blockingGet() - ) + get() = + ArrayList( + utilitiesDatabase.gridEntryDao().listPaths().subscribeOn(Schedulers.io()).blockingGet(), + ) /** * Return list of bookmarks. @@ -268,8 +276,8 @@ class UtilsHandler( get() { val row = ArrayList>() for ( - bookmark in utilitiesDatabase.bookmarkEntryDao().list() - .subscribeOn(Schedulers.io()).blockingGet() + bookmark in utilitiesDatabase.bookmarkEntryDao().list() + .subscribeOn(Schedulers.io()).blockingGet() ) { row.add(arrayOf(bookmark.name, bookmark.path)) } @@ -283,8 +291,8 @@ class UtilsHandler( get() { val retval = ArrayList>() for ( - entry in utilitiesDatabase.smbEntryDao().list().subscribeOn(Schedulers.io()) - .blockingGet() + entry in utilitiesDatabase.smbEntryDao().list().subscribeOn(Schedulers.io()) + .blockingGet() ) { try { retval.add(arrayOf(entry.name, entry.path)) @@ -295,7 +303,7 @@ class UtilsHandler( Toast.makeText( context, context.getString(R.string.failed_smb_decrypt_path), - Toast.LENGTH_LONG + Toast.LENGTH_LONG, ) .show() removeSmbPath(entry.name, "") @@ -305,7 +313,7 @@ class UtilsHandler( Toast.makeText( context, context.getString(R.string.failed_smb_decrypt_path), - Toast.LENGTH_LONG + Toast.LENGTH_LONG, ) .show() removeSmbPath(entry.name, "") @@ -322,8 +330,8 @@ class UtilsHandler( get() { val retval = ArrayList>() for ( - entry in utilitiesDatabase.sftpEntryDao().list().subscribeOn(Schedulers.io()) - .blockingGet() + entry in utilitiesDatabase.sftpEntryDao().list().subscribeOn(Schedulers.io()) + .blockingGet() ) { val path = entry.path if (path == null) { @@ -332,7 +340,7 @@ class UtilsHandler( Toast.makeText( context, context.getString(R.string.failed_smb_decrypt_path), - Toast.LENGTH_LONG + Toast.LENGTH_LONG, ).show() } else { retval.add(arrayOf(entry.name, path)) @@ -389,7 +397,10 @@ class UtilsHandler( } }.getOrNull() - private fun removeBookmarksPath(name: String, path: String) { + private fun removeBookmarksPath( + name: String, + path: String, + ) { utilitiesDatabase .bookmarkEntryDao() .deleteByNameAndPath(name, path) @@ -403,7 +414,10 @@ class UtilsHandler( * @param path the path we get from saved runtime variables is a decrypted, to remove entry, we * must encrypt it's password fiend first first */ - private fun removeSmbPath(name: String, path: String) { + private fun removeSmbPath( + name: String, + path: String, + ) { if ("" == path) { utilitiesDatabase.smbEntryDao().deleteByName(name) .subscribeOn(Schedulers.io()).subscribe() @@ -416,7 +430,10 @@ class UtilsHandler( } } - private fun removeSftpPath(name: String, path: String) { + private fun removeSftpPath( + name: String, + path: String, + ) { if ("" == path) { utilitiesDatabase.sftpEntryDao().deleteByName(name) .subscribeOn(Schedulers.io()).subscribe() @@ -432,18 +449,24 @@ class UtilsHandler( /** * Update [Bookmark]. */ - fun renameBookmark(oldName: String, oldPath: String, newName: String, newPath: String) { - val bookmark: Bookmark = kotlin.runCatching { - utilitiesDatabase - .bookmarkEntryDao() - .findByNameAndPath(oldName, oldPath) - .subscribeOn(Schedulers.io()) - .blockingGet() - }.onFailure { - // catch error to handle Single#onError for blockingGet - log.error(it.message!!) - return - }.getOrThrow() + fun renameBookmark( + oldName: String, + oldPath: String, + newName: String, + newPath: String, + ) { + val bookmark: Bookmark = + kotlin.runCatching { + utilitiesDatabase + .bookmarkEntryDao() + .findByNameAndPath(oldName, oldPath) + .subscribeOn(Schedulers.io()) + .blockingGet() + }.onFailure { + // catch error to handle Single#onError for blockingGet + log.error(it.message!!) + return + }.getOrThrow() bookmark.name = newName bookmark.path = newPath @@ -454,7 +477,12 @@ class UtilsHandler( /** * Update [SmbEntry]. */ - fun renameSMB(oldName: String, oldPath: String, newName: String, newPath: String) { + fun renameSMB( + oldName: String, + oldPath: String, + newName: String, + newPath: String, + ) { utilitiesDatabase .smbEntryDao() .findByNameAndPath(oldName, oldPath) @@ -475,8 +503,9 @@ class UtilsHandler( */ fun clearTable(table: Operation) { when (table) { - Operation.HISTORY -> utilitiesDatabase.historyEntryDao().clear() - .subscribeOn(Schedulers.io()).subscribe() + Operation.HISTORY -> + utilitiesDatabase.historyEntryDao().clear() + .subscribeOn(Schedulers.io()).subscribe() else -> {} } } diff --git a/app/src/main/java/com/amaze/filemanager/database/daos/BookmarkEntryDao.java b/app/src/main/java/com/amaze/filemanager/database/daos/BookmarkEntryDao.java index 8dcd21be09..7b0ca71154 100644 --- a/app/src/main/java/com/amaze/filemanager/database/daos/BookmarkEntryDao.java +++ b/app/src/main/java/com/amaze/filemanager/database/daos/BookmarkEntryDao.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. diff --git a/app/src/main/java/com/amaze/filemanager/database/daos/CloudEntryDao.java b/app/src/main/java/com/amaze/filemanager/database/daos/CloudEntryDao.java index 42e209f98c..62a83b211f 100644 --- a/app/src/main/java/com/amaze/filemanager/database/daos/CloudEntryDao.java +++ b/app/src/main/java/com/amaze/filemanager/database/daos/CloudEntryDao.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. diff --git a/app/src/main/java/com/amaze/filemanager/database/daos/EncryptedEntryDao.java b/app/src/main/java/com/amaze/filemanager/database/daos/EncryptedEntryDao.java index 824a52cad5..e21d4e3f2b 100644 --- a/app/src/main/java/com/amaze/filemanager/database/daos/EncryptedEntryDao.java +++ b/app/src/main/java/com/amaze/filemanager/database/daos/EncryptedEntryDao.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. diff --git a/app/src/main/java/com/amaze/filemanager/database/daos/GridEntryDao.java b/app/src/main/java/com/amaze/filemanager/database/daos/GridEntryDao.java index 57b753d92c..7ce356d355 100644 --- a/app/src/main/java/com/amaze/filemanager/database/daos/GridEntryDao.java +++ b/app/src/main/java/com/amaze/filemanager/database/daos/GridEntryDao.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. diff --git a/app/src/main/java/com/amaze/filemanager/database/daos/HiddenEntryDao.java b/app/src/main/java/com/amaze/filemanager/database/daos/HiddenEntryDao.java index 03442cb6f8..d652155e98 100644 --- a/app/src/main/java/com/amaze/filemanager/database/daos/HiddenEntryDao.java +++ b/app/src/main/java/com/amaze/filemanager/database/daos/HiddenEntryDao.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. diff --git a/app/src/main/java/com/amaze/filemanager/database/daos/HistoryEntryDao.java b/app/src/main/java/com/amaze/filemanager/database/daos/HistoryEntryDao.java index d1c791e39c..165e3adc4a 100644 --- a/app/src/main/java/com/amaze/filemanager/database/daos/HistoryEntryDao.java +++ b/app/src/main/java/com/amaze/filemanager/database/daos/HistoryEntryDao.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. diff --git a/app/src/main/java/com/amaze/filemanager/database/daos/ListEntryDao.java b/app/src/main/java/com/amaze/filemanager/database/daos/ListEntryDao.java index 417360c569..f026f9d436 100644 --- a/app/src/main/java/com/amaze/filemanager/database/daos/ListEntryDao.java +++ b/app/src/main/java/com/amaze/filemanager/database/daos/ListEntryDao.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. diff --git a/app/src/main/java/com/amaze/filemanager/database/daos/SftpEntryDao.java b/app/src/main/java/com/amaze/filemanager/database/daos/SftpEntryDao.java index babf1cca47..1e82e0cb6e 100644 --- a/app/src/main/java/com/amaze/filemanager/database/daos/SftpEntryDao.java +++ b/app/src/main/java/com/amaze/filemanager/database/daos/SftpEntryDao.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. diff --git a/app/src/main/java/com/amaze/filemanager/database/daos/SmbEntryDao.java b/app/src/main/java/com/amaze/filemanager/database/daos/SmbEntryDao.java index adfd23dc55..b00c8df9d8 100644 --- a/app/src/main/java/com/amaze/filemanager/database/daos/SmbEntryDao.java +++ b/app/src/main/java/com/amaze/filemanager/database/daos/SmbEntryDao.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. diff --git a/app/src/main/java/com/amaze/filemanager/database/daos/SortDao.java b/app/src/main/java/com/amaze/filemanager/database/daos/SortDao.java index 21f53b7813..ff09a5d551 100644 --- a/app/src/main/java/com/amaze/filemanager/database/daos/SortDao.java +++ b/app/src/main/java/com/amaze/filemanager/database/daos/SortDao.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. diff --git a/app/src/main/java/com/amaze/filemanager/database/daos/TabDao.java b/app/src/main/java/com/amaze/filemanager/database/daos/TabDao.java index 2466d6c308..164d1dec1b 100644 --- a/app/src/main/java/com/amaze/filemanager/database/daos/TabDao.java +++ b/app/src/main/java/com/amaze/filemanager/database/daos/TabDao.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. diff --git a/app/src/main/java/com/amaze/filemanager/database/models/OperationData.java b/app/src/main/java/com/amaze/filemanager/database/models/OperationData.java index b486238cfe..472e7b05cf 100644 --- a/app/src/main/java/com/amaze/filemanager/database/models/OperationData.java +++ b/app/src/main/java/com/amaze/filemanager/database/models/OperationData.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. diff --git a/app/src/main/java/com/amaze/filemanager/database/models/StringWrapper.java b/app/src/main/java/com/amaze/filemanager/database/models/StringWrapper.java index 2195c8d6d7..6968552aee 100644 --- a/app/src/main/java/com/amaze/filemanager/database/models/StringWrapper.java +++ b/app/src/main/java/com/amaze/filemanager/database/models/StringWrapper.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. diff --git a/app/src/main/java/com/amaze/filemanager/database/models/explorer/CloudEntry.java b/app/src/main/java/com/amaze/filemanager/database/models/explorer/CloudEntry.java index ec22c71edb..b161593856 100644 --- a/app/src/main/java/com/amaze/filemanager/database/models/explorer/CloudEntry.java +++ b/app/src/main/java/com/amaze/filemanager/database/models/explorer/CloudEntry.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. diff --git a/app/src/main/java/com/amaze/filemanager/database/models/explorer/EncryptedEntry.java b/app/src/main/java/com/amaze/filemanager/database/models/explorer/EncryptedEntry.java index 4f38a9a454..443f2c9d53 100644 --- a/app/src/main/java/com/amaze/filemanager/database/models/explorer/EncryptedEntry.java +++ b/app/src/main/java/com/amaze/filemanager/database/models/explorer/EncryptedEntry.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. diff --git a/app/src/main/java/com/amaze/filemanager/database/models/explorer/Sort.java b/app/src/main/java/com/amaze/filemanager/database/models/explorer/Sort.java index 30f82dc215..e05f6e2992 100644 --- a/app/src/main/java/com/amaze/filemanager/database/models/explorer/Sort.java +++ b/app/src/main/java/com/amaze/filemanager/database/models/explorer/Sort.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. diff --git a/app/src/main/java/com/amaze/filemanager/database/models/explorer/Tab.java b/app/src/main/java/com/amaze/filemanager/database/models/explorer/Tab.java index 436e998a47..b824a0ce50 100644 --- a/app/src/main/java/com/amaze/filemanager/database/models/explorer/Tab.java +++ b/app/src/main/java/com/amaze/filemanager/database/models/explorer/Tab.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. diff --git a/app/src/main/java/com/amaze/filemanager/database/models/utilities/Bookmark.java b/app/src/main/java/com/amaze/filemanager/database/models/utilities/Bookmark.java index 1fccbd0c6d..c2ab2f70e4 100644 --- a/app/src/main/java/com/amaze/filemanager/database/models/utilities/Bookmark.java +++ b/app/src/main/java/com/amaze/filemanager/database/models/utilities/Bookmark.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. diff --git a/app/src/main/java/com/amaze/filemanager/database/models/utilities/Grid.java b/app/src/main/java/com/amaze/filemanager/database/models/utilities/Grid.java index 92f976dea1..dddfb2ffaf 100644 --- a/app/src/main/java/com/amaze/filemanager/database/models/utilities/Grid.java +++ b/app/src/main/java/com/amaze/filemanager/database/models/utilities/Grid.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. diff --git a/app/src/main/java/com/amaze/filemanager/database/models/utilities/Hidden.java b/app/src/main/java/com/amaze/filemanager/database/models/utilities/Hidden.java index 86dfa305a7..4ae6ebb4a7 100644 --- a/app/src/main/java/com/amaze/filemanager/database/models/utilities/Hidden.java +++ b/app/src/main/java/com/amaze/filemanager/database/models/utilities/Hidden.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. diff --git a/app/src/main/java/com/amaze/filemanager/database/models/utilities/History.java b/app/src/main/java/com/amaze/filemanager/database/models/utilities/History.java index 9586e4b8d9..14fb5bb13f 100644 --- a/app/src/main/java/com/amaze/filemanager/database/models/utilities/History.java +++ b/app/src/main/java/com/amaze/filemanager/database/models/utilities/History.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. diff --git a/app/src/main/java/com/amaze/filemanager/database/models/utilities/List.java b/app/src/main/java/com/amaze/filemanager/database/models/utilities/List.java index 64b6f61292..0215527da1 100644 --- a/app/src/main/java/com/amaze/filemanager/database/models/utilities/List.java +++ b/app/src/main/java/com/amaze/filemanager/database/models/utilities/List.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. diff --git a/app/src/main/java/com/amaze/filemanager/database/models/utilities/OperationData.java b/app/src/main/java/com/amaze/filemanager/database/models/utilities/OperationData.java index 98eb2ab8af..dee0051065 100644 --- a/app/src/main/java/com/amaze/filemanager/database/models/utilities/OperationData.java +++ b/app/src/main/java/com/amaze/filemanager/database/models/utilities/OperationData.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. diff --git a/app/src/main/java/com/amaze/filemanager/database/models/utilities/OperationDataWithName.java b/app/src/main/java/com/amaze/filemanager/database/models/utilities/OperationDataWithName.java index 18314c524d..a39390c48b 100644 --- a/app/src/main/java/com/amaze/filemanager/database/models/utilities/OperationDataWithName.java +++ b/app/src/main/java/com/amaze/filemanager/database/models/utilities/OperationDataWithName.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. diff --git a/app/src/main/java/com/amaze/filemanager/database/models/utilities/SftpEntry.java b/app/src/main/java/com/amaze/filemanager/database/models/utilities/SftpEntry.java index 964663b85e..d6d2e33081 100644 --- a/app/src/main/java/com/amaze/filemanager/database/models/utilities/SftpEntry.java +++ b/app/src/main/java/com/amaze/filemanager/database/models/utilities/SftpEntry.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. diff --git a/app/src/main/java/com/amaze/filemanager/database/models/utilities/SmbEntry.java b/app/src/main/java/com/amaze/filemanager/database/models/utilities/SmbEntry.java index 6ae3b180f1..3910444eeb 100644 --- a/app/src/main/java/com/amaze/filemanager/database/models/utilities/SmbEntry.java +++ b/app/src/main/java/com/amaze/filemanager/database/models/utilities/SmbEntry.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. diff --git a/app/src/main/java/com/amaze/filemanager/database/typeconverters/EncryptedStringTypeConverter.kt b/app/src/main/java/com/amaze/filemanager/database/typeconverters/EncryptedStringTypeConverter.kt index ccf8bd8675..9d9a1975c6 100644 --- a/app/src/main/java/com/amaze/filemanager/database/typeconverters/EncryptedStringTypeConverter.kt +++ b/app/src/main/java/com/amaze/filemanager/database/typeconverters/EncryptedStringTypeConverter.kt @@ -37,7 +37,6 @@ import com.amaze.filemanager.utils.PasswordUtil.encryptPassword * @see PasswordUtil.decryptPassword */ object EncryptedStringTypeConverter { - @JvmStatic private val TAG = EncryptedStringTypeConverter::class.java.simpleName @@ -49,7 +48,7 @@ object EncryptedStringTypeConverter { fun toPassword(encryptedStringEntryInDb: String): StringWrapper { return runCatching { StringWrapper( - decryptPassword(AppConfig.getInstance(), encryptedStringEntryInDb) + decryptPassword(AppConfig.getInstance(), encryptedStringEntryInDb), ) }.onFailure { Log.e(TAG, "Error decrypting password", it) @@ -67,7 +66,7 @@ object EncryptedStringTypeConverter { return runCatching { encryptPassword( AppConfig.getInstance(), - unencryptedPasswordString.value + unencryptedPasswordString.value, ) }.onFailure { Log.e(TAG, "Error encrypting password", it) diff --git a/app/src/main/java/com/amaze/filemanager/database/typeconverters/OpenModeTypeConverter.kt b/app/src/main/java/com/amaze/filemanager/database/typeconverters/OpenModeTypeConverter.kt index 970cf04a4d..c1ca6e5f4f 100644 --- a/app/src/main/java/com/amaze/filemanager/database/typeconverters/OpenModeTypeConverter.kt +++ b/app/src/main/java/com/amaze/filemanager/database/typeconverters/OpenModeTypeConverter.kt @@ -25,7 +25,6 @@ import com.amaze.filemanager.fileoperations.filesystem.OpenMode /** [TypeConverter] for [OpenMode] objects to database columns. */ object OpenModeTypeConverter { - /** * Convert given [OpenMode] to integer constant for database storage. */ diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/CustomFileObserver.java b/app/src/main/java/com/amaze/filemanager/filesystem/CustomFileObserver.java index 76e053feda..4792e62fd6 100644 --- a/app/src/main/java/com/amaze/filemanager/filesystem/CustomFileObserver.java +++ b/app/src/main/java/com/amaze/filemanager/filesystem/CustomFileObserver.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/DeleteOperation.kt b/app/src/main/java/com/amaze/filemanager/filesystem/DeleteOperation.kt index 0799307ae1..c295a42b2e 100644 --- a/app/src/main/java/com/amaze/filemanager/filesystem/DeleteOperation.kt +++ b/app/src/main/java/com/amaze/filemanager/filesystem/DeleteOperation.kt @@ -37,7 +37,10 @@ object DeleteOperation { * @return true if successful. */ @JvmStatic - private fun rmdir(file: File, context: Context): Boolean { + private fun rmdir( + file: File, + context: Context, + ): Boolean { if (!file.exists()) return true val files = file.listFiles() if (files != null && files.size > 0) { @@ -70,7 +73,7 @@ object DeleteOperation { resolver.delete( MediaStore.Files.getContentUri("external"), MediaStore.MediaColumns.DATA + "=?", - arrayOf(file.absolutePath) + arrayOf(file.absolutePath), ) } return !file.exists() @@ -83,7 +86,10 @@ object DeleteOperation { * @return True if successfully deleted. */ @JvmStatic - fun deleteFile(file: File, context: Context): Boolean { + fun deleteFile( + file: File, + context: Context, + ): Boolean { // First try the normal deletion. val fileDelete = rmdir(file, context) if (file.delete() || fileDelete) return true diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/EditableFileAbstraction.java b/app/src/main/java/com/amaze/filemanager/filesystem/EditableFileAbstraction.java index a02428d303..ef4b95b48d 100644 --- a/app/src/main/java/com/amaze/filemanager/filesystem/EditableFileAbstraction.java +++ b/app/src/main/java/com/amaze/filemanager/filesystem/EditableFileAbstraction.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/ExternalSdCardOperation.kt b/app/src/main/java/com/amaze/filemanager/filesystem/ExternalSdCardOperation.kt index 3826d1a922..4872dfca54 100644 --- a/app/src/main/java/com/amaze/filemanager/filesystem/ExternalSdCardOperation.kt +++ b/app/src/main/java/com/amaze/filemanager/filesystem/ExternalSdCardOperation.kt @@ -32,7 +32,6 @@ import org.slf4j.Logger import org.slf4j.LoggerFactory import java.io.File import java.io.IOException -import java.util.* object ExternalSdCardOperation { private val log: Logger = LoggerFactory.getLogger(UtilsHandler::class.java) @@ -49,7 +48,7 @@ object ExternalSdCardOperation { fun getDocumentFile( file: File, isDirectory: Boolean, - context: Context + context: Context, ): DocumentFile? { if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) return DocumentFile.fromFile(file) val baseFolder = getExtSdCardFolder(file, context) @@ -69,8 +68,9 @@ object ExternalSdCardOperation { return null } - val preferenceUri = PreferenceManager.getDefaultSharedPreferences(context) - .getString(PreferencesConstants.PREFERENCE_URI, null) + val preferenceUri = + PreferenceManager.getDefaultSharedPreferences(context) + .getString(PreferencesConstants.PREFERENCE_URI, null) var treeUri: Uri? = null if (preferenceUri != null) { treeUri = Uri.parse(preferenceUri) @@ -93,11 +93,12 @@ object ExternalSdCardOperation { var nextDocument = document.findFile(parts[i]) if (nextDocument == null) { - nextDocument = if (i < parts.size - 1 || isDirectory) { - document.createDirectory(parts[i]) - } else { - document.createFile("image", parts[i]) - } + nextDocument = + if (i < parts.size - 1 || isDirectory) { + document.createDirectory(parts[i]) + } else { + document.createFile("image", parts[i]) + } } document = nextDocument } @@ -167,7 +168,10 @@ object ExternalSdCardOperation { */ @JvmStatic @TargetApi(Build.VERSION_CODES.KITKAT) - public fun getExtSdCardFolder(file: File, context: Context): String? { + public fun getExtSdCardFolder( + file: File, + context: Context, + ): String? { val extSdPaths = getExtSdCardPaths(context) try { for (i in extSdPaths.indices) { @@ -189,7 +193,10 @@ object ExternalSdCardOperation { */ @JvmStatic @TargetApi(Build.VERSION_CODES.KITKAT) - fun isOnExtSdCard(file: File, c: Context): Boolean { + fun isOnExtSdCard( + file: File, + c: Context, + ): Boolean { return getExtSdCardFolder(file, c) != null } } diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/FileProperties.kt b/app/src/main/java/com/amaze/filemanager/filesystem/FileProperties.kt index ed6f8128f5..2fbab94011 100644 --- a/app/src/main/java/com/amaze/filemanager/filesystem/FileProperties.kt +++ b/app/src/main/java/com/amaze/filemanager/filesystem/FileProperties.kt @@ -48,7 +48,6 @@ import java.util.regex.Pattern // TODO check if these can be done with just File methods // TODO make all of these methods File extensions object FileProperties { - private val log: Logger = LoggerFactory.getLogger(FileProperties::class.java) private const val STORAGE_PRIMARY = "primary" @@ -56,15 +55,17 @@ object FileProperties { "com.android.externalstorage.documents" @JvmField - val ANDROID_DATA_DIRS = arrayOf( - "Android/data", - "Android/obb" - ) + val ANDROID_DATA_DIRS = + arrayOf( + "Android/data", + "Android/obb", + ) @JvmField - val ANDROID_DEVICE_DATA_DIRS = ANDROID_DATA_DIRS.map { - File(Environment.getExternalStorageDirectory(), it).absolutePath - } + val ANDROID_DEVICE_DATA_DIRS = + ANDROID_DATA_DIRS.map { + File(Environment.getExternalStorageDirectory(), it).absolutePath + } /** * Check if a file is readable. @@ -122,7 +123,10 @@ object FileProperties { * @return true if it is possible to write in this directory. */ @JvmStatic - fun isWritableNormalOrSaf(folder: File?, c: Context): Boolean { + fun isWritableNormalOrSaf( + folder: File?, + c: Context, + ): Boolean { if (folder == null) { return false } @@ -158,6 +162,7 @@ object FileProperties { } // Utility methods for Kitkat + /** * Checks whether the target path exists or is writable * @@ -165,7 +170,10 @@ object FileProperties { * @return 1 if exists or writable, 0 if not writable */ @JvmStatic - fun checkFolder(f: String?, context: Context): Int { + fun checkFolder( + f: String?, + context: Context, + ): Int { if (f == null) return 0 if (f.startsWith(CifsContexts.SMB_URI_PREFIX) || f.startsWith(NetCopyClientConnectionPool.SSH_URI_PREFIX) || @@ -177,7 +185,9 @@ object FileProperties { f.startsWith(CloudHandler.CLOUD_PREFIX_DROPBOX) || f.startsWith(CloudHandler.CLOUD_PREFIX_ONE_DRIVE) || f.startsWith("content://") - ) return 1 + ) { + return 1 + } val folder = File(f) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && isOnExtSdCard(folder, context) @@ -190,15 +200,17 @@ object FileProperties { if (isWritableNormalOrSaf(folder, context)) { return 1 } - } else return if (Build.VERSION.SDK_INT == 19 && - isOnExtSdCard(folder, context) - ) { - // Assume that Kitkat workaround works - 1 - } else if (folder.canWrite()) { - 1 } else { - 0 + return if (Build.VERSION.SDK_INT == 19 && + isOnExtSdCard(folder, context) + ) { + // Assume that Kitkat workaround works + 1 + } else if (folder.canWrite()) { + 1 + } else { + 0 + } } return 0 } @@ -227,7 +239,7 @@ object FileProperties { return uri.path?.let { p -> File( Environment.getExternalStorageDirectory(), - p.substringAfter("tree/primary:") + p.substringAfter("tree/primary:"), ).absolutePath } } else { @@ -235,13 +247,23 @@ object FileProperties { } } + /** + * Remap file path + * @param path file path + * @param openDocumentTree open document tree default false + * @return remapped file path + */ @JvmStatic - fun remapPathForApi30OrAbove(path: String, openDocumentTree: Boolean = false): String { + fun remapPathForApi30OrAbove( + path: String, + openDocumentTree: Boolean = false, + ): String { return if (ANDROID_DEVICE_DATA_DIRS.containsPath(path)) { path - } else if (Build.VERSION.SDK_INT > Build.VERSION_CODES.Q && ANDROID_DEVICE_DATA_DIRS.any { - path.startsWith(it) && path != it - } + } else if (Build.VERSION.SDK_INT > Build.VERSION_CODES.Q && + ANDROID_DEVICE_DATA_DIRS.any { + path.startsWith(it) && path != it + } ) { val suffix = path.substringAfter(Environment.getExternalStorageDirectory().absolutePath) @@ -250,12 +272,12 @@ object FileProperties { if (openDocumentTree) { DocumentsContract.buildDocumentUri( COM_ANDROID_EXTERNALSTORAGE_DOCUMENTS, - documentId + documentId, ).toString() } else { DocumentsContract.buildTreeDocumentUri( COM_ANDROID_EXTERNALSTORAGE_DOCUMENTS, - documentId + documentId, ).toString() } } else { @@ -272,6 +294,8 @@ object FileProperties { AppConfig.getInstance().getSystemService(StorageStatsManager::class.java) .getFreeBytes(StorageManager.UUID_DEFAULT) } - } else 0L + } else { + 0L + } } } diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/FileUtil.java b/app/src/main/java/com/amaze/filemanager/filesystem/FileUtil.java index cd4bdec12b..0c55808a20 100644 --- a/app/src/main/java/com/amaze/filemanager/filesystem/FileUtil.java +++ b/app/src/main/java/com/amaze/filemanager/filesystem/FileUtil.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. @@ -43,7 +43,7 @@ import com.amaze.filemanager.ui.activities.MainActivity; import com.amaze.filemanager.utils.DataUtils; import com.amaze.filemanager.utils.OTGUtil; -import com.amaze.filemanager.utils.SmbUtil; +import com.amaze.filemanager.utils.smb.SmbUtil; import com.cloudrail.si.interfaces.CloudStorage; import android.content.ContentResolver; @@ -105,7 +105,7 @@ public static OutputStream getOutputStream(final File target, Context context) /** Writes uri stream from external application to the specified path */ public static final void writeUriToStorage( @NonNull final MainActivity mainActivity, - @NonNull final ArrayList uris, + @NonNull final List uris, @NonNull final ContentResolver contentResolver, @NonNull final String currentPath) { diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/FilenameHelper.kt b/app/src/main/java/com/amaze/filemanager/filesystem/FilenameHelper.kt new file mode 100644 index 0000000000..bab18a87f8 --- /dev/null +++ b/app/src/main/java/com/amaze/filemanager/filesystem/FilenameHelper.kt @@ -0,0 +1,260 @@ +/* + * Copyright (C) 2014-2023 Arpit Khurana , Vishal Nehra , + * Emmanuel Messulam, Raymond Lai and Contributors. + * + * This file is part of Amaze File Manager. + * + * Amaze File Manager is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.amaze.filemanager.filesystem + +import com.amaze.filemanager.application.AppConfig +import com.amaze.filemanager.filesystem.ftp.NetCopyConnectionInfo.Companion.SLASH +import kotlin.math.absoluteValue + +/** + * Convenient extension to return path element of a path string = the part before the last slash. + */ +fun String.pathDirname(): String = + if (contains(SLASH)) { + substringBeforeLast(SLASH) + } else { + "" + } + +/** + * Convenient extension to return the name element of a path = the part after the last slash. + */ +fun String.pathBasename(): String = + if (contains(SLASH)) { + substringAfterLast(SLASH) + } else { + this + } + +/** + * Convenient extension to return the basename element of a filename = the part after the last + * slash and before the extension (.). + */ +fun String.pathFileBasename(): String = + if (contains('.')) { + pathBasename().substringBeforeLast('.') + } else { + pathBasename() + } + +/** + * Convenient extension to return the extension element of a filename = the part after the last + * slash and after the extension (.). Returns empty string if no extension dot exist. + */ +fun String.pathFileExtension(): String = + if (contains('.')) { + pathBasename().substringAfterLast('.') + } else { + "" + } + +enum class FilenameFormatFlag { + DARWIN, + DEFAULT, + WINDOWS, + LINUX, +} + +object FilenameHelper { + // Don't split complex regexs into multiple lines. + + private const val REGEX_RAW_NUMBERS = "| [0-9]+" + private const val REGEX_SOURCE = " \\((?:(another|[0-9]+(th|st|nd|rd)) )?copy\\)|copy( [0-9]+)?|\\.\\(incomplete\\)| \\([0-9]+\\)|[- ]+" + + private val ordinals = arrayOf("th", "st", "nd", "rd") + + /** + * Strip the file path to one without increments or numbers. + * + * Default will not strip the raw numbers; specify removeRawNumbers = true to do so. + */ + @JvmStatic + fun strip( + input: String, + removeRawNumbers: Boolean = false, + ): String { + val filepath = stripIncrementInternal(input, removeRawNumbers) + val extension = filepath.pathFileExtension() + val dirname = stripIncrementInternal(filepath.pathDirname(), removeRawNumbers) + val stem = stem(filepath, removeRawNumbers) + return StringBuilder().run { + if (dirname.isNotBlank()) { + append(dirname).append(SLASH) + } + append(stem) + if (extension.isNotBlank()) { + append('.').append(extension) + } + toString() + } + } + + /** + * Returns the ordinals of the given number. So that + * + * - toOrdinal(1) returns "1st" + * - toOrdinal(2) returns "2nd" + * - toOrdinal(10) returns "10th" + * - toOrdinal(11) returns "11th" + * - toOrdinal(12) returns "12th" + * - toOrdinal(21) returns "21st" + * - toOrdinal(22) returns "22nd" + * - toOrdinal(23) returns "23rd" + * + * etc. + */ + @JvmStatic + fun toOrdinal(n: Int): String = "$n${ordinal(n.absoluteValue)}" + + /** + * Increment the filename of a given [HybridFile]. + * + * Uses [HybridFile.exists] to check file existence and if it exists, returns a HybridFile + * with new filename which does not exist. + */ + @JvmStatic + fun increment( + file: HybridFile, + platform: FilenameFormatFlag = FilenameFormatFlag.DEFAULT, + strip: Boolean = true, + removeRawNumbers: Boolean = false, + startArg: Int = 1, + ): HybridFile { + var filename = file.getName(AppConfig.getInstance()) + var dirname = file.path.pathDirname() + var basename = filename.pathFileBasename() + val extension = filename.pathFileExtension() + + var start: Int = startArg + + if (strip) { + filename = stripIncrementInternal(filename, removeRawNumbers) + dirname = stripIncrementInternal(dirname, removeRawNumbers) + basename = strip(basename, removeRawNumbers) + } + + var retval = + HybridFile( + file.mode, + dirname, + filename, + file.isDirectory(AppConfig.getInstance()), + ) + + while (retval.exists(AppConfig.getInstance())) { + filename = + if (extension.isNotBlank()) { + format(platform, basename, start++) + ".$extension" + } else { + format(platform, basename, start++) + } + retval = + HybridFile( + file.mode, + dirname, + filename, + file.isDirectory(AppConfig.getInstance()), + ) + } + + return retval + } + + private fun stripIncrementInternal( + input: String, + removeRawNumbers: Boolean = false, + ): String { + val source = + StringBuilder().run { + append(REGEX_SOURCE) + if (removeRawNumbers) { + append(REGEX_RAW_NUMBERS) + } + toString() + } + return Regex("($source)+$", RegexOption.IGNORE_CASE).replace(input, "") + } + + private fun stem( + filepath: String, + removeRawNumbers: Boolean = false, + ): String { + val extension = filepath.pathFileExtension() + return stripIncrementInternal( + filepath.pathBasename().substringBefore(".$extension"), + removeRawNumbers, + ) + } + + private fun ordinal(n: Int): String { + var retval = ordinals.getOrNull(((n % 100) - 20) % 10) + if (retval == null) { + retval = ordinals.getOrNull(n % 100) + } + if (retval == null) { + retval = ordinals[0] + } + return retval + } + + // TODO: i18n + private fun format( + flag: FilenameFormatFlag, + stem: String, + n: Int, + ): String { + return when (flag) { + FilenameFormatFlag.DARWIN -> { + if (n == 1) { + "$stem copy" + } else if (n > 1) { + "$stem copy $n" + } else { + stem + } + } + FilenameFormatFlag.LINUX -> { + when (n) { + 0 -> { + stem + } + 1 -> { + "$stem (copy)" + } + 2 -> { + "$stem (another copy)" + } + else -> { + "$stem (${toOrdinal(n)} copy)" + } + } + } + // Windows and default formatting are the same. + else -> { + if (n >= 1) { + "$stem ($n)" + } else { + stem + } + } + } + } +} diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/HybridFile.java b/app/src/main/java/com/amaze/filemanager/filesystem/HybridFile.java index 8f55be3882..ac9dc8b25d 100644 --- a/app/src/main/java/com/amaze/filemanager/filesystem/HybridFile.java +++ b/app/src/main/java/com/amaze/filemanager/filesystem/HybridFile.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. @@ -26,6 +26,8 @@ import static com.amaze.filemanager.filesystem.ftp.NetCopyClientConnectionPool.SSH_URI_PREFIX; import static com.amaze.filemanager.filesystem.ftp.NetCopyConnectionInfo.MULTI_SLASH; import static com.amaze.filemanager.filesystem.smb.CifsContexts.SMB_URI_PREFIX; +import static com.amaze.filemanager.filesystem.ssh.SFTPClientExtKt.READ_AHEAD_MAX_UNCONFIRMED_READS; +import static com.amaze.filemanager.filesystem.ssh.SshClientUtils.sftpGetSize; import java.io.File; import java.io.FileInputStream; @@ -39,13 +41,13 @@ import java.net.URLDecoder; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; -import java.text.DateFormat; -import java.text.SimpleDateFormat; import java.util.ArrayList; -import java.util.Calendar; +import java.util.Collections; import java.util.EnumSet; import java.util.List; import java.util.Locale; +import java.util.concurrent.Callable; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; @@ -67,6 +69,7 @@ import com.amaze.filemanager.filesystem.cloud.CloudUtil; import com.amaze.filemanager.filesystem.files.FileUtils; import com.amaze.filemanager.filesystem.files.GenericCopyUtil; +import com.amaze.filemanager.filesystem.files.MediaConnectionUtils; import com.amaze.filemanager.filesystem.ftp.ExtensionsKt; import com.amaze.filemanager.filesystem.ftp.FTPClientImpl; import com.amaze.filemanager.filesystem.ftp.FtpClientTemplate; @@ -74,6 +77,7 @@ import com.amaze.filemanager.filesystem.ftp.NetCopyConnectionInfo; import com.amaze.filemanager.filesystem.root.DeleteFileCommand; import com.amaze.filemanager.filesystem.root.ListFilesCommand; +import com.amaze.filemanager.filesystem.ssh.SFTPClientExtKt; import com.amaze.filemanager.filesystem.ssh.SFtpClientTemplate; import com.amaze.filemanager.filesystem.ssh.SshClientSessionTemplate; import com.amaze.filemanager.filesystem.ssh.SshClientUtils; @@ -82,11 +86,12 @@ import com.amaze.filemanager.ui.dialogs.GeneralDialogCreation; import com.amaze.filemanager.ui.fragments.preferencefragments.PreferencesConstants; import com.amaze.filemanager.utils.DataUtils; -import com.amaze.filemanager.utils.Function; import com.amaze.filemanager.utils.OTGUtil; import com.amaze.filemanager.utils.OnFileFound; -import com.amaze.filemanager.utils.SmbUtil; import com.amaze.filemanager.utils.Utils; +import com.amaze.filemanager.utils.smb.SmbUtil; +import com.amaze.trashbin.TrashBin; +import com.amaze.trashbin.TrashBinFile; import com.cloudrail.si.interfaces.CloudStorage; import com.cloudrail.si.types.SpaceAllocation; @@ -94,15 +99,16 @@ import android.content.Context; import android.net.Uri; import android.os.Build; +import android.text.TextUtils; import android.text.format.Formatter; import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.arch.core.util.Function; import androidx.documentfile.provider.DocumentFile; import androidx.preference.PreferenceManager; -import io.reactivex.Flowable; import io.reactivex.Single; import io.reactivex.SingleObserver; import io.reactivex.android.schedulers.AndroidSchedulers; @@ -119,6 +125,7 @@ import net.schmizz.sshj.connection.channel.direct.Session; import net.schmizz.sshj.sftp.FileMode; import net.schmizz.sshj.sftp.RemoteFile; +import net.schmizz.sshj.sftp.RemoteResourceInfo; import net.schmizz.sshj.sftp.SFTPClient; import net.schmizz.sshj.sftp.SFTPException; @@ -156,6 +163,8 @@ public HybridFile(OpenMode mode, String path, String name, boolean isDirectory) } else if (isRoot() && path.equals("/")) { // root of filesystem, don't concat another '/' this.path += name; + } else if (isTrashBin()) { + this.path = path; } else { this.path += "/" + name; } @@ -183,6 +192,8 @@ public void generateMode(Context context) { mode = OpenMode.GDRIVE; } else if (path.startsWith(CloudHandler.CLOUD_PREFIX_DROPBOX)) { mode = OpenMode.DROPBOX; + } else if (path.equals("7") || isTrashBin()) { + mode = OpenMode.TRASH_BIN; } else if (context == null) { mode = OpenMode.FILE; } else { @@ -230,6 +241,10 @@ public boolean isRoot() { return mode == OpenMode.ROOT; } + public boolean isTrashBin() { + return mode == OpenMode.TRASH_BIN; + } + public boolean isSmb() { return mode == OpenMode.SMB; } @@ -302,11 +317,11 @@ public long lastModified() { switch (mode) { case SFTP: final Long returnValue = - NetCopyClientUtils.INSTANCE.execute( - new SFtpClientTemplate(path, false) { + SshClientUtils.execute( + new SFtpClientTemplate(path, true) { @Override public Long execute(@NonNull SFTPClient client) throws IOException { - return client.mtime(NetCopyClientUtils.INSTANCE.extractRemotePathFrom(path)); + return client.mtime(NetCopyClientUtils.extractRemotePathFrom(path)); } }); @@ -332,6 +347,7 @@ public Long execute(@NonNull SFTPClient client) throws IOException { case NFS: break; case FILE: + case TRASH_BIN: return getFile().lastModified(); case DOCUMENT_FILE: return getDocumentFile(false).lastModified(); @@ -350,13 +366,7 @@ public long length(Context context) { if (this instanceof HybridFileParcelable) { return ((HybridFileParcelable) this).getSize(); } else { - return NetCopyClientUtils.INSTANCE.execute( - new SFtpClientTemplate(path, false) { - @Override - public Long execute(@NonNull SFTPClient client) throws IOException { - return client.size(NetCopyClientUtils.INSTANCE.extractRemotePathFrom(path)); - } - }); + return sftpGetSize.invoke(getPath()); } case SMB: s = @@ -383,6 +393,7 @@ public Long execute(@NonNull SFTPClient client) throws IOException { return s; case NFS: case FILE: + case TRASH_BIN: s = getFile().length(); return s; case ROOT: @@ -416,13 +427,18 @@ public Long execute(@NonNull SFTPClient client) throws IOException { } /** - * Path accessor. Avoid direct access to path since path may have been URL encoded. + * Path accessor. Avoid direct access to path (for non-local files) since path may have been URL + * encoded. * - * @return URL decoded path + * @return URL decoded path (for non-local files); the actual path for local files */ public String getPath() { + + if (isLocal() || isTrashBin() || isRoot() || isDocumentFile() || isAndroidDataDir()) + return path; + try { - return URLDecoder.decode(path.replace("+", "%2b"), "UTF-8"); + return URLDecoder.decode(path, "UTF-8"); } catch (UnsupportedEncodingException | IllegalArgumentException e) { LOG.warn("failed to decode path {}", path, e); return path; @@ -466,6 +482,8 @@ public String getName(Context context) { return OTGUtil.getDocumentFile( path, SafRootHolder.getUriRoot(), context, OpenMode.DOCUMENT_FILE, false) .getName(); + case TRASH_BIN: + return name; default: if (path.isEmpty()) { return ""; @@ -513,8 +531,7 @@ public FTPFile getFtpFile() { new FtpClientTemplate(path, false) { public FTPFile executeWithFtpClient(@NonNull FTPClient ftpClient) throws IOException { String path = - NetCopyClientUtils.INSTANCE.extractRemotePathFrom( - getParent(AppConfig.getInstance())); + NetCopyClientUtils.extractRemotePathFrom(getParent(AppConfig.getInstance())); ftpClient.changeWorkingDirectory(path); for (FTPFile ftpFile : ftpClient.listFiles()) { if (ftpFile.getName().equals(getName(AppConfig.getInstance()))) return ftpFile; @@ -547,6 +564,8 @@ public String getParent(Context context) { case FILE: case ROOT: return getFile().getParent(); + case TRASH_BIN: + return "7"; case SFTP: case DOCUMENT_FILE: String thisPath = path; @@ -561,14 +580,18 @@ public String getParent(Context context) { if (thisPath.isEmpty() || pathSegments.isEmpty()) return null; String currentName = pathSegments.get(pathSegments.size() - 1); - String parent = thisPath.substring(0, thisPath.lastIndexOf(currentName)); + int currentNameStartIndex = thisPath.lastIndexOf(currentName); + if (currentNameStartIndex < 0) { + return null; + } + String parent = thisPath.substring(0, currentNameStartIndex); if (ArraysKt.any(ANDROID_DATA_DIRS, dir -> parent.endsWith(dir + "/"))) { return FileProperties.unmapPathForApi30OrAbove(parent); } else { return parent; } default: - if (getPath().length() == getName(context).length()) { + if (getPath().length() <= getName(context).length()) { return null; } @@ -589,16 +612,8 @@ public boolean isDirectory() { switch (mode) { case SFTP: case FTP: - return isDirectory(AppConfig.getInstance()); case SMB: - SmbFile smbFile = getSmbFile(); - try { - isDirectory = smbFile != null && smbFile.isDirectory(); - } catch (SmbException e) { - LOG.warn("failed to get isDirectory for smb file", e); - isDirectory = false; - } - break; + return isDirectory(AppConfig.getInstance()); case ROOT: isDirectory = NativeOperations.isDirectory(path); break; @@ -610,6 +625,7 @@ public boolean isDirectory() { isDirectory = false; break; case FILE: + case TRASH_BIN: default: isDirectory = getFile().isDirectory(); break; @@ -618,17 +634,16 @@ public boolean isDirectory() { } public boolean isDirectory(Context context) { - boolean isDirectory; switch (mode) { case SFTP: final Boolean returnValue = - NetCopyClientUtils.INSTANCE.execute( - new SFtpClientTemplate(path, false) { + SshClientUtils.execute( + new SFtpClientTemplate(path, true) { @Override public Boolean execute(@NonNull SFTPClient client) { try { return client - .stat(NetCopyClientUtils.INSTANCE.extractRemotePathFrom(path)) + .stat(NetCopyClientUtils.extractRemotePathFrom(path)) .getType() .equals(FileMode.Type.DIRECTORY); } catch (IOException notFound) { @@ -640,56 +655,47 @@ public Boolean execute(@NonNull SFTPClient client) { if (returnValue == null) { LOG.error("Error obtaining if path is directory over SFTP"); + return false; } - //noinspection SimplifiableConditionalExpression - return returnValue == null ? false : returnValue; + return returnValue; case SMB: try { - isDirectory = - Single.fromCallable(() -> getSmbFile().isDirectory()) - .subscribeOn(Schedulers.io()) - .blockingGet(); + return Single.fromCallable(() -> getSmbFile().isDirectory()) + .subscribeOn(Schedulers.io()) + .blockingGet(); } catch (Exception e) { - isDirectory = false; LOG.warn("failed to get isDirectory with context for smb file", e); + return false; } - break; case FTP: FTPFile ftpFile = getFtpFile(); - isDirectory = ftpFile != null && ftpFile.isDirectory(); - break; - case FILE: - isDirectory = getFile().isDirectory(); - break; + return ftpFile != null && ftpFile.isDirectory(); case ROOT: - isDirectory = NativeOperations.isDirectory(path); - break; + return NativeOperations.isDirectory(path); case DOCUMENT_FILE: - isDirectory = getDocumentFile(false).isDirectory(); - break; + DocumentFile documentFile = getDocumentFile(false); + return documentFile != null && documentFile.isDirectory(); case OTG: - isDirectory = OTGUtil.getDocumentFile(path, context, false).isDirectory(); - break; + DocumentFile otgFile = OTGUtil.getDocumentFile(path, context, false); + return otgFile != null && otgFile.isDirectory(); case DROPBOX: case BOX: case GDRIVE: case ONEDRIVE: - isDirectory = - Single.fromCallable( - () -> - dataUtils - .getAccount(mode) - .getMetadata(CloudUtil.stripPath(mode, path)) - .getFolder()) - .subscribeOn(Schedulers.io()) - .blockingGet(); - break; - default: - isDirectory = getFile().isDirectory(); - break; + return Single.fromCallable( + () -> + dataUtils + .getAccount(mode) + .getMetadata(CloudUtil.stripPath(mode, path)) + .getFolder()) + .subscribeOn(Schedulers.io()) + .blockingGet(); + case TRASH_BIN: + default: // also handles the case `FILE` + File file = getFile(); + return file != null && file.isDirectory(); } - return isDirectory; } /** @@ -707,6 +713,7 @@ public long folderSize() { size = smbFile != null ? FileUtils.folderSize(getSmbFile()) : 0; break; case FILE: + case TRASH_BIN: size = FileUtils.folderSize(getFile(), null); break; case ROOT: @@ -726,25 +733,30 @@ public long folderSize(Context context) { switch (mode) { case SFTP: - final Long returnValue = - NetCopyClientUtils.INSTANCE.execute( - new SFtpClientTemplate(path, false) { - @Override - public Long execute(@NonNull SFTPClient client) throws IOException { - return client.size(NetCopyClientUtils.INSTANCE.extractRemotePathFrom(path)); - } - }); - - if (returnValue == null) { - LOG.error("Error obtaining size of folder over SFTP"); + Long retval = -1L; + String result = SshClientUtils.execute(getRemoteShellCommandLineResult("du -bs \"%s\"")); + if (!TextUtils.isEmpty(result) && result.indexOf('\t') > 0) { + try { + retval = Long.valueOf(result.substring(0, result.lastIndexOf('\t'))); + } catch (NumberFormatException ifParseFailed) { + LOG.warn("Unable to parse result (Seen {\"\"}), resort to old method", result); + retval = -1L; + } } - - return returnValue == null ? 0L : returnValue; + if (retval == -1L) { + Long returnValue = sftpGetSize.invoke(getPath()); + if (returnValue == null) { + LOG.error("Error obtaining size of folder over SFTP"); + } + return returnValue == null ? 0L : returnValue; + } + return retval; case SMB: SmbFile smbFile = getSmbFile(); size = (smbFile != null) ? FileUtils.folderSize(smbFile) : 0L; break; case FILE: + case TRASH_BIN: size = FileUtils.folderSize(getFile(), null); break; case ROOT: @@ -772,7 +784,6 @@ public Long execute(@NonNull SFTPClient client) throws IOException { mode, dataUtils.getAccount(mode).getMetadata(CloudUtil.stripPath(mode, path))); break; case FTP: - default: return 0l; } @@ -784,16 +795,24 @@ public long getUsableSpace() { long size = 0L; switch (mode) { case SMB: - try { - SmbFile smbFile = getSmbFile(); - size = smbFile != null ? smbFile.getDiskFreeSpace() : 0L; - } catch (SmbException e) { - size = 0L; - LOG.warn("failed to get usage space for smb file", e); - } + size = + Single.fromCallable( + (Callable) + () -> { + try { + SmbFile smbFile = getSmbFile(); + return smbFile != null ? smbFile.getDiskFreeSpace() : 0L; + } catch (SmbException e) { + LOG.warn("failed to get usage space for smb file", e); + return 0L; + } + }) + .subscribeOn(Schedulers.io()) + .blockingGet(); break; case FILE: case ROOT: + case TRASH_BIN: size = getFile().getUsableSpace(); break; case DROPBOX: @@ -805,8 +824,8 @@ public long getUsableSpace() { break; case SFTP: final Long returnValue = - NetCopyClientUtils.INSTANCE.execute( - new SFtpClientTemplate(path, false) { + SshClientUtils.execute( + new SFtpClientTemplate(path, true) { @Override public Long execute(@NonNull SFTPClient client) throws IOException { try { @@ -817,8 +836,7 @@ public Long execute(@NonNull SFTPClient client) throws IOException { .getSFTPEngine() .request( Statvfs.request( - client, - NetCopyClientUtils.INSTANCE.extractRemotePathFrom(path))) + client, NetCopyClientUtils.extractRemotePathFrom(path))) .retrieve()); return response.diskFreeSpace(); } catch (SFTPException e) { @@ -880,6 +898,7 @@ public long getTotal(Context context) { break; case FILE: case ROOT: + case TRASH_BIN: size = getFile().getTotalSpace(); break; case DROPBOX: @@ -891,8 +910,8 @@ public long getTotal(Context context) { break; case SFTP: final Long returnValue = - NetCopyClientUtils.INSTANCE.execute( - new SFtpClientTemplate(path, false) { + SshClientUtils.execute( + new SFtpClientTemplate(path, true) { @Override public Long execute(@NonNull SFTPClient client) throws IOException { try { @@ -903,8 +922,7 @@ public Long execute(@NonNull SFTPClient client) throws IOException { .getSFTPEngine() .request( Statvfs.request( - client, - NetCopyClientUtils.INSTANCE.extractRemotePathFrom(path))) + client, NetCopyClientUtils.extractRemotePathFrom(path))) .retrieve()); return response.diskSize(); } catch (SFTPException e) { @@ -941,40 +959,30 @@ public Long execute(@NonNull SFTPClient client) throws IOException { public void forEachChildrenFile(Context context, boolean isRoot, OnFileFound onFileFound) { switch (mode) { case SFTP: - NetCopyClientUtils.INSTANCE.execute( - new SFtpClientTemplate(getPath(), false) { + SshClientUtils.execute( + new SFtpClientTemplate(getPath(), true) { @Override public Boolean execute(@NonNull SFTPClient client) { try { - Flowable.fromIterable( - client.ls(NetCopyClientUtils.INSTANCE.extractRemotePathFrom(getPath()))) - .onBackpressureBuffer() - .subscribeOn(Schedulers.computation()) - .map( - info -> { - boolean isDirectory = false; - try { - isDirectory = SshClientUtils.isDirectory(client, info); - } catch (IOException ifBrokenSymlink) { - LOG.warn("IOException checking isDirectory(): " + info.getPath()); - return Flowable.empty(); - } - return new HybridFileParcelable(getPath(), isDirectory, info); - }) - .doOnNext( - v -> { - if (v instanceof HybridFileParcelable) { - onFileFound.onFileFound((HybridFileParcelable) v); - } - }) - .blockingSubscribe(); + for (RemoteResourceInfo info : + client.ls(NetCopyClientUtils.extractRemotePathFrom(getPath()))) { + boolean isDirectory = false; + try { + isDirectory = SshClientUtils.isDirectory(client, info); + } catch (IOException ifBrokenSymlink) { + LOG.warn("IOException checking isDirectory(): " + info.getPath()); + continue; + } + HybridFileParcelable f = new HybridFileParcelable(getPath(), isDirectory, info); + onFileFound.onFileFound(f); + } } catch (IOException e) { LOG.warn("IOException", e); AppConfig.toast( context, context.getString( R.string.cannot_read_directory, - parseAndFormatUriForDisplay(path), + parseAndFormatUriForDisplay(getPath()), e.getMessage())); } return true; @@ -1002,7 +1010,7 @@ public Boolean execute(@NonNull SFTPClient client) { } break; case FTP: - String thisPath = NetCopyClientUtils.INSTANCE.extractRemotePathFrom(getPath()); + String thisPath = NetCopyClientUtils.extractRemotePathFrom(getPath()); FTPFile[] ftpFiles = NetCopyClientUtils.INSTANCE.execute( new FtpClientTemplate(getPath(), false) { @@ -1033,6 +1041,7 @@ public FTPFile[] executeWithFtpClient(@NonNull FTPClient ftpClient) LOG.warn("failed to get children file for cloud file", e); } break; + case TRASH_BIN: default: ListFilesCommand.INSTANCE.listFiles( path, @@ -1097,13 +1106,18 @@ public InputStream getInputStream(Context context) { @Override public InputStream execute(@NonNull final SFTPClient client) throws IOException { final RemoteFile rf = - client.open(NetCopyClientUtils.INSTANCE.extractRemotePathFrom(getPath())); - return rf.new RemoteFileInputStream() { + SFTPClientExtKt.openWithReadAheadSupport( + client, NetCopyClientUtils.extractRemotePathFrom(getPath())); + return rf.new ReadAheadRemoteFileInputStream(READ_AHEAD_MAX_UNCONFIRMED_READS) { @Override public void close() throws IOException { try { + LOG.debug("Closing input stream for {}", getPath()); super.close(); + } catch (Throwable e) { + e.printStackTrace(); } finally { + LOG.debug("Closing client for {}", getPath()); rf.close(); client.close(); } @@ -1138,7 +1152,7 @@ public InputStream executeWithFtpClient(@NonNull FTPClient ftpClient) File tmpFile = File.createTempFile("ftp-transfer_", ".tmp"); tmpFile.deleteOnExit(); ftpClient.changeWorkingDirectory( - NetCopyClientUtils.INSTANCE.extractRemotePathFrom(parent)); + NetCopyClientUtils.extractRemotePathFrom(parent)); ftpClient.setFileType(FTP.BINARY_FILE_TYPE); InputStream fin = ftpClient.retrieveFileStream(getName(AppConfig.getInstance())); @@ -1179,6 +1193,7 @@ public InputStream executeWithFtpClient(@NonNull FTPClient ftpClient) LOG.debug(CloudUtil.stripPath(mode, path)); inputStream = cloudStorageOneDrive.download(CloudUtil.stripPath(mode, path)); break; + case TRASH_BIN: default: try { inputStream = new FileInputStream(path); @@ -1196,14 +1211,14 @@ public OutputStream getOutputStream(Context context) { OutputStream outputStream; switch (mode) { case SFTP: - return NetCopyClientUtils.INSTANCE.execute( + return SshClientUtils.execute( new SFtpClientTemplate(getPath(), false) { @Nullable @Override public OutputStream execute(@NonNull SFTPClient client) throws IOException { final RemoteFile rf = client.open( - NetCopyClientUtils.INSTANCE.extractRemotePathFrom(getPath()), + NetCopyClientUtils.extractRemotePathFrom(getPath()), EnumSet.of( net.schmizz.sshj.sftp.OpenMode.WRITE, net.schmizz.sshj.sftp.OpenMode.CREAT)); @@ -1231,7 +1246,7 @@ public void close() throws IOException { public OutputStream executeWithFtpClient(@NonNull FTPClient ftpClient) throws IOException { ftpClient.setFileType(FTP.BINARY_FILE_TYPE); - String remotePath = NetCopyClientUtils.INSTANCE.extractRemotePathFrom(path); + String remotePath = NetCopyClientUtils.extractRemotePathFrom(path); OutputStream outputStream = ftpClient.storeFileStream(remotePath); if (outputStream != null) { return FTPClientImpl.wrap(outputStream, ftpClient); @@ -1269,6 +1284,7 @@ public OutputStream executeWithFtpClient(@NonNull FTPClient ftpClient) outputStream = null; } break; + case TRASH_BIN: default: try { outputStream = FileUtil.getOutputStream(getFile(), context); @@ -1289,8 +1305,7 @@ public boolean exists() { @Override public Boolean execute(SFTPClient client) throws IOException { try { - return client.stat(NetCopyClientUtils.INSTANCE.extractRemotePathFrom(path)) - != null; + return client.stat(NetCopyClientUtils.extractRemotePathFrom(path)) != null; } catch (SFTPException notFound) { return false; } @@ -1330,6 +1345,9 @@ public Boolean execute(SFTPClient client) throws IOException { exists = getFile().exists(); } else if (isRoot()) { return RootHelper.fileExists(path); + } else if (isTrashBin()) { + if (getFile() != null) return getFile().exists(); + else return false; } return exists; @@ -1370,7 +1388,8 @@ public boolean isSimpleFile() { && !isDropBoxFile() && !isBoxFile() && !isSftp() - && !isFtp(); + && !isFtp() + && !isTrashBin(); } public boolean setLastModified(final long date) { @@ -1393,15 +1412,33 @@ public boolean setLastModified(final long date) { new FtpClientTemplate(path, false) { public Boolean executeWithFtpClient(@NonNull FTPClient ftpClient) throws IOException { - Calendar calendar = Calendar.getInstance(); - calendar.setTimeInMillis(date); - DateFormat df = new SimpleDateFormat("yyyyMMddHHmmss", Locale.US); - df.setCalendar(calendar); return ftpClient.setModificationTime( - NetCopyClientUtils.INSTANCE.extractRemotePathFrom(path), - df.format(calendar.getTime())); + NetCopyClientUtils.extractRemotePathFrom(path), + NetCopyClientUtils.getTimestampForTouch(date)); } })); + } else if (isSftp()) { + return Boolean.TRUE.equals( + SshClientUtils.execute( + new SshClientSessionTemplate(getPath()) { + @Override + public Boolean execute(@NonNull Session session) throws IOException { + Session.Command cmd = + session.exec( + String.format( + Locale.US, + "touch -m -t %s \"%s\"", + NetCopyClientUtils.getTimestampForTouch(date), + getPath())); + // Quirk: need to wait the command to finish + IOUtils.readFully(cmd.getInputStream()); + cmd.close(); + return 0 == cmd.getExitStatus(); + } + })); + } else if (isTrashBin()) { + // do nothing + return true; } else { File f = getFile(); return f.setLastModified(date); @@ -1410,12 +1447,12 @@ public Boolean executeWithFtpClient(@NonNull FTPClient ftpClient) public void mkdir(Context context) { if (isSftp()) { - NetCopyClientUtils.INSTANCE.execute( + SshClientUtils.execute( new SFtpClientTemplate(path, true) { @Override public Boolean execute(@NonNull SFTPClient client) { try { - client.mkdir(NetCopyClientUtils.INSTANCE.extractRemotePathFrom(path)); + client.mkdir(NetCopyClientUtils.extractRemotePathFrom(path)); } catch (IOException e) { LOG.error("Error making directory over SFTP", e); } @@ -1427,7 +1464,7 @@ public Boolean execute(@NonNull SFTPClient client) { new FtpClientTemplate(getPath(), false) { public Boolean executeWithFtpClient(@NonNull FTPClient ftpClient) throws IOException { ExtensionsKt.makeDirectoryTree( - ftpClient, NetCopyClientUtils.INSTANCE.extractRemotePathFrom(getPath())); + ftpClient, NetCopyClientUtils.extractRemotePathFrom(getPath())); return true; } }); @@ -1464,6 +1501,7 @@ public Boolean executeWithFtpClient(@NonNull FTPClient ftpClient) throws IOExcep } catch (Exception e) { LOG.warn("failed to create folder for cloud file", e); } + } else if (isTrashBin()) { // do nothing } else MakeDirectoryOperation.mkdirs(context, this); } @@ -1471,11 +1509,11 @@ public boolean delete(Context context, boolean rootmode) throws ShellNotRunningException, SmbException { if (isSftp()) { Boolean retval = - NetCopyClientUtils.INSTANCE.execute( - new SFtpClientTemplate(path, false) { + SshClientUtils.execute( + new SFtpClientTemplate(path, true) { @Override public Boolean execute(@NonNull SFTPClient client) throws IOException { - String _path = NetCopyClientUtils.INSTANCE.extractRemotePathFrom(path); + String _path = NetCopyClientUtils.extractRemotePathFrom(path); if (isDirectory(AppConfig.getInstance())) client.rmdir(_path); else client.rm(_path); return client.statExistence(_path) == null; @@ -1489,8 +1527,7 @@ public Boolean execute(@NonNull SFTPClient client) throws IOException { @Override public Boolean executeWithFtpClient(@NonNull FTPClient ftpClient) throws IOException { - return ftpClient.deleteFile( - NetCopyClientUtils.INSTANCE.extractRemotePathFrom(path)); + return ftpClient.deleteFile(NetCopyClientUtils.extractRemotePathFrom(path)); } }); return retval != null && retval; @@ -1501,6 +1538,13 @@ public Boolean executeWithFtpClient(@NonNull FTPClient ftpClient) LOG.error("Error delete SMB file", e); throw e; } + } else if (isTrashBin()) { + try { + deletePermanentlyFromBin(context); + } catch (Exception e) { + LOG.error("failed to delete trash bin file", e); + throw e; + } } else { if (isRoot() && rootmode) { setMode(OpenMode.ROOT); @@ -1512,6 +1556,59 @@ public Boolean executeWithFtpClient(@NonNull FTPClient ftpClient) return !exists(); } + public void restoreFromBin(Context context) { + List trashBinFiles = Collections.singletonList(this.toTrashBinFile(context)); + TrashBin trashBin = AppConfig.getInstance().getTrashBinInstance(); + if (trashBin != null) { + trashBin.moveToBin( + trashBinFiles, + true, + (originalFilePath, trashBinDestination) -> { + File source = new File(originalFilePath); + File dest = new File(trashBinDestination); + if (!source.renameTo(dest)) { + return false; + } + MediaConnectionUtils.scanFile(context, new HybridFile[] {this}); + return true; + }); + } + } + + public boolean moveToBin(Context context) { + List trashBinFiles = Collections.singletonList(this.toTrashBinFile(context)); + TrashBin trashBin = AppConfig.getInstance().getTrashBinInstance(); + if (trashBin != null) { + trashBin.moveToBin( + trashBinFiles, + true, + (originalFilePath, trashBinDestination) -> { + File source = new File(originalFilePath); + File dest = new File(trashBinDestination); + return source.renameTo(dest); + }); + } + return true; + } + + public boolean deletePermanentlyFromBin(Context context) { + List trashBinFiles = + Collections.singletonList(this.toTrashBinRestoreFile(context)); + TrashBin trashBin = AppConfig.getInstance().getTrashBinInstance(); + AtomicBoolean isDelete = new AtomicBoolean(false); + if (trashBin != null) { + trashBin.deletePermanently( + trashBinFiles, + s -> { + LOG.info("deleting from bin at path " + s); + isDelete.set(DeleteOperation.deleteFile(getFile(), context)); + return isDelete.get(); + }, + true); + } + return isDelete.get(); + } + /** * Returns the name of file excluding it's extension If no extension is found then whole file name * is returned @@ -1532,6 +1629,7 @@ public LayoutElementParcelable generateLayoutElement(@NonNull Context c, boolean switch (mode) { case FILE: case ROOT: + case TRASH_BIN: File file = getFile(); LayoutElementParcelable layoutElement; if (isDirectory(c)) { @@ -1546,7 +1644,7 @@ public LayoutElementParcelable generateLayoutElement(@NonNull Context c, boolean 0, true, file.lastModified() + "", - false, + file.isDirectory(), showThumbs, mode); } else { @@ -1643,7 +1741,7 @@ public void getMd5Checksum(Context context, Function callback) { switch (mode) { case SFTP: String md5Command = "md5sum -b \"%s\" | cut -c -32"; - return SshClientUtils.execute(getSftpHash(md5Command)); + return SshClientUtils.execute(getRemoteShellCommandLineResult(md5Command)); default: byte[] b = createChecksum(context); String result = ""; @@ -1685,7 +1783,7 @@ public void getSha256Checksum(Context context, Function callback) switch (mode) { case SFTP: String shaCommand = "sha256sum -b \"%s\" | cut -c -64"; - return SshClientUtils.execute(getSftpHash(shaCommand)); + return SshClientUtils.execute(getRemoteShellCommandLineResult(shaCommand)); default: MessageDigest messageDigest = MessageDigest.getInstance("SHA-256"); byte[] input = new byte[GenericCopyUtil.DEFAULT_BUFFER_SIZE]; @@ -1733,11 +1831,39 @@ public void onError(Throwable e) { }); } - private SshClientSessionTemplate getSftpHash(String command) { + /** + * Returns trash bin file with path that points to deleted path + * + * @param context + * @return + */ + public TrashBinFile toTrashBinFile(Context context) { + return new TrashBinFile(getName(context), isDirectory(context), path, length(context), null); + } + + /** + * Returns trash bin file with path that points to where the file should be restored + * + * @param context + * @return + */ + public TrashBinFile toTrashBinRestoreFile(Context context) { + TrashBin trashBin = AppConfig.getInstance().getTrashBinInstance(); + for (TrashBinFile trashBinFile : trashBin.listFilesInBin()) { + if (trashBinFile.getDeletedPath(trashBin.getConfig()).equals(path)) { + // finding path to restore to + return new TrashBinFile( + getName(context), isDirectory(context), trashBinFile.getPath(), length(context), null); + } + } + return null; + } + + private SshClientSessionTemplate getRemoteShellCommandLineResult(String command) { return new SshClientSessionTemplate(path) { @Override public String execute(Session session) throws IOException { - String extractedPath = NetCopyClientUtils.INSTANCE.extractRemotePathFrom(path); + String extractedPath = NetCopyClientUtils.extractRemotePathFrom(getPath()); String fullCommand = String.format(command, extractedPath); Session.Command cmd = session.exec(fullCommand); String result = new String(IOUtils.readFully(cmd.getInputStream()).toByteArray()); diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/HybridFileParcelable.java b/app/src/main/java/com/amaze/filemanager/filesystem/HybridFileParcelable.java index 50928e1e6b..46381a08a8 100644 --- a/app/src/main/java/com/amaze/filemanager/filesystem/HybridFileParcelable.java +++ b/app/src/main/java/com/amaze/filemanager/filesystem/HybridFileParcelable.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. @@ -27,6 +27,7 @@ import org.slf4j.LoggerFactory; import com.amaze.filemanager.fileoperations.filesystem.OpenMode; +import com.amaze.filemanager.filesystem.files.sort.ComparableParcelable; import com.amaze.filemanager.filesystem.ftp.ExtensionsKt; import com.amaze.filemanager.utils.Utils; @@ -44,7 +45,7 @@ import net.schmizz.sshj.sftp.RemoteResourceInfo; import net.schmizz.sshj.xfer.FilePermission; -public class HybridFileParcelable extends HybridFile implements Parcelable { +public class HybridFileParcelable extends HybridFile implements Parcelable, ComparableParcelable { private final Logger LOG = LoggerFactory.getLogger(HybridFileParcelable.class); private long date, size; @@ -99,6 +100,11 @@ public HybridFileParcelable(String path, boolean isDirectory, RemoteResourceInfo Integer.toString(FilePermission.toMask(sshFile.getAttributes().getPermissions()), 8)); } + @Override + public long lastModified() { + return date; + } + public String getName() { if (!Utils.isNullOrEmpty(name)) return name; else return super.getSimpleName(); @@ -142,6 +148,12 @@ public boolean isDirectory() { return isDirectory; } + @Override + public boolean isDirectory(Context context) { + if (isSmb() || isSftp()) return isDirectory; + else return super.isDirectory(context); + } + public boolean isHidden() { return name.startsWith("."); } @@ -250,4 +262,10 @@ public int hashCode() { result = 37 * result + (int) (date ^ date >>> 32); return result; } + + @NonNull + @Override + public String getParcelableName() { + return getName(); + } } diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/MakeDirectoryOperation.kt b/app/src/main/java/com/amaze/filemanager/filesystem/MakeDirectoryOperation.kt index 5d44a4a697..0106c0ddbd 100644 --- a/app/src/main/java/com/amaze/filemanager/filesystem/MakeDirectoryOperation.kt +++ b/app/src/main/java/com/amaze/filemanager/filesystem/MakeDirectoryOperation.kt @@ -33,7 +33,6 @@ import java.io.IOException // This object is here to not polute the global namespace // All functions must be static object MakeDirectoryOperation { - private val log: Logger = LoggerFactory.getLogger(MakeDirectoryOperation::class.java) /** @@ -44,7 +43,10 @@ object MakeDirectoryOperation { */ @JvmStatic @Deprecated("use {@link #mkdirs(Context, HybridFile)}") - fun mkdir(file: File?, context: Context): Boolean { + fun mkdir( + file: File?, + context: Context, + ): Boolean { if (file == null) return false if (file.exists()) { // nothing to create. @@ -73,11 +75,22 @@ object MakeDirectoryOperation { } catch (e: IOException) { false } - } else false + } else { + false + } } + /** + * Creates the directories on given [file] path, including nonexistent parent directories. + * So use proper [HybridFile] constructor as per your need. + * + * @return true if successfully created directory, otherwise returns false. + */ @JvmStatic - fun mkdirs(context: Context, file: HybridFile): Boolean { + fun mkdirs( + context: Context, + file: HybridFile, + ): Boolean { var isSuccessful = true when (file.mode) { OpenMode.SMB -> diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/MakeFileOperation.kt b/app/src/main/java/com/amaze/filemanager/filesystem/MakeFileOperation.kt index 528c5bc2d6..1e49ef1fda 100644 --- a/app/src/main/java/com/amaze/filemanager/filesystem/MakeFileOperation.kt +++ b/app/src/main/java/com/amaze/filemanager/filesystem/MakeFileOperation.kt @@ -43,13 +43,25 @@ object MakeFileOperation { * @return The temp file. */ @JvmStatic - fun getTempFile(file: File, context: Context): File { + fun getTempFile( + file: File, + context: Context, + ): File { val extDir = context.getExternalFilesDir(null) return File(extDir, file.name) } + /** + * Make normal file + * @param file File + * @param context Context + * @return true for success and false for failed + */ @JvmStatic - fun mkfile(file: File?, context: Context): Boolean { + fun mkfile( + file: File?, + context: Context, + ): Boolean { if (file == null) return false if (file.exists()) { // nothing to create. @@ -75,10 +87,10 @@ object MakeFileOperation { ( document?.createFile( MimeTypes.getMimeType(file.path, file.isDirectory), - file.name + file.name, ) != null - ) + ) } catch (e: UnsupportedOperationException) { log.warn("Failed to create file on sd card using document file", e) false @@ -86,15 +98,29 @@ object MakeFileOperation { } return if (Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT) { MediaStoreHack.mkfile(context, file) - } else false + } else { + false + } } + /** + * Make text file + * @param data file data + * @param path path + * @param fileName file name + * @return true for success and false for failed + */ @JvmStatic - fun mktextfile(data: String?, path: String?, fileName: String): Boolean { - val f = File( - path, - "$fileName${AppConstants.NEW_FILE_DELIMITER}${AppConstants.NEW_FILE_EXTENSION_TXT}" - ) + fun mktextfile( + data: String?, + path: String?, + fileName: String, + ): Boolean { + val f = + File( + path, + "$fileName${AppConstants.NEW_FILE_DELIMITER}${AppConstants.NEW_FILE_EXTENSION_TXT}", + ) var out: FileOutputStream? = null var outputWriter: OutputStreamWriter? = null return try { diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/MediaStoreHack.java b/app/src/main/java/com/amaze/filemanager/filesystem/MediaStoreHack.java index 38a34472d9..5336fd6bc0 100644 --- a/app/src/main/java/com/amaze/filemanager/filesystem/MediaStoreHack.java +++ b/app/src/main/java/com/amaze/filemanager/filesystem/MediaStoreHack.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/Operations.java b/app/src/main/java/com/amaze/filemanager/filesystem/Operations.java index 4125f726eb..02d46eb5ec 100644 --- a/app/src/main/java/com/amaze/filemanager/filesystem/Operations.java +++ b/app/src/main/java/com/amaze/filemanager/filesystem/Operations.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. @@ -41,12 +41,14 @@ import com.amaze.filemanager.fileoperations.filesystem.OpenMode; import com.amaze.filemanager.filesystem.cloud.CloudUtil; import com.amaze.filemanager.filesystem.files.FileUtils; +import com.amaze.filemanager.filesystem.files.MediaConnectionUtils; import com.amaze.filemanager.filesystem.ftp.FtpClientTemplate; import com.amaze.filemanager.filesystem.ftp.NetCopyClientUtils; import com.amaze.filemanager.filesystem.root.MakeDirectoryCommand; import com.amaze.filemanager.filesystem.root.MakeFileCommand; import com.amaze.filemanager.filesystem.root.RenameFileCommand; import com.amaze.filemanager.filesystem.ssh.SFtpClientTemplate; +import com.amaze.filemanager.filesystem.ssh.SshClientUtils; import com.amaze.filemanager.utils.DataUtils; import com.amaze.filemanager.utils.OTGUtil; import com.cloudrail.si.interfaces.CloudStorage; @@ -67,7 +69,7 @@ public class Operations { - private static Executor executor = AsyncTask.THREAD_POOL_EXECUTOR; + private static final Executor executor = AsyncTask.THREAD_POOL_EXECUTOR; private static final Logger LOG = LoggerFactory.getLogger(Operations.class); @@ -579,14 +581,14 @@ protected Void doInBackground(Void... params) { } return null; } else if (oldFile.isSftp()) { - NetCopyClientUtils.INSTANCE.execute( - new SFtpClientTemplate(oldFile.getPath(), false) { + SshClientUtils.execute( + new SFtpClientTemplate(oldFile.getPath(), true) { @Override public Void execute(@NonNull SFTPClient client) { try { client.rename( - NetCopyClientUtils.INSTANCE.extractRemotePathFrom(oldFile.getPath()), - NetCopyClientUtils.INSTANCE.extractRemotePathFrom(newFile.getPath())); + NetCopyClientUtils.extractRemotePathFrom(oldFile.getPath()), + NetCopyClientUtils.extractRemotePathFrom(newFile.getPath())); errorCallBack.done(newFile, true); } catch (IOException e) { String errmsg = @@ -620,8 +622,8 @@ public Boolean executeWithFtpClient(@NonNull FTPClient ftpClient) throws IOException { boolean result = ftpClient.rename( - NetCopyClientUtils.INSTANCE.extractRemotePathFrom(oldFile.getPath()), - NetCopyClientUtils.INSTANCE.extractRemotePathFrom(newFile.getPath())); + NetCopyClientUtils.extractRemotePathFrom(oldFile.getPath()), + NetCopyClientUtils.extractRemotePathFrom(newFile.getPath())); errorCallBack.done(newFile, result); return result; } @@ -715,7 +717,7 @@ protected void onPostExecute(Void aVoid) { super.onPostExecute(aVoid); if (newFile != null && oldFile != null) { HybridFile[] hybridFiles = {newFile, oldFile}; - FileUtils.scanFile(context, hybridFiles); + MediaConnectionUtils.scanFile(context, hybridFiles); } } }.executeOnExecutor(executor); diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/PasteHelper.java b/app/src/main/java/com/amaze/filemanager/filesystem/PasteHelper.java index ac83ec6726..74adac5fa3 100644 --- a/app/src/main/java/com/amaze/filemanager/filesystem/PasteHelper.java +++ b/app/src/main/java/com/amaze/filemanager/filesystem/PasteHelper.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. @@ -29,14 +29,13 @@ import org.slf4j.LoggerFactory; import com.amaze.filemanager.R; -import com.amaze.filemanager.asynchronous.asynctasks.movecopy.PrepareCopyTask; +import com.amaze.filemanager.asynchronous.asynctasks.movecopy.PreparePasteTask; import com.amaze.filemanager.ui.activities.MainActivity; import com.amaze.filemanager.ui.fragments.MainFragment; import com.amaze.filemanager.utils.Utils; import com.google.android.material.snackbar.BaseTransientBottomBar; import com.google.android.material.snackbar.Snackbar; -import android.os.AsyncTask; import android.os.Parcel; import android.os.Parcelable; import android.text.Spanned; @@ -168,14 +167,13 @@ public void onSuccess(Spanned spanned) { ArrayList arrayList = new ArrayList<>(Arrays.asList(paths)); boolean move = operation == PasteHelper.OPERATION_CUT; - new PrepareCopyTask( + new PreparePasteTask(mainActivity) + .execute( path, move, - mainActivity, mainActivity.isRootExplorer(), mainFragment.getMainFragmentViewModel().getOpenMode(), - arrayList) - .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + arrayList); dismissSnackbar(true); }, () -> dismissSnackbar(true)); diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/RenameOperation.kt b/app/src/main/java/com/amaze/filemanager/filesystem/RenameOperation.kt index 731ebb0cc4..27a4e2bf7d 100644 --- a/app/src/main/java/com/amaze/filemanager/filesystem/RenameOperation.kt +++ b/app/src/main/java/com/amaze/filemanager/filesystem/RenameOperation.kt @@ -26,7 +26,11 @@ import android.util.Log import com.amaze.filemanager.fileoperations.exceptions.ShellNotRunningException import com.amaze.filemanager.filesystem.MakeDirectoryOperation.mkdir import com.amaze.filemanager.filesystem.root.RenameFileCommand.renameFile -import java.io.* +import java.io.File +import java.io.FileInputStream +import java.io.FileOutputStream +import java.io.IOException +import java.io.OutputStream import java.nio.channels.FileChannel object RenameOperation { @@ -40,14 +44,17 @@ object RenameOperation { * @return true if the copying was successful. */ @JvmStatic - private fun copyFile(source: File, target: File, context: Context): Boolean { + private fun copyFile( + source: File, + target: File, + context: Context, + ): Boolean { var inStream: FileInputStream? = null var outStream: OutputStream? = null var inChannel: FileChannel? = null var outChannel: FileChannel? = null try { inStream = FileInputStream(source) - // First try the normal way if (FileProperties.isWritable(target)) { // standard way @@ -56,20 +63,21 @@ object RenameOperation { outChannel = outStream.channel inChannel.transferTo(0, inChannel.size(), outChannel) } else { - outStream = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - // Storage Access Framework - val targetDocument = - ExternalSdCardOperation.getDocumentFile(target, false, context) - targetDocument ?: throw IOException("Couldn't get DocumentFile") - context.contentResolver.openOutputStream(targetDocument.uri) - } else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT) { - // Workaround for Kitkat ext SD card - val uri = MediaStoreHack.getUriFromFile(target.absolutePath, context) - uri ?: return false - context.contentResolver.openOutputStream(uri) - } else { - return false - } + outStream = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + // Storage Access Framework + val targetDocument = + ExternalSdCardOperation.getDocumentFile(target, false, context) + targetDocument ?: throw IOException("Couldn't get DocumentFile") + context.contentResolver.openOutputStream(targetDocument.uri) + } else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT) { + // Workaround for Kitkat ext SD card + val uri = MediaStoreHack.getUriFromFile(target.absolutePath, context) + uri ?: return false + context.contentResolver.openOutputStream(uri) + } else { + return false + } if (outStream != null) { // Both for SAF and for Kitkat, write to output stream. val buffer = ByteArray(16384) // MAGIC_NUMBER @@ -83,7 +91,7 @@ object RenameOperation { Log.e( LOG, "Error when copying file from ${source.absolutePath} to ${target.absolutePath}", - e + e, ) return false } finally { @@ -113,7 +121,11 @@ object RenameOperation { @JvmStatic @Throws(ShellNotRunningException::class) - private fun rename(f: File, name: String, root: Boolean): Boolean { + private fun rename( + f: File, + name: String, + root: Boolean, + ): Boolean { val parentName = f.parent ?: return false val parentFile = f.parentFile ?: return false @@ -140,7 +152,7 @@ object RenameOperation { fun renameFolder( source: File, target: File, - context: Context + context: Context, ): Boolean { // First try the normal rename. if (rename(source, target.name, false)) { diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/RootHelper.java b/app/src/main/java/com/amaze/filemanager/filesystem/RootHelper.java index dd9c36f25b..bead57ded7 100644 --- a/app/src/main/java/com/amaze/filemanager/filesystem/RootHelper.java +++ b/app/src/main/java/com/amaze/filemanager/filesystem/RootHelper.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/cloud/CloudUtil.java b/app/src/main/java/com/amaze/filemanager/filesystem/cloud/CloudUtil.java index f83449dc50..bc7bb48d8e 100644 --- a/app/src/main/java/com/amaze/filemanager/filesystem/cloud/CloudUtil.java +++ b/app/src/main/java/com/amaze/filemanager/filesystem/cloud/CloudUtil.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/compressed/CompressedHelper.java b/app/src/main/java/com/amaze/filemanager/filesystem/compressed/CompressedHelper.java index 8dc2a80cf7..13d5135a78 100644 --- a/app/src/main/java/com/amaze/filemanager/filesystem/compressed/CompressedHelper.java +++ b/app/src/main/java/com/amaze/filemanager/filesystem/compressed/CompressedHelper.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/compressed/extractcontents/Extractor.java b/app/src/main/java/com/amaze/filemanager/filesystem/compressed/extractcontents/Extractor.java index 4b9b8506b2..3e1a5097a4 100644 --- a/app/src/main/java/com/amaze/filemanager/filesystem/compressed/extractcontents/Extractor.java +++ b/app/src/main/java/com/amaze/filemanager/filesystem/compressed/extractcontents/Extractor.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/compressed/extractcontents/helpers/AbstractCommonsArchiveExtractor.kt b/app/src/main/java/com/amaze/filemanager/filesystem/compressed/extractcontents/helpers/AbstractCommonsArchiveExtractor.kt index d2e0ee964d..d9e8e63899 100644 --- a/app/src/main/java/com/amaze/filemanager/filesystem/compressed/extractcontents/helpers/AbstractCommonsArchiveExtractor.kt +++ b/app/src/main/java/com/amaze/filemanager/filesystem/compressed/extractcontents/helpers/AbstractCommonsArchiveExtractor.kt @@ -30,17 +30,19 @@ import com.amaze.filemanager.filesystem.compressed.extractcontents.Extractor import com.amaze.filemanager.filesystem.files.GenericCopyUtil import org.apache.commons.compress.archivers.ArchiveEntry import org.apache.commons.compress.archivers.ArchiveInputStream -import java.io.* -import java.util.* +import java.io.BufferedOutputStream +import java.io.File +import java.io.FileInputStream +import java.io.IOException +import java.io.InputStream abstract class AbstractCommonsArchiveExtractor( context: Context, filePath: String, outputPath: String, listener: OnUpdate, - updatePosition: UpdatePosition + updatePosition: UpdatePosition, ) : Extractor(context, filePath, outputPath, listener, updatePosition) { - /** * Subclasses implement this method to create [ArchiveInputStream] instances with given archive * as [InputStream]. @@ -92,7 +94,7 @@ abstract class AbstractCommonsArchiveExtractor( context: Context, inputStream: ArchiveInputStream, entry: ArchiveEntry, - outputDir: String + outputDir: String, ) { if (entry.isDirectory) { MakeDirectoryOperation.mkdir(File(outputDir, entry.name), context) @@ -118,8 +120,8 @@ abstract class AbstractCommonsArchiveExtractor( context.getString( R.string.error_archive_cannot_extract, entry.name, - outputDir - ) + outputDir, + ), ) } } diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/compressed/extractcontents/helpers/AbstractCommonsCompressedFileExtractor.kt b/app/src/main/java/com/amaze/filemanager/filesystem/compressed/extractcontents/helpers/AbstractCommonsCompressedFileExtractor.kt index 52d9a78bab..1157b6e148 100644 --- a/app/src/main/java/com/amaze/filemanager/filesystem/compressed/extractcontents/helpers/AbstractCommonsCompressedFileExtractor.kt +++ b/app/src/main/java/com/amaze/filemanager/filesystem/compressed/extractcontents/helpers/AbstractCommonsCompressedFileExtractor.kt @@ -40,14 +40,14 @@ abstract class AbstractCommonsCompressedFileExtractor( filePath: String, outputPath: String, listener: OnUpdate, - updatePosition: UpdatePosition + updatePosition: UpdatePosition, ) : Extractor(context, filePath, outputPath, listener, updatePosition) { - private val compressorInputStreamConstructor: Constructor init { - compressorInputStreamConstructor = getCompressorInputStreamClass() - .getDeclaredConstructor(InputStream::class.java) + compressorInputStreamConstructor = + getCompressorInputStreamClass() + .getDeclaredConstructor(InputStream::class.java) compressorInputStreamConstructor.isAccessible = true } @@ -84,8 +84,8 @@ abstract class AbstractCommonsCompressedFileExtractor( context.getString( R.string.error_archive_cannot_extract, entryName, - outputPath - ) + outputPath, + ), ) } }.onFailure { diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/compressed/extractcontents/helpers/AbstractCompressedTarArchiveExtractor.kt b/app/src/main/java/com/amaze/filemanager/filesystem/compressed/extractcontents/helpers/AbstractCompressedTarArchiveExtractor.kt index 160869b244..1999ed7d95 100644 --- a/app/src/main/java/com/amaze/filemanager/filesystem/compressed/extractcontents/helpers/AbstractCompressedTarArchiveExtractor.kt +++ b/app/src/main/java/com/amaze/filemanager/filesystem/compressed/extractcontents/helpers/AbstractCompressedTarArchiveExtractor.kt @@ -32,15 +32,15 @@ abstract class AbstractCompressedTarArchiveExtractor( filePath: String, outputPath: String, listener: OnUpdate, - updatePosition: UpdatePosition + updatePosition: UpdatePosition, ) : AbstractCommonsArchiveExtractor(context, filePath, outputPath, listener, updatePosition) { - private val compressorInputStreamConstructor: Constructor init { - compressorInputStreamConstructor = getCompressorInputStreamClass() - .getDeclaredConstructor(InputStream::class.java) + compressorInputStreamConstructor = + getCompressorInputStreamClass() + .getDeclaredConstructor(InputStream::class.java) compressorInputStreamConstructor.isAccessible = true } diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/compressed/extractcontents/helpers/Bzip2Extractor.kt b/app/src/main/java/com/amaze/filemanager/filesystem/compressed/extractcontents/helpers/Bzip2Extractor.kt index f0c8148b2f..38a05d8a56 100644 --- a/app/src/main/java/com/amaze/filemanager/filesystem/compressed/extractcontents/helpers/Bzip2Extractor.kt +++ b/app/src/main/java/com/amaze/filemanager/filesystem/compressed/extractcontents/helpers/Bzip2Extractor.kt @@ -30,14 +30,14 @@ class Bzip2Extractor( filePath: String, outputPath: String, listener: OnUpdate, - updatePosition: UpdatePosition + updatePosition: UpdatePosition, ) : AbstractCommonsCompressedFileExtractor( - context, - filePath, - outputPath, - listener, - updatePosition -) { + context, + filePath, + outputPath, + listener, + updatePosition, + ) { override fun getCompressorInputStreamClass(): Class { return BZip2CompressorInputStream::class.java } diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/compressed/extractcontents/helpers/GzipExtractor.kt b/app/src/main/java/com/amaze/filemanager/filesystem/compressed/extractcontents/helpers/GzipExtractor.kt index 663fff431f..b20a5072c9 100644 --- a/app/src/main/java/com/amaze/filemanager/filesystem/compressed/extractcontents/helpers/GzipExtractor.kt +++ b/app/src/main/java/com/amaze/filemanager/filesystem/compressed/extractcontents/helpers/GzipExtractor.kt @@ -30,14 +30,14 @@ class GzipExtractor( filePath: String, outputPath: String, listener: OnUpdate, - updatePosition: UpdatePosition + updatePosition: UpdatePosition, ) : AbstractCommonsCompressedFileExtractor( - context, - filePath, - outputPath, - listener, - updatePosition -) { + context, + filePath, + outputPath, + listener, + updatePosition, + ) { override fun getCompressorInputStreamClass(): Class { return GzipCompressorInputStream::class.java } diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/compressed/extractcontents/helpers/LzmaExtractor.kt b/app/src/main/java/com/amaze/filemanager/filesystem/compressed/extractcontents/helpers/LzmaExtractor.kt index 79c6f08b0e..fd5021d9da 100644 --- a/app/src/main/java/com/amaze/filemanager/filesystem/compressed/extractcontents/helpers/LzmaExtractor.kt +++ b/app/src/main/java/com/amaze/filemanager/filesystem/compressed/extractcontents/helpers/LzmaExtractor.kt @@ -30,14 +30,14 @@ class LzmaExtractor( filePath: String, outputPath: String, listener: OnUpdate, - updatePosition: UpdatePosition + updatePosition: UpdatePosition, ) : AbstractCommonsCompressedFileExtractor( - context, - filePath, - outputPath, - listener, - updatePosition -) { + context, + filePath, + outputPath, + listener, + updatePosition, + ) { override fun getCompressorInputStreamClass(): Class { return LZMACompressorInputStream::class.java } diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/compressed/extractcontents/helpers/SevenZipExtractor.kt b/app/src/main/java/com/amaze/filemanager/filesystem/compressed/extractcontents/helpers/SevenZipExtractor.kt index c5e6528d97..b39c9db054 100644 --- a/app/src/main/java/com/amaze/filemanager/filesystem/compressed/extractcontents/helpers/SevenZipExtractor.kt +++ b/app/src/main/java/com/amaze/filemanager/filesystem/compressed/extractcontents/helpers/SevenZipExtractor.kt @@ -44,10 +44,9 @@ class SevenZipExtractor( filePath: String, outputPath: String, listener: OnUpdate, - updatePosition: UpdatePosition + updatePosition: UpdatePosition, ) : Extractor(context, filePath, outputPath, listener, updatePosition) { - companion object { @JvmStatic private val LOG: Logger = LoggerFactory.getLogger(SevenZipExtractor::class.java) @@ -56,22 +55,23 @@ class SevenZipExtractor( @Throws(IOException::class) override fun extractWithFilter(filter: Filter) { var totalBytes: Long = 0 - val sevenzFile = runCatching { - if (ArchivePasswordCache.getInstance().containsKey(filePath)) { - SevenZFile( - File(filePath), - ArchivePasswordCache.getInstance()[filePath]!!.toCharArray() - ) - } else { - SevenZFile(File(filePath)) - } - }.getOrElse { - if (it is PasswordRequiredException || it is CorruptedInputException) { - throw it - } else { - throw BadArchiveNotice(it) + val sevenzFile = + runCatching { + if (ArchivePasswordCache.getInstance().containsKey(filePath)) { + SevenZFile( + File(filePath), + ArchivePasswordCache.getInstance()[filePath]!!.toCharArray(), + ) + } else { + SevenZFile(File(filePath)) + } + }.getOrElse { + if (it is PasswordRequiredException || it is CorruptedInputException) { + throw it + } else { + throw BadArchiveNotice(it) + } } - } val arrayList = ArrayList() // iterating archive elements to find file names that are to be extracted @@ -103,7 +103,7 @@ class SevenZipExtractor( context: Context, sevenzFile: SevenZFile, entry: SevenZArchiveEntry, - outputDir: String + outputDir: String, ) { val name = entry.name if (entry.isDirectory) { @@ -121,26 +121,28 @@ class SevenZipExtractor( while (progress < entry.size) { var length: Int val bytesLeft = java.lang.Long.valueOf(entry.size - progress).toInt() - length = sevenzFile.read( - content, - 0, - if (bytesLeft > GenericCopyUtil.DEFAULT_BUFFER_SIZE) { - GenericCopyUtil.DEFAULT_BUFFER_SIZE - } else { - bytesLeft - } - ) + length = + sevenzFile.read( + content, + 0, + if (bytesLeft > GenericCopyUtil.DEFAULT_BUFFER_SIZE) { + GenericCopyUtil.DEFAULT_BUFFER_SIZE + } else { + bytesLeft + }, + ) write(content, 0, length) updatePosition.updatePosition(length.toLong()) progress += length.toLong() } close() - val lastModifiedDate = try { - entry.lastModifiedDate.time - } catch (e: UnsupportedOperationException) { - LOG.warn("Unable to get modified date for 7zip file") - System.currentTimeMillis() - } + val lastModifiedDate = + try { + entry.lastModifiedDate.time + } catch (e: UnsupportedOperationException) { + LOG.warn("Unable to get modified date for 7zip file") + System.currentTimeMillis() + } outputFile.setLastModified(lastModifiedDate) } }?.onFailure { @@ -150,8 +152,8 @@ class SevenZipExtractor( context.getString( R.string.error_archive_cannot_extract, entry.name, - outputDir - ) + outputDir, + ), ) } } diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/compressed/extractcontents/helpers/TarBzip2Extractor.kt b/app/src/main/java/com/amaze/filemanager/filesystem/compressed/extractcontents/helpers/TarBzip2Extractor.kt index 47b148e217..fd44569377 100644 --- a/app/src/main/java/com/amaze/filemanager/filesystem/compressed/extractcontents/helpers/TarBzip2Extractor.kt +++ b/app/src/main/java/com/amaze/filemanager/filesystem/compressed/extractcontents/helpers/TarBzip2Extractor.kt @@ -30,15 +30,13 @@ class TarBzip2Extractor( filePath: String, outputPath: String, listener: OnUpdate, - updatePosition: UpdatePosition + updatePosition: UpdatePosition, ) : AbstractCompressedTarArchiveExtractor( - context, - filePath, - outputPath, - listener, - updatePosition -) { - - override fun getCompressorInputStreamClass(): Class = - BZip2CompressorInputStream::class.java + context, + filePath, + outputPath, + listener, + updatePosition, + ) { + override fun getCompressorInputStreamClass(): Class = BZip2CompressorInputStream::class.java } diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/compressed/extractcontents/helpers/TarExtractor.kt b/app/src/main/java/com/amaze/filemanager/filesystem/compressed/extractcontents/helpers/TarExtractor.kt index 282c6d6273..b803433459 100644 --- a/app/src/main/java/com/amaze/filemanager/filesystem/compressed/extractcontents/helpers/TarExtractor.kt +++ b/app/src/main/java/com/amaze/filemanager/filesystem/compressed/extractcontents/helpers/TarExtractor.kt @@ -30,15 +30,14 @@ class TarExtractor( filePath: String, outputPath: String, listener: OnUpdate, - updatePosition: UpdatePosition + updatePosition: UpdatePosition, ) : AbstractCommonsArchiveExtractor( - context, - filePath, - outputPath, - listener, - updatePosition -) { - + context, + filePath, + outputPath, + listener, + updatePosition, + ) { override fun createFrom(inputStream: InputStream): TarArchiveInputStream = runCatching { TarArchiveInputStream(inputStream) diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/compressed/extractcontents/helpers/TarGzExtractor.kt b/app/src/main/java/com/amaze/filemanager/filesystem/compressed/extractcontents/helpers/TarGzExtractor.kt index b907f8e5d2..3cafd249a6 100644 --- a/app/src/main/java/com/amaze/filemanager/filesystem/compressed/extractcontents/helpers/TarGzExtractor.kt +++ b/app/src/main/java/com/amaze/filemanager/filesystem/compressed/extractcontents/helpers/TarGzExtractor.kt @@ -30,15 +30,13 @@ class TarGzExtractor( filePath: String, outputPath: String, listener: OnUpdate, - updatePosition: UpdatePosition + updatePosition: UpdatePosition, ) : AbstractCompressedTarArchiveExtractor( - context, - filePath, - outputPath, - listener, - updatePosition -) { - - override fun getCompressorInputStreamClass(): Class = - GzipCompressorInputStream::class.java + context, + filePath, + outputPath, + listener, + updatePosition, + ) { + override fun getCompressorInputStreamClass(): Class = GzipCompressorInputStream::class.java } diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/compressed/extractcontents/helpers/TarLzmaExtractor.kt b/app/src/main/java/com/amaze/filemanager/filesystem/compressed/extractcontents/helpers/TarLzmaExtractor.kt index 636a309c2b..553f0fc902 100644 --- a/app/src/main/java/com/amaze/filemanager/filesystem/compressed/extractcontents/helpers/TarLzmaExtractor.kt +++ b/app/src/main/java/com/amaze/filemanager/filesystem/compressed/extractcontents/helpers/TarLzmaExtractor.kt @@ -30,15 +30,13 @@ class TarLzmaExtractor( filePath: String, outputPath: String, listener: OnUpdate, - updatePosition: UpdatePosition + updatePosition: UpdatePosition, ) : AbstractCompressedTarArchiveExtractor( - context, - filePath, - outputPath, - listener, - updatePosition -) { - - override fun getCompressorInputStreamClass(): Class = - LZMACompressorInputStream::class.java + context, + filePath, + outputPath, + listener, + updatePosition, + ) { + override fun getCompressorInputStreamClass(): Class = LZMACompressorInputStream::class.java } diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/compressed/extractcontents/helpers/TarXzExtractor.kt b/app/src/main/java/com/amaze/filemanager/filesystem/compressed/extractcontents/helpers/TarXzExtractor.kt index a0c75212c6..6b701c2160 100644 --- a/app/src/main/java/com/amaze/filemanager/filesystem/compressed/extractcontents/helpers/TarXzExtractor.kt +++ b/app/src/main/java/com/amaze/filemanager/filesystem/compressed/extractcontents/helpers/TarXzExtractor.kt @@ -30,15 +30,13 @@ class TarXzExtractor( filePath: String, outputPath: String, listener: OnUpdate, - updatePosition: UpdatePosition + updatePosition: UpdatePosition, ) : AbstractCompressedTarArchiveExtractor( - context, - filePath, - outputPath, - listener, - updatePosition -) { - - override fun getCompressorInputStreamClass(): Class = - XZCompressorInputStream::class.java + context, + filePath, + outputPath, + listener, + updatePosition, + ) { + override fun getCompressorInputStreamClass(): Class = XZCompressorInputStream::class.java } diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/compressed/extractcontents/helpers/XzExtractor.kt b/app/src/main/java/com/amaze/filemanager/filesystem/compressed/extractcontents/helpers/XzExtractor.kt index 62a8a82f7e..0e219c1eed 100644 --- a/app/src/main/java/com/amaze/filemanager/filesystem/compressed/extractcontents/helpers/XzExtractor.kt +++ b/app/src/main/java/com/amaze/filemanager/filesystem/compressed/extractcontents/helpers/XzExtractor.kt @@ -30,14 +30,14 @@ class XzExtractor( filePath: String, outputPath: String, listener: OnUpdate, - updatePosition: UpdatePosition + updatePosition: UpdatePosition, ) : AbstractCommonsCompressedFileExtractor( - context, - filePath, - outputPath, - listener, - updatePosition -) { + context, + filePath, + outputPath, + listener, + updatePosition, + ) { override fun getCompressorInputStreamClass(): Class { return XZCompressorInputStream::class.java } diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/compressed/extractcontents/helpers/ZipExtractor.kt b/app/src/main/java/com/amaze/filemanager/filesystem/compressed/extractcontents/helpers/ZipExtractor.kt index 8e3535ee40..368bbbf2b3 100644 --- a/app/src/main/java/com/amaze/filemanager/filesystem/compressed/extractcontents/helpers/ZipExtractor.kt +++ b/app/src/main/java/com/amaze/filemanager/filesystem/compressed/extractcontents/helpers/ZipExtractor.kt @@ -39,16 +39,14 @@ import java.io.BufferedInputStream import java.io.BufferedOutputStream import java.io.File import java.io.IOException -import java.util.* class ZipExtractor( context: Context, filePath: String, outputPath: String, listener: OnUpdate, - updatePosition: UpdatePosition + updatePosition: UpdatePosition, ) : Extractor(context, filePath, outputPath, listener, updatePosition) { - private val isRobolectricTest = Build.HARDWARE == "robolectric" @Throws(IOException::class) @@ -109,7 +107,7 @@ class ZipExtractor( context: Context, zipFile: ZipFile, entry: FileHeader, - outputDir: String + outputDir: String, ) { val outputFile = File(outputDir, fixEntryName(entry.fileName)) if (!outputFile.canonicalPath.startsWith(outputDir) && @@ -135,7 +133,9 @@ class ZipExtractor( if (!listener.isCancelled) { write(buf, 0, len) updatePosition.updatePosition(len.toLong()) - } else break + } else { + break + } } close() outputFile.setLastModified(entry.lastModifiedTimeEpoch) @@ -145,8 +145,8 @@ class ZipExtractor( context.getString( R.string.error_archive_cannot_extract, entry.fileName, - outputDir - ) + outputDir, + ), ) } } diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/compressed/showcontents/Decompressor.kt b/app/src/main/java/com/amaze/filemanager/filesystem/compressed/showcontents/Decompressor.kt index 86824b93ac..65f9fa0464 100644 --- a/app/src/main/java/com/amaze/filemanager/filesystem/compressed/showcontents/Decompressor.kt +++ b/app/src/main/java/com/amaze/filemanager/filesystem/compressed/showcontents/Decompressor.kt @@ -29,7 +29,6 @@ import com.amaze.filemanager.asynchronous.services.ExtractService /** @author Emmanuel on 20/11/2017, at 17:14. */ abstract class Decompressor(protected var context: Context) { - lateinit var filePath: String /** @@ -39,16 +38,17 @@ abstract class Decompressor(protected var context: Context) { */ abstract fun changePath( path: String, - addGoBackItem: Boolean + addGoBackItem: Boolean, ): CompressedHelperCallable /** Decompress a file somewhere */ fun decompress(whereToDecompress: String) { - val intent = Intent(context, ExtractService::class.java).also { - it.putExtra(ExtractService.KEY_PATH_ZIP, filePath) - it.putExtra(ExtractService.KEY_ENTRIES_ZIP, arrayOfNulls(0)) - it.putExtra(ExtractService.KEY_PATH_EXTRACT, whereToDecompress) - } + val intent = + Intent(context, ExtractService::class.java).also { + it.putExtra(ExtractService.KEY_PATH_ZIP, filePath) + it.putExtra(ExtractService.KEY_ENTRIES_ZIP, arrayOfNulls(0)) + it.putExtra(ExtractService.KEY_PATH_EXTRACT, whereToDecompress) + } ServiceWatcherUtil.runService(context, intent) } @@ -58,15 +58,19 @@ abstract class Decompressor(protected var context: Context) { * @param subDirectories separator is "/", ended with "/" if it is a directory, does not if it's a * file */ - fun decompress(whereToDecompress: String, subDirectories: Array) { + fun decompress( + whereToDecompress: String, + subDirectories: Array, + ) { subDirectories.filterNotNull().map { realRelativeDirectory(it) }.run { - val intent = Intent(context, ExtractService::class.java).also { - it.putExtra(ExtractService.KEY_PATH_ZIP, filePath) - it.putExtra(ExtractService.KEY_ENTRIES_ZIP, subDirectories) - it.putExtra(ExtractService.KEY_PATH_EXTRACT, whereToDecompress) - } + val intent = + Intent(context, ExtractService::class.java).also { + it.putExtra(ExtractService.KEY_PATH_ZIP, filePath) + it.putExtra(ExtractService.KEY_ENTRIES_ZIP, subDirectories) + it.putExtra(ExtractService.KEY_PATH_EXTRACT, whereToDecompress) + } ServiceWatcherUtil.runService(context, intent) } } diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/compressed/showcontents/helpers/SevenZipDecompressor.kt b/app/src/main/java/com/amaze/filemanager/filesystem/compressed/showcontents/helpers/SevenZipDecompressor.kt index 0da0d7dd1a..7c8aedd351 100644 --- a/app/src/main/java/com/amaze/filemanager/filesystem/compressed/showcontents/helpers/SevenZipDecompressor.kt +++ b/app/src/main/java/com/amaze/filemanager/filesystem/compressed/showcontents/helpers/SevenZipDecompressor.kt @@ -27,7 +27,6 @@ import com.amaze.filemanager.filesystem.compressed.showcontents.Decompressor class SevenZipDecompressor(context: Context) : Decompressor(context) { override fun changePath( path: String, - addGoBackItem: Boolean - ) = - SevenZipHelperCallable(filePath, path, addGoBackItem) + addGoBackItem: Boolean, + ) = SevenZipHelperCallable(filePath, path, addGoBackItem) } diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/compressed/showcontents/helpers/TarBzip2Decompressor.kt b/app/src/main/java/com/amaze/filemanager/filesystem/compressed/showcontents/helpers/TarBzip2Decompressor.kt index 340af358ce..9a63002f58 100644 --- a/app/src/main/java/com/amaze/filemanager/filesystem/compressed/showcontents/helpers/TarBzip2Decompressor.kt +++ b/app/src/main/java/com/amaze/filemanager/filesystem/compressed/showcontents/helpers/TarBzip2Decompressor.kt @@ -27,7 +27,6 @@ import com.amaze.filemanager.filesystem.compressed.showcontents.Decompressor class TarBzip2Decompressor(context: Context) : Decompressor(context) { override fun changePath( path: String, - addGoBackItem: Boolean - ) = - TarBzip2HelperCallable(context, filePath, path, addGoBackItem) + addGoBackItem: Boolean, + ) = TarBzip2HelperCallable(context, filePath, path, addGoBackItem) } diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/compressed/showcontents/helpers/TarDecompressor.kt b/app/src/main/java/com/amaze/filemanager/filesystem/compressed/showcontents/helpers/TarDecompressor.kt index ad39d28a01..6de4cf5c38 100644 --- a/app/src/main/java/com/amaze/filemanager/filesystem/compressed/showcontents/helpers/TarDecompressor.kt +++ b/app/src/main/java/com/amaze/filemanager/filesystem/compressed/showcontents/helpers/TarDecompressor.kt @@ -27,7 +27,6 @@ import com.amaze.filemanager.filesystem.compressed.showcontents.Decompressor class TarDecompressor(context: Context) : Decompressor(context) { override fun changePath( path: String, - addGoBackItem: Boolean - ) = - TarHelperCallable(context, filePath, path, addGoBackItem) + addGoBackItem: Boolean, + ) = TarHelperCallable(context, filePath, path, addGoBackItem) } diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/compressed/showcontents/helpers/TarGzDecompressor.kt b/app/src/main/java/com/amaze/filemanager/filesystem/compressed/showcontents/helpers/TarGzDecompressor.kt index 03e0d8026e..870f26ab94 100644 --- a/app/src/main/java/com/amaze/filemanager/filesystem/compressed/showcontents/helpers/TarGzDecompressor.kt +++ b/app/src/main/java/com/amaze/filemanager/filesystem/compressed/showcontents/helpers/TarGzDecompressor.kt @@ -27,7 +27,6 @@ import com.amaze.filemanager.filesystem.compressed.showcontents.Decompressor class TarGzDecompressor(context: Context) : Decompressor(context) { override fun changePath( path: String, - addGoBackItem: Boolean - ) = - TarGzHelperCallable(context, filePath, path, addGoBackItem) + addGoBackItem: Boolean, + ) = TarGzHelperCallable(context, filePath, path, addGoBackItem) } diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/compressed/showcontents/helpers/TarLzmaDecompressor.kt b/app/src/main/java/com/amaze/filemanager/filesystem/compressed/showcontents/helpers/TarLzmaDecompressor.kt index a3697ba23b..4151224556 100644 --- a/app/src/main/java/com/amaze/filemanager/filesystem/compressed/showcontents/helpers/TarLzmaDecompressor.kt +++ b/app/src/main/java/com/amaze/filemanager/filesystem/compressed/showcontents/helpers/TarLzmaDecompressor.kt @@ -27,7 +27,6 @@ import com.amaze.filemanager.filesystem.compressed.showcontents.Decompressor class TarLzmaDecompressor(context: Context) : Decompressor(context) { override fun changePath( path: String, - addGoBackItem: Boolean - ) = - TarLzmaHelperCallable(context, filePath, path, addGoBackItem) + addGoBackItem: Boolean, + ) = TarLzmaHelperCallable(context, filePath, path, addGoBackItem) } diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/compressed/showcontents/helpers/TarXzDecompressor.kt b/app/src/main/java/com/amaze/filemanager/filesystem/compressed/showcontents/helpers/TarXzDecompressor.kt index adfbdd293b..ef89a5eca9 100644 --- a/app/src/main/java/com/amaze/filemanager/filesystem/compressed/showcontents/helpers/TarXzDecompressor.kt +++ b/app/src/main/java/com/amaze/filemanager/filesystem/compressed/showcontents/helpers/TarXzDecompressor.kt @@ -27,7 +27,6 @@ import com.amaze.filemanager.filesystem.compressed.showcontents.Decompressor class TarXzDecompressor(context: Context) : Decompressor(context) { override fun changePath( path: String, - addGoBackItem: Boolean - ) = - TarXzHelperCallable(context, filePath, path, addGoBackItem) + addGoBackItem: Boolean, + ) = TarXzHelperCallable(context, filePath, path, addGoBackItem) } diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/compressed/showcontents/helpers/UnknownCompressedFileDecompressor.kt b/app/src/main/java/com/amaze/filemanager/filesystem/compressed/showcontents/helpers/UnknownCompressedFileDecompressor.kt index 9f7120335e..a72ae8848e 100644 --- a/app/src/main/java/com/amaze/filemanager/filesystem/compressed/showcontents/helpers/UnknownCompressedFileDecompressor.kt +++ b/app/src/main/java/com/amaze/filemanager/filesystem/compressed/showcontents/helpers/UnknownCompressedFileDecompressor.kt @@ -30,7 +30,6 @@ import com.amaze.filemanager.filesystem.compressed.showcontents.Decompressor class UnknownCompressedFileDecompressor(context: Context) : Decompressor(context) { override fun changePath( path: String, - addGoBackItem: Boolean - ) = - UnknownCompressedFileHelperCallable(filePath, addGoBackItem) + addGoBackItem: Boolean, + ) = UnknownCompressedFileHelperCallable(filePath, addGoBackItem) } diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/compressed/showcontents/helpers/ZipDecompressor.kt b/app/src/main/java/com/amaze/filemanager/filesystem/compressed/showcontents/helpers/ZipDecompressor.kt index ab16ccd16f..f9288c24e7 100644 --- a/app/src/main/java/com/amaze/filemanager/filesystem/compressed/showcontents/helpers/ZipDecompressor.kt +++ b/app/src/main/java/com/amaze/filemanager/filesystem/compressed/showcontents/helpers/ZipDecompressor.kt @@ -27,7 +27,6 @@ import com.amaze.filemanager.filesystem.compressed.showcontents.Decompressor class ZipDecompressor(context: Context) : Decompressor(context) { override fun changePath( path: String, - addGoBackItem: Boolean - ) = - ZipHelperCallable(context, filePath, path, addGoBackItem) + addGoBackItem: Boolean, + ) = ZipHelperCallable(context, filePath, path, addGoBackItem) } diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/files/CryptUtil.java b/app/src/main/java/com/amaze/filemanager/filesystem/files/CryptUtil.java index 859ed4375e..b6ea1a7bbe 100644 --- a/app/src/main/java/com/amaze/filemanager/filesystem/files/CryptUtil.java +++ b/app/src/main/java/com/amaze/filemanager/filesystem/files/CryptUtil.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/files/EncryptDecryptUtils.java b/app/src/main/java/com/amaze/filemanager/filesystem/files/EncryptDecryptUtils.java index b2418779c6..5355727bf1 100644 --- a/app/src/main/java/com/amaze/filemanager/filesystem/files/EncryptDecryptUtils.java +++ b/app/src/main/java/com/amaze/filemanager/filesystem/files/EncryptDecryptUtils.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. @@ -50,10 +50,10 @@ import android.content.SharedPreferences; import android.os.Build; import android.util.Base64; -import android.widget.EditText; import android.widget.Toast; import androidx.annotation.NonNull; +import androidx.appcompat.widget.AppCompatEditText; import androidx.preference.PreferenceManager; /** @@ -65,6 +65,7 @@ public class EncryptDecryptUtils { public static final String DECRYPT_BROADCAST = "decrypt_broadcast"; private static final Logger LOG = LoggerFactory.getLogger(EncryptDecryptUtils.class); + /** * Queries database to map path and password. Starts the encryption process after database query * @@ -114,7 +115,7 @@ public static void decryptFile( R.string.crypt_decrypt, R.string.authenticate_password, (dialog, which) -> { - EditText editText = dialog.getView().findViewById(R.id.singleedittext_input); + AppCompatEditText editText = dialog.getView().findViewById(R.id.singleedittext_input); decryptIntent.putExtra(EncryptService.TAG_PASSWORD, editText.getText().toString()); ServiceWatcherUtil.runService(main.getContext(), decryptIntent); dialog.dismiss(); diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/files/FileListSorter.java b/app/src/main/java/com/amaze/filemanager/filesystem/files/FileListSorter.java deleted file mode 100644 index e97e83b59a..0000000000 --- a/app/src/main/java/com/amaze/filemanager/filesystem/files/FileListSorter.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , - * Emmanuel Messulam, Raymond Lai and Contributors. - * - * This file is part of Amaze File Manager. - * - * Amaze File Manager is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.amaze.filemanager.filesystem.files; - -import java.util.Comparator; - -import com.amaze.filemanager.adapters.data.LayoutElementParcelable; - -public class FileListSorter implements Comparator { - - private int dirsOnTop = 0; - private int asc = 1; - private int sort = 0; - - public FileListSorter(int dir, int sort, int asc) { - this.dirsOnTop = dir; - this.asc = asc; - this.sort = sort; - } - - private boolean isDirectory(LayoutElementParcelable path) { - return path.isDirectory; - } - - /** - * Compares two elements and return negative, zero and positive integer if first argument is less - * than, equal to or greater than second - */ - @Override - public int compare(LayoutElementParcelable file1, LayoutElementParcelable file2) { - - /*File f1; - - if(!file1.hasSymlink()) { - - f1=new File(file1.getDesc()); - } else { - f1=new File(file1.getSymlink()); - } - - File f2; - - if(!file2.hasSymlink()) { - - f2=new File(file2.getDesc()); - } else { - f2=new File(file1.getSymlink()); - }*/ - - if (dirsOnTop == 0) { - if (isDirectory(file1) && !isDirectory(file2)) { - return -1; - - } else if (isDirectory(file2) && !isDirectory(file1)) { - return 1; - } - } else if (dirsOnTop == 1) { - if (isDirectory(file1) && !isDirectory(file2)) { - - return 1; - } else if (isDirectory(file2) && !isDirectory(file1)) { - return -1; - } - } - - if (sort == 0) { - - // sort by name - return asc * file1.title.compareToIgnoreCase(file2.title); - } else if (sort == 1) { - - // sort by last modified - return asc * Long.valueOf(file1.date).compareTo(file2.date); - } else if (sort == 2) { - - // sort by size - if (!file1.isDirectory && !file2.isDirectory) { - - return asc * Long.valueOf(file1.longSize).compareTo(file2.longSize); - } else { - - return file1.title.compareToIgnoreCase(file2.title); - } - - } else if (sort == 3) { - - // sort by type - if (!file1.isDirectory && !file2.isDirectory) { - - final String ext_a = getExtension(file1.title); - final String ext_b = getExtension(file2.title); - - final int res = asc * ext_a.compareTo(ext_b); - if (res == 0) { - return asc * file1.title.compareToIgnoreCase(file2.title); - } - return res; - } else { - return file1.title.compareToIgnoreCase(file2.title); - } - } - return 0; - } - - private static String getExtension(String a) { - return a.substring(a.lastIndexOf(".") + 1).toLowerCase(); - } -} diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/files/FileListSorter.kt b/app/src/main/java/com/amaze/filemanager/filesystem/files/FileListSorter.kt new file mode 100644 index 0000000000..0398be0ca8 --- /dev/null +++ b/app/src/main/java/com/amaze/filemanager/filesystem/files/FileListSorter.kt @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Emmanuel Messulam, Raymond Lai and Contributors. + * + * This file is part of Amaze File Manager. + * + * Amaze File Manager is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.amaze.filemanager.filesystem.files + +import com.amaze.filemanager.adapters.data.LayoutElementParcelable +import com.amaze.filemanager.filesystem.files.sort.ComparableParcelable +import com.amaze.filemanager.filesystem.files.sort.DirSortBy +import com.amaze.filemanager.filesystem.files.sort.SortBy +import com.amaze.filemanager.filesystem.files.sort.SortType +import java.lang.Long +import java.util.Locale +import kotlin.Boolean +import kotlin.Comparator +import kotlin.Int +import kotlin.String + +/** + * [Comparator] implementation to sort [LayoutElementParcelable]s. + */ +class FileListSorter( + dirArg: DirSortBy, + sortType: SortType, +) : Comparator { + private var dirsOnTop = dirArg + private val asc: Int = sortType.sortOrder.sortFactor + private val sort: SortBy = sortType.sortBy + + private fun isDirectory(path: ComparableParcelable): Boolean { + return path.isDirectory() + } + + /** Compares the names of [file1] and [file2] */ + private fun compareName( + file1: ComparableParcelable, + file2: ComparableParcelable, + ): Int { + return file1.getParcelableName().compareTo(file2.getParcelableName(), ignoreCase = true) + } + + /** + * Compares two elements and return negative, zero and positive integer if first argument is less + * than, equal to or greater than second + */ + override fun compare( + file1: ComparableParcelable, + file2: ComparableParcelable, + ): Int { + /*File f1; + + if(!file1.hasSymlink()) { + + f1=new File(file1.getDesc()); + } else { + f1=new File(file1.getSymlink()); + } + + File f2; + + if(!file2.hasSymlink()) { + + f2=new File(file2.getDesc()); + } else { + f2=new File(file1.getSymlink()); + }*/ + if (dirsOnTop == DirSortBy.DIR_ON_TOP) { + if (isDirectory(file1) && !isDirectory(file2)) { + return -1 + } else if (isDirectory(file2) && !isDirectory(file1)) { + return 1 + } + } else if (dirsOnTop == DirSortBy.FILE_ON_TOP) { + if (isDirectory(file1) && !isDirectory(file2)) { + return 1 + } else if (isDirectory(file2) && !isDirectory(file1)) { + return -1 + } + } + + when (sort) { + SortBy.NAME -> { + // sort by name + return asc * compareName(file1, file2) + } + SortBy.LAST_MODIFIED -> { + // sort by last modified + return asc * Long.valueOf(file1.getDate()).compareTo(file2.getDate()) + } + SortBy.SIZE -> { + // sort by size + return if (!isDirectory(file1) && !isDirectory(file2)) { + asc * Long.valueOf(file1.getSize()).compareTo(file2.getSize()) + } else { + compareName(file1, file2) + } + } + SortBy.TYPE -> { + // sort by type + return if (!isDirectory(file1) && !isDirectory(file2)) { + val ext_a = getExtension(file1.getParcelableName()) + val ext_b = getExtension(file2.getParcelableName()) + val res = asc * ext_a.compareTo(ext_b) + if (res == 0) { + asc * compareName(file1, file2) + } else { + res + } + } else { + compareName(file1, file2) + } + } + SortBy.RELEVANCE -> { + // This case should not be called because it is not defined + return 0 + } + } + } + + companion object { + /** + * Convenience method to get the file extension in given path. + * + * TODO: merge with same definition somewhere else (if any) + */ + @JvmStatic + fun getExtension(a: String): String { + return a.substringAfterLast('.').lowercase(Locale.getDefault()) + } + } +} diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/files/FileUtils.java b/app/src/main/java/com/amaze/filemanager/filesystem/files/FileUtils.java index d55e7f09b3..d9bdd010a7 100644 --- a/app/src/main/java/com/amaze/filemanager/filesystem/files/FileUtils.java +++ b/app/src/main/java/com/amaze/filemanager/filesystem/files/FileUtils.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. @@ -30,7 +30,7 @@ import java.util.Date; import java.util.LinkedList; import java.util.List; -import java.util.concurrent.Callable; +import java.util.Locale; import java.util.concurrent.atomic.AtomicLong; import org.slf4j.Logger; @@ -42,7 +42,6 @@ import com.amaze.filemanager.application.AppConfig; import com.amaze.filemanager.fileoperations.filesystem.OpenMode; import com.amaze.filemanager.fileoperations.filesystem.smbstreamer.Streamer; -import com.amaze.filemanager.filesystem.ExternalSdCardOperation; import com.amaze.filemanager.filesystem.HybridFile; import com.amaze.filemanager.filesystem.HybridFileParcelable; import com.amaze.filemanager.filesystem.Operations; @@ -71,7 +70,6 @@ import android.Manifest; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; -import android.annotation.SuppressLint; import android.app.Activity; import android.content.ActivityNotFoundException; import android.content.Context; @@ -79,7 +77,6 @@ import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; -import android.media.MediaScannerConnection; import android.net.Uri; import android.os.AsyncTask; import android.os.Build; @@ -92,8 +89,6 @@ import androidx.core.util.Pair; import androidx.documentfile.provider.DocumentFile; -import io.reactivex.Flowable; -import io.reactivex.schedulers.Schedulers; import jcifs.smb.SmbFile; import kotlin.collections.ArraysKt; import net.schmizz.sshj.sftp.RemoteResourceInfo; @@ -217,76 +212,6 @@ public static long getBaseFileSize(HybridFileParcelable baseFile, Context contex } } - /** - * Triggers media scanner for multiple paths. The paths must all belong to same filesystem. It's - * upto the caller to call the mediastore scan on multiple files or only one source/target - * directory. Don't use filesystem API directly as files might not be present anymore (eg. - * move/rename) which may lead to {@link java.io.FileNotFoundException} - * - * @param hybridFiles - * @param context - */ - @SuppressLint("CheckResult") - public static void scanFile(@NonNull Context context, @NonNull HybridFile[] hybridFiles) { - Flowable.fromCallable( - (Callable) - () -> { - if (hybridFiles[0].exists(context) && hybridFiles[0].isLocal()) { - String[] paths = new String[hybridFiles.length]; - for (int i = 0; i < hybridFiles.length; i++) { - HybridFile hybridFile = hybridFiles[i]; - paths[i] = hybridFile.getPath(); - } - MediaScannerConnection.scanFile(context, paths, null, null); - } - for (HybridFile hybridFile : hybridFiles) { - scanFile(hybridFile, context); - } - return null; - }) - .subscribeOn(Schedulers.io()); - } - - /** - * Triggers media store for the file path - * - * @param hybridFile the file which was changed (directory not supported) - * @param context given context - */ - private static void scanFile(@NonNull HybridFile hybridFile, Context context) { - - if ((hybridFile.isLocal() || hybridFile.isOtgFile()) && hybridFile.exists(context)) { - - Uri uri = null; - if (Build.VERSION.SDK_INT >= 19) { - DocumentFile documentFile = - ExternalSdCardOperation.getDocumentFile( - hybridFile.getFile(), hybridFile.isDirectory(context), context); - // If FileUtil.getDocumentFile() returns null, fall back to DocumentFile.fromFile() - if (documentFile == null) documentFile = DocumentFile.fromFile(hybridFile.getFile()); - uri = documentFile.getUri(); - } else { - if (hybridFile.isLocal()) { - uri = Uri.fromFile(hybridFile.getFile()); - } - } - if (uri != null) { - FileUtils.scanFile(uri, context); - } - } - } - - /** - * Triggers {@link Intent#ACTION_MEDIA_SCANNER_SCAN_FILE} intent to refresh the media store. - * - * @param uri File's {@link Uri} - * @param c {@link Context} - */ - private static void scanFile(@NonNull Uri uri, @NonNull Context c) { - Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, uri); - c.sendBroadcast(mediaScanIntent); - } - public static void crossfade(View buttons, final View pathbar) { // Set the content view to 0% opacity but visible, so that it is visible // (but fully transparent) during the animation. @@ -749,7 +674,7 @@ public static void openFile( mainActivity.startActivity(intent); } else { try { - openFileDialogFragmentFor(f, mainActivity); + openFileDialogFragmentFor(f, mainActivity, useNewStack); } catch (Exception e) { Toast.makeText( mainActivity, mainActivity.getString(R.string.no_app_found), Toast.LENGTH_LONG) @@ -760,30 +685,42 @@ public static void openFile( } private static void openFileDialogFragmentFor( - @NonNull File file, @NonNull MainActivity mainActivity) { + @NonNull File file, @NonNull MainActivity mainActivity, @NonNull Boolean useNewStack) { openFileDialogFragmentFor( - file, mainActivity, MimeTypes.getMimeType(file.getAbsolutePath(), false)); + file, mainActivity, MimeTypes.getMimeType(file.getAbsolutePath(), false), useNewStack); } private static void openFileDialogFragmentFor( - @NonNull File file, @NonNull MainActivity mainActivity, @NonNull String mimeType) { + @NonNull File file, + @NonNull MainActivity mainActivity, + @NonNull String mimeType, + @NonNull Boolean useNewStack) { OpenFileDialogFragment.Companion.openFileOrShow( FileProvider.getUriForFile(mainActivity, mainActivity.getPackageName(), file), mimeType, - false, + useNewStack, mainActivity, false); } private static void openFileDialogFragmentFor( - @NonNull DocumentFile file, @NonNull MainActivity mainActivity) { + @NonNull DocumentFile file, + @NonNull MainActivity mainActivity, + @NonNull Boolean useNewStack) { openFileDialogFragmentFor( - file.getUri(), mainActivity, MimeTypes.getMimeType(file.getUri().toString(), false)); + file.getUri(), + mainActivity, + MimeTypes.getMimeType(file.getUri().toString(), false), + useNewStack); } private static void openFileDialogFragmentFor( - @NonNull Uri uri, @NonNull MainActivity mainActivity, @NonNull String mimeType) { - OpenFileDialogFragment.Companion.openFileOrShow(uri, mimeType, false, mainActivity, false); + @NonNull Uri uri, + @NonNull MainActivity mainActivity, + @NonNull String mimeType, + @NonNull Boolean useNewStack) { + OpenFileDialogFragment.Companion.openFileOrShow( + uri, mimeType, useNewStack, mainActivity, false); } private static boolean isSelfDefault(File f, Context c) { @@ -804,7 +741,7 @@ public static void openFile( boolean useNewStack = sharedPrefs.getBoolean(PreferencesConstants.PREFERENCE_TEXTEDITOR_NEWSTACK, false); try { - openFileDialogFragmentFor(f, m); + openFileDialogFragmentFor(f, m, useNewStack); } catch (Exception e) { Toast.makeText(m, m.getString(R.string.no_app_found), Toast.LENGTH_LONG).show(); openWith(f, m, useNewStack); @@ -929,10 +866,19 @@ public static HybridFileParcelable parseName(String line, boolean isStat) { } link = new StringBuilder(link.toString().trim()); } - long Size = (size == null || size.trim().length() == 0) ? -1 : Long.parseLong(size); + long Size; + if (size == null || size.trim().length() == 0) { + Size = -1; + } else { + try { + Size = Long.parseLong(size); + } catch (NumberFormatException ifItIsNotANumber) { + Size = -1; + } + } if (date.trim().length() > 0 && !isStat) { ParsePosition pos = new ParsePosition(0); - SimpleDateFormat simpledateformat = new SimpleDateFormat("yyyy-MM-dd | HH:mm"); + SimpleDateFormat simpledateformat = new SimpleDateFormat("yyyy-MM-dd | HH:mm", Locale.US); Date stringDate = simpledateformat.parse(date, pos); if (stringDate == null) { LOG.warn("parseName: unable to parse datetime string [" + date + "]"); diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/files/GenericCopyUtil.java b/app/src/main/java/com/amaze/filemanager/filesystem/files/GenericCopyUtil.java index 068d6a95fe..75dc8ab34f 100644 --- a/app/src/main/java/com/amaze/filemanager/filesystem/files/GenericCopyUtil.java +++ b/app/src/main/java/com/amaze/filemanager/filesystem/files/GenericCopyUtil.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. @@ -72,8 +72,6 @@ public class GenericCopyUtil { private final DataUtils dataUtils = DataUtils.getInstance(); private final ProgressHandler progressHandler; - public static final String PATH_FILE_DESCRIPTOR = "/proc/self/fd/"; - public static final int DEFAULT_BUFFER_SIZE = 8192; /* @@ -82,7 +80,7 @@ public class GenericCopyUtil { Cannot modify DEFAULT_BUFFER_SIZE since it's used by other classes, will have undesired effect on other functions */ - private static final int DEFAULT_TRANSFER_QUANTUM = 65536; + private static final int DEFAULT_TRANSFER_QUANTUM = 1024 * 1024; public GenericCopyUtil(Context context, ProgressHandler progressHandler) { this.mContext = context; @@ -243,10 +241,10 @@ private void startCopy( doCopy(inChannel, outChannel, updatePosition); } catch (IOException e) { - LOG.debug("I/O Error!", e); - throw new IOException(); + LOG.error("I/O Error copy {} to {}: {}", mSourceFile, mTargetFile, e); + throw new IOException(e); } catch (OutOfMemoryError e) { - LOG.warn("low memory while copying file", e); + LOG.warn("low memory while copying {} to {}: {}", mSourceFile, mTargetFile, e); onLowMemory.onLowMemory(); @@ -271,7 +269,7 @@ private void startCopy( // If target file is copied onto the device and copy was successful, trigger media store // rescan if (mTargetFile != null) { - FileUtils.scanFile(mContext, new HybridFile[] {mTargetFile}); + MediaConnectionUtils.scanFile(mContext, new HybridFile[] {mTargetFile}); } } } diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/files/MediaConnectionUtils.kt b/app/src/main/java/com/amaze/filemanager/filesystem/files/MediaConnectionUtils.kt new file mode 100644 index 0000000000..e51370578c --- /dev/null +++ b/app/src/main/java/com/amaze/filemanager/filesystem/files/MediaConnectionUtils.kt @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2014-2022 Arpit Khurana , Vishal Nehra , + * Emmanuel Messulam, Raymond Lai and Contributors. + * + * This file is part of Amaze File Manager. + * + * Amaze File Manager is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.amaze.filemanager.filesystem.files + +import android.content.Context +import android.media.MediaScannerConnection +import android.net.Uri +import com.amaze.filemanager.filesystem.HybridFile +import org.slf4j.LoggerFactory + +object MediaConnectionUtils { + private val LOG = LoggerFactory.getLogger(MediaConnectionUtils::class.java) + + /** + * Invokes MediaScannerConnection#scanFile for the given files + * + * @param context the context + * @param hybridFiles files to be scanned + */ + @JvmStatic + fun scanFile( + context: Context, + hybridFiles: Array, + ) { + val paths = arrayOfNulls(hybridFiles.size) + + for (i in hybridFiles.indices) paths[i] = hybridFiles[i].path + + MediaScannerConnection.scanFile( + context, + paths, + null, + ) { path: String, _: Uri? -> + LOG.info("MediaConnectionUtils#scanFile finished scanning path$path") + } + } +} diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/files/sort/ComparableParcelable.kt b/app/src/main/java/com/amaze/filemanager/filesystem/files/sort/ComparableParcelable.kt new file mode 100644 index 0000000000..c0034cc906 --- /dev/null +++ b/app/src/main/java/com/amaze/filemanager/filesystem/files/sort/ComparableParcelable.kt @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2014-2023 Arpit Khurana , Vishal Nehra , + * Emmanuel Messulam, Raymond Lai and Contributors. + * + * This file is part of Amaze File Manager. + * + * Amaze File Manager is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.amaze.filemanager.filesystem.files.sort + +/** Used by [FileListSorter] to get the needed information from a `Parcelable` */ +interface ComparableParcelable { + /** Returns if the parcelable represents a directory */ + fun isDirectory(): Boolean + + /** Returns the name of the item represented by the parcelable */ + fun getParcelableName(): String + + /** Returns the date of the item represented by the parcelable as a Long */ + fun getDate(): Long + + /** Returns the size of the item represented by the parcelable */ + fun getSize(): Long +} diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/files/sort/DirSortBy.kt b/app/src/main/java/com/amaze/filemanager/filesystem/files/sort/DirSortBy.kt new file mode 100644 index 0000000000..00ffd5bb43 --- /dev/null +++ b/app/src/main/java/com/amaze/filemanager/filesystem/files/sort/DirSortBy.kt @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2014-2023 Arpit Khurana , Vishal Nehra , + * Emmanuel Messulam, Raymond Lai and Contributors. + * + * This file is part of Amaze File Manager. + * + * Amaze File Manager is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.amaze.filemanager.filesystem.files.sort + +/** Represents the way in which directories and files should be sorted */ +enum class DirSortBy { + DIR_ON_TOP, + FILE_ON_TOP, + NONE_ON_TOP, + ; + + companion object { + /** Returns the corresponding [DirSortBy] to [index] */ + @JvmStatic + fun getDirSortBy(index: Int): DirSortBy { + return when (index) { + 0 -> DIR_ON_TOP + 1 -> FILE_ON_TOP + 2 -> NONE_ON_TOP + else -> NONE_ON_TOP + } + } + } +} diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/files/sort/SortBy.kt b/app/src/main/java/com/amaze/filemanager/filesystem/files/sort/SortBy.kt new file mode 100644 index 0000000000..c72836dade --- /dev/null +++ b/app/src/main/java/com/amaze/filemanager/filesystem/files/sort/SortBy.kt @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2014-2023 Arpit Khurana , Vishal Nehra , + * Emmanuel Messulam, Raymond Lai and Contributors. + * + * This file is part of Amaze File Manager. + * + * Amaze File Manager is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.amaze.filemanager.filesystem.files.sort + +import android.content.Context +import com.amaze.filemanager.R + +/** + * Represents the sort by types. + * [index] is the index of the sort in the xml string array resource + * [sortDirectory] indicates if the sort can be used to sort an directory. + */ +enum class SortBy(val index: Int, val sortDirectory: Boolean) { + NAME(0, true), + LAST_MODIFIED(1, true), + SIZE(2, true), + TYPE(3, true), + RELEVANCE(4, false), + ; + + /** Returns the corresponding string resource of the enum */ + fun toResourceString(context: Context): String { + return when (this) { + NAME -> context.resources.getString(R.string.sort_name) + LAST_MODIFIED -> context.resources.getString(R.string.lastModified) + SIZE -> context.resources.getString(R.string.sort_size) + TYPE -> context.resources.getString(R.string.type) + RELEVANCE -> context.resources.getString(R.string.sort_relevance) + } + } + + companion object { + const val NAME_INDEX = 0 + const val LAST_MODIFIED_INDEX = 1 + const val SIZE_INDEX = 2 + const val TYPE_INDEX = 3 + const val RELEVANCE_INDEX = 4 + + /** Returns the SortBy corresponding to [index] which can be used to sort directories */ + @JvmStatic + fun getDirectorySortBy(index: Int): SortBy { + return when (index) { + NAME_INDEX -> NAME + LAST_MODIFIED_INDEX -> LAST_MODIFIED + SIZE_INDEX -> SIZE + TYPE_INDEX -> TYPE + else -> NAME + } + } + + /** Returns the SortBy corresponding to [index] */ + @JvmStatic + fun getSortBy(index: Int): SortBy { + return when (index) { + NAME_INDEX -> NAME + LAST_MODIFIED_INDEX -> LAST_MODIFIED + SIZE_INDEX -> SIZE + TYPE_INDEX -> TYPE + RELEVANCE_INDEX -> RELEVANCE + else -> NAME + } + } + } +} diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/files/sort/SortOrder.kt b/app/src/main/java/com/amaze/filemanager/filesystem/files/sort/SortOrder.kt new file mode 100644 index 0000000000..17faafdb28 --- /dev/null +++ b/app/src/main/java/com/amaze/filemanager/filesystem/files/sort/SortOrder.kt @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2014-2023 Arpit Khurana , Vishal Nehra , + * Emmanuel Messulam, Raymond Lai and Contributors. + * + * This file is part of Amaze File Manager. + * + * Amaze File Manager is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.amaze.filemanager.filesystem.files.sort + +/** + * Represents the direction the sort should be ordered + * + * [sortFactor] is the factor that should be multiplied to the result of `compareTo()` to achieve the correct sort direction + */ +enum class SortOrder(val sortFactor: Int) { + ASC(1), + DESC(-1), +} diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/files/sort/SortType.kt b/app/src/main/java/com/amaze/filemanager/filesystem/files/sort/SortType.kt new file mode 100644 index 0000000000..9ac6862dab --- /dev/null +++ b/app/src/main/java/com/amaze/filemanager/filesystem/files/sort/SortType.kt @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2014-2023 Arpit Khurana , Vishal Nehra , + * Emmanuel Messulam, Raymond Lai and Contributors. + * + * This file is part of Amaze File Manager. + * + * Amaze File Manager is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.amaze.filemanager.filesystem.files.sort + +/** Describes how to sort with [sortBy] and which direction to use for the sort with [sortOrder] */ +data class SortType(val sortBy: SortBy, val sortOrder: SortOrder) { + /** + * Returns the Int corresponding to the combination of [sortBy] and [sortOrder] + */ + fun toDirectorySortInt(): Int { + val sortIndex = if (sortBy.sortDirectory) sortBy.index else 0 + return when (sortOrder) { + SortOrder.ASC -> sortIndex + SortOrder.DESC -> sortIndex + 4 + } + } + + companion object { + /** + * Returns the [SortType] with the [SortBy] and [SortOrder] corresponding to [index] + */ + @JvmStatic + fun getDirectorySortType(index: Int): SortType { + val sortOrder = if (index <= 3) SortOrder.ASC else SortOrder.DESC + val normalizedIndex = if (index <= 3) index else index - 4 + val sortBy = SortBy.getDirectorySortBy(normalizedIndex) + return SortType(sortBy, sortOrder) + } + } +} diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/ftp/Extensions.kt b/app/src/main/java/com/amaze/filemanager/filesystem/ftp/Extensions.kt index 01575e1fed..f2752db8f5 100644 --- a/app/src/main/java/com/amaze/filemanager/filesystem/ftp/Extensions.kt +++ b/app/src/main/java/com/amaze/filemanager/filesystem/ftp/Extensions.kt @@ -23,7 +23,12 @@ package com.amaze.filemanager.filesystem.ftp import net.schmizz.sshj.xfer.FilePermission import org.apache.commons.net.ftp.FTPClient import org.apache.commons.net.ftp.FTPFile -import org.apache.commons.net.ftp.FTPFile.* +import org.apache.commons.net.ftp.FTPFile.EXECUTE_PERMISSION +import org.apache.commons.net.ftp.FTPFile.GROUP_ACCESS +import org.apache.commons.net.ftp.FTPFile.READ_PERMISSION +import org.apache.commons.net.ftp.FTPFile.USER_ACCESS +import org.apache.commons.net.ftp.FTPFile.WORLD_ACCESS +import org.apache.commons.net.ftp.FTPFile.WRITE_PERMISSION import org.apache.commons.net.ftp.FTPReply import java.io.IOException @@ -49,13 +54,13 @@ fun FTPClient.makeDirectoryTree(dirTree: String) { if (!dirExists) { if (!makeDirectory(dir)) { throw IOException( - "Unable to create remote directory '$dir'. Error='$replyString'" + "Unable to create remote directory '$dir'. Error='$replyString'", ) } if (!changeWorkingDirectory(dir)) { throw IOException( "Unable to change into newly created remote directory '$dir'. " + - "Error='$replyString'" + "Error='$replyString'", ) } } diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/ftp/FTPClientImpl.kt b/app/src/main/java/com/amaze/filemanager/filesystem/ftp/FTPClientImpl.kt index 823942c9c3..ece7f0d13a 100644 --- a/app/src/main/java/com/amaze/filemanager/filesystem/ftp/FTPClientImpl.kt +++ b/app/src/main/java/com/amaze/filemanager/filesystem/ftp/FTPClientImpl.kt @@ -30,21 +30,24 @@ import java.io.OutputStream import kotlin.random.Random class FTPClientImpl(private val ftpClient: FTPClient) : NetCopyClient { - companion object { @JvmStatic private val logger: Logger = LoggerFactory.getLogger(FTPClientImpl::class.java) - @JvmStatic - val ANONYMOUS = "anonymous" + const val ANONYMOUS = "anonymous" + + const val ARG_TLS = "tls" + + const val TLS_EXPLICIT = "explicit" private const val ALPHABET = "abcdefghijklmnopqrstuvwxyz1234567890" @JvmStatic - private fun randomString(strlen: Int) = (1..strlen) - .map { Random.nextInt(0, ALPHABET.length) } - .map(ALPHABET::get) - .joinToString("") + private fun randomString(strlen: Int) = + (1..strlen) + .map { Random.nextInt(0, ALPHABET.length) } + .map(ALPHABET::get) + .joinToString("") /** * Generate random email address for anonymous FTP login. @@ -53,7 +56,7 @@ class FTPClientImpl(private val ftpClient: FTPClient) : NetCopyClient fun generateRandomEmailAddressForLogin( usernameLen: Int = 8, domainPrefixLen: Int = 5, - domainSuffixLen: Int = 3 + domainSuffixLen: Int = 3, ): String { val username = randomString(usernameLen) val domainPrefix = randomString(domainPrefixLen) @@ -67,34 +70,57 @@ class FTPClientImpl(private val ftpClient: FTPClient) : NetCopyClient * Most important part is to do [File.delete] when the reading is done. */ @JvmStatic - fun wrap(inputFile: File) = object : InputStream() { - private val inputStream = FileInputStream(inputFile) - override fun read() = inputStream.read() - override fun read(b: ByteArray?): Int = inputStream.read(b) - override fun read(b: ByteArray?, off: Int, len: Int): Int = - inputStream.read(b, off, len) - override fun reset() = inputStream.reset() - override fun available(): Int = inputStream.available() - override fun close() { - inputStream.close() - inputFile.delete() + fun wrap(inputFile: File) = + object : InputStream() { + private val inputStream = FileInputStream(inputFile) + + override fun read() = inputStream.read() + + override fun read(b: ByteArray?): Int = inputStream.read(b) + + override fun read( + b: ByteArray?, + off: Int, + len: Int, + ): Int = inputStream.read(b, off, len) + + override fun reset() = inputStream.reset() + + override fun available(): Int = inputStream.available() + + override fun close() { + inputStream.close() + inputFile.delete() + } + + override fun markSupported(): Boolean = inputStream.markSupported() + + override fun mark(readlimit: Int) = inputStream.mark(readlimit) + + override fun skip(n: Long): Long = inputStream.skip(n) } - override fun markSupported(): Boolean = inputStream.markSupported() - override fun mark(readlimit: Int) = inputStream.mark(readlimit) - override fun skip(n: Long): Long = inputStream.skip(n) - } /** * Wraps an [OutputStream] returned by [FTPClient.storeFileStream]. * Most important part is to do [FTPClient.completePendingCommand] on [OutputStream.close]. */ @JvmStatic - fun wrap(outputStream: OutputStream, ftpClient: FTPClient) = object : OutputStream() { + fun wrap( + outputStream: OutputStream, + ftpClient: FTPClient, + ) = object : OutputStream() { override fun write(b: Int) = outputStream.write(b) + override fun write(b: ByteArray?) = outputStream.write(b) - override fun write(b: ByteArray?, off: Int, len: Int) = - outputStream.write(b, off, len) + + override fun write( + b: ByteArray?, + off: Int, + len: Int, + ) = outputStream.write(b, off, len) + override fun flush() = outputStream.flush() + override fun close() { outputStream.close() ftpClient.completePendingCommand() diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/ftp/FtpClientTemplate.kt b/app/src/main/java/com/amaze/filemanager/filesystem/ftp/FtpClientTemplate.kt index 1aa1a01308..09cd44193f 100644 --- a/app/src/main/java/com/amaze/filemanager/filesystem/ftp/FtpClientTemplate.kt +++ b/app/src/main/java/com/amaze/filemanager/filesystem/ftp/FtpClientTemplate.kt @@ -29,7 +29,6 @@ import java.io.IOException */ abstract class FtpClientTemplate(url: String, closeClientOnFinish: Boolean = true) : NetCopyClientTemplate(url, closeClientOnFinish) { - @Throws(IOException::class) final override fun execute(client: NetCopyClient): T? { val ftpClient: FTPClient = client.getClientImpl() diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/ftp/NetCopyClient.kt b/app/src/main/java/com/amaze/filemanager/filesystem/ftp/NetCopyClient.kt index 4fd1dcc538..840ba7220d 100644 --- a/app/src/main/java/com/amaze/filemanager/filesystem/ftp/NetCopyClient.kt +++ b/app/src/main/java/com/amaze/filemanager/filesystem/ftp/NetCopyClient.kt @@ -24,7 +24,6 @@ package com.amaze.filemanager.filesystem.ftp * Base interface for defining client class that interacts with a remote server. */ interface NetCopyClient { - /** * Returns the physical client implementation. */ diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/ftp/NetCopyClientConnectionPool.kt b/app/src/main/java/com/amaze/filemanager/filesystem/ftp/NetCopyClientConnectionPool.kt index e5fb262932..91d43d52e4 100644 --- a/app/src/main/java/com/amaze/filemanager/filesystem/ftp/NetCopyClientConnectionPool.kt +++ b/app/src/main/java/com/amaze/filemanager/filesystem/ftp/NetCopyClientConnectionPool.kt @@ -25,7 +25,10 @@ import com.amaze.filemanager.application.AppConfig import com.amaze.filemanager.asynchronous.asynctasks.ftp.auth.FtpAuthenticationTask import com.amaze.filemanager.asynchronous.asynctasks.ssh.PemToKeyPairObservable import com.amaze.filemanager.asynchronous.asynctasks.ssh.SshAuthenticationTask +import com.amaze.filemanager.filesystem.ftp.FTPClientImpl.Companion.ARG_TLS +import com.amaze.filemanager.filesystem.ftp.FTPClientImpl.Companion.TLS_EXPLICIT import com.amaze.filemanager.filesystem.ftp.NetCopyClientUtils.extractBaseUriFrom +import com.amaze.filemanager.filesystem.ftp.NetCopyConnectionInfo.Companion.QUESTION_MARK import io.reactivex.Flowable import io.reactivex.Maybe import io.reactivex.Observable.create @@ -40,14 +43,12 @@ import org.json.JSONObject import org.slf4j.Logger import org.slf4j.LoggerFactory import java.security.KeyPair -import java.util.* import java.util.concurrent.Callable import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.CountDownLatch import java.util.concurrent.atomic.AtomicReference object NetCopyClientConnectionPool { - const val FTP_DEFAULT_PORT = 21 const val FTPS_DEFAULT_PORT = 990 const val SSH_DEFAULT_PORT = 22 @@ -70,7 +71,6 @@ object NetCopyClientConnectionPool { /** * Obtain a [NetCopyClient] connection from the underlying connection pool. * - * * Beneath it will return the connection if it exists; otherwise it will create a new one and * put it into the connection pool. * @@ -133,27 +133,32 @@ object NetCopyClientConnectionPool { hostFingerprint: String? = null, username: String, password: String? = null, - keyPair: KeyPair? = null + keyPair: KeyPair? = null, + explicitTls: Boolean = false, ): NetCopyClient<*>? { - val url = NetCopyClientUtils.deriveUriFrom( - protocol, - host, - port, - "", - username, - password - ) - var client = connections[url] - if (client == null) { - client = createNetCopyClientInternal( + val url = + NetCopyClientUtils.deriveUriFrom( protocol, host, port, - hostFingerprint, + "", username, password, - keyPair + explicitTls, ) + var client = connections[url] + if (client == null) { + client = + createNetCopyClientInternal( + protocol, + host, + port, + hostFingerprint, + username, + password, + keyPair, + explicitTls, + ) if (client != null) connections[url] = client } else { if (!validate(client)) { @@ -182,8 +187,9 @@ object NetCopyClientConnectionPool { String?, String, String?, - KeyPair? - ) -> NetCopyClient<*>? = { protocol, host, port, hostFingerprint, username, password, keyPair -> + KeyPair?, + Boolean, + ) -> NetCopyClient<*>? = { protocol, host, port, hostFingerprint, username, password, keyPair, explicitTls -> if (protocol == SSH_URI_PREFIX) { createSshClient(host, port, hostFingerprint!!, username, password, keyPair) } else { @@ -193,7 +199,8 @@ object NetCopyClientConnectionPool { port, hostFingerprint?.let { JSONObject(it) }, username, - password + password, + explicitTls, ) } } @@ -209,7 +216,10 @@ object NetCopyClientConnectionPool { * @param url SSH connection URI */ @SuppressLint("CheckResult") - fun removeConnection(url: String, callback: () -> Unit) { + fun removeConnection( + url: String, + callback: () -> Unit, + ) { Maybe.fromCallable(AsyncRemoveConnection(url)) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) @@ -239,9 +249,10 @@ object NetCopyClientConnectionPool { }.subscribeOn(NetCopyClientUtils.getScheduler(client)).blockingGet() } - private fun expire(client: NetCopyClient<*>) = Flowable.fromCallable { - client.expire() - }.subscribeOn(NetCopyClientUtils.getScheduler(client)) + private fun expire(client: NetCopyClient<*>) = + Flowable.fromCallable { + client.expire() + }.subscribeOn(NetCopyClientUtils.getScheduler(client)) // Logic for creating SSH connection. Depends on password existence in given Uri password or // key-based authentication @@ -268,7 +279,7 @@ object NetCopyClientConnectionPool { } } } - .blockingFirst() + .blockingFirst(), ) } val hostKey = utilsHandler.getRemoteHostKey(url) ?: return null @@ -278,7 +289,7 @@ object NetCopyClientConnectionPool { hostKey, connInfo.username, connInfo.password, - keyPair.get() + keyPair.get(), ) } @@ -289,7 +300,7 @@ object NetCopyClientConnectionPool { hostKey: String, username: String, password: String?, - keyPair: KeyPair? + keyPair: KeyPair?, ): NetCopyClient? { return createSshClientInternal( host, @@ -297,7 +308,7 @@ object NetCopyClientConnectionPool { hostKey, username, password, - keyPair + keyPair, ) } @@ -308,16 +319,17 @@ object NetCopyClientConnectionPool { hostKey: String, username: String, password: String?, - keyPair: KeyPair? + keyPair: KeyPair?, ): NetCopyClient? { - val task = SshAuthenticationTask( - hostname = host, - port = port, - hostKey = hostKey, - username = username, - password = password, - privateKey = keyPair - ) + val task = + SshAuthenticationTask( + hostname = host, + port = port, + hostKey = hostKey, + username = username, + password = password, + privateKey = keyPair, + ) val latch = CountDownLatch(1) var retval: SSHClient? = null Maybe.fromCallable(task.getTask()) @@ -337,18 +349,21 @@ object NetCopyClientConnectionPool { private fun createFtpClient(url: String): NetCopyClient? { NetCopyConnectionInfo(url).run { - val certInfo = if (FTPS_URI_PREFIX == prefix) { - AppConfig.getInstance().utilsHandler.getRemoteHostKey(url) - } else { - null - } + val certInfo = + if (FTPS_URI_PREFIX == prefix) { + AppConfig.getInstance().utilsHandler.getRemoteHostKey(url) + } else { + null + } return createFtpClient( prefix, host, port, certInfo?.let { JSONObject(it) }, username, - password + password, + true == arguments?.containsKey(ARG_TLS) && + TLS_EXPLICIT == arguments?.get(ARG_TLS), ) } } @@ -360,16 +375,19 @@ object NetCopyClientConnectionPool { port: Int, certInfo: JSONObject?, username: String, - password: String? + password: String?, + explicitTls: Boolean = false, ): NetCopyClient? { - val task = FtpAuthenticationTask( - protocol, - host, - port, - certInfo, - username, - password - ) + val task = + FtpAuthenticationTask( + protocol, + host, + port, + certInfo, + username, + password, + explicitTls, + ) val latch = CountDownLatch(1) var result: FTPClient? = null Single.fromCallable(task.getTask()) @@ -388,9 +406,8 @@ object NetCopyClientConnectionPool { } class AsyncRemoveConnection internal constructor( - private val url: String + private val url: String, ) : Callable { - override fun call() { extractBaseUriFrom(url).run { if (connections.containsKey(this)) { @@ -440,11 +457,15 @@ object NetCopyClientConnectionPool { override fun create(uri: String): FTPClient { return ( if (uri.startsWith(FTPS_URI_PREFIX)) { - FTPSClient("TLS", true) + FTPSClient( + "TLS", + !uri.contains(QUESTION_MARK) || + !uri.substringAfter(QUESTION_MARK).contains("$ARG_TLS=$TLS_EXPLICIT"), + ) } else { FTPClient() } - ).also { + ).also { it.addProtocolCommandListener(Slf4jPrintCommandListener()) it.connectTimeout = CONNECT_TIMEOUT it.controlEncoding = Charsets.UTF_8.name() diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/ftp/NetCopyClientTemplate.kt b/app/src/main/java/com/amaze/filemanager/filesystem/ftp/NetCopyClientTemplate.kt index af3a6660fa..82b80cc24c 100644 --- a/app/src/main/java/com/amaze/filemanager/filesystem/ftp/NetCopyClientTemplate.kt +++ b/app/src/main/java/com/amaze/filemanager/filesystem/ftp/NetCopyClientTemplate.kt @@ -23,25 +23,26 @@ package com.amaze.filemanager.filesystem.ftp import java.io.IOException abstract class NetCopyClientTemplate - /** - * Constructor, with closeClientOnFinish set to true (that the connection must close after ` - * execute`. - * - * @param url SSH connection URL, in the form of ` - * ssh://:@:` or ` - * ssh://@:` - */ -@JvmOverloads -constructor(@JvmField val url: String, @JvmField val closeClientOnFinish: Boolean = true) { - - /** - * Implement logic here. + * Constructor, with closeClientOnFinish set to true (that the connection must close after ` + * execute`. * - * @param client [NetCopyClient] instance, with connection opened and authenticated - * @param Requested return type - * @return Result of the execution of the type requested - **/ - @Throws(IOException::class) - abstract fun execute(client: NetCopyClient): T? -} + * @param url SSH connection URL, in the form of ` + * ssh://:@:` or ` + * ssh://@:` + */ + @JvmOverloads + constructor( + @JvmField val url: String, + @JvmField val closeClientOnFinish: Boolean = true, + ) { + /** + * Implement logic here. + * + * @param client [NetCopyClient] instance, with connection opened and authenticated + * @param Requested return type + * @return Result of the execution of the type requested + **/ + @Throws(IOException::class) + abstract fun execute(client: NetCopyClient): T? + } diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/ftp/NetCopyClientUtils.kt b/app/src/main/java/com/amaze/filemanager/filesystem/ftp/NetCopyClientUtils.kt index 372bc5279f..557efb985a 100644 --- a/app/src/main/java/com/amaze/filemanager/filesystem/ftp/NetCopyClientUtils.kt +++ b/app/src/main/java/com/amaze/filemanager/filesystem/ftp/NetCopyClientUtils.kt @@ -26,6 +26,8 @@ import com.amaze.filemanager.application.AppConfig import com.amaze.filemanager.fileoperations.filesystem.DOESNT_EXIST import com.amaze.filemanager.fileoperations.filesystem.FolderState import com.amaze.filemanager.fileoperations.filesystem.WRITABLE_ON_REMOTE +import com.amaze.filemanager.filesystem.ftp.FTPClientImpl.Companion.ARG_TLS +import com.amaze.filemanager.filesystem.ftp.FTPClientImpl.Companion.TLS_EXPLICIT import com.amaze.filemanager.filesystem.ftp.NetCopyClientConnectionPool.FTPS_DEFAULT_PORT import com.amaze.filemanager.filesystem.ftp.NetCopyClientConnectionPool.FTPS_URI_PREFIX import com.amaze.filemanager.filesystem.ftp.NetCopyClientConnectionPool.FTP_DEFAULT_PORT @@ -33,12 +35,14 @@ import com.amaze.filemanager.filesystem.ftp.NetCopyClientConnectionPool.FTP_URI_ import com.amaze.filemanager.filesystem.ftp.NetCopyClientConnectionPool.SSH_DEFAULT_PORT import com.amaze.filemanager.filesystem.ftp.NetCopyClientConnectionPool.SSH_URI_PREFIX import com.amaze.filemanager.filesystem.ftp.NetCopyClientConnectionPool.getConnection +import com.amaze.filemanager.filesystem.ftp.NetCopyConnectionInfo.Companion.AND import com.amaze.filemanager.filesystem.ftp.NetCopyConnectionInfo.Companion.AT import com.amaze.filemanager.filesystem.ftp.NetCopyConnectionInfo.Companion.COLON +import com.amaze.filemanager.filesystem.ftp.NetCopyConnectionInfo.Companion.QUESTION_MARK import com.amaze.filemanager.filesystem.ftp.NetCopyConnectionInfo.Companion.SLASH import com.amaze.filemanager.filesystem.smb.CifsContexts.SMB_URI_PREFIX import com.amaze.filemanager.filesystem.ssh.SFtpClientTemplate -import com.amaze.filemanager.utils.SmbUtil +import com.amaze.filemanager.utils.smb.SmbUtil import com.amaze.filemanager.utils.urlEncoded import io.reactivex.Maybe import io.reactivex.Scheduler @@ -48,9 +52,12 @@ import org.apache.commons.net.ftp.FTPClient import org.apache.commons.net.ftp.FTPReply import org.slf4j.LoggerFactory import java.io.IOException +import java.text.DateFormat +import java.text.SimpleDateFormat +import java.util.Calendar +import java.util.Locale object NetCopyClientUtils { - @JvmStatic private val LOG = LoggerFactory.getLogger(NetCopyClientUtils::class.java) @@ -74,33 +81,34 @@ object NetCopyClientUtils { * Execute the given NetCopyClientTemplate. * * This template pattern is borrowed from Spring Framework, to simplify code on operations - * using SftpClientTemplate. + * using NetCopyClientTemplate. + * + * FIXME: Over-simplification implementation causing unnecessarily closing SSHClient. * * @param template [NetCopyClientTemplate] to execute * @param Type of return value * @return Template execution results */ @WorkerThread - fun execute( - template: NetCopyClientTemplate - ): T? { + fun execute(template: NetCopyClientTemplate): T? { var client = getConnection(extractBaseUriFrom(template.url)) if (client == null) { client = getConnection(template.url) } var retval: T? = null if (client != null) { - retval = runCatching { - Maybe.fromCallable { - template.execute(client) - }.subscribeOn(getScheduler.invoke(client)).blockingGet() - }.onFailure { - LOG.error("Error executing template method", it) - }.also { - if (template.closeClientOnFinish) { - tryDisconnect(client) - } - }.getOrNull() + retval = + runCatching { + Maybe.fromCallable { + template.execute(client) + }.subscribeOn(getScheduler.invoke(client)).blockingGet() + }.onFailure { + LOG.error("Error executing template method", it) + }.also { + if (template.closeClientOnFinish) { + tryDisconnect(client) + } + }.getOrNull() } return retval } @@ -117,7 +125,7 @@ object NetCopyClientUtils { return if (uriWithoutProtocol.substringBefore(AT).indexOf(COLON) > 0) { SmbUtil.getSmbEncryptedPath( AppConfig.getInstance(), - fullUri + fullUri, ) } else { fullUri @@ -137,7 +145,7 @@ object NetCopyClientUtils { if (uriWithoutProtocol.lastIndexOf(COLON) > 0) { SmbUtil.getSmbDecryptedPath( AppConfig.getInstance(), - fullUri + fullUri, ) } else { fullUri @@ -173,6 +181,10 @@ object NetCopyClientUtils { if (it.port > 0) { append(COLON).append(it.port) } + if (!it.arguments.isNullOrEmpty()) { + append(QUESTION_MARK) + .append(it.arguments?.entries?.joinToString(AND.toString())) + } } } } @@ -187,6 +199,7 @@ object NetCopyClientUtils { * @param fullUri Full SSH URL * @return The remote path part of the full SSH URL */ + @JvmStatic fun extractRemotePathFrom(fullUri: String): String { return NetCopyConnectionInfo(fullUri).let { connInfo -> if (true == connInfo.defaultPath?.isNotEmpty()) { @@ -225,21 +238,24 @@ object NetCopyClientUtils { defaultPath: String? = null, username: String, password: String? = null, - edit: Boolean = false + explicitTls: Boolean = false, + edit: Boolean = false, ): String { // FIXME: should be caller's responsibility var pathSuffix = defaultPath if (pathSuffix == null) pathSuffix = SLASH.toString() - val thisPassword = if (password == "" || password == null) { - "" - } else { - ":${if (edit) { - password + if (explicitTls) pathSuffix = "$pathSuffix?$ARG_TLS=$TLS_EXPLICIT" + val thisPassword = + if (password == "" || password == null) { + "" } else { - password.urlEncoded() - }}" - } - return if (username == "" && (true == password?.isEmpty())) { + ":${if (edit) { + password + } else { + password.urlEncoded() + }}" + } + return if (username == "") { "$prefix$hostname:$port$pathSuffix" } else { "$prefix$username$thisPassword@$hostname:$port$pathSuffix" @@ -251,31 +267,32 @@ object NetCopyClientUtils { */ @FolderState fun checkFolder(path: String): Int { - val template: NetCopyClientTemplate<*, Int> = if (path.startsWith(SSH_URI_PREFIX)) { - object : SFtpClientTemplate(extractBaseUriFrom(path), false) { - @FolderState - @Throws(IOException::class) - override fun execute(client: SFTPClient): Int { - return if (client.statExistence(extractRemotePathFrom(path)) == null) { - WRITABLE_ON_REMOTE - } else { - DOESNT_EXIST + val template: NetCopyClientTemplate<*, Int> = + if (path.startsWith(SSH_URI_PREFIX)) { + object : SFtpClientTemplate(extractBaseUriFrom(path), false) { + @FolderState + @Throws(IOException::class) + override fun execute(client: SFTPClient): Int { + return if (client.statExistence(extractRemotePathFrom(path)) == null) { + WRITABLE_ON_REMOTE + } else { + DOESNT_EXIST + } } } - } - } else { - object : FtpClientTemplate(extractBaseUriFrom(path), false) { - override fun executeWithFtpClient(ftpClient: FTPClient): Int { - return if (ftpClient.stat(extractRemotePathFrom(path)) - == FTPReply.DIRECTORY_STATUS - ) { - WRITABLE_ON_REMOTE - } else { - DOESNT_EXIST + } else { + object : FtpClientTemplate(extractBaseUriFrom(path), false) { + override fun executeWithFtpClient(ftpClient: FTPClient): Int { + return if (ftpClient.stat(extractRemotePathFrom(path)) + == FTPReply.DIRECTORY_STATUS + ) { + WRITABLE_ON_REMOTE + } else { + DOESNT_EXIST + } } } } - } return execute(template) ?: DOESNT_EXIST } @@ -284,11 +301,24 @@ object NetCopyClientUtils { * * Reserved for future use. */ - fun defaultPort(prefix: String) = when (prefix) { - SSH_URI_PREFIX -> SSH_DEFAULT_PORT - FTPS_URI_PREFIX -> FTPS_DEFAULT_PORT - FTP_URI_PREFIX -> FTP_DEFAULT_PORT - SMB_URI_PREFIX -> 0 // SMB never requires explicit port number at URL - else -> throw IllegalArgumentException("Cannot derive default port") + fun defaultPort(prefix: String) = + when (prefix) { + SSH_URI_PREFIX -> SSH_DEFAULT_PORT + FTPS_URI_PREFIX -> FTPS_DEFAULT_PORT + FTP_URI_PREFIX -> FTP_DEFAULT_PORT + SMB_URI_PREFIX -> 0 // SMB never requires explicit port number at URL + else -> throw IllegalArgumentException("Cannot derive default port") + } + + /** + * Convenience method to format given UNIX timestamp to yyyyMMddHHmmss format. + */ + @JvmStatic + fun getTimestampForTouch(date: Long): String { + val calendar = Calendar.getInstance() + calendar.timeInMillis = date + val df: DateFormat = SimpleDateFormat("yyyyMMddHHmmss", Locale.US) + df.calendar = calendar + return df.format(calendar.time) } } diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/ftp/NetCopyConnectionInfo.kt b/app/src/main/java/com/amaze/filemanager/filesystem/ftp/NetCopyConnectionInfo.kt index bac9fa83b7..1b7bc4a134 100644 --- a/app/src/main/java/com/amaze/filemanager/filesystem/ftp/NetCopyConnectionInfo.kt +++ b/app/src/main/java/com/amaze/filemanager/filesystem/ftp/NetCopyConnectionInfo.kt @@ -43,7 +43,6 @@ import com.amaze.filemanager.filesystem.smb.CifsContexts.SMB_URI_PREFIX * occur. No validation is made at this point, so proceed at your own risk. */ class NetCopyConnectionInfo(url: String) { - val prefix: String val host: String val port: Int @@ -62,17 +61,16 @@ class NetCopyConnectionInfo(url: String) { // Regex taken from https://blog.stevenlevithan.com/archives/parseuri // (No, don't break it down to lines) - /* ktlint-disable max-line-length */ + @Suppress("ktlint:standard:max-line-length") private const val URI_REGEX = "^(?:(?![^:@]+:[^:@/]*@)([^:/?#.]+):)?(?://)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:/?#]*)(?::(\\d*))?)(((/(?:[^?#](?![^?#/]*\\.[^?#/.]+(?:[?#]|$)))*/?)?([^?#/]*))(?:\\?([^#]*))?(?:#(.*))?)" - /* ktlint-enable max-line-length */ - const val MULTI_SLASH = "(?<=[^:])(//+)" const val AND = '&' const val AT = '@' const val SLASH = '/' const val COLON = ':' + const val QUESTION_MARK = '?' } init { @@ -80,7 +78,7 @@ class NetCopyConnectionInfo(url: String) { url.startsWith(SSH_URI_PREFIX) or url.startsWith(FTP_URI_PREFIX) or url.startsWith(FTPS_URI_PREFIX) or - url.startsWith(SMB_URI_PREFIX) + url.startsWith(SMB_URI_PREFIX), ) { "Argument is not a supported remote URI: $url" } @@ -100,43 +98,46 @@ class NetCopyConnectionInfo(url: String) { username = credential.substringBefore(COLON) password = credential.substringAfter(COLON) } - port = if (it[7].isNotEmpty()) { + port = + if (it[7].isNotEmpty()) { /* * Invalid string would have been trapped to other branches. Strings fell into * this branch must be integer */ - it[7].toInt() - } else { - 0 - } - queryString = it[12].ifEmpty { null } - arguments = if (it[12].isNotEmpty()) { - it[12].split(AND).associate { valuePair -> - val pair = valuePair.split('=') - Pair( - pair[0], - pair[1].ifEmpty { - "" - } - ) + it[7].toInt() + } else { + 0 } - } else { - null - } - defaultPath = ( - if (it[9].isEmpty()) { - null - } else if (it[9] == SLASH.toString()) { - SLASH.toString() - } else if (!it[9].endsWith(SLASH)) { - if (it[11].isEmpty()) { - it[10] - } else { - it[10].substringBeforeLast(SLASH) + queryString = it[12].ifEmpty { null } + arguments = + if (it[12].isNotEmpty()) { + it[12].split(AND).associate { valuePair -> + val pair = valuePair.split('=') + Pair( + pair[0], + pair[1].ifEmpty { + "" + }, + ) } } else { - it[9] + null } + defaultPath = + ( + if (it[9].isEmpty()) { + null + } else if (it[9] == SLASH.toString()) { + SLASH.toString() + } else if (!it[9].endsWith(SLASH)) { + if (it[11].isEmpty()) { + it[10] + } else { + it[10].substringBeforeLast(SLASH) + } + } else { + it[9] + } )?.replace(Regex(MULTI_SLASH), SLASH.toString()) filename = it[11].ifEmpty { null } } @@ -157,7 +158,7 @@ class NetCopyConnectionInfo(url: String) { } override fun toString(): String { - return if (username.isNotEmpty()) { + return if (username.isNotBlank() && username.isNotEmpty()) { "$prefix$username@$host${if (port == 0) "" else ":$port"}${defaultPath ?: ""}" } else { "$prefix$host${if (port == 0) "" else ":$port"}${defaultPath ?: ""}" diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/ftp/SSHClientImpl.kt b/app/src/main/java/com/amaze/filemanager/filesystem/ftp/SSHClientImpl.kt index ad2bf887c0..ebcf17eecd 100644 --- a/app/src/main/java/com/amaze/filemanager/filesystem/ftp/SSHClientImpl.kt +++ b/app/src/main/java/com/amaze/filemanager/filesystem/ftp/SSHClientImpl.kt @@ -25,7 +25,6 @@ import org.slf4j.Logger import org.slf4j.LoggerFactory class SSHClientImpl(private val sshClient: SSHClient) : NetCopyClient { - companion object { @JvmStatic private val logger: Logger = LoggerFactory.getLogger(SSHClientImpl::class.java) @@ -33,8 +32,7 @@ class SSHClientImpl(private val sshClient: SSHClient) : NetCopyClient override fun getClientImpl() = sshClient - override fun isConnectionValid(): Boolean = - sshClient.isConnected && sshClient.isAuthenticated + override fun isConnectionValid(): Boolean = sshClient.isConnected && sshClient.isAuthenticated override fun expire() { if (sshClient.isConnected) { diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/ftp/Slf4jPrintCommandListener.kt b/app/src/main/java/com/amaze/filemanager/filesystem/ftp/Slf4jPrintCommandListener.kt index 18167640fb..8ac5595a65 100644 --- a/app/src/main/java/com/amaze/filemanager/filesystem/ftp/Slf4jPrintCommandListener.kt +++ b/app/src/main/java/com/amaze/filemanager/filesystem/ftp/Slf4jPrintCommandListener.kt @@ -36,10 +36,9 @@ internal class Slf4jPrintCommandListener( private val nologin: Boolean = true, private val eolMarker: Char = 0.toChar(), private val directionMarker: Boolean = false, - private val loggerLevel: Level = Level.DEBUG + private val loggerLevel: Level = Level.DEBUG, ) : ProtocolCommandListener { - private val logger: Logger = LoggerFactory.getLogger(SocketClient::class.java) private val logMessage: (String) -> Unit = { msg -> @@ -72,11 +71,12 @@ internal class Slf4jPrintCommandListener( } override fun protocolReplyReceived(event: ProtocolCommandEvent) { - val msg = if (directionMarker) { - "< ${event.message}" - } else { - event.message - } + val msg = + if (directionMarker) { + "< ${event.message}" + } else { + event.message + } logMessage.invoke(msg) } diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/ftpserver/AndroidFileSystemFactory.kt b/app/src/main/java/com/amaze/filemanager/filesystem/ftpserver/AndroidFileSystemFactory.kt index effc1e888a..af53162e3e 100644 --- a/app/src/main/java/com/amaze/filemanager/filesystem/ftpserver/AndroidFileSystemFactory.kt +++ b/app/src/main/java/com/amaze/filemanager/filesystem/ftpserver/AndroidFileSystemFactory.kt @@ -30,7 +30,6 @@ import org.apache.ftpserver.ftplet.User @RequiresApi(KITKAT) class AndroidFileSystemFactory(private val context: Context) : FileSystemFactory { - override fun createFileSystemView(user: User?): FileSystemView = AndroidFtpFileSystemView(context, user?.homeDirectory ?: FtpService.defaultPath(context)) } diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/ftpserver/AndroidFtpFile.kt b/app/src/main/java/com/amaze/filemanager/filesystem/ftpserver/AndroidFtpFile.kt index 3bc7621c2a..9f10594e3c 100644 --- a/app/src/main/java/com/amaze/filemanager/filesystem/ftpserver/AndroidFtpFile.kt +++ b/app/src/main/java/com/amaze/filemanager/filesystem/ftpserver/AndroidFtpFile.kt @@ -41,9 +41,8 @@ class AndroidFtpFile( context: Context, private val parentDocument: DocumentFile, private val backingDocument: DocumentFile?, - private val path: String + private val path: String, ) : FtpFile { - private val _context: WeakReference = WeakReference(context) private val context: Context get() = _context.get()!! @@ -127,16 +126,18 @@ class AndroidFtpFile( */ override fun setLastModified(time: Long): Boolean { return if (doesExist()) { - val updateValues = ContentValues().also { - it.put(DocumentsContract.Document.COLUMN_LAST_MODIFIED, time) - } + val updateValues = + ContentValues().also { + it.put(DocumentsContract.Document.COLUMN_LAST_MODIFIED, time) + } val docUri: Uri = backingDocument!!.uri - val updated: Int = context.contentResolver.update( - docUri, - updateValues, - null, - null - ) + val updated: Int = + context.contentResolver.update( + docUri, + updateValues, + null, + null, + ) return updated == 1 } else { false @@ -170,46 +171,49 @@ class AndroidFtpFile( * @see FtpFile.move * @see DocumentFile.renameTo */ - override fun move(destination: FtpFile): Boolean = - backingDocument?.renameTo(destination.name) ?: false + override fun move(destination: FtpFile): Boolean = backingDocument?.renameTo(destination.name) ?: false /** * @see FtpFile.listFiles * @see DocumentFile.listFiles */ - override fun listFiles(): MutableList = if (doesExist()) { - backingDocument!!.listFiles().map { - AndroidFtpFile(context, backingDocument, it, it.name!!) - }.toMutableList() - } else { - mutableListOf() - } + override fun listFiles(): MutableList = + if (doesExist()) { + backingDocument!!.listFiles().map { + AndroidFtpFile(context, backingDocument, it, it.name!!) + }.toMutableList() + } else { + mutableListOf() + } /** * @see FtpFile.createOutputStream * @see ContentResolver.openOutputStream */ - override fun createOutputStream(offset: Long): OutputStream? = runCatching { - val uri = if (doesExist()) { - backingDocument!!.uri - } else { - val newFile = parentDocument.createFile("", name) - newFile?.uri ?: throw IOException("Cannot create file at $path") - } - context.contentResolver.openOutputStream(uri) - }.getOrThrow() + override fun createOutputStream(offset: Long): OutputStream? = + runCatching { + val uri = + if (doesExist()) { + backingDocument!!.uri + } else { + val newFile = parentDocument.createFile("", name) + newFile?.uri ?: throw IOException("Cannot create file at $path") + } + context.contentResolver.openOutputStream(uri) + }.getOrThrow() /** * @see FtpFile.createInputStream * @see ContentResolver.openInputStream */ - override fun createInputStream(offset: Long): InputStream? = runCatching { - if (doesExist()) { - context.contentResolver.openInputStream(backingDocument!!.uri).also { - it?.skip(offset) + override fun createInputStream(offset: Long): InputStream? = + runCatching { + if (doesExist()) { + context.contentResolver.openInputStream(backingDocument!!.uri).also { + it?.skip(offset) + } + } else { + throw FileNotFoundException(path) } - } else { - throw FileNotFoundException(path) - } - }.getOrThrow() + }.getOrThrow() } diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/ftpserver/AndroidFtpFileSystemView.kt b/app/src/main/java/com/amaze/filemanager/filesystem/ftpserver/AndroidFtpFileSystemView.kt index b602ebc06f..1a25824733 100644 --- a/app/src/main/java/com/amaze/filemanager/filesystem/ftpserver/AndroidFtpFileSystemView.kt +++ b/app/src/main/java/com/amaze/filemanager/filesystem/ftpserver/AndroidFtpFileSystemView.kt @@ -34,20 +34,18 @@ import java.net.URI @RequiresApi(KITKAT) class AndroidFtpFileSystemView(private var context: Context, root: String) : FileSystemView { - private val rootPath = root private val rootDocumentFile = createDocumentFileFrom(rootPath) private var currentPath: String? = "/" - override fun getHomeDirectory(): FtpFile = - AndroidFtpFile(context, rootDocumentFile, resolveDocumentFileFromRoot("/"), "/") + override fun getHomeDirectory(): FtpFile = AndroidFtpFile(context, rootDocumentFile, resolveDocumentFileFromRoot("/"), "/") override fun getWorkingDirectory(): FtpFile { return AndroidFtpFile( context, rootDocumentFile, resolveDocumentFileFromRoot(currentPath!!), - currentPath!! + currentPath!!, ) } @@ -67,30 +65,32 @@ class AndroidFtpFileSystemView(private var context: Context, root: String) : Fil } } else -> { - currentPath = when { - currentPath.isNullOrEmpty() || currentPath == "/" -> dir - !dir.startsWith("/") -> normalizePath("$currentPath/$dir") - else -> normalizePath(dir) - } + currentPath = + when { + currentPath.isNullOrEmpty() || currentPath == "/" -> dir + !dir.startsWith("/") -> normalizePath("$currentPath/$dir") + else -> normalizePath(dir) + } resolveDocumentFileFromRoot(currentPath) != null } } } override fun getFile(file: String): FtpFile { - val path = if (currentPath.isNullOrEmpty() || currentPath == "/") { - "/$file" - } else if (file.startsWith('/')) { - file - } else { - "$currentPath/$file" - } + val path = + if (currentPath.isNullOrEmpty() || currentPath == "/") { + "/$file" + } else if (file.startsWith('/')) { + file + } else { + "$currentPath/$file" + } return normalizePath(path).let { normalizedPath -> AndroidFtpFile( context, resolveDocumentFileFromRoot(getParentFrom(normalizedPath))!!, // rootDocumentFile, resolveDocumentFileFromRoot(normalizedPath), - normalizedPath + normalizedPath, ) } } @@ -113,7 +113,7 @@ class AndroidFtpFileSystemView(private var context: Context, root: String) : Fil Uri.decode( URI(Uri.encode(path, "/")) .normalize() - .toString() + .toString(), ).replace("//", "/") } } diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/ftpserver/RootFileSystemFactory.kt b/app/src/main/java/com/amaze/filemanager/filesystem/ftpserver/RootFileSystemFactory.kt index 980ac2b559..808b8dc15b 100644 --- a/app/src/main/java/com/amaze/filemanager/filesystem/ftpserver/RootFileSystemFactory.kt +++ b/app/src/main/java/com/amaze/filemanager/filesystem/ftpserver/RootFileSystemFactory.kt @@ -26,9 +26,7 @@ import org.apache.ftpserver.ftplet.User class RootFileSystemFactory( private val fileFactory: RootFileSystemView.SuFileFactory = - RootFileSystemView.DefaultSuFileFactory() + RootFileSystemView.DefaultSuFileFactory(), ) : FileSystemFactory { - - override fun createFileSystemView(user: User): FileSystemView = - RootFileSystemView(user, fileFactory) + override fun createFileSystemView(user: User): FileSystemView = RootFileSystemView(user, fileFactory) } diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/ftpserver/RootFileSystemView.kt b/app/src/main/java/com/amaze/filemanager/filesystem/ftpserver/RootFileSystemView.kt index 83f6589d45..90f4eaaf68 100644 --- a/app/src/main/java/com/amaze/filemanager/filesystem/ftpserver/RootFileSystemView.kt +++ b/app/src/main/java/com/amaze/filemanager/filesystem/ftpserver/RootFileSystemView.kt @@ -27,13 +27,12 @@ import org.apache.ftpserver.ftplet.FtpFile import org.apache.ftpserver.ftplet.User import java.io.File import java.net.URI -import java.util.* +import java.util.StringTokenizer class RootFileSystemView( private val user: User, - private val fileFactory: SuFileFactory + private val fileFactory: SuFileFactory, ) : FileSystemView { - private var currDir: String private var rootDir: String @@ -51,7 +50,7 @@ class RootFileSystemView( Log.d( TAG, - "Native filesystem view created for user \"${user.name}\" with root \"${rootDir}\"" + "Native filesystem view created for user \"${user.name}\" with root \"${rootDir}\"", ) this.rootDir = rootDir @@ -122,7 +121,7 @@ class RootFileSystemView( private fun getPhysicalName( rootDir: String, currDir: String, - fileName: String + fileName: String, ): String { // normalize root dir var normalizedRootDir: String = normalizeSeparateChar(rootDir) @@ -134,13 +133,14 @@ class RootFileSystemView( // if file name is relative, set resArg to root dir + curr dir // if file name is absolute, set resArg to root dir - result = if (normalizedFileName[0] != '/') { - // file name is relative - val normalizedCurrDir = normalize(currDir) - normalizedRootDir + normalizedCurrDir.substring(1) - } else { - normalizedRootDir - } + result = + if (normalizedFileName[0] != '/') { + // file name is relative + val normalizedCurrDir = normalize(currDir) + normalizedRootDir + normalizedCurrDir.substring(1) + } else { + normalizedRootDir + } // strip last '/' result = trimTrailingSlash(result) @@ -253,12 +253,18 @@ class RootFileSystemView( /** * Create SuFile. */ - fun create(parent: String, child: String): SuFile = SuFile(parent, child) + fun create( + parent: String, + child: String, + ): SuFile = SuFile(parent, child) /** * Create SuFile. */ - fun create(parent: File, child: String): SuFile = SuFile(parent, child) + fun create( + parent: File, + child: String, + ): SuFile = SuFile(parent, child) /** * Create SuFile. diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/ftpserver/RootFtpFile.kt b/app/src/main/java/com/amaze/filemanager/filesystem/ftpserver/RootFtpFile.kt index 7a1b4994f9..0a85fac8dd 100644 --- a/app/src/main/java/com/amaze/filemanager/filesystem/ftpserver/RootFtpFile.kt +++ b/app/src/main/java/com/amaze/filemanager/filesystem/ftpserver/RootFtpFile.kt @@ -34,9 +34,8 @@ import java.io.OutputStream class RootFtpFile( private val fileName: String, private val backingFile: SuFile, - private val user: User + private val user: User, ) : FtpFile { - companion object { @JvmStatic private val logger: Logger = LoggerFactory.getLogger(RootFtpFile::class.java) @@ -86,18 +85,19 @@ class RootFtpFile( } // In order to maintain consistency, when possible we delete the last '/' character in the String val indexOfSlash = fullName.lastIndexOf('/') - val parentFullName: String = if (indexOfSlash == 0) { - "/" - } else { - fullName.substring(0, indexOfSlash) - } + val parentFullName: String = + if (indexOfSlash == 0) { + "/" + } else { + fullName.substring(0, indexOfSlash) + } // we check if the parent FileObject is writable. return backingFile.absoluteFile.parentFile?.run { RootFtpFile( parentFullName, this, - user + user, ).isWritable } ?: false } @@ -120,16 +120,14 @@ class RootFtpFile( override fun delete(): Boolean = backingFile.delete() - override fun move(destination: FtpFile): Boolean = - backingFile.renameTo(destination.physicalFile as SuFile) + override fun move(destination: FtpFile): Boolean = backingFile.renameTo(destination.physicalFile as SuFile) - override fun listFiles(): MutableList = backingFile.listFiles()?.map { - RootFtpFile(it.name, it, user) - }?.toMutableList() ?: emptyList().toMutableList() + override fun listFiles(): MutableList = + backingFile.listFiles()?.map { + RootFtpFile(it.name, it, user) + }?.toMutableList() ?: emptyList().toMutableList() - override fun createOutputStream(offset: Long): OutputStream = - SuFileOutputStream.open(backingFile.absolutePath) + override fun createOutputStream(offset: Long): OutputStream = SuFileOutputStream.open(backingFile.absolutePath) - override fun createInputStream(offset: Long): InputStream = - SuFileInputStream.open(backingFile.absolutePath) + override fun createInputStream(offset: Long): InputStream = SuFileInputStream.open(backingFile.absolutePath) } diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/ftpserver/commands/AVBL.kt b/app/src/main/java/com/amaze/filemanager/filesystem/ftpserver/commands/AVBL.kt index 0e7334c124..8d5809b589 100644 --- a/app/src/main/java/com/amaze/filemanager/filesystem/ftpserver/commands/AVBL.kt +++ b/app/src/main/java/com/amaze/filemanager/filesystem/ftpserver/commands/AVBL.kt @@ -46,35 +46,39 @@ import java.io.File * See [Draft spec](https://www.ietf.org/archive/id/draft-peterson-streamlined-ftp-command-extensions-10.txt) */ class AVBL : AbstractCommand() { - companion object { private val LOG: Logger = LoggerFactory.getLogger(AVBL::class.java) } - override fun execute(session: FtpIoSession, context: FtpServerContext, request: FtpRequest) { + override fun execute( + session: FtpIoSession, + context: FtpServerContext, + request: FtpRequest, + ) { // argument check val fileName: String? = request.argument if (context.fileSystemManager is AndroidFileSystemFactory) { doWriteReply( session, REPLY_502_COMMAND_NOT_IMPLEMENTED, - "AVBL.notimplemented" + "AVBL.notimplemented", ) } else { - val ftpFile: FtpFile? = if (true == fileName?.isNotBlank()) { - runCatching { - session.fileSystemView.getFile(fileName) - }.getOrNull() - } else { - session.fileSystemView.homeDirectory - } + val ftpFile: FtpFile? = + if (true == fileName?.isNotBlank()) { + runCatching { + session.fileSystemView.getFile(fileName) + }.getOrNull() + } else { + session.fileSystemView.homeDirectory + } if (ftpFile != null) { if (session.user.authorize( if (true == fileName?.isNotBlank()) { WriteRequest(fileName) } else { WriteRequest() - } + }, ) != null || !(ftpFile.physicalFile as File).canWrite() ) { @@ -83,7 +87,7 @@ class AVBL : AbstractCommand() { runCatching { freeSpace.let { session.write( - DefaultFtpReply(REPLY_213_FILE_STATUS, it.toString()) + DefaultFtpReply(REPLY_213_FILE_STATUS, it.toString()), ) } }.onFailure { @@ -107,14 +111,14 @@ class AVBL : AbstractCommand() { private fun replyError( session: FtpIoSession, subId: String, - fileName: String? = null + fileName: String? = null, ) = doWriteReply(session, REPLY_550_REQUESTED_ACTION_NOT_TAKEN, subId, fileName) private fun doWriteReply( session: FtpIoSession, code: Int, subId: String, - fileName: String? = null + fileName: String? = null, ) { val packageName = AppConfig.getInstance().packageName val resources = AppConfig.getInstance().resources @@ -123,9 +127,9 @@ class AVBL : AbstractCommand() { code, resources.getString( resources.getIdentifier("$packageName:string/ftp_error_$subId", null, null), - fileName - ) - ) + fileName, + ), + ), ) } } diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/ftpserver/commands/FEAT.kt b/app/src/main/java/com/amaze/filemanager/filesystem/ftpserver/commands/FEAT.kt index 15ef9a814a..ee284fd11e 100644 --- a/app/src/main/java/com/amaze/filemanager/filesystem/ftpserver/commands/FEAT.kt +++ b/app/src/main/java/com/amaze/filemanager/filesystem/ftpserver/commands/FEAT.kt @@ -33,13 +33,17 @@ import org.apache.ftpserver.impl.FtpServerContext * Custom [org.apache.ftpserver.command.impl.FEAT] to add [AVBL] command to the list. */ class FEAT : AbstractCommand() { - override fun execute(session: FtpIoSession, context: FtpServerContext, request: FtpRequest) { + override fun execute( + session: FtpIoSession, + context: FtpServerContext, + request: FtpRequest, + ) { session.resetState() session.write( DefaultFtpReply( FtpReply.REPLY_211_SYSTEM_STATUS_REPLY, - AppConfig.getInstance().getString(R.string.ftp_command_FEAT) - ) + AppConfig.getInstance().getString(R.string.ftp_command_FEAT), + ), ) } } diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/ftpserver/commands/PWD.kt b/app/src/main/java/com/amaze/filemanager/filesystem/ftpserver/commands/PWD.kt index 85da3468e8..eb270d168f 100644 --- a/app/src/main/java/com/amaze/filemanager/filesystem/ftpserver/commands/PWD.kt +++ b/app/src/main/java/com/amaze/filemanager/filesystem/ftpserver/commands/PWD.kt @@ -33,17 +33,17 @@ import java.io.IOException * Monkey-patch [org.apache.ftpserver.command.impl.PWD] to prevent true path exposed to end user. */ class PWD : AbstractCommand() { - @Throws(IOException::class, FtpException::class) override fun execute( session: FtpIoSession, context: FtpServerContext, - request: FtpRequest + request: FtpRequest, ) { session.resetState() val fsView = session.fileSystemView - var currDir = fsView.workingDirectory.absolutePath - .substringAfter(fsView.homeDirectory.absolutePath) + var currDir = + fsView.workingDirectory.absolutePath + .substringAfter(fsView.homeDirectory.absolutePath) if (currDir.isEmpty()) { currDir = "/" } @@ -57,8 +57,8 @@ class PWD : AbstractCommand() { context, FtpReply.REPLY_257_PATHNAME_CREATED, "PWD", - currDir - ) + currDir, + ), ) } } diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/root/ChangeFilePermissionsCommand.kt b/app/src/main/java/com/amaze/filemanager/filesystem/root/ChangeFilePermissionsCommand.kt index 490827774d..19715c0d56 100644 --- a/app/src/main/java/com/amaze/filemanager/filesystem/root/ChangeFilePermissionsCommand.kt +++ b/app/src/main/java/com/amaze/filemanager/filesystem/root/ChangeFilePermissionsCommand.kt @@ -26,7 +26,6 @@ import com.amaze.filemanager.filesystem.root.base.IRootCommand import com.topjohnwu.superuser.Shell object ChangeFilePermissionsCommand : IRootCommand() { - private const val CHMOD_COMMAND = "chmod %s %o \"%s\"" /** @@ -41,17 +40,18 @@ object ChangeFilePermissionsCommand : IRootCommand() { filePath: String, updatedPermissions: Int, isDirectory: Boolean, - onOperationPerform: (Boolean) -> Unit + onOperationPerform: (Boolean) -> Unit, ) { val mountPoint = MountPathCommand.mountPath(filePath, MountPathCommand.READ_WRITE) val options = if (isDirectory) "-R" else "" - val command = String.format( - CHMOD_COMMAND, - options, - updatedPermissions, - RootHelper.getCommandLineString(filePath) - ) + val command = + String.format( + CHMOD_COMMAND, + options, + updatedPermissions, + RootHelper.getCommandLineString(filePath), + ) runShellCommand(command).let { result: Shell.Result -> if (result.code < 0) { diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/root/ConcatenateFileCommand.kt b/app/src/main/java/com/amaze/filemanager/filesystem/root/ConcatenateFileCommand.kt index 9578ce446d..b53d8b4910 100644 --- a/app/src/main/java/com/amaze/filemanager/filesystem/root/ConcatenateFileCommand.kt +++ b/app/src/main/java/com/amaze/filemanager/filesystem/root/ConcatenateFileCommand.kt @@ -25,16 +25,18 @@ import com.amaze.filemanager.filesystem.RootHelper import com.amaze.filemanager.filesystem.root.base.IRootCommand object ConcatenateFileCommand : IRootCommand() { - /** * Concatenates (cat) file data to destination */ @Throws(ShellNotRunningException::class) - fun concatenateFile(sourcePath: String, destinationPath: String) { + fun concatenateFile( + sourcePath: String, + destinationPath: String, + ) { val mountPoint = MountPathCommand.mountPath(destinationPath, MountPathCommand.READ_WRITE) runShellCommand( "cat \"${RootHelper.getCommandLineString(sourcePath)}\"" + - " > \"${RootHelper.getCommandLineString(destinationPath)}\"" + " > \"${RootHelper.getCommandLineString(destinationPath)}\"", ) mountPoint?.let { MountPathCommand.mountPath(it, MountPathCommand.READ_ONLY) } } diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/root/CopyFilesCommand.kt b/app/src/main/java/com/amaze/filemanager/filesystem/root/CopyFilesCommand.kt index a8598038f4..b92db336b2 100644 --- a/app/src/main/java/com/amaze/filemanager/filesystem/root/CopyFilesCommand.kt +++ b/app/src/main/java/com/amaze/filemanager/filesystem/root/CopyFilesCommand.kt @@ -26,20 +26,22 @@ import com.amaze.filemanager.filesystem.root.MountPathCommand.mountPath import com.amaze.filemanager.filesystem.root.base.IRootCommand object CopyFilesCommand : IRootCommand() { - /** * Copies files using root * @param source given source * @param destination given destination */ @Throws(ShellNotRunningException::class) - fun copyFiles(source: String, destination: String) { + fun copyFiles( + source: String, + destination: String, + ) { // remounting destination as rw val mountPoint = mountPath(destination, MountPathCommand.READ_WRITE) runShellCommand( "cp -r \"${RootHelper.getCommandLineString(source)}\" " + - "\"${RootHelper.getCommandLineString(destination)}\"" + "\"${RootHelper.getCommandLineString(destination)}\"", ) // we mounted the filesystem as rw, let's mount it back to ro diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/root/DeleteFileCommand.kt b/app/src/main/java/com/amaze/filemanager/filesystem/root/DeleteFileCommand.kt index 72385c02b8..228a2e54e5 100644 --- a/app/src/main/java/com/amaze/filemanager/filesystem/root/DeleteFileCommand.kt +++ b/app/src/main/java/com/amaze/filemanager/filesystem/root/DeleteFileCommand.kt @@ -25,7 +25,6 @@ import com.amaze.filemanager.filesystem.RootHelper import com.amaze.filemanager.filesystem.root.base.IRootCommand object DeleteFileCommand : IRootCommand() { - /** * Recursively removes a path with it's contents (if any) * @@ -34,9 +33,10 @@ object DeleteFileCommand : IRootCommand() { @Throws(ShellNotRunningException::class) fun deleteFile(path: String): Boolean { val mountPoint = MountPathCommand.mountPath(path, MountPathCommand.READ_WRITE) - val result = runShellCommandToList( - "rm -rf \"${RootHelper.getCommandLineString(path)}\"" - ) + val result = + runShellCommandToList( + "rm -rf \"${RootHelper.getCommandLineString(path)}\"", + ) mountPoint?.let { MountPathCommand.mountPath(it, MountPathCommand.READ_ONLY) } diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/root/FindFileCommand.kt b/app/src/main/java/com/amaze/filemanager/filesystem/root/FindFileCommand.kt index a9baef6a64..33ad38bb71 100644 --- a/app/src/main/java/com/amaze/filemanager/filesystem/root/FindFileCommand.kt +++ b/app/src/main/java/com/amaze/filemanager/filesystem/root/FindFileCommand.kt @@ -25,7 +25,6 @@ import com.amaze.filemanager.filesystem.RootHelper import com.amaze.filemanager.filesystem.root.base.IRootCommand object FindFileCommand : IRootCommand() { - /** * find file at given path in root * @@ -33,9 +32,10 @@ object FindFileCommand : IRootCommand() { */ @Throws(ShellNotRunningException::class) fun findFile(path: String): Boolean { - val result = runShellCommandToList( - "find \"${RootHelper.getCommandLineString(path)}\"" - ) + val result = + runShellCommandToList( + "find \"${RootHelper.getCommandLineString(path)}\"", + ) return result.isNotEmpty() } } diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/root/ListFilesCommand.kt b/app/src/main/java/com/amaze/filemanager/filesystem/root/ListFilesCommand.kt index 3b2964fc75..a3ecf77d55 100644 --- a/app/src/main/java/com/amaze/filemanager/filesystem/root/ListFilesCommand.kt +++ b/app/src/main/java/com/amaze/filemanager/filesystem/root/ListFilesCommand.kt @@ -37,7 +37,6 @@ import org.slf4j.LoggerFactory import java.io.File object ListFilesCommand : IRootCommand() { - private val log: Logger = LoggerFactory.getLogger(ListFilesCommand::class.java) /** @@ -48,7 +47,7 @@ object ListFilesCommand : IRootCommand() { root: Boolean, showHidden: Boolean, openModeCallback: (openMode: OpenMode) -> Unit, - onFileFoundCallback: (file: HybridFileParcelable) -> Unit + onFileFoundCallback: (file: HybridFileParcelable) -> Unit, ) { val mode: OpenMode if (root && FileUtils.isRunningAboveStorage(path)) { @@ -60,7 +59,7 @@ object ListFilesCommand : IRootCommand() { parseStringForHybridFile( rawFile = it, path = path, - isStat = !result.second + isStat = !result.second, ) ?.let(onFileFoundCallback) } @@ -84,13 +83,14 @@ object ListFilesCommand : IRootCommand() { */ fun getOpenMode( path: String, - root: Boolean + root: Boolean, ): OpenMode { - val mode: OpenMode = if (root && FileUtils.isRunningAboveStorage(path)) { - OpenMode.ROOT - } else { - OpenMode.FILE - } + val mode: OpenMode = + if (root && FileUtils.isRunningAboveStorage(path)) { + OpenMode.ROOT + } else { + OpenMode.FILE + } return mode } @@ -102,7 +102,7 @@ object ListFilesCommand : IRootCommand() { fun executeRootCommand( path: String, showHidden: Boolean, - retryWithLs: Boolean = false + retryWithLs: Boolean = false, ): Pair, Boolean> { try { /** @@ -111,18 +111,20 @@ object ListFilesCommand : IRootCommand() { */ var appendedPath = path val sanitizedPath = RootHelper.getCommandLineString(appendedPath) - appendedPath = when (path) { - "/" -> sanitizedPath.replace("/", "") - else -> sanitizedPath.plus("/") - } + appendedPath = + when (path) { + "/" -> sanitizedPath.replace("/", "") + else -> sanitizedPath.plus("/") + } - val command = "stat -c '%A %h %G %U %B %Y %N' " + - "$appendedPath*" + (if (showHidden) " $appendedPath.* " else "") + val command = + "stat -c '%A %h %G %U %B %Y %N' " + + "$appendedPath*" + (if (showHidden) " $appendedPath.* " else "") val enforceLegacyFileListing: Boolean = PreferenceManager.getDefaultSharedPreferences(AppConfig.getInstance()) .getBoolean( PreferencesConstants.PREFERENCE_ROOT_LEGACY_LISTING, - false + false, ) // #3476: Check current working dir, change back to / before proceeding runShellCommand("pwd").run { @@ -133,23 +135,26 @@ object ListFilesCommand : IRootCommand() { return if (!retryWithLs && !enforceLegacyFileListing) { log.info("Using stat for list parsing") Pair( - first = runShellCommandToList(command).map { - it.replace(appendedPath, "") - }, - second = enforceLegacyFileListing + first = + runShellCommandToList(command).map { + it.replace(appendedPath, "") + }, + second = enforceLegacyFileListing, ) } else { log.info("Using ls for list parsing") Pair( - first = runShellCommandToList( - "ls -l " + (if (showHidden) "-a " else "") + - "\"$sanitizedPath\"" - ), - second = if (retryWithLs) { - true - } else { - enforceLegacyFileListing - } + first = + runShellCommandToList( + "ls -l " + (if (showHidden) "-a " else "") + + "\"$sanitizedPath\"", + ), + second = + if (retryWithLs) { + true + } else { + enforceLegacyFileListing + }, ) } } catch (invalidCommand: ShellCommandInvalidException) { @@ -177,7 +182,7 @@ object ListFilesCommand : IRootCommand() { private fun getFilesList( path: String, showHidden: Boolean, - listener: (HybridFileParcelable) -> Unit + listener: (HybridFileParcelable) -> Unit, ): ArrayList { val pathFile = File(path) val files = ArrayList() @@ -192,7 +197,7 @@ object ListFilesCommand : IRootCommand() { RootHelper.parseFilePermission(currentFile), currentFile.lastModified(), size, - currentFile.isDirectory + currentFile.isDirectory, ).let { baseFile -> baseFile.name = currentFile.name baseFile.mode = OpenMode.FILE @@ -223,14 +228,18 @@ object ListFilesCommand : IRootCommand() { private fun parseStringForHybridFile( rawFile: String, path: String, - isStat: Boolean + isStat: Boolean, ): HybridFileParcelable? { return FileUtils.parseName( - if (isStat) rawFile.replace( - "('|`)".toRegex(), - "" - ) else rawFile, - isStat + if (isStat) { + rawFile.replace( + "('|`)".toRegex(), + "", + ) + } else { + rawFile + }, + isStat, )?.apply { this.mode = OpenMode.ROOT this.name = this.path diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/root/MakeDirectoryCommand.kt b/app/src/main/java/com/amaze/filemanager/filesystem/root/MakeDirectoryCommand.kt index d159b0ebeb..4382039b12 100644 --- a/app/src/main/java/com/amaze/filemanager/filesystem/root/MakeDirectoryCommand.kt +++ b/app/src/main/java/com/amaze/filemanager/filesystem/root/MakeDirectoryCommand.kt @@ -25,7 +25,6 @@ import com.amaze.filemanager.filesystem.RootHelper import com.amaze.filemanager.filesystem.root.base.IRootCommand object MakeDirectoryCommand : IRootCommand() { - /** * Creates an empty directory using root * @@ -33,7 +32,10 @@ object MakeDirectoryCommand : IRootCommand() { * @param name name of directory */ @Throws(ShellNotRunningException::class) - fun makeDirectory(path: String, name: String) { + fun makeDirectory( + path: String, + name: String, + ) { val mountPoint = MountPathCommand.mountPath(path, MountPathCommand.READ_WRITE) val filePath = "$path/$name" runShellCommand("mkdir \"${RootHelper.getCommandLineString(filePath)}\"") diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/root/MakeFileCommand.kt b/app/src/main/java/com/amaze/filemanager/filesystem/root/MakeFileCommand.kt index 2a80ce33c3..fa0efb6611 100644 --- a/app/src/main/java/com/amaze/filemanager/filesystem/root/MakeFileCommand.kt +++ b/app/src/main/java/com/amaze/filemanager/filesystem/root/MakeFileCommand.kt @@ -25,7 +25,6 @@ import com.amaze.filemanager.filesystem.RootHelper import com.amaze.filemanager.filesystem.root.base.IRootCommand object MakeFileCommand : IRootCommand() { - /** * Creates an empty file using root * diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/root/MountPathCommand.kt b/app/src/main/java/com/amaze/filemanager/filesystem/root/MountPathCommand.kt index 6f7b44f602..fd2e61e430 100644 --- a/app/src/main/java/com/amaze/filemanager/filesystem/root/MountPathCommand.kt +++ b/app/src/main/java/com/amaze/filemanager/filesystem/root/MountPathCommand.kt @@ -22,10 +22,10 @@ package com.amaze.filemanager.filesystem.root import android.os.Build import com.amaze.filemanager.fileoperations.exceptions.ShellNotRunningException +import com.amaze.filemanager.filesystem.RootHelper import com.amaze.filemanager.filesystem.root.base.IRootCommand object MountPathCommand : IRootCommand() { - const val READ_ONLY = "RO" const val READ_WRITE = "RW" @@ -38,7 +38,11 @@ object MountPathCommand : IRootCommand() { * @return String the root of mount point that was ro, and mounted to rw; null otherwise */ @Throws(ShellNotRunningException::class) - fun mountPath(path: String, operation: String): String? { + fun mountPath( + pathArg: String, + operation: String, + ): String? { + val path = RootHelper.getCommandLineString(pathArg) return when (operation) { READ_WRITE -> mountReadWrite(path) READ_ONLY -> { @@ -96,7 +100,9 @@ object MountPathCommand : IRootCommand() { return if (mountOutput.isNotEmpty()) { // command failed, and we got a reason echo'ed null - } else mountPoint + } else { + mountPoint + } } } return null diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/root/MoveFileCommand.kt b/app/src/main/java/com/amaze/filemanager/filesystem/root/MoveFileCommand.kt index 5942486efc..b3ff0a5fc9 100644 --- a/app/src/main/java/com/amaze/filemanager/filesystem/root/MoveFileCommand.kt +++ b/app/src/main/java/com/amaze/filemanager/filesystem/root/MoveFileCommand.kt @@ -25,18 +25,21 @@ import com.amaze.filemanager.filesystem.RootHelper import com.amaze.filemanager.filesystem.root.base.IRootCommand object MoveFileCommand : IRootCommand() { - /** * Move files using root * @param path source path * @param destination */ @Throws(ShellNotRunningException::class) - fun moveFile(path: String, destination: String) { + fun moveFile( + path: String, + destination: String, + ) { // remounting destination as rw val mountPoint = MountPathCommand.mountPath(destination, MountPathCommand.READ_WRITE) - val command = "mv \"${RootHelper.getCommandLineString(path)}\"" + - " \"${RootHelper.getCommandLineString(destination)}\"" + val command = + "mv \"${RootHelper.getCommandLineString(path)}\"" + + " \"${RootHelper.getCommandLineString(destination)}\"" runShellCommand(command) mountPoint?.let { MountPathCommand.mountPath(it, MountPathCommand.READ_ONLY) } } diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/root/RenameFileCommand.kt b/app/src/main/java/com/amaze/filemanager/filesystem/root/RenameFileCommand.kt index 7720277ed4..e3e0e4fe14 100644 --- a/app/src/main/java/com/amaze/filemanager/filesystem/root/RenameFileCommand.kt +++ b/app/src/main/java/com/amaze/filemanager/filesystem/root/RenameFileCommand.kt @@ -28,7 +28,6 @@ import org.slf4j.Logger import org.slf4j.LoggerFactory object RenameFileCommand : IRootCommand() { - private val log: Logger = LoggerFactory.getLogger(RenameFileCommand::class.java) /** @@ -39,10 +38,14 @@ object RenameFileCommand : IRootCommand() { * @return if rename was successful or not */ @Throws(ShellNotRunningException::class) - fun renameFile(oldPath: String, newPath: String): Boolean { + fun renameFile( + oldPath: String, + newPath: String, + ): Boolean { val mountPoint = MountPathCommand.mountPath(oldPath, MountPathCommand.READ_WRITE) - val command = "mv \"${RootHelper.getCommandLineString(oldPath)}\"" + - " \"${RootHelper.getCommandLineString(newPath)}\"" + val command = + "mv \"${RootHelper.getCommandLineString(oldPath)}\"" + + " \"${RootHelper.getCommandLineString(newPath)}\"" return try { val output = runShellCommandToList(command) mountPoint?.let { MountPathCommand.mountPath(it, MountPathCommand.READ_ONLY) } diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/root/base/IRootCommand.kt b/app/src/main/java/com/amaze/filemanager/filesystem/root/base/IRootCommand.kt index ef14cd94da..5449613ba9 100644 --- a/app/src/main/java/com/amaze/filemanager/filesystem/root/base/IRootCommand.kt +++ b/app/src/main/java/com/amaze/filemanager/filesystem/root/base/IRootCommand.kt @@ -26,7 +26,6 @@ import com.amaze.filemanager.ui.activities.MainActivity import com.topjohnwu.superuser.Shell open class IRootCommand { - /** * Runs the command and stores output in a list. The listener is set on the handler thread [ ] * [MainActivity.handlerThread] thus any code run in callback must be thread safe. Command is run diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/smb/CifsContexts.kt b/app/src/main/java/com/amaze/filemanager/filesystem/smb/CifsContexts.kt index 229cdb38b1..286beb42fb 100644 --- a/app/src/main/java/com/amaze/filemanager/filesystem/smb/CifsContexts.kt +++ b/app/src/main/java/com/amaze/filemanager/filesystem/smb/CifsContexts.kt @@ -29,21 +29,21 @@ import jcifs.CIFSException import jcifs.config.PropertyConfiguration import jcifs.context.BaseContext import jcifs.context.SingletonContext -import java.util.* +import java.util.Properties import java.util.concurrent.ConcurrentHashMap object CifsContexts { - const val SMB_URI_PREFIX = "smb://" private val TAG = CifsContexts::class.java.simpleName - private val defaultProperties: Properties = Properties().apply { - setProperty("jcifs.resolveOrder", "BCAST") - setProperty("jcifs.smb.client.responseTimeout", "30000") - setProperty("jcifs.netbios.retryTimeout", "5000") - setProperty("jcifs.netbios.cachePolicy", "-1") - } + private val defaultProperties: Properties = + Properties().apply { + setProperty("jcifs.resolveOrder", "BCAST") + setProperty("jcifs.smb.client.responseTimeout", "30000") + setProperty("jcifs.netbios.retryTimeout", "5000") + setProperty("jcifs.netbios.cachePolicy", "-1") + } private val contexts: MutableMap = ConcurrentHashMap() @@ -62,7 +62,7 @@ object CifsContexts { @JvmStatic fun createWithDisableIpcSigningCheck( basePath: String, - disableIpcSigningCheck: Boolean + disableIpcSigningCheck: Boolean, ): BaseContext { return if (disableIpcSigningCheck) { val extraProperties = Properties() @@ -74,26 +74,31 @@ object CifsContexts { } @JvmStatic - fun create(basePath: String, extraProperties: Properties?): BaseContext { - val basePathKey: String = Uri.parse(basePath).run { - val prefix = "$scheme://$authority" - val suffix = if (TextUtils.isEmpty(query)) "" else "?$query" - "$prefix$suffix" - } + fun create( + basePath: String, + extraProperties: Properties?, + ): BaseContext { + val basePathKey: String = + Uri.parse(basePath).run { + val prefix = "$scheme://$authority" + val suffix = if (TextUtils.isEmpty(query)) "" else "?$query" + "$prefix$suffix" + } return if (contexts.containsKey(basePathKey)) { contexts.getValue(basePathKey) } else { - val context = Single.fromCallable { - try { - val p = Properties(defaultProperties) - if (extraProperties != null) p.putAll(extraProperties) - BaseContext(PropertyConfiguration(p)) - } catch (e: CIFSException) { - Log.e(TAG, "Error initialize jcifs BaseContext, returning default", e) - SingletonContext.getInstance() - } - }.subscribeOn(Schedulers.io()) - .blockingGet() + val context = + Single.fromCallable { + try { + val p = Properties(defaultProperties) + if (extraProperties != null) p.putAll(extraProperties) + BaseContext(PropertyConfiguration(p)) + } catch (e: CIFSException) { + Log.e(TAG, "Error initialize jcifs BaseContext, returning default", e) + SingletonContext.getInstance() + } + }.subscribeOn(Schedulers.io()) + .blockingGet() contexts[basePathKey] = context context } diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/ssh/CustomSshJConfig.kt b/app/src/main/java/com/amaze/filemanager/filesystem/ssh/CustomSshJConfig.kt index 87e3849930..285dbfe778 100644 --- a/app/src/main/java/com/amaze/filemanager/filesystem/ssh/CustomSshJConfig.kt +++ b/app/src/main/java/com/amaze/filemanager/filesystem/ssh/CustomSshJConfig.kt @@ -36,7 +36,6 @@ import java.security.Security * @see net.schmizz.sshj.AndroidConfig */ class CustomSshJConfig : DefaultConfig() { - companion object { /** * This is where we different from the original AndroidConfig. Found it only work if we remove diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/ssh/SFTPClientExt.kt b/app/src/main/java/com/amaze/filemanager/filesystem/ssh/SFTPClientExt.kt new file mode 100644 index 0000000000..7241f3b444 --- /dev/null +++ b/app/src/main/java/com/amaze/filemanager/filesystem/ssh/SFTPClientExt.kt @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2014-2023 Arpit Khurana , Vishal Nehra , + * Emmanuel Messulam, Raymond Lai and Contributors. + * + * This file is part of Amaze File Manager. + * + * Amaze File Manager is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.amaze.filemanager.filesystem.ssh + +import net.schmizz.sshj.sftp.FileAttributes +import net.schmizz.sshj.sftp.OpenMode +import net.schmizz.sshj.sftp.PacketType +import net.schmizz.sshj.sftp.RemoteFile +import net.schmizz.sshj.sftp.SFTPClient +import net.schmizz.sshj.sftp.SFTPEngine +import java.io.IOException +import java.util.EnumSet +import java.util.concurrent.TimeUnit + +const val READ_AHEAD_MAX_UNCONFIRMED_READS: Int = 16 + +/** + * Monkey-patch [SFTPEngine.open] until sshj adds back read ahead support in [RemoteFile]. + */ +@Throws(IOException::class) +fun SFTPEngine.openWithReadAheadSupport( + path: String, + modes: Set, + fa: FileAttributes, +): RemoteFile { + val handle: ByteArray = + request( + newRequest(PacketType.OPEN).putString(path, subsystem.remoteCharset) + .putUInt32(OpenMode.toMask(modes).toLong()).putFileAttributes(fa), + ).retrieve(timeoutMs.toLong(), TimeUnit.MILLISECONDS) + .ensurePacketTypeIs(PacketType.HANDLE).readBytes() + return RemoteFile(this, path, handle) +} + +/** + * Monkey-patch [SFTPClient.open] until sshj adds back read ahead support in [RemoteFile]. + */ +fun SFTPClient.openWithReadAheadSupport(path: String): RemoteFile { + return sftpEngine.openWithReadAheadSupport( + path, + EnumSet.of(OpenMode.READ), + FileAttributes.EMPTY, + ) +} diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/ssh/SFtpClientTemplate.kt b/app/src/main/java/com/amaze/filemanager/filesystem/ssh/SFtpClientTemplate.kt index 046929bdd1..d46d99d27f 100644 --- a/app/src/main/java/com/amaze/filemanager/filesystem/ssh/SFtpClientTemplate.kt +++ b/app/src/main/java/com/amaze/filemanager/filesystem/ssh/SFtpClientTemplate.kt @@ -32,7 +32,6 @@ import java.io.IOException */ abstract class SFtpClientTemplate(url: String, closeClientOnFinish: Boolean = true) : SshClientTemplate(url, closeClientOnFinish) { - private val LOG: Logger = LoggerFactory.getLogger(javaClass) override fun executeWithSSHClient(sshClient: SSHClient): T? { diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/ssh/SshClientSessionTemplate.kt b/app/src/main/java/com/amaze/filemanager/filesystem/ssh/SshClientSessionTemplate.kt index 87f78b0706..d07c7d7303 100644 --- a/app/src/main/java/com/amaze/filemanager/filesystem/ssh/SshClientSessionTemplate.kt +++ b/app/src/main/java/com/amaze/filemanager/filesystem/ssh/SshClientSessionTemplate.kt @@ -23,16 +23,15 @@ package com.amaze.filemanager.filesystem.ssh import net.schmizz.sshj.connection.channel.direct.Session import java.io.IOException -abstract class SshClientSessionTemplate -/** +abstract class SshClientSessionTemplate/** * Constructor. * * @param url SSH connection URL, in the form of ` * ssh://:@:` or ` * ssh://@:` - */ -(@JvmField val url: String) { - + */( + @JvmField val url: String, +) { /** * Implement logic here. * diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/ssh/SshClientTemplate.kt b/app/src/main/java/com/amaze/filemanager/filesystem/ssh/SshClientTemplate.kt index 2d56ac0be3..41d695a613 100644 --- a/app/src/main/java/com/amaze/filemanager/filesystem/ssh/SshClientTemplate.kt +++ b/app/src/main/java/com/amaze/filemanager/filesystem/ssh/SshClientTemplate.kt @@ -31,7 +31,6 @@ import java.io.IOException */ abstract class SshClientTemplate(url: String, closeClientOnFinish: Boolean = true) : NetCopyClientTemplate(url, closeClientOnFinish) { - @Throws(IOException::class) final override fun execute(client: NetCopyClient): T? { val sshClient: SSHClient = client.getClientImpl() diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/ssh/SshClientUtils.java b/app/src/main/java/com/amaze/filemanager/filesystem/ssh/SshClientUtils.java deleted file mode 100644 index 8af4363fa0..0000000000 --- a/app/src/main/java/com/amaze/filemanager/filesystem/ssh/SshClientUtils.java +++ /dev/null @@ -1,227 +0,0 @@ -/* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , - * Emmanuel Messulam, Raymond Lai and Contributors. - * - * This file is part of Amaze File Manager. - * - * Amaze File Manager is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.amaze.filemanager.filesystem.ssh; - -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.amaze.filemanager.R; -import com.amaze.filemanager.fileoperations.filesystem.cloud.CloudStreamer; -import com.amaze.filemanager.filesystem.HybridFile; -import com.amaze.filemanager.filesystem.ftp.NetCopyClientUtils; -import com.amaze.filemanager.ui.activities.MainActivity; -import com.amaze.filemanager.ui.icons.MimeTypes; - -import android.content.ActivityNotFoundException; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; -import android.net.Uri; -import android.widget.Toast; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import net.schmizz.sshj.SSHClient; -import net.schmizz.sshj.connection.channel.direct.Session; -import net.schmizz.sshj.sftp.FileAttributes; -import net.schmizz.sshj.sftp.FileMode; -import net.schmizz.sshj.sftp.RemoteResourceInfo; -import net.schmizz.sshj.sftp.SFTPClient; - -public abstract class SshClientUtils { - - private static final Logger LOG = LoggerFactory.getLogger(SshClientUtils.class); - - /** - * Execute the given template with SshClientTemplate. - * - * @param template {@link SshClientSessionTemplate} to execute - * @param Type of return value - * @return Template execution results - */ - public static T execute(@NonNull final SshClientSessionTemplate template) { - return NetCopyClientUtils.INSTANCE.execute( - new SshClientTemplate(template.url, false) { - @Override - public T executeWithSSHClient(@NonNull SSHClient sshClient) { - Session session = null; - T retval = null; - try { - session = sshClient.startSession(); - retval = template.execute(session); - } catch (IOException e) { - LOG.error("Error executing template method", e); - } finally { - if (session != null && session.isOpen()) { - try { - session.close(); - } catch (IOException e) { - LOG.warn("Error closing SFTP client", e); - } - } - } - return retval; - } - }); - } - - /** - * Execute the given template with SshClientTemplate. - * - * @param template {@link SFtpClientTemplate} to execute - * @param Type of return value - * @return Template execution results - */ - @Nullable - public static T execute(@NonNull final SFtpClientTemplate template) { - final SshClientTemplate ftpClientTemplate = - new SshClientTemplate(template.url, false) { - @Override - @Nullable - public T executeWithSSHClient(SSHClient sshClient) { - SFTPClient sftpClient = null; - T retval = null; - try { - sftpClient = sshClient.newSFTPClient(); - retval = template.execute(sftpClient); - } catch (IOException e) { - LOG.error("Error executing template method", e); - } finally { - if (sftpClient != null && template.closeClientOnFinish) { - try { - sftpClient.close(); - } catch (IOException e) { - LOG.warn("Error closing SFTP client", e); - } - } - } - return retval; - } - }; - - return NetCopyClientUtils.INSTANCE.execute(ftpClientTemplate); - } - - /** - * Converts plain path smb://127.0.0.1/test.pdf to authorized path - * smb://test:123@127.0.0.1/test.pdf from server list - * - * @param path - * @return - */ - public static String formatPlainServerPathToAuthorised(ArrayList servers, String path) { - for (String[] serverEntry : servers) { - Uri inputUri = Uri.parse(path); - Uri serverUri = Uri.parse(serverEntry[1]); - if (inputUri.getScheme().equalsIgnoreCase(serverUri.getScheme()) - && serverUri.getAuthority().contains(inputUri.getAuthority())) { - String output = - inputUri - .buildUpon() - .encodedAuthority(serverUri.getEncodedAuthority()) - .build() - .toString(); - LOG.info("build authorised path {} from plain path {}", output, path); - return output; - } - } - return path; - } - - /** - * Disconnects the given {@link SSHClient} but wrap all exceptions beneath, so callers are free - * from the hassles of handling thrown exceptions. - * - * @param client {@link SSHClient} instance - */ - public static void tryDisconnect(SSHClient client) { - if (client != null && client.isConnected()) { - try { - client.disconnect(); - } catch (IOException e) { - LOG.warn("Error closing SSHClient connection", e); - } - } - } - - public static void launchFtp(final HybridFile baseFile, final MainActivity activity) { - final CloudStreamer streamer = CloudStreamer.getInstance(); - - new Thread( - () -> { - try { - boolean isDirectory = baseFile.isDirectory(activity); - long fileLength = baseFile.length(activity); - streamer.setStreamSrc( - baseFile.getInputStream(activity), baseFile.getName(activity), fileLength); - activity.runOnUiThread( - () -> { - try { - File file = - new File( - NetCopyClientUtils.INSTANCE.extractRemotePathFrom( - baseFile.getPath())); - Uri uri = - Uri.parse(CloudStreamer.URL + Uri.fromFile(file).getEncodedPath()); - Intent i = new Intent(Intent.ACTION_VIEW); - i.setDataAndType( - uri, MimeTypes.getMimeType(baseFile.getPath(), isDirectory)); - PackageManager packageManager = activity.getPackageManager(); - List resInfos = packageManager.queryIntentActivities(i, 0); - if (resInfos != null && resInfos.size() > 0) activity.startActivity(i); - else - Toast.makeText( - activity, - activity.getResources().getString(R.string.smb_launch_error), - Toast.LENGTH_SHORT) - .show(); - } catch (ActivityNotFoundException e) { - LOG.warn("failed to launch sftp file", e); - } - }); - } catch (Exception e) { - LOG.warn("failed to launch sftp file", e); - } - }) - .start(); - } - - public static boolean isDirectory(@NonNull SFTPClient client, @NonNull RemoteResourceInfo info) - throws IOException { - boolean isDirectory = info.isDirectory(); - if (info.getAttributes().getType().equals(FileMode.Type.SYMLINK)) { - try { - FileAttributes symlinkAttrs = client.stat(info.getPath()); - isDirectory = symlinkAttrs.getType().equals(FileMode.Type.DIRECTORY); - } catch (IOException ifSymlinkIsBroken) { - LOG.warn("Symbolic link {} is broken, skipping", info.getPath()); - throw ifSymlinkIsBroken; - } - } - return isDirectory; - } -} diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/ssh/SshClientUtils.kt b/app/src/main/java/com/amaze/filemanager/filesystem/ssh/SshClientUtils.kt new file mode 100644 index 0000000000..b70ab11125 --- /dev/null +++ b/app/src/main/java/com/amaze/filemanager/filesystem/ssh/SshClientUtils.kt @@ -0,0 +1,231 @@ +/* + * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Emmanuel Messulam, Raymond Lai and Contributors. + * + * This file is part of Amaze File Manager. + * + * Amaze File Manager is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.amaze.filemanager.filesystem.ssh + +import android.content.ActivityNotFoundException +import android.content.Intent +import android.net.Uri +import android.widget.Toast +import com.amaze.filemanager.R +import com.amaze.filemanager.fileoperations.filesystem.cloud.CloudStreamer +import com.amaze.filemanager.filesystem.HybridFile +import com.amaze.filemanager.filesystem.ftp.NetCopyClientUtils +import com.amaze.filemanager.filesystem.ftp.NetCopyClientUtils.extractRemotePathFrom +import com.amaze.filemanager.ui.activities.MainActivity +import com.amaze.filemanager.ui.icons.MimeTypes +import net.schmizz.sshj.SSHClient +import net.schmizz.sshj.connection.channel.direct.Session +import net.schmizz.sshj.sftp.FileMode +import net.schmizz.sshj.sftp.RemoteResourceInfo +import net.schmizz.sshj.sftp.SFTPClient +import org.slf4j.LoggerFactory +import java.io.File +import java.io.IOException +import kotlin.concurrent.thread + +object SshClientUtils { + @JvmStatic + private val LOG = LoggerFactory.getLogger(SshClientUtils::class.java) + + @JvmField + val sftpGetSize: (String) -> Long? = { path -> + NetCopyClientUtils.execute( + object : SFtpClientTemplate(path, true) { + override fun execute(client: SFTPClient): Long { + return client.size(extractRemotePathFrom(path)) + } + }, + ) + } + + /** + * Execute the given template with SshClientTemplate. + * + * @param template [SshClientSessionTemplate] to execute + * @param Type of return value + * @return Template execution results + */ + @JvmStatic + fun execute(template: SshClientSessionTemplate): T? { + return NetCopyClientUtils.execute( + object : SshClientTemplate(template.url, false) { + override fun executeWithSSHClient(sshClient: SSHClient): T? { + var session: Session? = null + var retval: T? = null + try { + session = sshClient.startSession() + retval = template.execute(session) + } catch (e: IOException) { + LOG.error("Error executing template method", e) + } finally { + if (session != null && session.isOpen) { + try { + session.close() + } catch (e: IOException) { + LOG.warn("Error closing SFTP client", e) + } + } + } + return retval + } + }, + ) + } + + /** + * Execute the given template with SshClientTemplate. + * + * @param template [SFtpClientTemplate] to execute + * @param Type of return value + * @return Template execution results + */ + @JvmStatic + fun execute(template: SFtpClientTemplate): T? { + return NetCopyClientUtils.execute(template) + } + + /** + * Converts plain path smb://127.0.0.1/test.pdf to authorized path + * smb://test:123@127.0.0.1/test.pdf from server list + * + * @param path + * @return + */ + @JvmStatic + fun formatPlainServerPathToAuthorised( + servers: ArrayList>, + path: String, + ): String { + for (serverEntry in servers) { + val inputUri = Uri.parse(path) + val serverUri = Uri.parse(serverEntry[1]) + if (inputUri.scheme.equals(serverUri.scheme, ignoreCase = true) && + serverUri.authority!!.contains(inputUri.authority!!) + ) { + val output = + inputUri + .buildUpon() + .encodedAuthority(serverUri.encodedAuthority) + .build() + .toString() + LOG.info("build authorised path {} from plain path {}", output, path) + return output + } + } + return path + } + + /** + * Disconnects the given [SSHClient] but wrap all exceptions beneath, so callers are free + * from the hassles of handling thrown exceptions. + * + * @param client [SSHClient] instance + */ + fun tryDisconnect(client: SSHClient?) { + if (client != null && client.isConnected) { + try { + client.disconnect() + } catch (e: IOException) { + LOG.warn("Error closing SSHClient connection", e) + } + } + } + + /** + * Open a remote SSH file on local Android device. It uses the [CloudStreamer] to stream the + * file. + */ + @JvmStatic + @Suppress("Detekt.TooGenericExceptionCaught") + fun launchFtp( + baseFile: HybridFile, + activity: MainActivity, + ) { + val streamer = CloudStreamer.getInstance() + thread { + try { + val isDirectory = baseFile.isDirectory(activity) + val fileLength = baseFile.length(activity) + streamer.setStreamSrc( + baseFile.getInputStream(activity), + baseFile.getName(activity), + fileLength, + ) + activity.runOnUiThread { + try { + val file = + File( + extractRemotePathFrom( + baseFile.path, + ), + ) + val uri = Uri.parse(CloudStreamer.URL + Uri.fromFile(file).encodedPath) + val i = Intent(Intent.ACTION_VIEW) + i.setDataAndType( + uri, + MimeTypes.getMimeType(baseFile.path, isDirectory), + ) + val packageManager = activity.packageManager + val resInfos = packageManager.queryIntentActivities(i, 0) + if (resInfos != null && resInfos.size > 0) { + activity.startActivity(i) + } else { + Toast.makeText( + activity, + activity.resources.getString(R.string.smb_launch_error), + Toast.LENGTH_SHORT, + ) + .show() + } + } catch (e: ActivityNotFoundException) { + LOG.warn("failed to launch sftp file", e) + } + } + } catch (e: Exception) { + LOG.warn("failed to launch sftp file", e) + } + } + } + + /** + * Reads given [RemoteResourceInfo] and determines if the path it's related to is a directory. + * + * Will descend into corresponding target if given RemoteResourceInfo represents a symlink. + */ + @JvmStatic + @Throws(IOException::class) + fun isDirectory( + client: SFTPClient, + info: RemoteResourceInfo, + ): Boolean { + var isDirectory = info.isDirectory + if (info.attributes.type == FileMode.Type.SYMLINK) { + try { + val symlinkAttrs = client.stat(info.path) + isDirectory = symlinkAttrs.type == FileMode.Type.DIRECTORY + } catch (ifSymlinkIsBroken: IOException) { + LOG.warn("Symbolic link {} is broken, skipping", info.path) + throw ifSymlinkIsBroken + } + } + return isDirectory + } +} diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/ssh/Statvfs.java b/app/src/main/java/com/amaze/filemanager/filesystem/ssh/Statvfs.java index a4eef976b8..3f60c32cbb 100644 --- a/app/src/main/java/com/amaze/filemanager/filesystem/ssh/Statvfs.java +++ b/app/src/main/java/com/amaze/filemanager/filesystem/ssh/Statvfs.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. diff --git a/app/src/main/java/com/amaze/filemanager/ui/ColorCircleDrawable.java b/app/src/main/java/com/amaze/filemanager/ui/ColorCircleDrawable.java index dec8bb829e..b7a23342ef 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/ColorCircleDrawable.java +++ b/app/src/main/java/com/amaze/filemanager/ui/ColorCircleDrawable.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. diff --git a/app/src/main/java/com/amaze/filemanager/ui/Extensions.kt b/app/src/main/java/com/amaze/filemanager/ui/Extensions.kt index 8b2b0f8013..eced7e276c 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/Extensions.kt +++ b/app/src/main/java/com/amaze/filemanager/ui/Extensions.kt @@ -27,8 +27,8 @@ import android.content.pm.PackageManager import android.text.TextUtils import android.view.View import android.view.inputmethod.InputMethodManager -import android.widget.EditText import android.widget.Toast +import androidx.appcompat.widget.AppCompatEditText import com.amaze.filemanager.R import com.amaze.filemanager.application.AppConfig import com.google.android.material.textfield.TextInputLayout @@ -68,13 +68,13 @@ fun Context.updateAUAlias(shouldEnable: Boolean) { packageManager.setComponentEnabledSetting( component, PackageManager.COMPONENT_ENABLED_STATE_DISABLED, - PackageManager.DONT_KILL_APP + PackageManager.DONT_KILL_APP, ) } else { packageManager.setComponentEnabledSetting( component, PackageManager.COMPONENT_ENABLED_STATE_ENABLED, - PackageManager.DONT_KILL_APP + PackageManager.DONT_KILL_APP, ) } } @@ -82,7 +82,7 @@ fun Context.updateAUAlias(shouldEnable: Boolean) { /** * Force keyboard pop up on focus */ -fun EditText.openKeyboard(context: Context) { +fun AppCompatEditText.openKeyboard(context: Context) { this.requestFocus() this.postDelayed( @@ -90,10 +90,10 @@ fun EditText.openKeyboard(context: Context) { (context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager) .showSoftInput( this, - InputMethodManager.SHOW_IMPLICIT + InputMethodManager.SHOW_IMPLICIT, ) }, - 100 + 100, ) } @@ -116,7 +116,10 @@ fun View.showFade(duration: Long) { /** * Extension function to check for activity in package manager before triggering code */ -fun Intent.runIfDocumentsUIExists(context: Context, callback: Runnable) { +fun Intent.runIfDocumentsUIExists( + context: Context, + callback: Runnable, +) { if (this.resolveActivity(context.packageManager) != null) { callback.run() } else { diff --git a/app/src/main/java/com/amaze/filemanager/ui/ItemPopupMenu.java b/app/src/main/java/com/amaze/filemanager/ui/ItemPopupMenu.java index 1785f87d5a..2320ddb3c4 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/ItemPopupMenu.java +++ b/app/src/main/java/com/amaze/filemanager/ui/ItemPopupMenu.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. @@ -160,6 +160,12 @@ public boolean onMenuItemClick(MenuItem item) { GeneralDialogCreation.deleteFilesDialog( context, mainActivity, positions, utilitiesProvider.getAppTheme()); return true; + case R.id.restore: + ArrayList p2 = new ArrayList<>(); + p2.add(rowItem); + GeneralDialogCreation.restoreFilesDialog( + context, mainActivity, p2, utilitiesProvider.getAppTheme()); + return true; case R.id.open_with: boolean useNewStack = sharedPrefs.getBoolean(PreferencesConstants.PREFERENCE_TEXTEDITOR_NEWSTACK, false); @@ -241,8 +247,14 @@ public void onButtonPressed(Intent intent, String password) utilitiesProvider, false); return true; + case R.id.compress: + GeneralDialogCreation.showCompressDialog( + mainActivity, + rowItem.generateBaseFile(), + mainActivity.getCurrentMainFragment().getMainFragmentViewModel().getCurrentPath()); + return true; case R.id.return_select: - mainFragment.returnIntentResults(rowItem.generateBaseFile()); + mainFragment.returnIntentResults(new HybridFileParcelable[] {rowItem.generateBaseFile()}); return true; } return false; diff --git a/app/src/main/java/com/amaze/filemanager/ui/activities/AboutActivity.java b/app/src/main/java/com/amaze/filemanager/ui/activities/AboutActivity.java index 5bd40d7128..e74c7f46e9 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/activities/AboutActivity.java +++ b/app/src/main/java/com/amaze/filemanager/ui/activities/AboutActivity.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. @@ -49,9 +49,9 @@ import android.os.Bundle; import android.view.MenuItem; import android.view.View; -import android.widget.TextView; import androidx.annotation.Nullable; +import androidx.appcompat.widget.AppCompatTextView; import androidx.appcompat.widget.Toolbar; import androidx.coordinatorlayout.widget.CoordinatorLayout; import androidx.core.content.FileProvider; @@ -66,14 +66,15 @@ public class AboutActivity extends ThemedActivity implements View.OnClickListene private AppBarLayout mAppBarLayout; private CollapsingToolbarLayout mCollapsingToolbarLayout; - private TextView mTitleTextView; - private View mAuthorsDivider, mDeveloper1Divider; + private AppCompatTextView mTitleTextView; + private View mAuthorsDivider, mDeveloper1Divider, mDeveloper2Divider; private Billing billing; private static final String URL_AUTHOR1_GITHUB = "https://github.com/arpitkh96"; private static final String URL_AUTHOR2_GITHUB = "https://github.com/VishalNehra"; private static final String URL_DEVELOPER1_GITHUB = "https://github.com/EmmanuelMess"; private static final String URL_DEVELOPER2_GITHUB = "https://github.com/TranceLove"; + private static final String URL_DEVELOPER3_GITHUB = "https://github.com/VishnuSanal"; private static final String URL_REPO_CHANGELOG = "https://github.com/TeamAmaze/AmazeFileManager/commits/master"; private static final String URL_REPO = "https://github.com/TeamAmaze/AmazeFileManager"; @@ -108,6 +109,7 @@ public void onCreate(@Nullable Bundle savedInstanceState) { mTitleTextView = findViewById(R.id.text_view_title); mAuthorsDivider = findViewById(R.id.view_divider_authors); mDeveloper1Divider = findViewById(R.id.view_divider_developers_1); + mDeveloper2Divider = findViewById(R.id.view_divider_developers_2); mAppBarLayout.setLayoutParams(calculateHeaderViewParams()); @@ -200,6 +202,7 @@ private void switchIcons() { // dark theme mAuthorsDivider.setBackgroundColor(Utils.getColor(this, R.color.divider_dark_card)); mDeveloper1Divider.setBackgroundColor(Utils.getColor(this, R.color.divider_dark_card)); + mDeveloper2Divider.setBackgroundColor(Utils.getColor(this, R.color.divider_dark_card)); } } @@ -216,14 +219,15 @@ public void onClick(View v) { case R.id.relative_layout_share_logs: try { + File logFile = + new File( + "/data/data/" + getApplicationContext().getPackageName() + "/cache/logs.txt"); Uri logUri = FileProvider.getUriForFile( - this, - this.getPackageName(), - new File(String.format("/data/data/%s/cache/logs.txt", getPackageName()))); + getApplicationContext(), getApplicationContext().getPackageName(), logFile); ArrayList logUriList = new ArrayList<>(); logUriList.add(logUri); - new ShareTask(this, logUriList, this.getAppTheme(), getAccent()).execute("text/plain"); + new ShareTask(this, logUriList, this.getAppTheme(), getAccent()).execute("*/*"); } catch (Exception e) { LOG.warn("failed to share logs", e); } @@ -246,7 +250,7 @@ public void onClick(View v) { .withAboutSpecial1Description(getString(R.string.amaze_license)) .withLicenseShown(true); - switch (getAppTheme().getSimpleTheme(this)) { + switch (getAppTheme()) { case LIGHT: libsBuilder.withActivityStyle(Libs.ActivityStyle.LIGHT_DARK_TOOLBAR); break; @@ -280,6 +284,10 @@ public void onClick(View v) { openURL(URL_DEVELOPER2_GITHUB, this); break; + case R.id.text_view_developer_3_github: + openURL(URL_DEVELOPER3_GITHUB, this); + break; + case R.id.relative_layout_translate: openURL(URL_REPO_TRANSLATE, this); break; diff --git a/app/src/main/java/com/amaze/filemanager/ui/activities/DatabaseViewerActivity.java b/app/src/main/java/com/amaze/filemanager/ui/activities/DatabaseViewerActivity.java index 1ed65998a8..23c464bc02 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/activities/DatabaseViewerActivity.java +++ b/app/src/main/java/com/amaze/filemanager/ui/activities/DatabaseViewerActivity.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. diff --git a/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivity.java b/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivity.java index 04cd312614..fe7dec1436 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivity.java +++ b/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivity.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. @@ -38,6 +38,11 @@ import static com.amaze.filemanager.fileoperations.filesystem.OperationTypeKt.RENAME; import static com.amaze.filemanager.fileoperations.filesystem.OperationTypeKt.SAVE_FILE; import static com.amaze.filemanager.fileoperations.filesystem.OperationTypeKt.UNDEFINED; +import static com.amaze.filemanager.filesystem.ftp.FTPClientImpl.ARG_TLS; +import static com.amaze.filemanager.filesystem.ftp.FTPClientImpl.TLS_EXPLICIT; +import static com.amaze.filemanager.filesystem.ftp.NetCopyClientConnectionPool.FTPS_URI_PREFIX; +import static com.amaze.filemanager.filesystem.ftp.NetCopyClientConnectionPool.FTP_URI_PREFIX; +import static com.amaze.filemanager.filesystem.ftp.NetCopyClientConnectionPool.SSH_URI_PREFIX; import static com.amaze.filemanager.ui.dialogs.SftpConnectDialog.ARG_ADDRESS; import static com.amaze.filemanager.ui.dialogs.SftpConnectDialog.ARG_DEFAULT_PATH; import static com.amaze.filemanager.ui.dialogs.SftpConnectDialog.ARG_EDIT; @@ -71,6 +76,7 @@ import com.amaze.filemanager.BuildConfig; import com.amaze.filemanager.LogHelper; import com.amaze.filemanager.R; +import com.amaze.filemanager.adapters.data.LayoutElementParcelable; import com.amaze.filemanager.adapters.data.StorageDirectoryParcelable; import com.amaze.filemanager.application.AppConfig; import com.amaze.filemanager.asynchronous.SaveOnDataUtilsChange; @@ -122,8 +128,8 @@ import com.amaze.filemanager.ui.fragments.FtpServerFragment; import com.amaze.filemanager.ui.fragments.MainFragment; import com.amaze.filemanager.ui.fragments.ProcessViewerFragment; -import com.amaze.filemanager.ui.fragments.SearchWorkerFragment; import com.amaze.filemanager.ui.fragments.TabFragment; +import com.amaze.filemanager.ui.fragments.data.MainFragmentViewModel; import com.amaze.filemanager.ui.fragments.preferencefragments.PreferencesConstants; import com.amaze.filemanager.ui.strings.StorageNamingHelper; import com.amaze.filemanager.ui.theme.AppTheme; @@ -142,7 +148,6 @@ import com.amaze.filemanager.utils.Utils; import com.cloudrail.si.CloudRail; import com.google.android.material.appbar.AppBarLayout; -import com.google.android.material.bottomsheet.BottomSheetDialogFragment; import com.google.android.material.snackbar.BaseTransientBottomBar; import com.google.android.material.snackbar.Snackbar; import com.leinardi.android.speeddial.FabWithLabelView; @@ -212,7 +217,6 @@ public class MainActivity extends PermissionsActivity implements SmbConnectionListener, BookmarkCallback, - SearchWorkerFragment.HelperCallbacks, CloudConnectionCallbacks, LoaderManager.LoaderCallbacks, FolderChooserDialog.FolderCallback, @@ -233,6 +237,8 @@ public class MainActivity extends PermissionsActivity private SpeedDialView floatingActionButton; + private SpeedDialView fabConfirmSelection; + public MainActivityHelper mainActivityHelper; public int operation = -1; @@ -247,7 +253,7 @@ public class MainActivity extends PermissionsActivity public ArrayList oppatheList; // This holds the Uris to be written at initFabToSave() - private ArrayList urisToBeSaved; + private List urisToBeSaved; public static final String PASTEHELPER_BUNDLE = "pasteHelper"; @@ -276,6 +282,7 @@ public class MainActivity extends PermissionsActivity private UtilsHandler utilsHandler; private CloudHandler cloudHandler; private CloudLoaderAsyncTask cloudLoaderAsyncTask; + /** * This is for a hack. * @@ -324,7 +331,7 @@ public class MainActivity extends PermissionsActivity public static final int REQUEST_CODE_CLOUD_LIST_KEY = 5472; private PasteHelper pasteHelper; - private MainActivityActionMode mainActivityActionMode; + public MainActivityActionMode mainActivityActionMode; private static final String DEFAULT_FALLBACK_STORAGE_PATH = "/storage/sdcard0"; private static final String INTERNAL_SHARED_STORAGE = "Internal shared storage"; @@ -393,6 +400,8 @@ public void onCreate(final Bundle savedInstanceState) { checkForExternalIntent(intent); + initialiseFabConfirmSelection(); + drawer.setDrawerIndicatorEnabled(); if (!getBoolean(PREFERENCE_BOOKMARKS_ADDED)) { @@ -535,7 +544,7 @@ public void onPermissionGranted() { .subscribe( () -> { if (tabFragment != null) { - tabFragment.refactorDrawerStorages(false); + tabFragment.refactorDrawerStorages(false, false); Fragment main = tabFragment.getFragmentAtIndex(0); if (main != null) ((MainFragment) main).updateTabWithDb(tabHandler.findTab(1)); Fragment main1 = tabFragment.getFragmentAtIndex(1); @@ -555,12 +564,16 @@ public void onPermissionGranted() { } private void checkForExternalPermission() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + if (SDK_INT >= Build.VERSION_CODES.M) { if (!checkStoragePermission()) { - requestStoragePermission(this, true); + if (SDK_INT >= Build.VERSION_CODES.R) { + requestAllFilesAccess(this); + } else { + requestStoragePermission(this, true); + } } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - requestAllFilesAccess(this); + if (SDK_INT >= Build.VERSION_CODES.TIRAMISU && !checkNotificationPermission()) { + requestNotificationPermission(true); } } } @@ -577,7 +590,11 @@ private void checkForExternalIntent(Intent intent) { if (actionIntent.equals(Intent.ACTION_GET_CONTENT)) { // file picker intent mReturnIntent = true; - Toast.makeText(this, getString(R.string.pick_a_file), Toast.LENGTH_LONG).show(); + String text = + intent.getBooleanExtra(Intent.EXTRA_ALLOW_MULTIPLE, false) + ? getString(R.string.pick_files) + : getString(R.string.pick_a_file); + Toast.makeText(this, text, Toast.LENGTH_LONG).show(); // disable screen rotation just for convenience purpose // TODO: Support screen rotation when picking file @@ -630,9 +647,15 @@ private void checkForExternalIntent(Intent intent) { } else { // save a single file to filesystem Uri uri = intent.getParcelableExtra(Intent.EXTRA_STREAM); - ArrayList uris = new ArrayList<>(); - uris.add(uri); - initFabToSave(uris); + if (uri != null + && uri.getScheme() != null + && uri.getScheme().startsWith(ContentResolver.SCHEME_FILE)) { + ArrayList uris = new ArrayList<>(); + uris.add(uri); + initFabToSave(uris); + } else { + Toast.makeText(this, R.string.error_unsupported_or_null_uri, Toast.LENGTH_LONG).show(); + } } // disable screen rotation just for convenience purpose // TODO: Support screen rotation when saving a file @@ -651,7 +674,7 @@ private void checkForExternalIntent(Intent intent) { } /** Initializes the floating action button to act as to save data from an external intent */ - private void initFabToSave(final ArrayList uris) { + private void initFabToSave(final List uris) { Utils.showThemedSnackbar( this, getString(R.string.select_save_location), @@ -660,7 +683,7 @@ private void initFabToSave(final ArrayList uris) { () -> saveExternalIntent(uris)); } - private void saveExternalIntent(final ArrayList uris) { + private void saveExternalIntent(final List uris) { executeWithMainFragment( mainFragment -> { if (uris != null && uris.size() > 0) { @@ -931,7 +954,7 @@ public void onBackPressed() { fragmentTransaction.remove(compressedExplorerFragment); fragmentTransaction.commit(); supportInvalidateOptionsMenu(); - floatingActionButton.show(); + showFab(); } } else { compressedExplorerFragment.mActionMode.finish(); @@ -982,6 +1005,16 @@ public void exit() { } public void goToMain(String path) { + goToMain(path, false); + } + + /** + * Sets up the main view with a {@link MainFragment} + * + * @param path The path to which to go in the {@link MainFragment} + * @param hideFab Whether the FAB should be hidden in the new created {@link MainFragment} or not + */ + public void goToMain(String path, boolean hideFab) { FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); // title.setText(R.string.app_name); TabFragment tabFragment = new TabFragment(); @@ -992,17 +1025,19 @@ public void goToMain(String path) { path = "6"; } } + Bundle b = new Bundle(); if (path != null && path.length() > 0) { - Bundle b = new Bundle(); b.putString("path", path); - tabFragment.setArguments(b); } + // This boolean will be given to the newly created MainFragment + b.putBoolean(MainFragment.BUNDLE_HIDE_FAB, hideFab); + tabFragment.setArguments(b); transaction.replace(R.id.content_frame, tabFragment); // Commit the transaction transaction.addToBackStack("tabt" + 1); transaction.commitAllowingStateLoss(); appbar.setTitle(null); - floatingActionButton.show(); + if (isCompressedOpen && pathInCompressedArchive != null) { openCompressed(pathInCompressedArchive); pathInCompressedArchive = null; @@ -1063,8 +1098,6 @@ public boolean onPrepareOptionsMenu(Menu menu) { .getBottomBar() .updatePath( mainFragment.getCurrentPath(), - mainFragment.getMainFragmentViewModel().getResults(), - MainActivityHelper.SEARCH_TEXT, mainFragment.getMainFragmentViewModel().getOpenMode(), mainFragment.getMainFragmentViewModel().getFolderCount(), mainFragment.getMainFragmentViewModel().getFileCount(), @@ -1182,7 +1215,7 @@ public boolean onOptionsItemSelected(MenuItem item) { case R.id.dsort: String[] sort = getResources().getStringArray(R.array.directorysortmode); MaterialDialog.Builder builder = new MaterialDialog.Builder(mainActivity); - builder.theme(getAppTheme().getMaterialDialogTheme(this)); + builder.theme(getAppTheme().getMaterialDialogTheme()); builder.title(R.string.directorysort); int current = Integer.parseInt( @@ -1512,21 +1545,31 @@ public SpeedDialView getFAB() { } public void showFab() { - getFAB().setVisibility(View.VISIBLE); - getFAB().show(); - CoordinatorLayout.LayoutParams params = - (CoordinatorLayout.LayoutParams) getFAB().getLayoutParams(); + if (getCurrentMainFragment() != null && getCurrentMainFragment().getHideFab()) { + hideFab(); + } else { + showFab(getFAB()); + } + } + + private void showFab(SpeedDialView fab) { + fab.setVisibility(View.VISIBLE); + fab.show(); + CoordinatorLayout.LayoutParams params = (CoordinatorLayout.LayoutParams) fab.getLayoutParams(); params.setBehavior(new SpeedDialView.ScrollingViewSnackbarBehavior()); - getFAB().requestLayout(); + fab.requestLayout(); } public void hideFab() { - getFAB().setVisibility(View.GONE); - getFAB().hide(); - CoordinatorLayout.LayoutParams params = - (CoordinatorLayout.LayoutParams) getFAB().getLayoutParams(); + hideFab(getFAB()); + } + + private void hideFab(SpeedDialView fab) { + fab.setVisibility(View.GONE); + fab.hide(); + CoordinatorLayout.LayoutParams params = (CoordinatorLayout.LayoutParams) fab.getLayoutParams(); params.setBehavior(new SpeedDialView.NoBehavior()); - getFAB().requestLayout(); + fab.requestLayout(); } public AppBar getAppbar() { @@ -1539,9 +1582,7 @@ public Drawer getDrawer() { protected void onActivityResult(int requestCode, int responseCode, Intent intent) { super.onActivityResult(requestCode, responseCode, intent); - if (requestCode == Drawer.image_selector_request_code) { - drawer.onActivityResult(requestCode, responseCode, intent); - } else if (requestCode == 3) { + if (requestCode == 3) { Uri treeUri; if (responseCode == Activity.RESULT_OK) { // Get Uri from Storage Access Framework. @@ -1573,7 +1614,7 @@ protected void onActivityResult(int requestCode, int responseCode, Intent intent mainFragment -> { switch (operation) { case DELETE: // deletion - new DeleteTask(mainActivity).execute((oparrayList)); + new DeleteTask(mainActivity, true).execute((oparrayList)); break; case COPY: // copying // legacy compatibility @@ -1688,15 +1729,7 @@ void initialisePreferences() { void initialiseViews() { - appbar = - new AppBar( - this, - getPrefs(), - queue -> { - if (!queue.isEmpty()) { - mainActivityHelper.search(getPrefs(), queue); - } - }); + appbar = new AppBar(this, getPrefs()); appBarLayout = getAppbar().getAppbarLayout(); setSupportActionBar(getAppbar().getToolbar()); @@ -1707,7 +1740,7 @@ void initialiseViews() { getSupportActionBar().setDisplayShowTitleEnabled(false); fabBgView = findViewById(R.id.fabs_overlay_layout); - switch (getAppTheme().getSimpleTheme(this)) { + switch (getAppTheme()) { case DARK: fabBgView.setBackgroundResource(R.drawable.fab_shadow_dark); break; @@ -1721,7 +1754,7 @@ void initialiseViews() { if (getAppbar().getSearchView().isEnabled()) getAppbar().getSearchView().hideSearchView(); }); - drawer.setDrawerHeaderBackground(); + // drawer.setDrawerHeaderBackground(); } /** @@ -1787,7 +1820,34 @@ public void initializeFabActionViews() { FabWithLabelView newFolderFab = initFabTitle(R.id.menu_new_folder, R.string.folder, R.drawable.folder_fab); - floatingActionButton.setOnActionSelectedListener(new FabActionListener(this)); + floatingActionButton.setOnActionSelectedListener( + actionItem -> { + MainFragment mainFragment = getCurrentMainFragment(); + + if (mainFragment == null) return false; + + String path = mainFragment.getCurrentPath(); + + MainFragmentViewModel mainFragmentViewModel = mainFragment.getMainFragmentViewModel(); + + if (mainFragmentViewModel == null) return false; + + OpenMode openMode = mainFragmentViewModel.getOpenMode(); + + int id = actionItem.getId(); + + if (id == R.id.menu_new_folder) + mainActivity.mainActivityHelper.mkdir(openMode, path, mainFragment); + else if (id == R.id.menu_new_file) + mainActivity.mainActivityHelper.mkfile(openMode, path, mainFragment); + else if (id == R.id.menu_new_cloud) + new CloudSheetFragment() + .show(mainActivity.getSupportFragmentManager(), CloudSheetFragment.TAG_FRAGMENT); + + floatingActionButton.close(true); + return true; + }); + floatingActionButton.setOnClickListener( view -> { fabButtonClick(cloudFab); @@ -1850,7 +1910,7 @@ private FabWithLabelView initFabTitle( .setLabel(fabTitle) .setFabBackgroundColor(iconSkin); - switch (getAppTheme().getSimpleTheme(this)) { + switch (getAppTheme()) { case LIGHT: fabBgView.setBackgroundResource(R.drawable.fab_shadow_light); break; @@ -1871,6 +1931,52 @@ private FabWithLabelView initFabTitle( return floatingActionButton.addActionItem(builder.create()); } + private void initialiseFabConfirmSelection() { + fabConfirmSelection = findViewById(R.id.fabs_confirm_selection); + hideFabConfirmSelection(); + if (mReturnIntent) { + int colorAccent = getAccent(); + fabConfirmSelection.setMainFabClosedBackgroundColor(colorAccent); + fabConfirmSelection.setMainFabOpenedBackgroundColor(colorAccent); + + fabConfirmSelection.setOnChangeListener( + new SpeedDialView.OnChangeListener() { + @Override + public boolean onMainActionSelected() { + if (getCurrentMainFragment() != null + && getCurrentMainFragment().getMainFragmentViewModel() != null) { + ArrayList checkedItems = + getCurrentMainFragment().getMainFragmentViewModel().getCheckedItems(); + ArrayList baseFiles = new ArrayList<>(); + for (LayoutElementParcelable item : checkedItems) { + baseFiles.add(item.generateBaseFile()); + } + getCurrentMainFragment() + .returnIntentResults(baseFiles.toArray(new HybridFileParcelable[0])); + } + return false; + } + + @Override + public void onToggleChanged(boolean isOpen) {} + }); + } + } + + /** + * If a intent should be returned, shows the floating action button which confirms the selection + */ + public void showFabConfirmSelection() { + if (mReturnIntent) { + showFab(fabConfirmSelection); + } + } + + /** Hides the floating action button which confirms the selection */ + public void hideFabConfirmSelection() { + hideFab(fabConfirmSelection); + } + public boolean copyToClipboard(Context context, String text) { try { android.content.ClipboardManager clipboard = @@ -1990,6 +2096,7 @@ public void showSftpDialog(String name, String path, boolean edit) { if (i != -1) name = dataUtils.getServers().get(i)[0]; } SftpConnectDialog sftpConnectDialog = new SftpConnectDialog(); + sftpConnectDialog.setCancelable(false); String finalName = name; Flowable.fromCallable(() -> new NetCopyConnectionInfo(path)) .flatMap( @@ -2012,16 +2119,28 @@ public void showSftpDialog(String name, String path, boolean edit) { (Function1) s -> GenericExtKt.urlDecoded(s, Charsets.UTF_8))); } - retval.putString(ARG_USERNAME, connectionInfo.getUsername()); + if (!TextUtils.isEmpty(connectionInfo.getUsername())) { + retval.putString(ARG_USERNAME, connectionInfo.getUsername()); + } if (connectionInfo.getPassword() == null) { retval.putBoolean(ARG_HAS_PASSWORD, false); - retval.putString(ARG_KEYPAIR_NAME, utilsHandler.getSshAuthPrivateKeyName(path)); + if (SSH_URI_PREFIX.equals(connectionInfo.getPrefix())) { + retval.putString(ARG_KEYPAIR_NAME, utilsHandler.getSshAuthPrivateKeyName(path)); + } } else { retval.putBoolean(ARG_HAS_PASSWORD, true); retval.putString(ARG_PASSWORD, connectionInfo.getPassword()); } retval.putBoolean(ARG_EDIT, edit); + + if ((FTP_URI_PREFIX.equals(connectionInfo.getPrefix()) + || FTPS_URI_PREFIX.equals(connectionInfo.getPrefix())) + && connectionInfo.getArguments() != null + && TLS_EXPLICIT.equals(connectionInfo.getArguments().get(ARG_TLS))) { + retval.putString(ARG_TLS, TLS_EXPLICIT); + } + return Flowable.just(retval); }) .subscribeOn(Schedulers.computation()) @@ -2029,7 +2148,7 @@ public void showSftpDialog(String name, String path, boolean edit) { bundle -> { sftpConnectDialog.setArguments(bundle); sftpConnectDialog.setCancelable(true); - sftpConnectDialog.show(getSupportFragmentManager(), "sftpdialog"); + sftpConnectDialog.show(getSupportFragmentManager(), SftpConnectDialog.TAG); }); } @@ -2146,51 +2265,6 @@ public void modify(String oldpath, String oldname, String newPath, String newnam .subscribe(() -> drawer.refreshDrawer()); } - @Override - public void onPreExecute(String query) { - executeWithMainFragment( - mainFragment -> { - mainFragment.mSwipeRefreshLayout.setRefreshing(true); - mainFragment.onSearchPreExecute(query); - return null; - }); - } - - @Override - public void onPostExecute(String query) { - final MainFragment mainFragment = getCurrentMainFragment(); - if (mainFragment == null) { - // TODO cancel search - return; - } - - mainFragment.onSearchCompleted(query); - mainFragment.mSwipeRefreshLayout.setRefreshing(false); - } - - @Override - public void onProgressUpdate(@NonNull HybridFileParcelable hybridFileParcelable, String query) { - final MainFragment mainFragment = getCurrentMainFragment(); - if (mainFragment == null) { - // TODO cancel search - return; - } - - mainFragment.addSearchResult(hybridFileParcelable, query); - } - - @Override - public void onCancelled() { - final MainFragment mainFragment = getCurrentMainFragment(); - if (mainFragment == null) { - return; - } - - mainFragment.reloadListElements( - false, false, !mainFragment.getMainFragmentViewModel().isList()); - mainFragment.mSwipeRefreshLayout.setRefreshing(false); - } - @Override public void addConnection(OpenMode service) { try { @@ -2378,45 +2452,6 @@ private void initLeftRightAndTopDragListeners(boolean destroy, boolean shouldInv tabFragment.initLeftRightAndTopDragListeners(destroy, shouldInvokeLeftAndRight); } - private static final class FabActionListener implements SpeedDialView.OnActionSelectedListener { - - MainActivity mainActivity; - SpeedDialView floatingActionButton; - - FabActionListener(MainActivity mainActivity) { - this.mainActivity = mainActivity; - this.floatingActionButton = mainActivity.floatingActionButton; - } - - @Override - public boolean onActionSelected(SpeedDialActionItem actionItem) { - final MainFragment ma = - (MainFragment) - ((TabFragment) - mainActivity.getSupportFragmentManager().findFragmentById(R.id.content_frame)) - .getCurrentTabFragment(); - final String path = ma.getCurrentPath(); - - switch (actionItem.getId()) { - case R.id.menu_new_folder: - mainActivity.mainActivityHelper.mkdir( - ma.getMainFragmentViewModel().getOpenMode(), path, ma); - break; - case R.id.menu_new_file: - mainActivity.mainActivityHelper.mkfile( - ma.getMainFragmentViewModel().getOpenMode(), path, ma); - break; - case R.id.menu_new_cloud: - BottomSheetDialogFragment fragment = new CloudSheetFragment(); - fragment.show( - ma.getActivity().getSupportFragmentManager(), CloudSheetFragment.TAG_FRAGMENT); - break; - } - - floatingActionButton.close(true); - return true; - } - } /** * Invoke {@link FtpServerFragment#changeFTPServerPath(String)} to change FTP server share path. * diff --git a/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivityViewModel.kt b/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivityViewModel.kt index dc01bebcfd..e9c8c83246 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivityViewModel.kt +++ b/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivityViewModel.kt @@ -21,30 +21,66 @@ package com.amaze.filemanager.ui.activities import android.app.Application +import android.content.Intent +import android.provider.MediaStore import androidx.collection.LruCache import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData import androidx.lifecycle.viewModelScope +import androidx.preference.PreferenceManager +import com.amaze.filemanager.R import com.amaze.filemanager.adapters.data.LayoutElementParcelable +import com.amaze.filemanager.application.AppConfig +import com.amaze.filemanager.asynchronous.asynctasks.searchfilesystem.BasicSearch +import com.amaze.filemanager.asynchronous.asynctasks.searchfilesystem.DeepSearch +import com.amaze.filemanager.asynchronous.asynctasks.searchfilesystem.IndexedSearch +import com.amaze.filemanager.asynchronous.asynctasks.searchfilesystem.SearchParameters +import com.amaze.filemanager.asynchronous.asynctasks.searchfilesystem.SearchResult +import com.amaze.filemanager.asynchronous.asynctasks.searchfilesystem.searchParametersFromBoolean +import com.amaze.filemanager.fileoperations.filesystem.OpenMode +import com.amaze.filemanager.filesystem.HybridFile +import com.amaze.filemanager.filesystem.files.MediaConnectionUtils.scanFile +import com.amaze.filemanager.ui.fragments.preferencefragments.PreferencesConstants.PREFERENCE_REGEX +import com.amaze.filemanager.ui.fragments.preferencefragments.PreferencesConstants.PREFERENCE_REGEX_MATCHES +import com.amaze.filemanager.ui.fragments.preferencefragments.PreferencesConstants.PREFERENCE_SHOW_HIDDENFILES +import com.amaze.trashbin.MoveFilesCallback +import com.amaze.trashbin.TrashBinFile import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job import kotlinx.coroutines.launch +import org.slf4j.LoggerFactory +import java.io.File class MainActivityViewModel(val applicationContext: Application) : AndroidViewModel(applicationContext) { - var mediaCacheHash: List?> = List(5) { null } var listCache: LruCache> = LruCache(50) + var trashBinFilesLiveData: MutableLiveData?>? = null + + /** The [LiveData] of the last triggered search */ + var lastSearchLiveData: LiveData> = MutableLiveData(listOf()) + private set + + /** The [Job] of the last triggered search */ + var lastSearchJob: Job? = null + private set companion object { /** * size of list to be cached for local files */ val CACHE_LOCAL_LIST_THRESHOLD: Int = 100 + private val LOG = LoggerFactory.getLogger(MainActivityViewModel::class.java) } /** * Put list for a given path in cache */ - fun putInCache(path: String, listToCache: List) { + fun putInCache( + path: String, + listToCache: List, + ) { viewModelScope.launch(Dispatchers.Default) { listCache.put(path, listToCache) } @@ -72,4 +108,224 @@ class MainActivityViewModel(val applicationContext: Application) : fun getFromMediaFilesCache(mediaType: Int): List? { return mediaCacheHash[mediaType] } + + /** + * Perform basic search: searches on the current directory + */ + fun basicSearch( + mainActivity: MainActivity, + query: String, + ): LiveData> { + val searchParameters = createSearchParameters(mainActivity) + + val path = mainActivity.currentMainFragment?.currentPath ?: "" + + val basicSearch = BasicSearch(query, path, searchParameters, this.applicationContext) + + lastSearchJob = + viewModelScope.launch(Dispatchers.IO) { + basicSearch.search() + } + + lastSearchLiveData = basicSearch.foundFilesLiveData + return basicSearch.foundFilesLiveData + } + + /** + * Perform indexed search: on MediaStore items from the current directory & it's children + */ + fun indexedSearch( + mainActivity: MainActivity, + query: String, + ): LiveData> { + val projection = + arrayOf( + MediaStore.Files.FileColumns.DATA, + MediaStore.Files.FileColumns.DISPLAY_NAME, + ) + val cursor = + mainActivity + .contentResolver + .query(MediaStore.Files.getContentUri("external"), projection, null, null, null) + ?: return MutableLiveData() + + val searchParameters = createSearchParameters(mainActivity) + + val path = mainActivity.currentMainFragment?.currentPath ?: "" + + val indexedSearch = IndexedSearch(query, path, searchParameters, cursor) + + lastSearchJob = + viewModelScope.launch(Dispatchers.IO) { + indexedSearch.search() + } + + lastSearchLiveData = indexedSearch.foundFilesLiveData + return indexedSearch.foundFilesLiveData + } + + /** + * Perform deep search: search recursively for files matching [query] in the current path + */ + fun deepSearch( + mainActivity: MainActivity, + query: String, + ): LiveData> { + val searchParameters = createSearchParameters(mainActivity) + + val path = mainActivity.currentMainFragment?.currentPath ?: "" + val openMode = + mainActivity.currentMainFragment?.mainFragmentViewModel?.openMode ?: OpenMode.FILE + + val context = this.applicationContext + + val deepSearch = + DeepSearch( + query, + path, + searchParameters, + context, + openMode, + ) + + lastSearchJob = + viewModelScope.launch(Dispatchers.IO) { + deepSearch.search() + } + + lastSearchLiveData = deepSearch.foundFilesLiveData + return deepSearch.foundFilesLiveData + } + + private fun createSearchParameters(mainActivity: MainActivity): SearchParameters { + val sharedPref = PreferenceManager.getDefaultSharedPreferences(mainActivity) + return searchParametersFromBoolean( + showHiddenFiles = sharedPref.getBoolean(PREFERENCE_SHOW_HIDDENFILES, false), + isRegexEnabled = sharedPref.getBoolean(PREFERENCE_REGEX, false), + isRegexMatchesEnabled = sharedPref.getBoolean(PREFERENCE_REGEX_MATCHES, false), + isRoot = mainActivity.isRootExplorer, + ) + } + + /** + * TODO: Documentation + */ + fun moveToBinLightWeight(mediaFileInfoList: List) { + viewModelScope.launch(Dispatchers.IO) { + val trashBinFilesList = + mediaFileInfoList.map { + it.generateBaseFile() + .toTrashBinFile(applicationContext) + } + AppConfig.getInstance().trashBinInstance.moveToBin( + trashBinFilesList, + true, + object : MoveFilesCallback { + override fun invoke( + originalFilePath: String, + trashBinDestination: String, + ): Boolean { + val source = File(originalFilePath) + val dest = File(trashBinDestination) + if (!source.renameTo(dest)) { + return false + } + val hybridFile = + HybridFile( + OpenMode.TRASH_BIN, + originalFilePath, + ) + scanFile(applicationContext, arrayOf(hybridFile)) + val intent = Intent(MainActivity.KEY_INTENT_LOAD_LIST) + hybridFile.getParent(applicationContext)?.let { + intent.putExtra(MainActivity.KEY_INTENT_LOAD_LIST_FILE, it) + applicationContext.sendBroadcast(intent) + } + return true + } + }, + ) + } + } + + /** + * Restore files from trash bin + */ + fun restoreFromBin(mediaFileInfoList: List) { + viewModelScope.launch(Dispatchers.IO) { + LOG.info("Restoring media files from bin $mediaFileInfoList") + val filesToRestore = mutableListOf() + for (element in mediaFileInfoList) { + val restoreFile = + element.generateBaseFile() + .toTrashBinRestoreFile(applicationContext) + if (restoreFile != null) { + filesToRestore.add(restoreFile) + } + } + AppConfig.getInstance().trashBinInstance.restore( + filesToRestore, + true, + object : MoveFilesCallback { + override fun invoke( + source: String, + dest: String, + ): Boolean { + val sourceFile = File(source) + val destFile = File(dest) + if (destFile.exists()) { + AppConfig.toast( + applicationContext, + applicationContext.getString(R.string.fileexist), + ) + return false + } + if (destFile.parentFile != null && !destFile.parentFile!!.exists()) { + destFile.parentFile?.mkdirs() + } + if (!sourceFile.renameTo(destFile)) { + return false + } + val hybridFile = + HybridFile( + OpenMode.TRASH_BIN, + source, + ) + scanFile(applicationContext, arrayOf(hybridFile)) + val intent = Intent(MainActivity.KEY_INTENT_LOAD_LIST) + hybridFile.getParent(applicationContext)?.let { + intent.putExtra(MainActivity.KEY_INTENT_LOAD_LIST_FILE, it) + applicationContext.sendBroadcast(intent) + } + return true + } + }, + ) + } + } + + /** + * TODO: Documentation + */ + fun progressTrashBinFilesLiveData(): MutableLiveData?> { + if (trashBinFilesLiveData == null) { + trashBinFilesLiveData = MutableLiveData() + trashBinFilesLiveData?.value = null + viewModelScope.launch(Dispatchers.IO) { + trashBinFilesLiveData?.postValue( + ArrayList( + AppConfig.getInstance().trashBinInstance.listFilesInBin() + .map { + HybridFile(OpenMode.FILE, it.path, it.fileName, it.isDirectory) + .generateLayoutElement( + applicationContext, + false, + ) + }, + ), + ) + } + } + return trashBinFilesLiveData!! + } } diff --git a/app/src/main/java/com/amaze/filemanager/ui/activities/PreferencesActivity.kt b/app/src/main/java/com/amaze/filemanager/ui/activities/PreferencesActivity.kt index f54dc9848a..7566845fd7 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/activities/PreferencesActivity.kt +++ b/app/src/main/java/com/amaze/filemanager/ui/activities/PreferencesActivity.kt @@ -96,7 +96,9 @@ class PreferencesActivity : ThemedActivity(), FolderChooserDialog.FolderCallback return if (item.itemId == android.R.id.home) { onBackPressed() true - } else false + } else { + false + } } override fun recreate() { @@ -129,14 +131,16 @@ class PreferencesActivity : ThemedActivity(), FolderChooserDialog.FolderCallback * Used to update color */ fun invalidateNavBar() { - val primaryColor = ColorPreferenceHelper - .getPrimary(currentColorPreference, MainActivity.currentTab) + val primaryColor = + ColorPreferenceHelper + .getPrimary(currentColorPreference, MainActivity.currentTab) if (Build.VERSION.SDK_INT == 20 || Build.VERSION.SDK_INT == 19) { val tintManager = SystemBarTintManager(this) tintManager.isStatusBarTintEnabled = true tintManager.setStatusBarTintColor(primaryColor) - val layoutParams = findViewById(R.id.activity_preferences).layoutParams - as ViewGroup.MarginLayoutParams + val layoutParams = + findViewById(R.id.activity_preferences).layoutParams + as ViewGroup.MarginLayoutParams val config = tintManager.config layoutParams.setMargins(0, config.statusBarHeight, 0, 0) } else if (Build.VERSION.SDK_INT >= 21) { @@ -165,7 +169,10 @@ class PreferencesActivity : ThemedActivity(), FolderChooserDialog.FolderCallback } } - override fun onFolderSelection(dialog: FolderChooserDialog, folder: File) { + override fun onFolderSelection( + dialog: FolderChooserDialog, + folder: File, + ) { supportFragmentManager.fragments.lastOrNull { it is BasePrefsFragment }?.let { (it as BasePrefsFragment).onFolderSelection(dialog, folder) } diff --git a/app/src/main/java/com/amaze/filemanager/ui/activities/UtilitiesAliasActivity.kt b/app/src/main/java/com/amaze/filemanager/ui/activities/UtilitiesAliasActivity.kt index 11bbbbc0a2..173c457b62 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/activities/UtilitiesAliasActivity.kt +++ b/app/src/main/java/com/amaze/filemanager/ui/activities/UtilitiesAliasActivity.kt @@ -50,21 +50,23 @@ class UtilitiesAliasActivity : AppCompatActivity() { } else { AboutActivity.URL_AMAZE_UTILS }, - this + this, ) } _binding.cancelButton.setOnClickListener { finish() } - val isAUInstalled = PackageUtils.appInstalledOrNot( - AboutActivity.PACKAGE_AMAZE_UTILS, - packageManager - ) + val isAUInstalled = + PackageUtils.appInstalledOrNot( + AboutActivity.PACKAGE_AMAZE_UTILS, + packageManager, + ) if (isAUInstalled) { AppConfig.toast(this, R.string.amaze_utils_installed_alias) - val intent = packageManager.getLaunchIntentForPackage( - AboutActivity.PACKAGE_AMAZE_UTILS - ) + val intent = + packageManager.getLaunchIntentForPackage( + AboutActivity.PACKAGE_AMAZE_UTILS, + ) try { if (intent != null) { this.updateAUAlias(false) diff --git a/app/src/main/java/com/amaze/filemanager/ui/activities/superclasses/BasicActivity.java b/app/src/main/java/com/amaze/filemanager/ui/activities/superclasses/BasicActivity.java index 21de42aa06..a5ebc37afc 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/activities/superclasses/BasicActivity.java +++ b/app/src/main/java/com/amaze/filemanager/ui/activities/superclasses/BasicActivity.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. diff --git a/app/src/main/java/com/amaze/filemanager/ui/activities/superclasses/PermissionsActivity.java b/app/src/main/java/com/amaze/filemanager/ui/activities/superclasses/PermissionsActivity.java index 9c97929871..821f433420 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/activities/superclasses/PermissionsActivity.java +++ b/app/src/main/java/com/amaze/filemanager/ui/activities/superclasses/PermissionsActivity.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. @@ -20,6 +20,9 @@ package com.amaze.filemanager.ui.activities.superclasses; +import static android.os.Build.VERSION.SDK_INT; +import static android.os.Build.VERSION_CODES.TIRAMISU; + import com.afollestad.materialdialogs.DialogAction; import com.afollestad.materialdialogs.MaterialDialog; import com.amaze.filemanager.R; @@ -30,6 +33,7 @@ import com.google.android.material.snackbar.Snackbar; import android.Manifest; +import android.content.ActivityNotFoundException; import android.content.Intent; import android.content.pm.PackageManager; import android.net.Uri; @@ -48,10 +52,11 @@ public class PermissionsActivity extends ThemedActivity private static final String TAG = PermissionsActivity.class.getSimpleName(); - public static final int PERMISSION_LENGTH = 3; + public static final int PERMISSION_LENGTH = 4; public static final int STORAGE_PERMISSION = 0, INSTALL_APK_PERMISSION = 1, - ALL_FILES_PERMISSION = 2; + ALL_FILES_PERMISSION = 2, + NOTIFICATION_PERMISSION = 3; private final OnPermissionGranted[] permissionCallbacks = new OnPermissionGranted[PERMISSION_LENGTH]; @@ -69,7 +74,13 @@ public void onRequestPermissionsResult( Toast.makeText(this, R.string.grantfailed, Toast.LENGTH_SHORT).show(); requestStoragePermission(permissionCallbacks[STORAGE_PERMISSION], false); } - + } else if (requestCode == NOTIFICATION_PERMISSION && SDK_INT >= TIRAMISU) { + if (isGranted(grantResults)) { + Utils.enableScreenRotation(this); + } else { + Toast.makeText(this, R.string.grantfailed, Toast.LENGTH_SHORT).show(); + requestNotificationPermission(false); + } } else if (requestCode == INSTALL_APK_PERMISSION) { if (isGranted(grantResults)) { permissionCallbacks[INSTALL_APK_PERMISSION].onPermissionGranted(); @@ -80,10 +91,49 @@ public void onRequestPermissionsResult( public boolean checkStoragePermission() { // Verify that all required contact permissions have been granted. - return ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) + if (SDK_INT >= Build.VERSION_CODES.R) { + return (ActivityCompat.checkSelfPermission( + this, Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION) + == PackageManager.PERMISSION_GRANTED) + || (ActivityCompat.checkSelfPermission( + this, Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION) + == PackageManager.PERMISSION_GRANTED) + || Environment.isExternalStorageManager(); + } else { + return ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) + == PackageManager.PERMISSION_GRANTED; + } + } + + @RequiresApi(TIRAMISU) + public boolean checkNotificationPermission() { + return ActivityCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED; } + @RequiresApi(TIRAMISU) + public void requestNotificationPermission(boolean isInitialStart) { + Utils.disableScreenRotation(this); + final MaterialDialog materialDialog = + GeneralDialogCreation.showBasicDialog( + this, + R.string.grant_notification_permission, + R.string.grantper, + R.string.grant, + R.string.cancel); + materialDialog.getActionButton(DialogAction.NEGATIVE).setOnClickListener(v -> finish()); + materialDialog.setCancelable(false); + + requestPermission( + Manifest.permission.POST_NOTIFICATIONS, + NOTIFICATION_PERMISSION, + materialDialog, + () -> { + // do nothing + }, + isInitialStart); + } + public void requestStoragePermission( @NonNull final OnPermissionGranted onPermissionGranted, boolean isInitialStart) { Utils.disableScreenRotation(this); @@ -160,18 +210,27 @@ private void requestPermission( } else if (isInitialStart) { ActivityCompat.requestPermissions(this, new String[] {permission}, code); } else { - Snackbar.make( - findViewById(R.id.content_frame), - R.string.grantfailed, - BaseTransientBottomBar.LENGTH_INDEFINITE) - .setAction( - R.string.grant, - v -> - startActivity( - new Intent( - android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS, - Uri.parse(String.format("package:%s", getPackageName()))))) - .show(); + if (SDK_INT >= Build.VERSION_CODES.R) { + Snackbar.make( + findViewById(R.id.content_frame), + R.string.grantfailed, + BaseTransientBottomBar.LENGTH_INDEFINITE) + .setAction(R.string.grant, v -> requestAllFilesAccessPermission(onPermissionGranted)) + .show(); + } else { + Snackbar.make( + findViewById(R.id.content_frame), + R.string.grantfailed, + BaseTransientBottomBar.LENGTH_INDEFINITE) + .setAction( + R.string.grant, + v -> + startActivity( + new Intent( + android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS, + Uri.parse(String.format("package:%s", getPackageName()))))) + .show(); + } } } @@ -194,17 +253,7 @@ public void requestAllFilesAccess(@NonNull final OnPermissionGranted onPermissio .getActionButton(DialogAction.POSITIVE) .setOnClickListener( v -> { - Utils.disableScreenRotation(this); - permissionCallbacks[ALL_FILES_PERMISSION] = onPermissionGranted; - try { - Intent intent = - new Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION) - .setData(Uri.parse("package:" + getPackageName())); - startActivity(intent); - } catch (Exception e) { - Log.e(TAG, "Failed to initial activity to grant all files access", e); - AppConfig.toast(this, getString(R.string.grantfailed)); - } + requestAllFilesAccessPermission(onPermissionGranted); materialDialog.dismiss(); }); materialDialog.setCancelable(false); @@ -212,6 +261,32 @@ public void requestAllFilesAccess(@NonNull final OnPermissionGranted onPermissio } } + @RequiresApi(api = Build.VERSION_CODES.R) + private void requestAllFilesAccessPermission( + @NonNull final OnPermissionGranted onPermissionGranted) { + Utils.disableScreenRotation(this); + permissionCallbacks[ALL_FILES_PERMISSION] = onPermissionGranted; + try { + Intent intent = + new Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION) + .setData(Uri.parse("package:" + getPackageName())); + startActivity(intent); + } catch (ActivityNotFoundException anf) { + // fallback + try { + Intent intent = + new Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION) + .setData(Uri.parse("package:$packageName")); + startActivity(intent); + } catch (Exception e) { + AppConfig.toast(this, getString(R.string.grantfailed)); + } + } catch (Exception e) { + Log.e(TAG, "Failed to initial activity to grant all files access", e); + AppConfig.toast(this, getString(R.string.grantfailed)); + } + } + private boolean isGranted(int[] grantResults) { return grantResults.length == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED; } diff --git a/app/src/main/java/com/amaze/filemanager/ui/activities/superclasses/PreferenceActivity.java b/app/src/main/java/com/amaze/filemanager/ui/activities/superclasses/PreferenceActivity.java index 44f83b2ae9..526264186f 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/activities/superclasses/PreferenceActivity.java +++ b/app/src/main/java/com/amaze/filemanager/ui/activities/superclasses/PreferenceActivity.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. diff --git a/app/src/main/java/com/amaze/filemanager/ui/activities/superclasses/ThemedActivity.java b/app/src/main/java/com/amaze/filemanager/ui/activities/superclasses/ThemedActivity.java index e35172af3c..cbee76f25d 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/activities/superclasses/ThemedActivity.java +++ b/app/src/main/java/com/amaze/filemanager/ui/activities/superclasses/ThemedActivity.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. @@ -21,6 +21,7 @@ package com.amaze.filemanager.ui.activities.superclasses; import static android.os.Build.VERSION.SDK_INT; +import static android.os.Build.VERSION_CODES.LOLLIPOP; import static com.amaze.filemanager.ui.fragments.preferencefragments.PreferencesConstants.PREFERENCE_COLORED_NAVIGATION; import com.amaze.filemanager.R; @@ -29,15 +30,22 @@ import com.amaze.filemanager.ui.dialogs.ColorPickerDialog; import com.amaze.filemanager.ui.fragments.preferencefragments.PreferencesConstants; import com.amaze.filemanager.ui.theme.AppTheme; +import com.amaze.filemanager.ui.theme.AppThemePreference; import com.amaze.filemanager.utils.PreferenceUtils; import com.amaze.filemanager.utils.Utils; import com.readystatesoftware.systembartint.SystemBarTintManager; import android.app.ActivityManager; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.SharedPreferences; import android.content.res.Configuration; import android.graphics.drawable.BitmapDrawable; import android.os.Build; import android.os.Bundle; +import android.os.PowerManager; import android.view.View; import android.view.ViewGroup; import android.view.Window; @@ -49,15 +57,40 @@ import androidx.annotation.Nullable; import androidx.appcompat.widget.Toolbar; import androidx.core.content.ContextCompat; +import androidx.preference.PreferenceManager; /** Created by arpitkh996 on 03-03-2016. */ public class ThemedActivity extends PreferenceActivity { private int uiModeNight = -1; + /** + * BroadcastReceiver responsible for updating the theme if battery saver mode is turned on or off + */ + private final BroadcastReceiver powerModeReceiver = + new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent i) { + SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context); + boolean followBatterySaver = + preferences.getBoolean(PreferencesConstants.FRAGMENT_FOLLOW_BATTERY_SAVER, false); + + AppThemePreference theme = + AppThemePreference.getTheme( + Integer.parseInt( + preferences.getString(PreferencesConstants.FRAGMENT_THEME, "4"))); + + if (followBatterySaver && theme.getCanBeLight()) { + recreate(); + } + } + }; + @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + registerPowerModeReceiver(); + // setting window background color instead of each item, in order to reduce pixel overdraw if (getAppTheme().equals(AppTheme.LIGHT)) { getWindow().setBackgroundDrawableResource(android.R.color.white); @@ -134,7 +167,7 @@ public void onConfigurationChanged(@NonNull Configuration newConfig) { uiModeNight = newUiModeNight; if (getPrefs().getString(PreferencesConstants.FRAGMENT_THEME, "4").equals("4")) { - getUtilsProvider().getThemeManager().setAppTheme(AppTheme.getTheme(this, 4)); + getUtilsProvider().getThemeManager().setAppThemePreference(AppThemePreference.getTheme(4)); // Recreate activity, handling saved state // // Not smooth, but will only be called if the user changes the system theme, not @@ -177,7 +210,7 @@ private Toolbar getToolbar() { } void setTheme() { - AppTheme theme = getAppTheme().getSimpleTheme(this); + AppTheme theme = getAppTheme(); if (Build.VERSION.SDK_INT >= 21) { String stringRepresentation = String.format("#%06X", (0xFFFFFF & getAccent())); @@ -309,4 +342,29 @@ protected void onResume() { uiModeNight = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK; setTheme(); } + + @Override + protected void onDestroy() { + super.onDestroy(); + + unregisterPowerModeReceiver(); + } + + /** + * Registers the BroadcastReceiver \`powerModeReceiver\` to listen to broadcasts that the battery + * save mode has been changed + */ + private void registerPowerModeReceiver() { + if (SDK_INT >= LOLLIPOP) { + registerReceiver( + powerModeReceiver, new IntentFilter(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED)); + } + } + + /** Unregisters the BroadcastReceiver \`powerModeReceiver\` */ + private void unregisterPowerModeReceiver() { + if (SDK_INT >= LOLLIPOP) { + unregisterReceiver(powerModeReceiver); + } + } } diff --git a/app/src/main/java/com/amaze/filemanager/ui/activities/texteditor/ReturnedValueOnReadFile.kt b/app/src/main/java/com/amaze/filemanager/ui/activities/texteditor/ReturnedValueOnReadFile.kt index a7dc27bbdd..99ddc3550e 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/activities/texteditor/ReturnedValueOnReadFile.kt +++ b/app/src/main/java/com/amaze/filemanager/ui/activities/texteditor/ReturnedValueOnReadFile.kt @@ -25,5 +25,5 @@ import java.io.File data class ReturnedValueOnReadFile( val fileContents: String, val cachedFile: File?, - val fileIsTooLong: Boolean + val fileIsTooLong: Boolean, ) diff --git a/app/src/main/java/com/amaze/filemanager/ui/activities/texteditor/TextEditorActivity.java b/app/src/main/java/com/amaze/filemanager/ui/activities/texteditor/TextEditorActivity.java index 51f86a430e..97b518614e 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/activities/texteditor/TextEditorActivity.java +++ b/app/src/main/java/com/amaze/filemanager/ui/activities/texteditor/TextEditorActivity.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. @@ -49,41 +49,37 @@ import com.amaze.filemanager.utils.Utils; import com.google.android.material.snackbar.Snackbar; -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.ObjectAnimator; import android.content.Context; -import android.graphics.Color; import android.graphics.Typeface; import android.net.Uri; -import android.os.Build; import android.os.Bundle; import android.text.Editable; import android.text.Spanned; import android.text.TextWatcher; import android.text.style.BackgroundColorSpan; -import android.util.DisplayMetrics; import android.view.Menu; import android.view.MenuItem; import android.view.View; -import android.view.ViewAnimationUtils; -import android.view.animation.AccelerateDecelerateInterpolator; +import android.view.animation.Animation; +import android.view.animation.AnimationUtils; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodManager; -import android.widget.EditText; -import android.widget.ImageButton; -import android.widget.RelativeLayout; import android.widget.ScrollView; import android.widget.Toast; import androidx.annotation.ColorInt; +import androidx.annotation.NonNull; +import androidx.appcompat.app.ActionBar; +import androidx.appcompat.widget.AppCompatEditText; +import androidx.appcompat.widget.AppCompatImageButton; +import androidx.constraintlayout.widget.ConstraintLayout; import androidx.lifecycle.ViewModelProvider; public class TextEditorActivity extends ThemedActivity implements TextWatcher, View.OnClickListener { - public EditText mainTextView; - public EditText searchEditText; + public AppCompatEditText mainTextView; + public AppCompatEditText searchEditText; private Typeface inputTypefaceDefault; private Typeface inputTypefaceMono; private androidx.appcompat.widget.Toolbar toolbar; @@ -95,13 +91,14 @@ public class TextEditorActivity extends ThemedActivity private static final String KEY_ORIGINAL_TEXT = "original"; private static final String KEY_MONOFONT = "monofont"; - private RelativeLayout searchViewLayout; - public ImageButton upButton; - public ImageButton downButton; - public ImageButton closeButton; + private ConstraintLayout searchViewLayout; + public AppCompatImageButton upButton; + public AppCompatImageButton downButton; private Snackbar loadingSnackbar; + private TextEditorActivityViewModel viewModel; + @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -109,16 +106,15 @@ public void onCreate(Bundle savedInstanceState) { toolbar = findViewById(R.id.toolbar); setSupportActionBar(toolbar); - final TextEditorActivityViewModel viewModel = - new ViewModelProvider(this).get(TextEditorActivityViewModel.class); + viewModel = new ViewModelProvider(this).get(TextEditorActivityViewModel.class); + + searchViewLayout = findViewById(R.id.textEditorSearchBar); - searchViewLayout = findViewById(R.id.searchview); searchViewLayout.setBackgroundColor(getPrimary()); - searchEditText = searchViewLayout.findViewById(R.id.search_box); - upButton = searchViewLayout.findViewById(R.id.prev); - downButton = searchViewLayout.findViewById(R.id.next); - closeButton = searchViewLayout.findViewById(R.id.close); + searchEditText = searchViewLayout.findViewById(R.id.textEditorSearchBox); + upButton = searchViewLayout.findViewById(R.id.textEditorSearchPrevButton); + downButton = searchViewLayout.findViewById(R.id.textEditorSearchNextButton); searchEditText.addTextChangedListener(this); @@ -126,14 +122,13 @@ public void onCreate(Bundle savedInstanceState) { // upButton.setEnabled(false); downButton.setOnClickListener(this); // downButton.setEnabled(false); - closeButton.setOnClickListener(this); - - boolean useNewStack = getBoolean(PREFERENCE_TEXTEDITOR_NEWSTACK); - getSupportActionBar().setDisplayHomeAsUpEnabled(!useNewStack); - - mainTextView = findViewById(R.id.fname); - scrollView = findViewById(R.id.editscroll); + if (getSupportActionBar() != null) { + boolean useNewStack = getBoolean(PREFERENCE_TEXTEDITOR_NEWSTACK); + getSupportActionBar().setDisplayHomeAsUpEnabled(!useNewStack); + } + mainTextView = findViewById(R.id.textEditorMainEditText); + scrollView = findViewById(R.id.textEditorScrollView); final Uri uri = getIntent().getData(); if (uri != null) { @@ -144,14 +139,23 @@ public void onCreate(Bundle savedInstanceState) { return; } - getSupportActionBar().setTitle(viewModel.getFile().name); + ActionBar actionBar = getSupportActionBar(); + + if (actionBar != null) { + actionBar.setDisplayHomeAsUpEnabled(!getBoolean(PREFERENCE_TEXTEDITOR_NEWSTACK)); + actionBar.setTitle(viewModel.getFile().name); + } mainTextView.addTextChangedListener(this); if (getAppTheme().equals(AppTheme.DARK)) { - mainTextView.setBackgroundColor(Utils.getColor(this, R.color.holo_dark_background)); + mainTextView.setBackgroundColor(Utils.getColor(this, R.color.holo_dark_action_mode)); + mainTextView.setTextColor(Utils.getColor(this, R.color.primary_white)); } else if (getAppTheme().equals(AppTheme.BLACK)) { mainTextView.setBackgroundColor(Utils.getColor(this, android.R.color.black)); + mainTextView.setTextColor(Utils.getColor(this, R.color.primary_white)); + } else { + mainTextView.setTextColor(Utils.getColor(this, R.color.primary_grey_900)); } if (mainTextView.getTypeface() == null) { @@ -172,16 +176,17 @@ public void onCreate(Bundle savedInstanceState) { } else { load(this); } - initStatusBarResources(findViewById(R.id.texteditor)); + initStatusBarResources(findViewById(R.id.textEditorRootView)); } @Override - protected void onSaveInstanceState(Bundle outState) { + protected void onSaveInstanceState(@NonNull Bundle outState) { super.onSaveInstanceState(outState); final TextEditorActivityViewModel viewModel = new ViewModelProvider(this).get(TextEditorActivityViewModel.class); - outState.putString(KEY_MODIFIED_TEXT, mainTextView.getText().toString()); + outState.putString( + KEY_MODIFIED_TEXT, mainTextView.getText() != null ? mainTextView.getText().toString() : ""); outState.putInt(KEY_INDEX, mainTextView.getScrollY()); outState.putString(KEY_ORIGINAL_TEXT, viewModel.getOriginal()); outState.putBoolean(KEY_MONOFONT, inputTypefaceMono.equals(mainTextView.getTypeface())); @@ -193,6 +198,7 @@ private void checkUnsavedChanges() { if (viewModel.getOriginal() != null && mainTextView.isShown() + && mainTextView.getText() != null && !viewModel.getOriginal().equals(mainTextView.getText().toString())) { new MaterialDialog.Builder(this) .title(R.string.unsaved_changes) @@ -293,10 +299,13 @@ public boolean onOptionsItemSelected(MenuItem item) { break; case R.id.save: // Make sure EditText is visible before saving! - saveFile(this, mainTextView.getText().toString()); + if (mainTextView.getText() != null) { + saveFile(this, mainTextView.getText().toString()); + } break; case R.id.details: if (editableFileAbstraction.scheme.equals(FILE) + && editableFileAbstraction.hybridFileParcelable.getFile() != null && editableFileAbstraction.hybridFileParcelable.getFile().exists()) { GeneralDialogCreation.showPropertiesDialogWithoutPermissions( editableFileAbstraction.hybridFileParcelable, this, getAppTheme()); @@ -316,7 +325,7 @@ public boolean onOptionsItemSelected(MenuItem item) { case R.id.openwith: if (editableFileAbstraction.scheme.equals(FILE)) { File currentFile = editableFileAbstraction.hybridFileParcelable.getFile(); - if (currentFile.exists()) { + if (currentFile != null && currentFile.exists()) { boolean useNewStack = getBoolean(PREFERENCE_TEXTEDITOR_NEWSTACK); FileUtils.openWith(currentFile, this, useNewStack); } else { @@ -355,7 +364,8 @@ public void onDestroy() { @Override public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) { // condition to check if callback is called in search editText - if (searchEditText != null && charSequence.hashCode() == searchEditText.getText().hashCode()) { + if (searchEditText.getText() != null + && charSequence.hashCode() == searchEditText.getText().hashCode()) { final TextEditorActivityViewModel viewModel = new ViewModelProvider(this).get(TextEditorActivityViewModel.class); @@ -371,7 +381,8 @@ public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) @Override public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) { - if (charSequence.hashCode() == mainTextView.getText().hashCode()) { + if (mainTextView.getText() != null + && charSequence.hashCode() == mainTextView.getText().hashCode()) { final TextEditorActivityViewModel viewModel = new ViewModelProvider(this).get(TextEditorActivityViewModel.class); final Timer oldTimer = viewModel.getTimer(); @@ -400,11 +411,12 @@ public void run() { new ViewModelProvider(textEditorActivity).get(TextEditorActivityViewModel.class); modified = - !textEditorActivity - .mainTextView - .getText() - .toString() - .equals(viewModel.getOriginal()); + textEditorActivity.mainTextView.getText() != null + && !textEditorActivity + .mainTextView + .getText() + .toString() + .equals(viewModel.getOriginal()); if (viewModel.getModified() != modified) { viewModel.setModified(modified); invalidateOptionsMenu(); @@ -420,7 +432,8 @@ public void run() { @Override public void afterTextChanged(Editable editable) { // searchBox callback block - if (searchEditText != null && editable.hashCode() == searchEditText.getText().hashCode()) { + if (searchEditText.getText() != null + && editable.hashCode() == searchEditText.getText().hashCode()) { final WeakReference textEditorActivityWR = new WeakReference<>(this); final OnProgressUpdate onProgressUpdate = @@ -429,7 +442,7 @@ public void afterTextChanged(Editable editable) { if (textEditorActivity == null) { return; } - textEditorActivity.unhighlightSearchResult(index); + textEditorActivity.colorSearchResult(index, getPrimary()); }; final OnAsyncTaskFinished> onAsyncTaskFinished = @@ -445,7 +458,7 @@ public void afterTextChanged(Editable editable) { viewModel.setSearchResultIndices(data); for (SearchResultIndex searchResultIndex : data) { - textEditorActivity.unhighlightSearchResult(searchResultIndex); + textEditorActivity.colorSearchResult(searchResultIndex, getPrimary()); } if (data.size() != 0) { @@ -460,87 +473,72 @@ public void afterTextChanged(Editable editable) { } }; - searchTextTask = - new SearchTextTask( - mainTextView.getText().toString(), - editable.toString(), - onProgressUpdate, - onAsyncTaskFinished); - searchTextTask.execute(); + if (mainTextView.getText() != null) { + searchTextTask = + new SearchTextTask( + mainTextView.getText().toString(), + editable.toString(), + onProgressUpdate, + onAsyncTaskFinished); + searchTextTask.execute(); + } } } - /** show search view with a circular reveal animation */ private void revealSearchView() { - int startRadius = 4; - int endRadius = Math.max(searchViewLayout.getWidth(), searchViewLayout.getHeight()); - DisplayMetrics metrics = new DisplayMetrics(); - getWindowManager().getDefaultDisplay().getMetrics(metrics); + searchViewLayout.setVisibility(View.VISIBLE); - // hardcoded and completely random - int cx = metrics.widthPixels - 160; - int cy = toolbar.getBottom(); - Animator animator; + Animation animation = AnimationUtils.loadAnimation(this, R.anim.fade_in_top); - // FIXME: 2016/11/18 ViewAnimationUtils Compatibility - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) - animator = - ViewAnimationUtils.createCircularReveal(searchViewLayout, cx, cy, startRadius, endRadius); - else animator = ObjectAnimator.ofFloat(searchViewLayout, "alpha", 0f, 1f); + animation.setAnimationListener( + new Animation.AnimationListener() { + @Override + public void onAnimationStart(Animation animation) {} - animator.setInterpolator(new AccelerateDecelerateInterpolator()); - animator.setDuration(600); - searchViewLayout.setVisibility(View.VISIBLE); - searchEditText.setText(""); - animator.start(); - animator.addListener( - new AnimatorListenerAdapter() { @Override - public void onAnimationEnd(Animator animation) { + public void onAnimationEnd(Animation animation) { + searchEditText.requestFocus(); - InputMethodManager imm = - (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); - imm.showSoftInput(searchEditText, InputMethodManager.SHOW_IMPLICIT); + + ((InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE)) + .showSoftInput(searchEditText, InputMethodManager.SHOW_IMPLICIT); } + + @Override + public void onAnimationRepeat(Animation animation) {} }); + + searchViewLayout.startAnimation(animation); } - /** hide search view with a circular reveal animation */ private void hideSearchView() { - int endRadius = 4; - int startRadius = Math.max(searchViewLayout.getWidth(), searchViewLayout.getHeight()); - - DisplayMetrics metrics = new DisplayMetrics(); - getWindowManager().getDefaultDisplay().getMetrics(metrics); - // hardcoded and completely random - int cx = metrics.widthPixels - 160; - int cy = toolbar.getBottom(); + Animation animation = AnimationUtils.loadAnimation(this, R.anim.fade_out_top); - Animator animator; - // FIXME: 2016/11/18 ViewAnimationUtils Compatibility - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - animator = - ViewAnimationUtils.createCircularReveal(searchViewLayout, cx, cy, startRadius, endRadius); - } else { - animator = ObjectAnimator.ofFloat(searchViewLayout, "alpha", 0f, 1f); - } + animation.setAnimationListener( + new Animation.AnimationListener() { + @Override + public void onAnimationStart(Animation animation) {} - animator.setInterpolator(new AccelerateDecelerateInterpolator()); - animator.setDuration(600); - animator.start(); - animator.addListener( - new AnimatorListenerAdapter() { @Override - public void onAnimationEnd(Animator animation) { + public void onAnimationEnd(Animation animation) { + searchViewLayout.setVisibility(View.GONE); - InputMethodManager inputMethodManager = - (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE); - inputMethodManager.hideSoftInputFromWindow( - searchEditText.getWindowToken(), InputMethodManager.HIDE_IMPLICIT_ONLY); + + cleanSpans(viewModel); + searchEditText.setText(""); + + ((InputMethodManager) getSystemService(INPUT_METHOD_SERVICE)) + .hideSoftInputFromWindow( + searchEditText.getWindowToken(), InputMethodManager.HIDE_IMPLICIT_ONLY); } + + @Override + public void onAnimationRepeat(Animation animation) {} }); + + searchViewLayout.startAnimation(animation); } @Override @@ -549,7 +547,7 @@ public void onClick(View v) { new ViewModelProvider(this).get(TextEditorActivityViewModel.class); switch (v.getId()) { - case R.id.prev: + case R.id.textEditorSearchPrevButton: // upButton if (viewModel.getCurrent() > 0) { unhighlightCurrentSearchResult(viewModel); @@ -560,7 +558,7 @@ public void onClick(View v) { highlightCurrentSearchResult(viewModel); } break; - case R.id.next: + case R.id.textEditorSearchNextButton: // downButton if (viewModel.getCurrent() < viewModel.getSearchResultIndices().size() - 1) { unhighlightCurrentSearchResult(viewModel); @@ -570,11 +568,6 @@ public void onClick(View v) { highlightCurrentSearchResult(viewModel); } break; - case R.id.close: - // closeButton - findViewById(R.id.searchview).setVisibility(View.GONE); - cleanSpans(viewModel); - break; default: throw new IllegalStateException(); } @@ -586,41 +579,34 @@ private void unhighlightCurrentSearchResult(final TextEditorActivityViewModel vi } SearchResultIndex resultIndex = viewModel.getSearchResultIndices().get(viewModel.getCurrent()); - unhighlightSearchResult(resultIndex); + colorSearchResult(resultIndex, getPrimary()); } private void highlightCurrentSearchResult(final TextEditorActivityViewModel viewModel) { SearchResultIndex keyValueNew = viewModel.getSearchResultIndices().get(viewModel.getCurrent()); - colorSearchResult(keyValueNew, Utils.getColor(this, R.color.search_text_highlight)); + colorSearchResult(keyValueNew, getAccent()); // scrolling to the highlighted element - scrollView.scrollTo( - 0, - (Integer) keyValueNew.getLineNumber() - + mainTextView.getLineHeight() - + Math.round(mainTextView.getLineSpacingExtra()) - - getSupportActionBar().getHeight()); - } - - private void unhighlightSearchResult(SearchResultIndex resultIndex) { - @ColorInt int color; - if (getAppTheme().equals(AppTheme.LIGHT)) { - color = Color.YELLOW; - } else { - color = Color.LTGRAY; + if (getSupportActionBar() != null) { + scrollView.scrollTo( + 0, + (Integer) keyValueNew.getLineNumber() + + mainTextView.getLineHeight() + + Math.round(mainTextView.getLineSpacingExtra()) + - getSupportActionBar().getHeight()); } - - colorSearchResult(resultIndex, color); } private void colorSearchResult(SearchResultIndex resultIndex, @ColorInt int color) { - mainTextView - .getText() - .setSpan( - new BackgroundColorSpan(color), - (Integer) resultIndex.getStartCharNumber(), - (Integer) resultIndex.getEndCharNumber(), - Spanned.SPAN_INCLUSIVE_INCLUSIVE); + if (mainTextView.getText() != null) { + mainTextView + .getText() + .setSpan( + new BackgroundColorSpan(color), + (Integer) resultIndex.getStartCharNumber(), + (Integer) resultIndex.getEndCharNumber(), + Spanned.SPAN_INCLUSIVE_INCLUSIVE); + } } private void cleanSpans(TextEditorActivityViewModel viewModel) { @@ -630,10 +616,12 @@ private void cleanSpans(TextEditorActivityViewModel viewModel) { viewModel.setLine(0); // clearing textView spans - BackgroundColorSpan[] colorSpans = - mainTextView.getText().getSpans(0, mainTextView.length(), BackgroundColorSpan.class); - for (BackgroundColorSpan colorSpan : colorSpans) { - mainTextView.getText().removeSpan(colorSpan); + if (mainTextView.getText() != null) { + BackgroundColorSpan[] colorSpans = + mainTextView.getText().getSpans(0, mainTextView.length(), BackgroundColorSpan.class); + for (BackgroundColorSpan colorSpan : colorSpans) { + mainTextView.getText().removeSpan(colorSpan); + } } } } diff --git a/app/src/main/java/com/amaze/filemanager/ui/activities/texteditor/TextEditorActivityViewModel.kt b/app/src/main/java/com/amaze/filemanager/ui/activities/texteditor/TextEditorActivityViewModel.kt index 91b34b97d7..83a57ef534 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/activities/texteditor/TextEditorActivityViewModel.kt +++ b/app/src/main/java/com/amaze/filemanager/ui/activities/texteditor/TextEditorActivityViewModel.kt @@ -23,10 +23,9 @@ package com.amaze.filemanager.ui.activities.texteditor import androidx.lifecycle.ViewModel import com.amaze.filemanager.filesystem.EditableFileAbstraction import java.io.File -import java.util.* +import java.util.Timer class TextEditorActivityViewModel : ViewModel() { - var original: String? = null /** diff --git a/app/src/main/java/com/amaze/filemanager/ui/base/BaseBottomSheetFragment.kt b/app/src/main/java/com/amaze/filemanager/ui/base/BaseBottomSheetFragment.kt index 78c3b6449f..aee378a9cb 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/base/BaseBottomSheetFragment.kt +++ b/app/src/main/java/com/amaze/filemanager/ui/base/BaseBottomSheetFragment.kt @@ -30,7 +30,6 @@ import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.bottomsheet.BottomSheetDialogFragment open class BaseBottomSheetFragment : BottomSheetDialogFragment() { - override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { return super.onCreateDialog(savedInstanceState) as BottomSheetDialog } @@ -39,27 +38,27 @@ open class BaseBottomSheetFragment : BottomSheetDialogFragment() { * Initializes bottom sheet ui resources based on current theme */ fun initDialogResources(rootView: View) { - when ((activity as ThemedActivity?)!!.appTheme!!) { + when ((requireActivity() as ThemedActivity).appTheme!!) { AppTheme.DARK -> { rootView.setBackgroundDrawable( context?.resources?.getDrawable( - R.drawable.shape_dialog_bottomsheet_dark - ) + R.drawable.shape_dialog_bottomsheet_dark, + ), ) } AppTheme.BLACK -> { rootView.setBackgroundDrawable( context?.resources?.getDrawable( - R.drawable.shape_dialog_bottomsheet_black - ) + R.drawable.shape_dialog_bottomsheet_black, + ), ) } - AppTheme.LIGHT, AppTheme.TIMED -> { + AppTheme.LIGHT -> { rootView .setBackgroundDrawable( context?.resources?.getDrawable( - R.drawable.shape_dialog_bottomsheet_white - ) + R.drawable.shape_dialog_bottomsheet_white, + ), ) } } diff --git a/app/src/main/java/com/amaze/filemanager/ui/colors/ColorPreference.java b/app/src/main/java/com/amaze/filemanager/ui/colors/ColorPreference.java index 4cf8184814..988af9e4d5 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/colors/ColorPreference.java +++ b/app/src/main/java/com/amaze/filemanager/ui/colors/ColorPreference.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. diff --git a/app/src/main/java/com/amaze/filemanager/ui/colors/ColorPreferenceHelper.java b/app/src/main/java/com/amaze/filemanager/ui/colors/ColorPreferenceHelper.java index b3d5c1aa18..85066ee117 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/colors/ColorPreferenceHelper.java +++ b/app/src/main/java/com/amaze/filemanager/ui/colors/ColorPreferenceHelper.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. diff --git a/app/src/main/java/com/amaze/filemanager/ui/colors/ColorUtils.java b/app/src/main/java/com/amaze/filemanager/ui/colors/ColorUtils.java index 872e4aaa71..bd358d1c84 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/colors/ColorUtils.java +++ b/app/src/main/java/com/amaze/filemanager/ui/colors/ColorUtils.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. diff --git a/app/src/main/java/com/amaze/filemanager/ui/colors/UserColorPreferences.kt b/app/src/main/java/com/amaze/filemanager/ui/colors/UserColorPreferences.kt index b8c5e4f9dc..b8ada9589b 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/colors/UserColorPreferences.kt +++ b/app/src/main/java/com/amaze/filemanager/ui/colors/UserColorPreferences.kt @@ -29,5 +29,5 @@ class UserColorPreferences( @ColorInt val primaryFirstTab: Int, @ColorInt val primarySecondTab: Int, @ColorInt val accent: Int, - @ColorInt val iconSkin: Int + @ColorInt val iconSkin: Int, ) : Parcelable diff --git a/app/src/main/java/com/amaze/filemanager/ui/dialogs/AlertDialog.kt b/app/src/main/java/com/amaze/filemanager/ui/dialogs/AlertDialog.kt index 86bae17c22..91e0aefeab 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/dialogs/AlertDialog.kt +++ b/app/src/main/java/com/amaze/filemanager/ui/dialogs/AlertDialog.kt @@ -29,7 +29,6 @@ import com.amaze.filemanager.ui.activities.superclasses.ThemedActivity * Alert Dialog. */ object AlertDialog { - /** * Display an alert dialog. Optionally accepts a [MaterialDialog.SingleButtonCallback] to * provide additional behaviour when dialog button is pressed. @@ -43,20 +42,21 @@ object AlertDialog { @StringRes title: Int, @StringRes positiveButtonText: Int = android.R.string.ok, @Nullable onPositive: MaterialDialog.SingleButtonCallback? = null, - contentIsHtml: Boolean = false + contentIsHtml: Boolean = false, ) { val accentColor: Int = activity.accent - val a = MaterialDialog.Builder(activity) - .content(content, contentIsHtml) - .widgetColor(accentColor) - .theme( - activity - .appTheme - .getMaterialDialogTheme(activity.applicationContext) - ) - .title(title) - .positiveText(positiveButtonText) - .positiveColor(accentColor) + val a = + MaterialDialog.Builder(activity) + .content(content, contentIsHtml) + .widgetColor(accentColor) + .theme( + activity + .appTheme + .getMaterialDialogTheme(), + ) + .title(title) + .positiveText(positiveButtonText) + .positiveColor(accentColor) if (onPositive != null) { a.onPositive(onPositive) diff --git a/app/src/main/java/com/amaze/filemanager/ui/dialogs/ColorPickerDialog.java b/app/src/main/java/com/amaze/filemanager/ui/dialogs/ColorPickerDialog.java index 18aa5742b3..93cb5406c1 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/dialogs/ColorPickerDialog.java +++ b/app/src/main/java/com/amaze/filemanager/ui/dialogs/ColorPickerDialog.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. @@ -43,10 +43,11 @@ import android.view.View; import android.widget.LinearLayout; import android.widget.RadioButton; -import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.StringRes; +import androidx.appcompat.widget.AppCompatButton; +import androidx.appcompat.widget.AppCompatTextView; import androidx.core.util.Pair; import androidx.preference.Preference.BaseSavedState; import androidx.preference.PreferenceDialogFragmentCompat; @@ -116,7 +117,7 @@ public static ColorPickerDialog newInstance( final Bundle b = new Bundle(2); b.putString(ARG_KEY, key); b.putParcelable(ARG_COLOR_PREF, color); - b.putInt(ARG_APP_THEME, theme.ordinal()); + b.putString(ARG_APP_THEME, theme.toString()); retval.setArguments(b); return retval; } @@ -167,13 +168,12 @@ public void onBindDialogView(View view) { select(selectedItem, true); } - ((TextView) child.findViewById(R.id.text)).setText(COLORS[i].first); + ((AppCompatTextView) child.findViewById(R.id.text)).setText(COLORS[i].first); CircularColorsView colorsView = child.findViewById(R.id.circularColorsView); colorsView.setColors(getColor(i, 0), getColor(i, 1), getColor(i, 2), getColor(i, 3)); - AppTheme appTheme = - AppTheme.getTheme(requireContext(), requireArguments().getInt(ARG_APP_THEME)); - if (appTheme.getMaterialDialogTheme(requireContext()) == Theme.LIGHT) - colorsView.setDividerColor(Color.WHITE); + + AppTheme appTheme = AppTheme.valueOf(requireArguments().getString(ARG_APP_THEME)); + if (appTheme.getMaterialDialogTheme() == Theme.LIGHT) colorsView.setDividerColor(Color.WHITE); else colorsView.setDividerColor(Color.BLACK); container.addView(child); } @@ -185,7 +185,7 @@ public void onBindDialogView(View view) { select(selectedItem, true); } - ((TextView) child.findViewById(R.id.text)).setText(R.string.custom); + ((AppCompatTextView) child.findViewById(R.id.text)).setText(R.string.custom); child.findViewById(R.id.circularColorsView).setVisibility(View.INVISIBLE); container.addView(child); } @@ -197,7 +197,7 @@ public void onBindDialogView(View view) { select(selectedItem, true); } - ((TextView) child.findViewById(R.id.text)).setText(R.string.random); + ((AppCompatTextView) child.findViewById(R.id.text)).setText(R.string.random); child.findViewById(R.id.circularColorsView).setVisibility(View.INVISIBLE); container.addView(child); } @@ -249,9 +249,9 @@ public Dialog onCreateDialog(Bundle savedInstanceState) { ((UserColorPreferences) requireArguments().getParcelable(ARG_COLOR_PREF)).getAccent(); // Button views - ((TextView) dialog.findViewById(res.getIdentifier("button1", "id", "android"))) + ((AppCompatButton) dialog.findViewById(res.getIdentifier("button1", "id", "android"))) .setTextColor(accentColor); - ((TextView) dialog.findViewById(res.getIdentifier("button2", "id", "android"))) + ((AppCompatButton) dialog.findViewById(res.getIdentifier("button2", "id", "android"))) .setTextColor(accentColor); return dialog; diff --git a/app/src/main/java/com/amaze/filemanager/ui/dialogs/DecryptFingerprintDialog.kt b/app/src/main/java/com/amaze/filemanager/ui/dialogs/DecryptFingerprintDialog.kt index 149a24a319..f73cfec0ee 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/dialogs/DecryptFingerprintDialog.kt +++ b/app/src/main/java/com/amaze/filemanager/ui/dialogs/DecryptFingerprintDialog.kt @@ -25,8 +25,8 @@ import android.content.Intent import android.hardware.fingerprint.FingerprintManager import android.os.Build import android.view.View -import android.widget.Button import androidx.annotation.RequiresApi +import androidx.appcompat.widget.AppCompatButton import com.afollestad.materialdialogs.MaterialDialog import com.amaze.filemanager.R import com.amaze.filemanager.filesystem.files.CryptUtil @@ -41,7 +41,6 @@ import java.security.GeneralSecurityException * Decrypt dialog prompt for user fingerprint. */ object DecryptFingerprintDialog { - /** * Display dialog prompting user for fingerprint in order to decrypt file. */ @@ -49,24 +48,27 @@ object DecryptFingerprintDialog { @RequiresApi(api = Build.VERSION_CODES.M) @Throws( GeneralSecurityException::class, - IOException::class + IOException::class, ) fun show( c: Context, main: MainActivity, intent: Intent, appTheme: AppTheme, - decryptButtonCallbackInterface: DecryptButtonCallbackInterface + decryptButtonCallbackInterface: DecryptButtonCallbackInterface, ) { val accentColor = main.accent val builder = MaterialDialog.Builder(c) builder.title(c.getString(R.string.crypt_decrypt)) val rootView = View.inflate(c, R.layout.dialog_decrypt_fingerprint_authentication, null) - val cancelButton = rootView.findViewById