xref: /aosp_15_r20/development/tools/winscope/src/app/trace_pipeline.ts (revision 90c8c64db3049935a07c6143d7fd006e26f8ecca)
1*90c8c64dSAndroid Build Coastguard Worker/*
2*90c8c64dSAndroid Build Coastguard Worker * Copyright (C) 2022 The Android Open Source Project
3*90c8c64dSAndroid Build Coastguard Worker *
4*90c8c64dSAndroid Build Coastguard Worker * Licensed under the Apache License, Version 2.0 (the "License");
5*90c8c64dSAndroid Build Coastguard Worker * you may not use this file except in compliance with the License.
6*90c8c64dSAndroid Build Coastguard Worker * You may obtain a copy of the License at
7*90c8c64dSAndroid Build Coastguard Worker *
8*90c8c64dSAndroid Build Coastguard Worker *      http://www.apache.org/licenses/LICENSE-2.0
9*90c8c64dSAndroid Build Coastguard Worker *
10*90c8c64dSAndroid Build Coastguard Worker * Unless required by applicable law or agreed to in writing, software
11*90c8c64dSAndroid Build Coastguard Worker * distributed under the License is distributed on an "AS IS" BASIS,
12*90c8c64dSAndroid Build Coastguard Worker * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13*90c8c64dSAndroid Build Coastguard Worker * See the License for the specific language governing permissions and
14*90c8c64dSAndroid Build Coastguard Worker * limitations under the License.
15*90c8c64dSAndroid Build Coastguard Worker */
16*90c8c64dSAndroid Build Coastguard Worker
17*90c8c64dSAndroid Build Coastguard Workerimport {FileUtils} from 'common/file_utils';
18*90c8c64dSAndroid Build Coastguard Workerimport {OnProgressUpdateType} from 'common/function_utils';
19*90c8c64dSAndroid Build Coastguard Workerimport {
20*90c8c64dSAndroid Build Coastguard Worker  TimestampConverter,
21*90c8c64dSAndroid Build Coastguard Worker  UTC_TIMEZONE_INFO,
22*90c8c64dSAndroid Build Coastguard Worker} from 'common/timestamp_converter';
23*90c8c64dSAndroid Build Coastguard Workerimport {UserNotifier} from 'common/user_notifier';
24*90c8c64dSAndroid Build Coastguard Workerimport {Analytics} from 'logging/analytics';
25*90c8c64dSAndroid Build Coastguard Workerimport {ProgressListener} from 'messaging/progress_listener';
26*90c8c64dSAndroid Build Coastguard Workerimport {CorruptedArchive, NoValidFiles} from 'messaging/user_warnings';
27*90c8c64dSAndroid Build Coastguard Workerimport {FileAndParsers} from 'parsers/file_and_parsers';
28*90c8c64dSAndroid Build Coastguard Workerimport {ParserFactory as LegacyParserFactory} from 'parsers/legacy/parser_factory';
29*90c8c64dSAndroid Build Coastguard Workerimport {ParserFactory as PerfettoParserFactory} from 'parsers/perfetto/parser_factory';
30*90c8c64dSAndroid Build Coastguard Workerimport {ParserSearch} from 'parsers/search/parser_search';
31*90c8c64dSAndroid Build Coastguard Workerimport {TracesParserFactory} from 'parsers/traces/traces_parser_factory';
32*90c8c64dSAndroid Build Coastguard Workerimport {FrameMapper} from 'trace/frame_mapper';
33*90c8c64dSAndroid Build Coastguard Workerimport {Trace} from 'trace/trace';
34*90c8c64dSAndroid Build Coastguard Workerimport {Traces} from 'trace/traces';
35*90c8c64dSAndroid Build Coastguard Workerimport {TraceFile} from 'trace/trace_file';
36*90c8c64dSAndroid Build Coastguard Workerimport {TraceEntryTypeMap, TraceType, TraceTypeUtils} from 'trace/trace_type';
37*90c8c64dSAndroid Build Coastguard Workerimport {QueryResult} from 'trace_processor/query_result';
38*90c8c64dSAndroid Build Coastguard Workerimport {FilesSource} from './files_source';
39*90c8c64dSAndroid Build Coastguard Workerimport {LoadedParsers} from './loaded_parsers';
40*90c8c64dSAndroid Build Coastguard Workerimport {TraceFileFilter} from './trace_file_filter';
41*90c8c64dSAndroid Build Coastguard Worker
42*90c8c64dSAndroid Build Coastguard Workertype UnzippedArchive = TraceFile[];
43*90c8c64dSAndroid Build Coastguard Worker
44*90c8c64dSAndroid Build Coastguard Workerexport class TracePipeline {
45*90c8c64dSAndroid Build Coastguard Worker  private loadedParsers = new LoadedParsers();
46*90c8c64dSAndroid Build Coastguard Worker  private traceFileFilter = new TraceFileFilter();
47*90c8c64dSAndroid Build Coastguard Worker  private tracesParserFactory = new TracesParserFactory();
48*90c8c64dSAndroid Build Coastguard Worker  private traces = new Traces();
49*90c8c64dSAndroid Build Coastguard Worker  private downloadArchiveFilename?: string;
50*90c8c64dSAndroid Build Coastguard Worker  private timestampConverter = new TimestampConverter(UTC_TIMEZONE_INFO);
51*90c8c64dSAndroid Build Coastguard Worker
52*90c8c64dSAndroid Build Coastguard Worker  async loadFiles(
53*90c8c64dSAndroid Build Coastguard Worker    files: File[],
54*90c8c64dSAndroid Build Coastguard Worker    source: FilesSource,
55*90c8c64dSAndroid Build Coastguard Worker    progressListener: ProgressListener | undefined,
56*90c8c64dSAndroid Build Coastguard Worker  ) {
57*90c8c64dSAndroid Build Coastguard Worker    this.downloadArchiveFilename = this.makeDownloadArchiveFilename(
58*90c8c64dSAndroid Build Coastguard Worker      files,
59*90c8c64dSAndroid Build Coastguard Worker      source,
60*90c8c64dSAndroid Build Coastguard Worker    );
61*90c8c64dSAndroid Build Coastguard Worker
62*90c8c64dSAndroid Build Coastguard Worker    try {
63*90c8c64dSAndroid Build Coastguard Worker      const unzippedArchives = await this.unzipFiles(files, progressListener);
64*90c8c64dSAndroid Build Coastguard Worker
65*90c8c64dSAndroid Build Coastguard Worker      if (unzippedArchives.length === 0) {
66*90c8c64dSAndroid Build Coastguard Worker        UserNotifier.add(new NoValidFiles());
67*90c8c64dSAndroid Build Coastguard Worker        return;
68*90c8c64dSAndroid Build Coastguard Worker      }
69*90c8c64dSAndroid Build Coastguard Worker
70*90c8c64dSAndroid Build Coastguard Worker      for (const unzippedArchive of unzippedArchives) {
71*90c8c64dSAndroid Build Coastguard Worker        await this.loadUnzippedArchive(unzippedArchive, progressListener);
72*90c8c64dSAndroid Build Coastguard Worker      }
73*90c8c64dSAndroid Build Coastguard Worker
74*90c8c64dSAndroid Build Coastguard Worker      this.traces = new Traces();
75*90c8c64dSAndroid Build Coastguard Worker
76*90c8c64dSAndroid Build Coastguard Worker      this.loadedParsers.getParsers().forEach((parser) => {
77*90c8c64dSAndroid Build Coastguard Worker        const trace = Trace.fromParser(parser);
78*90c8c64dSAndroid Build Coastguard Worker        this.traces.addTrace(trace);
79*90c8c64dSAndroid Build Coastguard Worker        Analytics.Tracing.logTraceLoaded(parser);
80*90c8c64dSAndroid Build Coastguard Worker      });
81*90c8c64dSAndroid Build Coastguard Worker
82*90c8c64dSAndroid Build Coastguard Worker      const tracesParsers = await this.tracesParserFactory.createParsers(
83*90c8c64dSAndroid Build Coastguard Worker        this.traces,
84*90c8c64dSAndroid Build Coastguard Worker        this.timestampConverter,
85*90c8c64dSAndroid Build Coastguard Worker      );
86*90c8c64dSAndroid Build Coastguard Worker
87*90c8c64dSAndroid Build Coastguard Worker      tracesParsers.forEach((tracesParser) => {
88*90c8c64dSAndroid Build Coastguard Worker        const trace = Trace.fromParser(tracesParser);
89*90c8c64dSAndroid Build Coastguard Worker        this.traces.addTrace(trace);
90*90c8c64dSAndroid Build Coastguard Worker      });
91*90c8c64dSAndroid Build Coastguard Worker
92*90c8c64dSAndroid Build Coastguard Worker      const hasTransitionTrace =
93*90c8c64dSAndroid Build Coastguard Worker        this.traces.getTrace(TraceType.TRANSITION) !== undefined;
94*90c8c64dSAndroid Build Coastguard Worker      if (hasTransitionTrace) {
95*90c8c64dSAndroid Build Coastguard Worker        this.removeTracesAndParsersByType(TraceType.WM_TRANSITION);
96*90c8c64dSAndroid Build Coastguard Worker        this.removeTracesAndParsersByType(TraceType.SHELL_TRANSITION);
97*90c8c64dSAndroid Build Coastguard Worker      }
98*90c8c64dSAndroid Build Coastguard Worker
99*90c8c64dSAndroid Build Coastguard Worker      const hasCujTrace = this.traces.getTrace(TraceType.CUJS) !== undefined;
100*90c8c64dSAndroid Build Coastguard Worker      if (hasCujTrace) {
101*90c8c64dSAndroid Build Coastguard Worker        this.removeTracesAndParsersByType(TraceType.EVENT_LOG);
102*90c8c64dSAndroid Build Coastguard Worker      }
103*90c8c64dSAndroid Build Coastguard Worker
104*90c8c64dSAndroid Build Coastguard Worker      const hasMergedInputTrace =
105*90c8c64dSAndroid Build Coastguard Worker        this.traces.getTrace(TraceType.INPUT_EVENT_MERGED) !== undefined;
106*90c8c64dSAndroid Build Coastguard Worker      if (hasMergedInputTrace) {
107*90c8c64dSAndroid Build Coastguard Worker        this.removeTracesAndParsersByType(TraceType.INPUT_KEY_EVENT);
108*90c8c64dSAndroid Build Coastguard Worker        this.removeTracesAndParsersByType(TraceType.INPUT_MOTION_EVENT);
109*90c8c64dSAndroid Build Coastguard Worker      }
110*90c8c64dSAndroid Build Coastguard Worker    } finally {
111*90c8c64dSAndroid Build Coastguard Worker      progressListener?.onOperationFinished(true);
112*90c8c64dSAndroid Build Coastguard Worker    }
113*90c8c64dSAndroid Build Coastguard Worker  }
114*90c8c64dSAndroid Build Coastguard Worker
115*90c8c64dSAndroid Build Coastguard Worker  removeTrace<T extends TraceType>(
116*90c8c64dSAndroid Build Coastguard Worker    trace: Trace<TraceEntryTypeMap[T]>,
117*90c8c64dSAndroid Build Coastguard Worker    keepFileForDownload = false,
118*90c8c64dSAndroid Build Coastguard Worker  ) {
119*90c8c64dSAndroid Build Coastguard Worker    this.loadedParsers.remove(trace.getParser(), keepFileForDownload);
120*90c8c64dSAndroid Build Coastguard Worker    this.traces.deleteTrace(trace);
121*90c8c64dSAndroid Build Coastguard Worker  }
122*90c8c64dSAndroid Build Coastguard Worker
123*90c8c64dSAndroid Build Coastguard Worker  async makeZipArchiveWithLoadedTraceFiles(
124*90c8c64dSAndroid Build Coastguard Worker    onProgressUpdate?: OnProgressUpdateType,
125*90c8c64dSAndroid Build Coastguard Worker  ): Promise<Blob> {
126*90c8c64dSAndroid Build Coastguard Worker    return this.loadedParsers.makeZipArchive(onProgressUpdate);
127*90c8c64dSAndroid Build Coastguard Worker  }
128*90c8c64dSAndroid Build Coastguard Worker
129*90c8c64dSAndroid Build Coastguard Worker  filterTracesWithoutVisualization() {
130*90c8c64dSAndroid Build Coastguard Worker    const tracesWithoutVisualization = this.traces
131*90c8c64dSAndroid Build Coastguard Worker      .mapTrace((trace) => {
132*90c8c64dSAndroid Build Coastguard Worker        if (!TraceTypeUtils.isTraceTypeWithViewer(trace.type)) {
133*90c8c64dSAndroid Build Coastguard Worker          return trace;
134*90c8c64dSAndroid Build Coastguard Worker        }
135*90c8c64dSAndroid Build Coastguard Worker        return undefined;
136*90c8c64dSAndroid Build Coastguard Worker      })
137*90c8c64dSAndroid Build Coastguard Worker      .filter((trace) => trace !== undefined) as Array<Trace<object>>;
138*90c8c64dSAndroid Build Coastguard Worker    tracesWithoutVisualization.forEach((trace) =>
139*90c8c64dSAndroid Build Coastguard Worker      this.traces.deleteTrace(trace),
140*90c8c64dSAndroid Build Coastguard Worker    );
141*90c8c64dSAndroid Build Coastguard Worker  }
142*90c8c64dSAndroid Build Coastguard Worker
143*90c8c64dSAndroid Build Coastguard Worker  async buildTraces() {
144*90c8c64dSAndroid Build Coastguard Worker    for (const trace of this.traces) {
145*90c8c64dSAndroid Build Coastguard Worker      if (trace.lengthEntries === 0 || trace.isDumpWithoutTimestamp()) {
146*90c8c64dSAndroid Build Coastguard Worker        continue;
147*90c8c64dSAndroid Build Coastguard Worker      } else {
148*90c8c64dSAndroid Build Coastguard Worker        const timestamp = trace.getEntry(0).getTimestamp();
149*90c8c64dSAndroid Build Coastguard Worker        this.timestampConverter.initializeUTCOffset(timestamp);
150*90c8c64dSAndroid Build Coastguard Worker        break;
151*90c8c64dSAndroid Build Coastguard Worker      }
152*90c8c64dSAndroid Build Coastguard Worker    }
153*90c8c64dSAndroid Build Coastguard Worker    await new FrameMapper(this.traces).computeMapping();
154*90c8c64dSAndroid Build Coastguard Worker  }
155*90c8c64dSAndroid Build Coastguard Worker
156*90c8c64dSAndroid Build Coastguard Worker  getTraces(): Traces {
157*90c8c64dSAndroid Build Coastguard Worker    return this.traces;
158*90c8c64dSAndroid Build Coastguard Worker  }
159*90c8c64dSAndroid Build Coastguard Worker
160*90c8c64dSAndroid Build Coastguard Worker  getDownloadArchiveFilename(): string {
161*90c8c64dSAndroid Build Coastguard Worker    return this.downloadArchiveFilename ?? 'winscope';
162*90c8c64dSAndroid Build Coastguard Worker  }
163*90c8c64dSAndroid Build Coastguard Worker
164*90c8c64dSAndroid Build Coastguard Worker  getTimestampConverter(): TimestampConverter {
165*90c8c64dSAndroid Build Coastguard Worker    return this.timestampConverter;
166*90c8c64dSAndroid Build Coastguard Worker  }
167*90c8c64dSAndroid Build Coastguard Worker
168*90c8c64dSAndroid Build Coastguard Worker  async getScreenRecordingVideo(): Promise<undefined | Blob> {
169*90c8c64dSAndroid Build Coastguard Worker    const traces = this.getTraces();
170*90c8c64dSAndroid Build Coastguard Worker    const screenRecording =
171*90c8c64dSAndroid Build Coastguard Worker      traces.getTrace(TraceType.SCREEN_RECORDING) ??
172*90c8c64dSAndroid Build Coastguard Worker      traces.getTrace(TraceType.SCREENSHOT);
173*90c8c64dSAndroid Build Coastguard Worker    if (!screenRecording || screenRecording.lengthEntries === 0) {
174*90c8c64dSAndroid Build Coastguard Worker      return undefined;
175*90c8c64dSAndroid Build Coastguard Worker    }
176*90c8c64dSAndroid Build Coastguard Worker    return (await screenRecording.getEntry(0).getValue()).videoData;
177*90c8c64dSAndroid Build Coastguard Worker  }
178*90c8c64dSAndroid Build Coastguard Worker
179*90c8c64dSAndroid Build Coastguard Worker  async tryCreateSearchTrace(
180*90c8c64dSAndroid Build Coastguard Worker    query: string,
181*90c8c64dSAndroid Build Coastguard Worker  ): Promise<Trace<QueryResult> | undefined> {
182*90c8c64dSAndroid Build Coastguard Worker    try {
183*90c8c64dSAndroid Build Coastguard Worker      const parser = new ParserSearch(query, this.timestampConverter);
184*90c8c64dSAndroid Build Coastguard Worker      await parser.parse();
185*90c8c64dSAndroid Build Coastguard Worker      const trace = Trace.fromParser(parser);
186*90c8c64dSAndroid Build Coastguard Worker      this.traces.addTrace(trace);
187*90c8c64dSAndroid Build Coastguard Worker      return trace;
188*90c8c64dSAndroid Build Coastguard Worker    } catch (e) {
189*90c8c64dSAndroid Build Coastguard Worker      return undefined;
190*90c8c64dSAndroid Build Coastguard Worker    }
191*90c8c64dSAndroid Build Coastguard Worker  }
192*90c8c64dSAndroid Build Coastguard Worker
193*90c8c64dSAndroid Build Coastguard Worker  clear() {
194*90c8c64dSAndroid Build Coastguard Worker    this.loadedParsers.clear();
195*90c8c64dSAndroid Build Coastguard Worker    this.traces = new Traces();
196*90c8c64dSAndroid Build Coastguard Worker    this.timestampConverter.clear();
197*90c8c64dSAndroid Build Coastguard Worker    this.downloadArchiveFilename = undefined;
198*90c8c64dSAndroid Build Coastguard Worker  }
199*90c8c64dSAndroid Build Coastguard Worker
200*90c8c64dSAndroid Build Coastguard Worker  private async loadUnzippedArchive(
201*90c8c64dSAndroid Build Coastguard Worker    unzippedArchive: UnzippedArchive,
202*90c8c64dSAndroid Build Coastguard Worker    progressListener: ProgressListener | undefined,
203*90c8c64dSAndroid Build Coastguard Worker  ) {
204*90c8c64dSAndroid Build Coastguard Worker    const filterResult = await this.traceFileFilter.filter(unzippedArchive);
205*90c8c64dSAndroid Build Coastguard Worker    if (filterResult.timezoneInfo) {
206*90c8c64dSAndroid Build Coastguard Worker      this.timestampConverter = new TimestampConverter(
207*90c8c64dSAndroid Build Coastguard Worker        filterResult.timezoneInfo,
208*90c8c64dSAndroid Build Coastguard Worker      );
209*90c8c64dSAndroid Build Coastguard Worker    }
210*90c8c64dSAndroid Build Coastguard Worker
211*90c8c64dSAndroid Build Coastguard Worker    if (!filterResult.perfetto && filterResult.legacy.length === 0) {
212*90c8c64dSAndroid Build Coastguard Worker      UserNotifier.add(new NoValidFiles());
213*90c8c64dSAndroid Build Coastguard Worker      return;
214*90c8c64dSAndroid Build Coastguard Worker    }
215*90c8c64dSAndroid Build Coastguard Worker
216*90c8c64dSAndroid Build Coastguard Worker    const legacyParsers = await new LegacyParserFactory().createParsers(
217*90c8c64dSAndroid Build Coastguard Worker      filterResult.legacy,
218*90c8c64dSAndroid Build Coastguard Worker      this.timestampConverter,
219*90c8c64dSAndroid Build Coastguard Worker      filterResult.metadata,
220*90c8c64dSAndroid Build Coastguard Worker      progressListener,
221*90c8c64dSAndroid Build Coastguard Worker    );
222*90c8c64dSAndroid Build Coastguard Worker
223*90c8c64dSAndroid Build Coastguard Worker    let perfettoParsers: FileAndParsers | undefined;
224*90c8c64dSAndroid Build Coastguard Worker
225*90c8c64dSAndroid Build Coastguard Worker    if (filterResult.perfetto) {
226*90c8c64dSAndroid Build Coastguard Worker      const parsers = await new PerfettoParserFactory().createParsers(
227*90c8c64dSAndroid Build Coastguard Worker        filterResult.perfetto,
228*90c8c64dSAndroid Build Coastguard Worker        this.timestampConverter,
229*90c8c64dSAndroid Build Coastguard Worker        progressListener,
230*90c8c64dSAndroid Build Coastguard Worker      );
231*90c8c64dSAndroid Build Coastguard Worker      perfettoParsers = new FileAndParsers(filterResult.perfetto, parsers);
232*90c8c64dSAndroid Build Coastguard Worker    }
233*90c8c64dSAndroid Build Coastguard Worker
234*90c8c64dSAndroid Build Coastguard Worker    const monotonicTimeOffset =
235*90c8c64dSAndroid Build Coastguard Worker      this.loadedParsers.getLatestRealToMonotonicOffset(
236*90c8c64dSAndroid Build Coastguard Worker        legacyParsers
237*90c8c64dSAndroid Build Coastguard Worker          .map((fileAndParser) => fileAndParser.parser)
238*90c8c64dSAndroid Build Coastguard Worker          .concat(perfettoParsers?.parsers ?? []),
239*90c8c64dSAndroid Build Coastguard Worker      );
240*90c8c64dSAndroid Build Coastguard Worker
241*90c8c64dSAndroid Build Coastguard Worker    const realToBootTimeOffset =
242*90c8c64dSAndroid Build Coastguard Worker      this.loadedParsers.getLatestRealToBootTimeOffset(
243*90c8c64dSAndroid Build Coastguard Worker        legacyParsers
244*90c8c64dSAndroid Build Coastguard Worker          .map((fileAndParser) => fileAndParser.parser)
245*90c8c64dSAndroid Build Coastguard Worker          .concat(perfettoParsers?.parsers ?? []),
246*90c8c64dSAndroid Build Coastguard Worker      );
247*90c8c64dSAndroid Build Coastguard Worker
248*90c8c64dSAndroid Build Coastguard Worker    if (monotonicTimeOffset !== undefined) {
249*90c8c64dSAndroid Build Coastguard Worker      this.timestampConverter.setRealToMonotonicTimeOffsetNs(
250*90c8c64dSAndroid Build Coastguard Worker        monotonicTimeOffset,
251*90c8c64dSAndroid Build Coastguard Worker      );
252*90c8c64dSAndroid Build Coastguard Worker    }
253*90c8c64dSAndroid Build Coastguard Worker    if (realToBootTimeOffset !== undefined) {
254*90c8c64dSAndroid Build Coastguard Worker      this.timestampConverter.setRealToBootTimeOffsetNs(realToBootTimeOffset);
255*90c8c64dSAndroid Build Coastguard Worker    }
256*90c8c64dSAndroid Build Coastguard Worker
257*90c8c64dSAndroid Build Coastguard Worker    perfettoParsers?.parsers.forEach((p) => p.createTimestamps());
258*90c8c64dSAndroid Build Coastguard Worker    legacyParsers.forEach((fileAndParser) =>
259*90c8c64dSAndroid Build Coastguard Worker      fileAndParser.parser.createTimestamps(),
260*90c8c64dSAndroid Build Coastguard Worker    );
261*90c8c64dSAndroid Build Coastguard Worker
262*90c8c64dSAndroid Build Coastguard Worker    this.loadedParsers.addParsers(legacyParsers, perfettoParsers);
263*90c8c64dSAndroid Build Coastguard Worker  }
264*90c8c64dSAndroid Build Coastguard Worker
265*90c8c64dSAndroid Build Coastguard Worker  private makeDownloadArchiveFilename(
266*90c8c64dSAndroid Build Coastguard Worker    files: File[],
267*90c8c64dSAndroid Build Coastguard Worker    source: FilesSource,
268*90c8c64dSAndroid Build Coastguard Worker  ): string {
269*90c8c64dSAndroid Build Coastguard Worker    // set download archive file name, used to download all traces
270*90c8c64dSAndroid Build Coastguard Worker    let filenameWithCurrTime: string;
271*90c8c64dSAndroid Build Coastguard Worker    const currTime = new Date().toISOString().slice(0, -5).replace('T', '_');
272*90c8c64dSAndroid Build Coastguard Worker    if (!this.downloadArchiveFilename && files.length === 1) {
273*90c8c64dSAndroid Build Coastguard Worker      const filenameNoDir = FileUtils.removeDirFromFileName(files[0].name);
274*90c8c64dSAndroid Build Coastguard Worker      const filenameNoDirOrExt =
275*90c8c64dSAndroid Build Coastguard Worker        FileUtils.removeExtensionFromFilename(filenameNoDir);
276*90c8c64dSAndroid Build Coastguard Worker      filenameWithCurrTime = `${filenameNoDirOrExt}_${currTime}`;
277*90c8c64dSAndroid Build Coastguard Worker    } else {
278*90c8c64dSAndroid Build Coastguard Worker      filenameWithCurrTime = `${source}_${currTime}`;
279*90c8c64dSAndroid Build Coastguard Worker    }
280*90c8c64dSAndroid Build Coastguard Worker
281*90c8c64dSAndroid Build Coastguard Worker    const archiveFilenameNoIllegalChars = filenameWithCurrTime.replace(
282*90c8c64dSAndroid Build Coastguard Worker      FileUtils.ILLEGAL_FILENAME_CHARACTERS_REGEX,
283*90c8c64dSAndroid Build Coastguard Worker      '_',
284*90c8c64dSAndroid Build Coastguard Worker    );
285*90c8c64dSAndroid Build Coastguard Worker    if (FileUtils.DOWNLOAD_FILENAME_REGEX.test(archiveFilenameNoIllegalChars)) {
286*90c8c64dSAndroid Build Coastguard Worker      return archiveFilenameNoIllegalChars;
287*90c8c64dSAndroid Build Coastguard Worker    } else {
288*90c8c64dSAndroid Build Coastguard Worker      console.error(
289*90c8c64dSAndroid Build Coastguard Worker        "Cannot convert uploaded archive filename to acceptable format for download. Defaulting download filename to 'winscope.zip'.",
290*90c8c64dSAndroid Build Coastguard Worker      );
291*90c8c64dSAndroid Build Coastguard Worker      return 'winscope';
292*90c8c64dSAndroid Build Coastguard Worker    }
293*90c8c64dSAndroid Build Coastguard Worker  }
294*90c8c64dSAndroid Build Coastguard Worker
295*90c8c64dSAndroid Build Coastguard Worker  private async unzipFiles(
296*90c8c64dSAndroid Build Coastguard Worker    files: File[],
297*90c8c64dSAndroid Build Coastguard Worker    progressListener: ProgressListener | undefined,
298*90c8c64dSAndroid Build Coastguard Worker  ): Promise<UnzippedArchive[]> {
299*90c8c64dSAndroid Build Coastguard Worker    const unzippedArchives: UnzippedArchive[] = [];
300*90c8c64dSAndroid Build Coastguard Worker    const progressMessage = 'Unzipping files...';
301*90c8c64dSAndroid Build Coastguard Worker
302*90c8c64dSAndroid Build Coastguard Worker    progressListener?.onProgressUpdate(progressMessage, 0);
303*90c8c64dSAndroid Build Coastguard Worker
304*90c8c64dSAndroid Build Coastguard Worker    const currArchive: UnzippedArchive = [];
305*90c8c64dSAndroid Build Coastguard Worker    for (let i = 0; i < files.length; i++) {
306*90c8c64dSAndroid Build Coastguard Worker      let file = files[i];
307*90c8c64dSAndroid Build Coastguard Worker
308*90c8c64dSAndroid Build Coastguard Worker      const onSubProgressUpdate = (subPercentage: number) => {
309*90c8c64dSAndroid Build Coastguard Worker        const totalPercentage =
310*90c8c64dSAndroid Build Coastguard Worker          (100 * i) / files.length + subPercentage / files.length;
311*90c8c64dSAndroid Build Coastguard Worker        progressListener?.onProgressUpdate(progressMessage, totalPercentage);
312*90c8c64dSAndroid Build Coastguard Worker      };
313*90c8c64dSAndroid Build Coastguard Worker
314*90c8c64dSAndroid Build Coastguard Worker      if (await FileUtils.isGZipFile(file)) {
315*90c8c64dSAndroid Build Coastguard Worker        file = await FileUtils.decompressGZipFile(file);
316*90c8c64dSAndroid Build Coastguard Worker      }
317*90c8c64dSAndroid Build Coastguard Worker
318*90c8c64dSAndroid Build Coastguard Worker      if (await FileUtils.isZipFile(file)) {
319*90c8c64dSAndroid Build Coastguard Worker        try {
320*90c8c64dSAndroid Build Coastguard Worker          const subFiles = await FileUtils.unzipFile(file, onSubProgressUpdate);
321*90c8c64dSAndroid Build Coastguard Worker          const subTraceFiles = subFiles.map((subFile) => {
322*90c8c64dSAndroid Build Coastguard Worker            return new TraceFile(subFile, file);
323*90c8c64dSAndroid Build Coastguard Worker          });
324*90c8c64dSAndroid Build Coastguard Worker          unzippedArchives.push([...subTraceFiles]);
325*90c8c64dSAndroid Build Coastguard Worker          onSubProgressUpdate(100);
326*90c8c64dSAndroid Build Coastguard Worker        } catch (e) {
327*90c8c64dSAndroid Build Coastguard Worker          UserNotifier.add(new CorruptedArchive(file));
328*90c8c64dSAndroid Build Coastguard Worker        }
329*90c8c64dSAndroid Build Coastguard Worker      } else {
330*90c8c64dSAndroid Build Coastguard Worker        currArchive.push(new TraceFile(file, undefined));
331*90c8c64dSAndroid Build Coastguard Worker      }
332*90c8c64dSAndroid Build Coastguard Worker    }
333*90c8c64dSAndroid Build Coastguard Worker    if (currArchive.length > 0) {
334*90c8c64dSAndroid Build Coastguard Worker      unzippedArchives.push(currArchive);
335*90c8c64dSAndroid Build Coastguard Worker    }
336*90c8c64dSAndroid Build Coastguard Worker    progressListener?.onProgressUpdate(progressMessage, 100);
337*90c8c64dSAndroid Build Coastguard Worker
338*90c8c64dSAndroid Build Coastguard Worker    return unzippedArchives;
339*90c8c64dSAndroid Build Coastguard Worker  }
340*90c8c64dSAndroid Build Coastguard Worker
341*90c8c64dSAndroid Build Coastguard Worker  private removeTracesAndParsersByType(type: TraceType) {
342*90c8c64dSAndroid Build Coastguard Worker    const traces = this.traces.getTraces(type);
343*90c8c64dSAndroid Build Coastguard Worker    traces.forEach((trace) => {
344*90c8c64dSAndroid Build Coastguard Worker      this.removeTrace(trace, true);
345*90c8c64dSAndroid Build Coastguard Worker    });
346*90c8c64dSAndroid Build Coastguard Worker  }
347*90c8c64dSAndroid Build Coastguard Worker}
348