Commit b6644733 authored by John McCutchan's avatar John McCutchan

Support for synchronizing assets onto a DevFS

parent 1fe118fe
This diff is collapsed.
......@@ -9,23 +9,37 @@ import 'dart:io';
import 'package:path/path.dart' as path;
import 'dart/package_map.dart';
import 'asset.dart';
import 'globals.dart';
import 'observatory.dart';
// A file that has been added to a DevFS.
class DevFSEntry {
DevFSEntry(this.devicePath, this.file);
DevFSEntry(this.devicePath, this.file)
: bundleEntry = null;
DevFSEntry.bundle(this.devicePath, AssetBundleEntry bundleEntry)
: bundleEntry = bundleEntry,
file = bundleEntry.file;
final String devicePath;
final AssetBundleEntry bundleEntry;
final File file;
FileStat _fileStat;
// When we updated the DevFS, did we see this entry?
bool _wasSeen = false;
DateTime get lastModified => _fileStat?.modified;
bool get stillExists {
if (_isSourceEntry)
return true;
_stat();
return _fileStat.type != FileSystemEntityType.NOT_FOUND;
}
bool get isModified {
if (_isSourceEntry)
return true;
if (_fileStat == null) {
_stat();
return true;
......@@ -36,8 +50,18 @@ class DevFSEntry {
}
void _stat() {
if (_isSourceEntry)
return;
_fileStat = file.statSync();
}
bool get _isSourceEntry => file == null;
Future<List<int>> contentsAsBytes() async {
if (_isSourceEntry)
return bundleEntry.contentsAsBytes();
return file.readAsBytes();
}
}
......@@ -46,6 +70,7 @@ abstract class DevFSOperations {
Future<Uri> create(String fsName);
Future<dynamic> destroy(String fsName);
Future<dynamic> writeFile(String fsName, DevFSEntry entry);
Future<dynamic> deleteFile(String fsName, DevFSEntry entry);
Future<dynamic> writeSource(String fsName,
String devicePath,
String contents);
......@@ -74,7 +99,7 @@ class ServiceProtocolDevFSOperations implements DevFSOperations {
Future<dynamic> writeFile(String fsName, DevFSEntry entry) async {
List<int> bytes;
try {
bytes = await entry.file.readAsBytes();
bytes = await entry.contentsAsBytes();
} catch (e) {
return e;
}
......@@ -91,6 +116,11 @@ class ServiceProtocolDevFSOperations implements DevFSOperations {
}
}
@override
Future<dynamic> deleteFile(String fsName, DevFSEntry entry) async {
// TODO(johnmccutchan): Add file deletion to the devFS protocol.
}
@override
Future<dynamic> writeSource(String fsName,
String devicePath,
......@@ -135,7 +165,11 @@ class DevFS {
return await _operations.destroy(fsName);
}
Future<dynamic> update() async {
Future<dynamic> update([AssetBundle bundle = null]) async {
// Mark all entries as not seen.
_entries.forEach((String path, DevFSEntry entry) {
entry._wasSeen = false;
});
printTrace('DevFS: Starting sync from $rootDirectory');
// Send the root and lib directories.
Directory directory = rootDirectory;
......@@ -162,6 +196,27 @@ class DevFS {
}
}
}
if (bundle != null) {
// Synchronize asset bundle.
for (AssetBundleEntry entry in bundle.entries) {
// We write the assets into 'build/flx' so that they are in the
// same location in DevFS and the iOS simulator.
final String devicePath = path.join('build/flx', entry.archivePath);
_syncBundleEntry(devicePath, entry);
}
}
// Handle deletions.
final List<String> toRemove = new List<String>();
_entries.forEach((String path, DevFSEntry entry) {
if (!entry._wasSeen) {
_deleteEntry(path, entry);
toRemove.add(path);
}
});
for (int i = 0; i < toRemove.length; i++) {
_entries.remove(toRemove[i]);
}
// Send the assets.
printTrace('DevFS: Waiting for sync of ${_pendingWrites.length} files '
'to finish');
await Future.wait(_pendingWrites);
......@@ -175,6 +230,10 @@ class DevFS {
logger.flush();
}
void _deleteEntry(String path, DevFSEntry entry) {
_pendingWrites.add(_operations.deleteFile(fsName, entry));
}
void _syncFile(String devicePath, File file) {
DevFSEntry entry = _entries[devicePath];
if (entry == null) {
......@@ -182,6 +241,7 @@ class DevFS {
entry = new DevFSEntry(devicePath, file);
_entries[devicePath] = entry;
}
entry._wasSeen = true;
bool needsWrite = entry.isModified;
if (needsWrite) {
Future<dynamic> pendingWrite = _operations.writeFile(fsName, entry);
......@@ -193,13 +253,29 @@ class DevFS {
}
}
bool _shouldIgnore(String path) {
void _syncBundleEntry(String devicePath, AssetBundleEntry assetBundleEntry) {
DevFSEntry entry = _entries[devicePath];
if (entry == null) {
// New file.
entry = new DevFSEntry.bundle(devicePath, assetBundleEntry);
_entries[devicePath] = entry;
}
entry._wasSeen = true;
Future<dynamic> pendingWrite = _operations.writeFile(fsName, entry);
if (pendingWrite != null) {
_pendingWrites.add(pendingWrite);
} else {
printTrace('DevFS: Failed to sync "$devicePath"');
}
}
bool _shouldIgnore(String devicePath) {
List<String> ignoredPrefixes = <String>['android/',
'build/',
'ios/',
'packages/analyzer'];
for (String ignoredPrefix in ignoredPrefixes) {
if (path.startsWith(ignoredPrefix))
if (devicePath.startsWith(ignoredPrefix))
return true;
}
return false;
......
This diff is collapsed.
......@@ -2,12 +2,12 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:convert' show UTF8;
import 'dart:io';
import 'package:archive/archive.dart';
import 'package:path/path.dart' as path;
import 'asset.dart';
import 'base/process.dart';
abstract class ZipBuilder {
......@@ -21,30 +21,13 @@ abstract class ZipBuilder {
ZipBuilder._();
List<ZipEntry> entries = <ZipEntry>[];
List<AssetBundleEntry> entries = <AssetBundleEntry>[];
void addEntry(ZipEntry entry) => entries.add(entry);
void addEntry(AssetBundleEntry entry) => entries.add(entry);
void createZip(File outFile, Directory zipBuildDir);
}
class ZipEntry {
ZipEntry.fromFile(this.archivePath, File file) {
this._file = file;
}
ZipEntry.fromString(this.archivePath, String contents) {
this._contents = contents;
}
final String archivePath;
File _file;
String _contents;
bool get isStringEntry => _contents != null;
}
class _ArchiveZipBuilder extends ZipBuilder {
_ArchiveZipBuilder() : super._();
......@@ -52,14 +35,9 @@ class _ArchiveZipBuilder extends ZipBuilder {
void createZip(File outFile, Directory zipBuildDir) {
Archive archive = new Archive();
for (ZipEntry entry in entries) {
if (entry.isStringEntry) {
List<int> data = UTF8.encode(entry._contents);
for (AssetBundleEntry entry in entries) {
List<int> data = entry.contentsAsBytes();
archive.addFile(new ArchiveFile.noCompress(entry.archivePath, data.length, data));
} else {
List<int> data = entry._file.readAsBytesSync();
archive.addFile(new ArchiveFile(entry.archivePath, data.length, data));
}
}
List<int> zipData = new ZipEncoder().encode(archive);
......@@ -79,18 +57,11 @@ class _ZipToolBuilder extends ZipBuilder {
zipBuildDir.deleteSync(recursive: true);
zipBuildDir.createSync(recursive: true);
for (ZipEntry entry in entries) {
if (entry.isStringEntry) {
List<int> data = UTF8.encode(entry._contents);
for (AssetBundleEntry entry in entries) {
List<int> data = entry.contentsAsBytes();
File file = new File(path.join(zipBuildDir.path, entry.archivePath));
file.parent.createSync(recursive: true);
file.writeAsBytesSync(data);
} else {
List<int> data = entry._file.readAsBytesSync();
File file = new File(path.join(zipBuildDir.path, entry.archivePath));
file.parent.createSync(recursive: true);
file.writeAsBytesSync(data);
}
}
if (_getCompressedNames().isNotEmpty) {
......@@ -112,13 +83,13 @@ class _ZipToolBuilder extends ZipBuilder {
Iterable<String> _getCompressedNames() {
return entries
.where((ZipEntry entry) => !entry.isStringEntry)
.map((ZipEntry entry) => entry.archivePath);
.where((AssetBundleEntry entry) => !entry.isStringEntry)
.map((AssetBundleEntry entry) => entry.archivePath);
}
Iterable<String> _getStoredNames() {
return entries
.where((ZipEntry entry) => entry.isStringEntry)
.map((ZipEntry entry) => entry.archivePath);
.where((AssetBundleEntry entry) => entry.isStringEntry)
.map((AssetBundleEntry entry) => entry.archivePath);
}
}
......@@ -4,6 +4,7 @@
import 'dart:io';
import 'package:flutter_tools/src/asset.dart';
import 'package:flutter_tools/src/devfs.dart';
import 'package:path/path.dart' as path;
import 'package:test/test.dart';
......@@ -18,6 +19,8 @@ void main() {
String basePath;
MockDevFSOperations devFSOperations = new MockDevFSOperations();
DevFS devFS;
AssetBundle assetBundle = new AssetBundle();
assetBundle.entries.add(new AssetBundleEntry.fromString('a.txt', ''));
group('devfs', () {
testUsingContext('create local file system', () async {
tempDir = Directory.systemTemp.createTempSync();
......@@ -38,8 +41,6 @@ void main() {
testUsingContext('modify existing file on local file system', () async {
File file = new File(path.join(basePath, filePath));
file.writeAsBytesSync(<int>[1, 2, 3, 4, 5, 6]);
});
testUsingContext('update dev file system', () async {
await devFS.update();
expect(devFSOperations.contains('writeFile test bar/foo.txt'), isTrue);
});
......@@ -47,11 +48,29 @@ void main() {
File file = new File(path.join(basePath, filePath2));
await file.parent.create(recursive: true);
file.writeAsBytesSync(<int>[1, 2, 3, 4, 5, 6, 7]);
});
testUsingContext('update dev file system', () async {
await devFS.update();
expect(devFSOperations.contains('writeFile test foo/bar.txt'), isTrue);
});
testUsingContext('delete a file from the local file system', () async {
File file = new File(path.join(basePath, filePath));
await file.delete();
await devFS.update();
expect(devFSOperations.contains('deleteFile test bar/foo.txt'), isTrue);
});
testUsingContext('add file in an asset bundle', () async {
await devFS.update(assetBundle);
expect(devFSOperations.contains('writeFile test build/flx/a.txt'), isTrue);
});
testUsingContext('add a file to the asset bundle', () async {
assetBundle.entries.add(new AssetBundleEntry.fromString('b.txt', ''));
await devFS.update(assetBundle);
expect(devFSOperations.contains('writeFile test build/flx/b.txt'), isTrue);
});
testUsingContext('delete a file from the asset bundle', () async {
assetBundle.entries.clear();
await devFS.update(assetBundle);
expect(devFSOperations.contains('deleteFile test build/flx/b.txt'), isTrue);
});
testUsingContext('delete dev file system', () async {
await devFS.destroy();
});
......
......@@ -75,6 +75,8 @@ class MockDevFSOperations implements DevFSOperations {
final List<String> messages = new List<String>();
bool contains(String match) {
print('Checking for `$match` in:');
print(messages);
bool result = messages.contains(match);
messages.clear();
return result;
......@@ -96,6 +98,11 @@ class MockDevFSOperations implements DevFSOperations {
messages.add('writeFile $fsName ${entry.devicePath}');
}
@override
Future<dynamic> deleteFile(String fsName, DevFSEntry entry) async {
messages.add('deleteFile $fsName ${entry.devicePath}');
}
@override
Future<dynamic> writeSource(String fsName,
String devicePath,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment