xref: /aosp_15_r20/platform_testing/libraries/flicker/utils/src/android/tools/traces/io/FileArtifact.kt (revision dd0948b35e70be4c0246aabd6c72554a5eb8b22a)
1 /*
2  * Copyright (C) 2024 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.traces.io
18 
19 import android.tools.Scenario
20 import android.tools.io.Artifact
21 import android.tools.io.BUFFER_SIZE
22 import android.tools.io.FLICKER_IO_TAG
23 import android.tools.io.ResultArtifactDescriptor
24 import android.tools.io.RunStatus
25 import android.tools.traces.deleteIfExists
26 import android.tools.withTracing
27 import android.util.Log
28 import java.io.BufferedInputStream
29 import java.io.BufferedOutputStream
30 import java.io.ByteArrayOutputStream
31 import java.io.File
32 import java.io.FileInputStream
33 import java.io.FileNotFoundException
34 import java.io.IOException
35 import java.util.zip.ZipEntry
36 import java.util.zip.ZipInputStream
37 
38 class FileArtifact
39 internal constructor(private val scenario: Scenario, artifactFile: File, private val counter: Int) :
40     Artifact {
41     var file: File = artifactFile
42         private set
43 
44     init {
<lambda>null45         require(!scenario.isEmpty) { "Scenario shouldn't be empty" }
46     }
47 
48     override val runStatus: RunStatus
49         get() =
50             RunStatus.fromFileName(file.name)
51                 ?: error("Failed to get RunStatus from file name ${file.name}")
52 
53     override val absolutePath: String
54         get() = file.absolutePath
55 
56     override val fileName: String
57         get() = file.name
58 
59     override val stableId: String = "$scenario$counter"
60 
updateStatusnull61     override fun updateStatus(newStatus: RunStatus) {
62         val currFile = file
63         val newFile = getNewFilePath(newStatus)
64         if (currFile != newFile) {
65             withTracing("${this::class.simpleName}#updateStatus") {
66                 IoUtils.moveFile(currFile, newFile)
67                 file = newFile
68             }
69         }
70     }
71 
readBytesnull72     override fun readBytes(): ByteArray = file.readBytes()
73 
74     override fun deleteIfExists() {
75         file.deleteIfExists()
76     }
77 
hasTracenull78     override fun hasTrace(descriptor: ResultArtifactDescriptor): Boolean {
79         var found = false
80         forEachFileInZip { found = found || (it.name == descriptor.fileNameInArtifact) }
81         return found
82     }
83 
traceCountnull84     override fun traceCount(): Int {
85         var count = 0
86         forEachFileInZip { count++ }
87         return count
88     }
89 
toStringnull90     override fun toString(): String = fileName
91 
92     override fun equals(other: Any?): Boolean {
93         if (this === other) return true
94         if (other !is Artifact) return false
95 
96         if (absolutePath != other.absolutePath) return false
97 
98         return true
99     }
100 
hashCodenull101     override fun hashCode(): Int {
102         return absolutePath.hashCode()
103     }
104 
105     /** updates the artifact status to [newStatus] */
getNewFilePathnull106     private fun getNewFilePath(newStatus: RunStatus): File {
107         return file.resolveSibling(newStatus.generateArchiveNameFor(scenario, counter))
108     }
109 
110     @Throws(IOException::class)
readBytesnull111     override fun readBytes(descriptor: ResultArtifactDescriptor): ByteArray? {
112         Log.d(FLICKER_IO_TAG, "Reading descriptor=$descriptor from $this")
113 
114         var foundFile = false
115         val outByteArray = ByteArrayOutputStream()
116         val tmpBuffer = ByteArray(BUFFER_SIZE)
117         withZipFile {
118             var zipEntry: ZipEntry? = it.nextEntry
119             while (zipEntry != null) {
120                 if (zipEntry.name == descriptor.fileNameInArtifact) {
121                     val outputStream = BufferedOutputStream(outByteArray, BUFFER_SIZE)
122                     try {
123                         var size = it.read(tmpBuffer, 0, BUFFER_SIZE)
124                         while (size > 0) {
125                             outputStream.write(tmpBuffer, 0, size)
126                             size = it.read(tmpBuffer, 0, BUFFER_SIZE)
127                         }
128                         it.closeEntry()
129                     } finally {
130                         outputStream.flush()
131                         outputStream.close()
132                     }
133                     foundFile = true
134                     break
135                 }
136                 zipEntry = it.nextEntry
137             }
138         }
139 
140         return if (foundFile) outByteArray.toByteArray() else null
141     }
142 
withZipFilenull143     private fun withZipFile(predicate: (ZipInputStream) -> Unit) {
144         if (!file.exists()) {
145             val directory = file.parentFile
146             val files =
147                 try {
148                     directory?.listFiles()?.filterNot { it.isDirectory }?.map { it.absolutePath }
149                 } catch (e: Throwable) {
150                     null
151                 }
152             throw FileNotFoundException(
153                 buildString {
154                     append(file)
155                     appendLine(" could not be found!")
156                     append("Found ")
157                     append(files?.joinToString()?.ifEmpty { "no files" })
158                     append(" in ")
159                     append(directory?.absolutePath)
160                 }
161             )
162         }
163 
164         val zipInputStream = ZipInputStream(BufferedInputStream(FileInputStream(file), BUFFER_SIZE))
165         try {
166             predicate(zipInputStream)
167         } finally {
168             zipInputStream.closeEntry()
169             zipInputStream.close()
170         }
171     }
172 
forEachFileInZipnull173     private fun forEachFileInZip(predicate: (ZipEntry) -> Unit) {
174         withZipFile {
175             var zipEntry: ZipEntry? = it.nextEntry
176             while (zipEntry != null) {
177                 predicate(zipEntry)
178                 zipEntry = it.nextEntry
179             }
180         }
181     }
182 }
183