Adam Pardyl | d905ed2 | 2019-07-08 18:22:39 +0200 | [diff] [blame] | 1 | <!-- Copyright (C) 2019 The Android Open Source Project |
| 2 | |
| 3 | Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | you may not use this file except in compliance with the License. |
| 5 | You may obtain a copy of the License at |
| 6 | |
| 7 | http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | |
| 9 | Unless required by applicable law or agreed to in writing, software |
| 10 | distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | See the License for the specific language governing permissions and |
| 13 | limitations under the License. |
| 14 | --> |
| 15 | <template> |
Nataniel Borges | f230a13 | 2021-07-28 07:13:45 +0000 | [diff] [blame] | 16 | <div @dragleave="fileDragOut" @dragover="fileDragIn" @drop="handleFileDrop"> |
Pablo Gamito | 050e20b | 2020-07-01 16:41:56 +0100 | [diff] [blame] | 17 | <flat-card style="min-width: 50em"> |
| 18 | <md-card-header> |
| 19 | <div class="md-title">Open files</div> |
| 20 | </md-card-header> |
| 21 | <md-card-content> |
Nataniel Borges | 3907154 | 2021-09-22 09:19:41 +0000 | [diff] [blame] | 22 | <div class="dropbox"> |
| 23 | <md-list style="background: none"> |
| 24 | <md-list-item v-for="file in dataFiles" v-bind:key="file.filename"> |
| 25 | <md-icon>{{FILE_ICONS[file.type]}}</md-icon> |
| 26 | <span class="md-list-item-text">{{file.filename}} ({{file.type}}) |
| 27 | </span> |
| 28 | <md-button |
| 29 | class="md-icon-button md-accent" |
| 30 | @click="onRemoveFile(file.type)" |
| 31 | > |
| 32 | <md-icon>close</md-icon> |
| 33 | </md-button> |
| 34 | </md-list-item> |
| 35 | </md-list> |
| 36 | <md-progress-spinner |
| 37 | :md-diameter="30" |
| 38 | :md-stroke="3" |
| 39 | md-mode="indeterminate" |
| 40 | v-show="loadingFiles" |
| 41 | class="progress-spinner" |
| 42 | /> |
| 43 | <input |
| 44 | type="file" |
| 45 | @change="onLoadFile" |
| 46 | v-on:drop="handleFileDrop" |
| 47 | ref="fileUpload" |
| 48 | id="dropzone" |
| 49 | v-show="false" |
| 50 | multiple |
| 51 | /> |
| 52 | <p v-if="!dataReady"> |
| 53 | Drag your <b>.winscope</b> or <b>.zip</b> file(s) here to begin |
| 54 | </p> |
| 55 | </div> |
| 56 | |
Pablo Gamito | 050e20b | 2020-07-01 16:41:56 +0100 | [diff] [blame] | 57 | <div class="md-layout"> |
| 58 | <div class="md-layout-item md-small-size-100"> |
| 59 | <md-field> |
| 60 | <md-select v-model="fileType" id="file-type" placeholder="File type"> |
| 61 | <md-option value="auto">Detect type</md-option> |
| 62 | <md-option value="bugreport">Bug Report (.zip)</md-option> |
Pablo Gamito | bea6582 | 2020-09-02 16:35:32 +0200 | [diff] [blame] | 63 | <md-option |
| 64 | :value="k" v-for="(v,k) in FILE_DECODERS" |
| 65 | v-bind:key="v.name">{{v.name}} |
| 66 | ></md-option> |
Pablo Gamito | 050e20b | 2020-07-01 16:41:56 +0100 | [diff] [blame] | 67 | </md-select> |
| 68 | </md-field> |
Adam Pardyl | d905ed2 | 2019-07-08 18:22:39 +0200 | [diff] [blame] | 69 | </div> |
Pablo Gamito | 050e20b | 2020-07-01 16:41:56 +0100 | [diff] [blame] | 70 | </div> |
| 71 | <div class="md-layout"> |
Pablo Gamito | bea6582 | 2020-09-02 16:35:32 +0200 | [diff] [blame] | 72 | <md-button |
| 73 | class="md-primary md-theme-default" |
| 74 | @click="$refs.fileUpload.click()" |
| 75 | > |
| 76 | Add File |
| 77 | </md-button> |
| 78 | <md-button |
| 79 | v-if="dataReady" |
| 80 | @click="onSubmit" |
| 81 | class="md-button md-primary md-raised md-theme-default" |
| 82 | > |
| 83 | Submit |
| 84 | </md-button> |
Pablo Gamito | 050e20b | 2020-07-01 16:41:56 +0100 | [diff] [blame] | 85 | </div> |
| 86 | </md-card-content> |
Pablo Gamito | 8263930 | 2020-07-17 18:38:37 +0100 | [diff] [blame] | 87 | |
| 88 | <md-snackbar |
| 89 | md-position="center" |
| 90 | :md-duration="Infinity" |
| 91 | :md-active.sync="showFetchingSnackbar" |
| 92 | md-persistent |
| 93 | > |
| 94 | <span>{{ fetchingSnackbarText }}</span> |
| 95 | </md-snackbar> |
| 96 | |
| 97 | <md-snackbar |
| 98 | md-position="center" |
Pablo Gamito | afbfa78 | 2020-07-17 19:26:56 +0100 | [diff] [blame] | 99 | :md-duration="snackbarDuration" |
Pablo Gamito | 8263930 | 2020-07-17 18:38:37 +0100 | [diff] [blame] | 100 | :md-active.sync="showSnackbar" |
| 101 | md-persistent |
| 102 | > |
Nataniel Borges | f230a13 | 2021-07-28 07:13:45 +0000 | [diff] [blame] | 103 | <p class="snackbar-break-words">{{ snackbarText }}</p> |
Pablo Gamito | afbfa78 | 2020-07-17 19:26:56 +0100 | [diff] [blame] | 104 | <div @click="hideSnackbarMessage()"> |
| 105 | <md-button class="md-icon-button"> |
| 106 | <md-icon style="color: white">close</md-icon> |
| 107 | </md-button> |
| 108 | </div> |
Pablo Gamito | 8263930 | 2020-07-17 18:38:37 +0100 | [diff] [blame] | 109 | </md-snackbar> |
Pablo Gamito | 050e20b | 2020-07-01 16:41:56 +0100 | [diff] [blame] | 110 | </flat-card> |
Nataniel Borges | f230a13 | 2021-07-28 07:13:45 +0000 | [diff] [blame] | 111 | </div> |
Adam Pardyl | d905ed2 | 2019-07-08 18:22:39 +0200 | [diff] [blame] | 112 | </template> |
| 113 | <script> |
Pablo Gamito | 050e20b | 2020-07-01 16:41:56 +0100 | [diff] [blame] | 114 | import FlatCard from './components/FlatCard.vue'; |
Pablo Gamito | ed70940 | 2020-06-08 13:18:15 +0100 | [diff] [blame] | 115 | import JSZip from 'jszip'; |
Pablo Gamito | bea6582 | 2020-09-02 16:35:32 +0200 | [diff] [blame] | 116 | import { |
| 117 | detectAndDecode, |
| 118 | FILE_TYPES, |
| 119 | FILE_DECODERS, |
| 120 | FILE_ICONS, |
| 121 | UndetectableFileType, |
| 122 | } from './decode.js'; |
| 123 | import {WebContentScriptMessageType} from './utils/consts'; |
Pablo Gamito | ed70940 | 2020-06-08 13:18:15 +0100 | [diff] [blame] | 124 | |
Adam Pardyl | d905ed2 | 2019-07-08 18:22:39 +0200 | [diff] [blame] | 125 | export default { |
| 126 | name: 'datainput', |
| 127 | data() { |
| 128 | return { |
| 129 | FILE_TYPES, |
Pablo Gamito | f25221c | 2020-08-06 18:26:42 +0100 | [diff] [blame] | 130 | FILE_DECODERS, |
| 131 | FILE_ICONS, |
Pablo Gamito | bea6582 | 2020-09-02 16:35:32 +0200 | [diff] [blame] | 132 | fileType: 'auto', |
Adam Pardyl | d905ed2 | 2019-07-08 18:22:39 +0200 | [diff] [blame] | 133 | dataFiles: {}, |
Pablo Gamito | ed70940 | 2020-06-08 13:18:15 +0100 | [diff] [blame] | 134 | loadingFiles: false, |
Pablo Gamito | 8263930 | 2020-07-17 18:38:37 +0100 | [diff] [blame] | 135 | showFetchingSnackbar: false, |
| 136 | showSnackbar: false, |
Pablo Gamito | afbfa78 | 2020-07-17 19:26:56 +0100 | [diff] [blame] | 137 | snackbarDuration: 3500, |
Pablo Gamito | 8263930 | 2020-07-17 18:38:37 +0100 | [diff] [blame] | 138 | snackbarText: '', |
Pablo Gamito | bea6582 | 2020-09-02 16:35:32 +0200 | [diff] [blame] | 139 | fetchingSnackbarText: 'Fetching files...', |
| 140 | }; |
Adam Pardyl | d905ed2 | 2019-07-08 18:22:39 +0200 | [diff] [blame] | 141 | }, |
| 142 | props: ['store'], |
Pablo Gamito | c2ff984 | 2020-06-18 16:01:57 +0100 | [diff] [blame] | 143 | created() { |
| 144 | // Attempt to load files from extension if present |
| 145 | this.loadFilesFromExtension(); |
| 146 | }, |
Adam Pardyl | d905ed2 | 2019-07-08 18:22:39 +0200 | [diff] [blame] | 147 | methods: { |
Pablo Gamito | 8263930 | 2020-07-17 18:38:37 +0100 | [diff] [blame] | 148 | showSnackbarMessage(message, duration) { |
Nataniel Borges | f230a13 | 2021-07-28 07:13:45 +0000 | [diff] [blame] | 149 | this.snackbarText = '\n' + message + '\n'; |
Pablo Gamito | afbfa78 | 2020-07-17 19:26:56 +0100 | [diff] [blame] | 150 | this.snackbarDuration = duration; |
| 151 | this.showSnackbar = true; |
Pablo Gamito | 8263930 | 2020-07-17 18:38:37 +0100 | [diff] [blame] | 152 | }, |
| 153 | hideSnackbarMessage() { |
Pablo Gamito | 8263930 | 2020-07-17 18:38:37 +0100 | [diff] [blame] | 154 | this.showSnackbar = false; |
Nataniel Borges | 3907154 | 2021-09-22 09:19:41 +0000 | [diff] [blame] | 155 | this.buttonClicked("Hide Snackbar Message") |
Pablo Gamito | 8263930 | 2020-07-17 18:38:37 +0100 | [diff] [blame] | 156 | }, |
| 157 | getFetchFilesLoadingAnimation() { |
Pablo Gamito | c2ff984 | 2020-06-18 16:01:57 +0100 | [diff] [blame] | 158 | let frame = 0; |
| 159 | const fetchingStatusAnimation = () => { |
| 160 | frame++; |
Pablo Gamito | 8263930 | 2020-07-17 18:38:37 +0100 | [diff] [blame] | 161 | this.fetchingSnackbarText = `Fetching files${'.'.repeat(frame % 4)}`; |
Pablo Gamito | c2ff984 | 2020-06-18 16:01:57 +0100 | [diff] [blame] | 162 | }; |
| 163 | let interval = undefined; |
| 164 | |
| 165 | return Object.freeze({ |
| 166 | start: () => { |
Pablo Gamito | 8263930 | 2020-07-17 18:38:37 +0100 | [diff] [blame] | 167 | this.showFetchingSnackbar = true; |
Pablo Gamito | c2ff984 | 2020-06-18 16:01:57 +0100 | [diff] [blame] | 168 | interval = setInterval(fetchingStatusAnimation, 500); |
| 169 | }, |
| 170 | stop: () => { |
Pablo Gamito | 8263930 | 2020-07-17 18:38:37 +0100 | [diff] [blame] | 171 | this.showFetchingSnackbar = false; |
Pablo Gamito | c2ff984 | 2020-06-18 16:01:57 +0100 | [diff] [blame] | 172 | clearInterval(interval); |
| 173 | }, |
| 174 | }); |
| 175 | }, |
| 176 | /** |
| 177 | * Attempt to load files from the extension if present. |
| 178 | * |
| 179 | * If the source URL parameter is set to the extension it make a request |
| 180 | * to the extension to fetch the files from the extension. |
| 181 | */ |
| 182 | loadFilesFromExtension() { |
| 183 | const urlParams = new URLSearchParams(window.location.search); |
| 184 | if (urlParams.get('source') === 'openFromExtension' && chrome) { |
| 185 | // Fetch files from extension |
Pablo Gamito | bea6582 | 2020-09-02 16:35:32 +0200 | [diff] [blame] | 186 | const androidBugToolExtensionId = 'mbbaofdfoekifkfpgehgffcpagbbjkmj'; |
Pablo Gamito | c2ff984 | 2020-06-18 16:01:57 +0100 | [diff] [blame] | 187 | |
Pablo Gamito | 8263930 | 2020-07-17 18:38:37 +0100 | [diff] [blame] | 188 | const loading = this.getFetchFilesLoadingAnimation(); |
Pablo Gamito | c2ff984 | 2020-06-18 16:01:57 +0100 | [diff] [blame] | 189 | loading.start(); |
| 190 | |
| 191 | // Request to convert the blob object url "blob:chrome-extension://xxx" |
| 192 | // the chrome extension has to a web downloadable url "blob:http://xxx". |
| 193 | chrome.runtime.sendMessage(androidBugToolExtensionId, { |
Pablo Gamito | bea6582 | 2020-09-02 16:35:32 +0200 | [diff] [blame] | 194 | action: WebContentScriptMessageType.CONVERT_OBJECT_URL, |
Pablo Gamito | c2ff984 | 2020-06-18 16:01:57 +0100 | [diff] [blame] | 195 | }, async (response) => { |
| 196 | switch (response.action) { |
| 197 | case WebContentScriptMessageType.CONVERT_OBJECT_URL_RESPONSE: |
| 198 | if (response.attachments?.length > 0) { |
Pablo Gamito | bea6582 | 2020-09-02 16:35:32 +0200 | [diff] [blame] | 199 | const filesBlobPromises = response.attachments |
| 200 | .map(async (attachment) => { |
| 201 | const fileQueryResponse = |
| 202 | await fetch(attachment.objectUrl); |
| 203 | const blob = await fileQueryResponse.blob(); |
Pablo Gamito | c2ff984 | 2020-06-18 16:01:57 +0100 | [diff] [blame] | 204 | |
Pablo Gamito | bea6582 | 2020-09-02 16:35:32 +0200 | [diff] [blame] | 205 | /** |
| 206 | * Note: The blob's media type is not correct. |
| 207 | * It is always set to "image/png". |
| 208 | * Context: http://google3/javascript/closure/html/safeurl.js?g=0&l=256&rcl=273756987 |
| 209 | */ |
Pablo Gamito | c2ff984 | 2020-06-18 16:01:57 +0100 | [diff] [blame] | 210 | |
Pablo Gamito | bea6582 | 2020-09-02 16:35:32 +0200 | [diff] [blame] | 211 | // Clone blob to clear media type. |
| 212 | const file = new Blob([blob]); |
| 213 | file.name = attachment.name; |
Pablo Gamito | c2ff984 | 2020-06-18 16:01:57 +0100 | [diff] [blame] | 214 | |
Pablo Gamito | bea6582 | 2020-09-02 16:35:32 +0200 | [diff] [blame] | 215 | return file; |
| 216 | }); |
Pablo Gamito | c2ff984 | 2020-06-18 16:01:57 +0100 | [diff] [blame] | 217 | |
| 218 | const files = await Promise.all(filesBlobPromises); |
| 219 | |
| 220 | loading.stop(); |
| 221 | this.processFiles(files); |
| 222 | } else { |
Pablo Gamito | bea6582 | 2020-09-02 16:35:32 +0200 | [diff] [blame] | 223 | const failureMessages = 'Got no attachements from extension...'; |
Pablo Gamito | 8263930 | 2020-07-17 18:38:37 +0100 | [diff] [blame] | 224 | console.warn(failureMessages); |
| 225 | this.showSnackbarMessage(failureMessages, 3500); |
Pablo Gamito | c2ff984 | 2020-06-18 16:01:57 +0100 | [diff] [blame] | 226 | } |
| 227 | break; |
| 228 | |
| 229 | default: |
| 230 | loading.stop(); |
Pablo Gamito | bea6582 | 2020-09-02 16:35:32 +0200 | [diff] [blame] | 231 | const failureMessages = |
| 232 | 'Received unhandled response code from extension.'; |
Pablo Gamito | 8263930 | 2020-07-17 18:38:37 +0100 | [diff] [blame] | 233 | console.warn(failureMessages); |
| 234 | this.showSnackbarMessage(failureMessages, 3500); |
Pablo Gamito | c2ff984 | 2020-06-18 16:01:57 +0100 | [diff] [blame] | 235 | } |
| 236 | }); |
| 237 | } |
| 238 | }, |
Nataniel Borges | 3907154 | 2021-09-22 09:19:41 +0000 | [diff] [blame] | 239 | fileDragIn(e) { |
Nataniel Borges | f230a13 | 2021-07-28 07:13:45 +0000 | [diff] [blame] | 240 | e.preventDefault(); |
| 241 | }, |
Nataniel Borges | 3907154 | 2021-09-22 09:19:41 +0000 | [diff] [blame] | 242 | fileDragOut(e) { |
Nataniel Borges | f230a13 | 2021-07-28 07:13:45 +0000 | [diff] [blame] | 243 | e.preventDefault(); |
| 244 | }, |
| 245 | handleFileDrop(e) { |
| 246 | e.preventDefault(); |
| 247 | let droppedFiles = e.dataTransfer.files; |
| 248 | if(!droppedFiles) return; |
Nataniel Borges | 3907154 | 2021-09-22 09:19:41 +0000 | [diff] [blame] | 249 | // Record analytics event |
| 250 | this.draggedAndDropped(droppedFiles); |
| 251 | |
Nataniel Borges | f230a13 | 2021-07-28 07:13:45 +0000 | [diff] [blame] | 252 | this.processFiles(droppedFiles); |
| 253 | }, |
Pablo Gamito | c2ff984 | 2020-06-18 16:01:57 +0100 | [diff] [blame] | 254 | onLoadFile(e) { |
Pablo Gamito | 3db5c7d | 2020-06-08 10:52:14 +0100 | [diff] [blame] | 255 | const files = event.target.files || event.dataTransfer.files; |
Nataniel Borges | 3907154 | 2021-09-22 09:19:41 +0000 | [diff] [blame] | 256 | this.uploadedFileThroughFilesystem(files); |
Pablo Gamito | c2ff984 | 2020-06-18 16:01:57 +0100 | [diff] [blame] | 257 | this.processFiles(files); |
| 258 | }, |
| 259 | async processFiles(files) { |
Pablo Gamito | ed70940 | 2020-06-08 13:18:15 +0100 | [diff] [blame] | 260 | let error; |
| 261 | const decodedFiles = []; |
Pablo Gamito | 3db5c7d | 2020-06-08 10:52:14 +0100 | [diff] [blame] | 262 | for (const file of files) { |
Adam Pardyl | d905ed2 | 2019-07-08 18:22:39 +0200 | [diff] [blame] | 263 | try { |
Pablo Gamito | ed70940 | 2020-06-08 13:18:15 +0100 | [diff] [blame] | 264 | this.loadingFiles = true; |
Pablo Gamito | 8263930 | 2020-07-17 18:38:37 +0100 | [diff] [blame] | 265 | this.showSnackbarMessage(`Loading ${file.name}`, Infinity); |
Pablo Gamito | 3db5c7d | 2020-06-08 10:52:14 +0100 | [diff] [blame] | 266 | const result = await this.addFile(file); |
Pablo Gamito | ed70940 | 2020-06-08 13:18:15 +0100 | [diff] [blame] | 267 | decodedFiles.push(...result); |
Pablo Gamito | 8263930 | 2020-07-17 18:38:37 +0100 | [diff] [blame] | 268 | this.hideSnackbarMessage(); |
Pablo Gamito | bea6582 | 2020-09-02 16:35:32 +0200 | [diff] [blame] | 269 | } catch (e) { |
| 270 | this.showSnackbarMessage( |
| 271 | `Failed to load '${file.name}'...\n${e}`, 5000); |
Pablo Gamito | ed70940 | 2020-06-08 13:18:15 +0100 | [diff] [blame] | 272 | console.error(e); |
| 273 | error = e; |
Pablo Gamito | 3db5c7d | 2020-06-08 10:52:14 +0100 | [diff] [blame] | 274 | break; |
Pablo Gamito | ed70940 | 2020-06-08 13:18:15 +0100 | [diff] [blame] | 275 | } finally { |
| 276 | this.loadingFiles = false; |
Adam Pardyl | d905ed2 | 2019-07-08 18:22:39 +0200 | [diff] [blame] | 277 | } |
Adam Pardyl | d905ed2 | 2019-07-08 18:22:39 +0200 | [diff] [blame] | 278 | } |
Pablo Gamito | 3db5c7d | 2020-06-08 10:52:14 +0100 | [diff] [blame] | 279 | |
Pablo Gamito | 3db5c7d | 2020-06-08 10:52:14 +0100 | [diff] [blame] | 280 | event.target.value = ''; |
Pablo Gamito | ed70940 | 2020-06-08 13:18:15 +0100 | [diff] [blame] | 281 | |
| 282 | if (error) { |
| 283 | return; |
| 284 | } |
| 285 | |
Pablo Gamito | bea6582 | 2020-09-02 16:35:32 +0200 | [diff] [blame] | 286 | // TODO: Handle the fact that we can now have multiple files of type |
| 287 | // FILE_TYPES.TRANSACTION_EVENTS_TRACE |
Pablo Gamito | f25221c | 2020-08-06 18:26:42 +0100 | [diff] [blame] | 288 | |
Pablo Gamito | 586756f | 2020-07-17 19:19:20 +0100 | [diff] [blame] | 289 | const decodedFileTypes = new Set(Object.keys(this.dataFiles)); |
| 290 | // A file is overridden if a file of the same type is upload twice, as |
| 291 | // Winscope currently only support at most one file to each type |
Pablo Gamito | f25221c | 2020-08-06 18:26:42 +0100 | [diff] [blame] | 292 | const overriddenFileTypes = new Set(); |
Pablo Gamito | e718ac5 | 2020-09-24 12:55:10 +0200 | [diff] [blame] | 293 | const overriddenFiles = {}; // filetype => array of files |
Pablo Gamito | ed70940 | 2020-06-08 13:18:15 +0100 | [diff] [blame] | 294 | for (const decodedFile of decodedFiles) { |
Pablo Gamito | f25221c | 2020-08-06 18:26:42 +0100 | [diff] [blame] | 295 | const dataType = decodedFile.filetype; |
Pablo Gamito | 586756f | 2020-07-17 19:19:20 +0100 | [diff] [blame] | 296 | |
| 297 | if (decodedFileTypes.has(dataType)) { |
Pablo Gamito | f25221c | 2020-08-06 18:26:42 +0100 | [diff] [blame] | 298 | overriddenFileTypes.add(dataType); |
Pablo Gamito | 586756f | 2020-07-17 19:19:20 +0100 | [diff] [blame] | 299 | (overriddenFiles[dataType] = overriddenFiles[dataType] || []) |
Pablo Gamito | e718ac5 | 2020-09-24 12:55:10 +0200 | [diff] [blame] | 300 | .push(this.dataFiles[dataType]); |
Pablo Gamito | 586756f | 2020-07-17 19:19:20 +0100 | [diff] [blame] | 301 | } |
| 302 | decodedFileTypes.add(dataType); |
| 303 | |
Pablo Gamito | ed70940 | 2020-06-08 13:18:15 +0100 | [diff] [blame] | 304 | this.$set(this.dataFiles, |
Pablo Gamito | bea6582 | 2020-09-02 16:35:32 +0200 | [diff] [blame] | 305 | dataType, decodedFile.data); |
Pablo Gamito | 586756f | 2020-07-17 19:19:20 +0100 | [diff] [blame] | 306 | } |
| 307 | |
Pablo Gamito | e718ac5 | 2020-09-24 12:55:10 +0200 | [diff] [blame] | 308 | // TODO(b/169305853): Remove this once we have magic numbers or another |
| 309 | // way to detect the file type more reliably. |
| 310 | for (const dataType in overriddenFiles) { |
| 311 | if (overriddenFiles.hasOwnProperty(dataType)) { |
| 312 | const files = overriddenFiles[dataType]; |
| 313 | files.push(this.dataFiles[dataType]); |
Pablo Gamito | 586756f | 2020-07-17 19:19:20 +0100 | [diff] [blame] | 314 | |
Pablo Gamito | e718ac5 | 2020-09-24 12:55:10 +0200 | [diff] [blame] | 315 | const selectedFile = |
| 316 | this.getMostLikelyCandidateFile(dataType, files); |
| 317 | this.$set(this.dataFiles, dataType, selectedFile); |
Pablo Gamito | 586756f | 2020-07-17 19:19:20 +0100 | [diff] [blame] | 318 | |
Pablo Gamito | e718ac5 | 2020-09-24 12:55:10 +0200 | [diff] [blame] | 319 | // Remove selected file from overriden list |
| 320 | const index = files.indexOf(selectedFile); |
| 321 | files.splice(index, 1); |
Pablo Gamito | 586756f | 2020-07-17 19:19:20 +0100 | [diff] [blame] | 322 | } |
Pablo Gamito | ed70940 | 2020-06-08 13:18:15 +0100 | [diff] [blame] | 323 | } |
Pablo Gamito | e718ac5 | 2020-09-24 12:55:10 +0200 | [diff] [blame] | 324 | |
| 325 | if (overriddenFileTypes.size > 0) { |
| 326 | this.displayFilesOverridenWarning(overriddenFiles); |
| 327 | } |
Pablo Gamito | 3db5c7d | 2020-06-08 10:52:14 +0100 | [diff] [blame] | 328 | }, |
Pablo Gamito | e718ac5 | 2020-09-24 12:55:10 +0200 | [diff] [blame] | 329 | |
| 330 | /** |
| 331 | * Gets the file that is most likely to be the actual file of that type out |
| 332 | * of all the candidateFiles. This is required because there are some file |
| 333 | * types that have no magic number and may lead to false positives when |
| 334 | * decoding in decode.js. (b/169305853) |
| 335 | * @param {string} dataType - The type of the candidate files. |
| 336 | * @param {files[]} candidateFiles - The list all the files detected to be |
| 337 | * of type dataType, passed in the order |
| 338 | * they are detected/uploaded in. |
| 339 | * @return {file} - the most likely candidate. |
| 340 | */ |
| 341 | getMostLikelyCandidateFile(dataType, candidateFiles) { |
| 342 | const keyWordsByDataType = { |
| 343 | [FILE_TYPES.WINDOW_MANAGER_DUMP]: 'window', |
| 344 | [FILE_TYPES.SURFACE_FLINGER_DUMP]: 'surface', |
| 345 | }; |
| 346 | |
| 347 | if ( |
| 348 | !candidateFiles || |
| 349 | !candidateFiles.length || |
| 350 | candidateFiles.length == 0 |
| 351 | ) { |
| 352 | throw new Error('No candidate files provided'); |
| 353 | } |
| 354 | |
| 355 | if (!keyWordsByDataType.hasOwnProperty(dataType)) { |
| 356 | console.warn(`setMostLikelyCandidateFile doesn't know how to handle ` + |
| 357 | `candidates of dataType ${dataType} – setting last candidate as ` + |
| 358 | `target file.`); |
| 359 | |
| 360 | // We want to return the last candidate file so that, we always override |
| 361 | // old uploaded files with once of the latest uploaded files. |
| 362 | return candidateFiles.slice(-1)[0]; |
| 363 | } |
| 364 | |
| 365 | for (const file of candidateFiles) { |
| 366 | if (file.filename |
| 367 | .toLowerCase().includes(keyWordsByDataType[dataType])) { |
| 368 | return file; |
| 369 | } |
| 370 | } |
| 371 | |
| 372 | // We want to return the last candidate file so that, we always override |
| 373 | // old uploaded files with once of the latest uploaded files. |
| 374 | return candidateFiles.slice(-1)[0]; |
| 375 | }, |
| 376 | |
| 377 | /** |
| 378 | * Display a snackbar warning that files have been overriden and any |
| 379 | * relavant additional information in the logs. |
| 380 | * @param {{string: file[]}} overriddenFiles - a mapping from data types to |
| 381 | * the files of the of that datatype tha have been overriden. |
| 382 | */ |
| 383 | displayFilesOverridenWarning(overriddenFiles) { |
| 384 | const overriddenFileTypes = Object.keys(overriddenFiles); |
| 385 | const overriddenCount = Object.values(overriddenFiles) |
| 386 | .map((files) => files.length).reduce((length, next) => length + next); |
| 387 | |
| 388 | if (overriddenFileTypes.length === 1 && overriddenCount === 1) { |
| 389 | const type = overriddenFileTypes.values().next().value; |
| 390 | const overriddenFile = overriddenFiles[type][0].filename; |
| 391 | const keptFile = this.dataFiles[type].filename; |
| 392 | const message = |
| 393 | `'${overriddenFile}' is conflicting with '${keptFile}'. ` + |
| 394 | `Only '${keptFile}' will be kept. If you wish to display ` + |
| 395 | `'${overriddenFile}', please upload it again with no other file ` + |
| 396 | `of the same type.`; |
| 397 | |
| 398 | this.showSnackbarMessage(`WARNING: ${message}`, Infinity); |
| 399 | console.warn(message); |
| 400 | } else { |
| 401 | const message = `Mutiple conflicting files have been uploaded. ` + |
| 402 | `${overriddenCount} files have been discarded. Please check the ` + |
| 403 | `developer console for more information.`; |
| 404 | this.showSnackbarMessage(`WARNING: ${message}`, Infinity); |
| 405 | |
| 406 | const messageBuilder = []; |
| 407 | for (const type of overriddenFileTypes.values()) { |
| 408 | const keptFile = this.dataFiles[type].filename; |
| 409 | const overriddenFilesCount = overriddenFiles[type].length; |
| 410 | |
| 411 | messageBuilder.push(`${overriddenFilesCount} file` + |
| 412 | `${overriddenFilesCount > 1 ? 's' : ''} of type ${type} ` + |
| 413 | `${overriddenFilesCount > 1 ? 'have' : 'has'} been ` + |
| 414 | `overridden. Only '${keptFile}' has been kept.`); |
| 415 | } |
| 416 | |
| 417 | messageBuilder.push(''); |
| 418 | messageBuilder.push('Please reupload the specific files you want ' + |
| 419 | 'to read (one of each type).'); |
| 420 | messageBuilder.push(''); |
| 421 | |
| 422 | messageBuilder.push('===============DISCARDED FILES==============='); |
| 423 | |
| 424 | for (const type of overriddenFileTypes.values()) { |
| 425 | const discardedFiles = overriddenFiles[type]; |
| 426 | |
| 427 | messageBuilder.push(`The following files of type ${type} ` + |
| 428 | `have been discarded:`); |
| 429 | for (const discardedFile of discardedFiles) { |
| 430 | messageBuilder.push(` - ${discardedFile.filename}`); |
| 431 | } |
| 432 | messageBuilder.push(''); |
| 433 | } |
| 434 | |
| 435 | console.warn(messageBuilder.join('\n')); |
| 436 | } |
| 437 | }, |
| 438 | |
Pablo Gamito | c2ff984 | 2020-06-18 16:01:57 +0100 | [diff] [blame] | 439 | getFileExtensions(file) { |
| 440 | const split = file.name.split('.'); |
| 441 | if (split.length > 1) { |
| 442 | return split.pop(); |
| 443 | } |
| 444 | |
| 445 | return undefined; |
| 446 | }, |
Pablo Gamito | ed70940 | 2020-06-08 13:18:15 +0100 | [diff] [blame] | 447 | async addFile(file) { |
| 448 | const decodedFiles = []; |
| 449 | const type = this.fileType; |
Pablo Gamito | 3db5c7d | 2020-06-08 10:52:14 +0100 | [diff] [blame] | 450 | |
Pablo Gamito | c2ff984 | 2020-06-18 16:01:57 +0100 | [diff] [blame] | 451 | const extension = this.getFileExtensions(file); |
| 452 | |
Pablo Gamito | bea6582 | 2020-09-02 16:35:32 +0200 | [diff] [blame] | 453 | // extension === 'zip' is required on top of file.type === |
| 454 | // 'application/zip' because when loaded from the extension the type is |
| 455 | // incorrect. See comment in loadFilesFromExtension() for more |
| 456 | // information. |
Pablo Gamito | ed70940 | 2020-06-08 13:18:15 +0100 | [diff] [blame] | 457 | if (type === 'bugreport' || |
Pablo Gamito | bea6582 | 2020-09-02 16:35:32 +0200 | [diff] [blame] | 458 | (type === 'auto' && (extension === 'zip' || |
| 459 | file.type === 'application/zip'))) { |
Pablo Gamito | f77c452 | 2020-07-20 11:42:50 +0100 | [diff] [blame] | 460 | const results = await this.decodeArchive(file); |
Pablo Gamito | ed70940 | 2020-06-08 13:18:15 +0100 | [diff] [blame] | 461 | decodedFiles.push(...results); |
| 462 | } else { |
| 463 | const decodedFile = await this.decodeFile(file); |
| 464 | decodedFiles.push(decodedFile); |
| 465 | } |
Pablo Gamito | 3db5c7d | 2020-06-08 10:52:14 +0100 | [diff] [blame] | 466 | |
Pablo Gamito | ed70940 | 2020-06-08 13:18:15 +0100 | [diff] [blame] | 467 | return decodedFiles; |
| 468 | }, |
| 469 | readFile(file) { |
| 470 | return new Promise((resolve, _) => { |
Pablo Gamito | 3db5c7d | 2020-06-08 10:52:14 +0100 | [diff] [blame] | 471 | const reader = new FileReader(); |
Pablo Gamito | ed70940 | 2020-06-08 13:18:15 +0100 | [diff] [blame] | 472 | reader.onload = async (e) => { |
Pablo Gamito | 3db5c7d | 2020-06-08 10:52:14 +0100 | [diff] [blame] | 473 | const buffer = new Uint8Array(e.target.result); |
Pablo Gamito | ed70940 | 2020-06-08 13:18:15 +0100 | [diff] [blame] | 474 | resolve(buffer); |
| 475 | }; |
Pablo Gamito | 3db5c7d | 2020-06-08 10:52:14 +0100 | [diff] [blame] | 476 | reader.readAsArrayBuffer(file); |
| 477 | }); |
Adam Pardyl | d905ed2 | 2019-07-08 18:22:39 +0200 | [diff] [blame] | 478 | }, |
Pablo Gamito | ed70940 | 2020-06-08 13:18:15 +0100 | [diff] [blame] | 479 | async decodeFile(file) { |
Pablo Gamito | ed70940 | 2020-06-08 13:18:15 +0100 | [diff] [blame] | 480 | const buffer = await this.readFile(file); |
| 481 | |
Pablo Gamito | f25221c | 2020-08-06 18:26:42 +0100 | [diff] [blame] | 482 | let filetype = this.filetype; |
| 483 | let data; |
| 484 | if (filetype) { |
| 485 | const fileDecoder = FILE_DECODERS[filetype]; |
Pablo Gamito | bea6582 | 2020-09-02 16:35:32 +0200 | [diff] [blame] | 486 | data = fileDecoder.decoder( |
| 487 | buffer, fileDecoder.decoderParams, file.name, this.store); |
Pablo Gamito | ed70940 | 2020-06-08 13:18:15 +0100 | [diff] [blame] | 488 | } else { |
Pablo Gamito | f25221c | 2020-08-06 18:26:42 +0100 | [diff] [blame] | 489 | // Defaulting to auto — will attempt to detect file type |
Pablo Gamito | ed70940 | 2020-06-08 13:18:15 +0100 | [diff] [blame] | 490 | [filetype, data] = detectAndDecode(buffer, file.name, this.store); |
| 491 | } |
| 492 | |
| 493 | return {filetype, data}; |
| 494 | }, |
Pablo Gamito | f77c452 | 2020-07-20 11:42:50 +0100 | [diff] [blame] | 495 | async decodeArchive(archive) { |
| 496 | const buffer = await this.readFile(archive); |
Pablo Gamito | ed70940 | 2020-06-08 13:18:15 +0100 | [diff] [blame] | 497 | |
| 498 | const zip = new JSZip(); |
| 499 | const content = await zip.loadAsync(buffer); |
| 500 | |
| 501 | const decodedFiles = []; |
Pablo Gamito | ed70940 | 2020-06-08 13:18:15 +0100 | [diff] [blame] | 502 | |
Pablo Gamito | 348275d | 2020-07-17 19:18:47 +0100 | [diff] [blame] | 503 | for (const filename in content.files) { |
Pablo Gamito | bea6582 | 2020-09-02 16:35:32 +0200 | [diff] [blame] | 504 | if (content.files.hasOwnProperty(filename)) { |
| 505 | const file = content.files[filename]; |
Pablo Gamito | 1ca3eda | 2020-09-02 17:59:52 +0200 | [diff] [blame] | 506 | if (file.dir) { |
| 507 | // Ignore directories |
| 508 | continue; |
| 509 | } |
Pablo Gamito | 348275d | 2020-07-17 19:18:47 +0100 | [diff] [blame] | 510 | |
Pablo Gamito | bea6582 | 2020-09-02 16:35:32 +0200 | [diff] [blame] | 511 | const fileBlob = await file.async('blob'); |
Pablo Gamito | 1ca3eda | 2020-09-02 17:59:52 +0200 | [diff] [blame] | 512 | // Get only filename and remove rest of path |
| 513 | fileBlob.name = filename.split('/').slice(-1).pop(); |
Pablo Gamito | 348275d | 2020-07-17 19:18:47 +0100 | [diff] [blame] | 514 | |
Pablo Gamito | bea6582 | 2020-09-02 16:35:32 +0200 | [diff] [blame] | 515 | try { |
| 516 | const decodedFile = await this.decodeFile(fileBlob); |
Pablo Gamito | f77c452 | 2020-07-20 11:42:50 +0100 | [diff] [blame] | 517 | |
Pablo Gamito | bea6582 | 2020-09-02 16:35:32 +0200 | [diff] [blame] | 518 | decodedFiles.push(decodedFile); |
| 519 | } catch (e) { |
| 520 | if (!(e instanceof UndetectableFileType)) { |
| 521 | throw e; |
| 522 | } |
Pablo Gamito | 348275d | 2020-07-17 19:18:47 +0100 | [diff] [blame] | 523 | } |
Pablo Gamito | ed70940 | 2020-06-08 13:18:15 +0100 | [diff] [blame] | 524 | } |
| 525 | } |
| 526 | |
Pablo Gamito | 8263930 | 2020-07-17 18:38:37 +0100 | [diff] [blame] | 527 | if (decodedFiles.length == 0) { |
Pablo Gamito | bea6582 | 2020-09-02 16:35:32 +0200 | [diff] [blame] | 528 | throw new Error('No matching files found in archive', archive); |
Pablo Gamito | 8263930 | 2020-07-17 18:38:37 +0100 | [diff] [blame] | 529 | } |
| 530 | |
Pablo Gamito | ed70940 | 2020-06-08 13:18:15 +0100 | [diff] [blame] | 531 | return decodedFiles; |
| 532 | }, |
Adam Pardyl | d905ed2 | 2019-07-08 18:22:39 +0200 | [diff] [blame] | 533 | onRemoveFile(typeName) { |
| 534 | this.$delete(this.dataFiles, typeName); |
| 535 | }, |
| 536 | onSubmit() { |
Pablo Gamito | bea6582 | 2020-09-02 16:35:32 +0200 | [diff] [blame] | 537 | this.$emit('dataReady', |
| 538 | Object.keys(this.dataFiles).map((key) => this.dataFiles[key])); |
| 539 | }, |
Adam Pardyl | d905ed2 | 2019-07-08 18:22:39 +0200 | [diff] [blame] | 540 | }, |
| 541 | computed: { |
Pablo Gamito | bea6582 | 2020-09-02 16:35:32 +0200 | [diff] [blame] | 542 | dataReady: function() { |
| 543 | return Object.keys(this.dataFiles).length > 0; |
| 544 | }, |
Pablo Gamito | 050e20b | 2020-07-01 16:41:56 +0100 | [diff] [blame] | 545 | }, |
| 546 | components: { |
| 547 | 'flat-card': FlatCard, |
| 548 | }, |
Pablo Gamito | bea6582 | 2020-09-02 16:35:32 +0200 | [diff] [blame] | 549 | }; |
Adam Pardyl | d905ed2 | 2019-07-08 18:22:39 +0200 | [diff] [blame] | 550 | |
Nataniel Borges | 3907154 | 2021-09-22 09:19:41 +0000 | [diff] [blame] | 551 | </script> |
| 552 | <style> |
| 553 | .dropbox:hover { |
| 554 | background: rgb(224, 224, 224); |
| 555 | } |
| 556 | |
| 557 | .dropbox p { |
| 558 | font-size: 1.2em; |
| 559 | text-align: center; |
| 560 | padding: 50px 10px; |
| 561 | } |
| 562 | |
| 563 | .dropbox { |
| 564 | outline: 2px dashed #448aff; /* the dash box */ |
| 565 | outline-offset: -10px; |
| 566 | background: white; |
| 567 | color: #448aff; |
| 568 | padding: 10px 10px 10px 10px; |
| 569 | min-height: 200px; /* minimum height */ |
| 570 | position: relative; |
| 571 | cursor: pointer; |
| 572 | } |
| 573 | |
| 574 | .progress-spinner { |
| 575 | display: block; |
| 576 | } |
| 577 | </style> |