Skip to content

Commit 07a9c7b

Browse files
committed
ISO9660 and ZStandard archive support
Fixes #2988 Fixes #3318
1 parent 6783e4d commit 07a9c7b

36 files changed

+747
-38
lines changed

app/build.gradle

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ dependencies {
151151
testImplementation "org.jsoup:jsoup:$jsoupVersion"
152152
testImplementation "androidx.room:room-migration:$roomVersion"
153153
testImplementation "io.mockk:mockk:$mockkVersion"
154+
testImplementation "com.github.luben:zstd-jni:${zstdJniVersion}"
154155
kaptTest "com.google.auto.service:auto-service:1.0-rc4"
155156

156157
androidTestImplementation "junit:junit:$junitVersion"//tests the app logic
@@ -170,6 +171,14 @@ dependencies {
170171

171172
playImplementation "com.github.junrar:junrar:$junrarVersion"
172173

174+
implementation "com.github.luben:zstd-jni:${zstdJniVersion}@aar"
175+
implementation 'com.github.stephenc.java-iso-tools:loopy-core:1.2.2'
176+
177+
implementation "com.github.luben:zstd-jni:${zstdJniVersion}@aar"
178+
implementation ("com.github.stephenc.java-iso-tools:loopy-core:1.2.2") {
179+
transitive = false
180+
}
181+
173182
implementation "com.afollestad.material-dialogs:core:$materialDialogsVersion"
174183
implementation "com.afollestad.material-dialogs:commons:$materialDialogsVersion"
175184

@@ -255,7 +264,6 @@ dependencies {
255264
configurations.all {
256265
resolutionStrategy {
257266
dependencySubstitution {
258-
substitute module("commons-logging:commons-logging-api:1.1") with module("commons-logging:commons-logging:1.1.1")
259267
substitute module("com.android.support:support-annotations:27.1.1") with module("com.android.support:support-annotations:27.0.2")
260268
// These two lines are added to prevent possible class clashes between awaitility (which uses hamcrest 2.1) and junit (which uses hamcrest 1.3).
261269
substitute module('org.hamcrest:hamcrest-core:1.3') with module("org.hamcrest:hamcrest:2.1")

app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/compress/AbstractCommonsArchiveHelperCallable.kt

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,8 @@ import org.apache.commons.compress.archivers.ArchiveEntry
2929
import org.apache.commons.compress.archivers.ArchiveException
3030
import org.apache.commons.compress.archivers.ArchiveInputStream
3131
import java.io.FileInputStream
32-
import java.io.IOException
3332
import java.io.InputStream
3433
import java.lang.ref.WeakReference
35-
import java.util.*
3634

3735
abstract class AbstractCommonsArchiveHelperCallable(
3836
context: Context,
@@ -54,7 +52,7 @@ abstract class AbstractCommonsArchiveHelperCallable(
5452
@Throws(ArchiveException::class)
5553
@Suppress("LabeledExpression")
5654
public override fun addElements(elements: ArrayList<CompressedObjectParcelable>) {
57-
try {
55+
runCatching {
5856
createFrom(FileInputStream(filePath)).use { tarInputStream ->
5957
var entry: ArchiveEntry?
6058
while (tarInputStream.nextEntry.also { entry = it } != null) {
@@ -90,8 +88,9 @@ abstract class AbstractCommonsArchiveHelperCallable(
9088
}
9189
}
9290
}
93-
} catch (e: IOException) {
94-
throw ArchiveException(String.format("Tarball archive %s is corrupt", filePath), e)
91+
}.onFailure {
92+
logger.error("Error enumerating archive entries", it)
93+
throw ArchiveException(String.format("Tarball archive %s is corrupt", filePath))
9594
}
9695
}
9796
}

app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/compress/CompressedHelperCallable.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ package com.amaze.filemanager.asynchronous.asynctasks.compress
2323
import androidx.annotation.WorkerThread
2424
import com.amaze.filemanager.adapters.data.CompressedObjectParcelable
2525
import org.apache.commons.compress.archivers.ArchiveException
26+
import org.slf4j.Logger
27+
import org.slf4j.LoggerFactory
2628
import java.util.*
2729
import java.util.concurrent.Callable
2830

@@ -31,6 +33,8 @@ abstract class CompressedHelperCallable internal constructor(
3133
) :
3234
Callable<ArrayList<CompressedObjectParcelable>> {
3335

36+
protected val logger: Logger = LoggerFactory.getLogger(javaClass)
37+
3438
@WorkerThread
3539
@Throws(ArchiveException::class)
3640
override fun call(): ArrayList<CompressedObjectParcelable> {
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
/*
2+
* Copyright (C) 2014-2023 Arpit Khurana <arpitkh96@gmail.com>, Vishal Nehra <vishalmeham2@gmail.com>,
3+
* Emmanuel Messulam<emmanuelbendavid@gmail.com>, Raymond Lai <airwave209gt at gmail.com> and Contributors.
4+
*
5+
* This file is part of Amaze File Manager.
6+
*
7+
* Amaze File Manager is free software: you can redistribute it and/or modify
8+
* it under the terms of the GNU General Public License as published by
9+
* the Free Software Foundation, either version 3 of the License, or
10+
* (at your option) any later version.
11+
*
12+
* This program is distributed in the hope that it will be useful,
13+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
* GNU General Public License for more details.
16+
*
17+
* You should have received a copy of the GNU General Public License
18+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
19+
*/
20+
21+
package com.amaze.filemanager.asynchronous.asynctasks.compress
22+
23+
import com.amaze.filemanager.adapters.data.CompressedObjectParcelable
24+
import com.amaze.filemanager.filesystem.compressed.CompressedHelper
25+
import com.amaze.filemanager.filesystem.compressed.extractcontents.Extractor
26+
import net.didion.loopy.FileEntry
27+
import net.didion.loopy.iso9660.ISO9660FileEntry
28+
import net.didion.loopy.iso9660.ISO9660FileSystem
29+
import java.io.File
30+
import java.io.IOException
31+
import java.lang.reflect.Field
32+
33+
class Iso9660HelperCallable(
34+
private val filePath: String,
35+
private val relativePath: String,
36+
createBackItem: Boolean
37+
) : CompressedHelperCallable(createBackItem) {
38+
39+
private val SLASH = Regex("/")
40+
41+
// Hack. ISO9660FileEntry doesn't have getter for parentPath, we need to read it on our own
42+
private val parentPathField: Field =
43+
ISO9660FileEntry::class.java.getDeclaredField("parentPath").also {
44+
it.isAccessible = true
45+
}
46+
47+
override fun addElements(elements: ArrayList<CompressedObjectParcelable>) {
48+
val isoFile = ISO9660FileSystem(File(filePath), true)
49+
50+
val fileEntries: List<FileEntry> = runCatching {
51+
isoFile.entries?.let { isoFileEntries ->
52+
isoFileEntries.runCatching {
53+
isoFileEntries.toList().partition { entry ->
54+
CompressedHelper.isEntryPathValid((entry as FileEntry).path)
55+
}.let { pair ->
56+
pair.first as List<FileEntry>
57+
}
58+
}.onFailure {
59+
return
60+
}.getOrThrow()
61+
} ?: throw IOException("Empty archive or file is corrupt")
62+
}.onFailure {
63+
throw Extractor.BadArchiveNotice(it)
64+
}.getOrThrow().filter {
65+
it.name != "."
66+
}
67+
68+
val slashCount = if (relativePath == "") {
69+
0
70+
} else {
71+
SLASH.findAll("$relativePath/").count()
72+
}
73+
74+
fileEntries.filter {
75+
val parentPath = parentPathField.get(it)?.toString() ?: ""
76+
(
77+
if (slashCount == 0) {
78+
parentPath == ""
79+
} else {
80+
parentPath == "$relativePath/"
81+
}
82+
)
83+
}.forEach { entry ->
84+
elements.add(
85+
CompressedObjectParcelable(
86+
entry.name,
87+
entry.lastModifiedTime,
88+
entry.size.toLong(),
89+
entry.isDirectory
90+
)
91+
)
92+
}
93+
}
94+
}

app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/compress/SevenZipHelperCallable.kt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ import org.apache.commons.compress.PasswordRequiredException
2929
import org.apache.commons.compress.archivers.ArchiveException
3030
import java.io.File
3131
import java.io.IOException
32-
import java.lang.UnsupportedOperationException
3332

3433
class SevenZipHelperCallable(
3534
private val filePath: String,
@@ -58,7 +57,7 @@ class SevenZipHelperCallable(
5857
)
5958
val isInRelativeDir = (
6059
name.contains(CompressedHelper.SEPARATOR) &&
61-
name.substring(0, name.lastIndexOf(CompressedHelper.SEPARATOR))
60+
name.substringBeforeLast(CompressedHelper.SEPARATOR)
6261
== relativePath
6362
)
6463
if (isInBaseDir || isInRelativeDir) {
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
* Copyright (C) 2014-2021 Arpit Khurana <arpitkh96@gmail.com>, Vishal Nehra <vishalmeham2@gmail.com>,
3+
* Emmanuel Messulam<emmanuelbendavid@gmail.com>, Raymond Lai <airwave209gt at gmail.com> and Contributors.
4+
*
5+
* This file is part of Amaze File Manager.
6+
*
7+
* Amaze File Manager is free software: you can redistribute it and/or modify
8+
* it under the terms of the GNU General Public License as published by
9+
* the Free Software Foundation, either version 3 of the License, or
10+
* (at your option) any later version.
11+
*
12+
* This program is distributed in the hope that it will be useful,
13+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
* GNU General Public License for more details.
16+
*
17+
* You should have received a copy of the GNU General Public License
18+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
19+
*/
20+
21+
package com.amaze.filemanager.asynchronous.asynctasks.compress
22+
23+
import android.content.Context
24+
import org.apache.commons.compress.compressors.CompressorInputStream
25+
import org.apache.commons.compress.compressors.zstandard.ZstdCompressorInputStream
26+
27+
class TarZstHelperCallable(
28+
context: Context,
29+
filePath: String,
30+
relativePath: String,
31+
goBack: Boolean
32+
) :
33+
AbstractCompressedTarArchiveHelperCallable(context, filePath, relativePath, goBack) {
34+
35+
override fun getCompressorInputStreamClass(): Class<out CompressorInputStream> =
36+
ZstdCompressorInputStream::class.java
37+
}

app/src/main/java/com/amaze/filemanager/filesystem/compressed/CompressedHelper.java

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import com.amaze.filemanager.filesystem.compressed.extractcontents.Extractor;
3131
import com.amaze.filemanager.filesystem.compressed.extractcontents.helpers.Bzip2Extractor;
3232
import com.amaze.filemanager.filesystem.compressed.extractcontents.helpers.GzipExtractor;
33+
import com.amaze.filemanager.filesystem.compressed.extractcontents.helpers.Iso9660Extractor;
3334
import com.amaze.filemanager.filesystem.compressed.extractcontents.helpers.LzmaExtractor;
3435
import com.amaze.filemanager.filesystem.compressed.extractcontents.helpers.RarExtractor;
3536
import com.amaze.filemanager.filesystem.compressed.extractcontents.helpers.SevenZipExtractor;
@@ -38,16 +39,20 @@
3839
import com.amaze.filemanager.filesystem.compressed.extractcontents.helpers.TarGzExtractor;
3940
import com.amaze.filemanager.filesystem.compressed.extractcontents.helpers.TarLzmaExtractor;
4041
import com.amaze.filemanager.filesystem.compressed.extractcontents.helpers.TarXzExtractor;
42+
import com.amaze.filemanager.filesystem.compressed.extractcontents.helpers.TarZstExtractor;
4143
import com.amaze.filemanager.filesystem.compressed.extractcontents.helpers.XzExtractor;
4244
import com.amaze.filemanager.filesystem.compressed.extractcontents.helpers.ZipExtractor;
45+
import com.amaze.filemanager.filesystem.compressed.extractcontents.helpers.ZstdExtractor;
4346
import com.amaze.filemanager.filesystem.compressed.showcontents.Decompressor;
47+
import com.amaze.filemanager.filesystem.compressed.showcontents.helpers.Iso9660Decompressor;
4448
import com.amaze.filemanager.filesystem.compressed.showcontents.helpers.RarDecompressor;
4549
import com.amaze.filemanager.filesystem.compressed.showcontents.helpers.SevenZipDecompressor;
4650
import com.amaze.filemanager.filesystem.compressed.showcontents.helpers.TarBzip2Decompressor;
4751
import com.amaze.filemanager.filesystem.compressed.showcontents.helpers.TarDecompressor;
4852
import com.amaze.filemanager.filesystem.compressed.showcontents.helpers.TarGzDecompressor;
4953
import com.amaze.filemanager.filesystem.compressed.showcontents.helpers.TarLzmaDecompressor;
5054
import com.amaze.filemanager.filesystem.compressed.showcontents.helpers.TarXzDecompressor;
55+
import com.amaze.filemanager.filesystem.compressed.showcontents.helpers.TarZstDecompressor;
5156
import com.amaze.filemanager.filesystem.compressed.showcontents.helpers.UnknownCompressedFileDecompressor;
5257
import com.amaze.filemanager.filesystem.compressed.showcontents.helpers.ZipDecompressor;
5358
import com.amaze.filemanager.utils.Utils;
@@ -80,10 +85,14 @@ public abstract class CompressedHelper {
8085
public static final String fileExtension7zip = "7z";
8186
public static final String fileExtensionTarLzma = "tar.lzma";
8287
public static final String fileExtensionTarXz = "tar.xz";
88+
public static final String fileExtensionTarZst = "tar.zst";
8389
public static final String fileExtensionXz = "xz";
8490
public static final String fileExtensionLzma = "lzma";
8591
public static final String fileExtensionGz = "gz";
8692
public static final String fileExtensionBzip2 = "bz2";
93+
public static final String fileExtensionZst = "zst";
94+
95+
public static final String fileExtensionIso = "iso";
8796

8897
private static final String TAG = CompressedHelper.class.getSimpleName();
8998

@@ -114,6 +123,9 @@ public static Extractor getExtractorInstance(
114123
} else if (isLzippedTar(type)) {
115124
extractor =
116125
new TarLzmaExtractor(context, file.getPath(), outputPath, listener, updatePosition);
126+
} else if (isZstdTar(type)) {
127+
extractor =
128+
new TarZstExtractor(context, file.getPath(), outputPath, listener, updatePosition);
117129
} else if (is7zip(type)) {
118130
extractor =
119131
new SevenZipExtractor(context, file.getPath(), outputPath, listener, updatePosition);
@@ -125,6 +137,11 @@ public static Extractor getExtractorInstance(
125137
extractor = new GzipExtractor(context, file.getPath(), outputPath, listener, updatePosition);
126138
} else if (isBzip2(type)) {
127139
extractor = new Bzip2Extractor(context, file.getPath(), outputPath, listener, updatePosition);
140+
} else if (isZst(type)) {
141+
extractor = new ZstdExtractor(context, file.getPath(), outputPath, listener, updatePosition);
142+
} else if (isIso(type)) {
143+
extractor =
144+
new Iso9660Extractor(context, file.getPath(), outputPath, listener, updatePosition);
128145
} else {
129146
if (BuildConfig.DEBUG) {
130147
throw new IllegalArgumentException("The compressed file has no way of opening it: " + file);
@@ -156,9 +173,13 @@ public static Decompressor getCompressorInstance(@NonNull Context context, @NonN
156173
decompressor = new TarXzDecompressor(context);
157174
} else if (isLzippedTar(type)) {
158175
decompressor = new TarLzmaDecompressor(context);
176+
} else if (isZstdTar(type)) {
177+
decompressor = new TarZstDecompressor(context);
159178
} else if (is7zip(type)) {
160179
decompressor = new SevenZipDecompressor(context);
161-
} else if (isXz(type) || isLzma(type) || isGzip(type) || isBzip2(type)) {
180+
} else if (isIso(type)) {
181+
decompressor = new Iso9660Decompressor(context);
182+
} else if (isXz(type) || isLzma(type) || isGzip(type) || isBzip2(type) || isZst(type)) {
162183
// These 4 types are only compressing one single file.
163184
// Hence invoking this UnknownCompressedFileDecompressor which only returns the filename
164185
// without the compression extension
@@ -190,10 +211,13 @@ public static boolean isFileExtractable(String path) {
190211
|| isBzippedTar(type)
191212
|| isXzippedTar(type)
192213
|| isLzippedTar(type)
214+
|| isZstdTar(type)
193215
|| isBzip2(type)
194216
|| isGzip(type)
195217
|| isLzma(type)
196-
|| isXz(type);
218+
|| isXz(type)
219+
|| isZst(type)
220+
|| isIso(type);
197221
}
198222

199223
/**
@@ -214,12 +238,15 @@ public static String getFileName(String compressedName) {
214238
|| isGzip(compressedName)
215239
|| isBzip2(compressedName)
216240
|| isLzma(compressedName)
217-
|| isXz(compressedName)) {
241+
|| isXz(compressedName)
242+
|| isZst(compressedName)
243+
|| isIso(compressedName)) {
218244
return compressedName.substring(0, compressedName.lastIndexOf("."));
219245
} else if (isGzippedTar(compressedName)
220246
|| isXzippedTar(compressedName)
221247
|| isLzippedTar(compressedName)
222-
|| isBzippedTar(compressedName)) {
248+
|| isBzippedTar(compressedName)
249+
|| isZstdTar(compressedName)) {
223250
return compressedName.substring(0, Utils.nthToLastCharIndex(2, compressedName, '.'));
224251
} else {
225252
return compressedName;
@@ -265,6 +292,10 @@ private static boolean isLzippedTar(String type) {
265292
return type.endsWith(fileExtensionTarLzma);
266293
}
267294

295+
private static boolean isZstdTar(String type) {
296+
return type.endsWith(fileExtensionTarZst);
297+
}
298+
268299
private static boolean isXz(String type) {
269300
return type.endsWith(fileExtensionXz) && !isXzippedTar(type);
270301
}
@@ -281,6 +312,14 @@ private static boolean isBzip2(String type) {
281312
return type.endsWith(fileExtensionBzip2) && !isBzippedTar(type);
282313
}
283314

315+
private static boolean isZst(String type) {
316+
return type.endsWith(fileExtensionZst) && !isZstdTar(type);
317+
}
318+
319+
private static boolean isIso(String type) {
320+
return type.endsWith(fileExtensionIso);
321+
}
322+
284323
private static String getExtension(String path) {
285324
return path.substring(path.indexOf('.') + 1).toLowerCase();
286325
}

app/src/main/java/com/amaze/filemanager/filesystem/compressed/extractcontents/Extractor.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,5 +119,9 @@ public static class BadArchiveNotice extends IOException {
119119
public BadArchiveNotice(@NonNull Throwable reason) {
120120
super(reason);
121121
}
122+
123+
public BadArchiveNotice(@NonNull String reason) {
124+
super(reason);
125+
}
122126
}
123127
}

0 commit comments

Comments
 (0)