| // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| library pub.barback.sources; |
| |
| import 'dart:async'; |
| |
| import 'package:barback/barback.dart'; |
| import 'package:path/path.dart' as path; |
| import 'package:watcher/watcher.dart'; |
| |
| import '../entrypoint.dart'; |
| import '../io.dart'; |
| import '../package.dart'; |
| import '../package_graph.dart'; |
| |
| /// Adds all of the source assets in the provided packages to barback and |
| /// then watches the public directories for changes. |
| /// |
| /// [watcherFactory] should return a [DirectoryWatcher] watching the given |
| /// directory for changes. |
| /// |
| /// Returns a Future that completes when the sources are loaded and the watchers |
| /// are active. |
| Future watchSources(PackageGraph graph, Barback barback, |
| WatcherType watcherType) { |
| return Future.wait(graph.packages.values.map((package) { |
| // If this package comes from a cached source, its contents won't change so |
| // we don't need to monitor it. `packageId` will be null for the application |
| // package, since that's not locked. |
| var packageId = graph.lockFile.packages[package.name]; |
| if (packageId != null && |
| graph.entrypoint.cache.sources[packageId.source].shouldCache) { |
| barback.updateSources(_listAssets(graph.entrypoint, package)); |
| return new Future.value(); |
| } |
| |
| // Watch the visible package directories for changes. |
| return Future.wait(_getPublicDirectories(graph.entrypoint, package) |
| .map((name) { |
| var subdirectory = path.join(package.dir, name); |
| if (!dirExists(subdirectory)) return new Future.value(); |
| |
| // TODO(nweiz): close these watchers when [barback] is closed. |
| var watcher = watcherType.create(subdirectory); |
| watcher.events.listen((event) { |
| // Don't watch files symlinked into these directories. |
| // TODO(rnystrom): If pub gets rid of symlinks, remove this. |
| var parts = path.split(event.path); |
| if (parts.contains("packages") || parts.contains("assets")) return; |
| |
| var id = new AssetId(package.name, |
| path.relative(event.path, from: package.dir)); |
| if (event.type == ChangeType.REMOVE) { |
| barback.removeSources([id]); |
| } else { |
| barback.updateSources([id]); |
| } |
| }); |
| return watcher.ready; |
| })).then((_) { |
| barback.updateSources(_listAssets(graph.entrypoint, package)); |
| }); |
| })); |
| } |
| |
| /// Adds all of the source assets in the provided packages to barback. |
| void loadSources(PackageGraph graph, Barback barback) { |
| for (var package in graph.packages.values) { |
| barback.updateSources(_listAssets(graph.entrypoint, package)); |
| } |
| } |
| |
| /// Lists all of the visible files in [package]. |
| /// |
| /// This is the recursive contents of the "asset" and "lib" directories (if |
| /// present). If [package] is the entrypoint package, it also includes the |
| /// contents of "web". |
| List<AssetId> _listAssets(Entrypoint entrypoint, Package package) { |
| var files = <AssetId>[]; |
| |
| for (var dirPath in _getPublicDirectories(entrypoint, package)) { |
| var dir = path.join(package.dir, dirPath); |
| if (!dirExists(dir)) continue; |
| for (var entry in listDir(dir, recursive: true)) { |
| // Ignore "packages" symlinks if there. |
| if (path.split(entry).contains("packages")) continue; |
| |
| // Skip directories. |
| if (!fileExists(entry)) continue; |
| |
| var id = new AssetId(package.name, |
| path.relative(entry, from: package.dir)); |
| files.add(id); |
| } |
| } |
| |
| return files; |
| } |
| |
| /// Gets the names of the top-level directories in [package] whose contents |
| /// should be provided as source assets. |
| Iterable<String> _getPublicDirectories(Entrypoint entrypoint, Package package) { |
| var directories = ["asset", "lib"]; |
| if (package.name == entrypoint.root.name) directories.add("web"); |
| return directories; |
| } |
| |
| /// An enum describing different modes of constructing a [DirectoryWatcher]. |
| abstract class WatcherType { |
| /// A watcher that automatically chooses its type based on the operating |
| /// system. |
| static const AUTO = const _AutoWatcherType(); |
| |
| /// A watcher that always polls the filesystem for changes. |
| static const POLLING = const _PollingWatcherType(); |
| |
| /// No directory watcher at all. |
| static const NONE = const _NoneWatcherType(); |
| |
| /// Creates a new DirectoryWatcher. |
| DirectoryWatcher create(String directory); |
| |
| String toString(); |
| } |
| |
| class _AutoWatcherType implements WatcherType { |
| const _AutoWatcherType(); |
| |
| DirectoryWatcher create(String directory) => |
| new DirectoryWatcher(directory); |
| |
| String toString() => "auto"; |
| } |
| |
| class _PollingWatcherType implements WatcherType { |
| const _PollingWatcherType(); |
| |
| DirectoryWatcher create(String directory) => |
| new PollingDirectoryWatcher(directory); |
| |
| String toString() => "polling"; |
| } |
| |
| class _NoneWatcherType implements WatcherType { |
| const _NoneWatcherType(); |
| |
| DirectoryWatcher create(String directory) => null; |
| |
| String toString() => "none"; |
| } |