Support for synchronizing assets onto a DevFS
diff --git a/packages/flutter_tools/lib/src/devfs.dart b/packages/flutter_tools/lib/src/devfs.dart
index 4ac4df2..de56314 100644
--- a/packages/flutter_tools/lib/src/devfs.dart
+++ b/packages/flutter_tools/lib/src/devfs.dart
@@ -9,23 +9,37 @@
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 @@
}
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 @@
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 @@
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;
}
@@ -92,6 +117,11 @@
}
@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,
String contents) async {
@@ -135,7 +165,11 @@
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 @@
}
}
}
+ 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 @@
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 @@
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 @@
}
}
- 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;