• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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