utils.dart 8.52 KB
Newer Older
1 2 3 4 5
// 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';
6
import 'dart:convert';
7
import 'dart:math' show Random;
Devon Carew's avatar
Devon Carew committed
8 9

import 'package:crypto/crypto.dart';
10
import 'package:intl/intl.dart';
11
import 'package:quiver/time.dart';
Devon Carew's avatar
Devon Carew committed
12

13
import '../globals.dart';
14
import 'context.dart';
15
import 'file_system.dart';
16
import 'platform.dart';
17

18 19 20 21 22 23 24 25 26 27 28 29 30
final BotDetector _kBotDetector = const BotDetector();

class BotDetector {
  const BotDetector();

  bool get isRunningOnBot {
    return
      platform.environment['BOT'] == 'true' ||

          // https://docs.travis-ci.com/user/environment-variables/#Default-Environment-Variables
          platform.environment['TRAVIS'] == 'true' ||
          platform.environment['CONTINUOUS_INTEGRATION'] == 'true' ||
          platform.environment.containsKey('CI') || // Travis and AppVeyor
31

32 33
          // https://www.appveyor.com/docs/environment-variables/
          platform.environment.containsKey('APPVEYOR') ||
34

35 36
          // https://docs.aws.amazon.com/codebuild/latest/userguide/build-env-ref-env-vars.html
          (platform.environment.containsKey('AWS_REGION') && platform.environment.containsKey('CODEBUILD_INITIATOR')) ||
37

38 39
          // https://wiki.jenkins.io/display/JENKINS/Building+a+software+project#Buildingasoftwareproject-belowJenkinsSetEnvironmentVariables
          platform.environment.containsKey('JENKINS_URL') ||
40

41 42 43 44 45
          // Properties on Flutter's Chrome Infra bots.
          platform.environment['CHROME_HEADLESS'] == '1' ||
          platform.environment.containsKey('BUILDBOT_BUILDERNAME');
  }
}
46

47
bool get isRunningOnBot {
48 49
  final BotDetector botDetector = context?.getVariable(BotDetector) ?? _kBotDetector;
  return botDetector.isRunningOnBot;
50 51
}

Ian Hickson's avatar
Ian Hickson committed
52
String hex(List<int> bytes) {
53
  final StringBuffer result = new StringBuffer();
Ian Hickson's avatar
Ian Hickson committed
54 55 56 57 58
  for (int part in bytes)
    result.write('${part < 16 ? '0' : ''}${part.toRadixString(16)}');
  return result.toString();
}

Devon Carew's avatar
Devon Carew committed
59
String calculateSha(File file) {
Ian Hickson's avatar
Ian Hickson committed
60
  return hex(sha1.convert(file.readAsBytesSync()).bytes);
Devon Carew's avatar
Devon Carew committed
61
}
62

63 64 65 66 67 68 69 70 71 72 73 74
/// Convert `foo_bar` to `fooBar`.
String camelCase(String str) {
  int index = str.indexOf('_');
  while (index != -1 && index < str.length - 2) {
    str = str.substring(0, index) +
      str.substring(index + 1, index + 2).toUpperCase() +
      str.substring(index + 2);
    index = str.indexOf('_');
  }
  return str;
}

75 76 77 78 79 80
String toTitleCase(String str) {
  if (str.isEmpty)
    return str;
  return str.substring(0, 1).toUpperCase() + str.substring(1);
}

81 82 83
/// Return the plural of the given word (`cat(s)`).
String pluralize(String word, int count) => count == 1 ? word : word + 's';

84 85
/// Return the name of an enum item.
String getEnumName(dynamic enumItem) {
86 87
  final String name = '$enumItem';
  final int index = name.indexOf('.');
88 89 90
  return index == -1 ? name : name.substring(index + 1);
}

Devon Carew's avatar
Devon Carew committed
91
File getUniqueFile(Directory dir, String baseName, String ext) {
92
  final FileSystem fs = dir.fileSystem;
Devon Carew's avatar
Devon Carew committed
93 94 95
  int i = 1;

  while (true) {
96 97
    final String name = '${baseName}_${i.toString().padLeft(2, '0')}.$ext';
    final File file = fs.file(fs.path.join(dir.path, name));
Devon Carew's avatar
Devon Carew committed
98 99 100 101 102 103
    if (!file.existsSync())
      return file;
    i++;
  }
}

104
String toPrettyJson(Object jsonable) {
105
  return const JsonEncoder.withIndent('  ').convert(jsonable) + '\n';
106 107
}

108 109 110 111 112
/// Return a String - with units - for the size in MB of the given number of bytes.
String getSizeAsMB(int bytesLength) {
  return '${(bytesLength / (1024 * 1024)).toStringAsFixed(1)}MB';
}

113 114 115 116 117 118 119 120 121 122 123
final NumberFormat kSecondsFormat = new NumberFormat('0.0');
final NumberFormat kMillisecondsFormat = new NumberFormat.decimalPattern();

String getElapsedAsSeconds(Duration duration) {
  final double seconds = duration.inMilliseconds / Duration.MILLISECONDS_PER_SECOND;
  return '${kSecondsFormat.format(seconds)}s';
}

String getElapsedAsMilliseconds(Duration duration) {
  return '${kMillisecondsFormat.format(duration.inMilliseconds)}ms';
}
124

125 126 127
/// Return a relative path if [fullPath] is contained by the cwd, else return an
/// absolute path.
String getDisplayPath(String fullPath) {
128
  final String cwd = fs.currentDirectory.path + fs.path.separator;
129
  return fullPath.startsWith(cwd) ? fullPath.substring(cwd.length) : fullPath;
130 131
}

132 133 134 135 136 137 138 139 140 141 142 143 144 145
/// A class to maintain a list of items, fire events when items are added or
/// removed, and calculate a diff of changes when a new list of items is
/// available.
class ItemListNotifier<T> {
  ItemListNotifier() {
    _items = new Set<T>();
  }

  ItemListNotifier.from(List<T> items) {
    _items = new Set<T>.from(items);
  }

  Set<T> _items;

146 147
  final StreamController<T> _addedController = new StreamController<T>.broadcast();
  final StreamController<T> _removedController = new StreamController<T>.broadcast();
148 149 150 151 152 153 154

  Stream<T> get onAdded => _addedController.stream;
  Stream<T> get onRemoved => _removedController.stream;

  List<T> get items => _items.toList();

  void updateWithNewList(List<T> updatedList) {
155
    final Set<T> updatedSet = new Set<T>.from(updatedList);
156

157 158
    final Set<T> addedItems = updatedSet.difference(_items);
    final Set<T> removedItems = _items.difference(updatedSet);
159 160 161

    _items = updatedSet;

162 163
    addedItems.forEach(_addedController.add);
    removedItems.forEach(_removedController.add);
164 165 166 167 168 169 170 171
  }

  /// Close the streams.
  void dispose() {
    _addedController.close();
    _removedController.close();
  }
}
172 173

class SettingsFile {
174 175
  SettingsFile();

176 177 178 179 180
  SettingsFile.parse(String contents) {
    for (String line in contents.split('\n')) {
      line = line.trim();
      if (line.startsWith('#') || line.isEmpty)
        continue;
181
      final int index = line.indexOf('=');
182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198
      if (index != -1)
        values[line.substring(0, index)] = line.substring(index + 1);
    }
  }

  factory SettingsFile.parseFromFile(File file) {
    return new SettingsFile.parse(file.readAsStringSync());
  }

  final Map<String, String> values = <String, String>{};

  void writeContents(File file) {
    file.writeAsStringSync(values.keys.map((String key) {
      return '$key=${values[key]}';
    }).join('\n'));
  }
}
199 200 201 202 203

/// A UUID generator. This will generate unique IDs in the format:
///
///     f47ac10b-58cc-4372-a567-0e02b2c3d479
///
204
/// The generated UUIDs are 128 bit numbers encoded in a specific string format.
205 206 207 208
///
/// For more information, see
/// http://en.wikipedia.org/wiki/Universally_unique_identifier.
class Uuid {
209
  final Random _random = new Random();
210

211 212
  /// Generate a version 4 (random) UUID. This is a UUID scheme that only uses
  /// random numbers as the source of the generated UUID.
213 214
  String generateV4() {
    // Generate xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx / 8-4-4-4-12.
215
    final int special = 8 + _random.nextInt(4);
216 217 218 219 220

    return
      '${_bitsDigits(16, 4)}${_bitsDigits(16, 4)}-'
          '${_bitsDigits(16, 4)}-'
          '4${_bitsDigits(12, 3)}-'
221
          '${_printDigits(special, 1)}${_bitsDigits(12, 3)}-'
222 223 224 225 226 227 228 229 230 231 232
          '${_bitsDigits(16, 4)}${_bitsDigits(16, 4)}${_bitsDigits(16, 4)}';
  }

  String _bitsDigits(int bitCount, int digitCount) =>
      _printDigits(_generateBits(bitCount), digitCount);

  int _generateBits(int bitCount) => _random.nextInt(1 << bitCount);

  String _printDigits(int value, int count) =>
      value.toRadixString(16).padLeft(count, '0');
}
233 234

Clock get clock => context.putIfAbsent(Clock, () => const Clock());
235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273

typedef Future<Null> AsyncCallback();

/// A [Timer] inspired class that:
///   - has a different initial value for the first callback delay
///   - waits for a callback to be complete before it starts the next timer
class Poller {
  Poller(this.callback, this.pollingInterval, { this.initialDelay: Duration.ZERO }) {
    new Future<Null>.delayed(initialDelay, _handleCallback);
  }

  final AsyncCallback callback;
  final Duration initialDelay;
  final Duration pollingInterval;

  bool _cancelled = false;
  Timer _timer;

  Future<Null> _handleCallback() async {
    if (_cancelled)
      return;

    try {
      await callback();
    } catch (error) {
      printTrace('Error from poller: $error');
    }

    if (!_cancelled)
      _timer = new Timer(pollingInterval, _handleCallback);
  }

  /// Cancels the poller.
  void cancel() {
    _cancelled = true;
    _timer?.cancel();
    _timer = null;
  }
}
274 275 276 277 278 279 280 281 282 283 284

/// Returns a [Future] that completes when all given [Future]s complete.
///
/// Uses [Future.wait] but removes null elements from the provided
/// `futures` iterable first.
///
/// The returned [Future<List>] will be shorter than the given `futures` if
/// it contains nulls.
Future<List<T>> waitGroup<T>(Iterable<Future<T>> futures) {
  return Future.wait<T>(futures.where((Future<T> future) => future != null));
}