Skip to content

Commit 6ab71cf

Browse files
committed
implement compressing .7z, .tar.gz, .tar.xz
1 parent 44954f3 commit 6ab71cf

File tree

3 files changed

+256
-89
lines changed

3 files changed

+256
-89
lines changed

app/src/main/kotlin/com/simplemobiletools/filemanager/pro/adapters/ItemsAdapter.kt

Lines changed: 2 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,8 @@ import com.simplemobiletools.filemanager.pro.models.ListItem
4646
import com.stericson.RootTools.RootTools
4747
import net.lingala.zip4j.exception.ZipException
4848
import net.lingala.zip4j.io.inputstream.ZipInputStream
49-
import net.lingala.zip4j.io.outputstream.ZipOutputStream
5049
import net.lingala.zip4j.model.LocalFileHeader
51-
import net.lingala.zip4j.model.ZipParameters
52-
import net.lingala.zip4j.model.enums.EncryptionMethod
5350
import java.io.BufferedInputStream
54-
import java.io.Closeable
5551
import java.io.File
5652
import java.util.*
5753

@@ -476,7 +472,7 @@ class ItemsAdapter(
476472
return
477473
}
478474

479-
CompressAsDialog(activity, firstPath) { destination, password ->
475+
CompressAsDialog(activity, firstPath) { destination, compressionFormat, password ->
480476
activity.handleAndroidSAFDialog(firstPath) { granted ->
481477
if (!granted) {
482478
return@handleAndroidSAFDialog
@@ -489,7 +485,7 @@ class ItemsAdapter(
489485
activity.toast(R.string.compressing)
490486
val paths = getSelectedFileDirItems().map { it.path }
491487
ensureBackgroundThread {
492-
if (compressPaths(paths, destination, password)) {
488+
if (CompressionHelper.compress(activity, paths, destination, compressionFormat, password)) {
493489
activity.runOnUiThread {
494490
activity.toast(R.string.compression_successful)
495491
listener?.refreshFragment()
@@ -638,89 +634,6 @@ class ItemsAdapter(
638634
}
639635
}
640636

641-
@SuppressLint("NewApi")
642-
private fun compressPaths(sourcePaths: List<String>, targetPath: String, password: String? = null): Boolean {
643-
val queue = LinkedList<String>()
644-
val fos = activity.getFileOutputStreamSync(targetPath, "application/zip") ?: return false
645-
646-
val zout = password?.let { ZipOutputStream(fos, password.toCharArray()) } ?: ZipOutputStream(fos)
647-
var res: Closeable = fos
648-
649-
fun zipEntry(name: String) = ZipParameters().also {
650-
it.fileNameInZip = name
651-
if (password != null) {
652-
it.isEncryptFiles = true
653-
it.encryptionMethod = EncryptionMethod.AES
654-
}
655-
}
656-
657-
try {
658-
sourcePaths.forEach { currentPath ->
659-
var name: String
660-
var mainFilePath = currentPath
661-
val base = "${mainFilePath.getParentPath()}/"
662-
res = zout
663-
queue.push(mainFilePath)
664-
if (activity.getIsPathDirectory(mainFilePath)) {
665-
name = "${mainFilePath.getFilenameFromPath()}/"
666-
zout.putNextEntry(
667-
ZipParameters().also {
668-
it.fileNameInZip = name
669-
}
670-
)
671-
}
672-
673-
while (!queue.isEmpty()) {
674-
mainFilePath = queue.pop()
675-
if (activity.getIsPathDirectory(mainFilePath)) {
676-
if (activity.isRestrictedSAFOnlyRoot(mainFilePath)) {
677-
activity.getAndroidSAFFileItems(mainFilePath, true) { files ->
678-
for (file in files) {
679-
name = file.path.relativizeWith(base)
680-
if (activity.getIsPathDirectory(file.path)) {
681-
queue.push(file.path)
682-
name = "${name.trimEnd('/')}/"
683-
zout.putNextEntry(zipEntry(name))
684-
} else {
685-
zout.putNextEntry(zipEntry(name))
686-
activity.getFileInputStreamSync(file.path)!!.copyTo(zout)
687-
zout.closeEntry()
688-
}
689-
}
690-
}
691-
} else {
692-
val mainFile = File(mainFilePath)
693-
for (file in mainFile.listFiles()) {
694-
name = file.path.relativizeWith(base)
695-
if (activity.getIsPathDirectory(file.absolutePath)) {
696-
queue.push(file.absolutePath)
697-
name = "${name.trimEnd('/')}/"
698-
zout.putNextEntry(zipEntry(name))
699-
} else {
700-
zout.putNextEntry(zipEntry(name))
701-
activity.getFileInputStreamSync(file.path)!!.copyTo(zout)
702-
zout.closeEntry()
703-
}
704-
}
705-
}
706-
707-
} else {
708-
name = if (base == currentPath) currentPath.getFilenameFromPath() else mainFilePath.relativizeWith(base)
709-
zout.putNextEntry(zipEntry(name))
710-
activity.getFileInputStreamSync(mainFilePath)!!.copyTo(zout)
711-
zout.closeEntry()
712-
}
713-
}
714-
}
715-
} catch (exception: Exception) {
716-
activity.showErrorToast(exception)
717-
return false
718-
} finally {
719-
res.close()
720-
}
721-
return true
722-
}
723-
724637
private fun askConfirmDelete() {
725638
activity.handleDeletePasswordProtection {
726639
val itemsCnt = selectedKeys.size
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package com.simplemobiletools.filemanager.pro.helpers
2+
3+
import org.apache.commons.compress.compressors.CompressorStreamFactory
4+
5+
enum class CompressionFormat(
6+
val extension: String,
7+
val mimeType: String,
8+
val compressorStreamFactory: String,
9+
val canReadEncryptedArchive: Boolean,
10+
val canCreateEncryptedArchive: Boolean
11+
) {
12+
ZIP(".zip", "application/zip", "", true, true),
13+
SEVEN_ZIP(".7z", "application/x-7z-compressed", "", true, false),
14+
TAR_GZ(".tar.gz", "application/gzip", CompressorStreamFactory.GZIP, false, false),
15+
16+
// TAR_SZ(".tar.sz", "application/x-snappy-framed", CompressorStreamFactory.SNAPPY_FRAMED, false, false), // FIXME: ask for enabling it. it's not so common
17+
TAR_XZ(".tar.xz", "application/x-xz", CompressorStreamFactory.XZ, false, false);
18+
19+
companion object {
20+
fun fromExtension(extension: String): CompressionFormat {
21+
return when (extension.lowercase()) {
22+
ZIP.extension -> ZIP
23+
SEVEN_ZIP.extension -> SEVEN_ZIP
24+
TAR_GZ.extension -> TAR_GZ
25+
// TAR_SZ.extension -> TAR_SZ
26+
TAR_XZ.extension -> TAR_XZ
27+
else -> ZIP
28+
}
29+
}
30+
}
31+
}
Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
1+
package com.simplemobiletools.filemanager.pro.helpers
2+
3+
import com.simplemobiletools.commons.activities.BaseSimpleActivity
4+
import com.simplemobiletools.commons.extensions.*
5+
import net.lingala.zip4j.io.outputstream.ZipOutputStream
6+
import net.lingala.zip4j.model.ZipParameters
7+
import net.lingala.zip4j.model.enums.EncryptionMethod
8+
import org.apache.commons.compress.archivers.ArchiveEntry
9+
import org.apache.commons.compress.archivers.sevenz.SevenZOutputFile
10+
import org.apache.commons.compress.archivers.tar.TarArchiveEntry
11+
import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream
12+
import org.apache.commons.compress.compressors.CompressorStreamFactory
13+
import org.apache.commons.compress.utils.IOUtils
14+
import java.io.Closeable
15+
import java.io.File
16+
import java.io.FileInputStream
17+
import java.io.IOException
18+
import java.nio.file.Files
19+
import java.nio.file.Path
20+
import java.util.LinkedList
21+
22+
23+
class CompressionHelper {
24+
25+
companion object {
26+
fun compress(
27+
activity: BaseSimpleActivity,
28+
sourcePaths: List<String>,
29+
targetPath: String,
30+
compressionFormat: CompressionFormat,
31+
password: String? = null
32+
): Boolean {
33+
return when (compressionFormat) {
34+
CompressionFormat.ZIP -> compressToZip(activity, sourcePaths, targetPath, password)
35+
CompressionFormat.SEVEN_ZIP -> compressToSevenZip(activity, sourcePaths, targetPath)
36+
CompressionFormat.TAR_GZ,
37+
// CompressionFormat.TAR_SZ,
38+
CompressionFormat.TAR_XZ -> compressToTarVariants(activity, sourcePaths, targetPath, compressionFormat)
39+
}
40+
}
41+
42+
private fun compressToZip(
43+
activity: BaseSimpleActivity,
44+
sourcePaths: List<String>,
45+
targetPath: String,
46+
password: String? = null
47+
): Boolean {
48+
val queue = LinkedList<String>()
49+
val fos = activity.getFileOutputStreamSync(targetPath, CompressionFormat.ZIP.mimeType) ?: return false
50+
51+
val zout = password?.let { ZipOutputStream(fos, password.toCharArray()) } ?: ZipOutputStream(fos)
52+
var res: Closeable = fos
53+
54+
fun zipEntry(name: String) = ZipParameters().also {
55+
it.fileNameInZip = name
56+
if (password != null) {
57+
it.isEncryptFiles = true
58+
it.encryptionMethod = EncryptionMethod.AES
59+
}
60+
}
61+
62+
try {
63+
sourcePaths.forEach { currentPath ->
64+
var name: String
65+
var mainFilePath = currentPath
66+
val base = "${mainFilePath.getParentPath()}/"
67+
res = zout
68+
queue.push(mainFilePath)
69+
if (activity.getIsPathDirectory(mainFilePath)) {
70+
name = "${mainFilePath.getFilenameFromPath()}/"
71+
zout.putNextEntry(
72+
ZipParameters().also {
73+
it.fileNameInZip = name
74+
}
75+
)
76+
}
77+
78+
while (!queue.isEmpty()) {
79+
mainFilePath = queue.pop()
80+
if (activity.getIsPathDirectory(mainFilePath)) {
81+
if (activity.isRestrictedSAFOnlyRoot(mainFilePath)) {
82+
activity.getAndroidSAFFileItems(mainFilePath, true) { files ->
83+
for (file in files) {
84+
name = file.path.relativizeWith(base)
85+
if (activity.getIsPathDirectory(file.path)) {
86+
queue.push(file.path)
87+
name = "${name.trimEnd('/')}/"
88+
zout.putNextEntry(zipEntry(name))
89+
} else {
90+
zout.putNextEntry(zipEntry(name))
91+
activity.getFileInputStreamSync(file.path)!!.copyTo(zout)
92+
zout.closeEntry()
93+
}
94+
}
95+
}
96+
} else {
97+
val mainFile = File(mainFilePath)
98+
for (file in mainFile.listFiles()) {
99+
name = file.path.relativizeWith(base)
100+
if (activity.getIsPathDirectory(file.absolutePath)) {
101+
queue.push(file.absolutePath)
102+
name = "${name.trimEnd('/')}/"
103+
zout.putNextEntry(zipEntry(name))
104+
} else {
105+
zout.putNextEntry(zipEntry(name))
106+
activity.getFileInputStreamSync(file.path)!!.copyTo(zout)
107+
zout.closeEntry()
108+
}
109+
}
110+
}
111+
112+
} else {
113+
name = if (base == currentPath) currentPath.getFilenameFromPath() else mainFilePath.relativizeWith(base)
114+
zout.putNextEntry(zipEntry(name))
115+
activity.getFileInputStreamSync(mainFilePath)!!.copyTo(zout)
116+
zout.closeEntry()
117+
}
118+
}
119+
}
120+
} catch (exception: Exception) {
121+
activity.showErrorToast(exception)
122+
return false
123+
} finally {
124+
res.close()
125+
}
126+
return true
127+
}
128+
129+
private fun compressToSevenZip(
130+
activity: BaseSimpleActivity,
131+
sourcePaths: List<String>,
132+
targetPath: String,
133+
): Boolean {
134+
try {
135+
SevenZOutputFile(File(targetPath)).use { sevenZOutput ->
136+
sourcePaths.forEach { sourcePath ->
137+
Files.walk(File(sourcePath).toPath()).forEach { path ->
138+
val file = path.toFile()
139+
val basePath = "${sourcePath.getParentPath()}/"
140+
141+
if (!activity.getIsPathDirectory(file.absolutePath)) {
142+
FileInputStream(file).use { _ ->
143+
val entryName = if (basePath == sourcePath) {
144+
sourcePath.getFilenameFromPath()
145+
} else {
146+
path.toString().relativizeWith(basePath)
147+
}
148+
149+
val sevenZArchiveEntry = sevenZOutput.createArchiveEntry(file, entryName)
150+
sevenZOutput.putArchiveEntry(sevenZArchiveEntry)
151+
sevenZOutput.write(Files.readAllBytes(file.toPath()))
152+
sevenZOutput.closeArchiveEntry()
153+
}
154+
}
155+
}
156+
}
157+
158+
sevenZOutput.finish()
159+
}
160+
} catch (exception: IOException) {
161+
activity.showErrorToast(exception)
162+
return false
163+
}
164+
return true
165+
}
166+
167+
private fun compressToTarVariants(
168+
activity: BaseSimpleActivity,
169+
sourcePaths: List<String>,
170+
outFilePath: String,
171+
format: CompressionFormat
172+
): Boolean {
173+
if (!listOf(
174+
CompressionFormat.TAR_GZ,
175+
// CompressionFormat.TAR_SZ,
176+
CompressionFormat.TAR_XZ
177+
).contains(format)
178+
) {
179+
return false
180+
}
181+
182+
val fos = activity.getFileOutputStreamSync(outFilePath, format.mimeType)
183+
try {
184+
fos.use { fileOutputStream ->
185+
CompressorStreamFactory()
186+
.createCompressorOutputStream(format.compressorStreamFactory, fileOutputStream).use { compressedOut ->
187+
TarArchiveOutputStream(compressedOut).use { archive ->
188+
archive.setLongFileMode(TarArchiveOutputStream.LONGFILE_POSIX)
189+
sourcePaths.forEach { sourcePath ->
190+
val basePath = "${sourcePath.getParentPath()}/"
191+
Files.walk(File(sourcePath).toPath()).forEach { path: Path ->
192+
val file = path.toFile()
193+
194+
if (!activity.getIsPathDirectory(file.absolutePath)) {
195+
val entryName = if (basePath == sourcePath) {
196+
sourcePath.getFilenameFromPath()
197+
} else {
198+
path.toString().relativizeWith(basePath)
199+
}
200+
201+
val tarArchiveEntry: ArchiveEntry = TarArchiveEntry(file, entryName)
202+
FileInputStream(file).use { fis ->
203+
archive.putArchiveEntry(tarArchiveEntry)
204+
IOUtils.copy(fis, archive)
205+
archive.closeArchiveEntry()
206+
}
207+
}
208+
}
209+
}
210+
211+
archive.finish()
212+
}
213+
}
214+
}
215+
} catch (exception: IOException) {
216+
activity.showErrorToast(exception)
217+
return false
218+
}
219+
return true
220+
}
221+
222+
}
223+
}

0 commit comments

Comments
 (0)