I'd like to know if there's a way in kotlin native to call a command via posix and receive it's terminal output. For example, I'd like to get the "git diff" command working without having to create a temporary file, write output to it and then read from that file.
On SO I've only found solutions requiring ProcessBuilder, which isn't available on kotlin-native, as it's a Java library.
I found a working piece of code I wanted to use, so I'm posting it here for future viewers!
fun executeCommand(command: String): String{
val fp: CPointer<FILE>? = popen(command, "r")
val buffer = ByteArray(4096)
val returnString = StringBuilder()
/* Open the command for reading. */
if (fp == NULL) {
printf("Failed to run command\n" )
exit(1)
}
/* Read the output a line at a time - output it. */
var scan = fgets(buffer.refTo(0), buffer.size, fp)
if(scan != null) {
while (scan != NULL) {
returnString.append(scan!!.toKString())
scan = fgets(buffer.refTo(0), buffer.size, fp)
}
}
/* close */
pclose(fp)
return returnString.trim().toString()
}
It's an improved version of exec command for Kotlin Native posted by mg-lolenstine, it throws an exception with command stderr instead of just returning exit(1) (which not always desirable behavior itself), also trim is now optional
import kotlinx.cinterop.*
import platform.posix.*
fun executeCommand(
command: String,
trim: Boolean = true,
redirectStderr: Boolean = true
): String {
val commandToExecute = if (redirectStderr) "$command 2>&1" else command
val fp = popen(commandToExecute, "r") ?: error("Failed to run command: $command")
val stdout = buildString {
val buffer = ByteArray(4096)
while (true) {
val input = fgets(buffer.refTo(0), buffer.size, fp) ?: break
append(input.toKString())
}
}
val status = pclose(fp)
if (status != 0) {
error("Command `$command` failed with status $status${if (redirectStderr) ": $stdout" else ""}")
}
return if (trim) stdout.trim() else stdout
}
Usually you can use the POSIX api and use fork and wait and some I/O related functions for your purpose
fun main() {
val childPid: pid_t = fork()
if (childPid == 0) {
val commands = listOf("git", "diff", "HEAD^1", "$projectDir/path/to/file", null)
val cwd = "$projectDir"
chdir(cwd)
memScoped {
execvp(commands[0], allocArrayOf(commands.map { it?.cstr?.ptr }))
}
} else {
wait(null)
}
}
Of course, this needs to deal with a lot of c-style code, so I also wrote a more practical library for this
repositories {
mavenCentral()
}
// add dependencies into your native target sourceSet
dependencies {
implementation("com.kgit2:kommand:0.1.4")
}
It is also very simple to use
fun main(args: Array<String>) {
val diffResult = Command("git")
.args("diff", "HEAD^1", "$projectDir/path/to/file")
.cwd("$projectDir")
.spawn()
.output()
}
playing around I used above's answers to create a working gradle kotlin native/jvm multiplatform multiproject that runs arbitrary local Processes/Commands:
here's my result:
https://github.com/hoffipublic/minimal_kotlin_multiplatform
import kotlinx.cinterop.refTo
import kotlinx.cinterop.toKString
import platform.posix.fgets
import platform.posix.pclose
import platform.posix.popen
actual object MppProcess : IMppProcess {
actual override fun executeCommand(
command: String,
redirectStderr: Boolean
): String? {
val commandToExecute = if (redirectStderr) "$command 2>&1" else command
val fp = popen(commandToExecute, "r") ?: error("Failed to run command: $command")
val stdout = buildString {
val buffer = ByteArray(4096)
while (true) {
val input = fgets(buffer.refTo(0), buffer.size, fp) ?: break
append(input.toKString())
}
}
val status = pclose(fp)
if (status != 0) {
error("Command `$command` failed with status $status${if (redirectStderr) ": $stdout" else ""}")
}
return stdout
}
}
on jvm
import java.util.concurrent.TimeUnit
actual object MppProcess : IMppProcess {
actual override fun executeCommand(
command: String,
redirectStderr: Boolean
): String? {
return runCatching {
ProcessBuilder(command.split(Regex("(?<!(\"|').{0,255}) | (?!.*\\1.*)")))
//.directory(workingDir)
.redirectOutput(ProcessBuilder.Redirect.PIPE)
.apply { if (redirectStderr) this.redirectError(ProcessBuilder.Redirect.PIPE) }
.start().apply { waitFor(60L, TimeUnit.SECONDS) }
.inputStream.bufferedReader().readText()
}.onFailure { it.printStackTrace() }.getOrNull()
}
}
Related
I've recently updated to Xcode 14 for my KMM project and I now run into this build error when syncing gradle.
shared/build/cocoapods/synthetic/IOS/Pods/Pods.xcodeproj: error: Signing for "gRPC-C++-gRPCCertificates-Cpp" requires a development team. Select a development team in the Signing & Capabilities editor. (in target 'gRPC-C++-gRPCCertificates-Cpp' from project 'Pods')
I'm using Firebase and am including the pods as dependencies in my shared build.gradle file.
kotlin {
...
cocoapods {
...
ios.deploymentTarget = "15.0"
podfile = project.file("../iosApp/Podfile")
framework {
baseName = "shared"
}
xcodeConfigurationToNativeBuildType
val firebaseVersion = "9.6.0"
pod("FirebaseCore") { version = firebaseVersion }
pod("FirebaseAuth") { version = firebaseVersion }
pod("FirebaseFirestore") { version = firebaseVersion }
pod("FirebaseCrashlytics") { version = firebaseVersion }
pod("FirebaseAnalytics") { version = firebaseVersion }
}
...
}
I have a team setup and if I open Xcode and go to the Pods project, select gRPC-C++-gRPCCertificates-Cpp, then add my team to it, the project builds fine from Xcode.
However, this doesn't fix the gradle issue as it seems to use its own temporary project file when syncing (in shared/build/cocoapods/synthetic/IOS/Pods/Pods.xcodepro)
Is there any way to add my team to the gradle sync build?
As a temporary work around, I've switched back to Xcode 13.4.1 which doesn't have this problem.
This will be fixed in Kotlin 1.8.0 but in the meantime here is a workaround (from https://youtrack.jetbrains.com/issue/KT-54161)
First create the directories buildSrc/src/main/kotlin in the root of your project. In buildSrc create the file build.gradle.kts and add the following contents.
plugins {
`kotlin-dsl`
}
repositories {
mavenCentral()
google()
}
dependencies {
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.20")
implementation("com.android.tools.build:gradle:7.3.1")
}
Then create the file PatchedPodGenTask.kt in buildSrc/src/main/kotlin. Add the following to it.
import org.gradle.api.Project
import org.gradle.api.logging.Logger
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.*
import java.io.File
import java.io.IOException
import org.jetbrains.kotlin.konan.target.*
import org.jetbrains.kotlin.gradle.targets.native.tasks.PodGenTask
import org.jetbrains.kotlin.gradle.plugin.cocoapods.CocoapodsExtension.SpecRepos
import org.jetbrains.kotlin.gradle.plugin.cocoapods.CocoapodsExtension.CocoapodsDependency.PodLocation.*
import org.jetbrains.kotlin.gradle.plugin.cocoapods.CocoapodsExtension.CocoapodsDependency.PodLocation
import org.jetbrains.kotlin.gradle.plugin.mpp.NativeBuildType
import kotlin.concurrent.thread
import kotlin.reflect.KClass
//import org.jetbrains.kotlin.gradle.plugin.cocoapods.cocoapodsBuildDirs
private val Family.platformLiteral: String
get() = when (this) {
Family.OSX -> "macos"
Family.IOS -> "ios"
Family.TVOS -> "tvos"
Family.WATCHOS -> "watchos"
else -> throw IllegalArgumentException("Bad family ${this.name}")
}
internal val Project.cocoapodsBuildDirs: CocoapodsBuildDirs
get() = CocoapodsBuildDirs(this)
internal class CocoapodsBuildDirs(val project: Project) {
val root: File
get() = project.buildDir.resolve("cocoapods")
val framework: File
get() = root.resolve("framework")
val defs: File
get() = root.resolve("defs")
val buildSettings: File
get() = root.resolve("buildSettings")
val synthetic: File
get() = root.resolve("synthetic")
fun synthetic(family: Family) = synthetic.resolve(family.name)
val externalSources: File
get() = root.resolve("externalSources")
val publish: File = root.resolve("publish")
fun externalSources(fileName: String) = externalSources.resolve(fileName)
fun fatFramework(buildType: NativeBuildType) =
root.resolve("fat-frameworks/${buildType.getName()}")
}
/**
* The task generates a synthetic project with all cocoapods dependencies
*/
open class PatchedPodGenTask : PodGenTask() {
private val PODFILE_SUFFIX = """
post_install do |installer|
installer.pods_project.build_configurations.each do |config|
config.build_settings["EXCLUDED_ARCHS[sdk=iphonesimulator*]"] = "arm64"
end
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '13.0'
end
end
installer.generated_projects.each do |project|
project.targets.each do |target|
target.build_configurations.each do |config|
config.build_settings["DEVELOPMENT_TEAM"] = "..."
end
end
end
end
""".trimIndent()
#TaskAction
fun patchedGenerate() {
val syntheticDir = project.cocoapodsBuildDirs.synthetic(family).apply { mkdirs() }
val specRepos: Collection<String> = specReposAccessor.get().getAllAccessor()
val projResource = "/cocoapods/project.pbxproj"
val projDestination = syntheticDir.resolve("synthetic.xcodeproj").resolve("project.pbxproj")
projDestination.parentFile.mkdirs()
projDestination.outputStream().use { file ->
javaClass.getResourceAsStream(projResource).use { resource ->
resource.copyTo(file)
}
}
val podfile = syntheticDir.resolve("Podfile")
podfile.createNewFile()
val podfileContent = getPodfileContent(specRepos, family.platformLiteral) + PODFILE_SUFFIX
podfile.writeText(podfileContent)
val podInstallCommand = listOf("pod", "install")
runCommand(
podInstallCommand,
project.logger,
exceptionHandler = { e: IOException ->
CocoapodsErrorHandlingUtil.handle(e, podInstallCommand)
},
errorHandler = { retCode, output, _ ->
CocoapodsErrorHandlingUtil.handlePodInstallSyntheticError(
podInstallCommand.joinToString(" "),
retCode,
output,
family,
podNameAccessor.get()
)
},
processConfiguration = {
directory(syntheticDir)
})
val podsXcprojFile = podsXcodeProjDirAccessor.get()
check(podsXcprojFile.exists() && podsXcprojFile.isDirectory) {
"Synthetic project '${podsXcprojFile.path}' was not created."
}
}
private fun getPodfileContent(specRepos: Collection<String>, xcodeTarget: String) =
buildString {
specRepos.forEach {
appendLine("source '$it'")
}
appendLine("target '$xcodeTarget' do")
//if (useLibraries.get().not()) {
appendLine("\tuse_frameworks!")
//}
pods.get().mapNotNull {
buildString {
append("pod '${it.name}'")
val pathType = when (it.source) {
is Path -> "path"
is Url -> "path"
is Git -> "git"
else -> null
}
val path = it.source?.getLocalPathAccessor(project, it.name)
if (path != null && pathType != null) {
append(", :$pathType => '$path'")
}
}
}.forEach { appendLine("\t$it") }
appendLine("end\n")
}
}
fun <T : Any, R> KClass<T>.invokeDynamic(methodStart: String, instance: Any?, vararg params: Any?): R {
val method = this.java.methods.firstOrNull { it.name.startsWith(methodStart) } ?: error("Can't find accessor for $methodStart")
return method.invoke(instance, *params) as R
}
val PodGenTask.podsXcodeProjDirAccessor: Provider<File> get() = this::class.invokeDynamic("getPodsXcodeProjDir", this)
val PodGenTask.specReposAccessor: Provider<SpecRepos> get() = this::class.invokeDynamic("getSpecRepos", this)
val PodGenTask.podNameAccessor: Provider<String> get() = this::class.invokeDynamic("getPodName", this)
fun SpecRepos.getAllAccessor(): Collection<String> = this::class.invokeDynamic("getAll", this)
fun PodLocation.getLocalPathAccessor(project: Project, podName: String): String? = this::class.invokeDynamic("getLocalPath", this, project, podName)
private fun runCommand(
command: List<String>,
logger: Logger,
errorHandler: ((retCode: Int, output: String, process: Process) -> String?)? = null,
exceptionHandler: ((ex: IOException) -> Unit)? = null,
processConfiguration: ProcessBuilder.() -> Unit = { }
): String {
var process: Process? = null
try {
process = ProcessBuilder(command)
.apply {
this.processConfiguration()
}.start()
} catch (e: IOException) {
if (exceptionHandler != null) exceptionHandler(e) else throw e
}
if (process == null) {
throw IllegalStateException("Failed to run command ${command.joinToString(" ")}")
}
var inputText = ""
var errorText = ""
val inputThread = thread {
inputText = process.inputStream.use {
it.reader().readText()
}
}
val errorThread = thread {
errorText = process.errorStream.use {
it.reader().readText()
}
}
inputThread.join()
errorThread.join()
val retCode = process.waitFor()
logger.info(
"""
|Information about "${command.joinToString(" ")}" call:
|
|${inputText}
""".trimMargin()
)
check(retCode == 0) {
errorHandler?.invoke(retCode, inputText.ifBlank { errorText }, process)
?: """
|Executing of '${command.joinToString(" ")}' failed with code $retCode and message:
|
|$inputText
|
|$errorText
|
""".trimMargin()
}
return inputText
}
private object CocoapodsErrorHandlingUtil {
fun handle(e: IOException, command: List<String>) {
if (e.message?.contains("No such file or directory") == true) {
val message = """
|'${command.take(2).joinToString(" ")}' command failed with an exception:
| ${e.message}
|
| Full command: ${command.joinToString(" ")}
|
| Possible reason: CocoaPods is not installed
| Please check that CocoaPods v1.10 or above is installed.
|
| To check CocoaPods version type 'pod --version' in the terminal
|
| To install CocoaPods execute 'sudo gem install cocoapods'
|
""".trimMargin()
throw IllegalStateException(message)
} else {
throw e
}
}
fun handlePodInstallSyntheticError(command: String, retCode: Int, error: String, family: Family, podName: String): String? {
var message = """
|'pod install' command on the synthetic project failed with return code: $retCode
|
| Full command: $command
|
| Error: ${error.lines().filter { it.contains("[!]") }.joinToString("\n")}
|
""".trimMargin()
if (
error.contains("deployment target") ||
error.contains("no platform was specified") ||
error.contains(Regex("The platform of the target .+ is not compatible with `$podName"))
) {
message += """
|
| Possible reason: ${family.name.toLowerCase()} deployment target is not configured
| Configure deployment_target for ALL targets as follows:
| cocoapods {
| ...
| ${family.name.toLowerCase()}.deploymentTarget = "..."
| ...
| }
|
""".trimMargin()
return message
} else if (
error.contains("Unable to add a source with url") ||
error.contains("Couldn't determine repo name for URL") ||
error.contains("Unable to find a specification")
) {
message += """
|
| Possible reason: spec repos are not configured correctly.
| Ensure that spec repos are correctly configured for all private pod dependencies:
| cocoapods {
| specRepos {
| url("<private spec repo url>")
| }
| }
|
""".trimMargin()
return message
}
return null
}
}
Find the line that says config.build_settings["DEVELOPMENT_TEAM"] = "..." and replace ... with your actual dev team ID.
Finally, in the build.gradle.kts file of the module you want to apply it (mine was the shared module) add the following
// #TODO: This is a hack, and we should remove it once PodGenTask is fixed or supports adding suffixes in the official Kotlin plugin
tasks.replace("podGenIOS", PatchedPodGenTask::class.java)
Clean your project and re-sync Gradle. The patch should be applied and work with Xcode 14
I am trying to copy an image that is stored in my application folder to a predefined folder in my gallery.
I started from an image sharing code..
This is my code :
val extension = when (requireNotNull(pictureResult).format) {
PictureFormat.JPEG -> "jpg"
PictureFormat.DNG -> "dng"
else -> throw RuntimeException("Unknown format.")
}
val timestamp = System.currentTimeMillis()
val namePhoto = "picture_"+timestamp+"."+extension;
val destFile = File(filesDir, namePhoto)
val folder = "/CustomFolder"
CameraUtils.writeToFile(requireNotNull(pictureResult?.data), destFile) { file ->
if (file != null) {
// Code to share - it works
/*
val context = this#PicturePreviewActivity
val intent = Intent(Intent.ACTION_SEND)
intent.type = "image/*"
val uri = FileProvider.getUriForFile(context, context.packageName + ".provider", file)
intent.putExtra(Intent.EXTRA_STREAM, uri)
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
startActivity(intent)
*/
*/
// Code to save image to gallery - doesn't work :(
val photoDirectory = File(Environment.DIRECTORY_PICTURES+folder, namePhoto)
val sourcePath = Paths.get(file.toURI())
Log.i("LOG","sourcePath : "+sourcePath.toString()) // /data/user/0/com.app.demo/files/picture_1663772068143.jpg
val targetPath = Paths.get(photoDirectory.toURI())
Log.i("LOG","targetPath : "+targetPath.toString()) // /Pictures/CustomFolder/picture_1663772068143.jpg
Files.move(sourcePath, targetPath, StandardCopyOption.REPLACE_EXISTING)
// Error here but I don't know why
} else {
Toast.makeText(this#PicturePreviewActivity, "Error while writing file.", Toast.LENGTH_SHORT).show()
}
}
How do I copy the image to a predefined folder?
Ok, I did it !
Solution :
val folder = "/CustomFolder/" // name of your folder
val timestamp = System.currentTimeMillis()
val namePicture = "picture_"+timestamp+"."+extension;
try {
val path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES+folder);
if (!path.exists()) {
path.mkdir();
}
val pathImage = File(path,namePicture)
val stream = FileOutputStream(pathImage)
stream.write(imageByteArray).run {
stream.flush()
stream.close()
}
} catch (e: FileNotFoundException) {
e.printStackTrace()
}
Any suggestions on how to improve the following code to make it more Functional Programming oriented. Specifically how to remove the MutableList which signifies historical states. There are two data classes: Bank, which represents a riverbank (number of missionaries and number of cannibals currently on the bank) and BankState which represents a historical state of the two banks (the source bank, target bank and boatAtSource - a boolean which indicates whether the boat is currently at the source or target bank). overloaded operator function plus adds missionaries and cannibals to a riverbank and function minus removes them from a riverbank. The boat function is the one which carries the most heft. You can call the following algorithm from fun main (app.kt) as such:
app.kt
fun main(args:Array<String>) {
val source:Bank = Bank(3,3)
val target:Bank = Bank()
source boat target
}
Bank.kt
data class Bank(val missionaries:Int=0,val cannibals:Int=0)
data class BankState(val sourceTarget:Pair<Bank,Bank>,val boatAtSource:Boolean)
operator fun Bank.plus(b:Pair<Int,Int>):Bank = Bank(this.missionaries+b.first,this.cannibals+b.second)
operator fun Bank.minus(b:Pair<Int,Int>):Bank = Bank(this.missionaries-b.first,this.cannibals-b.second)
infix fun Bank.boat(target:Bank):List<BankState> {
val begin = Pair(this,target)
val history = mutableListOf<BankState>(BankState(begin,true))
boat(begin,true,this.missionaries,this.cannibals,history)
return history
}
fun boat(sourceTarget:Pair<Bank,Bank>,
boatAtSource:Boolean,
totalMissionaries:Int,
totalCannibals:Int,
history:MutableList<BankState>):Boolean {
if(sourceTarget.first.cannibals+sourceTarget.second.cannibals==totalCannibals &&
sourceTarget.first.missionaries + sourceTarget.second.missionaries==totalMissionaries &&
sourceTarget.first.cannibals>=0 &&
sourceTarget.first.missionaries>=0 &&
sourceTarget.second.cannibals>=0 &&
sourceTarget.second.missionaries>=0 &&
(sourceTarget.first.missionaries==0 || sourceTarget.first.missionaries>=sourceTarget.first.cannibals) &&
(sourceTarget.second.missionaries==0 || sourceTarget.second.missionaries >= sourceTarget.second.cannibals)) {
if(sourceTarget.second.missionaries==totalMissionaries &&
sourceTarget.second.cannibals==totalCannibals) {
history.forEach(::println)
return true
} else {
val deltas = listOf(Pair(0,1),Pair(1,1),Pair(1,0),Pair(2,0),Pair(0,2))
val comparator = object : Comparator<Pair<Pair<Boolean,Int>,Pair<Bank,Bank>>> {
override fun compare(arg1:Pair<Pair<Boolean,Int>,Pair<Bank,Bank>>,arg2:Pair<Pair<Boolean,Int>,Pair<Bank,Bank>>):Int {
if(arg1.first.first && arg2.first.first) {
return if(arg1.first.second<arg2.first.second) -1 else if(arg1.first.second>arg2.first.second) 1 else 0
} else if(arg1.first.first){
return 1
} else if(arg2.first.first) {
return -1
}
return 0
}
}
val result = deltas.map{
checkNext(it.first,it.second,totalMissionaries,totalCannibals,history,sourceTarget,boatAtSource)
}.maxWith(comparator)
if(result?.first?.first!=null && result.first.first) {
history.add(BankState(result.second,!boatAtSource))
return true;
}
}
}
return false
}
fun checkNext(missionariesDelta:Int,
cannibalsDelta:Int,
totalMissionaries:Int,
totalCannibals:Int,
history:MutableList<BankState>,
sourceTarget:Pair<Bank,Bank>,
boatAtSource:Boolean):Pair<Pair<Boolean,Int>,Pair<Bank,Bank>> {
val nextSrcTgt = if(boatAtSource) Pair(sourceTarget.first-Pair(missionariesDelta,cannibalsDelta),sourceTarget.second+Pair(missionariesDelta,cannibalsDelta))
else Pair(sourceTarget.first+Pair(missionariesDelta,cannibalsDelta),sourceTarget.second-Pair(missionariesDelta,cannibalsDelta))
val bankState:BankState = BankState(nextSrcTgt,!boatAtSource)
if(!history.contains(bankState)) {
history.add(bankState)
val combo2:Boolean = boat(nextSrcTgt,!boatAtSource,totalMissionaries,totalCannibals,history)
val combo2Depth = history.size
history.remove(bankState)
return Pair(Pair(combo2,combo2Depth),nextSrcTgt)
} else {
return Pair(Pair(false,0),nextSrcTgt)
}
}
Acording the blog, hdfs uses the lease mechanism to avoid two client writing the same file. So I think one can not delete the file which is written by the other client. Howerver, it's wrong.
When client A is writing the lock.txt, client B can delete lock.txt immediately. Client A can continue writing although the file no longer exists. Just when closing the stream, A will encounter the exception:
org.apache.hadoop.hdfs.server.namenode.LeaseExpiredException: No lease on /user/lock.txt (inode 643845185): File does not exist. Holder DFSClient_NONMAPREDUCE_-1636584585_1 does not have any open files**
Why this happen? My haddop version is 2.7.3.
================================
This is my test codeļ¼
// write process
object Create {
private val filePath = "/user/lock.txt"
def main(args: Array[String]): Unit = {
println("Create start!")
val fs = FileSystem.get(new Configuration())
val os = Option(fs.create(new Path(filePath), false))
if (os.isDefined) {
println(s"Create result! $os", System.currentTimeMillis())
0.until(300).foreach(index => {
os.get.write(100)
os.get.flush()
println("Writing...")
Thread.sleep(1000)
})
println("pre close" + System.currentTimeMillis())
os.get.close()
println("close success!" + System.currentTimeMillis())
}
}
}
// delete process
object Delete {
private val filePath = "/user/lock.txt"
def main(args: Array[String]): Unit = {
println("Delete start!")
val fs = FileSystem.get(new Configuration())
while (!fs.exists(new Path(filePath))) {
println("File no exist!")
Thread.sleep(1000)
}
println("File exist!")
while (true) {
println("try delete!")
val tmp = Option(fs.delete(new Path(filePath), false))
if (tmp.isDefined) {
println(s"delete result:${tmp.get}!" + System.currentTimeMillis())
}
println("Try recover")
if (fs.asInstanceOf[DistributedFileSystem].recoverLease(new Path(filePath))) {
println("Recover lease success!")
val res = Option(fs.delete(new Path(filePath), false))
println(s"File delete success:${res.get}")
} else {
println("Recover lease failed!")
}
Thread.sleep(1000)
}
}
}
I was toying with the idea of rewriting some existing bash scripts in kotlin script.
One of the scripts has a section that unzips all the files in a directory. In bash:
unzip *.zip
Is there a nice way to unzip a file(s) in kotlin script?
The easiest way is to just use exec unzip (assuming that the name of your zip file is stored in zipFileName variable):
ProcessBuilder()
.command("unzip", zipFileName)
.redirectError(ProcessBuilder.Redirect.INHERIT)
.redirectOutput(ProcessBuilder.Redirect.INHERIT)
.start()
.waitFor()
The different approach, that is more portable (it will run on any OS and does not require unzip executable to be present), but somewhat less feature-full (it will not restore Unix permissions), is to do unzipping in code:
import java.io.File
import java.util.zip.ZipFile
ZipFile(zipFileName).use { zip ->
zip.entries().asSequence().forEach { entry ->
zip.getInputStream(entry).use { input ->
File(entry.name).outputStream().use { output ->
input.copyTo(output)
}
}
}
}
If you need to scan all *.zip file, then you can do it like this:
File(".").list { _, name -> name.endsWith(".zip") }?.forEach { zipFileName ->
// any of the above approaches
}
or like this:
import java.nio.file.*
Files.newDirectoryStream(Paths.get("."), "*.zip").forEach { path ->
val zipFileName = path.toString()
// any of the above approaches
}
this code is for unziping from Assets
1.for unzping first u need InputStream
2.put it in ZipInputStream
3.if directory is not exist u have to make by .mkdirs()
private val BUFFER_SIZE = 8192//2048;
private val SDPath = Environment.getExternalStorageDirectory().absolutePath
private val unzipPath = "$SDPath/temp/zipunzipFile/unzip/"
var count: Int
val buffer = ByteArray(BUFFER_SIZE)
val context: Context = this
val am = context.getAssets()
val stream = context.getAssets().open("raw.zip")
try {
ZipInputStream(stream).use { zis ->
var ze: ZipEntry
while (zis.nextEntry.also { ze = it } != null) {
var fileName = ze.name
fileName = fileName.substring(fileName.indexOf("/") + 1)
val file = File(unzipPath, fileName)
val dir = if (ze.isDirectory) file else file.getParentFile()
if (!dir.isDirectory() && !dir.mkdirs())
throw FileNotFoundException("Invalid path: " + dir.getAbsolutePath())
if (ze.isDirectory) continue
val fout = FileOutputStream(file)
try {
while ( zis.read(buffer).also { count = it } != -1)
fout.write(buffer, 0, count)
} finally {
val fout : FileOutputStream =openFileOutput(fileName, Context.MODE_PRIVATE)
fout.close()
}
}
for unziping from externalStorage:
private val sourceFile= "$SDPath/unzipFile/data/"
ZipInputStream zis = null;
try {
zis = new ZipInputStream(new BufferedInputStream(new
FileInputStream(sourceFile)));
ZipEntry ze;
int count;
byte[] buffer = new byte[BUFFER_SIZE];
while ((ze = zis.getNextEntry()) != null) {
String fileName = ze.getName();
fileName = fileName.substring(fileName.indexOf("/") + 1);
File file = new File(destinationFolder, fileName);
File dir = ze.isDirectory() ? file : file.getParentFile();
if (!dir.isDirectory() && !dir.mkdirs())
throw new FileNotFoundException("Invalid path: " +
dir.getAbsolutePath());
if (ze.isDirectory()) continue;
FileOutputStream fout = new FileOutputStream(file);
try {
while ((count = zis.read(buffer)) != -1)
fout.write(buffer, 0, count);
} finally {
fout.close();
}
}
} catch (IOException ioe) {
Log.d(TAG, ioe.getMessage());
return false;
} finally {
if (zis != null)
try {
zis.close();
} catch (IOException e) {
}
}
return true;