// Copyright 2016 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'dart:async'; import 'package:archive/archive.dart'; import 'devfs.dart'; import 'base/file_system.dart'; import 'base/process.dart'; abstract class ZipBuilder { factory ZipBuilder() { if (exitsHappy(<String>['which', 'zip'])) { return new _ZipToolBuilder(); } else { return new _ArchiveZipBuilder(); } } ZipBuilder._(); Map<String, DevFSContent> entries = <String, DevFSContent>{}; Future<Null> createZip(File outFile, Directory zipBuildDir); } class _ArchiveZipBuilder extends ZipBuilder { _ArchiveZipBuilder() : super._(); @override Future<Null> createZip(File outFile, Directory zipBuildDir) async { Archive archive = new Archive(); final Completer<Null> finished = new Completer<Null>(); int count = entries.length; entries.forEach((String archivePath, DevFSContent content) { content.contentsAsBytes().then<Null>((List<int> data) { archive.addFile(new ArchiveFile.noCompress(archivePath, data.length, data)); count -= 1; if (count == 0) finished.complete(); }); }); await finished.future; List<int> zipData = new ZipEncoder().encode(archive); await outFile.writeAsBytes(zipData); } } class _ZipToolBuilder extends ZipBuilder { _ZipToolBuilder() : super._(); @override Future<Null> createZip(File outFile, Directory zipBuildDir) async { // If there are no assets, then create an empty zip file. if (entries.isEmpty) { List<int> zipData = new ZipEncoder().encode(new Archive()); await outFile.writeAsBytes(zipData); return; } if (outFile.existsSync()) outFile.deleteSync(); if (zipBuildDir.existsSync()) zipBuildDir.deleteSync(recursive: true); zipBuildDir.createSync(recursive: true); final Completer<Null> finished = new Completer<Null>(); int count = entries.length; entries.forEach((String archivePath, DevFSContent content) { content.contentsAsBytes().then<Null>((List<int> data) { File file = fs.file(fs.path.join(zipBuildDir.path, archivePath)); file.parent.createSync(recursive: true); file.writeAsBytes(data).then<Null>((File value) { count -= 1; if (count == 0) finished.complete(); }); }); }); await finished.future; final Iterable<String> compressedNames = _getCompressedNames(); if (compressedNames.isNotEmpty) { runCheckedSync( <String>['zip', '-q', outFile.absolute.path]..addAll(compressedNames), workingDirectory: zipBuildDir.path ); } final Iterable<String> storedNames = _getStoredNames(); if (storedNames.isNotEmpty) { runCheckedSync( <String>['zip', '-q', '-0', outFile.absolute.path]..addAll(storedNames), workingDirectory: zipBuildDir.path ); } } static const List<String> _kNoCompressFileExtensions = const <String>['.png', '.jpg']; bool isAssetCompressed(String archivePath) { return !_kNoCompressFileExtensions.any( (String extension) => archivePath.endsWith(extension) ); } Iterable<String> _getCompressedNames() => entries.keys.where(isAssetCompressed); Iterable<String> _getStoredNames() => entries.keys .where((String archivePath) => !isAssetCompressed(archivePath)); }