| // Copyright 2013 The Flutter Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| // @dart = 2.6 |
| import 'dart:async'; |
| |
| import 'package:meta/meta.dart'; |
| import 'package:path/path.dart' as path; |
| import 'package:watcher/watcher.dart'; |
| |
| enum PipelineStatus { |
| idle, |
| started, |
| stopping, |
| stopped, |
| error, |
| done, |
| } |
| |
| typedef PipelineStep = Future<void> Function(); |
| |
| /// Represents a sequence of asynchronous tasks to be executed. |
| /// |
| /// The pipeline can be executed by calling [start] and stopped by calling |
| /// [stop]. |
| /// |
| /// When a pipeline is stopped, it switches to the [PipelineStatus.stopping] |
| /// state and waits until the current task finishes. |
| class Pipeline { |
| Pipeline({@required this.steps}); |
| |
| final Iterable<PipelineStep> steps; |
| |
| Future<dynamic> _currentStepFuture; |
| |
| PipelineStatus get status => _status; |
| PipelineStatus _status = PipelineStatus.idle; |
| |
| /// Starts executing tasks of the pipeline. |
| /// |
| /// Returns a future that resolves after all steps have been performed. |
| Future<void> start() async { |
| _status = PipelineStatus.started; |
| try { |
| for (PipelineStep step in steps) { |
| if (status != PipelineStatus.started) { |
| break; |
| } |
| _currentStepFuture = step(); |
| await _currentStepFuture; |
| } |
| _status = PipelineStatus.done; |
| } catch (error, stackTrace) { |
| _status = PipelineStatus.error; |
| print('Error in the pipeline: $error'); |
| print(stackTrace); |
| } finally { |
| _currentStepFuture = null; |
| } |
| } |
| |
| /// Stops executing any more tasks in the pipeline. |
| /// |
| /// If a task is already being executed, it won't be interrupted. |
| Future<void> stop() { |
| _status = PipelineStatus.stopping; |
| return (_currentStepFuture ?? Future<void>.value(null)).then((_) { |
| _status = PipelineStatus.stopped; |
| }); |
| } |
| } |
| |
| /// Signature of functions to be called when a [WatchEvent] is received. |
| typedef WatchEventPredicate = bool Function(WatchEvent event); |
| |
| /// Responsible for watching a directory [dir] and executing the given |
| /// [pipeline] whenever a change occurs in the directory. |
| /// |
| /// The [ignore] callback can be used to customize the watching behavior to |
| /// ignore certain files. |
| class PipelineWatcher { |
| PipelineWatcher({ |
| @required this.dir, |
| @required this.pipeline, |
| this.ignore, |
| }) : watcher = DirectoryWatcher(dir); |
| |
| /// The path of the directory to watch for changes. |
| final String dir; |
| |
| /// The pipeline to be executed when an event is fired by the watcher. |
| final Pipeline pipeline; |
| |
| /// Used to watch a directory for any file system changes. |
| final DirectoryWatcher watcher; |
| |
| /// A callback that determines whether to rerun the pipeline or not for a |
| /// given [WatchEvent] instance. |
| final WatchEventPredicate ignore; |
| |
| /// Activates the watcher. |
| void start() { |
| watcher.events.listen(_onEvent); |
| } |
| |
| int _pipelineRunCount = 0; |
| Timer _scheduledPipeline; |
| |
| void _onEvent(WatchEvent event) { |
| if (ignore != null && ignore(event)) { |
| return; |
| } |
| |
| final String relativePath = path.relative(event.path, from: dir); |
| print('- [${event.type}] ${relativePath}'); |
| |
| _pipelineRunCount++; |
| _scheduledPipeline?.cancel(); |
| _scheduledPipeline = Timer(const Duration(milliseconds: 100), () { |
| _scheduledPipeline = null; |
| _runPipeline(); |
| }); |
| } |
| |
| void _runPipeline() { |
| int runCount; |
| switch (pipeline.status) { |
| case PipelineStatus.started: |
| pipeline.stop().then((_) { |
| runCount = _pipelineRunCount; |
| pipeline.start().then((_) => _pipelineDone(runCount)); |
| }); |
| break; |
| |
| case PipelineStatus.stopping: |
| // We are already trying to stop the pipeline. No need to do anything. |
| break; |
| |
| default: |
| runCount = _pipelineRunCount; |
| pipeline.start().then((_) => _pipelineDone(runCount)); |
| break; |
| } |
| } |
| |
| void _pipelineDone(int pipelineRunCount) { |
| if (pipelineRunCount == _pipelineRunCount) { |
| print('*** Done! ***'); |
| } |
| } |
| } |