Add support for creating password protected zips

This commit is contained in:
Ensar Sarajčić 2023-07-07 13:09:51 +02:00
parent 2ac5648037
commit 9a640c802c
4 changed files with 69 additions and 17 deletions

View File

@ -64,7 +64,7 @@ android {
} }
dependencies { dependencies {
implementation 'com.github.SimpleMobileTools:Simple-Commons:84c71fdcc1' implementation 'com.github.SimpleMobileTools:Simple-Commons:db25f91be3'
implementation 'com.github.tibbi:AndroidPdfViewer:e6a533125b' implementation 'com.github.tibbi:AndroidPdfViewer:e6a533125b'
implementation 'com.github.Stericson:RootTools:df729dcb13' implementation 'com.github.Stericson:RootTools:df729dcb13'
implementation 'com.github.Stericson:RootShell:1.6' implementation 'com.github.Stericson:RootShell:1.6'
@ -72,4 +72,5 @@ dependencies {
implementation 'androidx.documentfile:documentfile:1.0.1' implementation 'androidx.documentfile:documentfile:1.0.1'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0' implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
implementation 'me.grantland:autofittextview:0.2.1' implementation 'me.grantland:autofittextview:0.2.1'
implementation 'net.lingala.zip4j:zip4j:2.11.5'
} }

View File

@ -44,13 +44,14 @@ import kotlinx.android.synthetic.main.item_file_dir_list.view.item_icon
import kotlinx.android.synthetic.main.item_file_dir_list.view.item_name import kotlinx.android.synthetic.main.item_file_dir_list.view.item_name
import kotlinx.android.synthetic.main.item_file_grid.view.* import kotlinx.android.synthetic.main.item_file_grid.view.*
import kotlinx.android.synthetic.main.item_section.view.* import kotlinx.android.synthetic.main.item_section.view.*
import net.lingala.zip4j.io.outputstream.ZipOutputStream
import net.lingala.zip4j.model.ZipParameters
import java.io.BufferedInputStream import java.io.BufferedInputStream
import java.io.Closeable import java.io.Closeable
import java.io.File import java.io.File
import java.util.* import java.util.*
import java.util.zip.ZipEntry import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream import java.util.zip.ZipInputStream
import java.util.zip.ZipOutputStream
class ItemsAdapter( class ItemsAdapter(
activity: SimpleActivity, var listItems: MutableList<ListItem>, val listener: ItemOperationsListener?, recyclerView: MyRecyclerView, activity: SimpleActivity, var listItems: MutableList<ListItem>, val listener: ItemOperationsListener?, recyclerView: MyRecyclerView,
@ -482,8 +483,7 @@ class ItemsAdapter(
return return
} }
CompressAsDialog(activity, firstPath) { CompressAsDialog(activity, firstPath) { destination, password ->
val destination = it
activity.handleAndroidSAFDialog(firstPath) { granted -> activity.handleAndroidSAFDialog(firstPath) { granted ->
if (!granted) { if (!granted) {
return@handleAndroidSAFDialog return@handleAndroidSAFDialog
@ -496,7 +496,7 @@ class ItemsAdapter(
activity.toast(R.string.compressing) activity.toast(R.string.compressing)
val paths = getSelectedFileDirItems().map { it.path } val paths = getSelectedFileDirItems().map { it.path }
ensureBackgroundThread { ensureBackgroundThread {
if (compressPaths(paths, destination)) { if (compressPaths(paths, destination, password)) {
activity.runOnUiThread { activity.runOnUiThread {
activity.toast(R.string.compression_successful) activity.toast(R.string.compression_successful)
listener?.refreshFragment() listener?.refreshFragment()
@ -643,13 +643,17 @@ class ItemsAdapter(
} }
@SuppressLint("NewApi") @SuppressLint("NewApi")
private fun compressPaths(sourcePaths: List<String>, targetPath: String): Boolean { private fun compressPaths(sourcePaths: List<String>, targetPath: String, password: String? = null): Boolean {
val queue = LinkedList<String>() val queue = LinkedList<String>()
val fos = activity.getFileOutputStreamSync(targetPath, "application/zip") ?: return false val fos = activity.getFileOutputStreamSync(targetPath, "application/zip") ?: return false
val zout = ZipOutputStream(fos) val zout = password?.let { ZipOutputStream(fos, password.toCharArray()) } ?: ZipOutputStream(fos)
var res: Closeable = fos var res: Closeable = fos
fun zipEntry(name: String) = ZipParameters().also {
it.fileNameInZip = name
}
try { try {
sourcePaths.forEach { currentPath -> sourcePaths.forEach { currentPath ->
var name: String var name: String
@ -659,7 +663,11 @@ class ItemsAdapter(
queue.push(mainFilePath) queue.push(mainFilePath)
if (activity.getIsPathDirectory(mainFilePath)) { if (activity.getIsPathDirectory(mainFilePath)) {
name = "${mainFilePath.getFilenameFromPath()}/" name = "${mainFilePath.getFilenameFromPath()}/"
zout.putNextEntry(ZipEntry(name)) zout.putNextEntry(
ZipParameters().also {
it.fileNameInZip = name
}
)
} }
while (!queue.isEmpty()) { while (!queue.isEmpty()) {
@ -672,9 +680,9 @@ class ItemsAdapter(
if (activity.getIsPathDirectory(file.path)) { if (activity.getIsPathDirectory(file.path)) {
queue.push(file.path) queue.push(file.path)
name = "${name.trimEnd('/')}/" name = "${name.trimEnd('/')}/"
zout.putNextEntry(ZipEntry(name)) zout.putNextEntry(zipEntry(name))
} else { } else {
zout.putNextEntry(ZipEntry(name)) zout.putNextEntry(zipEntry(name))
activity.getFileInputStreamSync(file.path)!!.copyTo(zout) activity.getFileInputStreamSync(file.path)!!.copyTo(zout)
zout.closeEntry() zout.closeEntry()
} }
@ -687,9 +695,9 @@ class ItemsAdapter(
if (activity.getIsPathDirectory(file.absolutePath)) { if (activity.getIsPathDirectory(file.absolutePath)) {
queue.push(file.absolutePath) queue.push(file.absolutePath)
name = "${name.trimEnd('/')}/" name = "${name.trimEnd('/')}/"
zout.putNextEntry(ZipEntry(name)) zout.putNextEntry(zipEntry(name))
} else { } else {
zout.putNextEntry(ZipEntry(name)) zout.putNextEntry(zipEntry(name))
activity.getFileInputStreamSync(file.path)!!.copyTo(zout) activity.getFileInputStreamSync(file.path)!!.copyTo(zout)
zout.closeEntry() zout.closeEntry()
} }
@ -698,7 +706,7 @@ class ItemsAdapter(
} else { } else {
name = if (base == currentPath) currentPath.getFilenameFromPath() else mainFilePath.relativizeWith(base) name = if (base == currentPath) currentPath.getFilenameFromPath() else mainFilePath.relativizeWith(base)
zout.putNextEntry(ZipEntry(name)) zout.putNextEntry(zipEntry(name))
activity.getFileInputStreamSync(mainFilePath)!!.copyTo(zout) activity.getFileInputStreamSync(mainFilePath)!!.copyTo(zout)
zout.closeEntry() zout.closeEntry()
} }

View File

@ -7,10 +7,9 @@ import com.simplemobiletools.commons.dialogs.FilePickerDialog
import com.simplemobiletools.commons.extensions.* import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.filemanager.pro.R import com.simplemobiletools.filemanager.pro.R
import com.simplemobiletools.filemanager.pro.extensions.config import com.simplemobiletools.filemanager.pro.extensions.config
import kotlinx.android.synthetic.main.dialog_compress_as.view.filename_value import kotlinx.android.synthetic.main.dialog_compress_as.view.*
import kotlinx.android.synthetic.main.dialog_compress_as.view.folder
class CompressAsDialog(val activity: BaseSimpleActivity, val path: String, val callback: (destination: String) -> Unit) { class CompressAsDialog(val activity: BaseSimpleActivity, val path: String, val callback: (destination: String, password: String?) -> Unit) {
private val view = activity.layoutInflater.inflate(R.layout.dialog_compress_as, null) private val view = activity.layoutInflater.inflate(R.layout.dialog_compress_as, null)
init { init {
@ -29,6 +28,10 @@ class CompressAsDialog(val activity: BaseSimpleActivity, val path: String, val c
realPath = it realPath = it
} }
} }
password_protect.setOnCheckedChangeListener { _, _ ->
enter_password_hint.beVisibleIf(password_protect.isChecked)
}
} }
activity.getAlertDialogBuilder() activity.getAlertDialogBuilder()
@ -39,6 +42,14 @@ class CompressAsDialog(val activity: BaseSimpleActivity, val path: String, val c
alertDialog.showKeyboard(view.filename_value) alertDialog.showKeyboard(view.filename_value)
alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(View.OnClickListener { alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(View.OnClickListener {
val name = view.filename_value.value val name = view.filename_value.value
var password: String? = null
if (view.password_protect.isChecked) {
password = view.password.value
if (password.isEmpty()) {
activity.toast(R.string.empty_password)
return@OnClickListener
}
}
when { when {
name.isEmpty() -> activity.toast(R.string.empty_name) name.isEmpty() -> activity.toast(R.string.empty_name)
name.isAValidFilename() -> { name.isAValidFilename() -> {
@ -49,8 +60,9 @@ class CompressAsDialog(val activity: BaseSimpleActivity, val path: String, val c
} }
alertDialog.dismiss() alertDialog.dismiss()
callback(newPath) callback(newPath, password)
} }
else -> activity.toast(R.string.invalid_name) else -> activity.toast(R.string.invalid_name)
} }
}) })

View File

@ -27,6 +27,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_below="@+id/folder_hint" android:layout_below="@+id/folder_hint"
android:layout_marginBottom="@dimen/activity_margin"
android:hint="@string/filename_without_zip"> android:hint="@string/filename_without_zip">
<com.google.android.material.textfield.TextInputEditText <com.google.android.material.textfield.TextInputEditText
@ -39,4 +40,34 @@
android:textSize="@dimen/bigger_text_size" /> android:textSize="@dimen/bigger_text_size" />
</com.simplemobiletools.commons.views.MyTextInputLayout> </com.simplemobiletools.commons.views.MyTextInputLayout>
<com.simplemobiletools.commons.views.MyAppCompatCheckbox
android:id="@+id/password_protect"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/filename_hint"
android:layout_marginTop="@dimen/small_margin"
android:paddingTop="@dimen/normal_margin"
android:paddingBottom="@dimen/normal_margin"
android:text="@string/password" />
<com.simplemobiletools.commons.views.MyTextInputLayout
android:id="@+id/enter_password_hint"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/password_protect"
android:hint="@string/password"
android:visibility="gone">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/activity_margin"
android:inputType="textPassword"
android:singleLine="true"
android:textCursorDrawable="@null"
android:textSize="@dimen/normal_text_size" />
</com.simplemobiletools.commons.views.MyTextInputLayout>
</RelativeLayout> </RelativeLayout>