Skip to content

Commit

Permalink
Add option to specify output filename for CSV
Browse files Browse the repository at this point in the history
  • Loading branch information
nmalkin committed Jan 4, 2022
1 parent a1ad265 commit 314b462
Show file tree
Hide file tree
Showing 4 changed files with 84 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,11 @@ class ExportAndUploadCommand : Subcommand("run", "download a Qualtrics export an
fullName = "export-path",
description = "if specified, the Qualtrics CSV will be extracted to the given directory. Otherwise, a temporary file system directory is used."
)
val outputFilename by option(
ArgType.String,
fullName = "filename",
description = "filename to use for the extracted CSV"
)
val ifExists by option(
ArgType.Choice<FileExistsStrategy>(),
fullName = "if-exists",
Expand Down Expand Up @@ -94,7 +99,7 @@ class ExportAndUploadCommand : Subcommand("run", "download a Qualtrics export an

runBlocking {
checkApiToken(QualtricsDatacenter(datacenter), apiToken)
downloadSurvey(QualtricsDatacenter(datacenter), apiToken, surveyID, tmpDirectory, FileExistsStrategy.ABORT)
downloadSurvey(QualtricsDatacenter(datacenter), apiToken, surveyID, tmpDirectory, outputFilename, FileExistsStrategy.ABORT)
}

var csvFile: Path? = null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ class DownloadQualtricsCommand : Subcommand("download_qualtrics", "download a Qu
shortName = "o",
description = "target directory where to put the downloaded CSV"
).required()
val outputFilename by option(
ArgType.String,
fullName = "filename",
description = "filename to use for the extracted CSV"
)
val ifExists by option(
ArgType.Choice<FileExistsStrategy>(),
fullName = "if-exists",
Expand All @@ -47,7 +52,7 @@ class DownloadQualtricsCommand : Subcommand("download_qualtrics", "download a Qu

runBlocking {
checkApiToken(QualtricsDatacenter(datacenter), apiToken)
downloadSurvey(QualtricsDatacenter(datacenter), apiToken, surveyID, targetPath, ifExists)
downloadSurvey(QualtricsDatacenter(datacenter), apiToken, surveyID, targetPath, outputFilename, ifExists)
}

subcommandFinished = true
Expand Down
14 changes: 11 additions & 3 deletions qualtrics2sheets/src/main/kotlin/q2s/qualtrics/DownloadSurvey.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import kotlinx.serialization.json.jsonPrimitive
import logging.KotlinLogging
import q2s.util.FileExistsStrategy
import q2s.util.unzip
import q2s.util.unzipAndRename
import java.io.InputStream
import java.nio.file.Path

Expand Down Expand Up @@ -59,6 +60,7 @@ suspend fun downloadSurvey(
apiToken: String,
surveyID: String,
targetDirectory: Path,
outputFilename: String?,
fileExistsStrategy: FileExistsStrategy
) {
val client = HttpClient(Java)
Expand Down Expand Up @@ -96,7 +98,7 @@ suspend fun downloadSurvey(
throw QualtricsException("Qualtrics export complete but fileId is null")
}

downloadExport(client, urls, apiToken, surveyID, fileID, targetDirectory, fileExistsStrategy)
downloadExport(client, urls, apiToken, surveyID, fileID, targetDirectory, outputFilename, fileExistsStrategy)
}

suspend fun downloadExport(
Expand All @@ -106,6 +108,7 @@ suspend fun downloadExport(
surveyID: String,
fileID: String,
targetDirectory: Path,
outputFilename: String?,
fileExistsStrategy: FileExistsStrategy
) {
logger.debug { "downloading result from ${urls.getExportFile(surveyID, fileID)}" }
Expand All @@ -118,6 +121,11 @@ suspend fun downloadExport(
}
val content = downloadResponse.receive<InputStream>()

logger.debug { "unzipping $surveyID export to $targetDirectory" }
unzip(content, targetDirectory, fileExistsStrategy)
if (outputFilename == null) {
logger.debug { "unzipping $surveyID export to $targetDirectory" }
unzip(content, targetDirectory, fileExistsStrategy)
} else {
logger.debug { "unzipping $surveyID export as $outputFilename to $targetDirectory" }
unzipAndRename(content, targetDirectory, outputFilename, fileExistsStrategy)
}
}
61 changes: 61 additions & 0 deletions qualtrics2sheets/src/main/kotlin/q2s/util/Unzip.kt
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,64 @@ fun unzip(
}
}
}

/**
* Unzip archive, assuming it contains a single file, and rename that file to the given name
*/
fun unzipAndRename(
inputStream: InputStream,
targetDirectory: Path,
outputFilename: String,
fileExistsStrategy: FileExistsStrategy = FileExistsStrategy.ABORT
) {
val resolvedTarget: Path = targetDirectory.toAbsolutePath()

var filesFound = 0

ZipInputStream(inputStream).use { zipStream ->
while (true) {
val entry: ZipEntry = zipStream.nextEntry ?: break

var entryPath: Path = resolvedTarget.resolve(entry.name).normalize()

// Check for zip slip: https://snyk.io/research/zip-slip-vulnerability
if (!entryPath.startsWith(resolvedTarget)) {
throw RuntimeException("Entry with an illegal path: ${entry.name}")
}

if (entry.isDirectory) {
Files.createDirectories(entryPath)
} else {
if (filesFound > 0) {
throw RuntimeException("more than 1 file found in zip, so ambiguous which should be output to $outputFilename")
}
filesFound++

entryPath = resolvedTarget.resolve(outputFilename).normalize()

Files.createDirectories(entryPath.parent)

try {
Files.copy(zipStream, entryPath)
} catch (e: FileAlreadyExistsException) {
when (fileExistsStrategy) {
FileExistsStrategy.ABORT -> {
logger.warning { "file already exists, aborting: $entryPath" }
return
}
FileExistsStrategy.OVERWRITE -> {
logger.info { "file already exists, overwriting: $entryPath" }
Files.copy(zipStream, entryPath, StandardCopyOption.REPLACE_EXISTING)
}

FileExistsStrategy.BACKUP -> {
val backedUpFile = renameFileForBackup(entryPath)
logger.info { "file already exists, moved for backup to: $backedUpFile" }
Files.copy(zipStream, entryPath)
}
}
}
}
}
}
}

0 comments on commit 314b462

Please # to comment.