1 /* <lambda>null2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.tools.device.traces.io 18 19 import android.tools.common.Logger 20 import android.tools.common.Scenario 21 import android.tools.common.io.BUFFER_SIZE 22 import android.tools.common.io.FLICKER_IO_TAG 23 import android.tools.common.io.ResultArtifactDescriptor 24 import android.tools.common.io.RunStatus 25 import android.tools.device.traces.deleteIfExists 26 import java.io.BufferedInputStream 27 import java.io.BufferedOutputStream 28 import java.io.File 29 import java.io.FileInputStream 30 import java.io.FileOutputStream 31 import java.util.zip.ZipEntry 32 import java.util.zip.ZipOutputStream 33 34 /** 35 * Creates artifacts avoiding duplication. 36 * 37 * If an artifact already exists, append a counter at the end of the filename 38 */ 39 class ArtifactBuilder { 40 private var runStatus: RunStatus? = null 41 private var scenario: Scenario? = null 42 private var outputDir: File? = null 43 private var files: Map<ResultArtifactDescriptor, File> = emptyMap() 44 private var counter = 0 45 46 fun withScenario(value: Scenario): ArtifactBuilder = apply { scenario = value } 47 48 fun withOutputDir(value: File): ArtifactBuilder = apply { outputDir = value } 49 50 fun withStatus(value: RunStatus): ArtifactBuilder = apply { runStatus = value } 51 52 fun withFiles(value: Map<ResultArtifactDescriptor, File>): ArtifactBuilder = apply { 53 files = value 54 } 55 56 fun build(): Artifact { 57 return Logger.withTracing("ArtifactBuilder#build") { 58 val scenario = scenario ?: error("Missing scenario") 59 require(!scenario.isEmpty) { "Scenario shouldn't be empty" } 60 val artifactFile = createArtifactFile() 61 Logger.d(FLICKER_IO_TAG, "Creating artifact archive at $artifactFile") 62 63 writeToZip(artifactFile, files) 64 65 Artifact(scenario, artifactFile, counter) 66 } 67 } 68 69 private fun createArtifactFile(): File { 70 val fileName = getArtifactFileName() 71 72 val outputDir = outputDir ?: error("Missing output dir") 73 // Ensure output directory exists 74 outputDir.mkdirs() 75 return outputDir.resolve(fileName) 76 } 77 78 private fun getArtifactFileName(): String { 79 val runStatus = runStatus ?: error("Missing run status") 80 val scenario = scenario ?: error("Missing scenario") 81 val outputDir = outputDir ?: error("Missing output dir") 82 83 var artifactAlreadyExists = existsArchiveFor(outputDir, scenario, counter) 84 while (artifactAlreadyExists && counter < 100) { 85 artifactAlreadyExists = existsArchiveFor(outputDir, scenario, ++counter) 86 } 87 88 require(!artifactAlreadyExists) { 89 val files = 90 try { 91 outputDir.listFiles()?.filterNot { it.isDirectory }?.map { it.absolutePath } 92 } catch (e: Throwable) { 93 null 94 } 95 "An archive for $scenario already exists in ${outputDir.absolutePath}. " + 96 "Directory contains ${files?.joinToString()?.ifEmpty { "no files" }}" 97 } 98 99 return runStatus.generateArchiveNameFor(scenario, counter) 100 } 101 102 private fun existsArchiveFor(outputDir: File, scenario: Scenario, counter: Int): Boolean { 103 return RunStatus.values().any { 104 outputDir.resolve(it.generateArchiveNameFor(scenario, counter)).exists() 105 } 106 } 107 108 private fun addFile(zipOutputStream: ZipOutputStream, artifact: File, nameInArchive: String) { 109 Logger.v(FLICKER_IO_TAG, "Adding $artifact with name $nameInArchive to zip") 110 val fi = FileInputStream(artifact) 111 val inputStream = BufferedInputStream(fi, BUFFER_SIZE) 112 inputStream.use { 113 val entry = ZipEntry(nameInArchive) 114 zipOutputStream.putNextEntry(entry) 115 val data = ByteArray(BUFFER_SIZE) 116 var count: Int = it.read(data, 0, BUFFER_SIZE) 117 while (count != -1) { 118 zipOutputStream.write(data, 0, count) 119 count = it.read(data, 0, BUFFER_SIZE) 120 } 121 } 122 zipOutputStream.closeEntry() 123 artifact.deleteIfExists() 124 } 125 126 private fun writeToZip(file: File, files: Map<ResultArtifactDescriptor, File>) { 127 ZipOutputStream(BufferedOutputStream(FileOutputStream(file), BUFFER_SIZE)).use { 128 zipOutputStream -> 129 files.forEach { (descriptor, artifact) -> 130 addFile(zipOutputStream, artifact, nameInArchive = descriptor.fileNameInArtifact) 131 } 132 } 133 } 134 } 135